zcf 3.4.1 → 3.4.3
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.
- package/dist/chunks/claude-code-config-manager.mjs +28 -18
- package/dist/chunks/claude-code-incremental-manager.mjs +37 -20
- package/dist/chunks/codex-config-switch.mjs +8 -8
- package/dist/chunks/codex-provider-manager.mjs +30 -21
- package/dist/chunks/codex-uninstaller.mjs +3 -3
- package/dist/chunks/commands.mjs +2 -2
- package/dist/chunks/features.mjs +32 -20
- package/dist/chunks/simple-config.mjs +1046 -107
- package/dist/cli.mjs +10 -8
- package/dist/i18n/locales/en/cli.json +3 -1
- package/dist/i18n/locales/en/codex.json +2 -1
- package/dist/i18n/locales/en/common.json +2 -1
- package/dist/i18n/locales/en/configuration.json +3 -1
- package/dist/i18n/locales/en/errors.json +1 -1
- package/dist/i18n/locales/en/installation.json +34 -1
- package/dist/i18n/locales/en/updater.json +1 -0
- package/dist/i18n/locales/zh-CN/cli.json +3 -1
- package/dist/i18n/locales/zh-CN/codex.json +2 -1
- package/dist/i18n/locales/zh-CN/common.json +2 -1
- package/dist/i18n/locales/zh-CN/configuration.json +3 -1
- package/dist/i18n/locales/zh-CN/errors.json +1 -1
- package/dist/i18n/locales/zh-CN/installation.json +34 -1
- package/dist/i18n/locales/zh-CN/updater.json +1 -0
- package/dist/index.d.mts +43 -5
- package/dist/index.d.ts +43 -5
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/templates/claude-code/en/workflow/git/commands/git-commit.md +50 -3
- package/templates/claude-code/zh-CN/workflow/git/commands/git-commit.md +50 -3
- package/templates/codex/en/workflow/git/prompts/git-commit.md +50 -3
- package/templates/codex/zh-CN/workflow/git/prompts/git-commit.md +50 -3
|
@@ -4,21 +4,21 @@ import process from 'node:process';
|
|
|
4
4
|
import ansis from 'ansis';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import { exec as exec$1 } from 'node:child_process';
|
|
7
|
-
import { homedir, platform } from 'node:os';
|
|
7
|
+
import { homedir, platform as platform$1 } from 'node:os';
|
|
8
8
|
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
12
|
import toggleModule from 'inquirer-toggle';
|
|
13
13
|
import ora from 'ora';
|
|
14
|
+
import { exec, x } from 'tinyexec';
|
|
14
15
|
import semver from 'semver';
|
|
15
16
|
import { stringify, parse } from 'smol-toml';
|
|
16
|
-
import { exec, x } from 'tinyexec';
|
|
17
17
|
import { rm, mkdir, copyFile as copyFile$1 } from 'node:fs/promises';
|
|
18
18
|
import i18next from 'i18next';
|
|
19
19
|
import Backend from 'i18next-fs-backend';
|
|
20
20
|
|
|
21
|
-
const version = "3.4.
|
|
21
|
+
const version = "3.4.3";
|
|
22
22
|
const homepage = "https://github.com/UfoMiao/zcf";
|
|
23
23
|
|
|
24
24
|
const i18n = i18next.createInstance();
|
|
@@ -517,7 +517,7 @@ function displayBannerWithInfo(subtitle) {
|
|
|
517
517
|
|
|
518
518
|
const WINDOWS_WRAPPED_COMMANDS = ["npx", "uvx", "uv"];
|
|
519
519
|
function getPlatform() {
|
|
520
|
-
const p = platform();
|
|
520
|
+
const p = platform$1();
|
|
521
521
|
if (p === "win32")
|
|
522
522
|
return "windows";
|
|
523
523
|
if (p === "darwin")
|
|
@@ -703,9 +703,105 @@ async function commandExists(command) {
|
|
|
703
703
|
return true;
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
|
+
if (getPlatform() === "macos") {
|
|
707
|
+
const homebrewPaths = await getHomebrewCommandPaths(command);
|
|
708
|
+
for (const path of homebrewPaths) {
|
|
709
|
+
if (nodeFs.existsSync(path)) {
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
706
714
|
}
|
|
707
715
|
return false;
|
|
708
716
|
}
|
|
717
|
+
async function getHomebrewCommandPaths(command) {
|
|
718
|
+
const paths = [];
|
|
719
|
+
const homebrewPrefixes = [
|
|
720
|
+
"/opt/homebrew",
|
|
721
|
+
// Apple Silicon (M1/M2)
|
|
722
|
+
"/usr/local"
|
|
723
|
+
// Intel Mac
|
|
724
|
+
];
|
|
725
|
+
for (const prefix of homebrewPrefixes) {
|
|
726
|
+
paths.push(`${prefix}/bin/${command}`);
|
|
727
|
+
}
|
|
728
|
+
for (const prefix of homebrewPrefixes) {
|
|
729
|
+
const cellarNodePath = `${prefix}/Cellar/node`;
|
|
730
|
+
if (nodeFs.existsSync(cellarNodePath)) {
|
|
731
|
+
try {
|
|
732
|
+
const versions = nodeFs.readdirSync(cellarNodePath);
|
|
733
|
+
for (const version of versions) {
|
|
734
|
+
const binPath = `${cellarNodePath}/${version}/bin/${command}`;
|
|
735
|
+
paths.push(binPath);
|
|
736
|
+
}
|
|
737
|
+
} catch {
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const caskNameMap = {
|
|
742
|
+
claude: "claude-code",
|
|
743
|
+
codex: "codex"
|
|
744
|
+
};
|
|
745
|
+
const caskName = caskNameMap[command];
|
|
746
|
+
if (caskName) {
|
|
747
|
+
for (const prefix of homebrewPrefixes) {
|
|
748
|
+
const caskroomPath = `${prefix}/Caskroom/${caskName}`;
|
|
749
|
+
if (nodeFs.existsSync(caskroomPath)) {
|
|
750
|
+
try {
|
|
751
|
+
const versions = nodeFs.readdirSync(caskroomPath).filter((v) => !v.startsWith("."));
|
|
752
|
+
for (const version of versions) {
|
|
753
|
+
const binPath = `${caskroomPath}/${version}/${command}`;
|
|
754
|
+
paths.push(binPath);
|
|
755
|
+
}
|
|
756
|
+
} catch {
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return paths;
|
|
762
|
+
}
|
|
763
|
+
async function findCommandPath(command) {
|
|
764
|
+
try {
|
|
765
|
+
const cmd = getPlatform() === "windows" ? "where" : "which";
|
|
766
|
+
const res = await exec(cmd, [command]);
|
|
767
|
+
if (res.exitCode === 0 && res.stdout) {
|
|
768
|
+
return res.stdout.trim().split("\n")[0];
|
|
769
|
+
}
|
|
770
|
+
} catch {
|
|
771
|
+
}
|
|
772
|
+
const commonPaths = [
|
|
773
|
+
`/usr/local/bin/${command}`,
|
|
774
|
+
`/usr/bin/${command}`,
|
|
775
|
+
`/bin/${command}`,
|
|
776
|
+
`${process.env.HOME}/.local/bin/${command}`
|
|
777
|
+
];
|
|
778
|
+
for (const path of commonPaths) {
|
|
779
|
+
if (nodeFs.existsSync(path)) {
|
|
780
|
+
return path;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (getPlatform() === "macos") {
|
|
784
|
+
const homebrewPaths = await getHomebrewCommandPaths(command);
|
|
785
|
+
for (const path of homebrewPaths) {
|
|
786
|
+
if (nodeFs.existsSync(path)) {
|
|
787
|
+
return path;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (isTermux()) {
|
|
792
|
+
const termuxPrefix = getTermuxPrefix();
|
|
793
|
+
const termuxPaths = [
|
|
794
|
+
`${termuxPrefix}/bin/${command}`,
|
|
795
|
+
`${termuxPrefix}/usr/bin/${command}`
|
|
796
|
+
];
|
|
797
|
+
for (const path of termuxPaths) {
|
|
798
|
+
if (nodeFs.existsSync(path)) {
|
|
799
|
+
return path;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
709
805
|
function getRecommendedInstallMethods(codeType) {
|
|
710
806
|
const platform2 = getPlatform();
|
|
711
807
|
const wsl = isWSL();
|
|
@@ -731,6 +827,26 @@ function getRecommendedInstallMethods(codeType) {
|
|
|
731
827
|
return ["npm"];
|
|
732
828
|
}
|
|
733
829
|
|
|
830
|
+
const platform = {
|
|
831
|
+
__proto__: null,
|
|
832
|
+
commandExists: commandExists,
|
|
833
|
+
findCommandPath: findCommandPath,
|
|
834
|
+
getHomebrewCommandPaths: getHomebrewCommandPaths,
|
|
835
|
+
getMcpCommand: getMcpCommand,
|
|
836
|
+
getPlatform: getPlatform,
|
|
837
|
+
getRecommendedInstallMethods: getRecommendedInstallMethods,
|
|
838
|
+
getSystemRoot: getSystemRoot,
|
|
839
|
+
getTermuxPrefix: getTermuxPrefix,
|
|
840
|
+
getWSLDistro: getWSLDistro,
|
|
841
|
+
getWSLInfo: getWSLInfo,
|
|
842
|
+
isTermux: isTermux,
|
|
843
|
+
isWSL: isWSL,
|
|
844
|
+
isWindows: isWindows,
|
|
845
|
+
normalizeTomlPath: normalizeTomlPath,
|
|
846
|
+
shouldUseSudoForGlobalInstall: shouldUseSudoForGlobalInstall,
|
|
847
|
+
wrapCommandWithSudo: wrapCommandWithSudo
|
|
848
|
+
};
|
|
849
|
+
|
|
734
850
|
class FileSystemError extends Error {
|
|
735
851
|
constructor(message, path, cause) {
|
|
736
852
|
super(message);
|
|
@@ -1193,6 +1309,20 @@ const claudeConfig = {
|
|
|
1193
1309
|
writeMcpConfig: writeMcpConfig
|
|
1194
1310
|
};
|
|
1195
1311
|
|
|
1312
|
+
const MODEL_ENV_KEYS = [
|
|
1313
|
+
"ANTHROPIC_MODEL",
|
|
1314
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
1315
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
1316
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
1317
|
+
// Deprecated but still cleaned to avoid stale values
|
|
1318
|
+
"ANTHROPIC_SMALL_FAST_MODEL"
|
|
1319
|
+
];
|
|
1320
|
+
function clearModelEnv(env) {
|
|
1321
|
+
for (const key of MODEL_ENV_KEYS) {
|
|
1322
|
+
delete env[key];
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1196
1326
|
function cleanupPermissions(templatePermissions, userPermissions) {
|
|
1197
1327
|
const templateSet = new Set(templatePermissions);
|
|
1198
1328
|
const cleanedPermissions = userPermissions.filter((permission) => {
|
|
@@ -1310,8 +1440,8 @@ function mergeConfigs(sourceFile, targetFile) {
|
|
|
1310
1440
|
const merged = deepMerge(target, source);
|
|
1311
1441
|
writeJsonConfig(targetFile, merged);
|
|
1312
1442
|
}
|
|
1313
|
-
function updateCustomModel(primaryModel,
|
|
1314
|
-
if (!primaryModel?.trim() && !
|
|
1443
|
+
function updateCustomModel(primaryModel, haikuModel, sonnetModel, opusModel) {
|
|
1444
|
+
if (!primaryModel?.trim() && !haikuModel?.trim() && !sonnetModel?.trim() && !opusModel?.trim()) {
|
|
1315
1445
|
return;
|
|
1316
1446
|
}
|
|
1317
1447
|
let settings = getDefaultSettings();
|
|
@@ -1321,12 +1451,16 @@ function updateCustomModel(primaryModel, fastModel) {
|
|
|
1321
1451
|
}
|
|
1322
1452
|
delete settings.model;
|
|
1323
1453
|
settings.env = settings.env || {};
|
|
1454
|
+
clearModelEnv(settings.env);
|
|
1324
1455
|
if (primaryModel?.trim()) {
|
|
1325
1456
|
settings.env.ANTHROPIC_MODEL = primaryModel.trim();
|
|
1326
1457
|
}
|
|
1327
|
-
if (
|
|
1328
|
-
settings.env.
|
|
1329
|
-
|
|
1458
|
+
if (haikuModel?.trim())
|
|
1459
|
+
settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = haikuModel.trim();
|
|
1460
|
+
if (sonnetModel?.trim())
|
|
1461
|
+
settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL = sonnetModel.trim();
|
|
1462
|
+
if (opusModel?.trim())
|
|
1463
|
+
settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = opusModel.trim();
|
|
1330
1464
|
writeJsonConfig(SETTINGS_FILE, settings);
|
|
1331
1465
|
}
|
|
1332
1466
|
function updateDefaultModel(model) {
|
|
@@ -1338,13 +1472,10 @@ function updateDefaultModel(model) {
|
|
|
1338
1472
|
if (!settings.env) {
|
|
1339
1473
|
settings.env = {};
|
|
1340
1474
|
}
|
|
1341
|
-
if (model !== "custom"
|
|
1342
|
-
|
|
1343
|
-
delete settings.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
1475
|
+
if (model !== "custom") {
|
|
1476
|
+
clearModelEnv(settings.env);
|
|
1344
1477
|
}
|
|
1345
|
-
if (model === "default") {
|
|
1346
|
-
delete settings.model;
|
|
1347
|
-
} else if (model === "custom") {
|
|
1478
|
+
if (model === "default" || model === "custom") {
|
|
1348
1479
|
delete settings.model;
|
|
1349
1480
|
} else {
|
|
1350
1481
|
settings.model = model;
|
|
@@ -1395,13 +1526,18 @@ function getExistingModelConfig() {
|
|
|
1395
1526
|
if (!settings) {
|
|
1396
1527
|
return null;
|
|
1397
1528
|
}
|
|
1398
|
-
|
|
1529
|
+
const hasModelEnv = MODEL_ENV_KEYS.some((key) => settings.env?.[key]);
|
|
1530
|
+
if (hasModelEnv) {
|
|
1399
1531
|
return "custom";
|
|
1400
1532
|
}
|
|
1401
1533
|
if (!settings.model) {
|
|
1402
1534
|
return "default";
|
|
1403
1535
|
}
|
|
1404
|
-
|
|
1536
|
+
const validModels = ["opus", "sonnet", "sonnet[1m]"];
|
|
1537
|
+
if (validModels.includes(settings.model)) {
|
|
1538
|
+
return settings.model;
|
|
1539
|
+
}
|
|
1540
|
+
return "default";
|
|
1405
1541
|
}
|
|
1406
1542
|
function getExistingApiConfig() {
|
|
1407
1543
|
const settings = readJsonConfig(SETTINGS_FILE);
|
|
@@ -1671,7 +1807,7 @@ function getFallbackPresets() {
|
|
|
1671
1807
|
];
|
|
1672
1808
|
}
|
|
1673
1809
|
|
|
1674
|
-
const execAsync$
|
|
1810
|
+
const execAsync$3 = promisify(exec$1);
|
|
1675
1811
|
const CCR_CONFIG_DIR = join(homedir(), ".claude-code-router");
|
|
1676
1812
|
const CCR_CONFIG_FILE = join(CCR_CONFIG_DIR, "config.json");
|
|
1677
1813
|
const CCR_BACKUP_DIR = CCR_CONFIG_DIR;
|
|
@@ -1837,10 +1973,10 @@ async function restartAndCheckCcrStatus() {
|
|
|
1837
1973
|
ensureI18nInitialized();
|
|
1838
1974
|
try {
|
|
1839
1975
|
console.log(ansis.cyan(`${i18n.t("ccr:restartingCcr")}`));
|
|
1840
|
-
await execAsync$
|
|
1976
|
+
await execAsync$3("ccr restart");
|
|
1841
1977
|
console.log(ansis.green(`\u2714 ${i18n.t("ccr:ccrRestartSuccess")}`));
|
|
1842
1978
|
console.log(ansis.cyan(`${i18n.t("ccr:checkingCcrStatus")}`));
|
|
1843
|
-
const { stdout } = await execAsync$
|
|
1979
|
+
const { stdout } = await execAsync$3("ccr status");
|
|
1844
1980
|
console.log(ansis.gray(stdout));
|
|
1845
1981
|
} catch (error) {
|
|
1846
1982
|
console.error(ansis.red(`${i18n.t("ccr:ccrRestartFailed")}:`), error.message || error);
|
|
@@ -1977,16 +2113,16 @@ const config = {
|
|
|
1977
2113
|
writeCcrConfig: writeCcrConfig
|
|
1978
2114
|
};
|
|
1979
2115
|
|
|
1980
|
-
const execAsync$
|
|
2116
|
+
const execAsync$2 = promisify(exec$1);
|
|
1981
2117
|
async function getInstalledVersion(command, maxRetries = 3) {
|
|
1982
2118
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1983
2119
|
try {
|
|
1984
2120
|
let stdout;
|
|
1985
2121
|
try {
|
|
1986
|
-
const result = await execAsync$
|
|
2122
|
+
const result = await execAsync$2(`${command} -v`);
|
|
1987
2123
|
stdout = result.stdout;
|
|
1988
2124
|
} catch {
|
|
1989
|
-
const result = await execAsync$
|
|
2125
|
+
const result = await execAsync$2(`${command} --version`);
|
|
1990
2126
|
stdout = result.stdout;
|
|
1991
2127
|
}
|
|
1992
2128
|
const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
|
|
@@ -2003,7 +2139,7 @@ async function getInstalledVersion(command, maxRetries = 3) {
|
|
|
2003
2139
|
async function getLatestVersion(packageName, maxRetries = 3) {
|
|
2004
2140
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
2005
2141
|
try {
|
|
2006
|
-
const { stdout } = await execAsync$
|
|
2142
|
+
const { stdout } = await execAsync$2(`npm view ${packageName} version`);
|
|
2007
2143
|
return stdout.trim();
|
|
2008
2144
|
} catch {
|
|
2009
2145
|
if (attempt === maxRetries) {
|
|
@@ -2014,6 +2150,327 @@ async function getLatestVersion(packageName, maxRetries = 3) {
|
|
|
2014
2150
|
}
|
|
2015
2151
|
return null;
|
|
2016
2152
|
}
|
|
2153
|
+
async function getClaudeCodeInstallationSource() {
|
|
2154
|
+
if (getPlatform() !== "macos") {
|
|
2155
|
+
const commandPath2 = await findCommandPath("claude");
|
|
2156
|
+
return {
|
|
2157
|
+
isHomebrew: false,
|
|
2158
|
+
commandPath: commandPath2,
|
|
2159
|
+
source: commandPath2 ? "other" : "not-found"
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
const commandPath = await findCommandPath("claude");
|
|
2163
|
+
if (!commandPath) {
|
|
2164
|
+
return { isHomebrew: false, commandPath: null, source: "not-found" };
|
|
2165
|
+
}
|
|
2166
|
+
const isFromCaskroom = commandPath.includes("/Caskroom/claude-code/");
|
|
2167
|
+
if (isFromCaskroom) {
|
|
2168
|
+
return { isHomebrew: true, commandPath, source: "homebrew-cask" };
|
|
2169
|
+
}
|
|
2170
|
+
try {
|
|
2171
|
+
const { stdout: realPath } = await execAsync$2(`readlink -f "${commandPath}" 2>/dev/null || realpath "${commandPath}" 2>/dev/null || echo "${commandPath}"`);
|
|
2172
|
+
const resolvedPath = realPath.trim();
|
|
2173
|
+
if (resolvedPath.includes("/Caskroom/claude-code/")) {
|
|
2174
|
+
return { isHomebrew: true, commandPath, source: "homebrew-cask" };
|
|
2175
|
+
}
|
|
2176
|
+
} catch {
|
|
2177
|
+
}
|
|
2178
|
+
if (commandPath.includes("/node_modules/") || commandPath.includes("/npm/") || commandPath.includes("/Cellar/node/")) {
|
|
2179
|
+
return { isHomebrew: false, commandPath, source: "npm" };
|
|
2180
|
+
}
|
|
2181
|
+
return { isHomebrew: false, commandPath, source: "other" };
|
|
2182
|
+
}
|
|
2183
|
+
async function detectAllClaudeCodeInstallations() {
|
|
2184
|
+
const installations = [];
|
|
2185
|
+
const checkedPaths = /* @__PURE__ */ new Set();
|
|
2186
|
+
const activeCommandPath = await findCommandPath("claude");
|
|
2187
|
+
let activeResolvedPath = null;
|
|
2188
|
+
if (activeCommandPath) {
|
|
2189
|
+
try {
|
|
2190
|
+
const { stdout } = await execAsync$2(`readlink -f "${activeCommandPath}" 2>/dev/null || realpath "${activeCommandPath}" 2>/dev/null || echo "${activeCommandPath}"`);
|
|
2191
|
+
activeResolvedPath = stdout.trim();
|
|
2192
|
+
} catch {
|
|
2193
|
+
activeResolvedPath = activeCommandPath;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
async function getVersionFromPath(path) {
|
|
2197
|
+
try {
|
|
2198
|
+
const { stdout } = await execAsync$2(`"${path}" -v 2>/dev/null || "${path}" --version 2>/dev/null`);
|
|
2199
|
+
const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
|
|
2200
|
+
return versionMatch ? versionMatch[1] : null;
|
|
2201
|
+
} catch {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
function isActivePath(path) {
|
|
2206
|
+
if (!activeResolvedPath)
|
|
2207
|
+
return false;
|
|
2208
|
+
return path === activeResolvedPath || path === activeCommandPath;
|
|
2209
|
+
}
|
|
2210
|
+
async function addInstallation(path, source) {
|
|
2211
|
+
let resolvedPath = path;
|
|
2212
|
+
try {
|
|
2213
|
+
const { stdout } = await execAsync$2(`readlink -f "${path}" 2>/dev/null || realpath "${path}" 2>/dev/null || echo "${path}"`);
|
|
2214
|
+
resolvedPath = stdout.trim();
|
|
2215
|
+
} catch {
|
|
2216
|
+
}
|
|
2217
|
+
if (checkedPaths.has(resolvedPath))
|
|
2218
|
+
return;
|
|
2219
|
+
checkedPaths.add(resolvedPath);
|
|
2220
|
+
if (!nodeFs.existsSync(path))
|
|
2221
|
+
return;
|
|
2222
|
+
const version = await getVersionFromPath(path);
|
|
2223
|
+
installations.push({
|
|
2224
|
+
source,
|
|
2225
|
+
path,
|
|
2226
|
+
version,
|
|
2227
|
+
isActive: isActivePath(path) || isActivePath(resolvedPath)
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
if (activeCommandPath && nodeFs.existsSync(activeCommandPath)) {
|
|
2231
|
+
let activeSource = "other";
|
|
2232
|
+
if (activeResolvedPath?.includes("/Caskroom/claude-code/")) {
|
|
2233
|
+
activeSource = "homebrew-cask";
|
|
2234
|
+
} else if (activeResolvedPath?.includes("/node_modules/") || activeResolvedPath?.includes("/npm/") || activeResolvedPath?.includes("/fnm_multishells/") || activeResolvedPath?.includes("/.nvm/") || activeResolvedPath?.includes("/Cellar/node/") || activeCommandPath.includes("/fnm_multishells/") || activeCommandPath.includes("/.nvm/")) {
|
|
2235
|
+
activeSource = "npm";
|
|
2236
|
+
}
|
|
2237
|
+
await addInstallation(activeCommandPath, activeSource);
|
|
2238
|
+
}
|
|
2239
|
+
if (getPlatform() === "macos") {
|
|
2240
|
+
const homebrewPaths = await getHomebrewCommandPaths("claude");
|
|
2241
|
+
for (const path of homebrewPaths) {
|
|
2242
|
+
if (path.includes("/Caskroom/claude-code/")) {
|
|
2243
|
+
await addInstallation(path, "homebrew-cask");
|
|
2244
|
+
} else if (path.includes("/Cellar/node/")) {
|
|
2245
|
+
await addInstallation(path, "npm-homebrew-node");
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
try {
|
|
2249
|
+
await execAsync$2("brew list --cask claude-code");
|
|
2250
|
+
const homebrewPrefixes = ["/opt/homebrew", "/usr/local"];
|
|
2251
|
+
for (const prefix of homebrewPrefixes) {
|
|
2252
|
+
const caskroomPath = `${prefix}/Caskroom/claude-code`;
|
|
2253
|
+
if (nodeFs.existsSync(caskroomPath)) {
|
|
2254
|
+
const versions = nodeFs.readdirSync(caskroomPath).filter((v) => !v.startsWith("."));
|
|
2255
|
+
for (const version of versions) {
|
|
2256
|
+
const claudePath = `${caskroomPath}/${version}/claude`;
|
|
2257
|
+
await addInstallation(claudePath, "homebrew-cask");
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
} catch {
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
const npmGlobalPaths = [
|
|
2265
|
+
"/usr/local/bin/claude",
|
|
2266
|
+
"/usr/bin/claude",
|
|
2267
|
+
`${process.env.HOME}/.npm-global/bin/claude`,
|
|
2268
|
+
`${process.env.HOME}/.local/bin/claude`
|
|
2269
|
+
];
|
|
2270
|
+
for (const path of npmGlobalPaths) {
|
|
2271
|
+
if (nodeFs.existsSync(path)) {
|
|
2272
|
+
let resolvedPath = path;
|
|
2273
|
+
try {
|
|
2274
|
+
const { stdout } = await execAsync$2(`readlink -f "${path}" 2>/dev/null || realpath "${path}" 2>/dev/null || echo "${path}"`);
|
|
2275
|
+
resolvedPath = stdout.trim();
|
|
2276
|
+
} catch {
|
|
2277
|
+
}
|
|
2278
|
+
if (resolvedPath.includes("/node_modules/") || resolvedPath.includes("/npm/")) {
|
|
2279
|
+
await addInstallation(path, "npm");
|
|
2280
|
+
} else if (resolvedPath.includes("/Caskroom/")) ; else {
|
|
2281
|
+
await addInstallation(path, "other");
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
if (getPlatform() === "macos") {
|
|
2286
|
+
const homebrewPrefixes = ["/opt/homebrew", "/usr/local"];
|
|
2287
|
+
for (const prefix of homebrewPrefixes) {
|
|
2288
|
+
const cellarNodePath = `${prefix}/Cellar/node`;
|
|
2289
|
+
if (nodeFs.existsSync(cellarNodePath)) {
|
|
2290
|
+
try {
|
|
2291
|
+
const versions = nodeFs.readdirSync(cellarNodePath);
|
|
2292
|
+
for (const version of versions) {
|
|
2293
|
+
const claudePath = `${cellarNodePath}/${version}/bin/claude`;
|
|
2294
|
+
await addInstallation(claudePath, "npm-homebrew-node");
|
|
2295
|
+
}
|
|
2296
|
+
} catch {
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
return installations;
|
|
2302
|
+
}
|
|
2303
|
+
async function checkDuplicateInstallations() {
|
|
2304
|
+
const installations = await detectAllClaudeCodeInstallations();
|
|
2305
|
+
const activeInstallation = installations.find((i) => i.isActive) || null;
|
|
2306
|
+
const inactiveInstallations = installations.filter((i) => !i.isActive);
|
|
2307
|
+
const homebrewInstallation = installations.find((i) => i.source === "homebrew-cask") || null;
|
|
2308
|
+
const npmInstallation = installations.find((i) => i.source === "npm" || i.source === "npm-homebrew-node") || null;
|
|
2309
|
+
const hasDuplicates = homebrewInstallation !== null && npmInstallation !== null;
|
|
2310
|
+
const recommendation = hasDuplicates ? "remove-npm" : "none";
|
|
2311
|
+
return {
|
|
2312
|
+
hasDuplicates,
|
|
2313
|
+
installations,
|
|
2314
|
+
activeInstallation,
|
|
2315
|
+
inactiveInstallations,
|
|
2316
|
+
homebrewInstallation,
|
|
2317
|
+
npmInstallation,
|
|
2318
|
+
recommendation
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
function getSourceDisplayName(source, i18n) {
|
|
2322
|
+
const sourceMap = {
|
|
2323
|
+
"homebrew-cask": i18n.t("installation:sourceHomebrewCask"),
|
|
2324
|
+
"npm": i18n.t("installation:sourceNpm"),
|
|
2325
|
+
"npm-homebrew-node": i18n.t("installation:sourceNpmHomebrewNode"),
|
|
2326
|
+
"curl": i18n.t("installation:sourceCurl"),
|
|
2327
|
+
"other": i18n.t("installation:sourceOther")
|
|
2328
|
+
};
|
|
2329
|
+
return sourceMap[source] || source;
|
|
2330
|
+
}
|
|
2331
|
+
async function performNpmRemovalAndActivateHomebrew(_npmInstallation, homebrewInstallation, tinyExec, i18n, ansis) {
|
|
2332
|
+
const ora = (await import('ora')).default;
|
|
2333
|
+
const spinner = ora(i18n.t("installation:removingDuplicateInstallation")).start();
|
|
2334
|
+
try {
|
|
2335
|
+
const { wrapCommandWithSudo } = await Promise.resolve().then(function () { return platform; });
|
|
2336
|
+
const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["uninstall", "-g", "@anthropic-ai/claude-code"]);
|
|
2337
|
+
if (usedSudo) {
|
|
2338
|
+
spinner.info(i18n.t("installation:usingSudo"));
|
|
2339
|
+
spinner.start();
|
|
2340
|
+
}
|
|
2341
|
+
await tinyExec(command, args);
|
|
2342
|
+
spinner.succeed(i18n.t("installation:duplicateRemoved"));
|
|
2343
|
+
if (homebrewInstallation && !homebrewInstallation.isActive) {
|
|
2344
|
+
console.log("");
|
|
2345
|
+
console.log(ansis.cyan(`\u{1F517} ${i18n.t("installation:activatingHomebrew")}`));
|
|
2346
|
+
const { createHomebrewSymlink } = await Promise.resolve().then(function () { return installer; });
|
|
2347
|
+
const symlinkResult = await createHomebrewSymlink("claude", homebrewInstallation.path);
|
|
2348
|
+
if (symlinkResult.success) {
|
|
2349
|
+
console.log(ansis.green(`\u2714 ${i18n.t("installation:symlinkCreated", { path: symlinkResult.symlinkPath || "/usr/local/bin/claude" })}`));
|
|
2350
|
+
} else {
|
|
2351
|
+
console.log(ansis.yellow(`\u26A0 ${i18n.t("installation:manualSymlinkHint")}`));
|
|
2352
|
+
if (symlinkResult.error) {
|
|
2353
|
+
console.log(ansis.gray(` ${symlinkResult.error}`));
|
|
2354
|
+
} else {
|
|
2355
|
+
const homebrewBin = nodeFs.existsSync("/opt/homebrew/bin") ? "/opt/homebrew/bin" : "/usr/local/bin";
|
|
2356
|
+
console.log(ansis.gray(` sudo ln -sf "${homebrewInstallation.path}" ${homebrewBin}/claude`));
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return { hadDuplicates: true, resolved: true, action: "removed-npm" };
|
|
2361
|
+
} catch (error) {
|
|
2362
|
+
spinner.fail(i18n.t("installation:duplicateRemovalFailed"));
|
|
2363
|
+
if (error instanceof Error) {
|
|
2364
|
+
console.error(ansis.gray(error.message));
|
|
2365
|
+
}
|
|
2366
|
+
return { hadDuplicates: true, resolved: false, action: "kept-both" };
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
async function handleDuplicateInstallations(skipPrompt = false) {
|
|
2370
|
+
const { ensureI18nInitialized, format, i18n } = await Promise.resolve().then(function () { return index; });
|
|
2371
|
+
const ansis = (await import('ansis')).default;
|
|
2372
|
+
ensureI18nInitialized();
|
|
2373
|
+
const duplicateInfo = await checkDuplicateInstallations();
|
|
2374
|
+
if (!duplicateInfo.hasDuplicates) {
|
|
2375
|
+
return { hadDuplicates: false, resolved: true, action: "no-duplicates" };
|
|
2376
|
+
}
|
|
2377
|
+
const { npmInstallation, homebrewInstallation } = duplicateInfo;
|
|
2378
|
+
console.log("");
|
|
2379
|
+
console.log(ansis.yellow.bold(i18n.t("installation:duplicateInstallationsDetected")));
|
|
2380
|
+
console.log(ansis.gray(i18n.t("installation:duplicateInstallationsWarning")));
|
|
2381
|
+
console.log("");
|
|
2382
|
+
if (homebrewInstallation) {
|
|
2383
|
+
const isActive = homebrewInstallation.isActive;
|
|
2384
|
+
const statusIcon = isActive ? "\u2705" : "\u26A0\uFE0F";
|
|
2385
|
+
const statusColor = isActive ? ansis.green : ansis.yellow;
|
|
2386
|
+
console.log(ansis.cyan.bold(`\u{1F37A} Homebrew Cask ${i18n.t("installation:recommendedMethod")}:`));
|
|
2387
|
+
console.log(ansis.white(` ${i18n.t("installation:installationSource")}: ${statusColor(getSourceDisplayName(homebrewInstallation.source, i18n))}`));
|
|
2388
|
+
console.log(ansis.white(` ${i18n.t("installation:installationPath")}: ${ansis.gray(homebrewInstallation.path)}`));
|
|
2389
|
+
if (homebrewInstallation.version) {
|
|
2390
|
+
console.log(ansis.white(` ${i18n.t("installation:installationVersion")}: ${ansis.cyan(homebrewInstallation.version)}`));
|
|
2391
|
+
}
|
|
2392
|
+
console.log(ansis.white(` ${statusIcon} ${isActive ? i18n.t("installation:currentActiveInstallation") : i18n.t("installation:inactiveInstallations")}`));
|
|
2393
|
+
console.log("");
|
|
2394
|
+
}
|
|
2395
|
+
if (npmInstallation) {
|
|
2396
|
+
const isActive = npmInstallation.isActive;
|
|
2397
|
+
console.log(ansis.yellow.bold(`\u{1F4E6} npm ${i18n.t("installation:notRecommended")}:`));
|
|
2398
|
+
console.log(ansis.white(` ${i18n.t("installation:installationSource")}: ${ansis.yellow(getSourceDisplayName(npmInstallation.source, i18n))}`));
|
|
2399
|
+
console.log(ansis.white(` ${i18n.t("installation:installationPath")}: ${ansis.gray(npmInstallation.path)}`));
|
|
2400
|
+
if (npmInstallation.version) {
|
|
2401
|
+
console.log(ansis.white(` ${i18n.t("installation:installationVersion")}: ${ansis.cyan(npmInstallation.version)}`));
|
|
2402
|
+
if (homebrewInstallation?.version && npmInstallation.version !== homebrewInstallation.version) {
|
|
2403
|
+
console.log(ansis.red(` ${format(i18n.t("installation:versionMismatchWarning"), {
|
|
2404
|
+
npmVersion: npmInstallation.version,
|
|
2405
|
+
homebrewVersion: homebrewInstallation.version
|
|
2406
|
+
})}`));
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
if (isActive) {
|
|
2410
|
+
console.log(ansis.white(` \u26A0\uFE0F ${i18n.t("installation:currentActiveInstallation")}`));
|
|
2411
|
+
}
|
|
2412
|
+
console.log("");
|
|
2413
|
+
}
|
|
2414
|
+
console.log(ansis.cyan(`\u{1F4A1} ${i18n.t("installation:recommendRemoveNpm")}`));
|
|
2415
|
+
console.log("");
|
|
2416
|
+
if (!npmInstallation) {
|
|
2417
|
+
return { hadDuplicates: true, resolved: false, action: "kept-both" };
|
|
2418
|
+
}
|
|
2419
|
+
const { exec: tinyExec } = await import('tinyexec');
|
|
2420
|
+
if (skipPrompt) {
|
|
2421
|
+
console.log(ansis.cyan(`\u{1F504} ${i18n.t("installation:autoRemovingNpm")}`));
|
|
2422
|
+
return await performNpmRemovalAndActivateHomebrew(
|
|
2423
|
+
npmInstallation,
|
|
2424
|
+
homebrewInstallation,
|
|
2425
|
+
tinyExec,
|
|
2426
|
+
i18n,
|
|
2427
|
+
ansis
|
|
2428
|
+
);
|
|
2429
|
+
}
|
|
2430
|
+
const inquirer = (await import('inquirer')).default;
|
|
2431
|
+
const sourceDisplayName = getSourceDisplayName(npmInstallation.source, i18n);
|
|
2432
|
+
const confirmMessage = format(i18n.t("installation:confirmRemoveDuplicate"), { source: sourceDisplayName });
|
|
2433
|
+
const { action } = await inquirer.prompt([
|
|
2434
|
+
{
|
|
2435
|
+
type: "list",
|
|
2436
|
+
name: "action",
|
|
2437
|
+
message: confirmMessage,
|
|
2438
|
+
choices: [
|
|
2439
|
+
{
|
|
2440
|
+
name: `\u2705 ${i18n.t("common:yes")} - ${i18n.t("installation:removingDuplicateInstallation")}`,
|
|
2441
|
+
value: "remove"
|
|
2442
|
+
},
|
|
2443
|
+
{
|
|
2444
|
+
name: `\u274C ${i18n.t("installation:keepBothInstallations")}`,
|
|
2445
|
+
value: "keep"
|
|
2446
|
+
}
|
|
2447
|
+
]
|
|
2448
|
+
}
|
|
2449
|
+
]);
|
|
2450
|
+
if (action === "keep") {
|
|
2451
|
+
console.log(ansis.gray(i18n.t("installation:duplicateWarningContinue")));
|
|
2452
|
+
return { hadDuplicates: true, resolved: false, action: "kept-both" };
|
|
2453
|
+
}
|
|
2454
|
+
return await performNpmRemovalAndActivateHomebrew(
|
|
2455
|
+
npmInstallation,
|
|
2456
|
+
homebrewInstallation,
|
|
2457
|
+
tinyExec,
|
|
2458
|
+
i18n,
|
|
2459
|
+
ansis
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
async function getHomebrewClaudeCodeVersion() {
|
|
2463
|
+
try {
|
|
2464
|
+
const { stdout } = await execAsync$2("brew info --cask claude-code --json=v2");
|
|
2465
|
+
const info = JSON.parse(stdout);
|
|
2466
|
+
if (info.casks && info.casks.length > 0) {
|
|
2467
|
+
return info.casks[0].version;
|
|
2468
|
+
}
|
|
2469
|
+
return null;
|
|
2470
|
+
} catch {
|
|
2471
|
+
return null;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2017
2474
|
function compareVersions(current, latest) {
|
|
2018
2475
|
if (!semver.valid(current) || !semver.valid(latest)) {
|
|
2019
2476
|
return -1;
|
|
@@ -2035,12 +2492,22 @@ async function checkCcrVersion() {
|
|
|
2035
2492
|
}
|
|
2036
2493
|
async function checkClaudeCodeVersion() {
|
|
2037
2494
|
const currentVersion = await getInstalledVersion("claude");
|
|
2038
|
-
const
|
|
2495
|
+
const installationInfo = await getClaudeCodeInstallationSource();
|
|
2496
|
+
const { isHomebrew, commandPath, source: installationSource } = installationInfo;
|
|
2497
|
+
let latestVersion;
|
|
2498
|
+
if (isHomebrew) {
|
|
2499
|
+
latestVersion = await getHomebrewClaudeCodeVersion();
|
|
2500
|
+
} else {
|
|
2501
|
+
latestVersion = await getLatestVersion("@anthropic-ai/claude-code");
|
|
2502
|
+
}
|
|
2039
2503
|
return {
|
|
2040
2504
|
installed: currentVersion !== null,
|
|
2041
2505
|
currentVersion,
|
|
2042
2506
|
latestVersion,
|
|
2043
|
-
needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
|
|
2507
|
+
needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false,
|
|
2508
|
+
isHomebrew,
|
|
2509
|
+
commandPath,
|
|
2510
|
+
installationSource
|
|
2044
2511
|
};
|
|
2045
2512
|
}
|
|
2046
2513
|
async function checkCometixLineVersion() {
|
|
@@ -2067,7 +2534,24 @@ async function checkClaudeCodeVersionAndPrompt(skipPrompt = false) {
|
|
|
2067
2534
|
}
|
|
2068
2535
|
}
|
|
2069
2536
|
|
|
2070
|
-
|
|
2537
|
+
async function execWithSudoIfNeeded(command, args) {
|
|
2538
|
+
const needsSudo = shouldUseSudoForGlobalInstall();
|
|
2539
|
+
if (needsSudo) {
|
|
2540
|
+
console.log(ansis.yellow(`
|
|
2541
|
+
${i18n.t("updater:usingSudo")}`));
|
|
2542
|
+
const result = await exec("sudo", [command, ...args]);
|
|
2543
|
+
if (result.exitCode !== 0) {
|
|
2544
|
+
throw new Error(result.stderr || `Command failed with exit code ${result.exitCode}`);
|
|
2545
|
+
}
|
|
2546
|
+
return { usedSudo: true };
|
|
2547
|
+
} else {
|
|
2548
|
+
const result = await exec(command, args);
|
|
2549
|
+
if (result.exitCode !== 0) {
|
|
2550
|
+
throw new Error(result.stderr || `Command failed with exit code ${result.exitCode}`);
|
|
2551
|
+
}
|
|
2552
|
+
return { usedSudo: false };
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2071
2555
|
async function updateCcr(force = false, skipPrompt = false) {
|
|
2072
2556
|
ensureI18nInitialized();
|
|
2073
2557
|
const spinner = ora(i18n.t("updater:checkingVersion")).start();
|
|
@@ -2102,7 +2586,7 @@ async function updateCcr(force = false, skipPrompt = false) {
|
|
|
2102
2586
|
}
|
|
2103
2587
|
const updateSpinner = ora(format(i18n.t("updater:updating"), { tool: "CCR" })).start();
|
|
2104
2588
|
try {
|
|
2105
|
-
await
|
|
2589
|
+
await execWithSudoIfNeeded("npm", ["update", "-g", "@musistudio/claude-code-router"]);
|
|
2106
2590
|
updateSpinner.succeed(format(i18n.t("updater:updateSuccess"), { tool: "CCR" }));
|
|
2107
2591
|
return true;
|
|
2108
2592
|
} catch (error) {
|
|
@@ -2120,7 +2604,7 @@ async function updateClaudeCode(force = false, skipPrompt = false) {
|
|
|
2120
2604
|
ensureI18nInitialized();
|
|
2121
2605
|
const spinner = ora(i18n.t("updater:checkingVersion")).start();
|
|
2122
2606
|
try {
|
|
2123
|
-
const { installed, currentVersion, latestVersion, needsUpdate } = await checkClaudeCodeVersion();
|
|
2607
|
+
const { installed, currentVersion, latestVersion, needsUpdate, isHomebrew } = await checkClaudeCodeVersion();
|
|
2124
2608
|
spinner.stop();
|
|
2125
2609
|
if (!installed) {
|
|
2126
2610
|
console.log(ansis.yellow(i18n.t("updater:claudeCodeNotInstalled")));
|
|
@@ -2148,9 +2632,17 @@ async function updateClaudeCode(force = false, skipPrompt = false) {
|
|
|
2148
2632
|
} else {
|
|
2149
2633
|
console.log(ansis.cyan(format(i18n.t("updater:autoUpdating"), { tool: "Claude Code" })));
|
|
2150
2634
|
}
|
|
2151
|
-
const
|
|
2635
|
+
const toolName = isHomebrew ? "Claude Code (Homebrew)" : "Claude Code";
|
|
2636
|
+
const updateSpinner = ora(format(i18n.t("updater:updating"), { tool: toolName })).start();
|
|
2152
2637
|
try {
|
|
2153
|
-
|
|
2638
|
+
if (isHomebrew) {
|
|
2639
|
+
const result = await exec("brew", ["upgrade", "--cask", "claude-code"]);
|
|
2640
|
+
if (result.exitCode !== 0) {
|
|
2641
|
+
throw new Error(result.stderr || `Command failed with exit code ${result.exitCode}`);
|
|
2642
|
+
}
|
|
2643
|
+
} else {
|
|
2644
|
+
await execWithSudoIfNeeded("claude", ["update"]);
|
|
2645
|
+
}
|
|
2154
2646
|
updateSpinner.succeed(format(i18n.t("updater:updateSuccess"), { tool: "Claude Code" }));
|
|
2155
2647
|
return true;
|
|
2156
2648
|
} catch (error) {
|
|
@@ -2198,7 +2690,7 @@ async function updateCometixLine(force = false, skipPrompt = false) {
|
|
|
2198
2690
|
}
|
|
2199
2691
|
const updateSpinner = ora(format(i18n.t("updater:updating"), { tool: "CCometixLine" })).start();
|
|
2200
2692
|
try {
|
|
2201
|
-
await
|
|
2693
|
+
await execWithSudoIfNeeded("npm", ["update", "-g", "@cometix/ccline"]);
|
|
2202
2694
|
updateSpinner.succeed(format(i18n.t("updater:updateSuccess"), { tool: "CCometixLine" }));
|
|
2203
2695
|
return true;
|
|
2204
2696
|
} catch (error) {
|
|
@@ -2217,6 +2709,15 @@ async function checkAndUpdateTools(skipPrompt = false) {
|
|
|
2217
2709
|
console.log(ansis.bold.cyan(`
|
|
2218
2710
|
\u{1F50D} ${i18n.t("updater:checkingTools")}
|
|
2219
2711
|
`));
|
|
2712
|
+
try {
|
|
2713
|
+
const duplicateResult = await handleDuplicateInstallations(skipPrompt);
|
|
2714
|
+
if (duplicateResult.hadDuplicates) {
|
|
2715
|
+
console.log();
|
|
2716
|
+
}
|
|
2717
|
+
} catch (error) {
|
|
2718
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2719
|
+
console.warn(ansis.yellow(`\u26A0 Duplicate installation check failed: ${errorMessage}`));
|
|
2720
|
+
}
|
|
2220
2721
|
const results = [];
|
|
2221
2722
|
try {
|
|
2222
2723
|
const success = await updateCcr(false, skipPrompt);
|
|
@@ -2260,6 +2761,7 @@ async function checkAndUpdateTools(skipPrompt = false) {
|
|
|
2260
2761
|
const autoUpdater = {
|
|
2261
2762
|
__proto__: null,
|
|
2262
2763
|
checkAndUpdateTools: checkAndUpdateTools,
|
|
2764
|
+
execWithSudoIfNeeded: execWithSudoIfNeeded,
|
|
2263
2765
|
updateCcr: updateCcr,
|
|
2264
2766
|
updateClaudeCode: updateClaudeCode,
|
|
2265
2767
|
updateCometixLine: updateCometixLine
|
|
@@ -2881,11 +3383,83 @@ async function configureCodexMcp(options) {
|
|
|
2881
3383
|
ensureI18nInitialized();
|
|
2882
3384
|
const { skipPrompt = false } = options ?? {};
|
|
2883
3385
|
const existingConfig = readCodexConfig();
|
|
2884
|
-
if (skipPrompt)
|
|
2885
|
-
return;
|
|
2886
3386
|
const backupPath = backupCodexComplete();
|
|
2887
3387
|
if (backupPath)
|
|
2888
3388
|
console.log(ansis.gray(getBackupMessage(backupPath)));
|
|
3389
|
+
if (skipPrompt) {
|
|
3390
|
+
const { runCodexWorkflowSelection } = await Promise.resolve().then(function () { return codex; });
|
|
3391
|
+
await runCodexWorkflowSelection({ skipPrompt: true, workflows: options?.workflows ?? [] });
|
|
3392
|
+
if (options?.mcpServices === false) {
|
|
3393
|
+
updateZcfConfig({ codeToolType: "codex" });
|
|
3394
|
+
console.log(ansis.green(i18n.t("codex:mcpConfigured")));
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
3397
|
+
const defaultServiceIds = Array.isArray(options?.mcpServices) ? options.mcpServices : MCP_SERVICE_CONFIGS.filter((service) => !service.requiresApiKey).map((service) => service.id);
|
|
3398
|
+
const baseProviders2 = existingConfig?.providers || [];
|
|
3399
|
+
const existingServices2 = existingConfig?.mcpServices || [];
|
|
3400
|
+
const selection2 = [];
|
|
3401
|
+
for (const id of defaultServiceIds) {
|
|
3402
|
+
const configInfo = MCP_SERVICE_CONFIGS.find((service) => service.id === id);
|
|
3403
|
+
if (!configInfo)
|
|
3404
|
+
continue;
|
|
3405
|
+
let command = configInfo.config.command || id;
|
|
3406
|
+
let args = (configInfo.config.args || []).map((arg) => String(arg));
|
|
3407
|
+
if (id === "serena") {
|
|
3408
|
+
const idx = args.indexOf("--context");
|
|
3409
|
+
if (idx >= 0 && idx + 1 < args.length)
|
|
3410
|
+
args[idx + 1] = "codex";
|
|
3411
|
+
else
|
|
3412
|
+
args.push("--context", "codex");
|
|
3413
|
+
}
|
|
3414
|
+
const serviceConfig = { id: id.toLowerCase(), command, args };
|
|
3415
|
+
applyCodexPlatformCommand(serviceConfig);
|
|
3416
|
+
command = serviceConfig.command;
|
|
3417
|
+
args = serviceConfig.args || [];
|
|
3418
|
+
const env = { ...configInfo.config.env || {} };
|
|
3419
|
+
if (isWindows()) {
|
|
3420
|
+
const systemRoot = getSystemRoot();
|
|
3421
|
+
if (systemRoot)
|
|
3422
|
+
env.SYSTEMROOT = systemRoot;
|
|
3423
|
+
}
|
|
3424
|
+
selection2.push({
|
|
3425
|
+
id: id.toLowerCase(),
|
|
3426
|
+
command,
|
|
3427
|
+
args,
|
|
3428
|
+
env: Object.keys(env).length > 0 ? env : void 0,
|
|
3429
|
+
startup_timeout_sec: 30
|
|
3430
|
+
});
|
|
3431
|
+
}
|
|
3432
|
+
const mergedMap2 = /* @__PURE__ */ new Map();
|
|
3433
|
+
for (const svc of existingServices2)
|
|
3434
|
+
mergedMap2.set(svc.id.toLowerCase(), { ...svc });
|
|
3435
|
+
for (const svc of selection2)
|
|
3436
|
+
mergedMap2.set(svc.id.toLowerCase(), { ...svc });
|
|
3437
|
+
const finalServices2 = Array.from(mergedMap2.values()).map((svc) => {
|
|
3438
|
+
if (isWindows()) {
|
|
3439
|
+
const systemRoot = getSystemRoot();
|
|
3440
|
+
if (systemRoot) {
|
|
3441
|
+
return {
|
|
3442
|
+
...svc,
|
|
3443
|
+
env: {
|
|
3444
|
+
...svc.env || {},
|
|
3445
|
+
SYSTEMROOT: systemRoot
|
|
3446
|
+
}
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
return svc;
|
|
3451
|
+
});
|
|
3452
|
+
writeCodexConfig({
|
|
3453
|
+
model: existingConfig?.model || null,
|
|
3454
|
+
modelProvider: existingConfig?.modelProvider || null,
|
|
3455
|
+
providers: baseProviders2,
|
|
3456
|
+
mcpServices: finalServices2,
|
|
3457
|
+
otherConfig: existingConfig?.otherConfig || []
|
|
3458
|
+
});
|
|
3459
|
+
updateZcfConfig({ codeToolType: "codex" });
|
|
3460
|
+
console.log(ansis.green(i18n.t("codex:mcpConfigured")));
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
2889
3463
|
const selectedIds = await selectMcpServices();
|
|
2890
3464
|
if (!selectedIds)
|
|
2891
3465
|
return;
|
|
@@ -2962,7 +3536,7 @@ async function configureCodexMcp(options) {
|
|
|
2962
3536
|
command: serviceConfig.command,
|
|
2963
3537
|
args: serviceConfig.args,
|
|
2964
3538
|
env: Object.keys(env).length > 0 ? env : void 0,
|
|
2965
|
-
|
|
3539
|
+
startup_timeout_sec: 30
|
|
2966
3540
|
});
|
|
2967
3541
|
}
|
|
2968
3542
|
const mergedMap = /* @__PURE__ */ new Map();
|
|
@@ -2996,6 +3570,7 @@ async function configureCodexMcp(options) {
|
|
|
2996
3570
|
console.log(ansis.green(i18n.t("codex:mcpConfigured")));
|
|
2997
3571
|
}
|
|
2998
3572
|
|
|
3573
|
+
let cachedSkipPromptBackup = null;
|
|
2999
3574
|
function getRootDir$1() {
|
|
3000
3575
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
3001
3576
|
let dir = dirname(currentFilePath);
|
|
@@ -3084,12 +3659,16 @@ function createBackupDirectory(timestamp) {
|
|
|
3084
3659
|
function backupCodexFiles() {
|
|
3085
3660
|
if (!exists(CODEX_DIR))
|
|
3086
3661
|
return null;
|
|
3662
|
+
if (process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP === "true" && cachedSkipPromptBackup)
|
|
3663
|
+
return cachedSkipPromptBackup;
|
|
3087
3664
|
const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
|
|
3088
3665
|
const backupDir = createBackupDirectory(timestamp);
|
|
3089
3666
|
const filter = (path) => {
|
|
3090
3667
|
return !path.includes("/backup");
|
|
3091
3668
|
};
|
|
3092
3669
|
copyDir(CODEX_DIR, backupDir, { filter });
|
|
3670
|
+
if (process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP === "true")
|
|
3671
|
+
cachedSkipPromptBackup = backupDir;
|
|
3093
3672
|
return backupDir;
|
|
3094
3673
|
}
|
|
3095
3674
|
function backupCodexComplete() {
|
|
@@ -3109,6 +3688,8 @@ function backupCodexConfig() {
|
|
|
3109
3688
|
}
|
|
3110
3689
|
}
|
|
3111
3690
|
function backupCodexAgents() {
|
|
3691
|
+
if (process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP === "true" && cachedSkipPromptBackup)
|
|
3692
|
+
return cachedSkipPromptBackup;
|
|
3112
3693
|
if (!exists(CODEX_AGENTS_FILE))
|
|
3113
3694
|
return null;
|
|
3114
3695
|
try {
|
|
@@ -3122,6 +3703,8 @@ function backupCodexAgents() {
|
|
|
3122
3703
|
}
|
|
3123
3704
|
}
|
|
3124
3705
|
function backupCodexPrompts() {
|
|
3706
|
+
if (process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP === "true" && cachedSkipPromptBackup)
|
|
3707
|
+
return cachedSkipPromptBackup;
|
|
3125
3708
|
if (!exists(CODEX_PROMPTS_DIR))
|
|
3126
3709
|
return null;
|
|
3127
3710
|
try {
|
|
@@ -3137,9 +3720,91 @@ function backupCodexPrompts() {
|
|
|
3137
3720
|
function getBackupMessage(path) {
|
|
3138
3721
|
if (!path)
|
|
3139
3722
|
return "";
|
|
3140
|
-
|
|
3723
|
+
if (!i18n.isInitialized) {
|
|
3724
|
+
return `Backup created: ${path}`;
|
|
3725
|
+
}
|
|
3141
3726
|
return i18n.t("codex:backupSuccess", { path });
|
|
3142
3727
|
}
|
|
3728
|
+
function needsEnvKeyMigration() {
|
|
3729
|
+
if (!exists(CODEX_CONFIG_FILE))
|
|
3730
|
+
return false;
|
|
3731
|
+
try {
|
|
3732
|
+
const content = readFile(CODEX_CONFIG_FILE);
|
|
3733
|
+
const hasOldEnvKey = /^\s*env_key\s*=/m.test(content);
|
|
3734
|
+
return hasOldEnvKey;
|
|
3735
|
+
} catch {
|
|
3736
|
+
return false;
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
function migrateEnvKeyToTempEnvKey() {
|
|
3740
|
+
if (!exists(CODEX_CONFIG_FILE))
|
|
3741
|
+
return false;
|
|
3742
|
+
try {
|
|
3743
|
+
const content = readFile(CODEX_CONFIG_FILE);
|
|
3744
|
+
if (!needsEnvKeyMigration())
|
|
3745
|
+
return false;
|
|
3746
|
+
const backupPath = backupCodexConfig();
|
|
3747
|
+
if (backupPath) {
|
|
3748
|
+
console.log(ansis.gray(getBackupMessage(backupPath)));
|
|
3749
|
+
}
|
|
3750
|
+
const migratedContent = migrateEnvKeyInContent(content);
|
|
3751
|
+
writeFile(CODEX_CONFIG_FILE, migratedContent);
|
|
3752
|
+
updateTomlConfig(ZCF_CONFIG_FILE, {
|
|
3753
|
+
codex: {
|
|
3754
|
+
envKeyMigrated: true
|
|
3755
|
+
}
|
|
3756
|
+
});
|
|
3757
|
+
const message = i18n.isInitialized ? i18n.t("codex:envKeyMigrationComplete") : "\u2714 env_key to temp_env_key migration completed";
|
|
3758
|
+
console.log(ansis.green(message));
|
|
3759
|
+
return true;
|
|
3760
|
+
} catch (error) {
|
|
3761
|
+
console.error(ansis.yellow(`env_key migration warning: ${error.message}`));
|
|
3762
|
+
return false;
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
function migrateEnvKeyInContent(content) {
|
|
3766
|
+
const lines = content.split("\n");
|
|
3767
|
+
const result = [];
|
|
3768
|
+
let currentSectionHasTempEnvKey = false;
|
|
3769
|
+
let currentSection = "";
|
|
3770
|
+
const sectionHasTempEnvKey = /* @__PURE__ */ new Map();
|
|
3771
|
+
let tempSection = "";
|
|
3772
|
+
for (const line of lines) {
|
|
3773
|
+
const sectionMatch = line.match(/^\s*\[([^\]]+)\]/);
|
|
3774
|
+
if (sectionMatch) {
|
|
3775
|
+
tempSection = sectionMatch[1];
|
|
3776
|
+
}
|
|
3777
|
+
if (tempSection && /^\s*temp_env_key\s*=/.test(line)) {
|
|
3778
|
+
sectionHasTempEnvKey.set(tempSection, true);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
for (const line of lines) {
|
|
3782
|
+
const sectionMatch = line.match(/^\s*\[([^\]]+)\]/);
|
|
3783
|
+
if (sectionMatch) {
|
|
3784
|
+
currentSection = sectionMatch[1];
|
|
3785
|
+
currentSectionHasTempEnvKey = sectionHasTempEnvKey.get(currentSection) || false;
|
|
3786
|
+
}
|
|
3787
|
+
const envKeyMatch = line.match(/^(\s*)env_key(\s*=.*)$/);
|
|
3788
|
+
if (envKeyMatch) {
|
|
3789
|
+
if (currentSectionHasTempEnvKey) {
|
|
3790
|
+
continue;
|
|
3791
|
+
} else {
|
|
3792
|
+
result.push(`${envKeyMatch[1]}temp_env_key${envKeyMatch[2]}`);
|
|
3793
|
+
continue;
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
result.push(line);
|
|
3797
|
+
}
|
|
3798
|
+
return result.join("\n");
|
|
3799
|
+
}
|
|
3800
|
+
function ensureEnvKeyMigration() {
|
|
3801
|
+
const tomlConfig = readDefaultTomlConfig();
|
|
3802
|
+
if (tomlConfig?.codex?.envKeyMigrated)
|
|
3803
|
+
return;
|
|
3804
|
+
if (needsEnvKeyMigration()) {
|
|
3805
|
+
migrateEnvKeyToTempEnvKey();
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3143
3808
|
function sanitizeProviderName(input) {
|
|
3144
3809
|
const cleaned = input.trim();
|
|
3145
3810
|
if (!cleaned)
|
|
@@ -3172,7 +3837,7 @@ function parseCodexConfig(content) {
|
|
|
3172
3837
|
name: provider.name || id,
|
|
3173
3838
|
baseUrl: provider.base_url || "",
|
|
3174
3839
|
wireApi: provider.wire_api || "responses",
|
|
3175
|
-
|
|
3840
|
+
tempEnvKey: provider.temp_env_key || "OPENAI_API_KEY",
|
|
3176
3841
|
requiresOpenaiAuth: provider.requires_openai_auth !== false,
|
|
3177
3842
|
model: provider.model || void 0
|
|
3178
3843
|
// Parse model field from provider
|
|
@@ -3181,7 +3846,7 @@ function parseCodexConfig(content) {
|
|
|
3181
3846
|
}
|
|
3182
3847
|
const mcpServices = [];
|
|
3183
3848
|
if (tomlData.mcp_servers) {
|
|
3184
|
-
const KNOWN_MCP_FIELDS = /* @__PURE__ */ new Set(["command", "args", "env", "
|
|
3849
|
+
const KNOWN_MCP_FIELDS = /* @__PURE__ */ new Set(["command", "args", "env", "startup_timeout_sec"]);
|
|
3185
3850
|
for (const [id, mcpData] of Object.entries(tomlData.mcp_servers)) {
|
|
3186
3851
|
const mcp = mcpData;
|
|
3187
3852
|
const extraFields = {};
|
|
@@ -3195,7 +3860,7 @@ function parseCodexConfig(content) {
|
|
|
3195
3860
|
command: mcp.command || id,
|
|
3196
3861
|
args: mcp.args || [],
|
|
3197
3862
|
env: Object.keys(mcp.env || {}).length > 0 ? mcp.env : void 0,
|
|
3198
|
-
|
|
3863
|
+
startup_timeout_sec: mcp.startup_timeout_sec,
|
|
3199
3864
|
// Only add extraFields if there are any extra fields
|
|
3200
3865
|
extraFields: Object.keys(extraFields).length > 0 ? extraFields : void 0
|
|
3201
3866
|
});
|
|
@@ -3363,6 +4028,7 @@ function formatTomlField(key, value) {
|
|
|
3363
4028
|
function readCodexConfig() {
|
|
3364
4029
|
if (!exists(CODEX_CONFIG_FILE))
|
|
3365
4030
|
return null;
|
|
4031
|
+
ensureEnvKeyMigration();
|
|
3366
4032
|
try {
|
|
3367
4033
|
const content = readFile(CODEX_CONFIG_FILE);
|
|
3368
4034
|
return parseCodexConfig(content);
|
|
@@ -3416,7 +4082,7 @@ function renderCodexConfig(data) {
|
|
|
3416
4082
|
lines.push(`name = "${provider.name}"`);
|
|
3417
4083
|
lines.push(`base_url = "${provider.baseUrl}"`);
|
|
3418
4084
|
lines.push(`wire_api = "${provider.wireApi}"`);
|
|
3419
|
-
lines.push(`
|
|
4085
|
+
lines.push(`temp_env_key = "${provider.tempEnvKey}"`);
|
|
3420
4086
|
lines.push(`requires_openai_auth = ${provider.requiresOpenaiAuth}`);
|
|
3421
4087
|
if (provider.model) {
|
|
3422
4088
|
lines.push(`model = "${provider.model}"`);
|
|
@@ -3436,8 +4102,8 @@ function renderCodexConfig(data) {
|
|
|
3436
4102
|
const envEntries = Object.entries(service.env).map(([key, value]) => `${key} = '${value}'`).join(", ");
|
|
3437
4103
|
lines.push(`env = {${envEntries}}`);
|
|
3438
4104
|
}
|
|
3439
|
-
if (service.
|
|
3440
|
-
lines.push(`
|
|
4105
|
+
if (service.startup_timeout_sec) {
|
|
4106
|
+
lines.push(`startup_timeout_sec = ${service.startup_timeout_sec}`);
|
|
3441
4107
|
}
|
|
3442
4108
|
if (service.extraFields) {
|
|
3443
4109
|
for (const [key, value] of Object.entries(service.extraFields)) {
|
|
@@ -3460,6 +4126,7 @@ function renderCodexConfig(data) {
|
|
|
3460
4126
|
return result;
|
|
3461
4127
|
}
|
|
3462
4128
|
function writeCodexConfig(data) {
|
|
4129
|
+
ensureEnvKeyMigration();
|
|
3463
4130
|
ensureDir(CODEX_DIR);
|
|
3464
4131
|
writeFile(CODEX_CONFIG_FILE, renderCodexConfig(data));
|
|
3465
4132
|
}
|
|
@@ -3595,8 +4262,8 @@ async function runCodexSystemPromptSelection(skipPrompt = false) {
|
|
|
3595
4262
|
const rootDir = getRootDir$1();
|
|
3596
4263
|
const templateRoot = join(rootDir, "templates", "codex");
|
|
3597
4264
|
const zcfConfig$1 = readZcfConfig();
|
|
3598
|
-
const { readDefaultTomlConfig } = await Promise.resolve().then(function () { return zcfConfig; });
|
|
3599
|
-
const tomlConfig =
|
|
4265
|
+
const { readDefaultTomlConfig: readDefaultTomlConfig2 } = await Promise.resolve().then(function () { return zcfConfig; });
|
|
4266
|
+
const tomlConfig = readDefaultTomlConfig2();
|
|
3600
4267
|
const { resolveTemplateLanguage } = await Promise.resolve().then(function () { return prompts; });
|
|
3601
4268
|
const preferredLang = await resolveTemplateLanguage(
|
|
3602
4269
|
void 0,
|
|
@@ -3656,9 +4323,9 @@ async function runCodexSystemPromptSelection(skipPrompt = false) {
|
|
|
3656
4323
|
}
|
|
3657
4324
|
writeFile(CODEX_AGENTS_FILE, content);
|
|
3658
4325
|
try {
|
|
3659
|
-
const { updateTomlConfig } = await Promise.resolve().then(function () { return zcfConfig; });
|
|
3660
|
-
const { ZCF_CONFIG_FILE } = await Promise.resolve().then(function () { return constants; });
|
|
3661
|
-
|
|
4326
|
+
const { updateTomlConfig: updateTomlConfig2 } = await Promise.resolve().then(function () { return zcfConfig; });
|
|
4327
|
+
const { ZCF_CONFIG_FILE: ZCF_CONFIG_FILE2 } = await Promise.resolve().then(function () { return constants; });
|
|
4328
|
+
updateTomlConfig2(ZCF_CONFIG_FILE2, {
|
|
3662
4329
|
codex: {
|
|
3663
4330
|
systemPromptStyle: systemPrompt
|
|
3664
4331
|
}
|
|
@@ -3808,29 +4475,39 @@ async function applyCustomApiConfig(customApiConfig) {
|
|
|
3808
4475
|
if (backupPath) {
|
|
3809
4476
|
console.log(ansis.gray(getBackupMessage(backupPath)));
|
|
3810
4477
|
}
|
|
4478
|
+
const existingConfig = readCodexConfig();
|
|
4479
|
+
const existingAuth = readJsonConfig(CODEX_AUTH_FILE, { defaultValue: {} }) || {};
|
|
3811
4480
|
const providers = [];
|
|
3812
|
-
const authEntries = {};
|
|
4481
|
+
const authEntries = { ...existingAuth };
|
|
3813
4482
|
const providerId = type === "auth_token" ? "official-auth-token" : "custom-api-key";
|
|
3814
4483
|
const providerName = type === "auth_token" ? "Official Auth Token" : "Custom API Key";
|
|
4484
|
+
const existingProvider = existingConfig?.providers.find((p) => p.id === providerId);
|
|
3815
4485
|
providers.push({
|
|
3816
4486
|
id: providerId,
|
|
3817
4487
|
name: providerName,
|
|
3818
|
-
baseUrl: baseUrl || "https://api.anthropic.com",
|
|
3819
|
-
wireApi: "
|
|
3820
|
-
|
|
3821
|
-
requiresOpenaiAuth: false
|
|
4488
|
+
baseUrl: baseUrl || existingProvider?.baseUrl || "https://api.anthropic.com",
|
|
4489
|
+
wireApi: existingProvider?.wireApi || "responses",
|
|
4490
|
+
tempEnvKey: existingProvider?.tempEnvKey || `${providerId.toUpperCase()}_API_KEY`,
|
|
4491
|
+
requiresOpenaiAuth: existingProvider?.requiresOpenaiAuth ?? false,
|
|
4492
|
+
model: model || existingProvider?.model
|
|
3822
4493
|
});
|
|
4494
|
+
if (existingConfig?.providers) {
|
|
4495
|
+
providers.push(...existingConfig.providers.filter((p) => p.id !== providerId));
|
|
4496
|
+
}
|
|
3823
4497
|
if (token) {
|
|
3824
4498
|
authEntries[providerId] = token;
|
|
4499
|
+
authEntries.OPENAI_API_KEY = token;
|
|
3825
4500
|
}
|
|
3826
4501
|
const configData = {
|
|
3827
|
-
model: model || "claude-3-5-sonnet-20241022",
|
|
3828
|
-
//
|
|
4502
|
+
model: model || existingConfig?.model || "claude-3-5-sonnet-20241022",
|
|
4503
|
+
// Prefer provided model, then existing, fallback default
|
|
3829
4504
|
modelProvider: providerId,
|
|
3830
4505
|
modelProviderCommented: false,
|
|
3831
4506
|
providers,
|
|
3832
|
-
mcpServices: []
|
|
3833
|
-
|
|
4507
|
+
mcpServices: existingConfig?.mcpServices || [],
|
|
4508
|
+
otherConfig: existingConfig?.otherConfig || []
|
|
4509
|
+
};
|
|
4510
|
+
writeCodexConfig(configData);
|
|
3834
4511
|
writeJsonConfig(CODEX_AUTH_FILE, authEntries);
|
|
3835
4512
|
updateZcfConfig({ codeToolType: "codex" });
|
|
3836
4513
|
console.log(ansis.green(`\u2714 ${i18n.t("codex:apiConfigured")}`));
|
|
@@ -4008,7 +4685,7 @@ async function configureCodexApi(options) {
|
|
|
4008
4685
|
}
|
|
4009
4686
|
}
|
|
4010
4687
|
const providerId = sanitizeProviderName(answers.providerName);
|
|
4011
|
-
const
|
|
4688
|
+
const tempEnvKey = `${providerId.toUpperCase().replace(/-/g, "_")}_API_KEY`;
|
|
4012
4689
|
const existingProvider = existingMap.get(providerId);
|
|
4013
4690
|
const sessionProvider = currentSessionProviders.get(providerId);
|
|
4014
4691
|
if (existingProvider || sessionProvider) {
|
|
@@ -4038,14 +4715,14 @@ async function configureCodexApi(options) {
|
|
|
4038
4715
|
name: answers.providerName,
|
|
4039
4716
|
baseUrl: selectedProvider2 === "custom" ? answers.baseUrl : prefilledBaseUrl,
|
|
4040
4717
|
wireApi: selectedProvider2 === "custom" ? answers.wireApi || "responses" : prefilledWireApi,
|
|
4041
|
-
|
|
4718
|
+
tempEnvKey,
|
|
4042
4719
|
requiresOpenaiAuth: true,
|
|
4043
4720
|
model: customModel || prefilledModel || "gpt-5-codex"
|
|
4044
4721
|
// Use custom model, provider's default model, or fallback
|
|
4045
4722
|
};
|
|
4046
4723
|
providers.push(newProvider);
|
|
4047
4724
|
currentSessionProviders.set(providerId, newProvider);
|
|
4048
|
-
authEntries[
|
|
4725
|
+
authEntries[tempEnvKey] = answers.apiKey;
|
|
4049
4726
|
const addAnother = await promptBoolean({
|
|
4050
4727
|
message: i18n.t("codex:addProviderPrompt"),
|
|
4051
4728
|
defaultValue: false
|
|
@@ -4065,8 +4742,8 @@ async function configureCodexApi(options) {
|
|
|
4065
4742
|
}]);
|
|
4066
4743
|
const selectedProvider = providers.find((provider) => provider.id === defaultProvider);
|
|
4067
4744
|
if (selectedProvider) {
|
|
4068
|
-
const
|
|
4069
|
-
const defaultApiKey = authEntries[
|
|
4745
|
+
const tempEnvKey = selectedProvider.tempEnvKey;
|
|
4746
|
+
const defaultApiKey = authEntries[tempEnvKey] ?? existingAuth[tempEnvKey] ?? null;
|
|
4070
4747
|
if (defaultApiKey)
|
|
4071
4748
|
authEntries.OPENAI_API_KEY = defaultApiKey;
|
|
4072
4749
|
}
|
|
@@ -4351,7 +5028,7 @@ async function switchToProvider(providerId) {
|
|
|
4351
5028
|
};
|
|
4352
5029
|
writeCodexConfig(updatedConfig);
|
|
4353
5030
|
const auth = readJsonConfig(CODEX_AUTH_FILE, { defaultValue: {} }) || {};
|
|
4354
|
-
const envValue = auth[provider.
|
|
5031
|
+
const envValue = auth[provider.tempEnvKey] || null;
|
|
4355
5032
|
auth.OPENAI_API_KEY = envValue;
|
|
4356
5033
|
writeJsonConfig(CODEX_AUTH_FILE, auth, { pretty: true });
|
|
4357
5034
|
console.log(ansis.green(i18n.t("codex:providerSwitchSuccess", { provider: providerId })));
|
|
@@ -4375,11 +5052,15 @@ const codex = {
|
|
|
4375
5052
|
configureCodexApi: configureCodexApi,
|
|
4376
5053
|
configureCodexMcp: configureCodexMcp,
|
|
4377
5054
|
createBackupDirectory: createBackupDirectory,
|
|
5055
|
+
ensureEnvKeyMigration: ensureEnvKeyMigration,
|
|
4378
5056
|
getBackupMessage: getBackupMessage,
|
|
4379
5057
|
getCodexVersion: getCodexVersion,
|
|
4380
5058
|
installCodexCli: installCodexCli,
|
|
4381
5059
|
isCodexInstalled: isCodexInstalled$1,
|
|
4382
5060
|
listCodexProviders: listCodexProviders,
|
|
5061
|
+
migrateEnvKeyInContent: migrateEnvKeyInContent,
|
|
5062
|
+
migrateEnvKeyToTempEnvKey: migrateEnvKeyToTempEnvKey,
|
|
5063
|
+
needsEnvKeyMigration: needsEnvKeyMigration,
|
|
4383
5064
|
parseCodexConfig: parseCodexConfig,
|
|
4384
5065
|
readCodexConfig: readCodexConfig,
|
|
4385
5066
|
renderCodexConfig: renderCodexConfig,
|
|
@@ -4989,6 +5670,10 @@ async function installClaudeCode(skipMethodSelection = false) {
|
|
|
4989
5670
|
if (version) {
|
|
4990
5671
|
console.log(ansis.gray(` ${i18n.t("installation:detectedVersion", { version })}`));
|
|
4991
5672
|
}
|
|
5673
|
+
const verification = await verifyInstallation(codeType);
|
|
5674
|
+
if (verification.symlinkCreated) {
|
|
5675
|
+
displayVerificationResult(verification, codeType);
|
|
5676
|
+
}
|
|
4992
5677
|
await updateClaudeCode();
|
|
4993
5678
|
return;
|
|
4994
5679
|
}
|
|
@@ -5011,13 +5696,15 @@ async function installClaudeCode(skipMethodSelection = false) {
|
|
|
5011
5696
|
if (skipMethodSelection) {
|
|
5012
5697
|
console.log(i18n.t("installation:installing"));
|
|
5013
5698
|
try {
|
|
5014
|
-
const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
|
|
5699
|
+
const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@anthropic-ai/claude-code", "--force"]);
|
|
5015
5700
|
if (usedSudo) {
|
|
5016
5701
|
console.log(ansis.yellow(`\u2139 ${i18n.t("installation:usingSudo")}`));
|
|
5017
5702
|
}
|
|
5018
5703
|
await exec(command, args);
|
|
5019
5704
|
console.log(`\u2714 ${i18n.t("installation:installSuccess")}`);
|
|
5020
5705
|
await setInstallMethod("npm");
|
|
5706
|
+
const verification = await verifyInstallation(codeType);
|
|
5707
|
+
displayVerificationResult(verification, codeType);
|
|
5021
5708
|
if (isTermux()) {
|
|
5022
5709
|
console.log(ansis.gray(`
|
|
5023
5710
|
Claude Code installed to: ${getTermuxPrefix()}/bin/claude`));
|
|
@@ -5078,12 +5765,14 @@ async function installCodex(skipMethodSelection = false) {
|
|
|
5078
5765
|
if (skipMethodSelection) {
|
|
5079
5766
|
console.log(i18n.t("installation:installingWith", { method: "npm", codeType: codeTypeName }));
|
|
5080
5767
|
try {
|
|
5081
|
-
const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@openai/codex"]);
|
|
5768
|
+
const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", "@openai/codex", "--force"]);
|
|
5082
5769
|
if (usedSudo) {
|
|
5083
5770
|
console.log(ansis.yellow(`\u2139 ${i18n.t("installation:usingSudo")}`));
|
|
5084
5771
|
}
|
|
5085
5772
|
await exec(command, args);
|
|
5086
5773
|
console.log(ansis.green(`\u2714 ${codeTypeName} ${i18n.t("installation:installSuccess")}`));
|
|
5774
|
+
const verification = await verifyInstallation(codeType);
|
|
5775
|
+
displayVerificationResult(verification, codeType);
|
|
5087
5776
|
} catch (error) {
|
|
5088
5777
|
console.error(ansis.red(`\u2716 ${codeTypeName} ${i18n.t("installation:installFailed")}`));
|
|
5089
5778
|
throw error;
|
|
@@ -5161,7 +5850,7 @@ async function uninstallCodeTool(codeType) {
|
|
|
5161
5850
|
}
|
|
5162
5851
|
} else if (codeType === "codex") {
|
|
5163
5852
|
try {
|
|
5164
|
-
const result = await exec("brew", ["list", "codex"]);
|
|
5853
|
+
const result = await exec("brew", ["list", "--cask", "codex"]);
|
|
5165
5854
|
if (result.exitCode === 0) {
|
|
5166
5855
|
method = "homebrew";
|
|
5167
5856
|
}
|
|
@@ -5176,7 +5865,7 @@ async function uninstallCodeTool(codeType) {
|
|
|
5176
5865
|
const platform = getPlatform();
|
|
5177
5866
|
if (platform === "macos" || platform === "linux") {
|
|
5178
5867
|
try {
|
|
5179
|
-
const testResult = codeType === "claude-code" ? await exec("brew", ["list", "--cask", "claude-code"]) : await exec("brew", ["list", "codex"]);
|
|
5868
|
+
const testResult = codeType === "claude-code" ? await exec("brew", ["list", "--cask", "claude-code"]) : await exec("brew", ["list", "--cask", "codex"]);
|
|
5180
5869
|
if (testResult.exitCode === 0) {
|
|
5181
5870
|
method = "homebrew";
|
|
5182
5871
|
}
|
|
@@ -5205,7 +5894,7 @@ async function uninstallCodeTool(codeType) {
|
|
|
5205
5894
|
if (codeType === "claude-code") {
|
|
5206
5895
|
await exec("brew", ["uninstall", "--cask", "claude-code"]);
|
|
5207
5896
|
} else {
|
|
5208
|
-
await exec("brew", ["uninstall", "codex"]);
|
|
5897
|
+
await exec("brew", ["uninstall", "--cask", "codex"]);
|
|
5209
5898
|
}
|
|
5210
5899
|
break;
|
|
5211
5900
|
}
|
|
@@ -5276,6 +5965,22 @@ async function detectInstalledVersion(codeType) {
|
|
|
5276
5965
|
}
|
|
5277
5966
|
return null;
|
|
5278
5967
|
}
|
|
5968
|
+
function getInstallMethodLabel(method) {
|
|
5969
|
+
switch (method) {
|
|
5970
|
+
case "npm":
|
|
5971
|
+
return i18n.t("installation:installMethodNpm");
|
|
5972
|
+
case "homebrew":
|
|
5973
|
+
return i18n.t("installation:installMethodHomebrew");
|
|
5974
|
+
case "curl":
|
|
5975
|
+
return i18n.t("installation:installMethodCurl");
|
|
5976
|
+
case "powershell":
|
|
5977
|
+
return i18n.t("installation:installMethodPowershell");
|
|
5978
|
+
case "cmd":
|
|
5979
|
+
return i18n.t("installation:installMethodCmd");
|
|
5980
|
+
default:
|
|
5981
|
+
return method;
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5279
5984
|
function getInstallMethodOptions(codeType, recommendedMethods) {
|
|
5280
5985
|
const allMethods = ["npm", "homebrew", "curl", "powershell", "cmd"];
|
|
5281
5986
|
const platform = getPlatform();
|
|
@@ -5294,7 +5999,8 @@ function getInstallMethodOptions(codeType, recommendedMethods) {
|
|
|
5294
5999
|
const topRecommended = recommendedMethods.length > 0 ? recommendedMethods[0] : null;
|
|
5295
6000
|
return availableMethods.map((method) => {
|
|
5296
6001
|
const isTopRecommended = method === topRecommended;
|
|
5297
|
-
const
|
|
6002
|
+
const methodLabel = getInstallMethodLabel(method);
|
|
6003
|
+
const title = isTopRecommended ? `${methodLabel} ${ansis.green(`[${i18n.t("installation:recommendedMethod")}]`)}` : methodLabel;
|
|
5298
6004
|
return {
|
|
5299
6005
|
title,
|
|
5300
6006
|
value: method
|
|
@@ -5329,7 +6035,7 @@ async function executeInstallMethod(method, codeType) {
|
|
|
5329
6035
|
switch (method) {
|
|
5330
6036
|
case "npm": {
|
|
5331
6037
|
const packageName = codeType === "claude-code" ? "@anthropic-ai/claude-code" : "@openai/codex";
|
|
5332
|
-
const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", packageName]);
|
|
6038
|
+
const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["install", "-g", packageName, "--force"]);
|
|
5333
6039
|
if (usedSudo) {
|
|
5334
6040
|
spinner.info(i18n.t("installation:usingSudo"));
|
|
5335
6041
|
spinner.start();
|
|
@@ -5342,7 +6048,7 @@ async function executeInstallMethod(method, codeType) {
|
|
|
5342
6048
|
if (codeType === "claude-code") {
|
|
5343
6049
|
await exec("brew", ["install", "--cask", "claude-code"]);
|
|
5344
6050
|
} else {
|
|
5345
|
-
await exec("brew", ["install", "codex"]);
|
|
6051
|
+
await exec("brew", ["install", "--cask", "codex"]);
|
|
5346
6052
|
}
|
|
5347
6053
|
await setInstallMethod("homebrew", codeType);
|
|
5348
6054
|
break;
|
|
@@ -5381,6 +6087,8 @@ async function executeInstallMethod(method, codeType) {
|
|
|
5381
6087
|
throw new Error(`Unsupported install method: ${method}`);
|
|
5382
6088
|
}
|
|
5383
6089
|
spinner.succeed(i18n.t("installation:installMethodSuccess", { method }));
|
|
6090
|
+
const verification = await verifyInstallation(codeType);
|
|
6091
|
+
displayVerificationResult(verification, codeType);
|
|
5384
6092
|
return true;
|
|
5385
6093
|
} catch (error) {
|
|
5386
6094
|
spinner.fail(i18n.t("installation:installMethodFailed", { method }));
|
|
@@ -5411,10 +6119,187 @@ async function handleInstallFailure(codeType, failedMethods) {
|
|
|
5411
6119
|
}
|
|
5412
6120
|
return await handleInstallFailure(codeType, [...failedMethods, newMethod]);
|
|
5413
6121
|
}
|
|
6122
|
+
async function isCommandInPath(command) {
|
|
6123
|
+
try {
|
|
6124
|
+
const cmd = getPlatform() === "windows" ? "where" : "which";
|
|
6125
|
+
const res = await exec(cmd, [command]);
|
|
6126
|
+
return res.exitCode === 0;
|
|
6127
|
+
} catch {
|
|
6128
|
+
return false;
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
6131
|
+
async function verifyInstallation(codeType) {
|
|
6132
|
+
const command = codeType === "claude-code" ? "claude" : "codex";
|
|
6133
|
+
const commandInPath = await isCommandInPath(command);
|
|
6134
|
+
if (commandInPath) {
|
|
6135
|
+
const version = await detectInstalledVersion(codeType);
|
|
6136
|
+
return {
|
|
6137
|
+
success: true,
|
|
6138
|
+
commandPath: await findCommandPath(command),
|
|
6139
|
+
version,
|
|
6140
|
+
needsSymlink: false,
|
|
6141
|
+
symlinkCreated: false
|
|
6142
|
+
};
|
|
6143
|
+
}
|
|
6144
|
+
if (getPlatform() === "macos") {
|
|
6145
|
+
const homebrewPaths = await getHomebrewCommandPaths(command);
|
|
6146
|
+
let foundPath = null;
|
|
6147
|
+
for (const path of homebrewPaths) {
|
|
6148
|
+
if (exists(path)) {
|
|
6149
|
+
foundPath = path;
|
|
6150
|
+
break;
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
if (foundPath) {
|
|
6154
|
+
const symlinkResult = await createHomebrewSymlink(command, foundPath);
|
|
6155
|
+
if (symlinkResult.success) {
|
|
6156
|
+
const version = await detectInstalledVersion(codeType);
|
|
6157
|
+
return {
|
|
6158
|
+
success: true,
|
|
6159
|
+
commandPath: symlinkResult.symlinkPath,
|
|
6160
|
+
version,
|
|
6161
|
+
needsSymlink: true,
|
|
6162
|
+
symlinkCreated: true
|
|
6163
|
+
};
|
|
6164
|
+
}
|
|
6165
|
+
return {
|
|
6166
|
+
success: false,
|
|
6167
|
+
commandPath: foundPath,
|
|
6168
|
+
version: null,
|
|
6169
|
+
needsSymlink: true,
|
|
6170
|
+
symlinkCreated: false,
|
|
6171
|
+
error: symlinkResult.error
|
|
6172
|
+
};
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
if (isTermux()) {
|
|
6176
|
+
const termuxPrefix = getTermuxPrefix();
|
|
6177
|
+
const termuxPaths = [
|
|
6178
|
+
`${termuxPrefix}/bin/${command}`,
|
|
6179
|
+
`${termuxPrefix}/usr/bin/${command}`
|
|
6180
|
+
];
|
|
6181
|
+
for (const path of termuxPaths) {
|
|
6182
|
+
if (exists(path)) {
|
|
6183
|
+
const version = await detectInstalledVersion(codeType);
|
|
6184
|
+
return {
|
|
6185
|
+
success: true,
|
|
6186
|
+
commandPath: path,
|
|
6187
|
+
version,
|
|
6188
|
+
needsSymlink: false,
|
|
6189
|
+
symlinkCreated: false
|
|
6190
|
+
};
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
}
|
|
6194
|
+
return {
|
|
6195
|
+
success: false,
|
|
6196
|
+
commandPath: null,
|
|
6197
|
+
version: null,
|
|
6198
|
+
needsSymlink: false,
|
|
6199
|
+
symlinkCreated: false,
|
|
6200
|
+
error: "Command not found in any known location"
|
|
6201
|
+
};
|
|
6202
|
+
}
|
|
6203
|
+
async function createHomebrewSymlink(command, sourcePath) {
|
|
6204
|
+
const homebrewBinPaths = [
|
|
6205
|
+
"/opt/homebrew/bin",
|
|
6206
|
+
// Apple Silicon (M1/M2)
|
|
6207
|
+
"/usr/local/bin"
|
|
6208
|
+
// Intel Mac
|
|
6209
|
+
];
|
|
6210
|
+
let targetDir = null;
|
|
6211
|
+
for (const binPath of homebrewBinPaths) {
|
|
6212
|
+
if (nodeFs.existsSync(binPath)) {
|
|
6213
|
+
targetDir = binPath;
|
|
6214
|
+
break;
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
if (!targetDir) {
|
|
6218
|
+
return {
|
|
6219
|
+
success: false,
|
|
6220
|
+
symlinkPath: null,
|
|
6221
|
+
error: "No suitable Homebrew bin directory found"
|
|
6222
|
+
};
|
|
6223
|
+
}
|
|
6224
|
+
const symlinkPath = join(targetDir, command);
|
|
6225
|
+
try {
|
|
6226
|
+
const stats = nodeFs.lstatSync(symlinkPath);
|
|
6227
|
+
if (stats.isSymbolicLink()) {
|
|
6228
|
+
const existingTarget = nodeFs.readlinkSync(symlinkPath);
|
|
6229
|
+
if (existingTarget === sourcePath) {
|
|
6230
|
+
return {
|
|
6231
|
+
success: true,
|
|
6232
|
+
symlinkPath
|
|
6233
|
+
};
|
|
6234
|
+
}
|
|
6235
|
+
nodeFs.unlinkSync(symlinkPath);
|
|
6236
|
+
} else {
|
|
6237
|
+
return {
|
|
6238
|
+
success: false,
|
|
6239
|
+
symlinkPath: null,
|
|
6240
|
+
error: `File already exists at ${symlinkPath} and is not a symlink`
|
|
6241
|
+
};
|
|
6242
|
+
}
|
|
6243
|
+
} catch (error) {
|
|
6244
|
+
if (error.code !== "ENOENT") {
|
|
6245
|
+
return {
|
|
6246
|
+
success: false,
|
|
6247
|
+
symlinkPath: null,
|
|
6248
|
+
error: `Failed to check existing file: ${error}`
|
|
6249
|
+
};
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
6252
|
+
try {
|
|
6253
|
+
nodeFs.symlinkSync(sourcePath, symlinkPath);
|
|
6254
|
+
return {
|
|
6255
|
+
success: true,
|
|
6256
|
+
symlinkPath
|
|
6257
|
+
};
|
|
6258
|
+
} catch (error) {
|
|
6259
|
+
if (error.code === "EACCES") {
|
|
6260
|
+
return {
|
|
6261
|
+
success: false,
|
|
6262
|
+
symlinkPath: null,
|
|
6263
|
+
error: `Permission denied. Try running: sudo ln -sf ${sourcePath} ${symlinkPath}`
|
|
6264
|
+
};
|
|
6265
|
+
}
|
|
6266
|
+
return {
|
|
6267
|
+
success: false,
|
|
6268
|
+
symlinkPath: null,
|
|
6269
|
+
error: `Failed to create symlink: ${error.message}`
|
|
6270
|
+
};
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
function displayVerificationResult(result, codeType) {
|
|
6274
|
+
ensureI18nInitialized();
|
|
6275
|
+
const codeTypeName = codeType === "claude-code" ? i18n.t("common:claudeCode") : i18n.t("common:codex");
|
|
6276
|
+
if (result.success) {
|
|
6277
|
+
if (result.symlinkCreated) {
|
|
6278
|
+
console.log(ansis.green(`\u2714 ${codeTypeName} ${i18n.t("installation:verificationSuccess")}`));
|
|
6279
|
+
console.log(ansis.gray(` ${i18n.t("installation:symlinkCreated", { path: result.commandPath })}`));
|
|
6280
|
+
}
|
|
6281
|
+
if (result.version) {
|
|
6282
|
+
console.log(ansis.gray(` ${i18n.t("installation:detectedVersion", { version: result.version })}`));
|
|
6283
|
+
}
|
|
6284
|
+
} else {
|
|
6285
|
+
console.log(ansis.yellow(`\u26A0 ${codeTypeName} ${i18n.t("installation:verificationFailed")}`));
|
|
6286
|
+
if (result.commandPath) {
|
|
6287
|
+
console.log(ansis.gray(` ${i18n.t("installation:foundAtPath", { path: result.commandPath })}`));
|
|
6288
|
+
}
|
|
6289
|
+
if (result.error) {
|
|
6290
|
+
console.log(ansis.gray(` ${result.error}`));
|
|
6291
|
+
}
|
|
6292
|
+
if (result.needsSymlink && !result.symlinkCreated) {
|
|
6293
|
+
console.log(ansis.yellow(` ${i18n.t("installation:manualSymlinkHint")}`));
|
|
6294
|
+
}
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
5414
6297
|
|
|
5415
6298
|
const installer = {
|
|
5416
6299
|
__proto__: null,
|
|
6300
|
+
createHomebrewSymlink: createHomebrewSymlink,
|
|
5417
6301
|
detectInstalledVersion: detectInstalledVersion,
|
|
6302
|
+
displayVerificationResult: displayVerificationResult,
|
|
5418
6303
|
executeInstallMethod: executeInstallMethod,
|
|
5419
6304
|
getInstallationStatus: getInstallationStatus,
|
|
5420
6305
|
handleInstallFailure: handleInstallFailure,
|
|
@@ -5426,7 +6311,8 @@ const installer = {
|
|
|
5426
6311
|
removeLocalClaudeCode: removeLocalClaudeCode,
|
|
5427
6312
|
selectInstallMethod: selectInstallMethod,
|
|
5428
6313
|
setInstallMethod: setInstallMethod,
|
|
5429
|
-
uninstallCodeTool: uninstallCodeTool
|
|
6314
|
+
uninstallCodeTool: uninstallCodeTool,
|
|
6315
|
+
verifyInstallation: verifyInstallation
|
|
5430
6316
|
};
|
|
5431
6317
|
|
|
5432
6318
|
async function chooseInstallationMethod() {
|
|
@@ -5754,15 +6640,18 @@ async function validateSkipPromptOptions(options) {
|
|
|
5754
6640
|
if (options.apiConfigs && options.apiConfigsFile) {
|
|
5755
6641
|
throw new Error(i18n.t("multi-config:conflictingParams"));
|
|
5756
6642
|
}
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
6643
|
+
const modelParams = [
|
|
6644
|
+
["apiModel", options.apiModel],
|
|
6645
|
+
["apiHaikuModel", options.apiHaikuModel],
|
|
6646
|
+
["apiSonnetModel", options.apiSonnetModel],
|
|
6647
|
+
["apiOpusModel", options.apiOpusModel]
|
|
6648
|
+
];
|
|
6649
|
+
for (const [key, value] of modelParams) {
|
|
6650
|
+
if (value !== void 0 && typeof value !== "string") {
|
|
6651
|
+
if (key === "apiModel")
|
|
6652
|
+
throw new Error(i18n.t("errors:invalidApiModel", { value }));
|
|
6653
|
+
throw new Error(i18n.t("errors:invalidModelParam", { key, value }));
|
|
6654
|
+
}
|
|
5766
6655
|
}
|
|
5767
6656
|
if (options.apiType === "api_key" && !options.apiKey) {
|
|
5768
6657
|
throw new Error(i18n.t("errors:apiKeyRequiredForApiKey"));
|
|
@@ -5952,8 +6841,11 @@ async function init(options = {}) {
|
|
|
5952
6841
|
configLang = "en";
|
|
5953
6842
|
}
|
|
5954
6843
|
if (codeToolType === "codex") {
|
|
5955
|
-
|
|
5956
|
-
|
|
6844
|
+
if (options.skipPrompt)
|
|
6845
|
+
process.env.ZCF_CODEX_SKIP_PROMPT_SINGLE_BACKUP = "true";
|
|
6846
|
+
const hasApiConfigs = Boolean(options.apiConfigs || options.apiConfigsFile);
|
|
6847
|
+
const apiMode = hasApiConfigs ? "skip" : options.apiType === "auth_token" ? "official" : options.apiType === "api_key" ? "custom" : options.apiType === "skip" ? "skip" : options.skipPrompt ? "skip" : void 0;
|
|
6848
|
+
const customApiConfig = !hasApiConfigs && options.apiType === "api_key" && options.apiKey ? {
|
|
5957
6849
|
type: "api_key",
|
|
5958
6850
|
token: options.apiKey,
|
|
5959
6851
|
baseUrl: options.apiUrl,
|
|
@@ -5968,6 +6860,9 @@ async function init(options = {}) {
|
|
|
5968
6860
|
} else if (options.workflows === true) {
|
|
5969
6861
|
selectedWorkflows = [];
|
|
5970
6862
|
}
|
|
6863
|
+
if (hasApiConfigs) {
|
|
6864
|
+
await handleMultiConfigurations(options, "codex");
|
|
6865
|
+
}
|
|
5971
6866
|
const resolvedAiOutputLang = await runCodexFullInit({
|
|
5972
6867
|
aiOutputLang: options.aiOutputLang,
|
|
5973
6868
|
skipPrompt: options.skipPrompt,
|
|
@@ -6008,6 +6903,17 @@ async function init(options = {}) {
|
|
|
6008
6903
|
console.log(ansis.green(`\u2714 ${i18n.t("installation:localInstallationRemoved")}`));
|
|
6009
6904
|
}
|
|
6010
6905
|
}
|
|
6906
|
+
const { verifyInstallation, displayVerificationResult } = await Promise.resolve().then(function () { return installer; });
|
|
6907
|
+
const verification = await verifyInstallation("claude-code");
|
|
6908
|
+
if (verification.symlinkCreated) {
|
|
6909
|
+
console.log(ansis.green(`\u2714 ${i18n.t("installation:alreadyInstalled")}`));
|
|
6910
|
+
displayVerificationResult(verification, "claude-code");
|
|
6911
|
+
} else if (!verification.success) {
|
|
6912
|
+
console.log(ansis.yellow(`\u26A0 ${i18n.t("installation:verificationFailed")}`));
|
|
6913
|
+
if (verification.error) {
|
|
6914
|
+
console.log(ansis.gray(` ${verification.error}`));
|
|
6915
|
+
}
|
|
6916
|
+
}
|
|
6011
6917
|
} else {
|
|
6012
6918
|
if (options.skipPrompt) {
|
|
6013
6919
|
await installClaudeCode(true);
|
|
@@ -6075,9 +6981,12 @@ async function init(options = {}) {
|
|
|
6075
6981
|
key: options.apiKey,
|
|
6076
6982
|
url: preset?.claudeCode?.baseUrl || options.apiUrl || API_DEFAULT_URL
|
|
6077
6983
|
};
|
|
6078
|
-
if (preset?.claudeCode?.defaultModels && preset.claudeCode.defaultModels.length
|
|
6079
|
-
|
|
6080
|
-
options.
|
|
6984
|
+
if (preset?.claudeCode?.defaultModels && preset.claudeCode.defaultModels.length > 0) {
|
|
6985
|
+
const [primary, haiku, sonnet, opus] = preset.claudeCode.defaultModels;
|
|
6986
|
+
options.apiModel = options.apiModel || primary;
|
|
6987
|
+
options.apiHaikuModel = options.apiHaikuModel || haiku;
|
|
6988
|
+
options.apiSonnetModel = options.apiSonnetModel || sonnet;
|
|
6989
|
+
options.apiOpusModel = options.apiOpusModel || opus;
|
|
6081
6990
|
}
|
|
6082
6991
|
await saveSingleConfigToToml(apiConfig, options.provider, options);
|
|
6083
6992
|
} else if (options.apiType === "auth_token" && options.apiKey) {
|
|
@@ -6204,20 +7113,26 @@ async function init(options = {}) {
|
|
|
6204
7113
|
console.log(ansis.gray(` Key: ${formatApiKeyDisplay(configuredApi.key)}`));
|
|
6205
7114
|
}
|
|
6206
7115
|
}
|
|
6207
|
-
|
|
7116
|
+
const hasModelParams = options.apiModel || options.apiHaikuModel || options.apiSonnetModel || options.apiOpusModel;
|
|
7117
|
+
if (hasModelParams && action !== "docs-only" && codeToolType === "claude-code") {
|
|
6208
7118
|
if (options.skipPrompt) {
|
|
6209
7119
|
const { updateCustomModel } = await Promise.resolve().then(function () { return config$1; });
|
|
6210
7120
|
updateCustomModel(
|
|
6211
7121
|
options.apiModel || void 0,
|
|
6212
|
-
options.
|
|
7122
|
+
options.apiHaikuModel || void 0,
|
|
7123
|
+
options.apiSonnetModel || void 0,
|
|
7124
|
+
options.apiOpusModel || void 0
|
|
6213
7125
|
);
|
|
6214
7126
|
console.log(ansis.green(`\u2714 ${i18n.t("api:modelConfigSuccess")}`));
|
|
6215
7127
|
if (options.apiModel) {
|
|
6216
7128
|
console.log(ansis.gray(` ${i18n.t("api:primaryModel")}: ${options.apiModel}`));
|
|
6217
7129
|
}
|
|
6218
|
-
if (options.
|
|
6219
|
-
console.log(ansis.gray(`
|
|
6220
|
-
|
|
7130
|
+
if (options.apiHaikuModel)
|
|
7131
|
+
console.log(ansis.gray(` Haiku: ${options.apiHaikuModel}`));
|
|
7132
|
+
if (options.apiSonnetModel)
|
|
7133
|
+
console.log(ansis.gray(` Sonnet: ${options.apiSonnetModel}`));
|
|
7134
|
+
if (options.apiOpusModel)
|
|
7135
|
+
console.log(ansis.gray(` Opus: ${options.apiOpusModel}`));
|
|
6221
7136
|
}
|
|
6222
7137
|
}
|
|
6223
7138
|
if (action !== "docs-only") {
|
|
@@ -6440,6 +7355,7 @@ async function handleClaudeCodeConfigs(configs) {
|
|
|
6440
7355
|
}
|
|
6441
7356
|
async function handleCodexConfigs(configs) {
|
|
6442
7357
|
const { addProviderToExisting } = await import('./codex-provider-manager.mjs');
|
|
7358
|
+
const addedProviderIds = [];
|
|
6443
7359
|
for (const config of configs) {
|
|
6444
7360
|
try {
|
|
6445
7361
|
const provider = await convertToCodexProvider(config);
|
|
@@ -6447,6 +7363,7 @@ async function handleCodexConfigs(configs) {
|
|
|
6447
7363
|
if (!result.success) {
|
|
6448
7364
|
throw new Error(i18n.t("multi-config:providerAddFailed", { name: config.name, error: result.error }));
|
|
6449
7365
|
}
|
|
7366
|
+
addedProviderIds.push(provider.id);
|
|
6450
7367
|
console.log(ansis.green(`\u2714 ${i18n.t("multi-config:providerAdded", { name: config.name })}`));
|
|
6451
7368
|
} catch (error) {
|
|
6452
7369
|
console.error(ansis.red(i18n.t("multi-config:providerAddFailed", {
|
|
@@ -6459,8 +7376,14 @@ async function handleCodexConfigs(configs) {
|
|
|
6459
7376
|
const defaultConfig = configs.find((c) => c.default);
|
|
6460
7377
|
if (defaultConfig) {
|
|
6461
7378
|
const { switchCodexProvider } = await Promise.resolve().then(function () { return codex; });
|
|
6462
|
-
|
|
6463
|
-
|
|
7379
|
+
const displayName = defaultConfig.name || defaultConfig.provider || "custom";
|
|
7380
|
+
const providerId = displayName.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
7381
|
+
if (addedProviderIds.includes(providerId)) {
|
|
7382
|
+
await switchCodexProvider(providerId);
|
|
7383
|
+
console.log(ansis.green(`\u2714 ${i18n.t("multi-config:defaultProviderSet", { name: displayName })}`));
|
|
7384
|
+
} else {
|
|
7385
|
+
console.log(ansis.red(i18n.t("multi-config:providerAddFailed", { name: displayName, error: "provider not added" })));
|
|
7386
|
+
}
|
|
6464
7387
|
}
|
|
6465
7388
|
}
|
|
6466
7389
|
async function saveSingleConfigToToml(apiConfig, provider, options) {
|
|
@@ -6487,7 +7410,9 @@ async function convertSingleConfigToProfile(apiConfig, provider, options) {
|
|
|
6487
7410
|
const configName = provider && provider !== "custom" ? provider : "custom-config";
|
|
6488
7411
|
let baseUrl = apiConfig.url || API_DEFAULT_URL;
|
|
6489
7412
|
let primaryModel = options?.apiModel;
|
|
6490
|
-
let
|
|
7413
|
+
let defaultHaikuModel = options?.apiHaikuModel;
|
|
7414
|
+
let defaultSonnetModel = options?.apiSonnetModel;
|
|
7415
|
+
let defaultOpusModel = options?.apiOpusModel;
|
|
6491
7416
|
let authType = apiConfig.authType;
|
|
6492
7417
|
if (provider && provider !== "custom") {
|
|
6493
7418
|
const { getProviderPreset } = await import('./api-providers.mjs');
|
|
@@ -6495,9 +7420,12 @@ async function convertSingleConfigToProfile(apiConfig, provider, options) {
|
|
|
6495
7420
|
if (preset?.claudeCode) {
|
|
6496
7421
|
baseUrl = apiConfig.url || preset.claudeCode.baseUrl;
|
|
6497
7422
|
authType = preset.claudeCode.authType;
|
|
6498
|
-
if (preset.claudeCode.defaultModels && preset.claudeCode.defaultModels.length
|
|
6499
|
-
|
|
6500
|
-
|
|
7423
|
+
if (preset.claudeCode.defaultModels && preset.claudeCode.defaultModels.length > 0) {
|
|
7424
|
+
const [p, h, s, o] = preset.claudeCode.defaultModels;
|
|
7425
|
+
primaryModel = primaryModel || p;
|
|
7426
|
+
defaultHaikuModel = defaultHaikuModel || h;
|
|
7427
|
+
defaultSonnetModel = defaultSonnetModel || s;
|
|
7428
|
+
defaultOpusModel = defaultOpusModel || o;
|
|
6501
7429
|
}
|
|
6502
7430
|
}
|
|
6503
7431
|
}
|
|
@@ -6507,7 +7435,9 @@ async function convertSingleConfigToProfile(apiConfig, provider, options) {
|
|
|
6507
7435
|
apiKey: apiConfig.key,
|
|
6508
7436
|
baseUrl,
|
|
6509
7437
|
primaryModel,
|
|
6510
|
-
|
|
7438
|
+
defaultHaikuModel,
|
|
7439
|
+
defaultSonnetModel,
|
|
7440
|
+
defaultOpusModel,
|
|
6511
7441
|
id: ClaudeCodeConfigManager.generateProfileId(configName)
|
|
6512
7442
|
};
|
|
6513
7443
|
return profile;
|
|
@@ -6516,7 +7446,9 @@ async function convertToClaudeCodeProfile(config) {
|
|
|
6516
7446
|
const { ClaudeCodeConfigManager } = await import('./claude-code-config-manager.mjs');
|
|
6517
7447
|
let baseUrl = config.url;
|
|
6518
7448
|
let primaryModel = config.primaryModel;
|
|
6519
|
-
let
|
|
7449
|
+
let defaultHaikuModel = config.defaultHaikuModel;
|
|
7450
|
+
let defaultSonnetModel = config.defaultSonnetModel;
|
|
7451
|
+
let defaultOpusModel = config.defaultOpusModel;
|
|
6520
7452
|
let authType = config.type || "api_key";
|
|
6521
7453
|
if (config.provider && config.provider !== "custom") {
|
|
6522
7454
|
const { getProviderPreset } = await import('./api-providers.mjs');
|
|
@@ -6524,9 +7456,12 @@ async function convertToClaudeCodeProfile(config) {
|
|
|
6524
7456
|
if (preset?.claudeCode) {
|
|
6525
7457
|
baseUrl = baseUrl || preset.claudeCode.baseUrl;
|
|
6526
7458
|
authType = preset.claudeCode.authType;
|
|
6527
|
-
if (preset.claudeCode.defaultModels && preset.claudeCode.defaultModels.length
|
|
6528
|
-
|
|
6529
|
-
|
|
7459
|
+
if (preset.claudeCode.defaultModels && preset.claudeCode.defaultModels.length > 0) {
|
|
7460
|
+
const [p, h, s, o] = preset.claudeCode.defaultModels;
|
|
7461
|
+
primaryModel = primaryModel || p;
|
|
7462
|
+
defaultHaikuModel = defaultHaikuModel || h;
|
|
7463
|
+
defaultSonnetModel = defaultSonnetModel || s;
|
|
7464
|
+
defaultOpusModel = defaultOpusModel || o;
|
|
6530
7465
|
}
|
|
6531
7466
|
}
|
|
6532
7467
|
}
|
|
@@ -6536,15 +7471,19 @@ async function convertToClaudeCodeProfile(config) {
|
|
|
6536
7471
|
apiKey: config.key,
|
|
6537
7472
|
baseUrl,
|
|
6538
7473
|
primaryModel,
|
|
6539
|
-
|
|
7474
|
+
defaultHaikuModel,
|
|
7475
|
+
defaultSonnetModel,
|
|
7476
|
+
defaultOpusModel,
|
|
6540
7477
|
id: ClaudeCodeConfigManager.generateProfileId(config.name)
|
|
6541
7478
|
};
|
|
6542
7479
|
return profile;
|
|
6543
7480
|
}
|
|
6544
7481
|
async function convertToCodexProvider(config) {
|
|
7482
|
+
const displayName = config.name || config.provider || "custom";
|
|
7483
|
+
const providerId = displayName.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
6545
7484
|
let baseUrl = config.url || API_DEFAULT_URL;
|
|
6546
7485
|
let model = config.primaryModel || "gpt-5-codex";
|
|
6547
|
-
let wireApi = "
|
|
7486
|
+
let wireApi = "responses";
|
|
6548
7487
|
if (config.provider && config.provider !== "custom") {
|
|
6549
7488
|
const { getProviderPreset } = await import('./api-providers.mjs');
|
|
6550
7489
|
const preset = getProviderPreset(config.provider);
|
|
@@ -6555,11 +7494,11 @@ async function convertToCodexProvider(config) {
|
|
|
6555
7494
|
}
|
|
6556
7495
|
}
|
|
6557
7496
|
return {
|
|
6558
|
-
id:
|
|
6559
|
-
name:
|
|
7497
|
+
id: providerId,
|
|
7498
|
+
name: displayName,
|
|
6560
7499
|
baseUrl,
|
|
6561
7500
|
wireApi,
|
|
6562
|
-
|
|
7501
|
+
tempEnvKey: `${displayName}_API_KEY`.replace(/\W/g, "_").toUpperCase(),
|
|
6563
7502
|
requiresOpenaiAuth: false,
|
|
6564
7503
|
model
|
|
6565
7504
|
};
|
|
@@ -6643,4 +7582,4 @@ async function openSettingsJson() {
|
|
|
6643
7582
|
}
|
|
6644
7583
|
}
|
|
6645
7584
|
|
|
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,
|
|
7585
|
+
export { getExistingModelConfig as $, API_DEFAULT_URL as A, getAiOutputLanguageLabel as B, CLAUDE_DIR as C, DEFAULT_CODE_TOOL_TYPE as D, getMcpConfigPath as E, readMcpConfig as F, writeMcpConfig as G, backupMcpConfig as H, mergeMcpServers as I, buildMcpServerConfig as J, fixWindowsMcpConfig as K, LEGACY_ZCF_CONFIG_FILES as L, addCompletedOnboarding as M, ensureApiKeyApproved as N, removeApiKeyFromRejected as O, manageApiKeyApproval as P, setPrimaryApiKey as Q, ensureClaudeDir as R, SETTINGS_FILE as S, backupExistingConfig as T, copyConfigFiles as U, configureApi as V, mergeConfigs as W, updateCustomModel as X, updateDefaultModel as Y, ZCF_CONFIG_DIR as Z, mergeSettingsFile as _, commandExists as a, resolveAiOutputLanguage as a$, getExistingApiConfig as a0, applyAiLanguageDirective as a1, switchToOfficialLogin$1 as a2, promptApiConfigurationAction as a3, isClaudeCodeInstalled as a4, installClaudeCode as a5, isCodexInstalled as a6, installCodex as a7, isLocalClaudeCodeInstalled as a8, getInstallationStatus as a9, writeCodexConfig as aA, writeAuthFile as aB, updateZcfConfig as aC, changeLanguage as aD, readZcfConfig as aE, configureOutputStyle as aF, isWindows as aG, selectMcpServices as aH, getMcpServices as aI, isCcrInstalled as aJ, installCcr as aK, setupCcrConfiguration as aL, modifyApiConfigPartially as aM, formatApiKeyDisplay as aN, readCcrConfig as aO, configureCcrFeature as aP, handleExitPromptError as aQ, handleGeneralError as aR, COMETIX_COMMAND_NAME as aS, COMETIX_COMMANDS as aT, installCometixLine as aU, checkAndUpdateTools as aV, runCodexUpdate as aW, resolveCodeType as aX, writeJsonConfig as aY, displayBanner as aZ, version as a_, removeLocalClaudeCode as aa, uninstallCodeTool as ab, setInstallMethod as ac, detectInstalledVersion as ad, selectInstallMethod as ae, executeInstallMethod as af, handleInstallFailure as ag, verifyInstallation as ah, createHomebrewSymlink as ai, displayVerificationResult as aj, ensureI18nInitialized as ak, i18n as al, addNumbersToChoices as am, validateApiKey as an, promptBoolean as ao, ensureDir as ap, readDefaultTomlConfig as aq, createDefaultTomlConfig as ar, exists as as, readJsonConfig as at, writeTomlConfig as au, clearModelEnv as av, copyFile as aw, detectConfigManagementMode as ax, readCodexConfig as ay, backupCodexComplete as az, importRecommendedEnv as b, updatePromptOnly as b0, selectAndInstallWorkflows as b1, checkClaudeCodeVersionAndPrompt as b2, displayBannerWithInfo as b3, runCodexUninstall as b4, configureCodexMcp as b5, configureCodexApi as b6, runCodexWorkflowImportWithLanguageSelection as b7, runCodexFullInit as b8, switchCodexProvider as b9, listCodexProviders as ba, switchToOfficialLogin as bb, switchToProvider as bc, readZcfConfigAsync as bd, initI18n as be, selectScriptLanguage as bf, index as bg, fsOperations as bh, jsonConfig as bi, claudeConfig as bj, config$1 as bk, config as bl, prompts as bm, codex as bn, installer as bo, cleanupPermissions as c, importRecommendedPermissions as d, CLAUDE_MD_FILE as e, ClAUDE_CONFIG_FILE as f, getPlatform as g, CLAUDE_VSC_CONFIG_FILE as h, init as i, CODEX_DIR as j, CODEX_CONFIG_FILE as k, CODEX_AUTH_FILE as l, mergeAndCleanPermissions as m, CODEX_AGENTS_FILE as n, openSettingsJson as o, CODEX_PROMPTS_DIR as p, ZCF_CONFIG_FILE as q, CODE_TOOL_TYPES as r, CODE_TOOL_BANNERS as s, CODE_TOOL_ALIASES as t, isCodeToolType as u, API_ENV_KEY as v, resolveCodeToolType as w, SUPPORTED_LANGS as x, LANG_LABELS as y, AI_OUTPUT_LANGUAGES as z };
|