@cerefox/memory 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7438,7 +7438,7 @@ var exports_meta = {};
7438
7438
  __export(exports_meta, {
7439
7439
  PKG_VERSION: () => PKG_VERSION
7440
7440
  });
7441
- var PKG_VERSION = "1.7.0";
7441
+ var PKG_VERSION = "1.8.0";
7442
7442
  var init_meta = () => {};
7443
7443
 
7444
7444
  // ../../_shared/config/paths.ts
@@ -25743,7 +25743,7 @@ var init_bundled_docs = __esm(() => {
25743
25743
  });
25744
25744
 
25745
25745
  // ../../_shared/ef-meta/index.ts
25746
- var EF_VERSION = "1.7.0", CEREFOX_VERSION = "1.7.0", EF_LAST_CHANGED = "1.7.0";
25746
+ var EF_VERSION = "1.8.0", CEREFOX_VERSION = "1.8.0", EF_LAST_CHANGED = "1.7.0";
25747
25747
  var init_ef_meta = () => {};
25748
25748
 
25749
25749
  // ../../_shared/compatibility/index.ts
@@ -41592,10 +41592,10 @@ var require_flate = __commonJS((exports) => {
41592
41592
  var GenericWorker = require_GenericWorker();
41593
41593
  var ARRAY_TYPE = USE_TYPEDARRAY ? "uint8array" : "array";
41594
41594
  exports.magic = "\b\x00";
41595
- function FlateWorker(action19, options) {
41596
- GenericWorker.call(this, "FlateWorker/" + action19);
41595
+ function FlateWorker(action20, options) {
41596
+ GenericWorker.call(this, "FlateWorker/" + action20);
41597
41597
  this._pako = null;
41598
- this._pakoAction = action19;
41598
+ this._pakoAction = action20;
41599
41599
  this._pakoOptions = options;
41600
41600
  this.meta = {};
41601
41601
  }
@@ -57699,11 +57699,11 @@ async function runSyncSelfDocs(options = {}) {
57699
57699
  printTable(outcomes.filter((o) => o.status === "error").map((o) => ({ topic: o.topic, error: o.detail.slice(0, 100) })));
57700
57700
  }
57701
57701
  }
57702
- async function action21(options) {
57702
+ async function action22(options) {
57703
57703
  await runSyncSelfDocs(options);
57704
57704
  }
57705
57705
  function registerSyncSelfDocs(program2) {
57706
- 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(action21);
57706
+ 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(action22);
57707
57707
  }
57708
57708
  var init_sync_self_docs = __esm(() => {
57709
57709
  init_cli_core();
@@ -72797,11 +72797,48 @@ function registerDeleteDoc(program2) {
72797
72797
  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(action7);
72798
72798
  }
72799
72799
 
72800
+ // src/cli/commands/document-dead-links.ts
72801
+ init_cli_core();
72802
+ init_client();
72803
+ var SERVER_BEHIND = "The sweep did not run: this server has no cerefox_find_dead_links (needs schema 0.12.2). " + "Run `cerefox server deploy`, then retry.";
72804
+ async function action8(options) {
72805
+ const client = getClient();
72806
+ let rows;
72807
+ try {
72808
+ rows = await client.rpc("cerefox_find_dead_links", {});
72809
+ } catch (e) {
72810
+ const message = e instanceof Error ? e.message : String(e);
72811
+ if (isMissingFunctionError(message, "cerefox_find_dead_links")) {
72812
+ throw systemError(SERVER_BEHIND);
72813
+ }
72814
+ throw e;
72815
+ }
72816
+ if (rows === null)
72817
+ throw systemError(SERVER_BEHIND);
72818
+ if (options.json) {
72819
+ printJson(rows);
72820
+ return;
72821
+ }
72822
+ if (rows.length === 0) {
72823
+ println(c.green("✓ No dead document links found."));
72824
+ return;
72825
+ }
72826
+ println(c.yellow(`${rows.length} dead link(s) across ${new Set(rows.map((r) => r.document_id)).size} document(s):`));
72827
+ for (const r of rows) {
72828
+ println(` ${r.document_title} (${r.document_id})`);
72829
+ println(c.dim(` → [Text](${r.dead_link_id}) ×${r.occurrences} — target no longer exists`));
72830
+ }
72831
+ println(c.dim(`Fix each by editing the linking document (correct the id, remove the link, or backtick it as an example). ` + `The write-time guard prevents NEW dead links; these predate it or lost their target to a purge.`));
72832
+ }
72833
+ function registerDocumentDeadLinks(program2) {
72834
+ program2.command("dead-links").description("Find [Text](uuid) links whose target document no longer exists (#214 phase 2).").option("--json", "Machine-readable output.").action(action8);
72835
+ }
72836
+
72800
72837
  // src/cli/commands/delete-project.ts
72801
72838
  init_cli_core();
72802
72839
  init_client();
72803
72840
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
72804
- async function action8(target, options) {
72841
+ async function action9(target, options) {
72805
72842
  const client = getClient();
72806
72843
  const isUuid = UUID_RE.test(target);
72807
72844
  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();
@@ -72837,7 +72874,7 @@ async function action8(target, options) {
72837
72874
  println(c.green(`✓ Deleted project "${project.name}" (id: ${project.id}).`));
72838
72875
  }
72839
72876
  function registerDeleteProject(program2) {
72840
- 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(action8);
72877
+ 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(action9);
72841
72878
  }
72842
72879
 
72843
72880
  // src/cli/commands/deploy-server.ts
@@ -72951,7 +72988,13 @@ async function migrationStatus(opts) {
72951
72988
  }
72952
72989
  async function runDbMigrate(opts) {
72953
72990
  const log = opts.log ?? (() => {});
72954
- const sql = src_default(opts.dbUrl, { prepare: false, onnotice: () => {} });
72991
+ const sql = src_default(opts.dbUrl, {
72992
+ prepare: false,
72993
+ onnotice: (n) => {
72994
+ if (n.message)
72995
+ log(` ↳ ${n.message}`);
72996
+ }
72997
+ });
72955
72998
  try {
72956
72999
  await sql.unsafe(BOOTSTRAP_MIGRATIONS_SQL);
72957
73000
  const allFiles = listMigrationFiles(opts.assets.migrationsDir);
@@ -73043,7 +73086,7 @@ function listEdgeFunctions(functionsDir) {
73043
73086
  return [];
73044
73087
  return readdirSync2(functionsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("cerefox-")).map((e) => e.name).sort();
73045
73088
  }
73046
- async function action9(options) {
73089
+ async function action10(options) {
73047
73090
  const settings = loadSettings();
73048
73091
  const assets = resolveServerAssets();
73049
73092
  const doSchema = !options.functionsOnly;
@@ -73258,7 +73301,7 @@ Proceed with deployment to Supabase?`, true);
73258
73301
  println(c.dim("Verify with: cerefox doctor"));
73259
73302
  }
73260
73303
  function registerDeployServer(program2) {
73261
- 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).").option("--project-ref <ref>", "Supabase project ref for Edge Function deploys (default: derived from CEREFOX_SUPABASE_URL).").option("--yes", "Non-interactive (skip the deployment confirmation).").action(action9);
73304
+ 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).").option("--project-ref <ref>", "Supabase project ref for Edge Function deploys (default: derived from CEREFOX_SUPABASE_URL).").option("--yes", "Non-interactive (skip the deployment confirmation).").action(action10);
73262
73305
  }
73263
73306
 
73264
73307
  // src/cli/commands/document-edit.ts
@@ -73278,7 +73321,7 @@ function parseMetaPair(pair) {
73278
73321
  }
73279
73322
  return [key, value];
73280
73323
  }
73281
- async function action10(documentId, options) {
73324
+ async function action11(documentId, options) {
73282
73325
  const hasTitle = options.title !== undefined;
73283
73326
  const sets = options.setMeta ?? [];
73284
73327
  const unsets = options.unsetMeta ?? [];
@@ -73296,18 +73339,41 @@ async function action10(documentId, options) {
73296
73339
  if (doc.deleted_at) {
73297
73340
  throw userError(`Document ${documentId} is soft-deleted — restore it first (cerefox document restore).`);
73298
73341
  }
73299
- const metadata = { ...doc.metadata ?? {} };
73300
- for (const pair of sets) {
73301
- const [k, v] = parseMetaPair(pair);
73302
- metadata[k] = v;
73303
- }
73304
- for (const k of unsets)
73305
- delete metadata[k.trim()];
73342
+ const metaTouched = sets.length > 0 || unsets.length > 0;
73306
73343
  const newTitle = hasTitle ? options.title.trim() : doc.title;
73307
73344
  const titleChanged = newTitle !== doc.title;
73308
- const { error: updErr } = await client.raw.from("cerefox_documents").update({ title: newTitle, metadata, updated_at: new Date().toISOString() }).eq("id", documentId);
73309
- if (updErr)
73310
- throw systemError(`Update failed: ${updErr.message}`);
73345
+ const author = resolveAuthor(options.author);
73346
+ const authorType = resolveAuthorType(options.authorType);
73347
+ if (author === "unknown") {
73348
+ warn("No --author / CEREFOX_AUTHOR_NAME set — audit log will record this write as 'unknown'.");
73349
+ }
73350
+ if (metaTouched) {
73351
+ const patch = {};
73352
+ for (const pair of sets) {
73353
+ const [k, v] = parseMetaPair(pair);
73354
+ patch[k] = v;
73355
+ }
73356
+ for (const k of unsets)
73357
+ patch[k.trim()] = null;
73358
+ const { error: metaErr } = await client.raw.rpc("cerefox_set_document_metadata", {
73359
+ p_document_id: documentId,
73360
+ p_metadata: patch,
73361
+ p_replace: false,
73362
+ p_author: author,
73363
+ p_author_type: authorType
73364
+ });
73365
+ if (metaErr) {
73366
+ if (metaErr.message?.includes("CEREFOX_BAD_METADATA")) {
73367
+ throw userError(`Document ${documentId} has non-object metadata; a patch cannot repair it. ` + `Repair it first with: cerefox document set-metadata ${documentId} --replace --json '<the intended object>'`);
73368
+ }
73369
+ throw systemError(`Metadata update failed: ${metaErr.message}`);
73370
+ }
73371
+ }
73372
+ if (hasTitle) {
73373
+ const { error: updErr } = await client.raw.from("cerefox_documents").update({ title: newTitle, updated_at: new Date().toISOString() }).eq("id", documentId);
73374
+ if (updErr)
73375
+ throw systemError(`Update failed: ${updErr.message}`);
73376
+ }
73311
73377
  if (titleChanged) {
73312
73378
  const { error: ftsErr } = await client.raw.rpc("cerefox_update_chunk_fts", {
73313
73379
  p_document_id: documentId,
@@ -73316,18 +73382,15 @@ async function action10(documentId, options) {
73316
73382
  if (ftsErr)
73317
73383
  throw systemError(`Title updated but FTS refresh failed: ${ftsErr.message}`);
73318
73384
  }
73319
- const author = resolveAuthor(options.author);
73320
- const authorType = resolveAuthorType(options.authorType);
73321
- if (author === "unknown") {
73322
- warn("No --author / CEREFOX_AUTHOR_NAME set — audit log will record this write as 'unknown'.");
73385
+ if (titleChanged) {
73386
+ await client.raw.rpc("cerefox_create_audit_entry", {
73387
+ p_document_id: documentId,
73388
+ p_operation: "update-metadata",
73389
+ p_author: author,
73390
+ p_author_type: authorType,
73391
+ p_description: "Edited title"
73392
+ });
73323
73393
  }
73324
- await client.raw.rpc("cerefox_create_audit_entry", {
73325
- p_document_id: documentId,
73326
- p_operation: "update-metadata",
73327
- p_author: author,
73328
- p_author_type: authorType,
73329
- p_description: `Edited${titleChanged ? " title" : ""}` + (sets.length ? ` · set ${sets.length} meta key(s)` : "") + (unsets.length ? ` · unset ${unsets.length} meta key(s)` : "")
73330
- });
73331
73394
  println(c.green(`✓ Edited "${newTitle}" (id: ${documentId}).`));
73332
73395
  if (titleChanged) {
73333
73396
  println(c.dim(" Title changed: FTS refreshed; semantic embeddings update on next `cerefox server reindex`."));
@@ -73337,13 +73400,13 @@ function collect(value, prev) {
73337
73400
  return [...prev, value];
73338
73401
  }
73339
73402
  function registerDocumentEdit(parent) {
73340
- parent.command("edit").description("Edit a document's title and/or metadata (non-destructive patch). Content edits: `document ingest --document-id <id> --update`.").argument("<document-id>", "UUID of the document.").option("--title <title>", "New title (refreshes FTS; re-embed on next reindex).").option("--set-meta <key=value>", "Set/overwrite a metadata key (repeatable). Value is JSON-parsed when possible.", collect, []).option("--unset-meta <key>", "Remove a metadata key (repeatable).", collect, []).option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action10);
73403
+ parent.command("edit").description("Edit a document's title and/or metadata (non-destructive patch). Content edits: `document ingest --document-id <id> --update`.").argument("<document-id>", "UUID of the document.").option("--title <title>", "New title (refreshes FTS; re-embed on next reindex).").option("--set-meta <key=value>", "Set/overwrite a metadata key (repeatable). Value is JSON-parsed when possible.", collect, []).option("--unset-meta <key>", "Remove a metadata key (repeatable).", collect, []).option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action11);
73341
73404
  }
73342
73405
 
73343
73406
  // src/cli/commands/document-restore.ts
73344
73407
  init_cli_core();
73345
73408
  init_client();
73346
- async function action11(documentId, options) {
73409
+ async function action12(documentId, options) {
73347
73410
  const client = getClient();
73348
73411
  const { data: doc, error } = await client.raw.from("cerefox_documents").select("id, title, deleted_at").eq("id", documentId).maybeSingle();
73349
73412
  if (error)
@@ -73397,13 +73460,13 @@ async function action11(documentId, options) {
73397
73460
  }
73398
73461
  }
73399
73462
  function registerDocumentRestore(parent) {
73400
- parent.command("restore").description("Restore a soft-deleted document from the trash (inverse of `document delete`).").argument("<document-id>", "UUID of the soft-deleted document.").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").action(action11);
73463
+ parent.command("restore").description("Restore a soft-deleted document from the trash (inverse of `document delete`).").argument("<document-id>", "UUID of the soft-deleted document.").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").action(action12);
73401
73464
  }
73402
73465
 
73403
73466
  // src/cli/commands/document-set-metadata.ts
73404
73467
  init_cli_core();
73405
73468
  init_client();
73406
- async function action12(documentId, options) {
73469
+ async function action13(documentId, options) {
73407
73470
  const patch = {};
73408
73471
  if (options.json) {
73409
73472
  let parsed;
@@ -73475,14 +73538,14 @@ async function action12(documentId, options) {
73475
73538
  println(c.dim(" Content untouched — no new version, no re-embedding."));
73476
73539
  }
73477
73540
  function registerDocumentSetMetadata(parent) {
73478
- parent.command("set-metadata").description("Change a document's metadata without resending its content (merges by default).").argument("<document-id>", "UUID of the document.").option("-s, --set <key=value...>", "Set a key. Repeatable. Values are stored as JSON strings; quote to force JSON parsing.").option("-r, --remove <key...>", "Remove a key. Repeatable. (Sends a JSON null.)").option("--json <object>", "A JSON object of keys to set; a null value removes that key.").option("--replace", "Set the metadata to EXACTLY what was given, discarding every key not listed. Default is merge.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("--json-out", "Emit the result as JSON.").action(action12);
73541
+ parent.command("set-metadata").description("Change a document's metadata without resending its content (merges by default).").argument("<document-id>", "UUID of the document.").option("-s, --set <key=value...>", "Set a key. Repeatable. Values are stored as JSON strings; quote to force JSON parsing.").option("-r, --remove <key...>", "Remove a key. Repeatable. (Sends a JSON null.)").option("--json <object>", "A JSON object of keys to set; a null value removes that key.").option("--replace", "Set the metadata to EXACTLY what was given, discarding every key not listed. Default is merge.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("--json-out", "Emit the result as JSON.").action(action13);
73479
73542
  }
73480
73543
 
73481
73544
  // src/cli/commands/document-set-projects.ts
73482
73545
  init_cli_core();
73483
73546
  init__projects();
73484
73547
  init_client();
73485
- async function action13(documentId, projectNames, options) {
73548
+ async function action14(documentId, projectNames, options) {
73486
73549
  const names = projectNames ?? [];
73487
73550
  if (options.clear && names.length > 0) {
73488
73551
  throw userError("Pass either project names or --clear, not both.", "Use --clear on its own to remove the document from all projects.");
@@ -73512,7 +73575,7 @@ async function action13(documentId, projectNames, options) {
73512
73575
  println(c.dim(" This REPLACED the previous set — any project not listed is no longer associated."));
73513
73576
  }
73514
73577
  function registerDocumentSetProjects(parent) {
73515
- parent.command("set-projects").description("Replace a document's project memberships with exactly the given set (or --clear to remove all).").argument("<document-id>", "UUID of the document.").argument("[project-names...]", "Project names to set (created if missing). Omit and pass --clear to remove all.").option("--clear", "Remove the document from all projects.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action13);
73578
+ parent.command("set-projects").description("Replace a document's project memberships with exactly the given set (or --clear to remove all).").argument("<document-id>", "UUID of the document.").argument("[project-names...]", "Project names to set (created if missing). Omit and pass --clear to remove all.").option("--clear", "Remove the document from all projects.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action14);
73516
73579
  }
73517
73580
 
73518
73581
  // src/cli/commands/guides.ts
@@ -73562,7 +73625,7 @@ function registerGuides(parent) {
73562
73625
  // src/cli/commands/project-create.ts
73563
73626
  init_cli_core();
73564
73627
  init_client();
73565
- async function action14(name, options) {
73628
+ async function action15(name, options) {
73566
73629
  const trimmed = name.trim();
73567
73630
  if (!trimmed)
73568
73631
  throw userError("Project name is required.");
@@ -73574,14 +73637,14 @@ async function action14(name, options) {
73574
73637
  println(c.green(`✓ Created project "${data.name}" (id: ${data.id}).`));
73575
73638
  }
73576
73639
  function registerProjectCreate(parent) {
73577
- parent.command("create").description("Create a new (empty) project.").argument("<name>", "Project name (must be unique).").option("--description <text>", "Optional project description.").action(action14);
73640
+ parent.command("create").description("Create a new (empty) project.").argument("<name>", "Project name (must be unique).").option("--description <text>", "Optional project description.").action(action15);
73578
73641
  }
73579
73642
 
73580
73643
  // src/cli/commands/project-edit.ts
73581
73644
  init_cli_core();
73582
73645
  init_client();
73583
73646
  var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
73584
- async function action15(target, options) {
73647
+ async function action16(target, options) {
73585
73648
  const update = {};
73586
73649
  if (options.name !== undefined) {
73587
73650
  const n = options.name.trim();
@@ -73608,7 +73671,7 @@ async function action15(target, options) {
73608
73671
  println(c.green(`✓ Updated project "${data.name}" (id: ${data.id}).`));
73609
73672
  }
73610
73673
  function registerProjectEdit(parent) {
73611
- parent.command("edit").description("Rename a project and/or change its description.").argument("<name-or-id>", "Project name (exact match) or UUID.").option("--name <new-name>", "New project name.").option("--description <text>", "New project description.").action(action15);
73674
+ parent.command("edit").description("Rename a project and/or change its description.").argument("<name-or-id>", "Project name (exact match) or UUID.").option("--name <new-name>", "New project name.").option("--description <text>", "New project description.").action(action16);
73612
73675
  }
73613
73676
 
73614
73677
  // src/cli/commands/version-archive.ts
@@ -77430,14 +77493,9 @@ async function checkEmbedderMismatch() {
77430
77493
  };
77431
77494
  }
77432
77495
  }
77433
- var CONTENT_FORMAT_CHECK_NAME = "content format";
77434
- async function checkContentFormat() {
77435
- const settings = loadSettings();
77436
- if (!settings.supabaseUrl || !settings.supabaseKey) {
77437
- return { name: CONTENT_FORMAT_CHECK_NAME, status: "skipped", detail: "Supabase config missing; skipped." };
77438
- }
77496
+ async function probeRpcJson(settings, fn) {
77439
77497
  try {
77440
- const url = `${settings.supabaseUrl.replace(/\/$/, "")}/rest/v1/rpc/cerefox_content_format_stats`;
77498
+ const url = `${settings.supabaseUrl.replace(/\/$/, "")}/rest/v1/rpc/${fn}`;
77441
77499
  const resp = await fetch(url, {
77442
77500
  method: "POST",
77443
77501
  headers: {
@@ -77447,14 +77505,38 @@ async function checkContentFormat() {
77447
77505
  },
77448
77506
  body: "{}"
77449
77507
  });
77450
- if (!resp.ok) {
77508
+ if (resp.status === 404)
77509
+ return { kind: "absent" };
77510
+ if (!resp.ok)
77511
+ return { kind: "error", detail: `HTTP ${resp.status}` };
77512
+ return { kind: "ok", rows: await resp.json() };
77513
+ } catch (err) {
77514
+ return { kind: "error", detail: err instanceof Error ? err.message : String(err) };
77515
+ }
77516
+ }
77517
+ var CONTENT_FORMAT_CHECK_NAME = "content format";
77518
+ async function checkContentFormat() {
77519
+ const settings = loadSettings();
77520
+ if (!settings.supabaseUrl || !settings.supabaseKey) {
77521
+ return { name: CONTENT_FORMAT_CHECK_NAME, status: "skipped", detail: "Supabase config missing; skipped." };
77522
+ }
77523
+ {
77524
+ const probe = await probeRpcJson(settings, "cerefox_content_format_stats");
77525
+ if (probe.kind === "absent") {
77451
77526
  return {
77452
77527
  name: CONTENT_FORMAT_CHECK_NAME,
77453
77528
  status: "skipped",
77454
- detail: `format stats unavailable (${resp.status}); deploy schema 0.8.0 to enable.`
77529
+ detail: "format stats unavailable; deploy schema 0.8.0 to enable."
77455
77530
  };
77456
77531
  }
77457
- const rows = await resp.json();
77532
+ if (probe.kind === "error") {
77533
+ return {
77534
+ name: CONTENT_FORMAT_CHECK_NAME,
77535
+ status: "skipped",
77536
+ detail: `content-format check skipped (${probe.detail}).`
77537
+ };
77538
+ }
77539
+ const rows = probe.rows;
77458
77540
  const legacy = rows[0]?.legacy_docs ?? 0;
77459
77541
  const total = rows[0]?.total_docs ?? 0;
77460
77542
  if (legacy === 0) {
@@ -77470,13 +77552,39 @@ async function checkContentFormat() {
77470
77552
  detail: `${legacy} of ${total} document(s) use the legacy reconstruction format (format 1).`,
77471
77553
  hint: "Harmless — they auto-convert on next edit. To convert them all now: `cerefox server migrate-format` (re-embeds, so try `--dry-run` first). To read what chunk formats are: `cerefox guides show content-format`."
77472
77554
  };
77473
- } catch (err) {
77555
+ }
77556
+ }
77557
+ var METADATA_HEALTH_CHECK_NAME = "metadata health";
77558
+ async function checkMetadataHealth() {
77559
+ const settings = loadSettings();
77560
+ if (!settings.supabaseUrl || !settings.supabaseKey) {
77561
+ return { name: METADATA_HEALTH_CHECK_NAME, status: "skipped", detail: "Supabase config missing; skipped." };
77562
+ }
77563
+ const probe = await probeRpcJson(settings, "cerefox_metadata_health");
77564
+ if (probe.kind === "absent") {
77474
77565
  return {
77475
- name: CONTENT_FORMAT_CHECK_NAME,
77566
+ name: METADATA_HEALTH_CHECK_NAME,
77567
+ status: "skipped",
77568
+ detail: "metadata-health RPC not deployed; deploy schema 0.12.2 to enable."
77569
+ };
77570
+ }
77571
+ if (probe.kind === "error") {
77572
+ return {
77573
+ name: METADATA_HEALTH_CHECK_NAME,
77476
77574
  status: "skipped",
77477
- detail: `content-format check skipped: ${err instanceof Error ? err.message : String(err)}`
77575
+ detail: `metadata-health check skipped (${probe.detail}).`
77478
77576
  };
77479
77577
  }
77578
+ if (probe.rows.length === 0) {
77579
+ return { name: METADATA_HEALTH_CHECK_NAME, status: "ok", detail: "all document metadata is well-formed" };
77580
+ }
77581
+ const sample = probe.rows.slice(0, 3).map((r) => `"${r.document_title}" (${r.metadata_type})`).join(", ");
77582
+ return {
77583
+ name: METADATA_HEALTH_CHECK_NAME,
77584
+ status: "skipped",
77585
+ detail: `${probe.rows.length} document(s) hold non-object metadata: ${sample}${probe.rows.length > 3 ? ", …" : ""}.`,
77586
+ hint: "Writes that would merge onto these rows are refused (#212). Repair each with `cerefox document set-metadata <id> --replace --json '<the intended object>'`."
77587
+ };
77480
77588
  }
77481
77589
  function hasCerefoxInJsonFile(path) {
77482
77590
  if (!existsSync10(path))
@@ -77687,6 +77795,7 @@ async function runAllChecks(opts = {}) {
77687
77795
  { name: "schema + RPCs", phase: "Reading schema + RPC version", run: () => checkSchemaVersion() },
77688
77796
  { name: "embedder", phase: "Checking embedder consistency", run: () => checkEmbedderMismatch() },
77689
77797
  { name: "content format", phase: "Checking chunk reconstruction format", run: () => checkContentFormat() },
77798
+ { name: "metadata health", phase: "Checking metadata well-formedness", run: () => checkMetadataHealth() },
77690
77799
  { name: "edge functions", phase: "Probing Edge Function versions", run: () => checkEdgeFunctionsCompat() },
77691
77800
  { name: "postgres", phase: "Probing Postgres DDL endpoint", run: () => checkPostgres() },
77692
77801
  { name: "mcp clients", phase: "Scanning MCP client configs", run: () => checkMcpConfigs() }
@@ -77715,7 +77824,7 @@ function symbol(status) {
77715
77824
  return cErr.dim("ℹ");
77716
77825
  }
77717
77826
  }
77718
- async function action16(options) {
77827
+ async function action17(options) {
77719
77828
  const useSpinner = !options.json && process.stderr.isTTY;
77720
77829
  const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
77721
77830
  const results = await runAllChecks({
@@ -77757,6 +77866,10 @@ async function action16(options) {
77757
77866
  }
77758
77867
  if (remediation) {
77759
77868
  println(cErr.yellow("→ " + remediation));
77869
+ const configDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
77870
+ if (configDir) {
77871
+ println(cErr.dim(` (this doctor ran against CEREFOX_CONFIG_DIR=${configDir} — ` + `prefix the command above the same way, or use your environment alias, ` + `or a bare \`cerefox\` will act on your DEFAULT environment instead)`));
77872
+ }
77760
77873
  println("");
77761
77874
  }
77762
77875
  }
@@ -77779,13 +77892,13 @@ async function action16(options) {
77779
77892
  process.exit(1);
77780
77893
  }
77781
77894
  function registerDoctor(program2) {
77782
- program2.command("doctor").description("Run diagnostic checks against the installed Cerefox.").option("--json", "Emit machine-readable JSON (no colours, structured output).").option("--strict", "Exit non-zero when any check warns (default: only errors fail).").action(action16);
77895
+ program2.command("doctor").description("Run diagnostic checks against the installed Cerefox.").option("--json", "Emit machine-readable JSON (no colours, structured output).").option("--strict", "Exit non-zero when any check warns (default: only errors fail).").action(action17);
77783
77896
  }
77784
77897
 
77785
77898
  // src/cli/commands/get-audit-log.ts
77786
77899
  init_cli_core();
77787
77900
  init_client();
77788
- async function action17(options) {
77901
+ async function action18(options) {
77789
77902
  const limit = parsePositiveInt(options.limit, "--limit", 50);
77790
77903
  const client = getClient();
77791
77904
  const data = await client.rpc("cerefox_list_audit_entries", {
@@ -77823,7 +77936,7 @@ async function action17(options) {
77823
77936
  })));
77824
77937
  }
77825
77938
  function registerGetAuditLog(program2) {
77826
- 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(action17);
77939
+ 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(action18);
77827
77940
  }
77828
77941
 
77829
77942
  // src/cli/commands/relation.ts
@@ -77959,7 +78072,7 @@ init_cli_core();
77959
78072
  init_cli_core();
77960
78073
  init_partial_edits();
77961
78074
  init_client();
77962
- async function action18(documentId, options) {
78075
+ async function action19(documentId, options) {
77963
78076
  const section = (options.section ?? "").trim() || null;
77964
78077
  if (section && options.outline) {
77965
78078
  throw userError("Pass either --outline (the whole structure) or --section (one section's text), not both.");
@@ -78056,7 +78169,7 @@ async function action18(documentId, options) {
78056
78169
  println(doc.full_content);
78057
78170
  }
78058
78171
  function registerGetDoc(program2) {
78059
- 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.").option("--outline", "Show the heading structure, per-section sizes and content_hash instead of the content. Cheap, and the paths are the anchors the edit commands take.").option("--section <anchor>", "Show ONE section's text instead of the whole document: exactly what a replace_section on this anchor would overwrite. Pass the bare heading line when it is unique, or the full ' > ' path from --outline when it repeats.").option("--section-part <part>", "own_body | subtree — only when the target section has child sections, where 'the end' means two different places. You are told (with both options) whenever it is needed.").action(action18);
78172
+ 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.").option("--outline", "Show the heading structure, per-section sizes and content_hash instead of the content. Cheap, and the paths are the anchors the edit commands take.").option("--section <anchor>", "Show ONE section's text instead of the whole document: exactly what a replace_section on this anchor would overwrite. Pass the bare heading line when it is unique, or the full ' > ' path from --outline when it repeats.").option("--section-part <part>", "own_body | subtree — only when the target section has child sections, where 'the end' means two different places. You are told (with both options) whenever it is needed.").action(action19);
78060
78173
  }
78061
78174
 
78062
78175
  // src/cli/commands/ingest.ts
@@ -78720,7 +78833,7 @@ async function readContent(path, paste) {
78720
78833
  const titleFromPath = basename2(path, extname3(path));
78721
78834
  return { content, titleFromPath };
78722
78835
  }
78723
- async function action19(path, options) {
78836
+ async function action20(path, options) {
78724
78837
  const { content, titleFromPath } = await readContent(path, Boolean(options.paste));
78725
78838
  const updatingById = Boolean(options.documentId);
78726
78839
  let title = options.title ?? (updatingById ? null : titleFromPath);
@@ -78811,7 +78924,7 @@ async function action19(path, options) {
78811
78924
  }
78812
78925
  }
78813
78926
  function registerIngest(program2) {
78814
- 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. Omit it on an update and the document keeps the source it already has (#193); omit it on a create and it is recorded as "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("--expected-content-hash <sha256>", "Optimistic-concurrency token: the content_hash of the version this edit is based on (shown by `document get` / `search`). Required on content updates unless --last-write-wins.").option("--last-write-wins", "Skip the concurrency check and overwrite regardless of concurrent changes (recorded in the audit log).").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action19);
78927
+ 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. Omit it on an update and the document keeps the source it already has (#193); omit it on a create and it is recorded as "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("--expected-content-hash <sha256>", "Optimistic-concurrency token: the content_hash of the version this edit is based on (shown by `document get` / `search`). Required on content updates unless --last-write-wins.").option("--last-write-wins", "Skip the concurrency check and overwrite regardless of concurrent changes (recorded in the audit log).").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action20);
78815
78928
  }
78816
78929
 
78817
78930
  // src/cli/commands/document-partial-edit.ts
@@ -78914,7 +79027,7 @@ function walk(dir, extensions) {
78914
79027
  }
78915
79028
  return files;
78916
79029
  }
78917
- async function action20(dir, options) {
79030
+ async function action21(dir, options) {
78918
79031
  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));
78919
79032
  const files = walk(dir, extensions);
78920
79033
  if (files.length === 0) {
@@ -78989,7 +79102,7 @@ async function action20(dir, options) {
78989
79102
  }
78990
79103
  }
78991
79104
  function registerIngestDir(program2) {
78992
- 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. Omit it and each matched document keeps the source it already has (#193); newly created ones are recorded as "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(action20);
79105
+ 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. Omit it and each matched document keeps the source it already has (#193); newly created ones are recorded as "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(action21);
78993
79106
  }
78994
79107
 
78995
79108
  // src/cli/commands/init.ts
@@ -79320,7 +79433,7 @@ function writeAnswersTo(target, answers) {
79320
79433
  }
79321
79434
  }
79322
79435
  }
79323
- async function action22(options) {
79436
+ async function action23(options) {
79324
79437
  const homeEnv = join11(homedir7(), USER_STATE_DIR_NAME, ".env");
79325
79438
  const cwdEnv = join11(process.cwd(), ".env");
79326
79439
  const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
@@ -79429,13 +79542,13 @@ async function action22(options) {
79429
79542
  await postWriteLifecycle(target, options);
79430
79543
  }
79431
79544
  function registerInit(program2) {
79432
- 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(action22);
79545
+ 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(action23);
79433
79546
  }
79434
79547
 
79435
79548
  // src/cli/commands/list-docs.ts
79436
79549
  init_cli_core();
79437
79550
  init_client();
79438
- async function action23(options) {
79551
+ async function action24(options) {
79439
79552
  const deleted = !!options.deleted;
79440
79553
  const limit = parsePositiveInt(options.limit, "--limit", 100);
79441
79554
  const client = getClient();
@@ -79491,13 +79604,13 @@ async function action23(options) {
79491
79604
  }));
79492
79605
  }
79493
79606
  function registerListDocs(program2) {
79494
- 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("--deleted", "List soft-deleted (trashed) documents instead of active ones.").option("--json", "Emit machine-readable JSON.").action(action23);
79607
+ 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("--deleted", "List soft-deleted (trashed) documents instead of active ones.").option("--json", "Emit machine-readable JSON.").action(action24);
79495
79608
  }
79496
79609
 
79497
79610
  // src/cli/commands/list-metadata-keys.ts
79498
79611
  init_cli_core();
79499
79612
  init_client();
79500
- async function action24(options) {
79613
+ async function action25(options) {
79501
79614
  const client = getClient();
79502
79615
  const data = await client.rpc("cerefox_list_metadata_keys");
79503
79616
  if (data === null) {
@@ -79525,13 +79638,13 @@ async function action24(options) {
79525
79638
  })));
79526
79639
  }
79527
79640
  function registerListMetadataKeys(program2) {
79528
- 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(action24);
79641
+ 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(action25);
79529
79642
  }
79530
79643
 
79531
79644
  // src/cli/commands/list-projects.ts
79532
79645
  init_cli_core();
79533
79646
  init_client();
79534
- async function action25(options) {
79647
+ async function action26(options) {
79535
79648
  const client = getClient();
79536
79649
  const { data, error: error2 } = await client.raw.from("cerefox_projects").select("id, name, description, created_at").order("name", { ascending: true });
79537
79650
  if (error2) {
@@ -79560,13 +79673,13 @@ async function action25(options) {
79560
79673
  })), "(no projects)");
79561
79674
  }
79562
79675
  function registerListProjects(program2) {
79563
- 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(action25);
79676
+ 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(action26);
79564
79677
  }
79565
79678
 
79566
79679
  // src/cli/commands/list-versions.ts
79567
79680
  init_cli_core();
79568
79681
  init_client();
79569
- async function action26(documentId, options) {
79682
+ async function action27(documentId, options) {
79570
79683
  const client = getClient();
79571
79684
  const data = await client.rpc("cerefox_list_document_versions", {
79572
79685
  p_document_id: documentId
@@ -79607,7 +79720,7 @@ async function action26(documentId, options) {
79607
79720
  })));
79608
79721
  }
79609
79722
  function registerListVersions(program2) {
79610
- 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(action26);
79723
+ 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(action27);
79611
79724
  }
79612
79725
 
79613
79726
  // src/cli/commands/mcp.ts
@@ -79639,7 +79752,7 @@ function registerEmbedderWarmup(program2) {
79639
79752
  // src/cli/commands/metadata-search.ts
79640
79753
  init_cli_core();
79641
79754
  init_client();
79642
- async function action27(options) {
79755
+ async function action28(options) {
79643
79756
  const metadataFilter = parseJsonObjectArg(options.metadataFilter, "--metadata-filter") ?? {};
79644
79757
  if (Object.keys(metadataFilter).length === 0 && !options.projectName && !options.updatedSince && !options.createdSince) {
79645
79758
  throw userError("Provide at least one of: --metadata-filter, --project-name, --updated-since, or --created-since.", `Examples: --metadata-filter '{"type":"decision-log"}' · --project-name "research" (lists that project's docs).`);
@@ -79702,7 +79815,7 @@ async function action27(options) {
79702
79815
  }
79703
79816
  }
79704
79817
  function registerMetadataSearch(program2) {
79705
- program2.command("metadata-search").description("Find or list documents by metadata, project, or time criteria (no text query).").option("-f, --metadata-filter <json>", "JSON object; only docs whose metadata contains ALL pairs are returned. Optional — omit to list by --project-name / time range alone (at least one criterion is required).").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(action27);
79818
+ program2.command("metadata-search").description("Find or list documents by metadata, project, or time criteria (no text query).").option("-f, --metadata-filter <json>", "JSON object; only docs whose metadata contains ALL pairs are returned. Optional — omit to list by --project-name / time range alone (at least one criterion is required).").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(action28);
79706
79819
  }
79707
79820
 
79708
79821
  // src/cli/commands/reindex.ts
@@ -79728,7 +79841,7 @@ function warnLargeBulkWrite(opts) {
79728
79841
  }
79729
79842
 
79730
79843
  // src/cli/commands/reindex.ts
79731
- async function action28(options) {
79844
+ async function action29(options) {
79732
79845
  const settings = loadSettings();
79733
79846
  if (!settings.supabaseUrl || !settings.supabaseKey) {
79734
79847
  throw userError("Supabase credentials not configured — run `cerefox init` first.");
@@ -79818,7 +79931,7 @@ ${c2.content}`;
79818
79931
  }
79819
79932
  }
79820
79933
  function registerReindex(program2) {
79821
- 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(action28);
79934
+ 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(action29);
79822
79935
  }
79823
79936
 
79824
79937
  // src/cli/commands/migrate-format.ts
@@ -79826,7 +79939,7 @@ init_cli_core();
79826
79939
  init_config();
79827
79940
  init_client();
79828
79941
  var CURRENT_FORMAT = 2;
79829
- async function action29(options) {
79942
+ async function action30(options) {
79830
79943
  const settings = loadSettings();
79831
79944
  const client = getClient();
79832
79945
  const supabase = client.raw;
@@ -79953,7 +80066,7 @@ async function action29(options) {
79953
80066
  }
79954
80067
  }
79955
80068
  function registerMigrateFormat(program2) {
79956
- program2.command("migrate-format").description("Convert legacy-format documents to the current chunk format (re-chunks + re-embeds).").option("--dry-run", "Report how many documents would be converted; write nothing.").option("-l, --limit <n>", "Convert at most N documents (re-run to continue).").option("--document-id <uuid>", "Convert a single document.").option("--author <name>", "Recorded in the audit log for each conversion.").action(action29);
80069
+ program2.command("migrate-format").description("Convert legacy-format documents to the current chunk format (re-chunks + re-embeds).").option("--dry-run", "Report how many documents would be converted; write nothing.").option("-l, --limit <n>", "Convert at most N documents (re-run to continue).").option("--document-id <uuid>", "Convert a single document.").option("--author <name>", "Recorded in the audit log for each conversion.").action(action30);
79957
80070
  }
79958
80071
 
79959
80072
  // src/cli/commands/restore.ts
@@ -79983,7 +80096,7 @@ function resolveBackupFile(target) {
79983
80096
  }
79984
80097
  return join12(path, candidates[0].name);
79985
80098
  }
79986
- async function action30(target, options) {
80099
+ async function action31(target, options) {
79987
80100
  const file = resolveBackupFile(target);
79988
80101
  let payload;
79989
80102
  try {
@@ -80118,7 +80231,7 @@ async function action30(target, options) {
80118
80231
  }
80119
80232
  }
80120
80233
  function registerRestore(program2) {
80121
- 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 are restored from the backup itself (format 2+).").action(action30);
80234
+ 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 are restored from the backup itself (format 2+).").action(action31);
80122
80235
  }
80123
80236
 
80124
80237
  // src/cli/commands/search.ts
@@ -80143,7 +80256,7 @@ async function embedQuery(query) {
80143
80256
  }
80144
80257
 
80145
80258
  // src/cli/commands/search.ts
80146
- async function action31(query, options) {
80259
+ async function action32(query, options) {
80147
80260
  if (!query || query.trim() === "") {
80148
80261
  throw userError("Empty query.");
80149
80262
  }
@@ -80309,7 +80422,7 @@ async function action31(query, options) {
80309
80422
  }
80310
80423
  }
80311
80424
  function registerSearch(program2) {
80312
- 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: CEREFOX_SEARCH_ALPHA; else 0.7).").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE; else 0.5, or 0.6 with the local embedder).").option("--min-term-coverage <float>", "OR-fallback keyword matches must cover at least this fraction of the query's meaningful terms to count as confident hits (default: CEREFOX_MIN_TERM_COVERAGE; else the server default 0.5; needs schema ≥ 0.9.1).").option("--max-bytes <n>", "Response size budget in bytes (default: CEREFOX_MAX_RESPONSE_BYTES or 200000).").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").option("--only-metadata", "List matching docs (id, score, chunks, chars, partial/full) WITHOUT their content — like the web UI's collapsed result list. Grab a [id:…] then `cerefox document get <id>`.").action(action31);
80425
+ 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: CEREFOX_SEARCH_ALPHA; else 0.7).").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE; else 0.5, or 0.6 with the local embedder).").option("--min-term-coverage <float>", "OR-fallback keyword matches must cover at least this fraction of the query's meaningful terms to count as confident hits (default: CEREFOX_MIN_TERM_COVERAGE; else the server default 0.5; needs schema ≥ 0.9.1).").option("--max-bytes <n>", "Response size budget in bytes (default: CEREFOX_MAX_RESPONSE_BYTES or 200000).").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").option("--only-metadata", "List matching docs (id, score, chunks, chars, partial/full) WITHOUT their content — like the web UI's collapsed result list. Grab a [id:…] then `cerefox document get <id>`.").action(action32);
80313
80426
  }
80314
80427
 
80315
80428
  // src/cli/commands/self-update.ts
@@ -80356,7 +80469,7 @@ async function fetchLatestVersion() {
80356
80469
  }
80357
80470
  return body.version;
80358
80471
  }
80359
- async function action32(options) {
80472
+ async function action33(options) {
80360
80473
  let target;
80361
80474
  try {
80362
80475
  target = options.version ?? await fetchLatestVersion();
@@ -80409,7 +80522,7 @@ async function action32(options) {
80409
80522
  }
80410
80523
  function registerSelfUpdate(program2) {
80411
80524
  const desc = "Upgrade Cerefox in place. Alias: `cerefox upgrade`.";
80412
- 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(action32);
80525
+ 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(action33);
80413
80526
  declaration(program2.command("self-update"));
80414
80527
  declaration(program2.command("upgrade"));
80415
80528
  }
@@ -80428,7 +80541,7 @@ function symbol2(status) {
80428
80541
  return cErr.dim("ℹ");
80429
80542
  }
80430
80543
  }
80431
- async function action33(options) {
80544
+ async function action34(options) {
80432
80545
  const useSpinner = !options.json && process.stderr.isTTY;
80433
80546
  const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
80434
80547
  const results = await runFastChecks({
@@ -80449,7 +80562,7 @@ async function action33(options) {
80449
80562
  }
80450
80563
  }
80451
80564
  function registerStatus(program2) {
80452
- program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(action33);
80565
+ program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(action34);
80453
80566
  }
80454
80567
 
80455
80568
  // src/cli/commands/token.ts
@@ -80482,10 +80595,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
80482
80595
  }
80483
80596
  const re = new RegExp(`^(\\s*)${escapeRegExp(key)}=.*$`, "m");
80484
80597
  let next;
80485
- let action34;
80598
+ let action35;
80486
80599
  if (re.test(original)) {
80487
80600
  next = original.replace(re, `$1${line}`);
80488
- action34 = "updated";
80601
+ action35 = "updated";
80489
80602
  } else {
80490
80603
  const base = original.endsWith(`
80491
80604
  `) ? original : `${original}
@@ -80493,10 +80606,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
80493
80606
  next = `${base}
80494
80607
  ${header}${line}
80495
80608
  `;
80496
- action34 = "added";
80609
+ action35 = "added";
80497
80610
  }
80498
80611
  writeFileSync5(path, next);
80499
- return { path, action: action34, backupPath };
80612
+ return { path, action: action35, backupPath };
80500
80613
  }
80501
80614
  function readEnvVar(path, key) {
80502
80615
  if (!existsSync14(path))
@@ -85069,6 +85182,9 @@ function registerDocumentWriteRoutes(app, ctx) {
85069
85182
  const title = String(body.title ?? "").trim();
85070
85183
  const content = String(body.content ?? "");
85071
85184
  const projectIds = Array.isArray(body.project_ids) ? body.project_ids : [];
85185
+ if (body.metadata !== undefined && body.metadata !== null && (typeof body.metadata !== "object" || Array.isArray(body.metadata))) {
85186
+ return c2.json({ success: false, error: "metadata must be a JSON object of key/value pairs" }, 400);
85187
+ }
85072
85188
  const metadata = body.metadata ?? {};
85073
85189
  const doc2 = await getCurrentDoc(ctx, documentId);
85074
85190
  if (!doc2) {
@@ -86334,6 +86450,7 @@ Learn more:
86334
86450
  moveInto(document2, registerGetDoc, "get");
86335
86451
  moveInto(document2, registerListDocs, "list");
86336
86452
  moveInto(document2, registerDeleteDoc, "delete");
86453
+ moveInto(document2, registerDocumentDeadLinks, "dead-links");
86337
86454
  registerDocumentRestore(document2);
86338
86455
  registerDocumentEdit(document2);
86339
86456
  registerDocumentSetProjects(document2);