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
package/README.md
CHANGED
|
@@ -5,8 +5,26 @@ CLI companion for the [coding-friend](https://github.com/dinhanhthi/coding-frien
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
7
7
|
- Node.js >= 18
|
|
8
|
+
- npm (included with Node.js, but on some Linux distros you may need to install it separately)
|
|
8
9
|
- The [coding-friend plugin](https://github.com/dinhanhthi/coding-friend) installed in Claude Code
|
|
9
10
|
|
|
11
|
+
### Additional requirements for `cf memory init`
|
|
12
|
+
|
|
13
|
+
The memory system's Tier 1 (SQLite) uses native Node.js modules that require compilation. On **Linux** (Ubuntu/Debian), install the following system packages before running `cf memory init`:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
sudo apt update
|
|
17
|
+
sudo apt install -y build-essential python3
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
On **macOS**, install Xcode Command Line Tools:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
xcode-select --install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Without these, `cf memory init` will fail when installing SQLite dependencies (`better-sqlite3`, `sqlite-vec`). If you don't need Tier 1, you can choose the **lite** (Tier 2) or **markdown** (Tier 3) tier during init — these have no native dependencies.
|
|
27
|
+
|
|
10
28
|
## Install
|
|
11
29
|
|
|
12
30
|
```bash
|
|
@@ -55,6 +73,8 @@ cf permission --project # Save to project-level settings (.claude/settings.lo
|
|
|
55
73
|
cf permission --all --user # Apply all recommended permissions to user settings
|
|
56
74
|
cf statusline # Setup coding-friend statusline
|
|
57
75
|
cf update # Update plugin + CLI + statusline
|
|
76
|
+
# 💡 If update fails, open Claude Code (`claude`) and run:
|
|
77
|
+
# /plugins → Installed → coding-friend → Update now → /reload-plugins
|
|
58
78
|
cf update --cli # Update only the CLI (npm package)
|
|
59
79
|
cf update --plugin # Update only the Claude Code plugin
|
|
60
80
|
cf update --statusline # Update only the statusline
|
|
@@ -76,8 +96,11 @@ cf memory rm --prune # Remove orphaned projects (source dir missing or 0
|
|
|
76
96
|
cf memory start-daemon # Start memory daemon (enables Tier 2 search)
|
|
77
97
|
cf memory stop-daemon # Stop memory daemon
|
|
78
98
|
cf memory rebuild # Rebuild search index from markdown files
|
|
79
|
-
cf memory init # Initialize
|
|
99
|
+
cf memory init # Initialize memory system (interactive wizard)
|
|
100
|
+
cf memory config # Configure memory system settings
|
|
80
101
|
cf memory mcp # Show MCP server config for clients
|
|
102
|
+
cf status # Show comprehensive Coding Friend status
|
|
103
|
+
# (versions, plugin, memory, config)
|
|
81
104
|
cf session save # Save current Claude Code session to docs/sessions/
|
|
82
105
|
cf session load # Load a saved session from docs/sessions/
|
|
83
106
|
cf help # Show all commands
|
|
@@ -66,6 +66,18 @@ function enableMarketplaceAutoUpdate() {
|
|
|
66
66
|
return false;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
function detectPluginScope() {
|
|
70
|
+
const scopeOrder = ["local", "project", "user"];
|
|
71
|
+
for (const scope of scopeOrder) {
|
|
72
|
+
const settings = readJson(
|
|
73
|
+
settingsPathForScope(scope)
|
|
74
|
+
);
|
|
75
|
+
if (!settings) continue;
|
|
76
|
+
const enabled = settings.enabledPlugins;
|
|
77
|
+
if (enabled && PLUGIN_ID in enabled) return scope;
|
|
78
|
+
}
|
|
79
|
+
return "user";
|
|
80
|
+
}
|
|
69
81
|
function setPluginEnabled(scope, enabled) {
|
|
70
82
|
const filePath = settingsPathForScope(scope);
|
|
71
83
|
const settings = readJson(filePath) ?? {};
|
|
@@ -89,5 +101,6 @@ export {
|
|
|
89
101
|
isMarketplaceRegistered,
|
|
90
102
|
isPluginDisabled,
|
|
91
103
|
enableMarketplaceAutoUpdate,
|
|
104
|
+
detectPluginScope,
|
|
92
105
|
setPluginEnabled
|
|
93
106
|
};
|
|
@@ -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 memory permission statusline update dev session"
|
|
24
|
+
local commands="install uninstall disable enable init config host mcp memory permission statusline update status 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
|
|
|
@@ -33,7 +33,7 @@ _cf_completions() {
|
|
|
33
33
|
|
|
34
34
|
# Subcommands for 'memory'
|
|
35
35
|
if [[ "\${COMP_WORDS[1]}" == "memory" && \${COMP_CWORD} -eq 2 ]]; then
|
|
36
|
-
COMPREPLY=($(compgen -W "status search list rm init start-daemon stop-daemon rebuild mcp" -- "$cur"))
|
|
36
|
+
COMPREPLY=($(compgen -W "status search list rm init config start-daemon stop-daemon rebuild mcp" -- "$cur"))
|
|
37
37
|
return
|
|
38
38
|
fi
|
|
39
39
|
|
|
@@ -88,6 +88,7 @@ var ZSH_FUNCTION_BODY = `_cf() {
|
|
|
88
88
|
'statusline:Setup coding-friend statusline in Claude Code'
|
|
89
89
|
'update:Update coding-friend plugin and refresh statusline'
|
|
90
90
|
'dev:Switch between local and remote plugin for development'
|
|
91
|
+
'status:Show comprehensive Coding Friend status'
|
|
91
92
|
'session:Save and load Claude Code sessions across machines'
|
|
92
93
|
)
|
|
93
94
|
|
|
@@ -142,7 +143,8 @@ var ZSH_FUNCTION_BODY = `_cf() {
|
|
|
142
143
|
'search:Search memories by query'
|
|
143
144
|
'list:List memories (--projects for all DBs)'
|
|
144
145
|
'rm:Remove a project database'
|
|
145
|
-
'init:Initialize
|
|
146
|
+
'init:Initialize memory system (interactive wizard)'
|
|
147
|
+
'config:Configure memory system settings'
|
|
146
148
|
'start-daemon:Start the memory daemon (Tier 2)'
|
|
147
149
|
'stop-daemon:Stop the memory daemon'
|
|
148
150
|
'rebuild:Rebuild the daemon search index'
|
|
@@ -185,6 +187,7 @@ complete -c cf -n "__fish_use_subcommand" -a permission -d "Manage Claude Code p
|
|
|
185
187
|
complete -c cf -n "__fish_use_subcommand" -a statusline -d "Setup coding-friend statusline in Claude Code"
|
|
186
188
|
complete -c cf -n "__fish_use_subcommand" -a update -d "Update coding-friend plugin and refresh statusline"
|
|
187
189
|
complete -c cf -n "__fish_use_subcommand" -a dev -d "Switch between local and remote plugin for development"
|
|
190
|
+
complete -c cf -n "__fish_use_subcommand" -a status -d "Show comprehensive Coding Friend status"
|
|
188
191
|
complete -c cf -n "__fish_use_subcommand" -a session -d "Save and load Claude Code sessions across machines"
|
|
189
192
|
# Scope flags for install/uninstall/disable/enable
|
|
190
193
|
complete -c cf -n "__fish_seen_subcommand_from install uninstall disable enable" -l user -d "User scope (all projects)"
|
|
@@ -215,7 +218,8 @@ complete -c cf -n "__fish_seen_subcommand_from memory" -a status -d "Show memory
|
|
|
215
218
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a search -d "Search memories by query"
|
|
216
219
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a list -d "List memories (--projects for all DBs)"
|
|
217
220
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a rm -d "Remove a project database"
|
|
218
|
-
complete -c cf -n "__fish_seen_subcommand_from memory" -a init -d "Initialize
|
|
221
|
+
complete -c cf -n "__fish_seen_subcommand_from memory" -a init -d "Initialize memory system (interactive wizard)"
|
|
222
|
+
complete -c cf -n "__fish_seen_subcommand_from memory" -a config -d "Configure memory system settings"
|
|
219
223
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a start-daemon -d "Start the memory daemon (Tier 2)"
|
|
220
224
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a stop-daemon -d "Stop the memory daemon"
|
|
221
225
|
complete -c cf -n "__fish_seen_subcommand_from memory" -a rebuild -d "Rebuild the daemon search index"
|
|
@@ -229,9 +233,9 @@ var POWERSHELL_BLOCK = `
|
|
|
229
233
|
${MARKER_START}
|
|
230
234
|
Register-ArgumentCompleter -Native -CommandName cf -ScriptBlock {
|
|
231
235
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
232
|
-
$commands = @('install','uninstall','disable','enable','init','config','host','mcp','memory','permission','statusline','update','dev','session')
|
|
236
|
+
$commands = @('install','uninstall','disable','enable','init','config','host','mcp','memory','permission','statusline','update','status','dev','session')
|
|
233
237
|
$devSubcommands = @('on','off','status','restart','sync','update')
|
|
234
|
-
$memorySubcommands = @('status','search','list','rm','init','start-daemon','stop-daemon','rebuild','mcp')
|
|
238
|
+
$memorySubcommands = @('status','search','list','rm','init','config','start-daemon','stop-daemon','rebuild','mcp')
|
|
235
239
|
$sessionSubcommands = @('save','load')
|
|
236
240
|
$scopeFlags = @('--user','--global','--project','--local')
|
|
237
241
|
$updateFlags = @('--cli','--plugin','--statusline','--user','--global','--project','--local')
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-ORACWEDN.js";
|
|
5
5
|
import {
|
|
6
6
|
ensureShellCompletion
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NEQZP5D4.js";
|
|
8
8
|
import {
|
|
9
9
|
resolveScope
|
|
10
10
|
} from "./chunk-C5LYVVEI.js";
|
|
@@ -198,7 +198,7 @@ async function updateCommand(opts) {
|
|
|
198
198
|
log.dim(`stderr: ${result.stderr}`);
|
|
199
199
|
}
|
|
200
200
|
log.dim(
|
|
201
|
-
"Try manually:
|
|
201
|
+
"Try manually in Claude Code: /plugins \u2192 Installed \u2192 coding-friend \u2192 Update now \u2192 /reload-plugins"
|
|
202
202
|
);
|
|
203
203
|
}
|
|
204
204
|
}
|
|
@@ -255,6 +255,8 @@ async function updateCommand(opts) {
|
|
|
255
255
|
|
|
256
256
|
export {
|
|
257
257
|
semverCompare,
|
|
258
|
+
getCliVersion,
|
|
259
|
+
getLatestCliVersion,
|
|
258
260
|
getLatestVersion,
|
|
259
261
|
updateCommand
|
|
260
262
|
};
|
|
@@ -9,10 +9,33 @@ import {
|
|
|
9
9
|
} from "./chunk-RWUTFVRB.js";
|
|
10
10
|
|
|
11
11
|
// src/lib/config.ts
|
|
12
|
+
function deepMerge(base, override) {
|
|
13
|
+
const result = { ...base };
|
|
14
|
+
for (const key of Object.keys(override)) {
|
|
15
|
+
const baseVal = result[key];
|
|
16
|
+
const overVal = override[key];
|
|
17
|
+
if (baseVal && overVal && typeof baseVal === "object" && typeof overVal === "object" && !Array.isArray(baseVal) && !Array.isArray(overVal)) {
|
|
18
|
+
result[key] = deepMerge(
|
|
19
|
+
baseVal,
|
|
20
|
+
overVal
|
|
21
|
+
);
|
|
22
|
+
} else {
|
|
23
|
+
result[key] = overVal;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
12
28
|
function loadConfig() {
|
|
13
29
|
const global = readJson(globalConfigPath());
|
|
14
30
|
const local = readJson(localConfigPath());
|
|
15
|
-
|
|
31
|
+
const base = deepMerge(
|
|
32
|
+
DEFAULT_CONFIG,
|
|
33
|
+
global ?? {}
|
|
34
|
+
);
|
|
35
|
+
return deepMerge(
|
|
36
|
+
base,
|
|
37
|
+
local ?? {}
|
|
38
|
+
);
|
|
16
39
|
}
|
|
17
40
|
function resolveDocsDir(explicitPath) {
|
|
18
41
|
if (explicitPath) {
|
|
@@ -33,9 +56,6 @@ function resolveMemoryDir(explicitPath) {
|
|
|
33
56
|
return resolvePath(explicitPath);
|
|
34
57
|
}
|
|
35
58
|
const config = loadConfig();
|
|
36
|
-
if (config.memory?.docsDir) {
|
|
37
|
-
return resolvePath(config.memory.docsDir);
|
|
38
|
-
}
|
|
39
59
|
if (config.docsDir) {
|
|
40
60
|
return resolvePath(`${config.docsDir}/memory`);
|
|
41
61
|
}
|
|
@@ -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
|
+
};
|