modelstat 0.0.28 → 0.0.30
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 +100 -44
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/vendor/tray-mac/Sources/ModelstatTray/main.swift +115 -18
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
|
@@ -47031,6 +47031,26 @@ async function sendHeartbeat() {
|
|
|
47031
47031
|
}
|
|
47032
47032
|
} catch {
|
|
47033
47033
|
}
|
|
47034
|
+
writeLocalStatus(body).catch(() => void 0);
|
|
47035
|
+
}
|
|
47036
|
+
async function writeLocalStatus(snapshot) {
|
|
47037
|
+
const { homedir: homedir9 } = await import("os");
|
|
47038
|
+
const { join: join11 } = await import("path");
|
|
47039
|
+
const { writeFile, mkdir: mkdir2, rename } = await import("fs/promises");
|
|
47040
|
+
if (!lastStatusPath) {
|
|
47041
|
+
const dir = join11(homedir9(), ".modelstat");
|
|
47042
|
+
try {
|
|
47043
|
+
await mkdir2(dir, { recursive: true });
|
|
47044
|
+
} catch {
|
|
47045
|
+
}
|
|
47046
|
+
lastStatusPath = join11(dir, "last-status.json");
|
|
47047
|
+
}
|
|
47048
|
+
const tmp = `${lastStatusPath}.tmp`;
|
|
47049
|
+
try {
|
|
47050
|
+
await writeFile(tmp, JSON.stringify({ ...snapshot, written_at: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
47051
|
+
await rename(tmp, lastStatusPath);
|
|
47052
|
+
} catch {
|
|
47053
|
+
}
|
|
47034
47054
|
}
|
|
47035
47055
|
async function runDiscovery() {
|
|
47036
47056
|
const deviceId = state.deviceId;
|
|
@@ -47080,7 +47100,7 @@ function basename3(p) {
|
|
|
47080
47100
|
}
|
|
47081
47101
|
async function runDaemon(opts = {}) {
|
|
47082
47102
|
if (!state.bearer || !state.deviceId) {
|
|
47083
|
-
throw new Error("not enrolled \u2014 run `modelstat
|
|
47103
|
+
throw new Error("not enrolled \u2014 run `npx modelstat@latest` first");
|
|
47084
47104
|
}
|
|
47085
47105
|
const lock = acquireDaemonLock({
|
|
47086
47106
|
agentVersion: AGENT_VERSION2,
|
|
@@ -47174,7 +47194,7 @@ async function runDaemon(opts = {}) {
|
|
|
47174
47194
|
await new Promise(() => {
|
|
47175
47195
|
});
|
|
47176
47196
|
}
|
|
47177
|
-
var import_undici2, AGENT_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, status;
|
|
47197
|
+
var import_undici2, AGENT_VERSION2, HEARTBEAT_INTERVAL_MS, SCAN_INTERVAL_MS, status, lastStatusPath;
|
|
47178
47198
|
var init_daemon = __esm({
|
|
47179
47199
|
"src/daemon.ts"() {
|
|
47180
47200
|
"use strict";
|
|
@@ -47214,6 +47234,7 @@ var init_daemon = __esm({
|
|
|
47214
47234
|
stats: {},
|
|
47215
47235
|
lastEventAt: null
|
|
47216
47236
|
};
|
|
47237
|
+
lastStatusPath = null;
|
|
47217
47238
|
}
|
|
47218
47239
|
});
|
|
47219
47240
|
|
|
@@ -47830,7 +47851,7 @@ async function cmdConnect(opts) {
|
|
|
47830
47851
|
"the background service will retry the download on its first scan"
|
|
47831
47852
|
);
|
|
47832
47853
|
}
|
|
47833
|
-
step("Installing background service so the agent survives reboots");
|
|
47854
|
+
step("Installing/refreshing background service so the agent survives reboots");
|
|
47834
47855
|
let serviceOk = false;
|
|
47835
47856
|
try {
|
|
47836
47857
|
const svc = installService();
|
|
@@ -47844,7 +47865,9 @@ async function cmdConnect(opts) {
|
|
|
47844
47865
|
} catch (e) {
|
|
47845
47866
|
emitEvent(opts, "service_install_failed", { error: e.message });
|
|
47846
47867
|
warn(`couldn't install service: ${e.message}`);
|
|
47847
|
-
warn(
|
|
47868
|
+
warn(
|
|
47869
|
+
"the agent will not run in the background \u2014 re-run after fixing the issue"
|
|
47870
|
+
);
|
|
47848
47871
|
}
|
|
47849
47872
|
if (!opts.json) {
|
|
47850
47873
|
const tray = trayStatus();
|
|
@@ -47987,15 +48010,34 @@ function fmtTokens(v) {
|
|
|
47987
48010
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
47988
48011
|
return String(n);
|
|
47989
48012
|
}
|
|
48013
|
+
async function readLocalStatus() {
|
|
48014
|
+
try {
|
|
48015
|
+
const { homedir: homedir9 } = await import("os");
|
|
48016
|
+
const { join: join11 } = await import("path");
|
|
48017
|
+
const { readFile } = await import("fs/promises");
|
|
48018
|
+
const p = join11(homedir9(), ".modelstat", "last-status.json");
|
|
48019
|
+
const txt = await readFile(p, "utf8");
|
|
48020
|
+
return JSON.parse(txt);
|
|
48021
|
+
} catch {
|
|
48022
|
+
return null;
|
|
48023
|
+
}
|
|
48024
|
+
}
|
|
47990
48025
|
async function cmdStats(args) {
|
|
47991
48026
|
const asJson = args.includes("--json");
|
|
47992
48027
|
const claim = state.claimCode;
|
|
48028
|
+
const local = await readLocalStatus();
|
|
47993
48029
|
if (!claim) {
|
|
47994
48030
|
if (asJson) {
|
|
47995
|
-
process.stdout.write(
|
|
47996
|
-
|
|
48031
|
+
process.stdout.write(
|
|
48032
|
+
`${JSON.stringify({
|
|
48033
|
+
paired: false,
|
|
48034
|
+
reason: "no_claim_code",
|
|
48035
|
+
local
|
|
48036
|
+
})}
|
|
48037
|
+
`
|
|
48038
|
+
);
|
|
47997
48039
|
} else {
|
|
47998
|
-
console.log("no claim code on record \u2014 run `modelstat
|
|
48040
|
+
console.log("no claim code on record \u2014 run `npx modelstat@latest` first");
|
|
47999
48041
|
}
|
|
48000
48042
|
return;
|
|
48001
48043
|
}
|
|
@@ -48004,17 +48046,27 @@ async function cmdStats(args) {
|
|
|
48004
48046
|
const dashboard = `${state.apiUrl.replace(/\/$/, "")}/dashboard`;
|
|
48005
48047
|
if (asJson) {
|
|
48006
48048
|
process.stdout.write(
|
|
48007
|
-
`${JSON.stringify({
|
|
48049
|
+
`${JSON.stringify({
|
|
48050
|
+
paired: true,
|
|
48051
|
+
claimed: true,
|
|
48052
|
+
dashboard,
|
|
48053
|
+
local
|
|
48054
|
+
})}
|
|
48008
48055
|
`
|
|
48009
48056
|
);
|
|
48010
48057
|
} else {
|
|
48011
48058
|
console.log("device is claimed \u2014 live stats available at:");
|
|
48012
48059
|
console.log(` ${dashboard}`);
|
|
48060
|
+
if (local) {
|
|
48061
|
+
console.log(`local agent: ${local.status ?? "?"}${local.message ? ` \xB7 ${local.message}` : ""}`);
|
|
48062
|
+
const stats = local.stats ?? {};
|
|
48063
|
+
for (const [k, v] of Object.entries(stats)) console.log(` ${k}: ${v}`);
|
|
48064
|
+
}
|
|
48013
48065
|
}
|
|
48014
48066
|
return;
|
|
48015
48067
|
}
|
|
48016
48068
|
if (asJson) {
|
|
48017
|
-
process.stdout.write(`${JSON.stringify(view)}
|
|
48069
|
+
process.stdout.write(`${JSON.stringify({ ...view, local })}
|
|
48018
48070
|
`);
|
|
48019
48071
|
return;
|
|
48020
48072
|
}
|
|
@@ -48109,27 +48161,19 @@ async function main() {
|
|
|
48109
48161
|
const rest = process.argv.slice(3);
|
|
48110
48162
|
switch (cmd) {
|
|
48111
48163
|
case void 0:
|
|
48112
|
-
case "start":
|
|
48113
|
-
if (!state.bearer || !state.deviceId)
|
|
48114
|
-
return cmdConnect(parseConnectOpts(rest));
|
|
48115
|
-
return cmdStart(rest);
|
|
48116
48164
|
case "connect":
|
|
48117
48165
|
return cmdConnect(parseConnectOpts(rest));
|
|
48118
|
-
case "
|
|
48119
|
-
return cmdSelfRegister();
|
|
48120
|
-
case "await-claim":
|
|
48121
|
-
return cmdAwaitClaim();
|
|
48122
|
-
case "discover":
|
|
48123
|
-
return cmdDiscover();
|
|
48124
|
-
case "scan":
|
|
48125
|
-
return cmdScan();
|
|
48126
|
-
case "rescan":
|
|
48127
|
-
return cmdRescan();
|
|
48128
|
-
case "watch":
|
|
48129
|
-
return cmdWatch();
|
|
48130
|
-
case "stop":
|
|
48166
|
+
case "remove":
|
|
48131
48167
|
case "uninstall":
|
|
48168
|
+
case "stop":
|
|
48132
48169
|
return cmdStop();
|
|
48170
|
+
case "reinstall":
|
|
48171
|
+
return cmdConnect(parseConnectOpts(rest));
|
|
48172
|
+
// ── Internal / service entry point ─────────────────────────
|
|
48173
|
+
case "_daemon":
|
|
48174
|
+
case "start":
|
|
48175
|
+
return cmdStart(rest);
|
|
48176
|
+
// ── Diagnostics / dev one-shots ────────────────────────────
|
|
48133
48177
|
case "status":
|
|
48134
48178
|
return cmdStatus();
|
|
48135
48179
|
case "stats":
|
|
@@ -48139,26 +48183,38 @@ async function main() {
|
|
|
48139
48183
|
case "paths":
|
|
48140
48184
|
cmdPaths(rest);
|
|
48141
48185
|
return;
|
|
48186
|
+
case "discover":
|
|
48187
|
+
return cmdDiscover();
|
|
48188
|
+
case "scan":
|
|
48189
|
+
return cmdScan();
|
|
48190
|
+
case "rescan":
|
|
48191
|
+
return cmdRescan();
|
|
48192
|
+
case "watch":
|
|
48193
|
+
return cmdWatch();
|
|
48194
|
+
case "self-register":
|
|
48195
|
+
return cmdSelfRegister();
|
|
48196
|
+
case "await-claim":
|
|
48197
|
+
return cmdAwaitClaim();
|
|
48142
48198
|
default:
|
|
48143
|
-
console.log(
|
|
48144
|
-
|
|
48145
|
-
);
|
|
48199
|
+
console.log("usage:");
|
|
48200
|
+
console.log(" npx modelstat@latest \u2014 install or upgrade. Registers the device, installs the background service, exits.");
|
|
48201
|
+
console.log(" flags: --json (NDJSON events on stdout), --no-browser, --fresh, -y");
|
|
48202
|
+
console.log(" npx modelstat@latest remove \u2014 stop and uninstall the background service. Keeps your identity.");
|
|
48203
|
+
console.log(" npx modelstat@latest reinstall \u2014 alias for the default. Useful when you want to be explicit.");
|
|
48204
|
+
console.log();
|
|
48205
|
+
console.log("Diagnostics:");
|
|
48206
|
+
console.log(" npx modelstat@latest status \u2014 show pairing + service state");
|
|
48207
|
+
console.log(" npx modelstat@latest stats \u2014 live device summary: sessions \xB7 tokens \xB7 cost (--json)");
|
|
48208
|
+
console.log(" npx modelstat@latest jobs \u2014 pipeline queue + recent processing ledger (--json)");
|
|
48209
|
+
console.log(" npx modelstat@latest paths \u2014 print state file + log dir + api URL (--json)");
|
|
48210
|
+
console.log();
|
|
48211
|
+
console.log("Dev / one-shots:");
|
|
48212
|
+
console.log(" npx modelstat@latest scan \u2014 one-shot parse + upload of local JSONL");
|
|
48213
|
+
console.log(" npx modelstat@latest rescan \u2014 wipe file cursors so the next scan re-reads & re-summarises everything");
|
|
48214
|
+
console.log(" npx modelstat@latest watch \u2014 continuous (chokidar) with periodic backstop");
|
|
48215
|
+
console.log(" npx modelstat@latest discover \u2014 one-shot report of installs/identities");
|
|
48146
48216
|
console.log();
|
|
48147
|
-
console.log("
|
|
48148
|
-
console.log(" connect \u2014 self-register + install the background service; prints /d/:claim_code URL");
|
|
48149
|
-
console.log(" flags: --json (NDJSON events on stdout), --no-browser");
|
|
48150
|
-
console.log(" self-register \u2014 generate UUIDv7 + register; print claim URL and exit");
|
|
48151
|
-
console.log(" await-claim \u2014 block until a human claims this self-registered device");
|
|
48152
|
-
console.log(" status \u2014 show pairing + service state");
|
|
48153
|
-
console.log(" stats \u2014 live device summary: sessions, tokens, cost (use --json)");
|
|
48154
|
-
console.log(" jobs \u2014 pipeline queue + recent processing ledger (use --json)");
|
|
48155
|
-
console.log(" paths \u2014 print state file + log dir + api URL (use --json for machine-readable)");
|
|
48156
|
-
console.log(" stop \u2014 stop and uninstall the background service");
|
|
48157
|
-
console.log(" start \u2014 run the daemon (used by the installed service)");
|
|
48158
|
-
console.log(" discover \u2014 one-shot report of installs/identities");
|
|
48159
|
-
console.log(" scan \u2014 one-shot parse + upload of local JSONL");
|
|
48160
|
-
console.log(" rescan \u2014 wipe file cursors so next scan re-reads & re-summarises everything");
|
|
48161
|
-
console.log(" watch \u2014 continuous (chokidar) with periodic backstop");
|
|
48217
|
+
console.log("Internal (called by launchd/systemd, not by humans): _daemon, start, self-register, await-claim");
|
|
48162
48218
|
process.exit(1);
|
|
48163
48219
|
}
|
|
48164
48220
|
}
|