@bli-cockpit/cli 0.2.29 → 0.2.30
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 +22 -15
- package/dist/commands/collection-report.js +25 -21
- package/dist/commands/doctor.js +19 -17
- package/dist/commands/local-help.js +9 -9
- package/dist/commands/local.js +14 -14
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/status.js +65 -26
- package/dist/raw-evidence-gc.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@ added <packages> in <seconds>s
|
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
$ cockpit do-everything
|
|
16
|
-
|
|
16
|
+
Setting up Cockpit
|
|
17
17
|
Dashboard: https://bli-cockpit-dashboard.vercel.app
|
|
18
|
-
Ticket: general
|
|
18
|
+
Ticket: none (general work)
|
|
19
19
|
What's your @buildlaunchiterate.ca email? (press enter to skip): ian@buildlaunchiterate.ca
|
|
20
20
|
Signing in as ian@buildlaunchiterate.ca.
|
|
21
21
|
Code sent; valid 1h, resend in 60s by rerunning this command.
|
|
@@ -28,7 +28,7 @@ What you can do:
|
|
|
28
28
|
Code: 482913
|
|
29
29
|
Signed in as ian@buildlaunchiterate.ca.
|
|
30
30
|
2/5 Device paired.
|
|
31
|
-
PASS: Cockpit
|
|
31
|
+
PASS: Cockpit is set up and collecting.
|
|
32
32
|
You're live.
|
|
33
33
|
Dashboard: https://bli-cockpit-dashboard.vercel.app/my-work
|
|
34
34
|
Next: cockpit status
|
|
@@ -36,9 +36,15 @@ Next: cockpit status
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
$ cockpit status
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
42
48
|
```
|
|
43
49
|
|
|
44
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.
|
|
@@ -47,13 +53,14 @@ The OTP proves you own an approved BLI mailbox. The JWT is used once to register
|
|
|
47
53
|
|
|
48
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.
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
receipt or
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
Cockpit works the same way on an Apple Silicon Mac and on a normal Windows
|
|
57
|
+
PC. Every Codex and Claude session in a folder you have approved is collected,
|
|
58
|
+
stripped of anything sensitive before it leaves your machine, and then either
|
|
59
|
+
uploaded with a receipt or listed with the exact reason it was not. If a
|
|
60
|
+
session is missing, Cockpit says so — a green screen never hides one.
|
|
61
|
+
|
|
62
|
+
We only publish a version to npm after it has been tested end-to-end, as a
|
|
63
|
+
normal user, on both macOS and Windows.
|
|
57
64
|
|
|
58
65
|
| Command | What it does | Why it exists / why this name |
|
|
59
66
|
|---|---|---|
|
|
@@ -171,11 +178,11 @@ Local files:
|
|
|
171
178
|
|
|
172
179
|
- `~/.config/bli-cockpit/config.json`: dashboard URL, device label, collection roots.
|
|
173
180
|
- `~/.config/bli-cockpit/session.json`: paired device token and owner metadata.
|
|
174
|
-
- `~/.local/state/bli-cockpit/spool/`:
|
|
181
|
+
- `~/.local/state/bli-cockpit/spool/`: uploads waiting to be retried.
|
|
175
182
|
- `~/.local/state/bli-cockpit/spool/install-events/`: private atomic collector
|
|
176
183
|
health receipts waiting for authenticated delivery. Receipts contain only
|
|
177
184
|
sanitized operation metadata, never command output or transcript content.
|
|
178
|
-
- `~/.local/state/bli-cockpit/cursors/`:
|
|
185
|
+
- `~/.local/state/bli-cockpit/cursors/`: bookmarks for how far uploading has got, no raw content.
|
|
179
186
|
- `.codex-autorunner/contextspace/active_context.md`: current work context inside a repo.
|
|
180
187
|
|
|
181
188
|
Remote data:
|
|
@@ -4,7 +4,7 @@ export function shortSha(value) {
|
|
|
4
4
|
return value ? value.slice(0, 12) : "unknown";
|
|
5
5
|
}
|
|
6
6
|
export function displayTicketId(ticketId) {
|
|
7
|
-
return ticketId ?? "general
|
|
7
|
+
return ticketId ?? "none (general work)";
|
|
8
8
|
}
|
|
9
9
|
export function displayWorkLabel(status) {
|
|
10
10
|
if (status.work_label && status.work_id)
|
|
@@ -12,13 +12,16 @@ export function displayWorkLabel(status) {
|
|
|
12
12
|
return status.work_label ?? status.work_id ?? "no active work context";
|
|
13
13
|
}
|
|
14
14
|
function sourceFunnelLine(label, counts) {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// Same counters, plain words. Every counter still prints, including the ones
|
|
16
|
+
// that are zero: a funnel that hides a bucket cannot show where sessions go.
|
|
17
|
+
const readFailures = counts.read_failures > 0 ? `, ${counts.read_failures} could not be read` : "";
|
|
18
|
+
return `${label} sessions: ${counts.attributed} matched to a repo, ${counts.attributed_fallback} by best guess, ${counts.ambiguous} unclear which repo, ${counts.unattributed} could not be matched, ${counts.stale} too old, ${counts.skipped} skipped${readFailures}`;
|
|
17
19
|
}
|
|
18
20
|
function attributionReportLine(summary) {
|
|
21
|
+
// The parenthesised value is the machine reason label and never changes.
|
|
19
22
|
return summary.report_posted
|
|
20
|
-
? "
|
|
21
|
-
: `
|
|
23
|
+
? "Session report: saved"
|
|
24
|
+
: `Session report: skipped (${summary.report_reason})`;
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* One funnel line per source, an anomaly diagnostics line only when something
|
|
@@ -41,30 +44,31 @@ function claudeDiagnosticsLine(summary) {
|
|
|
41
44
|
const claude = summary.claude;
|
|
42
45
|
const parts = [];
|
|
43
46
|
if (claude.sidecars_skipped)
|
|
44
|
-
parts.push(
|
|
47
|
+
parts.push(`${claude.sidecars_skipped} helper sessions skipped`);
|
|
45
48
|
if (claude.sidecars_capped)
|
|
46
|
-
parts.push(
|
|
49
|
+
parts.push(`${claude.sidecars_capped} helper sessions cut short`);
|
|
47
50
|
if (claude.sidecars_failed)
|
|
48
|
-
parts.push(
|
|
51
|
+
parts.push(`${claude.sidecars_failed} helper sessions could not be read`);
|
|
49
52
|
if (claude.mains_oversized)
|
|
50
|
-
parts.push(
|
|
53
|
+
parts.push(`${claude.mains_oversized} sessions too big`);
|
|
51
54
|
if (claude.oversized_lines_skipped)
|
|
52
|
-
parts.push(
|
|
55
|
+
parts.push(`${claude.oversized_lines_skipped} oversized lines skipped`);
|
|
53
56
|
if (claude.project_dirs_skipped)
|
|
54
|
-
parts.push(
|
|
57
|
+
parts.push(`${claude.project_dirs_skipped} folders skipped`);
|
|
55
58
|
if (claude.sessions_schema_drift)
|
|
56
|
-
parts.push(
|
|
59
|
+
parts.push(`${claude.sessions_schema_drift} sessions in an unexpected format`);
|
|
57
60
|
if (claude.growth_damped)
|
|
58
|
-
parts.push(
|
|
61
|
+
parts.push(`${claude.growth_damped} fast-growing sessions slowed down`);
|
|
59
62
|
if (claude.first_run_backfill)
|
|
60
|
-
parts.push("
|
|
63
|
+
parts.push("first run, catching up on history");
|
|
61
64
|
if (summary.files_deferred_byte_budget)
|
|
62
|
-
parts.push(
|
|
65
|
+
parts.push(`${summary.files_deferred_byte_budget} files held back, size limit`);
|
|
63
66
|
if (summary.files_deferred_object_budget)
|
|
64
|
-
parts.push(
|
|
65
|
-
return parts.length > 0 ? `Claude
|
|
67
|
+
parts.push(`${summary.files_deferred_object_budget} files held back, file-count limit`);
|
|
68
|
+
return parts.length > 0 ? `Claude problems: ${parts.join(", ")}` : null;
|
|
66
69
|
}
|
|
67
70
|
export function rawEvidenceSyncLine(sync) {
|
|
71
|
+
// The reason lists themselves are machine labels and stay verbatim.
|
|
68
72
|
const failures = sync.raw_evidence_failure_reasons.length > 0
|
|
69
73
|
? ` failures: ${sync.raw_evidence_failure_reasons.join(",")}`
|
|
70
74
|
: "";
|
|
@@ -75,15 +79,15 @@ export function rawEvidenceSyncLine(sync) {
|
|
|
75
79
|
// Neither used to appear anywhere, which is how a 1,030-attempt loop stayed
|
|
76
80
|
// invisible (BLI-3066).
|
|
77
81
|
const held = sync.raw_evidence_delivery_held_count > 0
|
|
78
|
-
?
|
|
82
|
+
? `, ${sync.raw_evidence_delivery_held_count} waiting`
|
|
79
83
|
: "";
|
|
80
84
|
const stuck = sync.raw_evidence_stuck_object_count > 0
|
|
81
|
-
?
|
|
85
|
+
? `, ${sync.raw_evidence_stuck_object_count} stuck (${sync.raw_evidence_max_delivery_attempts} tries since ${sync.raw_evidence_oldest_delivery_failure_at ?? "unknown"})`
|
|
82
86
|
: "";
|
|
83
|
-
return `
|
|
87
|
+
return `Files: ${sync.raw_evidence_uploaded_object_count} uploaded, ${sync.raw_evidence_reused_count} already there, ${sync.raw_evidence_failed_count} failed${held}${stuck}${failures}${retries}`;
|
|
84
88
|
}
|
|
85
89
|
export function cursorStatusLine(sync) {
|
|
86
|
-
return `
|
|
90
|
+
return `Tracked so far: ${sync.cursor_tracked_object_count} uploaded item(s)`;
|
|
87
91
|
}
|
|
88
92
|
/**
|
|
89
93
|
* One line that cannot say "fine" while an object has never been accepted.
|
package/dist/commands/doctor.js
CHANGED
|
@@ -175,7 +175,7 @@ async function readAuthState(context) {
|
|
|
175
175
|
if (session?.session_state === "valid" &&
|
|
176
176
|
typeof session.device_token === "string" &&
|
|
177
177
|
session.device_token) {
|
|
178
|
-
return ok("authed", "device_token_present", "
|
|
178
|
+
return ok("authed", "device_token_present", "this machine is signed in");
|
|
179
179
|
}
|
|
180
180
|
return hardStop("authed", "pairing_required", [
|
|
181
181
|
"device is not signed in.",
|
|
@@ -216,12 +216,12 @@ async function checkAutostartState(context) {
|
|
|
216
216
|
exec,
|
|
217
217
|
});
|
|
218
218
|
if (result.status === "loaded") {
|
|
219
|
-
return ok("autostart-alive", "already_installed", "
|
|
219
|
+
return ok("autostart-alive", "already_installed", "background sync is running");
|
|
220
220
|
}
|
|
221
221
|
if (result.status === "unsupported") {
|
|
222
222
|
return skipped("autostart-alive", "unsupported", result.message ?? "unsupported");
|
|
223
223
|
}
|
|
224
|
-
return needsFix("autostart-alive", result.status === "not_loaded" ? "not_loaded" : "absent", "
|
|
224
|
+
return needsFix("autostart-alive", result.status === "not_loaded" ? "not_loaded" : "absent", "background sync is not running");
|
|
225
225
|
}
|
|
226
226
|
async function fixAutostartState(context) {
|
|
227
227
|
const exec = context.io.exec;
|
|
@@ -241,7 +241,7 @@ async function fixAutostartState(context) {
|
|
|
241
241
|
if (result.loaded === false) {
|
|
242
242
|
return fail("autostart-alive", "autostart_load_failed", result.message ?? "operating-system scheduler load failed");
|
|
243
243
|
}
|
|
244
|
-
return ok("autostart-alive", "installed", "
|
|
244
|
+
return ok("autostart-alive", "installed", "background sync installed and running");
|
|
245
245
|
}
|
|
246
246
|
/**
|
|
247
247
|
* Pure so it can be unit-tested without touching the real machine's home
|
|
@@ -261,10 +261,10 @@ export function backfillCompletionStepState(marker, roots) {
|
|
|
261
261
|
}
|
|
262
262
|
const oversized = marker?.oversized_skips;
|
|
263
263
|
if (oversized && oversized.count > 0) {
|
|
264
|
-
return ok("backfill-complete", "complete_with_oversized_skips", `
|
|
265
|
-
`(complete_with_oversized_skips · ${oversized.count} file${oversized.count === 1 ? "" : "s"}
|
|
264
|
+
return ok("backfill-complete", "complete_with_oversized_skips", `caught up on old Codex and Claude sessions in every saved folder ` +
|
|
265
|
+
`(complete_with_oversized_skips · ${oversized.count} file${oversized.count === 1 ? "" : "s"} too big to upload)`);
|
|
266
266
|
}
|
|
267
|
-
return ok("backfill-complete", "complete", "
|
|
267
|
+
return ok("backfill-complete", "complete", "caught up on old Codex and Claude sessions in every saved folder");
|
|
268
268
|
}
|
|
269
269
|
async function checkBackfillState(context) {
|
|
270
270
|
const paths = getCollectorRuntimePaths();
|
|
@@ -275,10 +275,10 @@ async function checkBackfillState(context) {
|
|
|
275
275
|
return covered;
|
|
276
276
|
const lock = await inspectBackfillLock(paths);
|
|
277
277
|
if (lock.held) {
|
|
278
|
-
return needsFix("backfill-complete", "backfill_already_running", `
|
|
278
|
+
return needsFix("backfill-complete", "backfill_already_running", `another catch-up run is still going, and this one has not finished yet (running since ${lock.held_since ?? "unknown"})`);
|
|
279
279
|
}
|
|
280
280
|
const cursor = await readBackfillCursor(paths);
|
|
281
|
-
return needsFix("backfill-complete", cursor.updated_at ? "partial" : "never_run", "
|
|
281
|
+
return needsFix("backfill-complete", cursor.updated_at ? "partial" : "never_run", "the catch-up over your old sessions has not finished");
|
|
282
282
|
}
|
|
283
283
|
async function fixBackfillState(context) {
|
|
284
284
|
const capture = capturedIo(context.io, !context.command.json);
|
|
@@ -308,20 +308,20 @@ async function fixBackfillState(context) {
|
|
|
308
308
|
}
|
|
309
309
|
async function checkGcState(context) {
|
|
310
310
|
if (context.io.env["COCKPIT_DISABLE_GC"] === "1") {
|
|
311
|
-
return skipped("gc-checked", "skipped_disabled", "
|
|
311
|
+
return skipped("gc-checked", "skipped_disabled", "cleanup is switched off");
|
|
312
312
|
}
|
|
313
313
|
const paths = getCollectorRuntimePaths();
|
|
314
314
|
const marker = path.join(paths.state_dir, ".last-raw-evidence-gc");
|
|
315
315
|
const info = await fs.stat(marker).catch(() => null);
|
|
316
316
|
if (info && Date.now() - info.mtimeMs < GC_MIN_INTERVAL_MS) {
|
|
317
|
-
return skipped("gc-checked", "skipped_throttled", "
|
|
317
|
+
return skipped("gc-checked", "skipped_throttled", "cleanup already ran today");
|
|
318
318
|
}
|
|
319
|
-
return needsFix("gc-checked", "due", "
|
|
319
|
+
return needsFix("gc-checked", "due", "cleanup is due");
|
|
320
320
|
}
|
|
321
321
|
async function fixGcState(context) {
|
|
322
322
|
const result = await runRawEvidenceLocalGc(getCollectorRuntimePaths(), context.io.env);
|
|
323
323
|
if (result.skipped) {
|
|
324
|
-
return skipped("gc-checked", "skipped_throttled", "
|
|
324
|
+
return skipped("gc-checked", "skipped_throttled", "cleanup already ran today");
|
|
325
325
|
}
|
|
326
326
|
if (result.removed_dirs === 0) {
|
|
327
327
|
return ok("gc-checked", "nothing_eligible", rawEvidenceGcSummary(result));
|
|
@@ -422,8 +422,8 @@ async function fixSyncState(context) {
|
|
|
422
422
|
if (result.code !== 0) {
|
|
423
423
|
const draining = syncBacklogDrainingVerdict(parsed);
|
|
424
424
|
if (draining) {
|
|
425
|
-
return needsFix("sync-fresh", "backlog_draining", `${repoRoot}:
|
|
426
|
-
`
|
|
425
|
+
return needsFix("sync-fresh", "backlog_draining", `${repoRoot}: still catching up (${draining.remainingObjects} ` +
|
|
426
|
+
`file${draining.remainingObjects === 1 ? "" : "s"} left for later this run); ` +
|
|
427
427
|
"rerun `cockpit sync` to continue");
|
|
428
428
|
}
|
|
429
429
|
return fail("sync-fresh", status ?? "sync_failed", `sync failed for ${repoRoot}`);
|
|
@@ -464,9 +464,11 @@ function writeDoctorOutput(command, io, rows) {
|
|
|
464
464
|
return;
|
|
465
465
|
}
|
|
466
466
|
writeLine(io.stdout, command.dryRun ? "Cockpit doctor dry-run" : "Cockpit doctor");
|
|
467
|
-
|
|
467
|
+
// The machine `code` stays in `--json`; a human reading the table wants the
|
|
468
|
+
// sentence, not the label (BLI-3194).
|
|
469
|
+
writeLine(io.stdout, "state step result");
|
|
468
470
|
for (const row of rows) {
|
|
469
|
-
writeLine(io.stdout, `${doctorMark(row)} ${row.id.padEnd(20)} ${
|
|
471
|
+
writeLine(io.stdout, `${doctorMark(row)} ${row.id.padEnd(20)} ${oneLine(row.message)}`);
|
|
470
472
|
}
|
|
471
473
|
const explanations = rows.filter((row) => (row.hardStop || row.status === "fail") && row.message.includes("\n"));
|
|
472
474
|
for (const row of explanations) {
|
|
@@ -64,7 +64,7 @@ function localSubcommandHelp(command) {
|
|
|
64
64
|
"Usage: 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]",
|
|
65
65
|
"",
|
|
66
66
|
"Installs, pairs, starts work context(s), syncs once, completes all-history",
|
|
67
|
-
"Codex and Claude backfill for the saved roots, and then
|
|
67
|
+
"Codex and Claude backfill for the saved roots, and then tells you it is working.",
|
|
68
68
|
"If --workspace is a parent folder, scans child git repos/worktrees and rolls them up by repo.",
|
|
69
69
|
"`--repo <path>` remains supported as a backward-compatible alias.",
|
|
70
70
|
`Omit --dashboard-url for normal production setup (${DEFAULT_DASHBOARD_URL}).`,
|
|
@@ -107,10 +107,10 @@ function localSubcommandHelp(command) {
|
|
|
107
107
|
[
|
|
108
108
|
"Usage: cockpit do-everything [--workspace <path>] [--dashboard-url <url>] [--update-tag <tag>] [--dry-run] [--json]",
|
|
109
109
|
"",
|
|
110
|
-
"
|
|
110
|
+
"Gets this machine fully set up, whether it is brand new, already set up, or handed down: latest CLI, sign-in and collection-root recovery when needed, saved folders, background sync, catching up on old sessions, cleaning up old files, and one fresh upload.",
|
|
111
111
|
"`cockpit fix` is an alias.",
|
|
112
|
-
"
|
|
113
|
-
"--dry-run
|
|
112
|
+
"Maintainers only: use `--update-tag next` so self-update and re-exec stay on the prerelease candidate.",
|
|
113
|
+
"--dry-run shows what it would do without changing anything.",
|
|
114
114
|
],
|
|
115
115
|
],
|
|
116
116
|
[
|
|
@@ -144,9 +144,9 @@ function localSubcommandHelp(command) {
|
|
|
144
144
|
[
|
|
145
145
|
"Usage: cockpit start [--ticket <id>|--clear-ticket] [--topic <label>] [--topic-summary <summary>] [--intent <intent>] [--phase <phase>] [--intent-confidence <0..1>] [--workspace <path>] [--branch <name>] [--json]",
|
|
146
146
|
"",
|
|
147
|
-
"Starts
|
|
147
|
+
"Starts collecting your work in the background. If you point it at a parent folder it covers every repo inside.",
|
|
148
148
|
"Add --ticket only when the work already has a visible ticket; omit it to preserve an existing binding.",
|
|
149
|
-
"Use --clear-ticket to
|
|
149
|
+
"Use --clear-ticket to go back to collecting general work with no ticket attached.",
|
|
150
150
|
"Use --topic/--intent/--phase for planning, discovery, and learning work that has no ticket yet.",
|
|
151
151
|
"Supported intents: implementation, bug_fix, root_cause_analysis, planning, discovery, review, testing, documentation, release, learning, coordination, maintenance, analysis, unknown, other.",
|
|
152
152
|
"Supported phases: planning, discovery, implementation, debugging, review, testing, documentation, release, handoff, analysis, unknown, other.",
|
|
@@ -158,7 +158,7 @@ function localSubcommandHelp(command) {
|
|
|
158
158
|
[
|
|
159
159
|
"Usage: cockpit sync [--workspace <path>] [--dashboard-url <url>] [--json]",
|
|
160
160
|
"",
|
|
161
|
-
"Uploads latest
|
|
161
|
+
"Uploads your latest collected work. If it cannot reach Cockpit it saves a retry and tries again later.",
|
|
162
162
|
"Omit --dashboard-url for normal production sync; pass it only for staging/custom dashboards or forced re-pairing.",
|
|
163
163
|
"`--repo <path>` remains supported as a backward-compatible alias.",
|
|
164
164
|
"Parent folders sync each child git worktree; Codex AND Claude Code JSONL",
|
|
@@ -166,7 +166,7 @@ function localSubcommandHelp(command) {
|
|
|
166
166
|
"deterministically and ambiguous transcripts are retained as unattributed",
|
|
167
167
|
"instead of being duplicated across repos. Use `cockpit sessions` to see why",
|
|
168
168
|
"a session is or is not collected.",
|
|
169
|
-
"
|
|
169
|
+
"New repos start being collected automatically, with no ticket attached.",
|
|
170
170
|
"Discovery scans 3 folder levels and up to 50 repos by default; tune with",
|
|
171
171
|
"--max-depth and --max-repos.",
|
|
172
172
|
"Also self-updates the CLI from npm latest once per day, strictly after",
|
|
@@ -179,7 +179,7 @@ function localSubcommandHelp(command) {
|
|
|
179
179
|
[
|
|
180
180
|
"Usage: cockpit analyze [--workspace <path>] [--dashboard-url <url>] [--json]",
|
|
181
181
|
"",
|
|
182
|
-
"Uploads
|
|
182
|
+
"Uploads your latest collected work, then asks Cockpit to analyse it.",
|
|
183
183
|
"The command returns after the batch is queued; view status and results in My Work.",
|
|
184
184
|
"Omit --dashboard-url for production; pass it only for staging/custom dashboards.",
|
|
185
185
|
"`--repo <path>` remains supported as a backward-compatible alias.",
|
package/dist/commands/local.js
CHANGED
|
@@ -306,9 +306,9 @@ function backgroundSyncLine(result) {
|
|
|
306
306
|
return result.status;
|
|
307
307
|
}
|
|
308
308
|
function writeOnboardBanner(command, io) {
|
|
309
|
-
writeLine(io.stdout, "
|
|
309
|
+
writeLine(io.stdout, "Setting up Cockpit");
|
|
310
310
|
writeLine(io.stdout, `Dashboard: ${command.dashboardUrl}`);
|
|
311
|
-
writeLine(io.stdout, `Ticket: ${command.activeTicketId
|
|
311
|
+
writeLine(io.stdout, `Ticket: ${displayTicketId(command.activeTicketId)}`);
|
|
312
312
|
}
|
|
313
313
|
/**
|
|
314
314
|
* Step 2 of onboarding: reuse the device session when it already belongs to
|
|
@@ -525,7 +525,7 @@ async function runOnboard(command, io) {
|
|
|
525
525
|
}, null, 2));
|
|
526
526
|
}
|
|
527
527
|
if (onboardOk && !command.json) {
|
|
528
|
-
writeLine(io.stdout, "PASS: Cockpit
|
|
528
|
+
writeLine(io.stdout, "PASS: Cockpit is set up and collecting.");
|
|
529
529
|
writeOnboardLiveStatus(io, resolvedCommand, collectionRoots, {
|
|
530
530
|
pair,
|
|
531
531
|
status,
|
|
@@ -552,7 +552,7 @@ async function runOnboard(command, io) {
|
|
|
552
552
|
writeLine(io.stdout, "3/5 Work context active.");
|
|
553
553
|
writeLine(io.stdout, `Repo: ${context.repo}`);
|
|
554
554
|
writeLine(io.stdout, `Branch: ${context.branch}`);
|
|
555
|
-
writeLine(io.stdout, `Ticket: ${context.active_ticket_id
|
|
555
|
+
writeLine(io.stdout, `Ticket: ${displayTicketId(context.active_ticket_id)}`);
|
|
556
556
|
writeLine(io.stdout, `Context: ${context.work_context_id}`);
|
|
557
557
|
}
|
|
558
558
|
addInstallEvent(installEvents, "work_context", "ok");
|
|
@@ -638,9 +638,9 @@ async function runOnboard(command, io) {
|
|
|
638
638
|
}, null, 2));
|
|
639
639
|
return finish(onboardOk ? 0 : 1);
|
|
640
640
|
}
|
|
641
|
-
writeLine(io.stdout, "4/5
|
|
641
|
+
writeLine(io.stdout, "4/5 Uploaded what you worked on.");
|
|
642
642
|
writeLine(io.stdout, `HTTP: ${sync.http_status}`);
|
|
643
|
-
writeLine(io.stdout, `
|
|
643
|
+
writeLine(io.stdout, `Things recorded: ${sync.event_count}`);
|
|
644
644
|
writeLine(io.stdout, `Sources: ${sync.source_scan_count}`);
|
|
645
645
|
writeLine(io.stdout, `Risk flags: ${sync.risk_flag_count}`);
|
|
646
646
|
writeLine(io.stdout, `Raw evidence files: ${sync.raw_evidence_file_count}`);
|
|
@@ -656,7 +656,7 @@ async function runOnboard(command, io) {
|
|
|
656
656
|
writeOnboardAutostartBlocker(io, autostart);
|
|
657
657
|
return finish(1);
|
|
658
658
|
}
|
|
659
|
-
writeLine(io.stdout, "PASS: Cockpit
|
|
659
|
+
writeLine(io.stdout, "PASS: Cockpit is set up and collecting.");
|
|
660
660
|
writeOnboardLiveStatus(io, resolvedCommand, collectionRoots, {
|
|
661
661
|
pair,
|
|
662
662
|
status,
|
|
@@ -839,7 +839,7 @@ function nextStepForOnboardBlocker(blocker, options = {}) {
|
|
|
839
839
|
case "ticket_binding":
|
|
840
840
|
return "Run `cockpit start --ticket <id>` when actual ticket work begins, then run `cockpit sync`.";
|
|
841
841
|
case "device_pairing":
|
|
842
|
-
return "
|
|
842
|
+
return "Ask Edward to approve this machine in the dashboard under Ambient -> Collector approvals, then run `cockpit onboard` again.";
|
|
843
843
|
case "network_or_ingest":
|
|
844
844
|
return "Check dashboard URL/network, then run `cockpit sync --json` or rerun `cockpit onboard`.";
|
|
845
845
|
case "install":
|
|
@@ -1125,7 +1125,7 @@ async function runSyncWithHealthReceipt(command, io) {
|
|
|
1125
1125
|
}, null, 2));
|
|
1126
1126
|
}
|
|
1127
1127
|
else {
|
|
1128
|
-
writeLine(io.stdout, "
|
|
1128
|
+
writeLine(io.stdout, "Pausing normal sync while it catches up on old sessions.");
|
|
1129
1129
|
}
|
|
1130
1130
|
return {
|
|
1131
1131
|
exitCode: 0,
|
|
@@ -1300,11 +1300,11 @@ async function runSyncLocked(command, io) {
|
|
|
1300
1300
|
return syncResult(run);
|
|
1301
1301
|
}
|
|
1302
1302
|
if (run.ok) {
|
|
1303
|
-
writeLine(io.stdout, "Cockpit
|
|
1303
|
+
writeLine(io.stdout, "Cockpit uploaded this session.");
|
|
1304
1304
|
writeLine(io.stdout, `Ticket: ${displayTicketId(result.ticket_id)}`);
|
|
1305
1305
|
writeLine(io.stdout, `Context: ${result.work_context_id}`);
|
|
1306
1306
|
writeLine(io.stdout, `Head: ${shortSha(result.head_sha)}`);
|
|
1307
|
-
writeLine(io.stdout, `
|
|
1307
|
+
writeLine(io.stdout, `Things recorded: ${result.event_count}`);
|
|
1308
1308
|
writeLine(io.stdout, `Risk flags: ${result.risk_flag_count}`);
|
|
1309
1309
|
writeLine(io.stdout, `Raw evidence files: ${result.raw_evidence_file_count}`);
|
|
1310
1310
|
writeLine(io.stdout, rawEvidenceSyncLine(result));
|
|
@@ -1315,11 +1315,11 @@ async function runSyncLocked(command, io) {
|
|
|
1315
1315
|
return syncResult(run);
|
|
1316
1316
|
}
|
|
1317
1317
|
if (result.status === "uploaded") {
|
|
1318
|
-
writeLine(io.stderr, "Cockpit
|
|
1318
|
+
writeLine(io.stderr, "Cockpit uploaded, but some sessions did not make it. Run `cockpit sync` again.");
|
|
1319
1319
|
writeAgentSessionSummary(io, run.summary);
|
|
1320
1320
|
return syncResult(run);
|
|
1321
1321
|
}
|
|
1322
|
-
writeLine(io.stderr, "Cockpit
|
|
1322
|
+
writeLine(io.stderr, "Cockpit could not upload. It saved a note to retry and will try again on the next sync.");
|
|
1323
1323
|
writeLine(io.stderr, `Ticket: ${displayTicketId(result.ticket_id)}`);
|
|
1324
1324
|
writeLine(io.stderr, `Failure: ${result.failure_reason}`);
|
|
1325
1325
|
writeLine(io.stderr, `Retry: ${result.retry_command}`);
|
|
@@ -1419,7 +1419,7 @@ async function runAnalyze(command, io) {
|
|
|
1419
1419
|
return 0;
|
|
1420
1420
|
}
|
|
1421
1421
|
replayCaptured(io.stderr, syncStderr);
|
|
1422
|
-
writeLine(io.stdout, "Cockpit latest
|
|
1422
|
+
writeLine(io.stdout, "Cockpit uploaded your latest work.");
|
|
1423
1423
|
writeLine(io.stdout, "Cockpit analysis queued.");
|
|
1424
1424
|
if (jobId)
|
|
1425
1425
|
writeLine(io.stdout, `Job: ${jobId}`);
|
|
@@ -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.
|
|
18
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.30");
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
21
|
|
package/dist/commands/status.js
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* active work, upload state, retry backlog, and how far historical backfill
|
|
4
4
|
* has got.
|
|
5
5
|
*
|
|
6
|
-
* Read-only. Split out of commands/local.ts (BLI-3104)
|
|
7
|
-
*
|
|
8
|
-
* wrong.
|
|
6
|
+
* Read-only. Split out of commands/local.ts (BLI-3104). BLI-3194 rewrote the
|
|
7
|
+
* human block into plain English, because these lines are what an intern
|
|
8
|
+
* pastes into Slack when something looks wrong. The `--json` payload is the
|
|
9
|
+
* machine contract and is byte-for-byte unchanged — `status-json-shape.test.ts`
|
|
10
|
+
* holds it to that.
|
|
9
11
|
*/
|
|
10
12
|
import { readdir, stat } from "node:fs/promises";
|
|
11
13
|
import os from "node:os";
|
|
@@ -32,8 +34,8 @@ export async function runStatus(command, io) {
|
|
|
32
34
|
writeLine(io.stdout, JSON.stringify({ mode: "multi_repo", statuses, backfill_cursor: backfillCursor }, null, 2));
|
|
33
35
|
return 0;
|
|
34
36
|
}
|
|
35
|
-
writeLine(io.stdout, "Cockpit
|
|
36
|
-
writeLine(io.stdout, `
|
|
37
|
+
writeLine(io.stdout, "Cockpit status — every repo below this folder");
|
|
38
|
+
writeLine(io.stdout, `Old sessions: ${backfillCursorLine(backfillCursor)}`);
|
|
37
39
|
for (const status of statuses) {
|
|
38
40
|
writeLine(io.stdout, `- ${status.repo_label ?? status.repo}/${status.worktree_label ?? "worktree"} · ${status.branch} · head:${shortSha(status.head_sha)} · ${status.upload_state}`);
|
|
39
41
|
}
|
|
@@ -44,24 +46,26 @@ export async function runStatus(command, io) {
|
|
|
44
46
|
writeLine(io.stdout, JSON.stringify({ ...status, backfill_cursor: backfillCursor }, null, 2));
|
|
45
47
|
return 0;
|
|
46
48
|
}
|
|
47
|
-
writeLine(io.stdout, "Cockpit
|
|
48
|
-
writeLine(io.stdout, `
|
|
49
|
-
writeLine(io.stdout, `
|
|
50
|
-
writeLine(io.stdout, `
|
|
51
|
-
writeLine(io.stdout, `
|
|
52
|
-
writeLine(io.stdout, `
|
|
53
|
-
writeLine(io.stdout, `
|
|
54
|
-
writeLine(io.stdout, `
|
|
55
|
-
writeLine(io.stdout, `
|
|
56
|
-
writeLine(io.stdout, `
|
|
57
|
-
writeLine(io.stdout, `
|
|
58
|
-
writeLine(io.stdout, `
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
writeLine(io.stdout, `
|
|
62
|
-
writeLine(io.stdout, `
|
|
63
|
-
writeLine(io.stdout, `
|
|
64
|
-
writeLine(io.stdout, `
|
|
49
|
+
writeLine(io.stdout, "Cockpit status");
|
|
50
|
+
writeLine(io.stdout, `Installed: ${status.installed ? "yes" : "no"}`);
|
|
51
|
+
writeLine(io.stdout, `Signed in: ${signedInLine(status.session_state)}`);
|
|
52
|
+
writeLine(io.stdout, `Repo: ${status.repo}`);
|
|
53
|
+
writeLine(io.stdout, `Branch: ${status.branch}`);
|
|
54
|
+
writeLine(io.stdout, `Ticket: ${displayTicketId(status.active_ticket_id)}`);
|
|
55
|
+
writeLine(io.stdout, `Work: ${displayWorkLabel(status)}`);
|
|
56
|
+
writeLine(io.stdout, `Last collected: ${lastCollectedLine(status.collector_freshness)}`);
|
|
57
|
+
writeLine(io.stdout, `Version: ${status.collector_version}`);
|
|
58
|
+
writeLine(io.stdout, `Uploads: ${uploadsLine(status.upload_state)}`);
|
|
59
|
+
writeLine(io.stdout, `Last upload tried: ${status.last_upload_attempt_at ?? "never"}`);
|
|
60
|
+
writeLine(io.stdout, `Last upload worked: ${status.last_upload_success_at ?? "never"}`);
|
|
61
|
+
// The failure reason itself is a machine label and prints verbatim: a status
|
|
62
|
+
// that hides why the last upload failed is the silent-success failure mode.
|
|
63
|
+
writeLine(io.stdout, `Last upload problem: ${status.last_upload_failure_reason ?? "none"}`);
|
|
64
|
+
writeLine(io.stdout, `Waiting to upload: ${status.pending_upload_count}`);
|
|
65
|
+
writeLine(io.stdout, `Health notes waiting to send: ${status.pending_health_receipt_count}`);
|
|
66
|
+
writeLine(io.stdout, `Last health note problem: ${status.last_health_receipt_failure_reason ?? "none"}`);
|
|
67
|
+
writeLine(io.stdout, `Old sessions: ${backfillCursorLine(backfillCursor)}`);
|
|
68
|
+
writeLine(io.stdout, `Stuck files: ${stuckEvidenceLine(status)}`);
|
|
65
69
|
for (const detail of status.details)
|
|
66
70
|
writeLine(io.stdout, `- ${detail}`);
|
|
67
71
|
return 0;
|
|
@@ -122,11 +126,46 @@ function summarizeBackfillSource(source) {
|
|
|
122
126
|
function backfillCursorLine(status) {
|
|
123
127
|
switch (status.state) {
|
|
124
128
|
case "done":
|
|
125
|
-
return `done (${status.completed_at ?? "
|
|
129
|
+
return `done (${status.completed_at ?? "finished, no date recorded"})`;
|
|
126
130
|
case "never_run":
|
|
127
|
-
return "
|
|
131
|
+
return "not started yet";
|
|
128
132
|
case "remaining":
|
|
129
|
-
return `${status.remaining_count}
|
|
133
|
+
return `${status.remaining_count} still to catch up on`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Plain words for the four session states; `--json` still gets the raw value. */
|
|
137
|
+
function signedInLine(sessionState) {
|
|
138
|
+
switch (sessionState) {
|
|
139
|
+
case "valid":
|
|
140
|
+
return "yes";
|
|
141
|
+
case "expired":
|
|
142
|
+
return "no — the sign-in expired, run `cockpit login`";
|
|
143
|
+
case "missing":
|
|
144
|
+
return "no — run `cockpit login`";
|
|
145
|
+
case "unknown":
|
|
146
|
+
return "cannot tell";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function lastCollectedLine(freshness) {
|
|
150
|
+
switch (freshness) {
|
|
151
|
+
case "fresh":
|
|
152
|
+
return "recently";
|
|
153
|
+
case "stale":
|
|
154
|
+
return "a while ago";
|
|
155
|
+
case "missing":
|
|
156
|
+
return "never";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function uploadsLine(uploadState) {
|
|
160
|
+
switch (uploadState) {
|
|
161
|
+
case "ready":
|
|
162
|
+
return "ready";
|
|
163
|
+
case "retry_pending":
|
|
164
|
+
return "waiting to retry";
|
|
165
|
+
case "local_only_missing_auth":
|
|
166
|
+
return "staying on this machine — not signed in";
|
|
167
|
+
case "not_installed":
|
|
168
|
+
return "not set up yet — run `cockpit onboard`";
|
|
130
169
|
}
|
|
131
170
|
}
|
|
132
171
|
async function countRemainingBackfillSessionFiles(homeDir, cursor) {
|
package/dist/raw-evidence-gc.js
CHANGED
|
@@ -68,7 +68,7 @@ export async function runRawEvidenceLocalGc(paths, env = process.env, now = new
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
export function rawEvidenceGcSummary(result) {
|
|
71
|
-
return `
|
|
71
|
+
return `Cleaned up: removed ${result.removed_dirs} old folders, freed ~${formatMb(result.freed_bytes)} MB`;
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Collapse byte-identical staged packs down to one survivor.
|