coding-friend-cli 1.33.1 → 1.34.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.
- package/dist/{chunk-LK6HFD35.js → chunk-AZ6AK5M4.js} +22 -10
- package/dist/chunk-CZDXMFEZ.js +118 -0
- package/dist/{chunk-OYPOADCX.js → chunk-DHH6SRXV.js} +0 -1
- package/dist/{chunk-G2P43ZK2.js → chunk-FAANBGCB.js} +78 -150
- package/dist/chunk-FI5HEQ43.js +85 -0
- package/dist/{chunk-UGTNIFBH.js → chunk-G4QGJEV7.js} +1 -1
- package/dist/{chunk-XOHLBLN2.js → chunk-LXZ47XCD.js} +1 -1
- package/dist/{chunk-NZKIYVIK.js → chunk-QCKIBJF6.js} +1 -1
- package/dist/{chunk-OV4AUDXL.js → config-44VQBNMS.js} +5 -78
- package/dist/{dev-S6VZUD33.js → dev-QXVERU42.js} +2 -2
- package/dist/{host-Y4N4PI4N.js → host-6IBKKCPA.js} +2 -2
- package/dist/index.js +26 -26
- package/dist/{init-HS4EXSXW.js → init-KY3ZPEWP.js} +7 -19
- package/dist/{install-FKL6WLIG.js → install-22BLHQ6D.js} +3 -3
- package/dist/{mcp-BD2K7OL6.js → mcp-OGZAZZRF.js} +70 -15
- package/dist/{memory-FZMACIGE.js → memory-JM45DEIX.js} +5 -4
- package/dist/{session-RHXTVNRU.js → session-RCEE6P35.js} +2 -2
- package/dist/{status-V2A7RSSM.js → status-NMSVOSC4.js} +84 -7
- package/dist/{statusline-MLLKBHHR.js → statusline-GKVAX7GR.js} +2 -2
- package/dist/{update-G5HZINBK.js → update-P5ZR2JVQ.js} +3 -3
- package/package.json +1 -1
- package/dist/config-2OJTBZU2.js +0 -22
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
warnStaleMcpJson
|
|
3
|
+
} from "./chunk-FI5HEQ43.js";
|
|
1
4
|
import {
|
|
2
5
|
editMemoryAutoCapture,
|
|
3
6
|
editMemoryAutoStart,
|
|
@@ -6,11 +9,11 @@ import {
|
|
|
6
9
|
getMemoryMcpStatus,
|
|
7
10
|
memoryConfigMenu,
|
|
8
11
|
writeMemoryMcpEntry
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-QCKIBJF6.js";
|
|
10
13
|
import {
|
|
11
14
|
loadConfig,
|
|
12
15
|
resolveMemoryDir
|
|
13
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-AZ6AK5M4.js";
|
|
14
17
|
import {
|
|
15
18
|
getLibPath
|
|
16
19
|
} from "./chunk-RZRT7NGT.js";
|
|
@@ -34,90 +37,17 @@ import {
|
|
|
34
37
|
} from "./chunk-5UVDWG5L.js";
|
|
35
38
|
|
|
36
39
|
// src/commands/memory.ts
|
|
37
|
-
import { existsSync
|
|
38
|
-
import { join
|
|
40
|
+
import { existsSync, readdirSync, statSync, rmSync } from "fs";
|
|
41
|
+
import { join, resolve, sep } from "path";
|
|
39
42
|
import { homedir } from "os";
|
|
40
43
|
import { confirm } from "@inquirer/prompts";
|
|
41
|
-
|
|
42
|
-
// src/lib/mcp-state.ts
|
|
43
|
-
import { existsSync } from "fs";
|
|
44
|
-
import { join } from "path";
|
|
45
44
|
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(
|
|
52
|
-
".mcp.json exists but could not be parsed \u2014 check for syntax errors."
|
|
53
|
-
);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const state = detectMemoryMcpState(mcpJson, existsSync);
|
|
57
|
-
if (state.kind === "stale" || state.kind === "legacy-valid") {
|
|
58
|
-
if (memoryDir) {
|
|
59
|
-
writeMemoryMcpEntry(memoryDir);
|
|
60
|
-
if (state.kind === "stale") {
|
|
61
|
-
log.success(
|
|
62
|
-
"Auto-updated stale .mcp.json to version-stable npx format."
|
|
63
|
-
);
|
|
64
|
-
} else {
|
|
65
|
-
log.success("Updated .mcp.json to version-stable npx format.");
|
|
66
|
-
}
|
|
67
|
-
console.log();
|
|
68
|
-
} else if (state.kind === "stale") {
|
|
69
|
-
console.log(
|
|
70
|
-
chalk.yellow(`\u26A0 Stale MCP config detected in .mcp.json`)
|
|
71
|
-
);
|
|
72
|
-
console.log(chalk.dim(` Path no longer exists: ${state.path}`));
|
|
73
|
-
console.log(
|
|
74
|
-
chalk.dim(` Run "cf memory mcp" to update to the new format.`)
|
|
75
|
-
);
|
|
76
|
-
console.log();
|
|
77
|
-
} else {
|
|
78
|
-
console.log(
|
|
79
|
-
chalk.cyan(
|
|
80
|
-
`\u2139 .mcp.json uses an absolute path for coding-friend-memory.`
|
|
81
|
-
)
|
|
82
|
-
);
|
|
83
|
-
console.log(
|
|
84
|
-
chalk.dim(
|
|
85
|
-
` Consider running "cf memory mcp" to switch to the version-stable format.`
|
|
86
|
-
)
|
|
87
|
-
);
|
|
88
|
-
console.log();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
function detectMemoryMcpState(mcpJson, pathExists) {
|
|
93
|
-
if (!mcpJson) return { kind: "none" };
|
|
94
|
-
const servers = mcpJson.mcpServers;
|
|
95
|
-
if (!servers) return { kind: "none" };
|
|
96
|
-
const entry = servers["coding-friend-memory"];
|
|
97
|
-
if (!entry) return { kind: "none" };
|
|
98
|
-
const command = entry.command;
|
|
99
|
-
if (!command) return { kind: "none" };
|
|
100
|
-
if (command === "npx") return { kind: "npx" };
|
|
101
|
-
if (command === "node") {
|
|
102
|
-
const args = entry.args;
|
|
103
|
-
const serverPath = args?.[0];
|
|
104
|
-
if (!serverPath) return { kind: "none" };
|
|
105
|
-
if (!pathExists(serverPath)) {
|
|
106
|
-
return { kind: "stale", path: serverPath };
|
|
107
|
-
}
|
|
108
|
-
return { kind: "legacy-valid", path: serverPath };
|
|
109
|
-
}
|
|
110
|
-
return { kind: "none" };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// src/commands/memory.ts
|
|
114
|
-
import chalk2 from "chalk";
|
|
115
45
|
function countMdFiles(dir) {
|
|
116
|
-
if (!
|
|
46
|
+
if (!existsSync(dir)) return 0;
|
|
117
47
|
let count = 0;
|
|
118
48
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
119
49
|
if (entry.isDirectory()) {
|
|
120
|
-
count += countMdFiles(
|
|
50
|
+
count += countMdFiles(join(dir, entry.name));
|
|
121
51
|
} else if (entry.name.endsWith(".md") && entry.name !== "README.md") {
|
|
122
52
|
count++;
|
|
123
53
|
}
|
|
@@ -137,7 +67,7 @@ function truncateError(text) {
|
|
|
137
67
|
return [...head, ` ... (${skipped} lines omitted) ...`, ...tail].join("\n");
|
|
138
68
|
}
|
|
139
69
|
function ensureMemoryBuilt(mcpDir) {
|
|
140
|
-
if (!
|
|
70
|
+
if (!existsSync(join(mcpDir, "node_modules"))) {
|
|
141
71
|
log.step("Installing memory server dependencies (one-time setup)...");
|
|
142
72
|
const result = runWithStderr("npm", ["install"], { cwd: mcpDir });
|
|
143
73
|
if (result.exitCode !== 0) {
|
|
@@ -147,7 +77,7 @@ function ensureMemoryBuilt(mcpDir) {
|
|
|
147
77
|
}
|
|
148
78
|
log.success("Done.");
|
|
149
79
|
}
|
|
150
|
-
if (!
|
|
80
|
+
if (!existsSync(join(mcpDir, "dist"))) {
|
|
151
81
|
log.step("Building memory server...");
|
|
152
82
|
const result = runWithStderr("npm", ["run", "build"], { cwd: mcpDir });
|
|
153
83
|
if (result.exitCode !== 0) {
|
|
@@ -159,7 +89,7 @@ function ensureMemoryBuilt(mcpDir) {
|
|
|
159
89
|
}
|
|
160
90
|
}
|
|
161
91
|
function printMemoryMcpConfig(memoryDir) {
|
|
162
|
-
console.log(
|
|
92
|
+
console.log(chalk.dim("Add this to your MCP client config:"));
|
|
163
93
|
console.log();
|
|
164
94
|
console.log(`{
|
|
165
95
|
"mcpServers": {
|
|
@@ -194,43 +124,43 @@ async function memoryStatusCommand() {
|
|
|
194
124
|
const docCount = countMdFiles(memoryDir);
|
|
195
125
|
const mcpDir = getLibPath("cf-memory");
|
|
196
126
|
ensureMemoryBuilt(mcpDir);
|
|
197
|
-
const { isDaemonRunning, getDaemonInfo } = await import(
|
|
198
|
-
const { areSqliteDepsAvailable } = await import(
|
|
127
|
+
const { isDaemonRunning, getDaemonInfo } = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
128
|
+
const { areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
|
|
199
129
|
const sqliteAvailable = areSqliteDepsAvailable();
|
|
200
130
|
const running = await isDaemonRunning();
|
|
201
131
|
const daemonInfo = getDaemonInfo();
|
|
202
132
|
let tierLabel;
|
|
203
133
|
if (sqliteAvailable) {
|
|
204
|
-
tierLabel =
|
|
134
|
+
tierLabel = chalk.cyan("Tier 1 (SQLite + Hybrid)");
|
|
205
135
|
} else if (running) {
|
|
206
|
-
tierLabel =
|
|
136
|
+
tierLabel = chalk.cyan("Tier 2 (MiniSearch + Daemon)");
|
|
207
137
|
} else {
|
|
208
|
-
tierLabel =
|
|
138
|
+
tierLabel = chalk.cyan("Tier 3 (Markdown)");
|
|
209
139
|
}
|
|
210
140
|
printBanner("\u{1F9E0} Coding Friend Memory");
|
|
211
141
|
console.log();
|
|
212
142
|
log.info(`Tier: ${tierLabel}`);
|
|
213
|
-
log.info(`Memory dir: ${
|
|
214
|
-
log.info(`Memories in this dir: ${
|
|
143
|
+
log.info(`Memory dir: ${chalk.cyan(memoryDir)}`);
|
|
144
|
+
log.info(`Memories in this dir: ${chalk.green(String(docCount))}`);
|
|
215
145
|
if (running && daemonInfo) {
|
|
216
146
|
const uptime = (Date.now() - daemonInfo.startedAt) / 1e3;
|
|
217
147
|
log.info(
|
|
218
|
-
`Daemon: ${
|
|
148
|
+
`Daemon: ${chalk.green("running")} (PID ${daemonInfo.pid}, uptime ${formatUptime(uptime)}) ${chalk.dim('Turn it off by "cf memory stop-daemon"')}`
|
|
219
149
|
);
|
|
220
150
|
} else if (sqliteAvailable) {
|
|
221
151
|
log.info(
|
|
222
|
-
`Daemon: ${
|
|
152
|
+
`Daemon: ${chalk.dim("stopped")} ${chalk.dim("(not needed \u2014 Tier 1 uses SQLite directly)")}`
|
|
223
153
|
);
|
|
224
154
|
} else {
|
|
225
155
|
log.info(
|
|
226
|
-
`Daemon: ${
|
|
156
|
+
`Daemon: ${chalk.dim("stopped")} ${chalk.dim('(run "cf memory start-daemon" for Tier 2 search)')}`
|
|
227
157
|
);
|
|
228
158
|
}
|
|
229
159
|
if (sqliteAvailable) {
|
|
230
|
-
log.info(`SQLite deps: ${
|
|
160
|
+
log.info(`SQLite deps: ${chalk.green("installed")}`);
|
|
231
161
|
} else {
|
|
232
162
|
log.info(
|
|
233
|
-
`SQLite deps: ${
|
|
163
|
+
`SQLite deps: ${chalk.dim("not installed")} (run "cf memory init" to enable Tier 1)`
|
|
234
164
|
);
|
|
235
165
|
}
|
|
236
166
|
const config = loadConfig();
|
|
@@ -238,33 +168,33 @@ async function memoryStatusCommand() {
|
|
|
238
168
|
if (embeddingConfig?.provider || embeddingConfig?.model) {
|
|
239
169
|
const provider = embeddingConfig.provider ?? "transformers";
|
|
240
170
|
const model = embeddingConfig.model ?? (provider === "ollama" ? "all-minilm:l6-v2" : "Xenova/all-MiniLM-L6-v2");
|
|
241
|
-
log.info(`Embedding: ${
|
|
171
|
+
log.info(`Embedding: ${chalk.cyan(model)} ${chalk.dim(`(${provider})`)}`);
|
|
242
172
|
}
|
|
243
173
|
const mcpStatus = getMemoryMcpStatus();
|
|
244
174
|
if (mcpStatus.configured && mcpStatus.scope === "local") {
|
|
245
175
|
log.info(
|
|
246
|
-
`MCP: ${
|
|
176
|
+
`MCP: ${chalk.green("configured")} ${chalk.dim("(local .mcp.json)")}`
|
|
247
177
|
);
|
|
248
178
|
} else if (mcpStatus.configured && mcpStatus.scope === "global") {
|
|
249
179
|
log.info(
|
|
250
|
-
`MCP: ${
|
|
180
|
+
`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")}`
|
|
251
181
|
);
|
|
252
182
|
} else {
|
|
253
183
|
log.info(
|
|
254
|
-
`MCP: ${
|
|
184
|
+
`MCP: ${chalk.dim("not configured in this project")} ${chalk.dim('(run "cf memory init" or add manually via "cf memory mcp")')}`
|
|
255
185
|
);
|
|
256
186
|
}
|
|
257
187
|
const autoCapture = config.memory?.autoCapture ?? false;
|
|
258
188
|
log.info(
|
|
259
|
-
`Auto-capture: ${autoCapture ?
|
|
189
|
+
`Auto-capture: ${autoCapture ? chalk.green("on") : chalk.dim("off")}`
|
|
260
190
|
);
|
|
261
|
-
if (
|
|
191
|
+
if (existsSync(memoryDir)) {
|
|
262
192
|
const categories = readdirSync(memoryDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
|
|
263
|
-
const catCount = countMdFiles(
|
|
193
|
+
const catCount = countMdFiles(join(memoryDir, d.name));
|
|
264
194
|
return `${d.name} (${catCount})`;
|
|
265
195
|
}).filter((s) => !s.endsWith("(0)"));
|
|
266
196
|
if (categories.length > 0) {
|
|
267
|
-
log.info(`Categories: ${
|
|
197
|
+
log.info(`Categories: ${chalk.dim(categories.join(", "))}`);
|
|
268
198
|
}
|
|
269
199
|
}
|
|
270
200
|
console.log();
|
|
@@ -273,7 +203,7 @@ async function memoryStatusCommand() {
|
|
|
273
203
|
}
|
|
274
204
|
async function memorySearchCommand(query) {
|
|
275
205
|
const memoryDir = getMemoryDir();
|
|
276
|
-
if (!
|
|
206
|
+
if (!existsSync(memoryDir)) {
|
|
277
207
|
log.error(`Memory dir not found: ${memoryDir}`);
|
|
278
208
|
log.dim("Run `cf init` to create project folders.");
|
|
279
209
|
process.exit(1);
|
|
@@ -285,7 +215,7 @@ async function memorySearchCommand(query) {
|
|
|
285
215
|
[
|
|
286
216
|
"-e",
|
|
287
217
|
`
|
|
288
|
-
import { MarkdownBackend } from ${JSON.stringify(
|
|
218
|
+
import { MarkdownBackend } from ${JSON.stringify(join(mcpDir, "dist/backends/markdown.js"))};
|
|
289
219
|
const backend = new MarkdownBackend(${JSON.stringify(memoryDir)});
|
|
290
220
|
const results = await backend.search({ query: process.env.CF_SEARCH_QUERY, limit: 10 });
|
|
291
221
|
for (const r of results) {
|
|
@@ -308,7 +238,7 @@ async function memoryListCommand(opts) {
|
|
|
308
238
|
return memoryListProjectsCommand();
|
|
309
239
|
}
|
|
310
240
|
const memoryDir = getMemoryDir();
|
|
311
|
-
if (!
|
|
241
|
+
if (!existsSync(memoryDir)) {
|
|
312
242
|
log.info(`No memory directory found at: ${memoryDir}`);
|
|
313
243
|
log.dim(
|
|
314
244
|
"This folder has no memories yet. Use --projects to list all project databases."
|
|
@@ -325,7 +255,7 @@ async function memoryListCommand(opts) {
|
|
|
325
255
|
[
|
|
326
256
|
"-e",
|
|
327
257
|
`
|
|
328
|
-
import { MarkdownBackend } from ${JSON.stringify(
|
|
258
|
+
import { MarkdownBackend } from ${JSON.stringify(join(mcpDir, "dist/backends/markdown.js"))};
|
|
329
259
|
const backend = new MarkdownBackend(${JSON.stringify(memoryDir)});
|
|
330
260
|
const metas = await backend.list({});
|
|
331
261
|
if (metas.length === 0) { console.log("No memories found."); process.exit(0); }
|
|
@@ -361,7 +291,7 @@ async function memoryStartDaemonCommand() {
|
|
|
361
291
|
const memoryDir = getMemoryDir();
|
|
362
292
|
const mcpDir = getLibPath("cf-memory");
|
|
363
293
|
ensureMemoryBuilt(mcpDir);
|
|
364
|
-
const { isDaemonRunning, getDaemonInfo, spawnDaemon } = await import(
|
|
294
|
+
const { isDaemonRunning, getDaemonInfo, spawnDaemon } = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
365
295
|
if (await isDaemonRunning()) {
|
|
366
296
|
const info = getDaemonInfo();
|
|
367
297
|
log.info(`Daemon already running (PID ${info?.pid})`);
|
|
@@ -373,7 +303,7 @@ async function memoryStartDaemonCommand() {
|
|
|
373
303
|
const result = await spawnDaemon(memoryDir, embedding, {});
|
|
374
304
|
if (result) {
|
|
375
305
|
log.success(`Daemon started (PID ${result.pid})`);
|
|
376
|
-
log.info(`Watching ${
|
|
306
|
+
log.info(`Watching ${chalk.cyan(memoryDir)} for changes`);
|
|
377
307
|
} else {
|
|
378
308
|
log.error("Daemon did not start within 3 seconds");
|
|
379
309
|
process.exit(1);
|
|
@@ -382,7 +312,7 @@ async function memoryStartDaemonCommand() {
|
|
|
382
312
|
async function memoryStopDaemonCommand() {
|
|
383
313
|
const mcpDir = getLibPath("cf-memory");
|
|
384
314
|
ensureMemoryBuilt(mcpDir);
|
|
385
|
-
const { stopDaemon, isDaemonRunning } = await import(
|
|
315
|
+
const { stopDaemon, isDaemonRunning } = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
386
316
|
if (!await isDaemonRunning()) {
|
|
387
317
|
log.info("Daemon is not running.");
|
|
388
318
|
return;
|
|
@@ -399,10 +329,10 @@ async function memoryRebuildCommand() {
|
|
|
399
329
|
const memoryDir = getMemoryDir();
|
|
400
330
|
const mcpDir = getLibPath("cf-memory");
|
|
401
331
|
ensureMemoryBuilt(mcpDir);
|
|
402
|
-
const { areSqliteDepsAvailable } = await import(
|
|
332
|
+
const { areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
|
|
403
333
|
if (areSqliteDepsAvailable()) {
|
|
404
334
|
log.step("Rebuilding SQLite index + embeddings...");
|
|
405
|
-
const { SqliteBackend } = await import(
|
|
335
|
+
const { SqliteBackend } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
|
|
406
336
|
const config = loadConfig();
|
|
407
337
|
const embedding = config.memory?.embedding;
|
|
408
338
|
const opts = embedding ? { embedding, skipVec: false } : { skipVec: false };
|
|
@@ -412,7 +342,7 @@ async function memoryRebuildCommand() {
|
|
|
412
342
|
const stats = await backend.stats();
|
|
413
343
|
log.success(`Rebuilt: ${stats.total} memories indexed.`);
|
|
414
344
|
if (backend.isVecEnabled()) {
|
|
415
|
-
log.info(`Vector search: ${
|
|
345
|
+
log.info(`Vector search: ${chalk.green("enabled")}`);
|
|
416
346
|
}
|
|
417
347
|
if (!backend.isRebuildNeeded()) {
|
|
418
348
|
log.info("Embedding dimensions: up to date");
|
|
@@ -422,14 +352,14 @@ async function memoryRebuildCommand() {
|
|
|
422
352
|
}
|
|
423
353
|
return;
|
|
424
354
|
}
|
|
425
|
-
const { isDaemonRunning, getDaemonPaths } = await import(
|
|
355
|
+
const { isDaemonRunning, getDaemonPaths } = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
426
356
|
if (!await isDaemonRunning()) {
|
|
427
357
|
log.info("No SQLite deps and daemon not running. Nothing to rebuild.");
|
|
428
358
|
log.dim("Install Tier 1 deps: cf memory init");
|
|
429
359
|
log.dim("Or start the daemon: cf memory start-daemon");
|
|
430
360
|
return;
|
|
431
361
|
}
|
|
432
|
-
const { DaemonClient } = await import(
|
|
362
|
+
const { DaemonClient } = await import(join(mcpDir, "dist/lib/daemon-client.js"));
|
|
433
363
|
const paths = getDaemonPaths();
|
|
434
364
|
const client = new DaemonClient(paths.socketPath);
|
|
435
365
|
log.step("Rebuilding search index via daemon...");
|
|
@@ -446,7 +376,7 @@ function getDbPath(memoryDir) {
|
|
|
446
376
|
const stripped = resolved.replace(/\/docs\/memory$/, "").replace(/\/memory$/, "");
|
|
447
377
|
const id = stripped.replace(/\//g, "-");
|
|
448
378
|
const home = homedir();
|
|
449
|
-
const dbPath =
|
|
379
|
+
const dbPath = join(
|
|
450
380
|
home,
|
|
451
381
|
".coding-friend",
|
|
452
382
|
"memory",
|
|
@@ -454,7 +384,7 @@ function getDbPath(memoryDir) {
|
|
|
454
384
|
id,
|
|
455
385
|
"db.sqlite"
|
|
456
386
|
);
|
|
457
|
-
return
|
|
387
|
+
return existsSync(dbPath) ? dbPath : null;
|
|
458
388
|
} catch {
|
|
459
389
|
return null;
|
|
460
390
|
}
|
|
@@ -463,7 +393,7 @@ async function ensureSqliteDepsIfNeeded(mcpDir) {
|
|
|
463
393
|
const config = loadConfig();
|
|
464
394
|
const tier = config.memory?.tier ?? "auto";
|
|
465
395
|
if (tier === "markdown" || tier === "lite") return true;
|
|
466
|
-
const { ensureDeps, areSqliteDepsAvailable } = await import(
|
|
396
|
+
const { ensureDeps, areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
|
|
467
397
|
if (areSqliteDepsAvailable()) return true;
|
|
468
398
|
log.step("Installing SQLite dependencies...");
|
|
469
399
|
const installed = await ensureDeps({
|
|
@@ -534,7 +464,7 @@ async function memoryInitWizard(memoryDir, mcpDir) {
|
|
|
534
464
|
}
|
|
535
465
|
const depsOk = await ensureSqliteDepsIfNeeded(mcpDir);
|
|
536
466
|
if (!depsOk) return;
|
|
537
|
-
if (!
|
|
467
|
+
if (!existsSync(memoryDir)) {
|
|
538
468
|
log.info(
|
|
539
469
|
"No memory directory found. Memories will be indexed as they're created."
|
|
540
470
|
);
|
|
@@ -550,7 +480,7 @@ async function memoryInitWizard(memoryDir, mcpDir) {
|
|
|
550
480
|
return;
|
|
551
481
|
}
|
|
552
482
|
log.step(`Importing ${docCount} existing memories into SQLite...`);
|
|
553
|
-
const { SqliteBackend } = await import(
|
|
483
|
+
const { SqliteBackend } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
|
|
554
484
|
const embedding = config.memory?.embedding;
|
|
555
485
|
const backend = new SqliteBackend(memoryDir, {
|
|
556
486
|
skipVec: false,
|
|
@@ -561,7 +491,7 @@ async function memoryInitWizard(memoryDir, mcpDir) {
|
|
|
561
491
|
const stats = await backend.stats();
|
|
562
492
|
log.success(`Imported ${stats.total} memories. DB: ${backend.getDbPath()}`);
|
|
563
493
|
log.info(
|
|
564
|
-
`Vector search: ${backend.isVecEnabled() ?
|
|
494
|
+
`Vector search: ${backend.isVecEnabled() ? chalk.green("enabled") : chalk.dim("disabled (sqlite-vec not available)")}`
|
|
565
495
|
);
|
|
566
496
|
} finally {
|
|
567
497
|
await backend.close();
|
|
@@ -570,13 +500,13 @@ async function memoryInitWizard(memoryDir, mcpDir) {
|
|
|
570
500
|
console.log();
|
|
571
501
|
log.success('Memory initialized! Run "cf memory status" to verify.');
|
|
572
502
|
log.info(
|
|
573
|
-
`Tip: Run ${
|
|
503
|
+
`Tip: Run ${chalk.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
|
|
574
504
|
);
|
|
575
505
|
}
|
|
576
506
|
async function setupMemoryMcp(memoryDir, _mcpDir) {
|
|
577
507
|
const mcpStatus = getMemoryMcpStatus();
|
|
578
508
|
if (mcpStatus.configured && mcpStatus.scope === "local") {
|
|
579
|
-
log.info(`MCP: ${
|
|
509
|
+
log.info(`MCP: ${chalk.green("already configured")} in .mcp.json`);
|
|
580
510
|
return;
|
|
581
511
|
}
|
|
582
512
|
console.log();
|
|
@@ -625,7 +555,7 @@ async function memoryConfigCommand() {
|
|
|
625
555
|
}
|
|
626
556
|
function getProjectsBaseDir() {
|
|
627
557
|
const home = homedir();
|
|
628
|
-
return
|
|
558
|
+
return join(home, ".coding-friend", "memory", "projects");
|
|
629
559
|
}
|
|
630
560
|
function formatSize(bytes) {
|
|
631
561
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -643,9 +573,9 @@ function formatDate(raw) {
|
|
|
643
573
|
}
|
|
644
574
|
function dirSize(dir) {
|
|
645
575
|
let total = 0;
|
|
646
|
-
if (!
|
|
576
|
+
if (!existsSync(dir)) return 0;
|
|
647
577
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
648
|
-
const p =
|
|
578
|
+
const p = join(dir, entry.name);
|
|
649
579
|
if (entry.isFile()) {
|
|
650
580
|
total += statSync(p).size;
|
|
651
581
|
} else if (entry.isDirectory()) {
|
|
@@ -655,7 +585,7 @@ function dirSize(dir) {
|
|
|
655
585
|
return total;
|
|
656
586
|
}
|
|
657
587
|
function getProjectInfo(projectDir, projectId, mcpDir, knownSourceDir) {
|
|
658
|
-
const dbPath =
|
|
588
|
+
const dbPath = join(projectDir, "db.sqlite");
|
|
659
589
|
const size = dirSize(projectDir);
|
|
660
590
|
const info = {
|
|
661
591
|
id: projectId,
|
|
@@ -664,7 +594,7 @@ function getProjectInfo(projectDir, projectId, mcpDir, knownSourceDir) {
|
|
|
664
594
|
size,
|
|
665
595
|
lastUpdated: null
|
|
666
596
|
};
|
|
667
|
-
if (!
|
|
597
|
+
if (!existsSync(dbPath)) return info;
|
|
668
598
|
try {
|
|
669
599
|
const backfillDir = knownSourceDir ? JSON.stringify(knownSourceDir) : "null";
|
|
670
600
|
const result = run(
|
|
@@ -705,7 +635,7 @@ async function memoryListProjectsCommand() {
|
|
|
705
635
|
const baseDir = getProjectsBaseDir();
|
|
706
636
|
const mcpDir = getLibPath("cf-memory");
|
|
707
637
|
ensureMemoryBuilt(mcpDir);
|
|
708
|
-
if (!
|
|
638
|
+
if (!existsSync(baseDir)) {
|
|
709
639
|
log.info("No memory projects found.");
|
|
710
640
|
log.dim('Run "cf memory init" in a project to create one.');
|
|
711
641
|
return;
|
|
@@ -716,35 +646,35 @@ async function memoryListProjectsCommand() {
|
|
|
716
646
|
return;
|
|
717
647
|
}
|
|
718
648
|
const currentMemoryDir = resolve(getMemoryDir());
|
|
719
|
-
const { projectId } = await import(
|
|
649
|
+
const { projectId } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
|
|
720
650
|
const currentProjectId = projectId(currentMemoryDir);
|
|
721
651
|
log.step(`Scanning ${dirs.length} project(s)...
|
|
722
652
|
`);
|
|
723
653
|
const projects = [];
|
|
724
654
|
for (const id of dirs) {
|
|
725
655
|
const knownDir = id === currentProjectId ? currentMemoryDir : void 0;
|
|
726
|
-
projects.push(getProjectInfo(
|
|
656
|
+
projects.push(getProjectInfo(join(baseDir, id), id, mcpDir, knownDir));
|
|
727
657
|
}
|
|
728
658
|
projects.sort((a, b) => b.size - a.size);
|
|
729
659
|
const totalSize = projects.reduce((sum, p) => sum + p.size, 0);
|
|
730
660
|
const idxW = String(projects.length).length;
|
|
731
661
|
const header = `${"#".padStart(idxW)} ${"SIZE".padStart(10)} ${"MEMS".padStart(4)} ${"PROJECT ID".padEnd(12)} ${"UPDATED".padEnd(16)} PATH`;
|
|
732
|
-
console.log(
|
|
733
|
-
console.log(
|
|
662
|
+
console.log(chalk.bold(header));
|
|
663
|
+
console.log(chalk.dim("-".repeat(header.length + 10)));
|
|
734
664
|
projects.forEach((p, i) => {
|
|
735
|
-
const idx =
|
|
736
|
-
const sizeStr =
|
|
737
|
-
const memCount =
|
|
738
|
-
const idStr =
|
|
739
|
-
const dateStr = p.lastUpdated ? formatDate(p.lastUpdated).padEnd(16) :
|
|
740
|
-
const pathStr = p.sourceDir ?
|
|
665
|
+
const idx = chalk.dim(String(i + 1).padStart(idxW));
|
|
666
|
+
const sizeStr = chalk.yellow(formatSize(p.size).padStart(10));
|
|
667
|
+
const memCount = chalk.green(String(p.memories).padStart(4));
|
|
668
|
+
const idStr = chalk.cyan(p.id.padEnd(12));
|
|
669
|
+
const dateStr = p.lastUpdated ? formatDate(p.lastUpdated).padEnd(16) : chalk.dim("n/a".padEnd(16));
|
|
670
|
+
const pathStr = p.sourceDir ? chalk.dim(p.sourceDir) : chalk.dim("(unknown)");
|
|
741
671
|
console.log(
|
|
742
672
|
`${idx} ${sizeStr} ${memCount} ${idStr} ${dateStr} ${pathStr}`
|
|
743
673
|
);
|
|
744
674
|
});
|
|
745
675
|
console.log();
|
|
746
676
|
console.log(
|
|
747
|
-
|
|
677
|
+
chalk.bold(
|
|
748
678
|
`Total: ${projects.length} project(s), ${formatSize(totalSize)}`
|
|
749
679
|
)
|
|
750
680
|
);
|
|
@@ -752,7 +682,7 @@ async function memoryListProjectsCommand() {
|
|
|
752
682
|
}
|
|
753
683
|
async function memoryRmCommand(opts) {
|
|
754
684
|
const baseDir = getProjectsBaseDir();
|
|
755
|
-
if (!
|
|
685
|
+
if (!existsSync(baseDir)) {
|
|
756
686
|
log.info("No memory projects found. Nothing to remove.");
|
|
757
687
|
return;
|
|
758
688
|
}
|
|
@@ -762,9 +692,9 @@ async function memoryRmCommand(opts) {
|
|
|
762
692
|
const dirs = readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name);
|
|
763
693
|
const orphaned = [];
|
|
764
694
|
for (const id of dirs) {
|
|
765
|
-
const projectDir =
|
|
695
|
+
const projectDir = join(baseDir, id);
|
|
766
696
|
const info = getProjectInfo(projectDir, id, mcpDir);
|
|
767
|
-
if (info.sourceDir && !
|
|
697
|
+
if (info.sourceDir && !existsSync(info.sourceDir)) {
|
|
768
698
|
orphaned.push({
|
|
769
699
|
id,
|
|
770
700
|
reason: `source dir missing: ${info.sourceDir}`,
|
|
@@ -788,7 +718,7 @@ async function memoryRmCommand(opts) {
|
|
|
788
718
|
);
|
|
789
719
|
console.log();
|
|
790
720
|
for (const o of orphaned) {
|
|
791
|
-
console.log(` ${
|
|
721
|
+
console.log(` ${chalk.cyan(o.id)} ${chalk.dim(o.reason)}`);
|
|
792
722
|
}
|
|
793
723
|
console.log();
|
|
794
724
|
const ok = await confirm({
|
|
@@ -800,7 +730,7 @@ async function memoryRmCommand(opts) {
|
|
|
800
730
|
return;
|
|
801
731
|
}
|
|
802
732
|
for (const o of orphaned) {
|
|
803
|
-
rmSync(
|
|
733
|
+
rmSync(join(baseDir, o.id), { recursive: true, force: true });
|
|
804
734
|
}
|
|
805
735
|
log.success(
|
|
806
736
|
`Deleted ${orphaned.length} orphaned project(s) (${formatSize(totalSize)}).`
|
|
@@ -816,7 +746,7 @@ async function memoryRmCommand(opts) {
|
|
|
816
746
|
return;
|
|
817
747
|
}
|
|
818
748
|
const totalSize = dirs.reduce(
|
|
819
|
-
(sum, d) => sum + dirSize(
|
|
749
|
+
(sum, d) => sum + dirSize(join(baseDir, d.name)),
|
|
820
750
|
0
|
|
821
751
|
);
|
|
822
752
|
log.warn(
|
|
@@ -833,19 +763,19 @@ async function memoryRmCommand(opts) {
|
|
|
833
763
|
return;
|
|
834
764
|
}
|
|
835
765
|
for (const d of dirs) {
|
|
836
|
-
rmSync(
|
|
766
|
+
rmSync(join(baseDir, d.name), { recursive: true, force: true });
|
|
837
767
|
}
|
|
838
768
|
log.success(`Deleted ${dirs.length} project database(s).`);
|
|
839
769
|
return;
|
|
840
770
|
}
|
|
841
771
|
if (opts.projectId) {
|
|
842
|
-
const projectDir =
|
|
772
|
+
const projectDir = join(baseDir, opts.projectId);
|
|
843
773
|
const resolved = resolve(projectDir);
|
|
844
774
|
if (!resolved.startsWith(resolve(baseDir) + sep)) {
|
|
845
775
|
log.error("Invalid project ID.");
|
|
846
776
|
process.exit(1);
|
|
847
777
|
}
|
|
848
|
-
if (!
|
|
778
|
+
if (!existsSync(projectDir)) {
|
|
849
779
|
log.error(`Project "${opts.projectId}" not found.`);
|
|
850
780
|
log.dim('Run "cf memory list --projects" to see available projects.');
|
|
851
781
|
process.exit(1);
|
|
@@ -881,8 +811,6 @@ async function memoryMcpCommand() {
|
|
|
881
811
|
}
|
|
882
812
|
|
|
883
813
|
export {
|
|
884
|
-
warnStaleMcpJson,
|
|
885
|
-
detectMemoryMcpState,
|
|
886
814
|
ensureMemoryBuilt,
|
|
887
815
|
printMemoryMcpConfig,
|
|
888
816
|
memoryStatusCommand,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
writeMemoryMcpEntry
|
|
3
|
+
} from "./chunk-QCKIBJF6.js";
|
|
4
|
+
import {
|
|
5
|
+
log
|
|
6
|
+
} from "./chunk-NREZK463.js";
|
|
7
|
+
import {
|
|
8
|
+
readJson
|
|
9
|
+
} from "./chunk-5UVDWG5L.js";
|
|
10
|
+
|
|
11
|
+
// src/lib/mcp-state.ts
|
|
12
|
+
import { existsSync } from "fs";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
function warnStaleMcpJson(memoryDir) {
|
|
16
|
+
const localMcpPath = join(process.cwd(), ".mcp.json");
|
|
17
|
+
if (!existsSync(localMcpPath)) return;
|
|
18
|
+
const mcpJson = readJson(localMcpPath);
|
|
19
|
+
if (mcpJson === null) {
|
|
20
|
+
log.warn(
|
|
21
|
+
".mcp.json exists but could not be parsed \u2014 check for syntax errors."
|
|
22
|
+
);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const state = detectMemoryMcpState(mcpJson, existsSync);
|
|
26
|
+
if (state.kind === "stale" || state.kind === "legacy-valid") {
|
|
27
|
+
if (memoryDir) {
|
|
28
|
+
writeMemoryMcpEntry(memoryDir);
|
|
29
|
+
if (state.kind === "stale") {
|
|
30
|
+
log.success(
|
|
31
|
+
"Auto-updated stale .mcp.json to version-stable npx format."
|
|
32
|
+
);
|
|
33
|
+
} else {
|
|
34
|
+
log.success("Updated .mcp.json to version-stable npx format.");
|
|
35
|
+
}
|
|
36
|
+
console.log();
|
|
37
|
+
} else if (state.kind === "stale") {
|
|
38
|
+
console.log(
|
|
39
|
+
chalk.yellow(`\u26A0 Stale MCP config detected in .mcp.json`)
|
|
40
|
+
);
|
|
41
|
+
console.log(chalk.dim(` Path no longer exists: ${state.path}`));
|
|
42
|
+
console.log(
|
|
43
|
+
chalk.dim(` Run "cf memory mcp" to update to the new format.`)
|
|
44
|
+
);
|
|
45
|
+
console.log();
|
|
46
|
+
} else {
|
|
47
|
+
console.log(
|
|
48
|
+
chalk.cyan(
|
|
49
|
+
`\u2139 .mcp.json uses an absolute path for coding-friend-memory.`
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
console.log(
|
|
53
|
+
chalk.dim(
|
|
54
|
+
` Consider running "cf memory mcp" to switch to the version-stable format.`
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
console.log();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function detectMemoryMcpState(mcpJson, pathExists) {
|
|
62
|
+
if (!mcpJson) return { kind: "none" };
|
|
63
|
+
const servers = mcpJson.mcpServers;
|
|
64
|
+
if (!servers) return { kind: "none" };
|
|
65
|
+
const entry = servers["coding-friend-memory"];
|
|
66
|
+
if (!entry) return { kind: "none" };
|
|
67
|
+
const command = entry.command;
|
|
68
|
+
if (!command) return { kind: "none" };
|
|
69
|
+
if (command === "npx") return { kind: "npx" };
|
|
70
|
+
if (command === "node") {
|
|
71
|
+
const args = entry.args;
|
|
72
|
+
const serverPath = args?.[0];
|
|
73
|
+
if (!serverPath) return { kind: "none" };
|
|
74
|
+
if (!pathExists(serverPath)) {
|
|
75
|
+
return { kind: "stale", path: serverPath };
|
|
76
|
+
}
|
|
77
|
+
return { kind: "legacy-valid", path: serverPath };
|
|
78
|
+
}
|
|
79
|
+
return { kind: "none" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
warnStaleMcpJson,
|
|
84
|
+
detectMemoryMcpState
|
|
85
|
+
};
|