coding-friend-cli 1.34.1 → 1.35.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/README.md +1 -0
- package/dist/chunk-2DTXVMPQ.js +26 -0
- package/dist/{chunk-DHH6SRXV.js → chunk-57EGAXYI.js} +3 -2
- package/dist/{chunk-AZ6AK5M4.js → chunk-B2NFXRFP.js} +12 -2
- package/dist/{chunk-QCKIBJF6.js → chunk-C5YWESSF.js} +2 -6
- package/dist/chunk-E5J24GD5.js +190 -0
- package/dist/{chunk-G4QGJEV7.js → chunk-H5TXJZD7.js} +6 -6
- package/dist/{chunk-FAANBGCB.js → chunk-L53RNEV3.js} +116 -73
- package/dist/{chunk-DMJCO6LJ.js → chunk-RLUNNJWJ.js} +2 -1
- package/dist/{chunk-LXZ47XCD.js → chunk-WNTMKIKC.js} +1 -1
- package/dist/chunk-WSUYLIYX.js +61 -0
- package/dist/clean-67ACB4OS.js +95 -0
- package/dist/{config-44VQBNMS.js → config-HTQAALMA.js} +181 -120
- package/dist/{dev-QXVERU42.js → dev-PFUF67SH.js} +7 -7
- package/dist/{disable-ZWQVU3QZ.js → disable-CDOM7SSI.js} +2 -2
- package/dist/{enable-NXP6APNI.js → enable-SF7742UR.js} +2 -2
- package/dist/{host-6IBKKCPA.js → host-HKA4OHBM.js} +3 -3
- package/dist/index.js +38 -34
- package/dist/{init-KY3ZPEWP.js → init-YM7YEEGK.js} +153 -85
- package/dist/{install-22BLHQ6D.js → install-VYZSTXN6.js} +8 -8
- package/dist/{mcp-OGZAZZRF.js → mcp-7MO2MCLE.js} +69 -66
- package/dist/{mcp-serve-learn-UNW6VH5M.js → mcp-serve-learn-OKVC5WV3.js} +19 -3
- package/dist/{memory-JM45DEIX.js → memory-XALEMWQF.js} +7 -7
- package/dist/{permission-DNW7DE24.js → permission-C5R3LMZQ.js} +2 -2
- package/dist/postinstall.js +1 -1
- package/dist/{session-RCEE6P35.js → session-M4KRQ4VU.js} +2 -2
- package/dist/{status-NMSVOSC4.js → status-2XM5OZDR.js} +19 -14
- package/dist/{statusline-GKVAX7GR.js → statusline-VFIA7OS2.js} +3 -3
- package/dist/{uninstall-6ZJRS2H4.js → uninstall-QKEC3O64.js} +6 -6
- package/dist/{update-P5ZR2JVQ.js → update-IV4Y3T72.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-CZDXMFEZ.js +0 -118
- package/dist/chunk-FI5HEQ43.js +0 -85
- package/dist/{chunk-Z5Q533UG.js → chunk-HPNRQYLM.js} +3 -3
- package/dist/{update-check-5SCD2JR2.js → update-check-BPCQ25AR.js} +3 -3
package/README.md
CHANGED
|
@@ -101,6 +101,7 @@ cf memory config # Configure memory system settings
|
|
|
101
101
|
cf memory mcp # Show MCP server config for clients
|
|
102
102
|
cf status # Show comprehensive Coding Friend status
|
|
103
103
|
# (versions, plugin, memory, config)
|
|
104
|
+
cf clean # Clean files generated by Coding Friend in docs/
|
|
104
105
|
cf session save # Save current Claude Code session to docs/sessions/
|
|
105
106
|
cf session load # Load a saved session from docs/sessions/
|
|
106
107
|
cf help # Show all commands
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/lib/fs-utils.ts
|
|
2
|
+
import { readdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
function listMdFilesRecursive(dir, maxDepth = 15) {
|
|
5
|
+
try {
|
|
6
|
+
const results = [];
|
|
7
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
8
|
+
if (entry.isDirectory()) {
|
|
9
|
+
if (maxDepth > 0) {
|
|
10
|
+
results.push(
|
|
11
|
+
...listMdFilesRecursive(join(dir, entry.name), maxDepth - 1)
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
} else if (entry.name.endsWith(".md") && entry.name !== "README.md") {
|
|
15
|
+
results.push(entry.name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return results;
|
|
19
|
+
} catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
listMdFilesRecursive
|
|
26
|
+
};
|
|
@@ -24,7 +24,7 @@ var DEFAULT_CONFIG = {
|
|
|
24
24
|
docsDir: "docs",
|
|
25
25
|
learn: {
|
|
26
26
|
language: "en",
|
|
27
|
-
outputDir: "
|
|
27
|
+
outputDir: "~/.coding-friend/learn",
|
|
28
28
|
categories: [
|
|
29
29
|
{
|
|
30
30
|
name: "concepts",
|
|
@@ -42,7 +42,8 @@ var DEFAULT_CONFIG = {
|
|
|
42
42
|
}
|
|
43
43
|
],
|
|
44
44
|
autoCommit: false,
|
|
45
|
-
readmeIndex: false
|
|
45
|
+
readmeIndex: false,
|
|
46
|
+
disabled: false
|
|
46
47
|
}
|
|
47
48
|
};
|
|
48
49
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_CONFIG
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-57EGAXYI.js";
|
|
4
4
|
import {
|
|
5
5
|
globalConfigPath,
|
|
6
6
|
localConfigPath,
|
|
@@ -40,7 +40,8 @@ var LearnConfigSchema = z.object({
|
|
|
40
40
|
outputDir: z.string().optional(),
|
|
41
41
|
categories: z.array(LearnCategorySchema).optional(),
|
|
42
42
|
autoCommit: z.boolean().optional(),
|
|
43
|
-
readmeIndex: z.union([z.boolean(), z.literal("per-category")]).optional()
|
|
43
|
+
readmeIndex: z.union([z.boolean(), z.literal("per-category")]).optional(),
|
|
44
|
+
disabled: z.boolean().optional()
|
|
44
45
|
});
|
|
45
46
|
var StatuslineComponentSchema = z.enum([
|
|
46
47
|
"version",
|
|
@@ -69,6 +70,7 @@ var MemoryConfigSchema = z.object({
|
|
|
69
70
|
var ConfigSchema = z.strictObject({
|
|
70
71
|
language: z.string().optional(),
|
|
71
72
|
docsDir: z.string().optional(),
|
|
73
|
+
tdd: z.boolean().optional(),
|
|
72
74
|
learn: LearnConfigSchema.optional(),
|
|
73
75
|
statusline: StatuslineConfigSchema.optional(),
|
|
74
76
|
memory: MemoryConfigSchema.optional(),
|
|
@@ -79,6 +81,7 @@ var ConfigSchema = z.strictObject({
|
|
|
79
81
|
var KNOWN_KEYS = [
|
|
80
82
|
"language",
|
|
81
83
|
"docsDir",
|
|
84
|
+
"tdd",
|
|
82
85
|
"learn",
|
|
83
86
|
"statusline",
|
|
84
87
|
"memory",
|
|
@@ -195,6 +198,12 @@ function resolveDocsDir(explicitPath) {
|
|
|
195
198
|
}
|
|
196
199
|
return resolvePath("docs/learn");
|
|
197
200
|
}
|
|
201
|
+
function resolveLearnDir(globalCfg) {
|
|
202
|
+
if (globalCfg?.learn?.outputDir) {
|
|
203
|
+
return resolvePath(globalCfg.learn.outputDir);
|
|
204
|
+
}
|
|
205
|
+
return resolvePath("~/.coding-friend/learn");
|
|
206
|
+
}
|
|
198
207
|
function resolveMemoryDir(explicitPath) {
|
|
199
208
|
if (explicitPath) {
|
|
200
209
|
return resolvePath(explicitPath);
|
|
@@ -210,5 +219,6 @@ export {
|
|
|
210
219
|
sanitizeRawConfig,
|
|
211
220
|
loadConfig,
|
|
212
221
|
resolveDocsDir,
|
|
222
|
+
resolveLearnDir,
|
|
213
223
|
resolveMemoryDir
|
|
214
224
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveMemoryDir
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-B2NFXRFP.js";
|
|
4
4
|
import {
|
|
5
5
|
getLibPath
|
|
6
6
|
} from "./chunk-RZRT7NGT.js";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
askScope,
|
|
10
10
|
formatScopeLabel,
|
|
11
11
|
injectBackChoice
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-HPNRQYLM.js";
|
|
13
13
|
import {
|
|
14
14
|
globalConfigPath,
|
|
15
15
|
localConfigPath
|
|
@@ -352,10 +352,6 @@ async function memoryConfigMenu(opts) {
|
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
export {
|
|
355
|
-
editMemoryTier,
|
|
356
|
-
editMemoryAutoCapture,
|
|
357
|
-
editMemoryAutoStart,
|
|
358
|
-
editMemoryEmbedding,
|
|
359
355
|
writeMemoryMcpEntry,
|
|
360
356
|
getMemoryMcpStatus,
|
|
361
357
|
memoryConfigMenu
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
writeMemoryMcpEntry
|
|
3
|
+
} from "./chunk-C5YWESSF.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
|
+
// src/lib/mcp-health.ts
|
|
83
|
+
import chalk2 from "chalk";
|
|
84
|
+
function printHealthSection(result) {
|
|
85
|
+
console.log(chalk2.dim("\u2500\u2500\u2500 Health Check \u2500\u2500\u2500"));
|
|
86
|
+
for (const check of result.checks) {
|
|
87
|
+
if (check.ok) {
|
|
88
|
+
const detail = check.detail ? `: ${check.detail}` : "";
|
|
89
|
+
console.log(chalk2.green(` \u2713 ${check.label}${detail}`));
|
|
90
|
+
} else if (check.warn) {
|
|
91
|
+
const detail = check.detail ? `: ${check.detail}` : "";
|
|
92
|
+
console.log(chalk2.yellow(` \u26A0 ${check.label}${detail}`));
|
|
93
|
+
} else {
|
|
94
|
+
const detail = check.detail ? `: ${check.detail}` : "";
|
|
95
|
+
console.log(chalk2.red(` \u2717 ${check.label}${detail}`));
|
|
96
|
+
if (check.fix) {
|
|
97
|
+
console.log(chalk2.dim(` \u2192 ${check.fix}`));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log();
|
|
102
|
+
}
|
|
103
|
+
async function checkMemoryMcpHealth(deps) {
|
|
104
|
+
const checks = [];
|
|
105
|
+
const mcpJson = deps.readMcpJson();
|
|
106
|
+
const state = detectMemoryMcpState(mcpJson, deps.pathExists);
|
|
107
|
+
if (state.kind === "npx") {
|
|
108
|
+
checks.push({ label: "Config (.mcp.json)", ok: true });
|
|
109
|
+
} else if (state.kind === "none") {
|
|
110
|
+
checks.push({
|
|
111
|
+
label: "Config (.mcp.json)",
|
|
112
|
+
ok: false,
|
|
113
|
+
detail: "coding-friend-memory not configured",
|
|
114
|
+
fix: 'Run "cf memory mcp" to add the MCP entry'
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
const detail = state.kind === "stale" ? `Stale path: ${state.path}` : `Absolute path (legacy): ${state.path}`;
|
|
118
|
+
checks.push({
|
|
119
|
+
label: "Config (.mcp.json)",
|
|
120
|
+
ok: false,
|
|
121
|
+
detail,
|
|
122
|
+
fix: 'Run "cf memory mcp" to update to the npx format'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const distExists = deps.pathExists(deps.memoryDistPath);
|
|
126
|
+
checks.push({
|
|
127
|
+
label: "Package built",
|
|
128
|
+
ok: distExists,
|
|
129
|
+
...distExists ? {} : {
|
|
130
|
+
detail: "cf-memory not built",
|
|
131
|
+
fix: 'Run "cf memory init" to build'
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
let daemonRunning = false;
|
|
135
|
+
let daemonCheckError;
|
|
136
|
+
try {
|
|
137
|
+
daemonRunning = await deps.isDaemonRunning();
|
|
138
|
+
} catch (err) {
|
|
139
|
+
daemonCheckError = err instanceof Error ? err.message : String(err);
|
|
140
|
+
}
|
|
141
|
+
checks.push({
|
|
142
|
+
label: "Daemon",
|
|
143
|
+
ok: daemonRunning,
|
|
144
|
+
detail: daemonRunning ? "running" : daemonCheckError ? `check failed: ${daemonCheckError}` : "stopped (starts automatically on MCP connect)",
|
|
145
|
+
...!daemonRunning ? { warn: true } : {}
|
|
146
|
+
});
|
|
147
|
+
const ok = checks.every((c) => c.ok || c.warn === true);
|
|
148
|
+
return { checks, ok };
|
|
149
|
+
}
|
|
150
|
+
async function checkLearnMcpHealth(deps) {
|
|
151
|
+
const checks = [];
|
|
152
|
+
const isRegistered = deps.checkRegistered();
|
|
153
|
+
checks.push({
|
|
154
|
+
label: "Registered (claude mcp)",
|
|
155
|
+
ok: isRegistered,
|
|
156
|
+
...isRegistered ? {} : {
|
|
157
|
+
detail: "coding-friend-learn not registered",
|
|
158
|
+
fix: 'Run "cf mcp" to register'
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
const distExists = deps.pathExists(deps.learnMcpDistPath);
|
|
162
|
+
checks.push({
|
|
163
|
+
label: "Package built",
|
|
164
|
+
ok: distExists,
|
|
165
|
+
...distExists ? {} : {
|
|
166
|
+
detail: "learn-mcp not built",
|
|
167
|
+
fix: 'Run "cf mcp" to install and build'
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
const mdFiles = deps.listMdFiles(deps.docsDir);
|
|
171
|
+
const hasDocs = mdFiles.length > 0;
|
|
172
|
+
checks.push({
|
|
173
|
+
label: "Docs directory",
|
|
174
|
+
ok: hasDocs,
|
|
175
|
+
...hasDocs ? {} : {
|
|
176
|
+
detail: `No .md files found in ${deps.docsDir}`,
|
|
177
|
+
fix: 'Run "/cf-learn" to generate docs'
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
const ok = checks.every((c) => c.ok || c.warn === true);
|
|
181
|
+
return { checks, ok };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export {
|
|
185
|
+
warnStaleMcpJson,
|
|
186
|
+
detectMemoryMcpState,
|
|
187
|
+
printHealthSection,
|
|
188
|
+
checkMemoryMcpHealth,
|
|
189
|
+
checkLearnMcpHealth
|
|
190
|
+
};
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureStatusline,
|
|
3
3
|
getInstalledVersion
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WNTMKIKC.js";
|
|
5
5
|
import {
|
|
6
6
|
ensureShellCompletion
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-RLUNNJWJ.js";
|
|
8
8
|
import {
|
|
9
9
|
resolveScope
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import {
|
|
12
|
-
claudeSettingsPath
|
|
13
|
-
} from "./chunk-TWKNGPBO.js";
|
|
10
|
+
} from "./chunk-HPNRQYLM.js";
|
|
14
11
|
import {
|
|
15
12
|
commandExists,
|
|
16
13
|
run,
|
|
17
14
|
runWithStderr,
|
|
18
15
|
sleepSync
|
|
19
16
|
} from "./chunk-EVGXUDX4.js";
|
|
17
|
+
import {
|
|
18
|
+
claudeSettingsPath
|
|
19
|
+
} from "./chunk-TWKNGPBO.js";
|
|
20
20
|
import {
|
|
21
21
|
log,
|
|
22
22
|
printBanner
|
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
|
+
checkMemoryMcpHealth,
|
|
3
|
+
printHealthSection,
|
|
2
4
|
warnStaleMcpJson
|
|
3
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-E5J24GD5.js";
|
|
4
6
|
import {
|
|
5
|
-
editMemoryAutoCapture,
|
|
6
|
-
editMemoryAutoStart,
|
|
7
|
-
editMemoryEmbedding,
|
|
8
|
-
editMemoryTier,
|
|
9
7
|
getMemoryMcpStatus,
|
|
10
8
|
memoryConfigMenu,
|
|
11
9
|
writeMemoryMcpEntry
|
|
12
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-C5YWESSF.js";
|
|
13
11
|
import {
|
|
14
12
|
loadConfig,
|
|
15
13
|
resolveMemoryDir
|
|
16
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-B2NFXRFP.js";
|
|
17
15
|
import {
|
|
18
16
|
getLibPath
|
|
19
17
|
} from "./chunk-RZRT7NGT.js";
|
|
20
18
|
import {
|
|
21
19
|
showConfigHint
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import {
|
|
24
|
-
globalConfigPath,
|
|
25
|
-
localConfigPath
|
|
26
|
-
} from "./chunk-TWKNGPBO.js";
|
|
20
|
+
} from "./chunk-HPNRQYLM.js";
|
|
27
21
|
import {
|
|
28
22
|
run,
|
|
29
23
|
runWithStderr
|
|
30
24
|
} from "./chunk-EVGXUDX4.js";
|
|
25
|
+
import {
|
|
26
|
+
globalConfigPath,
|
|
27
|
+
localConfigPath
|
|
28
|
+
} from "./chunk-TWKNGPBO.js";
|
|
31
29
|
import {
|
|
32
30
|
log,
|
|
33
31
|
printBanner
|
|
34
32
|
} from "./chunk-NREZK463.js";
|
|
35
33
|
import {
|
|
34
|
+
mergeJson,
|
|
36
35
|
readJson
|
|
37
36
|
} from "./chunk-5UVDWG5L.js";
|
|
38
37
|
|
|
@@ -336,19 +335,38 @@ async function memoryRebuildCommand() {
|
|
|
336
335
|
const config = loadConfig();
|
|
337
336
|
const embedding = config.memory?.embedding;
|
|
338
337
|
const opts = embedding ? { embedding, skipVec: false } : { skipVec: false };
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
338
|
+
const runRebuild = async () => {
|
|
339
|
+
const backend = new SqliteBackend(memoryDir, opts);
|
|
340
|
+
try {
|
|
341
|
+
await backend.rebuild();
|
|
342
|
+
const stats = await backend.stats();
|
|
343
|
+
log.success(`Rebuilt: ${stats.total} memories indexed.`);
|
|
344
|
+
if (backend.isVecEnabled()) {
|
|
345
|
+
log.info(`Vector search: ${chalk.green("enabled")}`);
|
|
346
|
+
}
|
|
347
|
+
if (!backend.isRebuildNeeded()) {
|
|
348
|
+
log.info("Embedding dimensions: up to date");
|
|
349
|
+
}
|
|
350
|
+
} finally {
|
|
351
|
+
await backend.close();
|
|
346
352
|
}
|
|
347
|
-
|
|
348
|
-
|
|
353
|
+
};
|
|
354
|
+
try {
|
|
355
|
+
await runRebuild();
|
|
356
|
+
} catch (err) {
|
|
357
|
+
const isCorrupt = err instanceof Error && (err.message.includes("database disk image is malformed") || err.code === "SQLITE_CORRUPT" || err.code === "SQLITE_IOERR_SHORT_READ");
|
|
358
|
+
if (isCorrupt) {
|
|
359
|
+
const dbPath = getDbPath(memoryDir);
|
|
360
|
+
if (dbPath) {
|
|
361
|
+
log.warn("SQLite database is corrupt. Deleting and recreating...");
|
|
362
|
+
for (const suffix of ["", "-shm", "-wal"]) {
|
|
363
|
+
rmSync(dbPath + suffix, { force: true });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
await runRebuild();
|
|
367
|
+
} else {
|
|
368
|
+
throw err;
|
|
349
369
|
}
|
|
350
|
-
} finally {
|
|
351
|
-
await backend.close();
|
|
352
370
|
}
|
|
353
371
|
return;
|
|
354
372
|
}
|
|
@@ -422,69 +440,80 @@ function isMemoryInitialized() {
|
|
|
422
440
|
return getDbPath(memoryDir) !== null;
|
|
423
441
|
}
|
|
424
442
|
async function memoryInitWizard(memoryDir, mcpDir) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
443
|
+
const globalCfg = readJson(globalConfigPath());
|
|
444
|
+
const globalMem = globalCfg?.memory ?? {};
|
|
445
|
+
const tier = globalMem.tier ?? "auto";
|
|
446
|
+
const embedding = globalMem.embedding ?? { provider: "transformers" };
|
|
447
|
+
const autoCapture = globalMem.autoCapture ?? true;
|
|
448
|
+
const autoStart = globalMem.autoStart ?? true;
|
|
449
|
+
const existingLocalCfg = readJson(localConfigPath());
|
|
450
|
+
const existingMem = existingLocalCfg?.memory ?? {};
|
|
451
|
+
mergeJson(localConfigPath(), {
|
|
452
|
+
memory: { ...existingMem, tier, embedding, autoCapture, autoStart }
|
|
453
|
+
});
|
|
454
|
+
const inherited = chalk.dim("\u2190 global config");
|
|
455
|
+
const defaulted = chalk.dim("\u2190 default");
|
|
456
|
+
const src = (field) => field !== void 0 ? inherited : defaulted;
|
|
430
457
|
console.log();
|
|
431
|
-
log.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
readJson(localConfigPath())
|
|
458
|
+
log.info(`Search tier: ${chalk.cyan(String(tier))} ${src(globalMem.tier)}`);
|
|
459
|
+
log.dim(
|
|
460
|
+
" Auto-detects best backend: SQLite (Tier 1) \u2192 MiniSearch daemon (Tier 2) \u2192 Grep (Tier 3)."
|
|
435
461
|
);
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
await editMemoryAutoCapture(
|
|
439
|
-
readJson(globalConfigPath()),
|
|
440
|
-
readJson(localConfigPath())
|
|
462
|
+
log.info(
|
|
463
|
+
`Embedding: ${chalk.cyan(String(embedding.provider ?? "transformers"))} ${src(globalMem.embedding)}`
|
|
441
464
|
);
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
465
|
+
log.dim(
|
|
466
|
+
" Powers semantic search in Tier 1. Runs locally, no external service needed."
|
|
467
|
+
);
|
|
468
|
+
log.info(
|
|
469
|
+
`Auto-capture: ${autoCapture ? chalk.green("on") : chalk.dim("off")} ${src(globalMem.autoCapture)}`
|
|
470
|
+
);
|
|
471
|
+
log.dim(
|
|
472
|
+
" Saves session summaries to memory when Claude Code compacts context."
|
|
473
|
+
);
|
|
474
|
+
log.info(
|
|
475
|
+
`Auto-start daemon: ${autoStart ? chalk.green("on") : chalk.dim("off")} ${src(globalMem.autoStart)}`
|
|
476
|
+
);
|
|
477
|
+
log.dim(
|
|
478
|
+
" Background process watches docs/memory/ and auto-rebuilds the search index on file changes."
|
|
447
479
|
);
|
|
448
480
|
console.log();
|
|
449
481
|
const config = loadConfig();
|
|
450
|
-
const
|
|
451
|
-
if (
|
|
482
|
+
const resolvedTier = config.memory?.tier ?? "auto";
|
|
483
|
+
if (resolvedTier === "markdown") {
|
|
452
484
|
await setupMemoryMcp(memoryDir, mcpDir);
|
|
453
|
-
log.
|
|
454
|
-
|
|
455
|
-
);
|
|
485
|
+
console.log('\u{1F389} Memory initialized! Run "cf memory status" to verify.');
|
|
486
|
+
printInitDone();
|
|
456
487
|
return;
|
|
457
488
|
}
|
|
458
|
-
if (
|
|
489
|
+
if (resolvedTier === "lite") {
|
|
459
490
|
await setupMemoryMcp(memoryDir, mcpDir);
|
|
460
|
-
log.
|
|
461
|
-
|
|
462
|
-
);
|
|
491
|
+
console.log('\u{1F389} Memory initialized! Run "cf memory status" to verify.');
|
|
492
|
+
printInitDone();
|
|
463
493
|
return;
|
|
464
494
|
}
|
|
465
495
|
const depsOk = await ensureSqliteDepsIfNeeded(mcpDir);
|
|
466
496
|
if (!depsOk) return;
|
|
467
497
|
if (!existsSync(memoryDir)) {
|
|
468
|
-
log.info(
|
|
469
|
-
"No memory directory found. Memories will be indexed as they're created."
|
|
470
|
-
);
|
|
471
498
|
await setupMemoryMcp(memoryDir, mcpDir);
|
|
472
|
-
log
|
|
499
|
+
console.log('\u{1F389} Memory initialized! Run "cf memory status" to verify.');
|
|
500
|
+
printInitDone();
|
|
473
501
|
return;
|
|
474
502
|
}
|
|
475
503
|
const docCount = countMdFiles(memoryDir);
|
|
476
504
|
if (docCount === 0) {
|
|
477
505
|
log.info("No existing memories to import.");
|
|
478
506
|
await setupMemoryMcp(memoryDir, mcpDir);
|
|
479
|
-
log
|
|
507
|
+
console.log('\u{1F389} Memory initialized! Run "cf memory status" to verify.');
|
|
508
|
+
printInitDone();
|
|
480
509
|
return;
|
|
481
510
|
}
|
|
482
511
|
log.step(`Importing ${docCount} existing memories into SQLite...`);
|
|
483
512
|
const { SqliteBackend } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
|
|
484
|
-
const
|
|
513
|
+
const configEmbedding = config.memory?.embedding;
|
|
485
514
|
const backend = new SqliteBackend(memoryDir, {
|
|
486
515
|
skipVec: false,
|
|
487
|
-
...
|
|
516
|
+
...configEmbedding && { embedding: configEmbedding }
|
|
488
517
|
});
|
|
489
518
|
try {
|
|
490
519
|
await backend.rebuild();
|
|
@@ -498,10 +527,20 @@ async function memoryInitWizard(memoryDir, mcpDir) {
|
|
|
498
527
|
}
|
|
499
528
|
await setupMemoryMcp(memoryDir, mcpDir);
|
|
500
529
|
console.log();
|
|
501
|
-
log
|
|
530
|
+
console.log('\u{1F389} Memory initialized! Run "cf memory status" to verify.');
|
|
502
531
|
log.info(
|
|
503
532
|
`Tip: Run ${chalk.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
|
|
504
533
|
);
|
|
534
|
+
log.dim(` Change settings anytime: ${chalk.cyan("cf memory config")}`);
|
|
535
|
+
log.dim(
|
|
536
|
+
` Documentation: ${chalk.cyan("https://cf.dinhanhthi.com/docs/cli/cf-memory/")}`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
function printInitDone() {
|
|
540
|
+
log.dim(` Change settings anytime: ${chalk.cyan("cf memory config")}`);
|
|
541
|
+
log.dim(
|
|
542
|
+
` Documentation: ${chalk.cyan("https://cf.dinhanhthi.com/docs/cli/cf-memory/")}`
|
|
543
|
+
);
|
|
505
544
|
}
|
|
506
545
|
async function setupMemoryMcp(memoryDir, _mcpDir) {
|
|
507
546
|
const mcpStatus = getMemoryMcpStatus();
|
|
@@ -509,20 +548,10 @@ async function setupMemoryMcp(memoryDir, _mcpDir) {
|
|
|
509
548
|
log.info(`MCP: ${chalk.green("already configured")} in .mcp.json`);
|
|
510
549
|
return;
|
|
511
550
|
}
|
|
512
|
-
|
|
513
|
-
log.step("MCP setup");
|
|
551
|
+
writeMemoryMcpEntry(memoryDir);
|
|
514
552
|
log.dim(
|
|
515
|
-
"
|
|
553
|
+
" Connects Claude Code to memory so skills can store and retrieve knowledge."
|
|
516
554
|
);
|
|
517
|
-
const addMcp = await confirm({
|
|
518
|
-
message: "Add coding-friend-memory to .mcp.json?",
|
|
519
|
-
default: true
|
|
520
|
-
});
|
|
521
|
-
if (!addMcp) {
|
|
522
|
-
log.dim('Skipped. Run "cf memory mcp" anytime to get the config.');
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
writeMemoryMcpEntry(memoryDir);
|
|
526
555
|
}
|
|
527
556
|
async function memoryInitCommand() {
|
|
528
557
|
const memoryDir = getMemoryDir();
|
|
@@ -541,9 +570,8 @@ async function memoryInitCommand() {
|
|
|
541
570
|
return;
|
|
542
571
|
}
|
|
543
572
|
console.log();
|
|
544
|
-
printBanner("\u{1F9E0} Memory Setup");
|
|
573
|
+
printBanner("\u{1F9E0} CF Memory Setup");
|
|
545
574
|
console.log();
|
|
546
|
-
showConfigHint();
|
|
547
575
|
await memoryInitWizard(memoryDir, mcpDir);
|
|
548
576
|
}
|
|
549
577
|
async function memoryConfigCommand() {
|
|
@@ -808,6 +836,21 @@ async function memoryMcpCommand() {
|
|
|
808
836
|
ensureMemoryBuilt(mcpDir);
|
|
809
837
|
warnStaleMcpJson(memoryDir);
|
|
810
838
|
printMemoryMcpConfig(memoryDir);
|
|
839
|
+
const localMcpPath = join(process.cwd(), ".mcp.json");
|
|
840
|
+
const memoryDistPath = join(mcpDir, "dist", "index.js");
|
|
841
|
+
let isDaemonRunning = async () => false;
|
|
842
|
+
try {
|
|
843
|
+
const proc = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
844
|
+
isDaemonRunning = proc.isDaemonRunning;
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
847
|
+
const memoryHealth = await checkMemoryMcpHealth({
|
|
848
|
+
readMcpJson: () => readJson(localMcpPath),
|
|
849
|
+
pathExists: existsSync,
|
|
850
|
+
isDaemonRunning,
|
|
851
|
+
memoryDistPath
|
|
852
|
+
});
|
|
853
|
+
printHealthSection(memoryHealth);
|
|
811
854
|
}
|
|
812
855
|
|
|
813
856
|
export {
|
|
@@ -21,7 +21,7 @@ ${MARKER_START}
|
|
|
21
21
|
_cf_completions() {
|
|
22
22
|
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
23
23
|
local prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
24
|
-
local commands="install uninstall disable enable init config host mcp memory permission statusline update status dev session"
|
|
24
|
+
local commands="install uninstall disable enable init config host mcp memory permission statusline update status clean dev session"
|
|
25
25
|
local scope_flags="--user --global --project --local"
|
|
26
26
|
local update_flags="--cli --plugin --statusline --user --global --project --local"
|
|
27
27
|
|
|
@@ -89,6 +89,7 @@ var ZSH_FUNCTION_BODY = `_cf() {
|
|
|
89
89
|
'update:Update coding-friend plugin and refresh statusline'
|
|
90
90
|
'dev:Switch between local and remote plugin for development'
|
|
91
91
|
'status:Show comprehensive Coding Friend status'
|
|
92
|
+
'clean:Clean files generated by Coding Friend in docs/'
|
|
92
93
|
'session:Save and load Claude Code sessions across machines'
|
|
93
94
|
)
|
|
94
95
|
|