modelstat 0.0.21 → 0.0.23

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
@@ -4341,7 +4341,14 @@ var init_schemas = __esm({
4341
4341
  files_touched: external_exports.array(external_exports.string().max(512)).max(256).default([]),
4342
4342
  // Reference to originating file for reparsing
4343
4343
  source_file: external_exports.string().max(1024).nullable(),
4344
- source_byte_offset: external_exports.number().int().nonnegative().nullable()
4344
+ source_byte_offset: external_exports.number().int().nonnegative().nullable(),
4345
+ // Billing mode. Tools with a flat-fee subscription tier (Claude
4346
+ // Code, Cursor Pro, GitHub Copilot, etc) emit events tagged
4347
+ // `billing: "subscription"` — the server short-circuits cost to $0
4348
+ // for those, since token-level pricing doesn't apply once the user
4349
+ // is paying the subscription. `api` (or absent) means pay-per-token
4350
+ // against whatever rates the org has configured.
4351
+ billing: external_exports.enum(["subscription", "api"]).optional()
4345
4352
  });
4346
4353
  RedactionReport = external_exports.object({
4347
4354
  secrets_found: external_exports.number().int().nonnegative().default(0),
@@ -4723,7 +4730,12 @@ async function parseClaudeCodeJsonl(ctx) {
4723
4730
  tool_calls: {},
4724
4731
  files_touched: [],
4725
4732
  source_file: ctx.sourceFile,
4726
- source_byte_offset: offsetAtLineStart
4733
+ source_byte_offset: offsetAtLineStart,
4734
+ // Files in ~/.claude/projects/ come from the Claude Code app
4735
+ // used via subscription (not the raw API). Mark them so the
4736
+ // server short-circuits token-level cost to $0 — the user has
4737
+ // already paid the flat monthly fee.
4738
+ billing: "subscription"
4727
4739
  });
4728
4740
  } else if (obj.type === "user") {
4729
4741
  const u = obj;
@@ -4748,7 +4760,8 @@ async function parseClaudeCodeJsonl(ctx) {
4748
4760
  tool_calls: {},
4749
4761
  files_touched: [],
4750
4762
  source_file: ctx.sourceFile,
4751
- source_byte_offset: offsetAtLineStart
4763
+ source_byte_offset: offsetAtLineStart,
4764
+ billing: "subscription"
4752
4765
  });
4753
4766
  } else {
4754
4767
  skipped += 1;
@@ -7474,6 +7487,107 @@ var init_src2 = __esm({
7474
7487
  }
7475
7488
  });
7476
7489
 
7490
+ // src/identity.ts
7491
+ import {
7492
+ chmodSync,
7493
+ mkdirSync,
7494
+ readFileSync as readFileSync2,
7495
+ renameSync,
7496
+ writeFileSync,
7497
+ existsSync as existsSync3
7498
+ } from "fs";
7499
+ import { homedir as homedir2, hostname as osHostname } from "os";
7500
+ import { join as join2 } from "path";
7501
+ function ensureRoot() {
7502
+ mkdirSync(ROOT, { recursive: true, mode: 448 });
7503
+ }
7504
+ function writeAtomic(meta) {
7505
+ ensureRoot();
7506
+ const tmp = `${IDENTITY_FILE}.${process.pid}.tmp`;
7507
+ writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
7508
+ renameSync(tmp, IDENTITY_FILE);
7509
+ try {
7510
+ chmodSync(IDENTITY_FILE, 384);
7511
+ } catch {
7512
+ }
7513
+ }
7514
+ function identityPath() {
7515
+ return IDENTITY_FILE;
7516
+ }
7517
+ function hasIdentityFile() {
7518
+ return existsSync3(IDENTITY_FILE);
7519
+ }
7520
+ function parseFile() {
7521
+ try {
7522
+ const raw = readFileSync2(IDENTITY_FILE, "utf8");
7523
+ const obj = JSON.parse(raw);
7524
+ if (!obj.deviceUuid || !obj.deviceId || !obj.bearerToken) {
7525
+ return null;
7526
+ }
7527
+ return {
7528
+ deviceUuid: obj.deviceUuid,
7529
+ deviceId: obj.deviceId,
7530
+ bearerToken: obj.bearerToken,
7531
+ claimCode: obj.claimCode ?? null,
7532
+ claimUrl: obj.claimUrl ?? null,
7533
+ hostname: obj.hostname ?? osHostname(),
7534
+ createdAt: obj.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
7535
+ userEmail: obj.userEmail ?? null,
7536
+ defaultOrgId: obj.defaultOrgId ?? null
7537
+ };
7538
+ } catch {
7539
+ return null;
7540
+ }
7541
+ }
7542
+ function loadIdentity(migrateFromConf2) {
7543
+ const fromFile = parseFile();
7544
+ if (fromFile) return fromFile;
7545
+ if (!migrateFromConf2) return null;
7546
+ const legacy = migrateFromConf2();
7547
+ if (!legacy) return null;
7548
+ if (!legacy.deviceUuid || !legacy.deviceId || !legacy.bearerToken) {
7549
+ return null;
7550
+ }
7551
+ const migrated = {
7552
+ deviceUuid: legacy.deviceUuid,
7553
+ deviceId: legacy.deviceId,
7554
+ bearerToken: legacy.bearerToken,
7555
+ claimCode: legacy.claimCode ?? null,
7556
+ claimUrl: legacy.claimUrl ?? null,
7557
+ hostname: osHostname(),
7558
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7559
+ userEmail: legacy.userEmail ?? null,
7560
+ defaultOrgId: legacy.defaultOrgId ?? null
7561
+ };
7562
+ writeAtomic(migrated);
7563
+ return migrated;
7564
+ }
7565
+ function saveIdentity(meta) {
7566
+ writeAtomic(meta);
7567
+ }
7568
+ function backupIdentity() {
7569
+ if (!existsSync3(IDENTITY_FILE)) return null;
7570
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7571
+ const dest = `${IDENTITY_FILE}.bak-${stamp}`;
7572
+ renameSync(IDENTITY_FILE, dest);
7573
+ return dest;
7574
+ }
7575
+ function updateIdentity(patch) {
7576
+ const current = parseFile();
7577
+ if (!current) return null;
7578
+ const merged = { ...current, ...patch };
7579
+ writeAtomic(merged);
7580
+ return merged;
7581
+ }
7582
+ var ROOT, IDENTITY_FILE;
7583
+ var init_identity = __esm({
7584
+ "src/identity.ts"() {
7585
+ "use strict";
7586
+ ROOT = join2(homedir2(), ".modelstat");
7587
+ IDENTITY_FILE = join2(ROOT, "identity.json");
7588
+ }
7589
+ });
7590
+
7477
7591
  // ../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/core/symbols.js
7478
7592
  var require_symbols = __commonJS({
7479
7593
  "../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/core/symbols.js"(exports, module) {
@@ -33048,15 +33162,15 @@ function envPaths(name, { suffix = "nodejs" } = {}) {
33048
33162
  }
33049
33163
  return linux(name);
33050
33164
  }
33051
- var homedir2, tmpdir, env, macos, windows, linux;
33165
+ var homedir3, tmpdir, env, macos, windows, linux;
33052
33166
  var init_env_paths = __esm({
33053
33167
  "../../node_modules/.pnpm/env-paths@3.0.0/node_modules/env-paths/index.js"() {
33054
33168
  "use strict";
33055
- homedir2 = os.homedir();
33169
+ homedir3 = os.homedir();
33056
33170
  tmpdir = os.tmpdir();
33057
33171
  ({ env } = process2);
33058
33172
  macos = (name) => {
33059
- const library = path.join(homedir2, "Library");
33173
+ const library = path.join(homedir3, "Library");
33060
33174
  return {
33061
33175
  data: path.join(library, "Application Support", name),
33062
33176
  config: path.join(library, "Preferences", name),
@@ -33066,8 +33180,8 @@ var init_env_paths = __esm({
33066
33180
  };
33067
33181
  };
33068
33182
  windows = (name) => {
33069
- const appData = env.APPDATA || path.join(homedir2, "AppData", "Roaming");
33070
- const localAppData = env.LOCALAPPDATA || path.join(homedir2, "AppData", "Local");
33183
+ const appData = env.APPDATA || path.join(homedir3, "AppData", "Roaming");
33184
+ const localAppData = env.LOCALAPPDATA || path.join(homedir3, "AppData", "Local");
33071
33185
  return {
33072
33186
  // Data/config/cache/log are invented by me as Windows isn't opinionated about this
33073
33187
  data: path.join(localAppData, name, "Data"),
@@ -33078,13 +33192,13 @@ var init_env_paths = __esm({
33078
33192
  };
33079
33193
  };
33080
33194
  linux = (name) => {
33081
- const username = path.basename(homedir2);
33195
+ const username = path.basename(homedir3);
33082
33196
  return {
33083
- data: path.join(env.XDG_DATA_HOME || path.join(homedir2, ".local", "share"), name),
33084
- config: path.join(env.XDG_CONFIG_HOME || path.join(homedir2, ".config"), name),
33085
- cache: path.join(env.XDG_CACHE_HOME || path.join(homedir2, ".cache"), name),
33197
+ data: path.join(env.XDG_DATA_HOME || path.join(homedir3, ".local", "share"), name),
33198
+ config: path.join(env.XDG_CONFIG_HOME || path.join(homedir3, ".config"), name),
33199
+ cache: path.join(env.XDG_CACHE_HOME || path.join(homedir3, ".cache"), name),
33086
33200
  // https://wiki.debian.org/XDGBaseDirectorySpecification#state
33087
- log: path.join(env.XDG_STATE_HOME || path.join(homedir2, ".local", "state"), name),
33201
+ log: path.join(env.XDG_STATE_HOME || path.join(homedir3, ".local", "state"), name),
33088
33202
  temp: path.join(tmpdir, username, name)
33089
33203
  };
33090
33204
  };
@@ -33525,9 +33639,9 @@ import { once } from "events";
33525
33639
  import { createWriteStream } from "fs";
33526
33640
  import path3 from "path";
33527
33641
  import { Readable } from "stream";
33528
- function writeFileSync(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
33642
+ function writeFileSync2(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
33529
33643
  if (isString(options))
33530
- return writeFileSync(filePath, data, { encoding: options });
33644
+ return writeFileSync2(filePath, data, { encoding: options });
33531
33645
  const timeout = options.timeout ?? DEFAULT_TIMEOUT_SYNC;
33532
33646
  const retryOptions = { timeout };
33533
33647
  let tempDisposer = null;
@@ -43650,7 +43764,7 @@ var init_source = __esm({
43650
43764
  fs2.writeFileSync(this.path, data, { mode: this.#options.configFileMode });
43651
43765
  } else {
43652
43766
  try {
43653
- writeFileSync(this.path, data, { mode: this.#options.configFileMode });
43767
+ writeFileSync2(this.path, data, { mode: this.#options.configFileMode });
43654
43768
  } catch (error) {
43655
43769
  if (error?.code === "EXDEV") {
43656
43770
  fs2.writeFileSync(this.path, data, { mode: this.#options.configFileMode });
@@ -43752,20 +43866,52 @@ var init_source = __esm({
43752
43866
  });
43753
43867
 
43754
43868
  // src/config.ts
43755
- import { existsSync as existsSync3 } from "fs";
43869
+ import { existsSync as existsSync4 } from "fs";
43756
43870
  import { hostname } from "os";
43757
43871
  import { dirname as dirname2, resolve as resolve3 } from "path";
43758
43872
  import { fileURLToPath } from "url";
43759
- var import_dotenv, here, DEFAULT_API_URL, LEGACY_LOCALHOST_API, store, state;
43873
+ function migrateFromConf() {
43874
+ const bearer = store.get("bearerToken");
43875
+ const deviceUuid = store.get("deviceUuid");
43876
+ const deviceId = store.get("deviceId");
43877
+ if (!bearer || !deviceUuid || !deviceId) return null;
43878
+ return {
43879
+ bearerToken: bearer,
43880
+ deviceId,
43881
+ deviceUuid,
43882
+ claimCode: store.get("claimCode"),
43883
+ claimUrl: store.get("claimUrl"),
43884
+ userEmail: store.get("userEmail"),
43885
+ defaultOrgId: store.get("defaultOrgId")
43886
+ };
43887
+ }
43888
+ function clearConfIdentity() {
43889
+ store.set("bearerToken", null);
43890
+ store.set("deviceId", null);
43891
+ store.set("deviceUuid", null);
43892
+ store.set("claimCode", null);
43893
+ store.set("claimUrl", null);
43894
+ }
43895
+ function writeThrough(patch) {
43896
+ if (!cachedIdentity) {
43897
+ throw new Error(
43898
+ "config: no identity yet \u2014 call state.saveFreshIdentity() first"
43899
+ );
43900
+ }
43901
+ cachedIdentity = { ...cachedIdentity, ...patch };
43902
+ updateIdentity(patch);
43903
+ }
43904
+ var import_dotenv, here, DEFAULT_API_URL, LEGACY_LOCALHOST_API, store, cachedIdentity, state;
43760
43905
  var init_config2 = __esm({
43761
43906
  "src/config.ts"() {
43762
43907
  "use strict";
43763
43908
  import_dotenv = __toESM(require_main(), 1);
43764
43909
  init_source();
43910
+ init_identity();
43765
43911
  here = dirname2(fileURLToPath(import.meta.url));
43766
43912
  for (let d = here, i = 0; i < 8; i++, d = resolve3(d, "..")) {
43767
43913
  const candidate = resolve3(d, ".env");
43768
- if (existsSync3(candidate)) {
43914
+ if (existsSync4(candidate)) {
43769
43915
  (0, import_dotenv.config)({ path: candidate });
43770
43916
  break;
43771
43917
  }
@@ -43791,6 +43937,14 @@ var init_config2 = __esm({
43791
43937
  cursor: {}
43792
43938
  }
43793
43939
  });
43940
+ cachedIdentity = (() => {
43941
+ const had = store.get("bearerToken") !== null;
43942
+ const id = loadIdentity(migrateFromConf);
43943
+ if (id && had && !store.path.includes("fallback")) {
43944
+ clearConfIdentity();
43945
+ }
43946
+ return id;
43947
+ })();
43794
43948
  state = {
43795
43949
  /** Resolution order: env var → stored value (if user ran `setApiUrl`
43796
43950
  * or paired pre-0.0.8) → production default. The legacy localhost
@@ -43804,42 +43958,66 @@ var init_config2 = __esm({
43804
43958
  setApiUrl(v) {
43805
43959
  store.set("apiUrl", v);
43806
43960
  },
43961
+ // ── Identity: backed by ~/.modelstat/identity.json ─────────────
43962
+ /** Seed a fresh identity after a successful self-register. Writes
43963
+ * the file atomically; use `state.backupAndReset()` first if
43964
+ * overwriting an existing identity. */
43965
+ saveFreshIdentity(meta) {
43966
+ const id = {
43967
+ deviceUuid: meta.deviceUuid,
43968
+ deviceId: meta.deviceId,
43969
+ bearerToken: meta.bearerToken,
43970
+ claimCode: meta.claimCode,
43971
+ claimUrl: meta.claimUrl,
43972
+ hostname: hostname(),
43973
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
43974
+ userEmail: null,
43975
+ defaultOrgId: null
43976
+ };
43977
+ saveIdentity(id);
43978
+ cachedIdentity = id;
43979
+ },
43980
+ get identity() {
43981
+ return cachedIdentity;
43982
+ },
43807
43983
  get bearer() {
43808
- return store.get("bearerToken");
43984
+ return cachedIdentity?.bearerToken ?? null;
43809
43985
  },
43810
43986
  setBearer(v) {
43811
- store.set("bearerToken", v);
43987
+ if (v === null) {
43988
+ cachedIdentity = null;
43989
+ return;
43990
+ }
43991
+ writeThrough({ bearerToken: v });
43812
43992
  },
43813
43993
  get deviceId() {
43814
- return store.get("deviceId");
43815
- },
43816
- setDeviceId(v) {
43817
- store.set("deviceId", v);
43818
- },
43819
- get userEmail() {
43820
- return store.get("userEmail");
43821
- },
43822
- setUserEmail(v) {
43823
- store.set("userEmail", v);
43994
+ return cachedIdentity?.deviceId ?? null;
43824
43995
  },
43825
43996
  get deviceUuid() {
43826
- return store.get("deviceUuid");
43827
- },
43828
- setDeviceUuid(v) {
43829
- store.set("deviceUuid", v);
43997
+ return cachedIdentity?.deviceUuid ?? null;
43830
43998
  },
43831
43999
  get claimCode() {
43832
- return store.get("claimCode");
44000
+ return cachedIdentity?.claimCode ?? null;
43833
44001
  },
43834
44002
  setClaimCode(v) {
43835
- store.set("claimCode", v);
44003
+ if (!cachedIdentity) return;
44004
+ writeThrough({ claimCode: v });
43836
44005
  },
43837
44006
  get claimUrl() {
43838
- return store.get("claimUrl");
44007
+ return cachedIdentity?.claimUrl ?? null;
43839
44008
  },
43840
44009
  setClaimUrl(v) {
43841
- store.set("claimUrl", v);
44010
+ if (!cachedIdentity) return;
44011
+ writeThrough({ claimUrl: v });
44012
+ },
44013
+ get userEmail() {
44014
+ return cachedIdentity?.userEmail ?? null;
43842
44015
  },
44016
+ setUserEmail(v) {
44017
+ if (!cachedIdentity) return;
44018
+ writeThrough({ userEmail: v });
44019
+ },
44020
+ // ── Runtime state: stays in conf ───────────────────────────────
43843
44021
  getCursor(path5) {
43844
44022
  return store.get("cursor")[path5];
43845
44023
  },
@@ -44389,23 +44567,23 @@ var init_pipeline2 = __esm({
44389
44567
 
44390
44568
  // src/scan.ts
44391
44569
  import { readdir, stat as stat2 } from "fs/promises";
44392
- import { homedir as homedir3 } from "os";
44393
- import { join as join2 } from "path";
44570
+ import { homedir as homedir4 } from "os";
44571
+ import { join as join3 } from "path";
44394
44572
  async function scanAll(cb = {}) {
44395
44573
  const deviceId = state.deviceId;
44396
44574
  if (!deviceId) throw new Error("agent not enrolled \u2014 run `register` first");
44397
44575
  const jobs = [];
44398
44576
  try {
44399
- const base = join2(homedir3(), ".claude/projects");
44577
+ const base = join3(homedir4(), ".claude/projects");
44400
44578
  const projects = await readdir(base).catch(() => []);
44401
44579
  for (const p of projects) {
44402
- const dir = join2(base, p);
44580
+ const dir = join3(base, p);
44403
44581
  const ds = await stat2(dir).catch(() => null);
44404
44582
  if (!ds?.isDirectory()) continue;
44405
44583
  const files = await readdir(dir);
44406
44584
  for (const f of files) {
44407
44585
  if (!f.endsWith(".jsonl")) continue;
44408
- const full = join2(dir, f);
44586
+ const full = join3(dir, f);
44409
44587
  jobs.push({
44410
44588
  path: full,
44411
44589
  parse: async () => {
@@ -44419,17 +44597,17 @@ async function scanAll(cb = {}) {
44419
44597
  console.warn("claude scan skipped:", e.message);
44420
44598
  }
44421
44599
  try {
44422
- const base = join2(homedir3(), ".codex/sessions");
44600
+ const base = join3(homedir4(), ".codex/sessions");
44423
44601
  const years = await readdir(base).catch(() => []);
44424
44602
  for (const y of years) {
44425
- const months = await readdir(join2(base, y)).catch(() => []);
44603
+ const months = await readdir(join3(base, y)).catch(() => []);
44426
44604
  for (const m of months) {
44427
- const days = await readdir(join2(base, y, m)).catch(() => []);
44605
+ const days = await readdir(join3(base, y, m)).catch(() => []);
44428
44606
  for (const d of days) {
44429
- const files = await readdir(join2(base, y, m, d)).catch(() => []);
44607
+ const files = await readdir(join3(base, y, m, d)).catch(() => []);
44430
44608
  for (const f of files) {
44431
44609
  if (!f.startsWith("rollout-") || !f.endsWith(".jsonl")) continue;
44432
- const full = join2(base, y, m, d, f);
44610
+ const full = join3(base, y, m, d, f);
44433
44611
  jobs.push({
44434
44612
  path: full,
44435
44613
  parse: async () => {
@@ -44503,7 +44681,7 @@ var init_scan = __esm({
44503
44681
  init_pipeline2();
44504
44682
  init_config2();
44505
44683
  init_api();
44506
- AGENT_VERSION = "agent-dev-0.0.21";
44684
+ AGENT_VERSION = "agent-dev-0.0.23";
44507
44685
  BATCH_MAX_EVENTS = 2e3;
44508
44686
  }
44509
44687
  });
@@ -44511,17 +44689,17 @@ var init_scan = __esm({
44511
44689
  // src/lock.ts
44512
44690
  import {
44513
44691
  closeSync,
44514
- existsSync as existsSync5,
44515
- mkdirSync as mkdirSync2,
44692
+ existsSync as existsSync6,
44693
+ mkdirSync as mkdirSync3,
44516
44694
  openSync,
44517
- readFileSync as readFileSync2,
44518
- renameSync,
44695
+ readFileSync as readFileSync3,
44696
+ renameSync as renameSync2,
44519
44697
  unlinkSync as unlinkSync2,
44520
- writeFileSync as writeFileSync3,
44698
+ writeFileSync as writeFileSync4,
44521
44699
  writeSync
44522
44700
  } from "fs";
44523
- import { homedir as homedir5 } from "os";
44524
- import { join as join4 } from "path";
44701
+ import { homedir as homedir6 } from "os";
44702
+ import { join as join5 } from "path";
44525
44703
  function isProcessAlive(pid) {
44526
44704
  if (!pid || pid <= 0) return false;
44527
44705
  try {
@@ -44535,7 +44713,7 @@ function isProcessAlive(pid) {
44535
44713
  }
44536
44714
  function readLock() {
44537
44715
  try {
44538
- const raw = readFileSync2(LOCK_FILE, "utf8");
44716
+ const raw = readFileSync3(LOCK_FILE, "utf8");
44539
44717
  const obj = JSON.parse(raw);
44540
44718
  if (typeof obj.pid !== "number") return null;
44541
44719
  return {
@@ -44549,7 +44727,7 @@ function readLock() {
44549
44727
  }
44550
44728
  }
44551
44729
  function writeLockAtomic(meta) {
44552
- mkdirSync2(LOCK_DIR, { recursive: true });
44730
+ mkdirSync3(LOCK_DIR, { recursive: true });
44553
44731
  const tmp = `${LOCK_FILE}.${meta.pid}.${Date.now()}.tmp`;
44554
44732
  const fd = openSync(tmp, "wx");
44555
44733
  try {
@@ -44557,7 +44735,7 @@ function writeLockAtomic(meta) {
44557
44735
  } finally {
44558
44736
  closeSync(fd);
44559
44737
  }
44560
- renameSync(tmp, LOCK_FILE);
44738
+ renameSync2(tmp, LOCK_FILE);
44561
44739
  }
44562
44740
  function removeLockIfOwned(ownerPid) {
44563
44741
  const lock = readLock();
@@ -44622,8 +44800,8 @@ var LOCK_DIR, LOCK_FILE;
44622
44800
  var init_lock = __esm({
44623
44801
  "src/lock.ts"() {
44624
44802
  "use strict";
44625
- LOCK_DIR = join4(homedir5(), ".modelstat");
44626
- LOCK_FILE = join4(LOCK_DIR, "daemon.lock");
44803
+ LOCK_DIR = join5(homedir6(), ".modelstat");
44804
+ LOCK_FILE = join5(LOCK_DIR, "daemon.lock");
44627
44805
  }
44628
44806
  });
44629
44807
 
@@ -46353,7 +46531,7 @@ __export(daemon_exports, {
46353
46531
  setProgress: () => setProgress,
46354
46532
  setQueue: () => setQueue
46355
46533
  });
46356
- import { existsSync as existsSync6, statSync as statSync2 } from "fs";
46534
+ import { existsSync as existsSync7, statSync as statSync2 } from "fs";
46357
46535
  function setPhase(phase, message) {
46358
46536
  status.phase = phase;
46359
46537
  status.message = message ?? null;
@@ -46475,21 +46653,21 @@ async function runDaemon(opts = {}) {
46475
46653
  await runDiscovery();
46476
46654
  await runScanCycle("startup");
46477
46655
  const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
46478
- const { homedir: homedir7, platform: platform5 } = await import("os");
46479
- const { join: join8 } = await import("path");
46480
- const home2 = homedir7();
46656
+ const { homedir: homedir8, platform: platform5 } = await import("os");
46657
+ const { join: join9 } = await import("path");
46658
+ const home2 = homedir8();
46481
46659
  const dirs = [
46482
- join8(home2, ".claude/projects"),
46483
- join8(home2, ".codex/sessions"),
46484
- join8(home2, ".cursor/ai-tracking"),
46485
- join8(home2, ".gemini"),
46660
+ join9(home2, ".claude/projects"),
46661
+ join9(home2, ".codex/sessions"),
46662
+ join9(home2, ".cursor/ai-tracking"),
46663
+ join9(home2, ".gemini"),
46486
46664
  ...platform5() === "darwin" ? [
46487
- join8(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
46488
- join8(home2, "Library/Application Support/Claude")
46665
+ join9(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
46666
+ join9(home2, "Library/Application Support/Claude")
46489
46667
  ] : [
46490
- join8(home2, ".config/Cursor/User/workspaceStorage")
46668
+ join9(home2, ".config/Cursor/User/workspaceStorage")
46491
46669
  ]
46492
- ].filter((p) => existsSync6(p) && statSync2(p).isDirectory());
46670
+ ].filter((p) => existsSync7(p) && statSync2(p).isDirectory());
46493
46671
  setPhase("watching", `Watching ${dirs.length} directories`);
46494
46672
  const watcher = chokidar.watch(dirs, {
46495
46673
  persistent: true,
@@ -46536,7 +46714,7 @@ var init_daemon = __esm({
46536
46714
  init_config2();
46537
46715
  init_lock();
46538
46716
  init_scan();
46539
- AGENT_VERSION2 = "agent-dev-0.0.21";
46717
+ AGENT_VERSION2 = "agent-dev-0.0.23";
46540
46718
  HEARTBEAT_INTERVAL_MS = 1e4;
46541
46719
  SCAN_INTERVAL_MS = 5 * 60 * 1e3;
46542
46720
  status = {
@@ -46556,37 +46734,37 @@ var watch_exports = {};
46556
46734
  __export(watch_exports, {
46557
46735
  watchForever: () => watchForever
46558
46736
  });
46559
- import { existsSync as existsSync7 } from "fs";
46560
- import { homedir as homedir6, platform as platform3 } from "os";
46561
- import { join as join7 } from "path";
46737
+ import { existsSync as existsSync8 } from "fs";
46738
+ import { homedir as homedir7, platform as platform3 } from "os";
46739
+ import { join as join8 } from "path";
46562
46740
  function resolveWatchDirs() {
46563
- const home2 = homedir6();
46564
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join7(home2, ".config");
46565
- const xdgData = process.env.XDG_DATA_HOME ?? join7(home2, ".local/share");
46741
+ const home2 = homedir7();
46742
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join8(home2, ".config");
46743
+ const xdgData = process.env.XDG_DATA_HOME ?? join8(home2, ".local/share");
46566
46744
  const candidates = [
46567
46745
  // universal (default HOME-rooted CLI data dirs)
46568
- join7(home2, ".claude/projects"),
46569
- join7(home2, ".codex/sessions"),
46570
- join7(home2, ".cursor/ai-tracking"),
46571
- join7(home2, ".gemini"),
46572
- join7(home2, ".aider"),
46746
+ join8(home2, ".claude/projects"),
46747
+ join8(home2, ".codex/sessions"),
46748
+ join8(home2, ".cursor/ai-tracking"),
46749
+ join8(home2, ".gemini"),
46750
+ join8(home2, ".aider"),
46573
46751
  // XDG / Linux
46574
- join7(xdgConfig, "claude/projects"),
46575
- join7(xdgConfig, "codex/sessions"),
46576
- join7(xdgConfig, "Cursor/User/workspaceStorage"),
46577
- join7(xdgConfig, "Code/User/workspaceStorage"),
46578
- join7(xdgConfig, "Code - Insiders/User/workspaceStorage"),
46579
- join7(xdgData, "claude/projects"),
46752
+ join8(xdgConfig, "claude/projects"),
46753
+ join8(xdgConfig, "codex/sessions"),
46754
+ join8(xdgConfig, "Cursor/User/workspaceStorage"),
46755
+ join8(xdgConfig, "Code/User/workspaceStorage"),
46756
+ join8(xdgConfig, "Code - Insiders/User/workspaceStorage"),
46757
+ join8(xdgData, "claude/projects"),
46580
46758
  // macOS
46581
46759
  ...platform3() === "darwin" ? [
46582
- join7(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
46583
- join7(home2, "Library/Application Support/Claude"),
46584
- join7(home2, "Library/Application Support/Code/User/workspaceStorage"),
46585
- join7(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
46586
- join7(home2, "Library/Application Support/Zed")
46760
+ join8(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
46761
+ join8(home2, "Library/Application Support/Claude"),
46762
+ join8(home2, "Library/Application Support/Code/User/workspaceStorage"),
46763
+ join8(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
46764
+ join8(home2, "Library/Application Support/Zed")
46587
46765
  ] : []
46588
46766
  ];
46589
- return Array.from(new Set(candidates)).filter((p) => existsSync7(p));
46767
+ return Array.from(new Set(candidates)).filter((p) => existsSync8(p));
46590
46768
  }
46591
46769
  async function safeScan(reason) {
46592
46770
  if (scanning) {
@@ -46646,51 +46824,53 @@ var init_watch = __esm({
46646
46824
 
46647
46825
  // src/cli.ts
46648
46826
  init_src2();
46827
+ init_identity();
46649
46828
  init_api();
46650
46829
  init_config2();
46651
46830
  init_scan();
46652
46831
  import { spawn } from "child_process";
46653
46832
  import { randomBytes } from "crypto";
46654
46833
  import { platform as platform4, release, arch as cpuArch, hostname as hostname2 } from "os";
46834
+ import { createInterface as createInterface3 } from "readline";
46655
46835
 
46656
46836
  // src/service.ts
46657
46837
  import { spawnSync } from "child_process";
46658
46838
  import {
46659
46839
  copyFileSync,
46660
- existsSync as existsSync4,
46661
- mkdirSync,
46840
+ existsSync as existsSync5,
46841
+ mkdirSync as mkdirSync2,
46662
46842
  unlinkSync,
46663
- writeFileSync as writeFileSync2
46843
+ writeFileSync as writeFileSync3
46664
46844
  } from "fs";
46665
- import { homedir as homedir4, platform as platform2, userInfo } from "os";
46666
- import { dirname as dirname4, join as join3 } from "path";
46845
+ import { homedir as homedir5, platform as platform2, userInfo } from "os";
46846
+ import { dirname as dirname4, join as join4 } from "path";
46667
46847
  import { fileURLToPath as fileURLToPath2 } from "url";
46668
46848
  var SERVICE_LABEL = "ai.modelstat.agent";
46669
46849
  var SYSTEMD_UNIT = "modelstat";
46670
46850
  function home() {
46671
- return homedir4();
46851
+ return homedir5();
46672
46852
  }
46673
46853
  function stateDir() {
46674
- return join3(home(), ".modelstat");
46854
+ return join4(home(), ".modelstat");
46675
46855
  }
46676
46856
  function binDir() {
46677
- return join3(stateDir(), "bin");
46857
+ return join4(stateDir(), "bin");
46678
46858
  }
46679
46859
  function logDir() {
46680
- return join3(stateDir(), "logs");
46860
+ return join4(stateDir(), "logs");
46681
46861
  }
46682
46862
  function installedCliPath() {
46683
- return join3(binDir(), "modelstat.mjs");
46863
+ return join4(binDir(), "modelstat.mjs");
46684
46864
  }
46685
46865
  function runningCliPath() {
46686
46866
  return fileURLToPath2(import.meta.url).replace(/service\.(mjs|js|ts)$/, "cli.mjs");
46687
46867
  }
46688
46868
  function installBundle() {
46689
- mkdirSync(binDir(), { recursive: true });
46690
- mkdirSync(logDir(), { recursive: true });
46869
+ mkdirSync2(binDir(), { recursive: true });
46870
+ mkdirSync2(logDir(), { recursive: true });
46691
46871
  const src = runningCliPath();
46692
46872
  const dest = installedCliPath();
46693
- if (!existsSync4(src)) {
46873
+ if (!existsSync5(src)) {
46694
46874
  throw new Error(
46695
46875
  `Can't find the CLI bundle to install from (${src}). Are you running a local dev build?`
46696
46876
  );
@@ -46702,21 +46882,21 @@ function nodeBinary() {
46702
46882
  return process.execPath;
46703
46883
  }
46704
46884
  function plistPath() {
46705
- return join3(home(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
46885
+ return join4(home(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
46706
46886
  }
46707
46887
  function locateTrayExecutable() {
46708
46888
  const candidates = [
46709
- join3(home(), "Applications", "ModelstatTray.app", "Contents", "MacOS", "modelstat-tray"),
46889
+ join4(home(), "Applications", "ModelstatTray.app", "Contents", "MacOS", "modelstat-tray"),
46710
46890
  "/Applications/ModelstatTray.app/Contents/MacOS/modelstat-tray"
46711
46891
  ];
46712
46892
  for (const p of candidates) {
46713
- if (existsSync4(p)) return p;
46893
+ if (existsSync5(p)) return p;
46714
46894
  }
46715
46895
  return null;
46716
46896
  }
46717
46897
  function writePlist(cliPath) {
46718
46898
  const p = plistPath();
46719
- mkdirSync(dirname4(p), { recursive: true });
46899
+ mkdirSync2(dirname4(p), { recursive: true });
46720
46900
  const tray = locateTrayExecutable();
46721
46901
  const programArgs = tray ? ` <string>${tray}</string>` : [
46722
46902
  ` <string>${nodeBinary()}</string>`,
@@ -46736,8 +46916,8 @@ ${programArgs}
46736
46916
  <key>KeepAlive</key>
46737
46917
  <dict><key>SuccessfulExit</key><false/></dict>
46738
46918
  <key>ThrottleInterval</key><integer>30</integer>
46739
- <key>StandardOutPath</key><string>${join3(logDir(), "out.log")}</string>
46740
- <key>StandardErrorPath</key><string>${join3(logDir(), "err.log")}</string>
46919
+ <key>StandardOutPath</key><string>${join4(logDir(), "out.log")}</string>
46920
+ <key>StandardErrorPath</key><string>${join4(logDir(), "err.log")}</string>
46741
46921
  <key>EnvironmentVariables</key>
46742
46922
  <dict>
46743
46923
  <key>PATH</key><string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
@@ -46746,7 +46926,7 @@ ${programArgs}
46746
46926
  </dict>
46747
46927
  </plist>
46748
46928
  `;
46749
- writeFileSync2(p, plist, { mode: 420 });
46929
+ writeFileSync3(p, plist, { mode: 420 });
46750
46930
  return p;
46751
46931
  }
46752
46932
  function launchctl(args) {
@@ -46777,7 +46957,7 @@ function macUninstall() {
46777
46957
  const target = `gui/${uid}/${SERVICE_LABEL}`;
46778
46958
  launchctl(["bootout", target]);
46779
46959
  const plist = plistPath();
46780
- if (existsSync4(plist)) {
46960
+ if (existsSync5(plist)) {
46781
46961
  try {
46782
46962
  unlinkSync(plist);
46783
46963
  } catch {
@@ -46790,12 +46970,12 @@ function macStatus() {
46790
46970
  return { running: r.ok, hint: r.ok ? "launchd managed" : "not installed" };
46791
46971
  }
46792
46972
  function systemdUnitPath() {
46793
- const xdg = process.env.XDG_CONFIG_HOME ?? join3(home(), ".config");
46794
- return join3(xdg, "systemd", "user", `${SYSTEMD_UNIT}.service`);
46973
+ const xdg = process.env.XDG_CONFIG_HOME ?? join4(home(), ".config");
46974
+ return join4(xdg, "systemd", "user", `${SYSTEMD_UNIT}.service`);
46795
46975
  }
46796
46976
  function writeSystemdUnit(cliPath) {
46797
46977
  const unitPath = systemdUnitPath();
46798
- mkdirSync(dirname4(unitPath), { recursive: true });
46978
+ mkdirSync2(dirname4(unitPath), { recursive: true });
46799
46979
  const unit = `[Unit]
46800
46980
  Description=modelstat agent
46801
46981
  Documentation=https://modelstat.ai
@@ -46810,13 +46990,13 @@ RestartSec=10
46810
46990
  # Don't restart-storm if the service is persistently unreachable.
46811
46991
  StartLimitIntervalSec=300
46812
46992
  StartLimitBurst=10
46813
- StandardOutput=append:${join3(logDir(), "out.log")}
46814
- StandardError=append:${join3(logDir(), "err.log")}
46993
+ StandardOutput=append:${join4(logDir(), "out.log")}
46994
+ StandardError=append:${join4(logDir(), "err.log")}
46815
46995
 
46816
46996
  [Install]
46817
46997
  WantedBy=default.target
46818
46998
  `;
46819
- writeFileSync2(unitPath, unit, { mode: 420 });
46999
+ writeFileSync3(unitPath, unit, { mode: 420 });
46820
47000
  return unitPath;
46821
47001
  }
46822
47002
  function systemctl(args) {
@@ -46836,7 +47016,7 @@ function linuxInstall() {
46836
47016
  function linuxUninstall() {
46837
47017
  systemctl(["disable", "--now", `${SYSTEMD_UNIT}.service`]);
46838
47018
  const unit = systemdUnitPath();
46839
- if (existsSync4(unit)) {
47019
+ if (existsSync5(unit)) {
46840
47020
  try {
46841
47021
  unlinkSync(unit);
46842
47022
  } catch {
@@ -46880,9 +47060,9 @@ function logsDir() {
46880
47060
  }
46881
47061
  function installTrayApp(sourceAppPath) {
46882
47062
  if (platform2() !== "darwin") return null;
46883
- if (!existsSync4(sourceAppPath)) return null;
46884
- const dest = join3(home(), "Applications", "ModelstatTray.app");
46885
- mkdirSync(dirname4(dest), { recursive: true });
47063
+ if (!existsSync5(sourceAppPath)) return null;
47064
+ const dest = join4(home(), "Applications", "ModelstatTray.app");
47065
+ mkdirSync2(dirname4(dest), { recursive: true });
46886
47066
  spawnSync("rm", ["-rf", dest]);
46887
47067
  const r = spawnSync("cp", ["-R", sourceAppPath, dest], { encoding: "utf8" });
46888
47068
  if (r.status !== 0) {
@@ -46895,25 +47075,25 @@ function bundledTrayAppPath() {
46895
47075
  const here2 = dirname4(fileURLToPath2(import.meta.url));
46896
47076
  const candidates = [
46897
47077
  // Pre-built .app — CI with codesigning drops one here.
46898
- join3(here2, "..", "vendor", "ModelstatTray.app"),
47078
+ join4(here2, "..", "vendor", "ModelstatTray.app"),
46899
47079
  // Local dev layout: apps/agent-dev/src/service.ts → ../../tray-mac/build/ModelstatTray.app
46900
- join3(here2, "..", "..", "tray-mac", "build", "ModelstatTray.app")
47080
+ join4(here2, "..", "..", "tray-mac", "build", "ModelstatTray.app")
46901
47081
  ];
46902
47082
  for (const c of candidates) {
46903
- if (existsSync4(c)) return c;
47083
+ if (existsSync5(c)) return c;
46904
47084
  }
46905
47085
  const sourceDirs = [
46906
- join3(here2, "..", "vendor", "tray-mac"),
46907
- join3(here2, "..", "..", "tray-mac")
47086
+ join4(here2, "..", "vendor", "tray-mac"),
47087
+ join4(here2, "..", "..", "tray-mac")
46908
47088
  ];
46909
47089
  for (const src of sourceDirs) {
46910
- const build = join3(src, "build-app.sh");
46911
- if (!existsSync4(build)) continue;
47090
+ const build = join4(src, "build-app.sh");
47091
+ if (!existsSync5(build)) continue;
46912
47092
  if (!hasSwift()) return null;
46913
47093
  const r = spawnSync("bash", [build], { cwd: src, encoding: "utf8" });
46914
47094
  if (r.status === 0) {
46915
- const app = join3(src, "build", "ModelstatTray.app");
46916
- if (existsSync4(app)) return app;
47095
+ const app = join4(src, "build", "ModelstatTray.app");
47096
+ if (existsSync5(app)) return app;
46917
47097
  }
46918
47098
  }
46919
47099
  return null;
@@ -46929,6 +47109,20 @@ function trayStatus() {
46929
47109
  }
46930
47110
 
46931
47111
  // src/cli.ts
47112
+ async function confirmPrompt(question, defaultYes) {
47113
+ if (process.stdin.isTTY !== true) return defaultYes;
47114
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
47115
+ try {
47116
+ const raw = await new Promise((resolve6) => rl.question(question, resolve6));
47117
+ const ans = raw.trim().toLowerCase();
47118
+ if (ans === "") return defaultYes;
47119
+ if (ans === "y" || ans === "yes") return true;
47120
+ if (ans === "n" || ans === "no") return false;
47121
+ return defaultYes;
47122
+ } finally {
47123
+ rl.close();
47124
+ }
47125
+ }
46932
47126
  function tryOpenBrowser(url) {
46933
47127
  const p = platform4();
46934
47128
  const cmd = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
@@ -46995,11 +47189,13 @@ async function cmdSelfRegister() {
46995
47189
  device_uuid: deviceUuid,
46996
47190
  fingerprint
46997
47191
  });
46998
- state.setDeviceUuid(res.device_uuid);
46999
- state.setDeviceId(res.device_id);
47000
- state.setBearer(res.device_secret);
47001
- state.setClaimCode(res.claim_code);
47002
- state.setClaimUrl(res.claim_url);
47192
+ state.saveFreshIdentity({
47193
+ deviceUuid: res.device_uuid,
47194
+ deviceId: res.device_id,
47195
+ bearerToken: res.device_secret,
47196
+ claimCode: res.claim_code,
47197
+ claimUrl: res.claim_url
47198
+ });
47003
47199
  process.stdout.write(` \x1B[32m\u2713\x1B[0m registered device_id=${res.device_id}
47004
47200
  `);
47005
47201
  process.stdout.write(` \x1B[32m\u2713\x1B[0m secret ${res.secret_prefix}\u2026 (hashed on server, never re-sent)
@@ -47059,19 +47255,21 @@ async function cmdConnect(opts) {
47059
47255
  };
47060
47256
  const wipeAndSelfRegister = async (reason) => {
47061
47257
  warn(`${reason} \u2014 re-registering this device`);
47258
+ const bak = backupIdentity();
47259
+ if (bak) warn(`old identity moved to ${bak}`);
47062
47260
  state.setBearer(null);
47063
- state.setDeviceId(null);
47064
- state.setDeviceUuid(null);
47065
- state.setClaimCode(null);
47066
- state.setClaimUrl(null);
47067
47261
  await cmdSelfRegister();
47068
47262
  };
47069
- if (!state.deviceUuid || !state.bearer || !state.deviceId) {
47263
+ if (opts.fresh && hasIdentityFile()) {
47264
+ step("`--fresh` passed \u2014 minting a new device identity");
47265
+ await wipeAndSelfRegister("forced fresh start");
47266
+ } else if (!state.deviceUuid || !state.bearer || !state.deviceId) {
47070
47267
  step("Registering this device with modelstat.ai");
47071
47268
  await cmdSelfRegister();
47072
47269
  } else {
47073
47270
  step("Re-using existing device identity");
47074
47271
  ok(`device ${state.deviceId}`);
47272
+ ok(`identity file ${identityPath()}`);
47075
47273
  try {
47076
47274
  const me = await fetchDeviceMe(state.bearer);
47077
47275
  if (me.claim_code && me.claim_code !== state.claimCode) {
@@ -47083,6 +47281,15 @@ async function cmdConnect(opts) {
47083
47281
  }
47084
47282
  } catch (e) {
47085
47283
  if (e instanceof DeviceMeUnauthorized) {
47284
+ const interactive = !opts.yes && process.stdin.isTTY === true;
47285
+ if (interactive) {
47286
+ const prompt = "cached credentials no longer accepted by the server. Re-register this device? [Y/n] ";
47287
+ const answer = await confirmPrompt(prompt, true);
47288
+ if (!answer) {
47289
+ warn("keeping existing identity; connect aborted");
47290
+ return;
47291
+ }
47292
+ }
47086
47293
  await wipeAndSelfRegister("cached credentials no longer valid");
47087
47294
  } else {
47088
47295
  warn(`couldn't refresh device state: ${e.message}`);
@@ -47362,7 +47569,9 @@ function cmdPaths(args) {
47362
47569
  function parseConnectOpts(argv) {
47363
47570
  return {
47364
47571
  json: argv.includes("--json"),
47365
- noBrowser: argv.includes("--no-browser")
47572
+ noBrowser: argv.includes("--no-browser"),
47573
+ fresh: argv.includes("--fresh"),
47574
+ yes: argv.includes("--yes") || argv.includes("-y")
47366
47575
  };
47367
47576
  }
47368
47577
  async function main() {