The complete manual for the cockpit, the approval inbox, employee chat, Telegram remote control, and configuration. For the flow editor see the dedicated chapter: docs/CANVAS.md. For threat model and guard details see SECURITY.md.
SquadCue is local-first mission control for AI CLI agents: a FastAPI server
(server.py) plus a single-file cockpit UI (static/cockpit.html) served at
http://127.0.0.1:8899.
Core vocabulary:
claude -p --resume <session-id> underneath), so it keeps its
own conversation memory, working directory, and history. You chat with it, rename it,
pin it, and rescue it after a reboot.runs/<id>/ with an
append-only event log, so it is auditable and replayable.Trust model in three sentences: the server refuses to start on a non-loopback host, and there is deliberately no authentication layer until one exists. Any local process can call the API — loopback binding is the boundary. Prompts go to your AI provider (as with any AI CLI), but the control plane and all its state stay on your machine.
Quickstart (full version in the README):
git clone https://github.com/hsienchuc/squadcue && cd squadcue
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python server.py # -> http://127.0.0.1:8899
Requirements: Python 3.11+ and at least one AI CLI on PATH — Claude Code (primary), Codex CLI (optional red-team engine).
What you’ll see on first launch. The Ops tab is mostly empty (no runs yet). The
Employees tab shows a disclosure panel first — session-derived endpoints (discovery,
chat history, chat send) stay off until you accept: SquadCue
asks permission to scan claude_projects (default ~/.claude/projects — the directory
where the Claude Code CLI itself stores conversation logs), and states exactly what a
scan does: glob session *.jsonl files, read the first 80 lines of up to 40 files
modified in the last 21 days to extract each session’s working directory and opening
message, and parse a session’s full history only when you open its chat. Choose
Scan sessions to build your contact list, or Do not scan to keep the feature
off (you can enable it later). Nothing is uploaded either way; content reaches your AI
provider only when you chat. Point the claude_projects config key at another
directory to scan somewhere else.
Optional next steps:
cp examples/squadcue.example.json squadcue.json — configure CLI paths, Telegram,
KB sources, port (reference below).cp examples/daily_todos.example.json data/daily_todos.json — daily checklist.python tg_bridge.py — Telegram remote control (section 5).flows/demo_daily_digest.json in the Canvas tab and hit ▶ Run for a
fetch → summarize → approve → notify demo.All configuration lives in one optional file, squadcue.json, in the repo root.
Priority: squadcue.json > environment variables (SQUADCUE_*) > defaults
(see config.py). Every key is optional.
| Key | Default | Env alternate | What it does |
|---|---|---|---|
claude_cmd |
autodetected from PATH (claude.cmd/claude on Windows, claude elsewhere) |
SQUADCUE_CLAUDE_CMD |
Path or name of the Claude Code CLI used for employees, one-shot commands, flows, diagnosis. |
codex_cmd |
autodetected from PATH (codex.cmd/codex on Windows, codex elsewhere) |
SQUADCUE_CODEX_CMD |
Path or name of the Codex CLI (read-only red-team engine). |
claude_projects |
~/.claude/projects |
SQUADCUE_CLAUDE_PROJECTS |
Where Claude Code stores session transcripts. Source for employee discovery and session rescue. Point at an empty dir to opt out of discovery. |
workroot |
~ (home) |
SQUADCUE_WORKROOT |
Read-only root directory Codex red-team runs are allowed to inspect (codex exec --sandbox read-only -C <workroot>). |
kb_sources |
[{"path": <repo root>, "glob": "*.md"}, {"path": <repo>/docs, "glob": "*.md"}] |
— (JSON file only) | List of {"path": dir, "glob": pattern} folders indexed by the local BM25 knowledge base (kb canvas node, /api/kb). |
telegram.bot_token |
"" (disabled) |
SQUADCUE_TG_TOKEN |
Telegram bot token. Everything degrades gracefully without it. |
telegram.chat_id |
"" (disabled) |
SQUADCUE_TG_CHAT |
The single chat id the bot pushes to and accepts commands from. |
host |
127.0.0.1 |
SQUADCUE_HOST |
Bind address. Must be 127.0.0.1 or localhost — the server refuses to start otherwise (IPv6 ::1 is rejected too: the request-host allowlist is IPv4/hostname-only). |
port |
8899 |
SQUADCUE_PORT |
Server port. |
Related environment variables read elsewhere: perm_hook.py honors
SQUADCUE_PERM_URL (default http://127.0.0.1:8899), SQUADCUE_PERM_TIMEOUT
(seconds, default 600, capped at 850 — the hook command itself has a 900 s budget
and must answer with an explicit deny before Claude Code cancels it; an unparseable
value falls back to the default), plus SQUADCUE_PERM_RUN / SQUADCUE_PERM_LABEL
which the server sets automatically per run. ANTHROPIC_API_KEY is stripped from
child processes spawned by the server (runs, chat, relaunch) so subscription CLIs
never silently switch to API billing.
The cockpit has four top-level tabs — 🎛 Ops, 💬 Employees, 🗂 Projects, 🎨 Canvas — plus the ⌨ +New task overlay.
The default tab: everything that is running or ran recently.
N todos — managed via ops/ scripts &
morning push) with a manual done/total progress counter. Definitions live in
data/daily_todos.json; see section “Schedule-yourself scripts” below.running, done, failed, stopped,
waiting_approval, lost = interrupted by a server restart). Click a row to select
it.--resume underneath) with an optional
Allow tools checkbox.Two subpages: 💬 Chat and 🖥 Rescue.
Chat. The left column is your contact list — 📌 Pinned employees first, then
Others — each showing nickname, last-activity time, and project folder. The footer
repeats the local-discovery note: these are your own Claude Code sessions, discovered
locally from ~/.claude/projects; the list never leaves your computer.
Click a contact to open the conversation (full history renders as chat bubbles), then:
sonnet (fast & cheap), opus, or
default (largest).--dangerously-skip-permissions and nothing pauses (the old unguarded behavior).The approval gate, end to end. With “Allow tools” on (and “Skip approvals” off),
the server attaches a per-run settings file that installs perm_hook.py as a
PreToolUse hook on the spawned CLI. Before every tool call the hook checks a safe list
of local read/query tools (Read, Grep, Glob, LS, TodoWrite, TodoRead,
NotebookRead, BashOutput, ListMcpResourcesTool) — those pass silently.
Anything else (Bash, Write, Edit, and also WebFetch/WebSearch/Task, which can
move data off the machine or spawn sub-agents) files a row in the approval inbox with
the full canonical tool input — the cockpit shows every character of what will
execute, and inputs too large to display are denied rather than truncated — and pushes
a Telegram card with Approve/Deny buttons (the TG text is a 200-char preview; review
the full payload in the cockpit). The tool call blocks while the hook polls. You
approve or deny from the Ops inbox card or the Telegram button — first response wins —
and the tool runs or is refused accordingly. A deny reason you type is fed back to the
agent verbatim. If nobody answers within the timeout (default 600 s), the call is
denied; the hook also denies on malformed input, an unreachable
server, or any internal error (fail-closed). Decisions that pass through the hook and
inbox are recorded in the approvals table. This is a supervision workflow, not a security boundary — an
approved shell command can spawn sub-processes the hook never sees, and Claude Code
settings can disable hooks entirely; see SECURITY.md.
Other chat behaviors:
/model [name] switch model, /context show memory size,
/clear clear the screen (memory intact), /help. These are handled in the browser;
interactive CLI commands like /compact are not available in headless chat — use
Rescue to reopen a real terminal window.Rescue. Lists the same sessions as terminal windows to bring back after a reboot:
🔄 Reopen launches a new console running claude --resume <id> in the session’s
project directory (full context intact), 📋 copies the equivalent resume command to
the clipboard, 📌 pins/unpins. Relaunch is Windows-only today (it opens a
cmd window via start); on other platforms use the 📋 copy button and paste the
command into your own terminal.
Two subpages: 🗂 Project cards and ◉ Issues.
data/projects.json) and renders one card
per project: status badge, one-liner, last git commit of the linked repo, open-issue
count, category filter bar. Clicking a card opens a detail modal with story, results,
gate, recent commits, related open issues, and a 🤖 Ask SquadCue about this
project shortcut that prefills a status-report prompt in the New task overlay.data/issues.json. Each open issue
expands to its context and offers 💬 Reply (append a comment), 🤖 Dispatch AI
(spawn an agent to push the issue one step forward and append its progress back —
gated: its risky tool calls pause into the approval inbox), ✅ Decided, and
🏁 Close.The drag-and-drop flow editor (Drawflow): build pipelines from typed nodes (action,
claude, codex, gate, shell, fetch, tg, wait, readfile, writefile,
kb, plus experimental swarm / tournament), wire them, and hit ▶ Run — the run
always executes the canvas you currently see, never a stale saved version.
✨ Generate from a sentence drafts a flow from a one-line description. Full node
reference, templating (``), gate timeouts, and demo walkthrough:
docs/CANVAS.md.
Opened from the tab bar; Escape closes it. Two sections:
sonnet (saves quota) / default model (largest — slow but strong) /
opus), Allow tools (adds --dangerously-skip-permissions — unchecked means
read-only analysis), Notify via TG when done (no need to keep the window open),
and 📋 Spec first, then build — the agent must output Requirements / Design /
Tasks and stop; implementation starts only after you say “go” in the Ops follow-up
box. The prompt goes to the CLI via stdin, and matching powers/*.md knowledge files
are auto-attached by keyword.Setup: put telegram.bot_token and telegram.chat_id in squadcue.json (or export
SQUADCUE_TG_TOKEN / SQUADCUE_TG_CHAT), then run python tg_bridge.py alongside the
server. chat_id must be your private DM chat with the bot — the design is
single-operator: inline-button taps are accepted only when both the clicker’s user id
and the chat match chat_id, so in a group chat every approval button would be
rejected (user id ≠ group id). The bridge only answers the configured chat_id, holds
a single-instance lock on 127.0.0.1:8898, and logs to logs/tg_bridge.log.
Keep-alive is yours to arrange (Task Scheduler / systemd / cron).
Commands (verified against tg_bridge.py):
| Command | What it does |
|---|---|
| any text | One-shot Claude run (sonnet, read-only analysis); the reply tail comes back to your phone. In employee mode, continues the picked employee’s conversation instead. |
! prefix |
Same, but with tools allowed (write files / run commands). Explicit opt-in that bypasses the approval gate (--dangerously-skip-permissions) — use it only for work you’d approve wholesale. |
@codex <prompt> |
One-shot Codex red-team run (read-only). A bare @codex, or a Codex CLI that is not on PATH, replies with usage instead of falling through to Claude. |
/who (aliases /lines, /employees) |
Numbered list of your employees (📌 = pinned, ⬅ = currently picked). |
/pick <n> |
Switch to employee n from the last /who listing — plain text now chats with its memory. |
/name <n> <nick> |
Rename employee n from the last /who listing. |
/release (alias /oneshot) |
Leave employee mode; each message is independent again. |
/status |
Server heartbeat, current mode, and the last 6 runs with status. |
/actions |
List predefined actions as ready-to-tap /act lines. |
/act <id> |
Run a predefined action; result tail is pushed when it finishes. |
/run <pipeline> |
Run the named pipeline (per-item high-risk approvals still happen on the panel). |
Approval button cards: when a gate or risky tool call fires, the bot sends a message with inline Approve / Reject buttons. Tapping one decides the whole item (first response wins — if the cockpit already answered, the tap reports “Already handled or expired”); the message is edited in place to record the outcome. Per-item fine-grained review stays on the cockpit.
After a reboot every terminal window is gone, but the sessions are not. The
🖥 Rescue subpage (Employees tab) lists recent real conversation sessions scanned
from claude_projects, pinned ones first. 🔄 Reopen spawns a visible console
window running claude --resume <session-id> in the session’s original project
directory — the agent comes back with full context.
Caveat: the one-click relaunch is Windows-only today (it shells out to
start ... cmd /k). On macOS/Linux, use the 📋 button — it copies the exact
cd ... && claude --resume <id> command for you to paste into any terminal.
Everything is plain files inside the repo directory — greppable, deletable, yours. All of the below are gitignored.
| Location | Contents |
|---|---|
runs/<id>/ |
Per-run audit trail: events.jsonl (append-only log) and state.json (status + stages) for every run; flow_snapshot.json (the exact flow definition executed) and steps.json (per-node inputs/outputs/durations) for flow runs; gate_settings.json (the per-run hook config) for gated runs (chat/heal/issue dispatch). |
data/ |
projects.json (project registry), issues.json (issue tracker), daily_todos.json (todo definitions), tg_muted (marker file muting TG pushes). |
mc.db |
SQLite: the approvals table (every gate and tool-permission decision, with when/via/comment and the full tool payload — the audit log; in a single-user system “who” is you) and daily_checks (todo check state). |
news.db |
SQLite: findings ledger for the health-scan/self-heal loop. |
employees.json |
Nicknames and roles you assigned to sessions. |
pinned_sessions.json |
Which sessions are pinned. |
tg_state.json |
Telegram bridge employee-mode state (survives bridge restarts). |
flows/ |
Saved flow definitions (one JSON per flow). |
logs/ |
tg_bridge.log, self-heal incident cards (heal_log.md). |
squadcue.json |
Your local config, including the TG token — gitignored on purpose. |
Your conversation transcripts themselves live where Claude Code puts them
(claude_projects, default ~/.claude/projects) — SquadCue reads but never moves them.
ops/)SquadCue ships no scheduler daemon; two small scripts are meant for your own Task Scheduler / cron:
python ops/daily_todo_push.py — pushes today’s checklist to Telegram (schedule it
each morning, e.g. 08:05).python ops/todos_to_ics.py [out.ics] — exports recurring todos as an importable
.ics calendar (weekly recurrence per line/time group; per-item override via a
"cal": {"time": "HH:MM", "dur": minutes} field).The cockpit shows “Cannot reach server”. The server is not running (or crashed) —
restart with python server.py. The UI polls and recovers automatically.
Port already in use. Another process owns 8899. Set "port" in squadcue.json
(or SQUADCUE_PORT) and restart both the server and tg_bridge.py (the bridge reads
the same config).
Server exits immediately with a loopback error. You set host to a LAN/remote
address. SquadCue has no auth layer and refuses non-loopback binding by design; keep
host at 127.0.0.1 and use a tunnel (e.g. SSH/Tailscale) if you need remote access.
Employees list is empty. Discovery scans claude_projects (default
~/.claude/projects). If your Claude Code stores sessions elsewhere, or you run the
server as a different user, set claude_projects in squadcue.json. Very small or
non-conversation session files are filtered out.
“claude CLI not found” / runs fail at launch. The CLI is autodetected from PATH.
If it lives elsewhere, set claude_cmd (and/or codex_cmd) in squadcue.json to the
full path. On Windows the server looks for claude.cmd first.
An approval “expired” and the tool call failed. That is the design: timeout =
deny, never allow. Approve faster, or raise the window — SQUADCUE_PERM_TIMEOUT
(seconds, capped at 850 so the hook always answers within its own 900 s budget) for
employee-chat tool calls, the gate node’s timeout param (seconds, default 7200) for
flows.
A run shows status lost. The server restarted while the run was in flight; the
event log up to that point is preserved in runs/<id>/. Rerun it (for flows:
🔁 Rerun original / 🔁 Rerun current).
My message to a busy employee seems stuck. Turns queue behind the global concurrency cap (3 simultaneous agents, up to 30 minutes waiting for a slot). Check the Ops runs list — the turn appears as its own run and starts when a slot frees up.
Telegram bot does not respond. Check telegram.bot_token / telegram.chat_id in
squadcue.json (the bridge exits at startup and says so if they are missing), make
sure only one tg_bridge.py instance runs (single-instance lock on port 8898), and
look at logs/tg_bridge.log. Messages from any other chat id are ignored silently.