@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.
- package/dist/adapters/attribution-core-fallbacks.js +15 -11
- package/dist/adapters/attribution-core-score.js +111 -87
- package/dist/adapters/car-state.js +38 -35
- package/dist/adapters/claude-attribution-discovery.js +81 -56
- package/dist/adapters/claude-attribution-score.js +108 -94
- package/dist/adapters/claude-attribution-signals.js +52 -42
- package/dist/adapters/codex-attribution.js +42 -33
- package/dist/adapters/codex-session-signals.js +11 -9
- package/dist/adapters/git-state.js +56 -50
- package/dist/adapters/local-sources.js +52 -43
- package/dist/adapters/raw-evidence-git-diff.js +4 -2
- package/dist/adapters/raw-evidence-sanitize.js +15 -12
- package/dist/adapters/raw-evidence-transcript-file.js +32 -31
- package/dist/autostart-self-heal.js +18 -2
- package/dist/commands/agent-session-report.js +51 -59
- package/dist/commands/agent-setup-observed.js +30 -23
- package/dist/commands/agent-setup.js +96 -45
- package/dist/commands/autostart-heal.js +8 -2
- package/dist/commands/autostart-reading.js +3 -0
- package/dist/commands/backfill-evidence-outcomes.js +9 -6
- package/dist/commands/backfill-issues.js +3 -0
- package/dist/commands/backfill-scan.js +27 -22
- package/dist/commands/backfill-upload.js +69 -60
- package/dist/commands/backfill.js +26 -21
- package/dist/commands/brief-done.js +4 -1
- package/dist/commands/brief-edit.js +71 -46
- package/dist/commands/cal-detail.js +35 -0
- package/dist/commands/cal-overlay.js +106 -0
- package/dist/commands/cal.js +40 -23
- package/dist/commands/clean.js +4 -1
- package/dist/commands/docs-read.js +5 -2
- package/dist/commands/doctor-backfill.js +88 -71
- package/dist/commands/doctor-pipeline.js +59 -46
- package/dist/commands/doctor-usage-telemetry.js +2 -1
- package/dist/commands/heartbeat-post.js +116 -0
- package/dist/commands/heartbeat-token-lock.js +46 -33
- package/dist/commands/heartbeat.js +43 -125
- package/dist/commands/install-update.js +17 -15
- package/dist/commands/issue-show.js +27 -28
- package/dist/commands/issue-write.js +70 -23
- package/dist/commands/issue.js +68 -37
- package/dist/commands/jarvis-live-answer.js +99 -0
- package/dist/commands/jarvis-render.js +1 -94
- package/dist/commands/jarvis-trace.js +30 -22
- package/dist/commands/jarvis-turn-report.js +124 -0
- package/dist/commands/jarvis-turn.js +108 -182
- package/dist/commands/jarvis.js +26 -17
- package/dist/commands/local-args-collector-setup.js +82 -84
- package/dist/commands/local-args-collector-work.js +37 -38
- package/dist/commands/local-args-tower-admin.js +155 -142
- package/dist/commands/local-args-tower-cal.js +54 -48
- package/dist/commands/local-args-tower-careers.js +73 -54
- package/dist/commands/local-args-tower-docs-msg.js +56 -188
- package/dist/commands/local-args-tower-github.js +26 -20
- package/dist/commands/local-args-tower-hits.js +32 -20
- package/dist/commands/local-args-tower-manager.js +85 -4
- package/dist/commands/local-args-tower-memory.js +139 -117
- package/dist/commands/local-args-tower-msg.js +149 -0
- package/dist/commands/local-args-tower-notes.js +346 -0
- package/dist/commands/local-args-tower-pages.js +100 -378
- package/dist/commands/local-args-tower-settings.js +28 -29
- package/dist/commands/local-args-tower-work.js +52 -33
- package/dist/commands/local-args.js +80 -129
- package/dist/commands/local-help-commands-tower-ops.js +15 -4
- package/dist/commands/local-help-commands-tower.js +11 -4
- package/dist/commands/local-help-commands.js +342 -341
- package/dist/commands/local-help.js +87 -83
- package/dist/commands/local-run-guards.js +100 -0
- package/dist/commands/local.js +137 -206
- package/dist/commands/login.js +13 -9
- package/dist/commands/logs.js +55 -38
- package/dist/commands/mail-attachments.js +44 -26
- package/dist/commands/mail.js +6 -4
- package/dist/commands/manager-render-home.js +208 -0
- package/dist/commands/manager-render.js +7 -3
- package/dist/commands/manager-requests.js +33 -0
- package/dist/commands/manager.js +39 -5
- package/dist/commands/memory-consolidation.js +18 -9
- package/dist/commands/memory-daemon-probe.js +37 -28
- package/dist/commands/memory-install-config.js +64 -60
- package/dist/commands/memory-install.js +21 -41
- package/dist/commands/memory-requeue.js +26 -19
- package/dist/commands/memory-rules.js +26 -18
- package/dist/commands/memory-search-print.js +26 -0
- package/dist/commands/memory-store.js +14 -30
- package/dist/commands/memory-verb-list.js +49 -48
- package/dist/commands/msg.js +51 -9
- package/dist/commands/notes-accounts.js +49 -32
- package/dist/commands/notes-sync-loop.js +115 -89
- package/dist/commands/notes-wisprflow-login.js +44 -23
- package/dist/commands/notes-writes.js +115 -100
- package/dist/commands/notes.js +33 -55
- package/dist/commands/onboard-flows.js +69 -51
- package/dist/commands/onboard.js +11 -9
- package/dist/commands/ops-render-memory.js +10 -7
- package/dist/commands/ops-render.js +33 -30
- package/dist/commands/ops.js +3 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/session-sync-outside-roots.js +40 -36
- package/dist/commands/session-sync-plan.js +14 -10
- package/dist/commands/session-sync-upload.js +24 -22
- package/dist/commands/session-sync.js +19 -8
- package/dist/commands/slack.js +25 -16
- package/dist/commands/sources-check.js +13 -7
- package/dist/commands/sources-discovery.js +25 -17
- package/dist/commands/sources-verdict.js +19 -18
- package/dist/commands/speed.js +23 -19
- package/dist/commands/start.js +20 -16
- package/dist/commands/sync-doctor.js +52 -48
- package/dist/commands/sync.js +22 -16
- package/dist/commands/usage-telemetry-toml.js +71 -45
- package/dist/commands/usage.js +91 -67
- package/dist/commands/workbook-render.js +10 -7
- package/dist/evidence-reconcile-client.js +75 -64
- package/dist/evidence-redelivery-plan.js +37 -34
- package/dist/evidence-redelivery.js +32 -29
- package/dist/evidence-transcripts-client.js +20 -20
- package/dist/evidence-upload-object.js +86 -89
- package/dist/evidence-upload-plan.js +94 -83
- package/dist/evidence-upload-terminal.js +45 -50
- package/dist/image-description-session.js +156 -159
- package/dist/local-state-pairing-code.js +39 -31
- package/dist/local-state-status.js +9 -2
- package/dist/onboarding-roots.js +5 -8
- package/dist/process-runner.js +8 -8
- package/dist/raw-evidence-gc.js +33 -38
- package/dist/raw-evidence-stuck-summary.js +7 -14
- package/dist/repo-identity.js +26 -23
- package/dist/scheduled-self-update.js +11 -15
- package/dist/second-install.js +3 -0
- package/dist/slim/codex-rules.js +23 -36
- package/dist/sync-lock.js +5 -15
- package/dist/tower-client.js +36 -53
- package/dist/tower-stream.js +7 -24
- package/dist/upload-agent-artifacts.js +58 -64
- package/dist/upload-envelope-build.js +60 -59
- package/dist/upload-evidence-delivery-reconcile.js +58 -56
- package/dist/upload-evidence-delivery-summary.js +32 -34
- package/dist/upload-ingest-receipt.js +20 -23
- package/dist/upload-session-reports-queue.js +4 -1
- package/dist/upload-session-reports-wire.js +64 -68
- package/dist/upload-sync.js +171 -157
- package/package.json +2 -2
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
|
|
22
22
|
const CAL_ACTIONS = new Set([
|
|
23
|
+
"event", "export", "conflicts", "calendar-change",
|
|
23
24
|
"today",
|
|
24
25
|
"week",
|
|
25
26
|
"next",
|
|
@@ -35,56 +36,59 @@ const CAL_ACTIONS = new Set([
|
|
|
35
36
|
"delete",
|
|
36
37
|
]);
|
|
37
38
|
/** Every verb that names ONE thing first — a calendar id, or (delete) an event id. */
|
|
38
|
-
const CAL_ACTIONS_NEEDING_A_SUBJECT = new Set(["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"]);
|
|
39
40
|
/** `sync-status [<calendar id>]` — every calendar, or one's. Ticket 10730. */
|
|
40
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
|
+
];
|
|
41
90
|
export function parseCalArgs(args) {
|
|
42
|
-
const values = parseNamedArgs(args, {
|
|
43
|
-
allowedFlags: [
|
|
44
|
-
"--home",
|
|
45
|
-
"--dashboard-url",
|
|
46
|
-
"--calendar",
|
|
47
|
-
"--account",
|
|
48
|
-
"--tz",
|
|
49
|
-
"--offset",
|
|
50
|
-
"--hours",
|
|
51
|
-
"--from",
|
|
52
|
-
"--to",
|
|
53
|
-
"--limit",
|
|
54
|
-
"--all",
|
|
55
|
-
"--name",
|
|
56
|
-
"--title",
|
|
57
|
-
"--at",
|
|
58
|
-
"--until",
|
|
59
|
-
"--location",
|
|
60
|
-
"--attendee",
|
|
61
|
-
"--all-day",
|
|
62
|
-
"--meet",
|
|
63
|
-
"--org-visible",
|
|
64
|
-
"--private",
|
|
65
|
-
"--full",
|
|
66
|
-
"--yes",
|
|
67
|
-
"--json",
|
|
68
|
-
],
|
|
69
|
-
valueFlags: [
|
|
70
|
-
"--home",
|
|
71
|
-
"--dashboard-url",
|
|
72
|
-
"--calendar",
|
|
73
|
-
"--account",
|
|
74
|
-
"--tz",
|
|
75
|
-
"--offset",
|
|
76
|
-
"--hours",
|
|
77
|
-
"--from",
|
|
78
|
-
"--to",
|
|
79
|
-
"--limit",
|
|
80
|
-
"--name",
|
|
81
|
-
"--title",
|
|
82
|
-
"--at",
|
|
83
|
-
"--until",
|
|
84
|
-
"--location",
|
|
85
|
-
"--attendee",
|
|
86
|
-
],
|
|
87
|
-
});
|
|
91
|
+
const values = parseNamedArgs(args, { allowedFlags: CAL_ALLOWED_FLAGS, valueFlags: CAL_VALUE_FLAGS });
|
|
88
92
|
const { action, rest } = readCalAction(values);
|
|
89
93
|
const { subject, query } = readCalSubjectOrQuery(action, rest);
|
|
90
94
|
const { title, startsAt, endsAt } = readCalCreateDetails(action, values);
|
|
@@ -100,7 +104,7 @@ function readCalAction(values) {
|
|
|
100
104
|
const first = values.positionals[0];
|
|
101
105
|
const action = (first === undefined ? "today" : first);
|
|
102
106
|
if (!CAL_ACTIONS.has(action)) {
|
|
103
|
-
throw new Error(`Unknown cal command: ${first}. Try today, week, next, find, calendars, add-ical, create, share, detach, sync, sync-status, or delete.`);
|
|
107
|
+
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.`);
|
|
104
108
|
}
|
|
105
109
|
return { action, rest: values.positionals.slice(first === undefined ? 0 : 1) };
|
|
106
110
|
}
|
|
@@ -170,6 +174,8 @@ function readCalOffset(values) {
|
|
|
170
174
|
function buildCalCommand(values, action, subject, query, title, startsAt, endsAt, offset, limit) {
|
|
171
175
|
return {
|
|
172
176
|
kind: "cal",
|
|
177
|
+
occurrenceStart: optionalNonEmpty(values.flags.get("--occurrence-start")),
|
|
178
|
+
color: optionalNonEmpty(values.flags.get("--color")),
|
|
173
179
|
action,
|
|
174
180
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
175
181
|
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
@@ -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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
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 {
|
|
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 {
|
|
@@ -243,146 +253,4 @@ function buildDocsCommand(values, action, docRef, docRefs, visibility, clearPare
|
|
|
243
253
|
json: values.booleans.has("--json"),
|
|
244
254
|
};
|
|
245
255
|
}
|
|
246
|
-
|
|
247
|
-
const MSG_ACTIONS_NEEDING_A_CHANNEL = new Set(["read", "send"]);
|
|
248
|
-
/**
|
|
249
|
-
* `--members a@x.test,b@y.test` — addresses only, split on commas, never
|
|
250
|
-
* fuzzy (BLI-3749). The door resolves each one exactly, so a typo comes back
|
|
251
|
-
* as `person_not_found` naming the address rather than as a stranger added
|
|
252
|
-
* to a private channel.
|
|
253
|
-
*/
|
|
254
|
-
function parseMemberEmails(raw) {
|
|
255
|
-
const value = optionalNonEmpty(raw);
|
|
256
|
-
if (!value)
|
|
257
|
-
return undefined;
|
|
258
|
-
const emails = value
|
|
259
|
-
.split(",")
|
|
260
|
-
.map((part) => part.trim())
|
|
261
|
-
.filter((part) => part !== "");
|
|
262
|
-
if (emails.length === 0)
|
|
263
|
-
throw new Error("msg create --members needs at least one email address.");
|
|
264
|
-
const notAnAddress = emails.find((email) => !email.includes("@"));
|
|
265
|
-
if (notAnAddress) {
|
|
266
|
-
throw new Error(`msg create --members takes email addresses; "${notAnAddress}" is not one.`);
|
|
267
|
-
}
|
|
268
|
-
return emails;
|
|
269
|
-
}
|
|
270
|
-
export function parseMsgArgs(args) {
|
|
271
|
-
const values = parseNamedArgs(args, {
|
|
272
|
-
allowedFlags: [
|
|
273
|
-
"--home",
|
|
274
|
-
"--dashboard-url",
|
|
275
|
-
"--channel",
|
|
276
|
-
"--thread",
|
|
277
|
-
"--limit",
|
|
278
|
-
"--private",
|
|
279
|
-
"--members",
|
|
280
|
-
"--description",
|
|
281
|
-
"--json",
|
|
282
|
-
],
|
|
283
|
-
valueFlags: ["--home", "--dashboard-url", "--channel", "--thread", "--limit", "--members", "--description"],
|
|
284
|
-
});
|
|
285
|
-
const { action, rest } = parseMsgAction(values.positionals);
|
|
286
|
-
const { channelName, dmEmail } = parseMsgCreateTarget(action, rest);
|
|
287
|
-
const { isPrivate, memberEmails, description } = parseMsgCreateFlags(action, values);
|
|
288
|
-
const { channelRef, threadId, limit } = parseMsgReadTarget(action, rest, values);
|
|
289
|
-
return buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit);
|
|
290
|
-
}
|
|
291
|
-
function parseMsgAction(positionals) {
|
|
292
|
-
const first = positionals[0];
|
|
293
|
-
const action = (first === undefined ? "channels" : first);
|
|
294
|
-
if (!MSG_ACTIONS.has(action)) {
|
|
295
|
-
throw new Error(`Unknown msg command: ${first}. Try channels, read, send, thread, create, or dm.`);
|
|
296
|
-
}
|
|
297
|
-
return { action, rest: positionals.slice(first === undefined ? 0 : 1) };
|
|
298
|
-
}
|
|
299
|
-
function parseMsgCreateTarget(action, rest) {
|
|
300
|
-
let channelName;
|
|
301
|
-
let dmEmail;
|
|
302
|
-
if (action === "create") {
|
|
303
|
-
channelName = optionalNonEmpty(rest[0]);
|
|
304
|
-
if (!channelName)
|
|
305
|
-
throw new Error("msg create needs a channel name, e.g. `cockpit msg create general`.");
|
|
306
|
-
if (rest.length > 1)
|
|
307
|
-
throw new Error(`msg create takes one channel name, not ${rest.length}.`);
|
|
308
|
-
// `#general` is how a person says it and how `read`/`send` accept it, so
|
|
309
|
-
// the leading # is dropped here rather than becoming part of the name.
|
|
310
|
-
if (channelName.startsWith("#"))
|
|
311
|
-
channelName = channelName.slice(1);
|
|
312
|
-
if (channelName === "")
|
|
313
|
-
throw new Error("msg create needs a channel name, e.g. `cockpit msg create general`.");
|
|
314
|
-
}
|
|
315
|
-
else if (action === "dm") {
|
|
316
|
-
dmEmail = optionalNonEmpty(rest[0]);
|
|
317
|
-
if (!dmEmail)
|
|
318
|
-
throw new Error("msg dm needs the person's email address, e.g. `cockpit msg dm ada@example.com`.");
|
|
319
|
-
if (rest.length > 1)
|
|
320
|
-
throw new Error(`msg dm takes one email address, not ${rest.length}.`);
|
|
321
|
-
if (!dmEmail.includes("@"))
|
|
322
|
-
throw new Error(`msg dm takes an email address; "${dmEmail}" is not one.`);
|
|
323
|
-
}
|
|
324
|
-
return { channelName, dmEmail };
|
|
325
|
-
}
|
|
326
|
-
function parseMsgCreateFlags(action, values) {
|
|
327
|
-
const isPrivate = values.booleans.has("--private");
|
|
328
|
-
if (isPrivate && action !== "create") {
|
|
329
|
-
throw new Error("--private belongs to `cockpit msg create`.");
|
|
330
|
-
}
|
|
331
|
-
const memberEmails = parseMemberEmails(values.flags.get("--members"));
|
|
332
|
-
if (memberEmails && action !== "create") {
|
|
333
|
-
throw new Error("--members belongs to `cockpit msg create`.");
|
|
334
|
-
}
|
|
335
|
-
const description = optionalNonEmpty(values.flags.get("--description"));
|
|
336
|
-
if (description && action !== "create") {
|
|
337
|
-
throw new Error("--description belongs to `cockpit msg create`.");
|
|
338
|
-
}
|
|
339
|
-
return { isPrivate, memberEmails, description };
|
|
340
|
-
}
|
|
341
|
-
function parseMsgReadTarget(action, rest, values) {
|
|
342
|
-
let channelRef;
|
|
343
|
-
if (MSG_ACTIONS_NEEDING_A_CHANNEL.has(action)) {
|
|
344
|
-
channelRef = optionalNonEmpty(rest[0]);
|
|
345
|
-
if (!channelRef)
|
|
346
|
-
throw new Error(`msg ${action} needs a channel id or name.`);
|
|
347
|
-
if (rest.length > 1)
|
|
348
|
-
throw new Error(`msg ${action} takes one channel, not ${rest.length}.`);
|
|
349
|
-
}
|
|
350
|
-
else if (action === "thread") {
|
|
351
|
-
// `thread <id> --channel <channel>` — the channel is a flag here because
|
|
352
|
-
// the positional names the THREAD, not the channel (`--channel` below).
|
|
353
|
-
if (rest.length > 1)
|
|
354
|
-
throw new Error(`msg thread takes one thread id, not ${rest.length}.`);
|
|
355
|
-
}
|
|
356
|
-
else if (action !== "create" && action !== "dm" && rest.length > 0) {
|
|
357
|
-
throw new Error(`msg ${action} does not take "${rest[0]}".`);
|
|
358
|
-
}
|
|
359
|
-
const threadFlag = optionalNonEmpty(values.flags.get("--thread"));
|
|
360
|
-
const threadId = action === "thread" ? optionalNonEmpty(rest[0]) : threadFlag;
|
|
361
|
-
if (action === "thread" && !threadId)
|
|
362
|
-
throw new Error("msg thread needs a thread id.");
|
|
363
|
-
const channelFlag = optionalNonEmpty(values.flags.get("--channel"));
|
|
364
|
-
if (action === "thread")
|
|
365
|
-
channelRef = channelFlag;
|
|
366
|
-
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
367
|
-
if (limit !== undefined && action !== "read" && action !== "thread") {
|
|
368
|
-
throw new Error("--limit belongs to `cockpit msg read` or `cockpit msg thread`.");
|
|
369
|
-
}
|
|
370
|
-
return { channelRef, threadId, limit };
|
|
371
|
-
}
|
|
372
|
-
function buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit) {
|
|
373
|
-
return {
|
|
374
|
-
kind: "msg",
|
|
375
|
-
action,
|
|
376
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
377
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
378
|
-
...(channelRef ? { channelRef } : {}),
|
|
379
|
-
...(channelName ? { channelName } : {}),
|
|
380
|
-
...(dmEmail ? { dmEmail } : {}),
|
|
381
|
-
...(memberEmails ? { memberEmails } : {}),
|
|
382
|
-
...(description ? { description } : {}),
|
|
383
|
-
isPrivate,
|
|
384
|
-
...(threadId ? { threadId } : {}),
|
|
385
|
-
...(limit === undefined ? {} : { limit }),
|
|
386
|
-
json: values.booleans.has("--json"),
|
|
387
|
-
};
|
|
388
|
-
}
|
|
256
|
+
export { parseMsgArgs } from "./local-args-tower-msg.js";
|
|
@@ -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
|
-
|
|
37
|
-
|
|
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
|
}
|