@cerefox/memory 1.0.0-beta.3 → 1.0.0-beta.4

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-beta.4";
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,296 @@ 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 norm = (v) => v.split(/[.-]/).slice(0, 3).map((p) => Number.parseInt(p, 10)).map((n) => Number.isFinite(n) ? n : 0);
25248
+ const pa = norm(a);
25249
+ const pb = norm(b2);
25250
+ for (let i = 0;i < 3; i++) {
25251
+ const x = pa[i] ?? 0;
25252
+ const y = pb[i] ?? 0;
25253
+ if (x !== y)
25254
+ return x < y ? -1 : 1;
25255
+ }
25256
+ return 0;
25202
25257
  }
25203
- function rstripHash(s) {
25204
- let i = s.length;
25205
- while (i > 0 && s[i - 1] === "#")
25206
- i--;
25207
- return s.slice(0, i);
25258
+ function classifyCompat(deployed, min, bundled) {
25259
+ if (!deployed)
25260
+ return "unknown";
25261
+ if (compareSemver(deployed, min) < 0)
25262
+ return "below-min";
25263
+ if (bundled && compareSemver(deployed, bundled) < 0)
25264
+ return "above-min-but-old";
25265
+ return "ok";
25208
25266
  }
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;
25267
+ function aggregatorUrlFor(supabaseUrl) {
25268
+ const base = supabaseUrl.replace(/\/$/, "");
25269
+ return `${base}/functions/v1/cerefox-mcp/version?peers=true`;
25217
25270
  }
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);
25271
+ async function checkServerCompatibility(opts) {
25272
+ const fetchImpl = opts.fetchImpl ?? fetch;
25273
+ const result = {
25274
+ schema: { deployed: null, min: COMPATIBILITY.minSchema, level: "unknown" },
25275
+ edgeFunctions: {
25276
+ deployed: null,
25277
+ min: COMPATIBILITY.minEdgeFunctions,
25278
+ level: "unknown",
25279
+ errors: []
25280
+ },
25281
+ blocking: false,
25282
+ efProbeSkipped: false
25283
+ };
25284
+ if (!opts.bearer) {
25285
+ result.efProbeSkipped = true;
25286
+ result.efSkipReason = "No CEREFOX_ACCESS_TOKEN configured; Edge Function version check skipped.";
25287
+ return result;
25226
25288
  }
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;
25289
+ let agg = null;
25290
+ try {
25291
+ const ctrl = new AbortController;
25292
+ const timer2 = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 6000);
25293
+ try {
25294
+ const resp = await fetchImpl(opts.aggregatorUrl, {
25295
+ method: "GET",
25296
+ headers: { Authorization: `Bearer ${opts.bearer}`, apikey: opts.bearer },
25297
+ signal: ctrl.signal
25298
+ });
25299
+ if (resp.ok) {
25300
+ agg = await resp.json();
25301
+ } else {
25302
+ result.efProbeSkipped = true;
25303
+ 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.`;
25304
+ }
25305
+ } finally {
25306
+ clearTimeout(timer2);
25238
25307
  }
25239
- buf += ch;
25240
- n++;
25308
+ } catch (err) {
25309
+ result.efProbeSkipped = true;
25310
+ result.efSkipReason = `Could not reach the version aggregator: ${err instanceof Error ? err.message : String(err)}`;
25241
25311
  }
25242
- if (buf)
25243
- out.push(buf);
25312
+ if (!agg)
25313
+ return result;
25314
+ result.schema.deployed = agg.schema ?? null;
25315
+ result.schema.level = classifyCompat(result.schema.deployed, COMPATIBILITY.minSchema, opts.bundledSchema);
25316
+ const versions = [];
25317
+ if (agg.version)
25318
+ versions.push(agg.version);
25319
+ for (const ef of agg.efs ?? [])
25320
+ versions.push(ef.version);
25321
+ result.edgeFunctions.errors = agg.errors ?? [];
25322
+ if (versions.length > 0) {
25323
+ const weakest = versions.reduce((lo, v) => compareSemver(v, lo) < 0 ? v : lo);
25324
+ result.edgeFunctions.deployed = weakest;
25325
+ result.edgeFunctions.level = classifyCompat(weakest, COMPATIBILITY.minEdgeFunctions, opts.bundledEf);
25326
+ } else {
25327
+ result.edgeFunctions.level = "unknown";
25328
+ result.efProbeSkipped = true;
25329
+ result.efSkipReason = "Aggregator reported no Edge Function versions; check skipped.";
25330
+ }
25331
+ result.blocking = result.schema.level === "below-min" || result.edgeFunctions.level === "below-min";
25332
+ return result;
25333
+ }
25334
+ var COMPATIBILITY;
25335
+ var init_compatibility = __esm(() => {
25336
+ COMPATIBILITY = {
25337
+ minSchema: "0.3.1",
25338
+ minEdgeFunctions: "0.6.0"
25339
+ };
25340
+ });
25341
+
25342
+ // ../../_shared/embeddings/onnx-embedder.ts
25343
+ var exports_onnx_embedder = {};
25344
+ __export(exports_onnx_embedder, {
25345
+ warmup: () => warmup,
25346
+ onnxEmbed: () => onnxEmbed,
25347
+ nomicPrefix: () => nomicPrefix,
25348
+ buildPrefixedInputs: () => buildPrefixedInputs,
25349
+ ONNX_MODEL_NAME: () => ONNX_MODEL_NAME,
25350
+ ONNX_MODEL_ID: () => ONNX_MODEL_ID,
25351
+ ONNX_MODEL_DTYPE: () => ONNX_MODEL_DTYPE,
25352
+ ONNX_MODEL_DIM: () => ONNX_MODEL_DIM,
25353
+ ONNX_MODEL_APPROX_MB: () => ONNX_MODEL_APPROX_MB
25354
+ });
25355
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3 } from "node:fs";
25356
+ import { homedir as homedir5 } from "node:os";
25357
+ import { join as join8 } from "node:path";
25358
+ function nomicPrefix(role) {
25359
+ return role === "query" ? "search_query: " : "search_document: ";
25360
+ }
25361
+ function buildPrefixedInputs(texts, role) {
25362
+ const p = nomicPrefix(role);
25363
+ return texts.map((t) => p + t);
25364
+ }
25365
+ function getCacheDir() {
25366
+ const env4 = globalThis.process?.env ?? {};
25367
+ if (env4.CEREFOX_MODELS_DIR)
25368
+ return env4.CEREFOX_MODELS_DIR;
25369
+ return join8(homedir5(), ".cerefox", "models");
25370
+ }
25371
+ async function loadTransformers() {
25372
+ if (transformersModule)
25373
+ return transformersModule;
25374
+ const spec = "@huggingface/transformers";
25375
+ transformersModule = await import(spec);
25376
+ const dir = getCacheDir();
25377
+ if (!existsSync9(dir))
25378
+ mkdirSync3(dir, { recursive: true });
25379
+ transformersModule.env.cacheDir = dir;
25380
+ transformersModule.env.allowLocalModels = true;
25381
+ transformersModule.env.allowRemoteModels = true;
25382
+ return transformersModule;
25383
+ }
25384
+ function makeBar(pct, width = 20) {
25385
+ const clamped = Math.max(0, Math.min(100, pct));
25386
+ const filled = Math.round(clamped / 100 * width);
25387
+ return `[${"█".repeat(filled)}${"░".repeat(width - filled)}]`;
25388
+ }
25389
+ function l2Normalise(v) {
25390
+ let sum = 0;
25391
+ for (let i = 0;i < v.length; i++)
25392
+ sum += v[i] * v[i];
25393
+ const norm = Math.sqrt(sum);
25394
+ if (norm === 0)
25395
+ return v;
25396
+ const out = new Float32Array(v.length);
25397
+ for (let i = 0;i < v.length; i++)
25398
+ out[i] = v[i] / norm;
25244
25399
  return out;
25245
25400
  }
25246
- function chunkMarkdown(text, maxChunkChars = 4000, _minChunkChars = 100) {
25247
- const doc = text.trim();
25248
- if (!doc)
25401
+ async function ensurePipeline() {
25402
+ if (pipelinePromise)
25403
+ return pipelinePromise;
25404
+ pipelinePromise = (async () => {
25405
+ const transformers = await loadTransformers();
25406
+ const mb = ONNX_MODEL_APPROX_MB;
25407
+ const fmt = (s) => s < 60 ? `${s}s` : `${Math.round(s / 60)}m`;
25408
+ 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)…
25409
+ `);
25410
+ const progressState = new Map;
25411
+ let activeFile = null;
25412
+ const isTty = !!process.stderr.isTTY;
25413
+ const RENDER_INTERVAL_MS = 250;
25414
+ const fmtMb = (n) => (n / 1024 / 1024).toFixed(1);
25415
+ const renderInPlace = (line) => {
25416
+ if (isTty)
25417
+ process.stderr.write(`\r\x1B[K${line}`);
25418
+ else
25419
+ process.stderr.write(`${line}
25420
+ `);
25421
+ };
25422
+ const finalizeLine = () => {
25423
+ if (isTty && activeFile !== null)
25424
+ process.stderr.write(`
25425
+ `);
25426
+ activeFile = null;
25427
+ };
25428
+ const progressCallback = (info3) => {
25429
+ const file = info3.file ?? info3.name ?? "(unknown)";
25430
+ const now = Date.now();
25431
+ if (info3.status === "progress") {
25432
+ const total = info3.total ?? 0;
25433
+ const loaded = info3.loaded ?? 0;
25434
+ if (total === 0 && loaded === 0)
25435
+ return;
25436
+ const prior = progressState.get(file) ?? {
25437
+ loaded: 0,
25438
+ total: 0,
25439
+ done: false,
25440
+ indeterminate: false,
25441
+ lastRenderAt: 0,
25442
+ lastRenderedPct: -1
25443
+ };
25444
+ const indeterminate = prior.indeterminate || prior.total > 0 && total > prior.total;
25445
+ const next = {
25446
+ loaded,
25447
+ total,
25448
+ done: false,
25449
+ indeterminate,
25450
+ lastRenderAt: prior.lastRenderAt,
25451
+ lastRenderedPct: prior.lastRenderedPct
25452
+ };
25453
+ if (activeFile !== file) {
25454
+ finalizeLine();
25455
+ activeFile = file;
25456
+ }
25457
+ if (indeterminate) {
25458
+ if (now - prior.lastRenderAt >= RENDER_INTERVAL_MS) {
25459
+ renderInPlace(`[cerefox-embed] [streaming...] ${fmtMb(loaded)} MB ${file}`);
25460
+ next.lastRenderAt = now;
25461
+ }
25462
+ } else if (total > 0) {
25463
+ const pct = Math.floor(loaded / total * 100);
25464
+ const stepBumped = pct >= prior.lastRenderedPct + 5;
25465
+ const timeBumped = isTty && now - prior.lastRenderAt >= RENDER_INTERVAL_MS && pct !== prior.lastRenderedPct;
25466
+ if (stepBumped || timeBumped) {
25467
+ renderInPlace(`[cerefox-embed] ${makeBar(pct)} ${pct.toString().padStart(3)}% ${fmtMb(loaded)}/${fmtMb(total)} MB ${file}`);
25468
+ next.lastRenderedPct = pct;
25469
+ next.lastRenderAt = now;
25470
+ }
25471
+ }
25472
+ progressState.set(file, next);
25473
+ } else if (info3.status === "done") {
25474
+ const prior = progressState.get(file);
25475
+ finalizeLine();
25476
+ const finalSize = prior && prior.loaded > 0 ? `${fmtMb(prior.loaded)} MB` : info3.total && info3.total > 0 ? `${fmtMb(info3.total)} MB` : "cached";
25477
+ process.stderr.write(`[cerefox-embed] ✓ ${file} (${finalSize})
25478
+ `);
25479
+ if (prior)
25480
+ progressState.set(file, { ...prior, done: true });
25481
+ }
25482
+ };
25483
+ const pipe = await transformers.pipeline("feature-extraction", ONNX_MODEL_ID, {
25484
+ dtype: ONNX_MODEL_DTYPE,
25485
+ progress_callback: progressCallback
25486
+ });
25487
+ process.stderr.write(`[cerefox-embed] embedder ready.
25488
+ `);
25489
+ return pipe;
25490
+ })();
25491
+ pipelinePromise.catch(() => {
25492
+ pipelinePromise = null;
25493
+ });
25494
+ return pipelinePromise;
25495
+ }
25496
+ async function warmup() {
25497
+ await ensurePipeline();
25498
+ }
25499
+ async function onnxEmbed(texts, role) {
25500
+ if (texts.length === 0)
25249
25501
  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
- ];
25502
+ const pipeline = await ensurePipeline();
25503
+ const inputs = buildPrefixedInputs(texts, role);
25504
+ const out = await pipeline(inputs, { pooling: "mean", normalize: true });
25505
+ const dim2 = out.dims[out.dims.length - 1];
25506
+ if (dim2 !== ONNX_MODEL_DIM) {
25507
+ throw new Error(`OnnxEmbedder: expected dim=${ONNX_MODEL_DIM} (schema vector(768)), got ${dim2} from model`);
25254
25508
  }
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);
25509
+ const vectors = [];
25510
+ for (let i = 0;i < inputs.length; i++) {
25511
+ const slice = out.data.slice(i * dim2, (i + 1) * dim2);
25512
+ vectors.push(Array.from(l2Normalise(slice)));
25266
25513
  }
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;
25306
- }
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}`;
25514
+ return vectors;
25313
25515
  }
25314
- var CONTENT_FORMAT_BLIND_STITCH = 2;
25516
+ 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;
25517
+ var init_onnx_embedder = () => {};
25315
25518
 
25316
25519
  // ../../_shared/embeddings/index.ts
25520
+ var exports_embeddings = {};
25521
+ __export(exports_embeddings, {
25522
+ resolveEmbedderKind: () => resolveEmbedderKind,
25523
+ openaiEmbeddingConfig: () => openaiEmbeddingConfig,
25524
+ getEmbedding: () => getEmbedding,
25525
+ embeddingMaxInputChars: () => embeddingMaxInputChars,
25526
+ embedBatch: () => embedBatch,
25527
+ capEmbeddingInput: () => capEmbeddingInput,
25528
+ activeEmbedderName: () => activeEmbedderName,
25529
+ OPENAI_MODEL: () => OPENAI_MODEL,
25530
+ OPENAI_EMBEDDING_URL: () => OPENAI_EMBEDDING_URL,
25531
+ EMBEDDING_DIMENSIONS: () => EMBEDDING_DIMENSIONS,
25532
+ EMBEDDING_BATCH_SIZE: () => EMBEDDING_BATCH_SIZE,
25533
+ DEFAULT_EMBED_MAX_INPUT_CHARS: () => DEFAULT_EMBED_MAX_INPUT_CHARS
25534
+ });
25317
25535
  function openaiEmbeddingConfig() {
25318
25536
  const env4 = globalThis.process?.env ?? {};
25319
25537
  const base = env4.CEREFOX_OPENAI_BASE_URL?.replace(/\/+$/, "");
@@ -25340,7 +25558,22 @@ function capEmbeddingInput(text) {
25340
25558
  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
25559
  return cut;
25342
25560
  }
25561
+ function resolveEmbedderKind() {
25562
+ const env4 = globalThis.process?.env ?? {};
25563
+ return env4.CEREFOX_EMBEDDER === "local" ? "local" : "openai";
25564
+ }
25565
+ function activeEmbedderName() {
25566
+ return resolveEmbedderKind() === "local" ? "nomic-embed-text-v1.5" : openaiEmbeddingConfig().model;
25567
+ }
25568
+ async function onnxModule() {
25569
+ return await Promise.resolve().then(() => (init_onnx_embedder(), exports_onnx_embedder));
25570
+ }
25343
25571
  async function getEmbedding(text, apiKey) {
25572
+ if (resolveEmbedderKind() === "local") {
25573
+ const onnx = await onnxModule();
25574
+ const [vec] = await onnx.onnxEmbed([capEmbeddingInput(text)], "query");
25575
+ return vec;
25576
+ }
25344
25577
  let lastError = null;
25345
25578
  const cfg = openaiEmbeddingConfig();
25346
25579
  const input = capEmbeddingInput(text);
@@ -25432,6 +25665,10 @@ async function embedBatchSingleCall(texts, apiKey) {
25432
25665
  async function embedBatch(texts, apiKey, batchSize = EMBEDDING_BATCH_SIZE) {
25433
25666
  if (texts.length === 0)
25434
25667
  return [];
25668
+ if (resolveEmbedderKind() === "local") {
25669
+ const onnx = await onnxModule();
25670
+ return onnx.onnxEmbed(texts.map(capEmbeddingInput), "document");
25671
+ }
25435
25672
  if (texts.length <= batchSize) {
25436
25673
  return embedBatchSingleCall(texts, apiKey);
25437
25674
  }
@@ -25446,6 +25683,126 @@ async function embedBatch(texts, apiKey, batchSize = EMBEDDING_BATCH_SIZE) {
25446
25683
  }
25447
25684
  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
25685
 
25686
+ // ../../_shared/ingest/chunker.ts
25687
+ function cpLen(s) {
25688
+ let n = 0;
25689
+ for (const _ of s)
25690
+ n++;
25691
+ return n;
25692
+ }
25693
+ function rstripHash(s) {
25694
+ let i = s.length;
25695
+ while (i > 0 && s[i - 1] === "#")
25696
+ i--;
25697
+ return s.slice(0, i);
25698
+ }
25699
+ function findHeadings(doc) {
25700
+ const out = [];
25701
+ const re = /^(#{1,3})[ \t]+(.+)$/gm;
25702
+ let m;
25703
+ while ((m = re.exec(doc)) !== null) {
25704
+ out.push({ offset: m.index, level: m[1].length, text: rstripHash(m[2]).trim() });
25705
+ }
25706
+ return out;
25707
+ }
25708
+ function activeHeadings(headings, offset) {
25709
+ const stack = [];
25710
+ for (const h of headings) {
25711
+ if (h.offset > offset)
25712
+ break;
25713
+ while (stack.length && stack[stack.length - 1].level >= h.level)
25714
+ stack.pop();
25715
+ stack.push(h);
25716
+ }
25717
+ return stack;
25718
+ }
25719
+ function hardSplitCp(s, maxCp) {
25720
+ const out = [];
25721
+ let buf = "";
25722
+ let n = 0;
25723
+ for (const ch of s) {
25724
+ if (n >= maxCp) {
25725
+ out.push(buf);
25726
+ buf = "";
25727
+ n = 0;
25728
+ }
25729
+ buf += ch;
25730
+ n++;
25731
+ }
25732
+ if (buf)
25733
+ out.push(buf);
25734
+ return out;
25735
+ }
25736
+ function chunkMarkdown(text, maxChunkChars = 4000, _minChunkChars = 100) {
25737
+ const doc = text.trim();
25738
+ if (!doc)
25739
+ return [];
25740
+ if (cpLen(doc) <= maxChunkChars) {
25741
+ return [
25742
+ { chunk_index: 0, heading_path: [], heading_level: 0, title: "", content: doc, char_count: cpLen(doc) }
25743
+ ];
25744
+ }
25745
+ const headings = findHeadings(doc);
25746
+ const parts = doc.split(/(\n{2,})/);
25747
+ const atoms = [];
25748
+ for (let i = 0;i < parts.length; i += 2) {
25749
+ const unit = (parts[i] ?? "") + (parts[i + 1] ?? "");
25750
+ if (unit === "")
25751
+ continue;
25752
+ if (cpLen(unit) > maxChunkChars)
25753
+ atoms.push(...hardSplitCp(unit, maxChunkChars));
25754
+ else
25755
+ atoms.push(unit);
25756
+ }
25757
+ const chunks = [];
25758
+ let buf = "";
25759
+ let bufCp = 0;
25760
+ let bufStart = 0;
25761
+ let offset = 0;
25762
+ const flush = () => {
25763
+ if (buf === "")
25764
+ return;
25765
+ const stack = activeHeadings(headings, bufStart);
25766
+ chunks.push({
25767
+ chunk_index: chunks.length,
25768
+ heading_path: stack.map((h) => h.text),
25769
+ heading_level: stack.length ? stack[stack.length - 1].level : 0,
25770
+ title: stack.length ? stack[stack.length - 1].text : "",
25771
+ content: buf,
25772
+ char_count: bufCp
25773
+ });
25774
+ buf = "";
25775
+ bufCp = 0;
25776
+ };
25777
+ for (const atom of atoms) {
25778
+ const cp = cpLen(atom);
25779
+ if (buf === "") {
25780
+ bufStart = offset;
25781
+ buf = atom;
25782
+ bufCp = cp;
25783
+ } else if (bufCp + cp <= maxChunkChars) {
25784
+ buf += atom;
25785
+ bufCp += cp;
25786
+ } else {
25787
+ flush();
25788
+ bufStart = offset;
25789
+ buf = atom;
25790
+ bufCp = cp;
25791
+ }
25792
+ offset += atom.length;
25793
+ }
25794
+ flush();
25795
+ return chunks;
25796
+ }
25797
+ function embeddingInputFor(docTitle, chunk) {
25798
+ const breadcrumb = chunk.heading_path.join(" > ");
25799
+ const head2 = breadcrumb ? `# ${docTitle}
25800
+ ${breadcrumb}` : `# ${docTitle}`;
25801
+ return `${head2}
25802
+ ${chunk.content}`;
25803
+ }
25804
+ var CONTENT_FORMAT_BLIND_STITCH = 2;
25805
+
25449
25806
  // ../../node_modules/.bun/underscore@1.13.8/node_modules/underscore/underscore-node-f.cjs
25450
25807
  var require_underscore_node_f = __commonJS((exports) => {
25451
25808
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -32746,7 +33103,7 @@ var require_BufferList = __commonJS((exports, module) => {
32746
33103
  this.head = this.tail = null;
32747
33104
  this.length = 0;
32748
33105
  };
32749
- BufferList.prototype.join = function join9(s) {
33106
+ BufferList.prototype.join = function join10(s) {
32750
33107
  if (this.length === 0)
32751
33108
  return "";
32752
33109
  var p = this.head;
@@ -54469,6 +54826,9 @@ async function handler4(supabase, args, ctx) {
54469
54826
  const project_names_raw = args.project_names;
54470
54827
  const source = args.source ?? "agent";
54471
54828
  const metadata = args.metadata ?? null;
54829
+ if (metadata !== null && (typeof metadata !== "object" || Array.isArray(metadata))) {
54830
+ throw new McpInvalidParams('metadata must be a JSON object of key/value pairs, e.g. {"type":"note"} — not a string, number, or array');
54831
+ }
54472
54832
  const update_if_exists = args.update_if_exists ?? false;
54473
54833
  const author = args.author ?? "mcp-agent";
54474
54834
  const author_type = "agent";
@@ -54481,7 +54841,7 @@ async function handler4(supabase, args, ctx) {
54481
54841
  throw new McpInvalidParams("project_names must be a JSON array of strings; for a single project use project_name (string)");
54482
54842
  }
54483
54843
  const project_names = Array.isArray(project_names_raw) ? project_names_raw.filter((s) => typeof s === "string" && s.length > 0) : null;
54484
- if (!ctx.openaiApiKey) {
54844
+ if (!ctx.openaiApiKey && resolveEmbedderKind() !== "local") {
54485
54845
  throw new Error("OpenAI API key not configured. Set OPENAI_API_KEY (Edge Function) or CEREFOX_OPENAI_API_KEY (.env, local).");
54486
54846
  }
54487
54847
  const contentHash2 = await sha256hex(normalizeContent(content));
@@ -54503,7 +54863,7 @@ async function handler4(supabase, args, ctx) {
54503
54863
  if (chunks2.length === 0)
54504
54864
  throw new Error("Content produced no chunks");
54505
54865
  const texts2 = chunks2.map((c2) => embeddingInputFor(title, c2));
54506
- const embeddings2 = await embedBatch(texts2, ctx.openaiApiKey);
54866
+ const embeddings2 = await embedBatch(texts2, ctx.openaiApiKey ?? "");
54507
54867
  const totalChars2 = chunks2.reduce((s, c2) => s + c2.char_count, 0);
54508
54868
  const chunkData2 = chunks2.map((chunk, i) => ({
54509
54869
  chunk_index: i,
@@ -54513,7 +54873,7 @@ async function handler4(supabase, args, ctx) {
54513
54873
  content: chunk.content,
54514
54874
  char_count: chunk.char_count,
54515
54875
  embedding: embeddings2[i],
54516
- embedder: OPENAI_MODEL
54876
+ embedder: activeEmbedderName()
54517
54877
  }));
54518
54878
  const { error: ingestErr2 } = await supabase.rpc("cerefox_ingest_document", {
54519
54879
  p_document_id: existingDoc.id,
@@ -54561,7 +54921,7 @@ async function handler4(supabase, args, ctx) {
54561
54921
  if (chunks2.length === 0)
54562
54922
  throw new Error("Content produced no chunks");
54563
54923
  const texts2 = chunks2.map((c2) => embeddingInputFor(title, c2));
54564
- const embeddings2 = await embedBatch(texts2, ctx.openaiApiKey);
54924
+ const embeddings2 = await embedBatch(texts2, ctx.openaiApiKey ?? "");
54565
54925
  const totalChars2 = chunks2.reduce((s, c2) => s + c2.char_count, 0);
54566
54926
  const chunkData2 = chunks2.map((chunk, i) => ({
54567
54927
  chunk_index: i,
@@ -54571,7 +54931,7 @@ async function handler4(supabase, args, ctx) {
54571
54931
  content: chunk.content,
54572
54932
  char_count: chunk.char_count,
54573
54933
  embedding: embeddings2[i],
54574
- embedder: OPENAI_MODEL
54934
+ embedder: activeEmbedderName()
54575
54935
  }));
54576
54936
  const { error: ingestErr2 } = await supabase.rpc("cerefox_ingest_document", {
54577
54937
  p_document_id: existingDoc.id,
@@ -54613,7 +54973,7 @@ async function handler4(supabase, args, ctx) {
54613
54973
  if (chunks.length === 0)
54614
54974
  throw new Error("Content produced no chunks");
54615
54975
  const texts = chunks.map((c2) => embeddingInputFor(title, c2));
54616
- const embeddings = await embedBatch(texts, ctx.openaiApiKey);
54976
+ const embeddings = await embedBatch(texts, ctx.openaiApiKey ?? "");
54617
54977
  const totalChars = chunks.reduce((s, c2) => s + c2.char_count, 0);
54618
54978
  const chunkData = chunks.map((chunk, i) => ({
54619
54979
  chunk_index: i,
@@ -54623,7 +54983,7 @@ async function handler4(supabase, args, ctx) {
54623
54983
  content: chunk.content,
54624
54984
  char_count: chunk.char_count,
54625
54985
  embedding: embeddings[i],
54626
- embedder: OPENAI_MODEL
54986
+ embedder: activeEmbedderName()
54627
54987
  }));
54628
54988
  const { data: ingestResult, error: ingestErr } = await supabase.rpc("cerefox_ingest_document", {
54629
54989
  p_document_id: null,
@@ -54960,7 +55320,7 @@ async function handler9(supabase, args, ctx) {
54960
55320
  }
54961
55321
  if (!query?.trim())
54962
55322
  throw new McpInvalidParams("query is required");
54963
- if (mode !== "fts" && !ctx.openaiApiKey) {
55323
+ if (mode !== "fts" && !ctx.openaiApiKey && resolveEmbedderKind() !== "local") {
54964
55324
  throw new Error("OpenAI API key not configured. Set OPENAI_API_KEY (Edge Function) or CEREFOX_OPENAI_API_KEY (.env, local).");
54965
55325
  }
54966
55326
  let projectId = null;
@@ -54971,7 +55331,7 @@ async function handler9(supabase, args, ctx) {
54971
55331
  }
54972
55332
  let embedding = null;
54973
55333
  if (mode !== "fts") {
54974
- embedding = await getEmbedding(query, ctx.openaiApiKey);
55334
+ embedding = await getEmbedding(query, ctx.openaiApiKey ?? "");
54975
55335
  }
54976
55336
  const metaFilterParam = metadata_filter && Object.keys(metadata_filter).length > 0 ? { p_metadata_filter: metadata_filter } : {};
54977
55337
  let rpcName;
@@ -68266,6 +68626,7 @@ var exports_server = {};
68266
68626
  __export(exports_server, {
68267
68627
  buildServer: () => buildServer
68268
68628
  });
68629
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "node:fs";
68269
68630
  function buildServer() {
68270
68631
  const settings = loadSettings();
68271
68632
  if (!settings.supabaseUrl || !settings.supabaseKey) {
@@ -68314,21 +68675,36 @@ async function warnIfSchemaVersionMismatch(supabase) {
68314
68675
  try {
68315
68676
  const { data } = await supabase.rpc("cerefox_schema_version");
68316
68677
  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.
68678
+ if (!deployed)
68679
+ return;
68680
+ let bundled = null;
68681
+ try {
68682
+ const assets = resolveServerAssets();
68683
+ if (existsSync12(assets.schemaFile)) {
68684
+ const m = readFileSync12(assets.schemaFile, "utf8").match(SCHEMA_VERSION_RE2);
68685
+ bundled = m ? m[1] : null;
68686
+ }
68687
+ } catch {}
68688
+ if (!bundled)
68689
+ return;
68690
+ if (compareSemver(deployed, bundled) < 0) {
68691
+ 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
68692
  `);
68320
68693
  }
68321
68694
  } catch {}
68322
68695
  }
68323
- var SERVER_NAME = "cerefox";
68696
+ var SERVER_NAME = "cerefox", SCHEMA_VERSION_RE2;
68324
68697
  var init_server3 = __esm(() => {
68325
68698
  init_server2();
68326
68699
  init_stdio2();
68327
68700
  init_types4();
68328
68701
  init_dist4();
68702
+ init_compatibility();
68329
68703
  init_config();
68704
+ init_server_assets();
68330
68705
  init_mcp_tools();
68331
68706
  init_meta();
68707
+ SCHEMA_VERSION_RE2 = /^--\s*@version:\s*(\S+)/m;
68332
68708
  });
68333
68709
 
68334
68710
  // src/bin/cerefox.ts
@@ -70166,58 +70542,11 @@ function registerDeleteProject(program2) {
70166
70542
  // src/cli/commands/deploy-server.ts
70167
70543
  init_cli_core();
70168
70544
  init_config();
70545
+ init_server_assets();
70169
70546
  import { spawnSync as spawnSync2 } from "node:child_process";
70170
70547
  import { existsSync as existsSync7 } from "node:fs";
70171
70548
  import { readdirSync as readdirSync2 } from "node:fs";
70172
70549
 
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
70550
  // ../../_shared/db-deploy/index.ts
70222
70551
  init_src();
70223
70552
  import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync } from "node:fs";
@@ -74276,112 +74605,18 @@ init_cli_core();
74276
74605
 
74277
74606
  // src/cli/util/checks.ts
74278
74607
  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";
74608
+ import { existsSync as existsSync10, readFileSync as readFileSync7, realpathSync, statSync as statSync2 } from "node:fs";
74609
+ import { homedir as homedir6 } from "node:os";
74610
+ import { join as join9 } from "node:path";
74282
74611
 
74283
74612
  // ../../_shared/ef-meta/index.ts
74284
- var EF_VERSION = "1.0.0-beta.3";
74613
+ var EF_VERSION = "1.0.0-beta.4";
74285
74614
 
74286
74615
  // src/cli/util/checks.ts
74287
74616
  init_config();
74288
74617
  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
74618
+ init_compatibility();
74619
+ init_server_assets();
74385
74620
  function checkBinary() {
74386
74621
  return {
74387
74622
  name: "binary",
@@ -74432,7 +74667,7 @@ function checkConfig() {
74432
74667
  hint: "Run `cerefox init` to bootstrap."
74433
74668
  };
74434
74669
  }
74435
- if (!existsSync9(envPath)) {
74670
+ if (!existsSync10(envPath)) {
74436
74671
  return {
74437
74672
  name: "config",
74438
74673
  status: "error",
@@ -74514,6 +74749,14 @@ async function checkSupabase() {
74514
74749
  async function checkOpenAI() {
74515
74750
  const settings = loadSettings();
74516
74751
  if (!settings.openaiApiKey) {
74752
+ const { resolveEmbedderKind: resolveEmbedderKind2 } = await Promise.resolve().then(() => exports_embeddings);
74753
+ if (resolveEmbedderKind2() === "local") {
74754
+ return {
74755
+ name: "openai",
74756
+ status: "skipped",
74757
+ detail: "no key set — not needed (local embedder active; see the embedder check)."
74758
+ };
74759
+ }
74517
74760
  return {
74518
74761
  name: "openai",
74519
74762
  status: "warn",
@@ -74560,7 +74803,7 @@ var SCHEMA_VERSION_RE = /^--\s*@version:\s*(\S+)/m;
74560
74803
  function readBundledSchemaVersion() {
74561
74804
  try {
74562
74805
  const assets = resolveServerAssets();
74563
- if (!existsSync9(assets.schemaFile))
74806
+ if (!existsSync10(assets.schemaFile))
74564
74807
  return null;
74565
74808
  const m = readFileSync7(assets.schemaFile, "utf8").match(SCHEMA_VERSION_RE);
74566
74809
  return m ? m[1] : null;
@@ -74630,6 +74873,46 @@ async function checkSchemaVersion() {
74630
74873
  };
74631
74874
  }
74632
74875
  }
74876
+ var EMBEDDER_CHECK_NAME = "embedder";
74877
+ async function checkEmbedderMismatch() {
74878
+ const settings = loadSettings();
74879
+ if (!settings.supabaseUrl || !settings.supabaseKey) {
74880
+ return { name: EMBEDDER_CHECK_NAME, status: "skipped", detail: "Supabase config missing; skipped." };
74881
+ }
74882
+ const { activeEmbedderName: activeEmbedderName2 } = await Promise.resolve().then(() => exports_embeddings);
74883
+ const active = activeEmbedderName2();
74884
+ try {
74885
+ const url = `${settings.supabaseUrl.replace(/\/$/, "")}/rest/v1/cerefox_chunks?version_id=is.null&embedder_primary=not.is.null&select=embedder_primary&limit=1000`;
74886
+ const resp = await fetch(url, {
74887
+ headers: { apikey: settings.supabaseKey, Authorization: `Bearer ${settings.supabaseKey}` }
74888
+ });
74889
+ if (!resp.ok) {
74890
+ return { name: EMBEDDER_CHECK_NAME, status: "skipped", detail: `chunk probe returned ${resp.status}; skipped.` };
74891
+ }
74892
+ const rows = await resp.json();
74893
+ const recorded = [...new Set(rows.map((r) => r.embedder_primary))];
74894
+ const stale = recorded.filter((r) => r !== active);
74895
+ if (stale.length === 0) {
74896
+ return {
74897
+ name: EMBEDDER_CHECK_NAME,
74898
+ status: "ok",
74899
+ detail: `configured "${active}"${recorded.length ? " — matches all existing chunks" : " (no embedded chunks yet)"}`
74900
+ };
74901
+ }
74902
+ return {
74903
+ name: EMBEDDER_CHECK_NAME,
74904
+ status: "warn",
74905
+ detail: `configured "${active}" but existing chunks were embedded with ${stale.map((r) => `"${r}"`).join(", ")} — semantic search across them is broken.`,
74906
+ hint: "Run `cerefox server reindex` to re-embed everything with the configured embedder."
74907
+ };
74908
+ } catch (err) {
74909
+ return {
74910
+ name: EMBEDDER_CHECK_NAME,
74911
+ status: "skipped",
74912
+ detail: `probe failed: ${err instanceof Error ? err.message : String(err)}`
74913
+ };
74914
+ }
74915
+ }
74633
74916
  var CONTENT_FORMAT_CHECK_NAME = "content format";
74634
74917
  async function checkContentFormat() {
74635
74918
  const settings = loadSettings();
@@ -74679,7 +74962,7 @@ async function checkContentFormat() {
74679
74962
  }
74680
74963
  }
74681
74964
  function hasCerefoxInJsonFile(path) {
74682
- if (!existsSync9(path))
74965
+ if (!existsSync10(path))
74683
74966
  return false;
74684
74967
  try {
74685
74968
  const parsed = JSON.parse(readFileSync7(path, "utf8"));
@@ -74690,10 +74973,10 @@ function hasCerefoxInJsonFile(path) {
74690
74973
  }
74691
74974
  }
74692
74975
  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");
74976
+ const home = homedir6();
74977
+ const claudeCodeUser = join9(home, ".claude.json");
74978
+ const claudeCodeProj = join9(process.cwd(), ".mcp.json");
74979
+ 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
74980
  const found = [];
74698
74981
  if (hasCerefoxInJsonFile(claudeCodeUser))
74699
74982
  found.push("Claude Code (user)");
@@ -74716,10 +74999,10 @@ function checkMcpConfigs() {
74716
74999
  };
74717
75000
  }
74718
75001
  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))
75002
+ const home = homedir6();
75003
+ const homeEnv = join9(home, USER_STATE_DIR_NAME, ".env");
75004
+ const cwdEnv = join9(process.cwd(), ".env");
75005
+ if (!existsSync10(homeEnv) || !existsSync10(cwdEnv))
74723
75006
  return null;
74724
75007
  try {
74725
75008
  if (realpathSync(homeEnv) === realpathSync(cwdEnv))
@@ -74729,7 +75012,7 @@ function checkLegacyShadowEnv() {
74729
75012
  name: "legacy env",
74730
75013
  status: "skipped",
74731
75014
  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."
75015
+ hint: "Shadowed by ~/.cerefox/.env and no longer read by anything (Python was removed at v1.0.0). Safe to delete."
74733
75016
  };
74734
75017
  }
74735
75018
  async function checkPostgres() {
@@ -74866,6 +75149,7 @@ async function runAllChecks(opts = {}) {
74866
75149
  { name: "supabase", phase: "Probing Supabase Data API", run: () => checkSupabase() },
74867
75150
  { name: "openai", phase: "Probing OpenAI embeddings", run: () => checkOpenAI() },
74868
75151
  { name: "schema + RPCs", phase: "Reading schema + RPC version", run: () => checkSchemaVersion() },
75152
+ { name: "embedder", phase: "Checking embedder consistency", run: () => checkEmbedderMismatch() },
74869
75153
  { name: "content format", phase: "Checking chunk reconstruction format", run: () => checkContentFormat() },
74870
75154
  { name: "edge functions", phase: "Probing Edge Function versions", run: () => checkEdgeFunctionsCompat() },
74871
75155
  { name: "postgres", phase: "Probing Postgres DDL endpoint", run: () => checkPostgres() },
@@ -75316,7 +75600,7 @@ class IngestionPipeline {
75316
75600
  constructor(deps) {
75317
75601
  this.db = new IngestionDbBridge(deps.supabase);
75318
75602
  this.apiKey = deps.openAiApiKey;
75319
- this.embedderModel = deps.embedderModel ?? "text-embedding-3-small";
75603
+ this.embedderModel = deps.embedderModel ?? activeEmbedderName();
75320
75604
  this.settings = { ...loadPipelineSettings(), ...deps.settings ?? {} };
75321
75605
  }
75322
75606
  async ingestText(opts) {
@@ -75681,7 +75965,7 @@ async function action18(path, options) {
75681
75965
  if (!settings.supabaseUrl || !settings.supabaseKey) {
75682
75966
  throw userError("Supabase credentials not configured — run `cerefox init` first.");
75683
75967
  }
75684
- if (!settings.openaiApiKey) {
75968
+ if (!settings.openaiApiKey && resolveEmbedderKind() !== "local") {
75685
75969
  throw userError("OPENAI_API_KEY not set — required for embeddings during ingest.");
75686
75970
  }
75687
75971
  const supabase = createClient(settings.supabaseUrl, settings.supabaseKey, {
@@ -75744,7 +76028,7 @@ init_cli_core();
75744
76028
  init_config();
75745
76029
  var import_cli_progress = __toESM(require_cli_progress(), 1);
75746
76030
  import { readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
75747
- import { basename as basename3, extname as extname4, join as join9 } from "node:path";
76031
+ import { basename as basename3, extname as extname4, join as join10 } from "node:path";
75748
76032
  function walk(dir, extensions) {
75749
76033
  let entries;
75750
76034
  try {
@@ -75754,7 +76038,7 @@ function walk(dir, extensions) {
75754
76038
  }
75755
76039
  const files = [];
75756
76040
  for (const name of entries) {
75757
- const full = join9(dir, name);
76041
+ const full = join10(dir, name);
75758
76042
  let stat;
75759
76043
  try {
75760
76044
  stat = statSync3(full);
@@ -75788,7 +76072,7 @@ async function action19(dir, options) {
75788
76072
  if (!settings.supabaseUrl || !settings.supabaseKey) {
75789
76073
  throw userError("Supabase credentials not configured — run `cerefox init` first.");
75790
76074
  }
75791
- if (!settings.openaiApiKey) {
76075
+ if (!settings.openaiApiKey && resolveEmbedderKind() !== "local") {
75792
76076
  throw userError("OPENAI_API_KEY not set — required for embeddings during ingest.");
75793
76077
  }
75794
76078
  const supabase = createClient(settings.supabaseUrl, settings.supabaseKey, {
@@ -75851,19 +76135,20 @@ function registerIngestDir(program2) {
75851
76135
  // src/cli/commands/init.ts
75852
76136
  init_cli_core();
75853
76137
  init_config();
76138
+ init_compatibility();
75854
76139
  import { spawnSync as spawnSync4 } from "node:child_process";
75855
76140
  import {
75856
76141
  chmodSync,
75857
76142
  copyFileSync as copyFileSync2,
75858
- existsSync as existsSync10,
75859
- mkdirSync as mkdirSync3,
76143
+ existsSync as existsSync11,
76144
+ mkdirSync as mkdirSync4,
75860
76145
  readFileSync as readFileSync11,
75861
76146
  writeFileSync as writeFileSync4
75862
76147
  } from "node:fs";
75863
- import { homedir as homedir6 } from "node:os";
75864
- import { dirname as dirname4, join as join10 } from "node:path";
76148
+ import { homedir as homedir7 } from "node:os";
76149
+ import { dirname as dirname4, join as join11 } from "node:path";
75865
76150
  async function readConfigFile(path) {
75866
- if (!existsSync10(path)) {
76151
+ if (!existsSync11(path)) {
75867
76152
  throw userError(`--config file not found: ${path}`);
75868
76153
  }
75869
76154
  let parsed;
@@ -76165,7 +76450,7 @@ async function postWriteLifecycle(envPath, options) {
76165
76450
  println(c.dim(` Config in effect: ${envPath}`));
76166
76451
  }
76167
76452
  function writeAnswersTo(target, answers) {
76168
- mkdirSync3(dirname4(target), { recursive: true });
76453
+ mkdirSync4(dirname4(target), { recursive: true });
76169
76454
  writeFileSync4(target, buildEnvFile(answers), "utf8");
76170
76455
  if (process.platform !== "win32") {
76171
76456
  try {
@@ -76176,12 +76461,12 @@ function writeAnswersTo(target, answers) {
76176
76461
  }
76177
76462
  }
76178
76463
  async function action21(options) {
76179
- const homeEnv = join10(homedir6(), USER_STATE_DIR_NAME, ".env");
76180
- const cwdEnv = join10(process.cwd(), ".env");
76464
+ const homeEnv = join11(homedir7(), USER_STATE_DIR_NAME, ".env");
76465
+ const cwdEnv = join11(process.cwd(), ".env");
76181
76466
  const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
76182
76467
  if (explicitDir) {
76183
76468
  const target2 = resolveEnvFile();
76184
- if (existsSync10(target2) && !options.force) {
76469
+ if (existsSync11(target2) && !options.force) {
76185
76470
  println(c.yellow(`⚠ Config already exists at ${target2}.`));
76186
76471
  const ok2 = await confirm("Overwrite?", true);
76187
76472
  if (!ok2) {
@@ -76203,7 +76488,7 @@ async function action21(options) {
76203
76488
  await postWriteLifecycle(target2, options);
76204
76489
  return;
76205
76490
  }
76206
- if (existsSync10(homeEnv) && !options.force) {
76491
+ if (existsSync11(homeEnv) && !options.force) {
76207
76492
  println(c.yellow(`⚠ Config already exists at ${homeEnv}.`));
76208
76493
  const ok2 = await confirm("Overwrite?", true);
76209
76494
  if (!ok2) {
@@ -76224,12 +76509,12 @@ async function action21(options) {
76224
76509
  await postWriteLifecycle(homeEnv, options);
76225
76510
  return;
76226
76511
  }
76227
- if (existsSync10(cwdEnv) && !options.force && !options.config) {
76512
+ if (existsSync11(cwdEnv) && !options.force && !options.config) {
76228
76513
  printMigrationMenu(cwdEnv, homeEnv);
76229
76514
  const ch = await promptMigrationChoice();
76230
76515
  println("");
76231
76516
  if (ch === "c") {
76232
- mkdirSync3(dirname4(homeEnv), { recursive: true });
76517
+ mkdirSync4(dirname4(homeEnv), { recursive: true });
76233
76518
  copyFileSync2(cwdEnv, homeEnv);
76234
76519
  if (process.platform !== "win32") {
76235
76520
  try {
@@ -76478,6 +76763,23 @@ function registerMcp(program2) {
76478
76763
  });
76479
76764
  }
76480
76765
 
76766
+ // src/cli/commands/embedder-warmup.ts
76767
+ function registerEmbedderWarmup(program2) {
76768
+ program2.command("embedder-warmup", { hidden: true }).description("Download + warm the local ONNX embedding model (Cerefox Local).").action(async () => {
76769
+ if (process.env.CEREFOX_EMBEDDER !== "local") {
76770
+ process.stderr.write(`embedder-warmup: CEREFOX_EMBEDDER is not 'local' — nothing to warm (the OpenAI embedder has no local model).
76771
+ `);
76772
+ process.exitCode = 1;
76773
+ return;
76774
+ }
76775
+ const onnx = await Promise.resolve().then(() => (init_onnx_embedder(), exports_onnx_embedder));
76776
+ await onnx.warmup();
76777
+ const [vec] = await onnx.onnxEmbed(["warmup"], "query");
76778
+ process.stderr.write(`[cerefox-embed] warm — ${vec.length}-dim vectors ready.
76779
+ `);
76780
+ });
76781
+ }
76782
+
76481
76783
  // src/cli/commands/metadata-search.ts
76482
76784
  init_cli_core();
76483
76785
  init_client();
@@ -76637,35 +76939,35 @@ function registerReindex(program2) {
76637
76939
  // src/cli/commands/restore.ts
76638
76940
  init_cli_core();
76639
76941
  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";
76942
+ import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
76943
+ import { homedir as homedir8 } from "node:os";
76944
+ import { join as join12, resolve as resolve4 } from "node:path";
76643
76945
  function expandHome2(path) {
76644
76946
  if (path === "~")
76645
- return homedir7();
76947
+ return homedir8();
76646
76948
  if (path.startsWith("~/"))
76647
- return join11(homedir7(), path.slice(2));
76949
+ return join12(homedir8(), path.slice(2));
76648
76950
  return path;
76649
76951
  }
76650
76952
  function resolveBackupFile(target) {
76651
76953
  const path = resolve4(expandHome2(target));
76652
- if (!existsSync11(path)) {
76954
+ if (!existsSync13(path)) {
76653
76955
  throw userError(`Backup path not found: ${target}`);
76654
76956
  }
76655
76957
  const stat = statSync4(path);
76656
76958
  if (stat.isFile())
76657
76959
  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);
76960
+ 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
76961
  if (candidates.length === 0) {
76660
76962
  throw userError(`No cerefox-*.json files in ${path}`);
76661
76963
  }
76662
- return join11(path, candidates[0].name);
76964
+ return join12(path, candidates[0].name);
76663
76965
  }
76664
76966
  async function action28(target, options) {
76665
76967
  const file = resolveBackupFile(target);
76666
76968
  let payload;
76667
76969
  try {
76668
- payload = JSON.parse(readFileSync12(file, "utf8"));
76970
+ payload = JSON.parse(readFileSync13(file, "utf8"));
76669
76971
  } catch (err) {
76670
76972
  throw userError(`Could not parse backup file ${file}: ${err instanceof Error ? err.message : String(err)}`);
76671
76973
  }
@@ -76730,7 +77032,7 @@ init_config();
76730
77032
  async function embedQuery(query) {
76731
77033
  const settings = loadSettings();
76732
77034
  const apiKey = settings.openaiApiKey;
76733
- if (!apiKey) {
77035
+ if (!apiKey && resolveEmbedderKind() !== "local") {
76734
77036
  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
77037
  }
76736
77038
  try {
@@ -77045,7 +77347,7 @@ import { randomBytes } from "node:crypto";
77045
77347
  import { spawnSync as spawnSync7 } from "node:child_process";
77046
77348
 
77047
77349
  // src/cli/util/env-file.ts
77048
- import { copyFileSync as copyFileSync3, existsSync as existsSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "node:fs";
77350
+ import { copyFileSync as copyFileSync3, existsSync as existsSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "node:fs";
77049
77351
  import { dirname as dirname5 } from "node:path";
77050
77352
  import { spawnSync as spawnSync6 } from "node:child_process";
77051
77353
  function escapeRegExp(s) {
@@ -77055,12 +77357,12 @@ function upsertEnvVar(path, key, value, opts = {}) {
77055
77357
  const line = `${key}=${value}`;
77056
77358
  const header = opts.comment ? `# ${opts.comment}
77057
77359
  ` : "";
77058
- if (!existsSync12(path)) {
77360
+ if (!existsSync14(path)) {
77059
77361
  writeFileSync5(path, `${header}${line}
77060
77362
  `, { mode: 384 });
77061
77363
  return { path, action: "created" };
77062
77364
  }
77063
- const original = readFileSync13(path, "utf8");
77365
+ const original = readFileSync14(path, "utf8");
77064
77366
  let backupPath;
77065
77367
  if (!opts.noBackup) {
77066
77368
  backupPath = `${path}.pre-cerefox.bak`;
@@ -77085,9 +77387,9 @@ ${header}${line}
77085
77387
  return { path, action: action32, backupPath };
77086
77388
  }
77087
77389
  function readEnvVar(path, key) {
77088
- if (!existsSync12(path))
77390
+ if (!existsSync14(path))
77089
77391
  return null;
77090
- const m = readFileSync13(path, "utf8").match(new RegExp(`^\\s*${escapeRegExp(key)}=(.*)$`, "m"));
77392
+ const m = readFileSync14(path, "utf8").match(new RegExp(`^\\s*${escapeRegExp(key)}=(.*)$`, "m"));
77091
77393
  return m ? m[1].trim() : null;
77092
77394
  }
77093
77395
  function envGitignoreWarning(path) {
@@ -78555,8 +78857,8 @@ var _baseMimes = {
78555
78857
  var baseMimes = _baseMimes;
78556
78858
 
78557
78859
  // ../../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";
78860
+ import { createReadStream, existsSync as existsSync15, statSync as statSync5 } from "node:fs";
78861
+ import { join as join13 } from "node:path";
78560
78862
  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
78863
  var ENCODINGS = {
78562
78864
  br: ".br",
@@ -78588,7 +78890,7 @@ var tryDecodeURI = (str) => tryDecode(str, decodeURI);
78588
78890
  var serveStatic = (options = { root: "" }) => {
78589
78891
  const root = options.root || "";
78590
78892
  const optionPath = options.path;
78591
- if (root !== "" && !existsSync13(root))
78893
+ if (root !== "" && !existsSync15(root))
78592
78894
  console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
78593
78895
  return async (c2, next) => {
78594
78896
  if (c2.finalized)
@@ -78605,11 +78907,11 @@ var serveStatic = (options = { root: "" }) => {
78605
78907
  await options.onNotFound?.(c2.req.path, c2);
78606
78908
  return next();
78607
78909
  }
78608
- let path = join12(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c2) : filename);
78910
+ let path = join13(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c2) : filename);
78609
78911
  let stats = getStats(path);
78610
78912
  if (stats && stats.isDirectory()) {
78611
78913
  const indexFile = options.index ?? "index.html";
78612
- path = join12(path, indexFile);
78914
+ path = join13(path, indexFile);
78613
78915
  stats = getStats(path);
78614
78916
  }
78615
78917
  if (!stats) {
@@ -78666,9 +78968,9 @@ var serveStatic = (options = { root: "" }) => {
78666
78968
  };
78667
78969
 
78668
78970
  // 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";
78971
+ import { existsSync as existsSync19 } from "node:fs";
78972
+ import { readFileSync as readFileSync17 } from "node:fs";
78973
+ import { join as join17 } from "node:path";
78672
78974
 
78673
78975
  // ../../node_modules/.bun/hono@4.12.29/node_modules/hono/dist/compose.js
78674
78976
  var compose = (middleware, onError, onNotFound) => {
@@ -80697,7 +80999,7 @@ async function runSearch(ctx, opts) {
80697
80999
  throw error4;
80698
81000
  return (data2 ?? []).map(projectChunkResult);
80699
81001
  }
80700
- if (!ctx.openAiApiKey) {
81002
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
80701
81003
  throw new HttpError(503, "Embedder not available");
80702
81004
  }
80703
81005
  const embedding = await getEmbedding(query, ctx.openAiApiKey);
@@ -81238,7 +81540,6 @@ function registerDocumentReadRoutes(app, ctx) {
81238
81540
  return c2.json({ exists: false });
81239
81541
  });
81240
81542
  }
81241
-
81242
81543
  // src/web/routes/documents-write.ts
81243
81544
  async function createAuditEntry(ctx, args) {
81244
81545
  try {
@@ -81289,7 +81590,7 @@ function registerDocumentWriteRoutes(app, ctx) {
81289
81590
  const proposedHash = content.trim() ? contentHash(content) : null;
81290
81591
  const contentChanged = proposedHash !== null && currentHash !== null && proposedHash !== currentHash;
81291
81592
  if (contentChanged) {
81292
- if (!ctx.openAiApiKey) {
81593
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81293
81594
  return c2.json({
81294
81595
  success: false,
81295
81596
  error: "Embedder not available — set OPENAI_API_KEY in your config"
@@ -81445,14 +81746,13 @@ function registerDocumentWriteRoutes(app, ctx) {
81445
81746
  return c2.json({ archived });
81446
81747
  });
81447
81748
  }
81448
-
81449
81749
  // src/web/routes/ingest.ts
81450
81750
  function notReady(error3) {
81451
81751
  return { success: false, error: error3 };
81452
81752
  }
81453
81753
  function registerIngestRoutes(app, ctx) {
81454
81754
  app.post("/api/v1/ingest", async (c2) => {
81455
- if (!ctx.openAiApiKey) {
81755
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81456
81756
  return c2.json(notReady("Embedder not available"), 503);
81457
81757
  }
81458
81758
  let body;
@@ -81500,7 +81800,7 @@ function registerIngestRoutes(app, ctx) {
81500
81800
  }
81501
81801
  });
81502
81802
  app.post("/api/v1/ingest/file", async (c2) => {
81503
- if (!ctx.openAiApiKey) {
81803
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81504
81804
  return c2.json(notReady("Embedder not available"), 503);
81505
81805
  }
81506
81806
  let form;
@@ -81562,7 +81862,7 @@ function registerIngestRoutes(app, ctx) {
81562
81862
  }
81563
81863
  });
81564
81864
  app.post("/api/v1/documents/:document_id/upload", async (c2) => {
81565
- if (!ctx.openAiApiKey) {
81865
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81566
81866
  return c2.json(notReady("Embedder not available"), 503);
81567
81867
  }
81568
81868
  const documentId = c2.req.param("document_id");
@@ -81617,12 +81917,12 @@ import { execFileSync } from "node:child_process";
81617
81917
 
81618
81918
  // src/web/docs.ts
81619
81919
  import {
81620
- existsSync as existsSync14,
81621
- readFileSync as readFileSync14,
81920
+ existsSync as existsSync16,
81921
+ readFileSync as readFileSync15,
81622
81922
  readdirSync as readdirSync6,
81623
81923
  statSync as statSync6
81624
81924
  } from "node:fs";
81625
- import { basename as basename5, dirname as dirname6, join as join13, resolve as resolve5 } from "node:path";
81925
+ import { basename as basename5, dirname as dirname6, join as join14, resolve as resolve5 } from "node:path";
81626
81926
  import { fileURLToPath as fileURLToPath3 } from "node:url";
81627
81927
  var TOP_LEVEL_DOCS = [
81628
81928
  { filename: "README.md", path: "README.md", category: "readme" },
@@ -81643,32 +81943,32 @@ function moduleDir2() {
81643
81943
  function resolveDocsRoots() {
81644
81944
  const here = moduleDir2();
81645
81945
  const pkgRootCandidates = [
81646
- join13(here, "..", ".."),
81647
- join13(here, "..", "..", "..", "..")
81946
+ join14(here, "..", ".."),
81947
+ join14(here, "..", "..", "..", "..")
81648
81948
  ];
81649
81949
  let pkgGuides = null;
81650
81950
  let pkgTopLevel = null;
81651
81951
  for (const pkg of pkgRootCandidates) {
81652
- const guides = join13(pkg, "docs", "guides");
81653
- if (existsSync14(guides) && statSync6(guides).isDirectory()) {
81952
+ const guides = join14(pkg, "docs", "guides");
81953
+ if (existsSync16(guides) && statSync6(guides).isDirectory()) {
81654
81954
  pkgGuides = guides;
81655
81955
  pkgTopLevel = pkg;
81656
81956
  break;
81657
81957
  }
81658
81958
  }
81659
- const repoCandidate = join13(here, "..", "..", "..", "..");
81660
- const repoGuides = join13(repoCandidate, "docs", "guides");
81959
+ const repoCandidate = join14(here, "..", "..", "..", "..");
81960
+ const repoGuides = join14(repoCandidate, "docs", "guides");
81661
81961
  const repoTopLevel = repoCandidate;
81662
81962
  return {
81663
81963
  pkgGuides,
81664
81964
  pkgTopLevel,
81665
- repoGuides: existsSync14(repoGuides) ? repoGuides : null,
81666
- repoTopLevel: existsSync14(join13(repoTopLevel, "README.md")) ? repoTopLevel : null
81965
+ repoGuides: existsSync16(repoGuides) ? repoGuides : null,
81966
+ repoTopLevel: existsSync16(join14(repoTopLevel, "README.md")) ? repoTopLevel : null
81667
81967
  };
81668
81968
  }
81669
81969
  function readH1(filePath) {
81670
81970
  try {
81671
- const content = readFileSync14(filePath, "utf8");
81971
+ const content = readFileSync15(filePath, "utf8");
81672
81972
  const match2 = content.match(/^#\s+(.+?)\s*$/m);
81673
81973
  return match2 ? match2[1] : null;
81674
81974
  } catch {
@@ -81688,8 +81988,8 @@ function listBundledDocs2() {
81688
81988
  const topRoot = pkgTopLevel ?? repoTopLevel;
81689
81989
  if (topRoot) {
81690
81990
  for (const t of TOP_LEVEL_DOCS) {
81691
- const abs = join13(topRoot, t.filename);
81692
- if (existsSync14(abs)) {
81991
+ const abs = join14(topRoot, t.filename);
81992
+ if (existsSync16(abs)) {
81693
81993
  entries.push(entryForFile(abs, t.path, t.category));
81694
81994
  }
81695
81995
  }
@@ -81698,7 +81998,7 @@ function listBundledDocs2() {
81698
81998
  if (guidesRoot) {
81699
81999
  const names = readdirSync6(guidesRoot).filter((n) => n.endsWith(".md")).sort();
81700
82000
  for (const name of names) {
81701
- const abs = join13(guidesRoot, name);
82001
+ const abs = join14(guidesRoot, name);
81702
82002
  entries.push(entryForFile(abs, `guides/${name}`, "guide"));
81703
82003
  }
81704
82004
  }
@@ -81716,9 +82016,9 @@ function readDoc(docPath) {
81716
82016
  if (!candidate.startsWith(rootResolved + "/") && candidate !== rootResolved) {
81717
82017
  return null;
81718
82018
  }
81719
- if (existsSync14(candidate) && statSync6(candidate).isFile()) {
82019
+ if (existsSync16(candidate) && statSync6(candidate).isFile()) {
81720
82020
  try {
81721
- return readFileSync14(candidate, "utf8");
82021
+ return readFileSync15(candidate, "utf8");
81722
82022
  } catch {
81723
82023
  return null;
81724
82024
  }
@@ -81727,6 +82027,7 @@ function readDoc(docPath) {
81727
82027
  }
81728
82028
 
81729
82029
  // src/web/routes/meta.ts
82030
+ init_compatibility();
81730
82031
  function resolveGitCommitShort() {
81731
82032
  const env4 = process.env.CEREFOX_GIT_COMMIT;
81732
82033
  if (env4)
@@ -81746,7 +82047,7 @@ var VERSION_INFO = {
81746
82047
  git_commit_short: resolveGitCommitShort(),
81747
82048
  build_date: process.env.CEREFOX_BUILD_DATE ?? null
81748
82049
  };
81749
- var SCHEMA_VERSION_RE2 = /^--\s*@version:\s*(\S+)/m;
82050
+ var SCHEMA_VERSION_RE3 = /^--\s*@version:\s*(\S+)/m;
81750
82051
  function registerMetaRoutes(app, ctx) {
81751
82052
  app.get("/api/v1/version", (c2) => c2.json(VERSION_INFO));
81752
82053
  app.get("/api/v1/docs", (c2) => c2.json(listBundledDocs2()));
@@ -81763,18 +82064,18 @@ function registerMetaRoutes(app, ctx) {
81763
82064
  app.get("/api/v1/schema-version", async (c2) => {
81764
82065
  let bundled = null;
81765
82066
  try {
81766
- const { readFileSync: readFileSync15, existsSync: existsSync15 } = await import("node:fs");
82067
+ const { readFileSync: readFileSync16, existsSync: existsSync17 } = await import("node:fs");
81767
82068
  const { fileURLToPath: fileURLToPath4 } = await import("node:url");
81768
- const { dirname: dirname7, join: join14 } = await import("node:path");
82069
+ const { dirname: dirname7, join: join15 } = await import("node:path");
81769
82070
  const here = dirname7(fileURLToPath4(import.meta.url));
81770
82071
  const candidates = [
81771
- join14(here, "..", "..", "..", "db", "schema.sql"),
81772
- join14(here, "..", "..", "..", "..", "..", "src", "cerefox", "db", "schema.sql")
82072
+ join15(here, "..", "..", "..", "db", "schema.sql"),
82073
+ join15(here, "..", "..", "..", "..", "..", "src", "cerefox", "db", "schema.sql")
81773
82074
  ];
81774
82075
  for (const path of candidates) {
81775
- if (existsSync15(path)) {
81776
- const sql = readFileSync15(path, "utf8");
81777
- const match2 = sql.match(SCHEMA_VERSION_RE2);
82076
+ if (existsSync17(path)) {
82077
+ const sql = readFileSync16(path, "utf8");
82078
+ const match2 = sql.match(SCHEMA_VERSION_RE3);
81778
82079
  bundled = match2 ? match2[1] : null;
81779
82080
  break;
81780
82081
  }
@@ -81869,17 +82170,17 @@ function registerPostgrestProxy(app) {
81869
82170
 
81870
82171
  // src/web/routes/preferences.ts
81871
82172
  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";
82173
+ import { existsSync as existsSync17, mkdirSync as mkdirSync5, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "node:fs";
82174
+ import { join as join15 } from "node:path";
81874
82175
  function isTheme(v) {
81875
82176
  return v === "auto" || v === "light" || v === "dark";
81876
82177
  }
81877
82178
  function prefsFile() {
81878
- return join14(userStateDir(), "web-prefs.json");
82179
+ return join15(userStateDir(), "web-prefs.json");
81879
82180
  }
81880
82181
  function readPrefs() {
81881
82182
  try {
81882
- const raw2 = JSON.parse(readFileSync15(prefsFile(), "utf8"));
82183
+ const raw2 = JSON.parse(readFileSync16(prefsFile(), "utf8"));
81883
82184
  if (isTheme(raw2.theme))
81884
82185
  return { theme: raw2.theme };
81885
82186
  } catch {}
@@ -81895,8 +82196,8 @@ function registerPreferencesRoutes(app) {
81895
82196
  const next = { ...readPrefs(), theme: body.theme };
81896
82197
  try {
81897
82198
  const dir = userStateDir();
81898
- if (!existsSync15(dir))
81899
- mkdirSync4(dir, { recursive: true });
82199
+ if (!existsSync17(dir))
82200
+ mkdirSync5(dir, { recursive: true });
81900
82201
  writeFileSync6(prefsFile(), `${JSON.stringify(next, null, 2)}
81901
82202
  `);
81902
82203
  } catch (err) {
@@ -81967,21 +82268,21 @@ function registerProjectsRoutes(app, ctx) {
81967
82268
  }
81968
82269
 
81969
82270
  // 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";
82271
+ import { existsSync as existsSync18, statSync as statSync7 } from "node:fs";
82272
+ import { dirname as dirname7, join as join16 } from "node:path";
81972
82273
  import { fileURLToPath as fileURLToPath4 } from "node:url";
81973
82274
  function moduleDir3() {
81974
82275
  return dirname7(fileURLToPath4(import.meta.url));
81975
82276
  }
81976
82277
  function isUsableSpaDir(dir) {
81977
- return existsSync16(dir) && statSync7(dir).isDirectory() && existsSync16(join15(dir, "index.html"));
82278
+ return existsSync18(dir) && statSync7(dir).isDirectory() && existsSync18(join16(dir, "index.html"));
81978
82279
  }
81979
82280
  function resolveSpaDist() {
81980
82281
  const here = moduleDir3();
81981
82282
  const candidates = [
81982
- join15(here, "..", "frontend"),
81983
- join15(here, "..", "..", "..", "..", "frontend", "dist"),
81984
- join15(here, "..", "..", "dist", "frontend")
82283
+ join16(here, "..", "frontend"),
82284
+ join16(here, "..", "..", "..", "..", "frontend", "dist"),
82285
+ join16(here, "..", "..", "dist", "frontend")
81985
82286
  ];
81986
82287
  for (const c2 of candidates) {
81987
82288
  if (isUsableSpaDir(c2))
@@ -81992,11 +82293,11 @@ function resolveSpaDist() {
81992
82293
  function resolveStaticDir() {
81993
82294
  const here = moduleDir3();
81994
82295
  const candidates = [
81995
- join15(here, "..", "static"),
81996
- join15(here, "..", "..", "..", "..", "web", "static")
82296
+ join16(here, "..", "static"),
82297
+ join16(here, "..", "..", "..", "..", "web", "static")
81997
82298
  ];
81998
82299
  for (const c2 of candidates) {
81999
- if (existsSync16(c2) && statSync7(c2).isDirectory())
82300
+ if (existsSync18(c2) && statSync7(c2).isDirectory())
82000
82301
  return c2;
82001
82302
  }
82002
82303
  return null;
@@ -82041,6 +82342,7 @@ var ROOT_REDIRECT_HTML = `<!DOCTYPE html>
82041
82342
  init_meta();
82042
82343
  init_cli_core();
82043
82344
  init_config();
82345
+ init_compatibility();
82044
82346
  function buildApp(ctx = buildWebContext()) {
82045
82347
  const app = new Hono2;
82046
82348
  if (true) {
@@ -82082,9 +82384,9 @@ function buildApp(ctx = buildWebContext()) {
82082
82384
  root: spaDist,
82083
82385
  rewriteRequestPath: (path) => path.replace(/^\/app/, "") || "/"
82084
82386
  }));
82085
- const indexPath = join16(spaDist, "index.html");
82086
- if (existsSync17(indexPath)) {
82087
- const indexHtml = readFileSync16(indexPath, "utf8");
82387
+ const indexPath = join17(spaDist, "index.html");
82388
+ if (existsSync19(indexPath)) {
82389
+ const indexHtml = readFileSync17(indexPath, "utf8");
82088
82390
  app.get("/app/*", (c2) => c2.html(indexHtml));
82089
82391
  }
82090
82392
  }
@@ -82142,28 +82444,28 @@ async function buildWebServer(options = {}) {
82142
82444
  // src/web/daemon.ts
82143
82445
  import { spawn } from "node:child_process";
82144
82446
  import {
82145
- existsSync as existsSync18,
82146
- mkdirSync as mkdirSync5,
82447
+ existsSync as existsSync20,
82448
+ mkdirSync as mkdirSync6,
82147
82449
  openSync,
82148
- readFileSync as readFileSync17,
82450
+ readFileSync as readFileSync18,
82149
82451
  rmSync,
82150
82452
  writeFileSync as writeFileSync7
82151
82453
  } 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");
82454
+ import { homedir as homedir9 } from "node:os";
82455
+ import { join as join18 } from "node:path";
82456
+ var STATE_DIR = join18(homedir9(), ".cerefox");
82457
+ var PID_FILE = join18(STATE_DIR, "web.pid");
82458
+ var LOG_FILE = join18(STATE_DIR, "web.log");
82157
82459
  var daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
82158
82460
  function ensureStateDir() {
82159
- if (!existsSync18(STATE_DIR))
82160
- mkdirSync5(STATE_DIR, { recursive: true });
82461
+ if (!existsSync20(STATE_DIR))
82462
+ mkdirSync6(STATE_DIR, { recursive: true });
82161
82463
  }
82162
82464
  function readPidFile() {
82163
- if (!existsSync18(PID_FILE))
82465
+ if (!existsSync20(PID_FILE))
82164
82466
  return null;
82165
82467
  try {
82166
- const parsed = JSON.parse(readFileSync17(PID_FILE, "utf8"));
82468
+ const parsed = JSON.parse(readFileSync18(PID_FILE, "utf8"));
82167
82469
  if (typeof parsed.pid !== "number")
82168
82470
  return null;
82169
82471
  return {
@@ -82474,6 +82776,7 @@ Learn more:
82474
82776
  registerConfigureAgent(program2);
82475
82777
  registerSelfUpdate(program2);
82476
82778
  registerMcp(program2);
82779
+ registerEmbedderWarmup(program2);
82477
82780
  registerWeb(program2);
82478
82781
  registerCompletion(program2);
82479
82782
  registerToken(program2);
@@ -82527,7 +82830,7 @@ Learn more:
82527
82830
 
82528
82831
  // src/bin/cerefox.ts
82529
82832
  async function bareEntryPoint() {
82530
- const { existsSync: existsSync19 } = await import("node:fs");
82833
+ const { existsSync: existsSync21 } = await import("node:fs");
82531
82834
  const { resolveEnvFile: resolveEnvFile2 } = await Promise.resolve().then(() => (init_config(), exports_config));
82532
82835
  const { c: c2, println: println2 } = await Promise.resolve().then(() => (init_cli_core(), exports_cli_core));
82533
82836
  const { PKG_VERSION: PKG_VERSION2 } = await Promise.resolve().then(() => (init_meta(), exports_meta));
@@ -82536,7 +82839,7 @@ async function bareEntryPoint() {
82536
82839
  println2("");
82537
82840
  let configExists = false;
82538
82841
  try {
82539
- configExists = existsSync19(resolveEnvFile2());
82842
+ configExists = existsSync21(resolveEnvFile2());
82540
82843
  } catch {}
82541
82844
  if (!configExists) {
82542
82845
  println2(c2.yellow("⚠ No config detected."));