zcf 3.3.3 → 3.4.1

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.
@@ -9,6 +9,7 @@ import { promisify } from 'node:util';
9
9
  import dayjs from 'dayjs';
10
10
  import { dirname, join } from 'pathe';
11
11
  import { fileURLToPath } from 'node:url';
12
+ import toggleModule from 'inquirer-toggle';
12
13
  import ora from 'ora';
13
14
  import semver from 'semver';
14
15
  import { stringify, parse } from 'smol-toml';
@@ -17,7 +18,7 @@ import { rm, mkdir, copyFile as copyFile$1 } from 'node:fs/promises';
17
18
  import i18next from 'i18next';
18
19
  import Backend from 'i18next-fs-backend';
19
20
 
20
- const version = "3.3.3";
21
+ const version = "3.4.1";
21
22
  const homepage = "https://github.com/UfoMiao/zcf";
22
23
 
23
24
  const i18n = i18next.createInstance();
@@ -589,6 +590,9 @@ function getMcpCommand(command = "npx") {
589
590
  }
590
591
  return [command];
591
592
  }
593
+ function normalizeTomlPath(str) {
594
+ return str.replace(/\\+/g, "/").replace(/\/+/g, "/");
595
+ }
592
596
  function getSystemRoot() {
593
597
  if (!isWindows())
594
598
  return null;
@@ -598,7 +602,7 @@ function getSystemRoot() {
598
602
  systemRoot = env.SYSTEMROOT;
599
603
  else if (Object.prototype.hasOwnProperty.call(env, "SystemRoot") && env.SystemRoot)
600
604
  systemRoot = env.SystemRoot;
601
- return systemRoot.replace(/\\+/g, "/").replace(/\/+/g, "/");
605
+ return normalizeTomlPath(systemRoot);
602
606
  }
603
607
  function shouldUseSudoForGlobalInstall() {
604
608
  if (isTermux())
@@ -635,7 +639,7 @@ function wrapCommandWithSudo(command, args) {
635
639
  }
636
640
  const WRITE_CHECK_FLAG = 2;
637
641
  function normalizePath(path) {
638
- return path.replace(/\\/g, "/").replace(/\/+$/, "");
642
+ return normalizeTomlPath(path).replace(/\/+$/, "");
639
643
  }
640
644
  function isPathInsideHome(path) {
641
645
  const home = process.env.HOME;
@@ -702,6 +706,30 @@ async function commandExists(command) {
702
706
  }
703
707
  return false;
704
708
  }
709
+ function getRecommendedInstallMethods(codeType) {
710
+ const platform2 = getPlatform();
711
+ const wsl = isWSL();
712
+ if (codeType === "claude-code") {
713
+ if (platform2 === "macos") {
714
+ return ["homebrew", "curl", "npm"];
715
+ }
716
+ if (platform2 === "linux" || wsl) {
717
+ return ["curl", "npm"];
718
+ }
719
+ if (platform2 === "windows") {
720
+ return ["powershell", "npm"];
721
+ }
722
+ }
723
+ if (codeType === "codex") {
724
+ if (platform2 === "macos") {
725
+ return ["homebrew", "npm"];
726
+ }
727
+ if (platform2 === "linux" || wsl || platform2 === "windows") {
728
+ return ["npm"];
729
+ }
730
+ }
731
+ return ["npm"];
732
+ }
705
733
 
706
734
  class FileSystemError extends Error {
707
735
  constructor(message, path, cause) {
@@ -1485,6 +1513,16 @@ const config$1 = {
1485
1513
  updateDefaultModel: updateDefaultModel
1486
1514
  };
1487
1515
 
1516
+ const togglePrompt = toggleModule?.default?.default || toggleModule?.default || toggleModule;
1517
+ async function promptBoolean(options) {
1518
+ const { message, defaultValue = false, theme } = options;
1519
+ return await togglePrompt({
1520
+ message,
1521
+ default: defaultValue,
1522
+ ...theme ? { theme } : {}
1523
+ });
1524
+ }
1525
+
1488
1526
  const PROVIDER_PRESETS_URL = "https://pub-0dc3e1677e894f07bbea11b17a29e032.r2.dev/providers.json";
1489
1527
  async function fetchProviderPresets() {
1490
1528
  try {
@@ -1848,13 +1886,10 @@ async function setupCcrConfiguration() {
1848
1886
  console.log(ansis.blue(`\u2139 ${i18n.t("ccr:existingCcrConfig")}`));
1849
1887
  let shouldBackupAndReconfigure = false;
1850
1888
  try {
1851
- const result = await inquirer.prompt({
1852
- type: "confirm",
1853
- name: "overwrite",
1889
+ shouldBackupAndReconfigure = await promptBoolean({
1854
1890
  message: i18n.t("ccr:overwriteCcrConfig"),
1855
- default: false
1891
+ defaultValue: false
1856
1892
  });
1857
- shouldBackupAndReconfigure = result.overwrite;
1858
1893
  } catch (error) {
1859
1894
  if (error.name === "ExitPromptError") {
1860
1895
  console.log(ansis.yellow(i18n.t("common:cancelled")));
@@ -2054,11 +2089,9 @@ async function updateCcr(force = false, skipPrompt = false) {
2054
2089
  console.log(ansis.cyan(format(i18n.t("updater:currentVersion"), { version: currentVersion || "" })));
2055
2090
  console.log(ansis.cyan(format(i18n.t("updater:latestVersion"), { version: latestVersion })));
2056
2091
  if (!skipPrompt) {
2057
- const { confirm } = await inquirer.prompt({
2058
- type: "confirm",
2059
- name: "confirm",
2092
+ const confirm = await promptBoolean({
2060
2093
  message: format(i18n.t("updater:confirmUpdate"), { tool: "CCR" }),
2061
- default: true
2094
+ defaultValue: true
2062
2095
  });
2063
2096
  if (!confirm) {
2064
2097
  console.log(ansis.gray(i18n.t("updater:updateSkipped")));
@@ -2104,11 +2137,9 @@ async function updateClaudeCode(force = false, skipPrompt = false) {
2104
2137
  console.log(ansis.cyan(format(i18n.t("updater:currentVersion"), { version: currentVersion || "" })));
2105
2138
  console.log(ansis.cyan(format(i18n.t("updater:latestVersion"), { version: latestVersion })));
2106
2139
  if (!skipPrompt) {
2107
- const { confirm } = await inquirer.prompt({
2108
- type: "confirm",
2109
- name: "confirm",
2140
+ const confirm = await promptBoolean({
2110
2141
  message: format(i18n.t("updater:confirmUpdate"), { tool: "Claude Code" }),
2111
- default: true
2142
+ defaultValue: true
2112
2143
  });
2113
2144
  if (!confirm) {
2114
2145
  console.log(ansis.gray(i18n.t("updater:updateSkipped")));
@@ -2119,7 +2150,7 @@ async function updateClaudeCode(force = false, skipPrompt = false) {
2119
2150
  }
2120
2151
  const updateSpinner = ora(format(i18n.t("updater:updating"), { tool: "Claude Code" })).start();
2121
2152
  try {
2122
- await execAsync$2("npm update -g @anthropic-ai/claude-code");
2153
+ await execAsync$2("claude update");
2123
2154
  updateSpinner.succeed(format(i18n.t("updater:updateSuccess"), { tool: "Claude Code" }));
2124
2155
  return true;
2125
2156
  } catch (error) {
@@ -2154,11 +2185,9 @@ async function updateCometixLine(force = false, skipPrompt = false) {
2154
2185
  console.log(ansis.cyan(format(i18n.t("updater:currentVersion"), { version: currentVersion || "" })));
2155
2186
  console.log(ansis.cyan(format(i18n.t("updater:latestVersion"), { version: latestVersion })));
2156
2187
  if (!skipPrompt) {
2157
- const { confirm } = await inquirer.prompt({
2158
- type: "confirm",
2159
- name: "confirm",
2188
+ const confirm = await promptBoolean({
2160
2189
  message: format(i18n.t("updater:confirmUpdate"), { tool: "CCometixLine" }),
2161
- default: true
2190
+ defaultValue: true
2162
2191
  });
2163
2192
  if (!confirm) {
2164
2193
  console.log(ansis.gray(i18n.t("updater:updateSkipped")));
@@ -2657,16 +2686,10 @@ async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfi
2657
2686
  }
2658
2687
  const currentLanguageLabel = getAiOutputLanguageLabel(savedConfig.aiOutputLang) || savedConfig.aiOutputLang;
2659
2688
  console.log(ansis.blue(`${i18n.t("language:currentConfigFound")}: ${currentLanguageLabel}`));
2660
- const { shouldModify } = await inquirer.prompt({
2661
- type: "confirm",
2662
- name: "shouldModify",
2689
+ const shouldModify = await promptBoolean({
2663
2690
  message: i18n.t("language:modifyConfigPrompt"),
2664
- default: false
2691
+ defaultValue: false
2665
2692
  });
2666
- if (shouldModify === void 0) {
2667
- console.log(ansis.yellow(i18n.t("common:cancelled")));
2668
- process.exit(0);
2669
- }
2670
2693
  if (!shouldModify) {
2671
2694
  console.log(ansis.gray(`\u2714 ${i18n.t("language:aiOutputLangHint")}: ${currentLanguageLabel}`));
2672
2695
  return savedConfig.aiOutputLang;
@@ -2712,16 +2735,10 @@ async function resolveTemplateLanguage(commandLineOption, savedConfig, skipPromp
2712
2735
  }
2713
2736
  const currentLanguageLabel = LANG_LABELS[savedConfig.templateLang];
2714
2737
  console.log(ansis.blue(`${i18n.t("language:currentTemplateLanguageFound")}: ${currentLanguageLabel}`));
2715
- const { shouldModify } = await inquirer.prompt({
2716
- type: "confirm",
2717
- name: "shouldModify",
2738
+ const shouldModify = await promptBoolean({
2718
2739
  message: i18n.t("language:modifyTemplateLanguagePrompt"),
2719
- default: false
2740
+ defaultValue: false
2720
2741
  });
2721
- if (shouldModify === void 0) {
2722
- console.log(ansis.yellow(i18n.t("common:cancelled")));
2723
- process.exit(0);
2724
- }
2725
2742
  if (!shouldModify) {
2726
2743
  console.log(ansis.gray(`\u2714 ${i18n.t("language:selectConfigLang")}: ${currentLanguageLabel}`));
2727
2744
  return savedConfig.templateLang;
@@ -2733,16 +2750,10 @@ async function resolveTemplateLanguage(commandLineOption, savedConfig, skipPromp
2733
2750
  return savedConfig.preferredLang;
2734
2751
  }
2735
2752
  console.log(ansis.yellow(`${i18n.t("language:usingFallbackTemplate")}: ${LANG_LABELS[savedConfig.preferredLang]}`));
2736
- const { shouldModify } = await inquirer.prompt({
2737
- type: "confirm",
2738
- name: "shouldModify",
2753
+ const shouldModify = await promptBoolean({
2739
2754
  message: i18n.t("language:modifyTemplateLanguagePrompt"),
2740
- default: false
2755
+ defaultValue: false
2741
2756
  });
2742
- if (shouldModify === void 0) {
2743
- console.log(ansis.yellow(i18n.t("common:cancelled")));
2744
- process.exit(0);
2745
- }
2746
2757
  if (!shouldModify) {
2747
2758
  return savedConfig.preferredLang;
2748
2759
  }
@@ -2766,16 +2777,10 @@ async function resolveSystemPromptStyle(availablePrompts, commandLineOption, sav
2766
2777
  return currentStyleId;
2767
2778
  }
2768
2779
  console.log(ansis.blue(`${i18n.t("language:currentSystemPromptFound")}: ${currentStyle.name}`));
2769
- const { shouldModify } = await inquirer.prompt({
2770
- type: "confirm",
2771
- name: "shouldModify",
2780
+ const shouldModify = await promptBoolean({
2772
2781
  message: i18n.t("language:modifySystemPromptPrompt"),
2773
- default: false
2782
+ defaultValue: false
2774
2783
  });
2775
- if (shouldModify === void 0) {
2776
- console.log(ansis.yellow(i18n.t("common:cancelled")));
2777
- process.exit(0);
2778
- }
2779
2784
  if (!shouldModify) {
2780
2785
  console.log(ansis.gray(`\u2714 ${i18n.t("language:currentSystemPromptFound")}: ${currentStyle.name}`));
2781
2786
  return currentStyleId;
@@ -3002,24 +3007,57 @@ function getRootDir$1() {
3002
3007
  }
3003
3008
  return dirname(currentFilePath);
3004
3009
  }
3005
- async function executeCodexInstallation(isUpdate) {
3006
- const action = isUpdate ? "update" : "install";
3007
- if (isUpdate) {
3008
- console.log(ansis.cyan(i18n.t("codex:updatingCli")));
3009
- } else {
3010
- console.log(ansis.cyan(i18n.t("codex:installingCli")));
3010
+ async function detectCodexInstallMethod() {
3011
+ try {
3012
+ const brewResult = await x("brew", ["list", "--cask", "codex"], { throwOnError: false });
3013
+ if (brewResult.exitCode === 0) {
3014
+ return "homebrew";
3015
+ }
3016
+ } catch {
3011
3017
  }
3012
- const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@openai/codex"]);
3013
- if (usedSudo)
3014
- console.log(ansis.yellow(i18n.t("codex:usingSudo")));
3015
- const result = await x(command, args);
3016
- if (result.exitCode !== 0) {
3017
- throw new Error(`Failed to ${action} codex CLI: exit code ${result.exitCode}`);
3018
+ try {
3019
+ const npmResult = await x("npm", ["list", "-g", "@openai/codex"], { throwOnError: false });
3020
+ if (npmResult.exitCode === 0 && npmResult.stdout.includes("@openai/codex")) {
3021
+ return "npm";
3022
+ }
3023
+ } catch {
3018
3024
  }
3025
+ return "unknown";
3026
+ }
3027
+ async function executeCodexInstallation(isUpdate, skipMethodSelection = false) {
3019
3028
  if (isUpdate) {
3029
+ console.log(ansis.cyan(i18n.t("codex:updatingCli")));
3030
+ const installMethod = await detectCodexInstallMethod();
3031
+ if (installMethod === "homebrew") {
3032
+ console.log(ansis.gray(i18n.t("codex:detectedHomebrew")));
3033
+ const result = await x("brew", ["upgrade", "--cask", "codex"]);
3034
+ if (result.exitCode !== 0) {
3035
+ throw new Error(`Failed to update codex via Homebrew: exit code ${result.exitCode}`);
3036
+ }
3037
+ } else if (installMethod === "npm") {
3038
+ console.log(ansis.gray(i18n.t("codex:detectedNpm")));
3039
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@openai/codex@latest"]);
3040
+ if (usedSudo)
3041
+ console.log(ansis.yellow(i18n.t("codex:usingSudo")));
3042
+ const result = await x(command, args);
3043
+ if (result.exitCode !== 0) {
3044
+ throw new Error(`Failed to update codex CLI: exit code ${result.exitCode}`);
3045
+ }
3046
+ } else {
3047
+ console.log(ansis.yellow(i18n.t("codex:unknownInstallMethod")));
3048
+ console.log(ansis.gray(i18n.t("codex:fallingBackToNpm")));
3049
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@openai/codex@latest"]);
3050
+ if (usedSudo)
3051
+ console.log(ansis.yellow(i18n.t("codex:usingSudo")));
3052
+ const result = await x(command, args);
3053
+ if (result.exitCode !== 0) {
3054
+ throw new Error(`Failed to update codex CLI: exit code ${result.exitCode}`);
3055
+ }
3056
+ }
3020
3057
  console.log(ansis.green(i18n.t("codex:updateSuccess")));
3021
3058
  } else {
3022
- console.log(ansis.green(i18n.t("codex:installSuccess")));
3059
+ const { installCodex } = await Promise.resolve().then(function () { return installer; });
3060
+ await installCodex(skipMethodSelection);
3023
3061
  }
3024
3062
  }
3025
3063
  function getUninstallOptions() {
@@ -3143,14 +3181,23 @@ function parseCodexConfig(content) {
3143
3181
  }
3144
3182
  const mcpServices = [];
3145
3183
  if (tomlData.mcp_servers) {
3184
+ const KNOWN_MCP_FIELDS = /* @__PURE__ */ new Set(["command", "args", "env", "startup_timeout_ms"]);
3146
3185
  for (const [id, mcpData] of Object.entries(tomlData.mcp_servers)) {
3147
3186
  const mcp = mcpData;
3187
+ const extraFields = {};
3188
+ for (const [key, value] of Object.entries(mcp)) {
3189
+ if (!KNOWN_MCP_FIELDS.has(key)) {
3190
+ extraFields[key] = value;
3191
+ }
3192
+ }
3148
3193
  mcpServices.push({
3149
3194
  id,
3150
3195
  command: mcp.command || id,
3151
3196
  args: mcp.args || [],
3152
3197
  env: Object.keys(mcp.env || {}).length > 0 ? mcp.env : void 0,
3153
- startup_timeout_ms: mcp.startup_timeout_ms
3198
+ startup_timeout_ms: mcp.startup_timeout_ms,
3199
+ // Only add extraFields if there are any extra fields
3200
+ extraFields: Object.keys(extraFields).length > 0 ? extraFields : void 0
3154
3201
  });
3155
3202
  }
3156
3203
  }
@@ -3249,6 +3296,70 @@ function parseCodexConfig(content) {
3249
3296
  };
3250
3297
  }
3251
3298
  }
3299
+ function formatInlineTableValue(value) {
3300
+ if (value === null || value === void 0) {
3301
+ return "";
3302
+ }
3303
+ if (typeof value === "string") {
3304
+ const normalized = normalizeTomlPath(value);
3305
+ return `'${normalized}'`;
3306
+ }
3307
+ if (typeof value === "number" || typeof value === "boolean") {
3308
+ return String(value);
3309
+ }
3310
+ if (Array.isArray(value)) {
3311
+ const items = value.map((item) => {
3312
+ if (typeof item === "string") {
3313
+ const normalized = normalizeTomlPath(item);
3314
+ return `'${normalized}'`;
3315
+ }
3316
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
3317
+ return formatInlineTable(item);
3318
+ }
3319
+ return String(item);
3320
+ }).join(", ");
3321
+ return `[${items}]`;
3322
+ }
3323
+ if (typeof value === "object") {
3324
+ return formatInlineTable(value);
3325
+ }
3326
+ return String(value);
3327
+ }
3328
+ function formatInlineTable(obj) {
3329
+ const entries = Object.entries(obj).filter(([_, v]) => v !== null && v !== void 0).map(([k, v]) => `${k} = ${formatInlineTableValue(v)}`).join(", ");
3330
+ return `{${entries}}`;
3331
+ }
3332
+ function formatTomlField(key, value) {
3333
+ if (value === null || value === void 0) {
3334
+ return "";
3335
+ }
3336
+ if (typeof value === "string") {
3337
+ const normalized = normalizeTomlPath(value);
3338
+ const escaped = normalized.replace(/"/g, '\\"');
3339
+ return `${key} = "${escaped}"`;
3340
+ }
3341
+ if (typeof value === "number" || typeof value === "boolean") {
3342
+ return `${key} = ${value}`;
3343
+ }
3344
+ if (Array.isArray(value)) {
3345
+ const items = value.map((item) => {
3346
+ if (typeof item === "string") {
3347
+ const normalized = normalizeTomlPath(item);
3348
+ const escaped = normalized.replace(/"/g, '\\"');
3349
+ return `"${escaped}"`;
3350
+ }
3351
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
3352
+ return formatInlineTable(item);
3353
+ }
3354
+ return String(item);
3355
+ }).join(", ");
3356
+ return `${key} = [${items}]`;
3357
+ }
3358
+ if (typeof value === "object") {
3359
+ return `${key} = ${formatInlineTable(value)}`;
3360
+ }
3361
+ return "";
3362
+ }
3252
3363
  function readCodexConfig() {
3253
3364
  if (!exists(CODEX_CONFIG_FILE))
3254
3365
  return null;
@@ -3317,16 +3428,25 @@ function renderCodexConfig(data) {
3317
3428
  lines.push("# --- MCP servers added by ZCF ---");
3318
3429
  for (const service of data.mcpServices) {
3319
3430
  lines.push(`[mcp_servers.${service.id}]`);
3320
- lines.push(`command = "${service.command}"`);
3431
+ const normalizedCommand = normalizeTomlPath(service.command);
3432
+ lines.push(`command = "${normalizedCommand}"`);
3321
3433
  const argsString = service.args.length > 0 ? service.args.map((arg) => `"${arg}"`).join(", ") : "";
3322
3434
  lines.push(`args = [${argsString}]`);
3323
3435
  if (service.env && Object.keys(service.env).length > 0) {
3324
- const envEntries = Object.entries(service.env).map(([key, value]) => `${key} = "${value}"`).join(", ");
3436
+ const envEntries = Object.entries(service.env).map(([key, value]) => `${key} = '${value}'`).join(", ");
3325
3437
  lines.push(`env = {${envEntries}}`);
3326
3438
  }
3327
3439
  if (service.startup_timeout_ms) {
3328
3440
  lines.push(`startup_timeout_ms = ${service.startup_timeout_ms}`);
3329
3441
  }
3442
+ if (service.extraFields) {
3443
+ for (const [key, value] of Object.entries(service.extraFields)) {
3444
+ const formatted = formatTomlField(key, value);
3445
+ if (formatted) {
3446
+ lines.push(formatted);
3447
+ }
3448
+ }
3449
+ }
3330
3450
  lines.push("");
3331
3451
  }
3332
3452
  if (lines[lines.length - 1] === "") {
@@ -3349,28 +3469,48 @@ function writeAuthFile(newEntries) {
3349
3469
  const merged = { ...existing, ...newEntries };
3350
3470
  writeJsonConfig(CODEX_AUTH_FILE, merged, { pretty: true });
3351
3471
  }
3352
- async function isCodexInstalled() {
3472
+ async function isCodexInstalled$1() {
3353
3473
  try {
3354
- const result = await x("npm", ["list", "-g", "--depth=0"]);
3355
- if (result.exitCode !== 0) {
3356
- return false;
3474
+ const npmResult = await x("npm", ["list", "-g", "--depth=0"]);
3475
+ if (npmResult.exitCode === 0 && npmResult.stdout.includes("@openai/codex@")) {
3476
+ return true;
3477
+ }
3478
+ } catch {
3479
+ }
3480
+ try {
3481
+ const brewResult = await x("brew", ["list", "--cask", "codex"], { throwOnError: false });
3482
+ if (brewResult.exitCode === 0) {
3483
+ return true;
3357
3484
  }
3358
- return result.stdout.includes("@openai/codex@");
3359
3485
  } catch {
3360
- return false;
3361
3486
  }
3487
+ return false;
3362
3488
  }
3363
3489
  async function getCodexVersion() {
3364
3490
  try {
3365
- const result = await x("npm", ["list", "-g", "--depth=0"]);
3366
- if (result.exitCode !== 0) {
3367
- return null;
3491
+ const npmResult = await x("npm", ["list", "-g", "--depth=0"]);
3492
+ if (npmResult.exitCode === 0) {
3493
+ const match = npmResult.stdout.match(/@openai\/codex@(\S+)/);
3494
+ if (match) {
3495
+ return match[1];
3496
+ }
3497
+ }
3498
+ } catch {
3499
+ }
3500
+ try {
3501
+ const brewResult = await x("brew", ["info", "--cask", "codex", "--json=v2"], { throwOnError: false });
3502
+ if (brewResult.exitCode === 0) {
3503
+ const info = JSON.parse(brewResult.stdout);
3504
+ if (info.casks && Array.isArray(info.casks) && info.casks.length > 0) {
3505
+ const cask = info.casks[0];
3506
+ if (cask.installed && typeof cask.installed === "string") {
3507
+ return cask.installed;
3508
+ }
3509
+ }
3368
3510
  }
3369
- const match = result.stdout.match(/@openai\/codex@(\S+)/);
3370
- return match ? match[1] : null;
3371
3511
  } catch {
3372
- return null;
3373
3512
  }
3513
+ return null;
3374
3514
  }
3375
3515
  async function checkCodexUpdate() {
3376
3516
  try {
@@ -3418,19 +3558,19 @@ async function checkCodexUpdate() {
3418
3558
  };
3419
3559
  }
3420
3560
  }
3421
- async function installCodexCli() {
3561
+ async function installCodexCli(skipMethodSelection = false) {
3422
3562
  ensureI18nInitialized();
3423
- if (await isCodexInstalled()) {
3563
+ if (await isCodexInstalled$1()) {
3424
3564
  const { needsUpdate } = await checkCodexUpdate();
3425
3565
  if (needsUpdate) {
3426
- await executeCodexInstallation(true);
3566
+ await executeCodexInstallation(true, skipMethodSelection);
3427
3567
  return;
3428
3568
  } else {
3429
3569
  console.log(ansis.yellow(i18n.t("codex:alreadyInstalled")));
3430
3570
  return;
3431
3571
  }
3432
3572
  }
3433
- await executeCodexInstallation(false);
3573
+ await executeCodexInstallation(false, skipMethodSelection);
3434
3574
  }
3435
3575
  async function runCodexWorkflowImportWithLanguageSelection(options) {
3436
3576
  ensureI18nInitialized();
@@ -3874,15 +4014,13 @@ async function configureCodexApi(options) {
3874
4014
  if (existingProvider || sessionProvider) {
3875
4015
  const sourceType = existingProvider ? "existing" : "session";
3876
4016
  const sourceProvider = existingProvider || sessionProvider;
3877
- const { shouldOverwrite } = await inquirer.prompt([{
3878
- type: "confirm",
3879
- name: "shouldOverwrite",
4017
+ const shouldOverwrite = await promptBoolean({
3880
4018
  message: i18n.t("codex:providerDuplicatePrompt", {
3881
4019
  name: sourceProvider.name,
3882
4020
  source: sourceType === "existing" ? i18n.t("codex:existingConfig") : i18n.t("codex:currentSession")
3883
4021
  }),
3884
- default: false
3885
- }]);
4022
+ defaultValue: false
4023
+ });
3886
4024
  if (!shouldOverwrite) {
3887
4025
  console.log(ansis.yellow(i18n.t("codex:providerDuplicateSkipped")));
3888
4026
  continue;
@@ -3908,12 +4046,10 @@ async function configureCodexApi(options) {
3908
4046
  providers.push(newProvider);
3909
4047
  currentSessionProviders.set(providerId, newProvider);
3910
4048
  authEntries[envKey] = answers.apiKey;
3911
- const { addAnother } = await inquirer.prompt([{
3912
- type: "confirm",
3913
- name: "addAnother",
4049
+ const addAnother = await promptBoolean({
3914
4050
  message: i18n.t("codex:addProviderPrompt"),
3915
- default: false
3916
- }]);
4051
+ defaultValue: false
4052
+ });
3917
4053
  addMore = addAnother;
3918
4054
  }
3919
4055
  if (providers.length === 0) {
@@ -3947,7 +4083,7 @@ async function configureCodexApi(options) {
3947
4083
  }
3948
4084
  async function runCodexFullInit(options) {
3949
4085
  ensureI18nInitialized();
3950
- await installCodexCli();
4086
+ await installCodexCli(options?.skipPrompt || false);
3951
4087
  const aiOutputLang = await runCodexWorkflowImportWithLanguageSelection(options);
3952
4088
  await configureCodexApi(options);
3953
4089
  await configureCodexMcp(options);
@@ -4012,11 +4148,9 @@ async function runCodexUpdate(force = false, skipPrompt = false) {
4012
4148
  console.log(ansis.cyan(format(i18n.t("codex:currentVersion"), { version: currentVersion || "" })));
4013
4149
  console.log(ansis.cyan(format(i18n.t("codex:latestVersion"), { version: latestVersion })));
4014
4150
  if (!skipPrompt) {
4015
- const { confirm } = await inquirer.prompt({
4016
- type: "confirm",
4017
- name: "confirm",
4151
+ const confirm = await promptBoolean({
4018
4152
  message: i18n.t("codex:confirmUpdate"),
4019
- default: true
4153
+ defaultValue: true
4020
4154
  });
4021
4155
  if (!confirm) {
4022
4156
  console.log(ansis.gray(i18n.t("codex:updateSkipped")));
@@ -4064,12 +4198,10 @@ async function runCodexUninstall() {
4064
4198
  }
4065
4199
  try {
4066
4200
  if (mode === "complete") {
4067
- const { confirm } = await inquirer.prompt([{
4068
- type: "confirm",
4069
- name: "confirm",
4201
+ const confirm = await promptBoolean({
4070
4202
  message: i18n.t("codex:uninstallPrompt"),
4071
- default: false
4072
- }]);
4203
+ defaultValue: false
4204
+ });
4073
4205
  if (!confirm) {
4074
4206
  handleUninstallCancellation();
4075
4207
  return;
@@ -4246,7 +4378,7 @@ const codex = {
4246
4378
  getBackupMessage: getBackupMessage,
4247
4379
  getCodexVersion: getCodexVersion,
4248
4380
  installCodexCli: installCodexCli,
4249
- isCodexInstalled: isCodexInstalled,
4381
+ isCodexInstalled: isCodexInstalled$1,
4250
4382
  listCodexProviders: listCodexProviders,
4251
4383
  parseCodexConfig: parseCodexConfig,
4252
4384
  readCodexConfig: readCodexConfig,
@@ -4528,11 +4660,9 @@ async function configureOutputStyle(preselectedStyles, preselectedDefault) {
4528
4660
  const availableStyles = getAvailableOutputStyles();
4529
4661
  if (hasLegacyPersonalityFiles() && !preselectedStyles) {
4530
4662
  console.log(ansis.yellow(`\u26A0\uFE0F ${i18n.t("configuration:legacyFilesDetected")}`));
4531
- const { cleanupLegacy } = await inquirer.prompt({
4532
- type: "confirm",
4533
- name: "cleanupLegacy",
4663
+ const cleanupLegacy = await promptBoolean({
4534
4664
  message: i18n.t("configuration:cleanupLegacyFiles"),
4535
- default: true
4665
+ defaultValue: true
4536
4666
  });
4537
4667
  if (cleanupLegacy) {
4538
4668
  cleanupLegacyPersonalityFiles();
@@ -4827,7 +4957,9 @@ ${ansis.cyan(i18n.t("common:complete"))}`);
4827
4957
  }
4828
4958
 
4829
4959
  function handleExitPromptError(error) {
4830
- if (error instanceof Error && error.name === "ExitPromptError") {
4960
+ const isExitError = error instanceof Error && (error.name === "ExitPromptError" || error.message?.includes("ExitPromptError") || error.message?.includes("User force closed the prompt"));
4961
+ if (isExitError) {
4962
+ ensureI18nInitialized();
4831
4963
  console.log(ansis.cyan(`
4832
4964
  ${i18n.t("common:goodbye")}
4833
4965
  `));
@@ -4847,13 +4979,17 @@ function handleGeneralError(error) {
4847
4979
  async function isClaudeCodeInstalled() {
4848
4980
  return await commandExists("claude");
4849
4981
  }
4850
- async function installClaudeCode() {
4982
+ async function installClaudeCode(skipMethodSelection = false) {
4851
4983
  ensureI18nInitialized();
4984
+ const codeType = "claude-code";
4852
4985
  const installed = await isClaudeCodeInstalled();
4853
4986
  if (installed) {
4854
4987
  console.log(ansis.green(`\u2714 ${i18n.t("installation:alreadyInstalled")}`));
4988
+ const version = await detectInstalledVersion(codeType);
4989
+ if (version) {
4990
+ console.log(ansis.gray(` ${i18n.t("installation:detectedVersion", { version })}`));
4991
+ }
4855
4992
  await updateClaudeCode();
4856
- await setInstallMethod("npm");
4857
4993
  return;
4858
4994
  }
4859
4995
  if (isTermux()) {
@@ -4872,31 +5008,100 @@ async function installClaudeCode() {
4872
5008
  }
4873
5009
  console.log(ansis.gray(i18n.t("installation:wslPathInfo", { path: `${homedir()}/.claude/` })));
4874
5010
  }
4875
- console.log(i18n.t("installation:installing"));
4876
- try {
4877
- const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
4878
- if (usedSudo) {
4879
- console.log(ansis.yellow(`\u2139 ${i18n.t("installation:usingSudo")}`));
4880
- }
4881
- await exec(command, args);
4882
- console.log(`\u2714 ${i18n.t("installation:installSuccess")}`);
4883
- await setInstallMethod("npm");
4884
- if (isTermux()) {
4885
- console.log(ansis.gray(`
5011
+ if (skipMethodSelection) {
5012
+ console.log(i18n.t("installation:installing"));
5013
+ try {
5014
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
5015
+ if (usedSudo) {
5016
+ console.log(ansis.yellow(`\u2139 ${i18n.t("installation:usingSudo")}`));
5017
+ }
5018
+ await exec(command, args);
5019
+ console.log(`\u2714 ${i18n.t("installation:installSuccess")}`);
5020
+ await setInstallMethod("npm");
5021
+ if (isTermux()) {
5022
+ console.log(ansis.gray(`
4886
5023
  Claude Code installed to: ${getTermuxPrefix()}/bin/claude`));
4887
- }
4888
- if (isWSL()) {
4889
- console.log(ansis.gray(`
5024
+ }
5025
+ if (isWSL()) {
5026
+ console.log(ansis.gray(`
4890
5027
  ${i18n.t("installation:wslInstallSuccess")}`));
4891
- }
4892
- } catch (error) {
4893
- console.error(`\u2716 ${i18n.t("installation:installFailed")}`);
4894
- if (isTermux()) {
4895
- console.error(ansis.yellow(`
5028
+ }
5029
+ } catch (error) {
5030
+ console.error(`\u2716 ${i18n.t("installation:installFailed")}`);
5031
+ if (isTermux()) {
5032
+ console.error(ansis.yellow(`
4896
5033
  ${i18n.t("installation:termuxInstallHint")}
4897
5034
  `));
5035
+ }
5036
+ throw error;
5037
+ }
5038
+ return;
5039
+ }
5040
+ const method = await selectInstallMethod(codeType);
5041
+ if (!method) {
5042
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
5043
+ return;
5044
+ }
5045
+ const success = await executeInstallMethod(method, codeType);
5046
+ if (!success) {
5047
+ const retrySuccess = await handleInstallFailure(codeType, [method]);
5048
+ if (!retrySuccess) {
5049
+ console.error(ansis.red(`\u2716 ${i18n.t("installation:installFailed")}`));
5050
+ throw new Error(i18n.t("installation:installFailed"));
5051
+ }
5052
+ }
5053
+ if (isTermux()) {
5054
+ console.log(ansis.gray(`
5055
+ Claude Code installed to: ${getTermuxPrefix()}/bin/claude`));
5056
+ }
5057
+ if (isWSL()) {
5058
+ console.log(ansis.gray(`
5059
+ ${i18n.t("installation:wslInstallSuccess")}`));
5060
+ }
5061
+ }
5062
+ async function isCodexInstalled() {
5063
+ return await commandExists("codex");
5064
+ }
5065
+ async function installCodex(skipMethodSelection = false) {
5066
+ ensureI18nInitialized();
5067
+ const codeType = "codex";
5068
+ const codeTypeName = i18n.t("common:codex");
5069
+ const installed = await isCodexInstalled();
5070
+ if (installed) {
5071
+ console.log(ansis.green(`\u2714 ${codeTypeName} ${i18n.t("installation:alreadyInstalled")}`));
5072
+ const version = await detectInstalledVersion(codeType);
5073
+ if (version) {
5074
+ console.log(ansis.gray(` ${i18n.t("installation:detectedVersion", { version })}`));
5075
+ }
5076
+ return;
5077
+ }
5078
+ if (skipMethodSelection) {
5079
+ console.log(i18n.t("installation:installingWith", { method: "npm", codeType: codeTypeName }));
5080
+ try {
5081
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@openai/codex"]);
5082
+ if (usedSudo) {
5083
+ console.log(ansis.yellow(`\u2139 ${i18n.t("installation:usingSudo")}`));
5084
+ }
5085
+ await exec(command, args);
5086
+ console.log(ansis.green(`\u2714 ${codeTypeName} ${i18n.t("installation:installSuccess")}`));
5087
+ } catch (error) {
5088
+ console.error(ansis.red(`\u2716 ${codeTypeName} ${i18n.t("installation:installFailed")}`));
5089
+ throw error;
5090
+ }
5091
+ return;
5092
+ }
5093
+ const method = await selectInstallMethod(codeType);
5094
+ if (!method) {
5095
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
5096
+ return;
5097
+ }
5098
+ const success = await executeInstallMethod(method, codeType);
5099
+ if (!success) {
5100
+ const retrySuccess = await handleInstallFailure(codeType, [method]);
5101
+ if (!retrySuccess) {
5102
+ console.error(ansis.red(`\u2716 ${codeTypeName} ${i18n.t("installation:installFailed")}`));
5103
+ throw new Error(i18n.t("installation:installFailed"));
4898
5104
  }
4899
- throw error;
4900
5105
  }
4901
5106
  }
4902
5107
  async function isLocalClaudeCodeInstalled() {
@@ -4930,28 +5135,298 @@ async function removeLocalClaudeCode() {
4930
5135
  throw new Error(`${i18n.t("installation:failedToRemoveLocalInstallation")}: ${error}`);
4931
5136
  }
4932
5137
  }
4933
- async function setInstallMethod(method = "npm") {
5138
+ async function getInstallMethodFromConfig(codeType) {
4934
5139
  try {
4935
- const { readMcpConfig, writeMcpConfig } = await Promise.resolve().then(function () { return claudeConfig; });
4936
- let config = readMcpConfig();
4937
- if (!config) {
4938
- config = { mcpServers: {} };
5140
+ if (codeType === "claude-code") {
5141
+ const { readMcpConfig } = await Promise.resolve().then(function () { return claudeConfig; });
5142
+ const config = readMcpConfig();
5143
+ return config?.installMethod || null;
4939
5144
  }
4940
- config.installMethod = method;
4941
- writeMcpConfig(config);
5145
+ } catch {
5146
+ }
5147
+ return null;
5148
+ }
5149
+ async function uninstallCodeTool(codeType) {
5150
+ ensureI18nInitialized();
5151
+ const codeTypeName = codeType === "claude-code" ? i18n.t("common:claudeCode") : i18n.t("common:codex");
5152
+ let method = await getInstallMethodFromConfig(codeType);
5153
+ if (!method) {
5154
+ if (codeType === "claude-code") {
5155
+ try {
5156
+ const result = await exec("brew", ["list", "--cask", "claude-code"]);
5157
+ if (result.exitCode === 0) {
5158
+ method = "homebrew";
5159
+ }
5160
+ } catch {
5161
+ }
5162
+ } else if (codeType === "codex") {
5163
+ try {
5164
+ const result = await exec("brew", ["list", "codex"]);
5165
+ if (result.exitCode === 0) {
5166
+ method = "homebrew";
5167
+ }
5168
+ } catch {
5169
+ }
5170
+ }
5171
+ if (!method) {
5172
+ method = "npm";
5173
+ }
5174
+ }
5175
+ if (method === "native") {
5176
+ const platform = getPlatform();
5177
+ if (platform === "macos" || platform === "linux") {
5178
+ try {
5179
+ const testResult = codeType === "claude-code" ? await exec("brew", ["list", "--cask", "claude-code"]) : await exec("brew", ["list", "codex"]);
5180
+ if (testResult.exitCode === 0) {
5181
+ method = "homebrew";
5182
+ }
5183
+ } catch {
5184
+ method = "manual";
5185
+ }
5186
+ } else {
5187
+ method = "manual";
5188
+ }
5189
+ }
5190
+ const spinner = ora(i18n.t("installation:uninstallingWith", { method, codeType: codeTypeName })).start();
5191
+ try {
5192
+ switch (method) {
5193
+ case "npm":
5194
+ case "npm-global": {
5195
+ const packageName = codeType === "claude-code" ? "@anthropic-ai/claude-code" : "@openai/codex";
5196
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["uninstall", "-g", packageName]);
5197
+ if (usedSudo) {
5198
+ spinner.info(i18n.t("installation:usingSudo"));
5199
+ spinner.start();
5200
+ }
5201
+ await exec(command, args);
5202
+ break;
5203
+ }
5204
+ case "homebrew": {
5205
+ if (codeType === "claude-code") {
5206
+ await exec("brew", ["uninstall", "--cask", "claude-code"]);
5207
+ } else {
5208
+ await exec("brew", ["uninstall", "codex"]);
5209
+ }
5210
+ break;
5211
+ }
5212
+ case "manual":
5213
+ default: {
5214
+ spinner.warn(i18n.t("installation:manualUninstallRequired", { codeType: codeTypeName }));
5215
+ const command = codeType === "claude-code" ? "claude" : "codex";
5216
+ try {
5217
+ const whichCmd = getPlatform() === "windows" ? "where" : "which";
5218
+ const result = await exec(whichCmd, [command]);
5219
+ if (result.stdout) {
5220
+ const binaryPath = result.stdout.trim().split("\n")[0];
5221
+ spinner.info(i18n.t("installation:binaryLocation", { path: binaryPath }));
5222
+ const platform = getPlatform();
5223
+ if (platform === "windows") {
5224
+ const quotedBinaryPath = `"${binaryPath}"`;
5225
+ await exec("cmd", ["/c", "del", "/f", "/q", quotedBinaryPath]);
5226
+ } else {
5227
+ const { command: rmCmd, args: rmArgs } = wrapCommandWithSudo("rm", ["-f", binaryPath]);
5228
+ if (rmCmd === "sudo") {
5229
+ spinner.info(i18n.t("installation:usingSudo"));
5230
+ spinner.start();
5231
+ }
5232
+ await exec(rmCmd, rmArgs);
5233
+ }
5234
+ }
5235
+ } catch {
5236
+ spinner.fail(i18n.t("installation:failedToLocateBinary", { command }));
5237
+ return false;
5238
+ }
5239
+ break;
5240
+ }
5241
+ }
5242
+ spinner.succeed(i18n.t("installation:uninstallSuccess", { method, codeType: codeTypeName }));
5243
+ return true;
5244
+ } catch (error) {
5245
+ spinner.fail(i18n.t("installation:uninstallFailed", { method, codeType: codeTypeName }));
5246
+ if (error instanceof Error) {
5247
+ console.error(ansis.gray(error.message));
5248
+ }
5249
+ return false;
5250
+ }
5251
+ }
5252
+ async function setInstallMethod(method, codeType = "claude-code") {
5253
+ try {
5254
+ if (codeType === "claude-code") {
5255
+ const { readMcpConfig, writeMcpConfig } = await Promise.resolve().then(function () { return claudeConfig; });
5256
+ let config = readMcpConfig();
5257
+ if (!config) {
5258
+ config = { mcpServers: {} };
5259
+ }
5260
+ config.installMethod = method === "npm" ? "npm-global" : method;
5261
+ writeMcpConfig(config);
5262
+ }
5263
+ } catch (error) {
5264
+ console.error("Failed to set installMethod:", error);
5265
+ }
5266
+ }
5267
+ async function detectInstalledVersion(codeType) {
5268
+ try {
5269
+ const command = codeType === "claude-code" ? "claude" : "codex";
5270
+ const result = await exec(command, ["--version"]);
5271
+ if (result.exitCode === 0 && result.stdout) {
5272
+ const versionMatch = result.stdout.match(/(\d+\.\d+\.\d+)/);
5273
+ return versionMatch ? versionMatch[1] : result.stdout.trim();
5274
+ }
5275
+ } catch {
5276
+ }
5277
+ return null;
5278
+ }
5279
+ function getInstallMethodOptions(codeType, recommendedMethods) {
5280
+ const allMethods = ["npm", "homebrew", "curl", "powershell", "cmd"];
5281
+ const platform = getPlatform();
5282
+ const availableMethods = allMethods.filter((method) => {
5283
+ if (codeType === "codex" && !["npm", "homebrew"].includes(method)) {
5284
+ return false;
5285
+ }
5286
+ if (method === "homebrew")
5287
+ return platform === "macos" || platform === "linux";
5288
+ if (method === "curl")
5289
+ return platform !== "windows" || isWSL();
5290
+ if (method === "powershell" || method === "cmd")
5291
+ return platform === "windows";
5292
+ return true;
5293
+ });
5294
+ const topRecommended = recommendedMethods.length > 0 ? recommendedMethods[0] : null;
5295
+ return availableMethods.map((method) => {
5296
+ const isTopRecommended = method === topRecommended;
5297
+ const title = isTopRecommended ? `${i18n.t(`installation:installMethod${method.charAt(0).toUpperCase() + method.slice(1)}`)} ${ansis.green(`[${i18n.t("installation:recommendedMethod")}]`)}` : i18n.t(`installation:installMethod${method.charAt(0).toUpperCase() + method.slice(1)}`);
5298
+ return {
5299
+ title,
5300
+ value: method
5301
+ };
5302
+ });
5303
+ }
5304
+ async function selectInstallMethod(codeType, excludeMethods = []) {
5305
+ ensureI18nInitialized();
5306
+ const codeTypeName = codeType === "claude-code" ? i18n.t("common:claudeCode") : i18n.t("common:codex");
5307
+ const recommendedMethods = getRecommendedInstallMethods(codeType);
5308
+ const methodOptions = getInstallMethodOptions(codeType, recommendedMethods).filter((option) => !excludeMethods.includes(option.value));
5309
+ if (methodOptions.length === 0) {
5310
+ console.log(ansis.yellow(i18n.t("installation:noMoreMethods")));
5311
+ return null;
5312
+ }
5313
+ const response = await inquirer.prompt({
5314
+ type: "list",
5315
+ name: "method",
5316
+ message: i18n.t("installation:selectInstallMethod", { codeType: codeTypeName }),
5317
+ choices: methodOptions.map((opt) => ({
5318
+ name: opt.title,
5319
+ value: opt.value
5320
+ }))
5321
+ });
5322
+ return response.method || null;
5323
+ }
5324
+ async function executeInstallMethod(method, codeType) {
5325
+ ensureI18nInitialized();
5326
+ const codeTypeName = codeType === "claude-code" ? i18n.t("common:claudeCode") : i18n.t("common:codex");
5327
+ const spinner = ora(i18n.t("installation:installingWith", { method, codeType: codeTypeName })).start();
5328
+ try {
5329
+ switch (method) {
5330
+ case "npm": {
5331
+ const packageName = codeType === "claude-code" ? "@anthropic-ai/claude-code" : "@openai/codex";
5332
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", packageName]);
5333
+ if (usedSudo) {
5334
+ spinner.info(i18n.t("installation:usingSudo"));
5335
+ spinner.start();
5336
+ }
5337
+ await exec(command, args);
5338
+ await setInstallMethod("npm", codeType);
5339
+ break;
5340
+ }
5341
+ case "homebrew": {
5342
+ if (codeType === "claude-code") {
5343
+ await exec("brew", ["install", "--cask", "claude-code"]);
5344
+ } else {
5345
+ await exec("brew", ["install", "codex"]);
5346
+ }
5347
+ await setInstallMethod("homebrew", codeType);
5348
+ break;
5349
+ }
5350
+ case "curl": {
5351
+ if (codeType === "claude-code") {
5352
+ await exec("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"]);
5353
+ } else {
5354
+ spinner.stop();
5355
+ return await executeInstallMethod("npm", codeType);
5356
+ }
5357
+ await setInstallMethod("curl", codeType);
5358
+ break;
5359
+ }
5360
+ case "powershell": {
5361
+ if (codeType === "claude-code") {
5362
+ await exec("powershell", ["-Command", "irm https://claude.ai/install.ps1 | iex"]);
5363
+ } else {
5364
+ spinner.stop();
5365
+ return await executeInstallMethod("npm", codeType);
5366
+ }
5367
+ await setInstallMethod("powershell", codeType);
5368
+ break;
5369
+ }
5370
+ case "cmd": {
5371
+ if (codeType === "claude-code") {
5372
+ await exec("cmd", ["/c", "curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd"]);
5373
+ } else {
5374
+ spinner.stop();
5375
+ return await executeInstallMethod("npm", codeType);
5376
+ }
5377
+ await setInstallMethod("cmd", codeType);
5378
+ break;
5379
+ }
5380
+ default:
5381
+ throw new Error(`Unsupported install method: ${method}`);
5382
+ }
5383
+ spinner.succeed(i18n.t("installation:installMethodSuccess", { method }));
5384
+ return true;
4942
5385
  } catch (error) {
4943
- console.error("Failed to set installMethod in ~/.claude.json:", error);
5386
+ spinner.fail(i18n.t("installation:installMethodFailed", { method }));
5387
+ if (error instanceof Error) {
5388
+ console.error(ansis.gray(error.message));
5389
+ }
5390
+ return false;
5391
+ }
5392
+ }
5393
+ async function handleInstallFailure(codeType, failedMethods) {
5394
+ ensureI18nInitialized();
5395
+ const response = await inquirer.prompt({
5396
+ type: "confirm",
5397
+ name: "retry",
5398
+ message: i18n.t("installation:tryAnotherMethod"),
5399
+ default: true
5400
+ });
5401
+ if (!response.retry) {
5402
+ return false;
5403
+ }
5404
+ const newMethod = await selectInstallMethod(codeType, failedMethods);
5405
+ if (!newMethod) {
5406
+ return false;
4944
5407
  }
5408
+ const success = await executeInstallMethod(newMethod, codeType);
5409
+ if (success) {
5410
+ return true;
5411
+ }
5412
+ return await handleInstallFailure(codeType, [...failedMethods, newMethod]);
4945
5413
  }
4946
5414
 
4947
5415
  const installer = {
4948
5416
  __proto__: null,
5417
+ detectInstalledVersion: detectInstalledVersion,
5418
+ executeInstallMethod: executeInstallMethod,
4949
5419
  getInstallationStatus: getInstallationStatus,
5420
+ handleInstallFailure: handleInstallFailure,
4950
5421
  installClaudeCode: installClaudeCode,
5422
+ installCodex: installCodex,
4951
5423
  isClaudeCodeInstalled: isClaudeCodeInstalled,
5424
+ isCodexInstalled: isCodexInstalled,
4952
5425
  isLocalClaudeCodeInstalled: isLocalClaudeCodeInstalled,
4953
5426
  removeLocalClaudeCode: removeLocalClaudeCode,
4954
- setInstallMethod: setInstallMethod
5427
+ selectInstallMethod: selectInstallMethod,
5428
+ setInstallMethod: setInstallMethod,
5429
+ uninstallCodeTool: uninstallCodeTool
4955
5430
  };
4956
5431
 
4957
5432
  async function chooseInstallationMethod() {
@@ -5521,7 +5996,7 @@ async function init(options = {}) {
5521
5996
  if (installationStatus.hasLocal) {
5522
5997
  if (!installationStatus.hasGlobal) {
5523
5998
  console.log(ansis.blue(`${i18n.t("installation:installingGlobalClaudeCode")}...`));
5524
- await installClaudeCode();
5999
+ await installClaudeCode(true);
5525
6000
  console.log(ansis.green(`\u2714 ${i18n.t("installation:globalInstallationCompleted")}`));
5526
6001
  }
5527
6002
  if (installationStatus.hasGlobal && installationStatus.hasLocal) {
@@ -5535,20 +6010,14 @@ async function init(options = {}) {
5535
6010
  }
5536
6011
  } else {
5537
6012
  if (options.skipPrompt) {
5538
- await installClaudeCode();
6013
+ await installClaudeCode(true);
5539
6014
  } else {
5540
- const { shouldInstall } = await inquirer.prompt({
5541
- type: "confirm",
5542
- name: "shouldInstall",
6015
+ const shouldInstall = await promptBoolean({
5543
6016
  message: i18n.t("installation:installPrompt"),
5544
- default: true
6017
+ defaultValue: true
5545
6018
  });
5546
- if (shouldInstall === void 0) {
5547
- console.log(ansis.yellow(i18n.t("common:cancelled")));
5548
- process.exit(0);
5549
- }
5550
6019
  if (shouldInstall) {
5551
- await installClaudeCode();
6020
+ await installClaudeCode(false);
5552
6021
  } else {
5553
6022
  console.log(ansis.yellow(i18n.t("common:skip")));
5554
6023
  }
@@ -5610,18 +6079,21 @@ async function init(options = {}) {
5610
6079
  options.apiModel = options.apiModel || preset.claudeCode.defaultModels[0];
5611
6080
  options.apiFastModel = options.apiFastModel || preset.claudeCode.defaultModels[1];
5612
6081
  }
6082
+ await saveSingleConfigToToml(apiConfig, options.provider, options);
5613
6083
  } else if (options.apiType === "auth_token" && options.apiKey) {
5614
6084
  apiConfig = {
5615
6085
  authType: "auth_token",
5616
6086
  key: options.apiKey,
5617
6087
  url: options.apiUrl || API_DEFAULT_URL
5618
6088
  };
6089
+ await saveSingleConfigToToml(apiConfig, void 0, options);
5619
6090
  } else if (options.apiType === "api_key" && options.apiKey) {
5620
6091
  apiConfig = {
5621
6092
  authType: "api_key",
5622
6093
  key: options.apiKey,
5623
6094
  url: options.apiUrl || API_DEFAULT_URL
5624
6095
  };
6096
+ await saveSingleConfigToToml(apiConfig, void 0, options);
5625
6097
  } else if (options.apiType === "ccr_proxy") {
5626
6098
  const ccrStatus = await isCcrInstalled();
5627
6099
  if (!ccrStatus.hasCorrectPackage) {
@@ -5753,16 +6225,10 @@ async function init(options = {}) {
5753
6225
  if (options.skipPrompt) {
5754
6226
  shouldConfigureMcp = options.mcpServices !== false;
5755
6227
  } else {
5756
- const { shouldConfigureMcp: userChoice } = await inquirer.prompt({
5757
- type: "confirm",
5758
- name: "shouldConfigureMcp",
6228
+ const userChoice = await promptBoolean({
5759
6229
  message: i18n.t("mcp:configureMcp"),
5760
- default: true
6230
+ defaultValue: true
5761
6231
  });
5762
- if (userChoice === void 0) {
5763
- console.log(ansis.yellow(i18n.t("common:cancelled")));
5764
- process.exit(0);
5765
- }
5766
6232
  shouldConfigureMcp = userChoice;
5767
6233
  }
5768
6234
  if (shouldConfigureMcp) {
@@ -5838,16 +6304,10 @@ async function init(options = {}) {
5838
6304
  if (options.skipPrompt) {
5839
6305
  shouldInstallCometix = options.installCometixLine !== false;
5840
6306
  } else {
5841
- const { shouldInstallCometix: userChoice } = await inquirer.prompt({
5842
- type: "confirm",
5843
- name: "shouldInstallCometix",
6307
+ const userChoice = await promptBoolean({
5844
6308
  message: i18n.t("cometix:installCometixPrompt"),
5845
- default: true
6309
+ defaultValue: true
5846
6310
  });
5847
- if (userChoice === void 0) {
5848
- console.log(ansis.yellow(i18n.t("common:cancelled")));
5849
- process.exit(0);
5850
- }
5851
6311
  shouldInstallCometix = userChoice;
5852
6312
  }
5853
6313
  if (shouldInstallCometix) {
@@ -6003,6 +6463,55 @@ async function handleCodexConfigs(configs) {
6003
6463
  console.log(ansis.green(`\u2714 ${i18n.t("multi-config:defaultProviderSet", { name: defaultConfig.name })}`));
6004
6464
  }
6005
6465
  }
6466
+ async function saveSingleConfigToToml(apiConfig, provider, options) {
6467
+ try {
6468
+ const { ClaudeCodeConfigManager } = await import('./claude-code-config-manager.mjs');
6469
+ const profile = await convertSingleConfigToProfile(apiConfig, provider, options);
6470
+ const result = await ClaudeCodeConfigManager.addProfile(profile);
6471
+ if (result.success) {
6472
+ const savedProfile = result.addedProfile || ClaudeCodeConfigManager.getProfileByName(profile.name) || profile;
6473
+ if (savedProfile.id) {
6474
+ await ClaudeCodeConfigManager.switchProfile(savedProfile.id);
6475
+ await ClaudeCodeConfigManager.applyProfileSettings(savedProfile);
6476
+ }
6477
+ console.log(ansis.green(`\u2714 ${i18n.t("configuration:singleConfigSaved", { name: profile.name })}`));
6478
+ } else {
6479
+ console.warn(ansis.yellow(`${i18n.t("configuration:singleConfigSaveFailed")}: ${result.error}`));
6480
+ }
6481
+ } catch (error) {
6482
+ console.warn(ansis.yellow(`${i18n.t("configuration:singleConfigSaveFailed")}: ${error instanceof Error ? error.message : String(error)}`));
6483
+ }
6484
+ }
6485
+ async function convertSingleConfigToProfile(apiConfig, provider, options) {
6486
+ const { ClaudeCodeConfigManager } = await import('./claude-code-config-manager.mjs');
6487
+ const configName = provider && provider !== "custom" ? provider : "custom-config";
6488
+ let baseUrl = apiConfig.url || API_DEFAULT_URL;
6489
+ let primaryModel = options?.apiModel;
6490
+ let fastModel = options?.apiFastModel;
6491
+ let authType = apiConfig.authType;
6492
+ if (provider && provider !== "custom") {
6493
+ const { getProviderPreset } = await import('./api-providers.mjs');
6494
+ const preset = getProviderPreset(provider);
6495
+ if (preset?.claudeCode) {
6496
+ baseUrl = apiConfig.url || preset.claudeCode.baseUrl;
6497
+ authType = preset.claudeCode.authType;
6498
+ if (preset.claudeCode.defaultModels && preset.claudeCode.defaultModels.length >= 2) {
6499
+ primaryModel = primaryModel || preset.claudeCode.defaultModels[0];
6500
+ fastModel = fastModel || preset.claudeCode.defaultModels[1];
6501
+ }
6502
+ }
6503
+ }
6504
+ const profile = {
6505
+ name: configName,
6506
+ authType,
6507
+ apiKey: apiConfig.key,
6508
+ baseUrl,
6509
+ primaryModel,
6510
+ fastModel,
6511
+ id: ClaudeCodeConfigManager.generateProfileId(configName)
6512
+ };
6513
+ return profile;
6514
+ }
6006
6515
  async function convertToClaudeCodeProfile(config) {
6007
6516
  const { ClaudeCodeConfigManager } = await import('./claude-code-config-manager.mjs');
6008
6517
  let baseUrl = config.url;
@@ -6134,4 +6643,4 @@ async function openSettingsJson() {
6134
6643
  }
6135
6644
  }
6136
6645
 
6137
- 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, switchToOfficialLogin as a$, getExistingApiConfig as a0, applyAiLanguageDirective as a1, switchToOfficialLogin$1 as a2, promptApiConfigurationAction as a3, isClaudeCodeInstalled as a4, installClaudeCode as a5, isLocalClaudeCodeInstalled as a6, getInstallationStatus as a7, removeLocalClaudeCode as a8, setInstallMethod as a9, modifyApiConfigPartially as aA, formatApiKeyDisplay as aB, readCcrConfig as aC, configureCcrFeature as aD, handleExitPromptError as aE, handleGeneralError as aF, COMETIX_COMMAND_NAME as aG, COMETIX_COMMANDS as aH, installCometixLine as aI, checkAndUpdateTools as aJ, runCodexUpdate as aK, resolveCodeType as aL, writeJsonConfig as aM, displayBanner as aN, version as aO, resolveAiOutputLanguage as aP, updatePromptOnly as aQ, selectAndInstallWorkflows as aR, checkClaudeCodeVersionAndPrompt as aS, displayBannerWithInfo as aT, runCodexUninstall as aU, configureCodexMcp as aV, configureCodexApi as aW, runCodexWorkflowImportWithLanguageSelection as aX, runCodexFullInit as aY, switchCodexProvider as aZ, listCodexProviders as a_, ensureI18nInitialized as aa, i18n as ab, addNumbersToChoices as ac, validateApiKey as ad, ensureDir as ae, readDefaultTomlConfig as af, createDefaultTomlConfig as ag, exists as ah, readJsonConfig as ai, writeTomlConfig as aj, copyFile as ak, detectConfigManagementMode as al, readCodexConfig as am, backupCodexComplete as an, writeCodexConfig as ao, writeAuthFile as ap, updateZcfConfig as aq, changeLanguage as ar, readZcfConfig as as, configureOutputStyle as at, isWindows as au, selectMcpServices as av, getMcpServices as aw, isCcrInstalled as ax, installCcr as ay, setupCcrConfiguration as az, importRecommendedEnv as b, switchToProvider as b0, readZcfConfigAsync as b1, initI18n as b2, selectScriptLanguage as b3, index as b4, fsOperations as b5, jsonConfig as b6, claudeConfig as b7, config$1 as b8, config as b9, prompts as ba, codex as bb, 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 };
6646
+ 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, displayBannerWithInfo 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, readZcfConfig as aA, configureOutputStyle as aB, isWindows as aC, selectMcpServices as aD, getMcpServices as aE, isCcrInstalled as aF, installCcr as aG, setupCcrConfiguration as aH, modifyApiConfigPartially as aI, formatApiKeyDisplay as aJ, readCcrConfig as aK, configureCcrFeature as aL, handleExitPromptError as aM, handleGeneralError as aN, COMETIX_COMMAND_NAME as aO, COMETIX_COMMANDS as aP, installCometixLine as aQ, checkAndUpdateTools as aR, runCodexUpdate as aS, resolveCodeType as aT, writeJsonConfig as aU, displayBanner as aV, version as aW, resolveAiOutputLanguage as aX, updatePromptOnly as aY, selectAndInstallWorkflows as aZ, checkClaudeCodeVersionAndPrompt as a_, removeLocalClaudeCode as aa, uninstallCodeTool as ab, setInstallMethod as ac, detectInstalledVersion as ad, selectInstallMethod as ae, executeInstallMethod as af, handleInstallFailure as ag, ensureI18nInitialized as ah, i18n as ai, addNumbersToChoices as aj, validateApiKey as ak, promptBoolean as al, ensureDir as am, readDefaultTomlConfig as an, createDefaultTomlConfig as ao, exists as ap, readJsonConfig as aq, writeTomlConfig as ar, copyFile as as, detectConfigManagementMode as at, readCodexConfig as au, backupCodexComplete as av, writeCodexConfig as aw, writeAuthFile as ax, updateZcfConfig as ay, changeLanguage as az, importRecommendedEnv as b, runCodexUninstall as b0, configureCodexMcp as b1, configureCodexApi as b2, runCodexWorkflowImportWithLanguageSelection as b3, runCodexFullInit as b4, switchCodexProvider as b5, listCodexProviders as b6, switchToOfficialLogin as b7, switchToProvider as b8, readZcfConfigAsync as b9, initI18n as ba, selectScriptLanguage as bb, index as bc, fsOperations as bd, jsonConfig as be, claudeConfig as bf, config$1 as bg, config as bh, prompts as bi, codex as bj, installer as bk, 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 };