@bli-cockpit/cli 0.2.98 → 0.2.100
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/backfill-checkpoint.js +3 -1
- package/dist/commands/backfill-issues.js +8 -55
- package/dist/commands/backfill-report.js +22 -6
- package/dist/commands/backfill-scan.js +2 -1
- package/dist/commands/backfill-skip-policy.js +134 -0
- package/dist/commands/careers.js +16 -0
- package/dist/commands/doctor-pipeline-verdicts.js +238 -0
- package/dist/commands/doctor-pipeline.js +37 -107
- package/dist/commands/doctor.js +8 -4
- package/dist/commands/local-args-tower-admin.js +17 -6
- package/dist/commands/local-args-tower-careers.js +20 -0
- package/dist/commands/local-args-tower-pages.js +23 -3
- package/dist/commands/local-args-tower-usage.js +8 -0
- package/dist/commands/local-args-tower.js +3 -1
- package/dist/commands/local-args.js +5 -1
- package/dist/commands/local-help-commands-tower.js +32 -5
- package/dist/commands/local-help-commands.js +11 -2
- package/dist/commands/local-help.js +8 -3
- package/dist/commands/local.js +13 -0
- package/dist/commands/memory-hook-counts.js +29 -8
- package/dist/commands/notes-file.js +8 -1
- package/dist/commands/notes-folders.js +35 -0
- package/dist/commands/notes-writes.js +74 -4
- package/dist/commands/notes.js +11 -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/cursors/backfill-completion-marker.js +135 -0
- package/dist/cursors/backfill-cursor.js +18 -99
- package/dist/process-runner.js +39 -1
- package/dist/sync-lock.js +10 -1
- package/package.json +5 -5
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { inspectBackfillLock } from "../backfill-lock.js";
|
|
4
|
-
import {
|
|
4
|
+
import { readBackfillCompletionMarker, readBackfillCursor, } from "../cursors/backfill-cursor.js";
|
|
5
5
|
import { savedDiscoveryLimitArgs } from "../discovery-limits.js";
|
|
6
|
-
import { describeError } from "../health-detail.js";
|
|
7
6
|
import { runStagingPrune } from "../disk-prune.js";
|
|
8
7
|
import { retentionOptionsFromEnv } from "../disk-retention.js";
|
|
9
8
|
import { readDiskFootprint } from "../disk-usage.js";
|
|
@@ -13,7 +12,8 @@ import { runRawEvidenceLocalGc, rawEvidenceGcSummary } from "../raw-evidence-gc.
|
|
|
13
12
|
import { runBackfillCommand } from "./backfill.js";
|
|
14
13
|
import { diskRowMessage, mib, redeliveryLine } from "./doctor-disk-words.js";
|
|
15
14
|
import { doctorRoots } from "./doctor-access.js";
|
|
16
|
-
import {
|
|
15
|
+
import { backfillCompletionStepState, backfillFixVerdict, jsonField, parseDoctorBackfillJson, parseDoctorSyncJson, syncBacklogDrainingVerdict, syncStandAsideVerdict, } from "./doctor-pipeline-verdicts.js";
|
|
16
|
+
import { fail, needsFix, ok, skipped } from "./doctor-report.js";
|
|
17
17
|
/**
|
|
18
18
|
* The `backfill-complete`, `gc-checked`, `disk-bounded`, and `sync-fresh`
|
|
19
19
|
* check family: does the collection pipeline itself have everything it
|
|
@@ -22,31 +22,15 @@ import { asRecord, fail, needsFix, ok, skipped } from "./doctor-report.js";
|
|
|
22
22
|
* is committed, disk-bounded reads per-file against the upload ledger
|
|
23
23
|
* (BLI-3619), and sync-fresh is the one check whose "fix" is a live proof
|
|
24
24
|
* (`cockpit sync`) rather than a local computation.
|
|
25
|
-
*/
|
|
26
|
-
const GC_MIN_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
27
|
-
/**
|
|
28
|
-
* Pure so it can be unit-tested without touching the real machine's home
|
|
29
|
-
* directory (`getCollectorRuntimePaths()` defaults to `os.homedir()` and
|
|
30
|
-
* doctor never threads `--home` through the backfill steps). Returns `null`
|
|
31
|
-
* when the marker does not cover the roots/sources — the caller falls
|
|
32
|
-
* through to the lock/never-run diagnosis in that case.
|
|
33
25
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
26
|
+
* This module is the half that TOUCHES the machine: it runs the catch-up,
|
|
27
|
+
* prunes the disk, execs a sync. The verdicts those steps reach — what a
|
|
28
|
+
* completion marker proves, what a stand-aside receipt means, when an
|
|
29
|
+
* unfinished backfill is busy rather than broken — are pure functions in
|
|
30
|
+
* `doctor-pipeline-verdicts.ts`, and every one of them is re-exported from
|
|
31
|
+
* here so callers keep one address.
|
|
38
32
|
*/
|
|
39
|
-
|
|
40
|
-
if (!backfillCompletionCovers(marker, roots, ["codex", "claude_code"])) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
const oversized = marker?.oversized_skips;
|
|
44
|
-
if (oversized && oversized.count > 0) {
|
|
45
|
-
return ok("backfill-complete", "complete_with_oversized_skips", `caught up on old Codex and Claude sessions in every saved folder ` +
|
|
46
|
-
`(complete_with_oversized_skips · ${oversized.count} file${oversized.count === 1 ? "" : "s"} too big to upload)`);
|
|
47
|
-
}
|
|
48
|
-
return ok("backfill-complete", "complete", "caught up on old Codex and Claude sessions in every saved folder");
|
|
49
|
-
}
|
|
33
|
+
const GC_MIN_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
50
34
|
export async function checkBackfillState(context) {
|
|
51
35
|
const paths = getCollectorRuntimePaths();
|
|
52
36
|
const roots = await doctorRoots(context);
|
|
@@ -70,7 +54,8 @@ export async function fixBackfillState(context) {
|
|
|
70
54
|
yes: true,
|
|
71
55
|
json: true,
|
|
72
56
|
}, capture.io);
|
|
73
|
-
const
|
|
57
|
+
const stdout = capture.stdout();
|
|
58
|
+
const output = `${stdout}\n${capture.stderr()}`;
|
|
74
59
|
if (code === 0) {
|
|
75
60
|
// Re-read the marker this run just wrote instead of hand-rolling a second
|
|
76
61
|
// message: `checkBackfillState`'s pure core already knows how to say
|
|
@@ -81,11 +66,9 @@ export async function fixBackfillState(context) {
|
|
|
81
66
|
return recheck;
|
|
82
67
|
return ok("backfill-complete", "completed", "ran `cockpit backfill --all --yes`");
|
|
83
68
|
}
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
return fail("backfill-complete", reason ?? "backfill_failed", "backfill did not complete");
|
|
69
|
+
const verdict = backfillFixVerdict(parseDoctorBackfillJson(stdout), jsonField(output, "failure_reason"));
|
|
70
|
+
console.error("[cockpit-doctor] catch-up run did not finish", JSON.stringify({ reason: verdict.code, row_status: verdict.status, exit_code: code }));
|
|
71
|
+
return verdict;
|
|
89
72
|
}
|
|
90
73
|
export async function checkGcState(context) {
|
|
91
74
|
if (context.io.env["COCKPIT_DISABLE_GC"] === "1") {
|
|
@@ -196,11 +179,6 @@ function captureStream(target, chunks, forward) {
|
|
|
196
179
|
},
|
|
197
180
|
};
|
|
198
181
|
}
|
|
199
|
-
function jsonField(output, field) {
|
|
200
|
-
const escaped = field.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
201
|
-
const match = output.match(new RegExp(`"${escaped}"\\s*:\\s*"([^"]+)"`, "u"));
|
|
202
|
-
return match?.[1] ?? null;
|
|
203
|
-
}
|
|
204
182
|
export async function checkSyncState(context) {
|
|
205
183
|
const roots = await doctorRoots(context);
|
|
206
184
|
if (roots.length === 0) {
|
|
@@ -211,75 +189,6 @@ export async function checkSyncState(context) {
|
|
|
211
189
|
// and only turns green from those command receipts.
|
|
212
190
|
return needsFix("sync-fresh", "per_root_verification_required", `fresh upload proof is required for ${roots.length} saved root${roots.length === 1 ? "" : "s"}`);
|
|
213
191
|
}
|
|
214
|
-
/**
|
|
215
|
-
* `cockpit sync --json` prints exactly one JSON document to stdout (stderr is
|
|
216
|
-
* for human text; see AGENTS.md logging conventions), so this is a real parse
|
|
217
|
-
* rather than the doctor module's usual regex field-scrape — which cannot
|
|
218
|
-
* disambiguate same-named fields nested under `codex_sessions.codex` vs
|
|
219
|
-
* `codex_sessions.claude` (BLI-2728).
|
|
220
|
-
*/
|
|
221
|
-
function parseDoctorSyncJson(stdout) {
|
|
222
|
-
try {
|
|
223
|
-
const parsed = JSON.parse(stdout.trim());
|
|
224
|
-
return parsed && typeof parsed === "object" ? parsed : null;
|
|
225
|
-
}
|
|
226
|
-
catch (error) {
|
|
227
|
-
// `null` sends doctor back to its regex field-scrape, quietly losing the
|
|
228
|
-
// BLI-2728 disambiguation. Something wrote to stdout that was not the one
|
|
229
|
-
// JSON document the contract promises — a stray console.log in the
|
|
230
|
-
// collector would do exactly this and look like nothing at all.
|
|
231
|
-
console.error("[cockpit-doctor] sync --json stdout was not one JSON document", JSON.stringify({
|
|
232
|
-
reason: "sync_json_unparseable",
|
|
233
|
-
byte_size: stdout.length,
|
|
234
|
-
...describeError(error),
|
|
235
|
-
}));
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* BLI-2728: a tick that only deferred objects past the per-tick raw-evidence
|
|
241
|
-
* object budget (`RAW_EVIDENCE_DEFAULT_OBJECT_BUDGET`, adapters/raw-evidence.ts)
|
|
242
|
-
* is a backlog that is draining, not a failure — `attributedSyncRunStatus`
|
|
243
|
-
* marks the run not-fully-`ok` (so `cockpit sync` exits non-zero and doctor's
|
|
244
|
-
* exec sees `code !== 0`) purely because objects remain queued, with the
|
|
245
|
-
* per-repo upload itself still having succeeded. A genuine failure (auth,
|
|
246
|
-
* server rejection, network, a real upload_failed outcome, an unposted
|
|
247
|
-
* session report) must still read red — this only fires when NOTHING else in
|
|
248
|
-
* the tick's own summary looks wrong. Pure so it is unit-testable without a
|
|
249
|
-
* live exec/fs harness; the remaining-object count is read straight from the
|
|
250
|
-
* tick's own summary, never recomputed.
|
|
251
|
-
*/
|
|
252
|
-
export function syncBacklogDrainingVerdict(parsed) {
|
|
253
|
-
if (!parsed)
|
|
254
|
-
return null;
|
|
255
|
-
const deferredObjects = positiveNumberOrZero(parsed.raw_evidence_deferred_object_budget);
|
|
256
|
-
if (deferredObjects <= 0)
|
|
257
|
-
return null;
|
|
258
|
-
const deferredBytes = positiveNumberOrZero(parsed.raw_evidence_deferred_byte_budget);
|
|
259
|
-
const failedCount = positiveNumberOrZero(parsed.raw_evidence_failed_count);
|
|
260
|
-
const retryReasons = Array.isArray(parsed.raw_evidence_retry_reasons)
|
|
261
|
-
? parsed.raw_evidence_retry_reasons.length
|
|
262
|
-
: 0;
|
|
263
|
-
const sessions = asRecord(parsed.codex_sessions);
|
|
264
|
-
const reportPosted = sessions?.["report_posted"];
|
|
265
|
-
const codexReadFailures = positiveNumberOrZero(asRecord(sessions?.["codex"])?.["read_failures"]);
|
|
266
|
-
const claudeSessions = asRecord(sessions?.["claude"]);
|
|
267
|
-
const claudeReadFailures = positiveNumberOrZero(claudeSessions?.["read_failures"]);
|
|
268
|
-
const claudeSidecarsFailed = positiveNumberOrZero(claudeSessions?.["sidecars_failed"]);
|
|
269
|
-
const onlyDeferredObjectBudget = deferredBytes === 0 &&
|
|
270
|
-
failedCount === 0 &&
|
|
271
|
-
retryReasons === 0 &&
|
|
272
|
-
reportPosted === true &&
|
|
273
|
-
codexReadFailures === 0 &&
|
|
274
|
-
claudeReadFailures === 0 &&
|
|
275
|
-
claudeSidecarsFailed === 0;
|
|
276
|
-
return onlyDeferredObjectBudget ? { remainingObjects: deferredObjects } : null;
|
|
277
|
-
}
|
|
278
|
-
function positiveNumberOrZero(value) {
|
|
279
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
280
|
-
? value
|
|
281
|
-
: 0;
|
|
282
|
-
}
|
|
283
192
|
export async function fixSyncState(context) {
|
|
284
193
|
const exec = context.io.exec;
|
|
285
194
|
if (!exec)
|
|
@@ -301,6 +210,23 @@ export async function fixSyncState(context) {
|
|
|
301
210
|
const parsed = parseDoctorSyncJson(result.stdout);
|
|
302
211
|
const output = `${result.stdout}\n${result.stderr}`;
|
|
303
212
|
const status = jsonField(output, "status");
|
|
213
|
+
// BLI-4303, before any verdict: a sync that stood aside for the collection
|
|
214
|
+
// lock never ran, so neither its exit code nor its missing `uploaded`
|
|
215
|
+
// receipt says anything about this machine's health.
|
|
216
|
+
const standAside = syncStandAsideVerdict(parsed, status);
|
|
217
|
+
if (standAside) {
|
|
218
|
+
console.error("[cockpit-doctor] sync stood aside for a running collection run", JSON.stringify({
|
|
219
|
+
reason: standAside.code,
|
|
220
|
+
owner_alive: standAside.ownerAlive,
|
|
221
|
+
held_since: standAside.heldSince,
|
|
222
|
+
}));
|
|
223
|
+
return standAside.ownerAlive
|
|
224
|
+
? ok("sync-fresh", standAside.code, "a background sync is running right now and owns the collection lock " +
|
|
225
|
+
`(last heartbeat ${standAside.heldSince}); this machine is collecting`)
|
|
226
|
+
: needsFix("sync-fresh", standAside.code, "sync stood aside for a collection lock whose owner has stopped " +
|
|
227
|
+
`reporting (last heartbeat ${standAside.heldSince ?? "unknown"}); ` +
|
|
228
|
+
"rerun `cockpit doctor` — the next sync takes the lock over");
|
|
229
|
+
}
|
|
304
230
|
if (result.code !== 0) {
|
|
305
231
|
const draining = syncBacklogDrainingVerdict(parsed);
|
|
306
232
|
if (draining) {
|
|
@@ -317,4 +243,8 @@ export async function fixSyncState(context) {
|
|
|
317
243
|
return ok("sync-fresh", "synced", roots.length === 1
|
|
318
244
|
? "ran `cockpit sync` and received an uploaded receipt"
|
|
319
245
|
: `received uploaded receipts for ${roots.length} saved roots`);
|
|
320
|
-
}
|
|
246
|
+
}
|
|
247
|
+
// Re-exported so `doctor.ts`, the doctor tests and every other caller keep
|
|
248
|
+
// importing these from `./doctor-pipeline.js` regardless of which sibling
|
|
249
|
+
// computes them.
|
|
250
|
+
export { backfillCompletionStepState, backfillFixVerdict, isCollectionBusyReason, syncBacklogDrainingVerdict, syncStandAsideVerdict, } from "./doctor-pipeline-verdicts.js";
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { withStage } from "../crash-guard.js";
|
|
1
2
|
import { checkSingleInstallState, fixAuthState, fixRootState, readAuthState, readRootState, } from "./doctor-access.js";
|
|
2
|
-
import { backfillCompletionStepState, checkBackfillState, checkDiskState, checkGcState, checkSyncState, fixBackfillState, fixDiskState, fixGcState, fixSyncState, syncBacklogDrainingVerdict, } from "./doctor-pipeline.js";
|
|
3
|
+
import { backfillCompletionStepState, backfillFixVerdict, checkBackfillState, checkDiskState, checkGcState, checkSyncState, fixBackfillState, fixDiskState, fixGcState, fixSyncState, syncBacklogDrainingVerdict, syncStandAsideVerdict, } from "./doctor-pipeline.js";
|
|
3
4
|
import { checkMcpAnswersState } from "./doctor-mcp.js";
|
|
4
5
|
import { checkMemoryDaemonState } from "./doctor-memory-daemon.js";
|
|
5
6
|
import { checkAutostartState, checkMemoryState, fixAutostartState, fixMemoryState, } from "./doctor-registration.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;
|
|
@@ -174,4 +178,4 @@ function defaultDoctorDeps(hooks) {
|
|
|
174
178
|
}
|
|
175
179
|
// Re-exported so every consumer keeps importing from `./doctor.js` regardless
|
|
176
180
|
// of which sibling a symbol now lives in.
|
|
177
|
-
export { backfillCompletionStepState, syncBacklogDrainingVerdict };
|
|
181
|
+
export { backfillCompletionStepState, backfillFixVerdict, syncBacklogDrainingVerdict, syncStandAsideVerdict, };
|
|
@@ -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,20 @@
|
|
|
1
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from './local-arg-values.js';
|
|
2
|
+
export function parseCareersArgs(args) {
|
|
3
|
+
const values = parseNamedArgs(args, { allowedFlags: ['--role', '--min-score', '--since', '--home', '--dashboard-url', '--json'], valueFlags: ['--role', '--min-score', '--since', '--home', '--dashboard-url'] });
|
|
4
|
+
const action = values.positionals[0] ?? 'list';
|
|
5
|
+
if (action !== 'list' && action !== 'show' && action !== 'rescreen')
|
|
6
|
+
throw new Error('careers takes list, show, or rescreen.');
|
|
7
|
+
const id = values.positionals[1];
|
|
8
|
+
if (action !== 'list' && !id)
|
|
9
|
+
throw new Error(`careers ${action} needs an application id.`);
|
|
10
|
+
if (values.positionals.length > (action === 'list' ? 1 : 2))
|
|
11
|
+
throw new Error('Unexpected careers argument.');
|
|
12
|
+
const rawScore = values.flags.get('--min-score');
|
|
13
|
+
const minScore = rawScore === undefined ? undefined : Number(rawScore);
|
|
14
|
+
if (minScore !== undefined && (!Number.isFinite(minScore) || minScore < 0 || minScore > 100))
|
|
15
|
+
throw new Error('--min-score must be 0 to 100.');
|
|
16
|
+
const since = optionalNonEmpty(values.flags.get('--since'));
|
|
17
|
+
if (since && !Number.isFinite(Date.parse(since)))
|
|
18
|
+
throw new Error('--since must be a date.');
|
|
19
|
+
return { kind: 'careers', action, id, role: optionalNonEmpty(values.flags.get('--role')), minScore, since: since ? new Date(since).toISOString() : undefined, homeDir: optionalNonEmpty(values.flags.get('--home')), dashboardUrl: optionalUrl(values.flags.get('--dashboard-url')), json: values.booleans.has('--json') };
|
|
20
|
+
}
|
|
@@ -153,6 +153,7 @@ export function parseBriefArgs(args) {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
const NOTES_ACTIONS = new Set([
|
|
156
|
+
"folders", "mkdir", "rmdir", "rename",
|
|
156
157
|
"list",
|
|
157
158
|
"show",
|
|
158
159
|
"shelf",
|
|
@@ -162,6 +163,7 @@ const NOTES_ACTIONS = new Set([
|
|
|
162
163
|
"share",
|
|
163
164
|
"unshare",
|
|
164
165
|
"move",
|
|
166
|
+
"place",
|
|
165
167
|
]);
|
|
166
168
|
/** Actions whose first positional is the note it acts on. */
|
|
167
169
|
const NOTES_ACTIONS_NEEDING_A_NOTE = new Set([
|
|
@@ -169,17 +171,21 @@ const NOTES_ACTIONS_NEEDING_A_NOTE = new Set([
|
|
|
169
171
|
"share",
|
|
170
172
|
"unshare",
|
|
171
173
|
"move",
|
|
174
|
+
"place",
|
|
172
175
|
]);
|
|
173
176
|
export function parseNotesArgs(args) {
|
|
174
177
|
const values = parseNamedArgs(args, {
|
|
175
178
|
allowedFlags: [
|
|
179
|
+
"--wait",
|
|
176
180
|
"--home",
|
|
177
181
|
"--dashboard-url",
|
|
178
182
|
"--file",
|
|
179
183
|
"--name",
|
|
180
184
|
"--exclude",
|
|
181
185
|
"--to",
|
|
186
|
+
"--folder",
|
|
182
187
|
"--clear-shelf",
|
|
188
|
+
"--apply",
|
|
183
189
|
"--series",
|
|
184
190
|
"--kind",
|
|
185
191
|
"--since",
|
|
@@ -187,6 +193,7 @@ export function parseNotesArgs(args) {
|
|
|
187
193
|
"--limit",
|
|
188
194
|
"--yes",
|
|
189
195
|
"--json",
|
|
196
|
+
"--tree",
|
|
190
197
|
],
|
|
191
198
|
valueFlags: [
|
|
192
199
|
"--home",
|
|
@@ -195,6 +202,7 @@ export function parseNotesArgs(args) {
|
|
|
195
202
|
"--name",
|
|
196
203
|
"--exclude",
|
|
197
204
|
"--to",
|
|
205
|
+
"--folder",
|
|
198
206
|
"--series",
|
|
199
207
|
"--kind",
|
|
200
208
|
"--since",
|
|
@@ -207,7 +215,7 @@ export function parseNotesArgs(args) {
|
|
|
207
215
|
const first = values.positionals[0];
|
|
208
216
|
const action = (first === undefined ? "list" : first);
|
|
209
217
|
if (!NOTES_ACTIONS.has(action)) {
|
|
210
|
-
throw new Error(`Unknown notes command: ${first}. Try list, show, shelf, shelves, upload, paste, share, unshare, or
|
|
218
|
+
throw new Error(`Unknown notes command: ${first}. Try list, show, shelf, shelves, upload, paste, share, unshare, move, or place.`);
|
|
211
219
|
}
|
|
212
220
|
const rest = values.positionals.slice(first === undefined ? 0 : 1);
|
|
213
221
|
const json = values.booleans.has("--json");
|
|
@@ -233,23 +241,33 @@ export function parseNotesArgs(args) {
|
|
|
233
241
|
if (paths.length === 0)
|
|
234
242
|
throw new Error("notes upload needs at least one file path.");
|
|
235
243
|
}
|
|
244
|
+
else if (action === "mkdir" || action === "rmdir" || action === "rename") {
|
|
245
|
+
if (rest.length !== 1 || !rest[0]?.trim())
|
|
246
|
+
throw new Error(`notes ${action} needs one folder path.`);
|
|
247
|
+
}
|
|
236
248
|
else if (action !== "paste" && !NOTES_ACTIONS_NEEDING_A_NOTE.has(action) && rest.length > 0) {
|
|
237
249
|
throw new Error(`notes ${action} does not take "${rest[0]}".`);
|
|
238
250
|
}
|
|
251
|
+
if (action === "rename" && !optionalNonEmpty(values.flags.get("--name")))
|
|
252
|
+
throw new Error("notes rename needs --name <name>.");
|
|
239
253
|
const to = optionalNonEmpty(values.flags.get("--to"));
|
|
240
254
|
const clearShelf = values.booleans.has("--clear-shelf");
|
|
241
255
|
if (action === "move") {
|
|
242
256
|
if (to && clearShelf) {
|
|
243
257
|
throw new Error("notes move accepts either --to or --clear-shelf, not both.");
|
|
244
258
|
}
|
|
245
|
-
if (!to && !clearShelf) {
|
|
246
|
-
throw new Error('notes move needs --to "<shelf>"
|
|
259
|
+
if (!to && !clearShelf && !values.flags.get("--folder")) {
|
|
260
|
+
throw new Error('notes move needs --to "<shelf>", --clear-shelf, or --folder "<path>".');
|
|
247
261
|
}
|
|
248
262
|
}
|
|
249
263
|
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
250
264
|
return {
|
|
251
265
|
kind: "notes",
|
|
266
|
+
...(values.booleans.has("--wait") ? { wait: true } : {}),
|
|
252
267
|
action,
|
|
268
|
+
...(values.flags.has("--folder") ? { folder: values.flags.get("--folder") } : {}),
|
|
269
|
+
...(action === "mkdir" || action === "rmdir" || action === "rename" ? { folder: rest[0] } : {}),
|
|
270
|
+
...(values.booleans.has("--tree") ? { tree: true } : {}),
|
|
253
271
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
254
272
|
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
255
273
|
...(noteId ? { noteId } : {}),
|
|
@@ -259,6 +277,8 @@ export function parseNotesArgs(args) {
|
|
|
259
277
|
exclude: optionalNonEmpty(values.flags.get("--exclude")),
|
|
260
278
|
...(to ? { to } : {}),
|
|
261
279
|
...(clearShelf ? { clearShelf } : {}),
|
|
280
|
+
// BLI-4058. `place` says where a note belongs; only `--apply` moves it.
|
|
281
|
+
...(values.booleans.has("--apply") ? { apply: true } : {}),
|
|
262
282
|
series: optionalNonEmpty(values.flags.get("--series")),
|
|
263
283
|
meetingKind: optionalNonEmpty(values.flags.get("--kind")),
|
|
264
284
|
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,6 @@ 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";
|
|
40
|
+
export { parseCareersArgs } from './local-args-tower-careers.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, parseCareersArgs, } 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,10 @@ export function parseLocalArgs(argv) {
|
|
|
115
115
|
return parseSearchArgs(argv.slice(1));
|
|
116
116
|
case "release":
|
|
117
117
|
return parseReleaseArgs(argv.slice(1));
|
|
118
|
+
case "careers":
|
|
119
|
+
return parseCareersArgs(argv.slice(1));
|
|
120
|
+
case "usage":
|
|
121
|
+
return parseUsageArgs(argv.slice(1));
|
|
118
122
|
default:
|
|
119
123
|
throw new Error(`Unknown local command: ${command ?? ""}`);
|
|
120
124
|
}
|
|
@@ -11,8 +11,18 @@
|
|
|
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 = [
|
|
25
|
+
["careers", ["Usage: cockpit careers [list|show <id>|rescreen <id>] [--role <slug>] [--min-score <n>] [--since <date>] [--json]", "Super-admin application review. Lists at most 100 matches with total and has_more; use filters to narrow."]],
|
|
16
26
|
[
|
|
17
27
|
"docs",
|
|
18
28
|
[
|
|
@@ -130,12 +140,27 @@ export const TOWER_COMMAND_HELP = [
|
|
|
130
140
|
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
131
141
|
],
|
|
132
142
|
],
|
|
143
|
+
[
|
|
144
|
+
"usage",
|
|
145
|
+
[
|
|
146
|
+
"Usage: cockpit usage people [--since 30d|<iso>] [--until <iso>] [--include-automated] [--json]",
|
|
147
|
+
"",
|
|
148
|
+
"Claude Code and Codex usage per person: sessions observed and extracted, tokens (total, output,",
|
|
149
|
+
"and the input / cache split when the row carries it), and an API list-price equivalent that is",
|
|
150
|
+
"labelled as such and is never actual spend. A super_admin sees everyone; a member sees their own row.",
|
|
151
|
+
"--since takes 7d, 30d, 90d or an ISO timestamp; --until an ISO timestamp (default now).",
|
|
152
|
+
"--include-automated adds harness, subagent and scheduled sessions, which are excluded by default.",
|
|
153
|
+
"It presses the same door as the /usage page and the usage_people MCP tool (GET /api/usage/people).",
|
|
154
|
+
"--json writes one machine-readable object to stdout; every reason and receipt line stays on stderr.",
|
|
155
|
+
],
|
|
156
|
+
],
|
|
133
157
|
[
|
|
134
158
|
"search",
|
|
135
159
|
[
|
|
136
|
-
|
|
160
|
+
`Usage: cockpit search "<words>" [--kind ${SEARCH_KIND_LIST}] [--limit <n>] [--json]`,
|
|
137
161
|
"",
|
|
138
|
-
|
|
162
|
+
`One bar over ${SEARCH_CORPORA_COUNT} corpora: documents, messages, issues, meeting notes, mail, calendar`,
|
|
163
|
+
"events and memory.",
|
|
139
164
|
"It presses the SAME door the browser's search bar presses (GET /api/search), so what you",
|
|
140
165
|
"read here is the same row, the same ranking and the same snippet a person sees in Tower.",
|
|
141
166
|
"",
|
|
@@ -144,12 +169,14 @@ export const TOWER_COMMAND_HELP = [
|
|
|
144
169
|
"Quoted phrases and -word work inside the query itself — the query goes to Postgres's",
|
|
145
170
|
"websearch parser, which shrugs at anything a person can type instead of raising.",
|
|
146
171
|
"",
|
|
147
|
-
|
|
172
|
+
`--kind narrows to one or more corpora, comma separated: ${SEARCH_KIND_LIST}.`,
|
|
173
|
+
`Omit it and all ${SEARCH_CORPORA_COUNT} are searched.`,
|
|
148
174
|
"--limit caps the number of results (default 20, maximum 50).",
|
|
149
175
|
"--json writes the door's whole answer to stdout, hits, per-kind counts, failures and all.",
|
|
150
176
|
"",
|
|
151
|
-
|
|
152
|
-
"own database session, so a document or a channel you cannot open is not in
|
|
177
|
+
`Every result is scoped by what YOU may read: ${SEARCH_CORPORA_COUNT - 1} of the ${SEARCH_CORPORA_COUNT} corpora are`,
|
|
178
|
+
"searched on your own database session, so a document or a channel you cannot open is not in",
|
|
179
|
+
"the list; memory runs on the service role behind the memory doors' own gate.",
|
|
153
180
|
"",
|
|
154
181
|
"A corpus that could not ANSWER gets its own line, separately from \"nothing matched\" —",
|
|
155
182
|
"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;",
|
|
@@ -448,8 +450,14 @@ export function localSubcommandHelp(command) {
|
|
|
448
450
|
[
|
|
449
451
|
"notes",
|
|
450
452
|
[
|
|
451
|
-
"Usage: cockpit notes [list|show <id>|shelf|shelves|upload <paths
|
|
453
|
+
"Usage: cockpit notes [list|show <id>|shelf|shelves|folders|mkdir <path>|rmdir <path>|rename <path>|upload <paths...> [--wait]|paste|share <id>|unshare <id>|move <id>] [flags]",
|
|
454
|
+
"Audio uploads: MP3, M4A, WAV, WebM and OGG, up to 4 MB. --wait polls transcription for at most 330 seconds.",
|
|
452
455
|
"",
|
|
456
|
+
"folders [--tree] [--json]: list folders and visible note counts.",
|
|
457
|
+
"mkdir <path> [--json]: create a folder and any missing ancestors.",
|
|
458
|
+
"rename <path> --name <name> [--json]: rename a folder and its descendant paths.",
|
|
459
|
+
"rmdir <path> [--json]: delete an empty folder; refuses notes or subfolders.",
|
|
460
|
+
"upload, paste and move accept --folder <path>; use / for Root. Shelf placement stays independent.",
|
|
453
461
|
"The /meeting-notes surface, typed. Bare `cockpit notes` lists the library.",
|
|
454
462
|
"list [--series <shelf>] [--kind <kind>] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] — every note you may open, grouped by shelf, newest meeting first. Never a word of a note.",
|
|
455
463
|
"show <id> — one note with its text, its shelf, and why you are allowed to see it.",
|
|
@@ -461,6 +469,7 @@ export function localSubcommandHelp(command) {
|
|
|
461
469
|
"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
470
|
"unshare <id> — take it back. Never asks: it only ever narrows who can read.",
|
|
463
471
|
"move <id> (--to \"<shelf>\"|--clear-shelf) — put it on a different shelf, or take the shelf off.",
|
|
472
|
+
"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
473
|
"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
474
|
"--json writes one machine-readable object to stdout; every reason, receipt and progress line stays on stderr.",
|
|
466
475
|
"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.",
|
|
@@ -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,8 @@ export const rootCommandNames = new Set([
|
|
|
51
52
|
"project",
|
|
52
53
|
"search",
|
|
53
54
|
"release",
|
|
55
|
+
"careers",
|
|
56
|
+
"usage",
|
|
54
57
|
]);
|
|
55
58
|
export function localCommandHelp(command) {
|
|
56
59
|
if (command)
|
|
@@ -72,7 +75,7 @@ export function localCommandHelp(command) {
|
|
|
72
75
|
" 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
76
|
" cockpit model [show|set <provider:model>] [--json]",
|
|
74
77
|
" 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]",
|
|
78
|
+
" cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
76
79
|
" cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
|
|
77
80
|
" 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
81
|
" 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,7 +84,7 @@ export function localCommandHelp(command) {
|
|
|
81
84
|
" 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
85
|
" cockpit brief status [--who <person>] [--render] [--dashboard-url <url>] [--json]",
|
|
83
86
|
" 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
|
|
87
|
+
" cockpit notes [list|show <id>|shelf|shelves|folders|mkdir <path>|rmdir <path>|rename <path>|upload <paths...> [--wait]|paste|share <id>|unshare <id>|move <id>|place <id>] [--folder <path>] [--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
88
|
" 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
89
|
" cockpit status [--workspace <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
87
90
|
" cockpit sessions [--source codex|claude] [--since-days <n>|--all] [--workspace <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
@@ -96,8 +99,10 @@ export function localCommandHelp(command) {
|
|
|
96
99
|
" 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
100
|
" 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
101
|
" cockpit project [list] [--archived] [--dashboard-url <url>] [--json]",
|
|
99
|
-
|
|
102
|
+
` cockpit search "<words>" [--kind ${SEARCH_KINDS.join(",")}] [--limit <n>] [--dashboard-url <url>] [--json]`,
|
|
100
103
|
" cockpit release [--dry-run] [--skip-checks] [--no-floor] [--tag <tag>] [--access <public|restricted>] [--otp <code>]",
|
|
104
|
+
" cockpit careers [list|show <id>|rescreen <id>] [--role <slug>] [--min-score <n>] [--since <date>] [--json]",
|
|
105
|
+
" cockpit usage people [--since 30d|<iso>] [--until <iso>] [--include-automated] [--dashboard-url <url>] [--json]",
|
|
101
106
|
"",
|
|
102
107
|
`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
108
|
].join("\n");
|