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), 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:

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 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.

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 (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:

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. 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.

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), 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.

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) 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.