zcf 2.7.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,82 +1,126 @@
1
1
  #!/usr/bin/env node
2
2
  import cac from 'cac';
3
3
  import ansis from 'ansis';
4
- import { I as I18N, Z as ZCF_CONFIG_FILE, h as SUPPORTED_LANGS, j as LANG_LABELS, H as updateZcfConfig, o as openSettingsJson, b as importRecommendedPermissions, a as importRecommendedEnv, J as readZcfConfig, K as resolveAiOutputLanguage, w as applyAiLanguageDirective, N as configureAiPersonality, u as updateDefaultModel, O as isWindows, y as readMcpConfig, F as fixWindowsMcpConfig, z as writeMcpConfig, P as selectMcpServices, B as backupMcpConfig, M as MCP_SERVICES, E as buildMcpServerConfig, D as mergeMcpServers, v as getExistingApiConfig, Q as formatApiKeyDisplay, R as modifyApiConfigPartially, T as validateApiKey, r as configureApi, U as readZcfConfigAsync, V as displayBanner, W as selectScriptLanguage, X as updatePromptOnly, Y as selectAndInstallWorkflows, _ as version, $ as handleExitPromptError, a0 as handleGeneralError, a1 as displayBannerWithInfo, i as init } from './shared/zcf.CZ3RjfyP.mjs';
4
+ import { H as getTranslation, Z as ZCF_CONFIG_FILE, h as SUPPORTED_LANGS, J as addNumbersToChoices, j as LANG_LABELS, K as updateZcfConfig, o as openSettingsJson, b as importRecommendedPermissions, a as importRecommendedEnv, N as readZcfConfig, O as resolveAiOutputLanguage, w as applyAiLanguageDirective, P as configureAiPersonality, u as updateDefaultModel, Q as isWindows, y as readMcpConfig, F as fixWindowsMcpConfig, z as writeMcpConfig, R as selectMcpServices, B as backupMcpConfig, M as MCP_SERVICES, E as buildMcpServerConfig, D as mergeMcpServers, v as getExistingApiConfig, T as formatApiKeyDisplay, G as addCompletedOnboarding, U as modifyApiConfigPartially, V as isCcrInstalled, W as installCcr, X as setupCcrConfiguration, Y as validateApiKey, r as configureApi, _ as readZcfConfigAsync, I as I18N, $ as readCcrConfig, a0 as configureCcrFeature, a1 as handleExitPromptError, a2 as handleGeneralError, a3 as displayBanner, a4 as selectScriptLanguage, a5 as updatePromptOnly, a6 as selectAndInstallWorkflows, a7 as version, a8 as displayBannerWithInfo, i as init } from './shared/zcf.CLtDS8Pu.mjs';
5
5
  import inquirer from 'inquirer';
6
6
  import { existsSync, unlinkSync } from 'node:fs';
7
7
  import { x } from 'tinyexec';
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { exec } from 'child_process';
11
+ import { promisify } from 'util';
8
12
  import 'pathe';
9
13
  import 'dayjs';
10
14
  import 'node:url';
11
15
  import 'node:fs/promises';
12
- import 'node:os';
16
+ import 'node:child_process';
17
+ import 'node:util';
13
18
 
14
19
  function handleCancellation(scriptLang) {
15
- console.log(ansis.yellow(I18N[scriptLang].cancelled));
20
+ const i18n = getTranslation(scriptLang);
21
+ console.log(ansis.yellow(i18n.common.cancelled));
16
22
  }
17
23
  async function configureApiFeature(scriptLang) {
18
- const i18n = I18N[scriptLang];
24
+ const i18n = getTranslation(scriptLang);
19
25
  const existingApiConfig = getExistingApiConfig();
20
26
  if (existingApiConfig) {
21
- console.log("\n" + ansis.blue(`\u2139 ${i18n.existingApiConfig}`));
22
- console.log(ansis.gray(` ${i18n.apiConfigUrl}: ${existingApiConfig.url || i18n.notConfigured}`));
23
- console.log(ansis.gray(` ${i18n.apiConfigKey}: ${existingApiConfig.key ? formatApiKeyDisplay(existingApiConfig.key) : i18n.notConfigured}`));
24
- console.log(ansis.gray(` ${i18n.apiConfigAuthType}: ${existingApiConfig.authType || i18n.notConfigured}
27
+ console.log("\n" + ansis.blue(`\u2139 ${i18n.api.existingApiConfig}`));
28
+ console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${existingApiConfig.url || i18n.common.notConfigured}`));
29
+ console.log(ansis.gray(` ${i18n.api.apiConfigKey}: ${existingApiConfig.key ? formatApiKeyDisplay(existingApiConfig.key) : i18n.common.notConfigured}`));
30
+ console.log(ansis.gray(` ${i18n.api.apiConfigAuthType}: ${existingApiConfig.authType || i18n.common.notConfigured}
25
31
  `));
26
32
  const { action } = await inquirer.prompt({
27
33
  type: "list",
28
34
  name: "action",
29
- message: i18n.selectApiAction,
30
- choices: [
31
- { name: i18n.keepExistingConfig, value: "keep" },
32
- { name: i18n.modifyAllConfig, value: "modify-all" },
33
- { name: i18n.modifyPartialConfig, value: "modify-partial" }
34
- ]
35
+ message: i18n.api.selectApiAction,
36
+ choices: addNumbersToChoices([
37
+ { name: i18n.api.keepExistingConfig, value: "keep" },
38
+ { name: i18n.api.modifyAllConfig, value: "modify-all" },
39
+ { name: i18n.api.modifyPartialConfig, value: "modify-partial" },
40
+ { name: i18n.api.useCcrProxy, value: "use-ccr" }
41
+ ])
35
42
  });
36
43
  if (!action) {
37
44
  handleCancellation(scriptLang);
38
45
  return;
39
46
  }
40
47
  if (action === "keep") {
41
- console.log(ansis.green(`\u2714 ${i18n.keepExistingConfig}`));
48
+ console.log(ansis.green(`\u2714 ${i18n.api.keepExistingConfig}`));
49
+ try {
50
+ addCompletedOnboarding();
51
+ } catch (error) {
52
+ console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
53
+ }
42
54
  return;
43
55
  } else if (action === "modify-partial") {
44
56
  await modifyApiConfigPartially(existingApiConfig, i18n, scriptLang);
45
57
  return;
58
+ } else if (action === "use-ccr") {
59
+ const ccrInstalled = await isCcrInstalled();
60
+ if (!ccrInstalled) {
61
+ console.log(ansis.yellow(`${i18n.ccr.installingCcr}`));
62
+ await installCcr(scriptLang);
63
+ } else {
64
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
65
+ }
66
+ const ccrConfigured = await setupCcrConfiguration(scriptLang);
67
+ if (ccrConfigured) {
68
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
69
+ }
70
+ return;
46
71
  }
47
72
  }
48
73
  const { apiChoice } = await inquirer.prompt({
49
74
  type: "list",
50
75
  name: "apiChoice",
51
- message: i18n.configureApi,
52
- choices: [
76
+ message: i18n.api.configureApi,
77
+ choices: addNumbersToChoices([
53
78
  {
54
- name: `${i18n.useAuthToken} - ${ansis.gray(i18n.authTokenDesc)}`,
79
+ name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
55
80
  value: "auth_token",
56
- short: i18n.useAuthToken
81
+ short: i18n.api.useAuthToken
57
82
  },
58
83
  {
59
- name: `${i18n.useApiKey} - ${ansis.gray(i18n.apiKeyDesc)}`,
84
+ name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
60
85
  value: "api_key",
61
- short: i18n.useApiKey
86
+ short: i18n.api.useApiKey
62
87
  },
63
- { name: i18n.skipApi, value: "skip" }
64
- ]
88
+ {
89
+ name: `${i18n.api.useCcrProxy} - ${ansis.gray(i18n.api.ccrProxyDesc)}`,
90
+ value: "ccr_proxy",
91
+ short: i18n.api.useCcrProxy
92
+ },
93
+ { name: i18n.api.skipApi, value: "skip" }
94
+ ])
65
95
  });
66
96
  if (!apiChoice || apiChoice === "skip") {
67
97
  return;
68
98
  }
99
+ if (apiChoice === "ccr_proxy") {
100
+ const ccrInstalled = await isCcrInstalled();
101
+ if (!ccrInstalled) {
102
+ console.log(ansis.yellow(`${i18n.ccr.installingCcr}`));
103
+ await installCcr(scriptLang);
104
+ } else {
105
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
106
+ }
107
+ const ccrConfigured = await setupCcrConfiguration(scriptLang);
108
+ if (ccrConfigured) {
109
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
110
+ }
111
+ return;
112
+ }
69
113
  const { url } = await inquirer.prompt({
70
114
  type: "input",
71
115
  name: "url",
72
- message: i18n.enterApiUrl,
116
+ message: i18n.api.enterApiUrl,
73
117
  validate: (value) => {
74
- if (!value) return i18n.urlRequired;
118
+ if (!value) return i18n.api.urlRequired;
75
119
  try {
76
120
  new URL(value);
77
121
  return true;
78
122
  } catch {
79
- return i18n.invalidUrl;
123
+ return i18n.api.invalidUrl;
80
124
  }
81
125
  }
82
126
  });
@@ -84,18 +128,18 @@ async function configureApiFeature(scriptLang) {
84
128
  handleCancellation(scriptLang);
85
129
  return;
86
130
  }
87
- const keyMessage = apiChoice === "auth_token" ? i18n.enterAuthToken : i18n.enterApiKey;
131
+ const keyMessage = apiChoice === "auth_token" ? i18n.api.enterAuthToken : i18n.api.enterApiKey;
88
132
  const { key } = await inquirer.prompt({
89
133
  type: "input",
90
134
  name: "key",
91
135
  message: keyMessage,
92
136
  validate: (value) => {
93
137
  if (!value) {
94
- return i18n.keyRequired;
138
+ return i18n.api.keyRequired;
95
139
  }
96
140
  const validation = validateApiKey(value, scriptLang);
97
141
  if (!validation.isValid) {
98
- return validation.error || i18n.invalidKeyFormat;
142
+ return validation.error || i18n.api.invalidKeyFormat;
99
143
  }
100
144
  return true;
101
145
  }
@@ -107,25 +151,25 @@ async function configureApiFeature(scriptLang) {
107
151
  const apiConfig = { url, key, authType: apiChoice };
108
152
  const configuredApi = configureApi(apiConfig);
109
153
  if (configuredApi) {
110
- console.log(ansis.green(`\u2714 ${i18n.apiConfigSuccess}`));
154
+ console.log(ansis.green(`\u2714 ${i18n.api.apiConfigSuccess}`));
111
155
  console.log(ansis.gray(` URL: ${configuredApi.url}`));
112
156
  console.log(ansis.gray(` Key: ${formatApiKeyDisplay(configuredApi.key)}`));
113
157
  }
114
158
  }
115
159
  async function configureMcpFeature(scriptLang) {
116
- const i18n = I18N[scriptLang];
160
+ const i18n = getTranslation(scriptLang);
117
161
  if (isWindows()) {
118
162
  const { fixWindows } = await inquirer.prompt({
119
163
  type: "confirm",
120
164
  name: "fixWindows",
121
- message: i18n.fixWindowsMcp || "Fix Windows MCP configuration?",
165
+ message: i18n.configuration.fixWindowsMcp || "Fix Windows MCP configuration?",
122
166
  default: true
123
167
  });
124
168
  if (fixWindows) {
125
169
  const existingConfig = readMcpConfig() || { mcpServers: {} };
126
170
  const fixedConfig = fixWindowsMcpConfig(existingConfig);
127
171
  writeMcpConfig(fixedConfig);
128
- console.log(ansis.green(`\u2714 ${i18n.windowsMcpFixed || "Windows MCP configuration fixed"}`));
172
+ console.log(ansis.green(`\u2714 Windows MCP configuration fixed`));
129
173
  }
130
174
  }
131
175
  const selectedServices = await selectMcpServices(scriptLang);
@@ -135,7 +179,7 @@ async function configureMcpFeature(scriptLang) {
135
179
  if (selectedServices.length > 0) {
136
180
  const mcpBackupPath = backupMcpConfig();
137
181
  if (mcpBackupPath) {
138
- console.log(ansis.gray(`\u2714 ${i18n.mcpBackupSuccess}: ${mcpBackupPath}`));
182
+ console.log(ansis.gray(`\u2714 ${i18n.mcp.mcpBackupSuccess}: ${mcpBackupPath}`));
139
183
  }
140
184
  const newServers = {};
141
185
  for (const serviceId of selectedServices) {
@@ -147,7 +191,7 @@ async function configureMcpFeature(scriptLang) {
147
191
  type: "input",
148
192
  name: "apiKey",
149
193
  message: service.apiKeyPrompt[scriptLang],
150
- validate: (value) => !!value || i18n.keyRequired
194
+ validate: (value) => !!value || i18n.api.keyRequired
151
195
  });
152
196
  if (apiKey) {
153
197
  config = buildMcpServerConfig(service.config, apiKey, service.apiKeyPlaceholder, service.apiKeyEnvVar);
@@ -161,43 +205,42 @@ async function configureMcpFeature(scriptLang) {
161
205
  let mergedConfig = mergeMcpServers(existingConfig, newServers);
162
206
  mergedConfig = fixWindowsMcpConfig(mergedConfig);
163
207
  writeMcpConfig(mergedConfig);
164
- console.log(ansis.green(`\u2714 ${i18n.mcpConfigSuccess}`));
208
+ console.log(ansis.green(`\u2714 ${i18n.mcp.mcpConfigSuccess}`));
165
209
  }
166
210
  }
167
211
  async function configureDefaultModelFeature(scriptLang) {
168
- const i18n = I18N[scriptLang];
169
212
  const { model } = await inquirer.prompt({
170
213
  type: "list",
171
214
  name: "model",
172
- message: i18n.selectDefaultModel || "Select default model",
173
- choices: [
215
+ message: "Select default model",
216
+ choices: addNumbersToChoices([
174
217
  { name: "Opus", value: "opus" },
175
218
  { name: "Sonnet", value: "sonnet" }
176
- ]
219
+ ])
177
220
  });
178
221
  if (!model) {
179
222
  handleCancellation(scriptLang);
180
223
  return;
181
224
  }
182
225
  updateDefaultModel(model);
183
- console.log(ansis.green(`\u2714 ${i18n.modelConfigSuccess || "Default model configured"}`));
226
+ console.log(ansis.green(`\u2714 Default model configured`));
184
227
  }
185
228
  async function configureAiMemoryFeature(scriptLang) {
186
- const i18n = I18N[scriptLang];
229
+ const i18n = getTranslation(scriptLang);
187
230
  const { option } = await inquirer.prompt({
188
231
  type: "list",
189
232
  name: "option",
190
- message: i18n.selectMemoryOption || "Select configuration option",
191
- choices: [
233
+ message: "Select configuration option",
234
+ choices: addNumbersToChoices([
192
235
  {
193
- name: i18n.configureAiLanguage || "Configure AI output language",
236
+ name: i18n.configuration.configureAiLanguage || "Configure AI output language",
194
237
  value: "language"
195
238
  },
196
239
  {
197
- name: i18n.configureAiPersonality || "Configure AI personality",
240
+ name: i18n.configuration.configureAiPersonality || "Configure AI personality",
198
241
  value: "personality"
199
242
  }
200
- ]
243
+ ])
201
244
  });
202
245
  if (!option) {
203
246
  return;
@@ -207,17 +250,17 @@ async function configureAiMemoryFeature(scriptLang) {
207
250
  const aiOutputLang = await resolveAiOutputLanguage(scriptLang, void 0, zcfConfig);
208
251
  applyAiLanguageDirective(aiOutputLang);
209
252
  updateZcfConfig({ aiOutputLang });
210
- console.log(ansis.green(`\u2714 ${i18n.aiLanguageConfigured || "AI output language configured"}`));
253
+ console.log(ansis.green(`\u2714 ${i18n.configuration.aiLanguageConfigured || "AI output language configured"}`));
211
254
  } else {
212
255
  await configureAiPersonality(scriptLang);
213
256
  }
214
257
  }
215
258
  async function clearZcfCacheFeature(scriptLang) {
216
- const i18n = I18N[scriptLang];
259
+ const i18n = getTranslation(scriptLang);
217
260
  const { confirm } = await inquirer.prompt({
218
261
  type: "confirm",
219
262
  name: "confirm",
220
- message: i18n.confirmClearCache || "Clear all ZCF preferences cache?",
263
+ message: i18n.configuration.confirmClearCache || "Clear all ZCF preferences cache?",
221
264
  default: false
222
265
  });
223
266
  if (!confirm) {
@@ -226,50 +269,51 @@ async function clearZcfCacheFeature(scriptLang) {
226
269
  }
227
270
  if (existsSync(ZCF_CONFIG_FILE)) {
228
271
  unlinkSync(ZCF_CONFIG_FILE);
229
- console.log(ansis.green(`\u2714 ${i18n.cacheCleared || "ZCF cache cleared"}`));
272
+ console.log(ansis.green(`\u2714 ${i18n.configuration.cacheCleared || "ZCF cache cleared"}`));
230
273
  } else {
231
- console.log(ansis.yellow(i18n.noCacheFound || "No cache found"));
274
+ console.log(ansis.yellow("No cache found"));
232
275
  }
233
276
  }
234
277
  async function changeScriptLanguageFeature(currentLang) {
235
- const i18n = I18N[currentLang];
278
+ const i18n = getTranslation(currentLang);
236
279
  const { lang } = await inquirer.prompt({
237
280
  type: "list",
238
281
  name: "lang",
239
- message: i18n.selectScriptLang,
240
- choices: SUPPORTED_LANGS.map((l) => ({
282
+ message: i18n.language.selectScriptLang,
283
+ choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
241
284
  name: LANG_LABELS[l],
242
285
  value: l
243
- })),
286
+ }))),
244
287
  default: SUPPORTED_LANGS.indexOf(currentLang)
245
288
  });
246
289
  if (!lang) {
247
290
  return currentLang;
248
291
  }
249
292
  updateZcfConfig({ preferredLang: lang });
250
- console.log(ansis.green(`\u2714 ${I18N[lang].languageChanged || "Language changed"}`));
293
+ const newI18n = getTranslation(lang);
294
+ console.log(ansis.green(`\u2714 ${newI18n.language.languageChanged || "Language changed"}`));
251
295
  return lang;
252
296
  }
253
297
  async function configureEnvPermissionFeature(scriptLang) {
254
- const i18n = I18N[scriptLang];
298
+ const i18n = getTranslation(scriptLang);
255
299
  const { choice } = await inquirer.prompt({
256
300
  type: "list",
257
301
  name: "choice",
258
- message: i18n.selectEnvPermissionOption,
259
- choices: [
302
+ message: i18n.configuration?.selectEnvPermissionOption || "Select option",
303
+ choices: addNumbersToChoices([
260
304
  {
261
- name: `${i18n.importRecommendedEnv} ${ansis.gray("- " + i18n.importRecommendedEnvDesc)}`,
305
+ name: `${i18n.configuration?.importRecommendedEnv || "Import environment"} ${ansis.gray("- " + (i18n.configuration?.importRecommendedEnvDesc || "Import env settings"))}`,
262
306
  value: "env"
263
307
  },
264
308
  {
265
- name: `${i18n.importRecommendedPermissions} ${ansis.gray("- " + i18n.importRecommendedPermissionsDesc)}`,
309
+ name: `${i18n.configuration?.importRecommendedPermissions || "Import permissions"} ${ansis.gray("- " + (i18n.configuration?.importRecommendedPermissionsDesc || "Import permission settings"))}`,
266
310
  value: "permissions"
267
311
  },
268
312
  {
269
- name: `${i18n.openSettingsJson} ${ansis.gray("- " + i18n.openSettingsJsonDesc)}`,
313
+ name: `${i18n.configuration?.openSettingsJson || "Open settings"} ${ansis.gray("- " + (i18n.configuration?.openSettingsJsonDesc || "View settings file"))}`,
270
314
  value: "open"
271
315
  }
272
- ]
316
+ ])
273
317
  });
274
318
  if (!choice) {
275
319
  handleCancellation(scriptLang);
@@ -279,32 +323,37 @@ async function configureEnvPermissionFeature(scriptLang) {
279
323
  switch (choice) {
280
324
  case "env":
281
325
  await importRecommendedEnv();
282
- console.log(ansis.green(`\u2705 ${i18n.envImportSuccess}`));
326
+ console.log(ansis.green(`\u2705 ${i18n.configuration.envImportSuccess}`));
283
327
  break;
284
328
  case "permissions":
285
329
  await importRecommendedPermissions();
286
- console.log(ansis.green(`\u2705 ${i18n.permissionsImportSuccess}`));
330
+ console.log(ansis.green(`\u2705 ${i18n.configuration?.permissionsImportSuccess || "Permissions imported"}`));
287
331
  break;
288
332
  case "open":
289
- console.log(ansis.cyan(i18n.openingSettingsJson));
333
+ console.log(ansis.cyan(i18n.configuration?.openingSettingsJson || "Opening settings.json..."));
290
334
  await openSettingsJson();
291
335
  break;
292
336
  }
293
337
  } catch (error) {
294
- console.error(ansis.red(`${i18n.error}: ${error.message}`));
338
+ console.error(ansis.red(`${i18n.common.error}: ${error.message}`));
295
339
  }
296
340
  }
297
341
 
298
342
  async function executeCcusage(args = []) {
299
343
  try {
300
- const zcfConfig = await readZcfConfigAsync();
301
- const rawLang = zcfConfig?.preferredLang || "en";
302
- const lang = getValidLanguage(rawLang);
344
+ let lang = "en";
345
+ try {
346
+ const zcfConfig = await readZcfConfigAsync();
347
+ const rawLang = zcfConfig?.preferredLang || "en";
348
+ lang = getValidLanguage(rawLang);
349
+ } catch {
350
+ lang = "en";
351
+ }
303
352
  const i18n = I18N[lang];
304
353
  const command = "npx";
305
- const commandArgs = ["ccusage@latest", ...args];
306
- console.log(ansis.cyan(i18n.runningCcusage));
307
- console.log(ansis.gray(`$ npx ccusage@latest ${args.join(" ")}`));
354
+ const commandArgs = ["ccusage@latest", ...args || []];
355
+ console.log(ansis.cyan(i18n.tools.runningCcusage));
356
+ console.log(ansis.gray(`$ npx ccusage@latest ${(args || []).join(" ")}`));
308
357
  console.log("");
309
358
  await x(command, commandArgs, {
310
359
  nodeOptions: {
@@ -312,14 +361,19 @@ async function executeCcusage(args = []) {
312
361
  }
313
362
  });
314
363
  } catch (error) {
315
- const zcfConfig = await readZcfConfigAsync();
316
- const rawLang = zcfConfig?.preferredLang || "en";
317
- const lang = getValidLanguage(rawLang);
364
+ let lang = "en";
365
+ try {
366
+ const zcfConfig = await readZcfConfigAsync();
367
+ const rawLang = zcfConfig?.preferredLang || "en";
368
+ lang = getValidLanguage(rawLang);
369
+ } catch {
370
+ lang = "en";
371
+ }
318
372
  const i18n = I18N[lang];
319
- console.error(ansis.red(i18n.ccusageFailed));
320
- console.error(ansis.yellow(i18n.checkNetworkConnection));
373
+ console.error(ansis.red(i18n.tools.ccusageFailed));
374
+ console.error(ansis.yellow(i18n.tools.checkNetworkConnection));
321
375
  if (process.env.DEBUG) {
322
- console.error(ansis.gray(i18n.errorDetails), error);
376
+ console.error(ansis.gray(i18n.tools.errorDetails), error);
323
377
  }
324
378
  if (process.env.NODE_ENV !== "test") {
325
379
  process.exit(1);
@@ -328,29 +382,228 @@ async function executeCcusage(args = []) {
328
382
  }
329
383
  }
330
384
 
385
+ const execAsync = promisify(exec);
386
+ async function runCcrUi(scriptLang, apiKey) {
387
+ const i18n = I18N[scriptLang];
388
+ console.log(ansis.cyan(`
389
+ \u{1F5A5}\uFE0F ${i18n.ccr.startingCcrUi}`));
390
+ if (apiKey) {
391
+ console.log(ansis.bold.green(`
392
+ \u{1F511} ${i18n.ccr.ccrUiApiKey || "CCR UI API Key"}: ${apiKey}`));
393
+ console.log(ansis.gray(` ${i18n.ccr.ccrUiApiKeyHint || "Use this API key to login to CCR UI"}
394
+ `));
395
+ }
396
+ try {
397
+ const { stdout, stderr } = await execAsync("ccr ui");
398
+ if (stdout) console.log(stdout);
399
+ if (stderr) console.error(ansis.yellow(stderr));
400
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrUiStarted}`));
401
+ } catch (error) {
402
+ console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
403
+ throw error;
404
+ }
405
+ }
406
+ async function runCcrStatus(scriptLang) {
407
+ const i18n = I18N[scriptLang];
408
+ console.log(ansis.cyan(`
409
+ \u{1F4CA} ${i18n.ccr.checkingCcrStatus}`));
410
+ try {
411
+ const { stdout, stderr } = await execAsync("ccr status");
412
+ if (stdout) {
413
+ console.log("\n" + ansis.bold(i18n.ccr.ccrStatusTitle));
414
+ console.log(stdout);
415
+ }
416
+ if (stderr) console.error(ansis.yellow(stderr));
417
+ } catch (error) {
418
+ console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
419
+ throw error;
420
+ }
421
+ }
422
+ async function runCcrRestart(scriptLang) {
423
+ const i18n = I18N[scriptLang];
424
+ console.log(ansis.cyan(`
425
+ \u{1F504} ${i18n.ccr.restartingCcr}`));
426
+ try {
427
+ const { stdout, stderr } = await execAsync("ccr restart");
428
+ if (stdout) console.log(stdout);
429
+ if (stderr) console.error(ansis.yellow(stderr));
430
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrRestarted}`));
431
+ } catch (error) {
432
+ console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
433
+ throw error;
434
+ }
435
+ }
436
+ async function runCcrStart(scriptLang) {
437
+ const i18n = I18N[scriptLang];
438
+ console.log(ansis.cyan(`
439
+ \u25B6\uFE0F ${i18n.ccr.startingCcr}`));
440
+ try {
441
+ const { stdout, stderr } = await execAsync("ccr start");
442
+ if (stdout) console.log(stdout);
443
+ if (stderr) console.error(ansis.yellow(stderr));
444
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrStarted}`));
445
+ } catch (error) {
446
+ console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
447
+ throw error;
448
+ }
449
+ }
450
+ async function runCcrStop(scriptLang) {
451
+ const i18n = I18N[scriptLang];
452
+ console.log(ansis.cyan(`
453
+ \u23F9\uFE0F ${i18n.ccr.stoppingCcr}`));
454
+ try {
455
+ const { stdout, stderr } = await execAsync("ccr stop");
456
+ if (stdout) console.log(stdout);
457
+ if (stderr) console.error(ansis.yellow(stderr));
458
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrStopped}`));
459
+ } catch (error) {
460
+ console.error(ansis.red(`\u2716 ${i18n.ccr.ccrCommandFailed}: ${error instanceof Error ? error.message : String(error)}`));
461
+ throw error;
462
+ }
463
+ }
464
+
465
+ function isCcrConfigured() {
466
+ const CCR_CONFIG_FILE = join(homedir(), ".claude-code-router", "config.json");
467
+ if (!existsSync(CCR_CONFIG_FILE)) {
468
+ return false;
469
+ }
470
+ const config = readCcrConfig();
471
+ return config !== null && config.Providers && config.Providers.length > 0;
472
+ }
473
+ async function showCcrMenu(scriptLang) {
474
+ try {
475
+ const i18n = I18N[scriptLang];
476
+ console.log("\n" + ansis.cyan("\u2550".repeat(50)));
477
+ console.log(ansis.bold.cyan(` ${i18n.ccr.ccrMenuTitle}`));
478
+ console.log(ansis.cyan("\u2550".repeat(50)) + "\n");
479
+ console.log(` ${ansis.cyan("1.")} ${i18n.ccr.ccrMenuOptions.initCcr} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.initCcr)}`);
480
+ console.log(` ${ansis.cyan("2.")} ${i18n.ccr.ccrMenuOptions.startUi} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.startUi)}`);
481
+ console.log(` ${ansis.cyan("3.")} ${i18n.ccr.ccrMenuOptions.checkStatus} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.checkStatus)}`);
482
+ console.log(` ${ansis.cyan("4.")} ${i18n.ccr.ccrMenuOptions.restart} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.restart)}`);
483
+ console.log(` ${ansis.cyan("5.")} ${i18n.ccr.ccrMenuOptions.start} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.start)}`);
484
+ console.log(` ${ansis.cyan("6.")} ${i18n.ccr.ccrMenuOptions.stop} ${ansis.gray("- " + i18n.ccr.ccrMenuDescriptions.stop)}`);
485
+ console.log(` ${ansis.yellow("0.")} ${i18n.ccr.ccrMenuOptions.back}`);
486
+ console.log("");
487
+ const { choice } = await inquirer.prompt({
488
+ type: "input",
489
+ name: "choice",
490
+ message: i18n.common.enterChoice,
491
+ validate: (value) => {
492
+ const valid = ["1", "2", "3", "4", "5", "6", "0"];
493
+ return valid.includes(value) || i18n.common.invalidChoice;
494
+ }
495
+ });
496
+ switch (choice) {
497
+ case "1":
498
+ const ccrInstalled = await isCcrInstalled();
499
+ if (!ccrInstalled) {
500
+ console.log(ansis.yellow(`${i18n.ccr.installingCcr}`));
501
+ await installCcr(scriptLang);
502
+ } else {
503
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
504
+ }
505
+ await configureCcrFeature(scriptLang);
506
+ console.log(ansis.green(`
507
+ \u2714 ${i18n.ccr.ccrSetupComplete}`));
508
+ break;
509
+ case "2":
510
+ if (!isCcrConfigured()) {
511
+ console.log(ansis.yellow(`
512
+ \u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
513
+ console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
514
+ `));
515
+ } else {
516
+ const config = readCcrConfig();
517
+ await runCcrUi(scriptLang, config?.APIKEY);
518
+ }
519
+ break;
520
+ case "3":
521
+ if (!isCcrConfigured()) {
522
+ console.log(ansis.yellow(`
523
+ \u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
524
+ console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
525
+ `));
526
+ } else {
527
+ await runCcrStatus(scriptLang);
528
+ }
529
+ break;
530
+ case "4":
531
+ if (!isCcrConfigured()) {
532
+ console.log(ansis.yellow(`
533
+ \u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
534
+ console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
535
+ `));
536
+ } else {
537
+ await runCcrRestart(scriptLang);
538
+ }
539
+ break;
540
+ case "5":
541
+ if (!isCcrConfigured()) {
542
+ console.log(ansis.yellow(`
543
+ \u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
544
+ console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
545
+ `));
546
+ } else {
547
+ await runCcrStart(scriptLang);
548
+ }
549
+ break;
550
+ case "6":
551
+ if (!isCcrConfigured()) {
552
+ console.log(ansis.yellow(`
553
+ \u26A0\uFE0F ${i18n.ccr.ccrNotConfigured || "CCR is not configured yet. Please initialize CCR first."}`));
554
+ console.log(ansis.cyan(` ${i18n.ccr.pleaseInitFirst || "Please select option 1 to initialize CCR."}
555
+ `));
556
+ } else {
557
+ await runCcrStop(scriptLang);
558
+ }
559
+ break;
560
+ case "0":
561
+ return false;
562
+ }
563
+ if (choice !== "0") {
564
+ console.log("\n" + ansis.dim("\u2500".repeat(50)) + "\n");
565
+ const { continueInCcr } = await inquirer.prompt({
566
+ type: "confirm",
567
+ name: "continueInCcr",
568
+ message: i18n.common.returnToMenu || "Return to CCR menu?",
569
+ default: true
570
+ });
571
+ if (continueInCcr) {
572
+ return await showCcrMenu(scriptLang);
573
+ }
574
+ }
575
+ return false;
576
+ } catch (error) {
577
+ if (!handleExitPromptError(error)) {
578
+ handleGeneralError(error, scriptLang);
579
+ }
580
+ return false;
581
+ }
582
+ }
583
+
331
584
  function getValidLanguage(lang) {
332
585
  return lang && lang in I18N ? lang : "en";
333
586
  }
334
587
  async function runCcusageFeature(scriptLang) {
335
588
  const validLang = getValidLanguage(scriptLang);
336
- const i18n = I18N[validLang];
589
+ const i18n = getTranslation(validLang);
337
590
  console.log("");
338
- console.log(ansis.cyan(i18n.menuOptions.ccusage));
339
- console.log(ansis.gray(`${i18n.ccusageDescription} - https://github.com/ryoppippi/ccusage`));
591
+ console.log(ansis.cyan(i18n.menu.menuOptions.ccusage));
592
+ console.log(ansis.gray(`${i18n.tools.ccusageDescription} - https://github.com/ryoppippi/ccusage`));
340
593
  console.log("");
341
594
  const choices = [
342
- { name: i18n.ccusageModes.daily, value: "daily" },
343
- { name: i18n.ccusageModes.monthly, value: "monthly" },
344
- { name: i18n.ccusageModes.session, value: "session" },
345
- { name: i18n.ccusageModes.blocks, value: "blocks" },
346
- { name: i18n.ccusageModes.custom, value: "custom" },
347
- { name: i18n.back, value: "back" }
595
+ { name: i18n.tools.ccusageModes.daily, value: "daily" },
596
+ { name: i18n.tools.ccusageModes.monthly, value: "monthly" },
597
+ { name: i18n.tools.ccusageModes.session, value: "session" },
598
+ { name: i18n.tools.ccusageModes.blocks, value: "blocks" },
599
+ { name: i18n.tools.ccusageModes.custom, value: "custom" },
600
+ { name: i18n.common.back, value: "back" }
348
601
  ];
349
602
  const { mode } = await inquirer.prompt({
350
603
  type: "list",
351
604
  name: "mode",
352
- message: i18n.selectAnalysisMode,
353
- choices
605
+ message: i18n.tools.selectAnalysisMode,
606
+ choices: addNumbersToChoices(choices)
354
607
  });
355
608
  if (mode === "back") {
356
609
  return;
@@ -360,7 +613,7 @@ async function runCcusageFeature(scriptLang) {
360
613
  const { customArgs } = await inquirer.prompt({
361
614
  type: "input",
362
615
  name: "customArgs",
363
- message: i18n.enterCustomArgs,
616
+ message: i18n.tools.enterCustomArgs,
364
617
  default: ""
365
618
  });
366
619
  if (customArgs === null || customArgs === void 0 || customArgs === "") {
@@ -391,9 +644,13 @@ async function runCcusageFeature(scriptLang) {
391
644
  await inquirer.prompt({
392
645
  type: "input",
393
646
  name: "continue",
394
- message: ansis.gray(i18n.pressEnterToContinue)
647
+ message: ansis.gray(i18n.tools.pressEnterToContinue)
395
648
  });
396
649
  }
650
+ async function runCcrMenuFeature(scriptLang) {
651
+ const validLang = getValidLanguage(scriptLang);
652
+ await showCcrMenu(validLang);
653
+ }
397
654
 
398
655
  async function update(options = {}) {
399
656
  try {
@@ -408,21 +665,21 @@ async function update(options = {}) {
408
665
  const { lang } = await inquirer.prompt({
409
666
  type: "list",
410
667
  name: "lang",
411
- message: i18n.updateConfigLangPrompt,
412
- choices: SUPPORTED_LANGS.map((l) => ({
413
- name: `${LANG_LABELS[l]} - ${i18n.configLangHint[l]}`,
668
+ message: i18n.language.updateConfigLangPrompt,
669
+ choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
670
+ name: `${LANG_LABELS[l]} - ${i18n.language.configLangHint[l]}`,
414
671
  value: l
415
- }))
672
+ })))
416
673
  });
417
674
  if (!lang) {
418
- console.log(ansis.yellow(i18n.cancelled));
675
+ console.log(ansis.yellow(i18n.common.cancelled));
419
676
  process.exit(0);
420
677
  }
421
678
  configLang = lang;
422
679
  }
423
680
  const aiOutputLang = await resolveAiOutputLanguage(scriptLang, options.aiOutputLang, zcfConfig);
424
681
  console.log(ansis.cyan(`
425
- ${i18n.updatingPrompts}
682
+ ${i18n.workflow.updatingPrompts}
426
683
  `));
427
684
  await updatePromptOnly(configLang, scriptLang, aiOutputLang);
428
685
  await selectAndInstallWorkflows(configLang, scriptLang);
@@ -446,69 +703,72 @@ async function showMainMenu() {
446
703
  let exitMenu = false;
447
704
  while (!exitMenu) {
448
705
  const i18n = I18N[scriptLang];
449
- console.log(ansis.cyan(i18n.selectFunction));
706
+ console.log(ansis.cyan(i18n.menu.selectFunction));
450
707
  console.log(" -------- Claude Code --------");
451
708
  console.log(
452
- ` ${ansis.cyan("1.")} ${i18n.menuOptions.fullInit} ${ansis.gray("- " + i18n.menuDescriptions.fullInit)}`
709
+ ` ${ansis.cyan("1.")} ${i18n.menu.menuOptions.fullInit} ${ansis.gray("- " + i18n.menu.menuDescriptions.fullInit)}`
453
710
  );
454
711
  console.log(
455
- ` ${ansis.cyan("2.")} ${i18n.menuOptions.importWorkflow} ${ansis.gray(
456
- "- " + i18n.menuDescriptions.importWorkflow
712
+ ` ${ansis.cyan("2.")} ${i18n.menu.menuOptions.importWorkflow} ${ansis.gray(
713
+ "- " + i18n.menu.menuDescriptions.importWorkflow
457
714
  )}`
458
715
  );
459
716
  console.log(
460
- ` ${ansis.cyan("3.")} ${i18n.menuOptions.configureApi} ${ansis.gray(
461
- "- " + i18n.menuDescriptions.configureApi
717
+ ` ${ansis.cyan("3.")} ${i18n.menu.menuOptions.configureApiOrCcr} ${ansis.gray(
718
+ "- " + i18n.menu.menuDescriptions.configureApiOrCcr
462
719
  )}`
463
720
  );
464
721
  console.log(
465
- ` ${ansis.cyan("4.")} ${i18n.menuOptions.configureMcp} ${ansis.gray(
466
- "- " + i18n.menuDescriptions.configureMcp
722
+ ` ${ansis.cyan("4.")} ${i18n.menu.menuOptions.configureMcp} ${ansis.gray(
723
+ "- " + i18n.menu.menuDescriptions.configureMcp
467
724
  )}`
468
725
  );
469
726
  console.log(
470
- ` ${ansis.cyan("5.")} ${i18n.menuOptions.configureModel} ${ansis.gray(
471
- "- " + i18n.menuDescriptions.configureModel
727
+ ` ${ansis.cyan("5.")} ${i18n.menu.menuOptions.configureModel} ${ansis.gray(
728
+ "- " + i18n.menu.menuDescriptions.configureModel
472
729
  )}`
473
730
  );
474
731
  console.log(
475
- ` ${ansis.cyan("6.")} ${i18n.menuOptions.configureAiMemory} ${ansis.gray(
476
- "- " + i18n.menuDescriptions.configureAiMemory
732
+ ` ${ansis.cyan("6.")} ${i18n.menu.menuOptions.configureAiMemory} ${ansis.gray(
733
+ "- " + i18n.menu.menuDescriptions.configureAiMemory
477
734
  )}`
478
735
  );
479
736
  console.log(
480
- ` ${ansis.cyan("7.")} ${i18n.menuOptions.configureEnvPermission} ${ansis.gray(
481
- "- " + i18n.menuDescriptions.configureEnvPermission
737
+ ` ${ansis.cyan("7.")} ${i18n.menu.menuOptions.configureEnvPermission} ${ansis.gray(
738
+ "- " + i18n.menu.menuDescriptions.configureEnvPermission
482
739
  )}`
483
740
  );
484
741
  console.log("");
485
- console.log(` --------- ${i18n.menuSections.otherTools} ----------`);
742
+ console.log(` --------- ${i18n.menu.menuSections.otherTools} ----------`);
486
743
  console.log(
487
- ` ${ansis.cyan("U.")} ${i18n.menuOptions.ccusage} ${ansis.gray("- " + i18n.menuDescriptions.ccusage)}`
744
+ ` ${ansis.cyan("R.")} ${i18n.menu.menuOptions.ccrManagement} ${ansis.gray("- " + i18n.menu.menuDescriptions.ccrManagement)}`
745
+ );
746
+ console.log(
747
+ ` ${ansis.cyan("U.")} ${i18n.menu.menuOptions.ccusage} ${ansis.gray("- " + i18n.menu.menuDescriptions.ccusage)}`
488
748
  );
489
749
  console.log("");
490
750
  console.log(" ------------ ZCF ------------");
491
751
  console.log(
492
- ` ${ansis.cyan("0.")} ${i18n.menuOptions.changeLanguage} ${ansis.gray(
493
- "- " + i18n.menuDescriptions.changeLanguage
752
+ ` ${ansis.cyan("0.")} ${i18n.menu.menuOptions.changeLanguage} ${ansis.gray(
753
+ "- " + i18n.menu.menuDescriptions.changeLanguage
494
754
  )}`
495
755
  );
496
756
  console.log(
497
- ` ${ansis.cyan("-.")} ${i18n.menuOptions.clearCache} ${ansis.gray("- " + i18n.menuDescriptions.clearCache)}`
757
+ ` ${ansis.cyan("-.")} ${i18n.menu.menuOptions.clearCache} ${ansis.gray("- " + i18n.menu.menuDescriptions.clearCache)}`
498
758
  );
499
- console.log(` ${ansis.red("Q.")} ${ansis.red(i18n.menuOptions.exit)}`);
759
+ console.log(` ${ansis.red("Q.")} ${ansis.red(i18n.menu.menuOptions.exit)}`);
500
760
  console.log("");
501
761
  const { choice } = await inquirer.prompt({
502
762
  type: "input",
503
763
  name: "choice",
504
- message: i18n.enterChoice,
764
+ message: i18n.common.enterChoice,
505
765
  validate: (value) => {
506
- const valid = ["1", "2", "3", "4", "5", "6", "7", "u", "U", "0", "-", "q", "Q"];
507
- return valid.includes(value) || i18n.invalidChoice;
766
+ const valid = ["1", "2", "3", "4", "5", "6", "7", "r", "R", "u", "U", "0", "-", "q", "Q"];
767
+ return valid.includes(value) || i18n.common.invalidChoice;
508
768
  }
509
769
  });
510
770
  if (!choice) {
511
- console.log(ansis.yellow(i18n.cancelled));
771
+ console.log(ansis.yellow(i18n.common.cancelled));
512
772
  exitMenu = true;
513
773
  break;
514
774
  }
@@ -534,6 +794,10 @@ async function showMainMenu() {
534
794
  case "7":
535
795
  await configureEnvPermissionFeature(scriptLang);
536
796
  break;
797
+ case "r":
798
+ case "R":
799
+ await runCcrMenuFeature(scriptLang);
800
+ break;
537
801
  case "u":
538
802
  case "U":
539
803
  await runCcusageFeature(scriptLang);
@@ -549,11 +813,11 @@ async function showMainMenu() {
549
813
  break;
550
814
  case "q":
551
815
  exitMenu = true;
552
- console.log(ansis.cyan(i18n.goodbye));
816
+ console.log(ansis.cyan(i18n.common.goodbye));
553
817
  break;
554
818
  }
555
819
  if (!exitMenu && choice.toLowerCase() !== "q") {
556
- if (choice === "0" || choice === "-" || choice.toLowerCase() === "u") {
820
+ if (choice === "0" || choice === "-" || choice.toLowerCase() === "u" || choice.toLowerCase() === "r") {
557
821
  console.log("\n" + ansis.dim("\u2500".repeat(50)) + "\n");
558
822
  continue;
559
823
  }
@@ -561,12 +825,12 @@ async function showMainMenu() {
561
825
  const { continue: shouldContinue } = await inquirer.prompt({
562
826
  type: "confirm",
563
827
  name: "continue",
564
- message: i18n.returnToMenu,
828
+ message: i18n.common.returnToMenu,
565
829
  default: true
566
830
  });
567
831
  if (!shouldContinue) {
568
832
  exitMenu = true;
569
- console.log(ansis.cyan(i18n.goodbye));
833
+ console.log(ansis.cyan(i18n.common.goodbye));
570
834
  }
571
835
  }
572
836
  }
@@ -577,6 +841,24 @@ async function showMainMenu() {
577
841
  }
578
842
  }
579
843
 
844
+ async function ccr(options = {}) {
845
+ try {
846
+ if (!options.skipBanner) {
847
+ displayBannerWithInfo();
848
+ }
849
+ const zcfConfig = await readZcfConfigAsync();
850
+ const scriptLang = options.lang || zcfConfig?.preferredLang || await selectScriptLanguage();
851
+ const continueInCcr = await showCcrMenu(scriptLang);
852
+ if (!continueInCcr && !options.skipBanner) {
853
+ await showMainMenu();
854
+ }
855
+ } catch (error) {
856
+ if (!handleExitPromptError(error)) {
857
+ handleGeneralError(error, options.lang);
858
+ }
859
+ }
860
+ }
861
+
580
862
  function setupCommands(cli) {
581
863
  cli.command("[lang]", "Show interactive menu (default)").option("--init", "Run full initialization directly").option("--config-lang, -c <lang>", "Configuration language (zh-CN, en)").option("--force, -f", "Force overwrite existing configuration").action(async (lang, options) => {
582
864
  await handleDefaultCommand(lang, options);
@@ -587,6 +869,9 @@ function setupCommands(cli) {
587
869
  cli.command("update", "Update Claude Code prompts only").alias("u").option("--config-lang, -c <lang>", "Configuration language (zh-CN, en)").action(async (options) => {
588
870
  await handleUpdateCommand(options);
589
871
  });
872
+ cli.command("ccr", "Configure Claude Code Router for model proxy").option("--lang, -l <lang>", "Display language (zh-CN, en)").action(async (options) => {
873
+ await ccr({ lang: options.lang });
874
+ });
590
875
  cli.command("ccu [...args]", "Run Claude Code usage analysis tool").allowUnknownOptions().action(async (args) => {
591
876
  await executeCcusage(args);
592
877
  });
@@ -628,6 +913,7 @@ function customizeHelp(sections) {
628
913
  "i"
629
914
  )} Initialize Claude Code configuration / \u521D\u59CB\u5316 Claude Code \u914D\u7F6E`,
630
915
  ` ${ansis.cyan("zcf update")} | ${ansis.cyan("u")} Update workflow-related md files / \u4EC5\u66F4\u65B0\u5DE5\u4F5C\u6D41\u76F8\u5173md`,
916
+ ` ${ansis.cyan("zcf ccr")} Configure Claude Code Router / \u914D\u7F6E\u6A21\u578B\u4EE3\u7406`,
631
917
  ` ${ansis.cyan("zcf ccu")} [args] Run Claude Code usage analysis / \u8FD0\u884C Claude Code \u7528\u91CF\u5206\u6790`,
632
918
  "",
633
919
  ansis.gray(" Shortcuts / \u5FEB\u6377\u65B9\u5F0F:"),