@bli-cockpit/cli 0.2.185 → 0.2.188

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.
@@ -31,8 +31,10 @@
31
31
  * MCP server and three hooks) and these act on Tower. `memory-store.ts` runs
32
32
  * them; `parseMemoryArgs` in `local-args-collector-status.ts` routes to here.
33
33
  */
34
+ import { parseMemoryRulesArgs } from "./memory-rules.js";
34
35
  import { optionalNonEmpty, optionalUrl, parseNamedArgs, workRootFlagValue, } from "./local-arg-values.js";
35
36
  export const MEMORY_STORE_ACTIONS = new Set([
37
+ "rules",
36
38
  "search",
37
39
  "save",
38
40
  "update",
@@ -86,6 +88,8 @@ const VALUE_FLAGS = [
86
88
  "--source",
87
89
  ];
88
90
  export function parseMemoryStoreArgs(action, args) {
91
+ if (action === "rules")
92
+ return parseMemoryRulesArgs(args);
89
93
  const values = parseNamedArgs(args, { allowedFlags: ALLOWED_FLAGS, valueFlags: VALUE_FLAGS });
90
94
  const rest = values.positionals.slice(1);
91
95
  const containers = values.flagValues.get("--container") ?? [];
@@ -383,8 +383,9 @@ export const TOWER_COMMAND_HELP = [
383
383
  [
384
384
  "memory",
385
385
  [
386
- "Usage: cockpit memory [search|save|update|forget|private|public|profile|consolidation|requeue|install|status|log] [flags]",
386
+ "Usage: cockpit memory [rules|search|save|update|forget|private|public|profile|consolidation|requeue|install|status|log] [flags]",
387
387
  "",
388
+ " rules [--person <name>] [--project <name>] [--limit <n>] [--json] — standing rules grouped by person.",
388
389
  "The STORE, over the same /api/memory/* doors the bli-memory MCP server calls:",
389
390
  " search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent]",
390
391
  " repeat --container to search more than one space; omit it for this repository's own.",
@@ -0,0 +1,9 @@
1
+ /** Cross-project search always names the project, after the date and before the author. */
2
+ export function memoryProjectPrefix(row) {
3
+ if (typeof row["project"] !== "string" || !row["project"])
4
+ return "";
5
+ const date = typeof row["createdAt"] === "string" ? new Date(row["createdAt"]) : null;
6
+ const day = date && !Number.isNaN(date.getTime()) ? `[${date.toISOString().slice(0, 10)}] ` : "";
7
+ const author = typeof row["savedBy"] === "string" && row["savedBy"] ? `(from ${row["savedBy"]}) ` : "";
8
+ return `${day}[${row["project"]}] ${author}`;
9
+ }
@@ -0,0 +1,43 @@
1
+ import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
2
+ import { writeLine } from "./cli-io.js";
3
+ import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
4
+ export function parseMemoryRulesArgs(args) {
5
+ const values = parseNamedArgs(args, { allowedFlags: ["--person", "--project", "--limit", "--json", "--home", "--dashboard-url"],
6
+ valueFlags: ["--person", "--project", "--limit", "--home", "--dashboard-url"] });
7
+ if (values.positionals.slice(1).length)
8
+ throw new Error("memory rules takes no positionals; use --person and --project.");
9
+ const raw = values.flags.get("--limit");
10
+ if (raw !== undefined && (!/^\d+$/.test(raw) || Number(raw) < 1 || Number(raw) > 1000))
11
+ throw new Error("--limit must be between 1 and 1000.");
12
+ return { kind: "memory-store", action: "rules", containerTags: [],
13
+ person: optionalNonEmpty(values.flags.get("--person")), project: optionalNonEmpty(values.flags.get("--project")),
14
+ limit: raw === undefined ? undefined : Number(raw), json: values.booleans.has("--json"),
15
+ homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")) };
16
+ }
17
+ export async function runMemoryRules(command, door) {
18
+ const params = new URLSearchParams();
19
+ if (command.person)
20
+ params.set("person", command.person);
21
+ if (command.project)
22
+ params.set("project", command.project);
23
+ if (command.limit !== undefined)
24
+ params.set("limit", String(command.limit));
25
+ const answer = await askAgentDoor(door, { path: `/api/memory/rules${params.size ? `?${params}` : ""}`,
26
+ method: "GET", label: "memory rules", timeoutMs: 60_000 });
27
+ if (!answer.ok)
28
+ return failAgentDoor(door, "[memory cli]", answer.reason, answer.detail);
29
+ const body = answer.body;
30
+ writeLine(door.io.stderr, `[memory cli] rules ${JSON.stringify({ reason: body.capped ? "capped" : "exhausted", returned: body.returned, groups: body.groups.length })}`);
31
+ if (door.json)
32
+ return emitAgentDoor(door, answer.body);
33
+ for (const group of body.groups) {
34
+ writeLine(door.io.stdout, group.owner);
35
+ for (const rule of group.rules)
36
+ writeLine(door.io.stdout, ` [${rule.created_at.slice(0, 10)}] ${rule.project ? `[${rule.project}] ` : ""}${rule.text}${group.owner === "unattributed" ? ` (space: ${rule.container})` : ""}`);
37
+ }
38
+ if (!body.returned)
39
+ writeLine(door.io.stdout, "No ground rules matched.");
40
+ if (body.capped)
41
+ writeLine(door.io.stdout, "Result capped. Narrow by person or project, or increase --limit (max 1000).");
42
+ return 0;
43
+ }
@@ -42,6 +42,8 @@
42
42
  * `ps` shows an argument list, shell history keeps it, and a memory with a
43
43
  * newline in it cannot be quoted correctly anyway.
44
44
  */
45
+ import { runMemoryRules } from "./memory-rules.js";
46
+ import { memoryProjectPrefix } from "./memory-project-label.js";
45
47
  import { resolveContainerTag } from "@bli-cockpit/memory-mcp/dist/container-tag.js";
46
48
  import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor, } from "./agent-door.js";
47
49
  import { isInteractiveStdin, readPipedText, writeLine } from "./cli-io.js";
@@ -62,6 +64,8 @@ const FORGET_MAX_CHARS = 10_000;
62
64
  export async function runMemoryStore(command, io) {
63
65
  const door = await openAgentDoor("memory", command, io);
64
66
  switch (command.action) {
67
+ case "rules":
68
+ return runMemoryRules(command, door);
65
69
  case "search":
66
70
  return searchMemory(command, door);
67
71
  case "save":
@@ -208,7 +212,7 @@ async function searchMemory(command, door) {
208
212
  }
209
213
  else {
210
214
  for (const row of results) {
211
- writeLine(door.io.stdout, `${similarityLabel(row)}${oneLine(row["memory"])}`);
215
+ writeLine(door.io.stdout, `${similarityLabel(row)}${memoryProjectPrefix(row)}${oneLine(row["memory"])}`);
212
216
  writeLine(door.io.stdout, ` ${String(row["id"] ?? "")} ${String(row["containerTag"] ?? "")}${filedByLabel(row)}`);
213
217
  }
214
218
  }
@@ -217,7 +221,7 @@ async function searchMemory(command, door) {
217
221
  writeLine(door.io.stdout, "Recent context:");
218
222
  for (const row of recent) {
219
223
  const author = typeof row["savedBy"] === "string" && row["savedBy"] ? `(from ${row["savedBy"]}) ` : "";
220
- writeLine(door.io.stdout, ` - ${author}${oneLine(row["preview"] ?? row["memory"])}`);
224
+ writeLine(door.io.stdout, ` - ${memoryProjectPrefix(row)}${row["project"] ? "" : author}${oneLine(row["preview"] ?? row["memory"])}`);
221
225
  }
222
226
  }
223
227
  if (body.degraded) {
@@ -44,6 +44,7 @@ export function runMemoryVerbList(io, json) {
44
44
  " you can forget or update only your own memories (a super admin, any)",
45
45
  " cockpit memory private <id> [--json] only you and super admins can read this memory",
46
46
  " cockpit memory public <id> [--json] every member can read it again (how every memory starts)",
47
+ " cockpit memory rules [--person <name>] [--project <name>] [--limit <n>] [--json]",
47
48
  " cockpit memory profile [--container <tag>] [--json]",
48
49
  " the compiled one-page briefing every session opens with; a read, never a compile",
49
50
  " cockpit memory consolidation [--container <tag>] [--json]",
@@ -23,7 +23,9 @@ function lengthOf(row) {
23
23
  export async function listRecordings(command, door) {
24
24
  const noteId = command.noteId ?? "";
25
25
  const answer = await ask(door, {
26
- path: `/api/notes/recordings?note_id=${encodeURIComponent(noteId)}`,
26
+ path: noteId.startsWith("event:")
27
+ ? `/api/notes/recordings?calendar_event_id=${encodeURIComponent(noteId.slice(6))}`
28
+ : `/api/notes/recordings?note_id=${encodeURIComponent(noteId)}`,
27
29
  method: "GET",
28
30
  label: "notes recordings",
29
31
  timeoutMs: READ_DEADLINE_MS,
@@ -31,9 +33,15 @@ export async function listRecordings(command, door) {
31
33
  if (!answer.ok)
32
34
  return fail(door, answer.reason, answer.detail);
33
35
  const recordings = answer.body.recordings ?? [];
36
+ const missed = answer.body.missed ?? [];
34
37
  if (door.json)
35
- return emit(door, { ok: true, note_id: noteId, recordings });
38
+ return emit(door, { ok: true,
39
+ ...(noteId.startsWith("event:") ? { calendar_event_id: noteId.slice(6) } : { note_id: noteId }), recordings, missed });
40
+ for (const outcome of missed)
41
+ writeLine(door.io.stdout, `No bot transcript: ${outcome.status} (${outcome.reason}), ${outcome.started_at}`);
36
42
  if (recordings.length === 0) {
43
+ if (missed.length > 0)
44
+ return 0;
37
45
  writeLine(door.io.stdout, "No recording is indexed for this note. It was filed before recordings were tracked, or by hand.");
38
46
  return 0;
39
47
  }
@@ -301,7 +301,8 @@ async function waitForTranscription(door, noteId, background = false) {
301
301
  if (!door.json)
302
302
  writeLine(door.io.stderr, lastDetail);
303
303
  }
304
- await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, Math.max(0, deadline - Date.now()))));
304
+ const pause = Math.min(intervalMs, Math.max(0, deadline - Date.now()));
305
+ await (door.io.sleep ? door.io.sleep(pause) : new Promise((resolve) => setTimeout(resolve, pause)));
305
306
  }
306
307
  return { status: "failed", reason: "transcription_wait_timeout" };
307
308
  }
@@ -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.185");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.188");
25
25
  return 0;
26
26
  }
27
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.185",
3
+ "version": "0.2.188",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,8 +27,8 @@
27
27
  "test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-surface-doors.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-cli-doctor-json.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
28
28
  },
29
29
  "dependencies": {
30
- "@bli-cockpit/memory-mcp": "0.1.55",
31
- "@bli-cockpit/mcp": "0.1.99",
30
+ "@bli-cockpit/memory-mcp": "0.1.56",
31
+ "@bli-cockpit/mcp": "0.1.101",
32
32
  "@bli-cockpit/telemetry-core": "0.1.66"
33
33
  },
34
34
  "repository": {