@modelstatus/cli 0.1.32 → 0.1.34
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/package.json +1 -1
- package/src/api.js +2 -0
- package/src/index.js +35 -0
- package/src/tui/views/inventory.js +21 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelstatus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"description": "Track which AI models you use, where, and never get surprised by a retirement. Free offline model-health for any repo (mm status), browser sign-in for cloud inventory + alerts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
package/src/api.js
CHANGED
|
@@ -63,6 +63,8 @@ export function createClient({ apiBase, apiKey }) {
|
|
|
63
63
|
deleteUsage: (id) => req("DELETE", `/usages/${id}`),
|
|
64
64
|
linkUsage: (id, modelId) => req("POST", `/usages/${id}/link`, { model_id: modelId }),
|
|
65
65
|
bulkUpload: (projectId, usages) => req("POST", "/usages/bulk", { project_id: projectId, usages }),
|
|
66
|
+
// Clear inventory: all usages, or `{ all: true }` to also wipe projects + rules + feed.
|
|
67
|
+
clearUsages: ({ all = false, project } = {}) => req("DELETE", `/usages${qs({ all: all ? "true" : undefined, project_id: project })}`),
|
|
66
68
|
|
|
67
69
|
// ci runs (Pro) — record a CI evaluation for the dashboard + alerts
|
|
68
70
|
ciRun: (body) => req("POST", "/ci/runs", body),
|
package/src/index.js
CHANGED
|
@@ -364,6 +364,39 @@ async function cmdCi(positional, flags) {
|
|
|
364
364
|
if (failing.length) process.exit(1);
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/** y/N prompt; returns false on a non-TTY (caller requires --yes there). */
|
|
368
|
+
async function confirm(prompt) {
|
|
369
|
+
if (!process.stdin.isTTY) return false;
|
|
370
|
+
const readline = await import("node:readline");
|
|
371
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
372
|
+
const ans = await new Promise((res) => rl.question(prompt, res));
|
|
373
|
+
rl.close();
|
|
374
|
+
return /^y(es)?$/i.test(String(ans).trim());
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Clear your cloud inventory. `--all` also wipes projects, alert rules + the
|
|
378
|
+
* in-app feed (a full reset). Destructive → confirms unless --yes/--force. */
|
|
379
|
+
async function cmdClear(_positional, flags) {
|
|
380
|
+
const { apiBase, apiKey } = resolveAuth(flags);
|
|
381
|
+
if (!apiKey) {
|
|
382
|
+
console.error("No API key. Run `mm login` or pass --key.");
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
const all = !!flags.all;
|
|
386
|
+
const scope = all ? "ALL usages, projects, alert rules + the in-app feed" : "ALL usages";
|
|
387
|
+
if (!flags.yes && !flags.force) {
|
|
388
|
+
if (!process.stdin.isTTY) {
|
|
389
|
+
console.error(`Refusing to delete ${scope} without confirmation. Re-run with --yes.`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
const ok = await confirm(`Delete ${scope} from your account? This cannot be undone. [y/N] `);
|
|
393
|
+
if (!ok) return console.log("Aborted — nothing deleted.");
|
|
394
|
+
}
|
|
395
|
+
const res = await createClient({ apiBase, apiKey }).clearUsages({ all });
|
|
396
|
+
if (flags.json) return console.log(JSON.stringify(res, null, 2));
|
|
397
|
+
console.log(`✓ Cleared ${res.usages ?? 0} usage(s)${res.projects ? ` + ${res.projects} project(s)` : ""}. Your inventory is clean — rescan to repopulate.`);
|
|
398
|
+
}
|
|
399
|
+
|
|
367
400
|
/** List detection sources and whether each can run right now. */
|
|
368
401
|
async function cmdSources(_positional, flags) {
|
|
369
402
|
const dir = path.resolve(flags.dir || ".");
|
|
@@ -460,6 +493,7 @@ Usage:
|
|
|
460
493
|
mm ci [dir] CI gate: fail the build on deprecated/retiring models (GitHub annotations)
|
|
461
494
|
(--diff <base> limits findings to files changed vs base; auto on PRs via GITHUB_BASE_REF)
|
|
462
495
|
mm sources List detection sources and whether each can run here
|
|
496
|
+
mm clear Delete all tracked usages from your inventory (--all also wipes projects/rules; --yes to skip the prompt)
|
|
463
497
|
mm upgrade Open Stripe checkout and poll until Pro is active
|
|
464
498
|
mm tui Force-launch the TUI (logs you in first if needed)
|
|
465
499
|
|
|
@@ -519,6 +553,7 @@ async function main() {
|
|
|
519
553
|
else if (cmd === "ci") await cmdCi(positional, flags);
|
|
520
554
|
else if (cmd === "status") await cmdStatus(positional, flags);
|
|
521
555
|
else if (cmd === "sources") await cmdSources(positional, flags);
|
|
556
|
+
else if (cmd === "clear") await cmdClear(positional, flags);
|
|
522
557
|
else if (cmd === "upgrade") await cmdUpgrade(positional, flags);
|
|
523
558
|
else if (cmd === "tui" || !cmd) await launchTui(positional[1], flags);
|
|
524
559
|
else if (cmd === "help" || flags.help) console.log(HELP);
|
|
@@ -19,6 +19,7 @@ export const meta = {
|
|
|
19
19
|
{ k: "e", label: "env" },
|
|
20
20
|
{ k: "c", label: "critical" },
|
|
21
21
|
{ k: "d", label: "delete" },
|
|
22
|
+
{ k: "C", label: "clear all" },
|
|
22
23
|
{ k: "r", label: "rescan" },
|
|
23
24
|
{ k: "n", label: "new" },
|
|
24
25
|
{ k: "g", label: "refresh" },
|
|
@@ -43,7 +44,11 @@ export function InventoryView({ client, ui, dir = ".", active, width = 78, heigh
|
|
|
43
44
|
// syntax-highlighted snippet as the Here tab, shown below the list (the side
|
|
44
45
|
// drawer stays). Reads the local file at source_path:source_line if present;
|
|
45
46
|
// keyed on scalar fields so it isn't re-read from disk on every re-render.
|
|
46
|
-
|
|
47
|
+
// Highlight what's actually IN the code: the canonical id ("claude-sonnet-4-6"),
|
|
48
|
+
// not the human display name ("Claude Sonnet 4.6") — the latter never appears in
|
|
49
|
+
// source, so readSnippet would find nothing to highlight. Custom ids carry their
|
|
50
|
+
// own detected string; fall back to display only when there's no id at all.
|
|
51
|
+
const matchStr = cur ? (cur.custom_model_name || cur.canonical_id || cur.model_display) : null;
|
|
47
52
|
const snippet = React.useMemo(() => {
|
|
48
53
|
if (!cur || !cur.source_path) return null;
|
|
49
54
|
// Search from the launch dir UPWARD — the inventory may be viewed from a
|
|
@@ -77,6 +82,21 @@ export function InventoryView({ client, ui, dir = ".", active, width = 78, heigh
|
|
|
77
82
|
if (input === "g") return q.reload();
|
|
78
83
|
if (input === "r") return ui.switchTo("scan");
|
|
79
84
|
if (input === "n") return ui.switchTo("add");
|
|
85
|
+
if (input === "C" && usages.length) {
|
|
86
|
+
return ui.askPrompt(`Type "clear" to delete ALL ${usages.length} usages`, {
|
|
87
|
+
onSubmit: (v) => {
|
|
88
|
+
if (String(v || "").trim().toLowerCase() !== "clear") return ui.showToast("clear cancelled");
|
|
89
|
+
client
|
|
90
|
+
.clearUsages({})
|
|
91
|
+
.then((res) => {
|
|
92
|
+
ui.showToast(`${GLYPH.check} cleared ${res.usages ?? usages.length} usages`);
|
|
93
|
+
setCursor(0);
|
|
94
|
+
q.reload();
|
|
95
|
+
})
|
|
96
|
+
.catch((e) => ui.showToast(e.message, "red"));
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
80
100
|
if (!cur) return;
|
|
81
101
|
if (input === "e") return patch(cur, { environment: ENV_ORDER[(ENV_ORDER.indexOf(cur.environment) + 1) % 4] }, "env updated");
|
|
82
102
|
if (input === "c") return patch(cur, { is_critical: !cur.is_critical }, "critical toggled");
|