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), but the
Employees tab is probably not: SquadCue discovers your existing Claude Code sessions
locally from ~/.claude/projects — the directory where the Claude Code CLI itself
stores conversation logs. SquadCue only reads them to offer chat/resume/rescue; nothing
is uploaded, indexed remotely, or transmitted anywhere. If seeing your sessions listed
surprises you, that is the intended discovery feature working on your own local data.
Point the claude_projects config key at another directory (or an empty one) to opt
out. The same note appears as a footer under the contact list in the UI.
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 loopback (127.0.0.1 / localhost / ::1) — the server refuses to start otherwise. |
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), plus SQUADCUE_PERM_RUN / SQUADCUE_PERM_LABEL which the
server sets automatically per run. ANTHROPIC_API_KEY is stripped from every child
process 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
(Read, Grep, Glob, LS, WebFetch, WebSearch, TodoWrite, TodoRead,
Task, NotebookRead, BashOutput, ListMcpResourcesTool) — those pass silently.
Anything else (Bash, Write, Edit, …) files a row in the approval inbox and pushes a
Telegram alert; 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. If nobody answers within the timeout (default 600 s), the call is
denied, never allowed. Every decision is audited in the approvals table.
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),
✅ 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. 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). |
@codex <prompt> |
One-shot Codex red-team run (read-only). |
/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), state.json (status + stages), flow_snapshot.json (the exact flow definition executed), steps.json (per-node inputs/outputs/durations), gate_settings.json (the per-run hook config for gated chat). |
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 who/when/via/comment — the audit log) 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) 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.