@cerefox/memory 1.17.4 → 1.18.0

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.
@@ -5318,7 +5318,7 @@ var init_cli_core = __esm(() => {
5318
5318
  });
5319
5319
 
5320
5320
  // src/meta.ts
5321
- var PKG_VERSION = "1.17.4";
5321
+ var PKG_VERSION = "1.18.0";
5322
5322
  var init_meta = () => {};
5323
5323
 
5324
5324
  // ../../_shared/config/paths.ts
@@ -26229,10 +26229,6 @@ var init_bundled_docs = __esm(() => {
26229
26229
  PACKAGE_ROOT = findPackageRoot();
26230
26230
  });
26231
26231
 
26232
- // ../../_shared/ef-meta/index.ts
26233
- var EF_VERSION = "1.17.4", CEREFOX_VERSION = "1.17.4", EF_LAST_CHANGED = "1.17.3";
26234
- var init_ef_meta = () => {};
26235
-
26236
26232
  // ../../_shared/compatibility/index.ts
26237
26233
  function compareSemver(a, b) {
26238
26234
  const split = (v) => {
@@ -26366,6 +26362,425 @@ var init_compatibility = __esm(() => {
26366
26362
  };
26367
26363
  });
26368
26364
 
26365
+ // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/conninfo.mjs
26366
+ var getConnInfo = (c) => {
26367
+ const bindings = c.env.server ? c.env.server : c.env;
26368
+ const address = bindings.incoming.socket.remoteAddress;
26369
+ const port = bindings.incoming.socket.remotePort;
26370
+ const family = bindings.incoming.socket.remoteFamily;
26371
+ return { remote: {
26372
+ address,
26373
+ port,
26374
+ addressType: family === "IPv4" ? "IPv4" : family === "IPv6" ? "IPv6" : undefined
26375
+ } };
26376
+ };
26377
+ var init_conninfo = () => {};
26378
+
26379
+ // ../../_shared/mcp-auth/index.ts
26380
+ function constantTimeEqual(a, b) {
26381
+ const ab = new TextEncoder().encode(a);
26382
+ const bb = new TextEncoder().encode(b);
26383
+ const len = Math.max(ab.length, bb.length);
26384
+ let diff = ab.length ^ bb.length;
26385
+ for (let i = 0;i < len; i++) {
26386
+ diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
26387
+ }
26388
+ return diff === 0;
26389
+ }
26390
+
26391
+ // ../../_shared/ef-auth/index.ts
26392
+ function parseAccessTokens(raw) {
26393
+ if (!raw)
26394
+ return [];
26395
+ return raw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
26396
+ }
26397
+ function checkAccessToken(authorizationHeader, config) {
26398
+ if (!authorizationHeader || !authorizationHeader.startsWith(BEARER_PREFIX)) {
26399
+ return { ok: false, reason: "no_token" };
26400
+ }
26401
+ const token = authorizationHeader.slice(BEARER_PREFIX.length).trim();
26402
+ if (!token)
26403
+ return { ok: false, reason: "no_token" };
26404
+ if (config.tokens.length === 0) {
26405
+ return {
26406
+ ok: false,
26407
+ reason: "no_tokens_configured",
26408
+ detail: "CEREFOX_ACCESS_TOKENS is unset/empty; refusing to accept-all"
26409
+ };
26410
+ }
26411
+ let matched = false;
26412
+ for (const accepted of config.tokens) {
26413
+ if (constantTimeEqual(token, accepted))
26414
+ matched = true;
26415
+ }
26416
+ return matched ? { ok: true } : { ok: false, reason: "bad_token" };
26417
+ }
26418
+ var BEARER_PREFIX = "Bearer ";
26419
+ var init_ef_auth = () => {};
26420
+
26421
+ // src/web/auth.ts
26422
+ import { existsSync as existsSync9 } from "node:fs";
26423
+ function isContainerised() {
26424
+ try {
26425
+ return existsSync9("/.dockerenv");
26426
+ } catch {
26427
+ return false;
26428
+ }
26429
+ }
26430
+ function containerGateWarning(env = process.env) {
26431
+ const hasKey = (env.CEREFOX_API_KEY ?? "").trim() !== "";
26432
+ const requireAll = (env.CEREFOX_API_REQUIRE_KEY ?? "") === "1";
26433
+ if (!hasKey || requireAll || !isContainerised())
26434
+ return null;
26435
+ return `
26436
+ ⚠ CEREFOX_API_KEY is set inside a container without CEREFOX_API_REQUIRE_KEY=1.
26437
+ ` + ` Docker rewrites the source address of every published-port request, so the
26438
+ ` + ` loopback exemption cannot match and EVERY caller will get a 401 — including
26439
+ ` + ` the web UI. In a container the gate must be all-or-nothing:
26440
+ ` + ` • publishing on 127.0.0.1 → leave CEREFOX_API_KEY unset (the bind is the boundary)
26441
+ ` + ` • publishing wider → also set CEREFOX_API_REQUIRE_KEY=1
26442
+ `;
26443
+ }
26444
+ function isLoopbackAddress(address) {
26445
+ if (!address)
26446
+ return false;
26447
+ return LOOPBACK_ADDRESSES.has(address.trim().toLowerCase());
26448
+ }
26449
+ function decideAuth(input) {
26450
+ const keys = input.configuredKeys.trim();
26451
+ if (!keys)
26452
+ return { allow: true, reason: "disabled" };
26453
+ const local = isLoopbackAddress(input.remoteAddress);
26454
+ if (local && !input.requireKeyEverywhere) {
26455
+ return { allow: true, reason: "loopback" };
26456
+ }
26457
+ const result = checkAccessToken(input.authorization, {
26458
+ tokens: parseAccessTokens(keys)
26459
+ });
26460
+ if (result.ok)
26461
+ return { allow: true, reason: "valid_key" };
26462
+ if (result.reason === "no_token") {
26463
+ return {
26464
+ allow: false,
26465
+ reason: "no_key",
26466
+ message: local ? "This server requires an API key on every request (CEREFOX_API_REQUIRE_KEY is set). " + "Send it as `Authorization: Bearer <key>`." : "This request did not come from the local machine, so it needs the server's API key " + "as `Authorization: Bearer <key>`. Print it with `cerefox api-key show` " + "(or `cerefox-local api-key` for a container install)."
26467
+ };
26468
+ }
26469
+ return {
26470
+ allow: false,
26471
+ reason: "bad_key",
26472
+ message: "The API key presented is not valid for this server."
26473
+ };
26474
+ }
26475
+ function apiAuth() {
26476
+ return async (c, next) => {
26477
+ const decision = decideAuth({
26478
+ remoteAddress: remoteAddressOf(c),
26479
+ authorization: c.req.header("Authorization") ?? null,
26480
+ configuredKeys: process.env.CEREFOX_API_KEY ?? "",
26481
+ requireKeyEverywhere: (process.env.CEREFOX_API_REQUIRE_KEY ?? "") === "1"
26482
+ });
26483
+ if (decision.allow)
26484
+ return next();
26485
+ return c.json({ detail: decision.message }, 401, {
26486
+ "WWW-Authenticate": 'Bearer realm="cerefox"'
26487
+ });
26488
+ };
26489
+ }
26490
+ function remoteAddressOf(c) {
26491
+ try {
26492
+ return getConnInfo(c).remote.address;
26493
+ } catch {
26494
+ return;
26495
+ }
26496
+ }
26497
+ var API_KEY_PREFIX = "cfx_lak_", LOOPBACK_ADDRESSES;
26498
+ var init_auth = __esm(() => {
26499
+ init_conninfo();
26500
+ init_ef_auth();
26501
+ LOOPBACK_ADDRESSES = new Set([
26502
+ "127.0.0.1",
26503
+ "::1",
26504
+ "::ffff:127.0.0.1"
26505
+ ]);
26506
+ });
26507
+
26508
+ // src/web/daemon.ts
26509
+ import { spawn } from "node:child_process";
26510
+ import {
26511
+ existsSync as existsSync10,
26512
+ mkdirSync as mkdirSync3,
26513
+ openSync,
26514
+ readFileSync as readFileSync8,
26515
+ rmSync,
26516
+ writeFileSync as writeFileSync4
26517
+ } from "node:fs";
26518
+ import { homedir as homedir5 } from "node:os";
26519
+ import { join as join8 } from "node:path";
26520
+ function resolveStateDir(override = process.env.CEREFOX_CONFIG_DIR, home = homedir5()) {
26521
+ override = (override ?? "").trim();
26522
+ if (!override)
26523
+ return join8(home, ".cerefox");
26524
+ return override === "~" || override.startsWith("~/") ? join8(home, override.slice(2)) : override;
26525
+ }
26526
+ function ensureStateDir() {
26527
+ if (!existsSync10(STATE_DIR))
26528
+ mkdirSync3(STATE_DIR, { recursive: true });
26529
+ }
26530
+ function readPidFile() {
26531
+ if (!existsSync10(PID_FILE))
26532
+ return null;
26533
+ try {
26534
+ const parsed = JSON.parse(readFileSync8(PID_FILE, "utf8"));
26535
+ if (typeof parsed.pid !== "number")
26536
+ return null;
26537
+ return {
26538
+ pid: parsed.pid,
26539
+ port: typeof parsed.port === "number" ? parsed.port : 8000,
26540
+ host: typeof parsed.host === "string" ? parsed.host : "127.0.0.1",
26541
+ startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : "unknown"
26542
+ };
26543
+ } catch {
26544
+ return null;
26545
+ }
26546
+ }
26547
+ function writePidFile(info) {
26548
+ ensureStateDir();
26549
+ writeFileSync4(PID_FILE, JSON.stringify(info, null, 2) + `
26550
+ `, "utf8");
26551
+ }
26552
+ function removePidFile() {
26553
+ rmSync(PID_FILE, { force: true });
26554
+ }
26555
+ function isProcessAlive(pid) {
26556
+ try {
26557
+ process.kill(pid, 0);
26558
+ return true;
26559
+ } catch (err) {
26560
+ return err.code === "EPERM";
26561
+ }
26562
+ }
26563
+ async function probeVersion(host, port, timeoutMs = 1500) {
26564
+ const probeHost = host === "0.0.0.0" ? "127.0.0.1" : host;
26565
+ try {
26566
+ const ctrl = new AbortController;
26567
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
26568
+ try {
26569
+ const resp = await fetch(`http://${probeHost}:${port}/api/v1/version`, {
26570
+ signal: ctrl.signal
26571
+ });
26572
+ if (!resp.ok)
26573
+ return { responding: false, version: null };
26574
+ const body = await resp.json().catch(() => null);
26575
+ return {
26576
+ responding: true,
26577
+ version: typeof body?.version === "string" ? body.version : null
26578
+ };
26579
+ } finally {
26580
+ clearTimeout(timer);
26581
+ }
26582
+ } catch {
26583
+ return { responding: false, version: null };
26584
+ }
26585
+ }
26586
+ async function isResponding(host, port, timeoutMs = 1500) {
26587
+ return (await probeVersion(host, port, timeoutMs)).responding;
26588
+ }
26589
+ function assertUnix() {
26590
+ if (process.platform === "win32") {
26591
+ throw new Error("Daemon mode (`cerefox web start/stop/status`) is not supported on Windows yet. " + "Run `cerefox web` in the foreground, or set up a Windows service manually.");
26592
+ }
26593
+ }
26594
+ async function startDaemon(opts) {
26595
+ assertUnix();
26596
+ ensureStateDir();
26597
+ const existing = readPidFile();
26598
+ if (existing && isProcessAlive(existing.pid)) {
26599
+ return existing.port === opts.port ? { kind: "already-running", info: existing } : { kind: "port-conflict", info: existing };
26600
+ }
26601
+ if (existing)
26602
+ removePidFile();
26603
+ const logFd = openSync(LOG_FILE, "a");
26604
+ const child = spawn(opts.runtime, [opts.scriptPath, "web", "--host", opts.host, "--port", String(opts.port)], { detached: true, stdio: ["ignore", logFd, logFd] });
26605
+ child.unref();
26606
+ if (typeof child.pid !== "number") {
26607
+ throw new Error("Failed to spawn the web server process (no pid).");
26608
+ }
26609
+ writePidFile({
26610
+ pid: child.pid,
26611
+ port: opts.port,
26612
+ host: opts.host,
26613
+ startedAt: new Date().toISOString()
26614
+ });
26615
+ let responding = false;
26616
+ for (let i = 0;i < 20; i++) {
26617
+ if (!isProcessAlive(child.pid))
26618
+ break;
26619
+ if (await isResponding(opts.host, opts.port)) {
26620
+ responding = true;
26621
+ break;
26622
+ }
26623
+ await sleep2(250);
26624
+ }
26625
+ return { kind: "started", pid: child.pid, responding };
26626
+ }
26627
+ async function stopDaemon() {
26628
+ assertUnix();
26629
+ const info = readPidFile();
26630
+ if (!info || !isProcessAlive(info.pid)) {
26631
+ removePidFile();
26632
+ return { kind: "not-running" };
26633
+ }
26634
+ try {
26635
+ process.kill(info.pid, "SIGTERM");
26636
+ } catch {}
26637
+ let forced = false;
26638
+ let alive = true;
26639
+ for (let i = 0;i < 12; i++) {
26640
+ await sleep2(250);
26641
+ if (!isProcessAlive(info.pid)) {
26642
+ alive = false;
26643
+ break;
26644
+ }
26645
+ }
26646
+ if (alive) {
26647
+ try {
26648
+ process.kill(info.pid, "SIGKILL");
26649
+ forced = true;
26650
+ } catch {}
26651
+ }
26652
+ removePidFile();
26653
+ return { kind: "stopped", pid: info.pid, forced };
26654
+ }
26655
+ function restartCommand(host, port) {
26656
+ const flags = `${host === "127.0.0.1" ? "" : ` --host ${host}`}${port === 8000 ? "" : ` --port ${port}`}`;
26657
+ return `cerefox web stop && cerefox web start${flags}`;
26658
+ }
26659
+ async function statusDaemon() {
26660
+ const info = readPidFile();
26661
+ if (!info)
26662
+ return { kind: "stopped" };
26663
+ if (!isProcessAlive(info.pid))
26664
+ return { kind: "stale", info };
26665
+ const { responding, version } = await probeVersion(info.host, info.port);
26666
+ return { kind: "running", info, responding, version };
26667
+ }
26668
+ var STATE_DIR, PID_FILE, LOG_FILE, daemonPaths, sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
26669
+ var init_daemon = __esm(() => {
26670
+ STATE_DIR = resolveStateDir();
26671
+ PID_FILE = join8(STATE_DIR, "web.pid");
26672
+ LOG_FILE = join8(STATE_DIR, "web.log");
26673
+ daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
26674
+ });
26675
+
26676
+ // src/update-check.ts
26677
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
26678
+ import { dirname as dirname4, join as join9 } from "node:path";
26679
+ function updateCheckDisabled(env = process.env) {
26680
+ const on = (v) => {
26681
+ const t = (v ?? "").trim().toLowerCase();
26682
+ return t !== "" && t !== "0" && t !== "false";
26683
+ };
26684
+ return on(env.CEREFOX_NO_UPDATE_CHECK) || on(env.NO_UPDATE_NOTIFIER) || on(env.CI);
26685
+ }
26686
+ function cachePath() {
26687
+ return join9(resolveStateDir(), "update-check.json");
26688
+ }
26689
+ function readCache(path = cachePath()) {
26690
+ try {
26691
+ const parsed = JSON.parse(readFileSync9(path, "utf8"));
26692
+ if (typeof parsed.latest !== "string" || typeof parsed.checkedAt !== "string")
26693
+ return null;
26694
+ return parsed;
26695
+ } catch {
26696
+ return null;
26697
+ }
26698
+ }
26699
+ function writeCache(cache, path = cachePath()) {
26700
+ try {
26701
+ mkdirSync4(dirname4(path), { recursive: true });
26702
+ writeFileSync5(path, JSON.stringify(cache, null, 2) + `
26703
+ `);
26704
+ } catch {}
26705
+ }
26706
+ async function fetchLatestVersion({ timeoutMs = 3000, fetchImpl = fetch } = {}) {
26707
+ const resp = await fetchImpl(LATEST_URL, { signal: AbortSignal.timeout(timeoutMs) });
26708
+ if (!resp.ok)
26709
+ throw new Error(`npm registry answered ${resp.status} ${resp.statusText}`);
26710
+ const body = await resp.json();
26711
+ if (typeof body.version !== "string" || body.version === "") {
26712
+ throw new Error("npm registry response has no version field");
26713
+ }
26714
+ return body.version;
26715
+ }
26716
+ function recordLatest(latest, path = cachePath(), now = new Date) {
26717
+ const prev = readCache(path);
26718
+ writeCache({ ...prev, latest, checkedAt: now.toISOString() }, path);
26719
+ }
26720
+ async function refreshLatest(opts = {}) {
26721
+ if (updateCheckDisabled(opts.env))
26722
+ return null;
26723
+ const path = opts.path ?? cachePath();
26724
+ const now = opts.now ?? new Date;
26725
+ const cached = readCache(path);
26726
+ const age = cached ? now.getTime() - Date.parse(cached.checkedAt) : Infinity;
26727
+ if (cached && !opts.force && age >= 0 && age < CACHE_TTL_MS)
26728
+ return cached.latest;
26729
+ try {
26730
+ const latest = await fetchLatestVersion({ fetchImpl: opts.fetchImpl });
26731
+ recordLatest(latest, path, now);
26732
+ return latest;
26733
+ } catch {
26734
+ return cached?.latest ?? null;
26735
+ }
26736
+ }
26737
+ function startPeriodicRefresh() {
26738
+ if (updateCheckDisabled())
26739
+ return;
26740
+ refreshLatest();
26741
+ const timer = setInterval(() => void refreshLatest(), CACHE_TTL_MS);
26742
+ timer.unref?.();
26743
+ }
26744
+ function newerRelease(latest, installed = PKG_VERSION) {
26745
+ if (!latest)
26746
+ return null;
26747
+ return compareSemver(latest, installed) > 0 ? latest : null;
26748
+ }
26749
+ function upgradeCommand() {
26750
+ return isContainerised() ? "cerefox-local upgrade" : "cerefox self-update";
26751
+ }
26752
+ function takeCliNotice(opts = {}) {
26753
+ if (updateCheckDisabled(opts.env))
26754
+ return null;
26755
+ const path = opts.path ?? cachePath();
26756
+ const now = opts.now ?? new Date;
26757
+ const cached = readCache(path);
26758
+ const newer = newerRelease(cached?.latest, opts.installed);
26759
+ if (!cached || !newer)
26760
+ return null;
26761
+ if (cached.notifiedAt) {
26762
+ const since = now.getTime() - Date.parse(cached.notifiedAt);
26763
+ if (since >= 0 && since < NOTIFY_INTERVAL_MS)
26764
+ return null;
26765
+ }
26766
+ writeCache({ ...cached, notifiedAt: now.toISOString() }, path);
26767
+ return `Cerefox ${newer} is available (you have ${opts.installed ?? PKG_VERSION}). Run \`${upgradeCommand()}\`.`;
26768
+ }
26769
+ var LATEST_URL = "https://registry.npmjs.org/@cerefox%2Fmemory/latest", DAY_MS, CACHE_TTL_MS, NOTIFY_INTERVAL_MS, releaseNotesUrl = (version) => `https://github.com/fstamatelopoulos/cerefox/releases/tag/v${version}`;
26770
+ var init_update_check = __esm(() => {
26771
+ init_compatibility();
26772
+ init_meta();
26773
+ init_auth();
26774
+ init_daemon();
26775
+ DAY_MS = 24 * 60 * 60 * 1000;
26776
+ CACHE_TTL_MS = DAY_MS;
26777
+ NOTIFY_INTERVAL_MS = DAY_MS;
26778
+ });
26779
+
26780
+ // ../../_shared/ef-meta/index.ts
26781
+ var EF_VERSION = "1.18.0", CEREFOX_VERSION = "1.18.0", EF_LAST_CHANGED = "1.17.3";
26782
+ var init_ef_meta = () => {};
26783
+
26369
26784
  // ../../_shared/embeddings/onnx-embedder.ts
26370
26785
  var exports_onnx_embedder = {};
26371
26786
  __export(exports_onnx_embedder, {
@@ -26382,9 +26797,9 @@ __export(exports_onnx_embedder, {
26382
26797
  onnxEmbed: () => onnxEmbed,
26383
26798
  warmup: () => warmup
26384
26799
  });
26385
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, renameSync, statSync as statSync2, writeFileSync as writeFileSync5, appendFileSync } from "node:fs";
26800
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, renameSync, statSync as statSync2, writeFileSync as writeFileSync6, appendFileSync } from "node:fs";
26386
26801
  import { homedir as homedir6 } from "node:os";
26387
- import { dirname as dirname4, join as join9 } from "node:path";
26802
+ import { dirname as dirname5, join as join10 } from "node:path";
26388
26803
  function nomicPrefix(role) {
26389
26804
  return role === "query" ? "search_query: " : "search_document: ";
26390
26805
  }
@@ -26421,17 +26836,17 @@ async function downloadModelFiles(remoteHost, onProgress, onDone, opts = {}) {
26421
26836
  const dir = getCacheDir();
26422
26837
  const base = remoteHost.endsWith("/") ? remoteHost : `${remoteHost}/`;
26423
26838
  for (const rel of MODEL_REPO_FILES) {
26424
- const dest = join9(dir, ONNX_MODEL_ID, rel);
26425
- if (existsSync10(dest))
26839
+ const dest = join10(dir, ONNX_MODEL_ID, rel);
26840
+ if (existsSync11(dest))
26426
26841
  continue;
26427
- mkdirSync4(dirname4(dest), { recursive: true });
26842
+ mkdirSync5(dirname5(dest), { recursive: true });
26428
26843
  const part = `${dest}.part`;
26429
26844
  const url = `${base}${ONNX_MODEL_ID}/resolve/main/${rel}`;
26430
26845
  let failures = 0;
26431
26846
  let lastError = "";
26432
26847
  let total = 0;
26433
26848
  for (;; ) {
26434
- const have = existsSync10(part) ? statSync2(part).size : 0;
26849
+ const have = existsSync11(part) ? statSync2(part).size : 0;
26435
26850
  if (total > 0 && have >= total)
26436
26851
  break;
26437
26852
  try {
@@ -26445,12 +26860,12 @@ async function downloadModelFiles(remoteHost, onProgress, onDone, opts = {}) {
26445
26860
  if (declared > 0 && bytes.byteLength !== declared) {
26446
26861
  throw new Error(`got ${bytes.byteLength} bytes, expected ${declared}`);
26447
26862
  }
26448
- writeFileSync5(part, bytes);
26863
+ writeFileSync6(part, bytes);
26449
26864
  total = bytes.byteLength;
26450
26865
  } else {
26451
26866
  total = got.total;
26452
26867
  if (have > total) {
26453
- writeFileSync5(part, new Uint8Array(0));
26868
+ writeFileSync6(part, new Uint8Array(0));
26454
26869
  continue;
26455
26870
  }
26456
26871
  appendFileSync(part, got.bytes);
@@ -26473,13 +26888,13 @@ async function downloadModelFiles(remoteHost, onProgress, onDone, opts = {}) {
26473
26888
  }
26474
26889
  }
26475
26890
  function isModelCached(dir = getCacheDir()) {
26476
- return MODEL_FILES.every((f) => existsSync10(join9(dir, f)));
26891
+ return MODEL_FILES.every((f) => existsSync11(join10(dir, f)));
26477
26892
  }
26478
26893
  function getCacheDir() {
26479
26894
  const env = globalThis.process?.env ?? {};
26480
26895
  if (env.CEREFOX_MODELS_DIR)
26481
26896
  return env.CEREFOX_MODELS_DIR;
26482
- return join9(homedir6(), ".cerefox", "models");
26897
+ return join10(homedir6(), ".cerefox", "models");
26483
26898
  }
26484
26899
  async function loadTransformers() {
26485
26900
  if (transformersModule)
@@ -26488,8 +26903,8 @@ async function loadTransformers() {
26488
26903
  process.env.ORT_DISABLE_TELEMETRY ??= "1";
26489
26904
  transformersModule = await import(spec);
26490
26905
  const dir = getCacheDir();
26491
- if (!existsSync10(dir))
26492
- mkdirSync4(dir, { recursive: true });
26906
+ if (!existsSync11(dir))
26907
+ mkdirSync5(dir, { recursive: true });
26493
26908
  transformersModule.env.cacheDir = dir;
26494
26909
  transformersModule.env.allowLocalModels = true;
26495
26910
  transformersModule.env.allowRemoteModels = true;
@@ -26654,7 +27069,7 @@ var init_onnx_embedder = __esm(() => {
26654
27069
  "tokenizer_config.json",
26655
27070
  `onnx/model_quantized.onnx`
26656
27071
  ];
26657
- MODEL_FILES = MODEL_REPO_FILES.map((f) => join9(ONNX_MODEL_ID, f));
27072
+ MODEL_FILES = MODEL_REPO_FILES.map((f) => join10(ONNX_MODEL_ID, f));
26658
27073
  EmbedderDownloadError = class EmbedderDownloadError extends Error {
26659
27074
  status = 503;
26660
27075
  constructor(detail) {
@@ -50797,7 +51212,7 @@ function resetFeatureFlagCache() {
50797
51212
  }
50798
51213
  async function readBoolFlag(supabase, key) {
50799
51214
  const hit = cache.get(key);
50800
- if (hit && Date.now() - hit.at < CACHE_TTL_MS)
51215
+ if (hit && Date.now() - hit.at < CACHE_TTL_MS2)
50801
51216
  return hit.value;
50802
51217
  try {
50803
51218
  const { data, error } = await supabase.rpc("cerefox_get_config", { p_key: key });
@@ -50819,7 +51234,7 @@ async function reviewWorkflowEnabled(supabase) {
50819
51234
  function disabledToolMessage(name) {
50820
51235
  return `${name} is part of the document-relations feature, which is off by default. ` + `Enable it with: cerefox config set relations_enabled true ` + `(deployment-wide; every access path picks it up).`;
50821
51236
  }
50822
- var RELATION_TOOL_NAMES, CACHE_TTL_MS = 15000, cache;
51237
+ var RELATION_TOOL_NAMES, CACHE_TTL_MS2 = 15000, cache;
50823
51238
  var init_feature_flags = __esm(() => {
50824
51239
  RELATION_TOOL_NAMES = new Set([
50825
51240
  "cerefox_set_relation",
@@ -53584,7 +53999,7 @@ var require_cli_progress = __commonJS(function(exports, module) {
53584
53999
  });
53585
54000
 
53586
54001
  // src/cli/commands/sync-self-docs.ts
53587
- import { readFileSync as readFileSync13 } from "node:fs";
54002
+ import { readFileSync as readFileSync14 } from "node:fs";
53588
54003
  import { basename as basename4, extname as extname5 } from "node:path";
53589
54004
  async function runSyncSelfDocs(options = {}) {
53590
54005
  const project = options.project ?? "_cerefox-self-docs";
@@ -53611,7 +54026,7 @@ async function runSyncSelfDocs(options = {}) {
53611
54026
  const authorType = resolveAuthorType("agent");
53612
54027
  const outcomes = [];
53613
54028
  for (const doc of docs) {
53614
- const content = readFileSync13(doc.path, "utf8");
54029
+ const content = readFileSync14(doc.path, "utf8");
53615
54030
  const m = content.match(/^#\s+(.+)$/m);
53616
54031
  const title = m ? m[1].trim() : basename4(doc.path, extname5(doc.path));
53617
54032
  try {
@@ -53694,7 +54109,7 @@ var init_v4_mini = __esm(() => {
53694
54109
  init_external2();
53695
54110
  });
53696
54111
 
53697
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
54112
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
53698
54113
  function isZ4Schema(s) {
53699
54114
  const schema = s;
53700
54115
  return !!schema._zod;
@@ -53770,7 +54185,7 @@ var init_v4 = __esm(() => {
53770
54185
  init_classic();
53771
54186
  });
53772
54187
 
53773
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
54188
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
53774
54189
  var LATEST_PROTOCOL_VERSION = "2025-11-25", SUPPORTED_PROTOCOL_VERSIONS, RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task", JSONRPC_VERSION = "2.0", AssertObjectSchema, ProgressTokenSchema, CursorSchema, TaskCreationParamsSchema, TaskMetadataSchema, RelatedTaskMetadataSchema, RequestMetaSchema, BaseRequestParamsSchema, TaskAugmentedRequestParamsSchema, isTaskAugmentedRequestParams = (value) => TaskAugmentedRequestParamsSchema.safeParse(value).success, RequestSchema, NotificationsParamsSchema, NotificationSchema, ResultSchema, RequestIdSchema, JSONRPCRequestSchema, isJSONRPCRequest = (value) => JSONRPCRequestSchema.safeParse(value).success, JSONRPCNotificationSchema, isJSONRPCNotification = (value) => JSONRPCNotificationSchema.safeParse(value).success, JSONRPCResultResponseSchema, isJSONRPCResultResponse = (value) => JSONRPCResultResponseSchema.safeParse(value).success, ErrorCode, JSONRPCErrorResponseSchema, isJSONRPCErrorResponse = (value) => JSONRPCErrorResponseSchema.safeParse(value).success, JSONRPCMessageSchema, JSONRPCResponseSchema, EmptyResultSchema, CancelledNotificationParamsSchema, CancelledNotificationSchema, IconSchema, IconsSchema, BaseMetadataSchema, ImplementationSchema, FormElicitationCapabilitySchema, ElicitationCapabilitySchema, ClientTasksCapabilitySchema, ServerTasksCapabilitySchema, ClientCapabilitiesSchema, InitializeRequestParamsSchema, InitializeRequestSchema, ServerCapabilitiesSchema, InitializeResultSchema, InitializedNotificationSchema, PingRequestSchema, ProgressSchema, ProgressNotificationParamsSchema, ProgressNotificationSchema, PaginatedRequestParamsSchema, PaginatedRequestSchema, PaginatedResultSchema, TaskStatusSchema, TaskSchema, CreateTaskResultSchema, TaskStatusNotificationParamsSchema, TaskStatusNotificationSchema, GetTaskRequestSchema, GetTaskResultSchema, GetTaskPayloadRequestSchema, GetTaskPayloadResultSchema, ListTasksRequestSchema, ListTasksResultSchema, CancelTaskRequestSchema, CancelTaskResultSchema, ResourceContentsSchema, TextResourceContentsSchema, Base64Schema, BlobResourceContentsSchema, RoleSchema, AnnotationsSchema, ResourceSchema, ResourceTemplateSchema, ListResourcesRequestSchema, ListResourcesResultSchema, ListResourceTemplatesRequestSchema, ListResourceTemplatesResultSchema, ResourceRequestParamsSchema, ReadResourceRequestParamsSchema, ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, SubscribeRequestParamsSchema, SubscribeRequestSchema, UnsubscribeRequestParamsSchema, UnsubscribeRequestSchema, ResourceUpdatedNotificationParamsSchema, ResourceUpdatedNotificationSchema, PromptArgumentSchema, PromptSchema, ListPromptsRequestSchema, ListPromptsResultSchema, GetPromptRequestParamsSchema, GetPromptRequestSchema, TextContentSchema, ImageContentSchema, AudioContentSchema, ToolUseContentSchema, EmbeddedResourceSchema, ResourceLinkSchema, ContentBlockSchema, PromptMessageSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ToolAnnotationsSchema, ToolExecutionSchema, ToolSchema, ListToolsRequestSchema, ListToolsResultSchema, CallToolResultSchema, CompatibilityCallToolResultSchema, CallToolRequestParamsSchema, CallToolRequestSchema, ToolListChangedNotificationSchema, ListChangedOptionsBaseSchema, LoggingLevelSchema, SetLevelRequestParamsSchema, SetLevelRequestSchema, LoggingMessageNotificationParamsSchema, LoggingMessageNotificationSchema, ModelHintSchema, ModelPreferencesSchema, ToolChoiceSchema, ToolResultContentSchema, SamplingContentSchema, SamplingMessageContentBlockSchema, SamplingMessageSchema, CreateMessageRequestParamsSchema, CreateMessageRequestSchema, CreateMessageResultSchema, CreateMessageResultWithToolsSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema, UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema, LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema, EnumSchemaSchema, PrimitiveSchemaDefinitionSchema, ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema, ElicitRequestParamsSchema, ElicitRequestSchema, ElicitationCompleteNotificationParamsSchema, ElicitationCompleteNotificationSchema, ElicitResultSchema, ResourceTemplateReferenceSchema, PromptReferenceSchema, CompleteRequestParamsSchema, CompleteRequestSchema, CompleteResultSchema, RootSchema, ListRootsRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, ClientRequestSchema, ClientNotificationSchema, ClientResultSchema, ServerRequestSchema, ServerNotificationSchema, ServerResultSchema, McpError, UrlElicitationRequiredError;
53775
54190
  var init_types3 = __esm(() => {
53776
54191
  init_v4();
@@ -54598,7 +55013,7 @@ var init_types3 = __esm(() => {
54598
55013
  };
54599
55014
  });
54600
55015
 
54601
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
55016
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
54602
55017
  function isTerminal(status) {
54603
55018
  return status === "completed" || status === "failed" || status === "cancelled";
54604
55019
  }
@@ -54816,7 +55231,7 @@ var init_esm = __esm(() => {
54816
55231
  init_zodToJsonSchema();
54817
55232
  });
54818
55233
 
54819
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
55234
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
54820
55235
  function getMethodLiteral(schema) {
54821
55236
  const shape = getObjectShape(schema);
54822
55237
  const methodSchema = shape?.method;
@@ -54841,7 +55256,7 @@ var init_zod_json_schema_compat = __esm(() => {
54841
55256
  init_esm();
54842
55257
  });
54843
55258
 
54844
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
55259
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
54845
55260
  class Protocol {
54846
55261
  constructor(_options) {
54847
55262
  this._options = _options;
@@ -54877,6 +55292,9 @@ class Protocol {
54877
55292
  this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
54878
55293
  const handleTaskResult = async () => {
54879
55294
  const taskId = request.params.taskId;
55295
+ if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
55296
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
55297
+ }
54880
55298
  if (this._taskMessageQueue) {
54881
55299
  let queuedMessage;
54882
55300
  while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
@@ -54907,12 +55325,12 @@ class Protocol {
54907
55325
  throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
54908
55326
  }
54909
55327
  if (!isTerminal(task.status)) {
54910
- await this._waitForTaskUpdate(taskId, extra.signal);
55328
+ await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
54911
55329
  return await handleTaskResult();
54912
55330
  }
54913
55331
  if (isTerminal(task.status)) {
54914
55332
  const result = await this._taskStore.getTaskResult(taskId, extra.sessionId);
54915
- this._clearTaskQueue(taskId);
55333
+ this._clearTaskQueue(taskId, extra.sessionId);
54916
55334
  return {
54917
55335
  ...result,
54918
55336
  _meta: {
@@ -54949,7 +55367,7 @@ class Protocol {
54949
55367
  throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
54950
55368
  }
54951
55369
  await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
54952
- this._clearTaskQueue(request.params.taskId);
55370
+ this._clearTaskQueue(request.params.taskId, extra.sessionId);
54953
55371
  const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
54954
55372
  if (!cancelledTask) {
54955
55373
  throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
@@ -55072,6 +55490,19 @@ class Protocol {
55072
55490
  const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
55073
55491
  const capturedTransport = this._transport;
55074
55492
  const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
55493
+ const sessionId = capturedTransport?.sessionId;
55494
+ const store = this._taskStore;
55495
+ let relatedTaskFound = true;
55496
+ let relatedTaskLookup;
55497
+ if (relatedTaskId && store && this._taskMessageQueue && sessionId !== undefined) {
55498
+ relatedTaskFound = false;
55499
+ relatedTaskLookup = (async () => {
55500
+ if (!await store.getTask(relatedTaskId, sessionId)) {
55501
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
55502
+ }
55503
+ relatedTaskFound = true;
55504
+ })();
55505
+ }
55075
55506
  if (handler === undefined) {
55076
55507
  const errorResponse = {
55077
55508
  jsonrpc: "2.0",
@@ -55081,7 +55512,10 @@ class Protocol {
55081
55512
  message: "Method not found"
55082
55513
  }
55083
55514
  };
55084
- if (relatedTaskId && this._taskMessageQueue) {
55515
+ if (relatedTaskId && relatedTaskLookup) {
55516
+ const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
55517
+ relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error) => this._onerror(new Error(`Failed to send an error response: ${error}`)));
55518
+ } else if (relatedTaskId && this._taskMessageQueue) {
55085
55519
  this._enqueueTaskMessage(relatedTaskId, {
55086
55520
  type: "error",
55087
55521
  message: errorResponse,
@@ -55132,7 +55566,10 @@ class Protocol {
55132
55566
  closeSSEStream: extra?.closeSSEStream,
55133
55567
  closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
55134
55568
  };
55135
- Promise.resolve().then(() => {
55569
+ (relatedTaskLookup ?? Promise.resolve()).then(() => {
55570
+ if (relatedTaskLookup && abortController.signal.aborted) {
55571
+ throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
55572
+ }
55136
55573
  if (taskCreationParams) {
55137
55574
  this.assertTaskHandlerCapability(request.method);
55138
55575
  }
@@ -55167,7 +55604,7 @@ class Protocol {
55167
55604
  ...error["data"] !== undefined && { data: error["data"] }
55168
55605
  }
55169
55606
  };
55170
- if (relatedTaskId && this._taskMessageQueue) {
55607
+ if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
55171
55608
  await this._enqueueTaskMessage(relatedTaskId, {
55172
55609
  type: "error",
55173
55610
  message: errorResponse,
@@ -55555,7 +55992,7 @@ class Protocol {
55555
55992
  throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
55556
55993
  }
55557
55994
  const maxQueueSize = this._options?.maxTaskQueueSize;
55558
- await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
55995
+ await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
55559
55996
  }
55560
55997
  async _clearTaskQueue(taskId, sessionId) {
55561
55998
  if (this._taskMessageQueue) {
@@ -55574,10 +56011,10 @@ class Protocol {
55574
56011
  }
55575
56012
  }
55576
56013
  }
55577
- async _waitForTaskUpdate(taskId, signal) {
56014
+ async _waitForTaskUpdate(taskId, signal, sessionId) {
55578
56015
  let interval = this._options?.defaultTaskPollInterval ?? 1000;
55579
56016
  try {
55580
- const task = await this._taskStore?.getTask(taskId);
56017
+ const task = await this._taskStore?.getTask(taskId, sessionId);
55581
56018
  if (task?.pollInterval) {
55582
56019
  interval = task.pollInterval;
55583
56020
  }
@@ -62573,7 +63010,7 @@ var require_dist3 = __commonJS(function(exports, module) {
62573
63010
  exports.default = formatsPlugin;
62574
63011
  });
62575
63012
 
62576
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
63013
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
62577
63014
  function createDefaultAjvInstance() {
62578
63015
  const ajv = new import_ajv.default({
62579
63016
  strict: false,
@@ -62616,7 +63053,7 @@ var init_ajv_provider = __esm(() => {
62616
63053
  import_ajv_formats = __toESM(require_dist3(), 1);
62617
63054
  });
62618
63055
 
62619
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
63056
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/server.js
62620
63057
  class ExperimentalServerTasks {
62621
63058
  constructor(_server) {
62622
63059
  this._server = _server;
@@ -62697,7 +63134,7 @@ var init_server = __esm(() => {
62697
63134
  init_types3();
62698
63135
  });
62699
63136
 
62700
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
63137
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
62701
63138
  function assertToolsCallTaskCapability(requests, method, entityName) {
62702
63139
  if (!requests) {
62703
63140
  throw new Error(`${entityName} does not support task creation (required for ${method})`);
@@ -62732,7 +63169,7 @@ function assertClientRequestTaskCapability(requests, method, entityName) {
62732
63169
  }
62733
63170
  }
62734
63171
 
62735
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
63172
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js
62736
63173
  var Server;
62737
63174
  var init_server2 = __esm(() => {
62738
63175
  init_protocol();
@@ -63064,7 +63501,7 @@ var init_server2 = __esm(() => {
63064
63501
  };
63065
63502
  });
63066
63503
 
63067
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
63504
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
63068
63505
  class ReadBuffer {
63069
63506
  constructor(options) {
63070
63507
  this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
@@ -63107,7 +63544,7 @@ var init_stdio = __esm(() => {
63107
63544
  STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
63108
63545
  });
63109
63546
 
63110
- // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.30.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
63547
+ // ../../node_modules/.bun/@modelcontextprotocol+sdk@1.32.1/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
63111
63548
  import process10 from "node:process";
63112
63549
 
63113
63550
  class StdioServerTransport {
@@ -63185,6 +63622,7 @@ function buildServer() {
63185
63622
  auth: { persistSession: false, autoRefreshToken: false }
63186
63623
  });
63187
63624
  warnIfSchemaVersionMismatch(supabase);
63625
+ Promise.resolve().then(() => (init_update_check(), {})).then((m) => refreshLatest()).catch(() => {});
63188
63626
  const ctx = {
63189
63627
  accessPath: "local-mcp",
63190
63628
  openaiApiKey: settings.openaiApiKey || undefined
@@ -63230,7 +63668,7 @@ async function warnIfSchemaVersionMismatch(supabase) {
63230
63668
  if (!bundled)
63231
63669
  return;
63232
63670
  if (compareSemver(deployed, bundled) < 0) {
63233
- process.stderr.write(`[cerefox-mcp] ⚠ schema version mismatch: this client bundles v${bundled} ` + `but the deployed schema is v${deployed}. Run \`cerefox server deploy\` ` + `to update it. Tools may behave unexpectedly until then.
63671
+ process.stderr.write(`[cerefox-mcp] ⚠ schema version mismatch: this client bundles v${bundled} but the deployed schema is v${deployed}. Run \`cerefox server deploy\` to update it. Tools may behave unexpectedly until then.
63234
63672
  `);
63235
63673
  }
63236
63674
  } catch {}
@@ -71859,182 +72297,16 @@ init_cli_core();
71859
72297
 
71860
72298
  // src/cli/util/checks.ts
71861
72299
  init_meta();
72300
+ init_update_check();
71862
72301
  init_ef_meta();
71863
72302
  init_config();
71864
72303
  init_config();
71865
72304
  init_compatibility();
71866
72305
  init_server_assets();
71867
- import { existsSync as existsSync11, readFileSync as readFileSync9, realpathSync, statSync as statSync3 } from "node:fs";
72306
+ init_daemon();
72307
+ import { existsSync as existsSync12, readFileSync as readFileSync10, realpathSync, statSync as statSync3 } from "node:fs";
71868
72308
  import { homedir as homedir7 } from "node:os";
71869
- import { join as join10 } from "node:path";
71870
-
71871
- // src/web/daemon.ts
71872
- import { spawn } from "node:child_process";
71873
- import {
71874
- existsSync as existsSync9,
71875
- mkdirSync as mkdirSync3,
71876
- openSync,
71877
- readFileSync as readFileSync8,
71878
- rmSync,
71879
- writeFileSync as writeFileSync4
71880
- } from "node:fs";
71881
- import { homedir as homedir5 } from "node:os";
71882
- import { join as join8 } from "node:path";
71883
- function resolveStateDir(override = process.env.CEREFOX_CONFIG_DIR, home = homedir5()) {
71884
- override = (override ?? "").trim();
71885
- if (!override)
71886
- return join8(home, ".cerefox");
71887
- return override === "~" || override.startsWith("~/") ? join8(home, override.slice(2)) : override;
71888
- }
71889
- var STATE_DIR = resolveStateDir();
71890
- var PID_FILE = join8(STATE_DIR, "web.pid");
71891
- var LOG_FILE = join8(STATE_DIR, "web.log");
71892
- var daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
71893
- function ensureStateDir() {
71894
- if (!existsSync9(STATE_DIR))
71895
- mkdirSync3(STATE_DIR, { recursive: true });
71896
- }
71897
- function readPidFile() {
71898
- if (!existsSync9(PID_FILE))
71899
- return null;
71900
- try {
71901
- const parsed = JSON.parse(readFileSync8(PID_FILE, "utf8"));
71902
- if (typeof parsed.pid !== "number")
71903
- return null;
71904
- return {
71905
- pid: parsed.pid,
71906
- port: typeof parsed.port === "number" ? parsed.port : 8000,
71907
- host: typeof parsed.host === "string" ? parsed.host : "127.0.0.1",
71908
- startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : "unknown"
71909
- };
71910
- } catch {
71911
- return null;
71912
- }
71913
- }
71914
- function writePidFile(info) {
71915
- ensureStateDir();
71916
- writeFileSync4(PID_FILE, JSON.stringify(info, null, 2) + `
71917
- `, "utf8");
71918
- }
71919
- function removePidFile() {
71920
- rmSync(PID_FILE, { force: true });
71921
- }
71922
- function isProcessAlive(pid) {
71923
- try {
71924
- process.kill(pid, 0);
71925
- return true;
71926
- } catch (err) {
71927
- return err.code === "EPERM";
71928
- }
71929
- }
71930
- async function probeVersion(host, port, timeoutMs = 1500) {
71931
- const probeHost = host === "0.0.0.0" ? "127.0.0.1" : host;
71932
- try {
71933
- const ctrl = new AbortController;
71934
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
71935
- try {
71936
- const resp = await fetch(`http://${probeHost}:${port}/api/v1/version`, {
71937
- signal: ctrl.signal
71938
- });
71939
- if (!resp.ok)
71940
- return { responding: false, version: null };
71941
- const body = await resp.json().catch(() => null);
71942
- return {
71943
- responding: true,
71944
- version: typeof body?.version === "string" ? body.version : null
71945
- };
71946
- } finally {
71947
- clearTimeout(timer);
71948
- }
71949
- } catch {
71950
- return { responding: false, version: null };
71951
- }
71952
- }
71953
- async function isResponding(host, port, timeoutMs = 1500) {
71954
- return (await probeVersion(host, port, timeoutMs)).responding;
71955
- }
71956
- var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
71957
- function assertUnix() {
71958
- if (process.platform === "win32") {
71959
- throw new Error("Daemon mode (`cerefox web start/stop/status`) is not supported on Windows yet. " + "Run `cerefox web` in the foreground, or set up a Windows service manually.");
71960
- }
71961
- }
71962
- async function startDaemon(opts) {
71963
- assertUnix();
71964
- ensureStateDir();
71965
- const existing = readPidFile();
71966
- if (existing && isProcessAlive(existing.pid)) {
71967
- return existing.port === opts.port ? { kind: "already-running", info: existing } : { kind: "port-conflict", info: existing };
71968
- }
71969
- if (existing)
71970
- removePidFile();
71971
- const logFd = openSync(LOG_FILE, "a");
71972
- const child = spawn(opts.runtime, [opts.scriptPath, "web", "--host", opts.host, "--port", String(opts.port)], { detached: true, stdio: ["ignore", logFd, logFd] });
71973
- child.unref();
71974
- if (typeof child.pid !== "number") {
71975
- throw new Error("Failed to spawn the web server process (no pid).");
71976
- }
71977
- writePidFile({
71978
- pid: child.pid,
71979
- port: opts.port,
71980
- host: opts.host,
71981
- startedAt: new Date().toISOString()
71982
- });
71983
- let responding = false;
71984
- for (let i = 0;i < 20; i++) {
71985
- if (!isProcessAlive(child.pid))
71986
- break;
71987
- if (await isResponding(opts.host, opts.port)) {
71988
- responding = true;
71989
- break;
71990
- }
71991
- await sleep2(250);
71992
- }
71993
- return { kind: "started", pid: child.pid, responding };
71994
- }
71995
- async function stopDaemon() {
71996
- assertUnix();
71997
- const info = readPidFile();
71998
- if (!info || !isProcessAlive(info.pid)) {
71999
- removePidFile();
72000
- return { kind: "not-running" };
72001
- }
72002
- try {
72003
- process.kill(info.pid, "SIGTERM");
72004
- } catch {}
72005
- let forced = false;
72006
- let alive = true;
72007
- for (let i = 0;i < 12; i++) {
72008
- await sleep2(250);
72009
- if (!isProcessAlive(info.pid)) {
72010
- alive = false;
72011
- break;
72012
- }
72013
- }
72014
- if (alive) {
72015
- try {
72016
- process.kill(info.pid, "SIGKILL");
72017
- forced = true;
72018
- } catch {}
72019
- }
72020
- removePidFile();
72021
- return { kind: "stopped", pid: info.pid, forced };
72022
- }
72023
- function restartCommand(host, port) {
72024
- const flags = `${host === "127.0.0.1" ? "" : ` --host ${host}`}${port === 8000 ? "" : ` --port ${port}`}`;
72025
- return `cerefox web stop && cerefox web start${flags}`;
72026
- }
72027
- async function statusDaemon() {
72028
- const info = readPidFile();
72029
- if (!info)
72030
- return { kind: "stopped" };
72031
- if (!isProcessAlive(info.pid))
72032
- return { kind: "stale", info };
72033
- const { responding, version } = await probeVersion(info.host, info.port);
72034
- return { kind: "running", info, responding, version };
72035
- }
72036
-
72037
- // src/cli/util/checks.ts
72309
+ import { join as join11 } from "node:path";
72038
72310
  function checkBinary() {
72039
72311
  return {
72040
72312
  name: "binary",
@@ -72074,6 +72346,28 @@ function checkVersion() {
72074
72346
  detail: `cerefox v${PKG_VERSION}`
72075
72347
  };
72076
72348
  }
72349
+ async function checkUpdates() {
72350
+ const name = "updates";
72351
+ if (updateCheckDisabled()) {
72352
+ return { name, status: "skipped", detail: "update check is off (CEREFOX_NO_UPDATE_CHECK, NO_UPDATE_NOTIFIER or CI)" };
72353
+ }
72354
+ let latest;
72355
+ try {
72356
+ latest = await fetchLatestVersion();
72357
+ recordLatest(latest);
72358
+ } catch {
72359
+ return { name, status: "skipped", detail: "could not reach the npm registry to check for a newer release" };
72360
+ }
72361
+ const newer = newerRelease(latest);
72362
+ if (!newer)
72363
+ return { name, status: "ok", detail: `up to date (latest release: v${latest})` };
72364
+ return {
72365
+ name,
72366
+ status: "skipped",
72367
+ detail: `v${newer} is available (installed: v${PKG_VERSION})`,
72368
+ hint: `Run \`${upgradeCommand()}\`. Release notes: ${releaseNotesUrl(newer)}`
72369
+ };
72370
+ }
72077
72371
  function checkConfig() {
72078
72372
  let envPath;
72079
72373
  try {
@@ -72086,7 +72380,7 @@ function checkConfig() {
72086
72380
  hint: "Run `cerefox init` to bootstrap."
72087
72381
  };
72088
72382
  }
72089
- if (!existsSync11(envPath)) {
72383
+ if (!existsSync12(envPath)) {
72090
72384
  const settings = loadSettings();
72091
72385
  if (settings.supabaseUrl && settings.supabaseKey) {
72092
72386
  return {
@@ -72514,6 +72808,47 @@ async function probeRpcJson(settings, fn) {
72514
72808
  return { kind: "error", detail: err instanceof Error ? err.message : String(err) };
72515
72809
  }
72516
72810
  }
72811
+ var CHUNK_COUNT_WARN_AT = 1e5;
72812
+ function classifyChunkCount(count) {
72813
+ const n = count.toLocaleString("en-US");
72814
+ if (count <= CHUNK_COUNT_WARN_AT) {
72815
+ return { name: "chunks", status: "ok", detail: `${n} current chunk(s)` };
72816
+ }
72817
+ return {
72818
+ name: "chunks",
72819
+ status: "warn",
72820
+ detail: `${n} current chunks: past ${CHUNK_COUNT_WARN_AT.toLocaleString("en-US")}, search's exact vector scan may slow down`,
72821
+ hint: "Search still returns correct results; it may just take longer. Large-store support (a tuned vector index) is tracked in https://github.com/fstamatelopoulos/cerefox/issues/328; comment there with your chunk count."
72822
+ };
72823
+ }
72824
+ async function checkChunkCount() {
72825
+ const settings = loadSettings();
72826
+ if (!settings.supabaseUrl || !settings.supabaseKey) {
72827
+ return { name: "chunks", status: "skipped", detail: "Supabase config missing; skipped." };
72828
+ }
72829
+ try {
72830
+ const url = `${settings.supabaseUrl.replace(/\/$/, "")}/rest/v1/cerefox_chunks?select=id&version_id=is.null`;
72831
+ const resp = await fetch(url, {
72832
+ method: "HEAD",
72833
+ headers: {
72834
+ apikey: settings.supabaseKey,
72835
+ Authorization: `Bearer ${settings.supabaseKey}`,
72836
+ Prefer: "count=exact"
72837
+ }
72838
+ });
72839
+ const total = Number((resp.headers.get("content-range") ?? "").split("/")[1]);
72840
+ if (!resp.ok || !Number.isFinite(total)) {
72841
+ return { name: "chunks", status: "skipped", detail: `chunk count unavailable (HTTP ${resp.status})` };
72842
+ }
72843
+ return classifyChunkCount(total);
72844
+ } catch (err) {
72845
+ return {
72846
+ name: "chunks",
72847
+ status: "skipped",
72848
+ detail: `chunk count unavailable (${err instanceof Error ? err.message : String(err)})`
72849
+ };
72850
+ }
72851
+ }
72517
72852
  var CONTENT_FORMAT_CHECK_NAME = "content format";
72518
72853
  async function checkContentFormat() {
72519
72854
  const settings = loadSettings();
@@ -72587,10 +72922,10 @@ async function checkMetadataHealth() {
72587
72922
  };
72588
72923
  }
72589
72924
  function hasCerefoxInJsonFile(path) {
72590
- if (!existsSync11(path))
72925
+ if (!existsSync12(path))
72591
72926
  return false;
72592
72927
  try {
72593
- const parsed = JSON.parse(readFileSync9(path, "utf8"));
72928
+ const parsed = JSON.parse(readFileSync10(path, "utf8"));
72594
72929
  const mcpServers = parsed.mcpServers;
72595
72930
  return Boolean(mcpServers && typeof mcpServers === "object" && "cerefox" in mcpServers);
72596
72931
  } catch {
@@ -72647,9 +72982,9 @@ async function checkWebDaemon() {
72647
72982
  }
72648
72983
  function checkMcpConfigs() {
72649
72984
  const home = homedir7();
72650
- const claudeCodeUser = join10(home, ".claude.json");
72651
- const claudeCodeProj = join10(process.cwd(), ".mcp.json");
72652
- const claudeDesktop = process.platform === "darwin" ? join10(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join10(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join10(home, ".config", "Claude", "claude_desktop_config.json");
72985
+ const claudeCodeUser = join11(home, ".claude.json");
72986
+ const claudeCodeProj = join11(process.cwd(), ".mcp.json");
72987
+ const claudeDesktop = process.platform === "darwin" ? join11(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join11(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join11(home, ".config", "Claude", "claude_desktop_config.json");
72653
72988
  const found = [];
72654
72989
  if (hasCerefoxInJsonFile(claudeCodeUser))
72655
72990
  found.push("Claude Code (user)");
@@ -72673,8 +73008,8 @@ function checkMcpConfigs() {
72673
73008
  }
72674
73009
  function checkLegacyShadowEnv(opts = {}) {
72675
73010
  const activeEnv = resolveEnvFile(opts);
72676
- const cwdEnv = join10(opts.cwd ?? process.cwd(), ".env");
72677
- if (!existsSync11(activeEnv) || !existsSync11(cwdEnv))
73011
+ const cwdEnv = join11(opts.cwd ?? process.cwd(), ".env");
73012
+ if (!existsSync12(activeEnv) || !existsSync12(cwdEnv))
72678
73013
  return null;
72679
73014
  try {
72680
73015
  if (realpathSync(activeEnv) === realpathSync(cwdEnv))
@@ -72836,6 +73171,7 @@ async function runAllChecks(opts = {}) {
72836
73171
  { name: "binary", phase: "Locating binary", run: () => checkBinary() },
72837
73172
  { name: "runtime", phase: "Inspecting runtime", run: () => checkRuntime() },
72838
73173
  { name: "version", phase: "Reading package version", run: () => checkVersion() },
73174
+ { name: "updates", phase: "Checking npm for a newer release", run: () => checkUpdates() },
72839
73175
  { name: "config", phase: "Resolving config", run: () => checkConfig() },
72840
73176
  { name: "legacy env", phase: "Checking legacy env shadowing", run: () => checkLegacyShadowEnv() },
72841
73177
  { name: "retired env", phase: "Checking retired env vars", run: () => checkRetiredEnvVars() },
@@ -72845,6 +73181,7 @@ async function runAllChecks(opts = {}) {
72845
73181
  { name: "review workflow", phase: "Reading review workflow flag", run: () => checkReviewWorkflow() },
72846
73182
  { name: "trash auto-purge", phase: "Reading trash auto-purge settings", run: () => checkTrashAutoPurge() },
72847
73183
  { name: "embedder", phase: "Checking embedder consistency", run: () => checkEmbedderMismatch() },
73184
+ { name: "chunks", phase: "Counting current chunks", run: () => checkChunkCount() },
72848
73185
  { name: "content format", phase: "Checking chunk reconstruction format", run: () => checkContentFormat() },
72849
73186
  { name: "metadata health", phase: "Checking metadata well-formedness", run: () => checkMetadataHealth() },
72850
73187
  { name: "edge functions", phase: "Probing Edge Function versions", run: () => checkEdgeFunctionsCompat() },
@@ -73246,11 +73583,11 @@ init_dist4();
73246
73583
  init_escape_heuristic();
73247
73584
  init_cli_core();
73248
73585
  init_config();
73249
- import { readFileSync as readFileSync11 } from "node:fs";
73586
+ import { readFileSync as readFileSync12 } from "node:fs";
73250
73587
  import { basename as basename2, extname as extname3 } from "node:path";
73251
73588
 
73252
73589
  // src/ingestion/pipeline.ts
73253
- import { readFileSync as readFileSync10 } from "node:fs";
73590
+ import { readFileSync as readFileSync11 } from "node:fs";
73254
73591
  import { basename, extname as extname2, resolve as resolve3 } from "node:path";
73255
73592
  // ../../_shared/ingest/pipeline-helpers.ts
73256
73593
  import { createHash } from "node:crypto";
@@ -73920,7 +74257,7 @@ class IngestionPipeline {
73920
74257
  };
73921
74258
  }
73922
74259
  async ingestFile(path, opts = {}) {
73923
- const text = await fileToMarkdown(path, readFileSync10(path));
74260
+ const text = await fileToMarkdown(path, readFileSync11(path));
73924
74261
  const absPath = resolve3(path);
73925
74262
  const stem = basename(absPath, extname2(absPath));
73926
74263
  return this.ingestText({
@@ -73951,11 +74288,11 @@ async function readContent(path, paste) {
73951
74288
  return { content, titleFromPath: undefined };
73952
74289
  }
73953
74290
  if (!path) {
73954
- throw userError("Provide a file path or use --paste.", "Example: cerefox ingest notes.md --title 'My Note'");
74291
+ throw userError("Provide a file path or use --paste.", "Example: cerefox document ingest notes.md --title 'My Note'");
73955
74292
  }
73956
74293
  let content;
73957
74294
  try {
73958
- content = readFileSync11(path, "utf8");
74295
+ content = readFileSync12(path, "utf8");
73959
74296
  } catch (err) {
73960
74297
  const msg = err instanceof Error ? err.message : String(err);
73961
74298
  throw userError(`Cannot read ${path}: ${msg}`);
@@ -74065,14 +74402,14 @@ init_cli_core();
74065
74402
  init_mcp_tools();
74066
74403
  init_config();
74067
74404
  init_client();
74068
- import { readFileSync as readFileSync12 } from "node:fs";
74405
+ import { readFileSync as readFileSync13 } from "node:fs";
74069
74406
  function resolveText(value, what) {
74070
74407
  if (value === undefined)
74071
74408
  throw userError(`${what} is required.`);
74072
74409
  if (value === "-")
74073
- return readFileSync12(0, "utf8");
74410
+ return readFileSync13(0, "utf8");
74074
74411
  if (value.startsWith("@"))
74075
- return readFileSync12(value.slice(1), "utf8");
74412
+ return readFileSync13(value.slice(1), "utf8");
74076
74413
  return value;
74077
74414
  }
74078
74415
  function context() {
@@ -74133,7 +74470,7 @@ init_cli_core();
74133
74470
  init_config();
74134
74471
  var import_cli_progress = __toESM(require_cli_progress(), 1);
74135
74472
  import { readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
74136
- import { basename as basename3, extname as extname4, join as join11 } from "node:path";
74473
+ import { basename as basename3, extname as extname4, join as join12 } from "node:path";
74137
74474
  function walk(dir, extensions) {
74138
74475
  let entries;
74139
74476
  try {
@@ -74143,7 +74480,7 @@ function walk(dir, extensions) {
74143
74480
  }
74144
74481
  const files = [];
74145
74482
  for (const name of entries) {
74146
- const full = join11(dir, name);
74483
+ const full = join12(dir, name);
74147
74484
  let stat;
74148
74485
  try {
74149
74486
  stat = statSync4(full);
@@ -74246,20 +74583,20 @@ import { spawnSync as spawnSync4 } from "node:child_process";
74246
74583
  import {
74247
74584
  chmodSync,
74248
74585
  copyFileSync as copyFileSync2,
74249
- existsSync as existsSync12,
74250
- mkdirSync as mkdirSync5,
74251
- readFileSync as readFileSync14,
74252
- writeFileSync as writeFileSync6
74586
+ existsSync as existsSync13,
74587
+ mkdirSync as mkdirSync6,
74588
+ readFileSync as readFileSync15,
74589
+ writeFileSync as writeFileSync7
74253
74590
  } from "node:fs";
74254
74591
  import { homedir as homedir8 } from "node:os";
74255
- import { dirname as dirname5, join as join12 } from "node:path";
74592
+ import { dirname as dirname6, join as join13 } from "node:path";
74256
74593
  async function readConfigFile(path) {
74257
- if (!existsSync12(path)) {
74594
+ if (!existsSync13(path)) {
74258
74595
  throw userError(`--config file not found: ${path}`);
74259
74596
  }
74260
74597
  let parsed;
74261
74598
  try {
74262
- parsed = JSON.parse(readFileSync14(path, "utf8"));
74599
+ parsed = JSON.parse(readFileSync15(path, "utf8"));
74263
74600
  } catch (err) {
74264
74601
  throw userError(`--config: invalid JSON in ${path}: ${err instanceof Error ? err.message : String(err)}`);
74265
74602
  }
@@ -74301,7 +74638,7 @@ function parseDotEnvFile(content) {
74301
74638
  return map;
74302
74639
  }
74303
74640
  function answersFromEnvFile(path) {
74304
- const parsed = parseDotEnvFile(readFileSync14(path, "utf8"));
74641
+ const parsed = parseDotEnvFile(readFileSync15(path, "utf8"));
74305
74642
  const required = ["CEREFOX_SUPABASE_URL", "CEREFOX_SUPABASE_KEY", "OPENAI_API_KEY"];
74306
74643
  for (const key of required) {
74307
74644
  if (!parsed[key] || parsed[key].trim() === "") {
@@ -74525,7 +74862,7 @@ async function postWriteLifecycle(envPath, options) {
74525
74862
  await Promise.resolve().then(() => init_sync_self_docs());
74526
74863
  await runSyncSelfDocs({});
74527
74864
  } catch (err) {
74528
- warn(`Self-doc ingest failed: ${err instanceof Error ? err.message : String(err)}. Run \`cerefox sync-self-docs\` manually after init.`);
74865
+ warn(`Self-doc ingest failed: ${err instanceof Error ? err.message : String(err)}. Run \`cerefox guides ingest\` manually after init.`);
74529
74866
  }
74530
74867
  println("");
74531
74868
  }
@@ -74549,15 +74886,15 @@ async function postWriteLifecycle(envPath, options) {
74549
74886
  }
74550
74887
  println("");
74551
74888
  println(c.green("Done. Try:"));
74552
- println(c.dim(" cerefox doctor # verify everything"));
74553
- println(c.dim(' cerefox search "…" # search the KB'));
74554
- println(c.dim(" cerefox ingest <file> # add a doc"));
74889
+ println(c.dim(" cerefox doctor # verify everything"));
74890
+ println(c.dim(' cerefox search "…" # search the KB'));
74891
+ println(c.dim(" cerefox document ingest <file> # add a doc"));
74555
74892
  println("");
74556
74893
  println(c.dim(` Config in effect: ${envPath}`));
74557
74894
  }
74558
74895
  function writeAnswersTo(target, answers) {
74559
- mkdirSync5(dirname5(target), { recursive: true });
74560
- writeFileSync6(target, buildEnvFile(answers), "utf8");
74896
+ mkdirSync6(dirname6(target), { recursive: true });
74897
+ writeFileSync7(target, buildEnvFile(answers), "utf8");
74561
74898
  if (process.platform !== "win32") {
74562
74899
  try {
74563
74900
  chmodSync(target, 384);
@@ -74567,12 +74904,12 @@ function writeAnswersTo(target, answers) {
74567
74904
  }
74568
74905
  }
74569
74906
  async function action23(options) {
74570
- const homeEnv = join12(homedir8(), USER_STATE_DIR_NAME, ".env");
74571
- const cwdEnv = join12(process.cwd(), ".env");
74907
+ const homeEnv = join13(homedir8(), USER_STATE_DIR_NAME, ".env");
74908
+ const cwdEnv = join13(process.cwd(), ".env");
74572
74909
  const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
74573
74910
  if (explicitDir) {
74574
74911
  const target = resolveEnvFile();
74575
- if (existsSync12(target) && !options.force) {
74912
+ if (existsSync13(target) && !options.force) {
74576
74913
  println(c.yellow(`⚠ Config already exists at ${target}.`));
74577
74914
  const ok = await confirm("Overwrite?", true);
74578
74915
  if (!ok) {
@@ -74594,7 +74931,7 @@ async function action23(options) {
74594
74931
  await postWriteLifecycle(target, options);
74595
74932
  return;
74596
74933
  }
74597
- if (existsSync12(homeEnv) && !options.force) {
74934
+ if (existsSync13(homeEnv) && !options.force) {
74598
74935
  println(c.yellow(`⚠ Config already exists at ${homeEnv}.`));
74599
74936
  const ok = await confirm("Overwrite?", true);
74600
74937
  if (!ok) {
@@ -74615,12 +74952,12 @@ async function action23(options) {
74615
74952
  await postWriteLifecycle(homeEnv, options);
74616
74953
  return;
74617
74954
  }
74618
- if (existsSync12(cwdEnv) && !options.force && !options.config) {
74955
+ if (existsSync13(cwdEnv) && !options.force && !options.config) {
74619
74956
  printMigrationMenu(cwdEnv, homeEnv);
74620
74957
  const ch = await promptMigrationChoice();
74621
74958
  println("");
74622
74959
  if (ch === "c") {
74623
- mkdirSync5(dirname5(homeEnv), { recursive: true });
74960
+ mkdirSync6(dirname6(homeEnv), { recursive: true });
74624
74961
  copyFileSync2(cwdEnv, homeEnv);
74625
74962
  if (process.platform !== "win32") {
74626
74963
  try {
@@ -74693,7 +75030,7 @@ async function action24(options) {
74693
75030
  throw systemError(`Project lookup failed: ${projectError.message}`);
74694
75031
  }
74695
75032
  if (!project) {
74696
- throw userError(`Project "${options.project}" not found.`, "Run `cerefox list-projects` to see available names.");
75033
+ throw userError(`Project "${options.project}" not found.`, "Run `cerefox project list` to see available names.");
74697
75034
  }
74698
75035
  projectId = project.id;
74699
75036
  }
@@ -75237,35 +75574,35 @@ function registerMigrateFormat(program) {
75237
75574
  // src/cli/commands/restore.ts
75238
75575
  init_cli_core();
75239
75576
  init_client();
75240
- import { existsSync as existsSync13, readFileSync as readFileSync15, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
75577
+ import { existsSync as existsSync14, readFileSync as readFileSync16, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
75241
75578
  import { homedir as homedir9 } from "node:os";
75242
- import { join as join13, resolve as resolve4 } from "node:path";
75579
+ import { join as join14, resolve as resolve4 } from "node:path";
75243
75580
  function expandHome2(path) {
75244
75581
  if (path === "~")
75245
75582
  return homedir9();
75246
75583
  if (path.startsWith("~/"))
75247
- return join13(homedir9(), path.slice(2));
75584
+ return join14(homedir9(), path.slice(2));
75248
75585
  return path;
75249
75586
  }
75250
75587
  function resolveBackupFile(target) {
75251
75588
  const path = resolve4(expandHome2(target));
75252
- if (!existsSync13(path)) {
75589
+ if (!existsSync14(path)) {
75253
75590
  throw userError(`Backup path not found: ${target}`);
75254
75591
  }
75255
75592
  const stat = statSync5(path);
75256
75593
  if (stat.isFile())
75257
75594
  return path;
75258
- const candidates = readdirSync5(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync5(join13(path, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
75595
+ const candidates = readdirSync5(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync5(join14(path, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
75259
75596
  if (candidates.length === 0) {
75260
75597
  throw userError(`No cerefox-*.json files in ${path}`);
75261
75598
  }
75262
- return join13(path, candidates[0].name);
75599
+ return join14(path, candidates[0].name);
75263
75600
  }
75264
75601
  async function action31(target, options) {
75265
75602
  const file = resolveBackupFile(target);
75266
75603
  let payload;
75267
75604
  try {
75268
- payload = JSON.parse(readFileSync15(file, "utf8"));
75605
+ payload = JSON.parse(readFileSync16(file, "utf8"));
75269
75606
  } catch (err) {
75270
75607
  throw userError(`Could not parse backup file ${file}: ${err instanceof Error ? err.message : String(err)}`);
75271
75608
  }
@@ -75595,6 +75932,8 @@ function registerSearch(program) {
75595
75932
  // src/cli/commands/self-update.ts
75596
75933
  init_cli_core();
75597
75934
  init_meta();
75935
+ init_update_check();
75936
+ init_daemon();
75598
75937
  import { spawnSync as spawnSync5 } from "node:child_process";
75599
75938
  function detectRuntime() {
75600
75939
  const bin = (process.argv[1] ?? "").toLowerCase();
@@ -75625,21 +75964,19 @@ function detectRuntime() {
75625
75964
  description: "npm"
75626
75965
  };
75627
75966
  }
75628
- async function fetchLatestVersion() {
75629
- const resp = await fetch("https://registry.npmjs.org/@cerefox%2Fmemory/latest");
75630
- if (!resp.ok) {
75631
- throw systemError(`Could not query npm registry: ${resp.status} ${resp.statusText}`);
75632
- }
75633
- const body = await resp.json();
75634
- if (!body.version) {
75635
- throw systemError("npm registry response missing version field.");
75967
+ async function fetchLatest() {
75968
+ try {
75969
+ const latest = await fetchLatestVersion({ timeoutMs: 15000 });
75970
+ recordLatest(latest);
75971
+ return latest;
75972
+ } catch (err) {
75973
+ throw systemError(`Could not query the npm registry: ${err instanceof Error ? err.message : String(err)}`);
75636
75974
  }
75637
- return body.version;
75638
75975
  }
75639
75976
  async function action33(options) {
75640
75977
  let target;
75641
75978
  try {
75642
- target = options.version ?? await fetchLatestVersion();
75979
+ target = options.version ?? await fetchLatest();
75643
75980
  } catch (err) {
75644
75981
  if (err instanceof Error)
75645
75982
  throw err;
@@ -75647,12 +75984,12 @@ async function action33(options) {
75647
75984
  }
75648
75985
  println(c.dim(`Installed: ${PKG_VERSION}`));
75649
75986
  println(c.dim(`Target: ${target}`));
75650
- if (target === PKG_VERSION && !options.version) {
75987
+ if (!options.version && !newerRelease(target)) {
75651
75988
  println(c.green("✓ Already up to date."));
75652
75989
  return;
75653
75990
  }
75654
75991
  if (options.check) {
75655
- if (target !== PKG_VERSION) {
75992
+ if (newerRelease(target)) {
75656
75993
  println(c.yellow(`Update available: ${PKG_VERSION} → ${target}`));
75657
75994
  println(c.dim("Run `cerefox self-update` to upgrade."));
75658
75995
  }
@@ -75740,8 +76077,8 @@ init_config();
75740
76077
  import { randomBytes } from "node:crypto";
75741
76078
 
75742
76079
  // src/cli/util/env-file.ts
75743
- import { copyFileSync as copyFileSync3, existsSync as existsSync14, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "node:fs";
75744
- import { dirname as dirname6 } from "node:path";
76080
+ import { copyFileSync as copyFileSync3, existsSync as existsSync15, readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "node:fs";
76081
+ import { dirname as dirname7 } from "node:path";
75745
76082
  import { spawnSync as spawnSync6 } from "node:child_process";
75746
76083
  function escapeRegExp(s) {
75747
76084
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -75750,12 +76087,12 @@ function upsertEnvVar(path, key, value, opts = {}) {
75750
76087
  const line = `${key}=${value}`;
75751
76088
  const header = opts.comment ? `# ${opts.comment}
75752
76089
  ` : "";
75753
- if (!existsSync14(path)) {
75754
- writeFileSync7(path, `${header}${line}
76090
+ if (!existsSync15(path)) {
76091
+ writeFileSync8(path, `${header}${line}
75755
76092
  `, { mode: 384 });
75756
76093
  return { path, action: "created" };
75757
76094
  }
75758
- const original = readFileSync16(path, "utf8");
76095
+ const original = readFileSync17(path, "utf8");
75759
76096
  let backupPath;
75760
76097
  if (!opts.noBackup) {
75761
76098
  backupPath = `${path}.pre-cerefox.bak`;
@@ -75776,18 +76113,18 @@ ${header}${line}
75776
76113
  `;
75777
76114
  action = "added";
75778
76115
  }
75779
- writeFileSync7(path, next);
76116
+ writeFileSync8(path, next);
75780
76117
  return { path, action, backupPath };
75781
76118
  }
75782
76119
  function readEnvVar(path, key) {
75783
- if (!existsSync14(path))
76120
+ if (!existsSync15(path))
75784
76121
  return null;
75785
- const m = readFileSync16(path, "utf8").match(new RegExp(`^\\s*${escapeRegExp(key)}=(.*)$`, "m"));
76122
+ const m = readFileSync17(path, "utf8").match(new RegExp(`^\\s*${escapeRegExp(key)}=(.*)$`, "m"));
75786
76123
  return m ? m[1].trim() : null;
75787
76124
  }
75788
76125
  function envGitignoreWarning(path) {
75789
76126
  try {
75790
- const dir = dirname6(path);
76127
+ const dir = dirname7(path);
75791
76128
  const inTree = spawnSync6("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], {
75792
76129
  encoding: "utf8"
75793
76130
  });
@@ -75802,146 +76139,8 @@ function envGitignoreWarning(path) {
75802
76139
  }
75803
76140
  }
75804
76141
 
75805
- // src/web/auth.ts
75806
- import { existsSync as existsSync15 } from "node:fs";
75807
-
75808
- // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/conninfo.mjs
75809
- var getConnInfo = (c) => {
75810
- const bindings = c.env.server ? c.env.server : c.env;
75811
- const address = bindings.incoming.socket.remoteAddress;
75812
- const port = bindings.incoming.socket.remotePort;
75813
- const family = bindings.incoming.socket.remoteFamily;
75814
- return { remote: {
75815
- address,
75816
- port,
75817
- addressType: family === "IPv4" ? "IPv4" : family === "IPv6" ? "IPv6" : undefined
75818
- } };
75819
- };
75820
-
75821
- // ../../_shared/mcp-auth/index.ts
75822
- function constantTimeEqual(a, b) {
75823
- const ab = new TextEncoder().encode(a);
75824
- const bb = new TextEncoder().encode(b);
75825
- const len = Math.max(ab.length, bb.length);
75826
- let diff = ab.length ^ bb.length;
75827
- for (let i = 0;i < len; i++) {
75828
- diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
75829
- }
75830
- return diff === 0;
75831
- }
75832
-
75833
- // ../../_shared/ef-auth/index.ts
75834
- function parseAccessTokens(raw) {
75835
- if (!raw)
75836
- return [];
75837
- return raw.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
75838
- }
75839
- var BEARER_PREFIX = "Bearer ";
75840
- function checkAccessToken(authorizationHeader, config) {
75841
- if (!authorizationHeader || !authorizationHeader.startsWith(BEARER_PREFIX)) {
75842
- return { ok: false, reason: "no_token" };
75843
- }
75844
- const token = authorizationHeader.slice(BEARER_PREFIX.length).trim();
75845
- if (!token)
75846
- return { ok: false, reason: "no_token" };
75847
- if (config.tokens.length === 0) {
75848
- return {
75849
- ok: false,
75850
- reason: "no_tokens_configured",
75851
- detail: "CEREFOX_ACCESS_TOKENS is unset/empty; refusing to accept-all"
75852
- };
75853
- }
75854
- let matched = false;
75855
- for (const accepted of config.tokens) {
75856
- if (constantTimeEqual(token, accepted))
75857
- matched = true;
75858
- }
75859
- return matched ? { ok: true } : { ok: false, reason: "bad_token" };
75860
- }
75861
-
75862
- // src/web/auth.ts
75863
- var API_KEY_PREFIX = "cfx_lak_";
75864
- var LOOPBACK_ADDRESSES = new Set([
75865
- "127.0.0.1",
75866
- "::1",
75867
- "::ffff:127.0.0.1"
75868
- ]);
75869
- function isContainerised() {
75870
- try {
75871
- return existsSync15("/.dockerenv");
75872
- } catch {
75873
- return false;
75874
- }
75875
- }
75876
- function containerGateWarning(env = process.env) {
75877
- const hasKey = (env.CEREFOX_API_KEY ?? "").trim() !== "";
75878
- const requireAll = (env.CEREFOX_API_REQUIRE_KEY ?? "") === "1";
75879
- if (!hasKey || requireAll || !isContainerised())
75880
- return null;
75881
- return `
75882
- ⚠ CEREFOX_API_KEY is set inside a container without CEREFOX_API_REQUIRE_KEY=1.
75883
- ` + ` Docker rewrites the source address of every published-port request, so the
75884
- ` + ` loopback exemption cannot match and EVERY caller will get a 401 — including
75885
- ` + ` the web UI. In a container the gate must be all-or-nothing:
75886
- ` + ` • publishing on 127.0.0.1 → leave CEREFOX_API_KEY unset (the bind is the boundary)
75887
- ` + ` • publishing wider → also set CEREFOX_API_REQUIRE_KEY=1
75888
- `;
75889
- }
75890
- function isLoopbackAddress(address) {
75891
- if (!address)
75892
- return false;
75893
- return LOOPBACK_ADDRESSES.has(address.trim().toLowerCase());
75894
- }
75895
- function decideAuth(input) {
75896
- const keys = input.configuredKeys.trim();
75897
- if (!keys)
75898
- return { allow: true, reason: "disabled" };
75899
- const local = isLoopbackAddress(input.remoteAddress);
75900
- if (local && !input.requireKeyEverywhere) {
75901
- return { allow: true, reason: "loopback" };
75902
- }
75903
- const result = checkAccessToken(input.authorization, {
75904
- tokens: parseAccessTokens(keys)
75905
- });
75906
- if (result.ok)
75907
- return { allow: true, reason: "valid_key" };
75908
- if (result.reason === "no_token") {
75909
- return {
75910
- allow: false,
75911
- reason: "no_key",
75912
- message: local ? "This server requires an API key on every request (CEREFOX_API_REQUIRE_KEY is set). " + "Send it as `Authorization: Bearer <key>`." : "This request did not come from the local machine, so it needs the server's API key " + "as `Authorization: Bearer <key>`. Print it with `cerefox api-key show` " + "(or `cerefox-local api-key` for a container install)."
75913
- };
75914
- }
75915
- return {
75916
- allow: false,
75917
- reason: "bad_key",
75918
- message: "The API key presented is not valid for this server."
75919
- };
75920
- }
75921
- function apiAuth() {
75922
- return async (c, next) => {
75923
- const decision = decideAuth({
75924
- remoteAddress: remoteAddressOf(c),
75925
- authorization: c.req.header("Authorization") ?? null,
75926
- configuredKeys: process.env.CEREFOX_API_KEY ?? "",
75927
- requireKeyEverywhere: (process.env.CEREFOX_API_REQUIRE_KEY ?? "") === "1"
75928
- });
75929
- if (decision.allow)
75930
- return next();
75931
- return c.json({ detail: decision.message }, 401, {
75932
- "WWW-Authenticate": 'Bearer realm="cerefox"'
75933
- });
75934
- };
75935
- }
75936
- function remoteAddressOf(c) {
75937
- try {
75938
- return getConnInfo(c).remote.address;
75939
- } catch {
75940
- return;
75941
- }
75942
- }
75943
-
75944
76142
  // src/cli/commands/api-key.ts
76143
+ init_auth();
75945
76144
  var ENV_NAME = "CEREFOX_API_KEY";
75946
76145
  function mint() {
75947
76146
  return API_KEY_PREFIX + randomBytes(32).toString("base64url");
@@ -76146,7 +76345,7 @@ init_sync_self_docs();
76146
76345
 
76147
76346
  // src/cli/commands/web.ts
76148
76347
  init_cli_core();
76149
- import { readFileSync as readFileSync20 } from "node:fs";
76348
+ import { readFileSync as readFileSync21 } from "node:fs";
76150
76349
 
76151
76350
  // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
76152
76351
  var X_ALREADY_SENT = "x-hono-already-sent";
@@ -77748,7 +77947,7 @@ var baseMimes = _baseMimes;
77748
77947
 
77749
77948
  // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/serve-static.mjs
77750
77949
  import { createReadStream, existsSync as existsSync16, statSync as statSync6 } from "node:fs";
77751
- import { join as join14 } from "node:path";
77950
+ import { join as join15 } from "node:path";
77752
77951
  var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
77753
77952
  var ENCODINGS = {
77754
77953
  br: ".br",
@@ -77838,11 +78037,11 @@ var serveStatic = (options = { root: "" }) => {
77838
78037
  await options.onNotFound?.(c.req.path, c);
77839
78038
  return next();
77840
78039
  }
77841
- let path = join14(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename);
78040
+ let path = join15(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename);
77842
78041
  let stats = getStats(path);
77843
78042
  if (stats && stats.isDirectory()) {
77844
78043
  const indexFile = options.index ?? "index.html";
77845
- path = join14(path, indexFile);
78044
+ path = join15(path, indexFile);
77846
78045
  stats = getStats(path);
77847
78046
  }
77848
78047
  if (!stats) {
@@ -77904,9 +78103,10 @@ var serveStatic = (options = { root: "" }) => {
77904
78103
  };
77905
78104
 
77906
78105
  // src/web/server.ts
78106
+ init_auth();
77907
78107
  import { existsSync as existsSync20 } from "node:fs";
77908
- import { readFileSync as readFileSync19, statSync as statSync9 } from "node:fs";
77909
- import { join as join18 } from "node:path";
78108
+ import { readFileSync as readFileSync20, statSync as statSync9 } from "node:fs";
78109
+ import { join as join19 } from "node:path";
77910
78110
 
77911
78111
  // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/compose.js
77912
78112
  var compose = (middleware, onError, onNotFound) => {
@@ -81165,18 +81365,19 @@ function registerIngestRoutes(app, ctx) {
81165
81365
  // src/web/routes/meta.ts
81166
81366
  init_server_assets();
81167
81367
  init_meta();
81368
+ init_update_check();
81168
81369
  import { execFileSync } from "node:child_process";
81169
- import { dirname as dirname8 } from "node:path";
81370
+ import { dirname as dirname9 } from "node:path";
81170
81371
  import { fileURLToPath as fileURLToPath4 } from "node:url";
81171
81372
 
81172
81373
  // src/web/docs.ts
81173
81374
  import {
81174
81375
  existsSync as existsSync17,
81175
- readFileSync as readFileSync17,
81376
+ readFileSync as readFileSync18,
81176
81377
  readdirSync as readdirSync6,
81177
81378
  statSync as statSync7
81178
81379
  } from "node:fs";
81179
- import { basename as basename5, dirname as dirname7, join as join15, resolve as resolve5 } from "node:path";
81380
+ import { basename as basename5, dirname as dirname8, join as join16, resolve as resolve5 } from "node:path";
81180
81381
  import { fileURLToPath as fileURLToPath3 } from "node:url";
81181
81382
  var TOP_LEVEL_DOCS = [
81182
81383
  { filename: "README.md", path: "README.md", category: "readme" },
@@ -81192,37 +81393,37 @@ var TOP_LEVEL_DOCS = [
81192
81393
  }
81193
81394
  ];
81194
81395
  function moduleDir2() {
81195
- return dirname7(fileURLToPath3(import.meta.url));
81396
+ return dirname8(fileURLToPath3(import.meta.url));
81196
81397
  }
81197
81398
  function resolveDocsRoots() {
81198
81399
  const here = moduleDir2();
81199
81400
  const pkgRootCandidates = [
81200
- join15(here, "..", ".."),
81201
- join15(here, "..", "..", "..", "..")
81401
+ join16(here, "..", ".."),
81402
+ join16(here, "..", "..", "..", "..")
81202
81403
  ];
81203
81404
  let pkgGuides = null;
81204
81405
  let pkgTopLevel = null;
81205
81406
  for (const pkg of pkgRootCandidates) {
81206
- const guides = join15(pkg, "docs", "guides");
81407
+ const guides = join16(pkg, "docs", "guides");
81207
81408
  if (existsSync17(guides) && statSync7(guides).isDirectory()) {
81208
81409
  pkgGuides = guides;
81209
81410
  pkgTopLevel = pkg;
81210
81411
  break;
81211
81412
  }
81212
81413
  }
81213
- const repoCandidate = join15(here, "..", "..", "..", "..");
81214
- const repoGuides = join15(repoCandidate, "docs", "guides");
81414
+ const repoCandidate = join16(here, "..", "..", "..", "..");
81415
+ const repoGuides = join16(repoCandidate, "docs", "guides");
81215
81416
  const repoTopLevel = repoCandidate;
81216
81417
  return {
81217
81418
  pkgGuides,
81218
81419
  pkgTopLevel,
81219
81420
  repoGuides: existsSync17(repoGuides) ? repoGuides : null,
81220
- repoTopLevel: existsSync17(join15(repoTopLevel, "README.md")) ? repoTopLevel : null
81421
+ repoTopLevel: existsSync17(join16(repoTopLevel, "README.md")) ? repoTopLevel : null
81221
81422
  };
81222
81423
  }
81223
81424
  function readH1(filePath) {
81224
81425
  try {
81225
- const content = readFileSync17(filePath, "utf8");
81426
+ const content = readFileSync18(filePath, "utf8");
81226
81427
  const match = content.match(/^#\s+(.+?)\s*$/m);
81227
81428
  return match ? match[1] : null;
81228
81429
  } catch {
@@ -81242,7 +81443,7 @@ function listBundledDocs2() {
81242
81443
  const topRoot = pkgTopLevel ?? repoTopLevel;
81243
81444
  if (topRoot) {
81244
81445
  for (const t of TOP_LEVEL_DOCS) {
81245
- const abs = join15(topRoot, t.filename);
81446
+ const abs = join16(topRoot, t.filename);
81246
81447
  if (existsSync17(abs)) {
81247
81448
  entries.push(entryForFile(abs, t.path, t.category));
81248
81449
  }
@@ -81252,7 +81453,7 @@ function listBundledDocs2() {
81252
81453
  if (guidesRoot) {
81253
81454
  const names = readdirSync6(guidesRoot).filter((n) => n.endsWith(".md")).sort();
81254
81455
  for (const name of names) {
81255
- const abs = join15(guidesRoot, name);
81456
+ const abs = join16(guidesRoot, name);
81256
81457
  entries.push(entryForFile(abs, `guides/${name}`, "guide"));
81257
81458
  }
81258
81459
  }
@@ -81272,7 +81473,7 @@ function readDoc(docPath) {
81272
81473
  }
81273
81474
  if (existsSync17(candidate) && statSync7(candidate).isFile()) {
81274
81475
  try {
81275
- return readFileSync17(candidate, "utf8");
81476
+ return readFileSync18(candidate, "utf8");
81276
81477
  } catch {
81277
81478
  return null;
81278
81479
  }
@@ -81281,22 +81482,22 @@ function readDoc(docPath) {
81281
81482
  }
81282
81483
  function readOpenApiDocument() {
81283
81484
  const here = moduleDir2();
81284
- const fromSource = here.endsWith(join15("src", "web"));
81285
- const repoCopy = join15(here, "..", "..", "..", "..", "docs", "api", "openapi.json");
81485
+ const fromSource = here.endsWith(join16("src", "web"));
81486
+ const repoCopy = join16(here, "..", "..", "..", "..", "docs", "api", "openapi.json");
81286
81487
  const candidates = fromSource ? [repoCopy] : [
81287
- join15(here, "..", "..", "docs", "api", "openapi.json"),
81488
+ join16(here, "..", "..", "docs", "api", "openapi.json"),
81288
81489
  repoCopy
81289
81490
  ];
81290
81491
  for (const p of candidates) {
81291
81492
  if (existsSync17(p))
81292
- return readFileSync17(p, "utf8");
81493
+ return readFileSync18(p, "utf8");
81293
81494
  }
81294
81495
  return null;
81295
81496
  }
81296
81497
 
81297
81498
  // src/web/routes/meta.ts
81298
81499
  init_compatibility();
81299
- function resolveGitCommitShort(dir = dirname8(fileURLToPath4(import.meta.url))) {
81500
+ function resolveGitCommitShort(dir = dirname9(fileURLToPath4(import.meta.url))) {
81300
81501
  const env = process.env.CEREFOX_GIT_COMMIT;
81301
81502
  if (env)
81302
81503
  return env.slice(0, 7);
@@ -81319,7 +81520,13 @@ var VERSION_INFO = {
81319
81520
  function registerMetaRoutes(app, ctx) {
81320
81521
  app.get("/api/v1/version", (c) => {
81321
81522
  const label = (process.env.CEREFOX_ENV_LABEL ?? "").trim();
81322
- return c.json({ ...VERSION_INFO, env_label: label.length > 0 ? label : null });
81523
+ const latest = updateCheckDisabled() ? null : readCache()?.latest ?? null;
81524
+ return c.json({
81525
+ ...VERSION_INFO,
81526
+ env_label: label.length > 0 ? label : null,
81527
+ latest,
81528
+ update_command: newerRelease(latest) ? upgradeCommand() : null
81529
+ });
81323
81530
  });
81324
81531
  app.get("/api/v1/docs", (c) => c.json(listBundledDocs2()));
81325
81532
  app.get("/api/v1/docs/:path{.+}", (c) => {
@@ -81428,17 +81635,17 @@ function registerPostgrestProxy(app) {
81428
81635
 
81429
81636
  // src/web/routes/preferences.ts
81430
81637
  init_config();
81431
- import { existsSync as existsSync18, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "node:fs";
81432
- import { join as join16 } from "node:path";
81638
+ import { existsSync as existsSync18, mkdirSync as mkdirSync7, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "node:fs";
81639
+ import { join as join17 } from "node:path";
81433
81640
  function isTheme(v) {
81434
81641
  return v === "auto" || v === "light" || v === "dark";
81435
81642
  }
81436
81643
  function prefsFile() {
81437
- return join16(userStateDir(), "web-prefs.json");
81644
+ return join17(userStateDir(), "web-prefs.json");
81438
81645
  }
81439
81646
  function readPrefs() {
81440
81647
  try {
81441
- const raw = JSON.parse(readFileSync18(prefsFile(), "utf8"));
81648
+ const raw = JSON.parse(readFileSync19(prefsFile(), "utf8"));
81442
81649
  if (isTheme(raw.theme))
81443
81650
  return { theme: raw.theme };
81444
81651
  } catch {}
@@ -81455,8 +81662,8 @@ function registerPreferencesRoutes(app) {
81455
81662
  try {
81456
81663
  const dir = userStateDir();
81457
81664
  if (!existsSync18(dir))
81458
- mkdirSync6(dir, { recursive: true });
81459
- writeFileSync8(prefsFile(), `${JSON.stringify(next, null, 2)}
81665
+ mkdirSync7(dir, { recursive: true });
81666
+ writeFileSync9(prefsFile(), `${JSON.stringify(next, null, 2)}
81460
81667
  `);
81461
81668
  } catch (err) {
81462
81669
  return c.json({ detail: err instanceof Error ? err.message : String(err) }, 500);
@@ -81592,20 +81799,20 @@ function registerProjectsRoutes(app, ctx) {
81592
81799
 
81593
81800
  // src/web/static.ts
81594
81801
  import { existsSync as existsSync19, statSync as statSync8 } from "node:fs";
81595
- import { dirname as dirname9, join as join17 } from "node:path";
81802
+ import { dirname as dirname10, join as join18 } from "node:path";
81596
81803
  import { fileURLToPath as fileURLToPath5 } from "node:url";
81597
81804
  function moduleDir3() {
81598
- return dirname9(fileURLToPath5(import.meta.url));
81805
+ return dirname10(fileURLToPath5(import.meta.url));
81599
81806
  }
81600
81807
  function isUsableSpaDir(dir) {
81601
- return existsSync19(dir) && statSync8(dir).isDirectory() && existsSync19(join17(dir, "index.html"));
81808
+ return existsSync19(dir) && statSync8(dir).isDirectory() && existsSync19(join18(dir, "index.html"));
81602
81809
  }
81603
81810
  function resolveSpaDist() {
81604
81811
  const here = moduleDir3();
81605
81812
  const candidates = [
81606
- join17(here, "..", "frontend"),
81607
- join17(here, "..", "..", "..", "..", "frontend", "dist"),
81608
- join17(here, "..", "..", "dist", "frontend")
81813
+ join18(here, "..", "frontend"),
81814
+ join18(here, "..", "..", "..", "..", "frontend", "dist"),
81815
+ join18(here, "..", "..", "dist", "frontend")
81609
81816
  ];
81610
81817
  for (const c of candidates) {
81611
81818
  if (isUsableSpaDir(c))
@@ -81616,8 +81823,8 @@ function resolveSpaDist() {
81616
81823
  function resolveStaticDir() {
81617
81824
  const here = moduleDir3();
81618
81825
  const candidates = [
81619
- join17(here, "..", "static"),
81620
- join17(here, "..", "..", "..", "..", "web", "static")
81826
+ join18(here, "..", "static"),
81827
+ join18(here, "..", "..", "..", "..", "web", "static")
81621
81828
  ];
81622
81829
  for (const c of candidates) {
81623
81830
  if (existsSync19(c) && statSync8(c).isDirectory())
@@ -81663,6 +81870,7 @@ var ROOT_REDIRECT_HTML = `<!DOCTYPE html>
81663
81870
 
81664
81871
  // src/web/server.ts
81665
81872
  init_meta();
81873
+ init_update_check();
81666
81874
  init_ef_meta();
81667
81875
  init_cli_core();
81668
81876
  init_config();
@@ -81716,14 +81924,14 @@ function buildApp(ctx = buildWebContext()) {
81716
81924
  rewriteRequestPath: (path) => path.replace(/^\/app/, "") || "/"
81717
81925
  }));
81718
81926
  app.get("/app/assets/*", (c) => c.text("Not found", 404));
81719
- const indexPath = join18(spaDist, "index.html");
81927
+ const indexPath = join19(spaDist, "index.html");
81720
81928
  if (existsSync20(indexPath)) {
81721
81929
  let cached = null;
81722
81930
  const readIndex = () => {
81723
81931
  try {
81724
81932
  const { mtimeMs, size } = statSync9(indexPath);
81725
81933
  if (!cached || cached.mtimeMs !== mtimeMs || cached.size !== size) {
81726
- cached = { mtimeMs, size, html: readFileSync19(indexPath, "utf8") };
81934
+ cached = { mtimeMs, size, html: readFileSync20(indexPath, "utf8") };
81727
81935
  }
81728
81936
  } catch {}
81729
81937
  return cached?.html ?? null;
@@ -81791,6 +81999,7 @@ async function buildWebServer(options = {}) {
81791
81999
  }
81792
82000
  const app = buildApp();
81793
82001
  const server = serve({ fetch: app.fetch, hostname: host, port, overrideGlobalObjects: false });
82002
+ startPeriodicRefresh();
81794
82003
  return {
81795
82004
  host,
81796
82005
  port,
@@ -81801,6 +82010,7 @@ async function buildWebServer(options = {}) {
81801
82010
  }
81802
82011
 
81803
82012
  // src/cli/commands/web.ts
82013
+ init_daemon();
81804
82014
  init_meta();
81805
82015
  function parsePort(raw) {
81806
82016
  const port = Number.parseInt(raw, 10);
@@ -81837,7 +82047,7 @@ async function runForeground(host, port, watch) {
81837
82047
  }
81838
82048
  function readStartupRefusal() {
81839
82049
  try {
81840
- const tail = readFileSync20(daemonPaths.logFile, "utf8").slice(-4000);
82050
+ const tail = readFileSync21(daemonPaths.logFile, "utf8").slice(-4000);
81841
82051
  const idx = tail.lastIndexOf("Refusing to start:");
81842
82052
  if (idx === -1)
81843
82053
  return null;
@@ -82093,17 +82303,17 @@ async function bareEntryPoint() {
82093
82303
  println(" " + c.bold("cerefox init") + " # interactive first-run setup (~2 min)");
82094
82304
  println("");
82095
82305
  println(c.dim("Once configured, try:"));
82096
- println(c.dim(" cerefox doctor # verify your install"));
82097
- println(c.dim(' cerefox search "…" # search the KB'));
82098
- println(c.dim(" cerefox ingest <file> # add a doc"));
82306
+ println(c.dim(" cerefox doctor # verify your install"));
82307
+ println(c.dim(' cerefox search "…" # search the KB'));
82308
+ println(c.dim(" cerefox document ingest <file> # add a doc"));
82099
82309
  } else {
82100
82310
  println(c.green("✓ Config detected. You're good to go."));
82101
82311
  println("");
82102
82312
  println("Common commands:");
82103
- println(c.dim(" cerefox doctor # diagnose your install"));
82104
- println(c.dim(' cerefox search "…" # search the KB'));
82105
- println(c.dim(" cerefox ingest <file> # add a doc"));
82106
- println(c.dim(" cerefox configure-agent # wire up an MCP client"));
82313
+ println(c.dim(" cerefox doctor # diagnose your install"));
82314
+ println(c.dim(' cerefox search "…" # search the KB'));
82315
+ println(c.dim(" cerefox document ingest <file> # add a doc"));
82316
+ println(c.dim(" cerefox configure-agent # wire up an MCP client"));
82107
82317
  println("");
82108
82318
  println(c.dim("Run `cerefox --help` for the full command list."));
82109
82319
  }
@@ -82117,6 +82327,7 @@ async function main() {
82117
82327
  }
82118
82328
  if (process.argv.length === 2) {
82119
82329
  await bareEntryPoint();
82330
+ await printUpdateNotice();
82120
82331
  return;
82121
82332
  }
82122
82333
  const program = buildProgram();
@@ -82127,6 +82338,25 @@ async function main() {
82127
82338
  process.exit(1);
82128
82339
  });
82129
82340
  await program.parseAsync(process.argv);
82341
+ await printUpdateNotice();
82342
+ }
82343
+ var NO_UPDATE_NOTICE = new Set(["mcp", "completion", "self-update", "upgrade", "doctor"]);
82344
+ async function printUpdateNotice() {
82345
+ const args = process.argv.slice(2);
82346
+ if (!process.stderr.isTTY || args.includes("--json"))
82347
+ return;
82348
+ if (args[0] && NO_UPDATE_NOTICE.has(args[0]))
82349
+ return;
82350
+ try {
82351
+ await Promise.resolve().then(() => init_update_check());
82352
+ const line = takeCliNotice();
82353
+ if (line) {
82354
+ await Promise.resolve().then(() => init_cli_core());
82355
+ process.stderr.write(`
82356
+ ` + cErr.yellow(line) + `
82357
+ `);
82358
+ }
82359
+ } catch {}
82130
82360
  }
82131
82361
  main().catch((err) => {
82132
82362
  if (err instanceof CliError) {