@cerefox/memory 0.7.2 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/bin/cerefox.js +1357 -361
  2. package/dist/frontend/assets/{index-BzAPcCXA.js → index-CAp2_lFX.js} +2 -2
  3. package/dist/frontend/assets/index-CAp2_lFX.js.map +1 -0
  4. package/dist/frontend/index.html +1 -1
  5. package/dist/server-assets/_shared/ef-meta/index.ts +97 -0
  6. package/dist/server-assets/_shared/embeddings/index.ts +175 -0
  7. package/dist/server-assets/_shared/mcp-tools/_chunker.ts +187 -0
  8. package/dist/server-assets/_shared/mcp-tools/_projects.ts +121 -0
  9. package/dist/server-assets/_shared/mcp-tools/_utils.ts +73 -0
  10. package/dist/server-assets/_shared/mcp-tools/audit-log.ts +95 -0
  11. package/dist/server-assets/_shared/mcp-tools/get-document.ts +73 -0
  12. package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +26 -0
  13. package/dist/server-assets/_shared/mcp-tools/get-help.ts +90 -0
  14. package/dist/server-assets/_shared/mcp-tools/index.ts +67 -0
  15. package/dist/server-assets/_shared/mcp-tools/ingest.ts +315 -0
  16. package/dist/server-assets/_shared/mcp-tools/list-metadata-keys.ts +55 -0
  17. package/dist/server-assets/_shared/mcp-tools/list-projects.ts +59 -0
  18. package/dist/server-assets/_shared/mcp-tools/list-versions.ts +72 -0
  19. package/dist/server-assets/_shared/mcp-tools/metadata-search.ts +154 -0
  20. package/dist/server-assets/_shared/mcp-tools/search.ts +193 -0
  21. package/dist/server-assets/_shared/mcp-tools/set-document-projects.ts +163 -0
  22. package/dist/server-assets/_shared/mcp-tools/types.ts +92 -0
  23. package/dist/server-assets/db/migrations/0003_add_document_versions.sql +91 -0
  24. package/dist/server-assets/db/migrations/0004_add_audit_log_review_status_archived.sql +71 -0
  25. package/dist/server-assets/db/migrations/0005_metadata_search.sql +628 -0
  26. package/dist/server-assets/db/migrations/0006_usage_log.sql +255 -0
  27. package/dist/server-assets/db/migrations/0007_usage_log_requestor.sql +178 -0
  28. package/dist/server-assets/db/migrations/0008_soft_delete.sql +130 -0
  29. package/dist/server-assets/db/migrations/0009_audit_log_restore_operation.sql +20 -0
  30. package/dist/server-assets/db/migrations/0010_requestor_enforcement_config.sql +12 -0
  31. package/dist/server-assets/db/migrations/0011_title_boosting.sql +48 -0
  32. package/dist/server-assets/db/rpcs.sql +1723 -0
  33. package/dist/server-assets/db/schema.sql +380 -0
  34. package/dist/server-assets/supabase/functions/cerefox-get-audit-log/index.ts +117 -0
  35. package/dist/server-assets/supabase/functions/cerefox-get-document/index.ts +138 -0
  36. package/dist/server-assets/supabase/functions/cerefox-ingest/index.ts +819 -0
  37. package/dist/server-assets/supabase/functions/cerefox-list-projects/index.ts +96 -0
  38. package/dist/server-assets/supabase/functions/cerefox-list-versions/index.ts +113 -0
  39. package/dist/server-assets/supabase/functions/cerefox-mcp/index.ts +294 -0
  40. package/dist/server-assets/supabase/functions/cerefox-mcp/shared.ts +42 -0
  41. package/dist/server-assets/supabase/functions/cerefox-metadata/index.ts +99 -0
  42. package/dist/server-assets/supabase/functions/cerefox-metadata-search/index.ts +146 -0
  43. package/dist/server-assets/supabase/functions/cerefox-search/index.ts +382 -0
  44. package/docs/guides/connect-agents.md +78 -3
  45. package/docs/guides/migration-v0.5.md +50 -0
  46. package/docs/guides/quickstart.md +6 -2
  47. package/package.json +3 -2
  48. package/dist/frontend/assets/index-BzAPcCXA.js.map +0 -1
@@ -182,6 +182,10 @@ function printTable(rows, emptyMessage = "(no rows)") {
182
182
  println(line(headers.map((h) => String(row[h] ?? ""))));
183
183
  }
184
184
  }
185
+ function localTimestamp(d = new Date) {
186
+ const p = (n, w = 2) => String(n).padStart(w, "0");
187
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` + `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}.${p(d.getMilliseconds(), 3)}`;
188
+ }
185
189
  function eprintln(line = "") {
186
190
  process.stderr.write(line + `
187
191
  `);
@@ -5315,6 +5319,7 @@ __export(exports_cli_core, {
5315
5319
  parseFloat01: () => parseFloat01,
5316
5320
  ok: () => ok,
5317
5321
  notFound: () => notFound,
5322
+ localTimestamp: () => localTimestamp,
5318
5323
  info: () => info,
5319
5324
  errorln: () => errorln,
5320
5325
  eprintln: () => eprintln,
@@ -7179,7 +7184,7 @@ var exports_meta = {};
7179
7184
  __export(exports_meta, {
7180
7185
  PKG_VERSION: () => PKG_VERSION
7181
7186
  });
7182
- var PKG_VERSION = "0.7.2";
7187
+ var PKG_VERSION = "0.8.1";
7183
7188
  var init_meta = () => {};
7184
7189
 
7185
7190
  // ../../node_modules/.bun/tslib@2.8.1/node_modules/tslib/tslib.js
@@ -22585,6 +22590,7 @@ function loadSettings(opts = {}) {
22585
22590
  return {
22586
22591
  supabaseUrl: env2.CEREFOX_SUPABASE_URL ?? "",
22587
22592
  supabaseKey: env2.CEREFOX_SUPABASE_KEY ?? "",
22593
+ supabaseAnonKey: env2.CEREFOX_SUPABASE_ANON_KEY ?? "",
22588
22594
  databaseUrl: env2.CEREFOX_DATABASE_URL ?? "",
22589
22595
  openaiApiKey: env2.CEREFOX_OPENAI_API_KEY ?? env2.OPENAI_API_KEY ?? "",
22590
22596
  fireworksApiKey: env2.CEREFOX_FIREWORKS_API_KEY ?? ""
@@ -22631,99 +22637,6 @@ var init_client = __esm(() => {
22631
22637
  init_cli_core();
22632
22638
  });
22633
22639
 
22634
- // src/cli/util/bundled-docs.ts
22635
- import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
22636
- import { dirname as dirname2, join as join4, resolve as resolve2 } from "node:path";
22637
- import { fileURLToPath } from "node:url";
22638
- function findPackageRoot() {
22639
- let dir = dirname2(fileURLToPath(import.meta.url));
22640
- for (let i = 0;i < 10; i++) {
22641
- const pkgJson = join4(dir, "package.json");
22642
- if (existsSync4(pkgJson)) {
22643
- try {
22644
- const parsed = JSON.parse(readFileSync4(pkgJson, "utf8"));
22645
- if (parsed.name === "@cerefox/memory")
22646
- return dir;
22647
- } catch {}
22648
- }
22649
- const parent = dirname2(dir);
22650
- if (parent === dir)
22651
- break;
22652
- dir = parent;
22653
- }
22654
- return resolve2(dirname2(fileURLToPath(import.meta.url)), "..", "..", "..");
22655
- }
22656
- function bundledDocsDir() {
22657
- const inPackage = join4(PACKAGE_ROOT, "docs");
22658
- if (existsSync4(inPackage))
22659
- return inPackage;
22660
- return resolve2(PACKAGE_ROOT, "..", "..", "docs");
22661
- }
22662
- function agentGuidePath() {
22663
- const candidates = [
22664
- join4(PACKAGE_ROOT, "AGENT_GUIDE.md"),
22665
- resolve2(PACKAGE_ROOT, "..", "..", "AGENT_GUIDE.md")
22666
- ];
22667
- for (const c2 of candidates)
22668
- if (existsSync4(c2))
22669
- return c2;
22670
- return null;
22671
- }
22672
- function agentQuickReferencePath() {
22673
- const candidates = [
22674
- join4(PACKAGE_ROOT, "AGENT_QUICK_REFERENCE.md"),
22675
- resolve2(PACKAGE_ROOT, "..", "..", "AGENT_QUICK_REFERENCE.md")
22676
- ];
22677
- for (const c2 of candidates)
22678
- if (existsSync4(c2))
22679
- return c2;
22680
- return null;
22681
- }
22682
- function listBundledDocs() {
22683
- const entries = [];
22684
- const docsDir = bundledDocsDir();
22685
- const guidesDir = join4(docsDir, "guides");
22686
- if (existsSync4(guidesDir) && statSync(guidesDir).isDirectory()) {
22687
- for (const name of readdirSync(guidesDir)) {
22688
- if (!name.endsWith(".md"))
22689
- continue;
22690
- const full = join4(guidesDir, name);
22691
- entries.push({
22692
- topic: name.replace(/\.md$/, ""),
22693
- path: full,
22694
- size: statSync(full).size
22695
- });
22696
- }
22697
- }
22698
- const ag = agentGuidePath();
22699
- if (ag)
22700
- entries.push({ topic: "agent-guide", path: ag, size: statSync(ag).size });
22701
- const aqr = agentQuickReferencePath();
22702
- if (aqr) {
22703
- entries.push({
22704
- topic: "agent-quick-reference",
22705
- path: aqr,
22706
- size: statSync(aqr).size
22707
- });
22708
- }
22709
- entries.sort((a, b) => a.topic.localeCompare(b.topic));
22710
- return entries;
22711
- }
22712
- function readBundledDoc(topic) {
22713
- const entry = listBundledDocs().find((d) => d.topic === topic);
22714
- if (!entry)
22715
- return null;
22716
- return {
22717
- topic: entry.topic,
22718
- path: entry.path,
22719
- content: readFileSync4(entry.path, "utf8")
22720
- };
22721
- }
22722
- var PACKAGE_ROOT;
22723
- var init_bundled_docs = __esm(() => {
22724
- PACKAGE_ROOT = findPackageRoot();
22725
- });
22726
-
22727
22640
  // ../../node_modules/.bun/postgres@3.4.9/node_modules/postgres/src/query.js
22728
22641
  function cachedError(xs) {
22729
22642
  if (originCache.has(xs))
@@ -22742,9 +22655,9 @@ var init_query = __esm(() => {
22742
22655
  CLOSE = {};
22743
22656
  Query = class Query extends Promise {
22744
22657
  constructor(strings, args, handler, canceller, options = {}) {
22745
- let resolve3, reject;
22658
+ let resolve2, reject;
22746
22659
  super((a, b) => {
22747
- resolve3 = a;
22660
+ resolve2 = a;
22748
22661
  reject = b;
22749
22662
  });
22750
22663
  this.tagged = Array.isArray(strings.raw);
@@ -22755,7 +22668,7 @@ var init_query = __esm(() => {
22755
22668
  this.options = options;
22756
22669
  this.state = null;
22757
22670
  this.statement = null;
22758
- this.resolve = (x) => (this.active = false, resolve3(x));
22671
+ this.resolve = (x) => (this.active = false, resolve2(x));
22759
22672
  this.reject = (x) => (this.active = false, reject(x));
22760
22673
  this.active = false;
22761
22674
  this.cancelled = null;
@@ -22803,12 +22716,12 @@ var init_query = __esm(() => {
22803
22716
  if (this.executed && !this.active)
22804
22717
  return { done: true };
22805
22718
  prev && prev();
22806
- const promise = new Promise((resolve3, reject) => {
22719
+ const promise = new Promise((resolve2, reject) => {
22807
22720
  this.cursorFn = (value) => {
22808
- resolve3({ value, done: false });
22721
+ resolve2({ value, done: false });
22809
22722
  return new Promise((r) => prev = r);
22810
22723
  };
22811
- this.resolve = () => (this.active = false, resolve3({ done: true }));
22724
+ this.resolve = () => (this.active = false, resolve2({ done: true }));
22812
22725
  this.reject = (x) => (this.active = false, reject(x));
22813
22726
  });
22814
22727
  this.execute();
@@ -22867,31 +22780,31 @@ var init_query = __esm(() => {
22867
22780
  // ../../node_modules/.bun/postgres@3.4.9/node_modules/postgres/src/errors.js
22868
22781
  function connection(x, options, socket) {
22869
22782
  const { host, port } = socket || options;
22870
- const error2 = Object.assign(new Error("write " + x + " " + (options.path || host + ":" + port)), {
22783
+ const error = Object.assign(new Error("write " + x + " " + (options.path || host + ":" + port)), {
22871
22784
  code: x,
22872
22785
  errno: x,
22873
22786
  address: options.path || host
22874
22787
  }, options.path ? {} : { port });
22875
- Error.captureStackTrace(error2, connection);
22876
- return error2;
22788
+ Error.captureStackTrace(error, connection);
22789
+ return error;
22877
22790
  }
22878
22791
  function postgres(x) {
22879
- const error2 = new PostgresError(x);
22880
- Error.captureStackTrace(error2, postgres);
22881
- return error2;
22792
+ const error = new PostgresError(x);
22793
+ Error.captureStackTrace(error, postgres);
22794
+ return error;
22882
22795
  }
22883
22796
  function generic(code, message) {
22884
- const error2 = Object.assign(new Error(code + ": " + message), { code });
22885
- Error.captureStackTrace(error2, generic);
22886
- return error2;
22797
+ const error = Object.assign(new Error(code + ": " + message), { code });
22798
+ Error.captureStackTrace(error, generic);
22799
+ return error;
22887
22800
  }
22888
22801
  function notSupported(x) {
22889
- const error2 = Object.assign(new Error(x + " (B) is not supported"), {
22802
+ const error = Object.assign(new Error(x + " (B) is not supported"), {
22890
22803
  code: "MESSAGE_NOT_SUPPORTED",
22891
22804
  name: x
22892
22805
  });
22893
- Error.captureStackTrace(error2, notSupported);
22894
- return error2;
22806
+ Error.captureStackTrace(error, notSupported);
22807
+ return error;
22895
22808
  }
22896
22809
  var PostgresError, Errors;
22897
22810
  var init_errors2 = __esm(() => {
@@ -23239,7 +23152,7 @@ function fit(x) {
23239
23152
  prev.copy(buffer);
23240
23153
  }
23241
23154
  }
23242
- function reset2() {
23155
+ function reset() {
23243
23156
  b.i = 0;
23244
23157
  return b;
23245
23158
  }
@@ -23255,7 +23168,7 @@ var init_bytes = __esm(() => {
23255
23168
  };
23256
23169
  return acc;
23257
23170
  }, {});
23258
- b = Object.assign(reset2, messages, {
23171
+ b = Object.assign(reset, messages, {
23259
23172
  N: String.fromCharCode(0),
23260
23173
  i: 0,
23261
23174
  inc(x) {
@@ -23354,22 +23267,22 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23354
23267
  try {
23355
23268
  x = options.socket ? await Promise.resolve(options.socket(options)) : new net.Socket;
23356
23269
  } catch (e) {
23357
- error2(e);
23270
+ error(e);
23358
23271
  return;
23359
23272
  }
23360
- x.on("error", error2);
23273
+ x.on("error", error);
23361
23274
  x.on("close", closed);
23362
23275
  x.on("drain", drain);
23363
23276
  return x;
23364
23277
  }
23365
- async function cancel({ pid, secret }, resolve3, reject) {
23278
+ async function cancel({ pid, secret }, resolve2, reject) {
23366
23279
  try {
23367
23280
  cancelMessage = bytes_default().i32(16).i32(80877102).i32(pid).i32(secret).end(16);
23368
23281
  await connect();
23369
23282
  socket.once("error", reject);
23370
- socket.once("close", resolve3);
23371
- } catch (error3) {
23372
- reject(error3);
23283
+ socket.once("close", resolve2);
23284
+ } catch (error2) {
23285
+ reject(error2);
23373
23286
  }
23374
23287
  }
23375
23288
  function execute(q) {
@@ -23384,9 +23297,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23384
23297
  query ? sent.push(q) : (query = q, query.active = true);
23385
23298
  build(q);
23386
23299
  return write(toBuffer(q)) && !q.describeFirst && !q.cursorFn && sent.length < max_pipeline && (!q.options.onexecute || q.options.onexecute(connection2));
23387
- } catch (error3) {
23300
+ } catch (error2) {
23388
23301
  sent.length === 0 && write(Sync);
23389
- errored(error3);
23302
+ errored(error2);
23390
23303
  return true;
23391
23304
  }
23392
23305
  }
@@ -23465,7 +23378,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23465
23378
  socket.removeAllListeners();
23466
23379
  socket = tls.connect(options2);
23467
23380
  socket.on("secureConnect", connected);
23468
- socket.on("error", error2);
23381
+ socket.on("error", error);
23469
23382
  socket.on("close", closed);
23470
23383
  socket.on("drain", drain);
23471
23384
  }
@@ -23531,10 +23444,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23531
23444
  const s = StartupMessage();
23532
23445
  write(s);
23533
23446
  } catch (err) {
23534
- error2(err);
23447
+ error(err);
23535
23448
  }
23536
23449
  }
23537
- function error2(err) {
23450
+ function error(err) {
23538
23451
  if (connection2.queue === queues.connecting && options.host[retries + 1])
23539
23452
  return;
23540
23453
  errored(err);
@@ -23567,7 +23480,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23567
23480
  function terminate() {
23568
23481
  terminated = true;
23569
23482
  if (stream || query || initial || sent.length)
23570
- error2(Errors.connection("CONNECTION_DESTROYED", options));
23483
+ error(Errors.connection("CONNECTION_DESTROYED", options));
23571
23484
  clearImmediate(nextWriteTimer);
23572
23485
  if (socket) {
23573
23486
  socket.removeListener("data", data);
@@ -23590,7 +23503,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23590
23503
  socket = null;
23591
23504
  if (initial)
23592
23505
  return reconnect();
23593
- !hadError && (query || sent.length) && error2(Errors.connection("CONNECTION_CLOSED", options, socket));
23506
+ !hadError && (query || sent.length) && error(Errors.connection("CONNECTION_CLOSED", options, socket));
23594
23507
  closedTime = performance.now();
23595
23508
  hadError && options.shared.retries++;
23596
23509
  delay = (typeof backoff === "function" ? backoff(options.shared.retries) : backoff) * 1000;
@@ -23814,9 +23727,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23814
23727
  errored(Errors.postgres(parseError(x)));
23815
23728
  }
23816
23729
  }
23817
- function retry(q, error3) {
23730
+ function retry(q, error2) {
23818
23731
  delete statements[q.signature];
23819
- q.retried = error3;
23732
+ q.retried = error2;
23820
23733
  execute(q);
23821
23734
  }
23822
23735
  function NotificationResponse(x) {
@@ -23847,9 +23760,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23847
23760
  write(chunk2, encoding, callback) {
23848
23761
  socket.write(bytes_default().d().raw(chunk2).end(), callback);
23849
23762
  },
23850
- destroy(error3, callback) {
23851
- callback(error3);
23852
- socket.write(bytes_default().f().str(error3 + bytes_default.N).end());
23763
+ destroy(error2, callback) {
23764
+ callback(error2);
23765
+ socket.write(bytes_default().f().str(error2 + bytes_default.N).end());
23853
23766
  stream = null;
23854
23767
  },
23855
23768
  final(callback) {
@@ -23877,9 +23790,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23877
23790
  write(chunk2, encoding, callback) {
23878
23791
  socket.write(bytes_default().d().raw(chunk2).end(), callback);
23879
23792
  },
23880
- destroy(error3, callback) {
23881
- callback(error3);
23882
- socket.write(bytes_default().f().str(error3 + bytes_default.N).end());
23793
+ destroy(error2, callback) {
23794
+ callback(error2);
23795
+ socket.write(bytes_default().f().str(error2 + bytes_default.N).end());
23883
23796
  stream = null;
23884
23797
  },
23885
23798
  final(callback) {
@@ -23955,15 +23868,15 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
23955
23868
  }
23956
23869
  }
23957
23870
  function parseError(x) {
23958
- const error2 = {};
23871
+ const error = {};
23959
23872
  let start = 5;
23960
23873
  for (let i = 5;i < x.length - 1; i++) {
23961
23874
  if (x[i] === 0) {
23962
- error2[errorFields[x[start]]] = x.toString("utf8", start + 1, i);
23875
+ error[errorFields[x[start]]] = x.toString("utf8", start + 1, i);
23963
23876
  start = i + 1;
23964
23877
  }
23965
23878
  }
23966
- return error2;
23879
+ return error;
23967
23880
  }
23968
23881
  function md5(x) {
23969
23882
  return crypto2.createHash("md5").update(x).digest("hex");
@@ -24109,10 +24022,10 @@ function Subscribe(postgres2, options) {
24109
24022
  lsn: Buffer.concat(x.consistent_point.split("/").map((x2) => Buffer.from(("00000000" + x2).slice(-8), "hex")))
24110
24023
  };
24111
24024
  stream2.on("data", data);
24112
- stream2.on("error", error2);
24025
+ stream2.on("error", error);
24113
24026
  stream2.on("close", sql2.close);
24114
24027
  return { stream: stream2, state: xs.state };
24115
- function error2(e) {
24028
+ function error(e) {
24116
24029
  console.error("Unexpected error during logical streaming - reconnecting", e);
24117
24030
  }
24118
24031
  function data(x2) {
@@ -24240,7 +24153,7 @@ var noop2 = () => {};
24240
24153
  // ../../node_modules/.bun/postgres@3.4.9/node_modules/postgres/src/large.js
24241
24154
  import Stream2 from "stream";
24242
24155
  function largeObject(sql, oid, mode = 131072 | 262144) {
24243
- return new Promise(async (resolve3, reject) => {
24156
+ return new Promise(async (resolve2, reject) => {
24244
24157
  await sql.begin(async (sql2) => {
24245
24158
  let finish;
24246
24159
  !oid && ([{ oid }] = await sql2`select lo_creat(-1) as oid`);
@@ -24266,7 +24179,7 @@ function largeObject(sql, oid, mode = 131072 | 262144) {
24266
24179
  ) seek
24267
24180
  `
24268
24181
  };
24269
- resolve3(lo);
24182
+ resolve2(lo);
24270
24183
  return new Promise(async (r) => finish = r);
24271
24184
  async function readable({
24272
24185
  highWaterMark = 2048 * 8,
@@ -24309,7 +24222,7 @@ var exports_src = {};
24309
24222
  __export(exports_src, {
24310
24223
  default: () => src_default
24311
24224
  });
24312
- import os2 from "os";
24225
+ import os from "os";
24313
24226
  import fs from "fs";
24314
24227
  function Postgres(a, b2) {
24315
24228
  const options = parseOptions(a, b2), subscribe = options.no_subscribe || Subscribe(Postgres, { ...options });
@@ -24426,8 +24339,8 @@ function Postgres(a, b2) {
24426
24339
  }
24427
24340
  async function reserve() {
24428
24341
  const queue = queue_default();
24429
- const c2 = open.length ? open.shift() : await new Promise((resolve3, reject) => {
24430
- const query = { reserve: resolve3, reject };
24342
+ const c2 = open.length ? open.shift() : await new Promise((resolve2, reject) => {
24343
+ const query = { reserve: resolve2, reject };
24431
24344
  queries.push(query);
24432
24345
  closed.length && connect(closed.shift(), query);
24433
24346
  });
@@ -24454,8 +24367,8 @@ function Postgres(a, b2) {
24454
24367
  scope(connection2, fn),
24455
24368
  new Promise((_, reject) => connection2.onclose = reject)
24456
24369
  ]);
24457
- } catch (error2) {
24458
- throw error2;
24370
+ } catch (error) {
24371
+ throw error;
24459
24372
  }
24460
24373
  async function scope(c2, fn2, name) {
24461
24374
  const sql2 = Sql(handler2);
@@ -24464,9 +24377,9 @@ function Postgres(a, b2) {
24464
24377
  let uncaughtError, result;
24465
24378
  name && await sql2`savepoint ${sql2(name)}`;
24466
24379
  try {
24467
- result = await new Promise((resolve3, reject) => {
24380
+ result = await new Promise((resolve2, reject) => {
24468
24381
  const x = fn2(sql2);
24469
- Promise.resolve(Array.isArray(x) ? Promise.all(x) : x).then(resolve3, reject);
24382
+ Promise.resolve(Array.isArray(x) ? Promise.all(x) : x).then(resolve2, reject);
24470
24383
  });
24471
24384
  if (uncaughtError)
24472
24385
  throw uncaughtError;
@@ -24523,8 +24436,8 @@ function Postgres(a, b2) {
24523
24436
  return c2.execute(query) ? move(c2, busy) : move(c2, full);
24524
24437
  }
24525
24438
  function cancel(query) {
24526
- return new Promise((resolve3, reject) => {
24527
- query.state ? query.active ? connection_default(options).cancel(query.state, resolve3, reject) : query.cancelled = { resolve: resolve3, reject } : (queries.remove(query), query.cancelled = true, query.reject(Errors.generic("57014", "canceling statement due to user request")), resolve3());
24439
+ return new Promise((resolve2, reject) => {
24440
+ query.state ? query.active ? connection_default(options).cancel(query.state, resolve2, reject) : query.cancelled = { resolve: resolve2, reject } : (queries.remove(query), query.cancelled = true, query.reject(Errors.generic("57014", "canceling statement due to user request")), resolve2());
24528
24441
  });
24529
24442
  }
24530
24443
  async function end({ timeout = null } = {}) {
@@ -24540,11 +24453,11 @@ function Postgres(a, b2) {
24540
24453
  async function close() {
24541
24454
  await Promise.all(connections.map((c2) => c2.end()));
24542
24455
  }
24543
- async function destroy(resolve3) {
24456
+ async function destroy(resolve2) {
24544
24457
  await Promise.all(connections.map((c2) => c2.terminate()));
24545
24458
  while (queries.length)
24546
24459
  queries.shift().reject(Errors.connection("CONNECTION_DESTROYED", options));
24547
- resolve3();
24460
+ resolve2();
24548
24461
  }
24549
24462
  function connect(c2, query) {
24550
24463
  move(c2, connecting);
@@ -24577,7 +24490,7 @@ function Postgres(a, b2) {
24577
24490
  function parseOptions(a, b2) {
24578
24491
  if (a && a.shared)
24579
24492
  return a;
24580
- const env4 = process.env, o = (!a || typeof a === "string" ? b2 : a) || {}, { url, multihost } = parseUrl(a), query = [...url.searchParams].reduce((a2, [b3, c2]) => (a2[b3] = c2, a2), {}), host = o.hostname || o.host || multihost || url.hostname || env4.PGHOST || "localhost", port = o.port || url.port || env4.PGPORT || 5432, user = o.user || o.username || url.username || env4.PGUSERNAME || env4.PGUSER || osUsername();
24493
+ const env3 = process.env, o = (!a || typeof a === "string" ? b2 : a) || {}, { url, multihost } = parseUrl(a), query = [...url.searchParams].reduce((a2, [b3, c2]) => (a2[b3] = c2, a2), {}), host = o.hostname || o.host || multihost || url.hostname || env3.PGHOST || "localhost", port = o.port || url.port || env3.PGPORT || 5432, user = o.user || o.username || url.username || env3.PGUSERNAME || env3.PGUSER || osUsername();
24581
24494
  o.no_prepare && (o.prepare = false);
24582
24495
  query.sslmode && (query.ssl = query.sslmode, delete query.sslmode);
24583
24496
  "timeout" in o && (console.log("The timeout option is deprecated, use idle_timeout instead"), o.idle_timeout = o.timeout);
@@ -24603,21 +24516,21 @@ function parseOptions(a, b2) {
24603
24516
  host: Array.isArray(host) ? host : host.split(",").map((x) => x.split(":")[0]),
24604
24517
  port: Array.isArray(port) ? port : host.split(",").map((x) => parseInt(x.split(":")[1] || port)),
24605
24518
  path: o.path || host.indexOf("/") > -1 && host + "/.s.PGSQL." + port,
24606
- database: o.database || o.db || (url.pathname || "").slice(1) || env4.PGDATABASE || user,
24519
+ database: o.database || o.db || (url.pathname || "").slice(1) || env3.PGDATABASE || user,
24607
24520
  user,
24608
- pass: o.pass || o.password || url.password || env4.PGPASSWORD || "",
24521
+ pass: o.pass || o.password || url.password || env3.PGPASSWORD || "",
24609
24522
  ...Object.entries(defaults).reduce((acc, [k, d]) => {
24610
- const value = k in o ? o[k] : (k in query) ? query[k] === "disable" || query[k] === "false" ? false : query[k] : env4["PG" + k.toUpperCase()] || d;
24523
+ const value = k in o ? o[k] : (k in query) ? query[k] === "disable" || query[k] === "false" ? false : query[k] : env3["PG" + k.toUpperCase()] || d;
24611
24524
  acc[k] = typeof value === "string" && ints.includes(k) ? +value : value;
24612
24525
  return acc;
24613
24526
  }, {}),
24614
24527
  connection: {
24615
- application_name: env4.PGAPPNAME || "postgres.js",
24528
+ application_name: env3.PGAPPNAME || "postgres.js",
24616
24529
  ...o.connection,
24617
24530
  ...Object.entries(query).reduce((acc, [k, v]) => ((k in defaults) || (acc[k] = v), acc), {})
24618
24531
  },
24619
24532
  types: o.types || {},
24620
- target_session_attrs: tsa(o, url, env4),
24533
+ target_session_attrs: tsa(o, url, env3),
24621
24534
  onnotice: o.onnotice,
24622
24535
  onnotify: o.onnotify,
24623
24536
  onclose: o.onclose,
@@ -24629,8 +24542,8 @@ function parseOptions(a, b2) {
24629
24542
  ...mergeUserTypes(o.types)
24630
24543
  };
24631
24544
  }
24632
- function tsa(o, url, env4) {
24633
- const x = o.target_session_attrs || url.searchParams.get("target_session_attrs") || env4.PGTARGETSESSIONATTRS;
24545
+ function tsa(o, url, env3) {
24546
+ const x = o.target_session_attrs || url.searchParams.get("target_session_attrs") || env3.PGTARGETSESSIONATTRS;
24634
24547
  if (!x || ["read-write", "read-only", "primary", "standby", "prefer-standby"].includes(x))
24635
24548
  return x;
24636
24549
  throw new Error("target_session_attrs " + x + " is not supported");
@@ -24680,7 +24593,7 @@ function parseUrl(url) {
24680
24593
  }
24681
24594
  function osUsername() {
24682
24595
  try {
24683
- return os2.userInfo().username;
24596
+ return os.userInfo().username;
24684
24597
  } catch (_) {
24685
24598
  return process.env.USERNAME || process.env.USER || process.env.LOGNAME;
24686
24599
  }
@@ -24714,6 +24627,99 @@ var init_src = __esm(() => {
24714
24627
  src_default = Postgres;
24715
24628
  });
24716
24629
 
24630
+ // src/cli/util/bundled-docs.ts
24631
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync } from "node:fs";
24632
+ import { dirname as dirname3, join as join6, resolve as resolve2 } from "node:path";
24633
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
24634
+ function findPackageRoot() {
24635
+ let dir = dirname3(fileURLToPath2(import.meta.url));
24636
+ for (let i = 0;i < 10; i++) {
24637
+ const pkgJson = join6(dir, "package.json");
24638
+ if (existsSync7(pkgJson)) {
24639
+ try {
24640
+ const parsed = JSON.parse(readFileSync5(pkgJson, "utf8"));
24641
+ if (parsed.name === "@cerefox/memory")
24642
+ return dir;
24643
+ } catch {}
24644
+ }
24645
+ const parent = dirname3(dir);
24646
+ if (parent === dir)
24647
+ break;
24648
+ dir = parent;
24649
+ }
24650
+ return resolve2(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "..");
24651
+ }
24652
+ function bundledDocsDir() {
24653
+ const inPackage = join6(PACKAGE_ROOT, "docs");
24654
+ if (existsSync7(inPackage))
24655
+ return inPackage;
24656
+ return resolve2(PACKAGE_ROOT, "..", "..", "docs");
24657
+ }
24658
+ function agentGuidePath() {
24659
+ const candidates = [
24660
+ join6(PACKAGE_ROOT, "AGENT_GUIDE.md"),
24661
+ resolve2(PACKAGE_ROOT, "..", "..", "AGENT_GUIDE.md")
24662
+ ];
24663
+ for (const c2 of candidates)
24664
+ if (existsSync7(c2))
24665
+ return c2;
24666
+ return null;
24667
+ }
24668
+ function agentQuickReferencePath() {
24669
+ const candidates = [
24670
+ join6(PACKAGE_ROOT, "AGENT_QUICK_REFERENCE.md"),
24671
+ resolve2(PACKAGE_ROOT, "..", "..", "AGENT_QUICK_REFERENCE.md")
24672
+ ];
24673
+ for (const c2 of candidates)
24674
+ if (existsSync7(c2))
24675
+ return c2;
24676
+ return null;
24677
+ }
24678
+ function listBundledDocs() {
24679
+ const entries = [];
24680
+ const docsDir = bundledDocsDir();
24681
+ const guidesDir = join6(docsDir, "guides");
24682
+ if (existsSync7(guidesDir) && statSync(guidesDir).isDirectory()) {
24683
+ for (const name of readdirSync3(guidesDir)) {
24684
+ if (!name.endsWith(".md"))
24685
+ continue;
24686
+ const full = join6(guidesDir, name);
24687
+ entries.push({
24688
+ topic: name.replace(/\.md$/, ""),
24689
+ path: full,
24690
+ size: statSync(full).size
24691
+ });
24692
+ }
24693
+ }
24694
+ const ag = agentGuidePath();
24695
+ if (ag)
24696
+ entries.push({ topic: "agent-guide", path: ag, size: statSync(ag).size });
24697
+ const aqr = agentQuickReferencePath();
24698
+ if (aqr) {
24699
+ entries.push({
24700
+ topic: "agent-quick-reference",
24701
+ path: aqr,
24702
+ size: statSync(aqr).size
24703
+ });
24704
+ }
24705
+ entries.sort((a, b2) => a.topic.localeCompare(b2.topic));
24706
+ return entries;
24707
+ }
24708
+ function readBundledDoc(topic) {
24709
+ const entry = listBundledDocs().find((d) => d.topic === topic);
24710
+ if (!entry)
24711
+ return null;
24712
+ return {
24713
+ topic: entry.topic,
24714
+ path: entry.path,
24715
+ content: readFileSync5(entry.path, "utf8")
24716
+ };
24717
+ }
24718
+ var PACKAGE_ROOT;
24719
+ var init_bundled_docs = __esm(() => {
24720
+ PACKAGE_ROOT = findPackageRoot();
24721
+ });
24722
+
24717
24723
  // ../../_shared/embeddings/index.ts
24718
24724
  async function getEmbedding(text, apiKey) {
24719
24725
  let lastError = null;
@@ -26752,7 +26758,7 @@ __export(exports_sync_self_docs, {
26752
26758
  runSyncSelfDocs: () => runSyncSelfDocs,
26753
26759
  registerSyncSelfDocs: () => registerSyncSelfDocs
26754
26760
  });
26755
- import { readFileSync as readFileSync8 } from "node:fs";
26761
+ import { readFileSync as readFileSync9 } from "node:fs";
26756
26762
  import { basename as basename4, extname as extname4 } from "node:path";
26757
26763
  async function runSyncSelfDocs(options = {}) {
26758
26764
  const project = options.project ?? "_cerefox-self-docs";
@@ -26779,7 +26785,7 @@ async function runSyncSelfDocs(options = {}) {
26779
26785
  const authorType = resolveAuthorType("agent");
26780
26786
  const outcomes = [];
26781
26787
  for (const doc of docs) {
26782
- const content = readFileSync8(doc.path, "utf8");
26788
+ const content = readFileSync9(doc.path, "utf8");
26783
26789
  const m = content.match(/^#\s+(.+)$/m);
26784
26790
  const title = m ? m[1].trim() : basename4(doc.path, extname4(doc.path));
26785
26791
  try {
@@ -26813,11 +26819,11 @@ async function runSyncSelfDocs(options = {}) {
26813
26819
  printTable(outcomes.filter((o) => o.status === "error").map((o) => ({ topic: o.topic, error: o.detail.slice(0, 100) })));
26814
26820
  }
26815
26821
  }
26816
- async function action13(options) {
26822
+ async function action15(options) {
26817
26823
  await runSyncSelfDocs(options);
26818
26824
  }
26819
26825
  function registerSyncSelfDocs(program2) {
26820
- program2.command("sync-self-docs").description("Ingest bundled Cerefox docs under the _cerefox-self-docs project.").option("--dry-run", "List what would be ingested without writing.").option("--project <name>", "Override the target project name.", "_cerefox-self-docs").action(action13);
26826
+ program2.command("sync-self-docs").description("Ingest bundled Cerefox docs under the _cerefox-self-docs project.").option("--dry-run", "List what would be ingested without writing.").option("--project <name>", "Override the target project name.", "_cerefox-self-docs").action(action15);
26821
26827
  }
26822
26828
  var init_sync_self_docs = __esm(() => {
26823
26829
  init_cli_core();
@@ -41527,18 +41533,472 @@ function registerDeleteDoc(program2) {
41527
41533
  program2.command("delete-doc").description("Soft-delete a document (recoverable via the web UI trash).").argument("<document-id>", "UUID of the document to delete.").option("--reason <text>", "Optional reason recorded in the audit log.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("--yes", "Skip the confirmation prompt.").action(action6);
41528
41534
  }
41529
41535
 
41536
+ // src/cli/commands/delete-project.ts
41537
+ init_cli_core();
41538
+ init_client();
41539
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
41540
+ async function action7(target, options) {
41541
+ const client = getClient();
41542
+ const isUuid = UUID_RE.test(target);
41543
+ const lookup = isUuid ? client.raw.from("cerefox_projects").select("id, name, description").eq("id", target).maybeSingle() : client.raw.from("cerefox_projects").select("id, name, description").eq("name", target).maybeSingle();
41544
+ const { data: project, error } = await lookup;
41545
+ if (error) {
41546
+ throw systemError(`Project lookup failed: ${error.message}`);
41547
+ }
41548
+ if (!project) {
41549
+ throw notFound(`Project "${target}" not found.`);
41550
+ }
41551
+ const { count, error: countErr } = await client.raw.from("cerefox_document_projects").select("*", { count: "exact", head: true }).eq("project_id", project.id);
41552
+ if (countErr) {
41553
+ throw systemError(`Could not count documents in project: ${countErr.message}`);
41554
+ }
41555
+ const docCount = count ?? 0;
41556
+ if (docCount > 0 && !options.force) {
41557
+ throw userError(`Project "${project.name}" still has ${docCount} document link(s).`, "Pass --force to delete the project anyway (documents remain; only the project row is removed).");
41558
+ }
41559
+ println(c.yellow("About to delete project:"));
41560
+ println(` ${project.name}`);
41561
+ println(c.dim(` ${project.id} · ${docCount} doc link(s)`));
41562
+ if (!options.yes) {
41563
+ const ok2 = await confirm("Continue?", true);
41564
+ if (!ok2) {
41565
+ println(c.dim("Aborted."));
41566
+ return;
41567
+ }
41568
+ }
41569
+ const { error: delErr } = await client.raw.from("cerefox_projects").delete().eq("id", project.id);
41570
+ if (delErr) {
41571
+ throw systemError(`Delete failed: ${delErr.message}`);
41572
+ }
41573
+ println(c.green(`✓ Deleted project "${project.name}" (id: ${project.id}).`));
41574
+ }
41575
+ function registerDeleteProject(program2) {
41576
+ program2.command("delete-project").description("Delete an empty project (use --force to remove a non-empty one).").argument("<name-or-id>", "Project name (exact match) or UUID.").option("--yes", "Skip the confirmation prompt.").option("--force", "Allow deletion when documents are still linked to the project.").action(action7);
41577
+ }
41578
+
41579
+ // src/cli/commands/deploy-server.ts
41580
+ init_cli_core();
41581
+ init_config();
41582
+ import { spawnSync as spawnSync2 } from "node:child_process";
41583
+ import { existsSync as existsSync6 } from "node:fs";
41584
+ import { readdirSync as readdirSync2 } from "node:fs";
41585
+
41586
+ // ../../_shared/server-assets/index.ts
41587
+ import { existsSync as existsSync4 } from "node:fs";
41588
+ import { dirname as dirname2, join as join4 } from "node:path";
41589
+ import { fileURLToPath } from "node:url";
41590
+ import { cwd as processCwd2 } from "node:process";
41591
+ function moduleDir() {
41592
+ return dirname2(fileURLToPath(import.meta.url));
41593
+ }
41594
+ function bundledServerAssets(serverAssetsRoot) {
41595
+ return {
41596
+ schemaFile: join4(serverAssetsRoot, "db", "schema.sql"),
41597
+ rpcsFile: join4(serverAssetsRoot, "db", "rpcs.sql"),
41598
+ migrationsDir: join4(serverAssetsRoot, "db", "migrations"),
41599
+ functionsDir: join4(serverAssetsRoot, "supabase", "functions"),
41600
+ layout: "bundled"
41601
+ };
41602
+ }
41603
+ function sourceServerAssets(repoRoot) {
41604
+ const dbDir = join4(repoRoot, "src", "cerefox", "db");
41605
+ return {
41606
+ schemaFile: join4(dbDir, "schema.sql"),
41607
+ rpcsFile: join4(dbDir, "rpcs.sql"),
41608
+ migrationsDir: join4(dbDir, "migrations"),
41609
+ functionsDir: join4(repoRoot, "supabase", "functions"),
41610
+ layout: "source"
41611
+ };
41612
+ }
41613
+ function serverAssetsUsable(p) {
41614
+ return existsSync4(p.schemaFile) && existsSync4(p.rpcsFile);
41615
+ }
41616
+ function resolveServerAssets(opts = {}) {
41617
+ if (opts.assetsDir) {
41618
+ return { ...bundledServerAssets(opts.assetsDir), layout: "explicit" };
41619
+ }
41620
+ const here = opts.moduleDirOverride ?? moduleDir();
41621
+ const cwd = opts.cwd ?? processCwd2();
41622
+ const candidates = [
41623
+ bundledServerAssets(join4(here, "..", "server-assets")),
41624
+ sourceServerAssets(join4(here, "..", "..")),
41625
+ sourceServerAssets(cwd)
41626
+ ];
41627
+ for (const candidate of candidates) {
41628
+ if (serverAssetsUsable(candidate))
41629
+ return candidate;
41630
+ }
41631
+ return sourceServerAssets(join4(here, "..", ".."));
41632
+ }
41633
+
41634
+ // ../../_shared/db-deploy/index.ts
41635
+ init_src();
41636
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync } from "node:fs";
41637
+ import { join as join5 } from "node:path";
41638
+ var RESET_SQL = `
41639
+ DROP TABLE IF EXISTS cerefox_chunks CASCADE;
41640
+ DROP TABLE IF EXISTS cerefox_documents CASCADE;
41641
+ DROP TABLE IF EXISTS cerefox_projects CASCADE;
41642
+ DROP TABLE IF EXISTS cerefox_migrations CASCADE;
41643
+ DROP FUNCTION IF EXISTS cerefox_set_updated_at CASCADE;
41644
+ DROP FUNCTION IF EXISTS cerefox_hybrid_search CASCADE;
41645
+ DROP FUNCTION IF EXISTS cerefox_fts_search CASCADE;
41646
+ DROP FUNCTION IF EXISTS cerefox_semantic_search CASCADE;
41647
+ DROP FUNCTION IF EXISTS cerefox_reconstruct_doc CASCADE;
41648
+ `;
41649
+ var EXTENSIONS_SQL = `
41650
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
41651
+ CREATE EXTENSION IF NOT EXISTS "vector";
41652
+ `;
41653
+ function listMigrationFiles(migrationsDir) {
41654
+ if (!existsSync5(migrationsDir))
41655
+ return [];
41656
+ return readdirSync(migrationsDir).filter((n) => n.endsWith(".sql")).sort();
41657
+ }
41658
+ function buildDeploySteps(assets, opts = {}) {
41659
+ const schemaSql = readFileSync4(assets.schemaFile, "utf8");
41660
+ const rpcsSql = readFileSync4(assets.rpcsFile, "utf8");
41661
+ const steps = [];
41662
+ if (opts.reset) {
41663
+ steps.push({ label: "Reset: drop existing Cerefox objects", sql: RESET_SQL });
41664
+ }
41665
+ steps.push({ label: "Enable extensions (uuid-ossp, vector/pgvector)", sql: EXTENSIONS_SQL }, { label: "Apply schema (tables, indexes, triggers)", sql: schemaSql }, { label: "Apply RPCs (search functions)", sql: rpcsSql });
41666
+ const migrationFiles = listMigrationFiles(assets.migrationsDir);
41667
+ if (migrationFiles.length > 0) {
41668
+ const values2 = migrationFiles.map((n) => `('${n.replace(/'/g, "''")}')`).join(", ");
41669
+ steps.push({
41670
+ label: "Stamp migration files as already applied",
41671
+ sql: `INSERT INTO cerefox_migrations (filename) VALUES ${values2} ON CONFLICT (filename) DO NOTHING;`
41672
+ });
41673
+ }
41674
+ return steps;
41675
+ }
41676
+ async function runDbDeploy(opts) {
41677
+ const log = opts.log ?? (() => {});
41678
+ const steps = buildDeploySteps(opts.assets, { reset: opts.reset });
41679
+ if (opts.dryRun) {
41680
+ for (const step of steps) {
41681
+ log(`▶ ${step.label}… (dry-run, not executed)`);
41682
+ }
41683
+ return { ok: true, stepsRun: steps.length };
41684
+ }
41685
+ const sql = src_default(opts.dbUrl, { prepare: false, onnotice: () => {} });
41686
+ let stepsRun = 0;
41687
+ try {
41688
+ for (const step of steps) {
41689
+ log(`▶ ${step.label}…`);
41690
+ try {
41691
+ await sql.unsafe(step.sql);
41692
+ stepsRun++;
41693
+ } catch (err) {
41694
+ const message = err instanceof Error ? err.message : String(err);
41695
+ return { ok: false, stepsRun, failedStep: step.label, error: message };
41696
+ }
41697
+ }
41698
+ return { ok: true, stepsRun };
41699
+ } finally {
41700
+ await sql.end({ timeout: 5 }).catch(() => {});
41701
+ }
41702
+ }
41703
+ var BOOTSTRAP_MIGRATIONS_SQL = `
41704
+ CREATE TABLE IF NOT EXISTS cerefox_migrations (
41705
+ id SERIAL PRIMARY KEY,
41706
+ filename TEXT NOT NULL UNIQUE,
41707
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
41708
+ );
41709
+ `;
41710
+ async function detectExistingSchema(dbUrl) {
41711
+ const sql = src_default(dbUrl, { prepare: false, onnotice: () => {} });
41712
+ try {
41713
+ const rows = await sql`SELECT to_regclass('public.cerefox_documents') AS t`;
41714
+ return rows[0]?.t != null;
41715
+ } finally {
41716
+ await sql.end({ timeout: 5 }).catch(() => {});
41717
+ }
41718
+ }
41719
+ async function migrationStatus(opts) {
41720
+ const sql = src_default(opts.dbUrl, { prepare: false, onnotice: () => {} });
41721
+ try {
41722
+ await sql.unsafe(BOOTSTRAP_MIGRATIONS_SQL);
41723
+ const all = listMigrationFiles(opts.assets.migrationsDir);
41724
+ const appliedRows = await sql`SELECT filename FROM cerefox_migrations ORDER BY filename`;
41725
+ const appliedSet = new Set(appliedRows.map((r) => r.filename));
41726
+ return {
41727
+ all,
41728
+ applied: all.filter((f) => appliedSet.has(f)),
41729
+ pending: all.filter((f) => !appliedSet.has(f))
41730
+ };
41731
+ } finally {
41732
+ await sql.end({ timeout: 5 }).catch(() => {});
41733
+ }
41734
+ }
41735
+ async function runDbMigrate(opts) {
41736
+ const log = opts.log ?? (() => {});
41737
+ const sql = src_default(opts.dbUrl, { prepare: false, onnotice: () => {} });
41738
+ try {
41739
+ await sql.unsafe(BOOTSTRAP_MIGRATIONS_SQL);
41740
+ const allFiles = listMigrationFiles(opts.assets.migrationsDir);
41741
+ const appliedRows = await sql`SELECT filename FROM cerefox_migrations ORDER BY filename`;
41742
+ const appliedSet = new Set(appliedRows.map((r) => r.filename));
41743
+ const pending = allFiles.filter((f) => !appliedSet.has(f));
41744
+ if (pending.length === 0)
41745
+ return { ok: true, applied: [], pending: [] };
41746
+ if (opts.dryRun) {
41747
+ for (const f of pending)
41748
+ log(`Would apply ${f}`);
41749
+ return { ok: true, applied: [], pending };
41750
+ }
41751
+ const applied = [];
41752
+ for (const f of pending) {
41753
+ const body = readFileSync4(join5(opts.assets.migrationsDir, f), "utf8");
41754
+ log(`Applying ${f}…`);
41755
+ try {
41756
+ await sql.begin(async (tx) => {
41757
+ await tx.unsafe(body);
41758
+ await tx`INSERT INTO cerefox_migrations (filename) VALUES (${f}) ON CONFLICT DO NOTHING`;
41759
+ });
41760
+ applied.push(f);
41761
+ } catch (err) {
41762
+ return {
41763
+ ok: false,
41764
+ applied,
41765
+ pending,
41766
+ failedFile: f,
41767
+ error: err instanceof Error ? err.message : String(err)
41768
+ };
41769
+ }
41770
+ }
41771
+ return { ok: true, applied, pending };
41772
+ } finally {
41773
+ await sql.end({ timeout: 5 }).catch(() => {});
41774
+ }
41775
+ }
41776
+ async function applyRpcs(opts) {
41777
+ const log = opts.log ?? (() => {});
41778
+ log("Refresh RPCs (rpcs.sql)…");
41779
+ if (opts.dryRun)
41780
+ return { ok: true };
41781
+ const rpcsSql = readFileSync4(opts.assets.rpcsFile, "utf8");
41782
+ const sql = src_default(opts.dbUrl, { prepare: false, onnotice: () => {} });
41783
+ try {
41784
+ await sql.unsafe(rpcsSql);
41785
+ return { ok: true };
41786
+ } catch (err) {
41787
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
41788
+ } finally {
41789
+ await sql.end({ timeout: 5 }).catch(() => {});
41790
+ }
41791
+ }
41792
+
41793
+ // src/cli/commands/deploy-server.ts
41794
+ function commandSucceeds(cmd, args) {
41795
+ try {
41796
+ const r = spawnSync2(cmd, args, { encoding: "utf8", timeout: 15000 });
41797
+ return r.status === 0;
41798
+ } catch {
41799
+ return false;
41800
+ }
41801
+ }
41802
+ function listEdgeFunctions(functionsDir) {
41803
+ if (!existsSync6(functionsDir))
41804
+ return [];
41805
+ return readdirSync2(functionsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("cerefox-")).map((e) => e.name).sort();
41806
+ }
41807
+ async function action8(options) {
41808
+ const settings = loadSettings();
41809
+ const assets = resolveServerAssets();
41810
+ const doSchema = !options.functionsOnly;
41811
+ const doFunctions = !options.schemaOnly;
41812
+ const checks = [];
41813
+ if (doSchema) {
41814
+ checks.push({
41815
+ label: "CEREFOX_DATABASE_URL is set (Session Pooler, port 5432)",
41816
+ ok: Boolean(settings.databaseUrl),
41817
+ remediation: "Set CEREFOX_DATABASE_URL in your .env. Use the Session Pooler URI " + "(port 5432, username postgres.<project-ref>, ?sslmode=require). " + "See docs/guides/setup-supabase.md → Connection pooling."
41818
+ });
41819
+ checks.push({
41820
+ label: "Bundled schema assets present",
41821
+ ok: existsSync6(assets.schemaFile) && existsSync6(assets.rpcsFile),
41822
+ remediation: "Schema files not found. Reinstall the package, or run from a repo " + "clone (src/cerefox/db/)."
41823
+ });
41824
+ }
41825
+ let efNames = [];
41826
+ if (doFunctions) {
41827
+ efNames = listEdgeFunctions(assets.functionsDir);
41828
+ checks.push({
41829
+ label: "Bundled Edge Function sources present",
41830
+ ok: efNames.length > 0,
41831
+ remediation: "Edge Function sources not found under the bundled assets. Reinstall " + "the package, or run from a repo clone (supabase/functions/)."
41832
+ });
41833
+ checks.push({
41834
+ label: "Supabase CLI reachable (`npx supabase --version`)",
41835
+ ok: commandSucceeds("npx", ["--yes", "supabase", "--version"]),
41836
+ remediation: "Install Node 20+ (npx ships with it) from nodejs.org, then re-run. " + "`cerefox deploy-server` shells out to the Supabase CLI via npx."
41837
+ });
41838
+ checks.push({
41839
+ label: "Supabase project linked (`npx supabase link`)",
41840
+ ok: existsSync6("supabase/config.toml") || existsSync6(".supabase/config.toml"),
41841
+ remediation: `Authenticate + link your project (one-time, from any working dir):
41842
+ ` + ` npx supabase login
41843
+ ` + ` npx supabase link --project-ref <your-project-ref>
41844
+ ` + " Your project ref is in the dashboard URL: " + "https://supabase.com/dashboard/project/<project-ref>"
41845
+ });
41846
+ }
41847
+ const failed = checks.filter((ch) => !ch.ok);
41848
+ println(c.bold("Cerefox deploy-server — pre-flight"));
41849
+ for (const ch of checks) {
41850
+ println(` ${ch.ok ? c.green("✓") : c.red("✗")} ${ch.label}`);
41851
+ }
41852
+ if (failed.length > 0) {
41853
+ eprintln("");
41854
+ eprintln(c.red(`Cannot deploy yet — ${failed.length} prerequisite(s) missing:`));
41855
+ for (const ch of failed) {
41856
+ eprintln(`
41857
+ ${c.red("✗")} ${ch.label}`);
41858
+ eprintln(c.dim(` → ${ch.remediation}`));
41859
+ }
41860
+ eprintln("\nFix the above and re-run `cerefox deploy-server` (it's idempotent).");
41861
+ process.exit(1);
41862
+ }
41863
+ println(c.green(`
41864
+ ✓ All prerequisites satisfied.`));
41865
+ let schemaMode = "unknown";
41866
+ let pending = [];
41867
+ if (doSchema) {
41868
+ try {
41869
+ const exists = await detectExistingSchema(settings.databaseUrl);
41870
+ schemaMode = exists ? "existing" : "fresh";
41871
+ if (exists) {
41872
+ pending = (await migrationStatus({ dbUrl: settings.databaseUrl, assets })).pending;
41873
+ }
41874
+ } catch (err) {
41875
+ if (!options.dryRun) {
41876
+ eprintln(c.red(`
41877
+ ✗ Could not connect to the database: ${err instanceof Error ? err.message : String(err)}`));
41878
+ eprintln(c.dim(" Verify CEREFOX_DATABASE_URL (Session Pooler, port 5432)."));
41879
+ process.exit(1);
41880
+ }
41881
+ }
41882
+ }
41883
+ const planLines = [];
41884
+ if (doSchema) {
41885
+ if (schemaMode === "fresh") {
41886
+ planLines.push(` • Deploy fresh schema + RPCs to ${settings.supabaseUrl || "your Supabase database"}`);
41887
+ } else if (schemaMode === "existing") {
41888
+ planLines.push(` • Update existing schema: apply ${pending.length} pending migration(s) + refresh RPCs`);
41889
+ for (const f of pending)
41890
+ planLines.push(c.dim(` – ${f}`));
41891
+ } else {
41892
+ planLines.push(` • Schema + RPCs (fresh or update — couldn't probe the DB)`);
41893
+ }
41894
+ }
41895
+ if (doFunctions) {
41896
+ planLines.push(` • Deploy ${efNames.length} Edge Function(s): ${efNames.join(", ")}`);
41897
+ }
41898
+ println(c.bold(`
41899
+ Plan:`));
41900
+ for (const line of planLines)
41901
+ println(line);
41902
+ if (options.dryRun) {
41903
+ println(c.yellow(`
41904
+ ⚠ --dry-run: nothing was deployed.`));
41905
+ process.exit(0);
41906
+ }
41907
+ const proceed = await confirm(`
41908
+ Proceed with deployment to Supabase?`, true);
41909
+ if (!proceed) {
41910
+ println(c.dim("Aborted."));
41911
+ process.exit(0);
41912
+ }
41913
+ if (doSchema) {
41914
+ if (schemaMode === "fresh") {
41915
+ println(c.bold(`
41916
+ ▶ Deploying fresh schema + RPCs…`));
41917
+ const result = await runDbDeploy({
41918
+ dbUrl: settings.databaseUrl,
41919
+ assets,
41920
+ log: (line) => println(c.dim(` ${line}`))
41921
+ });
41922
+ if (!result.ok) {
41923
+ eprintln(c.red(`
41924
+ ✗ Schema deploy failed at "${result.failedStep}": ${result.error}`));
41925
+ process.exit(1);
41926
+ }
41927
+ println(c.green(` ✓ Fresh schema deployed (${result.stepsRun} steps).`));
41928
+ } else {
41929
+ println(c.bold(`
41930
+ ▶ Updating existing schema (migrations + RPC refresh)…`));
41931
+ const mig = await runDbMigrate({
41932
+ dbUrl: settings.databaseUrl,
41933
+ assets,
41934
+ log: (line) => println(c.dim(` ${line}`))
41935
+ });
41936
+ if (!mig.ok) {
41937
+ eprintln(c.red(`
41938
+ ✗ Migration "${mig.failedFile}" failed: ${mig.error}`));
41939
+ eprintln(c.dim(" Earlier migrations in this run were committed. Fix + re-run."));
41940
+ process.exit(1);
41941
+ }
41942
+ const rpcs = await applyRpcs({
41943
+ dbUrl: settings.databaseUrl,
41944
+ assets,
41945
+ log: (line) => println(c.dim(` ${line}`))
41946
+ });
41947
+ if (!rpcs.ok) {
41948
+ eprintln(c.red(`
41949
+ ✗ RPC refresh failed: ${rpcs.error}`));
41950
+ process.exit(1);
41951
+ }
41952
+ println(c.green(` ✓ Applied ${mig.applied.length} migration(s); RPCs refreshed.`));
41953
+ }
41954
+ }
41955
+ if (doFunctions) {
41956
+ println(c.bold(`
41957
+ ▶ Deploying ${efNames.length} Edge Function(s)…`));
41958
+ let efOk = 0;
41959
+ const efFailed = [];
41960
+ for (const ef of efNames) {
41961
+ info(` deploying ${ef}…`);
41962
+ const workdir = assets.functionsDir.replace(/\/functions$/, "").replace(/\/supabase$/, "");
41963
+ const r = spawnSync2("npx", ["--yes", "supabase", "functions", "deploy", ef], {
41964
+ encoding: "utf8",
41965
+ stdio: "inherit",
41966
+ cwd: workdir,
41967
+ timeout: 120000
41968
+ });
41969
+ if (r.status === 0)
41970
+ efOk++;
41971
+ else
41972
+ efFailed.push(ef);
41973
+ }
41974
+ if (efFailed.length > 0) {
41975
+ eprintln(c.red(`
41976
+ ✗ ${efFailed.length} Edge Function(s) failed: ${efFailed.join(", ")}`));
41977
+ eprintln(c.dim(" Re-run `cerefox deploy-server --functions-only` after fixing the cause."));
41978
+ process.exit(1);
41979
+ }
41980
+ println(c.green(` ✓ Deployed ${efOk} Edge Function(s).`));
41981
+ }
41982
+ println(c.green(`
41983
+ ✓ Server deploy complete.`));
41984
+ println(c.dim("Verify with: cerefox doctor"));
41985
+ }
41986
+ function registerDeployServer(program2) {
41987
+ program2.command("deploy-server").description("Deploy/update the Cerefox server side (schema + RPCs + Edge Functions) on Supabase.").option("--dry-run", "Print the plan + pre-flight without deploying.").option("--schema-only", "Deploy/update only the schema + RPCs (skip Edge Functions).").option("--functions-only", "Deploy only the Edge Functions (skip the schema/RPCs).").action(action8);
41988
+ }
41989
+
41530
41990
  // src/cli/commands/docs.ts
41531
41991
  init_cli_core();
41532
41992
  init_bundled_docs();
41533
- import { spawnSync as spawnSync2 } from "node:child_process";
41993
+ import { spawnSync as spawnSync3 } from "node:child_process";
41534
41994
  function openInBrowser(path) {
41535
41995
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
41536
- const result = spawnSync2(cmd, [path], { stdio: "ignore" });
41996
+ const result = spawnSync3(cmd, [path], { stdio: "ignore" });
41537
41997
  if (result.status !== 0) {
41538
41998
  println(c.dim(`(could not auto-open; the file is at: ${path})`));
41539
41999
  }
41540
42000
  }
41541
- function action7(topic, options) {
42001
+ function action9(topic, options) {
41542
42002
  const docs = listBundledDocs();
41543
42003
  if (options.list || !topic && !options.print) {
41544
42004
  if (docs.length === 0) {
@@ -41567,7 +42027,7 @@ function action7(topic, options) {
41567
42027
  openInBrowser(doc.path);
41568
42028
  }
41569
42029
  function registerDocs(program2) {
41570
- program2.command("docs").description("Open bundled Cerefox docs in your browser (or print to stdout).").argument("[topic]", "Doc topic (e.g. quickstart, connect-agents). Omit for the index.").option("--print", "Print to stdout instead of opening a browser.").option("--list", "List available topics.").action(action7);
42030
+ program2.command("docs").description("Open bundled Cerefox docs in your browser (or print to stdout).").argument("[topic]", "Doc topic (e.g. quickstart, connect-agents). Omit for the index.").option("--print", "Print to stdout instead of opening a browser.").option("--list", "List available topics.").action(action9);
41571
42031
  }
41572
42032
 
41573
42033
  // ../../node_modules/.bun/ora@9.4.0/node_modules/ora/index.js
@@ -41753,7 +42213,7 @@ var ansi_styles_default = ansiStyles;
41753
42213
 
41754
42214
  // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
41755
42215
  import process2 from "node:process";
41756
- import os from "node:os";
42216
+ import os2 from "node:os";
41757
42217
  import tty from "node:tty";
41758
42218
  function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
41759
42219
  const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
@@ -41818,7 +42278,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
41818
42278
  return min;
41819
42279
  }
41820
42280
  if (process2.platform === "win32") {
41821
- const osRelease = os.release().split(".");
42281
+ const osRelease = os2.release().split(".");
41822
42282
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
41823
42283
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
41824
42284
  }
@@ -44132,7 +44592,7 @@ var format = (open, close) => {
44132
44592
  return result;
44133
44593
  };
44134
44594
  };
44135
- var reset = format(0, 0);
44595
+ var reset2 = format(0, 0);
44136
44596
  var bold = format(1, 22);
44137
44597
  var dim = format(2, 22);
44138
44598
  var italic = format(3, 23);
@@ -44959,9 +45419,105 @@ init_cli_core();
44959
45419
  init_meta();
44960
45420
  init_config();
44961
45421
  init_config();
44962
- import { existsSync as existsSync5, readFileSync as readFileSync5, realpathSync, statSync as statSync2 } from "node:fs";
45422
+ import { existsSync as existsSync8, readFileSync as readFileSync6, realpathSync, statSync as statSync2 } from "node:fs";
44963
45423
  import { homedir as homedir4 } from "node:os";
44964
- import { join as join5 } from "node:path";
45424
+ import { join as join7 } from "node:path";
45425
+
45426
+ // ../../_shared/compatibility/index.ts
45427
+ var COMPATIBILITY = {
45428
+ minSchema: "0.3.1",
45429
+ minEdgeFunctions: "0.6.0"
45430
+ };
45431
+ function compareSemver(a, b2) {
45432
+ const norm = (v) => v.split(/[.-]/).slice(0, 3).map((p) => Number.parseInt(p, 10)).map((n) => Number.isFinite(n) ? n : 0);
45433
+ const pa = norm(a);
45434
+ const pb = norm(b2);
45435
+ for (let i = 0;i < 3; i++) {
45436
+ const x = pa[i] ?? 0;
45437
+ const y = pb[i] ?? 0;
45438
+ if (x !== y)
45439
+ return x < y ? -1 : 1;
45440
+ }
45441
+ return 0;
45442
+ }
45443
+ function classifyCompat(deployed, min, bundled) {
45444
+ if (!deployed)
45445
+ return "unknown";
45446
+ if (compareSemver(deployed, min) < 0)
45447
+ return "below-min";
45448
+ if (bundled && compareSemver(deployed, bundled) < 0)
45449
+ return "above-min-but-old";
45450
+ return "ok";
45451
+ }
45452
+ function aggregatorUrlFor(supabaseUrl) {
45453
+ const base = supabaseUrl.replace(/\/$/, "");
45454
+ return `${base}/functions/v1/cerefox-mcp/version?peers=true`;
45455
+ }
45456
+ async function checkServerCompatibility(opts) {
45457
+ const fetchImpl = opts.fetchImpl ?? fetch;
45458
+ const result = {
45459
+ schema: { deployed: null, min: COMPATIBILITY.minSchema, level: "unknown" },
45460
+ edgeFunctions: {
45461
+ deployed: null,
45462
+ min: COMPATIBILITY.minEdgeFunctions,
45463
+ level: "unknown",
45464
+ errors: []
45465
+ },
45466
+ blocking: false,
45467
+ efProbeSkipped: false
45468
+ };
45469
+ if (!opts.bearer) {
45470
+ result.efProbeSkipped = true;
45471
+ result.efSkipReason = "No anon JWT (CEREFOX_SUPABASE_ANON_KEY) configured; Edge Function version check skipped.";
45472
+ return result;
45473
+ }
45474
+ let agg = null;
45475
+ try {
45476
+ const ctrl = new AbortController;
45477
+ const timer2 = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 6000);
45478
+ try {
45479
+ const resp = await fetchImpl(opts.aggregatorUrl, {
45480
+ method: "GET",
45481
+ headers: { Authorization: `Bearer ${opts.bearer}`, apikey: opts.bearer },
45482
+ signal: ctrl.signal
45483
+ });
45484
+ if (resp.ok) {
45485
+ agg = await resp.json();
45486
+ } else {
45487
+ result.efProbeSkipped = true;
45488
+ result.efSkipReason = resp.status === 404 || resp.status === 405 ? "Edge Functions predate v0.8 (no /version route). Redeploy with `cerefox deploy-server --functions-only` to enable version checks." : `Aggregator returned HTTP ${resp.status}; Edge Function version check skipped.`;
45489
+ }
45490
+ } finally {
45491
+ clearTimeout(timer2);
45492
+ }
45493
+ } catch (err) {
45494
+ result.efProbeSkipped = true;
45495
+ result.efSkipReason = `Could not reach the version aggregator: ${err instanceof Error ? err.message : String(err)}`;
45496
+ }
45497
+ if (!agg)
45498
+ return result;
45499
+ result.schema.deployed = agg.schema ?? null;
45500
+ result.schema.level = classifyCompat(result.schema.deployed, COMPATIBILITY.minSchema, opts.bundledSchema);
45501
+ const versions = [];
45502
+ if (agg.version)
45503
+ versions.push(agg.version);
45504
+ for (const ef of agg.efs ?? [])
45505
+ versions.push(ef.version);
45506
+ result.edgeFunctions.errors = agg.errors ?? [];
45507
+ if (versions.length > 0) {
45508
+ const weakest = versions.reduce((lo, v) => compareSemver(v, lo) < 0 ? v : lo);
45509
+ result.edgeFunctions.deployed = weakest;
45510
+ result.edgeFunctions.level = classifyCompat(weakest, COMPATIBILITY.minEdgeFunctions, opts.bundledEf);
45511
+ } else {
45512
+ result.edgeFunctions.level = "unknown";
45513
+ result.efProbeSkipped = true;
45514
+ result.efSkipReason = "Aggregator reported no Edge Function versions; check skipped.";
45515
+ }
45516
+ result.blocking = result.schema.level === "below-min" || result.edgeFunctions.level === "below-min";
45517
+ return result;
45518
+ }
45519
+
45520
+ // src/cli/util/checks.ts
44965
45521
  function checkBinary() {
44966
45522
  return {
44967
45523
  name: "binary",
@@ -45012,7 +45568,7 @@ function checkConfig() {
45012
45568
  hint: "Run `cerefox init` to bootstrap."
45013
45569
  };
45014
45570
  }
45015
- if (!existsSync5(envPath)) {
45571
+ if (!existsSync8(envPath)) {
45016
45572
  return {
45017
45573
  name: "config",
45018
45574
  status: "error",
@@ -45136,11 +45692,24 @@ async function checkOpenAI() {
45136
45692
  };
45137
45693
  }
45138
45694
  }
45695
+ var SCHEMA_VERSION_RE = /^--\s*@version:\s*(\S+)/m;
45696
+ function readBundledSchemaVersion() {
45697
+ try {
45698
+ const assets = resolveServerAssets();
45699
+ if (!existsSync8(assets.schemaFile))
45700
+ return null;
45701
+ const m = readFileSync6(assets.schemaFile, "utf8").match(SCHEMA_VERSION_RE);
45702
+ return m ? m[1] : null;
45703
+ } catch {
45704
+ return null;
45705
+ }
45706
+ }
45707
+ var SCHEMA_CHECK_NAME = "schema + RPCs";
45139
45708
  async function checkSchemaVersion() {
45140
45709
  const settings = loadSettings();
45141
45710
  if (!settings.supabaseUrl || !settings.supabaseKey) {
45142
45711
  return {
45143
- name: "schema",
45712
+ name: SCHEMA_CHECK_NAME,
45144
45713
  status: "skipped",
45145
45714
  detail: "Supabase config missing; skipped."
45146
45715
  };
@@ -45158,31 +45727,50 @@ async function checkSchemaVersion() {
45158
45727
  });
45159
45728
  if (!resp.ok) {
45160
45729
  return {
45161
- name: "schema",
45730
+ name: SCHEMA_CHECK_NAME,
45162
45731
  status: "error",
45163
- detail: `cerefox_schema_version returned ${resp.status}`,
45164
- hint: "Deploy the schema: `uv run python scripts/db_deploy.py`"
45732
+ detail: `cerefox_schema_version returned ${resp.status} — schema not deployed.`,
45733
+ hint: "Deploy the schema + RPCs (see remediation below)."
45165
45734
  };
45166
45735
  }
45167
45736
  const deployed = await resp.json();
45168
- return {
45169
- name: "schema",
45170
- status: "ok",
45171
- detail: `cerefox_schema_version() → "${deployed}"`
45172
- };
45737
+ const bundled = readBundledSchemaVersion();
45738
+ const level = classifyCompat(deployed, COMPATIBILITY.minSchema, bundled);
45739
+ switch (level) {
45740
+ case "below-min":
45741
+ return {
45742
+ name: SCHEMA_CHECK_NAME,
45743
+ status: "error",
45744
+ detail: `Deployed schema v${deployed} is below the required minimum v${COMPATIBILITY.minSchema}.`,
45745
+ hint: "Update the schema + RPCs (see remediation below)."
45746
+ };
45747
+ case "above-min-but-old":
45748
+ return {
45749
+ name: SCHEMA_CHECK_NAME,
45750
+ status: "warn",
45751
+ detail: `Deployed schema v${deployed} works but is older than this client's bundled v${bundled}.`,
45752
+ hint: "Update the schema + RPCs (see remediation below)."
45753
+ };
45754
+ default:
45755
+ return {
45756
+ name: SCHEMA_CHECK_NAME,
45757
+ status: "ok",
45758
+ detail: `cerefox_schema_version() → "${deployed}"${bundled ? ` (bundled v${bundled})` : ""}`
45759
+ };
45760
+ }
45173
45761
  } catch (err) {
45174
45762
  return {
45175
- name: "schema",
45763
+ name: SCHEMA_CHECK_NAME,
45176
45764
  status: "error",
45177
45765
  detail: `Schema version probe failed: ${err instanceof Error ? err.message : String(err)}`
45178
45766
  };
45179
45767
  }
45180
45768
  }
45181
45769
  function hasCerefoxInJsonFile(path) {
45182
- if (!existsSync5(path))
45770
+ if (!existsSync8(path))
45183
45771
  return false;
45184
45772
  try {
45185
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
45773
+ const parsed = JSON.parse(readFileSync6(path, "utf8"));
45186
45774
  const mcpServers = parsed.mcpServers;
45187
45775
  return Boolean(mcpServers && typeof mcpServers === "object" && "cerefox" in mcpServers);
45188
45776
  } catch {
@@ -45191,9 +45779,9 @@ function hasCerefoxInJsonFile(path) {
45191
45779
  }
45192
45780
  function checkMcpConfigs() {
45193
45781
  const home = homedir4();
45194
- const claudeCodeUser = join5(home, ".claude.json");
45195
- const claudeCodeProj = join5(process.cwd(), ".mcp.json");
45196
- const claudeDesktop = process.platform === "darwin" ? join5(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join5(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join5(home, ".config", "Claude", "claude_desktop_config.json");
45782
+ const claudeCodeUser = join7(home, ".claude.json");
45783
+ const claudeCodeProj = join7(process.cwd(), ".mcp.json");
45784
+ const claudeDesktop = process.platform === "darwin" ? join7(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join7(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join7(home, ".config", "Claude", "claude_desktop_config.json");
45197
45785
  const found = [];
45198
45786
  if (hasCerefoxInJsonFile(claudeCodeUser))
45199
45787
  found.push("Claude Code (user)");
@@ -45217,9 +45805,9 @@ function checkMcpConfigs() {
45217
45805
  }
45218
45806
  function checkLegacyShadowEnv() {
45219
45807
  const home = homedir4();
45220
- const homeEnv = join5(home, USER_STATE_DIR_NAME, ".env");
45221
- const cwdEnv = join5(process.cwd(), ".env");
45222
- if (!existsSync5(homeEnv) || !existsSync5(cwdEnv))
45808
+ const homeEnv = join7(home, USER_STATE_DIR_NAME, ".env");
45809
+ const cwdEnv = join7(process.cwd(), ".env");
45810
+ if (!existsSync8(homeEnv) || !existsSync8(cwdEnv))
45223
45811
  return null;
45224
45812
  try {
45225
45813
  if (realpathSync(homeEnv) === realpathSync(cwdEnv))
@@ -45278,6 +45866,68 @@ async function checkPostgres() {
45278
45866
  await sql.end({ timeout: 1 }).catch(() => {});
45279
45867
  }
45280
45868
  }
45869
+ async function checkEdgeFunctionsCompat() {
45870
+ const settings = loadSettings();
45871
+ if (!settings.supabaseUrl) {
45872
+ return {
45873
+ name: "edge functions",
45874
+ status: "skipped",
45875
+ detail: "Supabase URL not set; EF version check skipped."
45876
+ };
45877
+ }
45878
+ if (!settings.supabaseAnonKey) {
45879
+ return {
45880
+ name: "edge functions",
45881
+ status: "skipped",
45882
+ detail: "No CEREFOX_SUPABASE_ANON_KEY set; EF version check skipped.",
45883
+ hint: "Set the legacy anon JWT (eyJ…) to enable client↔server version checks."
45884
+ };
45885
+ }
45886
+ let compat;
45887
+ try {
45888
+ compat = await checkServerCompatibility({
45889
+ aggregatorUrl: aggregatorUrlFor(settings.supabaseUrl),
45890
+ bearer: settings.supabaseAnonKey,
45891
+ bundledEf: PKG_VERSION
45892
+ });
45893
+ } catch (err) {
45894
+ return {
45895
+ name: "edge functions",
45896
+ status: "skipped",
45897
+ detail: `Version probe failed: ${err instanceof Error ? err.message : String(err)}`
45898
+ };
45899
+ }
45900
+ if (compat.efProbeSkipped) {
45901
+ return {
45902
+ name: "edge functions",
45903
+ status: "skipped",
45904
+ detail: compat.efSkipReason ?? "EF version check skipped."
45905
+ };
45906
+ }
45907
+ const deployed = compat.edgeFunctions.deployed ?? "unknown";
45908
+ switch (compat.edgeFunctions.level) {
45909
+ case "below-min":
45910
+ return {
45911
+ name: "edge functions",
45912
+ status: "error",
45913
+ detail: `Deployed EF v${deployed} is below the required minimum v${compat.edgeFunctions.min}.`,
45914
+ hint: "Update the Edge Functions (see remediation below)."
45915
+ };
45916
+ case "above-min-but-old":
45917
+ return {
45918
+ name: "edge functions",
45919
+ status: "warn",
45920
+ detail: `Deployed EF v${deployed} works but is older than this client (v${PKG_VERSION}).`,
45921
+ hint: "Update the Edge Functions (see remediation below)."
45922
+ };
45923
+ default:
45924
+ return {
45925
+ name: "edge functions",
45926
+ status: "ok",
45927
+ detail: `Deployed EF v${deployed} (≥ required v${compat.edgeFunctions.min}).`
45928
+ };
45929
+ }
45930
+ }
45281
45931
  async function runSteps(steps, opts) {
45282
45932
  const results = [];
45283
45933
  for (let i = 0;i < steps.length; i++) {
@@ -45303,7 +45953,8 @@ async function runAllChecks(opts = {}) {
45303
45953
  { name: "legacy env", phase: "Checking legacy env shadowing", run: () => checkLegacyShadowEnv() },
45304
45954
  { name: "supabase", phase: "Probing Supabase Data API", run: () => checkSupabase() },
45305
45955
  { name: "openai", phase: "Probing OpenAI embeddings", run: () => checkOpenAI() },
45306
- { name: "schema", phase: "Reading schema version", run: () => checkSchemaVersion() },
45956
+ { name: "schema + RPCs", phase: "Reading schema + RPC version", run: () => checkSchemaVersion() },
45957
+ { name: "edge functions", phase: "Probing Edge Function versions", run: () => checkEdgeFunctionsCompat() },
45307
45958
  { name: "postgres", phase: "Probing Postgres DDL endpoint", run: () => checkPostgres() },
45308
45959
  { name: "mcp clients", phase: "Scanning MCP client configs", run: () => checkMcpConfigs() }
45309
45960
  ];
@@ -45331,7 +45982,7 @@ function symbol(status) {
45331
45982
  return cErr.dim("ℹ");
45332
45983
  }
45333
45984
  }
45334
- async function action8(options) {
45985
+ async function action10(options) {
45335
45986
  const useSpinner = !options.json && process.stderr.isTTY;
45336
45987
  const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
45337
45988
  const results = await runAllChecks({
@@ -45355,6 +46006,26 @@ async function action8(options) {
45355
46006
  }
45356
46007
  println("");
45357
46008
  }
46009
+ if (!options.json) {
46010
+ const stale = (name) => {
46011
+ const r = results.find((x) => x.name === name);
46012
+ return r != null && (r.status === "error" || r.status === "warn");
46013
+ };
46014
+ const needsSchema = stale("schema + RPCs");
46015
+ const needsEf = stale("edge functions");
46016
+ let remediation = null;
46017
+ if (needsSchema && needsEf) {
46018
+ remediation = "Update the server (schema + RPCs + Edge Functions): cerefox deploy-server";
46019
+ } else if (needsSchema) {
46020
+ remediation = "Update the schema + RPCs: cerefox deploy-server --schema-only";
46021
+ } else if (needsEf) {
46022
+ remediation = "Update the Edge Functions: cerefox deploy-server --functions-only";
46023
+ }
46024
+ if (remediation) {
46025
+ println(cErr.yellow("→ " + remediation));
46026
+ println("");
46027
+ }
46028
+ }
45358
46029
  const errCount = results.filter((r) => r.status === "error").length;
45359
46030
  const warnCount = results.filter((r) => r.status === "warn").length;
45360
46031
  if (errCount > 0) {
@@ -45368,13 +46039,13 @@ async function action8(options) {
45368
46039
  }
45369
46040
  }
45370
46041
  function registerDoctor(program2) {
45371
- program2.command("doctor").description("Run diagnostic checks against the installed Cerefox.").option("--json", "Emit machine-readable JSON (no colours, structured output).").action(action8);
46042
+ program2.command("doctor").description("Run diagnostic checks against the installed Cerefox.").option("--json", "Emit machine-readable JSON (no colours, structured output).").action(action10);
45372
46043
  }
45373
46044
 
45374
46045
  // src/cli/commands/get-audit-log.ts
45375
46046
  init_cli_core();
45376
46047
  init_client();
45377
- async function action9(options) {
46048
+ async function action11(options) {
45378
46049
  const limit = parsePositiveInt(options.limit, "--limit", 50);
45379
46050
  const client = getClient();
45380
46051
  const data = await client.rpc("cerefox_list_audit_entries", {
@@ -45412,14 +46083,14 @@ async function action9(options) {
45412
46083
  })));
45413
46084
  }
45414
46085
  function registerGetAuditLog(program2) {
45415
- program2.command("get-audit-log").description("Query the audit log with optional filters.").option("-d, --document-id <uuid>", "Filter by document.").option("-a, --author <name>", "Filter by author.").option("-o, --operation <type>", "Filter by operation: create, update-content, update-metadata, delete, restore.").option("--since <iso>", "Lower-bound ISO timestamp.").option("--until <iso>", "Upper-bound ISO timestamp.").option("-l, --limit <n>", "Maximum entries (max 200).", "50").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action9);
46086
+ program2.command("get-audit-log").description("Query the audit log with optional filters.").option("-d, --document-id <uuid>", "Filter by document.").option("-a, --author <name>", "Filter by author.").option("-o, --operation <type>", "Filter by operation: create, update-content, update-metadata, delete, restore.").option("--since <iso>", "Lower-bound ISO timestamp.").option("--until <iso>", "Upper-bound ISO timestamp.").option("-l, --limit <n>", "Maximum entries (max 200).", "50").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action11);
45416
46087
  }
45417
46088
 
45418
46089
  // src/cli/commands/get-doc.ts
45419
46090
  init_cli_core();
45420
46091
  init_cli_core();
45421
46092
  init_client();
45422
- async function action10(documentId, options) {
46093
+ async function action12(documentId, options) {
45423
46094
  const client = getClient();
45424
46095
  const rows = await client.rpc("cerefox_get_document", {
45425
46096
  p_document_id: documentId,
@@ -45449,18 +46120,18 @@ async function action10(documentId, options) {
45449
46120
  println(doc.full_content);
45450
46121
  }
45451
46122
  function registerGetDoc(program2) {
45452
- program2.command("get-doc").description("Retrieve the full content of a document by ID.").argument("<document-id>", "UUID of the document.").option("--version-id <uuid>", "Specific archived version (default: current).").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action10);
46123
+ program2.command("get-doc").description("Retrieve the full content of a document by ID.").argument("<document-id>", "UUID of the document.").option("--version-id <uuid>", "Specific archived version (default: current).").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action12);
45453
46124
  }
45454
46125
 
45455
46126
  // src/cli/commands/ingest.ts
45456
46127
  init_dist4();
45457
46128
  init_cli_core();
45458
46129
  init_config();
45459
- import { readFileSync as readFileSync7 } from "node:fs";
46130
+ import { readFileSync as readFileSync8 } from "node:fs";
45460
46131
  import { basename as basename2, extname as extname2 } from "node:path";
45461
46132
 
45462
46133
  // src/ingestion/pipeline.ts
45463
- import { readFileSync as readFileSync6 } from "node:fs";
46134
+ import { readFileSync as readFileSync7 } from "node:fs";
45464
46135
  import { basename, extname, resolve as resolve3 } from "node:path";
45465
46136
 
45466
46137
  // ../../_shared/ingest/chunker.ts
@@ -46144,7 +46815,7 @@ ${c2.content}`);
46144
46815
  };
46145
46816
  }
46146
46817
  async ingestFile(path, opts = {}) {
46147
- const text = readFileSync6(path, "utf8");
46818
+ const text = readFileSync7(path, "utf8");
46148
46819
  const absPath = resolve3(path);
46149
46820
  const stem = basename(absPath, extname(absPath));
46150
46821
  return this.ingestText({
@@ -46178,7 +46849,7 @@ async function readContent(path, paste) {
46178
46849
  }
46179
46850
  let content;
46180
46851
  try {
46181
- content = readFileSync7(path, "utf8");
46852
+ content = readFileSync8(path, "utf8");
46182
46853
  } catch (err) {
46183
46854
  const msg = err instanceof Error ? err.message : String(err);
46184
46855
  throw userError(`Cannot read ${path}: ${msg}`);
@@ -46186,7 +46857,7 @@ async function readContent(path, paste) {
46186
46857
  const titleFromPath = basename2(path, extname2(path));
46187
46858
  return { content, titleFromPath };
46188
46859
  }
46189
- async function action11(path, options) {
46860
+ async function action13(path, options) {
46190
46861
  const { content, titleFromPath } = await readContent(path, Boolean(options.paste));
46191
46862
  const title = options.title ?? titleFromPath;
46192
46863
  if (!title || title.trim() === "") {
@@ -46258,7 +46929,7 @@ async function action11(path, options) {
46258
46929
  }
46259
46930
  }
46260
46931
  function registerIngest(program2) {
46261
- program2.command("ingest").description("Ingest a file (or stdin paste) into the knowledge base.").argument("[path]", "Path to the file to ingest. Omit when using --paste.").option("--paste", "Read content from stdin instead of a file.").option("-t, --title <title>", "Document title (required with --paste; defaults to filename without extension).").option("-p, --project-name <name>", "Single project membership (non-destructive on update).").option("-P, --project-names <names>", "Comma-separated full project membership set (destructive replace on update).").option("-m, --metadata <json>", "JSON metadata object.").option("--source <label>", "Origin label (default: cli).", "cli").option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-i, --document-id <uuid>", "Update a specific document by UUID (overrides --update-if-exists).").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action11);
46932
+ program2.command("ingest").description("Ingest a file (or stdin paste) into the knowledge base.").argument("[path]", "Path to the file to ingest. Omit when using --paste.").option("--paste", "Read content from stdin instead of a file.").option("-t, --title <title>", "Document title (required with --paste; defaults to filename without extension).").option("-p, --project-name <name>", "Single project membership (non-destructive on update).").option("-P, --project-names <names>", "Comma-separated full project membership set (destructive replace on update).").option("-m, --metadata <json>", "JSON metadata object.").option("--source <label>", "Origin label (default: cli).", "cli").option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-i, --document-id <uuid>", "Update a specific document by UUID (overrides --update-if-exists).").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action13);
46262
46933
  }
46263
46934
 
46264
46935
  // src/cli/commands/ingest-dir.ts
@@ -46266,18 +46937,18 @@ init_dist4();
46266
46937
  init_cli_core();
46267
46938
  init_config();
46268
46939
  var import_cli_progress = __toESM(require_cli_progress(), 1);
46269
- import { readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
46270
- import { basename as basename3, extname as extname3, join as join6 } from "node:path";
46940
+ import { readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
46941
+ import { basename as basename3, extname as extname3, join as join8 } from "node:path";
46271
46942
  function walk(dir, extensions) {
46272
46943
  let entries;
46273
46944
  try {
46274
- entries = readdirSync2(dir);
46945
+ entries = readdirSync4(dir);
46275
46946
  } catch (err) {
46276
46947
  throw userError(`Cannot read directory ${dir}: ${err instanceof Error ? err.message : String(err)}`);
46277
46948
  }
46278
46949
  const files = [];
46279
46950
  for (const name of entries) {
46280
- const full = join6(dir, name);
46951
+ const full = join8(dir, name);
46281
46952
  let stat;
46282
46953
  try {
46283
46954
  stat = statSync3(full);
@@ -46294,7 +46965,7 @@ function walk(dir, extensions) {
46294
46965
  }
46295
46966
  return files;
46296
46967
  }
46297
- async function action12(dir, options) {
46968
+ async function action14(dir, options) {
46298
46969
  const extensions = new Set((options.extensions ?? ".md,.txt").split(",").map((e) => e.trim().toLowerCase()).map((e) => e.startsWith(".") ? e : "." + e).filter((e) => e.length > 0));
46299
46970
  const files = walk(dir, extensions);
46300
46971
  if (files.length === 0) {
@@ -46367,29 +47038,30 @@ async function action12(dir, options) {
46367
47038
  }
46368
47039
  }
46369
47040
  function registerIngestDir(program2) {
46370
- program2.command("ingest-dir").description("Recursively ingest a directory of markdown / text files.").argument("<dir>", "Root directory to walk.").option("-p, --project-name <name>", "Project membership for all ingested docs.").option("-m, --metadata <json>", "JSON metadata applied to every doc.").option("--source <label>", "Origin label (default: cli).", "cli").option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("-e, --extensions <list>", "Comma-separated file extensions to ingest.", ".md,.txt").action(action12);
47041
+ program2.command("ingest-dir").description("Recursively ingest a directory of markdown / text files.").argument("<dir>", "Root directory to walk.").option("-p, --project-name <name>", "Project membership for all ingested docs.").option("-m, --metadata <json>", "JSON metadata applied to every doc.").option("--source <label>", "Origin label (default: cli).", "cli").option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("-e, --extensions <list>", "Comma-separated file extensions to ingest.", ".md,.txt").action(action14);
46371
47042
  }
46372
47043
 
46373
47044
  // src/cli/commands/init.ts
46374
47045
  init_cli_core();
46375
47046
  init_config();
47047
+ import { spawnSync as spawnSync4 } from "node:child_process";
46376
47048
  import {
46377
47049
  chmodSync,
46378
47050
  copyFileSync as copyFileSync2,
46379
- existsSync as existsSync6,
47051
+ existsSync as existsSync9,
46380
47052
  mkdirSync as mkdirSync3,
46381
- readFileSync as readFileSync9,
47053
+ readFileSync as readFileSync10,
46382
47054
  writeFileSync as writeFileSync3
46383
47055
  } from "node:fs";
46384
47056
  import { homedir as homedir5 } from "node:os";
46385
- import { dirname as dirname3, join as join7 } from "node:path";
47057
+ import { dirname as dirname4, join as join9 } from "node:path";
46386
47058
  async function readConfigFile(path) {
46387
- if (!existsSync6(path)) {
47059
+ if (!existsSync9(path)) {
46388
47060
  throw userError(`--config file not found: ${path}`);
46389
47061
  }
46390
47062
  let parsed;
46391
47063
  try {
46392
- parsed = JSON.parse(readFileSync9(path, "utf8"));
47064
+ parsed = JSON.parse(readFileSync10(path, "utf8"));
46393
47065
  } catch (err) {
46394
47066
  throw userError(`--config: invalid JSON in ${path}: ${err instanceof Error ? err.message : String(err)}`);
46395
47067
  }
@@ -46431,7 +47103,7 @@ function parseDotEnvFile(content) {
46431
47103
  return map;
46432
47104
  }
46433
47105
  function answersFromEnvFile(path) {
46434
- const parsed = parseDotEnvFile(readFileSync9(path, "utf8"));
47106
+ const parsed = parseDotEnvFile(readFileSync10(path, "utf8"));
46435
47107
  const required = ["CEREFOX_SUPABASE_URL", "CEREFOX_SUPABASE_KEY", "OPENAI_API_KEY"];
46436
47108
  for (const key of required) {
46437
47109
  if (!parsed[key] || parsed[key].trim() === "") {
@@ -46592,14 +47264,60 @@ async function promptMigrationChoice() {
46592
47264
  const ch = choice.trim().toLowerCase() || "c";
46593
47265
  return ch;
46594
47266
  }
46595
- async function postWriteLifecycle(envPath, options) {
46596
- if (!options.skipSchema) {
47267
+ async function probeSchemaVersion(url, key) {
47268
+ try {
47269
+ const resp = await fetch(`${url.replace(/\/$/, "")}/rest/v1/rpc/cerefox_schema_version`, {
47270
+ method: "POST",
47271
+ headers: { "Content-Type": "application/json", apikey: key, Authorization: `Bearer ${key}` },
47272
+ body: "{}"
47273
+ });
47274
+ if (!resp.ok)
47275
+ return null;
47276
+ const data = await resp.json();
47277
+ return typeof data === "string" ? data : null;
47278
+ } catch {
47279
+ return null;
47280
+ }
47281
+ }
47282
+ function launchDeployServer(extraArgs = []) {
47283
+ const r = spawnSync4(process.execPath, [process.argv[1], "deploy-server", ...extraArgs], {
47284
+ stdio: "inherit"
47285
+ });
47286
+ return r.status ?? 1;
47287
+ }
47288
+ async function maybeOfferServerDeploy() {
47289
+ const settings = loadSettings();
47290
+ if (!settings.supabaseUrl || !settings.supabaseKey)
47291
+ return;
47292
+ const deployed = await probeSchemaVersion(settings.supabaseUrl, settings.supabaseKey);
47293
+ if (deployed === null) {
47294
+ println(c.bold("Schema deploy"));
47295
+ println(c.dim(" No Cerefox schema detected on this Supabase project."));
47296
+ const yes = await confirm(" Deploy the server now (schema + RPCs + Edge Functions)?", false);
47297
+ if (yes)
47298
+ launchDeployServer();
47299
+ else
47300
+ println(c.dim(" Skipped. Run `cerefox deploy-server` later (or `cerefox doctor` to recheck)."));
47301
+ println("");
47302
+ return;
47303
+ }
47304
+ if (compareSemver(deployed, COMPATIBILITY.minSchema) < 0) {
46597
47305
  println(c.bold("Schema deploy"));
46598
- println(c.dim(` v0.5 doesn't yet bundle the schema-deploy path (it needs the direct
46599
- ` + " Postgres connection ported, scheduled for v0.6). For now:"));
46600
- println(c.cyan(" uv run python scripts/db_deploy.py"));
46601
- println(c.dim(" Skip this if your Supabase already has the schema."));
47306
+ println(c.yellow(` Deployed schema v${deployed} is below the required v${COMPATIBILITY.minSchema}.`));
47307
+ const yes = await confirm(" Update the server now (applies pending migrations, refreshes RPCs + EFs)?", true);
47308
+ if (yes)
47309
+ launchDeployServer();
47310
+ else
47311
+ println(c.dim(" Skipped. Run `cerefox deploy-server` when ready."));
46602
47312
  println("");
47313
+ return;
47314
+ }
47315
+ println(c.dim(`Schema v${deployed} already deployed (≥ required v${COMPATIBILITY.minSchema}).`));
47316
+ println("");
47317
+ }
47318
+ async function postWriteLifecycle(envPath, options) {
47319
+ if (!options.skipSchema) {
47320
+ await maybeOfferServerDeploy();
46603
47321
  }
46604
47322
  if (!options.skipSelfDocs) {
46605
47323
  println(c.bold("Self-doc ingest"));
@@ -46640,7 +47358,7 @@ async function postWriteLifecycle(envPath, options) {
46640
47358
  println(c.dim(` Config in effect: ${envPath}`));
46641
47359
  }
46642
47360
  function writeAnswersTo(target, answers) {
46643
- mkdirSync3(dirname3(target), { recursive: true });
47361
+ mkdirSync3(dirname4(target), { recursive: true });
46644
47362
  writeFileSync3(target, buildEnvFile(answers), "utf8");
46645
47363
  if (process.platform !== "win32") {
46646
47364
  try {
@@ -46650,13 +47368,13 @@ function writeAnswersTo(target, answers) {
46650
47368
  }
46651
47369
  }
46652
47370
  }
46653
- async function action14(options) {
46654
- const homeEnv = join7(homedir5(), USER_STATE_DIR_NAME, ".env");
46655
- const cwdEnv = join7(process.cwd(), ".env");
47371
+ async function action16(options) {
47372
+ const homeEnv = join9(homedir5(), USER_STATE_DIR_NAME, ".env");
47373
+ const cwdEnv = join9(process.cwd(), ".env");
46656
47374
  const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
46657
47375
  if (explicitDir) {
46658
47376
  const target2 = resolveEnvFile();
46659
- if (existsSync6(target2) && !options.force) {
47377
+ if (existsSync9(target2) && !options.force) {
46660
47378
  println(c.yellow(`⚠ Config already exists at ${target2}.`));
46661
47379
  const ok2 = await confirm("Overwrite?", true);
46662
47380
  if (!ok2) {
@@ -46678,7 +47396,7 @@ async function action14(options) {
46678
47396
  await postWriteLifecycle(target2, options);
46679
47397
  return;
46680
47398
  }
46681
- if (existsSync6(homeEnv) && !options.force) {
47399
+ if (existsSync9(homeEnv) && !options.force) {
46682
47400
  println(c.yellow(`⚠ Config already exists at ${homeEnv}.`));
46683
47401
  const ok2 = await confirm("Overwrite?", true);
46684
47402
  if (!ok2) {
@@ -46699,12 +47417,12 @@ async function action14(options) {
46699
47417
  await postWriteLifecycle(homeEnv, options);
46700
47418
  return;
46701
47419
  }
46702
- if (existsSync6(cwdEnv) && !options.force && !options.config) {
47420
+ if (existsSync9(cwdEnv) && !options.force && !options.config) {
46703
47421
  printMigrationMenu(cwdEnv, homeEnv);
46704
47422
  const ch = await promptMigrationChoice();
46705
47423
  println("");
46706
47424
  if (ch === "c") {
46707
- mkdirSync3(dirname3(homeEnv), { recursive: true });
47425
+ mkdirSync3(dirname4(homeEnv), { recursive: true });
46708
47426
  copyFileSync2(cwdEnv, homeEnv);
46709
47427
  if (process.platform !== "win32") {
46710
47428
  try {
@@ -46759,13 +47477,13 @@ async function action14(options) {
46759
47477
  await postWriteLifecycle(target, options);
46760
47478
  }
46761
47479
  function registerInit(program2) {
46762
- program2.command("init").description("Interactive first-run setup (config, schema deploy stub, optional MCP wiring).").option("-c, --config <file>", "Non-interactive mode: read answers from a JSON file.").option("--force", "Overwrite existing configuration without prompting.").option("--skip-schema", "Skip the schema deploy step.").option("--skip-self-docs", "Skip the bundled self-doc ingest.").option("--skip-agent-config", "Skip the optional MCP agent wiring.").action(action14);
47480
+ program2.command("init").description("Interactive first-run setup (config, schema deploy stub, optional MCP wiring).").option("-c, --config <file>", "Non-interactive mode: read answers from a JSON file.").option("--force", "Overwrite existing configuration without prompting.").option("--skip-schema", "Skip the schema deploy step.").option("--skip-self-docs", "Skip the bundled self-doc ingest.").option("--skip-agent-config", "Skip the optional MCP agent wiring.").action(action16);
46763
47481
  }
46764
47482
 
46765
47483
  // src/cli/commands/list-docs.ts
46766
47484
  init_cli_core();
46767
47485
  init_client();
46768
- async function action15(options) {
47486
+ async function action17(options) {
46769
47487
  const limit = parsePositiveInt(options.limit, "--limit", 100);
46770
47488
  const client = getClient();
46771
47489
  let projectId;
@@ -46820,13 +47538,13 @@ async function action15(options) {
46820
47538
  })));
46821
47539
  }
46822
47540
  function registerListDocs(program2) {
46823
- program2.command("list-docs").description("List documents in the knowledge base.").option("-p, --project <name>", "Filter to a specific project.").option("-l, --limit <n>", "Maximum docs to return.", "100").option("--json", "Emit machine-readable JSON.").action(action15);
47541
+ program2.command("list-docs").description("List documents in the knowledge base.").option("-p, --project <name>", "Filter to a specific project.").option("-l, --limit <n>", "Maximum docs to return.", "100").option("--json", "Emit machine-readable JSON.").action(action17);
46824
47542
  }
46825
47543
 
46826
47544
  // src/cli/commands/list-metadata-keys.ts
46827
47545
  init_cli_core();
46828
47546
  init_client();
46829
- async function action16(options) {
47547
+ async function action18(options) {
46830
47548
  const client = getClient();
46831
47549
  const data = await client.rpc("cerefox_list_metadata_keys");
46832
47550
  if (data === null) {
@@ -46854,13 +47572,13 @@ async function action16(options) {
46854
47572
  })));
46855
47573
  }
46856
47574
  function registerListMetadataKeys(program2) {
46857
- program2.command("list-metadata-keys").description("List all metadata keys with document counts and example values.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action16);
47575
+ program2.command("list-metadata-keys").description("List all metadata keys with document counts and example values.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action18);
46858
47576
  }
46859
47577
 
46860
47578
  // src/cli/commands/list-projects.ts
46861
47579
  init_cli_core();
46862
47580
  init_client();
46863
- async function action17(options) {
47581
+ async function action19(options) {
46864
47582
  const client = getClient();
46865
47583
  const { data, error: error2 } = await client.raw.from("cerefox_projects").select("id, name, description, created_at").order("name", { ascending: true });
46866
47584
  if (error2) {
@@ -46889,13 +47607,13 @@ async function action17(options) {
46889
47607
  })), "(no projects)");
46890
47608
  }
46891
47609
  function registerListProjects(program2) {
46892
- program2.command("list-projects").description("List all projects in the knowledge base.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action17);
47610
+ program2.command("list-projects").description("List all projects in the knowledge base.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action19);
46893
47611
  }
46894
47612
 
46895
47613
  // src/cli/commands/list-versions.ts
46896
47614
  init_cli_core();
46897
47615
  init_client();
46898
- async function action18(documentId, options) {
47616
+ async function action20(documentId, options) {
46899
47617
  const client = getClient();
46900
47618
  const data = await client.rpc("cerefox_list_document_versions", {
46901
47619
  p_document_id: documentId
@@ -46936,7 +47654,7 @@ async function action18(documentId, options) {
46936
47654
  })));
46937
47655
  }
46938
47656
  function registerListVersions(program2) {
46939
- program2.command("list-versions").description("List archived versions of a document.").argument("<document-id>", "UUID of the document.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action18);
47657
+ program2.command("list-versions").description("List archived versions of a document.").argument("<document-id>", "UUID of the document.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action20);
46940
47658
  }
46941
47659
 
46942
47660
  // src/cli/commands/mcp.ts
@@ -46951,7 +47669,7 @@ function registerMcp(program2) {
46951
47669
  // src/cli/commands/metadata-search.ts
46952
47670
  init_cli_core();
46953
47671
  init_client();
46954
- async function action19(options) {
47672
+ async function action21(options) {
46955
47673
  const metadataFilter = parseJsonObjectArg(options.metadataFilter, "--metadata-filter");
46956
47674
  if (!metadataFilter || Object.keys(metadataFilter).length === 0) {
46957
47675
  throw userError("--metadata-filter is required and must be a non-empty JSON object.", `Example: --metadata-filter '{"type":"decision-log"}'.`);
@@ -47014,7 +47732,7 @@ async function action19(options) {
47014
47732
  }
47015
47733
  }
47016
47734
  function registerMetadataSearch(program2) {
47017
- program2.command("metadata-search").description("Find documents by metadata criteria (no text query).").requiredOption("-f, --metadata-filter <json>", "JSON object; only docs whose metadata contains ALL pairs are returned.").option("-p, --project-name <name>", "Filter to a specific project.").option("--updated-since <iso>", "Only docs updated on/after this ISO timestamp.").option("--created-since <iso>", "Only docs created on/after this ISO timestamp.").option("--include-content", "Include full document text in results.").option("-l, --limit <n>", "Maximum docs to return.", "10").option("--max-bytes <n>", "Response size budget in bytes (with --include-content).", "200000").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action19);
47735
+ program2.command("metadata-search").description("Find documents by metadata criteria (no text query).").requiredOption("-f, --metadata-filter <json>", "JSON object; only docs whose metadata contains ALL pairs are returned.").option("-p, --project-name <name>", "Filter to a specific project.").option("--updated-since <iso>", "Only docs updated on/after this ISO timestamp.").option("--created-since <iso>", "Only docs created on/after this ISO timestamp.").option("--include-content", "Include full document text in results.").option("-l, --limit <n>", "Maximum docs to return.", "10").option("--max-bytes <n>", "Response size budget in bytes (with --include-content).", "200000").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action21);
47018
47736
  }
47019
47737
 
47020
47738
  // src/cli/commands/reindex.ts
@@ -47022,7 +47740,7 @@ init_dist4();
47022
47740
  init_cli_core();
47023
47741
  init_config();
47024
47742
  var DEFAULT_MODEL = "text-embedding-3-small";
47025
- async function action20(options) {
47743
+ async function action22(options) {
47026
47744
  const settings = loadSettings();
47027
47745
  if (!settings.supabaseUrl || !settings.supabaseKey) {
47028
47746
  throw userError("Supabase credentials not configured — run `cerefox init` first.");
@@ -47101,41 +47819,41 @@ ${c2.content}`;
47101
47819
  }
47102
47820
  }
47103
47821
  function registerReindex(program2) {
47104
- program2.command("reindex").description("Re-embed existing document chunks (v0.7+).").option("--all", "Reindex every chunk regardless of embedder.").option("--batch <n>", "Chunks per OpenAI batch call. Capped at 96 internally.", "32").option("--dry-run", "Show counts without re-embedding.").option("-i, --document-id <uuid>", "Limit reindex to a single document.").action(action20);
47822
+ program2.command("reindex").description("Re-embed existing document chunks (v0.7+).").option("--all", "Reindex every chunk regardless of embedder.").option("--batch <n>", "Chunks per OpenAI batch call. Capped at 96 internally.", "32").option("--dry-run", "Show counts without re-embedding.").option("-i, --document-id <uuid>", "Limit reindex to a single document.").action(action22);
47105
47823
  }
47106
47824
 
47107
47825
  // src/cli/commands/restore.ts
47108
47826
  init_cli_core();
47109
47827
  init_client();
47110
- import { existsSync as existsSync7, readFileSync as readFileSync10, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
47828
+ import { existsSync as existsSync10, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
47111
47829
  import { homedir as homedir6 } from "node:os";
47112
- import { join as join8, resolve as resolve4 } from "node:path";
47830
+ import { join as join10, resolve as resolve4 } from "node:path";
47113
47831
  function expandHome2(path) {
47114
47832
  if (path === "~")
47115
47833
  return homedir6();
47116
47834
  if (path.startsWith("~/"))
47117
- return join8(homedir6(), path.slice(2));
47835
+ return join10(homedir6(), path.slice(2));
47118
47836
  return path;
47119
47837
  }
47120
47838
  function resolveBackupFile(target) {
47121
47839
  const path = resolve4(expandHome2(target));
47122
- if (!existsSync7(path)) {
47840
+ if (!existsSync10(path)) {
47123
47841
  throw userError(`Backup path not found: ${target}`);
47124
47842
  }
47125
47843
  const stat = statSync4(path);
47126
47844
  if (stat.isFile())
47127
47845
  return path;
47128
- const candidates = readdirSync3(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync4(join8(path, n)).mtimeMs })).sort((a, b2) => b2.mtime - a.mtime);
47846
+ const candidates = readdirSync5(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync4(join10(path, n)).mtimeMs })).sort((a, b2) => b2.mtime - a.mtime);
47129
47847
  if (candidates.length === 0) {
47130
47848
  throw userError(`No cerefox-*.json files in ${path}`);
47131
47849
  }
47132
- return join8(path, candidates[0].name);
47850
+ return join10(path, candidates[0].name);
47133
47851
  }
47134
- async function action21(target, options) {
47852
+ async function action23(target, options) {
47135
47853
  const file = resolveBackupFile(target);
47136
47854
  let payload;
47137
47855
  try {
47138
- payload = JSON.parse(readFileSync10(file, "utf8"));
47856
+ payload = JSON.parse(readFileSync11(file, "utf8"));
47139
47857
  } catch (err) {
47140
47858
  throw userError(`Could not parse backup file ${file}: ${err instanceof Error ? err.message : String(err)}`);
47141
47859
  }
@@ -47187,7 +47905,7 @@ async function action21(target, options) {
47187
47905
  }
47188
47906
  }
47189
47907
  function registerRestore(program2) {
47190
- program2.command("restore").description("Restore a JSON-snapshot backup into the knowledge base.").argument("<snapshot>", "Backup file (or directory; most recent is picked) produced by `cerefox backup`.").option("--dry-run", "Print what would be restored without writing.").option("-p, --project-name <name>", "Reserved for future use; currently ignored (project memberships ride along with each doc's metadata).").action(action21);
47908
+ program2.command("restore").description("Restore a JSON-snapshot backup into the knowledge base.").argument("<snapshot>", "Backup file (or directory; most recent is picked) produced by `cerefox backup`.").option("--dry-run", "Print what would be restored without writing.").option("-p, --project-name <name>", "Reserved for future use; currently ignored (project memberships ride along with each doc's metadata).").action(action23);
47191
47909
  }
47192
47910
 
47193
47911
  // src/cli/commands/search.ts
@@ -47212,7 +47930,7 @@ async function embedQuery(query) {
47212
47930
  }
47213
47931
 
47214
47932
  // src/cli/commands/search.ts
47215
- async function action22(query, options) {
47933
+ async function action24(query, options) {
47216
47934
  if (!query || query.trim() === "") {
47217
47935
  throw userError("Empty query.");
47218
47936
  }
@@ -47346,13 +48064,13 @@ async function action22(query, options) {
47346
48064
  }
47347
48065
  }
47348
48066
  function registerSearch(program2) {
47349
- program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: 0.7).", "0.7").option("--min-score <float>", "Minimum cosine similarity threshold.", "0.5").option("--max-bytes <n>", "Response size budget in bytes.", "200000").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").action(action22);
48067
+ program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: 0.7).", "0.7").option("--min-score <float>", "Minimum cosine similarity threshold.", "0.5").option("--max-bytes <n>", "Response size budget in bytes.", "200000").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").action(action24);
47350
48068
  }
47351
48069
 
47352
48070
  // src/cli/commands/self-update.ts
47353
48071
  init_cli_core();
47354
48072
  init_meta();
47355
- import { spawnSync as spawnSync3 } from "node:child_process";
48073
+ import { spawnSync as spawnSync5 } from "node:child_process";
47356
48074
  function detectRuntime() {
47357
48075
  const bin = (process.argv[1] ?? "").toLowerCase();
47358
48076
  if (bin.includes(".bun") || bin.includes("/bun/")) {
@@ -47393,7 +48111,7 @@ async function fetchLatestVersion() {
47393
48111
  }
47394
48112
  return body.version;
47395
48113
  }
47396
- async function action23(options) {
48114
+ async function action25(options) {
47397
48115
  let target;
47398
48116
  try {
47399
48117
  target = options.version ?? await fetchLatestVersion();
@@ -47425,7 +48143,7 @@ async function action23(options) {
47425
48143
  return;
47426
48144
  }
47427
48145
  }
47428
- const result = spawnSync3(runtime.command, runtime.args(target), {
48146
+ const result = spawnSync5(runtime.command, runtime.args(target), {
47429
48147
  stdio: "inherit"
47430
48148
  });
47431
48149
  if (result.status !== 0) {
@@ -47445,7 +48163,7 @@ async function action23(options) {
47445
48163
  }
47446
48164
  function registerSelfUpdate(program2) {
47447
48165
  const desc = "Upgrade Cerefox in place. Alias: `cerefox upgrade`.";
47448
- const declaration = (cmd) => cmd.description(desc).option("--check", "Print current vs latest; do nothing.").option("--yes", "Non-interactive (skip confirmation).").option("--version <version>", "Pin a specific version (e.g. 0.5.1 or 0.6.0-rc.1).").action(action23);
48166
+ const declaration = (cmd) => cmd.description(desc).option("--check", "Print current vs latest; do nothing.").option("--yes", "Non-interactive (skip confirmation).").option("--version <version>", "Pin a specific version (e.g. 0.5.1 or 0.6.0-rc.1).").action(action25);
47449
48167
  declaration(program2.command("self-update"));
47450
48168
  declaration(program2.command("upgrade"));
47451
48169
  }
@@ -47464,7 +48182,7 @@ function symbol2(status) {
47464
48182
  return cErr.dim("ℹ");
47465
48183
  }
47466
48184
  }
47467
- async function action24(options) {
48185
+ async function action26(options) {
47468
48186
  const useSpinner = !options.json && process.stderr.isTTY;
47469
48187
  const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
47470
48188
  const results = await runFastChecks({
@@ -47485,7 +48203,7 @@ async function action24(options) {
47485
48203
  }
47486
48204
  }
47487
48205
  function registerStatus(program2) {
47488
- program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(action24);
48206
+ program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(action26);
47489
48207
  }
47490
48208
 
47491
48209
  // src/cli/commands/sync-docs.ts
@@ -47494,19 +48212,19 @@ init_mcp_tools();
47494
48212
  init_config();
47495
48213
  init_client();
47496
48214
  import {
47497
- existsSync as existsSync8,
47498
- readFileSync as readFileSync11,
47499
- readdirSync as readdirSync4,
48215
+ existsSync as existsSync11,
48216
+ readFileSync as readFileSync12,
48217
+ readdirSync as readdirSync6,
47500
48218
  statSync as statSync5
47501
48219
  } from "node:fs";
47502
- import { basename as basename5, extname as extname5, join as join9, relative } from "node:path";
48220
+ import { basename as basename5, extname as extname5, join as join11, relative } from "node:path";
47503
48221
  var ROOT_LEVEL_DOCS = ["README.md", "AGENT_GUIDE.md", "AGENT_QUICK_REFERENCE.md"];
47504
48222
  function walkMarkdown(dir) {
47505
48223
  const out = [];
47506
- if (!existsSync8(dir))
48224
+ if (!existsSync11(dir))
47507
48225
  return out;
47508
- for (const name of readdirSync4(dir)) {
47509
- const full = join9(dir, name);
48226
+ for (const name of readdirSync6(dir)) {
48227
+ const full = join11(dir, name);
47510
48228
  let stat;
47511
48229
  try {
47512
48230
  stat = statSync5(full);
@@ -47521,16 +48239,16 @@ function walkMarkdown(dir) {
47521
48239
  }
47522
48240
  return out;
47523
48241
  }
47524
- async function action25(options) {
48242
+ async function action27(options) {
47525
48243
  const cwd = process.cwd();
47526
48244
  const project = options.project ?? "cerefox";
47527
48245
  const targets = [];
47528
48246
  for (const rel of ROOT_LEVEL_DOCS) {
47529
- const abs = join9(cwd, rel);
47530
- if (existsSync8(abs))
48247
+ const abs = join11(cwd, rel);
48248
+ if (existsSync11(abs))
47531
48249
  targets.push({ abs, rel });
47532
48250
  }
47533
- for (const abs of walkMarkdown(join9(cwd, "docs"))) {
48251
+ for (const abs of walkMarkdown(join11(cwd, "docs"))) {
47534
48252
  targets.push({ abs, rel: relative(cwd, abs) });
47535
48253
  }
47536
48254
  if (targets.length === 0) {
@@ -47554,7 +48272,7 @@ async function action25(options) {
47554
48272
  const authorType = resolveAuthorType("agent");
47555
48273
  const outcomes = [];
47556
48274
  for (const t of targets) {
47557
- const content = readFileSync11(t.abs, "utf8");
48275
+ const content = readFileSync12(t.abs, "utf8");
47558
48276
  const title = basename5(t.abs, extname5(t.abs));
47559
48277
  try {
47560
48278
  const message = await ingestTool.handler(client.raw, {
@@ -47583,7 +48301,7 @@ async function action25(options) {
47583
48301
  }
47584
48302
  }
47585
48303
  function registerSyncDocs(program2) {
47586
- program2.command("sync-docs").description("Sync repo docs (README, AGENT_*, docs/) into a Cerefox project.").option("--dry-run", "Print what would be synced without writing.").option("-p, --project <name>", "Target project for the sync.", "cerefox").action(action25);
48304
+ program2.command("sync-docs").description("Sync repo docs (README, AGENT_*, docs/) into a Cerefox project.").option("--dry-run", "Print what would be synced without writing.").option("-p, --project <name>", "Target project for the sync.", "cerefox").action(action27);
47587
48305
  }
47588
48306
 
47589
48307
  // src/cli/program.ts
@@ -48918,8 +49636,8 @@ var _baseMimes = {
48918
49636
  var baseMimes = _baseMimes;
48919
49637
 
48920
49638
  // ../../node_modules/.bun/@hono+node-server@2.0.4+1bbe96acb4c5ebf1/node_modules/@hono/node-server/dist/serve-static.mjs
48921
- import { createReadStream, existsSync as existsSync9, statSync as statSync6 } from "node:fs";
48922
- import { join as join10 } from "node:path";
49639
+ import { createReadStream, existsSync as existsSync12, statSync as statSync6 } from "node:fs";
49640
+ import { join as join12 } from "node:path";
48923
49641
  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;
48924
49642
  var ENCODINGS = {
48925
49643
  br: ".br",
@@ -48951,7 +49669,7 @@ var tryDecodeURI = (str) => tryDecode(str, decodeURI);
48951
49669
  var serveStatic = (options = { root: "" }) => {
48952
49670
  const root = options.root || "";
48953
49671
  const optionPath = options.path;
48954
- if (root !== "" && !existsSync9(root))
49672
+ if (root !== "" && !existsSync12(root))
48955
49673
  console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
48956
49674
  return async (c2, next) => {
48957
49675
  if (c2.finalized)
@@ -48968,11 +49686,11 @@ var serveStatic = (options = { root: "" }) => {
48968
49686
  await options.onNotFound?.(c2.req.path, c2);
48969
49687
  return next();
48970
49688
  }
48971
- let path = join10(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c2) : filename);
49689
+ let path = join12(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c2) : filename);
48972
49690
  let stats = getStats(path);
48973
49691
  if (stats && stats.isDirectory()) {
48974
49692
  const indexFile = options.index ?? "index.html";
48975
- path = join10(path, indexFile);
49693
+ path = join12(path, indexFile);
48976
49694
  stats = getStats(path);
48977
49695
  }
48978
49696
  if (!stats) {
@@ -49029,9 +49747,9 @@ var serveStatic = (options = { root: "" }) => {
49029
49747
  };
49030
49748
 
49031
49749
  // src/web/server.ts
49032
- import { existsSync as existsSync12 } from "node:fs";
49033
- import { readFileSync as readFileSync13 } from "node:fs";
49034
- import { join as join13 } from "node:path";
49750
+ import { existsSync as existsSync15 } from "node:fs";
49751
+ import { readFileSync as readFileSync14 } from "node:fs";
49752
+ import { join as join15 } from "node:path";
49035
49753
 
49036
49754
  // ../../node_modules/.bun/hono@4.12.23/node_modules/hono/dist/compose.js
49037
49755
  var compose = (middleware, onError, onNotFound) => {
@@ -50838,7 +51556,7 @@ function registerConfigRoutes(app, ctx) {
50838
51556
  }
50839
51557
 
50840
51558
  // src/web/routes/discovery.ts
50841
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
51559
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
50842
51560
  var DOC_COLS = "id, title, source, source_path, content_hash, metadata, chunk_count, total_chars, review_status, created_at, updated_at, deleted_at";
50843
51561
  function jsonByteLength(value) {
50844
51562
  return Buffer.byteLength(JSON.stringify(value), "utf8");
@@ -51284,7 +52002,7 @@ function registerDiscoveryRoutes(app, ctx) {
51284
52002
  if (!path) {
51285
52003
  return c2.json({ tried_path: "", anchor, matches: [] });
51286
52004
  }
51287
- if (UUID_RE.test(path)) {
52005
+ if (UUID_RE2.test(path)) {
51288
52006
  if (path === fromDocId) {
51289
52007
  return c2.json({ tried_path: path, anchor, matches: [] });
51290
52008
  }
@@ -51880,13 +52598,13 @@ import { execFileSync } from "node:child_process";
51880
52598
 
51881
52599
  // src/web/docs.ts
51882
52600
  import {
51883
- existsSync as existsSync10,
51884
- readFileSync as readFileSync12,
51885
- readdirSync as readdirSync5,
52601
+ existsSync as existsSync13,
52602
+ readFileSync as readFileSync13,
52603
+ readdirSync as readdirSync7,
51886
52604
  statSync as statSync7
51887
52605
  } from "node:fs";
51888
- import { basename as basename6, dirname as dirname4, join as join11, resolve as resolve5 } from "node:path";
51889
- import { fileURLToPath as fileURLToPath2 } from "node:url";
52606
+ import { basename as basename6, dirname as dirname5, join as join13, resolve as resolve5 } from "node:path";
52607
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
51890
52608
  var TOP_LEVEL_DOCS = [
51891
52609
  { filename: "README.md", path: "README.md", category: "readme" },
51892
52610
  {
@@ -51900,38 +52618,38 @@ var TOP_LEVEL_DOCS = [
51900
52618
  category: "agent-guide"
51901
52619
  }
51902
52620
  ];
51903
- function moduleDir() {
51904
- return dirname4(fileURLToPath2(import.meta.url));
52621
+ function moduleDir2() {
52622
+ return dirname5(fileURLToPath3(import.meta.url));
51905
52623
  }
51906
52624
  function resolveDocsRoots() {
51907
- const here = moduleDir();
52625
+ const here = moduleDir2();
51908
52626
  const pkgRootCandidates = [
51909
- join11(here, "..", ".."),
51910
- join11(here, "..", "..", "..", "..")
52627
+ join13(here, "..", ".."),
52628
+ join13(here, "..", "..", "..", "..")
51911
52629
  ];
51912
52630
  let pkgGuides = null;
51913
52631
  let pkgTopLevel = null;
51914
52632
  for (const pkg of pkgRootCandidates) {
51915
- const guides = join11(pkg, "docs", "guides");
51916
- if (existsSync10(guides) && statSync7(guides).isDirectory()) {
52633
+ const guides = join13(pkg, "docs", "guides");
52634
+ if (existsSync13(guides) && statSync7(guides).isDirectory()) {
51917
52635
  pkgGuides = guides;
51918
52636
  pkgTopLevel = pkg;
51919
52637
  break;
51920
52638
  }
51921
52639
  }
51922
- const repoCandidate = join11(here, "..", "..", "..", "..");
51923
- const repoGuides = join11(repoCandidate, "docs", "guides");
52640
+ const repoCandidate = join13(here, "..", "..", "..", "..");
52641
+ const repoGuides = join13(repoCandidate, "docs", "guides");
51924
52642
  const repoTopLevel = repoCandidate;
51925
52643
  return {
51926
52644
  pkgGuides,
51927
52645
  pkgTopLevel,
51928
- repoGuides: existsSync10(repoGuides) ? repoGuides : null,
51929
- repoTopLevel: existsSync10(join11(repoTopLevel, "README.md")) ? repoTopLevel : null
52646
+ repoGuides: existsSync13(repoGuides) ? repoGuides : null,
52647
+ repoTopLevel: existsSync13(join13(repoTopLevel, "README.md")) ? repoTopLevel : null
51930
52648
  };
51931
52649
  }
51932
52650
  function readH1(filePath) {
51933
52651
  try {
51934
- const content = readFileSync12(filePath, "utf8");
52652
+ const content = readFileSync13(filePath, "utf8");
51935
52653
  const match2 = content.match(/^#\s+(.+?)\s*$/m);
51936
52654
  return match2 ? match2[1] : null;
51937
52655
  } catch {
@@ -51951,17 +52669,17 @@ function listBundledDocs2() {
51951
52669
  const topRoot = pkgTopLevel ?? repoTopLevel;
51952
52670
  if (topRoot) {
51953
52671
  for (const t of TOP_LEVEL_DOCS) {
51954
- const abs = join11(topRoot, t.filename);
51955
- if (existsSync10(abs)) {
52672
+ const abs = join13(topRoot, t.filename);
52673
+ if (existsSync13(abs)) {
51956
52674
  entries.push(entryForFile(abs, t.path, t.category));
51957
52675
  }
51958
52676
  }
51959
52677
  }
51960
52678
  const guidesRoot = pkgGuides ?? repoGuides;
51961
52679
  if (guidesRoot) {
51962
- const names = readdirSync5(guidesRoot).filter((n) => n.endsWith(".md")).sort();
52680
+ const names = readdirSync7(guidesRoot).filter((n) => n.endsWith(".md")).sort();
51963
52681
  for (const name of names) {
51964
- const abs = join11(guidesRoot, name);
52682
+ const abs = join13(guidesRoot, name);
51965
52683
  entries.push(entryForFile(abs, `guides/${name}`, "guide"));
51966
52684
  }
51967
52685
  }
@@ -51975,9 +52693,9 @@ function readDoc(docPath) {
51975
52693
  if (!candidate.startsWith(resolve5(root) + "/") && candidate !== resolve5(root)) {
51976
52694
  continue;
51977
52695
  }
51978
- if (existsSync10(candidate) && statSync7(candidate).isFile()) {
52696
+ if (existsSync13(candidate) && statSync7(candidate).isFile()) {
51979
52697
  try {
51980
- return readFileSync12(candidate, "utf8");
52698
+ return readFileSync13(candidate, "utf8");
51981
52699
  } catch {
51982
52700
  return null;
51983
52701
  }
@@ -52006,7 +52724,7 @@ var VERSION_INFO = {
52006
52724
  git_commit_short: resolveGitCommitShort(),
52007
52725
  build_date: process.env.CEREFOX_BUILD_DATE ?? null
52008
52726
  };
52009
- var SCHEMA_VERSION_RE = /^--\s*@version:\s*(\S+)/m;
52727
+ var SCHEMA_VERSION_RE2 = /^--\s*@version:\s*(\S+)/m;
52010
52728
  function registerMetaRoutes(app, ctx) {
52011
52729
  app.get("/api/v1/version", (c2) => c2.json(VERSION_INFO));
52012
52730
  app.get("/api/v1/docs", (c2) => c2.json(listBundledDocs2()));
@@ -52023,18 +52741,18 @@ function registerMetaRoutes(app, ctx) {
52023
52741
  app.get("/api/v1/schema-version", async (c2) => {
52024
52742
  let bundled = null;
52025
52743
  try {
52026
- const { readFileSync: readFileSync13, existsSync: existsSync11 } = await import("node:fs");
52027
- const { fileURLToPath: fileURLToPath3 } = await import("node:url");
52028
- const { dirname: dirname5, join: join12 } = await import("node:path");
52029
- const here = dirname5(fileURLToPath3(import.meta.url));
52744
+ const { readFileSync: readFileSync14, existsSync: existsSync14 } = await import("node:fs");
52745
+ const { fileURLToPath: fileURLToPath4 } = await import("node:url");
52746
+ const { dirname: dirname6, join: join14 } = await import("node:path");
52747
+ const here = dirname6(fileURLToPath4(import.meta.url));
52030
52748
  const candidates = [
52031
- join12(here, "..", "..", "..", "db", "schema.sql"),
52032
- join12(here, "..", "..", "..", "..", "..", "src", "cerefox", "db", "schema.sql")
52749
+ join14(here, "..", "..", "..", "db", "schema.sql"),
52750
+ join14(here, "..", "..", "..", "..", "..", "src", "cerefox", "db", "schema.sql")
52033
52751
  ];
52034
52752
  for (const path of candidates) {
52035
- if (existsSync11(path)) {
52036
- const sql = readFileSync13(path, "utf8");
52037
- const match2 = sql.match(SCHEMA_VERSION_RE);
52753
+ if (existsSync14(path)) {
52754
+ const sql = readFileSync14(path, "utf8");
52755
+ const match2 = sql.match(SCHEMA_VERSION_RE2);
52038
52756
  bundled = match2 ? match2[1] : null;
52039
52757
  break;
52040
52758
  }
@@ -52071,7 +52789,14 @@ function registerMetaRoutes(app, ctx) {
52071
52789
  } catch {}
52072
52790
  }
52073
52791
  const mismatch = Boolean(bundled && deployed && bundled !== deployed);
52074
- return c2.json({ bundled, deployed, mismatch });
52792
+ const level = classifyCompat(deployed, COMPATIBILITY.minSchema, bundled);
52793
+ return c2.json({
52794
+ bundled,
52795
+ deployed,
52796
+ mismatch,
52797
+ level,
52798
+ min: COMPATIBILITY.minSchema
52799
+ });
52075
52800
  });
52076
52801
  }
52077
52802
 
@@ -52136,21 +52861,21 @@ function registerProjectsRoutes(app, ctx) {
52136
52861
  }
52137
52862
 
52138
52863
  // src/web/static.ts
52139
- import { existsSync as existsSync11, statSync as statSync8 } from "node:fs";
52140
- import { dirname as dirname5, join as join12 } from "node:path";
52141
- import { fileURLToPath as fileURLToPath3 } from "node:url";
52142
- function moduleDir2() {
52143
- return dirname5(fileURLToPath3(import.meta.url));
52864
+ import { existsSync as existsSync14, statSync as statSync8 } from "node:fs";
52865
+ import { dirname as dirname6, join as join14 } from "node:path";
52866
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
52867
+ function moduleDir3() {
52868
+ return dirname6(fileURLToPath4(import.meta.url));
52144
52869
  }
52145
52870
  function isUsableSpaDir(dir) {
52146
- return existsSync11(dir) && statSync8(dir).isDirectory() && existsSync11(join12(dir, "index.html"));
52871
+ return existsSync14(dir) && statSync8(dir).isDirectory() && existsSync14(join14(dir, "index.html"));
52147
52872
  }
52148
52873
  function resolveSpaDist() {
52149
- const here = moduleDir2();
52874
+ const here = moduleDir3();
52150
52875
  const candidates = [
52151
- join12(here, "..", "frontend"),
52152
- join12(here, "..", "..", "dist", "frontend"),
52153
- join12(here, "..", "..", "..", "..", "frontend", "dist")
52876
+ join14(here, "..", "frontend"),
52877
+ join14(here, "..", "..", "..", "..", "frontend", "dist"),
52878
+ join14(here, "..", "..", "dist", "frontend")
52154
52879
  ];
52155
52880
  for (const c2 of candidates) {
52156
52881
  if (isUsableSpaDir(c2))
@@ -52159,13 +52884,13 @@ function resolveSpaDist() {
52159
52884
  return null;
52160
52885
  }
52161
52886
  function resolveStaticDir() {
52162
- const here = moduleDir2();
52887
+ const here = moduleDir3();
52163
52888
  const candidates = [
52164
- join12(here, "..", "static"),
52165
- join12(here, "..", "..", "..", "..", "web", "static")
52889
+ join14(here, "..", "static"),
52890
+ join14(here, "..", "..", "..", "..", "web", "static")
52166
52891
  ];
52167
52892
  for (const c2 of candidates) {
52168
- if (existsSync11(c2) && statSync8(c2).isDirectory())
52893
+ if (existsSync14(c2) && statSync8(c2).isDirectory())
52169
52894
  return c2;
52170
52895
  }
52171
52896
  return null;
@@ -52207,10 +52932,13 @@ var ROOT_REDIRECT_HTML = `<!DOCTYPE html>
52207
52932
  </html>`;
52208
52933
 
52209
52934
  // src/web/server.ts
52935
+ init_meta();
52936
+ init_cli_core();
52937
+ init_config();
52210
52938
  function buildApp(ctx = buildWebContext()) {
52211
52939
  const app = new Hono2;
52212
52940
  if (true) {
52213
- app.use(logger());
52941
+ app.use(logger((message, ...rest) => console.log(`${localTimestamp()} ${message}`, ...rest)));
52214
52942
  }
52215
52943
  registerMetaRoutes(app, ctx);
52216
52944
  if (ctx) {
@@ -52246,18 +52974,52 @@ function buildApp(ctx = buildWebContext()) {
52246
52974
  root: spaDist,
52247
52975
  rewriteRequestPath: (path) => path.replace(/^\/app/, "") || "/"
52248
52976
  }));
52249
- const indexPath = join13(spaDist, "index.html");
52250
- if (existsSync12(indexPath)) {
52251
- const indexHtml = readFileSync13(indexPath, "utf8");
52977
+ const indexPath = join15(spaDist, "index.html");
52978
+ if (existsSync15(indexPath)) {
52979
+ const indexHtml = readFileSync14(indexPath, "utf8");
52252
52980
  app.get("/app/*", (c2) => c2.html(indexHtml));
52253
52981
  }
52254
52982
  }
52255
52983
  app.get("/", (c2) => c2.html(ROOT_REDIRECT_HTML));
52256
52984
  return app;
52257
52985
  }
52986
+
52987
+ class CompatibilityError extends Error {
52988
+ }
52989
+ async function assertServerCompatible() {
52990
+ const settings = loadSettings();
52991
+ if (!settings.supabaseUrl || !settings.supabaseAnonKey)
52992
+ return;
52993
+ let compat2;
52994
+ try {
52995
+ compat2 = await checkServerCompatibility({
52996
+ aggregatorUrl: aggregatorUrlFor(settings.supabaseUrl),
52997
+ bearer: settings.supabaseAnonKey,
52998
+ bundledEf: PKG_VERSION
52999
+ });
53000
+ } catch {
53001
+ return;
53002
+ }
53003
+ if (!compat2.blocking)
53004
+ return;
53005
+ const parts = [];
53006
+ if (compat2.schema.level === "below-min") {
53007
+ parts.push(` • schema v${compat2.schema.deployed} is below the required v${compat2.schema.min}`);
53008
+ }
53009
+ if (compat2.edgeFunctions.level === "below-min") {
53010
+ parts.push(` • Edge Functions v${compat2.edgeFunctions.deployed} are below the required v${compat2.edgeFunctions.min}`);
53011
+ }
53012
+ throw new CompatibilityError(`Refusing to start: the deployed Cerefox server is incompatible with this client (v${PKG_VERSION}).
53013
+ ` + parts.join(`
53014
+ `) + `
53015
+
53016
+ Redeploy your server: cerefox deploy-server
53017
+ ` + `(or downgrade the client to match the deployed server).`);
53018
+ }
52258
53019
  async function buildWebServer(options = {}) {
52259
53020
  const host = options.host ?? "127.0.0.1";
52260
53021
  const port = options.port ?? 8000;
53022
+ await assertServerCompatible();
52261
53023
  const app = buildApp();
52262
53024
  const server = serve({ fetch: app.fetch, hostname: host, port });
52263
53025
  return {
@@ -52269,31 +53031,263 @@ async function buildWebServer(options = {}) {
52269
53031
  };
52270
53032
  }
52271
53033
 
53034
+ // src/web/daemon.ts
53035
+ import { spawn } from "node:child_process";
53036
+ import {
53037
+ existsSync as existsSync16,
53038
+ mkdirSync as mkdirSync4,
53039
+ openSync,
53040
+ readFileSync as readFileSync15,
53041
+ rmSync,
53042
+ writeFileSync as writeFileSync4
53043
+ } from "node:fs";
53044
+ import { homedir as homedir7 } from "node:os";
53045
+ import { join as join16 } from "node:path";
53046
+ var STATE_DIR = join16(homedir7(), ".cerefox");
53047
+ var PID_FILE = join16(STATE_DIR, "web.pid");
53048
+ var LOG_FILE = join16(STATE_DIR, "web.log");
53049
+ var daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
53050
+ function ensureStateDir() {
53051
+ if (!existsSync16(STATE_DIR))
53052
+ mkdirSync4(STATE_DIR, { recursive: true });
53053
+ }
53054
+ function readPidFile() {
53055
+ if (!existsSync16(PID_FILE))
53056
+ return null;
53057
+ try {
53058
+ const parsed = JSON.parse(readFileSync15(PID_FILE, "utf8"));
53059
+ if (typeof parsed.pid !== "number")
53060
+ return null;
53061
+ return {
53062
+ pid: parsed.pid,
53063
+ port: typeof parsed.port === "number" ? parsed.port : 8000,
53064
+ host: typeof parsed.host === "string" ? parsed.host : "127.0.0.1",
53065
+ startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : "unknown"
53066
+ };
53067
+ } catch {
53068
+ return null;
53069
+ }
53070
+ }
53071
+ function writePidFile(info3) {
53072
+ ensureStateDir();
53073
+ writeFileSync4(PID_FILE, JSON.stringify(info3, null, 2) + `
53074
+ `, "utf8");
53075
+ }
53076
+ function removePidFile() {
53077
+ rmSync(PID_FILE, { force: true });
53078
+ }
53079
+ function isProcessAlive(pid) {
53080
+ try {
53081
+ process.kill(pid, 0);
53082
+ return true;
53083
+ } catch (err) {
53084
+ return err.code === "EPERM";
53085
+ }
53086
+ }
53087
+ async function isResponding(host, port, timeoutMs = 1500) {
53088
+ const probeHost = host === "0.0.0.0" ? "127.0.0.1" : host;
53089
+ try {
53090
+ const ctrl = new AbortController;
53091
+ const timer2 = setTimeout(() => ctrl.abort(), timeoutMs);
53092
+ try {
53093
+ const resp = await fetch(`http://${probeHost}:${port}/api/v1/version`, {
53094
+ signal: ctrl.signal
53095
+ });
53096
+ return resp.ok;
53097
+ } finally {
53098
+ clearTimeout(timer2);
53099
+ }
53100
+ } catch {
53101
+ return false;
53102
+ }
53103
+ }
53104
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
53105
+ function assertUnix() {
53106
+ if (process.platform === "win32") {
53107
+ throw new Error("Daemon mode (`cerefox web start/stop/status`) is not supported on Windows yet. " + "Run `cerefox web` in the foreground, or set up a Windows service manually.");
53108
+ }
53109
+ }
53110
+ async function startDaemon(opts) {
53111
+ assertUnix();
53112
+ ensureStateDir();
53113
+ const existing = readPidFile();
53114
+ if (existing && isProcessAlive(existing.pid)) {
53115
+ return existing.port === opts.port ? { kind: "already-running", info: existing } : { kind: "port-conflict", info: existing };
53116
+ }
53117
+ if (existing)
53118
+ removePidFile();
53119
+ const logFd = openSync(LOG_FILE, "a");
53120
+ const child = spawn(opts.runtime, [opts.scriptPath, "web", "--host", opts.host, "--port", String(opts.port)], { detached: true, stdio: ["ignore", logFd, logFd] });
53121
+ child.unref();
53122
+ if (typeof child.pid !== "number") {
53123
+ throw new Error("Failed to spawn the web server process (no pid).");
53124
+ }
53125
+ writePidFile({
53126
+ pid: child.pid,
53127
+ port: opts.port,
53128
+ host: opts.host,
53129
+ startedAt: new Date().toISOString()
53130
+ });
53131
+ let responding = false;
53132
+ for (let i = 0;i < 20; i++) {
53133
+ if (!isProcessAlive(child.pid))
53134
+ break;
53135
+ if (await isResponding(opts.host, opts.port)) {
53136
+ responding = true;
53137
+ break;
53138
+ }
53139
+ await sleep2(250);
53140
+ }
53141
+ return { kind: "started", pid: child.pid, responding };
53142
+ }
53143
+ async function stopDaemon() {
53144
+ assertUnix();
53145
+ const info3 = readPidFile();
53146
+ if (!info3 || !isProcessAlive(info3.pid)) {
53147
+ removePidFile();
53148
+ return { kind: "not-running" };
53149
+ }
53150
+ try {
53151
+ process.kill(info3.pid, "SIGTERM");
53152
+ } catch {}
53153
+ let forced = false;
53154
+ let alive = true;
53155
+ for (let i = 0;i < 12; i++) {
53156
+ await sleep2(250);
53157
+ if (!isProcessAlive(info3.pid)) {
53158
+ alive = false;
53159
+ break;
53160
+ }
53161
+ }
53162
+ if (alive) {
53163
+ try {
53164
+ process.kill(info3.pid, "SIGKILL");
53165
+ forced = true;
53166
+ } catch {}
53167
+ }
53168
+ removePidFile();
53169
+ return { kind: "stopped", pid: info3.pid, forced };
53170
+ }
53171
+ async function statusDaemon() {
53172
+ const info3 = readPidFile();
53173
+ if (!info3)
53174
+ return { kind: "stopped" };
53175
+ if (!isProcessAlive(info3.pid))
53176
+ return { kind: "stale", info: info3 };
53177
+ const responding = await isResponding(info3.host, info3.port);
53178
+ return { kind: "running", info: info3, responding };
53179
+ }
53180
+
52272
53181
  // src/cli/commands/web.ts
53182
+ function parsePort(raw2) {
53183
+ const port = Number.parseInt(raw2, 10);
53184
+ if (!Number.isFinite(port) || port < 1 || port > 65535) {
53185
+ eprintln(`Invalid --port: ${raw2}`);
53186
+ process.exit(2);
53187
+ }
53188
+ return port;
53189
+ }
53190
+ async function runForeground(host, port, watch) {
53191
+ if (watch) {
53192
+ info("--watch is reserved; not yet implemented.");
53193
+ }
53194
+ try {
53195
+ const handle = await buildWebServer({ host, port });
53196
+ println(`${localTimestamp()} Cerefox web listening on http://${handle.host}:${handle.port}/`);
53197
+ println(` Web UI: http://${handle.host}:${handle.port}/app/`);
53198
+ println(` API: http://${handle.host}:${handle.port}/api/v1/`);
53199
+ const shutdown = async (signal) => {
53200
+ println(`${localTimestamp()} Received ${signal}; shutting down.`);
53201
+ await handle.close().catch(() => {});
53202
+ process.exit(0);
53203
+ };
53204
+ process.on("SIGINT", () => void shutdown("SIGINT"));
53205
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
53206
+ } catch (err) {
53207
+ if (err instanceof CompatibilityError) {
53208
+ eprintln(err.message);
53209
+ } else {
53210
+ eprintln(`Failed to start web server: ${err instanceof Error ? err.message : String(err)}`);
53211
+ }
53212
+ process.exit(1);
53213
+ }
53214
+ }
52273
53215
  function registerWeb(program2) {
52274
- program2.command("web").description("Start the local web UI / API server.").option("--host <host>", "Bind host.", "127.0.0.1").option("--port <port>", "Bind port.", "8000").option("--watch", "Enable hot-reload (dev mode; reserved for v0.6.x).").action(async (rawOpts) => {
52275
- const port = Number.parseInt(rawOpts.port, 10);
52276
- if (!Number.isFinite(port) || port < 1 || port > 65535) {
52277
- eprintln(`Invalid --port: ${rawOpts.port}`);
52278
- process.exit(2);
53216
+ const web = program2.command("web").description("Start the local web UI / API server (foreground; see `web start` for daemon).").option("--host <host>", "Bind host.", "127.0.0.1").option("--port <port>", "Bind port.", "8000").option("--watch", "Enable hot-reload (dev mode; reserved).").action(async (rawOpts) => {
53217
+ await runForeground(rawOpts.host, parsePort(rawOpts.port), rawOpts.watch);
53218
+ });
53219
+ web.command("start").description("Start the web server in the background (detached daemon).").option("--host <host>", "Bind host.", "127.0.0.1").option("--port <port>", "Bind port.", "8000").action(async (rawOpts) => {
53220
+ const port = parsePort(rawOpts.port);
53221
+ try {
53222
+ const outcome = await startDaemon({
53223
+ host: rawOpts.host,
53224
+ port,
53225
+ scriptPath: process.argv[1],
53226
+ runtime: process.execPath
53227
+ });
53228
+ switch (outcome.kind) {
53229
+ case "already-running":
53230
+ info(`Cerefox web is already running on :${outcome.info.port} (pid ${outcome.info.pid}).`);
53231
+ process.exit(0);
53232
+ break;
53233
+ case "port-conflict":
53234
+ eprintln(`A Cerefox web daemon is already running on :${outcome.info.port} (pid ${outcome.info.pid}).
53235
+ ` + `Stop it first (\`cerefox web stop\`) or start on its port.`);
53236
+ process.exit(1);
53237
+ break;
53238
+ case "started":
53239
+ if (outcome.responding) {
53240
+ info(`Cerefox web started (pid ${outcome.pid}) on http://${rawOpts.host}:${port}/`);
53241
+ println(` Web UI: http://${rawOpts.host}:${port}/app/`);
53242
+ println(c.dim(` Logs: ${daemonPaths.logFile}`));
53243
+ println(c.dim(` Stop: cerefox web stop`));
53244
+ } else {
53245
+ eprintln(`Started (pid ${outcome.pid}) but it is not responding on :${port} yet.
53246
+ ` + `Check the log for errors: ${daemonPaths.logFile}`);
53247
+ process.exit(1);
53248
+ }
53249
+ break;
53250
+ }
53251
+ } catch (err) {
53252
+ eprintln(err instanceof Error ? err.message : String(err));
53253
+ process.exit(1);
52279
53254
  }
52280
- if (rawOpts.watch) {
52281
- info("--watch is reserved; not yet implemented in Part 24A.");
53255
+ });
53256
+ web.command("stop").description("Stop the background web server daemon.").action(async () => {
53257
+ try {
53258
+ const outcome = await stopDaemon();
53259
+ if (outcome.kind === "not-running") {
53260
+ info("No Cerefox web daemon is running.");
53261
+ } else {
53262
+ info(`Stopped Cerefox web (pid ${outcome.pid})${outcome.forced ? " — forced (SIGKILL)" : ""}.`);
53263
+ }
53264
+ } catch (err) {
53265
+ eprintln(err instanceof Error ? err.message : String(err));
53266
+ process.exit(1);
52282
53267
  }
53268
+ });
53269
+ web.command("status").description("Show the background web server daemon status.").action(async () => {
52283
53270
  try {
52284
- const handle = await buildWebServer({ host: rawOpts.host, port });
52285
- info(`Cerefox web listening on http://${handle.host}:${handle.port}/`);
52286
- println(` Web UI: http://${handle.host}:${handle.port}/app/`);
52287
- println(` API: http://${handle.host}:${handle.port}/api/v1/`);
52288
- const shutdown = async (signal) => {
52289
- info(`Received ${signal}; shutting down.`);
52290
- await handle.close().catch(() => {});
52291
- process.exit(0);
52292
- };
52293
- process.on("SIGINT", () => void shutdown("SIGINT"));
52294
- process.on("SIGTERM", () => void shutdown("SIGTERM"));
53271
+ const status = await statusDaemon();
53272
+ switch (status.kind) {
53273
+ case "stopped":
53274
+ println("Cerefox web: stopped (no daemon running).");
53275
+ break;
53276
+ case "stale":
53277
+ println(c.yellow(`Cerefox web: stale pidfile — process ${status.info.pid} is not running.`));
53278
+ println(c.dim(` Clean up: cerefox web stop (removes ${daemonPaths.pidFile})`));
53279
+ break;
53280
+ case "running":
53281
+ if (status.responding) {
53282
+ println(c.green(`Cerefox web: running on :${status.info.port} (pid ${status.info.pid}, since ${status.info.startedAt}).`));
53283
+ } else {
53284
+ println(c.yellow(`Cerefox web: process ${status.info.pid} alive but not responding on :${status.info.port}.`));
53285
+ println(c.dim(` Check the log: ${daemonPaths.logFile}`));
53286
+ }
53287
+ break;
53288
+ }
52295
53289
  } catch (err) {
52296
- eprintln(`Failed to start web server: ${err instanceof Error ? err.message : String(err)}`);
53290
+ eprintln(err instanceof Error ? err.message : String(err));
52297
53291
  process.exit(1);
52298
53292
  }
52299
53293
  });
@@ -52305,9 +53299,9 @@ function buildProgram() {
52305
53299
  Command groups (each row in the list above falls into one):
52306
53300
  ` + ` READS search · get-doc · list-docs · list-versions · list-projects
52307
53301
  ` + ` · list-metadata-keys · metadata-search · get-audit-log
52308
- ` + ` WRITES ingest · ingest-dir · delete-doc
53302
+ ` + ` WRITES ingest · ingest-dir · delete-doc · delete-project
52309
53303
  ` + ` SERVERS mcp · web
52310
- ` + ` LIFECYCLE init · doctor · status · configure-agent · self-update · upgrade · sync-self-docs
53304
+ ` + ` LIFECYCLE init · doctor · status · configure-agent · self-update · upgrade · sync-self-docs · deploy-server
52311
53305
  ` + ` OPS backup · restore · sync-docs · docs · reindex · config-get · config-set · completion
52312
53306
  ` + `
52313
53307
  Exit codes:
@@ -52330,6 +53324,7 @@ Learn more:
52330
53324
  registerIngest(program2);
52331
53325
  registerIngestDir(program2);
52332
53326
  registerDeleteDoc(program2);
53327
+ registerDeleteProject(program2);
52333
53328
  registerMcp(program2);
52334
53329
  registerWeb(program2);
52335
53330
  registerInit(program2);
@@ -52338,6 +53333,7 @@ Learn more:
52338
53333
  registerConfigureAgent(program2);
52339
53334
  registerSelfUpdate(program2);
52340
53335
  registerSyncSelfDocs(program2);
53336
+ registerDeployServer(program2);
52341
53337
  registerBackup(program2);
52342
53338
  registerRestore(program2);
52343
53339
  registerSyncDocs(program2);
@@ -52351,7 +53347,7 @@ Learn more:
52351
53347
 
52352
53348
  // src/bin/cerefox.ts
52353
53349
  async function bareEntryPoint() {
52354
- const { existsSync: existsSync13 } = await import("node:fs");
53350
+ const { existsSync: existsSync17 } = await import("node:fs");
52355
53351
  const { resolveEnvFile: resolveEnvFile2 } = await Promise.resolve().then(() => (init_config(), exports_config));
52356
53352
  const { c: c2, println: println2 } = await Promise.resolve().then(() => (init_cli_core(), exports_cli_core));
52357
53353
  const { PKG_VERSION: PKG_VERSION2 } = await Promise.resolve().then(() => (init_meta(), exports_meta));
@@ -52360,7 +53356,7 @@ async function bareEntryPoint() {
52360
53356
  println2("");
52361
53357
  let configExists = false;
52362
53358
  try {
52363
- configExists = existsSync13(resolveEnvFile2());
53359
+ configExists = existsSync17(resolveEnvFile2());
52364
53360
  } catch {}
52365
53361
  if (!configExists) {
52366
53362
  println2(c2.yellow("⚠ No config detected."));