coding-friend-cli 1.37.0 → 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 +7 -2
  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-676YCW6T.js → chunk-776ZY62N.js} +99 -7
  6. package/dist/{chunk-DP6DFQ4G.js → chunk-BXSCYGWE.js} +2 -2
  7. package/dist/{chunk-W6D3J5DX.js → chunk-CGEMETKQ.js} +8 -6
  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-6S7352JG.js → chunk-ONG4KRYP.js} +1 -1
  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-PI4P34KF.js → chunk-RFNKPAJW.js} +3 -1
  14. package/dist/{clean-WEYQZB34.js → clean-YWEMYDE5.js} +2 -2
  15. package/dist/{config-ZMR2AOBB.js → config-NG7SICM4.js} +12 -24
  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-SSRI4JZV.js → host-EV6QIVTI.js} +2 -2
  21. package/dist/index.js +59 -47
  22. package/dist/{init-6MFQWPOP.js → init-KYE5CJMW.js} +115 -12
  23. package/dist/{install-37NN4CQH.js → install-XAFQY5YF.js} +79 -6
  24. package/dist/{learn-OPH3CI67.js → learn-MC4VDHZH.js} +2 -2
  25. package/dist/{mcp-DWKVMNC7.js → mcp-FV6YX3ZT.js} +7 -7
  26. package/dist/{mcp-serve-5GODJ4DJ.js → mcp-serve-PXREYQRZ.js} +2 -2
  27. package/dist/{mcp-serve-learn-OF3ZJUJC.js → mcp-serve-learn-UVXIDOEZ.js} +1 -1
  28. package/dist/{memory-TRDLADUM.js → memory-DF7OUOTV.js} +6 -6
  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-S4WKA4P5.js → session-QK6OZO3X.js} +2 -2
  32. package/dist/{status-YZEOUX46.js → status-FVFTJ6GS.js} +11 -10
  33. package/dist/{statusline-KKFJPTI7.js → statusline-AOZHEQSV.js} +2 -2
  34. package/dist/{uninstall-AVA3J554.js → uninstall-MF6FRUBO.js} +51 -4
  35. package/dist/{update-Q3ACCK4Z.js → update-QNUCSDSA.js} +6 -5
  36. package/dist/{update-check-25SCLHZO.js → update-check-7LKJLJPM.js} +1 -1
  37. package/lib/cf-memory/src/lib/claude-md.ts +82 -70
  38. package/lib/cf-memory/src/lib/status-frame.ts +2 -2
  39. package/lib/cf-memory/src/tools/delete.ts +1 -1
  40. package/lib/cf-memory/src/tools/store.ts +6 -6
  41. package/lib/cf-memory/src/tools/update.ts +5 -11
  42. package/package.json +32 -4
  43. package/lib/cf-memory/README.md +0 -317
  44. package/lib/cf-memory/src/__tests__/claude-md.test.ts +0 -343
  45. package/lib/cf-memory/src/__tests__/daemon-client.test.ts +0 -146
  46. package/lib/cf-memory/src/__tests__/daemon-e2e.test.ts +0 -223
  47. package/lib/cf-memory/src/__tests__/daemon.test.ts +0 -407
  48. package/lib/cf-memory/src/__tests__/dedup.test.ts +0 -103
  49. package/lib/cf-memory/src/__tests__/embeddings.test.ts +0 -292
  50. package/lib/cf-memory/src/__tests__/lazy-install.test.ts +0 -210
  51. package/lib/cf-memory/src/__tests__/markdown-backend.test.ts +0 -450
  52. package/lib/cf-memory/src/__tests__/migration.test.ts +0 -255
  53. package/lib/cf-memory/src/__tests__/migrations.test.ts +0 -288
  54. package/lib/cf-memory/src/__tests__/minisearch-backend.test.ts +0 -262
  55. package/lib/cf-memory/src/__tests__/ollama.test.ts +0 -48
  56. package/lib/cf-memory/src/__tests__/project-id.test.ts +0 -43
  57. package/lib/cf-memory/src/__tests__/schema.test.ts +0 -128
  58. package/lib/cf-memory/src/__tests__/search.test.ts +0 -115
  59. package/lib/cf-memory/src/__tests__/sqlite-lazy-db.test.ts +0 -213
  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 -388
  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
 
@@ -23,7 +23,7 @@ Everything else (every skill, every agent, every hook except the three OPTIONAL
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,7 +1,7 @@
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";
@@ -9,10 +9,15 @@ import {
9
9
  isMemoryMcpRegistered,
10
10
  registerMemoryMcp,
11
11
  removeMemoryMcpEntry
12
- } from "./chunk-DP6DFQ4G.js";
12
+ } from "./chunk-BXSCYGWE.js";
13
13
  import {
14
+ CODEX_MARKETPLACE_NAME,
15
+ getCodexInstalledVersion
16
+ } from "./chunk-5QCGEPQO.js";
17
+ import {
18
+ resolveHostFlags,
14
19
  resolveScope
15
- } from "./chunk-IKLMHJH6.js";
20
+ } from "./chunk-GIH6E7GJ.js";
16
21
  import {
17
22
  commandExists,
18
23
  run,
@@ -24,7 +29,7 @@ import {
24
29
  } from "./chunk-5UVDWG5L.js";
25
30
  import {
26
31
  claudeSettingsPath
27
- } from "./chunk-X2H7JJMR.js";
32
+ } from "./chunk-PLSJW7QJ.js";
28
33
  import {
29
34
  log,
30
35
  printBanner
@@ -94,6 +99,11 @@ function getStatuslineVersion() {
94
99
  return match?.[1]?.replace(/^v/, "") ?? null;
95
100
  }
96
101
  async function updateCommand(opts) {
102
+ const { host } = resolveHostFlags(opts);
103
+ if (host === "codex") {
104
+ await updateCodexCommand(opts);
105
+ return;
106
+ }
97
107
  const updateAll = !opts.cli && !opts.plugin && !opts.statusline;
98
108
  const doCli = updateAll || !!opts.cli;
99
109
  const doPlugin = updateAll || !!opts.plugin;
@@ -254,7 +264,7 @@ async function updateCommand(opts) {
254
264
  }
255
265
  }
256
266
  if (doCli) {
257
- const { refreshMemoryAfterUpdate } = await import("./memory-TRDLADUM.js");
267
+ const { refreshMemoryAfterUpdate } = await import("./memory-DF7OUOTV.js");
258
268
  await refreshMemoryAfterUpdate();
259
269
  }
260
270
  if (!isMemoryMcpRegistered()) {
@@ -263,9 +273,13 @@ async function updateCommand(opts) {
263
273
  const migrationResult = removeMemoryMcpEntry();
264
274
  if (migrationResult.removed) {
265
275
  if (migrationResult.fileDeleted) {
266
- log.success("Removed legacy project-scope coding-friend-memory from .mcp.json (file deleted \u2014 was empty).");
276
+ log.success(
277
+ "Removed legacy project-scope coding-friend-memory from .mcp.json (file deleted \u2014 was empty)."
278
+ );
267
279
  } else {
268
- log.success("Removed legacy project-scope coding-friend-memory from .mcp.json");
280
+ log.success(
281
+ "Removed legacy project-scope coding-friend-memory from .mcp.json"
282
+ );
269
283
  }
270
284
  }
271
285
  if (doStatusline) {
@@ -278,6 +292,84 @@ async function updateCommand(opts) {
278
292
  console.log();
279
293
  log.dim("Restart Claude Code (or start a new session) to see changes.");
280
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
+ }
281
373
 
282
374
  export {
283
375
  semverCompare,
@@ -6,7 +6,7 @@ import {
6
6
  askScope,
7
7
  formatScopeLabel,
8
8
  injectBackChoice
9
- } from "./chunk-IKLMHJH6.js";
9
+ } from "./chunk-GIH6E7GJ.js";
10
10
  import {
11
11
  runWithStderr
12
12
  } from "./chunk-EVGXUDX4.js";
@@ -19,7 +19,7 @@ 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";
@@ -2,23 +2,23 @@ import {
2
2
  checkMemoryMcpHealth,
3
3
  printHealthSection,
4
4
  warnStaleMcpJson
5
- } from "./chunk-6S7352JG.js";
5
+ } from "./chunk-ONG4KRYP.js";
6
6
  import {
7
7
  loadConfig,
8
8
  resolveMemoryDir
9
- } from "./chunk-PI4P34KF.js";
9
+ } from "./chunk-RFNKPAJW.js";
10
10
  import {
11
11
  getMemoryMcpStatus,
12
12
  isMemoryMcpRegistered,
13
13
  memoryConfigMenu,
14
14
  registerMemoryMcp
15
- } from "./chunk-DP6DFQ4G.js";
15
+ } from "./chunk-BXSCYGWE.js";
16
16
  import {
17
17
  getLibPath
18
18
  } from "./chunk-RZRT7NGT.js";
19
19
  import {
20
20
  showConfigHint
21
- } from "./chunk-IKLMHJH6.js";
21
+ } from "./chunk-GIH6E7GJ.js";
22
22
  import {
23
23
  run,
24
24
  runWithStderr
@@ -30,7 +30,7 @@ import {
30
30
  import {
31
31
  globalConfigPath,
32
32
  localConfigPath
33
- } from "./chunk-X2H7JJMR.js";
33
+ } from "./chunk-PLSJW7QJ.js";
34
34
  import {
35
35
  log,
36
36
  printBanner
@@ -104,7 +104,9 @@ async function refreshMemoryAfterUpdate() {
104
104
  log.warn('Memory server build failed \u2014 run "cf memory status" to retry.');
105
105
  }
106
106
  } catch {
107
- log.warn('Could not refresh memory server \u2014 run "cf memory status" to retry.');
107
+ log.warn(
108
+ 'Could not refresh memory server \u2014 run "cf memory status" to retry.'
109
+ );
108
110
  }
109
111
  }
110
112
  function printMemoryMcpConfig(memoryDir) {
@@ -12,7 +12,7 @@ import {
12
12
  globalConfigPath,
13
13
  installedPluginsPath,
14
14
  pluginCachePath
15
- } from "./chunk-X2H7JJMR.js";
15
+ } from "./chunk-PLSJW7QJ.js";
16
16
 
17
17
  // src/lib/statusline.ts
18
18
  import { existsSync, readdirSync } from "fs";
@@ -7,11 +7,62 @@ import {
7
7
  import {
8
8
  globalConfigPath,
9
9
  localConfigPath
10
- } from "./chunk-X2H7JJMR.js";
10
+ } from "./chunk-PLSJW7QJ.js";
11
11
  import {
12
12
  log
13
13
  } from "./chunk-NREZK463.js";
14
14
 
15
+ // src/lib/host.ts
16
+ var CODEX_MIN_VERSION = "0.130.0";
17
+ function resolveHost(opts = {}) {
18
+ const agent = opts.agent?.trim().toLowerCase();
19
+ const codexAlias = opts.codex === true;
20
+ if (agent && agent !== "claude" && agent !== "codex") {
21
+ throw new Error(
22
+ `Unsupported agent "${opts.agent}". Use "claude" or "codex".`
23
+ );
24
+ }
25
+ if (codexAlias && agent === "claude") {
26
+ throw new Error("Use either --agent claude or --codex, not both.");
27
+ }
28
+ if (codexAlias) return "codex";
29
+ if (agent === "codex") return "codex";
30
+ return "claude";
31
+ }
32
+ function getCodexMinVersion() {
33
+ return CODEX_MIN_VERSION;
34
+ }
35
+ function checkCodexVersion() {
36
+ const min = getCodexMinVersion();
37
+ const output = run("codex", ["--version"]);
38
+ const actual = output ? extractVersion(output) : void 0;
39
+ return {
40
+ ok: actual ? compareVersions(actual, min) >= 0 : false,
41
+ actual,
42
+ min
43
+ };
44
+ }
45
+ function extractVersion(input) {
46
+ return input.match(/\b(\d+\.\d+\.\d+)\b/)?.[1];
47
+ }
48
+ function compareVersions(a, b) {
49
+ const left = parseVersion(a);
50
+ const right = parseVersion(b);
51
+ for (let i = 0; i < 3; i += 1) {
52
+ if (left[i] > right[i]) return 1;
53
+ if (left[i] < right[i]) return -1;
54
+ }
55
+ return 0;
56
+ }
57
+ function parseVersion(version) {
58
+ const parts = version.split(".").map((part) => Number.parseInt(part, 10));
59
+ return [
60
+ Number.isFinite(parts[0]) ? parts[0] : 0,
61
+ Number.isFinite(parts[1]) ? parts[1] : 0,
62
+ Number.isFinite(parts[2]) ? parts[2] : 0
63
+ ];
64
+ }
65
+
15
66
  // src/lib/prompt-utils.ts
16
67
  import { select, Separator } from "@inquirer/prompts";
17
68
  import { existsSync } from "fs";
@@ -80,6 +131,14 @@ async function resolveScope(opts, message) {
80
131
  }
81
132
  return askPluginScope(message);
82
133
  }
134
+ function resolveHostFlags(opts) {
135
+ try {
136
+ return { host: resolveHost(opts) };
137
+ } catch (err) {
138
+ log.error(err instanceof Error ? err.message : String(err));
139
+ process.exit(1);
140
+ }
141
+ }
83
142
  function showConfigHint() {
84
143
  console.log(chalk.dim("Config files:"));
85
144
  console.log(chalk.dim(" Global: ~/.coding-friend/config.json"));
@@ -173,10 +232,12 @@ function applyDocsDirChange(newValue, oldValue, scope, subfolders = []) {
173
232
  }
174
233
 
175
234
  export {
235
+ checkCodexVersion,
176
236
  BACK,
177
237
  injectBackChoice,
178
238
  askScope,
179
239
  resolveScope,
240
+ resolveHostFlags,
180
241
  showConfigHint,
181
242
  getScopeLabel,
182
243
  formatScopeLabel,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isMemoryMcpRegistered
3
- } from "./chunk-DP6DFQ4G.js";
3
+ } from "./chunk-BXSCYGWE.js";
4
4
  import {
5
5
  readJson
6
6
  } from "./chunk-5UVDWG5L.js";
@@ -1,6 +1,8 @@
1
1
  // src/lib/paths.ts
2
2
  import { homedir } from "os";
3
3
  import { resolve, join, isAbsolute } from "path";
4
+ var CODEX_MARKETPLACE_NAME = "coding-friend-marketplace";
5
+ var CODEX_PLUGIN_NAME = "coding-friend";
4
6
  function resolvePath(p, base = process.cwd()) {
5
7
  if (isAbsolute(p)) return p;
6
8
  if (p.startsWith("~/")) return join(homedir(), p.slice(2));
@@ -77,6 +79,29 @@ function claudeProjectsDir() {
77
79
  function claudeSessionDir(encodedPath) {
78
80
  return join(claudeProjectsDir(), encodedPath);
79
81
  }
82
+ function codexConfigDir() {
83
+ const env = process.env.CODEX_HOME?.trim();
84
+ if (!env) return join(homedir(), ".codex");
85
+ if (env === "~") return homedir();
86
+ if (env.startsWith("~/")) return join(homedir(), env.slice(2));
87
+ return env;
88
+ }
89
+ function codexConfigTomlPath() {
90
+ return join(codexConfigDir(), "config.toml");
91
+ }
92
+ function codexPluginsCacheDir() {
93
+ return join(codexConfigDir(), "plugins", "cache");
94
+ }
95
+ function codexInstalledPluginsPath() {
96
+ return join(
97
+ codexPluginsCacheDir(),
98
+ CODEX_MARKETPLACE_NAME,
99
+ CODEX_PLUGIN_NAME
100
+ );
101
+ }
102
+ function codexAgentsDir() {
103
+ return join(codexConfigDir(), "agents");
104
+ }
80
105
 
81
106
  export {
82
107
  resolvePath,
@@ -95,5 +120,8 @@ export {
95
120
  globalConfigDir,
96
121
  memoryDepsDir,
97
122
  encodeProjectPath,
98
- claudeSessionDir
123
+ claudeSessionDir,
124
+ codexConfigTomlPath,
125
+ codexInstalledPluginsPath,
126
+ codexAgentsDir
99
127
  };