Files
2026-09-09 00:02:16 -04:00

3.9 KiB

AGENTS.md

Context for OpenCode sessions working on the Dynamic Deck Stream Deck plugin (com.ryan-kegel.dynamic-deck).

What this is

An Elgato Stream Deck plugin (SDK v3, Node.js 24) built with TypeScript + Rollup. Some actions are Windows-only because they shell out to PowerShell and use Win32 P/Invoke via the koffi FFI (user32.dll, kernel32.dll). Other actions are platform-agnostic.

Commands

  • npm run build — rollup production build to com.ryan-kegel.dynamic-deck.sdPlugin/bin/plugin.js (terser-minified, no sourcemap).
  • npm run watch — rollup watch with sourcemaps; on every rebuild triggers streamdeck restart com.ryan-kegel.dynamic-deck.
  • node generate-assets.cjs — writes 1x1 placeholder PNGs into imgs/plugin/ and imgs/actions/. Run once after cloning if those images are missing.
  • There is no test runner, linter, or typecheck script. The only verification is npm run build (rollup runs the TS plugin, so type errors will surface here).

Source layout

  • src/plugin.ts — entrypoint. Registers actions, wires AppMonitor (1s polling for foreground-window changes) and AppTrackerService (persisted to app-stats.json in cwd), handles SIGINT/SIGTERM to flush stats, then streamDeck.connect()s.
  • src/actions/<name>.ts — one SingletonAction subclass per Stream Deck action, each @action({ UUID: "com.ryan-kegel.dynamic-deck.<name>" }).
  • src/services/ — app-monitor.ts (Win32 FFI foreground tracking), app-tracker.ts (launch stats / ranking), blacklist-manager.ts.
  • src/utils/app-launcher.ts — Start Menu .lnk scanning + 256x256 icon extraction via PowerShell + C# PrivateExtractIcons P/Invoke.
  • src/ui/<action>.html + *.js — Property Inspector assets, copied as-is to the .sdPlugin bundle.
  • com.ryan-kegel.dynamic-deck.sdPlugin/ — the built plugin bundle (output). manifest.json, bin/plugin.js, copied ui/, imgs/, scripts/, services/, utils/. This is what Stream Deck loads.

Conventions and gotchas

  • Bundle shape is non-obvious. rollup.config.mjs copies src/{ui,imgs,scripts,services,utils} into the .sdPlugin/ folder alongside the built bin/plugin.js. Edits to those files in the source tree won't appear until a rebuild copies them. The TS files copied under services/ and utils/ are not the ones Rollup bundles — they are inert copies; the bundled code is what ends up in bin/plugin.js.
  • RollupWatchPlugin emits {"type":"module"} into bin/package.json so the host can import() the bundle. Don't hand-edit that file.
  • manifest.json is treated as a watched file; changes trigger a rebuild.
  • Several actions are currently commented out in plugin.ts (e.g. IncrementCounter, EcobeeModeAction, EcobeeAction). To re-enable one, uncomment both the import and the registerAction line.
  • The app-stats.json tracker file is written next to process.cwd() — i.e. inside the .sdPlugin/ folder at runtime. Don't delete it without expecting reset ranking data.
  • launch-app.ts uses ev.action.setTitle() to write to Stream Deck's built-in Title field — do not add a custom title <input> in launch-app.html, you'll get two fields (see README §2).
  • node engine is pinned to v24 via manifest.json ("Debug": "enabled"); debug by attaching VS Code to the running plugin process (the "Attach to Plugin" launch config in .vscode/launch.json).
  • tsconfig.json extends @tsconfig/node20 and uses customConditions: ["node"] + moduleResolution: "Bundler" to match Rollup's resolution mode.

Things explicitly NOT to do

  • Don't run tsc directly — there's no script and no outDir. The TS plugin runs as part of Rollup.
  • Don't add a test / lint script "just in case" — verify with the user first; none have been configured and the user may not want them.
  • Don't assume macOS support exists for app launching features — app-launcher.ts and app-monitor.ts use Windows-specific APIs.