@cerefox/memory 1.1.0-beta.1 → 1.1.0-beta.3

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,9 +7438,143 @@ var exports_meta = {};
7438
7438
  __export(exports_meta, {
7439
7439
  PKG_VERSION: () => PKG_VERSION
7440
7440
  });
7441
- var PKG_VERSION = "1.1.0-beta.1";
7441
+ var PKG_VERSION = "1.1.0-beta.3";
7442
7442
  var init_meta = () => {};
7443
7443
 
7444
+ // ../../_shared/config/paths.ts
7445
+ import { existsSync, readFileSync } from "node:fs";
7446
+ import { homedir } from "node:os";
7447
+ import { join, resolve as resolvePath } from "node:path";
7448
+ import { cwd as processCwd, env } from "node:process";
7449
+ function expandTilde(p) {
7450
+ if (p === "~" || p.startsWith("~/")) {
7451
+ return join(homedir(), p.slice(2));
7452
+ }
7453
+ return p;
7454
+ }
7455
+ function userStateDirAbs(opts) {
7456
+ return resolvePath(join(opts.home ?? homedir(), USER_STATE_DIR_NAME));
7457
+ }
7458
+ function resolveConfigDir(opts = {}) {
7459
+ const override = (env.CEREFOX_CONFIG_DIR ?? "").trim();
7460
+ if (override) {
7461
+ return resolvePath(expandTilde(override));
7462
+ }
7463
+ const userState = userStateDirAbs(opts);
7464
+ if (existsSync(join(userState, ".env"))) {
7465
+ return userState;
7466
+ }
7467
+ const here = opts.cwd ?? processCwd();
7468
+ const cwdEnv = join(here, ".env");
7469
+ if (existsSync(cwdEnv) && cwdEnvHasCerefoxKey(cwdEnv)) {
7470
+ return resolvePath(here);
7471
+ }
7472
+ return userState;
7473
+ }
7474
+ function cwdEnvHasCerefoxKey(envPath) {
7475
+ try {
7476
+ const contents = readFileSync(envPath, "utf8");
7477
+ return /^\s*CEREFOX_[A-Z0-9_]+\s*=/m.test(contents);
7478
+ } catch {
7479
+ return false;
7480
+ }
7481
+ }
7482
+ function resolveEnvFile(opts = {}) {
7483
+ return join(resolveConfigDir(opts), ".env");
7484
+ }
7485
+ function userStateDir(opts = {}) {
7486
+ return userStateDirAbs(opts);
7487
+ }
7488
+ function isDevMode(opts = {}) {
7489
+ if ((env.CEREFOX_CONFIG_DIR ?? "").trim())
7490
+ return false;
7491
+ if (existsSync(join(userStateDirAbs(opts), ".env")))
7492
+ return false;
7493
+ const here = opts.cwd ?? processCwd();
7494
+ const cwdEnv = join(here, ".env");
7495
+ return existsSync(cwdEnv) && cwdEnvHasCerefoxKey(cwdEnv);
7496
+ }
7497
+ var USER_STATE_DIR_NAME = ".cerefox";
7498
+ var init_paths = () => {};
7499
+
7500
+ // ../../_shared/config/env.ts
7501
+ import { readFileSync as readFileSync2 } from "node:fs";
7502
+ import { env as env2 } from "node:process";
7503
+ function parseDotenv(content) {
7504
+ const result = {};
7505
+ for (const rawLine of content.split(/\r?\n/)) {
7506
+ const line = rawLine.trim();
7507
+ if (!line || line.startsWith("#"))
7508
+ continue;
7509
+ const match = line.match(KV_LINE);
7510
+ if (!match)
7511
+ continue;
7512
+ let value = match[2];
7513
+ if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
7514
+ value = value.slice(1, -1);
7515
+ }
7516
+ result[match[1]] = value;
7517
+ }
7518
+ return result;
7519
+ }
7520
+ function loadEnv(opts = {}) {
7521
+ if (_loaded) {
7522
+ return { path: "(already loaded)", vars: 0 };
7523
+ }
7524
+ _loaded = true;
7525
+ const envPath = resolveEnvFile(opts);
7526
+ let content;
7527
+ try {
7528
+ content = readFileSync2(envPath, "utf8");
7529
+ } catch {
7530
+ return { path: envPath, vars: 0 };
7531
+ }
7532
+ const configDirNamed = (env2.CEREFOX_CONFIG_DIR ?? "").trim() !== "";
7533
+ let count = 0;
7534
+ for (const [k, v] of Object.entries(parseDotenv(content))) {
7535
+ if (k === "CEREFOX_CONFIG_DIR")
7536
+ continue;
7537
+ if (env2[k] === undefined || configDirNamed) {
7538
+ env2[k] = v;
7539
+ count++;
7540
+ }
7541
+ }
7542
+ return { path: envPath, vars: count };
7543
+ }
7544
+ function loadSettings(opts = {}) {
7545
+ loadEnv(opts);
7546
+ return {
7547
+ supabaseUrl: env2.CEREFOX_SUPABASE_URL ?? "",
7548
+ supabaseKey: env2.CEREFOX_SUPABASE_KEY ?? "",
7549
+ supabaseAnonKey: env2.CEREFOX_SUPABASE_ANON_KEY ?? "",
7550
+ accessToken: env2.CEREFOX_ACCESS_TOKEN ?? "",
7551
+ databaseUrl: env2.CEREFOX_DATABASE_URL ?? "",
7552
+ openaiApiKey: env2.CEREFOX_OPENAI_API_KEY ?? env2.OPENAI_API_KEY ?? "",
7553
+ fireworksApiKey: env2.CEREFOX_FIREWORKS_API_KEY ?? ""
7554
+ };
7555
+ }
7556
+ var KV_LINE, _loaded = false;
7557
+ var init_env = __esm(() => {
7558
+ init_paths();
7559
+ KV_LINE = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/;
7560
+ });
7561
+
7562
+ // ../../_shared/config/index.ts
7563
+ var exports_config = {};
7564
+ __export(exports_config, {
7565
+ userStateDir: () => userStateDir,
7566
+ resolveEnvFile: () => resolveEnvFile,
7567
+ resolveConfigDir: () => resolveConfigDir,
7568
+ loadSettings: () => loadSettings,
7569
+ loadEnv: () => loadEnv,
7570
+ isDevMode: () => isDevMode,
7571
+ USER_STATE_DIR_NAME: () => USER_STATE_DIR_NAME
7572
+ });
7573
+ var init_config = __esm(() => {
7574
+ init_paths();
7575
+ init_env();
7576
+ });
7577
+
7444
7578
  // ../../node_modules/.bun/tslib@2.8.1/node_modules/tslib/tslib.js
7445
7579
  var require_tslib = __commonJS((exports, module) => {
7446
7580
  var __extends;
@@ -7935,7 +8069,7 @@ var require_tslib = __commonJS((exports, module) => {
7935
8069
  throw new TypeError("Cannot use 'in' operator on non-object");
7936
8070
  return typeof state === "function" ? receiver === state : state.has(receiver);
7937
8071
  };
7938
- __addDisposableResource = function(env, value, async) {
8072
+ __addDisposableResource = function(env3, value, async) {
7939
8073
  if (value !== null && value !== undefined) {
7940
8074
  if (typeof value !== "object" && typeof value !== "function")
7941
8075
  throw new TypeError("Object expected.");
@@ -7962,9 +8096,9 @@ var require_tslib = __commonJS((exports, module) => {
7962
8096
  return Promise.reject(e);
7963
8097
  }
7964
8098
  };
7965
- env.stack.push({ value, dispose, async });
8099
+ env3.stack.push({ value, dispose, async });
7966
8100
  } else if (async) {
7967
- env.stack.push({ async: true });
8101
+ env3.stack.push({ async: true });
7968
8102
  }
7969
8103
  return value;
7970
8104
  };
@@ -7972,17 +8106,17 @@ var require_tslib = __commonJS((exports, module) => {
7972
8106
  var e = new Error(message);
7973
8107
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
7974
8108
  };
7975
- __disposeResources = function(env) {
8109
+ __disposeResources = function(env3) {
7976
8110
  function fail(e) {
7977
- env.error = env.hasError ? new _SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
7978
- env.hasError = true;
8111
+ env3.error = env3.hasError ? new _SuppressedError(e, env3.error, "An error was suppressed during disposal.") : e;
8112
+ env3.hasError = true;
7979
8113
  }
7980
8114
  var r, s = 0;
7981
8115
  function next() {
7982
- while (r = env.stack.pop()) {
8116
+ while (r = env3.stack.pop()) {
7983
8117
  try {
7984
8118
  if (!r.async && s === 1)
7985
- return s = 0, env.stack.push(r), Promise.resolve().then(next);
8119
+ return s = 0, env3.stack.push(r), Promise.resolve().then(next);
7986
8120
  if (r.dispose) {
7987
8121
  var result = r.dispose.call(r.value);
7988
8122
  if (r.async)
@@ -7997,9 +8131,9 @@ var require_tslib = __commonJS((exports, module) => {
7997
8131
  }
7998
8132
  }
7999
8133
  if (s === 1)
8000
- return env.hasError ? Promise.reject(env.error) : Promise.resolve();
8001
- if (env.hasError)
8002
- throw env.error;
8134
+ return env3.hasError ? Promise.reject(env3.error) : Promise.resolve();
8135
+ if (env3.hasError)
8136
+ throw env3.error;
8003
8137
  }
8004
8138
  return next();
8005
8139
  };
@@ -9128,22 +9262,22 @@ var require_websocket_factory = __commonJS((exports) => {
9128
9262
  };
9129
9263
  }
9130
9264
  static getWebSocketConstructor() {
9131
- const env = this.detectEnvironment();
9132
- if (env.wsConstructor) {
9133
- return env.wsConstructor;
9265
+ const env3 = this.detectEnvironment();
9266
+ if (env3.wsConstructor) {
9267
+ return env3.wsConstructor;
9134
9268
  }
9135
- let errorMessage = env.error || "WebSocket not supported in this environment.";
9136
- if (env.workaround) {
9269
+ let errorMessage = env3.error || "WebSocket not supported in this environment.";
9270
+ if (env3.workaround) {
9137
9271
  errorMessage += `
9138
9272
 
9139
- Suggested solution: ${env.workaround}`;
9273
+ Suggested solution: ${env3.workaround}`;
9140
9274
  }
9141
9275
  throw new Error(errorMessage);
9142
9276
  }
9143
9277
  static isWebSocketSupported() {
9144
9278
  try {
9145
- const env = this.detectEnvironment();
9146
- return env.type === "native";
9279
+ const env3 = this.detectEnvironment();
9280
+ return env3.type === "native";
9147
9281
  } catch (_a) {
9148
9282
  return false;
9149
9283
  }
@@ -11592,8 +11726,8 @@ var require_RealtimeChannel = __commonJS((exports) => {
11592
11726
  });
11593
11727
  }
11594
11728
  _notThisChannelEvent(event, ref) {
11595
- const { close, error, leave, join } = constants_1.CHANNEL_EVENTS;
11596
- const events = [close, error, leave, join];
11729
+ const { close, error, leave, join: join2 } = constants_1.CHANNEL_EVENTS;
11730
+ const events = [close, error, leave, join2];
11597
11731
  return ref && events.includes(event) && ref !== this.joinPush.ref;
11598
11732
  }
11599
11733
  _updateFilterTransform() {
@@ -23274,137 +23408,6 @@ var init_db_client = __esm(() => {
23274
23408
  });
23275
23409
  });
23276
23410
 
23277
- // ../../_shared/config/paths.ts
23278
- import { existsSync, readFileSync } from "node:fs";
23279
- import { homedir } from "node:os";
23280
- import { join, resolve as resolvePath } from "node:path";
23281
- import { cwd as processCwd, env } from "node:process";
23282
- function expandTilde(p) {
23283
- if (p === "~" || p.startsWith("~/")) {
23284
- return join(homedir(), p.slice(2));
23285
- }
23286
- return p;
23287
- }
23288
- function userStateDirAbs(opts) {
23289
- return resolvePath(join(opts.home ?? homedir(), USER_STATE_DIR_NAME));
23290
- }
23291
- function resolveConfigDir(opts = {}) {
23292
- const override = (env.CEREFOX_CONFIG_DIR ?? "").trim();
23293
- if (override) {
23294
- return resolvePath(expandTilde(override));
23295
- }
23296
- const userState = userStateDirAbs(opts);
23297
- if (existsSync(join(userState, ".env"))) {
23298
- return userState;
23299
- }
23300
- const here = opts.cwd ?? processCwd();
23301
- const cwdEnv = join(here, ".env");
23302
- if (existsSync(cwdEnv) && cwdEnvHasCerefoxKey(cwdEnv)) {
23303
- return resolvePath(here);
23304
- }
23305
- return userState;
23306
- }
23307
- function cwdEnvHasCerefoxKey(envPath) {
23308
- try {
23309
- const contents = readFileSync(envPath, "utf8");
23310
- return /^\s*CEREFOX_[A-Z0-9_]+\s*=/m.test(contents);
23311
- } catch {
23312
- return false;
23313
- }
23314
- }
23315
- function resolveEnvFile(opts = {}) {
23316
- return join(resolveConfigDir(opts), ".env");
23317
- }
23318
- function userStateDir(opts = {}) {
23319
- return userStateDirAbs(opts);
23320
- }
23321
- function isDevMode(opts = {}) {
23322
- if ((env.CEREFOX_CONFIG_DIR ?? "").trim())
23323
- return false;
23324
- if (existsSync(join(userStateDirAbs(opts), ".env")))
23325
- return false;
23326
- const here = opts.cwd ?? processCwd();
23327
- const cwdEnv = join(here, ".env");
23328
- return existsSync(cwdEnv) && cwdEnvHasCerefoxKey(cwdEnv);
23329
- }
23330
- var USER_STATE_DIR_NAME = ".cerefox";
23331
- var init_paths = () => {};
23332
-
23333
- // ../../_shared/config/env.ts
23334
- import { readFileSync as readFileSync2 } from "node:fs";
23335
- import { env as env2 } from "node:process";
23336
- function parseDotenv(content) {
23337
- const result = {};
23338
- for (const rawLine of content.split(/\r?\n/)) {
23339
- const line = rawLine.trim();
23340
- if (!line || line.startsWith("#"))
23341
- continue;
23342
- const match = line.match(KV_LINE);
23343
- if (!match)
23344
- continue;
23345
- let value = match[2];
23346
- if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
23347
- value = value.slice(1, -1);
23348
- }
23349
- result[match[1]] = value;
23350
- }
23351
- return result;
23352
- }
23353
- function loadEnv(opts = {}) {
23354
- if (_loaded) {
23355
- return { path: "(already loaded)", vars: 0 };
23356
- }
23357
- _loaded = true;
23358
- const envPath = resolveEnvFile(opts);
23359
- let content;
23360
- try {
23361
- content = readFileSync2(envPath, "utf8");
23362
- } catch {
23363
- return { path: envPath, vars: 0 };
23364
- }
23365
- let count = 0;
23366
- for (const [k, v] of Object.entries(parseDotenv(content))) {
23367
- if (env2[k] === undefined) {
23368
- env2[k] = v;
23369
- count++;
23370
- }
23371
- }
23372
- return { path: envPath, vars: count };
23373
- }
23374
- function loadSettings(opts = {}) {
23375
- loadEnv(opts);
23376
- return {
23377
- supabaseUrl: env2.CEREFOX_SUPABASE_URL ?? "",
23378
- supabaseKey: env2.CEREFOX_SUPABASE_KEY ?? "",
23379
- supabaseAnonKey: env2.CEREFOX_SUPABASE_ANON_KEY ?? "",
23380
- accessToken: env2.CEREFOX_ACCESS_TOKEN ?? "",
23381
- databaseUrl: env2.CEREFOX_DATABASE_URL ?? "",
23382
- openaiApiKey: env2.CEREFOX_OPENAI_API_KEY ?? env2.OPENAI_API_KEY ?? "",
23383
- fireworksApiKey: env2.CEREFOX_FIREWORKS_API_KEY ?? ""
23384
- };
23385
- }
23386
- var KV_LINE, _loaded = false;
23387
- var init_env = __esm(() => {
23388
- init_paths();
23389
- KV_LINE = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/;
23390
- });
23391
-
23392
- // ../../_shared/config/index.ts
23393
- var exports_config = {};
23394
- __export(exports_config, {
23395
- userStateDir: () => userStateDir,
23396
- resolveEnvFile: () => resolveEnvFile,
23397
- resolveConfigDir: () => resolveConfigDir,
23398
- loadSettings: () => loadSettings,
23399
- loadEnv: () => loadEnv,
23400
- isDevMode: () => isDevMode,
23401
- USER_STATE_DIR_NAME: () => USER_STATE_DIR_NAME
23402
- });
23403
- var init_config = __esm(() => {
23404
- init_paths();
23405
- init_env();
23406
- });
23407
-
23408
23411
  // src/cli/util/client.ts
23409
23412
  function getClient() {
23410
23413
  const settings = loadSettings();
@@ -55186,6 +55189,36 @@ var init_audit_log = __esm(() => {
55186
55189
  };
55187
55190
  });
55188
55191
 
55192
+ // ../../_shared/mcp-tools/feature-flags.ts
55193
+ async function relationsEnabled(supabase) {
55194
+ if (cached && Date.now() - cached.at < CACHE_TTL_MS)
55195
+ return cached.value;
55196
+ try {
55197
+ const { data, error: error2 } = await supabase.rpc("cerefox_get_config", {
55198
+ p_key: "relations_enabled"
55199
+ });
55200
+ if (error2)
55201
+ throw new Error(error2.message);
55202
+ const value = String(data ?? "").trim().toLowerCase() === "true";
55203
+ cached = { value, at: Date.now() };
55204
+ return value;
55205
+ } catch {
55206
+ return false;
55207
+ }
55208
+ }
55209
+ function disabledToolMessage(name) {
55210
+ return `${name} is part of the document-relations feature, which is off by default. ` + `Enable it with: cerefox config set relations_enabled true ` + `(deployment-wide; every access path picks it up).`;
55211
+ }
55212
+ var RELATION_TOOL_NAMES, CACHE_TTL_MS = 60000, cached = null;
55213
+ var init_feature_flags = __esm(() => {
55214
+ RELATION_TOOL_NAMES = new Set([
55215
+ "cerefox_set_relation",
55216
+ "cerefox_delete_relation",
55217
+ "cerefox_get_relations",
55218
+ "cerefox_get_neighbors"
55219
+ ]);
55220
+ });
55221
+
55189
55222
  // ../../_shared/mcp-tools/types.ts
55190
55223
  var McpInvalidParams;
55191
55224
  var init_types3 = __esm(() => {
@@ -56281,9 +56314,21 @@ var init_set_document_projects = __esm(() => {
56281
56314
  });
56282
56315
 
56283
56316
  // ../../_shared/mcp-tools/index.ts
56317
+ async function listEnabledTools(supabase) {
56318
+ const relations = await relationsEnabled(supabase);
56319
+ return ALL_TOOLS.filter((t) => relations || !RELATION_TOOL_NAMES.has(t.name));
56320
+ }
56321
+ async function assertToolEnabled(supabase, name) {
56322
+ if (!RELATION_TOOL_NAMES.has(name))
56323
+ return;
56324
+ if (await relationsEnabled(supabase))
56325
+ return;
56326
+ throw new McpInvalidParams(disabledToolMessage(name));
56327
+ }
56284
56328
  var ALL_TOOLS, TOOLS_BY_NAME;
56285
56329
  var init_mcp_tools = __esm(() => {
56286
56330
  init_audit_log();
56331
+ init_feature_flags();
56287
56332
  init_relations();
56288
56333
  init_get_document();
56289
56334
  init_get_help();
@@ -56295,6 +56340,7 @@ var init_mcp_tools = __esm(() => {
56295
56340
  init_search();
56296
56341
  init_set_document_projects();
56297
56342
  init_types3();
56343
+ init_types3();
56298
56344
  ALL_TOOLS = [
56299
56345
  searchTool,
56300
56346
  ingestTool,
@@ -56500,7 +56546,7 @@ __export(exports_util, {
56500
56546
  cleanRegex: () => cleanRegex,
56501
56547
  cleanEnum: () => cleanEnum,
56502
56548
  captureStackTrace: () => captureStackTrace,
56503
- cached: () => cached,
56549
+ cached: () => cached2,
56504
56550
  assignProp: () => assignProp,
56505
56551
  assertNotEqual: () => assertNotEqual,
56506
56552
  assertNever: () => assertNever,
@@ -56537,7 +56583,7 @@ function jsonStringifyReplacer(_, value) {
56537
56583
  return value.toString();
56538
56584
  return value;
56539
56585
  }
56540
- function cached(getter) {
56586
+ function cached2(getter) {
56541
56587
  const set = false;
56542
56588
  return {
56543
56589
  get value() {
@@ -56949,7 +56995,7 @@ var captureStackTrace, allowsEval, getParsedType2 = (data) => {
56949
56995
  }, propertyKeyTypes, primitiveTypes, NUMBER_FORMAT_RANGES, BIGINT_FORMAT_RANGES;
56950
56996
  var init_util2 = __esm(() => {
56951
56997
  captureStackTrace = Error.captureStackTrace ? Error.captureStackTrace : (..._args) => {};
56952
- allowsEval = cached(() => {
56998
+ allowsEval = cached2(() => {
56953
56999
  if (typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare")) {
56954
57000
  return false;
56955
57001
  }
@@ -58254,7 +58300,7 @@ var init_schemas = __esm(() => {
58254
58300
  });
58255
58301
  $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
58256
58302
  $ZodType.init(inst, def);
58257
- const _normalized = cached(() => {
58303
+ const _normalized = cached2(() => {
58258
58304
  const keys = Object.keys(def.shape);
58259
58305
  for (const k of keys) {
58260
58306
  if (!(def.shape[k] instanceof $ZodType)) {
@@ -58474,7 +58520,7 @@ var init_schemas = __esm(() => {
58474
58520
  }
58475
58521
  return propValues;
58476
58522
  });
58477
- const disc = cached(() => {
58523
+ const disc = cached2(() => {
58478
58524
  const opts = def.options;
58479
58525
  const map = new Map;
58480
58526
  for (const o of opts) {
@@ -69455,7 +69501,7 @@ function buildServer() {
69455
69501
  };
69456
69502
  const server = new Server({ name: SERVER_NAME, version: PKG_VERSION }, { capabilities: { tools: {} } });
69457
69503
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
69458
- tools: ALL_TOOLS.map((t) => ({
69504
+ tools: (await listEnabledTools(supabase)).map((t) => ({
69459
69505
  name: t.name,
69460
69506
  description: t.description,
69461
69507
  inputSchema: t.inputSchema
@@ -69468,6 +69514,7 @@ function buildServer() {
69468
69514
  throw new McpInvalidParams(`Unknown tool: ${name}`);
69469
69515
  }
69470
69516
  try {
69517
+ await assertToolEnabled(supabase, name);
69471
69518
  const text = await tool.handler(supabase, args, ctx);
69472
69519
  return { content: [{ type: "text", text }] };
69473
69520
  } catch (err) {
@@ -69545,6 +69592,7 @@ init_cli_core();
69545
69592
 
69546
69593
  // src/cli/commands/backup.ts
69547
69594
  init_cli_core();
69595
+ init_config();
69548
69596
  import { existsSync as existsSync2, mkdirSync, writeFileSync } from "node:fs";
69549
69597
  import { homedir as homedir2 } from "node:os";
69550
69598
  import { join as join2, resolve } from "node:path";
@@ -69574,6 +69622,7 @@ async function fetchAllPages(makeQuery, batchSize = 200) {
69574
69622
 
69575
69623
  // src/cli/commands/backup.ts
69576
69624
  init_client();
69625
+ init_meta();
69577
69626
  function expandHome(path) {
69578
69627
  if (path === "~")
69579
69628
  return homedir2();
@@ -69587,18 +69636,69 @@ function utcStamp() {
69587
69636
  return d.getUTCFullYear().toString() + pad(d.getUTCMonth() + 1) + pad(d.getUTCDate()) + "T" + pad(d.getUTCHours()) + pad(d.getUTCMinutes()) + pad(d.getUTCSeconds()) + "Z";
69588
69637
  }
69589
69638
  async function action(options) {
69590
- const outDir = resolve(expandHome(options.outputDir ?? process.env.CEREFOX_BACKUP_DIR ?? "~/.cerefox/backups"));
69639
+ loadEnv();
69640
+ const configuredDir = options.outputDir ?? process.env.CEREFOX_BACKUP_DIR;
69641
+ const outDir = resolve(expandHome(configuredDir ?? "~/.cerefox/backups"));
69642
+ if (configuredDir !== undefined && !configuredDir.startsWith("/") && !configuredDir.startsWith("~")) {
69643
+ println(c.yellow("⚠ ") + `Backup directory "${configuredDir}" is relative — it resolves against the ` + "current working directory, so backups land in different places depending " + "on where you run this from.");
69644
+ println(c.dim(` Writing to: ${outDir}`));
69645
+ println(c.dim(" Set an absolute path (e.g. ~/.cerefox/backups) to keep them together."));
69646
+ }
69591
69647
  if (!existsSync2(outDir))
69592
69648
  mkdirSync(outDir, { recursive: true });
69649
+ const envLabel = (process.env.CEREFOX_ENV_LABEL ?? "").trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
69593
69650
  const stamp = utcStamp();
69594
- const filename = `cerefox-${stamp}${options.label ? "-" + options.label : ""}.json`;
69651
+ const filename = `cerefox-${envLabel ? envLabel + "-" : ""}${stamp}` + `${options.label ? "-" + options.label : ""}.json`;
69595
69652
  const dest = join2(outDir, filename);
69596
69653
  const client = getClient();
69654
+ let schemaVersion = "unknown";
69655
+ try {
69656
+ schemaVersion = await client.rpc("cerefox_schema_version", {}) ?? "unknown";
69657
+ } catch {}
69658
+ const includeTrash = options.trash !== false;
69659
+ const BASE_COLUMNS = "id, title, content_hash, source, metadata, total_chars, chunk_count, " + "review_status, created_at, updated_at, deleted_at";
69660
+ const fetchDocs = (columns) => fetchAllPages((from, to) => {
69661
+ const q = client.raw.from("cerefox_documents").select(columns);
69662
+ return (includeTrash ? q : q.is("deleted_at", null)).order("created_at", { ascending: true }).order("id", { ascending: true }).range(from, to);
69663
+ });
69597
69664
  let docs;
69665
+ let lifecycleCaptured = true;
69598
69666
  try {
69599
- docs = await fetchAllPages((from, to) => client.raw.from("cerefox_documents").select("id, title, content_hash, source, metadata, total_chars, chunk_count, " + "review_status, created_at, updated_at, deleted_at").is("deleted_at", null).order("created_at", { ascending: true }).order("id", { ascending: true }).range(from, to));
69667
+ docs = await fetchDocs(`${BASE_COLUMNS}, lifecycle_status`);
69600
69668
  } catch (err) {
69601
- throw systemError(`Document fetch failed: ${err instanceof Error ? err.message : String(err)}`);
69669
+ const message = err instanceof Error ? err.message : String(err);
69670
+ if (!/lifecycle_status/.test(message)) {
69671
+ throw systemError(`Document fetch failed: ${message}`);
69672
+ }
69673
+ lifecycleCaptured = false;
69674
+ try {
69675
+ docs = await fetchDocs(BASE_COLUMNS);
69676
+ } catch (retryErr) {
69677
+ throw systemError(`Document fetch failed: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`);
69678
+ }
69679
+ println(c.dim(" (server predates lifecycle_status — captured without it)"));
69680
+ }
69681
+ const trashedCount = docs.filter((d) => d.deleted_at != null).length;
69682
+ let projects = [];
69683
+ let memberships = [];
69684
+ try {
69685
+ projects = await fetchAllPages((from, to) => client.raw.from("cerefox_projects").select("id, name, description, created_at, updated_at").order("id", { ascending: true }).range(from, to));
69686
+ memberships = await fetchAllPages((from, to) => client.raw.from("cerefox_document_projects").select("document_id, project_id").order("document_id", { ascending: true }).order("project_id", { ascending: true }).range(from, to));
69687
+ } catch (err) {
69688
+ throw systemError(`Project/membership fetch failed: ${err instanceof Error ? err.message : String(err)}`);
69689
+ }
69690
+ let relations = [];
69691
+ try {
69692
+ relations = await fetchAllPages((from, to) => client.raw.from("cerefox_document_relations").select("source_id, target_id, rel_type, metadata, author, author_type, created_at").order("source_id", { ascending: true }).order("target_id", { ascending: true }).order("rel_type", { ascending: true }).range(from, to));
69693
+ } catch {}
69694
+ {
69695
+ const captured = new Set(docs.map((d) => d.id));
69696
+ const before = memberships.length;
69697
+ memberships = memberships.filter((m) => captured.has(m.document_id));
69698
+ const dropped = before - memberships.length;
69699
+ if (dropped > 0) {
69700
+ println(c.dim(` (skipped ${dropped} membership(s) belonging to trashed documents)`));
69701
+ }
69602
69702
  }
69603
69703
  let chunkTotal = 0;
69604
69704
  const enriched = [];
@@ -69622,22 +69722,39 @@ async function action(options) {
69622
69722
  `);
69623
69723
  const payload = {
69624
69724
  created_at: new Date().toISOString(),
69625
- cerefox_version: process.env.npm_package_version ?? "unknown",
69725
+ cerefox_version: PKG_VERSION,
69726
+ backup_format: 4,
69727
+ schema_version: schemaVersion,
69626
69728
  document_count: docs.length,
69729
+ trashed_count: trashedCount,
69730
+ includes_trash: includeTrash,
69731
+ env_label: envLabel || null,
69732
+ includes_lifecycle_status: lifecycleCaptured,
69627
69733
  chunk_count: chunkTotal,
69734
+ project_count: projects.length,
69735
+ membership_count: memberships.length,
69736
+ relation_count: relations.length,
69737
+ projects,
69738
+ memberships,
69739
+ relations,
69628
69740
  documents: enriched
69629
69741
  };
69630
69742
  writeFileSync(dest, JSON.stringify(payload, null, 2), "utf8");
69631
69743
  println("");
69632
69744
  println(c.green("✓ ") + `Backup written: ${dest}`);
69633
- println(c.dim(` documents: ${docs.length} · chunks: ${chunkTotal}`));
69745
+ println(c.dim(` documents: ${docs.length} · chunks: ${chunkTotal} · ` + `projects: ${projects.length} · memberships: ${memberships.length}` + (relations.length > 0 ? ` · relations: ${relations.length}` : "")));
69746
+ if (!includeTrash) {
69747
+ println(c.dim(" trashed documents: excluded (--no-trash)"));
69748
+ } else if (trashedCount > 0) {
69749
+ println(c.dim(` of which trashed: ${trashedCount} (restored as trash, not resurrected)`));
69750
+ }
69634
69751
  if (options.git) {
69635
69752
  println(c.yellow("⚠ ") + "--git commit is not implemented; the snapshot was written without a git checkpoint.");
69636
69753
  println(c.dim(" Commit the backup directory yourself if you want it version-controlled."));
69637
69754
  }
69638
69755
  }
69639
69756
  function registerBackup(program2) {
69640
- program2.command("backup").description("Write a JSON snapshot of the knowledge base.").option("-o, --output-dir <dir>", "Snapshot output directory (default: CEREFOX_BACKUP_DIR or ~/.cerefox/backups).").option("-l, --label <label>", "Optional suffix added to the filename.").option("--include-versions", "Include archived versions in the snapshot. (v0.5: ignored — current chunks only.)").option("--git", "Commit the snapshot to the output dir as a git checkpoint. (v0.5: ignored.)").action(action);
69757
+ program2.command("backup").description("Write a JSON snapshot of the knowledge base.").option("-o, --output-dir <dir>", "Snapshot output directory (default: CEREFOX_BACKUP_DIR or ~/.cerefox/backups).").option("-l, --label <label>", "Optional suffix added to the filename.").option("--include-versions", "Include archived versions in the snapshot. (v0.5: ignored — current chunks only.)").option("--git", "Commit the snapshot to the output dir as a git checkpoint. (v0.5: ignored.)").option("--no-trash", "Exclude soft-deleted documents. Default: they are captured and restored as trash.").action(action);
69641
69758
  }
69642
69759
 
69643
69760
  // src/cli/commands/completion.ts
@@ -69957,6 +70074,10 @@ var CONFIG_KEYS = [
69957
70074
  key: "min_term_coverage",
69958
70075
  description: "0–1 — fraction of a query's meaningful terms a keyword OR-fallback match must cover to count as confident. Default 0.5."
69959
70076
  },
70077
+ {
70078
+ key: "relations_enabled",
70079
+ description: "'true'/'false' — expose the document-relation tools to agents. Off by default; the feature is dormant until enabled (iteration 29)."
70080
+ },
69960
70081
  {
69961
70082
  key: "search_alpha",
69962
70083
  description: "0–1 — hybrid fusion weight: 1 = pure semantic, 0 = pure keyword. Default 0.7."
@@ -75502,8 +75623,8 @@ import { homedir as homedir6 } from "node:os";
75502
75623
  import { join as join9 } from "node:path";
75503
75624
 
75504
75625
  // ../../_shared/ef-meta/index.ts
75505
- var EF_VERSION = "1.1.0-beta.1";
75506
- var EF_LAST_CHANGED = "1.1.0-beta.1";
75626
+ var EF_VERSION = "1.1.0-beta.2";
75627
+ var EF_LAST_CHANGED = "1.1.0-beta.2";
75507
75628
 
75508
75629
  // src/cli/util/checks.ts
75509
75630
  init_config();
@@ -75591,10 +75712,11 @@ function checkConfig() {
75591
75712
  modeDetail = ` (mode 0${mode.toString(8)})`;
75592
75713
  } catch {}
75593
75714
  }
75715
+ const envLabel = (process.env.CEREFOX_ENV_LABEL ?? "").trim();
75594
75716
  return {
75595
75717
  name: "config",
75596
75718
  status: "ok",
75597
- detail: `${envPath}${modeDetail}`
75719
+ detail: `${envPath}${modeDetail}${envLabel ? ` [${envLabel.toUpperCase()}]` : ""}`
75598
75720
  };
75599
75721
  }
75600
75722
  async function checkSupabase() {
@@ -75852,7 +75974,7 @@ async function checkContentFormat() {
75852
75974
  name: CONTENT_FORMAT_CHECK_NAME,
75853
75975
  status: "skipped",
75854
75976
  detail: `${legacy} of ${total} document(s) use the legacy reconstruction format (format 1).`,
75855
- hint: "They auto-convert on next edit; run `cerefox server reindex` to convert all now. What this means: `cerefox guides show content-format`."
75977
+ 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`."
75856
75978
  };
75857
75979
  } catch (err) {
75858
75980
  return {
@@ -77997,6 +78119,114 @@ function registerReindex(program2) {
77997
78119
  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(action27);
77998
78120
  }
77999
78121
 
78122
+ // src/cli/commands/migrate-format.ts
78123
+ init_cli_core();
78124
+ init_config();
78125
+ init_client();
78126
+ var CURRENT_FORMAT = 2;
78127
+ async function action28(options) {
78128
+ const settings = loadSettings();
78129
+ const client = getClient();
78130
+ const supabase = client.raw;
78131
+ let legacyChunkRows;
78132
+ try {
78133
+ legacyChunkRows = await fetchAllPages((from, to) => {
78134
+ let q = supabase.from("cerefox_chunks").select("document_id").is("version_id", null).lt("content_format", CURRENT_FORMAT);
78135
+ if (options.documentId)
78136
+ q = q.eq("document_id", options.documentId);
78137
+ return q.order("document_id", { ascending: true }).range(from, to);
78138
+ }, 1000);
78139
+ } catch (err) {
78140
+ throw systemError(`Could not list legacy chunks: ${err instanceof Error ? err.message : String(err)}`);
78141
+ }
78142
+ const docIds = [...new Set(legacyChunkRows.map((r) => r.document_id))];
78143
+ const limit = options.limit ? parsePositiveInt(options.limit, "--limit", docIds.length) : docIds.length;
78144
+ const targets = docIds.slice(0, limit);
78145
+ if (targets.length === 0) {
78146
+ println(c.green("✓ Nothing to migrate — every document already uses the current format."));
78147
+ return;
78148
+ }
78149
+ println(c.bold(`${docIds.length} document(s) on the legacy format` + (targets.length < docIds.length ? `; converting ${targets.length} (--limit)` : "")));
78150
+ if (options.dryRun) {
78151
+ println(c.yellow("⚠ --dry-run: nothing was written."));
78152
+ println(c.dim(" Each document would be re-chunked and RE-EMBEDDED (embedding spend)."));
78153
+ return;
78154
+ }
78155
+ println(c.dim("Each document is re-chunked and re-embedded — this costs embedding spend."));
78156
+ println("");
78157
+ const author = resolveAuthor(options.author);
78158
+ const authorType = resolveAuthorType(undefined);
78159
+ const pipeline2 = new IngestionPipeline({
78160
+ supabase,
78161
+ openAiApiKey: settings.openaiApiKey
78162
+ });
78163
+ let converted = 0;
78164
+ let skipped = 0;
78165
+ const duplicates = [];
78166
+ const failures = [];
78167
+ for (let i = 0;i < targets.length; i++) {
78168
+ const id = targets[i];
78169
+ if (process.stdout.isTTY) {
78170
+ process.stderr.write(`\r Converting ${i + 1}/${targets.length}…`);
78171
+ }
78172
+ let doc2 = null;
78173
+ try {
78174
+ const rows = await client.rpc("cerefox_get_document", { p_document_id: id, p_version_id: null });
78175
+ doc2 = rows?.[0] ?? null;
78176
+ } catch (err) {
78177
+ failures.push({ document: id, reason: `read: ${err instanceof Error ? err.message : String(err)}` });
78178
+ continue;
78179
+ }
78180
+ if (!doc2) {
78181
+ failures.push({ document: id, reason: "read: document not found" });
78182
+ continue;
78183
+ }
78184
+ try {
78185
+ await pipeline2.ingestText({
78186
+ text: doc2.full_content,
78187
+ title: doc2.doc_title,
78188
+ documentId: id,
78189
+ source: "migrate-format",
78190
+ author,
78191
+ authorType,
78192
+ expectedContentHash: doc2.content_hash
78193
+ });
78194
+ converted++;
78195
+ } catch (err) {
78196
+ const message = err instanceof Error ? err.message : String(err);
78197
+ if (/conflict/i.test(message)) {
78198
+ skipped++;
78199
+ } else if (/identical content already exists/i.test(message)) {
78200
+ duplicates.push({ document: `${doc2.doc_title} (${id})`, reason: message });
78201
+ } else {
78202
+ failures.push({ document: `${doc2.doc_title} (${id})`, reason: message });
78203
+ }
78204
+ }
78205
+ }
78206
+ if (process.stdout.isTTY)
78207
+ process.stderr.write(`
78208
+ `);
78209
+ println("");
78210
+ println(c.bold(`Converted ${converted} · skipped ${skipped} (changed mid-run) · failed ${failures.length}`));
78211
+ if (targets.length < docIds.length) {
78212
+ println(c.dim(` ${docIds.length - targets.length} document(s) still pending — re-run to continue.`));
78213
+ }
78214
+ if (duplicates.length > 0) {
78215
+ println("");
78216
+ println(c.yellow(`⚠ ${duplicates.length} document(s) could not be converted because their content is ` + "byte-identical to another document."));
78217
+ println(c.dim(" Re-ingesting them would collide with the content-hash dedup check. They keep working " + "on the legacy format; de-duplicate them if you want them converted."));
78218
+ printTable(duplicates.map((d) => ({ document: d.document })));
78219
+ }
78220
+ if (failures.length > 0) {
78221
+ println("");
78222
+ printTable(failures);
78223
+ throw systemError(`${failures.length} document(s) failed to convert.`);
78224
+ }
78225
+ }
78226
+ function registerMigrateFormat(program2) {
78227
+ 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(action28);
78228
+ }
78229
+
78000
78230
  // src/cli/commands/restore.ts
78001
78231
  init_cli_core();
78002
78232
  init_client();
@@ -78024,7 +78254,7 @@ function resolveBackupFile(target) {
78024
78254
  }
78025
78255
  return join12(path, candidates[0].name);
78026
78256
  }
78027
- async function action28(target, options) {
78257
+ async function action29(target, options) {
78028
78258
  const file = resolveBackupFile(target);
78029
78259
  let payload;
78030
78260
  try {
@@ -78036,7 +78266,24 @@ async function action28(target, options) {
78036
78266
  throw userError(`Backup file is missing "documents" array: ${file}`);
78037
78267
  }
78038
78268
  println(c.bold(`Restoring from ${file}`));
78039
- println(c.dim(` cerefox_version: ${payload.cerefox_version ?? "?"} · ` + `documents in file: ${payload.documents.length} · chunks in file: ${payload.chunk_count ?? "?"}`));
78269
+ const hasMemberships = Array.isArray(payload.memberships);
78270
+ println(c.dim(` cerefox_version: ${payload.cerefox_version ?? "?"} · ` + `schema: ${payload.schema_version ?? "?"} · ` + `documents in file: ${payload.documents.length} · chunks in file: ${payload.chunk_count ?? "?"}`));
78271
+ if (hasMemberships) {
78272
+ println(c.dim(` projects: ${payload.projects?.length ?? 0} · memberships: ${payload.memberships?.length ?? 0}`));
78273
+ } else {
78274
+ warn("This backup predates project-membership capture (format 1) — documents " + "will be restored WITHOUT their project assignments.");
78275
+ }
78276
+ {
78277
+ const fileEnv = (payload.env_label ?? "").trim();
78278
+ const targetEnv = (process.env.CEREFOX_ENV_LABEL ?? "").trim();
78279
+ if (fileEnv !== targetEnv) {
78280
+ warn(`This snapshot came from ${fileEnv ? `the "${fileEnv}" environment` : "an unlabelled (production) environment"}, ` + `but you are restoring into ${targetEnv ? `"${targetEnv}"` : "an unlabelled (production) environment"}.`);
78281
+ }
78282
+ }
78283
+ const trashedInFile = payload.documents.filter((d) => d.deleted_at != null).length;
78284
+ if (trashedInFile > 0) {
78285
+ println(c.dim(` trashed documents in file: ${trashedInFile} — restored as trash ` + "(still deleted; recover with `cerefox document restore`)"));
78286
+ }
78040
78287
  println("");
78041
78288
  const client = getClient();
78042
78289
  let restored = 0;
@@ -78044,7 +78291,7 @@ async function action28(target, options) {
78044
78291
  let errors4 = 0;
78045
78292
  const errorDetails = [];
78046
78293
  for (const doc2 of payload.documents) {
78047
- const { data: existing } = await client.raw.from("cerefox_documents").select("id").eq("content_hash", doc2.content_hash).maybeSingle();
78294
+ const { data: existing } = await client.raw.from("cerefox_documents").select("id").or(`id.eq.${doc2.id},content_hash.eq.${doc2.content_hash}`).limit(1).maybeSingle();
78048
78295
  if (existing) {
78049
78296
  skipped++;
78050
78297
  continue;
@@ -78071,8 +78318,70 @@ async function action28(target, options) {
78071
78318
  }
78072
78319
  restored++;
78073
78320
  }
78321
+ let projectsRestored = 0;
78322
+ let membershipsRestored = 0;
78323
+ let relationsRestored = 0;
78324
+ if (!options.dryRun && hasMemberships) {
78325
+ const projects = payload.projects ?? [];
78326
+ if (projects.length > 0) {
78327
+ const { error: projErr } = await client.raw.from("cerefox_projects").upsert(projects, { onConflict: "id", ignoreDuplicates: true });
78328
+ if (projErr) {
78329
+ errors4++;
78330
+ errorDetails.push({ title: "(projects)", error: projErr.message });
78331
+ } else {
78332
+ projectsRestored = projects.length;
78333
+ }
78334
+ }
78335
+ const presentDocIds = new Set;
78336
+ {
78337
+ const ids = (payload.documents ?? []).map((d) => d.id);
78338
+ for (let i = 0;i < ids.length; i += 200) {
78339
+ const { data } = await client.raw.from("cerefox_documents").select("id").in("id", ids.slice(i, i + 200));
78340
+ for (const row of data ?? [])
78341
+ presentDocIds.add(row.id);
78342
+ }
78343
+ }
78344
+ const links = (payload.memberships ?? []).filter((m) => presentDocIds.has(m.document_id));
78345
+ for (let i = 0;i < links.length; i += 500) {
78346
+ const { error: linkErr } = await client.raw.from("cerefox_document_projects").upsert(links.slice(i, i + 500), {
78347
+ onConflict: "document_id,project_id",
78348
+ ignoreDuplicates: true
78349
+ });
78350
+ if (linkErr) {
78351
+ errors4++;
78352
+ errorDetails.push({ title: "(memberships)", error: linkErr.message });
78353
+ break;
78354
+ }
78355
+ membershipsRestored += links.slice(i, i + 500).length;
78356
+ }
78357
+ const relations = (payload.relations ?? []).filter((r) => presentDocIds.has(r.source_id) && presentDocIds.has(r.target_id));
78358
+ if (relations.length > 0) {
78359
+ for (let i = 0;i < relations.length; i += 500) {
78360
+ const { error: relErr } = await client.raw.from("cerefox_document_relations").upsert(relations.slice(i, i + 500), {
78361
+ onConflict: "source_id,target_id,rel_type",
78362
+ ignoreDuplicates: true
78363
+ });
78364
+ if (relErr) {
78365
+ errorDetails.push({ title: "(relations)", error: relErr.message });
78366
+ errors4++;
78367
+ break;
78368
+ }
78369
+ relationsRestored += relations.slice(i, i + 500).length;
78370
+ }
78371
+ const dropped = (payload.relations?.length ?? 0) - relations.length;
78372
+ if (dropped > 0) {
78373
+ warn(`${dropped} relation(s) skipped — one or both documents were not restored.`);
78374
+ }
78375
+ }
78376
+ }
78074
78377
  println("");
78075
78378
  println((options.dryRun ? c.yellow("(dry-run) ") : "") + c.bold(`Summary: ${restored} restored · ${skipped} skipped · ${errors4} errors`));
78379
+ if (hasMemberships && !options.dryRun) {
78380
+ println(c.dim(` projects: ${projectsRestored} · memberships: ${membershipsRestored}` + (relationsRestored > 0 ? ` · relations: ${relationsRestored}` : "")));
78381
+ }
78382
+ if (trashedInFile > 0) {
78383
+ println(c.dim(` ${trashedInFile} of those are trashed and stay trashed.`));
78384
+ }
78076
78385
  if (errors4 > 0) {
78077
78386
  println("");
78078
78387
  printTable(errorDetails);
@@ -78080,7 +78389,7 @@ async function action28(target, options) {
78080
78389
  }
78081
78390
  }
78082
78391
  function registerRestore(program2) {
78083
- 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(action28);
78392
+ 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(action29);
78084
78393
  }
78085
78394
 
78086
78395
  // src/cli/commands/search.ts
@@ -78105,7 +78414,7 @@ async function embedQuery(query) {
78105
78414
  }
78106
78415
 
78107
78416
  // src/cli/commands/search.ts
78108
- async function action29(query, options) {
78417
+ async function action30(query, options) {
78109
78418
  if (!query || query.trim() === "") {
78110
78419
  throw userError("Empty query.");
78111
78420
  }
@@ -78271,7 +78580,7 @@ async function action29(query, options) {
78271
78580
  }
78272
78581
  }
78273
78582
  function registerSearch(program2) {
78274
- 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(action29);
78583
+ 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(action30);
78275
78584
  }
78276
78585
 
78277
78586
  // src/cli/commands/self-update.ts
@@ -78318,7 +78627,7 @@ async function fetchLatestVersion() {
78318
78627
  }
78319
78628
  return body.version;
78320
78629
  }
78321
- async function action30(options) {
78630
+ async function action31(options) {
78322
78631
  let target;
78323
78632
  try {
78324
78633
  target = options.version ?? await fetchLatestVersion();
@@ -78371,7 +78680,7 @@ async function action30(options) {
78371
78680
  }
78372
78681
  function registerSelfUpdate(program2) {
78373
78682
  const desc = "Upgrade Cerefox in place. Alias: `cerefox upgrade`.";
78374
- 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(action30);
78683
+ 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(action31);
78375
78684
  declaration(program2.command("self-update"));
78376
78685
  declaration(program2.command("upgrade"));
78377
78686
  }
@@ -78390,7 +78699,7 @@ function symbol2(status) {
78390
78699
  return cErr.dim("ℹ");
78391
78700
  }
78392
78701
  }
78393
- async function action31(options) {
78702
+ async function action32(options) {
78394
78703
  const useSpinner = !options.json && process.stderr.isTTY;
78395
78704
  const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
78396
78705
  const results = await runFastChecks({
@@ -78411,7 +78720,7 @@ async function action31(options) {
78411
78720
  }
78412
78721
  }
78413
78722
  function registerStatus(program2) {
78414
- program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(action31);
78723
+ program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(action32);
78415
78724
  }
78416
78725
 
78417
78726
  // src/cli/commands/token.ts
@@ -78444,10 +78753,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
78444
78753
  }
78445
78754
  const re = new RegExp(`^(\\s*)${escapeRegExp(key)}=.*$`, "m");
78446
78755
  let next;
78447
- let action32;
78756
+ let action33;
78448
78757
  if (re.test(original)) {
78449
78758
  next = original.replace(re, `$1${line}`);
78450
- action32 = "updated";
78759
+ action33 = "updated";
78451
78760
  } else {
78452
78761
  const base = original.endsWith(`
78453
78762
  `) ? original : `${original}
@@ -78455,10 +78764,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
78455
78764
  next = `${base}
78456
78765
  ${header}${line}
78457
78766
  `;
78458
- action32 = "added";
78767
+ action33 = "added";
78459
78768
  }
78460
78769
  writeFileSync5(path, next);
78461
- return { path, action: action32, backupPath };
78770
+ return { path, action: action33, backupPath };
78462
78771
  }
78463
78772
  function readEnvVar(path, key) {
78464
78773
  if (!existsSync14(path))
@@ -83303,7 +83612,10 @@ var VERSION_INFO = {
83303
83612
  };
83304
83613
  var SCHEMA_VERSION_RE3 = /^--\s*@version:\s*(\S+)/m;
83305
83614
  function registerMetaRoutes(app, ctx) {
83306
- app.get("/api/v1/version", (c2) => c2.json(VERSION_INFO));
83615
+ app.get("/api/v1/version", (c2) => {
83616
+ const label = (process.env.CEREFOX_ENV_LABEL ?? "").trim();
83617
+ return c2.json({ ...VERSION_INFO, env_label: label.length > 0 ? label : null });
83618
+ });
83307
83619
  app.get("/api/v1/docs", (c2) => c2.json(listBundledDocs2()));
83308
83620
  app.get("/api/v1/docs/:path{.+}", (c2) => {
83309
83621
  const docPath = c2.req.param("path");
@@ -83707,7 +84019,13 @@ import {
83707
84019
  } from "node:fs";
83708
84020
  import { homedir as homedir9 } from "node:os";
83709
84021
  import { join as join18 } from "node:path";
83710
- var STATE_DIR = join18(homedir9(), ".cerefox");
84022
+ function resolveStateDir(override = process.env.CEREFOX_CONFIG_DIR, home = homedir9()) {
84023
+ override = (override ?? "").trim();
84024
+ if (!override)
84025
+ return join18(home, ".cerefox");
84026
+ return override === "~" || override.startsWith("~/") ? join18(home, override.slice(2)) : override;
84027
+ }
84028
+ var STATE_DIR = resolveStateDir();
83711
84029
  var PID_FILE = join18(STATE_DIR, "web.pid");
83712
84030
  var LOG_FILE = join18(STATE_DIR, "web.log");
83713
84031
  var daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
@@ -84075,6 +84393,7 @@ Learn more:
84075
84393
  const server = program2.command("server").description("Server side: deploy, reindex.");
84076
84394
  moveInto(server, registerDeployServer, "deploy");
84077
84395
  moveInto(server, registerReindex, "reindex");
84396
+ registerMigrateFormat(server);
84078
84397
  const guides = program2.command("guides").description("Bundled docs: list, open, show, ingest (into the KB).");
84079
84398
  registerGuides(guides);
84080
84399
  moveInto(guides, registerSyncSelfDocs, "ingest");
@@ -84122,6 +84441,12 @@ async function bareEntryPoint() {
84122
84441
  }
84123
84442
  }
84124
84443
  async function main() {
84444
+ {
84445
+ const { loadEnv: loadEnv2 } = await Promise.resolve().then(() => (init_config(), exports_config));
84446
+ try {
84447
+ loadEnv2();
84448
+ } catch {}
84449
+ }
84125
84450
  if (process.argv.length === 2) {
84126
84451
  await bareEntryPoint();
84127
84452
  return;