@bli-cockpit/cli 0.2.237 → 0.2.239

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 (148) 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-direct.js +119 -0
  29. package/dist/commands/cal-overlay.js +73 -30
  30. package/dist/commands/cal.js +62 -25
  31. package/dist/commands/clean.js +4 -1
  32. package/dist/commands/docs-read.js +5 -2
  33. package/dist/commands/docs.js +38 -6
  34. package/dist/commands/doctor-backfill.js +88 -71
  35. package/dist/commands/doctor-pipeline.js +59 -46
  36. package/dist/commands/heartbeat-post.js +116 -0
  37. package/dist/commands/heartbeat-token-lock.js +46 -33
  38. package/dist/commands/heartbeat.js +43 -125
  39. package/dist/commands/install-update.js +17 -15
  40. package/dist/commands/issue-show.js +27 -28
  41. package/dist/commands/issue-versions.js +93 -0
  42. package/dist/commands/issue-write.js +70 -23
  43. package/dist/commands/issue.js +73 -37
  44. package/dist/commands/jarvis-live-answer.js +99 -0
  45. package/dist/commands/jarvis-render.js +1 -94
  46. package/dist/commands/jarvis-trace.js +30 -22
  47. package/dist/commands/jarvis-turn-report.js +124 -0
  48. package/dist/commands/jarvis-turn.js +108 -182
  49. package/dist/commands/jarvis.js +26 -17
  50. package/dist/commands/local-args-collector-setup.js +82 -84
  51. package/dist/commands/local-args-collector-work.js +37 -38
  52. package/dist/commands/local-args-tower-admin.js +155 -142
  53. package/dist/commands/local-args-tower-cal.js +69 -54
  54. package/dist/commands/local-args-tower-careers.js +73 -54
  55. package/dist/commands/local-args-tower-docs-msg.js +79 -51
  56. package/dist/commands/local-args-tower-github.js +26 -20
  57. package/dist/commands/local-args-tower-hits.js +32 -20
  58. package/dist/commands/local-args-tower-manager.js +85 -4
  59. package/dist/commands/local-args-tower-memory.js +139 -117
  60. package/dist/commands/local-args-tower-msg.js +24 -6
  61. package/dist/commands/local-args-tower-notes.js +346 -0
  62. package/dist/commands/local-args-tower-pages.js +100 -378
  63. package/dist/commands/local-args-tower-settings.js +28 -29
  64. package/dist/commands/local-args-tower-setup.js +11 -4
  65. package/dist/commands/local-args-tower-work.js +74 -37
  66. package/dist/commands/local-args.js +80 -129
  67. package/dist/commands/local-help-commands-tower-ops.js +15 -4
  68. package/dist/commands/local-help-commands-tower.js +20 -7
  69. package/dist/commands/local-help-commands.js +342 -341
  70. package/dist/commands/local-help.js +87 -83
  71. package/dist/commands/local-run-guards.js +100 -0
  72. package/dist/commands/local.js +137 -206
  73. package/dist/commands/login.js +13 -9
  74. package/dist/commands/logs.js +55 -38
  75. package/dist/commands/mail-attachments.js +44 -26
  76. package/dist/commands/manager-render-home.js +208 -0
  77. package/dist/commands/manager-render.js +7 -3
  78. package/dist/commands/manager-requests.js +33 -0
  79. package/dist/commands/manager.js +39 -5
  80. package/dist/commands/memory-consolidation.js +18 -9
  81. package/dist/commands/memory-daemon-probe.js +37 -28
  82. package/dist/commands/memory-install-config.js +64 -60
  83. package/dist/commands/memory-install.js +21 -41
  84. package/dist/commands/memory-requeue.js +26 -19
  85. package/dist/commands/memory-rules.js +26 -18
  86. package/dist/commands/memory-search-print.js +26 -0
  87. package/dist/commands/memory-store.js +14 -30
  88. package/dist/commands/memory-verb-list.js +49 -48
  89. package/dist/commands/msg-read-line.js +32 -0
  90. package/dist/commands/msg.js +45 -3
  91. package/dist/commands/notes-accounts.js +49 -32
  92. package/dist/commands/notes-sync-loop.js +115 -89
  93. package/dist/commands/notes-wisprflow-login.js +44 -23
  94. package/dist/commands/notes-writes.js +115 -100
  95. package/dist/commands/notes.js +33 -55
  96. package/dist/commands/onboard-flows.js +69 -51
  97. package/dist/commands/onboard.js +11 -9
  98. package/dist/commands/ops-render-memory.js +10 -7
  99. package/dist/commands/ops-render.js +33 -30
  100. package/dist/commands/ops.js +3 -0
  101. package/dist/commands/public-root.js +1 -1
  102. package/dist/commands/session-sync-outside-roots.js +40 -36
  103. package/dist/commands/session-sync-plan.js +14 -10
  104. package/dist/commands/session-sync-upload.js +24 -22
  105. package/dist/commands/session-sync.js +19 -8
  106. package/dist/commands/setup.js +13 -0
  107. package/dist/commands/slack.js +25 -16
  108. package/dist/commands/sources-check.js +13 -7
  109. package/dist/commands/sources-discovery.js +25 -17
  110. package/dist/commands/sources-verdict.js +19 -18
  111. package/dist/commands/speed.js +23 -19
  112. package/dist/commands/start.js +20 -16
  113. package/dist/commands/sync-doctor.js +52 -48
  114. package/dist/commands/sync.js +22 -16
  115. package/dist/commands/usage-telemetry-toml.js +71 -45
  116. package/dist/commands/usage.js +94 -68
  117. package/dist/commands/workbook-render.js +10 -7
  118. package/dist/evidence-reconcile-client.js +75 -64
  119. package/dist/evidence-redelivery-plan.js +37 -34
  120. package/dist/evidence-redelivery.js +32 -29
  121. package/dist/evidence-transcripts-client.js +20 -20
  122. package/dist/evidence-upload-object.js +86 -89
  123. package/dist/evidence-upload-plan.js +94 -83
  124. package/dist/evidence-upload-terminal.js +45 -50
  125. package/dist/image-description-session.js +156 -159
  126. package/dist/local-state-pairing-code.js +39 -31
  127. package/dist/local-state-status.js +9 -2
  128. package/dist/onboarding-roots.js +5 -8
  129. package/dist/process-runner.js +8 -8
  130. package/dist/raw-evidence-gc.js +33 -38
  131. package/dist/raw-evidence-stuck-summary.js +7 -14
  132. package/dist/repo-identity.js +26 -23
  133. package/dist/scheduled-self-update.js +11 -15
  134. package/dist/second-install.js +3 -0
  135. package/dist/slim/codex-rules.js +23 -36
  136. package/dist/spool/local-spool-parse.js +13 -12
  137. package/dist/sync-lock.js +5 -15
  138. package/dist/tower-client.js +36 -53
  139. package/dist/tower-stream.js +7 -24
  140. package/dist/upload-agent-artifacts.js +58 -64
  141. package/dist/upload-envelope-build.js +60 -59
  142. package/dist/upload-evidence-delivery-reconcile.js +58 -56
  143. package/dist/upload-evidence-delivery-summary.js +32 -34
  144. package/dist/upload-ingest-receipt.js +20 -23
  145. package/dist/upload-session-reports-queue.js +4 -1
  146. package/dist/upload-session-reports-wire.js +64 -68
  147. package/dist/upload-sync.js +171 -157
  148. package/package.json +4 -4
@@ -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")),
@@ -1,6 +1,6 @@
1
1
  import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
2
- const MSG_ACTIONS = new Set(["channels", "read", "send", "thread", "create", "dm", "show"]);
3
- const MSG_ACTIONS_NEEDING_A_CHANNEL = new Set(["read", "send"]);
2
+ const MSG_ACTIONS = new Set(["channels", "read", "send", "thread", "create", "dm", "show", "unread"]);
3
+ const MSG_ACTIONS_NEEDING_A_CHANNEL = new Set(["read", "send", "unread"]);
4
4
  /**
5
5
  * `--members a@x.test,b@y.test` — addresses only, split on commas, never
6
6
  * fuzzy (BLI-3749). The door resolves each one exactly, so a typo comes back
@@ -35,24 +35,40 @@ export function parseMsgArgs(args) {
35
35
  "--private",
36
36
  "--members",
37
37
  "--description",
38
+ "--from",
39
+ "--mark-read",
38
40
  "--json",
39
41
  ],
40
- valueFlags: ["--home", "--dashboard-url", "--channel", "--thread", "--limit", "--members", "--description", "--before"],
42
+ valueFlags: ["--home", "--dashboard-url", "--channel", "--thread", "--limit", "--members", "--description", "--before", "--from"],
41
43
  });
42
44
  const { action, rest } = parseMsgAction(values.positionals);
43
45
  if (values.flags.has("--before") && action !== "read" && action !== "thread") {
44
46
  throw new Error("--before belongs to msg read or msg thread.");
45
47
  }
48
+ const { markRead, unreadFrom } = parseMsgReadLineFlags(action, values);
46
49
  const { channelName, dmEmail } = parseMsgCreateTarget(action, rest);
47
50
  const { isPrivate, memberEmails, description } = parseMsgCreateFlags(action, values);
48
51
  const { channelRef, threadId, limit } = parseMsgReadTarget(action, rest, values);
49
- return buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit);
52
+ return buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit, markRead, unreadFrom);
53
+ }
54
+ /** `read --mark-read` and `unread --from <message-id>` (BLI-21299): the read line, moved on purpose. */
55
+ function parseMsgReadLineFlags(action, values) {
56
+ const markRead = values.booleans.has("--mark-read");
57
+ if (markRead && action !== "read")
58
+ throw new Error("--mark-read belongs to `cockpit msg read`.");
59
+ const unreadFrom = optionalNonEmpty(values.flags.get("--from"));
60
+ if (action === "unread" && !unreadFrom) {
61
+ throw new Error("msg unread needs --from <message-id>, e.g. `cockpit msg unread general --from <message-id>`.");
62
+ }
63
+ if (unreadFrom && action !== "unread")
64
+ throw new Error("--from belongs to `cockpit msg unread`.");
65
+ return { markRead, unreadFrom };
50
66
  }
51
67
  function parseMsgAction(positionals) {
52
68
  const first = positionals[0];
53
69
  const action = (first === undefined ? "channels" : first);
54
70
  if (!MSG_ACTIONS.has(action)) {
55
- throw new Error(`Unknown msg command: ${first}. Try channels, read, send, thread, create, dm, or show.`);
71
+ throw new Error(`Unknown msg command: ${first}. Try channels, read, unread, send, thread, create, dm, or show.`);
56
72
  }
57
73
  return { action, rest: positionals.slice(first === undefined ? 0 : 1) };
58
74
  }
@@ -129,7 +145,7 @@ function parseMsgReadTarget(action, rest, values) {
129
145
  }
130
146
  return { channelRef, threadId, limit };
131
147
  }
132
- function buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit) {
148
+ function buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit, markRead, unreadFrom) {
133
149
  return {
134
150
  kind: "msg",
135
151
  action,
@@ -144,6 +160,8 @@ function buildMsgCommand(values, action, channelRef, channelName, dmEmail, membe
144
160
  isPrivate,
145
161
  ...(threadId ? { threadId } : {}),
146
162
  ...(limit === undefined ? {} : { limit }),
163
+ ...(markRead ? { markRead } : {}),
164
+ ...(unreadFrom ? { unreadFrom } : {}),
147
165
  json: values.booleans.has("--json"),
148
166
  };
149
167
  }