Imported from Hyperclaw79/seanime-download-notifier (
AGENTS.md). Install upstream withnpx skills add Hyperclaw79/seanime-download-notifier. Copyright stays with the author.
AGENTS.md
Mission and product boundary
Seanime Download Notifier sends completion notifications for torrents queued by Seanime's Auto Downloader. It is not a generic torrent-client monitor, a manual-download notifier, or a general Seanime event router.
- Repository:
Hyperclaw79/seanime-download-notifier - Production ID:
seanime-download-notifier - Development ID:
seanime-download-notifier-dev - Name:
Seanime Download Notifier - Manifest description:
Sends notifications when Seanime-tracked torrent downloads complete.
Discord is the first provider, not the product identity. Core behavior, manifests, and shared UI language must remain provider-neutral.
Source of truth and generated files
seanime-download-notifier.jsonis generated for release artifacts, ignored, and must not be treated as source.seanime-download-notifier-dev.example.jsonis the committed development template.scripts/manifest-shared.tsowns shared manifest fields and URL derivation.seanime-download-notifier-dev.jsonis generated, machine-local, ignored, and must not be treated as source.dist/plugin.jsanddist/plugin.dev.jsare generated and ignored.docs/apiis generated by TypeDoc and ignored.
Run npm run manifest:dev after a manifest-affecting change. For local Denshi testing, copy the result to %APPDATA%\Seanime\extensions\seanime-download-notifier-dev.json; the exact filename is required for Reload Plugin. Keep manifest URLs derived from the selected raw and release hosts; do not hand-mix unrelated repository hosts. Never publish local paths, webhook URLs, or machine-specific data.
The released production manifest is Seanime's stable install/update endpoint. Its manifest and payload URLs must target immutable release assets for the matching package.json version. Tag CI reuses the verified commit build, then publishes the bundle, manifest, documentation, and checksums. Action-run artifacts are temporary diagnostics and must never be used as install URLs.
Branching, remotes, and promotion
developis the integration branch.mainis the release branch.- External GitHub contributors should fork the repository and open pull requests against
develop. - Push routine maintainer work to Forgejo
developfirst. Treat Forgejo as the beta/RC mirror. - Do not push to GitHub unless the user explicitly asks. GitHub is the public/community publishing target.
- Promote changes to
mainthrough pull requests after validation ondevelop. - Use normal commits going forward. Do not amend, squash, rebase, or preserve a single-commit history unless the user explicitly asks.
Build workflows run on pull requests targeting develop or main, and on direct pushes to those two long-lived branches. This keeps PR validation and post-merge integrated-commit validation while avoiding full CI for every feature-branch push.
Runtime architecture
src/plugin.ts must stay a small registration entrypoint. It defines shared provider factories, then directly registers the isolated Auto Downloader hooks and UI callback.
Seanime/Denshi evaluates $ui.register(...) and hook callbacks in isolated Goja JavaScript runtimes. Inside a registered callback body:
- do not call an imported helper;
- do not reference a module-scope constant;
- do not depend on an outer closure;
- define required constants and helpers inside the callback;
- use only Goja-compatible JavaScript, not Node APIs.
This is why modular provider factories are exposed with $shared.define(...) and recreated inside the UI runtime with $shared.use(...). Do not reintroduce $ui.register((ctx) => startPlugin(ctx)). Tests in test/unit/plugin-entrypoint.test.ts inspect bundled function bodies and must be extended when a new isolated callback or provider factory is added.
Cross-runtime communication and persistence
$storeis for transient hook-to-UI signals such as poll requests and simulation snapshots.$storageis for durable state and provider configuration.- Torrent state key:
download-notifier-state-v1. - Provider config key:
download-notifier-provider-config-v1.
Hook runtimes must not send notifications directly. They record or signal work; the UI runtime owns polling and delivery.
Ownership, completion, and track isolation
onAutoDownloaderAfterDownloadTorrent means queued/added, not completed. Use it only to establish ownership of the torrent hash and Auto Downloader metadata. Poll ctx.torrentClient.getTorrents(), because completed torrents may not appear in active-only results. Completion is progress >= 1, regardless of seeding, paused, stopped, or other status.
Never notify for arbitrary or manually added torrents. Normalize hashes before matching. Every logical track must have its own identity token; before merging a poll result or marking delivery, reload state and confirm the current record still has the expected token. A stale async poll, repeated simulation, or reused hash must not mutate a newer/different track.
Mark a record notified only after every enabled provider succeeds. On any failure, preserve it as pending, increment retry metadata, and retry later. Retain successfully notified records for de-duplication and remove them only through retention cleanup.
Development simulation
Seanime's native Auto Downloader Simulation is the user-facing trigger; do not add a separate smoke-test button. Development smoke delivery requires its development preferences. The optional empty-run fallback requires all three:
devEnableSimulationSmokeModedevAllowSimulationNotificationsdevUseEmptyNativeSimulationFallback
The run-completed hook may only signal the UI through $store. The UI chooses an existing completed torrent as a snapshot, creates a temporary smoke track, preserves non-smoke records, and uses the normal poll/provider pipeline. Production builds must compile this behavior out. Smoke notifications must clearly say development smoke test; fallback notifications must also say native simulation fallback.
Provider architecture
Provider configuration, field definitions, normalization, readiness, payload construction, and transport belong under src/providers/. ui-runtime.ts owns generic building blocks only: adapter registration, provider document storage, generated field rendering, generic save/delete/test actions, and delivery dispatch by provider type.
Root manifest preferences are global/bootstrap settings only. Never add Discord or another provider's fields to userConfig.fields. Provider cards are generated from adapter field schemas and stored as records shaped like:
{
"id": "discord-main",
"type": "discord",
"enabled": true,
"label": "Discord",
"config": {}
}
To add a provider type:
- Extend the provider ID/types and implement its definition and isolated adapter factory under
src/providers/. - Make the factory self-contained, register it with a stable
$shared.define(...)key insrc/plugin.ts, and include it in the shared provider catalog. - Keep
ui-runtime.tscatalog-driven; it must not name or load an individual provider or copy its config fields or endpoint logic. - Add only the network permissions that transport needs.
- Add provider unit tests, bundle/isolation regression coverage, and Mock/E2E UI coverage.
Plugin UI and Mock parity
The actual plugin webview contains provider management, runtime status, tracked-torrent delivery state, a bounded/redacted in-memory activity log, and development status/actions in development builds. Global Preferences are Seanime-generated and do not belong inside the plugin webview. Tracking and activity panels are read-only operational views; never expose torrent hashes, content paths, webhook URLs, or other secrets there.
The normal Mock route must visually and behaviorally mirror the actual webview. ?harness=1 may add clearly Mock-only mutation controls needed by Playwright, but the tracking/activity panels themselves remain visible normally. Production mode must hide all development-only controls while retaining provider webhook testing and operational panels.
Testing and validation
- Pure logic: Vitest unit tests under
test/unit/. - Seanime lifecycle and storage: integration tests under
test/integration/. - Isolation, registrations, and provider boundaries:
test/unit/plugin-entrypoint.test.ts. - Visual behavior and user flows: Playwright in
mock-seanime/tests/.
Relevant commands:
npm run lint
npm run typecheck
npm test
npm run test:coverage
npm run test:e2e
npm run build:plugin
npm run mock:build
npm run manifest:validate
npm run docs
npm run build
Run focused tests while iterating, then the full relevant suites. Missing Playwright Chromium is environment setup (npx playwright install chromium), not an application failure. For UI changes, validate the normal development and production Mock routes, not only harness mode.
Repository map
src/plugin.ts Small Seanime registration entrypoint
src/core/ Tracking, polling, retention, scheduler logic
src/providers/ Provider contracts, config schemas, transports
src/seanime/isolated/ Self-contained Denshi callbacks
mock-seanime/src/ Visual reference and local harness
mock-seanime/tests/ Playwright E2E
scripts/ Manifest generation and validation
test/unit/ Pure logic and bundle-boundary tests
test/integration/ Mocked Seanime lifecycle tests
assets/ Public logo and screenshots
docs/api/ Generated TypeDoc output (ignored)
.github/workflows/ Public GitHub CI and release automation
.forgejo/workflows/ Required build and tag-publish automation
Coding and review constraints
- Preserve unrelated working-tree changes.
- Do not log secrets or full webhook URLs.
- Content paths remain excluded by default.
- Do not assume Reload Plugin disappearance is a plugin exception; verify filename and logs first.
- Do not mistake a missing browser binary for an E2E regression.
- Do not commit generated production manifests, development manifests, bundles, or generated API docs.
- When a Seanime API is uncertain, inspect upstream declarations/source instead of guessing.
- Before handing off, report exact files changed, commands run, results, and any manual Denshi step still required.