modelstat 0.0.20 → 0.0.22
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 +197 -44
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -44503,11 +44516,130 @@ var init_scan = __esm({
|
|
|
44503
44516
|
init_pipeline2();
|
|
44504
44517
|
init_config2();
|
|
44505
44518
|
init_api();
|
|
44506
|
-
AGENT_VERSION = "agent-dev-0.0.
|
|
44519
|
+
AGENT_VERSION = "agent-dev-0.0.22";
|
|
44507
44520
|
BATCH_MAX_EVENTS = 2e3;
|
|
44508
44521
|
}
|
|
44509
44522
|
});
|
|
44510
44523
|
|
|
44524
|
+
// src/lock.ts
|
|
44525
|
+
import {
|
|
44526
|
+
closeSync,
|
|
44527
|
+
existsSync as existsSync5,
|
|
44528
|
+
mkdirSync as mkdirSync2,
|
|
44529
|
+
openSync,
|
|
44530
|
+
readFileSync as readFileSync2,
|
|
44531
|
+
renameSync,
|
|
44532
|
+
unlinkSync as unlinkSync2,
|
|
44533
|
+
writeFileSync as writeFileSync3,
|
|
44534
|
+
writeSync
|
|
44535
|
+
} from "fs";
|
|
44536
|
+
import { homedir as homedir5 } from "os";
|
|
44537
|
+
import { join as join4 } from "path";
|
|
44538
|
+
function isProcessAlive(pid) {
|
|
44539
|
+
if (!pid || pid <= 0) return false;
|
|
44540
|
+
try {
|
|
44541
|
+
process.kill(pid, 0);
|
|
44542
|
+
return true;
|
|
44543
|
+
} catch (e) {
|
|
44544
|
+
const code = e.code;
|
|
44545
|
+
if (code === "EPERM") return true;
|
|
44546
|
+
return false;
|
|
44547
|
+
}
|
|
44548
|
+
}
|
|
44549
|
+
function readLock() {
|
|
44550
|
+
try {
|
|
44551
|
+
const raw = readFileSync2(LOCK_FILE, "utf8");
|
|
44552
|
+
const obj = JSON.parse(raw);
|
|
44553
|
+
if (typeof obj.pid !== "number") return null;
|
|
44554
|
+
return {
|
|
44555
|
+
pid: obj.pid,
|
|
44556
|
+
startedAt: obj.startedAt ?? "unknown",
|
|
44557
|
+
agentVersion: obj.agentVersion ?? "unknown",
|
|
44558
|
+
apiUrl: obj.apiUrl ?? "unknown"
|
|
44559
|
+
};
|
|
44560
|
+
} catch {
|
|
44561
|
+
return null;
|
|
44562
|
+
}
|
|
44563
|
+
}
|
|
44564
|
+
function writeLockAtomic(meta) {
|
|
44565
|
+
mkdirSync2(LOCK_DIR, { recursive: true });
|
|
44566
|
+
const tmp = `${LOCK_FILE}.${meta.pid}.${Date.now()}.tmp`;
|
|
44567
|
+
const fd = openSync(tmp, "wx");
|
|
44568
|
+
try {
|
|
44569
|
+
writeSync(fd, JSON.stringify(meta, null, 2));
|
|
44570
|
+
} finally {
|
|
44571
|
+
closeSync(fd);
|
|
44572
|
+
}
|
|
44573
|
+
renameSync(tmp, LOCK_FILE);
|
|
44574
|
+
}
|
|
44575
|
+
function removeLockIfOwned(ownerPid) {
|
|
44576
|
+
const lock = readLock();
|
|
44577
|
+
if (!lock) return;
|
|
44578
|
+
if (lock.pid !== ownerPid) return;
|
|
44579
|
+
try {
|
|
44580
|
+
unlinkSync2(LOCK_FILE);
|
|
44581
|
+
} catch {
|
|
44582
|
+
}
|
|
44583
|
+
}
|
|
44584
|
+
function acquireDaemonLock(opts) {
|
|
44585
|
+
const existing = readLock();
|
|
44586
|
+
if (existing && isProcessAlive(existing.pid)) {
|
|
44587
|
+
if (!opts.force) {
|
|
44588
|
+
const ageSec = ageInSeconds(existing.startedAt);
|
|
44589
|
+
return { kind: "already_running", owner: existing, ageSec };
|
|
44590
|
+
}
|
|
44591
|
+
try {
|
|
44592
|
+
process.kill(existing.pid, "SIGTERM");
|
|
44593
|
+
} catch {
|
|
44594
|
+
}
|
|
44595
|
+
}
|
|
44596
|
+
const meta = {
|
|
44597
|
+
pid: process.pid,
|
|
44598
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
44599
|
+
agentVersion: opts.agentVersion,
|
|
44600
|
+
apiUrl: opts.apiUrl
|
|
44601
|
+
};
|
|
44602
|
+
writeLockAtomic(meta);
|
|
44603
|
+
const cleanup = () => removeLockIfOwned(process.pid);
|
|
44604
|
+
process.once("beforeExit", cleanup);
|
|
44605
|
+
process.once("SIGINT", () => {
|
|
44606
|
+
cleanup();
|
|
44607
|
+
process.exit(130);
|
|
44608
|
+
});
|
|
44609
|
+
process.once("SIGTERM", () => {
|
|
44610
|
+
cleanup();
|
|
44611
|
+
process.exit(143);
|
|
44612
|
+
});
|
|
44613
|
+
process.once("uncaughtException", (err) => {
|
|
44614
|
+
cleanup();
|
|
44615
|
+
console.error("modelstat daemon crashed:", err);
|
|
44616
|
+
process.exit(1);
|
|
44617
|
+
});
|
|
44618
|
+
return { kind: "acquired" };
|
|
44619
|
+
}
|
|
44620
|
+
function ageInSeconds(iso) {
|
|
44621
|
+
const t = Date.parse(iso);
|
|
44622
|
+
if (Number.isNaN(t)) return -1;
|
|
44623
|
+
return Math.max(0, Math.floor((Date.now() - t) / 1e3));
|
|
44624
|
+
}
|
|
44625
|
+
function formatAge(seconds) {
|
|
44626
|
+
if (seconds < 0) return "?";
|
|
44627
|
+
if (seconds < 60) return `${seconds}s`;
|
|
44628
|
+
const m = Math.floor(seconds / 60);
|
|
44629
|
+
const s = seconds % 60;
|
|
44630
|
+
if (m < 60) return `${m}m ${s}s`;
|
|
44631
|
+
const h = Math.floor(m / 60);
|
|
44632
|
+
return `${h}h ${m % 60}m`;
|
|
44633
|
+
}
|
|
44634
|
+
var LOCK_DIR, LOCK_FILE;
|
|
44635
|
+
var init_lock = __esm({
|
|
44636
|
+
"src/lock.ts"() {
|
|
44637
|
+
"use strict";
|
|
44638
|
+
LOCK_DIR = join4(homedir5(), ".modelstat");
|
|
44639
|
+
LOCK_FILE = join4(LOCK_DIR, "daemon.lock");
|
|
44640
|
+
}
|
|
44641
|
+
});
|
|
44642
|
+
|
|
44511
44643
|
// ../../node_modules/.pnpm/readdirp@4.1.2/node_modules/readdirp/esm/index.js
|
|
44512
44644
|
import { stat as stat3, lstat, readdir as readdir2, realpath } from "fs/promises";
|
|
44513
44645
|
import { Readable as Readable2 } from "stream";
|
|
@@ -46234,7 +46366,7 @@ __export(daemon_exports, {
|
|
|
46234
46366
|
setProgress: () => setProgress,
|
|
46235
46367
|
setQueue: () => setQueue
|
|
46236
46368
|
});
|
|
46237
|
-
import { existsSync as
|
|
46369
|
+
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
46238
46370
|
function setPhase(phase, message) {
|
|
46239
46371
|
status.phase = phase;
|
|
46240
46372
|
status.message = message ?? null;
|
|
@@ -46326,10 +46458,29 @@ async function runScanCycle(reason) {
|
|
|
46326
46458
|
function basename3(p) {
|
|
46327
46459
|
return p.split("/").pop() ?? p;
|
|
46328
46460
|
}
|
|
46329
|
-
async function runDaemon() {
|
|
46461
|
+
async function runDaemon(opts = {}) {
|
|
46330
46462
|
if (!state.bearer || !state.deviceId) {
|
|
46331
46463
|
throw new Error("not enrolled \u2014 run `modelstat connect` first");
|
|
46332
46464
|
}
|
|
46465
|
+
const lock = acquireDaemonLock({
|
|
46466
|
+
agentVersion: AGENT_VERSION2,
|
|
46467
|
+
apiUrl: state.apiUrl,
|
|
46468
|
+
force: opts.force === true
|
|
46469
|
+
});
|
|
46470
|
+
if (lock.kind === "already_running") {
|
|
46471
|
+
console.log(
|
|
46472
|
+
`modelstat daemon is already running \u2014 PID ${lock.owner.pid}, started ${formatAge(
|
|
46473
|
+
lock.ageSec
|
|
46474
|
+
)} ago, agent ${lock.owner.agentVersion}.`
|
|
46475
|
+
);
|
|
46476
|
+
console.log(
|
|
46477
|
+
" \u2192 to stop it: kill " + lock.owner.pid
|
|
46478
|
+
);
|
|
46479
|
+
console.log(
|
|
46480
|
+
" \u2192 to force-replace it: modelstat start --force"
|
|
46481
|
+
);
|
|
46482
|
+
return;
|
|
46483
|
+
}
|
|
46333
46484
|
setPhase("starting", "Booting");
|
|
46334
46485
|
const hb = setInterval(() => void sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
46335
46486
|
hb.unref();
|
|
@@ -46337,21 +46488,21 @@ async function runDaemon() {
|
|
|
46337
46488
|
await runDiscovery();
|
|
46338
46489
|
await runScanCycle("startup");
|
|
46339
46490
|
const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
|
|
46340
|
-
const { homedir:
|
|
46341
|
-
const { join:
|
|
46342
|
-
const home2 =
|
|
46491
|
+
const { homedir: homedir7, platform: platform5 } = await import("os");
|
|
46492
|
+
const { join: join8 } = await import("path");
|
|
46493
|
+
const home2 = homedir7();
|
|
46343
46494
|
const dirs = [
|
|
46344
|
-
|
|
46345
|
-
|
|
46346
|
-
|
|
46347
|
-
|
|
46495
|
+
join8(home2, ".claude/projects"),
|
|
46496
|
+
join8(home2, ".codex/sessions"),
|
|
46497
|
+
join8(home2, ".cursor/ai-tracking"),
|
|
46498
|
+
join8(home2, ".gemini"),
|
|
46348
46499
|
...platform5() === "darwin" ? [
|
|
46349
|
-
|
|
46350
|
-
|
|
46500
|
+
join8(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
|
|
46501
|
+
join8(home2, "Library/Application Support/Claude")
|
|
46351
46502
|
] : [
|
|
46352
|
-
|
|
46503
|
+
join8(home2, ".config/Cursor/User/workspaceStorage")
|
|
46353
46504
|
]
|
|
46354
|
-
].filter((p) =>
|
|
46505
|
+
].filter((p) => existsSync6(p) && statSync2(p).isDirectory());
|
|
46355
46506
|
setPhase("watching", `Watching ${dirs.length} directories`);
|
|
46356
46507
|
const watcher = chokidar.watch(dirs, {
|
|
46357
46508
|
persistent: true,
|
|
@@ -46396,8 +46547,9 @@ var init_daemon = __esm({
|
|
|
46396
46547
|
init_src2();
|
|
46397
46548
|
init_api();
|
|
46398
46549
|
init_config2();
|
|
46550
|
+
init_lock();
|
|
46399
46551
|
init_scan();
|
|
46400
|
-
AGENT_VERSION2 = "agent-dev-0.0.
|
|
46552
|
+
AGENT_VERSION2 = "agent-dev-0.0.22";
|
|
46401
46553
|
HEARTBEAT_INTERVAL_MS = 1e4;
|
|
46402
46554
|
SCAN_INTERVAL_MS = 5 * 60 * 1e3;
|
|
46403
46555
|
status = {
|
|
@@ -46417,37 +46569,37 @@ var watch_exports = {};
|
|
|
46417
46569
|
__export(watch_exports, {
|
|
46418
46570
|
watchForever: () => watchForever
|
|
46419
46571
|
});
|
|
46420
|
-
import { existsSync as
|
|
46421
|
-
import { homedir as
|
|
46422
|
-
import { join as
|
|
46572
|
+
import { existsSync as existsSync7 } from "fs";
|
|
46573
|
+
import { homedir as homedir6, platform as platform3 } from "os";
|
|
46574
|
+
import { join as join7 } from "path";
|
|
46423
46575
|
function resolveWatchDirs() {
|
|
46424
|
-
const home2 =
|
|
46425
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
46426
|
-
const xdgData = process.env.XDG_DATA_HOME ??
|
|
46576
|
+
const home2 = homedir6();
|
|
46577
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join7(home2, ".config");
|
|
46578
|
+
const xdgData = process.env.XDG_DATA_HOME ?? join7(home2, ".local/share");
|
|
46427
46579
|
const candidates = [
|
|
46428
46580
|
// universal (default HOME-rooted CLI data dirs)
|
|
46429
|
-
|
|
46430
|
-
|
|
46431
|
-
|
|
46432
|
-
|
|
46433
|
-
|
|
46581
|
+
join7(home2, ".claude/projects"),
|
|
46582
|
+
join7(home2, ".codex/sessions"),
|
|
46583
|
+
join7(home2, ".cursor/ai-tracking"),
|
|
46584
|
+
join7(home2, ".gemini"),
|
|
46585
|
+
join7(home2, ".aider"),
|
|
46434
46586
|
// XDG / Linux
|
|
46435
|
-
|
|
46436
|
-
|
|
46437
|
-
|
|
46438
|
-
|
|
46439
|
-
|
|
46440
|
-
|
|
46587
|
+
join7(xdgConfig, "claude/projects"),
|
|
46588
|
+
join7(xdgConfig, "codex/sessions"),
|
|
46589
|
+
join7(xdgConfig, "Cursor/User/workspaceStorage"),
|
|
46590
|
+
join7(xdgConfig, "Code/User/workspaceStorage"),
|
|
46591
|
+
join7(xdgConfig, "Code - Insiders/User/workspaceStorage"),
|
|
46592
|
+
join7(xdgData, "claude/projects"),
|
|
46441
46593
|
// macOS
|
|
46442
46594
|
...platform3() === "darwin" ? [
|
|
46443
|
-
|
|
46444
|
-
|
|
46445
|
-
|
|
46446
|
-
|
|
46447
|
-
|
|
46595
|
+
join7(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
|
|
46596
|
+
join7(home2, "Library/Application Support/Claude"),
|
|
46597
|
+
join7(home2, "Library/Application Support/Code/User/workspaceStorage"),
|
|
46598
|
+
join7(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
|
|
46599
|
+
join7(home2, "Library/Application Support/Zed")
|
|
46448
46600
|
] : []
|
|
46449
46601
|
];
|
|
46450
|
-
return Array.from(new Set(candidates)).filter((p) =>
|
|
46602
|
+
return Array.from(new Set(candidates)).filter((p) => existsSync7(p));
|
|
46451
46603
|
}
|
|
46452
46604
|
async function safeScan(reason) {
|
|
46453
46605
|
if (scanning) {
|
|
@@ -47063,13 +47215,14 @@ async function cmdWatch() {
|
|
|
47063
47215
|
const { watchForever: watchForever2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
|
|
47064
47216
|
await watchForever2();
|
|
47065
47217
|
}
|
|
47066
|
-
async function cmdStart() {
|
|
47218
|
+
async function cmdStart(rest) {
|
|
47067
47219
|
if (!state.bearer || !state.deviceId) {
|
|
47068
47220
|
console.error("not paired yet. Run `modelstat connect` first.");
|
|
47069
47221
|
process.exit(1);
|
|
47070
47222
|
}
|
|
47223
|
+
const force = rest.includes("--force") || rest.includes("-f");
|
|
47071
47224
|
const { runDaemon: runDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
47072
|
-
await runDaemon2();
|
|
47225
|
+
await runDaemon2({ force });
|
|
47073
47226
|
}
|
|
47074
47227
|
async function cmdStop() {
|
|
47075
47228
|
try {
|
|
@@ -47233,7 +47386,7 @@ async function main() {
|
|
|
47233
47386
|
case "start":
|
|
47234
47387
|
if (!state.bearer || !state.deviceId)
|
|
47235
47388
|
return cmdConnect(parseConnectOpts(rest));
|
|
47236
|
-
return cmdStart();
|
|
47389
|
+
return cmdStart(rest);
|
|
47237
47390
|
case "connect":
|
|
47238
47391
|
return cmdConnect(parseConnectOpts(rest));
|
|
47239
47392
|
case "self-register":
|