{
  "markdown": "<p align=\"center\">\n  <img src=\"docs/logo.png\" width=\"88\" alt=\"\">\n</p>\n\n<h1 align=\"center\">Stream Deck Agent Cockpit</h1>\n\n<p align=\"center\">\n  Hardware controls for Claude Code, Codex, Pi, JCode, and any other terminal agent.<br>\n  Standalone, local, and evidence-backed.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://cskwork.github.io/streamdeck-agent-cockpit/\"><strong>Landing page</strong></a> ·\n  <a href=\"INSTALL.md\"><strong>Install</strong></a> ·\n  <a href=\"skills/streamdeck-agent-cockpit/SKILL.md\"><strong>SKILL.md</strong></a> ·\n  <a href=\"CHANGELOG.md\"><strong>Changelog</strong></a>\n</p>\n\n<p align=\"center\">\n  <img alt=\"license\" src=\"https://img.shields.io/badge/license-MIT-10B981\">\n  <img alt=\"version\" src=\"https://img.shields.io/badge/version-3.2.0-10B981\">\n  <img alt=\"python\" src=\"https://img.shields.io/badge/python-3.9%2B%20stdlib%20only-10B981\">\n  <img alt=\"no mcp\" src=\"https://img.shields.io/badge/MCP-not%20required-10B981\">\n</p>\n\n<p align=\"center\">\n  <strong>English</strong> · <a href=\"README.ko.md\">한국어</a>\n</p>\n\n---\n\nA single portable skill and reference runtime for turning Stream Deck into a local cockpit for Claude Code, Codex, Pi, JCode, and other terminal-based agents.\n\n**No MCP server is required.** The default implementation uses a loopback-only Python daemon, predeclared local commands, and either generated launchers or a Stream Deck SDK plugin.\n\n## Quick install\n\n```bash\nclaude plugin marketplace add cskwork/streamdeck-agent-cockpit\nclaude plugin install streamdeck-agent-cockpit@streamdeck-agent-cockpit\n```\n\nCodex, Gemini CLI, Cursor, OpenCode, Amp, Antigravity, and manual installs are all in\n[INSTALL.md](INSTALL.md).\n\n## What is standalone\n\n```text\n┌──────────────────────────────────────────────────────────────┐\n│ Stream Deck                                                  │\n│  A. built-in Open action → generated launcher               │\n│  B. local Agent Cockpit plugin → dynamic key/dial UI         │\n└───────────────────────┬──────────────────────────────────────┘\n                        │ authenticated localhost API\n┌───────────────────────▼──────────────────────────────────────┐\n│ cockpitd                                                     │\n│  config · command allowlist · state TTL · adapter registry   │\n└───────────────────────┬──────────────────────────────────────┘\n                        │ argv execution, never remote MCP\n┌───────────────────────▼──────────────────────────────────────┐\n│ tmux / terminal / agent CLI                                  │\n│  Claude Code · Codex · Pi · JCode · custom commands          │\n└──────────────────────────────────────────────────────────────┘\n```\n\n`streamdeck-mcp` and AgentDeck are not installed, imported, called, or expected by the runtime. They may be studied as prior art, but the skill's operation does not depend on them.\n\n## Modes\n\n| Mode | Requirements | Best for | Limitation |\n|---|---|---|---|\n| Launcher-only | Stream Deck app, Python 3.9+, configured terminal tools | Static tap-to-launch/focus actions | No live label/icon updates or dial events |\n| Native plugin | Above plus current official Stream Deck SDK toolchain | Live state, dynamic visuals, hold, dials, Property Inspector | Requires building/installing a local plugin |\n\nThe daemon and CLI use only Python's standard library.\n\n## Repository layout\n\n```text\nstreamdeck-agent-cockpit/\n├── README.md · INSTALL.md · CHANGELOG.md · LICENSE · VERSION\n├── .claude-plugin/          # Claude Code plugin + marketplace manifests\n├── .codex-plugin/           # Codex plugin manifest\n├── .agents/plugins/         # agents marketplace manifest\n├── .cursor/skills/…         # Cursor mirror of SKILL.md\n├── gemini-extension.json    # Gemini CLI extension (context: GEMINI.md)\n├── docs/index.html          # landing page (GitHub Pages)\n└── skills/streamdeck-agent-cockpit/\n    ├── SKILL.md\n    ├── assets/\n    │   ├── cockpit.example.json\n    │   ├── cockpit.live-sessions.example.json\n    │   └── cockpit.schema.json\n    ├── bin/\n    │   ├── cockpitd.py\n    │   ├── cockpitctl.py\n    │   ├── focus_tmux.py\n    │   ├── report_state.py\n    │   ├── slotclaims.py            # slot bookkeeping for attached sessions\n    │   ├── claim_probe.py           # coarse probe for a claimed slot\n    │   ├── focus_terminal.py        # iTerm2 / Apple Terminal focus by tty\n    │   ├── claude_hook.py           # Claude Code hook → semantic state\n    │   └── install_claude_hooks.py  # append-only hook registration\n    ├── scripts/\n    │   ├── generate_launchers.py\n    │   ├── install_runtime.py\n    │   ├── install_skill.py\n    │   ├── probe_environment.py\n    │   ├── smoke_test.py\n    │   └── validate_cockpit.py\n    ├── templates/streamdeck-plugin/\n    ├── references/\n    ├── evals/\n    └── tests/\n```\n\nEvery command below that starts with `python3 scripts/…` runs from\n`skills/streamdeck-agent-cockpit/`.\n\n## Install the skill manually\n\nFor plugin-manager installs (Claude Code, Codex, Gemini CLI, `npx skills`, agy), see\n[INSTALL.md](INSTALL.md). To copy the skill into a skills directory yourself:\n\n```bash\ncd skills/streamdeck-agent-cockpit\n\n# preview all supported locations\npython3 scripts/install_skill.py --target all --dry-run\n\n# install\npython3 scripts/install_skill.py --target all\n```\n\nSupported targets:\n\n| Target | Destination |\n|---|---|\n| `claude` | `~/.claude/skills/streamdeck-agent-cockpit` |\n| `agents` | `~/.agents/skills/streamdeck-agent-cockpit` |\n| `jcode` | convenience default `~/.jcode/skills/streamdeck-agent-cockpit`; override with `--destination` when the installed build uses a different discovery path |\n| `all` | all unique destinations above |\n\nUse `--destination /verified/local/skills/path` when a harness uses another discovery directory. Use `--mode symlink` for an editable development install. Existing destinations are refused unless `--force` is supplied; forced replacement first creates a timestamped backup.\n\n## Install the local runtime\n\n```bash\ncd skills/streamdeck-agent-cockpit\npython3 scripts/probe_environment.py --json   # inspect before assuming anything\npython3 scripts/install_runtime.py\n```\n\nThis creates:\n\n```text\n~/.agent-cockpit/\n├── bin/\n├── cockpit.json\n├── state.json       # created as needed\n└── token            # generated by the daemon with mode 0600\n```\n\nThe installer does not register a startup service or modify Stream Deck profiles.\n\n## Configure sessions and controls\n\nEdit `~/.agent-cockpit/cockpit.json`. The included example defines one named `tmux` session for each agent:\n\n- `session.claude.main`\n- `session.codex.main`\n- `session.pi.main`\n- `session.jcode.main`\n\nVerify the actual command names and flags installed on the machine:\n\n```bash\nclaude --help\ncodex --help\npi --help\njcode --help\n```\n\nThen validate:\n\n```bash\npython3 ~/.agent-cockpit/bin/validate_cockpit.py \\\n  ~/.agent-cockpit/cockpit.json\n```\n\n## Start and inspect the daemon\n\n```bash\npython3 ~/.agent-cockpit/bin/cockpitd.py \\\n  --config ~/.agent-cockpit/cockpit.json\n```\n\nIn another terminal:\n\n```bash\npython3 ~/.agent-cockpit/bin/cockpitctl.py \\\n  --config ~/.agent-cockpit/cockpit.json health\n\npython3 ~/.agent-cockpit/bin/cockpitctl.py \\\n  --config ~/.agent-cockpit/cockpit.json controls\n```\n\nInvoke a configured tap:\n\n```bash\npython3 ~/.agent-cockpit/bin/cockpitctl.py \\\n  --config ~/.agent-cockpit/cockpit.json \\\n  invoke session.claude.main --gesture tap\n```\n\nA hold-confirmed interrupt is explicit:\n\n```bash\npython3 ~/.agent-cockpit/bin/cockpitctl.py \\\n  --config ~/.agent-cockpit/cockpit.json \\\n  invoke session.claude.main --gesture longPress --confirm\n```\n\n## Launcher-only setup\n\nGenerate platform launchers:\n\n```bash\npython3 ~/.agent-cockpit/bin/generate_launchers.py \\\n  --config ~/.agent-cockpit/cockpit.json \\\n  --output ~/.agent-cockpit/launchers\n```\n\nIn the Stream Deck application, place a built-in **Open** action and select the launcher for the desired control. This path is fully independent and does not compile a plugin. It supports tap actions only.\n\n## Dynamic plugin setup\n\nUse the current official Stream Deck SDK to create a local plugin scaffold. Apply the files in [`skills/streamdeck-agent-cockpit/templates/streamdeck-plugin/`](skills/streamdeck-agent-cockpit/templates/streamdeck-plugin/) as described in its README. Each action instance stores only a logical `controlId` and contacts the local daemon for state and invocation.\n\nThe plugin must not read or rewrite Stream Deck's internal profile database. Users place the action normally or install an optional profile owned by this plugin.\n\nIt also cannot generically inspect or invoke arbitrary third-party plugin actions. Combine those actions manually in Stream Deck, or connect to the underlying service only when that service/plugin exposes a documented local API.\n\n## Honest progress reporting\n\nInfrastructure can verify that a tmux session exists, but that does not prove an agent is running, waiting, blocked, or done. Without an event source, the UI shows only coarse state.\n\nAn agent hook or workflow can report semantic state:\n\n```bash\npython3 ~/.agent-cockpit/bin/report_state.py \\\n  --config ~/.agent-cockpit/cockpit.json \\\n  --session session.codex.main \\\n  --state running \\\n  --label \"Reviewing changes\" \\\n  --ttl 180\n```\n\nLater:\n\n```bash\npython3 ~/.agent-cockpit/bin/report_state.py \\\n  --config ~/.agent-cockpit/cockpit.json \\\n  --session session.codex.main \\\n  --state needs_attention \\\n  --label \"Approval required\" \\\n  --ttl 600\n```\n\nWhen a report expires, the daemon falls back to coarse adapter state. A percentage is accepted only when explicitly reported by a real workflow.\n\n## Sessions you already have open\n\nThe sections above cover sessions the cockpit launches. Agent work usually already runs in terminal tabs you opened yourself, and those can appear on the deck too — with live state, and without moving them into tmux.\n\nThe daemon only accepts reports for sessions declared in `cockpit.json`, so a running session cannot register itself. Instead, predeclare a fixed number of **slots** and let a Claude Code hook bind live sessions to them. Start from [`cockpit.live-sessions.example.json`](skills/streamdeck-agent-cockpit/assets/cockpit.live-sessions.example.json), which pairs four attached slots with one tmux launch control:\n\n```bash\ncp skills/streamdeck-agent-cockpit/assets/cockpit.live-sessions.example.json \\\n   ~/.agent-cockpit/cockpit.json\npython3 ~/.agent-cockpit/bin/validate_cockpit.py ~/.agent-cockpit/cockpit.json\n```\n\nRegister the hook bridge — append-only, idempotent, and previewable:\n\n```bash\npython3 ~/.agent-cockpit/bin/install_claude_hooks.py --dry-run\npython3 ~/.agent-cockpit/bin/install_claude_hooks.py\n```\n\nBack up your settings file before the first write. State then comes from hook events only:\n\n| Hook event | Key shows |\n|---|---|\n| `SessionStart`, `Stop` | `IDLE` |\n| `UserPromptSubmit` | `RUN` |\n| `Notification` (permission, idle, elicitation) | `CHECK` |\n| `SessionEnd` | slot released, key returns to `OFF` |\n\nWant finer detail? `--extended` also registers the tool, permission, elicitation, subagent, task, and compaction events, so a key can show `BLOCKED` when permission is denied and `FAILED` when a turn ends in error. The trade is that the bridge then runs on every tool call, so start without it:\n\n```bash\npython3 ~/.agent-cockpit/bin/install_claude_hooks.py --extended --dry-run\n```\n\nEach key label carries the session's project directory name, never prompt text or model output.\n\nTapping a slot focuses the owning pane. On macOS `focus_terminal.py` supports iTerm2 and Apple Terminal, matching on the tty recorded when the slot was claimed. Windows Terminal has no scriptable tty, so it is addressed by exact tab title instead:\n\n```bash\npython3 ~/.agent-cockpit/bin/focus_terminal.py --tab-title \"Claude · Main\"\n```\n\nKnown limits of this path, all deliberate:\n\n- **Sessions already running when you install the bridge stay invisible** until they restart.\n- **Slots are finite.** When all are held by live sessions, a new one is ignored rather than evicting someone.\n- **No interrupt gesture on attached slots.** There is no supported way to send a scoped `Ctrl-C` through terminal automation, so interrupt stays on tmux-backed sessions where `tmux send-keys` is exact.\n- **Slot discovery is macOS only.** Binding a running session to a slot depends on `ps` ancestry and AppleScript. The Windows Terminal focus path works, but the title is something you set rather than something discovered, so it proves nothing about the session behind that tab.\n- **Terminal titles are never scraped.** They look like a usable signal but cannot separate \"thinking\" from \"waiting for approval\".\n\n## Verification\n\nFrom `skills/streamdeck-agent-cockpit/`:\n\n```bash\npython3 -m compileall -q bin scripts tests\npython3 -m unittest discover -s tests -v\npython3 scripts/validate_cockpit.py assets/cockpit.example.json\npython3 scripts/validate_cockpit.py assets/cockpit.live-sessions.example.json\npython3 scripts/smoke_test.py\n```\n\nPhysical-device behavior still requires testing in the Stream Deck application and on the target terminal. See [`skills/streamdeck-agent-cockpit/references/verification.md`](skills/streamdeck-agent-cockpit/references/verification.md).\n\n## Deliberate limitations\n\n- Launcher-only mode cannot display live state, distinguish hold, or process dial input.\n- The reference plugin template must be adapted and built with the current official SDK; no prebuilt plugin binary is included.\n- The official plugin boundary does not provide a safe generic API for editing arbitrary profiles or controlling unrelated third-party plugin actions.\n- Terminal focus behavior is terminal-specific and requires on-device verification.\n- Without a hook/RPC/workflow report, session state is coarse only.\n- Attached sessions occupy a fixed number of slots and carry no interrupt gesture; the reference hook bridge covers Claude Code only, and the bundled probe and focus helpers are macOS-only.\n\n## Security boundary\n\n- Loopback binding by default.\n- Random token stored in a local mode-0600 file.\n- No raw command endpoint.\n- Command argv arrays with `shell=False`.\n- No command output returned unless a future implementation deliberately adds a reviewed redaction path.\n- Confirmed hold required for the example interrupt actions.\n- No arbitrary Stream Deck profile edits.\n- No credentials in cockpit JSON, launchers, button settings, icons, or logs.\n\n## Uninstall\n\nStop the daemon, remove the action/profile owned by Agent Cockpit through the Stream Deck application, uninstall the local plugin if installed, and delete:\n\n```bash\nrm -rf ~/.agent-cockpit\nrm -rf ~/.claude/skills/streamdeck-agent-cockpit\nrm -rf ~/.agents/skills/streamdeck-agent-cockpit\nrm -rf ~/.jcode/skills/streamdeck-agent-cockpit\n```\n\nThis does not remove or alter unrelated profiles or third-party actions.\n",
  "bytes": 14837,
  "sha": "63b26876dcab81d792affda89feca58dbf4930107dea3085c648c92cbd4c4419",
  "repo_slug": "cskwork/streamdeck-agent-cockpit",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_cskwork_streamdeck_agent_cockpit_935bb05e/readme"
}