coding-friend-cli 1.16.0 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +12 -0
  2. package/dist/{chunk-D4EWPGBL.js → chunk-C5LYVVEI.js} +1 -1
  3. package/dist/{chunk-X5WEODUD.js → chunk-CYQU33FY.js} +1 -0
  4. package/dist/{chunk-QNLL3ZDF.js → chunk-G6CEEMAR.js} +3 -3
  5. package/dist/{chunk-4DB4XTSL.js → chunk-KTX4MGMR.js} +15 -1
  6. package/dist/{chunk-KJUGTLPQ.js → chunk-YO6JKGR3.js} +38 -2
  7. package/dist/{config-AIZJJ5D2.js → config-LZFXXOI4.js} +276 -14
  8. package/dist/{dev-WJ5QQ35B.js → dev-R3IYWZ3M.js} +2 -2
  9. package/dist/{disable-JDVOQNZG.js → disable-R6K5YJN4.js} +2 -2
  10. package/dist/{enable-JBJ4Q2S7.js → enable-HF4PYVJN.js} +2 -2
  11. package/dist/{host-NA7LZ4HX.js → host-SYZH3FVC.js} +4 -4
  12. package/dist/index.js +78 -18
  13. package/dist/{init-FZ3GG53E.js → init-MF7ISADJ.js} +102 -6
  14. package/dist/{install-I3GOS56Q.js → install-Q4PWEU43.js} +4 -4
  15. package/dist/{mcp-DLS3J6QJ.js → mcp-TBEDYELW.js} +4 -4
  16. package/dist/memory-RGLM35HC.js +647 -0
  17. package/dist/postinstall.js +1 -1
  18. package/dist/{session-E3CZJJZQ.js → session-H4XW2WXH.js} +1 -1
  19. package/dist/{statusline-6HQCDWBD.js → statusline-6Y2EBAFQ.js} +1 -1
  20. package/dist/{uninstall-JN5YIKKM.js → uninstall-3PSUDGI4.js} +3 -3
  21. package/dist/{update-OWS4IJTG.js → update-WL6SFGGO.js} +4 -4
  22. package/lib/cf-memory/CHANGELOG.md +25 -0
  23. package/lib/cf-memory/README.md +284 -0
  24. package/lib/cf-memory/package-lock.json +2790 -0
  25. package/lib/cf-memory/package.json +31 -0
  26. package/lib/cf-memory/scripts/migrate-frontmatter.ts +134 -0
  27. package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +223 -0
  28. package/lib/cf-memory/src/__tests__/daemon.test.ts +407 -0
  29. package/lib/cf-memory/src/__tests__/dedup.test.ts +103 -0
  30. package/lib/cf-memory/src/__tests__/embeddings.test.ts +292 -0
  31. package/lib/cf-memory/src/__tests__/lazy-install.test.ts +210 -0
  32. package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +410 -0
  33. package/lib/cf-memory/src/__tests__/migration.test.ts +255 -0
  34. package/lib/cf-memory/src/__tests__/migrations.test.ts +288 -0
  35. package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +262 -0
  36. package/lib/cf-memory/src/__tests__/ollama.test.ts +48 -0
  37. package/lib/cf-memory/src/__tests__/schema.test.ts +128 -0
  38. package/lib/cf-memory/src/__tests__/search.test.ts +115 -0
  39. package/lib/cf-memory/src/__tests__/temporal-decay.test.ts +54 -0
  40. package/lib/cf-memory/src/__tests__/tier.test.ts +293 -0
  41. package/lib/cf-memory/src/__tests__/tools.test.ts +83 -0
  42. package/lib/cf-memory/src/backends/markdown.ts +318 -0
  43. package/lib/cf-memory/src/backends/minisearch.ts +203 -0
  44. package/lib/cf-memory/src/backends/sqlite/embeddings.ts +286 -0
  45. package/lib/cf-memory/src/backends/sqlite/index.ts +549 -0
  46. package/lib/cf-memory/src/backends/sqlite/migrations.ts +188 -0
  47. package/lib/cf-memory/src/backends/sqlite/schema.ts +120 -0
  48. package/lib/cf-memory/src/backends/sqlite/search.ts +296 -0
  49. package/lib/cf-memory/src/bin/cf-memory.ts +2 -0
  50. package/lib/cf-memory/src/daemon/entry.ts +99 -0
  51. package/lib/cf-memory/src/daemon/process.ts +271 -0
  52. package/lib/cf-memory/src/daemon/server.ts +166 -0
  53. package/lib/cf-memory/src/daemon/watcher.ts +90 -0
  54. package/lib/cf-memory/src/index.ts +53 -0
  55. package/lib/cf-memory/src/lib/backend.ts +23 -0
  56. package/lib/cf-memory/src/lib/daemon-client.ts +163 -0
  57. package/lib/cf-memory/src/lib/dedup.ts +80 -0
  58. package/lib/cf-memory/src/lib/lazy-install.ts +274 -0
  59. package/lib/cf-memory/src/lib/ollama.ts +76 -0
  60. package/lib/cf-memory/src/lib/temporal-decay.ts +19 -0
  61. package/lib/cf-memory/src/lib/tier.ts +107 -0
  62. package/lib/cf-memory/src/lib/types.ts +109 -0
  63. package/lib/cf-memory/src/resources/index.ts +62 -0
  64. package/lib/cf-memory/src/server.ts +20 -0
  65. package/lib/cf-memory/src/tools/delete.ts +38 -0
  66. package/lib/cf-memory/src/tools/list.ts +38 -0
  67. package/lib/cf-memory/src/tools/retrieve.ts +52 -0
  68. package/lib/cf-memory/src/tools/search.ts +47 -0
  69. package/lib/cf-memory/src/tools/store.ts +70 -0
  70. package/lib/cf-memory/src/tools/update.ts +62 -0
  71. package/lib/cf-memory/tsconfig.json +15 -0
  72. package/lib/cf-memory/vitest.config.ts +7 -0
  73. package/package.json +1 -1
@@ -0,0 +1,647 @@
1
+ import {
2
+ loadConfig,
3
+ resolveMemoryDir
4
+ } from "./chunk-KTX4MGMR.js";
5
+ import {
6
+ getLibPath
7
+ } from "./chunk-RZRT7NGT.js";
8
+ import "./chunk-POC2WHU2.js";
9
+ import {
10
+ run
11
+ } from "./chunk-CYQU33FY.js";
12
+ import "./chunk-RWUTFVRB.js";
13
+ import {
14
+ log
15
+ } from "./chunk-W5CD7WTX.js";
16
+
17
+ // src/commands/memory.ts
18
+ import { createHash } from "crypto";
19
+ import { existsSync, readdirSync, statSync, rmSync } from "fs";
20
+ import { join, resolve, sep } from "path";
21
+ import { homedir } from "os";
22
+ import { confirm } from "@inquirer/prompts";
23
+ import chalk from "chalk";
24
+ function countMdFiles(dir) {
25
+ if (!existsSync(dir)) return 0;
26
+ let count = 0;
27
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
28
+ if (entry.isDirectory()) {
29
+ count += countMdFiles(join(dir, entry.name));
30
+ } else if (entry.name.endsWith(".md") && entry.name !== "README.md") {
31
+ count++;
32
+ }
33
+ }
34
+ return count;
35
+ }
36
+ function getMemoryDir(path) {
37
+ return resolveMemoryDir(path);
38
+ }
39
+ function ensureBuilt(mcpDir) {
40
+ if (!existsSync(join(mcpDir, "node_modules"))) {
41
+ log.step("Installing memory server dependencies (one-time setup)...");
42
+ const result = run("npm", ["install", "--silent"], { cwd: mcpDir });
43
+ if (result === null) {
44
+ log.error("Failed to install dependencies");
45
+ process.exit(1);
46
+ }
47
+ log.success("Done.");
48
+ }
49
+ if (!existsSync(join(mcpDir, "dist"))) {
50
+ log.step("Building memory server...");
51
+ const result = run("npm", ["run", "build", "--silent"], { cwd: mcpDir });
52
+ if (result === null) {
53
+ log.error("Failed to build memory server");
54
+ process.exit(1);
55
+ }
56
+ log.success("Done.");
57
+ }
58
+ }
59
+ function formatUptime(seconds) {
60
+ if (seconds < 60) return `${Math.round(seconds)}s`;
61
+ if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
62
+ return `${Math.round(seconds / 3600)}h ${Math.round(seconds % 3600 / 60)}m`;
63
+ }
64
+ async function memoryStatusCommand() {
65
+ const memoryDir = getMemoryDir();
66
+ const docCount = countMdFiles(memoryDir);
67
+ const mcpDir = getLibPath("cf-memory");
68
+ ensureBuilt(mcpDir);
69
+ const { isDaemonRunning, getDaemonInfo } = await import(join(mcpDir, "dist/daemon/process.js"));
70
+ const { areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
71
+ const sqliteAvailable = areSqliteDepsAvailable();
72
+ const running = await isDaemonRunning();
73
+ const daemonInfo = getDaemonInfo();
74
+ let tierLabel;
75
+ if (sqliteAvailable) {
76
+ tierLabel = chalk.cyan("Tier 1 (SQLite + Hybrid)");
77
+ } else if (running) {
78
+ tierLabel = chalk.cyan("Tier 2 (MiniSearch + Daemon)");
79
+ } else {
80
+ tierLabel = chalk.cyan("Tier 3 (Markdown)");
81
+ }
82
+ console.log("=== \u{1F9E0} Coding Friend Memory ===");
83
+ console.log();
84
+ log.info(`Tier: ${tierLabel}`);
85
+ log.info(`Memory dir: ${chalk.cyan(memoryDir)}`);
86
+ log.info(`Memories: ${chalk.green(String(docCount))}`);
87
+ if (running && daemonInfo) {
88
+ const uptime = (Date.now() - daemonInfo.startedAt) / 1e3;
89
+ log.info(
90
+ `Daemon: ${chalk.green("running")} (PID ${daemonInfo.pid}, uptime ${formatUptime(uptime)}) ${chalk.dim('Turn it off by "cf memory stop"')}`
91
+ );
92
+ } else if (sqliteAvailable) {
93
+ log.info(
94
+ `Daemon: ${chalk.dim("stopped")} ${chalk.dim("(not needed \u2014 Tier 1 uses SQLite directly)")}`
95
+ );
96
+ } else {
97
+ log.info(
98
+ `Daemon: ${chalk.dim("stopped")} ${chalk.dim('(run "cf memory start" for Tier 2 search)')}`
99
+ );
100
+ }
101
+ if (sqliteAvailable) {
102
+ log.info(`SQLite deps: ${chalk.green("installed")}`);
103
+ } else {
104
+ log.info(
105
+ `SQLite deps: ${chalk.dim("not installed")} (run "cf memory init" to enable Tier 1)`
106
+ );
107
+ }
108
+ if (existsSync(memoryDir)) {
109
+ const categories = readdirSync(memoryDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
110
+ const catCount = countMdFiles(join(memoryDir, d.name));
111
+ return `${d.name} (${catCount})`;
112
+ }).filter((s) => !s.endsWith("(0)"));
113
+ if (categories.length > 0) {
114
+ log.info(`Categories: ${chalk.dim(categories.join(", "))}`);
115
+ }
116
+ }
117
+ console.log();
118
+ log.dim(`Docs: https://cf.dinhanhthi.com/docs/reference/memory-system/`);
119
+ console.log();
120
+ }
121
+ async function memorySearchCommand(query) {
122
+ const memoryDir = getMemoryDir();
123
+ if (!existsSync(memoryDir)) {
124
+ log.error(`Memory dir not found: ${memoryDir}`);
125
+ log.dim("Run `cf init` to create project folders.");
126
+ process.exit(1);
127
+ }
128
+ const mcpDir = getLibPath("cf-memory");
129
+ ensureBuilt(mcpDir);
130
+ const result = run(
131
+ "node",
132
+ [
133
+ "-e",
134
+ `
135
+ import { MarkdownBackend } from "${join(mcpDir, "dist/backends/markdown.js")}";
136
+ const backend = new MarkdownBackend("${memoryDir}");
137
+ const results = await backend.search({ query: process.env.CF_SEARCH_QUERY, limit: 10 });
138
+ for (const r of results) {
139
+ console.log(\`[\${r.score}] \${r.memory.frontmatter.title}\`);
140
+ console.log(\` \${r.memory.frontmatter.description}\`);
141
+ console.log(\` id: \${r.memory.id} | type: \${r.memory.frontmatter.type} | matched: \${r.matchedOn.join(", ")}\`);
142
+ console.log();
143
+ }
144
+ if (results.length === 0) console.log("No results found.");
145
+ `
146
+ ],
147
+ { cwd: memoryDir, env: { ...process.env, CF_SEARCH_QUERY: query } }
148
+ );
149
+ if (result !== null) {
150
+ console.log(result);
151
+ }
152
+ }
153
+ async function memoryListCommand(opts) {
154
+ if (opts.projects) {
155
+ return memoryListProjectsCommand();
156
+ }
157
+ const memoryDir = getMemoryDir();
158
+ if (!existsSync(memoryDir)) {
159
+ log.info(`No memory directory found at: ${memoryDir}`);
160
+ log.dim(
161
+ "This folder has no memories yet. Use --projects to list all project databases."
162
+ );
163
+ log.dim(
164
+ 'Or run "cf init" to set up this project, then "/cf-scan" in Claude Code.'
165
+ );
166
+ return;
167
+ }
168
+ const mcpDir = getLibPath("cf-memory");
169
+ ensureBuilt(mcpDir);
170
+ const result = run(
171
+ "node",
172
+ [
173
+ "-e",
174
+ `
175
+ import { MarkdownBackend } from "${join(mcpDir, "dist/backends/markdown.js")}";
176
+ const backend = new MarkdownBackend("${memoryDir}");
177
+ const metas = await backend.list({});
178
+ if (metas.length === 0) { console.log("No memories found."); process.exit(0); }
179
+
180
+ // Compute column widths
181
+ const idxW = String(metas.length).length;
182
+ const typeW = Math.max(4, ...metas.map(m => m.frontmatter.type.length));
183
+ const idW = Math.max(2, ...metas.map(m => m.id.length));
184
+
185
+ // Header
186
+ const hdr = "#".padStart(idxW) + " " + "TYPE".padEnd(typeW) + " " + "ID".padEnd(idW) + " " + "TITLE";
187
+ console.log(hdr);
188
+ console.log("-".repeat(hdr.length + 10));
189
+
190
+ metas.forEach((m, i) => {
191
+ const idx = String(i + 1).padStart(idxW);
192
+ const type = m.frontmatter.type.padEnd(typeW);
193
+ const id = m.id.padEnd(idW);
194
+ console.log(idx + " " + type + " " + id + " " + m.frontmatter.title);
195
+ });
196
+
197
+ console.log();
198
+ console.log("Total: " + metas.length + " memories");
199
+ `
200
+ ],
201
+ { cwd: memoryDir }
202
+ );
203
+ if (result !== null) {
204
+ console.log(result);
205
+ }
206
+ }
207
+ async function memoryStartCommand() {
208
+ const memoryDir = getMemoryDir();
209
+ const mcpDir = getLibPath("cf-memory");
210
+ ensureBuilt(mcpDir);
211
+ const { isDaemonRunning, getDaemonInfo, spawnDaemon } = await import(join(mcpDir, "dist/daemon/process.js"));
212
+ if (await isDaemonRunning()) {
213
+ const info = getDaemonInfo();
214
+ log.info(`Daemon already running (PID ${info?.pid})`);
215
+ return;
216
+ }
217
+ log.step("Starting memory daemon...");
218
+ const config = loadConfig();
219
+ const embedding = config.memory?.embedding;
220
+ const result = await spawnDaemon(memoryDir, embedding);
221
+ if (result) {
222
+ log.success(`Daemon started (PID ${result.pid})`);
223
+ log.info(`Watching ${chalk.cyan(memoryDir)} for changes`);
224
+ } else {
225
+ log.error("Daemon did not start within 3 seconds");
226
+ process.exit(1);
227
+ }
228
+ }
229
+ async function memoryStopCommand() {
230
+ const mcpDir = getLibPath("cf-memory");
231
+ ensureBuilt(mcpDir);
232
+ const { stopDaemon, isDaemonRunning } = await import(join(mcpDir, "dist/daemon/process.js"));
233
+ if (!await isDaemonRunning()) {
234
+ log.info("Daemon is not running.");
235
+ return;
236
+ }
237
+ log.step("Stopping memory daemon...");
238
+ const stopped = await stopDaemon();
239
+ if (stopped) {
240
+ log.success("Daemon stopped.");
241
+ } else {
242
+ log.error("Failed to stop daemon.");
243
+ }
244
+ }
245
+ async function memoryRebuildCommand() {
246
+ const memoryDir = getMemoryDir();
247
+ const mcpDir = getLibPath("cf-memory");
248
+ ensureBuilt(mcpDir);
249
+ const { areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
250
+ if (areSqliteDepsAvailable()) {
251
+ log.step("Rebuilding SQLite index + embeddings...");
252
+ const { SqliteBackend } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
253
+ const config = loadConfig();
254
+ const embedding = config.memory?.embedding;
255
+ const opts = embedding ? { embedding, skipVec: false } : { skipVec: false };
256
+ const backend = new SqliteBackend(memoryDir, opts);
257
+ try {
258
+ await backend.rebuild();
259
+ const stats = await backend.stats();
260
+ log.success(`Rebuilt: ${stats.total} memories indexed.`);
261
+ if (backend.isVecEnabled()) {
262
+ log.info(`Vector search: ${chalk.green("enabled")}`);
263
+ }
264
+ if (!backend.isRebuildNeeded()) {
265
+ log.info("Embedding dimensions: up to date");
266
+ }
267
+ } finally {
268
+ await backend.close();
269
+ }
270
+ return;
271
+ }
272
+ const { isDaemonRunning, getDaemonPaths } = await import(join(mcpDir, "dist/daemon/process.js"));
273
+ if (!await isDaemonRunning()) {
274
+ log.info("No SQLite deps and daemon not running. Nothing to rebuild.");
275
+ log.dim("Install Tier 1 deps: cf memory init");
276
+ log.dim("Or start the daemon: cf memory start");
277
+ return;
278
+ }
279
+ const { DaemonClient } = await import(join(mcpDir, "dist/lib/daemon-client.js"));
280
+ const paths = getDaemonPaths();
281
+ const client = new DaemonClient(paths.socketPath);
282
+ log.step("Rebuilding search index via daemon...");
283
+ try {
284
+ await client.rebuild();
285
+ log.success("Index rebuilt.");
286
+ } catch {
287
+ log.error("Rebuild failed or not supported.");
288
+ }
289
+ }
290
+ async function memoryInitCommand() {
291
+ const memoryDir = getMemoryDir();
292
+ const mcpDir = getLibPath("cf-memory");
293
+ ensureBuilt(mcpDir);
294
+ log.step("Initializing Tier 1 (SQLite + Hybrid Search)...");
295
+ const { ensureDeps, areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
296
+ if (areSqliteDepsAvailable()) {
297
+ log.info("SQLite dependencies already installed.");
298
+ } else {
299
+ const installed = await ensureDeps({
300
+ onProgress: (msg) => log.step(msg)
301
+ });
302
+ if (!installed) {
303
+ log.error("Failed to install dependencies.");
304
+ log.dim(
305
+ "Ensure you have a C++ compiler installed (Xcode CLT on macOS, build-essential on Linux)."
306
+ );
307
+ process.exit(1);
308
+ }
309
+ log.success("Dependencies installed.");
310
+ }
311
+ if (!existsSync(memoryDir)) {
312
+ log.info("No memory directory found. Nothing to import.");
313
+ log.success(
314
+ "Tier 1 is ready. Memories will be indexed as they're created."
315
+ );
316
+ return;
317
+ }
318
+ const docCount = countMdFiles(memoryDir);
319
+ if (docCount === 0) {
320
+ log.info("No existing memories to import.");
321
+ log.success(
322
+ "Tier 1 is ready. Memories will be indexed as they're created."
323
+ );
324
+ return;
325
+ }
326
+ log.step(`Importing ${docCount} existing memories into SQLite...`);
327
+ const { SqliteBackend } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
328
+ const config = loadConfig();
329
+ const embedding = config.memory?.embedding;
330
+ const backend = new SqliteBackend(memoryDir, {
331
+ skipVec: false,
332
+ ...embedding && { embedding }
333
+ });
334
+ try {
335
+ await backend.rebuild();
336
+ const stats = await backend.stats();
337
+ log.success(`Imported ${stats.total} memories. DB: ${backend.getDbPath()}`);
338
+ if (backend.isVecEnabled()) {
339
+ log.info(`Vector search: ${chalk.green("enabled")}`);
340
+ } else {
341
+ log.info(
342
+ `Vector search: ${chalk.dim("disabled")} (sqlite-vec not available)`
343
+ );
344
+ }
345
+ } finally {
346
+ await backend.close();
347
+ }
348
+ log.success('Tier 1 initialized. Run "cf memory status" to verify.');
349
+ log.info(
350
+ `Tip: Run ${chalk.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
351
+ );
352
+ }
353
+ function getProjectsBaseDir() {
354
+ const home = homedir();
355
+ return join(home, ".coding-friend", "memory", "projects");
356
+ }
357
+ function formatSize(bytes) {
358
+ if (bytes < 1024) return `${bytes} B`;
359
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
360
+ if (bytes < 1024 * 1024 * 1024)
361
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
362
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
363
+ }
364
+ function formatDate(raw) {
365
+ const d = new Date(raw);
366
+ if (isNaN(d.getTime())) return raw;
367
+ return d.toISOString().slice(0, 16).replace("T", " ");
368
+ }
369
+ function dirSize(dir) {
370
+ let total = 0;
371
+ if (!existsSync(dir)) return 0;
372
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
373
+ const p = join(dir, entry.name);
374
+ if (entry.isFile()) {
375
+ total += statSync(p).size;
376
+ } else if (entry.isDirectory()) {
377
+ total += dirSize(p);
378
+ }
379
+ }
380
+ return total;
381
+ }
382
+ function getProjectInfo(projectDir, projectId, mcpDir, knownSourceDir) {
383
+ const dbPath = join(projectDir, "db.sqlite");
384
+ const size = dirSize(projectDir);
385
+ const info = {
386
+ id: projectId,
387
+ sourceDir: null,
388
+ memories: 0,
389
+ size,
390
+ lastUpdated: null
391
+ };
392
+ if (!existsSync(dbPath)) return info;
393
+ try {
394
+ const backfillDir = knownSourceDir ? JSON.stringify(knownSourceDir) : "null";
395
+ const result = run(
396
+ "node",
397
+ [
398
+ "-e",
399
+ `
400
+ const path = require("path");
401
+ const mod = require(path.join(${JSON.stringify(mcpDir)}, "dist/lib/lazy-install.js"));
402
+ if (!mod.areSqliteDepsAvailable()) { console.log(JSON.stringify({})); process.exit(0); }
403
+ const Database = require(mod.getDepsDir() + "/node_modules/better-sqlite3");
404
+ const backfill = ${backfillDir};
405
+ const db = new Database(${JSON.stringify(dbPath)}, { readonly: !backfill });
406
+ const getMeta = (key) => { try { const r = db.prepare("SELECT value FROM metadata WHERE key = ?").get(key); return r?.value ?? null; } catch { return null; } };
407
+ let sourceDir = getMeta("source_dir");
408
+ if (!sourceDir && backfill) {
409
+ try { db.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)").run("source_dir", backfill); sourceDir = backfill; } catch {}
410
+ }
411
+ const count = (() => { try { const r = db.prepare("SELECT COUNT(*) as c FROM memories").get(); return r?.c ?? 0; } catch { return 0; } })();
412
+ const lastUpdated = (() => { try { const r = db.prepare("SELECT updated FROM memories ORDER BY updated DESC LIMIT 1").get(); return r?.updated ?? null; } catch { return null; } })();
413
+ console.log(JSON.stringify({ sourceDir, memories: count, lastUpdated }));
414
+ db.close();
415
+ `
416
+ ],
417
+ { cwd: projectDir }
418
+ );
419
+ if (result) {
420
+ const parsed = JSON.parse(result.trim());
421
+ info.sourceDir = parsed.sourceDir ?? null;
422
+ info.memories = parsed.memories ?? 0;
423
+ info.lastUpdated = parsed.lastUpdated ?? null;
424
+ }
425
+ } catch {
426
+ }
427
+ return info;
428
+ }
429
+ async function memoryListProjectsCommand() {
430
+ const baseDir = getProjectsBaseDir();
431
+ const mcpDir = getLibPath("cf-memory");
432
+ ensureBuilt(mcpDir);
433
+ if (!existsSync(baseDir)) {
434
+ log.info("No memory projects found.");
435
+ log.dim('Run "cf memory init" in a project to create one.');
436
+ return;
437
+ }
438
+ const dirs = readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name);
439
+ if (dirs.length === 0) {
440
+ log.info("No memory projects found.");
441
+ return;
442
+ }
443
+ const currentMemoryDir = resolve(getMemoryDir());
444
+ const currentHash = createHash("sha256").update(currentMemoryDir).digest("hex").slice(0, 12);
445
+ log.step(`Scanning ${dirs.length} project(s)...
446
+ `);
447
+ const projects = [];
448
+ for (const id of dirs) {
449
+ const knownDir = id === currentHash ? currentMemoryDir : void 0;
450
+ projects.push(getProjectInfo(join(baseDir, id), id, mcpDir, knownDir));
451
+ }
452
+ projects.sort((a, b) => b.size - a.size);
453
+ const totalSize = projects.reduce((sum, p) => sum + p.size, 0);
454
+ const idxW = String(projects.length).length;
455
+ const header = `${"#".padStart(idxW)} ${"SIZE".padStart(10)} ${"MEMS".padStart(4)} ${"PROJECT ID".padEnd(12)} ${"UPDATED".padEnd(16)} PATH`;
456
+ console.log(chalk.bold(header));
457
+ console.log(chalk.dim("-".repeat(header.length + 10)));
458
+ projects.forEach((p, i) => {
459
+ const idx = chalk.dim(String(i + 1).padStart(idxW));
460
+ const sizeStr = chalk.yellow(formatSize(p.size).padStart(10));
461
+ const memCount = chalk.green(String(p.memories).padStart(4));
462
+ const idStr = chalk.cyan(p.id.padEnd(12));
463
+ const dateStr = p.lastUpdated ? formatDate(p.lastUpdated).padEnd(16) : chalk.dim("n/a".padEnd(16));
464
+ const pathStr = p.sourceDir ? chalk.dim(p.sourceDir) : chalk.dim("(unknown)");
465
+ console.log(
466
+ `${idx} ${sizeStr} ${memCount} ${idStr} ${dateStr} ${pathStr}`
467
+ );
468
+ });
469
+ console.log();
470
+ console.log(
471
+ chalk.bold(
472
+ `Total: ${projects.length} project(s), ${formatSize(totalSize)}`
473
+ )
474
+ );
475
+ console.log();
476
+ }
477
+ async function memoryRmCommand(opts) {
478
+ const baseDir = getProjectsBaseDir();
479
+ if (!existsSync(baseDir)) {
480
+ log.info("No memory projects found. Nothing to remove.");
481
+ return;
482
+ }
483
+ if (opts.prune) {
484
+ const mcpDir = getLibPath("cf-memory");
485
+ ensureBuilt(mcpDir);
486
+ const dirs = readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name);
487
+ const orphaned = [];
488
+ for (const id of dirs) {
489
+ const projectDir = join(baseDir, id);
490
+ const info = getProjectInfo(projectDir, id, mcpDir);
491
+ if (info.sourceDir && !existsSync(info.sourceDir)) {
492
+ orphaned.push({
493
+ id,
494
+ reason: `source dir missing: ${info.sourceDir}`,
495
+ size: info.size
496
+ });
497
+ } else if (info.memories === 0) {
498
+ orphaned.push({
499
+ id,
500
+ reason: info.sourceDir ? `0 memories (${info.sourceDir})` : "unknown path, 0 memories",
501
+ size: info.size
502
+ });
503
+ }
504
+ }
505
+ if (orphaned.length === 0) {
506
+ log.success("No orphaned projects found.");
507
+ return;
508
+ }
509
+ const totalSize = orphaned.reduce((sum, o) => sum + o.size, 0);
510
+ log.warn(
511
+ `Found ${orphaned.length} orphaned project(s) (${formatSize(totalSize)}):`
512
+ );
513
+ console.log();
514
+ for (const o of orphaned) {
515
+ console.log(` ${chalk.cyan(o.id)} ${chalk.dim(o.reason)}`);
516
+ }
517
+ console.log();
518
+ const ok = await confirm({
519
+ message: `Delete ${orphaned.length} orphaned project(s)?`,
520
+ default: false
521
+ });
522
+ if (!ok) {
523
+ log.info("Cancelled.");
524
+ return;
525
+ }
526
+ for (const o of orphaned) {
527
+ rmSync(join(baseDir, o.id), { recursive: true, force: true });
528
+ }
529
+ log.success(
530
+ `Deleted ${orphaned.length} orphaned project(s) (${formatSize(totalSize)}).`
531
+ );
532
+ return;
533
+ }
534
+ if (opts.all) {
535
+ const dirs = readdirSync(baseDir, { withFileTypes: true }).filter(
536
+ (d) => d.isDirectory() && !d.name.startsWith(".")
537
+ );
538
+ if (dirs.length === 0) {
539
+ log.info("No memory projects found. Nothing to remove.");
540
+ return;
541
+ }
542
+ const totalSize = dirs.reduce(
543
+ (sum, d) => sum + dirSize(join(baseDir, d.name)),
544
+ 0
545
+ );
546
+ log.warn(
547
+ `This will delete ALL ${dirs.length} project database(s) (${formatSize(totalSize)}).`
548
+ );
549
+ log.warn("Markdown source files in docs/memory/ will NOT be affected.");
550
+ console.log();
551
+ const ok = await confirm({
552
+ message: "Are you sure you want to delete all memory databases?",
553
+ default: false
554
+ });
555
+ if (!ok) {
556
+ log.info("Cancelled.");
557
+ return;
558
+ }
559
+ for (const d of dirs) {
560
+ rmSync(join(baseDir, d.name), { recursive: true, force: true });
561
+ }
562
+ log.success(`Deleted ${dirs.length} project database(s).`);
563
+ return;
564
+ }
565
+ if (opts.projectId) {
566
+ const projectDir = join(baseDir, opts.projectId);
567
+ const resolved = resolve(projectDir);
568
+ if (!resolved.startsWith(resolve(baseDir) + sep)) {
569
+ log.error("Invalid project ID.");
570
+ process.exit(1);
571
+ }
572
+ if (!existsSync(projectDir)) {
573
+ log.error(`Project "${opts.projectId}" not found.`);
574
+ log.dim('Run "cf memory list --projects" to see available projects.');
575
+ process.exit(1);
576
+ }
577
+ const size = dirSize(projectDir);
578
+ log.warn(
579
+ `This will delete project "${opts.projectId}" (${formatSize(size)}).`
580
+ );
581
+ log.warn("Markdown source files in docs/memory/ will NOT be affected.");
582
+ console.log();
583
+ const ok = await confirm({
584
+ message: `Delete project ${opts.projectId}?`,
585
+ default: false
586
+ });
587
+ if (!ok) {
588
+ log.info("Cancelled.");
589
+ return;
590
+ }
591
+ rmSync(projectDir, { recursive: true, force: true });
592
+ log.success(`Deleted project "${opts.projectId}".`);
593
+ return;
594
+ }
595
+ log.error("Specify --project-id <id>, --all, or --prune.");
596
+ log.dim('Run "cf memory list --projects" to see available projects.');
597
+ process.exit(1);
598
+ }
599
+ async function memoryMcpCommand() {
600
+ const memoryDir = getMemoryDir();
601
+ const mcpDir = getLibPath("cf-memory");
602
+ ensureBuilt(mcpDir);
603
+ const serverPath = join(mcpDir, "dist", "index.js");
604
+ console.log(`Add this to your MCP client config:
605
+
606
+ --- Claude Desktop / Claude Chat (claude_desktop_config.json) ---
607
+
608
+ {
609
+ "mcpServers": {
610
+ "coding-friend-memory": {
611
+ "command": "node",
612
+ "args": ["${serverPath}", "${memoryDir}"]
613
+ }
614
+ }
615
+ }
616
+
617
+ --- Generic MCP client ---
618
+
619
+ Server command: node ${serverPath} ${memoryDir}
620
+ Transport: stdio
621
+
622
+ --- Available tools ---
623
+
624
+ memory_store Store a new memory
625
+ memory_search Search memories (keyword match)
626
+ memory_retrieve Get a specific memory by ID
627
+ memory_list List memories with filtering
628
+ memory_update Update existing memory
629
+ memory_delete Delete a memory
630
+
631
+ --- Resources ---
632
+
633
+ memory://index Browse all memories
634
+ memory://stats Storage statistics
635
+ `);
636
+ }
637
+ export {
638
+ memoryInitCommand,
639
+ memoryListCommand,
640
+ memoryMcpCommand,
641
+ memoryRebuildCommand,
642
+ memoryRmCommand,
643
+ memorySearchCommand,
644
+ memoryStartCommand,
645
+ memoryStatusCommand,
646
+ memoryStopCommand
647
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ensureShellCompletion
4
- } from "./chunk-KJUGTLPQ.js";
4
+ } from "./chunk-YO6JKGR3.js";
5
5
  import "./chunk-W5CD7WTX.js";
6
6
 
7
7
  // src/postinstall.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  loadConfig
3
- } from "./chunk-4DB4XTSL.js";
3
+ } from "./chunk-KTX4MGMR.js";
4
4
  import "./chunk-POC2WHU2.js";
5
5
  import {
6
6
  claudeSessionDir,
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-POC2WHU2.js";
11
11
  import {
12
12
  commandExists
13
- } from "./chunk-X5WEODUD.js";
13
+ } from "./chunk-CYQU33FY.js";
14
14
  import "./chunk-RWUTFVRB.js";
15
15
  import {
16
16
  log
@@ -5,14 +5,14 @@ import {
5
5
  import {
6
6
  hasShellCompletion,
7
7
  removeShellCompletion
8
- } from "./chunk-KJUGTLPQ.js";
8
+ } from "./chunk-YO6JKGR3.js";
9
9
  import {
10
10
  resolveScope
11
- } from "./chunk-D4EWPGBL.js";
11
+ } from "./chunk-C5LYVVEI.js";
12
12
  import {
13
13
  commandExists,
14
14
  run
15
- } from "./chunk-X5WEODUD.js";
15
+ } from "./chunk-CYQU33FY.js";
16
16
  import {
17
17
  claudeSettingsPath,
18
18
  devStatePath,