coding-friend-cli 1.36.4 → 1.38.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 +8 -3
- package/dist/{chunk-GZT3YPAP.js → chunk-32UAW3NX.js} +1 -1
- package/dist/chunk-5QCGEPQO.js +238 -0
- package/dist/{chunk-YIXAQ4R2.js → chunk-6NXZKAOV.js} +1 -1
- package/dist/{chunk-XHP3EBR3.js → chunk-776ZY62N.js} +112 -4
- package/dist/{chunk-N4WZWRCI.js → chunk-BXSCYGWE.js} +85 -37
- package/dist/{chunk-AK6RMCAC.js → chunk-CGEMETKQ.js} +44 -30
- package/dist/{chunk-XEKQW4RN.js → chunk-ETTZRJ55.js} +1 -1
- package/dist/{chunk-IKLMHJH6.js → chunk-GIH6E7GJ.js} +62 -1
- package/dist/{chunk-56YJWGAN.js → chunk-ONG4KRYP.js} +60 -47
- package/dist/{chunk-X2H7JJMR.js → chunk-PLSJW7QJ.js} +29 -1
- package/dist/{chunk-O5ZKLFSL.js → chunk-RCE4OWNX.js} +1 -1
- package/dist/{chunk-K4WSC62G.js → chunk-RFNKPAJW.js} +42 -2
- package/dist/{clean-Q4VU662V.js → clean-YWEMYDE5.js} +3 -2
- package/dist/{config-CTTHWJH5.js → config-NG7SICM4.js} +15 -27
- package/dist/{dev-IGHMMEP4.js → dev-PTBNMKZT.js} +3 -3
- package/dist/{disable-HL5OEURT.js → disable-MO7AXYYF.js} +20 -3
- package/dist/{enable-CZIOCKKJ.js → enable-4GXLQEHD.js} +20 -3
- package/dist/{guide-CO2V4ZLZ.js → guide-HCR43LJN.js} +1 -1
- package/dist/{host-NCJMUSME.js → host-EV6QIVTI.js} +3 -3
- package/dist/index.js +62 -48
- package/dist/{init-UABKVDVT.js → init-KYE5CJMW.js} +135 -17
- package/dist/{install-R2HDM3N4.js → install-XAFQY5YF.js} +80 -5
- package/dist/{learn-K64AUUFB.js → learn-MC4VDHZH.js} +2 -2
- package/dist/{mcp-OMQFSJP4.js → mcp-FV6YX3ZT.js} +24 -24
- package/dist/{mcp-serve-3FEPFVVQ.js → mcp-serve-PXREYQRZ.js} +10 -1
- package/dist/{mcp-serve-learn-OF3ZJUJC.js → mcp-serve-learn-UVXIDOEZ.js} +1 -1
- package/dist/{memory-Y6GNALG3.js → memory-DF7OUOTV.js} +7 -7
- package/dist/{permission-I3OFJUYA.js → permission-VISPGMUK.js} +38 -4
- package/dist/{permissions-J46VP6US.js → permissions-2SBACQHV.js} +2 -2
- package/dist/{session-Q2DLQPYP.js → session-QK6OZO3X.js} +3 -2
- package/dist/{status-ML4XEDYL.js → status-FVFTJ6GS.js} +18 -14
- package/dist/{statusline-KKFJPTI7.js → statusline-AOZHEQSV.js} +2 -2
- package/dist/{uninstall-ASLHT3MH.js → uninstall-MF6FRUBO.js} +64 -3
- package/dist/{update-DIOH45TE.js → update-QNUCSDSA.js} +7 -4
- package/dist/{update-check-25SCLHZO.js → update-check-7LKJLJPM.js} +1 -1
- package/lib/cf-memory/src/backends/sqlite/index.ts +142 -85
- package/lib/cf-memory/src/lib/claude-md.ts +82 -70
- package/lib/cf-memory/src/lib/status-frame.ts +2 -2
- package/lib/cf-memory/src/tools/delete.ts +1 -1
- package/lib/cf-memory/src/tools/store.ts +6 -6
- package/lib/cf-memory/src/tools/update.ts +5 -11
- package/package.json +32 -4
- package/lib/cf-memory/README.md +0 -317
- package/lib/cf-memory/src/__tests__/claude-md.test.ts +0 -343
- package/lib/cf-memory/src/__tests__/daemon-client.test.ts +0 -146
- package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +0 -223
- package/lib/cf-memory/src/__tests__/daemon.test.ts +0 -407
- package/lib/cf-memory/src/__tests__/dedup.test.ts +0 -103
- package/lib/cf-memory/src/__tests__/embeddings.test.ts +0 -292
- package/lib/cf-memory/src/__tests__/lazy-install.test.ts +0 -210
- package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +0 -450
- package/lib/cf-memory/src/__tests__/migration.test.ts +0 -255
- package/lib/cf-memory/src/__tests__/migrations.test.ts +0 -288
- package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +0 -262
- package/lib/cf-memory/src/__tests__/ollama.test.ts +0 -48
- package/lib/cf-memory/src/__tests__/project-id.test.ts +0 -43
- package/lib/cf-memory/src/__tests__/schema.test.ts +0 -128
- package/lib/cf-memory/src/__tests__/search.test.ts +0 -115
- package/lib/cf-memory/src/__tests__/status-frame.test.ts +0 -136
- package/lib/cf-memory/src/__tests__/temporal-decay.test.ts +0 -54
- package/lib/cf-memory/src/__tests__/tier.test.ts +0 -367
- package/lib/cf-memory/src/__tests__/tools.test.ts +0 -83
- package/lib/cf-memory/vitest.config.ts +0 -7
- package/lib/learn-host/.prettierignore +0 -4
- package/lib/learn-host/.prettierrc +0 -8
- package/lib/learn-host/README.md +0 -113
- package/lib/learn-host/eslint.config.mjs +0 -6
- package/lib/learn-mcp/README.md +0 -187
- package/lib/learn-mcp/src/__tests__/docs.test.ts +0 -117
- package/lib/learn-mcp/vitest.config.ts +0 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# coding-friend-cli
|
|
2
2
|
|
|
3
|
-
CLI companion for the [coding-friend](https://github.com/dinhanhthi/coding-friend) plugin — a lean toolkit for disciplined engineering workflows with Claude Code.
|
|
3
|
+
CLI companion for the [coding-friend](https://github.com/dinhanhthi/coding-friend) plugin — a lean toolkit for disciplined engineering workflows with Claude Code and Codex CLI.
|
|
4
4
|
|
|
5
5
|
## Plugin without the CLI?
|
|
6
6
|
|
|
@@ -17,13 +17,13 @@ Everything else (every skill, every agent, every hook except the three OPTIONAL
|
|
|
17
17
|
- **`node` not found** — install Node.js 20+ from <https://nodejs.org> (the CLI declares `engines.node >= 20`; older versions will refuse to install).
|
|
18
18
|
- **`EACCES` on global install** — set a user-writable global prefix: `npm config set prefix ~/.npm-global` and add `~/.npm-global/bin` to your `PATH`.
|
|
19
19
|
- **`cf` not on PATH after install** — verify with `npm root -g` and ensure that directory's `bin/` is in your `PATH`.
|
|
20
|
-
- **MCP server not appearing in Claude Code** — after install, run `cf mcp` to register
|
|
20
|
+
- **MCP server not appearing in Claude Code** — after install, run `cf mcp` to register both Learn and Memory MCP at user scope, then restart Claude Code.
|
|
21
21
|
|
|
22
22
|
## Requirements
|
|
23
23
|
|
|
24
24
|
- Node.js >= 20
|
|
25
25
|
- npm (included with Node.js, but on some Linux distros you may need to install it separately)
|
|
26
|
-
- The [coding-friend plugin](https://github.com/dinhanhthi/coding-friend) installed in Claude Code
|
|
26
|
+
- The [coding-friend plugin](https://github.com/dinhanhthi/coding-friend) installed in Claude Code or Codex CLI
|
|
27
27
|
|
|
28
28
|
### Additional requirements for `cf memory init`
|
|
29
29
|
|
|
@@ -56,12 +56,14 @@ cf install --user # Install at user scope (all projects)
|
|
|
56
56
|
cf install --global # Same as --user
|
|
57
57
|
cf install --project # Install at project scope (shared via git)
|
|
58
58
|
cf install --local # Install at local scope (this machine only)
|
|
59
|
+
cf install --agent codex # Register/install Coding Friend for Codex CLI
|
|
59
60
|
# 💡 Safe to run multiple times (idempotent).
|
|
60
61
|
cf uninstall # Uninstall plugin (interactive scope chooser)
|
|
61
62
|
cf uninstall --user # Uninstall from user scope (full cleanup)
|
|
62
63
|
cf uninstall --global # Same as --user
|
|
63
64
|
cf uninstall --project # Uninstall from project scope only
|
|
64
65
|
cf uninstall --local # Uninstall from local scope only
|
|
66
|
+
cf uninstall --agent codex # Disable Coding Friend in Codex CLI
|
|
65
67
|
# 💡 Interactive — asks for confirmation before acting.
|
|
66
68
|
cf disable # Disable plugin (interactive scope chooser)
|
|
67
69
|
cf disable --user # Disable at user scope (all projects)
|
|
@@ -75,6 +77,7 @@ cf enable --global # Same as --user
|
|
|
75
77
|
cf enable --project # Enable at project scope
|
|
76
78
|
cf enable --local # Enable at local scope
|
|
77
79
|
cf init # Initialize workspace (interactive)
|
|
80
|
+
cf init --agent codex # Initialize workspace for Codex CLI
|
|
78
81
|
# 💡 You can run this anywhere, anytime.
|
|
79
82
|
cf config # Manage Coding Friend configuration (interactive menu)
|
|
80
83
|
# 💡 Edit docsDir, language, learn settings, and more.
|
|
@@ -89,6 +92,7 @@ cf permission # Manage Claude Code permission rules (interactive)
|
|
|
89
92
|
cf permission --all # Apply all recommended permissions without prompts
|
|
90
93
|
cf permission --user # Save to user-level settings (~/.claude/settings.json)
|
|
91
94
|
cf permission --project # Save to project-level settings (.claude/settings.local.json)
|
|
95
|
+
cf permission --agent codex --enable-auto-approve
|
|
92
96
|
cf permission --all --user # Apply all recommended permissions to user settings
|
|
93
97
|
cf statusline # Setup coding-friend statusline
|
|
94
98
|
cf update # Update plugin + CLI + statusline
|
|
@@ -97,6 +101,7 @@ cf update # Update plugin + CLI + statusline
|
|
|
97
101
|
cf update --cli # Update only the CLI (npm package)
|
|
98
102
|
cf update --plugin # Update only the Claude Code plugin
|
|
99
103
|
cf update --statusline # Update only the statusline
|
|
104
|
+
cf update --agent codex # Update Codex marketplace/plugin cache
|
|
100
105
|
cf update --project # Update plugin at project scope
|
|
101
106
|
cf update --local # Update plugin at local scope
|
|
102
107
|
cf dev on [path] # Switch to local plugin source for development
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codexAgentsDir,
|
|
3
|
+
codexConfigTomlPath,
|
|
4
|
+
codexInstalledPluginsPath
|
|
5
|
+
} from "./chunk-PLSJW7QJ.js";
|
|
6
|
+
|
|
7
|
+
// src/lib/codex-config.ts
|
|
8
|
+
import {
|
|
9
|
+
copyFileSync,
|
|
10
|
+
existsSync,
|
|
11
|
+
mkdirSync,
|
|
12
|
+
readdirSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
writeFileSync
|
|
16
|
+
} from "fs";
|
|
17
|
+
import { basename, dirname, join, resolve } from "path";
|
|
18
|
+
var CODEX_MARKETPLACE_NAME = "coding-friend-marketplace";
|
|
19
|
+
var CODEX_PLUGIN_NAME = "coding-friend";
|
|
20
|
+
var CODEX_PLUGIN_ID = `${CODEX_PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}`;
|
|
21
|
+
var CODEX_PLUGIN_TABLE = `plugins."${CODEX_PLUGIN_ID}"`;
|
|
22
|
+
var CODEX_MEMORY_MCP_TABLE = "mcp_servers.coding-friend-memory";
|
|
23
|
+
function readToml(filePath) {
|
|
24
|
+
try {
|
|
25
|
+
return readFileSync(filePath, "utf8");
|
|
26
|
+
} catch {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function writeToml(filePath, content) {
|
|
31
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
32
|
+
writeFileSync(filePath, content.endsWith("\n") ? content : `${content}
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
35
|
+
function tomlString(value) {
|
|
36
|
+
return JSON.stringify(value);
|
|
37
|
+
}
|
|
38
|
+
function tableHeader(tableName) {
|
|
39
|
+
return `[${tableName}]`;
|
|
40
|
+
}
|
|
41
|
+
function findTableRange(lines, header) {
|
|
42
|
+
const start = lines.findIndex((line) => line.trim() === header);
|
|
43
|
+
if (start === -1) return null;
|
|
44
|
+
let end = lines.length;
|
|
45
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
46
|
+
if (/^\s*\[[^\]]+\]\s*$/.test(lines[index])) {
|
|
47
|
+
end = index;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { start, end };
|
|
52
|
+
}
|
|
53
|
+
function replaceTable(content, tableName, body) {
|
|
54
|
+
const header = tableHeader(tableName);
|
|
55
|
+
const lines = content.split(/\n/);
|
|
56
|
+
const range = findTableRange(lines, header);
|
|
57
|
+
const nextBlock = [header, ...body];
|
|
58
|
+
if (range) {
|
|
59
|
+
lines.splice(range.start, range.end - range.start, ...nextBlock);
|
|
60
|
+
return `${lines.join("\n").replace(/\n*$/, "")}
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
const prefix = content.replace(/\n*$/, "");
|
|
64
|
+
return `${prefix}${prefix ? "\n\n" : ""}${nextBlock.join("\n")}
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
function upsertTableKey(content, tableName, key, valueLine) {
|
|
68
|
+
const header = tableHeader(tableName);
|
|
69
|
+
const lines = content.split(/\n/);
|
|
70
|
+
const range = findTableRange(lines, header);
|
|
71
|
+
if (!range) {
|
|
72
|
+
return replaceTable(content, tableName, [valueLine]);
|
|
73
|
+
}
|
|
74
|
+
const keyPattern = new RegExp(`^\\s*${key}\\s*=`);
|
|
75
|
+
let replaced = false;
|
|
76
|
+
for (let index = range.start + 1; index < range.end; index += 1) {
|
|
77
|
+
if (keyPattern.test(lines[index])) {
|
|
78
|
+
lines[index] = valueLine;
|
|
79
|
+
replaced = true;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!replaced) {
|
|
84
|
+
lines.splice(range.end, 0, valueLine);
|
|
85
|
+
}
|
|
86
|
+
return `${lines.join("\n").replace(/\n*$/, "")}
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
function getTable(content, tableName) {
|
|
90
|
+
const lines = content.split(/\n/);
|
|
91
|
+
const range = findTableRange(lines, tableHeader(tableName));
|
|
92
|
+
if (!range) return [];
|
|
93
|
+
return lines.slice(range.start + 1, range.end);
|
|
94
|
+
}
|
|
95
|
+
function isCodexMarketplaceRegistered(configPath = codexConfigTomlPath()) {
|
|
96
|
+
return readToml(configPath).split(/\n/).some(
|
|
97
|
+
(line) => line.trim() === tableHeader(`marketplaces.${CODEX_MARKETPLACE_NAME}`)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
function setCodexPluginEnabled(enabled, configPath = codexConfigTomlPath()) {
|
|
101
|
+
const content = readToml(configPath);
|
|
102
|
+
writeToml(
|
|
103
|
+
configPath,
|
|
104
|
+
upsertTableKey(
|
|
105
|
+
content,
|
|
106
|
+
CODEX_PLUGIN_TABLE,
|
|
107
|
+
"enabled",
|
|
108
|
+
`enabled = ${enabled ? "true" : "false"}`
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function isCodexPluginDisabled(configPath = codexConfigTomlPath()) {
|
|
113
|
+
const table = getTable(readToml(configPath), CODEX_PLUGIN_TABLE);
|
|
114
|
+
return table.some((line) => /^\s*enabled\s*=\s*false\s*(?:#.*)?$/.test(line));
|
|
115
|
+
}
|
|
116
|
+
function removeTable(content, tableName) {
|
|
117
|
+
const lines = content.split(/\n/);
|
|
118
|
+
const range = findTableRange(lines, tableHeader(tableName));
|
|
119
|
+
if (!range) return content;
|
|
120
|
+
let start = range.start;
|
|
121
|
+
while (start > 0 && lines[start - 1].trim() === "") start -= 1;
|
|
122
|
+
lines.splice(start, range.end - start);
|
|
123
|
+
return `${lines.join("\n").replace(/\n*$/, "")}
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
function removeCodexMemoryMcpConfig(configPath = codexConfigTomlPath()) {
|
|
127
|
+
const content = readToml(configPath);
|
|
128
|
+
const next = removeTable(content, CODEX_MEMORY_MCP_TABLE);
|
|
129
|
+
if (next === content) return false;
|
|
130
|
+
writeToml(configPath, next);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
function removeDeployedCodexAgents(targetDir = codexAgentsDir()) {
|
|
134
|
+
if (!existsSync(targetDir)) return 0;
|
|
135
|
+
let removed = 0;
|
|
136
|
+
for (const entry of readdirSync(targetDir, { withFileTypes: true })) {
|
|
137
|
+
if (!entry.isFile() || !/^cf-.*\.toml$/.test(entry.name)) continue;
|
|
138
|
+
rmSync(join(targetDir, entry.name));
|
|
139
|
+
removed += 1;
|
|
140
|
+
}
|
|
141
|
+
return removed;
|
|
142
|
+
}
|
|
143
|
+
function writeCodexMemoryMcpConfig(memoryDir, configPath = codexConfigTomlPath()) {
|
|
144
|
+
const content = readToml(configPath);
|
|
145
|
+
writeToml(
|
|
146
|
+
configPath,
|
|
147
|
+
replaceTable(content, CODEX_MEMORY_MCP_TABLE, [
|
|
148
|
+
'command = "npx"',
|
|
149
|
+
`args = ["-y", "coding-friend-cli", "mcp-serve", ${tomlString(memoryDir)}]`
|
|
150
|
+
])
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
function writeCodexAgentLimits(configPath = codexConfigTomlPath()) {
|
|
154
|
+
const content = readToml(configPath);
|
|
155
|
+
writeToml(
|
|
156
|
+
configPath,
|
|
157
|
+
upsertTableKey(content, "agents", "max_depth", "max_depth = 2")
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
function trustCodexProject(projectPath = process.cwd(), configPath = codexConfigTomlPath()) {
|
|
161
|
+
const tableName = `projects.${tomlString(projectPath)}`;
|
|
162
|
+
const content = readToml(configPath);
|
|
163
|
+
writeToml(
|
|
164
|
+
configPath,
|
|
165
|
+
upsertTableKey(
|
|
166
|
+
content,
|
|
167
|
+
tableName,
|
|
168
|
+
"trust_level",
|
|
169
|
+
'trust_level = "trusted"'
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
function generatedCodexAgentsDir(repoRoot = process.cwd()) {
|
|
174
|
+
return resolve(repoRoot, "plugin-codex", "agents");
|
|
175
|
+
}
|
|
176
|
+
function compareVersions(a, b) {
|
|
177
|
+
const left = a.split(".").map((part) => Number.parseInt(part, 10));
|
|
178
|
+
const right = b.split(".").map((part) => Number.parseInt(part, 10));
|
|
179
|
+
for (let index = 0; index < 3; index += 1) {
|
|
180
|
+
const diff = (left[index] || 0) - (right[index] || 0);
|
|
181
|
+
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
182
|
+
}
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
function latestCodexInstalledPluginDir(pluginRoot = codexInstalledPluginsPath()) {
|
|
186
|
+
const directManifest = join(pluginRoot, ".codex-plugin", "plugin.json");
|
|
187
|
+
if (existsSync(directManifest)) return pluginRoot;
|
|
188
|
+
if (!existsSync(pluginRoot)) return null;
|
|
189
|
+
const versions = readdirSync(pluginRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter(
|
|
190
|
+
(version) => existsSync(join(pluginRoot, version, ".codex-plugin", "plugin.json"))
|
|
191
|
+
).sort(compareVersions).reverse();
|
|
192
|
+
return versions[0] ? join(pluginRoot, versions[0]) : null;
|
|
193
|
+
}
|
|
194
|
+
function getCodexInstalledVersion(pluginRoot = codexInstalledPluginsPath()) {
|
|
195
|
+
const pluginDir = latestCodexInstalledPluginDir(pluginRoot);
|
|
196
|
+
if (!pluginDir) return null;
|
|
197
|
+
try {
|
|
198
|
+
const manifest = JSON.parse(
|
|
199
|
+
readFileSync(join(pluginDir, ".codex-plugin", "plugin.json"), "utf8")
|
|
200
|
+
);
|
|
201
|
+
return manifest.version ?? basename(pluginDir);
|
|
202
|
+
} catch {
|
|
203
|
+
return basename(pluginDir);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function findCodexAgentSourceDir(repoRoot = process.cwd()) {
|
|
207
|
+
const generated = generatedCodexAgentsDir(repoRoot);
|
|
208
|
+
if (existsSync(generated)) return generated;
|
|
209
|
+
const installedPluginDir = latestCodexInstalledPluginDir();
|
|
210
|
+
const installedAgents = installedPluginDir ? join(installedPluginDir, "agents") : null;
|
|
211
|
+
return installedAgents && existsSync(installedAgents) ? installedAgents : null;
|
|
212
|
+
}
|
|
213
|
+
function deployCodexAgents(sourceDir, targetDir = codexAgentsDir()) {
|
|
214
|
+
if (!existsSync(sourceDir)) return 0;
|
|
215
|
+
mkdirSync(targetDir, { recursive: true });
|
|
216
|
+
let copied = 0;
|
|
217
|
+
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
218
|
+
if (!entry.isFile() || !entry.name.endsWith(".toml")) continue;
|
|
219
|
+
copyFileSync(join(sourceDir, entry.name), join(targetDir, entry.name));
|
|
220
|
+
copied += 1;
|
|
221
|
+
}
|
|
222
|
+
return copied;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export {
|
|
226
|
+
CODEX_MARKETPLACE_NAME,
|
|
227
|
+
isCodexMarketplaceRegistered,
|
|
228
|
+
setCodexPluginEnabled,
|
|
229
|
+
isCodexPluginDisabled,
|
|
230
|
+
removeCodexMemoryMcpConfig,
|
|
231
|
+
removeDeployedCodexAgents,
|
|
232
|
+
writeCodexMemoryMcpConfig,
|
|
233
|
+
writeCodexAgentLimits,
|
|
234
|
+
trustCodexProject,
|
|
235
|
+
getCodexInstalledVersion,
|
|
236
|
+
findCodexAgentSourceDir,
|
|
237
|
+
deployCodexAgents
|
|
238
|
+
};
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureStatusline,
|
|
3
3
|
getInstalledVersion
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ETTZRJ55.js";
|
|
5
5
|
import {
|
|
6
6
|
ensureShellCompletion
|
|
7
7
|
} from "./chunk-667NMPN4.js";
|
|
8
8
|
import {
|
|
9
|
+
isMemoryMcpRegistered,
|
|
10
|
+
registerMemoryMcp,
|
|
11
|
+
removeMemoryMcpEntry
|
|
12
|
+
} from "./chunk-BXSCYGWE.js";
|
|
13
|
+
import {
|
|
14
|
+
CODEX_MARKETPLACE_NAME,
|
|
15
|
+
getCodexInstalledVersion
|
|
16
|
+
} from "./chunk-5QCGEPQO.js";
|
|
17
|
+
import {
|
|
18
|
+
resolveHostFlags,
|
|
9
19
|
resolveScope
|
|
10
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-GIH6E7GJ.js";
|
|
11
21
|
import {
|
|
12
22
|
commandExists,
|
|
13
23
|
run,
|
|
@@ -19,7 +29,7 @@ import {
|
|
|
19
29
|
} from "./chunk-5UVDWG5L.js";
|
|
20
30
|
import {
|
|
21
31
|
claudeSettingsPath
|
|
22
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-PLSJW7QJ.js";
|
|
23
33
|
import {
|
|
24
34
|
log,
|
|
25
35
|
printBanner
|
|
@@ -89,6 +99,11 @@ function getStatuslineVersion() {
|
|
|
89
99
|
return match?.[1]?.replace(/^v/, "") ?? null;
|
|
90
100
|
}
|
|
91
101
|
async function updateCommand(opts) {
|
|
102
|
+
const { host } = resolveHostFlags(opts);
|
|
103
|
+
if (host === "codex") {
|
|
104
|
+
await updateCodexCommand(opts);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
92
107
|
const updateAll = !opts.cli && !opts.plugin && !opts.statusline;
|
|
93
108
|
const doCli = updateAll || !!opts.cli;
|
|
94
109
|
const doPlugin = updateAll || !!opts.plugin;
|
|
@@ -249,9 +264,24 @@ async function updateCommand(opts) {
|
|
|
249
264
|
}
|
|
250
265
|
}
|
|
251
266
|
if (doCli) {
|
|
252
|
-
const { refreshMemoryAfterUpdate } = await import("./memory-
|
|
267
|
+
const { refreshMemoryAfterUpdate } = await import("./memory-DF7OUOTV.js");
|
|
253
268
|
await refreshMemoryAfterUpdate();
|
|
254
269
|
}
|
|
270
|
+
if (!isMemoryMcpRegistered()) {
|
|
271
|
+
registerMemoryMcp();
|
|
272
|
+
}
|
|
273
|
+
const migrationResult = removeMemoryMcpEntry();
|
|
274
|
+
if (migrationResult.removed) {
|
|
275
|
+
if (migrationResult.fileDeleted) {
|
|
276
|
+
log.success(
|
|
277
|
+
"Removed legacy project-scope coding-friend-memory from .mcp.json (file deleted \u2014 was empty)."
|
|
278
|
+
);
|
|
279
|
+
} else {
|
|
280
|
+
log.success(
|
|
281
|
+
"Removed legacy project-scope coding-friend-memory from .mcp.json"
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
255
285
|
if (doStatusline) {
|
|
256
286
|
const updatedVersion = ensureStatusline();
|
|
257
287
|
if (updatedVersion) {
|
|
@@ -262,6 +292,84 @@ async function updateCommand(opts) {
|
|
|
262
292
|
console.log();
|
|
263
293
|
log.dim("Restart Claude Code (or start a new session) to see changes.");
|
|
264
294
|
}
|
|
295
|
+
async function updateCodexCommand(opts) {
|
|
296
|
+
const updateAll = !opts.cli && !opts.plugin && !opts.statusline;
|
|
297
|
+
const doCli = updateAll || !!opts.cli;
|
|
298
|
+
const doPlugin = updateAll || !!opts.plugin;
|
|
299
|
+
printBanner("\u2728 Coding Friend Codex Update \u2728");
|
|
300
|
+
console.log();
|
|
301
|
+
if (doPlugin) {
|
|
302
|
+
const beforeVersion = getCodexInstalledVersion();
|
|
303
|
+
console.log(
|
|
304
|
+
` Plugin ${beforeVersion ? beforeVersion : chalk.dim("not installed")}`
|
|
305
|
+
);
|
|
306
|
+
if (!commandExists("codex")) {
|
|
307
|
+
log.error("Codex CLI not found. Cannot update Codex plugin.");
|
|
308
|
+
} else {
|
|
309
|
+
log.step("Updating Codex marketplace...");
|
|
310
|
+
const result = runWithStderr("codex", [
|
|
311
|
+
"plugin",
|
|
312
|
+
"marketplace",
|
|
313
|
+
"upgrade",
|
|
314
|
+
CODEX_MARKETPLACE_NAME
|
|
315
|
+
]);
|
|
316
|
+
if (result.exitCode !== 0) {
|
|
317
|
+
log.error(
|
|
318
|
+
`Codex marketplace upgrade failed. Try manually: codex plugin marketplace upgrade ${CODEX_MARKETPLACE_NAME}`
|
|
319
|
+
);
|
|
320
|
+
if (result.stderr) log.dim(`stderr: ${result.stderr}`);
|
|
321
|
+
} else {
|
|
322
|
+
const afterVersion = getCodexInstalledVersion();
|
|
323
|
+
if (afterVersion && afterVersion !== beforeVersion) {
|
|
324
|
+
log.success(
|
|
325
|
+
`Codex plugin updated to ${chalk.green(`v${afterVersion}`)}.`
|
|
326
|
+
);
|
|
327
|
+
} else if (afterVersion) {
|
|
328
|
+
log.success(
|
|
329
|
+
`Codex plugin is installed at ${chalk.green(`v${afterVersion}`)}.`
|
|
330
|
+
);
|
|
331
|
+
} else {
|
|
332
|
+
log.warn(
|
|
333
|
+
"Marketplace updated, but coding-friend is not installed yet. Open Codex and run /plugins to install it."
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (doCli) {
|
|
340
|
+
const cliVersion = getCliVersion();
|
|
341
|
+
const latestCliVersion = getLatestCliVersion();
|
|
342
|
+
if (!latestCliVersion) {
|
|
343
|
+
log.warn("Cannot check latest CLI version from npm.");
|
|
344
|
+
} else {
|
|
345
|
+
const cmp = semverCompare(cliVersion, latestCliVersion);
|
|
346
|
+
if (cmp < 0) {
|
|
347
|
+
log.step(
|
|
348
|
+
`CLI update available: ${chalk.yellow(`v${cliVersion}`)} \u2192 ${chalk.green(`v${latestCliVersion}`)}`
|
|
349
|
+
);
|
|
350
|
+
const result = run("npm", [
|
|
351
|
+
"install",
|
|
352
|
+
"-g",
|
|
353
|
+
"coding-friend-cli@latest"
|
|
354
|
+
]);
|
|
355
|
+
if (result === null) {
|
|
356
|
+
log.error(
|
|
357
|
+
"CLI update failed. Try manually: npm install -g coding-friend-cli@latest"
|
|
358
|
+
);
|
|
359
|
+
} else {
|
|
360
|
+
log.success(`CLI updated to ${chalk.green(`v${latestCliVersion}`)}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (opts.statusline) {
|
|
366
|
+
log.warn(
|
|
367
|
+
"Codex owns its TUI footer. Open Codex and run /statusline to configure it."
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
console.log();
|
|
371
|
+
log.dim("Restart Codex CLI (or start a new session) to see changes.");
|
|
372
|
+
}
|
|
265
373
|
|
|
266
374
|
export {
|
|
267
375
|
semverCompare,
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
resolveMemoryDir
|
|
3
|
-
} from "./chunk-K4WSC62G.js";
|
|
4
1
|
import {
|
|
5
2
|
getLibPath
|
|
6
3
|
} from "./chunk-RZRT7NGT.js";
|
|
@@ -9,7 +6,10 @@ import {
|
|
|
9
6
|
askScope,
|
|
10
7
|
formatScopeLabel,
|
|
11
8
|
injectBackChoice
|
|
12
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GIH6E7GJ.js";
|
|
10
|
+
import {
|
|
11
|
+
runWithStderr
|
|
12
|
+
} from "./chunk-EVGXUDX4.js";
|
|
13
13
|
import {
|
|
14
14
|
mergeJson,
|
|
15
15
|
readJson,
|
|
@@ -19,15 +19,49 @@ import {
|
|
|
19
19
|
claudeConfigDir,
|
|
20
20
|
globalConfigPath,
|
|
21
21
|
localConfigPath
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-PLSJW7QJ.js";
|
|
23
23
|
import {
|
|
24
24
|
log
|
|
25
25
|
} from "./chunk-NREZK463.js";
|
|
26
26
|
|
|
27
|
+
// src/lib/memory-mcp-register.ts
|
|
28
|
+
var MCP_NAME = "coding-friend-memory";
|
|
29
|
+
function registerMemoryMcp() {
|
|
30
|
+
const result = runWithStderr("claude", [
|
|
31
|
+
"mcp",
|
|
32
|
+
"add",
|
|
33
|
+
"--scope",
|
|
34
|
+
"user",
|
|
35
|
+
MCP_NAME,
|
|
36
|
+
"--",
|
|
37
|
+
"npx",
|
|
38
|
+
"-y",
|
|
39
|
+
"coding-friend-cli",
|
|
40
|
+
"mcp-serve"
|
|
41
|
+
]);
|
|
42
|
+
if (result.exitCode !== 0) {
|
|
43
|
+
const stderr = result.stderr ?? "";
|
|
44
|
+
if (stderr.includes("ENOENT") || stderr.includes("command not found")) {
|
|
45
|
+
log.warn(
|
|
46
|
+
`claude CLI not found \u2014 add MCP manually:
|
|
47
|
+
claude mcp add --scope user ${MCP_NAME} -- npx -y coding-friend-cli mcp-serve`
|
|
48
|
+
);
|
|
49
|
+
} else {
|
|
50
|
+
log.warn(`Could not register MCP: ${stderr || "unknown error"}`);
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function isMemoryMcpRegistered() {
|
|
57
|
+
const result = runWithStderr("claude", ["mcp", "get", MCP_NAME]);
|
|
58
|
+
return result.exitCode === 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
// src/lib/memory-prompts.ts
|
|
28
62
|
import { confirm, input, select } from "@inquirer/prompts";
|
|
63
|
+
import { rmSync } from "fs";
|
|
29
64
|
import chalk from "chalk";
|
|
30
|
-
import { homedir } from "os";
|
|
31
65
|
import { join } from "path";
|
|
32
66
|
function getMemoryFieldScope(field, globalCfg, localCfg) {
|
|
33
67
|
const localSection = localCfg?.memory;
|
|
@@ -216,54 +250,66 @@ async function editMemoryEmbedding(globalCfg, localCfg) {
|
|
|
216
250
|
if (ollamaUrl) embedding.ollamaUrl = ollamaUrl;
|
|
217
251
|
writeMemoryField(scope, "embedding", embedding);
|
|
218
252
|
}
|
|
219
|
-
function
|
|
253
|
+
function removeMemoryMcpEntry() {
|
|
220
254
|
const mcpPath = join(process.cwd(), ".mcp.json");
|
|
221
|
-
const existing = readJson(mcpPath)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
255
|
+
const existing = readJson(mcpPath);
|
|
256
|
+
if (existing == null) {
|
|
257
|
+
return { removed: false, fileDeleted: false };
|
|
258
|
+
}
|
|
259
|
+
const servers = existing.mcpServers;
|
|
260
|
+
if (servers == null || typeof servers !== "object" || Array.isArray(servers) || !("coding-friend-memory" in servers)) {
|
|
261
|
+
return { removed: false, fileDeleted: false };
|
|
262
|
+
}
|
|
263
|
+
delete servers["coding-friend-memory"];
|
|
264
|
+
if (Object.keys(servers).length === 0) {
|
|
265
|
+
delete existing.mcpServers;
|
|
266
|
+
}
|
|
267
|
+
if (Object.keys(existing).length === 0) {
|
|
268
|
+
rmSync(mcpPath, { force: true });
|
|
269
|
+
return { removed: true, fileDeleted: true };
|
|
270
|
+
}
|
|
271
|
+
writeJson(mcpPath, existing);
|
|
272
|
+
return { removed: true, fileDeleted: false };
|
|
234
273
|
}
|
|
235
|
-
function getMemoryMcpStatus() {
|
|
274
|
+
function getMemoryMcpStatus(checkIsRegistered = isMemoryMcpRegistered) {
|
|
236
275
|
const localMcpPath = join(process.cwd(), ".mcp.json");
|
|
237
276
|
const localMcp = readJson(localMcpPath);
|
|
238
277
|
const localServers = localMcp?.mcpServers;
|
|
278
|
+
const userScope = checkIsRegistered();
|
|
279
|
+
if (userScope) {
|
|
280
|
+
const hasShadow = localServers != null && "coding-friend-memory" in localServers;
|
|
281
|
+
return {
|
|
282
|
+
configured: true,
|
|
283
|
+
scope: hasShadow ? "local" : "user",
|
|
284
|
+
userScope: true
|
|
285
|
+
};
|
|
286
|
+
}
|
|
239
287
|
if (localServers != null && "coding-friend-memory" in localServers) {
|
|
240
|
-
return { configured: true, scope: "local" };
|
|
288
|
+
return { configured: true, scope: "local", userScope: false };
|
|
241
289
|
}
|
|
242
290
|
const globalMcpPath = join(claudeConfigDir(), ".mcp.json");
|
|
243
291
|
const globalMcp = readJson(globalMcpPath);
|
|
244
292
|
const globalServers = globalMcp?.mcpServers;
|
|
245
293
|
if (globalServers != null && "coding-friend-memory" in globalServers) {
|
|
246
|
-
return { configured: true, scope: "global" };
|
|
294
|
+
return { configured: true, scope: "global", userScope: false };
|
|
247
295
|
}
|
|
248
|
-
return { configured: false, scope: null };
|
|
296
|
+
return { configured: false, scope: null, userScope: false };
|
|
249
297
|
}
|
|
250
298
|
async function editMemoryMcp() {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const globalMcpDisplay = join(claudeConfigDir(), ".mcp.json").replace(
|
|
254
|
-
homedir(),
|
|
255
|
-
"~"
|
|
256
|
-
);
|
|
257
|
-
const label = status.scope === "local" ? chalk.green("configured") + chalk.dim(" (local .mcp.json)") : chalk.green("configured") + chalk.dim(` (global ${globalMcpDisplay})`) + " " + chalk.yellow("\u26A0 only works for one project");
|
|
258
|
-
log.info(`MCP: ${label}`);
|
|
299
|
+
if (isMemoryMcpRegistered()) {
|
|
300
|
+
log.info(`MCP: ${chalk.green("registered")} (user scope)`);
|
|
259
301
|
const reconfigure = await confirm({
|
|
260
|
-
message: "
|
|
302
|
+
message: "Re-register Memory MCP (user scope)?",
|
|
261
303
|
default: false
|
|
262
304
|
});
|
|
263
305
|
if (!reconfigure) return;
|
|
264
306
|
}
|
|
265
|
-
const
|
|
266
|
-
|
|
307
|
+
const ok = registerMemoryMcp();
|
|
308
|
+
if (ok) {
|
|
309
|
+
log.success(
|
|
310
|
+
"Registered coding-friend-memory (user scope). Restart Claude Code to activate."
|
|
311
|
+
);
|
|
312
|
+
}
|
|
267
313
|
}
|
|
268
314
|
async function memoryConfigMenu(opts) {
|
|
269
315
|
while (true) {
|
|
@@ -303,7 +349,7 @@ async function memoryConfigMenu(opts) {
|
|
|
303
349
|
);
|
|
304
350
|
const embeddingLabel = embeddingVal?.provider ? embeddingVal.model ? `${embeddingVal.model} (${embeddingVal.provider})` : embeddingVal.provider : "";
|
|
305
351
|
const mcpStatus = getMemoryMcpStatus();
|
|
306
|
-
const mcpLabel = mcpStatus.
|
|
352
|
+
const mcpLabel = mcpStatus.userScope ? mcpStatus.scope === "local" ? chalk.green("registered") + chalk.dim(" (user scope)") + " " + chalk.yellow("\u26A0 shadow") : chalk.green("registered") + chalk.dim(" (user scope)") : mcpStatus.configured ? mcpStatus.scope === "local" ? chalk.yellow("configured") + chalk.dim(" (.mcp.json)") : chalk.yellow("configured") + chalk.dim(" (global)") + " " + chalk.yellow("\u26A0") : chalk.dim("not registered");
|
|
307
353
|
const choice = await select({
|
|
308
354
|
message: "Memory settings:",
|
|
309
355
|
choices: injectBackChoice(
|
|
@@ -354,7 +400,9 @@ async function memoryConfigMenu(opts) {
|
|
|
354
400
|
}
|
|
355
401
|
|
|
356
402
|
export {
|
|
357
|
-
|
|
403
|
+
registerMemoryMcp,
|
|
404
|
+
isMemoryMcpRegistered,
|
|
405
|
+
removeMemoryMcpEntry,
|
|
358
406
|
getMemoryMcpStatus,
|
|
359
407
|
memoryConfigMenu
|
|
360
408
|
};
|