@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
package/dist/commands/mail.js
CHANGED
|
@@ -278,7 +278,7 @@ async function send(command, door) {
|
|
|
278
278
|
body: message.body,
|
|
279
279
|
});
|
|
280
280
|
if (!answer.ok)
|
|
281
|
-
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
281
|
+
return failAgentDoor(door, TAG, answer.httpStatus ? answer.reason : "send_outcome_unknown", answer.httpStatus ? answer.detail : "The send receipt was lost. Check Sent before sending again; Tower will not retry automatically.");
|
|
282
282
|
const sent = answer.body.sent;
|
|
283
283
|
// Report only what the door's receipt shows (BLI-12661), never what was posted.
|
|
284
284
|
const unconfirmed = unconfirmedAttachments(message.receipt, sent?.attachments, "send");
|
|
@@ -339,8 +339,8 @@ async function sync(command, door) {
|
|
|
339
339
|
writeLine(door.io.stdout, "Tower answered without a run. Nothing to report.");
|
|
340
340
|
return 0;
|
|
341
341
|
}
|
|
342
|
-
if (
|
|
343
|
-
writeLine(door.io.stdout, `
|
|
342
|
+
if (["ok", "quota_deferred", "history_gap", "interrupted"].includes(run.reason)) {
|
|
343
|
+
writeLine(door.io.stdout, `Sync pass: ${run.messagesAdded} new, ${run.messagesUpdated} updated, ${run.messagesDeleted} gone. ${run.reason}${run.coverage?.complete === false ? "; more work remains" : ""}.`);
|
|
344
344
|
return 0;
|
|
345
345
|
}
|
|
346
346
|
// A failure names itself; the exit code says it failed.
|
|
@@ -367,11 +367,13 @@ async function syncStatus(command, door) {
|
|
|
367
367
|
return 0;
|
|
368
368
|
}
|
|
369
369
|
for (const account of accounts) {
|
|
370
|
+
const coverage = account.last_run?.coverage;
|
|
371
|
+
const coverageLine = coverage ? ` requested=${coverage.requested} returned=${coverage.returned} unique=${coverage.unique} pending=${coverage.pending} complete=${coverage.complete}` : "";
|
|
370
372
|
const lastRun = account.last_run
|
|
371
373
|
? `${account.last_run.reason} @ ${account.last_run.started_at} (${account.last_run.items} item(s))`
|
|
372
374
|
: "never synced";
|
|
373
375
|
const lag = account.lag ? `${account.lag.lag_seconds}s` : "unknown";
|
|
374
|
-
writeLine(door.io.stdout, `${account.address} last_success=${account.last_success_at ?? "never"} last_run=${lastRun} failures_24h=${account.failures_24h} lag=${lag}${account.data_incomplete ? " (partial read)" : ""}`);
|
|
376
|
+
writeLine(door.io.stdout, `${account.address} last_success=${account.last_success_at ?? "never"} last_run=${lastRun} failures_24h=${account.failures_24h} lag=${lag}${account.data_incomplete ? " (partial read)" : ""}${coverageLine}`);
|
|
375
377
|
}
|
|
376
378
|
return 0;
|
|
377
379
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit memory exam` - the nightly memory exam score (BLI-19249).
|
|
3
|
+
*
|
|
4
|
+
* A READ of `GET /api/memory/exam`, with this machine's device token, through `agent-door.ts`: the same door the
|
|
5
|
+
* `memory_exam` MCP tool calls. The exam itself is a step of the nightly `memory-consolidate` Railway job. Output: the
|
|
6
|
+
* one line (purity, graded of planned, trend, worst project); `--misses` adds the missed questions with their reason
|
|
7
|
+
* label, true answer and fix status. The rows are the owner's own words, so no one else reads them, a super admin
|
|
8
|
+
* included; a caller with no exam gets `no exam yet`, never somebody else's night. Logs carry counts only.
|
|
9
|
+
*/
|
|
10
|
+
import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
|
|
11
|
+
import { writeLine } from "./cli-io.js";
|
|
12
|
+
const TAG = "[memory cli]";
|
|
13
|
+
const READ_DEADLINE_MS = 30_000;
|
|
14
|
+
export async function runMemoryExam(command, door) {
|
|
15
|
+
const params = new URLSearchParams();
|
|
16
|
+
if (command.night)
|
|
17
|
+
params.set("night", command.night);
|
|
18
|
+
if (command.misses)
|
|
19
|
+
params.set("misses", "1");
|
|
20
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
21
|
+
const answer = await askAgentDoor(door, { path: `/api/memory/exam${query}`, method: "GET", label: "memory exam", timeoutMs: READ_DEADLINE_MS });
|
|
22
|
+
if (!answer.ok)
|
|
23
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
24
|
+
const body = answer.body;
|
|
25
|
+
writeLine(door.io.stderr, `${TAG} exam ${JSON.stringify({ reason: body.reason ?? "exam_read", night: body.latest?.night ?? null, previous_nights: body.previous?.length ?? 0, misses: body.misses?.length ?? null })}`);
|
|
26
|
+
if (door.json)
|
|
27
|
+
return emitAgentDoor(door, answer.body);
|
|
28
|
+
if (!body.latest || !body.line) {
|
|
29
|
+
writeLine(door.io.stdout, body.reason === "night_not_found"
|
|
30
|
+
? `No exam is recorded for ${command.night ?? "that night"}.`
|
|
31
|
+
: "No memory exam yet. It runs as the last step of the nightly memory-consolidate job; nothing has been recorded for you.");
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
const { latest } = body;
|
|
35
|
+
writeLine(door.io.stdout, body.line);
|
|
36
|
+
for (const detail of body.detail ?? [])
|
|
37
|
+
writeLine(door.io.stdout, detail);
|
|
38
|
+
const reasons = Object.entries(latest.missReasons ?? {}).map(([label, count]) => `${label} ${count}`).join(", ");
|
|
39
|
+
writeLine(door.io.stdout, `Mode ${latest.mode ?? "report"}; spent $${Number(latest.costUsd ?? 0).toFixed(4)}; misses: ${reasons || "none"}.`);
|
|
40
|
+
for (const miss of body.misses ?? []) {
|
|
41
|
+
writeLine(door.io.stdout, ` #${miss.ordinal ?? "?"} [${miss.source ?? "?"}${miss.project ? ` / ${miss.project}` : ""}] ${miss.reason ?? "no reason"}${miss.fixStatus ? ` (${miss.fixKind ?? "fix"}: ${miss.fixStatus})` : ""}`);
|
|
42
|
+
writeLine(door.io.stdout, ` asked: ${miss.question ?? ""}`);
|
|
43
|
+
writeLine(door.io.stdout, ` truth: ${miss.expected ?? ""}`);
|
|
44
|
+
writeLine(door.io.stdout, ` said: ${miss.answer ?? "(no answer)"}`);
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
/** A whole number inside a range, or a refusal that names the flag. */
|
|
5
|
+
function bounded(flag, raw, min, max) {
|
|
6
|
+
if (raw === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (!/^\d+$/.test(raw) || Number(raw) < min || Number(raw) > max)
|
|
9
|
+
throw new Error(`${flag} must be between ${min} and ${max}.`);
|
|
10
|
+
return Number(raw);
|
|
11
|
+
}
|
|
12
|
+
/** BLI-19253: `cockpit memory repeats [--project <name>] [--min <n>] [--limit <n>] [--json]`. */
|
|
13
|
+
export function parseMemoryRepeatsArgs(args) {
|
|
14
|
+
const values = parseNamedArgs(args, { allowedFlags: ["--project", "--min", "--limit", "--json", "--home", "--dashboard-url"], valueFlags: ["--project", "--min", "--limit", "--home", "--dashboard-url"] });
|
|
15
|
+
if (values.positionals.slice(1).length)
|
|
16
|
+
throw new Error("memory repeats takes no positionals; use --project, --min and --limit.");
|
|
17
|
+
return { kind: "memory-store", action: "repeats", containerTags: [], project: optionalNonEmpty(values.flags.get("--project")),
|
|
18
|
+
minCount: bounded("--min", values.flags.get("--min"), 2, 50), limit: bounded("--limit", values.flags.get("--limit"), 1, 200), json: values.booleans.has("--json"),
|
|
19
|
+
homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")) };
|
|
20
|
+
}
|
|
21
|
+
export async function runMemoryRepeats(command, door) {
|
|
22
|
+
const params = new URLSearchParams();
|
|
23
|
+
if (command.project)
|
|
24
|
+
params.set("project", command.project);
|
|
25
|
+
if (command.minCount !== undefined)
|
|
26
|
+
params.set("min", String(command.minCount));
|
|
27
|
+
if (command.limit !== undefined)
|
|
28
|
+
params.set("limit", String(command.limit));
|
|
29
|
+
const query = params.toString();
|
|
30
|
+
const answer = await askAgentDoor(door, { method: "GET", path: `/api/memory/repeats${query ? `?${query}` : ""}`, label: "memory repeats", timeoutMs: 60_000 });
|
|
31
|
+
if (!answer.ok)
|
|
32
|
+
return failAgentDoor(door, "[memory repeats cli]", answer.reason, answer.detail);
|
|
33
|
+
const body = answer.body;
|
|
34
|
+
writeLine(door.io.stderr, `[memory repeats cli] answered ${JSON.stringify({ reason: body.capped ? "capped" : "exhausted", requested: body.requested, returned: body.returned })}`);
|
|
35
|
+
if (door.json)
|
|
36
|
+
return emitAgentDoor(door, body);
|
|
37
|
+
writeLine(door.io.stdout, body.words);
|
|
38
|
+
for (const repeat of body.repeats)
|
|
39
|
+
writeLine(door.io.stdout, `- ${repeat.line}${repeat.project ? ` [${repeat.project}]` : ""}`);
|
|
40
|
+
for (const row of body.coverage)
|
|
41
|
+
writeLine(door.io.stdout, `${row.label}: ${row.labelled.toLocaleString("en-US")} memories, ${row.grouped.toLocaleString("en-US")} grouped by subject`);
|
|
42
|
+
writeLine(door.io.stdout, `As of ${body.asOf}.${body.capped ? " Result capped; raise --limit (max 200)." : ""}`);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How `cockpit memory search` prints its rows (split out of `memory-store.ts`, which is at the file-length ceiling).
|
|
3
|
+
*
|
|
4
|
+
* A merged memory prints its originals under it (BLI-19246); an original found by an exact-detail question prints as
|
|
5
|
+
* its own row, tagged. One already printed as its own row is not repeated under its merged sentence.
|
|
6
|
+
*/
|
|
7
|
+
import { memoryProjectPrefix } from "./memory-project-label.js";
|
|
8
|
+
import { writeLine } from "./cli-io.js";
|
|
9
|
+
/** One terminal line: newlines folded, so a long memory cannot break the listing. */
|
|
10
|
+
export function oneLine(value) {
|
|
11
|
+
return String(value ?? "").replace(/\s+/g, " ").trim();
|
|
12
|
+
}
|
|
13
|
+
/** `[87%] ` when the row was found by meaning, empty when it was a keyword hit. */
|
|
14
|
+
function similarityLabel(row) {
|
|
15
|
+
const channels = row["channels"];
|
|
16
|
+
if (Array.isArray(channels) && !channels.includes("semantic"))
|
|
17
|
+
return "";
|
|
18
|
+
const similarity = Number(row["similarity"]);
|
|
19
|
+
return Number.isFinite(similarity) && similarity >= 0 && similarity <= 1
|
|
20
|
+
? `[${Math.round(similarity * 100)}%] `
|
|
21
|
+
: "";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* BLI-12927: memory is public across the team, so a hit can be a teammate's.
|
|
25
|
+
* ` filed by Edward on 2026-09-14` ahead of nothing when it is the caller's own.
|
|
26
|
+
*/
|
|
27
|
+
function filedByLabel(row) {
|
|
28
|
+
const name = typeof row["savedBy"] === "string" ? row["savedBy"].trim() : "";
|
|
29
|
+
if (!name)
|
|
30
|
+
return "";
|
|
31
|
+
const filed = typeof row["createdAt"] === "string" ? new Date(row["createdAt"]) : null;
|
|
32
|
+
const day = filed && !Number.isNaN(filed.getTime()) ? filed.toISOString().slice(0, 10) : null;
|
|
33
|
+
return day ? ` filed by ${name} on ${day}` : ` filed by ${name}`;
|
|
34
|
+
}
|
|
35
|
+
/** `, true from 2026-07-01 to 2026-09-01`, or nothing when the door sent no window. */
|
|
36
|
+
function windowText(row) {
|
|
37
|
+
return typeof row["window"] === "string" && row["window"] ? `, ${row["window"]}` : "";
|
|
38
|
+
}
|
|
39
|
+
export function printSearchRows(out, results) {
|
|
40
|
+
const printed = new Set(results.filter((entry) => entry["original"] === true || entry["earlierVersion"] === true).map((entry) => String(entry["id"])));
|
|
41
|
+
for (const row of results) {
|
|
42
|
+
const head = row["original"] === true
|
|
43
|
+
? `(original, merged into ${String(row["successorId"] ?? "?")}) `
|
|
44
|
+
: row["earlierVersion"] === true
|
|
45
|
+
// BLI-19247: a fact a newer one replaced, with when it was true.
|
|
46
|
+
? `(earlier version${windowText(row)}, replaced by ${String(row["successorId"] ?? "?")}) `
|
|
47
|
+
: similarityLabel(row);
|
|
48
|
+
writeLine(out, `${head}${memoryProjectPrefix(row)}${oneLine(row["memory"])}`);
|
|
49
|
+
writeLine(out, ` ${String(row["id"] ?? "")} ${String(row["containerTag"] ?? "")}${filedByLabel(row)}`);
|
|
50
|
+
printOriginals(out, row, printed);
|
|
51
|
+
printSource(out, row["source"], " ");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Why a source gave no text, in words (BLI-19255). */
|
|
55
|
+
const SOURCE_STATUS_WORDS = {
|
|
56
|
+
no_pointer: "this memory names no source",
|
|
57
|
+
unsupported_source: "it was imported from another store, so there is no raw source here",
|
|
58
|
+
needs_rls_client: "no session the database can attribute to you; run `cockpit login` again",
|
|
59
|
+
not_readable: "the source belongs to someone else and you may not read it",
|
|
60
|
+
missing: "the source is no longer available",
|
|
61
|
+
too_large: "the stored session is too large to read for an excerpt",
|
|
62
|
+
redaction_stub: "only a redaction stub was stored for that session",
|
|
63
|
+
fact_not_found: "the source was read and does not contain this fact",
|
|
64
|
+
budget: "reading the source ran out of time",
|
|
65
|
+
read_failed: "the archive did not answer",
|
|
66
|
+
};
|
|
67
|
+
/** The verbatim stretch of the raw session or note behind a memory, or the reason there is none (BLI-19255). */
|
|
68
|
+
export function printSource(out, source, indent) {
|
|
69
|
+
if (!source)
|
|
70
|
+
return;
|
|
71
|
+
const excerpt = (source["excerpt"] ?? null);
|
|
72
|
+
if (source["status"] !== "excerpt" || !excerpt) {
|
|
73
|
+
writeLine(out, `${indent}source not shown: ${SOURCE_STATUS_WORDS[String(source["status"])] ?? String(source["status"])}.`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const kind = source["type"] === "meeting_note" ? "meeting note" : "session";
|
|
77
|
+
writeLine(out, `${indent}source, verbatim from the ${kind} (lines ${String(excerpt["startLine"])}-${String(excerpt["endLine"])} of ${String(excerpt["totalLines"])}):`);
|
|
78
|
+
for (const line of String(excerpt["text"] ?? "").split("\n"))
|
|
79
|
+
writeLine(out, `${indent} | ${line}`);
|
|
80
|
+
}
|
|
81
|
+
function printOriginals(out, row, alreadyPrinted) {
|
|
82
|
+
const originals = Array.isArray(row["originals"]) ? row["originals"] : [];
|
|
83
|
+
const fresh = originals.filter((original) => !alreadyPrinted.has(String(original["id"])));
|
|
84
|
+
if (fresh.length === 0)
|
|
85
|
+
return;
|
|
86
|
+
const merged = Number(row["consolidatedFrom"] ?? 0);
|
|
87
|
+
writeLine(out, merged > 0 ? ` merged from ${merged} memories; originals, for exact details:` : " earlier versions, replaced by this one:");
|
|
88
|
+
for (const original of fresh) {
|
|
89
|
+
const earlier = original["kind"] === "earlier_version" ? `[earlier version${windowText(original)}] ` : "";
|
|
90
|
+
writeLine(out, ` - ${earlier}${oneLine(original["memory"])} (${String(original["id"] ?? "")})`);
|
|
91
|
+
}
|
|
92
|
+
const hidden = (row["originalsHidden"] ?? {});
|
|
93
|
+
if (Number(hidden["private"] ?? 0) > 0)
|
|
94
|
+
writeLine(out, ` (${String(hidden["private"])} private original(s) not shown)`);
|
|
95
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit memory source <id> [--query "<words>"] [--chars 200..6000]` (BLI-19255): the raw session or meeting
|
|
3
|
+
* behind a memory, as a bounded verbatim excerpt.
|
|
4
|
+
*
|
|
5
|
+
* Memory is an index over the archive. A READ over `GET /api/memory/source`, the same door the `memory_source` MCP
|
|
6
|
+
* tool calls, with this machine's device token. The door follows the memory's own source pointer and returns the
|
|
7
|
+
* stretch of the stored transcript or note around the fact, as stored (redaction unchanged), or the reason there is
|
|
8
|
+
* none (`not_readable`, `missing`, `too_large`, ...). It reads only what your own session may read and writes nothing.
|
|
9
|
+
*/
|
|
10
|
+
import { askAgentDoor, emitAgentDoor, failAgentDoor } from "./agent-door.js";
|
|
11
|
+
import { writeLine } from "./cli-io.js";
|
|
12
|
+
import { printSource } from "./memory-search-print.js";
|
|
13
|
+
const TAG = "[memory cli]";
|
|
14
|
+
const READ_DEADLINE_MS = 30_000;
|
|
15
|
+
export async function runMemorySource(command, door) {
|
|
16
|
+
if (!command.memoryId) {
|
|
17
|
+
return failAgentDoor(door, TAG, "id_required", "memory source needs the memory id `cockpit memory search` printed.");
|
|
18
|
+
}
|
|
19
|
+
const params = new URLSearchParams({ id: command.memoryId });
|
|
20
|
+
if (command.sourceQuery)
|
|
21
|
+
params.set("query", command.sourceQuery);
|
|
22
|
+
if (command.sourceChars !== undefined)
|
|
23
|
+
params.set("chars", String(command.sourceChars));
|
|
24
|
+
const answer = await askAgentDoor(door, {
|
|
25
|
+
path: `/api/memory/source?${params.toString()}`,
|
|
26
|
+
method: "GET",
|
|
27
|
+
label: "memory source",
|
|
28
|
+
timeoutMs: READ_DEADLINE_MS,
|
|
29
|
+
});
|
|
30
|
+
if (!answer.ok)
|
|
31
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
32
|
+
const body = answer.body;
|
|
33
|
+
// Status and sizes only: a source's text never enters a log line.
|
|
34
|
+
writeLine(door.io.stderr, `${TAG} source ${JSON.stringify({ status: body.source?.status ?? null, type: body.source?.type ?? null, chars: body.source?.excerpt?.chars ?? 0 })}`);
|
|
35
|
+
if (door.json)
|
|
36
|
+
return emitAgentDoor(door, answer.body);
|
|
37
|
+
printSource(door.io.stdout, body.source, "");
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
@@ -43,13 +43,17 @@
|
|
|
43
43
|
* newline in it cannot be quoted correctly anyway.
|
|
44
44
|
*/
|
|
45
45
|
import { runMemoryMap } from "./memory-map.js";
|
|
46
|
+
import { runMemoryRepeats } from "./memory-repeats.js";
|
|
46
47
|
import { runMemoryRules } from "./memory-rules.js";
|
|
47
48
|
import { memoryProjectPrefix } from "./memory-project-label.js";
|
|
49
|
+
import { oneLine, printSearchRows } from "./memory-search-print.js";
|
|
48
50
|
import { memorySaveOutsideRoots } from "@bli-cockpit/memory-mcp/dist/recorded-outside-roots.js";
|
|
49
51
|
import { resolveContainerTag } from "@bli-cockpit/memory-mcp/dist/container-tag.js";
|
|
50
52
|
import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor, } from "./agent-door.js";
|
|
51
53
|
import { isInteractiveStdin, readPipedText, writeLine } from "./cli-io.js";
|
|
52
54
|
import { runMemoryChain } from "./memory-chain.js";
|
|
55
|
+
import { runMemoryExam } from "./memory-exam.js";
|
|
56
|
+
import { runMemorySource } from "./memory-source.js";
|
|
53
57
|
import { runMemoryConsolidation } from "./memory-consolidation.js";
|
|
54
58
|
import { cacheAgeLabel, cacheCoverage, readThroughCache } from "./read-cache.js";
|
|
55
59
|
import { runMemoryRequeue } from "./memory-requeue.js";
|
|
@@ -69,6 +73,9 @@ export async function runMemoryStore(command, io) {
|
|
|
69
73
|
switch (command.action) {
|
|
70
74
|
case "map":
|
|
71
75
|
return runMemoryMap(door);
|
|
76
|
+
case "repeats":
|
|
77
|
+
// BLI-19253: subjects people corrected or ordered more than once. Its own module and door.
|
|
78
|
+
return runMemoryRepeats(command, door);
|
|
72
79
|
case "knowledge":
|
|
73
80
|
case "rules":
|
|
74
81
|
return runMemoryRules(command, door);
|
|
@@ -90,6 +97,12 @@ export async function runMemoryStore(command, io) {
|
|
|
90
97
|
return runMemoryRequeue(command, io);
|
|
91
98
|
case "chain":
|
|
92
99
|
return runMemoryChain(command, door);
|
|
100
|
+
case "exam":
|
|
101
|
+
// BLI-19249. Its own module: the nightly memory exam score, owner-only.
|
|
102
|
+
return runMemoryExam(command, door);
|
|
103
|
+
case "source":
|
|
104
|
+
// BLI-19255: the raw session or meeting behind a memory. Its own module, a different door and shape.
|
|
105
|
+
return runMemorySource(command, door);
|
|
93
106
|
case "private":
|
|
94
107
|
case "public":
|
|
95
108
|
// BLI-12927: the narrowing switch. Its own module, so this file stays
|
|
@@ -163,6 +176,10 @@ async function searchMemory(command, door) {
|
|
|
163
176
|
containerTag: containerTags,
|
|
164
177
|
...(command.limit === undefined ? {} : { limit: command.limit }),
|
|
165
178
|
includeProfile: command.includeRecent !== false,
|
|
179
|
+
// BLI-19246: absent lets the door decide by the question.
|
|
180
|
+
...(command.includeOriginals === undefined ? {} : { includeOriginals: command.includeOriginals }),
|
|
181
|
+
// BLI-19255: absent lets the door decide by the question.
|
|
182
|
+
...(command.includeSource === undefined ? {} : { includeSource: command.includeSource }),
|
|
166
183
|
};
|
|
167
184
|
const answer = await readThroughCache({
|
|
168
185
|
io: door.io,
|
|
@@ -216,10 +233,7 @@ async function searchMemory(command, door) {
|
|
|
216
233
|
writeLine(door.io.stdout, "No memories matched. The record is silent on this — it does not mean the store is down.");
|
|
217
234
|
}
|
|
218
235
|
else {
|
|
219
|
-
|
|
220
|
-
writeLine(door.io.stdout, `${similarityLabel(row)}${memoryProjectPrefix(row)}${oneLine(row["memory"])}`);
|
|
221
|
-
writeLine(door.io.stdout, ` ${String(row["id"] ?? "")} ${String(row["containerTag"] ?? "")}${filedByLabel(row)}`);
|
|
222
|
-
}
|
|
236
|
+
printSearchRows(door.io.stdout, results);
|
|
223
237
|
}
|
|
224
238
|
if (recent.length > 0) {
|
|
225
239
|
writeLine(door.io.stdout, "");
|
|
@@ -382,30 +396,4 @@ async function readBody(command, door, maxChars, what) {
|
|
|
382
396
|
detail: error instanceof Error ? error.message : String(error),
|
|
383
397
|
};
|
|
384
398
|
}
|
|
385
|
-
}
|
|
386
|
-
/** `[87%] ` when the row was found by meaning, empty when it was a keyword hit. */
|
|
387
|
-
function similarityLabel(row) {
|
|
388
|
-
const channels = row["channels"];
|
|
389
|
-
if (Array.isArray(channels) && !channels.includes("semantic"))
|
|
390
|
-
return "";
|
|
391
|
-
const similarity = Number(row["similarity"]);
|
|
392
|
-
return Number.isFinite(similarity) && similarity >= 0 && similarity <= 1
|
|
393
|
-
? `[${Math.round(similarity * 100)}%] `
|
|
394
|
-
: "";
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* BLI-12927: memory is public across the team, so a hit can be a teammate's.
|
|
398
|
-
* ` filed by Edward on 2026-09-14` ahead of nothing when it is the caller's own.
|
|
399
|
-
*/
|
|
400
|
-
function filedByLabel(row) {
|
|
401
|
-
const name = typeof row["savedBy"] === "string" ? row["savedBy"].trim() : "";
|
|
402
|
-
if (!name)
|
|
403
|
-
return "";
|
|
404
|
-
const filed = typeof row["createdAt"] === "string" ? new Date(row["createdAt"]) : null;
|
|
405
|
-
const day = filed && !Number.isNaN(filed.getTime()) ? filed.toISOString().slice(0, 10) : null;
|
|
406
|
-
return day ? ` filed by ${name} on ${day}` : ` filed by ${name}`;
|
|
407
|
-
}
|
|
408
|
-
/** One terminal line: newlines folded, so a long memory cannot break the listing. */
|
|
409
|
-
function oneLine(value) {
|
|
410
|
-
return String(value ?? "").replace(/\s+/g, " ").trim();
|
|
411
399
|
}
|
|
@@ -26,7 +26,7 @@ export function runMemoryVerbList(io, json) {
|
|
|
26
26
|
ok: true,
|
|
27
27
|
noun: "memory",
|
|
28
28
|
machine_verbs: ["install", "status"],
|
|
29
|
-
store_verbs: ["rules", "knowledge", "search", "save", "update", "forget", "private", "public", "profile", "consolidation", "chain", "requeue"],
|
|
29
|
+
store_verbs: ["rules", "knowledge", "search", "save", "update", "forget", "private", "public", "profile", "consolidation", "chain", "source", "exam", "requeue"],
|
|
30
30
|
log_verbs: ["log"],
|
|
31
31
|
}));
|
|
32
32
|
return 0;
|
|
@@ -35,7 +35,7 @@ export function runMemoryVerbList(io, json) {
|
|
|
35
35
|
"cockpit memory — BLI Memory: the store, and this machine's registration.",
|
|
36
36
|
"",
|
|
37
37
|
"The store (Tower, over /api/memory/*):",
|
|
38
|
-
" cockpit memory search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent] [--json]",
|
|
38
|
+
" cockpit memory search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent] [--include-originals|--no-originals] [--include-source|--no-source] [--json]",
|
|
39
39
|
" cockpit memory save [--container <tag>] [--custom-id <id>] [--extract] [--file <path>] [--json]",
|
|
40
40
|
" the memory comes from stdin, or --file; never from the command line",
|
|
41
41
|
" cockpit memory update <id> [--file <path>] [--json] replacement text on stdin",
|
|
@@ -45,6 +45,8 @@ export function runMemoryVerbList(io, json) {
|
|
|
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
47
|
" cockpit memory map [--json]",
|
|
48
|
+
" cockpit memory repeats [--project <name-or-slug>] [--min 2..50] [--limit 1..200] [--json]",
|
|
49
|
+
" subjects a person corrected an agent about, or ordered with emphasis, more than once",
|
|
48
50
|
" cockpit memory rules [--person <name>] [--project <name-or-slug>] [--limit <n>] [--per-project <n>] [--machine] [--json]",
|
|
49
51
|
" cockpit memory knowledge [--person <name>] [--project <name-or-slug>] [--limit <n>] [--per-project <n>] [--machine] [--json]",
|
|
50
52
|
" plain sentences per project; --per-project 1..100 (default 15) is each project's own",
|
|
@@ -55,6 +57,9 @@ export function runMemoryVerbList(io, json) {
|
|
|
55
57
|
" cockpit memory consolidation [--container <tag>] [--json]",
|
|
56
58
|
" what the nightly tidy-up pass did: clusters, edits by verb, refusals, cost",
|
|
57
59
|
" cockpit memory chain <id> [--json]",
|
|
60
|
+
" cockpit memory exam [--night <YYYY-MM-DD>] [--misses] [--json]",
|
|
61
|
+
" the nightly memory exam: purity, coverage, trend and worst project; yours alone",
|
|
62
|
+
" cockpit memory source <id> [--query \"<words>\"] [--chars 200..6000] [--json]",
|
|
58
63
|
" the memories the nightly pass folded into this one: who, when, what they said",
|
|
59
64
|
" cockpit memory requeue --since <iso> [--until <iso>] [--source hook|harvest] [--reason <label>] [--apply] [--json]",
|
|
60
65
|
" put a window of finished extractions back in the queue after the store refused",
|
package/dist/commands/msg.js
CHANGED
|
@@ -25,6 +25,8 @@ const CONTENT_MAX_CHARS = 8_000;
|
|
|
25
25
|
export async function runMsg(command, io) {
|
|
26
26
|
const door = await openAgentDoor("msg", command, io);
|
|
27
27
|
switch (command.action) {
|
|
28
|
+
case "show":
|
|
29
|
+
return showMessage(command, door);
|
|
28
30
|
case "channels":
|
|
29
31
|
return listChannels(door);
|
|
30
32
|
case "read":
|
|
@@ -59,9 +61,20 @@ async function fetchChannels(door) {
|
|
|
59
61
|
});
|
|
60
62
|
if (!answer.ok)
|
|
61
63
|
return { ok: false, reason: answer.reason, detail: answer.detail };
|
|
64
|
+
const channels = answer.body.channels ?? [];
|
|
65
|
+
let page = answer.body;
|
|
66
|
+
for (let count = 0; page.hasMore; count++) {
|
|
67
|
+
if (count >= 200 || !page.nextCursor)
|
|
68
|
+
return { ok: false, reason: "read_failed", detail: "Channel listing is incomplete. Retry online or use an ID." };
|
|
69
|
+
const next = await askAgentDoor(door, { path: `/api/msg/channels?before=${encodeURIComponent(page.nextCursor)}`, method: "GET", label: "msg channels page", timeoutMs: READ_DEADLINE_MS });
|
|
70
|
+
if (!next.ok)
|
|
71
|
+
return { ok: false, reason: next.reason, detail: next.detail };
|
|
72
|
+
channels.push(...(next.body.channels ?? []));
|
|
73
|
+
page = next.body;
|
|
74
|
+
}
|
|
62
75
|
return {
|
|
63
76
|
ok: true,
|
|
64
|
-
channels
|
|
77
|
+
channels,
|
|
65
78
|
fromCache: answer.fromCache,
|
|
66
79
|
ageMs: answer.ageMs,
|
|
67
80
|
coverage: answer.coverage,
|
|
@@ -89,6 +102,12 @@ async function listChannels(door) {
|
|
|
89
102
|
}
|
|
90
103
|
/** Exact id, or exact name with or without a leading `#`. Never fuzzy. */
|
|
91
104
|
async function resolveChannelId(door, ref) {
|
|
105
|
+
if (/^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(ref)) {
|
|
106
|
+
const answer = await askAgentDoor(door, { path: `/api/msg/channels?channel_id=${encodeURIComponent(ref)}`, method: "GET", label: "msg channel", timeoutMs: READ_DEADLINE_MS });
|
|
107
|
+
if (!answer.ok)
|
|
108
|
+
return answer.reason === "channel_not_found_or_unreadable" ? { status: "not_found" } : { status: "list_failed", reason: answer.reason, detail: answer.detail };
|
|
109
|
+
return { status: "ok", id: ref };
|
|
110
|
+
}
|
|
92
111
|
const fetched = await fetchChannels(door);
|
|
93
112
|
if (!fetched.ok)
|
|
94
113
|
return { status: "list_failed", reason: fetched.reason, detail: fetched.detail };
|
|
@@ -112,6 +131,8 @@ async function readChannel(command, door) {
|
|
|
112
131
|
if (resolved.status === "not_found")
|
|
113
132
|
return channelNotFound(door, ref);
|
|
114
133
|
const query = new URLSearchParams();
|
|
134
|
+
if (command.before)
|
|
135
|
+
query.set("before", command.before);
|
|
115
136
|
if (command.limit !== undefined)
|
|
116
137
|
query.set("limit", String(command.limit));
|
|
117
138
|
if (command.threadId)
|
|
@@ -124,7 +145,7 @@ async function readChannel(command, door) {
|
|
|
124
145
|
timeoutMs: READ_DEADLINE_MS,
|
|
125
146
|
});
|
|
126
147
|
if (!answer.ok)
|
|
127
|
-
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
148
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail, answer.body?.coverage === "failed_partial" ? answer.body : {});
|
|
128
149
|
const body = answer.body;
|
|
129
150
|
const messages = body.messages ?? [];
|
|
130
151
|
// BLI-10449: whether this channel is Tower's own or a Slack snapshot, and
|
|
@@ -132,12 +153,14 @@ async function readChannel(command, door) {
|
|
|
132
153
|
const provenance = body.provenance ?? null;
|
|
133
154
|
const limitApplied = body.limitApplied ?? null;
|
|
134
155
|
if (door.json)
|
|
135
|
-
return emitAgentDoor(door, { ok: true, channelId: resolved.id, provenance, limitApplied, messages });
|
|
156
|
+
return emitAgentDoor(door, { ok: true, ...body, channelId: resolved.id, provenance, limitApplied, messages, nextCursor: body.nextCursor, hasMore: body.hasMore, coverage: body.coverage });
|
|
136
157
|
if (provenance?.sentence)
|
|
137
158
|
writeLine(door.io.stdout, provenance.sentence);
|
|
138
159
|
if (limitApplied !== null) {
|
|
139
160
|
writeLine(door.io.stdout, `Showing the newest ${limitApplied}: one read returns at most ${limitApplied} messages.`);
|
|
140
161
|
}
|
|
162
|
+
if (body.nextCursor)
|
|
163
|
+
writeLine(door.io.stdout, `More history: --before ${body.nextCursor}`);
|
|
141
164
|
if (messages.length === 0) {
|
|
142
165
|
writeLine(door.io.stdout, "No messages.");
|
|
143
166
|
return 0;
|
|
@@ -145,7 +168,7 @@ async function readChannel(command, door) {
|
|
|
145
168
|
for (const message of [...messages].reverse()) {
|
|
146
169
|
// An imported Slack message has no Tower user; its Slack name is the label.
|
|
147
170
|
const who = message.agent_label ?? message.user_id ?? message.slack_author_label ?? "someone";
|
|
148
|
-
writeLine(door.io.stdout, `${message.created_at} ${who} ${message.content ?? ""}`);
|
|
171
|
+
writeLine(door.io.stdout, `${message.id} ${message.created_at} ${who} ${message.content ?? ""}`);
|
|
149
172
|
}
|
|
150
173
|
return 0;
|
|
151
174
|
}
|
|
@@ -159,23 +182,26 @@ async function readThread(command, door) {
|
|
|
159
182
|
if (resolved.status === "not_found")
|
|
160
183
|
return channelNotFound(door, command.channelRef);
|
|
161
184
|
const answer = await askAgentDoor(door, {
|
|
162
|
-
path: `/api/msg/channels/${encodeURIComponent(resolved.id)}/messages?thread_parent_id=${encodeURIComponent(command.threadId ?? "")}`,
|
|
185
|
+
path: `/api/msg/channels/${encodeURIComponent(resolved.id)}/messages?thread_parent_id=${encodeURIComponent(command.threadId ?? "")}${command.before ? `&before=${encodeURIComponent(command.before)}` : ""}${command.limit ? `&limit=${command.limit}` : ""}`,
|
|
163
186
|
method: "GET",
|
|
164
187
|
label: "msg thread",
|
|
165
188
|
timeoutMs: READ_DEADLINE_MS,
|
|
166
189
|
});
|
|
167
190
|
if (!answer.ok)
|
|
168
|
-
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
169
|
-
const
|
|
191
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail, answer.body?.coverage === "failed_partial" ? answer.body : {});
|
|
192
|
+
const body = answer.body;
|
|
193
|
+
const messages = body.messages ?? [];
|
|
170
194
|
if (door.json)
|
|
171
|
-
return emitAgentDoor(door, { ok: true, channelId: resolved.id, threadParentId: command.threadId, messages });
|
|
195
|
+
return emitAgentDoor(door, { ok: true, ...body, channelId: resolved.id, threadParentId: command.threadId, messages, nextCursor: body.nextCursor, hasMore: body.hasMore, coverage: body.coverage });
|
|
196
|
+
if (body.nextCursor)
|
|
197
|
+
writeLine(door.io.stdout, `More history: --before ${body.nextCursor}`);
|
|
172
198
|
if (messages.length === 0) {
|
|
173
199
|
writeLine(door.io.stdout, "No replies.");
|
|
174
200
|
return 0;
|
|
175
201
|
}
|
|
176
202
|
for (const message of [...messages].reverse()) {
|
|
177
203
|
const who = message.agent_label ?? message.user_id;
|
|
178
|
-
writeLine(door.io.stdout, `${message.created_at} ${who} ${message.content ?? ""}`);
|
|
204
|
+
writeLine(door.io.stdout, `${message.id} ${message.created_at} ${who} ${message.content ?? ""}`);
|
|
179
205
|
}
|
|
180
206
|
return 0;
|
|
181
207
|
}
|
|
@@ -307,4 +333,20 @@ async function openDm(command, door) {
|
|
|
307
333
|
// true either way rather than claiming a creation that may not have happened.
|
|
308
334
|
writeLine(door.io.stdout, `Direct message with ${email}: ${channel?.id ?? "?"}`);
|
|
309
335
|
return 0;
|
|
336
|
+
}
|
|
337
|
+
async function showMessage(command, door) {
|
|
338
|
+
const answer = await askAgentDoor(door, { path: `/api/msg/messages/${encodeURIComponent(command.threadId ?? "")}`, method: "GET", label: "msg show", timeoutMs: READ_DEADLINE_MS });
|
|
339
|
+
if (!answer.ok)
|
|
340
|
+
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
341
|
+
if (door.json)
|
|
342
|
+
return emitAgentDoor(door, { ok: true, ...answer.body });
|
|
343
|
+
const body = answer.body;
|
|
344
|
+
writeLine(door.io.stdout, body.href ?? "Message context");
|
|
345
|
+
for (const page of [body.channelPage, body.threadPage]) {
|
|
346
|
+
for (const row of [...(page?.messages ?? [])].reverse())
|
|
347
|
+
writeLine(door.io.stdout, `${row.id} ${row.created_at} ${row.content ?? ""}`);
|
|
348
|
+
if (page?.nextCursor)
|
|
349
|
+
writeLine(door.io.stdout, `More history: --before ${page.nextCursor}`);
|
|
350
|
+
}
|
|
351
|
+
return 0;
|
|
310
352
|
}
|
|
@@ -107,6 +107,21 @@ export async function listShelves(command, door) {
|
|
|
107
107
|
}
|
|
108
108
|
return 0;
|
|
109
109
|
}
|
|
110
|
+
/** One line per claim with its citations: `- text [s12 Ada 00:03:04]`. */
|
|
111
|
+
export function renderCitedSummary(summary) {
|
|
112
|
+
const lines = [`Summary v${summary.version} (${summary.supported} cited claims, ${summary.dropped} dropped as unsupported or uncited)`];
|
|
113
|
+
for (const [kind, heading] of [["decision", "Decisions"], ["action_item", "Action items"], ["key_point", "Key points"]]) {
|
|
114
|
+
const items = summary.items.filter((item) => item.kind === kind);
|
|
115
|
+
if (items.length === 0)
|
|
116
|
+
continue;
|
|
117
|
+
lines.push("", `${heading}:`);
|
|
118
|
+
for (const item of items) {
|
|
119
|
+
const cites = item.citations.map((c) => `${c.segment_id} ${c.speaker}${c.at ? ` ${c.at}` : ""}`).join("; ");
|
|
120
|
+
lines.push(`- ${item.owner ? `${item.owner}: ` : ""}${item.text} [${cites}]`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return lines;
|
|
124
|
+
}
|
|
110
125
|
/** `?offset=&limit=` for `notes show`: characters, like `docs read` (BLI-18006). */
|
|
111
126
|
function showQuery(command) {
|
|
112
127
|
const query = new URLSearchParams();
|
|
@@ -149,6 +164,11 @@ export async function showNote(command, door) {
|
|
|
149
164
|
const { offset, returnedChars, totalChars, nextOffset } = body.slice;
|
|
150
165
|
writeLine(door.io.stdout, `Characters ${offset}-${offset + returnedChars} of ${totalChars}${nextOffset === null ? " (the end)" : `; continue with --offset ${nextOffset}`}.`);
|
|
151
166
|
}
|
|
167
|
+
if (body.summary && body.summary.items.length > 0) {
|
|
168
|
+
writeLine(door.io.stdout, "");
|
|
169
|
+
for (const line of renderCitedSummary(body.summary))
|
|
170
|
+
writeLine(door.io.stdout, line);
|
|
171
|
+
}
|
|
152
172
|
writeLine(door.io.stdout, "");
|
|
153
173
|
writeLine(door.io.stdout, note.content.replace(/^In the room:/gm, "Invited:"));
|
|
154
174
|
return 0;
|
|
@@ -205,6 +205,30 @@ export async function shareNote(command, door) {
|
|
|
205
205
|
sayUpload(door, body);
|
|
206
206
|
return 0;
|
|
207
207
|
}
|
|
208
|
+
/** A summary run is two model calls over a long transcript: give it room (BLI-20882). */
|
|
209
|
+
const SUMMARIZE_DEADLINE_MS = 330_000;
|
|
210
|
+
/**
|
|
211
|
+
* `cockpit notes summarize <note-id>` (BLI-20882): write a new summary where every decision,
|
|
212
|
+
* action item and key point cites the speaker, timestamp and segment id it came from. A claim
|
|
213
|
+
* the transcript does not support is dropped. Earlier summaries are kept as older versions.
|
|
214
|
+
*/
|
|
215
|
+
export async function summarizeNote(command, door) {
|
|
216
|
+
const answer = await ask(door, {
|
|
217
|
+
path: `/api/notes/library/${encodeURIComponent(command.noteId ?? "")}/summary`,
|
|
218
|
+
method: "POST",
|
|
219
|
+
label: "notes summarize",
|
|
220
|
+
timeoutMs: SUMMARIZE_DEADLINE_MS,
|
|
221
|
+
body: {},
|
|
222
|
+
});
|
|
223
|
+
if (!answer.ok)
|
|
224
|
+
return fail(door, answer.reason, answer.detail);
|
|
225
|
+
const body = answer.body;
|
|
226
|
+
writeLine(door.io.stderr, `${TAG} summarize answered ${JSON.stringify({ note_id: command.noteId ?? null, status: body.status ?? null })}`);
|
|
227
|
+
if (door.json)
|
|
228
|
+
return emit(door, body);
|
|
229
|
+
writeLine(door.io.stdout, body.text ?? body.message ?? "Tower answered without a summary.");
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
208
232
|
/**
|
|
209
233
|
* `cockpit notes place <note-id> [--apply]` — BLI-4058.
|
|
210
234
|
*
|
package/dist/commands/notes.js
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
*/
|
|
44
44
|
import { loadPairedSession } from "../tower-client.js";
|
|
45
45
|
import { listNotes, listShelves, showNote, showShelf } from "./notes-reads.js";
|
|
46
|
-
import { uploadNotes, pasteNote, shareNote, moveNote, placeNote } from "./notes-writes.js";
|
|
46
|
+
import { uploadNotes, pasteNote, shareNote, summarizeNote, moveNote, placeNote } from "./notes-writes.js";
|
|
47
47
|
import { runFolderCommand } from "./notes-folders.js";
|
|
48
48
|
import { armNotesWebhook, connectNotetaker, detachNotetaker, listNotesAccounts, showNotesWebhook, syncNotetaker, syncStatusNotetakers, } from "./notes-accounts.js";
|
|
49
49
|
import { showBotUpcoming } from "./notes-bot-upcoming.js";
|
|
@@ -51,7 +51,7 @@ import { showGranolaStatus } from "./notes-granola-status.js";
|
|
|
51
51
|
import { showTranscriptionStats } from "./notes-transcription-stats.js";
|
|
52
52
|
import { listRecordings, showRecording } from "./notes-recordings.js";
|
|
53
53
|
export { listNotes, listShelves, showNote, showShelf } from "./notes-reads.js";
|
|
54
|
-
export { uploadNotes, pasteNote, shareNote, moveNote, placeNote } from "./notes-writes.js";
|
|
54
|
+
export { uploadNotes, pasteNote, shareNote, summarizeNote, moveNote, placeNote } from "./notes-writes.js";
|
|
55
55
|
export { armNotesWebhook, connectNotetaker, detachNotetaker, listNotesAccounts, showNotesWebhook, syncNotetaker, syncStatusNotetakers, } from "./notes-accounts.js";
|
|
56
56
|
export { showGranolaStatus } from "./notes-granola-status.js";
|
|
57
57
|
export { showTranscriptionStats } from "./notes-transcription-stats.js";
|
|
@@ -86,6 +86,8 @@ export async function runNotes(command, io) {
|
|
|
86
86
|
case "share":
|
|
87
87
|
case "unshare":
|
|
88
88
|
return shareNote(command, door);
|
|
89
|
+
case "summarize":
|
|
90
|
+
return summarizeNote(command, door);
|
|
89
91
|
case "move":
|
|
90
92
|
return moveNote(command, door);
|
|
91
93
|
case "place":
|