modelstat 0.0.44 → 0.0.46

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
@@ -50,9 +50,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
50
50
  ));
51
51
 
52
52
  // ../../packages/parsers/src/types.ts
53
+ var PARSER_EVENT_CHUNK;
53
54
  var init_types = __esm({
54
55
  "../../packages/parsers/src/types.ts"() {
55
56
  "use strict";
57
+ PARSER_EVENT_CHUNK = 256;
56
58
  }
57
59
  });
58
60
 
@@ -4688,6 +4690,16 @@ import { createReadStream, existsSync as existsSync2, readdirSync } from "fs";
4688
4690
  import { stat } from "fs/promises";
4689
4691
  import { dirname as dirname2, join } from "path";
4690
4692
  import { createInterface } from "readline";
4693
+ function countToolCalls(content) {
4694
+ const counts = {};
4695
+ if (!Array.isArray(content)) return counts;
4696
+ for (const block of content) {
4697
+ if (block && block.type === "tool_use" && typeof block.name === "string" && block.name) {
4698
+ counts[block.name] = (counts[block.name] ?? 0) + 1;
4699
+ }
4700
+ }
4701
+ return counts;
4702
+ }
4691
4703
  function extractExcerpt(content) {
4692
4704
  if (!content) return void 0;
4693
4705
  let text = "";
@@ -4712,6 +4724,21 @@ function extractExcerpt(content) {
4712
4724
  }
4713
4725
  async function parseClaudeCodeJsonl(ctx) {
4714
4726
  const events = [];
4727
+ let chunk = [];
4728
+ let emitted = 0;
4729
+ const emit = async (e) => {
4730
+ emitted += 1;
4731
+ if (!ctx.onEvents) {
4732
+ events.push(e);
4733
+ return;
4734
+ }
4735
+ chunk.push(e);
4736
+ if (chunk.length >= PARSER_EVENT_CHUNK) {
4737
+ const full = chunk;
4738
+ chunk = [];
4739
+ await ctx.onEvents(full);
4740
+ }
4741
+ };
4715
4742
  let rawLines = 0;
4716
4743
  let skipped = 0;
4717
4744
  let bytePos = 0;
@@ -4799,7 +4826,7 @@ async function parseClaudeCodeJsonl(ctx) {
4799
4826
  }
4800
4827
  const slug = guessRepoSlugFromPath(cwd);
4801
4828
  const excerpt = extractExcerpt(a.message?.content);
4802
- events.push({
4829
+ await emit({
4803
4830
  source_event_id: eventId,
4804
4831
  ts: a.timestamp,
4805
4832
  kind: "assistant_message",
@@ -4825,7 +4852,7 @@ async function parseClaudeCodeJsonl(ctx) {
4825
4852
  reasoning: 0
4826
4853
  },
4827
4854
  duration_ms: null,
4828
- tool_calls: {},
4855
+ tool_calls: countToolCalls(a.message?.content),
4829
4856
  files_touched: [],
4830
4857
  ...excerpt ? { content_excerpt: excerpt } : {},
4831
4858
  source_file: ctx.sourceFile,
@@ -4848,7 +4875,7 @@ async function parseClaudeCodeJsonl(ctx) {
4848
4875
  continue;
4849
4876
  }
4850
4877
  const excerpt = extractExcerpt(u.message?.content);
4851
- events.push({
4878
+ await emit({
4852
4879
  source_event_id: eventId,
4853
4880
  ts: u.timestamp,
4854
4881
  kind: "user_message",
@@ -4873,9 +4900,10 @@ async function parseClaudeCodeJsonl(ctx) {
4873
4900
  skipped += 1;
4874
4901
  }
4875
4902
  }
4903
+ if (ctx.onEvents && chunk.length > 0) await ctx.onEvents(chunk);
4876
4904
  return {
4877
4905
  events,
4878
- stats: { rawLines, emittedEvents: events.length, skipped },
4906
+ stats: { rawLines, emittedEvents: emitted, skipped },
4879
4907
  sourceFile: ctx.sourceFile
4880
4908
  };
4881
4909
  }
@@ -4897,6 +4925,7 @@ var init_claude_code = __esm({
4897
4925
  "../../packages/parsers/src/claude-code/index.ts"() {
4898
4926
  "use strict";
4899
4927
  init_src();
4928
+ init_types();
4900
4929
  init_git();
4901
4930
  }
4902
4931
  });
@@ -4912,6 +4941,21 @@ function deriveSessionIdFromRolloutPath(path5) {
4912
4941
  }
4913
4942
  async function parseCodexRollout(ctx) {
4914
4943
  const events = [];
4944
+ let chunk = [];
4945
+ let emitted = 0;
4946
+ const emit = async (e) => {
4947
+ emitted += 1;
4948
+ if (!ctx.onEvents) {
4949
+ events.push(e);
4950
+ return;
4951
+ }
4952
+ chunk.push(e);
4953
+ if (chunk.length >= PARSER_EVENT_CHUNK) {
4954
+ const full = chunk;
4955
+ chunk = [];
4956
+ await ctx.onEvents(full);
4957
+ }
4958
+ };
4915
4959
  let rawLines = 0;
4916
4960
  let skipped = 0;
4917
4961
  let bytePos = 0;
@@ -4967,7 +5011,7 @@ async function parseCodexRollout(ctx) {
4967
5011
  continue;
4968
5012
  }
4969
5013
  const slug = guessRepoSlugFromPath(cwd);
4970
- events.push({
5014
+ await emit({
4971
5015
  source_event_id: sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart),
4972
5016
  ts,
4973
5017
  kind: "assistant_message",
@@ -5006,7 +5050,7 @@ async function parseCodexRollout(ctx) {
5006
5050
  skipped += 1;
5007
5051
  continue;
5008
5052
  }
5009
- events.push({
5053
+ await emit({
5010
5054
  source_event_id: sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart),
5011
5055
  ts,
5012
5056
  kind: "user_message",
@@ -5032,9 +5076,10 @@ async function parseCodexRollout(ctx) {
5032
5076
  }
5033
5077
  skipped += 1;
5034
5078
  }
5079
+ if (ctx.onEvents && chunk.length > 0) await ctx.onEvents(chunk);
5035
5080
  return {
5036
5081
  events,
5037
- stats: { rawLines, emittedEvents: events.length, skipped },
5082
+ stats: { rawLines, emittedEvents: emitted, skipped },
5038
5083
  sourceFile: ctx.sourceFile
5039
5084
  };
5040
5085
  }
@@ -5042,6 +5087,7 @@ var init_codex = __esm({
5042
5087
  "../../packages/parsers/src/codex/index.ts"() {
5043
5088
  "use strict";
5044
5089
  init_src();
5090
+ init_types();
5045
5091
  init_git();
5046
5092
  }
5047
5093
  });
@@ -7612,107 +7658,6 @@ var init_src2 = __esm({
7612
7658
  }
7613
7659
  });
7614
7660
 
7615
- // src/identity.ts
7616
- import {
7617
- chmodSync,
7618
- mkdirSync,
7619
- readFileSync as readFileSync2,
7620
- renameSync,
7621
- writeFileSync,
7622
- existsSync as existsSync4
7623
- } from "fs";
7624
- import { homedir as homedir2, hostname as osHostname } from "os";
7625
- import { join as join3 } from "path";
7626
- function ensureRoot() {
7627
- mkdirSync(ROOT, { recursive: true, mode: 448 });
7628
- }
7629
- function writeAtomic(meta) {
7630
- ensureRoot();
7631
- const tmp = `${IDENTITY_FILE}.${process.pid}.tmp`;
7632
- writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
7633
- renameSync(tmp, IDENTITY_FILE);
7634
- try {
7635
- chmodSync(IDENTITY_FILE, 384);
7636
- } catch {
7637
- }
7638
- }
7639
- function identityPath() {
7640
- return IDENTITY_FILE;
7641
- }
7642
- function hasIdentityFile() {
7643
- return existsSync4(IDENTITY_FILE);
7644
- }
7645
- function parseFile() {
7646
- try {
7647
- const raw = readFileSync2(IDENTITY_FILE, "utf8");
7648
- const obj = JSON.parse(raw);
7649
- if (!obj.deviceUuid || !obj.deviceId || !obj.bearerToken) {
7650
- return null;
7651
- }
7652
- return {
7653
- deviceUuid: obj.deviceUuid,
7654
- deviceId: obj.deviceId,
7655
- bearerToken: obj.bearerToken,
7656
- claimCode: obj.claimCode ?? null,
7657
- claimUrl: obj.claimUrl ?? null,
7658
- hostname: obj.hostname ?? osHostname(),
7659
- createdAt: obj.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
7660
- userEmail: obj.userEmail ?? null,
7661
- defaultOrgId: obj.defaultOrgId ?? null
7662
- };
7663
- } catch {
7664
- return null;
7665
- }
7666
- }
7667
- function loadIdentity(migrateFromConf2) {
7668
- const fromFile = parseFile();
7669
- if (fromFile) return fromFile;
7670
- if (!migrateFromConf2) return null;
7671
- const legacy = migrateFromConf2();
7672
- if (!legacy) return null;
7673
- if (!legacy.deviceUuid || !legacy.deviceId || !legacy.bearerToken) {
7674
- return null;
7675
- }
7676
- const migrated = {
7677
- deviceUuid: legacy.deviceUuid,
7678
- deviceId: legacy.deviceId,
7679
- bearerToken: legacy.bearerToken,
7680
- claimCode: legacy.claimCode ?? null,
7681
- claimUrl: legacy.claimUrl ?? null,
7682
- hostname: osHostname(),
7683
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7684
- userEmail: legacy.userEmail ?? null,
7685
- defaultOrgId: legacy.defaultOrgId ?? null
7686
- };
7687
- writeAtomic(migrated);
7688
- return migrated;
7689
- }
7690
- function saveIdentity(meta) {
7691
- writeAtomic(meta);
7692
- }
7693
- function backupIdentity() {
7694
- if (!existsSync4(IDENTITY_FILE)) return null;
7695
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7696
- const dest = `${IDENTITY_FILE}.bak-${stamp}`;
7697
- renameSync(IDENTITY_FILE, dest);
7698
- return dest;
7699
- }
7700
- function updateIdentity(patch) {
7701
- const current = parseFile();
7702
- if (!current) return null;
7703
- const merged = { ...current, ...patch };
7704
- writeAtomic(merged);
7705
- return merged;
7706
- }
7707
- var ROOT, IDENTITY_FILE;
7708
- var init_identity = __esm({
7709
- "src/identity.ts"() {
7710
- "use strict";
7711
- ROOT = join3(homedir2(), ".modelstat");
7712
- IDENTITY_FILE = join3(ROOT, "identity.json");
7713
- }
7714
- });
7715
-
7716
7661
  // ../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/core/symbols.js
7717
7662
  var require_symbols = __commonJS({
7718
7663
  "../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/core/symbols.js"(exports, module) {
@@ -32604,6 +32549,9 @@ function classifyStatus(status2, attempt) {
32604
32549
  }
32605
32550
  return { type: "drop", reason: `http_${status2}` };
32606
32551
  }
32552
+ function wellFormedStringify(value) {
32553
+ return JSON.stringify(value, (_key, v) => typeof v === "string" ? v.toWellFormed() : v);
32554
+ }
32607
32555
  function sleep(ms) {
32608
32556
  return new Promise((r) => {
32609
32557
  setTimeout(r, ms);
@@ -32642,7 +32590,10 @@ var init_http = __esm({
32642
32590
  "content-type": "application/json",
32643
32591
  authorization: `Bearer ${token}`
32644
32592
  },
32645
- body: JSON.stringify(batch)
32593
+ // wellFormedStringify, not JSON.stringify: a truncated-emoji
32594
+ // lone surrogate in any excerpt 400s the whole batch on the
32595
+ // serde_json side. See the helper's doc comment.
32596
+ body: wellFormedStringify(batch)
32646
32597
  });
32647
32598
  } catch (err) {
32648
32599
  const detail = describeErrorWithCause(err);
@@ -33291,15 +33242,15 @@ function envPaths(name, { suffix = "nodejs" } = {}) {
33291
33242
  }
33292
33243
  return linux(name);
33293
33244
  }
33294
- var homedir3, tmpdir, env, macos, windows, linux;
33245
+ var homedir2, tmpdir, env, macos, windows, linux;
33295
33246
  var init_env_paths = __esm({
33296
33247
  "../../node_modules/.pnpm/env-paths@3.0.0/node_modules/env-paths/index.js"() {
33297
33248
  "use strict";
33298
- homedir3 = os.homedir();
33249
+ homedir2 = os.homedir();
33299
33250
  tmpdir = os.tmpdir();
33300
33251
  ({ env } = process2);
33301
33252
  macos = (name) => {
33302
- const library = path.join(homedir3, "Library");
33253
+ const library = path.join(homedir2, "Library");
33303
33254
  return {
33304
33255
  data: path.join(library, "Application Support", name),
33305
33256
  config: path.join(library, "Preferences", name),
@@ -33309,8 +33260,8 @@ var init_env_paths = __esm({
33309
33260
  };
33310
33261
  };
33311
33262
  windows = (name) => {
33312
- const appData = env.APPDATA || path.join(homedir3, "AppData", "Roaming");
33313
- const localAppData = env.LOCALAPPDATA || path.join(homedir3, "AppData", "Local");
33263
+ const appData = env.APPDATA || path.join(homedir2, "AppData", "Roaming");
33264
+ const localAppData = env.LOCALAPPDATA || path.join(homedir2, "AppData", "Local");
33314
33265
  return {
33315
33266
  // Data/config/cache/log are invented by me as Windows isn't opinionated about this
33316
33267
  data: path.join(localAppData, name, "Data"),
@@ -33321,13 +33272,13 @@ var init_env_paths = __esm({
33321
33272
  };
33322
33273
  };
33323
33274
  linux = (name) => {
33324
- const username = path.basename(homedir3);
33275
+ const username = path.basename(homedir2);
33325
33276
  return {
33326
- data: path.join(env.XDG_DATA_HOME || path.join(homedir3, ".local", "share"), name),
33327
- config: path.join(env.XDG_CONFIG_HOME || path.join(homedir3, ".config"), name),
33328
- cache: path.join(env.XDG_CACHE_HOME || path.join(homedir3, ".cache"), name),
33277
+ data: path.join(env.XDG_DATA_HOME || path.join(homedir2, ".local", "share"), name),
33278
+ config: path.join(env.XDG_CONFIG_HOME || path.join(homedir2, ".config"), name),
33279
+ cache: path.join(env.XDG_CACHE_HOME || path.join(homedir2, ".cache"), name),
33329
33280
  // https://wiki.debian.org/XDGBaseDirectorySpecification#state
33330
- log: path.join(env.XDG_STATE_HOME || path.join(homedir3, ".local", "state"), name),
33281
+ log: path.join(env.XDG_STATE_HOME || path.join(homedir2, ".local", "state"), name),
33331
33282
  temp: path.join(tmpdir, username, name)
33332
33283
  };
33333
33284
  };
@@ -33768,9 +33719,9 @@ import { once } from "events";
33768
33719
  import { createWriteStream } from "fs";
33769
33720
  import path3 from "path";
33770
33721
  import { Readable } from "stream";
33771
- function writeFileSync2(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
33722
+ function writeFileSync(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
33772
33723
  if (isString(options))
33773
- return writeFileSync2(filePath, data, { encoding: options });
33724
+ return writeFileSync(filePath, data, { encoding: options });
33774
33725
  const timeout = options.timeout ?? DEFAULT_TIMEOUT_SYNC;
33775
33726
  const retryOptions = { timeout };
33776
33727
  let tempDisposer = null;
@@ -43893,7 +43844,7 @@ var init_source = __esm({
43893
43844
  fs2.writeFileSync(this.path, data, { mode: this.#options.configFileMode });
43894
43845
  } else {
43895
43846
  try {
43896
- writeFileSync2(this.path, data, { mode: this.#options.configFileMode });
43847
+ writeFileSync(this.path, data, { mode: this.#options.configFileMode });
43897
43848
  } catch (error) {
43898
43849
  if (error?.code === "EXDEV") {
43899
43850
  fs2.writeFileSync(this.path, data, { mode: this.#options.configFileMode });
@@ -43994,6 +43945,107 @@ var init_source = __esm({
43994
43945
  }
43995
43946
  });
43996
43947
 
43948
+ // src/identity.ts
43949
+ import {
43950
+ chmodSync,
43951
+ mkdirSync,
43952
+ readFileSync as readFileSync2,
43953
+ renameSync,
43954
+ writeFileSync as writeFileSync2,
43955
+ existsSync as existsSync4
43956
+ } from "fs";
43957
+ import { homedir as homedir3, hostname as osHostname } from "os";
43958
+ import { join as join3 } from "path";
43959
+ function ensureRoot() {
43960
+ mkdirSync(ROOT, { recursive: true, mode: 448 });
43961
+ }
43962
+ function writeAtomic(meta) {
43963
+ ensureRoot();
43964
+ const tmp = `${IDENTITY_FILE}.${process.pid}.tmp`;
43965
+ writeFileSync2(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
43966
+ renameSync(tmp, IDENTITY_FILE);
43967
+ try {
43968
+ chmodSync(IDENTITY_FILE, 384);
43969
+ } catch {
43970
+ }
43971
+ }
43972
+ function identityPath() {
43973
+ return IDENTITY_FILE;
43974
+ }
43975
+ function hasIdentityFile() {
43976
+ return existsSync4(IDENTITY_FILE);
43977
+ }
43978
+ function parseFile() {
43979
+ try {
43980
+ const raw = readFileSync2(IDENTITY_FILE, "utf8");
43981
+ const obj = JSON.parse(raw);
43982
+ if (!obj.deviceUuid || !obj.deviceId || !obj.bearerToken) {
43983
+ return null;
43984
+ }
43985
+ return {
43986
+ deviceUuid: obj.deviceUuid,
43987
+ deviceId: obj.deviceId,
43988
+ bearerToken: obj.bearerToken,
43989
+ claimCode: obj.claimCode ?? null,
43990
+ claimUrl: obj.claimUrl ?? null,
43991
+ hostname: obj.hostname ?? osHostname(),
43992
+ createdAt: obj.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
43993
+ userEmail: obj.userEmail ?? null,
43994
+ defaultOrgId: obj.defaultOrgId ?? null
43995
+ };
43996
+ } catch {
43997
+ return null;
43998
+ }
43999
+ }
44000
+ function loadIdentity(migrateFromConf2) {
44001
+ const fromFile = parseFile();
44002
+ if (fromFile) return fromFile;
44003
+ if (!migrateFromConf2) return null;
44004
+ const legacy = migrateFromConf2();
44005
+ if (!legacy) return null;
44006
+ if (!legacy.deviceUuid || !legacy.deviceId || !legacy.bearerToken) {
44007
+ return null;
44008
+ }
44009
+ const migrated = {
44010
+ deviceUuid: legacy.deviceUuid,
44011
+ deviceId: legacy.deviceId,
44012
+ bearerToken: legacy.bearerToken,
44013
+ claimCode: legacy.claimCode ?? null,
44014
+ claimUrl: legacy.claimUrl ?? null,
44015
+ hostname: osHostname(),
44016
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
44017
+ userEmail: legacy.userEmail ?? null,
44018
+ defaultOrgId: legacy.defaultOrgId ?? null
44019
+ };
44020
+ writeAtomic(migrated);
44021
+ return migrated;
44022
+ }
44023
+ function saveIdentity(meta) {
44024
+ writeAtomic(meta);
44025
+ }
44026
+ function backupIdentity() {
44027
+ if (!existsSync4(IDENTITY_FILE)) return null;
44028
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
44029
+ const dest = `${IDENTITY_FILE}.bak-${stamp}`;
44030
+ renameSync(IDENTITY_FILE, dest);
44031
+ return dest;
44032
+ }
44033
+ function updateIdentity(patch) {
44034
+ const current = parseFile();
44035
+ if (!current) return null;
44036
+ const merged = { ...current, ...patch };
44037
+ writeAtomic(merged);
44038
+ return merged;
44039
+ }
44040
+ var ROOT, IDENTITY_FILE;
44041
+ var init_identity = __esm({
44042
+ "src/identity.ts"() {
44043
+ "use strict";
44044
+ ROOT = join3(homedir3(), ".modelstat");
44045
+ IDENTITY_FILE = join3(ROOT, "identity.json");
44046
+ }
44047
+ });
44048
+
43997
44049
  // src/config.ts
43998
44050
  import { existsSync as existsSync5 } from "fs";
43999
44051
  import { hostname } from "os";
@@ -45312,6 +45364,21 @@ var init_llama = __esm({
45312
45364
  }
45313
45365
  });
45314
45366
 
45367
+ // ../../packages/companion-core/src/optional-module.ts
45368
+ function isMissingOptionalModuleError(err) {
45369
+ const code = err?.code;
45370
+ if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") return true;
45371
+ const msg = err instanceof Error ? err.message : String(err);
45372
+ return /cannot find (package|module)|cannot resolve|failed to resolve/i.test(msg);
45373
+ }
45374
+ var OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS;
45375
+ var init_optional_module = __esm({
45376
+ "../../packages/companion-core/src/optional-module.ts"() {
45377
+ "use strict";
45378
+ OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS = 3;
45379
+ }
45380
+ });
45381
+
45315
45382
  // ../../packages/companion-core/src/node/transformersjs-embed.ts
45316
45383
  async function loadPipeline(model) {
45317
45384
  if (cached) return cached;
@@ -45319,11 +45386,7 @@ async function loadPipeline(model) {
45319
45386
  if (!loadPromise2) {
45320
45387
  loadPromise2 = (async () => {
45321
45388
  try {
45322
- const moduleId = "@huggingface/transformers";
45323
- const tjs = await import(
45324
- /* @vite-ignore */
45325
- moduleId
45326
- );
45389
+ const tjs = await importModule("@huggingface/transformers");
45327
45390
  const p = await tjs.pipeline("feature-extraction", model, {
45328
45391
  device: "cpu",
45329
45392
  dtype: "fp32"
@@ -45332,12 +45395,16 @@ async function loadPipeline(model) {
45332
45395
  return p;
45333
45396
  } catch (err) {
45334
45397
  const msg = err.message;
45335
- if (/unsupported|architecture|not supported|onnx|cannot resolve/i.test(msg)) {
45398
+ loadAttempts += 1;
45399
+ if (isMissingOptionalModuleError(err) || /unsupported|architecture|not supported|onnx/i.test(msg) || loadAttempts >= OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS) {
45336
45400
  loadFailedPermanently = true;
45337
45401
  }
45338
- console.warn(
45339
- `[modelstat] transformers.js embedder unavailable (segments will be re-embedded server-side): ${msg}`
45340
- );
45402
+ if (!warnedUnavailable) {
45403
+ warnedUnavailable = true;
45404
+ console.warn(
45405
+ `[modelstat] transformers.js embedder unavailable (segments will be re-embedded server-side; further attempts ${loadFailedPermanently ? "disabled" : `limited to ${OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS}`}, warning once): ${msg}`
45406
+ );
45407
+ }
45341
45408
  loadPromise2 = null;
45342
45409
  return null;
45343
45410
  }
@@ -45354,21 +45421,32 @@ function createTransformersJsEmbedder(model = DEFAULT_MODEL) {
45354
45421
  const out = await pipe(text, { pooling: "mean", normalize: true });
45355
45422
  return Array.from(out.data);
45356
45423
  } catch (err) {
45357
- console.warn(
45358
- `[modelstat] embed error (returning empty vector, server will re-embed): ${err.message}`
45359
- );
45424
+ if (!warnedInferenceError) {
45425
+ warnedInferenceError = true;
45426
+ console.warn(
45427
+ `[modelstat] embed error (returning empty vectors, server will re-embed; warning once): ${err.message}`
45428
+ );
45429
+ }
45360
45430
  return [];
45361
45431
  }
45362
45432
  };
45363
45433
  }
45364
- var cached, loadPromise2, loadFailedPermanently, DEFAULT_MODEL;
45434
+ var cached, loadPromise2, loadFailedPermanently, loadAttempts, warnedUnavailable, warnedInferenceError, DEFAULT_MODEL, importModule;
45365
45435
  var init_transformersjs_embed = __esm({
45366
45436
  "../../packages/companion-core/src/node/transformersjs-embed.ts"() {
45367
45437
  "use strict";
45438
+ init_optional_module();
45368
45439
  cached = null;
45369
45440
  loadPromise2 = null;
45370
45441
  loadFailedPermanently = false;
45442
+ loadAttempts = 0;
45443
+ warnedUnavailable = false;
45444
+ warnedInferenceError = false;
45371
45445
  DEFAULT_MODEL = "Xenova/bge-small-en-v1.5";
45446
+ importModule = (id) => import(
45447
+ /* @vite-ignore */
45448
+ id
45449
+ );
45372
45450
  }
45373
45451
  });
45374
45452
 
@@ -45407,18 +45485,23 @@ async function createPrivacyFilterRedactor(opts = {}) {
45407
45485
  const device = opts.device ?? (isBrowser ? "webgpu" : "cpu");
45408
45486
  const dtype = opts.dtype ?? "q4";
45409
45487
  const modelId = opts.model ?? "openai/privacy-filter";
45488
+ const importModule2 = opts.importModule ?? ((id) => import(
45489
+ /* @vite-ignore */
45490
+ id
45491
+ ));
45410
45492
  let cached2 = null;
45411
45493
  let loadPromise3 = null;
45494
+ let loadFailedPermanently2 = false;
45495
+ let loadAttempts2 = 0;
45496
+ let warnedUnavailable2 = false;
45497
+ let warnedInferenceError2 = false;
45412
45498
  async function loadPipeline2() {
45413
45499
  if (cached2) return cached2;
45500
+ if (loadFailedPermanently2) return null;
45414
45501
  if (!loadPromise3) {
45415
45502
  loadPromise3 = (async () => {
45416
45503
  try {
45417
- const moduleId = "@huggingface/transformers";
45418
- const tjs = await import(
45419
- /* @vite-ignore */
45420
- moduleId
45421
- );
45504
+ const tjs = await importModule2("@huggingface/transformers");
45422
45505
  const p = await tjs.pipeline("token-classification", modelId, {
45423
45506
  device,
45424
45507
  dtype,
@@ -45428,10 +45511,17 @@ async function createPrivacyFilterRedactor(opts = {}) {
45428
45511
  return p;
45429
45512
  } catch (err) {
45430
45513
  loadPromise3 = null;
45431
- console.warn(
45432
- "[privacy-filter] adapter unavailable \u2014 install @huggingface/transformers in the consuming package to enable model-based redaction. Falling back to pass-through.",
45433
- err.message
45434
- );
45514
+ loadAttempts2 += 1;
45515
+ if (isMissingOptionalModuleError(err) || loadAttempts2 >= OPTIONAL_MODULE_MAX_LOAD_ATTEMPTS) {
45516
+ loadFailedPermanently2 = true;
45517
+ }
45518
+ if (!warnedUnavailable2) {
45519
+ warnedUnavailable2 = true;
45520
+ console.warn(
45521
+ "[privacy-filter] adapter unavailable \u2014 install @huggingface/transformers in the consuming package to enable model-based redaction. Falling back to pass-through (warning once).",
45522
+ err.message
45523
+ );
45524
+ }
45435
45525
  return null;
45436
45526
  }
45437
45527
  })();
@@ -45454,10 +45544,13 @@ async function createPrivacyFilterRedactor(opts = {}) {
45454
45544
  try {
45455
45545
  tokens = await classify(text);
45456
45546
  } catch (err) {
45457
- console.warn(
45458
- "[privacy-filter] inference failed, returning input unchanged:",
45459
- err.message
45460
- );
45547
+ if (!warnedInferenceError2) {
45548
+ warnedInferenceError2 = true;
45549
+ console.warn(
45550
+ "[privacy-filter] inference failed, returning input unchanged (warning once):",
45551
+ err.message
45552
+ );
45553
+ }
45461
45554
  return empty;
45462
45555
  }
45463
45556
  const spans = [];
@@ -45494,6 +45587,7 @@ async function createPrivacyFilterRedactor(opts = {}) {
45494
45587
  var init_privacy_filter = __esm({
45495
45588
  "../../packages/companion-core/src/redact/privacy-filter.ts"() {
45496
45589
  "use strict";
45590
+ init_optional_module();
45497
45591
  }
45498
45592
  });
45499
45593
 
@@ -45603,9 +45697,8 @@ async function scanAll(cb = {}) {
45603
45697
  const full = join5(dir, f);
45604
45698
  jobs.push({
45605
45699
  path: full,
45606
- parse: async () => {
45607
- const r = await parseClaudeCodeJsonl({ deviceId, sourceFile: full });
45608
- return { events: r.events, path: full };
45700
+ parse: async (sink2) => {
45701
+ await parseClaudeCodeJsonl({ deviceId, sourceFile: full, onEvents: sink2 });
45609
45702
  }
45610
45703
  });
45611
45704
  }
@@ -45627,9 +45720,8 @@ async function scanAll(cb = {}) {
45627
45720
  const full = join5(base, y, m, d, f);
45628
45721
  jobs.push({
45629
45722
  path: full,
45630
- parse: async () => {
45631
- const r = await parseCodexRollout({ deviceId, sourceFile: full });
45632
- return { events: r.events, path: full };
45723
+ parse: async (sink2) => {
45724
+ await parseCodexRollout({ deviceId, sourceFile: full, onEvents: sink2 });
45633
45725
  }
45634
45726
  });
45635
45727
  }
@@ -45684,6 +45776,17 @@ async function scanAll(cb = {}) {
45684
45776
  buffer = [];
45685
45777
  cb.onUploaded?.({ events: res.accepted, segments: segments.length });
45686
45778
  }
45779
+ const sink = async (events) => {
45780
+ for (const e of events) {
45781
+ buffer.push(e);
45782
+ if (buffer.length >= BATCH_MAX_EVENTS) await flushBatch();
45783
+ }
45784
+ if (buffer.length > BATCH_BUFFER_HARD_CAP) {
45785
+ throw new Error(
45786
+ `scan event buffer exceeded ${BATCH_BUFFER_HARD_CAP} events \u2014 incremental batch flushing has regressed (see BATCH_BUFFER_HARD_CAP)`
45787
+ );
45788
+ }
45789
+ };
45687
45790
  for (let i = 0; i < jobs.length; i++) {
45688
45791
  const job = jobs[i];
45689
45792
  cb.onFile?.(job.path, i, jobs.length);
@@ -45695,13 +45798,7 @@ async function scanAll(cb = {}) {
45695
45798
  }
45696
45799
  filesScanned += 1;
45697
45800
  try {
45698
- const r = await job.parse();
45699
- if (r.events.length) {
45700
- for (const e of r.events) {
45701
- buffer.push(e);
45702
- if (buffer.length >= BATCH_MAX_EVENTS) await flushBatch();
45703
- }
45704
- }
45801
+ await job.parse(sink);
45705
45802
  if (cs) pendingCursors.push({ path: job.path, cs });
45706
45803
  } catch (e) {
45707
45804
  console.warn(` ! parse failed for ${job.path}:`, e.message);
@@ -45710,7 +45807,7 @@ async function scanAll(cb = {}) {
45710
45807
  await flushBatch();
45711
45808
  return { filesScanned, filesUnchanged, batchesUploaded, eventsUploaded, segmentsUploaded };
45712
45809
  }
45713
- var AGENT_VERSION, BATCH_MAX_EVENTS, ZERO_TOKENS;
45810
+ var AGENT_VERSION, BATCH_MAX_EVENTS, BATCH_BUFFER_HARD_CAP, ZERO_TOKENS;
45714
45811
  var init_scan = __esm({
45715
45812
  "src/scan.ts"() {
45716
45813
  "use strict";
@@ -45719,8 +45816,9 @@ var init_scan = __esm({
45719
45816
  init_pipeline2();
45720
45817
  init_config2();
45721
45818
  init_api();
45722
- AGENT_VERSION = true ? "agent-0.0.44" : "agent-dev";
45819
+ AGENT_VERSION = true ? "agent-0.0.46" : "agent-dev";
45723
45820
  BATCH_MAX_EVENTS = 2e3;
45821
+ BATCH_BUFFER_HARD_CAP = BATCH_MAX_EVENTS * 2;
45724
45822
  ZERO_TOKENS = {
45725
45823
  input: 0,
45726
45824
  output: 0,
@@ -45756,9 +45854,9 @@ function isProcessAlive(pid) {
45756
45854
  return false;
45757
45855
  }
45758
45856
  }
45759
- function readLock() {
45857
+ function readDaemonLock(lockFile = LOCK_FILE) {
45760
45858
  try {
45761
- const raw = readFileSync4(LOCK_FILE, "utf8");
45859
+ const raw = readFileSync4(lockFile, "utf8");
45762
45860
  const obj = JSON.parse(raw);
45763
45861
  if (typeof obj.pid !== "number") return null;
45764
45862
  return {
@@ -45783,7 +45881,7 @@ function writeLockAtomic(meta) {
45783
45881
  renameSync2(tmp, LOCK_FILE);
45784
45882
  }
45785
45883
  function removeLockIfOwned(ownerPid) {
45786
- const lock = readLock();
45884
+ const lock = readDaemonLock();
45787
45885
  if (!lock) return;
45788
45886
  if (lock.pid !== ownerPid) return;
45789
45887
  try {
@@ -45792,7 +45890,7 @@ function removeLockIfOwned(ownerPid) {
45792
45890
  }
45793
45891
  }
45794
45892
  function acquireDaemonLock(opts) {
45795
- const existing = readLock();
45893
+ const existing = readDaemonLock();
45796
45894
  if (existing && isProcessAlive(existing.pid)) {
45797
45895
  if (!opts.force) {
45798
45896
  const ageSec = ageInSeconds(existing.startedAt);
@@ -45810,6 +45908,20 @@ function acquireDaemonLock(opts) {
45810
45908
  apiUrl: opts.apiUrl
45811
45909
  };
45812
45910
  writeLockAtomic(meta);
45911
+ const recheck = setTimeout(() => {
45912
+ const current = readDaemonLock();
45913
+ if (checkLockOwnership(process.pid, current) !== "lost") return;
45914
+ const winner = current;
45915
+ if (opts.onLockLost) {
45916
+ opts.onLockLost(winner);
45917
+ return;
45918
+ }
45919
+ console.log(
45920
+ `[modelstat] another daemon (pid ${winner.pid}, started ${winner.startedAt}) won the lock race \u2014 this instance is standing down.`
45921
+ );
45922
+ process.exit(0);
45923
+ }, LOCK_RECHECK_MS);
45924
+ recheck.unref();
45813
45925
  const cleanup = () => removeLockIfOwned(process.pid);
45814
45926
  process.once("beforeExit", cleanup);
45815
45927
  process.once("SIGINT", () => {
@@ -45827,6 +45939,11 @@ function acquireDaemonLock(opts) {
45827
45939
  });
45828
45940
  return { kind: "acquired" };
45829
45941
  }
45942
+ function checkLockOwnership(myPid, current, pidAlive = isProcessAlive) {
45943
+ if (!current || current.pid === myPid) return "owned";
45944
+ if (!pidAlive(current.pid)) return "winner_dead";
45945
+ return "lost";
45946
+ }
45830
45947
  function ageInSeconds(iso) {
45831
45948
  const t = Date.parse(iso);
45832
45949
  if (Number.isNaN(t)) return -1;
@@ -45841,12 +45958,13 @@ function formatAge(seconds) {
45841
45958
  const h = Math.floor(m / 60);
45842
45959
  return `${h}h ${m % 60}m`;
45843
45960
  }
45844
- var LOCK_DIR, LOCK_FILE;
45961
+ var LOCK_DIR, LOCK_FILE, LOCK_RECHECK_MS;
45845
45962
  var init_lock = __esm({
45846
45963
  "src/lock.ts"() {
45847
45964
  "use strict";
45848
45965
  LOCK_DIR = join7(homedir7(), ".modelstat");
45849
45966
  LOCK_FILE = join7(LOCK_DIR, "daemon.lock");
45967
+ LOCK_RECHECK_MS = 5e3;
45850
45968
  }
45851
45969
  });
45852
45970
 
@@ -47720,17 +47838,44 @@ async function sendHeartbeat() {
47720
47838
  }
47721
47839
  writeLocalStatus(body).catch(() => void 0);
47722
47840
  }
47841
+ async function rotateRunawayLogs() {
47842
+ const { homedir: homedir10 } = await import("os");
47843
+ const { join: join12 } = await import("path");
47844
+ const { open: open2, stat: stat6, truncate, writeFile } = await import("fs/promises");
47845
+ const dir = join12(homedir10(), ".modelstat", "logs");
47846
+ for (const name of ["out.log", "err.log"]) {
47847
+ const p = join12(dir, name);
47848
+ try {
47849
+ const st = await stat6(p);
47850
+ if (st.size <= LOG_MAX_BYTES) continue;
47851
+ const keep = Math.min(LOG_TAIL_KEEP_BYTES, st.size);
47852
+ const fh = await open2(p, "r");
47853
+ try {
47854
+ const buf = Buffer.alloc(keep);
47855
+ await fh.read(buf, 0, keep, st.size - keep);
47856
+ await writeFile(p.replace(/\.log$/, ".old.log"), buf);
47857
+ } finally {
47858
+ await fh.close();
47859
+ }
47860
+ await truncate(p, 0);
47861
+ console.log(
47862
+ `[modelstat] rotated ${name}: was ${(st.size / 1024 / 1024).toFixed(0)} MB (> ${LOG_MAX_BYTES / 1024 / 1024} MB cap), tail kept in ${name.replace(/\.log$/, ".old.log")}`
47863
+ );
47864
+ } catch {
47865
+ }
47866
+ }
47867
+ }
47723
47868
  async function writeLocalStatus(snapshot) {
47724
- const { homedir: homedir9 } = await import("os");
47725
- const { join: join11 } = await import("path");
47869
+ const { homedir: homedir10 } = await import("os");
47870
+ const { join: join12 } = await import("path");
47726
47871
  const { writeFile, mkdir: mkdir2, rename } = await import("fs/promises");
47727
47872
  if (!lastStatusPath) {
47728
- const dir = join11(homedir9(), ".modelstat");
47873
+ const dir = join12(homedir10(), ".modelstat");
47729
47874
  try {
47730
47875
  await mkdir2(dir, { recursive: true });
47731
47876
  } catch {
47732
47877
  }
47733
- lastStatusPath = join11(dir, "last-status.json");
47878
+ lastStatusPath = join12(dir, "last-status.json");
47734
47879
  }
47735
47880
  const tmp = `${lastStatusPath}.tmp`;
47736
47881
  try {
@@ -47812,7 +47957,17 @@ async function runDaemon(opts = {}) {
47812
47957
  const lock = acquireDaemonLock({
47813
47958
  agentVersion: AGENT_VERSION2,
47814
47959
  apiUrl: state.apiUrl,
47815
- force: opts.force === true
47960
+ force: opts.force === true,
47961
+ // If a racing daemon out-renamed us for the lock (see lock.ts
47962
+ // recheck), stand down with exit 0: our supervisor's next health
47963
+ // check sees the winner and adopts it instead of respawning us.
47964
+ onLockLost: (winner) => {
47965
+ setPhase("offline", `another daemon (pid ${winner.pid}) owns the lock \u2014 standing down`);
47966
+ console.log(
47967
+ `[modelstat] another daemon (pid ${winner.pid}, started ${winner.startedAt}) won the lock race \u2014 this instance is standing down.`
47968
+ );
47969
+ process.exit(0);
47970
+ }
47816
47971
  });
47817
47972
  if (lock.kind === "already_running") {
47818
47973
  console.log(
@@ -47829,6 +47984,7 @@ async function runDaemon(opts = {}) {
47829
47984
  return;
47830
47985
  }
47831
47986
  setPhase("starting", "Booting");
47987
+ await rotateRunawayLogs();
47832
47988
  setStat("segments_sent", state.segmentsSent);
47833
47989
  const hb = setInterval(() => void sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
47834
47990
  hb.unref();
@@ -47852,19 +48008,19 @@ async function runDaemon(opts = {}) {
47852
48008
  await runDiscovery();
47853
48009
  await requestScan("startup");
47854
48010
  const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
47855
- const { homedir: homedir9, platform: platform5 } = await import("os");
47856
- const { join: join11 } = await import("path");
47857
- const home2 = homedir9();
48011
+ const { homedir: homedir10, platform: platform5 } = await import("os");
48012
+ const { join: join12 } = await import("path");
48013
+ const home2 = homedir10();
47858
48014
  const dirs = [
47859
- join11(home2, ".claude/projects"),
47860
- join11(home2, ".codex/sessions"),
47861
- join11(home2, ".cursor/ai-tracking"),
47862
- join11(home2, ".gemini"),
48015
+ join12(home2, ".claude/projects"),
48016
+ join12(home2, ".codex/sessions"),
48017
+ join12(home2, ".cursor/ai-tracking"),
48018
+ join12(home2, ".gemini"),
47863
48019
  ...platform5() === "darwin" ? [
47864
- join11(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
47865
- join11(home2, "Library/Application Support/Claude")
48020
+ join12(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
48021
+ join12(home2, "Library/Application Support/Claude")
47866
48022
  ] : [
47867
- join11(home2, ".config/Cursor/User/workspaceStorage")
48023
+ join12(home2, ".config/Cursor/User/workspaceStorage")
47868
48024
  ]
47869
48025
  ].filter((p) => existsSync9(p) && statSync2(p).isDirectory());
47870
48026
  setPhase("watching", `Watching ${dirs.length} directories`);
@@ -47907,7 +48063,7 @@ async function runDaemon(opts = {}) {
47907
48063
  await new Promise(() => {
47908
48064
  });
47909
48065
  }
47910
- var import_undici2, AGENT_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, DISCOVERY_INTERVAL_MS, status, LOCAL_FLUSH_THROTTLE_MS, localFlushTimer, localFlushPending, lastStatusPath, scanRunner;
48066
+ var import_undici2, AGENT_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;
47911
48067
  var init_daemon = __esm({
47912
48068
  "src/daemon.ts"() {
47913
48069
  "use strict";
@@ -47919,7 +48075,7 @@ var init_daemon = __esm({
47919
48075
  init_lock();
47920
48076
  init_scan();
47921
48077
  init_single_flight();
47922
- AGENT_VERSION2 = true ? "agent-0.0.44" : "agent-dev";
48078
+ AGENT_VERSION2 = true ? "agent-0.0.46" : "agent-dev";
47923
48079
  HEARTBEAT_INTERVAL_MS = 1e4;
47924
48080
  SCAN_INTERVAL_MS = 5 * 60 * 1e3;
47925
48081
  DISCOVERY_INTERVAL_MS = 6e4;
@@ -47935,6 +48091,8 @@ var init_daemon = __esm({
47935
48091
  LOCAL_FLUSH_THROTTLE_MS = 400;
47936
48092
  localFlushTimer = null;
47937
48093
  localFlushPending = false;
48094
+ LOG_MAX_BYTES = 64 * 1024 * 1024;
48095
+ LOG_TAIL_KEEP_BYTES = 4 * 1024 * 1024;
47938
48096
  lastStatusPath = null;
47939
48097
  scanRunner = createCoalescingRunner(runScanCycle);
47940
48098
  }
@@ -47946,33 +48104,33 @@ __export(watch_exports, {
47946
48104
  watchForever: () => watchForever
47947
48105
  });
47948
48106
  import { existsSync as existsSync10 } from "fs";
47949
- import { homedir as homedir8, platform as platform3 } from "os";
47950
- import { join as join10 } from "path";
48107
+ import { homedir as homedir9, platform as platform3 } from "os";
48108
+ import { join as join11 } from "path";
47951
48109
  function resolveWatchDirs() {
47952
- const home2 = homedir8();
47953
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join10(home2, ".config");
47954
- const xdgData = process.env.XDG_DATA_HOME ?? join10(home2, ".local/share");
48110
+ const home2 = homedir9();
48111
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join11(home2, ".config");
48112
+ const xdgData = process.env.XDG_DATA_HOME ?? join11(home2, ".local/share");
47955
48113
  const candidates = [
47956
48114
  // universal (default HOME-rooted CLI data dirs)
47957
- join10(home2, ".claude/projects"),
47958
- join10(home2, ".codex/sessions"),
47959
- join10(home2, ".cursor/ai-tracking"),
47960
- join10(home2, ".gemini"),
47961
- join10(home2, ".aider"),
48115
+ join11(home2, ".claude/projects"),
48116
+ join11(home2, ".codex/sessions"),
48117
+ join11(home2, ".cursor/ai-tracking"),
48118
+ join11(home2, ".gemini"),
48119
+ join11(home2, ".aider"),
47962
48120
  // XDG / Linux
47963
- join10(xdgConfig, "claude/projects"),
47964
- join10(xdgConfig, "codex/sessions"),
47965
- join10(xdgConfig, "Cursor/User/workspaceStorage"),
47966
- join10(xdgConfig, "Code/User/workspaceStorage"),
47967
- join10(xdgConfig, "Code - Insiders/User/workspaceStorage"),
47968
- join10(xdgData, "claude/projects"),
48121
+ join11(xdgConfig, "claude/projects"),
48122
+ join11(xdgConfig, "codex/sessions"),
48123
+ join11(xdgConfig, "Cursor/User/workspaceStorage"),
48124
+ join11(xdgConfig, "Code/User/workspaceStorage"),
48125
+ join11(xdgConfig, "Code - Insiders/User/workspaceStorage"),
48126
+ join11(xdgData, "claude/projects"),
47969
48127
  // macOS
47970
48128
  ...platform3() === "darwin" ? [
47971
- join10(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
47972
- join10(home2, "Library/Application Support/Claude"),
47973
- join10(home2, "Library/Application Support/Code/User/workspaceStorage"),
47974
- join10(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
47975
- join10(home2, "Library/Application Support/Zed")
48129
+ join11(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
48130
+ join11(home2, "Library/Application Support/Claude"),
48131
+ join11(home2, "Library/Application Support/Code/User/workspaceStorage"),
48132
+ join11(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
48133
+ join11(home2, "Library/Application Support/Zed")
47976
48134
  ] : []
47977
48135
  ];
47978
48136
  return Array.from(new Set(candidates)).filter((p) => existsSync10(p));
@@ -48037,13 +48195,13 @@ var init_watch = __esm({
48037
48195
 
48038
48196
  // src/cli.ts
48039
48197
  init_src2();
48040
- init_identity();
48041
48198
  init_api();
48042
48199
  init_config2();
48200
+ init_identity();
48043
48201
  init_scan();
48044
48202
  import { spawn } from "child_process";
48045
48203
  import { randomBytes } from "crypto";
48046
- import { platform as platform4, release, arch as cpuArch, hostname as hostname2 } from "os";
48204
+ import { arch as cpuArch, hostname as hostname2, platform as platform4, release } from "os";
48047
48205
  import { createInterface as createInterface3 } from "readline";
48048
48206
 
48049
48207
  // src/service.ts
@@ -48399,6 +48557,59 @@ function trayStatus() {
48399
48557
  return exe ? { installed: true, path: exe.replace(/\/Contents\/MacOS\/modelstat-tray$/, "") } : { installed: false, path: null };
48400
48558
  }
48401
48559
 
48560
+ // src/supervise.ts
48561
+ init_lock();
48562
+ import { readFileSync as readFileSync5 } from "fs";
48563
+ import { homedir as homedir8 } from "os";
48564
+ import { join as join8 } from "path";
48565
+ var STATUS_FRESH_MS = 12e4;
48566
+ var BOOT_GRACE_MS = 9e4;
48567
+ function decideSupervision(input) {
48568
+ const fresh = input.statusFreshMs ?? STATUS_FRESH_MS;
48569
+ const grace = input.bootGraceMs ?? BOOT_GRACE_MS;
48570
+ if (!input.lock || !input.ownerAlive) return "spawn";
48571
+ if (input.myAgentVersion && input.lock.agentVersion !== "unknown" && input.lock.agentVersion !== input.myAgentVersion) {
48572
+ return "replace";
48573
+ }
48574
+ if (input.statusAgeMs !== null && input.statusAgeMs <= fresh) return "adopt";
48575
+ if (input.lockAgeMs !== null && input.lockAgeMs <= grace) return "adopt";
48576
+ return "replace";
48577
+ }
48578
+ function daemonHealth(opts = {}) {
48579
+ const now = opts.now ?? Date.now();
48580
+ const pidAlive = opts.pidAlive ?? isProcessAlive;
48581
+ const statusPath = opts.statusPath ?? join8(homedir8(), ".modelstat", "last-status.json");
48582
+ const lock = opts.lockPath === void 0 ? readDaemonLock() : readDaemonLock(opts.lockPath);
48583
+ const ownerAlive = lock !== null && pidAlive(lock.pid);
48584
+ const lockAgeMs = lock ? ageMs(lock.startedAt, now) : null;
48585
+ let statusAgeMs = null;
48586
+ try {
48587
+ const raw = readFileSync5(statusPath, "utf8");
48588
+ const writtenAt = JSON.parse(raw).written_at;
48589
+ statusAgeMs = writtenAt ? ageMs(writtenAt, now) : null;
48590
+ } catch {
48591
+ statusAgeMs = null;
48592
+ }
48593
+ return {
48594
+ decision: decideSupervision({
48595
+ lock,
48596
+ ownerAlive,
48597
+ lockAgeMs,
48598
+ statusAgeMs,
48599
+ myAgentVersion: opts.myAgentVersion
48600
+ }),
48601
+ lock,
48602
+ ownerAlive,
48603
+ lockAgeMs,
48604
+ statusAgeMs
48605
+ };
48606
+ }
48607
+ function ageMs(iso, now) {
48608
+ const t = Date.parse(iso);
48609
+ if (Number.isNaN(t)) return null;
48610
+ return Math.max(0, now - t);
48611
+ }
48612
+
48402
48613
  // src/cli.ts
48403
48614
  async function confirmPrompt(question, defaultYes) {
48404
48615
  if (process.stdin.isTTY !== true) return defaultYes;
@@ -48429,7 +48640,7 @@ function tryOpenBrowser(url) {
48429
48640
  return false;
48430
48641
  }
48431
48642
  }
48432
- var AGENT_VERSION3 = true ? "agent-0.0.44" : "agent-dev";
48643
+ var AGENT_VERSION3 = true ? "agent-0.0.46" : "agent-dev";
48433
48644
  function osFamily() {
48434
48645
  const p = platform4();
48435
48646
  if (p === "darwin") return "macos";
@@ -48472,10 +48683,8 @@ async function cmdSelfRegister() {
48472
48683
  process.stdout.write(` \x1B[2mgenerated UUIDv7 ${deviceUuid.slice(0, 8)}\u2026\x1B[0m
48473
48684
  `);
48474
48685
  }
48475
- process.stdout.write(
48476
- ` \x1B[2m\u2192 POST ${state.apiUrl}/v1/devices/self-register\x1B[0m
48477
- `
48478
- );
48686
+ process.stdout.write(` \x1B[2m\u2192 POST ${state.apiUrl}/v1/devices/self-register\x1B[0m
48687
+ `);
48479
48688
  const res = await selfRegister({
48480
48689
  device_uuid: deviceUuid,
48481
48690
  fingerprint
@@ -48489,8 +48698,10 @@ async function cmdSelfRegister() {
48489
48698
  });
48490
48699
  process.stdout.write(` \x1B[32m\u2713\x1B[0m registered device_id=${res.device_id}
48491
48700
  `);
48492
- process.stdout.write(` \x1B[32m\u2713\x1B[0m secret ${res.secret_prefix}\u2026 (hashed on server, never re-sent)
48493
- `);
48701
+ process.stdout.write(
48702
+ ` \x1B[32m\u2713\x1B[0m secret ${res.secret_prefix}\u2026 (hashed on server, never re-sent)
48703
+ `
48704
+ );
48494
48705
  process.stdout.write(` \x1B[32m\u2713\x1B[0m claim code ${res.claim_code}
48495
48706
  `);
48496
48707
  }
@@ -48523,10 +48734,8 @@ async function cmdAwaitClaim() {
48523
48734
  }
48524
48735
  function emitEvent(opts, event, fields = {}) {
48525
48736
  if (!opts.json) return;
48526
- process.stdout.write(
48527
- `${JSON.stringify({ v: 1, ts: Date.now(), event, ...fields })}
48528
- `
48529
- );
48737
+ process.stdout.write(`${JSON.stringify({ v: 1, ts: Date.now(), event, ...fields })}
48738
+ `);
48530
48739
  }
48531
48740
  async function cmdConnect(opts) {
48532
48741
  const step = (msg) => {
@@ -48629,9 +48838,7 @@ async function cmdConnect(opts) {
48629
48838
  error: e.message
48630
48839
  });
48631
48840
  warn(`couldn't prepare summariser model: ${e.message}`);
48632
- warn(
48633
- "the background service will retry the download on its first scan"
48634
- );
48841
+ warn("the background service will retry the download on its first scan");
48635
48842
  }
48636
48843
  step("Installing/refreshing background service so the agent survives reboots");
48637
48844
  let serviceOk = false;
@@ -48647,9 +48854,7 @@ async function cmdConnect(opts) {
48647
48854
  } catch (e) {
48648
48855
  emitEvent(opts, "service_install_failed", { error: e.message });
48649
48856
  warn(`couldn't install service: ${e.message}`);
48650
- warn(
48651
- "the agent will not run in the background \u2014 re-run after fixing the issue"
48652
- );
48857
+ warn("the agent will not run in the background \u2014 re-run after fixing the issue");
48653
48858
  }
48654
48859
  step("Detecting installed AI tools and signed-in accounts");
48655
48860
  let discovered = null;
@@ -48667,9 +48872,7 @@ async function cmdConnect(opts) {
48667
48872
  identities: d.identities.length
48668
48873
  };
48669
48874
  emitEvent(opts, "discovered", discovered);
48670
- ok(
48671
- `${discovered.installations} installs \xB7 ${discovered.identities} accounts`
48672
- );
48875
+ ok(`${discovered.installations} installs \xB7 ${discovered.identities} accounts`);
48673
48876
  } catch (e) {
48674
48877
  emitEvent(opts, "discovery_failed", { error: e.message });
48675
48878
  warn(`couldn't detect accounts: ${e.message}`);
@@ -48765,9 +48968,7 @@ async function cmdRescan() {
48765
48968
  console.log(
48766
48969
  `[modelstat] cursors wiped \u2014 next \`modelstat scan\` (or daemon scan cycle) will re-read every JSONL from the start and re-summarise every session at processing version v${PROCESSING_VERSION2}.`
48767
48970
  );
48768
- console.log(
48769
- " If the daemon is running, kick it with: modelstat stop && modelstat start"
48770
- );
48971
+ console.log(" If the daemon is running, kick it with: modelstat stop && modelstat start");
48771
48972
  }
48772
48973
  async function cmdWatch() {
48773
48974
  const { watchForever: watchForever2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
@@ -48823,10 +49024,10 @@ function fmtTokens(v) {
48823
49024
  }
48824
49025
  async function readLocalStatus() {
48825
49026
  try {
48826
- const { homedir: homedir9 } = await import("os");
48827
- const { join: join11 } = await import("path");
49027
+ const { homedir: homedir10 } = await import("os");
49028
+ const { join: join12 } = await import("path");
48828
49029
  const { readFile } = await import("fs/promises");
48829
- const p = join11(homedir9(), ".modelstat", "last-status.json");
49030
+ const p = join12(homedir10(), ".modelstat", "last-status.json");
48830
49031
  const txt = await readFile(p, "utf8");
48831
49032
  return JSON.parse(txt);
48832
49033
  } catch {
@@ -48869,7 +49070,9 @@ async function cmdStats(args) {
48869
49070
  console.log("device is claimed \u2014 live stats available at:");
48870
49071
  console.log(` ${dashboard}`);
48871
49072
  if (local) {
48872
- console.log(`local agent: ${local.status ?? "?"}${local.message ? ` \xB7 ${local.message}` : ""}`);
49073
+ console.log(
49074
+ `local agent: ${local.status ?? "?"}${local.message ? ` \xB7 ${local.message}` : ""}`
49075
+ );
48873
49076
  const stats = local.stats ?? {};
48874
49077
  for (const [k, v] of Object.entries(stats)) console.log(` ${k}: ${v}`);
48875
49078
  }
@@ -48887,7 +49090,9 @@ async function cmdStats(args) {
48887
49090
  console.log(
48888
49091
  `status: ${view.device.agent_status ?? "(unknown)"}${view.device.last_seen_at ? ` \xB7 last seen ${view.device.last_seen_at}` : ""}`
48889
49092
  );
48890
- console.log(`claim: ${view.status}${view.status === "unclaimed" ? ` (at ${view.claim_url})` : ""}`);
49093
+ console.log(
49094
+ `claim: ${view.status}${view.status === "unclaimed" ? ` (at ${view.claim_url})` : ""}`
49095
+ );
48891
49096
  console.log(`sessions: ${fmtInt(view.analyzed.count)}`);
48892
49097
  console.log(`tokens: ${fmtTokens(view.analyzed.totalTokens)}`);
48893
49098
  console.log(`cost: ${fmtCost(view.analyzed.totalCostUsd)}`);
@@ -48912,10 +49117,8 @@ async function cmdJobs(args) {
48912
49117
  if (!jobs && !ledger) {
48913
49118
  const dashboard = `${state.apiUrl.replace(/\/$/, "")}/dashboard/jobs`;
48914
49119
  if (asJson) {
48915
- process.stdout.write(
48916
- `${JSON.stringify({ paired: true, claimed: true, dashboard })}
48917
- `
48918
- );
49120
+ process.stdout.write(`${JSON.stringify({ paired: true, claimed: true, dashboard })}
49121
+ `);
48919
49122
  } else {
48920
49123
  console.log("device is claimed \u2014 job queue at:");
48921
49124
  console.log(` ${dashboard}`);
@@ -48959,6 +49162,22 @@ function cmdPaths(args) {
48959
49162
  console.log(`${k.padEnd(8)} ${String(v)}`);
48960
49163
  }
48961
49164
  }
49165
+ function cmdToken(args) {
49166
+ if (!state.bearer) {
49167
+ console.error("not paired \u2014 run `npx modelstat@latest` first");
49168
+ process.exit(1);
49169
+ }
49170
+ if (args.includes("--json")) {
49171
+ process.stdout.write(`${JSON.stringify({ token: state.bearer, api: state.apiUrl })}
49172
+ `);
49173
+ return;
49174
+ }
49175
+ process.stdout.write(`${state.bearer}
49176
+ `);
49177
+ if (process.stderr.isTTY) {
49178
+ console.error("(device token \u2014 treat it like a password; rotate via the dashboard if leaked)");
49179
+ }
49180
+ }
48962
49181
  function parseConnectOpts(argv) {
48963
49182
  return {
48964
49183
  json: argv.includes("--json"),
@@ -48989,6 +49208,14 @@ async function main() {
48989
49208
  console.log(`\u2713 runtime staged at ${dest}`);
48990
49209
  return;
48991
49210
  }
49211
+ case "_daemon-health": {
49212
+ try {
49213
+ console.log(JSON.stringify(daemonHealth({ myAgentVersion: AGENT_VERSION3 })));
49214
+ } catch (e) {
49215
+ console.log(JSON.stringify({ decision: "spawn", error: e.message }));
49216
+ }
49217
+ return;
49218
+ }
48992
49219
  // ── Diagnostics / dev one-shots ────────────────────────────
48993
49220
  case "status":
48994
49221
  return cmdStatus();
@@ -48999,6 +49226,9 @@ async function main() {
48999
49226
  case "paths":
49000
49227
  cmdPaths(rest);
49001
49228
  return;
49229
+ case "token":
49230
+ cmdToken(rest);
49231
+ return;
49002
49232
  case "discover":
49003
49233
  return cmdDiscover();
49004
49234
  case "scan":
@@ -49013,24 +49243,39 @@ async function main() {
49013
49243
  return cmdAwaitClaim();
49014
49244
  default:
49015
49245
  console.log("usage:");
49016
- console.log(" npx modelstat@latest \u2014 install or upgrade. Registers the device, installs the background service, exits.");
49017
- console.log(" flags: --json (NDJSON events on stdout), --no-browser, --fresh, -y");
49018
- console.log(" npx modelstat@latest remove \u2014 stop and uninstall the background service. Keeps your identity.");
49019
- console.log(" npx modelstat@latest reinstall \u2014 alias for the default. Useful when you want to be explicit.");
49246
+ console.log(
49247
+ " npx modelstat@latest \u2014 install or upgrade. Registers the device, installs the background service, exits."
49248
+ );
49249
+ console.log(
49250
+ " flags: --json (NDJSON events on stdout), --no-browser, --fresh, -y"
49251
+ );
49252
+ console.log(
49253
+ " npx modelstat@latest remove \u2014 stop and uninstall the background service. Keeps your identity."
49254
+ );
49255
+ console.log(
49256
+ " npx modelstat@latest reinstall \u2014 alias for the default. Useful when you want to be explicit."
49257
+ );
49020
49258
  console.log();
49021
49259
  console.log("Diagnostics:");
49022
49260
  console.log(" npx modelstat@latest status \u2014 show pairing + service state");
49023
49261
  console.log(" npx modelstat@latest stats \u2014 live device summary: sessions \xB7 tokens \xB7 cost (--json)");
49024
49262
  console.log(" npx modelstat@latest jobs \u2014 pipeline queue + recent processing ledger (--json)");
49025
49263
  console.log(" npx modelstat@latest paths \u2014 print state file + log dir + api URL (--json)");
49264
+ console.log(" npx modelstat@latest token \u2014 print the device token for hosted MCP / API access (--json)");
49026
49265
  console.log();
49027
49266
  console.log("Dev / one-shots:");
49028
49267
  console.log(" npx modelstat@latest scan \u2014 one-shot parse + upload of local JSONL");
49029
- console.log(" npx modelstat@latest rescan \u2014 wipe file cursors so the next scan re-reads & re-summarises everything");
49030
- console.log(" npx modelstat@latest watch \u2014 continuous (chokidar) with periodic backstop");
49268
+ console.log(
49269
+ " npx modelstat@latest rescan \u2014 wipe file cursors so the next scan re-reads & re-summarises everything"
49270
+ );
49271
+ console.log(
49272
+ " npx modelstat@latest watch \u2014 continuous (chokidar) with periodic backstop"
49273
+ );
49031
49274
  console.log(" npx modelstat@latest discover \u2014 one-shot report of installs/identities");
49032
49275
  console.log();
49033
- console.log("Internal (called by launchd/systemd, not by humans): _daemon, start, self-register, await-claim");
49276
+ console.log(
49277
+ "Internal (called by launchd/systemd, not by humans): _daemon, start, self-register, await-claim"
49278
+ );
49034
49279
  process.exit(1);
49035
49280
  }
49036
49281
  }