@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,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]);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
2
|
+
const MSG_ACTIONS = new Set(["channels", "read", "send", "thread", "create", "dm", "show"]);
|
|
3
|
+
const MSG_ACTIONS_NEEDING_A_CHANNEL = new Set(["read", "send"]);
|
|
4
|
+
/**
|
|
5
|
+
* `--members a@x.test,b@y.test` — addresses only, split on commas, never
|
|
6
|
+
* fuzzy (BLI-3749). The door resolves each one exactly, so a typo comes back
|
|
7
|
+
* as `person_not_found` naming the address rather than as a stranger added
|
|
8
|
+
* to a private channel.
|
|
9
|
+
*/
|
|
10
|
+
function parseMemberEmails(raw) {
|
|
11
|
+
const value = optionalNonEmpty(raw);
|
|
12
|
+
if (!value)
|
|
13
|
+
return undefined;
|
|
14
|
+
const emails = value
|
|
15
|
+
.split(",")
|
|
16
|
+
.map((part) => part.trim())
|
|
17
|
+
.filter((part) => part !== "");
|
|
18
|
+
if (emails.length === 0)
|
|
19
|
+
throw new Error("msg create --members needs at least one email address.");
|
|
20
|
+
const notAnAddress = emails.find((email) => !email.includes("@"));
|
|
21
|
+
if (notAnAddress) {
|
|
22
|
+
throw new Error(`msg create --members takes email addresses; "${notAnAddress}" is not one.`);
|
|
23
|
+
}
|
|
24
|
+
return emails;
|
|
25
|
+
}
|
|
26
|
+
export function parseMsgArgs(args) {
|
|
27
|
+
const values = parseNamedArgs(args, {
|
|
28
|
+
allowedFlags: [
|
|
29
|
+
"--home",
|
|
30
|
+
"--dashboard-url",
|
|
31
|
+
"--channel",
|
|
32
|
+
"--thread",
|
|
33
|
+
"--limit",
|
|
34
|
+
"--before",
|
|
35
|
+
"--private",
|
|
36
|
+
"--members",
|
|
37
|
+
"--description",
|
|
38
|
+
"--json",
|
|
39
|
+
],
|
|
40
|
+
valueFlags: ["--home", "--dashboard-url", "--channel", "--thread", "--limit", "--members", "--description", "--before"],
|
|
41
|
+
});
|
|
42
|
+
const { action, rest } = parseMsgAction(values.positionals);
|
|
43
|
+
if (values.flags.has("--before") && action !== "read" && action !== "thread") {
|
|
44
|
+
throw new Error("--before belongs to msg read or msg thread.");
|
|
45
|
+
}
|
|
46
|
+
const { channelName, dmEmail } = parseMsgCreateTarget(action, rest);
|
|
47
|
+
const { isPrivate, memberEmails, description } = parseMsgCreateFlags(action, values);
|
|
48
|
+
const { channelRef, threadId, limit } = parseMsgReadTarget(action, rest, values);
|
|
49
|
+
return buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit);
|
|
50
|
+
}
|
|
51
|
+
function parseMsgAction(positionals) {
|
|
52
|
+
const first = positionals[0];
|
|
53
|
+
const action = (first === undefined ? "channels" : first);
|
|
54
|
+
if (!MSG_ACTIONS.has(action)) {
|
|
55
|
+
throw new Error(`Unknown msg command: ${first}. Try channels, read, send, thread, create, dm, or show.`);
|
|
56
|
+
}
|
|
57
|
+
return { action, rest: positionals.slice(first === undefined ? 0 : 1) };
|
|
58
|
+
}
|
|
59
|
+
function parseMsgCreateTarget(action, rest) {
|
|
60
|
+
let channelName;
|
|
61
|
+
let dmEmail;
|
|
62
|
+
if (action === "create") {
|
|
63
|
+
channelName = optionalNonEmpty(rest[0]);
|
|
64
|
+
if (!channelName)
|
|
65
|
+
throw new Error("msg create needs a channel name, e.g. `cockpit msg create general`.");
|
|
66
|
+
if (rest.length > 1)
|
|
67
|
+
throw new Error(`msg create takes one channel name, not ${rest.length}.`);
|
|
68
|
+
// `#general` is how a person says it and how `read`/`send` accept it, so
|
|
69
|
+
// the leading # is dropped here rather than becoming part of the name.
|
|
70
|
+
if (channelName.startsWith("#"))
|
|
71
|
+
channelName = channelName.slice(1);
|
|
72
|
+
if (channelName === "")
|
|
73
|
+
throw new Error("msg create needs a channel name, e.g. `cockpit msg create general`.");
|
|
74
|
+
}
|
|
75
|
+
else if (action === "dm") {
|
|
76
|
+
dmEmail = optionalNonEmpty(rest[0]);
|
|
77
|
+
if (!dmEmail)
|
|
78
|
+
throw new Error("msg dm needs the person's email address, e.g. `cockpit msg dm ada@example.com`.");
|
|
79
|
+
if (rest.length > 1)
|
|
80
|
+
throw new Error(`msg dm takes one email address, not ${rest.length}.`);
|
|
81
|
+
if (!dmEmail.includes("@"))
|
|
82
|
+
throw new Error(`msg dm takes an email address; "${dmEmail}" is not one.`);
|
|
83
|
+
}
|
|
84
|
+
return { channelName, dmEmail };
|
|
85
|
+
}
|
|
86
|
+
function parseMsgCreateFlags(action, values) {
|
|
87
|
+
const isPrivate = values.booleans.has("--private");
|
|
88
|
+
if (isPrivate && action !== "create") {
|
|
89
|
+
throw new Error("--private belongs to `cockpit msg create`.");
|
|
90
|
+
}
|
|
91
|
+
const memberEmails = parseMemberEmails(values.flags.get("--members"));
|
|
92
|
+
if (memberEmails && action !== "create") {
|
|
93
|
+
throw new Error("--members belongs to `cockpit msg create`.");
|
|
94
|
+
}
|
|
95
|
+
const description = optionalNonEmpty(values.flags.get("--description"));
|
|
96
|
+
if (description && action !== "create") {
|
|
97
|
+
throw new Error("--description belongs to `cockpit msg create`.");
|
|
98
|
+
}
|
|
99
|
+
return { isPrivate, memberEmails, description };
|
|
100
|
+
}
|
|
101
|
+
function parseMsgReadTarget(action, rest, values) {
|
|
102
|
+
let channelRef;
|
|
103
|
+
if (MSG_ACTIONS_NEEDING_A_CHANNEL.has(action)) {
|
|
104
|
+
channelRef = optionalNonEmpty(rest[0]);
|
|
105
|
+
if (!channelRef)
|
|
106
|
+
throw new Error(`msg ${action} needs a channel id or name.`);
|
|
107
|
+
if (rest.length > 1)
|
|
108
|
+
throw new Error(`msg ${action} takes one channel, not ${rest.length}.`);
|
|
109
|
+
}
|
|
110
|
+
else if (action === "thread" || action === "show") {
|
|
111
|
+
// `thread <id> --channel <channel>` — the channel is a flag here because
|
|
112
|
+
// the positional names the THREAD, not the channel (`--channel` below).
|
|
113
|
+
if (rest.length > 1)
|
|
114
|
+
throw new Error(`msg thread takes one thread id, not ${rest.length}.`);
|
|
115
|
+
}
|
|
116
|
+
else if (action !== "create" && action !== "dm" && rest.length > 0) {
|
|
117
|
+
throw new Error(`msg ${action} does not take "${rest[0]}".`);
|
|
118
|
+
}
|
|
119
|
+
const threadFlag = optionalNonEmpty(values.flags.get("--thread"));
|
|
120
|
+
const threadId = (action === "thread" || action === "show") ? optionalNonEmpty(rest[0]) : threadFlag;
|
|
121
|
+
if ((action === "thread" || action === "show") && !threadId)
|
|
122
|
+
throw new Error("msg thread needs a thread id.");
|
|
123
|
+
const channelFlag = optionalNonEmpty(values.flags.get("--channel"));
|
|
124
|
+
if (action === "thread")
|
|
125
|
+
channelRef = channelFlag;
|
|
126
|
+
const limit = optionalPositiveInteger(values.flags.get("--limit"), "--limit");
|
|
127
|
+
if (limit !== undefined && action !== "read" && action !== "thread") {
|
|
128
|
+
throw new Error("--limit belongs to `cockpit msg read` or `cockpit msg thread`.");
|
|
129
|
+
}
|
|
130
|
+
return { channelRef, threadId, limit };
|
|
131
|
+
}
|
|
132
|
+
function buildMsgCommand(values, action, channelRef, channelName, dmEmail, memberEmails, description, isPrivate, threadId, limit) {
|
|
133
|
+
return {
|
|
134
|
+
kind: "msg",
|
|
135
|
+
action,
|
|
136
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
137
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
138
|
+
...(values.flags.has("--before") ? { before: optionalNonEmpty(values.flags.get("--before")) } : {}),
|
|
139
|
+
...(channelRef ? { channelRef } : {}),
|
|
140
|
+
...(channelName ? { channelName } : {}),
|
|
141
|
+
...(dmEmail ? { dmEmail } : {}),
|
|
142
|
+
...(memberEmails ? { memberEmails } : {}),
|
|
143
|
+
...(description ? { description } : {}),
|
|
144
|
+
isPrivate,
|
|
145
|
+
...(threadId ? { threadId } : {}),
|
|
146
|
+
...(limit === undefined ? {} : { limit }),
|
|
147
|
+
json: values.booleans.has("--json"),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -200,6 +200,7 @@ const NOTES_ACTIONS = new Set([
|
|
|
200
200
|
"paste",
|
|
201
201
|
"share",
|
|
202
202
|
"unshare",
|
|
203
|
+
"summarize",
|
|
203
204
|
"move",
|
|
204
205
|
"place",
|
|
205
206
|
// BLI-4383: the notetaker a person connects, and the meetings it records.
|
|
@@ -223,6 +224,7 @@ const NOTES_ACTIONS_NEEDING_A_NOTE = new Set([
|
|
|
223
224
|
"show",
|
|
224
225
|
"share",
|
|
225
226
|
"unshare",
|
|
227
|
+
"summarize",
|
|
226
228
|
"move",
|
|
227
229
|
"place",
|
|
228
230
|
// BLI-12926: `recordings` names the note, `recording` names the recording.
|
|
@@ -299,7 +301,7 @@ export function parseNotesArgs(args) {
|
|
|
299
301
|
const first = values.positionals[0];
|
|
300
302
|
const action = (first === undefined ? "list" : first);
|
|
301
303
|
if (!NOTES_ACTIONS.has(action)) {
|
|
302
|
-
throw new Error(`Unknown notes command: ${first}. Try list, show, shelf, shelves, upload, paste, share, unshare, move, place, accounts, connect, detach, sync, sync-status, recordings, recording, webhook, granola-status, or transcription-stats.`);
|
|
304
|
+
throw new Error(`Unknown notes command: ${first}. Try list, show, shelf, shelves, upload, paste, share, unshare, summarize, move, place, accounts, connect, detach, sync, sync-status, recordings, recording, webhook, granola-status, or transcription-stats.`);
|
|
303
305
|
}
|
|
304
306
|
const rest = values.positionals.slice(first === undefined ? 0 : 1);
|
|
305
307
|
const json = values.booleans.has("--json");
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit people` argument parsing (ticket 19252).
|
|
3
|
+
*
|
|
4
|
+
* cockpit people show [<person>] [--json]
|
|
5
|
+
*
|
|
6
|
+
* The owner-only people models: one profile per principal and teammate. Reads `GET /api/people` with this machine's
|
|
7
|
+
* device token; only the company owner (and the owner's own owner-scoped brain token) is answered. A name of several
|
|
8
|
+
* words needs no quotes. No person lists everyone with a profile.
|
|
9
|
+
*/
|
|
10
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
11
|
+
export function parsePeopleArgs(args) {
|
|
12
|
+
const values = parseNamedArgs(args, { allowedFlags: ["--home", "--dashboard-url", "--json"], valueFlags: ["--home", "--dashboard-url"] });
|
|
13
|
+
const [action, ...rest] = values.positionals;
|
|
14
|
+
if (action !== "show")
|
|
15
|
+
throw new Error("people takes one verb: `show`. Example: `cockpit people show` or `cockpit people show Ada Lovelace`.");
|
|
16
|
+
const person = optionalNonEmpty(rest.join(" ").trim());
|
|
17
|
+
return {
|
|
18
|
+
kind: "people",
|
|
19
|
+
action: "show",
|
|
20
|
+
...(person ? { person } : {}),
|
|
21
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
22
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
23
|
+
json: values.booleans.has("--json"),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
2
2
|
export function parseUsageArgs(args) {
|
|
3
|
-
const values = parseNamedArgs(args, { allowedFlags: ["--by-task", "--task", "--by-subject", "--subject", "--tool", "--cursor", "--limit", "--repo", "--session", "--from-window", "--min-confidence", "--by-topic", "--by-repo", "--person", "--all", "--since", "--until", "--include-automated", "--detail", "--home", "--dashboard-url", "--json"], valueFlags: ["--task", "--subject", "--tool", "--cursor", "--limit", "--repo", "--session", "--from-window", "--min-confidence", "--person", "--since", "--until", "--home", "--dashboard-url"] });
|
|
4
|
-
if (["--by-task", "--by-repo", "--by-topic", "--by-subject"].filter(flag => values.booleans.has(flag)).length > 1)
|
|
5
|
-
throw new Error("--by-repo and --by-topic cannot be used together or with --by-subject or --by-
|
|
3
|
+
const values = parseNamedArgs(args, { allowedFlags: ["--by-client", "--by-task", "--task", "--by-subject", "--subject", "--tool", "--cursor", "--limit", "--repo", "--session", "--from-window", "--min-confidence", "--by-topic", "--by-repo", "--person", "--all", "--since", "--until", "--include-automated", "--detail", "--home", "--dashboard-url", "--json"], valueFlags: ["--task", "--subject", "--tool", "--cursor", "--limit", "--repo", "--session", "--from-window", "--min-confidence", "--person", "--since", "--until", "--home", "--dashboard-url"] });
|
|
4
|
+
if (["--by-task", "--by-repo", "--by-topic", "--by-subject", "--by-client"].filter(flag => values.booleans.has(flag)).length > 1)
|
|
5
|
+
throw new Error("--by-repo and --by-topic cannot be used together or with --by-subject, --by-task or --by-client.");
|
|
6
6
|
const minConfidence = values.flags.get("--min-confidence");
|
|
7
7
|
if (minConfidence !== undefined && !["high", "medium", "low"].includes(minConfidence))
|
|
8
8
|
throw new Error("--min-confidence accepts high, medium or low.");
|
|
@@ -11,5 +11,5 @@ export function parseUsageArgs(args) {
|
|
|
11
11
|
throw new Error("usage takes people, sessions or session <id>.");
|
|
12
12
|
if (action === "session" && !values.positionals[1])
|
|
13
13
|
throw new Error("usage session requires an id.");
|
|
14
|
-
return { byTask: values.booleans.has("--by-task"), task: optionalNonEmpty(values.flags.get("--task")), bySubject: values.booleans.has("--by-subject"), subject: optionalNonEmpty(values.flags.get("--subject")), repo: optionalNonEmpty(values.flags.get("--repo")), tool: optionalNonEmpty(values.flags.get("--tool")), cursor: optionalNonEmpty(values.flags.get("--cursor")), limit: optionalNonEmpty(values.flags.get("--limit")), session: action === "session" ? values.positionals[1] : optionalNonEmpty(values.flags.get("--session")), fromWindow: optionalNonEmpty(values.flags.get("--from-window")), kind: "usage", ...(minConfidence ? { minConfidence: minConfidence } : {}), byTopic: values.booleans.has("--by-topic"), byRepo: values.booleans.has("--by-repo"), person: optionalNonEmpty(values.flags.get("--person")), all: values.booleans.has("--all"), action: (action === "session" ? "sessions" : action), since: optionalNonEmpty(values.flags.get("--since")) ?? "30d", until: optionalNonEmpty(values.flags.get("--until")), detail: values.booleans.has("--detail"), includeAutomated: values.booleans.has("--include-automated"), homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")), json: values.booleans.has("--json") };
|
|
14
|
+
return { byTask: values.booleans.has("--by-task"), task: optionalNonEmpty(values.flags.get("--task")), bySubject: values.booleans.has("--by-subject"), subject: optionalNonEmpty(values.flags.get("--subject")), repo: optionalNonEmpty(values.flags.get("--repo")), tool: optionalNonEmpty(values.flags.get("--tool")), cursor: optionalNonEmpty(values.flags.get("--cursor")), limit: optionalNonEmpty(values.flags.get("--limit")), session: action === "session" ? values.positionals[1] : optionalNonEmpty(values.flags.get("--session")), fromWindow: optionalNonEmpty(values.flags.get("--from-window")), kind: "usage", ...(minConfidence ? { minConfidence: minConfidence } : {}), byTopic: values.booleans.has("--by-topic"), byRepo: values.booleans.has("--by-repo"), byClient: values.booleans.has("--by-client"), person: optionalNonEmpty(values.flags.get("--person")), all: values.booleans.has("--all"), action: (action === "session" ? "sessions" : action), since: optionalNonEmpty(values.flags.get("--since")) ?? "30d", until: optionalNonEmpty(values.flags.get("--until")), detail: values.booleans.has("--detail"), includeAutomated: values.booleans.has("--include-automated"), homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")), json: values.booleans.has("--json") };
|
|
15
15
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit who` argument parsing (ticket 19251).
|
|
3
|
+
*
|
|
4
|
+
* cockpit who [<topic>] [--layer frontend|backend|data|infrastructure|docs|tooling] [--limit <1-10>] [--json]
|
|
5
|
+
*
|
|
6
|
+
* Who knows what: the ranked owners of a project, a layer or a category, with the evidence counts behind each.
|
|
7
|
+
* Reads `GET /api/who` with this machine's device token. A topic of several words needs no quotes.
|
|
8
|
+
*/
|
|
9
|
+
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
10
|
+
/** The layers the directory ranks; `general` is a label for "none of these" and owns nothing. */
|
|
11
|
+
export const WHO_LAYERS = ["frontend", "backend", "data", "infrastructure", "docs", "tooling"];
|
|
12
|
+
export function parseWhoArgs(args) {
|
|
13
|
+
const values = parseNamedArgs(args, {
|
|
14
|
+
allowedFlags: ["--layer", "--limit", "--home", "--dashboard-url", "--json"],
|
|
15
|
+
valueFlags: ["--layer", "--limit", "--home", "--dashboard-url"],
|
|
16
|
+
});
|
|
17
|
+
const topic = optionalNonEmpty(values.positionals.join(" ").trim());
|
|
18
|
+
const layer = optionalNonEmpty(values.flags.get("--layer"))?.toLowerCase();
|
|
19
|
+
if (layer && !WHO_LAYERS.includes(layer))
|
|
20
|
+
throw new Error(`--layer wants one of ${WHO_LAYERS.join(", ")}.`);
|
|
21
|
+
const rawLimit = optionalNonEmpty(values.flags.get("--limit"));
|
|
22
|
+
const limit = rawLimit === undefined ? undefined : Number(rawLimit);
|
|
23
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 1 || limit > 10))
|
|
24
|
+
throw new Error("--limit wants a whole number from 1 to 10.");
|
|
25
|
+
if (layer && !topic)
|
|
26
|
+
throw new Error("--layer needs a topic: `cockpit who Tower --layer backend`.");
|
|
27
|
+
return {
|
|
28
|
+
kind: "who",
|
|
29
|
+
...(topic ? { topic } : {}),
|
|
30
|
+
...(layer ? { layer } : {}),
|
|
31
|
+
...(limit ? { limit } : {}),
|
|
32
|
+
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
33
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
34
|
+
json: values.booleans.has("--json"),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -42,9 +42,16 @@ export { parseSpeedArgs } from "./local-args-tower-speed.js";
|
|
|
42
42
|
export { parseUsageArgs } from "./local-args-tower-usage.js";
|
|
43
43
|
export { parseLogsArgs } from "./local-args-tower-logs.js";
|
|
44
44
|
export { parseBrainArgs } from "./local-args-tower-brain.js";
|
|
45
|
+
export { parseHitsArgs } from "./local-args-tower-hits.js";
|
|
46
|
+
export { parseIntentionsArgs } from "./local-args-tower-intentions.js";
|
|
47
|
+
export { parseWhoArgs } from "./local-args-tower-who.js";
|
|
48
|
+
export { parsePeopleArgs } from "./local-args-tower-people.js";
|
|
45
49
|
export { parseApprovalsArgs } from "./local-args-tower-approvals.js";
|
|
50
|
+
export { parseDrillsArgs } from "./local-args-tower-drills.js";
|
|
46
51
|
export { parseManagerArgs } from "./local-args-tower-manager.js";
|
|
47
52
|
export { parseImageArgs } from "./local-args-tower-image.js";
|
|
48
53
|
export { parseCareersArgs } from './local-args-tower-careers.js';
|
|
49
54
|
export { parseRenameArgs } from './local-args-tower-rename.js';
|
|
50
|
-
export { parseGlossaryArgs } from "./glossary.js";
|
|
55
|
+
export { parseGlossaryArgs } from "./glossary.js";
|
|
56
|
+
export { parseSpendArgs } from "./spend.js";
|
|
57
|
+
export { parseCiArgs } from "./ci.js";
|
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
* verbatim, no logic change.
|
|
17
17
|
*/
|
|
18
18
|
import { parseGlossaryArgs } from "./glossary.js";
|
|
19
|
+
import { parseSpendArgs } from "./spend.js";
|
|
19
20
|
import { parseRootsArgs } from "./roots.js";
|
|
20
21
|
import { SURFACE_ALIASES } from "./local-help.js";
|
|
21
22
|
import { parseAgentRulesArgs, parseAnalyzeArgs, parseAutostartArgs, parseBackfillArgs, parseCleanArgs, parseDoctorArgs, parseInstallArgs, parseLoginArgs, parseMemoryArgs, parseLogoutArgs, parseOnboardArgs, parseReleaseArgs, parseServeArgs, parseSessionsArgs, parseSourcesArgs, parseStartArgs, parseStatusArgs, parseSyncArgs, parseUpdateArgs, } from "./local-args-collector.js";
|
|
22
|
-
import { parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseApprovalsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
|
|
23
|
+
import { parseCiArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
|
|
23
24
|
// `normalizeUrl` has always been part of this module's surface — `local.ts` and
|
|
24
25
|
// `local-auth.ts` import it from here — so it stays exported from this address
|
|
25
26
|
// even though it now lives next door. The same goes for the four names the
|
|
@@ -76,6 +77,7 @@ export function parseLocalArgs(argv) {
|
|
|
76
77
|
return parseModelsArgs(argv.slice(1));
|
|
77
78
|
case "scout":
|
|
78
79
|
return parseScoutArgs(argv.slice(1));
|
|
80
|
+
case "ci": return parseCiArgs(argv.slice(1));
|
|
79
81
|
case "ops":
|
|
80
82
|
return parseOpsArgs(argv.slice(1));
|
|
81
83
|
case "slack":
|
|
@@ -112,6 +114,8 @@ export function parseLocalArgs(argv) {
|
|
|
112
114
|
return parseSourcesArgs(argv.slice(1));
|
|
113
115
|
case "glossary":
|
|
114
116
|
return parseGlossaryArgs(argv.slice(1));
|
|
117
|
+
case "spend":
|
|
118
|
+
return parseSpendArgs(argv.slice(1));
|
|
115
119
|
case "docs":
|
|
116
120
|
return parseDocsArgs(argv.slice(1));
|
|
117
121
|
case "msg":
|
|
@@ -144,8 +148,18 @@ export function parseLocalArgs(argv) {
|
|
|
144
148
|
return parseLogsArgs(argv.slice(1));
|
|
145
149
|
case "brain":
|
|
146
150
|
return parseBrainArgs(argv.slice(1));
|
|
151
|
+
case "hits":
|
|
152
|
+
return parseHitsArgs(argv.slice(1));
|
|
153
|
+
case "intentions":
|
|
154
|
+
return parseIntentionsArgs(argv.slice(1));
|
|
155
|
+
case "who":
|
|
156
|
+
return parseWhoArgs(argv.slice(1));
|
|
157
|
+
case "people":
|
|
158
|
+
return parsePeopleArgs(argv.slice(1));
|
|
147
159
|
case "approvals":
|
|
148
160
|
return parseApprovalsArgs(argv.slice(1));
|
|
161
|
+
case "drills":
|
|
162
|
+
return parseDrillsArgs(argv.slice(1));
|
|
149
163
|
case "manager":
|
|
150
164
|
return parseManagerArgs(argv.slice(1));
|
|
151
165
|
case "image":
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit drills` command shape (BLI-19259), beside `local-command-shapes-approvals.ts` for the same reason:
|
|
3
|
+
* `local-command-shapes.ts` sits at the readability ceiling.
|
|
4
|
+
*
|
|
5
|
+
* `theme set` is typed as two words and parsed to ONE action word (`theme-set`), the way `rules create` becomes `rules-create`.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|