@bli-cockpit/cli 0.2.237 → 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 (139) 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 +73 -30
  29. package/dist/commands/cal.js +36 -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/heartbeat-post.js +116 -0
  35. package/dist/commands/heartbeat-token-lock.js +46 -33
  36. package/dist/commands/heartbeat.js +43 -125
  37. package/dist/commands/install-update.js +17 -15
  38. package/dist/commands/issue-show.js +27 -28
  39. package/dist/commands/issue-write.js +70 -23
  40. package/dist/commands/issue.js +68 -37
  41. package/dist/commands/jarvis-live-answer.js +99 -0
  42. package/dist/commands/jarvis-render.js +1 -94
  43. package/dist/commands/jarvis-trace.js +30 -22
  44. package/dist/commands/jarvis-turn-report.js +124 -0
  45. package/dist/commands/jarvis-turn.js +108 -182
  46. package/dist/commands/jarvis.js +26 -17
  47. package/dist/commands/local-args-collector-setup.js +82 -84
  48. package/dist/commands/local-args-collector-work.js +37 -38
  49. package/dist/commands/local-args-tower-admin.js +155 -142
  50. package/dist/commands/local-args-tower-cal.js +52 -50
  51. package/dist/commands/local-args-tower-careers.js +73 -54
  52. package/dist/commands/local-args-tower-docs-msg.js +55 -45
  53. package/dist/commands/local-args-tower-github.js +26 -20
  54. package/dist/commands/local-args-tower-hits.js +32 -20
  55. package/dist/commands/local-args-tower-manager.js +85 -4
  56. package/dist/commands/local-args-tower-memory.js +139 -117
  57. package/dist/commands/local-args-tower-notes.js +346 -0
  58. package/dist/commands/local-args-tower-pages.js +100 -378
  59. package/dist/commands/local-args-tower-settings.js +28 -29
  60. package/dist/commands/local-args-tower-work.js +52 -33
  61. package/dist/commands/local-args.js +80 -129
  62. package/dist/commands/local-help-commands-tower-ops.js +15 -4
  63. package/dist/commands/local-help-commands-tower.js +6 -2
  64. package/dist/commands/local-help-commands.js +342 -341
  65. package/dist/commands/local-help.js +87 -83
  66. package/dist/commands/local-run-guards.js +100 -0
  67. package/dist/commands/local.js +137 -206
  68. package/dist/commands/login.js +13 -9
  69. package/dist/commands/logs.js +55 -38
  70. package/dist/commands/mail-attachments.js +44 -26
  71. package/dist/commands/manager-render-home.js +208 -0
  72. package/dist/commands/manager-render.js +7 -3
  73. package/dist/commands/manager-requests.js +33 -0
  74. package/dist/commands/manager.js +39 -5
  75. package/dist/commands/memory-consolidation.js +18 -9
  76. package/dist/commands/memory-daemon-probe.js +37 -28
  77. package/dist/commands/memory-install-config.js +64 -60
  78. package/dist/commands/memory-install.js +21 -41
  79. package/dist/commands/memory-requeue.js +26 -19
  80. package/dist/commands/memory-rules.js +26 -18
  81. package/dist/commands/memory-search-print.js +26 -0
  82. package/dist/commands/memory-store.js +14 -30
  83. package/dist/commands/memory-verb-list.js +49 -48
  84. package/dist/commands/notes-accounts.js +49 -32
  85. package/dist/commands/notes-sync-loop.js +115 -89
  86. package/dist/commands/notes-wisprflow-login.js +44 -23
  87. package/dist/commands/notes-writes.js +115 -100
  88. package/dist/commands/notes.js +33 -55
  89. package/dist/commands/onboard-flows.js +69 -51
  90. package/dist/commands/onboard.js +11 -9
  91. package/dist/commands/ops-render-memory.js +10 -7
  92. package/dist/commands/ops-render.js +33 -30
  93. package/dist/commands/ops.js +3 -0
  94. package/dist/commands/public-root.js +1 -1
  95. package/dist/commands/session-sync-outside-roots.js +40 -36
  96. package/dist/commands/session-sync-plan.js +14 -10
  97. package/dist/commands/session-sync-upload.js +24 -22
  98. package/dist/commands/session-sync.js +19 -8
  99. package/dist/commands/slack.js +25 -16
  100. package/dist/commands/sources-check.js +13 -7
  101. package/dist/commands/sources-discovery.js +25 -17
  102. package/dist/commands/sources-verdict.js +19 -18
  103. package/dist/commands/speed.js +23 -19
  104. package/dist/commands/start.js +20 -16
  105. package/dist/commands/sync-doctor.js +52 -48
  106. package/dist/commands/sync.js +22 -16
  107. package/dist/commands/usage-telemetry-toml.js +71 -45
  108. package/dist/commands/usage.js +91 -67
  109. package/dist/commands/workbook-render.js +10 -7
  110. package/dist/evidence-reconcile-client.js +75 -64
  111. package/dist/evidence-redelivery-plan.js +37 -34
  112. package/dist/evidence-redelivery.js +32 -29
  113. package/dist/evidence-transcripts-client.js +20 -20
  114. package/dist/evidence-upload-object.js +86 -89
  115. package/dist/evidence-upload-plan.js +94 -83
  116. package/dist/evidence-upload-terminal.js +45 -50
  117. package/dist/image-description-session.js +156 -159
  118. package/dist/local-state-pairing-code.js +39 -31
  119. package/dist/local-state-status.js +9 -2
  120. package/dist/onboarding-roots.js +5 -8
  121. package/dist/process-runner.js +8 -8
  122. package/dist/raw-evidence-gc.js +33 -38
  123. package/dist/raw-evidence-stuck-summary.js +7 -14
  124. package/dist/repo-identity.js +26 -23
  125. package/dist/scheduled-self-update.js +11 -15
  126. package/dist/second-install.js +3 -0
  127. package/dist/slim/codex-rules.js +23 -36
  128. package/dist/sync-lock.js +5 -15
  129. package/dist/tower-client.js +36 -53
  130. package/dist/tower-stream.js +7 -24
  131. package/dist/upload-agent-artifacts.js +58 -64
  132. package/dist/upload-envelope-build.js +60 -59
  133. package/dist/upload-evidence-delivery-reconcile.js +58 -56
  134. package/dist/upload-evidence-delivery-summary.js +32 -34
  135. package/dist/upload-ingest-receipt.js +20 -23
  136. package/dist/upload-session-reports-queue.js +4 -1
  137. package/dist/upload-session-reports-wire.js +64 -68
  138. package/dist/upload-sync.js +171 -157
  139. package/package.json +2 -2
@@ -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", "conflicts", "calendar-change",
24
24
  "today",
25
25
  "week",
26
26
  "next",
@@ -36,58 +36,59 @@ 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", "calendar-change", "sync", "detach", "share", "delete", "change", "cancel"]);
40
40
  /** `sync-status [<calendar id>]` — every calendar, or one's. Ticket 10730. */
41
41
  const CAL_ACTIONS_WITH_OPTIONAL_SUBJECT = new Set(["sync-status"]);
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
+ "--meet",
64
+ "--org-visible",
65
+ "--private",
66
+ "--full",
67
+ "--yes",
68
+ "--json",
69
+ ];
70
+ const CAL_VALUE_FLAGS = [
71
+ "--home",
72
+ "--dashboard-url",
73
+ "--color",
74
+ "--calendar",
75
+ "--account",
76
+ "--tz",
77
+ "--occurrence-start",
78
+ "--offset",
79
+ "--hours",
80
+ "--from",
81
+ "--to",
82
+ "--limit",
83
+ "--name",
84
+ "--title",
85
+ "--at",
86
+ "--until",
87
+ "--location",
88
+ "--attendee",
89
+ ];
42
90
  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
- });
91
+ const values = parseNamedArgs(args, { allowedFlags: CAL_ALLOWED_FLAGS, valueFlags: CAL_VALUE_FLAGS });
91
92
  const { action, rest } = readCalAction(values);
92
93
  const { subject, query } = readCalSubjectOrQuery(action, rest);
93
94
  const { title, startsAt, endsAt } = readCalCreateDetails(action, values);
@@ -173,6 +174,7 @@ function readCalOffset(values) {
173
174
  function buildCalCommand(values, action, subject, query, title, startsAt, endsAt, offset, limit) {
174
175
  return {
175
176
  kind: "cal",
177
+ occurrenceStart: optionalNonEmpty(values.flags.get("--occurrence-start")),
176
178
  color: optionalNonEmpty(values.flags.get("--color")),
177
179
  action,
178
180
  homeDir: optionalNonEmpty(values.flags.get("--home")),
@@ -9,11 +9,56 @@ const NEEDS_ROLE = ['takehome-show', 'takehome-set'];
9
9
  export function parseCareersArgs(args) {
10
10
  const valueFlags = ['--role', '--min-score', '--since', '--home', '--dashboard-url', '--decision', '--stage', '--subject', '--body-file', '--link', '--pass-score', '--account', '--auto-invite-min-score', '--attention-min-score', '--enabled', '--booking-link', '--grader-model', '--out', '--record-sent-at', '--at', '--followup-enabled', '--followup-after-days', '--takehome-window-hours'];
11
11
  const values = parseNamedArgs(args, { allowedFlags: [...valueFlags, '--json', '--now', '--force', '--clear'], valueFlags });
12
- const positionals = [...values.positionals];
13
- // `takehome show` and `settings set` are two words a person types and one
14
- // action everything downstream reads.
15
- // A bare `careers takehome` or `careers settings` reads as its `show`, the way
16
- // `team device` reads as `team device list`: the noun alone is a question.
12
+ const { verb, target } = readCareersVerb([...values.positionals]);
13
+ const rawScore = values.flags.get('--min-score');
14
+ const minScore = rawScore === undefined ? undefined : Number(rawScore);
15
+ if (minScore !== undefined && (!Number.isFinite(minScore) || minScore < 0 || minScore > 100))
16
+ throw new Error('--min-score must be 0 to 100.');
17
+ const since = optionalNonEmpty(values.flags.get('--since'));
18
+ if (since && !Number.isFinite(Date.parse(since)))
19
+ throw new Error('--since must be a date.');
20
+ const decision = optionalNonEmpty(values.flags.get('--decision'));
21
+ if (verb === 'decide' && (!decision || !DECISIONS.includes(decision)))
22
+ throw new Error(`careers decide needs --decision ${DECISIONS.join('|')}.`);
23
+ const passScore = numberFlag(values.flags.get('--pass-score'), '--pass-score', 0, 10);
24
+ const autoInviteMinScore = numberFlag(values.flags.get('--auto-invite-min-score'), '--auto-invite-min-score', 0, 100);
25
+ const attentionMinScore = numberFlag(values.flags.get('--attention-min-score'), '--attention-min-score', 0, 100);
26
+ const enabledRaw = trueOrFalseFlag(values.flags.get('--enabled'), '--enabled');
27
+ const recordSentAt = readRecordSentAt(values, verb);
28
+ const { interviewAt, clear } = readInterviewFlags(values, verb);
29
+ const followupEnabledRaw = trueOrFalseFlag(values.flags.get('--followup-enabled'), '--followup-enabled');
30
+ const followupAfterDays = wholeFlag(values.flags.get('--followup-after-days'), '--followup-after-days', 1, 60);
31
+ const takehomeWindowHours = wholeFlag(values.flags.get('--takehome-window-hours'), '--takehome-window-hours', 1, 720);
32
+ const { outPath, force } = readResumeFileFlags(values, verb);
33
+ return {
34
+ kind: 'careers', action: verb,
35
+ id: NEEDS_ID.includes(verb) ? target : undefined,
36
+ takehomeRole: NEEDS_ROLE.includes(verb) ? target : undefined,
37
+ role: optionalNonEmpty(values.flags.get('--role')), minScore, since: since ? new Date(since).toISOString() : undefined,
38
+ stage: optionalNonEmpty(values.flags.get('--stage')), decision,
39
+ subject: optionalNonEmpty(values.flags.get('--subject')), bodyFile: optionalNonEmpty(values.flags.get('--body-file')),
40
+ link: optionalNonEmpty(values.flags.get('--link')), passScore,
41
+ inviteAccountId: optionalNonEmpty(values.flags.get('--account')),
42
+ bookingLink: httpsFlag(values.flags.get('--booking-link')),
43
+ graderModel: optionalNonEmpty(values.flags.get('--grader-model')),
44
+ now: values.booleans.has('--now'),
45
+ outPath, force,
46
+ interviewAt: interviewAt ? new Date(interviewAt).toISOString() : undefined, clear: clear || undefined,
47
+ recordSentAt: recordSentAt ? new Date(recordSentAt).toISOString() : undefined,
48
+ followupEnabled: followupEnabledRaw === undefined ? undefined : followupEnabledRaw === 'true',
49
+ followupAfterDays, takehomeWindowHours,
50
+ autoInviteMinScore, attentionMinScore,
51
+ autoInviteEnabled: enabledRaw === undefined ? undefined : enabledRaw === 'true',
52
+ homeDir: optionalNonEmpty(values.flags.get('--home')), dashboardUrl: optionalUrl(values.flags.get('--dashboard-url')), json: values.booleans.has('--json'),
53
+ };
54
+ }
55
+ /**
56
+ * `takehome show` and `settings set` are two words a person types and one
57
+ * action everything downstream reads.
58
+ * A bare `careers takehome` or `careers settings` reads as its `show`, the way
59
+ * `team device` reads as `team device list`: the noun alone is a question.
60
+ */
61
+ function readCareersVerb(positionals) {
17
62
  const first = positionals[0] ?? 'list';
18
63
  const paired = first === 'takehome' || first === 'settings';
19
64
  const typed = paired
@@ -32,32 +77,28 @@ export function parseCareersArgs(args) {
32
77
  throw new Error(`careers ${verb.replace('-', ' ')} needs a role slug.`);
33
78
  if (positionals.length > 0)
34
79
  throw new Error('Unexpected careers argument.');
35
- const rawScore = values.flags.get('--min-score');
36
- const minScore = rawScore === undefined ? undefined : Number(rawScore);
37
- if (minScore !== undefined && (!Number.isFinite(minScore) || minScore < 0 || minScore > 100))
38
- throw new Error('--min-score must be 0 to 100.');
39
- const since = optionalNonEmpty(values.flags.get('--since'));
40
- if (since && !Number.isFinite(Date.parse(since)))
41
- throw new Error('--since must be a date.');
42
- const decision = optionalNonEmpty(values.flags.get('--decision'));
43
- if (verb === 'decide' && (!decision || !DECISIONS.includes(decision)))
44
- throw new Error(`careers decide needs --decision ${DECISIONS.join('|')}.`);
45
- const passScore = numberFlag(values.flags.get('--pass-score'), '--pass-score', 0, 10);
46
- const autoInviteMinScore = numberFlag(values.flags.get('--auto-invite-min-score'), '--auto-invite-min-score', 0, 100);
47
- const attentionMinScore = numberFlag(values.flags.get('--attention-min-score'), '--attention-min-score', 0, 100);
48
- const enabledRaw = optionalNonEmpty(values.flags.get('--enabled'));
49
- if (enabledRaw !== undefined && enabledRaw !== 'true' && enabledRaw !== 'false')
50
- throw new Error('--enabled must be true or false.');
51
- // BLI-12183: the second-chance follow-up. `--record-sent-at` records one sent
52
- // outside Tower and sends nothing; the three settings flags are whole numbers
53
- // and a boolean, refused here as well as on the door.
80
+ return { typed, verb, target };
81
+ }
82
+ function trueOrFalseFlag(raw, flag) {
83
+ const value = optionalNonEmpty(raw);
84
+ if (value !== undefined && value !== 'true' && value !== 'false')
85
+ throw new Error(`${flag} must be true or false.`);
86
+ return value;
87
+ }
88
+ // BLI-12183: the second-chance follow-up. `--record-sent-at` records one sent
89
+ // outside Tower and sends nothing; the three settings flags are whole numbers
90
+ // and a boolean, refused here as well as on the door.
91
+ function readRecordSentAt(values, verb) {
54
92
  const recordSentAt = optionalNonEmpty(values.flags.get('--record-sent-at'));
55
93
  if (recordSentAt !== undefined && verb !== 'followup')
56
94
  throw new Error('--record-sent-at belongs to `cockpit careers followup <id>`.');
57
95
  if (recordSentAt !== undefined && !Number.isFinite(Date.parse(recordSentAt)))
58
96
  throw new Error('--record-sent-at must be an ISO date-time, e.g. 2026-10-03T08:00:00Z.');
59
- // BLI-12198: an interview Edward booked himself. Exactly one of `--at <iso>`
60
- // (book or reschedule) or `--clear`, refused here as well as on the door.
97
+ return recordSentAt;
98
+ }
99
+ // BLI-12198: an interview Edward booked himself. Exactly one of `--at <iso>`
100
+ // (book or reschedule) or `--clear`, refused here as well as on the door.
101
+ function readInterviewFlags(values, verb) {
61
102
  const interviewAt = optionalNonEmpty(values.flags.get('--at'));
62
103
  const clear = values.booleans.has('--clear');
63
104
  if (interviewAt !== undefined && verb !== 'interview')
@@ -71,13 +112,11 @@ export function parseCareersArgs(args) {
71
112
  throw new Error('--at must be an ISO date-time with an offset, e.g. 2026-10-05T10:00:00-07:00.');
72
113
  }
73
114
  }
74
- const followupEnabledRaw = optionalNonEmpty(values.flags.get('--followup-enabled'));
75
- if (followupEnabledRaw !== undefined && followupEnabledRaw !== 'true' && followupEnabledRaw !== 'false')
76
- throw new Error('--followup-enabled must be true or false.');
77
- const followupAfterDays = wholeFlag(values.flags.get('--followup-after-days'), '--followup-after-days', 1, 60);
78
- const takehomeWindowHours = wholeFlag(values.flags.get('--takehome-window-hours'), '--takehome-window-hours', 1, 720);
79
- // The resume is the only thing this noun writes to disk, so both file flags
80
- // belong to it alone and say so rather than being accepted and ignored.
115
+ return { interviewAt, clear };
116
+ }
117
+ // The resume is the only thing this noun writes to disk, so both file flags
118
+ // belong to it alone and say so rather than being accepted and ignored.
119
+ function readResumeFileFlags(values, verb) {
81
120
  const outPath = optionalNonEmpty(values.flags.get('--out'));
82
121
  const force = values.booleans.has('--force');
83
122
  if (outPath !== undefined && verb !== 'resume')
@@ -86,27 +125,7 @@ export function parseCareersArgs(args) {
86
125
  throw new Error('--force belongs to `cockpit careers resume <id> --out <path>`.');
87
126
  if (force && outPath === undefined)
88
127
  throw new Error('--force only means something beside --out <path>: with no file to write there is nothing to overwrite.');
89
- return {
90
- kind: 'careers', action: verb,
91
- id: NEEDS_ID.includes(verb) ? target : undefined,
92
- takehomeRole: NEEDS_ROLE.includes(verb) ? target : undefined,
93
- role: optionalNonEmpty(values.flags.get('--role')), minScore, since: since ? new Date(since).toISOString() : undefined,
94
- stage: optionalNonEmpty(values.flags.get('--stage')), decision,
95
- subject: optionalNonEmpty(values.flags.get('--subject')), bodyFile: optionalNonEmpty(values.flags.get('--body-file')),
96
- link: optionalNonEmpty(values.flags.get('--link')), passScore,
97
- inviteAccountId: optionalNonEmpty(values.flags.get('--account')),
98
- bookingLink: httpsFlag(values.flags.get('--booking-link')),
99
- graderModel: optionalNonEmpty(values.flags.get('--grader-model')),
100
- now: values.booleans.has('--now'),
101
- outPath, force,
102
- interviewAt: interviewAt ? new Date(interviewAt).toISOString() : undefined, clear: clear || undefined,
103
- recordSentAt: recordSentAt ? new Date(recordSentAt).toISOString() : undefined,
104
- followupEnabled: followupEnabledRaw === undefined ? undefined : followupEnabledRaw === 'true',
105
- followupAfterDays, takehomeWindowHours,
106
- autoInviteMinScore, attentionMinScore,
107
- autoInviteEnabled: enabledRaw === undefined ? undefined : enabledRaw === 'true',
108
- homeDir: optionalNonEmpty(values.flags.get('--home')), dashboardUrl: optionalUrl(values.flags.get('--dashboard-url')), json: values.booleans.has('--json'),
109
- };
128
+ return { outPath, force };
110
129
  }
111
130
  /**
112
131
  * BLI-4461: a booking link goes into an email a stranger clicks, so the
@@ -15,44 +15,43 @@ const DOCS_ACTIONS = new Set(["list", "tree", "read", "create", "update", "delet
15
15
  // (the same door, called once per ref), so it joined `read`'s branch rather
16
16
  // than this set.
17
17
  const DOCS_ACTIONS_NEEDING_A_DOC = new Set(["update", "link", "export"]);
18
+ const DOCS_ALLOWED_FLAGS = [
19
+ "--home",
20
+ "--dashboard-url",
21
+ "--title",
22
+ "--parent",
23
+ "--clear-parent",
24
+ "--allow-empty",
25
+ "--visibility",
26
+ "--file",
27
+ "--body-stdin",
28
+ "--query",
29
+ "--word",
30
+ "--limit",
31
+ "--clear",
32
+ "--bodies",
33
+ "--section",
34
+ "--offset",
35
+ "--format",
36
+ "--out",
37
+ "--json",
38
+ ];
39
+ const DOCS_VALUE_FLAGS = [
40
+ "--home",
41
+ "--dashboard-url",
42
+ "--title",
43
+ "--parent",
44
+ "--visibility",
45
+ "--file",
46
+ "--query",
47
+ "--limit",
48
+ "--section",
49
+ "--offset",
50
+ "--format",
51
+ "--out",
52
+ ];
18
53
  export function parseDocsArgs(args) {
19
- const values = parseNamedArgs(args, {
20
- allowedFlags: [
21
- "--home",
22
- "--dashboard-url",
23
- "--title",
24
- "--parent",
25
- "--clear-parent",
26
- "--allow-empty",
27
- "--visibility",
28
- "--file",
29
- "--body-stdin",
30
- "--query",
31
- "--word",
32
- "--limit",
33
- "--clear",
34
- "--bodies",
35
- "--section",
36
- "--offset",
37
- "--format",
38
- "--out",
39
- "--json",
40
- ],
41
- valueFlags: [
42
- "--home",
43
- "--dashboard-url",
44
- "--title",
45
- "--parent",
46
- "--visibility",
47
- "--file",
48
- "--query",
49
- "--limit",
50
- "--section",
51
- "--offset",
52
- "--format",
53
- "--out",
54
- ],
55
- });
54
+ const values = parseNamedArgs(args, { allowedFlags: DOCS_ALLOWED_FLAGS, valueFlags: DOCS_VALUE_FLAGS });
56
55
  const { action, rest } = parseDocsAction(values.positionals);
57
56
  const link = parseDocsLink(action, rest, values.booleans.has("--clear"));
58
57
  const { docRef, docRefs } = parseDocsDocumentReference(action, link.rest);
@@ -150,6 +149,10 @@ function parseDocsDocumentReference(action, rest) {
150
149
  return { docRef: undefined, docRefs: undefined };
151
150
  }
152
151
  function parseDocsFlags(action, values) {
152
+ return { ...parseDocsWriteFlags(action, values), ...parseDocsReadFlags(action, values) };
153
+ }
154
+ /** The flags that decide where a document lives and what it is called. */
155
+ function parseDocsWriteFlags(action, values) {
153
156
  const visibility = optionalNonEmpty(values.flags.get("--visibility"));
154
157
  if (visibility && visibility !== "org" && visibility !== "private") {
155
158
  throw new Error('docs --visibility must be "org" or "private".');
@@ -174,6 +177,10 @@ function parseDocsFlags(action, values) {
174
177
  if (action === "create" && !title) {
175
178
  throw new Error("docs create needs --title.");
176
179
  }
180
+ return { visibility, clearParent, parentId, allowEmpty, title };
181
+ }
182
+ /** The flags that narrow or slice what a list or read returns. */
183
+ function parseDocsReadFlags(action, values) {
177
184
  // BLI-3737: narrowing belongs to `list` only. `--parent` keeps its create/
178
185
  // update meaning elsewhere (which parent to file the document under); on
179
186
  // `list` the same word names which parent to list UNDER, plus the literal
@@ -205,16 +212,19 @@ function parseDocsFlags(action, values) {
205
212
  const section = optionalNonEmpty(values.flags.get("--section"));
206
213
  if (section && action !== "read")
207
214
  throw new Error("--section belongs to `cockpit docs read`.");
208
- const offsetRaw = optionalNonEmpty(values.flags.get("--offset"));
209
- let offset;
210
- if (offsetRaw !== undefined) {
211
- offset = Number(offsetRaw);
212
- if (!Number.isInteger(offset) || offset < 0)
213
- throw new Error("--offset must be a whole number 0 or greater.");
214
- }
215
+ const offset = parseDocsOffset(values.flags.get("--offset"));
215
216
  if (offset !== undefined && action !== "read")
216
217
  throw new Error("--offset belongs to `cockpit docs read`.");
217
- return { visibility, clearParent, parentId, allowEmpty, title, query, wholeWord, limit, bodies, section, offset };
218
+ return { query, wholeWord, limit, bodies, section, offset };
219
+ }
220
+ function parseDocsOffset(raw) {
221
+ const offsetRaw = optionalNonEmpty(raw);
222
+ if (offsetRaw === undefined)
223
+ return undefined;
224
+ const offset = Number(offsetRaw);
225
+ if (!Number.isInteger(offset) || offset < 0)
226
+ throw new Error("--offset must be a whole number 0 or greater.");
227
+ return offset;
218
228
  }
219
229
  function buildDocsCommand(values, action, docRef, docRefs, visibility, clearParent, parentId, allowEmpty, title, query, wholeWord, limit, bodies, section, offset, link) {
220
230
  return {
@@ -33,26 +33,8 @@ export function parseGithubArgs(args) {
33
33
  throw new Error(`github ${action} takes a repo and a pull request number only.`);
34
34
  const pullNumber = numberText === undefined ? undefined : optionalPositiveInteger(numberText, "pull request number");
35
35
  const runId = optionalPositiveInteger(values.flags.get("--run"), "--run");
36
- if (action === "rerun") {
37
- if ((pullNumber === undefined) === (runId === undefined)) {
38
- throw new Error("github rerun needs exactly one of a pull request number or --run <id>.");
39
- }
40
- }
41
- else if (pullNumber === undefined) {
42
- throw new Error(`github ${action} needs a pull request number.`);
43
- }
44
- const only = (flag, allowed) => {
45
- if (values.flags.has(flag) && !allowed.includes(action)) {
46
- throw new Error(`${flag} belongs to \`cockpit github ${allowed.join("|")}\`.`);
47
- }
48
- };
49
- only("--claim", ["pr"]);
50
- only("--method", ["merge"]);
51
- only("--title", ["retitle"]);
52
- only("--run", ["rerun"]);
53
- if (values.flags.has("--confirm") && action === "pr") {
54
- throw new Error("--confirm belongs to merge, retitle and rerun; pr only reads.");
55
- }
36
+ requireTargetFor(action, pullNumber, runId);
37
+ refuseFlagsFromOtherActions(values, action);
56
38
  const title = optionalNonEmpty(values.flags.get("--title"));
57
39
  if (action === "retitle" && !title)
58
40
  throw new Error("github retitle needs --title \"<new title>\".");
@@ -78,4 +60,28 @@ export function parseGithubArgs(args) {
78
60
  dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
79
61
  json: values.booleans.has("--json"),
80
62
  };
63
+ }
64
+ function requireTargetFor(action, pullNumber, runId) {
65
+ if (action === "rerun") {
66
+ if ((pullNumber === undefined) === (runId === undefined)) {
67
+ throw new Error("github rerun needs exactly one of a pull request number or --run <id>.");
68
+ }
69
+ }
70
+ else if (pullNumber === undefined) {
71
+ throw new Error(`github ${action} needs a pull request number.`);
72
+ }
73
+ }
74
+ function refuseFlagsFromOtherActions(values, action) {
75
+ const only = (flag, allowed) => {
76
+ if (values.flags.has(flag) && !allowed.includes(action)) {
77
+ throw new Error(`${flag} belongs to \`cockpit github ${allowed.join("|")}\`.`);
78
+ }
79
+ };
80
+ only("--claim", ["pr"]);
81
+ only("--method", ["merge"]);
82
+ only("--title", ["retitle"]);
83
+ only("--run", ["rerun"]);
84
+ if (values.flags.has("--confirm") && action === "pr") {
85
+ throw new Error("--confirm belongs to merge, retitle and rerun; pr only reads.");
86
+ }
81
87
  }
@@ -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 } : {}),