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.
@@ -0,0 +1,331 @@
1
+ import {
2
+ getLibPath
3
+ } from "./chunk-RZRT7NGT.js";
4
+ import {
5
+ BACK,
6
+ askScope,
7
+ formatScopeLabel,
8
+ injectBackChoice
9
+ } from "./chunk-C5LYVVEI.js";
10
+ import {
11
+ globalConfigPath,
12
+ localConfigPath,
13
+ mergeJson,
14
+ readJson
15
+ } from "./chunk-RWUTFVRB.js";
16
+ import {
17
+ log
18
+ } from "./chunk-W5CD7WTX.js";
19
+
20
+ // src/lib/memory-prompts.ts
21
+ import { confirm, input, select } from "@inquirer/prompts";
22
+ import { join } from "path";
23
+ function getMemoryFieldScope(field, globalCfg, localCfg) {
24
+ const globalSection = globalCfg?.memory;
25
+ const localSection = localCfg?.memory;
26
+ const inGlobal = globalSection?.[field] !== void 0;
27
+ const inLocal = localSection?.[field] !== void 0;
28
+ if (inGlobal && inLocal) return "both";
29
+ if (inGlobal) return "global";
30
+ if (inLocal) return "local";
31
+ return "-";
32
+ }
33
+ function getMergedMemoryValue(field, globalCfg, localCfg) {
34
+ const localSection = localCfg?.memory;
35
+ if (localSection?.[field] !== void 0) return localSection[field];
36
+ const globalSection = globalCfg?.memory;
37
+ return globalSection?.[field];
38
+ }
39
+ function writeMemoryField(scope, field, value) {
40
+ const targetPath = scope === "global" ? globalConfigPath() : localConfigPath();
41
+ const existingConfig = readJson(targetPath);
42
+ const existingSection = existingConfig?.memory ?? {};
43
+ const updated = { ...existingSection, [field]: value };
44
+ mergeJson(targetPath, { memory: updated });
45
+ log.success(`Saved to ${targetPath}`);
46
+ }
47
+ async function editMemoryTier(globalCfg, localCfg) {
48
+ const currentValue = getMergedMemoryValue("tier", globalCfg, localCfg);
49
+ if (currentValue) {
50
+ log.dim(`Current: ${currentValue}`);
51
+ }
52
+ const choice = await select({
53
+ message: "Memory search tier:",
54
+ choices: injectBackChoice(
55
+ [
56
+ {
57
+ name: "auto \u2014 detect best available (recommended)",
58
+ value: "auto"
59
+ },
60
+ {
61
+ name: "full \u2014 SQLite + FTS5 + vector embeddings (Tier 1)",
62
+ value: "full"
63
+ },
64
+ {
65
+ name: "lite \u2014 MiniSearch daemon, in-memory BM25 + fuzzy (Tier 2)",
66
+ value: "lite"
67
+ },
68
+ {
69
+ name: "markdown \u2014 file-based substring search (Tier 3)",
70
+ value: "markdown"
71
+ }
72
+ ],
73
+ "Back"
74
+ )
75
+ });
76
+ if (choice === BACK) return;
77
+ const scope = await askScope();
78
+ if (scope === "back") return;
79
+ writeMemoryField(scope, "tier", choice);
80
+ }
81
+ async function editMemoryAutoCapture(globalCfg, localCfg) {
82
+ const currentValue = getMergedMemoryValue(
83
+ "autoCapture",
84
+ globalCfg,
85
+ localCfg
86
+ );
87
+ if (currentValue !== void 0) {
88
+ log.dim(`Current: ${currentValue}`);
89
+ }
90
+ const value = await confirm({
91
+ message: "Auto-capture session context to memory on PreCompact (context window compression)?",
92
+ default: currentValue ?? false
93
+ });
94
+ const scope = await askScope();
95
+ if (scope === "back") return;
96
+ writeMemoryField(scope, "autoCapture", value);
97
+ }
98
+ async function editMemoryAutoStart(globalCfg, localCfg) {
99
+ const currentValue = getMergedMemoryValue(
100
+ "autoStart",
101
+ globalCfg,
102
+ localCfg
103
+ );
104
+ if (currentValue !== void 0) {
105
+ log.dim(`Current: ${currentValue}`);
106
+ }
107
+ const value = await confirm({
108
+ message: "Auto-start memory daemon when MCP server connects?",
109
+ default: currentValue ?? false
110
+ });
111
+ const scope = await askScope();
112
+ if (scope === "back") return;
113
+ writeMemoryField(scope, "autoStart", value);
114
+ }
115
+ async function editMemoryEmbedding(globalCfg, localCfg) {
116
+ const currentEmbedding = getMergedMemoryValue(
117
+ "embedding",
118
+ globalCfg,
119
+ localCfg
120
+ );
121
+ if (currentEmbedding) {
122
+ const parts = [`provider: ${currentEmbedding.provider ?? "transformers"}`];
123
+ if (currentEmbedding.model) parts.push(`model: ${currentEmbedding.model}`);
124
+ if (currentEmbedding.ollamaUrl)
125
+ parts.push(`url: ${currentEmbedding.ollamaUrl}`);
126
+ log.dim(`Current: ${parts.join(", ")}`);
127
+ }
128
+ const provider = await select({
129
+ message: "Embedding provider:",
130
+ choices: injectBackChoice(
131
+ [
132
+ {
133
+ name: "transformers \u2014 Transformers.js, runs in-process (no external deps)",
134
+ value: "transformers"
135
+ },
136
+ {
137
+ name: "ollama \u2014 Local Ollama server (faster, GPU support, wider model selection)",
138
+ value: "ollama"
139
+ }
140
+ ],
141
+ "Back"
142
+ )
143
+ });
144
+ if (provider === BACK) return;
145
+ let model;
146
+ let ollamaUrl;
147
+ if (provider === "ollama") {
148
+ ollamaUrl = await input({
149
+ message: "Ollama server URL:",
150
+ default: currentEmbedding?.ollamaUrl ?? "http://localhost:11434"
151
+ });
152
+ if (ollamaUrl === "http://localhost:11434") ollamaUrl = void 0;
153
+ const mcpDir = getLibPath("cf-memory");
154
+ try {
155
+ const { isOllamaRunning, hasOllamaEmbeddingModel } = await import(join(mcpDir, "dist/lib/ollama.js"));
156
+ const url = ollamaUrl ?? "http://localhost:11434";
157
+ const running = await isOllamaRunning(url);
158
+ if (!running) {
159
+ console.log();
160
+ log.warn("Ollama is not running at " + url);
161
+ log.dim(
162
+ "Install Ollama: https://ollama.ai \xB7 Docs: https://cf.dinhanhthi.com/cli/cf-memory"
163
+ );
164
+ console.log();
165
+ const fallback = await confirm({
166
+ message: "Fall back to Transformers.js instead?",
167
+ default: true
168
+ });
169
+ if (fallback) {
170
+ const scope2 = await askScope();
171
+ if (scope2 === "back") return;
172
+ writeMemoryField(scope2, "embedding", { provider: "transformers" });
173
+ return;
174
+ }
175
+ } else {
176
+ model = await input({
177
+ message: "Ollama model name:",
178
+ default: currentEmbedding?.model ?? "all-minilm:l6-v2"
179
+ });
180
+ const hasModel = await hasOllamaEmbeddingModel(model, url);
181
+ if (!hasModel) {
182
+ console.log();
183
+ log.warn(`Model "${model}" not found in Ollama.`);
184
+ log.dim(`Pull it with: ollama pull ${model}`);
185
+ console.log();
186
+ const proceed = await confirm({
187
+ message: "Save this config anyway? (you can pull the model later)",
188
+ default: true
189
+ });
190
+ if (!proceed) return;
191
+ }
192
+ }
193
+ } catch {
194
+ model = await input({
195
+ message: "Ollama model name:",
196
+ default: currentEmbedding?.model ?? "all-minilm:l6-v2"
197
+ });
198
+ }
199
+ } else {
200
+ model = await input({
201
+ message: "Transformers.js model:",
202
+ default: currentEmbedding?.model ?? "Xenova/all-MiniLM-L6-v2"
203
+ });
204
+ if (model === "Xenova/all-MiniLM-L6-v2") model = void 0;
205
+ }
206
+ const scope = await askScope();
207
+ if (scope === "back") return;
208
+ const embedding = { provider };
209
+ if (model) embedding.model = model;
210
+ if (ollamaUrl) embedding.ollamaUrl = ollamaUrl;
211
+ writeMemoryField(scope, "embedding", embedding);
212
+ }
213
+ async function editMemoryDaemonTimeout(globalCfg, localCfg) {
214
+ const currentDaemon = getMergedMemoryValue("daemon", globalCfg, localCfg);
215
+ const currentMs = currentDaemon?.idleTimeout;
216
+ const currentMin = currentMs ? currentMs / 6e4 : void 0;
217
+ if (currentMin !== void 0) {
218
+ log.dim(`Current: ${currentMin} minutes`);
219
+ }
220
+ const value = await input({
221
+ message: "Daemon idle timeout (minutes):",
222
+ default: String(currentMin ?? 30),
223
+ validate: (val) => {
224
+ const n = Number(val);
225
+ if (isNaN(n) || n < 1) return "Must be a positive number";
226
+ return true;
227
+ }
228
+ });
229
+ const scope = await askScope();
230
+ if (scope === "back") return;
231
+ writeMemoryField(scope, "daemon", {
232
+ ...currentDaemon,
233
+ idleTimeout: Number(value) * 6e4
234
+ });
235
+ }
236
+ async function memoryConfigMenu(opts) {
237
+ while (true) {
238
+ const globalCfg = readJson(globalConfigPath());
239
+ const localCfg = readJson(localConfigPath());
240
+ const tierScope = getMemoryFieldScope("tier", globalCfg, localCfg);
241
+ const tierVal = getMergedMemoryValue("tier", globalCfg, localCfg);
242
+ const autoCaptureScope = getMemoryFieldScope(
243
+ "autoCapture",
244
+ globalCfg,
245
+ localCfg
246
+ );
247
+ const autoCaptureVal = getMergedMemoryValue(
248
+ "autoCapture",
249
+ globalCfg,
250
+ localCfg
251
+ );
252
+ const autoStartScope = getMemoryFieldScope(
253
+ "autoStart",
254
+ globalCfg,
255
+ localCfg
256
+ );
257
+ const autoStartVal = getMergedMemoryValue(
258
+ "autoStart",
259
+ globalCfg,
260
+ localCfg
261
+ );
262
+ const embeddingScope = getMemoryFieldScope(
263
+ "embedding",
264
+ globalCfg,
265
+ localCfg
266
+ );
267
+ const embeddingVal = getMergedMemoryValue(
268
+ "embedding",
269
+ globalCfg,
270
+ localCfg
271
+ );
272
+ const daemonScope = getMemoryFieldScope("daemon", globalCfg, localCfg);
273
+ const daemonVal = getMergedMemoryValue("daemon", globalCfg, localCfg);
274
+ const embeddingLabel = embeddingVal?.provider ? embeddingVal.model ? `${embeddingVal.model} (${embeddingVal.provider})` : embeddingVal.provider : "";
275
+ const choice = await select({
276
+ message: "Memory settings:",
277
+ choices: injectBackChoice(
278
+ [
279
+ {
280
+ name: `Tier ${formatScopeLabel(tierScope)}${tierVal ? ` (${tierVal})` : ""}`,
281
+ value: "tier"
282
+ },
283
+ {
284
+ name: `Auto-capture ${formatScopeLabel(autoCaptureScope)}${autoCaptureVal !== void 0 ? ` (${autoCaptureVal})` : ""}`,
285
+ value: "autoCapture"
286
+ },
287
+ {
288
+ name: `Auto-start daemon ${formatScopeLabel(autoStartScope)}${autoStartVal !== void 0 ? ` (${autoStartVal})` : ""}`,
289
+ value: "autoStart"
290
+ },
291
+ {
292
+ name: `Embedding ${formatScopeLabel(embeddingScope)}${embeddingLabel ? ` (${embeddingLabel})` : ""}`,
293
+ value: "embedding"
294
+ },
295
+ {
296
+ name: `Daemon timeout ${formatScopeLabel(daemonScope)}${daemonVal?.idleTimeout ? ` (${daemonVal.idleTimeout / 6e4}min)` : ""}`,
297
+ value: "daemon"
298
+ }
299
+ ],
300
+ opts?.exitLabel ?? "Back"
301
+ )
302
+ });
303
+ if (choice === BACK) return;
304
+ switch (choice) {
305
+ case "tier":
306
+ await editMemoryTier(globalCfg, localCfg);
307
+ break;
308
+ case "autoCapture":
309
+ await editMemoryAutoCapture(globalCfg, localCfg);
310
+ break;
311
+ case "autoStart":
312
+ await editMemoryAutoStart(globalCfg, localCfg);
313
+ break;
314
+ case "embedding":
315
+ await editMemoryEmbedding(globalCfg, localCfg);
316
+ break;
317
+ case "daemon":
318
+ await editMemoryDaemonTimeout(globalCfg, localCfg);
319
+ break;
320
+ }
321
+ }
322
+ }
323
+
324
+ export {
325
+ editMemoryTier,
326
+ editMemoryAutoCapture,
327
+ editMemoryAutoStart,
328
+ editMemoryEmbedding,
329
+ editMemoryDaemonTimeout,
330
+ memoryConfigMenu
331
+ };