@modelstatus/cli 0.1.52 → 0.1.53
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 +45 -4
- package/src/updater.js +35 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelstatus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.53",
|
|
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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
5
6
|
import { resolveAuth, loadConfig, saveConfig, clearAuth, configFilePath } from "./config.js";
|
|
6
7
|
import { createClient } from "./api.js";
|
|
7
8
|
import { collectFrom, availability, ALL_SOURCE_IDS, getSource } from "./sources/index.js";
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
import { redactValue } from "./redact.js";
|
|
13
14
|
import { assignProjects, buildUsages } from "./upload.js";
|
|
14
15
|
import { loginViaBrowser } from "./auth.js";
|
|
15
|
-
import { maybeCheckForUpdate } from "./updater.js";
|
|
16
|
+
import { maybeCheckForUpdate, forceUpdate } from "./updater.js";
|
|
16
17
|
import { track, maybeAnalyticsNotice } from "./telemetry.js";
|
|
17
18
|
import { BUILD_VERSION } from "./version.js";
|
|
18
19
|
|
|
@@ -691,7 +692,8 @@ Usage:
|
|
|
691
692
|
mm sources List detection sources and whether each can run here
|
|
692
693
|
mm integrations Manage live integrations (list | enable <id> | disable <id> | env <id> <tag>)
|
|
693
694
|
mm clear Delete all tracked usages from your inventory (--all also wipes projects/rules; --yes to skip the prompt)
|
|
694
|
-
mm
|
|
695
|
+
mm update Update to the latest version now and relaunch (or add --update to any command)
|
|
696
|
+
mm upgrade Open Stripe checkout and poll until Pro is active (the paid plan, not the binary)
|
|
695
697
|
mm play [dir] Play Donkey Kong while a background scan walks the dir (just for fun)
|
|
696
698
|
mm tui Force-launch the TUI (logs you in first if needed)
|
|
697
699
|
|
|
@@ -705,7 +707,7 @@ Scan sources (--sources; default = filesystem + enabled integrations; "all" for
|
|
|
705
707
|
Secret sources shell out to your already-authenticated CLIs, run read-only,
|
|
706
708
|
and only ever upload model ids — secret VALUES never leave your machine.
|
|
707
709
|
|
|
708
|
-
Flags: --api <url> · --key <key> · --project <id|name> · --yes · --json · --ci · --dry-run
|
|
710
|
+
Flags: --update · --api <url> · --key <key> · --project <id|name> · --yes · --json · --ci · --dry-run
|
|
709
711
|
--sources <list> · --region <r> · --namespace <ns> · --kube-context <c> · --db <dsn> · --sql-table <t>
|
|
710
712
|
--vercel-project <p> · --vercel-team <t> · --gh-repo <owner/name> · --supabase-ref <ref>
|
|
711
713
|
|
|
@@ -721,13 +723,38 @@ async function maybePrintUpdate(promise) {
|
|
|
721
723
|
// be unsafe. Point the user at a one-line reinstall to a user-owned dir.
|
|
722
724
|
process.stderr.write(`\n✦ mm ${r.to} is available (you're on ${r.from}). Auto-update can't write ${process.execPath} — reinstall:\n curl -fsSL https://llmstatus.ai/install.sh | bash\n`);
|
|
723
725
|
} else {
|
|
724
|
-
process.stderr.write(`\n✓ Updated mm ${r.from} → ${r.to} — your next run uses
|
|
726
|
+
process.stderr.write(`\n✓ Updated mm ${r.from} → ${r.to} — your next run uses it (or run \`mm update\` to relaunch on it now).\n`);
|
|
725
727
|
}
|
|
726
728
|
} catch {
|
|
727
729
|
/* swallow */
|
|
728
730
|
}
|
|
729
731
|
}
|
|
730
732
|
|
|
733
|
+
/**
|
|
734
|
+
* `mm update` (command) or `--update` (flag on any command): apply the in-place
|
|
735
|
+
* self-update NOW and RE-EXEC the fresh binary, so a single run lands on the new
|
|
736
|
+
* version — no opening the app twice. `rerunArgs` is what the new binary should
|
|
737
|
+
* run (the same invocation minus the update trigger). Re-execs + exits on a real
|
|
738
|
+
* update; otherwise prints status and returns (caller continues normally).
|
|
739
|
+
*/
|
|
740
|
+
async function applyUpdate(rerunArgs) {
|
|
741
|
+
const r = await forceUpdate();
|
|
742
|
+
if (r.status === "updated") {
|
|
743
|
+
process.stderr.write(`✓ Updated mm ${r.from} → ${r.to}. Relaunching…\n`);
|
|
744
|
+
const child = spawnSync(process.execPath, rerunArgs, {
|
|
745
|
+
stdio: "inherit",
|
|
746
|
+
env: { ...process.env, MM_JUST_UPDATED: r.to },
|
|
747
|
+
});
|
|
748
|
+
process.exit(child.status ?? 0);
|
|
749
|
+
}
|
|
750
|
+
if (r.status === "latest") process.stderr.write(`✓ Already on the latest mm (${r.version}).\n`);
|
|
751
|
+
else if (r.status === "manual")
|
|
752
|
+
process.stderr.write(`✦ mm ${r.to} is available (you're on ${r.from}), but auto-update can't write ${process.execPath}. Reinstall:\n curl -fsSL https://llmstatus.ai/install.sh | bash\n`);
|
|
753
|
+
else if (r.status === "npm") process.stderr.write(`This is an npm-managed install. Update with:\n npm update -g @modelstatus/cli\n`);
|
|
754
|
+
else if (r.status === "unsupported") process.stderr.write(`Self-update isn't supported for this build. Reinstall:\n curl -fsSL https://llmstatus.ai/install.sh | bash\n`);
|
|
755
|
+
else if (r.status === "error") process.stderr.write(`! Update check failed: ${r.message}\n`);
|
|
756
|
+
}
|
|
757
|
+
|
|
731
758
|
async function main() {
|
|
732
759
|
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
733
760
|
const cmd = positional[0];
|
|
@@ -750,6 +777,20 @@ async function main() {
|
|
|
750
777
|
maybeAnalyticsNotice();
|
|
751
778
|
track("cli_command", { command: cmd || "tui" });
|
|
752
779
|
|
|
780
|
+
// Explicit self-update: `mm update` (command) or `--update` (flag on any
|
|
781
|
+
// command). Apply the update in place and RE-EXEC the fresh binary so this one
|
|
782
|
+
// run lands on the latest — no opening the app twice. `mm update` with nothing
|
|
783
|
+
// else then opens the TUI on the new version.
|
|
784
|
+
if (cmd === "update" || flags.update) {
|
|
785
|
+
const rerun = process.argv.slice(2).filter((a) => a !== "update" && a !== "--update");
|
|
786
|
+
await applyUpdate(rerun); // re-execs + exits when it actually updates
|
|
787
|
+
if (cmd === "update") {
|
|
788
|
+
await launchTui(positional[1], flags);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
// A bare `--update` on another command falls through to run that command.
|
|
792
|
+
}
|
|
793
|
+
|
|
753
794
|
// Kick off the background self-update check — runs in parallel with the
|
|
754
795
|
// user's command, capped at 1/24h, only for shell-installed binaries.
|
|
755
796
|
const updatePromise = maybeCheckForUpdate(flags);
|
package/src/updater.js
CHANGED
|
@@ -133,6 +133,41 @@ async function downloadAndReplace(version, key, expectedSha) {
|
|
|
133
133
|
* "0.1.8 → v0.1.9" which looks like a typo. */
|
|
134
134
|
function dispVer(v) { return String(v).replace(/^v/, ""); }
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Force an update RIGHT NOW for an explicit `mm update` / `--update` — ignores
|
|
138
|
+
* the 30s throttle AND MM_NO_AUTO_UPDATE (the user asked for it). Swaps the
|
|
139
|
+
* binary in place but never re-execs (the caller does, so this one run can land
|
|
140
|
+
* on the new version without opening the app twice). Never throws; returns:
|
|
141
|
+
* { status: "updated", from, to } binary swapped — caller should re-exec
|
|
142
|
+
* { status: "latest", version } already the newest
|
|
143
|
+
* { status: "manual", from, to } newer exists but the dir isn't writable
|
|
144
|
+
* { status: "npm" } npm-managed install (use npm update -g)
|
|
145
|
+
* { status: "unsupported" } not a shell binary / unknown platform
|
|
146
|
+
* { status: "error", message } the check or download failed
|
|
147
|
+
*/
|
|
148
|
+
export async function forceUpdate() {
|
|
149
|
+
try {
|
|
150
|
+
if (!IS_SHELL_INSTALL) return { status: "npm" };
|
|
151
|
+
const key = platformKey();
|
|
152
|
+
if (!key) return { status: "unsupported" };
|
|
153
|
+
|
|
154
|
+
const manifest = await fetchJson(`${CDN}/cli/${CHANNEL_PATH}/version.json`);
|
|
155
|
+
writeCache({ ...(readCache() || {}), last_check: Date.now(), latest_known: manifest.version });
|
|
156
|
+
if (!manifest.version) return { status: "error", message: "manifest has no version" };
|
|
157
|
+
if (compareVer(manifest.version, BUILD_VERSION) <= 0) return { status: "latest", version: dispVer(BUILD_VERSION) };
|
|
158
|
+
|
|
159
|
+
const expectedSha = manifest.sha256?.[key];
|
|
160
|
+
if (!expectedSha) return { status: "error", message: `no sha256 for ${key}` };
|
|
161
|
+
if (!dirWritable(path.dirname(process.execPath))) {
|
|
162
|
+
return { status: "manual", from: dispVer(BUILD_VERSION), to: dispVer(manifest.version) };
|
|
163
|
+
}
|
|
164
|
+
await downloadAndReplace(manifest.version, key, expectedSha);
|
|
165
|
+
return { status: "updated", from: dispVer(BUILD_VERSION), to: dispVer(manifest.version) };
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return { status: "error", message: e?.message ?? String(e) };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
136
171
|
/** Returns { from, to } if an update was completed, else null. Never throws. */
|
|
137
172
|
export async function maybeCheckForUpdate(flags = {}) {
|
|
138
173
|
try {
|