@bli-cockpit/cli 0.2.54 → 0.2.56
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 +247 -0
- package/dist/adapters/attribution-core-paths.js +182 -0
- package/dist/adapters/attribution-core-score.js +159 -0
- package/dist/adapters/attribution-core-types.js +13 -0
- package/dist/adapters/attribution-core.js +13 -565
- package/dist/adapters/claude-attribution-discovery.js +186 -0
- package/dist/adapters/claude-attribution-score.js +204 -0
- package/dist/adapters/claude-attribution-signals.js +180 -0
- package/dist/adapters/claude-attribution-types.js +25 -0
- package/dist/adapters/claude-attribution.js +14 -569
- package/dist/commands/doctor-access.js +129 -0
- package/dist/commands/doctor-pipeline.js +326 -0
- package/dist/commands/doctor-registration.js +105 -0
- package/dist/commands/doctor-report.js +111 -0
- package/dist/commands/doctor-update.js +120 -0
- package/dist/commands/doctor.js +8 -753
- package/dist/commands/heartbeat.js +8 -0
- package/dist/commands/jarvis-contracts.js +8 -0
- package/dist/commands/jarvis-render.js +413 -0
- package/dist/commands/jarvis-turn.js +305 -0
- package/dist/commands/jarvis.js +23 -698
- package/dist/commands/local-args-collector-setup.js +250 -0
- package/dist/commands/local-args-collector-status.js +227 -0
- package/dist/commands/local-args-collector-work.js +175 -0
- package/dist/commands/local-args-collector.js +19 -624
- package/dist/commands/local-args-tower-admin.js +456 -0
- package/dist/commands/local-args-tower-chat.js +194 -0
- package/dist/commands/local-args-tower-pages.js +314 -0
- package/dist/commands/local-args-tower.js +13 -880
- package/dist/commands/local-help.js +10 -2
- package/dist/commands/onboard-completion.js +136 -0
- package/dist/commands/onboard-flows.js +165 -0
- package/dist/commands/onboard-setup.js +102 -0
- package/dist/commands/onboard.js +5 -392
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/session-sync-counters.js +55 -0
- package/dist/commands/session-sync-health.js +8 -1
- package/dist/commands/session-sync-plan.js +47 -7
- package/dist/commands/session-sync-scan.js +4 -4
- package/dist/commands/session-sync.js +6 -0
- package/dist/commands/settings-render.js +27 -0
- package/dist/commands/sync-followups.js +5 -1
- package/dist/commands/sync.js +5 -1
- package/dist/commands/team-device-reasons.js +16 -0
- package/dist/commands/team.js +87 -7
- package/dist/evidence-upload-client.js +14 -763
- package/dist/evidence-upload-object.js +181 -0
- package/dist/evidence-upload-plan.js +233 -0
- package/dist/evidence-upload-terminal.js +309 -0
- package/dist/evidence-upload-transport.js +104 -0
- package/dist/spool/local-spool-io.js +122 -0
- package/dist/spool/local-spool-mutations.js +174 -0
- package/dist/spool/local-spool-parse.js +143 -0
- package/dist/spool/local-spool-types.js +22 -0
- package/dist/spool/local-spool.js +20 -426
- package/dist/upload-evidence-delivery-offer.js +144 -0
- package/dist/upload-evidence-delivery-reconcile.js +134 -0
- package/dist/upload-evidence-delivery-summary.js +205 -0
- package/dist/upload-evidence-delivery.js +12 -482
- package/package.json +3 -3
|
@@ -6,886 +6,19 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Split out of commands/local-args.ts (BLI-3578) alongside its sibling
|
|
8
8
|
* local-args-collector.ts, which parses the commands that act on THIS machine.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* constants a parser publishes (`SCOUT_MIN_PREFIX_LENGTH`,
|
|
13
|
-
* `SLACK_WORKSPACE_KEYS`, `WORKBOOK_MIN_WIDTH`) and the `NotesAction` type are
|
|
14
|
-
* re-exported from `./local-args.js`, the address they have always had.
|
|
15
|
-
*/
|
|
16
|
-
import { optionalEmail, optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
|
|
17
|
-
export function parseJarvisArgs(args) {
|
|
18
|
-
const values = parseNamedArgs(args, {
|
|
19
|
-
allowedFlags: [
|
|
20
|
-
"--home",
|
|
21
|
-
"--dashboard-url",
|
|
22
|
-
"--prompt",
|
|
23
|
-
"--as",
|
|
24
|
-
"--thread",
|
|
25
|
-
"--model",
|
|
26
|
-
"--image",
|
|
27
|
-
"--file",
|
|
28
|
-
"--no-stream",
|
|
29
|
-
// BLI-3484: which day's page this turn is about.
|
|
30
|
-
"--date",
|
|
31
|
-
// BLI-3458: reading back what was already said, rather than saying
|
|
32
|
-
// something new. Neither takes a turn or reaches the model.
|
|
33
|
-
"--threads",
|
|
34
|
-
"--history",
|
|
35
|
-
// BLI-3560: reading back what a turn DID — the step tree of one turn.
|
|
36
|
-
"--trace",
|
|
37
|
-
"--limit",
|
|
38
|
-
"--json",
|
|
39
|
-
],
|
|
40
|
-
valueFlags: [
|
|
41
|
-
"--home",
|
|
42
|
-
"--dashboard-url",
|
|
43
|
-
"--prompt",
|
|
44
|
-
"--as",
|
|
45
|
-
"--thread",
|
|
46
|
-
"--model",
|
|
47
|
-
"--trace",
|
|
48
|
-
"--image",
|
|
49
|
-
"--file",
|
|
50
|
-
"--date",
|
|
51
|
-
"--limit",
|
|
52
|
-
],
|
|
53
|
-
});
|
|
54
|
-
const flaggedPrompt = optionalNonEmpty(values.flags.get("--prompt"));
|
|
55
|
-
const positionalPrompt = optionalNonEmpty(values.positionals.join(" "));
|
|
56
|
-
if (flaggedPrompt && positionalPrompt) {
|
|
57
|
-
throw new Error("jarvis accepts either --prompt or positional text, not both.");
|
|
58
|
-
}
|
|
59
|
-
const thread = optionalNonEmpty(values.flags.get("--thread")) ?? "main";
|
|
60
|
-
if (!/^[A-Za-z0-9_-]{1,40}$/.test(thread)) {
|
|
61
|
-
throw new Error("jarvis --thread must use 1 to 40 letters, numbers, underscores, or hyphens.");
|
|
62
|
-
}
|
|
63
|
-
// BLI-3414: `--file` is a plain alias for `--image` — same flag, whichever
|
|
64
|
-
// word a person reaches for first.
|
|
65
|
-
const image = optionalNonEmpty(values.flags.get("--image"));
|
|
66
|
-
const file = optionalNonEmpty(values.flags.get("--file"));
|
|
67
|
-
if (image && file) {
|
|
68
|
-
throw new Error("jarvis accepts either --image or --file, not both — they are the same flag.");
|
|
69
|
-
}
|
|
70
|
-
// BLI-3458. Reading history and asking a question are different acts, and a
|
|
71
|
-
// command that quietly did one while you asked for the other would be worse
|
|
72
|
-
// than a refusal — `--threads` with a question would silently drop the
|
|
73
|
-
// question.
|
|
74
|
-
const threads = values.booleans.has("--threads");
|
|
75
|
-
const history = values.booleans.has("--history");
|
|
76
|
-
if (threads && history) {
|
|
77
|
-
throw new Error("jarvis --threads lists every thread; --history replays one. Pass one, not both.");
|
|
78
|
-
}
|
|
79
|
-
if ((threads || history) && (flaggedPrompt || positionalPrompt)) {
|
|
80
|
-
throw new Error("jarvis --threads and --history read back what was already said; they do not take a question.");
|
|
81
|
-
}
|
|
82
|
-
if ((threads || history) && image) {
|
|
83
|
-
throw new Error("jarvis --threads and --history do not take an attachment.");
|
|
84
|
-
}
|
|
85
|
-
// BLI-3484. `--date` binds the page that was live on one of the subject's
|
|
86
|
-
// days, so a turn can be about Sunday's page. Refused on the two reading
|
|
87
|
-
// commands for the same reason an attachment is: they replay what was said
|
|
88
|
-
// and bind no page at all.
|
|
89
|
-
const date = optionalNonEmpty(values.flags.get("--date"));
|
|
90
|
-
if ((threads || history) && date) {
|
|
91
|
-
throw new Error("jarvis --threads and --history replay what was said; they bind no page.");
|
|
92
|
-
}
|
|
93
|
-
// BLI-3560. `--trace` reads back what a turn DID, the same class of act as
|
|
94
|
-
// `--threads` and `--history`, and refused alongside them for the same
|
|
95
|
-
// reason: a command that quietly asked a question while you asked to see one
|
|
96
|
-
// is worse than a refusal.
|
|
97
|
-
const trace = optionalNonEmpty(values.flags.get("--trace"));
|
|
98
|
-
if (trace && (threads || history)) {
|
|
99
|
-
throw new Error("jarvis --trace shows one turn's steps; --threads and --history replay what was said. Pass one.");
|
|
100
|
-
}
|
|
101
|
-
if (trace && (flaggedPrompt || positionalPrompt)) {
|
|
102
|
-
throw new Error("jarvis --trace shows a turn that already ran; it does not take a question.");
|
|
103
|
-
}
|
|
104
|
-
if (trace && image) {
|
|
105
|
-
throw new Error("jarvis --trace does not take an attachment.");
|
|
106
|
-
}
|
|
107
|
-
if (trace !== undefined && !/^(last|[A-Za-z0-9_-]{1,64})$/.test(trace)) {
|
|
108
|
-
throw new Error("jarvis --trace takes `last` or a trace id (up to 64 letters, numbers, underscores or hyphens).");
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
kind: "jarvis",
|
|
112
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
113
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
114
|
-
prompt: flaggedPrompt ?? positionalPrompt,
|
|
115
|
-
subject: optionalNonEmpty(values.flags.get("--as")),
|
|
116
|
-
thread,
|
|
117
|
-
threads,
|
|
118
|
-
history,
|
|
119
|
-
...(trace ? { trace } : {}),
|
|
120
|
-
limit: optionalPositiveInteger(values.flags.get("--limit"), "--limit"),
|
|
121
|
-
// BLI-3381: no client-side allowlist — the dashboard forwards this key
|
|
122
|
-
// to the inference server's own allowlist and relays its refusal.
|
|
123
|
-
model: optionalNonEmpty(values.flags.get("--model")),
|
|
124
|
-
...(date ? { date } : {}),
|
|
125
|
-
imagePath: image ?? file,
|
|
126
|
-
// BLI-3457: streaming is on unless a caller opts out. A dashboard that
|
|
127
|
-
// does not stream yet still answers plain JSON, so this flag is for
|
|
128
|
-
// callers that want the single-body shape on purpose, not a compat knob.
|
|
129
|
-
stream: !values.booleans.has("--no-stream"),
|
|
130
|
-
json: values.booleans.has("--json"),
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
/** The shortest prefix `cockpit scout start` will resolve. Below this, ids collide. */
|
|
134
|
-
export const SCOUT_MIN_PREFIX_LENGTH = 6;
|
|
135
|
-
/**
|
|
136
|
-
* `cockpit scout [--days <n>]` reads the board; `cockpit scout start|dismiss|undo
|
|
137
|
-
* <id>` moves one card. Positional shape modelled on `autostart`: an optional
|
|
138
|
-
* action word first, then what it acts on.
|
|
139
|
-
*/
|
|
140
|
-
export function parseScoutArgs(args) {
|
|
141
|
-
const values = parseNamedArgs(args, {
|
|
142
|
-
allowedFlags: ["--home", "--dashboard-url", "--days", "--json"],
|
|
143
|
-
valueFlags: ["--home", "--dashboard-url", "--days"],
|
|
144
|
-
});
|
|
145
|
-
if (values.positionals.length > 2) {
|
|
146
|
-
throw new Error("scout accepts at most an action (start|dismiss|undo) and one experiment id.");
|
|
147
|
-
}
|
|
148
|
-
const [rawAction, rawRef] = values.positionals;
|
|
149
|
-
const base = {
|
|
150
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
151
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
152
|
-
days: optionalPositiveInteger(values.flags.get("--days"), "--days"),
|
|
153
|
-
json: values.booleans.has("--json"),
|
|
154
|
-
};
|
|
155
|
-
if (!rawAction)
|
|
156
|
-
return { kind: "scout", action: "board", ...base };
|
|
157
|
-
if (rawAction !== "start" && rawAction !== "dismiss" && rawAction !== "undo") {
|
|
158
|
-
throw new Error("scout action must be start, dismiss, or undo.");
|
|
159
|
-
}
|
|
160
|
-
const experimentRef = optionalNonEmpty(rawRef);
|
|
161
|
-
if (!experimentRef) {
|
|
162
|
-
throw new Error(`scout ${rawAction} needs an experiment id from the board.`);
|
|
163
|
-
}
|
|
164
|
-
if (experimentRef.length < SCOUT_MIN_PREFIX_LENGTH) {
|
|
165
|
-
throw new Error(`scout ${rawAction} needs a full experiment id or a prefix of at least ${SCOUT_MIN_PREFIX_LENGTH} characters.`);
|
|
166
|
-
}
|
|
167
|
-
return { kind: "scout", action: rawAction, experimentRef, ...base };
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* `cockpit ops status [--job <id>] [--skips]` and
|
|
171
|
-
* `cockpit ops recompile --person <p> [--dry-run]` (BLI-3462).
|
|
172
|
-
*
|
|
173
|
-
* There is deliberately **no `--cadence`** on `recompile`. The compile path
|
|
174
|
-
* behind it (`writePageAgain` → `publishPage`) takes no cadence and writes a
|
|
175
|
-
* daily; accepting the flag and dropping it is the silent breakage BLI-2490
|
|
176
|
-
* forbids, and threading cadence through that path is its own change. Ask for a
|
|
177
|
-
* weekly or a monthly with the compile script, which does support it.
|
|
178
|
-
*/
|
|
179
|
-
export function parseOpsArgs(args) {
|
|
180
|
-
const values = parseNamedArgs(args, {
|
|
181
|
-
allowedFlags: [
|
|
182
|
-
"--home",
|
|
183
|
-
"--dashboard-url",
|
|
184
|
-
"--job",
|
|
185
|
-
"--skips",
|
|
186
|
-
"--person",
|
|
187
|
-
"--dry-run",
|
|
188
|
-
"--json",
|
|
189
|
-
],
|
|
190
|
-
valueFlags: ["--home", "--dashboard-url", "--job", "--person"],
|
|
191
|
-
});
|
|
192
|
-
if (values.positionals.length > 1) {
|
|
193
|
-
throw new Error("ops accepts one action: status or recompile.");
|
|
194
|
-
}
|
|
195
|
-
const rawAction = values.positionals[0] ?? "status";
|
|
196
|
-
if (rawAction !== "status" && rawAction !== "recompile") {
|
|
197
|
-
throw new Error("ops action must be status or recompile.");
|
|
198
|
-
}
|
|
199
|
-
const base = {
|
|
200
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
201
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
202
|
-
json: values.booleans.has("--json"),
|
|
203
|
-
};
|
|
204
|
-
if (rawAction === "status") {
|
|
205
|
-
return {
|
|
206
|
-
kind: "ops",
|
|
207
|
-
action: "status",
|
|
208
|
-
job: optionalNonEmpty(values.flags.get("--job")),
|
|
209
|
-
skips: values.booleans.has("--skips"),
|
|
210
|
-
...base,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
const person = optionalNonEmpty(values.flags.get("--person"));
|
|
214
|
-
if (!person) {
|
|
215
|
-
throw new Error("ops recompile needs --person <email|name|id>. Nothing is recompiled by default, on purpose.");
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
kind: "ops",
|
|
219
|
-
action: "recompile",
|
|
220
|
-
person,
|
|
221
|
-
dryRun: values.booleans.has("--dry-run"),
|
|
222
|
-
...base,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
/** The workspaces Cockpit collects. A typo is refused here rather than searched for. */
|
|
226
|
-
export const SLACK_WORKSPACE_KEYS = ["bli", "blue_pearl"];
|
|
227
|
-
/**
|
|
228
|
-
* `cockpit slack coverage [--workspace <key>] [--stale-only]` and
|
|
229
|
-
* `cockpit slack read [--person|--channel|--query|--since|--until|--limit]`
|
|
230
|
-
* (BLI-3462).
|
|
231
|
-
*/
|
|
232
|
-
export function parseSlackArgs(args) {
|
|
233
|
-
const values = parseNamedArgs(args, {
|
|
234
|
-
allowedFlags: [
|
|
235
|
-
"--home",
|
|
236
|
-
"--dashboard-url",
|
|
237
|
-
"--workspace",
|
|
238
|
-
"--stale-only",
|
|
239
|
-
"--person",
|
|
240
|
-
"--channel",
|
|
241
|
-
"--query",
|
|
242
|
-
"--since",
|
|
243
|
-
"--until",
|
|
244
|
-
"--limit",
|
|
245
|
-
"--json",
|
|
246
|
-
],
|
|
247
|
-
valueFlags: [
|
|
248
|
-
"--home",
|
|
249
|
-
"--dashboard-url",
|
|
250
|
-
"--workspace",
|
|
251
|
-
"--person",
|
|
252
|
-
"--channel",
|
|
253
|
-
"--query",
|
|
254
|
-
"--since",
|
|
255
|
-
"--until",
|
|
256
|
-
"--limit",
|
|
257
|
-
],
|
|
258
|
-
});
|
|
259
|
-
if (values.positionals.length > 1) {
|
|
260
|
-
throw new Error("slack accepts one action: coverage or read.");
|
|
261
|
-
}
|
|
262
|
-
const rawAction = values.positionals[0] ?? "coverage";
|
|
263
|
-
if (rawAction !== "coverage" && rawAction !== "read") {
|
|
264
|
-
throw new Error("slack action must be coverage or read.");
|
|
265
|
-
}
|
|
266
|
-
const base = {
|
|
267
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
268
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
269
|
-
json: values.booleans.has("--json"),
|
|
270
|
-
};
|
|
271
|
-
if (rawAction === "coverage") {
|
|
272
|
-
// `--workspace` here is a Slack workspace key, NOT the collection-root
|
|
273
|
-
// `--workspace <path>` every setup command takes. Named at the point of use
|
|
274
|
-
// so the two cannot be confused silently.
|
|
275
|
-
const workspace = optionalNonEmpty(values.flags.get("--workspace"));
|
|
276
|
-
if (workspace && !SLACK_WORKSPACE_KEYS.includes(workspace)) {
|
|
277
|
-
throw new Error(`slack --workspace must be one of ${SLACK_WORKSPACE_KEYS.join(", ")}; got ${workspace}.`);
|
|
278
|
-
}
|
|
279
|
-
return {
|
|
280
|
-
kind: "slack",
|
|
281
|
-
action: "coverage",
|
|
282
|
-
workspace,
|
|
283
|
-
staleOnly: values.booleans.has("--stale-only"),
|
|
284
|
-
...base,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
const person = optionalNonEmpty(values.flags.get("--person"));
|
|
288
|
-
const channel = optionalNonEmpty(values.flags.get("--channel"));
|
|
289
|
-
const query = optionalNonEmpty(values.flags.get("--query"));
|
|
290
|
-
if (!person && !channel && !query) {
|
|
291
|
-
throw new Error("slack read needs at least one of --person, --channel or --query. An unfiltered dump of every " +
|
|
292
|
-
"message is not something this command offers.");
|
|
293
|
-
}
|
|
294
|
-
return {
|
|
295
|
-
kind: "slack",
|
|
296
|
-
action: "read",
|
|
297
|
-
person,
|
|
298
|
-
channel,
|
|
299
|
-
query,
|
|
300
|
-
since: optionalNonEmpty(values.flags.get("--since")),
|
|
301
|
-
until: optionalNonEmpty(values.flags.get("--until")),
|
|
302
|
-
limit: optionalPositiveInteger(values.flags.get("--limit"), "--limit"),
|
|
303
|
-
...base,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
/** The narrowest width worth wrapping to; below it every line is one word. */
|
|
307
|
-
export const WORKBOOK_MIN_WIDTH = 20;
|
|
308
|
-
/**
|
|
309
|
-
* `cockpit workbook` with nothing lists the library, one positional lists a
|
|
310
|
-
* project's shelf, two read a document.
|
|
311
|
-
*/
|
|
312
|
-
export function parseWorkbookArgs(args) {
|
|
313
|
-
const values = parseNamedArgs(args, {
|
|
314
|
-
allowedFlags: [
|
|
315
|
-
"--home",
|
|
316
|
-
"--dashboard-url",
|
|
317
|
-
"--section",
|
|
318
|
-
"--width",
|
|
319
|
-
"--markdown",
|
|
320
|
-
"--json",
|
|
321
|
-
],
|
|
322
|
-
valueFlags: ["--home", "--dashboard-url", "--section", "--width"],
|
|
323
|
-
});
|
|
324
|
-
if (values.positionals.length > 2) {
|
|
325
|
-
throw new Error("workbook accepts at most a project and a document.");
|
|
326
|
-
}
|
|
327
|
-
const project = optionalNonEmpty(values.positionals[0]);
|
|
328
|
-
const doc = optionalNonEmpty(values.positionals[1]);
|
|
329
|
-
const section = optionalNonEmpty(values.flags.get("--section"));
|
|
330
|
-
const markdown = values.booleans.has("--markdown");
|
|
331
|
-
if (!doc && (section || markdown)) {
|
|
332
|
-
throw new Error("workbook --section and --markdown need a project and a document, e.g. `cockpit workbook tower workbook`.");
|
|
333
|
-
}
|
|
334
|
-
const width = optionalPositiveInteger(values.flags.get("--width"), "--width");
|
|
335
|
-
if (width !== undefined && width < WORKBOOK_MIN_WIDTH) {
|
|
336
|
-
throw new Error(`workbook --width must be at least ${WORKBOOK_MIN_WIDTH}.`);
|
|
337
|
-
}
|
|
338
|
-
return {
|
|
339
|
-
kind: "workbook",
|
|
340
|
-
project,
|
|
341
|
-
doc,
|
|
342
|
-
section,
|
|
343
|
-
markdown,
|
|
344
|
-
width,
|
|
345
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
346
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
347
|
-
json: values.booleans.has("--json"),
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* `cockpit brief` (BLI-3458) — the TODAY page, in the terminal.
|
|
352
|
-
*
|
|
353
|
-
* `--for` is the terminal's `?p=`, and it follows the same rule the website
|
|
354
|
-
* does: the dashboard resolves it against the roster and the database (or the
|
|
355
|
-
* route's own mirror of the database's rule) decides whether the page opens.
|
|
356
|
-
* Nothing is decided here.
|
|
9
|
+
* This file itself became a table of contents in BLI-3636, split by COMMAND
|
|
10
|
+
* FAMILY into three siblings, each moved verbatim (same flags, same defaults,
|
|
11
|
+
* same refusal sentences):
|
|
357
12
|
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
|
|
364
|
-
export function parseBriefArgs(args) {
|
|
365
|
-
const values = parseNamedArgs(args, {
|
|
366
|
-
allowedFlags: [
|
|
367
|
-
"--home",
|
|
368
|
-
"--dashboard-url",
|
|
369
|
-
"--for",
|
|
370
|
-
"--as",
|
|
371
|
-
"--who",
|
|
372
|
-
"--version",
|
|
373
|
-
"--date",
|
|
374
|
-
"--delta",
|
|
375
|
-
"--against",
|
|
376
|
-
"--days",
|
|
377
|
-
"--tldr",
|
|
378
|
-
"--full",
|
|
379
|
-
"--versions",
|
|
380
|
-
"--claims",
|
|
381
|
-
"--render",
|
|
382
|
-
"--reason",
|
|
383
|
-
"--wait",
|
|
384
|
-
"--no-wait",
|
|
385
|
-
"--json",
|
|
386
|
-
],
|
|
387
|
-
valueFlags: [
|
|
388
|
-
"--home",
|
|
389
|
-
"--dashboard-url",
|
|
390
|
-
"--for",
|
|
391
|
-
"--as",
|
|
392
|
-
"--who",
|
|
393
|
-
"--version",
|
|
394
|
-
"--date",
|
|
395
|
-
"--against",
|
|
396
|
-
"--days",
|
|
397
|
-
"--reason",
|
|
398
|
-
],
|
|
399
|
-
});
|
|
400
|
-
// Bare `cockpit brief` reads the page, which is what somebody typing it almost
|
|
401
|
-
// always wants — the same shape `cockpit notes` and `cockpit scout` have.
|
|
402
|
-
// `status` (BLI-3462) answers why a brief was or was not delivered.
|
|
403
|
-
const first = values.positionals[0];
|
|
404
|
-
const action = (first === undefined ? "read" : first);
|
|
405
|
-
if (!["read", "edit", "rewrite", "history", "status"].includes(action)) {
|
|
406
|
-
throw new Error(`Unknown brief command: ${first}. Try edit, rewrite, history or status, or nothing to read it.`);
|
|
407
|
-
}
|
|
408
|
-
if (values.positionals.length > (first === undefined ? 0 : 1)) {
|
|
409
|
-
throw new Error(`brief ${action} does not take "${values.positionals[1]}".`);
|
|
410
|
-
}
|
|
411
|
-
const tldr = values.booleans.has("--tldr");
|
|
412
|
-
const full = values.booleans.has("--full");
|
|
413
|
-
if (tldr && full) {
|
|
414
|
-
throw new Error("brief accepts either --tldr or --full, not both.");
|
|
415
|
-
}
|
|
416
|
-
const wait = values.booleans.has("--wait");
|
|
417
|
-
const noWait = values.booleans.has("--no-wait");
|
|
418
|
-
if (wait && noWait) {
|
|
419
|
-
throw new Error("brief rewrite accepts either --wait or --no-wait, not both.");
|
|
420
|
-
}
|
|
421
|
-
const reason = optionalNonEmpty(values.flags.get("--reason"));
|
|
422
|
-
if (reason && reason.length > 280) {
|
|
423
|
-
// The server's own ceiling (`REASON_MAX`). Said here so the person is told
|
|
424
|
-
// before the page is opened rather than after they have finished editing.
|
|
425
|
-
throw new Error("A reason is limited to 280 characters.");
|
|
426
|
-
}
|
|
427
|
-
if (action !== "edit" && reason) {
|
|
428
|
-
throw new Error("--reason belongs to `cockpit brief edit`.");
|
|
429
|
-
}
|
|
430
|
-
if (action !== "rewrite" && (wait || noWait)) {
|
|
431
|
-
throw new Error("--wait and --no-wait belong to `cockpit brief rewrite`.");
|
|
432
|
-
}
|
|
433
|
-
// ── Reading a past day (BLI-3484) ────────────────────────────────────────
|
|
434
|
-
const date = optionalNonEmpty(values.flags.get("--date"));
|
|
435
|
-
const against = optionalNonEmpty(values.flags.get("--against"));
|
|
436
|
-
const delta = values.booleans.has("--delta");
|
|
437
|
-
const days = optionalPositiveInteger(values.flags.get("--days"), "--days");
|
|
438
|
-
if (date && action !== "read") {
|
|
439
|
-
throw new Error("--date belongs to `cockpit brief` on its own — it reads one day's page.");
|
|
440
|
-
}
|
|
441
|
-
if (delta && action !== "read") {
|
|
442
|
-
throw new Error("--delta belongs to `cockpit brief` on its own.");
|
|
443
|
-
}
|
|
444
|
-
if (against && !delta) {
|
|
445
|
-
// Said rather than silently ignored: somebody who typed `--against` asked
|
|
446
|
-
// for a comparison, and running the plain read would answer a different
|
|
447
|
-
// question without saying so.
|
|
448
|
-
throw new Error("--against needs --delta: it names the day to compare against.");
|
|
449
|
-
}
|
|
450
|
-
if (days !== undefined && action !== "history") {
|
|
451
|
-
throw new Error("--days belongs to `cockpit brief history`.");
|
|
452
|
-
}
|
|
453
|
-
if (action === "history" && (values.booleans.has("--tldr") || values.booleans.has("--claims"))) {
|
|
454
|
-
throw new Error("brief history lists days; --tldr and --claims belong to reading a page.");
|
|
455
|
-
}
|
|
456
|
-
// `--as` is accepted as an alias so the two conversational commands read the
|
|
457
|
-
// same way; `cockpit jarvis --as <person>` has meant this since BLI-3380.
|
|
458
|
-
// `--who` is the third spelling, and it exists because `cockpit brief status
|
|
459
|
-
// --who <person>` is the terminal echo of `jarvis:dm --who`.
|
|
460
|
-
const forPerson = optionalNonEmpty(values.flags.get("--for"));
|
|
461
|
-
const asPerson = optionalNonEmpty(values.flags.get("--as"));
|
|
462
|
-
const whoPerson = optionalNonEmpty(values.flags.get("--who"));
|
|
463
|
-
const named = [forPerson, asPerson, whoPerson].filter(Boolean);
|
|
464
|
-
if (new Set(named).size > 1) {
|
|
465
|
-
throw new Error("brief --for, --as and --who must name the same person.");
|
|
466
|
-
}
|
|
467
|
-
const render = values.booleans.has("--render");
|
|
468
|
-
if (render && action !== "status") {
|
|
469
|
-
throw new Error("brief --render belongs to `cockpit brief status`; the page is printed by default.");
|
|
470
|
-
}
|
|
471
|
-
return {
|
|
472
|
-
kind: "brief",
|
|
473
|
-
action,
|
|
474
|
-
render,
|
|
475
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
476
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
477
|
-
subject: forPerson ?? asPerson ?? whoPerson,
|
|
478
|
-
version: optionalNonEmpty(values.flags.get("--version")),
|
|
479
|
-
...(date ? { date } : {}),
|
|
480
|
-
...(delta ? { delta: true } : {}),
|
|
481
|
-
...(against ? { against } : {}),
|
|
482
|
-
...(days !== undefined ? { days } : {}),
|
|
483
|
-
tldr,
|
|
484
|
-
versions: values.booleans.has("--versions"),
|
|
485
|
-
claims: values.booleans.has("--claims"),
|
|
486
|
-
...(reason ? { reason } : {}),
|
|
487
|
-
// Waiting is the default; only an explicit `--no-wait` turns it off. A
|
|
488
|
-
// person who typed `rewrite` wants the page, not a receipt.
|
|
489
|
-
...(noWait ? { wait: false } : {}),
|
|
490
|
-
json: values.booleans.has("--json"),
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* `cockpit correct` (BLI-3458) — say that one line on the page is wrong.
|
|
495
|
-
*
|
|
496
|
-
* `--claim` is required and comes from `cockpit brief --claims`, which prints
|
|
497
|
-
* the id beside every line it names. A correction with no claim would be a note
|
|
498
|
-
* about the page in general, and the panel has a sentinel for that; the
|
|
499
|
-
* terminal does not offer it, because a person who has just read
|
|
500
|
-
* `[claimId] the sentence` has the id in front of them.
|
|
501
|
-
*
|
|
502
|
-
* `--text` may be omitted when something is piped in — `git log | cockpit
|
|
503
|
-
* correct --claim x` — and the command reads stdin instead. Absent both, the
|
|
504
|
-
* command refuses rather than filing an empty correction.
|
|
505
|
-
*/
|
|
506
|
-
export function parseCorrectArgs(args) {
|
|
507
|
-
const values = parseNamedArgs(args, {
|
|
508
|
-
allowedFlags: [
|
|
509
|
-
"--home",
|
|
510
|
-
"--dashboard-url",
|
|
511
|
-
"--claim",
|
|
512
|
-
"--text",
|
|
513
|
-
"--for",
|
|
514
|
-
"--as",
|
|
515
|
-
"--version",
|
|
516
|
-
"--supersedes",
|
|
517
|
-
"--json",
|
|
518
|
-
],
|
|
519
|
-
valueFlags: [
|
|
520
|
-
"--home",
|
|
521
|
-
"--dashboard-url",
|
|
522
|
-
"--claim",
|
|
523
|
-
"--text",
|
|
524
|
-
"--for",
|
|
525
|
-
"--as",
|
|
526
|
-
"--version",
|
|
527
|
-
"--supersedes",
|
|
528
|
-
],
|
|
529
|
-
});
|
|
530
|
-
const claimId = optionalNonEmpty(values.flags.get("--claim"));
|
|
531
|
-
if (!claimId) {
|
|
532
|
-
throw new Error("correct needs --claim <claimId>. Run `cockpit brief --claims` to see the id beside every line.");
|
|
533
|
-
}
|
|
534
|
-
const flaggedText = optionalNonEmpty(values.flags.get("--text"));
|
|
535
|
-
const positionalText = optionalNonEmpty(values.positionals.join(" "));
|
|
536
|
-
if (flaggedText && positionalText) {
|
|
537
|
-
throw new Error("correct accepts either --text or positional text, not both.");
|
|
538
|
-
}
|
|
539
|
-
const text = flaggedText ?? positionalText;
|
|
540
|
-
if (text && text.length > 4000) {
|
|
541
|
-
throw new Error("A correction is limited to 4000 characters.");
|
|
542
|
-
}
|
|
543
|
-
const forPerson = optionalNonEmpty(values.flags.get("--for"));
|
|
544
|
-
const asPerson = optionalNonEmpty(values.flags.get("--as"));
|
|
545
|
-
if (forPerson && asPerson && forPerson !== asPerson) {
|
|
546
|
-
throw new Error("correct --for and --as must name the same person.");
|
|
547
|
-
}
|
|
548
|
-
return {
|
|
549
|
-
kind: "correct",
|
|
550
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
551
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
552
|
-
claimId,
|
|
553
|
-
text,
|
|
554
|
-
subject: forPerson ?? asPerson,
|
|
555
|
-
version: optionalNonEmpty(values.flags.get("--version")),
|
|
556
|
-
supersedes: optionalNonEmpty(values.flags.get("--supersedes")),
|
|
557
|
-
json: values.booleans.has("--json"),
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
const NOTES_ACTIONS = new Set([
|
|
561
|
-
"list",
|
|
562
|
-
"show",
|
|
563
|
-
"shelf",
|
|
564
|
-
"shelves",
|
|
565
|
-
"upload",
|
|
566
|
-
"paste",
|
|
567
|
-
"share",
|
|
568
|
-
"unshare",
|
|
569
|
-
"move",
|
|
570
|
-
]);
|
|
571
|
-
/** Actions whose first positional is the note it acts on. */
|
|
572
|
-
const NOTES_ACTIONS_NEEDING_A_NOTE = new Set([
|
|
573
|
-
"show",
|
|
574
|
-
"share",
|
|
575
|
-
"unshare",
|
|
576
|
-
"move",
|
|
577
|
-
]);
|
|
578
|
-
export function parseNotesArgs(args) {
|
|
579
|
-
const values = parseNamedArgs(args, {
|
|
580
|
-
allowedFlags: [
|
|
581
|
-
"--home",
|
|
582
|
-
"--dashboard-url",
|
|
583
|
-
"--file",
|
|
584
|
-
"--name",
|
|
585
|
-
"--exclude",
|
|
586
|
-
"--to",
|
|
587
|
-
"--clear-shelf",
|
|
588
|
-
"--series",
|
|
589
|
-
"--kind",
|
|
590
|
-
"--since",
|
|
591
|
-
"--until",
|
|
592
|
-
"--limit",
|
|
593
|
-
"--yes",
|
|
594
|
-
"--json",
|
|
595
|
-
],
|
|
596
|
-
valueFlags: [
|
|
597
|
-
"--home",
|
|
598
|
-
"--dashboard-url",
|
|
599
|
-
"--file",
|
|
600
|
-
"--name",
|
|
601
|
-
"--exclude",
|
|
602
|
-
"--to",
|
|
603
|
-
"--series",
|
|
604
|
-
"--kind",
|
|
605
|
-
"--since",
|
|
606
|
-
"--until",
|
|
607
|
-
"--limit",
|
|
608
|
-
],
|
|
609
|
-
});
|
|
610
|
-
// Bare `cockpit notes` is the list, which is the thing a person typing it
|
|
611
|
-
// almost always wants — same reasoning as bare `cockpit` running convergence.
|
|
612
|
-
const first = values.positionals[0];
|
|
613
|
-
const action = (first === undefined ? "list" : first);
|
|
614
|
-
if (!NOTES_ACTIONS.has(action)) {
|
|
615
|
-
throw new Error(`Unknown notes command: ${first}. Try list, show, shelf, shelves, upload, paste, share, unshare, or move.`);
|
|
616
|
-
}
|
|
617
|
-
const rest = values.positionals.slice(first === undefined ? 0 : 1);
|
|
618
|
-
const json = values.booleans.has("--json");
|
|
619
|
-
// `--json` implies `--yes`: a machine-readable run has nobody to prompt, and
|
|
620
|
-
// failing it for want of a confirmation flag it cannot see is a worse
|
|
621
|
-
// surprise than an explicit share it already asked for by name.
|
|
622
|
-
const yes = values.booleans.has("--yes") || json;
|
|
623
|
-
let noteId;
|
|
624
|
-
if (NOTES_ACTIONS_NEEDING_A_NOTE.has(action)) {
|
|
625
|
-
noteId = optionalNonEmpty(rest[0]);
|
|
626
|
-
if (!noteId)
|
|
627
|
-
throw new Error(`notes ${action} needs a note id.`);
|
|
628
|
-
if (rest.length > 1) {
|
|
629
|
-
throw new Error(`notes ${action} takes one note id, not ${rest.length}.`);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
let paths;
|
|
633
|
-
if (action === "upload") {
|
|
634
|
-
// Explicit paths only. No globbing happens here — a shell that expands
|
|
635
|
-
// `*.md` hands us the names it found, and a shell that does not would have
|
|
636
|
-
// this command silently uploading a file literally called `*.md`.
|
|
637
|
-
paths = rest.filter((value) => value.trim() !== "");
|
|
638
|
-
if (paths.length === 0)
|
|
639
|
-
throw new Error("notes upload needs at least one file path.");
|
|
640
|
-
}
|
|
641
|
-
else if (action !== "paste" && !NOTES_ACTIONS_NEEDING_A_NOTE.has(action) && rest.length > 0) {
|
|
642
|
-
throw new Error(`notes ${action} does not take "${rest[0]}".`);
|
|
643
|
-
}
|
|
644
|
-
const to = optionalNonEmpty(values.flags.get("--to"));
|
|
645
|
-
const clearShelf = values.booleans.has("--clear-shelf");
|
|
646
|
-
if (action === "move") {
|
|
647
|
-
if (to && clearShelf) {
|
|
648
|
-
throw new Error("notes move accepts either --to or --clear-shelf, not both.");
|
|
649
|
-
}
|
|
650
|
-
if (!to && !clearShelf) {
|
|
651
|
-
throw new Error('notes move needs --to "<shelf>" or --clear-shelf.');
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
655
|
-
return {
|
|
656
|
-
kind: "notes",
|
|
657
|
-
action,
|
|
658
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
659
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
660
|
-
...(noteId ? { noteId } : {}),
|
|
661
|
-
...(paths ? { paths } : {}),
|
|
662
|
-
filePath: optionalNonEmpty(values.flags.get("--file")),
|
|
663
|
-
name: optionalNonEmpty(values.flags.get("--name")),
|
|
664
|
-
exclude: optionalNonEmpty(values.flags.get("--exclude")),
|
|
665
|
-
...(to ? { to } : {}),
|
|
666
|
-
...(clearShelf ? { clearShelf } : {}),
|
|
667
|
-
series: optionalNonEmpty(values.flags.get("--series")),
|
|
668
|
-
meetingKind: optionalNonEmpty(values.flags.get("--kind")),
|
|
669
|
-
since: optionalNonEmpty(values.flags.get("--since")),
|
|
670
|
-
until: optionalNonEmpty(values.flags.get("--until")),
|
|
671
|
-
...(limit === undefined ? {} : { limit }),
|
|
672
|
-
yes,
|
|
673
|
-
json,
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
const SETTINGS_SECTIONS = ["personal", "switches", "models", "env", "cli-floor"];
|
|
677
|
-
/**
|
|
678
|
-
* `cockpit settings [section] [verb] [args]` (BLI-3461).
|
|
13
|
+
* local-args-tower-chat.ts jarvis, correct — talking to JARVIS directly
|
|
14
|
+
* local-args-tower-pages.ts brief, notes, — reading a page instead of
|
|
15
|
+
* workbook asking a question
|
|
16
|
+
* local-args-tower-admin.ts scout, ops, slack, — managing the account or
|
|
17
|
+
* settings, team, watching the pipeline
|
|
18
|
+
* model
|
|
679
19
|
*
|
|
680
|
-
*
|
|
681
|
-
* afterwards, per section — the same shape `parseDoctorArgs` uses. A flag that
|
|
682
|
-
* belongs to another section is refused by name rather than silently ignored,
|
|
683
|
-
* because a dropped `--project` on an env write is a change a person believes
|
|
684
|
-
* they made.
|
|
20
|
+
* Every name this module has ever exported is still importable from here.
|
|
685
21
|
*/
|
|
686
|
-
export
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
"--home",
|
|
690
|
-
"--dashboard-url",
|
|
691
|
-
"--json",
|
|
692
|
-
"--chat-model",
|
|
693
|
-
"--brief-model",
|
|
694
|
-
"--chat",
|
|
695
|
-
"--memory",
|
|
696
|
-
"--project",
|
|
697
|
-
"--file",
|
|
698
|
-
"--id",
|
|
699
|
-
"--content-stdin",
|
|
700
|
-
"--yes",
|
|
701
|
-
],
|
|
702
|
-
valueFlags: [
|
|
703
|
-
"--home",
|
|
704
|
-
"--dashboard-url",
|
|
705
|
-
"--chat-model",
|
|
706
|
-
"--brief-model",
|
|
707
|
-
"--chat",
|
|
708
|
-
"--memory",
|
|
709
|
-
"--project",
|
|
710
|
-
"--file",
|
|
711
|
-
"--id",
|
|
712
|
-
],
|
|
713
|
-
});
|
|
714
|
-
const [rawSection, ...rest] = values.positionals;
|
|
715
|
-
const common = {
|
|
716
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
717
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
718
|
-
yes: values.booleans.has("--yes"),
|
|
719
|
-
json: values.booleans.has("--json"),
|
|
720
|
-
};
|
|
721
|
-
if (!rawSection) {
|
|
722
|
-
if (rest.length > 0)
|
|
723
|
-
throw new Error("settings does not accept that argument.");
|
|
724
|
-
return { kind: "settings", section: "overview", action: "show", ...common };
|
|
725
|
-
}
|
|
726
|
-
if (!SETTINGS_SECTIONS.includes(rawSection)) {
|
|
727
|
-
throw new Error(`settings section must be one of ${SETTINGS_SECTIONS.join(", ")} — or nothing, for all of them.`);
|
|
728
|
-
}
|
|
729
|
-
const section = rawSection;
|
|
730
|
-
if (section === "personal") {
|
|
731
|
-
if (rest.length > 0)
|
|
732
|
-
throw new Error("settings personal does not accept positional arguments.");
|
|
733
|
-
const chatModel = optionalNonEmpty(values.flags.get("--chat-model"));
|
|
734
|
-
const briefModel = optionalNonEmpty(values.flags.get("--brief-model"));
|
|
735
|
-
return {
|
|
736
|
-
kind: "settings",
|
|
737
|
-
section,
|
|
738
|
-
action: chatModel || briefModel ? "set" : "show",
|
|
739
|
-
chatModel,
|
|
740
|
-
briefModel,
|
|
741
|
-
...common,
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
if (section === "switches") {
|
|
745
|
-
if (rest.length === 0) {
|
|
746
|
-
return { kind: "settings", section, action: "show", ...common };
|
|
747
|
-
}
|
|
748
|
-
if (rest[0] !== "set") {
|
|
749
|
-
throw new Error("settings switches takes no verb, or `set <key> <value>`.");
|
|
750
|
-
}
|
|
751
|
-
const [, key, value, ...extra] = rest;
|
|
752
|
-
if (!key || !value || extra.length > 0) {
|
|
753
|
-
throw new Error("settings switches set needs exactly a key and a value.");
|
|
754
|
-
}
|
|
755
|
-
return { kind: "settings", section, action: "set", switchKey: key, switchValue: value, ...common };
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* BLI-3557: `settings cli-floor` shows the fleet forced-update floor,
|
|
759
|
-
* `settings cli-floor <version>` raises it. There is no `set` verb because
|
|
760
|
-
* there is nothing else to do to a floor, and no flag for the version because
|
|
761
|
-
* a bare positional is how a release says it: `cockpit settings cli-floor
|
|
762
|
-
* 0.2.48`. The server refuses a LOWER version; the terminal does not
|
|
763
|
-
* second-guess that decision locally.
|
|
764
|
-
*/
|
|
765
|
-
if (section === "cli-floor") {
|
|
766
|
-
if (rest.length === 0) {
|
|
767
|
-
return { kind: "settings", section, action: "show", ...common };
|
|
768
|
-
}
|
|
769
|
-
const [version, ...extra] = rest;
|
|
770
|
-
if (!version || extra.length > 0) {
|
|
771
|
-
throw new Error("settings cli-floor takes no argument, or one version: `cli-floor 0.2.48`.");
|
|
772
|
-
}
|
|
773
|
-
return { kind: "settings", section, action: "set", floorVersion: version, ...common };
|
|
774
|
-
}
|
|
775
|
-
if (section === "models") {
|
|
776
|
-
if (rest.length === 0) {
|
|
777
|
-
return { kind: "settings", section, action: "show", ...common };
|
|
778
|
-
}
|
|
779
|
-
if (rest[0] !== "set" || rest.length > 1) {
|
|
780
|
-
throw new Error("settings models takes no verb, or `set --chat <key>` / `set --memory <id>`.");
|
|
781
|
-
}
|
|
782
|
-
const orgChatModel = optionalNonEmpty(values.flags.get("--chat"));
|
|
783
|
-
const orgMemoryModel = optionalNonEmpty(values.flags.get("--memory"));
|
|
784
|
-
if (!orgChatModel && !orgMemoryModel) {
|
|
785
|
-
throw new Error("settings models set needs --chat <key>, --memory <id>, or both.");
|
|
786
|
-
}
|
|
787
|
-
return { kind: "settings", section, action: "set", orgChatModel, orgMemoryModel, ...common };
|
|
788
|
-
}
|
|
789
|
-
// env
|
|
790
|
-
const verb = rest[0] ?? "list";
|
|
791
|
-
if (rest.length > 1)
|
|
792
|
-
throw new Error("settings env takes one verb: list, set, or delete.");
|
|
793
|
-
if (verb === "list") {
|
|
794
|
-
return { kind: "settings", section, action: "list", ...common };
|
|
795
|
-
}
|
|
796
|
-
if (verb === "set") {
|
|
797
|
-
const envProject = optionalNonEmpty(values.flags.get("--project"));
|
|
798
|
-
const envFile = optionalNonEmpty(values.flags.get("--file"));
|
|
799
|
-
if (!envProject || !envFile) {
|
|
800
|
-
throw new Error("settings env set needs --project <project> and --file <file name>.");
|
|
801
|
-
}
|
|
802
|
-
if (!values.booleans.has("--content-stdin")) {
|
|
803
|
-
// Deliberate: there is no `--content <value>` flag and never will be. A
|
|
804
|
-
// secret on a command line lands in shell history and in every process
|
|
805
|
-
// listing on the machine.
|
|
806
|
-
throw new Error("settings env set reads the file contents from stdin: add --content-stdin and pipe the file in.");
|
|
807
|
-
}
|
|
808
|
-
return {
|
|
809
|
-
kind: "settings",
|
|
810
|
-
section,
|
|
811
|
-
action: "set",
|
|
812
|
-
envProject,
|
|
813
|
-
envFile,
|
|
814
|
-
contentStdin: true,
|
|
815
|
-
...common,
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
if (verb === "delete") {
|
|
819
|
-
const envId = optionalNonEmpty(values.flags.get("--id"));
|
|
820
|
-
if (!envId)
|
|
821
|
-
throw new Error("settings env delete needs --id <uuid>.");
|
|
822
|
-
return { kind: "settings", section, action: "delete", envId, ...common };
|
|
823
|
-
}
|
|
824
|
-
throw new Error("settings env takes one verb: list, set, or delete.");
|
|
825
|
-
}
|
|
826
|
-
/** `cockpit team [members|invite <email>|role <userId>]` (BLI-3461). */
|
|
827
|
-
export function parseTeamArgs(args) {
|
|
828
|
-
const values = parseNamedArgs(args, {
|
|
829
|
-
allowedFlags: ["--home", "--dashboard-url", "--json", "--role", "--team-id", "--yes"],
|
|
830
|
-
valueFlags: ["--home", "--dashboard-url", "--role", "--team-id"],
|
|
831
|
-
});
|
|
832
|
-
const common = {
|
|
833
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
834
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
835
|
-
teamId: optionalNonEmpty(values.flags.get("--team-id")),
|
|
836
|
-
yes: values.booleans.has("--yes"),
|
|
837
|
-
json: values.booleans.has("--json"),
|
|
838
|
-
};
|
|
839
|
-
const [verb, subject, ...extra] = values.positionals;
|
|
840
|
-
if (extra.length > 0)
|
|
841
|
-
throw new Error("team does not accept that many arguments.");
|
|
842
|
-
if (!verb || verb === "members") {
|
|
843
|
-
if (subject)
|
|
844
|
-
throw new Error("team members does not accept positional arguments.");
|
|
845
|
-
return { kind: "team", action: "members", ...common };
|
|
846
|
-
}
|
|
847
|
-
if (verb === "invite") {
|
|
848
|
-
if (!subject)
|
|
849
|
-
throw new Error("team invite needs an email address.");
|
|
850
|
-
const email = optionalEmail(subject);
|
|
851
|
-
const role = optionalNonEmpty(values.flags.get("--role"));
|
|
852
|
-
if (!role)
|
|
853
|
-
throw new Error("team invite needs --role <role>.");
|
|
854
|
-
return { kind: "team", action: "invite", email, role, ...common };
|
|
855
|
-
}
|
|
856
|
-
if (verb === "role") {
|
|
857
|
-
if (!subject)
|
|
858
|
-
throw new Error("team role needs the user id whose role is changing.");
|
|
859
|
-
const role = optionalNonEmpty(values.flags.get("--role"));
|
|
860
|
-
if (!role)
|
|
861
|
-
throw new Error("team role needs --role <role>.");
|
|
862
|
-
return { kind: "team", action: "role", targetUserId: subject, role, ...common };
|
|
863
|
-
}
|
|
864
|
-
throw new Error("team takes one verb: members, invite, or role.");
|
|
865
|
-
}
|
|
866
|
-
/** `cockpit model [show|set <key>]` — the alias surface for the personal chat model. */
|
|
867
|
-
export function parseModelArgs(args) {
|
|
868
|
-
const values = parseNamedArgs(args, {
|
|
869
|
-
allowedFlags: ["--home", "--dashboard-url", "--json"],
|
|
870
|
-
valueFlags: ["--home", "--dashboard-url"],
|
|
871
|
-
});
|
|
872
|
-
const [verb, key, ...extra] = values.positionals;
|
|
873
|
-
if (extra.length > 0)
|
|
874
|
-
throw new Error("model does not accept that many arguments.");
|
|
875
|
-
const common = {
|
|
876
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
877
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
878
|
-
json: values.booleans.has("--json"),
|
|
879
|
-
};
|
|
880
|
-
if (!verb || verb === "show") {
|
|
881
|
-
if (key)
|
|
882
|
-
throw new Error("model show does not accept positional arguments.");
|
|
883
|
-
return { kind: "model", action: "show", ...common };
|
|
884
|
-
}
|
|
885
|
-
if (verb === "set") {
|
|
886
|
-
if (!key)
|
|
887
|
-
throw new Error("model set needs a model key, e.g. openai:gpt-5.6-terra.");
|
|
888
|
-
return { kind: "model", action: "set", modelKey: key, ...common };
|
|
889
|
-
}
|
|
890
|
-
throw new Error("model takes one verb: show or set <key>.");
|
|
891
|
-
}
|
|
22
|
+
export { parseJarvisArgs, parseCorrectArgs } from "./local-args-tower-chat.js";
|
|
23
|
+
export { parseBriefArgs, WORKBOOK_MIN_WIDTH, parseWorkbookArgs, parseNotesArgs, } from "./local-args-tower-pages.js";
|
|
24
|
+
export { SCOUT_MIN_PREFIX_LENGTH, parseScoutArgs, parseOpsArgs, SLACK_WORKSPACE_KEYS, parseSlackArgs, parseSettingsArgs, parseTeamArgs, parseModelArgs, } from "./local-args-tower-admin.js";
|