@cerefox/memory 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,11 +79,19 @@ already provisioned (see "Before you install").
79
79
  > the deploy logic is ported to the TS CLI. For now, the setup-supabase
80
80
  > guide walks through it.
81
81
 
82
+ > **Upgrading from the Python `cerefox` CLI?** If you have a working
83
+ > `.env` in your repo clone, init detects it and offers to **copy** it to
84
+ > `~/.cerefox/.env` so the TS CLI uses the new home while Python keeps
85
+ > reading the repo file unchanged. See the migration-v0.5 guide for the
86
+ > three-choice prompt. Existing users with no `~/.cerefox/.env` see zero
87
+ > behavior change until they opt in.
88
+
82
89
  ---
83
90
 
84
91
  ## Connect an AI agent
85
92
 
86
93
  ```bash
94
+ # Run the configure-agent commands that apply to your setup:
87
95
  cerefox configure-agent --tool claude-code # writes ~/.claude/mcp.json
88
96
  cerefox configure-agent --tool claude-desktop # writes Claude Desktop config
89
97
  ```
@@ -7179,7 +7179,7 @@ var exports_meta = {};
7179
7179
  __export(exports_meta, {
7180
7180
  PKG_VERSION: () => PKG_VERSION
7181
7181
  });
7182
- var PKG_VERSION = "0.5.2";
7182
+ var PKG_VERSION = "0.5.4";
7183
7183
  var init_meta = () => {};
7184
7184
 
7185
7185
  // ../../node_modules/.bun/tslib@2.8.1/node_modules/tslib/tslib.js
@@ -22494,26 +22494,35 @@ function expandTilde(p) {
22494
22494
  }
22495
22495
  return p;
22496
22496
  }
22497
+ function userStateDirAbs(opts) {
22498
+ return resolvePath(join(opts.home ?? homedir(), USER_STATE_DIR_NAME));
22499
+ }
22497
22500
  function resolveConfigDir(opts = {}) {
22498
22501
  const override = (env.CEREFOX_CONFIG_DIR ?? "").trim();
22499
22502
  if (override) {
22500
22503
  return resolvePath(expandTilde(override));
22501
22504
  }
22505
+ const userState = userStateDirAbs(opts);
22506
+ if (existsSync(join(userState, ".env"))) {
22507
+ return userState;
22508
+ }
22502
22509
  const here = opts.cwd ?? processCwd();
22503
22510
  if (existsSync(join(here, ".env"))) {
22504
22511
  return resolvePath(here);
22505
22512
  }
22506
- return resolvePath(join(homedir(), USER_STATE_DIR_NAME));
22513
+ return userState;
22507
22514
  }
22508
22515
  function resolveEnvFile(opts = {}) {
22509
22516
  return join(resolveConfigDir(opts), ".env");
22510
22517
  }
22511
- function userStateDir() {
22512
- return resolvePath(join(homedir(), USER_STATE_DIR_NAME));
22518
+ function userStateDir(opts = {}) {
22519
+ return userStateDirAbs(opts);
22513
22520
  }
22514
22521
  function isDevMode(opts = {}) {
22515
22522
  if ((env.CEREFOX_CONFIG_DIR ?? "").trim())
22516
22523
  return false;
22524
+ if (existsSync(join(userStateDirAbs(opts), ".env")))
22525
+ return false;
22517
22526
  const here = opts.cwd ?? processCwd();
22518
22527
  return existsSync(join(here, ".env"));
22519
22528
  }
@@ -24728,7 +24737,7 @@ __export(exports_sync_self_docs, {
24728
24737
  runSyncSelfDocs: () => runSyncSelfDocs,
24729
24738
  registerSyncSelfDocs: () => registerSyncSelfDocs
24730
24739
  });
24731
- import { readFileSync as readFileSync6 } from "node:fs";
24740
+ import { readFileSync as readFileSync7 } from "node:fs";
24732
24741
  import { basename as basename3, extname as extname3 } from "node:path";
24733
24742
  async function runSyncSelfDocs(options = {}) {
24734
24743
  const project = options.project ?? "_cerefox-self-docs";
@@ -24755,7 +24764,7 @@ async function runSyncSelfDocs(options = {}) {
24755
24764
  const authorType = resolveAuthorType("agent");
24756
24765
  const outcomes = [];
24757
24766
  for (const doc of docs) {
24758
- const content = readFileSync6(doc.path, "utf8");
24767
+ const content = readFileSync7(doc.path, "utf8");
24759
24768
  const m = content.match(/^#\s+(.+)$/m);
24760
24769
  const title = m ? m[1].trim() : basename3(doc.path, extname3(doc.path));
24761
24770
  try {
@@ -38135,17 +38144,24 @@ function registerConfigSet(program2) {
38135
38144
  init_cli_core();
38136
38145
 
38137
38146
  // src/cli/util/mcp-config-writers.ts
38138
- import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
38147
+ import {
38148
+ copyFileSync,
38149
+ existsSync as existsSync3,
38150
+ mkdirSync as mkdirSync2,
38151
+ readFileSync as readFileSync2,
38152
+ writeFileSync as writeFileSync2
38153
+ } from "node:fs";
38139
38154
  import { homedir as homedir3, platform } from "node:os";
38140
38155
  import { dirname, join as join3 } from "node:path";
38156
+ import { spawnSync } from "node:child_process";
38141
38157
  function defaultCerefoxEntry() {
38142
38158
  return {
38143
38159
  command: "npx",
38144
38160
  args: ["-y", "--package=@cerefox/memory", "cerefox", "mcp"]
38145
38161
  };
38146
38162
  }
38147
- function claudeCodeConfigPath() {
38148
- return join3(homedir3(), ".claude", "mcp.json");
38163
+ function claudeCodeUserConfigPath() {
38164
+ return join3(homedir3(), ".claude.json");
38149
38165
  }
38150
38166
  function claudeDesktopConfigPath() {
38151
38167
  const home = homedir3();
@@ -38157,22 +38173,40 @@ function claudeDesktopConfigPath() {
38157
38173
  }
38158
38174
  return join3(home, ".config", "Claude", "claude_desktop_config.json");
38159
38175
  }
38176
+ function claudeCodeDelegated() {
38177
+ const entry = defaultCerefoxEntry();
38178
+ return {
38179
+ cmd: "claude",
38180
+ args: ["mcp", "add", "cerefox", "--scope", "user", "--", entry.command, ...entry.args]
38181
+ };
38182
+ }
38160
38183
  var WRITERS = {
38161
38184
  "claude-code": {
38162
38185
  id: "claude-code",
38163
38186
  label: "Claude Code",
38164
- configPath: claudeCodeConfigPath(),
38165
- buildServerEntry: defaultCerefoxEntry
38187
+ kind: "delegated",
38188
+ configPath: claudeCodeUserConfigPath(),
38189
+ buildServerEntry: defaultCerefoxEntry,
38190
+ delegated: claudeCodeDelegated
38166
38191
  },
38167
38192
  "claude-desktop": {
38168
38193
  id: "claude-desktop",
38169
38194
  label: "Claude Desktop",
38195
+ kind: "direct-write",
38170
38196
  configPath: claudeDesktopConfigPath(),
38171
38197
  buildServerEntry: defaultCerefoxEntry
38172
38198
  }
38173
38199
  };
38174
38200
  function writeMcpConfig(writer, opts = {}) {
38175
- const configPath = opts.customPath ?? writer.configPath;
38201
+ if (opts.customPath) {
38202
+ return directWrite({ ...writer, kind: "direct-write" }, opts.customPath, opts);
38203
+ }
38204
+ if (writer.kind === "delegated") {
38205
+ return delegatedWrite(writer, opts);
38206
+ }
38207
+ return directWrite(writer, writer.configPath, opts);
38208
+ }
38209
+ function directWrite(writer, configPath, opts) {
38176
38210
  const entry = writer.buildServerEntry();
38177
38211
  if (!opts.dryRun)
38178
38212
  mkdirSync2(dirname(configPath), { recursive: true });
@@ -38200,6 +38234,46 @@ function writeMcpConfig(writer, opts = {}) {
38200
38234
  }
38201
38235
  return { configPath, backupPath, action: action5, serverEntry: entry };
38202
38236
  }
38237
+ function delegatedWrite(writer, opts) {
38238
+ if (!writer.delegated) {
38239
+ throw new Error(`${writer.label}: kind=delegated but no delegated() factory`);
38240
+ }
38241
+ const { cmd, args } = writer.delegated();
38242
+ const entry = writer.buildServerEntry();
38243
+ const delegatedCommand = `${cmd} ${args.join(" ")}`;
38244
+ if (opts.dryRun) {
38245
+ return {
38246
+ configPath: writer.configPath,
38247
+ backupPath: null,
38248
+ action: "delegated",
38249
+ serverEntry: entry,
38250
+ delegatedCommand
38251
+ };
38252
+ }
38253
+ let backupPath = null;
38254
+ if (!opts.noBackup && existsSync3(writer.configPath)) {
38255
+ backupPath = writer.configPath + ".pre-cerefox.bak";
38256
+ copyFileSync(writer.configPath, backupPath);
38257
+ }
38258
+ const result = spawnSync(cmd, args, { stdio: "inherit" });
38259
+ if (result.error) {
38260
+ const err = result.error;
38261
+ if (err.code === "ENOENT") {
38262
+ throw new Error(`${writer.label}: \`${cmd}\` not found on PATH. ` + `Install ${writer.label} (https://docs.claude.com/en/docs/claude-code) ` + `and re-run \`cerefox configure-agent --tool ${writer.id}\`.`);
38263
+ }
38264
+ throw new Error(`${writer.label}: failed to spawn \`${cmd}\`: ${err.message}`);
38265
+ }
38266
+ if (result.status !== 0) {
38267
+ throw new Error(`${writer.label}: \`${delegatedCommand}\` exited with status ${result.status ?? "unknown"}. ` + `Check the output above; you may need to update or re-authenticate ${writer.label}.`);
38268
+ }
38269
+ return {
38270
+ configPath: writer.configPath,
38271
+ backupPath,
38272
+ action: "delegated",
38273
+ serverEntry: entry,
38274
+ delegatedCommand
38275
+ };
38276
+ }
38203
38277
 
38204
38278
  // src/cli/commands/configure-agent.ts
38205
38279
  function action5(options) {
@@ -38226,12 +38300,15 @@ function action5(options) {
38226
38300
  println(c.dim(` backup: ${result.backupPath}`));
38227
38301
  }
38228
38302
  println(c.dim(` action: ${result.action}`));
38303
+ if (result.delegatedCommand) {
38304
+ println(c.dim(` invoked: ${result.delegatedCommand}`));
38305
+ }
38229
38306
  println("");
38230
38307
  println(c.bold("Server entry written:"));
38231
38308
  println(JSON.stringify(result.serverEntry, null, 2));
38232
38309
  if (!options.dryRun) {
38233
38310
  println("");
38234
- println(c.dim(writer.id === "claude-desktop" ? "Restart Claude Desktop fully (Cmd+Q on macOS) to pick up the new server." : "Reload your Claude Code session to pick up the new server."));
38311
+ println(c.dim(writer.id === "claude-desktop" ? "Restart Claude Desktop fully (Cmd+Q on macOS) to pick up the new server." : "Start a new Claude Code session to pick up the new server " + "(running sessions cache MCP server lists at startup)."));
38235
38312
  }
38236
38313
  }
38237
38314
  function registerConfigureAgent(program2) {
@@ -38286,10 +38363,10 @@ function registerDeleteDoc(program2) {
38286
38363
  // src/cli/commands/docs.ts
38287
38364
  init_cli_core();
38288
38365
  init_bundled_docs();
38289
- import { spawnSync } from "node:child_process";
38366
+ import { spawnSync as spawnSync2 } from "node:child_process";
38290
38367
  function openInBrowser(path) {
38291
38368
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
38292
- const result = spawnSync(cmd, [path], { stdio: "ignore" });
38369
+ const result = spawnSync2(cmd, [path], { stdio: "ignore" });
38293
38370
  if (result.status !== 0) {
38294
38371
  println(c.dim(`(could not auto-open; the file is at: ${path})`));
38295
38372
  }
@@ -38333,7 +38410,7 @@ init_cli_core();
38333
38410
  init_meta();
38334
38411
  init_config();
38335
38412
  init_config();
38336
- import { existsSync as existsSync5, statSync as statSync2 } from "node:fs";
38413
+ import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync, statSync as statSync2 } from "node:fs";
38337
38414
  import { homedir as homedir4 } from "node:os";
38338
38415
  import { join as join5 } from "node:path";
38339
38416
  function checkBinary() {
@@ -38552,29 +38629,58 @@ async function checkSchemaVersion() {
38552
38629
  };
38553
38630
  }
38554
38631
  }
38632
+ function hasCerefoxInJsonFile(path) {
38633
+ if (!existsSync5(path))
38634
+ return false;
38635
+ try {
38636
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
38637
+ const mcpServers = parsed.mcpServers;
38638
+ return Boolean(mcpServers && typeof mcpServers === "object" && "cerefox" in mcpServers);
38639
+ } catch {
38640
+ return false;
38641
+ }
38642
+ }
38555
38643
  function checkMcpConfigs() {
38556
38644
  const home = homedir4();
38557
- const candidates = [
38558
- { label: "Claude Code (user)", path: join5(home, ".claude", "mcp.json") },
38559
- { label: "Claude Code (proj)", path: join5(process.cwd(), ".mcp.json") },
38560
- {
38561
- label: "Claude Desktop",
38562
- path: process.platform === "darwin" ? join5(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join5(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join5(home, ".config", "Claude", "claude_desktop_config.json")
38563
- }
38564
- ];
38565
- const found = candidates.filter((c2) => existsSync5(c2.path));
38645
+ const claudeCodeUser = join5(home, ".claude.json");
38646
+ const claudeCodeProj = join5(process.cwd(), ".mcp.json");
38647
+ const claudeDesktop = process.platform === "darwin" ? join5(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join5(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join5(home, ".config", "Claude", "claude_desktop_config.json");
38648
+ const found = [];
38649
+ if (hasCerefoxInJsonFile(claudeCodeUser))
38650
+ found.push("Claude Code (user)");
38651
+ if (hasCerefoxInJsonFile(claudeCodeProj))
38652
+ found.push("Claude Code (proj)");
38653
+ if (hasCerefoxInJsonFile(claudeDesktop))
38654
+ found.push("Claude Desktop");
38566
38655
  if (found.length === 0) {
38567
38656
  return {
38568
38657
  name: "mcp clients",
38569
38658
  status: "warn",
38570
- detail: "No MCP client configs detected.",
38571
- hint: "Run `cerefox configure-agent --tool claude-code` to wire up Claude Code."
38659
+ detail: "No MCP client configs reference Cerefox.",
38660
+ hint: "Run `cerefox configure-agent --tool claude-code` (or `--tool claude-desktop`) to wire up a client."
38572
38661
  };
38573
38662
  }
38574
38663
  return {
38575
38664
  name: "mcp clients",
38576
38665
  status: "ok",
38577
- detail: found.map((f) => f.label).join(", ")
38666
+ detail: found.join(", ")
38667
+ };
38668
+ }
38669
+ function checkLegacyShadowEnv() {
38670
+ const home = homedir4();
38671
+ const homeEnv = join5(home, USER_STATE_DIR_NAME, ".env");
38672
+ const cwdEnv = join5(process.cwd(), ".env");
38673
+ if (!existsSync5(homeEnv) || !existsSync5(cwdEnv))
38674
+ return null;
38675
+ try {
38676
+ if (realpathSync(homeEnv) === realpathSync(cwdEnv))
38677
+ return null;
38678
+ } catch {}
38679
+ return {
38680
+ name: "legacy env",
38681
+ status: "skipped",
38682
+ detail: `${cwdEnv} (shadowed by ~/.cerefox/.env)`,
38683
+ hint: "Python `uv run cerefox …` still reads this file during the v0.5–v0.7 migration window. " + "Safe to delete once Python support is removed (v0.9+)."
38578
38684
  };
38579
38685
  }
38580
38686
  function checkPostgres() {
@@ -38593,11 +38699,13 @@ function checkPostgres() {
38593
38699
  };
38594
38700
  }
38595
38701
  async function runAllChecks() {
38702
+ const legacy = checkLegacyShadowEnv();
38596
38703
  return [
38597
38704
  checkBinary(),
38598
38705
  checkRuntime(),
38599
38706
  checkVersion(),
38600
38707
  checkConfig(),
38708
+ ...legacy ? [legacy] : [],
38601
38709
  await checkSupabase(),
38602
38710
  await checkOpenAI(),
38603
38711
  await checkSchemaVersion(),
@@ -38738,7 +38846,7 @@ init_cli_core();
38738
38846
  init_mcp_tools();
38739
38847
  init_config();
38740
38848
  init_client();
38741
- import { readFileSync as readFileSync4 } from "node:fs";
38849
+ import { readFileSync as readFileSync5 } from "node:fs";
38742
38850
  import { basename, extname } from "node:path";
38743
38851
  async function readContent(path, paste) {
38744
38852
  if (paste) {
@@ -38760,7 +38868,7 @@ async function readContent(path, paste) {
38760
38868
  }
38761
38869
  let content;
38762
38870
  try {
38763
- content = readFileSync4(path, "utf8");
38871
+ content = readFileSync5(path, "utf8");
38764
38872
  } catch (err) {
38765
38873
  const msg = err instanceof Error ? err.message : String(err);
38766
38874
  throw userError(`Cannot read ${path}: ${msg}`);
@@ -38825,7 +38933,7 @@ init_mcp_tools();
38825
38933
  init_config();
38826
38934
  init_client();
38827
38935
  var import_cli_progress = __toESM(require_cli_progress(), 1);
38828
- import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
38936
+ import { readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
38829
38937
  import { basename as basename2, extname as extname2, join as join6 } from "node:path";
38830
38938
  function walk(dir, extensions) {
38831
38939
  let entries;
@@ -38881,7 +38989,7 @@ async function action12(dir, options) {
38881
38989
  bar?.update({ file: basename2(file) });
38882
38990
  let content;
38883
38991
  try {
38884
- content = readFileSync5(file, "utf8");
38992
+ content = readFileSync6(file, "utf8");
38885
38993
  } catch (err) {
38886
38994
  outcomes.push({ file, status: "error", detail: `read failed: ${err instanceof Error ? err.message : String(err)}` });
38887
38995
  bar?.increment();
@@ -38938,19 +39046,21 @@ init_cli_core();
38938
39046
  init_config();
38939
39047
  import {
38940
39048
  chmodSync,
39049
+ copyFileSync as copyFileSync2,
38941
39050
  existsSync as existsSync6,
38942
39051
  mkdirSync as mkdirSync3,
38943
- readFileSync as readFileSync7,
39052
+ readFileSync as readFileSync8,
38944
39053
  writeFileSync as writeFileSync3
38945
39054
  } from "node:fs";
38946
- import { dirname as dirname3 } from "node:path";
39055
+ import { homedir as homedir5 } from "node:os";
39056
+ import { dirname as dirname3, join as join7 } from "node:path";
38947
39057
  async function readConfigFile(path) {
38948
39058
  if (!existsSync6(path)) {
38949
39059
  throw userError(`--config file not found: ${path}`);
38950
39060
  }
38951
39061
  let parsed;
38952
39062
  try {
38953
- parsed = JSON.parse(readFileSync7(path, "utf8"));
39063
+ parsed = JSON.parse(readFileSync8(path, "utf8"));
38954
39064
  } catch (err) {
38955
39065
  throw userError(`--config: invalid JSON in ${path}: ${err instanceof Error ? err.message : String(err)}`);
38956
39066
  }
@@ -38973,6 +39083,41 @@ async function readConfigFile(path) {
38973
39083
  CEREFOX_AUTHOR_TYPE: typeof obj.CEREFOX_AUTHOR_TYPE === "string" ? obj.CEREFOX_AUTHOR_TYPE : undefined
38974
39084
  };
38975
39085
  }
39086
+ function parseDotEnvFile(content) {
39087
+ const map = {};
39088
+ for (const rawLine of content.split(/\r?\n/)) {
39089
+ const line = rawLine.trim();
39090
+ if (!line || line.startsWith("#"))
39091
+ continue;
39092
+ const eqIdx = line.indexOf("=");
39093
+ if (eqIdx === -1)
39094
+ continue;
39095
+ const key = line.slice(0, eqIdx).trim();
39096
+ let value = line.slice(eqIdx + 1).trim();
39097
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
39098
+ value = value.slice(1, -1);
39099
+ }
39100
+ map[key] = value;
39101
+ }
39102
+ return map;
39103
+ }
39104
+ function answersFromEnvFile(path) {
39105
+ const parsed = parseDotEnvFile(readFileSync8(path, "utf8"));
39106
+ const required = ["CEREFOX_SUPABASE_URL", "CEREFOX_SUPABASE_KEY", "OPENAI_API_KEY"];
39107
+ for (const key of required) {
39108
+ if (!parsed[key] || parsed[key].trim() === "") {
39109
+ throw userError(`Existing .env at ${path} is missing required key "${key}".`, `Fix it manually or run \`cerefox init --force\` to start fresh.`);
39110
+ }
39111
+ }
39112
+ return {
39113
+ CEREFOX_SUPABASE_URL: parsed.CEREFOX_SUPABASE_URL,
39114
+ CEREFOX_SUPABASE_KEY: parsed.CEREFOX_SUPABASE_KEY,
39115
+ OPENAI_API_KEY: parsed.OPENAI_API_KEY,
39116
+ CEREFOX_DATABASE_URL: parsed.CEREFOX_DATABASE_URL,
39117
+ CEREFOX_AUTHOR_NAME: parsed.CEREFOX_AUTHOR_NAME,
39118
+ CEREFOX_AUTHOR_TYPE: parsed.CEREFOX_AUTHOR_TYPE
39119
+ };
39120
+ }
38976
39121
  async function promptForAnswers() {
38977
39122
  println(c.bold("Cerefox first-run setup."));
38978
39123
  println(c.dim(`This will write configuration to ~/.cerefox/.env (or CEREFOX_CONFIG_DIR if set).
@@ -39087,35 +39232,38 @@ async function validateOpenAI(key) {
39087
39232
  throw userError(`OpenAI key validation failed: ${resp.status} ${body.slice(0, 100)}`, "Verify the key on https://platform.openai.com/api-keys.");
39088
39233
  }
39089
39234
  }
39090
- async function action14(options) {
39091
- const envPath = resolveEnvFile();
39092
- if (existsSync6(envPath) && !options.force) {
39093
- println(c.yellow(`⚠ Config already exists at ${envPath}.`));
39094
- const ok2 = await confirm("Overwrite?", true);
39095
- if (!ok2) {
39096
- println(c.dim("Aborted. Use `--force` to skip this prompt next time."));
39097
- return;
39098
- }
39099
- }
39100
- const answers = options.config ? await readConfigFile(options.config) : await promptForAnswers();
39235
+ function printMigrationMenu(cwdEnv, homeEnv) {
39101
39236
  println("");
39102
- println(c.bold("Validating credentials…"));
39103
- await validateSupabase(answers.CEREFOX_SUPABASE_URL, answers.CEREFOX_SUPABASE_KEY);
39104
- println(c.green(" Supabase reachable"));
39105
- await validateOpenAI(answers.OPENAI_API_KEY);
39106
- println(c.green(" ✓ OpenAI key valid (test embedding succeeded)"));
39107
- mkdirSync3(dirname3(envPath), { recursive: true });
39108
- writeFileSync3(envPath, buildEnvFile(answers), "utf8");
39109
- if (process.platform !== "win32") {
39110
- try {
39111
- chmodSync(envPath, 384);
39112
- } catch {
39113
- warn(`Could not chmod 0600 ${envPath}.`);
39114
- }
39115
- }
39237
+ println(c.yellow(`⚠ Found existing config at ${cwdEnv}.`));
39238
+ println("");
39239
+ println("This may be from a previous Python install. The TS CLI can use the");
39240
+ println("same .env — env-var names are identical, no rewrite needed.");
39241
+ println("");
39242
+ println(" " + c.bold("[c]") + " Copy to " + homeEnv + " " + c.green("(recommended)"));
39243
+ println(c.dim(" • TS reads the new home from now on"));
39244
+ println(c.dim(" • Python keeps reading " + cwdEnv + " (backward compat)"));
39245
+ println(c.dim(" • Edit ~/.cerefox/.env going forward; the repo .env is legacy"));
39246
+ println("");
39247
+ println(" " + c.bold("[u]") + " Use " + cwdEnv + " as-is, skip writing anything");
39248
+ println(c.dim(" Both TS and Python keep reading the existing file"));
39249
+ println(c.dim(" • Defer the migration"));
39116
39250
  println("");
39117
- println(c.green(`✓ Wrote ${envPath}`));
39251
+ println(" " + c.bold("[f]") + " Fresh start — interactive prompts, write to " + homeEnv);
39252
+ println(c.dim(" • Use if the existing file is stale or wrong"));
39118
39253
  println("");
39254
+ }
39255
+ async function promptMigrationChoice() {
39256
+ const choice = await ask({
39257
+ type: "text",
39258
+ name: "choice",
39259
+ message: "Choice (c/u/f) [c]",
39260
+ initial: "c",
39261
+ validate: (v) => /^[cuf]?$/i.test(v.trim()) || "Expected c, u, or f."
39262
+ });
39263
+ const ch = choice.trim().toLowerCase() || "c";
39264
+ return ch;
39265
+ }
39266
+ async function postWriteLifecycle(envPath, options) {
39119
39267
  if (!options.skipSchema) {
39120
39268
  println(c.bold("Schema deploy"));
39121
39269
  println(c.dim(` v0.5 doesn't yet bundle the schema-deploy path (it needs the direct
@@ -39159,6 +39307,127 @@ async function action14(options) {
39159
39307
  println(c.dim(" cerefox doctor # verify everything"));
39160
39308
  println(c.dim(' cerefox search "…" # search the KB'));
39161
39309
  println(c.dim(" cerefox ingest <file> # add a doc"));
39310
+ println("");
39311
+ println(c.dim(` Config in effect: ${envPath}`));
39312
+ }
39313
+ function writeAnswersTo(target, answers) {
39314
+ mkdirSync3(dirname3(target), { recursive: true });
39315
+ writeFileSync3(target, buildEnvFile(answers), "utf8");
39316
+ if (process.platform !== "win32") {
39317
+ try {
39318
+ chmodSync(target, 384);
39319
+ } catch {
39320
+ warn(`Could not chmod 0600 ${target}.`);
39321
+ }
39322
+ }
39323
+ }
39324
+ async function action14(options) {
39325
+ const homeEnv = join7(homedir5(), USER_STATE_DIR_NAME, ".env");
39326
+ const cwdEnv = join7(process.cwd(), ".env");
39327
+ const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
39328
+ if (explicitDir) {
39329
+ const target2 = resolveEnvFile();
39330
+ if (existsSync6(target2) && !options.force) {
39331
+ println(c.yellow(`⚠ Config already exists at ${target2}.`));
39332
+ const ok2 = await confirm("Overwrite?", true);
39333
+ if (!ok2) {
39334
+ println(c.dim("Aborted. Use `--force` to skip this prompt next time."));
39335
+ return;
39336
+ }
39337
+ }
39338
+ const answers2 = options.config ? await readConfigFile(options.config) : await promptForAnswers();
39339
+ println("");
39340
+ println(c.bold("Validating credentials…"));
39341
+ await validateSupabase(answers2.CEREFOX_SUPABASE_URL, answers2.CEREFOX_SUPABASE_KEY);
39342
+ println(c.green(" ✓ Supabase reachable"));
39343
+ await validateOpenAI(answers2.OPENAI_API_KEY);
39344
+ println(c.green(" ✓ OpenAI key valid (test embedding succeeded)"));
39345
+ writeAnswersTo(target2, answers2);
39346
+ println("");
39347
+ println(c.green(`✓ Wrote ${target2}`));
39348
+ println("");
39349
+ await postWriteLifecycle(target2, options);
39350
+ return;
39351
+ }
39352
+ if (existsSync6(homeEnv) && !options.force) {
39353
+ println(c.yellow(`⚠ Config already exists at ${homeEnv}.`));
39354
+ const ok2 = await confirm("Overwrite?", true);
39355
+ if (!ok2) {
39356
+ println(c.dim("Aborted. Use `--force` to skip this prompt next time."));
39357
+ return;
39358
+ }
39359
+ const answers2 = options.config ? await readConfigFile(options.config) : await promptForAnswers();
39360
+ println("");
39361
+ println(c.bold("Validating credentials…"));
39362
+ await validateSupabase(answers2.CEREFOX_SUPABASE_URL, answers2.CEREFOX_SUPABASE_KEY);
39363
+ println(c.green(" ✓ Supabase reachable"));
39364
+ await validateOpenAI(answers2.OPENAI_API_KEY);
39365
+ println(c.green(" ✓ OpenAI key valid (test embedding succeeded)"));
39366
+ writeAnswersTo(homeEnv, answers2);
39367
+ println("");
39368
+ println(c.green(`✓ Wrote ${homeEnv}`));
39369
+ println("");
39370
+ await postWriteLifecycle(homeEnv, options);
39371
+ return;
39372
+ }
39373
+ if (existsSync6(cwdEnv) && !options.force && !options.config) {
39374
+ printMigrationMenu(cwdEnv, homeEnv);
39375
+ const ch = await promptMigrationChoice();
39376
+ println("");
39377
+ if (ch === "c") {
39378
+ mkdirSync3(dirname3(homeEnv), { recursive: true });
39379
+ copyFileSync2(cwdEnv, homeEnv);
39380
+ if (process.platform !== "win32") {
39381
+ try {
39382
+ chmodSync(homeEnv, 384);
39383
+ } catch {
39384
+ warn(`Could not chmod 0600 ${homeEnv}.`);
39385
+ }
39386
+ }
39387
+ println(c.green(`✓ Copied ${cwdEnv} → ${homeEnv}`));
39388
+ println(c.dim(` Repo file unchanged — Python still reads it during migration.`));
39389
+ println("");
39390
+ const answers2 = answersFromEnvFile(homeEnv);
39391
+ println(c.bold("Validating credentials…"));
39392
+ await validateSupabase(answers2.CEREFOX_SUPABASE_URL, answers2.CEREFOX_SUPABASE_KEY);
39393
+ println(c.green(" ✓ Supabase reachable"));
39394
+ await validateOpenAI(answers2.OPENAI_API_KEY);
39395
+ println(c.green(" ✓ OpenAI key valid (test embedding succeeded)"));
39396
+ println("");
39397
+ await postWriteLifecycle(homeEnv, options);
39398
+ return;
39399
+ }
39400
+ if (ch === "u") {
39401
+ const answers2 = answersFromEnvFile(cwdEnv);
39402
+ println(c.bold("Validating existing config…"));
39403
+ await validateSupabase(answers2.CEREFOX_SUPABASE_URL, answers2.CEREFOX_SUPABASE_KEY);
39404
+ println(c.green(" ✓ Supabase reachable"));
39405
+ await validateOpenAI(answers2.OPENAI_API_KEY);
39406
+ println(c.green(" ✓ OpenAI key valid (test embedding succeeded)"));
39407
+ println("");
39408
+ println(c.green(`✓ Using existing config at ${cwdEnv}`));
39409
+ println(c.dim(` TS reads it via the legacy dev-mode fallback (~/.cerefox/.env not present).`));
39410
+ println(c.dim(` Run \`cerefox init\` again later to migrate to the new home.`));
39411
+ println("");
39412
+ await postWriteLifecycle(cwdEnv, options);
39413
+ return;
39414
+ }
39415
+ println(c.dim(`Fresh start. Ignoring ${cwdEnv}; writing a new config to ${homeEnv}.`));
39416
+ println("");
39417
+ }
39418
+ const target = homeEnv;
39419
+ const answers = options.config ? await readConfigFile(options.config) : await promptForAnswers();
39420
+ println("");
39421
+ println(c.bold("Validating credentials…"));
39422
+ await validateSupabase(answers.CEREFOX_SUPABASE_URL, answers.CEREFOX_SUPABASE_KEY);
39423
+ println(c.green(" ✓ Supabase reachable"));
39424
+ await validateOpenAI(answers.OPENAI_API_KEY);
39425
+ println(c.green(" ✓ OpenAI key valid (test embedding succeeded)"));
39426
+ writeAnswersTo(target, answers);
39427
+ println("");
39428
+ println(c.green(`✓ Wrote ${target}`));
39429
+ println("");
39430
+ await postWriteLifecycle(target, options);
39162
39431
  }
39163
39432
  function registerInit(program2) {
39164
39433
  program2.command("init").description("Interactive first-run setup (config, schema deploy stub, optional MCP wiring).").option("-c, --config <file>", "Non-interactive mode: read answers from a JSON file.").option("--force", "Overwrite existing configuration without prompting.").option("--skip-schema", "Skip the schema deploy step.").option("--skip-self-docs", "Skip the bundled self-doc ingest.").option("--skip-agent-config", "Skip the optional MCP agent wiring.").action(action14);
@@ -39435,14 +39704,14 @@ function registerReindex(program2) {
39435
39704
  // src/cli/commands/restore.ts
39436
39705
  init_cli_core();
39437
39706
  init_client();
39438
- import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
39439
- import { homedir as homedir5 } from "node:os";
39440
- import { join as join7, resolve as resolve3 } from "node:path";
39707
+ import { existsSync as existsSync7, readFileSync as readFileSync9, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
39708
+ import { homedir as homedir6 } from "node:os";
39709
+ import { join as join8, resolve as resolve3 } from "node:path";
39441
39710
  function expandHome2(path) {
39442
39711
  if (path === "~")
39443
- return homedir5();
39712
+ return homedir6();
39444
39713
  if (path.startsWith("~/"))
39445
- return join7(homedir5(), path.slice(2));
39714
+ return join8(homedir6(), path.slice(2));
39446
39715
  return path;
39447
39716
  }
39448
39717
  function resolveBackupFile(target) {
@@ -39453,17 +39722,17 @@ function resolveBackupFile(target) {
39453
39722
  const stat = statSync4(path);
39454
39723
  if (stat.isFile())
39455
39724
  return path;
39456
- const candidates = readdirSync3(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync4(join7(path, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
39725
+ const candidates = readdirSync3(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync4(join8(path, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
39457
39726
  if (candidates.length === 0) {
39458
39727
  throw userError(`No cerefox-*.json files in ${path}`);
39459
39728
  }
39460
- return join7(path, candidates[0].name);
39729
+ return join8(path, candidates[0].name);
39461
39730
  }
39462
39731
  async function action20(target, options) {
39463
39732
  const file = resolveBackupFile(target);
39464
39733
  let payload;
39465
39734
  try {
39466
- payload = JSON.parse(readFileSync8(file, "utf8"));
39735
+ payload = JSON.parse(readFileSync9(file, "utf8"));
39467
39736
  } catch (err) {
39468
39737
  throw userError(`Could not parse backup file ${file}: ${err instanceof Error ? err.message : String(err)}`);
39469
39738
  }
@@ -39680,7 +39949,7 @@ function registerSearch(program2) {
39680
39949
  // src/cli/commands/self-update.ts
39681
39950
  init_cli_core();
39682
39951
  init_meta();
39683
- import { spawnSync as spawnSync2 } from "node:child_process";
39952
+ import { spawnSync as spawnSync3 } from "node:child_process";
39684
39953
  function detectRuntime() {
39685
39954
  const bin = (process.argv[1] ?? "").toLowerCase();
39686
39955
  if (bin.includes(".bun") || bin.includes("/bun/")) {
@@ -39753,7 +40022,7 @@ async function action22(options) {
39753
40022
  return;
39754
40023
  }
39755
40024
  }
39756
- const result = spawnSync2(runtime.command, runtime.args(target), {
40025
+ const result = spawnSync3(runtime.command, runtime.args(target), {
39757
40026
  stdio: "inherit"
39758
40027
  });
39759
40028
  if (result.status !== 0) {
@@ -39820,18 +40089,18 @@ init_config();
39820
40089
  init_client();
39821
40090
  import {
39822
40091
  existsSync as existsSync8,
39823
- readFileSync as readFileSync9,
40092
+ readFileSync as readFileSync10,
39824
40093
  readdirSync as readdirSync4,
39825
40094
  statSync as statSync5
39826
40095
  } from "node:fs";
39827
- import { basename as basename4, extname as extname4, join as join8, relative } from "node:path";
40096
+ import { basename as basename4, extname as extname4, join as join9, relative } from "node:path";
39828
40097
  var ROOT_LEVEL_DOCS = ["README.md", "AGENT_GUIDE.md", "AGENT_QUICK_REFERENCE.md"];
39829
40098
  function walkMarkdown(dir) {
39830
40099
  const out = [];
39831
40100
  if (!existsSync8(dir))
39832
40101
  return out;
39833
40102
  for (const name of readdirSync4(dir)) {
39834
- const full = join8(dir, name);
40103
+ const full = join9(dir, name);
39835
40104
  let stat;
39836
40105
  try {
39837
40106
  stat = statSync5(full);
@@ -39851,11 +40120,11 @@ async function action24(options) {
39851
40120
  const project = options.project ?? "cerefox";
39852
40121
  const targets = [];
39853
40122
  for (const rel of ROOT_LEVEL_DOCS) {
39854
- const abs = join8(cwd, rel);
40123
+ const abs = join9(cwd, rel);
39855
40124
  if (existsSync8(abs))
39856
40125
  targets.push({ abs, rel });
39857
40126
  }
39858
- for (const abs of walkMarkdown(join8(cwd, "docs"))) {
40127
+ for (const abs of walkMarkdown(join9(cwd, "docs"))) {
39859
40128
  targets.push({ abs, rel: relative(cwd, abs) });
39860
40129
  }
39861
40130
  if (targets.length === 0) {
@@ -39879,7 +40148,7 @@ async function action24(options) {
39879
40148
  const authorType = resolveAuthorType("agent");
39880
40149
  const outcomes = [];
39881
40150
  for (const t of targets) {
39882
- const content = readFileSync9(t.abs, "utf8");
40151
+ const content = readFileSync10(t.abs, "utf8");
39883
40152
  const title = basename4(t.abs, extname4(t.abs));
39884
40153
  try {
39885
40154
  const message = await ingestTool.handler(client.raw, {
@@ -112,19 +112,22 @@ The local Cerefox MCP server runs on your machine and exposes the same 10 tools
112
112
  Edge Function, communicating with clients over stdio.
113
113
 
114
114
  As of **v0.4.0** the local server ships as an npm package — **[`@cerefox/memory`](https://www.npmjs.com/package/@cerefox/memory)** — built with the official `@modelcontextprotocol/sdk`.
115
- The bin entry is `cerefox` (run as `cerefox mcp`). The recommended client config is `npx -y --package=@cerefox/memory cerefox mcp`.
115
+ The bin entry is `cerefox` (run as `cerefox mcp`). The recommended client config is `npx -y --package=@cerefox/memory cerefox mcp`, or if you've installed the package globally, just `cerefox mcp`.
116
116
 
117
- The legacy `uv run cerefox mcp` invocation **still works** and is preserved as a soft
118
- wrapper: it tries `npx --no-install @cerefox/memory cerefox mcp` first and falls back to the
119
- Python MCP server if npm is unavailable or `@cerefox/memory` isn't installed. New users
120
- should prefer the npm-native config; existing users don't have to change anything.
117
+ The Python `uv run cerefox mcp` invocation **still works** and remains the right choice if
118
+ you've installed Cerefox from a source checkout. v0.4 through v0.5.1 advertised a
119
+ "soft wrapper" that tried to auto-delegate to the npm package, but the probe was unreliable
120
+ under MCP-client launch environments v0.5.2 removed it. The two paths (Python via
121
+ `uv run`, TS via `cerefox mcp` on PATH or via `npx`) are now **fully independent**.
122
+ Pick one explicitly in your MCP client config.
121
123
 
122
124
  - Embeddings are computed locally using your `.env` key (no extra credentials)
123
125
  - Works offline except for the OpenAI embedding API call per query
124
126
  - One setup, all compatible local clients (Claude Desktop, Cursor, Claude Code, Codex CLI, …)
125
127
 
126
- See [`docs/guides/migration-v0.4.md`](migration-v0.4.md) for before/after config snippets
127
- per client.
128
+ See [`docs/guides/migration-v0.5.md`](migration-v0.5.md) for the per-client config
129
+ snippets, the v0.5.2 soft-wrapper removal explainer, and the v0.5.3 `.env` location
130
+ change.
128
131
 
129
132
  > **Why not `mcp-server-fetch`?** The generic fetch MCP only supports GET requests and cannot
130
133
  > make authenticated POST calls to the Edge Functions. The built-in local server is
@@ -1,6 +1,23 @@
1
- # Migrating to Cerefox v0.4.0
2
-
3
- **TL;DR**: nothing urgent. Your existing `cerefox mcp` configs keep
1
+ # Migrating to Cerefox v0.4.0 (historical)
2
+
3
+ > ## Historical document do not use the snippets in this file
4
+ >
5
+ > This guide documents the **v0.4.0 → v0.4.3 migration window** (May 2026).
6
+ > The `cerefox-mcp` bin name referenced throughout was dropped in **v0.5.1**;
7
+ > the soft-wrapper described in some sections was removed in **v0.5.2**.
8
+ > The per-client config snippets below **will not work on @cerefox/memory v0.5+**.
9
+ >
10
+ > **If you're upgrading today, use the current guide instead:**
11
+ > → [`migration-v0.5.md`](migration-v0.5.md) — covers Python `cerefox` → v0.5.x
12
+ > AND v0.4.x → v0.5.x in a single document, with the v0.5.0/v0.5.1/v0.5.2/v0.5.3
13
+ > transitions all explained.
14
+ >
15
+ > This file is preserved so historical CHANGELOG entries that reference it
16
+ > still resolve. It's not maintained.
17
+
18
+ ---
19
+
20
+ **Original TL;DR (preserved verbatim)**: nothing urgent. Your existing `cerefox mcp` configs keep
4
21
  working unchanged. The Python `cerefox mcp` command is now a soft
5
22
  wrapper that transparently uses the new TypeScript MCP server if it's
6
23
  installed, falling back to the legacy Python implementation otherwise.
@@ -1,4 +1,23 @@
1
- # Migrating to Cerefox v0.5.0
1
+ # Migrating to Cerefox v0.5.x
2
+
3
+ **This is the canonical upgrade guide for any user landing on Cerefox v0.5+.**
4
+ It covers the v0.4 → v0.5 transition, the v0.5.x patch trail (v0.5.1, v0.5.2,
5
+ v0.5.3), and the Python `cerefox` → TS `cerefox` migration path.
6
+
7
+ ## Where to start
8
+
9
+ | Coming from | Read |
10
+ |---|---|
11
+ | Never used Cerefox before | [`quickstart.md`](quickstart.md) first, then come back here only if you hit a `.env` / config question |
12
+ | Python `cerefox` (any version through v0.5.x) | "What changed" → "Install paths" → "v0.5.3 migrated `.env`" sections below |
13
+ | `@cerefox/memory` v0.4.x (npm) | "Upgrading an existing MCP client config" → "v0.5.2 fixed the soft wrapper" → "v0.5.3 migrated `.env`" |
14
+ | `@cerefox/memory` v0.5.0 or v0.5.1 (npm) | "v0.5.2 fixed the soft wrapper" + "v0.5.3 migrated `.env`" + "v0.5.4 fixed configure-agent claude-code" |
15
+ | `@cerefox/memory` v0.5.2 (npm) | "v0.5.3 migrated `.env`" + "v0.5.4 fixed configure-agent claude-code" |
16
+ | `@cerefox/memory` v0.5.3 (npm) | **"v0.5.4 fixed configure-agent claude-code"** — re-run configure-agent |
17
+
18
+ > Looking for `migration-v0.4.md`? It's been demoted to a historical
19
+ > record (the bin names it documents no longer exist). Everything you
20
+ > need to know about the v0.4 → v0.5 transition lives in this file.
2
21
 
3
22
  **TL;DR:** the Cerefox CLI is now a TypeScript binary published to npm.
4
23
  You can keep using the Python CLI through v0.7.x (it just prints a
@@ -196,6 +215,122 @@ explicit instead of "magic delegation".
196
215
 
197
216
  ---
198
217
 
218
+ ## v0.5.3 migrated `.env` from `<repo>/.env` to `~/.cerefox/.env`
219
+
220
+ If you've been using the Python `cerefox` CLI, your `.env` lives in your
221
+ repo root (`/path/to/cerefox/.env`). The TS CLI v0.5.2 also read that
222
+ file, via a "CWD `.env` wins" precedence inherited from Python. v0.5.3
223
+ flips that precedence: **once `~/.cerefox/.env` exists, the TS CLI reads
224
+ from there**; the repo file becomes a legacy fallback for Python's
225
+ `uv run cerefox …` workflows.
226
+
227
+ **You see zero behavior change until you run `cerefox init`.** If your
228
+ home dir doesn't have `~/.cerefox/.env`, the TS CLI keeps reading your
229
+ existing repo `.env` (legacy dev-mode precedence). No action required.
230
+
231
+ When you do run `cerefox init` with a repo `.env` already in place, the
232
+ TS CLI offers a three-choice menu:
233
+
234
+ ```
235
+ ⚠ Found existing config at /path/to/cerefox/.env.
236
+
237
+ [c] Copy to /Users/you/.cerefox/.env (recommended)
238
+ • TS reads the new home from now on
239
+ • Python keeps reading /path/to/cerefox/.env (backward compat)
240
+ • Edit ~/.cerefox/.env going forward; the repo .env is legacy
241
+
242
+ [u] Use /path/to/cerefox/.env as-is, skip writing anything
243
+ • Both TS and Python keep reading the existing file
244
+ • Defer the migration
245
+
246
+ [f] Fresh start — interactive prompts, write to /Users/you/.cerefox/.env
247
+ • Use if the existing file is stale or wrong
248
+ ```
249
+
250
+ Pick **[c]** for the typical Python → TS upgrade. The TS CLI starts
251
+ reading `~/.cerefox/.env`; your remaining Python `uv run cerefox …`
252
+ commands keep reading the unchanged repo file. The two files diverge
253
+ only if you start editing one of them — keep them in sync (or just edit
254
+ `~/.cerefox/.env` and accept that Python uses a frozen snapshot until
255
+ v0.9).
256
+
257
+ After v0.9 (Python CLI removed), `cerefox doctor` will say "ok" if you
258
+ delete the repo file. Until then, `doctor` reports it as
259
+ `legacy env … (shadowed by ~/.cerefox/.env)` so you know it's harmless.
260
+
261
+ ### Python paths.py precedence (unchanged)
262
+
263
+ `src/cerefox/paths.py` keeps the v0.5.2 precedence (CWD `.env` wins).
264
+ Your existing `uv run cerefox …` invocations from inside the repo
265
+ continue to read the repo file regardless of what's in `~/.cerefox/`.
266
+ When this module goes away in v0.9+, the divergence resolves naturally.
267
+
268
+ ### `CEREFOX_CONFIG_DIR` is unchanged
269
+
270
+ If you have `CEREFOX_CONFIG_DIR` set (e.g. for a non-standard install),
271
+ it still wins over both home and repo `.env` files. Init writes there
272
+ and skips the migration prompt.
273
+
274
+ ---
275
+
276
+ ## v0.5.4 fixed `cerefox configure-agent --tool claude-code`
277
+
278
+ **If you ran `cerefox configure-agent --tool claude-code` on any version
279
+ from v0.5.0 through v0.5.3, Claude Code did not actually pick up the
280
+ config.** The writer wrote to `~/.claude/mcp.json` — a path Claude Code
281
+ doesn't read. Claude Code's user-scope MCP servers live in
282
+ **`~/.claude.json`** (a dot-file in `$HOME`) under the `.mcpServers` key.
283
+
284
+ The bug went unnoticed because `cerefox doctor` was scanning the same
285
+ wrong path — both surfaces were consistently lying.
286
+
287
+ ### What v0.5.4 changed
288
+
289
+ - **`configure-agent --tool claude-code`** now shells out to Claude Code's
290
+ own `claude mcp add --scope user` CLI. Claude Code manages its own
291
+ config schema; delegating is future-proof. Requires the `claude` CLI
292
+ on PATH (fair assumption — you're configuring it).
293
+ - Before invoking the delegated CLI, the writer takes a defensive backup
294
+ of `~/.claude.json` to `~/.claude.json.pre-cerefox.bak`.
295
+ - **`cerefox doctor`** now scans `~/.claude.json` for a `mcpServers.cerefox`
296
+ entry (not the orphaned `~/.claude/mcp.json` from the bug window).
297
+ - **`--tool claude-desktop` is unchanged** — Claude Desktop has no CLI
298
+ helper, so its writer remains direct-file-write.
299
+
300
+ ### What you need to do
301
+
302
+ Anyone who ran `configure-agent --tool claude-code` on v0.5.0–v0.5.3:
303
+
304
+ ```bash
305
+ # 1. Upgrade
306
+ bun update -g @cerefox/memory # or: npm update -g @cerefox/memory
307
+
308
+ # 2. (Optional) Remove the orphaned file the buggy versions wrote.
309
+ # It does nothing — Claude Code never read it. Safe to delete.
310
+ rm -f ~/.claude/mcp.json
311
+
312
+ # 3. Re-run configure-agent to write the config at the correct path.
313
+ cerefox configure-agent --tool claude-code
314
+
315
+ # 4. Verify
316
+ claude mcp list # should now show 'cerefox'
317
+ cerefox doctor # 'mcp clients' should list 'Claude Code (user)'
318
+
319
+ # 5. Start a fresh Claude Code session — the cerefox tools appear.
320
+ ```
321
+
322
+ Running sessions cache the MCP server list at startup, so an
323
+ **already-open Claude Code session won't pick up the new server**.
324
+ Open a new session.
325
+
326
+ ### `--config-path FILE` override (advanced)
327
+
328
+ If you pass `--config-path FILE` explicitly, configure-agent does a
329
+ direct-write to FILE instead of shelling out — preserves the v0.5.0–v0.5.3
330
+ test path and works for power users who want a specific file location.
331
+
332
+ ---
333
+
199
334
  ## Known gotchas
200
335
 
201
336
  ### `npx` from inside an npm workspace
@@ -4,9 +4,70 @@ Get Cerefox running locally and ingest your first document.
4
4
 
5
5
  > **Upgrading from a previous version?** See the [Upgrading Guide](upgrading.md) for migration steps instead.
6
6
 
7
+ ## Two install paths
8
+
9
+ | You want | Read this section | Why |
10
+ |---|---|---|
11
+ | **Use Cerefox as an MCP server / CLI** | "Path A — npm install" below (fastest) | One install, callable from any directory. No Python needed. Recommended for end users since v0.5. |
12
+ | **Contribute to Cerefox, run the web UI, deploy schema** | "Path B — source checkout" below | Full source: schema deploy, web UI, ingestion pipeline. Required for contributors and for the web UI (until v0.6). |
13
+
14
+ You'll need a Supabase project (free tier works) and an OpenAI API key for either
15
+ path — those go into `.env`. The npm install asks for them interactively via
16
+ `cerefox init`; the source checkout has you write them into a `.env` file
17
+ yourself.
18
+
19
+ ---
20
+
21
+ ## Path A — npm install (fastest, 5 min total)
22
+
23
+ For end users who just want the Cerefox CLI + MCP server on their machine.
24
+
25
+ ### A.1 Prerequisites
26
+ - Node.js 20+ (`node --version`) or Bun 1.0+ (`bun --version`)
27
+ - A Supabase account -- [supabase.com](https://supabase.com) (free tier works)
28
+ - An OpenAI API key -- [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
29
+ - **Schema must be deployed** to your Supabase. Until v0.6 ports the schema
30
+ deploy to TypeScript, this still requires the source checkout (Path B) once,
31
+ or someone else who has the source checkout to deploy it for you.
32
+
33
+ ### A.2 Install
34
+ ```bash
35
+ # One-line install (detects Bun or installs it, falls back to npm):
36
+ curl -fsSL https://github.com/fstamatelopoulos/cerefox/releases/latest/download/install.sh | sh
37
+
38
+ # Or direct:
39
+ bun add -g @cerefox/memory # preferred
40
+ # npm install -g @cerefox/memory # alternative
41
+ ```
42
+
43
+ ### A.3 First-run setup
44
+ ```bash
45
+ cerefox init # 5-step interactive setup
46
+ cerefox doctor # verify the install
47
+ ```
48
+
49
+ ### A.4 Wire up your MCP client
50
+ ```bash
51
+ # Run the ones that apply:
52
+ cerefox configure-agent --tool claude-code
53
+ cerefox configure-agent --tool claude-desktop
54
+ ```
55
+
56
+ `--tool claude-code` shells out to Claude Code's own `claude mcp add --scope user`
57
+ to register the server (Claude Code knows where to store the config).
58
+ `--tool claude-desktop` writes the JSON config file directly.
59
+
60
+ Then restart your MCP client. **Path A users skip ahead to "[Connect an AI agent](#8-connect-an-ai-agent-optional-5-min)" (step 8) for the verification prompt.** Steps 3–7
61
+ below are Path B-only (setting up `.env` by hand, deploying the schema, the web UI).
62
+
7
63
  ---
8
64
 
9
- ## 1. Prerequisites (2 min)
65
+ ## Path B — source checkout (contributors, schema deploy, web UI)
66
+
67
+ For anyone hacking on Cerefox itself, deploying the schema for the first time,
68
+ or running the web UI.
69
+
70
+ ### B.1 Prerequisites
10
71
 
11
72
  - Python 3.11+ (`python3 --version`)
12
73
  - Node.js 18+ and npm (`node --version`)
@@ -14,9 +75,7 @@ Get Cerefox running locally and ingest your first document.
14
75
  - A Supabase account -- [supabase.com](https://supabase.com) (free tier works)
15
76
  - An OpenAI API key -- [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
16
77
 
17
- ---
18
-
19
- ## 2. Install Cerefox (2 min)
78
+ ### B.2 Install Cerefox (2 min)
20
79
 
21
80
  ```bash
22
81
  git clone https://github.com/fstamatelopoulos/cerefox.git
@@ -1,8 +1,27 @@
1
1
  # Upgrading Cerefox
2
2
 
3
- This guide covers upgrading an existing Cerefox installation to the latest version. All steps are idempotent and safe to re-run.
3
+ This guide covers upgrading an existing Cerefox installation. All steps are idempotent and safe to re-run.
4
4
 
5
- ## Standard Upgrade Checklist
5
+ ## Pick your path
6
+
7
+ Cerefox has two install paths since v0.4.0 (npm) and v0.5.0 (TS CLI). The right
8
+ upgrade procedure depends on how you installed Cerefox:
9
+
10
+ | You installed via | Upgrade with |
11
+ |---|---|
12
+ | **npm / Bun** (`@cerefox/memory` global) | `bun update -g @cerefox/memory` (or `npm update -g @cerefox/memory`), then read [`migration-v0.5.md`](migration-v0.5.md) for any breaking-change notes per version. **`cerefox doctor`** verifies the install. |
13
+ | **Source checkout** (`git clone` + `uv sync`) | The "Standard Upgrade Checklist" below — Python deps, schema migrations, Edge Functions, frontend build, the works. |
14
+ | **Both** (you contribute AND have the npm bin globally) | Both flows. The two paths share the same Supabase + `.env`; just keep them updated in lockstep. |
15
+
16
+ > If you're upgrading from Python `cerefox` to the npm-installed TS CLI for the first
17
+ > time, [`migration-v0.5.md`](migration-v0.5.md) is the canonical guide — it covers
18
+ > `cerefox init`'s coexistence flow (`[c]opy` your existing `.env` to `~/.cerefox/.env`),
19
+ > the v0.5.2 soft-wrapper removal, and the v0.5.3 paths precedence change.
20
+
21
+ The rest of this document covers the **source checkout** path (Python + frontend + Edge
22
+ Functions). If you're an npm-installed user, you've already got everything you need.
23
+
24
+ ## Standard Upgrade Checklist (source-checkout users)
6
25
 
7
26
  Run these steps every time you pull a new version:
8
27
 
@@ -61,6 +80,30 @@ open http://localhost:8000/app/
61
80
 
62
81
  Most upgrades require no special steps beyond the standard checklist above. Notes below only apply when upgrading across specific version boundaries.
63
82
 
83
+ ### Upgrading to v0.5.x (from any v0.4.x or earlier)
84
+
85
+ The v0.4 → v0.5 transition is a milestone — the CLI itself moved from Python
86
+ to TypeScript. The full migration guide is [`migration-v0.5.md`](migration-v0.5.md);
87
+ the short version for source-checkout users is:
88
+
89
+ - The Python `cerefox` CLI **still works** through v0.7.x — `uv sync` is enough
90
+ to pull it. It prints a one-line ⚠ deprecation banner on every invocation.
91
+ - The new npm CLI lives alongside: `bun install -g @cerefox/memory` (or
92
+ `npm install -g @cerefox/memory`). `cerefox doctor` from any directory will
93
+ verify it.
94
+ - **v0.5.2** stripped the Python `cerefox mcp` soft-wrapper. If your MCP
95
+ client config uses `uv run --directory /path/to/cerefox cerefox mcp`,
96
+ nothing changes — that path runs the Python MCP server directly. If you
97
+ want the TS server instead, point your client at `cerefox mcp`
98
+ (npm-installed) or `npx -y --package=@cerefox/memory cerefox mcp`.
99
+ - **v0.5.3** changed the TS CLI's `.env` precedence: `~/.cerefox/.env` now
100
+ wins over `<repo>/.env` when both exist. Your existing `<repo>/.env` keeps
101
+ working until you run `cerefox init` and pick the `[c]opy` migration
102
+ option. Python `paths.py` is unchanged.
103
+
104
+ No schema migration, no Edge Function redeploy, no chunk reindex required
105
+ for the v0.4 → v0.5.3 arc.
106
+
64
107
  ### Upgrading to v0.1.20 (from v0.1.19) -- Multi-Project Preservation Fix
65
108
 
66
109
  **Edge Function redeploy is required.** v0.1.20 fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerefox/memory",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Cerefox — user-owned shared memory for AI agents. The local TypeScript runtime: stdio MCP server in v0.4; CLI binary added in v0.5; in-process web server in v0.6; ingestion pipeline in v0.7.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/fstamatelopoulos/cerefox",