@bli-cockpit/cli 0.2.236 → 0.2.238

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.
Files changed (143) hide show
  1. package/dist/adapters/attribution-core-fallbacks.js +15 -11
  2. package/dist/adapters/attribution-core-score.js +111 -87
  3. package/dist/adapters/car-state.js +38 -35
  4. package/dist/adapters/claude-attribution-discovery.js +81 -56
  5. package/dist/adapters/claude-attribution-score.js +108 -94
  6. package/dist/adapters/claude-attribution-signals.js +52 -42
  7. package/dist/adapters/codex-attribution.js +42 -33
  8. package/dist/adapters/codex-session-signals.js +11 -9
  9. package/dist/adapters/git-state.js +56 -50
  10. package/dist/adapters/local-sources.js +52 -43
  11. package/dist/adapters/raw-evidence-git-diff.js +4 -2
  12. package/dist/adapters/raw-evidence-sanitize.js +15 -12
  13. package/dist/adapters/raw-evidence-transcript-file.js +32 -31
  14. package/dist/autostart-self-heal.js +18 -2
  15. package/dist/commands/agent-session-report.js +51 -59
  16. package/dist/commands/agent-setup-observed.js +30 -23
  17. package/dist/commands/agent-setup.js +96 -45
  18. package/dist/commands/autostart-heal.js +8 -2
  19. package/dist/commands/autostart-reading.js +3 -0
  20. package/dist/commands/backfill-evidence-outcomes.js +9 -6
  21. package/dist/commands/backfill-issues.js +3 -0
  22. package/dist/commands/backfill-scan.js +27 -22
  23. package/dist/commands/backfill-upload.js +69 -60
  24. package/dist/commands/backfill.js +26 -21
  25. package/dist/commands/brief-done.js +4 -1
  26. package/dist/commands/brief-edit.js +71 -46
  27. package/dist/commands/cal-detail.js +35 -0
  28. package/dist/commands/cal-overlay.js +106 -0
  29. package/dist/commands/cal.js +40 -23
  30. package/dist/commands/clean.js +4 -1
  31. package/dist/commands/docs-read.js +5 -2
  32. package/dist/commands/doctor-backfill.js +88 -71
  33. package/dist/commands/doctor-pipeline.js +59 -46
  34. package/dist/commands/doctor-usage-telemetry.js +2 -1
  35. package/dist/commands/heartbeat-post.js +116 -0
  36. package/dist/commands/heartbeat-token-lock.js +46 -33
  37. package/dist/commands/heartbeat.js +43 -125
  38. package/dist/commands/install-update.js +17 -15
  39. package/dist/commands/issue-show.js +27 -28
  40. package/dist/commands/issue-write.js +70 -23
  41. package/dist/commands/issue.js +68 -37
  42. package/dist/commands/jarvis-live-answer.js +99 -0
  43. package/dist/commands/jarvis-render.js +1 -94
  44. package/dist/commands/jarvis-trace.js +30 -22
  45. package/dist/commands/jarvis-turn-report.js +124 -0
  46. package/dist/commands/jarvis-turn.js +108 -182
  47. package/dist/commands/jarvis.js +26 -17
  48. package/dist/commands/local-args-collector-setup.js +82 -84
  49. package/dist/commands/local-args-collector-work.js +37 -38
  50. package/dist/commands/local-args-tower-admin.js +155 -142
  51. package/dist/commands/local-args-tower-cal.js +54 -48
  52. package/dist/commands/local-args-tower-careers.js +73 -54
  53. package/dist/commands/local-args-tower-docs-msg.js +56 -188
  54. package/dist/commands/local-args-tower-github.js +26 -20
  55. package/dist/commands/local-args-tower-hits.js +32 -20
  56. package/dist/commands/local-args-tower-manager.js +85 -4
  57. package/dist/commands/local-args-tower-memory.js +139 -117
  58. package/dist/commands/local-args-tower-msg.js +149 -0
  59. package/dist/commands/local-args-tower-notes.js +346 -0
  60. package/dist/commands/local-args-tower-pages.js +100 -378
  61. package/dist/commands/local-args-tower-settings.js +28 -29
  62. package/dist/commands/local-args-tower-work.js +52 -33
  63. package/dist/commands/local-args.js +80 -129
  64. package/dist/commands/local-help-commands-tower-ops.js +15 -4
  65. package/dist/commands/local-help-commands-tower.js +11 -4
  66. package/dist/commands/local-help-commands.js +342 -341
  67. package/dist/commands/local-help.js +87 -83
  68. package/dist/commands/local-run-guards.js +100 -0
  69. package/dist/commands/local.js +137 -206
  70. package/dist/commands/login.js +13 -9
  71. package/dist/commands/logs.js +55 -38
  72. package/dist/commands/mail-attachments.js +44 -26
  73. package/dist/commands/mail.js +6 -4
  74. package/dist/commands/manager-render-home.js +208 -0
  75. package/dist/commands/manager-render.js +7 -3
  76. package/dist/commands/manager-requests.js +33 -0
  77. package/dist/commands/manager.js +39 -5
  78. package/dist/commands/memory-consolidation.js +18 -9
  79. package/dist/commands/memory-daemon-probe.js +37 -28
  80. package/dist/commands/memory-install-config.js +64 -60
  81. package/dist/commands/memory-install.js +21 -41
  82. package/dist/commands/memory-requeue.js +26 -19
  83. package/dist/commands/memory-rules.js +26 -18
  84. package/dist/commands/memory-search-print.js +26 -0
  85. package/dist/commands/memory-store.js +14 -30
  86. package/dist/commands/memory-verb-list.js +49 -48
  87. package/dist/commands/msg.js +51 -9
  88. package/dist/commands/notes-accounts.js +49 -32
  89. package/dist/commands/notes-sync-loop.js +115 -89
  90. package/dist/commands/notes-wisprflow-login.js +44 -23
  91. package/dist/commands/notes-writes.js +115 -100
  92. package/dist/commands/notes.js +33 -55
  93. package/dist/commands/onboard-flows.js +69 -51
  94. package/dist/commands/onboard.js +11 -9
  95. package/dist/commands/ops-render-memory.js +10 -7
  96. package/dist/commands/ops-render.js +33 -30
  97. package/dist/commands/ops.js +3 -0
  98. package/dist/commands/public-root.js +1 -1
  99. package/dist/commands/session-sync-outside-roots.js +40 -36
  100. package/dist/commands/session-sync-plan.js +14 -10
  101. package/dist/commands/session-sync-upload.js +24 -22
  102. package/dist/commands/session-sync.js +19 -8
  103. package/dist/commands/slack.js +25 -16
  104. package/dist/commands/sources-check.js +13 -7
  105. package/dist/commands/sources-discovery.js +25 -17
  106. package/dist/commands/sources-verdict.js +19 -18
  107. package/dist/commands/speed.js +23 -19
  108. package/dist/commands/start.js +20 -16
  109. package/dist/commands/sync-doctor.js +52 -48
  110. package/dist/commands/sync.js +22 -16
  111. package/dist/commands/usage-telemetry-toml.js +71 -45
  112. package/dist/commands/usage.js +91 -67
  113. package/dist/commands/workbook-render.js +10 -7
  114. package/dist/evidence-reconcile-client.js +75 -64
  115. package/dist/evidence-redelivery-plan.js +37 -34
  116. package/dist/evidence-redelivery.js +32 -29
  117. package/dist/evidence-transcripts-client.js +20 -20
  118. package/dist/evidence-upload-object.js +86 -89
  119. package/dist/evidence-upload-plan.js +94 -83
  120. package/dist/evidence-upload-terminal.js +45 -50
  121. package/dist/image-description-session.js +156 -159
  122. package/dist/local-state-pairing-code.js +39 -31
  123. package/dist/local-state-status.js +9 -2
  124. package/dist/onboarding-roots.js +5 -8
  125. package/dist/process-runner.js +8 -8
  126. package/dist/raw-evidence-gc.js +33 -38
  127. package/dist/raw-evidence-stuck-summary.js +7 -14
  128. package/dist/repo-identity.js +26 -23
  129. package/dist/scheduled-self-update.js +11 -15
  130. package/dist/second-install.js +3 -0
  131. package/dist/slim/codex-rules.js +23 -36
  132. package/dist/sync-lock.js +5 -15
  133. package/dist/tower-client.js +36 -53
  134. package/dist/tower-stream.js +7 -24
  135. package/dist/upload-agent-artifacts.js +58 -64
  136. package/dist/upload-envelope-build.js +60 -59
  137. package/dist/upload-evidence-delivery-reconcile.js +58 -56
  138. package/dist/upload-evidence-delivery-summary.js +32 -34
  139. package/dist/upload-ingest-receipt.js +20 -23
  140. package/dist/upload-session-reports-queue.js +4 -1
  141. package/dist/upload-session-reports-wire.js +64 -68
  142. package/dist/upload-sync.js +171 -157
  143. package/package.json +2 -2
@@ -37,27 +37,39 @@ export function parseHitsArgs(args) {
37
37
  }
38
38
  if (given("--weeks") && action !== "timeline")
39
39
  throw new Error("--weeks belongs to hits timeline.");
40
- if (action === "timeline") {
41
- if (given("--scope") || given("--version"))
42
- throw new Error("--scope and --version belong to hits approve.");
43
- const weeks = optionalPositiveInteger(values.flags.get("--weeks"), "--weeks");
44
- if (weeks !== undefined && weeks > 26)
45
- throw new Error("--weeks must be 26 or fewer.");
46
- return { ...common, action, ...(weeks !== undefined ? { weeks } : {}) };
47
- }
48
- if (action === "coach") {
49
- if (given("--scope") || given("--version"))
50
- throw new Error("--scope and --version belong to hits approve.");
51
- return { ...common, action };
52
- }
53
- if (action === "approve") {
54
- const scope = optionalNonEmpty(values.flags.get("--scope"));
55
- if (!scope || !SCOPE.test(scope))
56
- throw new Error("hits approve wants --scope team, agents or project:<id> (for example project:pearl-portal).");
57
- return { ...common, action, scope, ...versionOf(values.flags.get("--version")) };
58
- }
40
+ const parsed = { values, given, common };
41
+ if (action === "timeline")
42
+ return parseHitsTimeline(parsed);
43
+ if (action === "coach")
44
+ return parseHitsCoach(parsed);
45
+ if (action === "approve")
46
+ return parseHitsApprove(parsed);
47
+ return parseHitsRead(parsed);
48
+ }
49
+ function refuseApproveFlags({ given }) {
59
50
  if (given("--scope") || given("--version"))
60
51
  throw new Error("--scope and --version belong to hits approve.");
52
+ }
53
+ function parseHitsTimeline(parsed) {
54
+ refuseApproveFlags(parsed);
55
+ const weeks = optionalPositiveInteger(parsed.values.flags.get("--weeks"), "--weeks");
56
+ if (weeks !== undefined && weeks > 26)
57
+ throw new Error("--weeks must be 26 or fewer.");
58
+ return { ...parsed.common, action: "timeline", ...(weeks !== undefined ? { weeks } : {}) };
59
+ }
60
+ function parseHitsCoach(parsed) {
61
+ refuseApproveFlags(parsed);
62
+ return { ...parsed.common, action: "coach" };
63
+ }
64
+ function parseHitsApprove({ values, common }) {
65
+ const scope = optionalNonEmpty(values.flags.get("--scope"));
66
+ if (!scope || !SCOPE.test(scope))
67
+ throw new Error("hits approve wants --scope team, agents or project:<id> (for example project:pearl-portal).");
68
+ return { ...common, action: "approve", scope, ...versionOf(values.flags.get("--version")) };
69
+ }
70
+ function parseHitsRead(parsed) {
71
+ const { values, given, common } = parsed;
72
+ refuseApproveFlags(parsed);
61
73
  const whoFlags = ["--mine", "--person", "--agent", "--project"].filter(given);
62
74
  if (whoFlags.length > 1)
63
75
  throw new Error(`Pick one of --mine, --person, --agent or --project (got ${whoFlags.join(" and ")}).`);
@@ -77,7 +89,7 @@ export function parseHitsArgs(args) {
77
89
  throw new Error("--learning is read for one person: use --mine or --person <name>.");
78
90
  return {
79
91
  ...common,
80
- action,
92
+ action: "read",
81
93
  ...(who ? { who } : {}),
82
94
  ...(person ? { person } : {}),
83
95
  ...(project ? { project } : {}),
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * cockpit manager clients
5
5
  * cockpit manager client <client>
6
+ * cockpit manager home <client>
7
+ * cockpit manager summary <client> --version <v>|none [--health on_track|at_risk|blocked] [--headline <t>] [--bullet "YYYY-MM-DD: t"|"t"]... [--next <t>|none] [--blocker <t>|none]
6
8
  * cockpit manager run <run-id>
7
9
  * cockpit manager list <client> [--timeline] [--needs-review] [--status open|done|dropped|closed|all] [--owner <name> | --no-owner] [--overdue] [--kind <kind>] [--search <text>] [--archived] [--review dismissed|all]
8
10
  * cockpit manager queue <client>
@@ -22,27 +24,36 @@
22
24
  */
23
25
  import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
24
26
  export const MANAGER_VERBS = [
25
- "clients", "client", "run", "list", "queue", "show", "add", "edit", "done", "reopen", "link",
27
+ "clients", "client", "home", "summary", "run", "list", "queue", "show", "add", "edit", "done", "reopen", "link",
26
28
  "approve", "dismiss", "proposal", "archive", "unarchive",
27
29
  ];
28
30
  export const MANAGER_ACTION_STATUSES = ["open", "done", "dropped", "closed", "all"];
29
31
  export const MANAGER_TIMELINE_KINDS = ["meeting", "document", "milestone", "internal"];
32
+ export const MANAGER_SUMMARY_HEALTHS = ["on_track", "at_risk", "blocked"];
33
+ /** The verbs whose one positional word is a client (its id or its exact name). */
34
+ const CLIENT_VERBS = new Set(["client", "home", "summary", "list", "queue", "add"]);
30
35
  const ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
31
36
  const VERSION = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
32
37
  const ONE_VERB = `manager takes one verb: ${MANAGER_VERBS.join(", ")}. Example: \`cockpit manager clients\` or \`cockpit manager list <client>\`.`;
33
38
  const OWN_FLAGS = [
34
39
  "--timeline", "--needs-review", "--status", "--owner", "--owners", "--no-owner", "--kind", "--search", "--archived", "--review",
35
40
  "--title", "--detail", "--due", "--date", "--meeting", "--none", "--version", "--request-id", "--restore", "--overdue", "--source-url", "--source-note",
41
+ "--health", "--headline", "--bullet", "--next", "--blocker",
36
42
  ];
37
43
  const COMMON_FLAGS = ["--home", "--dashboard-url", "--json", "--help"];
38
44
  const VALUE_FLAGS = [
39
45
  "--status", "--owner", "--owners", "--kind", "--search", "--review", "--title", "--detail", "--due", "--date", "--meeting",
40
46
  "--version", "--request-id", "--source-url", "--source-note", "--home", "--dashboard-url",
47
+ "--health", "--headline", "--bullet", "--next", "--blocker",
41
48
  ];
49
+ /** `summary --bullet` is given once per bullet: the one value flag that may repeat. */
50
+ const REPEATABLE_FLAGS = new Set(["--bullet"]);
42
51
  /** Which flags each verb owns, so a flag on the wrong verb is refused by name rather than ignored. */
43
52
  export const FLAGS_BY_VERB = {
44
53
  clients: [],
45
54
  client: [],
55
+ home: [],
56
+ summary: ["--version", "--health", "--headline", "--bullet", "--next", "--blocker"],
46
57
  run: [],
47
58
  list: ["--timeline", "--needs-review", "--status", "--owner", "--no-owner", "--overdue", "--kind", "--search", "--archived", "--review"],
48
59
  queue: [],
@@ -62,6 +73,8 @@ export const FLAGS_BY_VERB = {
62
73
  const POSITIONALS = {
63
74
  clients: [],
64
75
  client: ["client"],
76
+ home: ["client"],
77
+ summary: ["client"],
65
78
  run: ["run id"],
66
79
  list: ["client"],
67
80
  queue: ["client"],
@@ -80,6 +93,8 @@ const POSITIONALS = {
80
93
  /** A value flag given twice would silently keep only the last; say so instead. */
81
94
  function refuseRepeatedFlags(args) {
82
95
  for (const flag of VALUE_FLAGS) {
96
+ if (REPEATABLE_FLAGS.has(flag))
97
+ continue;
83
98
  const times = args.filter((arg) => arg === flag || arg.startsWith(`${flag}=`)).length;
84
99
  if (times > 1) {
85
100
  throw new Error(flag === "--owners"
@@ -113,12 +128,12 @@ export function parseManagerArgs(args) {
113
128
  if (rest.length !== wanted.length) {
114
129
  throw new Error(wanted.length === 0
115
130
  ? `\`cockpit manager ${verb}\` takes no positional arguments.`
116
- : `\`cockpit manager ${verb}\` takes exactly: ${wanted.map((word) => `<${word}>`).join(" ")}${verb === "list" || verb === "queue" || verb === "add" || verb === "client" ? " (quote a client's name that has spaces)" : ""}.`);
131
+ : `\`cockpit manager ${verb}\` takes exactly: ${wanted.map((word) => `<${word}>`).join(" ")}${CLIENT_VERBS.has(verb) ? " (quote a client's name that has spaces)" : ""}.`);
117
132
  }
118
133
  const timeline = values.booleans.has("--timeline");
119
134
  const shape = { ...base, action: verb, timeline };
120
135
  const flag = (name) => values.flags.get(name);
121
- if (verb === "list" || verb === "queue" || verb === "add" || verb === "client")
136
+ if (CLIENT_VERBS.has(verb))
122
137
  shape.client = rest[0];
123
138
  else if (verb === "run")
124
139
  shape.runId = readId(rest[0], "A run");
@@ -128,9 +143,11 @@ export function parseManagerArgs(args) {
128
143
  shape.proposalId = rest[1];
129
144
  shape.decision = readDecision(rest[2]);
130
145
  }
131
- const needsVersion = !["clients", "client", "run", "list", "queue", "show", "add"].includes(verb);
146
+ const needsVersion = !["clients", "client", "home", "summary", "run", "list", "queue", "show", "add"].includes(verb);
132
147
  if (needsVersion)
133
148
  shape.version = readVersion(flag("--version"), verb);
149
+ if (verb === "summary")
150
+ readSummaryFields(values, shape);
134
151
  if (verb === "list")
135
152
  readListFilters(values, shape);
136
153
  if (verb === "add")
@@ -368,4 +385,68 @@ function readLink(values, shape) {
368
385
  if (!meeting && !none)
369
386
  throw new Error("`cockpit manager link` needs --meeting <timeline-id> to link, or --none to unlink.");
370
387
  shape.timelineItemId = meeting ? readId(meeting, "A timeline item") : null;
388
+ }
389
+ /** `summary`'s version: the card's `summary.updatedAt` as `home --json` gives it, or `none` when the client has no card yet. */
390
+ function readSummaryVersion(raw) {
391
+ if (raw === undefined) {
392
+ throw new Error("`cockpit manager summary` needs --version: the card's summary.updatedAt from `cockpit manager home <client> --json`, or none when the client has no card yet. It keeps you from writing over a newer card.");
393
+ }
394
+ if (isNone(raw))
395
+ return null;
396
+ if (!VERSION.test(raw))
397
+ throw new Error("--version is the card's summary.updatedAt exactly as `cockpit manager home <client> --json` gives it, like 2026-10-09T17:03:11.123456+00:00, or none.");
398
+ return raw;
399
+ }
400
+ /** `--bullet "2026-10-14: Signed the SOW"` is a dated bullet; without a leading `YYYY-MM-DD: ` it is undated. */
401
+ export function readBullet(raw) {
402
+ // A bullet pasted from a list keeps no "- " or "* " of its own.
403
+ const value = raw.trim().replace(/^[-*•]\s+/u, "");
404
+ const dated = /^(\d{4}-\d{2}-\d{2}):\s*(.*)$/s.exec(value);
405
+ const dateLike = dated ? null : /^(\d{1,4}[-/.]\d{1,2}[-/.]\d{1,4})\s*:/.exec(value);
406
+ if (dateLike)
407
+ throw new Error(`--bullet "${raw}" starts with ${dateLike[1]}, which is not a YYYY-MM-DD day. Write it like 2026-10-14, or leave the date out for an undated bullet.`);
408
+ const date = dated ? dated[1] : null;
409
+ const text = (dated ? dated[2] : value).trim();
410
+ if (date !== null && !isCalendarDate(date))
411
+ throw new Error(`--bullet "${raw}" starts with ${date}, which is not a real day. Write the date YYYY-MM-DD, like 2026-10-14, or leave it out for an undated bullet.`);
412
+ if (text === "")
413
+ throw new Error("--bullet needs words after its date. Example: --bullet \"2026-10-14: Signed the SOW\".");
414
+ return { date, text };
415
+ }
416
+ /** The card's fields. Counts, lengths and "blocked needs a blocker" are the server's rules, refused there by name. */
417
+ function readSummaryFields(values, shape) {
418
+ shape.version = readSummaryVersion(values.flags.get("--version"));
419
+ let changes = 0;
420
+ const health = values.flags.get("--health");
421
+ if (health !== undefined) {
422
+ if (!MANAGER_SUMMARY_HEALTHS.includes(health))
423
+ throw new Error(`--health wants one of ${MANAGER_SUMMARY_HEALTHS.join(", ")}.`);
424
+ shape.health = health;
425
+ changes += 1;
426
+ }
427
+ const headline = values.flags.get("--headline");
428
+ if (headline !== undefined) {
429
+ if (headline.trim() === "")
430
+ throw new Error("--headline cannot be blank.");
431
+ shape.headline = headline.trim();
432
+ changes += 1;
433
+ }
434
+ const bullets = values.flagValues.get("--bullet");
435
+ if (bullets !== undefined && bullets.length > 0) {
436
+ shape.bullets = bullets.map(readBullet);
437
+ changes += 1;
438
+ }
439
+ const next = values.flags.get("--next");
440
+ if (next !== undefined) {
441
+ shape.next = isNone(next) ? null : next.trim();
442
+ changes += 1;
443
+ }
444
+ const blocker = values.flags.get("--blocker");
445
+ if (blocker !== undefined) {
446
+ shape.blocker = isNone(blocker) ? null : blocker.trim();
447
+ changes += 1;
448
+ }
449
+ if (changes === 0) {
450
+ throw new Error("`cockpit manager summary` has nothing to change. Give at least one of --health, --headline, --bullet (1 to 5 of them, replacing the list), --next, --blocker. A flag you leave out keeps its value; none clears --next or --blocker.");
451
+ }
371
452
  }
@@ -119,6 +119,40 @@ export function parseMemoryStoreArgs(action, args) {
119
119
  const values = parseNamedArgs(args, { allowedFlags: ALLOWED_FLAGS, valueFlags: VALUE_FLAGS });
120
120
  const rest = values.positionals.slice(1);
121
121
  const containers = values.flagValues.get("--container") ?? [];
122
+ rejectFlagsThatDoNotBelongTo(action, values, containers, rest);
123
+ const command = {
124
+ kind: "memory-store",
125
+ homeDir: optionalNonEmpty(values.flags.get("--home")),
126
+ dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
127
+ workspace: optionalNonEmpty(workRootFlagValue(values)),
128
+ containerTags: containers
129
+ .map((tag) => tag.trim())
130
+ .filter((tag) => tag.length > 0),
131
+ json: values.booleans.has("--json"),
132
+ };
133
+ if (action === "requeue")
134
+ return parseRequeueVerb(command, values, rest);
135
+ if (action === "profile")
136
+ return parseProfileVerb(command, rest);
137
+ if (action === "search")
138
+ return parseSearchVerb(command, values, rest);
139
+ if (action === "consolidation")
140
+ return { ...command, action };
141
+ if (action === "exam")
142
+ return parseExamVerb(command, values, rest);
143
+ if (action === "source")
144
+ return parseSourceVerb(command, values, rest);
145
+ if (action === "private" || action === "public" || action === "chain") {
146
+ return parseOneMemoryIdVerb(command, action, rest);
147
+ }
148
+ if (action === "save")
149
+ return parseSaveVerb(command, values, rest);
150
+ if (action === "update")
151
+ return parseUpdateVerb(command, values, rest);
152
+ return parseForgetVerb(command, values, rest);
153
+ }
154
+ /** Every flag is legal to the parser; this says which verb each one belongs to. */
155
+ function rejectFlagsThatDoNotBelongTo(action, values, containers, rest) {
122
156
  if (action !== "search" && action !== "consolidation" && containers.length > 1) {
123
157
  throw new Error(`memory ${action} takes at most one --container.`);
124
158
  }
@@ -166,130 +200,118 @@ export function parseMemoryStoreArgs(action, args) {
166
200
  if (values.flags.has("--file") && action !== "save" && action !== "update") {
167
201
  throw new Error("--file only applies to memory save and memory update.");
168
202
  }
169
- const command = {
170
- kind: "memory-store",
171
- action,
172
- homeDir: optionalNonEmpty(values.flags.get("--home")),
173
- dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
174
- workspace: optionalNonEmpty(workRootFlagValue(values)),
175
- containerTags: containers
176
- .map((tag) => tag.trim())
177
- .filter((tag) => tag.length > 0),
178
- json: values.booleans.has("--json"),
179
- };
180
- if (action === "requeue") {
181
- // A window is REQUIRED. There is no requeue-everything: applying re-runs
182
- // one model call per receipt, so the ceiling on a wrong filter is a bill.
183
- if (rest.length > 0) {
184
- throw new Error("memory requeue takes no words; name the window with --since and --until.");
185
- }
186
- const since = readInstant(values.flags.get("--since"), "--since");
187
- if (!since) {
188
- throw new Error("memory requeue needs --since <iso>, e.g. `cockpit memory requeue --since 2026-09-21T04:10:00Z --dry-run`.");
189
- }
190
- const until = readInstant(values.flags.get("--until"), "--until");
191
- if (until && until < since)
192
- throw new Error("--until is before --since; nothing could match.");
193
- const source = optionalNonEmpty(values.flags.get("--source"));
194
- if (source && source !== "hook" && source !== "harvest") {
195
- throw new Error("--source is hook or harvest — the two values a receipt can carry.");
196
- }
197
- if (values.booleans.has("--dry-run") && values.booleans.has("--apply")) {
198
- throw new Error("--dry-run and --apply are opposites; name one.");
199
- }
200
- return {
201
- ...command,
202
- action,
203
- since,
204
- until,
205
- ...(source ? { source: source } : {}),
206
- // The safe question unless somebody typed otherwise.
207
- dryRun: !values.booleans.has("--apply"),
208
- reason: optionalNonEmpty(values.flags.get("--reason")),
209
- };
210
- }
211
- if (action === "profile") {
212
- // A pure read of ONE container's compiled page. No positional: there is
213
- // nothing to ask it, and a stray word would look like a query this verb
214
- // silently ignored.
215
- if (rest.length > 0) {
216
- throw new Error("memory profile takes no words; it reads this repository's compiled page. Name another space with --container.");
217
- }
218
- return { ...command, action };
203
+ }
204
+ function parseRequeueVerb(command, values, rest) {
205
+ // A window is REQUIRED. There is no requeue-everything: applying re-runs
206
+ // one model call per receipt, so the ceiling on a wrong filter is a bill.
207
+ if (rest.length > 0) {
208
+ throw new Error("memory requeue takes no words; name the window with --since and --until.");
219
209
  }
220
- if (action === "search") {
221
- // Every remaining positional joined, so `cockpit memory search what did we
222
- // decide about pagination` needs no quotes — the same shape `cockpit
223
- // search` and `cockpit cal find` already have.
224
- const query = rest.join(" ").trim();
225
- if (!query)
226
- throw new Error("memory search needs a question, e.g. `cockpit memory search \"pagination rule\"`.");
227
- // BLI-19246: both flags absent leaves the choice to the door (exact-detail questions reach the originals).
228
- const includeOriginals = values.booleans.has("--include-originals") ? true : values.booleans.has("--no-originals") ? false : undefined;
229
- // BLI-19255: the raw source behind the top hits; absent lets the door decide by the question.
230
- const includeSource = values.booleans.has("--include-source") ? true : values.booleans.has("--no-source") ? false : undefined;
231
- return {
232
- ...command, action, query, limit: readLimit(values.flags.get("--limit")), includeRecent: !values.booleans.has("--no-recent"),
233
- ...(includeOriginals === undefined ? {} : { includeOriginals }),
234
- ...(includeSource === undefined ? {} : { includeSource }),
235
- };
210
+ const since = readInstant(values.flags.get("--since"), "--since");
211
+ if (!since) {
212
+ throw new Error("memory requeue needs --since <iso>, e.g. `cockpit memory requeue --since 2026-09-21T04:10:00Z --dry-run`.");
236
213
  }
237
- if (action === "consolidation") {
238
- return { ...command, action };
214
+ const until = readInstant(values.flags.get("--until"), "--until");
215
+ if (until && until < since)
216
+ throw new Error("--until is before --since; nothing could match.");
217
+ const source = optionalNonEmpty(values.flags.get("--source"));
218
+ if (source && source !== "hook" && source !== "harvest") {
219
+ throw new Error("--source is hook or harvest — the two values a receipt can carry.");
239
220
  }
240
- if (action === "exam") {
241
- if (rest.length > 0)
242
- throw new Error("memory exam takes no words: `cockpit memory exam [--night <YYYY-MM-DD>] [--misses] [--json]`.");
243
- const night = optionalNonEmpty(values.flags.get("--night"));
244
- if (night && !/^\d{4}-\d{2}-\d{2}$/.test(night))
245
- throw new Error("--night must be a date like 2026-10-14.");
246
- return { ...command, action, ...(night ? { night } : {}), misses: values.booleans.has("--misses") };
221
+ if (values.booleans.has("--dry-run") && values.booleans.has("--apply")) {
222
+ throw new Error("--dry-run and --apply are opposites; name one.");
247
223
  }
248
- if (action === "source") {
249
- // BLI-19255: one memory, named by the id `cockpit memory search` printed; `--query` steers which part of the source.
250
- const memoryId = optionalNonEmpty(rest[0]);
251
- if (!memoryId)
252
- throw new Error("memory source needs the memory id `cockpit memory search` printed.");
253
- if (rest.length > 1)
254
- throw new Error("memory source takes one id.");
255
- const rawChars = values.flags.get("--chars");
256
- const chars = rawChars === undefined ? undefined : Number(rawChars);
257
- if (chars !== undefined && (!Number.isInteger(chars) || chars < 200 || chars > 6000))
258
- throw new Error("--chars is a whole number from 200 to 6000.");
259
- return { ...command, action, memoryId, ...(values.flags.get("--query") ? { sourceQuery: values.flags.get("--query") } : {}), ...(chars === undefined ? {} : { sourceChars: chars }) };
224
+ return {
225
+ ...command,
226
+ action: "requeue",
227
+ since,
228
+ until,
229
+ ...(source ? { source: source } : {}),
230
+ // The safe question unless somebody typed otherwise.
231
+ dryRun: !values.booleans.has("--apply"),
232
+ reason: optionalNonEmpty(values.flags.get("--reason")),
233
+ };
234
+ }
235
+ function parseProfileVerb(command, rest) {
236
+ // A pure read of ONE container's compiled page. No positional: there is
237
+ // nothing to ask it, and a stray word would look like a query this verb
238
+ // silently ignored.
239
+ if (rest.length > 0) {
240
+ throw new Error("memory profile takes no words; it reads this repository's compiled page. Name another space with --container.");
260
241
  }
261
- if (action === "private" || action === "public" || action === "chain") {
262
- // BLI-12927: one memory, named by the id `cockpit memory search` printed.
263
- const memoryId = optionalNonEmpty(rest[0]);
264
- if (!memoryId) {
265
- throw new Error(`memory ${action} needs the memory id \`cockpit memory search\` printed.`);
266
- }
267
- if (rest.length > 1)
268
- throw new Error(`memory ${action} takes one id.`);
269
- return { ...command, action, memoryId };
242
+ return { ...command, action: "profile" };
243
+ }
244
+ function parseSearchVerb(command, values, rest) {
245
+ // Every remaining positional joined, so `cockpit memory search what did we
246
+ // decide about pagination` needs no quotes — the same shape `cockpit
247
+ // search` and `cockpit cal find` already have.
248
+ const query = rest.join(" ").trim();
249
+ if (!query)
250
+ throw new Error("memory search needs a question, e.g. `cockpit memory search \"pagination rule\"`.");
251
+ // BLI-19246: both flags absent leaves the choice to the door (exact-detail questions reach the originals).
252
+ const includeOriginals = values.booleans.has("--include-originals") ? true : values.booleans.has("--no-originals") ? false : undefined;
253
+ // BLI-19255: the raw source behind the top hits; absent lets the door decide by the question.
254
+ const includeSource = values.booleans.has("--include-source") ? true : values.booleans.has("--no-source") ? false : undefined;
255
+ return {
256
+ ...command, action: "search", query, limit: readLimit(values.flags.get("--limit")), includeRecent: !values.booleans.has("--no-recent"),
257
+ ...(includeOriginals === undefined ? {} : { includeOriginals }),
258
+ ...(includeSource === undefined ? {} : { includeSource }),
259
+ };
260
+ }
261
+ function parseExamVerb(command, values, rest) {
262
+ if (rest.length > 0)
263
+ throw new Error("memory exam takes no words: `cockpit memory exam [--night <YYYY-MM-DD>] [--misses] [--json]`.");
264
+ const night = optionalNonEmpty(values.flags.get("--night"));
265
+ if (night && !/^\d{4}-\d{2}-\d{2}$/.test(night))
266
+ throw new Error("--night must be a date like 2026-10-14.");
267
+ return { ...command, action: "exam", ...(night ? { night } : {}), misses: values.booleans.has("--misses") };
268
+ }
269
+ function parseSourceVerb(command, values, rest) {
270
+ // BLI-19255: one memory, named by the id `cockpit memory search` printed; `--query` steers which part of the source.
271
+ const memoryId = optionalNonEmpty(rest[0]);
272
+ if (!memoryId)
273
+ throw new Error("memory source needs the memory id `cockpit memory search` printed.");
274
+ if (rest.length > 1)
275
+ throw new Error("memory source takes one id.");
276
+ const rawChars = values.flags.get("--chars");
277
+ const chars = rawChars === undefined ? undefined : Number(rawChars);
278
+ if (chars !== undefined && (!Number.isInteger(chars) || chars < 200 || chars > 6000))
279
+ throw new Error("--chars is a whole number from 200 to 6000.");
280
+ return { ...command, action: "source", memoryId, ...(values.flags.get("--query") ? { sourceQuery: values.flags.get("--query") } : {}), ...(chars === undefined ? {} : { sourceChars: chars }) };
281
+ }
282
+ function parseOneMemoryIdVerb(command, action, rest) {
283
+ // BLI-12927: one memory, named by the id `cockpit memory search` printed.
284
+ const memoryId = optionalNonEmpty(rest[0]);
285
+ if (!memoryId) {
286
+ throw new Error(`memory ${action} needs the memory id \`cockpit memory search\` printed.`);
270
287
  }
271
- if (action === "save") {
272
- if (rest.length > 0) {
273
- throw new Error("memory save reads the memory from stdin or --file, never from the command line: `echo \"...\" | cockpit memory save`.");
274
- }
275
- return {
276
- ...command,
277
- action,
278
- customId: optionalNonEmpty(values.flags.get("--custom-id")),
279
- ...(values.flagValues.has("--alias") ? { aliases: values.flagValues.get("--alias") } : {}),
280
- extract: values.booleans.has("--extract"),
281
- filePath: optionalNonEmpty(values.flags.get("--file")),
282
- };
288
+ if (rest.length > 1)
289
+ throw new Error(`memory ${action} takes one id.`);
290
+ return { ...command, action, memoryId };
291
+ }
292
+ function parseSaveVerb(command, values, rest) {
293
+ if (rest.length > 0) {
294
+ throw new Error("memory save reads the memory from stdin or --file, never from the command line: `echo \"...\" | cockpit memory save`.");
283
295
  }
284
- if (action === "update") {
285
- const memoryId = optionalNonEmpty(rest[0]);
286
- if (!memoryId)
287
- throw new Error("memory update needs the memory id `cockpit memory search` printed.");
288
- if (rest.length > 1) {
289
- throw new Error("memory update takes one id; the replacement text comes from stdin or --file, never from the command line.");
290
- }
291
- return { ...command, action, memoryId, filePath: optionalNonEmpty(values.flags.get("--file")) };
296
+ return {
297
+ ...command,
298
+ action: "save",
299
+ customId: optionalNonEmpty(values.flags.get("--custom-id")),
300
+ ...(values.flagValues.has("--alias") ? { aliases: values.flagValues.get("--alias") } : {}),
301
+ extract: values.booleans.has("--extract"),
302
+ filePath: optionalNonEmpty(values.flags.get("--file")),
303
+ };
304
+ }
305
+ function parseUpdateVerb(command, values, rest) {
306
+ const memoryId = optionalNonEmpty(rest[0]);
307
+ if (!memoryId)
308
+ throw new Error("memory update needs the memory id `cockpit memory search` printed.");
309
+ if (rest.length > 1) {
310
+ throw new Error("memory update takes one id; the replacement text comes from stdin or --file, never from the command line.");
292
311
  }
312
+ return { ...command, action: "update", memoryId, filePath: optionalNonEmpty(values.flags.get("--file")) };
313
+ }
314
+ function parseForgetVerb(command, values, rest) {
293
315
  const memoryId = optionalNonEmpty(rest[0]);
294
316
  if (rest.length > 1)
295
317
  throw new Error("memory forget takes one id, or --content-stdin with a --container.");
@@ -302,7 +324,7 @@ export function parseMemoryStoreArgs(action, args) {
302
324
  }
303
325
  return {
304
326
  ...command,
305
- action,
327
+ action: "forget",
306
328
  memoryId,
307
329
  contentStdin,
308
330
  reason: optionalNonEmpty(values.flags.get("--reason")),