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.
Files changed (71) hide show
  1. package/README.md +8 -3
  2. package/dist/{chunk-GZT3YPAP.js → chunk-32UAW3NX.js} +1 -1
  3. package/dist/chunk-5QCGEPQO.js +238 -0
  4. package/dist/{chunk-YIXAQ4R2.js → chunk-6NXZKAOV.js} +1 -1
  5. package/dist/{chunk-XHP3EBR3.js → chunk-776ZY62N.js} +112 -4
  6. package/dist/{chunk-N4WZWRCI.js → chunk-BXSCYGWE.js} +85 -37
  7. package/dist/{chunk-AK6RMCAC.js → chunk-CGEMETKQ.js} +44 -30
  8. package/dist/{chunk-XEKQW4RN.js → chunk-ETTZRJ55.js} +1 -1
  9. package/dist/{chunk-IKLMHJH6.js → chunk-GIH6E7GJ.js} +62 -1
  10. package/dist/{chunk-56YJWGAN.js → chunk-ONG4KRYP.js} +60 -47
  11. package/dist/{chunk-X2H7JJMR.js → chunk-PLSJW7QJ.js} +29 -1
  12. package/dist/{chunk-O5ZKLFSL.js → chunk-RCE4OWNX.js} +1 -1
  13. package/dist/{chunk-K4WSC62G.js → chunk-RFNKPAJW.js} +42 -2
  14. package/dist/{clean-Q4VU662V.js → clean-YWEMYDE5.js} +3 -2
  15. package/dist/{config-CTTHWJH5.js → config-NG7SICM4.js} +15 -27
  16. package/dist/{dev-IGHMMEP4.js → dev-PTBNMKZT.js} +3 -3
  17. package/dist/{disable-HL5OEURT.js → disable-MO7AXYYF.js} +20 -3
  18. package/dist/{enable-CZIOCKKJ.js → enable-4GXLQEHD.js} +20 -3
  19. package/dist/{guide-CO2V4ZLZ.js → guide-HCR43LJN.js} +1 -1
  20. package/dist/{host-NCJMUSME.js → host-EV6QIVTI.js} +3 -3
  21. package/dist/index.js +62 -48
  22. package/dist/{init-UABKVDVT.js → init-KYE5CJMW.js} +135 -17
  23. package/dist/{install-R2HDM3N4.js → install-XAFQY5YF.js} +80 -5
  24. package/dist/{learn-K64AUUFB.js → learn-MC4VDHZH.js} +2 -2
  25. package/dist/{mcp-OMQFSJP4.js → mcp-FV6YX3ZT.js} +24 -24
  26. package/dist/{mcp-serve-3FEPFVVQ.js → mcp-serve-PXREYQRZ.js} +10 -1
  27. package/dist/{mcp-serve-learn-OF3ZJUJC.js → mcp-serve-learn-UVXIDOEZ.js} +1 -1
  28. package/dist/{memory-Y6GNALG3.js → memory-DF7OUOTV.js} +7 -7
  29. package/dist/{permission-I3OFJUYA.js → permission-VISPGMUK.js} +38 -4
  30. package/dist/{permissions-J46VP6US.js → permissions-2SBACQHV.js} +2 -2
  31. package/dist/{session-Q2DLQPYP.js → session-QK6OZO3X.js} +3 -2
  32. package/dist/{status-ML4XEDYL.js → status-FVFTJ6GS.js} +18 -14
  33. package/dist/{statusline-KKFJPTI7.js → statusline-AOZHEQSV.js} +2 -2
  34. package/dist/{uninstall-ASLHT3MH.js → uninstall-MF6FRUBO.js} +64 -3
  35. package/dist/{update-DIOH45TE.js → update-QNUCSDSA.js} +7 -4
  36. package/dist/{update-check-25SCLHZO.js → update-check-7LKJLJPM.js} +1 -1
  37. package/lib/cf-memory/src/backends/sqlite/index.ts +142 -85
  38. package/lib/cf-memory/src/lib/claude-md.ts +82 -70
  39. package/lib/cf-memory/src/lib/status-frame.ts +2 -2
  40. package/lib/cf-memory/src/tools/delete.ts +1 -1
  41. package/lib/cf-memory/src/tools/store.ts +6 -6
  42. package/lib/cf-memory/src/tools/update.ts +5 -11
  43. package/package.json +32 -4
  44. package/lib/cf-memory/README.md +0 -317
  45. package/lib/cf-memory/src/__tests__/claude-md.test.ts +0 -343
  46. package/lib/cf-memory/src/__tests__/daemon-client.test.ts +0 -146
  47. package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +0 -223
  48. package/lib/cf-memory/src/__tests__/daemon.test.ts +0 -407
  49. package/lib/cf-memory/src/__tests__/dedup.test.ts +0 -103
  50. package/lib/cf-memory/src/__tests__/embeddings.test.ts +0 -292
  51. package/lib/cf-memory/src/__tests__/lazy-install.test.ts +0 -210
  52. package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +0 -450
  53. package/lib/cf-memory/src/__tests__/migration.test.ts +0 -255
  54. package/lib/cf-memory/src/__tests__/migrations.test.ts +0 -288
  55. package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +0 -262
  56. package/lib/cf-memory/src/__tests__/ollama.test.ts +0 -48
  57. package/lib/cf-memory/src/__tests__/project-id.test.ts +0 -43
  58. package/lib/cf-memory/src/__tests__/schema.test.ts +0 -128
  59. package/lib/cf-memory/src/__tests__/search.test.ts +0 -115
  60. package/lib/cf-memory/src/__tests__/status-frame.test.ts +0 -136
  61. package/lib/cf-memory/src/__tests__/temporal-decay.test.ts +0 -54
  62. package/lib/cf-memory/src/__tests__/tier.test.ts +0 -367
  63. package/lib/cf-memory/src/__tests__/tools.test.ts +0 -83
  64. package/lib/cf-memory/vitest.config.ts +0 -7
  65. package/lib/learn-host/.prettierignore +0 -4
  66. package/lib/learn-host/.prettierrc +0 -8
  67. package/lib/learn-host/README.md +0 -113
  68. package/lib/learn-host/eslint.config.mjs +0 -6
  69. package/lib/learn-mcp/README.md +0 -187
  70. package/lib/learn-mcp/src/__tests__/docs.test.ts +0 -117
  71. 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 the memory MCP, then restart Claude Code.
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
@@ -8,7 +8,7 @@ import {
8
8
  claudeSettingsPath,
9
9
  installedPluginsPath,
10
10
  knownMarketplacesPath
11
- } from "./chunk-X2H7JJMR.js";
11
+ } from "./chunk-PLSJW7QJ.js";
12
12
 
13
13
  // src/lib/plugin-state.ts
14
14
  var MARKETPLACE_NAME = "coding-friend-marketplace";
@@ -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
+ };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-EVGXUDX4.js";
4
4
  import {
5
5
  resolvePath
6
- } from "./chunk-X2H7JJMR.js";
6
+ } from "./chunk-PLSJW7QJ.js";
7
7
  import {
8
8
  log
9
9
  } from "./chunk-NREZK463.js";
@@ -1,13 +1,23 @@
1
1
  import {
2
2
  ensureStatusline,
3
3
  getInstalledVersion
4
- } from "./chunk-XEKQW4RN.js";
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-IKLMHJH6.js";
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-X2H7JJMR.js";
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-Y6GNALG3.js");
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-IKLMHJH6.js";
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-X2H7JJMR.js";
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 writeMemoryMcpEntry(memoryDir) {
253
+ function removeMemoryMcpEntry() {
220
254
  const mcpPath = join(process.cwd(), ".mcp.json");
221
- const existing = readJson(mcpPath) ?? {};
222
- const servers = existing.mcpServers ?? {};
223
- writeJson(mcpPath, {
224
- ...existing,
225
- mcpServers: {
226
- ...servers,
227
- "coding-friend-memory": {
228
- command: "npx",
229
- args: ["-y", "coding-friend-cli", "mcp-serve", memoryDir]
230
- }
231
- }
232
- });
233
- log.success("Added coding-friend-memory to .mcp.json");
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
- const status = getMemoryMcpStatus();
252
- if (status.configured) {
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: "Reconfigure Memory MCP in local .mcp.json?",
302
+ message: "Re-register Memory MCP (user scope)?",
261
303
  default: false
262
304
  });
263
305
  if (!reconfigure) return;
264
306
  }
265
- const memoryDir = resolveMemoryDir();
266
- writeMemoryMcpEntry(memoryDir);
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.configured ? mcpStatus.scope === "local" ? chalk.green("configured") + chalk.dim(" (.mcp.json)") : chalk.green("configured") + chalk.dim(" (global)") + " " + chalk.yellow("\u26A0") : chalk.dim("not configured");
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
- writeMemoryMcpEntry,
403
+ registerMemoryMcp,
404
+ isMemoryMcpRegistered,
405
+ removeMemoryMcpEntry,
358
406
  getMemoryMcpStatus,
359
407
  memoryConfigMenu
360
408
  };