billion-context 0.1.17 → 0.1.19

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/index.js CHANGED
@@ -3234,16 +3234,18 @@ function logMsg(opts, level, msg2) {
3234
3234
  }
3235
3235
 
3236
3236
  // src/update.ts
3237
- import { readFile, writeFile, mkdir } from "fs/promises";
3237
+ import { readFile, writeFile, mkdir, access, constants, rm } from "fs/promises";
3238
3238
  import { execFile } from "child_process";
3239
3239
  import path4 from "path";
3240
+ import { fileURLToPath } from "url";
3240
3241
  var REGISTRY_BASE = "https://registry.npmjs.org";
3241
3242
  var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
3242
3243
  var THROTTLE_FILE = path4.join(cacheDir(), ".update-check");
3243
- var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/;
3244
+ var LOCK_FILE = path4.join(cacheDir(), ".update-lock");
3245
+ var LOCK_STALE_MS = 2 * 60 * 1e3;
3244
3246
  var timer;
3245
3247
  var inFlight = false;
3246
- var notified = /* @__PURE__ */ new Set();
3248
+ var firstCheckDone = false;
3247
3249
  function parseVersion(v) {
3248
3250
  return v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
3249
3251
  }
@@ -3273,22 +3275,98 @@ async function writeLastCheck(ts) {
3273
3275
  } catch {
3274
3276
  }
3275
3277
  }
3278
+ async function findInstallDir(packageName) {
3279
+ let dir = path4.dirname(fileURLToPath(import.meta.url));
3280
+ for (; ; ) {
3281
+ try {
3282
+ const pkg = JSON.parse(await readFile(path4.join(dir, "package.json"), "utf-8"));
3283
+ if (pkg.name === packageName) return dir;
3284
+ } catch {
3285
+ }
3286
+ const parent = path4.dirname(dir);
3287
+ if (parent === dir) return void 0;
3288
+ dir = parent;
3289
+ }
3290
+ }
3291
+ async function readDiskVersion(installDir) {
3292
+ try {
3293
+ const pkg = JSON.parse(await readFile(path4.join(installDir, "package.json"), "utf-8"));
3294
+ return pkg.version;
3295
+ } catch {
3296
+ return void 0;
3297
+ }
3298
+ }
3299
+ async function tryAcquireLock() {
3300
+ const pid = process.pid;
3301
+ const now = Date.now();
3302
+ async function readLock() {
3303
+ try {
3304
+ const raw = await readFile(LOCK_FILE, "utf-8");
3305
+ const data = JSON.parse(raw);
3306
+ if (typeof data.pid === "number" && typeof data.ts === "number") {
3307
+ return data;
3308
+ }
3309
+ } catch {
3310
+ }
3311
+ return null;
3312
+ }
3313
+ function isAlive(checkPid) {
3314
+ try {
3315
+ process.kill(checkPid, 0);
3316
+ return true;
3317
+ } catch {
3318
+ return false;
3319
+ }
3320
+ }
3321
+ const existing = await readLock();
3322
+ if (existing) {
3323
+ const age = now - existing.ts;
3324
+ const holderAlive = isAlive(existing.pid);
3325
+ if (holderAlive && age < LOCK_STALE_MS) {
3326
+ return null;
3327
+ }
3328
+ log("info", `[update] stealing stale lock (pid=${existing.pid}, age=${Math.round(age / 1e3)}s, alive=${holderAlive})`);
3329
+ }
3330
+ try {
3331
+ await writeFile(LOCK_FILE, JSON.stringify({ pid, ts: now }), { flag: "wx" });
3332
+ } catch {
3333
+ const winner = await readLock();
3334
+ if (winner && winner.pid !== pid) {
3335
+ log("info", `[update] lost lock race to pid=${winner.pid}, skipping update`);
3336
+ return null;
3337
+ }
3338
+ }
3339
+ const confirmed = await readLock();
3340
+ if (!confirmed || confirmed.pid !== pid) {
3341
+ log("info", `[update] lock held by pid=${confirmed?.pid}, skipping update`);
3342
+ return null;
3343
+ }
3344
+ return {
3345
+ release: async () => {
3346
+ const current = await readLock();
3347
+ if (current?.pid === pid) {
3348
+ await rm(LOCK_FILE, { force: true }).catch(() => {
3349
+ });
3350
+ }
3351
+ }
3352
+ };
3353
+ }
3276
3354
  async function checkForUpdate(opts, force = false) {
3277
3355
  if (!opts.autoUpdate && !force) return;
3278
3356
  if (inFlight) return;
3279
3357
  inFlight = true;
3280
- const firstInProcess = !notified.has("__first_check_done__");
3281
3358
  try {
3282
3359
  const now = Date.now();
3283
3360
  const lastCheck = await readLastCheck();
3284
3361
  const sinceLastSec = lastCheck ? (now - lastCheck) / 1e3 | 0 : -1;
3285
- if (!force && !firstInProcess && now - lastCheck < CHECK_INTERVAL_MS) {
3362
+ if (!force && firstCheckDone && now - lastCheck < CHECK_INTERVAL_MS) {
3286
3363
  const retryIn = (CHECK_INTERVAL_MS - (now - lastCheck)) / 1e3 | 0;
3287
3364
  log("info", `[update] throttled \u2014 last checked ${sinceLastSec}s ago, retry in ${retryIn}s`);
3288
3365
  return;
3289
3366
  }
3290
3367
  await writeLastCheck(now);
3291
- log("info", `[update] checking npm registry for ${opts.packageName}${firstInProcess ? " (startup check)" : sinceLastSec < 0 ? "" : ` (last check ${sinceLastSec}s ago)`}\u2026`);
3368
+ firstCheckDone = true;
3369
+ log("info", `[update] checking npm registry for ${opts.packageName}${sinceLastSec < 0 ? " (startup check)" : sinceLastSec === 0 ? "" : ` (last check ${sinceLastSec}s ago)`}\u2026`);
3292
3370
  const url = `${REGISTRY_BASE}/${opts.packageName}/latest`;
3293
3371
  const res = await fetch(url, {
3294
3372
  signal: AbortSignal.timeout(5e3),
@@ -3304,44 +3382,124 @@ async function checkForUpdate(opts, force = false) {
3304
3382
  log("warn", `[update] registry response had no version, skipping`);
3305
3383
  return;
3306
3384
  }
3307
- if (!isNewer(latest, opts.currentVersion)) {
3308
- log("info", `[update] current=${opts.currentVersion} latest=${latest} (up to date)`);
3385
+ const installDir = await findInstallDir(opts.packageName);
3386
+ const diskVersion = installDir ? await readDiskVersion(installDir) : void 0;
3387
+ const currentVersion = diskVersion ?? opts.currentVersion;
3388
+ if (!isNewer(latest, currentVersion)) {
3389
+ log("info", `[update] current=${currentVersion} latest=${latest} (up to date)`);
3309
3390
  return;
3310
3391
  }
3311
- if (notified.has(latest)) {
3312
- log("info", `[update] latest=${latest} already installed this run, awaiting restart`);
3392
+ const tarballUrl = data.dist?.tarball;
3393
+ if (!tarballUrl) {
3394
+ log("warn", `[update] registry response for ${latest} had no tarball URL`);
3313
3395
  return;
3314
3396
  }
3315
- log("info", `[update] new version found: ${opts.currentVersion} \u2192 ${latest}, installing\u2026`);
3316
- const installed = await installLatest(opts.packageName, latest);
3317
- if (installed) {
3318
- notified.add(latest);
3319
- log("info", `[update] installed ${opts.packageName} ${opts.currentVersion} \u2192 ${latest}. Restart to finish.`);
3320
- } else {
3321
- log("warn", `[update] install failed; will retry next cycle. Or: npm install -g ${opts.packageName}@${latest}`);
3397
+ log("info", `[update] new version found: ${currentVersion} \u2192 ${latest}, downloading\u2026`);
3398
+ const lock = await tryAcquireLock();
3399
+ if (!lock) {
3400
+ log("info", `[update] another process is updating, will check next cycle`);
3401
+ return;
3402
+ }
3403
+ try {
3404
+ const result = await installViaTarball(latest, tarballUrl, installDir);
3405
+ if (result.ok) {
3406
+ log("info", `[update] installed ${currentVersion} \u2192 ${latest}. Restart to finish.`);
3407
+ } else {
3408
+ log("warn", `[update] install failed: ${result.error}. Will retry next cycle.`);
3409
+ }
3410
+ } finally {
3411
+ await lock.release();
3322
3412
  }
3323
3413
  } catch (e) {
3324
3414
  log("warn", `[update] check failed: ${String(e)}`);
3325
3415
  } finally {
3326
3416
  inFlight = false;
3327
- notified.add("__first_check_done__");
3328
3417
  }
3329
3418
  }
3330
- async function installLatest(packageName, latest) {
3331
- if (!SEMVER_RE.test(latest)) return false;
3419
+ async function installViaTarball(version, tarballUrl, installDir) {
3420
+ if (!installDir) {
3421
+ return { ok: false, error: "cannot determine install directory (package.json not found walking up from running binary)" };
3422
+ }
3423
+ try {
3424
+ await access(installDir, constants.W_OK);
3425
+ } catch {
3426
+ return { ok: false, error: `install dir not writable: ${installDir}` };
3427
+ }
3428
+ let tgzBuffer;
3429
+ try {
3430
+ const tgzRes = await fetch(tarballUrl, { signal: AbortSignal.timeout(6e4) });
3431
+ if (!tgzRes.ok) {
3432
+ return { ok: false, error: `tarball download failed: HTTP ${tgzRes.status} ${tgzRes.statusText}` };
3433
+ }
3434
+ tgzBuffer = Buffer.from(await tgzRes.arrayBuffer());
3435
+ } catch (e) {
3436
+ return { ok: false, error: `tarball download failed: ${String(e)}` };
3437
+ }
3438
+ const tmpFile = path4.join(cacheDir(), `.update-${version}.tgz`);
3332
3439
  try {
3333
- const code = await new Promise((resolve) => {
3440
+ await mkdir(cacheDir(), { recursive: true });
3441
+ await writeFile(tmpFile, tgzBuffer);
3442
+ } catch (e) {
3443
+ return { ok: false, error: `failed to write temp file ${tmpFile}: ${String(e)}` };
3444
+ }
3445
+ const stagingDir = path4.join(cacheDir(), `.update-staging-${version}`);
3446
+ try {
3447
+ await rm(stagingDir, { recursive: true, force: true });
3448
+ await mkdir(stagingDir, { recursive: true });
3449
+ const { code, stderr } = await new Promise((resolve) => {
3334
3450
  execFile(
3335
- "npm",
3336
- ["install", "-g", `${packageName}@${latest}`, "--silent", "--no-audit", "--no-fund"],
3337
- { timeout: 12e4, shell: process.platform === "win32" },
3338
- (err) => resolve(err ? 1 : 0)
3451
+ "tar",
3452
+ ["xzf", tmpFile, "-C", stagingDir, "--strip-components=1"],
3453
+ { timeout: 3e4 },
3454
+ (err, _stdout, stderr2) => {
3455
+ if (err) {
3456
+ resolve({ code: 1, stderr: stderr2 || err.message });
3457
+ } else {
3458
+ resolve({ code: 0, stderr: stderr2 || "" });
3459
+ }
3460
+ }
3339
3461
  );
3340
3462
  });
3341
- return code === 0;
3342
- } catch {
3343
- return false;
3463
+ if (code !== 0) {
3464
+ return { ok: false, error: `extraction failed (tar exit ${code})${stderr.trim() ? `: ${stderr.trim()}` : ""}` };
3465
+ }
3466
+ const stagingVersion = await readDiskVersion(stagingDir);
3467
+ if (stagingVersion !== version) {
3468
+ return { ok: false, error: `staging verification failed: version is ${stagingVersion ?? "missing"}, expected ${version}` };
3469
+ }
3470
+ } catch (e) {
3471
+ return { ok: false, error: `extraction failed: ${String(e)}` };
3472
+ } finally {
3473
+ await rm(tmpFile, { force: true });
3344
3474
  }
3475
+ try {
3476
+ const { code, stderr } = await new Promise((resolve) => {
3477
+ execFile(
3478
+ "cp",
3479
+ ["-r", stagingDir + "/.", installDir + "/"],
3480
+ { timeout: 3e4 },
3481
+ (err, _stdout, stderr2) => {
3482
+ if (err) {
3483
+ resolve({ code: 1, stderr: stderr2 || err.message });
3484
+ } else {
3485
+ resolve({ code: 0, stderr: stderr2 || "" });
3486
+ }
3487
+ }
3488
+ );
3489
+ });
3490
+ if (code !== 0) {
3491
+ return { ok: false, error: `failed to copy to install dir (cp exit ${code})${stderr.trim() ? `: ${stderr.trim()}` : ""}` };
3492
+ }
3493
+ } catch (e) {
3494
+ return { ok: false, error: `failed to copy to install dir: ${String(e)}` };
3495
+ } finally {
3496
+ await rm(stagingDir, { recursive: true, force: true });
3497
+ }
3498
+ const newVersion = await readDiskVersion(installDir);
3499
+ if (newVersion !== version) {
3500
+ return { ok: false, error: `post-install verification failed: package.json version is ${newVersion ?? "missing"}, expected ${version}` };
3501
+ }
3502
+ return { ok: true };
3345
3503
  }
3346
3504
  function startAutoUpdate(opts) {
3347
3505
  log("info", `[update] auto-update enabled (checking every ${CHECK_INTERVAL_MS / 1e3 | 0}s)`);
@@ -3356,11 +3514,11 @@ function startAutoUpdate(opts) {
3356
3514
 
3357
3515
  // src/cli.ts
3358
3516
  import { readFileSync as readFileSync3 } from "fs";
3359
- import { fileURLToPath } from "url";
3517
+ import { fileURLToPath as fileURLToPath2 } from "url";
3360
3518
  import path5 from "path";
3361
3519
  var VERSION = (() => {
3362
3520
  try {
3363
- const here = fileURLToPath(import.meta.url);
3521
+ const here = fileURLToPath2(import.meta.url);
3364
3522
  const pkg = path5.join(path5.dirname(here), "..", "package.json");
3365
3523
  return JSON.parse(readFileSync3(pkg, "utf8")).version ?? "dev";
3366
3524
  } catch {
@@ -3369,7 +3527,7 @@ var VERSION = (() => {
3369
3527
  })();
3370
3528
  var PACKAGE_NAME = (() => {
3371
3529
  try {
3372
- const here = fileURLToPath(import.meta.url);
3530
+ const here = fileURLToPath2(import.meta.url);
3373
3531
  const pkg = path5.join(path5.dirname(here), "..", "package.json");
3374
3532
  return JSON.parse(readFileSync3(pkg, "utf8")).name ?? "billion-context";
3375
3533
  } catch {