squadrant 0.9.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -174,6 +174,60 @@ Pass `--direction right|left|up|down` to use a split pane instead of a tab. Stat
174
174
  - **Semantic heartbeat** — crews emit a lifecycle signal the captain reads as **CREW IDLE / QUIET / STALLED**, distinguishing "waiting for you" from "wedged" without scraping the pane ([#354](https://github.com/tu11aa/squadrant/issues/354)).
175
175
  - **`stopped` project status + orphan reap** — when a captain goes away, the daemon reaps its orphaned crews and marks the project `stopped` (intentional shutdown) rather than leaving stale tabs or faulting ([#324](https://github.com/tu11aa/squadrant/issues/324) / [#323](https://github.com/tu11aa/squadrant/issues/323) / [#388](https://github.com/tu11aa/squadrant/pull/388)).
176
176
 
177
+ ### Telegram (Two-Way, opt-in)
178
+
179
+ Drive squadrant from your phone ([#65](https://github.com/tu11aa/squadrant/issues/65)). When a `telegram` block is present in config, a daemon-internal bridge:
180
+
181
+ - **Outbound** — pushes each project's crew lifecycle events (CREW DONE / FAILED / BLOCKED / APPROVAL / INPUT / TIMEOUT / IDLE) and other captain notifications to that project's Telegram forum topic, filtered by a per-project **crew tier** (see [Notification tuning](#notification-tuning-per-project) below). Best-effort: a Telegram failure never delays or breaks delivery to the captain pane.
182
+ - **Inbound (project topic)** — a message you send in a project's topic is delivered into that project's **captain pane** as a labeled `📩 [from Telegram]` message; the captain decides what to do with it. With remote control enabled (see below), if no captain is alive the daemon **auto-launches** one, then delivers ([#403](https://github.com/tu11aa/squadrant/issues/403)).
183
+ - **General command channel** — slash commands in the supergroup's **General topic** run a curated set of squadrant operations from your phone ([#402](https://github.com/tu11aa/squadrant/issues/402)). Available: `/help`, `/status`, `/projects`, `/crews <project>`, `/launch <project>`, `/effort [max|balance|low]`, `/config get <key>`, `/config set <key> <value>`, `/spawn <project> <task…>`. Each maps to a validated CLI argv run via async `execFile` — never a shell passthrough. Unknown commands and freeform text get a `/help` hint.
184
+
185
+ Absent the config block, the bridge is never constructed — zero behavior change. No runtime SDK is added (plain `fetch`; `@grammyjs/types` is a dev-only type dependency).
186
+
187
+ **Setup (recommended):**
188
+ 1. Create a bot with [@BotFather](https://t.me/BotFather) and copy its token.
189
+ 2. Run `squadrant telegram setup` — it prompts for the bot token (input hidden), validates it via the Bot API, auto-detects your supergroup id, captures your Telegram user-id, and offers to enable remote control.
190
+ 3. Bind a project to a topic: `squadrant telegram link <project>` (creates the forum topic and records the binding).
191
+ 4. Check wiring with `squadrant telegram status`.
192
+
193
+ **Setup (manual):**
194
+ Put the token + ids in config (or export `TELEGRAM_BOT_TOKEN`) — see the `telegram` block under [Config](#config). Then run `squadrant telegram link <project>`.
195
+
196
+ #### Security model (#321) — fail-closed remote control
197
+
198
+ The control surfaces (auto-launch + General command channel) are **off by default** and gated by two independent checks. A control action runs **only when both** hold:
199
+
200
+ 1. `remoteControl: true` — an explicit opt-in master switch (default `false`).
201
+ 2. `message.from.id ∈ users[]` — the sender's Telegram **user-id** is on the allowlist. An empty/absent `users` list ⇒ control is disabled (fail-closed). Chat membership alone is **never** enough for control.
202
+
203
+ When remote control is off (the default after upgrade), behavior is **exactly v1**: project-topic messages queue to the captain pane (no auto-launch), and General-topic slash commands are rejected with `⛔ not authorized`. Inbound text is always treated as data; only the curated registry maps to actions, and `/config set` is restricted to a default-deny writable-key allowlist (currently just `defaults.effort`) — **secrets (`botToken`, `users`, `chats`, `supergroupId`) can never be written over Telegram.**
204
+
205
+ #### Notification tuning (per-project)
206
+
207
+ Notifications resolve through a **layered config**: built-in defaults → global `config.json` (`telegram.notify`) → per-project `~/.config/squadrant/projects/<name>.json`, merged per key (overriding one key never resets its siblings). Two axes are independent:
208
+
209
+ - **Live mute (`active`)** — system-tracked *session* state in `telegram-state.json`. Flipped by engagement (any message into a topic auto-unmutes), `/mute` / `/unmute` (Telegram), or `squadrant telegram notify <project> on|off`. The live value wins over the config default when present.
210
+ - **Deliberate preferences (`crew`, `cap`)** — persistent settings in the per-project config file. Written by `squadrant telegram notify <project> crew <tier>` / `cap <on|off>` (CLI) or `/notify crew <tier>` / `/notify cap <on|off>` (Telegram, fail-closed behind remote control).
211
+
212
+ **Crew tiers** (cumulative) select which lifecycle events reach a topic when active:
213
+
214
+ | Tier | Events delivered |
215
+ |---|---|
216
+ | `none` | nothing |
217
+ | `done_only` | `task.done`, `task.failed` |
218
+ | `alert_only` *(default)* | `done_only` + `task.blocked`, `task.approval.requested`, `task.input.requested`, `task.timeout` |
219
+ | `all` | every lifecycle event (incl. progress/heartbeat noise) |
220
+
221
+ **`cap`** (default `on`) gates explicit captain pushes via `squadrant telegram send` — set `cap off` for a project to stop the captain DM-ing you there (independent of idle-mute; an explicit push is not dropped just because the topic is idle-muted).
222
+
223
+ Config (deliberate prefs) and state (live toggles) are kept separate by design: `/unmute` flips your session, it does **not** rewrite your config file. An absent `projects/<name>.json` behaves exactly as the global defaults — the layer is fully additive, no migration. See `docs/superpowers/specs/2026-06-23-per-project-layered-config-design.md`.
224
+
225
+ #### Remote wake (#403) — operator-side
226
+
227
+ Auto-launch boots a captain when the **daemon is already running**. Waking a *sleeping Mac* from your phone (Wake-on-LAN / a relay that nudges the machine) is operator-side infrastructure, out of scope for this repo — see [#403](https://github.com/tu11aa/squadrant/issues/403) for the end-to-end flow.
228
+
229
+ > **Interim note (link ↔ daemon 409):** the Telegram Bot API allows only one `getUpdates` consumer at a time. The `setup` wizard polls `getUpdates` to detect your group/user-id, so run it with the daemon stopped. `link` uses only `createForumTopic`, so it's unaffected.
230
+
177
231
  ### Projection (Cross-Agent Config Sync)
178
232
 
179
233
  Squadrant rules (Karpathy principles, captain-ops) and per-project AGENTS.md emit to each supported agent's canonical path via `squadrant projection emit`. User-level projection pushes squadrant's skills to `~/.cursor/rules/squadrant-global.mdc`, `~/.codex/AGENTS.md`, `~/.gemini/GEMINI.md`. Project-level projection pushes a managed project's own `AGENTS.md` into `{project}/CLAUDE.md`, `{project}/.cursor/rules/squadrant.mdc`, `{project}/GEMINI.md` — zero squadrant-global content leaks into the project repo. Shared files use `<!-- squadrant:start --> ... <!-- squadrant:end -->` markers; dedicated files overwrite. See `docs/specs/archive/2026-04-24-plugin-system-projection-design.md`.
@@ -213,6 +267,14 @@ The user-level projection now also inlines `templates/captain.generic.md` and `t
213
267
  "runtime": "cmux",
214
268
  "workspace": "obsidian",
215
269
  "notifier": "cmux",
270
+ "telegram": {
271
+ "botToken": "123456:ABC...",
272
+ "supergroupId": -1001234567890,
273
+ "chats": [-1001234567890],
274
+ "users": [987654321],
275
+ "remoteControl": true,
276
+ "pollMs": 1000
277
+ },
216
278
  "projects": {
217
279
  "brove": {
218
280
  "path": "~/projects/brove",
@@ -242,6 +304,8 @@ The user-level projection now also inlines `templates/captain.generic.md` and `t
242
304
  }
243
305
  ```
244
306
 
307
+ The `telegram` block is **optional** — omit it and the Telegram bridge is never constructed. `botToken` may be left out of the file and supplied via the `TELEGRAM_BOT_TOKEN` env var instead. `chats` is the inbound `chat_id` allowlist; `users` is the per-user-id allowlist for **control** actions and `remoteControl` (default `false`) is the master opt-in for auto-launch + the General command channel — both must be set for any remote control to act (fail-closed, [#321](https://github.com/tu11aa/squadrant/issues/321)). `pollMs` (default `1000`) is the inbound long-poll cadence. See [Telegram (Two-Way, opt-in)](#telegram-two-way-opt-in).
308
+
245
309
  ## Supported Agents
246
310
 
247
311
  | Agent | Status | Notes |