modelstat 0.0.19 → 0.0.21
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 +234 -48
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -44049,7 +44049,15 @@ async function buildForOneSession(sessionId, events, adapters2) {
|
|
|
44049
44049
|
const turnSurfaces = sorted.map((e) => turnSurface(e));
|
|
44050
44050
|
const turnEmbeddings = [];
|
|
44051
44051
|
for (const s of turnSurfaces) {
|
|
44052
|
-
|
|
44052
|
+
if (s.length === 0) {
|
|
44053
|
+
turnEmbeddings.push([]);
|
|
44054
|
+
continue;
|
|
44055
|
+
}
|
|
44056
|
+
try {
|
|
44057
|
+
turnEmbeddings.push(await adapters2.embed(s));
|
|
44058
|
+
} catch {
|
|
44059
|
+
turnEmbeddings.push([]);
|
|
44060
|
+
}
|
|
44053
44061
|
}
|
|
44054
44062
|
const boundaries = [];
|
|
44055
44063
|
let runStart = 0;
|
|
@@ -44320,27 +44328,62 @@ var init_node2 = __esm({
|
|
|
44320
44328
|
});
|
|
44321
44329
|
|
|
44322
44330
|
// src/pipeline.ts
|
|
44323
|
-
function
|
|
44324
|
-
|
|
44325
|
-
const
|
|
44331
|
+
async function probeOllama(baseUrl) {
|
|
44332
|
+
try {
|
|
44333
|
+
const ctrl = new AbortController();
|
|
44334
|
+
const t = setTimeout(() => ctrl.abort(), 500);
|
|
44335
|
+
const res = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/tags`, {
|
|
44336
|
+
method: "GET",
|
|
44337
|
+
signal: ctrl.signal
|
|
44338
|
+
});
|
|
44339
|
+
clearTimeout(t);
|
|
44340
|
+
return res.ok;
|
|
44341
|
+
} catch {
|
|
44342
|
+
return false;
|
|
44343
|
+
}
|
|
44344
|
+
}
|
|
44345
|
+
function fallbackAdapters() {
|
|
44346
|
+
return {
|
|
44347
|
+
embed: async () => [],
|
|
44348
|
+
// Throw → companion-core/summariseSlice's try/catch falls back to
|
|
44349
|
+
// the `promptFacts` string which is what we want here.
|
|
44350
|
+
summarize: async () => {
|
|
44351
|
+
throw new Error("ollama_unavailable");
|
|
44352
|
+
},
|
|
44353
|
+
tokenize: (text) => Math.max(1, Math.ceil(text.length / 4))
|
|
44354
|
+
};
|
|
44355
|
+
}
|
|
44356
|
+
async function getAdapters() {
|
|
44357
|
+
if (adapters && probed) return adapters;
|
|
44358
|
+
const cfg = defaultOllamaConfig();
|
|
44359
|
+
const up = await probeOllama(cfg.baseUrl);
|
|
44360
|
+
probed = true;
|
|
44361
|
+
if (up) {
|
|
44362
|
+
console.log(`[modelstat] ollama up at ${cfg.baseUrl} \u2014 using LLM pipeline`);
|
|
44326
44363
|
adapters = {
|
|
44327
44364
|
embed: ollamaEmbed(cfg),
|
|
44328
44365
|
summarize: ollamaSummarize(cfg),
|
|
44329
44366
|
tokenize: ollamaTokenize()
|
|
44330
44367
|
};
|
|
44368
|
+
} else {
|
|
44369
|
+
console.log(
|
|
44370
|
+
`[modelstat] ollama not reachable at ${cfg.baseUrl} \u2014 using fallback pipeline (server does classification)`
|
|
44371
|
+
);
|
|
44372
|
+
adapters = fallbackAdapters();
|
|
44331
44373
|
}
|
|
44332
44374
|
return adapters;
|
|
44333
44375
|
}
|
|
44334
44376
|
async function buildSegments(events) {
|
|
44335
|
-
return buildSegmentsForSession(events, getAdapters());
|
|
44377
|
+
return buildSegmentsForSession(events, await getAdapters());
|
|
44336
44378
|
}
|
|
44337
|
-
var adapters;
|
|
44379
|
+
var adapters, probed;
|
|
44338
44380
|
var init_pipeline2 = __esm({
|
|
44339
44381
|
"src/pipeline.ts"() {
|
|
44340
44382
|
"use strict";
|
|
44341
44383
|
init_pipeline();
|
|
44342
44384
|
init_node2();
|
|
44343
44385
|
adapters = null;
|
|
44386
|
+
probed = false;
|
|
44344
44387
|
}
|
|
44345
44388
|
});
|
|
44346
44389
|
|
|
@@ -44406,6 +44449,7 @@ async function scanAll(cb = {}) {
|
|
|
44406
44449
|
let batchesUploaded = 0;
|
|
44407
44450
|
let eventsUploaded = 0;
|
|
44408
44451
|
let buffer = [];
|
|
44452
|
+
let pendingCursors = [];
|
|
44409
44453
|
async function flushBatch() {
|
|
44410
44454
|
if (!buffer.length) return;
|
|
44411
44455
|
cb.onUpload?.(buffer.length);
|
|
@@ -44420,6 +44464,8 @@ async function scanAll(cb = {}) {
|
|
|
44420
44464
|
const res = await uploadBatch(batch);
|
|
44421
44465
|
batchesUploaded += 1;
|
|
44422
44466
|
eventsUploaded += res.accepted;
|
|
44467
|
+
for (const pc of pendingCursors) state.setCursor(pc.path, pc.cs);
|
|
44468
|
+
pendingCursors = [];
|
|
44423
44469
|
buffer = [];
|
|
44424
44470
|
}
|
|
44425
44471
|
for (let i = 0; i < jobs.length; i++) {
|
|
@@ -44440,7 +44486,7 @@ async function scanAll(cb = {}) {
|
|
|
44440
44486
|
if (buffer.length >= BATCH_MAX_EVENTS) await flushBatch();
|
|
44441
44487
|
}
|
|
44442
44488
|
}
|
|
44443
|
-
if (cs)
|
|
44489
|
+
if (cs) pendingCursors.push({ path: job.path, cs });
|
|
44444
44490
|
} catch (e) {
|
|
44445
44491
|
console.warn(` ! parse failed for ${job.path}:`, e.message);
|
|
44446
44492
|
}
|
|
@@ -44457,11 +44503,130 @@ var init_scan = __esm({
|
|
|
44457
44503
|
init_pipeline2();
|
|
44458
44504
|
init_config2();
|
|
44459
44505
|
init_api();
|
|
44460
|
-
AGENT_VERSION = "agent-dev-0.0.
|
|
44506
|
+
AGENT_VERSION = "agent-dev-0.0.21";
|
|
44461
44507
|
BATCH_MAX_EVENTS = 2e3;
|
|
44462
44508
|
}
|
|
44463
44509
|
});
|
|
44464
44510
|
|
|
44511
|
+
// src/lock.ts
|
|
44512
|
+
import {
|
|
44513
|
+
closeSync,
|
|
44514
|
+
existsSync as existsSync5,
|
|
44515
|
+
mkdirSync as mkdirSync2,
|
|
44516
|
+
openSync,
|
|
44517
|
+
readFileSync as readFileSync2,
|
|
44518
|
+
renameSync,
|
|
44519
|
+
unlinkSync as unlinkSync2,
|
|
44520
|
+
writeFileSync as writeFileSync3,
|
|
44521
|
+
writeSync
|
|
44522
|
+
} from "fs";
|
|
44523
|
+
import { homedir as homedir5 } from "os";
|
|
44524
|
+
import { join as join4 } from "path";
|
|
44525
|
+
function isProcessAlive(pid) {
|
|
44526
|
+
if (!pid || pid <= 0) return false;
|
|
44527
|
+
try {
|
|
44528
|
+
process.kill(pid, 0);
|
|
44529
|
+
return true;
|
|
44530
|
+
} catch (e) {
|
|
44531
|
+
const code = e.code;
|
|
44532
|
+
if (code === "EPERM") return true;
|
|
44533
|
+
return false;
|
|
44534
|
+
}
|
|
44535
|
+
}
|
|
44536
|
+
function readLock() {
|
|
44537
|
+
try {
|
|
44538
|
+
const raw = readFileSync2(LOCK_FILE, "utf8");
|
|
44539
|
+
const obj = JSON.parse(raw);
|
|
44540
|
+
if (typeof obj.pid !== "number") return null;
|
|
44541
|
+
return {
|
|
44542
|
+
pid: obj.pid,
|
|
44543
|
+
startedAt: obj.startedAt ?? "unknown",
|
|
44544
|
+
agentVersion: obj.agentVersion ?? "unknown",
|
|
44545
|
+
apiUrl: obj.apiUrl ?? "unknown"
|
|
44546
|
+
};
|
|
44547
|
+
} catch {
|
|
44548
|
+
return null;
|
|
44549
|
+
}
|
|
44550
|
+
}
|
|
44551
|
+
function writeLockAtomic(meta) {
|
|
44552
|
+
mkdirSync2(LOCK_DIR, { recursive: true });
|
|
44553
|
+
const tmp = `${LOCK_FILE}.${meta.pid}.${Date.now()}.tmp`;
|
|
44554
|
+
const fd = openSync(tmp, "wx");
|
|
44555
|
+
try {
|
|
44556
|
+
writeSync(fd, JSON.stringify(meta, null, 2));
|
|
44557
|
+
} finally {
|
|
44558
|
+
closeSync(fd);
|
|
44559
|
+
}
|
|
44560
|
+
renameSync(tmp, LOCK_FILE);
|
|
44561
|
+
}
|
|
44562
|
+
function removeLockIfOwned(ownerPid) {
|
|
44563
|
+
const lock = readLock();
|
|
44564
|
+
if (!lock) return;
|
|
44565
|
+
if (lock.pid !== ownerPid) return;
|
|
44566
|
+
try {
|
|
44567
|
+
unlinkSync2(LOCK_FILE);
|
|
44568
|
+
} catch {
|
|
44569
|
+
}
|
|
44570
|
+
}
|
|
44571
|
+
function acquireDaemonLock(opts) {
|
|
44572
|
+
const existing = readLock();
|
|
44573
|
+
if (existing && isProcessAlive(existing.pid)) {
|
|
44574
|
+
if (!opts.force) {
|
|
44575
|
+
const ageSec = ageInSeconds(existing.startedAt);
|
|
44576
|
+
return { kind: "already_running", owner: existing, ageSec };
|
|
44577
|
+
}
|
|
44578
|
+
try {
|
|
44579
|
+
process.kill(existing.pid, "SIGTERM");
|
|
44580
|
+
} catch {
|
|
44581
|
+
}
|
|
44582
|
+
}
|
|
44583
|
+
const meta = {
|
|
44584
|
+
pid: process.pid,
|
|
44585
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
44586
|
+
agentVersion: opts.agentVersion,
|
|
44587
|
+
apiUrl: opts.apiUrl
|
|
44588
|
+
};
|
|
44589
|
+
writeLockAtomic(meta);
|
|
44590
|
+
const cleanup = () => removeLockIfOwned(process.pid);
|
|
44591
|
+
process.once("beforeExit", cleanup);
|
|
44592
|
+
process.once("SIGINT", () => {
|
|
44593
|
+
cleanup();
|
|
44594
|
+
process.exit(130);
|
|
44595
|
+
});
|
|
44596
|
+
process.once("SIGTERM", () => {
|
|
44597
|
+
cleanup();
|
|
44598
|
+
process.exit(143);
|
|
44599
|
+
});
|
|
44600
|
+
process.once("uncaughtException", (err) => {
|
|
44601
|
+
cleanup();
|
|
44602
|
+
console.error("modelstat daemon crashed:", err);
|
|
44603
|
+
process.exit(1);
|
|
44604
|
+
});
|
|
44605
|
+
return { kind: "acquired" };
|
|
44606
|
+
}
|
|
44607
|
+
function ageInSeconds(iso) {
|
|
44608
|
+
const t = Date.parse(iso);
|
|
44609
|
+
if (Number.isNaN(t)) return -1;
|
|
44610
|
+
return Math.max(0, Math.floor((Date.now() - t) / 1e3));
|
|
44611
|
+
}
|
|
44612
|
+
function formatAge(seconds) {
|
|
44613
|
+
if (seconds < 0) return "?";
|
|
44614
|
+
if (seconds < 60) return `${seconds}s`;
|
|
44615
|
+
const m = Math.floor(seconds / 60);
|
|
44616
|
+
const s = seconds % 60;
|
|
44617
|
+
if (m < 60) return `${m}m ${s}s`;
|
|
44618
|
+
const h = Math.floor(m / 60);
|
|
44619
|
+
return `${h}h ${m % 60}m`;
|
|
44620
|
+
}
|
|
44621
|
+
var LOCK_DIR, LOCK_FILE;
|
|
44622
|
+
var init_lock = __esm({
|
|
44623
|
+
"src/lock.ts"() {
|
|
44624
|
+
"use strict";
|
|
44625
|
+
LOCK_DIR = join4(homedir5(), ".modelstat");
|
|
44626
|
+
LOCK_FILE = join4(LOCK_DIR, "daemon.lock");
|
|
44627
|
+
}
|
|
44628
|
+
});
|
|
44629
|
+
|
|
44465
44630
|
// ../../node_modules/.pnpm/readdirp@4.1.2/node_modules/readdirp/esm/index.js
|
|
44466
44631
|
import { stat as stat3, lstat, readdir as readdir2, realpath } from "fs/promises";
|
|
44467
44632
|
import { Readable as Readable2 } from "stream";
|
|
@@ -46188,7 +46353,7 @@ __export(daemon_exports, {
|
|
|
46188
46353
|
setProgress: () => setProgress,
|
|
46189
46354
|
setQueue: () => setQueue
|
|
46190
46355
|
});
|
|
46191
|
-
import { existsSync as
|
|
46356
|
+
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
46192
46357
|
function setPhase(phase, message) {
|
|
46193
46358
|
status.phase = phase;
|
|
46194
46359
|
status.message = message ?? null;
|
|
@@ -46280,10 +46445,29 @@ async function runScanCycle(reason) {
|
|
|
46280
46445
|
function basename3(p) {
|
|
46281
46446
|
return p.split("/").pop() ?? p;
|
|
46282
46447
|
}
|
|
46283
|
-
async function runDaemon() {
|
|
46448
|
+
async function runDaemon(opts = {}) {
|
|
46284
46449
|
if (!state.bearer || !state.deviceId) {
|
|
46285
46450
|
throw new Error("not enrolled \u2014 run `modelstat connect` first");
|
|
46286
46451
|
}
|
|
46452
|
+
const lock = acquireDaemonLock({
|
|
46453
|
+
agentVersion: AGENT_VERSION2,
|
|
46454
|
+
apiUrl: state.apiUrl,
|
|
46455
|
+
force: opts.force === true
|
|
46456
|
+
});
|
|
46457
|
+
if (lock.kind === "already_running") {
|
|
46458
|
+
console.log(
|
|
46459
|
+
`modelstat daemon is already running \u2014 PID ${lock.owner.pid}, started ${formatAge(
|
|
46460
|
+
lock.ageSec
|
|
46461
|
+
)} ago, agent ${lock.owner.agentVersion}.`
|
|
46462
|
+
);
|
|
46463
|
+
console.log(
|
|
46464
|
+
" \u2192 to stop it: kill " + lock.owner.pid
|
|
46465
|
+
);
|
|
46466
|
+
console.log(
|
|
46467
|
+
" \u2192 to force-replace it: modelstat start --force"
|
|
46468
|
+
);
|
|
46469
|
+
return;
|
|
46470
|
+
}
|
|
46287
46471
|
setPhase("starting", "Booting");
|
|
46288
46472
|
const hb = setInterval(() => void sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
46289
46473
|
hb.unref();
|
|
@@ -46291,21 +46475,21 @@ async function runDaemon() {
|
|
|
46291
46475
|
await runDiscovery();
|
|
46292
46476
|
await runScanCycle("startup");
|
|
46293
46477
|
const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
|
|
46294
|
-
const { homedir:
|
|
46295
|
-
const { join:
|
|
46296
|
-
const home2 =
|
|
46478
|
+
const { homedir: homedir7, platform: platform5 } = await import("os");
|
|
46479
|
+
const { join: join8 } = await import("path");
|
|
46480
|
+
const home2 = homedir7();
|
|
46297
46481
|
const dirs = [
|
|
46298
|
-
|
|
46299
|
-
|
|
46300
|
-
|
|
46301
|
-
|
|
46482
|
+
join8(home2, ".claude/projects"),
|
|
46483
|
+
join8(home2, ".codex/sessions"),
|
|
46484
|
+
join8(home2, ".cursor/ai-tracking"),
|
|
46485
|
+
join8(home2, ".gemini"),
|
|
46302
46486
|
...platform5() === "darwin" ? [
|
|
46303
|
-
|
|
46304
|
-
|
|
46487
|
+
join8(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
|
|
46488
|
+
join8(home2, "Library/Application Support/Claude")
|
|
46305
46489
|
] : [
|
|
46306
|
-
|
|
46490
|
+
join8(home2, ".config/Cursor/User/workspaceStorage")
|
|
46307
46491
|
]
|
|
46308
|
-
].filter((p) =>
|
|
46492
|
+
].filter((p) => existsSync6(p) && statSync2(p).isDirectory());
|
|
46309
46493
|
setPhase("watching", `Watching ${dirs.length} directories`);
|
|
46310
46494
|
const watcher = chokidar.watch(dirs, {
|
|
46311
46495
|
persistent: true,
|
|
@@ -46350,8 +46534,9 @@ var init_daemon = __esm({
|
|
|
46350
46534
|
init_src2();
|
|
46351
46535
|
init_api();
|
|
46352
46536
|
init_config2();
|
|
46537
|
+
init_lock();
|
|
46353
46538
|
init_scan();
|
|
46354
|
-
AGENT_VERSION2 = "agent-dev-0.0.
|
|
46539
|
+
AGENT_VERSION2 = "agent-dev-0.0.21";
|
|
46355
46540
|
HEARTBEAT_INTERVAL_MS = 1e4;
|
|
46356
46541
|
SCAN_INTERVAL_MS = 5 * 60 * 1e3;
|
|
46357
46542
|
status = {
|
|
@@ -46371,37 +46556,37 @@ var watch_exports = {};
|
|
|
46371
46556
|
__export(watch_exports, {
|
|
46372
46557
|
watchForever: () => watchForever
|
|
46373
46558
|
});
|
|
46374
|
-
import { existsSync as
|
|
46375
|
-
import { homedir as
|
|
46376
|
-
import { join as
|
|
46559
|
+
import { existsSync as existsSync7 } from "fs";
|
|
46560
|
+
import { homedir as homedir6, platform as platform3 } from "os";
|
|
46561
|
+
import { join as join7 } from "path";
|
|
46377
46562
|
function resolveWatchDirs() {
|
|
46378
|
-
const home2 =
|
|
46379
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
46380
|
-
const xdgData = process.env.XDG_DATA_HOME ??
|
|
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");
|
|
46381
46566
|
const candidates = [
|
|
46382
46567
|
// universal (default HOME-rooted CLI data dirs)
|
|
46383
|
-
|
|
46384
|
-
|
|
46385
|
-
|
|
46386
|
-
|
|
46387
|
-
|
|
46568
|
+
join7(home2, ".claude/projects"),
|
|
46569
|
+
join7(home2, ".codex/sessions"),
|
|
46570
|
+
join7(home2, ".cursor/ai-tracking"),
|
|
46571
|
+
join7(home2, ".gemini"),
|
|
46572
|
+
join7(home2, ".aider"),
|
|
46388
46573
|
// XDG / Linux
|
|
46389
|
-
|
|
46390
|
-
|
|
46391
|
-
|
|
46392
|
-
|
|
46393
|
-
|
|
46394
|
-
|
|
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"),
|
|
46395
46580
|
// macOS
|
|
46396
46581
|
...platform3() === "darwin" ? [
|
|
46397
|
-
|
|
46398
|
-
|
|
46399
|
-
|
|
46400
|
-
|
|
46401
|
-
|
|
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")
|
|
46402
46587
|
] : []
|
|
46403
46588
|
];
|
|
46404
|
-
return Array.from(new Set(candidates)).filter((p) =>
|
|
46589
|
+
return Array.from(new Set(candidates)).filter((p) => existsSync7(p));
|
|
46405
46590
|
}
|
|
46406
46591
|
async function safeScan(reason) {
|
|
46407
46592
|
if (scanning) {
|
|
@@ -47017,13 +47202,14 @@ async function cmdWatch() {
|
|
|
47017
47202
|
const { watchForever: watchForever2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
|
|
47018
47203
|
await watchForever2();
|
|
47019
47204
|
}
|
|
47020
|
-
async function cmdStart() {
|
|
47205
|
+
async function cmdStart(rest) {
|
|
47021
47206
|
if (!state.bearer || !state.deviceId) {
|
|
47022
47207
|
console.error("not paired yet. Run `modelstat connect` first.");
|
|
47023
47208
|
process.exit(1);
|
|
47024
47209
|
}
|
|
47210
|
+
const force = rest.includes("--force") || rest.includes("-f");
|
|
47025
47211
|
const { runDaemon: runDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
47026
|
-
await runDaemon2();
|
|
47212
|
+
await runDaemon2({ force });
|
|
47027
47213
|
}
|
|
47028
47214
|
async function cmdStop() {
|
|
47029
47215
|
try {
|
|
@@ -47187,7 +47373,7 @@ async function main() {
|
|
|
47187
47373
|
case "start":
|
|
47188
47374
|
if (!state.bearer || !state.deviceId)
|
|
47189
47375
|
return cmdConnect(parseConnectOpts(rest));
|
|
47190
|
-
return cmdStart();
|
|
47376
|
+
return cmdStart(rest);
|
|
47191
47377
|
case "connect":
|
|
47192
47378
|
return cmdConnect(parseConnectOpts(rest));
|
|
47193
47379
|
case "self-register":
|