@cerefox/memory 1.0.0-beta.2 → 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.2";
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
@@ -68414,8 +68790,8 @@ async function action(options) {
68414
68790
  println(c.green("✓ ") + `Backup written: ${dest}`);
68415
68791
  println(c.dim(` documents: ${docs.length} · chunks: ${chunkTotal}`));
68416
68792
  if (options.git) {
68417
- println(c.yellow("⚠ ") + "--git commit option not yet ported to the TS CLI.");
68418
- println(c.dim(" For git-aware backups, run `uv run cerefox backup --git` from a clone."));
68793
+ println(c.yellow("⚠ ") + "--git commit is not implemented; the snapshot was written without a git checkpoint.");
68794
+ println(c.dim(" Commit the backup directory yourself if you want it version-controlled."));
68419
68795
  }
68420
68796
  }
68421
68797
  function registerBackup(program2) {
@@ -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.2";
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: "Python `uv run cerefox …` still reads this file during the v0.5–v0.7 migration window. " + "Safe to delete once Python support is removed (v0.9+)."
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;
@@ -75959,7 +76244,7 @@ async function promptForAnswers() {
75959
76244
  });
75960
76245
  println("");
75961
76246
  println(c.cyan("▶ Step 4/5 — Direct Postgres connection (optional for npm-installed users)"));
75962
- println(c.dim(" Only needed for `uv run python scripts/db_deploy.py` (schema deploy + migrations).\n" + ` npm-installed users without Python can skip — press Enter.
76247
+ println(c.dim(" Only needed for `cerefox server deploy` (schema deploy + migrations).\n" + ` You can skip this and set CEREFOX_DATABASE_URL later — press Enter.
75963
76248
  ` + " Format: postgresql://postgres.<project-ref>:<pw>@…:5432/postgres?sslmode=require"));
75964
76249
  const databaseUrl = await ask({
75965
76250
  type: "text",
@@ -76045,16 +76330,15 @@ function printMigrationMenu(cwdEnv, homeEnv) {
76045
76330
  println("");
76046
76331
  println(c.yellow(`⚠ Found existing config at ${cwdEnv}.`));
76047
76332
  println("");
76048
- println("This may be from a previous Python install. The TS CLI can use the");
76333
+ println("This may be from an earlier install. The CLI can use the");
76049
76334
  println("same .env — env-var names are identical, no rewrite needed.");
76050
76335
  println("");
76051
76336
  println(" " + c.bold("[c]") + " Copy to " + homeEnv + " " + c.green("(recommended)"));
76052
- println(c.dim(" • TS reads the new home from now on"));
76053
- println(c.dim(" • Python keeps reading " + cwdEnv + " (backward compat)"));
76337
+ println(c.dim(" • The CLI reads the new home from now on"));
76054
76338
  println(c.dim(" • Edit ~/.cerefox/.env going forward; the repo .env is legacy"));
76055
76339
  println("");
76056
76340
  println(" " + c.bold("[u]") + " Use " + cwdEnv + " as-is, skip writing anything");
76057
- println(c.dim(" • Both TS and Python keep reading the existing file"));
76341
+ println(c.dim(" • The CLI keeps reading the existing file"));
76058
76342
  println(c.dim(" • Defer the migration"));
76059
76343
  println("");
76060
76344
  println(" " + c.bold("[f]") + " Fresh start — interactive prompts, write to " + homeEnv);
@@ -76166,7 +76450,7 @@ async function postWriteLifecycle(envPath, options) {
76166
76450
  println(c.dim(` Config in effect: ${envPath}`));
76167
76451
  }
76168
76452
  function writeAnswersTo(target, answers) {
76169
- mkdirSync3(dirname4(target), { recursive: true });
76453
+ mkdirSync4(dirname4(target), { recursive: true });
76170
76454
  writeFileSync4(target, buildEnvFile(answers), "utf8");
76171
76455
  if (process.platform !== "win32") {
76172
76456
  try {
@@ -76177,12 +76461,12 @@ function writeAnswersTo(target, answers) {
76177
76461
  }
76178
76462
  }
76179
76463
  async function action21(options) {
76180
- const homeEnv = join10(homedir6(), USER_STATE_DIR_NAME, ".env");
76181
- const cwdEnv = join10(process.cwd(), ".env");
76464
+ const homeEnv = join11(homedir7(), USER_STATE_DIR_NAME, ".env");
76465
+ const cwdEnv = join11(process.cwd(), ".env");
76182
76466
  const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
76183
76467
  if (explicitDir) {
76184
76468
  const target2 = resolveEnvFile();
76185
- if (existsSync10(target2) && !options.force) {
76469
+ if (existsSync11(target2) && !options.force) {
76186
76470
  println(c.yellow(`⚠ Config already exists at ${target2}.`));
76187
76471
  const ok2 = await confirm("Overwrite?", true);
76188
76472
  if (!ok2) {
@@ -76204,7 +76488,7 @@ async function action21(options) {
76204
76488
  await postWriteLifecycle(target2, options);
76205
76489
  return;
76206
76490
  }
76207
- if (existsSync10(homeEnv) && !options.force) {
76491
+ if (existsSync11(homeEnv) && !options.force) {
76208
76492
  println(c.yellow(`⚠ Config already exists at ${homeEnv}.`));
76209
76493
  const ok2 = await confirm("Overwrite?", true);
76210
76494
  if (!ok2) {
@@ -76225,12 +76509,12 @@ async function action21(options) {
76225
76509
  await postWriteLifecycle(homeEnv, options);
76226
76510
  return;
76227
76511
  }
76228
- if (existsSync10(cwdEnv) && !options.force && !options.config) {
76512
+ if (existsSync11(cwdEnv) && !options.force && !options.config) {
76229
76513
  printMigrationMenu(cwdEnv, homeEnv);
76230
76514
  const ch = await promptMigrationChoice();
76231
76515
  println("");
76232
76516
  if (ch === "c") {
76233
- mkdirSync3(dirname4(homeEnv), { recursive: true });
76517
+ mkdirSync4(dirname4(homeEnv), { recursive: true });
76234
76518
  copyFileSync2(cwdEnv, homeEnv);
76235
76519
  if (process.platform !== "win32") {
76236
76520
  try {
@@ -76240,7 +76524,7 @@ async function action21(options) {
76240
76524
  }
76241
76525
  }
76242
76526
  println(c.green(`✓ Copied ${cwdEnv} → ${homeEnv}`));
76243
- println(c.dim(` Repo file unchanged — Python still reads it during migration.`));
76527
+ println(c.dim(` Repo file left unchanged — safe to delete once the new location works.`));
76244
76528
  println("");
76245
76529
  const answers2 = answersFromEnvFile(homeEnv);
76246
76530
  println(c.bold("Validating credentials…"));
@@ -76479,6 +76763,23 @@ function registerMcp(program2) {
76479
76763
  });
76480
76764
  }
76481
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
+
76482
76783
  // src/cli/commands/metadata-search.ts
76483
76784
  init_cli_core();
76484
76785
  init_client();
@@ -76638,35 +76939,35 @@ function registerReindex(program2) {
76638
76939
  // src/cli/commands/restore.ts
76639
76940
  init_cli_core();
76640
76941
  init_client();
76641
- import { existsSync as existsSync11, readFileSync as readFileSync12, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
76642
- import { homedir as homedir7 } from "node:os";
76643
- 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";
76644
76945
  function expandHome2(path) {
76645
76946
  if (path === "~")
76646
- return homedir7();
76947
+ return homedir8();
76647
76948
  if (path.startsWith("~/"))
76648
- return join11(homedir7(), path.slice(2));
76949
+ return join12(homedir8(), path.slice(2));
76649
76950
  return path;
76650
76951
  }
76651
76952
  function resolveBackupFile(target) {
76652
76953
  const path = resolve4(expandHome2(target));
76653
- if (!existsSync11(path)) {
76954
+ if (!existsSync13(path)) {
76654
76955
  throw userError(`Backup path not found: ${target}`);
76655
76956
  }
76656
76957
  const stat = statSync4(path);
76657
76958
  if (stat.isFile())
76658
76959
  return path;
76659
- 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);
76660
76961
  if (candidates.length === 0) {
76661
76962
  throw userError(`No cerefox-*.json files in ${path}`);
76662
76963
  }
76663
- return join11(path, candidates[0].name);
76964
+ return join12(path, candidates[0].name);
76664
76965
  }
76665
76966
  async function action28(target, options) {
76666
76967
  const file = resolveBackupFile(target);
76667
76968
  let payload;
76668
76969
  try {
76669
- payload = JSON.parse(readFileSync12(file, "utf8"));
76970
+ payload = JSON.parse(readFileSync13(file, "utf8"));
76670
76971
  } catch (err) {
76671
76972
  throw userError(`Could not parse backup file ${file}: ${err instanceof Error ? err.message : String(err)}`);
76672
76973
  }
@@ -76731,7 +77032,7 @@ init_config();
76731
77032
  async function embedQuery(query) {
76732
77033
  const settings = loadSettings();
76733
77034
  const apiKey = settings.openaiApiKey;
76734
- if (!apiKey) {
77035
+ if (!apiKey && resolveEmbedderKind() !== "local") {
76735
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.");
76736
77037
  }
76737
77038
  try {
@@ -77046,7 +77347,7 @@ import { randomBytes } from "node:crypto";
77046
77347
  import { spawnSync as spawnSync7 } from "node:child_process";
77047
77348
 
77048
77349
  // src/cli/util/env-file.ts
77049
- 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";
77050
77351
  import { dirname as dirname5 } from "node:path";
77051
77352
  import { spawnSync as spawnSync6 } from "node:child_process";
77052
77353
  function escapeRegExp(s) {
@@ -77056,12 +77357,12 @@ function upsertEnvVar(path, key, value, opts = {}) {
77056
77357
  const line = `${key}=${value}`;
77057
77358
  const header = opts.comment ? `# ${opts.comment}
77058
77359
  ` : "";
77059
- if (!existsSync12(path)) {
77360
+ if (!existsSync14(path)) {
77060
77361
  writeFileSync5(path, `${header}${line}
77061
77362
  `, { mode: 384 });
77062
77363
  return { path, action: "created" };
77063
77364
  }
77064
- const original = readFileSync13(path, "utf8");
77365
+ const original = readFileSync14(path, "utf8");
77065
77366
  let backupPath;
77066
77367
  if (!opts.noBackup) {
77067
77368
  backupPath = `${path}.pre-cerefox.bak`;
@@ -77086,9 +77387,9 @@ ${header}${line}
77086
77387
  return { path, action: action32, backupPath };
77087
77388
  }
77088
77389
  function readEnvVar(path, key) {
77089
- if (!existsSync12(path))
77390
+ if (!existsSync14(path))
77090
77391
  return null;
77091
- 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"));
77092
77393
  return m ? m[1].trim() : null;
77093
77394
  }
77094
77395
  function envGitignoreWarning(path) {
@@ -78556,8 +78857,8 @@ var _baseMimes = {
78556
78857
  var baseMimes = _baseMimes;
78557
78858
 
78558
78859
  // ../../node_modules/.bun/@hono+node-server@2.0.8+68404363cdc13251/node_modules/@hono/node-server/dist/serve-static.mjs
78559
- import { createReadStream, existsSync as existsSync13, statSync as statSync5 } from "node:fs";
78560
- 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";
78561
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;
78562
78863
  var ENCODINGS = {
78563
78864
  br: ".br",
@@ -78589,7 +78890,7 @@ var tryDecodeURI = (str) => tryDecode(str, decodeURI);
78589
78890
  var serveStatic = (options = { root: "" }) => {
78590
78891
  const root = options.root || "";
78591
78892
  const optionPath = options.path;
78592
- if (root !== "" && !existsSync13(root))
78893
+ if (root !== "" && !existsSync15(root))
78593
78894
  console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
78594
78895
  return async (c2, next) => {
78595
78896
  if (c2.finalized)
@@ -78606,11 +78907,11 @@ var serveStatic = (options = { root: "" }) => {
78606
78907
  await options.onNotFound?.(c2.req.path, c2);
78607
78908
  return next();
78608
78909
  }
78609
- 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);
78610
78911
  let stats = getStats(path);
78611
78912
  if (stats && stats.isDirectory()) {
78612
78913
  const indexFile = options.index ?? "index.html";
78613
- path = join12(path, indexFile);
78914
+ path = join13(path, indexFile);
78614
78915
  stats = getStats(path);
78615
78916
  }
78616
78917
  if (!stats) {
@@ -78667,9 +78968,9 @@ var serveStatic = (options = { root: "" }) => {
78667
78968
  };
78668
78969
 
78669
78970
  // src/web/server.ts
78670
- import { existsSync as existsSync17 } from "node:fs";
78671
- import { readFileSync as readFileSync16 } from "node:fs";
78672
- 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";
78673
78974
 
78674
78975
  // ../../node_modules/.bun/hono@4.12.29/node_modules/hono/dist/compose.js
78675
78976
  var compose = (middleware, onError, onNotFound) => {
@@ -80698,7 +80999,7 @@ async function runSearch(ctx, opts) {
80698
80999
  throw error4;
80699
81000
  return (data2 ?? []).map(projectChunkResult);
80700
81001
  }
80701
- if (!ctx.openAiApiKey) {
81002
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
80702
81003
  throw new HttpError(503, "Embedder not available");
80703
81004
  }
80704
81005
  const embedding = await getEmbedding(query, ctx.openAiApiKey);
@@ -81239,7 +81540,6 @@ function registerDocumentReadRoutes(app, ctx) {
81239
81540
  return c2.json({ exists: false });
81240
81541
  });
81241
81542
  }
81242
-
81243
81543
  // src/web/routes/documents-write.ts
81244
81544
  async function createAuditEntry(ctx, args) {
81245
81545
  try {
@@ -81290,7 +81590,7 @@ function registerDocumentWriteRoutes(app, ctx) {
81290
81590
  const proposedHash = content.trim() ? contentHash(content) : null;
81291
81591
  const contentChanged = proposedHash !== null && currentHash !== null && proposedHash !== currentHash;
81292
81592
  if (contentChanged) {
81293
- if (!ctx.openAiApiKey) {
81593
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81294
81594
  return c2.json({
81295
81595
  success: false,
81296
81596
  error: "Embedder not available — set OPENAI_API_KEY in your config"
@@ -81446,14 +81746,13 @@ function registerDocumentWriteRoutes(app, ctx) {
81446
81746
  return c2.json({ archived });
81447
81747
  });
81448
81748
  }
81449
-
81450
81749
  // src/web/routes/ingest.ts
81451
81750
  function notReady(error3) {
81452
81751
  return { success: false, error: error3 };
81453
81752
  }
81454
81753
  function registerIngestRoutes(app, ctx) {
81455
81754
  app.post("/api/v1/ingest", async (c2) => {
81456
- if (!ctx.openAiApiKey) {
81755
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81457
81756
  return c2.json(notReady("Embedder not available"), 503);
81458
81757
  }
81459
81758
  let body;
@@ -81501,7 +81800,7 @@ function registerIngestRoutes(app, ctx) {
81501
81800
  }
81502
81801
  });
81503
81802
  app.post("/api/v1/ingest/file", async (c2) => {
81504
- if (!ctx.openAiApiKey) {
81803
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81505
81804
  return c2.json(notReady("Embedder not available"), 503);
81506
81805
  }
81507
81806
  let form;
@@ -81563,7 +81862,7 @@ function registerIngestRoutes(app, ctx) {
81563
81862
  }
81564
81863
  });
81565
81864
  app.post("/api/v1/documents/:document_id/upload", async (c2) => {
81566
- if (!ctx.openAiApiKey) {
81865
+ if (!ctx.openAiApiKey && resolveEmbedderKind() !== "local") {
81567
81866
  return c2.json(notReady("Embedder not available"), 503);
81568
81867
  }
81569
81868
  const documentId = c2.req.param("document_id");
@@ -81618,12 +81917,12 @@ import { execFileSync } from "node:child_process";
81618
81917
 
81619
81918
  // src/web/docs.ts
81620
81919
  import {
81621
- existsSync as existsSync14,
81622
- readFileSync as readFileSync14,
81920
+ existsSync as existsSync16,
81921
+ readFileSync as readFileSync15,
81623
81922
  readdirSync as readdirSync6,
81624
81923
  statSync as statSync6
81625
81924
  } from "node:fs";
81626
- 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";
81627
81926
  import { fileURLToPath as fileURLToPath3 } from "node:url";
81628
81927
  var TOP_LEVEL_DOCS = [
81629
81928
  { filename: "README.md", path: "README.md", category: "readme" },
@@ -81644,32 +81943,32 @@ function moduleDir2() {
81644
81943
  function resolveDocsRoots() {
81645
81944
  const here = moduleDir2();
81646
81945
  const pkgRootCandidates = [
81647
- join13(here, "..", ".."),
81648
- join13(here, "..", "..", "..", "..")
81946
+ join14(here, "..", ".."),
81947
+ join14(here, "..", "..", "..", "..")
81649
81948
  ];
81650
81949
  let pkgGuides = null;
81651
81950
  let pkgTopLevel = null;
81652
81951
  for (const pkg of pkgRootCandidates) {
81653
- const guides = join13(pkg, "docs", "guides");
81654
- if (existsSync14(guides) && statSync6(guides).isDirectory()) {
81952
+ const guides = join14(pkg, "docs", "guides");
81953
+ if (existsSync16(guides) && statSync6(guides).isDirectory()) {
81655
81954
  pkgGuides = guides;
81656
81955
  pkgTopLevel = pkg;
81657
81956
  break;
81658
81957
  }
81659
81958
  }
81660
- const repoCandidate = join13(here, "..", "..", "..", "..");
81661
- const repoGuides = join13(repoCandidate, "docs", "guides");
81959
+ const repoCandidate = join14(here, "..", "..", "..", "..");
81960
+ const repoGuides = join14(repoCandidate, "docs", "guides");
81662
81961
  const repoTopLevel = repoCandidate;
81663
81962
  return {
81664
81963
  pkgGuides,
81665
81964
  pkgTopLevel,
81666
- repoGuides: existsSync14(repoGuides) ? repoGuides : null,
81667
- repoTopLevel: existsSync14(join13(repoTopLevel, "README.md")) ? repoTopLevel : null
81965
+ repoGuides: existsSync16(repoGuides) ? repoGuides : null,
81966
+ repoTopLevel: existsSync16(join14(repoTopLevel, "README.md")) ? repoTopLevel : null
81668
81967
  };
81669
81968
  }
81670
81969
  function readH1(filePath) {
81671
81970
  try {
81672
- const content = readFileSync14(filePath, "utf8");
81971
+ const content = readFileSync15(filePath, "utf8");
81673
81972
  const match2 = content.match(/^#\s+(.+?)\s*$/m);
81674
81973
  return match2 ? match2[1] : null;
81675
81974
  } catch {
@@ -81689,8 +81988,8 @@ function listBundledDocs2() {
81689
81988
  const topRoot = pkgTopLevel ?? repoTopLevel;
81690
81989
  if (topRoot) {
81691
81990
  for (const t of TOP_LEVEL_DOCS) {
81692
- const abs = join13(topRoot, t.filename);
81693
- if (existsSync14(abs)) {
81991
+ const abs = join14(topRoot, t.filename);
81992
+ if (existsSync16(abs)) {
81694
81993
  entries.push(entryForFile(abs, t.path, t.category));
81695
81994
  }
81696
81995
  }
@@ -81699,7 +81998,7 @@ function listBundledDocs2() {
81699
81998
  if (guidesRoot) {
81700
81999
  const names = readdirSync6(guidesRoot).filter((n) => n.endsWith(".md")).sort();
81701
82000
  for (const name of names) {
81702
- const abs = join13(guidesRoot, name);
82001
+ const abs = join14(guidesRoot, name);
81703
82002
  entries.push(entryForFile(abs, `guides/${name}`, "guide"));
81704
82003
  }
81705
82004
  }
@@ -81717,9 +82016,9 @@ function readDoc(docPath) {
81717
82016
  if (!candidate.startsWith(rootResolved + "/") && candidate !== rootResolved) {
81718
82017
  return null;
81719
82018
  }
81720
- if (existsSync14(candidate) && statSync6(candidate).isFile()) {
82019
+ if (existsSync16(candidate) && statSync6(candidate).isFile()) {
81721
82020
  try {
81722
- return readFileSync14(candidate, "utf8");
82021
+ return readFileSync15(candidate, "utf8");
81723
82022
  } catch {
81724
82023
  return null;
81725
82024
  }
@@ -81728,6 +82027,7 @@ function readDoc(docPath) {
81728
82027
  }
81729
82028
 
81730
82029
  // src/web/routes/meta.ts
82030
+ init_compatibility();
81731
82031
  function resolveGitCommitShort() {
81732
82032
  const env4 = process.env.CEREFOX_GIT_COMMIT;
81733
82033
  if (env4)
@@ -81747,7 +82047,7 @@ var VERSION_INFO = {
81747
82047
  git_commit_short: resolveGitCommitShort(),
81748
82048
  build_date: process.env.CEREFOX_BUILD_DATE ?? null
81749
82049
  };
81750
- var SCHEMA_VERSION_RE2 = /^--\s*@version:\s*(\S+)/m;
82050
+ var SCHEMA_VERSION_RE3 = /^--\s*@version:\s*(\S+)/m;
81751
82051
  function registerMetaRoutes(app, ctx) {
81752
82052
  app.get("/api/v1/version", (c2) => c2.json(VERSION_INFO));
81753
82053
  app.get("/api/v1/docs", (c2) => c2.json(listBundledDocs2()));
@@ -81764,18 +82064,18 @@ function registerMetaRoutes(app, ctx) {
81764
82064
  app.get("/api/v1/schema-version", async (c2) => {
81765
82065
  let bundled = null;
81766
82066
  try {
81767
- const { readFileSync: readFileSync15, existsSync: existsSync15 } = await import("node:fs");
82067
+ const { readFileSync: readFileSync16, existsSync: existsSync17 } = await import("node:fs");
81768
82068
  const { fileURLToPath: fileURLToPath4 } = await import("node:url");
81769
- const { dirname: dirname7, join: join14 } = await import("node:path");
82069
+ const { dirname: dirname7, join: join15 } = await import("node:path");
81770
82070
  const here = dirname7(fileURLToPath4(import.meta.url));
81771
82071
  const candidates = [
81772
- join14(here, "..", "..", "..", "db", "schema.sql"),
81773
- join14(here, "..", "..", "..", "..", "..", "src", "cerefox", "db", "schema.sql")
82072
+ join15(here, "..", "..", "..", "db", "schema.sql"),
82073
+ join15(here, "..", "..", "..", "..", "..", "src", "cerefox", "db", "schema.sql")
81774
82074
  ];
81775
82075
  for (const path of candidates) {
81776
- if (existsSync15(path)) {
81777
- const sql = readFileSync15(path, "utf8");
81778
- 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);
81779
82079
  bundled = match2 ? match2[1] : null;
81780
82080
  break;
81781
82081
  }
@@ -81870,17 +82170,17 @@ function registerPostgrestProxy(app) {
81870
82170
 
81871
82171
  // src/web/routes/preferences.ts
81872
82172
  init_config();
81873
- import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "node:fs";
81874
- 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";
81875
82175
  function isTheme(v) {
81876
82176
  return v === "auto" || v === "light" || v === "dark";
81877
82177
  }
81878
82178
  function prefsFile() {
81879
- return join14(userStateDir(), "web-prefs.json");
82179
+ return join15(userStateDir(), "web-prefs.json");
81880
82180
  }
81881
82181
  function readPrefs() {
81882
82182
  try {
81883
- const raw2 = JSON.parse(readFileSync15(prefsFile(), "utf8"));
82183
+ const raw2 = JSON.parse(readFileSync16(prefsFile(), "utf8"));
81884
82184
  if (isTheme(raw2.theme))
81885
82185
  return { theme: raw2.theme };
81886
82186
  } catch {}
@@ -81896,8 +82196,8 @@ function registerPreferencesRoutes(app) {
81896
82196
  const next = { ...readPrefs(), theme: body.theme };
81897
82197
  try {
81898
82198
  const dir = userStateDir();
81899
- if (!existsSync15(dir))
81900
- mkdirSync4(dir, { recursive: true });
82199
+ if (!existsSync17(dir))
82200
+ mkdirSync5(dir, { recursive: true });
81901
82201
  writeFileSync6(prefsFile(), `${JSON.stringify(next, null, 2)}
81902
82202
  `);
81903
82203
  } catch (err) {
@@ -81968,21 +82268,21 @@ function registerProjectsRoutes(app, ctx) {
81968
82268
  }
81969
82269
 
81970
82270
  // src/web/static.ts
81971
- import { existsSync as existsSync16, statSync as statSync7 } from "node:fs";
81972
- 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";
81973
82273
  import { fileURLToPath as fileURLToPath4 } from "node:url";
81974
82274
  function moduleDir3() {
81975
82275
  return dirname7(fileURLToPath4(import.meta.url));
81976
82276
  }
81977
82277
  function isUsableSpaDir(dir) {
81978
- return existsSync16(dir) && statSync7(dir).isDirectory() && existsSync16(join15(dir, "index.html"));
82278
+ return existsSync18(dir) && statSync7(dir).isDirectory() && existsSync18(join16(dir, "index.html"));
81979
82279
  }
81980
82280
  function resolveSpaDist() {
81981
82281
  const here = moduleDir3();
81982
82282
  const candidates = [
81983
- join15(here, "..", "frontend"),
81984
- join15(here, "..", "..", "..", "..", "frontend", "dist"),
81985
- join15(here, "..", "..", "dist", "frontend")
82283
+ join16(here, "..", "frontend"),
82284
+ join16(here, "..", "..", "..", "..", "frontend", "dist"),
82285
+ join16(here, "..", "..", "dist", "frontend")
81986
82286
  ];
81987
82287
  for (const c2 of candidates) {
81988
82288
  if (isUsableSpaDir(c2))
@@ -81993,11 +82293,11 @@ function resolveSpaDist() {
81993
82293
  function resolveStaticDir() {
81994
82294
  const here = moduleDir3();
81995
82295
  const candidates = [
81996
- join15(here, "..", "static"),
81997
- join15(here, "..", "..", "..", "..", "web", "static")
82296
+ join16(here, "..", "static"),
82297
+ join16(here, "..", "..", "..", "..", "web", "static")
81998
82298
  ];
81999
82299
  for (const c2 of candidates) {
82000
- if (existsSync16(c2) && statSync7(c2).isDirectory())
82300
+ if (existsSync18(c2) && statSync7(c2).isDirectory())
82001
82301
  return c2;
82002
82302
  }
82003
82303
  return null;
@@ -82042,6 +82342,7 @@ var ROOT_REDIRECT_HTML = `<!DOCTYPE html>
82042
82342
  init_meta();
82043
82343
  init_cli_core();
82044
82344
  init_config();
82345
+ init_compatibility();
82045
82346
  function buildApp(ctx = buildWebContext()) {
82046
82347
  const app = new Hono2;
82047
82348
  if (true) {
@@ -82083,9 +82384,9 @@ function buildApp(ctx = buildWebContext()) {
82083
82384
  root: spaDist,
82084
82385
  rewriteRequestPath: (path) => path.replace(/^\/app/, "") || "/"
82085
82386
  }));
82086
- const indexPath = join16(spaDist, "index.html");
82087
- if (existsSync17(indexPath)) {
82088
- const indexHtml = readFileSync16(indexPath, "utf8");
82387
+ const indexPath = join17(spaDist, "index.html");
82388
+ if (existsSync19(indexPath)) {
82389
+ const indexHtml = readFileSync17(indexPath, "utf8");
82089
82390
  app.get("/app/*", (c2) => c2.html(indexHtml));
82090
82391
  }
82091
82392
  }
@@ -82143,28 +82444,28 @@ async function buildWebServer(options = {}) {
82143
82444
  // src/web/daemon.ts
82144
82445
  import { spawn } from "node:child_process";
82145
82446
  import {
82146
- existsSync as existsSync18,
82147
- mkdirSync as mkdirSync5,
82447
+ existsSync as existsSync20,
82448
+ mkdirSync as mkdirSync6,
82148
82449
  openSync,
82149
- readFileSync as readFileSync17,
82450
+ readFileSync as readFileSync18,
82150
82451
  rmSync,
82151
82452
  writeFileSync as writeFileSync7
82152
82453
  } from "node:fs";
82153
- import { homedir as homedir8 } from "node:os";
82154
- import { join as join17 } from "node:path";
82155
- var STATE_DIR = join17(homedir8(), ".cerefox");
82156
- var PID_FILE = join17(STATE_DIR, "web.pid");
82157
- 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");
82158
82459
  var daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
82159
82460
  function ensureStateDir() {
82160
- if (!existsSync18(STATE_DIR))
82161
- mkdirSync5(STATE_DIR, { recursive: true });
82461
+ if (!existsSync20(STATE_DIR))
82462
+ mkdirSync6(STATE_DIR, { recursive: true });
82162
82463
  }
82163
82464
  function readPidFile() {
82164
- if (!existsSync18(PID_FILE))
82465
+ if (!existsSync20(PID_FILE))
82165
82466
  return null;
82166
82467
  try {
82167
- const parsed = JSON.parse(readFileSync17(PID_FILE, "utf8"));
82468
+ const parsed = JSON.parse(readFileSync18(PID_FILE, "utf8"));
82168
82469
  if (typeof parsed.pid !== "number")
82169
82470
  return null;
82170
82471
  return {
@@ -82475,6 +82776,7 @@ Learn more:
82475
82776
  registerConfigureAgent(program2);
82476
82777
  registerSelfUpdate(program2);
82477
82778
  registerMcp(program2);
82779
+ registerEmbedderWarmup(program2);
82478
82780
  registerWeb(program2);
82479
82781
  registerCompletion(program2);
82480
82782
  registerToken(program2);
@@ -82528,7 +82830,7 @@ Learn more:
82528
82830
 
82529
82831
  // src/bin/cerefox.ts
82530
82832
  async function bareEntryPoint() {
82531
- const { existsSync: existsSync19 } = await import("node:fs");
82833
+ const { existsSync: existsSync21 } = await import("node:fs");
82532
82834
  const { resolveEnvFile: resolveEnvFile2 } = await Promise.resolve().then(() => (init_config(), exports_config));
82533
82835
  const { c: c2, println: println2 } = await Promise.resolve().then(() => (init_cli_core(), exports_cli_core));
82534
82836
  const { PKG_VERSION: PKG_VERSION2 } = await Promise.resolve().then(() => (init_meta(), exports_meta));
@@ -82537,7 +82839,7 @@ async function bareEntryPoint() {
82537
82839
  println2("");
82538
82840
  let configExists = false;
82539
82841
  try {
82540
- configExists = existsSync19(resolveEnvFile2());
82842
+ configExists = existsSync21(resolveEnvFile2());
82541
82843
  } catch {}
82542
82844
  if (!configExists) {
82543
82845
  println2(c2.yellow("⚠ No config detected."));