coding-friend-cli 1.33.1 → 1.34.0
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-NZKIYVIK.js → chunk-3UIH5U3Y.js} +1 -1
- package/dist/{chunk-G2P43ZK2.js → chunk-5X3JZKYW.js} +78 -150
- package/dist/chunk-AYIREX73.js +85 -0
- package/dist/{chunk-OV4AUDXL.js → chunk-CXG6I6ZF.js} +1 -1
- package/dist/{chunk-LK6HFD35.js → chunk-K6EDQGDN.js} +17 -0
- package/dist/chunk-TVAY4OQL.js +118 -0
- package/dist/{config-2OJTBZU2.js → config-MCEQOLE5.js} +3 -3
- package/dist/{host-Y4N4PI4N.js → host-Y3WIXDSS.js} +1 -1
- package/dist/index.js +17 -17
- package/dist/{init-HS4EXSXW.js → init-WDFYY5MM.js} +6 -5
- package/dist/{mcp-BD2K7OL6.js → mcp-J4SD646S.js} +67 -14
- package/dist/{memory-FZMACIGE.js → memory-RLZR7XQY.js} +4 -3
- package/dist/{session-RHXTVNRU.js → session-6WQGKIRG.js} +1 -1
- package/dist/{status-V2A7RSSM.js → status-BX2SVBBW.js} +81 -4
- package/package.json +1 -1
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
warnStaleMcpJson
|
|
3
|
+
} from "./chunk-AYIREX73.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-3UIH5U3Y.js";
|
|
10
13
|
import {
|
|
11
14
|
loadConfig,
|
|
12
15
|
resolveMemoryDir
|
|
13
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-K6EDQGDN.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-3UIH5U3Y.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
|
+
};
|
|
@@ -160,6 +160,22 @@ function stripPath(obj, path) {
|
|
|
160
160
|
}
|
|
161
161
|
delete current[String(path[path.length - 1])];
|
|
162
162
|
}
|
|
163
|
+
function sanitizeRawConfig(raw) {
|
|
164
|
+
const result = ConfigSchema.safeParse(raw);
|
|
165
|
+
if (result.success) {
|
|
166
|
+
return result.data;
|
|
167
|
+
}
|
|
168
|
+
const unknownKeys = [];
|
|
169
|
+
for (const issue of result.error.issues) {
|
|
170
|
+
if (issue.code === "unrecognized_keys") {
|
|
171
|
+
const keys = issue.keys ?? [];
|
|
172
|
+
unknownKeys.push(...keys);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const keyList = unknownKeys.length > 0 ? `: ${unknownKeys.join(", ")}` : "";
|
|
176
|
+
log.warn(`sanitizeRawConfig: unrecognized config keys${keyList} \u2014 returning raw config`);
|
|
177
|
+
return raw;
|
|
178
|
+
}
|
|
163
179
|
function loadConfig() {
|
|
164
180
|
const global = readJson(globalConfigPath());
|
|
165
181
|
const local = readJson(localConfigPath());
|
|
@@ -196,6 +212,7 @@ function resolveMemoryDir(explicitPath) {
|
|
|
196
212
|
}
|
|
197
213
|
|
|
198
214
|
export {
|
|
215
|
+
sanitizeRawConfig,
|
|
199
216
|
loadConfig,
|
|
200
217
|
resolveDocsDir,
|
|
201
218
|
resolveMemoryDir
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectMemoryMcpState
|
|
3
|
+
} from "./chunk-AYIREX73.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/mcp-health.ts
|
|
6
|
+
async function checkMemoryMcpHealth(deps) {
|
|
7
|
+
const checks = [];
|
|
8
|
+
const mcpJson = deps.readMcpJson();
|
|
9
|
+
const state = detectMemoryMcpState(mcpJson, deps.pathExists);
|
|
10
|
+
if (state.kind === "npx") {
|
|
11
|
+
checks.push({ label: "Config (.mcp.json)", ok: true });
|
|
12
|
+
} else if (state.kind === "none") {
|
|
13
|
+
checks.push({
|
|
14
|
+
label: "Config (.mcp.json)",
|
|
15
|
+
ok: false,
|
|
16
|
+
detail: "coding-friend-memory not configured",
|
|
17
|
+
fix: 'Run "cf memory mcp" to add the MCP entry'
|
|
18
|
+
});
|
|
19
|
+
} else {
|
|
20
|
+
const detail = state.kind === "stale" ? `Stale path: ${state.path}` : `Absolute path (legacy): ${state.path}`;
|
|
21
|
+
checks.push({
|
|
22
|
+
label: "Config (.mcp.json)",
|
|
23
|
+
ok: false,
|
|
24
|
+
detail,
|
|
25
|
+
fix: 'Run "cf memory mcp" to update to the npx format'
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const distExists = deps.pathExists(deps.memoryDistPath);
|
|
29
|
+
checks.push({
|
|
30
|
+
label: "Package built",
|
|
31
|
+
ok: distExists,
|
|
32
|
+
...distExists ? {} : {
|
|
33
|
+
detail: "cf-memory not built",
|
|
34
|
+
fix: 'Run "cf memory init" to build'
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
let daemonRunning = false;
|
|
38
|
+
let daemonCheckError;
|
|
39
|
+
try {
|
|
40
|
+
daemonRunning = await deps.isDaemonRunning();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
daemonCheckError = err instanceof Error ? err.message : String(err);
|
|
43
|
+
}
|
|
44
|
+
checks.push({
|
|
45
|
+
label: "Daemon status",
|
|
46
|
+
ok: daemonRunning,
|
|
47
|
+
...daemonRunning ? {} : {
|
|
48
|
+
warn: true,
|
|
49
|
+
detail: daemonCheckError ? `check failed: ${daemonCheckError}` : "stopped (starts automatically on MCP connect)"
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const ok = checks.every((c) => c.ok || c.warn === true);
|
|
53
|
+
return { checks, ok };
|
|
54
|
+
}
|
|
55
|
+
async function checkLearnMcpHealth(deps) {
|
|
56
|
+
const checks = [];
|
|
57
|
+
const mcpJson = deps.readMcpJson();
|
|
58
|
+
const servers = mcpJson?.mcpServers;
|
|
59
|
+
const hasEntry = servers != null && typeof servers === "object" && !Array.isArray(servers) && "coding-friend-learn" in servers;
|
|
60
|
+
checks.push({
|
|
61
|
+
label: "Config (.mcp.json)",
|
|
62
|
+
ok: hasEntry,
|
|
63
|
+
...hasEntry ? {} : {
|
|
64
|
+
detail: "coding-friend-learn not configured",
|
|
65
|
+
fix: 'Run "cf mcp" to print the config snippet and add it'
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const distExists = deps.pathExists(deps.learnMcpDistPath);
|
|
69
|
+
checks.push({
|
|
70
|
+
label: "Package built",
|
|
71
|
+
ok: distExists,
|
|
72
|
+
...distExists ? {} : {
|
|
73
|
+
detail: "learn-mcp not built",
|
|
74
|
+
fix: 'Run "cf mcp" to install and build'
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const mdFiles = deps.listMdFiles(deps.docsDir);
|
|
78
|
+
const hasDocs = mdFiles.length > 0;
|
|
79
|
+
checks.push({
|
|
80
|
+
label: "Docs directory",
|
|
81
|
+
ok: hasDocs,
|
|
82
|
+
...hasDocs ? {} : {
|
|
83
|
+
detail: `No .md files found in ${deps.docsDir}`,
|
|
84
|
+
fix: 'Run "/cf-learn" to generate docs'
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const ok = checks.every((c) => c.ok || c.warn === true);
|
|
88
|
+
return { checks, ok };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/lib/fs-utils.ts
|
|
92
|
+
import { readdirSync } from "fs";
|
|
93
|
+
import { join } from "path";
|
|
94
|
+
function listMdFilesRecursive(dir, maxDepth = 15) {
|
|
95
|
+
try {
|
|
96
|
+
const results = [];
|
|
97
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
98
|
+
if (entry.isDirectory()) {
|
|
99
|
+
if (maxDepth > 0) {
|
|
100
|
+
results.push(
|
|
101
|
+
...listMdFilesRecursive(join(dir, entry.name), maxDepth - 1)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
} else if (entry.name.endsWith(".md") && entry.name !== "README.md") {
|
|
105
|
+
results.push(entry.name);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return results;
|
|
109
|
+
} catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
checkMemoryMcpHealth,
|
|
116
|
+
checkLearnMcpHealth,
|
|
117
|
+
listMdFilesRecursive
|
|
118
|
+
};
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
buildCodexConfig,
|
|
3
3
|
configCommand,
|
|
4
4
|
editCodex
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-CXG6I6ZF.js";
|
|
6
|
+
import "./chunk-3UIH5U3Y.js";
|
|
7
|
+
import "./chunk-K6EDQGDN.js";
|
|
8
8
|
import "./chunk-RZRT7NGT.js";
|
|
9
9
|
import "./chunk-XOHLBLN2.js";
|
|
10
10
|
import "./chunk-OYPOADCX.js";
|
package/dist/index.js
CHANGED
|
@@ -30,19 +30,19 @@ program.command("enable").description("Re-enable the Coding Friend plugin").opti
|
|
|
30
30
|
await enableCommand(opts);
|
|
31
31
|
});
|
|
32
32
|
program.command("init").description("Initialize coding-friend in current project").action(async () => {
|
|
33
|
-
const { initCommand } = await import("./init-
|
|
33
|
+
const { initCommand } = await import("./init-WDFYY5MM.js");
|
|
34
34
|
await initCommand();
|
|
35
35
|
});
|
|
36
36
|
program.command("config").description("Manage Coding Friend configuration").action(async () => {
|
|
37
|
-
const { configCommand } = await import("./config-
|
|
37
|
+
const { configCommand } = await import("./config-MCEQOLE5.js");
|
|
38
38
|
await configCommand();
|
|
39
39
|
});
|
|
40
40
|
program.command("host").description("Build and serve learning docs as a static website").argument("[path]", "path to docs folder").option("-p, --port <port>", "port number", "3333").action(async (path, opts) => {
|
|
41
|
-
const { hostCommand } = await import("./host-
|
|
41
|
+
const { hostCommand } = await import("./host-Y3WIXDSS.js");
|
|
42
42
|
await hostCommand(path, opts);
|
|
43
43
|
});
|
|
44
44
|
program.command("mcp").description("Setup MCP server for learning docs").argument("[path]", "path to docs folder").action(async (path) => {
|
|
45
|
-
const { mcpCommand } = await import("./mcp-
|
|
45
|
+
const { mcpCommand } = await import("./mcp-J4SD646S.js");
|
|
46
46
|
await mcpCommand(path);
|
|
47
47
|
});
|
|
48
48
|
program.command("mcp-serve").description("Start the cf-memory MCP server (used internally by npx)").argument("<memoryDir>", "path to memory directory").action(async (memoryDir) => {
|
|
@@ -69,7 +69,7 @@ program.command("update").description("Update coding-friend plugin, CLI, and sta
|
|
|
69
69
|
await updateCommand(opts);
|
|
70
70
|
});
|
|
71
71
|
program.command("status").description("Show comprehensive Coding Friend status").action(async () => {
|
|
72
|
-
const { statusCommand } = await import("./status-
|
|
72
|
+
const { statusCommand } = await import("./status-BX2SVBBW.js");
|
|
73
73
|
await statusCommand();
|
|
74
74
|
});
|
|
75
75
|
var session = program.command("session").description("Save and load Claude Code sessions across machines");
|
|
@@ -84,11 +84,11 @@ session.command("save").description("Save current Claude Code session to sync fo
|
|
|
84
84
|
"-s, --session-id <id>",
|
|
85
85
|
"session UUID to save (default: auto-detect newest)"
|
|
86
86
|
).option("-l, --label <label>", "label for this session").action(async (opts) => {
|
|
87
|
-
const { sessionSaveCommand } = await import("./session-
|
|
87
|
+
const { sessionSaveCommand } = await import("./session-6WQGKIRG.js");
|
|
88
88
|
await sessionSaveCommand(opts);
|
|
89
89
|
});
|
|
90
90
|
session.command("load").description("Load a saved session from sync folder").action(async () => {
|
|
91
|
-
const { sessionLoadCommand } = await import("./session-
|
|
91
|
+
const { sessionLoadCommand } = await import("./session-6WQGKIRG.js");
|
|
92
92
|
await sessionLoadCommand();
|
|
93
93
|
});
|
|
94
94
|
var memory = program.command("memory").description("AI memory system \u2014 store and search project knowledge");
|
|
@@ -108,43 +108,43 @@ Memory subcommands:
|
|
|
108
108
|
memory mcp Show MCP server setup instructions`
|
|
109
109
|
);
|
|
110
110
|
memory.command("status").description("Show memory system status").action(async () => {
|
|
111
|
-
const { memoryStatusCommand } = await import("./memory-
|
|
111
|
+
const { memoryStatusCommand } = await import("./memory-RLZR7XQY.js");
|
|
112
112
|
await memoryStatusCommand();
|
|
113
113
|
});
|
|
114
114
|
memory.command("search").description("Search memories by query").argument("<query>", "search query").action(async (query) => {
|
|
115
|
-
const { memorySearchCommand } = await import("./memory-
|
|
115
|
+
const { memorySearchCommand } = await import("./memory-RLZR7XQY.js");
|
|
116
116
|
await memorySearchCommand(query);
|
|
117
117
|
});
|
|
118
118
|
memory.command("list").description(
|
|
119
119
|
"List memories in current project, or all projects with --projects"
|
|
120
120
|
).option("--projects", "List all project databases with size and metadata").action(async (opts) => {
|
|
121
|
-
const { memoryListCommand } = await import("./memory-
|
|
121
|
+
const { memoryListCommand } = await import("./memory-RLZR7XQY.js");
|
|
122
122
|
await memoryListCommand(opts);
|
|
123
123
|
});
|
|
124
124
|
memory.command("init").description(
|
|
125
125
|
"Initialize memory system \u2014 interactive wizard (first time) or config menu"
|
|
126
126
|
).action(async () => {
|
|
127
|
-
const { memoryInitCommand } = await import("./memory-
|
|
127
|
+
const { memoryInitCommand } = await import("./memory-RLZR7XQY.js");
|
|
128
128
|
await memoryInitCommand();
|
|
129
129
|
});
|
|
130
130
|
memory.command("config").description("Configure memory system settings").action(async () => {
|
|
131
|
-
const { memoryConfigCommand } = await import("./memory-
|
|
131
|
+
const { memoryConfigCommand } = await import("./memory-RLZR7XQY.js");
|
|
132
132
|
await memoryConfigCommand();
|
|
133
133
|
});
|
|
134
134
|
memory.command("start-daemon").description("Start the memory daemon (Tier 2 \u2014 MiniSearch)").action(async () => {
|
|
135
|
-
const { memoryStartDaemonCommand } = await import("./memory-
|
|
135
|
+
const { memoryStartDaemonCommand } = await import("./memory-RLZR7XQY.js");
|
|
136
136
|
await memoryStartDaemonCommand();
|
|
137
137
|
});
|
|
138
138
|
memory.command("stop-daemon").description("Stop the memory daemon").action(async () => {
|
|
139
|
-
const { memoryStopDaemonCommand } = await import("./memory-
|
|
139
|
+
const { memoryStopDaemonCommand } = await import("./memory-RLZR7XQY.js");
|
|
140
140
|
await memoryStopDaemonCommand();
|
|
141
141
|
});
|
|
142
142
|
memory.command("rebuild").description("Rebuild the daemon search index").action(async () => {
|
|
143
|
-
const { memoryRebuildCommand } = await import("./memory-
|
|
143
|
+
const { memoryRebuildCommand } = await import("./memory-RLZR7XQY.js");
|
|
144
144
|
await memoryRebuildCommand();
|
|
145
145
|
});
|
|
146
146
|
memory.command("mcp").description("Show MCP server setup instructions").action(async () => {
|
|
147
|
-
const { memoryMcpCommand } = await import("./memory-
|
|
147
|
+
const { memoryMcpCommand } = await import("./memory-RLZR7XQY.js");
|
|
148
148
|
await memoryMcpCommand();
|
|
149
149
|
});
|
|
150
150
|
memory.command("rm").description("Remove a project database").option("--project-id <id>", "Project ID to remove").option("--all", "Remove all project databases").option(
|
|
@@ -152,7 +152,7 @@ memory.command("rm").description("Remove a project database").option("--project-
|
|
|
152
152
|
"Remove orphaned projects (source dir missing or 0 memories)"
|
|
153
153
|
).action(
|
|
154
154
|
async (opts) => {
|
|
155
|
-
const { memoryRmCommand } = await import("./memory-
|
|
155
|
+
const { memoryRmCommand } = await import("./memory-RLZR7XQY.js");
|
|
156
156
|
await memoryRmCommand(opts);
|
|
157
157
|
}
|
|
158
158
|
);
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
editCodex
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CXG6I6ZF.js";
|
|
4
4
|
import {
|
|
5
5
|
ensureMemoryBuilt,
|
|
6
6
|
isMemoryInitialized,
|
|
7
7
|
memoryInitWizard
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5X3JZKYW.js";
|
|
9
|
+
import "./chunk-AYIREX73.js";
|
|
9
10
|
import {
|
|
10
11
|
memoryConfigMenu
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-3UIH5U3Y.js";
|
|
13
|
+
import "./chunk-K6EDQGDN.js";
|
|
13
14
|
import {
|
|
14
15
|
getLibPath
|
|
15
16
|
} from "./chunk-RZRT7NGT.js";
|
|
@@ -1013,7 +1014,7 @@ async function initCommand() {
|
|
|
1013
1014
|
console.log();
|
|
1014
1015
|
log.congrats("Setup complete!");
|
|
1015
1016
|
log.dim(
|
|
1016
|
-
"Available commands: /cf-ask, /cf-plan, /cf-fix, /cf-commit, /cf-review, /cf-review-out, /cf-review-in, /cf-ship, /cf-optimize, /cf-scan, /cf-remember, /cf-learn, /cf-teach, /cf-research, /cf-session, /cf-warm, /cf-help"
|
|
1017
|
+
"Available commands: /cf-ask, /cf-plan, /cf-fix, /cf-commit, /cf-review, /cf-review-codex, /cf-review-out, /cf-review-in, /cf-ship, /cf-optimize, /cf-scan, /cf-remember, /cf-learn, /cf-teach, /cf-research, /cf-session, /cf-warm, /cf-help"
|
|
1017
1018
|
);
|
|
1018
1019
|
}
|
|
1019
1020
|
export {
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
checkLearnMcpHealth,
|
|
3
|
+
checkMemoryMcpHealth,
|
|
4
|
+
listMdFilesRecursive
|
|
5
|
+
} from "./chunk-TVAY4OQL.js";
|
|
6
|
+
import {
|
|
3
7
|
ensureMemoryBuilt,
|
|
4
|
-
printMemoryMcpConfig
|
|
8
|
+
printMemoryMcpConfig
|
|
9
|
+
} from "./chunk-5X3JZKYW.js";
|
|
10
|
+
import {
|
|
11
|
+
detectMemoryMcpState,
|
|
5
12
|
warnStaleMcpJson
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-AYIREX73.js";
|
|
14
|
+
import "./chunk-3UIH5U3Y.js";
|
|
8
15
|
import {
|
|
9
16
|
resolveDocsDir,
|
|
10
17
|
resolveMemoryDir
|
|
11
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-K6EDQGDN.js";
|
|
12
19
|
import {
|
|
13
20
|
getLibPath
|
|
14
21
|
} from "./chunk-RZRT7NGT.js";
|
|
@@ -22,7 +29,9 @@ import {
|
|
|
22
29
|
log,
|
|
23
30
|
printBanner
|
|
24
31
|
} from "./chunk-NREZK463.js";
|
|
25
|
-
import
|
|
32
|
+
import {
|
|
33
|
+
readJson
|
|
34
|
+
} from "./chunk-5UVDWG5L.js";
|
|
26
35
|
|
|
27
36
|
// src/commands/mcp.ts
|
|
28
37
|
import { existsSync, readdirSync } from "fs";
|
|
@@ -39,10 +48,28 @@ function countMdFiles(dir) {
|
|
|
39
48
|
}
|
|
40
49
|
return count;
|
|
41
50
|
}
|
|
51
|
+
function printHealthSection(result) {
|
|
52
|
+
console.log(chalk.dim("\u2500\u2500\u2500 Health Check \u2500\u2500\u2500"));
|
|
53
|
+
for (const check of result.checks) {
|
|
54
|
+
if (check.ok) {
|
|
55
|
+
console.log(chalk.green(` \u2713 ${check.label}`));
|
|
56
|
+
} else if (check.warn) {
|
|
57
|
+
const detail = check.detail ? `: ${check.detail}` : "";
|
|
58
|
+
console.log(chalk.yellow(` \u26A0 ${check.label}${detail}`));
|
|
59
|
+
} else {
|
|
60
|
+
const detail = check.detail ? `: ${check.detail}` : "";
|
|
61
|
+
console.log(chalk.red(` \u2717 ${check.label}${detail}`));
|
|
62
|
+
if (check.fix) {
|
|
63
|
+
console.log(chalk.dim(` \u2192 ${check.fix}`));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
42
69
|
async function mcpCommand(path) {
|
|
43
70
|
warnStaleMcpJson(resolveMemoryDir());
|
|
44
71
|
const docsDir = resolveDocsDir(path);
|
|
45
|
-
const
|
|
72
|
+
const learnMcpDir = getLibPath("learn-mcp");
|
|
46
73
|
if (!existsSync(docsDir)) {
|
|
47
74
|
log.error(`Docs folder not found: ${docsDir}`);
|
|
48
75
|
log.dim("Run /cf-learn first to generate some docs.");
|
|
@@ -57,18 +84,18 @@ async function mcpCommand(path) {
|
|
|
57
84
|
printBanner("\u{1F4DA} Learn MCP");
|
|
58
85
|
log.info(`Docs folder: ${chalk.cyan(docsDir)}`);
|
|
59
86
|
log.info(`Found: ${chalk.green(docCount)} docs`);
|
|
60
|
-
if (!existsSync(join(
|
|
87
|
+
if (!existsSync(join(learnMcpDir, "node_modules"))) {
|
|
61
88
|
log.step("Installing MCP server dependencies (one-time setup)...");
|
|
62
|
-
const result = run("npm", ["install", "--silent"], { cwd:
|
|
89
|
+
const result = run("npm", ["install", "--silent"], { cwd: learnMcpDir });
|
|
63
90
|
if (result === null) {
|
|
64
91
|
log.error("Failed to install dependencies");
|
|
65
92
|
process.exit(1);
|
|
66
93
|
}
|
|
67
94
|
log.success("Done.");
|
|
68
95
|
}
|
|
69
|
-
if (!existsSync(join(
|
|
96
|
+
if (!existsSync(join(learnMcpDir, "dist"))) {
|
|
70
97
|
log.step("Building MCP server...");
|
|
71
|
-
const result = run("npm", ["run", "build", "--silent"], { cwd:
|
|
98
|
+
const result = run("npm", ["run", "build", "--silent"], { cwd: learnMcpDir });
|
|
72
99
|
if (result === null) {
|
|
73
100
|
log.error("Failed to build MCP server");
|
|
74
101
|
process.exit(1);
|
|
@@ -96,9 +123,19 @@ async function mcpCommand(path) {
|
|
|
96
123
|
"Available tools & resources: https://cf.dinhanhthi.com/docs/cli/cf-mcp/"
|
|
97
124
|
);
|
|
98
125
|
console.log();
|
|
99
|
-
|
|
126
|
+
const localMcpPath = join(process.cwd(), ".mcp.json");
|
|
127
|
+
const learnMcpDistPath = join(learnMcpDir, "dist", "index.js");
|
|
128
|
+
const learnHealth = await checkLearnMcpHealth({
|
|
129
|
+
readMcpJson: () => readJson(localMcpPath),
|
|
130
|
+
pathExists: existsSync,
|
|
131
|
+
listMdFiles: listMdFilesRecursive,
|
|
132
|
+
docsDir,
|
|
133
|
+
learnMcpDistPath
|
|
134
|
+
});
|
|
135
|
+
printHealthSection(learnHealth);
|
|
136
|
+
await printMemoryMcp();
|
|
100
137
|
}
|
|
101
|
-
function printMemoryMcp() {
|
|
138
|
+
async function printMemoryMcp() {
|
|
102
139
|
const memoryDir = resolveMemoryDir();
|
|
103
140
|
let mcpDir;
|
|
104
141
|
try {
|
|
@@ -115,8 +152,24 @@ function printMemoryMcp() {
|
|
|
115
152
|
log.info(`Memory dir: ${chalk.cyan(memoryDir)}`);
|
|
116
153
|
console.log();
|
|
117
154
|
printMemoryMcpConfig(memoryDir);
|
|
155
|
+
const localMcpPath = join(process.cwd(), ".mcp.json");
|
|
156
|
+
const memoryDistPath = join(mcpDir, "dist", "index.js");
|
|
157
|
+
let isDaemonRunning = async () => false;
|
|
158
|
+
try {
|
|
159
|
+
const proc = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
160
|
+
isDaemonRunning = proc.isDaemonRunning;
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
const memoryHealth = await checkMemoryMcpHealth({
|
|
164
|
+
readMcpJson: () => readJson(localMcpPath),
|
|
165
|
+
pathExists: existsSync,
|
|
166
|
+
isDaemonRunning,
|
|
167
|
+
memoryDistPath
|
|
168
|
+
});
|
|
169
|
+
printHealthSection(memoryHealth);
|
|
118
170
|
}
|
|
119
171
|
export {
|
|
120
172
|
detectMemoryMcpState,
|
|
121
|
-
mcpCommand
|
|
173
|
+
mcpCommand,
|
|
174
|
+
printHealthSection
|
|
122
175
|
};
|
|
@@ -13,9 +13,10 @@ import {
|
|
|
13
13
|
memoryStatusCommand,
|
|
14
14
|
memoryStopDaemonCommand,
|
|
15
15
|
printMemoryMcpConfig
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-5X3JZKYW.js";
|
|
17
|
+
import "./chunk-AYIREX73.js";
|
|
18
|
+
import "./chunk-3UIH5U3Y.js";
|
|
19
|
+
import "./chunk-K6EDQGDN.js";
|
|
19
20
|
import "./chunk-RZRT7NGT.js";
|
|
20
21
|
import "./chunk-OYPOADCX.js";
|
|
21
22
|
import "./chunk-Z5Q533UG.js";
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
checkLearnMcpHealth,
|
|
3
|
+
checkMemoryMcpHealth,
|
|
4
|
+
listMdFilesRecursive
|
|
5
|
+
} from "./chunk-TVAY4OQL.js";
|
|
6
|
+
import "./chunk-AYIREX73.js";
|
|
7
|
+
import "./chunk-3UIH5U3Y.js";
|
|
8
|
+
import {
|
|
9
|
+
resolveDocsDir,
|
|
10
|
+
resolveMemoryDir,
|
|
11
|
+
sanitizeRawConfig
|
|
12
|
+
} from "./chunk-K6EDQGDN.js";
|
|
4
13
|
import {
|
|
5
14
|
getLibPath
|
|
6
15
|
} from "./chunk-RZRT7NGT.js";
|
|
@@ -212,8 +221,76 @@ async function statusCommand() {
|
|
|
212
221
|
console.log(`${pad("Daemon", VERSION_COL)}${daemonLabel}`);
|
|
213
222
|
console.log(`${pad("Documents", VERSION_COL)}${docCount} files`);
|
|
214
223
|
console.log(chalk.dim(` \u2192 Run "cf memory status" for details`));
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
console.log();
|
|
225
|
+
console.log(chalk.bold("\u{1F50C} MCP"));
|
|
226
|
+
try {
|
|
227
|
+
const localMcpPath = join(process.cwd(), ".mcp.json");
|
|
228
|
+
console.log(chalk.dim(" Memory MCP (coding-friend-memory)"));
|
|
229
|
+
let memIsDaemonRunning = async () => false;
|
|
230
|
+
const memMcpDir = (() => {
|
|
231
|
+
try {
|
|
232
|
+
return getLibPath("cf-memory");
|
|
233
|
+
} catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
})();
|
|
237
|
+
const memoryDistPath = memMcpDir ? join(memMcpDir, "dist", "index.js") : "/unavailable";
|
|
238
|
+
if (memMcpDir) {
|
|
239
|
+
try {
|
|
240
|
+
const proc = await import(join(memMcpDir, "dist/daemon/process.js"));
|
|
241
|
+
memIsDaemonRunning = proc.isDaemonRunning;
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const memHealth = await checkMemoryMcpHealth({
|
|
246
|
+
readMcpJson: () => readJson(localMcpPath),
|
|
247
|
+
pathExists: existsSync,
|
|
248
|
+
isDaemonRunning: memIsDaemonRunning,
|
|
249
|
+
memoryDistPath
|
|
250
|
+
});
|
|
251
|
+
let memHasIssues = false;
|
|
252
|
+
for (const check of memHealth.checks) {
|
|
253
|
+
const indicator = check.ok ? chalk.green("\u2713") : check.warn ? chalk.yellow("\u26A0") : chalk.red("\u2717");
|
|
254
|
+
const detail = !check.ok && check.detail ? `: ${check.detail}` : "";
|
|
255
|
+
console.log(` ${indicator} ${pad(check.label, 20)}${detail}`);
|
|
256
|
+
if (!check.ok && !check.warn) memHasIssues = true;
|
|
257
|
+
}
|
|
258
|
+
if (memHasIssues) {
|
|
259
|
+
console.log(chalk.dim(` \u2192 Run "cf mcp" to fix`));
|
|
260
|
+
}
|
|
261
|
+
console.log();
|
|
262
|
+
console.log(chalk.dim(" Learn MCP (coding-friend-learn)"));
|
|
263
|
+
let learnMcpDir = null;
|
|
264
|
+
try {
|
|
265
|
+
learnMcpDir = getLibPath("learn-mcp");
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
const learnMcpDistPath = learnMcpDir ? join(learnMcpDir, "dist", "index.js") : "/unavailable";
|
|
269
|
+
const docsDir = resolveDocsDir();
|
|
270
|
+
const learnHealth = await checkLearnMcpHealth({
|
|
271
|
+
readMcpJson: () => readJson(localMcpPath),
|
|
272
|
+
pathExists: existsSync,
|
|
273
|
+
listMdFiles: listMdFilesRecursive,
|
|
274
|
+
docsDir,
|
|
275
|
+
learnMcpDistPath
|
|
276
|
+
});
|
|
277
|
+
let learnHasIssues = false;
|
|
278
|
+
for (const check of learnHealth.checks) {
|
|
279
|
+
const indicator = check.ok ? chalk.green("\u2713") : check.warn ? chalk.yellow("\u26A0") : chalk.red("\u2717");
|
|
280
|
+
const detail = !check.ok && check.detail ? `: ${check.detail}` : "";
|
|
281
|
+
console.log(` ${indicator} ${pad(check.label, 20)}${detail}`);
|
|
282
|
+
if (!check.ok && !check.warn) learnHasIssues = true;
|
|
283
|
+
}
|
|
284
|
+
if (learnHasIssues) {
|
|
285
|
+
console.log(chalk.dim(` \u2192 Run "cf mcp" to fix`));
|
|
286
|
+
}
|
|
287
|
+
} catch {
|
|
288
|
+
console.log(`${pad("MCP", VERSION_COL)}${chalk.dim("unavailable")}`);
|
|
289
|
+
}
|
|
290
|
+
const rawGlobalConfig = readJson(globalConfigPath());
|
|
291
|
+
const rawLocalConfig = readJson(localConfigPath());
|
|
292
|
+
const globalConfig = rawGlobalConfig ? sanitizeRawConfig(rawGlobalConfig) : null;
|
|
293
|
+
const localConfig = rawLocalConfig ? sanitizeRawConfig(rawLocalConfig) : null;
|
|
217
294
|
if (globalConfig && Object.keys(globalConfig).length > 0) {
|
|
218
295
|
console.log();
|
|
219
296
|
console.log(chalk.bold(`\u2699\uFE0F Config (global: ${globalConfigPath()})`));
|