coding-friend-cli 1.18.0 → 1.20.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 +24 -1
- package/dist/{chunk-YC6MBHCT.js → chunk-JFGLNTZI.js} +13 -0
- package/dist/{chunk-VUAUAO2R.js → chunk-NEQZP5D4.js} +10 -6
- package/dist/{chunk-A427XMWE.js → chunk-PHQK2MMO.js} +4 -2
- package/dist/{chunk-KTX4MGMR.js → chunk-QMD7P67N.js} +24 -4
- package/dist/chunk-WEMDLEK5.js +331 -0
- package/dist/{config-6SBGNTAQ.js → config-UQXY45DN.js} +6 -244
- package/dist/{dev-MC6TGHRT.js → dev-7DLYIXBO.js} +2 -2
- package/dist/{disable-R6K5YJN4.js → disable-XYZRE3TD.js} +1 -1
- package/dist/{enable-HF4PYVJN.js → enable-3NZBQWLQ.js} +1 -1
- package/dist/{host-SYZH3FVC.js → host-QDWBFJB2.js} +1 -1
- package/dist/index.js +37 -28
- package/dist/{init-2HLPKYXB.js → init-ONUC6QMM.js} +1 -1
- package/dist/{install-3QCRGPTY.js → install-35IWHBIS.js} +3 -3
- package/dist/{mcp-TBEDYELW.js → mcp-GFIOFXOL.js} +1 -1
- package/dist/{memory-BQK2R7BV.js → memory-47RXG7VL.js} +158 -26
- package/dist/postinstall.js +1 -1
- package/dist/{session-H4XW2WXH.js → session-JGRF5SNX.js} +1 -1
- package/dist/status-SENJZQ3G.js +226 -0
- package/dist/{uninstall-5LRHXFSF.js → uninstall-NNCEKPIE.js} +2 -2
- package/dist/{update-4YUSCBCB.js → update-NZ2HRWEN.js} +6 -2
- package/lib/cf-memory/CHANGELOG.md +4 -0
- package/lib/cf-memory/README.md +29 -1
- package/lib/cf-memory/package.json +1 -1
- package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +41 -1
- package/lib/cf-memory/src/backends/markdown.ts +28 -12
- package/lib/cf-memory/src/lib/types.ts +1 -0
- package/lib/cf-memory/src/tools/store.ts +23 -5
- package/lib/learn-host/CHANGELOG.md +5 -0
- package/lib/learn-host/package.json +1 -1
- package/lib/learn-host/src/app/[category]/[slug]/page.tsx +4 -3
- package/lib/learn-host/src/components/Breadcrumbs.tsx +1 -1
- package/lib/learn-host/src/components/DocCard.tsx +4 -1
- package/lib/learn-host/src/components/Sidebar.tsx +1 -1
- package/package.json +1 -1
|
@@ -1,15 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
editMemoryAutoCapture,
|
|
3
|
+
editMemoryAutoStart,
|
|
4
|
+
editMemoryDaemonTimeout,
|
|
5
|
+
editMemoryEmbedding,
|
|
6
|
+
editMemoryTier,
|
|
7
|
+
memoryConfigMenu
|
|
8
|
+
} from "./chunk-WEMDLEK5.js";
|
|
1
9
|
import {
|
|
2
10
|
loadConfig,
|
|
3
11
|
resolveMemoryDir
|
|
4
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-QMD7P67N.js";
|
|
5
13
|
import {
|
|
6
14
|
getLibPath
|
|
7
15
|
} from "./chunk-RZRT7NGT.js";
|
|
8
16
|
import "./chunk-POC2WHU2.js";
|
|
9
17
|
import {
|
|
10
|
-
|
|
18
|
+
showConfigHint
|
|
19
|
+
} from "./chunk-C5LYVVEI.js";
|
|
20
|
+
import {
|
|
21
|
+
run,
|
|
22
|
+
runWithStderr
|
|
11
23
|
} from "./chunk-CYQU33FY.js";
|
|
12
|
-
import
|
|
24
|
+
import {
|
|
25
|
+
globalConfigPath,
|
|
26
|
+
localConfigPath,
|
|
27
|
+
readJson
|
|
28
|
+
} from "./chunk-RWUTFVRB.js";
|
|
13
29
|
import {
|
|
14
30
|
log
|
|
15
31
|
} from "./chunk-W5CD7WTX.js";
|
|
@@ -35,21 +51,32 @@ function countMdFiles(dir) {
|
|
|
35
51
|
function getMemoryDir(path) {
|
|
36
52
|
return resolveMemoryDir(path);
|
|
37
53
|
}
|
|
54
|
+
var MAX_ERROR_LINES = 30;
|
|
55
|
+
function truncateError(text) {
|
|
56
|
+
const lines = text.split("\n");
|
|
57
|
+
if (lines.length <= MAX_ERROR_LINES) return text;
|
|
58
|
+
const head = lines.slice(0, 20);
|
|
59
|
+
const tail = lines.slice(-8);
|
|
60
|
+
const skipped = lines.length - 28;
|
|
61
|
+
return [...head, ` ... (${skipped} lines omitted) ...`, ...tail].join("\n");
|
|
62
|
+
}
|
|
38
63
|
function ensureBuilt(mcpDir) {
|
|
39
64
|
if (!existsSync(join(mcpDir, "node_modules"))) {
|
|
40
65
|
log.step("Installing memory server dependencies (one-time setup)...");
|
|
41
|
-
const result =
|
|
42
|
-
if (result
|
|
66
|
+
const result = runWithStderr("npm", ["install"], { cwd: mcpDir });
|
|
67
|
+
if (result.exitCode !== 0) {
|
|
43
68
|
log.error("Failed to install dependencies");
|
|
69
|
+
if (result.stderr) log.error(truncateError(result.stderr));
|
|
44
70
|
process.exit(1);
|
|
45
71
|
}
|
|
46
72
|
log.success("Done.");
|
|
47
73
|
}
|
|
48
74
|
if (!existsSync(join(mcpDir, "dist"))) {
|
|
49
75
|
log.step("Building memory server...");
|
|
50
|
-
const result =
|
|
51
|
-
if (result
|
|
76
|
+
const result = runWithStderr("npm", ["run", "build"], { cwd: mcpDir });
|
|
77
|
+
if (result.exitCode !== 0) {
|
|
52
78
|
log.error("Failed to build memory server");
|
|
79
|
+
if (result.stderr) log.error(truncateError(result.stderr));
|
|
53
80
|
process.exit(1);
|
|
54
81
|
}
|
|
55
82
|
log.success("Done.");
|
|
@@ -82,7 +109,7 @@ async function memoryStatusCommand() {
|
|
|
82
109
|
console.log();
|
|
83
110
|
log.info(`Tier: ${tierLabel}`);
|
|
84
111
|
log.info(`Memory dir: ${chalk.cyan(memoryDir)}`);
|
|
85
|
-
log.info(`Memories: ${chalk.green(String(docCount))}`);
|
|
112
|
+
log.info(`Memories in this dir: ${chalk.green(String(docCount))}`);
|
|
86
113
|
if (running && daemonInfo) {
|
|
87
114
|
const uptime = (Date.now() - daemonInfo.startedAt) / 1e3;
|
|
88
115
|
log.info(
|
|
@@ -104,6 +131,17 @@ async function memoryStatusCommand() {
|
|
|
104
131
|
`SQLite deps: ${chalk.dim("not installed")} (run "cf memory init" to enable Tier 1)`
|
|
105
132
|
);
|
|
106
133
|
}
|
|
134
|
+
const config = loadConfig();
|
|
135
|
+
const embeddingConfig = config.memory?.embedding;
|
|
136
|
+
if (embeddingConfig?.provider || embeddingConfig?.model) {
|
|
137
|
+
const provider = embeddingConfig.provider ?? "transformers";
|
|
138
|
+
const model = embeddingConfig.model ?? (provider === "ollama" ? "all-minilm:l6-v2" : "Xenova/all-MiniLM-L6-v2");
|
|
139
|
+
log.info(`Embedding: ${chalk.cyan(model)} ${chalk.dim(`(${provider})`)}`);
|
|
140
|
+
}
|
|
141
|
+
const autoCapture = config.memory?.autoCapture ?? false;
|
|
142
|
+
log.info(
|
|
143
|
+
`Auto-capture: ${autoCapture ? chalk.green("on") : chalk.dim("off")}`
|
|
144
|
+
);
|
|
107
145
|
if (existsSync(memoryDir)) {
|
|
108
146
|
const categories = readdirSync(memoryDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
|
|
109
147
|
const catCount = countMdFiles(join(memoryDir, d.name));
|
|
@@ -286,11 +324,95 @@ async function memoryRebuildCommand() {
|
|
|
286
324
|
log.error("Rebuild failed or not supported.");
|
|
287
325
|
}
|
|
288
326
|
}
|
|
327
|
+
function getDbPath(memoryDir) {
|
|
328
|
+
try {
|
|
329
|
+
const resolved = resolve(memoryDir).replace(/\/+$/, "");
|
|
330
|
+
const stripped = resolved.replace(/\/docs\/memory$/, "").replace(/\/memory$/, "");
|
|
331
|
+
const id = stripped.replace(/\//g, "-");
|
|
332
|
+
const home = homedir();
|
|
333
|
+
const dbPath = join(
|
|
334
|
+
home,
|
|
335
|
+
".coding-friend",
|
|
336
|
+
"memory",
|
|
337
|
+
"projects",
|
|
338
|
+
id,
|
|
339
|
+
"db.sqlite"
|
|
340
|
+
);
|
|
341
|
+
return existsSync(dbPath) ? dbPath : null;
|
|
342
|
+
} catch {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
var em = chalk.hex("#10b981");
|
|
289
347
|
async function memoryInitCommand() {
|
|
290
348
|
const memoryDir = getMemoryDir();
|
|
291
349
|
const mcpDir = getLibPath("cf-memory");
|
|
292
350
|
ensureBuilt(mcpDir);
|
|
293
|
-
|
|
351
|
+
const dbExists = getDbPath(memoryDir) !== null;
|
|
352
|
+
if (dbExists) {
|
|
353
|
+
console.log();
|
|
354
|
+
log.info(
|
|
355
|
+
"Memory already initialized. Opening config menu to adjust settings."
|
|
356
|
+
);
|
|
357
|
+
log.dim('To re-import memories, run "cf memory rebuild".');
|
|
358
|
+
console.log();
|
|
359
|
+
await memoryConfigMenu({ exitLabel: "Done" });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
console.log();
|
|
363
|
+
console.log(em(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
364
|
+
console.log(
|
|
365
|
+
em(" \u2502 ") + "\u{1F9E0}" + em(" ") + chalk.bold.white("Memory Setup") + em(" \u2502")
|
|
366
|
+
);
|
|
367
|
+
console.log(em(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
368
|
+
console.log(em(" \u2570\u2500\u25B8"));
|
|
369
|
+
console.log();
|
|
370
|
+
showConfigHint();
|
|
371
|
+
log.step("Step 1/5: Search tier");
|
|
372
|
+
await editMemoryTier(
|
|
373
|
+
readJson(globalConfigPath()),
|
|
374
|
+
readJson(localConfigPath())
|
|
375
|
+
);
|
|
376
|
+
console.log();
|
|
377
|
+
log.step("Step 2/5: Embedding provider");
|
|
378
|
+
await editMemoryEmbedding(
|
|
379
|
+
readJson(globalConfigPath()),
|
|
380
|
+
readJson(localConfigPath())
|
|
381
|
+
);
|
|
382
|
+
console.log();
|
|
383
|
+
log.step("Step 3/5: Auto-capture");
|
|
384
|
+
await editMemoryAutoCapture(
|
|
385
|
+
readJson(globalConfigPath()),
|
|
386
|
+
readJson(localConfigPath())
|
|
387
|
+
);
|
|
388
|
+
console.log();
|
|
389
|
+
log.step("Step 4/5: Auto-start daemon");
|
|
390
|
+
await editMemoryAutoStart(
|
|
391
|
+
readJson(globalConfigPath()),
|
|
392
|
+
readJson(localConfigPath())
|
|
393
|
+
);
|
|
394
|
+
console.log();
|
|
395
|
+
log.step("Step 5/5: Daemon idle timeout");
|
|
396
|
+
await editMemoryDaemonTimeout(
|
|
397
|
+
readJson(globalConfigPath()),
|
|
398
|
+
readJson(localConfigPath())
|
|
399
|
+
);
|
|
400
|
+
console.log();
|
|
401
|
+
const config = loadConfig();
|
|
402
|
+
const tier = config.memory?.tier ?? "auto";
|
|
403
|
+
if (tier === "markdown") {
|
|
404
|
+
log.success(
|
|
405
|
+
'Memory initialized with Tier 3 (markdown). Run "cf memory status" to verify.'
|
|
406
|
+
);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (tier === "lite") {
|
|
410
|
+
log.success(
|
|
411
|
+
'Memory initialized. Run "cf memory start-daemon" to enable Tier 2 search.'
|
|
412
|
+
);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
log.step("Installing SQLite dependencies...");
|
|
294
416
|
const { ensureDeps, areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
|
|
295
417
|
if (areSqliteDepsAvailable()) {
|
|
296
418
|
log.info("SQLite dependencies already installed.");
|
|
@@ -299,32 +421,32 @@ async function memoryInitCommand() {
|
|
|
299
421
|
onProgress: (msg) => log.step(msg)
|
|
300
422
|
});
|
|
301
423
|
if (!installed) {
|
|
302
|
-
log.error("Failed to install dependencies.");
|
|
424
|
+
log.error("Failed to install SQLite dependencies.");
|
|
303
425
|
log.dim(
|
|
304
426
|
"Ensure you have a C++ compiler installed (Xcode CLT on macOS, build-essential on Linux)."
|
|
305
427
|
);
|
|
306
|
-
|
|
428
|
+
log.dim(
|
|
429
|
+
'Memory will fall back to a lower tier. You can retry later with "cf memory init".'
|
|
430
|
+
);
|
|
431
|
+
return;
|
|
307
432
|
}
|
|
308
433
|
log.success("Dependencies installed.");
|
|
309
434
|
}
|
|
310
435
|
if (!existsSync(memoryDir)) {
|
|
311
|
-
log.info(
|
|
312
|
-
|
|
313
|
-
"Tier 1 is ready. Memories will be indexed as they're created."
|
|
436
|
+
log.info(
|
|
437
|
+
"No memory directory found. Memories will be indexed as they're created."
|
|
314
438
|
);
|
|
439
|
+
log.success('Memory initialized. Run "cf memory status" to verify.');
|
|
315
440
|
return;
|
|
316
441
|
}
|
|
317
442
|
const docCount = countMdFiles(memoryDir);
|
|
318
443
|
if (docCount === 0) {
|
|
319
444
|
log.info("No existing memories to import.");
|
|
320
|
-
log.success(
|
|
321
|
-
"Tier 1 is ready. Memories will be indexed as they're created."
|
|
322
|
-
);
|
|
445
|
+
log.success('Memory initialized. Run "cf memory status" to verify.');
|
|
323
446
|
return;
|
|
324
447
|
}
|
|
325
448
|
log.step(`Importing ${docCount} existing memories into SQLite...`);
|
|
326
449
|
const { SqliteBackend } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
|
|
327
|
-
const config = loadConfig();
|
|
328
450
|
const embedding = config.memory?.embedding;
|
|
329
451
|
const backend = new SqliteBackend(memoryDir, {
|
|
330
452
|
skipVec: false,
|
|
@@ -334,21 +456,30 @@ async function memoryInitCommand() {
|
|
|
334
456
|
await backend.rebuild();
|
|
335
457
|
const stats = await backend.stats();
|
|
336
458
|
log.success(`Imported ${stats.total} memories. DB: ${backend.getDbPath()}`);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
log.info(
|
|
341
|
-
`Vector search: ${chalk.dim("disabled")} (sqlite-vec not available)`
|
|
342
|
-
);
|
|
343
|
-
}
|
|
459
|
+
log.info(
|
|
460
|
+
`Vector search: ${backend.isVecEnabled() ? chalk.green("enabled") : chalk.dim("disabled (sqlite-vec not available)")}`
|
|
461
|
+
);
|
|
344
462
|
} finally {
|
|
345
463
|
await backend.close();
|
|
346
464
|
}
|
|
347
|
-
log
|
|
465
|
+
console.log();
|
|
466
|
+
log.success('Memory initialized! Run "cf memory status" to verify.');
|
|
348
467
|
log.info(
|
|
349
468
|
`Tip: Run ${chalk.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
|
|
350
469
|
);
|
|
351
470
|
}
|
|
471
|
+
async function memoryConfigCommand() {
|
|
472
|
+
console.log();
|
|
473
|
+
console.log(em(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
474
|
+
console.log(
|
|
475
|
+
em(" \u2502 ") + "\u{1F9E0}" + em(" ") + chalk.bold.white("Memory Config") + em(" \u2502")
|
|
476
|
+
);
|
|
477
|
+
console.log(em(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
478
|
+
console.log(em(" \u2570\u2500\u25B8"));
|
|
479
|
+
console.log();
|
|
480
|
+
showConfigHint();
|
|
481
|
+
await memoryConfigMenu({ exitLabel: "Done" });
|
|
482
|
+
}
|
|
352
483
|
function getProjectsBaseDir() {
|
|
353
484
|
const home = homedir();
|
|
354
485
|
return join(home, ".coding-friend", "memory", "projects");
|
|
@@ -637,6 +768,7 @@ Transport: stdio
|
|
|
637
768
|
`);
|
|
638
769
|
}
|
|
639
770
|
export {
|
|
771
|
+
memoryConfigCommand,
|
|
640
772
|
memoryInitCommand,
|
|
641
773
|
memoryListCommand,
|
|
642
774
|
memoryMcpCommand,
|
package/dist/postinstall.js
CHANGED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveMemoryDir
|
|
3
|
+
} from "./chunk-QMD7P67N.js";
|
|
4
|
+
import {
|
|
5
|
+
getCliVersion,
|
|
6
|
+
getLatestCliVersion,
|
|
7
|
+
getLatestVersion,
|
|
8
|
+
semverCompare
|
|
9
|
+
} from "./chunk-PHQK2MMO.js";
|
|
10
|
+
import {
|
|
11
|
+
detectPluginScope,
|
|
12
|
+
isPluginDisabled
|
|
13
|
+
} from "./chunk-JFGLNTZI.js";
|
|
14
|
+
import {
|
|
15
|
+
getExistingRules
|
|
16
|
+
} from "./chunk-7CAIGH2Y.js";
|
|
17
|
+
import {
|
|
18
|
+
getLibPath
|
|
19
|
+
} from "./chunk-RZRT7NGT.js";
|
|
20
|
+
import {
|
|
21
|
+
getInstalledVersion
|
|
22
|
+
} from "./chunk-ORACWEDN.js";
|
|
23
|
+
import "./chunk-POC2WHU2.js";
|
|
24
|
+
import "./chunk-NEQZP5D4.js";
|
|
25
|
+
import "./chunk-C5LYVVEI.js";
|
|
26
|
+
import "./chunk-CYQU33FY.js";
|
|
27
|
+
import {
|
|
28
|
+
claudeSettingsPath,
|
|
29
|
+
devStatePath,
|
|
30
|
+
globalConfigPath,
|
|
31
|
+
localConfigPath,
|
|
32
|
+
readJson
|
|
33
|
+
} from "./chunk-RWUTFVRB.js";
|
|
34
|
+
import "./chunk-W5CD7WTX.js";
|
|
35
|
+
|
|
36
|
+
// src/commands/status.ts
|
|
37
|
+
import { existsSync, readdirSync } from "fs";
|
|
38
|
+
import { join } from "path";
|
|
39
|
+
import chalk from "chalk";
|
|
40
|
+
var VERSION_COL = 11;
|
|
41
|
+
var CONFIG_KEY_COL = 16;
|
|
42
|
+
var CONFIG_SUB_COL = 16;
|
|
43
|
+
function countMdFiles(dir) {
|
|
44
|
+
if (!existsSync(dir)) return 0;
|
|
45
|
+
let count = 0;
|
|
46
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
count += countMdFiles(join(dir, entry.name));
|
|
49
|
+
} else if (entry.name.endsWith(".md") && entry.name !== "README.md") {
|
|
50
|
+
count++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return count;
|
|
54
|
+
}
|
|
55
|
+
function formatUptime(seconds) {
|
|
56
|
+
if (seconds < 60) return `${Math.round(seconds)}s`;
|
|
57
|
+
if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
|
|
58
|
+
return `${Math.round(seconds / 3600)}h ${Math.round(seconds % 3600 / 60)}m`;
|
|
59
|
+
}
|
|
60
|
+
function formatScalar(value) {
|
|
61
|
+
if (typeof value === "string") return value;
|
|
62
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
63
|
+
if (typeof value === "number") return String(value);
|
|
64
|
+
return String(value);
|
|
65
|
+
}
|
|
66
|
+
function pad(label, width, color) {
|
|
67
|
+
const displayed = color ? color(label) : label;
|
|
68
|
+
return ` ${displayed}${" ".repeat(Math.max(1, width - label.length))}`;
|
|
69
|
+
}
|
|
70
|
+
function subLine(key, value, overrides, color) {
|
|
71
|
+
const displayed = color ? color(key) : key;
|
|
72
|
+
console.log(
|
|
73
|
+
` ${displayed}${" ".repeat(Math.max(1, CONFIG_SUB_COL - key.length))}${value}${overrides}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
function printConfig(obj, otherConfig) {
|
|
77
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
78
|
+
const overrides = otherConfig && key in otherConfig ? ` ${chalk.yellow("(overrides global)")}` : "";
|
|
79
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
80
|
+
console.log(pad(key, CONFIG_KEY_COL, chalk.cyan) + chalk.dim("\u2500") + overrides);
|
|
81
|
+
const nested = value;
|
|
82
|
+
const nestedOther = otherConfig && typeof otherConfig[key] === "object" ? otherConfig[key] : null;
|
|
83
|
+
for (const [subKey, subVal] of Object.entries(nested)) {
|
|
84
|
+
const subOverrides = nestedOther && subKey in nestedOther ? ` ${chalk.yellow("(overrides global)")}` : "";
|
|
85
|
+
if (subKey === "categories" && Array.isArray(subVal)) {
|
|
86
|
+
const names = subVal.map(
|
|
87
|
+
(c) => typeof c === "object" && c !== null && "name" in c ? c.name : String(c)
|
|
88
|
+
).join(", ");
|
|
89
|
+
subLine(subKey, names, subOverrides, chalk.cyan);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (typeof subVal === "object" && subVal !== null && !Array.isArray(subVal)) {
|
|
93
|
+
const innerEntries = Object.entries(
|
|
94
|
+
subVal
|
|
95
|
+
);
|
|
96
|
+
const inline = innerEntries.map(([k, v]) => `${k}: ${formatScalar(v)}`).join(", ");
|
|
97
|
+
subLine(subKey, inline, subOverrides, chalk.cyan);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(subVal)) {
|
|
101
|
+
subLine(
|
|
102
|
+
subKey,
|
|
103
|
+
subVal.map((v) => formatScalar(v)).join(", "),
|
|
104
|
+
subOverrides,
|
|
105
|
+
chalk.cyan
|
|
106
|
+
);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
subLine(subKey, formatScalar(subVal), subOverrides, chalk.cyan);
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(value)) {
|
|
114
|
+
console.log(
|
|
115
|
+
`${pad(key, CONFIG_KEY_COL, chalk.cyan)}${value.map((v) => formatScalar(v)).join(", ")}${overrides}`
|
|
116
|
+
);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
console.log(
|
|
120
|
+
`${pad(key, CONFIG_KEY_COL, chalk.cyan)}${formatScalar(value)}${overrides}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function versionLine(label, current, latest) {
|
|
125
|
+
const padded = pad(label, VERSION_COL);
|
|
126
|
+
if (!latest) {
|
|
127
|
+
return `${padded}${current} ${chalk.dim("(latest unavailable)")}`;
|
|
128
|
+
}
|
|
129
|
+
const cmp = semverCompare(current, latest);
|
|
130
|
+
const indicator = cmp >= 0 ? chalk.green("\u2714 up to date") : chalk.yellow("\u26A0 update available");
|
|
131
|
+
return `${padded}${current} \u2192 ${latest} ${indicator}`;
|
|
132
|
+
}
|
|
133
|
+
async function statusCommand() {
|
|
134
|
+
const pluginVersion = getInstalledVersion();
|
|
135
|
+
const latestPlugin = getLatestVersion();
|
|
136
|
+
const cliVersion = getCliVersion();
|
|
137
|
+
const latestCli = getLatestCliVersion();
|
|
138
|
+
console.log();
|
|
139
|
+
console.log(chalk.bold("\u{1F4E6} Versions"));
|
|
140
|
+
if (pluginVersion) {
|
|
141
|
+
console.log(versionLine("Plugin", pluginVersion, latestPlugin));
|
|
142
|
+
} else {
|
|
143
|
+
console.log(`${pad("Plugin", VERSION_COL)}${chalk.dim("not installed")}`);
|
|
144
|
+
}
|
|
145
|
+
console.log(versionLine("CLI", cliVersion, latestCli));
|
|
146
|
+
console.log();
|
|
147
|
+
console.log(chalk.bold("\u{1F527} Plugin"));
|
|
148
|
+
const detectedScope = detectPluginScope();
|
|
149
|
+
console.log(`${pad("Scope", VERSION_COL)}${detectedScope}`);
|
|
150
|
+
const disabled = isPluginDisabled(detectedScope);
|
|
151
|
+
console.log(
|
|
152
|
+
`${pad("Status", VERSION_COL)}${disabled ? chalk.yellow("disabled") : chalk.green("enabled")}`
|
|
153
|
+
);
|
|
154
|
+
try {
|
|
155
|
+
const claudeSettings = readJson(claudeSettingsPath());
|
|
156
|
+
const marketplaces = claudeSettings?.extraKnownMarketplaces;
|
|
157
|
+
const autoUpdate = marketplaces?.["coding-friend-marketplace"]?.autoUpdate === true;
|
|
158
|
+
console.log(
|
|
159
|
+
`${pad("Auto-update", VERSION_COL)}${autoUpdate ? chalk.green("on") : chalk.dim("off")}`
|
|
160
|
+
);
|
|
161
|
+
} catch {
|
|
162
|
+
console.log(`${pad("Auto-update", VERSION_COL)}${chalk.dim("unknown")}`);
|
|
163
|
+
}
|
|
164
|
+
const devState = readJson(devStatePath());
|
|
165
|
+
console.log(
|
|
166
|
+
`${pad("Dev mode", VERSION_COL)}${devState ? chalk.cyan("on") : chalk.dim("off")}`
|
|
167
|
+
);
|
|
168
|
+
const rules = getExistingRules(claudeSettingsPath());
|
|
169
|
+
console.log(
|
|
170
|
+
`${pad("Permissions", VERSION_COL)}${rules.length} rules ${chalk.dim('\u2192 Run "cf permission" for details')}`
|
|
171
|
+
);
|
|
172
|
+
console.log();
|
|
173
|
+
console.log(chalk.bold("\u{1F9E0} Memory"));
|
|
174
|
+
const memoryDir = resolveMemoryDir();
|
|
175
|
+
const docCount = countMdFiles(memoryDir);
|
|
176
|
+
let tierLabel = chalk.dim("unavailable");
|
|
177
|
+
let daemonLabel = chalk.dim("unavailable");
|
|
178
|
+
try {
|
|
179
|
+
const mcpDir = getLibPath("cf-memory");
|
|
180
|
+
if (existsSync(join(mcpDir, "dist"))) {
|
|
181
|
+
const { areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
|
|
182
|
+
const { isDaemonRunning, getDaemonInfo } = await import(join(mcpDir, "dist/daemon/process.js"));
|
|
183
|
+
const sqliteAvailable = areSqliteDepsAvailable();
|
|
184
|
+
const running = await isDaemonRunning();
|
|
185
|
+
if (sqliteAvailable) {
|
|
186
|
+
tierLabel = chalk.cyan("Tier 1 (SQLite + Hybrid)");
|
|
187
|
+
} else if (running) {
|
|
188
|
+
tierLabel = chalk.cyan("Tier 2 (MiniSearch + Daemon)");
|
|
189
|
+
} else {
|
|
190
|
+
tierLabel = chalk.cyan("Tier 3 (Markdown)");
|
|
191
|
+
}
|
|
192
|
+
if (running) {
|
|
193
|
+
const info = getDaemonInfo();
|
|
194
|
+
if (info) {
|
|
195
|
+
const uptime = (Date.now() - info.startedAt) / 1e3;
|
|
196
|
+
daemonLabel = `${chalk.green("running")} (PID ${info.pid}, uptime ${formatUptime(uptime)}) ${chalk.dim('\u2192 "cf memory stop-daemon" to stop')}`;
|
|
197
|
+
} else {
|
|
198
|
+
daemonLabel = `${chalk.green("running")} ${chalk.dim('\u2192 "cf memory stop-daemon" to stop')}`;
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
daemonLabel = `${chalk.dim("stopped")} ${chalk.dim('\u2192 "cf memory start-daemon" to start')}`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
}
|
|
206
|
+
console.log(`${pad("Tier", VERSION_COL)}${tierLabel}`);
|
|
207
|
+
console.log(`${pad("Daemon", VERSION_COL)}${daemonLabel}`);
|
|
208
|
+
console.log(`${pad("Documents", VERSION_COL)}${docCount} files`);
|
|
209
|
+
console.log(chalk.dim(` \u2192 Run "cf memory status" for details`));
|
|
210
|
+
const globalConfig = readJson(globalConfigPath());
|
|
211
|
+
const localConfig = readJson(localConfigPath());
|
|
212
|
+
if (globalConfig && Object.keys(globalConfig).length > 0) {
|
|
213
|
+
console.log();
|
|
214
|
+
console.log(chalk.bold(`\u2699\uFE0F Config (global: ${globalConfigPath()})`));
|
|
215
|
+
printConfig(globalConfig, null);
|
|
216
|
+
}
|
|
217
|
+
if (localConfig && Object.keys(localConfig).length > 0) {
|
|
218
|
+
console.log();
|
|
219
|
+
console.log(chalk.bold(`\u2699\uFE0F Config (local: ${localConfigPath()})`));
|
|
220
|
+
printConfig(localConfig, globalConfig);
|
|
221
|
+
}
|
|
222
|
+
console.log();
|
|
223
|
+
}
|
|
224
|
+
export {
|
|
225
|
+
statusCommand
|
|
226
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isMarketplaceRegistered,
|
|
3
3
|
isPluginInstalled
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-JFGLNTZI.js";
|
|
5
5
|
import {
|
|
6
6
|
hasShellCompletion,
|
|
7
7
|
removeShellCompletion
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-NEQZP5D4.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveScope
|
|
11
11
|
} from "./chunk-C5LYVVEI.js";
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
+
getCliVersion,
|
|
3
|
+
getLatestCliVersion,
|
|
2
4
|
getLatestVersion,
|
|
3
5
|
semverCompare,
|
|
4
6
|
updateCommand
|
|
5
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-PHQK2MMO.js";
|
|
6
8
|
import "./chunk-ORACWEDN.js";
|
|
7
9
|
import "./chunk-POC2WHU2.js";
|
|
8
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-NEQZP5D4.js";
|
|
9
11
|
import "./chunk-C5LYVVEI.js";
|
|
10
12
|
import "./chunk-CYQU33FY.js";
|
|
11
13
|
import "./chunk-RWUTFVRB.js";
|
|
12
14
|
import "./chunk-W5CD7WTX.js";
|
|
13
15
|
export {
|
|
16
|
+
getCliVersion,
|
|
17
|
+
getLatestCliVersion,
|
|
14
18
|
getLatestVersion,
|
|
15
19
|
semverCompare,
|
|
16
20
|
updateCommand
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# CF Memory Changelog
|
|
2
2
|
|
|
3
|
+
## v0.2.0 (2026-03-21)
|
|
4
|
+
|
|
5
|
+
- Add `index_only` option to `memory_store` MCP tool — skip file writing when file already exists on disk, enabling clean separation between file creation and indexing [#7f56711](https://github.com/dinhanhthi/coding-friend/commit/7f56711)
|
|
6
|
+
|
|
3
7
|
## v0.1.3 (2026-03-19)
|
|
4
8
|
|
|
5
9
|
- Use path-based project IDs instead of SHA256 hashes for human-readable project directories (e.g. `-Users-thi-git-foo` instead of `a1b2c3d4e5f6`) [#9c4cac0](https://github.com/dinhanhthi/coding-friend/commit/9c4cac0)
|
package/lib/cf-memory/README.md
CHANGED
|
@@ -265,9 +265,37 @@ The `cf` CLI exposes memory commands that use this package:
|
|
|
265
265
|
| `cf memory start-daemon` | Start the MiniSearch daemon (Tier 2) |
|
|
266
266
|
| `cf memory stop-daemon` | Stop the daemon |
|
|
267
267
|
| `cf memory rebuild` | Rebuild search index (Tier 1 direct or via daemon) |
|
|
268
|
-
| `cf memory init` | Install Tier 1 deps + import existing memories into SQLite
|
|
268
|
+
| `cf memory init` | Install Tier 1 deps + import existing memories into SQLite (see [prerequisites](#prerequisites-for-tier-1-on-linux)) |
|
|
269
269
|
| `cf memory mcp` | Print MCP server config for use in Claude Desktop / other clients |
|
|
270
270
|
|
|
271
|
+
## Prerequisites for Tier 1 on Linux
|
|
272
|
+
|
|
273
|
+
Tier 1 uses `better-sqlite3` and `sqlite-vec`, which are native Node.js modules requiring C++ compilation. On a fresh Linux install, you need build tools before running `cf memory init`:
|
|
274
|
+
|
|
275
|
+
**Ubuntu/Debian:**
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
sudo apt update
|
|
279
|
+
sudo apt install -y build-essential python3
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Fedora/RHEL:**
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
sudo dnf groupinstall "Development Tools"
|
|
286
|
+
sudo dnf install python3
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Arch Linux:**
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
sudo pacman -S base-devel python
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
If these are missing, `cf memory init` will fail at the "Installing SQLite dependencies" step. You can still use Tier 2 (lite) or Tier 3 (markdown) without native dependencies — choose them during the init wizard.
|
|
296
|
+
|
|
297
|
+
**macOS** users need Xcode Command Line Tools: `xcode-select --install`.
|
|
298
|
+
|
|
271
299
|
## Environment Variables
|
|
272
300
|
|
|
273
301
|
| Variable | Default | Description |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { mkdirSync, rmSync, existsSync, readFileSync } from "fs";
|
|
2
|
+
import { mkdirSync, rmSync, existsSync, readFileSync, readdirSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { tmpdir } from "os";
|
|
5
5
|
import matter from "gray-matter";
|
|
@@ -85,6 +85,46 @@ describe("MarkdownBackend", () => {
|
|
|
85
85
|
expect(m2.slug).toContain("api-authentication-pattern-");
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
+
it("index_only=true returns Memory without writing when file exists", async () => {
|
|
89
|
+
// Pre-create the file via normal store
|
|
90
|
+
const original = await backend.store(sampleInput);
|
|
91
|
+
const filePath = join(
|
|
92
|
+
testDir,
|
|
93
|
+
"features",
|
|
94
|
+
"api-authentication-pattern.md",
|
|
95
|
+
);
|
|
96
|
+
expect(existsSync(filePath)).toBe(true);
|
|
97
|
+
|
|
98
|
+
// Snapshot file content before index_only call
|
|
99
|
+
const contentBefore = readFileSync(filePath, "utf-8");
|
|
100
|
+
|
|
101
|
+
// Now store with index_only — should NOT create a second file
|
|
102
|
+
const indexed = await backend.store({ ...sampleInput, index_only: true });
|
|
103
|
+
|
|
104
|
+
// Should return clean slug (no timestamp suffix)
|
|
105
|
+
expect(indexed.slug).toBe("api-authentication-pattern");
|
|
106
|
+
expect(indexed.id).toBe("features/api-authentication-pattern");
|
|
107
|
+
expect(indexed.category).toBe("features");
|
|
108
|
+
expect(indexed.frontmatter.title).toBe(sampleInput.title);
|
|
109
|
+
expect(indexed.frontmatter.type).toBe(sampleInput.type);
|
|
110
|
+
expect(indexed.content).toBe(sampleInput.content);
|
|
111
|
+
|
|
112
|
+
// Verify no duplicate file was created (only one .md file in features/)
|
|
113
|
+
const files = readdirSync(join(testDir, "features")).filter((f: string) =>
|
|
114
|
+
f.endsWith(".md"),
|
|
115
|
+
);
|
|
116
|
+
expect(files.length).toBe(1);
|
|
117
|
+
|
|
118
|
+
// Verify existing file was not modified
|
|
119
|
+
expect(readFileSync(filePath, "utf-8")).toBe(contentBefore);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("index_only=true throws when file does not exist", async () => {
|
|
123
|
+
await expect(
|
|
124
|
+
backend.store({ ...sampleInput, index_only: true }),
|
|
125
|
+
).rejects.toThrow(/index_only.*file not found/i);
|
|
126
|
+
});
|
|
127
|
+
|
|
88
128
|
it("respects custom importance and source", async () => {
|
|
89
129
|
const memory = await backend.store({
|
|
90
130
|
...sampleInput,
|