zcf 2.9.11 → 2.10.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.
@@ -1,23 +1,83 @@
1
- import inquirer from 'inquirer';
2
1
  import ansis from 'ansis';
2
+ import inquirer from 'inquirer';
3
3
  import { existsSync, mkdirSync, copyFileSync, writeFileSync, readFileSync, readdirSync, statSync } from 'node:fs';
4
4
  import { join, dirname } from 'pathe';
5
5
  import dayjs from 'dayjs';
6
+ import { exec as exec$1 } from 'node:child_process';
7
+ import { homedir, platform } from 'node:os';
8
+ import { join as join$1 } from 'node:path';
9
+ import { promisify } from 'node:util';
6
10
  import { fileURLToPath } from 'node:url';
7
- import { exec } from 'tinyexec';
8
- import { exec as exec$1 } from 'child_process';
9
- import { promisify } from 'util';
11
+ import { exec as exec$2 } from 'child_process';
12
+ import { promisify as promisify$1 } from 'util';
10
13
  import ora from 'ora';
11
14
  import semver from 'semver';
15
+ import { exec } from 'tinyexec';
12
16
  import { rm, mkdir, copyFile as copyFile$1 } from 'node:fs/promises';
13
- import { exec as exec$2 } from 'node:child_process';
14
- import { promisify as promisify$1 } from 'node:util';
15
- import { homedir, platform } from 'node:os';
16
- import { join as join$1 } from 'node:path';
17
17
 
18
- const version = "2.9.11";
18
+ const version = "2.10.1";
19
19
  const homepage = "https://github.com/UfoMiao/zcf";
20
20
 
21
+ const WORKFLOW_CONFIGS = [
22
+ {
23
+ id: "sixStepsWorkflow",
24
+ nameKey: "workflowOption.sixStepsWorkflow",
25
+ descriptionKey: "workflowDescription.sixStepsWorkflow",
26
+ defaultSelected: true,
27
+ order: 1,
28
+ commands: ["workflow.md"],
29
+ agents: [],
30
+ autoInstallAgents: false,
31
+ category: "sixStep",
32
+ outputDir: "workflow"
33
+ },
34
+ {
35
+ id: "featPlanUx",
36
+ nameKey: "workflowOption.featPlanUx",
37
+ descriptionKey: "workflowDescription.featPlanUx",
38
+ defaultSelected: true,
39
+ order: 2,
40
+ commands: ["feat.md"],
41
+ agents: [
42
+ { id: "planner", filename: "planner.md", required: true },
43
+ { id: "ui-ux-designer", filename: "ui-ux-designer.md", required: true }
44
+ ],
45
+ autoInstallAgents: true,
46
+ category: "plan",
47
+ outputDir: "feat"
48
+ },
49
+ {
50
+ id: "gitWorkflow",
51
+ nameKey: "workflowOption.gitWorkflow",
52
+ descriptionKey: "workflowDescription.gitWorkflow",
53
+ defaultSelected: true,
54
+ order: 3,
55
+ commands: ["git-commit.md", "git-rollback.md", "git-cleanBranches.md", "git-worktree.md"],
56
+ agents: [],
57
+ autoInstallAgents: false,
58
+ category: "git",
59
+ outputDir: "git"
60
+ },
61
+ {
62
+ id: "bmadWorkflow",
63
+ nameKey: "workflowOption.bmadWorkflow",
64
+ descriptionKey: "workflowDescription.bmadWorkflow",
65
+ defaultSelected: true,
66
+ order: 4,
67
+ commands: ["bmad-init.md"],
68
+ agents: [],
69
+ autoInstallAgents: false,
70
+ category: "bmad",
71
+ outputDir: "bmad"
72
+ }
73
+ ];
74
+ function getWorkflowConfig(workflowId) {
75
+ return WORKFLOW_CONFIGS.find((config) => config.id === workflowId);
76
+ }
77
+ function getOrderedWorkflows() {
78
+ return [...WORKFLOW_CONFIGS].sort((a, b) => a.order - b.order);
79
+ }
80
+
21
81
  const common$1 = {
22
82
  // Basic
23
83
  multiSelectHint: "\uFF08\u7A7A\u683C\u9009\u62E9\uFF0Ca\u5168\u9009\uFF0Ci\u53CD\u9009\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09",
@@ -1129,21 +1189,6 @@ const MCP_SERVICES = [
1129
1189
  }
1130
1190
  ];
1131
1191
 
1132
- function addNumbersToChoices(choices, startFrom = 1, format = (n) => `${n}. `) {
1133
- let currentNumber = startFrom;
1134
- return choices.map((choice) => {
1135
- if (choice.disabled) {
1136
- return choice;
1137
- }
1138
- const numbered = {
1139
- ...choice,
1140
- name: `${format(currentNumber)}${choice.name}`
1141
- };
1142
- currentNumber++;
1143
- return numbered;
1144
- });
1145
- }
1146
-
1147
1192
  class FileSystemError extends Error {
1148
1193
  constructor(message, path, cause) {
1149
1194
  super(message);
@@ -1324,6 +1369,21 @@ function updateZcfConfig(updates) {
1324
1369
  writeZcfConfig(newConfig);
1325
1370
  }
1326
1371
 
1372
+ function addNumbersToChoices(choices, startFrom = 1, format = (n) => `${n}. `) {
1373
+ let currentNumber = startFrom;
1374
+ return choices.map((choice) => {
1375
+ if (choice.disabled) {
1376
+ return choice;
1377
+ }
1378
+ const numbered = {
1379
+ ...choice,
1380
+ name: `${format(currentNumber)}${choice.name}`
1381
+ };
1382
+ currentNumber++;
1383
+ return numbered;
1384
+ });
1385
+ }
1386
+
1327
1387
  const AI_PERSONALITIES = [
1328
1388
  {
1329
1389
  id: "professional",
@@ -1370,9 +1430,29 @@ function getExistingPersonality() {
1370
1430
  function getPersonalityInfo(personalityId) {
1371
1431
  return AI_PERSONALITIES.find((p) => p.id === personalityId);
1372
1432
  }
1373
- async function configureAiPersonality(scriptLang, showExisting = true) {
1433
+ async function configureAiPersonality(scriptLang, preselectedPersonality) {
1434
+ const showExisting = typeof preselectedPersonality === "boolean" ? preselectedPersonality : true;
1435
+ const preselected = typeof preselectedPersonality === "string" ? preselectedPersonality : void 0;
1374
1436
  const i18n = getTranslation(scriptLang);
1375
1437
  const existingPersonality = getExistingPersonality();
1438
+ if (preselected) {
1439
+ let directive2 = "";
1440
+ if (preselected === "custom") {
1441
+ directive2 = "You are a helpful assistant.";
1442
+ } else {
1443
+ const selected = AI_PERSONALITIES.find((p) => p.id === preselected);
1444
+ if (selected) {
1445
+ directive2 = selected.directive[scriptLang];
1446
+ } else {
1447
+ console.error(ansis.red(`Invalid personality: ${preselected}`));
1448
+ return;
1449
+ }
1450
+ }
1451
+ await applyPersonalityDirective(directive2);
1452
+ updateZcfConfig({ aiPersonality: preselected });
1453
+ console.log(ansis.green(`\u2714 ${i18n.configuration.personalityConfigured || "AI personality configured"}`));
1454
+ return;
1455
+ }
1376
1456
  if (showExisting && existingPersonality) {
1377
1457
  const personalityInfo = getPersonalityInfo(existingPersonality);
1378
1458
  if (personalityInfo) {
@@ -1469,30 +1549,6 @@ function displayBannerWithInfo(subtitle) {
1469
1549
  `));
1470
1550
  }
1471
1551
 
1472
- function handleExitPromptError(error) {
1473
- if (error instanceof Error && error.name === "ExitPromptError") {
1474
- const zcfConfig = readZcfConfig();
1475
- const defaultLang = zcfConfig?.preferredLang || "zh-CN";
1476
- const i18n = getTranslation(defaultLang);
1477
- console.log(ansis.cyan(`
1478
- ${i18n.common.goodbye}
1479
- `));
1480
- process.exit(0);
1481
- }
1482
- return false;
1483
- }
1484
- function handleGeneralError(error, lang) {
1485
- const zcfConfig = readZcfConfig();
1486
- const defaultLang = lang || zcfConfig?.preferredLang || "en";
1487
- const i18n = getTranslation(defaultLang);
1488
- const errorMsg = i18n.common.error || "Error";
1489
- console.error(ansis.red(`${errorMsg}:`), error);
1490
- if (error instanceof Error) {
1491
- console.error(ansis.gray(`Stack: ${error.stack}`));
1492
- }
1493
- process.exit(1);
1494
- }
1495
-
1496
1552
  function mergeArraysUnique(arr1, arr2) {
1497
1553
  const combined = [...arr1 || [], ...arr2 || []];
1498
1554
  return [...new Set(combined)];
@@ -1927,247 +1983,470 @@ function applyAiLanguageDirective(aiOutputLang) {
1927
1983
  writeFile(languageFile, directive);
1928
1984
  }
1929
1985
 
1930
- function validateApiKey(apiKey, lang = "zh-CN") {
1931
- const i18n = getTranslation(lang);
1932
- if (!apiKey || apiKey.trim() === "") {
1933
- return {
1934
- isValid: false,
1935
- error: i18n.api.apiKeyValidation.empty
1936
- };
1937
- }
1938
- return { isValid: true };
1939
- }
1940
- function formatApiKeyDisplay(apiKey) {
1941
- if (!apiKey || apiKey.length < 12) {
1942
- return apiKey;
1943
- }
1944
- return `${apiKey.substring(0, 8)}...${apiKey.substring(apiKey.length - 4)}`;
1945
- }
1946
-
1947
- async function configureApiCompletely(i18n, scriptLang, preselectedAuthType) {
1948
- let authType = preselectedAuthType;
1949
- if (!authType) {
1950
- const { authType: selectedAuthType } = await inquirer.prompt({
1951
- type: "list",
1952
- name: "authType",
1953
- message: i18n.api.configureApi,
1954
- choices: addNumbersToChoices([
1955
- {
1956
- name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
1957
- value: "auth_token",
1958
- short: i18n.api.useAuthToken
1959
- },
1960
- {
1961
- name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
1962
- value: "api_key",
1963
- short: i18n.api.useApiKey
1964
- }
1965
- ])
1986
+ const PROVIDER_PRESETS_URL = "https://pub-0dc3e1677e894f07bbea11b17a29e032.r2.dev/providers.json";
1987
+ async function fetchProviderPresets() {
1988
+ try {
1989
+ const controller = new AbortController();
1990
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
1991
+ const response = await fetch(PROVIDER_PRESETS_URL, {
1992
+ signal: controller.signal
1966
1993
  });
1967
- if (!selectedAuthType) {
1968
- console.log(ansis.yellow(i18n.common.cancelled));
1969
- return null;
1970
- }
1971
- authType = selectedAuthType;
1972
- }
1973
- const { url } = await inquirer.prompt({
1974
- type: "input",
1975
- name: "url",
1976
- message: i18n.api.enterApiUrl,
1977
- validate: (value) => {
1978
- if (!value) return i18n.api.urlRequired;
1979
- try {
1980
- new URL(value);
1981
- return true;
1982
- } catch {
1983
- return i18n.api.invalidUrl;
1984
- }
1994
+ clearTimeout(timeoutId);
1995
+ if (!response.ok) {
1996
+ throw new Error(`HTTP error! status: ${response.status}`);
1985
1997
  }
1986
- });
1987
- if (url === void 0) {
1988
- console.log(ansis.yellow(i18n.common.cancelled));
1989
- return null;
1990
- }
1991
- const keyMessage = authType === "auth_token" ? i18n.api.enterAuthToken : i18n.api.enterApiKey;
1992
- const { key } = await inquirer.prompt({
1993
- type: "input",
1994
- name: "key",
1995
- message: keyMessage,
1996
- validate: (value) => {
1997
- if (!value) {
1998
- return i18n.api.keyRequired;
1998
+ const data = await response.json();
1999
+ const presets = [];
2000
+ if (Array.isArray(data)) {
2001
+ for (const provider of data) {
2002
+ if (provider && typeof provider === "object") {
2003
+ presets.push({
2004
+ name: provider.name || "",
2005
+ provider: provider.name || "",
2006
+ baseURL: provider.api_base_url || provider.baseURL || provider.url,
2007
+ requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
2008
+ models: provider.models || [],
2009
+ description: provider.description || provider.name || "",
2010
+ transformer: provider.transformer
2011
+ });
2012
+ }
1999
2013
  }
2000
- const validation = validateApiKey(value, scriptLang);
2001
- if (!validation.isValid) {
2002
- return validation.error || i18n.api.invalidKeyFormat;
2014
+ } else if (data && typeof data === "object") {
2015
+ for (const [key, value] of Object.entries(data)) {
2016
+ if (typeof value === "object" && value !== null) {
2017
+ const provider = value;
2018
+ presets.push({
2019
+ name: provider.name || key,
2020
+ provider: key,
2021
+ baseURL: provider.api_base_url || provider.baseURL || provider.url,
2022
+ requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
2023
+ models: provider.models || [],
2024
+ description: provider.description || "",
2025
+ transformer: provider.transformer
2026
+ });
2027
+ }
2003
2028
  }
2004
- return true;
2005
2029
  }
2006
- });
2007
- if (key === void 0) {
2008
- console.log(ansis.yellow(i18n.common.cancelled));
2009
- return null;
2030
+ return presets;
2031
+ } catch (error) {
2032
+ return getFallbackPresets();
2010
2033
  }
2011
- console.log(ansis.gray(` API Key: ${formatApiKeyDisplay(key)}`));
2012
- return { url, key, authType };
2013
2034
  }
2014
- async function modifyApiConfigPartially(existingConfig, i18n, scriptLang) {
2015
- let currentConfig = { ...existingConfig };
2016
- const latestConfig = getExistingApiConfig();
2017
- if (latestConfig) {
2018
- currentConfig = latestConfig;
2019
- }
2020
- const { item } = await inquirer.prompt({
2021
- type: "list",
2022
- name: "item",
2023
- message: i18n.api.selectModifyItems,
2024
- choices: addNumbersToChoices([
2025
- { name: i18n.api.modifyApiUrl, value: "url" },
2026
- { name: i18n.api.modifyApiKey, value: "key" },
2027
- { name: i18n.api.modifyAuthType, value: "authType" }
2028
- ])
2029
- });
2030
- if (!item) {
2031
- console.log(ansis.yellow(i18n.common.cancelled));
2032
- return;
2033
- }
2034
- if (item === "url") {
2035
- const { url } = await inquirer.prompt({
2036
- type: "input",
2037
- name: "url",
2038
- message: i18n.api.enterNewApiUrl.replace("{url}", currentConfig.url || i18n.common.none),
2039
- default: currentConfig.url,
2040
- validate: (value) => {
2041
- if (!value) return i18n.api.urlRequired;
2042
- try {
2043
- new URL(value);
2044
- return true;
2045
- } catch {
2046
- return i18n.api.invalidUrl;
2035
+ function getFallbackPresets() {
2036
+ return [
2037
+ {
2038
+ name: "dashscope",
2039
+ provider: "dashscope",
2040
+ baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
2041
+ requiresApiKey: true,
2042
+ models: ["qwen3-coder-plus"],
2043
+ description: "Alibaba DashScope",
2044
+ transformer: {
2045
+ use: [["maxtoken", { max_tokens: 65536 }]],
2046
+ "qwen3-coder-plus": {
2047
+ use: ["enhancetool"]
2047
2048
  }
2048
2049
  }
2049
- });
2050
- if (url === void 0) {
2051
- console.log(ansis.yellow(i18n.common.cancelled));
2052
- return;
2053
- }
2054
- currentConfig.url = url;
2055
- const savedConfig = configureApi(currentConfig);
2056
- if (savedConfig) {
2057
- console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2058
- console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${savedConfig.url}`));
2059
- }
2060
- } else if (item === "key") {
2061
- const authType = currentConfig.authType || "auth_token";
2062
- 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);
2063
- const { key } = await inquirer.prompt({
2064
- type: "input",
2065
- name: "key",
2066
- message: keyMessage,
2067
- validate: (value) => {
2068
- if (!value) {
2069
- return i18n.api.keyRequired;
2050
+ },
2051
+ {
2052
+ name: "deepseek",
2053
+ provider: "deepseek",
2054
+ baseURL: "https://api.deepseek.com/chat/completions",
2055
+ requiresApiKey: true,
2056
+ models: ["deepseek-chat", "deepseek-reasoner"],
2057
+ description: "DeepSeek AI models",
2058
+ transformer: {
2059
+ use: ["deepseek"],
2060
+ "deepseek-chat": {
2061
+ use: ["tooluse"]
2070
2062
  }
2071
- const validation = validateApiKey(value, scriptLang);
2072
- if (!validation.isValid) {
2073
- return validation.error || i18n.api.invalidKeyFormat;
2063
+ }
2064
+ },
2065
+ {
2066
+ name: "gemini",
2067
+ provider: "gemini",
2068
+ baseURL: "https://generativelanguage.googleapis.com/v1beta/models/",
2069
+ requiresApiKey: true,
2070
+ models: ["gemini-2.5-flash", "gemini-2.5-pro"],
2071
+ description: "Google Gemini models",
2072
+ transformer: {
2073
+ use: ["gemini"]
2074
+ }
2075
+ },
2076
+ {
2077
+ name: "modelscope",
2078
+ provider: "modelscope",
2079
+ baseURL: "https://api-inference.modelscope.cn/v1/chat/completions",
2080
+ requiresApiKey: true,
2081
+ models: ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507", "ZhipuAI/GLM-4.5"],
2082
+ description: "ModelScope AI models",
2083
+ transformer: {
2084
+ use: [["maxtoken", { max_tokens: 65536 }]],
2085
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct": {
2086
+ use: ["enhancetool"]
2087
+ },
2088
+ "Qwen/Qwen3-235B-A22B-Thinking-2507": {
2089
+ use: ["reasoning"]
2074
2090
  }
2075
- return true;
2076
2091
  }
2077
- });
2078
- if (key === void 0) {
2079
- console.log(ansis.yellow(i18n.common.cancelled));
2080
- return;
2081
- }
2082
- currentConfig.key = key;
2083
- const savedConfig = configureApi(currentConfig);
2084
- if (savedConfig) {
2085
- console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2086
- console.log(ansis.gray(` ${i18n.api.apiConfigKey}: ${formatApiKeyDisplay(savedConfig.key)}`));
2087
- }
2088
- } else if (item === "authType") {
2089
- const { authType } = await inquirer.prompt({
2090
- type: "list",
2091
- name: "authType",
2092
- message: i18n.api.selectNewAuthType.replace("{type}", currentConfig.authType || i18n.common.none),
2093
- choices: addNumbersToChoices([
2094
- { name: "Auth Token (OAuth)", value: "auth_token" },
2095
- { name: "API Key", value: "api_key" }
2096
- ]),
2097
- default: currentConfig.authType === "api_key" ? 1 : 0
2098
- });
2099
- if (authType === void 0) {
2100
- console.log(ansis.yellow(i18n.common.cancelled));
2101
- return;
2102
- }
2103
- currentConfig.authType = authType;
2104
- const savedConfig = configureApi(currentConfig);
2105
- if (savedConfig) {
2106
- console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2107
- console.log(ansis.gray(` ${i18n.api.apiConfigAuthType}: ${savedConfig.authType}`));
2092
+ },
2093
+ {
2094
+ name: "openrouter",
2095
+ provider: "openrouter",
2096
+ baseURL: "https://openrouter.ai/api/v1/chat/completions",
2097
+ requiresApiKey: true,
2098
+ models: [
2099
+ "google/gemini-2.5-pro-preview",
2100
+ "anthropic/claude-sonnet-4",
2101
+ "anthropic/claude-3.5-sonnet",
2102
+ "anthropic/claude-3.7-sonnet:thinking"
2103
+ ],
2104
+ description: "OpenRouter API",
2105
+ transformer: {
2106
+ use: ["openrouter"]
2107
+ }
2108
+ },
2109
+ {
2110
+ name: "siliconflow",
2111
+ provider: "siliconflow",
2112
+ baseURL: "https://api.siliconflow.cn/v1/chat/completions",
2113
+ requiresApiKey: true,
2114
+ models: ["moonshotai/Kimi-K2-Instruct"],
2115
+ description: "SiliconFlow AI",
2116
+ transformer: {
2117
+ use: [["maxtoken", { max_tokens: 16384 }]]
2118
+ }
2119
+ },
2120
+ {
2121
+ name: "volcengine",
2122
+ provider: "volcengine",
2123
+ baseURL: "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
2124
+ requiresApiKey: true,
2125
+ models: ["deepseek-v3-250324", "deepseek-r1-250528"],
2126
+ description: "Volcengine AI",
2127
+ transformer: {
2128
+ use: ["deepseek"]
2129
+ }
2108
2130
  }
2109
- }
2110
- }
2111
- async function updatePromptOnly(configLang, scriptLang, aiOutputLang) {
2112
- const i18n = getTranslation(scriptLang);
2113
- const backupDir = backupExistingConfig();
2114
- if (backupDir) {
2115
- console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
2116
- }
2117
- copyConfigFiles(configLang, true);
2118
- if (aiOutputLang) {
2119
- applyAiLanguageDirective(aiOutputLang);
2120
- }
2121
- await configureAiPersonality(scriptLang);
2122
- console.log(ansis.green(`\u2714 ${i18n.configuration.configSuccess} ${CLAUDE_DIR}`));
2123
- console.log("\n" + ansis.cyan(i18n.common.complete));
2124
- }
2125
-
2126
- function format(template, replacements) {
2127
- return template.replace(/\{(\w+)\}/g, (match, key) => {
2128
- return replacements[key] || match;
2129
- });
2131
+ ];
2130
2132
  }
2131
2133
 
2132
2134
  const execAsync$4 = promisify(exec$1);
2133
- async function getInstalledVersion(command) {
2135
+ const CCR_CONFIG_DIR = join$1(homedir(), ".claude-code-router");
2136
+ const CCR_CONFIG_FILE = join$1(CCR_CONFIG_DIR, "config.json");
2137
+ const CCR_BACKUP_DIR = CCR_CONFIG_DIR;
2138
+ function ensureCcrConfigDir() {
2139
+ if (!existsSync(CCR_CONFIG_DIR)) {
2140
+ mkdirSync(CCR_CONFIG_DIR, { recursive: true });
2141
+ }
2142
+ }
2143
+ function backupCcrConfig(scriptLang) {
2144
+ const i18n = getTranslation(scriptLang);
2134
2145
  try {
2135
- let stdout;
2136
- try {
2137
- const result = await execAsync$4(`${command} -v`);
2138
- stdout = result.stdout;
2139
- } catch {
2140
- const result = await execAsync$4(`${command} --version`);
2141
- stdout = result.stdout;
2146
+ if (!existsSync(CCR_CONFIG_FILE)) {
2147
+ return null;
2142
2148
  }
2143
- const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
2144
- return versionMatch ? versionMatch[1] : null;
2145
- } catch {
2149
+ const timestamp = dayjs().format("YYYY-MM-DDTHH-mm-ss-SSS") + "Z";
2150
+ const backupFileName = `config.json.${timestamp}.bak`;
2151
+ const backupPath = join$1(CCR_BACKUP_DIR, backupFileName);
2152
+ console.log(ansis.cyan(`${i18n.ccr.backupCcrConfig}`));
2153
+ copyFileSync(CCR_CONFIG_FILE, backupPath);
2154
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrBackupSuccess.replace("{path}", backupPath)}`));
2155
+ return backupPath;
2156
+ } catch (error) {
2157
+ console.error(ansis.red(`${i18n.ccr.ccrBackupFailed}:`), error.message);
2146
2158
  return null;
2147
2159
  }
2148
2160
  }
2149
- async function getLatestVersion(packageName) {
2150
- try {
2151
- const { stdout } = await execAsync$4(`npm view ${packageName} version`);
2152
- return stdout.trim();
2153
- } catch {
2161
+ function readCcrConfig() {
2162
+ if (!existsSync(CCR_CONFIG_FILE)) {
2154
2163
  return null;
2155
2164
  }
2165
+ return readJsonConfig(CCR_CONFIG_FILE);
2156
2166
  }
2157
- function compareVersions(current, latest) {
2158
- if (!semver.valid(current) || !semver.valid(latest)) {
2159
- return -1;
2160
- }
2161
- return semver.compare(current, latest);
2167
+ function writeCcrConfig(config) {
2168
+ ensureCcrConfigDir();
2169
+ writeJsonConfig(CCR_CONFIG_FILE, config);
2162
2170
  }
2163
- function shouldUpdate(current, latest) {
2164
- return compareVersions(current, latest) < 0;
2171
+ async function configureCcrProxy(ccrConfig) {
2172
+ const settings = readJsonConfig(SETTINGS_FILE) || {};
2173
+ const host = ccrConfig.HOST || "127.0.0.1";
2174
+ const port = ccrConfig.PORT || 3456;
2175
+ const apiKey = ccrConfig.APIKEY || "sk-zcf-x-ccr";
2176
+ if (!settings.env) {
2177
+ settings.env = {};
2178
+ }
2179
+ settings.env.ANTHROPIC_BASE_URL = `http://${host}:${port}`;
2180
+ settings.env.ANTHROPIC_API_KEY = apiKey;
2181
+ writeJsonConfig(SETTINGS_FILE, settings);
2165
2182
  }
2166
- async function checkCcrVersion() {
2167
- const currentVersion = await getInstalledVersion("ccr");
2168
- const latestVersion = await getLatestVersion("@musistudio/claude-code-router");
2169
- return {
2170
- installed: currentVersion !== null,
2183
+ async function selectCcrPreset(scriptLang) {
2184
+ const i18n = getTranslation(scriptLang);
2185
+ console.log(ansis.cyan(`${i18n.ccr.fetchingPresets}`));
2186
+ const presets = await fetchProviderPresets();
2187
+ if (!presets || presets.length === 0) {
2188
+ console.log(ansis.yellow(`${i18n.ccr.noPresetsAvailable}`));
2189
+ return null;
2190
+ }
2191
+ try {
2192
+ const choices = [
2193
+ {
2194
+ name: `1. ${i18n.ccr.skipOption}`,
2195
+ value: "skip"
2196
+ },
2197
+ ...presets.map((p, index) => ({
2198
+ name: `${index + 2}. ${p.name}`,
2199
+ value: p
2200
+ }))
2201
+ ];
2202
+ const { preset } = await inquirer.prompt({
2203
+ type: "list",
2204
+ name: "preset",
2205
+ message: i18n.ccr.selectCcrPreset,
2206
+ choices
2207
+ });
2208
+ return preset;
2209
+ } catch (error) {
2210
+ if (error.name === "ExitPromptError") {
2211
+ console.log(ansis.yellow(i18n.common.cancelled));
2212
+ return null;
2213
+ }
2214
+ throw error;
2215
+ }
2216
+ }
2217
+ async function configureCcrWithPreset(preset, scriptLang) {
2218
+ const i18n = getTranslation(scriptLang);
2219
+ const provider = {
2220
+ name: preset.name,
2221
+ // Use the original name from JSON
2222
+ api_base_url: preset.baseURL || "",
2223
+ api_key: "",
2224
+ models: preset.models
2225
+ };
2226
+ if (preset.transformer) {
2227
+ provider.transformer = preset.transformer;
2228
+ }
2229
+ if (preset.requiresApiKey) {
2230
+ try {
2231
+ const { apiKey } = await inquirer.prompt({
2232
+ type: "input",
2233
+ name: "apiKey",
2234
+ message: i18n.ccr.enterApiKeyForProvider.replace("{provider}", preset.name),
2235
+ validate: (value) => !!value || i18n.api.keyRequired
2236
+ });
2237
+ provider.api_key = apiKey;
2238
+ } catch (error) {
2239
+ if (error.name === "ExitPromptError") {
2240
+ throw error;
2241
+ }
2242
+ throw error;
2243
+ }
2244
+ } else {
2245
+ provider.api_key = "sk-free";
2246
+ }
2247
+ let defaultModel = preset.models[0];
2248
+ if (preset.models.length > 1) {
2249
+ try {
2250
+ const { model } = await inquirer.prompt({
2251
+ type: "list",
2252
+ name: "model",
2253
+ message: i18n.ccr.selectDefaultModelForProvider.replace("{provider}", preset.name),
2254
+ choices: preset.models.map((m, index) => ({
2255
+ name: `${index + 1}. ${m}`,
2256
+ value: m
2257
+ }))
2258
+ });
2259
+ defaultModel = model;
2260
+ } catch (error) {
2261
+ if (error.name === "ExitPromptError") {
2262
+ throw error;
2263
+ }
2264
+ throw error;
2265
+ }
2266
+ }
2267
+ const router = {
2268
+ default: `${preset.name},${defaultModel}`,
2269
+ // Use the original name
2270
+ background: `${preset.name},${defaultModel}`,
2271
+ think: `${preset.name},${defaultModel}`,
2272
+ longContext: `${preset.name},${defaultModel}`,
2273
+ longContextThreshold: 6e4,
2274
+ webSearch: `${preset.name},${defaultModel}`
2275
+ };
2276
+ const config = {
2277
+ LOG: true,
2278
+ CLAUDE_PATH: "",
2279
+ HOST: "127.0.0.1",
2280
+ PORT: 3456,
2281
+ APIKEY: "sk-zcf-x-ccr",
2282
+ API_TIMEOUT_MS: "600000",
2283
+ PROXY_URL: "",
2284
+ transformers: [],
2285
+ Providers: [provider],
2286
+ Router: router
2287
+ };
2288
+ return config;
2289
+ }
2290
+ async function restartAndCheckCcrStatus(scriptLang) {
2291
+ const i18n = getTranslation(scriptLang);
2292
+ try {
2293
+ console.log(ansis.cyan(`${i18n.ccr.restartingCcr}`));
2294
+ await execAsync$4("ccr restart");
2295
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrRestartSuccess}`));
2296
+ console.log(ansis.cyan(`${i18n.ccr.checkingCcrStatus}`));
2297
+ const { stdout } = await execAsync$4("ccr status");
2298
+ console.log(ansis.gray(stdout));
2299
+ } catch (error) {
2300
+ console.error(ansis.red(`${i18n.ccr.ccrRestartFailed}:`), error.message || error);
2301
+ if (process.env.DEBUG) {
2302
+ console.error("Full error:", error);
2303
+ }
2304
+ }
2305
+ }
2306
+ function showConfigurationTips(scriptLang, apiKey) {
2307
+ const i18n = getTranslation(scriptLang);
2308
+ console.log(ansis.bold.cyan(`
2309
+ \u{1F4CC} ${i18n.ccr.configTips}:`));
2310
+ console.log(ansis.blue(` \u2022 ${i18n.ccr.advancedConfigTip}`));
2311
+ console.log(ansis.blue(` \u2022 ${i18n.ccr.manualConfigTip}`));
2312
+ console.log(ansis.bold.yellow(` \u2022 ${i18n.ccr.useClaudeCommand}`));
2313
+ if (apiKey) {
2314
+ console.log(ansis.bold.green(` \u2022 ${i18n.ccr.ccrUiApiKey || "CCR UI API Key"}: ${apiKey}`));
2315
+ console.log(ansis.gray(` ${i18n.ccr.ccrUiApiKeyHint || "Use this API key to login to CCR UI"}`));
2316
+ }
2317
+ console.log("");
2318
+ }
2319
+ function createDefaultCcrConfig() {
2320
+ return {
2321
+ LOG: false,
2322
+ CLAUDE_PATH: "",
2323
+ HOST: "127.0.0.1",
2324
+ PORT: 3456,
2325
+ APIKEY: "sk-zcf-x-ccr",
2326
+ API_TIMEOUT_MS: "600000",
2327
+ PROXY_URL: "",
2328
+ transformers: [],
2329
+ Providers: [],
2330
+ // Empty providers array - user configures in UI
2331
+ Router: {}
2332
+ // Empty router configuration - user configures in UI
2333
+ };
2334
+ }
2335
+ async function setupCcrConfiguration(scriptLang) {
2336
+ const i18n = getTranslation(scriptLang);
2337
+ try {
2338
+ const existingConfig = readCcrConfig();
2339
+ if (existingConfig) {
2340
+ console.log(ansis.blue(`\u2139 ${i18n.ccr.existingCcrConfig}`));
2341
+ let shouldBackupAndReconfigure = false;
2342
+ try {
2343
+ const result = await inquirer.prompt({
2344
+ type: "confirm",
2345
+ name: "overwrite",
2346
+ message: i18n.ccr.overwriteCcrConfig,
2347
+ default: false
2348
+ });
2349
+ shouldBackupAndReconfigure = result.overwrite;
2350
+ } catch (error) {
2351
+ if (error.name === "ExitPromptError") {
2352
+ console.log(ansis.yellow(i18n.common.cancelled));
2353
+ return false;
2354
+ }
2355
+ throw error;
2356
+ }
2357
+ if (!shouldBackupAndReconfigure) {
2358
+ console.log(ansis.yellow(`${i18n.ccr.keepingExistingConfig}`));
2359
+ await configureCcrProxy(existingConfig);
2360
+ return true;
2361
+ }
2362
+ backupCcrConfig(scriptLang);
2363
+ }
2364
+ const preset = await selectCcrPreset(scriptLang);
2365
+ if (!preset) {
2366
+ return false;
2367
+ }
2368
+ let config;
2369
+ if (preset === "skip") {
2370
+ console.log(ansis.yellow(`${i18n.ccr.skipConfiguring}`));
2371
+ config = createDefaultCcrConfig();
2372
+ } else {
2373
+ config = await configureCcrWithPreset(preset, scriptLang);
2374
+ }
2375
+ writeCcrConfig(config);
2376
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrConfigSuccess}`));
2377
+ await configureCcrProxy(config);
2378
+ console.log(ansis.green(`\u2714 ${i18n.ccr.proxyConfigSuccess}`));
2379
+ await restartAndCheckCcrStatus(scriptLang);
2380
+ showConfigurationTips(scriptLang, config.APIKEY);
2381
+ try {
2382
+ addCompletedOnboarding();
2383
+ } catch (error) {
2384
+ console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
2385
+ }
2386
+ return true;
2387
+ } catch (error) {
2388
+ if (error.name === "ExitPromptError") {
2389
+ console.log(ansis.yellow(i18n.common.cancelled));
2390
+ return false;
2391
+ }
2392
+ console.error(ansis.red(`${i18n.ccr.ccrConfigFailed}:`), error);
2393
+ return false;
2394
+ }
2395
+ }
2396
+ async function configureCcrFeature(scriptLang) {
2397
+ const i18n = getTranslation(scriptLang);
2398
+ const backupDir = backupExistingConfig();
2399
+ if (backupDir) {
2400
+ console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
2401
+ }
2402
+ await setupCcrConfiguration(scriptLang);
2403
+ }
2404
+
2405
+ function format(template, replacements) {
2406
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
2407
+ return replacements[key] || match;
2408
+ });
2409
+ }
2410
+
2411
+ const execAsync$3 = promisify$1(exec$2);
2412
+ async function getInstalledVersion(command) {
2413
+ try {
2414
+ let stdout;
2415
+ try {
2416
+ const result = await execAsync$3(`${command} -v`);
2417
+ stdout = result.stdout;
2418
+ } catch {
2419
+ const result = await execAsync$3(`${command} --version`);
2420
+ stdout = result.stdout;
2421
+ }
2422
+ const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
2423
+ return versionMatch ? versionMatch[1] : null;
2424
+ } catch {
2425
+ return null;
2426
+ }
2427
+ }
2428
+ async function getLatestVersion(packageName) {
2429
+ try {
2430
+ const { stdout } = await execAsync$3(`npm view ${packageName} version`);
2431
+ return stdout.trim();
2432
+ } catch {
2433
+ return null;
2434
+ }
2435
+ }
2436
+ function compareVersions(current, latest) {
2437
+ if (!semver.valid(current) || !semver.valid(latest)) {
2438
+ return -1;
2439
+ }
2440
+ return semver.compare(current, latest);
2441
+ }
2442
+ function shouldUpdate(current, latest) {
2443
+ return compareVersions(current, latest) < 0;
2444
+ }
2445
+ async function checkCcrVersion() {
2446
+ const currentVersion = await getInstalledVersion("ccr");
2447
+ const latestVersion = await getLatestVersion("@musistudio/claude-code-router");
2448
+ return {
2449
+ installed: currentVersion !== null,
2171
2450
  currentVersion,
2172
2451
  latestVersion,
2173
2452
  needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
@@ -2184,8 +2463,8 @@ async function checkClaudeCodeVersion() {
2184
2463
  };
2185
2464
  }
2186
2465
  async function checkCometixLineVersion() {
2187
- const currentVersion = await getInstalledVersion("ccometix");
2188
- const latestVersion = await getLatestVersion("ccometix");
2466
+ const currentVersion = await getInstalledVersion("ccline");
2467
+ const latestVersion = await getLatestVersion("@cometix/ccline");
2189
2468
  return {
2190
2469
  installed: currentVersion !== null,
2191
2470
  currentVersion,
@@ -2194,7 +2473,7 @@ async function checkCometixLineVersion() {
2194
2473
  };
2195
2474
  }
2196
2475
 
2197
- const execAsync$3 = promisify(exec$1);
2476
+ const execAsync$2 = promisify$1(exec$2);
2198
2477
  async function updateCcr(scriptLang, force = false) {
2199
2478
  const i18n = getTranslation(scriptLang);
2200
2479
  const spinner = ora(i18n.updater.checkingVersion).start();
@@ -2227,7 +2506,7 @@ async function updateCcr(scriptLang, force = false) {
2227
2506
  }
2228
2507
  const updateSpinner = ora(format(i18n.updater.updating, { tool: "CCR" })).start();
2229
2508
  try {
2230
- await execAsync$3("npm update -g @musistudio/claude-code-router");
2509
+ await execAsync$2("npm update -g @musistudio/claude-code-router");
2231
2510
  updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "CCR" }));
2232
2511
  return true;
2233
2512
  } catch (error) {
@@ -2273,7 +2552,7 @@ async function updateClaudeCode(scriptLang, force = false) {
2273
2552
  }
2274
2553
  const updateSpinner = ora(format(i18n.updater.updating, { tool: "Claude Code" })).start();
2275
2554
  try {
2276
- await execAsync$3("npm update -g @anthropic-ai/claude-code");
2555
+ await execAsync$2("npm update -g @anthropic-ai/claude-code");
2277
2556
  updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "Claude Code" }));
2278
2557
  return true;
2279
2558
  } catch (error) {
@@ -2319,7 +2598,7 @@ async function updateCometixLine(scriptLang, force = false) {
2319
2598
  }
2320
2599
  const updateSpinner = ora(format(i18n.updater.updating, { tool: "CCometixLine" })).start();
2321
2600
  try {
2322
- await execAsync$3("cargo install ccometix");
2601
+ await execAsync$2("cargo install ccometix");
2323
2602
  updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "CCometixLine" }));
2324
2603
  return true;
2325
2604
  } catch (error) {
@@ -2345,927 +2624,771 @@ async function checkAndUpdateTools(scriptLang) {
2345
2624
  await updateCometixLine(scriptLang);
2346
2625
  }
2347
2626
 
2348
- async function isClaudeCodeInstalled() {
2349
- return await commandExists("claude");
2627
+ const execAsync$1 = promisify(exec$1);
2628
+ async function isCcrInstalled() {
2629
+ let commandExists = false;
2630
+ try {
2631
+ await execAsync$1("ccr version");
2632
+ commandExists = true;
2633
+ } catch {
2634
+ try {
2635
+ await execAsync$1("which ccr");
2636
+ commandExists = true;
2637
+ } catch {
2638
+ commandExists = false;
2639
+ }
2640
+ }
2641
+ let hasCorrectPackage = false;
2642
+ try {
2643
+ await execAsync$1("npm list -g @musistudio/claude-code-router");
2644
+ hasCorrectPackage = true;
2645
+ } catch {
2646
+ hasCorrectPackage = false;
2647
+ }
2648
+ return {
2649
+ isInstalled: commandExists,
2650
+ hasCorrectPackage
2651
+ };
2350
2652
  }
2351
- async function installClaudeCode(lang) {
2352
- const i18n = getTranslation(lang);
2353
- const installed = await isClaudeCodeInstalled();
2354
- if (installed) {
2355
- console.log(ansis.green(`\u2714 ${i18n.installation.alreadyInstalled}`));
2356
- await updateClaudeCode(lang);
2653
+ async function installCcr(scriptLang) {
2654
+ const i18n = getTranslation(scriptLang);
2655
+ const { isInstalled, hasCorrectPackage } = await isCcrInstalled();
2656
+ if (hasCorrectPackage) {
2657
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
2658
+ await updateCcr(scriptLang);
2357
2659
  return;
2358
2660
  }
2359
- if (isTermux()) {
2360
- console.log(ansis.yellow(`\u2139 ${i18n.installation.termuxDetected}`));
2361
- const termuxPrefix = getTermuxPrefix();
2362
- console.log(ansis.gray(i18n.installation.termuxPathInfo.replace("{path}", termuxPrefix)));
2363
- console.log(ansis.gray(`Node.js: ${termuxPrefix}/bin/node`));
2364
- console.log(ansis.gray(`npm: ${termuxPrefix}/bin/npm`));
2661
+ if (isInstalled && !hasCorrectPackage) {
2662
+ try {
2663
+ await execAsync$1("npm list -g claude-code-router");
2664
+ console.log(ansis.yellow(`\u26A0 ${i18n.ccr.detectedIncorrectPackage}`));
2665
+ try {
2666
+ await execAsync$1("npm uninstall -g claude-code-router");
2667
+ console.log(ansis.green(`\u2714 ${i18n.ccr.uninstalledIncorrectPackage}`));
2668
+ } catch (uninstallError) {
2669
+ console.log(ansis.yellow(`\u26A0 ${i18n.ccr.failedToUninstallIncorrectPackage}`));
2670
+ }
2671
+ } catch {
2672
+ }
2365
2673
  }
2366
- console.log(i18n.installation.installing);
2674
+ console.log(ansis.cyan(`\u{1F4E6} ${i18n.ccr.installingCcr}`));
2367
2675
  try {
2368
- await exec("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
2369
- console.log(`\u2714 ${i18n.installation.installSuccess}`);
2370
- if (isTermux()) {
2371
- console.log(ansis.gray(`
2372
- Claude Code installed to: ${getTermuxPrefix()}/bin/claude`));
2373
- }
2676
+ await execAsync$1("npm install -g @musistudio/claude-code-router --force");
2677
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrInstallSuccess}`));
2374
2678
  } catch (error) {
2375
- console.error(`\u2716 ${i18n.installation.installFailed}`);
2376
- if (isTermux()) {
2377
- console.error(ansis.yellow(`
2378
- ${i18n.installation.termuxInstallHint}
2379
- `));
2679
+ if (error.message?.includes("EEXIST")) {
2680
+ console.log(ansis.yellow(`\u26A0 ${i18n.ccr.ccrAlreadyInstalled}`));
2681
+ await updateCcr(scriptLang);
2682
+ return;
2380
2683
  }
2684
+ console.error(ansis.red(`\u2716 ${i18n.ccr.ccrInstallFailed}`));
2381
2685
  throw error;
2382
2686
  }
2383
2687
  }
2384
2688
 
2385
- async function selectAiOutputLanguage(scriptLang, defaultLang) {
2386
- const i18n = getTranslation(scriptLang);
2387
- console.log(ansis.dim(`
2388
- ${i18n.language.aiOutputLangHint}
2389
- `));
2390
- const aiLangChoices = Object.entries(AI_OUTPUT_LANGUAGES).map(([key, value]) => ({
2391
- title: value.label,
2392
- value: key
2393
- }));
2394
- const defaultChoice = defaultLang || (scriptLang === "zh-CN" ? "zh-CN" : "en");
2395
- const { lang } = await inquirer.prompt({
2396
- type: "list",
2397
- name: "lang",
2398
- message: i18n.language.selectAiOutputLang,
2399
- choices: addNumbersToChoices(aiLangChoices.map((choice) => ({
2400
- name: choice.title,
2401
- value: choice.value
2402
- }))),
2403
- default: defaultChoice
2404
- });
2405
- if (!lang) {
2406
- console.log(ansis.yellow(i18n.common.cancelled));
2407
- process.exit(0);
2408
- }
2409
- let aiOutputLang = lang;
2410
- if (aiOutputLang === "custom") {
2411
- const { customLang } = await inquirer.prompt({
2412
- type: "input",
2413
- name: "customLang",
2414
- message: i18n.language.enterCustomLanguage,
2415
- validate: (value) => !!value || i18n.language?.languageRequired || "Language is required"
2416
- });
2417
- if (!customLang) {
2418
- console.log(ansis.yellow(i18n.common.cancelled));
2419
- process.exit(0);
2689
+ const COMETIX_PACKAGE_NAME = "@cometix/ccline";
2690
+ const COMETIX_COMMAND_NAME = "ccline";
2691
+ const COMETIX_COMMANDS = {
2692
+ CHECK_INSTALL: `npm list -g ${COMETIX_PACKAGE_NAME}`,
2693
+ INSTALL: `npm install -g ${COMETIX_PACKAGE_NAME}`,
2694
+ UPDATE: `npm update -g ${COMETIX_PACKAGE_NAME}`,
2695
+ PRINT_CONFIG: `${COMETIX_COMMAND_NAME} --print`
2696
+ };
2697
+
2698
+ function getPlatformStatusLineConfig() {
2699
+ return {
2700
+ type: "command",
2701
+ command: isWindows() ? "%USERPROFILE%\\.claude\\ccline\\ccline.exe" : "~/.claude/ccline/ccline",
2702
+ padding: 0
2703
+ };
2704
+ }
2705
+
2706
+ function addCCometixLineConfig() {
2707
+ try {
2708
+ const statusLineConfig = getPlatformStatusLineConfig();
2709
+ let settings = {};
2710
+ if (exists(SETTINGS_FILE)) {
2711
+ settings = readJsonConfig(SETTINGS_FILE) || {};
2420
2712
  }
2421
- return customLang;
2713
+ settings.statusLine = statusLineConfig;
2714
+ writeJsonConfig(SETTINGS_FILE, settings);
2715
+ return true;
2716
+ } catch (error) {
2717
+ console.error("Failed to add CCometixLine configuration:", error);
2718
+ return false;
2422
2719
  }
2423
- return aiOutputLang;
2424
2720
  }
2425
- async function selectScriptLanguage(currentLang) {
2426
- const zcfConfig = readZcfConfig();
2427
- if (zcfConfig?.preferredLang) {
2428
- return zcfConfig.preferredLang;
2429
- }
2430
- if (currentLang) {
2431
- return currentLang;
2721
+ function hasCCometixLineConfig() {
2722
+ try {
2723
+ if (!exists(SETTINGS_FILE)) {
2724
+ return false;
2725
+ }
2726
+ const settings = readJsonConfig(SETTINGS_FILE);
2727
+ return !!settings?.statusLine?.command?.includes("ccline");
2728
+ } catch (error) {
2729
+ return false;
2432
2730
  }
2433
- const { lang } = await inquirer.prompt({
2434
- type: "list",
2435
- name: "lang",
2436
- message: "Select ZCF display language / \u9009\u62E9ZCF\u663E\u793A\u8BED\u8A00",
2437
- choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
2438
- name: LANG_LABELS[l],
2439
- value: l
2440
- })))
2441
- });
2442
- if (!lang) {
2443
- console.log(ansis.yellow("Operation cancelled / \u64CD\u4F5C\u5DF2\u53D6\u6D88"));
2444
- process.exit(0);
2731
+ }
2732
+
2733
+ const execAsync = promisify(exec$1);
2734
+ async function isCometixLineInstalled() {
2735
+ try {
2736
+ await execAsync(COMETIX_COMMANDS.CHECK_INSTALL);
2737
+ return true;
2738
+ } catch {
2739
+ return false;
2445
2740
  }
2446
- const scriptLang = lang;
2447
- updateZcfConfig({
2448
- version,
2449
- preferredLang: scriptLang
2450
- });
2451
- return scriptLang;
2452
2741
  }
2453
- async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfig) {
2742
+ async function installCometixLine(scriptLang) {
2454
2743
  const i18n = getTranslation(scriptLang);
2455
- if (commandLineOption) {
2456
- return commandLineOption;
2744
+ const isInstalled = await isCometixLineInstalled();
2745
+ if (isInstalled) {
2746
+ console.log(ansis.green(`\u2714 ${i18n.cometix.cometixAlreadyInstalled}`));
2747
+ try {
2748
+ console.log(ansis.blue(`${i18n.cometix.installingOrUpdating}`));
2749
+ await execAsync(COMETIX_COMMANDS.INSTALL);
2750
+ console.log(ansis.green(`\u2714 ${i18n.cometix.installUpdateSuccess}`));
2751
+ } catch (error) {
2752
+ console.warn(ansis.yellow(`\u26A0 ${i18n.cometix.installUpdateFailed}: ${error}`));
2753
+ }
2754
+ if (!hasCCometixLineConfig()) {
2755
+ try {
2756
+ addCCometixLineConfig();
2757
+ console.log(ansis.green(`\u2714 ${i18n.cometix.statusLineConfigured || "Claude Code statusLine configured"}`));
2758
+ } catch (error) {
2759
+ console.warn(ansis.yellow(`\u26A0 ${i18n.cometix.statusLineConfigFailed || "Failed to configure statusLine"}: ${error}`));
2760
+ }
2761
+ } else {
2762
+ console.log(ansis.blue(`\u2139 ${i18n.cometix.statusLineAlreadyConfigured || "Claude Code statusLine already configured"}`));
2763
+ }
2764
+ return;
2457
2765
  }
2458
- if (savedConfig?.aiOutputLang) {
2459
- console.log(ansis.gray(`\u2714 ${i18n.language.aiOutputLangHint}: ${savedConfig.aiOutputLang}`));
2460
- return savedConfig.aiOutputLang;
2766
+ try {
2767
+ console.log(ansis.blue(`${i18n.cometix.installingCometix}`));
2768
+ await execAsync(COMETIX_COMMANDS.INSTALL);
2769
+ console.log(ansis.green(`\u2714 ${i18n.cometix.cometixInstallSuccess}`));
2770
+ try {
2771
+ addCCometixLineConfig();
2772
+ console.log(ansis.green(`\u2714 ${i18n.cometix.statusLineConfigured || "Claude Code statusLine configured"}`));
2773
+ } catch (configError) {
2774
+ console.warn(ansis.yellow(`\u26A0 ${i18n.cometix.statusLineConfigFailed || "Failed to configure statusLine"}: ${configError}`));
2775
+ console.log(ansis.blue(`\u{1F4A1} ${i18n.cometix.statusLineManualConfig || "Please manually add statusLine configuration to Claude Code settings"}`));
2776
+ }
2777
+ } catch (error) {
2778
+ console.error(ansis.red(`\u2717 ${i18n.cometix.cometixInstallFailed}: ${error}`));
2779
+ throw error;
2461
2780
  }
2462
- return await selectAiOutputLanguage(scriptLang, scriptLang);
2463
2781
  }
2464
2782
 
2465
- const prompts = {
2466
- __proto__: null,
2467
- resolveAiOutputLanguage: resolveAiOutputLanguage,
2468
- selectAiOutputLanguage: selectAiOutputLanguage,
2469
- selectScriptLanguage: selectScriptLanguage
2470
- };
2471
-
2472
- async function selectMcpServices(scriptLang) {
2473
- const i18n = getTranslation(scriptLang);
2474
- const choices = MCP_SERVICES.map((service) => ({
2475
- name: `${service.name[scriptLang]} - ${ansis.gray(service.description[scriptLang])}`,
2476
- value: service.id,
2477
- selected: false
2478
- }));
2479
- const { services } = await inquirer.prompt({
2480
- type: "checkbox",
2481
- name: "services",
2482
- message: `${i18n.mcp.selectMcpServices}${i18n.common.multiSelectHint}`,
2483
- choices
2484
- });
2485
- if (services === void 0) {
2486
- console.log(ansis.yellow(i18n.common.cancelled));
2487
- return void 0;
2783
+ function validateApiKey(apiKey, lang = "zh-CN") {
2784
+ const i18n = getTranslation(lang);
2785
+ if (!apiKey || apiKey.trim() === "") {
2786
+ return {
2787
+ isValid: false,
2788
+ error: i18n.api.apiKeyValidation.empty
2789
+ };
2488
2790
  }
2489
- return services;
2791
+ return { isValid: true };
2490
2792
  }
2491
-
2492
- const WORKFLOW_CONFIGS = [
2493
- {
2494
- id: "sixStepsWorkflow",
2495
- nameKey: "workflowOption.sixStepsWorkflow",
2496
- descriptionKey: "workflowDescription.sixStepsWorkflow",
2497
- defaultSelected: true,
2498
- order: 1,
2499
- commands: ["workflow.md"],
2500
- agents: [],
2501
- autoInstallAgents: false,
2502
- category: "sixStep",
2503
- outputDir: "workflow"
2504
- },
2505
- {
2506
- id: "featPlanUx",
2507
- nameKey: "workflowOption.featPlanUx",
2508
- descriptionKey: "workflowDescription.featPlanUx",
2509
- defaultSelected: true,
2510
- order: 2,
2511
- commands: ["feat.md"],
2512
- agents: [
2513
- { id: "planner", filename: "planner.md", required: true },
2514
- { id: "ui-ux-designer", filename: "ui-ux-designer.md", required: true }
2515
- ],
2516
- autoInstallAgents: true,
2517
- category: "plan",
2518
- outputDir: "feat"
2519
- },
2520
- {
2521
- id: "gitWorkflow",
2522
- nameKey: "workflowOption.gitWorkflow",
2523
- descriptionKey: "workflowDescription.gitWorkflow",
2524
- defaultSelected: true,
2525
- order: 3,
2526
- commands: ["git-commit.md", "git-rollback.md", "git-cleanBranches.md", "git-worktree.md"],
2527
- agents: [],
2528
- autoInstallAgents: false,
2529
- category: "git",
2530
- outputDir: "git"
2531
- },
2532
- {
2533
- id: "bmadWorkflow",
2534
- nameKey: "workflowOption.bmadWorkflow",
2535
- descriptionKey: "workflowDescription.bmadWorkflow",
2536
- defaultSelected: true,
2537
- order: 4,
2538
- commands: ["bmad-init.md"],
2539
- agents: [],
2540
- autoInstallAgents: false,
2541
- category: "bmad",
2542
- outputDir: "bmad"
2793
+ function formatApiKeyDisplay(apiKey) {
2794
+ if (!apiKey || apiKey.length < 12) {
2795
+ return apiKey;
2543
2796
  }
2544
- ];
2545
- function getWorkflowConfig(workflowId) {
2546
- return WORKFLOW_CONFIGS.find((config) => config.id === workflowId);
2547
- }
2548
- function getOrderedWorkflows() {
2549
- return [...WORKFLOW_CONFIGS].sort((a, b) => a.order - b.order);
2797
+ return `${apiKey.substring(0, 8)}...${apiKey.substring(apiKey.length - 4)}`;
2550
2798
  }
2551
2799
 
2552
- function getRootDir() {
2553
- const currentFilePath = fileURLToPath(import.meta.url);
2554
- const distDir = dirname(dirname(currentFilePath));
2555
- return dirname(distDir);
2556
- }
2557
- async function selectAndInstallWorkflows(configLang, scriptLang) {
2558
- const i18n = getTranslation(scriptLang);
2559
- const workflows = getOrderedWorkflows();
2560
- const choices = workflows.map((workflow) => {
2561
- const nameKey = workflow.id;
2562
- const name = i18n.workflow.workflowOption[nameKey] || workflow.id;
2563
- return {
2564
- name,
2565
- value: workflow.id,
2566
- checked: workflow.defaultSelected
2567
- };
2568
- });
2569
- const { selectedWorkflows } = await inquirer.prompt({
2570
- type: "checkbox",
2571
- name: "selectedWorkflows",
2572
- message: `${i18n.workflow.selectWorkflowType}${i18n.common.multiSelectHint}`,
2573
- choices
2574
- });
2575
- if (!selectedWorkflows || selectedWorkflows.length === 0) {
2576
- console.log(ansis.yellow(i18n.common.cancelled));
2577
- return;
2578
- }
2579
- await cleanupOldVersionFiles(scriptLang);
2580
- for (const workflowId of selectedWorkflows) {
2581
- const config = getWorkflowConfig(workflowId);
2582
- if (config) {
2583
- await installWorkflowWithDependencies(config, configLang, scriptLang);
2584
- }
2585
- }
2586
- }
2587
- async function installWorkflowWithDependencies(config, configLang, scriptLang) {
2588
- const rootDir = getRootDir();
2589
- const i18n = getTranslation(scriptLang);
2590
- const result = {
2591
- workflow: config.id,
2592
- success: true,
2593
- installedCommands: [],
2594
- installedAgents: [],
2595
- errors: []
2596
- };
2597
- const workflowName = i18n.workflow.workflowOption[config.id] || config.id;
2598
- console.log(ansis.cyan(`
2599
- \u{1F4E6} ${i18n.workflow.installingWorkflow}: ${workflowName}...`));
2600
- const commandsDir = join(CLAUDE_DIR, "commands", "zcf");
2601
- if (!existsSync(commandsDir)) {
2602
- await mkdir(commandsDir, { recursive: true });
2603
- }
2604
- for (const commandFile of config.commands) {
2605
- const commandSource = join(rootDir, "templates", configLang, "workflow", config.category, "commands", commandFile);
2606
- const destFileName = commandFile;
2607
- const commandDest = join(commandsDir, destFileName);
2608
- if (existsSync(commandSource)) {
2609
- try {
2610
- await copyFile$1(commandSource, commandDest);
2611
- result.installedCommands.push(destFileName);
2612
- console.log(ansis.gray(` \u2714 ${i18n.workflow.installedCommand}: zcf/${destFileName}`));
2613
- } catch (error) {
2614
- const errorMsg = `${i18n.workflow.failedToInstallCommand} ${commandFile}: ${error}`;
2615
- result.errors?.push(errorMsg);
2616
- console.error(ansis.red(` \u2717 ${errorMsg}`));
2617
- result.success = false;
2618
- }
2619
- }
2620
- }
2621
- if (config.autoInstallAgents && config.agents.length > 0) {
2622
- const agentsCategoryDir = join(CLAUDE_DIR, "agents", "zcf", config.category);
2623
- if (!existsSync(agentsCategoryDir)) {
2624
- await mkdir(agentsCategoryDir, { recursive: true });
2625
- }
2626
- for (const agent of config.agents) {
2627
- const agentSource = join(rootDir, "templates", configLang, "workflow", config.category, "agents", agent.filename);
2628
- const agentDest = join(agentsCategoryDir, agent.filename);
2629
- if (existsSync(agentSource)) {
2630
- try {
2631
- await copyFile$1(agentSource, agentDest);
2632
- result.installedAgents.push(agent.filename);
2633
- console.log(ansis.gray(` \u2714 ${i18n.workflow.installedAgent}: zcf/${config.category}/${agent.filename}`));
2634
- } catch (error) {
2635
- const errorMsg = `${i18n.workflow.failedToInstallAgent} ${agent.filename}: ${error}`;
2636
- result.errors?.push(errorMsg);
2637
- console.error(ansis.red(` \u2717 ${errorMsg}`));
2638
- if (agent.required) {
2639
- result.success = false;
2640
- }
2800
+ async function configureApiCompletely(i18n, scriptLang, preselectedAuthType) {
2801
+ let authType = preselectedAuthType;
2802
+ if (!authType) {
2803
+ const { authType: selectedAuthType } = await inquirer.prompt({
2804
+ type: "list",
2805
+ name: "authType",
2806
+ message: i18n.api.configureApi,
2807
+ choices: addNumbersToChoices([
2808
+ {
2809
+ name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
2810
+ value: "auth_token",
2811
+ short: i18n.api.useAuthToken
2812
+ },
2813
+ {
2814
+ name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
2815
+ value: "api_key",
2816
+ short: i18n.api.useApiKey
2641
2817
  }
2642
- }
2643
- }
2644
- }
2645
- if (result.success) {
2646
- console.log(ansis.green(`\u2714 ${workflowName} ${i18n.workflow.workflowInstallSuccess}`));
2647
- if (config.id === "bmadWorkflow") {
2648
- console.log(ansis.cyan(`
2649
- ${i18n.workflow.bmadInitPrompt}`));
2818
+ ])
2819
+ });
2820
+ if (!selectedAuthType) {
2821
+ console.log(ansis.yellow(i18n.common.cancelled));
2822
+ return null;
2650
2823
  }
2651
- } else {
2652
- console.log(ansis.red(`\u2717 ${workflowName} ${i18n.workflow.workflowInstallError}`));
2824
+ authType = selectedAuthType;
2653
2825
  }
2654
- return result;
2655
- }
2656
- async function cleanupOldVersionFiles(scriptLang) {
2657
- const i18n = getTranslation(scriptLang);
2658
- console.log(ansis.cyan(`
2659
- \u{1F9F9} ${i18n.workflow.cleaningOldFiles || "Cleaning up old version files"}...`));
2660
- const oldCommandFiles = [
2661
- join(CLAUDE_DIR, "commands", "workflow.md"),
2662
- join(CLAUDE_DIR, "commands", "feat.md")
2663
- ];
2664
- const oldAgentFiles = [
2665
- join(CLAUDE_DIR, "agents", "planner.md"),
2666
- join(CLAUDE_DIR, "agents", "ui-ux-designer.md")
2667
- ];
2668
- for (const file of oldCommandFiles) {
2669
- if (existsSync(file)) {
2826
+ const { url } = await inquirer.prompt({
2827
+ type: "input",
2828
+ name: "url",
2829
+ message: i18n.api.enterApiUrl,
2830
+ validate: (value) => {
2831
+ if (!value) return i18n.api.urlRequired;
2670
2832
  try {
2671
- await rm(file, { force: true });
2672
- console.log(ansis.gray(` \u2714 ${i18n.workflow.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2673
- } catch (error) {
2674
- console.error(ansis.yellow(` \u26A0 ${i18n.workflow.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2833
+ new URL(value);
2834
+ return true;
2835
+ } catch {
2836
+ return i18n.api.invalidUrl;
2675
2837
  }
2676
2838
  }
2839
+ });
2840
+ if (url === void 0) {
2841
+ console.log(ansis.yellow(i18n.common.cancelled));
2842
+ return null;
2677
2843
  }
2678
- for (const file of oldAgentFiles) {
2679
- if (existsSync(file)) {
2680
- try {
2681
- await rm(file, { force: true });
2682
- console.log(ansis.gray(` \u2714 ${i18n.workflow.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2683
- } catch (error) {
2684
- console.error(ansis.yellow(` \u26A0 ${i18n.workflow.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2844
+ const keyMessage = authType === "auth_token" ? i18n.api.enterAuthToken : i18n.api.enterApiKey;
2845
+ const { key } = await inquirer.prompt({
2846
+ type: "input",
2847
+ name: "key",
2848
+ message: keyMessage,
2849
+ validate: (value) => {
2850
+ if (!value) {
2851
+ return i18n.api.keyRequired;
2852
+ }
2853
+ const validation = validateApiKey(value, scriptLang);
2854
+ if (!validation.isValid) {
2855
+ return validation.error || i18n.api.invalidKeyFormat;
2685
2856
  }
2857
+ return true;
2686
2858
  }
2859
+ });
2860
+ if (key === void 0) {
2861
+ console.log(ansis.yellow(i18n.common.cancelled));
2862
+ return null;
2687
2863
  }
2864
+ console.log(ansis.gray(` API Key: ${formatApiKeyDisplay(key)}`));
2865
+ return { url, key, authType };
2688
2866
  }
2689
-
2690
- const execAsync$2 = promisify$1(exec$2);
2691
- async function isCcrInstalled() {
2692
- let commandExists = false;
2693
- try {
2694
- await execAsync$2("ccr version");
2695
- commandExists = true;
2696
- } catch {
2697
- try {
2698
- await execAsync$2("which ccr");
2699
- commandExists = true;
2700
- } catch {
2701
- commandExists = false;
2702
- }
2703
- }
2704
- let hasCorrectPackage = false;
2705
- try {
2706
- await execAsync$2("npm list -g @musistudio/claude-code-router");
2707
- hasCorrectPackage = true;
2708
- } catch {
2709
- hasCorrectPackage = false;
2867
+ async function modifyApiConfigPartially(existingConfig, i18n, scriptLang) {
2868
+ let currentConfig = { ...existingConfig };
2869
+ const latestConfig = getExistingApiConfig();
2870
+ if (latestConfig) {
2871
+ currentConfig = latestConfig;
2710
2872
  }
2711
- return {
2712
- isInstalled: commandExists,
2713
- hasCorrectPackage
2714
- };
2715
- }
2716
- async function installCcr(scriptLang) {
2717
- const i18n = getTranslation(scriptLang);
2718
- const { isInstalled, hasCorrectPackage } = await isCcrInstalled();
2719
- if (hasCorrectPackage) {
2720
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
2721
- await updateCcr(scriptLang);
2873
+ const { item } = await inquirer.prompt({
2874
+ type: "list",
2875
+ name: "item",
2876
+ message: i18n.api.selectModifyItems,
2877
+ choices: addNumbersToChoices([
2878
+ { name: i18n.api.modifyApiUrl, value: "url" },
2879
+ { name: i18n.api.modifyApiKey, value: "key" },
2880
+ { name: i18n.api.modifyAuthType, value: "authType" }
2881
+ ])
2882
+ });
2883
+ if (!item) {
2884
+ console.log(ansis.yellow(i18n.common.cancelled));
2722
2885
  return;
2723
2886
  }
2724
- if (isInstalled && !hasCorrectPackage) {
2725
- try {
2726
- await execAsync$2("npm list -g claude-code-router");
2727
- console.log(ansis.yellow(`\u26A0 ${i18n.ccr.detectedIncorrectPackage}`));
2728
- try {
2729
- await execAsync$2("npm uninstall -g claude-code-router");
2730
- console.log(ansis.green(`\u2714 ${i18n.ccr.uninstalledIncorrectPackage}`));
2731
- } catch (uninstallError) {
2732
- console.log(ansis.yellow(`\u26A0 ${i18n.ccr.failedToUninstallIncorrectPackage}`));
2733
- }
2734
- } catch {
2735
- }
2736
- }
2737
- console.log(ansis.cyan(`\u{1F4E6} ${i18n.ccr.installingCcr}`));
2738
- try {
2739
- await execAsync$2("npm install -g @musistudio/claude-code-router --force");
2740
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrInstallSuccess}`));
2741
- } catch (error) {
2742
- if (error.message?.includes("EEXIST")) {
2743
- console.log(ansis.yellow(`\u26A0 ${i18n.ccr.ccrAlreadyInstalled}`));
2744
- await updateCcr(scriptLang);
2745
- return;
2746
- }
2747
- console.error(ansis.red(`\u2716 ${i18n.ccr.ccrInstallFailed}`));
2748
- throw error;
2749
- }
2750
- }
2751
-
2752
- const PROVIDER_PRESETS_URL = "https://pub-0dc3e1677e894f07bbea11b17a29e032.r2.dev/providers.json";
2753
- async function fetchProviderPresets() {
2754
- try {
2755
- const controller = new AbortController();
2756
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
2757
- const response = await fetch(PROVIDER_PRESETS_URL, {
2758
- signal: controller.signal
2759
- });
2760
- clearTimeout(timeoutId);
2761
- if (!response.ok) {
2762
- throw new Error(`HTTP error! status: ${response.status}`);
2763
- }
2764
- const data = await response.json();
2765
- const presets = [];
2766
- if (Array.isArray(data)) {
2767
- for (const provider of data) {
2768
- if (provider && typeof provider === "object") {
2769
- presets.push({
2770
- name: provider.name || "",
2771
- provider: provider.name || "",
2772
- baseURL: provider.api_base_url || provider.baseURL || provider.url,
2773
- requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
2774
- models: provider.models || [],
2775
- description: provider.description || provider.name || "",
2776
- transformer: provider.transformer
2777
- });
2778
- }
2779
- }
2780
- } else if (data && typeof data === "object") {
2781
- for (const [key, value] of Object.entries(data)) {
2782
- if (typeof value === "object" && value !== null) {
2783
- const provider = value;
2784
- presets.push({
2785
- name: provider.name || key,
2786
- provider: key,
2787
- baseURL: provider.api_base_url || provider.baseURL || provider.url,
2788
- requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
2789
- models: provider.models || [],
2790
- description: provider.description || "",
2791
- transformer: provider.transformer
2792
- });
2793
- }
2794
- }
2795
- }
2796
- return presets;
2797
- } catch (error) {
2798
- return getFallbackPresets();
2799
- }
2800
- }
2801
- function getFallbackPresets() {
2802
- return [
2803
- {
2804
- name: "dashscope",
2805
- provider: "dashscope",
2806
- baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
2807
- requiresApiKey: true,
2808
- models: ["qwen3-coder-plus"],
2809
- description: "Alibaba DashScope",
2810
- transformer: {
2811
- use: [["maxtoken", { max_tokens: 65536 }]],
2812
- "qwen3-coder-plus": {
2813
- use: ["enhancetool"]
2814
- }
2815
- }
2816
- },
2817
- {
2818
- name: "deepseek",
2819
- provider: "deepseek",
2820
- baseURL: "https://api.deepseek.com/chat/completions",
2821
- requiresApiKey: true,
2822
- models: ["deepseek-chat", "deepseek-reasoner"],
2823
- description: "DeepSeek AI models",
2824
- transformer: {
2825
- use: ["deepseek"],
2826
- "deepseek-chat": {
2827
- use: ["tooluse"]
2828
- }
2829
- }
2830
- },
2831
- {
2832
- name: "gemini",
2833
- provider: "gemini",
2834
- baseURL: "https://generativelanguage.googleapis.com/v1beta/models/",
2835
- requiresApiKey: true,
2836
- models: ["gemini-2.5-flash", "gemini-2.5-pro"],
2837
- description: "Google Gemini models",
2838
- transformer: {
2839
- use: ["gemini"]
2840
- }
2841
- },
2842
- {
2843
- name: "modelscope",
2844
- provider: "modelscope",
2845
- baseURL: "https://api-inference.modelscope.cn/v1/chat/completions",
2846
- requiresApiKey: true,
2847
- models: ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507", "ZhipuAI/GLM-4.5"],
2848
- description: "ModelScope AI models",
2849
- transformer: {
2850
- use: [["maxtoken", { max_tokens: 65536 }]],
2851
- "Qwen/Qwen3-Coder-480B-A35B-Instruct": {
2852
- use: ["enhancetool"]
2853
- },
2854
- "Qwen/Qwen3-235B-A22B-Thinking-2507": {
2855
- use: ["reasoning"]
2856
- }
2857
- }
2858
- },
2859
- {
2860
- name: "openrouter",
2861
- provider: "openrouter",
2862
- baseURL: "https://openrouter.ai/api/v1/chat/completions",
2863
- requiresApiKey: true,
2864
- models: [
2865
- "google/gemini-2.5-pro-preview",
2866
- "anthropic/claude-sonnet-4",
2867
- "anthropic/claude-3.5-sonnet",
2868
- "anthropic/claude-3.7-sonnet:thinking"
2869
- ],
2870
- description: "OpenRouter API",
2871
- transformer: {
2872
- use: ["openrouter"]
2873
- }
2874
- },
2875
- {
2876
- name: "siliconflow",
2877
- provider: "siliconflow",
2878
- baseURL: "https://api.siliconflow.cn/v1/chat/completions",
2879
- requiresApiKey: true,
2880
- models: ["moonshotai/Kimi-K2-Instruct"],
2881
- description: "SiliconFlow AI",
2882
- transformer: {
2883
- use: [["maxtoken", { max_tokens: 16384 }]]
2887
+ if (item === "url") {
2888
+ const { url } = await inquirer.prompt({
2889
+ type: "input",
2890
+ name: "url",
2891
+ message: i18n.api.enterNewApiUrl.replace("{url}", currentConfig.url || i18n.common.none),
2892
+ default: currentConfig.url,
2893
+ validate: (value) => {
2894
+ if (!value) return i18n.api.urlRequired;
2895
+ try {
2896
+ new URL(value);
2897
+ return true;
2898
+ } catch {
2899
+ return i18n.api.invalidUrl;
2900
+ }
2884
2901
  }
2885
- },
2886
- {
2887
- name: "volcengine",
2888
- provider: "volcengine",
2889
- baseURL: "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
2890
- requiresApiKey: true,
2891
- models: ["deepseek-v3-250324", "deepseek-r1-250528"],
2892
- description: "Volcengine AI",
2893
- transformer: {
2894
- use: ["deepseek"]
2902
+ });
2903
+ if (url === void 0) {
2904
+ console.log(ansis.yellow(i18n.common.cancelled));
2905
+ return;
2906
+ }
2907
+ currentConfig.url = url;
2908
+ const savedConfig = configureApi(currentConfig);
2909
+ if (savedConfig) {
2910
+ console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2911
+ console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${savedConfig.url}`));
2912
+ }
2913
+ } else if (item === "key") {
2914
+ const authType = currentConfig.authType || "auth_token";
2915
+ 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);
2916
+ const { key } = await inquirer.prompt({
2917
+ type: "input",
2918
+ name: "key",
2919
+ message: keyMessage,
2920
+ validate: (value) => {
2921
+ if (!value) {
2922
+ return i18n.api.keyRequired;
2923
+ }
2924
+ const validation = validateApiKey(value, scriptLang);
2925
+ if (!validation.isValid) {
2926
+ return validation.error || i18n.api.invalidKeyFormat;
2927
+ }
2928
+ return true;
2895
2929
  }
2930
+ });
2931
+ if (key === void 0) {
2932
+ console.log(ansis.yellow(i18n.common.cancelled));
2933
+ return;
2896
2934
  }
2897
- ];
2935
+ currentConfig.key = key;
2936
+ const savedConfig = configureApi(currentConfig);
2937
+ if (savedConfig) {
2938
+ console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2939
+ console.log(ansis.gray(` ${i18n.api.apiConfigKey}: ${formatApiKeyDisplay(savedConfig.key)}`));
2940
+ }
2941
+ } else if (item === "authType") {
2942
+ const { authType } = await inquirer.prompt({
2943
+ type: "list",
2944
+ name: "authType",
2945
+ message: i18n.api.selectNewAuthType.replace("{type}", currentConfig.authType || i18n.common.none),
2946
+ choices: addNumbersToChoices([
2947
+ { name: "Auth Token (OAuth)", value: "auth_token" },
2948
+ { name: "API Key", value: "api_key" }
2949
+ ]),
2950
+ default: currentConfig.authType === "api_key" ? 1 : 0
2951
+ });
2952
+ if (authType === void 0) {
2953
+ console.log(ansis.yellow(i18n.common.cancelled));
2954
+ return;
2955
+ }
2956
+ currentConfig.authType = authType;
2957
+ const savedConfig = configureApi(currentConfig);
2958
+ if (savedConfig) {
2959
+ console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2960
+ console.log(ansis.gray(` ${i18n.api.apiConfigAuthType}: ${savedConfig.authType}`));
2961
+ }
2962
+ }
2963
+ }
2964
+ async function updatePromptOnly(configLang, scriptLang, aiOutputLang) {
2965
+ const i18n = getTranslation(scriptLang);
2966
+ const backupDir = backupExistingConfig();
2967
+ if (backupDir) {
2968
+ console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
2969
+ }
2970
+ copyConfigFiles(configLang, true);
2971
+ if (aiOutputLang) {
2972
+ applyAiLanguageDirective(aiOutputLang);
2973
+ }
2974
+ await configureAiPersonality(scriptLang);
2975
+ console.log(ansis.green(`\u2714 ${i18n.configuration.configSuccess} ${CLAUDE_DIR}`));
2976
+ console.log("\n" + ansis.cyan(i18n.common.complete));
2898
2977
  }
2899
2978
 
2900
- const execAsync$1 = promisify$1(exec$2);
2901
- const CCR_CONFIG_DIR = join$1(homedir(), ".claude-code-router");
2902
- const CCR_CONFIG_FILE = join$1(CCR_CONFIG_DIR, "config.json");
2903
- const CCR_BACKUP_DIR = CCR_CONFIG_DIR;
2904
- function ensureCcrConfigDir() {
2905
- if (!existsSync(CCR_CONFIG_DIR)) {
2906
- mkdirSync(CCR_CONFIG_DIR, { recursive: true });
2979
+ function handleExitPromptError(error) {
2980
+ if (error instanceof Error && error.name === "ExitPromptError") {
2981
+ const zcfConfig = readZcfConfig();
2982
+ const defaultLang = zcfConfig?.preferredLang || "zh-CN";
2983
+ const i18n = getTranslation(defaultLang);
2984
+ console.log(ansis.cyan(`
2985
+ ${i18n.common.goodbye}
2986
+ `));
2987
+ process.exit(0);
2907
2988
  }
2989
+ return false;
2908
2990
  }
2909
- function backupCcrConfig(scriptLang) {
2910
- const i18n = getTranslation(scriptLang);
2991
+ function handleGeneralError(error, lang) {
2992
+ const zcfConfig = readZcfConfig();
2993
+ const defaultLang = lang || zcfConfig?.preferredLang || "en";
2994
+ const i18n = getTranslation(defaultLang);
2995
+ const errorMsg = i18n.common.error || "Error";
2996
+ console.error(ansis.red(`${errorMsg}:`), error);
2997
+ if (error instanceof Error) {
2998
+ console.error(ansis.gray(`Stack: ${error.stack}`));
2999
+ }
3000
+ process.exit(1);
3001
+ }
3002
+
3003
+ async function isClaudeCodeInstalled() {
3004
+ return await commandExists("claude");
3005
+ }
3006
+ async function installClaudeCode(lang) {
3007
+ const i18n = getTranslation(lang);
3008
+ const installed = await isClaudeCodeInstalled();
3009
+ if (installed) {
3010
+ console.log(ansis.green(`\u2714 ${i18n.installation.alreadyInstalled}`));
3011
+ await updateClaudeCode(lang);
3012
+ return;
3013
+ }
3014
+ if (isTermux()) {
3015
+ console.log(ansis.yellow(`\u2139 ${i18n.installation.termuxDetected}`));
3016
+ const termuxPrefix = getTermuxPrefix();
3017
+ console.log(ansis.gray(i18n.installation.termuxPathInfo.replace("{path}", termuxPrefix)));
3018
+ console.log(ansis.gray(`Node.js: ${termuxPrefix}/bin/node`));
3019
+ console.log(ansis.gray(`npm: ${termuxPrefix}/bin/npm`));
3020
+ }
3021
+ console.log(i18n.installation.installing);
2911
3022
  try {
2912
- if (!existsSync(CCR_CONFIG_FILE)) {
2913
- return null;
3023
+ await exec("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
3024
+ console.log(`\u2714 ${i18n.installation.installSuccess}`);
3025
+ if (isTermux()) {
3026
+ console.log(ansis.gray(`
3027
+ Claude Code installed to: ${getTermuxPrefix()}/bin/claude`));
2914
3028
  }
2915
- const timestamp = dayjs().format("YYYY-MM-DDTHH-mm-ss-SSS") + "Z";
2916
- const backupFileName = `config.json.${timestamp}.bak`;
2917
- const backupPath = join$1(CCR_BACKUP_DIR, backupFileName);
2918
- console.log(ansis.cyan(`${i18n.ccr.backupCcrConfig}`));
2919
- copyFileSync(CCR_CONFIG_FILE, backupPath);
2920
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrBackupSuccess.replace("{path}", backupPath)}`));
2921
- return backupPath;
2922
3029
  } catch (error) {
2923
- console.error(ansis.red(`${i18n.ccr.ccrBackupFailed}:`), error.message);
2924
- return null;
2925
- }
2926
- }
2927
- function readCcrConfig() {
2928
- if (!existsSync(CCR_CONFIG_FILE)) {
2929
- return null;
3030
+ console.error(`\u2716 ${i18n.installation.installFailed}`);
3031
+ if (isTermux()) {
3032
+ console.error(ansis.yellow(`
3033
+ ${i18n.installation.termuxInstallHint}
3034
+ `));
3035
+ }
3036
+ throw error;
2930
3037
  }
2931
- return readJsonConfig(CCR_CONFIG_FILE);
2932
3038
  }
2933
- function writeCcrConfig(config) {
2934
- ensureCcrConfigDir();
2935
- writeJsonConfig(CCR_CONFIG_FILE, config);
2936
- }
2937
- async function configureCcrProxy(ccrConfig) {
2938
- const settings = readJsonConfig(SETTINGS_FILE) || {};
2939
- const host = ccrConfig.HOST || "127.0.0.1";
2940
- const port = ccrConfig.PORT || 3456;
2941
- const apiKey = ccrConfig.APIKEY || "sk-zcf-x-ccr";
2942
- if (!settings.env) {
2943
- settings.env = {};
3039
+
3040
+ async function selectMcpServices(scriptLang) {
3041
+ const i18n = getTranslation(scriptLang);
3042
+ const choices = MCP_SERVICES.map((service) => ({
3043
+ name: `${service.name[scriptLang]} - ${ansis.gray(service.description[scriptLang])}`,
3044
+ value: service.id,
3045
+ selected: false
3046
+ }));
3047
+ const { services } = await inquirer.prompt({
3048
+ type: "checkbox",
3049
+ name: "services",
3050
+ message: `${i18n.mcp.selectMcpServices}${i18n.common.multiSelectHint}`,
3051
+ choices
3052
+ });
3053
+ if (services === void 0) {
3054
+ console.log(ansis.yellow(i18n.common.cancelled));
3055
+ return void 0;
2944
3056
  }
2945
- settings.env.ANTHROPIC_BASE_URL = `http://${host}:${port}`;
2946
- settings.env.ANTHROPIC_API_KEY = apiKey;
2947
- writeJsonConfig(SETTINGS_FILE, settings);
3057
+ return services;
2948
3058
  }
2949
- async function selectCcrPreset(scriptLang) {
3059
+
3060
+ async function selectAiOutputLanguage(scriptLang, defaultLang) {
2950
3061
  const i18n = getTranslation(scriptLang);
2951
- console.log(ansis.cyan(`${i18n.ccr.fetchingPresets}`));
2952
- const presets = await fetchProviderPresets();
2953
- if (!presets || presets.length === 0) {
2954
- console.log(ansis.yellow(`${i18n.ccr.noPresetsAvailable}`));
2955
- return null;
3062
+ console.log(ansis.dim(`
3063
+ ${i18n.language.aiOutputLangHint}
3064
+ `));
3065
+ const aiLangChoices = Object.entries(AI_OUTPUT_LANGUAGES).map(([key, value]) => ({
3066
+ title: value.label,
3067
+ value: key
3068
+ }));
3069
+ const defaultChoice = defaultLang || (scriptLang === "zh-CN" ? "zh-CN" : "en");
3070
+ const { lang } = await inquirer.prompt({
3071
+ type: "list",
3072
+ name: "lang",
3073
+ message: i18n.language.selectAiOutputLang,
3074
+ choices: addNumbersToChoices(aiLangChoices.map((choice) => ({
3075
+ name: choice.title,
3076
+ value: choice.value
3077
+ }))),
3078
+ default: defaultChoice
3079
+ });
3080
+ if (!lang) {
3081
+ console.log(ansis.yellow(i18n.common.cancelled));
3082
+ process.exit(0);
2956
3083
  }
2957
- try {
2958
- const choices = [
2959
- {
2960
- name: `1. ${i18n.ccr.skipOption}`,
2961
- value: "skip"
2962
- },
2963
- ...presets.map((p, index) => ({
2964
- name: `${index + 2}. ${p.name}`,
2965
- value: p
2966
- }))
2967
- ];
2968
- const { preset } = await inquirer.prompt({
2969
- type: "list",
2970
- name: "preset",
2971
- message: i18n.ccr.selectCcrPreset,
2972
- choices
3084
+ let aiOutputLang = lang;
3085
+ if (aiOutputLang === "custom") {
3086
+ const { customLang } = await inquirer.prompt({
3087
+ type: "input",
3088
+ name: "customLang",
3089
+ message: i18n.language.enterCustomLanguage,
3090
+ validate: (value) => !!value || i18n.language?.languageRequired || "Language is required"
2973
3091
  });
2974
- return preset;
2975
- } catch (error) {
2976
- if (error.name === "ExitPromptError") {
3092
+ if (!customLang) {
2977
3093
  console.log(ansis.yellow(i18n.common.cancelled));
2978
- return null;
3094
+ process.exit(0);
2979
3095
  }
2980
- throw error;
3096
+ return customLang;
2981
3097
  }
3098
+ return aiOutputLang;
2982
3099
  }
2983
- async function configureCcrWithPreset(preset, scriptLang) {
2984
- const i18n = getTranslation(scriptLang);
2985
- const provider = {
2986
- name: preset.name,
2987
- // Use the original name from JSON
2988
- api_base_url: preset.baseURL || "",
2989
- api_key: "",
2990
- models: preset.models
2991
- };
2992
- if (preset.transformer) {
2993
- provider.transformer = preset.transformer;
3100
+ async function selectScriptLanguage(currentLang) {
3101
+ const zcfConfig = readZcfConfig();
3102
+ if (zcfConfig?.preferredLang) {
3103
+ return zcfConfig.preferredLang;
2994
3104
  }
2995
- if (preset.requiresApiKey) {
2996
- try {
2997
- const { apiKey } = await inquirer.prompt({
2998
- type: "input",
2999
- name: "apiKey",
3000
- message: i18n.ccr.enterApiKeyForProvider.replace("{provider}", preset.name),
3001
- validate: (value) => !!value || i18n.api.keyRequired
3002
- });
3003
- provider.api_key = apiKey;
3004
- } catch (error) {
3005
- if (error.name === "ExitPromptError") {
3006
- throw error;
3007
- }
3008
- throw error;
3009
- }
3010
- } else {
3011
- provider.api_key = "sk-free";
3105
+ if (currentLang) {
3106
+ return currentLang;
3012
3107
  }
3013
- let defaultModel = preset.models[0];
3014
- if (preset.models.length > 1) {
3015
- try {
3016
- const { model } = await inquirer.prompt({
3017
- type: "list",
3018
- name: "model",
3019
- message: i18n.ccr.selectDefaultModelForProvider.replace("{provider}", preset.name),
3020
- choices: preset.models.map((m, index) => ({
3021
- name: `${index + 1}. ${m}`,
3022
- value: m
3023
- }))
3024
- });
3025
- defaultModel = model;
3026
- } catch (error) {
3027
- if (error.name === "ExitPromptError") {
3028
- throw error;
3029
- }
3030
- throw error;
3031
- }
3108
+ const { lang } = await inquirer.prompt({
3109
+ type: "list",
3110
+ name: "lang",
3111
+ message: "Select ZCF display language / \u9009\u62E9ZCF\u663E\u793A\u8BED\u8A00",
3112
+ choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
3113
+ name: LANG_LABELS[l],
3114
+ value: l
3115
+ })))
3116
+ });
3117
+ if (!lang) {
3118
+ console.log(ansis.yellow("Operation cancelled / \u64CD\u4F5C\u5DF2\u53D6\u6D88"));
3119
+ process.exit(0);
3032
3120
  }
3033
- const router = {
3034
- default: `${preset.name},${defaultModel}`,
3035
- // Use the original name
3036
- background: `${preset.name},${defaultModel}`,
3037
- think: `${preset.name},${defaultModel}`,
3038
- longContext: `${preset.name},${defaultModel}`,
3039
- longContextThreshold: 6e4,
3040
- webSearch: `${preset.name},${defaultModel}`
3041
- };
3042
- const config = {
3043
- LOG: true,
3044
- CLAUDE_PATH: "",
3045
- HOST: "127.0.0.1",
3046
- PORT: 3456,
3047
- APIKEY: "sk-zcf-x-ccr",
3048
- API_TIMEOUT_MS: "600000",
3049
- PROXY_URL: "",
3050
- transformers: [],
3051
- Providers: [provider],
3052
- Router: router
3053
- };
3054
- return config;
3121
+ const scriptLang = lang;
3122
+ updateZcfConfig({
3123
+ version,
3124
+ preferredLang: scriptLang
3125
+ });
3126
+ return scriptLang;
3055
3127
  }
3056
- async function restartAndCheckCcrStatus(scriptLang) {
3128
+ async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfig) {
3057
3129
  const i18n = getTranslation(scriptLang);
3058
- try {
3059
- console.log(ansis.cyan(`${i18n.ccr.restartingCcr}`));
3060
- await execAsync$1("ccr restart");
3061
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrRestartSuccess}`));
3062
- console.log(ansis.cyan(`${i18n.ccr.checkingCcrStatus}`));
3063
- const { stdout } = await execAsync$1("ccr status");
3064
- console.log(ansis.gray(stdout));
3065
- } catch (error) {
3066
- console.error(ansis.red(`${i18n.ccr.ccrRestartFailed}:`), error.message || error);
3067
- if (process.env.DEBUG) {
3068
- console.error("Full error:", error);
3069
- }
3130
+ if (commandLineOption) {
3131
+ return commandLineOption;
3070
3132
  }
3133
+ if (savedConfig?.aiOutputLang) {
3134
+ console.log(ansis.gray(`\u2714 ${i18n.language.aiOutputLangHint}: ${savedConfig.aiOutputLang}`));
3135
+ return savedConfig.aiOutputLang;
3136
+ }
3137
+ return await selectAiOutputLanguage(scriptLang, scriptLang);
3071
3138
  }
3072
- function showConfigurationTips(scriptLang, apiKey) {
3139
+
3140
+ const prompts = {
3141
+ __proto__: null,
3142
+ resolveAiOutputLanguage: resolveAiOutputLanguage,
3143
+ selectAiOutputLanguage: selectAiOutputLanguage,
3144
+ selectScriptLanguage: selectScriptLanguage
3145
+ };
3146
+
3147
+ function getRootDir() {
3148
+ const currentFilePath = fileURLToPath(import.meta.url);
3149
+ const distDir = dirname(dirname(currentFilePath));
3150
+ return dirname(distDir);
3151
+ }
3152
+ async function selectAndInstallWorkflows(configLang, scriptLang, preselectedWorkflows) {
3073
3153
  const i18n = getTranslation(scriptLang);
3074
- console.log(ansis.bold.cyan(`
3075
- \u{1F4CC} ${i18n.ccr.configTips}:`));
3076
- console.log(ansis.blue(` \u2022 ${i18n.ccr.advancedConfigTip}`));
3077
- console.log(ansis.blue(` \u2022 ${i18n.ccr.manualConfigTip}`));
3078
- console.log(ansis.bold.yellow(` \u2022 ${i18n.ccr.useClaudeCommand}`));
3079
- if (apiKey) {
3080
- console.log(ansis.bold.green(` \u2022 ${i18n.ccr.ccrUiApiKey || "CCR UI API Key"}: ${apiKey}`));
3081
- console.log(ansis.gray(` ${i18n.ccr.ccrUiApiKeyHint || "Use this API key to login to CCR UI"}`));
3154
+ const workflows = getOrderedWorkflows();
3155
+ const choices = workflows.map((workflow) => {
3156
+ const nameKey = workflow.id;
3157
+ const name = i18n.workflow.workflowOption[nameKey] || workflow.id;
3158
+ return {
3159
+ name,
3160
+ value: workflow.id,
3161
+ checked: workflow.defaultSelected
3162
+ };
3163
+ });
3164
+ let selectedWorkflows;
3165
+ if (preselectedWorkflows) {
3166
+ selectedWorkflows = preselectedWorkflows;
3167
+ } else {
3168
+ const response = await inquirer.prompt({
3169
+ type: "checkbox",
3170
+ name: "selectedWorkflows",
3171
+ message: `${i18n.workflow.selectWorkflowType}${i18n.common.multiSelectHint}`,
3172
+ choices
3173
+ });
3174
+ selectedWorkflows = response.selectedWorkflows;
3175
+ }
3176
+ if (!selectedWorkflows || selectedWorkflows.length === 0) {
3177
+ console.log(ansis.yellow(i18n.common.cancelled));
3178
+ return;
3179
+ }
3180
+ await cleanupOldVersionFiles(scriptLang);
3181
+ for (const workflowId of selectedWorkflows) {
3182
+ const config = getWorkflowConfig(workflowId);
3183
+ if (config) {
3184
+ await installWorkflowWithDependencies(config, configLang, scriptLang);
3185
+ }
3082
3186
  }
3083
- console.log("");
3084
3187
  }
3085
- async function setupCcrConfiguration(scriptLang) {
3188
+ async function installWorkflowWithDependencies(config, configLang, scriptLang) {
3189
+ const rootDir = getRootDir();
3086
3190
  const i18n = getTranslation(scriptLang);
3087
- try {
3088
- const existingConfig = readCcrConfig();
3089
- if (existingConfig) {
3090
- console.log(ansis.blue(`\u2139 ${i18n.ccr.existingCcrConfig}`));
3091
- let shouldBackupAndReconfigure = false;
3191
+ const result = {
3192
+ workflow: config.id,
3193
+ success: true,
3194
+ installedCommands: [],
3195
+ installedAgents: [],
3196
+ errors: []
3197
+ };
3198
+ const workflowName = i18n.workflow.workflowOption[config.id] || config.id;
3199
+ console.log(ansis.cyan(`
3200
+ \u{1F4E6} ${i18n.workflow.installingWorkflow}: ${workflowName}...`));
3201
+ const commandsDir = join(CLAUDE_DIR, "commands", "zcf");
3202
+ if (!existsSync(commandsDir)) {
3203
+ await mkdir(commandsDir, { recursive: true });
3204
+ }
3205
+ for (const commandFile of config.commands) {
3206
+ const commandSource = join(rootDir, "templates", configLang, "workflow", config.category, "commands", commandFile);
3207
+ const destFileName = commandFile;
3208
+ const commandDest = join(commandsDir, destFileName);
3209
+ if (existsSync(commandSource)) {
3092
3210
  try {
3093
- const result = await inquirer.prompt({
3094
- type: "confirm",
3095
- name: "overwrite",
3096
- message: i18n.ccr.overwriteCcrConfig,
3097
- default: false
3098
- });
3099
- shouldBackupAndReconfigure = result.overwrite;
3211
+ await copyFile$1(commandSource, commandDest);
3212
+ result.installedCommands.push(destFileName);
3213
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.installedCommand}: zcf/${destFileName}`));
3100
3214
  } catch (error) {
3101
- if (error.name === "ExitPromptError") {
3102
- console.log(ansis.yellow(i18n.common.cancelled));
3103
- return false;
3104
- }
3105
- throw error;
3106
- }
3107
- if (!shouldBackupAndReconfigure) {
3108
- console.log(ansis.yellow(`${i18n.ccr.keepingExistingConfig}`));
3109
- await configureCcrProxy(existingConfig);
3110
- return true;
3215
+ const errorMsg = `${i18n.workflow.failedToInstallCommand} ${commandFile}: ${error}`;
3216
+ result.errors?.push(errorMsg);
3217
+ console.error(ansis.red(` \u2717 ${errorMsg}`));
3218
+ result.success = false;
3111
3219
  }
3112
- backupCcrConfig(scriptLang);
3113
3220
  }
3114
- const preset = await selectCcrPreset(scriptLang);
3115
- if (!preset) {
3116
- return false;
3117
- }
3118
- let config;
3119
- if (preset === "skip") {
3120
- console.log(ansis.yellow(`${i18n.ccr.skipConfiguring}`));
3121
- config = {
3122
- LOG: false,
3123
- CLAUDE_PATH: "",
3124
- HOST: "127.0.0.1",
3125
- PORT: 3456,
3126
- APIKEY: "sk-zcf-x-ccr",
3127
- API_TIMEOUT_MS: "600000",
3128
- PROXY_URL: "",
3129
- transformers: [],
3130
- Providers: [],
3131
- // Empty providers array
3132
- Router: {
3133
- // Empty router configuration - user will configure in CCR UI
3134
- }
3135
- };
3136
- } else {
3137
- config = await configureCcrWithPreset(preset, scriptLang);
3221
+ }
3222
+ if (config.autoInstallAgents && config.agents.length > 0) {
3223
+ const agentsCategoryDir = join(CLAUDE_DIR, "agents", "zcf", config.category);
3224
+ if (!existsSync(agentsCategoryDir)) {
3225
+ await mkdir(agentsCategoryDir, { recursive: true });
3138
3226
  }
3139
- writeCcrConfig(config);
3140
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrConfigSuccess}`));
3141
- await configureCcrProxy(config);
3142
- console.log(ansis.green(`\u2714 ${i18n.ccr.proxyConfigSuccess}`));
3143
- await restartAndCheckCcrStatus(scriptLang);
3144
- showConfigurationTips(scriptLang, config.APIKEY);
3145
- try {
3146
- addCompletedOnboarding();
3147
- } catch (error) {
3148
- console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
3227
+ for (const agent of config.agents) {
3228
+ const agentSource = join(rootDir, "templates", configLang, "workflow", config.category, "agents", agent.filename);
3229
+ const agentDest = join(agentsCategoryDir, agent.filename);
3230
+ if (existsSync(agentSource)) {
3231
+ try {
3232
+ await copyFile$1(agentSource, agentDest);
3233
+ result.installedAgents.push(agent.filename);
3234
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.installedAgent}: zcf/${config.category}/${agent.filename}`));
3235
+ } catch (error) {
3236
+ const errorMsg = `${i18n.workflow.failedToInstallAgent} ${agent.filename}: ${error}`;
3237
+ result.errors?.push(errorMsg);
3238
+ console.error(ansis.red(` \u2717 ${errorMsg}`));
3239
+ if (agent.required) {
3240
+ result.success = false;
3241
+ }
3242
+ }
3243
+ }
3149
3244
  }
3150
- return true;
3151
- } catch (error) {
3152
- if (error.name === "ExitPromptError") {
3153
- console.log(ansis.yellow(i18n.common.cancelled));
3154
- return false;
3245
+ }
3246
+ if (result.success) {
3247
+ console.log(ansis.green(`\u2714 ${workflowName} ${i18n.workflow.workflowInstallSuccess}`));
3248
+ if (config.id === "bmadWorkflow") {
3249
+ console.log(ansis.cyan(`
3250
+ ${i18n.workflow.bmadInitPrompt}`));
3155
3251
  }
3156
- console.error(ansis.red(`${i18n.ccr.ccrConfigFailed}:`), error);
3157
- return false;
3252
+ } else {
3253
+ console.log(ansis.red(`\u2717 ${workflowName} ${i18n.workflow.workflowInstallError}`));
3158
3254
  }
3255
+ return result;
3159
3256
  }
3160
- async function configureCcrFeature(scriptLang) {
3257
+ async function cleanupOldVersionFiles(scriptLang) {
3161
3258
  const i18n = getTranslation(scriptLang);
3162
- const backupDir = backupExistingConfig();
3163
- if (backupDir) {
3164
- console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
3165
- }
3166
- await setupCcrConfiguration(scriptLang);
3167
- }
3168
-
3169
- const COMETIX_PACKAGE_NAME = "@cometix/ccline";
3170
- const COMETIX_COMMAND_NAME = "ccline";
3171
- const COMETIX_COMMANDS = {
3172
- CHECK_INSTALL: `npm list -g ${COMETIX_PACKAGE_NAME}`,
3173
- INSTALL: `npm install -g ${COMETIX_PACKAGE_NAME}`,
3174
- UPDATE: `npm update -g ${COMETIX_PACKAGE_NAME}`,
3175
- PRINT_CONFIG: `${COMETIX_COMMAND_NAME} --print`
3176
- };
3177
-
3178
- function getPlatformStatusLineConfig() {
3179
- return {
3180
- type: "command",
3181
- command: isWindows() ? "%USERPROFILE%\\.claude\\ccline\\ccline.exe" : "~/.claude/ccline/ccline",
3182
- padding: 0
3183
- };
3184
- }
3185
-
3186
- function addCCometixLineConfig() {
3187
- try {
3188
- const statusLineConfig = getPlatformStatusLineConfig();
3189
- let settings = {};
3190
- if (exists(SETTINGS_FILE)) {
3191
- settings = readJsonConfig(SETTINGS_FILE) || {};
3259
+ console.log(ansis.cyan(`
3260
+ \u{1F9F9} ${i18n.workflow.cleaningOldFiles || "Cleaning up old version files"}...`));
3261
+ const oldCommandFiles = [
3262
+ join(CLAUDE_DIR, "commands", "workflow.md"),
3263
+ join(CLAUDE_DIR, "commands", "feat.md")
3264
+ ];
3265
+ const oldAgentFiles = [
3266
+ join(CLAUDE_DIR, "agents", "planner.md"),
3267
+ join(CLAUDE_DIR, "agents", "ui-ux-designer.md")
3268
+ ];
3269
+ for (const file of oldCommandFiles) {
3270
+ if (existsSync(file)) {
3271
+ try {
3272
+ await rm(file, { force: true });
3273
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
3274
+ } catch (error) {
3275
+ console.error(ansis.yellow(` \u26A0 ${i18n.workflow.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
3276
+ }
3192
3277
  }
3193
- settings.statusLine = statusLineConfig;
3194
- writeJsonConfig(SETTINGS_FILE, settings);
3195
- return true;
3196
- } catch (error) {
3197
- console.error("Failed to add CCometixLine configuration:", error);
3198
- return false;
3199
3278
  }
3200
- }
3201
- function hasCCometixLineConfig() {
3202
- try {
3203
- if (!exists(SETTINGS_FILE)) {
3204
- return false;
3279
+ for (const file of oldAgentFiles) {
3280
+ if (existsSync(file)) {
3281
+ try {
3282
+ await rm(file, { force: true });
3283
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
3284
+ } catch (error) {
3285
+ console.error(ansis.yellow(` \u26A0 ${i18n.workflow.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
3286
+ }
3205
3287
  }
3206
- const settings = readJsonConfig(SETTINGS_FILE);
3207
- return !!settings?.statusLine?.command?.includes("ccline");
3208
- } catch (error) {
3209
- return false;
3210
3288
  }
3211
3289
  }
3212
3290
 
3213
- const execAsync = promisify$1(exec$2);
3214
- async function isCometixLineInstalled() {
3215
- try {
3216
- await execAsync(COMETIX_COMMANDS.CHECK_INSTALL);
3217
- return true;
3218
- } catch {
3219
- return false;
3291
+ function validateSkipPromptOptions(options) {
3292
+ if (options.allLang) {
3293
+ if (options.allLang === "zh-CN" || options.allLang === "en") {
3294
+ options.lang = options.allLang;
3295
+ options.configLang = options.allLang;
3296
+ options.aiOutputLang = options.allLang;
3297
+ } else {
3298
+ options.lang = "en";
3299
+ options.configLang = "en";
3300
+ options.aiOutputLang = options.allLang;
3301
+ }
3220
3302
  }
3221
- }
3222
- async function installCometixLine(scriptLang) {
3223
- const i18n = getTranslation(scriptLang);
3224
- const isInstalled = await isCometixLineInstalled();
3225
- if (isInstalled) {
3226
- console.log(ansis.green(`\u2714 ${i18n.cometix.cometixAlreadyInstalled}`));
3227
- try {
3228
- console.log(ansis.blue(`${i18n.cometix.installingOrUpdating}`));
3229
- await execAsync(COMETIX_COMMANDS.INSTALL);
3230
- console.log(ansis.green(`\u2714 ${i18n.cometix.installUpdateSuccess}`));
3231
- } catch (error) {
3232
- console.warn(ansis.yellow(`\u26A0 ${i18n.cometix.installUpdateFailed}: ${error}`));
3303
+ if (!options.configAction) {
3304
+ options.configAction = "backup";
3305
+ }
3306
+ if (!options.lang) {
3307
+ options.lang = "en";
3308
+ }
3309
+ if (!options.configLang) {
3310
+ options.configLang = "en";
3311
+ }
3312
+ if (!options.aiOutputLang) {
3313
+ options.aiOutputLang = "en";
3314
+ }
3315
+ if (!options.aiPersonality) {
3316
+ options.aiPersonality = "professional";
3317
+ }
3318
+ if (typeof options.installCometixLine === "string") {
3319
+ options.installCometixLine = options.installCometixLine.toLowerCase() === "true";
3320
+ }
3321
+ if (options.installCometixLine === void 0) {
3322
+ options.installCometixLine = true;
3323
+ }
3324
+ if (options.configAction && !["new", "backup", "merge", "docs-only", "skip"].includes(options.configAction)) {
3325
+ throw new Error(
3326
+ `Invalid configAction value: ${options.configAction}. Must be 'new', 'backup', 'merge', 'docs-only', or 'skip'`
3327
+ );
3328
+ }
3329
+ if (options.apiType && !["auth_token", "api_key", "ccr_proxy", "skip"].includes(options.apiType)) {
3330
+ throw new Error(
3331
+ `Invalid apiType value: ${options.apiType}. Must be 'auth_token', 'api_key', 'ccr_proxy', or 'skip'`
3332
+ );
3333
+ }
3334
+ if (options.apiType === "api_key" && !options.apiKey) {
3335
+ throw new Error('API key is required when apiType is "api_key"');
3336
+ }
3337
+ if (options.apiType === "auth_token" && !options.apiKey) {
3338
+ throw new Error('API key is required when apiType is "auth_token"');
3339
+ }
3340
+ if (typeof options.mcpServices === "string") {
3341
+ if (options.mcpServices === "skip") {
3342
+ options.mcpServices = false;
3343
+ } else if (options.mcpServices === "all") {
3344
+ options.mcpServices = MCP_SERVICES.filter((s) => !s.requiresApiKey).map((s) => s.id);
3345
+ } else {
3346
+ options.mcpServices = options.mcpServices.split(",").map((s) => s.trim());
3233
3347
  }
3234
- if (!hasCCometixLineConfig()) {
3235
- try {
3236
- addCCometixLineConfig();
3237
- console.log(ansis.green(`\u2714 ${i18n.cometix.statusLineConfigured || "Claude Code statusLine configured"}`));
3238
- } catch (error) {
3239
- console.warn(ansis.yellow(`\u26A0 ${i18n.cometix.statusLineConfigFailed || "Failed to configure statusLine"}: ${error}`));
3348
+ }
3349
+ if (Array.isArray(options.mcpServices)) {
3350
+ const validServices = MCP_SERVICES.map((s) => s.id);
3351
+ for (const service of options.mcpServices) {
3352
+ if (!validServices.includes(service)) {
3353
+ throw new Error(`Invalid MCP service: ${service}. Available services: ${validServices.join(", ")}`);
3240
3354
  }
3355
+ }
3356
+ }
3357
+ if (typeof options.workflows === "string") {
3358
+ if (options.workflows === "skip") {
3359
+ options.workflows = false;
3360
+ } else if (options.workflows === "all") {
3361
+ options.workflows = WORKFLOW_CONFIGS.map((w) => w.id);
3241
3362
  } else {
3242
- console.log(ansis.blue(`\u2139 ${i18n.cometix.statusLineAlreadyConfigured || "Claude Code statusLine already configured"}`));
3363
+ options.workflows = options.workflows.split(",").map((s) => s.trim());
3243
3364
  }
3244
- return;
3245
3365
  }
3246
- try {
3247
- console.log(ansis.blue(`${i18n.cometix.installingCometix}`));
3248
- await execAsync(COMETIX_COMMANDS.INSTALL);
3249
- console.log(ansis.green(`\u2714 ${i18n.cometix.cometixInstallSuccess}`));
3250
- try {
3251
- addCCometixLineConfig();
3252
- console.log(ansis.green(`\u2714 ${i18n.cometix.statusLineConfigured || "Claude Code statusLine configured"}`));
3253
- } catch (configError) {
3254
- console.warn(ansis.yellow(`\u26A0 ${i18n.cometix.statusLineConfigFailed || "Failed to configure statusLine"}: ${configError}`));
3255
- console.log(ansis.blue(`\u{1F4A1} ${i18n.cometix.statusLineManualConfig || "Please manually add statusLine configuration to Claude Code settings"}`));
3366
+ if (Array.isArray(options.workflows)) {
3367
+ const validWorkflows = WORKFLOW_CONFIGS.map((w) => w.id);
3368
+ for (const workflow of options.workflows) {
3369
+ if (!validWorkflows.includes(workflow)) {
3370
+ throw new Error(`Invalid workflow: ${workflow}. Available workflows: ${validWorkflows.join(", ")}`);
3371
+ }
3256
3372
  }
3257
- } catch (error) {
3258
- console.error(ansis.red(`\u2717 ${i18n.cometix.cometixInstallFailed}: ${error}`));
3259
- throw error;
3373
+ }
3374
+ if (options.mcpServices === void 0) {
3375
+ options.mcpServices = "all";
3376
+ options.mcpServices = MCP_SERVICES.filter((s) => !s.requiresApiKey).map((s) => s.id);
3377
+ }
3378
+ if (options.workflows === void 0) {
3379
+ options.workflows = "all";
3380
+ options.workflows = WORKFLOW_CONFIGS.map((w) => w.id);
3260
3381
  }
3261
3382
  }
3262
-
3263
3383
  async function init(options = {}) {
3384
+ if (options.skipPrompt) {
3385
+ validateSkipPromptOptions(options);
3386
+ }
3264
3387
  try {
3265
3388
  if (!options.skipBanner) {
3266
3389
  displayBannerWithInfo();
3267
3390
  }
3268
- const scriptLang = await selectScriptLanguage(options.lang);
3391
+ const scriptLang = options.skipPrompt ? options.lang || "en" : await selectScriptLanguage(options.lang);
3269
3392
  const i18n = I18N[scriptLang];
3270
3393
  if (isTermux()) {
3271
3394
  console.log(ansis.yellow(`
@@ -3273,40 +3396,48 @@ async function init(options = {}) {
3273
3396
  console.log(ansis.gray(i18n.installation.termuxEnvironmentInfo));
3274
3397
  }
3275
3398
  let configLang = options.configLang;
3276
- if (!configLang) {
3399
+ if (!configLang && !options.skipPrompt) {
3277
3400
  const { lang } = await inquirer.prompt({
3278
3401
  type: "list",
3279
3402
  name: "lang",
3280
3403
  message: i18n.language.selectConfigLang,
3281
- choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
3282
- name: `${LANG_LABELS[l]} - ${i18n.language.configLangHint[l]}`,
3283
- value: l
3284
- })))
3404
+ choices: addNumbersToChoices(
3405
+ SUPPORTED_LANGS.map((l) => ({
3406
+ name: `${LANG_LABELS[l]} - ${i18n.language.configLangHint[l]}`,
3407
+ value: l
3408
+ }))
3409
+ )
3285
3410
  });
3286
3411
  if (!lang) {
3287
3412
  console.log(ansis.yellow(i18n.common.cancelled));
3288
3413
  process.exit(0);
3289
3414
  }
3290
3415
  configLang = lang;
3416
+ } else if (!configLang && options.skipPrompt) {
3417
+ configLang = "en";
3291
3418
  }
3292
3419
  const zcfConfig = readZcfConfig();
3293
- const aiOutputLang = await resolveAiOutputLanguage(scriptLang, options.aiOutputLang, zcfConfig);
3420
+ const aiOutputLang = options.skipPrompt ? options.aiOutputLang || "en" : await resolveAiOutputLanguage(scriptLang, options.aiOutputLang, zcfConfig);
3294
3421
  const installed = await isClaudeCodeInstalled();
3295
3422
  if (!installed) {
3296
- const { shouldInstall } = await inquirer.prompt({
3297
- type: "confirm",
3298
- name: "shouldInstall",
3299
- message: i18n.installation.installPrompt,
3300
- default: true
3301
- });
3302
- if (shouldInstall === void 0) {
3303
- console.log(ansis.yellow(i18n.common.cancelled));
3304
- process.exit(0);
3305
- }
3306
- if (shouldInstall) {
3423
+ if (options.skipPrompt) {
3307
3424
  await installClaudeCode(scriptLang);
3308
3425
  } else {
3309
- console.log(ansis.yellow(i18n.common.skip));
3426
+ const { shouldInstall } = await inquirer.prompt({
3427
+ type: "confirm",
3428
+ name: "shouldInstall",
3429
+ message: i18n.installation.installPrompt,
3430
+ default: true
3431
+ });
3432
+ if (shouldInstall === void 0) {
3433
+ console.log(ansis.yellow(i18n.common.cancelled));
3434
+ process.exit(0);
3435
+ }
3436
+ if (shouldInstall) {
3437
+ await installClaudeCode(scriptLang);
3438
+ } else {
3439
+ console.log(ansis.yellow(i18n.common.skip));
3440
+ }
3310
3441
  }
3311
3442
  } else {
3312
3443
  console.log(ansis.green(`\u2714 ${i18n.installation.alreadyInstalled}`));
@@ -3314,129 +3445,182 @@ async function init(options = {}) {
3314
3445
  ensureClaudeDir();
3315
3446
  let action = "new";
3316
3447
  if (existsSync(SETTINGS_FILE) && !options.force) {
3317
- const { action: userAction } = await inquirer.prompt({
3318
- type: "list",
3319
- name: "action",
3320
- message: i18n.configuration.existingConfig,
3321
- choices: addNumbersToChoices([
3322
- { name: i18n.configuration.backupAndOverwrite, value: "backup" },
3323
- { name: i18n.configuration.updateDocsOnly, value: "docs-only" },
3324
- { name: i18n.configuration.mergeConfig, value: "merge" },
3325
- { name: i18n.common.skip, value: "skip" }
3326
- ])
3327
- });
3328
- if (!userAction) {
3329
- console.log(ansis.yellow(i18n.common.cancelled));
3330
- process.exit(0);
3331
- }
3332
- action = userAction;
3333
- if (action === "skip") {
3334
- console.log(ansis.yellow(i18n.common.skip));
3335
- return;
3336
- }
3337
- }
3338
- let apiConfig = null;
3339
- const isNewInstall = !existsSync(SETTINGS_FILE);
3340
- if (action !== "docs-only" && (isNewInstall || ["backup", "merge"].includes(action))) {
3341
- const existingApiConfig = getExistingApiConfig();
3342
- if (existingApiConfig) {
3343
- console.log("\n" + ansis.blue(`\u2139 ${i18n.api.existingApiConfig}`));
3344
- console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${existingApiConfig.url || i18n.common.notConfigured}`));
3345
- console.log(
3346
- ansis.gray(
3347
- ` ${i18n.api.apiConfigKey}: ${existingApiConfig.key ? formatApiKeyDisplay(existingApiConfig.key) : i18n.common.notConfigured}`
3348
- )
3349
- );
3350
- console.log(ansis.gray(` ${i18n.api.apiConfigAuthType}: ${existingApiConfig.authType || i18n.common.notConfigured}
3351
- `));
3352
- const { action: apiAction } = await inquirer.prompt({
3448
+ if (options.skipPrompt) {
3449
+ action = options.configAction || "backup";
3450
+ if (action === "skip") {
3451
+ console.log(ansis.yellow(i18n.common.skip));
3452
+ return;
3453
+ }
3454
+ } else {
3455
+ const { action: userAction } = await inquirer.prompt({
3353
3456
  type: "list",
3354
3457
  name: "action",
3355
- message: i18n.api.selectApiAction,
3458
+ message: i18n.configuration.existingConfig,
3356
3459
  choices: addNumbersToChoices([
3357
- { name: i18n.api.keepExistingConfig, value: "keep" },
3358
- { name: i18n.api.modifyAllConfig, value: "modify-all" },
3359
- { name: i18n.api.modifyPartialConfig, value: "modify-partial" },
3360
- { name: i18n.api.useCcrProxy, value: "use-ccr" },
3361
- { name: i18n.api.skipApi, value: "skip" }
3460
+ { name: i18n.configuration.backupAndOverwrite, value: "backup" },
3461
+ { name: i18n.configuration.updateDocsOnly, value: "docs-only" },
3462
+ { name: i18n.configuration.mergeConfig, value: "merge" },
3463
+ { name: i18n.common.skip, value: "skip" }
3362
3464
  ])
3363
3465
  });
3364
- if (!apiAction) {
3466
+ if (!userAction) {
3365
3467
  console.log(ansis.yellow(i18n.common.cancelled));
3366
3468
  process.exit(0);
3367
3469
  }
3368
- if (apiAction === "keep" || apiAction === "skip") {
3369
- apiConfig = null;
3370
- if (apiAction === "keep") {
3371
- try {
3372
- addCompletedOnboarding();
3373
- } catch (error) {
3374
- console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
3375
- }
3376
- }
3377
- } else if (apiAction === "modify-partial") {
3378
- await modifyApiConfigPartially(existingApiConfig, i18n, scriptLang);
3379
- apiConfig = null;
3380
- } else if (apiAction === "modify-all") {
3381
- apiConfig = await configureApiCompletely(i18n, scriptLang);
3382
- } else if (apiAction === "use-ccr") {
3470
+ action = userAction;
3471
+ if (action === "skip") {
3472
+ console.log(ansis.yellow(i18n.common.skip));
3473
+ return;
3474
+ }
3475
+ }
3476
+ } else if (options.skipPrompt && options.configAction) {
3477
+ action = options.configAction;
3478
+ }
3479
+ let apiConfig = null;
3480
+ const isNewInstall = !existsSync(SETTINGS_FILE);
3481
+ if (action !== "docs-only" && (isNewInstall || ["backup", "merge", "new"].includes(action))) {
3482
+ if (options.skipPrompt) {
3483
+ if (options.apiType === "auth_token" && options.apiKey) {
3484
+ apiConfig = {
3485
+ authType: "auth_token",
3486
+ key: options.apiKey,
3487
+ url: options.apiUrl || "https://api.anthropic.com"
3488
+ };
3489
+ } else if (options.apiType === "api_key" && options.apiKey) {
3490
+ apiConfig = {
3491
+ authType: "api_key",
3492
+ key: options.apiKey,
3493
+ url: options.apiUrl || "https://api.anthropic.com"
3494
+ };
3495
+ } else if (options.apiType === "ccr_proxy") {
3383
3496
  const ccrStatus = await isCcrInstalled();
3384
3497
  if (!ccrStatus.hasCorrectPackage) {
3385
3498
  await installCcr(scriptLang);
3386
3499
  } else {
3387
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
3500
+ console.log(ansis.green(`\u2714 ${i18n.ccr?.ccrAlreadyInstalled || "CCR already installed"}`));
3388
3501
  }
3389
- const ccrConfigured = await setupCcrConfiguration(scriptLang);
3390
- if (ccrConfigured) {
3391
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
3392
- apiConfig = null;
3502
+ const existingCcrConfig = readCcrConfig();
3503
+ if (existingCcrConfig) {
3504
+ const backupPath = backupCcrConfig(scriptLang);
3505
+ if (backupPath) {
3506
+ console.log(ansis.gray(`\u2714 ${i18n.ccr?.ccrBackupSuccess?.replace("{path}", backupPath) || "CCR configuration backed up"}`));
3507
+ }
3508
+ }
3509
+ const defaultCcrConfig = createDefaultCcrConfig();
3510
+ writeCcrConfig(defaultCcrConfig);
3511
+ console.log(ansis.green(`\u2714 ${i18n.ccr?.ccrConfigSuccess || "CCR configuration created"}`));
3512
+ await configureCcrProxy(defaultCcrConfig);
3513
+ console.log(ansis.green(`\u2714 ${i18n.ccr?.proxyConfigSuccess || "Proxy settings configured"}`));
3514
+ try {
3515
+ addCompletedOnboarding();
3516
+ } catch (error) {
3517
+ console.error(ansis.red(i18n.configuration?.failedToSetOnboarding || "Failed to set onboarding flag"), error);
3393
3518
  }
3519
+ apiConfig = null;
3394
3520
  }
3395
3521
  } else {
3396
- const { apiChoice } = await inquirer.prompt({
3397
- type: "list",
3398
- name: "apiChoice",
3399
- message: i18n.api.configureApi,
3400
- choices: [
3401
- {
3402
- name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
3403
- value: "auth_token",
3404
- short: i18n.api.useAuthToken
3405
- },
3406
- {
3407
- name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
3408
- value: "api_key",
3409
- short: i18n.api.useApiKey
3410
- },
3411
- {
3412
- name: `${i18n.api.useCcrProxy} - ${ansis.gray(i18n.api.ccrProxyDesc)}`,
3413
- value: "ccr_proxy",
3414
- short: i18n.api.useCcrProxy
3415
- },
3416
- {
3417
- name: i18n.api.skipApi,
3418
- value: "skip"
3419
- }
3420
- ]
3421
- });
3422
- if (!apiChoice) {
3423
- console.log(ansis.yellow(i18n.common.cancelled));
3424
- process.exit(0);
3425
- }
3426
- if (apiChoice === "ccr_proxy") {
3427
- const ccrStatus = await isCcrInstalled();
3428
- if (!ccrStatus.hasCorrectPackage) {
3429
- await installCcr(scriptLang);
3430
- } else {
3431
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
3522
+ const existingApiConfig = getExistingApiConfig();
3523
+ if (existingApiConfig) {
3524
+ console.log("\n" + ansis.blue(`\u2139 ${i18n.api.existingApiConfig}`));
3525
+ console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${existingApiConfig.url || i18n.common.notConfigured}`));
3526
+ console.log(
3527
+ ansis.gray(
3528
+ ` ${i18n.api.apiConfigKey}: ${existingApiConfig.key ? formatApiKeyDisplay(existingApiConfig.key) : i18n.common.notConfigured}`
3529
+ )
3530
+ );
3531
+ console.log(
3532
+ ansis.gray(` ${i18n.api.apiConfigAuthType}: ${existingApiConfig.authType || i18n.common.notConfigured}
3533
+ `)
3534
+ );
3535
+ const { action: apiAction } = await inquirer.prompt({
3536
+ type: "list",
3537
+ name: "action",
3538
+ message: i18n.api.selectApiAction,
3539
+ choices: addNumbersToChoices([
3540
+ { name: i18n.api.keepExistingConfig, value: "keep" },
3541
+ { name: i18n.api.modifyAllConfig, value: "modify-all" },
3542
+ { name: i18n.api.modifyPartialConfig, value: "modify-partial" },
3543
+ { name: i18n.api.useCcrProxy, value: "use-ccr" },
3544
+ { name: i18n.api.skipApi, value: "skip" }
3545
+ ])
3546
+ });
3547
+ if (!apiAction) {
3548
+ console.log(ansis.yellow(i18n.common.cancelled));
3549
+ process.exit(0);
3432
3550
  }
3433
- const ccrConfigured = await setupCcrConfiguration(scriptLang);
3434
- if (ccrConfigured) {
3435
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
3551
+ if (apiAction === "keep" || apiAction === "skip") {
3552
+ apiConfig = null;
3553
+ if (apiAction === "keep") {
3554
+ try {
3555
+ addCompletedOnboarding();
3556
+ } catch (error) {
3557
+ console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
3558
+ }
3559
+ }
3560
+ } else if (apiAction === "modify-partial") {
3561
+ await modifyApiConfigPartially(existingApiConfig, i18n, scriptLang);
3436
3562
  apiConfig = null;
3563
+ } else if (apiAction === "modify-all") {
3564
+ apiConfig = await configureApiCompletely(i18n, scriptLang);
3565
+ } else if (apiAction === "use-ccr") {
3566
+ const ccrStatus = await isCcrInstalled();
3567
+ if (!ccrStatus.hasCorrectPackage) {
3568
+ await installCcr(scriptLang);
3569
+ } else {
3570
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
3571
+ }
3572
+ const ccrConfigured = await setupCcrConfiguration(scriptLang);
3573
+ if (ccrConfigured) {
3574
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
3575
+ apiConfig = null;
3576
+ }
3577
+ }
3578
+ } else {
3579
+ const { apiChoice } = await inquirer.prompt({
3580
+ type: "list",
3581
+ name: "apiChoice",
3582
+ message: i18n.api.configureApi,
3583
+ choices: [
3584
+ {
3585
+ name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
3586
+ value: "auth_token",
3587
+ short: i18n.api.useAuthToken
3588
+ },
3589
+ {
3590
+ name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
3591
+ value: "api_key",
3592
+ short: i18n.api.useApiKey
3593
+ },
3594
+ {
3595
+ name: `${i18n.api.useCcrProxy} - ${ansis.gray(i18n.api.ccrProxyDesc)}`,
3596
+ value: "ccr_proxy",
3597
+ short: i18n.api.useCcrProxy
3598
+ },
3599
+ {
3600
+ name: i18n.api.skipApi,
3601
+ value: "skip"
3602
+ }
3603
+ ]
3604
+ });
3605
+ if (!apiChoice) {
3606
+ console.log(ansis.yellow(i18n.common.cancelled));
3607
+ process.exit(0);
3608
+ }
3609
+ if (apiChoice === "ccr_proxy") {
3610
+ const ccrStatus = await isCcrInstalled();
3611
+ if (!ccrStatus.hasCorrectPackage) {
3612
+ await installCcr(scriptLang);
3613
+ } else {
3614
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
3615
+ }
3616
+ const ccrConfigured = await setupCcrConfiguration(scriptLang);
3617
+ if (ccrConfigured) {
3618
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
3619
+ apiConfig = null;
3620
+ }
3621
+ } else if (apiChoice !== "skip") {
3622
+ apiConfig = await configureApiCompletely(i18n, scriptLang, apiChoice);
3437
3623
  }
3438
- } else if (apiChoice !== "skip") {
3439
- apiConfig = await configureApiCompletely(i18n, scriptLang, apiChoice);
3440
3624
  }
3441
3625
  }
3442
3626
  }
@@ -3448,13 +3632,29 @@ async function init(options = {}) {
3448
3632
  }
3449
3633
  if (action === "docs-only") {
3450
3634
  copyConfigFiles(configLang, true);
3451
- await selectAndInstallWorkflows(configLang, scriptLang);
3635
+ if (options.skipPrompt) {
3636
+ if (options.workflows !== false) {
3637
+ await selectAndInstallWorkflows(configLang, scriptLang, options.workflows);
3638
+ }
3639
+ } else {
3640
+ await selectAndInstallWorkflows(configLang, scriptLang);
3641
+ }
3452
3642
  } else if (["backup", "merge", "new"].includes(action)) {
3453
3643
  copyConfigFiles(configLang, false);
3454
- await selectAndInstallWorkflows(configLang, scriptLang);
3644
+ if (options.skipPrompt) {
3645
+ if (options.workflows !== false) {
3646
+ await selectAndInstallWorkflows(configLang, scriptLang, options.workflows);
3647
+ }
3648
+ } else {
3649
+ await selectAndInstallWorkflows(configLang, scriptLang);
3650
+ }
3455
3651
  }
3456
3652
  applyAiLanguageDirective(aiOutputLang);
3457
- await configureAiPersonality(scriptLang);
3653
+ if (options.skipPrompt) {
3654
+ await configureAiPersonality(scriptLang, options.aiPersonality);
3655
+ } else {
3656
+ await configureAiPersonality(scriptLang);
3657
+ }
3458
3658
  if (apiConfig && action !== "docs-only") {
3459
3659
  const configuredApi = configureApi(apiConfig);
3460
3660
  if (configuredApi) {
@@ -3464,23 +3664,34 @@ async function init(options = {}) {
3464
3664
  }
3465
3665
  }
3466
3666
  if (action !== "docs-only") {
3467
- const { shouldConfigureMcp } = await inquirer.prompt({
3468
- type: "confirm",
3469
- name: "shouldConfigureMcp",
3470
- message: i18n.mcp.configureMcp,
3471
- default: true
3472
- });
3473
- if (shouldConfigureMcp === void 0) {
3474
- console.log(ansis.yellow(i18n.common.cancelled));
3475
- process.exit(0);
3667
+ let shouldConfigureMcp = false;
3668
+ if (options.skipPrompt) {
3669
+ shouldConfigureMcp = options.mcpServices !== false;
3670
+ } else {
3671
+ const { shouldConfigureMcp: userChoice } = await inquirer.prompt({
3672
+ type: "confirm",
3673
+ name: "shouldConfigureMcp",
3674
+ message: i18n.mcp.configureMcp,
3675
+ default: true
3676
+ });
3677
+ if (userChoice === void 0) {
3678
+ console.log(ansis.yellow(i18n.common.cancelled));
3679
+ process.exit(0);
3680
+ }
3681
+ shouldConfigureMcp = userChoice;
3476
3682
  }
3477
3683
  if (shouldConfigureMcp) {
3478
3684
  if (isWindows()) {
3479
3685
  console.log(ansis.blue(`\u2139 ${i18n.installation.windowsDetected}`));
3480
3686
  }
3481
- const selectedServices = await selectMcpServices(scriptLang);
3482
- if (selectedServices === void 0) {
3483
- process.exit(0);
3687
+ let selectedServices;
3688
+ if (options.skipPrompt) {
3689
+ selectedServices = options.mcpServices;
3690
+ } else {
3691
+ selectedServices = await selectMcpServices(scriptLang);
3692
+ if (selectedServices === void 0) {
3693
+ process.exit(0);
3694
+ }
3484
3695
  }
3485
3696
  if (selectedServices.length > 0) {
3486
3697
  const mcpBackupPath = backupMcpConfig();
@@ -3493,20 +3704,21 @@ async function init(options = {}) {
3493
3704
  if (!service) continue;
3494
3705
  let config = service.config;
3495
3706
  if (service.requiresApiKey) {
3496
- const { apiKey } = await inquirer.prompt({
3497
- type: "input",
3498
- name: "apiKey",
3499
- message: service.apiKeyPrompt[scriptLang],
3500
- validate: (value) => !!value || i18n.api.keyRequired
3501
- });
3502
- if (apiKey === void 0) {
3503
- console.log(ansis.yellow(`${i18n.common.skip}: ${service.name[scriptLang]}`));
3707
+ if (options.skipPrompt) {
3708
+ console.log(ansis.yellow(`${i18n.common.skip}: ${service.name[scriptLang]} (requires API key)`));
3504
3709
  continue;
3505
- }
3506
- if (apiKey) {
3507
- config = buildMcpServerConfig(service.config, apiKey, service.apiKeyPlaceholder, service.apiKeyEnvVar);
3508
3710
  } else {
3509
- continue;
3711
+ const response = await inquirer.prompt({
3712
+ type: "input",
3713
+ name: "apiKey",
3714
+ message: service.apiKeyPrompt[scriptLang],
3715
+ validate: (value) => !!value || i18n.api.keyRequired
3716
+ });
3717
+ if (!response.apiKey) {
3718
+ console.log(ansis.yellow(`${i18n.common.skip}: ${service.name[scriptLang]}`));
3719
+ continue;
3720
+ }
3721
+ config = buildMcpServerConfig(service.config, response.apiKey, service.apiKeyPlaceholder, service.apiKeyEnvVar);
3510
3722
  }
3511
3723
  }
3512
3724
  newServers[service.id] = config;
@@ -3525,15 +3737,21 @@ async function init(options = {}) {
3525
3737
  }
3526
3738
  const cometixInstalled = await isCometixLineInstalled();
3527
3739
  if (!cometixInstalled) {
3528
- const { shouldInstallCometix } = await inquirer.prompt({
3529
- type: "confirm",
3530
- name: "shouldInstallCometix",
3531
- message: i18n.cometix.installCometixPrompt,
3532
- default: true
3533
- });
3534
- if (shouldInstallCometix === void 0) {
3535
- console.log(ansis.yellow(i18n.common.cancelled));
3536
- process.exit(0);
3740
+ let shouldInstallCometix = false;
3741
+ if (options.skipPrompt) {
3742
+ shouldInstallCometix = options.installCometixLine !== false;
3743
+ } else {
3744
+ const { shouldInstallCometix: userChoice } = await inquirer.prompt({
3745
+ type: "confirm",
3746
+ name: "shouldInstallCometix",
3747
+ message: i18n.cometix.installCometixPrompt,
3748
+ default: true
3749
+ });
3750
+ if (userChoice === void 0) {
3751
+ console.log(ansis.yellow(i18n.common.cancelled));
3752
+ process.exit(0);
3753
+ }
3754
+ shouldInstallCometix = userChoice;
3537
3755
  }
3538
3756
  if (shouldInstallCometix) {
3539
3757
  await installCometixLine(scriptLang);