modelstat 0.0.50 → 0.0.52
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 +2 -2
- package/dist/cli.mjs +1358 -719
- package/dist/cli.mjs.map +1 -1
- package/package.json +4 -3
- package/vendor/ModelstatTray.app/Contents/Info.plist +42 -0
- package/vendor/ModelstatTray.app/Contents/MacOS/modelstat-tray +0 -0
- package/vendor/ModelstatTray.app/Contents/PkgInfo +1 -0
- package/vendor/ModelstatTray.app/Contents/_CodeSignature/CodeResources +115 -0
- package/vendor/tray-mac/Sources/ModelstatTray/main.swift +15 -6
- package/vendor/tray-mac/build-app.sh +53 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modelstat",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.52",
|
|
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",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"scripts/postinstall.mjs",
|
|
14
|
+
"vendor/ModelstatTray.app",
|
|
14
15
|
"vendor/tray-mac",
|
|
15
16
|
"README.md",
|
|
16
17
|
"LICENSE"
|
|
@@ -28,9 +29,9 @@
|
|
|
28
29
|
"tsup": "^8.3.5",
|
|
29
30
|
"tsx": "^4.19.2",
|
|
30
31
|
"typescript": "^5.7.3",
|
|
31
|
-
"@modelstat/
|
|
32
|
+
"@modelstat/parsers": "0.0.0",
|
|
32
33
|
"@modelstat/core": "0.0.0",
|
|
33
|
-
"@modelstat/
|
|
34
|
+
"@modelstat/companion-core": "0.0.0"
|
|
34
35
|
},
|
|
35
36
|
"engines": {
|
|
36
37
|
"node": ">=20.18.0"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>en</string>
|
|
7
|
+
<key>CFBundleDisplayName</key>
|
|
8
|
+
<string>Modelstat</string>
|
|
9
|
+
<key>CFBundleExecutable</key>
|
|
10
|
+
<string>modelstat-tray</string>
|
|
11
|
+
<key>CFBundleIdentifier</key>
|
|
12
|
+
<string>ai.modelstat.tray</string>
|
|
13
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
14
|
+
<string>6.0</string>
|
|
15
|
+
<key>CFBundleName</key>
|
|
16
|
+
<string>Modelstat</string>
|
|
17
|
+
<key>CFBundlePackageType</key>
|
|
18
|
+
<string>APPL</string>
|
|
19
|
+
<key>CFBundleShortVersionString</key>
|
|
20
|
+
<string>0.1.0</string>
|
|
21
|
+
<key>CFBundleVersion</key>
|
|
22
|
+
<string>1</string>
|
|
23
|
+
<key>LSMinimumSystemVersion</key>
|
|
24
|
+
<string>12.0</string>
|
|
25
|
+
<!-- Agent-style app: no Dock icon, no window at launch, just the
|
|
26
|
+
menu-bar item. -->
|
|
27
|
+
<key>LSUIElement</key>
|
|
28
|
+
<true/>
|
|
29
|
+
<!-- Launch at login when the installer drops us into
|
|
30
|
+
~/Library/LaunchAgents (actually driven by the launchd plist). -->
|
|
31
|
+
<key>LSBackgroundOnly</key>
|
|
32
|
+
<false/>
|
|
33
|
+
<key>NSHighResolutionCapable</key>
|
|
34
|
+
<true/>
|
|
35
|
+
<key>NSSupportsAutomaticTermination</key>
|
|
36
|
+
<false/>
|
|
37
|
+
<key>NSSupportsSuddenTermination</key>
|
|
38
|
+
<true/>
|
|
39
|
+
<key>NSHumanReadableCopyright</key>
|
|
40
|
+
<string>© 2026 modelstat</string>
|
|
41
|
+
</dict>
|
|
42
|
+
</plist>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
APPL????
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>files</key>
|
|
6
|
+
<dict/>
|
|
7
|
+
<key>files2</key>
|
|
8
|
+
<dict/>
|
|
9
|
+
<key>rules</key>
|
|
10
|
+
<dict>
|
|
11
|
+
<key>^Resources/</key>
|
|
12
|
+
<true/>
|
|
13
|
+
<key>^Resources/.*\.lproj/</key>
|
|
14
|
+
<dict>
|
|
15
|
+
<key>optional</key>
|
|
16
|
+
<true/>
|
|
17
|
+
<key>weight</key>
|
|
18
|
+
<real>1000</real>
|
|
19
|
+
</dict>
|
|
20
|
+
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
|
21
|
+
<dict>
|
|
22
|
+
<key>omit</key>
|
|
23
|
+
<true/>
|
|
24
|
+
<key>weight</key>
|
|
25
|
+
<real>1100</real>
|
|
26
|
+
</dict>
|
|
27
|
+
<key>^Resources/Base\.lproj/</key>
|
|
28
|
+
<dict>
|
|
29
|
+
<key>weight</key>
|
|
30
|
+
<real>1010</real>
|
|
31
|
+
</dict>
|
|
32
|
+
<key>^version.plist$</key>
|
|
33
|
+
<true/>
|
|
34
|
+
</dict>
|
|
35
|
+
<key>rules2</key>
|
|
36
|
+
<dict>
|
|
37
|
+
<key>.*\.dSYM($|/)</key>
|
|
38
|
+
<dict>
|
|
39
|
+
<key>weight</key>
|
|
40
|
+
<real>11</real>
|
|
41
|
+
</dict>
|
|
42
|
+
<key>^(.*/)?\.DS_Store$</key>
|
|
43
|
+
<dict>
|
|
44
|
+
<key>omit</key>
|
|
45
|
+
<true/>
|
|
46
|
+
<key>weight</key>
|
|
47
|
+
<real>2000</real>
|
|
48
|
+
</dict>
|
|
49
|
+
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
|
|
50
|
+
<dict>
|
|
51
|
+
<key>nested</key>
|
|
52
|
+
<true/>
|
|
53
|
+
<key>weight</key>
|
|
54
|
+
<real>10</real>
|
|
55
|
+
</dict>
|
|
56
|
+
<key>^.*</key>
|
|
57
|
+
<true/>
|
|
58
|
+
<key>^Info\.plist$</key>
|
|
59
|
+
<dict>
|
|
60
|
+
<key>omit</key>
|
|
61
|
+
<true/>
|
|
62
|
+
<key>weight</key>
|
|
63
|
+
<real>20</real>
|
|
64
|
+
</dict>
|
|
65
|
+
<key>^PkgInfo$</key>
|
|
66
|
+
<dict>
|
|
67
|
+
<key>omit</key>
|
|
68
|
+
<true/>
|
|
69
|
+
<key>weight</key>
|
|
70
|
+
<real>20</real>
|
|
71
|
+
</dict>
|
|
72
|
+
<key>^Resources/</key>
|
|
73
|
+
<dict>
|
|
74
|
+
<key>weight</key>
|
|
75
|
+
<real>20</real>
|
|
76
|
+
</dict>
|
|
77
|
+
<key>^Resources/.*\.lproj/</key>
|
|
78
|
+
<dict>
|
|
79
|
+
<key>optional</key>
|
|
80
|
+
<true/>
|
|
81
|
+
<key>weight</key>
|
|
82
|
+
<real>1000</real>
|
|
83
|
+
</dict>
|
|
84
|
+
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
|
85
|
+
<dict>
|
|
86
|
+
<key>omit</key>
|
|
87
|
+
<true/>
|
|
88
|
+
<key>weight</key>
|
|
89
|
+
<real>1100</real>
|
|
90
|
+
</dict>
|
|
91
|
+
<key>^Resources/Base\.lproj/</key>
|
|
92
|
+
<dict>
|
|
93
|
+
<key>weight</key>
|
|
94
|
+
<real>1010</real>
|
|
95
|
+
</dict>
|
|
96
|
+
<key>^[^/]+$</key>
|
|
97
|
+
<dict>
|
|
98
|
+
<key>nested</key>
|
|
99
|
+
<true/>
|
|
100
|
+
<key>weight</key>
|
|
101
|
+
<real>10</real>
|
|
102
|
+
</dict>
|
|
103
|
+
<key>^embedded\.provisionprofile$</key>
|
|
104
|
+
<dict>
|
|
105
|
+
<key>weight</key>
|
|
106
|
+
<real>20</real>
|
|
107
|
+
</dict>
|
|
108
|
+
<key>^version\.plist$</key>
|
|
109
|
+
<dict>
|
|
110
|
+
<key>weight</key>
|
|
111
|
+
<real>20</real>
|
|
112
|
+
</dict>
|
|
113
|
+
</dict>
|
|
114
|
+
</dict>
|
|
115
|
+
</plist>
|
|
@@ -275,10 +275,14 @@ final class TrayController: NSObject {
|
|
|
275
275
|
}
|
|
276
276
|
guard !ensureInFlight else { return }
|
|
277
277
|
ensureInFlight = true
|
|
278
|
-
superviseQueue.async {
|
|
278
|
+
superviseQueue.async {
|
|
279
279
|
// Probe off-main: the health command boots node (~100-300ms).
|
|
280
280
|
let decision = Self.queryDaemonHealth(cli: cli) ?? "spawn"
|
|
281
|
-
|
|
281
|
+
// Capture self on the closure that actually hops back to @MainActor,
|
|
282
|
+
// not the supervise-queue closure (which only touches statics) — a weak
|
|
283
|
+
// *var* captured across the actor hop is a data race the CI toolchain
|
|
284
|
+
// rejects outright.
|
|
285
|
+
DispatchQueue.main.async { [weak self] in
|
|
282
286
|
MainActor.assumeIsolated {
|
|
283
287
|
guard let self else { return }
|
|
284
288
|
self.ensureInFlight = false
|
|
@@ -343,14 +347,17 @@ final class TrayController: NSObject {
|
|
|
343
347
|
let err = FileHandle(forWritingAtPath: "\(logsDir)/err.log") ?? FileHandle.standardError
|
|
344
348
|
p.standardOutput = out
|
|
345
349
|
p.standardError = err
|
|
346
|
-
p.terminationHandler = {
|
|
350
|
+
p.terminationHandler = { proc in
|
|
347
351
|
// Daemon exited — re-converge via the health check, which adopts
|
|
348
352
|
// a replacement daemon instead of counter-killing it. A clean
|
|
349
353
|
// sub-5s exit means "another daemon owns the lock" (or an equally
|
|
350
354
|
// immediate no-op); skip the hot retry and let the 30s watchdog
|
|
351
355
|
// re-check, so a stale CLI can't put us in a 2s spawn loop.
|
|
352
356
|
let status = proc.terminationStatus
|
|
353
|
-
|
|
357
|
+
// Capture weak self on the @MainActor Task, not the (non-isolated)
|
|
358
|
+
// terminationHandler — capturing the outer weak var across the actor
|
|
359
|
+
// boundary is a data race the CI toolchain rejects.
|
|
360
|
+
Task { @MainActor [weak self] in
|
|
354
361
|
guard let self else { return }
|
|
355
362
|
let uptime = self.daemonSpawnedAt.map { Date().timeIntervalSince($0) } ?? .infinity
|
|
356
363
|
self.daemon = nil
|
|
@@ -442,11 +449,13 @@ final class TrayController: NSObject {
|
|
|
442
449
|
return
|
|
443
450
|
}
|
|
444
451
|
// Run on a background queue so we don't block the main loop.
|
|
445
|
-
DispatchQueue.global(qos: .utility).async {
|
|
452
|
+
DispatchQueue.global(qos: .utility).async {
|
|
446
453
|
p.waitUntilExit()
|
|
447
454
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
448
455
|
let stats = try? JSONDecoder().decode(AgentStats.self, from: data)
|
|
449
|
-
|
|
456
|
+
// Hand self to the main-queue closure (which mutates @MainActor state),
|
|
457
|
+
// not the background closure that only does blocking IO.
|
|
458
|
+
DispatchQueue.main.async { [weak self] in
|
|
450
459
|
self?.latest = stats
|
|
451
460
|
self?.renderStats()
|
|
452
461
|
}
|
|
@@ -2,16 +2,24 @@
|
|
|
2
2
|
# Build ModelstatTray.app from the Swift package.
|
|
3
3
|
#
|
|
4
4
|
# Output:
|
|
5
|
-
#
|
|
6
|
-
# build/ModelstatTray.app (bundle ready to drop into /Applications)
|
|
5
|
+
# build/ModelstatTray.app (ad-hoc-signed bundle, ready to run/ship)
|
|
7
6
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
7
|
+
# Modes (env):
|
|
8
|
+
# TRAY_BUILD_CONFIG=release|debug build config (default: release)
|
|
9
|
+
# TRAY_UNIVERSAL=1 arm64 + x86_64 fat (default: host arch)
|
|
10
|
+
# — universal needs FULL Xcode (xcbuild);
|
|
11
|
+
# host-arch builds on Command Line Tools
|
|
12
|
+
# alone, which is the on-device fallback.
|
|
11
13
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
14
|
+
# Signing: we ad-hoc sign (`codesign -s -`). That is enough to RUN — arm64
|
|
15
|
+
# requires at least an ad-hoc signature, and an app delivered inside the npm
|
|
16
|
+
# tarball is not quarantined, so Gatekeeper's notarization gate never fires.
|
|
17
|
+
# To distribute a DOWNLOADED build (a DMG off the website) without a Gatekeeper
|
|
18
|
+
# prompt, re-sign with a Developer ID cert + Hardened Runtime and notarize:
|
|
19
|
+
# codesign --force --options runtime --timestamp \
|
|
20
|
+
# --sign "Developer ID Application: …" build/ModelstatTray.app
|
|
21
|
+
# xcrun notarytool submit … && xcrun stapler staple build/ModelstatTray.app
|
|
22
|
+
# That is purely additive and slots into CI once the Apple account exists.
|
|
15
23
|
|
|
16
24
|
set -euo pipefail
|
|
17
25
|
|
|
@@ -20,24 +28,52 @@ cd "$HERE"
|
|
|
20
28
|
|
|
21
29
|
APP_NAME="ModelstatTray"
|
|
22
30
|
BUNDLE="build/${APP_NAME}.app"
|
|
31
|
+
CONFIG="${TRAY_BUILD_CONFIG:-release}"
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
BUILD_ARGS=(-c "$CONFIG")
|
|
34
|
+
if [[ "${TRAY_UNIVERSAL:-}" == "1" ]]; then
|
|
35
|
+
BUILD_ARGS+=(--arch arm64 --arch x86_64)
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
echo "▶ swift build ${BUILD_ARGS[*]}"
|
|
39
|
+
swift build "${BUILD_ARGS[@]}"
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
# Locate the produced binary. Universal builds land under
|
|
42
|
+
# .build/apple/Products/<Config>/; single-arch under .build/<config>/ (a
|
|
43
|
+
# triple-prefixed dir that SwiftPM also symlinks as .build/<config>).
|
|
44
|
+
BIN=""
|
|
45
|
+
for cand in \
|
|
46
|
+
".build/apple/Products/Release/modelstat-tray" \
|
|
47
|
+
".build/apple/Products/Debug/modelstat-tray" \
|
|
48
|
+
".build/${CONFIG}/modelstat-tray" \
|
|
49
|
+
".build/release/modelstat-tray" \
|
|
50
|
+
".build/debug/modelstat-tray"; do
|
|
51
|
+
if [[ -f "$cand" ]]; then BIN="$cand"; break; fi
|
|
52
|
+
done
|
|
53
|
+
if [[ -z "$BIN" ]]; then
|
|
54
|
+
echo "✗ could not find a built modelstat-tray binary under .build/" >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
echo "▶ binary: $BIN"
|
|
58
|
+
lipo -archs "$BIN" 2>/dev/null | sed 's/^/ arches: /' || true
|
|
31
59
|
|
|
32
|
-
|
|
60
|
+
# Assemble the bundle fresh (leave .build/ so incremental compiles stay fast).
|
|
61
|
+
rm -rf "$BUNDLE"
|
|
62
|
+
mkdir -p "$BUNDLE/Contents/MacOS" "$BUNDLE/Contents/Resources"
|
|
63
|
+
cp "$BIN" "$BUNDLE/Contents/MacOS/modelstat-tray"
|
|
33
64
|
chmod +x "$BUNDLE/Contents/MacOS/modelstat-tray"
|
|
34
65
|
cp "Resources/Info.plist" "$BUNDLE/Contents/Info.plist"
|
|
35
66
|
|
|
36
|
-
# Embedded PkgInfo file — AppKit used to be picky about this. Costs
|
|
37
|
-
#
|
|
67
|
+
# Embedded PkgInfo file — AppKit used to be picky about this. Costs four
|
|
68
|
+
# bytes to include and silences a startup warning on older macOS.
|
|
38
69
|
printf "APPL????" > "$BUNDLE/Contents/PkgInfo"
|
|
39
70
|
|
|
71
|
+
# Ad-hoc sign the assembled bundle (seals it; required to launch on arm64).
|
|
72
|
+
echo "▶ codesign --force --sign - (ad-hoc)"
|
|
73
|
+
codesign --force --sign - "$BUNDLE"
|
|
74
|
+
|
|
40
75
|
echo
|
|
41
76
|
echo "✓ Built $BUNDLE"
|
|
77
|
+
codesign -dv "$BUNDLE" 2>&1 | grep -iE "signature|identifier" | sed 's/^/ /' || true
|
|
42
78
|
echo " run: open '$BUNDLE'"
|
|
43
79
|
echo " install: cp -R '$BUNDLE' /Applications/"
|