coding-friend-cli 1.17.4 → 1.19.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.
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  applyPermissions,
3
3
  buildLearnDirRules,
4
+ cleanupStalePluginRules,
5
+ getAllRules,
4
6
  getExistingRules,
5
- getMissingRules
6
- } from "./chunk-56U7US6J.js";
7
+ getMissingRules,
8
+ logPluginScriptWarning
9
+ } from "./chunk-7CAIGH2Y.js";
7
10
  import {
8
11
  getLibPath
9
12
  } from "./chunk-RZRT7NGT.js";
@@ -20,7 +23,7 @@ import {
20
23
  import {
21
24
  ensureShellCompletion,
22
25
  hasShellCompletion
23
- } from "./chunk-DVMWMXDZ.js";
26
+ } from "./chunk-NEQZP5D4.js";
24
27
  import {
25
28
  BACK,
26
29
  applyDocsDirChange,
@@ -37,6 +40,7 @@ import {
37
40
  run
38
41
  } from "./chunk-CYQU33FY.js";
39
42
  import {
43
+ claudeLocalSettingsPath,
40
44
  claudeSettingsPath,
41
45
  globalConfigPath,
42
46
  localConfigPath,
@@ -71,7 +75,9 @@ function printBanner() {
71
75
  console.log();
72
76
  }
73
77
  var _stepIndex = 0;
78
+ var _suppressStepHeaders = false;
74
79
  function printStepHeader(label, description) {
80
+ if (_suppressStepHeaders) return;
75
81
  _stepIndex++;
76
82
  const line = chalk.hex("#10b981")("\u2500".repeat(44));
77
83
  console.log();
@@ -136,6 +142,14 @@ function printSetupStatus(globalCfg, localCfg, gitAvailable) {
136
142
  if (scope === "-") notConfiguredCount++;
137
143
  if (scope === "-" || scope === "global") notLocalCount++;
138
144
  }
145
+ const projectRules = getExistingRules(claudeLocalSettingsPath());
146
+ const userRules = getExistingRules(claudeSettingsPath());
147
+ const allRules = getAllRules();
148
+ const allRuleStrings = allRules.map((r) => r.rule);
149
+ const configuredRuleCount = (/* @__PURE__ */ new Set([
150
+ ...projectRules.filter((r) => allRuleStrings.includes(r)),
151
+ ...userRules.filter((r) => allRuleStrings.includes(r))
152
+ ])).size;
139
153
  const setupItems = [
140
154
  {
141
155
  label: ".gitignore",
@@ -144,7 +158,12 @@ function printSetupStatus(globalCfg, localCfg, gitAvailable) {
144
158
  },
145
159
  { label: "Shell completion", done: hasShellCompletion(), skipped: false },
146
160
  { label: "Statusline", done: isStatuslineConfigured(), skipped: false },
147
- { label: "CF Memory MCP", done: isMemoryMcpConfigured(), skipped: false }
161
+ { label: "CF Memory MCP", done: isMemoryMcpConfigured(), skipped: false },
162
+ {
163
+ label: `Permissions (${configuredRuleCount}/${allRules.length} rules)`,
164
+ done: configuredRuleCount > 0,
165
+ skipped: false
166
+ }
148
167
  ];
149
168
  for (const item of setupItems) {
150
169
  if (item.skipped) {
@@ -639,33 +658,57 @@ async function stepMemory(docsDir) {
639
658
  `Tip: Run ${chalk.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
640
659
  );
641
660
  }
642
- async function stepClaudePermissions(outputDir, autoCommit) {
643
- const resolved = resolvePath(outputDir);
644
- const homePath = resolved.startsWith(homedir()) ? resolved.replace(homedir(), "~") : resolved;
645
- const settingsPath = claudeSettingsPath();
646
- const existing = getExistingRules(settingsPath);
647
- if (existing.length === 0 && !readJson(settingsPath)) {
648
- log.warn(
649
- "~/.claude/settings.json not found. Create it via Claude Code settings first."
650
- );
661
+ async function stepClaudePermissions(externalLearnDir, autoCommit) {
662
+ const scope = await select({
663
+ message: "Where should permissions be saved?",
664
+ choices: [
665
+ {
666
+ name: "Project \u2014 .claude/settings.local.json (this project only, gitignored)",
667
+ value: "project"
668
+ },
669
+ {
670
+ name: "User \u2014 ~/.claude/settings.json (all projects)",
671
+ value: "user"
672
+ },
673
+ { name: "Skip", value: "skip" }
674
+ ]
675
+ });
676
+ if (scope === "skip") {
677
+ log.dim("Skipped. Run `cf permission` later to configure.");
651
678
  return;
652
679
  }
653
- const learnRules = buildLearnDirRules(homePath, autoCommit);
654
- const missing = getMissingRules(existing, learnRules);
680
+ const settingsPath = scope === "user" ? claudeSettingsPath() : claudeLocalSettingsPath();
681
+ const existing = getExistingRules(settingsPath);
682
+ const allRules = getAllRules();
683
+ const recommended = allRules.filter((r) => r.recommended);
684
+ let allToAdd = recommended;
685
+ if (externalLearnDir) {
686
+ const resolved = resolvePath(externalLearnDir);
687
+ const homePath = resolved.startsWith(homedir()) ? resolved.replace(homedir(), "~") : resolved;
688
+ const learnRules = buildLearnDirRules(homePath, autoCommit);
689
+ allToAdd = [...recommended, ...learnRules];
690
+ }
691
+ const missing = getMissingRules(existing, allToAdd);
655
692
  if (missing.length === 0) {
656
- log.dim("All permission rules already configured.");
693
+ log.dim("All recommended permission rules already configured.");
657
694
  return;
658
695
  }
659
- console.log("\nTo avoid repeated permission prompts, add these rules:");
696
+ log.step(`${missing.length} permission rules to add:`);
660
697
  for (const r of missing) {
661
- console.log(` ${r.rule}`);
698
+ console.log(` ${chalk.green("+")} ${r.rule}`);
662
699
  }
700
+ const hasPluginRule = missing.some((r) => r.category === "Plugin Scripts");
701
+ if (hasPluginRule) {
702
+ console.log();
703
+ logPluginScriptWarning(log, chalk);
704
+ }
705
+ console.log();
663
706
  const ok = await confirm({
664
- message: "Add these to ~/.claude/settings.json?",
707
+ message: `Add all ${missing.length} recommended rules?`,
665
708
  default: true
666
709
  });
667
710
  if (!ok) {
668
- log.dim("Skipped. You'll get prompted each time.");
711
+ log.dim("Skipped. Run `cf permission` later to configure interactively.");
669
712
  return;
670
713
  }
671
714
  applyPermissions(
@@ -673,8 +716,125 @@ async function stepClaudePermissions(outputDir, autoCommit) {
673
716
  missing.map((r) => r.rule),
674
717
  []
675
718
  );
719
+ const cleaned = cleanupStalePluginRules(settingsPath);
720
+ if (cleaned > 0) {
721
+ log.dim(`Removed ${cleaned} stale old-format plugin rules.`);
722
+ }
676
723
  log.success(`Added ${missing.length} permission rules.`);
677
- log.dim("For all plugin permissions, run: `cf permission`");
724
+ log.dim("Fine-tune later with: `cf permission` or `cf config` \u2192 Permissions");
725
+ }
726
+ async function initMenu(gitAvailable) {
727
+ while (true) {
728
+ const globalCfg = readJson(globalConfigPath());
729
+ const localCfg = readJson(localConfigPath());
730
+ const docsDir = getDocsDir(globalCfg, localCfg);
731
+ const docsDirScope = getScopeLabel("docsDir", globalCfg, localCfg);
732
+ const docsDirVal = getMergedValue("docsDir", globalCfg, localCfg);
733
+ const langScope = getScopeLabel("language", globalCfg, localCfg);
734
+ const langVal = getMergedValue("language", globalCfg, localCfg);
735
+ const learnScope = getScopeLabel("learn", globalCfg, localCfg);
736
+ const gitignoreStatus = !gitAvailable ? chalk.dim("skipped") : hasGitignoreBlock() ? chalk.green("configured") : chalk.yellow("not configured");
737
+ const completionStatus = hasShellCompletion() ? chalk.green("installed") : chalk.yellow("not installed");
738
+ const statuslineStatus = isStatuslineConfigured() ? chalk.green("configured") : chalk.yellow("not configured");
739
+ const memoryStatus = isMemoryMcpConfigured() ? chalk.green("configured") : chalk.yellow("not configured");
740
+ const projectRules = getExistingRules(claudeLocalSettingsPath());
741
+ const userRules = getExistingRules(claudeSettingsPath());
742
+ const allRules = getAllRules();
743
+ const allRuleStrings = allRules.map((r) => r.rule);
744
+ const configuredRuleCount = (/* @__PURE__ */ new Set([
745
+ ...projectRules.filter((r) => allRuleStrings.includes(r)),
746
+ ...userRules.filter((r) => allRuleStrings.includes(r))
747
+ ])).size;
748
+ const permissionStatus = configuredRuleCount > 0 ? chalk.green(`${configuredRuleCount}/${allRules.length}`) : chalk.yellow(`0/${allRules.length}`);
749
+ const choices = [
750
+ {
751
+ name: `Docs folder ${formatScopeLabel(docsDirScope)}${docsDirVal ? ` (${docsDirVal})` : ""}`,
752
+ value: "docsDir",
753
+ description: " Where plans, memory, research, and session docs are stored"
754
+ },
755
+ {
756
+ name: `.gitignore (${gitignoreStatus})`,
757
+ value: "gitignore",
758
+ description: " Add or update coding-friend artifacts in .gitignore",
759
+ ...gitAvailable ? {} : { disabled: "not in a git repo" }
760
+ },
761
+ {
762
+ name: `Docs language ${formatScopeLabel(langScope)}${langVal ? ` (${langVal})` : ""}`,
763
+ value: "language",
764
+ description: " Language for /cf-plan, /cf-ask, /cf-remember generated docs"
765
+ },
766
+ {
767
+ name: `/cf-learn config ${formatScopeLabel(learnScope)}`,
768
+ value: "learn",
769
+ description: " Output dir, language, categories, auto-commit, README index"
770
+ },
771
+ {
772
+ name: `Shell completion (${completionStatus})`,
773
+ value: "completion",
774
+ description: " Tab-complete for cf commands in your shell"
775
+ },
776
+ {
777
+ name: `Statusline (${statuslineStatus})`,
778
+ value: "statusline",
779
+ description: " Token count, model, and session info in your terminal prompt"
780
+ },
781
+ {
782
+ name: `CF Memory MCP (${memoryStatus})`,
783
+ value: "memory",
784
+ description: " Connect the memory system to Claude Code via MCP"
785
+ },
786
+ {
787
+ name: `Permissions (${permissionStatus} rules)`,
788
+ value: "permissions",
789
+ description: " Grant Coding Friend skills/hooks the permissions they need"
790
+ }
791
+ ];
792
+ const choice = await select({
793
+ message: "Which setting to configure?",
794
+ choices: injectBackChoice(choices, "Exit")
795
+ });
796
+ if (choice === BACK) {
797
+ log.dim("Done. Run `cf init` anytime to reconfigure.");
798
+ return;
799
+ }
800
+ _suppressStepHeaders = true;
801
+ switch (choice) {
802
+ case "docsDir":
803
+ await stepDocsDir(globalCfg, localCfg);
804
+ break;
805
+ case "gitignore":
806
+ await stepGitignore(docsDir);
807
+ break;
808
+ case "language":
809
+ await stepDocsLanguage(globalCfg, localCfg);
810
+ break;
811
+ case "learn":
812
+ await stepLearnConfig(globalCfg, localCfg, gitAvailable);
813
+ break;
814
+ case "completion":
815
+ await stepShellCompletion();
816
+ break;
817
+ case "statusline":
818
+ await stepStatusline();
819
+ break;
820
+ case "memory":
821
+ await stepMemory(docsDir);
822
+ break;
823
+ case "permissions": {
824
+ const learnCfg = localCfg?.learn ?? globalCfg?.learn;
825
+ const learnOutputDir = learnCfg?.outputDir || `${docsDir}/learn`;
826
+ const learnIsExternal = !learnOutputDir.startsWith(`${docsDir}/`);
827
+ const learnAutoCommit = learnCfg?.autoCommit || false;
828
+ await stepClaudePermissions(
829
+ learnIsExternal ? learnOutputDir : null,
830
+ learnAutoCommit
831
+ );
832
+ break;
833
+ }
834
+ }
835
+ _suppressStepHeaders = false;
836
+ console.log();
837
+ }
678
838
  }
679
839
  async function initCommand() {
680
840
  _stepIndex = 0;
@@ -685,11 +845,43 @@ async function initCommand() {
685
845
  log.warn("Not inside a git repo -- git-related steps will be skipped.");
686
846
  console.log();
687
847
  }
848
+ const alreadyInitialized = existsSync(join(process.cwd(), ".coding-friend"));
688
849
  const globalCfg = readJson(globalConfigPath());
689
850
  const localCfg = readJson(localConfigPath());
690
851
  console.log("Project status:");
691
852
  console.log();
692
853
  const { allDone, notLocalCount, notConfiguredCount, missingFolders } = printSetupStatus(globalCfg, localCfg, gitAvailable);
854
+ if (alreadyInitialized) {
855
+ if (allDone) {
856
+ if (notLocalCount > 0) {
857
+ console.log(
858
+ chalk.dim(
859
+ ` ${notLocalCount} setting(s) inherited from global config only.`
860
+ )
861
+ );
862
+ console.log();
863
+ }
864
+ log.success("All settings configured!");
865
+ console.log();
866
+ } else {
867
+ const parts = [];
868
+ if (missingFolders > 0) {
869
+ parts.push(`${missingFolders} folder(s) missing`);
870
+ }
871
+ if (notConfiguredCount > 0) {
872
+ parts.push(`${notConfiguredCount} setting(s) not configured`);
873
+ }
874
+ if (notLocalCount > 0) {
875
+ parts.push(`${notLocalCount} not set locally`);
876
+ }
877
+ if (parts.length > 0) {
878
+ console.log(` ${chalk.yellow("\u26A0")} ${parts.join(" \xB7 ")}`);
879
+ console.log();
880
+ }
881
+ }
882
+ await initMenu(gitAvailable);
883
+ return;
884
+ }
693
885
  if (allDone) {
694
886
  if (notLocalCount > 0) {
695
887
  console.log(
@@ -755,20 +947,13 @@ async function initCommand() {
755
947
  await stepShellCompletion();
756
948
  await stepStatusline();
757
949
  await stepMemory(docsDir);
758
- if (isExternal) {
759
- printStepHeader(
760
- "Configure Claude permissions",
761
- "Grants Claude read/write access to your external learn folder without repeated prompts."
762
- );
763
- await stepClaudePermissions(outputDir, autoCommit);
764
- } else {
765
- printStepHeader(
766
- `Configure Claude permissions ${chalk.dim("[skipped]")}`,
767
- "Grants Claude read/write access to your external learn folder without repeated prompts."
768
- );
769
- log.dim(
770
- "Skipped \u2014 learn directory is inside the project. Run `cf permission` for other permissions."
771
- );
950
+ printStepHeader(
951
+ "Configure Claude permissions",
952
+ "Grants Coding Friend skills/hooks the permissions they need, so you get fewer prompts."
953
+ );
954
+ await stepClaudePermissions(isExternal ? outputDir : null, autoCommit);
955
+ if (!existsSync(localConfigPath())) {
956
+ writeJson(localConfigPath(), {});
772
957
  }
773
958
  console.log();
774
959
  log.congrats("Setup complete!");
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  getLatestVersion,
3
3
  semverCompare
4
- } from "./chunk-UWQPMVJY.js";
4
+ } from "./chunk-IRPW2BMP.js";
5
5
  import {
6
6
  enableMarketplaceAutoUpdate,
7
7
  isMarketplaceRegistered,
8
8
  isPluginDisabled
9
- } from "./chunk-YC6MBHCT.js";
9
+ } from "./chunk-JFGLNTZI.js";
10
10
  import {
11
11
  getInstalledVersion
12
12
  } from "./chunk-ORACWEDN.js";
13
13
  import "./chunk-POC2WHU2.js";
14
14
  import {
15
15
  ensureShellCompletion
16
- } from "./chunk-DVMWMXDZ.js";
16
+ } from "./chunk-NEQZP5D4.js";
17
17
  import {
18
18
  resolveScope
19
19
  } from "./chunk-C5LYVVEI.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveDocsDir
3
- } from "./chunk-KTX4MGMR.js";
3
+ } from "./chunk-QMD7P67N.js";
4
4
  import {
5
5
  getLibPath
6
6
  } from "./chunk-RZRT7NGT.js";
@@ -1,15 +1,30 @@
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-KTX4MGMR.js";
12
+ } from "./chunk-QMD7P67N.js";
5
13
  import {
6
14
  getLibPath
7
15
  } from "./chunk-RZRT7NGT.js";
8
16
  import "./chunk-POC2WHU2.js";
17
+ import {
18
+ showConfigHint
19
+ } from "./chunk-C5LYVVEI.js";
9
20
  import {
10
21
  run
11
22
  } from "./chunk-CYQU33FY.js";
12
- import "./chunk-RWUTFVRB.js";
23
+ import {
24
+ globalConfigPath,
25
+ localConfigPath,
26
+ readJson
27
+ } from "./chunk-RWUTFVRB.js";
13
28
  import {
14
29
  log
15
30
  } from "./chunk-W5CD7WTX.js";
@@ -82,7 +97,7 @@ async function memoryStatusCommand() {
82
97
  console.log();
83
98
  log.info(`Tier: ${tierLabel}`);
84
99
  log.info(`Memory dir: ${chalk.cyan(memoryDir)}`);
85
- log.info(`Memories: ${chalk.green(String(docCount))}`);
100
+ log.info(`Memories in this dir: ${chalk.green(String(docCount))}`);
86
101
  if (running && daemonInfo) {
87
102
  const uptime = (Date.now() - daemonInfo.startedAt) / 1e3;
88
103
  log.info(
@@ -104,6 +119,17 @@ async function memoryStatusCommand() {
104
119
  `SQLite deps: ${chalk.dim("not installed")} (run "cf memory init" to enable Tier 1)`
105
120
  );
106
121
  }
122
+ const config = loadConfig();
123
+ const embeddingConfig = config.memory?.embedding;
124
+ if (embeddingConfig?.provider || embeddingConfig?.model) {
125
+ const provider = embeddingConfig.provider ?? "transformers";
126
+ const model = embeddingConfig.model ?? (provider === "ollama" ? "all-minilm:l6-v2" : "Xenova/all-MiniLM-L6-v2");
127
+ log.info(`Embedding: ${chalk.cyan(model)} ${chalk.dim(`(${provider})`)}`);
128
+ }
129
+ const autoCapture = config.memory?.autoCapture ?? false;
130
+ log.info(
131
+ `Auto-capture: ${autoCapture ? chalk.green("on") : chalk.dim("off")}`
132
+ );
107
133
  if (existsSync(memoryDir)) {
108
134
  const categories = readdirSync(memoryDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
109
135
  const catCount = countMdFiles(join(memoryDir, d.name));
@@ -286,11 +312,95 @@ async function memoryRebuildCommand() {
286
312
  log.error("Rebuild failed or not supported.");
287
313
  }
288
314
  }
315
+ function getDbPath(memoryDir) {
316
+ try {
317
+ const resolved = resolve(memoryDir).replace(/\/+$/, "");
318
+ const stripped = resolved.replace(/\/docs\/memory$/, "").replace(/\/memory$/, "");
319
+ const id = stripped.replace(/\//g, "-");
320
+ const home = homedir();
321
+ const dbPath = join(
322
+ home,
323
+ ".coding-friend",
324
+ "memory",
325
+ "projects",
326
+ id,
327
+ "db.sqlite"
328
+ );
329
+ return existsSync(dbPath) ? dbPath : null;
330
+ } catch {
331
+ return null;
332
+ }
333
+ }
334
+ var em = chalk.hex("#10b981");
289
335
  async function memoryInitCommand() {
290
336
  const memoryDir = getMemoryDir();
291
337
  const mcpDir = getLibPath("cf-memory");
292
338
  ensureBuilt(mcpDir);
293
- log.step("Initializing Tier 1 (SQLite + Hybrid Search)...");
339
+ const dbExists = getDbPath(memoryDir) !== null;
340
+ if (dbExists) {
341
+ console.log();
342
+ log.info(
343
+ "Memory already initialized. Opening config menu to adjust settings."
344
+ );
345
+ log.dim('To re-import memories, run "cf memory rebuild".');
346
+ console.log();
347
+ await memoryConfigMenu({ exitLabel: "Done" });
348
+ return;
349
+ }
350
+ console.log();
351
+ 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"));
352
+ console.log(
353
+ em(" \u2502 ") + "\u{1F9E0}" + em(" ") + chalk.bold.white("Memory Setup") + em(" \u2502")
354
+ );
355
+ 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"));
356
+ console.log(em(" \u2570\u2500\u25B8"));
357
+ console.log();
358
+ showConfigHint();
359
+ log.step("Step 1/5: Search tier");
360
+ await editMemoryTier(
361
+ readJson(globalConfigPath()),
362
+ readJson(localConfigPath())
363
+ );
364
+ console.log();
365
+ log.step("Step 2/5: Embedding provider");
366
+ await editMemoryEmbedding(
367
+ readJson(globalConfigPath()),
368
+ readJson(localConfigPath())
369
+ );
370
+ console.log();
371
+ log.step("Step 3/5: Auto-capture");
372
+ await editMemoryAutoCapture(
373
+ readJson(globalConfigPath()),
374
+ readJson(localConfigPath())
375
+ );
376
+ console.log();
377
+ log.step("Step 4/5: Auto-start daemon");
378
+ await editMemoryAutoStart(
379
+ readJson(globalConfigPath()),
380
+ readJson(localConfigPath())
381
+ );
382
+ console.log();
383
+ log.step("Step 5/5: Daemon idle timeout");
384
+ await editMemoryDaemonTimeout(
385
+ readJson(globalConfigPath()),
386
+ readJson(localConfigPath())
387
+ );
388
+ console.log();
389
+ const config = loadConfig();
390
+ const tier = config.memory?.tier ?? "auto";
391
+ if (tier === "markdown") {
392
+ log.success(
393
+ 'Memory initialized with Tier 3 (markdown). Run "cf memory status" to verify.'
394
+ );
395
+ return;
396
+ }
397
+ if (tier === "lite") {
398
+ log.success(
399
+ 'Memory initialized. Run "cf memory start-daemon" to enable Tier 2 search.'
400
+ );
401
+ return;
402
+ }
403
+ log.step("Installing SQLite dependencies...");
294
404
  const { ensureDeps, areSqliteDepsAvailable } = await import(join(mcpDir, "dist/lib/lazy-install.js"));
295
405
  if (areSqliteDepsAvailable()) {
296
406
  log.info("SQLite dependencies already installed.");
@@ -299,32 +409,32 @@ async function memoryInitCommand() {
299
409
  onProgress: (msg) => log.step(msg)
300
410
  });
301
411
  if (!installed) {
302
- log.error("Failed to install dependencies.");
412
+ log.error("Failed to install SQLite dependencies.");
303
413
  log.dim(
304
414
  "Ensure you have a C++ compiler installed (Xcode CLT on macOS, build-essential on Linux)."
305
415
  );
306
- process.exit(1);
416
+ log.dim(
417
+ 'Memory will fall back to a lower tier. You can retry later with "cf memory init".'
418
+ );
419
+ return;
307
420
  }
308
421
  log.success("Dependencies installed.");
309
422
  }
310
423
  if (!existsSync(memoryDir)) {
311
- log.info("No memory directory found. Nothing to import.");
312
- log.success(
313
- "Tier 1 is ready. Memories will be indexed as they're created."
424
+ log.info(
425
+ "No memory directory found. Memories will be indexed as they're created."
314
426
  );
427
+ log.success('Memory initialized. Run "cf memory status" to verify.');
315
428
  return;
316
429
  }
317
430
  const docCount = countMdFiles(memoryDir);
318
431
  if (docCount === 0) {
319
432
  log.info("No existing memories to import.");
320
- log.success(
321
- "Tier 1 is ready. Memories will be indexed as they're created."
322
- );
433
+ log.success('Memory initialized. Run "cf memory status" to verify.');
323
434
  return;
324
435
  }
325
436
  log.step(`Importing ${docCount} existing memories into SQLite...`);
326
437
  const { SqliteBackend } = await import(join(mcpDir, "dist/backends/sqlite/index.js"));
327
- const config = loadConfig();
328
438
  const embedding = config.memory?.embedding;
329
439
  const backend = new SqliteBackend(memoryDir, {
330
440
  skipVec: false,
@@ -334,21 +444,30 @@ async function memoryInitCommand() {
334
444
  await backend.rebuild();
335
445
  const stats = await backend.stats();
336
446
  log.success(`Imported ${stats.total} memories. DB: ${backend.getDbPath()}`);
337
- if (backend.isVecEnabled()) {
338
- log.info(`Vector search: ${chalk.green("enabled")}`);
339
- } else {
340
- log.info(
341
- `Vector search: ${chalk.dim("disabled")} (sqlite-vec not available)`
342
- );
343
- }
447
+ log.info(
448
+ `Vector search: ${backend.isVecEnabled() ? chalk.green("enabled") : chalk.dim("disabled (sqlite-vec not available)")}`
449
+ );
344
450
  } finally {
345
451
  await backend.close();
346
452
  }
347
- log.success('Tier 1 initialized. Run "cf memory status" to verify.');
453
+ console.log();
454
+ log.success('Memory initialized! Run "cf memory status" to verify.');
348
455
  log.info(
349
456
  `Tip: Run ${chalk.cyan("/cf-scan")} in Claude Code to populate memory with project knowledge.`
350
457
  );
351
458
  }
459
+ async function memoryConfigCommand() {
460
+ console.log();
461
+ 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"));
462
+ console.log(
463
+ em(" \u2502 ") + "\u{1F9E0}" + em(" ") + chalk.bold.white("Memory Config") + em(" \u2502")
464
+ );
465
+ 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"));
466
+ console.log(em(" \u2570\u2500\u25B8"));
467
+ console.log();
468
+ showConfigHint();
469
+ await memoryConfigMenu({ exitLabel: "Done" });
470
+ }
352
471
  function getProjectsBaseDir() {
353
472
  const home = homedir();
354
473
  return join(home, ".coding-friend", "memory", "projects");
@@ -637,6 +756,7 @@ Transport: stdio
637
756
  `);
638
757
  }
639
758
  export {
759
+ memoryConfigCommand,
640
760
  memoryInitCommand,
641
761
  memoryListCommand,
642
762
  memoryMcpCommand,