@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
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,2407 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import cac from 'cac';
|
|
3
|
+
import ansis from 'ansis';
|
|
4
|
+
import { D as DEFAULT_CODE_TOOL_TYPE, X as ensureDir, P as exists, Y as readFile, _ as writeFile, e as ZCF_CONFIG_FILE, $ as readJsonConfig, L as LEGACY_ZCF_CONFIG_FILES, Z as ZCF_CONFIG_DIR, j as isCodeToolType, l as SUPPORTED_LANGS, H as i18n, G as ensureI18nInitialized, a0 as checkAndUpdateTools, S as SETTINGS_FILE, a1 as clearModelEnv, a2 as copyFile, r as resolveCodeToolType, a3 as version, a4 as homepage, m as LANG_LABELS, a5 as changeLanguage, E as switchToOfficialLogin, a6 as writeJsonConfig, W as promptBoolean, h as CODE_TOOL_BANNERS, a7 as initI18n } from './chunks/auto-updater.mjs';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { existsSync, mkdirSync, renameSync, copyFileSync, rmSync } from 'node:fs';
|
|
7
|
+
import { dirname, join } from 'pathe';
|
|
8
|
+
import { edit, parse, stringify, initSync } from '@rainbowatcher/toml-edit-js';
|
|
9
|
+
import inquirer from 'inquirer';
|
|
10
|
+
import dayjs from 'dayjs';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { pathExists } from 'fs-extra';
|
|
13
|
+
import { exec } from 'tinyexec';
|
|
14
|
+
import trash from 'trash';
|
|
15
|
+
import 'node:url';
|
|
16
|
+
import 'ora';
|
|
17
|
+
import 'semver';
|
|
18
|
+
import 'i18next';
|
|
19
|
+
import 'i18next-fs-backend';
|
|
20
|
+
import 'inquirer-toggle';
|
|
21
|
+
import 'node:child_process';
|
|
22
|
+
import 'node:util';
|
|
23
|
+
|
|
24
|
+
let initialized = false;
|
|
25
|
+
function ensureTomlInitSync() {
|
|
26
|
+
if (!initialized) {
|
|
27
|
+
initSync();
|
|
28
|
+
initialized = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function parseToml(content) {
|
|
32
|
+
ensureTomlInitSync();
|
|
33
|
+
return parse(content);
|
|
34
|
+
}
|
|
35
|
+
function stringifyToml(data) {
|
|
36
|
+
ensureTomlInitSync();
|
|
37
|
+
return stringify(data);
|
|
38
|
+
}
|
|
39
|
+
function batchEditToml(content, edits) {
|
|
40
|
+
ensureTomlInitSync();
|
|
41
|
+
let result = content;
|
|
42
|
+
for (const [path, value] of edits) {
|
|
43
|
+
result = edit(result, path, value);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isSupportedLang(value) {
|
|
49
|
+
return SUPPORTED_LANGS.includes(value);
|
|
50
|
+
}
|
|
51
|
+
function sanitizePreferredLang(lang) {
|
|
52
|
+
return isSupportedLang(lang) ? lang : "en";
|
|
53
|
+
}
|
|
54
|
+
function sanitizeCodeToolType(codeTool) {
|
|
55
|
+
return isCodeToolType(codeTool) ? codeTool : DEFAULT_CODE_TOOL_TYPE;
|
|
56
|
+
}
|
|
57
|
+
function readTomlConfig(configPath) {
|
|
58
|
+
try {
|
|
59
|
+
if (!exists(configPath)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const content = readFile(configPath);
|
|
63
|
+
const parsed = parseToml(content);
|
|
64
|
+
return parsed;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function insertAtTopLevelStart(topLevel, content) {
|
|
70
|
+
const lines = topLevel.split("\n");
|
|
71
|
+
let insertLineIndex = 0;
|
|
72
|
+
for (let i = 0; i < lines.length; i++) {
|
|
73
|
+
const trimmed = lines[i].trim();
|
|
74
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
75
|
+
insertLineIndex = i + 1;
|
|
76
|
+
} else {
|
|
77
|
+
insertLineIndex = i;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
lines.splice(insertLineIndex, 0, content.replace(/\n$/, ""));
|
|
82
|
+
return lines.join("\n");
|
|
83
|
+
}
|
|
84
|
+
function insertAfterVersionField(topLevel, content) {
|
|
85
|
+
const versionRegex = /^version\s*=\s*["'][^"']*["'][ \t]*(?:#.*)?$/m;
|
|
86
|
+
const match = topLevel.match(versionRegex);
|
|
87
|
+
if (match && match.index !== void 0) {
|
|
88
|
+
const versionEnd = match.index + match[0].length;
|
|
89
|
+
const before = topLevel.slice(0, versionEnd);
|
|
90
|
+
const after = topLevel.slice(versionEnd);
|
|
91
|
+
return `${before}
|
|
92
|
+
${content.replace(/\n$/, "")}${after}`;
|
|
93
|
+
}
|
|
94
|
+
return insertAtTopLevelStart(topLevel, content);
|
|
95
|
+
}
|
|
96
|
+
function updateTopLevelTomlFields(content, version, lastUpdated) {
|
|
97
|
+
const firstSectionMatch = content.match(/^\[/m);
|
|
98
|
+
const topLevelEnd = firstSectionMatch?.index ?? content.length;
|
|
99
|
+
let topLevel = content.slice(0, topLevelEnd);
|
|
100
|
+
const rest = content.slice(topLevelEnd);
|
|
101
|
+
const versionRegex = /^version\s*=\s*["'][^"']*["'][ \t]*(?:#.*)?$/m;
|
|
102
|
+
const versionMatch = topLevel.match(versionRegex);
|
|
103
|
+
if (versionMatch) {
|
|
104
|
+
topLevel = topLevel.replace(versionRegex, `version = "${version}"`);
|
|
105
|
+
} else {
|
|
106
|
+
topLevel = insertAtTopLevelStart(topLevel, `version = "${version}"`);
|
|
107
|
+
}
|
|
108
|
+
const lastUpdatedRegex = /^lastUpdated\s*=\s*["'][^"']*["'][ \t]*(?:#.*)?$/m;
|
|
109
|
+
const lastUpdatedMatch = topLevel.match(lastUpdatedRegex);
|
|
110
|
+
if (lastUpdatedMatch) {
|
|
111
|
+
topLevel = topLevel.replace(lastUpdatedRegex, `lastUpdated = "${lastUpdated}"`);
|
|
112
|
+
} else {
|
|
113
|
+
topLevel = insertAfterVersionField(topLevel, `lastUpdated = "${lastUpdated}"`);
|
|
114
|
+
}
|
|
115
|
+
if (rest.length > 0 && !topLevel.endsWith("\n\n") && !topLevel.endsWith("\n")) {
|
|
116
|
+
topLevel += "\n";
|
|
117
|
+
}
|
|
118
|
+
return topLevel + rest;
|
|
119
|
+
}
|
|
120
|
+
function writeTomlConfig(configPath, config) {
|
|
121
|
+
try {
|
|
122
|
+
const configDir = dirname(configPath);
|
|
123
|
+
ensureDir(configDir);
|
|
124
|
+
if (exists(configPath)) {
|
|
125
|
+
const existingContent = readFile(configPath);
|
|
126
|
+
const edits = [
|
|
127
|
+
// General section
|
|
128
|
+
["general.preferredLang", config.general.preferredLang],
|
|
129
|
+
["general.currentTool", config.general.currentTool]
|
|
130
|
+
];
|
|
131
|
+
if (config.general.templateLang !== void 0) {
|
|
132
|
+
edits.push(["general.templateLang", config.general.templateLang]);
|
|
133
|
+
}
|
|
134
|
+
if (config.general.aiOutputLang !== void 0) {
|
|
135
|
+
edits.push(["general.aiOutputLang", config.general.aiOutputLang]);
|
|
136
|
+
}
|
|
137
|
+
edits.push(
|
|
138
|
+
["claudeCode.enabled", config.claudeCode.enabled],
|
|
139
|
+
["claudeCode.outputStyles", config.claudeCode.outputStyles],
|
|
140
|
+
["claudeCode.installType", config.claudeCode.installType]
|
|
141
|
+
);
|
|
142
|
+
if (config.claudeCode.defaultOutputStyle !== void 0) {
|
|
143
|
+
edits.push(["claudeCode.defaultOutputStyle", config.claudeCode.defaultOutputStyle]);
|
|
144
|
+
}
|
|
145
|
+
if (config.claudeCode.currentProfile !== void 0) {
|
|
146
|
+
edits.push(["claudeCode.currentProfile", config.claudeCode.currentProfile]);
|
|
147
|
+
}
|
|
148
|
+
if (config.claudeCode.profiles !== void 0) {
|
|
149
|
+
edits.push(["claudeCode.profiles", config.claudeCode.profiles]);
|
|
150
|
+
}
|
|
151
|
+
if (config.claudeCode.version !== void 0) {
|
|
152
|
+
edits.push(["claudeCode.version", config.claudeCode.version]);
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
let updatedContent = batchEditToml(existingContent, edits);
|
|
156
|
+
updatedContent = updateTopLevelTomlFields(
|
|
157
|
+
updatedContent,
|
|
158
|
+
config.version,
|
|
159
|
+
config.lastUpdated
|
|
160
|
+
);
|
|
161
|
+
const expectedProfiles = Object.keys(config.claudeCode.profiles || {}).length;
|
|
162
|
+
if (expectedProfiles > 0) {
|
|
163
|
+
const verify = parseToml(updatedContent);
|
|
164
|
+
const actualProfiles = Object.keys(verify?.claudeCode?.profiles || {}).length;
|
|
165
|
+
if (actualProfiles !== expectedProfiles) {
|
|
166
|
+
throw new Error("Incremental edit produced inconsistent profiles");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
writeFile(configPath, updatedContent);
|
|
170
|
+
} catch {
|
|
171
|
+
const tomlContent = stringifyToml(config);
|
|
172
|
+
writeFile(configPath, tomlContent);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
const tomlContent = stringifyToml(config);
|
|
176
|
+
writeFile(configPath, tomlContent);
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function createDefaultTomlConfig(preferredLang = "en", claudeCodeInstallType = "global") {
|
|
182
|
+
return {
|
|
183
|
+
version: "1.0.0",
|
|
184
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
185
|
+
general: {
|
|
186
|
+
preferredLang,
|
|
187
|
+
templateLang: preferredLang,
|
|
188
|
+
// Default templateLang to preferredLang for new installations
|
|
189
|
+
aiOutputLang: preferredLang === "zh-CN" ? "zh-CN" : void 0,
|
|
190
|
+
currentTool: DEFAULT_CODE_TOOL_TYPE
|
|
191
|
+
},
|
|
192
|
+
claudeCode: {
|
|
193
|
+
enabled: true,
|
|
194
|
+
outputStyles: ["engineer-professional"],
|
|
195
|
+
defaultOutputStyle: "engineer-professional",
|
|
196
|
+
installType: claudeCodeInstallType,
|
|
197
|
+
currentProfile: "",
|
|
198
|
+
profiles: {}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function migrateFromJsonConfig(jsonConfig) {
|
|
203
|
+
const claudeCodeInstallType = jsonConfig.claudeCodeInstallation?.type || "global";
|
|
204
|
+
const defaultConfig = createDefaultTomlConfig("en", claudeCodeInstallType);
|
|
205
|
+
const tomlConfig = {
|
|
206
|
+
version: jsonConfig.version || defaultConfig.version,
|
|
207
|
+
lastUpdated: jsonConfig.lastUpdated || (/* @__PURE__ */ new Date()).toISOString(),
|
|
208
|
+
general: {
|
|
209
|
+
preferredLang: jsonConfig.preferredLang || defaultConfig.general.preferredLang,
|
|
210
|
+
templateLang: jsonConfig.templateLang || jsonConfig.preferredLang || defaultConfig.general.preferredLang,
|
|
211
|
+
// Backward compatibility: use preferredLang as default
|
|
212
|
+
aiOutputLang: jsonConfig.aiOutputLang || defaultConfig.general.aiOutputLang,
|
|
213
|
+
currentTool: jsonConfig.codeToolType || defaultConfig.general.currentTool
|
|
214
|
+
},
|
|
215
|
+
claudeCode: {
|
|
216
|
+
enabled: jsonConfig.codeToolType === "claude-code",
|
|
217
|
+
outputStyles: jsonConfig.outputStyles || defaultConfig.claudeCode.outputStyles,
|
|
218
|
+
defaultOutputStyle: jsonConfig.defaultOutputStyle ?? defaultConfig.claudeCode.defaultOutputStyle,
|
|
219
|
+
installType: claudeCodeInstallType,
|
|
220
|
+
currentProfile: jsonConfig.currentProfileId || defaultConfig.claudeCode.currentProfile,
|
|
221
|
+
profiles: jsonConfig.claudeCode?.profiles || {}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
return tomlConfig;
|
|
225
|
+
}
|
|
226
|
+
function convertTomlToLegacyConfig(tomlConfig) {
|
|
227
|
+
return {
|
|
228
|
+
version: tomlConfig.version,
|
|
229
|
+
preferredLang: tomlConfig.general.preferredLang,
|
|
230
|
+
templateLang: tomlConfig.general.templateLang,
|
|
231
|
+
aiOutputLang: tomlConfig.general.aiOutputLang,
|
|
232
|
+
outputStyles: tomlConfig.claudeCode.outputStyles,
|
|
233
|
+
defaultOutputStyle: tomlConfig.claudeCode.defaultOutputStyle,
|
|
234
|
+
codeToolType: tomlConfig.general.currentTool,
|
|
235
|
+
lastUpdated: tomlConfig.lastUpdated
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function convertLegacyToTomlConfig(legacyConfig) {
|
|
239
|
+
return migrateFromJsonConfig(legacyConfig);
|
|
240
|
+
}
|
|
241
|
+
function normalizeZcfConfig(config) {
|
|
242
|
+
if (!config) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
version: typeof config.version === "string" ? config.version : "1.0.0",
|
|
247
|
+
preferredLang: sanitizePreferredLang(config.preferredLang),
|
|
248
|
+
templateLang: config.templateLang ? sanitizePreferredLang(config.templateLang) : void 0,
|
|
249
|
+
aiOutputLang: config.aiOutputLang,
|
|
250
|
+
outputStyles: Array.isArray(config.outputStyles) ? config.outputStyles : void 0,
|
|
251
|
+
defaultOutputStyle: typeof config.defaultOutputStyle === "string" ? config.defaultOutputStyle : void 0,
|
|
252
|
+
codeToolType: sanitizeCodeToolType(config.codeToolType),
|
|
253
|
+
lastUpdated: typeof config.lastUpdated === "string" ? config.lastUpdated : (/* @__PURE__ */ new Date()).toISOString()
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function migrateZcfConfigIfNeeded() {
|
|
257
|
+
const target = ZCF_CONFIG_FILE;
|
|
258
|
+
const removed = [];
|
|
259
|
+
const targetExists = existsSync(target);
|
|
260
|
+
const legacySources = LEGACY_ZCF_CONFIG_FILES.filter((path) => existsSync(path));
|
|
261
|
+
if (!targetExists && legacySources.length > 0) {
|
|
262
|
+
const source = legacySources[0];
|
|
263
|
+
if (!existsSync(ZCF_CONFIG_DIR)) {
|
|
264
|
+
mkdirSync(ZCF_CONFIG_DIR, { recursive: true });
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
renameSync(source, target);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (error?.code !== "EXDEV") {
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
copyFileSync(source, target);
|
|
273
|
+
rmSync(source, { force: true });
|
|
274
|
+
}
|
|
275
|
+
for (const leftover of legacySources.slice(1)) {
|
|
276
|
+
try {
|
|
277
|
+
rmSync(leftover, { force: true });
|
|
278
|
+
removed.push(leftover);
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return { migrated: true, source, target, removed };
|
|
283
|
+
}
|
|
284
|
+
if (targetExists && legacySources.length > 0) {
|
|
285
|
+
for (const source of legacySources) {
|
|
286
|
+
try {
|
|
287
|
+
rmSync(source, { force: true });
|
|
288
|
+
removed.push(source);
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { migrated: false, target, removed };
|
|
293
|
+
}
|
|
294
|
+
return { migrated: false, target, removed };
|
|
295
|
+
}
|
|
296
|
+
function readZcfConfig() {
|
|
297
|
+
migrateZcfConfigIfNeeded();
|
|
298
|
+
const tomlConfig = readTomlConfig(ZCF_CONFIG_FILE);
|
|
299
|
+
if (tomlConfig) {
|
|
300
|
+
return convertTomlToLegacyConfig(tomlConfig);
|
|
301
|
+
}
|
|
302
|
+
const raw = readJsonConfig(ZCF_CONFIG_FILE.replace(".toml", ".json"));
|
|
303
|
+
const normalized = normalizeZcfConfig(raw || null);
|
|
304
|
+
if (normalized) {
|
|
305
|
+
return normalized;
|
|
306
|
+
}
|
|
307
|
+
for (const legacyPath of LEGACY_ZCF_CONFIG_FILES) {
|
|
308
|
+
if (existsSync(legacyPath)) {
|
|
309
|
+
const legacyRaw = readJsonConfig(legacyPath);
|
|
310
|
+
const legacyNormalized = normalizeZcfConfig(legacyRaw || null);
|
|
311
|
+
if (legacyNormalized) {
|
|
312
|
+
return legacyNormalized;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
async function readZcfConfigAsync() {
|
|
319
|
+
return readZcfConfig();
|
|
320
|
+
}
|
|
321
|
+
function writeZcfConfig(config) {
|
|
322
|
+
try {
|
|
323
|
+
const sanitizedConfig = {
|
|
324
|
+
...config,
|
|
325
|
+
codeToolType: sanitizeCodeToolType(config.codeToolType)
|
|
326
|
+
};
|
|
327
|
+
const existingTomlConfig = readTomlConfig(ZCF_CONFIG_FILE);
|
|
328
|
+
const tomlConfig = convertLegacyToTomlConfig(sanitizedConfig);
|
|
329
|
+
if (existingTomlConfig?.claudeCode) {
|
|
330
|
+
if (existingTomlConfig.claudeCode.profiles) {
|
|
331
|
+
tomlConfig.claudeCode.profiles = existingTomlConfig.claudeCode.profiles;
|
|
332
|
+
}
|
|
333
|
+
if (existingTomlConfig.claudeCode.currentProfile !== void 0) {
|
|
334
|
+
tomlConfig.claudeCode.currentProfile = existingTomlConfig.claudeCode.currentProfile;
|
|
335
|
+
}
|
|
336
|
+
if (existingTomlConfig.claudeCode.version) {
|
|
337
|
+
tomlConfig.claudeCode.version = existingTomlConfig.claudeCode.version;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
writeTomlConfig(ZCF_CONFIG_FILE, tomlConfig);
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function updateZcfConfig(updates) {
|
|
345
|
+
const existingConfig = readZcfConfig();
|
|
346
|
+
const newConfig = {
|
|
347
|
+
version: updates.version || existingConfig?.version || "1.0.0",
|
|
348
|
+
preferredLang: updates.preferredLang || existingConfig?.preferredLang || "en",
|
|
349
|
+
templateLang: updates.templateLang !== void 0 ? updates.templateLang : existingConfig?.templateLang,
|
|
350
|
+
aiOutputLang: updates.aiOutputLang || existingConfig?.aiOutputLang,
|
|
351
|
+
outputStyles: updates.outputStyles !== void 0 ? updates.outputStyles : existingConfig?.outputStyles,
|
|
352
|
+
defaultOutputStyle: updates.defaultOutputStyle !== void 0 ? updates.defaultOutputStyle : existingConfig?.defaultOutputStyle,
|
|
353
|
+
codeToolType: updates.codeToolType || existingConfig?.codeToolType || DEFAULT_CODE_TOOL_TYPE,
|
|
354
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
355
|
+
};
|
|
356
|
+
writeZcfConfig(newConfig);
|
|
357
|
+
}
|
|
358
|
+
function readDefaultTomlConfig() {
|
|
359
|
+
return readTomlConfig(ZCF_CONFIG_FILE);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const CODE_TYPE_ABBREVIATIONS = {
|
|
363
|
+
cc: "claude-code"
|
|
364
|
+
};
|
|
365
|
+
async function resolveCodeType$1(codeTypeParam) {
|
|
366
|
+
if (codeTypeParam) {
|
|
367
|
+
const normalizedParam = codeTypeParam.toLowerCase().trim();
|
|
368
|
+
if (normalizedParam in CODE_TYPE_ABBREVIATIONS) {
|
|
369
|
+
return CODE_TYPE_ABBREVIATIONS[normalizedParam];
|
|
370
|
+
}
|
|
371
|
+
if (isValidCodeType(normalizedParam)) {
|
|
372
|
+
return normalizedParam;
|
|
373
|
+
}
|
|
374
|
+
const validAbbreviations = Object.keys(CODE_TYPE_ABBREVIATIONS);
|
|
375
|
+
const validFullTypes = Object.values(CODE_TYPE_ABBREVIATIONS);
|
|
376
|
+
const validOptions = [...validAbbreviations, ...validFullTypes].join(", ");
|
|
377
|
+
let defaultValue = DEFAULT_CODE_TOOL_TYPE;
|
|
378
|
+
try {
|
|
379
|
+
const config = await readZcfConfigAsync();
|
|
380
|
+
if (config?.codeToolType && isValidCodeType(config.codeToolType)) {
|
|
381
|
+
defaultValue = config.codeToolType;
|
|
382
|
+
}
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
throw new Error(
|
|
386
|
+
i18n.t("errors:invalidCodeType", { value: codeTypeParam, validOptions, defaultValue })
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
const config = await readZcfConfigAsync();
|
|
391
|
+
if (config?.codeToolType && isValidCodeType(config.codeToolType)) {
|
|
392
|
+
return config.codeToolType;
|
|
393
|
+
}
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
return DEFAULT_CODE_TOOL_TYPE;
|
|
397
|
+
}
|
|
398
|
+
function isValidCodeType(value) {
|
|
399
|
+
return value === "claude-code";
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
class ToolUpdateScheduler {
|
|
403
|
+
/**
|
|
404
|
+
* Update tools based on code type
|
|
405
|
+
* @param codeType - The code tool type to update
|
|
406
|
+
* @param skipPrompt - Whether to skip interactive prompts
|
|
407
|
+
*/
|
|
408
|
+
async updateByCodeType(codeType, skipPrompt = false) {
|
|
409
|
+
await ensureI18nInitialized();
|
|
410
|
+
switch (codeType) {
|
|
411
|
+
case "claude-code":
|
|
412
|
+
await this.updateClaudeCodeTools(skipPrompt);
|
|
413
|
+
break;
|
|
414
|
+
default:
|
|
415
|
+
throw new Error(`Unsupported code type: ${codeType}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Update Claude Code related tools
|
|
420
|
+
* @param skipPrompt - Whether to skip interactive prompts
|
|
421
|
+
*/
|
|
422
|
+
async updateClaudeCodeTools(skipPrompt) {
|
|
423
|
+
await checkAndUpdateTools(skipPrompt);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async function checkUpdates(options = {}) {
|
|
428
|
+
try {
|
|
429
|
+
const skipPrompt = options.skipPrompt || false;
|
|
430
|
+
let codeType;
|
|
431
|
+
try {
|
|
432
|
+
codeType = await resolveCodeType$1(options.codeType);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
435
|
+
console.error(ansis.red(`${errorMessage}
|
|
436
|
+
Defaulting to "claude-code".`));
|
|
437
|
+
codeType = "claude-code";
|
|
438
|
+
}
|
|
439
|
+
const scheduler = new ToolUpdateScheduler();
|
|
440
|
+
await scheduler.updateByCodeType(codeType, skipPrompt);
|
|
441
|
+
} catch (error) {
|
|
442
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
443
|
+
console.error(ansis.red(`${i18n.t("updater:errorCheckingUpdates")} ${errorMessage}`));
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
class ClaudeCodeConfigManager {
|
|
449
|
+
static CONFIG_FILE = ZCF_CONFIG_FILE;
|
|
450
|
+
static LEGACY_CONFIG_FILE = join(ZCF_CONFIG_DIR, "claude-code-configs.json");
|
|
451
|
+
/**
|
|
452
|
+
* Ensure configuration directory exists
|
|
453
|
+
*/
|
|
454
|
+
static ensureConfigDir() {
|
|
455
|
+
ensureDir(ZCF_CONFIG_DIR);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Read TOML configuration
|
|
459
|
+
*/
|
|
460
|
+
static readTomlConfig() {
|
|
461
|
+
return readDefaultTomlConfig();
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Load TOML configuration, falling back to default when missing
|
|
465
|
+
*/
|
|
466
|
+
static loadTomlConfig() {
|
|
467
|
+
const existingConfig = this.readTomlConfig();
|
|
468
|
+
if (existingConfig) {
|
|
469
|
+
return existingConfig;
|
|
470
|
+
}
|
|
471
|
+
return createDefaultTomlConfig();
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Migrate legacy JSON-based configuration into TOML storage
|
|
475
|
+
*/
|
|
476
|
+
static migrateFromLegacyConfig() {
|
|
477
|
+
if (!exists(this.LEGACY_CONFIG_FILE)) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const legacyConfig = readJsonConfig(this.LEGACY_CONFIG_FILE);
|
|
482
|
+
if (!legacyConfig) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
const normalizedProfiles = {};
|
|
486
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
487
|
+
let migratedCurrentKey = "";
|
|
488
|
+
Object.entries(legacyConfig.profiles || {}).forEach(([legacyKey, profile]) => {
|
|
489
|
+
const sourceProfile = profile;
|
|
490
|
+
const name = sourceProfile.name?.trim() || legacyKey;
|
|
491
|
+
const baseKey = this.generateProfileId(name);
|
|
492
|
+
let uniqueKey = baseKey || legacyKey;
|
|
493
|
+
let suffix = 2;
|
|
494
|
+
while (existingKeys.has(uniqueKey)) {
|
|
495
|
+
uniqueKey = `${baseKey || legacyKey}-${suffix++}`;
|
|
496
|
+
}
|
|
497
|
+
existingKeys.add(uniqueKey);
|
|
498
|
+
const sanitizedProfile = this.sanitizeProfile({
|
|
499
|
+
...sourceProfile,
|
|
500
|
+
name
|
|
501
|
+
});
|
|
502
|
+
normalizedProfiles[uniqueKey] = {
|
|
503
|
+
...sanitizedProfile,
|
|
504
|
+
id: uniqueKey
|
|
505
|
+
};
|
|
506
|
+
if (legacyConfig.currentProfileId === legacyKey || legacyConfig.currentProfileId === sourceProfile.id) {
|
|
507
|
+
migratedCurrentKey = uniqueKey;
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
if (!migratedCurrentKey && legacyConfig.currentProfileId) {
|
|
511
|
+
const fallbackKey = this.generateProfileId(legacyConfig.currentProfileId);
|
|
512
|
+
if (existingKeys.has(fallbackKey)) {
|
|
513
|
+
migratedCurrentKey = fallbackKey;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (!migratedCurrentKey && existingKeys.size > 0) {
|
|
517
|
+
migratedCurrentKey = Array.from(existingKeys)[0];
|
|
518
|
+
}
|
|
519
|
+
const migratedConfig = {
|
|
520
|
+
currentProfileId: migratedCurrentKey,
|
|
521
|
+
profiles: normalizedProfiles
|
|
522
|
+
};
|
|
523
|
+
this.writeConfig(migratedConfig);
|
|
524
|
+
return migratedConfig;
|
|
525
|
+
} catch (error) {
|
|
526
|
+
console.error("Failed to migrate legacy Claude Code config:", error);
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Read configuration
|
|
532
|
+
*/
|
|
533
|
+
static readConfig() {
|
|
534
|
+
try {
|
|
535
|
+
const tomlConfig = readDefaultTomlConfig();
|
|
536
|
+
if (!tomlConfig || !tomlConfig.claudeCode) {
|
|
537
|
+
return this.migrateFromLegacyConfig();
|
|
538
|
+
}
|
|
539
|
+
const { claudeCode } = tomlConfig;
|
|
540
|
+
const rawProfiles = claudeCode.profiles || {};
|
|
541
|
+
const sanitizedProfiles = Object.fromEntries(
|
|
542
|
+
Object.entries(rawProfiles).map(([key, profile]) => {
|
|
543
|
+
const storedProfile = this.sanitizeProfile({
|
|
544
|
+
...profile,
|
|
545
|
+
name: profile.name || key
|
|
546
|
+
});
|
|
547
|
+
return [key, { ...storedProfile, id: key }];
|
|
548
|
+
})
|
|
549
|
+
);
|
|
550
|
+
const configData = {
|
|
551
|
+
currentProfileId: claudeCode.currentProfile || "",
|
|
552
|
+
profiles: sanitizedProfiles
|
|
553
|
+
};
|
|
554
|
+
if (Object.keys(configData.profiles).length === 0) {
|
|
555
|
+
const migrated = this.migrateFromLegacyConfig();
|
|
556
|
+
if (migrated) {
|
|
557
|
+
return migrated;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return configData;
|
|
561
|
+
} catch (error) {
|
|
562
|
+
console.error("Failed to read Claude Code config:", error);
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Write configuration
|
|
568
|
+
*/
|
|
569
|
+
static writeConfig(config) {
|
|
570
|
+
try {
|
|
571
|
+
this.ensureConfigDir();
|
|
572
|
+
const keyMap = /* @__PURE__ */ new Map();
|
|
573
|
+
const sanitizedProfiles = Object.fromEntries(
|
|
574
|
+
Object.entries(config.profiles).map(([key, profile]) => {
|
|
575
|
+
const normalizedName = profile.name?.trim() || key;
|
|
576
|
+
const profileKey = this.generateProfileId(normalizedName);
|
|
577
|
+
keyMap.set(key, profileKey);
|
|
578
|
+
const sanitizedProfile = this.sanitizeProfile({
|
|
579
|
+
...profile,
|
|
580
|
+
name: normalizedName
|
|
581
|
+
});
|
|
582
|
+
return [profileKey, sanitizedProfile];
|
|
583
|
+
})
|
|
584
|
+
);
|
|
585
|
+
const tomlConfig = this.loadTomlConfig();
|
|
586
|
+
const nextTomlConfig = {
|
|
587
|
+
...tomlConfig,
|
|
588
|
+
claudeCode: {
|
|
589
|
+
...tomlConfig.claudeCode,
|
|
590
|
+
currentProfile: keyMap.get(config.currentProfileId) || config.currentProfileId,
|
|
591
|
+
profiles: sanitizedProfiles
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
writeTomlConfig(this.CONFIG_FILE, nextTomlConfig);
|
|
595
|
+
} catch (error) {
|
|
596
|
+
console.error("Failed to write Claude Code config:", error);
|
|
597
|
+
throw new Error(`Failed to write config: ${error instanceof Error ? error.message : String(error)}`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Create empty configuration
|
|
602
|
+
*/
|
|
603
|
+
static createEmptyConfig() {
|
|
604
|
+
return {
|
|
605
|
+
currentProfileId: "",
|
|
606
|
+
profiles: {}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Apply profile settings to Claude Code runtime
|
|
611
|
+
*/
|
|
612
|
+
static async applyProfileSettings(profile) {
|
|
613
|
+
const { ensureI18nInitialized, i18n } = await import('./chunks/auto-updater.mjs').then(function (n) { return n.a8; });
|
|
614
|
+
ensureI18nInitialized();
|
|
615
|
+
try {
|
|
616
|
+
if (!profile) {
|
|
617
|
+
const { switchToOfficialLogin } = await import('./chunks/auto-updater.mjs').then(function (n) { return n.ab; });
|
|
618
|
+
switchToOfficialLogin();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const { readJsonConfig: readJsonConfig2, writeJsonConfig } = await import('./chunks/auto-updater.mjs').then(function (n) { return n.a9; });
|
|
622
|
+
const settings = readJsonConfig2(SETTINGS_FILE) || {};
|
|
623
|
+
if (!settings.env)
|
|
624
|
+
settings.env = {};
|
|
625
|
+
clearModelEnv(settings.env);
|
|
626
|
+
if (profile.authType === "api_key") {
|
|
627
|
+
settings.env.ANTHROPIC_API_KEY = profile.apiKey;
|
|
628
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
629
|
+
} else if (profile.authType === "auth_token") {
|
|
630
|
+
settings.env.ANTHROPIC_AUTH_TOKEN = profile.apiKey;
|
|
631
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
632
|
+
}
|
|
633
|
+
if (profile.baseUrl)
|
|
634
|
+
settings.env.ANTHROPIC_BASE_URL = profile.baseUrl;
|
|
635
|
+
else
|
|
636
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
637
|
+
const hasModelConfig = Boolean(
|
|
638
|
+
profile.primaryModel || profile.defaultHaikuModel || profile.defaultSonnetModel || profile.defaultOpusModel
|
|
639
|
+
);
|
|
640
|
+
if (hasModelConfig) {
|
|
641
|
+
if (profile.primaryModel)
|
|
642
|
+
settings.env.ANTHROPIC_MODEL = profile.primaryModel;
|
|
643
|
+
if (profile.defaultHaikuModel)
|
|
644
|
+
settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = profile.defaultHaikuModel;
|
|
645
|
+
if (profile.defaultSonnetModel)
|
|
646
|
+
settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL = profile.defaultSonnetModel;
|
|
647
|
+
if (profile.defaultOpusModel)
|
|
648
|
+
settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = profile.defaultOpusModel;
|
|
649
|
+
} else {
|
|
650
|
+
clearModelEnv(settings.env);
|
|
651
|
+
}
|
|
652
|
+
writeJsonConfig(SETTINGS_FILE, settings);
|
|
653
|
+
const { setPrimaryApiKey, addCompletedOnboarding } = await import('./chunks/auto-updater.mjs').then(function (n) { return n.aa; });
|
|
654
|
+
setPrimaryApiKey();
|
|
655
|
+
addCompletedOnboarding();
|
|
656
|
+
} catch (error) {
|
|
657
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
658
|
+
throw new Error(`${i18n.t("multi-config:failedToApplySettings")}: ${reason}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
static async applyCurrentProfile() {
|
|
662
|
+
const currentProfile = this.getCurrentProfile();
|
|
663
|
+
await this.applyProfileSettings(currentProfile);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Remove unsupported fields from profile payload
|
|
667
|
+
*/
|
|
668
|
+
static sanitizeProfile(profile) {
|
|
669
|
+
const sanitized = {
|
|
670
|
+
name: profile.name,
|
|
671
|
+
authType: profile.authType
|
|
672
|
+
};
|
|
673
|
+
if (profile.apiKey)
|
|
674
|
+
sanitized.apiKey = profile.apiKey;
|
|
675
|
+
if (profile.baseUrl)
|
|
676
|
+
sanitized.baseUrl = profile.baseUrl;
|
|
677
|
+
if (profile.primaryModel)
|
|
678
|
+
sanitized.primaryModel = profile.primaryModel;
|
|
679
|
+
if (profile.defaultHaikuModel)
|
|
680
|
+
sanitized.defaultHaikuModel = profile.defaultHaikuModel;
|
|
681
|
+
if (profile.defaultSonnetModel)
|
|
682
|
+
sanitized.defaultSonnetModel = profile.defaultSonnetModel;
|
|
683
|
+
if (profile.defaultOpusModel)
|
|
684
|
+
sanitized.defaultOpusModel = profile.defaultOpusModel;
|
|
685
|
+
return sanitized;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Backup configuration
|
|
689
|
+
*/
|
|
690
|
+
static backupConfig() {
|
|
691
|
+
try {
|
|
692
|
+
if (!exists(this.CONFIG_FILE)) {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
|
|
696
|
+
const backupPath = join(ZCF_CONFIG_DIR, `config.backup.${timestamp}.toml`);
|
|
697
|
+
copyFile(this.CONFIG_FILE, backupPath);
|
|
698
|
+
return backupPath;
|
|
699
|
+
} catch (error) {
|
|
700
|
+
console.error("Failed to backup Claude Code config:", error);
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Add configuration
|
|
706
|
+
*/
|
|
707
|
+
static async addProfile(profile) {
|
|
708
|
+
try {
|
|
709
|
+
const validationErrors = this.validateProfile(profile);
|
|
710
|
+
if (validationErrors.length > 0) {
|
|
711
|
+
return {
|
|
712
|
+
success: false,
|
|
713
|
+
error: `Validation failed: ${validationErrors.join(", ")}`
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
const backupPath = this.backupConfig();
|
|
717
|
+
let config = this.readConfig();
|
|
718
|
+
if (!config) {
|
|
719
|
+
config = this.createEmptyConfig();
|
|
720
|
+
}
|
|
721
|
+
if (profile.id && config.profiles[profile.id]) {
|
|
722
|
+
return {
|
|
723
|
+
success: false,
|
|
724
|
+
error: `Profile with ID "${profile.id}" already exists`,
|
|
725
|
+
backupPath: backupPath || void 0
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
const normalizedName = profile.name.trim();
|
|
729
|
+
const profileKey = this.generateProfileId(normalizedName);
|
|
730
|
+
const existingNames = Object.values(config.profiles).map((p) => p.name || "");
|
|
731
|
+
if (config.profiles[profileKey] || existingNames.some((name) => name.toLowerCase() === normalizedName.toLowerCase())) {
|
|
732
|
+
return {
|
|
733
|
+
success: false,
|
|
734
|
+
error: `Profile with name "${profile.name}" already exists`,
|
|
735
|
+
backupPath: backupPath || void 0
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
const sanitizedProfile = this.sanitizeProfile({
|
|
739
|
+
...profile,
|
|
740
|
+
name: normalizedName
|
|
741
|
+
});
|
|
742
|
+
const runtimeProfile = {
|
|
743
|
+
...sanitizedProfile,
|
|
744
|
+
id: profileKey
|
|
745
|
+
};
|
|
746
|
+
config.profiles[profileKey] = runtimeProfile;
|
|
747
|
+
if (!config.currentProfileId) {
|
|
748
|
+
config.currentProfileId = profileKey;
|
|
749
|
+
}
|
|
750
|
+
this.writeConfig(config);
|
|
751
|
+
return {
|
|
752
|
+
success: true,
|
|
753
|
+
backupPath: backupPath || void 0,
|
|
754
|
+
addedProfile: runtimeProfile
|
|
755
|
+
};
|
|
756
|
+
} catch (error) {
|
|
757
|
+
return {
|
|
758
|
+
success: false,
|
|
759
|
+
error: error instanceof Error ? error.message : String(error)
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Update configuration
|
|
765
|
+
*/
|
|
766
|
+
static async updateProfile(id, data) {
|
|
767
|
+
try {
|
|
768
|
+
const validationErrors = this.validateProfile(data, true);
|
|
769
|
+
if (validationErrors.length > 0) {
|
|
770
|
+
return {
|
|
771
|
+
success: false,
|
|
772
|
+
error: `Validation failed: ${validationErrors.join(", ")}`
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
const backupPath = this.backupConfig();
|
|
776
|
+
const config = this.readConfig();
|
|
777
|
+
if (!config || !config.profiles[id]) {
|
|
778
|
+
return {
|
|
779
|
+
success: false,
|
|
780
|
+
error: `Profile with ID "${id}" not found`,
|
|
781
|
+
backupPath: backupPath || void 0
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
const existingProfile = config.profiles[id];
|
|
785
|
+
const nextName = data.name !== void 0 ? data.name.trim() : existingProfile.name;
|
|
786
|
+
const nextKey = this.generateProfileId(nextName);
|
|
787
|
+
const nameChanged = nextKey !== id;
|
|
788
|
+
if (nameChanged) {
|
|
789
|
+
const duplicateName = Object.entries(config.profiles).some(([key, profile]) => key !== id && (profile.name || "").toLowerCase() === nextName.toLowerCase());
|
|
790
|
+
if (duplicateName || config.profiles[nextKey]) {
|
|
791
|
+
return {
|
|
792
|
+
success: false,
|
|
793
|
+
error: `Profile with name "${data.name}" already exists`,
|
|
794
|
+
backupPath: backupPath || void 0
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const mergedProfile = this.sanitizeProfile({
|
|
799
|
+
...existingProfile,
|
|
800
|
+
...data,
|
|
801
|
+
name: nextName
|
|
802
|
+
});
|
|
803
|
+
if (nameChanged) {
|
|
804
|
+
delete config.profiles[id];
|
|
805
|
+
config.profiles[nextKey] = {
|
|
806
|
+
...mergedProfile,
|
|
807
|
+
id: nextKey
|
|
808
|
+
};
|
|
809
|
+
if (config.currentProfileId === id) {
|
|
810
|
+
config.currentProfileId = nextKey;
|
|
811
|
+
}
|
|
812
|
+
} else {
|
|
813
|
+
config.profiles[id] = {
|
|
814
|
+
...mergedProfile,
|
|
815
|
+
id
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
this.writeConfig(config);
|
|
819
|
+
return {
|
|
820
|
+
success: true,
|
|
821
|
+
backupPath: backupPath || void 0,
|
|
822
|
+
updatedProfile: {
|
|
823
|
+
...mergedProfile,
|
|
824
|
+
id: nameChanged ? nextKey : id
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
} catch (error) {
|
|
828
|
+
return {
|
|
829
|
+
success: false,
|
|
830
|
+
error: error instanceof Error ? error.message : String(error)
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Delete configuration
|
|
836
|
+
*/
|
|
837
|
+
static async deleteProfile(id) {
|
|
838
|
+
try {
|
|
839
|
+
const backupPath = this.backupConfig();
|
|
840
|
+
const config = this.readConfig();
|
|
841
|
+
if (!config || !config.profiles[id]) {
|
|
842
|
+
return {
|
|
843
|
+
success: false,
|
|
844
|
+
error: `Profile with ID "${id}" not found`,
|
|
845
|
+
backupPath: backupPath || void 0
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
const profileCount = Object.keys(config.profiles).length;
|
|
849
|
+
if (profileCount === 1) {
|
|
850
|
+
return {
|
|
851
|
+
success: false,
|
|
852
|
+
error: "Cannot delete the last profile. At least one profile must remain.",
|
|
853
|
+
backupPath: backupPath || void 0
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
delete config.profiles[id];
|
|
857
|
+
if (config.currentProfileId === id) {
|
|
858
|
+
const remainingIds = Object.keys(config.profiles);
|
|
859
|
+
config.currentProfileId = remainingIds[0];
|
|
860
|
+
}
|
|
861
|
+
this.writeConfig(config);
|
|
862
|
+
return {
|
|
863
|
+
success: true,
|
|
864
|
+
backupPath: backupPath || void 0,
|
|
865
|
+
remainingProfiles: Object.entries(config.profiles).map(([key, profile]) => ({
|
|
866
|
+
...profile,
|
|
867
|
+
id: key
|
|
868
|
+
}))
|
|
869
|
+
};
|
|
870
|
+
} catch (error) {
|
|
871
|
+
return {
|
|
872
|
+
success: false,
|
|
873
|
+
error: error instanceof Error ? error.message : String(error)
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Delete multiple configurations
|
|
879
|
+
*/
|
|
880
|
+
static async deleteProfiles(ids) {
|
|
881
|
+
try {
|
|
882
|
+
const backupPath = this.backupConfig();
|
|
883
|
+
const config = this.readConfig();
|
|
884
|
+
if (!config) {
|
|
885
|
+
return {
|
|
886
|
+
success: false,
|
|
887
|
+
error: "No configuration found",
|
|
888
|
+
backupPath: backupPath || void 0
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
const missingIds = ids.filter((id) => !config.profiles[id]);
|
|
892
|
+
if (missingIds.length > 0) {
|
|
893
|
+
return {
|
|
894
|
+
success: false,
|
|
895
|
+
error: `Profiles not found: ${missingIds.join(", ")}`,
|
|
896
|
+
backupPath: backupPath || void 0
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
const remainingCount = Object.keys(config.profiles).length - ids.length;
|
|
900
|
+
if (remainingCount === 0) {
|
|
901
|
+
return {
|
|
902
|
+
success: false,
|
|
903
|
+
error: "Cannot delete all profiles. At least one profile must remain.",
|
|
904
|
+
backupPath: backupPath || void 0
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
let newCurrentProfileId;
|
|
908
|
+
ids.forEach((id) => {
|
|
909
|
+
delete config.profiles[id];
|
|
910
|
+
});
|
|
911
|
+
if (ids.includes(config.currentProfileId)) {
|
|
912
|
+
const remainingIds = Object.keys(config.profiles);
|
|
913
|
+
config.currentProfileId = remainingIds[0];
|
|
914
|
+
newCurrentProfileId = config.currentProfileId;
|
|
915
|
+
}
|
|
916
|
+
this.writeConfig(config);
|
|
917
|
+
return {
|
|
918
|
+
success: true,
|
|
919
|
+
backupPath: backupPath || void 0,
|
|
920
|
+
newCurrentProfileId,
|
|
921
|
+
deletedProfiles: ids,
|
|
922
|
+
remainingProfiles: Object.entries(config.profiles).map(([key, profile]) => ({
|
|
923
|
+
...profile,
|
|
924
|
+
id: key
|
|
925
|
+
}))
|
|
926
|
+
};
|
|
927
|
+
} catch (error) {
|
|
928
|
+
return {
|
|
929
|
+
success: false,
|
|
930
|
+
error: error instanceof Error ? error.message : String(error)
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Generate profile ID from name
|
|
936
|
+
*/
|
|
937
|
+
static generateProfileId(name) {
|
|
938
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "profile";
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Switch configuration
|
|
942
|
+
*/
|
|
943
|
+
static async switchProfile(id) {
|
|
944
|
+
try {
|
|
945
|
+
const config = this.readConfig();
|
|
946
|
+
if (!config || !config.profiles[id]) {
|
|
947
|
+
return {
|
|
948
|
+
success: false,
|
|
949
|
+
error: "Profile not found"
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
if (config.currentProfileId === id) {
|
|
953
|
+
return { success: true };
|
|
954
|
+
}
|
|
955
|
+
config.currentProfileId = id;
|
|
956
|
+
this.writeConfig(config);
|
|
957
|
+
return { success: true };
|
|
958
|
+
} catch (error) {
|
|
959
|
+
return {
|
|
960
|
+
success: false,
|
|
961
|
+
error: error instanceof Error ? error.message : String(error)
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* List all configurations
|
|
967
|
+
*/
|
|
968
|
+
static listProfiles() {
|
|
969
|
+
const config = this.readConfig();
|
|
970
|
+
if (!config) {
|
|
971
|
+
return [];
|
|
972
|
+
}
|
|
973
|
+
return Object.values(config.profiles);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Get current configuration
|
|
977
|
+
*/
|
|
978
|
+
static getCurrentProfile() {
|
|
979
|
+
const config = this.readConfig();
|
|
980
|
+
if (!config || !config.currentProfileId) {
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
return config.profiles[config.currentProfileId] || null;
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Get configuration by ID
|
|
987
|
+
*/
|
|
988
|
+
static getProfileById(id) {
|
|
989
|
+
const config = this.readConfig();
|
|
990
|
+
if (!config) {
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
return config.profiles[id] || null;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Get configuration by name
|
|
997
|
+
*/
|
|
998
|
+
static getProfileByName(name) {
|
|
999
|
+
const config = this.readConfig();
|
|
1000
|
+
if (!config) {
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
return Object.values(config.profiles).find((p) => p.name === name) || null;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Switch to official login
|
|
1007
|
+
*/
|
|
1008
|
+
static async switchToOfficial() {
|
|
1009
|
+
try {
|
|
1010
|
+
const config = this.readConfig();
|
|
1011
|
+
if (!config) {
|
|
1012
|
+
return { success: true };
|
|
1013
|
+
}
|
|
1014
|
+
config.currentProfileId = "";
|
|
1015
|
+
this.writeConfig(config);
|
|
1016
|
+
return { success: true };
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
return {
|
|
1019
|
+
success: false,
|
|
1020
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Validate configuration
|
|
1026
|
+
*/
|
|
1027
|
+
static validateProfile(profile, isUpdate = false) {
|
|
1028
|
+
const errors = [];
|
|
1029
|
+
if (!isUpdate && (!profile.name || typeof profile.name !== "string" || profile.name.trim() === "")) {
|
|
1030
|
+
errors.push("Profile name is required");
|
|
1031
|
+
}
|
|
1032
|
+
if (profile.name && typeof profile.name !== "string") {
|
|
1033
|
+
errors.push("Profile name must be a string");
|
|
1034
|
+
}
|
|
1035
|
+
if (profile.authType && !["api_key", "auth_token", "ccr_proxy"].includes(profile.authType)) {
|
|
1036
|
+
errors.push("Invalid auth type. Must be one of: api_key, auth_token, ccr_proxy");
|
|
1037
|
+
}
|
|
1038
|
+
if (profile.authType === "api_key" || profile.authType === "auth_token") {
|
|
1039
|
+
if (!profile.apiKey || typeof profile.apiKey !== "string" || profile.apiKey.trim() === "") {
|
|
1040
|
+
errors.push("API key is required for api_key and auth_token types");
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (profile.baseUrl) {
|
|
1044
|
+
try {
|
|
1045
|
+
new URL(profile.baseUrl);
|
|
1046
|
+
} catch {
|
|
1047
|
+
errors.push("Invalid base URL format");
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return errors;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* 检查是否为最后一个配置
|
|
1054
|
+
*/
|
|
1055
|
+
static isLastProfile(id) {
|
|
1056
|
+
const config = this.readConfig();
|
|
1057
|
+
if (!config || !config.profiles[id]) {
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
return Object.keys(config.profiles).length === 1;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function handleExitPromptError(error) {
|
|
1065
|
+
const isExitError = error instanceof Error && (error.name === "ExitPromptError" || error.message?.includes("ExitPromptError") || error.message?.includes("User force closed the prompt"));
|
|
1066
|
+
if (isExitError) {
|
|
1067
|
+
ensureI18nInitialized();
|
|
1068
|
+
console.log(ansis.cyan(`
|
|
1069
|
+
${i18n.t("common:goodbye")}
|
|
1070
|
+
`));
|
|
1071
|
+
process.exit(0);
|
|
1072
|
+
}
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
function handleGeneralError(error) {
|
|
1076
|
+
ensureI18nInitialized();
|
|
1077
|
+
console.error(ansis.red(`${i18n.t("errors:generalError")}:`), error);
|
|
1078
|
+
if (error instanceof Error) {
|
|
1079
|
+
console.error(ansis.gray(`${i18n.t("errors:stackTrace")}: ${error.stack}`));
|
|
1080
|
+
}
|
|
1081
|
+
process.exit(1);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function addNumbersToChoices(choices, startFrom = 1, format = (n) => `${n}. `) {
|
|
1085
|
+
let currentNumber = startFrom;
|
|
1086
|
+
return choices.map((choice) => {
|
|
1087
|
+
if (choice.disabled) {
|
|
1088
|
+
return choice;
|
|
1089
|
+
}
|
|
1090
|
+
const numbered = {
|
|
1091
|
+
...choice,
|
|
1092
|
+
name: `${format(currentNumber)}${choice.name}`
|
|
1093
|
+
};
|
|
1094
|
+
currentNumber++;
|
|
1095
|
+
return numbered;
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
async function configSwitchCommand(options) {
|
|
1100
|
+
try {
|
|
1101
|
+
ensureI18nInitialized();
|
|
1102
|
+
if (options.list) {
|
|
1103
|
+
await handleList(options.codeType);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (options.target) {
|
|
1107
|
+
const resolvedCodeType = resolveCodeType(options.codeType);
|
|
1108
|
+
await handleDirectSwitch(resolvedCodeType, options.target);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
await handleInteractiveSwitch(options.codeType);
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
1114
|
+
throw error;
|
|
1115
|
+
}
|
|
1116
|
+
handleGeneralError(error);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function resolveCodeType(codeType) {
|
|
1120
|
+
if (codeType !== void 0) {
|
|
1121
|
+
const resolved = resolveCodeToolType(codeType);
|
|
1122
|
+
return resolved;
|
|
1123
|
+
}
|
|
1124
|
+
const zcfConfig = readZcfConfig();
|
|
1125
|
+
if (zcfConfig?.codeToolType && isCodeToolType(zcfConfig.codeToolType)) {
|
|
1126
|
+
return zcfConfig.codeToolType;
|
|
1127
|
+
}
|
|
1128
|
+
return DEFAULT_CODE_TOOL_TYPE;
|
|
1129
|
+
}
|
|
1130
|
+
async function handleList(codeType) {
|
|
1131
|
+
resolveCodeType(codeType);
|
|
1132
|
+
await listClaudeCodeProfiles();
|
|
1133
|
+
}
|
|
1134
|
+
async function listClaudeCodeProfiles() {
|
|
1135
|
+
const config = ClaudeCodeConfigManager.readConfig();
|
|
1136
|
+
if (!config || !config.profiles || Object.keys(config.profiles).length === 0) {
|
|
1137
|
+
console.log(ansis.yellow(i18n.t("multi-config:noClaudeCodeProfilesAvailable")));
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
console.log(ansis.bold(i18n.t("multi-config:availableClaudeCodeProfiles")));
|
|
1141
|
+
console.log();
|
|
1142
|
+
const currentProfileId = config.currentProfileId;
|
|
1143
|
+
Object.values(config.profiles).forEach((profile) => {
|
|
1144
|
+
const isCurrent = profile.id === currentProfileId;
|
|
1145
|
+
const status = isCurrent ? ansis.green("\u25CF ") : " ";
|
|
1146
|
+
const current = isCurrent ? ansis.yellow(i18n.t("common:current")) : "";
|
|
1147
|
+
console.log(`${status}${ansis.white(profile.name)}${current}`);
|
|
1148
|
+
console.log(` ${ansis.cyan(`ID: ${profile.id}`)} ${ansis.gray(`(${profile.authType})`)}`);
|
|
1149
|
+
console.log();
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
async function handleDirectSwitch(codeType, target) {
|
|
1153
|
+
resolveCodeType(codeType);
|
|
1154
|
+
await handleClaudeCodeDirectSwitch(target);
|
|
1155
|
+
}
|
|
1156
|
+
async function handleClaudeCodeDirectSwitch(target) {
|
|
1157
|
+
if (target === "official") {
|
|
1158
|
+
const result = await ClaudeCodeConfigManager.switchToOfficial();
|
|
1159
|
+
if (result.success) {
|
|
1160
|
+
try {
|
|
1161
|
+
await ClaudeCodeConfigManager.applyProfileSettings(null);
|
|
1162
|
+
console.log(ansis.green(i18n.t("multi-config:successfullySwitchedToOfficial")));
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1165
|
+
console.log(ansis.red(reason));
|
|
1166
|
+
}
|
|
1167
|
+
} else {
|
|
1168
|
+
console.log(ansis.red(i18n.t("multi-config:failedToSwitchToOfficial", { error: result.error })));
|
|
1169
|
+
}
|
|
1170
|
+
} else {
|
|
1171
|
+
const config = ClaudeCodeConfigManager.readConfig();
|
|
1172
|
+
if (!config || !config.profiles || Object.keys(config.profiles).length === 0) {
|
|
1173
|
+
console.log(ansis.yellow(i18n.t("multi-config:noClaudeCodeProfilesAvailable")));
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const normalizedTarget = target.trim();
|
|
1177
|
+
let resolvedId = normalizedTarget;
|
|
1178
|
+
let resolvedProfile = config.profiles[normalizedTarget];
|
|
1179
|
+
if (!resolvedProfile) {
|
|
1180
|
+
const match = Object.entries(config.profiles).find(([, profile]) => profile.name === normalizedTarget);
|
|
1181
|
+
if (match) {
|
|
1182
|
+
resolvedId = match[0];
|
|
1183
|
+
resolvedProfile = match[1];
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (!resolvedProfile) {
|
|
1187
|
+
console.log(ansis.red(i18n.t("multi-config:profileNameNotFound", { name: target })));
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
const result = await ClaudeCodeConfigManager.switchProfile(resolvedId);
|
|
1191
|
+
if (result.success) {
|
|
1192
|
+
try {
|
|
1193
|
+
await ClaudeCodeConfigManager.applyProfileSettings({ ...resolvedProfile, id: resolvedId });
|
|
1194
|
+
console.log(ansis.green(i18n.t("multi-config:successfullySwitchedToProfile", { name: resolvedProfile.name })));
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1197
|
+
console.log(ansis.red(reason));
|
|
1198
|
+
}
|
|
1199
|
+
} else {
|
|
1200
|
+
console.log(ansis.red(i18n.t("multi-config:failedToSwitchToProfile", { error: result.error })));
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
async function handleInteractiveSwitch(codeType) {
|
|
1205
|
+
resolveCodeType(codeType);
|
|
1206
|
+
await handleClaudeCodeInteractiveSwitch();
|
|
1207
|
+
}
|
|
1208
|
+
async function handleClaudeCodeInteractiveSwitch() {
|
|
1209
|
+
const config = ClaudeCodeConfigManager.readConfig();
|
|
1210
|
+
if (!config || !config.profiles || Object.keys(config.profiles).length === 0) {
|
|
1211
|
+
console.log(ansis.yellow(i18n.t("multi-config:noClaudeCodeProfilesAvailable")));
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
const currentProfileId = config.currentProfileId;
|
|
1215
|
+
const createClaudeCodeChoices = (profiles, currentProfileId2) => {
|
|
1216
|
+
const choices2 = [];
|
|
1217
|
+
const isOfficialMode = !currentProfileId2 || currentProfileId2 === "official";
|
|
1218
|
+
choices2.push({
|
|
1219
|
+
name: isOfficialMode ? `${ansis.green("\u25CF ")}${i18n.t("api:useOfficialLogin")} ${ansis.yellow(`(${i18n.t("common:current")})`)}` : ` ${i18n.t("api:useOfficialLogin")}`,
|
|
1220
|
+
value: "official"
|
|
1221
|
+
});
|
|
1222
|
+
Object.values(profiles).forEach((profile) => {
|
|
1223
|
+
const isCurrent = profile.id === currentProfileId2;
|
|
1224
|
+
choices2.push({
|
|
1225
|
+
name: isCurrent ? `${ansis.green("\u25CF ")}${profile.name} ${ansis.yellow("(current)")}` : ` ${profile.name}`,
|
|
1226
|
+
value: profile.id
|
|
1227
|
+
});
|
|
1228
|
+
});
|
|
1229
|
+
return choices2;
|
|
1230
|
+
};
|
|
1231
|
+
const choices = createClaudeCodeChoices(config.profiles, currentProfileId);
|
|
1232
|
+
try {
|
|
1233
|
+
const { selectedConfig } = await inquirer.prompt([{
|
|
1234
|
+
type: "list",
|
|
1235
|
+
name: "selectedConfig",
|
|
1236
|
+
message: i18n.t("multi-config:selectClaudeCodeConfiguration"),
|
|
1237
|
+
choices: addNumbersToChoices(choices)
|
|
1238
|
+
}]);
|
|
1239
|
+
if (!selectedConfig) {
|
|
1240
|
+
console.log(ansis.yellow(i18n.t("multi-config:cancelled")));
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
await handleClaudeCodeDirectSwitch(selectedConfig);
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
if (error.name === "ExitPromptError") {
|
|
1246
|
+
console.log(ansis.cyan(`
|
|
1247
|
+
${i18n.t("common:goodbye")}`));
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
throw error;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
const configSwitch = {
|
|
1255
|
+
__proto__: null,
|
|
1256
|
+
configSwitchCommand: configSwitchCommand
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
function getDisplayWidth(str) {
|
|
1260
|
+
let width = 0;
|
|
1261
|
+
for (const char of str) {
|
|
1262
|
+
if (char.match(/[\u4E00-\u9FFF\uFF01-\uFF60\u3000-\u303F]/)) {
|
|
1263
|
+
width += 2;
|
|
1264
|
+
} else {
|
|
1265
|
+
width += 1;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return width;
|
|
1269
|
+
}
|
|
1270
|
+
function padToDisplayWidth(str, targetWidth) {
|
|
1271
|
+
const currentWidth = getDisplayWidth(str);
|
|
1272
|
+
const paddingNeeded = Math.max(0, targetWidth - currentWidth);
|
|
1273
|
+
return str + " ".repeat(paddingNeeded);
|
|
1274
|
+
}
|
|
1275
|
+
function displayBanner(subtitle) {
|
|
1276
|
+
ensureI18nInitialized();
|
|
1277
|
+
i18n.t("cli:banner.subtitle");
|
|
1278
|
+
const subtitleText = subtitle;
|
|
1279
|
+
const paddedSubtitle = padToDisplayWidth(subtitleText, 30);
|
|
1280
|
+
const paddedTitle = padToDisplayWidth("Claude Code Switch", 60);
|
|
1281
|
+
console.log(
|
|
1282
|
+
ansis.cyan.bold(`
|
|
1283
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
1284
|
+
\u2551 \u2551
|
|
1285
|
+
\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2551
|
|
1286
|
+
\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2551
|
|
1287
|
+
\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2551
|
|
1288
|
+
\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2551
|
|
1289
|
+
\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2551
|
|
1290
|
+
\u2551 \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D ${ansis.gray(paddedSubtitle)} \u2551
|
|
1291
|
+
\u2551 \u2551
|
|
1292
|
+
\u2551 ${ansis.white.bold(paddedTitle)} \u2551
|
|
1293
|
+
\u2551 \u2551
|
|
1294
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
1295
|
+
`)
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
function displayBannerWithInfo(subtitle) {
|
|
1299
|
+
displayBanner(subtitle);
|
|
1300
|
+
console.log(ansis.gray(` Version: ${ansis.cyan(version)} | ${ansis.cyan(homepage)}
|
|
1301
|
+
`));
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
async function handleCancellation() {
|
|
1305
|
+
ensureI18nInitialized();
|
|
1306
|
+
console.log(ansis.yellow(i18n.t("common:cancelled")));
|
|
1307
|
+
}
|
|
1308
|
+
async function handleOfficialLoginMode() {
|
|
1309
|
+
ensureI18nInitialized();
|
|
1310
|
+
const success = switchToOfficialLogin();
|
|
1311
|
+
if (success) {
|
|
1312
|
+
console.log(ansis.green(`\u2714 ${i18n.t("api:officialLoginConfigured")}`));
|
|
1313
|
+
} else {
|
|
1314
|
+
console.log(ansis.red(i18n.t("api:officialLoginFailed")));
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async function handleCustomApiMode() {
|
|
1318
|
+
ensureI18nInitialized();
|
|
1319
|
+
const { configureIncrementalManagement } = await import('./chunks/claude-code-incremental-manager.mjs');
|
|
1320
|
+
await configureIncrementalManagement();
|
|
1321
|
+
}
|
|
1322
|
+
async function handleSwitchConfigMode() {
|
|
1323
|
+
ensureI18nInitialized();
|
|
1324
|
+
const { configSwitchCommand } = await Promise.resolve().then(function () { return configSwitch; });
|
|
1325
|
+
await configSwitchCommand({});
|
|
1326
|
+
}
|
|
1327
|
+
async function configureApiFeature() {
|
|
1328
|
+
ensureI18nInitialized();
|
|
1329
|
+
const { mode } = await inquirer.prompt({
|
|
1330
|
+
type: "list",
|
|
1331
|
+
name: "mode",
|
|
1332
|
+
message: i18n.t("api:apiModePrompt"),
|
|
1333
|
+
choices: addNumbersToChoices([
|
|
1334
|
+
{ name: i18n.t("api:apiModeOfficial"), value: "official" },
|
|
1335
|
+
{ name: i18n.t("api:apiModeCustom"), value: "custom" },
|
|
1336
|
+
{ name: i18n.t("api:apiModeSwitch"), value: "switch" },
|
|
1337
|
+
{ name: i18n.t("api:apiModeSkip"), value: "skip" }
|
|
1338
|
+
])
|
|
1339
|
+
});
|
|
1340
|
+
if (!mode || mode === "skip") {
|
|
1341
|
+
await handleCancellation();
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
switch (mode) {
|
|
1345
|
+
case "official":
|
|
1346
|
+
await handleOfficialLoginMode();
|
|
1347
|
+
break;
|
|
1348
|
+
case "custom":
|
|
1349
|
+
await handleCustomApiMode();
|
|
1350
|
+
break;
|
|
1351
|
+
case "switch":
|
|
1352
|
+
await handleSwitchConfigMode();
|
|
1353
|
+
break;
|
|
1354
|
+
default:
|
|
1355
|
+
await handleCancellation();
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
async function promptCustomModels(defaultPrimaryModel, defaultHaikuModel, defaultSonnetModel, defaultOpusModel) {
|
|
1360
|
+
const { primaryModel } = await inquirer.prompt({
|
|
1361
|
+
type: "input",
|
|
1362
|
+
name: "primaryModel",
|
|
1363
|
+
message: `${i18n.t("configuration:enterPrimaryModel")}${i18n.t("common:emptyToSkip")}`,
|
|
1364
|
+
default: defaultPrimaryModel || ""
|
|
1365
|
+
});
|
|
1366
|
+
const { haikuModel } = await inquirer.prompt({
|
|
1367
|
+
type: "input",
|
|
1368
|
+
name: "haikuModel",
|
|
1369
|
+
message: `${i18n.t("configuration:enterHaikuModel")}${i18n.t("common:emptyToSkip")}`,
|
|
1370
|
+
default: defaultHaikuModel || ""
|
|
1371
|
+
});
|
|
1372
|
+
const { sonnetModel } = await inquirer.prompt({
|
|
1373
|
+
type: "input",
|
|
1374
|
+
name: "sonnetModel",
|
|
1375
|
+
message: `${i18n.t("configuration:enterSonnetModel")}${i18n.t("common:emptyToSkip")}`,
|
|
1376
|
+
default: defaultSonnetModel || ""
|
|
1377
|
+
});
|
|
1378
|
+
const { opusModel } = await inquirer.prompt({
|
|
1379
|
+
type: "input",
|
|
1380
|
+
name: "opusModel",
|
|
1381
|
+
message: `${i18n.t("configuration:enterOpusModel")}${i18n.t("common:emptyToSkip")}`,
|
|
1382
|
+
default: defaultOpusModel || ""
|
|
1383
|
+
});
|
|
1384
|
+
return { primaryModel, haikuModel, sonnetModel, opusModel };
|
|
1385
|
+
}
|
|
1386
|
+
async function changeScriptLanguageFeature(currentLang) {
|
|
1387
|
+
ensureI18nInitialized();
|
|
1388
|
+
const { lang } = await inquirer.prompt({
|
|
1389
|
+
type: "list",
|
|
1390
|
+
name: "lang",
|
|
1391
|
+
message: i18n.t("language:selectScriptLang"),
|
|
1392
|
+
choices: addNumbersToChoices(
|
|
1393
|
+
SUPPORTED_LANGS.map((l) => ({
|
|
1394
|
+
name: LANG_LABELS[l],
|
|
1395
|
+
value: l
|
|
1396
|
+
}))
|
|
1397
|
+
),
|
|
1398
|
+
default: SUPPORTED_LANGS.indexOf(currentLang)
|
|
1399
|
+
});
|
|
1400
|
+
if (!lang) {
|
|
1401
|
+
return currentLang;
|
|
1402
|
+
}
|
|
1403
|
+
updateZcfConfig({ preferredLang: lang });
|
|
1404
|
+
await changeLanguage(lang);
|
|
1405
|
+
console.log(ansis.green(`\u2714 ${i18n.t("language:languageChanged") || "Language changed"}`));
|
|
1406
|
+
return lang;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const features = {
|
|
1410
|
+
__proto__: null,
|
|
1411
|
+
changeScriptLanguageFeature: changeScriptLanguageFeature,
|
|
1412
|
+
configureApiFeature: configureApiFeature,
|
|
1413
|
+
promptCustomModels: promptCustomModels
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
async function moveToTrash(paths) {
|
|
1417
|
+
const pathArray = Array.isArray(paths) ? paths : [paths];
|
|
1418
|
+
const results = [];
|
|
1419
|
+
for (const path of pathArray) {
|
|
1420
|
+
try {
|
|
1421
|
+
const exists = await pathExists(path);
|
|
1422
|
+
if (!exists) {
|
|
1423
|
+
results.push({
|
|
1424
|
+
success: false,
|
|
1425
|
+
path,
|
|
1426
|
+
error: "Path does not exist"
|
|
1427
|
+
});
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
await trash(path);
|
|
1431
|
+
results.push({
|
|
1432
|
+
success: true,
|
|
1433
|
+
path
|
|
1434
|
+
});
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
results.push({
|
|
1437
|
+
success: false,
|
|
1438
|
+
path,
|
|
1439
|
+
error: error.message || "Unknown error occurred"
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return results;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
class ZcfUninstaller {
|
|
1447
|
+
_lang;
|
|
1448
|
+
// Reserved for future i18n support
|
|
1449
|
+
conflictResolution = /* @__PURE__ */ new Map();
|
|
1450
|
+
constructor(lang = "en") {
|
|
1451
|
+
this._lang = lang;
|
|
1452
|
+
this.conflictResolution.set("claude-code", ["mcps"]);
|
|
1453
|
+
void this._lang;
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* 1. Remove outputStyle field from settings.json and output-styles directory
|
|
1457
|
+
*/
|
|
1458
|
+
async removeOutputStyles() {
|
|
1459
|
+
const result = {
|
|
1460
|
+
success: false,
|
|
1461
|
+
removed: [],
|
|
1462
|
+
removedConfigs: [],
|
|
1463
|
+
errors: [],
|
|
1464
|
+
warnings: []
|
|
1465
|
+
};
|
|
1466
|
+
try {
|
|
1467
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
1468
|
+
const outputStylesPath = join(homedir(), ".claude", "output-styles");
|
|
1469
|
+
if (await pathExists(settingsPath)) {
|
|
1470
|
+
const settings = readJsonConfig(settingsPath) || {};
|
|
1471
|
+
if (settings.outputStyle) {
|
|
1472
|
+
delete settings.outputStyle;
|
|
1473
|
+
writeJsonConfig(settingsPath, settings);
|
|
1474
|
+
result.removedConfigs.push("outputStyle field from settings.json");
|
|
1475
|
+
}
|
|
1476
|
+
} else {
|
|
1477
|
+
result.warnings.push(i18n.t("uninstall:settingsJsonNotFound"));
|
|
1478
|
+
}
|
|
1479
|
+
if (await pathExists(outputStylesPath)) {
|
|
1480
|
+
const trashResult = await moveToTrash(outputStylesPath);
|
|
1481
|
+
if (!trashResult[0]?.success) {
|
|
1482
|
+
result.warnings.push(trashResult[0]?.error || "Failed to move to trash");
|
|
1483
|
+
}
|
|
1484
|
+
result.removed.push("~/.claude/output-styles/");
|
|
1485
|
+
} else {
|
|
1486
|
+
result.warnings.push(i18n.t("uninstall:outputStylesDirectoryNotFound"));
|
|
1487
|
+
}
|
|
1488
|
+
result.success = true;
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
result.errors.push(`Failed to remove output styles: ${error.message}`);
|
|
1491
|
+
}
|
|
1492
|
+
return result;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* 2. Remove custom commands directory (commands/zcf/)
|
|
1496
|
+
*/
|
|
1497
|
+
async removeCustomCommands() {
|
|
1498
|
+
const result = {
|
|
1499
|
+
success: false,
|
|
1500
|
+
removed: [],
|
|
1501
|
+
removedConfigs: [],
|
|
1502
|
+
errors: [],
|
|
1503
|
+
warnings: []
|
|
1504
|
+
};
|
|
1505
|
+
try {
|
|
1506
|
+
const commandsPath = join(homedir(), ".claude", "commands", "zcf");
|
|
1507
|
+
if (await pathExists(commandsPath)) {
|
|
1508
|
+
const trashResult = await moveToTrash(commandsPath);
|
|
1509
|
+
if (!trashResult[0]?.success) {
|
|
1510
|
+
result.warnings.push(trashResult[0]?.error || "Failed to move to trash");
|
|
1511
|
+
}
|
|
1512
|
+
result.removed.push("commands/zcf/");
|
|
1513
|
+
result.success = true;
|
|
1514
|
+
} else {
|
|
1515
|
+
result.warnings.push(i18n.t("uninstall:commandsNotFound"));
|
|
1516
|
+
result.success = true;
|
|
1517
|
+
}
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
result.errors.push(`Failed to remove custom commands: ${error.message}`);
|
|
1520
|
+
}
|
|
1521
|
+
return result;
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* 3. Remove custom agents directory (agents/zcf/)
|
|
1525
|
+
*/
|
|
1526
|
+
async removeCustomAgents() {
|
|
1527
|
+
const result = {
|
|
1528
|
+
success: false,
|
|
1529
|
+
removed: [],
|
|
1530
|
+
removedConfigs: [],
|
|
1531
|
+
errors: [],
|
|
1532
|
+
warnings: []
|
|
1533
|
+
};
|
|
1534
|
+
try {
|
|
1535
|
+
const agentsPath = join(homedir(), ".claude", "agents", "zcf");
|
|
1536
|
+
if (await pathExists(agentsPath)) {
|
|
1537
|
+
const trashResult = await moveToTrash(agentsPath);
|
|
1538
|
+
if (!trashResult[0]?.success) {
|
|
1539
|
+
result.warnings.push(trashResult[0]?.error || "Failed to move to trash");
|
|
1540
|
+
}
|
|
1541
|
+
result.removed.push("agents/zcf/");
|
|
1542
|
+
result.success = true;
|
|
1543
|
+
} else {
|
|
1544
|
+
result.warnings.push(i18n.t("uninstall:agentsNotFound"));
|
|
1545
|
+
result.success = true;
|
|
1546
|
+
}
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
result.errors.push(`Failed to remove custom agents: ${error.message}`);
|
|
1549
|
+
}
|
|
1550
|
+
return result;
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* 4. Remove global memory file (CLAUDE.md)
|
|
1554
|
+
*/
|
|
1555
|
+
async removeClaudeMd() {
|
|
1556
|
+
const result = {
|
|
1557
|
+
success: false,
|
|
1558
|
+
removed: [],
|
|
1559
|
+
removedConfigs: [],
|
|
1560
|
+
errors: [],
|
|
1561
|
+
warnings: []
|
|
1562
|
+
};
|
|
1563
|
+
try {
|
|
1564
|
+
const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
|
|
1565
|
+
if (await pathExists(claudeMdPath)) {
|
|
1566
|
+
const trashResult = await moveToTrash(claudeMdPath);
|
|
1567
|
+
if (!trashResult[0]?.success) {
|
|
1568
|
+
result.warnings.push(trashResult[0]?.error || "Failed to move to trash");
|
|
1569
|
+
}
|
|
1570
|
+
result.removed.push("CLAUDE.md");
|
|
1571
|
+
result.success = true;
|
|
1572
|
+
} else {
|
|
1573
|
+
result.warnings.push(i18n.t("uninstall:claudeMdNotFound"));
|
|
1574
|
+
result.success = true;
|
|
1575
|
+
}
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
result.errors.push(`Failed to remove CLAUDE.md: ${error.message}`);
|
|
1578
|
+
}
|
|
1579
|
+
return result;
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* 5. Remove permissions and environment variables
|
|
1583
|
+
*/
|
|
1584
|
+
async removePermissionsAndEnvs() {
|
|
1585
|
+
const result = {
|
|
1586
|
+
success: false,
|
|
1587
|
+
removed: [],
|
|
1588
|
+
removedConfigs: [],
|
|
1589
|
+
errors: [],
|
|
1590
|
+
warnings: []
|
|
1591
|
+
};
|
|
1592
|
+
try {
|
|
1593
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
1594
|
+
if (await pathExists(settingsPath)) {
|
|
1595
|
+
const settings = readJsonConfig(settingsPath) || {};
|
|
1596
|
+
let modified = false;
|
|
1597
|
+
if (settings.permissions) {
|
|
1598
|
+
delete settings.permissions;
|
|
1599
|
+
result.removedConfigs.push("permissions configuration");
|
|
1600
|
+
modified = true;
|
|
1601
|
+
}
|
|
1602
|
+
if (settings.env) {
|
|
1603
|
+
delete settings.env;
|
|
1604
|
+
result.removedConfigs.push("environment variables");
|
|
1605
|
+
modified = true;
|
|
1606
|
+
}
|
|
1607
|
+
if (modified) {
|
|
1608
|
+
writeJsonConfig(settingsPath, settings);
|
|
1609
|
+
}
|
|
1610
|
+
result.success = true;
|
|
1611
|
+
} else {
|
|
1612
|
+
result.warnings.push(i18n.t("uninstall:settingsJsonNotFound"));
|
|
1613
|
+
result.success = true;
|
|
1614
|
+
}
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
result.errors.push(`Failed to remove permissions and envs: ${error.message}`);
|
|
1617
|
+
}
|
|
1618
|
+
return result;
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* 6. Remove MCP servers from .claude.json (mcpServers field only)
|
|
1622
|
+
*/
|
|
1623
|
+
async removeMcps() {
|
|
1624
|
+
const result = {
|
|
1625
|
+
success: false,
|
|
1626
|
+
removed: [],
|
|
1627
|
+
removedConfigs: [],
|
|
1628
|
+
errors: [],
|
|
1629
|
+
warnings: []
|
|
1630
|
+
};
|
|
1631
|
+
try {
|
|
1632
|
+
const claudeJsonPath = join(homedir(), ".claude.json");
|
|
1633
|
+
if (await pathExists(claudeJsonPath)) {
|
|
1634
|
+
const config = readJsonConfig(claudeJsonPath) || {};
|
|
1635
|
+
if (config.mcpServers) {
|
|
1636
|
+
delete config.mcpServers;
|
|
1637
|
+
writeJsonConfig(claudeJsonPath, config);
|
|
1638
|
+
result.removedConfigs.push("mcpServers from .claude.json");
|
|
1639
|
+
}
|
|
1640
|
+
result.success = true;
|
|
1641
|
+
} else {
|
|
1642
|
+
result.warnings.push(i18n.t("uninstall:claudeJsonNotFound"));
|
|
1643
|
+
result.success = true;
|
|
1644
|
+
}
|
|
1645
|
+
} catch (error) {
|
|
1646
|
+
result.errors.push(`Failed to remove MCP servers: ${error.message}`);
|
|
1647
|
+
}
|
|
1648
|
+
return result;
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* 7. Uninstall Claude Code Router and remove configuration
|
|
1652
|
+
*/
|
|
1653
|
+
async uninstallCcr() {
|
|
1654
|
+
const result = {
|
|
1655
|
+
success: false,
|
|
1656
|
+
removed: [],
|
|
1657
|
+
removedConfigs: [],
|
|
1658
|
+
errors: [],
|
|
1659
|
+
warnings: []
|
|
1660
|
+
};
|
|
1661
|
+
try {
|
|
1662
|
+
const ccrPath = join(homedir(), ".claude-code-router");
|
|
1663
|
+
if (await pathExists(ccrPath)) {
|
|
1664
|
+
const trashResult = await moveToTrash(ccrPath);
|
|
1665
|
+
if (!trashResult[0]?.success) {
|
|
1666
|
+
result.warnings.push(trashResult[0]?.error || "Failed to move to trash");
|
|
1667
|
+
}
|
|
1668
|
+
result.removed.push(".claude-code-router/");
|
|
1669
|
+
}
|
|
1670
|
+
try {
|
|
1671
|
+
await exec("npm", ["uninstall", "-g", "@musistudio/claude-code-router"]);
|
|
1672
|
+
result.removed.push("@musistudio/claude-code-router package");
|
|
1673
|
+
result.success = true;
|
|
1674
|
+
} catch (npmError) {
|
|
1675
|
+
if (npmError.message.includes("not found") || npmError.message.includes("not installed")) {
|
|
1676
|
+
result.warnings.push(i18n.t("uninstall:ccrPackageNotFound"));
|
|
1677
|
+
result.success = true;
|
|
1678
|
+
} else {
|
|
1679
|
+
result.errors.push(`Failed to uninstall CCR package: ${npmError.message}`);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
} catch (error) {
|
|
1683
|
+
result.errors.push(`Failed to uninstall CCR: ${error.message}`);
|
|
1684
|
+
}
|
|
1685
|
+
return result;
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* 8. Uninstall CCometixLine
|
|
1689
|
+
*/
|
|
1690
|
+
async uninstallCcline() {
|
|
1691
|
+
const result = {
|
|
1692
|
+
success: false,
|
|
1693
|
+
removed: [],
|
|
1694
|
+
removedConfigs: [],
|
|
1695
|
+
errors: [],
|
|
1696
|
+
warnings: []
|
|
1697
|
+
};
|
|
1698
|
+
try {
|
|
1699
|
+
await exec("npm", ["uninstall", "-g", "@cometix/ccline"]);
|
|
1700
|
+
result.removed.push("@cometix/ccline package");
|
|
1701
|
+
result.success = true;
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
if (error.message.includes("not found") || error.message.includes("not installed")) {
|
|
1704
|
+
result.warnings.push(i18n.t("uninstall:cclinePackageNotFound"));
|
|
1705
|
+
result.success = true;
|
|
1706
|
+
} else {
|
|
1707
|
+
result.errors.push(`Failed to uninstall CCometixLine: ${error.message}`);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
return result;
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* 9. Uninstall Claude Code and remove entire .claude.json
|
|
1714
|
+
*/
|
|
1715
|
+
async uninstallClaudeCode() {
|
|
1716
|
+
const result = {
|
|
1717
|
+
success: false,
|
|
1718
|
+
removed: [],
|
|
1719
|
+
removedConfigs: [],
|
|
1720
|
+
errors: [],
|
|
1721
|
+
warnings: []
|
|
1722
|
+
};
|
|
1723
|
+
try {
|
|
1724
|
+
const claudeJsonPath = join(homedir(), ".claude.json");
|
|
1725
|
+
if (await pathExists(claudeJsonPath)) {
|
|
1726
|
+
const trashResult = await moveToTrash(claudeJsonPath);
|
|
1727
|
+
if (!trashResult[0]?.success) {
|
|
1728
|
+
result.warnings.push(trashResult[0]?.error || "Failed to move to trash");
|
|
1729
|
+
}
|
|
1730
|
+
result.removed.push(".claude.json (includes MCP configuration)");
|
|
1731
|
+
}
|
|
1732
|
+
try {
|
|
1733
|
+
const { uninstallCodeTool } = await import('./chunks/installer.mjs');
|
|
1734
|
+
const success = await uninstallCodeTool("claude-code");
|
|
1735
|
+
if (success) {
|
|
1736
|
+
result.removed.push("@anthropic-ai/claude-code");
|
|
1737
|
+
result.success = true;
|
|
1738
|
+
} else {
|
|
1739
|
+
result.errors.push(i18n.t("uninstall:uninstallFailed", { codeType: i18n.t("common:claudeCode"), message: "" }));
|
|
1740
|
+
}
|
|
1741
|
+
} catch (npmError) {
|
|
1742
|
+
if (npmError.message.includes("not found") || npmError.message.includes("not installed")) {
|
|
1743
|
+
result.warnings.push(i18n.t("uninstall:claudeCodePackageNotFound"));
|
|
1744
|
+
result.success = true;
|
|
1745
|
+
} else {
|
|
1746
|
+
result.errors.push(i18n.t("uninstall:uninstallFailed", { codeType: i18n.t("common:claudeCode"), message: `: ${npmError.message}` }));
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
} catch (error) {
|
|
1750
|
+
result.errors.push(i18n.t("uninstall:uninstallFailed", { codeType: i18n.t("common:claudeCode"), message: `: ${error.message}` }));
|
|
1751
|
+
}
|
|
1752
|
+
return result;
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* 10. Remove backup files
|
|
1756
|
+
*/
|
|
1757
|
+
async removeBackups() {
|
|
1758
|
+
const result = {
|
|
1759
|
+
success: false,
|
|
1760
|
+
removed: [],
|
|
1761
|
+
removedConfigs: [],
|
|
1762
|
+
errors: [],
|
|
1763
|
+
warnings: []
|
|
1764
|
+
};
|
|
1765
|
+
try {
|
|
1766
|
+
const backupPath = join(homedir(), ".claude", "backup");
|
|
1767
|
+
if (await pathExists(backupPath)) {
|
|
1768
|
+
const trashResult = await moveToTrash(backupPath);
|
|
1769
|
+
if (!trashResult[0]?.success) {
|
|
1770
|
+
result.warnings.push(trashResult[0]?.error || "Failed to move to trash");
|
|
1771
|
+
}
|
|
1772
|
+
result.removed.push("backup/");
|
|
1773
|
+
result.success = true;
|
|
1774
|
+
} else {
|
|
1775
|
+
result.warnings.push(i18n.t("uninstall:backupsNotFound"));
|
|
1776
|
+
result.success = true;
|
|
1777
|
+
}
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
result.errors.push(`Failed to remove backups: ${error.message}`);
|
|
1780
|
+
}
|
|
1781
|
+
return result;
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* 11. Remove ZCF preference configuration
|
|
1785
|
+
*/
|
|
1786
|
+
async removeZcfConfig() {
|
|
1787
|
+
const result = {
|
|
1788
|
+
success: false,
|
|
1789
|
+
removed: [],
|
|
1790
|
+
removedConfigs: [],
|
|
1791
|
+
errors: [],
|
|
1792
|
+
warnings: []
|
|
1793
|
+
};
|
|
1794
|
+
try {
|
|
1795
|
+
const zcfConfigPath = ZCF_CONFIG_FILE;
|
|
1796
|
+
const relativeName = zcfConfigPath.replace(homedir(), "~");
|
|
1797
|
+
if (await pathExists(zcfConfigPath)) {
|
|
1798
|
+
const trashResult = await moveToTrash(zcfConfigPath);
|
|
1799
|
+
if (!trashResult[0]?.success) {
|
|
1800
|
+
result.warnings.push(trashResult[0]?.error || "Failed to move to trash");
|
|
1801
|
+
}
|
|
1802
|
+
result.removed.push(relativeName);
|
|
1803
|
+
result.success = true;
|
|
1804
|
+
} else {
|
|
1805
|
+
result.warnings.push(i18n.t("uninstall:zcfConfigNotFound"));
|
|
1806
|
+
result.success = true;
|
|
1807
|
+
}
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
result.errors.push(`Failed to remove ZCF config: ${error.message}`);
|
|
1810
|
+
}
|
|
1811
|
+
return result;
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Complete uninstall - remove all directories and packages
|
|
1815
|
+
*/
|
|
1816
|
+
async completeUninstall() {
|
|
1817
|
+
const result = {
|
|
1818
|
+
success: true,
|
|
1819
|
+
removed: [],
|
|
1820
|
+
removedConfigs: [],
|
|
1821
|
+
errors: [],
|
|
1822
|
+
warnings: []
|
|
1823
|
+
};
|
|
1824
|
+
try {
|
|
1825
|
+
const directoriesToRemove = [
|
|
1826
|
+
{ path: join(homedir(), ".claude"), name: "~/.claude/" },
|
|
1827
|
+
{ path: join(homedir(), ".claude.json"), name: "~/.claude.json" },
|
|
1828
|
+
{ path: join(homedir(), ".claude-code-router"), name: "~/.claude-code-router/" }
|
|
1829
|
+
];
|
|
1830
|
+
for (const dir of directoriesToRemove) {
|
|
1831
|
+
try {
|
|
1832
|
+
if (await pathExists(dir.path)) {
|
|
1833
|
+
const trashResult = await moveToTrash(dir.path);
|
|
1834
|
+
if (!trashResult[0]?.success) {
|
|
1835
|
+
result.warnings.push(`Failed to move ${dir.name} to trash: ${trashResult[0]?.error || "Unknown error"}`);
|
|
1836
|
+
}
|
|
1837
|
+
result.removed.push(dir.name);
|
|
1838
|
+
}
|
|
1839
|
+
} catch (error) {
|
|
1840
|
+
result.warnings.push(`Failed to remove ${dir.name}: ${error.message}`);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
const packagesToUninstall = [
|
|
1844
|
+
"@musistudio/claude-code-router",
|
|
1845
|
+
"@cometix/ccline",
|
|
1846
|
+
"@anthropic-ai/claude-code"
|
|
1847
|
+
];
|
|
1848
|
+
for (const pkg of packagesToUninstall) {
|
|
1849
|
+
try {
|
|
1850
|
+
await exec("npm", ["uninstall", "-g", pkg]);
|
|
1851
|
+
result.removed.push(`${pkg} package`);
|
|
1852
|
+
} catch (error) {
|
|
1853
|
+
if (error.message.includes("not found") || error.message.includes("not installed")) {
|
|
1854
|
+
if (pkg.includes("claude-code-router")) {
|
|
1855
|
+
result.warnings.push(i18n.t("uninstall:ccrPackageNotFound"));
|
|
1856
|
+
} else if (pkg.includes("ccline")) {
|
|
1857
|
+
result.warnings.push(i18n.t("uninstall:cclinePackageNotFound"));
|
|
1858
|
+
} else {
|
|
1859
|
+
result.warnings.push(i18n.t("uninstall:claudeCodePackageNotFound"));
|
|
1860
|
+
}
|
|
1861
|
+
} else {
|
|
1862
|
+
result.warnings.push(`Failed to uninstall ${pkg}: ${error.message}`);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
result.errors.push(`Complete uninstall failed: ${error.message}`);
|
|
1868
|
+
result.success = false;
|
|
1869
|
+
}
|
|
1870
|
+
return result;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Custom uninstall with conflict resolution
|
|
1874
|
+
*/
|
|
1875
|
+
async customUninstall(selectedItems) {
|
|
1876
|
+
const resolvedItems = this.resolveConflicts(selectedItems);
|
|
1877
|
+
const results = [];
|
|
1878
|
+
for (const item of resolvedItems) {
|
|
1879
|
+
try {
|
|
1880
|
+
const result = await this.executeUninstallItem(item);
|
|
1881
|
+
results.push(result);
|
|
1882
|
+
} catch (error) {
|
|
1883
|
+
results.push({
|
|
1884
|
+
success: false,
|
|
1885
|
+
removed: [],
|
|
1886
|
+
removedConfigs: [],
|
|
1887
|
+
errors: [`Failed to execute ${item}: ${error.message}`],
|
|
1888
|
+
warnings: []
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
return results;
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Resolve conflicts between uninstall items
|
|
1896
|
+
*/
|
|
1897
|
+
resolveConflicts(items) {
|
|
1898
|
+
const resolved = [...items];
|
|
1899
|
+
for (const [primary, conflicts] of this.conflictResolution) {
|
|
1900
|
+
if (resolved.includes(primary)) {
|
|
1901
|
+
conflicts.forEach((conflict) => {
|
|
1902
|
+
const index = resolved.indexOf(conflict);
|
|
1903
|
+
if (index > -1) {
|
|
1904
|
+
resolved.splice(index, 1);
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
return resolved;
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Execute uninstall for a specific item
|
|
1913
|
+
*/
|
|
1914
|
+
async executeUninstallItem(item) {
|
|
1915
|
+
switch (item) {
|
|
1916
|
+
case "output-styles":
|
|
1917
|
+
return await this.removeOutputStyles();
|
|
1918
|
+
case "commands":
|
|
1919
|
+
return await this.removeCustomCommands();
|
|
1920
|
+
case "agents":
|
|
1921
|
+
return await this.removeCustomAgents();
|
|
1922
|
+
case "claude-md":
|
|
1923
|
+
return await this.removeClaudeMd();
|
|
1924
|
+
case "permissions-envs":
|
|
1925
|
+
return await this.removePermissionsAndEnvs();
|
|
1926
|
+
case "mcps":
|
|
1927
|
+
return await this.removeMcps();
|
|
1928
|
+
case "ccr":
|
|
1929
|
+
return await this.uninstallCcr();
|
|
1930
|
+
case "ccline":
|
|
1931
|
+
return await this.uninstallCcline();
|
|
1932
|
+
case "claude-code":
|
|
1933
|
+
return await this.uninstallClaudeCode();
|
|
1934
|
+
case "backups":
|
|
1935
|
+
return await this.removeBackups();
|
|
1936
|
+
case "zcf-config":
|
|
1937
|
+
return await this.removeZcfConfig();
|
|
1938
|
+
default:
|
|
1939
|
+
return {
|
|
1940
|
+
success: false,
|
|
1941
|
+
removed: [],
|
|
1942
|
+
removedConfigs: [],
|
|
1943
|
+
errors: [`Unknown uninstall item: ${item}`],
|
|
1944
|
+
warnings: []
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
async function uninstall(options = {}) {
|
|
1951
|
+
try {
|
|
1952
|
+
ensureI18nInitialized();
|
|
1953
|
+
const uninstaller = new ZcfUninstaller(options.lang || "en");
|
|
1954
|
+
if (options.mode && options.mode !== "interactive") {
|
|
1955
|
+
if (options.mode === "complete") {
|
|
1956
|
+
await executeCompleteUninstall(uninstaller);
|
|
1957
|
+
return;
|
|
1958
|
+
} else if (options.mode === "custom" && options.items) {
|
|
1959
|
+
let items;
|
|
1960
|
+
if (typeof options.items === "string") {
|
|
1961
|
+
items = options.items.split(",").map((item) => item.trim());
|
|
1962
|
+
} else {
|
|
1963
|
+
items = options.items;
|
|
1964
|
+
}
|
|
1965
|
+
await executeCustomUninstall(uninstaller, items);
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
await showInteractiveUninstall(uninstaller);
|
|
1970
|
+
} catch (error) {
|
|
1971
|
+
if (!handleExitPromptError(error)) {
|
|
1972
|
+
handleGeneralError(error);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
async function showInteractiveUninstall(uninstaller) {
|
|
1977
|
+
console.log(ansis.cyan.bold(i18n.t("uninstall:title")));
|
|
1978
|
+
console.log("");
|
|
1979
|
+
const { mainChoice } = await inquirer.prompt({
|
|
1980
|
+
type: "list",
|
|
1981
|
+
name: "mainChoice",
|
|
1982
|
+
message: i18n.t("uninstall:selectMainOption"),
|
|
1983
|
+
choices: addNumbersToChoices([
|
|
1984
|
+
{
|
|
1985
|
+
name: `${i18n.t("uninstall:completeUninstall")} - ${ansis.gray(i18n.t("uninstall:completeUninstallDesc"))}`,
|
|
1986
|
+
value: "complete",
|
|
1987
|
+
short: i18n.t("uninstall:completeUninstall")
|
|
1988
|
+
},
|
|
1989
|
+
{
|
|
1990
|
+
name: `${i18n.t("uninstall:customUninstall")} - ${ansis.gray(i18n.t("uninstall:customUninstallDesc"))}`,
|
|
1991
|
+
value: "custom",
|
|
1992
|
+
short: i18n.t("uninstall:customUninstall")
|
|
1993
|
+
}
|
|
1994
|
+
])
|
|
1995
|
+
});
|
|
1996
|
+
if (!mainChoice) {
|
|
1997
|
+
console.log(ansis.yellow(i18n.t("common:cancelled")));
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
if (mainChoice === "complete") {
|
|
2001
|
+
await executeCompleteUninstall(uninstaller);
|
|
2002
|
+
} else {
|
|
2003
|
+
await showCustomUninstallMenu(uninstaller);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
async function showCustomUninstallMenu(uninstaller) {
|
|
2007
|
+
console.log("");
|
|
2008
|
+
console.log(ansis.cyan(i18n.t("uninstall:selectCustomItems")));
|
|
2009
|
+
const { customItems } = await inquirer.prompt({
|
|
2010
|
+
type: "checkbox",
|
|
2011
|
+
name: "customItems",
|
|
2012
|
+
message: `${i18n.t("uninstall:selectItemsToRemove")} ${i18n.t("common:multiSelectHint")}`,
|
|
2013
|
+
choices: [
|
|
2014
|
+
{
|
|
2015
|
+
name: i18n.t("uninstall:outputStyles"),
|
|
2016
|
+
value: "output-styles"
|
|
2017
|
+
},
|
|
2018
|
+
{
|
|
2019
|
+
name: i18n.t("uninstall:commands"),
|
|
2020
|
+
value: "commands"
|
|
2021
|
+
},
|
|
2022
|
+
{
|
|
2023
|
+
name: i18n.t("uninstall:agents"),
|
|
2024
|
+
value: "agents"
|
|
2025
|
+
},
|
|
2026
|
+
{
|
|
2027
|
+
name: i18n.t("uninstall:claudeMd"),
|
|
2028
|
+
value: "claude-md"
|
|
2029
|
+
},
|
|
2030
|
+
{
|
|
2031
|
+
name: i18n.t("uninstall:permissionsEnvs"),
|
|
2032
|
+
value: "permissions-envs"
|
|
2033
|
+
},
|
|
2034
|
+
{
|
|
2035
|
+
name: i18n.t("uninstall:mcps"),
|
|
2036
|
+
value: "mcps"
|
|
2037
|
+
},
|
|
2038
|
+
{
|
|
2039
|
+
name: i18n.t("uninstall:ccr"),
|
|
2040
|
+
value: "ccr"
|
|
2041
|
+
},
|
|
2042
|
+
{
|
|
2043
|
+
name: i18n.t("uninstall:ccline"),
|
|
2044
|
+
value: "ccline"
|
|
2045
|
+
},
|
|
2046
|
+
{
|
|
2047
|
+
name: i18n.t("uninstall:claudeCode"),
|
|
2048
|
+
value: "claude-code"
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
name: i18n.t("uninstall:backups"),
|
|
2052
|
+
value: "backups"
|
|
2053
|
+
},
|
|
2054
|
+
{
|
|
2055
|
+
name: i18n.t("uninstall:zcfConfig"),
|
|
2056
|
+
value: "zcf-config"
|
|
2057
|
+
}
|
|
2058
|
+
],
|
|
2059
|
+
validate: (answers) => {
|
|
2060
|
+
if (answers.length === 0) {
|
|
2061
|
+
return i18n.t("uninstall:selectAtLeastOne");
|
|
2062
|
+
}
|
|
2063
|
+
return true;
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
if (!customItems || customItems.length === 0) {
|
|
2067
|
+
console.log(ansis.yellow(i18n.t("common:cancelled")));
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
await executeCustomUninstall(uninstaller, customItems);
|
|
2071
|
+
}
|
|
2072
|
+
async function executeCompleteUninstall(uninstaller) {
|
|
2073
|
+
console.log("");
|
|
2074
|
+
console.log(ansis.red.bold(i18n.t("uninstall:executingComplete")));
|
|
2075
|
+
console.log(ansis.yellow(i18n.t("uninstall:completeWarning")));
|
|
2076
|
+
const confirm = await promptBoolean({
|
|
2077
|
+
message: i18n.t("uninstall:confirmComplete"),
|
|
2078
|
+
defaultValue: false
|
|
2079
|
+
});
|
|
2080
|
+
if (!confirm) {
|
|
2081
|
+
console.log(ansis.yellow(i18n.t("common:cancelled")));
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
console.log("");
|
|
2085
|
+
console.log(ansis.cyan(i18n.t("uninstall:processingComplete")));
|
|
2086
|
+
const result = await uninstaller.completeUninstall();
|
|
2087
|
+
displayUninstallResult("complete", [result]);
|
|
2088
|
+
}
|
|
2089
|
+
async function executeCustomUninstall(uninstaller, items) {
|
|
2090
|
+
console.log("");
|
|
2091
|
+
console.log(ansis.cyan(i18n.t("uninstall:executingCustom")));
|
|
2092
|
+
console.log(ansis.gray(i18n.t("uninstall:selectedItems")));
|
|
2093
|
+
items.forEach((item) => {
|
|
2094
|
+
console.log(` \u2022 ${i18n.t(`uninstall:${item}`)}`);
|
|
2095
|
+
});
|
|
2096
|
+
const confirm = await promptBoolean({
|
|
2097
|
+
message: i18n.t("uninstall:confirmCustom"),
|
|
2098
|
+
defaultValue: false
|
|
2099
|
+
});
|
|
2100
|
+
if (!confirm) {
|
|
2101
|
+
console.log(ansis.yellow(i18n.t("common:cancelled")));
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
console.log("");
|
|
2105
|
+
console.log(ansis.cyan(i18n.t("uninstall:processingCustom")));
|
|
2106
|
+
const results = await uninstaller.customUninstall(items);
|
|
2107
|
+
displayUninstallResult("custom", results);
|
|
2108
|
+
}
|
|
2109
|
+
function displayUninstallResult(mode, results) {
|
|
2110
|
+
console.log("");
|
|
2111
|
+
console.log(ansis.cyan("\u2500".repeat(50)));
|
|
2112
|
+
let totalSuccess = 0;
|
|
2113
|
+
let totalErrors = 0;
|
|
2114
|
+
let totalWarnings = 0;
|
|
2115
|
+
results.forEach((result) => {
|
|
2116
|
+
if (result.success) {
|
|
2117
|
+
totalSuccess++;
|
|
2118
|
+
}
|
|
2119
|
+
if (result.removed && result.removed.length > 0) {
|
|
2120
|
+
console.log(ansis.green(`\u{1F5D1}\uFE0F ${i18n.t("uninstall:movedToTrash")}:`));
|
|
2121
|
+
result.removed.forEach((item) => {
|
|
2122
|
+
console.log(ansis.gray(` \u2022 ${item}`));
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
if (result.removedConfigs && result.removedConfigs.length > 0) {
|
|
2126
|
+
console.log(ansis.green(`\u2714 ${i18n.t("uninstall:removedConfigs")}:`));
|
|
2127
|
+
result.removedConfigs.forEach((item) => {
|
|
2128
|
+
console.log(ansis.gray(` \u2022 ${item}`));
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
if (result.errors && result.errors.length > 0) {
|
|
2132
|
+
totalErrors += result.errors.length;
|
|
2133
|
+
console.log(ansis.red(`\u2716 ${i18n.t("uninstall:errors")}:`));
|
|
2134
|
+
result.errors.forEach((error) => {
|
|
2135
|
+
console.log(ansis.red(` \u2022 ${error}`));
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
2139
|
+
totalWarnings += result.warnings.length;
|
|
2140
|
+
console.log(ansis.yellow(`\u26A0 ${i18n.t("uninstall:warnings")}:`));
|
|
2141
|
+
result.warnings.forEach((warning) => {
|
|
2142
|
+
console.log(ansis.yellow(` \u2022 ${warning}`));
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
});
|
|
2146
|
+
const totalRemovedFiles = results.reduce((count, result) => count + (result.removed?.length || 0), 0);
|
|
2147
|
+
const totalRemovedConfigs = results.reduce((count, result) => count + (result.removedConfigs?.length || 0), 0);
|
|
2148
|
+
console.log("");
|
|
2149
|
+
console.log(ansis.cyan("\u2500".repeat(50)));
|
|
2150
|
+
if (mode === "complete") {
|
|
2151
|
+
if (totalErrors === 0) {
|
|
2152
|
+
console.log(ansis.green.bold(`\u2714 ${i18n.t("uninstall:completeSuccess")}`));
|
|
2153
|
+
} else {
|
|
2154
|
+
console.log(ansis.yellow.bold(`\u26A0 ${i18n.t("uninstall:completePartialSuccess")}`));
|
|
2155
|
+
}
|
|
2156
|
+
} else {
|
|
2157
|
+
if (totalRemovedFiles > 0 && totalRemovedConfigs > 0) {
|
|
2158
|
+
console.log(ansis.green.bold(`\u2714 ${i18n.t("uninstall:customSuccessBoth", {
|
|
2159
|
+
fileCount: totalRemovedFiles,
|
|
2160
|
+
configCount: totalRemovedConfigs
|
|
2161
|
+
})}`));
|
|
2162
|
+
} else if (totalRemovedFiles > 0) {
|
|
2163
|
+
console.log(ansis.green.bold(`\u2714 ${i18n.t("uninstall:customSuccessFiles", {
|
|
2164
|
+
count: totalRemovedFiles
|
|
2165
|
+
})}`));
|
|
2166
|
+
} else if (totalRemovedConfigs > 0) {
|
|
2167
|
+
console.log(ansis.green.bold(`\u2714 ${i18n.t("uninstall:customSuccessConfigs", {
|
|
2168
|
+
count: totalRemovedConfigs
|
|
2169
|
+
})}`));
|
|
2170
|
+
} else {
|
|
2171
|
+
console.log(ansis.green.bold(`\u2714 ${i18n.t("uninstall:customSuccess", { count: totalSuccess })}`));
|
|
2172
|
+
}
|
|
2173
|
+
if (totalErrors > 0) {
|
|
2174
|
+
console.log(ansis.red(`\u2716 ${i18n.t("uninstall:errorsCount", { count: totalErrors })}`));
|
|
2175
|
+
}
|
|
2176
|
+
if (totalWarnings > 0) {
|
|
2177
|
+
console.log(ansis.yellow(`\u26A0 ${i18n.t("uninstall:warningsCount", { count: totalWarnings })}`));
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
console.log("");
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
function printSeparator() {
|
|
2184
|
+
console.log(`
|
|
2185
|
+
${ansis.dim("\u2500".repeat(50))}
|
|
2186
|
+
`);
|
|
2187
|
+
}
|
|
2188
|
+
async function showMenu() {
|
|
2189
|
+
console.log(ansis.cyan(i18n.t("menu:selectFunction")));
|
|
2190
|
+
console.log("");
|
|
2191
|
+
console.log(
|
|
2192
|
+
` ${ansis.cyan("1.")} ${i18n.t("menu:menuOptions.configureApiOrCcr")} ${ansis.gray(`- ${i18n.t("menu:menuDescriptions.configureApiOrCcr")}`)}`
|
|
2193
|
+
);
|
|
2194
|
+
console.log("");
|
|
2195
|
+
console.log(
|
|
2196
|
+
` ${ansis.cyan("0.")} ${i18n.t("menu:menuOptions.changeLanguage")} ${ansis.gray(`- ${i18n.t("menu:menuDescriptions.changeLanguage")}`)}`
|
|
2197
|
+
);
|
|
2198
|
+
console.log(
|
|
2199
|
+
` ${ansis.cyan("-.")} ${i18n.t("menu:menuOptions.uninstall")} ${ansis.gray(`- ${i18n.t("menu:menuDescriptions.uninstall")}`)}`
|
|
2200
|
+
);
|
|
2201
|
+
console.log(
|
|
2202
|
+
` ${ansis.cyan("+.")} ${i18n.t("menu:menuOptions.checkUpdates")} ${ansis.gray(`- ${i18n.t("menu:menuDescriptions.checkUpdates")}`)}`
|
|
2203
|
+
);
|
|
2204
|
+
console.log(` ${ansis.red("Q.")} ${ansis.red(i18n.t("menu:menuOptions.exit"))}`);
|
|
2205
|
+
console.log("");
|
|
2206
|
+
const { choice } = await inquirer.prompt({
|
|
2207
|
+
type: "input",
|
|
2208
|
+
name: "choice",
|
|
2209
|
+
message: i18n.t("common:enterChoice"),
|
|
2210
|
+
validate: (value) => {
|
|
2211
|
+
const valid = ["1", "0", "-", "+", "q", "Q"];
|
|
2212
|
+
return valid.includes(value) || i18n.t("common:invalidChoice");
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
if (!choice) {
|
|
2216
|
+
console.log(ansis.yellow(i18n.t("common:cancelled")));
|
|
2217
|
+
return "exit";
|
|
2218
|
+
}
|
|
2219
|
+
const normalized = choice.toLowerCase();
|
|
2220
|
+
switch (normalized) {
|
|
2221
|
+
case "1":
|
|
2222
|
+
await configureApiFeature();
|
|
2223
|
+
break;
|
|
2224
|
+
case "0": {
|
|
2225
|
+
const currentLang = i18n.language;
|
|
2226
|
+
await changeScriptLanguageFeature(currentLang);
|
|
2227
|
+
printSeparator();
|
|
2228
|
+
return void 0;
|
|
2229
|
+
}
|
|
2230
|
+
case "-":
|
|
2231
|
+
await uninstall();
|
|
2232
|
+
printSeparator();
|
|
2233
|
+
return void 0;
|
|
2234
|
+
case "+":
|
|
2235
|
+
await checkUpdates();
|
|
2236
|
+
printSeparator();
|
|
2237
|
+
return void 0;
|
|
2238
|
+
case "q":
|
|
2239
|
+
console.log(ansis.cyan(i18n.t("common:goodbye")));
|
|
2240
|
+
return "exit";
|
|
2241
|
+
default:
|
|
2242
|
+
return void 0;
|
|
2243
|
+
}
|
|
2244
|
+
printSeparator();
|
|
2245
|
+
const shouldContinue = await promptBoolean({
|
|
2246
|
+
message: i18n.t("common:returnToMenu"),
|
|
2247
|
+
defaultValue: true
|
|
2248
|
+
});
|
|
2249
|
+
if (!shouldContinue) {
|
|
2250
|
+
console.log(ansis.cyan(i18n.t("common:goodbye")));
|
|
2251
|
+
return "exit";
|
|
2252
|
+
}
|
|
2253
|
+
return void 0;
|
|
2254
|
+
}
|
|
2255
|
+
async function showMainMenu() {
|
|
2256
|
+
try {
|
|
2257
|
+
let exitMenu = false;
|
|
2258
|
+
while (!exitMenu) {
|
|
2259
|
+
displayBannerWithInfo(CODE_TOOL_BANNERS["claude-code"] || "ccs");
|
|
2260
|
+
const result = await showMenu();
|
|
2261
|
+
if (result === "exit") {
|
|
2262
|
+
exitMenu = true;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
} catch (error) {
|
|
2266
|
+
if (!handleExitPromptError(error)) {
|
|
2267
|
+
handleGeneralError(error);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
const LANGUAGE_SELECTION_MESSAGES = {
|
|
2273
|
+
selectLanguage: "Select ZCF display language / \u9009\u62E9ZCF\u663E\u793A\u8BED\u8A00",
|
|
2274
|
+
operationCancelled: "Operation cancelled / \u64CD\u4F5C\u5DF2\u53D6\u6D88"
|
|
2275
|
+
};
|
|
2276
|
+
async function selectScriptLanguage(currentLang) {
|
|
2277
|
+
const zcfConfig = readZcfConfig();
|
|
2278
|
+
if (zcfConfig?.preferredLang) {
|
|
2279
|
+
return zcfConfig.preferredLang;
|
|
2280
|
+
}
|
|
2281
|
+
const { lang } = await inquirer.prompt({
|
|
2282
|
+
type: "list",
|
|
2283
|
+
name: "lang",
|
|
2284
|
+
message: LANGUAGE_SELECTION_MESSAGES.selectLanguage,
|
|
2285
|
+
choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
|
|
2286
|
+
name: LANG_LABELS[l],
|
|
2287
|
+
value: l
|
|
2288
|
+
})))
|
|
2289
|
+
});
|
|
2290
|
+
if (!lang) {
|
|
2291
|
+
console.log(ansis.yellow(LANGUAGE_SELECTION_MESSAGES.operationCancelled));
|
|
2292
|
+
process.exit(0);
|
|
2293
|
+
}
|
|
2294
|
+
const scriptLang = lang;
|
|
2295
|
+
updateZcfConfig({
|
|
2296
|
+
version,
|
|
2297
|
+
preferredLang: scriptLang
|
|
2298
|
+
});
|
|
2299
|
+
return scriptLang;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
async function resolveAndSwitchLanguage(lang, options, skipPrompt = false) {
|
|
2303
|
+
const zcfConfig = await readZcfConfigAsync();
|
|
2304
|
+
const targetLang = options?.allLang || lang || options?.lang || zcfConfig?.preferredLang || (skipPrompt ? "en" : await selectScriptLanguage());
|
|
2305
|
+
if (i18n.isInitialized && i18n.language !== targetLang) {
|
|
2306
|
+
await changeLanguage(targetLang);
|
|
2307
|
+
}
|
|
2308
|
+
return targetLang;
|
|
2309
|
+
}
|
|
2310
|
+
async function withLanguageResolution(action, skipPrompt = false) {
|
|
2311
|
+
return async (...args) => {
|
|
2312
|
+
const options = args[0];
|
|
2313
|
+
const languageOptions = extractLanguageOptions(options);
|
|
2314
|
+
await resolveAndSwitchLanguage(void 0, languageOptions, skipPrompt || languageOptions.skipPrompt);
|
|
2315
|
+
return await action(...args);
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
function extractLanguageOptions(options) {
|
|
2319
|
+
if (!options || typeof options !== "object" || options === null) {
|
|
2320
|
+
return {};
|
|
2321
|
+
}
|
|
2322
|
+
const obj = options;
|
|
2323
|
+
return {
|
|
2324
|
+
lang: typeof obj.lang === "string" ? obj.lang : void 0,
|
|
2325
|
+
allLang: typeof obj.allLang === "string" ? obj.allLang : void 0,
|
|
2326
|
+
skipPrompt: typeof obj.skipPrompt === "boolean" ? obj.skipPrompt : void 0
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
function customizeHelp(sections) {
|
|
2330
|
+
sections.unshift({
|
|
2331
|
+
title: "",
|
|
2332
|
+
body: ansis.cyan.bold(`ccs - Claude Code Switch v${version}`)
|
|
2333
|
+
});
|
|
2334
|
+
sections.push({
|
|
2335
|
+
title: ansis.yellow(i18n.t("cli:help.commands")),
|
|
2336
|
+
body: [
|
|
2337
|
+
` ${ansis.cyan("ccs")} ${i18n.t("cli:help.commandDescriptions.showInteractiveMenuDefault")}`,
|
|
2338
|
+
` ${ansis.cyan("ccs config-switch")} | ${ansis.cyan("cs")} ${i18n.t("cli:help.commandDescriptions.switchConfiguration")}`,
|
|
2339
|
+
` ${ansis.cyan("ccs check-updates")} | ${ansis.cyan("check")} ${i18n.t("cli:help.commandDescriptions.checkUpdateVersions")}`,
|
|
2340
|
+
` ${ansis.cyan("ccs uninstall")} ${i18n.t("cli:help.commandDescriptions.uninstallConfigurations")}`
|
|
2341
|
+
].join("\n")
|
|
2342
|
+
});
|
|
2343
|
+
sections.push({
|
|
2344
|
+
title: ansis.yellow(i18n.t("cli:help.options")),
|
|
2345
|
+
body: [
|
|
2346
|
+
` ${ansis.green("--lang, -l")} <lang> ${i18n.t("cli:help.optionDescriptions.displayLanguage")} (zh-CN, en)`,
|
|
2347
|
+
` ${ansis.green("--all-lang, -g")} <lang> ${i18n.t("cli:help.optionDescriptions.setAllLanguageParams")}`,
|
|
2348
|
+
` ${ansis.green("--list, -l")} ${i18n.t("cli:help.optionDescriptions.listConfigurations")}`,
|
|
2349
|
+
` ${ansis.green("--help, -h")} ${i18n.t("cli:help.optionDescriptions.displayHelp")}`,
|
|
2350
|
+
` ${ansis.green("--version, -v")} ${i18n.t("cli:help.optionDescriptions.displayVersion")}`
|
|
2351
|
+
].join("\n")
|
|
2352
|
+
});
|
|
2353
|
+
sections.push({
|
|
2354
|
+
title: ansis.yellow(i18n.t("cli:help.examples")),
|
|
2355
|
+
body: [
|
|
2356
|
+
ansis.gray(` # ${i18n.t("cli:help.exampleDescriptions.showInteractiveMenu")}`),
|
|
2357
|
+
` ${ansis.cyan("npx @xwm111/ccs")}`,
|
|
2358
|
+
"",
|
|
2359
|
+
ansis.gray(` # ${i18n.t("cli:help.exampleDescriptions.switchConfiguration")}`),
|
|
2360
|
+
` ${ansis.cyan("ccs config-switch --list")}`,
|
|
2361
|
+
` ${ansis.cyan("ccs cs my-endpoint")}`,
|
|
2362
|
+
"",
|
|
2363
|
+
ansis.gray(` # ${i18n.t("cli:help.exampleDescriptions.checkAndUpdateTools")}`),
|
|
2364
|
+
` ${ansis.cyan("ccs check-updates")}`,
|
|
2365
|
+
` ${ansis.cyan("ccs check")}`,
|
|
2366
|
+
"",
|
|
2367
|
+
ansis.gray(` # ${i18n.t("cli:help.exampleDescriptions.uninstallConfigurations")}`),
|
|
2368
|
+
` ${ansis.cyan("ccs uninstall")}`,
|
|
2369
|
+
""
|
|
2370
|
+
].join("\n")
|
|
2371
|
+
});
|
|
2372
|
+
return sections;
|
|
2373
|
+
}
|
|
2374
|
+
async function setupCommands(cli) {
|
|
2375
|
+
try {
|
|
2376
|
+
const zcfConfig = await readZcfConfigAsync();
|
|
2377
|
+
const defaultLang = zcfConfig?.preferredLang || "en";
|
|
2378
|
+
await initI18n(defaultLang);
|
|
2379
|
+
} catch {
|
|
2380
|
+
}
|
|
2381
|
+
cli.command("", "Show interactive menu (default)").option("--lang, -l <lang>", "Display language (zh-CN, en)").option("--all-lang, -g <lang>", "Set all language parameters to this value").action(await withLanguageResolution(async () => {
|
|
2382
|
+
await showMainMenu();
|
|
2383
|
+
}));
|
|
2384
|
+
cli.command("config-switch [target]", "Switch Claude Code API configuration, or list available configurations").alias("cs").option("--lang <lang>", "Display language (zh-CN, en)").option("--all-lang, -g <lang>", "Set all language parameters to this value").option("--list, -l", "List available configurations").action(await withLanguageResolution(async (target, options) => {
|
|
2385
|
+
await configSwitchCommand({
|
|
2386
|
+
target,
|
|
2387
|
+
list: options.list
|
|
2388
|
+
});
|
|
2389
|
+
}));
|
|
2390
|
+
cli.command("uninstall", "Remove ccs configurations and tools").option("--lang, -l <lang>", "Display language (zh-CN, en)").option("--all-lang, -g <lang>", "Set all language parameters to this value").option("--mode, -m <mode>", "Uninstall mode (complete/custom/interactive), default: interactive").option("--items, -i <items>", "Comma-separated items for custom uninstall mode").action(await withLanguageResolution(async (options) => {
|
|
2391
|
+
await uninstall(options);
|
|
2392
|
+
}));
|
|
2393
|
+
cli.command("check-updates", "Check and update Claude Code and ccs to latest versions").alias("check").option("--lang, -l <lang>", "Display language (zh-CN, en)").option("--all-lang, -g <lang>", "Set all language parameters to this value").option("--skip-prompt, -s", "Skip all interactive prompts (non-interactive mode)").action(await withLanguageResolution(async (options) => {
|
|
2394
|
+
await checkUpdates(options);
|
|
2395
|
+
}));
|
|
2396
|
+
cli.help((sections) => customizeHelp(sections));
|
|
2397
|
+
cli.version(version);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
async function main() {
|
|
2401
|
+
const cli = cac("ccs");
|
|
2402
|
+
await setupCommands(cli);
|
|
2403
|
+
cli.parse();
|
|
2404
|
+
}
|
|
2405
|
+
main().catch(console.error);
|
|
2406
|
+
|
|
2407
|
+
export { ClaudeCodeConfigManager as C, addNumbersToChoices as a, features as f };
|