@bli-cockpit/cli 0.2.224 → 0.2.236
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/ci.js +23 -0
- package/dist/commands/docs-export.js +62 -0
- package/dist/commands/docs.js +3 -0
- 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-docs-msg.js +9 -3
- 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-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 +19 -7
- package/dist/commands/local-help-commands.js +3 -0
- package/dist/commands/local-help.js +24 -4
- 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/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/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,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))
|
|
@@ -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,6 +239,7 @@ 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
|
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit logs search` argument parsing (BLI-20654).
|
|
3
|
+
*
|
|
4
|
+
* cockpit logs search <project> [--source gcp|supabase|vercel|railway|deploys|db-host|<label>] [--stream api|postgres|auth]
|
|
5
|
+
* [--since 30m|6h (default 1h) | --from <iso> [--to <iso>]] [--status 500|5xx|500-503] [--method GET] [--route <text>]
|
|
6
|
+
* [--severity error] [--q <text>] [--limit <n>] [--context] [--json]
|
|
7
|
+
*
|
|
8
|
+
* Plain words: it asks Tower to read the project's raw logs NOW, from the platform, and print the matching lines, scrubbed.
|
|
9
|
+
* Owner or admin only (the server decides). The values are validated by the server; this parser only checks the shape.
|
|
10
|
+
*/
|
|
11
|
+
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
12
|
+
export function parseLogsSearchArgs(args) {
|
|
13
|
+
const values = parseNamedArgs(args, {
|
|
14
|
+
allowedFlags: ["--source", "--stream", "--since", "--from", "--to", "--status", "--method", "--route", "--severity", "--q", "--limit", "--context", "--home", "--dashboard-url", "--json"],
|
|
15
|
+
valueFlags: ["--source", "--stream", "--since", "--from", "--to", "--status", "--method", "--route", "--severity", "--q", "--limit", "--home", "--dashboard-url"],
|
|
16
|
+
});
|
|
17
|
+
const project = values.positionals[0];
|
|
18
|
+
if (!project || values.positionals.length > 1)
|
|
19
|
+
throw new Error("Use cockpit logs search <project> --since 30m [--source gcp] [--status 500] [--method GET] [--route <text>] [--q <text>] [--context] [--json].");
|
|
20
|
+
const from = optionalNonEmpty(values.flags.get("--from"));
|
|
21
|
+
// No window named means the last hour: a search is for something that just happened, and a default keeps the bare verb runnable.
|
|
22
|
+
const since = optionalNonEmpty(values.flags.get("--since")) ?? (from ? undefined : "1h");
|
|
23
|
+
if (since && from)
|
|
24
|
+
throw new Error("logs search takes --since or --from, not both.");
|
|
25
|
+
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
26
|
+
const pick = (name) => optionalNonEmpty(values.flags.get(name));
|
|
27
|
+
return {
|
|
28
|
+
kind: "logs",
|
|
29
|
+
source: project,
|
|
30
|
+
stored: false,
|
|
31
|
+
list: false,
|
|
32
|
+
allLevels: false,
|
|
33
|
+
search: {
|
|
34
|
+
...(pick("--source") ? { logSource: pick("--source") } : {}),
|
|
35
|
+
...(pick("--stream") ? { stream: pick("--stream") } : {}),
|
|
36
|
+
...(since ? { since } : {}),
|
|
37
|
+
...(from ? { from } : {}),
|
|
38
|
+
...(pick("--to") ? { to: pick("--to") } : {}),
|
|
39
|
+
...(pick("--status") ? { status: pick("--status") } : {}),
|
|
40
|
+
...(pick("--method") ? { method: pick("--method") } : {}),
|
|
41
|
+
...(pick("--route") ? { route: pick("--route") } : {}),
|
|
42
|
+
...(pick("--severity") ? { severity: pick("--severity") } : {}),
|
|
43
|
+
...(pick("--q") ? { q: pick("--q") } : {}),
|
|
44
|
+
...(limit === undefined ? {} : { limit }),
|
|
45
|
+
context: values.booleans.has("--context"),
|
|
46
|
+
},
|
|
47
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
48
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
49
|
+
json: values.booleans.has("--json"),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -16,12 +16,15 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { LOG_PROJECT_PATTERN } from "@bli-cockpit/telemetry-core/log-digest";
|
|
18
18
|
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
19
|
+
import { parseLogsSearchArgs } from "./local-args-tower-logs-search.js";
|
|
19
20
|
/** The source spellings, for the usage line and the refusal. `railway:<service>` takes any service name. */
|
|
20
21
|
export const LOGS_SOURCES = ["collector", "dashboard", "railway", "railway:<service>", "-", "<project>"];
|
|
21
22
|
/** The spellings that read a local log; every other name is a registry project (stored digests). */
|
|
22
23
|
const LOCAL_SOURCE_PATTERN = /^(collector|dashboard|railway|railway:[A-Za-z0-9][A-Za-z0-9._-]{0,63}|-)$/;
|
|
23
24
|
const SINCE_PATTERN = /^(\d{1,4}[smhd]|\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:\d{2})?)?)$/;
|
|
24
25
|
export function parseLogsArgs(args) {
|
|
26
|
+
if (args[0] === "search")
|
|
27
|
+
return parseLogsSearchArgs(args.slice(1));
|
|
25
28
|
if (args[0] === "keys") {
|
|
26
29
|
const action = args[1];
|
|
27
30
|
const source = args[2];
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
* them; `parseMemoryArgs` in `local-args-collector-status.ts` routes to here.
|
|
33
33
|
*/
|
|
34
34
|
import { parseMemoryMapArgs } from "./memory-map.js";
|
|
35
|
+
import { parseMemoryRepeatsArgs } from "./memory-repeats.js";
|
|
35
36
|
import { parseMemoryRulesArgs } from "./memory-rules.js";
|
|
36
37
|
import { optionalNonEmpty, optionalUrl, parseNamedArgs, workRootFlagValue, } from "./local-arg-values.js";
|
|
37
38
|
export const MEMORY_STORE_ACTIONS = new Set([
|
|
38
39
|
"map",
|
|
40
|
+
"repeats",
|
|
39
41
|
"rules",
|
|
40
42
|
"knowledge",
|
|
41
43
|
"search",
|
|
@@ -54,6 +56,10 @@ export const MEMORY_STORE_ACTIONS = new Set([
|
|
|
54
56
|
"consolidation",
|
|
55
57
|
// BLI-13376: the originals behind a consolidated memory, one id.
|
|
56
58
|
"chain",
|
|
59
|
+
// BLI-19255: the raw session or meeting behind a memory, as a bounded verbatim excerpt.
|
|
60
|
+
"source",
|
|
61
|
+
// BLI-19249: a READ of the nightly memory exam score. The exam is a Railway job; owner-only.
|
|
62
|
+
"exam",
|
|
57
63
|
"requeue",
|
|
58
64
|
]);
|
|
59
65
|
const ALLOWED_FLAGS = [
|
|
@@ -64,6 +70,12 @@ const ALLOWED_FLAGS = [
|
|
|
64
70
|
"--container",
|
|
65
71
|
"--limit",
|
|
66
72
|
"--no-recent",
|
|
73
|
+
"--include-originals",
|
|
74
|
+
"--no-originals",
|
|
75
|
+
"--include-source",
|
|
76
|
+
"--no-source",
|
|
77
|
+
"--query",
|
|
78
|
+
"--chars",
|
|
67
79
|
"--custom-id",
|
|
68
80
|
"--alias",
|
|
69
81
|
"--extract",
|
|
@@ -75,6 +87,8 @@ const ALLOWED_FLAGS = [
|
|
|
75
87
|
"--source",
|
|
76
88
|
"--dry-run",
|
|
77
89
|
"--apply",
|
|
90
|
+
"--misses",
|
|
91
|
+
"--night",
|
|
78
92
|
"--json",
|
|
79
93
|
];
|
|
80
94
|
const VALUE_FLAGS = [
|
|
@@ -84,6 +98,8 @@ const VALUE_FLAGS = [
|
|
|
84
98
|
"--repo",
|
|
85
99
|
"--container",
|
|
86
100
|
"--limit",
|
|
101
|
+
"--query",
|
|
102
|
+
"--chars",
|
|
87
103
|
"--custom-id",
|
|
88
104
|
"--alias",
|
|
89
105
|
"--file",
|
|
@@ -91,10 +107,13 @@ const VALUE_FLAGS = [
|
|
|
91
107
|
"--since",
|
|
92
108
|
"--until",
|
|
93
109
|
"--source",
|
|
110
|
+
"--night",
|
|
94
111
|
];
|
|
95
112
|
export function parseMemoryStoreArgs(action, args) {
|
|
96
113
|
if (action === "map")
|
|
97
114
|
return parseMemoryMapArgs(args);
|
|
115
|
+
if (action === "repeats")
|
|
116
|
+
return parseMemoryRepeatsArgs(args);
|
|
98
117
|
if (action === "rules" || action === "knowledge")
|
|
99
118
|
return parseMemoryRulesArgs(args, action);
|
|
100
119
|
const values = parseNamedArgs(args, { allowedFlags: ALLOWED_FLAGS, valueFlags: VALUE_FLAGS });
|
|
@@ -106,6 +125,21 @@ export function parseMemoryStoreArgs(action, args) {
|
|
|
106
125
|
if (action !== "search" && (values.flags.has("--limit") || values.booleans.has("--no-recent"))) {
|
|
107
126
|
throw new Error("--limit and --no-recent only apply to memory search.");
|
|
108
127
|
}
|
|
128
|
+
if (action !== "search" && (values.booleans.has("--include-originals") || values.booleans.has("--no-originals"))) {
|
|
129
|
+
throw new Error("--include-originals and --no-originals only apply to memory search.");
|
|
130
|
+
}
|
|
131
|
+
if (values.booleans.has("--include-originals") && values.booleans.has("--no-originals")) {
|
|
132
|
+
throw new Error("--include-originals and --no-originals are opposites; name one.");
|
|
133
|
+
}
|
|
134
|
+
if (action !== "search" && (values.booleans.has("--include-source") || values.booleans.has("--no-source"))) {
|
|
135
|
+
throw new Error("--include-source and --no-source only apply to memory search.");
|
|
136
|
+
}
|
|
137
|
+
if (values.booleans.has("--include-source") && values.booleans.has("--no-source")) {
|
|
138
|
+
throw new Error("--include-source and --no-source are opposites; name one.");
|
|
139
|
+
}
|
|
140
|
+
if (action !== "source" && (values.flags.has("--query") || values.flags.has("--chars"))) {
|
|
141
|
+
throw new Error("--query and --chars only apply to memory source.");
|
|
142
|
+
}
|
|
109
143
|
if (action === "consolidation" && rest.length > 0) {
|
|
110
144
|
throw new Error("memory consolidation takes no positionals: `cockpit memory consolidation [--container <tag>] [--json]`.");
|
|
111
145
|
}
|
|
@@ -126,6 +160,9 @@ export function parseMemoryStoreArgs(action, args) {
|
|
|
126
160
|
|| values.booleans.has("--dry-run") || values.booleans.has("--apply"))) {
|
|
127
161
|
throw new Error("--since, --until, --source, --dry-run and --apply only apply to memory requeue.");
|
|
128
162
|
}
|
|
163
|
+
if (action !== "exam" && (values.flags.has("--night") || values.booleans.has("--misses"))) {
|
|
164
|
+
throw new Error("--night and --misses only apply to memory exam.");
|
|
165
|
+
}
|
|
129
166
|
if (values.flags.has("--file") && action !== "save" && action !== "update") {
|
|
130
167
|
throw new Error("--file only applies to memory save and memory update.");
|
|
131
168
|
}
|
|
@@ -187,11 +224,40 @@ export function parseMemoryStoreArgs(action, args) {
|
|
|
187
224
|
const query = rest.join(" ").trim();
|
|
188
225
|
if (!query)
|
|
189
226
|
throw new Error("memory search needs a question, e.g. `cockpit memory search \"pagination rule\"`.");
|
|
190
|
-
|
|
227
|
+
// BLI-19246: both flags absent leaves the choice to the door (exact-detail questions reach the originals).
|
|
228
|
+
const includeOriginals = values.booleans.has("--include-originals") ? true : values.booleans.has("--no-originals") ? false : undefined;
|
|
229
|
+
// BLI-19255: the raw source behind the top hits; absent lets the door decide by the question.
|
|
230
|
+
const includeSource = values.booleans.has("--include-source") ? true : values.booleans.has("--no-source") ? false : undefined;
|
|
231
|
+
return {
|
|
232
|
+
...command, action, query, limit: readLimit(values.flags.get("--limit")), includeRecent: !values.booleans.has("--no-recent"),
|
|
233
|
+
...(includeOriginals === undefined ? {} : { includeOriginals }),
|
|
234
|
+
...(includeSource === undefined ? {} : { includeSource }),
|
|
235
|
+
};
|
|
191
236
|
}
|
|
192
237
|
if (action === "consolidation") {
|
|
193
238
|
return { ...command, action };
|
|
194
239
|
}
|
|
240
|
+
if (action === "exam") {
|
|
241
|
+
if (rest.length > 0)
|
|
242
|
+
throw new Error("memory exam takes no words: `cockpit memory exam [--night <YYYY-MM-DD>] [--misses] [--json]`.");
|
|
243
|
+
const night = optionalNonEmpty(values.flags.get("--night"));
|
|
244
|
+
if (night && !/^\d{4}-\d{2}-\d{2}$/.test(night))
|
|
245
|
+
throw new Error("--night must be a date like 2026-10-14.");
|
|
246
|
+
return { ...command, action, ...(night ? { night } : {}), misses: values.booleans.has("--misses") };
|
|
247
|
+
}
|
|
248
|
+
if (action === "source") {
|
|
249
|
+
// BLI-19255: one memory, named by the id `cockpit memory search` printed; `--query` steers which part of the source.
|
|
250
|
+
const memoryId = optionalNonEmpty(rest[0]);
|
|
251
|
+
if (!memoryId)
|
|
252
|
+
throw new Error("memory source needs the memory id `cockpit memory search` printed.");
|
|
253
|
+
if (rest.length > 1)
|
|
254
|
+
throw new Error("memory source takes one id.");
|
|
255
|
+
const rawChars = values.flags.get("--chars");
|
|
256
|
+
const chars = rawChars === undefined ? undefined : Number(rawChars);
|
|
257
|
+
if (chars !== undefined && (!Number.isInteger(chars) || chars < 200 || chars > 6000))
|
|
258
|
+
throw new Error("--chars is a whole number from 200 to 6000.");
|
|
259
|
+
return { ...command, action, memoryId, ...(values.flags.get("--query") ? { sourceQuery: values.flags.get("--query") } : {}), ...(chars === undefined ? {} : { sourceChars: chars }) };
|
|
260
|
+
}
|
|
195
261
|
if (action === "private" || action === "public" || action === "chain") {
|
|
196
262
|
// BLI-12927: one memory, named by the id `cockpit memory search` printed.
|
|
197
263
|
const memoryId = optionalNonEmpty(rest[0]);
|