squadcue

SquadCue User Guide

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.

1. Overview & concepts

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:

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.

2. Install & first launch

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:

3. Configuration reference

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.

4. Tab-by-tab guide

The cockpit has four top-level tabs — 🎛 Ops, 💬 Employees, 🗂 Projects, 🎨 Canvas — plus the ⌨ +New task overlay.

4.1 Ops

The default tab: everything that is running or ran recently.

4.2 Employees

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:

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:

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.

4.3 Projects

Two subpages: 🗂 Project cards and ◉ Issues.

4.4 Canvas

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.

4.5 ⌨ +New task (overlay)

Opened from the tab bar; Escape closes it. Two sections:

5. Telegram remote control

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.

6. Session rescue

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.

7. Where your data lives

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.

8. Schedule-yourself scripts (ops/)

SquadCue ships no scheduler daemon; two small scripts are meant for your own Task Scheduler / cron:

9. Troubleshooting / FAQ

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.