coding-friend-cli 1.32.0 → 1.32.2

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.
@@ -6,25 +6,25 @@ import {
6
6
  getMemoryMcpStatus,
7
7
  memoryConfigMenu,
8
8
  writeMemoryMcpEntry
9
- } from "./chunk-U7IVVN45.js";
10
- import {
11
- getLibPath
12
- } from "./chunk-RZRT7NGT.js";
9
+ } from "./chunk-3QK6S74O.js";
13
10
  import {
14
11
  loadConfig,
15
12
  resolveMemoryDir
16
13
  } from "./chunk-GTX6I57V.js";
17
14
  import {
18
- showConfigHint
19
- } from "./chunk-HPNRQYLM.js";
15
+ getLibPath
16
+ } from "./chunk-RZRT7NGT.js";
20
17
  import {
21
- run,
22
- runWithStderr
23
- } from "./chunk-EVGXUDX4.js";
18
+ showConfigHint
19
+ } from "./chunk-Z5Q533UG.js";
24
20
  import {
25
21
  globalConfigPath,
26
22
  localConfigPath
27
23
  } from "./chunk-TWKNGPBO.js";
24
+ import {
25
+ run,
26
+ runWithStderr
27
+ } from "./chunk-EVGXUDX4.js";
28
28
  import {
29
29
  log,
30
30
  printBanner
@@ -34,17 +34,74 @@ import {
34
34
  } from "./chunk-5UVDWG5L.js";
35
35
 
36
36
  // src/commands/memory.ts
37
- import { existsSync, readdirSync, statSync, rmSync } from "fs";
38
- import { join, resolve, sep } from "path";
37
+ import { existsSync as existsSync2, readdirSync, statSync, rmSync } from "fs";
38
+ import { join as join2, resolve, sep } from "path";
39
39
  import { homedir } from "os";
40
40
  import { confirm } from "@inquirer/prompts";
41
+
42
+ // src/lib/mcp-state.ts
43
+ import { existsSync } from "fs";
44
+ import { join } from "path";
41
45
  import chalk from "chalk";
46
+ function warnStaleMcpJson(memoryDir) {
47
+ const localMcpPath = join(process.cwd(), ".mcp.json");
48
+ if (!existsSync(localMcpPath)) return;
49
+ const mcpJson = readJson(localMcpPath);
50
+ if (mcpJson === null) {
51
+ log.warn(".mcp.json exists but could not be parsed \u2014 check for syntax errors.");
52
+ return;
53
+ }
54
+ const state = detectMemoryMcpState(mcpJson, existsSync);
55
+ if (state.kind === "stale" || state.kind === "legacy-valid") {
56
+ if (memoryDir) {
57
+ writeMemoryMcpEntry(memoryDir);
58
+ if (state.kind === "stale") {
59
+ log.success("Auto-updated stale .mcp.json to version-stable npx format.");
60
+ } else {
61
+ log.success("Updated .mcp.json to version-stable npx format.");
62
+ }
63
+ console.log();
64
+ } else if (state.kind === "stale") {
65
+ console.log(chalk.yellow(`\u26A0 Stale MCP config detected in .mcp.json`));
66
+ console.log(chalk.dim(` Path no longer exists: ${state.path}`));
67
+ console.log(chalk.dim(` Run "cf memory mcp" to update to the new format.`));
68
+ console.log();
69
+ } else {
70
+ console.log(chalk.cyan(`\u2139 .mcp.json uses an absolute path for coding-friend-memory.`));
71
+ console.log(chalk.dim(` Consider running "cf memory mcp" to switch to the version-stable format.`));
72
+ console.log();
73
+ }
74
+ }
75
+ }
76
+ function detectMemoryMcpState(mcpJson, pathExists) {
77
+ if (!mcpJson) return { kind: "none" };
78
+ const servers = mcpJson.mcpServers;
79
+ if (!servers) return { kind: "none" };
80
+ const entry = servers["coding-friend-memory"];
81
+ if (!entry) return { kind: "none" };
82
+ const command = entry.command;
83
+ if (!command) return { kind: "none" };
84
+ if (command === "npx") return { kind: "npx" };
85
+ if (command === "node") {
86
+ const args = entry.args;
87
+ const serverPath = args?.[0];
88
+ if (!serverPath) return { kind: "none" };
89
+ if (!pathExists(serverPath)) {
90
+ return { kind: "stale", path: serverPath };
91
+ }
92
+ return { kind: "legacy-valid", path: serverPath };
93
+ }
94
+ return { kind: "none" };
95
+ }
96
+
97
+ // src/commands/memory.ts
98
+ import chalk2 from "chalk";
42
99
  function countMdFiles(dir) {
43
- if (!existsSync(dir)) return 0;
100
+ if (!existsSync2(dir)) return 0;
44
101
  let count = 0;
45
102
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
46
103
  if (entry.isDirectory()) {
47
- count += countMdFiles(join(dir, entry.name));
104
+ count += countMdFiles(join2(dir, entry.name));
48
105
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md") {
49
106
  count++;
50
107
  }
@@ -64,7 +121,7 @@ function truncateError(text) {
64
121
  return [...head, ` ... (${skipped} lines omitted) ...`, ...tail].join("\n");
65
122
  }
66
123
  function ensureMemoryBuilt(mcpDir) {
67
- if (!existsSync(join(mcpDir, "node_modules"))) {
124
+ if (!existsSync2(join2(mcpDir, "node_modules"))) {
68
125
  log.step("Installing memory server dependencies (one-time setup)...");
69
126
  const result = runWithStderr("npm", ["install"], { cwd: mcpDir });
70
127
  if (result.exitCode !== 0) {
@@ -74,7 +131,7 @@ function ensureMemoryBuilt(mcpDir) {
74
131
  }
75
132
  log.success("Done.");
76
133
  }
77
- if (!existsSync(join(mcpDir, "dist"))) {
134
+ if (!existsSync2(join2(mcpDir, "dist"))) {
78
135
  log.step("Building memory server...");
79
136
  const result = runWithStderr("npm", ["run", "build"], { cwd: mcpDir });
80
137
  if (result.exitCode !== 0) {
@@ -85,70 +142,25 @@ function ensureMemoryBuilt(mcpDir) {
85
142
  log.success("Done.");
86
143
  }
87
144
  }
88
- function printMemoryMcpConfig(serverPath, memoryDir) {
89
- console.log(chalk.dim("Add this to your MCP client config:"));
90
- console.log();
91
- console.log(
92
- chalk.yellow.bold("--- Claude Code (.mcp.json in project root) ---")
93
- );
94
- console.log(`
95
- {
96
- "mcpServers": {
97
- "coding-friend-memory": {
98
- "command": "node",
99
- "args": ["${serverPath}", "${memoryDir}"]
100
- }
101
- }
102
- }`);
145
+ function printMemoryMcpConfig(memoryDir) {
146
+ console.log(chalk2.dim("Add this to your MCP client config:"));
103
147
  console.log();
104
- console.log(
105
- chalk.yellow.bold(
106
- "--- Claude Desktop / Claude Chat (claude_desktop_config.json) ---"
107
- )
108
- );
109
- console.log(`
110
- {
148
+ console.log(`{
111
149
  "mcpServers": {
112
150
  "coding-friend-memory": {
113
- "command": "node",
114
- "args": ["${serverPath}", "${memoryDir}"]
151
+ "command": "npx",
152
+ "args": [
153
+ "-y",
154
+ "coding-friend-cli",
155
+ "mcp-serve",
156
+ "${memoryDir}"
157
+ ]
115
158
  }
116
159
  }
117
160
  }`);
118
161
  console.log();
119
- console.log(chalk.yellow.bold("--- Generic MCP client ---"));
120
- console.log(`
121
- Server command: node ${serverPath} ${memoryDir}
122
- Transport: stdio`);
123
- console.log();
124
- console.log(chalk.yellow.bold("--- Available tools ---"));
125
- console.log();
126
- console.log(
127
- ` ${chalk.white("memory_store")} ${chalk.dim("Store a new memory")}`
128
- );
129
- console.log(
130
- ` ${chalk.white("memory_search")} ${chalk.dim("Search memories (keyword match)")}`
131
- );
132
- console.log(
133
- ` ${chalk.white("memory_retrieve")} ${chalk.dim("Get a specific memory by ID")}`
134
- );
135
- console.log(
136
- ` ${chalk.white("memory_list")} ${chalk.dim("List memories with filtering")}`
137
- );
138
- console.log(
139
- ` ${chalk.white("memory_update")} ${chalk.dim("Update existing memory")}`
140
- );
141
- console.log(
142
- ` ${chalk.white("memory_delete")} ${chalk.dim("Delete a memory")}`
143
- );
144
- console.log();
145
- console.log(chalk.yellow.bold("--- Resources ---"));
146
- console.log();
147
- console.log(
148
- ` ${chalk.white("memory://index")} ${chalk.dim("Browse all memories")}`
149
- );
150
- console.log(
151
- ` ${chalk.white("memory://stats")} ${chalk.dim("Storage statistics")}`
162
+ log.dim(
163
+ "Available tools & resources: https://cf.dinhanhthi.com/docs/cli/cf-mcp/"
152
164
  );
153
165
  console.log();
154
166
  log.warn(
@@ -166,43 +178,43 @@ async function memoryStatusCommand() {
166
178
  const docCount = countMdFiles(memoryDir);
167
179
  const mcpDir = getLibPath("cf-memory");
168
180
  ensureMemoryBuilt(mcpDir);
169
- const { isDaemonRunning, getDaemonInfo } = await import(join(mcpDir, "dist/daemon/process.js"));
170
- const { areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
181
+ const { isDaemonRunning, getDaemonInfo } = await import(join2(mcpDir, "dist/daemon/process.js"));
182
+ const { areSqliteDepsAvailable } = await import(join2(mcpDir, "dist/lib/lazy-install.js"));
171
183
  const sqliteAvailable = areSqliteDepsAvailable();
172
184
  const running = await isDaemonRunning();
173
185
  const daemonInfo = getDaemonInfo();
174
186
  let tierLabel;
175
187
  if (sqliteAvailable) {
176
- tierLabel = chalk.cyan("Tier 1 (SQLite + Hybrid)");
188
+ tierLabel = chalk2.cyan("Tier 1 (SQLite + Hybrid)");
177
189
  } else if (running) {
178
- tierLabel = chalk.cyan("Tier 2 (MiniSearch + Daemon)");
190
+ tierLabel = chalk2.cyan("Tier 2 (MiniSearch + Daemon)");
179
191
  } else {
180
- tierLabel = chalk.cyan("Tier 3 (Markdown)");
192
+ tierLabel = chalk2.cyan("Tier 3 (Markdown)");
181
193
  }
182
194
  printBanner("\u{1F9E0} Coding Friend Memory");
183
195
  console.log();
184
196
  log.info(`Tier: ${tierLabel}`);
185
- log.info(`Memory dir: ${chalk.cyan(memoryDir)}`);
186
- log.info(`Memories in this dir: ${chalk.green(String(docCount))}`);
197
+ log.info(`Memory dir: ${chalk2.cyan(memoryDir)}`);
198
+ log.info(`Memories in this dir: ${chalk2.green(String(docCount))}`);
187
199
  if (running && daemonInfo) {
188
200
  const uptime = (Date.now() - daemonInfo.startedAt) / 1e3;
189
201
  log.info(
190
- `Daemon: ${chalk.green("running")} (PID ${daemonInfo.pid}, uptime ${formatUptime(uptime)}) ${chalk.dim('Turn it off by "cf memory stop-daemon"')}`
202
+ `Daemon: ${chalk2.green("running")} (PID ${daemonInfo.pid}, uptime ${formatUptime(uptime)}) ${chalk2.dim('Turn it off by "cf memory stop-daemon"')}`
191
203
  );
192
204
  } else if (sqliteAvailable) {
193
205
  log.info(
194
- `Daemon: ${chalk.dim("stopped")} ${chalk.dim("(not needed \u2014 Tier 1 uses SQLite directly)")}`
206
+ `Daemon: ${chalk2.dim("stopped")} ${chalk2.dim("(not needed \u2014 Tier 1 uses SQLite directly)")}`
195
207
  );
196
208
  } else {
197
209
  log.info(
198
- `Daemon: ${chalk.dim("stopped")} ${chalk.dim('(run "cf memory start-daemon" for Tier 2 search)')}`
210
+ `Daemon: ${chalk2.dim("stopped")} ${chalk2.dim('(run "cf memory start-daemon" for Tier 2 search)')}`
199
211
  );
200
212
  }
201
213
  if (sqliteAvailable) {
202
- log.info(`SQLite deps: ${chalk.green("installed")}`);
214
+ log.info(`SQLite deps: ${chalk2.green("installed")}`);
203
215
  } else {
204
216
  log.info(
205
- `SQLite deps: ${chalk.dim("not installed")} (run "cf memory init" to enable Tier 1)`
217
+ `SQLite deps: ${chalk2.dim("not installed")} (run "cf memory init" to enable Tier 1)`
206
218
  );
207
219
  }
208
220
  const config = loadConfig();
@@ -210,33 +222,33 @@ async function memoryStatusCommand() {
210
222
  if (embeddingConfig?.provider || embeddingConfig?.model) {
211
223
  const provider = embeddingConfig.provider ?? "transformers";
212
224
  const model = embeddingConfig.model ?? (provider === "ollama" ? "all-minilm:l6-v2" : "Xenova/all-MiniLM-L6-v2");
213
- log.info(`Embedding: ${chalk.cyan(model)} ${chalk.dim(`(${provider})`)}`);
225
+ log.info(`Embedding: ${chalk2.cyan(model)} ${chalk2.dim(`(${provider})`)}`);
214
226
  }
215
227
  const mcpStatus = getMemoryMcpStatus();
216
228
  if (mcpStatus.configured && mcpStatus.scope === "local") {
217
229
  log.info(
218
- `MCP: ${chalk.green("configured")} ${chalk.dim("(local .mcp.json)")}`
230
+ `MCP: ${chalk2.green("configured")} ${chalk2.dim("(local .mcp.json)")}`
219
231
  );
220
232
  } else if (mcpStatus.configured && mcpStatus.scope === "global") {
221
233
  log.info(
222
- `MCP: ${chalk.green("configured")} ${chalk.dim("(global ~/.claude/.mcp.json)")} ${chalk.yellow("\u26A0 global config uses a fixed path \u2014 only works for one project")}`
234
+ `MCP: ${chalk2.green("configured")} ${chalk2.dim("(global ~/.claude/.mcp.json)")} ${chalk2.yellow("\u26A0 global config uses a fixed path \u2014 only works for one project")}`
223
235
  );
224
236
  } else {
225
237
  log.info(
226
- `MCP: ${chalk.dim("not configured in this project")} ${chalk.dim('(run "cf memory init" or add manually via "cf memory mcp")')}`
238
+ `MCP: ${chalk2.dim("not configured in this project")} ${chalk2.dim('(run "cf memory init" or add manually via "cf memory mcp")')}`
227
239
  );
228
240
  }
229
241
  const autoCapture = config.memory?.autoCapture ?? false;
230
242
  log.info(
231
- `Auto-capture: ${autoCapture ? chalk.green("on") : chalk.dim("off")}`
243
+ `Auto-capture: ${autoCapture ? chalk2.green("on") : chalk2.dim("off")}`
232
244
  );
233
- if (existsSync(memoryDir)) {
245
+ if (existsSync2(memoryDir)) {
234
246
  const categories = readdirSync(memoryDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
235
- const catCount = countMdFiles(join(memoryDir, d.name));
247
+ const catCount = countMdFiles(join2(memoryDir, d.name));
236
248
  return `${d.name} (${catCount})`;
237
249
  }).filter((s) => !s.endsWith("(0)"));
238
250
  if (categories.length > 0) {
239
- log.info(`Categories: ${chalk.dim(categories.join(", "))}`);
251
+ log.info(`Categories: ${chalk2.dim(categories.join(", "))}`);
240
252
  }
241
253
  }
242
254
  console.log();
@@ -245,7 +257,7 @@ async function memoryStatusCommand() {
245
257
  }
246
258
  async function memorySearchCommand(query) {
247
259
  const memoryDir = getMemoryDir();
248
- if (!existsSync(memoryDir)) {
260
+ if (!existsSync2(memoryDir)) {
249
261
  log.error(`Memory dir not found: ${memoryDir}`);
250
262
  log.dim("Run `cf init` to create project folders.");
251
263
  process.exit(1);
@@ -257,7 +269,7 @@ async function memorySearchCommand(query) {
257
269
  [
258
270
  "-e",
259
271
  `
260
- import { MarkdownBackend } from ${JSON.stringify(join(mcpDir, "dist/backends/markdown.js"))};
272
+ import { MarkdownBackend } from ${JSON.stringify(join2(mcpDir, "dist/backends/markdown.js"))};
261
273
  const backend = new MarkdownBackend(${JSON.stringify(memoryDir)});
262
274
  const results = await backend.search({ query: process.env.CF_SEARCH_QUERY, limit: 10 });
263
275
  for (const r of results) {
@@ -280,7 +292,7 @@ async function memoryListCommand(opts) {
280
292
  return memoryListProjectsCommand();
281
293
  }
282
294
  const memoryDir = getMemoryDir();
283
- if (!existsSync(memoryDir)) {
295
+ if (!existsSync2(memoryDir)) {
284
296
  log.info(`No memory directory found at: ${memoryDir}`);
285
297
  log.dim(
286
298
  "This folder has no memories yet. Use --projects to list all project databases."
@@ -297,7 +309,7 @@ async function memoryListCommand(opts) {
297
309
  [
298
310
  "-e",
299
311
  `
300
- import { MarkdownBackend } from ${JSON.stringify(join(mcpDir, "dist/backends/markdown.js"))};
312
+ import { MarkdownBackend } from ${JSON.stringify(join2(mcpDir, "dist/backends/markdown.js"))};
301
313
  const backend = new MarkdownBackend(${JSON.stringify(memoryDir)});
302
314
  const metas = await backend.list({});
303
315
  if (metas.length === 0) { console.log("No memories found."); process.exit(0); }
@@ -333,7 +345,7 @@ async function memoryStartDaemonCommand() {
333
345
  const memoryDir = getMemoryDir();
334
346
  const mcpDir = getLibPath("cf-memory");
335
347
  ensureMemoryBuilt(mcpDir);
336
- const { isDaemonRunning, getDaemonInfo, spawnDaemon } = await import(join(mcpDir, "dist/daemon/process.js"));
348
+ const { isDaemonRunning, getDaemonInfo, spawnDaemon } = await import(join2(mcpDir, "dist/daemon/process.js"));
337
349
  if (await isDaemonRunning()) {
338
350
  const info = getDaemonInfo();
339
351
  log.info(`Daemon already running (PID ${info?.pid})`);
@@ -345,7 +357,7 @@ async function memoryStartDaemonCommand() {
345
357
  const result = await spawnDaemon(memoryDir, embedding, {});
346
358
  if (result) {
347
359
  log.success(`Daemon started (PID ${result.pid})`);
348
- log.info(`Watching ${chalk.cyan(memoryDir)} for changes`);
360
+ log.info(`Watching ${chalk2.cyan(memoryDir)} for changes`);
349
361
  } else {
350
362
  log.error("Daemon did not start within 3 seconds");
351
363
  process.exit(1);
@@ -354,7 +366,7 @@ async function memoryStartDaemonCommand() {
354
366
  async function memoryStopDaemonCommand() {
355
367
  const mcpDir = getLibPath("cf-memory");
356
368
  ensureMemoryBuilt(mcpDir);
357
- const { stopDaemon, isDaemonRunning } = await import(join(mcpDir, "dist/daemon/process.js"));
369
+ const { stopDaemon, isDaemonRunning } = await import(join2(mcpDir, "dist/daemon/process.js"));
358
370
  if (!await isDaemonRunning()) {
359
371
  log.info("Daemon is not running.");
360
372
  return;
@@ -371,10 +383,10 @@ async function memoryRebuildCommand() {
371
383
  const memoryDir = getMemoryDir();
372
384
  const mcpDir = getLibPath("cf-memory");
373
385
  ensureMemoryBuilt(mcpDir);
374
- const { areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
386
+ const { areSqliteDepsAvailable } = await import(join2(mcpDir, "dist/lib/lazy-install.js"));
375
387
  if (areSqliteDepsAvailable()) {
376
388
  log.step("Rebuilding SQLite index + embeddings...");
377
- const { SqliteBackend } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
389
+ const { SqliteBackend } = await import(join2(mcpDir, "dist/backends/sqlite/index.js"));
378
390
  const config = loadConfig();
379
391
  const embedding = config.memory?.embedding;
380
392
  const opts = embedding ? { embedding, skipVec: false } : { skipVec: false };
@@ -384,7 +396,7 @@ async function memoryRebuildCommand() {
384
396
  const stats = await backend.stats();
385
397
  log.success(`Rebuilt: ${stats.total} memories indexed.`);
386
398
  if (backend.isVecEnabled()) {
387
- log.info(`Vector search: ${chalk.green("enabled")}`);
399
+ log.info(`Vector search: ${chalk2.green("enabled")}`);
388
400
  }
389
401
  if (!backend.isRebuildNeeded()) {
390
402
  log.info("Embedding dimensions: up to date");
@@ -394,14 +406,14 @@ async function memoryRebuildCommand() {
394
406
  }
395
407
  return;
396
408
  }
397
- const { isDaemonRunning, getDaemonPaths } = await import(join(mcpDir, "dist/daemon/process.js"));
409
+ const { isDaemonRunning, getDaemonPaths } = await import(join2(mcpDir, "dist/daemon/process.js"));
398
410
  if (!await isDaemonRunning()) {
399
411
  log.info("No SQLite deps and daemon not running. Nothing to rebuild.");
400
412
  log.dim("Install Tier 1 deps: cf memory init");
401
413
  log.dim("Or start the daemon: cf memory start-daemon");
402
414
  return;
403
415
  }
404
- const { DaemonClient } = await import(join(mcpDir, "dist/lib/daemon-client.js"));
416
+ const { DaemonClient } = await import(join2(mcpDir, "dist/lib/daemon-client.js"));
405
417
  const paths = getDaemonPaths();
406
418
  const client = new DaemonClient(paths.socketPath);
407
419
  log.step("Rebuilding search index via daemon...");
@@ -418,7 +430,7 @@ function getDbPath(memoryDir) {
418
430
  const stripped = resolved.replace(/\/docs\/memory$/, "").replace(/\/memory$/, "");
419
431
  const id = stripped.replace(/\//g, "-");
420
432
  const home = homedir();
421
- const dbPath = join(
433
+ const dbPath = join2(
422
434
  home,
423
435
  ".coding-friend",
424
436
  "memory",
@@ -426,7 +438,7 @@ function getDbPath(memoryDir) {
426
438
  id,
427
439
  "db.sqlite"
428
440
  );
429
- return existsSync(dbPath) ? dbPath : null;
441
+ return existsSync2(dbPath) ? dbPath : null;
430
442
  } catch {
431
443
  return null;
432
444
  }
@@ -435,7 +447,7 @@ async function ensureSqliteDepsIfNeeded(mcpDir) {
435
447
  const config = loadConfig();
436
448
  const tier = config.memory?.tier ?? "auto";
437
449
  if (tier === "markdown" || tier === "lite") return true;
438
- const { ensureDeps, areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
450
+ const { ensureDeps, areSqliteDepsAvailable } = await import(join2(mcpDir, "dist/lib/lazy-install.js"));
439
451
  if (areSqliteDepsAvailable()) return true;
440
452
  log.step("Installing SQLite dependencies...");
441
453
  const installed = await ensureDeps({
@@ -506,7 +518,7 @@ async function memoryInitWizard(memoryDir, mcpDir) {
506
518
  }
507
519
  const depsOk = await ensureSqliteDepsIfNeeded(mcpDir);
508
520
  if (!depsOk) return;
509
- if (!existsSync(memoryDir)) {
521
+ if (!existsSync2(memoryDir)) {
510
522
  log.info(
511
523
  "No memory directory found. Memories will be indexed as they're created."
512
524
  );
@@ -522,7 +534,7 @@ async function memoryInitWizard(memoryDir, mcpDir) {
522
534
  return;
523
535
  }
524
536
  log.step(`Importing ${docCount} existing memories into SQLite...`);
525
- const { SqliteBackend } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
537
+ const { SqliteBackend } = await import(join2(mcpDir, "dist/backends/sqlite/index.js"));
526
538
  const embedding = config.memory?.embedding;
527
539
  const backend = new SqliteBackend(memoryDir, {
528
540
  skipVec: false,
@@ -533,7 +545,7 @@ async function memoryInitWizard(memoryDir, mcpDir) {
533
545
  const stats = await backend.stats();
534
546
  log.success(`Imported ${stats.total} memories. DB: ${backend.getDbPath()}`);
535
547
  log.info(
536
- `Vector search: ${backend.isVecEnabled() ? chalk.green("enabled") : chalk.dim("disabled (sqlite-vec not available)")}`
548
+ `Vector search: ${backend.isVecEnabled() ? chalk2.green("enabled") : chalk2.dim("disabled (sqlite-vec not available)")}`
537
549
  );
538
550
  } finally {
539
551
  await backend.close();
@@ -542,13 +554,13 @@ async function memoryInitWizard(memoryDir, mcpDir) {
542
554
  console.log();
543
555
  log.success('Memory initialized! Run "cf memory status" to verify.');
544
556
  log.info(
545
- `Tip: Run ${chalk.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
557
+ `Tip: Run ${chalk2.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
546
558
  );
547
559
  }
548
- async function setupMemoryMcp(memoryDir, mcpDir) {
560
+ async function setupMemoryMcp(memoryDir, _mcpDir) {
549
561
  const mcpStatus = getMemoryMcpStatus();
550
562
  if (mcpStatus.configured && mcpStatus.scope === "local") {
551
- log.info(`MCP: ${chalk.green("already configured")} in .mcp.json`);
563
+ log.info(`MCP: ${chalk2.green("already configured")} in .mcp.json`);
552
564
  return;
553
565
  }
554
566
  console.log();
@@ -564,14 +576,7 @@ async function setupMemoryMcp(memoryDir, mcpDir) {
564
576
  log.dim('Skipped. Run "cf memory mcp" anytime to get the config.');
565
577
  return;
566
578
  }
567
- const serverPath = join(mcpDir, "dist", "index.js");
568
- if (!existsSync(serverPath)) {
569
- log.warn(
570
- "cf-memory not built yet. Run `cf memory mcp` after building to get the config."
571
- );
572
- return;
573
- }
574
- writeMemoryMcpEntry(serverPath, memoryDir);
579
+ writeMemoryMcpEntry(memoryDir);
575
580
  }
576
581
  async function memoryInitCommand() {
577
582
  const memoryDir = getMemoryDir();
@@ -604,7 +609,7 @@ async function memoryConfigCommand() {
604
609
  }
605
610
  function getProjectsBaseDir() {
606
611
  const home = homedir();
607
- return join(home, ".coding-friend", "memory", "projects");
612
+ return join2(home, ".coding-friend", "memory", "projects");
608
613
  }
609
614
  function formatSize(bytes) {
610
615
  if (bytes < 1024) return `${bytes} B`;
@@ -622,9 +627,9 @@ function formatDate(raw) {
622
627
  }
623
628
  function dirSize(dir) {
624
629
  let total = 0;
625
- if (!existsSync(dir)) return 0;
630
+ if (!existsSync2(dir)) return 0;
626
631
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
627
- const p = join(dir, entry.name);
632
+ const p = join2(dir, entry.name);
628
633
  if (entry.isFile()) {
629
634
  total += statSync(p).size;
630
635
  } else if (entry.isDirectory()) {
@@ -634,7 +639,7 @@ function dirSize(dir) {
634
639
  return total;
635
640
  }
636
641
  function getProjectInfo(projectDir, projectId, mcpDir, knownSourceDir) {
637
- const dbPath = join(projectDir, "db.sqlite");
642
+ const dbPath = join2(projectDir, "db.sqlite");
638
643
  const size = dirSize(projectDir);
639
644
  const info = {
640
645
  id: projectId,
@@ -643,7 +648,7 @@ function getProjectInfo(projectDir, projectId, mcpDir, knownSourceDir) {
643
648
  size,
644
649
  lastUpdated: null
645
650
  };
646
- if (!existsSync(dbPath)) return info;
651
+ if (!existsSync2(dbPath)) return info;
647
652
  try {
648
653
  const backfillDir = knownSourceDir ? JSON.stringify(knownSourceDir) : "null";
649
654
  const result = run(
@@ -684,7 +689,7 @@ async function memoryListProjectsCommand() {
684
689
  const baseDir = getProjectsBaseDir();
685
690
  const mcpDir = getLibPath("cf-memory");
686
691
  ensureMemoryBuilt(mcpDir);
687
- if (!existsSync(baseDir)) {
692
+ if (!existsSync2(baseDir)) {
688
693
  log.info("No memory projects found.");
689
694
  log.dim('Run "cf memory init" in a project to create one.');
690
695
  return;
@@ -695,35 +700,35 @@ async function memoryListProjectsCommand() {
695
700
  return;
696
701
  }
697
702
  const currentMemoryDir = resolve(getMemoryDir());
698
- const { projectId } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
703
+ const { projectId } = await import(join2(mcpDir, "dist/backends/sqlite/index.js"));
699
704
  const currentProjectId = projectId(currentMemoryDir);
700
705
  log.step(`Scanning ${dirs.length} project(s)...
701
706
  `);
702
707
  const projects = [];
703
708
  for (const id of dirs) {
704
709
  const knownDir = id === currentProjectId ? currentMemoryDir : void 0;
705
- projects.push(getProjectInfo(join(baseDir, id), id, mcpDir, knownDir));
710
+ projects.push(getProjectInfo(join2(baseDir, id), id, mcpDir, knownDir));
706
711
  }
707
712
  projects.sort((a, b) => b.size - a.size);
708
713
  const totalSize = projects.reduce((sum, p) => sum + p.size, 0);
709
714
  const idxW = String(projects.length).length;
710
715
  const header = `${"#".padStart(idxW)} ${"SIZE".padStart(10)} ${"MEMS".padStart(4)} ${"PROJECT ID".padEnd(12)} ${"UPDATED".padEnd(16)} PATH`;
711
- console.log(chalk.bold(header));
712
- console.log(chalk.dim("-".repeat(header.length + 10)));
716
+ console.log(chalk2.bold(header));
717
+ console.log(chalk2.dim("-".repeat(header.length + 10)));
713
718
  projects.forEach((p, i) => {
714
- const idx = chalk.dim(String(i + 1).padStart(idxW));
715
- const sizeStr = chalk.yellow(formatSize(p.size).padStart(10));
716
- const memCount = chalk.green(String(p.memories).padStart(4));
717
- const idStr = chalk.cyan(p.id.padEnd(12));
718
- const dateStr = p.lastUpdated ? formatDate(p.lastUpdated).padEnd(16) : chalk.dim("n/a".padEnd(16));
719
- const pathStr = p.sourceDir ? chalk.dim(p.sourceDir) : chalk.dim("(unknown)");
719
+ const idx = chalk2.dim(String(i + 1).padStart(idxW));
720
+ const sizeStr = chalk2.yellow(formatSize(p.size).padStart(10));
721
+ const memCount = chalk2.green(String(p.memories).padStart(4));
722
+ const idStr = chalk2.cyan(p.id.padEnd(12));
723
+ const dateStr = p.lastUpdated ? formatDate(p.lastUpdated).padEnd(16) : chalk2.dim("n/a".padEnd(16));
724
+ const pathStr = p.sourceDir ? chalk2.dim(p.sourceDir) : chalk2.dim("(unknown)");
720
725
  console.log(
721
726
  `${idx} ${sizeStr} ${memCount} ${idStr} ${dateStr} ${pathStr}`
722
727
  );
723
728
  });
724
729
  console.log();
725
730
  console.log(
726
- chalk.bold(
731
+ chalk2.bold(
727
732
  `Total: ${projects.length} project(s), ${formatSize(totalSize)}`
728
733
  )
729
734
  );
@@ -731,7 +736,7 @@ async function memoryListProjectsCommand() {
731
736
  }
732
737
  async function memoryRmCommand(opts) {
733
738
  const baseDir = getProjectsBaseDir();
734
- if (!existsSync(baseDir)) {
739
+ if (!existsSync2(baseDir)) {
735
740
  log.info("No memory projects found. Nothing to remove.");
736
741
  return;
737
742
  }
@@ -741,9 +746,9 @@ async function memoryRmCommand(opts) {
741
746
  const dirs = readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name);
742
747
  const orphaned = [];
743
748
  for (const id of dirs) {
744
- const projectDir = join(baseDir, id);
749
+ const projectDir = join2(baseDir, id);
745
750
  const info = getProjectInfo(projectDir, id, mcpDir);
746
- if (info.sourceDir && !existsSync(info.sourceDir)) {
751
+ if (info.sourceDir && !existsSync2(info.sourceDir)) {
747
752
  orphaned.push({
748
753
  id,
749
754
  reason: `source dir missing: ${info.sourceDir}`,
@@ -767,7 +772,7 @@ async function memoryRmCommand(opts) {
767
772
  );
768
773
  console.log();
769
774
  for (const o of orphaned) {
770
- console.log(` ${chalk.cyan(o.id)} ${chalk.dim(o.reason)}`);
775
+ console.log(` ${chalk2.cyan(o.id)} ${chalk2.dim(o.reason)}`);
771
776
  }
772
777
  console.log();
773
778
  const ok = await confirm({
@@ -779,7 +784,7 @@ async function memoryRmCommand(opts) {
779
784
  return;
780
785
  }
781
786
  for (const o of orphaned) {
782
- rmSync(join(baseDir, o.id), { recursive: true, force: true });
787
+ rmSync(join2(baseDir, o.id), { recursive: true, force: true });
783
788
  }
784
789
  log.success(
785
790
  `Deleted ${orphaned.length} orphaned project(s) (${formatSize(totalSize)}).`
@@ -795,7 +800,7 @@ async function memoryRmCommand(opts) {
795
800
  return;
796
801
  }
797
802
  const totalSize = dirs.reduce(
798
- (sum, d) => sum + dirSize(join(baseDir, d.name)),
803
+ (sum, d) => sum + dirSize(join2(baseDir, d.name)),
799
804
  0
800
805
  );
801
806
  log.warn(
@@ -812,19 +817,19 @@ async function memoryRmCommand(opts) {
812
817
  return;
813
818
  }
814
819
  for (const d of dirs) {
815
- rmSync(join(baseDir, d.name), { recursive: true, force: true });
820
+ rmSync(join2(baseDir, d.name), { recursive: true, force: true });
816
821
  }
817
822
  log.success(`Deleted ${dirs.length} project database(s).`);
818
823
  return;
819
824
  }
820
825
  if (opts.projectId) {
821
- const projectDir = join(baseDir, opts.projectId);
826
+ const projectDir = join2(baseDir, opts.projectId);
822
827
  const resolved = resolve(projectDir);
823
828
  if (!resolved.startsWith(resolve(baseDir) + sep)) {
824
829
  log.error("Invalid project ID.");
825
830
  process.exit(1);
826
831
  }
827
- if (!existsSync(projectDir)) {
832
+ if (!existsSync2(projectDir)) {
828
833
  log.error(`Project "${opts.projectId}" not found.`);
829
834
  log.dim('Run "cf memory list --projects" to see available projects.');
830
835
  process.exit(1);
@@ -855,11 +860,13 @@ async function memoryMcpCommand() {
855
860
  const memoryDir = getMemoryDir();
856
861
  const mcpDir = getLibPath("cf-memory");
857
862
  ensureMemoryBuilt(mcpDir);
858
- const serverPath = join(mcpDir, "dist", "index.js");
859
- printMemoryMcpConfig(serverPath, memoryDir);
863
+ warnStaleMcpJson(memoryDir);
864
+ printMemoryMcpConfig(memoryDir);
860
865
  }
861
866
 
862
867
  export {
868
+ warnStaleMcpJson,
869
+ detectMemoryMcpState,
863
870
  ensureMemoryBuilt,
864
871
  printMemoryMcpConfig,
865
872
  memoryStatusCommand,