@modelstatus/cli 0.1.75 → 0.1.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/package.json +1 -1
- package/src/index.js +7 -3
- package/src/registry/local.js +31 -0
- package/src/tui/scan-stream.js +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelstatus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.76",
|
|
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/index.js
CHANGED
|
@@ -373,9 +373,11 @@ async function ciReport(dir, flags, res) {
|
|
|
373
373
|
const uniq = [...new Set(res.candidates.map((c) => c.model_string))];
|
|
374
374
|
const resolved = uniq.length ? (await client.resolve(uniq)).data || [] : [];
|
|
375
375
|
const byStr = new Map(resolved.map((r) => [r.input.toLowerCase(), r]));
|
|
376
|
+
const { dropResolvedFragments } = await import("./registry/local.js");
|
|
377
|
+
const uploadable = dropResolvedFragments(res.candidates, (c) => !!byStr.get(c.model_string.toLowerCase())?.model_id);
|
|
376
378
|
const seen = new Set();
|
|
377
379
|
const usages = [];
|
|
378
|
-
for (const c of
|
|
380
|
+
for (const c of uploadable) {
|
|
379
381
|
const r = byStr.get(c.model_string.toLowerCase());
|
|
380
382
|
const k = `${r?.model_id ?? "custom:" + c.model_string}|${c.location_label}`;
|
|
381
383
|
if (seen.has(k)) continue;
|
|
@@ -685,12 +687,14 @@ function cmdIntegrations(positional, flags) {
|
|
|
685
687
|
async function cmdStatus(positional, flags) {
|
|
686
688
|
const dir = path.resolve(positional[1] || flags.dir || ".");
|
|
687
689
|
const { getRegistry } = await import("./registry/fetch.js");
|
|
688
|
-
const { resolveLocal, computeHealth } = await import("./registry/local.js");
|
|
690
|
+
const { resolveLocal, computeHealth, dropResolvedFragments } = await import("./registry/local.js");
|
|
689
691
|
const snapshot = await getRegistry({ offline: !!flags.offline, log: (m) => process.stderr.write(`! ${m}\n`) });
|
|
690
692
|
|
|
691
|
-
|
|
693
|
+
let candidates = await collectFrom(parseSources(flags), scanOpts(flags, dir), snapshot.detection, explicitSources(flags));
|
|
692
694
|
const resolved = resolveLocal(snapshot, [...new Set(candidates.map((c) => c.model_string))]);
|
|
693
695
|
const byStr = new Map(resolved.map((r) => [r.input.toLowerCase(), r]));
|
|
696
|
+
// Suppress detector fragments of resolved aliases (see dropResolvedFragments).
|
|
697
|
+
candidates = dropResolvedFragments(candidates, (c) => !!byStr.get(c.model_string.toLowerCase())?.model_slug);
|
|
694
698
|
|
|
695
699
|
// Aggregate locations per known model / per custom string.
|
|
696
700
|
const known = new Map(); // slug -> { model, count }
|
package/src/registry/local.js
CHANGED
|
@@ -51,3 +51,34 @@ export function needsAttention(snapshot, retiringWindowDays = 90, today = new Da
|
|
|
51
51
|
.filter((m) => m.health !== "ok")
|
|
52
52
|
.sort((a, b) => String(a.retires_date || "9999-99-99").localeCompare(String(b.retires_date || "9999-99-99")));
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Drop unresolved (custom) candidates that NEST with a resolved match at the
|
|
57
|
+
* same location (either direction of containment). The detector often emits
|
|
58
|
+
* overlapping spans for one id — e.g. from the Bedrock ARN
|
|
59
|
+
* "anthropic.claude-3-sonnet-20240229-v1:0" it emits both
|
|
60
|
+
* "claude-3-sonnet-20240229" (resolves → claude-3-sonnet) and the longer
|
|
61
|
+
* fragment "claude-3-sonnet-20240229-v1" (doesn't resolve) — which would
|
|
62
|
+
* double-count the line as a known model AND a phantom custom id. Both
|
|
63
|
+
* directions are intentional: for variants like "ft:gpt-4:acme", the resolved
|
|
64
|
+
* base model carries the lifecycle signal (a fine-tune dies with its base), so
|
|
65
|
+
* the custom row is noise there too. Customs that share a line with a resolved
|
|
66
|
+
* model WITHOUT string overlap (two different ids on one line) are kept.
|
|
67
|
+
*/
|
|
68
|
+
export function dropResolvedFragments(candidates, isResolved) {
|
|
69
|
+
const locOf = (c) => (c.source_path || c.location_label || "") + ":" + (c.source_line || "");
|
|
70
|
+
const resolvedByLoc = new Map(); // loc → [lowercased resolved strings]
|
|
71
|
+
for (const c of candidates) {
|
|
72
|
+
if (!isResolved(c)) continue;
|
|
73
|
+
const k = locOf(c);
|
|
74
|
+
if (!resolvedByLoc.has(k)) resolvedByLoc.set(k, []);
|
|
75
|
+
resolvedByLoc.get(k).push(c.model_string.toLowerCase());
|
|
76
|
+
}
|
|
77
|
+
return candidates.filter((c) => {
|
|
78
|
+
if (isResolved(c)) return true;
|
|
79
|
+
const here = resolvedByLoc.get(locOf(c));
|
|
80
|
+
if (!here) return true;
|
|
81
|
+
const s = c.model_string.toLowerCase();
|
|
82
|
+
return !here.some((rs) => rs !== s && (rs.includes(s) || s.includes(rs)));
|
|
83
|
+
});
|
|
84
|
+
}
|
package/src/tui/scan-stream.js
CHANGED
|
@@ -11,7 +11,7 @@ import fs from "node:fs";
|
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { getRegistry } from "../registry/fetch.js";
|
|
14
|
-
import { resolveLocal, computeHealth } from "../registry/local.js";
|
|
14
|
+
import { resolveLocal, computeHealth, dropResolvedFragments } from "../registry/local.js";
|
|
15
15
|
import { scanFilesystemStreaming } from "../sources/filesystem.js";
|
|
16
16
|
import { compilePatterns } from "../detect/core.js";
|
|
17
17
|
|
|
@@ -76,6 +76,9 @@ export function loadRegistry(log = () => {}) {
|
|
|
76
76
|
export function summarize(snapshot, candidates) {
|
|
77
77
|
const resolved = resolveLocal(snapshot, [...new Set(candidates.map((c) => c.model_string))]);
|
|
78
78
|
const byStr = new Map(resolved.map((r) => [r.input.toLowerCase(), r]));
|
|
79
|
+
// A custom id that's a fragment of a resolved alias on the same line is a
|
|
80
|
+
// detector artifact, not a finding (e.g. the inner piece of a Bedrock ARN).
|
|
81
|
+
candidates = dropResolvedFragments(candidates, (c) => !!byStr.get(c.model_string.toLowerCase())?.model_slug);
|
|
79
82
|
const known = new Map(); // slug → { model, count }
|
|
80
83
|
const custom = new Map(); // string → count
|
|
81
84
|
const refsBySlug = new Map(); // slug → [candidate]
|