modelstat 0.0.26 → 0.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelstat",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
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",
@@ -26,6 +26,7 @@
26
26
  */
27
27
 
28
28
  import { existsSync } from "node:fs";
29
+ import { homedir } from "node:os";
29
30
  import { dirname, join, resolve } from "node:path";
30
31
  import { fileURLToPath } from "node:url";
31
32
 
@@ -97,7 +98,7 @@ async function main() {
97
98
  try {
98
99
  await ensureLlamaModel(defaultLlamaConfig());
99
100
  console.log("");
100
- console.log("[modelstat] ✓ summariser ready — run `modelstat connect` next");
101
+ console.log("[modelstat] ✓ summariser ready");
101
102
  } catch (err) {
102
103
  // Don't fail the npm install. The model will retry-download on
103
104
  // first scan; better to leave the user with a working binary
@@ -109,6 +110,95 @@ async function main() {
109
110
  "[modelstat] the model will download lazily on first `modelstat scan` instead",
110
111
  );
111
112
  }
113
+
114
+ // ── Auto-replace + restart the existing service ───────────────
115
+ // If a previous version of the agent is already installed as a
116
+ // launchd / systemd service, its bundle copy at
117
+ // ~/.modelstat/bin/modelstat.mjs is now STALE — the new code we
118
+ // just installed lives in <global node_modules>/modelstat/dist.
119
+ // Stop the service, refresh the bundle copy, restart. Without
120
+ // this step the user has to manually `modelstat stop && modelstat
121
+ // connect` after every upgrade.
122
+ await rebootServiceIfInstalled();
123
+ console.log(
124
+ "[modelstat] all set — your dashboard already has the new agent running",
125
+ );
126
+ }
127
+
128
+ async function rebootServiceIfInstalled() {
129
+ const stateDir = join(homedir(), ".modelstat");
130
+ const installedBundle = join(stateDir, "bin", "modelstat.mjs");
131
+ const launchdPlist = join(
132
+ homedir(),
133
+ "Library",
134
+ "LaunchAgents",
135
+ "ai.modelstat.agent.plist",
136
+ );
137
+ const systemdUnit = join(
138
+ process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"),
139
+ "systemd",
140
+ "user",
141
+ "modelstat.service",
142
+ );
143
+ const hasService = existsSync(launchdPlist) || existsSync(systemdUnit);
144
+ if (!hasService) {
145
+ console.log(
146
+ "[modelstat] no background service installed — run `modelstat connect` to set one up",
147
+ );
148
+ return;
149
+ }
150
+
151
+ // Locate the freshly-installed bundle in this package. From
152
+ // scripts/postinstall.mjs → ../dist/cli.mjs.
153
+ const here = dirname(fileURLToPath(import.meta.url));
154
+ const freshBundle = join(here, "..", "dist", "cli.mjs");
155
+ if (!existsSync(freshBundle)) {
156
+ console.warn(
157
+ "[modelstat] couldn't find new bundle — service refresh skipped",
158
+ );
159
+ return;
160
+ }
161
+
162
+ // Spawn the FRESH bundle to do the actual restart through its own
163
+ // service.ts — that's the same code that knows how to talk to
164
+ // launchd / systemd on each platform. We can't import service.ts
165
+ // directly here (postinstall is a plain .mjs without TS resolution),
166
+ // but spawning the new bundle gives us the new behaviour.
167
+ const { spawnSync } = await import("node:child_process");
168
+ console.log("[modelstat] refreshing background service…");
169
+
170
+ // Stop first — best effort, ignore failures.
171
+ spawnSync(process.execPath, [freshBundle, "stop"], {
172
+ stdio: "ignore",
173
+ timeout: 30_000,
174
+ });
175
+
176
+ // Copy the new bundle over the old `~/.modelstat/bin/modelstat.mjs`
177
+ // so the service supervisor (launchd/systemd) loads the new code on
178
+ // next launch. The fresh bundle's `connect` / `start` does this on
179
+ // its own first run, but we want the service to come back alive
180
+ // immediately — not wait for a manual `modelstat connect`.
181
+ const { copyFileSync, mkdirSync } = await import("node:fs");
182
+ try {
183
+ mkdirSync(dirname(installedBundle), { recursive: true });
184
+ copyFileSync(freshBundle, installedBundle);
185
+ } catch (err) {
186
+ console.warn(
187
+ `[modelstat] couldn't refresh installed bundle: ${err && err.message ? err.message : err}`,
188
+ );
189
+ }
190
+
191
+ // Start back up. `modelstat start` re-runs preflight (incl. the
192
+ // processing-version reconcile that wipes cursors when we ship a
193
+ // new pipeline), so the upgrade picks up the new behaviour
194
+ // immediately. Detached so the daemon survives this script.
195
+ const { spawn } = await import("node:child_process");
196
+ const child = spawn(process.execPath, [freshBundle, "start"], {
197
+ detached: true,
198
+ stdio: "ignore",
199
+ });
200
+ child.unref();
201
+ console.log("[modelstat] ✓ background service restarted with new build");
112
202
  }
113
203
 
114
204
  main().catch((err) => {