@bli-cockpit/cli 0.2.90 → 0.2.92
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-models.js +33 -5
- package/dist/commands/local-help-commands.js +10 -1
- package/dist/commands/local-help.js +1 -1
- package/dist/commands/memory-hook-counts.js +12 -1
- package/dist/commands/models.js +35 -4
- package/dist/commands/ops-render-memory.js +5 -0
- package/dist/commands/ops-render-spend.js +14 -1
- package/dist/commands/public-root.js +1 -1
- package/package.json +4 -4
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* cockpit models # every card: documented vs measured
|
|
5
5
|
* cockpit models list --json
|
|
6
6
|
* cockpit models show openai:gpt-5.6-terra
|
|
7
|
+
* cockpit models compare openai:gpt-5.6-luna openai:gpt-5.6-terra --highlight
|
|
7
8
|
*
|
|
8
9
|
* Two nouns that look alike and are not: `cockpit model` is the ORG SETTING for
|
|
9
10
|
* which model a slot runs on (`model show`, `model set`); `cockpit models` is
|
|
@@ -11,20 +12,47 @@
|
|
|
11
12
|
* quirks, probe verdicts and stale marks. The help text for each says so.
|
|
12
13
|
*/
|
|
13
14
|
import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
|
|
15
|
+
/** Two is not enough to compare against, five stops being readable (BLI-3919). */
|
|
16
|
+
const COMPARE_MIN = 2;
|
|
17
|
+
const COMPARE_MAX = 4;
|
|
14
18
|
export function parseModelsArgs(args) {
|
|
15
19
|
const values = parseNamedArgs(args, {
|
|
16
|
-
allowedFlags: ["--home", "--dashboard-url", "--json"],
|
|
20
|
+
allowedFlags: ["--home", "--dashboard-url", "--json", "--highlight"],
|
|
17
21
|
valueFlags: ["--home", "--dashboard-url"],
|
|
18
22
|
});
|
|
19
|
-
if (values.positionals.length > 2) {
|
|
20
|
-
throw new Error("models accepts at most an action (list|show) and one model id.");
|
|
21
|
-
}
|
|
22
|
-
const [rawAction, rawId] = values.positionals;
|
|
23
23
|
const base = {
|
|
24
24
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
25
25
|
dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
|
|
26
26
|
json: values.booleans.has("--json"),
|
|
27
27
|
};
|
|
28
|
+
// `compare` is the one verb that takes several ids, so it is parsed before
|
|
29
|
+
// the at-most-two rule the other two live under (BLI-3919).
|
|
30
|
+
if (values.positionals[0] === "compare") {
|
|
31
|
+
const modelIds = values.positionals.slice(1).map((id) => id.trim()).filter((id) => id.length > 0);
|
|
32
|
+
const unique = [...new Set(modelIds)];
|
|
33
|
+
if (unique.length < COMPARE_MIN) {
|
|
34
|
+
throw new Error(`models compare needs at least ${COMPARE_MIN} model ids, e.g. ` +
|
|
35
|
+
"`models compare openai:gpt-5.6-luna openai:gpt-5.6-terra`.");
|
|
36
|
+
}
|
|
37
|
+
if (unique.length > COMPARE_MAX) {
|
|
38
|
+
throw new Error(`models compare holds at most ${COMPARE_MAX} models; ${unique.length} named. ` +
|
|
39
|
+
"A fifth column stops being readable.");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
kind: "models",
|
|
43
|
+
action: "compare",
|
|
44
|
+
modelIds: unique,
|
|
45
|
+
highlight: values.booleans.has("--highlight"),
|
|
46
|
+
...base,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (values.booleans.has("--highlight")) {
|
|
50
|
+
throw new Error("--highlight only means something for `models compare`.");
|
|
51
|
+
}
|
|
52
|
+
if (values.positionals.length > 2) {
|
|
53
|
+
throw new Error("models accepts at most an action (list|show) and one model id.");
|
|
54
|
+
}
|
|
55
|
+
const [rawAction, rawId] = values.positionals;
|
|
28
56
|
if (!rawAction || rawAction === "list") {
|
|
29
57
|
if (rawAction === "list" && rawId) {
|
|
30
58
|
throw new Error("models list takes no model id — use `models show <provider:model>`.");
|
|
@@ -276,7 +276,7 @@ export function localSubcommandHelp(command) {
|
|
|
276
276
|
[
|
|
277
277
|
"models",
|
|
278
278
|
[
|
|
279
|
-
"Usage: cockpit models [list|show <provider:model>] [--dashboard-url <url>] [--json]",
|
|
279
|
+
"Usage: cockpit models [list|show <provider:model>|compare <id> <id> [...]] [--highlight] [--dashboard-url <url>] [--json]",
|
|
280
280
|
"",
|
|
281
281
|
"The model-card shelf: one card per model, with what the PROVIDER documents (context",
|
|
282
282
|
"window, max output, price per million, reasoning modes, tool calling, structured output,",
|
|
@@ -287,6 +287,15 @@ export function localSubcommandHelp(command) {
|
|
|
287
287
|
"STALE:<field> means a measurement contradicted the documentation — reasoning-off refused",
|
|
288
288
|
"where the card says it can be turned off, the belt refused where the card says tool",
|
|
289
289
|
"calling. The line under it is the command that re-reads the provider.",
|
|
290
|
+
"compare puts two to four cards side by side: the same rows the page at /settings/models/",
|
|
291
|
+
"compare renders, including `$ per 100 JARVIS turns` (our own measured tokens a turn times",
|
|
292
|
+
"the provider's price, with the window it was measured over beside it — `not measured`",
|
|
293
|
+
"where no profile exists, never a per-million price relabelled) and the bench row, which",
|
|
294
|
+
"appears only when every model was judged in the SAME run against the SAME baseline;",
|
|
295
|
+
"otherwise it says `benched separately — not a head-to-head` rather than subtracting two",
|
|
296
|
+
"win rates. --highlight marks the winner in each scored row (lowest wins on price and",
|
|
297
|
+
"latency, highest on tokens/s, most wins on the bench); a row where any column is `—` or",
|
|
298
|
+
"`not measured` is not scored at all, and says why.",
|
|
290
299
|
"`cockpit models` is the SHELF; `cockpit model` is the org setting saying which model a",
|
|
291
300
|
"slot runs on. Different nouns, one letter apart, on purpose.",
|
|
292
301
|
"--json writes one object to stdout; operational lines stay on stderr.",
|
|
@@ -71,7 +71,7 @@ export function localCommandHelp(command) {
|
|
|
71
71
|
" cockpit analyze [--workspace <path>] [--dashboard-url <url>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
72
72
|
" cockpit jarvis [question] [--prompt <question>] [--as <person>] [--date <YYYY-MM-DD>] [--thread <name>] [--model <key>] [--image <path>|--file <path>] [--no-stream] [--threads|--history [--limit <n>]] [--trace <id|last>] [--dashboard-url <url>] [--json [--show-approval-code]]",
|
|
73
73
|
" cockpit model [show|set <provider:model>] [--json]",
|
|
74
|
-
" cockpit models [list|show <provider:model>] [--dashboard-url <url>] [--json]",
|
|
74
|
+
" cockpit models [list|show <provider:model>|compare <provider:model> <provider:model> [...]] [--highlight] [--dashboard-url <url>] [--json]",
|
|
75
75
|
" cockpit scout [start|dismiss|undo <experiment-id>] [--days <n>] [--dashboard-url <url>] [--json]",
|
|
76
76
|
" cockpit ops [status [--job <id>] [--skips] [--memory] [--models] | recompile --person <email|name|id> [--dry-run]] [--dashboard-url <url>] [--json]",
|
|
77
77
|
" 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]",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* which is the exact false green this ticket exists to remove.
|
|
28
28
|
*/
|
|
29
29
|
import fs from "node:fs";
|
|
30
|
-
import { memoryHookStatsFilePath, parseMemoryHookStats, summariseMemoryHookWindow, } from "@bli-cockpit/telemetry-core";
|
|
30
|
+
import { memoryHookCharsPercentile, memoryHookStatsFilePath, parseMemoryHookStats, summariseMemoryHookWindow, } from "@bli-cockpit/telemetry-core";
|
|
31
31
|
export function readMemoryHookCounts(options) {
|
|
32
32
|
const readText = options.readText ?? defaultReadText;
|
|
33
33
|
const file = memoryHookStatsFilePath(options.homeDir);
|
|
@@ -55,6 +55,17 @@ export function readMemoryHookCounts(options) {
|
|
|
55
55
|
: {}),
|
|
56
56
|
hook_skipped_trivial_24h: window.skippedTrivial,
|
|
57
57
|
hook_billing_exhausted_24h: window.billingExhausted,
|
|
58
|
+
// Ticket 3934. Reported as the bin's LOWER bound — the smaller, true
|
|
59
|
+
// claim — and only when something was measured. A machine that recorded
|
|
60
|
+
// no histogram carries no key at all, which the board reads as
|
|
61
|
+
// "not recorded"; a zero here would say its recalls were empty.
|
|
62
|
+
...(window.charsMeasured && window.charsSamples > 0
|
|
63
|
+
? {
|
|
64
|
+
hook_chars_p50: memoryHookCharsPercentile(window.chars, 50)?.lower ?? 0,
|
|
65
|
+
hook_chars_p95: memoryHookCharsPercentile(window.chars, 95)?.lower ?? 0,
|
|
66
|
+
hook_chars_samples: window.charsSamples,
|
|
67
|
+
}
|
|
68
|
+
: {}),
|
|
58
69
|
},
|
|
59
70
|
reason: "ok",
|
|
60
71
|
};
|
package/dist/commands/models.js
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* forget about it — so a terminal reads the model page a browser reads, in the
|
|
12
12
|
* same order, including today's spend and the `stale:<field>` markers.
|
|
13
13
|
*
|
|
14
|
+
* `models compare <id> <id> [...]` asks it for TWO TO FOUR cards side by side
|
|
15
|
+
* (BLI-3919): the same rows, the same `$ per 100 JARVIS turns` and the same
|
|
16
|
+
* bench head-to-head the page at `/settings/models/compare` renders, computed
|
|
17
|
+
* by one pure builder on the server. `--highlight` marks the best cell in each
|
|
18
|
+
* scored row; it is off by default, exactly as the page's toggle is.
|
|
19
|
+
*
|
|
14
20
|
* **Every line comes from the server** (`/api/models/cards`, rendered by
|
|
15
21
|
* `lib/models/model-lines.ts` and `card-section-lines.ts`). This file decides an
|
|
16
22
|
* exit code and nothing else. A terminal that formatted a card itself would
|
|
@@ -28,13 +34,29 @@ import { writeLine } from "./cli-io.js";
|
|
|
28
34
|
import { loadPairedSession, towerJsonRequest, } from "../tower-client.js";
|
|
29
35
|
/** A card read is checked-in data on the server side; it should never hang a shell. */
|
|
30
36
|
const READ_DEADLINE_MS = 30_000;
|
|
37
|
+
/**
|
|
38
|
+
* Which question this invocation asks the door.
|
|
39
|
+
*
|
|
40
|
+
* `compare` sends `--highlight` along, because the winner is decided ON THE
|
|
41
|
+
* SERVER and each surface only decides whether to SHOW it — a terminal that
|
|
42
|
+
* scored the rows itself would eventually disagree with the page about which
|
|
43
|
+
* model is cheaper.
|
|
44
|
+
*/
|
|
45
|
+
function requestPath(command) {
|
|
46
|
+
if (command.action === "compare") {
|
|
47
|
+
const ids = (command.modelIds ?? []).join(",");
|
|
48
|
+
return `/api/models/cards?compare=${encodeURIComponent(ids)}${command.highlight ? "&highlight=1" : ""}`;
|
|
49
|
+
}
|
|
50
|
+
if (command.action === "show" && command.modelId) {
|
|
51
|
+
return `/api/models/cards?id=${encodeURIComponent(command.modelId)}§ions=1`;
|
|
52
|
+
}
|
|
53
|
+
return "/api/models/cards";
|
|
54
|
+
}
|
|
31
55
|
export async function runModels(command, io) {
|
|
32
56
|
const session = await loadPairedSession("models", command.homeDir);
|
|
33
57
|
const dashboardUrl = command.dashboardUrl ?? session.dashboard_url;
|
|
34
58
|
const log = (line) => writeLine(io.stderr, line);
|
|
35
|
-
const path = command
|
|
36
|
-
? `/api/models/cards?id=${encodeURIComponent(command.modelId)}§ions=1`
|
|
37
|
-
: "/api/models/cards";
|
|
59
|
+
const path = requestPath(command);
|
|
38
60
|
const result = await towerJsonRequest({
|
|
39
61
|
dashboardUrl,
|
|
40
62
|
path,
|
|
@@ -54,7 +76,13 @@ export async function runModels(command, io) {
|
|
|
54
76
|
writeLine(io.stdout, JSON.stringify({ ok: true, ...payload }));
|
|
55
77
|
}
|
|
56
78
|
else {
|
|
57
|
-
|
|
79
|
+
// A server that predates a branch sends no lines for it; the next-best
|
|
80
|
+
// rendering is printed rather than an empty screen.
|
|
81
|
+
const lines = payload.compare_lines?.length
|
|
82
|
+
? payload.compare_lines
|
|
83
|
+
: payload.section_lines?.length
|
|
84
|
+
? payload.section_lines
|
|
85
|
+
: (payload.lines ?? []);
|
|
58
86
|
for (const line of lines)
|
|
59
87
|
writeLine(io.stdout, line);
|
|
60
88
|
}
|
|
@@ -66,8 +94,10 @@ export async function runModels(command, io) {
|
|
|
66
94
|
writeLine(io.stderr, `[models cli] read ${JSON.stringify({
|
|
67
95
|
action: command.action,
|
|
68
96
|
model: command.modelId ?? null,
|
|
97
|
+
models: command.modelIds?.length ?? 0,
|
|
69
98
|
cards: Array.isArray(payload.cards) ? payload.cards.length : payload.card ? 1 : 0,
|
|
70
99
|
sections: payload.section_lines?.length ? "server" : "absent",
|
|
100
|
+
compare: payload.compare_lines?.length ? "server" : "absent",
|
|
71
101
|
unreadable: payload.unreadable?.length ?? 0,
|
|
72
102
|
})}`);
|
|
73
103
|
return 0;
|
|
@@ -91,5 +121,6 @@ function writeFailure(command, io, failure) {
|
|
|
91
121
|
reason: failure.reason,
|
|
92
122
|
http_status: failure.httpStatus ?? null,
|
|
93
123
|
model: command.modelId ?? null,
|
|
124
|
+
models: command.modelIds?.length ?? 0,
|
|
94
125
|
})}`);
|
|
95
126
|
}
|
|
@@ -110,6 +110,11 @@ export function renderMemoryHooks(hooks, dim) {
|
|
|
110
110
|
// printed verbatim, like every other sentence in this section.
|
|
111
111
|
if (device.viaLine)
|
|
112
112
|
lines.push(dim(` ${device.viaLine}`));
|
|
113
|
+
// Ticket 3934: how much of a person's context the recall took. Written on
|
|
114
|
+
// the server (`lib/ops/memory-hook-misses.ts`) and printed verbatim, like
|
|
115
|
+
// every other sentence in this section.
|
|
116
|
+
if (device.charsLine)
|
|
117
|
+
lines.push(dim(` ${device.charsLine}`));
|
|
113
118
|
}
|
|
114
119
|
return lines;
|
|
115
120
|
}
|
|
@@ -29,8 +29,21 @@ function usdColumn(usd) {
|
|
|
29
29
|
*/
|
|
30
30
|
export function renderSpend(spend, dim) {
|
|
31
31
|
const lines = ["", `SPEND ${spend.summary ?? "(no summary)"}`];
|
|
32
|
+
// The subscription question, on its own line and in the server's own words
|
|
33
|
+
// (ticket 3934). Printed BEFORE the per-model rows because it is the number
|
|
34
|
+
// somebody acts on: the rows say where the money went, this says whether it
|
|
35
|
+
// had to be spent at all.
|
|
36
|
+
if (spend.offsettableLine)
|
|
37
|
+
lines.push(dim(` ${spend.offsettableLine}`));
|
|
32
38
|
for (const row of spend.models ?? []) {
|
|
33
|
-
|
|
39
|
+
// Two dollar columns: what the provider LISTS and what the account was
|
|
40
|
+
// BILLED. They are equal on Fireworks and OpenRouter and differ by the
|
|
41
|
+
// credit divisor on OpenAI, so printing one alone was three times the bill
|
|
42
|
+
// for every OpenAI row and said nothing about which figure it was.
|
|
43
|
+
const real = typeof row.realUsd === "number" && row.realUsd !== row.usd
|
|
44
|
+
? ` (${usdColumn(row.realUsd)} real)`
|
|
45
|
+
: "";
|
|
46
|
+
lines.push(dim(` ${usdColumn(row.usd).padStart(9)}${real} ${row.model ?? "?"} · ${row.calls ?? 0} call(s) · ${row.purpose ?? "?"} · ${(row.bases ?? []).join("+") || "?"} · ${(row.sources ?? []).join(",") || "?"}`));
|
|
34
47
|
}
|
|
35
48
|
return lines;
|
|
36
49
|
}
|
|
@@ -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.92");
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
21
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.92",
|
|
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-runtime-files.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.
|
|
32
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
30
|
+
"@bli-cockpit/memory-mcp": "0.1.22",
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.25",
|
|
32
|
+
"@bli-cockpit/telemetry-core": "0.1.39"
|
|
33
33
|
}
|
|
34
34
|
}
|