@xwm111/ccs 0.1.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.
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/bin/ccs.mjs +2 -0
- package/dist/chunks/auto-updater.mjs +1708 -0
- package/dist/chunks/claude-code-incremental-manager.mjs +576 -0
- package/dist/chunks/installer.mjs +610 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +2407 -0
- package/dist/i18n/locales/en/api.json +51 -0
- package/dist/i18n/locales/en/cli.json +58 -0
- package/dist/i18n/locales/en/common.json +19 -0
- package/dist/i18n/locales/en/configuration.json +81 -0
- package/dist/i18n/locales/en/errors.json +26 -0
- package/dist/i18n/locales/en/installation.json +80 -0
- package/dist/i18n/locales/en/language.json +19 -0
- package/dist/i18n/locales/en/menu.json +31 -0
- package/dist/i18n/locales/en/multi-config.json +79 -0
- package/dist/i18n/locales/en/uninstall.json +56 -0
- package/dist/i18n/locales/en/updater.json +26 -0
- package/dist/i18n/locales/zh-CN/api.json +51 -0
- package/dist/i18n/locales/zh-CN/cli.json +58 -0
- package/dist/i18n/locales/zh-CN/common.json +19 -0
- package/dist/i18n/locales/zh-CN/configuration.json +81 -0
- package/dist/i18n/locales/zh-CN/errors.json +26 -0
- package/dist/i18n/locales/zh-CN/installation.json +80 -0
- package/dist/i18n/locales/zh-CN/language.json +19 -0
- package/dist/i18n/locales/zh-CN/menu.json +31 -0
- package/dist/i18n/locales/zh-CN/multi-config.json +79 -0
- package/dist/i18n/locales/zh-CN/uninstall.json +56 -0
- package/dist/i18n/locales/zh-CN/updater.json +26 -0
- package/dist/index.d.mts +222 -0
- package/dist/index.d.ts +222 -0
- package/dist/index.mjs +18 -0
- package/package.json +109 -0
|
@@ -0,0 +1,1708 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import ansis from 'ansis';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { dirname, join } from 'pathe';
|
|
6
|
+
import { homedir, platform as platform$1 } from 'node:os';
|
|
7
|
+
import * as nodeFs from 'node:fs';
|
|
8
|
+
import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, lstatSync, statSync, rmSync, rmdirSync, readdirSync, unlinkSync } from 'node:fs';
|
|
9
|
+
import process from 'node:process';
|
|
10
|
+
import { exec } from 'tinyexec';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
import semver from 'semver';
|
|
13
|
+
import i18next from 'i18next';
|
|
14
|
+
import Backend from 'i18next-fs-backend';
|
|
15
|
+
import toggleModule from 'inquirer-toggle';
|
|
16
|
+
import { exec as exec$1 } from 'node:child_process';
|
|
17
|
+
import { promisify } from 'node:util';
|
|
18
|
+
|
|
19
|
+
const i18n = i18next.createInstance();
|
|
20
|
+
const NAMESPACES = [
|
|
21
|
+
"common",
|
|
22
|
+
"api",
|
|
23
|
+
"cli",
|
|
24
|
+
"configuration",
|
|
25
|
+
"errors",
|
|
26
|
+
"installation",
|
|
27
|
+
"language",
|
|
28
|
+
"menu",
|
|
29
|
+
"multi-config",
|
|
30
|
+
"uninstall",
|
|
31
|
+
"updater"
|
|
32
|
+
];
|
|
33
|
+
function ensureI18nInitialized() {
|
|
34
|
+
if (!i18n.isInitialized) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"i18n is not initialized. Please call initI18n() in CLI command before using utility functions."
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function initI18n(language = "zh-CN") {
|
|
41
|
+
if (i18n.isInitialized) {
|
|
42
|
+
if (i18n.language !== language) {
|
|
43
|
+
await i18n.changeLanguage(language);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await i18n.use(Backend).init({
|
|
48
|
+
lng: language,
|
|
49
|
+
fallbackLng: "en",
|
|
50
|
+
// Load all translations as a single flat structure
|
|
51
|
+
ns: NAMESPACES,
|
|
52
|
+
defaultNS: "common",
|
|
53
|
+
preload: [language],
|
|
54
|
+
// Preload the selected language
|
|
55
|
+
// Backend configuration for loading JSON files
|
|
56
|
+
backend: {
|
|
57
|
+
loadPath: (() => {
|
|
58
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
59
|
+
const packageRoot = (() => {
|
|
60
|
+
let dir = currentDir;
|
|
61
|
+
while (dir !== dirname(dir)) {
|
|
62
|
+
if (existsSync(join(dir, "package.json"))) {
|
|
63
|
+
return dir;
|
|
64
|
+
}
|
|
65
|
+
dir = dirname(dir);
|
|
66
|
+
}
|
|
67
|
+
return currentDir;
|
|
68
|
+
})();
|
|
69
|
+
const possibleBasePaths = [
|
|
70
|
+
join(currentDir, "locales"),
|
|
71
|
+
// Development: src/i18n/locales
|
|
72
|
+
join(packageRoot, "dist/i18n/locales"),
|
|
73
|
+
// NPM package: /node_modules/zcf/dist/i18n/locales
|
|
74
|
+
join(process.cwd(), "dist/i18n/locales"),
|
|
75
|
+
// Production build: ./dist/i18n/locales
|
|
76
|
+
join(currentDir, "../../../dist/i18n/locales"),
|
|
77
|
+
// Fallback for deep chunk paths
|
|
78
|
+
join(currentDir, "../../i18n/locales")
|
|
79
|
+
// Alternative chunk structure
|
|
80
|
+
];
|
|
81
|
+
for (const basePath of possibleBasePaths) {
|
|
82
|
+
const testFile = join(basePath, "zh-CN/common.json");
|
|
83
|
+
if (existsSync(testFile)) {
|
|
84
|
+
return join(basePath, "{{lng}}/{{ns}}.json");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return join(process.cwd(), "dist/i18n/locales/{{lng}}/{{ns}}.json");
|
|
88
|
+
})()
|
|
89
|
+
},
|
|
90
|
+
// Interpolation settings
|
|
91
|
+
interpolation: {
|
|
92
|
+
escapeValue: false
|
|
93
|
+
// Not needed for server-side usage
|
|
94
|
+
},
|
|
95
|
+
// Disable key separator for flat keys, enable namespace separator
|
|
96
|
+
keySeparator: false,
|
|
97
|
+
nsSeparator: ":",
|
|
98
|
+
// Debugging (disable for clean output)
|
|
99
|
+
debug: false
|
|
100
|
+
});
|
|
101
|
+
for (const ns of NAMESPACES) {
|
|
102
|
+
if (ns !== "common") {
|
|
103
|
+
await i18n.loadNamespaces(ns);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function format(template, values) {
|
|
108
|
+
if (!values)
|
|
109
|
+
return template;
|
|
110
|
+
return Object.keys(values).reduce((result, key) => {
|
|
111
|
+
return result.replace(new RegExp(`{${key}}`, "g"), values[key]);
|
|
112
|
+
}, template);
|
|
113
|
+
}
|
|
114
|
+
async function changeLanguage(lng) {
|
|
115
|
+
await i18n.changeLanguage(lng);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const index = {
|
|
119
|
+
__proto__: null,
|
|
120
|
+
changeLanguage: changeLanguage,
|
|
121
|
+
ensureI18nInitialized: ensureI18nInitialized,
|
|
122
|
+
format: format,
|
|
123
|
+
i18n: i18n,
|
|
124
|
+
initI18n: initI18n
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const CLAUDE_DIR = join(homedir(), ".claude");
|
|
128
|
+
const SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
|
|
129
|
+
const CLAUDE_MD_FILE = join(CLAUDE_DIR, "CLAUDE.md");
|
|
130
|
+
const ClAUDE_CONFIG_FILE = join(homedir(), ".claude.json");
|
|
131
|
+
const CLAUDE_VSC_CONFIG_FILE = join(CLAUDE_DIR, "config.json");
|
|
132
|
+
const ZCF_CONFIG_DIR = join(homedir(), ".ccs");
|
|
133
|
+
const ZCF_CONFIG_FILE = join(ZCF_CONFIG_DIR, "config.toml");
|
|
134
|
+
const LEGACY_ZCF_CONFIG_FILES = [
|
|
135
|
+
join(CLAUDE_DIR, ".zcf-config.json"),
|
|
136
|
+
join(homedir(), ".zcf.json")
|
|
137
|
+
];
|
|
138
|
+
const CODE_TOOL_TYPES = ["claude-code"];
|
|
139
|
+
const DEFAULT_CODE_TOOL_TYPE = "claude-code";
|
|
140
|
+
const CODE_TOOL_BANNERS = {
|
|
141
|
+
"claude-code": "for Claude Code"
|
|
142
|
+
};
|
|
143
|
+
const CODE_TOOL_ALIASES = {
|
|
144
|
+
cc: "claude-code"
|
|
145
|
+
};
|
|
146
|
+
function isCodeToolType(value) {
|
|
147
|
+
return CODE_TOOL_TYPES.includes(value);
|
|
148
|
+
}
|
|
149
|
+
const API_DEFAULT_URL = "https://api.anthropic.com";
|
|
150
|
+
const API_ENV_KEY = "ANTHROPIC_API_KEY";
|
|
151
|
+
function resolveCodeToolType(value) {
|
|
152
|
+
if (isCodeToolType(value)) {
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
if (typeof value === "string" && value in CODE_TOOL_ALIASES) {
|
|
156
|
+
return CODE_TOOL_ALIASES[value];
|
|
157
|
+
}
|
|
158
|
+
return DEFAULT_CODE_TOOL_TYPE;
|
|
159
|
+
}
|
|
160
|
+
const SUPPORTED_LANGS = ["zh-CN", "en"];
|
|
161
|
+
const LANG_LABELS = {
|
|
162
|
+
"zh-CN": "\u7B80\u4F53\u4E2D\u6587",
|
|
163
|
+
"en": "English"
|
|
164
|
+
};
|
|
165
|
+
const AI_OUTPUT_LANGUAGES = {
|
|
166
|
+
"zh-CN": { directive: "Always respond in Chinese-simplified" },
|
|
167
|
+
"en": { directive: "Always respond in English" },
|
|
168
|
+
"custom": { directive: "" }
|
|
169
|
+
};
|
|
170
|
+
function getAiOutputLanguageLabel(lang) {
|
|
171
|
+
if (lang in LANG_LABELS) {
|
|
172
|
+
return LANG_LABELS[lang];
|
|
173
|
+
}
|
|
174
|
+
if (lang === "custom" && i18n?.isInitialized) {
|
|
175
|
+
try {
|
|
176
|
+
return i18n.t("language:labels.custom");
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return lang;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getPlatform() {
|
|
184
|
+
const p = platform$1();
|
|
185
|
+
if (p === "win32")
|
|
186
|
+
return "windows";
|
|
187
|
+
if (p === "darwin")
|
|
188
|
+
return "macos";
|
|
189
|
+
return "linux";
|
|
190
|
+
}
|
|
191
|
+
function isTermux() {
|
|
192
|
+
return !!(process.env.PREFIX && process.env.PREFIX.includes("com.termux")) || !!process.env.TERMUX_VERSION || nodeFs.existsSync("/data/data/com.termux/files/usr");
|
|
193
|
+
}
|
|
194
|
+
function getTermuxPrefix() {
|
|
195
|
+
return process.env.PREFIX || "/data/data/com.termux/files/usr";
|
|
196
|
+
}
|
|
197
|
+
function isWindows() {
|
|
198
|
+
return getPlatform() === "windows";
|
|
199
|
+
}
|
|
200
|
+
function isWSL() {
|
|
201
|
+
if (process.env.WSL_DISTRO_NAME) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (nodeFs.existsSync("/proc/version")) {
|
|
205
|
+
try {
|
|
206
|
+
const version = nodeFs.readFileSync("/proc/version", "utf8");
|
|
207
|
+
if (version.includes("Microsoft") || version.includes("WSL")) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (nodeFs.existsSync("/mnt/c")) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
function getWSLDistro() {
|
|
219
|
+
if (process.env.WSL_DISTRO_NAME) {
|
|
220
|
+
return process.env.WSL_DISTRO_NAME;
|
|
221
|
+
}
|
|
222
|
+
if (nodeFs.existsSync("/etc/os-release")) {
|
|
223
|
+
try {
|
|
224
|
+
const osRelease = nodeFs.readFileSync("/etc/os-release", "utf8");
|
|
225
|
+
const nameMatch = osRelease.match(/^PRETTY_NAME="(.+)"$/m);
|
|
226
|
+
if (nameMatch) {
|
|
227
|
+
return nameMatch[1];
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
function getWSLInfo() {
|
|
235
|
+
if (!isWSL()) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
let version = null;
|
|
239
|
+
if (nodeFs.existsSync("/proc/version")) {
|
|
240
|
+
try {
|
|
241
|
+
version = nodeFs.readFileSync("/proc/version", "utf8").trim();
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
isWSL: true,
|
|
247
|
+
distro: getWSLDistro(),
|
|
248
|
+
version
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function normalizeTomlPath(str) {
|
|
252
|
+
return str.replace(/\\+/g, "/").replace(/\/+/g, "/");
|
|
253
|
+
}
|
|
254
|
+
function shouldUseSudoForGlobalInstall() {
|
|
255
|
+
if (isTermux())
|
|
256
|
+
return false;
|
|
257
|
+
if (getPlatform() !== "linux")
|
|
258
|
+
return false;
|
|
259
|
+
const npmPrefix = getGlobalNpmPrefix();
|
|
260
|
+
if (npmPrefix) {
|
|
261
|
+
if (isPathInsideHome(npmPrefix) || canWriteToPath(npmPrefix))
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
const getuid = process.getuid;
|
|
265
|
+
if (typeof getuid !== "function")
|
|
266
|
+
return false;
|
|
267
|
+
try {
|
|
268
|
+
return getuid() !== 0;
|
|
269
|
+
} catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function wrapCommandWithSudo(command, args) {
|
|
274
|
+
if (shouldUseSudoForGlobalInstall()) {
|
|
275
|
+
return {
|
|
276
|
+
command: "sudo",
|
|
277
|
+
args: [command, ...args],
|
|
278
|
+
usedSudo: true
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
command,
|
|
283
|
+
args,
|
|
284
|
+
usedSudo: false
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const WRITE_CHECK_FLAG = 2;
|
|
288
|
+
function normalizePath(path) {
|
|
289
|
+
return normalizeTomlPath(path).replace(/\/+$/, "");
|
|
290
|
+
}
|
|
291
|
+
function isPathInsideHome(path) {
|
|
292
|
+
const home = process.env.HOME;
|
|
293
|
+
if (!home)
|
|
294
|
+
return false;
|
|
295
|
+
const normalizedHome = normalizePath(home);
|
|
296
|
+
const normalizedPath = normalizePath(path);
|
|
297
|
+
return normalizedPath === normalizedHome || normalizedPath.startsWith(`${normalizedHome}/`);
|
|
298
|
+
}
|
|
299
|
+
function canWriteToPath(path) {
|
|
300
|
+
try {
|
|
301
|
+
nodeFs.accessSync(path, WRITE_CHECK_FLAG);
|
|
302
|
+
return true;
|
|
303
|
+
} catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function getGlobalNpmPrefix() {
|
|
308
|
+
const env = process.env;
|
|
309
|
+
const envPrefix = env.npm_config_prefix || env.NPM_CONFIG_PREFIX || env.PREFIX;
|
|
310
|
+
if (envPrefix)
|
|
311
|
+
return envPrefix;
|
|
312
|
+
const execPath = process.execPath;
|
|
313
|
+
if (execPath) {
|
|
314
|
+
const binDir = dirname(execPath);
|
|
315
|
+
return dirname(binDir);
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
async function commandExists(command) {
|
|
320
|
+
try {
|
|
321
|
+
const cmd = getPlatform() === "windows" ? "where" : "which";
|
|
322
|
+
const res = await exec(cmd, [command]);
|
|
323
|
+
if (res.exitCode === 0) {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
if (isTermux()) {
|
|
329
|
+
const termuxPrefix = getTermuxPrefix();
|
|
330
|
+
const possiblePaths = [
|
|
331
|
+
`${termuxPrefix}/bin/${command}`,
|
|
332
|
+
`${termuxPrefix}/usr/bin/${command}`,
|
|
333
|
+
`/data/data/com.termux/files/usr/bin/${command}`
|
|
334
|
+
];
|
|
335
|
+
for (const path of possiblePaths) {
|
|
336
|
+
if (nodeFs.existsSync(path)) {
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (getPlatform() !== "windows") {
|
|
342
|
+
const commonPaths = [
|
|
343
|
+
`/usr/local/bin/${command}`,
|
|
344
|
+
`/usr/bin/${command}`,
|
|
345
|
+
`/bin/${command}`,
|
|
346
|
+
`${process.env.HOME}/.local/bin/${command}`
|
|
347
|
+
];
|
|
348
|
+
for (const path of commonPaths) {
|
|
349
|
+
if (nodeFs.existsSync(path)) {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (getPlatform() === "macos") {
|
|
354
|
+
const homebrewPaths = await getHomebrewCommandPaths(command);
|
|
355
|
+
for (const path of homebrewPaths) {
|
|
356
|
+
if (nodeFs.existsSync(path)) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
async function getHomebrewCommandPaths(command) {
|
|
365
|
+
const paths = [];
|
|
366
|
+
const homebrewPrefixes = [
|
|
367
|
+
"/opt/homebrew",
|
|
368
|
+
// Apple Silicon (M1/M2)
|
|
369
|
+
"/usr/local"
|
|
370
|
+
// Intel Mac
|
|
371
|
+
];
|
|
372
|
+
for (const prefix of homebrewPrefixes) {
|
|
373
|
+
paths.push(`${prefix}/bin/${command}`);
|
|
374
|
+
}
|
|
375
|
+
for (const prefix of homebrewPrefixes) {
|
|
376
|
+
const cellarNodePath = `${prefix}/Cellar/node`;
|
|
377
|
+
if (nodeFs.existsSync(cellarNodePath)) {
|
|
378
|
+
try {
|
|
379
|
+
const versions = nodeFs.readdirSync(cellarNodePath);
|
|
380
|
+
for (const version of versions) {
|
|
381
|
+
const binPath = `${cellarNodePath}/${version}/bin/${command}`;
|
|
382
|
+
paths.push(binPath);
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const caskNameMap = {
|
|
389
|
+
claude: "claude-code",
|
|
390
|
+
codex: "codex"
|
|
391
|
+
};
|
|
392
|
+
const caskName = caskNameMap[command];
|
|
393
|
+
if (caskName) {
|
|
394
|
+
for (const prefix of homebrewPrefixes) {
|
|
395
|
+
const caskroomPath = `${prefix}/Caskroom/${caskName}`;
|
|
396
|
+
if (nodeFs.existsSync(caskroomPath)) {
|
|
397
|
+
try {
|
|
398
|
+
const versions = nodeFs.readdirSync(caskroomPath).filter((v) => !v.startsWith("."));
|
|
399
|
+
for (const version of versions) {
|
|
400
|
+
const binPath = `${caskroomPath}/${version}/${command}`;
|
|
401
|
+
paths.push(binPath);
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return paths;
|
|
409
|
+
}
|
|
410
|
+
async function findCommandPath(command) {
|
|
411
|
+
try {
|
|
412
|
+
const cmd = getPlatform() === "windows" ? "where" : "which";
|
|
413
|
+
const res = await exec(cmd, [command]);
|
|
414
|
+
if (res.exitCode === 0 && res.stdout) {
|
|
415
|
+
return res.stdout.trim().split("\n")[0];
|
|
416
|
+
}
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
const commonPaths = [
|
|
420
|
+
`/usr/local/bin/${command}`,
|
|
421
|
+
`/usr/bin/${command}`,
|
|
422
|
+
`/bin/${command}`,
|
|
423
|
+
`${process.env.HOME}/.local/bin/${command}`
|
|
424
|
+
];
|
|
425
|
+
for (const path of commonPaths) {
|
|
426
|
+
if (nodeFs.existsSync(path)) {
|
|
427
|
+
return path;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (getPlatform() === "macos") {
|
|
431
|
+
const homebrewPaths = await getHomebrewCommandPaths(command);
|
|
432
|
+
for (const path of homebrewPaths) {
|
|
433
|
+
if (nodeFs.existsSync(path)) {
|
|
434
|
+
return path;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (isTermux()) {
|
|
439
|
+
const termuxPrefix = getTermuxPrefix();
|
|
440
|
+
const termuxPaths = [
|
|
441
|
+
`${termuxPrefix}/bin/${command}`,
|
|
442
|
+
`${termuxPrefix}/usr/bin/${command}`
|
|
443
|
+
];
|
|
444
|
+
for (const path of termuxPaths) {
|
|
445
|
+
if (nodeFs.existsSync(path)) {
|
|
446
|
+
return path;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
function getRecommendedInstallMethods(codeType) {
|
|
453
|
+
const platform2 = getPlatform();
|
|
454
|
+
const wsl = isWSL();
|
|
455
|
+
if (codeType === "claude-code") {
|
|
456
|
+
if (platform2 === "macos") {
|
|
457
|
+
return ["homebrew", "curl", "npm"];
|
|
458
|
+
}
|
|
459
|
+
if (platform2 === "linux" || wsl) {
|
|
460
|
+
return ["curl", "npm"];
|
|
461
|
+
}
|
|
462
|
+
if (platform2 === "windows") {
|
|
463
|
+
return ["powershell", "npm"];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (codeType === "codex") {
|
|
467
|
+
if (platform2 === "macos") {
|
|
468
|
+
return ["homebrew", "npm"];
|
|
469
|
+
}
|
|
470
|
+
if (platform2 === "linux" || wsl || platform2 === "windows") {
|
|
471
|
+
return ["npm"];
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return ["npm"];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const platform = {
|
|
478
|
+
__proto__: null,
|
|
479
|
+
commandExists: commandExists,
|
|
480
|
+
findCommandPath: findCommandPath,
|
|
481
|
+
getHomebrewCommandPaths: getHomebrewCommandPaths,
|
|
482
|
+
getPlatform: getPlatform,
|
|
483
|
+
getRecommendedInstallMethods: getRecommendedInstallMethods,
|
|
484
|
+
getTermuxPrefix: getTermuxPrefix,
|
|
485
|
+
getWSLDistro: getWSLDistro,
|
|
486
|
+
getWSLInfo: getWSLInfo,
|
|
487
|
+
isTermux: isTermux,
|
|
488
|
+
isWSL: isWSL,
|
|
489
|
+
isWindows: isWindows,
|
|
490
|
+
normalizeTomlPath: normalizeTomlPath,
|
|
491
|
+
shouldUseSudoForGlobalInstall: shouldUseSudoForGlobalInstall,
|
|
492
|
+
wrapCommandWithSudo: wrapCommandWithSudo
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
class FileSystemError extends Error {
|
|
496
|
+
constructor(message, path, cause) {
|
|
497
|
+
super(message);
|
|
498
|
+
this.path = path;
|
|
499
|
+
this.cause = cause;
|
|
500
|
+
this.name = "FileSystemError";
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function exists(path) {
|
|
504
|
+
return existsSync(path);
|
|
505
|
+
}
|
|
506
|
+
function ensureDir(path) {
|
|
507
|
+
if (!existsSync(path)) {
|
|
508
|
+
mkdirSync(path, { recursive: true });
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function ensureFileDir(filePath) {
|
|
512
|
+
const dir = dirname(filePath);
|
|
513
|
+
ensureDir(dir);
|
|
514
|
+
}
|
|
515
|
+
function readFile(path, encoding = "utf-8") {
|
|
516
|
+
try {
|
|
517
|
+
return readFileSync(path, encoding);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
throw new FileSystemError(
|
|
520
|
+
`Failed to read file: ${path}`,
|
|
521
|
+
path,
|
|
522
|
+
error
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function writeFile(path, content, encoding = "utf-8") {
|
|
527
|
+
try {
|
|
528
|
+
ensureFileDir(path);
|
|
529
|
+
writeFileSync(path, content, encoding);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
throw new FileSystemError(
|
|
532
|
+
`Failed to write file: ${path}`,
|
|
533
|
+
path,
|
|
534
|
+
error
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function copyFile(src, dest) {
|
|
539
|
+
try {
|
|
540
|
+
ensureFileDir(dest);
|
|
541
|
+
copyFileSync(src, dest);
|
|
542
|
+
} catch (error) {
|
|
543
|
+
throw new FileSystemError(
|
|
544
|
+
`Failed to copy file from ${src} to ${dest}`,
|
|
545
|
+
src,
|
|
546
|
+
error
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function readDir(path) {
|
|
551
|
+
try {
|
|
552
|
+
return readdirSync(path);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
throw new FileSystemError(
|
|
555
|
+
`Failed to read directory: ${path}`,
|
|
556
|
+
path,
|
|
557
|
+
error
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function getStats(path) {
|
|
562
|
+
try {
|
|
563
|
+
return statSync(path);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
throw new FileSystemError(
|
|
566
|
+
`Failed to get stats for: ${path}`,
|
|
567
|
+
path,
|
|
568
|
+
error
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function removeFile(path) {
|
|
573
|
+
try {
|
|
574
|
+
if (exists(path)) {
|
|
575
|
+
unlinkSync(path);
|
|
576
|
+
}
|
|
577
|
+
} catch (error) {
|
|
578
|
+
throw new FileSystemError(
|
|
579
|
+
`Failed to remove file: ${path}`,
|
|
580
|
+
path,
|
|
581
|
+
error
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function copyDir(src, dest, options = {}) {
|
|
586
|
+
const { filter, overwrite = true } = options;
|
|
587
|
+
if (!exists(src)) {
|
|
588
|
+
throw new FileSystemError(`Source directory does not exist: ${src}`, src);
|
|
589
|
+
}
|
|
590
|
+
ensureDir(dest);
|
|
591
|
+
const entries = readDir(src);
|
|
592
|
+
for (const entry of entries) {
|
|
593
|
+
const srcPath = `${src}/${entry}`;
|
|
594
|
+
const destPath = `${dest}/${entry}`;
|
|
595
|
+
let stats;
|
|
596
|
+
try {
|
|
597
|
+
stats = lstatSync(srcPath);
|
|
598
|
+
} catch (error) {
|
|
599
|
+
if (error.code === "ENOENT") {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
604
|
+
if (filter && !filter(srcPath, stats)) {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
if (stats.isSymbolicLink()) {
|
|
608
|
+
try {
|
|
609
|
+
existsSync(srcPath);
|
|
610
|
+
const targetStats = statSync(srcPath);
|
|
611
|
+
if (targetStats.isDirectory()) {
|
|
612
|
+
copyDir(srcPath, destPath, options);
|
|
613
|
+
} else {
|
|
614
|
+
if (!overwrite && exists(destPath)) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
copyFile(srcPath, destPath);
|
|
618
|
+
}
|
|
619
|
+
} catch {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (stats.isDirectory()) {
|
|
625
|
+
copyDir(srcPath, destPath, options);
|
|
626
|
+
} else {
|
|
627
|
+
if (!overwrite && exists(destPath)) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
copyFile(srcPath, destPath);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
async function isExecutable(path) {
|
|
635
|
+
try {
|
|
636
|
+
if (!exists(path)) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
const stats = getStats(path);
|
|
640
|
+
if (!stats.isFile()) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
if (!isWindows()) {
|
|
644
|
+
const mode = stats.mode;
|
|
645
|
+
const executePermission = 73;
|
|
646
|
+
return (mode & executePermission) !== 0;
|
|
647
|
+
}
|
|
648
|
+
const isWinExecutable = path.endsWith(".exe") || path.endsWith(".cmd") || path.endsWith(".bat");
|
|
649
|
+
return isWinExecutable || !path.includes(".");
|
|
650
|
+
} catch {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async function remove(path) {
|
|
655
|
+
try {
|
|
656
|
+
if (!exists(path)) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const stats = getStats(path);
|
|
660
|
+
if (stats.isDirectory()) {
|
|
661
|
+
const entries = readDir(path);
|
|
662
|
+
for (const entry of entries) {
|
|
663
|
+
await remove(`${path}/${entry}`);
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
if (rmSync) {
|
|
667
|
+
rmSync(path, { recursive: true, force: true });
|
|
668
|
+
} else if (rmdirSync) {
|
|
669
|
+
rmdirSync(path);
|
|
670
|
+
}
|
|
671
|
+
} catch (error) {
|
|
672
|
+
throw new FileSystemError(
|
|
673
|
+
`Failed to remove directory: ${path}`,
|
|
674
|
+
path,
|
|
675
|
+
error
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
} else {
|
|
679
|
+
removeFile(path);
|
|
680
|
+
}
|
|
681
|
+
} catch (error) {
|
|
682
|
+
throw new FileSystemError(
|
|
683
|
+
`Failed to remove: ${path}`,
|
|
684
|
+
path,
|
|
685
|
+
error
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function readJsonConfig(path, options = {}) {
|
|
691
|
+
const { defaultValue = null, validate, sanitize } = options;
|
|
692
|
+
if (!exists(path)) {
|
|
693
|
+
return defaultValue;
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
const content = readFile(path);
|
|
697
|
+
const data = JSON.parse(content);
|
|
698
|
+
if (validate && !validate(data)) {
|
|
699
|
+
console.log(`Invalid configuration: ${path}`);
|
|
700
|
+
return defaultValue;
|
|
701
|
+
}
|
|
702
|
+
if (sanitize) {
|
|
703
|
+
return sanitize(data);
|
|
704
|
+
}
|
|
705
|
+
return data;
|
|
706
|
+
} catch (error) {
|
|
707
|
+
console.error(`Failed to parse JSON: ${path}`, error);
|
|
708
|
+
return defaultValue;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function writeJsonConfig(path, data, options = {}) {
|
|
712
|
+
const { pretty = true, backup = false, backupDir } = options;
|
|
713
|
+
if (backup && exists(path)) {
|
|
714
|
+
backupJsonConfig(path, backupDir);
|
|
715
|
+
}
|
|
716
|
+
const content = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
717
|
+
writeFile(path, content);
|
|
718
|
+
}
|
|
719
|
+
function backupJsonConfig(path, backupDir) {
|
|
720
|
+
if (!exists(path)) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
|
|
724
|
+
const fileName = path.split("/").pop() || "config.json";
|
|
725
|
+
const baseDir = backupDir || join(path, "..", "backup");
|
|
726
|
+
const backupPath = join(baseDir, `${fileName}.backup_${timestamp}`);
|
|
727
|
+
try {
|
|
728
|
+
ensureDir(baseDir);
|
|
729
|
+
copyFile(path, backupPath);
|
|
730
|
+
return backupPath;
|
|
731
|
+
} catch (error) {
|
|
732
|
+
console.error("Failed to backup config", error);
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const jsonConfig = {
|
|
738
|
+
__proto__: null,
|
|
739
|
+
backupJsonConfig: backupJsonConfig,
|
|
740
|
+
readJsonConfig: readJsonConfig,
|
|
741
|
+
writeJsonConfig: writeJsonConfig
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
function mergeArraysUnique(arr1, arr2) {
|
|
745
|
+
const combined = [...arr1 || [], ...arr2 || []];
|
|
746
|
+
return [...new Set(combined)];
|
|
747
|
+
}
|
|
748
|
+
function isPlainObject(value) {
|
|
749
|
+
return value !== null && typeof value === "object" && value.constructor === Object && Object.prototype.toString.call(value) === "[object Object]";
|
|
750
|
+
}
|
|
751
|
+
function deepMerge(target, source, options = {}) {
|
|
752
|
+
const { mergeArrays = false, arrayMergeStrategy = "replace" } = options;
|
|
753
|
+
const result = { ...target };
|
|
754
|
+
for (const key in source) {
|
|
755
|
+
const sourceValue = source[key];
|
|
756
|
+
const targetValue = result[key];
|
|
757
|
+
if (sourceValue === void 0) {
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
|
|
761
|
+
result[key] = deepMerge(targetValue, sourceValue, options);
|
|
762
|
+
} else if (Array.isArray(sourceValue)) {
|
|
763
|
+
if (!mergeArrays || !Array.isArray(targetValue)) {
|
|
764
|
+
result[key] = sourceValue;
|
|
765
|
+
} else {
|
|
766
|
+
switch (arrayMergeStrategy) {
|
|
767
|
+
case "concat":
|
|
768
|
+
result[key] = [...targetValue, ...sourceValue];
|
|
769
|
+
break;
|
|
770
|
+
case "unique":
|
|
771
|
+
result[key] = mergeArraysUnique(targetValue, sourceValue);
|
|
772
|
+
break;
|
|
773
|
+
case "replace":
|
|
774
|
+
default:
|
|
775
|
+
result[key] = sourceValue;
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
} else {
|
|
780
|
+
result[key] = sourceValue;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return result;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function readMcpConfig() {
|
|
787
|
+
return readJsonConfig(ClAUDE_CONFIG_FILE);
|
|
788
|
+
}
|
|
789
|
+
function writeMcpConfig(config) {
|
|
790
|
+
writeJsonConfig(ClAUDE_CONFIG_FILE, config);
|
|
791
|
+
}
|
|
792
|
+
function addCompletedOnboarding() {
|
|
793
|
+
try {
|
|
794
|
+
let config = readMcpConfig();
|
|
795
|
+
if (!config) {
|
|
796
|
+
config = { mcpServers: {} };
|
|
797
|
+
}
|
|
798
|
+
if (config.hasCompletedOnboarding === true) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
config.hasCompletedOnboarding = true;
|
|
802
|
+
writeMcpConfig(config);
|
|
803
|
+
} catch (error) {
|
|
804
|
+
console.error("Failed to add onboarding flag", error);
|
|
805
|
+
throw error;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
function setPrimaryApiKey() {
|
|
809
|
+
try {
|
|
810
|
+
let config = readJsonConfig(CLAUDE_VSC_CONFIG_FILE);
|
|
811
|
+
if (!config) {
|
|
812
|
+
config = {};
|
|
813
|
+
}
|
|
814
|
+
config.primaryApiKey = "zcf";
|
|
815
|
+
writeJsonConfig(CLAUDE_VSC_CONFIG_FILE, config);
|
|
816
|
+
} catch (error) {
|
|
817
|
+
ensureI18nInitialized();
|
|
818
|
+
console.error(i18n.t("api:primaryApiKeySetFailed"), error);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const claudeConfig = {
|
|
823
|
+
__proto__: null,
|
|
824
|
+
addCompletedOnboarding: addCompletedOnboarding,
|
|
825
|
+
readMcpConfig: readMcpConfig,
|
|
826
|
+
setPrimaryApiKey: setPrimaryApiKey,
|
|
827
|
+
writeMcpConfig: writeMcpConfig
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
const MODEL_ENV_KEYS = [
|
|
831
|
+
"ANTHROPIC_MODEL",
|
|
832
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
833
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
834
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
835
|
+
// Deprecated but still cleaned to avoid stale values
|
|
836
|
+
"ANTHROPIC_SMALL_FAST_MODEL"
|
|
837
|
+
];
|
|
838
|
+
function clearModelEnv(env) {
|
|
839
|
+
for (const key of MODEL_ENV_KEYS) {
|
|
840
|
+
delete env[key];
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function ensureClaudeDir() {
|
|
845
|
+
ensureDir(CLAUDE_DIR);
|
|
846
|
+
}
|
|
847
|
+
function backupExistingConfig() {
|
|
848
|
+
if (!exists(CLAUDE_DIR)) {
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
|
|
852
|
+
const backupBaseDir = join(CLAUDE_DIR, "backup");
|
|
853
|
+
const backupDir = join(backupBaseDir, `backup_${timestamp}`);
|
|
854
|
+
ensureDir(backupDir);
|
|
855
|
+
const filter = (path) => {
|
|
856
|
+
return !path.includes("/backup");
|
|
857
|
+
};
|
|
858
|
+
copyDir(CLAUDE_DIR, backupDir, { filter });
|
|
859
|
+
return backupDir;
|
|
860
|
+
}
|
|
861
|
+
function copyConfigFiles(onlyMd = false) {
|
|
862
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
863
|
+
const distDir = dirname(dirname(currentFilePath));
|
|
864
|
+
const rootDir = dirname(distDir);
|
|
865
|
+
const baseTemplateDir = join(rootDir, "templates", "claude-code");
|
|
866
|
+
if (!onlyMd) {
|
|
867
|
+
const baseSettingsPath = join(baseTemplateDir, "common", "settings.json");
|
|
868
|
+
const destSettingsPath = join(CLAUDE_DIR, "settings.json");
|
|
869
|
+
if (exists(baseSettingsPath)) {
|
|
870
|
+
mergeSettingsFile(baseSettingsPath, destSettingsPath);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
function getDefaultSettings() {
|
|
875
|
+
try {
|
|
876
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
877
|
+
const distDir = dirname(dirname(currentFilePath));
|
|
878
|
+
const rootDir = dirname(distDir);
|
|
879
|
+
const templateSettingsPath = join(rootDir, "templates", "claude-code", "common", "settings.json");
|
|
880
|
+
return readJsonConfig(templateSettingsPath) || {};
|
|
881
|
+
} catch (error) {
|
|
882
|
+
console.error("Failed to read template settings", error);
|
|
883
|
+
return {};
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function configureApi(apiConfig) {
|
|
887
|
+
if (!apiConfig)
|
|
888
|
+
return null;
|
|
889
|
+
let settings = getDefaultSettings();
|
|
890
|
+
const existingSettings = readJsonConfig(SETTINGS_FILE);
|
|
891
|
+
if (existingSettings) {
|
|
892
|
+
settings = deepMerge(settings, existingSettings);
|
|
893
|
+
}
|
|
894
|
+
if (!settings.env) {
|
|
895
|
+
settings.env = {};
|
|
896
|
+
}
|
|
897
|
+
if (apiConfig.authType === "api_key") {
|
|
898
|
+
settings.env.ANTHROPIC_API_KEY = apiConfig.key;
|
|
899
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
900
|
+
} else if (apiConfig.authType === "auth_token") {
|
|
901
|
+
settings.env.ANTHROPIC_AUTH_TOKEN = apiConfig.key;
|
|
902
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
903
|
+
}
|
|
904
|
+
if (apiConfig.url) {
|
|
905
|
+
settings.env.ANTHROPIC_BASE_URL = apiConfig.url;
|
|
906
|
+
}
|
|
907
|
+
writeJsonConfig(SETTINGS_FILE, settings);
|
|
908
|
+
if (apiConfig.authType) {
|
|
909
|
+
try {
|
|
910
|
+
setPrimaryApiKey();
|
|
911
|
+
} catch (error) {
|
|
912
|
+
ensureI18nInitialized();
|
|
913
|
+
console.error(i18n.t("api:primaryApiKeySetFailed"), error);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
addCompletedOnboarding();
|
|
918
|
+
} catch (error) {
|
|
919
|
+
console.error("Failed to set onboarding flag", error);
|
|
920
|
+
}
|
|
921
|
+
return apiConfig;
|
|
922
|
+
}
|
|
923
|
+
function mergeConfigs(sourceFile, targetFile) {
|
|
924
|
+
if (!exists(sourceFile))
|
|
925
|
+
return;
|
|
926
|
+
const target = readJsonConfig(targetFile) || {};
|
|
927
|
+
const source = readJsonConfig(sourceFile) || {};
|
|
928
|
+
const merged = deepMerge(target, source);
|
|
929
|
+
writeJsonConfig(targetFile, merged);
|
|
930
|
+
}
|
|
931
|
+
function updateCustomModel(primaryModel, haikuModel, sonnetModel, opusModel) {
|
|
932
|
+
if (!primaryModel?.trim() && !haikuModel?.trim() && !sonnetModel?.trim() && !opusModel?.trim()) {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
let settings = getDefaultSettings();
|
|
936
|
+
const existingSettings = readJsonConfig(SETTINGS_FILE);
|
|
937
|
+
if (existingSettings) {
|
|
938
|
+
settings = existingSettings;
|
|
939
|
+
}
|
|
940
|
+
delete settings.model;
|
|
941
|
+
settings.env = settings.env || {};
|
|
942
|
+
clearModelEnv(settings.env);
|
|
943
|
+
if (primaryModel?.trim()) {
|
|
944
|
+
settings.env.ANTHROPIC_MODEL = primaryModel.trim();
|
|
945
|
+
}
|
|
946
|
+
if (haikuModel?.trim())
|
|
947
|
+
settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = haikuModel.trim();
|
|
948
|
+
if (sonnetModel?.trim())
|
|
949
|
+
settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL = sonnetModel.trim();
|
|
950
|
+
if (opusModel?.trim())
|
|
951
|
+
settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = opusModel.trim();
|
|
952
|
+
writeJsonConfig(SETTINGS_FILE, settings);
|
|
953
|
+
}
|
|
954
|
+
function updateDefaultModel(model) {
|
|
955
|
+
let settings = getDefaultSettings();
|
|
956
|
+
const existingSettings = readJsonConfig(SETTINGS_FILE);
|
|
957
|
+
if (existingSettings) {
|
|
958
|
+
settings = existingSettings;
|
|
959
|
+
}
|
|
960
|
+
if (!settings.env) {
|
|
961
|
+
settings.env = {};
|
|
962
|
+
}
|
|
963
|
+
if (model !== "custom") {
|
|
964
|
+
clearModelEnv(settings.env);
|
|
965
|
+
}
|
|
966
|
+
if (model === "default" || model === "custom") {
|
|
967
|
+
delete settings.model;
|
|
968
|
+
} else {
|
|
969
|
+
settings.model = model;
|
|
970
|
+
}
|
|
971
|
+
writeJsonConfig(SETTINGS_FILE, settings);
|
|
972
|
+
}
|
|
973
|
+
function mergeSettingsFile(templatePath, targetPath) {
|
|
974
|
+
try {
|
|
975
|
+
const templateSettings = readJsonConfig(templatePath);
|
|
976
|
+
if (!templateSettings) {
|
|
977
|
+
console.error("Failed to read template settings");
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (!exists(targetPath)) {
|
|
981
|
+
writeJsonConfig(targetPath, templateSettings);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const existingSettings = readJsonConfig(targetPath) || {};
|
|
985
|
+
const mergedEnv = {
|
|
986
|
+
...templateSettings.env || {},
|
|
987
|
+
// Template env vars first
|
|
988
|
+
...existingSettings.env || {}
|
|
989
|
+
// User's env vars override (preserving API keys, etc.)
|
|
990
|
+
};
|
|
991
|
+
const mergedSettings = deepMerge(templateSettings, existingSettings, {
|
|
992
|
+
mergeArrays: true,
|
|
993
|
+
arrayMergeStrategy: "unique"
|
|
994
|
+
});
|
|
995
|
+
mergedSettings.env = mergedEnv;
|
|
996
|
+
if (mergedSettings.permissions && mergedSettings.permissions.allow) {
|
|
997
|
+
mergedSettings.permissions.allow = [
|
|
998
|
+
.../* @__PURE__ */ new Set([
|
|
999
|
+
...templateSettings.permissions?.allow ?? [],
|
|
1000
|
+
...existingSettings.permissions?.allow ?? []
|
|
1001
|
+
])
|
|
1002
|
+
];
|
|
1003
|
+
}
|
|
1004
|
+
writeJsonConfig(targetPath, mergedSettings);
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
console.error("Failed to merge settings", error);
|
|
1007
|
+
if (exists(targetPath)) {
|
|
1008
|
+
console.log("Preserving existing settings");
|
|
1009
|
+
} else {
|
|
1010
|
+
copyFile(templatePath, targetPath);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
function getExistingModelConfig() {
|
|
1015
|
+
const settings = readJsonConfig(SETTINGS_FILE);
|
|
1016
|
+
if (!settings) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
const hasModelEnv = MODEL_ENV_KEYS.some((key) => settings.env?.[key]);
|
|
1020
|
+
if (hasModelEnv) {
|
|
1021
|
+
return "custom";
|
|
1022
|
+
}
|
|
1023
|
+
if (!settings.model) {
|
|
1024
|
+
return "default";
|
|
1025
|
+
}
|
|
1026
|
+
const validModels = ["opus", "sonnet", "sonnet[1m]"];
|
|
1027
|
+
if (validModels.includes(settings.model)) {
|
|
1028
|
+
return settings.model;
|
|
1029
|
+
}
|
|
1030
|
+
return "default";
|
|
1031
|
+
}
|
|
1032
|
+
function getExistingApiConfig() {
|
|
1033
|
+
const settings = readJsonConfig(SETTINGS_FILE);
|
|
1034
|
+
if (!settings || !settings.env) {
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
const { ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL } = settings.env;
|
|
1038
|
+
if (!ANTHROPIC_BASE_URL && !ANTHROPIC_API_KEY && !ANTHROPIC_AUTH_TOKEN) {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
let authType;
|
|
1042
|
+
let key;
|
|
1043
|
+
if (ANTHROPIC_AUTH_TOKEN) {
|
|
1044
|
+
authType = "auth_token";
|
|
1045
|
+
key = ANTHROPIC_AUTH_TOKEN;
|
|
1046
|
+
} else if (ANTHROPIC_API_KEY) {
|
|
1047
|
+
authType = "api_key";
|
|
1048
|
+
key = ANTHROPIC_API_KEY;
|
|
1049
|
+
}
|
|
1050
|
+
return {
|
|
1051
|
+
url: ANTHROPIC_BASE_URL || "",
|
|
1052
|
+
key: key || "",
|
|
1053
|
+
authType
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
function applyAiLanguageDirective(aiOutputLang) {
|
|
1057
|
+
const claudeFile = join(CLAUDE_DIR, "CLAUDE.md");
|
|
1058
|
+
let directive = "";
|
|
1059
|
+
if (aiOutputLang === "custom") {
|
|
1060
|
+
return;
|
|
1061
|
+
} else if (AI_OUTPUT_LANGUAGES[aiOutputLang]) {
|
|
1062
|
+
directive = AI_OUTPUT_LANGUAGES[aiOutputLang].directive;
|
|
1063
|
+
} else {
|
|
1064
|
+
directive = `Always respond in ${aiOutputLang}`;
|
|
1065
|
+
}
|
|
1066
|
+
writeFile(claudeFile, directive);
|
|
1067
|
+
}
|
|
1068
|
+
function switchToOfficialLogin() {
|
|
1069
|
+
try {
|
|
1070
|
+
ensureI18nInitialized();
|
|
1071
|
+
const settings = readJsonConfig(SETTINGS_FILE) || {};
|
|
1072
|
+
if (settings.env) {
|
|
1073
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
1074
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
1075
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
1076
|
+
}
|
|
1077
|
+
writeJsonConfig(SETTINGS_FILE, settings);
|
|
1078
|
+
const vscConfig = readJsonConfig(CLAUDE_VSC_CONFIG_FILE);
|
|
1079
|
+
if (vscConfig) {
|
|
1080
|
+
delete vscConfig.primaryApiKey;
|
|
1081
|
+
writeJsonConfig(CLAUDE_VSC_CONFIG_FILE, vscConfig);
|
|
1082
|
+
}
|
|
1083
|
+
console.log(i18n.t("api:officialLoginConfigured"));
|
|
1084
|
+
return true;
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
ensureI18nInitialized();
|
|
1087
|
+
console.error(i18n.t("api:officialLoginFailed"), error);
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async function promptApiConfigurationAction() {
|
|
1092
|
+
ensureI18nInitialized();
|
|
1093
|
+
const existingConfig = getExistingApiConfig();
|
|
1094
|
+
if (!existingConfig) {
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
console.log(`
|
|
1098
|
+
${ansis.blue(`\u2139 ${i18n.t("api:existingApiConfig")}`)}`);
|
|
1099
|
+
console.log(ansis.gray(` ${i18n.t("api:apiConfigUrl")}: ${existingConfig.url || "N/A"}`));
|
|
1100
|
+
console.log(ansis.gray(` ${i18n.t("api:apiConfigKey")}: ${existingConfig.key ? `***${existingConfig.key.slice(-4)}` : "N/A"}`));
|
|
1101
|
+
console.log(ansis.gray(` ${i18n.t("api:apiConfigAuthType")}: ${existingConfig.authType || "N/A"}
|
|
1102
|
+
`));
|
|
1103
|
+
const { choice } = await inquirer.prompt({
|
|
1104
|
+
type: "list",
|
|
1105
|
+
name: "choice",
|
|
1106
|
+
message: i18n.t("api:selectCustomConfigAction"),
|
|
1107
|
+
choices: [
|
|
1108
|
+
{
|
|
1109
|
+
name: i18n.t("api:modifyPartialConfig"),
|
|
1110
|
+
value: "modify-partial"
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
name: i18n.t("api:modifyAllConfig"),
|
|
1114
|
+
value: "modify-all"
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
name: i18n.t("api:keepExistingConfig"),
|
|
1118
|
+
value: "keep-existing"
|
|
1119
|
+
}
|
|
1120
|
+
]
|
|
1121
|
+
});
|
|
1122
|
+
return choice || null;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
const config = {
|
|
1126
|
+
__proto__: null,
|
|
1127
|
+
applyAiLanguageDirective: applyAiLanguageDirective,
|
|
1128
|
+
backupExistingConfig: backupExistingConfig,
|
|
1129
|
+
configureApi: configureApi,
|
|
1130
|
+
copyConfigFiles: copyConfigFiles,
|
|
1131
|
+
ensureClaudeDir: ensureClaudeDir,
|
|
1132
|
+
getExistingApiConfig: getExistingApiConfig,
|
|
1133
|
+
getExistingModelConfig: getExistingModelConfig,
|
|
1134
|
+
mergeConfigs: mergeConfigs,
|
|
1135
|
+
mergeSettingsFile: mergeSettingsFile,
|
|
1136
|
+
promptApiConfigurationAction: promptApiConfigurationAction,
|
|
1137
|
+
switchToOfficialLogin: switchToOfficialLogin,
|
|
1138
|
+
updateCustomModel: updateCustomModel,
|
|
1139
|
+
updateDefaultModel: updateDefaultModel
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
const version = "0.1.0";
|
|
1143
|
+
const homepage = "https://github.com/xwm111/ccs";
|
|
1144
|
+
|
|
1145
|
+
const togglePrompt = toggleModule?.default?.default || toggleModule?.default || toggleModule;
|
|
1146
|
+
async function promptBoolean(options) {
|
|
1147
|
+
const { message, defaultValue = false, theme } = options;
|
|
1148
|
+
return await togglePrompt({
|
|
1149
|
+
message,
|
|
1150
|
+
default: defaultValue,
|
|
1151
|
+
...theme ? { theme } : {}
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const execAsync = promisify(exec$1);
|
|
1156
|
+
async function getInstalledVersion(command, maxRetries = 3) {
|
|
1157
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1158
|
+
try {
|
|
1159
|
+
let stdout;
|
|
1160
|
+
try {
|
|
1161
|
+
const result = await execAsync(`${command} -v`);
|
|
1162
|
+
stdout = result.stdout;
|
|
1163
|
+
} catch {
|
|
1164
|
+
const result = await execAsync(`${command} --version`);
|
|
1165
|
+
stdout = result.stdout;
|
|
1166
|
+
}
|
|
1167
|
+
const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
|
|
1168
|
+
return versionMatch ? versionMatch[1] : null;
|
|
1169
|
+
} catch {
|
|
1170
|
+
if (attempt === maxRetries) {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
await new Promise((resolve) => setTimeout(resolve, 100 * attempt));
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
async function getLatestVersion(packageName, maxRetries = 3) {
|
|
1179
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1180
|
+
try {
|
|
1181
|
+
const { stdout } = await execAsync(`npm view ${packageName} version`);
|
|
1182
|
+
return stdout.trim();
|
|
1183
|
+
} catch {
|
|
1184
|
+
if (attempt === maxRetries) {
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
await new Promise((resolve) => setTimeout(resolve, 200 * attempt));
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
async function getClaudeCodeInstallationSource() {
|
|
1193
|
+
if (getPlatform() !== "macos") {
|
|
1194
|
+
const commandPath2 = await findCommandPath("claude");
|
|
1195
|
+
return {
|
|
1196
|
+
isHomebrew: false,
|
|
1197
|
+
commandPath: commandPath2,
|
|
1198
|
+
source: commandPath2 ? "other" : "not-found"
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
const commandPath = await findCommandPath("claude");
|
|
1202
|
+
if (!commandPath) {
|
|
1203
|
+
return { isHomebrew: false, commandPath: null, source: "not-found" };
|
|
1204
|
+
}
|
|
1205
|
+
const isFromCaskroom = commandPath.includes("/Caskroom/claude-code/");
|
|
1206
|
+
if (isFromCaskroom) {
|
|
1207
|
+
return { isHomebrew: true, commandPath, source: "homebrew-cask" };
|
|
1208
|
+
}
|
|
1209
|
+
try {
|
|
1210
|
+
const { stdout: realPath } = await execAsync(`readlink -f "${commandPath}" 2>/dev/null || realpath "${commandPath}" 2>/dev/null || echo "${commandPath}"`);
|
|
1211
|
+
const resolvedPath = realPath.trim();
|
|
1212
|
+
if (resolvedPath.includes("/Caskroom/claude-code/")) {
|
|
1213
|
+
return { isHomebrew: true, commandPath, source: "homebrew-cask" };
|
|
1214
|
+
}
|
|
1215
|
+
} catch {
|
|
1216
|
+
}
|
|
1217
|
+
if (commandPath.includes("/node_modules/") || commandPath.includes("/npm/") || commandPath.includes("/Cellar/node/")) {
|
|
1218
|
+
return { isHomebrew: false, commandPath, source: "npm" };
|
|
1219
|
+
}
|
|
1220
|
+
return { isHomebrew: false, commandPath, source: "other" };
|
|
1221
|
+
}
|
|
1222
|
+
async function detectAllClaudeCodeInstallations() {
|
|
1223
|
+
const installations = [];
|
|
1224
|
+
const checkedPaths = /* @__PURE__ */ new Set();
|
|
1225
|
+
const activeCommandPath = await findCommandPath("claude");
|
|
1226
|
+
let activeResolvedPath = null;
|
|
1227
|
+
if (activeCommandPath) {
|
|
1228
|
+
try {
|
|
1229
|
+
const { stdout } = await execAsync(`readlink -f "${activeCommandPath}" 2>/dev/null || realpath "${activeCommandPath}" 2>/dev/null || echo "${activeCommandPath}"`);
|
|
1230
|
+
activeResolvedPath = stdout.trim();
|
|
1231
|
+
} catch {
|
|
1232
|
+
activeResolvedPath = activeCommandPath;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
async function getVersionFromPath(path) {
|
|
1236
|
+
try {
|
|
1237
|
+
const { stdout } = await execAsync(`"${path}" -v 2>/dev/null || "${path}" --version 2>/dev/null`);
|
|
1238
|
+
const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
|
|
1239
|
+
return versionMatch ? versionMatch[1] : null;
|
|
1240
|
+
} catch {
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function isActivePath(path) {
|
|
1245
|
+
if (!activeResolvedPath)
|
|
1246
|
+
return false;
|
|
1247
|
+
return path === activeResolvedPath || path === activeCommandPath;
|
|
1248
|
+
}
|
|
1249
|
+
async function addInstallation(path, source) {
|
|
1250
|
+
let resolvedPath = path;
|
|
1251
|
+
try {
|
|
1252
|
+
const { stdout } = await execAsync(`readlink -f "${path}" 2>/dev/null || realpath "${path}" 2>/dev/null || echo "${path}"`);
|
|
1253
|
+
resolvedPath = stdout.trim();
|
|
1254
|
+
} catch {
|
|
1255
|
+
}
|
|
1256
|
+
if (checkedPaths.has(resolvedPath))
|
|
1257
|
+
return;
|
|
1258
|
+
checkedPaths.add(resolvedPath);
|
|
1259
|
+
if (!nodeFs.existsSync(path))
|
|
1260
|
+
return;
|
|
1261
|
+
const version = await getVersionFromPath(path);
|
|
1262
|
+
installations.push({
|
|
1263
|
+
source,
|
|
1264
|
+
path,
|
|
1265
|
+
version,
|
|
1266
|
+
isActive: isActivePath(path) || isActivePath(resolvedPath)
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
if (activeCommandPath && nodeFs.existsSync(activeCommandPath)) {
|
|
1270
|
+
let activeSource = "other";
|
|
1271
|
+
if (activeResolvedPath?.includes("/Caskroom/claude-code/")) {
|
|
1272
|
+
activeSource = "homebrew-cask";
|
|
1273
|
+
} 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/")) {
|
|
1274
|
+
activeSource = "npm";
|
|
1275
|
+
}
|
|
1276
|
+
await addInstallation(activeCommandPath, activeSource);
|
|
1277
|
+
}
|
|
1278
|
+
if (getPlatform() === "macos") {
|
|
1279
|
+
const homebrewPaths = await getHomebrewCommandPaths("claude");
|
|
1280
|
+
for (const path of homebrewPaths) {
|
|
1281
|
+
if (path.includes("/Caskroom/claude-code/")) {
|
|
1282
|
+
await addInstallation(path, "homebrew-cask");
|
|
1283
|
+
} else if (path.includes("/Cellar/node/")) {
|
|
1284
|
+
await addInstallation(path, "npm-homebrew-node");
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
try {
|
|
1288
|
+
await execAsync("brew list --cask claude-code");
|
|
1289
|
+
const homebrewPrefixes = ["/opt/homebrew", "/usr/local"];
|
|
1290
|
+
for (const prefix of homebrewPrefixes) {
|
|
1291
|
+
const caskroomPath = `${prefix}/Caskroom/claude-code`;
|
|
1292
|
+
if (nodeFs.existsSync(caskroomPath)) {
|
|
1293
|
+
const versions = nodeFs.readdirSync(caskroomPath).filter((v) => !v.startsWith("."));
|
|
1294
|
+
for (const version of versions) {
|
|
1295
|
+
const claudePath = `${caskroomPath}/${version}/claude`;
|
|
1296
|
+
await addInstallation(claudePath, "homebrew-cask");
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
} catch {
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const npmGlobalPaths = [
|
|
1304
|
+
"/usr/local/bin/claude",
|
|
1305
|
+
"/usr/bin/claude",
|
|
1306
|
+
`${process.env.HOME}/.npm-global/bin/claude`,
|
|
1307
|
+
`${process.env.HOME}/.local/bin/claude`
|
|
1308
|
+
];
|
|
1309
|
+
for (const path of npmGlobalPaths) {
|
|
1310
|
+
if (nodeFs.existsSync(path)) {
|
|
1311
|
+
let resolvedPath = path;
|
|
1312
|
+
try {
|
|
1313
|
+
const { stdout } = await execAsync(`readlink -f "${path}" 2>/dev/null || realpath "${path}" 2>/dev/null || echo "${path}"`);
|
|
1314
|
+
resolvedPath = stdout.trim();
|
|
1315
|
+
} catch {
|
|
1316
|
+
}
|
|
1317
|
+
if (resolvedPath.includes("/node_modules/") || resolvedPath.includes("/npm/")) {
|
|
1318
|
+
await addInstallation(path, "npm");
|
|
1319
|
+
} else if (resolvedPath.includes("/Caskroom/")) ; else {
|
|
1320
|
+
await addInstallation(path, "other");
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
if (getPlatform() === "macos") {
|
|
1325
|
+
const homebrewPrefixes = ["/opt/homebrew", "/usr/local"];
|
|
1326
|
+
for (const prefix of homebrewPrefixes) {
|
|
1327
|
+
const cellarNodePath = `${prefix}/Cellar/node`;
|
|
1328
|
+
if (nodeFs.existsSync(cellarNodePath)) {
|
|
1329
|
+
try {
|
|
1330
|
+
const versions = nodeFs.readdirSync(cellarNodePath);
|
|
1331
|
+
for (const version of versions) {
|
|
1332
|
+
const claudePath = `${cellarNodePath}/${version}/bin/claude`;
|
|
1333
|
+
await addInstallation(claudePath, "npm-homebrew-node");
|
|
1334
|
+
}
|
|
1335
|
+
} catch {
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
return installations;
|
|
1341
|
+
}
|
|
1342
|
+
async function checkDuplicateInstallations() {
|
|
1343
|
+
const installations = await detectAllClaudeCodeInstallations();
|
|
1344
|
+
const activeInstallation = installations.find((i) => i.isActive) || null;
|
|
1345
|
+
const inactiveInstallations = installations.filter((i) => !i.isActive);
|
|
1346
|
+
const homebrewInstallation = installations.find((i) => i.source === "homebrew-cask") || null;
|
|
1347
|
+
const npmInstallation = installations.find((i) => i.source === "npm" || i.source === "npm-homebrew-node") || null;
|
|
1348
|
+
const hasDuplicates = homebrewInstallation !== null && npmInstallation !== null;
|
|
1349
|
+
const recommendation = hasDuplicates ? "remove-npm" : "none";
|
|
1350
|
+
return {
|
|
1351
|
+
hasDuplicates,
|
|
1352
|
+
installations,
|
|
1353
|
+
activeInstallation,
|
|
1354
|
+
inactiveInstallations,
|
|
1355
|
+
homebrewInstallation,
|
|
1356
|
+
npmInstallation,
|
|
1357
|
+
recommendation
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function getSourceDisplayName(source, i18n) {
|
|
1361
|
+
const sourceMap = {
|
|
1362
|
+
"homebrew-cask": i18n.t("installation:sourceHomebrewCask"),
|
|
1363
|
+
"npm": i18n.t("installation:sourceNpm"),
|
|
1364
|
+
"npm-homebrew-node": i18n.t("installation:sourceNpmHomebrewNode"),
|
|
1365
|
+
"curl": i18n.t("installation:sourceCurl"),
|
|
1366
|
+
"other": i18n.t("installation:sourceOther")
|
|
1367
|
+
};
|
|
1368
|
+
return sourceMap[source] || source;
|
|
1369
|
+
}
|
|
1370
|
+
async function performNpmRemovalAndActivateHomebrew(_npmInstallation, homebrewInstallation, tinyExec, i18n, ansis) {
|
|
1371
|
+
const ora = (await import('ora')).default;
|
|
1372
|
+
const spinner = ora(i18n.t("installation:removingDuplicateInstallation")).start();
|
|
1373
|
+
try {
|
|
1374
|
+
const { wrapCommandWithSudo } = await Promise.resolve().then(function () { return platform; });
|
|
1375
|
+
const { command, args, usedSudo } = wrapCommandWithSudo("npm", ["uninstall", "-g", "@anthropic-ai/claude-code"]);
|
|
1376
|
+
if (usedSudo) {
|
|
1377
|
+
spinner.info(i18n.t("installation:usingSudo"));
|
|
1378
|
+
spinner.start();
|
|
1379
|
+
}
|
|
1380
|
+
await tinyExec(command, args);
|
|
1381
|
+
spinner.succeed(i18n.t("installation:duplicateRemoved"));
|
|
1382
|
+
if (homebrewInstallation && !homebrewInstallation.isActive) {
|
|
1383
|
+
console.log("");
|
|
1384
|
+
console.log(ansis.cyan(`\u{1F517} ${i18n.t("installation:activatingHomebrew")}`));
|
|
1385
|
+
const { createHomebrewSymlink } = await import('./installer.mjs');
|
|
1386
|
+
const symlinkResult = await createHomebrewSymlink("claude", homebrewInstallation.path);
|
|
1387
|
+
if (symlinkResult.success) {
|
|
1388
|
+
console.log(ansis.green(`\u2714 ${i18n.t("installation:symlinkCreated", { path: symlinkResult.symlinkPath || "/usr/local/bin/claude" })}`));
|
|
1389
|
+
} else {
|
|
1390
|
+
console.log(ansis.yellow(`\u26A0 ${i18n.t("installation:manualSymlinkHint")}`));
|
|
1391
|
+
if (symlinkResult.error) {
|
|
1392
|
+
console.log(ansis.gray(` ${symlinkResult.error}`));
|
|
1393
|
+
} else {
|
|
1394
|
+
const homebrewBin = nodeFs.existsSync("/opt/homebrew/bin") ? "/opt/homebrew/bin" : "/usr/local/bin";
|
|
1395
|
+
console.log(ansis.gray(` sudo ln -sf "${homebrewInstallation.path}" ${homebrewBin}/claude`));
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return { hadDuplicates: true, resolved: true, action: "removed-npm" };
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
spinner.fail(i18n.t("installation:duplicateRemovalFailed"));
|
|
1402
|
+
if (error instanceof Error) {
|
|
1403
|
+
console.error(ansis.gray(error.message));
|
|
1404
|
+
}
|
|
1405
|
+
return { hadDuplicates: true, resolved: false, action: "kept-both" };
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
async function handleDuplicateInstallations(skipPrompt = false) {
|
|
1409
|
+
const { ensureI18nInitialized, format, i18n } = await Promise.resolve().then(function () { return index; });
|
|
1410
|
+
const ansis = (await import('ansis')).default;
|
|
1411
|
+
ensureI18nInitialized();
|
|
1412
|
+
const duplicateInfo = await checkDuplicateInstallations();
|
|
1413
|
+
if (!duplicateInfo.hasDuplicates) {
|
|
1414
|
+
return { hadDuplicates: false, resolved: true, action: "no-duplicates" };
|
|
1415
|
+
}
|
|
1416
|
+
const { npmInstallation, homebrewInstallation } = duplicateInfo;
|
|
1417
|
+
console.log("");
|
|
1418
|
+
console.log(ansis.yellow.bold(i18n.t("installation:duplicateInstallationsDetected")));
|
|
1419
|
+
console.log(ansis.gray(i18n.t("installation:duplicateInstallationsWarning")));
|
|
1420
|
+
console.log("");
|
|
1421
|
+
if (homebrewInstallation) {
|
|
1422
|
+
const isActive = homebrewInstallation.isActive;
|
|
1423
|
+
const statusIcon = isActive ? "\u2705" : "\u26A0\uFE0F";
|
|
1424
|
+
const statusColor = isActive ? ansis.green : ansis.yellow;
|
|
1425
|
+
console.log(ansis.cyan.bold(`\u{1F37A} Homebrew Cask ${i18n.t("installation:recommendedMethod")}:`));
|
|
1426
|
+
console.log(ansis.white(` ${i18n.t("installation:installationSource")}: ${statusColor(getSourceDisplayName(homebrewInstallation.source, i18n))}`));
|
|
1427
|
+
console.log(ansis.white(` ${i18n.t("installation:installationPath")}: ${ansis.gray(homebrewInstallation.path)}`));
|
|
1428
|
+
if (homebrewInstallation.version) {
|
|
1429
|
+
console.log(ansis.white(` ${i18n.t("installation:installationVersion")}: ${ansis.cyan(homebrewInstallation.version)}`));
|
|
1430
|
+
}
|
|
1431
|
+
console.log(ansis.white(` ${statusIcon} ${isActive ? i18n.t("installation:currentActiveInstallation") : i18n.t("installation:inactiveInstallations")}`));
|
|
1432
|
+
console.log("");
|
|
1433
|
+
}
|
|
1434
|
+
if (npmInstallation) {
|
|
1435
|
+
const isActive = npmInstallation.isActive;
|
|
1436
|
+
console.log(ansis.yellow.bold(`\u{1F4E6} npm ${i18n.t("installation:notRecommended")}:`));
|
|
1437
|
+
console.log(ansis.white(` ${i18n.t("installation:installationSource")}: ${ansis.yellow(getSourceDisplayName(npmInstallation.source, i18n))}`));
|
|
1438
|
+
console.log(ansis.white(` ${i18n.t("installation:installationPath")}: ${ansis.gray(npmInstallation.path)}`));
|
|
1439
|
+
if (npmInstallation.version) {
|
|
1440
|
+
console.log(ansis.white(` ${i18n.t("installation:installationVersion")}: ${ansis.cyan(npmInstallation.version)}`));
|
|
1441
|
+
if (homebrewInstallation?.version && npmInstallation.version !== homebrewInstallation.version) {
|
|
1442
|
+
console.log(ansis.red(` ${format(i18n.t("installation:versionMismatchWarning"), {
|
|
1443
|
+
npmVersion: npmInstallation.version,
|
|
1444
|
+
homebrewVersion: homebrewInstallation.version
|
|
1445
|
+
})}`));
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
if (isActive) {
|
|
1449
|
+
console.log(ansis.white(` \u26A0\uFE0F ${i18n.t("installation:currentActiveInstallation")}`));
|
|
1450
|
+
}
|
|
1451
|
+
console.log("");
|
|
1452
|
+
}
|
|
1453
|
+
console.log(ansis.cyan(`\u{1F4A1} ${i18n.t("installation:recommendRemoveNpm")}`));
|
|
1454
|
+
console.log("");
|
|
1455
|
+
if (!npmInstallation) {
|
|
1456
|
+
return { hadDuplicates: true, resolved: false, action: "kept-both" };
|
|
1457
|
+
}
|
|
1458
|
+
const { exec: tinyExec } = await import('tinyexec');
|
|
1459
|
+
if (skipPrompt) {
|
|
1460
|
+
console.log(ansis.cyan(`\u{1F504} ${i18n.t("installation:autoRemovingNpm")}`));
|
|
1461
|
+
return await performNpmRemovalAndActivateHomebrew(
|
|
1462
|
+
npmInstallation,
|
|
1463
|
+
homebrewInstallation,
|
|
1464
|
+
tinyExec,
|
|
1465
|
+
i18n,
|
|
1466
|
+
ansis
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
const inquirer = (await import('inquirer')).default;
|
|
1470
|
+
const sourceDisplayName = getSourceDisplayName(npmInstallation.source, i18n);
|
|
1471
|
+
const confirmMessage = format(i18n.t("installation:confirmRemoveDuplicate"), { source: sourceDisplayName });
|
|
1472
|
+
const { action } = await inquirer.prompt([
|
|
1473
|
+
{
|
|
1474
|
+
type: "list",
|
|
1475
|
+
name: "action",
|
|
1476
|
+
message: confirmMessage,
|
|
1477
|
+
choices: [
|
|
1478
|
+
{
|
|
1479
|
+
name: `\u2705 ${i18n.t("common:yes")} - ${i18n.t("installation:removingDuplicateInstallation")}`,
|
|
1480
|
+
value: "remove"
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
name: `\u274C ${i18n.t("installation:keepBothInstallations")}`,
|
|
1484
|
+
value: "keep"
|
|
1485
|
+
}
|
|
1486
|
+
]
|
|
1487
|
+
}
|
|
1488
|
+
]);
|
|
1489
|
+
if (action === "keep") {
|
|
1490
|
+
console.log(ansis.gray(i18n.t("installation:duplicateWarningContinue")));
|
|
1491
|
+
return { hadDuplicates: true, resolved: false, action: "kept-both" };
|
|
1492
|
+
}
|
|
1493
|
+
return await performNpmRemovalAndActivateHomebrew(
|
|
1494
|
+
npmInstallation,
|
|
1495
|
+
homebrewInstallation,
|
|
1496
|
+
tinyExec,
|
|
1497
|
+
i18n,
|
|
1498
|
+
ansis
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
async function getHomebrewClaudeCodeVersion() {
|
|
1502
|
+
try {
|
|
1503
|
+
const { stdout } = await execAsync("brew info --cask claude-code --json=v2");
|
|
1504
|
+
const info = JSON.parse(stdout);
|
|
1505
|
+
if (info.casks && info.casks.length > 0) {
|
|
1506
|
+
return info.casks[0].version;
|
|
1507
|
+
}
|
|
1508
|
+
return null;
|
|
1509
|
+
} catch {
|
|
1510
|
+
return null;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
function compareVersions(current, latest) {
|
|
1514
|
+
if (!semver.valid(current) || !semver.valid(latest)) {
|
|
1515
|
+
return -1;
|
|
1516
|
+
}
|
|
1517
|
+
return semver.compare(current, latest);
|
|
1518
|
+
}
|
|
1519
|
+
function shouldUpdate(current, latest) {
|
|
1520
|
+
return compareVersions(current, latest) < 0;
|
|
1521
|
+
}
|
|
1522
|
+
async function checkClaudeCodeVersion() {
|
|
1523
|
+
const currentVersion = await getInstalledVersion("claude");
|
|
1524
|
+
const installationInfo = await getClaudeCodeInstallationSource();
|
|
1525
|
+
const { isHomebrew, commandPath, source: installationSource } = installationInfo;
|
|
1526
|
+
let latestVersion;
|
|
1527
|
+
if (isHomebrew) {
|
|
1528
|
+
latestVersion = await getHomebrewClaudeCodeVersion();
|
|
1529
|
+
} else {
|
|
1530
|
+
latestVersion = await getLatestVersion("@anthropic-ai/claude-code");
|
|
1531
|
+
}
|
|
1532
|
+
return {
|
|
1533
|
+
installed: currentVersion !== null,
|
|
1534
|
+
currentVersion,
|
|
1535
|
+
latestVersion,
|
|
1536
|
+
needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false,
|
|
1537
|
+
isHomebrew,
|
|
1538
|
+
commandPath,
|
|
1539
|
+
installationSource
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
const CCS_PACKAGE_NAME = "@xwm111/ccs";
|
|
1544
|
+
async function execWithSudoIfNeeded(command, args) {
|
|
1545
|
+
const needsSudo = shouldUseSudoForGlobalInstall();
|
|
1546
|
+
if (needsSudo) {
|
|
1547
|
+
console.log(ansis.yellow(`
|
|
1548
|
+
${i18n.t("updater:usingSudo")}`));
|
|
1549
|
+
const result = await exec("sudo", [command, ...args]);
|
|
1550
|
+
if (result.exitCode !== 0) {
|
|
1551
|
+
throw new Error(result.stderr || `Command failed with exit code ${result.exitCode}`);
|
|
1552
|
+
}
|
|
1553
|
+
return { usedSudo: true };
|
|
1554
|
+
} else {
|
|
1555
|
+
const result = await exec(command, args);
|
|
1556
|
+
if (result.exitCode !== 0) {
|
|
1557
|
+
throw new Error(result.stderr || `Command failed with exit code ${result.exitCode}`);
|
|
1558
|
+
}
|
|
1559
|
+
return { usedSudo: false };
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
async function updateCcsSelf(force = false, skipPrompt = false) {
|
|
1563
|
+
ensureI18nInitialized();
|
|
1564
|
+
const spinner = ora(i18n.t("updater:checkingVersion")).start();
|
|
1565
|
+
try {
|
|
1566
|
+
const latestVersion = await getLatestVersion(CCS_PACKAGE_NAME);
|
|
1567
|
+
spinner.stop();
|
|
1568
|
+
if (!latestVersion) {
|
|
1569
|
+
console.log(ansis.yellow(i18n.t("updater:cannotCheckVersion")));
|
|
1570
|
+
return false;
|
|
1571
|
+
}
|
|
1572
|
+
const needsUpdate = semver.valid(latestVersion) && semver.valid(version) ? semver.gt(latestVersion, version) : latestVersion !== version;
|
|
1573
|
+
if (!needsUpdate && !force) {
|
|
1574
|
+
console.log(ansis.green(format(i18n.t("updater:ccsUpToDate"), { version })));
|
|
1575
|
+
return true;
|
|
1576
|
+
}
|
|
1577
|
+
console.log(ansis.cyan(format(i18n.t("updater:currentVersion"), { version })));
|
|
1578
|
+
console.log(ansis.cyan(format(i18n.t("updater:latestVersion"), { version: latestVersion })));
|
|
1579
|
+
if (!skipPrompt) {
|
|
1580
|
+
const confirm = await promptBoolean({
|
|
1581
|
+
message: format(i18n.t("updater:confirmUpdate"), { tool: "ccs" }),
|
|
1582
|
+
defaultValue: true
|
|
1583
|
+
});
|
|
1584
|
+
if (!confirm) {
|
|
1585
|
+
console.log(ansis.gray(i18n.t("updater:updateSkipped")));
|
|
1586
|
+
return true;
|
|
1587
|
+
}
|
|
1588
|
+
} else {
|
|
1589
|
+
console.log(ansis.cyan(format(i18n.t("updater:autoUpdating"), { tool: "ccs" })));
|
|
1590
|
+
}
|
|
1591
|
+
const updateSpinner = ora(format(i18n.t("updater:updating"), { tool: "ccs" })).start();
|
|
1592
|
+
try {
|
|
1593
|
+
await execWithSudoIfNeeded("npm", ["install", "-g", `${CCS_PACKAGE_NAME}@latest`]);
|
|
1594
|
+
updateSpinner.succeed(format(i18n.t("updater:updateSuccess"), { tool: "ccs" }));
|
|
1595
|
+
return true;
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
updateSpinner.fail(format(i18n.t("updater:updateFailed"), { tool: "ccs" }));
|
|
1598
|
+
console.error(ansis.red(error instanceof Error ? error.message : String(error)));
|
|
1599
|
+
return false;
|
|
1600
|
+
}
|
|
1601
|
+
} catch (error) {
|
|
1602
|
+
spinner.fail(i18n.t("updater:checkFailed"));
|
|
1603
|
+
console.error(ansis.red(error instanceof Error ? error.message : String(error)));
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
async function updateClaudeCode(force = false, skipPrompt = false) {
|
|
1608
|
+
ensureI18nInitialized();
|
|
1609
|
+
const spinner = ora(i18n.t("updater:checkingVersion")).start();
|
|
1610
|
+
try {
|
|
1611
|
+
const { installed, currentVersion, latestVersion, needsUpdate, isHomebrew } = await checkClaudeCodeVersion();
|
|
1612
|
+
spinner.stop();
|
|
1613
|
+
if (!installed) {
|
|
1614
|
+
console.log(ansis.yellow(i18n.t("updater:claudeCodeNotInstalled")));
|
|
1615
|
+
return false;
|
|
1616
|
+
}
|
|
1617
|
+
if (!needsUpdate && !force) {
|
|
1618
|
+
console.log(ansis.green(format(i18n.t("updater:claudeCodeUpToDate"), { version: currentVersion || "" })));
|
|
1619
|
+
return true;
|
|
1620
|
+
}
|
|
1621
|
+
if (!latestVersion) {
|
|
1622
|
+
console.log(ansis.yellow(i18n.t("updater:cannotCheckVersion")));
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1625
|
+
console.log(ansis.cyan(format(i18n.t("updater:currentVersion"), { version: currentVersion || "" })));
|
|
1626
|
+
console.log(ansis.cyan(format(i18n.t("updater:latestVersion"), { version: latestVersion })));
|
|
1627
|
+
if (!skipPrompt) {
|
|
1628
|
+
const confirm = await promptBoolean({
|
|
1629
|
+
message: format(i18n.t("updater:confirmUpdate"), { tool: "Claude Code" }),
|
|
1630
|
+
defaultValue: true
|
|
1631
|
+
});
|
|
1632
|
+
if (!confirm) {
|
|
1633
|
+
console.log(ansis.gray(i18n.t("updater:updateSkipped")));
|
|
1634
|
+
return true;
|
|
1635
|
+
}
|
|
1636
|
+
} else {
|
|
1637
|
+
console.log(ansis.cyan(format(i18n.t("updater:autoUpdating"), { tool: "Claude Code" })));
|
|
1638
|
+
}
|
|
1639
|
+
const toolName = isHomebrew ? "Claude Code (Homebrew)" : "Claude Code";
|
|
1640
|
+
const updateSpinner = ora(format(i18n.t("updater:updating"), { tool: toolName })).start();
|
|
1641
|
+
try {
|
|
1642
|
+
if (isHomebrew) {
|
|
1643
|
+
const result = await exec("brew", ["upgrade", "--cask", "claude-code"]);
|
|
1644
|
+
if (result.exitCode !== 0) {
|
|
1645
|
+
throw new Error(result.stderr || `Command failed with exit code ${result.exitCode}`);
|
|
1646
|
+
}
|
|
1647
|
+
} else {
|
|
1648
|
+
await execWithSudoIfNeeded("claude", ["update"]);
|
|
1649
|
+
}
|
|
1650
|
+
updateSpinner.succeed(format(i18n.t("updater:updateSuccess"), { tool: "Claude Code" }));
|
|
1651
|
+
return true;
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
updateSpinner.fail(format(i18n.t("updater:updateFailed"), { tool: "Claude Code" }));
|
|
1654
|
+
console.error(ansis.red(error instanceof Error ? error.message : String(error)));
|
|
1655
|
+
return false;
|
|
1656
|
+
}
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
spinner.fail(i18n.t("updater:checkFailed"));
|
|
1659
|
+
console.error(ansis.red(error instanceof Error ? error.message : String(error)));
|
|
1660
|
+
return false;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
async function checkAndUpdateTools(skipPrompt = false) {
|
|
1664
|
+
ensureI18nInitialized();
|
|
1665
|
+
console.log(ansis.bold.cyan(`
|
|
1666
|
+
\u{1F50D} ${i18n.t("updater:checkingTools")}
|
|
1667
|
+
`));
|
|
1668
|
+
try {
|
|
1669
|
+
const duplicateResult = await handleDuplicateInstallations(skipPrompt);
|
|
1670
|
+
if (duplicateResult.hadDuplicates) {
|
|
1671
|
+
console.log();
|
|
1672
|
+
}
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1675
|
+
console.warn(ansis.yellow(`\u26A0 Duplicate installation check failed: ${errorMessage}`));
|
|
1676
|
+
}
|
|
1677
|
+
const results = [];
|
|
1678
|
+
try {
|
|
1679
|
+
const success = await updateCcsSelf(false, skipPrompt);
|
|
1680
|
+
results.push({ tool: "ccs", success });
|
|
1681
|
+
} catch (error) {
|
|
1682
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1683
|
+
console.error(ansis.red(`\u274C ${format(i18n.t("updater:updateFailed"), { tool: "ccs" })}: ${errorMessage}`));
|
|
1684
|
+
results.push({ tool: "ccs", success: false, error: errorMessage });
|
|
1685
|
+
}
|
|
1686
|
+
console.log();
|
|
1687
|
+
try {
|
|
1688
|
+
const success = await updateClaudeCode(false, skipPrompt);
|
|
1689
|
+
results.push({ tool: "Claude Code", success });
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1692
|
+
console.error(ansis.red(`\u274C ${format(i18n.t("updater:updateFailed"), { tool: "Claude Code" })}: ${errorMessage}`));
|
|
1693
|
+
results.push({ tool: "Claude Code", success: false, error: errorMessage });
|
|
1694
|
+
}
|
|
1695
|
+
if (skipPrompt) {
|
|
1696
|
+
console.log(ansis.bold.cyan(`
|
|
1697
|
+
\u{1F4CB} ${i18n.t("updater:updateSummary")}`));
|
|
1698
|
+
for (const result of results) {
|
|
1699
|
+
if (result.success) {
|
|
1700
|
+
console.log(ansis.green(`\u2714 ${result.tool}: ${i18n.t("updater:success")}`));
|
|
1701
|
+
} else {
|
|
1702
|
+
console.log(ansis.red(`\u274C ${result.tool}: ${i18n.t("updater:failed")} ${result.error ? `(${result.error})` : ""}`));
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
export { readJsonConfig as $, API_DEFAULT_URL as A, applyAiLanguageDirective as B, CLAUDE_DIR as C, DEFAULT_CODE_TOOL_TYPE as D, switchToOfficialLogin as E, promptApiConfigurationAction as F, ensureI18nInitialized as G, i18n as H, updateClaudeCode as I, isTermux as J, getTermuxPrefix as K, LEGACY_ZCF_CONFIG_FILES as L, isWSL as M, getWSLInfo as N, wrapCommandWithSudo as O, exists as P, isExecutable as Q, remove as R, SETTINGS_FILE as S, getRecommendedInstallMethods as T, findCommandPath as U, getHomebrewCommandPaths as V, promptBoolean as W, ensureDir as X, readFile as Y, ZCF_CONFIG_DIR as Z, writeFile as _, CLAUDE_MD_FILE as a, checkAndUpdateTools as a0, clearModelEnv as a1, copyFile as a2, version as a3, homepage as a4, changeLanguage as a5, writeJsonConfig as a6, initI18n as a7, index as a8, jsonConfig as a9, claudeConfig as aa, config as ab, ClAUDE_CONFIG_FILE as b, commandExists as c, CLAUDE_VSC_CONFIG_FILE as d, ZCF_CONFIG_FILE as e, CODE_TOOL_TYPES as f, getPlatform as g, CODE_TOOL_BANNERS as h, CODE_TOOL_ALIASES as i, isCodeToolType as j, API_ENV_KEY as k, SUPPORTED_LANGS as l, LANG_LABELS as m, AI_OUTPUT_LANGUAGES as n, getAiOutputLanguageLabel as o, ensureClaudeDir as p, backupExistingConfig as q, resolveCodeToolType as r, copyConfigFiles as s, configureApi as t, mergeConfigs as u, updateCustomModel as v, updateDefaultModel as w, mergeSettingsFile as x, getExistingModelConfig as y, getExistingApiConfig as z };
|