modelstat 0.0.27 → 0.0.29
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/README.md +22 -24
- package/dist/cli.mjs +107 -36
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +91 -1
package/README.md
CHANGED
|
@@ -6,47 +6,45 @@
|
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
One command. Re-running it upgrades you to the newest published version (postinstall stops the running service, swaps the bundle, restarts it).
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
|
|
12
|
+
npx modelstat@latest
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Same one-shot via Bun or pnpm:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
|
|
19
|
-
modelstat
|
|
18
|
+
bunx modelstat@latest
|
|
19
|
+
pnpm dlx modelstat@latest
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
The first run downloads the on-device summariser model (~2.7 GB Qwen3.5-4B GGUF to `~/.modelstat/models/`), pairs the device, and installs a **launchd user agent** on macOS (at `~/Library/LaunchAgents/ai.modelstat.agent.plist`) or a **systemd user unit** on Linux (at `~/.config/systemd/user/modelstat.service`). The daemon starts automatically on login and watches your AI-tool session logs in the background. The CLI exits cleanly — there's no foreground process to keep open.
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
npm install -g @modelstat/agent && modelstat connect
|
|
26
|
-
pnpm add -g @modelstat/agent && modelstat connect
|
|
27
|
-
bun add -g @modelstat/agent && modelstat connect
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
`modelstat connect` installs a **launchd user agent** on macOS (at `~/Library/LaunchAgents/ai.modelstat.agent.plist`) or a **systemd user unit** on Linux (at `~/.config/systemd/user/modelstat.service`). The daemon starts automatically on login.
|
|
31
|
-
|
|
32
|
-
Requires Node 20+ (or skip Node entirely with the Homebrew route). macOS and Linux (x86_64, arm64) supported.
|
|
24
|
+
Requires Node 20+. macOS and Linux (x86_64, arm64) supported.
|
|
33
25
|
|
|
34
26
|
## Commands
|
|
35
27
|
|
|
36
28
|
```bash
|
|
37
|
-
modelstat
|
|
38
|
-
modelstat
|
|
39
|
-
modelstat
|
|
40
|
-
|
|
41
|
-
modelstat
|
|
42
|
-
modelstat
|
|
43
|
-
modelstat
|
|
29
|
+
npx modelstat@latest # install or upgrade. Default action.
|
|
30
|
+
npx modelstat@latest remove # stop and uninstall the background service
|
|
31
|
+
npx modelstat@latest reinstall # alias for the default — explicit form
|
|
32
|
+
|
|
33
|
+
npx modelstat@latest status # show pairing + service state
|
|
34
|
+
npx modelstat@latest stats # live device summary: sessions · tokens · cost
|
|
35
|
+
npx modelstat@latest jobs # pipeline queue + recent processing ledger
|
|
36
|
+
npx modelstat@latest paths [--json] # state file + log dir + API URL
|
|
37
|
+
|
|
38
|
+
npx modelstat@latest scan # one-shot parse + upload of local JSONL
|
|
39
|
+
npx modelstat@latest rescan # wipe file cursors so next scan re-reads & re-summarises everything
|
|
40
|
+
npx modelstat@latest watch # foreground watcher (no service install)
|
|
41
|
+
npx modelstat@latest discover # report detected tool installs + identities
|
|
44
42
|
```
|
|
45
43
|
|
|
46
44
|
**Programmatic pairing** (used by harness skills for OpenClaw, NanoClaw, etc.):
|
|
47
45
|
|
|
48
46
|
```bash
|
|
49
|
-
modelstat
|
|
47
|
+
npx modelstat@latest --json --no-browser
|
|
50
48
|
```
|
|
51
49
|
|
|
52
50
|
Emits one NDJSON event per line. Schema documented at [integrations/harness-skills/modelstat-connect/README.md](https://github.com/modelstat/modelstat/tree/main/integrations/harness-skills/modelstat-connect).
|
|
@@ -72,7 +70,7 @@ To point the agent at your own modelstat API (not the hosted SaaS):
|
|
|
72
70
|
|
|
73
71
|
```bash
|
|
74
72
|
export AGENT_API_URL=https://your-modelstat-api.example.com
|
|
75
|
-
modelstat
|
|
73
|
+
npx modelstat@latest
|
|
76
74
|
```
|
|
77
75
|
|
|
78
76
|
`AGENT_API_URL` can also be set persistently via `.env` or in the systemd/launchd unit.
|
package/dist/cli.mjs
CHANGED
|
@@ -43981,7 +43981,8 @@ var init_config2 = __esm({
|
|
|
43981
43981
|
claimUrl: null,
|
|
43982
43982
|
userEmail: null,
|
|
43983
43983
|
defaultOrgId: null,
|
|
43984
|
-
cursor: {}
|
|
43984
|
+
cursor: {},
|
|
43985
|
+
processingVersion: null
|
|
43985
43986
|
}
|
|
43986
43987
|
});
|
|
43987
43988
|
cachedIdentity = (() => {
|
|
@@ -44073,6 +44074,19 @@ var init_config2 = __esm({
|
|
|
44073
44074
|
map[path5] = v;
|
|
44074
44075
|
store.set("cursor", map);
|
|
44075
44076
|
},
|
|
44077
|
+
/** Drop every per-file cursor so the next scan re-reads every
|
|
44078
|
+
* JSONL from byte 0. Called when the local processing-pipeline
|
|
44079
|
+
* version bumps (see processing-version.ts) or via the
|
|
44080
|
+
* `modelstat rescan` CLI command. */
|
|
44081
|
+
wipeCursors() {
|
|
44082
|
+
store.set("cursor", {});
|
|
44083
|
+
},
|
|
44084
|
+
get processingVersion() {
|
|
44085
|
+
return store.get("processingVersion");
|
|
44086
|
+
},
|
|
44087
|
+
setProcessingVersion(v) {
|
|
44088
|
+
store.set("processingVersion", v);
|
|
44089
|
+
},
|
|
44076
44090
|
get storePath() {
|
|
44077
44091
|
return store.path;
|
|
44078
44092
|
}
|
|
@@ -45220,6 +45234,29 @@ var init_lock = __esm({
|
|
|
45220
45234
|
}
|
|
45221
45235
|
});
|
|
45222
45236
|
|
|
45237
|
+
// src/processing-version.ts
|
|
45238
|
+
var processing_version_exports = {};
|
|
45239
|
+
__export(processing_version_exports, {
|
|
45240
|
+
PROCESSING_VERSION: () => PROCESSING_VERSION,
|
|
45241
|
+
reconcileProcessingVersion: () => reconcileProcessingVersion
|
|
45242
|
+
});
|
|
45243
|
+
function reconcileProcessingVersion(state2) {
|
|
45244
|
+
const stored = state2.processingVersion ?? 1;
|
|
45245
|
+
if (stored >= PROCESSING_VERSION) {
|
|
45246
|
+
return { changed: false, from: stored, to: PROCESSING_VERSION };
|
|
45247
|
+
}
|
|
45248
|
+
state2.wipeCursors();
|
|
45249
|
+
state2.setProcessingVersion(PROCESSING_VERSION);
|
|
45250
|
+
return { changed: true, from: stored, to: PROCESSING_VERSION };
|
|
45251
|
+
}
|
|
45252
|
+
var PROCESSING_VERSION;
|
|
45253
|
+
var init_processing_version = __esm({
|
|
45254
|
+
"src/processing-version.ts"() {
|
|
45255
|
+
"use strict";
|
|
45256
|
+
PROCESSING_VERSION = 2;
|
|
45257
|
+
}
|
|
45258
|
+
});
|
|
45259
|
+
|
|
45223
45260
|
// ../../node_modules/.pnpm/readdirp@4.1.2/node_modules/readdirp/esm/index.js
|
|
45224
45261
|
import { stat as stat3, lstat, readdir as readdir2, realpath } from "fs/promises";
|
|
45225
45262
|
import { Readable as Readable2 } from "stream";
|
|
@@ -47043,7 +47080,7 @@ function basename3(p) {
|
|
|
47043
47080
|
}
|
|
47044
47081
|
async function runDaemon(opts = {}) {
|
|
47045
47082
|
if (!state.bearer || !state.deviceId) {
|
|
47046
|
-
throw new Error("not enrolled \u2014 run `modelstat
|
|
47083
|
+
throw new Error("not enrolled \u2014 run `npx modelstat@latest` first");
|
|
47047
47084
|
}
|
|
47048
47085
|
const lock = acquireDaemonLock({
|
|
47049
47086
|
agentVersion: AGENT_VERSION2,
|
|
@@ -47077,6 +47114,13 @@ async function runDaemon(opts = {}) {
|
|
|
47077
47114
|
setPhase("error", `summariser preflight failed: ${err.message}`);
|
|
47078
47115
|
throw err;
|
|
47079
47116
|
}
|
|
47117
|
+
const { reconcileProcessingVersion: reconcileProcessingVersion2 } = await Promise.resolve().then(() => (init_processing_version(), processing_version_exports));
|
|
47118
|
+
const pv = reconcileProcessingVersion2(state);
|
|
47119
|
+
if (pv.changed) {
|
|
47120
|
+
console.log(
|
|
47121
|
+
`[modelstat] processing pipeline v${pv.from} \u2192 v${pv.to} \u2014 wiped file cursors so every session is re-processed by the new pipeline`
|
|
47122
|
+
);
|
|
47123
|
+
}
|
|
47080
47124
|
await runDiscovery();
|
|
47081
47125
|
await runScanCycle("startup");
|
|
47082
47126
|
const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
|
|
@@ -47786,7 +47830,7 @@ async function cmdConnect(opts) {
|
|
|
47786
47830
|
"the background service will retry the download on its first scan"
|
|
47787
47831
|
);
|
|
47788
47832
|
}
|
|
47789
|
-
step("Installing background service so the agent survives reboots");
|
|
47833
|
+
step("Installing/refreshing background service so the agent survives reboots");
|
|
47790
47834
|
let serviceOk = false;
|
|
47791
47835
|
try {
|
|
47792
47836
|
const svc = installService();
|
|
@@ -47800,7 +47844,9 @@ async function cmdConnect(opts) {
|
|
|
47800
47844
|
} catch (e) {
|
|
47801
47845
|
emitEvent(opts, "service_install_failed", { error: e.message });
|
|
47802
47846
|
warn(`couldn't install service: ${e.message}`);
|
|
47803
|
-
warn(
|
|
47847
|
+
warn(
|
|
47848
|
+
"the agent will not run in the background \u2014 re-run after fixing the issue"
|
|
47849
|
+
);
|
|
47804
47850
|
}
|
|
47805
47851
|
if (!opts.json) {
|
|
47806
47852
|
const tray = trayStatus();
|
|
@@ -47867,12 +47913,30 @@ async function cmdScan() {
|
|
|
47867
47913
|
const { preflightSummariser: preflightSummariser2 } = await Promise.resolve().then(() => (init_pipeline2(), pipeline_exports));
|
|
47868
47914
|
const sample = await preflightSummariser2();
|
|
47869
47915
|
console.log(`[modelstat] summariser preflight ok: "${sample}"`);
|
|
47916
|
+
const { reconcileProcessingVersion: reconcileProcessingVersion2 } = await Promise.resolve().then(() => (init_processing_version(), processing_version_exports));
|
|
47917
|
+
const pv = reconcileProcessingVersion2(state);
|
|
47918
|
+
if (pv.changed) {
|
|
47919
|
+
console.log(
|
|
47920
|
+
`[modelstat] processing pipeline v${pv.from} \u2192 v${pv.to} \u2014 wiped file cursors so every session is re-processed`
|
|
47921
|
+
);
|
|
47922
|
+
}
|
|
47870
47923
|
const r = await scanAll();
|
|
47871
47924
|
console.log();
|
|
47872
47925
|
console.log(
|
|
47873
47926
|
`Done: ${r.filesScanned} files scanned, ${r.filesUnchanged} unchanged, ${r.batchesUploaded} batches, ${r.eventsUploaded} events uploaded`
|
|
47874
47927
|
);
|
|
47875
47928
|
}
|
|
47929
|
+
async function cmdRescan() {
|
|
47930
|
+
const { PROCESSING_VERSION: PROCESSING_VERSION2 } = await Promise.resolve().then(() => (init_processing_version(), processing_version_exports));
|
|
47931
|
+
state.wipeCursors();
|
|
47932
|
+
state.setProcessingVersion(PROCESSING_VERSION2);
|
|
47933
|
+
console.log(
|
|
47934
|
+
`[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}.`
|
|
47935
|
+
);
|
|
47936
|
+
console.log(
|
|
47937
|
+
" If the daemon is running, kick it with: modelstat stop && modelstat start"
|
|
47938
|
+
);
|
|
47939
|
+
}
|
|
47876
47940
|
async function cmdWatch() {
|
|
47877
47941
|
const { watchForever: watchForever2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
|
|
47878
47942
|
await watchForever2();
|
|
@@ -48047,25 +48111,19 @@ async function main() {
|
|
|
48047
48111
|
const rest = process.argv.slice(3);
|
|
48048
48112
|
switch (cmd) {
|
|
48049
48113
|
case void 0:
|
|
48050
|
-
case "start":
|
|
48051
|
-
if (!state.bearer || !state.deviceId)
|
|
48052
|
-
return cmdConnect(parseConnectOpts(rest));
|
|
48053
|
-
return cmdStart(rest);
|
|
48054
48114
|
case "connect":
|
|
48055
48115
|
return cmdConnect(parseConnectOpts(rest));
|
|
48056
|
-
case "
|
|
48057
|
-
return cmdSelfRegister();
|
|
48058
|
-
case "await-claim":
|
|
48059
|
-
return cmdAwaitClaim();
|
|
48060
|
-
case "discover":
|
|
48061
|
-
return cmdDiscover();
|
|
48062
|
-
case "scan":
|
|
48063
|
-
return cmdScan();
|
|
48064
|
-
case "watch":
|
|
48065
|
-
return cmdWatch();
|
|
48066
|
-
case "stop":
|
|
48116
|
+
case "remove":
|
|
48067
48117
|
case "uninstall":
|
|
48118
|
+
case "stop":
|
|
48068
48119
|
return cmdStop();
|
|
48120
|
+
case "reinstall":
|
|
48121
|
+
return cmdConnect(parseConnectOpts(rest));
|
|
48122
|
+
// ── Internal / service entry point ─────────────────────────
|
|
48123
|
+
case "_daemon":
|
|
48124
|
+
case "start":
|
|
48125
|
+
return cmdStart(rest);
|
|
48126
|
+
// ── Diagnostics / dev one-shots ────────────────────────────
|
|
48069
48127
|
case "status":
|
|
48070
48128
|
return cmdStatus();
|
|
48071
48129
|
case "stats":
|
|
@@ -48075,25 +48133,38 @@ async function main() {
|
|
|
48075
48133
|
case "paths":
|
|
48076
48134
|
cmdPaths(rest);
|
|
48077
48135
|
return;
|
|
48136
|
+
case "discover":
|
|
48137
|
+
return cmdDiscover();
|
|
48138
|
+
case "scan":
|
|
48139
|
+
return cmdScan();
|
|
48140
|
+
case "rescan":
|
|
48141
|
+
return cmdRescan();
|
|
48142
|
+
case "watch":
|
|
48143
|
+
return cmdWatch();
|
|
48144
|
+
case "self-register":
|
|
48145
|
+
return cmdSelfRegister();
|
|
48146
|
+
case "await-claim":
|
|
48147
|
+
return cmdAwaitClaim();
|
|
48078
48148
|
default:
|
|
48079
|
-
console.log(
|
|
48080
|
-
|
|
48081
|
-
);
|
|
48149
|
+
console.log("usage:");
|
|
48150
|
+
console.log(" npx modelstat@latest \u2014 install or upgrade. Registers the device, installs the background service, exits.");
|
|
48151
|
+
console.log(" flags: --json (NDJSON events on stdout), --no-browser, --fresh, -y");
|
|
48152
|
+
console.log(" npx modelstat@latest remove \u2014 stop and uninstall the background service. Keeps your identity.");
|
|
48153
|
+
console.log(" npx modelstat@latest reinstall \u2014 alias for the default. Useful when you want to be explicit.");
|
|
48154
|
+
console.log();
|
|
48155
|
+
console.log("Diagnostics:");
|
|
48156
|
+
console.log(" npx modelstat@latest status \u2014 show pairing + service state");
|
|
48157
|
+
console.log(" npx modelstat@latest stats \u2014 live device summary: sessions \xB7 tokens \xB7 cost (--json)");
|
|
48158
|
+
console.log(" npx modelstat@latest jobs \u2014 pipeline queue + recent processing ledger (--json)");
|
|
48159
|
+
console.log(" npx modelstat@latest paths \u2014 print state file + log dir + api URL (--json)");
|
|
48160
|
+
console.log();
|
|
48161
|
+
console.log("Dev / one-shots:");
|
|
48162
|
+
console.log(" npx modelstat@latest scan \u2014 one-shot parse + upload of local JSONL");
|
|
48163
|
+
console.log(" npx modelstat@latest rescan \u2014 wipe file cursors so the next scan re-reads & re-summarises everything");
|
|
48164
|
+
console.log(" npx modelstat@latest watch \u2014 continuous (chokidar) with periodic backstop");
|
|
48165
|
+
console.log(" npx modelstat@latest discover \u2014 one-shot report of installs/identities");
|
|
48082
48166
|
console.log();
|
|
48083
|
-
console.log("
|
|
48084
|
-
console.log(" connect \u2014 self-register + install the background service; prints /d/:claim_code URL");
|
|
48085
|
-
console.log(" flags: --json (NDJSON events on stdout), --no-browser");
|
|
48086
|
-
console.log(" self-register \u2014 generate UUIDv7 + register; print claim URL and exit");
|
|
48087
|
-
console.log(" await-claim \u2014 block until a human claims this self-registered device");
|
|
48088
|
-
console.log(" status \u2014 show pairing + service state");
|
|
48089
|
-
console.log(" stats \u2014 live device summary: sessions, tokens, cost (use --json)");
|
|
48090
|
-
console.log(" jobs \u2014 pipeline queue + recent processing ledger (use --json)");
|
|
48091
|
-
console.log(" paths \u2014 print state file + log dir + api URL (use --json for machine-readable)");
|
|
48092
|
-
console.log(" stop \u2014 stop and uninstall the background service");
|
|
48093
|
-
console.log(" start \u2014 run the daemon (used by the installed service)");
|
|
48094
|
-
console.log(" discover \u2014 one-shot report of installs/identities");
|
|
48095
|
-
console.log(" scan \u2014 one-shot parse + upload of local JSONL");
|
|
48096
|
-
console.log(" watch \u2014 continuous (chokidar) with periodic backstop");
|
|
48167
|
+
console.log("Internal (called by launchd/systemd, not by humans): _daemon, start, self-register, await-claim");
|
|
48097
48168
|
process.exit(1);
|
|
48098
48169
|
}
|
|
48099
48170
|
}
|