zcf 3.4.2 → 3.5.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 (60) hide show
  1. package/README.md +1 -1
  2. package/dist/chunks/api-providers.mjs +1 -1
  3. package/dist/chunks/claude-code-config-manager.mjs +28 -18
  4. package/dist/chunks/claude-code-incremental-manager.mjs +36 -19
  5. package/dist/chunks/codex-config-switch.mjs +4 -4
  6. package/dist/chunks/codex-provider-manager.mjs +28 -19
  7. package/dist/chunks/codex-uninstaller.mjs +2 -2
  8. package/dist/chunks/commands.mjs +1 -1
  9. package/dist/chunks/features.mjs +32 -20
  10. package/dist/chunks/simple-config.mjs +294 -114
  11. package/dist/cli.mjs +8 -6
  12. package/dist/i18n/locales/en/cli.json +3 -1
  13. package/dist/i18n/locales/en/configuration.json +3 -1
  14. package/dist/i18n/locales/en/errors.json +1 -1
  15. package/dist/i18n/locales/en/updater.json +1 -0
  16. package/dist/i18n/locales/zh-CN/cli.json +3 -1
  17. package/dist/i18n/locales/zh-CN/configuration.json +3 -1
  18. package/dist/i18n/locales/zh-CN/errors.json +1 -1
  19. package/dist/i18n/locales/zh-CN/updater.json +1 -0
  20. package/dist/index.d.mts +7 -3
  21. package/dist/index.d.ts +7 -3
  22. package/dist/index.mjs +1 -1
  23. package/package.json +1 -1
  24. package/templates/CLAUDE.md +39 -11
  25. package/templates/{codex/en/workflow/git/prompts → common/workflow/git/en}/git-commit.md +50 -3
  26. package/templates/{claude-code/zh-CN/workflow/git/commands → common/workflow/git/zh-CN}/git-commit.md +50 -3
  27. package/templates/{codex/en/workflow/sixStep/prompts → common/workflow/sixStep/en}/workflow.md +25 -4
  28. package/templates/{codex/zh-CN/workflow/sixStep/prompts → common/workflow/sixStep/zh-CN}/workflow.md +25 -4
  29. package/templates/claude-code/en/workflow/git/commands/git-commit.md +0 -158
  30. package/templates/claude-code/en/workflow/sixStep/commands/workflow.md +0 -230
  31. package/templates/claude-code/zh-CN/workflow/sixStep/commands/workflow.md +0 -194
  32. package/templates/codex/en/system-prompt/engineer-professional.md +0 -88
  33. package/templates/codex/en/system-prompt/laowang-engineer.md +0 -127
  34. package/templates/codex/en/system-prompt/nekomata-engineer.md +0 -120
  35. package/templates/codex/en/system-prompt/ojousama-engineer.md +0 -121
  36. package/templates/codex/en/workflow/git/prompts/git-cleanBranches.md +0 -102
  37. package/templates/codex/en/workflow/git/prompts/git-rollback.md +0 -90
  38. package/templates/codex/en/workflow/git/prompts/git-worktree.md +0 -276
  39. package/templates/codex/zh-CN/system-prompt/engineer-professional.md +0 -89
  40. package/templates/codex/zh-CN/system-prompt/laowang-engineer.md +0 -127
  41. package/templates/codex/zh-CN/system-prompt/nekomata-engineer.md +0 -120
  42. package/templates/codex/zh-CN/system-prompt/ojousama-engineer.md +0 -121
  43. package/templates/codex/zh-CN/workflow/git/prompts/git-cleanBranches.md +0 -102
  44. package/templates/codex/zh-CN/workflow/git/prompts/git-commit.md +0 -158
  45. package/templates/codex/zh-CN/workflow/git/prompts/git-rollback.md +0 -90
  46. package/templates/codex/zh-CN/workflow/git/prompts/git-worktree.md +0 -276
  47. /package/templates/{claude-code/en/output-styles → common/output-styles/en}/engineer-professional.md +0 -0
  48. /package/templates/{claude-code/en/output-styles → common/output-styles/en}/laowang-engineer.md +0 -0
  49. /package/templates/{claude-code/en/output-styles → common/output-styles/en}/nekomata-engineer.md +0 -0
  50. /package/templates/{claude-code/en/output-styles → common/output-styles/en}/ojousama-engineer.md +0 -0
  51. /package/templates/{claude-code/zh-CN/output-styles → common/output-styles/zh-CN}/engineer-professional.md +0 -0
  52. /package/templates/{claude-code/zh-CN/output-styles → common/output-styles/zh-CN}/laowang-engineer.md +0 -0
  53. /package/templates/{claude-code/zh-CN/output-styles → common/output-styles/zh-CN}/nekomata-engineer.md +0 -0
  54. /package/templates/{claude-code/zh-CN/output-styles → common/output-styles/zh-CN}/ojousama-engineer.md +0 -0
  55. /package/templates/{claude-code/en/workflow/git/commands → common/workflow/git/en}/git-cleanBranches.md +0 -0
  56. /package/templates/{claude-code/en/workflow/git/commands → common/workflow/git/en}/git-rollback.md +0 -0
  57. /package/templates/{claude-code/en/workflow/git/commands → common/workflow/git/en}/git-worktree.md +0 -0
  58. /package/templates/{claude-code/zh-CN/workflow/git/commands → common/workflow/git/zh-CN}/git-cleanBranches.md +0 -0
  59. /package/templates/{claude-code/zh-CN/workflow/git/commands → common/workflow/git/zh-CN}/git-rollback.md +0 -0
  60. /package/templates/{claude-code/zh-CN/workflow/git/commands → common/workflow/git/zh-CN}/git-worktree.md +0 -0
@@ -11,14 +11,14 @@ import { dirname, join } from 'pathe';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import toggleModule from 'inquirer-toggle';
13
13
  import ora from 'ora';
14
+ import { exec, x } from 'tinyexec';
14
15
  import semver from 'semver';
15
16
  import { stringify, parse } from 'smol-toml';
16
- import { exec, x } from 'tinyexec';
17
17
  import { rm, mkdir, copyFile as copyFile$1 } from 'node:fs/promises';
18
18
  import i18next from 'i18next';
19
19
  import Backend from 'i18next-fs-backend';
20
20
 
21
- const version = "3.4.2";
21
+ const version = "3.5.0";
22
22
  const homepage = "https://github.com/UfoMiao/zcf";
23
23
 
24
24
  const i18n = i18next.createInstance();
@@ -1309,6 +1309,20 @@ const claudeConfig = {
1309
1309
  writeMcpConfig: writeMcpConfig
1310
1310
  };
1311
1311
 
1312
+ const MODEL_ENV_KEYS = [
1313
+ "ANTHROPIC_MODEL",
1314
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
1315
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
1316
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
1317
+ // Deprecated but still cleaned to avoid stale values
1318
+ "ANTHROPIC_SMALL_FAST_MODEL"
1319
+ ];
1320
+ function clearModelEnv(env) {
1321
+ for (const key of MODEL_ENV_KEYS) {
1322
+ delete env[key];
1323
+ }
1324
+ }
1325
+
1312
1326
  function cleanupPermissions(templatePermissions, userPermissions) {
1313
1327
  const templateSet = new Set(templatePermissions);
1314
1328
  const cleanedPermissions = userPermissions.filter((permission) => {
@@ -1426,8 +1440,8 @@ function mergeConfigs(sourceFile, targetFile) {
1426
1440
  const merged = deepMerge(target, source);
1427
1441
  writeJsonConfig(targetFile, merged);
1428
1442
  }
1429
- function updateCustomModel(primaryModel, fastModel) {
1430
- if (!primaryModel?.trim() && !fastModel?.trim()) {
1443
+ function updateCustomModel(primaryModel, haikuModel, sonnetModel, opusModel) {
1444
+ if (!primaryModel?.trim() && !haikuModel?.trim() && !sonnetModel?.trim() && !opusModel?.trim()) {
1431
1445
  return;
1432
1446
  }
1433
1447
  let settings = getDefaultSettings();
@@ -1437,12 +1451,16 @@ function updateCustomModel(primaryModel, fastModel) {
1437
1451
  }
1438
1452
  delete settings.model;
1439
1453
  settings.env = settings.env || {};
1454
+ clearModelEnv(settings.env);
1440
1455
  if (primaryModel?.trim()) {
1441
1456
  settings.env.ANTHROPIC_MODEL = primaryModel.trim();
1442
1457
  }
1443
- if (fastModel?.trim()) {
1444
- settings.env.ANTHROPIC_SMALL_FAST_MODEL = fastModel.trim();
1445
- }
1458
+ if (haikuModel?.trim())
1459
+ settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = haikuModel.trim();
1460
+ if (sonnetModel?.trim())
1461
+ settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL = sonnetModel.trim();
1462
+ if (opusModel?.trim())
1463
+ settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = opusModel.trim();
1446
1464
  writeJsonConfig(SETTINGS_FILE, settings);
1447
1465
  }
1448
1466
  function updateDefaultModel(model) {
@@ -1454,13 +1472,10 @@ function updateDefaultModel(model) {
1454
1472
  if (!settings.env) {
1455
1473
  settings.env = {};
1456
1474
  }
1457
- if (model !== "custom" && settings.env) {
1458
- delete settings.env.ANTHROPIC_MODEL;
1459
- delete settings.env.ANTHROPIC_SMALL_FAST_MODEL;
1475
+ if (model !== "custom") {
1476
+ clearModelEnv(settings.env);
1460
1477
  }
1461
- if (model === "default") {
1462
- delete settings.model;
1463
- } else if (model === "custom") {
1478
+ if (model === "default" || model === "custom") {
1464
1479
  delete settings.model;
1465
1480
  } else {
1466
1481
  settings.model = model;
@@ -1511,13 +1526,18 @@ function getExistingModelConfig() {
1511
1526
  if (!settings) {
1512
1527
  return null;
1513
1528
  }
1514
- if (settings.env && (settings.env.ANTHROPIC_MODEL || settings.env.ANTHROPIC_SMALL_FAST_MODEL)) {
1529
+ const hasModelEnv = MODEL_ENV_KEYS.some((key) => settings.env?.[key]);
1530
+ if (hasModelEnv) {
1515
1531
  return "custom";
1516
1532
  }
1517
1533
  if (!settings.model) {
1518
1534
  return "default";
1519
1535
  }
1520
- return settings.model;
1536
+ const validModels = ["opus", "sonnet", "sonnet[1m]"];
1537
+ if (validModels.includes(settings.model)) {
1538
+ return settings.model;
1539
+ }
1540
+ return "default";
1521
1541
  }
1522
1542
  function getExistingApiConfig() {
1523
1543
  const settings = readJsonConfig(SETTINGS_FILE);
@@ -1787,7 +1807,7 @@ function getFallbackPresets() {
1787
1807
  ];
1788
1808
  }
1789
1809
 
1790
- const execAsync$4 = promisify(exec$1);
1810
+ const execAsync$3 = promisify(exec$1);
1791
1811
  const CCR_CONFIG_DIR = join(homedir(), ".claude-code-router");
1792
1812
  const CCR_CONFIG_FILE = join(CCR_CONFIG_DIR, "config.json");
1793
1813
  const CCR_BACKUP_DIR = CCR_CONFIG_DIR;
@@ -1832,6 +1852,7 @@ async function configureCcrProxy(ccrConfig) {
1832
1852
  if (!settings.env) {
1833
1853
  settings.env = {};
1834
1854
  }
1855
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
1835
1856
  settings.env.ANTHROPIC_BASE_URL = `http://${host}:${port}`;
1836
1857
  settings.env.ANTHROPIC_API_KEY = apiKey;
1837
1858
  writeJsonConfig(SETTINGS_FILE, settings);
@@ -1953,10 +1974,10 @@ async function restartAndCheckCcrStatus() {
1953
1974
  ensureI18nInitialized();
1954
1975
  try {
1955
1976
  console.log(ansis.cyan(`${i18n.t("ccr:restartingCcr")}`));
1956
- await execAsync$4("ccr restart");
1977
+ await execAsync$3("ccr restart");
1957
1978
  console.log(ansis.green(`\u2714 ${i18n.t("ccr:ccrRestartSuccess")}`));
1958
1979
  console.log(ansis.cyan(`${i18n.t("ccr:checkingCcrStatus")}`));
1959
- const { stdout } = await execAsync$4("ccr status");
1980
+ const { stdout } = await execAsync$3("ccr status");
1960
1981
  console.log(ansis.gray(stdout));
1961
1982
  } catch (error) {
1962
1983
  console.error(ansis.red(`${i18n.t("ccr:ccrRestartFailed")}:`), error.message || error);
@@ -2093,16 +2114,16 @@ const config = {
2093
2114
  writeCcrConfig: writeCcrConfig
2094
2115
  };
2095
2116
 
2096
- const execAsync$3 = promisify(exec$1);
2117
+ const execAsync$2 = promisify(exec$1);
2097
2118
  async function getInstalledVersion(command, maxRetries = 3) {
2098
2119
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
2099
2120
  try {
2100
2121
  let stdout;
2101
2122
  try {
2102
- const result = await execAsync$3(`${command} -v`);
2123
+ const result = await execAsync$2(`${command} -v`);
2103
2124
  stdout = result.stdout;
2104
2125
  } catch {
2105
- const result = await execAsync$3(`${command} --version`);
2126
+ const result = await execAsync$2(`${command} --version`);
2106
2127
  stdout = result.stdout;
2107
2128
  }
2108
2129
  const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
@@ -2119,7 +2140,7 @@ async function getInstalledVersion(command, maxRetries = 3) {
2119
2140
  async function getLatestVersion(packageName, maxRetries = 3) {
2120
2141
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
2121
2142
  try {
2122
- const { stdout } = await execAsync$3(`npm view ${packageName} version`);
2143
+ const { stdout } = await execAsync$2(`npm view ${packageName} version`);
2123
2144
  return stdout.trim();
2124
2145
  } catch {
2125
2146
  if (attempt === maxRetries) {
@@ -2148,7 +2169,7 @@ async function getClaudeCodeInstallationSource() {
2148
2169
  return { isHomebrew: true, commandPath, source: "homebrew-cask" };
2149
2170
  }
2150
2171
  try {
2151
- const { stdout: realPath } = await execAsync$3(`readlink -f "${commandPath}" 2>/dev/null || realpath "${commandPath}" 2>/dev/null || echo "${commandPath}"`);
2172
+ const { stdout: realPath } = await execAsync$2(`readlink -f "${commandPath}" 2>/dev/null || realpath "${commandPath}" 2>/dev/null || echo "${commandPath}"`);
2152
2173
  const resolvedPath = realPath.trim();
2153
2174
  if (resolvedPath.includes("/Caskroom/claude-code/")) {
2154
2175
  return { isHomebrew: true, commandPath, source: "homebrew-cask" };
@@ -2167,7 +2188,7 @@ async function detectAllClaudeCodeInstallations() {
2167
2188
  let activeResolvedPath = null;
2168
2189
  if (activeCommandPath) {
2169
2190
  try {
2170
- const { stdout } = await execAsync$3(`readlink -f "${activeCommandPath}" 2>/dev/null || realpath "${activeCommandPath}" 2>/dev/null || echo "${activeCommandPath}"`);
2191
+ const { stdout } = await execAsync$2(`readlink -f "${activeCommandPath}" 2>/dev/null || realpath "${activeCommandPath}" 2>/dev/null || echo "${activeCommandPath}"`);
2171
2192
  activeResolvedPath = stdout.trim();
2172
2193
  } catch {
2173
2194
  activeResolvedPath = activeCommandPath;
@@ -2175,7 +2196,7 @@ async function detectAllClaudeCodeInstallations() {
2175
2196
  }
2176
2197
  async function getVersionFromPath(path) {
2177
2198
  try {
2178
- const { stdout } = await execAsync$3(`"${path}" -v 2>/dev/null || "${path}" --version 2>/dev/null`);
2199
+ const { stdout } = await execAsync$2(`"${path}" -v 2>/dev/null || "${path}" --version 2>/dev/null`);
2179
2200
  const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
2180
2201
  return versionMatch ? versionMatch[1] : null;
2181
2202
  } catch {
@@ -2190,7 +2211,7 @@ async function detectAllClaudeCodeInstallations() {
2190
2211
  async function addInstallation(path, source) {
2191
2212
  let resolvedPath = path;
2192
2213
  try {
2193
- const { stdout } = await execAsync$3(`readlink -f "${path}" 2>/dev/null || realpath "${path}" 2>/dev/null || echo "${path}"`);
2214
+ const { stdout } = await execAsync$2(`readlink -f "${path}" 2>/dev/null || realpath "${path}" 2>/dev/null || echo "${path}"`);
2194
2215
  resolvedPath = stdout.trim();
2195
2216
  } catch {
2196
2217
  }
@@ -2226,7 +2247,7 @@ async function detectAllClaudeCodeInstallations() {
2226
2247
  }
2227
2248
  }
2228
2249
  try {
2229
- await execAsync$3("brew list --cask claude-code");
2250
+ await execAsync$2("brew list --cask claude-code");
2230
2251
  const homebrewPrefixes = ["/opt/homebrew", "/usr/local"];
2231
2252
  for (const prefix of homebrewPrefixes) {
2232
2253
  const caskroomPath = `${prefix}/Caskroom/claude-code`;
@@ -2251,7 +2272,7 @@ async function detectAllClaudeCodeInstallations() {
2251
2272
  if (nodeFs.existsSync(path)) {
2252
2273
  let resolvedPath = path;
2253
2274
  try {
2254
- const { stdout } = await execAsync$3(`readlink -f "${path}" 2>/dev/null || realpath "${path}" 2>/dev/null || echo "${path}"`);
2275
+ const { stdout } = await execAsync$2(`readlink -f "${path}" 2>/dev/null || realpath "${path}" 2>/dev/null || echo "${path}"`);
2255
2276
  resolvedPath = stdout.trim();
2256
2277
  } catch {
2257
2278
  }
@@ -2441,7 +2462,7 @@ async function handleDuplicateInstallations(skipPrompt = false) {
2441
2462
  }
2442
2463
  async function getHomebrewClaudeCodeVersion() {
2443
2464
  try {
2444
- const { stdout } = await execAsync$3("brew info --cask claude-code --json=v2");
2465
+ const { stdout } = await execAsync$2("brew info --cask claude-code --json=v2");
2445
2466
  const info = JSON.parse(stdout);
2446
2467
  if (info.casks && info.casks.length > 0) {
2447
2468
  return info.casks[0].version;
@@ -2514,7 +2535,24 @@ async function checkClaudeCodeVersionAndPrompt(skipPrompt = false) {
2514
2535
  }
2515
2536
  }
2516
2537
 
2517
- const execAsync$2 = promisify(exec$1);
2538
+ async function execWithSudoIfNeeded(command, args) {
2539
+ const needsSudo = shouldUseSudoForGlobalInstall();
2540
+ if (needsSudo) {
2541
+ console.log(ansis.yellow(`
2542
+ ${i18n.t("updater:usingSudo")}`));
2543
+ const result = await exec("sudo", [command, ...args]);
2544
+ if (result.exitCode !== 0) {
2545
+ throw new Error(result.stderr || `Command failed with exit code ${result.exitCode}`);
2546
+ }
2547
+ return { usedSudo: true };
2548
+ } else {
2549
+ const result = await exec(command, args);
2550
+ if (result.exitCode !== 0) {
2551
+ throw new Error(result.stderr || `Command failed with exit code ${result.exitCode}`);
2552
+ }
2553
+ return { usedSudo: false };
2554
+ }
2555
+ }
2518
2556
  async function updateCcr(force = false, skipPrompt = false) {
2519
2557
  ensureI18nInitialized();
2520
2558
  const spinner = ora(i18n.t("updater:checkingVersion")).start();
@@ -2549,7 +2587,7 @@ async function updateCcr(force = false, skipPrompt = false) {
2549
2587
  }
2550
2588
  const updateSpinner = ora(format(i18n.t("updater:updating"), { tool: "CCR" })).start();
2551
2589
  try {
2552
- await execAsync$2("npm update -g @musistudio/claude-code-router");
2590
+ await execWithSudoIfNeeded("npm", ["update", "-g", "@musistudio/claude-code-router"]);
2553
2591
  updateSpinner.succeed(format(i18n.t("updater:updateSuccess"), { tool: "CCR" }));
2554
2592
  return true;
2555
2593
  } catch (error) {
@@ -2599,9 +2637,12 @@ async function updateClaudeCode(force = false, skipPrompt = false) {
2599
2637
  const updateSpinner = ora(format(i18n.t("updater:updating"), { tool: toolName })).start();
2600
2638
  try {
2601
2639
  if (isHomebrew) {
2602
- await execAsync$2("brew upgrade --cask claude-code");
2640
+ const result = await exec("brew", ["upgrade", "--cask", "claude-code"]);
2641
+ if (result.exitCode !== 0) {
2642
+ throw new Error(result.stderr || `Command failed with exit code ${result.exitCode}`);
2643
+ }
2603
2644
  } else {
2604
- await execAsync$2("claude update");
2645
+ await execWithSudoIfNeeded("claude", ["update"]);
2605
2646
  }
2606
2647
  updateSpinner.succeed(format(i18n.t("updater:updateSuccess"), { tool: "Claude Code" }));
2607
2648
  return true;
@@ -2650,7 +2691,7 @@ async function updateCometixLine(force = false, skipPrompt = false) {
2650
2691
  }
2651
2692
  const updateSpinner = ora(format(i18n.t("updater:updating"), { tool: "CCometixLine" })).start();
2652
2693
  try {
2653
- await execAsync$2("npm update -g @cometix/ccline");
2694
+ await execWithSudoIfNeeded("npm", ["update", "-g", "@cometix/ccline"]);
2654
2695
  updateSpinner.succeed(format(i18n.t("updater:updateSuccess"), { tool: "CCometixLine" }));
2655
2696
  return true;
2656
2697
  } catch (error) {
@@ -2721,6 +2762,7 @@ async function checkAndUpdateTools(skipPrompt = false) {
2721
2762
  const autoUpdater = {
2722
2763
  __proto__: null,
2723
2764
  checkAndUpdateTools: checkAndUpdateTools,
2765
+ execWithSudoIfNeeded: execWithSudoIfNeeded,
2724
2766
  updateCcr: updateCcr,
2725
2767
  updateClaudeCode: updateClaudeCode,
2726
2768
  updateCometixLine: updateCometixLine
@@ -3342,11 +3384,83 @@ async function configureCodexMcp(options) {
3342
3384
  ensureI18nInitialized();
3343
3385
  const { skipPrompt = false } = options ?? {};
3344
3386
  const existingConfig = readCodexConfig();
3345
- if (skipPrompt)
3346
- return;
3347
3387
  const backupPath = backupCodexComplete();
3348
3388
  if (backupPath)
3349
3389
  console.log(ansis.gray(getBackupMessage(backupPath)));
3390
+ if (skipPrompt) {
3391
+ const { runCodexWorkflowSelection } = await Promise.resolve().then(function () { return codex; });
3392
+ await runCodexWorkflowSelection({ skipPrompt: true, workflows: options?.workflows ?? [] });
3393
+ if (options?.mcpServices === false) {
3394
+ updateZcfConfig({ codeToolType: "codex" });
3395
+ console.log(ansis.green(i18n.t("codex:mcpConfigured")));
3396
+ return;
3397
+ }
3398
+ const defaultServiceIds = Array.isArray(options?.mcpServices) ? options.mcpServices : MCP_SERVICE_CONFIGS.filter((service) => !service.requiresApiKey).map((service) => service.id);
3399
+ const baseProviders2 = existingConfig?.providers || [];
3400
+ const existingServices2 = existingConfig?.mcpServices || [];
3401
+ const selection2 = [];
3402
+ for (const id of defaultServiceIds) {
3403
+ const configInfo = MCP_SERVICE_CONFIGS.find((service) => service.id === id);
3404
+ if (!configInfo)
3405
+ continue;
3406
+ let command = configInfo.config.command || id;
3407
+ let args = (configInfo.config.args || []).map((arg) => String(arg));
3408
+ if (id === "serena") {
3409
+ const idx = args.indexOf("--context");
3410
+ if (idx >= 0 && idx + 1 < args.length)
3411
+ args[idx + 1] = "codex";
3412
+ else
3413
+ args.push("--context", "codex");
3414
+ }
3415
+ const serviceConfig = { id: id.toLowerCase(), command, args };
3416
+ applyCodexPlatformCommand(serviceConfig);
3417
+ command = serviceConfig.command;
3418
+ args = serviceConfig.args || [];
3419
+ const env = { ...configInfo.config.env || {} };
3420
+ if (isWindows()) {
3421
+ const systemRoot = getSystemRoot();
3422
+ if (systemRoot)
3423
+ env.SYSTEMROOT = systemRoot;
3424
+ }
3425
+ selection2.push({
3426
+ id: id.toLowerCase(),
3427
+ command,
3428
+ args,
3429
+ env: Object.keys(env).length > 0 ? env : void 0,
3430
+ startup_timeout_sec: 30
3431
+ });
3432
+ }
3433
+ const mergedMap2 = /* @__PURE__ */ new Map();
3434
+ for (const svc of existingServices2)
3435
+ mergedMap2.set(svc.id.toLowerCase(), { ...svc });
3436
+ for (const svc of selection2)
3437
+ mergedMap2.set(svc.id.toLowerCase(), { ...svc });
3438
+ const finalServices2 = Array.from(mergedMap2.values()).map((svc) => {
3439
+ if (isWindows()) {
3440
+ const systemRoot = getSystemRoot();
3441
+ if (systemRoot) {
3442
+ return {
3443
+ ...svc,
3444
+ env: {
3445
+ ...svc.env || {},
3446
+ SYSTEMROOT: systemRoot
3447
+ }
3448
+ };
3449
+ }
3450
+ }
3451
+ return svc;
3452
+ });
3453
+ writeCodexConfig({
3454
+ model: existingConfig?.model || null,
3455
+ modelProvider: existingConfig?.modelProvider || null,
3456
+ providers: baseProviders2,
3457
+ mcpServices: finalServices2,
3458
+ otherConfig: existingConfig?.otherConfig || []
3459
+ });
3460
+ updateZcfConfig({ codeToolType: "codex" });
3461
+ console.log(ansis.green(i18n.t("codex:mcpConfigured")));
3462
+ return;
3463
+ }
3350
3464
  const selectedIds = await selectMcpServices();
3351
3465
  if (!selectedIds)
3352
3466
  return;
@@ -3457,6 +3571,7 @@ async function configureCodexMcp(options) {
3457
3571
  console.log(ansis.green(i18n.t("codex:mcpConfigured")));
3458
3572
  }
3459
3573
 
3574
+ let cachedSkipPromptBackup = null;
3460
3575
  function getRootDir$1() {
3461
3576
  const currentFilePath = fileURLToPath(import.meta.url);
3462
3577
  let dir = dirname(currentFilePath);
@@ -3545,12 +3660,16 @@ function createBackupDirectory(timestamp) {
3545
3660
  function backupCodexFiles() {
3546
3661
  if (!exists(CODEX_DIR))
3547
3662
  return null;
3663
+ if (process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP === "true" && cachedSkipPromptBackup)
3664
+ return cachedSkipPromptBackup;
3548
3665
  const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
3549
3666
  const backupDir = createBackupDirectory(timestamp);
3550
3667
  const filter = (path) => {
3551
3668
  return !path.includes("/backup");
3552
3669
  };
3553
3670
  copyDir(CODEX_DIR, backupDir, { filter });
3671
+ if (process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP === "true")
3672
+ cachedSkipPromptBackup = backupDir;
3554
3673
  return backupDir;
3555
3674
  }
3556
3675
  function backupCodexComplete() {
@@ -3570,6 +3689,8 @@ function backupCodexConfig() {
3570
3689
  }
3571
3690
  }
3572
3691
  function backupCodexAgents() {
3692
+ if (process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP === "true" && cachedSkipPromptBackup)
3693
+ return cachedSkipPromptBackup;
3573
3694
  if (!exists(CODEX_AGENTS_FILE))
3574
3695
  return null;
3575
3696
  try {
@@ -3583,6 +3704,8 @@ function backupCodexAgents() {
3583
3704
  }
3584
3705
  }
3585
3706
  function backupCodexPrompts() {
3707
+ if (process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP === "true" && cachedSkipPromptBackup)
3708
+ return cachedSkipPromptBackup;
3586
3709
  if (!exists(CODEX_PROMPTS_DIR))
3587
3710
  return null;
3588
3711
  try {
@@ -4138,7 +4261,6 @@ async function runCodexWorkflowImportWithLanguageSelection(options) {
4138
4261
  async function runCodexSystemPromptSelection(skipPrompt = false) {
4139
4262
  ensureI18nInitialized();
4140
4263
  const rootDir = getRootDir$1();
4141
- const templateRoot = join(rootDir, "templates", "codex");
4142
4264
  const zcfConfig$1 = readZcfConfig();
4143
4265
  const { readDefaultTomlConfig: readDefaultTomlConfig2 } = await Promise.resolve().then(function () { return zcfConfig; });
4144
4266
  const tomlConfig = readDefaultTomlConfig2();
@@ -4151,10 +4273,9 @@ async function runCodexSystemPromptSelection(skipPrompt = false) {
4151
4273
  // Pass skipPrompt flag
4152
4274
  );
4153
4275
  updateZcfConfig({ templateLang: preferredLang });
4154
- let langDir = join(templateRoot, preferredLang);
4155
- if (!exists(langDir))
4156
- langDir = join(templateRoot, "zh-CN");
4157
- const systemPromptSrc = join(langDir, "system-prompt");
4276
+ let systemPromptSrc = join(rootDir, "templates", "common", "output-styles", preferredLang);
4277
+ if (!exists(systemPromptSrc))
4278
+ systemPromptSrc = join(rootDir, "templates", "common", "output-styles", "zh-CN");
4158
4279
  if (!exists(systemPromptSrc))
4159
4280
  return;
4160
4281
  const availablePrompts = [
@@ -4216,17 +4337,17 @@ async function runCodexWorkflowSelection(options) {
4216
4337
  ensureI18nInitialized();
4217
4338
  const { skipPrompt = false, workflows: presetWorkflows = [] } = options ?? {};
4218
4339
  const rootDir = getRootDir$1();
4219
- const templateRoot = join(rootDir, "templates", "codex");
4220
4340
  const zcfConfig = readZcfConfig();
4221
4341
  const templateLang = zcfConfig?.templateLang || zcfConfig?.preferredLang || "en";
4222
- const preferredLang = templateLang === "en" ? "en" : "zh-CN";
4223
- let langDir = join(templateRoot, preferredLang);
4224
- if (!exists(langDir))
4225
- langDir = join(templateRoot, "zh-CN");
4226
- const workflowSrc = join(langDir, "workflow");
4342
+ let preferredLang = templateLang === "en" ? "en" : "zh-CN";
4343
+ const workflowSrc = join(rootDir, "templates", "common", "workflow");
4227
4344
  if (!exists(workflowSrc))
4228
4345
  return;
4229
- const allWorkflows = getAllWorkflowFiles(workflowSrc);
4346
+ let allWorkflows = getAllWorkflowFiles(workflowSrc, preferredLang);
4347
+ if (allWorkflows.length === 0 && preferredLang === "en") {
4348
+ preferredLang = "zh-CN";
4349
+ allWorkflows = getAllWorkflowFiles(workflowSrc, preferredLang);
4350
+ }
4230
4351
  if (allWorkflows.length === 0)
4231
4352
  return;
4232
4353
  if (skipPrompt) {
@@ -4240,9 +4361,9 @@ async function runCodexWorkflowSelection(options) {
4240
4361
  const selectedWorkflows = allWorkflows.filter(
4241
4362
  (workflow) => presetWorkflows.includes(workflow.name)
4242
4363
  );
4243
- workflowsToInstall = expandSelectedWorkflowPaths(selectedWorkflows.map((w) => w.path), workflowSrc);
4364
+ workflowsToInstall = expandSelectedWorkflowPaths(selectedWorkflows.map((w) => w.path), workflowSrc, preferredLang);
4244
4365
  } else {
4245
- workflowsToInstall = expandSelectedWorkflowPaths(allWorkflows.map((w) => w.path), workflowSrc);
4366
+ workflowsToInstall = expandSelectedWorkflowPaths(allWorkflows.map((w) => w.path), workflowSrc, preferredLang);
4246
4367
  }
4247
4368
  for (const workflowPath of workflowsToInstall) {
4248
4369
  const content = readFile(workflowPath);
@@ -4270,7 +4391,7 @@ async function runCodexWorkflowSelection(options) {
4270
4391
  if (backupPath) {
4271
4392
  console.log(ansis.gray(getBackupMessage(backupPath)));
4272
4393
  }
4273
- const finalWorkflowPaths = expandSelectedWorkflowPaths(workflows, workflowSrc);
4394
+ const finalWorkflowPaths = expandSelectedWorkflowPaths(workflows, workflowSrc, preferredLang);
4274
4395
  for (const workflowPath of finalWorkflowPaths) {
4275
4396
  const content = readFile(workflowPath);
4276
4397
  const filename = workflowPath.split("/").pop() || "workflow.md";
@@ -4279,19 +4400,16 @@ async function runCodexWorkflowSelection(options) {
4279
4400
  }
4280
4401
  }
4281
4402
  const GIT_GROUP_SENTINEL = "::gitGroup";
4282
- function getAllWorkflowFiles(dirPath) {
4403
+ function getAllWorkflowFiles(workflowSrc, preferredLang) {
4283
4404
  const workflows = [];
4284
- const sixStepDir = join(dirPath, "sixStep", "prompts");
4285
- if (exists(sixStepDir)) {
4286
- const workflowFile = join(sixStepDir, "workflow.md");
4287
- if (exists(workflowFile)) {
4288
- workflows.push({
4289
- name: i18n.t("workflow:workflowOption.sixStepsWorkflow"),
4290
- path: workflowFile
4291
- });
4292
- }
4405
+ const sixStepFile = join(workflowSrc, "sixStep", preferredLang, "workflow.md");
4406
+ if (exists(sixStepFile)) {
4407
+ workflows.push({
4408
+ name: i18n.t("workflow:workflowOption.sixStepsWorkflow"),
4409
+ path: sixStepFile
4410
+ });
4293
4411
  }
4294
- const gitPromptsDir = join(dirPath, "git", "prompts");
4412
+ const gitPromptsDir = join(workflowSrc, "git", preferredLang);
4295
4413
  if (exists(gitPromptsDir)) {
4296
4414
  workflows.push({
4297
4415
  name: i18n.t("workflow:workflowOption.gitWorkflow"),
@@ -4301,19 +4419,19 @@ function getAllWorkflowFiles(dirPath) {
4301
4419
  }
4302
4420
  return workflows;
4303
4421
  }
4304
- function expandSelectedWorkflowPaths(paths, workflowSrc) {
4422
+ function expandSelectedWorkflowPaths(paths, workflowSrc, preferredLang) {
4305
4423
  const expanded = [];
4306
4424
  for (const p of paths) {
4307
4425
  if (p === GIT_GROUP_SENTINEL) {
4308
- expanded.push(...getGitPromptFiles(workflowSrc));
4426
+ expanded.push(...getGitPromptFiles(workflowSrc, preferredLang));
4309
4427
  } else {
4310
4428
  expanded.push(p);
4311
4429
  }
4312
4430
  }
4313
4431
  return expanded;
4314
4432
  }
4315
- function getGitPromptFiles(workflowSrc) {
4316
- const gitDir = join(workflowSrc, "git", "prompts");
4433
+ function getGitPromptFiles(workflowSrc, preferredLang) {
4434
+ const gitPromptsDir = join(workflowSrc, "git", preferredLang);
4317
4435
  const files = [
4318
4436
  "git-commit.md",
4319
4437
  "git-rollback.md",
@@ -4322,7 +4440,7 @@ function getGitPromptFiles(workflowSrc) {
4322
4440
  ];
4323
4441
  const resolved = [];
4324
4442
  for (const f of files) {
4325
- const full = join(gitDir, f);
4443
+ const full = join(gitPromptsDir, f);
4326
4444
  if (exists(full))
4327
4445
  resolved.push(full);
4328
4446
  }
@@ -4353,29 +4471,39 @@ async function applyCustomApiConfig(customApiConfig) {
4353
4471
  if (backupPath) {
4354
4472
  console.log(ansis.gray(getBackupMessage(backupPath)));
4355
4473
  }
4474
+ const existingConfig = readCodexConfig();
4475
+ const existingAuth = readJsonConfig(CODEX_AUTH_FILE, { defaultValue: {} }) || {};
4356
4476
  const providers = [];
4357
- const authEntries = {};
4477
+ const authEntries = { ...existingAuth };
4358
4478
  const providerId = type === "auth_token" ? "official-auth-token" : "custom-api-key";
4359
4479
  const providerName = type === "auth_token" ? "Official Auth Token" : "Custom API Key";
4480
+ const existingProvider = existingConfig?.providers.find((p) => p.id === providerId);
4360
4481
  providers.push({
4361
4482
  id: providerId,
4362
4483
  name: providerName,
4363
- baseUrl: baseUrl || "https://api.anthropic.com",
4364
- wireApi: "claude",
4365
- tempEnvKey: `${providerId.toUpperCase()}_API_KEY`,
4366
- requiresOpenaiAuth: false
4484
+ baseUrl: baseUrl || existingProvider?.baseUrl || "https://api.anthropic.com",
4485
+ wireApi: existingProvider?.wireApi || "responses",
4486
+ tempEnvKey: existingProvider?.tempEnvKey || `${providerId.toUpperCase()}_API_KEY`,
4487
+ requiresOpenaiAuth: existingProvider?.requiresOpenaiAuth ?? false,
4488
+ model: model || existingProvider?.model
4367
4489
  });
4490
+ if (existingConfig?.providers) {
4491
+ providers.push(...existingConfig.providers.filter((p) => p.id !== providerId));
4492
+ }
4368
4493
  if (token) {
4369
4494
  authEntries[providerId] = token;
4495
+ authEntries.OPENAI_API_KEY = token;
4370
4496
  }
4371
4497
  const configData = {
4372
- model: model || "claude-3-5-sonnet-20241022",
4373
- // Use provided model or default
4498
+ model: model || existingConfig?.model || "claude-3-5-sonnet-20241022",
4499
+ // Prefer provided model, then existing, fallback default
4374
4500
  modelProvider: providerId,
4375
4501
  modelProviderCommented: false,
4376
4502
  providers,
4377
- mcpServices: []};
4378
- writeFile(CODEX_CONFIG_FILE, renderCodexConfig(configData));
4503
+ mcpServices: existingConfig?.mcpServices || [],
4504
+ otherConfig: existingConfig?.otherConfig || []
4505
+ };
4506
+ writeCodexConfig(configData);
4379
4507
  writeJsonConfig(CODEX_AUTH_FILE, authEntries);
4380
4508
  updateZcfConfig({ codeToolType: "codex" });
4381
4509
  console.log(ansis.green(`\u2714 ${i18n.t("codex:apiConfigured")}`));
@@ -5135,7 +5263,7 @@ async function copyOutputStyles(selectedStyles, lang) {
5135
5263
  const currentFilePath = fileURLToPath(import.meta.url);
5136
5264
  const distDir = dirname(dirname(currentFilePath));
5137
5265
  const rootDir = dirname(distDir);
5138
- const templateDir = join(rootDir, "templates", "claude-code", lang, "output-styles");
5266
+ const templateDir = join(rootDir, "templates", "common", "output-styles", lang);
5139
5267
  for (const styleId of selectedStyles) {
5140
5268
  const style = OUTPUT_STYLES.find((s) => s.id === styleId);
5141
5269
  if (!style || !style.isCustom || !style.filePath) {
@@ -6283,6 +6411,7 @@ function getRootDir() {
6283
6411
  return dirname(distDir);
6284
6412
  }
6285
6413
  const DEFAULT_CODE_TOOL_TEMPLATE = "claude-code";
6414
+ const COMMON_TEMPLATE_CATEGORIES = ["git", "sixStep"];
6286
6415
  async function selectAndInstallWorkflows(configLang, preselectedWorkflows) {
6287
6416
  ensureI18nInitialized();
6288
6417
  const workflows = getOrderedWorkflows();
@@ -6342,7 +6471,16 @@ async function installWorkflowWithDependencies(config, configLang) {
6342
6471
  await mkdir(commandsDir, { recursive: true });
6343
6472
  }
6344
6473
  for (const commandFile of config.commands) {
6345
- const commandSource = join(
6474
+ const isCommonTemplate = COMMON_TEMPLATE_CATEGORIES.includes(config.category);
6475
+ const commandSource = isCommonTemplate ? join(
6476
+ rootDir,
6477
+ "templates",
6478
+ "common",
6479
+ "workflow",
6480
+ config.category,
6481
+ configLang,
6482
+ commandFile
6483
+ ) : join(
6346
6484
  rootDir,
6347
6485
  "templates",
6348
6486
  DEFAULT_CODE_TOOL_TEMPLATE,
@@ -6508,15 +6646,18 @@ async function validateSkipPromptOptions(options) {
6508
6646
  if (options.apiConfigs && options.apiConfigsFile) {
6509
6647
  throw new Error(i18n.t("multi-config:conflictingParams"));
6510
6648
  }
6511
- if (options.apiModel && typeof options.apiModel !== "string") {
6512
- throw new Error(
6513
- i18n.t("errors:invalidApiModel", { value: options.apiModel })
6514
- );
6515
- }
6516
- if (options.apiFastModel && typeof options.apiFastModel !== "string") {
6517
- throw new Error(
6518
- i18n.t("errors:invalidApiFastModel", { value: options.apiFastModel })
6519
- );
6649
+ const modelParams = [
6650
+ ["apiModel", options.apiModel],
6651
+ ["apiHaikuModel", options.apiHaikuModel],
6652
+ ["apiSonnetModel", options.apiSonnetModel],
6653
+ ["apiOpusModel", options.apiOpusModel]
6654
+ ];
6655
+ for (const [key, value] of modelParams) {
6656
+ if (value !== void 0 && typeof value !== "string") {
6657
+ if (key === "apiModel")
6658
+ throw new Error(i18n.t("errors:invalidApiModel", { value }));
6659
+ throw new Error(i18n.t("errors:invalidModelParam", { key, value }));
6660
+ }
6520
6661
  }
6521
6662
  if (options.apiType === "api_key" && !options.apiKey) {
6522
6663
  throw new Error(i18n.t("errors:apiKeyRequiredForApiKey"));
@@ -6706,8 +6847,11 @@ async function init(options = {}) {
6706
6847
  configLang = "en";
6707
6848
  }
6708
6849
  if (codeToolType === "codex") {
6709
- const apiMode = options.apiType === "auth_token" ? "official" : options.apiType === "api_key" ? "custom" : options.apiType === "skip" ? "skip" : options.skipPrompt ? "skip" : void 0;
6710
- const customApiConfig = options.apiType === "api_key" && options.apiKey ? {
6850
+ if (options.skipPrompt)
6851
+ process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP = "true";
6852
+ const hasApiConfigs = Boolean(options.apiConfigs || options.apiConfigsFile);
6853
+ const apiMode = hasApiConfigs ? "skip" : options.apiType === "auth_token" ? "official" : options.apiType === "api_key" ? "custom" : options.apiType === "skip" ? "skip" : options.skipPrompt ? "skip" : void 0;
6854
+ const customApiConfig = !hasApiConfigs && options.apiType === "api_key" && options.apiKey ? {
6711
6855
  type: "api_key",
6712
6856
  token: options.apiKey,
6713
6857
  baseUrl: options.apiUrl,
@@ -6722,6 +6866,9 @@ async function init(options = {}) {
6722
6866
  } else if (options.workflows === true) {
6723
6867
  selectedWorkflows = [];
6724
6868
  }
6869
+ if (hasApiConfigs) {
6870
+ await handleMultiConfigurations(options, "codex");
6871
+ }
6725
6872
  const resolvedAiOutputLang = await runCodexFullInit({
6726
6873
  aiOutputLang: options.aiOutputLang,
6727
6874
  skipPrompt: options.skipPrompt,
@@ -6840,9 +6987,12 @@ async function init(options = {}) {
6840
6987
  key: options.apiKey,
6841
6988
  url: preset?.claudeCode?.baseUrl || options.apiUrl || API_DEFAULT_URL
6842
6989
  };
6843
- if (preset?.claudeCode?.defaultModels && preset.claudeCode.defaultModels.length >= 2) {
6844
- options.apiModel = options.apiModel || preset.claudeCode.defaultModels[0];
6845
- options.apiFastModel = options.apiFastModel || preset.claudeCode.defaultModels[1];
6990
+ if (preset?.claudeCode?.defaultModels && preset.claudeCode.defaultModels.length > 0) {
6991
+ const [primary, haiku, sonnet, opus] = preset.claudeCode.defaultModels;
6992
+ options.apiModel = options.apiModel || primary;
6993
+ options.apiHaikuModel = options.apiHaikuModel || haiku;
6994
+ options.apiSonnetModel = options.apiSonnetModel || sonnet;
6995
+ options.apiOpusModel = options.apiOpusModel || opus;
6846
6996
  }
6847
6997
  await saveSingleConfigToToml(apiConfig, options.provider, options);
6848
6998
  } else if (options.apiType === "auth_token" && options.apiKey) {
@@ -6969,20 +7119,26 @@ async function init(options = {}) {
6969
7119
  console.log(ansis.gray(` Key: ${formatApiKeyDisplay(configuredApi.key)}`));
6970
7120
  }
6971
7121
  }
6972
- if ((options.apiModel || options.apiFastModel) && action !== "docs-only" && codeToolType === "claude-code") {
7122
+ const hasModelParams = options.apiModel || options.apiHaikuModel || options.apiSonnetModel || options.apiOpusModel;
7123
+ if (hasModelParams && action !== "docs-only" && codeToolType === "claude-code") {
6973
7124
  if (options.skipPrompt) {
6974
7125
  const { updateCustomModel } = await Promise.resolve().then(function () { return config$1; });
6975
7126
  updateCustomModel(
6976
7127
  options.apiModel || void 0,
6977
- options.apiFastModel || void 0
7128
+ options.apiHaikuModel || void 0,
7129
+ options.apiSonnetModel || void 0,
7130
+ options.apiOpusModel || void 0
6978
7131
  );
6979
7132
  console.log(ansis.green(`\u2714 ${i18n.t("api:modelConfigSuccess")}`));
6980
7133
  if (options.apiModel) {
6981
7134
  console.log(ansis.gray(` ${i18n.t("api:primaryModel")}: ${options.apiModel}`));
6982
7135
  }
6983
- if (options.apiFastModel) {
6984
- console.log(ansis.gray(` ${i18n.t("api:fastModel")}: ${options.apiFastModel}`));
6985
- }
7136
+ if (options.apiHaikuModel)
7137
+ console.log(ansis.gray(` Haiku: ${options.apiHaikuModel}`));
7138
+ if (options.apiSonnetModel)
7139
+ console.log(ansis.gray(` Sonnet: ${options.apiSonnetModel}`));
7140
+ if (options.apiOpusModel)
7141
+ console.log(ansis.gray(` Opus: ${options.apiOpusModel}`));
6986
7142
  }
6987
7143
  }
6988
7144
  if (action !== "docs-only") {
@@ -7205,6 +7361,7 @@ async function handleClaudeCodeConfigs(configs) {
7205
7361
  }
7206
7362
  async function handleCodexConfigs(configs) {
7207
7363
  const { addProviderToExisting } = await import('./codex-provider-manager.mjs');
7364
+ const addedProviderIds = [];
7208
7365
  for (const config of configs) {
7209
7366
  try {
7210
7367
  const provider = await convertToCodexProvider(config);
@@ -7212,6 +7369,7 @@ async function handleCodexConfigs(configs) {
7212
7369
  if (!result.success) {
7213
7370
  throw new Error(i18n.t("multi-config:providerAddFailed", { name: config.name, error: result.error }));
7214
7371
  }
7372
+ addedProviderIds.push(provider.id);
7215
7373
  console.log(ansis.green(`\u2714 ${i18n.t("multi-config:providerAdded", { name: config.name })}`));
7216
7374
  } catch (error) {
7217
7375
  console.error(ansis.red(i18n.t("multi-config:providerAddFailed", {
@@ -7224,8 +7382,14 @@ async function handleCodexConfigs(configs) {
7224
7382
  const defaultConfig = configs.find((c) => c.default);
7225
7383
  if (defaultConfig) {
7226
7384
  const { switchCodexProvider } = await Promise.resolve().then(function () { return codex; });
7227
- await switchCodexProvider(defaultConfig.name);
7228
- console.log(ansis.green(`\u2714 ${i18n.t("multi-config:defaultProviderSet", { name: defaultConfig.name })}`));
7385
+ const displayName = defaultConfig.name || defaultConfig.provider || "custom";
7386
+ const providerId = displayName.toLowerCase().replace(/[^a-z0-9]/g, "-");
7387
+ if (addedProviderIds.includes(providerId)) {
7388
+ await switchCodexProvider(providerId);
7389
+ console.log(ansis.green(`\u2714 ${i18n.t("multi-config:defaultProviderSet", { name: displayName })}`));
7390
+ } else {
7391
+ console.log(ansis.red(i18n.t("multi-config:providerAddFailed", { name: displayName, error: "provider not added" })));
7392
+ }
7229
7393
  }
7230
7394
  }
7231
7395
  async function saveSingleConfigToToml(apiConfig, provider, options) {
@@ -7252,7 +7416,9 @@ async function convertSingleConfigToProfile(apiConfig, provider, options) {
7252
7416
  const configName = provider && provider !== "custom" ? provider : "custom-config";
7253
7417
  let baseUrl = apiConfig.url || API_DEFAULT_URL;
7254
7418
  let primaryModel = options?.apiModel;
7255
- let fastModel = options?.apiFastModel;
7419
+ let defaultHaikuModel = options?.apiHaikuModel;
7420
+ let defaultSonnetModel = options?.apiSonnetModel;
7421
+ let defaultOpusModel = options?.apiOpusModel;
7256
7422
  let authType = apiConfig.authType;
7257
7423
  if (provider && provider !== "custom") {
7258
7424
  const { getProviderPreset } = await import('./api-providers.mjs');
@@ -7260,9 +7426,12 @@ async function convertSingleConfigToProfile(apiConfig, provider, options) {
7260
7426
  if (preset?.claudeCode) {
7261
7427
  baseUrl = apiConfig.url || preset.claudeCode.baseUrl;
7262
7428
  authType = preset.claudeCode.authType;
7263
- if (preset.claudeCode.defaultModels && preset.claudeCode.defaultModels.length >= 2) {
7264
- primaryModel = primaryModel || preset.claudeCode.defaultModels[0];
7265
- fastModel = fastModel || preset.claudeCode.defaultModels[1];
7429
+ if (preset.claudeCode.defaultModels && preset.claudeCode.defaultModels.length > 0) {
7430
+ const [p, h, s, o] = preset.claudeCode.defaultModels;
7431
+ primaryModel = primaryModel || p;
7432
+ defaultHaikuModel = defaultHaikuModel || h;
7433
+ defaultSonnetModel = defaultSonnetModel || s;
7434
+ defaultOpusModel = defaultOpusModel || o;
7266
7435
  }
7267
7436
  }
7268
7437
  }
@@ -7272,7 +7441,9 @@ async function convertSingleConfigToProfile(apiConfig, provider, options) {
7272
7441
  apiKey: apiConfig.key,
7273
7442
  baseUrl,
7274
7443
  primaryModel,
7275
- fastModel,
7444
+ defaultHaikuModel,
7445
+ defaultSonnetModel,
7446
+ defaultOpusModel,
7276
7447
  id: ClaudeCodeConfigManager.generateProfileId(configName)
7277
7448
  };
7278
7449
  return profile;
@@ -7281,7 +7452,9 @@ async function convertToClaudeCodeProfile(config) {
7281
7452
  const { ClaudeCodeConfigManager } = await import('./claude-code-config-manager.mjs');
7282
7453
  let baseUrl = config.url;
7283
7454
  let primaryModel = config.primaryModel;
7284
- let fastModel = config.fastModel;
7455
+ let defaultHaikuModel = config.defaultHaikuModel;
7456
+ let defaultSonnetModel = config.defaultSonnetModel;
7457
+ let defaultOpusModel = config.defaultOpusModel;
7285
7458
  let authType = config.type || "api_key";
7286
7459
  if (config.provider && config.provider !== "custom") {
7287
7460
  const { getProviderPreset } = await import('./api-providers.mjs');
@@ -7289,9 +7462,12 @@ async function convertToClaudeCodeProfile(config) {
7289
7462
  if (preset?.claudeCode) {
7290
7463
  baseUrl = baseUrl || preset.claudeCode.baseUrl;
7291
7464
  authType = preset.claudeCode.authType;
7292
- if (preset.claudeCode.defaultModels && preset.claudeCode.defaultModels.length >= 2) {
7293
- primaryModel = primaryModel || preset.claudeCode.defaultModels[0];
7294
- fastModel = fastModel || preset.claudeCode.defaultModels[1];
7465
+ if (preset.claudeCode.defaultModels && preset.claudeCode.defaultModels.length > 0) {
7466
+ const [p, h, s, o] = preset.claudeCode.defaultModels;
7467
+ primaryModel = primaryModel || p;
7468
+ defaultHaikuModel = defaultHaikuModel || h;
7469
+ defaultSonnetModel = defaultSonnetModel || s;
7470
+ defaultOpusModel = defaultOpusModel || o;
7295
7471
  }
7296
7472
  }
7297
7473
  }
@@ -7301,15 +7477,19 @@ async function convertToClaudeCodeProfile(config) {
7301
7477
  apiKey: config.key,
7302
7478
  baseUrl,
7303
7479
  primaryModel,
7304
- fastModel,
7480
+ defaultHaikuModel,
7481
+ defaultSonnetModel,
7482
+ defaultOpusModel,
7305
7483
  id: ClaudeCodeConfigManager.generateProfileId(config.name)
7306
7484
  };
7307
7485
  return profile;
7308
7486
  }
7309
7487
  async function convertToCodexProvider(config) {
7488
+ const displayName = config.name || config.provider || "custom";
7489
+ const providerId = displayName.toLowerCase().replace(/[^a-z0-9]/g, "-");
7310
7490
  let baseUrl = config.url || API_DEFAULT_URL;
7311
7491
  let model = config.primaryModel || "gpt-5-codex";
7312
- let wireApi = "chat";
7492
+ let wireApi = "responses";
7313
7493
  if (config.provider && config.provider !== "custom") {
7314
7494
  const { getProviderPreset } = await import('./api-providers.mjs');
7315
7495
  const preset = getProviderPreset(config.provider);
@@ -7320,11 +7500,11 @@ async function convertToCodexProvider(config) {
7320
7500
  }
7321
7501
  }
7322
7502
  return {
7323
- id: config.name.toLowerCase().replace(/[^a-z0-9]/g, "-"),
7324
- name: config.name,
7503
+ id: providerId,
7504
+ name: displayName,
7325
7505
  baseUrl,
7326
7506
  wireApi,
7327
- tempEnvKey: API_ENV_KEY,
7507
+ tempEnvKey: `${displayName}_API_KEY`.replace(/\W/g, "_").toUpperCase(),
7328
7508
  requiresOpenaiAuth: false,
7329
7509
  model
7330
7510
  };
@@ -7408,4 +7588,4 @@ async function openSettingsJson() {
7408
7588
  }
7409
7589
  }
7410
7590
 
7411
- export { getExistingModelConfig as $, API_DEFAULT_URL as A, getAiOutputLanguageLabel as B, CLAUDE_DIR as C, DEFAULT_CODE_TOOL_TYPE as D, getMcpConfigPath as E, readMcpConfig as F, writeMcpConfig as G, backupMcpConfig as H, mergeMcpServers as I, buildMcpServerConfig as J, fixWindowsMcpConfig as K, LEGACY_ZCF_CONFIG_FILES as L, addCompletedOnboarding as M, ensureApiKeyApproved as N, removeApiKeyFromRejected as O, manageApiKeyApproval as P, setPrimaryApiKey as Q, ensureClaudeDir as R, SETTINGS_FILE as S, backupExistingConfig as T, copyConfigFiles as U, configureApi as V, mergeConfigs as W, updateCustomModel as X, updateDefaultModel as Y, ZCF_CONFIG_DIR as Z, mergeSettingsFile as _, commandExists as a, updatePromptOnly as a$, getExistingApiConfig as a0, applyAiLanguageDirective as a1, switchToOfficialLogin$1 as a2, promptApiConfigurationAction as a3, isClaudeCodeInstalled as a4, installClaudeCode as a5, isCodexInstalled as a6, installCodex as a7, isLocalClaudeCodeInstalled as a8, getInstallationStatus as a9, writeAuthFile as aA, updateZcfConfig as aB, changeLanguage as aC, readZcfConfig as aD, configureOutputStyle as aE, isWindows as aF, selectMcpServices as aG, getMcpServices as aH, isCcrInstalled as aI, installCcr as aJ, setupCcrConfiguration as aK, modifyApiConfigPartially as aL, formatApiKeyDisplay as aM, readCcrConfig as aN, configureCcrFeature as aO, handleExitPromptError as aP, handleGeneralError as aQ, COMETIX_COMMAND_NAME as aR, COMETIX_COMMANDS as aS, installCometixLine as aT, checkAndUpdateTools as aU, runCodexUpdate as aV, resolveCodeType as aW, writeJsonConfig as aX, displayBanner as aY, version as aZ, resolveAiOutputLanguage as a_, removeLocalClaudeCode as aa, uninstallCodeTool as ab, setInstallMethod as ac, detectInstalledVersion as ad, selectInstallMethod as ae, executeInstallMethod as af, handleInstallFailure as ag, verifyInstallation as ah, createHomebrewSymlink as ai, displayVerificationResult as aj, ensureI18nInitialized as ak, i18n as al, addNumbersToChoices as am, validateApiKey as an, promptBoolean as ao, ensureDir as ap, readDefaultTomlConfig as aq, createDefaultTomlConfig as ar, exists as as, readJsonConfig as at, writeTomlConfig as au, copyFile as av, detectConfigManagementMode as aw, readCodexConfig as ax, backupCodexComplete as ay, writeCodexConfig as az, importRecommendedEnv as b, selectAndInstallWorkflows as b0, checkClaudeCodeVersionAndPrompt as b1, displayBannerWithInfo as b2, runCodexUninstall as b3, configureCodexMcp as b4, configureCodexApi as b5, runCodexWorkflowImportWithLanguageSelection as b6, runCodexFullInit as b7, switchCodexProvider as b8, listCodexProviders as b9, switchToOfficialLogin as ba, switchToProvider as bb, readZcfConfigAsync as bc, initI18n as bd, selectScriptLanguage as be, index as bf, fsOperations as bg, jsonConfig as bh, claudeConfig as bi, config$1 as bj, config as bk, prompts as bl, codex as bm, installer as bn, cleanupPermissions as c, importRecommendedPermissions as d, CLAUDE_MD_FILE as e, ClAUDE_CONFIG_FILE as f, getPlatform as g, CLAUDE_VSC_CONFIG_FILE as h, init as i, CODEX_DIR as j, CODEX_CONFIG_FILE as k, CODEX_AUTH_FILE as l, mergeAndCleanPermissions as m, CODEX_AGENTS_FILE as n, openSettingsJson as o, CODEX_PROMPTS_DIR as p, ZCF_CONFIG_FILE as q, CODE_TOOL_TYPES as r, CODE_TOOL_BANNERS as s, CODE_TOOL_ALIASES as t, isCodeToolType as u, API_ENV_KEY as v, resolveCodeToolType as w, SUPPORTED_LANGS as x, LANG_LABELS as y, AI_OUTPUT_LANGUAGES as z };
7591
+ export { getExistingModelConfig as $, API_DEFAULT_URL as A, getAiOutputLanguageLabel as B, CLAUDE_DIR as C, DEFAULT_CODE_TOOL_TYPE as D, getMcpConfigPath as E, readMcpConfig as F, writeMcpConfig as G, backupMcpConfig as H, mergeMcpServers as I, buildMcpServerConfig as J, fixWindowsMcpConfig as K, LEGACY_ZCF_CONFIG_FILES as L, addCompletedOnboarding as M, ensureApiKeyApproved as N, removeApiKeyFromRejected as O, manageApiKeyApproval as P, setPrimaryApiKey as Q, ensureClaudeDir as R, SETTINGS_FILE as S, backupExistingConfig as T, copyConfigFiles as U, configureApi as V, mergeConfigs as W, updateCustomModel as X, updateDefaultModel as Y, ZCF_CONFIG_DIR as Z, mergeSettingsFile as _, commandExists as a, resolveAiOutputLanguage as a$, getExistingApiConfig as a0, applyAiLanguageDirective as a1, switchToOfficialLogin$1 as a2, promptApiConfigurationAction as a3, isClaudeCodeInstalled as a4, installClaudeCode as a5, isCodexInstalled as a6, installCodex as a7, isLocalClaudeCodeInstalled as a8, getInstallationStatus as a9, writeCodexConfig as aA, writeAuthFile as aB, updateZcfConfig as aC, changeLanguage as aD, readZcfConfig as aE, configureOutputStyle as aF, isWindows as aG, selectMcpServices as aH, getMcpServices as aI, isCcrInstalled as aJ, installCcr as aK, setupCcrConfiguration as aL, modifyApiConfigPartially as aM, formatApiKeyDisplay as aN, readCcrConfig as aO, configureCcrFeature as aP, handleExitPromptError as aQ, handleGeneralError as aR, COMETIX_COMMAND_NAME as aS, COMETIX_COMMANDS as aT, installCometixLine as aU, checkAndUpdateTools as aV, runCodexUpdate as aW, resolveCodeType as aX, writeJsonConfig as aY, displayBanner as aZ, version as a_, removeLocalClaudeCode as aa, uninstallCodeTool as ab, setInstallMethod as ac, detectInstalledVersion as ad, selectInstallMethod as ae, executeInstallMethod as af, handleInstallFailure as ag, verifyInstallation as ah, createHomebrewSymlink as ai, displayVerificationResult as aj, ensureI18nInitialized as ak, i18n as al, addNumbersToChoices as am, validateApiKey as an, promptBoolean as ao, ensureDir as ap, readDefaultTomlConfig as aq, createDefaultTomlConfig as ar, exists as as, readJsonConfig as at, writeTomlConfig as au, clearModelEnv as av, copyFile as aw, detectConfigManagementMode as ax, readCodexConfig as ay, backupCodexComplete as az, importRecommendedEnv as b, updatePromptOnly as b0, selectAndInstallWorkflows as b1, checkClaudeCodeVersionAndPrompt as b2, displayBannerWithInfo as b3, runCodexUninstall as b4, configureCodexMcp as b5, configureCodexApi as b6, runCodexWorkflowImportWithLanguageSelection as b7, runCodexFullInit as b8, switchCodexProvider as b9, listCodexProviders as ba, switchToOfficialLogin as bb, switchToProvider as bc, readZcfConfigAsync as bd, initI18n as be, selectScriptLanguage as bf, index as bg, fsOperations as bh, jsonConfig as bi, claudeConfig as bj, config$1 as bk, config as bl, prompts as bm, codex as bn, installer as bo, cleanupPermissions as c, importRecommendedPermissions as d, CLAUDE_MD_FILE as e, ClAUDE_CONFIG_FILE as f, getPlatform as g, CLAUDE_VSC_CONFIG_FILE as h, init as i, CODEX_DIR as j, CODEX_CONFIG_FILE as k, CODEX_AUTH_FILE as l, mergeAndCleanPermissions as m, CODEX_AGENTS_FILE as n, openSettingsJson as o, CODEX_PROMPTS_DIR as p, ZCF_CONFIG_FILE as q, CODE_TOOL_TYPES as r, CODE_TOOL_BANNERS as s, CODE_TOOL_ALIASES as t, isCodeToolType as u, API_ENV_KEY as v, resolveCodeToolType as w, SUPPORTED_LANGS as x, LANG_LABELS as y, AI_OUTPUT_LANGUAGES as z };