Skip to content

Snacks v2.19.0

Latest

Choose a tag to compare

@derekshreds derekshreds released this 29 Aug 02:22
65561f0

Snacks v2.19.0

Automated Media Library Transcoder

This release is all about one thing: the Luna integration. Snacks can now act as a secure, outbound-only connector between your Sonarr/Radarr libraries and Luna, so you can talk to your media library: ask Luna what your collection says about your taste, get recommendations grounded in what you actually own, find the gaps in a series run, and — only if you explicitly allow it — add the winners straight to Radarr or Sonarr from chat or voice. No port forwarding, no public hostname, no inbound tunnel, and a privacy model where paths, API keys, and URLs never leave your box.


✨ Luna integration

Snacks becomes a Luna connector for your enabled Sonarr and Radarr instances. Connect once from Settings → Connections → Luna, and Luna — on the web or iOS, in text chat or voice mode — can see your library the way you'd describe it to a friend and act on it the way you'd trust an assistant to.

What it allows

Read-only, out of the box (with the metadata-read permission):

  • "Look through my Radarr library and tell me which genres and directors I return to most."
  • "Based on my collection, suggest ten science-fiction movies not already in Radarr. Do not add anything yet."
  • "Find completed mystery series that fit my taste and show what Sonarr is missing."

Library changes (separate, explicit opt-in):

  • "Add The Matrix to Radarr, monitor it, and start a search."
  • "Recommend five movies, explain each choice, and wait for my approval before adding anything."

All of it works in Luna voice mode too — Luna acknowledges the request while Snacks works, then speaks the answer or completion status.

Supported actions

A deliberately small allow-list — task arguments can select ids and ordinary options, but can never supply a URL, API key, path, HTTP verb, or raw request body:

Capability Local operation Returned data
radarr.library.read Paged Radarr movie list title, year, public ids, genres, basic status
radarr.catalog.search Radarr lookup up to 20 path-free candidates
radarr.movie.add Idempotent add by TMDb id sanitized added/existing movie
sonarr.library.read Paged Sonarr series list title, year, public ids, genres, episode counts
sonarr.catalog.search Sonarr lookup up to 20 path-free candidates
sonarr.series.add Idempotent add by TVDb id sanitized added/existing series

Adds are idempotent by public id — asking twice returns the existing entry instead of erroring or duplicating. Unless a task supplies explicit ids, adds use the first accessible Arr root folder and first quality profile; optional root/profile ids can select among your local settings without ever revealing their paths.

Outbound-only by design

Snacks polls Luna for leased tasks (server-suggested cadence, clamped to 2–30s via LunaTaskWorker), executes them against your local Arr APIs, and returns a sanitized result. Nothing connects in: no port forwarding, no reverse proxy, no tunnel, no exposed endpoint. Production Snacks connects only to the pinned official URL https://veryluna.com — custom URLs and plain-HTTP endpoints require explicit env-var overrides that exist solely for the project owner's local integration testing.

Privacy model

  • Path-free, always — Arr base URLs, API keys, root-folder paths, file paths/names, and file sizes never enter a task result. Sanitization is structural (the executor builds the payload field by field), not a filter over raw responses.
  • Two independent permissionslibrary metadata reads and library changes are separate toggles on top of the master enable. Snacks rechecks the live settings for every leased task, so removing a permission takes effect immediately — even before Luna receives the next capability heartbeat.
  • Credentials handled once — sign-in sends your password to Luna a single time and never writes it to disk; only the scoped refresh session is persisted (via the secrets store, tightened to owner read/write on Unix). Disconnecting revokes the token server-side and deregisters the connector so Luna shows it offline immediately.
  • Nothing lingers — completed remote-task rows are hard-deleted the moment Luna consumes their result, a five-minute safety prune removes abandoned rows, and disconnecting or revoking deletes unfinished tasks. The connection can be reviewed or revoked any time from Luna's Settings → Connected apps on web or iOS.
  • Hard result bounds — 200-row library pages, 20 search candidates, and a 16MB cap on any single Arr response keep one remote task from buffering or exfiltrating a whole library.

Surfaces

  • Settings → Connections → Luna — enable polling, opt into reads/changes, sign in, and watch live connection status (online/offline, last error, account) without leaving the page.
  • Docs — a full Luna section in /docs/index.html and docs/LUNA_INTEGRATION.md covering setup, the capability table, the privacy contract, and example prompts.

Tests

  • LunaIntegrationTests (new) — library reads are paginated and structurally path-free; adds use local defaults but never return the root path; existing-series adds are idempotent; capabilities follow each explicit privacy permission; production accepts only the official URL (local testing requires both env overrides); connect persists only the scoped session and polls outbound.
  • Electron docs.test.cjs / frontend.test.cjs — the docs page carries the Luna section and the integrations panel wires the Luna connect/permission flows.

Files Changed

Luna integration

  • Snacks/Services/LunaConnectionService.cs (new) — session lifecycle (connect/refresh/disconnect/revoke), URL policy pinned to https://veryluna.com, outbound task-lease polling, scoped-secret persistence
  • Snacks/Services/LunaTaskExecutor.cs (new) — the sanitized task allow-list: paged library reads, catalog search, idempotent adds; result bounds and typed error codes
  • Snacks/Controllers/LunaConnectionController.cs (new), Snacks/Models/LunaConnectionState.cs (new), Snacks/Models/IntegrationConfig.csLunaIntegration config (enable, reads, changes), token-free status API
  • Snacks/Program.csLunaTaskWorker background service and the redirect-disabled LunaConnector HTTP client
  • Snacks/Services/ConfigFileService.cs — scoped-secret save path used by the Luna session
  • Snacks/Views/Shared/_IntegrationSettings.cshtml, Snacks/wwwroot/js/settings/panels/integrations-panel.js, luna-status.js, Snacks/wwwroot/js/api.js, Snacks/wwwroot/css/site.css — the Luna settings panel and live status
  • docs/LUNA_INTEGRATION.md (new), Snacks/wwwroot/docs/index.html — full integration documentation

Tests

  • Snacks.Tests/Integration/LunaIntegrationTests.cs — Luna privacy, capability, URL-policy, and session suite (new)
  • electron-app/tests/docs.test.cjs, frontend.test.cjs — Luna docs and settings-panel coverage

Version bumps

  • Snacks/Snacks.csproj<Version>2.19.0</Version> (single source of truth)
  • electron-app/package.json / package-lock.json, build-and-export.bat, Snacks.Tests/Settings/AppVersionTests.cs, Snacks/wwwroot/docs/index.html — synchronized via sync-version.mjs

Full documentation: README.md · /docs/index.html on a running instance