@cerefox/memory 1.0.0-beta.3 → 1.0.0-rc.1

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.
@@ -7184,7 +7184,7 @@ var exports_meta = {};
7184
7184
  __export(exports_meta, {
7185
7185
  PKG_VERSION: () => PKG_VERSION
7186
7186
  });
7187
- var PKG_VERSION = "1.0.0-beta.3";
7187
+ var PKG_VERSION = "1.0.0-rc.1";
7188
7188
  var init_meta = () => {};
7189
7189
 
7190
7190
  // ../../node_modules/.bun/tslib@2.8.1/node_modules/tslib/tslib.js
@@ -22959,6 +22959,55 @@ var init_client = __esm(() => {
22959
22959
  init_cli_core();
22960
22960
  });
22961
22961
 
22962
+ // ../../_shared/server-assets/index.ts
22963
+ import { existsSync as existsSync5 } from "node:fs";
22964
+ import { dirname as dirname2, join as join5 } from "node:path";
22965
+ import { fileURLToPath } from "node:url";
22966
+ import { cwd as processCwd2 } from "node:process";
22967
+ function moduleDir() {
22968
+ return dirname2(fileURLToPath(import.meta.url));
22969
+ }
22970
+ function bundledServerAssets(serverAssetsRoot) {
22971
+ return {
22972
+ schemaFile: join5(serverAssetsRoot, "db", "schema.sql"),
22973
+ rpcsFile: join5(serverAssetsRoot, "db", "rpcs.sql"),
22974
+ migrationsDir: join5(serverAssetsRoot, "db", "migrations"),
22975
+ functionsDir: join5(serverAssetsRoot, "supabase", "functions"),
22976
+ layout: "bundled"
22977
+ };
22978
+ }
22979
+ function sourceServerAssets(repoRoot) {
22980
+ const dbDir = join5(repoRoot, "src", "cerefox", "db");
22981
+ return {
22982
+ schemaFile: join5(dbDir, "schema.sql"),
22983
+ rpcsFile: join5(dbDir, "rpcs.sql"),
22984
+ migrationsDir: join5(dbDir, "migrations"),
22985
+ functionsDir: join5(repoRoot, "supabase", "functions"),
22986
+ layout: "source"
22987
+ };
22988
+ }
22989
+ function serverAssetsUsable(p) {
22990
+ return existsSync5(p.schemaFile) && existsSync5(p.rpcsFile);
22991
+ }
22992
+ function resolveServerAssets(opts = {}) {
22993
+ if (opts.assetsDir) {
22994
+ return { ...bundledServerAssets(opts.assetsDir), layout: "explicit" };
22995
+ }
22996
+ const here = opts.moduleDirOverride ?? moduleDir();
22997
+ const cwd = opts.cwd ?? processCwd2();
22998
+ const candidates = [
22999
+ bundledServerAssets(join5(here, "..", "server-assets")),
23000
+ sourceServerAssets(join5(here, "..", "..")),
23001
+ sourceServerAssets(cwd)
23002
+ ];
23003
+ for (const candidate of candidates) {
23004
+ if (serverAssetsUsable(candidate))
23005
+ return candidate;
23006
+ }
23007
+ return sourceServerAssets(join5(here, "..", ".."));
23008
+ }
23009
+ var init_server_assets = () => {};
23010
+
22962
23011
  // ../../node_modules/.bun/postgres@3.4.9/node_modules/postgres/src/query.js
22963
23012
  function cachedError(xs) {
22964
23013
  if (originCache.has(xs))
@@ -25193,127 +25242,332 @@ var init_bundled_docs = __esm(() => {
25193
25242
  PACKAGE_ROOT = findPackageRoot();
25194
25243
  });
25195
25244
 
25196
- // ../../_shared/ingest/chunker.ts
25197
- function cpLen(s) {
25198
- let n = 0;
25199
- for (const _ of s)
25200
- n++;
25201
- return n;
25245
+ // ../../_shared/compatibility/index.ts
25246
+ function compareSemver(a, b2) {
25247
+ const split = (v) => {
25248
+ const [core, ...preParts] = v.split("-");
25249
+ return {
25250
+ core: core.split(".").slice(0, 3).map((p) => {
25251
+ const n = Number.parseInt(p, 10);
25252
+ return Number.isFinite(n) ? n : 0;
25253
+ }),
25254
+ pre: preParts.length ? preParts.join("-").split(".") : []
25255
+ };
25256
+ };
25257
+ const pa = split(a);
25258
+ const pb = split(b2);
25259
+ for (let i = 0;i < 3; i++) {
25260
+ const x = pa.core[i] ?? 0;
25261
+ const y = pb.core[i] ?? 0;
25262
+ if (x !== y)
25263
+ return x < y ? -1 : 1;
25264
+ }
25265
+ if (pa.pre.length === 0 && pb.pre.length === 0)
25266
+ return 0;
25267
+ if (pa.pre.length === 0)
25268
+ return 1;
25269
+ if (pb.pre.length === 0)
25270
+ return -1;
25271
+ const len = Math.max(pa.pre.length, pb.pre.length);
25272
+ for (let i = 0;i < len; i++) {
25273
+ const x = pa.pre[i];
25274
+ const y = pb.pre[i];
25275
+ if (x === undefined)
25276
+ return -1;
25277
+ if (y === undefined)
25278
+ return 1;
25279
+ const xn = /^\d+$/.test(x) ? Number.parseInt(x, 10) : null;
25280
+ const yn = /^\d+$/.test(y) ? Number.parseInt(y, 10) : null;
25281
+ if (xn !== null && yn !== null) {
25282
+ if (xn !== yn)
25283
+ return xn < yn ? -1 : 1;
25284
+ } else if (xn !== null) {
25285
+ return -1;
25286
+ } else if (yn !== null) {
25287
+ return 1;
25288
+ } else if (x !== y) {
25289
+ return x < y ? -1 : 1;
25290
+ }
25291
+ }
25292
+ return 0;
25202
25293
  }
25203
- function rstripHash(s) {
25204
- let i = s.length;
25205
- while (i > 0 && s[i - 1] === "#")
25206
- i--;
25207
- return s.slice(0, i);
25294
+ function classifyCompat(deployed, min, bundled) {
25295
+ if (!deployed)
25296
+ return "unknown";
25297
+ if (compareSemver(deployed, min) < 0)
25298
+ return "below-min";
25299
+ if (bundled && compareSemver(deployed, bundled) < 0)
25300
+ return "above-min-but-old";
25301
+ return "ok";
25208
25302
  }
25209
- function findHeadings(doc) {
25210
- const out = [];
25211
- const re = /^(#{1,3})[ \t]+(.+)$/gm;
25212
- let m;
25213
- while ((m = re.exec(doc)) !== null) {
25214
- out.push({ offset: m.index, level: m[1].length, text: rstripHash(m[2]).trim() });
25215
- }
25216
- return out;
25303
+ function aggregatorUrlFor(supabaseUrl) {
25304
+ const base = supabaseUrl.replace(/\/$/, "");
25305
+ return `${base}/functions/v1/cerefox-mcp/version?peers=true`;
25217
25306
  }
25218
- function activeHeadings(headings, offset) {
25219
- const stack = [];
25220
- for (const h of headings) {
25221
- if (h.offset > offset)
25222
- break;
25223
- while (stack.length && stack[stack.length - 1].level >= h.level)
25224
- stack.pop();
25225
- stack.push(h);
25307
+ async function checkServerCompatibility(opts) {
25308
+ const fetchImpl = opts.fetchImpl ?? fetch;
25309
+ const result = {
25310
+ schema: { deployed: null, min: COMPATIBILITY.minSchema, level: "unknown" },
25311
+ edgeFunctions: {
25312
+ deployed: null,
25313
+ min: COMPATIBILITY.minEdgeFunctions,
25314
+ level: "unknown",
25315
+ errors: []
25316
+ },
25317
+ blocking: false,
25318
+ efProbeSkipped: false
25319
+ };
25320
+ if (!opts.bearer) {
25321
+ result.efProbeSkipped = true;
25322
+ result.efSkipReason = "No CEREFOX_ACCESS_TOKEN configured; Edge Function version check skipped.";
25323
+ return result;
25226
25324
  }
25227
- return stack;
25228
- }
25229
- function hardSplitCp(s, maxCp) {
25230
- const out = [];
25231
- let buf = "";
25232
- let n = 0;
25233
- for (const ch of s) {
25234
- if (n >= maxCp) {
25235
- out.push(buf);
25236
- buf = "";
25237
- n = 0;
25325
+ let agg = null;
25326
+ try {
25327
+ const ctrl = new AbortController;
25328
+ const timer2 = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 6000);
25329
+ try {
25330
+ const resp = await fetchImpl(opts.aggregatorUrl, {
25331
+ method: "GET",
25332
+ headers: { Authorization: `Bearer ${opts.bearer}`, apikey: opts.bearer },
25333
+ signal: ctrl.signal
25334
+ });
25335
+ if (resp.ok) {
25336
+ agg = await resp.json();
25337
+ } else {
25338
+ result.efProbeSkipped = true;
25339
+ result.efSkipReason = resp.status === 404 || resp.status === 405 ? "Edge Functions predate v0.8 (no /version route). Redeploy with `cerefox server deploy --functions-only` to enable version checks." : `Aggregator returned HTTP ${resp.status}; Edge Function version check skipped.`;
25340
+ }
25341
+ } finally {
25342
+ clearTimeout(timer2);
25238
25343
  }
25239
- buf += ch;
25240
- n++;
25344
+ } catch (err) {
25345
+ result.efProbeSkipped = true;
25346
+ result.efSkipReason = `Could not reach the version aggregator: ${err instanceof Error ? err.message : String(err)}`;
25241
25347
  }
25242
- if (buf)
25243
- out.push(buf);
25348
+ if (!agg)
25349
+ return result;
25350
+ result.schema.deployed = agg.schema ?? null;
25351
+ result.schema.level = classifyCompat(result.schema.deployed, COMPATIBILITY.minSchema, opts.bundledSchema);
25352
+ const versions = [];
25353
+ if (agg.version)
25354
+ versions.push(agg.version);
25355
+ for (const ef of agg.efs ?? [])
25356
+ versions.push(ef.version);
25357
+ result.edgeFunctions.errors = agg.errors ?? [];
25358
+ if (versions.length > 0) {
25359
+ const weakest = versions.reduce((lo, v) => compareSemver(v, lo) < 0 ? v : lo);
25360
+ result.edgeFunctions.deployed = weakest;
25361
+ result.edgeFunctions.level = classifyCompat(weakest, COMPATIBILITY.minEdgeFunctions, opts.bundledEf);
25362
+ } else {
25363
+ result.edgeFunctions.level = "unknown";
25364
+ result.efProbeSkipped = true;
25365
+ result.efSkipReason = "Aggregator reported no Edge Function versions; check skipped.";
25366
+ }
25367
+ result.blocking = result.schema.level === "below-min" || result.edgeFunctions.level === "below-min";
25368
+ return result;
25369
+ }
25370
+ var COMPATIBILITY;
25371
+ var init_compatibility = __esm(() => {
25372
+ COMPATIBILITY = {
25373
+ minSchema: "0.3.1",
25374
+ minEdgeFunctions: "0.6.0"
25375
+ };
25376
+ });
25377
+
25378
+ // ../../_shared/embeddings/onnx-embedder.ts
25379
+ var exports_onnx_embedder = {};
25380
+ __export(exports_onnx_embedder, {
25381
+ warmup: () => warmup,
25382
+ onnxEmbed: () => onnxEmbed,
25383
+ nomicPrefix: () => nomicPrefix,
25384
+ buildPrefixedInputs: () => buildPrefixedInputs,
25385
+ ONNX_MODEL_NAME: () => ONNX_MODEL_NAME,
25386
+ ONNX_MODEL_ID: () => ONNX_MODEL_ID,
25387
+ ONNX_MODEL_DTYPE: () => ONNX_MODEL_DTYPE,
25388
+ ONNX_MODEL_DIM: () => ONNX_MODEL_DIM,
25389
+ ONNX_MODEL_APPROX_MB: () => ONNX_MODEL_APPROX_MB
25390
+ });
25391
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3 } from "node:fs";
25392
+ import { homedir as homedir5 } from "node:os";
25393
+ import { join as join8 } from "node:path";
25394
+ function nomicPrefix(role) {
25395
+ return role === "query" ? "search_query: " : "search_document: ";
25396
+ }
25397
+ function buildPrefixedInputs(texts, role) {
25398
+ const p = nomicPrefix(role);
25399
+ return texts.map((t) => p + t);
25400
+ }
25401
+ function getCacheDir() {
25402
+ const env4 = globalThis.process?.env ?? {};
25403
+ if (env4.CEREFOX_MODELS_DIR)
25404
+ return env4.CEREFOX_MODELS_DIR;
25405
+ return join8(homedir5(), ".cerefox", "models");
25406
+ }
25407
+ async function loadTransformers() {
25408
+ if (transformersModule)
25409
+ return transformersModule;
25410
+ const spec = "@huggingface/transformers";
25411
+ transformersModule = await import(spec);
25412
+ const dir = getCacheDir();
25413
+ if (!existsSync9(dir))
25414
+ mkdirSync3(dir, { recursive: true });
25415
+ transformersModule.env.cacheDir = dir;
25416
+ transformersModule.env.allowLocalModels = true;
25417
+ transformersModule.env.allowRemoteModels = true;
25418
+ return transformersModule;
25419
+ }
25420
+ function makeBar(pct, width = 20) {
25421
+ const clamped = Math.max(0, Math.min(100, pct));
25422
+ const filled = Math.round(clamped / 100 * width);
25423
+ return `[${"█".repeat(filled)}${"░".repeat(width - filled)}]`;
25424
+ }
25425
+ function l2Normalise(v) {
25426
+ let sum = 0;
25427
+ for (let i = 0;i < v.length; i++)
25428
+ sum += v[i] * v[i];
25429
+ const norm = Math.sqrt(sum);
25430
+ if (norm === 0)
25431
+ return v;
25432
+ const out = new Float32Array(v.length);
25433
+ for (let i = 0;i < v.length; i++)
25434
+ out[i] = v[i] / norm;
25244
25435
  return out;
25245
25436
  }
25246
- function chunkMarkdown(text, maxChunkChars = 4000, _minChunkChars = 100) {
25247
- const doc = text.trim();
25248
- if (!doc)
25437
+ async function ensurePipeline() {
25438
+ if (pipelinePromise)
25439
+ return pipelinePromise;
25440
+ pipelinePromise = (async () => {
25441
+ const transformers = await loadTransformers();
25442
+ const mb = ONNX_MODEL_APPROX_MB;
25443
+ const fmt = (s) => s < 60 ? `${s}s` : `${Math.round(s / 60)}m`;
25444
+ process.stderr.write(`[cerefox-embed] loading "${ONNX_MODEL_NAME}" from HuggingFace (~${mb} MB; est. ${fmt(Math.round(mb * 8 / 50))}-${fmt(Math.round(mb * 8 / 10))} at 50-10 Mbps; first-run only)…
25445
+ `);
25446
+ const progressState = new Map;
25447
+ let activeFile = null;
25448
+ const isTty = !!process.stderr.isTTY;
25449
+ const RENDER_INTERVAL_MS = 250;
25450
+ const fmtMb = (n) => (n / 1024 / 1024).toFixed(1);
25451
+ const renderInPlace = (line) => {
25452
+ if (isTty)
25453
+ process.stderr.write(`\r\x1B[K${line}`);
25454
+ else
25455
+ process.stderr.write(`${line}
25456
+ `);
25457
+ };
25458
+ const finalizeLine = () => {
25459
+ if (isTty && activeFile !== null)
25460
+ process.stderr.write(`
25461
+ `);
25462
+ activeFile = null;
25463
+ };
25464
+ const progressCallback = (info3) => {
25465
+ const file = info3.file ?? info3.name ?? "(unknown)";
25466
+ const now = Date.now();
25467
+ if (info3.status === "progress") {
25468
+ const total = info3.total ?? 0;
25469
+ const loaded = info3.loaded ?? 0;
25470
+ if (total === 0 && loaded === 0)
25471
+ return;
25472
+ const prior = progressState.get(file) ?? {
25473
+ loaded: 0,
25474
+ total: 0,
25475
+ done: false,
25476
+ indeterminate: false,
25477
+ lastRenderAt: 0,
25478
+ lastRenderedPct: -1
25479
+ };
25480
+ const indeterminate = prior.indeterminate || prior.total > 0 && total > prior.total;
25481
+ const next = {
25482
+ loaded,
25483
+ total,
25484
+ done: false,
25485
+ indeterminate,
25486
+ lastRenderAt: prior.lastRenderAt,
25487
+ lastRenderedPct: prior.lastRenderedPct
25488
+ };
25489
+ if (activeFile !== file) {
25490
+ finalizeLine();
25491
+ activeFile = file;
25492
+ }
25493
+ if (indeterminate) {
25494
+ if (now - prior.lastRenderAt >= RENDER_INTERVAL_MS) {
25495
+ renderInPlace(`[cerefox-embed] [streaming...] ${fmtMb(loaded)} MB ${file}`);
25496
+ next.lastRenderAt = now;
25497
+ }
25498
+ } else if (total > 0) {
25499
+ const pct = Math.floor(loaded / total * 100);
25500
+ const stepBumped = pct >= prior.lastRenderedPct + 5;
25501
+ const timeBumped = isTty && now - prior.lastRenderAt >= RENDER_INTERVAL_MS && pct !== prior.lastRenderedPct;
25502
+ if (stepBumped || timeBumped) {
25503
+ renderInPlace(`[cerefox-embed] ${makeBar(pct)} ${pct.toString().padStart(3)}% ${fmtMb(loaded)}/${fmtMb(total)} MB ${file}`);
25504
+ next.lastRenderedPct = pct;
25505
+ next.lastRenderAt = now;
25506
+ }
25507
+ }
25508
+ progressState.set(file, next);
25509
+ } else if (info3.status === "done") {
25510
+ const prior = progressState.get(file);
25511
+ finalizeLine();
25512
+ const finalSize = prior && prior.loaded > 0 ? `${fmtMb(prior.loaded)} MB` : info3.total && info3.total > 0 ? `${fmtMb(info3.total)} MB` : "cached";
25513
+ process.stderr.write(`[cerefox-embed] ✓ ${file} (${finalSize})
25514
+ `);
25515
+ if (prior)
25516
+ progressState.set(file, { ...prior, done: true });
25517
+ }
25518
+ };
25519
+ const pipe = await transformers.pipeline("feature-extraction", ONNX_MODEL_ID, {
25520
+ dtype: ONNX_MODEL_DTYPE,
25521
+ progress_callback: progressCallback
25522
+ });
25523
+ process.stderr.write(`[cerefox-embed] embedder ready.
25524
+ `);
25525
+ return pipe;
25526
+ })();
25527
+ pipelinePromise.catch(() => {
25528
+ pipelinePromise = null;
25529
+ });
25530
+ return pipelinePromise;
25531
+ }
25532
+ async function warmup() {
25533
+ await ensurePipeline();
25534
+ }
25535
+ async function onnxEmbed(texts, role) {
25536
+ if (texts.length === 0)
25249
25537
  return [];
25250
- if (cpLen(doc) <= maxChunkChars) {
25251
- return [
25252
- { chunk_index: 0, heading_path: [], heading_level: 0, title: "", content: doc, char_count: cpLen(doc) }
25253
- ];
25538
+ const pipeline = await ensurePipeline();
25539
+ const inputs = buildPrefixedInputs(texts, role);
25540
+ const out = await pipeline(inputs, { pooling: "mean", normalize: true });
25541
+ const dim2 = out.dims[out.dims.length - 1];
25542
+ if (dim2 !== ONNX_MODEL_DIM) {
25543
+ throw new Error(`OnnxEmbedder: expected dim=${ONNX_MODEL_DIM} (schema vector(768)), got ${dim2} from model`);
25254
25544
  }
25255
- const headings = findHeadings(doc);
25256
- const parts = doc.split(/(\n{2,})/);
25257
- const atoms = [];
25258
- for (let i = 0;i < parts.length; i += 2) {
25259
- const unit = (parts[i] ?? "") + (parts[i + 1] ?? "");
25260
- if (unit === "")
25261
- continue;
25262
- if (cpLen(unit) > maxChunkChars)
25263
- atoms.push(...hardSplitCp(unit, maxChunkChars));
25264
- else
25265
- atoms.push(unit);
25545
+ const vectors = [];
25546
+ for (let i = 0;i < inputs.length; i++) {
25547
+ const slice = out.data.slice(i * dim2, (i + 1) * dim2);
25548
+ vectors.push(Array.from(l2Normalise(slice)));
25266
25549
  }
25267
- const chunks = [];
25268
- let buf = "";
25269
- let bufCp = 0;
25270
- let bufStart = 0;
25271
- let offset = 0;
25272
- const flush = () => {
25273
- if (buf === "")
25274
- return;
25275
- const stack = activeHeadings(headings, bufStart);
25276
- chunks.push({
25277
- chunk_index: chunks.length,
25278
- heading_path: stack.map((h) => h.text),
25279
- heading_level: stack.length ? stack[stack.length - 1].level : 0,
25280
- title: stack.length ? stack[stack.length - 1].text : "",
25281
- content: buf,
25282
- char_count: bufCp
25283
- });
25284
- buf = "";
25285
- bufCp = 0;
25286
- };
25287
- for (const atom of atoms) {
25288
- const cp = cpLen(atom);
25289
- if (buf === "") {
25290
- bufStart = offset;
25291
- buf = atom;
25292
- bufCp = cp;
25293
- } else if (bufCp + cp <= maxChunkChars) {
25294
- buf += atom;
25295
- bufCp += cp;
25296
- } else {
25297
- flush();
25298
- bufStart = offset;
25299
- buf = atom;
25300
- bufCp = cp;
25301
- }
25302
- offset += atom.length;
25303
- }
25304
- flush();
25305
- return chunks;
25550
+ return vectors;
25306
25551
  }
25307
- function embeddingInputFor(docTitle, chunk) {
25308
- const breadcrumb = chunk.heading_path.join(" > ");
25309
- const head2 = breadcrumb ? `# ${docTitle}
25310
- ${breadcrumb}` : `# ${docTitle}`;
25311
- return `${head2}
25312
- ${chunk.content}`;
25313
- }
25314
- var CONTENT_FORMAT_BLIND_STITCH = 2;
25552
+ var ONNX_MODEL_ID = "nomic-ai/nomic-embed-text-v1.5", ONNX_MODEL_NAME = "nomic-embed-text-v1.5", ONNX_MODEL_DTYPE = "q8", ONNX_MODEL_DIM = 768, ONNX_MODEL_APPROX_MB = 130, transformersModule = null, pipelinePromise = null;
25553
+ var init_onnx_embedder = () => {};
25315
25554
 
25316
25555
  // ../../_shared/embeddings/index.ts
25556
+ var exports_embeddings = {};
25557
+ __export(exports_embeddings, {
25558
+ resolveEmbedderKind: () => resolveEmbedderKind,
25559
+ openaiEmbeddingConfig: () => openaiEmbeddingConfig,
25560
+ getEmbedding: () => getEmbedding,
25561
+ embeddingMaxInputChars: () => embeddingMaxInputChars,
25562
+ embedBatch: () => embedBatch,
25563
+ capEmbeddingInput: () => capEmbeddingInput,
25564
+ activeEmbedderName: () => activeEmbedderName,
25565
+ OPENAI_MODEL: () => OPENAI_MODEL,
25566
+ OPENAI_EMBEDDING_URL: () => OPENAI_EMBEDDING_URL,
25567
+ EMBEDDING_DIMENSIONS: () => EMBEDDING_DIMENSIONS,
25568
+ EMBEDDING_BATCH_SIZE: () => EMBEDDING_BATCH_SIZE,
25569
+ DEFAULT_EMBED_MAX_INPUT_CHARS: () => DEFAULT_EMBED_MAX_INPUT_CHARS
25570
+ });
25317
25571
  function openaiEmbeddingConfig() {
25318
25572
  const env4 = globalThis.process?.env ?? {};
25319
25573
  const base = env4.CEREFOX_OPENAI_BASE_URL?.replace(/\/+$/, "");
@@ -25340,7 +25594,22 @@ function capEmbeddingInput(text) {
25340
25594
  console.warn(`[embeddings] truncated an embedding input: ${text.length} → ${cut.length} chars ` + `(cap CEREFOX_EMBED_MAX_INPUT_CHARS=${max}). The full content is stored and reconstructed ` + `untouched; only this chunk's embedding uses the prefix (degraded search for this chunk).`);
25341
25595
  return cut;
25342
25596
  }
25597
+ function resolveEmbedderKind() {
25598
+ const env4 = globalThis.process?.env ?? {};
25599
+ return env4.CEREFOX_EMBEDDER === "local" ? "local" : "openai";
25600
+ }
25601
+ function activeEmbedderName() {
25602
+ return resolveEmbedderKind() === "local" ? "nomic-embed-text-v1.5" : openaiEmbeddingConfig().model;
25603
+ }
25604
+ async function onnxModule() {
25605
+ return await Promise.resolve().then(() => (init_onnx_embedder(), exports_onnx_embedder));
25606
+ }
25343
25607
  async function getEmbedding(text, apiKey) {
25608
+ if (resolveEmbedderKind() === "local") {
25609
+ const onnx = await onnxModule();
25610
+ const [vec] = await onnx.onnxEmbed([capEmbeddingInput(text)], "query");
25611
+ return vec;
25612
+ }
25344
25613
  let lastError = null;
25345
25614
  const cfg = openaiEmbeddingConfig();
25346
25615
  const input = capEmbeddingInput(text);
@@ -25432,6 +25701,10 @@ async function embedBatchSingleCall(texts, apiKey) {
25432
25701
  async function embedBatch(texts, apiKey, batchSize = EMBEDDING_BATCH_SIZE) {
25433
25702
  if (texts.length === 0)
25434
25703
  return [];
25704
+ if (resolveEmbedderKind() === "local") {
25705
+ const onnx = await onnxModule();
25706
+ return onnx.onnxEmbed(texts.map(capEmbeddingInput), "document");
25707
+ }
25435
25708
  if (texts.length <= batchSize) {
25436
25709
  return embedBatchSingleCall(texts, apiKey);
25437
25710
  }
@@ -25446,6 +25719,126 @@ async function embedBatch(texts, apiKey, batchSize = EMBEDDING_BATCH_SIZE) {
25446
25719
  }
25447
25720
  var OPENAI_EMBEDDING_URL = "https://api.openai.com/v1/embeddings", OPENAI_MODEL = "text-embedding-3-small", EMBEDDING_DIMENSIONS = 768, DEFAULT_EMBED_MAX_INPUT_CHARS = 20000, EMBEDDING_MAX_RETRIES = 3, EMBEDDING_INITIAL_BACKOFF_MS = 500, EMBEDDING_BATCH_SIZE = 96;
25448
25721
 
25722
+ // ../../_shared/ingest/chunker.ts
25723
+ function cpLen(s) {
25724
+ let n = 0;
25725
+ for (const _ of s)
25726
+ n++;
25727
+ return n;
25728
+ }
25729
+ function rstripHash(s) {
25730
+ let i = s.length;
25731
+ while (i > 0 && s[i - 1] === "#")
25732
+ i--;
25733
+ return s.slice(0, i);
25734
+ }
25735
+ function findHeadings(doc) {
25736
+ const out = [];
25737
+ const re = /^(#{1,3})[ \t]+(.+)$/gm;
25738
+ let m;
25739
+ while ((m = re.exec(doc)) !== null) {
25740
+ out.push({ offset: m.index, level: m[1].length, text: rstripHash(m[2]).trim() });
25741
+ }
25742
+ return out;
25743
+ }
25744
+ function activeHeadings(headings, offset) {
25745
+ const stack = [];
25746
+ for (const h of headings) {
25747
+ if (h.offset > offset)
25748
+ break;
25749
+ while (stack.length && stack[stack.length - 1].level >= h.level)
25750
+ stack.pop();
25751
+ stack.push(h);
25752
+ }
25753
+ return stack;
25754
+ }
25755
+ function hardSplitCp(s, maxCp) {
25756
+ const out = [];
25757
+ let buf = "";
25758
+ let n = 0;
25759
+ for (const ch of s) {
25760
+ if (n >= maxCp) {
25761
+ out.push(buf);
25762
+ buf = "";
25763
+ n = 0;
25764
+ }
25765
+ buf += ch;
25766
+ n++;
25767
+ }
25768
+ if (buf)
25769
+ out.push(buf);
25770
+ return out;
25771
+ }
25772
+ function chunkMarkdown(text, maxChunkChars = 4000, _minChunkChars = 100) {
25773
+ const doc = text.trim();
25774
+ if (!doc)
25775
+ return [];
25776
+ if (cpLen(doc) <= maxChunkChars) {
25777
+ return [
25778
+ { chunk_index: 0, heading_path: [], heading_level: 0, title: "", content: doc, char_count: cpLen(doc) }
25779
+ ];
25780
+ }
25781
+ const headings = findHeadings(doc);
25782
+ const parts = doc.split(/(\n{2,})/);
25783
+ const atoms = [];
25784
+ for (let i = 0;i < parts.length; i += 2) {
25785
+ const unit = (parts[i] ?? "") + (parts[i + 1] ?? "");
25786
+ if (unit === "")
25787
+ continue;
25788
+ if (cpLen(unit) > maxChunkChars)
25789
+ atoms.push(...hardSplitCp(unit, maxChunkChars));
25790
+ else
25791
+ atoms.push(unit);
25792
+ }
25793
+ const chunks = [];
25794
+ let buf = "";
25795
+ let bufCp = 0;
25796
+ let bufStart = 0;
25797
+ let offset = 0;
25798
+ const flush = () => {
25799
+ if (buf === "")
25800
+ return;
25801
+ const stack = activeHeadings(headings, bufStart);
25802
+ chunks.push({
25803
+ chunk_index: chunks.length,
25804
+ heading_path: stack.map((h) => h.text),
25805
+ heading_level: stack.length ? stack[stack.length - 1].level : 0,
25806
+ title: stack.length ? stack[stack.length - 1].text : "",
25807
+ content: buf,
25808
+ char_count: bufCp
25809
+ });
25810
+ buf = "";
25811
+ bufCp = 0;
25812
+ };
25813
+ for (const atom of atoms) {
25814
+ const cp = cpLen(atom);
25815
+ if (buf === "") {
25816
+ bufStart = offset;
25817
+ buf = atom;
25818
+ bufCp = cp;
25819
+ } else if (bufCp + cp <= maxChunkChars) {
25820
+ buf += atom;
25821
+ bufCp += cp;
25822
+ } else {
25823
+ flush();
25824
+ bufStart = offset;
25825
+ buf = atom;
25826
+ bufCp = cp;
25827
+ }
25828
+ offset += atom.length;
25829
+ }
25830
+ flush();
25831
+ return chunks;
25832
+ }
25833
+ function embeddingInputFor(docTitle, chunk) {
25834
+ const breadcrumb = chunk.heading_path.join(" > ");
25835
+ const head2 = breadcrumb ? `# ${docTitle}
25836
+ ${breadcrumb}` : `# ${docTitle}`;
25837
+ return `${head2}
25838
+ ${chunk.content}`;
25839
+ }
25840
+ var CONTENT_FORMAT_BLIND_STITCH = 2;
25841
+
25449
25842
  // ../../node_modules/.bun/underscore@1.13.8/node_modules/underscore/underscore-node-f.cjs
25450
25843
  var require_underscore_node_f = __commonJS((exports) => {
25451
25844
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -32746,7 +33139,7 @@ var require_BufferList = __commonJS((exports, module) => {
32746
33139
  this.head = this.tail = null;
32747
33140
  this.length = 0;
32748
33141
  };
32749
- BufferList.prototype.join = function join9(s) {
33142
+ BufferList.prototype.join = function join10(s) {
32750
33143
  if (this.length === 0)
32751
33144
  return "";
32752
33145
  var p = this.head;
@@ -54469,6 +54862,9 @@ async function handler4(supabase, args, ctx) {
54469
54862
  const project_names_raw = args.project_names;
54470
54863
  const source = args.source ?? "agent";
54471
54864
  const metadata = args.metadata ?? null;
54865
+ if (metadata !== null && (typeof metadata !== "object" || Array.isArray(metadata))) {
54866
+ throw new McpInvalidParams('metadata must be a JSON object of key/value pairs, e.g. {"type":"note"} — not a string, number, or array');
54867
+ }
54472
54868
  const update_if_exists = args.update_if_exists ?? false;
54473
54869
  const author = args.author ?? "mcp-agent";
54474
54870
  const author_type = "agent";
@@ -54481,7 +54877,7 @@ async function handler4(supabase, args, ctx) {
54481
54877
  throw new McpInvalidParams("project_names must be a JSON array of strings; for a single project use project_name (string)");
54482
54878
  }
54483
54879
  const project_names = Array.isArray(project_names_raw) ? project_names_raw.filter((s) => typeof s === "string" && s.length > 0) : null;
54484
- if (!ctx.openaiApiKey) {
54880
+ if (!ctx.openaiApiKey && resolveEmbedderKind() !== "local") {
54485
54881
  throw new Error("OpenAI API key not configured. Set OPENAI_API_KEY (Edge Function) or CEREFOX_OPENAI_API_KEY (.env, local).");
54486
54882
  }
54487
54883
  const contentHash2 = await sha256hex(normalizeContent(content));
@@ -54503,7 +54899,7 @@ async function handler4(supabase, args, ctx) {
54503
54899
  if (chunks2.length === 0)
54504
54900
  throw new Error("Content produced no chunks");
54505
54901
  const texts2 = chunks2.map((c2) => embeddingInputFor(title, c2));
54506
- const embeddings2 = await embedBatch(texts2, ctx.openaiApiKey);
54902
+ const embeddings2 = await embedBatch(texts2, ctx.openaiApiKey ?? "");
54507
54903
  const totalChars2 = chunks2.reduce((s, c2) => s + c2.char_count, 0);
54508
54904
  const chunkData2 = chunks2.map((chunk, i) => ({
54509
54905
  chunk_index: i,
@@ -54513,7 +54909,7 @@ async function handler4(supabase, args, ctx) {
54513
54909
  content: chunk.content,
54514
54910
  char_count: chunk.char_count,
54515
54911
  embedding: embeddings2[i],
54516
- embedder: OPENAI_MODEL
54912
+ embedder: activeEmbedderName()
54517
54913
  }));
54518
54914
  const { error: ingestErr2 } = await supabase.rpc("cerefox_ingest_document", {
54519
54915
  p_document_id: existingDoc.id,
@@ -54561,7 +54957,7 @@ async function handler4(supabase, args, ctx) {
54561
54957
  if (chunks2.length === 0)
54562
54958
  throw new Error("Content produced no chunks");
54563
54959
  const texts2 = chunks2.map((c2) => embeddingInputFor(title, c2));
54564
- const embeddings2 = await embedBatch(texts2, ctx.openaiApiKey);
54960
+ const embeddings2 = await embedBatch(texts2, ctx.openaiApiKey ?? "");
54565
54961
  const totalChars2 = chunks2.reduce((s, c2) => s + c2.char_count, 0);
54566
54962
  const chunkData2 = chunks2.map((chunk, i) => ({
54567
54963
  chunk_index: i,
@@ -54571,7 +54967,7 @@ async function handler4(supabase, args, ctx) {
54571
54967
  content: chunk.content,
54572
54968
  char_count: chunk.char_count,
54573
54969
  embedding: embeddings2[i],
54574
- embedder: OPENAI_MODEL
54970
+ embedder: activeEmbedderName()
54575
54971
  }));
54576
54972
  const { error: ingestErr2 } = await supabase.rpc("cerefox_ingest_document", {
54577
54973
  p_document_id: existingDoc.id,
@@ -54613,7 +55009,7 @@ async function handler4(supabase, args, ctx) {
54613
55009
  if (chunks.length === 0)
54614
55010
  throw new Error("Content produced no chunks");
54615
55011
  const texts = chunks.map((c2) => embeddingInputFor(title, c2));
54616
- const embeddings = await embedBatch(texts, ctx.openaiApiKey);
55012
+ const embeddings = await embedBatch(texts, ctx.openaiApiKey ?? "");
54617
55013
  const totalChars = chunks.reduce((s, c2) => s + c2.char_count, 0);
54618
55014
  const chunkData = chunks.map((chunk, i) => ({
54619
55015
  chunk_index: i,
@@ -54623,7 +55019,7 @@ async function handler4(supabase, args, ctx) {
54623
55019
  content: chunk.content,
54624
55020
  char_count: chunk.char_count,
54625
55021
  embedding: embeddings[i],
54626
- embedder: OPENAI_MODEL
55022
+ embedder: activeEmbedderName()
54627
55023
  }));
54628
55024
  const { data: ingestResult, error: ingestErr } = await supabase.rpc("cerefox_ingest_document", {
54629
55025
  p_document_id: null,
@@ -54960,7 +55356,7 @@ async function handler9(supabase, args, ctx) {
54960
55356
  }
54961
55357
  if (!query?.trim())
54962
55358
  throw new McpInvalidParams("query is required");
54963
- if (mode !== "fts" && !ctx.openaiApiKey) {
55359
+ if (mode !== "fts" && !ctx.openaiApiKey && resolveEmbedderKind() !== "local") {
54964
55360
  throw new Error("OpenAI API key not configured. Set OPENAI_API_KEY (Edge Function) or CEREFOX_OPENAI_API_KEY (.env, local).");
54965
55361
  }
54966
55362
  let projectId = null;
@@ -54971,7 +55367,7 @@ async function handler9(supabase, args, ctx) {
54971
55367
  }
54972
55368
  let embedding = null;
54973
55369
  if (mode !== "fts") {
54974
- embedding = await getEmbedding(query, ctx.openaiApiKey);
55370
+ embedding = await getEmbedding(query, ctx.openaiApiKey ?? "");
54975
55371
  }
54976
55372
  const metaFilterParam = metadata_filter && Object.keys(metadata_filter).length > 0 ? { p_metadata_filter: metadata_filter } : {};
54977
55373
  let rpcName;
@@ -68266,6 +68662,7 @@ var exports_server = {};
68266
68662
  __export(exports_server, {
68267
68663
  buildServer: () => buildServer
68268
68664
  });
68665
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "node:fs";
68269
68666
  function buildServer() {
68270
68667
  const settings = loadSettings();
68271
68668
  if (!settings.supabaseUrl || !settings.supabaseKey) {
@@ -68314,21 +68711,36 @@ async function warnIfSchemaVersionMismatch(supabase) {
68314
68711
  try {
68315
68712
  const { data } = await supabase.rpc("cerefox_schema_version");
68316
68713
  const deployed = typeof data === "string" ? data : null;
68317
- if (deployed && deployed !== PKG_VERSION) {
68318
- process.stderr.write(`[cerefox-mcp] ⚠ schema version mismatch: bundled ${PKG_VERSION}, ` + `deployed ${deployed}. Run \`uv run python scripts/db_deploy.py\` ` + `to update the database. Tools may behave unexpectedly until then.
68714
+ if (!deployed)
68715
+ return;
68716
+ let bundled = null;
68717
+ try {
68718
+ const assets = resolveServerAssets();
68719
+ if (existsSync12(assets.schemaFile)) {
68720
+ const m = readFileSync12(assets.schemaFile, "utf8").match(SCHEMA_VERSION_RE2);
68721
+ bundled = m ? m[1] : null;
68722
+ }
68723
+ } catch {}
68724
+ if (!bundled)
68725
+ return;
68726
+ if (compareSemver(deployed, bundled) < 0) {
68727
+ 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.
68319
68728
  `);
68320
68729
  }
68321
68730
  } catch {}
68322
68731
  }
68323
- var SERVER_NAME = "cerefox";
68732
+ var SERVER_NAME = "cerefox", SCHEMA_VERSION_RE2;
68324
68733
  var init_server3 = __esm(() => {
68325
68734
  init_server2();
68326
68735
  init_stdio2();
68327
68736
  init_types4();
68328
68737
  init_dist4();
68738
+ init_compatibility();
68329
68739
  init_config();
68740
+ init_server_assets();
68330
68741
  init_mcp_tools();
68331
68742
  init_meta();
68743
+ SCHEMA_VERSION_RE2 = /^--\s*@version:\s*(\S+)/m;
68332
68744
  });
68333
68745
 
68334
68746
  // src/bin/cerefox.ts
@@ -70166,58 +70578,11 @@ function registerDeleteProject(program2) {
70166
70578
  // src/cli/commands/deploy-server.ts
70167
70579
  init_cli_core();
70168
70580
  init_config();
70581
+ init_server_assets();
70169
70582
  import { spawnSync as spawnSync2 } from "node:child_process";
70170
70583
  import { existsSync as existsSync7 } from "node:fs";
70171
70584
  import { readdirSync as readdirSync2 } from "node:fs";
70172
70585
 
70173
- // ../../_shared/server-assets/index.ts
70174
- import { existsSync as existsSync5 } from "node:fs";
70175
- import { dirname as dirname2, join as join5 } from "node:path";
70176
- import { fileURLToPath } from "node:url";
70177
- import { cwd as processCwd2 } from "node:process";
70178
- function moduleDir() {
70179
- return dirname2(fileURLToPath(import.meta.url));
70180
- }
70181
- function bundledServerAssets(serverAssetsRoot) {
70182
- return {
70183
- schemaFile: join5(serverAssetsRoot, "db", "schema.sql"),
70184
- rpcsFile: join5(serverAssetsRoot, "db", "rpcs.sql"),
70185
- migrationsDir: join5(serverAssetsRoot, "db", "migrations"),
70186
- functionsDir: join5(serverAssetsRoot, "supabase", "functions"),
70187
- layout: "bundled"
70188
- };
70189
- }
70190
- function sourceServerAssets(repoRoot) {
70191
- const dbDir = join5(repoRoot, "src", "cerefox", "db");
70192
- return {
70193
- schemaFile: join5(dbDir, "schema.sql"),
70194
- rpcsFile: join5(dbDir, "rpcs.sql"),
70195
- migrationsDir: join5(dbDir, "migrations"),
70196
- functionsDir: join5(repoRoot, "supabase", "functions"),
70197
- layout: "source"
70198
- };
70199
- }
70200
- function serverAssetsUsable(p) {
70201
- return existsSync5(p.schemaFile) && existsSync5(p.rpcsFile);
70202
- }
70203
- function resolveServerAssets(opts = {}) {
70204
- if (opts.assetsDir) {
70205
- return { ...bundledServerAssets(opts.assetsDir), layout: "explicit" };
70206
- }
70207
- const here = opts.moduleDirOverride ?? moduleDir();
70208
- const cwd = opts.cwd ?? processCwd2();
70209
- const candidates = [
70210
- bundledServerAssets(join5(here, "..", "server-assets")),
70211
- sourceServerAssets(join5(here, "..", "..")),
70212
- sourceServerAssets(cwd)
70213
- ];
70214
- for (const candidate of candidates) {
70215
- if (serverAssetsUsable(candidate))
70216
- return candidate;
70217
- }
70218
- return sourceServerAssets(join5(here, "..", ".."));
70219
- }
70220
-
70221
70586
  // ../../_shared/db-deploy/index.ts
70222
70587
  init_src();
70223
70588
  import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync } from "node:fs";
@@ -74276,112 +74641,18 @@ init_cli_core();
74276
74641
 
74277
74642
  // src/cli/util/checks.ts
74278
74643
  init_meta();
74279
- import { existsSync as existsSync9, readFileSync as readFileSync7, realpathSync, statSync as statSync2 } from "node:fs";
74280
- import { homedir as homedir5 } from "node:os";
74281
- import { join as join8 } from "node:path";
74644
+ import { existsSync as existsSync10, readFileSync as readFileSync7, realpathSync, statSync as statSync2 } from "node:fs";
74645
+ import { homedir as homedir6 } from "node:os";
74646
+ import { join as join9 } from "node:path";
74282
74647
 
74283
74648
  // ../../_shared/ef-meta/index.ts
74284
- var EF_VERSION = "1.0.0-beta.3";
74649
+ var EF_VERSION = "1.0.0-beta.4";
74285
74650
 
74286
74651
  // src/cli/util/checks.ts
74287
74652
  init_config();
74288
74653
  init_config();
74289
-
74290
- // ../../_shared/compatibility/index.ts
74291
- var COMPATIBILITY = {
74292
- minSchema: "0.3.1",
74293
- minEdgeFunctions: "0.6.0"
74294
- };
74295
- function compareSemver(a, b2) {
74296
- const norm = (v) => v.split(/[.-]/).slice(0, 3).map((p) => Number.parseInt(p, 10)).map((n) => Number.isFinite(n) ? n : 0);
74297
- const pa = norm(a);
74298
- const pb = norm(b2);
74299
- for (let i = 0;i < 3; i++) {
74300
- const x = pa[i] ?? 0;
74301
- const y = pb[i] ?? 0;
74302
- if (x !== y)
74303
- return x < y ? -1 : 1;
74304
- }
74305
- return 0;
74306
- }
74307
- function classifyCompat(deployed, min, bundled) {
74308
- if (!deployed)
74309
- return "unknown";
74310
- if (compareSemver(deployed, min) < 0)
74311
- return "below-min";
74312
- if (bundled && compareSemver(deployed, bundled) < 0)
74313
- return "above-min-but-old";
74314
- return "ok";
74315
- }
74316
- function aggregatorUrlFor(supabaseUrl) {
74317
- const base = supabaseUrl.replace(/\/$/, "");
74318
- return `${base}/functions/v1/cerefox-mcp/version?peers=true`;
74319
- }
74320
- async function checkServerCompatibility(opts) {
74321
- const fetchImpl = opts.fetchImpl ?? fetch;
74322
- const result = {
74323
- schema: { deployed: null, min: COMPATIBILITY.minSchema, level: "unknown" },
74324
- edgeFunctions: {
74325
- deployed: null,
74326
- min: COMPATIBILITY.minEdgeFunctions,
74327
- level: "unknown",
74328
- errors: []
74329
- },
74330
- blocking: false,
74331
- efProbeSkipped: false
74332
- };
74333
- if (!opts.bearer) {
74334
- result.efProbeSkipped = true;
74335
- result.efSkipReason = "No CEREFOX_ACCESS_TOKEN configured; Edge Function version check skipped.";
74336
- return result;
74337
- }
74338
- let agg = null;
74339
- try {
74340
- const ctrl = new AbortController;
74341
- const timer2 = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 6000);
74342
- try {
74343
- const resp = await fetchImpl(opts.aggregatorUrl, {
74344
- method: "GET",
74345
- headers: { Authorization: `Bearer ${opts.bearer}`, apikey: opts.bearer },
74346
- signal: ctrl.signal
74347
- });
74348
- if (resp.ok) {
74349
- agg = await resp.json();
74350
- } else {
74351
- result.efProbeSkipped = true;
74352
- result.efSkipReason = resp.status === 404 || resp.status === 405 ? "Edge Functions predate v0.8 (no /version route). Redeploy with `cerefox server deploy --functions-only` to enable version checks." : `Aggregator returned HTTP ${resp.status}; Edge Function version check skipped.`;
74353
- }
74354
- } finally {
74355
- clearTimeout(timer2);
74356
- }
74357
- } catch (err) {
74358
- result.efProbeSkipped = true;
74359
- result.efSkipReason = `Could not reach the version aggregator: ${err instanceof Error ? err.message : String(err)}`;
74360
- }
74361
- if (!agg)
74362
- return result;
74363
- result.schema.deployed = agg.schema ?? null;
74364
- result.schema.level = classifyCompat(result.schema.deployed, COMPATIBILITY.minSchema, opts.bundledSchema);
74365
- const versions = [];
74366
- if (agg.version)
74367
- versions.push(agg.version);
74368
- for (const ef of agg.efs ?? [])
74369
- versions.push(ef.version);
74370
- result.edgeFunctions.errors = agg.errors ?? [];
74371
- if (versions.length > 0) {
74372
- const weakest = versions.reduce((lo, v) => compareSemver(v, lo) < 0 ? v : lo);
74373
- result.edgeFunctions.deployed = weakest;
74374
- result.edgeFunctions.level = classifyCompat(weakest, COMPATIBILITY.minEdgeFunctions, opts.bundledEf);
74375
- } else {
74376
- result.edgeFunctions.level = "unknown";
74377
- result.efProbeSkipped = true;
74378
- result.efSkipReason = "Aggregator reported no Edge Function versions; check skipped.";
74379
- }
74380
- result.blocking = result.schema.level === "below-min" || result.edgeFunctions.level === "below-min";
74381
- return result;
74382
- }
74383
-
74384
- // src/cli/util/checks.ts
74654
+ init_compatibility();
74655
+ init_server_assets();
74385
74656
  function checkBinary() {
74386
74657
  return {
74387
74658
  name: "binary",
@@ -74432,7 +74703,7 @@ function checkConfig() {
74432
74703
  hint: "Run `cerefox init` to bootstrap."
74433
74704
  };
74434
74705
  }
74435
- if (!existsSync9(envPath)) {
74706
+ if (!existsSync10(envPath)) {
74436
74707
  return {
74437
74708
  name: "config",
74438
74709
  status: "error",
@@ -74514,6 +74785,14 @@ async function checkSupabase() {
74514
74785
  async function checkOpenAI() {
74515
74786
  const settings = loadSettings();
74516
74787
  if (!settings.openaiApiKey) {
74788
+ const { resolveEmbedderKind: resolveEmbedderKind2 } = await Promise.resolve().then(() => exports_embeddings);
74789
+ if (resolveEmbedderKind2() === "local") {
74790
+ return {
74791
+ name: "openai",
74792
+ status: "skipped",
74793
+ detail: "no key set — not needed (local embedder active; see the embedder check)."
74794
+ };
74795
+ }
74517
74796
  return {
74518
74797
  name: "openai",
74519
74798
  status: "warn",
@@ -74560,7 +74839,7 @@ var SCHEMA_VERSION_RE = /^--\s*@version:\s*(\S+)/m;
74560
74839
  function readBundledSchemaVersion() {
74561
74840
  try {
74562
74841
  const assets = resolveServerAssets();
74563
- if (!existsSync9(assets.schemaFile))
74842
+ if (!existsSync10(assets.schemaFile))
74564
74843
  return null;
74565
74844
  const m = readFileSync7(assets.schemaFile, "utf8").match(SCHEMA_VERSION_RE);
74566
74845
  return m ? m[1] : null;
@@ -74630,6 +74909,46 @@ async function checkSchemaVersion() {
74630
74909
  };
74631
74910
  }
74632
74911
  }
74912
+ var EMBEDDER_CHECK_NAME = "embedder";
74913
+ async function checkEmbedderMismatch() {
74914
+ const settings = loadSettings();
74915
+ if (!settings.supabaseUrl || !settings.supabaseKey) {
74916
+ return { name: EMBEDDER_CHECK_NAME, status: "skipped", detail: "Supabase config missing; skipped." };
74917
+ }
74918
+ const { activeEmbedderName: activeEmbedderName2 } = await Promise.resolve().then(() => exports_embeddings);
74919
+ const active = activeEmbedderName2();
74920
+ try {
74921
+ const url = `${settings.supabaseUrl.replace(/\/$/, "")}/rest/v1/cerefox_chunks?version_id=is.null&embedder_primary=not.is.null&select=embedder_primary&limit=1000`;
74922
+ const resp = await fetch(url, {
74923
+ headers: { apikey: settings.supabaseKey, Authorization: `Bearer ${settings.supabaseKey}` }
74924
+ });
74925
+ if (!resp.ok) {
74926
+ return { name: EMBEDDER_CHECK_NAME, status: "skipped", detail: `chunk probe returned ${resp.status}; skipped.` };
74927
+ }
74928
+ const rows = await resp.json();
74929
+ const recorded = [...new Set(rows.map((r) => r.embedder_primary))];
74930
+ const stale = recorded.filter((r) => r !== active);
74931
+ if (stale.length === 0) {
74932
+ return {
74933
+ name: EMBEDDER_CHECK_NAME,
74934
+ status: "ok",
74935
+ detail: `configured "${active}"${recorded.length ? " — matches all existing chunks" : " (no embedded chunks yet)"}`
74936
+ };
74937
+ }
74938
+ return {
74939
+ name: EMBEDDER_CHECK_NAME,
74940
+ status: "warn",
74941
+ detail: `configured "${active}" but existing chunks were embedded with ${stale.map((r) => `"${r}"`).join(", ")} — semantic search across them is broken.`,
74942
+ hint: "Run `cerefox server reindex` to re-embed everything with the configured embedder."
74943
+ };
74944
+ } catch (err) {
74945
+ return {
74946
+ name: EMBEDDER_CHECK_NAME,
74947
+ status: "skipped",
74948
+ detail: `probe failed: ${err instanceof Error ? err.message : String(err)}`
74949
+ };
74950
+ }
74951
+ }
74633
74952
  var CONTENT_FORMAT_CHECK_NAME = "content format";
74634
74953
  async function checkContentFormat() {
74635
74954
  const settings = loadSettings();
@@ -74679,7 +74998,7 @@ async function checkContentFormat() {
74679
74998
  }
74680
74999
  }
74681
75000
  function hasCerefoxInJsonFile(path) {
74682
- if (!existsSync9(path))
75001
+ if (!existsSync10(path))
74683
75002
  return false;
74684
75003
  try {
74685
75004
  const parsed = JSON.parse(readFileSync7(path, "utf8"));
@@ -74690,10 +75009,10 @@ function hasCerefoxInJsonFile(path) {
74690
75009
  }
74691
75010
  }
74692
75011
  function checkMcpConfigs() {
74693
- const home = homedir5();
74694
- const claudeCodeUser = join8(home, ".claude.json");
74695
- const claudeCodeProj = join8(process.cwd(), ".mcp.json");
74696
- const claudeDesktop = process.platform === "darwin" ? join8(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join8(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join8(home, ".config", "Claude", "claude_desktop_config.json");
75012
+ const home = homedir6();
75013
+ const claudeCodeUser = join9(home, ".claude.json");
75014
+ const claudeCodeProj = join9(process.cwd(), ".mcp.json");
75015
+ const claudeDesktop = process.platform === "darwin" ? join9(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join9(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join9(home, ".config", "Claude", "claude_desktop_config.json");
74697
75016
  const found = [];
74698
75017
  if (hasCerefoxInJsonFile(claudeCodeUser))
74699
75018
  found.push("Claude Code (user)");
@@ -74716,10 +75035,10 @@ function checkMcpConfigs() {
74716
75035
  };
74717
75036
  }
74718
75037
  function checkLegacyShadowEnv() {
74719
- const home = homedir5();
74720
- const homeEnv = join8(home, USER_STATE_DIR_NAME, ".env");
74721
- const cwdEnv = join8(process.cwd(), ".env");
74722
- if (!existsSync9(homeEnv) || !existsSync9(cwdEnv))
75038
+ const home = homedir6();
75039
+ const homeEnv = join9(home, USER_STATE_DIR_NAME, ".env");
75040
+ const cwdEnv = join9(process.cwd(), ".env");
75041
+ if (!existsSync10(homeEnv) || !existsSync10(cwdEnv))
74723
75042
  return null;
74724
75043
  try {
74725
75044
  if (realpathSync(homeEnv) === realpathSync(cwdEnv))
@@ -74729,7 +75048,7 @@ function checkLegacyShadowEnv() {
74729
75048
  name: "legacy env",
74730
75049
  status: "skipped",
74731
75050
  detail: `${cwdEnv} (shadowed by ~/.cerefox/.env)`,
74732
- hint: "Shadowed by ~/.cerefox/.env and no longer read by anything (Python was removed at " + "v1.0.0). Safe to delete."
75051
+ hint: "Shadowed by ~/.cerefox/.env and no longer read by anything (Python was removed at v1.0.0). Safe to delete."
74733
75052
  };
74734
75053
  }
74735
75054
  async function checkPostgres() {
@@ -74866,6 +75185,7 @@ async function runAllChecks(opts = {}) {
74866
75185
  { name: "supabase", phase: "Probing Supabase Data API", run: () => checkSupabase() },
74867
75186
  { name: "openai", phase: "Probing OpenAI embeddings", run: () => checkOpenAI() },
74868
75187
  { name: "schema + RPCs", phase: "Reading schema + RPC version", run: () => checkSchemaVersion() },
75188
+ { name: "embedder", phase: "Checking embedder consistency", run: () => checkEmbedderMismatch() },
74869
75189
  { name: "content format", phase: "Checking chunk reconstruction format", run: () => checkContentFormat() },
74870
75190
  { name: "edge functions", phase: "Probing Edge Function versions", run: () => checkEdgeFunctionsCompat() },
74871
75191
  { name: "postgres", phase: "Probing Postgres DDL endpoint", run: () => checkPostgres() },
@@ -75316,7 +75636,7 @@ class IngestionPipeline {
75316
75636
  constructor(deps) {
75317
75637
  this.db = new IngestionDbBridge(deps.supabase);
75318
75638
  this.apiKey = deps.openAiApiKey;
75319
- this.embedderModel = deps.embedderModel ?? "text-embedding-3-small";
75639
+ this.embedderModel = deps.embedderModel ?? activeEmbedderName();
75320
75640
  this.settings = { ...loadPipelineSettings(), ...deps.settings ?? {} };
75321
75641
  }
75322
75642
  async ingestText(opts) {
@@ -75681,7 +76001,7 @@ async function action18(path, options) {
75681
76001
  if (!settings.supabaseUrl || !settings.supabaseKey) {
75682
76002
  throw userError("Supabase credentials not configured — run `cerefox init` first.");
75683
76003
  }
75684
- if (!settings.openaiApiKey) {
76004
+ if (!settings.openaiApiKey && resolveEmbedderKind() !== "local") {
75685
76005
  throw userError("OPENAI_API_KEY not set — required for embeddings during ingest.");
75686
76006
  }
75687
76007
  const supabase = createClient(settings.supabaseUrl, settings.supabaseKey, {
@@ -75744,7 +76064,7 @@ init_cli_core();
75744
76064
  init_config();
75745
76065
  var import_cli_progress = __toESM(require_cli_progress(), 1);
75746
76066
  import { readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
75747
- import { basename as basename3, extname as extname4, join as join9 } from "node:path";
76067
+ import { basename as basename3, extname as extname4, join as join10 } from "node:path";
75748
76068
  function walk(dir, extensions) {
75749
76069
  let entries;
75750
76070
  try {
@@ -75754,7 +76074,7 @@ function walk(dir, extensions) {
75754
76074
  }
75755
76075
  const files = [];
75756
76076
  for (const name of entries) {
75757
- const full = join9(dir, name);
76077
+ const full = join10(dir, name);
75758
76078
  let stat;
75759
76079
  try {
75760
76080
  stat = statSync3(full);
@@ -75788,7 +76108,7 @@ async function action19(dir, options) {
75788
76108
  if (!settings.supabaseUrl || !settings.supabaseKey) {
75789
76109
  throw userError("Supabase credentials not configured — run `cerefox init` first.");
75790
76110
  }
75791
- if (!settings.openaiApiKey) {
76111
+ if (!settings.openaiApiKey && resolveEmbedderKind() !== "local") {
75792
76112
  throw userError("OPENAI_API_KEY not set — required for embeddings during ingest.");
75793
76113
  }
75794
76114
  const supabase = createClient(settings.supabaseUrl, settings.supabaseKey, {
@@ -75851,19 +76171,20 @@ function registerIngestDir(program2) {
75851
76171
  // src/cli/commands/init.ts
75852
76172
  init_cli_core();
75853
76173
  init_config();
76174
+ init_compatibility();
75854
76175
  import { spawnSync as spawnSync4 } from "node:child_process";
75855
76176
  import {
75856
76177
  chmodSync,
75857
76178
  copyFileSync as copyFileSync2,
75858
- existsSync as existsSync10,
75859
- mkdirSync as mkdirSync3,
76179
+ existsSync as existsSync11,
76180
+ mkdirSync as mkdirSync4,
75860
76181
  readFileSync as readFileSync11,
75861
76182
  writeFileSync as writeFileSync4
75862
76183
  } from "node:fs";
75863
- import { homedir as homedir6 } from "node:os";
75864
- import { dirname as dirname4, join as join10 } from "node:path";
76184
+ import { homedir as homedir7 } from "node:os";
76185
+ import { dirname as dirname4, join as join11 } from "node:path";
75865
76186
  async function readConfigFile(path) {
75866
- if (!existsSync10(path)) {
76187
+ if (!existsSync11(path)) {
75867
76188
  throw userError(`--config file not found: ${path}`);
75868
76189
  }
75869
76190
  let parsed;
@@ -76165,7 +76486,7 @@ async function postWriteLifecycle(envPath, options) {
76165
76486
  println(c.dim(` Config in effect: ${envPath}`));
76166
76487
  }
76167
76488
  function writeAnswersTo(target, answers) {
76168
- mkdirSync3(dirname4(target), { recursive: true });
76489
+ mkdirSync4(dirname4(target), { recursive: true });
76169
76490
  writeFileSync4(target, buildEnvFile(answers), "utf8");
76170
76491
  if (process.platform !== "win32") {
76171
76492
  try {
@@ -76176,12 +76497,12 @@ function writeAnswersTo(target, answers) {
76176
76497
  }
76177
76498
  }
76178
76499
  async function action21(options) {
76179
- const homeEnv = join10(homedir6(), USER_STATE_DIR_NAME, ".env");
76180
- const cwdEnv = join10(process.cwd(), ".env");
76500
+ const homeEnv = join11(homedir7(), USER_STATE_DIR_NAME, ".env");
76501
+ const cwdEnv = join11(process.cwd(), ".env");
76181
76502
  const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
76182
76503
  if (explicitDir) {
76183
76504
  const target2 = resolveEnvFile();
76184
- if (existsSync10(target2) && !options.force) {
76505
+ if (existsSync11(target2) && !options.force) {
76185
76506
  println(c.yellow(`⚠ Config already exists at ${target2}.`));
76186
76507
  const ok2 = await confirm("Overwrite?", true);
76187
76508
  if (!ok2) {
@@ -76203,7 +76524,7 @@ async function action21(options) {
76203
76524
  await postWriteLifecycle(target2, options);
76204
76525
  return;
76205
76526
  }
76206
- if (existsSync10(homeEnv) && !options.force) {
76527
+ if (existsSync11(homeEnv) && !options.force) {
76207
76528
  println(c.yellow(`⚠ Config already exists at ${homeEnv}.`));
76208
76529
  const ok2 = await confirm("Overwrite?", true);
76209
76530
  if (!ok2) {
@@ -76224,12 +76545,12 @@ async function action21(options) {
76224
76545
  await postWriteLifecycle(homeEnv, options);
76225
76546
  return;
76226
76547
  }
76227
- if (existsSync10(cwdEnv) && !options.force && !options.config) {
76548
+ if (existsSync11(cwdEnv) && !options.force && !options.config) {
76228
76549
  printMigrationMenu(cwdEnv, homeEnv);
76229
76550
  const ch = await promptMigrationChoice();
76230
76551
  println("");
76231
76552
  if (ch === "c") {
76232
- mkdirSync3(dirname4(homeEnv), { recursive: true });
76553
+ mkdirSync4(dirname4(homeEnv), { recursive: true });
76233
76554
  copyFileSync2(cwdEnv, homeEnv);
76234
76555
  if (process.platform !== "win32") {
76235
76556
  try {
@@ -76478,6 +76799,23 @@ function registerMcp(program2) {
76478
76799
  });
76479
76800
  }
76480
76801
 
76802
+ // src/cli/commands/embedder-warmup.ts
76803
+ function registerEmbedderWarmup(program2) {
76804
+ program2.command("embedder-warmup", { hidden: true }).description("Download + warm the local ONNX embedding model (Cerefox Local).").action(async () => {
76805
+ if (process.env.CEREFOX_EMBEDDER !== "local") {
76806
+ process.stderr.write(`embedder-warmup: CEREFOX_EMBEDDER is not 'local' — nothing to warm (the OpenAI embedder has no local model).
76807
+ `);
76808
+ process.exitCode = 1;
76809
+ return;
76810
+ }
76811
+ const onnx = await Promise.resolve().then(() => (init_onnx_embedder(), exports_onnx_embedder));
76812
+ await onnx.warmup();
76813
+ const [vec] = await onnx.onnxEmbed(["warmup"], "query");
76814
+ process.stderr.write(`[cerefox-embed] warm — ${vec.length}-dim vectors ready.
76815
+ `);
76816
+ });
76817
+ }
76818
+
76481
76819
  // src/cli/commands/metadata-search.ts
76482
76820
  init_cli_core();
76483
76821
  init_client();
@@ -76637,35 +76975,35 @@ function registerReindex(program2) {
76637
76975
  // src/cli/commands/restore.ts
76638
76976
  init_cli_core();
76639
76977
  init_client();
76640
- import { existsSync as existsSync11, readFileSync as readFileSync12, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
76641
- import { homedir as homedir7 } from "node:os";
76642
- import { join as join11, resolve as resolve4 } from "node:path";
76978
+ import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
76979
+ import { homedir as homedir8 } from "node:os";
76980
+ import { join as join12, resolve as resolve4 } from "node:path";
76643
76981
  function expandHome2(path) {
76644
76982
  if (path === "~")
76645
- return homedir7();
76983
+ return homedir8();
76646
76984
  if (path.startsWith("~/"))
76647
- return join11(homedir7(), path.slice(2));
76985
+ return join12(homedir8(), path.slice(2));
76648
76986
  return path;
76649
76987
  }
76650
76988
  function resolveBackupFile(target) {
76651
76989
  const path = resolve4(expandHome2(target));
76652
- if (!existsSync11(path)) {
76990
+ if (!existsSync13(path)) {
76653
76991
  throw userError(`Backup path not found: ${target}`);
76654
76992
  }
76655
76993
  const stat = statSync4(path);
76656
76994
  if (stat.isFile())
76657
76995
  return path;
76658
- const candidates = readdirSync5(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync4(join11(path, n)).mtimeMs })).sort((a, b2) => b2.mtime - a.mtime);
76996
+ const candidates = readdirSync5(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync4(join12(path, n)).mtimeMs })).sort((a, b2) => b2.mtime - a.mtime);
76659
76997
  if (candidates.length === 0) {
76660
76998
  throw userError(`No cerefox-*.json files in ${path}`);
76661
76999
  }
76662
- return join11(path, candidates[0].name);
77000
+ return join12(path, candidates[0].name);
76663
77001
  }
76664
77002
  async function action28(target, options) {
76665
77003
  const file = resolveBackupFile(target);
76666
77004
  let payload;
76667
77005
  try {
76668
- payload = JSON.parse(readFileSync12(file, "utf8"));
77006
+ payload = JSON.parse(readFileSync13(file, "utf8"));
76669
77007
  } catch (err) {
76670
77008
  throw userError(`Could not parse backup file ${file}: ${err instanceof Error ? err.message : String(err)}`);
76671
77009
  }
@@ -76730,7 +77068,7 @@ init_config();
76730
77068
  async function embedQuery(query) {
76731
77069
  const settings = loadSettings();
76732
77070
  const apiKey = settings.openaiApiKey;
76733
- if (!apiKey) {
77071
+ if (!apiKey && resolveEmbedderKind() !== "local") {
76734
77072
  throw userError("OPENAI_API_KEY (or CEREFOX_OPENAI_API_KEY) is required for search.", "Set the key in your .env, or run `cerefox init` to bootstrap.");
76735
77073
  }
76736
77074
  try {
@@ -77045,7 +77383,7 @@ import { randomBytes } from "node:crypto";
77045
77383
  import { spawnSync as spawnSync7 } from "node:child_process";
77046
77384
 
77047
77385
  // src/cli/util/env-file.ts
77048
- import { copyFileSync as copyFileSync3, existsSync as existsSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "node:fs";
77386
+ import { copyFileSync as copyFileSync3, existsSync as existsSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "node:fs";
77049
77387
  import { dirname as dirname5 } from "node:path";
77050
77388
  import { spawnSync as spawnSync6 } from "node:child_process";
77051
77389
  function escapeRegExp(s) {
@@ -77055,12 +77393,12 @@ function upsertEnvVar(path, key, value, opts = {}) {
77055
77393
  const line = `${key}=${value}`;
77056
77394
  const header = opts.comment ? `# ${opts.comment}
77057
77395
  ` : "";
77058
- if (!existsSync12(path)) {
77396
+ if (!existsSync14(path)) {
77059
77397
  writeFileSync5(path, `${header}${line}
77060
77398
  `, { mode: 384 });
77061
77399
  return { path, action: "created" };
77062
77400
  }
77063
- const original = readFileSync13(path, "utf8");
77401
+ const original = readFileSync14(path, "utf8");
77064
77402
  let backupPath;
77065
77403
  if (!opts.noBackup) {
77066
77404
  backupPath = `${path}.pre-cerefox.bak`;
@@ -77085,9 +77423,9 @@ ${header}${line}
77085
77423
  return { path, action: action32, backupPath };
77086
77424
  }
77087
77425
  function readEnvVar(path, key) {
77088
- if (!existsSync12(path))
77426
+ if (!existsSync14(path))
77089
77427
  return null;
77090
- const m = readFileSync13(path, "utf8").match(new RegExp(`^\\s*${escapeRegExp(key)}=(.*)$`, "m"));
77428
+ const m = readFileSync14(path, "utf8").match(new RegExp(`^\\s*${escapeRegExp(key)}=(.*)$`, "m"));
77091
77429
  return m ? m[1].trim() : null;
77092
77430
  }
77093
77431
  function envGitignoreWarning(path) {
@@ -78555,8 +78893,8 @@ var _baseMimes = {
78555
78893
  var baseMimes = _baseMimes;
78556
78894
 
78557
78895
  // ../../node_modules/.bun/@hono+node-server@2.0.8+68404363cdc13251/node_modules/@hono/node-server/dist/serve-static.mjs
78558
- import { createReadStream, existsSync as existsSync13, statSync as statSync5 } from "node:fs";
78559
- import { join as join12 } from "node:path";
78896
+ import { createReadStream, existsSync as existsSync15, statSync as statSync5 } from "node:fs";
78897
+ import { join as join13 } from "node:path";
78560
78898
  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;
78561
78899
  var ENCODINGS = {
78562
78900
  br: ".br",
@@ -78588,7 +78926,7 @@ var tryDecodeURI = (str) => tryDecode(str, decodeURI);
78588
78926
  var serveStatic = (options = { root: "" }) => {
78589
78927
  const root = options.root || "";
78590
78928
  const optionPath = options.path;
78591
- if (root !== "" && !existsSync13(root))
78929
+ if (root !== "" && !existsSync15(root))
78592
78930
  console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
78593
78931
  return async (c2, next) => {
78594
78932
  if (c2.finalized)
@@ -78605,11 +78943,11 @@ var serveStatic = (options = { root: "" }) => {
78605
78943
  await options.onNotFound?.(c2.req.path, c2);
78606
78944
  return next();
78607
78945
  }
78608
- let path = join12(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c2) : filename);
78946
+ let path = join13(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c2) : filename);
78609
78947
  let stats = getStats(path);
78610
78948
  if (stats && stats.isDirectory()) {
78611
78949
  const indexFile = options.index ?? "index.html";
78612
- path = join12(path, indexFile);
78950
+ path = join13(path, indexFile);
78613
78951
  stats = getStats(path);
78614
78952
  }
78615
78953
  if (!stats) {
@@ -78666,9 +79004,9 @@ var serveStatic = (options = { root: "" }) => {
78666
79004
  };
78667
79005
 
78668
79006
  // src/web/server.ts
78669
- import { existsSync as existsSync17 } from "node:fs";
78670
- import { readFileSync as readFileSync16 } from "node:fs";
78671
- import { join as join16 } from "node:path";
79007
+ import { existsSync as existsSync19 } from "node:fs";
79008
+ import { readFileSync as readFileSync17 } from "node:fs";
79009
+ import { join as join17 } from "node:path";
78672
79010
 
78673
79011
  // ../../node_modules/.bun/hono@4.12.29/node_modules/hono/dist/compose.js
78674
79012
  var compose = (middleware, onError, onNotFound) => {
@@ -80697,7 +81035,7 @@ async function runSearch(ctx, opts) {
80697
81035
  throw error4;
80698
81036
  return (data2 ?? []).map(projectChunkResult);
80699
81037
  }
80700
- if (!ctx.openAiApiKey) {
81038
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
80701
81039
  throw new HttpError(503, "Embedder not available");
80702
81040
  }
80703
81041
  const embedding = await getEmbedding(query, ctx.openAiApiKey);
@@ -81238,7 +81576,6 @@ function registerDocumentReadRoutes(app, ctx) {
81238
81576
  return c2.json({ exists: false });
81239
81577
  });
81240
81578
  }
81241
-
81242
81579
  // src/web/routes/documents-write.ts
81243
81580
  async function createAuditEntry(ctx, args) {
81244
81581
  try {
@@ -81289,7 +81626,7 @@ function registerDocumentWriteRoutes(app, ctx) {
81289
81626
  const proposedHash = content.trim() ? contentHash(content) : null;
81290
81627
  const contentChanged = proposedHash !== null && currentHash !== null && proposedHash !== currentHash;
81291
81628
  if (contentChanged) {
81292
- if (!ctx.openAiApiKey) {
81629
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81293
81630
  return c2.json({
81294
81631
  success: false,
81295
81632
  error: "Embedder not available — set OPENAI_API_KEY in your config"
@@ -81445,14 +81782,13 @@ function registerDocumentWriteRoutes(app, ctx) {
81445
81782
  return c2.json({ archived });
81446
81783
  });
81447
81784
  }
81448
-
81449
81785
  // src/web/routes/ingest.ts
81450
81786
  function notReady(error3) {
81451
81787
  return { success: false, error: error3 };
81452
81788
  }
81453
81789
  function registerIngestRoutes(app, ctx) {
81454
81790
  app.post("/api/v1/ingest", async (c2) => {
81455
- if (!ctx.openAiApiKey) {
81791
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81456
81792
  return c2.json(notReady("Embedder not available"), 503);
81457
81793
  }
81458
81794
  let body;
@@ -81500,7 +81836,7 @@ function registerIngestRoutes(app, ctx) {
81500
81836
  }
81501
81837
  });
81502
81838
  app.post("/api/v1/ingest/file", async (c2) => {
81503
- if (!ctx.openAiApiKey) {
81839
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81504
81840
  return c2.json(notReady("Embedder not available"), 503);
81505
81841
  }
81506
81842
  let form;
@@ -81562,7 +81898,7 @@ function registerIngestRoutes(app, ctx) {
81562
81898
  }
81563
81899
  });
81564
81900
  app.post("/api/v1/documents/:document_id/upload", async (c2) => {
81565
- if (!ctx.openAiApiKey) {
81901
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81566
81902
  return c2.json(notReady("Embedder not available"), 503);
81567
81903
  }
81568
81904
  const documentId = c2.req.param("document_id");
@@ -81617,12 +81953,12 @@ import { execFileSync } from "node:child_process";
81617
81953
 
81618
81954
  // src/web/docs.ts
81619
81955
  import {
81620
- existsSync as existsSync14,
81621
- readFileSync as readFileSync14,
81956
+ existsSync as existsSync16,
81957
+ readFileSync as readFileSync15,
81622
81958
  readdirSync as readdirSync6,
81623
81959
  statSync as statSync6
81624
81960
  } from "node:fs";
81625
- import { basename as basename5, dirname as dirname6, join as join13, resolve as resolve5 } from "node:path";
81961
+ import { basename as basename5, dirname as dirname6, join as join14, resolve as resolve5 } from "node:path";
81626
81962
  import { fileURLToPath as fileURLToPath3 } from "node:url";
81627
81963
  var TOP_LEVEL_DOCS = [
81628
81964
  { filename: "README.md", path: "README.md", category: "readme" },
@@ -81643,32 +81979,32 @@ function moduleDir2() {
81643
81979
  function resolveDocsRoots() {
81644
81980
  const here = moduleDir2();
81645
81981
  const pkgRootCandidates = [
81646
- join13(here, "..", ".."),
81647
- join13(here, "..", "..", "..", "..")
81982
+ join14(here, "..", ".."),
81983
+ join14(here, "..", "..", "..", "..")
81648
81984
  ];
81649
81985
  let pkgGuides = null;
81650
81986
  let pkgTopLevel = null;
81651
81987
  for (const pkg of pkgRootCandidates) {
81652
- const guides = join13(pkg, "docs", "guides");
81653
- if (existsSync14(guides) && statSync6(guides).isDirectory()) {
81988
+ const guides = join14(pkg, "docs", "guides");
81989
+ if (existsSync16(guides) && statSync6(guides).isDirectory()) {
81654
81990
  pkgGuides = guides;
81655
81991
  pkgTopLevel = pkg;
81656
81992
  break;
81657
81993
  }
81658
81994
  }
81659
- const repoCandidate = join13(here, "..", "..", "..", "..");
81660
- const repoGuides = join13(repoCandidate, "docs", "guides");
81995
+ const repoCandidate = join14(here, "..", "..", "..", "..");
81996
+ const repoGuides = join14(repoCandidate, "docs", "guides");
81661
81997
  const repoTopLevel = repoCandidate;
81662
81998
  return {
81663
81999
  pkgGuides,
81664
82000
  pkgTopLevel,
81665
- repoGuides: existsSync14(repoGuides) ? repoGuides : null,
81666
- repoTopLevel: existsSync14(join13(repoTopLevel, "README.md")) ? repoTopLevel : null
82001
+ repoGuides: existsSync16(repoGuides) ? repoGuides : null,
82002
+ repoTopLevel: existsSync16(join14(repoTopLevel, "README.md")) ? repoTopLevel : null
81667
82003
  };
81668
82004
  }
81669
82005
  function readH1(filePath) {
81670
82006
  try {
81671
- const content = readFileSync14(filePath, "utf8");
82007
+ const content = readFileSync15(filePath, "utf8");
81672
82008
  const match2 = content.match(/^#\s+(.+?)\s*$/m);
81673
82009
  return match2 ? match2[1] : null;
81674
82010
  } catch {
@@ -81688,8 +82024,8 @@ function listBundledDocs2() {
81688
82024
  const topRoot = pkgTopLevel ?? repoTopLevel;
81689
82025
  if (topRoot) {
81690
82026
  for (const t of TOP_LEVEL_DOCS) {
81691
- const abs = join13(topRoot, t.filename);
81692
- if (existsSync14(abs)) {
82027
+ const abs = join14(topRoot, t.filename);
82028
+ if (existsSync16(abs)) {
81693
82029
  entries.push(entryForFile(abs, t.path, t.category));
81694
82030
  }
81695
82031
  }
@@ -81698,7 +82034,7 @@ function listBundledDocs2() {
81698
82034
  if (guidesRoot) {
81699
82035
  const names = readdirSync6(guidesRoot).filter((n) => n.endsWith(".md")).sort();
81700
82036
  for (const name of names) {
81701
- const abs = join13(guidesRoot, name);
82037
+ const abs = join14(guidesRoot, name);
81702
82038
  entries.push(entryForFile(abs, `guides/${name}`, "guide"));
81703
82039
  }
81704
82040
  }
@@ -81716,9 +82052,9 @@ function readDoc(docPath) {
81716
82052
  if (!candidate.startsWith(rootResolved + "/") && candidate !== rootResolved) {
81717
82053
  return null;
81718
82054
  }
81719
- if (existsSync14(candidate) && statSync6(candidate).isFile()) {
82055
+ if (existsSync16(candidate) && statSync6(candidate).isFile()) {
81720
82056
  try {
81721
- return readFileSync14(candidate, "utf8");
82057
+ return readFileSync15(candidate, "utf8");
81722
82058
  } catch {
81723
82059
  return null;
81724
82060
  }
@@ -81727,6 +82063,7 @@ function readDoc(docPath) {
81727
82063
  }
81728
82064
 
81729
82065
  // src/web/routes/meta.ts
82066
+ init_compatibility();
81730
82067
  function resolveGitCommitShort() {
81731
82068
  const env4 = process.env.CEREFOX_GIT_COMMIT;
81732
82069
  if (env4)
@@ -81746,7 +82083,7 @@ var VERSION_INFO = {
81746
82083
  git_commit_short: resolveGitCommitShort(),
81747
82084
  build_date: process.env.CEREFOX_BUILD_DATE ?? null
81748
82085
  };
81749
- var SCHEMA_VERSION_RE2 = /^--\s*@version:\s*(\S+)/m;
82086
+ var SCHEMA_VERSION_RE3 = /^--\s*@version:\s*(\S+)/m;
81750
82087
  function registerMetaRoutes(app, ctx) {
81751
82088
  app.get("/api/v1/version", (c2) => c2.json(VERSION_INFO));
81752
82089
  app.get("/api/v1/docs", (c2) => c2.json(listBundledDocs2()));
@@ -81763,18 +82100,18 @@ function registerMetaRoutes(app, ctx) {
81763
82100
  app.get("/api/v1/schema-version", async (c2) => {
81764
82101
  let bundled = null;
81765
82102
  try {
81766
- const { readFileSync: readFileSync15, existsSync: existsSync15 } = await import("node:fs");
82103
+ const { readFileSync: readFileSync16, existsSync: existsSync17 } = await import("node:fs");
81767
82104
  const { fileURLToPath: fileURLToPath4 } = await import("node:url");
81768
- const { dirname: dirname7, join: join14 } = await import("node:path");
82105
+ const { dirname: dirname7, join: join15 } = await import("node:path");
81769
82106
  const here = dirname7(fileURLToPath4(import.meta.url));
81770
82107
  const candidates = [
81771
- join14(here, "..", "..", "..", "db", "schema.sql"),
81772
- join14(here, "..", "..", "..", "..", "..", "src", "cerefox", "db", "schema.sql")
82108
+ join15(here, "..", "..", "..", "db", "schema.sql"),
82109
+ join15(here, "..", "..", "..", "..", "..", "src", "cerefox", "db", "schema.sql")
81773
82110
  ];
81774
82111
  for (const path of candidates) {
81775
- if (existsSync15(path)) {
81776
- const sql = readFileSync15(path, "utf8");
81777
- const match2 = sql.match(SCHEMA_VERSION_RE2);
82112
+ if (existsSync17(path)) {
82113
+ const sql = readFileSync16(path, "utf8");
82114
+ const match2 = sql.match(SCHEMA_VERSION_RE3);
81778
82115
  bundled = match2 ? match2[1] : null;
81779
82116
  break;
81780
82117
  }
@@ -81869,17 +82206,17 @@ function registerPostgrestProxy(app) {
81869
82206
 
81870
82207
  // src/web/routes/preferences.ts
81871
82208
  init_config();
81872
- import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "node:fs";
81873
- import { join as join14 } from "node:path";
82209
+ import { existsSync as existsSync17, mkdirSync as mkdirSync5, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "node:fs";
82210
+ import { join as join15 } from "node:path";
81874
82211
  function isTheme(v) {
81875
82212
  return v === "auto" || v === "light" || v === "dark";
81876
82213
  }
81877
82214
  function prefsFile() {
81878
- return join14(userStateDir(), "web-prefs.json");
82215
+ return join15(userStateDir(), "web-prefs.json");
81879
82216
  }
81880
82217
  function readPrefs() {
81881
82218
  try {
81882
- const raw2 = JSON.parse(readFileSync15(prefsFile(), "utf8"));
82219
+ const raw2 = JSON.parse(readFileSync16(prefsFile(), "utf8"));
81883
82220
  if (isTheme(raw2.theme))
81884
82221
  return { theme: raw2.theme };
81885
82222
  } catch {}
@@ -81895,8 +82232,8 @@ function registerPreferencesRoutes(app) {
81895
82232
  const next = { ...readPrefs(), theme: body.theme };
81896
82233
  try {
81897
82234
  const dir = userStateDir();
81898
- if (!existsSync15(dir))
81899
- mkdirSync4(dir, { recursive: true });
82235
+ if (!existsSync17(dir))
82236
+ mkdirSync5(dir, { recursive: true });
81900
82237
  writeFileSync6(prefsFile(), `${JSON.stringify(next, null, 2)}
81901
82238
  `);
81902
82239
  } catch (err) {
@@ -81967,21 +82304,21 @@ function registerProjectsRoutes(app, ctx) {
81967
82304
  }
81968
82305
 
81969
82306
  // src/web/static.ts
81970
- import { existsSync as existsSync16, statSync as statSync7 } from "node:fs";
81971
- import { dirname as dirname7, join as join15 } from "node:path";
82307
+ import { existsSync as existsSync18, statSync as statSync7 } from "node:fs";
82308
+ import { dirname as dirname7, join as join16 } from "node:path";
81972
82309
  import { fileURLToPath as fileURLToPath4 } from "node:url";
81973
82310
  function moduleDir3() {
81974
82311
  return dirname7(fileURLToPath4(import.meta.url));
81975
82312
  }
81976
82313
  function isUsableSpaDir(dir) {
81977
- return existsSync16(dir) && statSync7(dir).isDirectory() && existsSync16(join15(dir, "index.html"));
82314
+ return existsSync18(dir) && statSync7(dir).isDirectory() && existsSync18(join16(dir, "index.html"));
81978
82315
  }
81979
82316
  function resolveSpaDist() {
81980
82317
  const here = moduleDir3();
81981
82318
  const candidates = [
81982
- join15(here, "..", "frontend"),
81983
- join15(here, "..", "..", "..", "..", "frontend", "dist"),
81984
- join15(here, "..", "..", "dist", "frontend")
82319
+ join16(here, "..", "frontend"),
82320
+ join16(here, "..", "..", "..", "..", "frontend", "dist"),
82321
+ join16(here, "..", "..", "dist", "frontend")
81985
82322
  ];
81986
82323
  for (const c2 of candidates) {
81987
82324
  if (isUsableSpaDir(c2))
@@ -81992,11 +82329,11 @@ function resolveSpaDist() {
81992
82329
  function resolveStaticDir() {
81993
82330
  const here = moduleDir3();
81994
82331
  const candidates = [
81995
- join15(here, "..", "static"),
81996
- join15(here, "..", "..", "..", "..", "web", "static")
82332
+ join16(here, "..", "static"),
82333
+ join16(here, "..", "..", "..", "..", "web", "static")
81997
82334
  ];
81998
82335
  for (const c2 of candidates) {
81999
- if (existsSync16(c2) && statSync7(c2).isDirectory())
82336
+ if (existsSync18(c2) && statSync7(c2).isDirectory())
82000
82337
  return c2;
82001
82338
  }
82002
82339
  return null;
@@ -82041,6 +82378,7 @@ var ROOT_REDIRECT_HTML = `<!DOCTYPE html>
82041
82378
  init_meta();
82042
82379
  init_cli_core();
82043
82380
  init_config();
82381
+ init_compatibility();
82044
82382
  function buildApp(ctx = buildWebContext()) {
82045
82383
  const app = new Hono2;
82046
82384
  if (true) {
@@ -82082,9 +82420,9 @@ function buildApp(ctx = buildWebContext()) {
82082
82420
  root: spaDist,
82083
82421
  rewriteRequestPath: (path) => path.replace(/^\/app/, "") || "/"
82084
82422
  }));
82085
- const indexPath = join16(spaDist, "index.html");
82086
- if (existsSync17(indexPath)) {
82087
- const indexHtml = readFileSync16(indexPath, "utf8");
82423
+ const indexPath = join17(spaDist, "index.html");
82424
+ if (existsSync19(indexPath)) {
82425
+ const indexHtml = readFileSync17(indexPath, "utf8");
82088
82426
  app.get("/app/*", (c2) => c2.html(indexHtml));
82089
82427
  }
82090
82428
  }
@@ -82142,28 +82480,28 @@ async function buildWebServer(options = {}) {
82142
82480
  // src/web/daemon.ts
82143
82481
  import { spawn } from "node:child_process";
82144
82482
  import {
82145
- existsSync as existsSync18,
82146
- mkdirSync as mkdirSync5,
82483
+ existsSync as existsSync20,
82484
+ mkdirSync as mkdirSync6,
82147
82485
  openSync,
82148
- readFileSync as readFileSync17,
82486
+ readFileSync as readFileSync18,
82149
82487
  rmSync,
82150
82488
  writeFileSync as writeFileSync7
82151
82489
  } from "node:fs";
82152
- import { homedir as homedir8 } from "node:os";
82153
- import { join as join17 } from "node:path";
82154
- var STATE_DIR = join17(homedir8(), ".cerefox");
82155
- var PID_FILE = join17(STATE_DIR, "web.pid");
82156
- var LOG_FILE = join17(STATE_DIR, "web.log");
82490
+ import { homedir as homedir9 } from "node:os";
82491
+ import { join as join18 } from "node:path";
82492
+ var STATE_DIR = join18(homedir9(), ".cerefox");
82493
+ var PID_FILE = join18(STATE_DIR, "web.pid");
82494
+ var LOG_FILE = join18(STATE_DIR, "web.log");
82157
82495
  var daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
82158
82496
  function ensureStateDir() {
82159
- if (!existsSync18(STATE_DIR))
82160
- mkdirSync5(STATE_DIR, { recursive: true });
82497
+ if (!existsSync20(STATE_DIR))
82498
+ mkdirSync6(STATE_DIR, { recursive: true });
82161
82499
  }
82162
82500
  function readPidFile() {
82163
- if (!existsSync18(PID_FILE))
82501
+ if (!existsSync20(PID_FILE))
82164
82502
  return null;
82165
82503
  try {
82166
- const parsed = JSON.parse(readFileSync17(PID_FILE, "utf8"));
82504
+ const parsed = JSON.parse(readFileSync18(PID_FILE, "utf8"));
82167
82505
  if (typeof parsed.pid !== "number")
82168
82506
  return null;
82169
82507
  return {
@@ -82474,6 +82812,7 @@ Learn more:
82474
82812
  registerConfigureAgent(program2);
82475
82813
  registerSelfUpdate(program2);
82476
82814
  registerMcp(program2);
82815
+ registerEmbedderWarmup(program2);
82477
82816
  registerWeb(program2);
82478
82817
  registerCompletion(program2);
82479
82818
  registerToken(program2);
@@ -82527,7 +82866,7 @@ Learn more:
82527
82866
 
82528
82867
  // src/bin/cerefox.ts
82529
82868
  async function bareEntryPoint() {
82530
- const { existsSync: existsSync19 } = await import("node:fs");
82869
+ const { existsSync: existsSync21 } = await import("node:fs");
82531
82870
  const { resolveEnvFile: resolveEnvFile2 } = await Promise.resolve().then(() => (init_config(), exports_config));
82532
82871
  const { c: c2, println: println2 } = await Promise.resolve().then(() => (init_cli_core(), exports_cli_core));
82533
82872
  const { PKG_VERSION: PKG_VERSION2 } = await Promise.resolve().then(() => (init_meta(), exports_meta));
@@ -82536,7 +82875,7 @@ async function bareEntryPoint() {
82536
82875
  println2("");
82537
82876
  let configExists = false;
82538
82877
  try {
82539
- configExists = existsSync19(resolveEnvFile2());
82878
+ configExists = existsSync21(resolveEnvFile2());
82540
82879
  } catch {}
82541
82880
  if (!configExists) {
82542
82881
  println2(c2.yellow("⚠ No config detected."));