@bli-cockpit/cli 0.2.97 → 0.2.99
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/cli.js +13 -0
- package/dist/commands/doctor.js +6 -2
- package/dist/commands/local-args-collector-status.js +27 -6
- package/dist/commands/local-args-tower-admin.js +17 -6
- package/dist/commands/local-args-tower-memory.js +151 -0
- package/dist/commands/local-args-tower-pages.js +6 -1
- package/dist/commands/local-args-tower-usage.js +8 -0
- package/dist/commands/local-args-tower.js +2 -1
- package/dist/commands/local-args.js +3 -1
- package/dist/commands/local-command-shapes-memory.js +22 -0
- package/dist/commands/local-help-commands-tower.js +31 -5
- package/dist/commands/local-help-commands.js +25 -5
- package/dist/commands/local-help.js +7 -4
- package/dist/commands/local.js +18 -0
- package/dist/commands/memory-hook-counts.js +29 -8
- package/dist/commands/memory-store.js +331 -0
- package/dist/commands/notes-writes.js +37 -0
- package/dist/commands/notes.js +5 -3
- package/dist/commands/ops-render.js +5 -1
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/usage.js +23 -0
- package/dist/crash-guard.js +167 -0
- package/dist/process-runner.js +39 -1
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runCockpitCli } from "./commands/public-root.js";
|
|
3
|
+
import {
|
|
4
|
+
EXIT_DIED_MID_RUN,
|
|
5
|
+
installCrashGuard,
|
|
6
|
+
markCommandReturned,
|
|
7
|
+
} from "./crash-guard.js";
|
|
8
|
+
|
|
9
|
+
// BLI-4110. Success is EARNED, not assumed — see crash-guard.js. This
|
|
10
|
+
// entry point is GENERATED, so it must mirror src/cli.ts; the packed CLI
|
|
11
|
+
// is the one the fleet runs.
|
|
12
|
+
process.exitCode = EXIT_DIED_MID_RUN;
|
|
13
|
+
installCrashGuard();
|
|
3
14
|
|
|
4
15
|
const exitCode = await runCockpitCli(process.argv.slice(2));
|
|
16
|
+
|
|
17
|
+
markCommandReturned();
|
|
5
18
|
process.exitCode = exitCode;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withStage } from "../crash-guard.js";
|
|
1
2
|
import { checkSingleInstallState, fixAuthState, fixRootState, readAuthState, readRootState, } from "./doctor-access.js";
|
|
2
3
|
import { backfillCompletionStepState, checkBackfillState, checkDiskState, checkGcState, checkSyncState, fixBackfillState, fixDiskState, fixGcState, fixSyncState, syncBacklogDrainingVerdict, } from "./doctor-pipeline.js";
|
|
3
4
|
import { checkMcpAnswersState } from "./doctor-mcp.js";
|
|
@@ -14,7 +15,7 @@ export async function runDoctorWithDeps(command, io, deps) {
|
|
|
14
15
|
const context = { command, io, deps };
|
|
15
16
|
const rows = [];
|
|
16
17
|
for (const invariant of doctorInvariants()) {
|
|
17
|
-
const checked = await invariant.check(context);
|
|
18
|
+
const checked = await withStage(`doctor:check:${invariant.id}`, () => invariant.check(context));
|
|
18
19
|
if (checked.status === "ok" || checked.status === "skipped") {
|
|
19
20
|
rows.push(checked);
|
|
20
21
|
continue;
|
|
@@ -31,7 +32,10 @@ export async function runDoctorWithDeps(command, io, deps) {
|
|
|
31
32
|
break;
|
|
32
33
|
continue;
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
+
// BLI-4110: named so a crash inside a repair says WHICH repair. The
|
|
36
|
+
// 2026-09-09 incident printed a bare `read ENOTCONN` stack and the only
|
|
37
|
+
// way to place it was the log line that happened to precede it.
|
|
38
|
+
const fixed = await withStage(`doctor:fix:${invariant.id}`, () => invariant.fix(context, checked));
|
|
35
39
|
rows.push({ ...fixed, fixed: fixed.status !== "fail" });
|
|
36
40
|
if (fixed.hardStop)
|
|
37
41
|
break;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { DEFAULT_AUTOSTART_INTERVAL_SECONDS } from "../autostart.js";
|
|
12
12
|
import { assertNoPositionals, normalizeUrl, optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, workRootFlagValue, } from "./local-arg-values.js";
|
|
13
13
|
import { DEFAULT_DASHBOARD_URL } from "../local-state.js";
|
|
14
|
+
import { MEMORY_STORE_ACTIONS, parseMemoryStoreArgs, } from "./local-args-tower-memory.js";
|
|
14
15
|
export function parseStatusArgs(args) {
|
|
15
16
|
const values = parseNamedArgs(args, {
|
|
16
17
|
allowedFlags: [
|
|
@@ -164,11 +165,30 @@ export function parseAgentRulesArgs(args) {
|
|
|
164
165
|
};
|
|
165
166
|
}
|
|
166
167
|
/**
|
|
167
|
-
* BLI-3580
|
|
168
|
-
*
|
|
169
|
-
* `cockpit memory`
|
|
168
|
+
* BLI-3580, amended by BLI-4047.
|
|
169
|
+
*
|
|
170
|
+
* `cockpit memory` is now SIX verbs behind one noun: the two that configure
|
|
171
|
+
* this machine (`install`, `status`), the experience log (`log`), and the four
|
|
172
|
+
* that read and write the memory store itself (`search`, `save`, `update`,
|
|
173
|
+
* `forget`, parsed next door in `local-args-tower-memory.ts`).
|
|
174
|
+
*
|
|
175
|
+
* **A bare `cockpit memory` prints the verb list; it no longer installs.**
|
|
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
|
|
178
|
+
* `{ kind: "memory", action: "install" }` in code (`doctor-registration.ts`,
|
|
179
|
+
* `sync-followups-memory.ts`, `onboard-completion.ts`) and never pass through
|
|
180
|
+
* this parser. The only caller the default ever served was a PERSON typing the
|
|
181
|
+
* noun to find out what it does, and what they got was an MCP server and three
|
|
182
|
+
* hooks registered on their machine. Installing stays one word away.
|
|
170
183
|
*/
|
|
171
184
|
export function parseMemoryArgs(args) {
|
|
185
|
+
// The verb word first, always: reading it off `args[0]` rather than "the
|
|
186
|
+
// first token without a leading dash" is what keeps `cockpit memory --home
|
|
187
|
+
// /tmp/x status` from being read as the verb `/tmp/x`.
|
|
188
|
+
const first = args[0];
|
|
189
|
+
if (first !== undefined && MEMORY_STORE_ACTIONS.has(first)) {
|
|
190
|
+
return parseMemoryStoreArgs(first, args);
|
|
191
|
+
}
|
|
172
192
|
const values = parseNamedArgs(args, {
|
|
173
193
|
allowedFlags: ["--home", "--dashboard-url", "--dry-run", "--json", "--store", "--reason-stdin"],
|
|
174
194
|
valueFlags: ["--home", "--dashboard-url", "--store"],
|
|
@@ -192,9 +212,10 @@ export function parseMemoryArgs(args) {
|
|
|
192
212
|
if (values.positionals.length > 1) {
|
|
193
213
|
throw new Error("memory accepts at most one action (install|status).");
|
|
194
214
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
215
|
+
// BLI-4047: no positional means "tell me what this noun does", not "install".
|
|
216
|
+
const action = values.positionals[0] ?? "help";
|
|
217
|
+
if (action !== "install" && action !== "status" && action !== "help") {
|
|
218
|
+
throw new Error("memory action must be install, status, help, log, search, save, update, or forget.");
|
|
198
219
|
}
|
|
199
220
|
return {
|
|
200
221
|
kind: "memory",
|
|
@@ -14,9 +14,17 @@ import { isTeamDeviceRevokeReasonLabel, TEAM_DEVICE_REVOKE_REASON_LABELS, } from
|
|
|
14
14
|
/** The shortest prefix `cockpit scout start` will resolve. Below this, ids collide. */
|
|
15
15
|
export const SCOUT_MIN_PREFIX_LENGTH = 6;
|
|
16
16
|
/**
|
|
17
|
-
* `cockpit scout [--days <n>]` reads the board; `cockpit scout
|
|
18
|
-
* <id>` moves one card. Positional shape modelled on
|
|
19
|
-
* action word first, then what it acts on.
|
|
17
|
+
* `cockpit scout [board] [--days <n>]` reads the board; `cockpit scout
|
|
18
|
+
* start|dismiss|undo <id>` moves one card. Positional shape modelled on
|
|
19
|
+
* `autostart`: an optional action word first, then what it acts on.
|
|
20
|
+
*
|
|
21
|
+
* `board` is spellable as well as implied (BLI-4048). The parsed command has
|
|
22
|
+
* always carried `action: "board"`, the MCP twin has always been `scout_board`,
|
|
23
|
+
* and three documents printed `cockpit scout board` under a "CLI verb" heading
|
|
24
|
+
* while the parser answered "scout action must be start, dismiss, or undo" —
|
|
25
|
+
* the promise was older than the refusal. Every sibling noun (`ops status`,
|
|
26
|
+
* `slack coverage`, `notes list`) lets a person name the read it defaults to,
|
|
27
|
+
* so scout does too, and the word costs one branch.
|
|
20
28
|
*/
|
|
21
29
|
export function parseScoutArgs(args) {
|
|
22
30
|
const values = parseNamedArgs(args, {
|
|
@@ -24,7 +32,7 @@ export function parseScoutArgs(args) {
|
|
|
24
32
|
valueFlags: ["--home", "--dashboard-url", "--days"],
|
|
25
33
|
});
|
|
26
34
|
if (values.positionals.length > 2) {
|
|
27
|
-
throw new Error("scout accepts at most an action (start|dismiss|undo) and one experiment id.");
|
|
35
|
+
throw new Error("scout accepts at most an action (board|start|dismiss|undo) and one experiment id.");
|
|
28
36
|
}
|
|
29
37
|
const [rawAction, rawRef] = values.positionals;
|
|
30
38
|
const base = {
|
|
@@ -33,10 +41,13 @@ export function parseScoutArgs(args) {
|
|
|
33
41
|
days: optionalPositiveInteger(values.flags.get("--days"), "--days"),
|
|
34
42
|
json: values.booleans.has("--json"),
|
|
35
43
|
};
|
|
36
|
-
if (!rawAction)
|
|
44
|
+
if (!rawAction || rawAction === "board") {
|
|
45
|
+
if (rawRef)
|
|
46
|
+
throw new Error("scout board reads the whole board; it takes no experiment id.");
|
|
37
47
|
return { kind: "scout", action: "board", ...base };
|
|
48
|
+
}
|
|
38
49
|
if (rawAction !== "start" && rawAction !== "dismiss" && rawAction !== "undo") {
|
|
39
|
-
throw new Error("scout action must be start, dismiss, or undo.");
|
|
50
|
+
throw new Error("scout action must be board, start, dismiss, or undo.");
|
|
40
51
|
}
|
|
41
52
|
const experimentRef = optionalNonEmpty(rawRef);
|
|
42
53
|
if (!experimentRef) {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit memory search|save|update|forget` — the argument table (BLI-4047).
|
|
3
|
+
*
|
|
4
|
+
* BLI Memory was the one Tower surface with no command-line door: the browser
|
|
5
|
+
* had one, the `bli-memory` MCP server had one, and a person or a script at a
|
|
6
|
+
* terminal had nothing. These four verbs are the twins of that server's four
|
|
7
|
+
* tools, over the SAME `/api/memory/*` doors.
|
|
8
|
+
*
|
|
9
|
+
* Two rules this table exists to enforce, both of them the same discipline
|
|
10
|
+
* `cockpit docs create` and `cockpit notes paste` already follow:
|
|
11
|
+
*
|
|
12
|
+
* - **A body never travels on the command line.** `save`, `update` and
|
|
13
|
+
* `forget --content` read their text from stdin (or `--file`, which is the
|
|
14
|
+
* safest way to say it on Windows). An argument list is visible in `ps`,
|
|
15
|
+
* lands in a shell history file, and is quoted wrong on the first line that
|
|
16
|
+
* contains a newline. A QUERY is not a body — `search` takes one
|
|
17
|
+
* positional, the same way `cockpit search` and `cockpit docs list --query`
|
|
18
|
+
* do — but the text of a memory always is.
|
|
19
|
+
* - **The container tag is derived, not typed.** Omit `--container` and the
|
|
20
|
+
* tag comes from this repository, resolved by `@bli-cockpit/memory-mcp`'s
|
|
21
|
+
* own resolver, so a memory saved from the terminal lands in the same space
|
|
22
|
+
* the MCP server and the hooks write to. `--container` may be repeated on
|
|
23
|
+
* `search` alone, because searching two spaces is meaningful and saving
|
|
24
|
+
* into two is not.
|
|
25
|
+
*
|
|
26
|
+
* The verbs live in their own `kind` (`memory-store`) rather than widening
|
|
27
|
+
* `kind: "memory"`, because that command acts on THIS MACHINE (it registers an
|
|
28
|
+
* MCP server and three hooks) and these act on Tower. `memory-store.ts` runs
|
|
29
|
+
* them; `parseMemoryArgs` in `local-args-collector-status.ts` routes to here.
|
|
30
|
+
*/
|
|
31
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs, workRootFlagValue, } from "./local-arg-values.js";
|
|
32
|
+
export const MEMORY_STORE_ACTIONS = new Set([
|
|
33
|
+
"search",
|
|
34
|
+
"save",
|
|
35
|
+
"update",
|
|
36
|
+
"forget",
|
|
37
|
+
]);
|
|
38
|
+
const ALLOWED_FLAGS = [
|
|
39
|
+
"--home",
|
|
40
|
+
"--dashboard-url",
|
|
41
|
+
"--workspace",
|
|
42
|
+
"--repo",
|
|
43
|
+
"--container",
|
|
44
|
+
"--limit",
|
|
45
|
+
"--no-recent",
|
|
46
|
+
"--custom-id",
|
|
47
|
+
"--extract",
|
|
48
|
+
"--file",
|
|
49
|
+
"--content-stdin",
|
|
50
|
+
"--reason",
|
|
51
|
+
"--json",
|
|
52
|
+
];
|
|
53
|
+
const VALUE_FLAGS = [
|
|
54
|
+
"--home",
|
|
55
|
+
"--dashboard-url",
|
|
56
|
+
"--workspace",
|
|
57
|
+
"--repo",
|
|
58
|
+
"--container",
|
|
59
|
+
"--limit",
|
|
60
|
+
"--custom-id",
|
|
61
|
+
"--file",
|
|
62
|
+
"--reason",
|
|
63
|
+
];
|
|
64
|
+
export function parseMemoryStoreArgs(action, args) {
|
|
65
|
+
const values = parseNamedArgs(args, { allowedFlags: ALLOWED_FLAGS, valueFlags: VALUE_FLAGS });
|
|
66
|
+
const rest = values.positionals.slice(1);
|
|
67
|
+
const containers = values.flagValues.get("--container") ?? [];
|
|
68
|
+
if (action !== "search" && containers.length > 1) {
|
|
69
|
+
throw new Error(`memory ${action} takes at most one --container.`);
|
|
70
|
+
}
|
|
71
|
+
if (action !== "search" && (values.flags.has("--limit") || values.booleans.has("--no-recent"))) {
|
|
72
|
+
throw new Error("--limit and --no-recent only apply to memory search.");
|
|
73
|
+
}
|
|
74
|
+
if (action !== "save" && (values.flags.has("--custom-id") || values.booleans.has("--extract"))) {
|
|
75
|
+
throw new Error("--custom-id and --extract only apply to memory save.");
|
|
76
|
+
}
|
|
77
|
+
if (action !== "forget" && (values.flags.has("--reason") || values.booleans.has("--content-stdin"))) {
|
|
78
|
+
throw new Error("--reason and --content-stdin only apply to memory forget.");
|
|
79
|
+
}
|
|
80
|
+
if (values.flags.has("--file") && action !== "save" && action !== "update") {
|
|
81
|
+
throw new Error("--file only applies to memory save and memory update.");
|
|
82
|
+
}
|
|
83
|
+
const command = {
|
|
84
|
+
kind: "memory-store",
|
|
85
|
+
action,
|
|
86
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
87
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
88
|
+
workspace: optionalNonEmpty(workRootFlagValue(values)),
|
|
89
|
+
containerTags: containers
|
|
90
|
+
.map((tag) => tag.trim())
|
|
91
|
+
.filter((tag) => tag.length > 0),
|
|
92
|
+
json: values.booleans.has("--json"),
|
|
93
|
+
};
|
|
94
|
+
if (action === "search") {
|
|
95
|
+
// Every remaining positional joined, so `cockpit memory search what did we
|
|
96
|
+
// decide about pagination` needs no quotes — the same shape `cockpit
|
|
97
|
+
// search` and `cockpit cal find` already have.
|
|
98
|
+
const query = rest.join(" ").trim();
|
|
99
|
+
if (!query)
|
|
100
|
+
throw new Error("memory search needs a question, e.g. `cockpit memory search \"pagination rule\"`.");
|
|
101
|
+
return { ...command, action, query, limit: readLimit(values.flags.get("--limit")), includeRecent: !values.booleans.has("--no-recent") };
|
|
102
|
+
}
|
|
103
|
+
if (action === "save") {
|
|
104
|
+
if (rest.length > 0) {
|
|
105
|
+
throw new Error("memory save reads the memory from stdin or --file, never from the command line: `echo \"...\" | cockpit memory save`.");
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
...command,
|
|
109
|
+
action,
|
|
110
|
+
customId: optionalNonEmpty(values.flags.get("--custom-id")),
|
|
111
|
+
extract: values.booleans.has("--extract"),
|
|
112
|
+
filePath: optionalNonEmpty(values.flags.get("--file")),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (action === "update") {
|
|
116
|
+
const memoryId = optionalNonEmpty(rest[0]);
|
|
117
|
+
if (!memoryId)
|
|
118
|
+
throw new Error("memory update needs the memory id `cockpit memory search` printed.");
|
|
119
|
+
if (rest.length > 1) {
|
|
120
|
+
throw new Error("memory update takes one id; the replacement text comes from stdin or --file, never from the command line.");
|
|
121
|
+
}
|
|
122
|
+
return { ...command, action, memoryId, filePath: optionalNonEmpty(values.flags.get("--file")) };
|
|
123
|
+
}
|
|
124
|
+
const memoryId = optionalNonEmpty(rest[0]);
|
|
125
|
+
if (rest.length > 1)
|
|
126
|
+
throw new Error("memory forget takes one id, or --content-stdin with a --container.");
|
|
127
|
+
const contentStdin = values.booleans.has("--content-stdin");
|
|
128
|
+
if (memoryId && contentStdin) {
|
|
129
|
+
throw new Error("memory forget takes an id OR --content-stdin, never both — they name different rows.");
|
|
130
|
+
}
|
|
131
|
+
if (!memoryId && !contentStdin) {
|
|
132
|
+
throw new Error("memory forget needs an id, or --content-stdin plus --container to forget by exact text.");
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
...command,
|
|
136
|
+
action,
|
|
137
|
+
memoryId,
|
|
138
|
+
contentStdin,
|
|
139
|
+
reason: optionalNonEmpty(values.flags.get("--reason")),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** 1..20, the same window the MCP tool and the door both accept. */
|
|
143
|
+
function readLimit(value) {
|
|
144
|
+
if (value === undefined)
|
|
145
|
+
return undefined;
|
|
146
|
+
const parsed = Number.parseInt(value, 10);
|
|
147
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 20) {
|
|
148
|
+
throw new Error("--limit must be between 1 and 20.");
|
|
149
|
+
}
|
|
150
|
+
return parsed;
|
|
151
|
+
}
|
|
@@ -162,6 +162,7 @@ const NOTES_ACTIONS = new Set([
|
|
|
162
162
|
"share",
|
|
163
163
|
"unshare",
|
|
164
164
|
"move",
|
|
165
|
+
"place",
|
|
165
166
|
]);
|
|
166
167
|
/** Actions whose first positional is the note it acts on. */
|
|
167
168
|
const NOTES_ACTIONS_NEEDING_A_NOTE = new Set([
|
|
@@ -169,6 +170,7 @@ const NOTES_ACTIONS_NEEDING_A_NOTE = new Set([
|
|
|
169
170
|
"share",
|
|
170
171
|
"unshare",
|
|
171
172
|
"move",
|
|
173
|
+
"place",
|
|
172
174
|
]);
|
|
173
175
|
export function parseNotesArgs(args) {
|
|
174
176
|
const values = parseNamedArgs(args, {
|
|
@@ -180,6 +182,7 @@ export function parseNotesArgs(args) {
|
|
|
180
182
|
"--exclude",
|
|
181
183
|
"--to",
|
|
182
184
|
"--clear-shelf",
|
|
185
|
+
"--apply",
|
|
183
186
|
"--series",
|
|
184
187
|
"--kind",
|
|
185
188
|
"--since",
|
|
@@ -207,7 +210,7 @@ export function parseNotesArgs(args) {
|
|
|
207
210
|
const first = values.positionals[0];
|
|
208
211
|
const action = (first === undefined ? "list" : first);
|
|
209
212
|
if (!NOTES_ACTIONS.has(action)) {
|
|
210
|
-
throw new Error(`Unknown notes command: ${first}. Try list, show, shelf, shelves, upload, paste, share, unshare, or
|
|
213
|
+
throw new Error(`Unknown notes command: ${first}. Try list, show, shelf, shelves, upload, paste, share, unshare, move, or place.`);
|
|
211
214
|
}
|
|
212
215
|
const rest = values.positionals.slice(first === undefined ? 0 : 1);
|
|
213
216
|
const json = values.booleans.has("--json");
|
|
@@ -259,6 +262,8 @@ export function parseNotesArgs(args) {
|
|
|
259
262
|
exclude: optionalNonEmpty(values.flags.get("--exclude")),
|
|
260
263
|
...(to ? { to } : {}),
|
|
261
264
|
...(clearShelf ? { clearShelf } : {}),
|
|
265
|
+
// BLI-4058. `place` says where a note belongs; only `--apply` moves it.
|
|
266
|
+
...(values.booleans.has("--apply") ? { apply: true } : {}),
|
|
262
267
|
series: optionalNonEmpty(values.flags.get("--series")),
|
|
263
268
|
meetingKind: optionalNonEmpty(values.flags.get("--kind")),
|
|
264
269
|
since: optionalNonEmpty(values.flags.get("--since")),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
2
|
+
export function parseUsageArgs(args) {
|
|
3
|
+
const values = parseNamedArgs(args, { allowedFlags: ["--since", "--until", "--include-automated", "--home", "--dashboard-url", "--json"], valueFlags: ["--since", "--until", "--home", "--dashboard-url"] });
|
|
4
|
+
const action = values.positionals[0] ?? "people";
|
|
5
|
+
if (action !== "people" || values.positionals.length > 1)
|
|
6
|
+
throw new Error("usage takes one verb: people.");
|
|
7
|
+
return { kind: "usage", action: "people", since: optionalNonEmpty(values.flags.get("--since")) ?? "30d", until: optionalNonEmpty(values.flags.get("--until")), includeAutomated: values.booleans.has("--include-automated"), homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")), json: values.booleans.has("--json") };
|
|
8
|
+
}
|
|
@@ -35,4 +35,5 @@ export { ISSUE_STATES, parseIssueArgs, parseProjectArgs, } from "./local-args-to
|
|
|
35
35
|
export { SEARCH_KINDS, parseSearchArgs } from "./local-args-tower-search.js";
|
|
36
36
|
export { parseModelsArgs } from "./local-args-tower-models.js";
|
|
37
37
|
export { parseMailArgs } from "./local-args-tower-mail.js";
|
|
38
|
-
export { parseCalArgs } from "./local-args-tower-cal.js";
|
|
38
|
+
export { parseCalArgs } from "./local-args-tower-cal.js";
|
|
39
|
+
export { parseUsageArgs } from "./local-args-tower-usage.js";
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* verbatim, no logic change.
|
|
17
17
|
*/
|
|
18
18
|
import { parseAgentRulesArgs, parseAnalyzeArgs, parseAutostartArgs, parseBackfillArgs, parseCleanArgs, parseDoctorArgs, parseInstallArgs, parseLoginArgs, parseMemoryArgs, parseLogoutArgs, parseOnboardArgs, parseReleaseArgs, parseServeArgs, parseSessionsArgs, parseStartArgs, parseStatusArgs, parseSyncArgs, parseUpdateArgs, } from "./local-args-collector.js";
|
|
19
|
-
import { parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, } from "./local-args-tower.js";
|
|
19
|
+
import { parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, } from "./local-args-tower.js";
|
|
20
20
|
// `normalizeUrl` has always been part of this module's surface — `local.ts` and
|
|
21
21
|
// `local-auth.ts` import it from here — so it stays exported from this address
|
|
22
22
|
// even though it now lives next door. The same goes for the four names the
|
|
@@ -115,6 +115,8 @@ export function parseLocalArgs(argv) {
|
|
|
115
115
|
return parseSearchArgs(argv.slice(1));
|
|
116
116
|
case "release":
|
|
117
117
|
return parseReleaseArgs(argv.slice(1));
|
|
118
|
+
case "usage":
|
|
119
|
+
return parseUsageArgs(argv.slice(1));
|
|
118
120
|
default:
|
|
119
121
|
throw new Error(`Unknown local command: ${command ?? ""}`);
|
|
120
122
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `cockpit memory` family's command shapes (BLI-4047).
|
|
3
|
+
*
|
|
4
|
+
* Split out of `local-command-shapes.ts` the same way `SearchCommandShape` and
|
|
5
|
+
* the product shapes were: that file crossed the repo's 500-line band floor
|
|
6
|
+
* when the store verbs arrived, and one noun's shapes are exactly the kind of
|
|
7
|
+
* thing that reads better in its own file than as a third of a 540-line union.
|
|
8
|
+
*
|
|
9
|
+
* Three kinds, and the difference between them is the point:
|
|
10
|
+
*
|
|
11
|
+
* `memory` install / status / help — acts on THIS MACHINE, writing an
|
|
12
|
+
* MCP registration and three hooks into a home directory.
|
|
13
|
+
* `memory-store` search / save / update / forget — acts on TOWER, over
|
|
14
|
+
* `/api/memory/*`, the same doors the `bli-memory` MCP
|
|
15
|
+
* server and the browser call.
|
|
16
|
+
* `memory-log` the agent-experience opinion, part local file part Tower.
|
|
17
|
+
*
|
|
18
|
+
* They are separate kinds rather than one widening `action` union because they
|
|
19
|
+
* refuse different things, run on different credentials, and one of them can
|
|
20
|
+
* be run unasked by a scheduler while the others never are.
|
|
21
|
+
*/
|
|
22
|
+
export {};
|
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
* Every string moved verbatim. Help output is what an intern pastes back when
|
|
12
12
|
* something breaks, so it is a user-visible contract like any other.
|
|
13
13
|
*/
|
|
14
|
+
import { SEARCH_KINDS } from "./local-args-tower-search.js";
|
|
15
|
+
/**
|
|
16
|
+
* The corpora `cockpit search` can name, spelled the way `--kind` takes them.
|
|
17
|
+
* Read off `SEARCH_KINDS` rather than typed out: the help string said five for
|
|
18
|
+
* the two months `mail` and `cal` were searchable, so a person was told the
|
|
19
|
+
* door was narrower than it is (BLI-4048).
|
|
20
|
+
*/
|
|
21
|
+
const SEARCH_KIND_LIST = SEARCH_KINDS.join(",");
|
|
22
|
+
const SEARCH_CORPORA_COUNT = SEARCH_KINDS.length;
|
|
14
23
|
/** One entry per Tower noun, in the order `cockpit --help` lists them. */
|
|
15
24
|
export const TOWER_COMMAND_HELP = [
|
|
16
25
|
[
|
|
@@ -130,12 +139,27 @@ export const TOWER_COMMAND_HELP = [
|
|
|
130
139
|
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
131
140
|
],
|
|
132
141
|
],
|
|
142
|
+
[
|
|
143
|
+
"usage",
|
|
144
|
+
[
|
|
145
|
+
"Usage: cockpit usage people [--since 30d|<iso>] [--until <iso>] [--include-automated] [--json]",
|
|
146
|
+
"",
|
|
147
|
+
"Claude Code and Codex usage per person: sessions observed and extracted, tokens (total, output,",
|
|
148
|
+
"and the input / cache split when the row carries it), and an API list-price equivalent that is",
|
|
149
|
+
"labelled as such and is never actual spend. A super_admin sees everyone; a member sees their own row.",
|
|
150
|
+
"--since takes 7d, 30d, 90d or an ISO timestamp; --until an ISO timestamp (default now).",
|
|
151
|
+
"--include-automated adds harness, subagent and scheduled sessions, which are excluded by default.",
|
|
152
|
+
"It presses the same door as the /usage page and the usage_people MCP tool (GET /api/usage/people).",
|
|
153
|
+
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
154
|
+
],
|
|
155
|
+
],
|
|
133
156
|
[
|
|
134
157
|
"search",
|
|
135
158
|
[
|
|
136
|
-
|
|
159
|
+
`Usage: cockpit search "<words>" [--kind ${SEARCH_KIND_LIST}] [--limit <n>] [--json]`,
|
|
137
160
|
"",
|
|
138
|
-
|
|
161
|
+
`One bar over ${SEARCH_CORPORA_COUNT} corpora: documents, messages, issues, meeting notes, mail, calendar`,
|
|
162
|
+
"events and memory.",
|
|
139
163
|
"It presses the SAME door the browser's search bar presses (GET /api/search), so what you",
|
|
140
164
|
"read here is the same row, the same ranking and the same snippet a person sees in Tower.",
|
|
141
165
|
"",
|
|
@@ -144,12 +168,14 @@ export const TOWER_COMMAND_HELP = [
|
|
|
144
168
|
"Quoted phrases and -word work inside the query itself — the query goes to Postgres's",
|
|
145
169
|
"websearch parser, which shrugs at anything a person can type instead of raising.",
|
|
146
170
|
"",
|
|
147
|
-
|
|
171
|
+
`--kind narrows to one or more corpora, comma separated: ${SEARCH_KIND_LIST}.`,
|
|
172
|
+
`Omit it and all ${SEARCH_CORPORA_COUNT} are searched.`,
|
|
148
173
|
"--limit caps the number of results (default 20, maximum 50).",
|
|
149
174
|
"--json writes the door's whole answer to stdout, hits, per-kind counts, failures and all.",
|
|
150
175
|
"",
|
|
151
|
-
|
|
152
|
-
"own database session, so a document or a channel you cannot open is not in
|
|
176
|
+
`Every result is scoped by what YOU may read: ${SEARCH_CORPORA_COUNT - 1} of the ${SEARCH_CORPORA_COUNT} corpora are`,
|
|
177
|
+
"searched on your own database session, so a document or a channel you cannot open is not in",
|
|
178
|
+
"the list; memory runs on the service role behind the memory doors' own gate.",
|
|
153
179
|
"",
|
|
154
180
|
"A corpus that could not ANSWER gets its own line, separately from \"nothing matched\" —",
|
|
155
181
|
"those are different facts and folding them together would let a broken search read as silence.",
|
|
@@ -305,12 +305,14 @@ export function localSubcommandHelp(command) {
|
|
|
305
305
|
[
|
|
306
306
|
"scout",
|
|
307
307
|
[
|
|
308
|
-
"Usage: cockpit scout [start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
308
|
+
"Usage: cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
309
309
|
"",
|
|
310
310
|
"Prints the same Scout board the Tower page shows, in the same words: the standing",
|
|
311
311
|
"watch line, the cards waiting on a decision, and the raw signal watch underneath.",
|
|
312
312
|
"This is the board itself. To ask a QUESTION about it — what it means, whether it is",
|
|
313
313
|
"worth doing here — use `cockpit jarvis`, whose readScout tool reads the same rows.",
|
|
314
|
+
"`cockpit scout board` and a bare `cockpit scout` are the same command; the MCP twin",
|
|
315
|
+
"is `scout_board` on the bli-tower server, over the same GET /api/cockpit/scout door.",
|
|
314
316
|
"--days <n> widens or narrows the window (1 to 90; the board defaults to 14).",
|
|
315
317
|
"Reading follows the Scout page audience setting, so whoever can open /scout can run this.",
|
|
316
318
|
"start, dismiss, and undo move one card and are super_admin actions on every surface;",
|
|
@@ -461,6 +463,7 @@ export function localSubcommandHelp(command) {
|
|
|
461
463
|
"share <id> [--yes] — let everyone signed in read the statements that are safe to share. Asks first in a terminal; --yes is required without one, and --json implies --yes.",
|
|
462
464
|
"unshare <id> — take it back. Never asks: it only ever narrows who can read.",
|
|
463
465
|
"move <id> (--to \"<shelf>\"|--clear-shelf) — put it on a different shelf, or take the shelf off.",
|
|
466
|
+
"place <id> [--apply] — say which shelf this note belongs on and why, from the meeting it is part of, who was in the room and its own name. Moves nothing without --apply, and never overrules a shelf somebody typed.",
|
|
464
467
|
"A large note is read by a model on the server and can take a couple of minutes; the terminal says so before it waits.",
|
|
465
468
|
"--json writes one machine-readable object to stdout; every reason, receipt and progress line stays on stderr.",
|
|
466
469
|
"Sharing and moving need a signed-in session the database can see. If this deployment cannot mint one, they are refused as needs_signed_in_session rather than done with no permission check — and reads say when they came back narrower than the browser's.",
|
|
@@ -555,15 +558,32 @@ export function localSubcommandHelp(command) {
|
|
|
555
558
|
[
|
|
556
559
|
"memory",
|
|
557
560
|
[
|
|
558
|
-
"Usage: cockpit memory [
|
|
559
|
-
"",
|
|
560
|
-
"
|
|
561
|
+
"Usage: cockpit memory [search|save|update|forget|install|status|log] [flags]",
|
|
562
|
+
"",
|
|
563
|
+
"The STORE, over the same /api/memory/* doors the bli-memory MCP server calls:",
|
|
564
|
+
" search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent]",
|
|
565
|
+
" repeat --container to search more than one space; omit it for this repository's own.",
|
|
566
|
+
" save [--container <tag>] [--custom-id <id>] [--extract] [--file <path>]",
|
|
567
|
+
" the memory comes from stdin or --file, never from the command line:",
|
|
568
|
+
" `echo \"Edward decided X on 2026-09-10\" | cockpit memory save`.",
|
|
569
|
+
" --custom-id makes a repeat save update instead of duplicating;",
|
|
570
|
+
" --extract distils atomic facts first (one model call) instead of storing as given.",
|
|
571
|
+
" update <id> [--file <path>] replacement text on stdin; the old row is kept as history.",
|
|
572
|
+
" forget <id> [--reason <label>]",
|
|
573
|
+
" forget --content-stdin --container <tag> [--reason <label>] forget by exact text.",
|
|
574
|
+
"Refusals keep the door's own label — needs_rls_client, unknown_memory,",
|
|
575
|
+
"already_superseded, no_match — and --json prints the door's answer unchanged.",
|
|
576
|
+
"",
|
|
577
|
+
"BARE `cockpit memory` PRINTS THIS LIST AND DOES NOTHING ELSE (BLI-4047).",
|
|
578
|
+
"It used to run the installer; installing is now `cockpit memory install`.",
|
|
579
|
+
"",
|
|
580
|
+
"install/status register BLI Memory on this machine: the `bli-memory` MCP server plus the",
|
|
561
581
|
"recall/save hooks, for Claude Code (~/.claude.json, ~/.claude/settings.json)",
|
|
562
582
|
"and for Codex (~/.codex/config.toml, ~/.codex/skills/bli-memory/).",
|
|
563
583
|
"Idempotent: it merges with what is already there, never duplicates its own",
|
|
564
584
|
"entries, and reads the stored config back before reporting success.",
|
|
565
585
|
"`do-everything` runs it, and the sync tick re-runs it at most once a day.",
|
|
566
|
-
"
|
|
586
|
+
"See docs/runbooks/bli-memory-install.md.",
|
|
567
587
|
"log appends an opinion to ~/.codex/AGENT-EXPERIENCE.md and posts it to Tower.",
|
|
568
588
|
"Use --reason-stdin instead of a quoted reason; --json reports shipped status.",
|
|
569
589
|
"Reasons: one line, max 500 characters; no prompts, memory bodies or secrets.",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* user-visible contract like any other.
|
|
9
9
|
*/
|
|
10
10
|
import { DEFAULT_DASHBOARD_URL } from "../local-state.js";
|
|
11
|
+
import { SEARCH_KINDS } from "./local-args-tower-search.js";
|
|
11
12
|
import { localSubcommandHelp } from "./local-help-commands.js";
|
|
12
13
|
export const rootCommandNames = new Set([
|
|
13
14
|
"onboard",
|
|
@@ -51,6 +52,7 @@ export const rootCommandNames = new Set([
|
|
|
51
52
|
"project",
|
|
52
53
|
"search",
|
|
53
54
|
"release",
|
|
55
|
+
"usage",
|
|
54
56
|
]);
|
|
55
57
|
export function localCommandHelp(command) {
|
|
56
58
|
if (command)
|
|
@@ -72,7 +74,7 @@ export function localCommandHelp(command) {
|
|
|
72
74
|
" cockpit jarvis [question] [--prompt <question>] [--as <person>] [--date <YYYY-MM-DD>] [--thread <name>] [--model <key>] [--image <path>|--file <path>] [--no-stream] [--threads|--history [--limit <n>]] [--trace <id|last>] [--dashboard-url <url>] [--json [--show-approval-code]]",
|
|
73
75
|
" cockpit model [show|set <provider:model>] [--json]",
|
|
74
76
|
" cockpit models [list|show <provider:model>|compare <provider:model> <provider:model> [...]] [--highlight] [--dashboard-url <url>] [--json]",
|
|
75
|
-
" cockpit scout [start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
77
|
+
" cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
76
78
|
" cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
|
|
77
79
|
" cockpit slack [coverage [--workspace bli|blue_pearl] [--stale-only] | read [--person <p>] [--channel <c>] [--query <text>] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>]] [--json]",
|
|
78
80
|
" cockpit settings [personal [--chat-model <key>] [--brief-model <key>] | switches [set <key> <value>] | models [set --chat <key>] [--memory <id>] | cli-floor [<version>] | env list|set --project <p> --file <f> --content-stdin|delete --id <uuid> [--yes]] [--json]",
|
|
@@ -81,14 +83,14 @@ export function localCommandHelp(command) {
|
|
|
81
83
|
" cockpit brief [edit|rewrite|history] [--for <person>] [--date <YYYY-MM-DD>] [--delta [--against <YYYY-MM-DD>]] [--version <pageId>] [--tldr|--full] [--versions] [--claims] [--days <n>] [--reason \"<why>\"] [--wait|--no-wait] [--dashboard-url <url>] [--json]",
|
|
82
84
|
" cockpit brief status [--who <person>] [--render] [--dashboard-url <url>] [--json]",
|
|
83
85
|
" cockpit correct --claim <claimId> --text \"<what is wrong>\" [--for <person>] [--version <pageId>] [--supersedes <id>] [--dashboard-url <url>] [--json]",
|
|
84
|
-
" cockpit notes [list|show <id>|shelf|shelves|upload <paths...>|paste|share <id>|unshare <id>|move <id>] [--series <shelf>] [--kind <kind>] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--file <path>] [--name <n>] [--exclude \"<sentence>\"] [--to \"<shelf>\"|--clear-shelf] [--yes] [--dashboard-url <url>] [--json]",
|
|
86
|
+
" cockpit notes [list|show <id>|shelf|shelves|upload <paths...>|paste|share <id>|unshare <id>|move <id>|place <id>] [--series <shelf>] [--kind <kind>] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--file <path>] [--name <n>] [--exclude \"<sentence>\"] [--to \"<shelf>\"|--clear-shelf] [--apply] [--yes] [--dashboard-url <url>] [--json]",
|
|
85
87
|
" cockpit backfill (--since-days <n>|--all) [--source codex|claude] [--dry-run] [--max-files <n>] [--max-depth <n>] [--max-repos <n>] [--yes] [--workspace <path>] [--json]",
|
|
86
88
|
" cockpit status [--workspace <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
87
89
|
" cockpit sessions [--source codex|claude] [--since-days <n>|--all] [--workspace <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
88
90
|
" cockpit serve [--port <port>] [--workspace <path>]",
|
|
89
91
|
" cockpit autostart [install|uninstall|status] [--workspace <path>] [--dashboard-url <url>] [--interval-seconds <n>] [--json]",
|
|
90
92
|
" cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--workspace <path>] [--json]",
|
|
91
|
-
" cockpit memory [install|status|log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"] [--dashboard-url <url>] [--dry-run] [--json]",
|
|
93
|
+
" cockpit memory [search \"<question>\"|save|update <id>|forget <id>|install|status|log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"] [--container <tag>] [--limit <n>] [--no-recent] [--custom-id <id>] [--extract] [--content-stdin] [--reason <label>] [--file <path>] [--dashboard-url <url>] [--dry-run] [--json]",
|
|
92
94
|
" cockpit clean [--dry-run] [--all-committed] [--reconcile] [--dashboard-url <url>] [--json]",
|
|
93
95
|
" cockpit docs [list|tree|read <id|slug>|create --title <t>|update <id>] [--parent <id>|root|--clear-parent] [--query <text>] [--limit <n>] [--visibility org|private] [--file <path>|--body-stdin] [--allow-empty] [--dashboard-url <url>] [--json]",
|
|
94
96
|
" cockpit msg [channels|create <name>|dm <email>|read <channel>|send <channel>|thread <id> --channel <channel>] [--private] [--members a@x,b@y] [--description <text>] [--thread <id>] [--limit <n>] [--dashboard-url <url>] [--json]",
|
|
@@ -96,8 +98,9 @@ export function localCommandHelp(command) {
|
|
|
96
98
|
" cockpit mail [accounts|add-imap --address <a>|inbox|read <thread>|search \"<words>\"|send --account <id> --to <a> --subject <s>|attachment <id> --out <path>|sync <account>|detach <account>] [--account <id>] [--limit <n>] [--unread] [--label <l>] [--file <path>] [--dashboard-url <url>] [--json]",
|
|
97
99
|
" cockpit cal [today|week|next|find \"<words>\"|calendars|add-ical|create --calendar <id> --title <t> --at <iso> --until <iso>|share <id> --org-visible|--private|sync <id> [--full]|detach <id>] [--tz <zone>] [--offset <n>] [--hours <n>] [--from <d> --to <d>] [--calendar <id>] [--limit <n>] [--all] [--dashboard-url <url>] [--json]",
|
|
98
100
|
" cockpit project [list] [--archived] [--dashboard-url <url>] [--json]",
|
|
99
|
-
|
|
101
|
+
` cockpit search "<words>" [--kind ${SEARCH_KINDS.join(",")}] [--limit <n>] [--dashboard-url <url>] [--json]`,
|
|
100
102
|
" cockpit release [--dry-run] [--skip-checks] [--no-floor] [--tag <tag>] [--access <public|restricted>] [--otp <code>]",
|
|
103
|
+
" cockpit usage people [--since 30d|<iso>] [--until <iso>] [--include-automated] [--dashboard-url <url>] [--json]",
|
|
101
104
|
"",
|
|
102
105
|
`Default dashboard: ${DEFAULT_DASHBOARD_URL}. Omit --dashboard-url for normal production use; pass it only for staging/custom dashboards or to force a different pairing.`,
|
|
103
106
|
].join("\n");
|