@bli-cockpit/cli 0.2.48 → 0.2.50
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/dist/adapters/raw-evidence-attribution-gaps.js +133 -0
- package/dist/adapters/raw-evidence-claude-reader.js +108 -0
- package/dist/adapters/raw-evidence-codex-reader.js +147 -0
- package/dist/adapters/raw-evidence-collection-state.js +199 -0
- package/dist/adapters/raw-evidence-facts.js +338 -0
- package/dist/adapters/raw-evidence-git-diff-reader.js +187 -0
- package/dist/adapters/raw-evidence-image-reader.js +107 -0
- package/dist/adapters/raw-evidence-sanitize.js +56 -0
- package/dist/adapters/raw-evidence-transcript-file.js +182 -0
- package/dist/adapters/raw-evidence.js +94 -1203
- package/dist/autostart-contract.js +79 -0
- package/dist/autostart-darwin-plist.js +265 -0
- package/dist/autostart-darwin.js +171 -0
- package/dist/autostart-windows-scripts.js +310 -0
- package/dist/autostart-windows-task-xml.js +260 -0
- package/dist/autostart-windows.js +237 -0
- package/dist/autostart-xml.js +23 -0
- package/dist/autostart.js +35 -1148
- package/dist/commands/agent-rules-command.js +55 -0
- package/dist/commands/agent-session-report.js +290 -0
- package/dist/commands/analyze.js +131 -0
- package/dist/commands/autostart-command.js +105 -0
- package/dist/commands/backfill-batches.js +34 -0
- package/dist/commands/backfill-candidates.js +54 -0
- package/dist/commands/backfill-checkpoint.js +101 -0
- package/dist/commands/backfill-command-line.js +70 -0
- package/dist/commands/backfill-evidence-outcomes.js +104 -0
- package/dist/commands/backfill-issues.js +265 -0
- package/dist/commands/backfill-output.js +75 -0
- package/dist/commands/backfill-plan.js +71 -0
- package/dist/commands/backfill-reasons.js +107 -0
- package/dist/commands/backfill-report.js +298 -0
- package/dist/commands/backfill-result.js +150 -0
- package/dist/commands/backfill-scan.js +274 -0
- package/dist/commands/backfill-scope.js +114 -0
- package/dist/commands/backfill-session-report.js +145 -0
- package/dist/commands/backfill-types.js +1 -0
- package/dist/commands/backfill-upload.js +212 -0
- package/dist/commands/backfill.js +58 -1705
- package/dist/commands/cli-io.js +13 -0
- package/dist/commands/doctor.js +57 -0
- package/dist/commands/jarvis-trace.js +184 -0
- package/dist/commands/jarvis.js +323 -7
- package/dist/commands/local-arg-values.js +169 -0
- package/dist/commands/local-args-collector.js +604 -0
- package/dist/commands/local-args-tower.js +891 -0
- package/dist/commands/local-args.js +10 -1549
- package/dist/commands/local-help.js +30 -5
- package/dist/commands/local.js +21 -1786
- package/dist/commands/login.js +53 -0
- package/dist/commands/logout.js +66 -0
- package/dist/commands/memory-install-claude.js +294 -0
- package/dist/commands/memory-install-codex.js +205 -0
- package/dist/commands/memory-install-contract.js +231 -0
- package/dist/commands/memory-install-files.js +63 -0
- package/dist/commands/memory-install-skills.js +121 -0
- package/dist/commands/memory-install-toml.js +265 -0
- package/dist/commands/memory-install.js +378 -0
- package/dist/commands/onboard-receipts.js +66 -0
- package/dist/commands/onboard-report.js +274 -0
- package/dist/commands/onboard.js +449 -0
- package/dist/commands/ops-render.js +36 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/serve.js +13 -0
- package/dist/commands/session-sync.js +513 -534
- package/dist/commands/settings-render.js +28 -0
- package/dist/commands/settings.js +66 -2
- package/dist/commands/start.js +47 -0
- package/dist/commands/sync-followups.js +308 -0
- package/dist/commands/sync.js +387 -0
- package/dist/local-state-attributed-target.js +75 -0
- package/dist/local-state-config.js +147 -0
- package/dist/local-state-files.js +59 -0
- package/dist/local-state-identity.js +73 -0
- package/dist/local-state-pairing.js +263 -0
- package/dist/local-state-paths.js +61 -0
- package/dist/local-state-session.js +68 -0
- package/dist/local-state-status.js +163 -0
- package/dist/local-state-work-context.js +190 -0
- package/dist/local-state.js +34 -848
- package/dist/tower-client.js +3 -2
- package/dist/tower-stream.js +76 -6
- package/package.json +2 -1
package/dist/commands/cli-io.js
CHANGED
|
@@ -10,6 +10,19 @@ export function writeRaw(stream, text) {
|
|
|
10
10
|
if (!text.endsWith("\n"))
|
|
11
11
|
stream.write("\n");
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Part of a line, and nothing else (BLI-3517).
|
|
15
|
+
*
|
|
16
|
+
* `writeRaw` finishes the line for you, which is right for a whole block of
|
|
17
|
+
* captured output and wrong for a sentence still being written: a streamed
|
|
18
|
+
* answer arrives as a dozen fragments and each one would land on its own row.
|
|
19
|
+
* This adds nothing at all; the caller ends the line when the thought ends.
|
|
20
|
+
*/
|
|
21
|
+
export function writeFragment(stream, text) {
|
|
22
|
+
if (!text)
|
|
23
|
+
return;
|
|
24
|
+
stream.write(text);
|
|
25
|
+
}
|
|
13
26
|
export function errorMessage(error) {
|
|
14
27
|
return error instanceof Error ? error.message : String(error);
|
|
15
28
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -10,6 +10,7 @@ import { normalizeCollectionRoots } from "../root-normalization.js";
|
|
|
10
10
|
import { detectSecondCockpitInstall } from "../second-install.js";
|
|
11
11
|
import { runRawEvidenceLocalGc, rawEvidenceGcSummary } from "../raw-evidence-gc.js";
|
|
12
12
|
import { runBackfillCommand } from "./backfill.js";
|
|
13
|
+
import { installMemoryIntegration, inspectMemoryIntegration, } from "./memory-install.js";
|
|
13
14
|
import { createInteractiveExecRunner } from "../process-runner.js";
|
|
14
15
|
const GC_MIN_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
15
16
|
export async function runDoctor(command, io, hooks, overrides = {}) {
|
|
@@ -81,6 +82,14 @@ function doctorInvariants() {
|
|
|
81
82
|
check: (context) => context.deps.checkAutostart(context),
|
|
82
83
|
fix: (context, _state) => context.deps.fixAutostart(context),
|
|
83
84
|
},
|
|
85
|
+
// BLI-3580. Runs right after autostart because it is the same kind of
|
|
86
|
+
// thing: a registration on this machine that nobody should have to be
|
|
87
|
+
// asked about. Its fix writes host configs, never installs software.
|
|
88
|
+
{
|
|
89
|
+
id: "memory-registered",
|
|
90
|
+
check: (context) => context.deps.checkMemory(context),
|
|
91
|
+
fix: (context, _state) => context.deps.fixMemory(context),
|
|
92
|
+
},
|
|
84
93
|
{
|
|
85
94
|
id: "backfill-complete",
|
|
86
95
|
check: (context) => context.deps.checkBackfill(context),
|
|
@@ -111,6 +120,8 @@ function defaultDoctorDeps(hooks) {
|
|
|
111
120
|
checkSingleInstall: checkSingleInstallState,
|
|
112
121
|
checkAutostart: checkAutostartState,
|
|
113
122
|
fixAutostart: fixAutostartState,
|
|
123
|
+
checkMemory: checkMemoryState,
|
|
124
|
+
fixMemory: fixMemoryState,
|
|
114
125
|
checkBackfill: checkBackfillState,
|
|
115
126
|
fixBackfill: fixBackfillState,
|
|
116
127
|
checkGc: checkGcState,
|
|
@@ -307,6 +318,52 @@ async function fixAutostartState(context) {
|
|
|
307
318
|
}
|
|
308
319
|
return ok("autostart-alive", "installed", "background sync installed and running");
|
|
309
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* BLI Memory's registration on this machine (BLI-3580).
|
|
323
|
+
*
|
|
324
|
+
* The step owns the CONFIG, never the software: it writes the MCP entry, the
|
|
325
|
+
* hooks and the Codex table only when `bli-memory-mcp` actually resolves. With
|
|
326
|
+
* no server it writes NOTHING and reads `skipped bin_missing` — registering
|
|
327
|
+
* hooks that point at an absent binary would make every Claude Code turn print
|
|
328
|
+
* a hook failure, which is worse than waiting a day.
|
|
329
|
+
*/
|
|
330
|
+
async function checkMemoryState(context) {
|
|
331
|
+
const outcome = await inspectMemoryIntegration(memoryCommandFor(context), context.io);
|
|
332
|
+
return memoryStepState(outcome, "check");
|
|
333
|
+
}
|
|
334
|
+
async function fixMemoryState(context) {
|
|
335
|
+
const outcome = await installMemoryIntegration(memoryCommandFor(context), context.io);
|
|
336
|
+
return memoryStepState(outcome, "fix");
|
|
337
|
+
}
|
|
338
|
+
function memoryCommandFor(context) {
|
|
339
|
+
return {
|
|
340
|
+
kind: "memory",
|
|
341
|
+
action: "install",
|
|
342
|
+
homeDir: context.command.homeDir,
|
|
343
|
+
dashboardUrl: context.command.dashboardUrl,
|
|
344
|
+
dryRun: false,
|
|
345
|
+
json: context.command.json,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function memoryStepState(outcome, phase) {
|
|
349
|
+
const broken = outcome.targets.filter((target) => target.target !== "bin" && target.status === "failed");
|
|
350
|
+
if (broken.length > 0) {
|
|
351
|
+
const first = broken[0];
|
|
352
|
+
const message = `${first?.target ?? "memory"}: ${first?.reason ?? "failed"}`;
|
|
353
|
+
return phase === "fix"
|
|
354
|
+
? fail("memory-registered", first?.reason ?? "memory_install_failed", message)
|
|
355
|
+
: needsFix("memory-registered", first?.reason ?? "memory_install_failed", message);
|
|
356
|
+
}
|
|
357
|
+
const pending = outcome.targets.filter((target) => target.target !== "bin" &&
|
|
358
|
+
(target.status === "missing" || target.status === "mismatch"));
|
|
359
|
+
if (pending.length > 0) {
|
|
360
|
+
return needsFix("memory-registered", "registration_incomplete", `BLI Memory is not registered with ${pending.map((target) => target.target).join(", ")}`);
|
|
361
|
+
}
|
|
362
|
+
if (!outcome.bin_found) {
|
|
363
|
+
return skipped("memory-registered", "bin_missing", "bli-memory-mcp is not on this machine yet; nothing was written and the next run will try again");
|
|
364
|
+
}
|
|
365
|
+
return ok("memory-registered", phase === "fix" ? "installed" : "already_installed", "BLI Memory is registered with both agent hosts");
|
|
366
|
+
}
|
|
310
367
|
/**
|
|
311
368
|
* Pure so it can be unit-tested without touching the real machine's home
|
|
312
369
|
* directory (`getCollectorRuntimePaths()` defaults to `os.homedir()` and
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit jarvis --trace <id|last>` — the step tree of one turn (BLI-3560).
|
|
3
|
+
*
|
|
4
|
+
* The one thing anybody here ever opened Langfuse for, in the terminal where
|
|
5
|
+
* the turn was asked. Every model step, every tool call, every server-side
|
|
6
|
+
* memory step (BLI-3416's doubled answer was one of those and no other surface
|
|
7
|
+
* showed it), with how long each took, which model ran it, what it spent and
|
|
8
|
+
* whether it failed.
|
|
9
|
+
*
|
|
10
|
+
* ## `last` and the remembered receipt
|
|
11
|
+
*
|
|
12
|
+
* A turn now hands back its own trace id, and this module writes that id — with
|
|
13
|
+
* the conversation it belonged to — to one small file beside the collector's
|
|
14
|
+
* other state. `--trace last` reads it, so the loop is: ask a question, watch
|
|
15
|
+
* it come back slow, type `cockpit jarvis --trace last`. If the file is not
|
|
16
|
+
* there (a fresh shell, a machine that has not asked anything since the
|
|
17
|
+
* release), the file's remembered THREAD is used to ask the server for that
|
|
18
|
+
* conversation's newest turn instead — and when there is neither, the refusal
|
|
19
|
+
* says so in words rather than printing nothing.
|
|
20
|
+
*
|
|
21
|
+
* The file holds two opaque ids and a timestamp. No question, no answer, no
|
|
22
|
+
* name; it is a bookmark, not a transcript.
|
|
23
|
+
*
|
|
24
|
+
* ## The lines
|
|
25
|
+
*
|
|
26
|
+
* Rendered SERVER-SIDE and printed verbatim. That is deliberate: the ops door,
|
|
27
|
+
* this terminal and JARVIS's own `readTurnTrace` tool all read the same
|
|
28
|
+
* sentences from `lib/ops/turn-trace-lines.ts`, so an older CLI against a newer
|
|
29
|
+
* dashboard still prints the current tree rather than its own stale idea of one.
|
|
30
|
+
*/
|
|
31
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
32
|
+
import os from "node:os";
|
|
33
|
+
import path from "node:path";
|
|
34
|
+
import { colorEnabled, dim, writeLine } from "./cli-io.js";
|
|
35
|
+
import { getCollectorRuntimePaths } from "../local-state.js";
|
|
36
|
+
import { towerFailureDetail, towerJsonRequest } from "../tower-client.js";
|
|
37
|
+
/** The bookmark file's name, inside the collector's own state directory. */
|
|
38
|
+
const LAST_TURN_FILE = "last-turn-trace.json";
|
|
39
|
+
function bookmarkPath(homeDir) {
|
|
40
|
+
const paths = getCollectorRuntimePaths(homeDir ?? os.homedir());
|
|
41
|
+
return path.join(paths.state_dir, LAST_TURN_FILE);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Remember the turn that just happened. Never throws: a bookmark that could not
|
|
45
|
+
* be written must not fail the answer a person already received, so the failure
|
|
46
|
+
* is named on stderr and the turn stands.
|
|
47
|
+
*/
|
|
48
|
+
export async function rememberTurnTrace(turn, io, homeDir) {
|
|
49
|
+
if (!turn.traceId && !turn.threadId)
|
|
50
|
+
return;
|
|
51
|
+
const file = bookmarkPath(homeDir);
|
|
52
|
+
const payload = {
|
|
53
|
+
traceId: turn.traceId ?? null,
|
|
54
|
+
threadId: turn.threadId ?? null,
|
|
55
|
+
at: new Date().toISOString(),
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
59
|
+
await writeFile(file, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
writeLine(io.stderr, `[jarvis cli] could not remember this turn's trace ${JSON.stringify({
|
|
63
|
+
reason: "bookmark_write_failed",
|
|
64
|
+
error: error instanceof Error ? error.name : "unknown",
|
|
65
|
+
})}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** The remembered turn, or null when there is not one. Never throws. */
|
|
69
|
+
export async function readRememberedTurn(homeDir) {
|
|
70
|
+
try {
|
|
71
|
+
const raw = await readFile(bookmarkPath(homeDir), "utf8");
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
const traceId = typeof parsed.traceId === "string" ? parsed.traceId : null;
|
|
74
|
+
const threadId = typeof parsed.threadId === "string" ? parsed.threadId : null;
|
|
75
|
+
if (!traceId && !threadId)
|
|
76
|
+
return null;
|
|
77
|
+
return { traceId, threadId, at: typeof parsed.at === "string" ? parsed.at : "" };
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// A missing or unreadable bookmark is "nothing is remembered", which is the
|
|
81
|
+
// exact answer asked for. The caller says so out loud in a sentence.
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export async function runJarvisTrace(context, io) {
|
|
86
|
+
const log = (line) => writeLine(io.stderr, line);
|
|
87
|
+
let traceId = context.requested;
|
|
88
|
+
let resolvedFrom = "argument";
|
|
89
|
+
if (context.requested === "last") {
|
|
90
|
+
const remembered = await readRememberedTurn(context.homeDir);
|
|
91
|
+
if (!remembered) {
|
|
92
|
+
return refuse(context, io, "no_remembered_turn", "This machine has not asked JARVIS anything since traces were turned on, so there is no last turn to open. Ask something, then run this again — or pass the trace id from the turn's receipt.");
|
|
93
|
+
}
|
|
94
|
+
if (remembered.traceId) {
|
|
95
|
+
traceId = remembered.traceId;
|
|
96
|
+
resolvedFrom = "bookmark";
|
|
97
|
+
}
|
|
98
|
+
else if (remembered.threadId) {
|
|
99
|
+
// The bookmark knew the conversation but not the turn (an older receipt).
|
|
100
|
+
// Ask the server for that conversation's newest turn rather than nothing.
|
|
101
|
+
const newest = await newestTraceForThread(context, remembered.threadId, io);
|
|
102
|
+
if (!newest) {
|
|
103
|
+
return refuse(context, io, "no_traces_in_thread", "That conversation has no recorded turns inside the 30-day retention. Pass a trace id, or ask something new.");
|
|
104
|
+
}
|
|
105
|
+
traceId = newest;
|
|
106
|
+
resolvedFrom = "thread";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const result = await towerJsonRequest({
|
|
110
|
+
dashboardUrl: context.dashboardUrl,
|
|
111
|
+
path: `/api/ops/trace/${encodeURIComponent(traceId)}`,
|
|
112
|
+
deviceToken: context.deviceToken,
|
|
113
|
+
fetch: io.fetch,
|
|
114
|
+
method: "GET",
|
|
115
|
+
label: "jarvis:trace",
|
|
116
|
+
timeoutMs: 30_000,
|
|
117
|
+
log,
|
|
118
|
+
});
|
|
119
|
+
if (!result.ok) {
|
|
120
|
+
return refuse(context, io, result.reason, towerFailureDetail(result.reason, result.detail));
|
|
121
|
+
}
|
|
122
|
+
const body = result.body;
|
|
123
|
+
if (body.ok === false) {
|
|
124
|
+
return refuse(context, io, body.error ?? "trace_unavailable", body.message ?? "Tower had nothing to show for that turn.");
|
|
125
|
+
}
|
|
126
|
+
if (context.json) {
|
|
127
|
+
writeLine(io.stdout, JSON.stringify(body));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
writeTraceTree(io, traceId, body);
|
|
131
|
+
}
|
|
132
|
+
writeLine(io.stderr, `[jarvis cli] trace read ${JSON.stringify({
|
|
133
|
+
resolved_from: resolvedFrom,
|
|
134
|
+
spans: body.trace?.spanCount ?? null,
|
|
135
|
+
errors: body.trace?.errorCount ?? null,
|
|
136
|
+
running: body.trace?.runningCount ?? null,
|
|
137
|
+
duration_ms: body.trace?.durationMs ?? null,
|
|
138
|
+
truncated: body.truncated ?? null,
|
|
139
|
+
})}`);
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
async function newestTraceForThread(context, threadId, io) {
|
|
143
|
+
const result = await towerJsonRequest({
|
|
144
|
+
dashboardUrl: context.dashboardUrl,
|
|
145
|
+
path: `/api/ops/trace?thread=${encodeURIComponent(threadId)}&limit=1`,
|
|
146
|
+
deviceToken: context.deviceToken,
|
|
147
|
+
fetch: io.fetch,
|
|
148
|
+
method: "GET",
|
|
149
|
+
label: "jarvis:trace-thread",
|
|
150
|
+
timeoutMs: 30_000,
|
|
151
|
+
log: (line) => writeLine(io.stderr, line),
|
|
152
|
+
});
|
|
153
|
+
if (!result.ok)
|
|
154
|
+
return null;
|
|
155
|
+
const body = result.body;
|
|
156
|
+
return body.traces?.[0]?.traceId ?? null;
|
|
157
|
+
}
|
|
158
|
+
function writeTraceTree(io, traceId, body) {
|
|
159
|
+
const styled = colorEnabled(io);
|
|
160
|
+
writeLine(io.stdout, `Turn ${traceId}`);
|
|
161
|
+
if (body.headline)
|
|
162
|
+
writeLine(io.stdout, dim(body.headline, styled));
|
|
163
|
+
writeLine(io.stdout, "");
|
|
164
|
+
for (const line of body.lines ?? [])
|
|
165
|
+
writeLine(io.stdout, line);
|
|
166
|
+
if ((body.lines ?? []).length === 0) {
|
|
167
|
+
// Never a blank screen: an empty tree is a fact and it gets a sentence.
|
|
168
|
+
writeLine(io.stdout, "That turn recorded no steps.");
|
|
169
|
+
}
|
|
170
|
+
if (body.truncated) {
|
|
171
|
+
writeLine(io.stdout, "");
|
|
172
|
+
writeLine(io.stdout, dim("Only the first 1000 spans of this turn are shown.", styled));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function refuse(context, io, reason, detail) {
|
|
176
|
+
if (context.json) {
|
|
177
|
+
writeLine(io.stdout, JSON.stringify({ ok: false, error: reason, detail }));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
writeLine(io.stderr, detail);
|
|
181
|
+
}
|
|
182
|
+
writeLine(io.stderr, `[jarvis cli] trace unavailable ${JSON.stringify({ reason })}`);
|
|
183
|
+
return 1;
|
|
184
|
+
}
|