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