modelstat 0.0.24 → 0.0.26
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 +483 -121
- package/dist/cli.mjs.map +1 -1
- package/package.json +22 -18
- package/scripts/postinstall.mjs +120 -0
- package/vendor/tray-mac/Sources/ModelstatTray/main.swift +24 -3
- package/vendor/tray-mac/build-app.sh +0 -0
- package/LICENSE +0 -87
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modelstat",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"description": "modelstat companion — reads local AI-tool usage and ships tokenised events to modelstat.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -10,28 +10,43 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
+
"scripts/postinstall.mjs",
|
|
13
14
|
"vendor/tray-mac",
|
|
14
15
|
"README.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev:connect": "tsx ./src/cli.ts connect",
|
|
20
|
+
"dev:scan": "tsx ./src/cli.ts scan",
|
|
21
|
+
"dev:watch": "tsx ./src/cli.ts watch",
|
|
22
|
+
"dev:discover": "tsx ./src/cli.ts discover",
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"build:tray": "bash ./scripts/build-tray.sh",
|
|
25
|
+
"prepack": "pnpm run build && pnpm run build:tray",
|
|
26
|
+
"pack:tarball": "pnpm run build && npm pack --pack-destination ../..",
|
|
27
|
+
"install:local": "bash ./scripts/install-local.sh",
|
|
28
|
+
"postinstall": "node ./scripts/postinstall.mjs",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
17
31
|
"dependencies": {
|
|
18
32
|
"chokidar": "^4.0.3",
|
|
19
33
|
"conf": "^13.1.0",
|
|
20
34
|
"dotenv": "^16.4.7",
|
|
35
|
+
"node-llama-cpp": "^3.18.0",
|
|
21
36
|
"ulid": "^2.3.0",
|
|
22
37
|
"undici": "^7.1.0"
|
|
23
38
|
},
|
|
24
39
|
"devDependencies": {
|
|
40
|
+
"@modelstat/companion-core": "workspace:*",
|
|
41
|
+
"@modelstat/core": "workspace:*",
|
|
42
|
+
"@modelstat/parsers": "workspace:*",
|
|
25
43
|
"@types/node": "^22.10.5",
|
|
26
44
|
"tsup": "^8.3.5",
|
|
27
45
|
"tsx": "^4.19.2",
|
|
28
|
-
"typescript": "^5.7.3"
|
|
29
|
-
"@modelstat/companion-core": "0.0.0",
|
|
30
|
-
"@modelstat/core": "0.0.0",
|
|
31
|
-
"@modelstat/parsers": "0.0.0"
|
|
46
|
+
"typescript": "^5.7.3"
|
|
32
47
|
},
|
|
33
48
|
"engines": {
|
|
34
|
-
"node": ">=20.
|
|
49
|
+
"node": ">=20.18.0"
|
|
35
50
|
},
|
|
36
51
|
"os": [
|
|
37
52
|
"darwin",
|
|
@@ -56,16 +71,5 @@
|
|
|
56
71
|
"type": "git",
|
|
57
72
|
"url": "git+https://github.com/modelstat/modelstat.git",
|
|
58
73
|
"directory": "apps/agent-dev"
|
|
59
|
-
},
|
|
60
|
-
"scripts": {
|
|
61
|
-
"dev:connect": "tsx ./src/cli.ts connect",
|
|
62
|
-
"dev:scan": "tsx ./src/cli.ts scan",
|
|
63
|
-
"dev:watch": "tsx ./src/cli.ts watch",
|
|
64
|
-
"dev:discover": "tsx ./src/cli.ts discover",
|
|
65
|
-
"build": "tsup",
|
|
66
|
-
"build:tray": "bash ./scripts/build-tray.sh",
|
|
67
|
-
"pack:tarball": "pnpm run build && npm pack --pack-destination ../..",
|
|
68
|
-
"install:local": "bash ./scripts/install-local.sh",
|
|
69
|
-
"typecheck": "tsc --noEmit"
|
|
70
74
|
}
|
|
71
|
-
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Post-install hook for the @modelstat/agent CLI.
|
|
4
|
+
*
|
|
5
|
+
* Runs after `npm install -g modelstat` (or `npx modelstat` cache
|
|
6
|
+
* population) and downloads the bundled summariser GGUF (~2.7 GB)
|
|
7
|
+
* with visible progress, so the user sees the heavy download
|
|
8
|
+
* attached to the install they just kicked off — not as a surprise
|
|
9
|
+
* 7-minute hang the first time they run `modelstat scan`.
|
|
10
|
+
*
|
|
11
|
+
* Skipped when:
|
|
12
|
+
* - Running inside this repo's pnpm workspace (we don't want every
|
|
13
|
+
* dev `pnpm install` to pull 2.7 GB; the developer can opt in by
|
|
14
|
+
* running `modelstat connect` once or pre-pulling the model).
|
|
15
|
+
* - `MODELSTAT_SKIP_POSTINSTALL=1` is set (CI escape hatch, or for
|
|
16
|
+
* packagers who handle the model out-of-band).
|
|
17
|
+
* - `CI=true` is set (most CI providers; avoids blocking automated
|
|
18
|
+
* installs on a multi-GB pull).
|
|
19
|
+
* - stdout isn't a TTY AND CI isn't set (likely a scripted
|
|
20
|
+
* install that doesn't want long-running side-effects).
|
|
21
|
+
*
|
|
22
|
+
* In all skip cases the model still downloads lazily on first
|
|
23
|
+
* `modelstat scan` — the agent always preflights the summariser
|
|
24
|
+
* before producing any segments (see src/pipeline.ts), so users in
|
|
25
|
+
* skip-mode just see the download then, instead of now.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { existsSync } from "node:fs";
|
|
29
|
+
import { dirname, join, resolve } from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
|
|
32
|
+
function inThisMonorepo() {
|
|
33
|
+
// Walk up from this script looking for the repo's pnpm-workspace.yaml.
|
|
34
|
+
// When npm installs us into a global node_modules, this file isn't an
|
|
35
|
+
// ancestor — so we know we're in a real install and should download.
|
|
36
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
for (let i = 0; i < 10; i++) {
|
|
38
|
+
if (existsSync(join(dir, "pnpm-workspace.yaml"))) return true;
|
|
39
|
+
const up = resolve(dir, "..");
|
|
40
|
+
if (up === dir) return false;
|
|
41
|
+
dir = up;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function main() {
|
|
47
|
+
if (process.env.MODELSTAT_SKIP_POSTINSTALL === "1") {
|
|
48
|
+
console.log(
|
|
49
|
+
"[modelstat] postinstall skipped (MODELSTAT_SKIP_POSTINSTALL=1) — model downloads lazily on first scan",
|
|
50
|
+
);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (process.env.CI === "true" || process.env.CI === "1") {
|
|
54
|
+
console.log(
|
|
55
|
+
"[modelstat] postinstall skipped (CI=1) — model downloads lazily on first scan",
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (inThisMonorepo()) {
|
|
60
|
+
// Dev environment — don't pull 2.7 GB on every workspace install.
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!process.stdout.isTTY) {
|
|
64
|
+
console.log(
|
|
65
|
+
"[modelstat] postinstall: non-TTY — model will download lazily on first `modelstat scan`",
|
|
66
|
+
);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Resolve the helper from the workspace package the bundle uses.
|
|
71
|
+
// companion-core is bundled into dist/cli.mjs by tsup, but we need
|
|
72
|
+
// the helper as a standalone import here (postinstall runs against
|
|
73
|
+
// the unbundled source layout).
|
|
74
|
+
let ensureLlamaModel;
|
|
75
|
+
let defaultLlamaConfig;
|
|
76
|
+
try {
|
|
77
|
+
({ ensureLlamaModel, defaultLlamaConfig } = await import(
|
|
78
|
+
"@modelstat/companion-core/node"
|
|
79
|
+
));
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`[modelstat] postinstall: couldn't import summariser helper (${err && err.message ? err.message : err}); model will download lazily on first scan`,
|
|
83
|
+
);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log("");
|
|
88
|
+
console.log("━".repeat(60));
|
|
89
|
+
console.log(" Pre-downloading the local summariser model");
|
|
90
|
+
console.log("");
|
|
91
|
+
console.log(" modelstat ships with a small LLM that runs ON YOUR MACHINE");
|
|
92
|
+
console.log(" to summarise every coding session. The model is ~2.7 GB —");
|
|
93
|
+
console.log(" pulling it now so the first `modelstat scan` is instant.");
|
|
94
|
+
console.log("━".repeat(60));
|
|
95
|
+
console.log("");
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await ensureLlamaModel(defaultLlamaConfig());
|
|
99
|
+
console.log("");
|
|
100
|
+
console.log("[modelstat] ✓ summariser ready — run `modelstat connect` next");
|
|
101
|
+
} catch (err) {
|
|
102
|
+
// Don't fail the npm install. The model will retry-download on
|
|
103
|
+
// first scan; better to leave the user with a working binary
|
|
104
|
+
// than to abort because their network blipped.
|
|
105
|
+
console.warn(
|
|
106
|
+
`[modelstat] ⚠ couldn't pre-download summariser (${err && err.message ? err.message : err})`,
|
|
107
|
+
);
|
|
108
|
+
console.warn(
|
|
109
|
+
"[modelstat] the model will download lazily on first `modelstat scan` instead",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main().catch((err) => {
|
|
115
|
+
console.warn(
|
|
116
|
+
`[modelstat] postinstall failed: ${err && err.message ? err.message : err}`,
|
|
117
|
+
);
|
|
118
|
+
// Never fail the install for a postinstall problem.
|
|
119
|
+
process.exit(0);
|
|
120
|
+
});
|
|
@@ -149,12 +149,17 @@ final class TrayController: NSObject {
|
|
|
149
149
|
return
|
|
150
150
|
}
|
|
151
151
|
let p = Process()
|
|
152
|
+
// --force: the tray owns the daemon. If a stale lock from a prior
|
|
153
|
+
// run (crash, kill -9, OS reboot mid-write) is left behind, the
|
|
154
|
+
// unforced `start` exits in <1s with "already running" and the
|
|
155
|
+
// tray's terminationHandler retries it forever. With --force we
|
|
156
|
+
// claim the lock unconditionally and become the live daemon.
|
|
152
157
|
if cli.pathExtension == "mjs" {
|
|
153
158
|
p.launchPath = "/usr/bin/env"
|
|
154
|
-
p.arguments = ["node", cli.path, "start"]
|
|
159
|
+
p.arguments = ["node", cli.path, "start", "--force"]
|
|
155
160
|
} else {
|
|
156
161
|
p.launchPath = cli.path
|
|
157
|
-
p.arguments = ["start"]
|
|
162
|
+
p.arguments = ["start", "--force"]
|
|
158
163
|
}
|
|
159
164
|
// Bolt stdout/stderr onto the same log the launchd plist uses so
|
|
160
165
|
// `modelstat status` still sees the same tail.
|
|
@@ -208,6 +213,10 @@ final class TrayController: NSObject {
|
|
|
208
213
|
do {
|
|
209
214
|
try p.run()
|
|
210
215
|
} catch {
|
|
216
|
+
// Surface the failure in the menu instead of leaving the title
|
|
217
|
+
// stuck on whatever it was last (e.g. "Starting…" forever). Most
|
|
218
|
+
// likely cause is `node` not being on the launchd-inherited PATH.
|
|
219
|
+
statusMI.title = "stats failed: \(error.localizedDescription)"
|
|
211
220
|
return
|
|
212
221
|
}
|
|
213
222
|
// Run on a background queue so we don't block the main loop.
|
|
@@ -336,11 +345,23 @@ final class TrayController: NSObject {
|
|
|
336
345
|
// TrayController is @MainActor, so its init must run on the main
|
|
337
346
|
// actor. We wrap the bootstrap in a main-actor function to satisfy
|
|
338
347
|
// Swift 6's strict concurrency without bloating the controller.
|
|
348
|
+
//
|
|
349
|
+
// IMPORTANT: nothing in AppKit retains TrayController for us. The
|
|
350
|
+
// NSStatusItem holds the menu, and NSMenuItem.target is a weak
|
|
351
|
+
// reference, so the controller has no strong owners. Without a
|
|
352
|
+
// global anchor, ARC deallocates the controller as soon as init
|
|
353
|
+
// returns — which leaves the timer's `[weak self]` callback firing
|
|
354
|
+
// against nil and the menu title frozen on "Starting…" forever.
|
|
355
|
+
// The `controller` global below is the strong reference that keeps
|
|
356
|
+
// the controller alive for the entire app lifetime.
|
|
357
|
+
@MainActor
|
|
358
|
+
private var controller: TrayController?
|
|
359
|
+
|
|
339
360
|
@MainActor
|
|
340
361
|
func bootstrap() {
|
|
341
362
|
let app = NSApplication.shared
|
|
342
363
|
app.setActivationPolicy(.accessory)
|
|
343
|
-
|
|
364
|
+
controller = TrayController()
|
|
344
365
|
app.run()
|
|
345
366
|
}
|
|
346
367
|
|
|
File without changes
|
package/LICENSE
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
Copyright (c) 2026 ModelState Inc
|
|
2
|
-
|
|
3
|
-
Source-Available License
|
|
4
|
-
|
|
5
|
-
1. Grant of Rights
|
|
6
|
-
|
|
7
|
-
Subject to the terms of this License, you are granted a non-exclusive, worldwide,
|
|
8
|
-
non-transferable, non-sublicensable license to:
|
|
9
|
-
|
|
10
|
-
- View, read, and inspect the source code
|
|
11
|
-
- Build, modify, and run the software
|
|
12
|
-
- Use the software internally, including in production, solely to access or interact
|
|
13
|
-
with ModelState Inc’s hosted services
|
|
14
|
-
|
|
15
|
-
2. Permitted Use
|
|
16
|
-
|
|
17
|
-
You may use the software as a client, agent, or self-hosted component that connects
|
|
18
|
-
to and depends on ModelState Inc’s cloud or hosted services.
|
|
19
|
-
|
|
20
|
-
3. Restrictions
|
|
21
|
-
|
|
22
|
-
You may NOT, without explicit prior written permission from ModelState Inc:
|
|
23
|
-
|
|
24
|
-
- Use the software to provide a hosted, managed, or SaaS service to third parties
|
|
25
|
-
- Use the software in any product or service that competes with ModelState Inc
|
|
26
|
-
- Use the software to build or operate an alternative to ModelState Inc’s services
|
|
27
|
-
- Redistribute, sublicense, sell, license, or commercially exploit the software
|
|
28
|
-
- Offer the software (modified or unmodified) as part of a commercial offering
|
|
29
|
-
- Make the software available to third parties as a service
|
|
30
|
-
- Use the software for the benefit of third parties (including multi-tenant or shared environments)
|
|
31
|
-
- Circumvent or attempt to circumvent the limitations of this License
|
|
32
|
-
- Remove or alter any licensing, copyright, or attribution notices
|
|
33
|
-
|
|
34
|
-
4. Definition of Competing Service
|
|
35
|
-
|
|
36
|
-
“Competing Service” means any product or service that provides substantially similar
|
|
37
|
-
functionality to ModelState Inc’s offerings, including but not limited to:
|
|
38
|
-
|
|
39
|
-
- AI or LLM usage tracking, monitoring, or observability systems
|
|
40
|
-
- Model analytics platforms or dashboards
|
|
41
|
-
- Inference tracking, logging, or telemetry pipelines
|
|
42
|
-
- Evaluation, benchmarking, or quality analysis systems for AI/ML models
|
|
43
|
-
- Cost tracking, performance tracking, or optimization systems for model inference
|
|
44
|
-
- Any system that collects, processes, analyzes, or visualizes usage or behavior of AI or machine learning models, including large language models (LLMs), when offered as a product or service
|
|
45
|
-
|
|
46
|
-
5. Internal Use
|
|
47
|
-
|
|
48
|
-
Use of the software is permitted only for your internal business or personal use,
|
|
49
|
-
and not for the benefit of third parties.
|
|
50
|
-
|
|
51
|
-
6. Network Use Restriction
|
|
52
|
-
|
|
53
|
-
You may not use the software to expose APIs, endpoints, dashboards, or services to external
|
|
54
|
-
users except as part of accessing ModelState Inc’s services.
|
|
55
|
-
|
|
56
|
-
7. Data Extraction Restriction
|
|
57
|
-
|
|
58
|
-
You may not use the software to replicate, extract, reverse engineer, or reconstruct
|
|
59
|
-
ModelState Inc’s service behavior, APIs, data models, or system design for the purpose
|
|
60
|
-
of building, improving, or operating a competing system or service.
|
|
61
|
-
|
|
62
|
-
8. Ownership
|
|
63
|
-
|
|
64
|
-
All rights, title, and interest in the software remain exclusively with ModelState Inc.
|
|
65
|
-
The software is licensed, not sold.
|
|
66
|
-
|
|
67
|
-
9. Contributions
|
|
68
|
-
|
|
69
|
-
Unless explicitly agreed otherwise in writing, any contributions submitted to the software
|
|
70
|
-
grant ModelState Inc a perpetual, irrevocable, worldwide, royalty-free license to use,
|
|
71
|
-
modify, and distribute those contributions.
|
|
72
|
-
|
|
73
|
-
10. Termination
|
|
74
|
-
|
|
75
|
-
This License terminates automatically if you violate any of its terms.
|
|
76
|
-
Upon termination, you must immediately cease all use and delete all copies of the software.
|
|
77
|
-
|
|
78
|
-
11. Disclaimer of Warranty
|
|
79
|
-
|
|
80
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
81
|
-
EXPRESS OR IMPLIED.
|
|
82
|
-
|
|
83
|
-
12. Limitation of Liability
|
|
84
|
-
|
|
85
|
-
IN NO EVENT SHALL MODELSTATE INC BE LIABLE FOR ANY CLAIM, DAMAGES,
|
|
86
|
-
OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE
|
|
87
|
-
OR ITS USE OR OTHER DEALINGS IN THE SOFTWARE.
|