@bli-cockpit/cli 0.2.74 → 0.2.76
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/local-args-tower-admin.js +12 -1
- package/dist/commands/local-help-commands.js +6 -1
- package/dist/commands/msg.js +2 -1
- package/dist/commands/ops-render-coverage.js +62 -0
- package/dist/commands/ops-render.js +20 -2
- package/dist/commands/ops.js +12 -1
- package/dist/commands/public-root.js +1 -1
- package/package.json +1 -1
|
@@ -66,6 +66,8 @@ export function parseOpsArgs(args) {
|
|
|
66
66
|
"--skips",
|
|
67
67
|
"--memory",
|
|
68
68
|
"--memory-days",
|
|
69
|
+
// BLI-3798: the coverage half on its own, with the bucket table.
|
|
70
|
+
"--coverage",
|
|
69
71
|
"--person",
|
|
70
72
|
"--dry-run",
|
|
71
73
|
"--json",
|
|
@@ -91,10 +93,19 @@ export function parseOpsArgs(args) {
|
|
|
91
93
|
if (memoryDays !== undefined && !/^[0-9]{1,2}$/u.test(memoryDays)) {
|
|
92
94
|
throw new Error("ops --memory-days takes a whole number of days, 1 to 30.");
|
|
93
95
|
}
|
|
96
|
+
const coverageOnly = values.booleans.has("--coverage");
|
|
97
|
+
const job = optionalNonEmpty(values.flags.get("--job"));
|
|
98
|
+
if (coverageOnly && job !== undefined && job !== "collection-coverage") {
|
|
99
|
+
// Refused rather than silently preferring one: `--coverage --job
|
|
100
|
+
// daily-brief` is somebody asking two different questions in one
|
|
101
|
+
// command, and answering the wrong one is how a board loses trust.
|
|
102
|
+
throw new Error("ops --coverage IS --job collection-coverage. Pass one or the other, not a --job naming a different pipeline.");
|
|
103
|
+
}
|
|
94
104
|
return {
|
|
95
105
|
kind: "ops",
|
|
96
106
|
action: "status",
|
|
97
|
-
job:
|
|
107
|
+
job: coverageOnly ? "collection-coverage" : job,
|
|
108
|
+
coverage: coverageOnly,
|
|
98
109
|
skips: values.booleans.has("--skips"),
|
|
99
110
|
memory: values.booleans.has("--memory") || memoryDays !== undefined,
|
|
100
111
|
...(memoryDays === undefined ? {} : { memoryDays: Number(memoryDays) }),
|
|
@@ -295,7 +295,7 @@ export function localSubcommandHelp(command) {
|
|
|
295
295
|
[
|
|
296
296
|
"ops",
|
|
297
297
|
[
|
|
298
|
-
"Usage: cockpit ops [status [--job <id>] [--skips] [--memory [--memory-days N]] | recompile --person <email|name|id> [--dry-run]] [--json]",
|
|
298
|
+
"Usage: cockpit ops [status [--job <id>] [--coverage] [--skips] [--memory [--memory-days N]] | recompile --person <email|name|id> [--dry-run]] [--json]",
|
|
299
299
|
"",
|
|
300
300
|
" cockpit ops status",
|
|
301
301
|
" One line per scheduled job: when it last produced something, and whether that",
|
|
@@ -306,6 +306,11 @@ export function localSubcommandHelp(command) {
|
|
|
306
306
|
" week rather than a broken cron.",
|
|
307
307
|
" --job <id> asks about one; --skips adds the open Slack and external-sync skips,",
|
|
308
308
|
" grouped by reason. Exits 1 if anything is stale, empty or unreadable.",
|
|
309
|
+
" --coverage is the collection-coverage row on its own, with the BUCKET table:",
|
|
310
|
+
" which bucket the missing sessions are in (staged-not-committed with the upload",
|
|
311
|
+
" ledger's own reason, still pending upload, deliberately withheld, or no row at",
|
|
312
|
+
" all), whose machines they are on, which of their own days, and what to run for",
|
|
313
|
+
" each. That table also prints on the ordinary board whenever anything is missing.",
|
|
309
314
|
" Every machine's line also carries its BLI Memory column — whether the MCP",
|
|
310
315
|
" servers and the recall/save hooks are registered for Claude Code and Codex,",
|
|
311
316
|
" and whether Codex has been told to TRUST its hooks. That column never colours",
|
package/dist/commands/msg.js
CHANGED
|
@@ -61,7 +61,8 @@ async function listChannels(door) {
|
|
|
61
61
|
}
|
|
62
62
|
for (const channel of fetched.channels) {
|
|
63
63
|
const label = channel.is_dm ? "dm" : channel.is_private ? "private" : "public";
|
|
64
|
-
|
|
64
|
+
const workspace = channel.is_dm ? "-" : (channel.slack_workspace ?? "bli");
|
|
65
|
+
writeLine(door.io.stdout, `${channel.id} ${label.padEnd(7)} ${workspace.padEnd(10)} ${channel.name ?? "(unnamed)"}`);
|
|
65
66
|
}
|
|
66
67
|
return 0;
|
|
67
68
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cockpit ops --coverage` — WHICH sessions never arrived (BLI-3798).
|
|
3
|
+
*
|
|
4
|
+
* The board's COVERAGE block already prints a line per person. What it never
|
|
5
|
+
* printed was the BUCKET table: the reasons were computed on the server, sat
|
|
6
|
+
* on `people[].days[].reasons` in every response, and were never rendered
|
|
7
|
+
* anywhere — so `42 sessions seen and never arrived` was the whole of what a
|
|
8
|
+
* person saw, ten QA ticks running, and reading further meant writing a script
|
|
9
|
+
* against production.
|
|
10
|
+
*
|
|
11
|
+
* Same rule as the rest of this file's family: every sentence in here was
|
|
12
|
+
* written on the SERVER (`lib/ops/coverage-rollup.ts` and
|
|
13
|
+
* `lib/ops/coverage-buckets.ts`) and is printed VERBATIM. This module decides
|
|
14
|
+
* order, indentation and column widths and nothing else. A terminal that
|
|
15
|
+
* composed its own fix sentence would eventually tell somebody to run a
|
|
16
|
+
* command the board does not believe in, and the one that is wrong is always
|
|
17
|
+
* the one somebody is reading.
|
|
18
|
+
*
|
|
19
|
+
* A CLI too old to know this section simply does not print it: the buckets
|
|
20
|
+
* ride the ordinary `coverage` section of the same response, so nothing here
|
|
21
|
+
* is load-bearing for delivery. What is load-bearing is that the SERVER also
|
|
22
|
+
* puts the dominant bucket in the `collection-coverage` row's `detail`, which
|
|
23
|
+
* every version prints when the row is `failing`.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* The bucket table, largest first, each with its people and its fix.
|
|
27
|
+
*
|
|
28
|
+
* Printed after the person lines, because "who" comes before "which bucket"
|
|
29
|
+
* when somebody is scanning — but the HEADLINE goes first, since it is the one
|
|
30
|
+
* line that answers the question the row has been failing to answer.
|
|
31
|
+
*/
|
|
32
|
+
export function renderCoverageBuckets(table, dim) {
|
|
33
|
+
if (!table) {
|
|
34
|
+
// Never a silent gap where a section was asked for: an older Tower that
|
|
35
|
+
// does not send the table says so, rather than looking like a clean fleet.
|
|
36
|
+
return [
|
|
37
|
+
"",
|
|
38
|
+
"BUCKETS not sent by this Tower — it predates the coverage bucket table; the per-person lines above are all it knows",
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
const buckets = table.buckets ?? [];
|
|
42
|
+
const lines = ["", `BUCKETS ${table.headline ?? "(no headline)"}`];
|
|
43
|
+
if (buckets.length === 0) {
|
|
44
|
+
lines.push(dim(" nothing is unaccounted for, so there are no buckets to show"));
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
for (const bucket of buckets) {
|
|
48
|
+
lines.push(` ${String(bucket.count ?? 0).padStart(5)} ${bucket.reason ?? "unlabelled"}`);
|
|
49
|
+
for (const person of bucket.people ?? []) {
|
|
50
|
+
const days = (person.days ?? []).join(", ");
|
|
51
|
+
lines.push(dim(` ${String(person.count ?? 0).padStart(4)} ${person.displayName ?? "(person)"}` +
|
|
52
|
+
(days ? ` ${days}` : "")));
|
|
53
|
+
}
|
|
54
|
+
// The fix is NOT dimmed. It is the only line on this board that tells
|
|
55
|
+
// somebody to do something, and a board whose one action is grey is a
|
|
56
|
+
// board that gets scrolled past — which is what ten ticks of `failing`
|
|
57
|
+
// with no named bucket already proved.
|
|
58
|
+
if (bucket.owner)
|
|
59
|
+
lines.push(` fix: ${bucket.owner}`);
|
|
60
|
+
}
|
|
61
|
+
return lines;
|
|
62
|
+
}
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* artifact does and does not prove — because that is exactly the moment
|
|
13
13
|
* somebody is about to conclude something from it.
|
|
14
14
|
*/
|
|
15
|
+
import { renderCoverageBuckets, } from "./ops-render-coverage.js";
|
|
15
16
|
export { renderMemoryHooks, renderMemoryUsage } from "./ops-render-memory.js";
|
|
17
|
+
export { renderCoverageBuckets };
|
|
16
18
|
/** The word a person reads. Short, fixed width, and never a bare colour. */
|
|
17
19
|
export function verdictWord(verdict) {
|
|
18
20
|
switch (verdict) {
|
|
@@ -64,7 +66,15 @@ export function intervalWord(row) {
|
|
|
64
66
|
return `every ${Math.round(hours / 24)}d`;
|
|
65
67
|
return `every ${Math.round(hours)}h`;
|
|
66
68
|
}
|
|
67
|
-
export function renderOpsStatus(payload, dim
|
|
69
|
+
export function renderOpsStatus(payload, dim,
|
|
70
|
+
/**
|
|
71
|
+
* BLI-3798. `alwaysShowBuckets` is what `cockpit ops --coverage` passes: the
|
|
72
|
+
* board hides the table on a clean fleet (it would be nineteen rows of
|
|
73
|
+
* nothing), but somebody who asked for the coverage view specifically is
|
|
74
|
+
* owed the answer "nothing is unaccounted for" out loud rather than an
|
|
75
|
+
* absent section they have to interpret.
|
|
76
|
+
*/
|
|
77
|
+
options = {}) {
|
|
68
78
|
const rows = payload.pipelines ?? [];
|
|
69
79
|
const lines = [];
|
|
70
80
|
const counts = new Map();
|
|
@@ -104,8 +114,16 @@ export function renderOpsStatus(payload, dim) {
|
|
|
104
114
|
}
|
|
105
115
|
if (payload.fleet)
|
|
106
116
|
lines.push(...renderFleet(payload.fleet, dim));
|
|
107
|
-
if (payload.coverage)
|
|
117
|
+
if (payload.coverage) {
|
|
108
118
|
lines.push(...renderCoverage(payload.coverage, dim));
|
|
119
|
+
// BLI-3798. The bucket table rides EVERY board, not only `--coverage`:
|
|
120
|
+
// ten QA ticks of `42 sessions seen and never arrived` is what happens
|
|
121
|
+
// when the reasons are only reachable behind a flag nobody knew about.
|
|
122
|
+
// Skipped entirely on a clean fleet, so it costs a healthy board nothing.
|
|
123
|
+
if (options.alwaysShowBuckets || (payload.coverage.counts?.gapSessions ?? 0) > 0) {
|
|
124
|
+
lines.push(...renderCoverageBuckets(payload.coverage.buckets, dim));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
109
127
|
const slack = payload.skips?.slack;
|
|
110
128
|
const external = payload.skips?.external;
|
|
111
129
|
if (slack || external) {
|
package/dist/commands/ops.js
CHANGED
|
@@ -16,6 +16,15 @@
|
|
|
16
16
|
* the command, and it does not pass silently either: it prints as `n/a` with
|
|
17
17
|
* the reason there is nothing to read.
|
|
18
18
|
*
|
|
19
|
+
* `ops status --coverage` (BLI-3798) is the same door narrowed to the
|
|
20
|
+
* collection-coverage row, with the BUCKET table under it: which bucket the
|
|
21
|
+
* missing sessions are in, whose machines they are on, which of their own days,
|
|
22
|
+
* and one fix sentence per bucket. It exists because the board said
|
|
23
|
+
* `42 sessions seen and never arrived` for ten consecutive QA ticks while the
|
|
24
|
+
* reasons sat one field deeper in the same response, and reading them meant
|
|
25
|
+
* writing a script against production. The table also rides the ordinary board
|
|
26
|
+
* whenever anything is missing, so nobody has to know this flag exists.
|
|
27
|
+
*
|
|
19
28
|
* `ops recompile` spends a real provider call, so it says what it is about to
|
|
20
29
|
* do and reports the ceiling by name. A run that outlives Tower's own 800-second
|
|
21
30
|
* budget produces no response at all, which is indistinguishable from a network
|
|
@@ -83,7 +92,9 @@ async function runOpsStatus(command, io, tower) {
|
|
|
83
92
|
}
|
|
84
93
|
else {
|
|
85
94
|
const styled = colorEnabled(io);
|
|
86
|
-
for (const line of renderOpsStatus(payload, (text) => dim(text, styled)
|
|
95
|
+
for (const line of renderOpsStatus(payload, (text) => dim(text, styled), {
|
|
96
|
+
alwaysShowBuckets: command.coverage === true,
|
|
97
|
+
})) {
|
|
87
98
|
writeLine(io.stdout, line);
|
|
88
99
|
}
|
|
89
100
|
if (memory?.section) {
|
|
@@ -15,7 +15,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
18
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
18
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.76");
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
21
|
|