coding-friend-cli 1.17.0 → 1.17.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/index.js +10 -10
- package/dist/{init-YK6YRTOT.js → init-MF7ISADJ.js} +2 -2
- package/dist/{memory-7RM67ZLS.js → memory-3M2IY6MR.js} +13 -32
- package/lib/cf-memory/CHANGELOG.md +15 -1
- package/lib/cf-memory/package.json +1 -1
- package/lib/cf-memory/src/backends/markdown.ts +3 -1
- package/lib/cf-memory/src/daemon/process.ts +51 -0
- package/lib/cf-memory/src/index.ts +8 -0
- package/lib/learn-host/CHANGELOG.md +0 -4
- package/lib/learn-host/package.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ 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-MF7ISADJ.js");
|
|
34
34
|
await initCommand();
|
|
35
35
|
});
|
|
36
36
|
program.command("config").description("Manage Coding Friend configuration").action(async () => {
|
|
@@ -92,39 +92,39 @@ Memory subcommands:
|
|
|
92
92
|
memory mcp Show MCP server setup instructions`
|
|
93
93
|
);
|
|
94
94
|
memory.command("status").description("Show memory system status").action(async () => {
|
|
95
|
-
const { memoryStatusCommand } = await import("./memory-
|
|
95
|
+
const { memoryStatusCommand } = await import("./memory-3M2IY6MR.js");
|
|
96
96
|
await memoryStatusCommand();
|
|
97
97
|
});
|
|
98
98
|
memory.command("search").description("Search memories by query").argument("<query>", "search query").action(async (query) => {
|
|
99
|
-
const { memorySearchCommand } = await import("./memory-
|
|
99
|
+
const { memorySearchCommand } = await import("./memory-3M2IY6MR.js");
|
|
100
100
|
await memorySearchCommand(query);
|
|
101
101
|
});
|
|
102
102
|
memory.command("list").description(
|
|
103
103
|
"List memories in current project, or all projects with --projects"
|
|
104
104
|
).option("--projects", "List all project databases with size and metadata").action(async (opts) => {
|
|
105
|
-
const { memoryListCommand } = await import("./memory-
|
|
105
|
+
const { memoryListCommand } = await import("./memory-3M2IY6MR.js");
|
|
106
106
|
await memoryListCommand(opts);
|
|
107
107
|
});
|
|
108
108
|
memory.command("init").description(
|
|
109
109
|
"Initialize Tier 1 \u2014 install SQLite deps and import existing memories"
|
|
110
110
|
).action(async () => {
|
|
111
|
-
const { memoryInitCommand } = await import("./memory-
|
|
111
|
+
const { memoryInitCommand } = await import("./memory-3M2IY6MR.js");
|
|
112
112
|
await memoryInitCommand();
|
|
113
113
|
});
|
|
114
114
|
memory.command("start").description("Start the memory daemon (Tier 2 \u2014 MiniSearch)").action(async () => {
|
|
115
|
-
const { memoryStartCommand } = await import("./memory-
|
|
115
|
+
const { memoryStartCommand } = await import("./memory-3M2IY6MR.js");
|
|
116
116
|
await memoryStartCommand();
|
|
117
117
|
});
|
|
118
118
|
memory.command("stop").description("Stop the memory daemon").action(async () => {
|
|
119
|
-
const { memoryStopCommand } = await import("./memory-
|
|
119
|
+
const { memoryStopCommand } = await import("./memory-3M2IY6MR.js");
|
|
120
120
|
await memoryStopCommand();
|
|
121
121
|
});
|
|
122
122
|
memory.command("rebuild").description("Rebuild the daemon search index").action(async () => {
|
|
123
|
-
const { memoryRebuildCommand } = await import("./memory-
|
|
123
|
+
const { memoryRebuildCommand } = await import("./memory-3M2IY6MR.js");
|
|
124
124
|
await memoryRebuildCommand();
|
|
125
125
|
});
|
|
126
126
|
memory.command("mcp").description("Show MCP server setup instructions").action(async () => {
|
|
127
|
-
const { memoryMcpCommand } = await import("./memory-
|
|
127
|
+
const { memoryMcpCommand } = await import("./memory-3M2IY6MR.js");
|
|
128
128
|
await memoryMcpCommand();
|
|
129
129
|
});
|
|
130
130
|
memory.command("rm").description("Remove a project database").option("--project-id <id>", "Project ID to remove").option("--all", "Remove all project databases").option(
|
|
@@ -132,7 +132,7 @@ memory.command("rm").description("Remove a project database").option("--project-
|
|
|
132
132
|
"Remove orphaned projects (source dir missing or 0 memories)"
|
|
133
133
|
).action(
|
|
134
134
|
async (opts) => {
|
|
135
|
-
const { memoryRmCommand } = await import("./memory-
|
|
135
|
+
const { memoryRmCommand } = await import("./memory-3M2IY6MR.js");
|
|
136
136
|
await memoryRmCommand(opts);
|
|
137
137
|
}
|
|
138
138
|
);
|
|
@@ -636,7 +636,7 @@ async function stepMemory(docsDir) {
|
|
|
636
636
|
}
|
|
637
637
|
}
|
|
638
638
|
log.info(
|
|
639
|
-
`Tip: Run ${chalk.cyan("/cf-
|
|
639
|
+
`Tip: Run ${chalk.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
|
|
640
640
|
);
|
|
641
641
|
}
|
|
642
642
|
async function stepClaudePermissions(outputDir, autoCommit) {
|
|
@@ -773,7 +773,7 @@ async function initCommand() {
|
|
|
773
773
|
console.log();
|
|
774
774
|
log.congrats("Setup complete!");
|
|
775
775
|
log.dim(
|
|
776
|
-
"Available commands: /cf-ask, /cf-plan, /cf-fix, /cf-commit, /cf-review, /cf-ship, /cf-optimize, /cf-
|
|
776
|
+
"Available commands: /cf-ask, /cf-plan, /cf-fix, /cf-commit, /cf-review, /cf-ship, /cf-optimize, /cf-scan, /cf-remember, /cf-learn, /cf-research, /cf-session, /cf-help"
|
|
777
777
|
);
|
|
778
778
|
}
|
|
779
779
|
export {
|
|
@@ -19,7 +19,6 @@ import { createHash } from "crypto";
|
|
|
19
19
|
import { existsSync, readdirSync, statSync, rmSync } from "fs";
|
|
20
20
|
import { join, resolve, sep } from "path";
|
|
21
21
|
import { homedir } from "os";
|
|
22
|
-
import { spawn } from "child_process";
|
|
23
22
|
import { confirm } from "@inquirer/prompts";
|
|
24
23
|
import chalk from "chalk";
|
|
25
24
|
function countMdFiles(dir) {
|
|
@@ -88,7 +87,7 @@ async function memoryStatusCommand() {
|
|
|
88
87
|
if (running && daemonInfo) {
|
|
89
88
|
const uptime = (Date.now() - daemonInfo.startedAt) / 1e3;
|
|
90
89
|
log.info(
|
|
91
|
-
`Daemon: ${chalk.green("running")} (PID ${daemonInfo.pid}, uptime ${formatUptime(uptime)})`
|
|
90
|
+
`Daemon: ${chalk.green("running")} (PID ${daemonInfo.pid}, uptime ${formatUptime(uptime)}) ${chalk.dim('Turn it off by "cf memory stop"')}`
|
|
92
91
|
);
|
|
93
92
|
} else if (sqliteAvailable) {
|
|
94
93
|
log.info(
|
|
@@ -162,7 +161,7 @@ async function memoryListCommand(opts) {
|
|
|
162
161
|
"This folder has no memories yet. Use --projects to list all project databases."
|
|
163
162
|
);
|
|
164
163
|
log.dim(
|
|
165
|
-
'Or run "cf init" to set up this project, then "/cf-
|
|
164
|
+
'Or run "cf init" to set up this project, then "/cf-scan" in Claude Code.'
|
|
166
165
|
);
|
|
167
166
|
return;
|
|
168
167
|
}
|
|
@@ -209,43 +208,23 @@ async function memoryStartCommand() {
|
|
|
209
208
|
const memoryDir = getMemoryDir();
|
|
210
209
|
const mcpDir = getLibPath("cf-memory");
|
|
211
210
|
ensureBuilt(mcpDir);
|
|
212
|
-
const { isDaemonRunning, getDaemonInfo } = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
211
|
+
const { isDaemonRunning, getDaemonInfo, spawnDaemon } = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
213
212
|
if (await isDaemonRunning()) {
|
|
214
213
|
const info = getDaemonInfo();
|
|
215
214
|
log.info(`Daemon already running (PID ${info?.pid})`);
|
|
216
215
|
return;
|
|
217
216
|
}
|
|
218
|
-
const entryPath = join(mcpDir, "dist/daemon/entry.js");
|
|
219
|
-
if (!existsSync(entryPath)) {
|
|
220
|
-
log.error("Daemon entry point not found. Try rebuilding: npm run build");
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
217
|
log.step("Starting memory daemon...");
|
|
224
|
-
const args = [entryPath, memoryDir];
|
|
225
218
|
const config = loadConfig();
|
|
226
219
|
const embedding = config.memory?.embedding;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
stdio: "ignore",
|
|
235
|
-
env: { ...process.env }
|
|
236
|
-
});
|
|
237
|
-
child.unref();
|
|
238
|
-
for (let i = 0; i < 50; i++) {
|
|
239
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
240
|
-
if (await isDaemonRunning()) {
|
|
241
|
-
const info = getDaemonInfo();
|
|
242
|
-
log.success(`Daemon started (PID ${info?.pid})`);
|
|
243
|
-
log.info(`Tier: ${chalk.cyan("Tier 2 (MiniSearch + Daemon)")}`);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
220
|
+
const result = await spawnDaemon(memoryDir, embedding);
|
|
221
|
+
if (result) {
|
|
222
|
+
log.success(`Daemon started (PID ${result.pid})`);
|
|
223
|
+
log.info(`Watching ${chalk.cyan(memoryDir)} for changes`);
|
|
224
|
+
} else {
|
|
225
|
+
log.error("Daemon did not start within 3 seconds");
|
|
226
|
+
process.exit(1);
|
|
246
227
|
}
|
|
247
|
-
log.error("Daemon did not start within 5 seconds");
|
|
248
|
-
process.exit(1);
|
|
249
228
|
}
|
|
250
229
|
async function memoryStopCommand() {
|
|
251
230
|
const mcpDir = getLibPath("cf-memory");
|
|
@@ -368,7 +347,7 @@ async function memoryInitCommand() {
|
|
|
368
347
|
}
|
|
369
348
|
log.success('Tier 1 initialized. Run "cf memory status" to verify.');
|
|
370
349
|
log.info(
|
|
371
|
-
`Tip: Run ${chalk.cyan("/cf-
|
|
350
|
+
`Tip: Run ${chalk.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
|
|
372
351
|
);
|
|
373
352
|
}
|
|
374
353
|
function getProjectsBaseDir() {
|
|
@@ -383,6 +362,8 @@ function formatSize(bytes) {
|
|
|
383
362
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
384
363
|
}
|
|
385
364
|
function formatDate(raw) {
|
|
365
|
+
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(raw)) return raw;
|
|
366
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) return raw;
|
|
386
367
|
const d = new Date(raw);
|
|
387
368
|
if (isNaN(d.getTime())) return raw;
|
|
388
369
|
return d.toISOString().slice(0, 16).replace("T", " ");
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# CF Memory Changelog
|
|
2
2
|
|
|
3
|
-
## v0.
|
|
3
|
+
## v0.1.1 (2026-03-17)
|
|
4
|
+
|
|
5
|
+
- Fix `today()` to capture full timestamp (`YYYY-MM-DD HH:MM`) instead of date-only for memory created/updated fields [#31e0824](https://github.com/dinhanhthi/coding-friend/commit/31e0824)
|
|
6
|
+
|
|
7
|
+
## v0.1.0 (2026-03-17)
|
|
8
|
+
|
|
9
|
+
- Auto-start daemon from MCP server for file watching — daemon spawns automatically when Tier 1 or 2 is detected, no manual `cf memory start` needed [#2211b84](https://github.com/dinhanhthi/coding-friend/commit/2211b84)
|
|
10
|
+
- Add `spawnDaemon()` function to `daemon/process.ts` with dynamic path resolution via `import.meta.url` [#2211b84](https://github.com/dinhanhthi/coding-friend/commit/2211b84)
|
|
11
|
+
|
|
12
|
+
## v0.0.1 (2026-03-16)
|
|
4
13
|
|
|
5
14
|
- Add persistent memory system — MCP server with 6 tools (`memory_store`, `memory_search`, `memory_retrieve`, `memory_list`, `memory_update`, `memory_delete`) + 2 resources (`memory://index`, `memory://stats`)
|
|
6
15
|
- Add 3-tier graceful degradation: SQLite + hybrid search (Tier 1) → MiniSearch daemon + BM25/fuzzy (Tier 2) → Markdown file I/O (Tier 3)
|
|
@@ -13,3 +22,8 @@
|
|
|
13
22
|
- Add deduplication detection and temporal decay scoring
|
|
14
23
|
- Add frontmatter migration script for existing `docs/memory/` files
|
|
15
24
|
- Migrate `/cf-remember` and Frontmatter Recall to use `memory_search` MCP tool
|
|
25
|
+
- Add `cf memory rm` and `cf memory ls` commands for project database management [#1da99b1](https://github.com/dinhanhthi/coding-friend/commit/1da99b1)
|
|
26
|
+
- Add dynamic embedding dimensions support and config wiring [#b7f6eba](https://github.com/dinhanhthi/coding-friend/commit/b7f6eba)
|
|
27
|
+
- Fix correct embedding model name in v1→v2 migration [#04d3c19](https://github.com/dinhanhthi/coding-friend/commit/04d3c19)
|
|
28
|
+
- Fix empty project directories created on SQLite backend failure [#4ab7570](https://github.com/dinhanhthi/coding-friend/commit/4ab7570)
|
|
29
|
+
- Fix unit tests creating orphaned SQLite databases [#ec0bfee](https://github.com/dinhanhthi/coding-friend/commit/ec0bfee)
|
|
@@ -57,7 +57,9 @@ function parseFrontmatter(
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function today(): string {
|
|
60
|
-
|
|
60
|
+
const d = new Date();
|
|
61
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
62
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
export class MarkdownBackend implements MemoryBackend {
|
|
@@ -176,6 +176,57 @@ export function startDaemonServer(
|
|
|
176
176
|
return { close: shutdown, server };
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Spawn the daemon as a detached background process (if not already running).
|
|
181
|
+
*
|
|
182
|
+
* Resolves `daemon/entry.js` relative to this file so it works from both
|
|
183
|
+
* the MCP server and the CLI without hardcoding paths.
|
|
184
|
+
*/
|
|
185
|
+
export async function spawnDaemon(
|
|
186
|
+
docsDir: string,
|
|
187
|
+
embeddingConfig?: {
|
|
188
|
+
provider?: string;
|
|
189
|
+
model?: string;
|
|
190
|
+
ollamaUrl?: string;
|
|
191
|
+
},
|
|
192
|
+
): Promise<{ pid: number } | null> {
|
|
193
|
+
if (await isDaemonRunning()) return null;
|
|
194
|
+
|
|
195
|
+
const { spawn } = await import("node:child_process");
|
|
196
|
+
const { fileURLToPath } = await import("node:url");
|
|
197
|
+
|
|
198
|
+
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
199
|
+
const entryPath = path.join(thisDir, "entry.js");
|
|
200
|
+
|
|
201
|
+
if (!fs.existsSync(entryPath)) return null;
|
|
202
|
+
|
|
203
|
+
const args = [entryPath, docsDir];
|
|
204
|
+
if (embeddingConfig?.provider)
|
|
205
|
+
args.push(`--embedding-provider=${embeddingConfig.provider}`);
|
|
206
|
+
if (embeddingConfig?.model)
|
|
207
|
+
args.push(`--embedding-model=${embeddingConfig.model}`);
|
|
208
|
+
if (embeddingConfig?.ollamaUrl)
|
|
209
|
+
args.push(`--embedding-ollama-url=${embeddingConfig.ollamaUrl}`);
|
|
210
|
+
|
|
211
|
+
const child = spawn("node", args, {
|
|
212
|
+
detached: true,
|
|
213
|
+
stdio: "ignore",
|
|
214
|
+
env: { ...process.env },
|
|
215
|
+
});
|
|
216
|
+
child.unref();
|
|
217
|
+
|
|
218
|
+
// Wait for daemon to be ready (max 3 seconds)
|
|
219
|
+
for (let i = 0; i < 30; i++) {
|
|
220
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
221
|
+
if (await isDaemonRunning()) {
|
|
222
|
+
const info = getDaemonInfo();
|
|
223
|
+
return info ? { pid: info.pid } : null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
179
230
|
/**
|
|
180
231
|
* Stop the daemon by sending SIGTERM.
|
|
181
232
|
*/
|
|
@@ -33,6 +33,14 @@ const { backend, tier } = await createBackendForTier(
|
|
|
33
33
|
embeddingConfig,
|
|
34
34
|
);
|
|
35
35
|
|
|
36
|
+
// Auto-start daemon for file watching (Tier 1 & 2)
|
|
37
|
+
// Daemon watches docs/memory/ for external changes (git pull, manual edits)
|
|
38
|
+
// and rebuilds the search index automatically.
|
|
39
|
+
if (tier.name !== "markdown") {
|
|
40
|
+
const { spawnDaemon } = await import("./daemon/process.js");
|
|
41
|
+
spawnDaemon(docsDir, embeddingConfig).catch(() => {});
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
const server = new McpServer({
|
|
37
45
|
name: "coding-friend-memory",
|
|
38
46
|
version: "0.0.1",
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# Changelog (Learn Host)
|
|
2
2
|
|
|
3
|
-
## v0.2.2 (2026-03-16)
|
|
4
|
-
|
|
5
|
-
- Improve `cf init` to be more interactive with step-by-step guided setup ([#d5bbc9c](https://github.com/dinhanhthi/coding-friend/commit/d5bbc9c))
|
|
6
|
-
|
|
7
3
|
## v0.2.1 (2026-03-05)
|
|
8
4
|
|
|
9
5
|
- Fix TOC heading text stripping markdown links from slug generation ([#9a8fb5c](https://github.com/dinhanhthi/coding-friend/commit/9a8fb5c))
|