zcf 3.3.2 → 3.4.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.
@@ -1,4 +1,5 @@
1
- import { existsSync, readFileSync, mkdirSync, copyFileSync, writeFileSync, rmSync, rmdirSync, readdirSync, statSync, unlinkSync, renameSync } from 'node:fs';
1
+ import * as nodeFs from 'node:fs';
2
+ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, rmSync, rmdirSync, readdirSync, statSync, unlinkSync, renameSync } from 'node:fs';
2
3
  import process from 'node:process';
3
4
  import ansis from 'ansis';
4
5
  import inquirer from 'inquirer';
@@ -8,6 +9,7 @@ import { promisify } from 'node:util';
8
9
  import dayjs from 'dayjs';
9
10
  import { dirname, join } from 'pathe';
10
11
  import { fileURLToPath } from 'node:url';
12
+ import toggleModule from 'inquirer-toggle';
11
13
  import ora from 'ora';
12
14
  import semver from 'semver';
13
15
  import { stringify, parse } from 'smol-toml';
@@ -16,7 +18,7 @@ import { rm, mkdir, copyFile as copyFile$1 } from 'node:fs/promises';
16
18
  import i18next from 'i18next';
17
19
  import Backend from 'i18next-fs-backend';
18
20
 
19
- const version = "3.3.2";
21
+ const version = "3.4.0";
20
22
  const homepage = "https://github.com/UfoMiao/zcf";
21
23
 
22
24
  const i18n = i18next.createInstance();
@@ -523,7 +525,7 @@ function getPlatform() {
523
525
  return "linux";
524
526
  }
525
527
  function isTermux() {
526
- return !!(process.env.PREFIX && process.env.PREFIX.includes("com.termux")) || !!process.env.TERMUX_VERSION || existsSync("/data/data/com.termux/files/usr");
528
+ return !!(process.env.PREFIX && process.env.PREFIX.includes("com.termux")) || !!process.env.TERMUX_VERSION || nodeFs.existsSync("/data/data/com.termux/files/usr");
527
529
  }
528
530
  function getTermuxPrefix() {
529
531
  return process.env.PREFIX || "/data/data/com.termux/files/usr";
@@ -535,16 +537,16 @@ function isWSL() {
535
537
  if (process.env.WSL_DISTRO_NAME) {
536
538
  return true;
537
539
  }
538
- if (existsSync("/proc/version")) {
540
+ if (nodeFs.existsSync("/proc/version")) {
539
541
  try {
540
- const version = readFileSync("/proc/version", "utf8");
542
+ const version = nodeFs.readFileSync("/proc/version", "utf8");
541
543
  if (version.includes("Microsoft") || version.includes("WSL")) {
542
544
  return true;
543
545
  }
544
546
  } catch {
545
547
  }
546
548
  }
547
- if (existsSync("/mnt/c")) {
549
+ if (nodeFs.existsSync("/mnt/c")) {
548
550
  return true;
549
551
  }
550
552
  return false;
@@ -553,9 +555,9 @@ function getWSLDistro() {
553
555
  if (process.env.WSL_DISTRO_NAME) {
554
556
  return process.env.WSL_DISTRO_NAME;
555
557
  }
556
- if (existsSync("/etc/os-release")) {
558
+ if (nodeFs.existsSync("/etc/os-release")) {
557
559
  try {
558
- const osRelease = readFileSync("/etc/os-release", "utf8");
560
+ const osRelease = nodeFs.readFileSync("/etc/os-release", "utf8");
559
561
  const nameMatch = osRelease.match(/^PRETTY_NAME="(.+)"$/m);
560
562
  if (nameMatch) {
561
563
  return nameMatch[1];
@@ -570,9 +572,9 @@ function getWSLInfo() {
570
572
  return null;
571
573
  }
572
574
  let version = null;
573
- if (existsSync("/proc/version")) {
575
+ if (nodeFs.existsSync("/proc/version")) {
574
576
  try {
575
- version = readFileSync("/proc/version", "utf8").trim();
577
+ version = nodeFs.readFileSync("/proc/version", "utf8").trim();
576
578
  } catch {
577
579
  }
578
580
  }
@@ -588,6 +590,9 @@ function getMcpCommand(command = "npx") {
588
590
  }
589
591
  return [command];
590
592
  }
593
+ function normalizeTomlPath(str) {
594
+ return str.replace(/\\+/g, "/").replace(/\/+/g, "/");
595
+ }
591
596
  function getSystemRoot() {
592
597
  if (!isWindows())
593
598
  return null;
@@ -597,7 +602,72 @@ function getSystemRoot() {
597
602
  systemRoot = env.SYSTEMROOT;
598
603
  else if (Object.prototype.hasOwnProperty.call(env, "SystemRoot") && env.SystemRoot)
599
604
  systemRoot = env.SystemRoot;
600
- return systemRoot.replace(/\\+/g, "/").replace(/\/+/g, "/");
605
+ return normalizeTomlPath(systemRoot);
606
+ }
607
+ function shouldUseSudoForGlobalInstall() {
608
+ if (isTermux())
609
+ return false;
610
+ if (getPlatform() !== "linux")
611
+ return false;
612
+ const npmPrefix = getGlobalNpmPrefix();
613
+ if (npmPrefix) {
614
+ if (isPathInsideHome(npmPrefix) || canWriteToPath(npmPrefix))
615
+ return false;
616
+ }
617
+ const getuid = process.getuid;
618
+ if (typeof getuid !== "function")
619
+ return false;
620
+ try {
621
+ return getuid() !== 0;
622
+ } catch {
623
+ return false;
624
+ }
625
+ }
626
+ function wrapCommandWithSudo(command, args) {
627
+ if (shouldUseSudoForGlobalInstall()) {
628
+ return {
629
+ command: "sudo",
630
+ args: [command, ...args],
631
+ usedSudo: true
632
+ };
633
+ }
634
+ return {
635
+ command,
636
+ args,
637
+ usedSudo: false
638
+ };
639
+ }
640
+ const WRITE_CHECK_FLAG = 2;
641
+ function normalizePath(path) {
642
+ return normalizeTomlPath(path).replace(/\/+$/, "");
643
+ }
644
+ function isPathInsideHome(path) {
645
+ const home = process.env.HOME;
646
+ if (!home)
647
+ return false;
648
+ const normalizedHome = normalizePath(home);
649
+ const normalizedPath = normalizePath(path);
650
+ return normalizedPath === normalizedHome || normalizedPath.startsWith(`${normalizedHome}/`);
651
+ }
652
+ function canWriteToPath(path) {
653
+ try {
654
+ nodeFs.accessSync(path, WRITE_CHECK_FLAG);
655
+ return true;
656
+ } catch {
657
+ return false;
658
+ }
659
+ }
660
+ function getGlobalNpmPrefix() {
661
+ const env = process.env;
662
+ const envPrefix = env.npm_config_prefix || env.NPM_CONFIG_PREFIX || env.PREFIX;
663
+ if (envPrefix)
664
+ return envPrefix;
665
+ const execPath = process.execPath;
666
+ if (execPath) {
667
+ const binDir = dirname(execPath);
668
+ return dirname(binDir);
669
+ }
670
+ return null;
601
671
  }
602
672
  async function commandExists(command) {
603
673
  try {
@@ -616,7 +686,7 @@ async function commandExists(command) {
616
686
  `/data/data/com.termux/files/usr/bin/${command}`
617
687
  ];
618
688
  for (const path of possiblePaths) {
619
- if (existsSync(path)) {
689
+ if (nodeFs.existsSync(path)) {
620
690
  return true;
621
691
  }
622
692
  }
@@ -629,13 +699,37 @@ async function commandExists(command) {
629
699
  `${process.env.HOME}/.local/bin/${command}`
630
700
  ];
631
701
  for (const path of commonPaths) {
632
- if (existsSync(path)) {
702
+ if (nodeFs.existsSync(path)) {
633
703
  return true;
634
704
  }
635
705
  }
636
706
  }
637
707
  return false;
638
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
+ }
639
733
 
640
734
  class FileSystemError extends Error {
641
735
  constructor(message, path, cause) {
@@ -1419,6 +1513,16 @@ const config$1 = {
1419
1513
  updateDefaultModel: updateDefaultModel
1420
1514
  };
1421
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
+
1422
1526
  const PROVIDER_PRESETS_URL = "https://pub-0dc3e1677e894f07bbea11b17a29e032.r2.dev/providers.json";
1423
1527
  async function fetchProviderPresets() {
1424
1528
  try {
@@ -1782,13 +1886,10 @@ async function setupCcrConfiguration() {
1782
1886
  console.log(ansis.blue(`\u2139 ${i18n.t("ccr:existingCcrConfig")}`));
1783
1887
  let shouldBackupAndReconfigure = false;
1784
1888
  try {
1785
- const result = await inquirer.prompt({
1786
- type: "confirm",
1787
- name: "overwrite",
1889
+ shouldBackupAndReconfigure = await promptBoolean({
1788
1890
  message: i18n.t("ccr:overwriteCcrConfig"),
1789
- default: false
1891
+ defaultValue: false
1790
1892
  });
1791
- shouldBackupAndReconfigure = result.overwrite;
1792
1893
  } catch (error) {
1793
1894
  if (error.name === "ExitPromptError") {
1794
1895
  console.log(ansis.yellow(i18n.t("common:cancelled")));
@@ -1988,11 +2089,9 @@ async function updateCcr(force = false, skipPrompt = false) {
1988
2089
  console.log(ansis.cyan(format(i18n.t("updater:currentVersion"), { version: currentVersion || "" })));
1989
2090
  console.log(ansis.cyan(format(i18n.t("updater:latestVersion"), { version: latestVersion })));
1990
2091
  if (!skipPrompt) {
1991
- const { confirm } = await inquirer.prompt({
1992
- type: "confirm",
1993
- name: "confirm",
2092
+ const confirm = await promptBoolean({
1994
2093
  message: format(i18n.t("updater:confirmUpdate"), { tool: "CCR" }),
1995
- default: true
2094
+ defaultValue: true
1996
2095
  });
1997
2096
  if (!confirm) {
1998
2097
  console.log(ansis.gray(i18n.t("updater:updateSkipped")));
@@ -2038,11 +2137,9 @@ async function updateClaudeCode(force = false, skipPrompt = false) {
2038
2137
  console.log(ansis.cyan(format(i18n.t("updater:currentVersion"), { version: currentVersion || "" })));
2039
2138
  console.log(ansis.cyan(format(i18n.t("updater:latestVersion"), { version: latestVersion })));
2040
2139
  if (!skipPrompt) {
2041
- const { confirm } = await inquirer.prompt({
2042
- type: "confirm",
2043
- name: "confirm",
2140
+ const confirm = await promptBoolean({
2044
2141
  message: format(i18n.t("updater:confirmUpdate"), { tool: "Claude Code" }),
2045
- default: true
2142
+ defaultValue: true
2046
2143
  });
2047
2144
  if (!confirm) {
2048
2145
  console.log(ansis.gray(i18n.t("updater:updateSkipped")));
@@ -2088,11 +2185,9 @@ async function updateCometixLine(force = false, skipPrompt = false) {
2088
2185
  console.log(ansis.cyan(format(i18n.t("updater:currentVersion"), { version: currentVersion || "" })));
2089
2186
  console.log(ansis.cyan(format(i18n.t("updater:latestVersion"), { version: latestVersion })));
2090
2187
  if (!skipPrompt) {
2091
- const { confirm } = await inquirer.prompt({
2092
- type: "confirm",
2093
- name: "confirm",
2188
+ const confirm = await promptBoolean({
2094
2189
  message: format(i18n.t("updater:confirmUpdate"), { tool: "CCometixLine" }),
2095
- default: true
2190
+ defaultValue: true
2096
2191
  });
2097
2192
  if (!confirm) {
2098
2193
  console.log(ansis.gray(i18n.t("updater:updateSkipped")));
@@ -2219,7 +2314,12 @@ async function installCcr() {
2219
2314
  }
2220
2315
  console.log(ansis.cyan(`\u{1F4E6} ${i18n.t("ccr:installingCcr")}`));
2221
2316
  try {
2222
- await execAsync$1("npm install -g @musistudio/claude-code-router --force");
2317
+ const installArgs = ["install", "-g", "@musistudio/claude-code-router", "--force"];
2318
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", installArgs);
2319
+ if (usedSudo) {
2320
+ console.log(ansis.yellow(`\u2139 ${i18n.t("installation:usingSudo")}`));
2321
+ }
2322
+ await execAsync$1([command, ...args].join(" "));
2223
2323
  console.log(ansis.green(`\u2714 ${i18n.t("ccr:ccrInstallSuccess")}`));
2224
2324
  } catch (error) {
2225
2325
  if (error.message?.includes("EEXIST")) {
@@ -2586,16 +2686,10 @@ async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfi
2586
2686
  }
2587
2687
  const currentLanguageLabel = getAiOutputLanguageLabel(savedConfig.aiOutputLang) || savedConfig.aiOutputLang;
2588
2688
  console.log(ansis.blue(`${i18n.t("language:currentConfigFound")}: ${currentLanguageLabel}`));
2589
- const { shouldModify } = await inquirer.prompt({
2590
- type: "confirm",
2591
- name: "shouldModify",
2689
+ const shouldModify = await promptBoolean({
2592
2690
  message: i18n.t("language:modifyConfigPrompt"),
2593
- default: false
2691
+ defaultValue: false
2594
2692
  });
2595
- if (shouldModify === void 0) {
2596
- console.log(ansis.yellow(i18n.t("common:cancelled")));
2597
- process.exit(0);
2598
- }
2599
2693
  if (!shouldModify) {
2600
2694
  console.log(ansis.gray(`\u2714 ${i18n.t("language:aiOutputLangHint")}: ${currentLanguageLabel}`));
2601
2695
  return savedConfig.aiOutputLang;
@@ -2641,16 +2735,10 @@ async function resolveTemplateLanguage(commandLineOption, savedConfig, skipPromp
2641
2735
  }
2642
2736
  const currentLanguageLabel = LANG_LABELS[savedConfig.templateLang];
2643
2737
  console.log(ansis.blue(`${i18n.t("language:currentTemplateLanguageFound")}: ${currentLanguageLabel}`));
2644
- const { shouldModify } = await inquirer.prompt({
2645
- type: "confirm",
2646
- name: "shouldModify",
2738
+ const shouldModify = await promptBoolean({
2647
2739
  message: i18n.t("language:modifyTemplateLanguagePrompt"),
2648
- default: false
2740
+ defaultValue: false
2649
2741
  });
2650
- if (shouldModify === void 0) {
2651
- console.log(ansis.yellow(i18n.t("common:cancelled")));
2652
- process.exit(0);
2653
- }
2654
2742
  if (!shouldModify) {
2655
2743
  console.log(ansis.gray(`\u2714 ${i18n.t("language:selectConfigLang")}: ${currentLanguageLabel}`));
2656
2744
  return savedConfig.templateLang;
@@ -2662,16 +2750,10 @@ async function resolveTemplateLanguage(commandLineOption, savedConfig, skipPromp
2662
2750
  return savedConfig.preferredLang;
2663
2751
  }
2664
2752
  console.log(ansis.yellow(`${i18n.t("language:usingFallbackTemplate")}: ${LANG_LABELS[savedConfig.preferredLang]}`));
2665
- const { shouldModify } = await inquirer.prompt({
2666
- type: "confirm",
2667
- name: "shouldModify",
2753
+ const shouldModify = await promptBoolean({
2668
2754
  message: i18n.t("language:modifyTemplateLanguagePrompt"),
2669
- default: false
2755
+ defaultValue: false
2670
2756
  });
2671
- if (shouldModify === void 0) {
2672
- console.log(ansis.yellow(i18n.t("common:cancelled")));
2673
- process.exit(0);
2674
- }
2675
2757
  if (!shouldModify) {
2676
2758
  return savedConfig.preferredLang;
2677
2759
  }
@@ -2695,16 +2777,10 @@ async function resolveSystemPromptStyle(availablePrompts, commandLineOption, sav
2695
2777
  return currentStyleId;
2696
2778
  }
2697
2779
  console.log(ansis.blue(`${i18n.t("language:currentSystemPromptFound")}: ${currentStyle.name}`));
2698
- const { shouldModify } = await inquirer.prompt({
2699
- type: "confirm",
2700
- name: "shouldModify",
2780
+ const shouldModify = await promptBoolean({
2701
2781
  message: i18n.t("language:modifySystemPromptPrompt"),
2702
- default: false
2782
+ defaultValue: false
2703
2783
  });
2704
- if (shouldModify === void 0) {
2705
- console.log(ansis.yellow(i18n.t("common:cancelled")));
2706
- process.exit(0);
2707
- }
2708
2784
  if (!shouldModify) {
2709
2785
  console.log(ansis.gray(`\u2714 ${i18n.t("language:currentSystemPromptFound")}: ${currentStyle.name}`));
2710
2786
  return currentStyleId;
@@ -2931,21 +3007,20 @@ function getRootDir$1() {
2931
3007
  }
2932
3008
  return dirname(currentFilePath);
2933
3009
  }
2934
- async function executeCodexInstallation(isUpdate) {
2935
- const action = isUpdate ? "update" : "install";
3010
+ async function executeCodexInstallation(isUpdate, skipMethodSelection = false) {
2936
3011
  if (isUpdate) {
2937
3012
  console.log(ansis.cyan(i18n.t("codex:updatingCli")));
2938
- } else {
2939
- console.log(ansis.cyan(i18n.t("codex:installingCli")));
2940
- }
2941
- const result = await x("npm", ["install", "-g", "@openai/codex"]);
2942
- if (result.exitCode !== 0) {
2943
- throw new Error(`Failed to ${action} codex CLI: exit code ${result.exitCode}`);
2944
- }
2945
- if (isUpdate) {
3013
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@openai/codex@latest"]);
3014
+ if (usedSudo)
3015
+ console.log(ansis.yellow(i18n.t("codex:usingSudo")));
3016
+ const result = await x(command, args);
3017
+ if (result.exitCode !== 0) {
3018
+ throw new Error(`Failed to update codex CLI: exit code ${result.exitCode}`);
3019
+ }
2946
3020
  console.log(ansis.green(i18n.t("codex:updateSuccess")));
2947
3021
  } else {
2948
- console.log(ansis.green(i18n.t("codex:installSuccess")));
3022
+ const { installCodex } = await Promise.resolve().then(function () { return installer; });
3023
+ await installCodex(skipMethodSelection);
2949
3024
  }
2950
3025
  }
2951
3026
  function getUninstallOptions() {
@@ -3243,11 +3318,12 @@ function renderCodexConfig(data) {
3243
3318
  lines.push("# --- MCP servers added by ZCF ---");
3244
3319
  for (const service of data.mcpServices) {
3245
3320
  lines.push(`[mcp_servers.${service.id}]`);
3246
- lines.push(`command = "${service.command}"`);
3321
+ const normalizedCommand = normalizeTomlPath(service.command);
3322
+ lines.push(`command = "${normalizedCommand}"`);
3247
3323
  const argsString = service.args.length > 0 ? service.args.map((arg) => `"${arg}"`).join(", ") : "";
3248
3324
  lines.push(`args = [${argsString}]`);
3249
3325
  if (service.env && Object.keys(service.env).length > 0) {
3250
- const envEntries = Object.entries(service.env).map(([key, value]) => `${key} = "${value}"`).join(", ");
3326
+ const envEntries = Object.entries(service.env).map(([key, value]) => `${key} = '${value}'`).join(", ");
3251
3327
  lines.push(`env = {${envEntries}}`);
3252
3328
  }
3253
3329
  if (service.startup_timeout_ms) {
@@ -3275,7 +3351,7 @@ function writeAuthFile(newEntries) {
3275
3351
  const merged = { ...existing, ...newEntries };
3276
3352
  writeJsonConfig(CODEX_AUTH_FILE, merged, { pretty: true });
3277
3353
  }
3278
- async function isCodexInstalled() {
3354
+ async function isCodexInstalled$1() {
3279
3355
  try {
3280
3356
  const result = await x("npm", ["list", "-g", "--depth=0"]);
3281
3357
  if (result.exitCode !== 0) {
@@ -3344,19 +3420,19 @@ async function checkCodexUpdate() {
3344
3420
  };
3345
3421
  }
3346
3422
  }
3347
- async function installCodexCli() {
3423
+ async function installCodexCli(skipMethodSelection = false) {
3348
3424
  ensureI18nInitialized();
3349
- if (await isCodexInstalled()) {
3425
+ if (await isCodexInstalled$1()) {
3350
3426
  const { needsUpdate } = await checkCodexUpdate();
3351
3427
  if (needsUpdate) {
3352
- await executeCodexInstallation(true);
3428
+ await executeCodexInstallation(true, skipMethodSelection);
3353
3429
  return;
3354
3430
  } else {
3355
3431
  console.log(ansis.yellow(i18n.t("codex:alreadyInstalled")));
3356
3432
  return;
3357
3433
  }
3358
3434
  }
3359
- await executeCodexInstallation(false);
3435
+ await executeCodexInstallation(false, skipMethodSelection);
3360
3436
  }
3361
3437
  async function runCodexWorkflowImportWithLanguageSelection(options) {
3362
3438
  ensureI18nInitialized();
@@ -3800,15 +3876,13 @@ async function configureCodexApi(options) {
3800
3876
  if (existingProvider || sessionProvider) {
3801
3877
  const sourceType = existingProvider ? "existing" : "session";
3802
3878
  const sourceProvider = existingProvider || sessionProvider;
3803
- const { shouldOverwrite } = await inquirer.prompt([{
3804
- type: "confirm",
3805
- name: "shouldOverwrite",
3879
+ const shouldOverwrite = await promptBoolean({
3806
3880
  message: i18n.t("codex:providerDuplicatePrompt", {
3807
3881
  name: sourceProvider.name,
3808
3882
  source: sourceType === "existing" ? i18n.t("codex:existingConfig") : i18n.t("codex:currentSession")
3809
3883
  }),
3810
- default: false
3811
- }]);
3884
+ defaultValue: false
3885
+ });
3812
3886
  if (!shouldOverwrite) {
3813
3887
  console.log(ansis.yellow(i18n.t("codex:providerDuplicateSkipped")));
3814
3888
  continue;
@@ -3834,12 +3908,10 @@ async function configureCodexApi(options) {
3834
3908
  providers.push(newProvider);
3835
3909
  currentSessionProviders.set(providerId, newProvider);
3836
3910
  authEntries[envKey] = answers.apiKey;
3837
- const { addAnother } = await inquirer.prompt([{
3838
- type: "confirm",
3839
- name: "addAnother",
3911
+ const addAnother = await promptBoolean({
3840
3912
  message: i18n.t("codex:addProviderPrompt"),
3841
- default: false
3842
- }]);
3913
+ defaultValue: false
3914
+ });
3843
3915
  addMore = addAnother;
3844
3916
  }
3845
3917
  if (providers.length === 0) {
@@ -3873,7 +3945,7 @@ async function configureCodexApi(options) {
3873
3945
  }
3874
3946
  async function runCodexFullInit(options) {
3875
3947
  ensureI18nInitialized();
3876
- await installCodexCli();
3948
+ await installCodexCli(options?.skipPrompt || false);
3877
3949
  const aiOutputLang = await runCodexWorkflowImportWithLanguageSelection(options);
3878
3950
  await configureCodexApi(options);
3879
3951
  await configureCodexMcp(options);
@@ -3938,11 +4010,9 @@ async function runCodexUpdate(force = false, skipPrompt = false) {
3938
4010
  console.log(ansis.cyan(format(i18n.t("codex:currentVersion"), { version: currentVersion || "" })));
3939
4011
  console.log(ansis.cyan(format(i18n.t("codex:latestVersion"), { version: latestVersion })));
3940
4012
  if (!skipPrompt) {
3941
- const { confirm } = await inquirer.prompt({
3942
- type: "confirm",
3943
- name: "confirm",
4013
+ const confirm = await promptBoolean({
3944
4014
  message: i18n.t("codex:confirmUpdate"),
3945
- default: true
4015
+ defaultValue: true
3946
4016
  });
3947
4017
  if (!confirm) {
3948
4018
  console.log(ansis.gray(i18n.t("codex:updateSkipped")));
@@ -3970,7 +4040,10 @@ async function runCodexUpdate(force = false, skipPrompt = false) {
3970
4040
  async function runCodexUninstall() {
3971
4041
  ensureI18nInitialized();
3972
4042
  const { CodexUninstaller } = await import('./codex-uninstaller.mjs');
3973
- const uninstaller = new CodexUninstaller("en");
4043
+ const zcfConfig = readZcfConfig();
4044
+ const preferredLang = zcfConfig?.preferredLang;
4045
+ const uninstallLang = preferredLang && SUPPORTED_LANGS.includes(preferredLang) ? preferredLang : "en";
4046
+ const uninstaller = new CodexUninstaller(uninstallLang);
3974
4047
  const { mode } = await inquirer.prompt([{
3975
4048
  type: "list",
3976
4049
  name: "mode",
@@ -3987,12 +4060,10 @@ async function runCodexUninstall() {
3987
4060
  }
3988
4061
  try {
3989
4062
  if (mode === "complete") {
3990
- const { confirm } = await inquirer.prompt([{
3991
- type: "confirm",
3992
- name: "confirm",
4063
+ const confirm = await promptBoolean({
3993
4064
  message: i18n.t("codex:uninstallPrompt"),
3994
- default: false
3995
- }]);
4065
+ defaultValue: false
4066
+ });
3996
4067
  if (!confirm) {
3997
4068
  handleUninstallCancellation();
3998
4069
  return;
@@ -4169,7 +4240,7 @@ const codex = {
4169
4240
  getBackupMessage: getBackupMessage,
4170
4241
  getCodexVersion: getCodexVersion,
4171
4242
  installCodexCli: installCodexCli,
4172
- isCodexInstalled: isCodexInstalled,
4243
+ isCodexInstalled: isCodexInstalled$1,
4173
4244
  listCodexProviders: listCodexProviders,
4174
4245
  parseCodexConfig: parseCodexConfig,
4175
4246
  readCodexConfig: readCodexConfig,
@@ -4284,12 +4355,20 @@ async function isCometixLineInstalled() {
4284
4355
  }
4285
4356
  async function installCometixLine() {
4286
4357
  ensureI18nInitialized();
4358
+ const runInstallCommand = async () => {
4359
+ const installArgs = ["install", "-g", COMETIX_PACKAGE_NAME];
4360
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", installArgs);
4361
+ if (usedSudo) {
4362
+ console.log(ansis.yellow(`\u2139 ${i18n.t("installation:usingSudo")}`));
4363
+ }
4364
+ await execAsync([command, ...args].join(" "));
4365
+ };
4287
4366
  const isInstalled = await isCometixLineInstalled();
4288
4367
  if (isInstalled) {
4289
4368
  console.log(ansis.green(`\u2714 ${i18n.t("cometix:cometixAlreadyInstalled")}`));
4290
4369
  try {
4291
4370
  console.log(ansis.blue(`${i18n.t("cometix:installingOrUpdating")}`));
4292
- await execAsync(COMETIX_COMMANDS.INSTALL);
4371
+ await runInstallCommand();
4293
4372
  console.log(ansis.green(`\u2714 ${i18n.t("cometix:installUpdateSuccess")}`));
4294
4373
  } catch (error) {
4295
4374
  console.log(ansis.yellow(`\u26A0 ${i18n.t("cometix:installUpdateFailed")}: ${error}`));
@@ -4308,7 +4387,7 @@ async function installCometixLine() {
4308
4387
  }
4309
4388
  try {
4310
4389
  console.log(ansis.blue(`${i18n.t("cometix:installingCometix")}`));
4311
- await execAsync(COMETIX_COMMANDS.INSTALL);
4390
+ await runInstallCommand();
4312
4391
  console.log(ansis.green(`\u2714 ${i18n.t("cometix:cometixInstallSuccess")}`));
4313
4392
  try {
4314
4393
  addCCometixLineConfig();
@@ -4443,11 +4522,9 @@ async function configureOutputStyle(preselectedStyles, preselectedDefault) {
4443
4522
  const availableStyles = getAvailableOutputStyles();
4444
4523
  if (hasLegacyPersonalityFiles() && !preselectedStyles) {
4445
4524
  console.log(ansis.yellow(`\u26A0\uFE0F ${i18n.t("configuration:legacyFilesDetected")}`));
4446
- const { cleanupLegacy } = await inquirer.prompt({
4447
- type: "confirm",
4448
- name: "cleanupLegacy",
4525
+ const cleanupLegacy = await promptBoolean({
4449
4526
  message: i18n.t("configuration:cleanupLegacyFiles"),
4450
- default: true
4527
+ defaultValue: true
4451
4528
  });
4452
4529
  if (cleanupLegacy) {
4453
4530
  cleanupLegacyPersonalityFiles();
@@ -4742,7 +4819,9 @@ ${ansis.cyan(i18n.t("common:complete"))}`);
4742
4819
  }
4743
4820
 
4744
4821
  function handleExitPromptError(error) {
4745
- if (error instanceof Error && error.name === "ExitPromptError") {
4822
+ const isExitError = error instanceof Error && (error.name === "ExitPromptError" || error.message?.includes("ExitPromptError") || error.message?.includes("User force closed the prompt"));
4823
+ if (isExitError) {
4824
+ ensureI18nInitialized();
4746
4825
  console.log(ansis.cyan(`
4747
4826
  ${i18n.t("common:goodbye")}
4748
4827
  `));
@@ -4762,13 +4841,17 @@ function handleGeneralError(error) {
4762
4841
  async function isClaudeCodeInstalled() {
4763
4842
  return await commandExists("claude");
4764
4843
  }
4765
- async function installClaudeCode() {
4844
+ async function installClaudeCode(skipMethodSelection = false) {
4766
4845
  ensureI18nInitialized();
4846
+ const codeType = "claude-code";
4767
4847
  const installed = await isClaudeCodeInstalled();
4768
4848
  if (installed) {
4769
4849
  console.log(ansis.green(`\u2714 ${i18n.t("installation:alreadyInstalled")}`));
4850
+ const version = await detectInstalledVersion(codeType);
4851
+ if (version) {
4852
+ console.log(ansis.gray(` ${i18n.t("installation:detectedVersion", { version })}`));
4853
+ }
4770
4854
  await updateClaudeCode();
4771
- await setInstallMethod("npm");
4772
4855
  return;
4773
4856
  }
4774
4857
  if (isTermux()) {
@@ -4787,27 +4870,100 @@ async function installClaudeCode() {
4787
4870
  }
4788
4871
  console.log(ansis.gray(i18n.t("installation:wslPathInfo", { path: `${homedir()}/.claude/` })));
4789
4872
  }
4790
- console.log(i18n.t("installation:installing"));
4791
- try {
4792
- await exec("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
4793
- console.log(`\u2714 ${i18n.t("installation:installSuccess")}`);
4794
- await setInstallMethod("npm");
4795
- if (isTermux()) {
4796
- console.log(ansis.gray(`
4873
+ if (skipMethodSelection) {
4874
+ console.log(i18n.t("installation:installing"));
4875
+ try {
4876
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
4877
+ if (usedSudo) {
4878
+ console.log(ansis.yellow(`\u2139 ${i18n.t("installation:usingSudo")}`));
4879
+ }
4880
+ await exec(command, args);
4881
+ console.log(`\u2714 ${i18n.t("installation:installSuccess")}`);
4882
+ await setInstallMethod("npm");
4883
+ if (isTermux()) {
4884
+ console.log(ansis.gray(`
4797
4885
  Claude Code installed to: ${getTermuxPrefix()}/bin/claude`));
4798
- }
4799
- if (isWSL()) {
4800
- console.log(ansis.gray(`
4886
+ }
4887
+ if (isWSL()) {
4888
+ console.log(ansis.gray(`
4801
4889
  ${i18n.t("installation:wslInstallSuccess")}`));
4802
- }
4803
- } catch (error) {
4804
- console.error(`\u2716 ${i18n.t("installation:installFailed")}`);
4805
- if (isTermux()) {
4806
- console.error(ansis.yellow(`
4890
+ }
4891
+ } catch (error) {
4892
+ console.error(`\u2716 ${i18n.t("installation:installFailed")}`);
4893
+ if (isTermux()) {
4894
+ console.error(ansis.yellow(`
4807
4895
  ${i18n.t("installation:termuxInstallHint")}
4808
4896
  `));
4897
+ }
4898
+ throw error;
4899
+ }
4900
+ return;
4901
+ }
4902
+ const method = await selectInstallMethod(codeType);
4903
+ if (!method) {
4904
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
4905
+ return;
4906
+ }
4907
+ const success = await executeInstallMethod(method, codeType);
4908
+ if (!success) {
4909
+ const retrySuccess = await handleInstallFailure(codeType, [method]);
4910
+ if (!retrySuccess) {
4911
+ console.error(ansis.red(`\u2716 ${i18n.t("installation:installFailed")}`));
4912
+ throw new Error(i18n.t("installation:installFailed"));
4913
+ }
4914
+ }
4915
+ if (isTermux()) {
4916
+ console.log(ansis.gray(`
4917
+ Claude Code installed to: ${getTermuxPrefix()}/bin/claude`));
4918
+ }
4919
+ if (isWSL()) {
4920
+ console.log(ansis.gray(`
4921
+ ${i18n.t("installation:wslInstallSuccess")}`));
4922
+ }
4923
+ }
4924
+ async function isCodexInstalled() {
4925
+ return await commandExists("codex");
4926
+ }
4927
+ async function installCodex(skipMethodSelection = false) {
4928
+ ensureI18nInitialized();
4929
+ const codeType = "codex";
4930
+ const codeTypeName = i18n.t("common:codex");
4931
+ const installed = await isCodexInstalled();
4932
+ if (installed) {
4933
+ console.log(ansis.green(`\u2714 ${codeTypeName} ${i18n.t("installation:alreadyInstalled")}`));
4934
+ const version = await detectInstalledVersion(codeType);
4935
+ if (version) {
4936
+ console.log(ansis.gray(` ${i18n.t("installation:detectedVersion", { version })}`));
4937
+ }
4938
+ return;
4939
+ }
4940
+ if (skipMethodSelection) {
4941
+ console.log(i18n.t("installation:installingWith", { method: "npm", codeType: codeTypeName }));
4942
+ try {
4943
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@openai/codex"]);
4944
+ if (usedSudo) {
4945
+ console.log(ansis.yellow(`\u2139 ${i18n.t("installation:usingSudo")}`));
4946
+ }
4947
+ await exec(command, args);
4948
+ console.log(ansis.green(`\u2714 ${codeTypeName} ${i18n.t("installation:installSuccess")}`));
4949
+ } catch (error) {
4950
+ console.error(ansis.red(`\u2716 ${codeTypeName} ${i18n.t("installation:installFailed")}`));
4951
+ throw error;
4952
+ }
4953
+ return;
4954
+ }
4955
+ const method = await selectInstallMethod(codeType);
4956
+ if (!method) {
4957
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
4958
+ return;
4959
+ }
4960
+ const success = await executeInstallMethod(method, codeType);
4961
+ if (!success) {
4962
+ const retrySuccess = await handleInstallFailure(codeType, [method]);
4963
+ if (!retrySuccess) {
4964
+ console.error(ansis.red(`\u2716 ${codeTypeName} ${i18n.t("installation:installFailed")}`));
4965
+ throw new Error(i18n.t("installation:installFailed"));
4809
4966
  }
4810
- throw error;
4811
4967
  }
4812
4968
  }
4813
4969
  async function isLocalClaudeCodeInstalled() {
@@ -4841,28 +4997,297 @@ async function removeLocalClaudeCode() {
4841
4997
  throw new Error(`${i18n.t("installation:failedToRemoveLocalInstallation")}: ${error}`);
4842
4998
  }
4843
4999
  }
4844
- async function setInstallMethod(method = "npm") {
5000
+ async function getInstallMethodFromConfig(codeType) {
4845
5001
  try {
4846
- const { readMcpConfig, writeMcpConfig } = await Promise.resolve().then(function () { return claudeConfig; });
4847
- let config = readMcpConfig();
4848
- if (!config) {
4849
- config = { mcpServers: {} };
5002
+ if (codeType === "claude-code") {
5003
+ const { readMcpConfig } = await Promise.resolve().then(function () { return claudeConfig; });
5004
+ const config = readMcpConfig();
5005
+ return config?.installMethod || null;
4850
5006
  }
4851
- config.installMethod = method;
4852
- writeMcpConfig(config);
5007
+ } catch {
5008
+ }
5009
+ return null;
5010
+ }
5011
+ async function uninstallCodeTool(codeType) {
5012
+ ensureI18nInitialized();
5013
+ const codeTypeName = codeType === "claude-code" ? i18n.t("common:claudeCode") : i18n.t("common:codex");
5014
+ let method = await getInstallMethodFromConfig(codeType);
5015
+ if (!method) {
5016
+ if (codeType === "claude-code") {
5017
+ try {
5018
+ const result = await exec("brew", ["list", "--cask", "claude-code"]);
5019
+ if (result.exitCode === 0) {
5020
+ method = "homebrew";
5021
+ }
5022
+ } catch {
5023
+ }
5024
+ } else if (codeType === "codex") {
5025
+ try {
5026
+ const result = await exec("brew", ["list", "codex"]);
5027
+ if (result.exitCode === 0) {
5028
+ method = "homebrew";
5029
+ }
5030
+ } catch {
5031
+ }
5032
+ }
5033
+ if (!method) {
5034
+ method = "npm";
5035
+ }
5036
+ }
5037
+ if (method === "native") {
5038
+ const platform = getPlatform();
5039
+ if (platform === "macos" || platform === "linux") {
5040
+ try {
5041
+ const testResult = codeType === "claude-code" ? await exec("brew", ["list", "--cask", "claude-code"]) : await exec("brew", ["list", "codex"]);
5042
+ if (testResult.exitCode === 0) {
5043
+ method = "homebrew";
5044
+ }
5045
+ } catch {
5046
+ method = "manual";
5047
+ }
5048
+ } else {
5049
+ method = "manual";
5050
+ }
5051
+ }
5052
+ const spinner = ora(i18n.t("installation:uninstallingWith", { method, codeType: codeTypeName })).start();
5053
+ try {
5054
+ switch (method) {
5055
+ case "npm": {
5056
+ const packageName = codeType === "claude-code" ? "@anthropic-ai/claude-code" : "@openai/codex";
5057
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["uninstall", "-g", packageName]);
5058
+ if (usedSudo) {
5059
+ spinner.info(i18n.t("installation:usingSudo"));
5060
+ spinner.start();
5061
+ }
5062
+ await exec(command, args);
5063
+ break;
5064
+ }
5065
+ case "homebrew": {
5066
+ if (codeType === "claude-code") {
5067
+ await exec("brew", ["uninstall", "--cask", "claude-code"]);
5068
+ } else {
5069
+ await exec("brew", ["uninstall", "codex"]);
5070
+ }
5071
+ break;
5072
+ }
5073
+ case "manual":
5074
+ default: {
5075
+ spinner.warn(i18n.t("installation:manualUninstallRequired", { codeType: codeTypeName }));
5076
+ const command = codeType === "claude-code" ? "claude" : "codex";
5077
+ try {
5078
+ const whichCmd = getPlatform() === "windows" ? "where" : "which";
5079
+ const result = await exec(whichCmd, [command]);
5080
+ if (result.stdout) {
5081
+ const binaryPath = result.stdout.trim().split("\n")[0];
5082
+ spinner.info(i18n.t("installation:binaryLocation", { path: binaryPath }));
5083
+ const platform = getPlatform();
5084
+ if (platform === "windows") {
5085
+ const quotedBinaryPath = `"${binaryPath}"`;
5086
+ await exec("cmd", ["/c", "del", "/f", "/q", quotedBinaryPath]);
5087
+ } else {
5088
+ const { command: rmCmd, args: rmArgs } = wrapCommandWithSudo("rm", ["-f", binaryPath]);
5089
+ if (rmCmd === "sudo") {
5090
+ spinner.info(i18n.t("installation:usingSudo"));
5091
+ spinner.start();
5092
+ }
5093
+ await exec(rmCmd, rmArgs);
5094
+ }
5095
+ }
5096
+ } catch {
5097
+ spinner.fail(i18n.t("installation:failedToLocateBinary", { command }));
5098
+ return false;
5099
+ }
5100
+ break;
5101
+ }
5102
+ }
5103
+ spinner.succeed(i18n.t("installation:uninstallSuccess", { method, codeType: codeTypeName }));
5104
+ return true;
4853
5105
  } catch (error) {
4854
- console.error("Failed to set installMethod in ~/.claude.json:", error);
5106
+ spinner.fail(i18n.t("installation:uninstallFailed", { method, codeType: codeTypeName }));
5107
+ if (error instanceof Error) {
5108
+ console.error(ansis.gray(error.message));
5109
+ }
5110
+ return false;
5111
+ }
5112
+ }
5113
+ async function setInstallMethod(method = "npm", codeType = "claude-code") {
5114
+ try {
5115
+ if (codeType === "claude-code") {
5116
+ const { readMcpConfig, writeMcpConfig } = await Promise.resolve().then(function () { return claudeConfig; });
5117
+ let config = readMcpConfig();
5118
+ if (!config) {
5119
+ config = { mcpServers: {} };
5120
+ }
5121
+ config.installMethod = method === "npm" ? "npm" : "native";
5122
+ writeMcpConfig(config);
5123
+ }
5124
+ } catch (error) {
5125
+ console.error("Failed to set installMethod:", error);
4855
5126
  }
4856
5127
  }
5128
+ async function detectInstalledVersion(codeType) {
5129
+ try {
5130
+ const command = codeType === "claude-code" ? "claude" : "codex";
5131
+ const result = await exec(command, ["--version"]);
5132
+ if (result.exitCode === 0 && result.stdout) {
5133
+ const versionMatch = result.stdout.match(/(\d+\.\d+\.\d+)/);
5134
+ return versionMatch ? versionMatch[1] : result.stdout.trim();
5135
+ }
5136
+ } catch {
5137
+ }
5138
+ return null;
5139
+ }
5140
+ function getInstallMethodOptions(codeType, recommendedMethods) {
5141
+ const allMethods = ["npm", "homebrew", "curl", "powershell", "cmd"];
5142
+ const platform = getPlatform();
5143
+ const availableMethods = allMethods.filter((method) => {
5144
+ if (codeType === "codex" && !["npm", "homebrew"].includes(method)) {
5145
+ return false;
5146
+ }
5147
+ if (method === "homebrew")
5148
+ return platform === "macos" || platform === "linux";
5149
+ if (method === "curl")
5150
+ return platform !== "windows" || isWSL();
5151
+ if (method === "powershell" || method === "cmd")
5152
+ return platform === "windows";
5153
+ return true;
5154
+ });
5155
+ const topRecommended = recommendedMethods.length > 0 ? recommendedMethods[0] : null;
5156
+ return availableMethods.map((method) => {
5157
+ const isTopRecommended = method === topRecommended;
5158
+ 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)}`);
5159
+ return {
5160
+ title,
5161
+ value: method
5162
+ };
5163
+ });
5164
+ }
5165
+ async function selectInstallMethod(codeType, excludeMethods = []) {
5166
+ ensureI18nInitialized();
5167
+ const codeTypeName = codeType === "claude-code" ? i18n.t("common:claudeCode") : i18n.t("common:codex");
5168
+ const recommendedMethods = getRecommendedInstallMethods(codeType);
5169
+ const methodOptions = getInstallMethodOptions(codeType, recommendedMethods).filter((option) => !excludeMethods.includes(option.value));
5170
+ if (methodOptions.length === 0) {
5171
+ console.log(ansis.yellow(i18n.t("installation:noMoreMethods")));
5172
+ return null;
5173
+ }
5174
+ const response = await inquirer.prompt({
5175
+ type: "list",
5176
+ name: "method",
5177
+ message: i18n.t("installation:selectInstallMethod", { codeType: codeTypeName }),
5178
+ choices: methodOptions.map((opt) => ({
5179
+ name: opt.title,
5180
+ value: opt.value
5181
+ }))
5182
+ });
5183
+ return response.method || null;
5184
+ }
5185
+ async function executeInstallMethod(method, codeType) {
5186
+ ensureI18nInitialized();
5187
+ const codeTypeName = codeType === "claude-code" ? i18n.t("common:claudeCode") : i18n.t("common:codex");
5188
+ const spinner = ora(i18n.t("installation:installingWith", { method, codeType: codeTypeName })).start();
5189
+ try {
5190
+ switch (method) {
5191
+ case "npm": {
5192
+ const packageName = codeType === "claude-code" ? "@anthropic-ai/claude-code" : "@openai/codex";
5193
+ const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", packageName]);
5194
+ if (usedSudo) {
5195
+ spinner.info(i18n.t("installation:usingSudo"));
5196
+ spinner.start();
5197
+ }
5198
+ await exec(command, args);
5199
+ await setInstallMethod("npm", codeType);
5200
+ break;
5201
+ }
5202
+ case "homebrew": {
5203
+ if (codeType === "claude-code") {
5204
+ await exec("brew", ["install", "--cask", "claude-code"]);
5205
+ } else {
5206
+ await exec("brew", ["install", "codex"]);
5207
+ }
5208
+ await setInstallMethod("homebrew", codeType);
5209
+ break;
5210
+ }
5211
+ case "curl": {
5212
+ if (codeType === "claude-code") {
5213
+ await exec("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"]);
5214
+ } else {
5215
+ spinner.stop();
5216
+ return await executeInstallMethod("npm", codeType);
5217
+ }
5218
+ await setInstallMethod("curl", codeType);
5219
+ break;
5220
+ }
5221
+ case "powershell": {
5222
+ if (codeType === "claude-code") {
5223
+ await exec("powershell", ["-Command", "irm https://claude.ai/install.ps1 | iex"]);
5224
+ } else {
5225
+ spinner.stop();
5226
+ return await executeInstallMethod("npm", codeType);
5227
+ }
5228
+ await setInstallMethod("powershell", codeType);
5229
+ break;
5230
+ }
5231
+ case "cmd": {
5232
+ if (codeType === "claude-code") {
5233
+ await exec("cmd", ["/c", "curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd"]);
5234
+ } else {
5235
+ spinner.stop();
5236
+ return await executeInstallMethod("npm", codeType);
5237
+ }
5238
+ await setInstallMethod("cmd", codeType);
5239
+ break;
5240
+ }
5241
+ default:
5242
+ throw new Error(`Unsupported install method: ${method}`);
5243
+ }
5244
+ spinner.succeed(i18n.t("installation:installMethodSuccess", { method }));
5245
+ return true;
5246
+ } catch (error) {
5247
+ spinner.fail(i18n.t("installation:installMethodFailed", { method }));
5248
+ if (error instanceof Error) {
5249
+ console.error(ansis.gray(error.message));
5250
+ }
5251
+ return false;
5252
+ }
5253
+ }
5254
+ async function handleInstallFailure(codeType, failedMethods) {
5255
+ ensureI18nInitialized();
5256
+ const response = await inquirer.prompt({
5257
+ type: "confirm",
5258
+ name: "retry",
5259
+ message: i18n.t("installation:tryAnotherMethod"),
5260
+ default: true
5261
+ });
5262
+ if (!response.retry) {
5263
+ return false;
5264
+ }
5265
+ const newMethod = await selectInstallMethod(codeType, failedMethods);
5266
+ if (!newMethod) {
5267
+ return false;
5268
+ }
5269
+ const success = await executeInstallMethod(newMethod, codeType);
5270
+ if (success) {
5271
+ return true;
5272
+ }
5273
+ return await handleInstallFailure(codeType, [...failedMethods, newMethod]);
5274
+ }
4857
5275
 
4858
5276
  const installer = {
4859
5277
  __proto__: null,
5278
+ detectInstalledVersion: detectInstalledVersion,
5279
+ executeInstallMethod: executeInstallMethod,
4860
5280
  getInstallationStatus: getInstallationStatus,
5281
+ handleInstallFailure: handleInstallFailure,
4861
5282
  installClaudeCode: installClaudeCode,
5283
+ installCodex: installCodex,
4862
5284
  isClaudeCodeInstalled: isClaudeCodeInstalled,
5285
+ isCodexInstalled: isCodexInstalled,
4863
5286
  isLocalClaudeCodeInstalled: isLocalClaudeCodeInstalled,
4864
5287
  removeLocalClaudeCode: removeLocalClaudeCode,
4865
- setInstallMethod: setInstallMethod
5288
+ selectInstallMethod: selectInstallMethod,
5289
+ setInstallMethod: setInstallMethod,
5290
+ uninstallCodeTool: uninstallCodeTool
4866
5291
  };
4867
5292
 
4868
5293
  async function chooseInstallationMethod() {
@@ -5432,7 +5857,7 @@ async function init(options = {}) {
5432
5857
  if (installationStatus.hasLocal) {
5433
5858
  if (!installationStatus.hasGlobal) {
5434
5859
  console.log(ansis.blue(`${i18n.t("installation:installingGlobalClaudeCode")}...`));
5435
- await installClaudeCode();
5860
+ await installClaudeCode(true);
5436
5861
  console.log(ansis.green(`\u2714 ${i18n.t("installation:globalInstallationCompleted")}`));
5437
5862
  }
5438
5863
  if (installationStatus.hasGlobal && installationStatus.hasLocal) {
@@ -5446,20 +5871,14 @@ async function init(options = {}) {
5446
5871
  }
5447
5872
  } else {
5448
5873
  if (options.skipPrompt) {
5449
- await installClaudeCode();
5874
+ await installClaudeCode(true);
5450
5875
  } else {
5451
- const { shouldInstall } = await inquirer.prompt({
5452
- type: "confirm",
5453
- name: "shouldInstall",
5876
+ const shouldInstall = await promptBoolean({
5454
5877
  message: i18n.t("installation:installPrompt"),
5455
- default: true
5878
+ defaultValue: true
5456
5879
  });
5457
- if (shouldInstall === void 0) {
5458
- console.log(ansis.yellow(i18n.t("common:cancelled")));
5459
- process.exit(0);
5460
- }
5461
5880
  if (shouldInstall) {
5462
- await installClaudeCode();
5881
+ await installClaudeCode(false);
5463
5882
  } else {
5464
5883
  console.log(ansis.yellow(i18n.t("common:skip")));
5465
5884
  }
@@ -5664,16 +6083,10 @@ async function init(options = {}) {
5664
6083
  if (options.skipPrompt) {
5665
6084
  shouldConfigureMcp = options.mcpServices !== false;
5666
6085
  } else {
5667
- const { shouldConfigureMcp: userChoice } = await inquirer.prompt({
5668
- type: "confirm",
5669
- name: "shouldConfigureMcp",
6086
+ const userChoice = await promptBoolean({
5670
6087
  message: i18n.t("mcp:configureMcp"),
5671
- default: true
6088
+ defaultValue: true
5672
6089
  });
5673
- if (userChoice === void 0) {
5674
- console.log(ansis.yellow(i18n.t("common:cancelled")));
5675
- process.exit(0);
5676
- }
5677
6090
  shouldConfigureMcp = userChoice;
5678
6091
  }
5679
6092
  if (shouldConfigureMcp) {
@@ -5749,16 +6162,10 @@ async function init(options = {}) {
5749
6162
  if (options.skipPrompt) {
5750
6163
  shouldInstallCometix = options.installCometixLine !== false;
5751
6164
  } else {
5752
- const { shouldInstallCometix: userChoice } = await inquirer.prompt({
5753
- type: "confirm",
5754
- name: "shouldInstallCometix",
6165
+ const userChoice = await promptBoolean({
5755
6166
  message: i18n.t("cometix:installCometixPrompt"),
5756
- default: true
6167
+ defaultValue: true
5757
6168
  });
5758
- if (userChoice === void 0) {
5759
- console.log(ansis.yellow(i18n.t("common:cancelled")));
5760
- process.exit(0);
5761
- }
5762
6169
  shouldInstallCometix = userChoice;
5763
6170
  }
5764
6171
  if (shouldInstallCometix) {
@@ -6045,4 +6452,4 @@ async function openSettingsJson() {
6045
6452
  }
6046
6453
  }
6047
6454
 
6048
- 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 };
6455
+ 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 };