@bli-cockpit/cli 0.2.137 → 0.2.141
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/docs-read.js +197 -0
- package/dist/commands/docs.js +26 -32
- package/dist/commands/issue-directory.js +63 -0
- package/dist/commands/issue-show.js +147 -0
- package/dist/commands/issue.js +54 -71
- package/dist/commands/local-args-tower-admin.js +25 -0
- package/dist/commands/local-args-tower-docs-msg.js +95 -12
- package/dist/commands/local-args-tower-work.js +17 -5
- package/dist/commands/local-help-commands-tower-ops.js +134 -0
- package/dist/commands/local-help-commands-tower.js +201 -14
- package/dist/commands/local-help-commands.js +4 -290
- package/dist/commands/local-help.js +3 -2
- package/dist/commands/local.js +8 -0
- package/dist/commands/mail.js +5 -1
- package/dist/commands/memory-store.js +10 -54
- package/dist/commands/memory-verb-list.js +68 -0
- package/dist/commands/msg.js +14 -3
- package/dist/commands/notes-reads.js +29 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/slack.js +59 -15
- package/dist/tower-client.js +29 -7
- package/package.json +3 -3
|
@@ -100,7 +100,7 @@ export function localCommandHelp(command) {
|
|
|
100
100
|
" cockpit models [list|show <provider:model>|compare <provider:model> <provider:model> [...]] [--highlight] [--dashboard-url <url>] [--json]",
|
|
101
101
|
" cockpit scout [board|start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
102
102
|
" cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
|
|
103
|
-
" cockpit slack [coverage [--workspace bli|blue_pearl] [--stale-only] | read [--person <p>] [--channel <c>] [--query <text>] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>]] [--json]",
|
|
103
|
+
" cockpit slack [coverage [--workspace bli|blue_pearl] [--stale-only] [--list] | read [--person <p>] [--channel <c>] [--query <text> [--word]] [--since <YYYY-MM-DD>] [--until <YYYY-MM-DD>] [--limit <n>] [--format compact|full]] [--json]",
|
|
104
104
|
" cockpit settings [personal [--chat-model <key>] [--brief-model <key>] | switches [set <key> <value>] | models [set --chat <key>] [--memory <id>] | cli-floor [<version>] | env list|set --project <p> --file <f> --content-stdin|delete --id <uuid> [--yes]] [--json]",
|
|
105
105
|
" cockpit team [members | invite <email> --role <role> [--team-id <uuid>] | role <userId> --role <role> [--yes] | device [list] | device revoke <id|name> --reason <label> [--note \"<text>\"] [--yes]] [--json]",
|
|
106
106
|
" cockpit workbook [<project> [<doc>]] [--section <id>] [--markdown] [--width <n>] [--dashboard-url <url>] [--json]",
|
|
@@ -117,7 +117,7 @@ export function localCommandHelp(command) {
|
|
|
117
117
|
" cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--workspace <path>] [--json]",
|
|
118
118
|
" cockpit memory [search \"<question>\"|save|update <id>|forget <id>|profile|install|status|log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"] [--container <tag>] [--limit <n>] [--no-recent] [--custom-id <id>] [--extract] [--content-stdin] [--reason <label>] [--file <path>] [--dashboard-url <url>] [--dry-run] [--json]",
|
|
119
119
|
" cockpit clean [--dry-run] [--all-committed] [--reconcile] [--dashboard-url <url>] [--json]",
|
|
120
|
-
" cockpit docs [list|tree|read <id|slug>|create --title <t>|update <id>] [--parent <id>|root|--clear-parent] [--query <text>] [--limit <n>] [--visibility org|private] [--file <path>|--body-stdin] [--allow-empty] [--dashboard-url <url>] [--json]",
|
|
120
|
+
" cockpit docs [list|tree|read <id|slug>|create --title <t>|update <id>] [--parent <id>|root|--clear-parent] [--query <text> [--word]] [--limit <n>] [--visibility org|private] [--file <path>|--body-stdin] [--allow-empty] [--dashboard-url <url>] [--json]",
|
|
121
121
|
" cockpit msg [channels|create <name>|dm <email>|read <channel>|send <channel>|thread <id> --channel <channel>] [--private] [--members a@x,b@y] [--description <text>] [--thread <id>] [--limit <n>] [--dashboard-url <url>] [--json]",
|
|
122
122
|
" cockpit issue [list|show <BLI-id>|create --title <t>|update <BLI-id>|move <BLI-id> <state>|comment <BLI-id>|history <BLI-id>] [--state <s>] [--assignee me|unassigned|<uuid>] [--project <name|id>] [--limit <n>] [--priority 0-4] [--parent <BLI-id>] [--file <path>|--body-stdin] [--dashboard-url <url>] [--json]",
|
|
123
123
|
" cockpit mail [accounts|add-imap --address <a>|inbox|read <thread>|search \"<words>\"|send --account <id> --to <a> --subject <s>|attachment <id> --out <path>|sync <account>|detach <account>] [--account <id>] [--limit <n>] [--unread] [--label <l>] [--file <path>] [--dashboard-url <url>] [--json]",
|
|
@@ -136,6 +136,7 @@ export function localCommandHelp(command) {
|
|
|
136
136
|
" cockpit usage people [--by-repo | --by-topic | --by-subject | --by-task] [--min-confidence high|medium|low] [--person <email|me>] [--all] [--detail] [--since <n>d|<n>h|<iso>] [--until <iso>] [--include-automated] [--dashboard-url <url>] [--json]",
|
|
137
137
|
"",
|
|
138
138
|
`Default dashboard: ${DEFAULT_DASHBOARD_URL}. Omit --dashboard-url for normal production use; pass it only for staging/custom dashboards or to force a different pairing.`,
|
|
139
|
+
"--quiet on any command (or COCKPIT_QUIET=1) drops the `[tower client] responded {...}` trace line from stderr; --json already implies it. A failure still logs — only that success-path trace goes quiet (BLI-10450).",
|
|
139
140
|
].join("\n");
|
|
140
141
|
}
|
|
141
142
|
/**
|
package/dist/commands/local.js
CHANGED
|
@@ -57,6 +57,14 @@ export { assertCollectionRootPersisted } from "./collection-roots.js";
|
|
|
57
57
|
export { runSelfUpdate, SelfUpdateError, } from "./install-update.js";
|
|
58
58
|
import { runRoots } from "./roots.js";
|
|
59
59
|
export async function runLocalCockpitCli(argv, io = defaultIo()) {
|
|
60
|
+
// BLI-10450: `--quiet` is a global toggle, stripped here so no per-command
|
|
61
|
+
// parser's `allowedFlags` needs to learn it, and mirrored onto
|
|
62
|
+
// `COCKPIT_QUIET` so `tower-client.ts` can react without every call site
|
|
63
|
+
// threading a flag through. `--json` already implies it (tower-client.ts).
|
|
64
|
+
if (argv.includes("--quiet")) {
|
|
65
|
+
argv = argv.filter((token) => token !== "--quiet");
|
|
66
|
+
process.env.COCKPIT_QUIET = "1";
|
|
67
|
+
}
|
|
60
68
|
if (isLocalHelpRequest(argv)) {
|
|
61
69
|
writeLine(io.stdout, localCommandHelp(argv[0]));
|
|
62
70
|
return 0;
|
package/dist/commands/mail.js
CHANGED
|
@@ -177,7 +177,11 @@ function messageLine(message) {
|
|
|
177
177
|
const paperclip = message.has_attachments ? "@" : " ";
|
|
178
178
|
const from = message.from_address?.address ?? "(no sender)";
|
|
179
179
|
const day = message.internal_date.slice(0, 16).replace("T", " ");
|
|
180
|
-
|
|
180
|
+
// BLI-10450: a text row carried no thread id, so `mail read` needed a
|
|
181
|
+
// second call with `--json` just to learn what `search`/`inbox` already
|
|
182
|
+
// had in hand. `mail read <thread>` takes this verbatim.
|
|
183
|
+
const thread = message.thread_id ? ` [thread ${message.thread_id}]` : "";
|
|
184
|
+
return `${flag}${paperclip} ${day} ${from.padEnd(30).slice(0, 30)} ${(message.subject ?? "(no subject)").slice(0, 60)}${thread}`;
|
|
181
185
|
}
|
|
182
186
|
async function readThread(command, door) {
|
|
183
187
|
const answer = await askAgentDoor(door, {
|
|
@@ -48,6 +48,7 @@ import { isInteractiveStdin, readPipedText, writeLine } from "./cli-io.js";
|
|
|
48
48
|
import { runMemoryConsolidation } from "./memory-consolidation.js";
|
|
49
49
|
import { runMemoryRequeue } from "./memory-requeue.js";
|
|
50
50
|
import { readNoteFile } from "./notes-file.js";
|
|
51
|
+
export { runMemoryVerbList } from "./memory-verb-list.js";
|
|
51
52
|
const TAG = "[memory cli]";
|
|
52
53
|
const SEARCH_DEADLINE_MS = 60_000;
|
|
53
54
|
/** `--extract` is one model call, sometimes two; the door waits for it when asked to. */
|
|
@@ -130,59 +131,6 @@ async function profileMemory(command, door) {
|
|
|
130
131
|
writeLine(door.io.stdout, `Compiled ${String(profile.compiledAt ?? "—")} from ${String(profile.sourceRows ?? 0)} memories by ${String(profile.model ?? "—")}.`);
|
|
131
132
|
return 0;
|
|
132
133
|
}
|
|
133
|
-
/**
|
|
134
|
-
* The verb list a bare `cockpit memory` prints (BLI-4047).
|
|
135
|
-
*
|
|
136
|
-
* It used to run the installer. Typing a noun to find out what it does is the
|
|
137
|
-
* single most common thing a person does with a new CLI, and this was the one
|
|
138
|
-
* command in the collector that answered that by writing five files in a home
|
|
139
|
-
* directory. Installing is still one word away, and every unattended caller
|
|
140
|
-
* asks for `install` by name in code.
|
|
141
|
-
*/
|
|
142
|
-
export function runMemoryVerbList(io, json) {
|
|
143
|
-
if (json) {
|
|
144
|
-
writeLine(io.stdout, JSON.stringify({
|
|
145
|
-
ok: true,
|
|
146
|
-
noun: "memory",
|
|
147
|
-
machine_verbs: ["install", "status"],
|
|
148
|
-
store_verbs: ["search", "save", "update", "forget", "profile", "consolidation", "requeue"],
|
|
149
|
-
log_verbs: ["log"],
|
|
150
|
-
}));
|
|
151
|
-
return 0;
|
|
152
|
-
}
|
|
153
|
-
for (const line of [
|
|
154
|
-
"cockpit memory — BLI Memory: the store, and this machine's registration.",
|
|
155
|
-
"",
|
|
156
|
-
"The store (Tower, over /api/memory/*):",
|
|
157
|
-
" cockpit memory search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent] [--json]",
|
|
158
|
-
" cockpit memory save [--container <tag>] [--custom-id <id>] [--extract] [--file <path>] [--json]",
|
|
159
|
-
" the memory comes from stdin, or --file; never from the command line",
|
|
160
|
-
" cockpit memory update <id> [--file <path>] [--json] replacement text on stdin",
|
|
161
|
-
" cockpit memory forget <id> [--reason <label>] [--json]",
|
|
162
|
-
" cockpit memory forget --content-stdin --container <tag> [--reason <label>] [--json]",
|
|
163
|
-
" cockpit memory profile [--container <tag>] [--json]",
|
|
164
|
-
" the compiled one-page briefing every session opens with; a read, never a compile",
|
|
165
|
-
" cockpit memory consolidation [--container <tag>] [--json]",
|
|
166
|
-
" what the nightly tidy-up pass did: clusters, edits by verb, refusals, cost",
|
|
167
|
-
" cockpit memory requeue --since <iso> [--until <iso>] [--source hook|harvest] [--reason <label>] [--apply] [--json]",
|
|
168
|
-
" put a window of finished extractions back in the queue after the store refused",
|
|
169
|
-
" their writes; super admins only, and a DRY RUN unless you pass --apply",
|
|
170
|
-
"",
|
|
171
|
-
"This machine:",
|
|
172
|
-
" cockpit memory install [--dry-run] [--json] register the MCP server and the three hooks",
|
|
173
|
-
" cockpit memory status [--json] read the stored config back",
|
|
174
|
-
"",
|
|
175
|
-
"The experience log:",
|
|
176
|
-
" cockpit memory log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"",
|
|
177
|
-
"",
|
|
178
|
-
"Save accepts repeated --alias names (up to 16 names of 64 characters).",
|
|
179
|
-
"Omit --container and this repository's own memory space is used — the same",
|
|
180
|
-
"space the bli-memory MCP server and its hooks read and write.",
|
|
181
|
-
]) {
|
|
182
|
-
writeLine(io.stdout, line);
|
|
183
|
-
}
|
|
184
|
-
return 0;
|
|
185
|
-
}
|
|
186
134
|
/**
|
|
187
135
|
* This repository's memory space, resolved by the MCP server's own resolver.
|
|
188
136
|
* `--container` wins; `--workspace` says which directory to derive it from.
|
|
@@ -222,7 +170,15 @@ async function searchMemory(command, door) {
|
|
|
222
170
|
})}`);
|
|
223
171
|
if (door.json)
|
|
224
172
|
return emitAgentDoor(door, answer.body);
|
|
225
|
-
if (results.length === 0) {
|
|
173
|
+
if (results.length === 0 && body.degraded) {
|
|
174
|
+
// BLI-10448: an empty answer from a search that lost a channel is NOT the
|
|
175
|
+
// record being silent. `semantic_timed_out` is the store answering on
|
|
176
|
+
// keywords alone after its vector search ran out of time, and saying
|
|
177
|
+
// "the record is silent" over it sends a person away from memories that
|
|
178
|
+
// exist.
|
|
179
|
+
writeLine(door.io.stdout, `No memories matched on the channels that answered, and one did not (${body.degraded}). Do not read this as the record being silent; ask again.`);
|
|
180
|
+
}
|
|
181
|
+
else if (results.length === 0) {
|
|
226
182
|
// The distinction the whole store is built on: silence is not an outage.
|
|
227
183
|
// A failure took the `!answer.ok` branch above and named itself there.
|
|
228
184
|
writeLine(door.io.stdout, "No memories matched. The record is silent on this — it does not mean the store is down.");
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verb list a bare `cockpit memory` prints: what the noun does, never a
|
|
3
|
+
* side effect.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `memory-store.ts` (BLI-10445) when that file crossed the
|
|
6
|
+
* 500-line readability ratchet. The seam: `memory-store.ts` talks to the
|
|
7
|
+
* `/api/memory/*` doors; this module talks to nobody and only describes them,
|
|
8
|
+
* so it changes when a verb is added, not when a door's contract moves.
|
|
9
|
+
* `memory-store.ts` re-exports it so the router and tests import one name.
|
|
10
|
+
*
|
|
11
|
+
* Every string moved verbatim.
|
|
12
|
+
*/
|
|
13
|
+
import { writeLine } from "./cli-io.js";
|
|
14
|
+
/**
|
|
15
|
+
* The verb list a bare `cockpit memory` prints (BLI-4047).
|
|
16
|
+
*
|
|
17
|
+
* It used to run the installer. Typing a noun to find out what it does is the
|
|
18
|
+
* single most common thing a person does with a new CLI, and this was the one
|
|
19
|
+
* command in the collector that answered that by writing five files in a home
|
|
20
|
+
* directory. Installing is still one word away, and every unattended caller
|
|
21
|
+
* asks for `install` by name in code.
|
|
22
|
+
*/
|
|
23
|
+
export function runMemoryVerbList(io, json) {
|
|
24
|
+
if (json) {
|
|
25
|
+
writeLine(io.stdout, JSON.stringify({
|
|
26
|
+
ok: true,
|
|
27
|
+
noun: "memory",
|
|
28
|
+
machine_verbs: ["install", "status"],
|
|
29
|
+
store_verbs: ["search", "save", "update", "forget", "profile", "consolidation", "requeue"],
|
|
30
|
+
log_verbs: ["log"],
|
|
31
|
+
}));
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
for (const line of [
|
|
35
|
+
"cockpit memory — BLI Memory: the store, and this machine's registration.",
|
|
36
|
+
"",
|
|
37
|
+
"The store (Tower, over /api/memory/*):",
|
|
38
|
+
" cockpit memory search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent] [--json]",
|
|
39
|
+
" cockpit memory save [--container <tag>] [--custom-id <id>] [--extract] [--file <path>] [--json]",
|
|
40
|
+
" the memory comes from stdin, or --file; never from the command line",
|
|
41
|
+
" cockpit memory update <id> [--file <path>] [--json] replacement text on stdin",
|
|
42
|
+
" cockpit memory forget <id> [--reason <label>] [--json]",
|
|
43
|
+
" cockpit memory forget --content-stdin --container <tag> [--reason <label>] [--json]",
|
|
44
|
+
" cockpit memory profile [--container <tag>] [--json]",
|
|
45
|
+
" the compiled one-page briefing every session opens with; a read, never a compile",
|
|
46
|
+
" cockpit memory consolidation [--container <tag>] [--json]",
|
|
47
|
+
" what the nightly tidy-up pass did: clusters, edits by verb, refusals, cost",
|
|
48
|
+
" cockpit memory requeue --since <iso> [--until <iso>] [--source hook|harvest] [--reason <label>] [--apply] [--json]",
|
|
49
|
+
" put a window of finished extractions back in the queue after the store refused",
|
|
50
|
+
" their writes; super admins only, and a DRY RUN unless you pass --apply",
|
|
51
|
+
"",
|
|
52
|
+
"This machine:",
|
|
53
|
+
" cockpit memory install [--dry-run] [--json] register the MCP server and the three hooks",
|
|
54
|
+
" cockpit memory status [--json] read the stored config back",
|
|
55
|
+
"",
|
|
56
|
+
"The experience log:",
|
|
57
|
+
" cockpit memory log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"",
|
|
58
|
+
"",
|
|
59
|
+
"Search: --limit caps the MEMORIES (default 5, at most 20). Up to 3 Tower pages and",
|
|
60
|
+
"tickets ride beside them whatever --limit says; for more, use `cockpit search`.",
|
|
61
|
+
"Save accepts repeated --alias names (up to 16 names of 64 characters).",
|
|
62
|
+
"Omit --container and this repository's own memory space is used — the same",
|
|
63
|
+
"space the bli-memory MCP server and its hooks read and write.",
|
|
64
|
+
]) {
|
|
65
|
+
writeLine(io.stdout, line);
|
|
66
|
+
}
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
package/dist/commands/msg.js
CHANGED
|
@@ -104,15 +104,26 @@ async function readChannel(command, door) {
|
|
|
104
104
|
});
|
|
105
105
|
if (!answer.ok)
|
|
106
106
|
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
107
|
-
const
|
|
107
|
+
const body = answer.body;
|
|
108
|
+
const messages = body.messages ?? [];
|
|
109
|
+
// BLI-10449: whether this channel is Tower's own or a Slack snapshot, and
|
|
110
|
+
// how fresh — the door's own sentence, printed as sent.
|
|
111
|
+
const provenance = body.provenance ?? null;
|
|
112
|
+
const limitApplied = body.limitApplied ?? null;
|
|
108
113
|
if (door.json)
|
|
109
|
-
return emitAgentDoor(door, { ok: true, channelId: resolved.id, messages });
|
|
114
|
+
return emitAgentDoor(door, { ok: true, channelId: resolved.id, provenance, limitApplied, messages });
|
|
115
|
+
if (provenance?.sentence)
|
|
116
|
+
writeLine(door.io.stdout, provenance.sentence);
|
|
117
|
+
if (limitApplied !== null) {
|
|
118
|
+
writeLine(door.io.stdout, `Showing the newest ${limitApplied}: one read returns at most ${limitApplied} messages.`);
|
|
119
|
+
}
|
|
110
120
|
if (messages.length === 0) {
|
|
111
121
|
writeLine(door.io.stdout, "No messages.");
|
|
112
122
|
return 0;
|
|
113
123
|
}
|
|
114
124
|
for (const message of [...messages].reverse()) {
|
|
115
|
-
|
|
125
|
+
// An imported Slack message has no Tower user; its Slack name is the label.
|
|
126
|
+
const who = message.agent_label ?? message.user_id ?? message.slack_author_label ?? "someone";
|
|
116
127
|
writeLine(door.io.stdout, `${message.created_at} ${who} ${message.content ?? ""}`);
|
|
117
128
|
}
|
|
118
129
|
return 0;
|
|
@@ -4,6 +4,30 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { writeLine } from "./cli-io.js";
|
|
6
6
|
import { ask, fail, emit, sayScope, READ_DEADLINE_MS } from "./notes-door.js";
|
|
7
|
+
/**
|
|
8
|
+
* The lines `notes list` prints under the notes: every meeting record the
|
|
9
|
+
* docs door would show this caller, with the ref `docs read` takes, and which
|
|
10
|
+
* copy is canonical. Empty when there are none.
|
|
11
|
+
*/
|
|
12
|
+
export function meetingDocumentLines(meeting, readVerb) {
|
|
13
|
+
if (!meeting || meeting.count === 0)
|
|
14
|
+
return [];
|
|
15
|
+
const refOf = new Map(meeting.items.map((item) => [item.id, item.slug ?? item.id]));
|
|
16
|
+
const copies = meeting.copies > 0 ? `, ${meeting.copies} of them ${meeting.copies === 1 ? "a copy" : "copies"}` : "";
|
|
17
|
+
const lines = [
|
|
18
|
+
`Meeting records in Docs, not in Notes (${meeting.count}${copies}). Read one with \`${readVerb} <ref>\`:`,
|
|
19
|
+
];
|
|
20
|
+
for (const item of meeting.items) {
|
|
21
|
+
const size = `${Math.max(1, Math.round(item.bodyChars / 1000))} KB${item.parts.length > 0 ? ` in ${item.parts.length + 1} parts` : ""}`;
|
|
22
|
+
const mark = item.canonical
|
|
23
|
+
? item.canonicalReason && item.canonicalReason !== "only_copy" ? ` · canonical (${item.canonicalReason})` : ""
|
|
24
|
+
: ` · copy of ${refOf.get(item.duplicateOf ?? "") ?? item.duplicateOf}`;
|
|
25
|
+
lines.push(` ${item.meetingDate ?? "undated "} ${refOf.get(item.id)} ${item.title} ${item.visibility} · ${item.source} · ${size}${mark}`);
|
|
26
|
+
}
|
|
27
|
+
if (meeting.truncated)
|
|
28
|
+
lines.push(" (the read hit its cap, so there may be more)");
|
|
29
|
+
return lines;
|
|
30
|
+
}
|
|
7
31
|
function libraryQuery(command) {
|
|
8
32
|
const query = new URLSearchParams();
|
|
9
33
|
if (command.series)
|
|
@@ -33,8 +57,11 @@ export async function listNotes(command, door) {
|
|
|
33
57
|
return emit(door, body);
|
|
34
58
|
sayScope(door, body);
|
|
35
59
|
const series = body.series ?? [];
|
|
60
|
+
const inDocs = meetingDocumentLines(body.meetingDocuments, "cockpit docs read");
|
|
36
61
|
if (series.length === 0) {
|
|
37
62
|
writeLine(door.io.stdout, "No meeting notes matched.");
|
|
63
|
+
if (inDocs.length > 0)
|
|
64
|
+
writeLine(door.io.stdout, ["", ...inDocs].join("\n"));
|
|
38
65
|
return 0;
|
|
39
66
|
}
|
|
40
67
|
for (const one of series) {
|
|
@@ -47,6 +74,8 @@ export async function listNotes(command, door) {
|
|
|
47
74
|
}
|
|
48
75
|
writeLine(door.io.stdout, "");
|
|
49
76
|
writeLine(door.io.stdout, `${body.count ?? 0} note(s)${body.more ? ", and more exist beyond the limit" : ""}.`);
|
|
77
|
+
if (inDocs.length > 0)
|
|
78
|
+
writeLine(door.io.stdout, ["", ...inDocs].join("\n"));
|
|
50
79
|
return 0;
|
|
51
80
|
}
|
|
52
81
|
export async function listShelves(command, door) {
|
|
@@ -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.
|
|
24
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.141");
|
|
25
25
|
return 0;
|
|
26
26
|
}
|
|
27
27
|
|
package/dist/commands/slack.js
CHANGED
|
@@ -29,9 +29,15 @@ export async function runSlack(command, io) {
|
|
|
29
29
|
: runReadAction(command, io, tower);
|
|
30
30
|
}
|
|
31
31
|
async function runCoverageAction(command, io, tower) {
|
|
32
|
-
const
|
|
32
|
+
const params = new URLSearchParams();
|
|
33
|
+
if (command.workspace)
|
|
34
|
+
params.set("workspace", command.workspace);
|
|
35
|
+
// BLI-10450: counts alone could not answer "is there a Kaiben channel?".
|
|
36
|
+
if (command.list)
|
|
37
|
+
params.set("list", "1");
|
|
38
|
+
const query = params.toString();
|
|
33
39
|
const result = await callTower(tower, {
|
|
34
|
-
path: `/api/ops/slack/coverage${query}`,
|
|
40
|
+
path: `/api/ops/slack/coverage${query ? `?${query}` : ""}`,
|
|
35
41
|
label: "slack-coverage",
|
|
36
42
|
});
|
|
37
43
|
if (!result.ok) {
|
|
@@ -58,7 +64,7 @@ function writeCoverageReply(command, io, body, coverage, workspaces) {
|
|
|
58
64
|
return;
|
|
59
65
|
}
|
|
60
66
|
const styled = colorEnabled(io);
|
|
61
|
-
for (const line of coverageLines(coverage, workspaces, command.staleOnly === true, (text) => dim(text, styled))) {
|
|
67
|
+
for (const line of coverageLines(coverage, workspaces, command.staleOnly === true, command.list === true, (text) => dim(text, styled))) {
|
|
62
68
|
writeLine(io.stdout, line);
|
|
63
69
|
}
|
|
64
70
|
}
|
|
@@ -71,27 +77,43 @@ function logCoverageRead(command, io, workspaces) {
|
|
|
71
77
|
stale: staleTotal,
|
|
72
78
|
open_skips: workspaces.reduce((sum, row) => sum + numberOf(row["openSkipsTotal"]), 0),
|
|
73
79
|
stale_only: command.staleOnly === true,
|
|
80
|
+
list: command.list === true,
|
|
74
81
|
})}`);
|
|
75
82
|
}
|
|
76
|
-
function coverageLines(coverage, workspaces, staleOnly, paint) {
|
|
83
|
+
function coverageLines(coverage, workspaces, staleOnly, list, paint) {
|
|
77
84
|
const lines = [];
|
|
78
85
|
const staleAfter = numberOf(coverage["staleAfterHours"]);
|
|
79
86
|
for (const workspace of workspaces) {
|
|
80
|
-
writeWorkspaceCoverageLines(lines, workspace, staleAfter, staleOnly, paint);
|
|
87
|
+
writeWorkspaceCoverageLines(lines, workspace, staleAfter, staleOnly, list, paint);
|
|
81
88
|
}
|
|
82
89
|
writeCoverageFooter(lines, coverage, paint);
|
|
83
90
|
return lines;
|
|
84
91
|
}
|
|
85
|
-
function writeWorkspaceCoverageLines(lines, workspace, staleAfter, staleOnly, paint) {
|
|
92
|
+
function writeWorkspaceCoverageLines(lines, workspace, staleAfter, staleOnly, list, paint) {
|
|
86
93
|
const key = String(workspace["workspace"] ?? "?");
|
|
87
94
|
const known = numberOf(workspace["channelsKnown"]);
|
|
88
95
|
const covered = numberOf(workspace["covered"]);
|
|
89
96
|
lines.push(`${key.toUpperCase()} ${covered} of ${known} channels readable`);
|
|
90
97
|
writeVisibilityLines(lines, workspace, staleOnly, paint);
|
|
98
|
+
if (list)
|
|
99
|
+
writeChannelListLines(lines, workspace, paint);
|
|
91
100
|
writeStaleChannelLines(lines, workspace, staleAfter, paint);
|
|
92
101
|
writeOpenSkipLines(lines, workspace, paint);
|
|
93
102
|
lines.push("");
|
|
94
103
|
}
|
|
104
|
+
/** BLI-10450: `--list` names every channel, not just the per-state counts. */
|
|
105
|
+
function writeChannelListLines(lines, workspace, paint) {
|
|
106
|
+
const channels = Array.isArray(workspace["channels"])
|
|
107
|
+
? workspace["channels"]
|
|
108
|
+
: [];
|
|
109
|
+
if (channels.length === 0)
|
|
110
|
+
return;
|
|
111
|
+
for (const channel of channels) {
|
|
112
|
+
const meaning = String(channel["meaning"] ?? "?");
|
|
113
|
+
const line = ` #${String(channel["name"] ?? "?")} ${meaning}`;
|
|
114
|
+
lines.push(meaning === "readable" ? line : paint(line));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
95
117
|
function writeCoverageFooter(lines, coverage, paint) {
|
|
96
118
|
const orphanChannels = numberOf(coverage["orphanChannels"]);
|
|
97
119
|
if (orphanChannels > 0) {
|
|
@@ -175,6 +197,8 @@ function buildReadBody(command) {
|
|
|
175
197
|
body["channel"] = command.channel;
|
|
176
198
|
if (command.query)
|
|
177
199
|
body["query"] = command.query;
|
|
200
|
+
if (command.query && command.word)
|
|
201
|
+
body["word"] = true;
|
|
178
202
|
if (command.since)
|
|
179
203
|
body["since"] = command.since;
|
|
180
204
|
if (command.until)
|
|
@@ -183,16 +207,32 @@ function buildReadBody(command) {
|
|
|
183
207
|
body["limit"] = command.limit;
|
|
184
208
|
return body;
|
|
185
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* BLI-10450: every result used to print twice. `read["summary"]` is built by
|
|
212
|
+
* `apps/dashboard/src/lib/jarvis/chat-v2/tools-slack-render.ts` `renderSummary`
|
|
213
|
+
* for the JARVIS chat tool, which needs ONE self-contained string — so it
|
|
214
|
+
* already embeds a header, one bullet line per message, AND the note. This
|
|
215
|
+
* CLI then rendered the SAME messages a second time as full blocks and
|
|
216
|
+
* appended the note a THIRD time. Only the header (summary's first line) is
|
|
217
|
+
* still printed verbatim, because it is the sentence that keeps an empty
|
|
218
|
+
* answer from reading as "nobody said anything"; the messages are rendered
|
|
219
|
+
* exactly once, in the format the caller asked for, and the note prints once.
|
|
220
|
+
*/
|
|
186
221
|
function writeReadReply(command, io, payload, read, messages) {
|
|
187
222
|
if (command.json) {
|
|
188
223
|
writeLine(io.stdout, JSON.stringify(payload));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const styled = colorEnabled(io);
|
|
227
|
+
const summary = typeof read["summary"] === "string" ? read["summary"] : "";
|
|
228
|
+
const header = summary.split("\n")[0] ?? "";
|
|
229
|
+
if (header)
|
|
230
|
+
writeLine(io.stdout, header);
|
|
231
|
+
if (command.format === "compact") {
|
|
232
|
+
for (const message of messages)
|
|
233
|
+
writeLine(io.stdout, compactMessageLine(message));
|
|
189
234
|
}
|
|
190
235
|
else {
|
|
191
|
-
const styled = colorEnabled(io);
|
|
192
|
-
// The server's own summary, first and verbatim: it is the sentence that
|
|
193
|
-
// keeps an empty answer from reading as "nobody said anything".
|
|
194
|
-
if (typeof read["summary"] === "string")
|
|
195
|
-
writeLine(io.stdout, read["summary"]);
|
|
196
236
|
if (messages.length > 0)
|
|
197
237
|
writeLine(io.stdout, "");
|
|
198
238
|
for (const message of messages) {
|
|
@@ -202,11 +242,15 @@ function writeReadReply(command, io, payload, read, messages) {
|
|
|
202
242
|
writeLine(io.stdout, dim(` ${message["permalink"]}`, styled));
|
|
203
243
|
}
|
|
204
244
|
}
|
|
205
|
-
if (typeof read["note"] === "string" && read["note"]) {
|
|
206
|
-
writeLine(io.stdout, "");
|
|
207
|
-
writeLine(io.stdout, dim(read["note"], styled));
|
|
208
|
-
}
|
|
209
245
|
}
|
|
246
|
+
if (typeof read["note"] === "string" && read["note"]) {
|
|
247
|
+
writeLine(io.stdout, "");
|
|
248
|
+
writeLine(io.stdout, dim(read["note"], styled));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function compactMessageLine(message) {
|
|
252
|
+
return `- #${String(message["channel"] ?? "?")} — ${String(message["author"] ?? "?")} — `
|
|
253
|
+
+ `${String(message["messageTs"] ?? "?")} — "${String(message["text"] ?? "")}" — ${String(message["permalink"] ?? "")}`;
|
|
210
254
|
}
|
|
211
255
|
function logReadResult(command, io, read, messages) {
|
|
212
256
|
const coverage = asRecord(read["coverage"]);
|
package/dist/tower-client.js
CHANGED
|
@@ -29,6 +29,20 @@ import { normalizeDashboardUrl, readResponseJson, responseErrorMessage, } from "
|
|
|
29
29
|
const defaultLog = (line) => {
|
|
30
30
|
console.error(line);
|
|
31
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* BLI-10450: every call printed `[tower client] responded {...}` on stderr,
|
|
34
|
+
* which mixed into content whenever a caller redirected `2>&1` — the exact
|
|
35
|
+
* shape an agent piping `cockpit docs read ... 2>&1` hit. The line is a
|
|
36
|
+
* per-request trace, not a decision (the failure branch below is the one the
|
|
37
|
+
* repo's "never fail silently" rule actually protects), so it is the one
|
|
38
|
+
* allowed to go quiet: under `--json` (the line is never meant for a person
|
|
39
|
+
* reading machine output), a bare `--quiet` (stripped by the router in
|
|
40
|
+
* `local.ts` and mirrored onto `COCKPIT_QUIET` so this module needs no extra
|
|
41
|
+
* plumbing), or the env var directly.
|
|
42
|
+
*/
|
|
43
|
+
export function shouldSuppressTowerSuccessLog(argv = process.argv) {
|
|
44
|
+
return argv.includes("--json") || Boolean(process.env.COCKPIT_QUIET);
|
|
45
|
+
}
|
|
32
46
|
/**
|
|
33
47
|
* Loads the paired device session, or explains in one sentence why it cannot.
|
|
34
48
|
*
|
|
@@ -82,13 +96,21 @@ export async function towerRequest(options) {
|
|
|
82
96
|
try {
|
|
83
97
|
const response = await options.fetch(url, init);
|
|
84
98
|
const respondedMs = Date.now() - startedAt;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
// The success branch always decided something (BLI-10450 kept the
|
|
100
|
+
// "log the success branch too" rule intact) — it is just that under
|
|
101
|
+
// `--json`/`--quiet` the reader already gets that decision back as the
|
|
102
|
+
// command's own stdout object, so a second copy on stderr is noise, not
|
|
103
|
+
// signal. The failure branch below never quiets: a request that reached
|
|
104
|
+
// no response at all is the one case AGENTS.md's logging rule protects.
|
|
105
|
+
if (!shouldSuppressTowerSuccessLog()) {
|
|
106
|
+
log(`[tower client] responded ${JSON.stringify({
|
|
107
|
+
request: options.label,
|
|
108
|
+
http_status: response.status,
|
|
109
|
+
content_type: response.headers.get("content-type") ?? "none",
|
|
110
|
+
streamed: (response.headers.get("content-type") ?? "").includes("application/x-ndjson"),
|
|
111
|
+
elapsed_ms: respondedMs,
|
|
112
|
+
})}`);
|
|
113
|
+
}
|
|
92
114
|
return { ok: true, response, startedAt, respondedMs };
|
|
93
115
|
}
|
|
94
116
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.141",
|
|
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-package-pack.mjs --workspace=@bli-cockpit/cli"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@bli-cockpit/memory-mcp": "0.1.
|
|
31
|
-
"@bli-cockpit/mcp": "0.1.
|
|
30
|
+
"@bli-cockpit/memory-mcp": "0.1.36",
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.66",
|
|
32
32
|
"@bli-cockpit/telemetry-core": "0.1.48"
|
|
33
33
|
}
|
|
34
34
|
}
|