Full Claude control on your Elgato Stream Deck. One key per project, glowing with that
project's live Claude Code status — working, needs-you, done — plus an attention doorbell,
usage gauges, and deck-answerable permission prompts. Add app / URL / command shortcut keys,
give any key a colour, emoji, or the app's real logo, and build your whole board by talking
to it (jetstream chat) — live, no re-import. See SPEC.md.
Requires macOS 12+ and the Stream Deck app 6.9+. Windows isn't supported yet (the plugin manifest is macOS-only).
-
Claude Code, logged in with your subscription (
claude→/login). LeaveANTHROPIC_API_KEYunset: Jetstream strips it from anything it spawns so a keypress can never silently bill the metered API. -
Jetstream:
npm i -g @pimmesz/jetstream, thenjetstream install— it hands the packed plugin to the Stream Deck app; approve the install prompt there. (Updating?jetstream update— it fetches the latest published package from npmjs.org and reinstalls the plugin, so a barenpm i -gbehind a stale mirror can't strand you on an old version; override the registry withJETSTREAM_REGISTRY.) That's it — on first launch (once, recorded in a marker next toprojects.json) the plugin wires two hooks into~/.claude/settings.json, backing the file up first: the per-project status hook (lights the board) and the permission hook (lets Approve/Deny keys answer Claude's permission prompts from the deck — while the plugin runs, an unanswered prompt falls back to Claude's own dialog after ~90s). No terminal step. Restart any runningclaudesessions to pick the hooks up. Remove the hooks fromsettings.jsonand they stay removed — re-wire any time withjetstream setup. The usage/statusline hook is installed automatically on first launch if you have no statusline yet; if another tool already owns your statusline, hand the slot over withjetstream hooks install --replace-statusline(otherwise the usage gauge stays blank).With the plugin installed (step 2 above), set up your fleet — two ways, both via the
jetstreamCLI:jetstream chat # conversational — "3 repos in ~/dev: …", "add a Telegram key at a8" jetstream init # guided wizard — repos, theme/timings, a ready-made layout
chatlets you describe repos AND arrange keys in plain English (add app/URL/run shortcuts, recolour, rename, set emoji/logo icons), applied live.initasks for your repos (or scans a folder), your theme and timings, writesprojects.json(see below), wires the hooks — and can prebuild a ready-made key layout for your deck (Mini / MK.2 / XL) as aJetstream.streamDeckProfilein~/Downloadsyou double-click to import. The import installs it as a new profile on the device you pick in the dialog; your existing layout is never touched. (The layout file mirrors the profile format Elgato's own plugins ship, but treat it as experimental — dragging keys by hand always works.)The smaller pieces still exist:
setup(hooks + a starterprojects.jsontemplate),hooks install(only the hooks; the oldbin/hooks-install.jsstill works;--tool-detailadds the active-tool hooks,--replace-statuslinetakes the statusline slot from another tool so the usage gauge works),update(bump + reinstall in one step), anddoctor(read-only health check for when the board isn't lighting up).
Drag keys onto your deck: Project status (set a name + project path per key;
short-press opens the project folder in your editor (VS Code → Cursor → $EDITOR, else the OS
folder opener), long-press interrupts the session; done keys show the
change size, done 4m · +120/-40), Fleet roll-up (one always-visible key counting the whole
fleet — 3w 1! 2✓ — coloured by the worst state present, so "is anything waiting on me?" is
answerable even when projects outnumber keys), Attention (flashes if a request goes
unanswered), Usage gauge (5h/7d used + the sooner reset, resets 3h33m),
Approve / Deny (place one of each — they answer
the oldest pending Claude permission request straight from the deck; no press within ~90s → Claude
falls back to its normal dialog), and Jetstream settings (a press opens jetstream doctor;
its inspector toggles colour-blind mode and sets escalation/long-press/refresh). A waiting Project key reads
approve on deck when you can answer it from the deck, or answer in Claude when the question needs a typed reply.
The board reads without relying on colour: each key's sub-line names its state in words (done 4m,
approve on deck), and a corner glyph marks the exceptions — a stall, a failure, or a working key showing
its tool (Bash · 12m, where the glyph carries "working"). The settings key's high-contrast theme also
swaps the red/green pair for orange/blue.
Nothing is device-specific — you drag as many keys as your device has (Mini 6, MK.2 15, XL 32,
Neo). No fixed layout is required — drag keys wherever you like, or let jetstream init prebuild
a starting layout (Mini / MK.2 / XL); each Project key holds its own name+path, so everyone's
board is their own. (On the Stream Deck +, drag the Fleet dial onto an encoder: rotate scrubs the
fleet, the touchscreen shows the current project, a tap or short-press opens it, a long-touch or long-press interrupts.)
Define your whole fleet in one place instead of a placed key per repo — jetstream init
builds this file for you, or write it by hand. Jetstream reads
$XDG_CONFIG_HOME/jetstream/projects.json (else ~/.config/jetstream/projects.json;
%APPDATA%\jetstream\projects.json on Windows) at startup:
{
"projects": [{ "id": "falcon", "name": "Falcon", "path": "/Users/you/falcon" }]
}The Fleet roll-up and Attention doorbell then cover every repo in the file, so placed
Project keys become optional focused jump-to buttons rather than the only way the plugin learns
your repos. An optional "settings" block presets the plugin config: theme, longPressMs,
usageRefreshSec, escalateAfterSec, plus the two destructive-key opt-ins allowRunKeys and
allowStopKeys. Run jetstream doctor to check the file is parseable.
Stop-all and Run keys are OFF by default and do nothing until you opt in — the plugin's loopback endpoint is unauthenticated during the upgrade grace period, so a key planted through it must not be able to SIGINT your whole fleet or run a command. Turn them on deliberately:
{
"projects": [{ "id": "falcon", "name": "Falcon", "path": "/Users/you/falcon" }],
"settings": { "allowStopKeys": true, "allowRunKeys": true }
}Working keys can show the current tool (Bash · 12m) instead of just working 12m. It needs the
higher-overhead PreToolUse/PostToolUse hooks (a hook process per tool call), so it's opt-in:
node "<plugin folder>/bin/jetstream.js" hooks install --tool-detail- The usage gauge shows your interactive 5h/7d subscription windows.
- Watching status costs nothing; the hooks only talk to the plugin locally
(
127.0.0.1, never the network). The status hook reports lifecycle events; the permission hook additionally holds a pending prompt briefly so a deck key can answer it (falling back to Claude's own dialog after ~90s when unanswered).
Elgato ships an official MCP server (npm install -g @elgato/mcp-server, Stream Deck app
7.4+, enable MCP Deck in Preferences → General) that lets an AI assistant trigger
actions — but only ones you've placed on the dedicated MCP Actions profile; your other
profiles stay private. Jetstream keys work there like any action, so "approve the pending
Claude prompt" by voice/text is possible. Two caveats: the MCP server can trigger keys,
never build layouts (that's what jetstream init is for), and its actions fire without
per-call confirmation — use the stdio transport, skip the ngrok/HTTP modes, and only place
keys you'd let an AI press.
pnpm install # install workspace deps
pnpm check # build the shared cores, then typecheck + test every package
pnpm build # build:cores + bundle the plugin into the .sdPlugin
pnpm --filter '@pimmesz/jetstream' run validate # Elgato manifest validation
pnpm --filter '@pimmesz/jetstream' run repack # rebuild, validate, and produce the .streamDeckPlugin (pack --force)