zcf 2.9.11 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.0";
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,775 +1983,654 @@ 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,
2171
- currentVersion,
2172
- latestVersion,
2173
- needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
2174
- };
2175
- }
2176
- async function checkClaudeCodeVersion() {
2177
- const currentVersion = await getInstalledVersion("claude");
2178
- const latestVersion = await getLatestVersion("@anthropic-ai/claude-code");
2179
- return {
2180
- installed: currentVersion !== null,
2181
- currentVersion,
2182
- latestVersion,
2183
- needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
2184
- };
2185
- }
2186
- async function checkCometixLineVersion() {
2187
- const currentVersion = await getInstalledVersion("ccometix");
2188
- const latestVersion = await getLatestVersion("ccometix");
2189
- return {
2190
- installed: currentVersion !== null,
2191
- currentVersion,
2192
- latestVersion,
2193
- needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
2194
- };
2195
- }
2196
-
2197
- const execAsync$3 = promisify(exec$1);
2198
- async function updateCcr(scriptLang, force = false) {
2183
+ async function selectCcrPreset(scriptLang) {
2199
2184
  const i18n = getTranslation(scriptLang);
2200
- const spinner = ora(i18n.updater.checkingVersion).start();
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
+ }
2201
2191
  try {
2202
- const { installed, currentVersion, latestVersion, needsUpdate } = await checkCcrVersion();
2203
- spinner.stop();
2204
- if (!installed) {
2205
- console.log(ansis.yellow(i18n.updater.ccrNotInstalled));
2206
- return false;
2207
- }
2208
- if (!needsUpdate && !force) {
2209
- console.log(ansis.green(format(i18n.updater.ccrUpToDate, { version: currentVersion || "" })));
2210
- return true;
2211
- }
2212
- if (!latestVersion) {
2213
- console.log(ansis.yellow(i18n.updater.cannotCheckVersion));
2214
- return false;
2215
- }
2216
- console.log(ansis.cyan(format(i18n.updater.currentVersion, { version: currentVersion || "" })));
2217
- console.log(ansis.cyan(format(i18n.updater.latestVersion, { version: latestVersion })));
2218
- const { confirm } = await inquirer.prompt({
2219
- type: "confirm",
2220
- name: "confirm",
2221
- message: format(i18n.updater.confirmUpdate, { tool: "CCR" }),
2222
- default: true
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
2223
2207
  });
2224
- if (!confirm) {
2225
- console.log(ansis.gray(i18n.updater.updateSkipped));
2226
- return true;
2227
- }
2228
- const updateSpinner = ora(format(i18n.updater.updating, { tool: "CCR" })).start();
2229
- try {
2230
- await execAsync$3("npm update -g @musistudio/claude-code-router");
2231
- updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "CCR" }));
2232
- return true;
2233
- } catch (error) {
2234
- updateSpinner.fail(format(i18n.updater.updateFailed, { tool: "CCR" }));
2235
- console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2236
- return false;
2237
- }
2208
+ return preset;
2238
2209
  } catch (error) {
2239
- spinner.fail(i18n.updater.checkFailed);
2240
- console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2241
- return false;
2210
+ if (error.name === "ExitPromptError") {
2211
+ console.log(ansis.yellow(i18n.common.cancelled));
2212
+ return null;
2213
+ }
2214
+ throw error;
2242
2215
  }
2243
2216
  }
2244
- async function updateClaudeCode(scriptLang, force = false) {
2217
+ async function configureCcrWithPreset(preset, scriptLang) {
2245
2218
  const i18n = getTranslation(scriptLang);
2246
- const spinner = ora(i18n.updater.checkingVersion).start();
2247
- try {
2248
- const { installed, currentVersion, latestVersion, needsUpdate } = await checkClaudeCodeVersion();
2249
- spinner.stop();
2250
- if (!installed) {
2251
- console.log(ansis.yellow(i18n.updater.claudeCodeNotInstalled));
2252
- return false;
2253
- }
2254
- if (!needsUpdate && !force) {
2255
- console.log(ansis.green(format(i18n.updater.claudeCodeUpToDate, { version: currentVersion || "" })));
2256
- return true;
2257
- }
2258
- if (!latestVersion) {
2259
- console.log(ansis.yellow(i18n.updater.cannotCheckVersion));
2260
- return false;
2261
- }
2262
- console.log(ansis.cyan(format(i18n.updater.currentVersion, { version: currentVersion || "" })));
2263
- console.log(ansis.cyan(format(i18n.updater.latestVersion, { version: latestVersion })));
2264
- const { confirm } = await inquirer.prompt({
2265
- type: "confirm",
2266
- name: "confirm",
2267
- message: format(i18n.updater.confirmUpdate, { tool: "Claude Code" }),
2268
- default: true
2269
- });
2270
- if (!confirm) {
2271
- console.log(ansis.gray(i18n.updater.updateSkipped));
2272
- return true;
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;
2273
2243
  }
2274
- const updateSpinner = ora(format(i18n.updater.updating, { tool: "Claude Code" })).start();
2244
+ } else {
2245
+ provider.api_key = "sk-free";
2246
+ }
2247
+ let defaultModel = preset.models[0];
2248
+ if (preset.models.length > 1) {
2275
2249
  try {
2276
- await execAsync$3("npm update -g @anthropic-ai/claude-code");
2277
- updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "Claude Code" }));
2278
- return true;
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;
2279
2260
  } catch (error) {
2280
- updateSpinner.fail(format(i18n.updater.updateFailed, { tool: "Claude Code" }));
2281
- console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2282
- return false;
2261
+ if (error.name === "ExitPromptError") {
2262
+ throw error;
2263
+ }
2264
+ throw error;
2283
2265
  }
2284
- } catch (error) {
2285
- spinner.fail(i18n.updater.checkFailed);
2286
- console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2287
- return false;
2288
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
2289
  }
2290
- async function updateCometixLine(scriptLang, force = false) {
2290
+ async function restartAndCheckCcrStatus(scriptLang) {
2291
2291
  const i18n = getTranslation(scriptLang);
2292
- const spinner = ora(i18n.updater.checkingVersion).start();
2293
2292
  try {
2294
- const { installed, currentVersion, latestVersion, needsUpdate } = await checkCometixLineVersion();
2295
- spinner.stop();
2296
- if (!installed) {
2297
- console.log(ansis.yellow(i18n.updater.cometixLineNotInstalled));
2298
- return false;
2299
- }
2300
- if (!needsUpdate && !force) {
2301
- console.log(ansis.green(format(i18n.updater.cometixLineUpToDate, { version: currentVersion || "" })));
2302
- return true;
2303
- }
2304
- if (!latestVersion) {
2305
- console.log(ansis.yellow(i18n.updater.cannotCheckVersion));
2306
- return false;
2307
- }
2308
- console.log(ansis.cyan(format(i18n.updater.currentVersion, { version: currentVersion || "" })));
2309
- console.log(ansis.cyan(format(i18n.updater.latestVersion, { version: latestVersion })));
2310
- const { confirm } = await inquirer.prompt({
2311
- type: "confirm",
2312
- name: "confirm",
2313
- message: format(i18n.updater.confirmUpdate, { tool: "CCometixLine" }),
2314
- default: true
2315
- });
2316
- if (!confirm) {
2317
- console.log(ansis.gray(i18n.updater.updateSkipped));
2318
- return true;
2319
- }
2320
- const updateSpinner = ora(format(i18n.updater.updating, { tool: "CCometixLine" })).start();
2321
- try {
2322
- await execAsync$3("cargo install ccometix");
2323
- updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "CCometixLine" }));
2324
- return true;
2325
- } catch (error) {
2326
- updateSpinner.fail(format(i18n.updater.updateFailed, { tool: "CCometixLine" }));
2327
- console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2328
- return false;
2329
- }
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));
2330
2299
  } catch (error) {
2331
- spinner.fail(i18n.updater.checkFailed);
2332
- console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2333
- return false;
2300
+ console.error(ansis.red(`${i18n.ccr.ccrRestartFailed}:`), error.message || error);
2301
+ if (process.env.DEBUG) {
2302
+ console.error("Full error:", error);
2303
+ }
2334
2304
  }
2335
2305
  }
2336
- async function checkAndUpdateTools(scriptLang) {
2306
+ function showConfigurationTips(scriptLang, apiKey) {
2337
2307
  const i18n = getTranslation(scriptLang);
2338
2308
  console.log(ansis.bold.cyan(`
2339
- \u{1F50D} ${i18n.updater.checkingTools}
2340
- `));
2341
- await updateCcr(scriptLang);
2342
- console.log();
2343
- await updateClaudeCode(scriptLang);
2344
- console.log();
2345
- await updateCometixLine(scriptLang);
2346
- }
2347
-
2348
- async function isClaudeCodeInstalled() {
2349
- return await commandExists("claude");
2350
- }
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);
2357
- return;
2358
- }
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`));
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"}`));
2365
2316
  }
2366
- console.log(i18n.installation.installing);
2317
+ console.log("");
2318
+ }
2319
+ async function setupCcrConfiguration(scriptLang) {
2320
+ const i18n = getTranslation(scriptLang);
2367
2321
  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`));
2322
+ const existingConfig = readCcrConfig();
2323
+ if (existingConfig) {
2324
+ console.log(ansis.blue(`\u2139 ${i18n.ccr.existingCcrConfig}`));
2325
+ let shouldBackupAndReconfigure = false;
2326
+ try {
2327
+ const result = await inquirer.prompt({
2328
+ type: "confirm",
2329
+ name: "overwrite",
2330
+ message: i18n.ccr.overwriteCcrConfig,
2331
+ default: false
2332
+ });
2333
+ shouldBackupAndReconfigure = result.overwrite;
2334
+ } catch (error) {
2335
+ if (error.name === "ExitPromptError") {
2336
+ console.log(ansis.yellow(i18n.common.cancelled));
2337
+ return false;
2338
+ }
2339
+ throw error;
2340
+ }
2341
+ if (!shouldBackupAndReconfigure) {
2342
+ console.log(ansis.yellow(`${i18n.ccr.keepingExistingConfig}`));
2343
+ await configureCcrProxy(existingConfig);
2344
+ return true;
2345
+ }
2346
+ backupCcrConfig(scriptLang);
2347
+ }
2348
+ const preset = await selectCcrPreset(scriptLang);
2349
+ if (!preset) {
2350
+ return false;
2373
2351
  }
2352
+ let config;
2353
+ if (preset === "skip") {
2354
+ console.log(ansis.yellow(`${i18n.ccr.skipConfiguring}`));
2355
+ config = {
2356
+ LOG: false,
2357
+ CLAUDE_PATH: "",
2358
+ HOST: "127.0.0.1",
2359
+ PORT: 3456,
2360
+ APIKEY: "sk-zcf-x-ccr",
2361
+ API_TIMEOUT_MS: "600000",
2362
+ PROXY_URL: "",
2363
+ transformers: [],
2364
+ Providers: [],
2365
+ // Empty providers array
2366
+ Router: {
2367
+ // Empty router configuration - user will configure in CCR UI
2368
+ }
2369
+ };
2370
+ } else {
2371
+ config = await configureCcrWithPreset(preset, scriptLang);
2372
+ }
2373
+ writeCcrConfig(config);
2374
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrConfigSuccess}`));
2375
+ await configureCcrProxy(config);
2376
+ console.log(ansis.green(`\u2714 ${i18n.ccr.proxyConfigSuccess}`));
2377
+ await restartAndCheckCcrStatus(scriptLang);
2378
+ showConfigurationTips(scriptLang, config.APIKEY);
2379
+ try {
2380
+ addCompletedOnboarding();
2381
+ } catch (error) {
2382
+ console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
2383
+ }
2384
+ return true;
2374
2385
  } catch (error) {
2375
- console.error(`\u2716 ${i18n.installation.installFailed}`);
2376
- if (isTermux()) {
2377
- console.error(ansis.yellow(`
2378
- ${i18n.installation.termuxInstallHint}
2379
- `));
2386
+ if (error.name === "ExitPromptError") {
2387
+ console.log(ansis.yellow(i18n.common.cancelled));
2388
+ return false;
2380
2389
  }
2381
- throw error;
2390
+ console.error(ansis.red(`${i18n.ccr.ccrConfigFailed}:`), error);
2391
+ return false;
2382
2392
  }
2383
2393
  }
2384
-
2385
- async function selectAiOutputLanguage(scriptLang, defaultLang) {
2394
+ async function configureCcrFeature(scriptLang) {
2386
2395
  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);
2420
- }
2421
- return customLang;
2396
+ const backupDir = backupExistingConfig();
2397
+ if (backupDir) {
2398
+ console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
2422
2399
  }
2423
- return aiOutputLang;
2400
+ await setupCcrConfiguration(scriptLang);
2424
2401
  }
2425
- async function selectScriptLanguage(currentLang) {
2426
- const zcfConfig = readZcfConfig();
2427
- if (zcfConfig?.preferredLang) {
2428
- return zcfConfig.preferredLang;
2429
- }
2430
- if (currentLang) {
2431
- return currentLang;
2432
- }
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
- })))
2402
+
2403
+ function format(template, replacements) {
2404
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
2405
+ return replacements[key] || match;
2441
2406
  });
2442
- if (!lang) {
2443
- console.log(ansis.yellow("Operation cancelled / \u64CD\u4F5C\u5DF2\u53D6\u6D88"));
2444
- process.exit(0);
2407
+ }
2408
+
2409
+ const execAsync$3 = promisify$1(exec$2);
2410
+ async function getInstalledVersion(command) {
2411
+ try {
2412
+ let stdout;
2413
+ try {
2414
+ const result = await execAsync$3(`${command} -v`);
2415
+ stdout = result.stdout;
2416
+ } catch {
2417
+ const result = await execAsync$3(`${command} --version`);
2418
+ stdout = result.stdout;
2419
+ }
2420
+ const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
2421
+ return versionMatch ? versionMatch[1] : null;
2422
+ } catch {
2423
+ return null;
2445
2424
  }
2446
- const scriptLang = lang;
2447
- updateZcfConfig({
2448
- version,
2449
- preferredLang: scriptLang
2450
- });
2451
- return scriptLang;
2452
2425
  }
2453
- async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfig) {
2454
- const i18n = getTranslation(scriptLang);
2455
- if (commandLineOption) {
2456
- return commandLineOption;
2426
+ async function getLatestVersion(packageName) {
2427
+ try {
2428
+ const { stdout } = await execAsync$3(`npm view ${packageName} version`);
2429
+ return stdout.trim();
2430
+ } catch {
2431
+ return null;
2457
2432
  }
2458
- if (savedConfig?.aiOutputLang) {
2459
- console.log(ansis.gray(`\u2714 ${i18n.language.aiOutputLangHint}: ${savedConfig.aiOutputLang}`));
2460
- return savedConfig.aiOutputLang;
2433
+ }
2434
+ function compareVersions(current, latest) {
2435
+ if (!semver.valid(current) || !semver.valid(latest)) {
2436
+ return -1;
2461
2437
  }
2462
- return await selectAiOutputLanguage(scriptLang, scriptLang);
2438
+ return semver.compare(current, latest);
2439
+ }
2440
+ function shouldUpdate(current, latest) {
2441
+ return compareVersions(current, latest) < 0;
2442
+ }
2443
+ async function checkCcrVersion() {
2444
+ const currentVersion = await getInstalledVersion("ccr");
2445
+ const latestVersion = await getLatestVersion("@musistudio/claude-code-router");
2446
+ return {
2447
+ installed: currentVersion !== null,
2448
+ currentVersion,
2449
+ latestVersion,
2450
+ needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
2451
+ };
2452
+ }
2453
+ async function checkClaudeCodeVersion() {
2454
+ const currentVersion = await getInstalledVersion("claude");
2455
+ const latestVersion = await getLatestVersion("@anthropic-ai/claude-code");
2456
+ return {
2457
+ installed: currentVersion !== null,
2458
+ currentVersion,
2459
+ latestVersion,
2460
+ needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
2461
+ };
2462
+ }
2463
+ async function checkCometixLineVersion() {
2464
+ const currentVersion = await getInstalledVersion("ccometix");
2465
+ const latestVersion = await getLatestVersion("ccometix");
2466
+ return {
2467
+ installed: currentVersion !== null,
2468
+ currentVersion,
2469
+ latestVersion,
2470
+ needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
2471
+ };
2463
2472
  }
2464
2473
 
2465
- const prompts = {
2466
- __proto__: null,
2467
- resolveAiOutputLanguage: resolveAiOutputLanguage,
2468
- selectAiOutputLanguage: selectAiOutputLanguage,
2469
- selectScriptLanguage: selectScriptLanguage
2470
- };
2471
-
2472
- async function selectMcpServices(scriptLang) {
2474
+ const execAsync$2 = promisify$1(exec$2);
2475
+ async function updateCcr(scriptLang, force = false) {
2473
2476
  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;
2488
- }
2489
- return services;
2490
- }
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"
2543
- }
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);
2550
- }
2551
-
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);
2477
+ const spinner = ora(i18n.updater.checkingVersion).start();
2478
+ try {
2479
+ const { installed, currentVersion, latestVersion, needsUpdate } = await checkCcrVersion();
2480
+ spinner.stop();
2481
+ if (!installed) {
2482
+ console.log(ansis.yellow(i18n.updater.ccrNotInstalled));
2483
+ return false;
2484
+ }
2485
+ if (!needsUpdate && !force) {
2486
+ console.log(ansis.green(format(i18n.updater.ccrUpToDate, { version: currentVersion || "" })));
2487
+ return true;
2584
2488
  }
2489
+ if (!latestVersion) {
2490
+ console.log(ansis.yellow(i18n.updater.cannotCheckVersion));
2491
+ return false;
2492
+ }
2493
+ console.log(ansis.cyan(format(i18n.updater.currentVersion, { version: currentVersion || "" })));
2494
+ console.log(ansis.cyan(format(i18n.updater.latestVersion, { version: latestVersion })));
2495
+ const { confirm } = await inquirer.prompt({
2496
+ type: "confirm",
2497
+ name: "confirm",
2498
+ message: format(i18n.updater.confirmUpdate, { tool: "CCR" }),
2499
+ default: true
2500
+ });
2501
+ if (!confirm) {
2502
+ console.log(ansis.gray(i18n.updater.updateSkipped));
2503
+ return true;
2504
+ }
2505
+ const updateSpinner = ora(format(i18n.updater.updating, { tool: "CCR" })).start();
2506
+ try {
2507
+ await execAsync$2("npm update -g @musistudio/claude-code-router");
2508
+ updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "CCR" }));
2509
+ return true;
2510
+ } catch (error) {
2511
+ updateSpinner.fail(format(i18n.updater.updateFailed, { tool: "CCR" }));
2512
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2513
+ return false;
2514
+ }
2515
+ } catch (error) {
2516
+ spinner.fail(i18n.updater.checkFailed);
2517
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2518
+ return false;
2585
2519
  }
2586
2520
  }
2587
- async function installWorkflowWithDependencies(config, configLang, scriptLang) {
2588
- const rootDir = getRootDir();
2521
+ async function updateClaudeCode(scriptLang, force = false) {
2589
2522
  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
- }
2523
+ const spinner = ora(i18n.updater.checkingVersion).start();
2524
+ try {
2525
+ const { installed, currentVersion, latestVersion, needsUpdate } = await checkClaudeCodeVersion();
2526
+ spinner.stop();
2527
+ if (!installed) {
2528
+ console.log(ansis.yellow(i18n.updater.claudeCodeNotInstalled));
2529
+ return false;
2619
2530
  }
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 });
2531
+ if (!needsUpdate && !force) {
2532
+ console.log(ansis.green(format(i18n.updater.claudeCodeUpToDate, { version: currentVersion || "" })));
2533
+ return true;
2625
2534
  }
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
- }
2641
- }
2642
- }
2535
+ if (!latestVersion) {
2536
+ console.log(ansis.yellow(i18n.updater.cannotCheckVersion));
2537
+ return false;
2643
2538
  }
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}`));
2539
+ console.log(ansis.cyan(format(i18n.updater.currentVersion, { version: currentVersion || "" })));
2540
+ console.log(ansis.cyan(format(i18n.updater.latestVersion, { version: latestVersion })));
2541
+ const { confirm } = await inquirer.prompt({
2542
+ type: "confirm",
2543
+ name: "confirm",
2544
+ message: format(i18n.updater.confirmUpdate, { tool: "Claude Code" }),
2545
+ default: true
2546
+ });
2547
+ if (!confirm) {
2548
+ console.log(ansis.gray(i18n.updater.updateSkipped));
2549
+ return true;
2650
2550
  }
2651
- } else {
2652
- console.log(ansis.red(`\u2717 ${workflowName} ${i18n.workflow.workflowInstallError}`));
2551
+ const updateSpinner = ora(format(i18n.updater.updating, { tool: "Claude Code" })).start();
2552
+ try {
2553
+ await execAsync$2("npm update -g @anthropic-ai/claude-code");
2554
+ updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "Claude Code" }));
2555
+ return true;
2556
+ } catch (error) {
2557
+ updateSpinner.fail(format(i18n.updater.updateFailed, { tool: "Claude Code" }));
2558
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2559
+ return false;
2560
+ }
2561
+ } catch (error) {
2562
+ spinner.fail(i18n.updater.checkFailed);
2563
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2564
+ return false;
2653
2565
  }
2654
- return result;
2655
2566
  }
2656
- async function cleanupOldVersionFiles(scriptLang) {
2567
+ async function updateCometixLine(scriptLang, force = false) {
2657
2568
  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)) {
2670
- 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")}`));
2675
- }
2569
+ const spinner = ora(i18n.updater.checkingVersion).start();
2570
+ try {
2571
+ const { installed, currentVersion, latestVersion, needsUpdate } = await checkCometixLineVersion();
2572
+ spinner.stop();
2573
+ if (!installed) {
2574
+ console.log(ansis.yellow(i18n.updater.cometixLineNotInstalled));
2575
+ return false;
2676
2576
  }
2677
- }
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")}`));
2685
- }
2577
+ if (!needsUpdate && !force) {
2578
+ console.log(ansis.green(format(i18n.updater.cometixLineUpToDate, { version: currentVersion || "" })));
2579
+ return true;
2686
2580
  }
2581
+ if (!latestVersion) {
2582
+ console.log(ansis.yellow(i18n.updater.cannotCheckVersion));
2583
+ return false;
2584
+ }
2585
+ console.log(ansis.cyan(format(i18n.updater.currentVersion, { version: currentVersion || "" })));
2586
+ console.log(ansis.cyan(format(i18n.updater.latestVersion, { version: latestVersion })));
2587
+ const { confirm } = await inquirer.prompt({
2588
+ type: "confirm",
2589
+ name: "confirm",
2590
+ message: format(i18n.updater.confirmUpdate, { tool: "CCometixLine" }),
2591
+ default: true
2592
+ });
2593
+ if (!confirm) {
2594
+ console.log(ansis.gray(i18n.updater.updateSkipped));
2595
+ return true;
2596
+ }
2597
+ const updateSpinner = ora(format(i18n.updater.updating, { tool: "CCometixLine" })).start();
2598
+ try {
2599
+ await execAsync$2("cargo install ccometix");
2600
+ updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "CCometixLine" }));
2601
+ return true;
2602
+ } catch (error) {
2603
+ updateSpinner.fail(format(i18n.updater.updateFailed, { tool: "CCometixLine" }));
2604
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2605
+ return false;
2606
+ }
2607
+ } catch (error) {
2608
+ spinner.fail(i18n.updater.checkFailed);
2609
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2610
+ return false;
2687
2611
  }
2688
2612
  }
2613
+ async function checkAndUpdateTools(scriptLang) {
2614
+ const i18n = getTranslation(scriptLang);
2615
+ console.log(ansis.bold.cyan(`
2616
+ \u{1F50D} ${i18n.updater.checkingTools}
2617
+ `));
2618
+ await updateCcr(scriptLang);
2619
+ console.log();
2620
+ await updateClaudeCode(scriptLang);
2621
+ console.log();
2622
+ await updateCometixLine(scriptLang);
2623
+ }
2689
2624
 
2690
- const execAsync$2 = promisify$1(exec$2);
2625
+ const execAsync$1 = promisify(exec$1);
2691
2626
  async function isCcrInstalled() {
2692
2627
  let commandExists = false;
2693
2628
  try {
2694
- await execAsync$2("ccr version");
2629
+ await execAsync$1("ccr version");
2695
2630
  commandExists = true;
2696
2631
  } catch {
2697
2632
  try {
2698
- await execAsync$2("which ccr");
2633
+ await execAsync$1("which ccr");
2699
2634
  commandExists = true;
2700
2635
  } catch {
2701
2636
  commandExists = false;
@@ -2703,7 +2638,7 @@ async function isCcrInstalled() {
2703
2638
  }
2704
2639
  let hasCorrectPackage = false;
2705
2640
  try {
2706
- await execAsync$2("npm list -g @musistudio/claude-code-router");
2641
+ await execAsync$1("npm list -g @musistudio/claude-code-router");
2707
2642
  hasCorrectPackage = true;
2708
2643
  } catch {
2709
2644
  hasCorrectPackage = false;
@@ -2723,549 +2658,735 @@ async function installCcr(scriptLang) {
2723
2658
  }
2724
2659
  if (isInstalled && !hasCorrectPackage) {
2725
2660
  try {
2726
- await execAsync$2("npm list -g claude-code-router");
2661
+ await execAsync$1("npm list -g claude-code-router");
2727
2662
  console.log(ansis.yellow(`\u26A0 ${i18n.ccr.detectedIncorrectPackage}`));
2728
2663
  try {
2729
- await execAsync$2("npm uninstall -g claude-code-router");
2664
+ await execAsync$1("npm uninstall -g claude-code-router");
2730
2665
  console.log(ansis.green(`\u2714 ${i18n.ccr.uninstalledIncorrectPackage}`));
2731
2666
  } catch (uninstallError) {
2732
2667
  console.log(ansis.yellow(`\u26A0 ${i18n.ccr.failedToUninstallIncorrectPackage}`));
2733
2668
  }
2734
- } catch {
2669
+ } catch {
2670
+ }
2671
+ }
2672
+ console.log(ansis.cyan(`\u{1F4E6} ${i18n.ccr.installingCcr}`));
2673
+ try {
2674
+ await execAsync$1("npm install -g @musistudio/claude-code-router --force");
2675
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrInstallSuccess}`));
2676
+ } catch (error) {
2677
+ if (error.message?.includes("EEXIST")) {
2678
+ console.log(ansis.yellow(`\u26A0 ${i18n.ccr.ccrAlreadyInstalled}`));
2679
+ await updateCcr(scriptLang);
2680
+ return;
2681
+ }
2682
+ console.error(ansis.red(`\u2716 ${i18n.ccr.ccrInstallFailed}`));
2683
+ throw error;
2684
+ }
2685
+ }
2686
+
2687
+ const COMETIX_PACKAGE_NAME = "@cometix/ccline";
2688
+ const COMETIX_COMMAND_NAME = "ccline";
2689
+ const COMETIX_COMMANDS = {
2690
+ CHECK_INSTALL: `npm list -g ${COMETIX_PACKAGE_NAME}`,
2691
+ INSTALL: `npm install -g ${COMETIX_PACKAGE_NAME}`,
2692
+ UPDATE: `npm update -g ${COMETIX_PACKAGE_NAME}`,
2693
+ PRINT_CONFIG: `${COMETIX_COMMAND_NAME} --print`
2694
+ };
2695
+
2696
+ function getPlatformStatusLineConfig() {
2697
+ return {
2698
+ type: "command",
2699
+ command: isWindows() ? "%USERPROFILE%\\.claude\\ccline\\ccline.exe" : "~/.claude/ccline/ccline",
2700
+ padding: 0
2701
+ };
2702
+ }
2703
+
2704
+ function addCCometixLineConfig() {
2705
+ try {
2706
+ const statusLineConfig = getPlatformStatusLineConfig();
2707
+ let settings = {};
2708
+ if (exists(SETTINGS_FILE)) {
2709
+ settings = readJsonConfig(SETTINGS_FILE) || {};
2710
+ }
2711
+ settings.statusLine = statusLineConfig;
2712
+ writeJsonConfig(SETTINGS_FILE, settings);
2713
+ return true;
2714
+ } catch (error) {
2715
+ console.error("Failed to add CCometixLine configuration:", error);
2716
+ return false;
2717
+ }
2718
+ }
2719
+ function hasCCometixLineConfig() {
2720
+ try {
2721
+ if (!exists(SETTINGS_FILE)) {
2722
+ return false;
2723
+ }
2724
+ const settings = readJsonConfig(SETTINGS_FILE);
2725
+ return !!settings?.statusLine?.command?.includes("ccline");
2726
+ } catch (error) {
2727
+ return false;
2728
+ }
2729
+ }
2730
+
2731
+ const execAsync = promisify(exec$1);
2732
+ async function isCometixLineInstalled() {
2733
+ try {
2734
+ await execAsync(COMETIX_COMMANDS.CHECK_INSTALL);
2735
+ return true;
2736
+ } catch {
2737
+ return false;
2738
+ }
2739
+ }
2740
+ async function installCometixLine(scriptLang) {
2741
+ const i18n = getTranslation(scriptLang);
2742
+ const isInstalled = await isCometixLineInstalled();
2743
+ if (isInstalled) {
2744
+ console.log(ansis.green(`\u2714 ${i18n.cometix.cometixAlreadyInstalled}`));
2745
+ try {
2746
+ console.log(ansis.blue(`${i18n.cometix.installingOrUpdating}`));
2747
+ await execAsync(COMETIX_COMMANDS.INSTALL);
2748
+ console.log(ansis.green(`\u2714 ${i18n.cometix.installUpdateSuccess}`));
2749
+ } catch (error) {
2750
+ console.warn(ansis.yellow(`\u26A0 ${i18n.cometix.installUpdateFailed}: ${error}`));
2751
+ }
2752
+ if (!hasCCometixLineConfig()) {
2753
+ try {
2754
+ addCCometixLineConfig();
2755
+ console.log(ansis.green(`\u2714 ${i18n.cometix.statusLineConfigured || "Claude Code statusLine configured"}`));
2756
+ } catch (error) {
2757
+ console.warn(ansis.yellow(`\u26A0 ${i18n.cometix.statusLineConfigFailed || "Failed to configure statusLine"}: ${error}`));
2758
+ }
2759
+ } else {
2760
+ console.log(ansis.blue(`\u2139 ${i18n.cometix.statusLineAlreadyConfigured || "Claude Code statusLine already configured"}`));
2735
2761
  }
2762
+ return;
2736
2763
  }
2737
- console.log(ansis.cyan(`\u{1F4E6} ${i18n.ccr.installingCcr}`));
2738
2764
  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;
2765
+ console.log(ansis.blue(`${i18n.cometix.installingCometix}`));
2766
+ await execAsync(COMETIX_COMMANDS.INSTALL);
2767
+ console.log(ansis.green(`\u2714 ${i18n.cometix.cometixInstallSuccess}`));
2768
+ try {
2769
+ addCCometixLineConfig();
2770
+ console.log(ansis.green(`\u2714 ${i18n.cometix.statusLineConfigured || "Claude Code statusLine configured"}`));
2771
+ } catch (configError) {
2772
+ console.warn(ansis.yellow(`\u26A0 ${i18n.cometix.statusLineConfigFailed || "Failed to configure statusLine"}: ${configError}`));
2773
+ console.log(ansis.blue(`\u{1F4A1} ${i18n.cometix.statusLineManualConfig || "Please manually add statusLine configuration to Claude Code settings"}`));
2746
2774
  }
2747
- console.error(ansis.red(`\u2716 ${i18n.ccr.ccrInstallFailed}`));
2775
+ } catch (error) {
2776
+ console.error(ansis.red(`\u2717 ${i18n.cometix.cometixInstallFailed}: ${error}`));
2748
2777
  throw error;
2749
2778
  }
2750
2779
  }
2751
2780
 
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
2781
+ function validateApiKey(apiKey, lang = "zh-CN") {
2782
+ const i18n = getTranslation(lang);
2783
+ if (!apiKey || apiKey.trim() === "") {
2784
+ return {
2785
+ isValid: false,
2786
+ error: i18n.api.apiKeyValidation.empty
2787
+ };
2788
+ }
2789
+ return { isValid: true };
2790
+ }
2791
+ function formatApiKeyDisplay(apiKey) {
2792
+ if (!apiKey || apiKey.length < 12) {
2793
+ return apiKey;
2794
+ }
2795
+ return `${apiKey.substring(0, 8)}...${apiKey.substring(apiKey.length - 4)}`;
2796
+ }
2797
+
2798
+ async function configureApiCompletely(i18n, scriptLang, preselectedAuthType) {
2799
+ let authType = preselectedAuthType;
2800
+ if (!authType) {
2801
+ const { authType: selectedAuthType } = await inquirer.prompt({
2802
+ type: "list",
2803
+ name: "authType",
2804
+ message: i18n.api.configureApi,
2805
+ choices: addNumbersToChoices([
2806
+ {
2807
+ name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
2808
+ value: "auth_token",
2809
+ short: i18n.api.useAuthToken
2810
+ },
2811
+ {
2812
+ name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
2813
+ value: "api_key",
2814
+ short: i18n.api.useApiKey
2815
+ }
2816
+ ])
2759
2817
  });
2760
- clearTimeout(timeoutId);
2761
- if (!response.ok) {
2762
- throw new Error(`HTTP error! status: ${response.status}`);
2818
+ if (!selectedAuthType) {
2819
+ console.log(ansis.yellow(i18n.common.cancelled));
2820
+ return null;
2763
2821
  }
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
- }
2822
+ authType = selectedAuthType;
2823
+ }
2824
+ const { url } = await inquirer.prompt({
2825
+ type: "input",
2826
+ name: "url",
2827
+ message: i18n.api.enterApiUrl,
2828
+ validate: (value) => {
2829
+ if (!value) return i18n.api.urlRequired;
2830
+ try {
2831
+ new URL(value);
2832
+ return true;
2833
+ } catch {
2834
+ return i18n.api.invalidUrl;
2794
2835
  }
2795
2836
  }
2796
- return presets;
2797
- } catch (error) {
2798
- return getFallbackPresets();
2837
+ });
2838
+ if (url === void 0) {
2839
+ console.log(ansis.yellow(i18n.common.cancelled));
2840
+ return null;
2799
2841
  }
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
- }
2842
+ const keyMessage = authType === "auth_token" ? i18n.api.enterAuthToken : i18n.api.enterApiKey;
2843
+ const { key } = await inquirer.prompt({
2844
+ type: "input",
2845
+ name: "key",
2846
+ message: keyMessage,
2847
+ validate: (value) => {
2848
+ if (!value) {
2849
+ return i18n.api.keyRequired;
2857
2850
  }
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"]
2851
+ const validation = validateApiKey(value, scriptLang);
2852
+ if (!validation.isValid) {
2853
+ return validation.error || i18n.api.invalidKeyFormat;
2873
2854
  }
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 }]]
2855
+ return true;
2856
+ }
2857
+ });
2858
+ if (key === void 0) {
2859
+ console.log(ansis.yellow(i18n.common.cancelled));
2860
+ return null;
2861
+ }
2862
+ console.log(ansis.gray(` API Key: ${formatApiKeyDisplay(key)}`));
2863
+ return { url, key, authType };
2864
+ }
2865
+ async function modifyApiConfigPartially(existingConfig, i18n, scriptLang) {
2866
+ let currentConfig = { ...existingConfig };
2867
+ const latestConfig = getExistingApiConfig();
2868
+ if (latestConfig) {
2869
+ currentConfig = latestConfig;
2870
+ }
2871
+ const { item } = await inquirer.prompt({
2872
+ type: "list",
2873
+ name: "item",
2874
+ message: i18n.api.selectModifyItems,
2875
+ choices: addNumbersToChoices([
2876
+ { name: i18n.api.modifyApiUrl, value: "url" },
2877
+ { name: i18n.api.modifyApiKey, value: "key" },
2878
+ { name: i18n.api.modifyAuthType, value: "authType" }
2879
+ ])
2880
+ });
2881
+ if (!item) {
2882
+ console.log(ansis.yellow(i18n.common.cancelled));
2883
+ return;
2884
+ }
2885
+ if (item === "url") {
2886
+ const { url } = await inquirer.prompt({
2887
+ type: "input",
2888
+ name: "url",
2889
+ message: i18n.api.enterNewApiUrl.replace("{url}", currentConfig.url || i18n.common.none),
2890
+ default: currentConfig.url,
2891
+ validate: (value) => {
2892
+ if (!value) return i18n.api.urlRequired;
2893
+ try {
2894
+ new URL(value);
2895
+ return true;
2896
+ } catch {
2897
+ return i18n.api.invalidUrl;
2898
+ }
2884
2899
  }
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"]
2900
+ });
2901
+ if (url === void 0) {
2902
+ console.log(ansis.yellow(i18n.common.cancelled));
2903
+ return;
2904
+ }
2905
+ currentConfig.url = url;
2906
+ const savedConfig = configureApi(currentConfig);
2907
+ if (savedConfig) {
2908
+ console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2909
+ console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${savedConfig.url}`));
2910
+ }
2911
+ } else if (item === "key") {
2912
+ const authType = currentConfig.authType || "auth_token";
2913
+ 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);
2914
+ const { key } = await inquirer.prompt({
2915
+ type: "input",
2916
+ name: "key",
2917
+ message: keyMessage,
2918
+ validate: (value) => {
2919
+ if (!value) {
2920
+ return i18n.api.keyRequired;
2921
+ }
2922
+ const validation = validateApiKey(value, scriptLang);
2923
+ if (!validation.isValid) {
2924
+ return validation.error || i18n.api.invalidKeyFormat;
2925
+ }
2926
+ return true;
2895
2927
  }
2928
+ });
2929
+ if (key === void 0) {
2930
+ console.log(ansis.yellow(i18n.common.cancelled));
2931
+ return;
2896
2932
  }
2897
- ];
2933
+ currentConfig.key = key;
2934
+ const savedConfig = configureApi(currentConfig);
2935
+ if (savedConfig) {
2936
+ console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2937
+ console.log(ansis.gray(` ${i18n.api.apiConfigKey}: ${formatApiKeyDisplay(savedConfig.key)}`));
2938
+ }
2939
+ } else if (item === "authType") {
2940
+ const { authType } = await inquirer.prompt({
2941
+ type: "list",
2942
+ name: "authType",
2943
+ message: i18n.api.selectNewAuthType.replace("{type}", currentConfig.authType || i18n.common.none),
2944
+ choices: addNumbersToChoices([
2945
+ { name: "Auth Token (OAuth)", value: "auth_token" },
2946
+ { name: "API Key", value: "api_key" }
2947
+ ]),
2948
+ default: currentConfig.authType === "api_key" ? 1 : 0
2949
+ });
2950
+ if (authType === void 0) {
2951
+ console.log(ansis.yellow(i18n.common.cancelled));
2952
+ return;
2953
+ }
2954
+ currentConfig.authType = authType;
2955
+ const savedConfig = configureApi(currentConfig);
2956
+ if (savedConfig) {
2957
+ console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2958
+ console.log(ansis.gray(` ${i18n.api.apiConfigAuthType}: ${savedConfig.authType}`));
2959
+ }
2960
+ }
2961
+ }
2962
+ async function updatePromptOnly(configLang, scriptLang, aiOutputLang) {
2963
+ const i18n = getTranslation(scriptLang);
2964
+ const backupDir = backupExistingConfig();
2965
+ if (backupDir) {
2966
+ console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
2967
+ }
2968
+ copyConfigFiles(configLang, true);
2969
+ if (aiOutputLang) {
2970
+ applyAiLanguageDirective(aiOutputLang);
2971
+ }
2972
+ await configureAiPersonality(scriptLang);
2973
+ console.log(ansis.green(`\u2714 ${i18n.configuration.configSuccess} ${CLAUDE_DIR}`));
2974
+ console.log("\n" + ansis.cyan(i18n.common.complete));
2898
2975
  }
2899
2976
 
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 });
2977
+ function handleExitPromptError(error) {
2978
+ if (error instanceof Error && error.name === "ExitPromptError") {
2979
+ const zcfConfig = readZcfConfig();
2980
+ const defaultLang = zcfConfig?.preferredLang || "zh-CN";
2981
+ const i18n = getTranslation(defaultLang);
2982
+ console.log(ansis.cyan(`
2983
+ ${i18n.common.goodbye}
2984
+ `));
2985
+ process.exit(0);
2907
2986
  }
2987
+ return false;
2908
2988
  }
2909
- function backupCcrConfig(scriptLang) {
2910
- const i18n = getTranslation(scriptLang);
2989
+ function handleGeneralError(error, lang) {
2990
+ const zcfConfig = readZcfConfig();
2991
+ const defaultLang = lang || zcfConfig?.preferredLang || "en";
2992
+ const i18n = getTranslation(defaultLang);
2993
+ const errorMsg = i18n.common.error || "Error";
2994
+ console.error(ansis.red(`${errorMsg}:`), error);
2995
+ if (error instanceof Error) {
2996
+ console.error(ansis.gray(`Stack: ${error.stack}`));
2997
+ }
2998
+ process.exit(1);
2999
+ }
3000
+
3001
+ async function isClaudeCodeInstalled() {
3002
+ return await commandExists("claude");
3003
+ }
3004
+ async function installClaudeCode(lang) {
3005
+ const i18n = getTranslation(lang);
3006
+ const installed = await isClaudeCodeInstalled();
3007
+ if (installed) {
3008
+ console.log(ansis.green(`\u2714 ${i18n.installation.alreadyInstalled}`));
3009
+ await updateClaudeCode(lang);
3010
+ return;
3011
+ }
3012
+ if (isTermux()) {
3013
+ console.log(ansis.yellow(`\u2139 ${i18n.installation.termuxDetected}`));
3014
+ const termuxPrefix = getTermuxPrefix();
3015
+ console.log(ansis.gray(i18n.installation.termuxPathInfo.replace("{path}", termuxPrefix)));
3016
+ console.log(ansis.gray(`Node.js: ${termuxPrefix}/bin/node`));
3017
+ console.log(ansis.gray(`npm: ${termuxPrefix}/bin/npm`));
3018
+ }
3019
+ console.log(i18n.installation.installing);
2911
3020
  try {
2912
- if (!existsSync(CCR_CONFIG_FILE)) {
2913
- return null;
3021
+ await exec("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
3022
+ console.log(`\u2714 ${i18n.installation.installSuccess}`);
3023
+ if (isTermux()) {
3024
+ console.log(ansis.gray(`
3025
+ Claude Code installed to: ${getTermuxPrefix()}/bin/claude`));
2914
3026
  }
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
3027
  } 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;
3028
+ console.error(`\u2716 ${i18n.installation.installFailed}`);
3029
+ if (isTermux()) {
3030
+ console.error(ansis.yellow(`
3031
+ ${i18n.installation.termuxInstallHint}
3032
+ `));
3033
+ }
3034
+ throw error;
2930
3035
  }
2931
- return readJsonConfig(CCR_CONFIG_FILE);
2932
3036
  }
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 = {};
3037
+
3038
+ async function selectMcpServices(scriptLang) {
3039
+ const i18n = getTranslation(scriptLang);
3040
+ const choices = MCP_SERVICES.map((service) => ({
3041
+ name: `${service.name[scriptLang]} - ${ansis.gray(service.description[scriptLang])}`,
3042
+ value: service.id,
3043
+ selected: false
3044
+ }));
3045
+ const { services } = await inquirer.prompt({
3046
+ type: "checkbox",
3047
+ name: "services",
3048
+ message: `${i18n.mcp.selectMcpServices}${i18n.common.multiSelectHint}`,
3049
+ choices
3050
+ });
3051
+ if (services === void 0) {
3052
+ console.log(ansis.yellow(i18n.common.cancelled));
3053
+ return void 0;
2944
3054
  }
2945
- settings.env.ANTHROPIC_BASE_URL = `http://${host}:${port}`;
2946
- settings.env.ANTHROPIC_API_KEY = apiKey;
2947
- writeJsonConfig(SETTINGS_FILE, settings);
3055
+ return services;
2948
3056
  }
2949
- async function selectCcrPreset(scriptLang) {
3057
+
3058
+ async function selectAiOutputLanguage(scriptLang, defaultLang) {
2950
3059
  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;
3060
+ console.log(ansis.dim(`
3061
+ ${i18n.language.aiOutputLangHint}
3062
+ `));
3063
+ const aiLangChoices = Object.entries(AI_OUTPUT_LANGUAGES).map(([key, value]) => ({
3064
+ title: value.label,
3065
+ value: key
3066
+ }));
3067
+ const defaultChoice = defaultLang || (scriptLang === "zh-CN" ? "zh-CN" : "en");
3068
+ const { lang } = await inquirer.prompt({
3069
+ type: "list",
3070
+ name: "lang",
3071
+ message: i18n.language.selectAiOutputLang,
3072
+ choices: addNumbersToChoices(aiLangChoices.map((choice) => ({
3073
+ name: choice.title,
3074
+ value: choice.value
3075
+ }))),
3076
+ default: defaultChoice
3077
+ });
3078
+ if (!lang) {
3079
+ console.log(ansis.yellow(i18n.common.cancelled));
3080
+ process.exit(0);
2956
3081
  }
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
2973
- });
2974
- return preset;
2975
- } catch (error) {
2976
- if (error.name === "ExitPromptError") {
3082
+ let aiOutputLang = lang;
3083
+ if (aiOutputLang === "custom") {
3084
+ const { customLang } = await inquirer.prompt({
3085
+ type: "input",
3086
+ name: "customLang",
3087
+ message: i18n.language.enterCustomLanguage,
3088
+ validate: (value) => !!value || i18n.language?.languageRequired || "Language is required"
3089
+ });
3090
+ if (!customLang) {
2977
3091
  console.log(ansis.yellow(i18n.common.cancelled));
2978
- return null;
3092
+ process.exit(0);
2979
3093
  }
2980
- throw error;
3094
+ return customLang;
2981
3095
  }
3096
+ return aiOutputLang;
2982
3097
  }
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;
3098
+ async function selectScriptLanguage(currentLang) {
3099
+ const zcfConfig = readZcfConfig();
3100
+ if (zcfConfig?.preferredLang) {
3101
+ return zcfConfig.preferredLang;
2994
3102
  }
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";
3103
+ if (currentLang) {
3104
+ return currentLang;
3012
3105
  }
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
- }
3106
+ const { lang } = await inquirer.prompt({
3107
+ type: "list",
3108
+ name: "lang",
3109
+ message: "Select ZCF display language / \u9009\u62E9ZCF\u663E\u793A\u8BED\u8A00",
3110
+ choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
3111
+ name: LANG_LABELS[l],
3112
+ value: l
3113
+ })))
3114
+ });
3115
+ if (!lang) {
3116
+ console.log(ansis.yellow("Operation cancelled / \u64CD\u4F5C\u5DF2\u53D6\u6D88"));
3117
+ process.exit(0);
3032
3118
  }
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;
3119
+ const scriptLang = lang;
3120
+ updateZcfConfig({
3121
+ version,
3122
+ preferredLang: scriptLang
3123
+ });
3124
+ return scriptLang;
3055
3125
  }
3056
- async function restartAndCheckCcrStatus(scriptLang) {
3126
+ async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfig) {
3057
3127
  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
- }
3128
+ if (commandLineOption) {
3129
+ return commandLineOption;
3130
+ }
3131
+ if (savedConfig?.aiOutputLang) {
3132
+ console.log(ansis.gray(`\u2714 ${i18n.language.aiOutputLangHint}: ${savedConfig.aiOutputLang}`));
3133
+ return savedConfig.aiOutputLang;
3070
3134
  }
3135
+ return await selectAiOutputLanguage(scriptLang, scriptLang);
3071
3136
  }
3072
- function showConfigurationTips(scriptLang, apiKey) {
3137
+
3138
+ const prompts = {
3139
+ __proto__: null,
3140
+ resolveAiOutputLanguage: resolveAiOutputLanguage,
3141
+ selectAiOutputLanguage: selectAiOutputLanguage,
3142
+ selectScriptLanguage: selectScriptLanguage
3143
+ };
3144
+
3145
+ function getRootDir() {
3146
+ const currentFilePath = fileURLToPath(import.meta.url);
3147
+ const distDir = dirname(dirname(currentFilePath));
3148
+ return dirname(distDir);
3149
+ }
3150
+ async function selectAndInstallWorkflows(configLang, scriptLang, preselectedWorkflows) {
3073
3151
  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"}`));
3152
+ const workflows = getOrderedWorkflows();
3153
+ const choices = workflows.map((workflow) => {
3154
+ const nameKey = workflow.id;
3155
+ const name = i18n.workflow.workflowOption[nameKey] || workflow.id;
3156
+ return {
3157
+ name,
3158
+ value: workflow.id,
3159
+ checked: workflow.defaultSelected
3160
+ };
3161
+ });
3162
+ let selectedWorkflows;
3163
+ if (preselectedWorkflows) {
3164
+ selectedWorkflows = preselectedWorkflows;
3165
+ } else {
3166
+ const response = await inquirer.prompt({
3167
+ type: "checkbox",
3168
+ name: "selectedWorkflows",
3169
+ message: `${i18n.workflow.selectWorkflowType}${i18n.common.multiSelectHint}`,
3170
+ choices
3171
+ });
3172
+ selectedWorkflows = response.selectedWorkflows;
3173
+ }
3174
+ if (!selectedWorkflows || selectedWorkflows.length === 0) {
3175
+ console.log(ansis.yellow(i18n.common.cancelled));
3176
+ return;
3177
+ }
3178
+ await cleanupOldVersionFiles(scriptLang);
3179
+ for (const workflowId of selectedWorkflows) {
3180
+ const config = getWorkflowConfig(workflowId);
3181
+ if (config) {
3182
+ await installWorkflowWithDependencies(config, configLang, scriptLang);
3183
+ }
3082
3184
  }
3083
- console.log("");
3084
3185
  }
3085
- async function setupCcrConfiguration(scriptLang) {
3186
+ async function installWorkflowWithDependencies(config, configLang, scriptLang) {
3187
+ const rootDir = getRootDir();
3086
3188
  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;
3189
+ const result = {
3190
+ workflow: config.id,
3191
+ success: true,
3192
+ installedCommands: [],
3193
+ installedAgents: [],
3194
+ errors: []
3195
+ };
3196
+ const workflowName = i18n.workflow.workflowOption[config.id] || config.id;
3197
+ console.log(ansis.cyan(`
3198
+ \u{1F4E6} ${i18n.workflow.installingWorkflow}: ${workflowName}...`));
3199
+ const commandsDir = join(CLAUDE_DIR, "commands", "zcf");
3200
+ if (!existsSync(commandsDir)) {
3201
+ await mkdir(commandsDir, { recursive: true });
3202
+ }
3203
+ for (const commandFile of config.commands) {
3204
+ const commandSource = join(rootDir, "templates", configLang, "workflow", config.category, "commands", commandFile);
3205
+ const destFileName = commandFile;
3206
+ const commandDest = join(commandsDir, destFileName);
3207
+ if (existsSync(commandSource)) {
3092
3208
  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;
3209
+ await copyFile$1(commandSource, commandDest);
3210
+ result.installedCommands.push(destFileName);
3211
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.installedCommand}: zcf/${destFileName}`));
3100
3212
  } 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;
3213
+ const errorMsg = `${i18n.workflow.failedToInstallCommand} ${commandFile}: ${error}`;
3214
+ result.errors?.push(errorMsg);
3215
+ console.error(ansis.red(` \u2717 ${errorMsg}`));
3216
+ result.success = false;
3111
3217
  }
3112
- backupCcrConfig(scriptLang);
3113
3218
  }
3114
- const preset = await selectCcrPreset(scriptLang);
3115
- if (!preset) {
3116
- return false;
3219
+ }
3220
+ if (config.autoInstallAgents && config.agents.length > 0) {
3221
+ const agentsCategoryDir = join(CLAUDE_DIR, "agents", "zcf", config.category);
3222
+ if (!existsSync(agentsCategoryDir)) {
3223
+ await mkdir(agentsCategoryDir, { recursive: true });
3117
3224
  }
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
3225
+ for (const agent of config.agents) {
3226
+ const agentSource = join(rootDir, "templates", configLang, "workflow", config.category, "agents", agent.filename);
3227
+ const agentDest = join(agentsCategoryDir, agent.filename);
3228
+ if (existsSync(agentSource)) {
3229
+ try {
3230
+ await copyFile$1(agentSource, agentDest);
3231
+ result.installedAgents.push(agent.filename);
3232
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.installedAgent}: zcf/${config.category}/${agent.filename}`));
3233
+ } catch (error) {
3234
+ const errorMsg = `${i18n.workflow.failedToInstallAgent} ${agent.filename}: ${error}`;
3235
+ result.errors?.push(errorMsg);
3236
+ console.error(ansis.red(` \u2717 ${errorMsg}`));
3237
+ if (agent.required) {
3238
+ result.success = false;
3239
+ }
3134
3240
  }
3135
- };
3136
- } else {
3137
- config = await configureCcrWithPreset(preset, scriptLang);
3138
- }
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);
3241
+ }
3149
3242
  }
3150
- return true;
3151
- } catch (error) {
3152
- if (error.name === "ExitPromptError") {
3153
- console.log(ansis.yellow(i18n.common.cancelled));
3154
- return false;
3243
+ }
3244
+ if (result.success) {
3245
+ console.log(ansis.green(`\u2714 ${workflowName} ${i18n.workflow.workflowInstallSuccess}`));
3246
+ if (config.id === "bmadWorkflow") {
3247
+ console.log(ansis.cyan(`
3248
+ ${i18n.workflow.bmadInitPrompt}`));
3155
3249
  }
3156
- console.error(ansis.red(`${i18n.ccr.ccrConfigFailed}:`), error);
3157
- return false;
3250
+ } else {
3251
+ console.log(ansis.red(`\u2717 ${workflowName} ${i18n.workflow.workflowInstallError}`));
3158
3252
  }
3253
+ return result;
3159
3254
  }
3160
- async function configureCcrFeature(scriptLang) {
3255
+ async function cleanupOldVersionFiles(scriptLang) {
3161
3256
  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) || {};
3257
+ console.log(ansis.cyan(`
3258
+ \u{1F9F9} ${i18n.workflow.cleaningOldFiles || "Cleaning up old version files"}...`));
3259
+ const oldCommandFiles = [
3260
+ join(CLAUDE_DIR, "commands", "workflow.md"),
3261
+ join(CLAUDE_DIR, "commands", "feat.md")
3262
+ ];
3263
+ const oldAgentFiles = [
3264
+ join(CLAUDE_DIR, "agents", "planner.md"),
3265
+ join(CLAUDE_DIR, "agents", "ui-ux-designer.md")
3266
+ ];
3267
+ for (const file of oldCommandFiles) {
3268
+ if (existsSync(file)) {
3269
+ try {
3270
+ await rm(file, { force: true });
3271
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
3272
+ } catch (error) {
3273
+ console.error(ansis.yellow(` \u26A0 ${i18n.workflow.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
3274
+ }
3192
3275
  }
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
3276
  }
3200
- }
3201
- function hasCCometixLineConfig() {
3202
- try {
3203
- if (!exists(SETTINGS_FILE)) {
3204
- return false;
3277
+ for (const file of oldAgentFiles) {
3278
+ if (existsSync(file)) {
3279
+ try {
3280
+ await rm(file, { force: true });
3281
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
3282
+ } catch (error) {
3283
+ console.error(ansis.yellow(` \u26A0 ${i18n.workflow.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
3284
+ }
3205
3285
  }
3206
- const settings = readJsonConfig(SETTINGS_FILE);
3207
- return !!settings?.statusLine?.command?.includes("ccline");
3208
- } catch (error) {
3209
- return false;
3210
3286
  }
3211
3287
  }
3212
3288
 
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;
3289
+ function validateSkipPromptOptions(options) {
3290
+ if (options.allLang) {
3291
+ if (options.allLang === "zh-CN" || options.allLang === "en") {
3292
+ options.lang = options.allLang;
3293
+ options.configLang = options.allLang;
3294
+ options.aiOutputLang = options.allLang;
3295
+ } else {
3296
+ options.lang = "en";
3297
+ options.configLang = "en";
3298
+ options.aiOutputLang = options.allLang;
3299
+ }
3220
3300
  }
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}`));
3301
+ if (!options.configAction) {
3302
+ options.configAction = "backup";
3303
+ }
3304
+ if (!options.lang) {
3305
+ options.lang = "en";
3306
+ }
3307
+ if (!options.configLang) {
3308
+ options.configLang = "en";
3309
+ }
3310
+ if (!options.aiOutputLang) {
3311
+ options.aiOutputLang = "en";
3312
+ }
3313
+ if (!options.aiPersonality) {
3314
+ options.aiPersonality = "professional";
3315
+ }
3316
+ if (typeof options.installCometixLine === "string") {
3317
+ options.installCometixLine = options.installCometixLine.toLowerCase() === "true";
3318
+ }
3319
+ if (options.installCometixLine === void 0) {
3320
+ options.installCometixLine = true;
3321
+ }
3322
+ if (options.configAction && !["new", "backup", "merge", "docs-only", "skip"].includes(options.configAction)) {
3323
+ throw new Error(
3324
+ `Invalid configAction value: ${options.configAction}. Must be 'new', 'backup', 'merge', 'docs-only', or 'skip'`
3325
+ );
3326
+ }
3327
+ if (options.apiType && !["auth_token", "api_key", "ccr_proxy", "skip"].includes(options.apiType)) {
3328
+ throw new Error(
3329
+ `Invalid apiType value: ${options.apiType}. Must be 'auth_token', 'api_key', 'ccr_proxy', or 'skip'`
3330
+ );
3331
+ }
3332
+ if (options.apiType === "api_key" && !options.apiKey) {
3333
+ throw new Error('API key is required when apiType is "api_key"');
3334
+ }
3335
+ if (options.apiType === "auth_token" && !options.apiKey) {
3336
+ throw new Error('API key is required when apiType is "auth_token"');
3337
+ }
3338
+ if (typeof options.mcpServices === "string") {
3339
+ if (options.mcpServices === "skip") {
3340
+ options.mcpServices = false;
3341
+ } else if (options.mcpServices === "all") {
3342
+ options.mcpServices = MCP_SERVICES.filter((s) => !s.requiresApiKey).map((s) => s.id);
3343
+ } else {
3344
+ options.mcpServices = options.mcpServices.split(",").map((s) => s.trim());
3233
3345
  }
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}`));
3346
+ }
3347
+ if (Array.isArray(options.mcpServices)) {
3348
+ const validServices = MCP_SERVICES.map((s) => s.id);
3349
+ for (const service of options.mcpServices) {
3350
+ if (!validServices.includes(service)) {
3351
+ throw new Error(`Invalid MCP service: ${service}. Available services: ${validServices.join(", ")}`);
3240
3352
  }
3353
+ }
3354
+ }
3355
+ if (typeof options.workflows === "string") {
3356
+ if (options.workflows === "skip") {
3357
+ options.workflows = false;
3358
+ } else if (options.workflows === "all") {
3359
+ options.workflows = WORKFLOW_CONFIGS.map((w) => w.id);
3241
3360
  } else {
3242
- console.log(ansis.blue(`\u2139 ${i18n.cometix.statusLineAlreadyConfigured || "Claude Code statusLine already configured"}`));
3361
+ options.workflows = options.workflows.split(",").map((s) => s.trim());
3243
3362
  }
3244
- return;
3245
3363
  }
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"}`));
3364
+ if (Array.isArray(options.workflows)) {
3365
+ const validWorkflows = WORKFLOW_CONFIGS.map((w) => w.id);
3366
+ for (const workflow of options.workflows) {
3367
+ if (!validWorkflows.includes(workflow)) {
3368
+ throw new Error(`Invalid workflow: ${workflow}. Available workflows: ${validWorkflows.join(", ")}`);
3369
+ }
3256
3370
  }
3257
- } catch (error) {
3258
- console.error(ansis.red(`\u2717 ${i18n.cometix.cometixInstallFailed}: ${error}`));
3259
- throw error;
3371
+ }
3372
+ if (options.mcpServices === void 0) {
3373
+ options.mcpServices = "all";
3374
+ options.mcpServices = MCP_SERVICES.filter((s) => !s.requiresApiKey).map((s) => s.id);
3375
+ }
3376
+ if (options.workflows === void 0) {
3377
+ options.workflows = "all";
3378
+ options.workflows = WORKFLOW_CONFIGS.map((w) => w.id);
3260
3379
  }
3261
3380
  }
3262
-
3263
3381
  async function init(options = {}) {
3382
+ if (options.skipPrompt) {
3383
+ validateSkipPromptOptions(options);
3384
+ }
3264
3385
  try {
3265
3386
  if (!options.skipBanner) {
3266
3387
  displayBannerWithInfo();
3267
3388
  }
3268
- const scriptLang = await selectScriptLanguage(options.lang);
3389
+ const scriptLang = options.skipPrompt ? options.lang || "en" : await selectScriptLanguage(options.lang);
3269
3390
  const i18n = I18N[scriptLang];
3270
3391
  if (isTermux()) {
3271
3392
  console.log(ansis.yellow(`
@@ -3273,40 +3394,48 @@ async function init(options = {}) {
3273
3394
  console.log(ansis.gray(i18n.installation.termuxEnvironmentInfo));
3274
3395
  }
3275
3396
  let configLang = options.configLang;
3276
- if (!configLang) {
3397
+ if (!configLang && !options.skipPrompt) {
3277
3398
  const { lang } = await inquirer.prompt({
3278
3399
  type: "list",
3279
3400
  name: "lang",
3280
3401
  message: i18n.language.selectConfigLang,
3281
- choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
3282
- name: `${LANG_LABELS[l]} - ${i18n.language.configLangHint[l]}`,
3283
- value: l
3284
- })))
3402
+ choices: addNumbersToChoices(
3403
+ SUPPORTED_LANGS.map((l) => ({
3404
+ name: `${LANG_LABELS[l]} - ${i18n.language.configLangHint[l]}`,
3405
+ value: l
3406
+ }))
3407
+ )
3285
3408
  });
3286
3409
  if (!lang) {
3287
3410
  console.log(ansis.yellow(i18n.common.cancelled));
3288
3411
  process.exit(0);
3289
3412
  }
3290
3413
  configLang = lang;
3414
+ } else if (!configLang && options.skipPrompt) {
3415
+ configLang = "en";
3291
3416
  }
3292
3417
  const zcfConfig = readZcfConfig();
3293
- const aiOutputLang = await resolveAiOutputLanguage(scriptLang, options.aiOutputLang, zcfConfig);
3418
+ const aiOutputLang = options.skipPrompt ? options.aiOutputLang || "en" : await resolveAiOutputLanguage(scriptLang, options.aiOutputLang, zcfConfig);
3294
3419
  const installed = await isClaudeCodeInstalled();
3295
3420
  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) {
3421
+ if (options.skipPrompt) {
3307
3422
  await installClaudeCode(scriptLang);
3308
3423
  } else {
3309
- console.log(ansis.yellow(i18n.common.skip));
3424
+ const { shouldInstall } = await inquirer.prompt({
3425
+ type: "confirm",
3426
+ name: "shouldInstall",
3427
+ message: i18n.installation.installPrompt,
3428
+ default: true
3429
+ });
3430
+ if (shouldInstall === void 0) {
3431
+ console.log(ansis.yellow(i18n.common.cancelled));
3432
+ process.exit(0);
3433
+ }
3434
+ if (shouldInstall) {
3435
+ await installClaudeCode(scriptLang);
3436
+ } else {
3437
+ console.log(ansis.yellow(i18n.common.skip));
3438
+ }
3310
3439
  }
3311
3440
  } else {
3312
3441
  console.log(ansis.green(`\u2714 ${i18n.installation.alreadyInstalled}`));
@@ -3314,77 +3443,57 @@ async function init(options = {}) {
3314
3443
  ensureClaudeDir();
3315
3444
  let action = "new";
3316
3445
  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({
3446
+ if (options.skipPrompt) {
3447
+ action = options.configAction || "backup";
3448
+ if (action === "skip") {
3449
+ console.log(ansis.yellow(i18n.common.skip));
3450
+ return;
3451
+ }
3452
+ } else {
3453
+ const { action: userAction } = await inquirer.prompt({
3353
3454
  type: "list",
3354
3455
  name: "action",
3355
- message: i18n.api.selectApiAction,
3456
+ message: i18n.configuration.existingConfig,
3356
3457
  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" }
3458
+ { name: i18n.configuration.backupAndOverwrite, value: "backup" },
3459
+ { name: i18n.configuration.updateDocsOnly, value: "docs-only" },
3460
+ { name: i18n.configuration.mergeConfig, value: "merge" },
3461
+ { name: i18n.common.skip, value: "skip" }
3362
3462
  ])
3363
3463
  });
3364
- if (!apiAction) {
3464
+ if (!userAction) {
3365
3465
  console.log(ansis.yellow(i18n.common.cancelled));
3366
3466
  process.exit(0);
3367
3467
  }
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") {
3383
- const ccrStatus = await isCcrInstalled();
3384
- if (!ccrStatus.hasCorrectPackage) {
3468
+ action = userAction;
3469
+ if (action === "skip") {
3470
+ console.log(ansis.yellow(i18n.common.skip));
3471
+ return;
3472
+ }
3473
+ }
3474
+ } else if (options.skipPrompt && options.configAction) {
3475
+ action = options.configAction;
3476
+ }
3477
+ let apiConfig = null;
3478
+ const isNewInstall = !existsSync(SETTINGS_FILE);
3479
+ if (action !== "docs-only" && (isNewInstall || ["backup", "merge"].includes(action))) {
3480
+ if (options.skipPrompt) {
3481
+ if (options.apiType === "auth_token" && options.apiKey) {
3482
+ apiConfig = {
3483
+ authType: "auth_token",
3484
+ key: options.apiKey,
3485
+ url: options.apiUrl || "https://api.anthropic.com"
3486
+ };
3487
+ } else if (options.apiType === "api_key" && options.apiKey) {
3488
+ apiConfig = {
3489
+ authType: "api_key",
3490
+ key: options.apiKey,
3491
+ url: options.apiUrl || "https://api.anthropic.com"
3492
+ };
3493
+ } else if (options.apiType === "ccr_proxy") {
3494
+ const ccrInstalled = await isCcrInstalled();
3495
+ if (!ccrInstalled) {
3385
3496
  await installCcr(scriptLang);
3386
- } else {
3387
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
3388
3497
  }
3389
3498
  const ccrConfigured = await setupCcrConfiguration(scriptLang);
3390
3499
  if (ccrConfigured) {
@@ -3393,50 +3502,108 @@ async function init(options = {}) {
3393
3502
  }
3394
3503
  }
3395
3504
  } 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}`));
3505
+ const existingApiConfig = getExistingApiConfig();
3506
+ if (existingApiConfig) {
3507
+ console.log("\n" + ansis.blue(`\u2139 ${i18n.api.existingApiConfig}`));
3508
+ console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${existingApiConfig.url || i18n.common.notConfigured}`));
3509
+ console.log(
3510
+ ansis.gray(
3511
+ ` ${i18n.api.apiConfigKey}: ${existingApiConfig.key ? formatApiKeyDisplay(existingApiConfig.key) : i18n.common.notConfigured}`
3512
+ )
3513
+ );
3514
+ console.log(
3515
+ ansis.gray(` ${i18n.api.apiConfigAuthType}: ${existingApiConfig.authType || i18n.common.notConfigured}
3516
+ `)
3517
+ );
3518
+ const { action: apiAction } = await inquirer.prompt({
3519
+ type: "list",
3520
+ name: "action",
3521
+ message: i18n.api.selectApiAction,
3522
+ choices: addNumbersToChoices([
3523
+ { name: i18n.api.keepExistingConfig, value: "keep" },
3524
+ { name: i18n.api.modifyAllConfig, value: "modify-all" },
3525
+ { name: i18n.api.modifyPartialConfig, value: "modify-partial" },
3526
+ { name: i18n.api.useCcrProxy, value: "use-ccr" },
3527
+ { name: i18n.api.skipApi, value: "skip" }
3528
+ ])
3529
+ });
3530
+ if (!apiAction) {
3531
+ console.log(ansis.yellow(i18n.common.cancelled));
3532
+ process.exit(0);
3432
3533
  }
3433
- const ccrConfigured = await setupCcrConfiguration(scriptLang);
3434
- if (ccrConfigured) {
3435
- console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
3534
+ if (apiAction === "keep" || apiAction === "skip") {
3535
+ apiConfig = null;
3536
+ if (apiAction === "keep") {
3537
+ try {
3538
+ addCompletedOnboarding();
3539
+ } catch (error) {
3540
+ console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
3541
+ }
3542
+ }
3543
+ } else if (apiAction === "modify-partial") {
3544
+ await modifyApiConfigPartially(existingApiConfig, i18n, scriptLang);
3436
3545
  apiConfig = null;
3546
+ } else if (apiAction === "modify-all") {
3547
+ apiConfig = await configureApiCompletely(i18n, scriptLang);
3548
+ } else if (apiAction === "use-ccr") {
3549
+ const ccrStatus = await isCcrInstalled();
3550
+ if (!ccrStatus.hasCorrectPackage) {
3551
+ await installCcr(scriptLang);
3552
+ } else {
3553
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
3554
+ }
3555
+ const ccrConfigured = await setupCcrConfiguration(scriptLang);
3556
+ if (ccrConfigured) {
3557
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
3558
+ apiConfig = null;
3559
+ }
3560
+ }
3561
+ } else {
3562
+ const { apiChoice } = await inquirer.prompt({
3563
+ type: "list",
3564
+ name: "apiChoice",
3565
+ message: i18n.api.configureApi,
3566
+ choices: [
3567
+ {
3568
+ name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
3569
+ value: "auth_token",
3570
+ short: i18n.api.useAuthToken
3571
+ },
3572
+ {
3573
+ name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
3574
+ value: "api_key",
3575
+ short: i18n.api.useApiKey
3576
+ },
3577
+ {
3578
+ name: `${i18n.api.useCcrProxy} - ${ansis.gray(i18n.api.ccrProxyDesc)}`,
3579
+ value: "ccr_proxy",
3580
+ short: i18n.api.useCcrProxy
3581
+ },
3582
+ {
3583
+ name: i18n.api.skipApi,
3584
+ value: "skip"
3585
+ }
3586
+ ]
3587
+ });
3588
+ if (!apiChoice) {
3589
+ console.log(ansis.yellow(i18n.common.cancelled));
3590
+ process.exit(0);
3591
+ }
3592
+ if (apiChoice === "ccr_proxy") {
3593
+ const ccrStatus = await isCcrInstalled();
3594
+ if (!ccrStatus.hasCorrectPackage) {
3595
+ await installCcr(scriptLang);
3596
+ } else {
3597
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
3598
+ }
3599
+ const ccrConfigured = await setupCcrConfiguration(scriptLang);
3600
+ if (ccrConfigured) {
3601
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
3602
+ apiConfig = null;
3603
+ }
3604
+ } else if (apiChoice !== "skip") {
3605
+ apiConfig = await configureApiCompletely(i18n, scriptLang, apiChoice);
3437
3606
  }
3438
- } else if (apiChoice !== "skip") {
3439
- apiConfig = await configureApiCompletely(i18n, scriptLang, apiChoice);
3440
3607
  }
3441
3608
  }
3442
3609
  }
@@ -3448,13 +3615,29 @@ async function init(options = {}) {
3448
3615
  }
3449
3616
  if (action === "docs-only") {
3450
3617
  copyConfigFiles(configLang, true);
3451
- await selectAndInstallWorkflows(configLang, scriptLang);
3618
+ if (options.skipPrompt) {
3619
+ if (options.workflows !== false) {
3620
+ await selectAndInstallWorkflows(configLang, scriptLang, options.workflows);
3621
+ }
3622
+ } else {
3623
+ await selectAndInstallWorkflows(configLang, scriptLang);
3624
+ }
3452
3625
  } else if (["backup", "merge", "new"].includes(action)) {
3453
3626
  copyConfigFiles(configLang, false);
3454
- await selectAndInstallWorkflows(configLang, scriptLang);
3627
+ if (options.skipPrompt) {
3628
+ if (options.workflows !== false) {
3629
+ await selectAndInstallWorkflows(configLang, scriptLang, options.workflows);
3630
+ }
3631
+ } else {
3632
+ await selectAndInstallWorkflows(configLang, scriptLang);
3633
+ }
3455
3634
  }
3456
3635
  applyAiLanguageDirective(aiOutputLang);
3457
- await configureAiPersonality(scriptLang);
3636
+ if (options.skipPrompt) {
3637
+ await configureAiPersonality(scriptLang, options.aiPersonality);
3638
+ } else {
3639
+ await configureAiPersonality(scriptLang);
3640
+ }
3458
3641
  if (apiConfig && action !== "docs-only") {
3459
3642
  const configuredApi = configureApi(apiConfig);
3460
3643
  if (configuredApi) {
@@ -3464,23 +3647,34 @@ async function init(options = {}) {
3464
3647
  }
3465
3648
  }
3466
3649
  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);
3650
+ let shouldConfigureMcp = false;
3651
+ if (options.skipPrompt) {
3652
+ shouldConfigureMcp = options.mcpServices !== false;
3653
+ } else {
3654
+ const { shouldConfigureMcp: userChoice } = await inquirer.prompt({
3655
+ type: "confirm",
3656
+ name: "shouldConfigureMcp",
3657
+ message: i18n.mcp.configureMcp,
3658
+ default: true
3659
+ });
3660
+ if (userChoice === void 0) {
3661
+ console.log(ansis.yellow(i18n.common.cancelled));
3662
+ process.exit(0);
3663
+ }
3664
+ shouldConfigureMcp = userChoice;
3476
3665
  }
3477
3666
  if (shouldConfigureMcp) {
3478
3667
  if (isWindows()) {
3479
3668
  console.log(ansis.blue(`\u2139 ${i18n.installation.windowsDetected}`));
3480
3669
  }
3481
- const selectedServices = await selectMcpServices(scriptLang);
3482
- if (selectedServices === void 0) {
3483
- process.exit(0);
3670
+ let selectedServices;
3671
+ if (options.skipPrompt) {
3672
+ selectedServices = options.mcpServices;
3673
+ } else {
3674
+ selectedServices = await selectMcpServices(scriptLang);
3675
+ if (selectedServices === void 0) {
3676
+ process.exit(0);
3677
+ }
3484
3678
  }
3485
3679
  if (selectedServices.length > 0) {
3486
3680
  const mcpBackupPath = backupMcpConfig();
@@ -3493,20 +3687,21 @@ async function init(options = {}) {
3493
3687
  if (!service) continue;
3494
3688
  let config = service.config;
3495
3689
  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]}`));
3690
+ if (options.skipPrompt) {
3691
+ console.log(ansis.yellow(`${i18n.common.skip}: ${service.name[scriptLang]} (requires API key)`));
3504
3692
  continue;
3505
- }
3506
- if (apiKey) {
3507
- config = buildMcpServerConfig(service.config, apiKey, service.apiKeyPlaceholder, service.apiKeyEnvVar);
3508
3693
  } else {
3509
- continue;
3694
+ const response = await inquirer.prompt({
3695
+ type: "input",
3696
+ name: "apiKey",
3697
+ message: service.apiKeyPrompt[scriptLang],
3698
+ validate: (value) => !!value || i18n.api.keyRequired
3699
+ });
3700
+ if (!response.apiKey) {
3701
+ console.log(ansis.yellow(`${i18n.common.skip}: ${service.name[scriptLang]}`));
3702
+ continue;
3703
+ }
3704
+ config = buildMcpServerConfig(service.config, response.apiKey, service.apiKeyPlaceholder, service.apiKeyEnvVar);
3510
3705
  }
3511
3706
  }
3512
3707
  newServers[service.id] = config;
@@ -3525,15 +3720,21 @@ async function init(options = {}) {
3525
3720
  }
3526
3721
  const cometixInstalled = await isCometixLineInstalled();
3527
3722
  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);
3723
+ let shouldInstallCometix = false;
3724
+ if (options.skipPrompt) {
3725
+ shouldInstallCometix = options.installCometixLine !== false;
3726
+ } else {
3727
+ const { shouldInstallCometix: userChoice } = await inquirer.prompt({
3728
+ type: "confirm",
3729
+ name: "shouldInstallCometix",
3730
+ message: i18n.cometix.installCometixPrompt,
3731
+ default: true
3732
+ });
3733
+ if (userChoice === void 0) {
3734
+ console.log(ansis.yellow(i18n.common.cancelled));
3735
+ process.exit(0);
3736
+ }
3737
+ shouldInstallCometix = userChoice;
3537
3738
  }
3538
3739
  if (shouldInstallCometix) {
3539
3740
  await installCometixLine(scriptLang);