@bli-cockpit/cli 0.2.191 → 0.2.193

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.
@@ -15,38 +15,29 @@
15
15
  */
16
16
  import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs, } from "./local-arg-values.js";
17
17
  /**
18
- * Every corpus. Kept here so an unknown kind is refused BEFORE a round trip,
19
- * and mirrors `SEARCH_KINDS` in the dashboard — nothing in the type system ties
20
- * the two lists together.
18
+ * What `--kind` names is the SERVER's to say (BLI-13027). This used to be a copy
19
+ * of the dashboard's kind list that refused anything it did not hold, so an
20
+ * installed CLI told a person that Scout did not exist the day Scout shipped.
21
+ * Now the CLI forwards the words, and the door answers `unknown_kind` with the
22
+ * list this account is offered. `cockpit search --categories` prints that list.
21
23
  */
22
- export const SEARCH_KINDS = [
23
- "doc",
24
- "msg",
25
- "issue",
26
- "note",
27
- "mail",
28
- "cal",
29
- "memory",
30
- ];
31
24
  export function parseSearchArgs(args) {
32
25
  const values = parseNamedArgs(args, {
33
- allowedFlags: ["--home", "--dashboard-url", "--kind", "--limit", "--json"],
26
+ allowedFlags: ["--home", "--dashboard-url", "--kind", "--limit", "--json", "--categories"],
34
27
  valueFlags: ["--home", "--dashboard-url", "--kind", "--limit"],
35
28
  });
36
29
  const query = values.positionals.join(" ").trim();
37
- if (query.length === 0) {
30
+ const categories = values.booleans.has("--categories");
31
+ if (query.length === 0 && !categories) {
38
32
  throw new Error('search needs something to search for, e.g. `cockpit search "storage ceiling"`.');
39
33
  }
40
34
  const kindFlag = optionalNonEmpty(values.flags.get("--kind"));
41
35
  let kinds;
42
36
  if (kindFlag) {
43
37
  kinds = [];
44
- for (const part of kindFlag.split(",").map((entry) => entry.trim()).filter(Boolean)) {
45
- if (!SEARCH_KINDS.includes(part)) {
46
- throw new Error(`search --kind must be one or more of ${SEARCH_KINDS.join(", ")} — not "${part}".`);
47
- }
48
- if (!kinds.includes(part))
49
- kinds.push(part);
38
+ for (const raw of kindFlag.split(",").map((entry) => entry.trim().toLowerCase()).filter(Boolean)) {
39
+ if (!kinds.includes(raw))
40
+ kinds.push(raw);
50
41
  }
51
42
  if (kinds.length === 0)
52
43
  kinds = undefined;
@@ -54,6 +45,7 @@ export function parseSearchArgs(args) {
54
45
  return {
55
46
  kind: "search",
56
47
  query,
48
+ ...(categories ? { categories: true } : {}),
57
49
  ...(kinds ? { kinds } : {}),
58
50
  limit: optionalPositiveInteger(values.flags.get("--limit"), "--limit"),
59
51
  homeDir: optionalNonEmpty(values.flags.get("--home")),
@@ -33,7 +33,7 @@ export { SCOUT_MIN_PREFIX_LENGTH, parseScoutArgs, parseOpsArgs, SLACK_WORKSPACE_
33
33
  export { parseDocsArgs, parseMsgArgs, } from "./local-args-tower-docs-msg.js";
34
34
  export { ISSUE_STATES, parseIssueArgs, parseProjectArgs, } from "./local-args-tower-work.js";
35
35
  export { parseGithubArgs } from "./local-args-tower-github.js";
36
- export { SEARCH_KINDS, parseSearchArgs } from "./local-args-tower-search.js";
36
+ export { parseSearchArgs } from "./local-args-tower-search.js";
37
37
  export { parseModelsArgs } from "./local-args-tower-models.js";
38
38
  export { parseMailArgs } from "./local-args-tower-mail.js";
39
39
  export { parseCalArgs } from "./local-args-tower-cal.js";
@@ -11,15 +11,6 @@
11
11
  * Every string moved verbatim. Help output is what an intern pastes back when
12
12
  * something breaks, so it is a user-visible contract like any other.
13
13
  */
14
- import { SEARCH_KINDS } from "./local-args-tower-search.js";
15
- /**
16
- * The corpora `cockpit search` can name, spelled the way `--kind` takes them.
17
- * Read off `SEARCH_KINDS` rather than typed out: the help string said five for
18
- * the two months `mail` and `cal` were searchable, so a person was told the
19
- * door was narrower than it is (BLI-4048).
20
- */
21
- const SEARCH_KIND_LIST = SEARCH_KINDS.join(",");
22
- const SEARCH_CORPORA_COUNT = SEARCH_KINDS.length;
23
14
  /** One entry per Tower noun, in the order `cockpit --help` lists them. */
24
15
  export const TOWER_COMMAND_HELP = [
25
16
  [
@@ -207,10 +198,11 @@ export const TOWER_COMMAND_HELP = [
207
198
  [
208
199
  "search",
209
200
  [
210
- `Usage: cockpit search "<words>" [--kind ${SEARCH_KIND_LIST}] [--limit <n>] [--json]`,
201
+ 'Usage: cockpit search "<words>" [--kind <category,...>] [--limit <n>] [--json]',
202
+ " cockpit search --categories [--json]",
211
203
  "",
212
- `One bar over ${SEARCH_CORPORA_COUNT} corpora: documents, messages, issues, meeting notes, mail, calendar`,
213
- "events and memory.",
204
+ "One bar over every corpus Tower holds: tickets, documents, meeting notes, messages, mail,",
205
+ "calendar events, memory, Scout and (where you may read it) Careers.",
214
206
  "It presses the SAME door the browser's search bar presses (GET /api/search), so what you",
215
207
  "read here is the same row, the same ranking and the same snippet a person sees in Tower.",
216
208
  "",
@@ -219,14 +211,16 @@ export const TOWER_COMMAND_HELP = [
219
211
  "Quoted phrases and -word work inside the query itself — the query goes to Postgres's",
220
212
  "websearch parser, which shrugs at anything a person can type instead of raising.",
221
213
  "",
222
- `--kind narrows to one or more corpora, comma separated: ${SEARCH_KIND_LIST}.`,
223
- `Omit it and all ${SEARCH_CORPORA_COUNT} are searched.`,
214
+ "--kind narrows to one or more categories, comma separated (ticket works for issue).",
215
+ "--categories prints the list Tower offers THIS account, in the order the browser's Command-K",
216
+ "chips use, so the names are never a guess; an unknown --kind is refused by the door with that list.",
217
+ "Omit --kind and every category is searched.",
224
218
  "--limit caps the number of results (default 20, maximum 50).",
225
219
  "--json writes the door's whole answer to stdout, hits, per-kind counts, failures and all.",
226
220
  "",
227
- `Every result is scoped by what YOU may read: ${SEARCH_CORPORA_COUNT - 1} of the ${SEARCH_CORPORA_COUNT} corpora are`,
228
- "searched on your own database session, so a document or a channel you cannot open is not in",
229
- "the list; memory runs on the service role behind the memory doors' own gate.",
221
+ "Every result is scoped by what YOU may read: most corpora (and Careers always) are searched on",
222
+ "your own database session, so a document, a channel or an applicant you cannot open is not in",
223
+ "the list; memory and Scout run on the service role behind their own gates.",
230
224
  "",
231
225
  "A corpus that could not ANSWER gets its own line, separately from \"nothing matched\" —",
232
226
  "those are different facts and folding them together would let a broken search read as silence.",
@@ -400,13 +400,13 @@ export function localSubcommandHelp(command) {
400
400
  ]);
401
401
  // BLI-10431: a Tower page name answers with its own usage line first, then
402
402
  // the manual of the noun it routes to, so `cockpit work --help` names every
403
- // Work verb instead of `Unknown command: work`.
403
+ // ticket verb instead of `Unknown command: work`.
404
404
  const aliasTarget = SURFACE_ALIASES.get(command);
405
405
  if (aliasTarget) {
406
406
  return [
407
407
  `Usage: cockpit ${command} [same verbs and flags as cockpit ${aliasTarget}]`,
408
408
  `\`cockpit ${command}\` is the Tower page's name for \`cockpit ${aliasTarget}\`; both spellings run the same command.`,
409
- ...(command === "work" ? ["Projects the issues are filed under: `cockpit project list`."] : []),
409
+ ...(aliasTarget === "issue" ? ["Projects the Tower tickets are filed under: `cockpit project list`."] : []),
410
410
  "",
411
411
  ...(helpByCommand.get(aliasTarget) ?? [localCommandHelp()]),
412
412
  ].join("\n");
@@ -8,23 +8,26 @@
8
8
  * user-visible contract like any other.
9
9
  */
10
10
  import { DEFAULT_DASHBOARD_URL } from "../local-state.js";
11
- import { SEARCH_KINDS } from "./local-args-tower-search.js";
12
11
  import { localSubcommandHelp } from "./local-help-commands.js";
13
12
  /**
14
13
  * The Tower PAGE names a person reads in the rail, each routed to the CLI noun
15
14
  * that already serves that page (BLI-10431).
16
15
  *
17
- * The rail says WORK, CALENDAR, MESSAGES and MEETING NOTES; the MCP server
16
+ * The rail says TICKETS, CALENDAR, MESSAGES and MEETING NOTES; the MCP server
18
17
  * says `work_list_issues`; the CLI spelled the same doors `issue`, `cal`,
19
18
  * `msg` and `notes`. `cockpit work --help` answered `Unknown command: work` on
20
- * 0.2.133 while every Work verb existed one word away. An alias here is a
21
- * second spelling of the same parser, never a second parser: `cockpit work
22
- * list` IS `cockpit issue list`, byte for byte.
19
+ * 0.2.133 while every verb existed one word away. An alias here is a
20
+ * second spelling of the same parser, never a second parser: `cockpit ticket
21
+ * list` IS `cockpit issue list`, byte for byte. BLI-12947 renamed the Work
22
+ * page to Tickets and added `ticket` and `tickets`; `work` stays for every
23
+ * agent and doc that already says it.
23
24
  *
24
25
  * `surface-doors.test.ts` reads the rail and the MCP tool names and fails when
25
26
  * a page or a tool family has no spelling here or in `rootCommandNames`.
26
27
  */
27
28
  export const SURFACE_ALIASES = new Map([
29
+ ["ticket", "issue"],
30
+ ["tickets", "issue"],
28
31
  ["work", "issue"],
29
32
  ["calendar", "cal"],
30
33
  ["messages", "msg"],
@@ -132,7 +135,8 @@ export function localCommandHelp(command) {
132
135
  " cockpit cal [today|week|next|find \"<words>\"|calendars|add-ical|create --calendar <id> --title <t> --at <iso> --until <iso>|share <id> --org-visible|--private|sync <id> [--full]|detach <id>] [--tz <zone>] [--offset <n>] [--hours <n>] [--from <d> --to <d>] [--calendar <id>] [--limit <n>] [--all] [--dashboard-url <url>] [--json]",
133
136
  " cockpit project [list] [--archived] [--dashboard-url <url>] [--json]",
134
137
  " cockpit github <pr|merge|retitle|rerun> <owner/repo> <n> [--claim <state>] [--method squash|merge|rebase] [--title \"<text>\"] [--run <id>] [--confirm <code>] [--dashboard-url <url>] [--json]",
135
- ` cockpit search "<words>" [--kind ${SEARCH_KINDS.join(",")}] [--limit <n>] [--dashboard-url <url>] [--json]`,
138
+ ` cockpit search "<words>" [--kind <category,...>] [--limit <n>] [--dashboard-url <url>] [--json]`,
139
+ " cockpit search --categories [--dashboard-url <url>] [--json]",
136
140
  " cockpit release [--dry-run] [--skip-checks] [--no-floor] [--tag <tag>] [--access <public|restricted>] [--otp <code>]",
137
141
  " cockpit careers [list|show <id>|rescreen <id>|send-takehome <id>|decide <id> --decision <passed_on|archived|reopen>|followup <id> [--record-sent-at <iso>]|interview <id> <--at <iso>|--clear>|grade <id> [--now]|resume <id> [--out <path>]|takehome show <role>|takehome set <role> --body-file <path>|settings show|settings set --enabled <true|false> --followup-enabled <true|false> --followup-after-days <n> --takehome-window-hours <n>] [--role <slug>] [--min-score <n>] [--since <date>] [--stage <s>] [--force] [--json]",
138
142
  " cockpit glossary list|add <wrong> --to <name>|remove <wrong> [--dashboard-url <url>] [--json]",
@@ -143,7 +147,9 @@ export function localCommandHelp(command) {
143
147
  " cockpit usage sessions [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--since <window>] [--json]",
144
148
  " cockpit usage sessions --session <id> [--from-window <n>] [--json]",
145
149
  " --by-topic groups work types; --by-subject groups open subjects from session summaries.",
146
- " cockpit work [same verbs and flags as cockpit issue] (the Work page; projects: `cockpit project list`)",
150
+ " cockpit ticket [same verbs and flags as cockpit issue] (the Tickets page; projects: `cockpit project list`)",
151
+ " cockpit tickets ... same as cockpit ticket",
152
+ " cockpit work ... same as cockpit ticket (the older name)",
147
153
  " cockpit calendar [same verbs and flags as cockpit cal]",
148
154
  " cockpit messages [same verbs and flags as cockpit msg]",
149
155
  " cockpit meeting-notes [same verbs and flags as cockpit notes]",
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
21
21
  }
22
22
 
23
23
  if (command === "--version" || command === "-V" || command === "version") {
24
- writeLine(io?.stdout ?? process.stdout, "0.2.191");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.193");
25
25
  return 0;
26
26
  }
27
27
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `cockpit search` — one bar over five corpora, in a terminal (BLI-3728).
2
+ * `cockpit search` — one bar over every corpus, in a terminal (BLI-3728).
3
3
  *
4
4
  * The SAME door the browser's search bar presses: `GET /api/search`, opted
5
5
  * into the collector device token in `route-caller-census.test.ts`. So a
@@ -37,15 +37,20 @@ const READ_DEADLINE_MS = 30_000;
37
37
  const KIND_LABELS = {
38
38
  doc: "DOCUMENTS",
39
39
  msg: "MESSAGES",
40
- issue: "ISSUES",
40
+ issue: "TICKETS",
41
41
  note: "MEETING NOTES",
42
42
  mail: "MAIL",
43
43
  cal: "CALENDAR",
44
44
  memory: "MEMORY",
45
+ scout: "SCOUT",
46
+ careers: "CAREERS",
45
47
  };
46
- const KIND_ORDER = ["doc", "msg", "issue", "note", "mail", "cal", "memory"];
48
+ const KIND_ORDER = ["doc", "msg", "issue", "note", "mail", "cal", "memory", "scout", "careers"];
47
49
  export async function runSearch(command, io) {
48
50
  const door = await openAgentDoor("search", command, io);
51
+ // BLI-13027: the list the browser's Command-K chips are drawn from.
52
+ if (command.categories)
53
+ return await runCategories(door);
49
54
  const params = new URLSearchParams({ q: command.query });
50
55
  if (command.kinds && command.kinds.length > 0)
51
56
  params.set("kinds", command.kinds.join(","));
@@ -64,6 +69,28 @@ export async function runSearch(command, io) {
64
69
  return emitAgentDoor(door, body);
65
70
  return render(door, body);
66
71
  }
72
+ async function runCategories(door) {
73
+ const answer = await askAgentDoor(door, {
74
+ path: "/api/search?categories=1",
75
+ method: "GET",
76
+ label: "search categories",
77
+ timeoutMs: READ_DEADLINE_MS,
78
+ });
79
+ if (!answer.ok)
80
+ return failAgentDoor(door, TAG, answer.reason, answer.detail);
81
+ const body = answer.body;
82
+ if (door.json)
83
+ return emitAgentDoor(door, body);
84
+ const categories = body.categories ?? [];
85
+ writeLine(door.io.stdout, `${body.everything?.label ?? "Everything"} (searched when --kind is left out)`);
86
+ for (const entry of categories) {
87
+ const aliases = (entry.aliases ?? []).length > 0 ? ` (also: ${(entry.aliases ?? []).join(", ")})` : "";
88
+ writeLine(door.io.stdout, ` ${entry.kind} ${entry.label}${aliases}`);
89
+ }
90
+ writeLine(door.io.stdout, "");
91
+ writeLine(door.io.stdout, `${categories.length} categories. Name them with --kind, comma separated.`);
92
+ return 0;
93
+ }
67
94
  function render(door, body) {
68
95
  const hits = body.hits ?? [];
69
96
  const failures = body.failures ?? {};
@@ -76,7 +103,10 @@ function render(door, body) {
76
103
  writeLine(door.io.stdout, "Nothing matched in the corpora that answered.");
77
104
  }
78
105
  }
79
- for (const kind of KIND_ORDER) {
106
+ // Known kinds in the usual order, then any kind this CLI has never heard of
107
+ // (a newer door than this build): printed, not swallowed.
108
+ const unknownKinds = [...new Set(hits.map((hit) => hit.kind))].filter((kind) => !KIND_ORDER.includes(kind));
109
+ for (const kind of [...KIND_ORDER, ...unknownKinds]) {
80
110
  const group = hits.filter((hit) => hit.kind === kind);
81
111
  if (group.length === 0)
82
112
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.191",
3
+ "version": "0.2.193",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@bli-cockpit/memory-mcp": "0.1.57",
31
- "@bli-cockpit/mcp": "0.1.103",
31
+ "@bli-cockpit/mcp": "0.1.105",
32
32
  "@bli-cockpit/telemetry-core": "0.1.66"
33
33
  },
34
34
  "repository": {