modelstat 0.0.31 → 0.0.32

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.31",
3
+ "version": "0.0.32",
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",
@@ -196,6 +196,16 @@ async function rebootServiceIfInstalled() {
196
196
  );
197
197
  }
198
198
 
199
+ // The bundle is portable EXCEPT for `node-llama-cpp` and its
200
+ // platform-specific native sub-packages — they're marked external
201
+ // in tsup so they're resolved at runtime via Node's `node_modules`
202
+ // walk-up. From `~/.modelstat/bin/modelstat.mjs` there's no parent
203
+ // `node_modules` to find them in, so the bundled summariser dies
204
+ // at preflight with "Cannot find package 'node-llama-cpp'". Set up
205
+ // a sibling `node_modules` directory containing the runtime
206
+ // dependencies, sourced from this package's own install location.
207
+ await setupNativeNodeModules(installedBundle);
208
+
199
209
  // Start back up. `modelstat start` re-runs preflight (incl. the
200
210
  // processing-version reconcile that wipes cursors when we ship a
201
211
  // new pipeline), so the upgrade picks up the new behaviour
@@ -209,6 +219,98 @@ async function rebootServiceIfInstalled() {
209
219
  console.log("[modelstat] ✓ background service restarted with new build");
210
220
  }
211
221
 
222
+ /**
223
+ * Make `node-llama-cpp` and its platform-specific native sibling
224
+ * packages reachable from `~/.modelstat/bin/modelstat.mjs` so the
225
+ * bundle's runtime `import("node-llama-cpp")` resolves.
226
+ *
227
+ * Strategy:
228
+ * - Find the source `node_modules` directory in this package's
229
+ * install (npm flat layout puts it at <pkg>/.. for global
230
+ * installs; pnpm/bun layouts vary but the same walk-up works
231
+ * since postinstall always runs from the package root).
232
+ * - For each runtime dep we care about (`node-llama-cpp` plus
233
+ * every `@node-llama-cpp/*` and `@reflink/*` sub-package),
234
+ * symlink it into `~/.modelstat/bin/node_modules/<name>`.
235
+ * Symlinks are free + fast; if symlinking fails (filesystem
236
+ * boundary, permissions) fall back to a recursive copy with
237
+ * `dereference:true` so the resulting tree has no dangling
238
+ * refs into the source location.
239
+ *
240
+ * Idempotent: each upgrade clears the destination first.
241
+ */
242
+ async function setupNativeNodeModules(installedBundle) {
243
+ const here = dirname(fileURLToPath(import.meta.url));
244
+ // Find a parent `node_modules` containing `node-llama-cpp`. The
245
+ // walk handles npm flat (<prefix>/lib/node_modules), pnpm flat
246
+ // (<prefix>/lib/node_modules with peer hoisting), and the npx
247
+ // cache layout (~/.npm/_npx/<hash>/node_modules).
248
+ let srcNodeModules = null;
249
+ for (let dir = here, i = 0; i < 8; i++) {
250
+ const probe = join(dir, "node_modules", "node-llama-cpp", "package.json");
251
+ if (existsSync(probe)) {
252
+ srcNodeModules = join(dir, "node_modules");
253
+ break;
254
+ }
255
+ const up = resolve(dir, "..");
256
+ if (up === dir) break;
257
+ dir = up;
258
+ }
259
+ if (!srcNodeModules) {
260
+ console.warn(
261
+ "[modelstat] couldn't locate node-llama-cpp in this install — bundled summariser will fail at preflight. Re-install with `npm i -g modelstat` or report a bug.",
262
+ );
263
+ return;
264
+ }
265
+
266
+ const fs = await import("node:fs/promises");
267
+ const destDir = join(dirname(installedBundle), "node_modules");
268
+ await fs.mkdir(destDir, { recursive: true });
269
+
270
+ // Discover every native package we need: the main one + every
271
+ // platform-specific sibling (the binary blob is in one of the
272
+ // `@node-llama-cpp/*` packages chosen by node-llama-cpp's
273
+ // optional-dependencies resolution at install time).
274
+ const wanted = new Set(["node-llama-cpp"]);
275
+ const all = await fs
276
+ .readdir(srcNodeModules, { withFileTypes: true })
277
+ .catch(() => []);
278
+ for (const e of all) {
279
+ if (!e.isDirectory()) continue;
280
+ if (!e.name.startsWith("@")) continue;
281
+ if (
282
+ e.name === "@node-llama-cpp" ||
283
+ e.name === "@reflink"
284
+ ) {
285
+ // Scoped dirs contain multiple packages — copy/symlink the
286
+ // whole scope dir so all platform variants are reachable.
287
+ wanted.add(e.name);
288
+ }
289
+ }
290
+
291
+ for (const pkg of wanted) {
292
+ const src = join(srcNodeModules, pkg);
293
+ const dest = join(destDir, pkg);
294
+ if (!existsSync(src)) continue;
295
+ try {
296
+ await fs.rm(dest, { recursive: true, force: true });
297
+ } catch {
298
+ /* ignore */
299
+ }
300
+ try {
301
+ await fs.symlink(src, dest, "dir");
302
+ } catch {
303
+ try {
304
+ await fs.cp(src, dest, { recursive: true, dereference: true });
305
+ } catch (err2) {
306
+ console.warn(
307
+ `[modelstat] couldn't link ${pkg}: ${err2 && err2.message ? err2.message : err2}`,
308
+ );
309
+ }
310
+ }
311
+ }
312
+ }
313
+
212
314
  /**
213
315
  * Kill any daemon process the service supervisor didn't reap.
214
316
  * Reads ~/.modelstat/daemon.lock for the PID, sends SIGTERM, then