@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
@@ -65,6 +65,27 @@ export function parseScoutArgs(args) {
65
65
  }
66
66
  return { kind: "scout", action: rawAction, experimentRef, ...base };
67
67
  }
68
+ const OPS_ALLOWED_FLAGS = [
69
+ "--home",
70
+ "--dashboard-url",
71
+ "--job",
72
+ "--skips",
73
+ "--memory",
74
+ "--memory-days",
75
+ // BLI-3798: the coverage half on its own, with the bucket table.
76
+ "--coverage",
77
+ // BLI-3912: one line per model — latency, tokens/s, today's spend, last
78
+ // probe verdict. A SECOND door like --memory, asked for only when named.
79
+ "--models",
80
+ "--turns",
81
+ "--tool-router",
82
+ // BLI-12507: the MCP's `people: true`. Per-device and per-person rows in --json.
83
+ "--people",
84
+ "--person",
85
+ "--dry-run",
86
+ "--json",
87
+ ];
88
+ const OPS_VALUE_FLAGS = ["--home", "--dashboard-url", "--job", "--person", "--memory-days"];
68
89
  /**
69
90
  * `cockpit ops status [--job <id>] [--skips] [--memory [--memory-days N]]` and
70
91
  * `cockpit ops recompile --person <p> [--dry-run]` (BLI-3462).
@@ -76,29 +97,7 @@ export function parseScoutArgs(args) {
76
97
  * weekly or a monthly with the compile script, which does support it.
77
98
  */
78
99
  export function parseOpsArgs(args) {
79
- const values = parseNamedArgs(args, {
80
- allowedFlags: [
81
- "--home",
82
- "--dashboard-url",
83
- "--job",
84
- "--skips",
85
- "--memory",
86
- "--memory-days",
87
- // BLI-3798: the coverage half on its own, with the bucket table.
88
- "--coverage",
89
- // BLI-3912: one line per model — latency, tokens/s, today's spend, last
90
- // probe verdict. A SECOND door like --memory, asked for only when named.
91
- "--models",
92
- "--turns",
93
- "--tool-router",
94
- // BLI-12507: the MCP's `people: true`. Per-device and per-person rows in --json.
95
- "--people",
96
- "--person",
97
- "--dry-run",
98
- "--json",
99
- ],
100
- valueFlags: ["--home", "--dashboard-url", "--job", "--person", "--memory-days"],
101
- });
100
+ const values = parseNamedArgs(args, { allowedFlags: OPS_ALLOWED_FLAGS, valueFlags: OPS_VALUE_FLAGS });
102
101
  if (values.positionals.length > 1) {
103
102
  throw new Error("ops accepts one action: status or recompile.");
104
103
  }
@@ -111,36 +110,39 @@ export function parseOpsArgs(args) {
111
110
  dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
112
111
  json: values.booleans.has("--json"),
113
112
  };
114
- if (rawAction === "status") {
115
- // BLI-3729: `--memory-days N` implies `--memory`, because asking for a
116
- // window and getting nothing back is the silent breakage BLI-2490 forbids.
117
- const memoryDays = optionalNonEmpty(values.flags.get("--memory-days"));
118
- if (memoryDays !== undefined && !/^[0-9]{1,2}$/u.test(memoryDays)) {
119
- throw new Error("ops --memory-days takes a whole number of days, 1 to 30.");
120
- }
121
- const coverageOnly = values.booleans.has("--coverage");
122
- const job = optionalNonEmpty(values.flags.get("--job"));
123
- if (coverageOnly && job !== undefined && job !== "collection-coverage") {
124
- // Refused rather than silently preferring one: `--coverage --job
125
- // daily-brief` is somebody asking two different questions in one
126
- // command, and answering the wrong one is how a board loses trust.
127
- throw new Error("ops --coverage IS --job collection-coverage. Pass one or the other, not a --job naming a different pipeline.");
128
- }
129
- return {
130
- kind: "ops",
131
- action: "status",
132
- job: coverageOnly ? "collection-coverage" : job,
133
- coverage: coverageOnly,
134
- skips: values.booleans.has("--skips"),
135
- memory: values.booleans.has("--memory") || memoryDays !== undefined,
136
- models: values.booleans.has("--models"),
137
- turns: values.booleans.has("--turns"),
138
- toolRouter: values.booleans.has("--tool-router"),
139
- people: values.booleans.has("--people"),
140
- ...(memoryDays === undefined ? {} : { memoryDays: Number(memoryDays) }),
141
- ...base,
142
- };
113
+ return rawAction === "status" ? parseOpsStatus(values, base) : parseOpsRecompile(values, base);
114
+ }
115
+ function parseOpsStatus(values, base) {
116
+ // BLI-3729: `--memory-days N` implies `--memory`, because asking for a
117
+ // window and getting nothing back is the silent breakage BLI-2490 forbids.
118
+ const memoryDays = optionalNonEmpty(values.flags.get("--memory-days"));
119
+ if (memoryDays !== undefined && !/^[0-9]{1,2}$/u.test(memoryDays)) {
120
+ throw new Error("ops --memory-days takes a whole number of days, 1 to 30.");
121
+ }
122
+ const coverageOnly = values.booleans.has("--coverage");
123
+ const job = optionalNonEmpty(values.flags.get("--job"));
124
+ if (coverageOnly && job !== undefined && job !== "collection-coverage") {
125
+ // Refused rather than silently preferring one: `--coverage --job
126
+ // daily-brief` is somebody asking two different questions in one
127
+ // command, and answering the wrong one is how a board loses trust.
128
+ throw new Error("ops --coverage IS --job collection-coverage. Pass one or the other, not a --job naming a different pipeline.");
143
129
  }
130
+ return {
131
+ kind: "ops",
132
+ action: "status",
133
+ job: coverageOnly ? "collection-coverage" : job,
134
+ coverage: coverageOnly,
135
+ skips: values.booleans.has("--skips"),
136
+ memory: values.booleans.has("--memory") || memoryDays !== undefined,
137
+ models: values.booleans.has("--models"),
138
+ turns: values.booleans.has("--turns"),
139
+ toolRouter: values.booleans.has("--tool-router"),
140
+ people: values.booleans.has("--people"),
141
+ ...(memoryDays === undefined ? {} : { memoryDays: Number(memoryDays) }),
142
+ ...base,
143
+ };
144
+ }
145
+ function parseOpsRecompile(values, base) {
144
146
  const person = optionalNonEmpty(values.flags.get("--person"));
145
147
  if (!person) {
146
148
  throw new Error("ops recompile needs --person <email|name|id>. Nothing is recompiled by default, on purpose.");
@@ -161,37 +163,36 @@ export const SLACK_WORKSPACE_KEYS = ["bli", "blue_pearl"];
161
163
  * (BLI-3462).
162
164
  */
163
165
  const SLACK_READ_FORMATS = ["compact", "full"];
166
+ const SLACK_ALLOWED_FLAGS = [
167
+ "--home",
168
+ "--dashboard-url",
169
+ "--workspace",
170
+ "--stale-only",
171
+ "--list",
172
+ "--person",
173
+ "--channel",
174
+ "--query",
175
+ "--word",
176
+ "--since",
177
+ "--until",
178
+ "--limit",
179
+ "--format",
180
+ "--json",
181
+ ];
182
+ const SLACK_VALUE_FLAGS = [
183
+ "--home",
184
+ "--dashboard-url",
185
+ "--workspace",
186
+ "--person",
187
+ "--channel",
188
+ "--query",
189
+ "--since",
190
+ "--until",
191
+ "--limit",
192
+ "--format",
193
+ ];
164
194
  export function parseSlackArgs(args) {
165
- const values = parseNamedArgs(args, {
166
- allowedFlags: [
167
- "--home",
168
- "--dashboard-url",
169
- "--workspace",
170
- "--stale-only",
171
- "--list",
172
- "--person",
173
- "--channel",
174
- "--query",
175
- "--word",
176
- "--since",
177
- "--until",
178
- "--limit",
179
- "--format",
180
- "--json",
181
- ],
182
- valueFlags: [
183
- "--home",
184
- "--dashboard-url",
185
- "--workspace",
186
- "--person",
187
- "--channel",
188
- "--query",
189
- "--since",
190
- "--until",
191
- "--limit",
192
- "--format",
193
- ],
194
- });
195
+ const values = parseNamedArgs(args, { allowedFlags: SLACK_ALLOWED_FLAGS, valueFlags: SLACK_VALUE_FLAGS });
195
196
  if (values.positionals.length > 1) {
196
197
  throw new Error("slack accepts one action: coverage, read, connect or disconnect.");
197
198
  }
@@ -210,32 +211,39 @@ export function parseSlackArgs(args) {
210
211
  if (values.booleans.has("--list") && rawAction !== "coverage") {
211
212
  throw new Error("--list belongs to `cockpit slack coverage`.");
212
213
  }
213
- if (rawAction === "connect" || rawAction === "disconnect") {
214
- const workspace = optionalNonEmpty(values.flags.get("--workspace"));
215
- if (workspace && !SLACK_WORKSPACE_KEYS.includes(workspace))
216
- throw new Error("Unknown Slack workspace.");
217
- if (rawAction === "disconnect" && !workspace)
218
- throw new Error("slack disconnect needs --workspace bli|blue_pearl.");
219
- return { kind: "slack", action: rawAction, workspace, ...base };
220
- }
221
- if (rawAction === "coverage") {
222
- // `--workspace` here is a Slack workspace key, NOT the collection-root
223
- // `--workspace <path>` every setup command takes. Named at the point of use
224
- // so the two cannot be confused silently.
225
- const workspace = optionalNonEmpty(values.flags.get("--workspace"));
226
- if (workspace && !SLACK_WORKSPACE_KEYS.includes(workspace)) {
227
- throw new Error(`slack --workspace must be one of ${SLACK_WORKSPACE_KEYS.join(", ")}; got ${workspace}.`);
228
- }
229
- return {
230
- kind: "slack",
231
- action: "coverage",
232
- workspace,
233
- staleOnly: values.booleans.has("--stale-only"),
234
- // BLI-10450: counts alone could not answer "is there a Kaiben channel?".
235
- list: values.booleans.has("--list"),
236
- ...base,
237
- };
214
+ if (rawAction === "connect" || rawAction === "disconnect")
215
+ return parseSlackConnection(values, base, rawAction);
216
+ if (rawAction === "coverage")
217
+ return parseSlackCoverage(values, base);
218
+ return parseSlackRead(values, base);
219
+ }
220
+ function parseSlackConnection(values, base, action) {
221
+ const workspace = optionalNonEmpty(values.flags.get("--workspace"));
222
+ if (workspace && !SLACK_WORKSPACE_KEYS.includes(workspace))
223
+ throw new Error("Unknown Slack workspace.");
224
+ if (action === "disconnect" && !workspace)
225
+ throw new Error("slack disconnect needs --workspace bli|blue_pearl.");
226
+ return { kind: "slack", action, workspace, ...base };
227
+ }
228
+ function parseSlackCoverage(values, base) {
229
+ // `--workspace` here is a Slack workspace key, NOT the collection-root
230
+ // `--workspace <path>` every setup command takes. Named at the point of use
231
+ // so the two cannot be confused silently.
232
+ const workspace = optionalNonEmpty(values.flags.get("--workspace"));
233
+ if (workspace && !SLACK_WORKSPACE_KEYS.includes(workspace)) {
234
+ throw new Error(`slack --workspace must be one of ${SLACK_WORKSPACE_KEYS.join(", ")}; got ${workspace}.`);
238
235
  }
236
+ return {
237
+ kind: "slack",
238
+ action: "coverage",
239
+ workspace,
240
+ staleOnly: values.booleans.has("--stale-only"),
241
+ // BLI-10450: counts alone could not answer "is there a Kaiben channel?".
242
+ list: values.booleans.has("--list"),
243
+ ...base,
244
+ };
245
+ }
246
+ function parseSlackRead(values, base) {
239
247
  const person = optionalNonEmpty(values.flags.get("--person"));
240
248
  const channel = optionalNonEmpty(values.flags.get("--channel"));
241
249
  const query = optionalNonEmpty(values.flags.get("--query"));
@@ -271,20 +279,19 @@ export function parseSlackArgs(args) {
271
279
  * `cockpit team [members|invite <email>|role <userId>|device list|device
272
280
  * revoke <id|name>]` (BLI-3461; `device` added BLI-3559).
273
281
  */
282
+ const TEAM_ALLOWED_FLAGS = [
283
+ "--home",
284
+ "--dashboard-url",
285
+ "--json",
286
+ "--role",
287
+ "--team-id",
288
+ "--yes",
289
+ "--reason",
290
+ "--note",
291
+ ];
292
+ const TEAM_VALUE_FLAGS = ["--home", "--dashboard-url", "--role", "--team-id", "--reason", "--note"];
274
293
  export function parseTeamArgs(args) {
275
- const values = parseNamedArgs(args, {
276
- allowedFlags: [
277
- "--home",
278
- "--dashboard-url",
279
- "--json",
280
- "--role",
281
- "--team-id",
282
- "--yes",
283
- "--reason",
284
- "--note",
285
- ],
286
- valueFlags: ["--home", "--dashboard-url", "--role", "--team-id", "--reason", "--note"],
287
- });
294
+ const values = parseNamedArgs(args, { allowedFlags: TEAM_ALLOWED_FLAGS, valueFlags: TEAM_VALUE_FLAGS });
288
295
  const common = {
289
296
  homeDir: optionalNonEmpty(values.flags.get("--home")),
290
297
  dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
@@ -293,31 +300,15 @@ export function parseTeamArgs(args) {
293
300
  json: values.booleans.has("--json"),
294
301
  };
295
302
  const [verb, ...rest] = values.positionals;
296
- if (verb === "github-login") {
297
- const [operation, personRef, githubLogin, ...extra] = rest;
298
- if (!["add", "remove", "list"].includes(operation ?? "") || !personRef || extra.length ||
299
- (operation === "list" ? githubLogin !== undefined : !githubLogin)) {
300
- throw new Error("team github-login takes add|remove <person> <login>, or list <person>.");
301
- }
302
- return { kind: "team", action: `github-login-${operation}`, personRef, githubLogin, ...common };
303
- }
304
- if (verb === "timezone") {
305
- const [operation, personRef, zone, ...extra] = rest;
306
- if (!["set", "list"].includes(operation ?? "") || extra.length ||
307
- (operation === "list" ? personRef !== undefined : !personRef || !zone)) {
308
- throw new Error("team timezone takes set <person> <iana-zone> (or the word unknown), or list.");
309
- }
310
- return { kind: "team", action: operation === "set" ? "timezone-set" : "timezone-list", ...(personRef ? { personRef } : {}), ...(zone ? { zone } : {}), ...common };
311
- }
303
+ if (verb === "github-login")
304
+ return parseTeamGithubLoginArgs(rest, common);
305
+ if (verb === "timezone")
306
+ return parseTeamTimezoneArgs(rest, common);
312
307
  if (verb === "device") {
313
308
  return parseTeamDeviceArgs(rest, values.flags, common);
314
309
  }
315
- if (verb === "grant" || verb === "revoke") {
316
- const [person, section, level = "see", ...extra] = rest;
317
- if (!person || section !== "careers" || !["see", "act"].includes(level) || extra.length)
318
- throw new Error("team grant|revoke <person uuid> careers [see|act]");
319
- return { kind: "team", action: verb, targetUserId: person, section: "careers", level: level, ...common };
320
- }
310
+ if (verb === "grant" || verb === "revoke")
311
+ return parseTeamGrantArgs(verb, rest, common);
321
312
  const [subject, ...extra] = rest;
322
313
  if (extra.length > 0)
323
314
  throw new Error("team does not accept that many arguments.");
@@ -345,6 +336,28 @@ export function parseTeamArgs(args) {
345
336
  }
346
337
  throw new Error("team takes one verb: members, invite, role, grant, revoke, or device.");
347
338
  }
339
+ function parseTeamGithubLoginArgs(rest, common) {
340
+ const [operation, personRef, githubLogin, ...extra] = rest;
341
+ if (!["add", "remove", "list"].includes(operation ?? "") || !personRef || extra.length ||
342
+ (operation === "list" ? githubLogin !== undefined : !githubLogin)) {
343
+ throw new Error("team github-login takes add|remove <person> <login>, or list <person>.");
344
+ }
345
+ return { kind: "team", action: `github-login-${operation}`, personRef, githubLogin, ...common };
346
+ }
347
+ function parseTeamTimezoneArgs(rest, common) {
348
+ const [operation, personRef, zone, ...extra] = rest;
349
+ if (!["set", "list"].includes(operation ?? "") || extra.length ||
350
+ (operation === "list" ? personRef !== undefined : !personRef || !zone)) {
351
+ throw new Error("team timezone takes set <person> <iana-zone> (or the word unknown), or list.");
352
+ }
353
+ return { kind: "team", action: operation === "set" ? "timezone-set" : "timezone-list", ...(personRef ? { personRef } : {}), ...(zone ? { zone } : {}), ...common };
354
+ }
355
+ function parseTeamGrantArgs(verb, rest, common) {
356
+ const [person, section, level = "see", ...extra] = rest;
357
+ if (!person || section !== "careers" || !["see", "act"].includes(level) || extra.length)
358
+ throw new Error("team grant|revoke <person uuid> careers [see|act]");
359
+ return { kind: "team", action: verb, targetUserId: person, section: "careers", level: level, ...common };
360
+ }
348
361
  /**
349
362
  * `cockpit team device [list] [--json]` or
350
363
  * `cockpit team device revoke <id|name> --reason <label> [--note "<text>"]`
@@ -20,7 +20,7 @@
20
20
  */
21
21
  import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
22
22
  const CAL_ACTIONS = new Set([
23
- "conflicts", "calendar-change",
23
+ "event", "export", "duplicate", "batch", "undo", "conflicts", "calendar-change",
24
24
  "today",
25
25
  "week",
26
26
  "next",
@@ -36,58 +36,67 @@ const CAL_ACTIONS = new Set([
36
36
  "delete",
37
37
  ]);
38
38
  /** Every verb that names ONE thing first — a calendar id, or (delete) an event id. */
39
- const CAL_ACTIONS_NEEDING_A_SUBJECT = new Set(["calendar-change", "sync", "detach", "share", "delete", "change", "cancel"]);
39
+ const CAL_ACTIONS_NEEDING_A_SUBJECT = new Set(["event", "export", "duplicate", "calendar-change", "sync", "detach", "share", "delete", "change", "cancel"]);
40
40
  /** `sync-status [<calendar id>]` — every calendar, or one's. Ticket 10730. */
41
- const CAL_ACTIONS_WITH_OPTIONAL_SUBJECT = new Set(["sync-status"]);
41
+ const CAL_ACTIONS_WITH_OPTIONAL_SUBJECT = new Set(["sync-status", "undo"]);
42
+ const CAL_ALLOWED_FLAGS = [
43
+ "--home",
44
+ "--dashboard-url",
45
+ "--color",
46
+ "--calendar",
47
+ "--account",
48
+ "--tz",
49
+ "--occurrence-start",
50
+ "--offset",
51
+ "--hours",
52
+ "--from",
53
+ "--to",
54
+ "--limit",
55
+ "--all",
56
+ "--name",
57
+ "--title",
58
+ "--at",
59
+ "--until",
60
+ "--location",
61
+ "--attendee",
62
+ "--all-day",
63
+ "--timed",
64
+ "--keep-invitees",
65
+ "--meet",
66
+ "--org-visible",
67
+ "--private",
68
+ "--full",
69
+ "--yes",
70
+ "--json",
71
+ "--idempotency-key",
72
+ "--expected-version",
73
+ "--reason",
74
+ ];
75
+ const CAL_VALUE_FLAGS = [
76
+ "--home",
77
+ "--dashboard-url",
78
+ "--color",
79
+ "--calendar",
80
+ "--account",
81
+ "--tz",
82
+ "--occurrence-start",
83
+ "--offset",
84
+ "--hours",
85
+ "--from",
86
+ "--to",
87
+ "--limit",
88
+ "--name",
89
+ "--title",
90
+ "--at",
91
+ "--until",
92
+ "--location",
93
+ "--attendee",
94
+ "--idempotency-key",
95
+ "--expected-version",
96
+ "--reason",
97
+ ];
42
98
  export function parseCalArgs(args) {
43
- const values = parseNamedArgs(args, {
44
- allowedFlags: [
45
- "--home",
46
- "--dashboard-url",
47
- "--color",
48
- "--calendar",
49
- "--account",
50
- "--tz",
51
- "--offset",
52
- "--hours",
53
- "--from",
54
- "--to",
55
- "--limit",
56
- "--all",
57
- "--name",
58
- "--title",
59
- "--at",
60
- "--until",
61
- "--location",
62
- "--attendee",
63
- "--all-day",
64
- "--meet",
65
- "--org-visible",
66
- "--private",
67
- "--full",
68
- "--yes",
69
- "--json",
70
- ],
71
- valueFlags: [
72
- "--home",
73
- "--dashboard-url",
74
- "--color",
75
- "--calendar",
76
- "--account",
77
- "--tz",
78
- "--offset",
79
- "--hours",
80
- "--from",
81
- "--to",
82
- "--limit",
83
- "--name",
84
- "--title",
85
- "--at",
86
- "--until",
87
- "--location",
88
- "--attendee",
89
- ],
90
- });
99
+ const values = parseNamedArgs(args, { allowedFlags: CAL_ALLOWED_FLAGS, valueFlags: CAL_VALUE_FLAGS });
91
100
  const { action, rest } = readCalAction(values);
92
101
  const { subject, query } = readCalSubjectOrQuery(action, rest);
93
102
  const { title, startsAt, endsAt } = readCalCreateDetails(action, values);
@@ -103,7 +112,7 @@ function readCalAction(values) {
103
112
  const first = values.positionals[0];
104
113
  const action = (first === undefined ? "today" : first);
105
114
  if (!CAL_ACTIONS.has(action)) {
106
- throw new Error(`Unknown cal command: ${first}. Try today, week, conflicts, calendar-change, next, find, calendars, add-ical, create, share, detach, sync, sync-status, or delete.`);
115
+ throw new Error(`Unknown cal command: ${first}. Try today, week, conflicts, calendar-change, next, find, calendars, add-ical, create, duplicate, batch, undo, share, detach, sync, sync-status, or delete.`);
107
116
  }
108
117
  return { action, rest: values.positionals.slice(first === undefined ? 0 : 1) };
109
118
  }
@@ -113,8 +122,8 @@ function readCalSubjectOrQuery(action, rest) {
113
122
  if (CAL_ACTIONS_NEEDING_A_SUBJECT.has(action)) {
114
123
  subject = optionalNonEmpty(rest[0]);
115
124
  if (!subject) {
116
- throw new Error(action === "delete"
117
- ? "cal delete needs an event id — take one from a window verb's output (today/week/next/find)."
125
+ throw new Error(action === "delete" || action === "duplicate"
126
+ ? `cal ${action} needs an event id — take one from a window verb's output (today/week/next/find).`
118
127
  : `cal ${action} needs a calendar id — take one from \`cockpit cal calendars\`.`);
119
128
  }
120
129
  if (rest.length > 1)
@@ -173,6 +182,7 @@ function readCalOffset(values) {
173
182
  function buildCalCommand(values, action, subject, query, title, startsAt, endsAt, offset, limit) {
174
183
  return {
175
184
  kind: "cal",
185
+ occurrenceStart: optionalNonEmpty(values.flags.get("--occurrence-start")),
176
186
  color: optionalNonEmpty(values.flags.get("--color")),
177
187
  action,
178
188
  homeDir: optionalNonEmpty(values.flags.get("--home")),
@@ -199,6 +209,11 @@ function buildCalCommand(values, action, subject, query, title, startsAt, endsAt
199
209
  location: optionalNonEmpty(values.flags.get("--location")),
200
210
  attendees: splitAddresses(values.flags.get("--attendee")),
201
211
  allDay: values.booleans.has("--all-day"),
212
+ timed: values.booleans.has("--timed"),
213
+ keepInvitees: values.booleans.has("--keep-invitees"),
214
+ idempotencyKey: optionalNonEmpty(values.flags.get("--idempotency-key")),
215
+ expectedVersion: optionalNonEmpty(values.flags.get("--expected-version")),
216
+ reason: optionalNonEmpty(values.flags.get("--reason")),
202
217
  meet: values.booleans.has("--meet"),
203
218
  orgVisible: values.booleans.has("--org-visible"),
204
219
  makePrivate: values.booleans.has("--private"),