@modelstatus/cli 0.1.88 → 0.1.89

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelstatus/cli",
3
- "version": "0.1.88",
3
+ "version": "0.1.89",
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",
@@ -38,7 +38,8 @@
38
38
  "LICENSE"
39
39
  ],
40
40
  "publishConfig": {
41
- "access": "public"
41
+ "access": "public",
42
+ "provenance": false
42
43
  },
43
44
  "engines": {
44
45
  "node": ">=18"
package/src/fix-prompt.js CHANGED
@@ -50,6 +50,24 @@ function healthPhrase(f) {
50
50
  return `${f.health}${f.retires_date ? `, retires ${f.retires_date}` : ""}`;
51
51
  }
52
52
 
53
+ /** Dedupe + drop empty findings + sort — the rows the prompt actually renders. */
54
+ function buildRows(findings) {
55
+ return (findings || [])
56
+ .map((f) => ({ ...f, refs: dedupeRefs(f.refs) }))
57
+ .filter((f) => f.refs.length)
58
+ .sort((a, b) => (TIER[a.health] ?? 3) - (TIER[b.health] ?? 3)
59
+ || String(a.retires_date || "9999").localeCompare(String(b.retires_date || "9999"))
60
+ || String(a.slug || a.display).localeCompare(String(b.slug || b.display)));
61
+ }
62
+
63
+ /** The model/reference counts the rendered prompt will actually contain (after
64
+ * dedupe and empty-finding drops) — use these for any summary line or toast so
65
+ * it never disagrees with the prompt body. */
66
+ export function promptCounts(findings) {
67
+ const rows = buildRows(findings);
68
+ return { models: rows.length, refs: rows.reduce((n, f) => n + f.refs.length, 0) };
69
+ }
70
+
53
71
  /**
54
72
  * Render the prompt. `findings` = [{ slug, display, health, retires_date,
55
73
  * replacement, refs: [{ file, line, matched, repo? }] }] where `replacement`
@@ -58,15 +76,12 @@ function healthPhrase(f) {
58
76
  * usable refs are dropped; returns null when nothing remains.
59
77
  */
60
78
  export function buildFixPrompt(findings, { date = new Date() } = {}) {
61
- const rows = (findings || [])
62
- .map((f) => ({ ...f, refs: dedupeRefs(f.refs) }))
63
- .filter((f) => f.refs.length)
64
- .sort((a, b) => (TIER[a.health] ?? 3) - (TIER[b.health] ?? 3)
65
- || String(a.retires_date || "9999").localeCompare(String(b.retires_date || "9999"))
66
- || String(a.slug || a.display).localeCompare(String(b.slug || b.display)));
79
+ const rows = buildRows(findings);
67
80
  if (!rows.length) return null;
68
81
 
69
- const day = date.toISOString().slice(0, 10);
82
+ // Local calendar datetoISOString() is UTC and stamps "tomorrow" for
83
+ // anyone west of Greenwich in the evening.
84
+ const day = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
70
85
  const nRefs = rows.reduce((n, f) => n + f.refs.length, 0);
71
86
  const out = [];
72
87
  out.push("# Task: update retired / retiring AI model references");
package/src/index.js CHANGED
@@ -792,7 +792,7 @@ async function cmdPrompt(positional, flags) {
792
792
  const { getRegistry } = await import("./registry/fetch.js");
793
793
  const { resolveLocal, computeHealth, dropResolvedFragments } = await import("./registry/local.js");
794
794
  const { terminalReplacement } = await import("./fix.js");
795
- const { buildFixPrompt } = await import("./fix-prompt.js");
795
+ const { buildFixPrompt, promptCounts } = await import("./fix-prompt.js");
796
796
  const prog = startProgress(true, "fetching the model registry…");
797
797
  const snapshot = await getRegistry({
798
798
  offline: !!flags.offline || process.env.LLMSTATUS_REGISTRY_OFFLINE === "1",
@@ -842,9 +842,9 @@ async function cmdPrompt(positional, flags) {
842
842
  return;
843
843
  }
844
844
  console.log(text);
845
- const nRefs = findings.reduce((n, f) => n + f.refs.length, 0);
845
+ const counts = promptCounts(findings);
846
846
  const copyTool = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
847
- process.stderr.write(`\n(${findings.length} model(s), ${nRefs} reference(s). Copy it straight to your clipboard: mm prompt | ${copyTool})\n`);
847
+ process.stderr.write(`\n(${counts.models} model(s), ${counts.refs} reference(s). Copy it straight to your clipboard: mm prompt | ${copyTool})\n`);
848
848
  }
849
849
 
850
850
  /** List detection sources and whether each can run right now. Live integrations
@@ -861,7 +861,7 @@ async function cmdSources(_positional, flags) {
861
861
  }
862
862
  console.log("\nScan with: mm scan --sources env,aws-secrets,k8s,helm,sql (or --sources all)");
863
863
  console.log("Integrations (toggle with `mm integrations enable <id>`): " + INTEGRATION_IDS.join(", "));
864
- console.log("Options: --region <r> · --namespace <ns> · --kube-context <c> · --db <pg-dsn> --sql-table <t>");
864
+ console.log("Options: --region <r> · --namespace <ns> · --kube-context <c> · --db <pg-dsn> · --sql-table <t>");
865
865
  console.log(" --vercel-project <p> · --vercel-team <t> · --gh-repo <owner/name> · --supabase-ref <ref>");
866
866
  console.log("Safety: secret VALUES never leave your machine — only model ids upload. Use --dry-run to preview.");
867
867
  }
@@ -1083,7 +1083,7 @@ Scan sources (--sources; default = filesystem + enabled integrations; "all" for
1083
1083
  and only ever upload model ids — secret VALUES never leave your machine.
1084
1084
 
1085
1085
  Flags: --update · --api <url> · --key <key> · --project <id|name> · --dir <path> · --yes · --json · --ci · --dry-run
1086
- --offline (use the cached registry) · --model <slug> (fix) · --fail-on <t> · --json-out <file> (ci) · --all · --force (clear)
1086
+ --offline (use the cached registry) · --model <slug> (fix, prompt) · --fail-on <t> · --json-out <file> (ci) · --all · --force (clear)
1087
1087
  --sources <list> · --region <r> · --namespace <ns> · --kube-context <c> · --db <dsn> · --sql-table <t>
1088
1088
  --vercel-project <p> · --vercel-team <t> · --gh-repo <owner/name> · --supabase-ref <ref>
1089
1089
 
@@ -112,7 +112,7 @@ export function InventoryView({ client, ui, dir = ".", active, width = 78, heigh
112
112
  const dying = filtered.filter((u) => u.health && u.health !== "ok" && u.health !== "custom");
113
113
  if (!dying.length) return ui.showToast(query ? "no matching usages need fixing" : "all current — nothing to fix", "#16a34a");
114
114
  try {
115
- const [{ terminalReplacement }, { buildFixPrompt }, { deliverText }, { computeHealth }] = await Promise.all([
115
+ const [{ terminalReplacement }, { buildFixPrompt, promptCounts }, { deliverText }, { computeHealth }] = await Promise.all([
116
116
  import("../../fix.js"), import("../../fix-prompt.js"), import("../../clipboard.js"), import("../../registry/local.js"),
117
117
  ]);
118
118
  const today = new Date();
@@ -146,10 +146,10 @@ export function InventoryView({ client, ui, dir = ".", active, width = 78, heigh
146
146
  }
147
147
  const text = buildFixPrompt([...byModel.values()], { date: today });
148
148
  if (!text) return ui.showToast("no locatable references to hand off", "#d97706");
149
- const nRefs = [...byModel.values()].reduce((n, f) => n + f.refs.length, 0);
149
+ const counts = promptCounts([...byModel.values()]);
150
150
  const res = deliverText(text);
151
- if (res.method === "clipboard") ui.showToast(`${GLYPH.check} LLM fix prompt copied (${byModel.size} model${byModel.size === 1 ? "" : "s"} · ${nRefs} refs) — paste into your AI agent`);
152
- else if (res.method === "file") ui.showToast(`${GLYPH.check} no clipboard tool — prompt saved to ${res.path}`);
151
+ if (res.method === "clipboard") ui.showToast(`${GLYPH.check} LLM fix prompt copied (${counts.models} model${counts.models === 1 ? "" : "s"} · ${counts.refs} refs) — paste into your AI agent`);
152
+ else if (res.method === "file") ui.showToast(`${GLYPH.check} couldn't copy — prompt saved to ${res.path}`);
153
153
  else ui.showToast("couldn't copy or save the prompt", "#dc2626");
154
154
  } catch (e) {
155
155
  ui.showToast(e.message, "red");
@@ -143,7 +143,7 @@ export function LocalView({ client, me, dir, ui, width = 78, height = 14, active
143
143
  const dying = items.filter((it) => it.model && it.health !== "ok" && it.health !== "custom");
144
144
  if (!dying.length) return ui?.showToast?.("all current — nothing to fix", "#16a34a");
145
145
  Promise.all([import("../../fix.js"), import("../../fix-prompt.js"), import("../../clipboard.js")]).then(
146
- ([{ terminalReplacement }, { buildFixPrompt }, { deliverText }]) => {
146
+ ([{ terminalReplacement }, { buildFixPrompt, promptCounts }, { deliverText }]) => {
147
147
  const bySlug = new Map((scan.snapshot?.models || []).map((m) => [m.slug, m]));
148
148
  const today = new Date();
149
149
  const isCurrent = (m) => computeHealth(m, 90, today) === "ok";
@@ -159,10 +159,10 @@ export function LocalView({ client, me, dir, ui, width = 78, height = 14, active
159
159
  }));
160
160
  const text = buildFixPrompt(findings, { date: today });
161
161
  if (!text) return ui?.showToast?.("no file references to hand off", "#d97706");
162
- const nRefs = findings.reduce((n, f) => n + f.refs.length, 0);
162
+ const counts = promptCounts(findings);
163
163
  const res = deliverText(text);
164
- if (res.method === "clipboard") ui?.showToast?.(`${GLYPH.check} LLM fix prompt copied (${dying.length} model${dying.length === 1 ? "" : "s"} · ${nRefs} refs) — paste into your AI agent`);
165
- else if (res.method === "file") ui?.showToast?.(`${GLYPH.check} no clipboard tool — prompt saved to ${res.path}`);
164
+ if (res.method === "clipboard") ui?.showToast?.(`${GLYPH.check} LLM fix prompt copied (${counts.models} model${counts.models === 1 ? "" : "s"} · ${counts.refs} refs) — paste into your AI agent`);
165
+ else if (res.method === "file") ui?.showToast?.(`${GLYPH.check} couldn't copy — prompt saved to ${res.path}`);
166
166
  else ui?.showToast?.("couldn't copy or save the prompt", "#dc2626");
167
167
  },
168
168
  );