@bli-cockpit/cli 0.2.101 → 0.2.103

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
@@ -4,54 +4,18 @@ Cockpit watches how you and your AI agents work so Edward can coach from evidenc
4
4
 
5
5
  ## 90-second speedrun
6
6
 
7
- Run these three commands on the Mac or Windows PC that should be collected:
7
+ Run this on the Mac or Windows PC that should be collected:
8
8
 
9
9
  ```bash
10
- $ npm install -g @bli-cockpit/cli@latest
11
- added <packages> in <seconds>s
10
+ npm i -g @bli-cockpit/cli && cockpit doctor
12
11
  ```
13
12
 
14
- ```bash
15
- $ cockpit do-everything
16
- Setting up Cockpit
17
- Dashboard: https://bli-cockpit-dashboard.vercel.app
18
- Ticket: none (general work)
19
- What's your @buildlaunchiterate.ca email? (press enter to skip): ian@buildlaunchiterate.ca
20
- Signing in as ian@buildlaunchiterate.ca.
21
- Code sent; valid 1h, resend in 60s by rerunning this command.
22
- Email code needed:
23
- Check your latest Cockpit email for a 6- to 10-digit code.
24
- What you can do:
25
- 1) Paste the code here.
26
- 2) No code yet: wait for the resend window, then rerun this command.
27
- 3) Can't use email: rerun with --no-auth for manual approval.
28
- Code: 482913
29
- Signed in as ian@buildlaunchiterate.ca.
30
- 2/5 Device paired.
31
- PASS: Cockpit is set up and collecting.
32
- You're live.
33
- Dashboard: https://bli-cockpit-dashboard.vercel.app/my-work
34
- Next: cockpit status
35
- ```
36
-
37
- ```bash
38
- $ cockpit status
39
- Cockpit status
40
- Installed: yes
41
- Signed in: yes
42
- Ticket: none (general work)
43
- Last collected: recently
44
- Uploads: ready
45
- Waiting to upload: 0
46
- Old sessions: done
47
- Stuck files: none
48
- ```
49
-
50
- The OTP proves you own an approved BLI mailbox. The JWT is used once to register this device and is never saved; the durable local credential remains the existing Cockpit device token.
13
+ Doctor installs, repairs, and verifies the machine. Follow any sign-in or trust
14
+ prompt it prints. It finishes with proof of collection or the remaining action.
51
15
 
52
16
  ## Every command, what it does, and why it's called that
53
17
 
54
- On a blank Mac or Windows PC, `npm i -g @bli-cockpit/cli && cockpit do-everything` is enough to sign in, choose roots, and converge the machine. `onboard` remains the named setup subset, and the rest exist for recovery and maintenance.
18
+ On a blank Mac or Windows PC, `npm i -g @bli-cockpit/cli && cockpit doctor` is enough to sign in, choose roots, and converge the machine. `onboard` remains the named setup subset, and the rest exist for recovery and maintenance.
55
19
 
56
20
  Cockpit works the same way on an Apple Silicon Mac and on a normal Windows
57
21
  PC. Every Codex and Claude session in a folder you have approved is collected,
@@ -64,8 +28,8 @@ normal user, on both macOS and Windows.
64
28
 
65
29
  | Command | What it does | Why it exists / why this name |
66
30
  |---|---|---|
67
- | `cockpit onboard` | The named setup subset: signs you in (email code), registers this machine, starts capture, uploads once, completes all-history Codex and Claude backfill for saved approved roots, installs the 15-min background sync, and only then prints proof you're live. | You are boarding the crew. `do-everything` calls this flow when setup is missing; rerunning it directly is safe. Partial or failed backfill blocks readiness with an exact retry reason. |
68
- | `cockpit do-everything` / `cockpit fix` / `cockpit doctor` | Converges a Mac or Windows PC from blank or already-onboarded state: latest CLI, signed-in device token, saved roots, autostart, historical backfill, raw-evidence GC, and fresh sync. Interactive first runs prompt for email OTP and collection roots; headless/`--json` runs explain and exit instead of blocking. `--dry-run` previews without writing. | Edward can post one line and every intern machine should end green. `fix` is the alias people guess; `doctor` is the alias the ops board, the setup receipt and the heartbeat all tell you to run, and it is the same command (it checks each row, then fixes it). |
31
+ | `cockpit onboard` | The named setup subset: signs you in (email code), registers this machine, starts capture, uploads once, completes all-history Codex and Claude backfill for saved approved roots, installs the 15-min background sync, and only then prints proof you're live. | You are boarding the crew. `doctor` calls this flow when setup is missing; rerunning it directly is safe. Partial or failed backfill blocks readiness with an exact retry reason. |
32
+ | `cockpit doctor` | Converges a Mac or Windows PC from blank or already-onboarded state: latest CLI, signed-in device token, saved roots, autostart, historical backfill, raw-evidence GC, and fresh sync. Interactive first runs prompt for email OTP and collection roots; headless/`--json` runs explain and exit instead of blocking. `--dry-run` previews without writing. | Edward can post one line and every intern machine should end green. It checks each row, repairs it, and verifies the result. |
69
33
  | `cockpit status` | Prints install / sign-in / capture / upload health in one screen. | The "is it working?" command. Run it whenever you're unsure. |
70
34
  | `cockpit backfill --all` | Uploads your HISTORICAL Codex + Claude sessions (from before Cockpit existed on this machine). Discovery defaults to depth 3 and 50 repos; raise `--max-depth` or `--max-repos` when the returned retry command says a cap was reached. | One-time catch-up so your past work counts too. "Backfill" = fill in the back-catalog. |
71
35
  | `cockpit sync` | Captures and uploads once, right now. This is what the background agent runs every 15 min — you almost never type it yourself. | Named for what it does: synchronize local session files up to the dashboard. |
@@ -105,7 +69,7 @@ cockpit onboard --no-auth
105
69
  - `--no-auth` forces the old manual approval queue.
106
70
  - `--repo` still works as a legacy alias for `--workspace`.
107
71
 
108
- `cockpit do-everything` is the normal fleet convergence command. On an interactive first run it uses the same email OTP login and root-picker flow as onboarding, then re-checks the machine. In headless, launchd, `--json`, or no-TTY runs it never prompts; missing auth or roots stay red with the repair text. `--dry-run` previews without writing auth or config.
72
+ `cockpit doctor` is the normal fleet convergence command. On an interactive first run it uses the same email OTP login and root-picker flow as onboarding, then re-checks the machine. In headless, launchd, `--json`, or no-TTY runs it never prompts; missing auth or roots stay red with the repair text. `--dry-run` previews without writing auth or config.
109
73
 
110
74
  `cockpit update` installs the latest public CLI and reruns onboarding checks against saved roots. `cockpit upgrade` is the same command.
111
75
 
@@ -116,13 +116,13 @@ function collectionRootNotPersistedMessage(homeDir) {
116
116
  "Fix it by running ONE of these:",
117
117
  "",
118
118
  " # Sync everything on this machine (what most people want on a work laptop)",
119
- " cockpit do-everything --allow-home-root",
119
+ " cockpit doctor --allow-home-root",
120
120
  "",
121
121
  " # Or sync one folder — replace the path with where your projects live",
122
- ` cockpit do-everything --workspace ${path.join(home, "BLI")}`,
122
+ ` cockpit doctor --workspace ${path.join(home, "BLI")}`,
123
123
  "",
124
124
  " # Or answer the folder question interactively",
125
- " cockpit do-everything",
125
+ " cockpit doctor",
126
126
  "",
127
127
  "Then check it worked:",
128
128
  " cockpit status",
@@ -139,10 +139,10 @@ function collectionRootMissingOnDiskMessage(saved, homeDir) {
139
139
  "Fix it by running ONE of these:",
140
140
  "",
141
141
  " # Point Tower at where your projects actually live now",
142
- ` cockpit do-everything --workspace ${path.join(home, "BLI")}`,
142
+ ` cockpit doctor --workspace ${path.join(home, "BLI")}`,
143
143
  "",
144
144
  " # Or sync everything on this machine and stop worrying about the path",
145
- " cockpit do-everything --allow-home-root",
145
+ " cockpit doctor --allow-home-root",
146
146
  "",
147
147
  "Not sure where your projects are? This lists the folders Tower can see:",
148
148
  " cockpit status",
@@ -14,7 +14,7 @@ import { runBackfillCommand } from "./backfill.js";
14
14
  import { diskRowMessage, mib, redeliveryLine } from "./doctor-disk-words.js";
15
15
  import { doctorRoots } from "./doctor-access.js";
16
16
  import { backfillCompletionStepState, backfillFixVerdict, jsonField, parseDoctorBackfillJson, parseDoctorSyncJson, syncBacklogDrainingVerdict, syncStandAsideVerdict, } from "./doctor-pipeline-verdicts.js";
17
- import { fail, needsFix, ok, skipped } from "./doctor-report.js";
17
+ import { asRecord, fail, needsFix, ok, skipped } from "./doctor-report.js";
18
18
  /**
19
19
  * The `backfill-complete`, `gc-checked`, `disk-bounded`, and `sync-fresh`
20
20
  * check family: does the collection pipeline itself have everything it
@@ -47,33 +47,69 @@ export async function checkBackfillState(context) {
47
47
  return needsFix("backfill-complete", cursor.updated_at ? "partial" : "never_run", "the catch-up over your old sessions has not finished");
48
48
  }
49
49
  export async function fixBackfillState(context) {
50
- return withDoctorLockWait(context, () => runBackfillRepair(context));
51
- }
52
- async function runBackfillRepair(context) {
53
- const capture = capturedIo(context.io, !context.command.json);
54
- const code = await runBackfillCommand({
55
- homeDir: context.command.homeDir,
56
- repoRoot: context.command.repoRoot,
57
- all: true,
58
- dryRun: false,
59
- yes: true,
60
- json: true,
61
- }, capture.io);
62
- const stdout = capture.stdout();
63
- const output = `${stdout}\n${capture.stderr()}`;
64
- if (code === 0) {
65
- // Re-read the marker this run just wrote instead of hand-rolling a second
66
- // message: `checkBackfillState`'s pure core already knows how to say
67
- // "complete" vs "complete_with_oversized_skips" (BLI-2727), and this way
68
- // the two can never say something different for the same marker.
69
- const recheck = await checkBackfillState(context);
70
- if (recheck.status === "ok")
71
- return recheck;
72
- return recheck;
50
+ const started = Date.now();
51
+ const deadline = started + (context.command.backfillBudgetSeconds ?? 900) * 1000;
52
+ let caughtUp = 0;
53
+ let remaining = null;
54
+ const unfinished = (code, reason) => ({
55
+ ...needsFix("backfill-complete", code, `caught up on ${caughtUp} old sessions this run, ${remaining ?? "unknown"} still to go; ${reason}; run \`cockpit doctor\` again to continue`),
56
+ nextAction: "cockpit doctor",
57
+ });
58
+ const progress = setInterval(() => {
59
+ const seconds = Math.floor((Date.now() - started) / 1000);
60
+ context.io.stderr.write(`backfill: ${caughtUp} caught up, ${remaining ?? "unknown"} to go, ${Math.floor(seconds / 60)}m${seconds % 60}s\n`);
61
+ console.error("[doctor] backfill-complete progress", JSON.stringify({ caught_up: caughtUp, remaining, elapsed_seconds: seconds }));
62
+ }, 30_000);
63
+ try {
64
+ while (Date.now() < deadline) {
65
+ // Every chunk reacquires the collector's locks. Bound lock waiting by
66
+ // the overall budget too; never abandon an in-flight cursor write.
67
+ const chunkContext = { ...context, command: { ...context.command,
68
+ lockWaitSeconds: Math.min(context.command.lockWaitSeconds ?? 600, (deadline - Date.now()) / 1000),
69
+ } };
70
+ const row = await withDoctorLockWait(chunkContext, async () => {
71
+ if (Date.now() >= deadline)
72
+ return unfinished("backfill_budget_timeout", "backfill time budget reached");
73
+ const capture = capturedIo(context.io, false);
74
+ const code = await runBackfillCommand({
75
+ homeDir: context.command.homeDir, repoRoot: context.command.repoRoot,
76
+ all: true, dryRun: false, yes: true, json: true,
77
+ }, capture.io);
78
+ const parsed = parseDoctorBackfillJson(capture.stdout());
79
+ const counts = asRecord(parsed?.counts);
80
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
81
+ caughtUp += count(counts?.["backfilled"]) ?? 0;
82
+ remaining = count(counts?.["remaining"]) ?? remaining;
83
+ if (code === 0 && parsed?.status === "complete") {
84
+ // Reporting totals include permanent caps. Preserve their marker
85
+ // note instead of retrying history the collector says is complete.
86
+ const checked = await checkBackfillState(context);
87
+ return ok("backfill-complete", checked.status === "ok" ? checked.code : "complete", `caught up on ${caughtUp} old sessions this run` + (checked.status === "ok" ? `; ${checked.message}` : ""));
88
+ }
89
+ const verdict = backfillFixVerdict(parsed, jsonField(capture.stderr(), "failure_reason"));
90
+ if (parsed?.status === "partial" &&
91
+ (parsed.failure_reason === "deferred_budget_exhausted" || !parsed.failure_reason)) {
92
+ return needsFix("backfill-complete", "backfill_chunk_pending", "more history remains");
93
+ }
94
+ return { ...verdict, message: typeof parsed?.failure_reason === "string" ? parsed.failure_reason : "backfill failed without a valid completion receipt" };
95
+ });
96
+ if (row.status === "ok") {
97
+ console.error("[doctor] backfill-complete finished", JSON.stringify({ caught_up: caughtUp, remaining, elapsed_seconds: Math.floor((Date.now() - started) / 1000) }));
98
+ return row;
99
+ }
100
+ if (row.code !== "backfill_chunk_pending") {
101
+ return unfinished(row.code === "lock_wait_timeout" && Date.now() >= deadline ? "backfill_budget_timeout" : row.code, row.message);
102
+ }
103
+ }
104
+ return unfinished("backfill_budget_timeout", "backfill time budget reached");
105
+ }
106
+ catch (error) {
107
+ console.error("[doctor] backfill-complete failed", JSON.stringify({ reason: "backfill_threw", error_name: error instanceof Error ? error.name : "unknown" }));
108
+ return unfinished("backfill_threw", "backfill failed");
109
+ }
110
+ finally {
111
+ clearInterval(progress);
73
112
  }
74
- const verdict = backfillFixVerdict(parseDoctorBackfillJson(stdout), jsonField(output, "failure_reason"));
75
- console.error("[cockpit-doctor] catch-up run did not finish", JSON.stringify({ reason: verdict.code, row_status: verdict.status, exit_code: code }));
76
- return verdict;
77
113
  }
78
114
  export async function checkGcState(context) {
79
115
  if (context.io.env["COCKPIT_DISABLE_GC"] === "1") {
@@ -134,19 +134,19 @@ export function writeLine(stream, text) {
134
134
  }
135
135
  export function doctorNeedsPerson(rows, receipt) {
136
136
  const defaults = {
137
- "cli-latest": "npm install -g @bli-cockpit/cli@latest",
138
- "authed": "cockpit login",
137
+ "cli-latest": "npm i -g @bli-cockpit/cli && cockpit doctor",
138
+ "authed": "cockpit doctor",
139
139
  "roots-ok": "cockpit doctor",
140
140
  "single-install": "Run npm uninstall -g @bli-cockpit/cli with the Node installation that owns the extra CLI listed above.",
141
- "autostart-alive": "cockpit autostart install",
142
- "memory-registered": "cockpit memory install",
143
- "agent-rules": "cockpit agent-rules install",
141
+ "autostart-alive": "cockpit doctor",
142
+ "memory-registered": "cockpit doctor",
143
+ "agent-rules": "cockpit doctor",
144
144
  "mcp-answers": "cockpit doctor",
145
- "memory-daemon": "cockpit memory install",
146
- "backfill-complete": "cockpit backfill --all --yes",
147
- "sync-fresh": "cockpit sync --json",
145
+ "memory-daemon": "cockpit doctor",
146
+ "backfill-complete": "cockpit doctor",
147
+ "sync-fresh": "cockpit doctor",
148
148
  "gc-checked": "cockpit doctor",
149
- "disk-bounded": "cockpit clean --all-committed",
149
+ "disk-bounded": "cockpit doctor",
150
150
  };
151
151
  const items = rows.filter((row) => row.status === "fail" || row.status === "needs_fix" || row.hardStop).map((row) => ({
152
152
  step: row.id, reason: row.code,
@@ -71,6 +71,8 @@ export function reexecDoctor(command, io) {
71
71
  const args = ["doctor"];
72
72
  if (command.homeDir)
73
73
  args.push("--home", command.homeDir);
74
+ if (command.backfillBudgetSeconds)
75
+ args.push("--backfill-budget", String(command.backfillBudgetSeconds));
74
76
  if (command.lockWaitSeconds)
75
77
  args.push("--lock-wait", String(command.lockWaitSeconds));
76
78
  if (command.allowHomeRoot)
@@ -25,7 +25,7 @@ export async function readHeartbeatSession(homeDir) {
25
25
  console.error("[heartbeat] no usable device session on this machine; the dashboard will show it as quiet", JSON.stringify({
26
26
  reason: "no_device_session",
27
27
  session_state: state,
28
- next_action: "run `cockpit do-everything` to pair this machine again",
28
+ next_action: "run `cockpit doctor` to pair this machine again",
29
29
  }));
30
30
  return { usable: false };
31
31
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The six human "set my machine up" doors (`install`, `onboard`, `login`,
3
- * `pair`, `update`, `upgrade`), `do-everything`/`fix`, `logout`, and the
3
+ * `pair`, `update`, `upgrade`), `doctor`, `logout`, and the
4
4
  * maintainer's `release`. Split out of local-args-collector.ts (BLI-3636).
5
5
  *
6
6
  * `OnboardLikeOptions` — the flag set the setup doors share — lives here with
@@ -101,6 +101,7 @@ export function parseDoctorArgs(alias, args) {
101
101
  "--check",
102
102
  "--no-repair",
103
103
  "--lock-wait",
104
+ "--backfill-budget",
104
105
  "--json",
105
106
  "--allow-home-root",
106
107
  "--max-depth",
@@ -117,6 +118,7 @@ export function parseDoctorArgs(alias, args) {
117
118
  ],
118
119
  valueFlags: [
119
120
  "--lock-wait",
121
+ "--backfill-budget",
120
122
  "--home",
121
123
  "--repo",
122
124
  "--workspace",
@@ -140,6 +142,7 @@ export function parseDoctorArgs(alias, args) {
140
142
  return {
141
143
  kind: "doctor",
142
144
  checkOnly: values.booleans.has("--check") || values.booleans.has("--no-repair"),
145
+ backfillBudgetSeconds: optionalPositiveInteger(values.flags.get("--backfill-budget"), "--backfill-budget") ?? 900,
143
146
  lockWaitSeconds: optionalPositiveInteger(values.flags.get("--lock-wait"), "--lock-wait") ?? 600,
144
147
  alias,
145
148
  homeDir: optionalNonEmpty(values.flags.get("--home")),
@@ -174,7 +174,7 @@ export function parseAgentRulesArgs(args) {
174
174
  *
175
175
  * **A bare `cockpit memory` prints the verb list; it no longer installs.**
176
176
  * BLI-3580 made `install` the default because the command is run unasked by
177
- * `do-everything` and by the sync tick but those callers build
177
+ * `doctor` and by the sync tick, but those callers build
178
178
  * `{ kind: "memory", action: "install" }` in code (`doctor-registration.ts`,
179
179
  * `sync-followups-memory.ts`, `onboard-completion.ts`) and never pass through
180
180
  * this parser. The only caller the default ever served was a PERSON typing the
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * One decision table per subcommand that acts on THIS MACHINE: the six setup
3
3
  * doors (`install`, `onboard`, `login`, `pair`, `update`, `upgrade`) plus
4
- * `do-everything`/`fix`, and then `logout`, `start`, `sync`, `analyze`,
4
+ * `doctor`, and then `logout`, `start`, `sync`, `analyze`,
5
5
  * `backfill`, `status`, `sessions`, `serve`, `autostart`, `agent-rules` and the
6
6
  * maintainer's `release`.
7
7
  *
@@ -12,7 +12,7 @@
12
12
  * same defaults, same refusal sentences):
13
13
  *
14
14
  * local-args-collector-setup.ts install, onboard, login, pair, update,
15
- * upgrade, do-everything, fix, logout,
15
+ * upgrade, doctor, logout,
16
16
  * release — getting a machine collecting
17
17
  * local-args-collector-work.ts start, sync, analyze, backfill — the
18
18
  * ongoing collection work an already-set-up
@@ -98,7 +98,7 @@ function incompleteDiscoveryMessage(input) {
98
98
  const nextDepth = maxDepth + 3;
99
99
  const nextRepos = Math.max(maxRepos * 2, found + 50);
100
100
  const retry = [
101
- "cockpit do-everything",
101
+ "cockpit doctor",
102
102
  ...roots.map((root) => `--workspace ${root}`),
103
103
  `--max-depth ${hitRepoCap ? maxDepth : nextDepth}`,
104
104
  `--max-repos ${nextRepos}`,
@@ -71,37 +71,26 @@ export function localSubcommandHelp(command) {
71
71
  [
72
72
  "do-everything",
73
73
  [
74
- "Usage: cockpit do-everything [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--json]",
75
- "",
76
- "Diagnose, repair, and verify this machine. doctor, do-everything, and fix run the same job.",
77
- "Repairs CLI, background sync, sign-in, roots, memory and hooks, agent rules, catch-up, uploads, and cleanup.",
78
- "--check or --no-repair diagnoses without repairs. --dry-run previews repairs.",
79
- "--lock-wait defaults to 600 seconds; progress prints every 30 seconds.",
80
- "--json includes steps, repairs, and needs_person. Exit 0 means nothing needs you.",
81
- "Maintainers: --update-tag next keeps self-update on the prerelease candidate.",
74
+ "Usage: cockpit do-everything [same flags as cockpit doctor]",
75
+ "Same command as `cockpit doctor`.",
82
76
  ],
83
77
  ],
84
78
  [
85
79
  "fix",
86
80
  [
87
- "Usage: cockpit fix [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--json]",
88
- "",
89
- "Diagnose, repair, and verify this machine. doctor, do-everything, and fix run the same job.",
90
- "Repairs CLI, background sync, sign-in, roots, memory and hooks, agent rules, catch-up, uploads, and cleanup.",
91
- "--check or --no-repair diagnoses without repairs. --dry-run previews repairs.",
92
- "--lock-wait defaults to 600 seconds; progress prints every 30 seconds.",
93
- "--json includes steps, repairs, and needs_person. Exit 0 means nothing needs you.",
94
- "Maintainers: --update-tag next keeps self-update on the prerelease candidate.",
81
+ "Usage: cockpit fix [same flags as cockpit doctor]",
82
+ "Same command as `cockpit doctor`.",
95
83
  ],
96
84
  ],
97
85
  [
98
86
  "doctor",
99
87
  [
100
- "Usage: cockpit doctor [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--json]",
88
+ "Usage: cockpit doctor [--workspace <path>] [--check | --no-repair] [--lock-wait <seconds>] [--backfill-budget <seconds>] [--json]",
101
89
  "",
102
- "Diagnose, repair, and verify this machine. doctor, do-everything, and fix run the same job.",
90
+ "Diagnose, repair, and verify this machine.",
103
91
  "Repairs CLI, background sync, sign-in, roots, memory and hooks, agent rules, catch-up, uploads, and cleanup.",
104
92
  "--check or --no-repair diagnoses without repairs. --dry-run previews repairs.",
93
+ "--backfill-budget defaults to 900 seconds; catch-up repeats chunks until complete, with progress every 30 seconds.",
105
94
  "--lock-wait defaults to 600 seconds; progress prints every 30 seconds.",
106
95
  "--json includes steps, repairs, and needs_person. Exit 0 means nothing needs you.",
107
96
  "Maintainers: --update-tag next keeps self-update on the prerelease candidate.",
@@ -598,7 +587,7 @@ export function localSubcommandHelp(command) {
598
587
  "and for Codex (~/.codex/config.toml, ~/.codex/skills/bli-memory/).",
599
588
  "Idempotent: it merges with what is already there, never duplicates its own",
600
589
  "entries, and reads the stored config back before reporting success.",
601
- "`do-everything` runs it, and the sync tick re-runs it at most once a day.",
590
+ "`cockpit doctor` runs it, and the sync tick re-runs it at most once a day.",
602
591
  "See docs/runbooks/bli-memory-install.md.",
603
592
  "log appends an opinion to ~/.codex/AGENT-EXPERIENCE.md and posts it to Tower.",
604
593
  "Use --reason-stdin instead of a quoted reason; --json reports shipped status.",
@@ -55,6 +55,8 @@ export const rootCommandNames = new Set([
55
55
  "careers",
56
56
  "usage",
57
57
  ]);
58
+ /** Public names omit compatibility spellings while the router keeps accepting them. */
59
+ export const publicCommandNames = new Set([...rootCommandNames].filter((name) => name !== "do-everything" && name !== "fix"));
58
60
  export function localCommandHelp(command) {
59
61
  if (command)
60
62
  return localSubcommandHelp(command);
@@ -62,9 +64,7 @@ export function localCommandHelp(command) {
62
64
  " cockpit onboard [--ticket <id>] [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--workspace <path>] [--allow-home-root] [--branch <name>] [--no-auth] [--max-depth <n>] [--max-repos <n>] [--json]",
63
65
  " cockpit update [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--workspace <path>] [--allow-home-root] [--no-auth] [--json]",
64
66
  " cockpit upgrade [same flags as update]",
65
- " cockpit do-everything [--workspace <path>] [--dashboard-url <url>] [--update-tag <tag>] [--dry-run] [--json]",
66
- " cockpit fix [same flags as do-everything]",
67
- " cockpit doctor [--check | --no-repair] [--lock-wait <seconds>] [--json]",
67
+ " cockpit doctor [--check | --no-repair] [--lock-wait <seconds>] [--backfill-budget <seconds>] [--json]",
68
68
  " cockpit install [--dashboard-url <url>] [--workspace <path>] [--allow-home-root] [--json]",
69
69
  " cockpit login [--pair <code>] [--no-browser] [--legacy-pair] [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--no-auth] [--json]",
70
70
  " cockpit pair [--pair <code>] [--no-browser] [--legacy-pair] [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--no-auth] [--json]",
@@ -6,7 +6,7 @@
6
6
  * hosts talk to. Nothing about that reaches an intern's machine unless
7
7
  * something puts it there — the vendor plugin never had a fleet install path,
8
8
  * which is why every machine was configured by hand. This is that path, and it
9
- * does not ask: `do-everything` runs it, and the sync tick re-runs it at most
9
+ * does not ask: `doctor` runs it, and the sync tick re-runs it at most
10
10
  * once a day so a machine converges without anyone typing anything.
11
11
  *
12
12
  * What it writes, and where, is in the two halves:
@@ -137,11 +137,11 @@ export function nextStepForOnboardBlocker(blocker, options = {}) {
137
137
  case "ticket_binding":
138
138
  return "Run `cockpit start --ticket <id>` when actual ticket work begins, then run `cockpit sync`.";
139
139
  case "device_pairing":
140
- return "Ask Edward to approve this machine in the dashboard under Ambient -> Collector approvals, then run `cockpit onboard` again.";
140
+ return "Ask Edward to approve this machine in the dashboard under Ambient -> Collector approvals, then run `cockpit doctor` again.";
141
141
  case "network_or_ingest":
142
- return "Check dashboard URL/network, then run `cockpit sync --json` or rerun `cockpit onboard`.";
142
+ return "Check dashboard URL/network, then run `cockpit doctor`.";
143
143
  case "install":
144
- return "Rerun `cockpit onboard` from the repo root; it will reinstall local config.";
144
+ return "Rerun `cockpit doctor` from the repo root; it will reinstall local config.";
145
145
  case "work_context":
146
146
  return "Run `cockpit start --ticket <id> --workspace \"$PWD\"`, then retry `cockpit sync`.";
147
147
  default:
@@ -7,7 +7,7 @@ export async function runCockpitCli(argv, io) {
7
7
  // commands/root.ts, so the routing has to be mirrored here or the fix
8
8
  // ships to nobody.
9
9
  if (!command) {
10
- return runLocalCockpitCli(["do-everything"], io);
10
+ return runLocalCockpitCli(["doctor"], io);
11
11
  }
12
12
  if (command === "--help" || command === "-h") {
13
13
  writeLine(io?.stdout ?? process.stdout, cockpitHelp());
@@ -15,7 +15,7 @@ export async function runCockpitCli(argv, io) {
15
15
  }
16
16
 
17
17
  if (command === "--version" || command === "-V" || command === "version") {
18
- writeLine(io?.stdout ?? process.stdout, "0.2.101");
18
+ writeLine(io?.stdout ?? process.stdout, "0.2.103");
19
19
  return 0;
20
20
  }
21
21
 
@@ -31,7 +31,7 @@ export async function runCockpitCli(argv, io) {
31
31
 
32
32
  function cockpitHelp() {
33
33
  return [
34
- "Just run `cockpit do-everything` it takes this machine from nothing to collecting and asks you anything it needs.",
34
+ "Just run `cockpit doctor`, it takes this machine from nothing to collecting and asks you anything it needs.",
35
35
  "(Running plain `cockpit` does the same thing.)",
36
36
  "",
37
37
  "Everything below is for scripting and agents.",
@@ -39,11 +39,8 @@ function cockpitHelp() {
39
39
  "Usage:",
40
40
  localCommandHelp(),
41
41
  "",
42
- "Install: `npm install -g @bli-cockpit/cli@latest`.",
43
- "Fix everything: run `cockpit doctor`. `do-everything` and `fix` are exact aliases; --check diagnoses without repairs.",
44
- "Update: run `cockpit update` to refresh the global CLI and rerun onboarding checks.",
45
- "Intern path: run `cockpit onboard`; it confirms a `/BLI` collection root before syncing.",
46
- "Headless/reused laptop path: `cockpit onboard --email <email> --workspace ~/BLI`.",
42
+ "Install: `npm i -g @bli-cockpit/cli && cockpit doctor`.",
43
+ "Repair and verify: run `cockpit doctor`; --check diagnoses without repairs.",
47
44
  "Already onboarded: run `cockpit doctor` from anywhere to repair and verify this machine.",
48
45
  "Agent setup: `cockpit doctor` installs and verifies AGENTS.md/CLAUDE.md rules.",
49
46
  "Dashboard URL is optional for normal production use; pass `--dashboard-url` only for staging/custom dashboards or forced re-pairing.",
@@ -65,7 +65,7 @@ function sayNoProcessRunner() {
65
65
  console.error("[autostart-self-heal] no process runner on this io; the repair could not be attempted", JSON.stringify({
66
66
  reason: "runner_unavailable",
67
67
  platform: process.platform,
68
- next_action: "reinstall the CLI (npm i -g @bli-cockpit/cli) and run `cockpit autostart install`",
68
+ next_action: "reinstall the CLI (npm i -g @bli-cockpit/cli && cockpit doctor) and run `cockpit autostart install`",
69
69
  }));
70
70
  }
71
71
  /** The same fact as a receipt, so the fleet table can see it too. */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Follow-up three: keep BLI Memory registered with both agent hosts (BLI-3580).
3
3
  *
4
- * Nobody is going to be asked to install a hook. `do-everything` registers it
4
+ * Nobody is going to be asked to install a hook. `doctor` registers it
5
5
  * on the way through, and this puts it back if a host config is edited,
6
6
  * replaced, or restored from a machine that never had it — at most once a day,
7
7
  * because the steady state is "already current" and re-proving that every
@@ -4,7 +4,7 @@ import { getCollectorRuntimePaths, LOCAL_COLLECTOR_VERSION, } from "../local-sta
4
4
  import { envWithNodeRuntimeOnPath, runScheduledSelfUpdate, } from "../scheduled-self-update.js";
5
5
  /**
6
6
  * BLI-2601: the fleet keeps itself current on npm `latest` without anyone
7
- * re-running `npm i -g @bli-cockpit/cli` by hand after day 0. This always
7
+ * re-running `npm i -g @bli-cockpit/cli && cockpit doctor` by hand after day 0. This always
8
8
  * runs AFTER `runSync` has already decided and reported collection's own
9
9
  * outcome above — a stuck or failing self-update can never block or delay
10
10
  * collection, and a collection failure never blocks the chance to
@@ -3,7 +3,7 @@
3
3
  * `bli-memory` (BLI-3706, "Agent-friendly is the definition of done").
4
4
  *
5
5
  * Rides `cockpit memory install`/`cockpit memory status` rather than a new
6
- * verb: that command already runs unasked from `do-everything` and the daily
6
+ * verb: that command already runs unasked from `doctor` and the daily
7
7
  * sync tick (`memory-install.ts`'s own header), which is the ONLY way a
8
8
  * registration reaches every intern machine without anyone typing anything.
9
9
  * A second, un-invoked `mcp install` verb of its own would ship the feature
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A run that dies must not be able to say it worked (BLI-4110).
3
3
  *
4
- * On 2026-09-09 `cockpit do-everything` on the founder's Windows box reached
4
+ * On 2026-09-09 `cockpit doctor` on the founder's Windows box reached
5
5
  * "windows task repair converged", printed `Error: read ENOTCONN` from
6
6
  * `child_process.spawn`, and **exited 0**. That is the worse half of that
7
7
  * incident. A repair that fails loudly gets looked at; a repair that dies
@@ -56,7 +56,7 @@ export function createCapturedExecRunner(options = {}) {
56
56
  * the Windows Task Scheduler — and any other non-interactive host — the
57
57
  * parent's stdin is not a console: it can be a pipe nobody is writing to, or a
58
58
  * socket that was never connected, and reading it raises `read ENOTCONN` from
59
- * inside `child_process.spawn`. That is the error `cockpit do-everything`
59
+ * inside `child_process.spawn`. That is the error `cockpit doctor`
60
60
  * printed on the founder's box on 2026-09-09, from `reexecDoctor`, in a
61
61
  * non-interactive shell.
62
62
  *
@@ -66,7 +66,7 @@ export function createCapturedExecRunner(options = {}) {
66
66
  * this whole ticket is about.
67
67
  *
68
68
  * **stdin is inherited only when there is a console to read from.** A
69
- * re-execed `do-everything` under a scheduler has nothing to type at it; the
69
+ * re-execed `doctor` under a scheduler has nothing to type at it; the
70
70
  * handle was pure liability. `"ignore"` gives the child a real, closed stdin
71
71
  * rather than a broken one, so a child that does read it gets EOF instead of
72
72
  * an error.
@@ -26,7 +26,7 @@ export function envWithNodeRuntimeOnPath(env, nodeExecutable = process.execPath)
26
26
  /**
27
27
  * BLI-2601. Runs at most once per day from inside `cockpit sync`'s
28
28
  * post-collection tail, so the fleet converges on npm `latest` without
29
- * anyone re-running `npm i -g @bli-cockpit/cli` by hand after day 0.
29
+ * anyone re-running `npm i -g @bli-cockpit/cli && cockpit doctor` by hand after day 0.
30
30
  *
31
31
  * Deliberately does not re-exec: this process already has the OLD code
32
32
  * loaded in memory, so nothing in-process could prove a re-exec actually
@@ -46,7 +46,7 @@ async function readPairedCollector(paths) {
46
46
  ...describeError(error),
47
47
  }));
48
48
  }
49
- throw new LocalUploadBlockedError("not_installed", "Local collector config missing. Install/update the CLI, then run `cockpit do-everything` before `cockpit sync`.", "npm install -g @bli-cockpit/cli@latest && cockpit do-everything");
49
+ throw new LocalUploadBlockedError("not_installed", "Local collector config missing. Install/update the CLI, then run `cockpit doctor` before `cockpit sync`.", "npm i -g @bli-cockpit/cli && cockpit doctor");
50
50
  });
51
51
  const sessionFile = await readLocalCollectorSessionFile(paths).catch((error) => {
52
52
  // Same trap on the pairing half: `unpaired` sends the operator to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.101",
3
+ "version": "0.2.103",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,8 +27,8 @@
27
27
  "test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
28
28
  },
29
29
  "dependencies": {
30
- "@bli-cockpit/memory-mcp": "0.1.26",
31
- "@bli-cockpit/mcp": "0.1.32",
30
+ "@bli-cockpit/memory-mcp": "0.1.27",
31
+ "@bli-cockpit/mcp": "0.1.33",
32
32
  "@bli-cockpit/telemetry-core": "0.1.43"
33
33
  }
34
34
  }