@bli-cockpit/cli 0.2.161 → 0.2.163

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.
@@ -0,0 +1,48 @@
1
+ import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
2
+ export const RENAME_SCOPES = ["page", "everywhere", "undo"];
3
+ export function parseRenameArgs(args) {
4
+ const values = parseNamedArgs(args, {
5
+ allowedFlags: ["--home", "--dashboard-url", "--scope", "--wrong", "--right", "--json"],
6
+ valueFlags: ["--home", "--dashboard-url", "--scope", "--wrong", "--right"],
7
+ });
8
+ const positionals = [...values.positionals];
9
+ const typed = positionals.shift() ?? "list";
10
+ if (typed !== "list" && typed !== "resolve") {
11
+ throw new Error(`Unknown rename command: ${typed}. Try list, or resolve <id> --scope page|everywhere|undo.`);
12
+ }
13
+ const id = positionals.shift();
14
+ // An OLD rule has no open choice and so no id: `--wrong X --right Y --scope
15
+ // undo` names it by its pair and removes the caller's own row.
16
+ const wrong = optionalNonEmpty(values.flags.get("--wrong"));
17
+ const right = optionalNonEmpty(values.flags.get("--right"));
18
+ const byPair = typed === "resolve" && !id && wrong !== undefined && right !== undefined;
19
+ if (typed === "resolve" && !id && !byPair) {
20
+ throw new Error("rename resolve needs the id from `cockpit rename list`, or --wrong and --right with --scope undo.");
21
+ }
22
+ if ((wrong !== undefined || right !== undefined) && (typed !== "resolve" || id || wrong === undefined || right === undefined)) {
23
+ throw new Error("--wrong and --right go together, on `cockpit rename resolve --scope undo`, instead of an id.");
24
+ }
25
+ if (positionals.length > 0 || (typed === "list" && id)) {
26
+ throw new Error(`rename ${typed} does not take "${typed === "list" ? id : positionals[0]}".`);
27
+ }
28
+ const scope = optionalNonEmpty(values.flags.get("--scope"));
29
+ if (typed === "resolve" && (!scope || !RENAME_SCOPES.includes(scope))) {
30
+ throw new Error(`rename resolve needs --scope ${RENAME_SCOPES.join("|")}.`);
31
+ }
32
+ if (byPair && scope !== "undo") {
33
+ throw new Error("--wrong and --right name a rule that already exists, so they take --scope undo only.");
34
+ }
35
+ if (typed === "list" && scope) {
36
+ throw new Error("--scope belongs to `cockpit rename resolve`.");
37
+ }
38
+ return {
39
+ kind: "rename",
40
+ action: typed,
41
+ ...(id ? { id } : {}),
42
+ ...(scope ? { scope: scope } : {}),
43
+ ...(byPair ? { wrongForm: wrong, canonicalForm: right } : {}),
44
+ homeDir: optionalNonEmpty(values.flags.get("--home")),
45
+ dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
46
+ json: values.booleans.has("--json"),
47
+ };
48
+ }
@@ -37,4 +37,5 @@ export { parseModelsArgs } from "./local-args-tower-models.js";
37
37
  export { parseMailArgs } from "./local-args-tower-mail.js";
38
38
  export { parseCalArgs } from "./local-args-tower-cal.js";
39
39
  export { parseUsageArgs } from "./local-args-tower-usage.js";
40
- export { parseCareersArgs } from './local-args-tower-careers.js';
40
+ export { parseCareersArgs } from './local-args-tower-careers.js';
41
+ export { parseRenameArgs } from './local-args-tower-rename.js';
@@ -18,7 +18,7 @@
18
18
  import { parseRootsArgs } from "./roots.js";
19
19
  import { SURFACE_ALIASES } from "./local-help.js";
20
20
  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";
21
- import { parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseCareersArgs, } from "./local-args-tower.js";
21
+ import { parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
22
22
  // `normalizeUrl` has always been part of this module's surface — `local.ts` and
23
23
  // `local-auth.ts` import it from here — so it stays exported from this address
24
24
  // even though it now lives next door. The same goes for the four names the
@@ -127,6 +127,8 @@ export function parseLocalArgs(argv) {
127
127
  return parseReleaseArgs(argv.slice(1));
128
128
  case "careers":
129
129
  return parseCareersArgs(argv.slice(1));
130
+ case "rename":
131
+ return parseRenameArgs(argv.slice(1));
130
132
  case "usage":
131
133
  return parseUsageArgs(argv.slice(1));
132
134
  default:
@@ -22,6 +22,17 @@ const SEARCH_KIND_LIST = SEARCH_KINDS.join(",");
22
22
  const SEARCH_CORPORA_COUNT = SEARCH_KINDS.length;
23
23
  /** One entry per Tower noun, in the order `cockpit --help` lists them. */
24
24
  export const TOWER_COMMAND_HELP = [
25
+ [
26
+ "rename",
27
+ [
28
+ "Usage: cockpit rename [list|resolve <id> --scope <page|everywhere|undo>] [--dashboard-url <url>] [--json]",
29
+ "",
30
+ "A correction JARVIS cannot classify is fixed on ONE page and waits for your call: just this page, or everywhere from now on. A bare `cockpit rename` lists what is waiting.",
31
+ "resolve <id> --scope page — keep it a one-page fix. everywhere — write the rule and fix every other page. undo — take back a rule JARVIS applied on its own (yours only, unless you are a super admin).",
32
+ "An OLD rule has no open choice: `cockpit rename resolve --wrong <form> --right <form> --scope undo` removes your own row for that pair (a super admin may remove anyone's).",
33
+ "A fact (a status, a date, a number) is never made a rule, whatever is asked. The same door answers the panel's two buttons, a spoken answer, `cockpit jarvis` and the rename_* MCP tools.",
34
+ ],
35
+ ],
25
36
  [
26
37
  "careers",
27
38
  [
@@ -75,6 +75,7 @@ export const rootCommandNames = new Set([
75
75
  "search",
76
76
  "release",
77
77
  "careers",
78
+ "rename",
78
79
  "usage",
79
80
  ...SURFACE_ALIASES.keys(),
80
81
  ]);
@@ -128,6 +129,7 @@ export function localCommandHelp(command) {
128
129
  ` cockpit search "<words>" [--kind ${SEARCH_KINDS.join(",")}] [--limit <n>] [--dashboard-url <url>] [--json]`,
129
130
  " cockpit release [--dry-run] [--skip-checks] [--no-floor] [--tag <tag>] [--access <public|restricted>] [--otp <code>]",
130
131
  " 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]",
132
+ " cockpit rename [list|resolve <id> --scope <page|everywhere|undo>] [--dashboard-url <url>] [--json]",
131
133
  " cockpit usage sessions [--task <BLI-NNNN> | --subject <name>] [--repo <label>] [--since <window>] [--json]",
132
134
  " cockpit usage sessions --session <id> [--from-window <n>] [--json]",
133
135
  " --by-topic groups work types; --by-subject groups open subjects from session summaries.",
@@ -46,6 +46,7 @@ import { runMail } from "./mail.js";
46
46
  import { runProject } from "./project.js";
47
47
  import { runSearch } from "./search.js";
48
48
  import { runCareers } from "./careers.js";
49
+ import { runRename } from "./rename-scope.js";
49
50
  import { runUsage } from "./usage.js";
50
51
  import { parseLocalArgs } from "./local-args.js";
51
52
  // `./local.js` is the published entry point for this command surface: the
@@ -234,6 +235,8 @@ export async function runLocalCockpitCli(argv, io = defaultIo()) {
234
235
  return await runRelease(command, io);
235
236
  case "careers":
236
237
  return await runCareers(command, io);
238
+ case "rename":
239
+ return await runRename(command, io);
237
240
  case "usage":
238
241
  return await runUsage(command, io);
239
242
  }
@@ -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.161");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.163");
25
25
  return 0;
26
26
  }
27
27
 
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `cockpit rename list` / `cockpit rename resolve <id> --scope ...` (BLI-12378).
3
+ *
4
+ * The terminal twin of the panel's two buttons, over the same door
5
+ * (`/api/jarvis/rename-scope`) with the same device token. The door's reply
6
+ * sentence is printed as it is: it is the one JARVIS says everywhere.
7
+ *
8
+ * Metadata only in the logs: the ids and counts, never the words being renamed.
9
+ */
10
+ import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor } from "./agent-door.js";
11
+ import { writeLine } from "./cli-io.js";
12
+ const TAG = "[rename cli]";
13
+ const DEADLINE_MS = 120_000;
14
+ export async function runRename(command, io) {
15
+ const door = await openAgentDoor("rename", command, io);
16
+ const listing = command.action === "list";
17
+ const answer = await askAgentDoor(door, {
18
+ path: "/api/jarvis/rename-scope",
19
+ method: listing ? "GET" : "POST",
20
+ label: `rename ${command.action}`,
21
+ timeoutMs: DEADLINE_MS,
22
+ ...(listing
23
+ ? {}
24
+ : {
25
+ body: command.id
26
+ ? { pendingId: command.id, choice: command.scope }
27
+ : { wrongForm: command.wrongForm, canonicalForm: command.canonicalForm, choice: command.scope },
28
+ }),
29
+ });
30
+ if (!answer.ok)
31
+ return failAgentDoor(door, TAG, answer.reason, answer.detail);
32
+ const body = answer.body;
33
+ // An HTTP 200 with no recognisable shape is a dashboard that has not shipped
34
+ // this door, not an empty list.
35
+ if (listing ? !Array.isArray(body.pending) : typeof body.reply !== "string") {
36
+ return failAgentDoor(door, TAG, "rename_answer_unreadable", "Tower answered 200 with no rename receipt in it. The usual cause is a dashboard that has not shipped /api/jarvis/rename-scope yet; nothing was changed.");
37
+ }
38
+ writeLine(door.io.stderr, `${TAG} ${command.action} ${JSON.stringify({ open: body.pending?.length ?? null, ok: body.ok ?? null, scope: command.scope ?? null })}`);
39
+ if (door.json)
40
+ return emitAgentDoor(door, answer.body);
41
+ if (listing) {
42
+ const open = body.pending ?? [];
43
+ if (open.length === 0) {
44
+ writeLine(door.io.stdout, "No correction is waiting on you. Nothing was made a rule without a yes.");
45
+ return 0;
46
+ }
47
+ for (const row of open) {
48
+ const what = row.kind === "undo" ? "applied everywhere, can be undone" : "this page only, waiting for your call";
49
+ writeLine(door.io.stdout, `${row.id} “${row.wrongForm}” -> ${row.canonicalForm} (${what})`);
50
+ }
51
+ writeLine(door.io.stdout, "");
52
+ writeLine(door.io.stdout, "Answer one with: cockpit rename resolve <id> --scope page|everywhere|undo");
53
+ return 0;
54
+ }
55
+ writeLine(door.io.stdout, body.reply);
56
+ return body.ok === false ? 1 : 0;
57
+ }
@@ -226,6 +226,13 @@ function writeReadReply(command, io, payload, read, messages) {
226
226
  const styled = colorEnabled(io);
227
227
  const summary = typeof read["summary"] === "string" ? read["summary"] : "";
228
228
  const header = summary.split("\n")[0] ?? "";
229
+ // BLI-12381: an unknown, uncovered or empty read leads with its reason
230
+ // label on stdout (not only the stderr receipt), so a zero never reads as
231
+ // "nobody said anything".
232
+ const status = typeof read["status"] === "string" ? read["status"] : null;
233
+ if (status && (status !== "ok" || messages.length === 0)) {
234
+ writeLine(io.stdout, `slack read: status=${status} reason=${String(read["reason"] ?? "?")}`);
235
+ }
229
236
  if (header)
230
237
  writeLine(io.stdout, header);
231
238
  if (command.format === "compact") {
@@ -243,7 +250,8 @@ function writeReadReply(command, io, payload, read, messages) {
243
250
  }
244
251
  }
245
252
  }
246
- if (typeof read["note"] === "string" && read["note"]) {
253
+ // A refusal's summary IS its note (tools-slack.ts `boundary`); print it once.
254
+ if (typeof read["note"] === "string" && read["note"] && read["note"] !== header) {
247
255
  writeLine(io.stdout, "");
248
256
  writeLine(io.stdout, dim(read["note"], styled));
249
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.161",
3
+ "version": "0.2.163",
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.45",
31
- "@bli-cockpit/mcp": "0.1.79",
31
+ "@bli-cockpit/mcp": "0.1.81",
32
32
  "@bli-cockpit/telemetry-core": "0.1.57"
33
33
  },
34
34
  "repository": {