modelstat 0.5.1 → 0.7.0

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/cli.mjs CHANGED
@@ -37086,7 +37086,7 @@ var init_scan = __esm({
37086
37086
  init_api();
37087
37087
  init_config2();
37088
37088
  init_pipeline2();
37089
- DAEMON_VERSION = true ? "daemon-0.5.1" : "daemon-dev";
37089
+ DAEMON_VERSION = true ? "daemon-0.7.0" : "daemon-dev";
37090
37090
  BATCH_MAX_EVENTS = INGEST_BATCH_MAX_EVENTS;
37091
37091
  BATCH_MAX_TOOL_CALLS = 2e4;
37092
37092
  BATCH_BUFFER_HARD_CAP = BATCH_MAX_EVENTS * 2;
@@ -37239,6 +37239,90 @@ var init_lock = __esm({
37239
37239
  }
37240
37240
  });
37241
37241
 
37242
+ // src/update.ts
37243
+ import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
37244
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
37245
+ import { platform as platform4 } from "os";
37246
+ function prefPath() {
37247
+ return homePath("auto-update.json");
37248
+ }
37249
+ function storedAutoUpdate() {
37250
+ try {
37251
+ const o = JSON.parse(readFileSync8(prefPath(), "utf8"));
37252
+ return o.autoUpdate ?? true;
37253
+ } catch {
37254
+ return true;
37255
+ }
37256
+ }
37257
+ function setStoredAutoUpdate(enabled) {
37258
+ mkdirSync6(modelstatHome(), { recursive: true, mode: 448 });
37259
+ const tmp = `${prefPath()}.${process.pid}.tmp`;
37260
+ writeFileSync6(tmp, JSON.stringify({ autoUpdate: enabled }), { mode: 384 });
37261
+ renameSync4(tmp, prefPath());
37262
+ }
37263
+ function parseFlag(v) {
37264
+ if (v == null) return null;
37265
+ const s = v.trim().toLowerCase();
37266
+ if (["0", "off", "false", "no", "disable", "disabled"].includes(s)) return false;
37267
+ if (["1", "on", "true", "yes", "enable", "enabled"].includes(s)) return true;
37268
+ return null;
37269
+ }
37270
+ function autoUpdateEnabled() {
37271
+ const env = parseFlag(process.env.MODELSTAT_AUTO_UPDATE);
37272
+ return env ?? storedAutoUpdate();
37273
+ }
37274
+ function autoUpdatePinnedByEnv() {
37275
+ return parseFlag(process.env.MODELSTAT_AUTO_UPDATE) !== null;
37276
+ }
37277
+ function canSelfUpdate() {
37278
+ try {
37279
+ return spawnSync3(NPM, ["--version"], { stdio: "ignore", timeout: 1e4 }).status === 0;
37280
+ } catch {
37281
+ return false;
37282
+ }
37283
+ }
37284
+ function runUpgrade() {
37285
+ if (!canSelfUpdate()) {
37286
+ return { started: false, reason: "npm not found on PATH" };
37287
+ }
37288
+ try {
37289
+ const env = { ...process.env };
37290
+ delete env.npm_config_global;
37291
+ delete env.npm_config_prefix;
37292
+ const child = spawn2(NPM, ["install", "-g", `${PACKAGE}@latest`], {
37293
+ detached: true,
37294
+ stdio: "ignore",
37295
+ env
37296
+ });
37297
+ child.unref();
37298
+ return { started: true };
37299
+ } catch (e) {
37300
+ return { started: false, reason: e.message };
37301
+ }
37302
+ }
37303
+ function maybeAutoUpdate(verdict, target) {
37304
+ if (verdict !== "update_available" && verdict !== "upgrade_required") return null;
37305
+ const key = `${verdict}:${target ?? ""}`;
37306
+ if (handled.has(key)) return null;
37307
+ handled.add(key);
37308
+ const required = verdict === "upgrade_required";
37309
+ if (!autoUpdateEnabled()) {
37310
+ return `${required ? "upgrade required" : "update available"} (latest ${target ?? "?"}); auto-update is off \u2014 run \`modelstat upgrade\` or \`npm i -g modelstat@latest\``;
37311
+ }
37312
+ const r = runUpgrade();
37313
+ return r.started ? `auto-updating to ${target ?? "latest"} \u2014 \`npm i -g modelstat@latest\`; the service will restart on the new version` : `auto-update could not start (${r.reason}); upgrade manually: \`npm i -g modelstat@latest\``;
37314
+ }
37315
+ var NPM, PACKAGE, handled;
37316
+ var init_update = __esm({
37317
+ "src/update.ts"() {
37318
+ "use strict";
37319
+ init_paths();
37320
+ NPM = platform4() === "win32" ? "npm.cmd" : "npm";
37321
+ PACKAGE = "modelstat";
37322
+ handled = /* @__PURE__ */ new Set();
37323
+ }
37324
+ });
37325
+
37242
37326
  // src/reconcile.ts
37243
37327
  import { stat as stat3 } from "fs/promises";
37244
37328
  function utcDay(ts) {
@@ -39418,6 +39502,10 @@ function noteEventAt(iso) {
39418
39502
  status.lastEventAt = iso;
39419
39503
  scheduleLocalFlush();
39420
39504
  }
39505
+ function setUpdate(u) {
39506
+ status.update = u;
39507
+ scheduleLocalFlush();
39508
+ }
39421
39509
  function snapshotBody() {
39422
39510
  return {
39423
39511
  device_id: state.deviceId ?? null,
@@ -39429,7 +39517,9 @@ function snapshotBody() {
39429
39517
  stats: status.stats,
39430
39518
  last_event_at: status.lastEventAt,
39431
39519
  daemon_version: DAEMON_VERSION2,
39432
- machine_id: machineKey()
39520
+ machine_id: machineKey(),
39521
+ update: status.update,
39522
+ auto_update: autoUpdateEnabled()
39433
39523
  };
39434
39524
  }
39435
39525
  function scheduleLocalFlush() {
@@ -39447,6 +39537,26 @@ function scheduleLocalFlush() {
39447
39537
  }, LOCAL_FLUSH_THROTTLE_MS);
39448
39538
  localFlushTimer.unref();
39449
39539
  }
39540
+ function handleRelease(rel) {
39541
+ const verdict = rel?.verdict ?? "ok";
39542
+ const latest = rel?.latest ?? null;
39543
+ if (verdict === "ok") {
39544
+ if (status.update) setUpdate(null);
39545
+ lastVerdict = "ok";
39546
+ return;
39547
+ }
39548
+ setUpdate({ verdict, latest });
39549
+ if (verdict !== lastVerdict) {
39550
+ console.log(
39551
+ `[modelstat] release ${verdict}: this daemon ${DAEMON_VERSION2}, latest ${latest ?? "?"}`
39552
+ );
39553
+ }
39554
+ lastVerdict = verdict;
39555
+ const note = maybeAutoUpdate(verdict, latest);
39556
+ if (note) {
39557
+ console.log(`[modelstat] ${note}`);
39558
+ }
39559
+ }
39450
39560
  async function sendHeartbeat() {
39451
39561
  const bearer = state.bearer;
39452
39562
  const deviceId = state.deviceId;
@@ -39460,6 +39570,12 @@ async function sendHeartbeat() {
39460
39570
  });
39461
39571
  if (res.statusCode >= 300) {
39462
39572
  await res.body.text();
39573
+ } else {
39574
+ try {
39575
+ const data = await res.body.json();
39576
+ handleRelease(data?.daemon_release);
39577
+ } catch {
39578
+ }
39463
39579
  }
39464
39580
  } catch {
39465
39581
  }
@@ -39639,7 +39755,7 @@ async function runDaemon(opts = {}) {
39639
39755
  setMessage(`policy refresh unavailable: ${err.message}`);
39640
39756
  }
39641
39757
  const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
39642
- const { homedir: homedir9, platform: platform6 } = await import("os");
39758
+ const { homedir: homedir9, platform: platform7 } = await import("os");
39643
39759
  const { join: join14 } = await import("path");
39644
39760
  const home2 = homedir9();
39645
39761
  const dirs = [
@@ -39647,7 +39763,7 @@ async function runDaemon(opts = {}) {
39647
39763
  join14(home2, ".codex/sessions"),
39648
39764
  join14(home2, ".cursor/ai-tracking"),
39649
39765
  join14(home2, ".gemini"),
39650
- ...platform6() === "darwin" ? [
39766
+ ...platform7() === "darwin" ? [
39651
39767
  join14(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
39652
39768
  join14(home2, "Library/Application Support/Claude")
39653
39769
  ] : [join14(home2, ".config/Cursor/User/workspaceStorage")]
@@ -39701,7 +39817,7 @@ async function runDaemon(opts = {}) {
39701
39817
  await new Promise(() => {
39702
39818
  });
39703
39819
  }
39704
- var import_undici2, DAEMON_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, DISCOVERY_INTERVAL_MS, status, LOCAL_FLUSH_THROTTLE_MS, localFlushTimer, localFlushPending, LOG_MAX_BYTES, LOG_TAIL_KEEP_BYTES, lastStatusPath, scanRunner;
39820
+ var import_undici2, DAEMON_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, DISCOVERY_INTERVAL_MS, status, LOCAL_FLUSH_THROTTLE_MS, localFlushTimer, localFlushPending, lastVerdict, LOG_MAX_BYTES, LOG_TAIL_KEEP_BYTES, lastStatusPath, scanRunner;
39705
39821
  var init_daemon = __esm({
39706
39822
  "src/daemon.ts"() {
39707
39823
  "use strict";
@@ -39715,7 +39831,8 @@ var init_daemon = __esm({
39715
39831
  init_reconcile();
39716
39832
  init_scan();
39717
39833
  init_single_flight();
39718
- DAEMON_VERSION2 = true ? "daemon-0.5.1" : "daemon-dev";
39834
+ init_update();
39835
+ DAEMON_VERSION2 = true ? "daemon-0.7.0" : "daemon-dev";
39719
39836
  HEARTBEAT_INTERVAL_MS = 1e4;
39720
39837
  SCAN_INTERVAL_MS = 5 * 60 * 1e3;
39721
39838
  DISCOVERY_INTERVAL_MS = 6e4;
@@ -39726,11 +39843,13 @@ var init_daemon = __esm({
39726
39843
  progressTotal: 0,
39727
39844
  queueSize: 0,
39728
39845
  stats: {},
39729
- lastEventAt: null
39846
+ lastEventAt: null,
39847
+ update: null
39730
39848
  };
39731
39849
  LOCAL_FLUSH_THROTTLE_MS = 400;
39732
39850
  localFlushTimer = null;
39733
39851
  localFlushPending = false;
39852
+ lastVerdict = "ok";
39734
39853
  LOG_MAX_BYTES = 64 * 1024 * 1024;
39735
39854
  LOG_TAIL_KEEP_BYTES = 4 * 1024 * 1024;
39736
39855
  lastStatusPath = null;
@@ -39744,7 +39863,7 @@ __export(watch_exports, {
39744
39863
  watchForever: () => watchForever
39745
39864
  });
39746
39865
  import { existsSync as existsSync12 } from "fs";
39747
- import { homedir as homedir8, platform as platform4 } from "os";
39866
+ import { homedir as homedir8, platform as platform5 } from "os";
39748
39867
  import { join as join13 } from "path";
39749
39868
  function resolveWatchDirs() {
39750
39869
  const home2 = homedir8();
@@ -39765,7 +39884,7 @@ function resolveWatchDirs() {
39765
39884
  join13(xdgConfig, "Code - Insiders/User/workspaceStorage"),
39766
39885
  join13(xdgData, "claude/projects"),
39767
39886
  // macOS
39768
- ...platform4() === "darwin" ? [
39887
+ ...platform5() === "darwin" ? [
39769
39888
  join13(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
39770
39889
  join13(home2, "Library/Application Support/Claude"),
39771
39890
  join13(home2, "Library/Application Support/Code/User/workspaceStorage"),
@@ -39840,8 +39959,8 @@ init_config2();
39840
39959
  init_identity();
39841
39960
  init_machine_key();
39842
39961
  init_scan();
39843
- import { spawn as spawn2 } from "child_process";
39844
- import { arch as cpuArch, hostname as hostname2, platform as platform5, release } from "os";
39962
+ import { spawn as spawn3 } from "child_process";
39963
+ import { arch as cpuArch, hostname as hostname2, platform as platform6, release } from "os";
39845
39964
  import { createInterface as createInterface3 } from "readline";
39846
39965
 
39847
39966
  // src/service.ts
@@ -40287,6 +40406,7 @@ function ageMs(iso, now) {
40287
40406
  }
40288
40407
 
40289
40408
  // src/cli.ts
40409
+ init_update();
40290
40410
  async function confirmPrompt(question, defaultYes) {
40291
40411
  if (process.stdin.isTTY !== true) return defaultYes;
40292
40412
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
@@ -40302,11 +40422,11 @@ async function confirmPrompt(question, defaultYes) {
40302
40422
  }
40303
40423
  }
40304
40424
  function tryOpenBrowser(url) {
40305
- const p = platform5();
40425
+ const p = platform6();
40306
40426
  const cmd = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
40307
40427
  const args = p === "win32" ? ["/c", "start", "", url] : [url];
40308
40428
  try {
40309
- const child = spawn2(cmd, args, {
40429
+ const child = spawn3(cmd, args, {
40310
40430
  stdio: "ignore",
40311
40431
  detached: true
40312
40432
  });
@@ -40316,9 +40436,9 @@ function tryOpenBrowser(url) {
40316
40436
  return false;
40317
40437
  }
40318
40438
  }
40319
- var DAEMON_VERSION3 = true ? "daemon-0.5.1" : "daemon-dev";
40439
+ var DAEMON_VERSION3 = true ? "daemon-0.7.0" : "daemon-dev";
40320
40440
  function osFamily() {
40321
- const p = platform5();
40441
+ const p = platform6();
40322
40442
  if (p === "darwin") return "macos";
40323
40443
  if (p === "linux") return "linux";
40324
40444
  return "other";
@@ -40542,7 +40662,7 @@ async function cmdConnect(opts) {
40542
40662
  claim_url: claimUrl,
40543
40663
  agent_url: agentUrl
40544
40664
  });
40545
- if (platform5() === "darwin") {
40665
+ if (platform6() === "darwin") {
40546
40666
  step("Installing menu-bar tray (macOS)");
40547
40667
  const buildUi = createTrayBuildUi(opts);
40548
40668
  try {
@@ -40593,7 +40713,7 @@ async function cmdConnect(opts) {
40593
40713
  logs: svc.logs,
40594
40714
  summariser_ready: modelReady
40595
40715
  });
40596
- ok(`${platform5() === "darwin" ? "launchd" : "systemd --user"}: ${svc.path}`);
40716
+ ok(`${platform6() === "darwin" ? "launchd" : "systemd --user"}: ${svc.path}`);
40597
40717
  } catch (e) {
40598
40718
  emitEvent(opts, "service_install_failed", { error: e.message });
40599
40719
  warn(`couldn't install service: ${e.message}`);
@@ -40636,7 +40756,7 @@ async function cmdConnect(opts) {
40636
40756
  ` detected: \x1B[32m${discovered.installations} installs \xB7 ${discovered.identities} accounts\x1B[0m`
40637
40757
  );
40638
40758
  }
40639
- if (platform5() === "darwin") {
40759
+ if (platform6() === "darwin") {
40640
40760
  console.log(
40641
40761
  ` tray : \x1B[${tray.installed ? "32" : "2"}m${tray.installed ? "menu-bar icon ready" : "not installed"}\x1B[0m`
40642
40762
  );
@@ -40755,6 +40875,44 @@ async function cmdStatus() {
40755
40875
  console.log(`logs: ${logsDir()}`);
40756
40876
  console.log(`state: ${state.storePath}`);
40757
40877
  console.log(`api: ${state.apiUrl}`);
40878
+ console.log(
40879
+ `auto-update: ${autoUpdateEnabled() ? "on" : "off"}${autoUpdatePinnedByEnv() ? " (pinned by env)" : ""}`
40880
+ );
40881
+ const ls = await readLocalStatus();
40882
+ const upd = ls?.update;
40883
+ if (upd?.verdict && upd.verdict !== "ok") {
40884
+ const what = upd.verdict === "upgrade_required" ? "REQUIRED" : "available";
40885
+ console.log(`update: ${what} \u2014 latest ${upd.latest ?? "?"} (run \`modelstat upgrade\`)`);
40886
+ }
40887
+ }
40888
+ function cmdAutoUpdate(args) {
40889
+ const sub = (args[0] ?? "").toLowerCase();
40890
+ if (sub === "on" || sub === "enable") {
40891
+ setStoredAutoUpdate(true);
40892
+ } else if (sub === "off" || sub === "disable") {
40893
+ setStoredAutoUpdate(false);
40894
+ } else if (sub === "toggle") {
40895
+ setStoredAutoUpdate(!storedAutoUpdate());
40896
+ } else if (sub !== "" && sub !== "status") {
40897
+ console.log("usage: modelstat autoupdate [on|off|toggle|status]");
40898
+ return;
40899
+ }
40900
+ console.log(`auto-update: ${autoUpdateEnabled() ? "on" : "off"}`);
40901
+ if (autoUpdatePinnedByEnv()) {
40902
+ console.log(" (pinned by MODELSTAT_AUTO_UPDATE env \u2014 the stored toggle is ignored)");
40903
+ }
40904
+ }
40905
+ function cmdUpgrade() {
40906
+ console.log("upgrading modelstat to the latest published version\u2026");
40907
+ const r = runUpgrade();
40908
+ if (r.started) {
40909
+ console.log(
40910
+ " started `npm install -g modelstat@latest` \u2014 the service restarts on the new build."
40911
+ );
40912
+ } else {
40913
+ console.log(` couldn't start the upgrade: ${r.reason}`);
40914
+ console.log(" upgrade manually: npm install -g modelstat@latest");
40915
+ }
40758
40916
  }
40759
40917
  function fmtInt(n) {
40760
40918
  return Number(n).toLocaleString("en-US");
@@ -40994,6 +41152,12 @@ async function main() {
40994
41152
  return cmdSelfRegister();
40995
41153
  case "await-claim":
40996
41154
  return cmdAwaitClaim();
41155
+ case "upgrade":
41156
+ cmdUpgrade();
41157
+ return;
41158
+ case "autoupdate":
41159
+ cmdAutoUpdate(rest);
41160
+ return;
40997
41161
  default:
40998
41162
  console.log("usage:");
40999
41163
  console.log(
@@ -41011,6 +41175,10 @@ async function main() {
41011
41175
  console.log();
41012
41176
  console.log("Diagnostics:");
41013
41177
  console.log(" npx modelstat@latest status \u2014 show pairing + service state");
41178
+ console.log(" npx modelstat@latest upgrade \u2014 update to the latest version now");
41179
+ console.log(
41180
+ " npx modelstat@latest autoupdate \u2014 show/set auto-update: on|off|toggle"
41181
+ );
41014
41182
  console.log(
41015
41183
  " npx modelstat@latest stats \u2014 live device summary: sessions \xB7 tokens \xB7 cost (--json)"
41016
41184
  );