@bli-cockpit/cli 0.2.224 → 0.2.237
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/commands/agent-door.js +35 -0
- package/dist/commands/approvals.js +3 -1
- package/dist/commands/brain.js +23 -0
- package/dist/commands/cal-overlay.js +63 -0
- package/dist/commands/cal.js +4 -0
- package/dist/commands/ci.js +23 -0
- package/dist/commands/docs-export.js +62 -0
- package/dist/commands/docs.js +3 -0
- package/dist/commands/doctor-usage-telemetry.js +2 -1
- package/dist/commands/drills-render.js +63 -0
- package/dist/commands/drills.js +152 -0
- package/dist/commands/hits.js +83 -0
- package/dist/commands/intentions.js +49 -0
- package/dist/commands/local-args-docs-export.js +23 -0
- package/dist/commands/local-args-tower-admin.js +8 -0
- package/dist/commands/local-args-tower-brain-ladder.js +90 -0
- package/dist/commands/local-args-tower-brain.js +11 -1
- package/dist/commands/local-args-tower-cal.js +6 -2
- package/dist/commands/local-args-tower-docs-msg.js +10 -146
- package/dist/commands/local-args-tower-drills.js +87 -0
- package/dist/commands/local-args-tower-hits.js +93 -0
- package/dist/commands/local-args-tower-intentions.js +70 -0
- package/dist/commands/local-args-tower-logs-search.js +51 -0
- package/dist/commands/local-args-tower-logs.js +3 -0
- package/dist/commands/local-args-tower-memory.js +67 -1
- package/dist/commands/local-args-tower-msg.js +149 -0
- package/dist/commands/local-args-tower-pages.js +3 -1
- package/dist/commands/local-args-tower-people.js +25 -0
- package/dist/commands/local-args-tower-usage.js +4 -4
- package/dist/commands/local-args-tower-who.js +36 -0
- package/dist/commands/local-args-tower.js +8 -1
- package/dist/commands/local-args.js +15 -1
- package/dist/commands/local-command-shapes-auth.js +1 -0
- package/dist/commands/local-command-shapes-drills.js +7 -0
- package/dist/commands/local-command-shapes-hits.js +5 -0
- package/dist/commands/local-help-commands-tower-ops.js +123 -1
- package/dist/commands/local-help-commands-tower.js +25 -10
- package/dist/commands/local-help-commands.js +3 -0
- package/dist/commands/local-help.js +26 -6
- package/dist/commands/local-run-ops.js +11 -0
- package/dist/commands/local.js +16 -8
- package/dist/commands/logs-search.js +45 -0
- package/dist/commands/logs.js +3 -0
- package/dist/commands/mail.js +6 -4
- package/dist/commands/memory-exam.js +47 -0
- package/dist/commands/memory-repeats.js +44 -0
- package/dist/commands/memory-search-print.js +95 -0
- package/dist/commands/memory-source.js +39 -0
- package/dist/commands/memory-store.js +18 -30
- package/dist/commands/memory-verb-list.js +7 -2
- package/dist/commands/msg.js +51 -9
- package/dist/commands/notes-reads.js +20 -0
- package/dist/commands/notes-writes.js +24 -0
- package/dist/commands/notes.js +4 -2
- package/dist/commands/people.js +29 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/spend.js +79 -0
- package/dist/commands/team.js +35 -0
- package/dist/commands/usage.js +9 -0
- package/dist/commands/who.js +36 -0
- package/dist/evidence-upload-direct.js +44 -0
- package/dist/evidence-upload-object.js +18 -4
- package/dist/evidence-upload-plan.js +1 -0
- package/dist/evidence-upload-transport.js +2 -0
- package/package.json +4 -4
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit docs export` flag parsing (ticket 20694), split out of `local-args-tower-docs-msg.ts`,
|
|
3
|
+
* which sits at the file-size ratchet.
|
|
4
|
+
*/
|
|
5
|
+
import { optionalNonEmpty } from "./local-arg-values.js";
|
|
6
|
+
const DOCS_EXPORT_FORMATS = ["md", "pdf", "docx", "txt"];
|
|
7
|
+
/** Ticket 20694: `--format`/`--out` mean something on `export` alone, and an unknown format is refused by name. */
|
|
8
|
+
export function parseDocsExportFlags(action, values) {
|
|
9
|
+
const format = optionalNonEmpty(values.flags.get("--format"))?.toLowerCase();
|
|
10
|
+
const outPath = optionalNonEmpty(values.flags.get("--out"));
|
|
11
|
+
if (action !== "export") {
|
|
12
|
+
if (format)
|
|
13
|
+
throw new Error("--format belongs to `cockpit docs export`.");
|
|
14
|
+
if (outPath)
|
|
15
|
+
throw new Error("--out belongs to `cockpit docs export`.");
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
const chosen = format === "markdown" ? "md" : format === "word" ? "docx" : format === "text" ? "txt" : (format ?? "md");
|
|
19
|
+
if (!DOCS_EXPORT_FORMATS.includes(chosen)) {
|
|
20
|
+
throw new Error(`docs export --format must be one of ${DOCS_EXPORT_FORMATS.join(", ")} (Word is docx), not "${format}".`);
|
|
21
|
+
}
|
|
22
|
+
return { exportFormat: chosen, ...(outPath ? { outPath } : {}) };
|
|
23
|
+
}
|
|
@@ -301,6 +301,14 @@ export function parseTeamArgs(args) {
|
|
|
301
301
|
}
|
|
302
302
|
return { kind: "team", action: `github-login-${operation}`, personRef, githubLogin, ...common };
|
|
303
303
|
}
|
|
304
|
+
if (verb === "timezone") {
|
|
305
|
+
const [operation, personRef, zone, ...extra] = rest;
|
|
306
|
+
if (!["set", "list"].includes(operation ?? "") || extra.length ||
|
|
307
|
+
(operation === "list" ? personRef !== undefined : !personRef || !zone)) {
|
|
308
|
+
throw new Error("team timezone takes set <person> <iana-zone> (or the word unknown), or list.");
|
|
309
|
+
}
|
|
310
|
+
return { kind: "team", action: operation === "set" ? "timezone-set" : "timezone-list", ...(personRef ? { personRef } : {}), ...(zone ? { zone } : {}), ...common };
|
|
311
|
+
}
|
|
304
312
|
if (verb === "device") {
|
|
305
313
|
return parseTeamDeviceArgs(rest, values.flags, common);
|
|
306
314
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit brain ladder` argument parsing (BLI-19250): Edward's call ladder.
|
|
3
|
+
*
|
|
4
|
+
* cockpit brain ladder show [--json]
|
|
5
|
+
* cockpit brain ladder promote <class> [--anyway] [--note <text>] [--json]
|
|
6
|
+
* cockpit brain ladder demote <class> [--note <text>] [--json]
|
|
7
|
+
* cockpit brain ladder predict "<question>" [--context <text>] [--asker <label>] [--source-kind <k>] [--source-ref <ref>] [--json]
|
|
8
|
+
* cockpit brain ladder answer "<what he answered>" (--prediction <uuid> | --source-kind <k> --source-ref <ref>) [--json]
|
|
9
|
+
*
|
|
10
|
+
* The class names are checked by the door against the closed list (`lib/edward-predictor/classes.ts`), not copied here.
|
|
11
|
+
*/
|
|
12
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
13
|
+
const VERBS = ["show", "promote", "demote", "predict", "answer"];
|
|
14
|
+
const SOURCE_KIND = /^[a-z][a-z0-9_]{0,31}$/;
|
|
15
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16
|
+
export function parseBrainLadderArgs(args) {
|
|
17
|
+
const values = parseNamedArgs(args, {
|
|
18
|
+
allowedFlags: ["--home", "--dashboard-url", "--json", "--anyway", "--note", "--context", "--asker", "--source-kind", "--source-ref", "--prediction"],
|
|
19
|
+
valueFlags: ["--home", "--dashboard-url", "--note", "--context", "--asker", "--source-kind", "--source-ref", "--prediction"],
|
|
20
|
+
});
|
|
21
|
+
const [ladderWord, verb, ...rest] = values.positionals;
|
|
22
|
+
if (ladderWord !== "ladder" || !VERBS.includes(verb ?? "")) {
|
|
23
|
+
throw new Error(`brain ladder takes one verb: ${VERBS.join(", ")}. Example: \`cockpit brain ladder show\`.`);
|
|
24
|
+
}
|
|
25
|
+
const shape = { verb: verb };
|
|
26
|
+
const flag = (name) => optionalNonEmpty(values.flags.get(name))?.trim();
|
|
27
|
+
const note = flag("--note");
|
|
28
|
+
if (note && note.length > 300)
|
|
29
|
+
throw new Error("--note is at most 300 characters.");
|
|
30
|
+
const sourceKind = flag("--source-kind");
|
|
31
|
+
if (sourceKind && !SOURCE_KIND.test(sourceKind))
|
|
32
|
+
throw new Error("--source-kind wants a lowercase label like approval_card.");
|
|
33
|
+
const prediction = flag("--prediction");
|
|
34
|
+
if (prediction && !UUID.test(prediction))
|
|
35
|
+
throw new Error("--prediction wants a prediction id (a UUID).");
|
|
36
|
+
const only = (allowed) => {
|
|
37
|
+
for (const name of ["--anyway", "--note", "--context", "--asker", "--source-kind", "--source-ref", "--prediction"]) {
|
|
38
|
+
const given = values.booleans.has(name) || values.flags.has(name);
|
|
39
|
+
if (given && !allowed.includes(name))
|
|
40
|
+
throw new Error(`${name} does not belong to brain ladder ${shape.verb}.`);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
switch (shape.verb) {
|
|
44
|
+
case "show":
|
|
45
|
+
if (rest.length)
|
|
46
|
+
throw new Error("brain ladder show takes no arguments.");
|
|
47
|
+
only([]);
|
|
48
|
+
break;
|
|
49
|
+
case "promote":
|
|
50
|
+
case "demote":
|
|
51
|
+
if (rest.length !== 1)
|
|
52
|
+
throw new Error(`brain ladder ${shape.verb} takes one class name. Example: \`cockpit brain ladder ${shape.verb} merge\`.`);
|
|
53
|
+
only(shape.verb === "promote" ? ["--anyway", "--note"] : ["--note"]);
|
|
54
|
+
shape.className = rest[0];
|
|
55
|
+
if (values.booleans.has("--anyway"))
|
|
56
|
+
shape.anyway = true;
|
|
57
|
+
if (note)
|
|
58
|
+
shape.note = note;
|
|
59
|
+
break;
|
|
60
|
+
case "predict":
|
|
61
|
+
if (rest.length !== 1)
|
|
62
|
+
throw new Error('brain ladder predict takes the question in quotes. Example: `cockpit brain ladder predict "Merge #1820 now?"`.');
|
|
63
|
+
only(["--context", "--asker", "--source-kind", "--source-ref"]);
|
|
64
|
+
shape.text = rest[0];
|
|
65
|
+
if (flag("--context"))
|
|
66
|
+
shape.context = flag("--context");
|
|
67
|
+
if (flag("--asker"))
|
|
68
|
+
shape.asker = flag("--asker");
|
|
69
|
+
if (sourceKind)
|
|
70
|
+
shape.sourceKind = sourceKind;
|
|
71
|
+
if (flag("--source-ref"))
|
|
72
|
+
shape.sourceRef = flag("--source-ref");
|
|
73
|
+
break;
|
|
74
|
+
case "answer":
|
|
75
|
+
if (rest.length !== 1)
|
|
76
|
+
throw new Error('brain ladder answer takes what he answered, in quotes. Example: `cockpit brain ladder answer "Yes, merge it" --prediction <id>`.');
|
|
77
|
+
only(["--prediction", "--source-kind", "--source-ref"]);
|
|
78
|
+
shape.text = rest[0];
|
|
79
|
+
if (prediction)
|
|
80
|
+
shape.predictionId = prediction;
|
|
81
|
+
if (sourceKind)
|
|
82
|
+
shape.sourceKind = sourceKind;
|
|
83
|
+
if (flag("--source-ref"))
|
|
84
|
+
shape.sourceRef = flag("--source-ref");
|
|
85
|
+
if (!shape.predictionId && !(shape.sourceKind && shape.sourceRef))
|
|
86
|
+
throw new Error("brain ladder answer needs --prediction <id>, or --source-kind and --source-ref.");
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
return { ladder: shape, dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")), homeDir: flag("--home"), json: values.booleans.has("--json") };
|
|
90
|
+
}
|
|
@@ -9,16 +9,26 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { LOG_PROJECT_PATTERN } from "@bli-cockpit/telemetry-core/log-digest";
|
|
11
11
|
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
12
|
+
import { parseBrainLadderArgs } from "./local-args-tower-brain-ladder.js";
|
|
12
13
|
/** The statuses the queue's door accepts for `--show`; the migration's CHECK constraint is the other copy. */
|
|
13
14
|
export const BRAIN_SHOW_STATUSES = ["open", "running", "needs_human", "fixed", "not_actionable", "cleared"];
|
|
14
15
|
export function parseBrainArgs(args) {
|
|
16
|
+
// BLI-19250: `brain ladder ...` is Edward's call ladder; it has its own verbs and flags.
|
|
17
|
+
// The census spells these `brain ladder-show` (one action word), so that spelling is accepted beside `brain ladder show`.
|
|
18
|
+
const first = args.find((arg) => !arg.startsWith("--"));
|
|
19
|
+
if (first === "ladder" || /^ladder-[a-z]+$/.test(first ?? "")) {
|
|
20
|
+
const hyphen = first !== "ladder";
|
|
21
|
+
const split = hyphen ? args.flatMap((arg) => (arg === first ? ["ladder", first.slice("ladder-".length)] : [arg])) : args;
|
|
22
|
+
const parsed = parseBrainLadderArgs(split);
|
|
23
|
+
return { kind: "brain", action: `ladder-${parsed.ladder.verb}`, ladder: parsed.ladder, dashboardUrl: parsed.dashboardUrl, homeDir: parsed.homeDir, json: parsed.json };
|
|
24
|
+
}
|
|
15
25
|
const values = parseNamedArgs(args, {
|
|
16
26
|
allowedFlags: ["--project", "--show", "--home", "--dashboard-url", "--json", "--all-needs-human", "--reason"],
|
|
17
27
|
valueFlags: ["--project", "--show", "--home", "--dashboard-url", "--reason"],
|
|
18
28
|
});
|
|
19
29
|
const action = values.positionals[0];
|
|
20
30
|
if ((action !== "status" && action !== "retry") || (action === "status" && values.positionals.length > 1)) {
|
|
21
|
-
throw new Error("brain takes one verb: `status` or `
|
|
31
|
+
throw new Error("brain takes one verb: `status`, `retry` or `ladder`. Example: `cockpit brain status` or `cockpit brain status --project pearl-portal`.");
|
|
22
32
|
}
|
|
23
33
|
const project = optionalNonEmpty(values.flags.get("--project"));
|
|
24
34
|
if (project && !LOG_PROJECT_PATTERN.test(project))
|
|
@@ -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
|
+
"conflicts", "calendar-change",
|
|
23
24
|
"today",
|
|
24
25
|
"week",
|
|
25
26
|
"next",
|
|
@@ -35,7 +36,7 @@ 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(["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"]);
|
|
41
42
|
export function parseCalArgs(args) {
|
|
@@ -43,6 +44,7 @@ export function parseCalArgs(args) {
|
|
|
43
44
|
allowedFlags: [
|
|
44
45
|
"--home",
|
|
45
46
|
"--dashboard-url",
|
|
47
|
+
"--color",
|
|
46
48
|
"--calendar",
|
|
47
49
|
"--account",
|
|
48
50
|
"--tz",
|
|
@@ -69,6 +71,7 @@ export function parseCalArgs(args) {
|
|
|
69
71
|
valueFlags: [
|
|
70
72
|
"--home",
|
|
71
73
|
"--dashboard-url",
|
|
74
|
+
"--color",
|
|
72
75
|
"--calendar",
|
|
73
76
|
"--account",
|
|
74
77
|
"--tz",
|
|
@@ -100,7 +103,7 @@ function readCalAction(values) {
|
|
|
100
103
|
const first = values.positionals[0];
|
|
101
104
|
const action = (first === undefined ? "today" : first);
|
|
102
105
|
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.`);
|
|
106
|
+
throw new Error(`Unknown cal command: ${first}. Try today, week, conflicts, calendar-change, next, find, calendars, add-ical, create, share, detach, sync, sync-status, or delete.`);
|
|
104
107
|
}
|
|
105
108
|
return { action, rest: values.positionals.slice(first === undefined ? 0 : 1) };
|
|
106
109
|
}
|
|
@@ -170,6 +173,7 @@ function readCalOffset(values) {
|
|
|
170
173
|
function buildCalCommand(values, action, subject, query, title, startsAt, endsAt, offset, limit) {
|
|
171
174
|
return {
|
|
172
175
|
kind: "cal",
|
|
176
|
+
color: optionalNonEmpty(values.flags.get("--color")),
|
|
173
177
|
action,
|
|
174
178
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
175
179
|
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* added both without stretching either existing family's own doc comment.
|
|
8
8
|
*/
|
|
9
9
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
|
|
10
|
-
|
|
10
|
+
import { parseDocsExportFlags } from "./local-args-docs-export.js";
|
|
11
|
+
const DOCS_ACTIONS = new Set(["list", "tree", "read", "create", "update", "delete", "link", "sync-status", "export"]);
|
|
11
12
|
// BLI-10450: `read` moved to its own branch in `parseDocsDocumentReference` —
|
|
12
13
|
// it takes SEVERAL references now, so it no longer shares the "exactly one"
|
|
13
14
|
// rule `update`/`link` still enforce. BLI‑10555: `delete` takes several too
|
|
14
15
|
// (the same door, called once per ref), so it joined `read`'s branch rather
|
|
15
16
|
// than this set.
|
|
16
|
-
const DOCS_ACTIONS_NEEDING_A_DOC = new Set(["update", "link"]);
|
|
17
|
+
const DOCS_ACTIONS_NEEDING_A_DOC = new Set(["update", "link", "export"]);
|
|
17
18
|
export function parseDocsArgs(args) {
|
|
18
19
|
const values = parseNamedArgs(args, {
|
|
19
20
|
allowedFlags: [
|
|
@@ -33,6 +34,8 @@ export function parseDocsArgs(args) {
|
|
|
33
34
|
"--bodies",
|
|
34
35
|
"--section",
|
|
35
36
|
"--offset",
|
|
37
|
+
"--format",
|
|
38
|
+
"--out",
|
|
36
39
|
"--json",
|
|
37
40
|
],
|
|
38
41
|
valueFlags: [
|
|
@@ -46,6 +49,8 @@ export function parseDocsArgs(args) {
|
|
|
46
49
|
"--limit",
|
|
47
50
|
"--section",
|
|
48
51
|
"--offset",
|
|
52
|
+
"--format",
|
|
53
|
+
"--out",
|
|
49
54
|
],
|
|
50
55
|
});
|
|
51
56
|
const { action, rest } = parseDocsAction(values.positionals);
|
|
@@ -111,7 +116,7 @@ function parseDocsAction(positionals) {
|
|
|
111
116
|
const first = positionals[0];
|
|
112
117
|
const action = (first === undefined ? "list" : first);
|
|
113
118
|
if (!DOCS_ACTIONS.has(action)) {
|
|
114
|
-
throw new Error(`Unknown docs command: ${first}. Try list, tree, read, create, update, delete, link,
|
|
119
|
+
throw new Error(`Unknown docs command: ${first}. Try list, tree, read, create, update, delete, link, sync-status, or export.`);
|
|
115
120
|
}
|
|
116
121
|
return { action, rest: positionals.slice(first === undefined ? 0 : 1) };
|
|
117
122
|
}
|
|
@@ -234,149 +239,8 @@ function buildDocsCommand(values, action, docRef, docRefs, visibility, clearPare
|
|
|
234
239
|
...(offset === undefined ? {} : { offset }),
|
|
235
240
|
...(link.notionPage ? { notionPage: link.notionPage } : {}),
|
|
236
241
|
...(link.clearLink ? { clearLink: true } : {}),
|
|
242
|
+
...parseDocsExportFlags(action, values),
|
|
237
243
|
json: values.booleans.has("--json"),
|
|
238
244
|
};
|
|
239
245
|
}
|
|
240
|
-
|
|
241
|
-
const MSG_ACTIONS_NEEDING_A_CHANNEL = new Set(["read", "send"]);
|
|
242
|
-
/**
|
|
243
|
-
* `--members a@x.test,b@y.test` — addresses only, split on commas, never
|
|
244
|
-
* fuzzy (BLI-3749). The door resolves each one exactly, so a typo comes back
|
|
245
|
-
* as `person_not_found` naming the address rather than as a stranger added
|
|
246
|
-
* to a private channel.
|
|
247
|
-
*/
|
|
248
|
-
function parseMemberEmails(raw) {
|
|
249
|
-
const value = optionalNonEmpty(raw);
|
|
250
|
-
if (!value)
|
|
251
|
-
return undefined;
|
|
252
|
-
const emails = value
|
|
253
|
-
.split(",")
|
|
254
|
-
.map((part) => part.trim())
|
|
255
|
-
.filter((part) => part !== "");
|
|
256
|
-
if (emails.length === 0)
|
|
257
|
-
throw new Error("msg create --members needs at least one email address.");
|
|
258
|
-
const notAnAddress = emails.find((email) => !email.includes("@"));
|
|
259
|
-
if (notAnAddress) {
|
|
260
|
-
throw new Error(`msg create --members takes email addresses; "${notAnAddress}" is not one.`);
|
|
261
|
-
}
|
|
262
|
-
return emails;
|
|
263
|
-
}
|
|
264
|
-
export function parseMsgArgs(args) {
|
|
265
|
-
const values = parseNamedArgs(args, {
|
|
266
|
-
allowedFlags: [
|
|
267
|
-
"--home",
|
|
268
|
-
"--dashboard-url",
|
|
269
|
-
"--channel",
|
|
270
|
-
"--thread",
|
|
271
|
-
"--limit",
|
|
272
|
-
"--private",
|
|
273
|
-
"--members",
|
|
274
|
-
"--description",
|
|
275
|
-
"--json",
|
|
276
|
-
],
|
|
277
|
-
valueFlags: ["--home", "--dashboard-url", "--channel", "--thread", "--limit", "--members", "--description"],
|
|
278
|
-
});
|
|
279
|
-
const { action, rest } = parseMsgAction(values.positionals);
|
|
280
|
-
const { channelName, dmEmail } = parseMsgCreateTarget(action, rest);
|
|
281
|
-
const { isPrivate, memberEmails, description } = parseMsgCreateFlags(action, values);
|
|
282
|
-
const { channelRef, threadId, limit } = parseMsgReadTarget(action, rest, values);
|
|
283
|
-
return buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit);
|
|
284
|
-
}
|
|
285
|
-
function parseMsgAction(positionals) {
|
|
286
|
-
const first = positionals[0];
|
|
287
|
-
const action = (first === undefined ? "channels" : first);
|
|
288
|
-
if (!MSG_ACTIONS.has(action)) {
|
|
289
|
-
throw new Error(`Unknown msg command: ${first}. Try channels, read, send, thread, create, or dm.`);
|
|
290
|
-
}
|
|
291
|
-
return { action, rest: positionals.slice(first === undefined ? 0 : 1) };
|
|
292
|
-
}
|
|
293
|
-
function parseMsgCreateTarget(action, rest) {
|
|
294
|
-
let channelName;
|
|
295
|
-
let dmEmail;
|
|
296
|
-
if (action === "create") {
|
|
297
|
-
channelName = optionalNonEmpty(rest[0]);
|
|
298
|
-
if (!channelName)
|
|
299
|
-
throw new Error("msg create needs a channel name, e.g. `cockpit msg create general`.");
|
|
300
|
-
if (rest.length > 1)
|
|
301
|
-
throw new Error(`msg create takes one channel name, not ${rest.length}.`);
|
|
302
|
-
// `#general` is how a person says it and how `read`/`send` accept it, so
|
|
303
|
-
// the leading # is dropped here rather than becoming part of the name.
|
|
304
|
-
if (channelName.startsWith("#"))
|
|
305
|
-
channelName = channelName.slice(1);
|
|
306
|
-
if (channelName === "")
|
|
307
|
-
throw new Error("msg create needs a channel name, e.g. `cockpit msg create general`.");
|
|
308
|
-
}
|
|
309
|
-
else if (action === "dm") {
|
|
310
|
-
dmEmail = optionalNonEmpty(rest[0]);
|
|
311
|
-
if (!dmEmail)
|
|
312
|
-
throw new Error("msg dm needs the person's email address, e.g. `cockpit msg dm ada@example.com`.");
|
|
313
|
-
if (rest.length > 1)
|
|
314
|
-
throw new Error(`msg dm takes one email address, not ${rest.length}.`);
|
|
315
|
-
if (!dmEmail.includes("@"))
|
|
316
|
-
throw new Error(`msg dm takes an email address; "${dmEmail}" is not one.`);
|
|
317
|
-
}
|
|
318
|
-
return { channelName, dmEmail };
|
|
319
|
-
}
|
|
320
|
-
function parseMsgCreateFlags(action, values) {
|
|
321
|
-
const isPrivate = values.booleans.has("--private");
|
|
322
|
-
if (isPrivate && action !== "create") {
|
|
323
|
-
throw new Error("--private belongs to `cockpit msg create`.");
|
|
324
|
-
}
|
|
325
|
-
const memberEmails = parseMemberEmails(values.flags.get("--members"));
|
|
326
|
-
if (memberEmails && action !== "create") {
|
|
327
|
-
throw new Error("--members belongs to `cockpit msg create`.");
|
|
328
|
-
}
|
|
329
|
-
const description = optionalNonEmpty(values.flags.get("--description"));
|
|
330
|
-
if (description && action !== "create") {
|
|
331
|
-
throw new Error("--description belongs to `cockpit msg create`.");
|
|
332
|
-
}
|
|
333
|
-
return { isPrivate, memberEmails, description };
|
|
334
|
-
}
|
|
335
|
-
function parseMsgReadTarget(action, rest, values) {
|
|
336
|
-
let channelRef;
|
|
337
|
-
if (MSG_ACTIONS_NEEDING_A_CHANNEL.has(action)) {
|
|
338
|
-
channelRef = optionalNonEmpty(rest[0]);
|
|
339
|
-
if (!channelRef)
|
|
340
|
-
throw new Error(`msg ${action} needs a channel id or name.`);
|
|
341
|
-
if (rest.length > 1)
|
|
342
|
-
throw new Error(`msg ${action} takes one channel, not ${rest.length}.`);
|
|
343
|
-
}
|
|
344
|
-
else if (action === "thread") {
|
|
345
|
-
// `thread <id> --channel <channel>` — the channel is a flag here because
|
|
346
|
-
// the positional names the THREAD, not the channel (`--channel` below).
|
|
347
|
-
if (rest.length > 1)
|
|
348
|
-
throw new Error(`msg thread takes one thread id, not ${rest.length}.`);
|
|
349
|
-
}
|
|
350
|
-
else if (action !== "create" && action !== "dm" && rest.length > 0) {
|
|
351
|
-
throw new Error(`msg ${action} does not take "${rest[0]}".`);
|
|
352
|
-
}
|
|
353
|
-
const threadFlag = optionalNonEmpty(values.flags.get("--thread"));
|
|
354
|
-
const threadId = action === "thread" ? optionalNonEmpty(rest[0]) : threadFlag;
|
|
355
|
-
if (action === "thread" && !threadId)
|
|
356
|
-
throw new Error("msg thread needs a thread id.");
|
|
357
|
-
const channelFlag = optionalNonEmpty(values.flags.get("--channel"));
|
|
358
|
-
if (action === "thread")
|
|
359
|
-
channelRef = channelFlag;
|
|
360
|
-
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
361
|
-
if (limit !== undefined && action !== "read" && action !== "thread") {
|
|
362
|
-
throw new Error("--limit belongs to `cockpit msg read` or `cockpit msg thread`.");
|
|
363
|
-
}
|
|
364
|
-
return { channelRef, threadId, limit };
|
|
365
|
-
}
|
|
366
|
-
function buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit) {
|
|
367
|
-
return {
|
|
368
|
-
kind: "msg",
|
|
369
|
-
action,
|
|
370
|
-
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
371
|
-
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
372
|
-
...(channelRef ? { channelRef } : {}),
|
|
373
|
-
...(channelName ? { channelName } : {}),
|
|
374
|
-
...(dmEmail ? { dmEmail } : {}),
|
|
375
|
-
...(memberEmails ? { memberEmails } : {}),
|
|
376
|
-
...(description ? { description } : {}),
|
|
377
|
-
isPrivate,
|
|
378
|
-
...(threadId ? { threadId } : {}),
|
|
379
|
-
...(limit === undefined ? {} : { limit }),
|
|
380
|
-
json: values.booleans.has("--json"),
|
|
381
|
-
};
|
|
382
|
-
}
|
|
246
|
+
export { parseMsgArgs } from "./local-args-tower-msg.js";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit drills` argument parsing (BLI-19259).
|
|
3
|
+
*
|
|
4
|
+
* cockpit drills [list] your open drill, queue, bosses, streak, history
|
|
5
|
+
* cockpit drills start open the next drill (answer first: the cause is not shown)
|
|
6
|
+
* cockpit drills answer --confidence <1-5> [--text <words>] [--attempt <id>] (the answer on stdin when --text is absent)
|
|
7
|
+
* cockpit drills theme your theme and the choices
|
|
8
|
+
* cockpit drills theme set <theme> [--play <t>] [--watch <t>] [--train <t>]
|
|
9
|
+
* cockpit drills review your own week: hits, wins, highs, lows, plateaus, what to practise
|
|
10
|
+
*
|
|
11
|
+
* Every verb is one `/api/drills` door on this machine's device token, the same doors the web page, the MCP twins and JARVIS use.
|
|
12
|
+
*/
|
|
13
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
14
|
+
const ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15
|
+
const ONE_VERB = "drills takes one verb: list, start, answer, theme, theme set or review. Example: `cockpit drills` or `cockpit drills start`.";
|
|
16
|
+
const FLAGS_BY_ACTION = {
|
|
17
|
+
list: [],
|
|
18
|
+
start: [],
|
|
19
|
+
answer: ["--confidence", "--text", "--attempt"],
|
|
20
|
+
theme: [],
|
|
21
|
+
"theme-set": ["--play", "--watch", "--train"],
|
|
22
|
+
review: [],
|
|
23
|
+
};
|
|
24
|
+
const OWN_FLAGS = ["--confidence", "--text", "--attempt", "--play", "--watch", "--train"];
|
|
25
|
+
const COMMON_FLAGS = ["--home", "--dashboard-url", "--json"];
|
|
26
|
+
export function parseDrillsArgs(args) {
|
|
27
|
+
const values = parseNamedArgs(args, {
|
|
28
|
+
allowedFlags: [...OWN_FLAGS, ...COMMON_FLAGS],
|
|
29
|
+
valueFlags: [...OWN_FLAGS, "--home", "--dashboard-url"],
|
|
30
|
+
});
|
|
31
|
+
const first = values.positionals[0];
|
|
32
|
+
let action;
|
|
33
|
+
let consumed = 1;
|
|
34
|
+
if (first === undefined) {
|
|
35
|
+
action = "list";
|
|
36
|
+
consumed = 0;
|
|
37
|
+
}
|
|
38
|
+
else if (first === "theme" && values.positionals[1] === "set") {
|
|
39
|
+
action = "theme-set";
|
|
40
|
+
consumed = 2;
|
|
41
|
+
}
|
|
42
|
+
else if (first === "list" || first === "start" || first === "answer" || first === "theme" || first === "review") {
|
|
43
|
+
action = first;
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
throw new Error(ONE_VERB);
|
|
47
|
+
const given = [...values.flags.keys()].filter((flag) => OWN_FLAGS.includes(flag));
|
|
48
|
+
const owned = FLAGS_BY_ACTION[action];
|
|
49
|
+
const stray = given.find((flag) => !owned.includes(flag));
|
|
50
|
+
if (stray)
|
|
51
|
+
throw new Error(`${stray} does not belong to \`cockpit drills ${action === "theme-set" ? "theme set" : action}\`.`);
|
|
52
|
+
const rest = values.positionals.slice(consumed);
|
|
53
|
+
let theme;
|
|
54
|
+
if (action === "theme-set") {
|
|
55
|
+
theme = rest.join(" ").trim();
|
|
56
|
+
if (!theme)
|
|
57
|
+
throw new Error("`cockpit drills theme set` takes a theme: video-game, boxing, muay-thai, crime-drama, anime, sports, or your own words.");
|
|
58
|
+
}
|
|
59
|
+
else if (rest.length > 0)
|
|
60
|
+
throw new Error(`\`cockpit drills ${action}\` takes no positional arguments.`);
|
|
61
|
+
let confidence;
|
|
62
|
+
const rawConfidence = values.flags.get("--confidence");
|
|
63
|
+
if (action === "answer") {
|
|
64
|
+
if (rawConfidence === undefined)
|
|
65
|
+
throw new Error("`cockpit drills answer` needs --confidence 1 to 5: how sure you are, from a guess to certain.");
|
|
66
|
+
confidence = Number(rawConfidence);
|
|
67
|
+
if (!Number.isInteger(confidence) || confidence < 1 || confidence > 5)
|
|
68
|
+
throw new Error("--confidence wants a whole number from 1 (a guess) to 5 (certain).");
|
|
69
|
+
}
|
|
70
|
+
const attempt = optionalNonEmpty(values.flags.get("--attempt"));
|
|
71
|
+
if (attempt && !ID.test(attempt))
|
|
72
|
+
throw new Error("--attempt looks like 8f14e45f-ceea-467a-9575-1d3a4e7b2c10 (`cockpit drills` shows the open one).");
|
|
73
|
+
return {
|
|
74
|
+
kind: "drills",
|
|
75
|
+
action,
|
|
76
|
+
...(attempt ? { attemptId: attempt } : {}),
|
|
77
|
+
...(confidence !== undefined ? { confidence } : {}),
|
|
78
|
+
...(optionalNonEmpty(values.flags.get("--text")) ? { text: optionalNonEmpty(values.flags.get("--text")) } : {}),
|
|
79
|
+
...(theme ? { theme } : {}),
|
|
80
|
+
...(optionalNonEmpty(values.flags.get("--play")) ? { play: optionalNonEmpty(values.flags.get("--play")) } : {}),
|
|
81
|
+
...(optionalNonEmpty(values.flags.get("--watch")) ? { watch: optionalNonEmpty(values.flags.get("--watch")) } : {}),
|
|
82
|
+
...(optionalNonEmpty(values.flags.get("--train")) ? { train: optionalNonEmpty(values.flags.get("--train")) } : {}),
|
|
83
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
84
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
85
|
+
json: values.booleans.has("--json"),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit hits` argument parsing (BLI-19257).
|
|
3
|
+
*
|
|
4
|
+
* cockpit hits [--mine | --person <email|github-login|name> | --agent | --project <id>] [--days <n>] [--patterns] [--learning] [--top <n>] [--json]
|
|
5
|
+
* cockpit hits coach [--json]
|
|
6
|
+
* cockpit hits timeline [--weeks <n>] [--json]
|
|
7
|
+
* cockpit hits approve --scope <team|agents|project:<id>> [--version <n>] [--json]
|
|
8
|
+
*
|
|
9
|
+
* The hit ledger over `GET /api/hits`, `GET /api/hits/coach` and `POST /api/hits/patterns/approve` with this machine's
|
|
10
|
+
* device token. Who may see what is the database's rule and the door's refusal, not this parser's.
|
|
11
|
+
*/
|
|
12
|
+
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
13
|
+
const PROJECT_ID = /^[a-z0-9][a-z0-9-]{0,40}$/;
|
|
14
|
+
const SCOPE = /^(?:team|agents|project:[a-z0-9][a-z0-9-]{0,40})$/;
|
|
15
|
+
const READ_ONLY_FLAGS = ["--mine", "--person", "--agent", "--project", "--days", "--top", "--patterns", "--learning"];
|
|
16
|
+
export function parseHitsArgs(args) {
|
|
17
|
+
const values = parseNamedArgs(args, {
|
|
18
|
+
allowedFlags: [...READ_ONLY_FLAGS, "--scope", "--version", "--weeks", "--home", "--dashboard-url", "--json"],
|
|
19
|
+
valueFlags: ["--person", "--project", "--days", "--top", "--scope", "--version", "--weeks", "--home", "--dashboard-url"],
|
|
20
|
+
});
|
|
21
|
+
const verb = values.positionals[0];
|
|
22
|
+
if (values.positionals.length > 1 || (verb !== undefined && verb !== "read" && verb !== "coach" && verb !== "approve" && verb !== "timeline")) {
|
|
23
|
+
throw new Error("hits takes no verb (read the ledger), `timeline`, `coach` or `approve`. Example: `cockpit hits --mine` or `cockpit hits timeline`.");
|
|
24
|
+
}
|
|
25
|
+
const action = verb === "coach" || verb === "approve" || verb === "timeline" ? verb : "read";
|
|
26
|
+
const given = (flag) => values.flags.has(flag) || values.booleans.has(flag);
|
|
27
|
+
const common = {
|
|
28
|
+
kind: "hits",
|
|
29
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
30
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
31
|
+
json: values.booleans.has("--json"),
|
|
32
|
+
};
|
|
33
|
+
if (action !== "read") {
|
|
34
|
+
const stray = READ_ONLY_FLAGS.find(given);
|
|
35
|
+
if (stray)
|
|
36
|
+
throw new Error(`${stray} belongs to \`cockpit hits\` (reading the ledger), not to hits ${action}.`);
|
|
37
|
+
}
|
|
38
|
+
if (given("--weeks") && action !== "timeline")
|
|
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
|
+
}
|
|
59
|
+
if (given("--scope") || given("--version"))
|
|
60
|
+
throw new Error("--scope and --version belong to hits approve.");
|
|
61
|
+
const whoFlags = ["--mine", "--person", "--agent", "--project"].filter(given);
|
|
62
|
+
if (whoFlags.length > 1)
|
|
63
|
+
throw new Error(`Pick one of --mine, --person, --agent or --project (got ${whoFlags.join(" and ")}).`);
|
|
64
|
+
const person = optionalNonEmpty(values.flags.get("--person"))?.trim();
|
|
65
|
+
const project = optionalNonEmpty(values.flags.get("--project"));
|
|
66
|
+
if (project && !PROJECT_ID.test(project))
|
|
67
|
+
throw new Error("--project wants a registry project id like pearl-portal.");
|
|
68
|
+
const days = optionalPositiveInteger(values.flags.get("--days"), "--days");
|
|
69
|
+
if (days !== undefined && days > 365)
|
|
70
|
+
throw new Error("--days must be 365 or fewer.");
|
|
71
|
+
const top = optionalPositiveInteger(values.flags.get("--top"), "--top");
|
|
72
|
+
if (top !== undefined && top > 50)
|
|
73
|
+
throw new Error("--top must be 50 or fewer.");
|
|
74
|
+
const learning = values.booleans.has("--learning");
|
|
75
|
+
const who = values.booleans.has("--mine") ? "mine" : person ? "person" : values.booleans.has("--agent") ? "agents" : project ? "project" : undefined;
|
|
76
|
+
if (learning && who !== "mine" && who !== "person")
|
|
77
|
+
throw new Error("--learning is read for one person: use --mine or --person <name>.");
|
|
78
|
+
return {
|
|
79
|
+
...common,
|
|
80
|
+
action,
|
|
81
|
+
...(who ? { who } : {}),
|
|
82
|
+
...(person ? { person } : {}),
|
|
83
|
+
...(project ? { project } : {}),
|
|
84
|
+
...(days !== undefined ? { days } : {}),
|
|
85
|
+
...(top !== undefined ? { top } : {}),
|
|
86
|
+
...(values.booleans.has("--patterns") ? { patterns: true } : {}),
|
|
87
|
+
...(learning ? { learning: true } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function versionOf(raw) {
|
|
91
|
+
const version = optionalPositiveInteger(raw, "--version");
|
|
92
|
+
return version === undefined ? {} : { version };
|
|
93
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit intentions` argument parsing (BLI-19248).
|
|
3
|
+
*
|
|
4
|
+
* cockpit intentions [list] [--status open|overdue|done|dropped|all] [--project <name>] [--limit <n>] [--json]
|
|
5
|
+
* cockpit intentions show <id> [--json]
|
|
6
|
+
* cockpit intentions done <id> --evidence "<what settled it>" [--by person|agent] [--json]
|
|
7
|
+
* cockpit intentions drop <id> --evidence "<why>" [--by person|agent] [--json]
|
|
8
|
+
* cockpit intentions reopen <id> [--json]
|
|
9
|
+
*
|
|
10
|
+
* Prospective memory: what a person or an agent said they would do later, tied to a trigger. Reads and writes
|
|
11
|
+
* `/api/intentions` with this machine's device token.
|
|
12
|
+
*/
|
|
13
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
14
|
+
export const INTENTION_LIST_STATUSES = ["open", "overdue", "done", "dropped", "all"];
|
|
15
|
+
const ACTIONS = ["list", "show", "done", "drop", "reopen"];
|
|
16
|
+
export function parseIntentionsArgs(args) {
|
|
17
|
+
const values = parseNamedArgs(args, {
|
|
18
|
+
allowedFlags: ["--status", "--project", "--limit", "--evidence", "--by", "--home", "--dashboard-url", "--json"],
|
|
19
|
+
valueFlags: ["--status", "--project", "--limit", "--evidence", "--by", "--home", "--dashboard-url"],
|
|
20
|
+
});
|
|
21
|
+
const first = values.positionals[0];
|
|
22
|
+
const action = (first ?? "list");
|
|
23
|
+
if (!ACTIONS.includes(action)) {
|
|
24
|
+
throw new Error("intentions takes one verb: list, show, done, drop or reopen. Example: `cockpit intentions list --status overdue`.");
|
|
25
|
+
}
|
|
26
|
+
const rest = values.positionals.slice(first ? 1 : 0);
|
|
27
|
+
const status = optionalNonEmpty(values.flags.get("--status"));
|
|
28
|
+
const project = optionalNonEmpty(values.flags.get("--project"));
|
|
29
|
+
const evidence = optionalNonEmpty(values.flags.get("--evidence"))?.trim();
|
|
30
|
+
const by = optionalNonEmpty(values.flags.get("--by"));
|
|
31
|
+
const rawLimit = optionalNonEmpty(values.flags.get("--limit"));
|
|
32
|
+
if (status && !INTENTION_LIST_STATUSES.includes(status))
|
|
33
|
+
throw new Error(`--status wants one of ${INTENTION_LIST_STATUSES.join(", ")}.`);
|
|
34
|
+
if (by && by !== "person" && by !== "agent")
|
|
35
|
+
throw new Error("--by wants person or agent.");
|
|
36
|
+
const limit = rawLimit ? Number(rawLimit) : undefined;
|
|
37
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 1 || limit > 200))
|
|
38
|
+
throw new Error("--limit wants a whole number from 1 to 200.");
|
|
39
|
+
if (action === "list") {
|
|
40
|
+
if (rest.length)
|
|
41
|
+
throw new Error("intentions list takes no id; use `cockpit intentions show <id>`.");
|
|
42
|
+
if (evidence || by)
|
|
43
|
+
throw new Error("--evidence and --by belong to intentions done and drop.");
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
if (rest.length !== 1)
|
|
47
|
+
throw new Error(`intentions ${action} takes exactly one id (the uuid or the 8-character prefix list prints).`);
|
|
48
|
+
if (status || project || limit !== undefined)
|
|
49
|
+
throw new Error("--status, --project and --limit belong to intentions list.");
|
|
50
|
+
if (action === "done" || action === "drop") {
|
|
51
|
+
if (!evidence || evidence.length < 3 || evidence.length > 300)
|
|
52
|
+
throw new Error(`intentions ${action} needs --evidence "<what settled it>" of 3 to 300 characters.`);
|
|
53
|
+
}
|
|
54
|
+
else if (evidence || by)
|
|
55
|
+
throw new Error("--evidence and --by belong to intentions done and drop.");
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
kind: "intentions",
|
|
59
|
+
action,
|
|
60
|
+
...(rest[0] ? { id: rest[0] } : {}),
|
|
61
|
+
...(status ? { status } : {}),
|
|
62
|
+
...(project ? { project } : {}),
|
|
63
|
+
...(limit !== undefined ? { limit } : {}),
|
|
64
|
+
...(evidence ? { evidence } : {}),
|
|
65
|
+
...(by ? { by: by } : {}),
|
|
66
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
67
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
68
|
+
json: values.booleans.has("--json"),
|
|
69
|
+
};
|
|
70
|
+
}
|