coding-friend-cli 1.16.0 → 1.17.1

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.
Files changed (73) hide show
  1. package/README.md +12 -0
  2. package/dist/{chunk-D4EWPGBL.js → chunk-C5LYVVEI.js} +1 -1
  3. package/dist/{chunk-X5WEODUD.js → chunk-CYQU33FY.js} +1 -0
  4. package/dist/{chunk-QNLL3ZDF.js → chunk-G6CEEMAR.js} +3 -3
  5. package/dist/{chunk-4DB4XTSL.js → chunk-KTX4MGMR.js} +15 -1
  6. package/dist/{chunk-KJUGTLPQ.js → chunk-YO6JKGR3.js} +38 -2
  7. package/dist/{config-AIZJJ5D2.js → config-LZFXXOI4.js} +276 -14
  8. package/dist/{dev-WJ5QQ35B.js → dev-R3IYWZ3M.js} +2 -2
  9. package/dist/{disable-JDVOQNZG.js → disable-R6K5YJN4.js} +2 -2
  10. package/dist/{enable-JBJ4Q2S7.js → enable-HF4PYVJN.js} +2 -2
  11. package/dist/{host-NA7LZ4HX.js → host-SYZH3FVC.js} +4 -4
  12. package/dist/index.js +78 -18
  13. package/dist/{init-FZ3GG53E.js → init-MF7ISADJ.js} +102 -6
  14. package/dist/{install-I3GOS56Q.js → install-Q4PWEU43.js} +4 -4
  15. package/dist/{mcp-DLS3J6QJ.js → mcp-TBEDYELW.js} +4 -4
  16. package/dist/memory-RGLM35HC.js +647 -0
  17. package/dist/postinstall.js +1 -1
  18. package/dist/{session-E3CZJJZQ.js → session-H4XW2WXH.js} +1 -1
  19. package/dist/{statusline-6HQCDWBD.js → statusline-6Y2EBAFQ.js} +1 -1
  20. package/dist/{uninstall-JN5YIKKM.js → uninstall-3PSUDGI4.js} +3 -3
  21. package/dist/{update-OWS4IJTG.js → update-WL6SFGGO.js} +4 -4
  22. package/lib/cf-memory/CHANGELOG.md +25 -0
  23. package/lib/cf-memory/README.md +284 -0
  24. package/lib/cf-memory/package-lock.json +2790 -0
  25. package/lib/cf-memory/package.json +31 -0
  26. package/lib/cf-memory/scripts/migrate-frontmatter.ts +134 -0
  27. package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +223 -0
  28. package/lib/cf-memory/src/__tests__/daemon.test.ts +407 -0
  29. package/lib/cf-memory/src/__tests__/dedup.test.ts +103 -0
  30. package/lib/cf-memory/src/__tests__/embeddings.test.ts +292 -0
  31. package/lib/cf-memory/src/__tests__/lazy-install.test.ts +210 -0
  32. package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +410 -0
  33. package/lib/cf-memory/src/__tests__/migration.test.ts +255 -0
  34. package/lib/cf-memory/src/__tests__/migrations.test.ts +288 -0
  35. package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +262 -0
  36. package/lib/cf-memory/src/__tests__/ollama.test.ts +48 -0
  37. package/lib/cf-memory/src/__tests__/schema.test.ts +128 -0
  38. package/lib/cf-memory/src/__tests__/search.test.ts +115 -0
  39. package/lib/cf-memory/src/__tests__/temporal-decay.test.ts +54 -0
  40. package/lib/cf-memory/src/__tests__/tier.test.ts +293 -0
  41. package/lib/cf-memory/src/__tests__/tools.test.ts +83 -0
  42. package/lib/cf-memory/src/backends/markdown.ts +318 -0
  43. package/lib/cf-memory/src/backends/minisearch.ts +203 -0
  44. package/lib/cf-memory/src/backends/sqlite/embeddings.ts +286 -0
  45. package/lib/cf-memory/src/backends/sqlite/index.ts +549 -0
  46. package/lib/cf-memory/src/backends/sqlite/migrations.ts +188 -0
  47. package/lib/cf-memory/src/backends/sqlite/schema.ts +120 -0
  48. package/lib/cf-memory/src/backends/sqlite/search.ts +296 -0
  49. package/lib/cf-memory/src/bin/cf-memory.ts +2 -0
  50. package/lib/cf-memory/src/daemon/entry.ts +99 -0
  51. package/lib/cf-memory/src/daemon/process.ts +271 -0
  52. package/lib/cf-memory/src/daemon/server.ts +166 -0
  53. package/lib/cf-memory/src/daemon/watcher.ts +90 -0
  54. package/lib/cf-memory/src/index.ts +53 -0
  55. package/lib/cf-memory/src/lib/backend.ts +23 -0
  56. package/lib/cf-memory/src/lib/daemon-client.ts +163 -0
  57. package/lib/cf-memory/src/lib/dedup.ts +80 -0
  58. package/lib/cf-memory/src/lib/lazy-install.ts +274 -0
  59. package/lib/cf-memory/src/lib/ollama.ts +76 -0
  60. package/lib/cf-memory/src/lib/temporal-decay.ts +19 -0
  61. package/lib/cf-memory/src/lib/tier.ts +107 -0
  62. package/lib/cf-memory/src/lib/types.ts +109 -0
  63. package/lib/cf-memory/src/resources/index.ts +62 -0
  64. package/lib/cf-memory/src/server.ts +20 -0
  65. package/lib/cf-memory/src/tools/delete.ts +38 -0
  66. package/lib/cf-memory/src/tools/list.ts +38 -0
  67. package/lib/cf-memory/src/tools/retrieve.ts +52 -0
  68. package/lib/cf-memory/src/tools/search.ts +47 -0
  69. package/lib/cf-memory/src/tools/store.ts +70 -0
  70. package/lib/cf-memory/src/tools/update.ts +62 -0
  71. package/lib/cf-memory/tsconfig.json +15 -0
  72. package/lib/cf-memory/vitest.config.ts +7 -0
  73. package/package.json +1 -1
package/README.md CHANGED
@@ -63,6 +63,18 @@ cf dev status # Show current dev mode (local or remote)
63
63
  cf dev sync # Sync local changes to cache (no version bump needed)
64
64
  cf dev restart # Reinstall local dev plugin (off + on)
65
65
  cf dev update # Update local dev plugin to latest version (off + on)
66
+ cf memory status # Show memory system status and stats
67
+ cf memory search # Search memories by keyword
68
+ cf memory list # List all memories in current project
69
+ cf memory list --projects # List all project databases with size and metadata
70
+ cf memory rm --project-id <id> # Remove a specific project database
71
+ cf memory rm --all # Remove all project databases
72
+ cf memory rm --prune # Remove orphaned projects (source dir missing or 0 memories)
73
+ cf memory start # Start memory daemon (enables Tier 2 search)
74
+ cf memory stop # Stop memory daemon
75
+ cf memory rebuild # Rebuild search index from markdown files
76
+ cf memory init # Initialize SQLite backend (Tier 1) and import memories
77
+ cf memory mcp # Show MCP server config for clients
66
78
  cf session save # Save current Claude Code session to docs/sessions/
67
79
  cf session load # Load a saved session from docs/sessions/
68
80
  cf help # Show all commands
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  run
3
- } from "./chunk-X5WEODUD.js";
3
+ } from "./chunk-CYQU33FY.js";
4
4
  import {
5
5
  globalConfigPath,
6
6
  localConfigPath,
@@ -5,6 +5,7 @@ function run(cmd, args = [], opts) {
5
5
  return execFileSync(cmd, args, {
6
6
  encoding: "utf-8",
7
7
  cwd: opts?.cwd,
8
+ env: opts?.env,
8
9
  stdio: ["pipe", "pipe", "pipe"]
9
10
  }).trim();
10
11
  } catch {
@@ -4,16 +4,16 @@ import {
4
4
  } from "./chunk-ORACWEDN.js";
5
5
  import {
6
6
  ensureShellCompletion
7
- } from "./chunk-KJUGTLPQ.js";
7
+ } from "./chunk-YO6JKGR3.js";
8
8
  import {
9
9
  resolveScope
10
- } from "./chunk-D4EWPGBL.js";
10
+ } from "./chunk-C5LYVVEI.js";
11
11
  import {
12
12
  commandExists,
13
13
  run,
14
14
  runWithStderr,
15
15
  sleepSync
16
- } from "./chunk-X5WEODUD.js";
16
+ } from "./chunk-CYQU33FY.js";
17
17
  import {
18
18
  claudeSettingsPath,
19
19
  readJson
@@ -28,8 +28,22 @@ function resolveDocsDir(explicitPath) {
28
28
  }
29
29
  return resolvePath("docs/learn");
30
30
  }
31
+ function resolveMemoryDir(explicitPath) {
32
+ if (explicitPath) {
33
+ return resolvePath(explicitPath);
34
+ }
35
+ const config = loadConfig();
36
+ if (config.memory?.docsDir) {
37
+ return resolvePath(config.memory.docsDir);
38
+ }
39
+ if (config.docsDir) {
40
+ return resolvePath(`${config.docsDir}/memory`);
41
+ }
42
+ return resolvePath("docs/memory");
43
+ }
31
44
 
32
45
  export {
33
46
  loadConfig,
34
- resolveDocsDir
47
+ resolveDocsDir,
48
+ resolveMemoryDir
35
49
  };
@@ -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 permission statusline update dev session"
24
+ local commands="install uninstall disable enable init config host mcp memory permission statusline update 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
 
@@ -31,6 +31,12 @@ _cf_completions() {
31
31
  return
32
32
  fi
33
33
 
34
+ # Subcommands for 'memory'
35
+ if [[ "\${COMP_WORDS[1]}" == "memory" && \${COMP_CWORD} -eq 2 ]]; then
36
+ COMPREPLY=($(compgen -W "status search list rm init start stop rebuild mcp" -- "$cur"))
37
+ return
38
+ fi
39
+
34
40
  # Subcommands for 'session'
35
41
  if [[ "\${COMP_WORDS[1]}" == "session" && \${COMP_CWORD} -eq 2 ]]; then
36
42
  COMPREPLY=($(compgen -W "save load" -- "$cur"))
@@ -71,6 +77,7 @@ var ZSH_FUNCTION_BODY = `_cf() {
71
77
  'config:Manage Coding Friend configuration'
72
78
  'host:Build and serve learning docs as a static website'
73
79
  'mcp:Setup MCP server for learning docs'
80
+ 'memory:AI memory system \u2014 store and search project knowledge'
74
81
  'permission:Manage Claude Code permission rules for Coding Friend'
75
82
  'statusline:Setup coding-friend statusline in Claude Code'
76
83
  'update:Update coding-friend plugin and refresh statusline'
@@ -114,6 +121,20 @@ var ZSH_FUNCTION_BODY = `_cf() {
114
121
  'update:Update local dev plugin to latest version'
115
122
  )
116
123
  _describe 'subcommand' subcommands
124
+ elif (( CURRENT == 3 )) && [[ "\${words[2]}" == "memory" ]]; then
125
+ local -a subcommands
126
+ subcommands=(
127
+ 'status:Show memory system status'
128
+ 'search:Search memories by query'
129
+ 'list:List memories (--projects for all DBs)'
130
+ 'rm:Remove a project database'
131
+ 'init:Initialize Tier 1 (SQLite + Hybrid Search)'
132
+ 'start:Start the memory daemon (Tier 2)'
133
+ 'stop:Stop the memory daemon'
134
+ 'rebuild:Rebuild the daemon search index'
135
+ 'mcp:Show MCP server setup instructions'
136
+ )
137
+ _describe 'subcommand' subcommands
117
138
  elif (( CURRENT == 3 )) && [[ "\${words[2]}" == "session" ]]; then
118
139
  local -a subcommands
119
140
  subcommands=(
@@ -145,6 +166,7 @@ complete -c cf -n "__fish_use_subcommand" -a init -d "Initialize coding-friend i
145
166
  complete -c cf -n "__fish_use_subcommand" -a config -d "Manage Coding Friend configuration"
146
167
  complete -c cf -n "__fish_use_subcommand" -a host -d "Build and serve learning docs as a static website"
147
168
  complete -c cf -n "__fish_use_subcommand" -a mcp -d "Setup MCP server for learning docs"
169
+ complete -c cf -n "__fish_use_subcommand" -a memory -d "AI memory system \u2014 store and search project knowledge"
148
170
  complete -c cf -n "__fish_use_subcommand" -a permission -d "Manage Claude Code permission rules"
149
171
  complete -c cf -n "__fish_use_subcommand" -a statusline -d "Setup coding-friend statusline in Claude Code"
150
172
  complete -c cf -n "__fish_use_subcommand" -a update -d "Update coding-friend plugin and refresh statusline"
@@ -170,6 +192,16 @@ complete -c cf -n "__fish_seen_subcommand_from dev" -a status -d "Show current d
170
192
  complete -c cf -n "__fish_seen_subcommand_from dev" -a restart -d "Restart dev mode"
171
193
  complete -c cf -n "__fish_seen_subcommand_from dev" -a sync -d "Sync local plugin files"
172
194
  complete -c cf -n "__fish_seen_subcommand_from dev" -a update -d "Update local dev plugin"
195
+ # Memory subcommands
196
+ complete -c cf -n "__fish_seen_subcommand_from memory" -a status -d "Show memory system status"
197
+ complete -c cf -n "__fish_seen_subcommand_from memory" -a search -d "Search memories by query"
198
+ complete -c cf -n "__fish_seen_subcommand_from memory" -a list -d "List memories (--projects for all DBs)"
199
+ complete -c cf -n "__fish_seen_subcommand_from memory" -a rm -d "Remove a project database"
200
+ complete -c cf -n "__fish_seen_subcommand_from memory" -a init -d "Initialize Tier 1 (SQLite + Hybrid Search)"
201
+ complete -c cf -n "__fish_seen_subcommand_from memory" -a start -d "Start the memory daemon (Tier 2)"
202
+ complete -c cf -n "__fish_seen_subcommand_from memory" -a stop -d "Stop the memory daemon"
203
+ complete -c cf -n "__fish_seen_subcommand_from memory" -a rebuild -d "Rebuild the daemon search index"
204
+ complete -c cf -n "__fish_seen_subcommand_from memory" -a mcp -d "Show MCP server setup instructions"
173
205
  # Session subcommands
174
206
  complete -c cf -n "__fish_seen_subcommand_from session" -a save -d "Save current session to docs/sessions/"
175
207
  complete -c cf -n "__fish_seen_subcommand_from session" -a load -d "Load a saved session from docs/sessions/"
@@ -179,8 +211,9 @@ var POWERSHELL_BLOCK = `
179
211
  ${MARKER_START}
180
212
  Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
181
213
  param($wordToComplete, $commandAst, $cursorPosition)
182
- $commands = @('install','uninstall','disable','enable','init','config','host','mcp','permission','statusline','update','dev','session')
214
+ $commands = @('install','uninstall','disable','enable','init','config','host','mcp','memory','permission','statusline','update','dev','session')
183
215
  $devSubcommands = @('on','off','status','restart','sync','update')
216
+ $memorySubcommands = @('status','search','list','rm','init','start','stop','rebuild','mcp')
184
217
  $sessionSubcommands = @('save','load')
185
218
  $scopeFlags = @('--user','--global','--project','--local')
186
219
  $updateFlags = @('--cli','--plugin','--statusline','--user','--global','--project','--local')
@@ -188,6 +221,9 @@ Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
188
221
  if ($words.Count -ge 2 -and $words[1].ToString() -eq 'dev') {
189
222
  $devSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
190
223
  ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
224
+ } elseif ($words.Count -ge 2 -and $words[1].ToString() -eq 'memory') {
225
+ $memorySubcommands | Where-Object { $_ -like "$wordToComplete*" } |
226
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
191
227
  } elseif ($words.Count -ge 2 -and $words[1].ToString() -eq 'session') {
192
228
  $sessionSubcommands | Where-Object { $_ -like "$wordToComplete*" } |
193
229
  ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
@@ -13,7 +13,7 @@ import {
13
13
  ensureShellCompletion,
14
14
  hasShellCompletion,
15
15
  removeShellCompletion
16
- } from "./chunk-KJUGTLPQ.js";
16
+ } from "./chunk-YO6JKGR3.js";
17
17
  import {
18
18
  BACK,
19
19
  applyDocsDirChange,
@@ -23,10 +23,10 @@ import {
23
23
  getScopeLabel,
24
24
  injectBackChoice,
25
25
  showConfigHint
26
- } from "./chunk-D4EWPGBL.js";
26
+ } from "./chunk-C5LYVVEI.js";
27
27
  import {
28
28
  run
29
- } from "./chunk-X5WEODUD.js";
29
+ } from "./chunk-CYQU33FY.js";
30
30
  import {
31
31
  globalConfigPath,
32
32
  localConfigPath,
@@ -42,33 +42,53 @@ import {
42
42
  import { checkbox, confirm, input, select } from "@inquirer/prompts";
43
43
  import chalk from "chalk";
44
44
  import { existsSync, readFileSync, writeFileSync } from "fs";
45
- function getLearnFieldScope(field, globalCfg, localCfg) {
46
- const inGlobal = globalCfg?.learn ? globalCfg.learn[field] !== void 0 : false;
47
- const inLocal = localCfg?.learn ? localCfg.learn[field] !== void 0 : false;
45
+ function getNestedFieldScope(section, field, globalCfg, localCfg) {
46
+ const globalSection = globalCfg?.[section];
47
+ const localSection = localCfg?.[section];
48
+ const inGlobal = globalSection?.[field] !== void 0;
49
+ const inLocal = localSection?.[field] !== void 0;
48
50
  if (inGlobal && inLocal) return "both";
49
51
  if (inGlobal) return "global";
50
52
  if (inLocal) return "local";
51
53
  return "-";
52
54
  }
55
+ function getLearnFieldScope(field, globalCfg, localCfg) {
56
+ return getNestedFieldScope("learn", field, globalCfg, localCfg);
57
+ }
58
+ function getMemoryFieldScope(field, globalCfg, localCfg) {
59
+ return getNestedFieldScope("memory", field, globalCfg, localCfg);
60
+ }
61
+ function getMergedNestedValue(section, field, globalCfg, localCfg) {
62
+ const localSection = localCfg?.[section];
63
+ if (localSection?.[field] !== void 0) return localSection[field];
64
+ const globalSection = globalCfg?.[section];
65
+ return globalSection?.[field];
66
+ }
53
67
  function getMergedLearnValue(field, globalCfg, localCfg) {
54
- const localVal = localCfg?.learn ? localCfg.learn[field] : void 0;
55
- if (localVal !== void 0) return localVal;
56
- const globalVal = globalCfg?.learn ? globalCfg.learn[field] : void 0;
57
- return globalVal;
68
+ return getMergedNestedValue("learn", field, globalCfg, localCfg);
69
+ }
70
+ function getMergedMemoryValue(field, globalCfg, localCfg) {
71
+ return getMergedNestedValue("memory", field, globalCfg, localCfg);
58
72
  }
59
73
  function writeToScope(scope, data) {
60
74
  const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
61
75
  mergeJson(targetPath, data);
62
76
  log.success(`Saved to ${targetPath}`);
63
77
  }
64
- function writeLearnField(scope, field, value) {
78
+ function writeNestedField(section, scope, field, value) {
65
79
  const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
66
80
  const existingConfig = readJson(targetPath);
67
- const existingLearn = existingConfig?.learn ?? {};
68
- const updated = { ...existingLearn, [field]: value };
69
- mergeJson(targetPath, { learn: updated });
81
+ const existingSection = existingConfig?.[section] ?? {};
82
+ const updated = { ...existingSection, [field]: value };
83
+ mergeJson(targetPath, { [section]: updated });
70
84
  log.success(`Saved to ${targetPath}`);
71
85
  }
86
+ function writeLearnField(scope, field, value) {
87
+ writeNestedField("learn", scope, field, value);
88
+ }
89
+ function writeMemoryField(scope, field, value) {
90
+ writeNestedField("memory", scope, field, value);
91
+ }
72
92
  async function editDocsDir(globalCfg, localCfg) {
73
93
  const currentValue = getMergedValue("docsDir", globalCfg, localCfg);
74
94
  if (currentValue) {
@@ -354,6 +374,239 @@ async function learnSubMenu() {
354
374
  }
355
375
  }
356
376
  }
377
+ async function editMemoryTier(globalCfg, localCfg) {
378
+ const currentValue = getMergedMemoryValue("tier", globalCfg, localCfg);
379
+ if (currentValue) {
380
+ log.dim(`Current: ${currentValue}`);
381
+ }
382
+ const choice = await select({
383
+ message: "Memory search tier:",
384
+ choices: injectBackChoice(
385
+ [
386
+ {
387
+ name: "auto \u2014 detect best available (recommended)",
388
+ value: "auto"
389
+ },
390
+ {
391
+ name: "full \u2014 SQLite + FTS5 + vector embeddings (Tier 1)",
392
+ value: "full"
393
+ },
394
+ {
395
+ name: "lite \u2014 MiniSearch daemon, in-memory BM25 + fuzzy (Tier 2)",
396
+ value: "lite"
397
+ },
398
+ {
399
+ name: "markdown \u2014 file-based substring search (Tier 3)",
400
+ value: "markdown"
401
+ }
402
+ ],
403
+ "Back"
404
+ )
405
+ });
406
+ if (choice === BACK) return;
407
+ const scope = await askScope();
408
+ if (scope === "back") return;
409
+ writeMemoryField(scope, "tier", choice);
410
+ }
411
+ async function editMemoryAutoCapture(globalCfg, localCfg) {
412
+ const currentValue = getMergedMemoryValue(
413
+ "autoCapture",
414
+ globalCfg,
415
+ localCfg
416
+ );
417
+ if (currentValue !== void 0) {
418
+ log.dim(`Current: ${currentValue}`);
419
+ }
420
+ const value = await confirm({
421
+ message: "Auto-capture session context to memory on PreCompact (context window compression)?",
422
+ default: currentValue ?? false
423
+ });
424
+ const scope = await askScope();
425
+ if (scope === "back") return;
426
+ writeMemoryField(scope, "autoCapture", value);
427
+ }
428
+ async function editMemoryAutoStart(globalCfg, localCfg) {
429
+ const currentValue = getMergedMemoryValue(
430
+ "autoStart",
431
+ globalCfg,
432
+ localCfg
433
+ );
434
+ if (currentValue !== void 0) {
435
+ log.dim(`Current: ${currentValue}`);
436
+ }
437
+ const value = await confirm({
438
+ message: "Auto-start memory daemon when MCP server connects?",
439
+ default: currentValue ?? false
440
+ });
441
+ const scope = await askScope();
442
+ if (scope === "back") return;
443
+ writeMemoryField(scope, "autoStart", value);
444
+ }
445
+ async function editMemoryEmbedding(globalCfg, localCfg) {
446
+ const currentEmbedding = getMergedMemoryValue(
447
+ "embedding",
448
+ globalCfg,
449
+ localCfg
450
+ );
451
+ if (currentEmbedding) {
452
+ const parts = [`provider: ${currentEmbedding.provider ?? "transformers"}`];
453
+ if (currentEmbedding.model) parts.push(`model: ${currentEmbedding.model}`);
454
+ if (currentEmbedding.ollamaUrl)
455
+ parts.push(`url: ${currentEmbedding.ollamaUrl}`);
456
+ log.dim(`Current: ${parts.join(", ")}`);
457
+ }
458
+ const provider = await select({
459
+ message: "Embedding provider:",
460
+ choices: injectBackChoice(
461
+ [
462
+ {
463
+ name: "transformers \u2014 Transformers.js, runs in-process (no external deps)",
464
+ value: "transformers"
465
+ },
466
+ {
467
+ name: "ollama \u2014 Local Ollama server (faster, GPU support, wider model selection)",
468
+ value: "ollama"
469
+ }
470
+ ],
471
+ "Back"
472
+ )
473
+ });
474
+ if (provider === BACK) return;
475
+ let model;
476
+ let ollamaUrl;
477
+ if (provider === "ollama") {
478
+ model = await input({
479
+ message: "Ollama model name:",
480
+ default: currentEmbedding?.model ?? "all-minilm:l6-v2"
481
+ });
482
+ ollamaUrl = await input({
483
+ message: "Ollama server URL:",
484
+ default: currentEmbedding?.ollamaUrl ?? "http://localhost:11434"
485
+ });
486
+ if (ollamaUrl === "http://localhost:11434") ollamaUrl = void 0;
487
+ } else {
488
+ model = await input({
489
+ message: "Transformers.js model:",
490
+ default: currentEmbedding?.model ?? "Xenova/all-MiniLM-L6-v2"
491
+ });
492
+ if (model === "Xenova/all-MiniLM-L6-v2") model = void 0;
493
+ }
494
+ const scope = await askScope();
495
+ if (scope === "back") return;
496
+ const embedding = { provider };
497
+ if (model) embedding.model = model;
498
+ if (ollamaUrl) embedding.ollamaUrl = ollamaUrl;
499
+ writeMemoryField(scope, "embedding", embedding);
500
+ }
501
+ async function editMemoryDaemonTimeout(globalCfg, localCfg) {
502
+ const currentDaemon = getMergedMemoryValue("daemon", globalCfg, localCfg);
503
+ const currentMs = currentDaemon?.idleTimeout;
504
+ const currentMin = currentMs ? currentMs / 6e4 : void 0;
505
+ if (currentMin !== void 0) {
506
+ log.dim(`Current: ${currentMin} minutes`);
507
+ }
508
+ const value = await input({
509
+ message: "Daemon idle timeout (minutes):",
510
+ default: String(currentMin ?? 30),
511
+ validate: (val) => {
512
+ const n = Number(val);
513
+ if (isNaN(n) || n < 1) return "Must be a positive number";
514
+ return true;
515
+ }
516
+ });
517
+ const scope = await askScope();
518
+ if (scope === "back") return;
519
+ writeMemoryField(scope, "daemon", {
520
+ ...currentDaemon,
521
+ idleTimeout: Number(value) * 6e4
522
+ });
523
+ }
524
+ async function memorySubMenu() {
525
+ while (true) {
526
+ const globalCfg = readJson(globalConfigPath());
527
+ const localCfg = readJson(localConfigPath());
528
+ const tierScope = getMemoryFieldScope("tier", globalCfg, localCfg);
529
+ const tierVal = getMergedMemoryValue("tier", globalCfg, localCfg);
530
+ const autoCaptureScope = getMemoryFieldScope(
531
+ "autoCapture",
532
+ globalCfg,
533
+ localCfg
534
+ );
535
+ const autoCaptureVal = getMergedMemoryValue(
536
+ "autoCapture",
537
+ globalCfg,
538
+ localCfg
539
+ );
540
+ const autoStartScope = getMemoryFieldScope(
541
+ "autoStart",
542
+ globalCfg,
543
+ localCfg
544
+ );
545
+ const autoStartVal = getMergedMemoryValue(
546
+ "autoStart",
547
+ globalCfg,
548
+ localCfg
549
+ );
550
+ const embeddingScope = getMemoryFieldScope(
551
+ "embedding",
552
+ globalCfg,
553
+ localCfg
554
+ );
555
+ const embeddingVal = getMergedMemoryValue(
556
+ "embedding",
557
+ globalCfg,
558
+ localCfg
559
+ );
560
+ const daemonScope = getMemoryFieldScope("daemon", globalCfg, localCfg);
561
+ const daemonVal = getMergedMemoryValue("daemon", globalCfg, localCfg);
562
+ const choice = await select({
563
+ message: "Memory settings:",
564
+ choices: injectBackChoice(
565
+ [
566
+ {
567
+ name: `Tier ${formatScopeLabel(tierScope)}${tierVal ? ` (${tierVal})` : ""}`,
568
+ value: "tier"
569
+ },
570
+ {
571
+ name: `Auto-capture ${formatScopeLabel(autoCaptureScope)}${autoCaptureVal !== void 0 ? ` (${autoCaptureVal})` : ""}`,
572
+ value: "autoCapture"
573
+ },
574
+ {
575
+ name: `Auto-start daemon ${formatScopeLabel(autoStartScope)}${autoStartVal !== void 0 ? ` (${autoStartVal})` : ""}`,
576
+ value: "autoStart"
577
+ },
578
+ {
579
+ name: `Embedding ${formatScopeLabel(embeddingScope)}${embeddingVal?.provider ? ` (${embeddingVal.provider})` : ""}`,
580
+ value: "embedding"
581
+ },
582
+ {
583
+ name: `Daemon timeout ${formatScopeLabel(daemonScope)}${daemonVal?.idleTimeout ? ` (${daemonVal.idleTimeout / 6e4}min)` : ""}`,
584
+ value: "daemon"
585
+ }
586
+ ],
587
+ "Back"
588
+ )
589
+ });
590
+ if (choice === BACK) return;
591
+ switch (choice) {
592
+ case "tier":
593
+ await editMemoryTier(globalCfg, localCfg);
594
+ break;
595
+ case "autoCapture":
596
+ await editMemoryAutoCapture(globalCfg, localCfg);
597
+ break;
598
+ case "autoStart":
599
+ await editMemoryAutoStart(globalCfg, localCfg);
600
+ break;
601
+ case "embedding":
602
+ await editMemoryEmbedding(globalCfg, localCfg);
603
+ break;
604
+ case "daemon":
605
+ await editMemoryDaemonTimeout(globalCfg, localCfg);
606
+ break;
607
+ }
608
+ }
609
+ }
357
610
  async function editStatusline() {
358
611
  const hookResult = findStatuslineHookPath();
359
612
  if (!hookResult) {
@@ -496,6 +749,7 @@ async function configCommand() {
496
749
  const langScope = getScopeLabel("language", globalCfg, localCfg);
497
750
  const langVal = getMergedValue("language", globalCfg, localCfg);
498
751
  const learnScope = getScopeLabel("learn", globalCfg, localCfg);
752
+ const memoryScope = getScopeLabel("memory", globalCfg, localCfg);
499
753
  const statuslineStatus = isStatuslineConfigured() ? chalk.green("configured") : chalk.yellow("not configured");
500
754
  const completionStatus = hasShellCompletion() ? chalk.green("installed") : chalk.yellow("not installed");
501
755
  const choice = await select({
@@ -517,6 +771,11 @@ async function configCommand() {
517
771
  value: "learn",
518
772
  description: " Output dir, language, categories, auto-commit, README index"
519
773
  },
774
+ {
775
+ name: `Memory settings ${formatScopeLabel(memoryScope)}`,
776
+ value: "memory",
777
+ description: " Tier, auto-capture, auto-start, embedding provider, daemon timeout"
778
+ },
520
779
  {
521
780
  name: `Statusline (${statuslineStatus})`,
522
781
  value: "statusline",
@@ -549,6 +808,9 @@ async function configCommand() {
549
808
  case "learn":
550
809
  await learnSubMenu();
551
810
  break;
811
+ case "memory":
812
+ await memorySubMenu();
813
+ break;
552
814
  case "statusline":
553
815
  await editStatusline();
554
816
  break;
@@ -8,11 +8,11 @@ import {
8
8
  import "./chunk-POC2WHU2.js";
9
9
  import {
10
10
  ensureShellCompletion
11
- } from "./chunk-KJUGTLPQ.js";
11
+ } from "./chunk-YO6JKGR3.js";
12
12
  import {
13
13
  commandExists,
14
14
  run
15
- } from "./chunk-X5WEODUD.js";
15
+ } from "./chunk-CYQU33FY.js";
16
16
  import {
17
17
  devStatePath,
18
18
  knownMarketplacesPath,
@@ -4,8 +4,8 @@ import {
4
4
  } from "./chunk-YC6MBHCT.js";
5
5
  import {
6
6
  resolveScope
7
- } from "./chunk-D4EWPGBL.js";
8
- import "./chunk-X5WEODUD.js";
7
+ } from "./chunk-C5LYVVEI.js";
8
+ import "./chunk-CYQU33FY.js";
9
9
  import "./chunk-RWUTFVRB.js";
10
10
  import {
11
11
  log
@@ -4,8 +4,8 @@ import {
4
4
  } from "./chunk-YC6MBHCT.js";
5
5
  import {
6
6
  resolveScope
7
- } from "./chunk-D4EWPGBL.js";
8
- import "./chunk-X5WEODUD.js";
7
+ } from "./chunk-C5LYVVEI.js";
8
+ import "./chunk-CYQU33FY.js";
9
9
  import "./chunk-RWUTFVRB.js";
10
10
  import {
11
11
  log
@@ -1,14 +1,14 @@
1
+ import {
2
+ resolveDocsDir
3
+ } from "./chunk-KTX4MGMR.js";
1
4
  import {
2
5
  getLibPath
3
6
  } from "./chunk-RZRT7NGT.js";
4
- import {
5
- resolveDocsDir
6
- } from "./chunk-4DB4XTSL.js";
7
7
  import "./chunk-POC2WHU2.js";
8
8
  import {
9
9
  run,
10
10
  streamExec
11
- } from "./chunk-X5WEODUD.js";
11
+ } from "./chunk-CYQU33FY.js";
12
12
  import "./chunk-RWUTFVRB.js";
13
13
  import {
14
14
  log