aico-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/bin/aico.mjs +2 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +1475 -0
- package/dist/index.d.mts +154 -0
- package/dist/index.d.ts +154 -0
- package/dist/index.mjs +10 -0
- package/dist/shared/aico-cli.D4gky7Vp.mjs +2322 -0
- package/package.json +57 -0
- package/templates/CLAUDE.md +5 -0
- package/templates/en/memory/mcp.md +6 -0
- package/templates/en/memory/personality.md +1 -0
- package/templates/en/memory/rules.md +45 -0
- package/templates/en/memory/technical-guides.md +97 -0
- package/templates/en/workflow/bmad/commands/bmad-init.md +103 -0
- package/templates/en/workflow/git/commands/git-cleanBranches.md +101 -0
- package/templates/en/workflow/git/commands/git-commit.md +152 -0
- package/templates/en/workflow/git/commands/git-rollback.md +89 -0
- package/templates/en/workflow/plan/agents/planner.md +116 -0
- package/templates/en/workflow/plan/agents/ui-ux-designer.md +91 -0
- package/templates/en/workflow/plan/commands/feat.md +105 -0
- package/templates/en/workflow/sixStep/commands/workflow.md +230 -0
- package/templates/settings.json +33 -0
- package/templates/zh-CN/memory/mcp.md +34 -0
- package/templates/zh-CN/memory/personality.md +1 -0
- package/templates/zh-CN/memory/rules.md +45 -0
- package/templates/zh-CN/memory/technical-guides.md +126 -0
- package/templates/zh-CN/workflow/bmad/commands/bmad-init.md +109 -0
- package/templates/zh-CN/workflow/git/commands/git-cleanBranches.md +101 -0
- package/templates/zh-CN/workflow/git/commands/git-commit.md +152 -0
- package/templates/zh-CN/workflow/git/commands/git-rollback.md +90 -0
- package/templates/zh-CN/workflow/plan/agents/planner.md +116 -0
- package/templates/zh-CN/workflow/plan/agents/ui-ux-designer.md +91 -0
- package/templates/zh-CN/workflow/plan/commands/feat.md +105 -0
- package/templates/zh-CN/workflow/sixStep/commands/workflow.md +199 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,1475 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import cac from 'cac';
|
|
3
|
+
import ansis from 'ansis';
|
|
4
|
+
import { J as readZcfConfig, K as inquirer, N as addNumbersToChoices, O as updateZcfConfig, P as getTranslation, h as SUPPORTED_LANGS, j as LANG_LABELS, k as AI_OUTPUT_LANGUAGES, Q as version, q as backupExistingConfig, r as copyConfigFiles, x as applyAiLanguageDirective, R as configureAiPersonality, C as CLAUDE_DIR, w as getExistingApiConfig, s as configureApi, T as readJsonConfig, H as addCompletedOnboarding, S as SETTINGS_FILE, U as writeJsonConfig, o as openSettingsJson, b as importRecommendedPermissions, a as importRecommendedEnv, V as isWindows, z as readMcpConfig, G as fixWindowsMcpConfig, B as writeMcpConfig, W as selectMcpServices, D as backupMcpConfig, M as MCP_SERVICES, F as buildMcpServerConfig, E as mergeMcpServers, X as displayBanner, I as I18N, Y as selectAndInstallWorkflows, Z as handleExitPromptError, _ as handleGeneralError, $ as displayBannerWithInfo, a0 as readZcfConfigAsync, i as init, a1 as executeWithEscapeSupport, a2 as EscapeKeyPressed } from './shared/aico-cli.D4gky7Vp.mjs';
|
|
5
|
+
import inquirer$1 from 'inquirer';
|
|
6
|
+
import { existsSync, copyFileSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { exec } from 'node:child_process';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import dayjs from 'dayjs';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { x } from 'tinyexec';
|
|
13
|
+
import 'pathe';
|
|
14
|
+
import { exec as exec$1 } from 'child_process';
|
|
15
|
+
import { promisify as promisify$1 } from 'util';
|
|
16
|
+
import 'node:url';
|
|
17
|
+
import 'node:fs/promises';
|
|
18
|
+
|
|
19
|
+
async function selectAiOutputLanguage(scriptLang, defaultLang) {
|
|
20
|
+
const i18n = getTranslation(scriptLang);
|
|
21
|
+
console.log(ansis.dim(`
|
|
22
|
+
${i18n.language.aiOutputLangHint}
|
|
23
|
+
`));
|
|
24
|
+
const aiLangChoices = Object.entries(AI_OUTPUT_LANGUAGES).map(
|
|
25
|
+
([key, value]) => ({
|
|
26
|
+
title: value.label,
|
|
27
|
+
value: key
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
const defaultChoice = defaultLang || (scriptLang === "zh-CN" ? "zh-CN" : "en");
|
|
31
|
+
const { lang } = await inquirer.prompt({
|
|
32
|
+
type: "list",
|
|
33
|
+
name: "lang",
|
|
34
|
+
message: i18n.language.selectAiOutputLang,
|
|
35
|
+
choices: addNumbersToChoices(
|
|
36
|
+
aiLangChoices.map((choice) => ({
|
|
37
|
+
name: choice.title,
|
|
38
|
+
value: choice.value
|
|
39
|
+
}))
|
|
40
|
+
),
|
|
41
|
+
default: defaultChoice
|
|
42
|
+
});
|
|
43
|
+
if (!lang) {
|
|
44
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
let aiOutputLang = lang;
|
|
48
|
+
if (aiOutputLang === "custom") {
|
|
49
|
+
const { customLang } = await inquirer.prompt({
|
|
50
|
+
type: "input",
|
|
51
|
+
name: "customLang",
|
|
52
|
+
message: i18n.language.enterCustomLanguage,
|
|
53
|
+
validate: (value) => !!value || i18n.language?.languageRequired || "Language is required"
|
|
54
|
+
});
|
|
55
|
+
if (!customLang) {
|
|
56
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
return customLang;
|
|
60
|
+
}
|
|
61
|
+
return aiOutputLang;
|
|
62
|
+
}
|
|
63
|
+
async function selectScriptLanguage(currentLang) {
|
|
64
|
+
const zcfConfig = readZcfConfig();
|
|
65
|
+
if (zcfConfig?.preferredLang) {
|
|
66
|
+
return zcfConfig.preferredLang;
|
|
67
|
+
}
|
|
68
|
+
const { lang } = await inquirer.prompt({
|
|
69
|
+
type: "list",
|
|
70
|
+
name: "lang",
|
|
71
|
+
message: "Select AICO display language / \u9009\u62E9\u663E\u793A\u8BED\u8A00",
|
|
72
|
+
choices: addNumbersToChoices(
|
|
73
|
+
SUPPORTED_LANGS.map((l) => ({
|
|
74
|
+
name: LANG_LABELS[l],
|
|
75
|
+
value: l
|
|
76
|
+
}))
|
|
77
|
+
)
|
|
78
|
+
});
|
|
79
|
+
if (!lang) {
|
|
80
|
+
console.log(ansis.yellow("Operation cancelled / \u64CD\u4F5C\u5DF2\u53D6\u6D88"));
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
const scriptLang = lang;
|
|
84
|
+
updateZcfConfig({
|
|
85
|
+
version,
|
|
86
|
+
preferredLang: scriptLang
|
|
87
|
+
});
|
|
88
|
+
return scriptLang;
|
|
89
|
+
}
|
|
90
|
+
async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfig) {
|
|
91
|
+
const i18n = getTranslation(scriptLang);
|
|
92
|
+
if (commandLineOption) {
|
|
93
|
+
return commandLineOption;
|
|
94
|
+
}
|
|
95
|
+
if (savedConfig?.aiOutputLang) {
|
|
96
|
+
console.log(
|
|
97
|
+
ansis.gray(
|
|
98
|
+
`\u2714 ${i18n.language.aiOutputLangHint}: ${savedConfig.aiOutputLang}`
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
return savedConfig.aiOutputLang;
|
|
102
|
+
}
|
|
103
|
+
return await selectAiOutputLanguage(scriptLang, scriptLang);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function validateApiKey(apiKey, lang = "zh-CN") {
|
|
107
|
+
const i18n = getTranslation(lang);
|
|
108
|
+
if (!apiKey || apiKey.trim() === "") {
|
|
109
|
+
return {
|
|
110
|
+
isValid: false,
|
|
111
|
+
error: i18n.api.apiKeyValidation.empty
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return { isValid: true };
|
|
115
|
+
}
|
|
116
|
+
function formatApiKeyDisplay(apiKey) {
|
|
117
|
+
if (!apiKey || apiKey.length < 12) {
|
|
118
|
+
return apiKey;
|
|
119
|
+
}
|
|
120
|
+
return `${apiKey.substring(0, 8)}...${apiKey.substring(apiKey.length - 4)}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function modifyApiConfigPartially(existingConfig, i18n, scriptLang) {
|
|
124
|
+
let currentConfig = { ...existingConfig };
|
|
125
|
+
const latestConfig = getExistingApiConfig();
|
|
126
|
+
if (latestConfig) {
|
|
127
|
+
currentConfig = latestConfig;
|
|
128
|
+
}
|
|
129
|
+
const { item } = await inquirer.prompt({
|
|
130
|
+
type: "list",
|
|
131
|
+
name: "item",
|
|
132
|
+
message: i18n.api.selectModifyItems,
|
|
133
|
+
choices: addNumbersToChoices([
|
|
134
|
+
{ name: i18n.api.modifyApiUrl, value: "url" },
|
|
135
|
+
{ name: i18n.api.modifyApiKey, value: "key" },
|
|
136
|
+
{ name: i18n.api.modifyAuthType, value: "authType" }
|
|
137
|
+
])
|
|
138
|
+
});
|
|
139
|
+
if (!item) {
|
|
140
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (item === "url") {
|
|
144
|
+
const { url } = await inquirer.prompt({
|
|
145
|
+
type: "input",
|
|
146
|
+
name: "url",
|
|
147
|
+
message: i18n.api.enterNewApiUrl.replace("{url}", currentConfig.url || i18n.common.none),
|
|
148
|
+
default: currentConfig.url,
|
|
149
|
+
validate: (value) => {
|
|
150
|
+
if (!value) return i18n.api.urlRequired;
|
|
151
|
+
try {
|
|
152
|
+
new URL(value);
|
|
153
|
+
return true;
|
|
154
|
+
} catch {
|
|
155
|
+
return i18n.api.invalidUrl;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (url === void 0) {
|
|
160
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
currentConfig.url = url;
|
|
164
|
+
const savedConfig = configureApi(currentConfig);
|
|
165
|
+
if (savedConfig) {
|
|
166
|
+
console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
|
|
167
|
+
console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${savedConfig.url}`));
|
|
168
|
+
}
|
|
169
|
+
} else if (item === "key") {
|
|
170
|
+
const authType = currentConfig.authType || "auth_token";
|
|
171
|
+
const keyMessage = authType === "auth_token" ? i18n.api.enterNewApiKey.replace("{key}", currentConfig.key ? formatApiKeyDisplay(currentConfig.key) : i18n.common.none) : i18n.api.enterNewApiKey.replace("{key}", currentConfig.key ? formatApiKeyDisplay(currentConfig.key) : i18n.common.none);
|
|
172
|
+
const { key } = await inquirer.prompt({
|
|
173
|
+
type: "input",
|
|
174
|
+
name: "key",
|
|
175
|
+
message: keyMessage,
|
|
176
|
+
validate: (value) => {
|
|
177
|
+
if (!value) {
|
|
178
|
+
return i18n.api.keyRequired;
|
|
179
|
+
}
|
|
180
|
+
const validation = validateApiKey(value, scriptLang);
|
|
181
|
+
if (!validation.isValid) {
|
|
182
|
+
return validation.error || i18n.api.invalidKeyFormat;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
if (key === void 0) {
|
|
188
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
currentConfig.key = key;
|
|
192
|
+
const savedConfig = configureApi(currentConfig);
|
|
193
|
+
if (savedConfig) {
|
|
194
|
+
console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
|
|
195
|
+
console.log(ansis.gray(` ${i18n.api.apiConfigKey}: ${formatApiKeyDisplay(savedConfig.key)}`));
|
|
196
|
+
}
|
|
197
|
+
} else if (item === "authType") {
|
|
198
|
+
const { authType } = await inquirer.prompt({
|
|
199
|
+
type: "list",
|
|
200
|
+
name: "authType",
|
|
201
|
+
message: i18n.api.selectNewAuthType.replace("{type}", currentConfig.authType || i18n.common.none),
|
|
202
|
+
choices: addNumbersToChoices([
|
|
203
|
+
{ name: "Auth Token (OAuth)", value: "auth_token" },
|
|
204
|
+
{ name: "API Key", value: "api_key" }
|
|
205
|
+
]),
|
|
206
|
+
default: currentConfig.authType === "api_key" ? 1 : 0
|
|
207
|
+
});
|
|
208
|
+
if (authType === void 0) {
|
|
209
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
currentConfig.authType = authType;
|
|
213
|
+
const savedConfig = configureApi(currentConfig);
|
|
214
|
+
if (savedConfig) {
|
|
215
|
+
console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
|
|
216
|
+
console.log(ansis.gray(` ${i18n.api.apiConfigAuthType}: ${savedConfig.authType}`));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function updatePromptOnly(configLang, scriptLang, aiOutputLang) {
|
|
221
|
+
const i18n = getTranslation(scriptLang);
|
|
222
|
+
const backupDir = backupExistingConfig();
|
|
223
|
+
if (backupDir) {
|
|
224
|
+
console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
|
|
225
|
+
}
|
|
226
|
+
copyConfigFiles(configLang, true);
|
|
227
|
+
if (aiOutputLang) {
|
|
228
|
+
applyAiLanguageDirective(aiOutputLang);
|
|
229
|
+
}
|
|
230
|
+
await configureAiPersonality(scriptLang);
|
|
231
|
+
console.log(ansis.green(`\u2714 ${i18n.configuration.configSuccess} ${CLAUDE_DIR}`));
|
|
232
|
+
console.log("\n" + ansis.cyan(i18n.common.complete));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const execAsync$2 = promisify(exec);
|
|
236
|
+
async function isCcrInstalled() {
|
|
237
|
+
try {
|
|
238
|
+
await execAsync$2("ccr version");
|
|
239
|
+
return true;
|
|
240
|
+
} catch {
|
|
241
|
+
try {
|
|
242
|
+
await execAsync$2("which ccr");
|
|
243
|
+
return true;
|
|
244
|
+
} catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function installCcr(scriptLang) {
|
|
250
|
+
const i18n = getTranslation(scriptLang);
|
|
251
|
+
const installed = await isCcrInstalled();
|
|
252
|
+
if (installed) {
|
|
253
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
console.log(ansis.cyan(`\u{1F4E6} ${i18n.ccr.installingCcr}`));
|
|
257
|
+
try {
|
|
258
|
+
await execAsync$2("npm install -g claude-code-router --force");
|
|
259
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrInstallSuccess}`));
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (error.message?.includes("EEXIST")) {
|
|
262
|
+
console.log(ansis.yellow(`\u26A0 ${i18n.ccr.ccrAlreadyInstalled}`));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
console.error(ansis.red(`\u2716 ${i18n.ccr.ccrInstallFailed}`));
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const PROVIDER_PRESETS_URL = "https://pub-0dc3e1677e894f07bbea11b17a29e032.r2.dev/providers.json";
|
|
271
|
+
async function fetchProviderPresets() {
|
|
272
|
+
try {
|
|
273
|
+
const controller = new AbortController();
|
|
274
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
275
|
+
const response = await fetch(PROVIDER_PRESETS_URL, {
|
|
276
|
+
signal: controller.signal
|
|
277
|
+
});
|
|
278
|
+
clearTimeout(timeoutId);
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
281
|
+
}
|
|
282
|
+
const data = await response.json();
|
|
283
|
+
const presets = [];
|
|
284
|
+
if (Array.isArray(data)) {
|
|
285
|
+
for (const provider of data) {
|
|
286
|
+
if (provider && typeof provider === "object") {
|
|
287
|
+
presets.push({
|
|
288
|
+
name: provider.name || "",
|
|
289
|
+
provider: provider.name || "",
|
|
290
|
+
baseURL: provider.api_base_url || provider.baseURL || provider.url,
|
|
291
|
+
requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
|
|
292
|
+
models: provider.models || [],
|
|
293
|
+
description: provider.description || provider.name || "",
|
|
294
|
+
transformer: provider.transformer
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} else if (data && typeof data === "object") {
|
|
299
|
+
for (const [key, value] of Object.entries(data)) {
|
|
300
|
+
if (typeof value === "object" && value !== null) {
|
|
301
|
+
const provider = value;
|
|
302
|
+
presets.push({
|
|
303
|
+
name: provider.name || key,
|
|
304
|
+
provider: key,
|
|
305
|
+
baseURL: provider.api_base_url || provider.baseURL || provider.url,
|
|
306
|
+
requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
|
|
307
|
+
models: provider.models || [],
|
|
308
|
+
description: provider.description || "",
|
|
309
|
+
transformer: provider.transformer
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return presets;
|
|
315
|
+
} catch (error) {
|
|
316
|
+
return getFallbackPresets();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function getFallbackPresets() {
|
|
320
|
+
return [
|
|
321
|
+
{
|
|
322
|
+
name: "dashscope",
|
|
323
|
+
provider: "dashscope",
|
|
324
|
+
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
|
|
325
|
+
requiresApiKey: true,
|
|
326
|
+
models: ["qwen3-coder-plus"],
|
|
327
|
+
description: "Alibaba DashScope",
|
|
328
|
+
transformer: {
|
|
329
|
+
use: [["maxtoken", { max_tokens: 65536 }]],
|
|
330
|
+
"qwen3-coder-plus": {
|
|
331
|
+
use: ["enhancetool"]
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: "deepseek",
|
|
337
|
+
provider: "deepseek",
|
|
338
|
+
baseURL: "https://api.deepseek.com/chat/completions",
|
|
339
|
+
requiresApiKey: true,
|
|
340
|
+
models: ["deepseek-chat", "deepseek-reasoner"],
|
|
341
|
+
description: "DeepSeek AI models",
|
|
342
|
+
transformer: {
|
|
343
|
+
use: ["deepseek"],
|
|
344
|
+
"deepseek-chat": {
|
|
345
|
+
use: ["tooluse"]
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: "gemini",
|
|
351
|
+
provider: "gemini",
|
|
352
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/models/",
|
|
353
|
+
requiresApiKey: true,
|
|
354
|
+
models: ["gemini-2.5-flash", "gemini-2.5-pro"],
|
|
355
|
+
description: "Google Gemini models",
|
|
356
|
+
transformer: {
|
|
357
|
+
use: ["gemini"]
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "modelscope",
|
|
362
|
+
provider: "modelscope",
|
|
363
|
+
baseURL: "https://api-inference.modelscope.cn/v1/chat/completions",
|
|
364
|
+
requiresApiKey: true,
|
|
365
|
+
models: ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507", "ZhipuAI/GLM-4.5"],
|
|
366
|
+
description: "ModelScope AI models",
|
|
367
|
+
transformer: {
|
|
368
|
+
use: [["maxtoken", { max_tokens: 65536 }]],
|
|
369
|
+
"Qwen/Qwen3-Coder-480B-A35B-Instruct": {
|
|
370
|
+
use: ["enhancetool"]
|
|
371
|
+
},
|
|
372
|
+
"Qwen/Qwen3-235B-A22B-Thinking-2507": {
|
|
373
|
+
use: ["reasoning"]
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: "openrouter",
|
|
379
|
+
provider: "openrouter",
|
|
380
|
+
baseURL: "https://openrouter.ai/api/v1/chat/completions",
|
|
381
|
+
requiresApiKey: true,
|
|
382
|
+
models: [
|
|
383
|
+
"google/gemini-2.5-pro-preview",
|
|
384
|
+
"anthropic/claude-sonnet-4",
|
|
385
|
+
"anthropic/claude-3.5-sonnet",
|
|
386
|
+
"anthropic/claude-3.7-sonnet:thinking"
|
|
387
|
+
],
|
|
388
|
+
description: "OpenRouter API",
|
|
389
|
+
transformer: {
|
|
390
|
+
use: ["openrouter"]
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "siliconflow",
|
|
395
|
+
provider: "siliconflow",
|
|
396
|
+
baseURL: "https://api.siliconflow.cn/v1/chat/completions",
|
|
397
|
+
requiresApiKey: true,
|
|
398
|
+
models: ["moonshotai/Kimi-K2-Instruct"],
|
|
399
|
+
description: "SiliconFlow AI",
|
|
400
|
+
transformer: {
|
|
401
|
+
use: [["maxtoken", { max_tokens: 16384 }]]
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "volcengine",
|
|
406
|
+
provider: "volcengine",
|
|
407
|
+
baseURL: "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
|
|
408
|
+
requiresApiKey: true,
|
|
409
|
+
models: ["deepseek-v3-250324", "deepseek-r1-250528"],
|
|
410
|
+
description: "Volcengine AI",
|
|
411
|
+
transformer: {
|
|
412
|
+
use: ["deepseek"]
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
];
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const execAsync$1 = promisify(exec);
|
|
419
|
+
const CCR_CONFIG_DIR = join(homedir(), ".claude-code-router");
|
|
420
|
+
const CCR_CONFIG_FILE = join(CCR_CONFIG_DIR, "config.json");
|
|
421
|
+
const CCR_BACKUP_DIR = CCR_CONFIG_DIR;
|
|
422
|
+
function ensureCcrConfigDir() {
|
|
423
|
+
if (!existsSync(CCR_CONFIG_DIR)) {
|
|
424
|
+
mkdirSync(CCR_CONFIG_DIR, { recursive: true });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function backupCcrConfig(scriptLang) {
|
|
428
|
+
const i18n = getTranslation(scriptLang);
|
|
429
|
+
try {
|
|
430
|
+
if (!existsSync(CCR_CONFIG_FILE)) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
const timestamp = dayjs().format("YYYY-MM-DDTHH-mm-ss-SSS") + "Z";
|
|
434
|
+
const backupFileName = `config.json.${timestamp}.bak`;
|
|
435
|
+
const backupPath = join(CCR_BACKUP_DIR, backupFileName);
|
|
436
|
+
console.log(ansis.cyan(`${i18n.ccr.backupCcrConfig}`));
|
|
437
|
+
copyFileSync(CCR_CONFIG_FILE, backupPath);
|
|
438
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrBackupSuccess.replace("{path}", backupPath)}`));
|
|
439
|
+
return backupPath;
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error(ansis.red(`${i18n.ccr.ccrBackupFailed}:`), error.message);
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function readCcrConfig() {
|
|
446
|
+
if (!existsSync(CCR_CONFIG_FILE)) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
return readJsonConfig(CCR_CONFIG_FILE);
|
|
450
|
+
}
|
|
451
|
+
function writeCcrConfig(config) {
|
|
452
|
+
ensureCcrConfigDir();
|
|
453
|
+
writeJsonConfig(CCR_CONFIG_FILE, config);
|
|
454
|
+
}
|
|
455
|
+
async function configureCcrProxy(ccrConfig) {
|
|
456
|
+
const settings = readJsonConfig(SETTINGS_FILE) || {};
|
|
457
|
+
const host = ccrConfig.HOST || "127.0.0.1";
|
|
458
|
+
const port = ccrConfig.PORT || 3456;
|
|
459
|
+
const apiKey = ccrConfig.APIKEY || "sk-aico-x-ccr";
|
|
460
|
+
if (!settings.env) {
|
|
461
|
+
settings.env = {};
|
|
462
|
+
}
|
|
463
|
+
settings.env.ANTHROPIC_BASE_URL = `http://${host}:${port}`;
|
|
464
|
+
settings.env.ANTHROPIC_API_KEY = apiKey;
|
|
465
|
+
writeJsonConfig(SETTINGS_FILE, settings);
|
|
466
|
+
}
|
|
467
|
+
async function selectCcrPreset(scriptLang) {
|
|
468
|
+
const i18n = getTranslation(scriptLang);
|
|
469
|
+
console.log(ansis.cyan(`${i18n.ccr.fetchingPresets}`));
|
|
470
|
+
const presets = await fetchProviderPresets();
|
|
471
|
+
if (!presets || presets.length === 0) {
|
|
472
|
+
console.log(ansis.yellow(`${i18n.ccr.noPresetsAvailable}`));
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
const choices = [
|
|
477
|
+
...presets.map((p, index) => ({
|
|
478
|
+
name: `${index + 1}. ${p.name}`,
|
|
479
|
+
value: p
|
|
480
|
+
})),
|
|
481
|
+
{
|
|
482
|
+
name: `${presets.length + 1}. ${i18n.ccr.skipOption}`,
|
|
483
|
+
value: "skip"
|
|
484
|
+
}
|
|
485
|
+
];
|
|
486
|
+
const { preset } = await inquirer.prompt({
|
|
487
|
+
type: "list",
|
|
488
|
+
name: "preset",
|
|
489
|
+
message: i18n.ccr.selectCcrPreset,
|
|
490
|
+
choices
|
|
491
|
+
});
|
|
492
|
+
return preset;
|
|
493
|
+
} catch (error) {
|
|
494
|
+
if (error.name === "ExitPromptError") {
|
|
495
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async function configureCcrWithPreset(preset, scriptLang) {
|
|
502
|
+
const i18n = getTranslation(scriptLang);
|
|
503
|
+
const provider = {
|
|
504
|
+
name: preset.name,
|
|
505
|
+
// Use the original name from JSON
|
|
506
|
+
api_base_url: preset.baseURL || "",
|
|
507
|
+
api_key: "",
|
|
508
|
+
models: preset.models
|
|
509
|
+
};
|
|
510
|
+
if (preset.transformer) {
|
|
511
|
+
provider.transformer = preset.transformer;
|
|
512
|
+
}
|
|
513
|
+
if (preset.requiresApiKey) {
|
|
514
|
+
try {
|
|
515
|
+
const { apiKey } = await inquirer.prompt({
|
|
516
|
+
type: "input",
|
|
517
|
+
name: "apiKey",
|
|
518
|
+
message: i18n.ccr.enterApiKeyForProvider.replace("{provider}", preset.name),
|
|
519
|
+
validate: (value) => !!value || i18n.api.keyRequired
|
|
520
|
+
});
|
|
521
|
+
provider.api_key = apiKey;
|
|
522
|
+
} catch (error) {
|
|
523
|
+
if (error.name === "ExitPromptError") {
|
|
524
|
+
throw error;
|
|
525
|
+
}
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
} else {
|
|
529
|
+
provider.api_key = "sk-free";
|
|
530
|
+
}
|
|
531
|
+
let defaultModel = preset.models[0];
|
|
532
|
+
if (preset.models.length > 1) {
|
|
533
|
+
try {
|
|
534
|
+
const { model } = await inquirer.prompt({
|
|
535
|
+
type: "list",
|
|
536
|
+
name: "model",
|
|
537
|
+
message: i18n.ccr.selectDefaultModelForProvider.replace("{provider}", preset.name),
|
|
538
|
+
choices: preset.models.map((m, index) => ({
|
|
539
|
+
name: `${index + 1}. ${m}`,
|
|
540
|
+
value: m
|
|
541
|
+
}))
|
|
542
|
+
});
|
|
543
|
+
defaultModel = model;
|
|
544
|
+
} catch (error) {
|
|
545
|
+
if (error.name === "ExitPromptError") {
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const router = {
|
|
552
|
+
default: `${preset.name},${defaultModel}`,
|
|
553
|
+
// Use the original name
|
|
554
|
+
background: `${preset.name},${defaultModel}`,
|
|
555
|
+
think: `${preset.name},${defaultModel}`,
|
|
556
|
+
longContext: `${preset.name},${defaultModel}`,
|
|
557
|
+
longContextThreshold: 6e4,
|
|
558
|
+
webSearch: `${preset.name},${defaultModel}`
|
|
559
|
+
};
|
|
560
|
+
const config = {
|
|
561
|
+
LOG: true,
|
|
562
|
+
CLAUDE_PATH: "",
|
|
563
|
+
HOST: "127.0.0.1",
|
|
564
|
+
PORT: 3456,
|
|
565
|
+
APIKEY: "sk-aico-x-ccr",
|
|
566
|
+
API_TIMEOUT_MS: "600000",
|
|
567
|
+
PROXY_URL: "",
|
|
568
|
+
transformers: [],
|
|
569
|
+
Providers: [provider],
|
|
570
|
+
Router: router
|
|
571
|
+
};
|
|
572
|
+
return config;
|
|
573
|
+
}
|
|
574
|
+
async function restartAndCheckCcrStatus(scriptLang) {
|
|
575
|
+
const i18n = getTranslation(scriptLang);
|
|
576
|
+
try {
|
|
577
|
+
console.log(ansis.cyan(`${i18n.ccr.restartingCcr}`));
|
|
578
|
+
await execAsync$1("ccr restart");
|
|
579
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrRestartSuccess}`));
|
|
580
|
+
console.log(ansis.cyan(`${i18n.ccr.checkingCcrStatus}`));
|
|
581
|
+
const { stdout } = await execAsync$1("ccr status");
|
|
582
|
+
console.log(ansis.gray(stdout));
|
|
583
|
+
} catch (error) {
|
|
584
|
+
console.error(ansis.red(`${i18n.ccr.ccrRestartFailed}:`), error.message || error);
|
|
585
|
+
if (process.env.DEBUG) {
|
|
586
|
+
console.error("Full error:", error);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function showConfigurationTips(scriptLang, apiKey) {
|
|
591
|
+
const i18n = getTranslation(scriptLang);
|
|
592
|
+
console.log(ansis.bold.cyan(`
|
|
593
|
+
\u{1F4CC} ${i18n.ccr.configTips}:`));
|
|
594
|
+
console.log(ansis.blue(` \u2022 ${i18n.ccr.advancedConfigTip}`));
|
|
595
|
+
console.log(ansis.blue(` \u2022 ${i18n.ccr.manualConfigTip}`));
|
|
596
|
+
console.log(ansis.bold.yellow(` \u2022 ${i18n.ccr.useClaudeCommand}`));
|
|
597
|
+
if (apiKey) {
|
|
598
|
+
console.log(ansis.bold.green(` \u2022 ${i18n.ccr.ccrUiApiKey || "CCR UI API Key"}: ${apiKey}`));
|
|
599
|
+
console.log(ansis.gray(` ${i18n.ccr.ccrUiApiKeyHint || "Use this API key to login to CCR UI"}`));
|
|
600
|
+
}
|
|
601
|
+
console.log("");
|
|
602
|
+
}
|
|
603
|
+
async function setupCcrConfiguration(scriptLang) {
|
|
604
|
+
const i18n = getTranslation(scriptLang);
|
|
605
|
+
try {
|
|
606
|
+
const existingConfig = readCcrConfig();
|
|
607
|
+
if (existingConfig) {
|
|
608
|
+
console.log(ansis.blue(`\u2139 ${i18n.ccr.existingCcrConfig}`));
|
|
609
|
+
let shouldBackupAndReconfigure = false;
|
|
610
|
+
try {
|
|
611
|
+
const result = await inquirer.prompt({
|
|
612
|
+
type: "confirm",
|
|
613
|
+
name: "overwrite",
|
|
614
|
+
message: i18n.ccr.overwriteCcrConfig,
|
|
615
|
+
default: false
|
|
616
|
+
});
|
|
617
|
+
shouldBackupAndReconfigure = result.overwrite;
|
|
618
|
+
} catch (error) {
|
|
619
|
+
if (error.name === "ExitPromptError") {
|
|
620
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
625
|
+
if (!shouldBackupAndReconfigure) {
|
|
626
|
+
console.log(ansis.yellow(`${i18n.ccr.keepingExistingConfig}`));
|
|
627
|
+
await configureCcrProxy(existingConfig);
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
backupCcrConfig(scriptLang);
|
|
631
|
+
}
|
|
632
|
+
const preset = await selectCcrPreset(scriptLang);
|
|
633
|
+
if (!preset) {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
let config;
|
|
637
|
+
if (preset === "skip") {
|
|
638
|
+
console.log(ansis.yellow(`${i18n.ccr.skipConfiguring}`));
|
|
639
|
+
config = {
|
|
640
|
+
LOG: false,
|
|
641
|
+
CLAUDE_PATH: "",
|
|
642
|
+
HOST: "127.0.0.1",
|
|
643
|
+
PORT: 3456,
|
|
644
|
+
APIKEY: "sk-aico-x-ccr",
|
|
645
|
+
API_TIMEOUT_MS: "600000",
|
|
646
|
+
PROXY_URL: "",
|
|
647
|
+
transformers: [],
|
|
648
|
+
Providers: [],
|
|
649
|
+
// Empty providers array
|
|
650
|
+
Router: {
|
|
651
|
+
// Empty router configuration - user will configure in CCR UI
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
} else {
|
|
655
|
+
config = await configureCcrWithPreset(preset, scriptLang);
|
|
656
|
+
}
|
|
657
|
+
writeCcrConfig(config);
|
|
658
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrConfigSuccess}`));
|
|
659
|
+
await configureCcrProxy(config);
|
|
660
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.proxyConfigSuccess}`));
|
|
661
|
+
await restartAndCheckCcrStatus(scriptLang);
|
|
662
|
+
showConfigurationTips(scriptLang, config.APIKEY);
|
|
663
|
+
try {
|
|
664
|
+
addCompletedOnboarding();
|
|
665
|
+
} catch (error) {
|
|
666
|
+
console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
|
|
667
|
+
}
|
|
668
|
+
return true;
|
|
669
|
+
} catch (error) {
|
|
670
|
+
if (error.name === "ExitPromptError") {
|
|
671
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
console.error(ansis.red(`${i18n.ccr.ccrConfigFailed}:`), error);
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
async function configureCcrFeature(scriptLang) {
|
|
679
|
+
const i18n = getTranslation(scriptLang);
|
|
680
|
+
const backupDir = backupExistingConfig();
|
|
681
|
+
if (backupDir) {
|
|
682
|
+
console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
|
|
683
|
+
}
|
|
684
|
+
await setupCcrConfiguration(scriptLang);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function handleCancellation(scriptLang) {
|
|
688
|
+
const i18n = getTranslation(scriptLang);
|
|
689
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
690
|
+
}
|
|
691
|
+
async function configureApiFeature(scriptLang) {
|
|
692
|
+
const i18n = getTranslation(scriptLang);
|
|
693
|
+
const existingApiConfig = getExistingApiConfig();
|
|
694
|
+
if (existingApiConfig) {
|
|
695
|
+
console.log("\n" + ansis.blue(`\u2139 ${i18n.api.existingApiConfig}`));
|
|
696
|
+
console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${existingApiConfig.url || i18n.common.notConfigured}`));
|
|
697
|
+
console.log(ansis.gray(` ${i18n.api.apiConfigKey}: ${existingApiConfig.key ? formatApiKeyDisplay(existingApiConfig.key) : i18n.common.notConfigured}`));
|
|
698
|
+
console.log(ansis.gray(` ${i18n.api.apiConfigAuthType}: ${existingApiConfig.authType || i18n.common.notConfigured}
|
|
699
|
+
`));
|
|
700
|
+
const { action } = await inquirer.prompt({
|
|
701
|
+
type: "list",
|
|
702
|
+
name: "action",
|
|
703
|
+
message: i18n.api.selectApiAction,
|
|
704
|
+
choices: addNumbersToChoices([
|
|
705
|
+
{ name: i18n.api.keepExistingConfig, value: "keep" },
|
|
706
|
+
{ name: i18n.api.modifyAllConfig, value: "modify-all" },
|
|
707
|
+
{ name: i18n.api.modifyPartialConfig, value: "modify-partial" },
|
|
708
|
+
{ name: i18n.api.useCcrProxy, value: "use-ccr" }
|
|
709
|
+
])
|
|
710
|
+
});
|
|
711
|
+
if (!action) {
|
|
712
|
+
handleCancellation(scriptLang);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (action === "keep") {
|
|
716
|
+
console.log(ansis.green(`\u2714 ${i18n.api.keepExistingConfig}`));
|
|
717
|
+
try {
|
|
718
|
+
addCompletedOnboarding();
|
|
719
|
+
} catch (error) {
|
|
720
|
+
console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
} else if (action === "modify-partial") {
|
|
724
|
+
await modifyApiConfigPartially(existingApiConfig, i18n, scriptLang);
|
|
725
|
+
return;
|
|
726
|
+
} else if (action === "use-ccr") {
|
|
727
|
+
const ccrInstalled = await isCcrInstalled();
|
|
728
|
+
if (!ccrInstalled) {
|
|
729
|
+
console.log(ansis.yellow(`${i18n.ccr.installingCcr}`));
|
|
730
|
+
await installCcr(scriptLang);
|
|
731
|
+
} else {
|
|
732
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
|
|
733
|
+
}
|
|
734
|
+
const ccrConfigured = await setupCcrConfiguration(scriptLang);
|
|
735
|
+
if (ccrConfigured) {
|
|
736
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const { apiChoice } = await inquirer.prompt({
|
|
742
|
+
type: "list",
|
|
743
|
+
name: "apiChoice",
|
|
744
|
+
message: i18n.api.configureApi,
|
|
745
|
+
choices: addNumbersToChoices([
|
|
746
|
+
{
|
|
747
|
+
name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
|
|
748
|
+
value: "auth_token",
|
|
749
|
+
short: i18n.api.useAuthToken
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
|
|
753
|
+
value: "api_key",
|
|
754
|
+
short: i18n.api.useApiKey
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
name: `${i18n.api.useCcrProxy} - ${ansis.gray(i18n.api.ccrProxyDesc)}`,
|
|
758
|
+
value: "ccr_proxy",
|
|
759
|
+
short: i18n.api.useCcrProxy
|
|
760
|
+
},
|
|
761
|
+
{ name: i18n.api.skipApi, value: "skip" }
|
|
762
|
+
])
|
|
763
|
+
});
|
|
764
|
+
if (!apiChoice || apiChoice === "skip") {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (apiChoice === "ccr_proxy") {
|
|
768
|
+
const ccrInstalled = await isCcrInstalled();
|
|
769
|
+
if (!ccrInstalled) {
|
|
770
|
+
console.log(ansis.yellow(`${i18n.ccr.installingCcr}`));
|
|
771
|
+
await installCcr(scriptLang);
|
|
772
|
+
} else {
|
|
773
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
|
|
774
|
+
}
|
|
775
|
+
const ccrConfigured = await setupCcrConfiguration(scriptLang);
|
|
776
|
+
if (ccrConfigured) {
|
|
777
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
|
|
778
|
+
}
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const { url } = await inquirer.prompt({
|
|
782
|
+
type: "input",
|
|
783
|
+
name: "url",
|
|
784
|
+
message: i18n.api.enterApiUrl,
|
|
785
|
+
validate: (value) => {
|
|
786
|
+
if (!value) return i18n.api.urlRequired;
|
|
787
|
+
try {
|
|
788
|
+
new URL(value);
|
|
789
|
+
return true;
|
|
790
|
+
} catch {
|
|
791
|
+
return i18n.api.invalidUrl;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
if (!url) {
|
|
796
|
+
handleCancellation(scriptLang);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const keyMessage = apiChoice === "auth_token" ? i18n.api.enterAuthToken : i18n.api.enterApiKey;
|
|
800
|
+
const { key } = await inquirer.prompt({
|
|
801
|
+
type: "input",
|
|
802
|
+
name: "key",
|
|
803
|
+
message: keyMessage,
|
|
804
|
+
validate: (value) => {
|
|
805
|
+
if (!value) {
|
|
806
|
+
return i18n.api.keyRequired;
|
|
807
|
+
}
|
|
808
|
+
const validation = validateApiKey(value, scriptLang);
|
|
809
|
+
if (!validation.isValid) {
|
|
810
|
+
return validation.error || i18n.api.invalidKeyFormat;
|
|
811
|
+
}
|
|
812
|
+
return true;
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
if (!key) {
|
|
816
|
+
handleCancellation(scriptLang);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const apiConfig = { url, key, authType: apiChoice };
|
|
820
|
+
const configuredApi = configureApi(apiConfig);
|
|
821
|
+
if (configuredApi) {
|
|
822
|
+
console.log(ansis.green(`\u2714 ${i18n.api.apiConfigSuccess}`));
|
|
823
|
+
console.log(ansis.gray(` URL: ${configuredApi.url}`));
|
|
824
|
+
console.log(ansis.gray(` Key: ${formatApiKeyDisplay(configuredApi.key)}`));
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
async function configureMcpFeature(scriptLang) {
|
|
828
|
+
const i18n = getTranslation(scriptLang);
|
|
829
|
+
if (isWindows()) {
|
|
830
|
+
const { fixWindows } = await inquirer.prompt({
|
|
831
|
+
type: "confirm",
|
|
832
|
+
name: "fixWindows",
|
|
833
|
+
message: i18n.configuration.fixWindowsMcp || "Fix Windows MCP configuration?",
|
|
834
|
+
default: true
|
|
835
|
+
});
|
|
836
|
+
if (fixWindows) {
|
|
837
|
+
const existingConfig = readMcpConfig() || { mcpServers: {} };
|
|
838
|
+
const fixedConfig = fixWindowsMcpConfig(existingConfig);
|
|
839
|
+
writeMcpConfig(fixedConfig);
|
|
840
|
+
console.log(ansis.green(`\u2714 Windows MCP configuration fixed`));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
const selectedServices = await selectMcpServices(scriptLang);
|
|
844
|
+
if (!selectedServices) {
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (selectedServices.length > 0) {
|
|
848
|
+
const mcpBackupPath = backupMcpConfig();
|
|
849
|
+
if (mcpBackupPath) {
|
|
850
|
+
console.log(ansis.gray(`\u2714 ${i18n.mcp.mcpBackupSuccess}: ${mcpBackupPath}`));
|
|
851
|
+
}
|
|
852
|
+
const newServers = {};
|
|
853
|
+
for (const serviceId of selectedServices) {
|
|
854
|
+
const service = MCP_SERVICES.find((s) => s.id === serviceId);
|
|
855
|
+
if (!service) continue;
|
|
856
|
+
let config = service.config;
|
|
857
|
+
if (service.requiresApiKey) {
|
|
858
|
+
const { apiKey } = await inquirer.prompt({
|
|
859
|
+
type: "input",
|
|
860
|
+
name: "apiKey",
|
|
861
|
+
message: service.apiKeyPrompt[scriptLang],
|
|
862
|
+
validate: (value) => !!value || i18n.api.keyRequired
|
|
863
|
+
});
|
|
864
|
+
if (apiKey) {
|
|
865
|
+
config = buildMcpServerConfig(service.config, apiKey, service.apiKeyPlaceholder, service.apiKeyEnvVar);
|
|
866
|
+
} else {
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
newServers[service.id] = config;
|
|
871
|
+
}
|
|
872
|
+
const existingConfig = readMcpConfig();
|
|
873
|
+
let mergedConfig = mergeMcpServers(existingConfig, newServers);
|
|
874
|
+
mergedConfig = fixWindowsMcpConfig(mergedConfig);
|
|
875
|
+
writeMcpConfig(mergedConfig);
|
|
876
|
+
console.log(ansis.green(`\u2714 ${i18n.mcp.mcpConfigSuccess}`));
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
async function configureAiMemoryFeature(scriptLang) {
|
|
880
|
+
const i18n = getTranslation(scriptLang);
|
|
881
|
+
const { option } = await inquirer.prompt({
|
|
882
|
+
type: "list",
|
|
883
|
+
name: "option",
|
|
884
|
+
message: "Select configuration option",
|
|
885
|
+
choices: addNumbersToChoices([
|
|
886
|
+
{
|
|
887
|
+
name: i18n.configuration.configureAiLanguage || "Configure AI output language",
|
|
888
|
+
value: "language"
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
name: i18n.configuration.configureAiPersonality || "Configure AI personality",
|
|
892
|
+
value: "personality"
|
|
893
|
+
}
|
|
894
|
+
])
|
|
895
|
+
});
|
|
896
|
+
if (!option) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (option === "language") {
|
|
900
|
+
const zcfConfig = readZcfConfig();
|
|
901
|
+
const aiOutputLang = await resolveAiOutputLanguage(scriptLang, void 0, zcfConfig);
|
|
902
|
+
applyAiLanguageDirective(aiOutputLang);
|
|
903
|
+
updateZcfConfig({ aiOutputLang });
|
|
904
|
+
console.log(ansis.green(`\u2714 ${i18n.configuration.aiLanguageConfigured || "AI output language configured"}`));
|
|
905
|
+
} else {
|
|
906
|
+
await configureAiPersonality(scriptLang);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
async function configureEnvPermissionFeature(scriptLang) {
|
|
910
|
+
const i18n = getTranslation(scriptLang);
|
|
911
|
+
const { choice } = await inquirer.prompt({
|
|
912
|
+
type: "list",
|
|
913
|
+
name: "choice",
|
|
914
|
+
message: i18n.configuration?.selectEnvPermissionOption || "Select option",
|
|
915
|
+
choices: addNumbersToChoices([
|
|
916
|
+
{
|
|
917
|
+
name: `${i18n.configuration?.importRecommendedEnv || "Import environment"} ${ansis.gray("- " + (i18n.configuration?.importRecommendedEnvDesc || "Import env settings"))}`,
|
|
918
|
+
value: "env"
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
name: `${i18n.configuration?.importRecommendedPermissions || "Import permissions"} ${ansis.gray("- " + (i18n.configuration?.importRecommendedPermissionsDesc || "Import permission settings"))}`,
|
|
922
|
+
value: "permissions"
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
name: `${i18n.configuration?.openSettingsJson || "Open settings"} ${ansis.gray("- " + (i18n.configuration?.openSettingsJsonDesc || "View settings file"))}`,
|
|
926
|
+
value: "open"
|
|
927
|
+
}
|
|
928
|
+
])
|
|
929
|
+
});
|
|
930
|
+
if (!choice) {
|
|
931
|
+
handleCancellation(scriptLang);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
try {
|
|
935
|
+
switch (choice) {
|
|
936
|
+
case "env":
|
|
937
|
+
await importRecommendedEnv();
|
|
938
|
+
console.log(ansis.green(`\u2705 ${i18n.configuration.envImportSuccess}`));
|
|
939
|
+
break;
|
|
940
|
+
case "permissions":
|
|
941
|
+
await importRecommendedPermissions();
|
|
942
|
+
console.log(ansis.green(`\u2705 ${i18n.configuration?.permissionsImportSuccess || "Permissions imported"}`));
|
|
943
|
+
break;
|
|
944
|
+
case "open":
|
|
945
|
+
console.log(ansis.cyan(i18n.configuration?.openingSettingsJson || "Opening settings.json..."));
|
|
946
|
+
await openSettingsJson();
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
} catch (error) {
|
|
950
|
+
console.error(ansis.red(`${i18n.common.error}: ${error.message}`));
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
async function update(options = {}) {
|
|
955
|
+
try {
|
|
956
|
+
if (!options.skipBanner) {
|
|
957
|
+
displayBanner("Update configuration for Claude Code");
|
|
958
|
+
}
|
|
959
|
+
const scriptLang = await selectScriptLanguage();
|
|
960
|
+
const zcfConfig = readZcfConfig();
|
|
961
|
+
const i18n = I18N[scriptLang];
|
|
962
|
+
const configLang = options.configLang || "zh-CN";
|
|
963
|
+
const aiOutputLang = await resolveAiOutputLanguage(scriptLang, options.aiOutputLang, zcfConfig);
|
|
964
|
+
console.log(ansis.cyan(`
|
|
965
|
+
${i18n.workflow.updatingPrompts}
|
|
966
|
+
`));
|
|
967
|
+
await updatePromptOnly(configLang, scriptLang, aiOutputLang);
|
|
968
|
+
await selectAndInstallWorkflows(configLang, scriptLang);
|
|
969
|
+
updateZcfConfig({
|
|
970
|
+
version,
|
|
971
|
+
preferredLang: scriptLang,
|
|
972
|
+
aiOutputLang
|
|
973
|
+
});
|
|
974
|
+
} catch (error) {
|
|
975
|
+
if (!handleExitPromptError(error)) {
|
|
976
|
+
handleGeneralError(error);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
async function showMainMenu() {
|
|
982
|
+
try {
|
|
983
|
+
displayBannerWithInfo();
|
|
984
|
+
const zcfConfig = await readZcfConfigAsync();
|
|
985
|
+
let scriptLang = zcfConfig?.preferredLang || await selectScriptLanguage();
|
|
986
|
+
const menuProcessors = {
|
|
987
|
+
"1": {
|
|
988
|
+
key: "1",
|
|
989
|
+
labelKey: "fullInit",
|
|
990
|
+
processor: () => init({ lang: scriptLang, skipBanner: true }),
|
|
991
|
+
section: "\u57FA\u7840\u914D\u7F6E"
|
|
992
|
+
},
|
|
993
|
+
"2": {
|
|
994
|
+
key: "2",
|
|
995
|
+
labelKey: "importWorkflow",
|
|
996
|
+
processor: () => update({ skipBanner: true }),
|
|
997
|
+
section: "\u57FA\u7840\u914D\u7F6E"
|
|
998
|
+
},
|
|
999
|
+
"3": {
|
|
1000
|
+
key: "3",
|
|
1001
|
+
labelKey: "configureMcp",
|
|
1002
|
+
processor: () => configureMcpFeature(scriptLang),
|
|
1003
|
+
section: "\u57FA\u7840\u914D\u7F6E"
|
|
1004
|
+
},
|
|
1005
|
+
"4": {
|
|
1006
|
+
key: "4",
|
|
1007
|
+
labelKey: "configureAiMemory",
|
|
1008
|
+
processor: () => configureAiMemoryFeature(scriptLang),
|
|
1009
|
+
section: "\u57FA\u7840\u914D\u7F6E"
|
|
1010
|
+
},
|
|
1011
|
+
"5": {
|
|
1012
|
+
key: "5",
|
|
1013
|
+
labelKey: "configureApi",
|
|
1014
|
+
processor: () => configureApiFeature(scriptLang),
|
|
1015
|
+
section: "\u57FA\u7840\u914D\u7F6E"
|
|
1016
|
+
},
|
|
1017
|
+
"6": {
|
|
1018
|
+
key: "6",
|
|
1019
|
+
labelKey: "configureEnvPermission",
|
|
1020
|
+
processor: () => configureEnvPermissionFeature(scriptLang),
|
|
1021
|
+
section: "\u57FA\u7840\u914D\u7F6E"
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
let exitMenu = false;
|
|
1025
|
+
while (!exitMenu) {
|
|
1026
|
+
const i18n = I18N[scriptLang];
|
|
1027
|
+
const menuChoices = Object.values(menuProcessors).map((item) => ({
|
|
1028
|
+
name: `${item.key}. ${i18n.menu.menuOptions[item.labelKey]} - ${ansis.gray(i18n.menu.menuDescriptions[item.labelKey])}`,
|
|
1029
|
+
value: item.key,
|
|
1030
|
+
short: i18n.menu.menuOptions[item.labelKey]
|
|
1031
|
+
}));
|
|
1032
|
+
menuChoices.push({
|
|
1033
|
+
name: `0. ${i18n.common.exit || "\u9000\u51FA"}`,
|
|
1034
|
+
value: "0",
|
|
1035
|
+
short: i18n.common.exit || "\u9000\u51FA"
|
|
1036
|
+
});
|
|
1037
|
+
const { choice } = await inquirer$1.prompt({
|
|
1038
|
+
type: "list",
|
|
1039
|
+
name: "choice",
|
|
1040
|
+
message: i18n.menu.selectFunction,
|
|
1041
|
+
choices: menuChoices
|
|
1042
|
+
});
|
|
1043
|
+
if (!choice) {
|
|
1044
|
+
console.log(ansis.yellow(i18n.common.cancelled));
|
|
1045
|
+
exitMenu = true;
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1048
|
+
const menuChoice = choice.toLowerCase();
|
|
1049
|
+
if (menuChoice === "0") {
|
|
1050
|
+
exitMenu = true;
|
|
1051
|
+
console.log(ansis.cyan(i18n.common.goodbye));
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
const selectedItem = menuProcessors[choice];
|
|
1055
|
+
if (selectedItem) {
|
|
1056
|
+
const success = await executeWithEscapeSupport(
|
|
1057
|
+
selectedItem.processor,
|
|
1058
|
+
i18n
|
|
1059
|
+
);
|
|
1060
|
+
if (success) {
|
|
1061
|
+
if (selectedItem.key === "0") {
|
|
1062
|
+
try {
|
|
1063
|
+
const result = await selectedItem.processor();
|
|
1064
|
+
if (result !== scriptLang) {
|
|
1065
|
+
scriptLang = result;
|
|
1066
|
+
}
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
await handleMenuContinuation(choice, i18n);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
if (!handleExitPromptError(error)) {
|
|
1076
|
+
handleGeneralError(error);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
async function handleMenuContinuation(choice, i18n) {
|
|
1081
|
+
console.log("\n" + ansis.dim("\u2500".repeat(50)) + "\n");
|
|
1082
|
+
if (choice === "0" || choice === "-" || choice.toLowerCase() === "u" || choice.toLowerCase() === "r") {
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
try {
|
|
1086
|
+
const { continue: shouldContinue } = await inquirer.prompt({
|
|
1087
|
+
type: "confirm",
|
|
1088
|
+
name: "continue",
|
|
1089
|
+
message: i18n.common.returnToMenu,
|
|
1090
|
+
default: true
|
|
1091
|
+
});
|
|
1092
|
+
if (!shouldContinue) {
|
|
1093
|
+
console.log(ansis.cyan(i18n.common.goodbye));
|
|
1094
|
+
process.exit(0);
|
|
1095
|
+
}
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
if (error instanceof EscapeKeyPressed) {
|
|
1098
|
+
console.log(
|
|
1099
|
+
ansis.yellow(`
|
|
1100
|
+
${i18n.common.returnToPrevious || "\u8FD4\u56DE\u4E0A\u4E00\u6B65"}`)
|
|
1101
|
+
);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
throw error;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const execAsync = promisify$1(exec$1);
|
|
1109
|
+
async function runCcrUi(scriptLang, apiKey) {
|
|
1110
|
+
const i18n = I18N[scriptLang];
|
|
1111
|
+
console.log(ansis.cyan(`
|
|
1112
|
+
\u{1F5A5}\uFE0F ${i18n.ccr.startingCcrUi}`));
|
|
1113
|
+
if (apiKey) {
|
|
1114
|
+
console.log(ansis.bold.green(`
|
|
1115
|
+
\u{1F511} ${i18n.ccr.ccrUiApiKey || "CCR UI API Key"}: ${apiKey}`));
|
|
1116
|
+
console.log(ansis.gray(` ${i18n.ccr.ccrUiApiKeyHint || "Use this API key to login to CCR UI"}
|
|
1117
|
+
`));
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
const { stdout, stderr } = await execAsync("ccr ui");
|
|
1121
|
+
if (stdout) console.log(stdout);
|
|
1122
|
+
if (stderr) console.error(ansis.yellow(stderr));
|
|
1123
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrUiStarted}`));
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
|
|
1126
|
+
throw error;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
async function runCcrStatus(scriptLang) {
|
|
1130
|
+
const i18n = I18N[scriptLang];
|
|
1131
|
+
console.log(ansis.cyan(`
|
|
1132
|
+
\u{1F4CA} ${i18n.ccr.checkingCcrStatus}`));
|
|
1133
|
+
try {
|
|
1134
|
+
const { stdout, stderr } = await execAsync("ccr status");
|
|
1135
|
+
if (stdout) {
|
|
1136
|
+
console.log("\n" + ansis.bold(i18n.ccr.ccrStatusTitle));
|
|
1137
|
+
console.log(stdout);
|
|
1138
|
+
}
|
|
1139
|
+
if (stderr) console.error(ansis.yellow(stderr));
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
|
|
1142
|
+
throw error;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
async function runCcrRestart(scriptLang) {
|
|
1146
|
+
const i18n = I18N[scriptLang];
|
|
1147
|
+
console.log(ansis.cyan(`
|
|
1148
|
+
\u{1F504} ${i18n.ccr.restartingCcr}`));
|
|
1149
|
+
try {
|
|
1150
|
+
const { stdout, stderr } = await execAsync("ccr restart");
|
|
1151
|
+
if (stdout) console.log(stdout);
|
|
1152
|
+
if (stderr) console.error(ansis.yellow(stderr));
|
|
1153
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrRestarted}`));
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
|
|
1156
|
+
throw error;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
async function runCcrStart(scriptLang) {
|
|
1160
|
+
const i18n = I18N[scriptLang];
|
|
1161
|
+
console.log(ansis.cyan(`
|
|
1162
|
+
\u25B6\uFE0F ${i18n.ccr.startingCcr}`));
|
|
1163
|
+
try {
|
|
1164
|
+
const { stdout, stderr } = await execAsync("ccr start");
|
|
1165
|
+
if (stdout) console.log(stdout);
|
|
1166
|
+
if (stderr) console.error(ansis.yellow(stderr));
|
|
1167
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrStarted}`));
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
async function runCcrStop(scriptLang) {
|
|
1174
|
+
const i18n = I18N[scriptLang];
|
|
1175
|
+
console.log(ansis.cyan(`
|
|
1176
|
+
\u23F9\uFE0F ${i18n.ccr.stoppingCcr}`));
|
|
1177
|
+
try {
|
|
1178
|
+
const { stdout, stderr } = await execAsync("ccr stop");
|
|
1179
|
+
if (stdout) console.log(stdout);
|
|
1180
|
+
if (stderr) console.error(ansis.yellow(stderr));
|
|
1181
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrStopped}`));
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
|
|
1184
|
+
throw error;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function isCcrConfigured() {
|
|
1189
|
+
const CCR_CONFIG_FILE = join(homedir(), ".claude-code-router", "config.json");
|
|
1190
|
+
if (!existsSync(CCR_CONFIG_FILE)) {
|
|
1191
|
+
return false;
|
|
1192
|
+
}
|
|
1193
|
+
const config = readCcrConfig();
|
|
1194
|
+
return config !== null && config.Providers && config.Providers.length > 0;
|
|
1195
|
+
}
|
|
1196
|
+
async function showCcrMenu(scriptLang) {
|
|
1197
|
+
try {
|
|
1198
|
+
const i18n = I18N[scriptLang];
|
|
1199
|
+
console.log("\n" + ansis.cyan("\u2550".repeat(50)));
|
|
1200
|
+
console.log(ansis.bold.cyan(` ${i18n.ccr.ccrMenuTitle}`));
|
|
1201
|
+
console.log(ansis.cyan("\u2550".repeat(50)) + "\n");
|
|
1202
|
+
console.log(` ${ansis.cyan("1.")} ${i18n.ccr.ccrMenuOptions.initCcr} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.initCcr)}`);
|
|
1203
|
+
console.log(` ${ansis.cyan("2.")} ${i18n.ccr.ccrMenuOptions.startUi} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.startUi)}`);
|
|
1204
|
+
console.log(` ${ansis.cyan("3.")} ${i18n.ccr.ccrMenuOptions.checkStatus} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.checkStatus)}`);
|
|
1205
|
+
console.log(` ${ansis.cyan("4.")} ${i18n.ccr.ccrMenuOptions.restart} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.restart)}`);
|
|
1206
|
+
console.log(` ${ansis.cyan("5.")} ${i18n.ccr.ccrMenuOptions.start} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.start)}`);
|
|
1207
|
+
console.log(` ${ansis.cyan("6.")} ${i18n.ccr.ccrMenuOptions.stop} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.stop)}`);
|
|
1208
|
+
console.log(` ${ansis.yellow("0.")} ${i18n.ccr.ccrMenuOptions.back}`);
|
|
1209
|
+
console.log("");
|
|
1210
|
+
const { choice } = await inquirer.prompt({
|
|
1211
|
+
type: "input",
|
|
1212
|
+
name: "choice",
|
|
1213
|
+
message: i18n.common.enterChoice,
|
|
1214
|
+
validate: (value) => {
|
|
1215
|
+
const valid = ["1", "2", "3", "4", "5", "6", "0"];
|
|
1216
|
+
return valid.includes(value) || i18n.common.invalidChoice;
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
switch (choice) {
|
|
1220
|
+
case "1":
|
|
1221
|
+
const ccrInstalled = await isCcrInstalled();
|
|
1222
|
+
if (!ccrInstalled) {
|
|
1223
|
+
console.log(ansis.yellow(`${i18n.ccr.installingCcr}`));
|
|
1224
|
+
await installCcr(scriptLang);
|
|
1225
|
+
} else {
|
|
1226
|
+
console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
|
|
1227
|
+
}
|
|
1228
|
+
await configureCcrFeature(scriptLang);
|
|
1229
|
+
console.log(ansis.green(`
|
|
1230
|
+
\u2714 ${i18n.ccr.ccrSetupComplete}`));
|
|
1231
|
+
break;
|
|
1232
|
+
case "2":
|
|
1233
|
+
if (!isCcrConfigured()) {
|
|
1234
|
+
console.log(ansis.yellow(`
|
|
1235
|
+
\u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
|
|
1236
|
+
console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
|
|
1237
|
+
`));
|
|
1238
|
+
} else {
|
|
1239
|
+
const config = readCcrConfig();
|
|
1240
|
+
await runCcrUi(scriptLang, config?.APIKEY);
|
|
1241
|
+
}
|
|
1242
|
+
break;
|
|
1243
|
+
case "3":
|
|
1244
|
+
if (!isCcrConfigured()) {
|
|
1245
|
+
console.log(ansis.yellow(`
|
|
1246
|
+
\u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
|
|
1247
|
+
console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
|
|
1248
|
+
`));
|
|
1249
|
+
} else {
|
|
1250
|
+
await runCcrStatus(scriptLang);
|
|
1251
|
+
}
|
|
1252
|
+
break;
|
|
1253
|
+
case "4":
|
|
1254
|
+
if (!isCcrConfigured()) {
|
|
1255
|
+
console.log(ansis.yellow(`
|
|
1256
|
+
\u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
|
|
1257
|
+
console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
|
|
1258
|
+
`));
|
|
1259
|
+
} else {
|
|
1260
|
+
await runCcrRestart(scriptLang);
|
|
1261
|
+
}
|
|
1262
|
+
break;
|
|
1263
|
+
case "5":
|
|
1264
|
+
if (!isCcrConfigured()) {
|
|
1265
|
+
console.log(ansis.yellow(`
|
|
1266
|
+
\u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
|
|
1267
|
+
console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
|
|
1268
|
+
`));
|
|
1269
|
+
} else {
|
|
1270
|
+
await runCcrStart(scriptLang);
|
|
1271
|
+
}
|
|
1272
|
+
break;
|
|
1273
|
+
case "6":
|
|
1274
|
+
if (!isCcrConfigured()) {
|
|
1275
|
+
console.log(ansis.yellow(`
|
|
1276
|
+
\u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
|
|
1277
|
+
console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
|
|
1278
|
+
`));
|
|
1279
|
+
} else {
|
|
1280
|
+
await runCcrStop(scriptLang);
|
|
1281
|
+
}
|
|
1282
|
+
break;
|
|
1283
|
+
case "0":
|
|
1284
|
+
return false;
|
|
1285
|
+
}
|
|
1286
|
+
if (choice !== "0") {
|
|
1287
|
+
console.log("\n" + ansis.dim("\u2500".repeat(50)) + "\n");
|
|
1288
|
+
const { continueInCcr } = await inquirer.prompt({
|
|
1289
|
+
type: "confirm",
|
|
1290
|
+
name: "continueInCcr",
|
|
1291
|
+
message: i18n.common.returnToMenu || "Return to CCR menu?",
|
|
1292
|
+
default: true
|
|
1293
|
+
});
|
|
1294
|
+
if (continueInCcr) {
|
|
1295
|
+
return await showCcrMenu(scriptLang);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return false;
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
if (!handleExitPromptError(error)) {
|
|
1301
|
+
handleGeneralError(error, scriptLang);
|
|
1302
|
+
}
|
|
1303
|
+
return false;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function getValidLanguage(lang) {
|
|
1308
|
+
return lang && lang in I18N ? lang : "en";
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
async function executeCcusage(args = []) {
|
|
1312
|
+
try {
|
|
1313
|
+
let lang = "en";
|
|
1314
|
+
try {
|
|
1315
|
+
const zcfConfig = await readZcfConfigAsync();
|
|
1316
|
+
const rawLang = zcfConfig?.preferredLang || "en";
|
|
1317
|
+
lang = getValidLanguage(rawLang);
|
|
1318
|
+
} catch {
|
|
1319
|
+
lang = "en";
|
|
1320
|
+
}
|
|
1321
|
+
const i18n = I18N[lang];
|
|
1322
|
+
const command = "npx";
|
|
1323
|
+
const commandArgs = ["ccusage@latest", ...args || []];
|
|
1324
|
+
console.log(ansis.cyan(i18n.tools.runningCcusage));
|
|
1325
|
+
console.log(ansis.gray(`$ npx ccusage@latest ${(args || []).join(" ")}`));
|
|
1326
|
+
console.log("");
|
|
1327
|
+
await x(command, commandArgs, {
|
|
1328
|
+
nodeOptions: {
|
|
1329
|
+
stdio: "inherit"
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
let lang = "en";
|
|
1334
|
+
try {
|
|
1335
|
+
const zcfConfig = await readZcfConfigAsync();
|
|
1336
|
+
const rawLang = zcfConfig?.preferredLang || "en";
|
|
1337
|
+
lang = getValidLanguage(rawLang);
|
|
1338
|
+
} catch {
|
|
1339
|
+
lang = "en";
|
|
1340
|
+
}
|
|
1341
|
+
const i18n = I18N[lang];
|
|
1342
|
+
console.error(ansis.red(i18n.tools.ccusageFailed));
|
|
1343
|
+
console.error(ansis.yellow(i18n.tools.checkNetworkConnection));
|
|
1344
|
+
if (process.env.DEBUG) {
|
|
1345
|
+
console.error(ansis.gray(i18n.tools.errorDetails), error);
|
|
1346
|
+
}
|
|
1347
|
+
if (process.env.NODE_ENV !== "test") {
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1350
|
+
throw error;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
async function ccr(options = {}) {
|
|
1355
|
+
try {
|
|
1356
|
+
if (!options.skipBanner) {
|
|
1357
|
+
displayBannerWithInfo();
|
|
1358
|
+
}
|
|
1359
|
+
const zcfConfig = await readZcfConfigAsync();
|
|
1360
|
+
const scriptLang = options.lang || zcfConfig?.preferredLang || await selectScriptLanguage();
|
|
1361
|
+
const continueInCcr = await showCcrMenu(scriptLang);
|
|
1362
|
+
if (!continueInCcr && !options.skipBanner) {
|
|
1363
|
+
await showMainMenu();
|
|
1364
|
+
}
|
|
1365
|
+
} catch (error) {
|
|
1366
|
+
if (!handleExitPromptError(error)) {
|
|
1367
|
+
handleGeneralError(error, options.lang);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function setupCommands(cli) {
|
|
1373
|
+
cli.command("[lang]", "Show interactive menu (default)").option("--init", "Run full initialization directly").option("--config-lang, -c <lang>", "Configuration language (zh-CN, en)").option("--force, -f", "Force overwrite existing configuration").action(async (lang, options) => {
|
|
1374
|
+
await handleDefaultCommand(lang, options);
|
|
1375
|
+
});
|
|
1376
|
+
cli.command("init", "Initialize Claude Code configuration").alias("i").option("--lang, -l <lang>", "AICO display language (zh-CN, en)").option("--config-lang, -c <lang>", "Configuration language (zh-CN, en)").option("--ai-output-lang, -a <lang>", "AI output language").option("--force, -f", "Force overwrite existing configuration").action(async (options) => {
|
|
1377
|
+
await handleInitCommand(options);
|
|
1378
|
+
});
|
|
1379
|
+
cli.command("update", "Update Claude Code prompts only").alias("u").option("--config-lang, -c <lang>", "Configuration language (zh-CN, en)").action(async (options) => {
|
|
1380
|
+
await handleUpdateCommand(options);
|
|
1381
|
+
});
|
|
1382
|
+
cli.command("ccr", "Configure Claude Code Router for model proxy").option("--lang, -l <lang>", "Display language (zh-CN, en)").action(async (options) => {
|
|
1383
|
+
await ccr({ lang: options.lang });
|
|
1384
|
+
});
|
|
1385
|
+
cli.command("ccu [...args]", "Run Claude Code usage analysis tool").allowUnknownOptions().action(async (args) => {
|
|
1386
|
+
await executeCcusage(args);
|
|
1387
|
+
});
|
|
1388
|
+
cli.help((sections) => customizeHelp(sections));
|
|
1389
|
+
cli.version(version);
|
|
1390
|
+
}
|
|
1391
|
+
async function handleDefaultCommand(lang, options) {
|
|
1392
|
+
if (options.init) {
|
|
1393
|
+
await init({
|
|
1394
|
+
lang: lang || options.lang,
|
|
1395
|
+
configLang: options.configLang,
|
|
1396
|
+
force: options.force
|
|
1397
|
+
});
|
|
1398
|
+
} else {
|
|
1399
|
+
await showMainMenu();
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
async function handleInitCommand(options) {
|
|
1403
|
+
await init({
|
|
1404
|
+
lang: options.lang,
|
|
1405
|
+
configLang: options.configLang,
|
|
1406
|
+
aiOutputLang: options.aiOutputLang,
|
|
1407
|
+
force: options.force
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
async function handleUpdateCommand(options) {
|
|
1411
|
+
await update({ configLang: options.configLang });
|
|
1412
|
+
}
|
|
1413
|
+
function customizeHelp(sections) {
|
|
1414
|
+
sections.unshift({
|
|
1415
|
+
title: "",
|
|
1416
|
+
body: ansis.cyan.bold(`AICO - Zero-Config Claude-Code Flow v${version}`)
|
|
1417
|
+
});
|
|
1418
|
+
sections.push({
|
|
1419
|
+
title: ansis.yellow("Commands / \u547D\u4EE4:"),
|
|
1420
|
+
body: [
|
|
1421
|
+
` ${ansis.cyan("aico")} Show interactive menu (default) / \u663E\u793A\u4EA4\u4E92\u5F0F\u83DC\u5355\uFF08\u9ED8\u8BA4\uFF09`,
|
|
1422
|
+
` ${ansis.cyan("aico init")} | ${ansis.cyan(
|
|
1423
|
+
"i"
|
|
1424
|
+
)} Initialize Claude Code configuration / \u521D\u59CB\u5316 Claude Code \u914D\u7F6E`,
|
|
1425
|
+
` ${ansis.cyan("aico update")} | ${ansis.cyan("u")} Update workflow-related md files / \u4EC5\u66F4\u65B0\u5DE5\u4F5C\u6D41\u76F8\u5173md`,
|
|
1426
|
+
` ${ansis.cyan("aico ccr")} Configure Claude Code Router / \u914D\u7F6E\u6A21\u578B\u4EE3\u7406`,
|
|
1427
|
+
` ${ansis.cyan("aico ccu")} [args] Run Claude Code usage analysis / \u8FD0\u884C Claude Code \u7528\u91CF\u5206\u6790`,
|
|
1428
|
+
"",
|
|
1429
|
+
ansis.gray(" Shortcuts / \u5FEB\u6377\u65B9\u5F0F:"),
|
|
1430
|
+
` ${ansis.cyan("aico i")} Quick init / \u5FEB\u901F\u521D\u59CB\u5316`,
|
|
1431
|
+
` ${ansis.cyan("aico u")} Quick update / \u5FEB\u901F\u66F4\u65B0`
|
|
1432
|
+
].join("\n")
|
|
1433
|
+
});
|
|
1434
|
+
sections.push({
|
|
1435
|
+
title: ansis.yellow("Options / \u9009\u9879:"),
|
|
1436
|
+
body: [
|
|
1437
|
+
` ${ansis.green("--init")} Run full initialization directly / \u76F4\u63A5\u8FD0\u884C\u5B8C\u6574\u521D\u59CB\u5316`,
|
|
1438
|
+
` ${ansis.green("--config-lang, -c")} <lang> Configuration language / \u914D\u7F6E\u8BED\u8A00 (zh-CN, en)`,
|
|
1439
|
+
` ${ansis.green("--force, -f")} Force overwrite / \u5F3A\u5236\u8986\u76D6\u73B0\u6709\u914D\u7F6E`,
|
|
1440
|
+
` ${ansis.green("--help, -h")} Display help / \u663E\u793A\u5E2E\u52A9`,
|
|
1441
|
+
` ${ansis.green("--version, -v")} Display version / \u663E\u793A\u7248\u672C`
|
|
1442
|
+
].join("\n")
|
|
1443
|
+
});
|
|
1444
|
+
sections.push({
|
|
1445
|
+
title: ansis.yellow("Examples / \u793A\u4F8B:"),
|
|
1446
|
+
body: [
|
|
1447
|
+
ansis.gray(" # Show interactive menu / \u663E\u793A\u4EA4\u4E92\u5F0F\u83DC\u5355"),
|
|
1448
|
+
` ${ansis.cyan("npx aico")}`,
|
|
1449
|
+
"",
|
|
1450
|
+
ansis.gray(" # Run full initialization / \u8FD0\u884C\u5B8C\u6574\u521D\u59CB\u5316"),
|
|
1451
|
+
` ${ansis.cyan("npx aico init")}`,
|
|
1452
|
+
` ${ansis.cyan("npx aico i")}`,
|
|
1453
|
+
` ${ansis.cyan("npx aico --init")}`,
|
|
1454
|
+
"",
|
|
1455
|
+
ansis.gray(" # Update workflow-related md files only / \u4EC5\u66F4\u65B0\u5DE5\u4F5C\u6D41\u76F8\u5173md\u6587\u4EF6"),
|
|
1456
|
+
` ${ansis.cyan("npx aico u")}`,
|
|
1457
|
+
"",
|
|
1458
|
+
ansis.gray(" # Run Claude Code usage analysis / \u8FD0\u884C Claude Code \u7528\u91CF\u5206\u6790"),
|
|
1459
|
+
` ${ansis.cyan("npx aico ccu")} ${ansis.gray("# Daily usage (default)")}`,
|
|
1460
|
+
` ${ansis.cyan("npx aico ccu monthly --json")}`,
|
|
1461
|
+
"",
|
|
1462
|
+
ansis.gray(" # Force overwrite with Chinese config / \u5F3A\u5236\u4F7F\u7528\u4E2D\u6587\u914D\u7F6E\u8986\u76D6"),
|
|
1463
|
+
` ${ansis.cyan("npx aico --init -c zh-CN -f")}`,
|
|
1464
|
+
` ${ansis.cyan("npx aico --init --config-lang zh-CN --force")}`
|
|
1465
|
+
].join("\n")
|
|
1466
|
+
});
|
|
1467
|
+
return sections;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
async function main() {
|
|
1471
|
+
const cli = cac("aico");
|
|
1472
|
+
setupCommands(cli);
|
|
1473
|
+
cli.parse();
|
|
1474
|
+
}
|
|
1475
|
+
main().catch(console.error);
|