@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.
Files changed (65) hide show
  1. package/dist/commands/agent-door.js +35 -0
  2. package/dist/commands/approvals.js +3 -1
  3. package/dist/commands/brain.js +23 -0
  4. package/dist/commands/cal-overlay.js +63 -0
  5. package/dist/commands/cal.js +4 -0
  6. package/dist/commands/ci.js +23 -0
  7. package/dist/commands/docs-export.js +62 -0
  8. package/dist/commands/docs.js +3 -0
  9. package/dist/commands/doctor-usage-telemetry.js +2 -1
  10. package/dist/commands/drills-render.js +63 -0
  11. package/dist/commands/drills.js +152 -0
  12. package/dist/commands/hits.js +83 -0
  13. package/dist/commands/intentions.js +49 -0
  14. package/dist/commands/local-args-docs-export.js +23 -0
  15. package/dist/commands/local-args-tower-admin.js +8 -0
  16. package/dist/commands/local-args-tower-brain-ladder.js +90 -0
  17. package/dist/commands/local-args-tower-brain.js +11 -1
  18. package/dist/commands/local-args-tower-cal.js +6 -2
  19. package/dist/commands/local-args-tower-docs-msg.js +10 -146
  20. package/dist/commands/local-args-tower-drills.js +87 -0
  21. package/dist/commands/local-args-tower-hits.js +93 -0
  22. package/dist/commands/local-args-tower-intentions.js +70 -0
  23. package/dist/commands/local-args-tower-logs-search.js +51 -0
  24. package/dist/commands/local-args-tower-logs.js +3 -0
  25. package/dist/commands/local-args-tower-memory.js +67 -1
  26. package/dist/commands/local-args-tower-msg.js +149 -0
  27. package/dist/commands/local-args-tower-pages.js +3 -1
  28. package/dist/commands/local-args-tower-people.js +25 -0
  29. package/dist/commands/local-args-tower-usage.js +4 -4
  30. package/dist/commands/local-args-tower-who.js +36 -0
  31. package/dist/commands/local-args-tower.js +8 -1
  32. package/dist/commands/local-args.js +15 -1
  33. package/dist/commands/local-command-shapes-auth.js +1 -0
  34. package/dist/commands/local-command-shapes-drills.js +7 -0
  35. package/dist/commands/local-command-shapes-hits.js +5 -0
  36. package/dist/commands/local-help-commands-tower-ops.js +123 -1
  37. package/dist/commands/local-help-commands-tower.js +25 -10
  38. package/dist/commands/local-help-commands.js +3 -0
  39. package/dist/commands/local-help.js +26 -6
  40. package/dist/commands/local-run-ops.js +11 -0
  41. package/dist/commands/local.js +16 -8
  42. package/dist/commands/logs-search.js +45 -0
  43. package/dist/commands/logs.js +3 -0
  44. package/dist/commands/mail.js +6 -4
  45. package/dist/commands/memory-exam.js +47 -0
  46. package/dist/commands/memory-repeats.js +44 -0
  47. package/dist/commands/memory-search-print.js +95 -0
  48. package/dist/commands/memory-source.js +39 -0
  49. package/dist/commands/memory-store.js +18 -30
  50. package/dist/commands/memory-verb-list.js +7 -2
  51. package/dist/commands/msg.js +51 -9
  52. package/dist/commands/notes-reads.js +20 -0
  53. package/dist/commands/notes-writes.js +24 -0
  54. package/dist/commands/notes.js +4 -2
  55. package/dist/commands/people.js +29 -0
  56. package/dist/commands/public-root.js +1 -1
  57. package/dist/commands/spend.js +79 -0
  58. package/dist/commands/team.js +35 -0
  59. package/dist/commands/usage.js +9 -0
  60. package/dist/commands/who.js +36 -0
  61. package/dist/evidence-upload-direct.js +44 -0
  62. package/dist/evidence-upload-object.js +18 -4
  63. package/dist/evidence-upload-plan.js +1 -0
  64. package/dist/evidence-upload-transport.js +2 -0
  65. 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
- return { ...command, action, query, limit: readLimit(values.flags.get("--limit")), includeRecent: !values.booleans.has("--no-recent") };
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-task.");
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 {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `cockpit hits` command shape (BLI-19257), beside `local-command-shapes-brain.ts` for the same reason:
3
+ * `local-command-shapes.ts` sits at the readability ceiling.
4
+ */
5
+ export {};