zcf 2.7.1 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,10 +5,21 @@ import { join, dirname } from 'pathe';
5
5
  import dayjs from 'dayjs';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { exec } from 'tinyexec';
8
- import { rm, mkdir, copyFile as copyFile$1 } from 'node:fs/promises';
8
+ import { exec as exec$1 } from 'child_process';
9
+ import { promisify } from 'util';
10
+ import ora from 'ora';
11
+ import prompts$1 from 'prompts';
12
+ import { readFile as readFile$1, writeFile as writeFile$1, mkdir } from 'fs/promises';
13
+ import { join as join$1 } from 'path';
14
+ import { homedir as homedir$1 } from 'os';
15
+ import semver from 'semver';
16
+ import { rm, mkdir as mkdir$1, copyFile as copyFile$1 } from 'node:fs/promises';
17
+ import { exec as exec$2 } from 'node:child_process';
18
+ import { promisify as promisify$1 } from 'node:util';
9
19
  import { homedir, platform } from 'node:os';
20
+ import { join as join$2 } from 'node:path';
10
21
 
11
- const version = "2.7.1";
22
+ const version = "2.8.1";
12
23
  const homepage = "https://github.com/UfoMiao/zcf";
13
24
 
14
25
  const common$1 = {
@@ -52,6 +63,7 @@ const installation$1 = {
52
63
  installPrompt: "\u68C0\u6D4B\u5230 Claude Code \u672A\u5B89\u88C5\uFF0C\u662F\u5426\u81EA\u52A8\u5B89\u88C5\uFF1F",
53
64
  installing: "\u6B63\u5728\u5B89\u88C5 Claude Code...",
54
65
  installSuccess: "Claude Code \u5B89\u88C5\u6210\u529F",
66
+ alreadyInstalled: "Claude Code \u5DF2\u5B89\u88C5",
55
67
  installFailed: "Claude Code \u5B89\u88C5\u5931\u8D25",
56
68
  npmNotFound: "npm \u672A\u5B89\u88C5\u3002\u8BF7\u5148\u5B89\u88C5 Node.js \u548C npm\u3002",
57
69
  // Termux specific
@@ -70,6 +82,8 @@ const api$1 = {
70
82
  authTokenDesc: "\u9002\u7528\u4E8E\u901A\u8FC7 OAuth \u6216\u6D4F\u89C8\u5668\u767B\u5F55\u83B7\u53D6\u7684\u4EE4\u724C",
71
83
  useApiKey: "\u4F7F\u7528 API Key (\u5BC6\u94A5\u8BA4\u8BC1)",
72
84
  apiKeyDesc: "\u9002\u7528\u4E8E\u4ECE Anthropic Console \u83B7\u53D6\u7684 API \u5BC6\u94A5",
85
+ useCcrProxy: "\u4F7F\u7528 CCR \u4EE3\u7406",
86
+ ccrProxyDesc: "\u901A\u8FC7 Claude Code Router \u4F7F\u7528\u591A\u4E2A AI \u6A21\u578B",
73
87
  skipApi: "\u8DF3\u8FC7\uFF08\u7A0D\u540E\u624B\u52A8\u914D\u7F6E\uFF09",
74
88
  enterApiUrl: "\u8BF7\u8F93\u5165 API URL",
75
89
  enterAuthToken: "\u8BF7\u8F93\u5165 Auth Token",
@@ -117,11 +131,21 @@ const configuration$1 = {
117
131
  // Model configuration
118
132
  selectDefaultModel: "\u9009\u62E9\u9ED8\u8BA4\u6A21\u578B",
119
133
  modelConfigSuccess: "\u9ED8\u8BA4\u6A21\u578B\u5DF2\u914D\u7F6E",
134
+ existingModelConfig: "\u68C0\u6D4B\u5230\u5DF2\u6709\u6A21\u578B\u914D\u7F6E",
135
+ currentModel: "\u5F53\u524D\u6A21\u578B",
136
+ modifyModel: "\u662F\u5426\u4FEE\u6539\u6A21\u578B\u914D\u7F6E\uFF1F",
137
+ keepModel: "\u4FDD\u6301\u5F53\u524D\u6A21\u578B\u914D\u7F6E",
138
+ defaultModelOption: "\u9ED8\u8BA4\uFF08\u8BA9 Claude Code \u81EA\u52A8\u9009\u62E9\uFF09",
139
+ modelConfigured: "\u9ED8\u8BA4\u6A21\u578B\u5DF2\u914D\u7F6E",
120
140
  // AI memory configuration
121
141
  selectMemoryOption: "\u9009\u62E9\u914D\u7F6E\u9009\u9879",
122
142
  configureAiLanguage: "\u914D\u7F6E AI \u8F93\u51FA\u8BED\u8A00",
123
143
  configureAiPersonality: "\u914D\u7F6E AI \u4E2A\u6027\u98CE\u683C",
124
144
  aiLanguageConfigured: "AI \u8F93\u51FA\u8BED\u8A00\u5DF2\u914D\u7F6E",
145
+ existingLanguageConfig: "\u68C0\u6D4B\u5230\u5DF2\u6709 AI \u8F93\u51FA\u8BED\u8A00\u914D\u7F6E",
146
+ currentLanguage: "\u5F53\u524D\u8BED\u8A00",
147
+ modifyLanguage: "\u662F\u5426\u4FEE\u6539 AI \u8F93\u51FA\u8BED\u8A00\uFF1F",
148
+ keepLanguage: "\u4FDD\u6301\u5F53\u524D\u8BED\u8A00\u914D\u7F6E",
125
149
  // AI personality
126
150
  selectAiPersonality: "\u9009\u62E9 AI \u4E2A\u6027\u98CE\u683C",
127
151
  customPersonalityHint: "\u5B9A\u4E49\u4F60\u81EA\u5DF1\u7684\u4E2A\u6027",
@@ -147,7 +171,17 @@ const configuration$1 = {
147
171
  openSettingsJsonDesc: "\u9AD8\u7EA7\u7528\u6237\u81EA\u5B9A\u4E49",
148
172
  envImportSuccess: "\u73AF\u5883\u53D8\u91CF\u5DF2\u5BFC\u5165",
149
173
  permissionsImportSuccess: "\u6743\u9650\u914D\u7F6E\u5DF2\u5BFC\u5165",
150
- openingSettingsJson: "\u6B63\u5728\u6253\u5F00 settings.json..."
174
+ openingSettingsJson: "\u6B63\u5728\u6253\u5F00 settings.json...",
175
+ // JSON config related
176
+ invalidConfiguration: "\u914D\u7F6E\u65E0\u6548",
177
+ failedToParseJson: "\u89E3\u6790 JSON \u6587\u4EF6\u5931\u8D25:",
178
+ failedToBackupConfig: "\u5907\u4EFD\u914D\u7F6E\u5931\u8D25",
179
+ failedToReadTemplateSettings: "\u8BFB\u53D6\u6A21\u677F\u8BBE\u7F6E\u5931\u8D25",
180
+ failedToMergeSettings: "\u5408\u5E76\u8BBE\u7F6E\u5931\u8D25",
181
+ preservingExistingSettings: "\u4FDD\u7559\u73B0\u6709\u8BBE\u7F6E",
182
+ memoryDirNotFound: "\u672A\u627E\u5230\u8BB0\u5FC6\u76EE\u5F55",
183
+ failedToSetOnboarding: "\u8BBE\u7F6E\u5F15\u5BFC\u6807\u5FD7\u5931\u8D25",
184
+ fixWindowsMcp: "\u4FEE\u590D Windows MCP \u914D\u7F6E\uFF1F"
151
185
  };
152
186
 
153
187
  const mcp$1 = {
@@ -174,11 +208,14 @@ const menu$1 = {
174
208
  menuOptions: {
175
209
  fullInit: "\u5B8C\u6574\u521D\u59CB\u5316",
176
210
  importWorkflow: "\u5BFC\u5165\u5DE5\u4F5C\u6D41",
211
+ configureApiOrCcr: "\u914D\u7F6E API \u6216 CCR \u4EE3\u7406",
177
212
  configureApi: "\u914D\u7F6E API",
178
213
  configureMcp: "\u914D\u7F6E MCP",
179
214
  configureModel: "\u914D\u7F6E\u9ED8\u8BA4\u6A21\u578B",
180
215
  configureAiMemory: "\u914D\u7F6E Claude \u5168\u5C40\u8BB0\u5FC6",
181
216
  configureEnvPermission: "\u5BFC\u5165\u63A8\u8350\u73AF\u5883\u53D8\u91CF\u548C\u6743\u9650\u914D\u7F6E",
217
+ configureCcr: "\u914D\u7F6E\u6A21\u578B\u4EE3\u7406 (CCR)",
218
+ ccrManagement: "CCR",
182
219
  ccusage: "ccusage",
183
220
  installBmad: "\u5B89\u88C5 BMad Method",
184
221
  clearCache: "\u6E05\u9664\u504F\u597D\u7F13\u5B58",
@@ -186,13 +223,16 @@ const menu$1 = {
186
223
  exit: "\u9000\u51FA"
187
224
  },
188
225
  menuDescriptions: {
189
- fullInit: "\u5B89\u88C5 Claude Code + \u5BFC\u5165\u5DE5\u4F5C\u6D41 + \u914D\u7F6E API + \u914D\u7F6E MCP \u670D\u52A1",
226
+ fullInit: "\u5B89\u88C5 Claude Code + \u5BFC\u5165\u5DE5\u4F5C\u6D41 + \u914D\u7F6E API \u6216 CCR \u4EE3\u7406 + \u914D\u7F6E MCP \u670D\u52A1",
190
227
  importWorkflow: "\u4EC5\u5BFC\u5165/\u66F4\u65B0\u5DE5\u4F5C\u6D41\u76F8\u5173\u6587\u4EF6",
228
+ configureApiOrCcr: "\u914D\u7F6E API URL\u3001\u8BA4\u8BC1\u4FE1\u606F\u6216 CCR \u4EE3\u7406",
191
229
  configureApi: "\u914D\u7F6E API URL \u548C\u8BA4\u8BC1\u4FE1\u606F",
192
230
  configureMcp: "\u914D\u7F6E MCP \u670D\u52A1\uFF08\u542B Windows \u4FEE\u590D\uFF09",
193
231
  configureModel: "\u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u578B\uFF08opus/sonnet\uFF09",
194
232
  configureAiMemory: "\u914D\u7F6E AI \u8F93\u51FA\u8BED\u8A00\u548C\u89D2\u8272\u98CE\u683C",
195
233
  configureEnvPermission: "\u5BFC\u5165\u9690\u79C1\u4FDD\u62A4\u73AF\u5883\u53D8\u91CF\u548C\u7CFB\u7EDF\u6743\u9650\u914D\u7F6E",
234
+ configureCcr: "\u914D\u7F6E Claude Code Router \u4EE5\u4F7F\u7528\u591A\u4E2A AI \u6A21\u578B",
235
+ ccrManagement: "\u914D\u7F6E Claude Code Router \u4EE5\u4F7F\u7528\u591A\u4E2A AI \u6A21\u578B",
196
236
  ccusage: "Claude Code \u7528\u91CF\u5206\u6790",
197
237
  installBmad: "AI \u9A71\u52A8\u7684\u5F00\u53D1\u65B9\u6CD5\u8BBA\u6846\u67B6",
198
238
  clearCache: "\u6E05\u9664\u504F\u597D\u8BED\u8A00\u7B49\u7F13\u5B58",
@@ -326,19 +366,121 @@ const tools$1 = {
326
366
  errorDetails: "\u9519\u8BEF\u8BE6\u60C5:"
327
367
  };
328
368
 
369
+ const ccrMessages$1 = {
370
+ // Installation
371
+ installingCcr: "\u6B63\u5728\u5B89\u88C5 Claude Code Router...",
372
+ ccrInstallSuccess: "Claude Code Router \u5B89\u88C5\u6210\u529F",
373
+ ccrInstallFailed: "\u5B89\u88C5 Claude Code Router \u5931\u8D25",
374
+ ccrAlreadyInstalled: "Claude Code Router \u5DF2\u5B89\u88C5",
375
+ // Configuration
376
+ configureCcr: "\u914D\u7F6E\u6A21\u578B\u4EE3\u7406 (CCR)",
377
+ useCcrProxy: "\u4F7F\u7528 CCR \u4EE3\u7406",
378
+ ccrProxyDesc: "\u901A\u8FC7 Claude Code Router \u8FDE\u63A5\u591A\u4E2A AI \u6A21\u578B",
379
+ fetchingPresets: "\u6B63\u5728\u83B7\u53D6\u63D0\u4F9B\u5546\u9884\u8BBE...",
380
+ noPresetsAvailable: "\u6CA1\u6709\u53EF\u7528\u7684\u9884\u8BBE",
381
+ selectCcrPreset: "\u9009\u62E9\u4E00\u4E2A\u63D0\u4F9B\u5546\u9884\u8BBE\uFF1A",
382
+ keyRequired: "API \u5BC6\u94A5\u4E0D\u80FD\u4E3A\u7A7A",
383
+ // Existing config
384
+ existingCcrConfig: "\u53D1\u73B0\u73B0\u6709\u7684 CCR \u914D\u7F6E",
385
+ overwriteCcrConfig: "\u662F\u5426\u5907\u4EFD\u73B0\u6709\u7684 CCR \u914D\u7F6E\u5E76\u91CD\u65B0\u914D\u7F6E\uFF1F",
386
+ keepingExistingConfig: "\u4FDD\u7559\u73B0\u6709\u914D\u7F6E",
387
+ backupCcrConfig: "\u6B63\u5728\u5907\u4EFD\u73B0\u6709\u7684 CCR \u914D\u7F6E...",
388
+ ccrBackupSuccess: "CCR \u914D\u7F6E\u5DF2\u5907\u4EFD\u5230\uFF1A{path}",
389
+ ccrBackupFailed: "\u5907\u4EFD CCR \u914D\u7F6E\u5931\u8D25",
390
+ // Model selection
391
+ selectDefaultModelForProvider: "\u9009\u62E9 {provider} \u7684\u9ED8\u8BA4\u6A21\u578B\uFF1A",
392
+ enterApiKeyForProvider: "\u8BF7\u8F93\u5165 {provider} \u7684 API \u5BC6\u94A5\uFF1A",
393
+ // Skip option
394
+ skipOption: "\u8DF3\u8FC7\uFF0C\u5728 CCR \u4E2D\u81EA\u884C\u914D\u7F6E",
395
+ skipConfiguring: "\u8DF3\u8FC7\u9884\u8BBE\u914D\u7F6E\uFF0C\u5C06\u521B\u5EFA\u7A7A\u914D\u7F6E\u6846\u67B6",
396
+ // Success/Error messages
397
+ ccrConfigSuccess: "CCR \u914D\u7F6E\u5DF2\u4FDD\u5B58",
398
+ proxyConfigSuccess: "\u4EE3\u7406\u8BBE\u7F6E\u5DF2\u914D\u7F6E",
399
+ ccrConfigFailed: "\u914D\u7F6E CCR \u5931\u8D25",
400
+ ccrSetupComplete: "CCR \u8BBE\u7F6E\u5B8C\u6210",
401
+ fetchPresetsError: "\u83B7\u53D6\u63D0\u4F9B\u5546\u9884\u8BBE\u5931\u8D25",
402
+ failedToStartCcrService: "\u542F\u52A8 CCR \u670D\u52A1\u5931\u8D25",
403
+ errorStartingCcrService: "\u542F\u52A8 CCR \u670D\u52A1\u65F6\u51FA\u9519",
404
+ // CCR service status
405
+ ccrRestartSuccess: "CCR \u670D\u52A1\u5DF2\u91CD\u542F",
406
+ ccrRestartFailed: "CCR \u670D\u52A1\u91CD\u542F\u5931\u8D25",
407
+ // Configuration tips
408
+ configTips: "\u914D\u7F6E\u63D0\u793A",
409
+ useClaudeCommand: "\u8BF7\u4F7F\u7528 claude \u547D\u4EE4\u542F\u52A8 Claude Code\uFF08\u800C\u975E ccr code\uFF09",
410
+ advancedConfigTip: "\u60A8\u53EF\u4EE5\u4F7F\u7528 ccr ui \u547D\u4EE4\u8FDB\u884C\u66F4\u9AD8\u7EA7\u7684\u914D\u7F6E",
411
+ manualConfigTip: "\u624B\u52A8\u4FEE\u6539\u914D\u7F6E\u6587\u4EF6\u540E\uFF0C\u8BF7\u6267\u884C ccr restart \u4F7F\u914D\u7F6E\u751F\u6548",
412
+ // CCR Menu
413
+ ccrMenuTitle: "CCR - Claude Code Router \u7BA1\u7406",
414
+ ccrMenuOptions: {
415
+ initCcr: "\u521D\u59CB\u5316 CCR",
416
+ startUi: "\u542F\u52A8 CCR UI",
417
+ checkStatus: "\u67E5\u8BE2 CCR \u72B6\u6001",
418
+ restart: "\u91CD\u542F CCR",
419
+ start: "\u542F\u52A8 CCR",
420
+ stop: "\u505C\u6B62 CCR",
421
+ back: "\u8FD4\u56DE\u4E3B\u83DC\u5355"
422
+ },
423
+ ccrMenuDescriptions: {
424
+ initCcr: "\u5B89\u88C5\u5E76\u914D\u7F6E CCR",
425
+ startUi: "\u6253\u5F00 Web \u754C\u9762\u7BA1\u7406 CCR",
426
+ checkStatus: "\u67E5\u770B CCR \u670D\u52A1\u8FD0\u884C\u72B6\u6001",
427
+ restart: "\u91CD\u542F CCR \u670D\u52A1",
428
+ start: "\u542F\u52A8 CCR \u670D\u52A1",
429
+ stop: "\u505C\u6B62 CCR \u670D\u52A1"
430
+ },
431
+ // Command execution messages
432
+ startingCcrUi: "\u6B63\u5728\u542F\u52A8 CCR UI...",
433
+ ccrUiStarted: "CCR UI \u5DF2\u542F\u52A8",
434
+ checkingCcrStatus: "\u6B63\u5728\u67E5\u8BE2 CCR \u72B6\u6001...",
435
+ ccrStatusTitle: "CCR \u72B6\u6001\u4FE1\u606F\uFF1A",
436
+ restartingCcr: "\u6B63\u5728\u91CD\u542F CCR...",
437
+ ccrRestarted: "CCR \u5DF2\u91CD\u542F",
438
+ startingCcr: "\u6B63\u5728\u542F\u52A8 CCR...",
439
+ ccrStarted: "CCR \u5DF2\u542F\u52A8",
440
+ stoppingCcr: "\u6B63\u5728\u505C\u6B62 CCR...",
441
+ ccrStopped: "CCR \u5DF2\u505C\u6B62",
442
+ ccrCommandFailed: "\u6267\u884C CCR \u547D\u4EE4\u5931\u8D25",
443
+ // Configuration check messages
444
+ ccrNotConfigured: "CCR \u5C1A\u672A\u914D\u7F6E\u3002\u8BF7\u5148\u521D\u59CB\u5316 CCR\u3002",
445
+ pleaseInitFirst: "\u8BF7\u9009\u62E9\u9009\u9879 1 \u6765\u521D\u59CB\u5316 CCR\u3002",
446
+ // UI API Key messages
447
+ ccrUiApiKey: "CCR UI \u767B\u5F55\u5BC6\u94A5",
448
+ ccrUiApiKeyHint: "\u4F7F\u7528\u6B64\u5BC6\u94A5\u767B\u5F55 CCR UI \u754C\u9762"
449
+ };
450
+
451
+ const updater$1 = {
452
+ checkingVersion: "\u6B63\u5728\u68C0\u67E5\u7248\u672C...",
453
+ checkingTools: "\u68C0\u67E5\u5DE5\u5177\u7248\u672C",
454
+ ccrNotInstalled: "CCR \u672A\u5B89\u88C5",
455
+ ccrUpToDate: "CCR \u5DF2\u662F\u6700\u65B0\u7248\u672C (v{version})",
456
+ claudeCodeNotInstalled: "Claude Code \u672A\u5B89\u88C5",
457
+ claudeCodeUpToDate: "Claude Code \u5DF2\u662F\u6700\u65B0\u7248\u672C (v{version})",
458
+ cannotCheckVersion: "\u65E0\u6CD5\u68C0\u67E5\u6700\u65B0\u7248\u672C",
459
+ currentVersion: "\u5F53\u524D\u7248\u672C: v{version}",
460
+ latestVersion: "\u6700\u65B0\u7248\u672C: v{version}",
461
+ confirmUpdate: "\u662F\u5426\u66F4\u65B0 {tool} \u5230\u6700\u65B0\u7248\u672C\uFF1F",
462
+ updateSkipped: "\u5DF2\u8DF3\u8FC7\u66F4\u65B0",
463
+ updating: "\u6B63\u5728\u66F4\u65B0 {tool}...",
464
+ updateSuccess: "{tool} \u66F4\u65B0\u6210\u529F\uFF01",
465
+ updateFailed: "{tool} \u66F4\u65B0\u5931\u8D25",
466
+ checkFailed: "\u7248\u672C\u68C0\u67E5\u5931\u8D25"
467
+ };
468
+
329
469
  const zhCN = {
330
- ...common$1,
331
- ...language$1,
332
- ...installation$1,
333
- ...api$1,
334
- ...configuration$1,
335
- ...mcp$1,
336
- ...menu$1,
337
- ...workflow$1,
338
- ...cli$1,
339
- ...bmad$1,
340
- ...errors$1,
341
- ...tools$1
470
+ common: common$1,
471
+ language: language$1,
472
+ installation: installation$1,
473
+ api: api$1,
474
+ configuration: configuration$1,
475
+ mcp: mcp$1,
476
+ menu: menu$1,
477
+ workflow: workflow$1,
478
+ cli: cli$1,
479
+ bmad: bmad$1,
480
+ errors: errors$1,
481
+ tools: tools$1,
482
+ ccr: ccrMessages$1,
483
+ updater: updater$1
342
484
  };
343
485
 
344
486
  const common = {
@@ -382,6 +524,7 @@ const installation = {
382
524
  installPrompt: "Claude Code not found. Install automatically?",
383
525
  installing: "Installing Claude Code...",
384
526
  installSuccess: "Claude Code installed successfully",
527
+ alreadyInstalled: "Claude Code is already installed",
385
528
  installFailed: "Failed to install Claude Code",
386
529
  npmNotFound: "npm is not installed. Please install Node.js and npm first.",
387
530
  // Termux specific
@@ -400,6 +543,8 @@ const api = {
400
543
  authTokenDesc: "For tokens obtained via OAuth or browser login",
401
544
  useApiKey: "Use API Key (Key authentication)",
402
545
  apiKeyDesc: "For API keys from Anthropic Console",
546
+ useCcrProxy: "Use CCR Proxy",
547
+ ccrProxyDesc: "Use multiple AI models via Claude Code Router",
403
548
  skipApi: "Skip (configure manually later)",
404
549
  enterApiUrl: "Enter API URL",
405
550
  enterAuthToken: "Enter Auth Token",
@@ -447,11 +592,21 @@ const configuration = {
447
592
  // Model configuration
448
593
  selectDefaultModel: "Select default model",
449
594
  modelConfigSuccess: "Default model configured",
595
+ existingModelConfig: "Existing model configuration detected",
596
+ currentModel: "Current model",
597
+ modifyModel: "Modify model configuration?",
598
+ keepModel: "Keeping existing model configuration",
599
+ defaultModelOption: "Default (Let Claude Code choose)",
600
+ modelConfigured: "Default model configured",
450
601
  // AI memory configuration
451
602
  selectMemoryOption: "Select configuration option",
452
603
  configureAiLanguage: "Configure AI output language",
453
604
  configureAiPersonality: "Configure AI personality",
454
605
  aiLanguageConfigured: "AI output language configured",
606
+ existingLanguageConfig: "Existing AI output language configuration detected",
607
+ currentLanguage: "Current language",
608
+ modifyLanguage: "Modify AI output language?",
609
+ keepLanguage: "Keeping existing language configuration",
455
610
  // AI personality
456
611
  selectAiPersonality: "Select AI personality",
457
612
  customPersonalityHint: "Define your own personality",
@@ -477,7 +632,17 @@ const configuration = {
477
632
  openSettingsJsonDesc: "Advanced user customization",
478
633
  envImportSuccess: "Environment variables imported",
479
634
  permissionsImportSuccess: "Permissions imported",
480
- openingSettingsJson: "Opening settings.json..."
635
+ openingSettingsJson: "Opening settings.json...",
636
+ // JSON config related
637
+ invalidConfiguration: "Invalid configuration",
638
+ failedToParseJson: "Failed to parse JSON file:",
639
+ failedToBackupConfig: "Failed to backup config",
640
+ failedToReadTemplateSettings: "Failed to read template settings",
641
+ failedToMergeSettings: "Failed to merge settings",
642
+ preservingExistingSettings: "Preserving existing settings",
643
+ memoryDirNotFound: "Memory directory not found",
644
+ failedToSetOnboarding: "Failed to set onboarding flag",
645
+ fixWindowsMcp: "Fix Windows MCP configuration?"
481
646
  };
482
647
 
483
648
  const mcp = {
@@ -504,11 +669,14 @@ const menu = {
504
669
  menuOptions: {
505
670
  fullInit: "Full initialization",
506
671
  importWorkflow: "Import workflow",
672
+ configureApiOrCcr: "Configure API / CCR proxy",
507
673
  configureApi: "Configure API",
508
674
  configureMcp: "Configure MCP",
509
675
  configureModel: "Configure default model",
510
676
  configureAiMemory: "Configure Claude global memory",
511
677
  configureEnvPermission: "Import recommended environment variables and permissions",
678
+ configureCcr: "Configure Model Proxy (CCR)",
679
+ ccrManagement: "CCR",
512
680
  ccusage: "ccusage",
513
681
  installBmad: "Install BMad Method",
514
682
  clearCache: "Clear preference cache",
@@ -516,13 +684,16 @@ const menu = {
516
684
  exit: "Exit"
517
685
  },
518
686
  menuDescriptions: {
519
- fullInit: "Install Claude Code + Import workflow + Configure API + Configure MCP services",
687
+ fullInit: "Install Claude Code + Import workflow + Configure API or CCR proxy + Configure MCP services",
520
688
  importWorkflow: "Import/update workflow-related files only",
689
+ configureApiOrCcr: "Configure API URL, authentication or CCR proxy",
521
690
  configureApi: "Configure API URL and authentication",
522
691
  configureMcp: "Configure MCP services (includes Windows fix)",
523
692
  configureModel: "Set default model (opus/sonnet)",
524
693
  configureAiMemory: "Configure AI output language and personality",
525
694
  configureEnvPermission: "Import privacy protection environment variables and system permissions",
695
+ configureCcr: "Configure Claude Code Router to use multiple AI models",
696
+ ccrManagement: "Configure Claude Code Router to use multiple AI models",
526
697
  ccusage: "Claude Code usage analysis",
527
698
  installBmad: "AI-driven development methodology framework",
528
699
  clearCache: "Clear preference language and other caches",
@@ -656,25 +827,130 @@ const tools = {
656
827
  errorDetails: "Error details:"
657
828
  };
658
829
 
830
+ const ccrMessages = {
831
+ // Installation
832
+ installingCcr: "Installing Claude Code Router...",
833
+ ccrInstallSuccess: "Claude Code Router installed successfully",
834
+ ccrInstallFailed: "Failed to install Claude Code Router",
835
+ ccrAlreadyInstalled: "Claude Code Router is already installed",
836
+ // Configuration
837
+ configureCcr: "Configure Model Proxy (CCR)",
838
+ useCcrProxy: "Use CCR Proxy",
839
+ ccrProxyDesc: "Connect to multiple AI models via Claude Code Router",
840
+ fetchingPresets: "Fetching provider presets...",
841
+ noPresetsAvailable: "No presets available",
842
+ selectCcrPreset: "Select a provider preset:",
843
+ keyRequired: "API key is required",
844
+ // Existing config
845
+ existingCcrConfig: "Existing CCR configuration found",
846
+ overwriteCcrConfig: "Backup existing CCR configuration and reconfigure?",
847
+ keepingExistingConfig: "Keeping existing configuration",
848
+ backupCcrConfig: "Backing up existing CCR configuration...",
849
+ ccrBackupSuccess: "CCR configuration backed up to: {path}",
850
+ ccrBackupFailed: "Failed to backup CCR configuration",
851
+ // Model selection
852
+ selectDefaultModelForProvider: "Select default model for {provider}:",
853
+ enterApiKeyForProvider: "Enter API key for {provider}:",
854
+ // Skip option
855
+ skipOption: "Skip, configure in CCR manually",
856
+ skipConfiguring: "Skipping preset configuration, will create empty configuration framework",
857
+ // Success/Error messages
858
+ ccrConfigSuccess: "CCR configuration saved",
859
+ proxyConfigSuccess: "Proxy settings configured",
860
+ ccrConfigFailed: "Failed to configure CCR",
861
+ ccrSetupComplete: "CCR setup complete",
862
+ fetchPresetsError: "Failed to fetch provider presets",
863
+ failedToStartCcrService: "Failed to start CCR service",
864
+ errorStartingCcrService: "Error starting CCR service",
865
+ // CCR service status
866
+ ccrRestartSuccess: "CCR service restarted",
867
+ ccrRestartFailed: "Failed to restart CCR service",
868
+ // Configuration tips
869
+ configTips: "Configuration Tips",
870
+ useClaudeCommand: "Use the claude command to start Claude Code (not ccr code)",
871
+ advancedConfigTip: "You can use the ccr ui command for advanced configuration",
872
+ manualConfigTip: "After manually modifying the configuration file, run ccr restart to apply changes",
873
+ // CCR Menu
874
+ ccrMenuTitle: "CCR - Claude Code Router Management",
875
+ ccrMenuOptions: {
876
+ initCcr: "Initialize CCR",
877
+ startUi: "Start CCR UI",
878
+ checkStatus: "Check CCR Status",
879
+ restart: "Restart CCR",
880
+ start: "Start CCR",
881
+ stop: "Stop CCR",
882
+ back: "Back to Main Menu"
883
+ },
884
+ ccrMenuDescriptions: {
885
+ initCcr: "Install and configure CCR",
886
+ startUi: "Open web interface to manage CCR",
887
+ checkStatus: "View CCR service status",
888
+ restart: "Restart CCR service",
889
+ start: "Start CCR service",
890
+ stop: "Stop CCR service"
891
+ },
892
+ // Command execution messages
893
+ startingCcrUi: "Starting CCR UI...",
894
+ ccrUiStarted: "CCR UI started",
895
+ checkingCcrStatus: "Checking CCR status...",
896
+ ccrStatusTitle: "CCR Status:",
897
+ restartingCcr: "Restarting CCR...",
898
+ ccrRestarted: "CCR restarted",
899
+ startingCcr: "Starting CCR...",
900
+ ccrStarted: "CCR started",
901
+ stoppingCcr: "Stopping CCR...",
902
+ ccrStopped: "CCR stopped",
903
+ ccrCommandFailed: "Failed to execute CCR command",
904
+ // Configuration check messages
905
+ ccrNotConfigured: "CCR is not configured yet. Please initialize CCR first.",
906
+ pleaseInitFirst: "Please select option 1 to initialize CCR.",
907
+ // UI API Key messages
908
+ ccrUiApiKey: "CCR UI API Key",
909
+ ccrUiApiKeyHint: "Use this API key to login to CCR UI"
910
+ };
911
+
912
+ const updater = {
913
+ checkingVersion: "Checking version...",
914
+ checkingTools: "Checking tool versions",
915
+ ccrNotInstalled: "CCR is not installed",
916
+ ccrUpToDate: "CCR is up to date (v{version})",
917
+ claudeCodeNotInstalled: "Claude Code is not installed",
918
+ claudeCodeUpToDate: "Claude Code is up to date (v{version})",
919
+ cannotCheckVersion: "Cannot check latest version",
920
+ currentVersion: "Current version: v{version}",
921
+ latestVersion: "Latest version: v{version}",
922
+ confirmUpdate: "Update {tool} to the latest version?",
923
+ updateSkipped: "Update skipped",
924
+ updating: "Updating {tool}...",
925
+ updateSuccess: "{tool} updated successfully!",
926
+ updateFailed: "{tool} update failed",
927
+ checkFailed: "Version check failed"
928
+ };
929
+
659
930
  const en = {
660
- ...common,
661
- ...language,
662
- ...installation,
663
- ...api,
664
- ...configuration,
665
- ...mcp,
666
- ...menu,
667
- ...workflow,
668
- ...cli,
669
- ...bmad,
670
- ...errors,
671
- ...tools
931
+ common,
932
+ language,
933
+ installation,
934
+ api,
935
+ configuration,
936
+ mcp,
937
+ menu,
938
+ workflow,
939
+ cli,
940
+ bmad,
941
+ errors,
942
+ tools,
943
+ ccr: ccrMessages,
944
+ updater
672
945
  };
673
946
 
674
- const translations = {
947
+ const I18N$1 = {
675
948
  "zh-CN": zhCN,
676
949
  en
677
950
  };
951
+ function getTranslation(lang) {
952
+ return I18N$1[lang];
953
+ }
678
954
 
679
955
  const CLAUDE_DIR = join(homedir(), ".claude");
680
956
  const SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
@@ -692,7 +968,7 @@ const AI_OUTPUT_LANGUAGES = {
692
968
  en: { label: "English", directive: "Always respond in English" },
693
969
  custom: { label: "Custom", directive: "" }
694
970
  };
695
- const I18N = translations;
971
+ const I18N = I18N$1;
696
972
  const MCP_SERVICES = [
697
973
  {
698
974
  id: "context7",
@@ -763,6 +1039,21 @@ const MCP_SERVICES = [
763
1039
  }
764
1040
  ];
765
1041
 
1042
+ function addNumbersToChoices(choices, startFrom = 1, format = (n) => `${n}. `) {
1043
+ let currentNumber = startFrom;
1044
+ return choices.map((choice) => {
1045
+ if (choice.disabled) {
1046
+ return choice;
1047
+ }
1048
+ const numbered = {
1049
+ ...choice,
1050
+ name: `${format(currentNumber)}${choice.name}`
1051
+ };
1052
+ currentNumber++;
1053
+ return numbered;
1054
+ });
1055
+ }
1056
+
766
1057
  class FileSystemError extends Error {
767
1058
  constructor(message, path, cause) {
768
1059
  super(message);
@@ -874,8 +1165,8 @@ function readJsonConfig(path, options = {}) {
874
1165
  const content = readFile(path);
875
1166
  const data = JSON.parse(content);
876
1167
  if (validate && !validate(data)) {
877
- const i18n = I18N[readZcfConfig()?.preferredLang || "en"];
878
- console.warn(`${i18n.invalidConfiguration} (${path})`);
1168
+ const i18n = getTranslation(readZcfConfig()?.preferredLang || "en");
1169
+ console.warn(`${i18n.configuration.invalidConfiguration} (${path})`);
879
1170
  return defaultValue;
880
1171
  }
881
1172
  if (sanitize) {
@@ -883,8 +1174,8 @@ function readJsonConfig(path, options = {}) {
883
1174
  }
884
1175
  return data;
885
1176
  } catch (error) {
886
- const i18n = I18N[readZcfConfig()?.preferredLang || "en"];
887
- console.error(`${i18n.failedToParseJson} ${path}`, error);
1177
+ const i18n = getTranslation(readZcfConfig()?.preferredLang || "en");
1178
+ console.error(`${i18n.configuration.failedToParseJson} ${path}`, error);
888
1179
  return defaultValue;
889
1180
  }
890
1181
  }
@@ -909,8 +1200,8 @@ function backupJsonConfig(path, backupDir) {
909
1200
  copyFile(path, backupPath);
910
1201
  return backupPath;
911
1202
  } catch (error) {
912
- const i18n = I18N[readZcfConfig()?.preferredLang || "en"];
913
- console.error(i18n.failedToBackupConfig, error);
1203
+ const i18n = getTranslation(readZcfConfig()?.preferredLang || "en");
1204
+ console.error(i18n.configuration.failedToBackupConfig, error);
914
1205
  return null;
915
1206
  }
916
1207
  }
@@ -990,23 +1281,23 @@ function getPersonalityInfo(personalityId) {
990
1281
  return AI_PERSONALITIES.find((p) => p.id === personalityId);
991
1282
  }
992
1283
  async function configureAiPersonality(scriptLang, showExisting = true) {
993
- const i18n = I18N[scriptLang];
1284
+ const i18n = getTranslation(scriptLang);
994
1285
  const existingPersonality = getExistingPersonality();
995
1286
  if (showExisting && existingPersonality) {
996
1287
  const personalityInfo = getPersonalityInfo(existingPersonality);
997
1288
  if (personalityInfo) {
998
- console.log("\n" + ansis.blue(`\u2139 ${i18n.existingPersonality || "Existing AI personality configuration"}`));
1289
+ console.log("\n" + ansis.blue(`\u2139 ${i18n.configuration.existingPersonality || "Existing AI personality configuration"}`));
999
1290
  console.log(
1000
- ansis.gray(` ${i18n.currentPersonality || "Current personality"}: ${personalityInfo.name[scriptLang]}`)
1291
+ ansis.gray(` ${i18n.configuration.currentPersonality || "Current personality"}: ${personalityInfo.name[scriptLang]}`)
1001
1292
  );
1002
1293
  const { modify } = await inquirer.prompt({
1003
1294
  type: "confirm",
1004
1295
  name: "modify",
1005
- message: i18n.modifyPersonality || "Modify AI personality?",
1296
+ message: i18n.configuration.modifyPersonality || "Modify AI personality?",
1006
1297
  default: false
1007
1298
  });
1008
1299
  if (!modify) {
1009
- console.log(ansis.green(`\u2714 ${i18n.keepPersonality || "Keeping existing personality"}`));
1300
+ console.log(ansis.green(`\u2714 ${i18n.configuration.keepPersonality || "Keeping existing personality"}`));
1010
1301
  return;
1011
1302
  }
1012
1303
  }
@@ -1014,16 +1305,16 @@ async function configureAiPersonality(scriptLang, showExisting = true) {
1014
1305
  const { personality } = await inquirer.prompt({
1015
1306
  type: "list",
1016
1307
  name: "personality",
1017
- message: i18n.selectAiPersonality || "Select AI personality",
1018
- choices: AI_PERSONALITIES.map((p) => ({
1019
- name: p.id !== "custom" ? `${p.name[scriptLang]} - ${ansis.gray(p.directive[scriptLang].substring(0, 50) + "...")}` : `${p.name[scriptLang]} - ${ansis.gray(i18n.customPersonalityHint || "Define your own personality")}`,
1308
+ message: i18n.configuration.selectAiPersonality || "Select AI personality",
1309
+ choices: addNumbersToChoices(AI_PERSONALITIES.map((p) => ({
1310
+ name: p.id !== "custom" ? `${p.name[scriptLang]} - ${ansis.gray(p.directive[scriptLang].substring(0, 50) + "...")}` : `${p.name[scriptLang]} - ${ansis.gray(i18n.configuration.customPersonalityHint || "Define your own personality")}`,
1020
1311
  value: p.id,
1021
1312
  short: p.name[scriptLang]
1022
- })),
1313
+ }))),
1023
1314
  default: existingPersonality ? AI_PERSONALITIES.findIndex((p) => p.id === existingPersonality) : 0
1024
1315
  });
1025
1316
  if (!personality) {
1026
- console.log(ansis.yellow(i18n.cancelled));
1317
+ console.log(ansis.yellow(i18n.common.cancelled));
1027
1318
  return;
1028
1319
  }
1029
1320
  let directive = "";
@@ -1031,11 +1322,11 @@ async function configureAiPersonality(scriptLang, showExisting = true) {
1031
1322
  const { customDirective } = await inquirer.prompt({
1032
1323
  type: "input",
1033
1324
  name: "customDirective",
1034
- message: i18n.enterCustomPersonality || "Enter custom personality directive",
1035
- validate: (value) => !!value || i18n.directiveCannotBeEmpty
1325
+ message: i18n.configuration.enterCustomPersonality || "Enter custom personality directive",
1326
+ validate: (value) => !!value || i18n.configuration.directiveCannotBeEmpty
1036
1327
  });
1037
1328
  if (!customDirective) {
1038
- console.log(ansis.yellow(i18n.cancelled));
1329
+ console.log(ansis.yellow(i18n.common.cancelled));
1039
1330
  return;
1040
1331
  }
1041
1332
  directive = customDirective;
@@ -1047,14 +1338,16 @@ async function configureAiPersonality(scriptLang, showExisting = true) {
1047
1338
  }
1048
1339
  await applyPersonalityDirective(directive);
1049
1340
  updateZcfConfig({ aiPersonality: personality });
1050
- console.log(ansis.green(`\u2714 ${i18n.personalityConfigured || "AI personality configured"}`));
1341
+ console.log(ansis.green(`\u2714 ${i18n.configuration.personalityConfigured || "AI personality configured"}`));
1051
1342
  }
1052
1343
  async function applyPersonalityDirective(directive) {
1053
1344
  try {
1054
1345
  const personalityFile = join(CLAUDE_DIR, "personality.md");
1055
1346
  writeFile(personalityFile, directive);
1056
1347
  } catch (error) {
1057
- console.error(ansis.red(I18N[readZcfConfig()?.preferredLang || "en"].failedToApplyPersonality), error);
1348
+ const lang = readZcfConfig()?.preferredLang || "en";
1349
+ const errorI18n = getTranslation(lang);
1350
+ console.error(ansis.red(errorI18n.configuration.failedToApplyPersonality || "Failed to apply personality"), error);
1058
1351
  }
1059
1352
  }
1060
1353
 
@@ -1090,9 +1383,9 @@ function handleExitPromptError(error) {
1090
1383
  if (error instanceof Error && error.name === "ExitPromptError") {
1091
1384
  const zcfConfig = readZcfConfig();
1092
1385
  const defaultLang = zcfConfig?.preferredLang || "zh-CN";
1093
- const i18n = I18N[defaultLang];
1386
+ const i18n = getTranslation(defaultLang);
1094
1387
  console.log(ansis.cyan(`
1095
- ${i18n.goodbye}
1388
+ ${i18n.common.goodbye}
1096
1389
  `));
1097
1390
  process.exit(0);
1098
1391
  }
@@ -1101,7 +1394,8 @@ ${i18n.goodbye}
1101
1394
  function handleGeneralError(error, lang) {
1102
1395
  const zcfConfig = readZcfConfig();
1103
1396
  const defaultLang = lang || zcfConfig?.preferredLang || "en";
1104
- const errorMsg = I18N[defaultLang].error;
1397
+ const i18n = getTranslation(defaultLang);
1398
+ const errorMsg = i18n.common.error || "Error";
1105
1399
  console.error(ansis.red(`${errorMsg}:`), error);
1106
1400
  if (error instanceof Error) {
1107
1401
  console.error(ansis.gray(`Stack: ${error.stack}`));
@@ -1200,6 +1494,142 @@ function mergeAndCleanPermissions(templatePermissions, userPermissions) {
1200
1494
  return cleanupPermissions(template, user);
1201
1495
  }
1202
1496
 
1497
+ function getPlatform() {
1498
+ const p = platform();
1499
+ if (p === "win32") return "windows";
1500
+ if (p === "darwin") return "macos";
1501
+ return "linux";
1502
+ }
1503
+ function isTermux() {
1504
+ return !!(process.env.PREFIX && process.env.PREFIX.includes("com.termux")) || !!process.env.TERMUX_VERSION || existsSync("/data/data/com.termux/files/usr");
1505
+ }
1506
+ function getTermuxPrefix() {
1507
+ return process.env.PREFIX || "/data/data/com.termux/files/usr";
1508
+ }
1509
+ function isWindows() {
1510
+ return getPlatform() === "windows";
1511
+ }
1512
+ function getMcpCommand() {
1513
+ if (isWindows()) {
1514
+ return ["cmd", "/c", "npx"];
1515
+ }
1516
+ return ["npx"];
1517
+ }
1518
+ async function commandExists(command) {
1519
+ try {
1520
+ const cmd = getPlatform() === "windows" ? "where" : "which";
1521
+ const res = await exec(cmd, [command]);
1522
+ if (res.exitCode === 0) {
1523
+ return true;
1524
+ }
1525
+ } catch {
1526
+ }
1527
+ if (isTermux()) {
1528
+ const termuxPrefix = getTermuxPrefix();
1529
+ const possiblePaths = [
1530
+ `${termuxPrefix}/bin/${command}`,
1531
+ `${termuxPrefix}/usr/bin/${command}`,
1532
+ `/data/data/com.termux/files/usr/bin/${command}`
1533
+ ];
1534
+ for (const path of possiblePaths) {
1535
+ if (existsSync(path)) {
1536
+ return true;
1537
+ }
1538
+ }
1539
+ }
1540
+ if (getPlatform() !== "windows") {
1541
+ const commonPaths = [
1542
+ `/usr/local/bin/${command}`,
1543
+ `/usr/bin/${command}`,
1544
+ `/bin/${command}`,
1545
+ `${process.env.HOME}/.local/bin/${command}`
1546
+ ];
1547
+ for (const path of commonPaths) {
1548
+ if (existsSync(path)) {
1549
+ return true;
1550
+ }
1551
+ }
1552
+ }
1553
+ return false;
1554
+ }
1555
+
1556
+ function getMcpConfigPath() {
1557
+ return ClAUDE_CONFIG_FILE;
1558
+ }
1559
+ function readMcpConfig() {
1560
+ return readJsonConfig(ClAUDE_CONFIG_FILE);
1561
+ }
1562
+ function writeMcpConfig(config) {
1563
+ writeJsonConfig(ClAUDE_CONFIG_FILE, config);
1564
+ }
1565
+ function backupMcpConfig() {
1566
+ const backupBaseDir = join(CLAUDE_DIR, "backup");
1567
+ return backupJsonConfig(ClAUDE_CONFIG_FILE, backupBaseDir);
1568
+ }
1569
+ function mergeMcpServers(existing, newServers) {
1570
+ const config = existing || { mcpServers: {} };
1571
+ if (!config.mcpServers) {
1572
+ config.mcpServers = {};
1573
+ }
1574
+ Object.assign(config.mcpServers, newServers);
1575
+ return config;
1576
+ }
1577
+ function applyPlatformCommand(config) {
1578
+ if (config.command === "npx" && isWindows()) {
1579
+ const mcpCmd = getMcpCommand();
1580
+ config.command = mcpCmd[0];
1581
+ config.args = [...mcpCmd.slice(1), ...config.args || []];
1582
+ }
1583
+ }
1584
+ function buildMcpServerConfig(baseConfig, apiKey, placeholder = "YOUR_EXA_API_KEY", envVarName) {
1585
+ const config = deepClone(baseConfig);
1586
+ applyPlatformCommand(config);
1587
+ if (!apiKey) {
1588
+ return config;
1589
+ }
1590
+ if (envVarName && config.env) {
1591
+ config.env[envVarName] = apiKey;
1592
+ return config;
1593
+ }
1594
+ if (config.args) {
1595
+ config.args = config.args.map((arg) => arg.replace(placeholder, apiKey));
1596
+ }
1597
+ if (config.url) {
1598
+ config.url = config.url.replace(placeholder, apiKey);
1599
+ }
1600
+ return config;
1601
+ }
1602
+ function fixWindowsMcpConfig(config) {
1603
+ if (!isWindows() || !config.mcpServers) {
1604
+ return config;
1605
+ }
1606
+ const fixed = { ...config };
1607
+ for (const [, serverConfig] of Object.entries(fixed.mcpServers)) {
1608
+ if (serverConfig && typeof serverConfig === "object" && "command" in serverConfig) {
1609
+ applyPlatformCommand(serverConfig);
1610
+ }
1611
+ }
1612
+ return fixed;
1613
+ }
1614
+ function addCompletedOnboarding() {
1615
+ try {
1616
+ let config = readMcpConfig();
1617
+ if (!config) {
1618
+ config = { mcpServers: {} };
1619
+ }
1620
+ if (config.hasCompletedOnboarding === true) {
1621
+ return;
1622
+ }
1623
+ config.hasCompletedOnboarding = true;
1624
+ writeMcpConfig(config);
1625
+ } catch (error) {
1626
+ const lang = readZcfConfig()?.preferredLang || "en";
1627
+ const i18n = getTranslation(lang);
1628
+ console.error(i18n.configuration?.failedToAddOnboardingFlag || "Failed to add onboarding flag", error);
1629
+ throw error;
1630
+ }
1631
+ }
1632
+
1203
1633
  function ensureClaudeDir() {
1204
1634
  ensureDir(CLAUDE_DIR);
1205
1635
  }
@@ -1239,8 +1669,8 @@ function copyConfigFiles(lang, onlyMd = false) {
1239
1669
  function copyClaudeMemoryFiles(lang, rootDir) {
1240
1670
  const memorySourceDir = join(rootDir, "templates", lang, "memory");
1241
1671
  if (!exists(memorySourceDir)) {
1242
- const i18n = I18N[lang];
1243
- throw new Error(`${i18n.memoryDirNotFound || "Memory directory not found:"} ${memorySourceDir}`);
1672
+ const i18n = getTranslation(lang);
1673
+ throw new Error(`${i18n.configuration.memoryDirNotFound || "Memory directory not found:"} ${memorySourceDir}`);
1244
1674
  }
1245
1675
  const files = readDir(memorySourceDir);
1246
1676
  files?.forEach((file) => {
@@ -1259,7 +1689,9 @@ function getDefaultSettings() {
1259
1689
  const templateSettingsPath = join(rootDir, "templates", "settings.json");
1260
1690
  return readJsonConfig(templateSettingsPath) || {};
1261
1691
  } catch (error) {
1262
- console.error(I18N[readZcfConfig()?.preferredLang || "en"].failedToReadTemplateSettings, error);
1692
+ const lang = readZcfConfig()?.preferredLang || "en";
1693
+ const i18n = getTranslation(lang);
1694
+ console.error(i18n.configuration.failedToReadTemplateSettings || "Failed to read template settings", error);
1263
1695
  return {};
1264
1696
  }
1265
1697
  }
@@ -1284,6 +1716,13 @@ function configureApi(apiConfig) {
1284
1716
  settings.env.ANTHROPIC_BASE_URL = apiConfig.url;
1285
1717
  }
1286
1718
  writeJsonConfig(SETTINGS_FILE, settings);
1719
+ try {
1720
+ addCompletedOnboarding();
1721
+ } catch (error) {
1722
+ const lang = readZcfConfig()?.preferredLang || "en";
1723
+ const i18n = getTranslation(lang);
1724
+ console.error(i18n.configuration.failedToSetOnboarding || "Failed to set onboarding flag", error);
1725
+ }
1287
1726
  return apiConfig;
1288
1727
  }
1289
1728
  function mergeConfigs(sourceFile, targetFile) {
@@ -1299,14 +1738,20 @@ function updateDefaultModel(model) {
1299
1738
  if (existingSettings) {
1300
1739
  settings = existingSettings;
1301
1740
  }
1302
- settings.model = model;
1741
+ if (model === "default") {
1742
+ delete settings.model;
1743
+ } else {
1744
+ settings.model = model;
1745
+ }
1303
1746
  writeJsonConfig(SETTINGS_FILE, settings);
1304
1747
  }
1305
1748
  function mergeSettingsFile(templatePath, targetPath) {
1306
1749
  try {
1307
1750
  const templateSettings = readJsonConfig(templatePath);
1308
1751
  if (!templateSettings) {
1309
- console.error(I18N[readZcfConfig()?.preferredLang || "en"].failedToReadTemplateSettings);
1752
+ const lang = readZcfConfig()?.preferredLang || "en";
1753
+ const i18n = getTranslation(lang);
1754
+ console.error(i18n.configuration?.failedToReadTemplateSettings || "Failed to read template settings");
1310
1755
  return;
1311
1756
  }
1312
1757
  if (!exists(targetPath)) {
@@ -1333,14 +1778,28 @@ function mergeSettingsFile(templatePath, targetPath) {
1333
1778
  }
1334
1779
  writeJsonConfig(targetPath, mergedSettings);
1335
1780
  } catch (error) {
1336
- console.error(I18N[readZcfConfig()?.preferredLang || "en"].failedToMergeSettings, error);
1781
+ const lang = readZcfConfig()?.preferredLang || "en";
1782
+ const i18n = getTranslation(lang);
1783
+ console.error(i18n.configuration.failedToMergeSettings || "Failed to merge settings", error);
1337
1784
  if (exists(targetPath)) {
1338
- console.error(I18N[readZcfConfig()?.preferredLang || "en"].preservingExistingSettings);
1785
+ const lang2 = readZcfConfig()?.preferredLang || "en";
1786
+ const i18n2 = getTranslation(lang2);
1787
+ console.warn(i18n2.configuration.preservingExistingSettings || "Preserving existing settings");
1339
1788
  } else {
1340
1789
  copyFile(templatePath, targetPath);
1341
1790
  }
1342
1791
  }
1343
1792
  }
1793
+ function getExistingModelConfig() {
1794
+ const settings = readJsonConfig(SETTINGS_FILE);
1795
+ if (!settings) {
1796
+ return null;
1797
+ }
1798
+ if (!settings.model) {
1799
+ return "default";
1800
+ }
1801
+ return settings.model;
1802
+ }
1344
1803
  function getExistingApiConfig() {
1345
1804
  const settings = readJsonConfig(SETTINGS_FILE);
1346
1805
  if (!settings || !settings.env) {
@@ -1379,11 +1838,11 @@ function applyAiLanguageDirective(aiOutputLang) {
1379
1838
  }
1380
1839
 
1381
1840
  function validateApiKey(apiKey, lang = "zh-CN") {
1382
- const i18n = I18N[lang];
1841
+ const i18n = getTranslation(lang);
1383
1842
  if (!apiKey || apiKey.trim() === "") {
1384
1843
  return {
1385
1844
  isValid: false,
1386
- error: i18n.apiKeyValidation.empty
1845
+ error: i18n.api.apiKeyValidation.empty
1387
1846
  };
1388
1847
  }
1389
1848
  return { isValid: true };
@@ -1401,22 +1860,22 @@ async function configureApiCompletely(i18n, scriptLang, preselectedAuthType) {
1401
1860
  const { authType: selectedAuthType } = await inquirer.prompt({
1402
1861
  type: "list",
1403
1862
  name: "authType",
1404
- message: i18n.configureApi,
1405
- choices: [
1863
+ message: i18n.api.configureApi,
1864
+ choices: addNumbersToChoices([
1406
1865
  {
1407
- name: `${i18n.useAuthToken} - ${ansis.gray(i18n.authTokenDesc)}`,
1866
+ name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
1408
1867
  value: "auth_token",
1409
- short: i18n.useAuthToken
1868
+ short: i18n.api.useAuthToken
1410
1869
  },
1411
1870
  {
1412
- name: `${i18n.useApiKey} - ${ansis.gray(i18n.apiKeyDesc)}`,
1871
+ name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
1413
1872
  value: "api_key",
1414
- short: i18n.useApiKey
1873
+ short: i18n.api.useApiKey
1415
1874
  }
1416
- ]
1875
+ ])
1417
1876
  });
1418
1877
  if (!selectedAuthType) {
1419
- console.log(ansis.yellow(i18n.cancelled));
1878
+ console.log(ansis.yellow(i18n.common.cancelled));
1420
1879
  return null;
1421
1880
  }
1422
1881
  authType = selectedAuthType;
@@ -1424,39 +1883,39 @@ async function configureApiCompletely(i18n, scriptLang, preselectedAuthType) {
1424
1883
  const { url } = await inquirer.prompt({
1425
1884
  type: "input",
1426
1885
  name: "url",
1427
- message: i18n.enterApiUrl,
1886
+ message: i18n.api.enterApiUrl,
1428
1887
  validate: (value) => {
1429
- if (!value) return i18n.urlRequired;
1888
+ if (!value) return i18n.api.urlRequired;
1430
1889
  try {
1431
1890
  new URL(value);
1432
1891
  return true;
1433
1892
  } catch {
1434
- return i18n.invalidUrl;
1893
+ return i18n.api.invalidUrl;
1435
1894
  }
1436
1895
  }
1437
1896
  });
1438
1897
  if (url === void 0) {
1439
- console.log(ansis.yellow(i18n.cancelled));
1898
+ console.log(ansis.yellow(i18n.common.cancelled));
1440
1899
  return null;
1441
1900
  }
1442
- const keyMessage = authType === "auth_token" ? i18n.enterAuthToken : i18n.enterApiKey;
1901
+ const keyMessage = authType === "auth_token" ? i18n.api.enterAuthToken : i18n.api.enterApiKey;
1443
1902
  const { key } = await inquirer.prompt({
1444
1903
  type: "input",
1445
1904
  name: "key",
1446
1905
  message: keyMessage,
1447
1906
  validate: (value) => {
1448
1907
  if (!value) {
1449
- return i18n.keyRequired;
1908
+ return i18n.api.keyRequired;
1450
1909
  }
1451
1910
  const validation = validateApiKey(value, scriptLang);
1452
1911
  if (!validation.isValid) {
1453
- return validation.error || i18n.invalidKeyFormat;
1912
+ return validation.error || i18n.api.invalidKeyFormat;
1454
1913
  }
1455
1914
  return true;
1456
1915
  }
1457
1916
  });
1458
1917
  if (key === void 0) {
1459
- console.log(ansis.yellow(i18n.cancelled));
1918
+ console.log(ansis.yellow(i18n.common.cancelled));
1460
1919
  return null;
1461
1920
  }
1462
1921
  console.log(ansis.gray(` API Key: ${formatApiKeyDisplay(key)}`));
@@ -1471,276 +1930,356 @@ async function modifyApiConfigPartially(existingConfig, i18n, scriptLang) {
1471
1930
  const { item } = await inquirer.prompt({
1472
1931
  type: "list",
1473
1932
  name: "item",
1474
- message: i18n.selectModifyItems,
1475
- choices: [
1476
- { name: i18n.modifyApiUrl, value: "url" },
1477
- { name: i18n.modifyApiKey, value: "key" },
1478
- { name: i18n.modifyAuthType, value: "authType" }
1479
- ]
1933
+ message: i18n.api.selectModifyItems,
1934
+ choices: addNumbersToChoices([
1935
+ { name: i18n.api.modifyApiUrl, value: "url" },
1936
+ { name: i18n.api.modifyApiKey, value: "key" },
1937
+ { name: i18n.api.modifyAuthType, value: "authType" }
1938
+ ])
1480
1939
  });
1481
1940
  if (!item) {
1482
- console.log(ansis.yellow(i18n.cancelled));
1941
+ console.log(ansis.yellow(i18n.common.cancelled));
1483
1942
  return;
1484
1943
  }
1485
1944
  if (item === "url") {
1486
1945
  const { url } = await inquirer.prompt({
1487
1946
  type: "input",
1488
1947
  name: "url",
1489
- message: i18n.enterNewApiUrl.replace("{url}", currentConfig.url || i18n.none),
1948
+ message: i18n.api.enterNewApiUrl.replace("{url}", currentConfig.url || i18n.common.none),
1490
1949
  default: currentConfig.url,
1491
1950
  validate: (value) => {
1492
- if (!value) return i18n.urlRequired;
1951
+ if (!value) return i18n.api.urlRequired;
1493
1952
  try {
1494
1953
  new URL(value);
1495
1954
  return true;
1496
1955
  } catch {
1497
- return i18n.invalidUrl;
1956
+ return i18n.api.invalidUrl;
1498
1957
  }
1499
1958
  }
1500
1959
  });
1501
1960
  if (url === void 0) {
1502
- console.log(ansis.yellow(i18n.cancelled));
1961
+ console.log(ansis.yellow(i18n.common.cancelled));
1503
1962
  return;
1504
1963
  }
1505
1964
  currentConfig.url = url;
1506
1965
  const savedConfig = configureApi(currentConfig);
1507
1966
  if (savedConfig) {
1508
- console.log(ansis.green(`\u2714 ${i18n.modificationSaved}`));
1509
- console.log(ansis.gray(` ${i18n.apiConfigUrl}: ${savedConfig.url}`));
1967
+ console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
1968
+ console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${savedConfig.url}`));
1510
1969
  }
1511
1970
  } else if (item === "key") {
1512
1971
  const authType = currentConfig.authType || "auth_token";
1513
- const keyMessage = authType === "auth_token" ? i18n.enterNewApiKey.replace("{key}", currentConfig.key ? formatApiKeyDisplay(currentConfig.key) : i18n.none) : i18n.enterNewApiKey.replace("{key}", currentConfig.key ? formatApiKeyDisplay(currentConfig.key) : i18n.none);
1972
+ 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);
1514
1973
  const { key } = await inquirer.prompt({
1515
1974
  type: "input",
1516
1975
  name: "key",
1517
1976
  message: keyMessage,
1518
1977
  validate: (value) => {
1519
1978
  if (!value) {
1520
- return i18n.keyRequired;
1979
+ return i18n.api.keyRequired;
1521
1980
  }
1522
1981
  const validation = validateApiKey(value, scriptLang);
1523
1982
  if (!validation.isValid) {
1524
- return validation.error || i18n.invalidKeyFormat;
1983
+ return validation.error || i18n.api.invalidKeyFormat;
1525
1984
  }
1526
1985
  return true;
1527
1986
  }
1528
1987
  });
1529
1988
  if (key === void 0) {
1530
- console.log(ansis.yellow(i18n.cancelled));
1989
+ console.log(ansis.yellow(i18n.common.cancelled));
1531
1990
  return;
1532
1991
  }
1533
1992
  currentConfig.key = key;
1534
1993
  const savedConfig = configureApi(currentConfig);
1535
1994
  if (savedConfig) {
1536
- console.log(ansis.green(`\u2714 ${i18n.modificationSaved}`));
1537
- console.log(ansis.gray(` ${i18n.apiConfigKey}: ${formatApiKeyDisplay(savedConfig.key)}`));
1995
+ console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
1996
+ console.log(ansis.gray(` ${i18n.api.apiConfigKey}: ${formatApiKeyDisplay(savedConfig.key)}`));
1538
1997
  }
1539
1998
  } else if (item === "authType") {
1540
1999
  const { authType } = await inquirer.prompt({
1541
2000
  type: "list",
1542
2001
  name: "authType",
1543
- message: i18n.selectNewAuthType.replace("{type}", currentConfig.authType || i18n.none),
1544
- choices: [
2002
+ message: i18n.api.selectNewAuthType.replace("{type}", currentConfig.authType || i18n.common.none),
2003
+ choices: addNumbersToChoices([
1545
2004
  { name: "Auth Token (OAuth)", value: "auth_token" },
1546
2005
  { name: "API Key", value: "api_key" }
1547
- ],
2006
+ ]),
1548
2007
  default: currentConfig.authType === "api_key" ? 1 : 0
1549
2008
  });
1550
2009
  if (authType === void 0) {
1551
- console.log(ansis.yellow(i18n.cancelled));
2010
+ console.log(ansis.yellow(i18n.common.cancelled));
1552
2011
  return;
1553
2012
  }
1554
2013
  currentConfig.authType = authType;
1555
2014
  const savedConfig = configureApi(currentConfig);
1556
2015
  if (savedConfig) {
1557
- console.log(ansis.green(`\u2714 ${i18n.modificationSaved}`));
1558
- console.log(ansis.gray(` ${i18n.apiConfigAuthType}: ${savedConfig.authType}`));
2016
+ console.log(ansis.green(`\u2714 ${i18n.api.modificationSaved}`));
2017
+ console.log(ansis.gray(` ${i18n.api.apiConfigAuthType}: ${savedConfig.authType}`));
1559
2018
  }
1560
2019
  }
1561
2020
  }
1562
2021
  async function updatePromptOnly(configLang, scriptLang, aiOutputLang) {
1563
- const i18n = I18N[scriptLang];
2022
+ const i18n = getTranslation(scriptLang);
1564
2023
  const backupDir = backupExistingConfig();
1565
2024
  if (backupDir) {
1566
- console.log(ansis.gray(`\u2714 ${i18n.backupSuccess}: ${backupDir}`));
2025
+ console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
1567
2026
  }
1568
2027
  copyConfigFiles(configLang, true);
1569
2028
  if (aiOutputLang) {
1570
2029
  applyAiLanguageDirective(aiOutputLang);
1571
2030
  }
1572
2031
  await configureAiPersonality(scriptLang);
1573
- console.log(ansis.green(`\u2714 ${i18n.configSuccess} ${CLAUDE_DIR}`));
1574
- console.log("\n" + ansis.cyan(i18n.complete));
2032
+ console.log(ansis.green(`\u2714 ${i18n.configuration.configSuccess} ${CLAUDE_DIR}`));
2033
+ console.log("\n" + ansis.cyan(i18n.common.complete));
1575
2034
  }
1576
2035
 
1577
- function getPlatform() {
1578
- const p = platform();
1579
- if (p === "win32") return "windows";
1580
- if (p === "darwin") return "macos";
1581
- return "linux";
2036
+ function format(template, replacements) {
2037
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
2038
+ return replacements[key] || match;
2039
+ });
1582
2040
  }
1583
- function isTermux() {
1584
- return !!(process.env.PREFIX && process.env.PREFIX.includes("com.termux")) || !!process.env.TERMUX_VERSION || existsSync("/data/data/com.termux/files/usr");
2041
+
2042
+ const execAsync$3 = promisify(exec$1);
2043
+ const CACHE_DIR = join$1(homedir$1(), ".claude", "cache");
2044
+ const CACHE_FILE = join$1(CACHE_DIR, "version-cache.json");
2045
+ const CACHE_DURATION = 24 * 60 * 60 * 1e3;
2046
+ async function ensureCacheDir() {
2047
+ try {
2048
+ await mkdir(CACHE_DIR, { recursive: true });
2049
+ } catch (error) {
2050
+ }
1585
2051
  }
1586
- function getTermuxPrefix() {
1587
- return process.env.PREFIX || "/data/data/com.termux/files/usr";
2052
+ async function readCache() {
2053
+ try {
2054
+ const content = await readFile$1(CACHE_FILE, "utf-8");
2055
+ return JSON.parse(content);
2056
+ } catch {
2057
+ return {};
2058
+ }
1588
2059
  }
1589
- function isWindows() {
1590
- return getPlatform() === "windows";
2060
+ async function writeCache(cache) {
2061
+ await ensureCacheDir();
2062
+ await writeFile$1(CACHE_FILE, JSON.stringify(cache, null, 2));
1591
2063
  }
1592
- function getMcpCommand() {
1593
- if (isWindows()) {
1594
- return ["cmd", "/c", "npx"];
2064
+ async function getInstalledVersion(command) {
2065
+ try {
2066
+ let stdout;
2067
+ try {
2068
+ const result = await execAsync$3(`${command} -v`);
2069
+ stdout = result.stdout;
2070
+ } catch {
2071
+ const result = await execAsync$3(`${command} --version`);
2072
+ stdout = result.stdout;
2073
+ }
2074
+ const versionMatch = stdout.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
2075
+ return versionMatch ? versionMatch[1] : null;
2076
+ } catch {
2077
+ return null;
1595
2078
  }
1596
- return ["npx"];
1597
2079
  }
1598
- async function commandExists(command) {
1599
- try {
1600
- const cmd = getPlatform() === "windows" ? "where" : "which";
1601
- const res = await exec(cmd, [command]);
1602
- if (res.exitCode === 0) {
1603
- return true;
2080
+ async function getLatestVersion(packageName, useCache = true) {
2081
+ if (useCache) {
2082
+ const cache = await readCache();
2083
+ const cached = cache[packageName];
2084
+ if (cached && Date.now() - cached.checkedAt < CACHE_DURATION) {
2085
+ return cached.version;
1604
2086
  }
2087
+ }
2088
+ try {
2089
+ const { stdout } = await execAsync$3(`npm view ${packageName} version`);
2090
+ const version = stdout.trim();
2091
+ const cache = await readCache();
2092
+ cache[packageName] = {
2093
+ version,
2094
+ checkedAt: Date.now()
2095
+ };
2096
+ await writeCache(cache);
2097
+ return version;
1605
2098
  } catch {
2099
+ return null;
1606
2100
  }
1607
- if (isTermux()) {
1608
- const termuxPrefix = getTermuxPrefix();
1609
- const possiblePaths = [
1610
- `${termuxPrefix}/bin/${command}`,
1611
- `${termuxPrefix}/usr/bin/${command}`,
1612
- `/data/data/com.termux/files/usr/bin/${command}`
1613
- ];
1614
- for (const path of possiblePaths) {
1615
- if (existsSync(path)) {
1616
- return true;
2101
+ }
2102
+ function compareVersions(current, latest) {
2103
+ if (!semver.valid(current) || !semver.valid(latest)) {
2104
+ return -1;
2105
+ }
2106
+ return semver.compare(current, latest);
2107
+ }
2108
+ function shouldUpdate(current, latest) {
2109
+ return compareVersions(current, latest) < 0;
2110
+ }
2111
+ async function checkCcrVersion() {
2112
+ const currentVersion = await getInstalledVersion("ccr");
2113
+ let latestVersion = await getLatestVersion("@musistudio/claude-code-router");
2114
+ if (!latestVersion) {
2115
+ latestVersion = await getLatestVersion("claude-code-router");
2116
+ }
2117
+ return {
2118
+ installed: currentVersion !== null,
2119
+ currentVersion,
2120
+ latestVersion,
2121
+ needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
2122
+ };
2123
+ }
2124
+ async function checkClaudeCodeVersion() {
2125
+ const currentVersion = await getInstalledVersion("claude");
2126
+ const latestVersion = await getLatestVersion("@anthropic-ai/claude-code");
2127
+ return {
2128
+ installed: currentVersion !== null,
2129
+ currentVersion,
2130
+ latestVersion,
2131
+ needsUpdate: currentVersion && latestVersion ? shouldUpdate(currentVersion, latestVersion) : false
2132
+ };
2133
+ }
2134
+
2135
+ const execAsync$2 = promisify(exec$1);
2136
+ async function updateCcr(scriptLang, force = false) {
2137
+ const i18n = getTranslation(scriptLang);
2138
+ const spinner = ora(i18n.updater.checkingVersion).start();
2139
+ try {
2140
+ const { installed, currentVersion, latestVersion, needsUpdate } = await checkCcrVersion();
2141
+ spinner.stop();
2142
+ if (!installed) {
2143
+ console.log(ansis.yellow(i18n.updater.ccrNotInstalled));
2144
+ return false;
2145
+ }
2146
+ if (!needsUpdate && !force) {
2147
+ console.log(ansis.green(format(i18n.updater.ccrUpToDate, { version: currentVersion || "" })));
2148
+ return true;
2149
+ }
2150
+ if (!latestVersion) {
2151
+ console.log(ansis.yellow(i18n.updater.cannotCheckVersion));
2152
+ return false;
2153
+ }
2154
+ console.log(ansis.cyan(format(i18n.updater.currentVersion, { version: currentVersion || "" })));
2155
+ console.log(ansis.cyan(format(i18n.updater.latestVersion, { version: latestVersion })));
2156
+ const { confirm } = await prompts$1({
2157
+ type: "confirm",
2158
+ name: "confirm",
2159
+ message: format(i18n.updater.confirmUpdate, { tool: "CCR" }),
2160
+ initial: true
2161
+ });
2162
+ if (!confirm) {
2163
+ console.log(ansis.gray(i18n.updater.updateSkipped));
2164
+ return true;
2165
+ }
2166
+ const updateSpinner = ora(format(i18n.updater.updating, { tool: "CCR" })).start();
2167
+ try {
2168
+ try {
2169
+ await execAsync$2("npm update -g @musistudio/claude-code-router");
2170
+ } catch {
2171
+ await execAsync$2("npm update -g claude-code-router");
1617
2172
  }
2173
+ updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "CCR" }));
2174
+ return true;
2175
+ } catch (error) {
2176
+ updateSpinner.fail(format(i18n.updater.updateFailed, { tool: "CCR" }));
2177
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2178
+ return false;
1618
2179
  }
2180
+ } catch (error) {
2181
+ spinner.fail(i18n.updater.checkFailed);
2182
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2183
+ return false;
1619
2184
  }
1620
- if (getPlatform() !== "windows") {
1621
- const commonPaths = [
1622
- `/usr/local/bin/${command}`,
1623
- `/usr/bin/${command}`,
1624
- `/bin/${command}`,
1625
- `${process.env.HOME}/.local/bin/${command}`
1626
- ];
1627
- for (const path of commonPaths) {
1628
- if (existsSync(path)) {
1629
- return true;
1630
- }
2185
+ }
2186
+ async function updateClaudeCode(scriptLang, force = false) {
2187
+ const i18n = getTranslation(scriptLang);
2188
+ const spinner = ora(i18n.updater.checkingVersion).start();
2189
+ try {
2190
+ const { installed, currentVersion, latestVersion, needsUpdate } = await checkClaudeCodeVersion();
2191
+ spinner.stop();
2192
+ if (!installed) {
2193
+ console.log(ansis.yellow(i18n.updater.claudeCodeNotInstalled));
2194
+ return false;
2195
+ }
2196
+ if (!needsUpdate && !force) {
2197
+ console.log(ansis.green(format(i18n.updater.claudeCodeUpToDate, { version: currentVersion || "" })));
2198
+ return true;
2199
+ }
2200
+ if (!latestVersion) {
2201
+ console.log(ansis.yellow(i18n.updater.cannotCheckVersion));
2202
+ return false;
2203
+ }
2204
+ console.log(ansis.cyan(format(i18n.updater.currentVersion, { version: currentVersion || "" })));
2205
+ console.log(ansis.cyan(format(i18n.updater.latestVersion, { version: latestVersion })));
2206
+ const { confirm } = await prompts$1({
2207
+ type: "confirm",
2208
+ name: "confirm",
2209
+ message: format(i18n.updater.confirmUpdate, { tool: "Claude Code" }),
2210
+ initial: true
2211
+ });
2212
+ if (!confirm) {
2213
+ console.log(ansis.gray(i18n.updater.updateSkipped));
2214
+ return true;
1631
2215
  }
2216
+ const updateSpinner = ora(format(i18n.updater.updating, { tool: "Claude Code" })).start();
2217
+ try {
2218
+ await execAsync$2("npm update -g @anthropic-ai/claude-code");
2219
+ updateSpinner.succeed(format(i18n.updater.updateSuccess, { tool: "Claude Code" }));
2220
+ return true;
2221
+ } catch (error) {
2222
+ updateSpinner.fail(format(i18n.updater.updateFailed, { tool: "Claude Code" }));
2223
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2224
+ return false;
2225
+ }
2226
+ } catch (error) {
2227
+ spinner.fail(i18n.updater.checkFailed);
2228
+ console.error(ansis.red(error instanceof Error ? error.message : String(error)));
2229
+ return false;
1632
2230
  }
1633
- return false;
2231
+ }
2232
+ async function checkAndUpdateTools(scriptLang) {
2233
+ const i18n = getTranslation(scriptLang);
2234
+ console.log(ansis.bold.cyan(`
2235
+ \u{1F50D} ${i18n.updater.checkingTools}
2236
+ `));
2237
+ await updateCcr(scriptLang);
2238
+ console.log();
2239
+ await updateClaudeCode(scriptLang);
1634
2240
  }
1635
2241
 
1636
2242
  async function isClaudeCodeInstalled() {
1637
2243
  return await commandExists("claude");
1638
2244
  }
1639
2245
  async function installClaudeCode(lang) {
1640
- const i18n = I18N[lang];
2246
+ const i18n = getTranslation(lang);
2247
+ const installed = await isClaudeCodeInstalled();
2248
+ if (installed) {
2249
+ console.log(ansis.green(`\u2714 ${i18n.installation.alreadyInstalled}`));
2250
+ await updateClaudeCode(lang);
2251
+ return;
2252
+ }
1641
2253
  if (isTermux()) {
1642
- console.log(ansis.yellow(`\u2139 ${i18n.termuxDetected}`));
2254
+ console.log(ansis.yellow(`\u2139 ${i18n.installation.termuxDetected}`));
1643
2255
  const termuxPrefix = getTermuxPrefix();
1644
- console.log(ansis.gray(i18n.termuxPathInfo.replace("{path}", termuxPrefix)));
2256
+ console.log(ansis.gray(i18n.installation.termuxPathInfo.replace("{path}", termuxPrefix)));
1645
2257
  console.log(ansis.gray(`Node.js: ${termuxPrefix}/bin/node`));
1646
2258
  console.log(ansis.gray(`npm: ${termuxPrefix}/bin/npm`));
1647
2259
  }
1648
- console.log(i18n.installing);
2260
+ console.log(i18n.installation.installing);
1649
2261
  try {
1650
2262
  await exec("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
1651
- console.log(`\u2714 ${i18n.installSuccess}`);
2263
+ console.log(`\u2714 ${i18n.installation.installSuccess}`);
1652
2264
  if (isTermux()) {
1653
2265
  console.log(ansis.gray(`
1654
2266
  Claude Code installed to: ${getTermuxPrefix()}/bin/claude`));
1655
2267
  }
1656
2268
  } catch (error) {
1657
- console.error(`\u2716 ${i18n.installFailed}`);
2269
+ console.error(`\u2716 ${i18n.installation.installFailed}`);
1658
2270
  if (isTermux()) {
1659
2271
  console.error(ansis.yellow(`
1660
- ${i18n.termuxInstallHint}
2272
+ ${i18n.installation.termuxInstallHint}
1661
2273
  `));
1662
2274
  }
1663
2275
  throw error;
1664
2276
  }
1665
2277
  }
1666
2278
 
1667
- function getMcpConfigPath() {
1668
- return ClAUDE_CONFIG_FILE;
1669
- }
1670
- function readMcpConfig() {
1671
- return readJsonConfig(ClAUDE_CONFIG_FILE);
1672
- }
1673
- function writeMcpConfig(config) {
1674
- writeJsonConfig(ClAUDE_CONFIG_FILE, config);
1675
- }
1676
- function backupMcpConfig() {
1677
- const backupBaseDir = join(CLAUDE_DIR, "backup");
1678
- return backupJsonConfig(ClAUDE_CONFIG_FILE, backupBaseDir);
1679
- }
1680
- function mergeMcpServers(existing, newServers) {
1681
- const config = existing || { mcpServers: {} };
1682
- if (!config.mcpServers) {
1683
- config.mcpServers = {};
1684
- }
1685
- Object.assign(config.mcpServers, newServers);
1686
- return config;
1687
- }
1688
- function applyPlatformCommand(config) {
1689
- if (config.command === "npx" && isWindows()) {
1690
- const mcpCmd = getMcpCommand();
1691
- config.command = mcpCmd[0];
1692
- config.args = [...mcpCmd.slice(1), ...config.args || []];
1693
- }
1694
- }
1695
- function buildMcpServerConfig(baseConfig, apiKey, placeholder = "YOUR_EXA_API_KEY", envVarName) {
1696
- const config = deepClone(baseConfig);
1697
- applyPlatformCommand(config);
1698
- if (!apiKey) {
1699
- return config;
1700
- }
1701
- if (envVarName && config.env) {
1702
- config.env[envVarName] = apiKey;
1703
- return config;
1704
- }
1705
- if (config.args) {
1706
- config.args = config.args.map((arg) => arg.replace(placeholder, apiKey));
1707
- }
1708
- if (config.url) {
1709
- config.url = config.url.replace(placeholder, apiKey);
1710
- }
1711
- return config;
1712
- }
1713
- function fixWindowsMcpConfig(config) {
1714
- if (!isWindows() || !config.mcpServers) {
1715
- return config;
1716
- }
1717
- const fixed = { ...config };
1718
- for (const [, serverConfig] of Object.entries(fixed.mcpServers)) {
1719
- if (serverConfig && typeof serverConfig === "object" && "command" in serverConfig) {
1720
- applyPlatformCommand(serverConfig);
1721
- }
1722
- }
1723
- return fixed;
1724
- }
1725
- function addCompletedOnboarding() {
1726
- try {
1727
- let config = readMcpConfig();
1728
- if (!config) {
1729
- config = { mcpServers: {} };
1730
- }
1731
- config.hasCompletedOnboarding = true;
1732
- config = fixWindowsMcpConfig(config);
1733
- writeMcpConfig(config);
1734
- } catch (error) {
1735
- console.error(I18N[readZcfConfig()?.preferredLang || "en"].failedToAddOnboardingFlag, error);
1736
- throw error;
1737
- }
1738
- }
1739
-
1740
2279
  async function selectAiOutputLanguage(scriptLang, defaultLang) {
1741
- const i18n = I18N[scriptLang];
2280
+ const i18n = getTranslation(scriptLang);
1742
2281
  console.log(ansis.dim(`
1743
- ${i18n.aiOutputLangHint}
2282
+ ${i18n.language.aiOutputLangHint}
1744
2283
  `));
1745
2284
  const aiLangChoices = Object.entries(AI_OUTPUT_LANGUAGES).map(([key, value]) => ({
1746
2285
  title: value.label,
@@ -1750,15 +2289,15 @@ async function selectAiOutputLanguage(scriptLang, defaultLang) {
1750
2289
  const { lang } = await inquirer.prompt({
1751
2290
  type: "list",
1752
2291
  name: "lang",
1753
- message: i18n.selectAiOutputLang,
1754
- choices: aiLangChoices.map((choice) => ({
2292
+ message: i18n.language.selectAiOutputLang,
2293
+ choices: addNumbersToChoices(aiLangChoices.map((choice) => ({
1755
2294
  name: choice.title,
1756
2295
  value: choice.value
1757
- })),
2296
+ }))),
1758
2297
  default: defaultChoice
1759
2298
  });
1760
2299
  if (!lang) {
1761
- console.log(ansis.yellow(i18n.cancelled));
2300
+ console.log(ansis.yellow(i18n.common.cancelled));
1762
2301
  process.exit(0);
1763
2302
  }
1764
2303
  let aiOutputLang = lang;
@@ -1766,11 +2305,11 @@ async function selectAiOutputLanguage(scriptLang, defaultLang) {
1766
2305
  const { customLang } = await inquirer.prompt({
1767
2306
  type: "input",
1768
2307
  name: "customLang",
1769
- message: i18n.enterCustomLanguage,
1770
- validate: (value) => !!value || i18n.languageRequired
2308
+ message: i18n.language.enterCustomLanguage,
2309
+ validate: (value) => !!value || i18n.language?.languageRequired || "Language is required"
1771
2310
  });
1772
2311
  if (!customLang) {
1773
- console.log(ansis.yellow(i18n.cancelled));
2312
+ console.log(ansis.yellow(i18n.common.cancelled));
1774
2313
  process.exit(0);
1775
2314
  }
1776
2315
  return customLang;
@@ -1789,10 +2328,10 @@ async function selectScriptLanguage(currentLang) {
1789
2328
  type: "list",
1790
2329
  name: "lang",
1791
2330
  message: "Select ZCF display language / \u9009\u62E9ZCF\u663E\u793A\u8BED\u8A00",
1792
- choices: SUPPORTED_LANGS.map((l) => ({
2331
+ choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
1793
2332
  name: LANG_LABELS[l],
1794
2333
  value: l
1795
- }))
2334
+ })))
1796
2335
  });
1797
2336
  if (!lang) {
1798
2337
  console.log(ansis.yellow("Operation cancelled / \u64CD\u4F5C\u5DF2\u53D6\u6D88"));
@@ -1806,19 +2345,26 @@ async function selectScriptLanguage(currentLang) {
1806
2345
  return scriptLang;
1807
2346
  }
1808
2347
  async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfig) {
1809
- const i18n = I18N[scriptLang];
2348
+ const i18n = getTranslation(scriptLang);
1810
2349
  if (commandLineOption) {
1811
2350
  return commandLineOption;
1812
2351
  }
1813
2352
  if (savedConfig?.aiOutputLang) {
1814
- console.log(ansis.gray(`\u2714 ${i18n.aiOutputLangHint}: ${savedConfig.aiOutputLang}`));
2353
+ console.log(ansis.gray(`\u2714 ${i18n.language.aiOutputLangHint}: ${savedConfig.aiOutputLang}`));
1815
2354
  return savedConfig.aiOutputLang;
1816
2355
  }
1817
2356
  return await selectAiOutputLanguage(scriptLang, scriptLang);
1818
2357
  }
1819
2358
 
2359
+ const prompts = {
2360
+ __proto__: null,
2361
+ resolveAiOutputLanguage: resolveAiOutputLanguage,
2362
+ selectAiOutputLanguage: selectAiOutputLanguage,
2363
+ selectScriptLanguage: selectScriptLanguage
2364
+ };
2365
+
1820
2366
  async function selectMcpServices(scriptLang) {
1821
- const i18n = I18N[scriptLang];
2367
+ const i18n = getTranslation(scriptLang);
1822
2368
  const choices = MCP_SERVICES.map((service) => ({
1823
2369
  name: `${service.name[scriptLang]} - ${ansis.gray(service.description[scriptLang])}`,
1824
2370
  value: service.id,
@@ -1827,11 +2373,11 @@ async function selectMcpServices(scriptLang) {
1827
2373
  const { services } = await inquirer.prompt({
1828
2374
  type: "checkbox",
1829
2375
  name: "services",
1830
- message: `${i18n.selectMcpServices}${i18n.multiSelectHint}`,
2376
+ message: `${i18n.mcp.selectMcpServices}${i18n.common.multiSelectHint}`,
1831
2377
  choices
1832
2378
  });
1833
2379
  if (services === void 0) {
1834
- console.log(ansis.yellow(i18n.cancelled));
2380
+ console.log(ansis.yellow(i18n.common.cancelled));
1835
2381
  return void 0;
1836
2382
  }
1837
2383
  return services;
@@ -1891,11 +2437,11 @@ function getRootDir() {
1891
2437
  return dirname(distDir);
1892
2438
  }
1893
2439
  async function selectAndInstallWorkflows(configLang, scriptLang) {
1894
- const i18n = I18N[scriptLang];
2440
+ const i18n = getTranslation(scriptLang);
1895
2441
  const workflows = getOrderedWorkflows();
1896
2442
  const choices = workflows.map((workflow) => {
1897
2443
  const nameKey = workflow.id;
1898
- const name = i18n.workflowOption[nameKey] || workflow.id;
2444
+ const name = i18n.workflow.workflowOption[nameKey] || workflow.id;
1899
2445
  return {
1900
2446
  name,
1901
2447
  value: workflow.id,
@@ -1905,11 +2451,11 @@ async function selectAndInstallWorkflows(configLang, scriptLang) {
1905
2451
  const { selectedWorkflows } = await inquirer.prompt({
1906
2452
  type: "checkbox",
1907
2453
  name: "selectedWorkflows",
1908
- message: `${i18n.selectWorkflowType}${i18n.multiSelectHint}`,
2454
+ message: `${i18n.workflow.selectWorkflowType}${i18n.common.multiSelectHint}`,
1909
2455
  choices
1910
2456
  });
1911
2457
  if (!selectedWorkflows || selectedWorkflows.length === 0) {
1912
- console.log(ansis.yellow(i18n.cancelled));
2458
+ console.log(ansis.yellow(i18n.common.cancelled));
1913
2459
  return;
1914
2460
  }
1915
2461
  await cleanupOldVersionFiles(scriptLang);
@@ -1922,7 +2468,7 @@ async function selectAndInstallWorkflows(configLang, scriptLang) {
1922
2468
  }
1923
2469
  async function installWorkflowWithDependencies(config, configLang, scriptLang) {
1924
2470
  const rootDir = getRootDir();
1925
- const i18n = I18N[scriptLang];
2471
+ const i18n = getTranslation(scriptLang);
1926
2472
  const result = {
1927
2473
  workflow: config.id,
1928
2474
  success: true,
@@ -1930,12 +2476,12 @@ async function installWorkflowWithDependencies(config, configLang, scriptLang) {
1930
2476
  installedAgents: [],
1931
2477
  errors: []
1932
2478
  };
1933
- const workflowName = i18n.workflowOption[config.id] || config.id;
2479
+ const workflowName = i18n.workflow.workflowOption[config.id] || config.id;
1934
2480
  console.log(ansis.cyan(`
1935
- \u{1F4E6} ${i18n.installingWorkflow}: ${workflowName}...`));
2481
+ \u{1F4E6} ${i18n.workflow.installingWorkflow}: ${workflowName}...`));
1936
2482
  const commandsDir = join(CLAUDE_DIR, "commands", "zcf");
1937
2483
  if (!existsSync(commandsDir)) {
1938
- await mkdir(commandsDir, { recursive: true });
2484
+ await mkdir$1(commandsDir, { recursive: true });
1939
2485
  }
1940
2486
  for (const commandFile of config.commands) {
1941
2487
  const commandSource = join(rootDir, "templates", configLang, "workflow", config.category, "commands", commandFile);
@@ -1945,9 +2491,9 @@ async function installWorkflowWithDependencies(config, configLang, scriptLang) {
1945
2491
  try {
1946
2492
  await copyFile$1(commandSource, commandDest);
1947
2493
  result.installedCommands.push(destFileName);
1948
- console.log(ansis.gray(` \u2714 ${i18n.installedCommand}: zcf/${destFileName}`));
2494
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.installedCommand}: zcf/${destFileName}`));
1949
2495
  } catch (error) {
1950
- const errorMsg = `${i18n.failedToInstallCommand} ${commandFile}: ${error}`;
2496
+ const errorMsg = `${i18n.workflow.failedToInstallCommand} ${commandFile}: ${error}`;
1951
2497
  result.errors?.push(errorMsg);
1952
2498
  console.error(ansis.red(` \u2717 ${errorMsg}`));
1953
2499
  result.success = false;
@@ -1957,7 +2503,7 @@ async function installWorkflowWithDependencies(config, configLang, scriptLang) {
1957
2503
  if (config.autoInstallAgents && config.agents.length > 0) {
1958
2504
  const agentsCategoryDir = join(CLAUDE_DIR, "agents", "zcf", config.category);
1959
2505
  if (!existsSync(agentsCategoryDir)) {
1960
- await mkdir(agentsCategoryDir, { recursive: true });
2506
+ await mkdir$1(agentsCategoryDir, { recursive: true });
1961
2507
  }
1962
2508
  for (const agent of config.agents) {
1963
2509
  const agentSource = join(rootDir, "templates", configLang, "workflow", config.category, "agents", agent.filename);
@@ -1966,9 +2512,9 @@ async function installWorkflowWithDependencies(config, configLang, scriptLang) {
1966
2512
  try {
1967
2513
  await copyFile$1(agentSource, agentDest);
1968
2514
  result.installedAgents.push(agent.filename);
1969
- console.log(ansis.gray(` \u2714 ${i18n.installedAgent}: zcf/${config.category}/${agent.filename}`));
2515
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.installedAgent}: zcf/${config.category}/${agent.filename}`));
1970
2516
  } catch (error) {
1971
- const errorMsg = `${i18n.failedToInstallAgent} ${agent.filename}: ${error}`;
2517
+ const errorMsg = `${i18n.workflow.failedToInstallAgent} ${agent.filename}: ${error}`;
1972
2518
  result.errors?.push(errorMsg);
1973
2519
  console.error(ansis.red(` \u2717 ${errorMsg}`));
1974
2520
  if (agent.required) {
@@ -1979,20 +2525,20 @@ async function installWorkflowWithDependencies(config, configLang, scriptLang) {
1979
2525
  }
1980
2526
  }
1981
2527
  if (result.success) {
1982
- console.log(ansis.green(`\u2714 ${workflowName} ${i18n.workflowInstallSuccess}`));
2528
+ console.log(ansis.green(`\u2714 ${workflowName} ${i18n.workflow.workflowInstallSuccess}`));
1983
2529
  if (config.id === "bmadWorkflow") {
1984
2530
  console.log(ansis.cyan(`
1985
- ${i18n.bmadInitPrompt}`));
2531
+ ${i18n.bmad.bmadInitPrompt}`));
1986
2532
  }
1987
2533
  } else {
1988
- console.log(ansis.red(`\u2717 ${workflowName} ${i18n.workflowInstallError}`));
2534
+ console.log(ansis.red(`\u2717 ${workflowName} ${i18n.workflow.workflowInstallError}`));
1989
2535
  }
1990
2536
  return result;
1991
2537
  }
1992
2538
  async function cleanupOldVersionFiles(scriptLang) {
1993
- const i18n = I18N[scriptLang];
2539
+ const i18n = getTranslation(scriptLang);
1994
2540
  console.log(ansis.cyan(`
1995
- \u{1F9F9} ${i18n.cleaningOldFiles || "Cleaning up old version files"}...`));
2541
+ \u{1F9F9} ${i18n.workflow.cleaningOldFiles || "Cleaning up old version files"}...`));
1996
2542
  const oldCommandFiles = [
1997
2543
  join(CLAUDE_DIR, "commands", "workflow.md"),
1998
2544
  join(CLAUDE_DIR, "commands", "feat.md")
@@ -2005,9 +2551,9 @@ async function cleanupOldVersionFiles(scriptLang) {
2005
2551
  if (existsSync(file)) {
2006
2552
  try {
2007
2553
  await rm(file, { force: true });
2008
- console.log(ansis.gray(` \u2714 ${i18n.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2554
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2009
2555
  } catch (error) {
2010
- console.error(ansis.yellow(` \u26A0 ${i18n.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2556
+ console.error(ansis.yellow(` \u26A0 ${i18n.workflow.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2011
2557
  }
2012
2558
  }
2013
2559
  }
@@ -2015,14 +2561,468 @@ async function cleanupOldVersionFiles(scriptLang) {
2015
2561
  if (existsSync(file)) {
2016
2562
  try {
2017
2563
  await rm(file, { force: true });
2018
- console.log(ansis.gray(` \u2714 ${i18n.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2564
+ console.log(ansis.gray(` \u2714 ${i18n.workflow.removedOldFile || "Removed old file"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2019
2565
  } catch (error) {
2020
- console.error(ansis.yellow(` \u26A0 ${i18n.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2566
+ console.error(ansis.yellow(` \u26A0 ${i18n.workflow.failedToRemoveFile || "Failed to remove"}: ${file.replace(CLAUDE_DIR, "~/.claude")}`));
2021
2567
  }
2022
2568
  }
2023
2569
  }
2024
2570
  }
2025
2571
 
2572
+ const execAsync$1 = promisify$1(exec$2);
2573
+ async function isCcrInstalled() {
2574
+ try {
2575
+ await execAsync$1("ccr version");
2576
+ return true;
2577
+ } catch {
2578
+ try {
2579
+ await execAsync$1("which ccr");
2580
+ return true;
2581
+ } catch {
2582
+ return false;
2583
+ }
2584
+ }
2585
+ }
2586
+ async function installCcr(scriptLang) {
2587
+ const i18n = getTranslation(scriptLang);
2588
+ const installed = await isCcrInstalled();
2589
+ if (installed) {
2590
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
2591
+ await updateCcr(scriptLang);
2592
+ return;
2593
+ }
2594
+ console.log(ansis.cyan(`\u{1F4E6} ${i18n.ccr.installingCcr}`));
2595
+ try {
2596
+ await execAsync$1("npm install -g claude-code-router --force");
2597
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrInstallSuccess}`));
2598
+ } catch (error) {
2599
+ if (error.message?.includes("EEXIST")) {
2600
+ console.log(ansis.yellow(`\u26A0 ${i18n.ccr.ccrAlreadyInstalled}`));
2601
+ await updateCcr(scriptLang);
2602
+ return;
2603
+ }
2604
+ console.error(ansis.red(`\u2716 ${i18n.ccr.ccrInstallFailed}`));
2605
+ throw error;
2606
+ }
2607
+ }
2608
+
2609
+ const PROVIDER_PRESETS_URL = "https://pub-0dc3e1677e894f07bbea11b17a29e032.r2.dev/providers.json";
2610
+ async function fetchProviderPresets() {
2611
+ try {
2612
+ const controller = new AbortController();
2613
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
2614
+ const response = await fetch(PROVIDER_PRESETS_URL, {
2615
+ signal: controller.signal
2616
+ });
2617
+ clearTimeout(timeoutId);
2618
+ if (!response.ok) {
2619
+ throw new Error(`HTTP error! status: ${response.status}`);
2620
+ }
2621
+ const data = await response.json();
2622
+ const presets = [];
2623
+ if (Array.isArray(data)) {
2624
+ for (const provider of data) {
2625
+ if (provider && typeof provider === "object") {
2626
+ presets.push({
2627
+ name: provider.name || "",
2628
+ provider: provider.name || "",
2629
+ baseURL: provider.api_base_url || provider.baseURL || provider.url,
2630
+ requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
2631
+ models: provider.models || [],
2632
+ description: provider.description || provider.name || "",
2633
+ transformer: provider.transformer
2634
+ });
2635
+ }
2636
+ }
2637
+ } else if (data && typeof data === "object") {
2638
+ for (const [key, value] of Object.entries(data)) {
2639
+ if (typeof value === "object" && value !== null) {
2640
+ const provider = value;
2641
+ presets.push({
2642
+ name: provider.name || key,
2643
+ provider: key,
2644
+ baseURL: provider.api_base_url || provider.baseURL || provider.url,
2645
+ requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
2646
+ models: provider.models || [],
2647
+ description: provider.description || "",
2648
+ transformer: provider.transformer
2649
+ });
2650
+ }
2651
+ }
2652
+ }
2653
+ return presets;
2654
+ } catch (error) {
2655
+ return getFallbackPresets();
2656
+ }
2657
+ }
2658
+ function getFallbackPresets() {
2659
+ return [
2660
+ {
2661
+ name: "dashscope",
2662
+ provider: "dashscope",
2663
+ baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
2664
+ requiresApiKey: true,
2665
+ models: ["qwen3-coder-plus"],
2666
+ description: "Alibaba DashScope",
2667
+ transformer: {
2668
+ use: [["maxtoken", { max_tokens: 65536 }]],
2669
+ "qwen3-coder-plus": {
2670
+ use: ["enhancetool"]
2671
+ }
2672
+ }
2673
+ },
2674
+ {
2675
+ name: "deepseek",
2676
+ provider: "deepseek",
2677
+ baseURL: "https://api.deepseek.com/chat/completions",
2678
+ requiresApiKey: true,
2679
+ models: ["deepseek-chat", "deepseek-reasoner"],
2680
+ description: "DeepSeek AI models",
2681
+ transformer: {
2682
+ use: ["deepseek"],
2683
+ "deepseek-chat": {
2684
+ use: ["tooluse"]
2685
+ }
2686
+ }
2687
+ },
2688
+ {
2689
+ name: "gemini",
2690
+ provider: "gemini",
2691
+ baseURL: "https://generativelanguage.googleapis.com/v1beta/models/",
2692
+ requiresApiKey: true,
2693
+ models: ["gemini-2.5-flash", "gemini-2.5-pro"],
2694
+ description: "Google Gemini models",
2695
+ transformer: {
2696
+ use: ["gemini"]
2697
+ }
2698
+ },
2699
+ {
2700
+ name: "modelscope",
2701
+ provider: "modelscope",
2702
+ baseURL: "https://api-inference.modelscope.cn/v1/chat/completions",
2703
+ requiresApiKey: true,
2704
+ models: ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507", "ZhipuAI/GLM-4.5"],
2705
+ description: "ModelScope AI models",
2706
+ transformer: {
2707
+ use: [["maxtoken", { max_tokens: 65536 }]],
2708
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct": {
2709
+ use: ["enhancetool"]
2710
+ },
2711
+ "Qwen/Qwen3-235B-A22B-Thinking-2507": {
2712
+ use: ["reasoning"]
2713
+ }
2714
+ }
2715
+ },
2716
+ {
2717
+ name: "openrouter",
2718
+ provider: "openrouter",
2719
+ baseURL: "https://openrouter.ai/api/v1/chat/completions",
2720
+ requiresApiKey: true,
2721
+ models: [
2722
+ "google/gemini-2.5-pro-preview",
2723
+ "anthropic/claude-sonnet-4",
2724
+ "anthropic/claude-3.5-sonnet",
2725
+ "anthropic/claude-3.7-sonnet:thinking"
2726
+ ],
2727
+ description: "OpenRouter API",
2728
+ transformer: {
2729
+ use: ["openrouter"]
2730
+ }
2731
+ },
2732
+ {
2733
+ name: "siliconflow",
2734
+ provider: "siliconflow",
2735
+ baseURL: "https://api.siliconflow.cn/v1/chat/completions",
2736
+ requiresApiKey: true,
2737
+ models: ["moonshotai/Kimi-K2-Instruct"],
2738
+ description: "SiliconFlow AI",
2739
+ transformer: {
2740
+ use: [["maxtoken", { max_tokens: 16384 }]]
2741
+ }
2742
+ },
2743
+ {
2744
+ name: "volcengine",
2745
+ provider: "volcengine",
2746
+ baseURL: "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
2747
+ requiresApiKey: true,
2748
+ models: ["deepseek-v3-250324", "deepseek-r1-250528"],
2749
+ description: "Volcengine AI",
2750
+ transformer: {
2751
+ use: ["deepseek"]
2752
+ }
2753
+ }
2754
+ ];
2755
+ }
2756
+
2757
+ const execAsync = promisify$1(exec$2);
2758
+ const CCR_CONFIG_DIR = join$2(homedir(), ".claude-code-router");
2759
+ const CCR_CONFIG_FILE = join$2(CCR_CONFIG_DIR, "config.json");
2760
+ const CCR_BACKUP_DIR = CCR_CONFIG_DIR;
2761
+ function ensureCcrConfigDir() {
2762
+ if (!existsSync(CCR_CONFIG_DIR)) {
2763
+ mkdirSync(CCR_CONFIG_DIR, { recursive: true });
2764
+ }
2765
+ }
2766
+ function backupCcrConfig(scriptLang) {
2767
+ const i18n = getTranslation(scriptLang);
2768
+ try {
2769
+ if (!existsSync(CCR_CONFIG_FILE)) {
2770
+ return null;
2771
+ }
2772
+ const timestamp = dayjs().format("YYYY-MM-DDTHH-mm-ss-SSS") + "Z";
2773
+ const backupFileName = `config.json.${timestamp}.bak`;
2774
+ const backupPath = join$2(CCR_BACKUP_DIR, backupFileName);
2775
+ console.log(ansis.cyan(`${i18n.ccr.backupCcrConfig}`));
2776
+ copyFileSync(CCR_CONFIG_FILE, backupPath);
2777
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrBackupSuccess.replace("{path}", backupPath)}`));
2778
+ return backupPath;
2779
+ } catch (error) {
2780
+ console.error(ansis.red(`${i18n.ccr.ccrBackupFailed}:`), error.message);
2781
+ return null;
2782
+ }
2783
+ }
2784
+ function readCcrConfig() {
2785
+ if (!existsSync(CCR_CONFIG_FILE)) {
2786
+ return null;
2787
+ }
2788
+ return readJsonConfig(CCR_CONFIG_FILE);
2789
+ }
2790
+ function writeCcrConfig(config) {
2791
+ ensureCcrConfigDir();
2792
+ writeJsonConfig(CCR_CONFIG_FILE, config);
2793
+ }
2794
+ async function configureCcrProxy(ccrConfig) {
2795
+ const settings = readJsonConfig(SETTINGS_FILE) || {};
2796
+ const host = ccrConfig.HOST || "127.0.0.1";
2797
+ const port = ccrConfig.PORT || 3456;
2798
+ const apiKey = ccrConfig.APIKEY || "sk-zcf-x-ccr";
2799
+ if (!settings.env) {
2800
+ settings.env = {};
2801
+ }
2802
+ settings.env.ANTHROPIC_BASE_URL = `http://${host}:${port}`;
2803
+ settings.env.ANTHROPIC_API_KEY = apiKey;
2804
+ writeJsonConfig(SETTINGS_FILE, settings);
2805
+ }
2806
+ async function selectCcrPreset(scriptLang) {
2807
+ const i18n = getTranslation(scriptLang);
2808
+ console.log(ansis.cyan(`${i18n.ccr.fetchingPresets}`));
2809
+ const presets = await fetchProviderPresets();
2810
+ if (!presets || presets.length === 0) {
2811
+ console.log(ansis.yellow(`${i18n.ccr.noPresetsAvailable}`));
2812
+ return null;
2813
+ }
2814
+ try {
2815
+ const choices = [
2816
+ ...presets.map((p, index) => ({
2817
+ name: `${index + 1}. ${p.name}`,
2818
+ value: p
2819
+ })),
2820
+ {
2821
+ name: `${presets.length + 1}. ${i18n.ccr.skipOption}`,
2822
+ value: "skip"
2823
+ }
2824
+ ];
2825
+ const { preset } = await inquirer.prompt({
2826
+ type: "list",
2827
+ name: "preset",
2828
+ message: i18n.ccr.selectCcrPreset,
2829
+ choices
2830
+ });
2831
+ return preset;
2832
+ } catch (error) {
2833
+ if (error.name === "ExitPromptError") {
2834
+ console.log(ansis.yellow(i18n.common.cancelled));
2835
+ return null;
2836
+ }
2837
+ throw error;
2838
+ }
2839
+ }
2840
+ async function configureCcrWithPreset(preset, scriptLang) {
2841
+ const i18n = getTranslation(scriptLang);
2842
+ const provider = {
2843
+ name: preset.name,
2844
+ // Use the original name from JSON
2845
+ api_base_url: preset.baseURL || "",
2846
+ api_key: "",
2847
+ models: preset.models
2848
+ };
2849
+ if (preset.transformer) {
2850
+ provider.transformer = preset.transformer;
2851
+ }
2852
+ if (preset.requiresApiKey) {
2853
+ try {
2854
+ const { apiKey } = await inquirer.prompt({
2855
+ type: "input",
2856
+ name: "apiKey",
2857
+ message: i18n.ccr.enterApiKeyForProvider.replace("{provider}", preset.name),
2858
+ validate: (value) => !!value || i18n.api.keyRequired
2859
+ });
2860
+ provider.api_key = apiKey;
2861
+ } catch (error) {
2862
+ if (error.name === "ExitPromptError") {
2863
+ throw error;
2864
+ }
2865
+ throw error;
2866
+ }
2867
+ } else {
2868
+ provider.api_key = "sk-free";
2869
+ }
2870
+ let defaultModel = preset.models[0];
2871
+ if (preset.models.length > 1) {
2872
+ try {
2873
+ const { model } = await inquirer.prompt({
2874
+ type: "list",
2875
+ name: "model",
2876
+ message: i18n.ccr.selectDefaultModelForProvider.replace("{provider}", preset.name),
2877
+ choices: preset.models.map((m, index) => ({
2878
+ name: `${index + 1}. ${m}`,
2879
+ value: m
2880
+ }))
2881
+ });
2882
+ defaultModel = model;
2883
+ } catch (error) {
2884
+ if (error.name === "ExitPromptError") {
2885
+ throw error;
2886
+ }
2887
+ throw error;
2888
+ }
2889
+ }
2890
+ const router = {
2891
+ default: `${preset.name},${defaultModel}`,
2892
+ // Use the original name
2893
+ background: `${preset.name},${defaultModel}`,
2894
+ think: `${preset.name},${defaultModel}`,
2895
+ longContext: `${preset.name},${defaultModel}`,
2896
+ longContextThreshold: 6e4,
2897
+ webSearch: `${preset.name},${defaultModel}`
2898
+ };
2899
+ const config = {
2900
+ LOG: true,
2901
+ CLAUDE_PATH: "",
2902
+ HOST: "127.0.0.1",
2903
+ PORT: 3456,
2904
+ APIKEY: "sk-zcf-x-ccr",
2905
+ API_TIMEOUT_MS: "600000",
2906
+ PROXY_URL: "",
2907
+ transformers: [],
2908
+ Providers: [provider],
2909
+ Router: router
2910
+ };
2911
+ return config;
2912
+ }
2913
+ async function restartAndCheckCcrStatus(scriptLang) {
2914
+ const i18n = getTranslation(scriptLang);
2915
+ try {
2916
+ console.log(ansis.cyan(`${i18n.ccr.restartingCcr}`));
2917
+ await execAsync("ccr restart");
2918
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrRestartSuccess}`));
2919
+ console.log(ansis.cyan(`${i18n.ccr.checkingCcrStatus}`));
2920
+ const { stdout } = await execAsync("ccr status");
2921
+ console.log(ansis.gray(stdout));
2922
+ } catch (error) {
2923
+ console.error(ansis.red(`${i18n.ccr.ccrRestartFailed}:`), error.message || error);
2924
+ if (process.env.DEBUG) {
2925
+ console.error("Full error:", error);
2926
+ }
2927
+ }
2928
+ }
2929
+ function showConfigurationTips(scriptLang, apiKey) {
2930
+ const i18n = getTranslation(scriptLang);
2931
+ console.log(ansis.bold.cyan(`
2932
+ \u{1F4CC} ${i18n.ccr.configTips}:`));
2933
+ console.log(ansis.blue(` \u2022 ${i18n.ccr.advancedConfigTip}`));
2934
+ console.log(ansis.blue(` \u2022 ${i18n.ccr.manualConfigTip}`));
2935
+ console.log(ansis.bold.yellow(` \u2022 ${i18n.ccr.useClaudeCommand}`));
2936
+ if (apiKey) {
2937
+ console.log(ansis.bold.green(` \u2022 ${i18n.ccr.ccrUiApiKey || "CCR UI API Key"}: ${apiKey}`));
2938
+ console.log(ansis.gray(` ${i18n.ccr.ccrUiApiKeyHint || "Use this API key to login to CCR UI"}`));
2939
+ }
2940
+ console.log("");
2941
+ }
2942
+ async function setupCcrConfiguration(scriptLang) {
2943
+ const i18n = getTranslation(scriptLang);
2944
+ try {
2945
+ const existingConfig = readCcrConfig();
2946
+ if (existingConfig) {
2947
+ console.log(ansis.blue(`\u2139 ${i18n.ccr.existingCcrConfig}`));
2948
+ let shouldBackupAndReconfigure = false;
2949
+ try {
2950
+ const result = await inquirer.prompt({
2951
+ type: "confirm",
2952
+ name: "overwrite",
2953
+ message: i18n.ccr.overwriteCcrConfig,
2954
+ default: false
2955
+ });
2956
+ shouldBackupAndReconfigure = result.overwrite;
2957
+ } catch (error) {
2958
+ if (error.name === "ExitPromptError") {
2959
+ console.log(ansis.yellow(i18n.common.cancelled));
2960
+ return false;
2961
+ }
2962
+ throw error;
2963
+ }
2964
+ if (!shouldBackupAndReconfigure) {
2965
+ console.log(ansis.yellow(`${i18n.ccr.keepingExistingConfig}`));
2966
+ await configureCcrProxy(existingConfig);
2967
+ return true;
2968
+ }
2969
+ backupCcrConfig(scriptLang);
2970
+ }
2971
+ const preset = await selectCcrPreset(scriptLang);
2972
+ if (!preset) {
2973
+ return false;
2974
+ }
2975
+ let config;
2976
+ if (preset === "skip") {
2977
+ console.log(ansis.yellow(`${i18n.ccr.skipConfiguring}`));
2978
+ config = {
2979
+ LOG: false,
2980
+ CLAUDE_PATH: "",
2981
+ HOST: "127.0.0.1",
2982
+ PORT: 3456,
2983
+ APIKEY: "sk-zcf-x-ccr",
2984
+ API_TIMEOUT_MS: "600000",
2985
+ PROXY_URL: "",
2986
+ transformers: [],
2987
+ Providers: [],
2988
+ // Empty providers array
2989
+ Router: {
2990
+ // Empty router configuration - user will configure in CCR UI
2991
+ }
2992
+ };
2993
+ } else {
2994
+ config = await configureCcrWithPreset(preset, scriptLang);
2995
+ }
2996
+ writeCcrConfig(config);
2997
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrConfigSuccess}`));
2998
+ await configureCcrProxy(config);
2999
+ console.log(ansis.green(`\u2714 ${i18n.ccr.proxyConfigSuccess}`));
3000
+ await restartAndCheckCcrStatus(scriptLang);
3001
+ showConfigurationTips(scriptLang, config.APIKEY);
3002
+ try {
3003
+ addCompletedOnboarding();
3004
+ } catch (error) {
3005
+ console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
3006
+ }
3007
+ return true;
3008
+ } catch (error) {
3009
+ if (error.name === "ExitPromptError") {
3010
+ console.log(ansis.yellow(i18n.common.cancelled));
3011
+ return false;
3012
+ }
3013
+ console.error(ansis.red(`${i18n.ccr.ccrConfigFailed}:`), error);
3014
+ return false;
3015
+ }
3016
+ }
3017
+ async function configureCcrFeature(scriptLang) {
3018
+ const i18n = getTranslation(scriptLang);
3019
+ const backupDir = backupExistingConfig();
3020
+ if (backupDir) {
3021
+ console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
3022
+ }
3023
+ await setupCcrConfiguration(scriptLang);
3024
+ }
3025
+
2026
3026
  async function init(options = {}) {
2027
3027
  try {
2028
3028
  if (!options.skipBanner) {
@@ -2032,22 +3032,22 @@ async function init(options = {}) {
2032
3032
  const i18n = I18N[scriptLang];
2033
3033
  if (isTermux()) {
2034
3034
  console.log(ansis.yellow(`
2035
- \u2139 ${i18n.termuxDetected}`));
2036
- console.log(ansis.gray(i18n.termuxEnvironmentInfo));
3035
+ \u2139 ${i18n.installation.termuxDetected}`));
3036
+ console.log(ansis.gray(i18n.installation.termuxEnvironmentInfo));
2037
3037
  }
2038
3038
  let configLang = options.configLang;
2039
3039
  if (!configLang) {
2040
3040
  const { lang } = await inquirer.prompt({
2041
3041
  type: "list",
2042
3042
  name: "lang",
2043
- message: i18n.selectConfigLang,
2044
- choices: SUPPORTED_LANGS.map((l) => ({
2045
- name: `${LANG_LABELS[l]} - ${i18n.configLangHint[l]}`,
3043
+ message: i18n.language.selectConfigLang,
3044
+ choices: addNumbersToChoices(SUPPORTED_LANGS.map((l) => ({
3045
+ name: `${LANG_LABELS[l]} - ${i18n.language.configLangHint[l]}`,
2046
3046
  value: l
2047
- }))
3047
+ })))
2048
3048
  });
2049
3049
  if (!lang) {
2050
- console.log(ansis.yellow(i18n.cancelled));
3050
+ console.log(ansis.yellow(i18n.common.cancelled));
2051
3051
  process.exit(0);
2052
3052
  }
2053
3053
  configLang = lang;
@@ -2059,20 +3059,20 @@ async function init(options = {}) {
2059
3059
  const { shouldInstall } = await inquirer.prompt({
2060
3060
  type: "confirm",
2061
3061
  name: "shouldInstall",
2062
- message: i18n.installPrompt,
3062
+ message: i18n.installation.installPrompt,
2063
3063
  default: true
2064
3064
  });
2065
3065
  if (shouldInstall === void 0) {
2066
- console.log(ansis.yellow(i18n.cancelled));
3066
+ console.log(ansis.yellow(i18n.common.cancelled));
2067
3067
  process.exit(0);
2068
3068
  }
2069
3069
  if (shouldInstall) {
2070
3070
  await installClaudeCode(scriptLang);
2071
3071
  } else {
2072
- console.log(ansis.yellow(i18n.skip));
3072
+ console.log(ansis.yellow(i18n.common.skip));
2073
3073
  }
2074
3074
  } else {
2075
- console.log(ansis.green(`\u2714 ${i18n.installSuccess}`));
3075
+ console.log(ansis.green(`\u2714 ${i18n.installation.alreadyInstalled}`));
2076
3076
  }
2077
3077
  ensureClaudeDir();
2078
3078
  let action = "new";
@@ -2080,21 +3080,21 @@ async function init(options = {}) {
2080
3080
  const { action: userAction } = await inquirer.prompt({
2081
3081
  type: "list",
2082
3082
  name: "action",
2083
- message: i18n.existingConfig,
2084
- choices: [
2085
- { name: i18n.backupAndOverwrite, value: "backup" },
2086
- { name: i18n.updateDocsOnly, value: "docs-only" },
2087
- { name: i18n.mergeConfig, value: "merge" },
2088
- { name: i18n.skip, value: "skip" }
2089
- ]
3083
+ message: i18n.configuration.existingConfig,
3084
+ choices: addNumbersToChoices([
3085
+ { name: i18n.configuration.backupAndOverwrite, value: "backup" },
3086
+ { name: i18n.configuration.updateDocsOnly, value: "docs-only" },
3087
+ { name: i18n.configuration.mergeConfig, value: "merge" },
3088
+ { name: i18n.common.skip, value: "skip" }
3089
+ ])
2090
3090
  });
2091
3091
  if (!userAction) {
2092
- console.log(ansis.yellow(i18n.cancelled));
3092
+ console.log(ansis.yellow(i18n.common.cancelled));
2093
3093
  process.exit(0);
2094
3094
  }
2095
3095
  action = userAction;
2096
3096
  if (action === "skip") {
2097
- console.log(ansis.yellow(i18n.skip));
3097
+ console.log(ansis.yellow(i18n.common.skip));
2098
3098
  return;
2099
3099
  }
2100
3100
  }
@@ -2103,65 +3103,103 @@ async function init(options = {}) {
2103
3103
  if (action !== "docs-only" && (isNewInstall || ["backup", "merge"].includes(action))) {
2104
3104
  const existingApiConfig = getExistingApiConfig();
2105
3105
  if (existingApiConfig) {
2106
- console.log("\n" + ansis.blue(`\u2139 ${i18n.existingApiConfig}`));
2107
- console.log(ansis.gray(` ${i18n.apiConfigUrl}: ${existingApiConfig.url || i18n.notConfigured}`));
3106
+ console.log("\n" + ansis.blue(`\u2139 ${i18n.api.existingApiConfig}`));
3107
+ console.log(ansis.gray(` ${i18n.api.apiConfigUrl}: ${existingApiConfig.url || i18n.common.notConfigured}`));
2108
3108
  console.log(
2109
3109
  ansis.gray(
2110
- ` ${i18n.apiConfigKey}: ${existingApiConfig.key ? formatApiKeyDisplay(existingApiConfig.key) : i18n.notConfigured}`
3110
+ ` ${i18n.api.apiConfigKey}: ${existingApiConfig.key ? formatApiKeyDisplay(existingApiConfig.key) : i18n.common.notConfigured}`
2111
3111
  )
2112
3112
  );
2113
- console.log(ansis.gray(` ${i18n.apiConfigAuthType}: ${existingApiConfig.authType || i18n.notConfigured}
3113
+ console.log(ansis.gray(` ${i18n.api.apiConfigAuthType}: ${existingApiConfig.authType || i18n.common.notConfigured}
2114
3114
  `));
2115
3115
  const { action: apiAction } = await inquirer.prompt({
2116
3116
  type: "list",
2117
3117
  name: "action",
2118
- message: i18n.selectApiAction,
2119
- choices: [
2120
- { name: i18n.keepExistingConfig, value: "keep" },
2121
- { name: i18n.modifyAllConfig, value: "modify-all" },
2122
- { name: i18n.modifyPartialConfig, value: "modify-partial" },
2123
- { name: i18n.skipApi, value: "skip" }
2124
- ]
3118
+ message: i18n.api.selectApiAction,
3119
+ choices: addNumbersToChoices([
3120
+ { name: i18n.api.keepExistingConfig, value: "keep" },
3121
+ { name: i18n.api.modifyAllConfig, value: "modify-all" },
3122
+ { name: i18n.api.modifyPartialConfig, value: "modify-partial" },
3123
+ { name: i18n.api.useCcrProxy, value: "use-ccr" },
3124
+ { name: i18n.api.skipApi, value: "skip" }
3125
+ ])
2125
3126
  });
2126
3127
  if (!apiAction) {
2127
- console.log(ansis.yellow(i18n.cancelled));
3128
+ console.log(ansis.yellow(i18n.common.cancelled));
2128
3129
  process.exit(0);
2129
3130
  }
2130
3131
  if (apiAction === "keep" || apiAction === "skip") {
2131
3132
  apiConfig = null;
3133
+ if (apiAction === "keep") {
3134
+ try {
3135
+ addCompletedOnboarding();
3136
+ } catch (error) {
3137
+ console.error(ansis.red(i18n.configuration.failedToSetOnboarding), error);
3138
+ }
3139
+ }
2132
3140
  } else if (apiAction === "modify-partial") {
2133
3141
  await modifyApiConfigPartially(existingApiConfig, i18n, scriptLang);
2134
3142
  apiConfig = null;
2135
3143
  } else if (apiAction === "modify-all") {
2136
3144
  apiConfig = await configureApiCompletely(i18n, scriptLang);
3145
+ } else if (apiAction === "use-ccr") {
3146
+ const ccrInstalled = await isCcrInstalled();
3147
+ if (!ccrInstalled) {
3148
+ console.log(ansis.yellow(`${i18n.ccr.installingCcr}`));
3149
+ await installCcr(scriptLang);
3150
+ } else {
3151
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
3152
+ }
3153
+ const ccrConfigured = await setupCcrConfiguration(scriptLang);
3154
+ if (ccrConfigured) {
3155
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
3156
+ apiConfig = null;
3157
+ }
2137
3158
  }
2138
3159
  } else {
2139
3160
  const { apiChoice } = await inquirer.prompt({
2140
3161
  type: "list",
2141
3162
  name: "apiChoice",
2142
- message: i18n.configureApi,
3163
+ message: i18n.api.configureApi,
2143
3164
  choices: [
2144
3165
  {
2145
- name: `${i18n.useAuthToken} - ${ansis.gray(i18n.authTokenDesc)}`,
3166
+ name: `${i18n.api.useAuthToken} - ${ansis.gray(i18n.api.authTokenDesc)}`,
2146
3167
  value: "auth_token",
2147
- short: i18n.useAuthToken
3168
+ short: i18n.api.useAuthToken
2148
3169
  },
2149
3170
  {
2150
- name: `${i18n.useApiKey} - ${ansis.gray(i18n.apiKeyDesc)}`,
3171
+ name: `${i18n.api.useApiKey} - ${ansis.gray(i18n.api.apiKeyDesc)}`,
2151
3172
  value: "api_key",
2152
- short: i18n.useApiKey
3173
+ short: i18n.api.useApiKey
2153
3174
  },
2154
3175
  {
2155
- name: i18n.skipApi,
3176
+ name: `${i18n.api.useCcrProxy} - ${ansis.gray(i18n.api.ccrProxyDesc)}`,
3177
+ value: "ccr_proxy",
3178
+ short: i18n.api.useCcrProxy
3179
+ },
3180
+ {
3181
+ name: i18n.api.skipApi,
2156
3182
  value: "skip"
2157
3183
  }
2158
3184
  ]
2159
3185
  });
2160
3186
  if (!apiChoice) {
2161
- console.log(ansis.yellow(i18n.cancelled));
3187
+ console.log(ansis.yellow(i18n.common.cancelled));
2162
3188
  process.exit(0);
2163
3189
  }
2164
- if (apiChoice !== "skip") {
3190
+ if (apiChoice === "ccr_proxy") {
3191
+ const ccrInstalled = await isCcrInstalled();
3192
+ if (!ccrInstalled) {
3193
+ await installCcr(scriptLang);
3194
+ } else {
3195
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrAlreadyInstalled}`));
3196
+ }
3197
+ const ccrConfigured = await setupCcrConfiguration(scriptLang);
3198
+ if (ccrConfigured) {
3199
+ console.log(ansis.green(`\u2714 ${i18n.ccr.ccrSetupComplete}`));
3200
+ apiConfig = null;
3201
+ }
3202
+ } else if (apiChoice !== "skip") {
2165
3203
  apiConfig = await configureApiCompletely(i18n, scriptLang, apiChoice);
2166
3204
  }
2167
3205
  }
@@ -2169,7 +3207,7 @@ async function init(options = {}) {
2169
3207
  if (["backup", "docs-only", "merge"].includes(action)) {
2170
3208
  const backupDir = backupExistingConfig();
2171
3209
  if (backupDir) {
2172
- console.log(ansis.gray(`\u2714 ${i18n.backupSuccess}: ${backupDir}`));
3210
+ console.log(ansis.gray(`\u2714 ${i18n.configuration.backupSuccess}: ${backupDir}`));
2173
3211
  }
2174
3212
  }
2175
3213
  if (action === "docs-only") {
@@ -2184,30 +3222,25 @@ async function init(options = {}) {
2184
3222
  if (apiConfig && action !== "docs-only") {
2185
3223
  const configuredApi = configureApi(apiConfig);
2186
3224
  if (configuredApi) {
2187
- console.log(ansis.green(`\u2714 ${i18n.apiConfigSuccess}`));
3225
+ console.log(ansis.green(`\u2714 ${i18n.api.apiConfigSuccess}`));
2188
3226
  console.log(ansis.gray(` URL: ${configuredApi.url}`));
2189
3227
  console.log(ansis.gray(` Key: ${formatApiKeyDisplay(configuredApi.key)}`));
2190
- try {
2191
- addCompletedOnboarding();
2192
- } catch (error) {
2193
- console.error(ansis.red(i18n.failedToSetOnboarding), error);
2194
- }
2195
3228
  }
2196
3229
  }
2197
3230
  if (action !== "docs-only") {
2198
3231
  const { shouldConfigureMcp } = await inquirer.prompt({
2199
3232
  type: "confirm",
2200
3233
  name: "shouldConfigureMcp",
2201
- message: i18n.configureMcp,
3234
+ message: i18n.mcp.configureMcp,
2202
3235
  default: true
2203
3236
  });
2204
3237
  if (shouldConfigureMcp === void 0) {
2205
- console.log(ansis.yellow(i18n.cancelled));
3238
+ console.log(ansis.yellow(i18n.common.cancelled));
2206
3239
  process.exit(0);
2207
3240
  }
2208
3241
  if (shouldConfigureMcp) {
2209
3242
  if (isWindows()) {
2210
- console.log(ansis.blue(`\u2139 ${I18N[scriptLang].windowsDetected}`));
3243
+ console.log(ansis.blue(`\u2139 ${i18n.installation.windowsDetected}`));
2211
3244
  }
2212
3245
  const selectedServices = await selectMcpServices(scriptLang);
2213
3246
  if (selectedServices === void 0) {
@@ -2216,7 +3249,7 @@ async function init(options = {}) {
2216
3249
  if (selectedServices.length > 0) {
2217
3250
  const mcpBackupPath = backupMcpConfig();
2218
3251
  if (mcpBackupPath) {
2219
- console.log(ansis.gray(`\u2714 ${i18n.mcpBackupSuccess}: ${mcpBackupPath}`));
3252
+ console.log(ansis.gray(`\u2714 ${i18n.mcp.mcpBackupSuccess}: ${mcpBackupPath}`));
2220
3253
  }
2221
3254
  const newServers = {};
2222
3255
  for (const serviceId of selectedServices) {
@@ -2228,10 +3261,10 @@ async function init(options = {}) {
2228
3261
  type: "input",
2229
3262
  name: "apiKey",
2230
3263
  message: service.apiKeyPrompt[scriptLang],
2231
- validate: (value) => !!value || i18n.keyRequired
3264
+ validate: (value) => !!value || i18n.api.keyRequired
2232
3265
  });
2233
3266
  if (apiKey === void 0) {
2234
- console.log(ansis.yellow(`${i18n.skip}: ${service.name[scriptLang]}`));
3267
+ console.log(ansis.yellow(`${i18n.common.skip}: ${service.name[scriptLang]}`));
2235
3268
  continue;
2236
3269
  }
2237
3270
  if (apiKey) {
@@ -2247,9 +3280,9 @@ async function init(options = {}) {
2247
3280
  mergedConfig = fixWindowsMcpConfig(mergedConfig);
2248
3281
  try {
2249
3282
  writeMcpConfig(mergedConfig);
2250
- console.log(ansis.green(`\u2714 ${i18n.mcpConfigSuccess}`));
3283
+ console.log(ansis.green(`\u2714 ${i18n.mcp.mcpConfigSuccess}`));
2251
3284
  } catch (error) {
2252
- console.error(ansis.red(`${i18n.failedToWriteMcpConfig} ${error}`));
3285
+ console.error(ansis.red(`${i18n.configuration.failedToWriteMcpConfig} ${error}`));
2253
3286
  }
2254
3287
  }
2255
3288
  }
@@ -2259,8 +3292,8 @@ async function init(options = {}) {
2259
3292
  preferredLang: scriptLang,
2260
3293
  aiOutputLang
2261
3294
  });
2262
- console.log(ansis.green(`\u2714 ${i18n.configSuccess} ${CLAUDE_DIR}`));
2263
- console.log("\n" + ansis.cyan(i18n.complete));
3295
+ console.log(ansis.green(`\u2714 ${i18n.configuration.configSuccess} ${CLAUDE_DIR}`));
3296
+ console.log("\n" + ansis.cyan(i18n.common.complete));
2264
3297
  } catch (error) {
2265
3298
  if (!handleExitPromptError(error)) {
2266
3299
  handleGeneralError(error, options.lang);
@@ -2346,4 +3379,4 @@ async function openSettingsJson() {
2346
3379
  }
2347
3380
  }
2348
3381
 
2349
- export { handleExitPromptError as $, AI_OUTPUT_LANGUAGES as A, backupMcpConfig as B, CLAUDE_DIR as C, mergeMcpServers as D, buildMcpServerConfig as E, fixWindowsMcpConfig as F, addCompletedOnboarding as G, updateZcfConfig as H, I18N as I, readZcfConfig as J, resolveAiOutputLanguage as K, LEGACY_ZCF_CONFIG_FILE as L, MCP_SERVICES as M, configureAiPersonality as N, isWindows as O, selectMcpServices as P, formatApiKeyDisplay as Q, modifyApiConfigPartially as R, SETTINGS_FILE as S, validateApiKey as T, readZcfConfigAsync as U, displayBanner as V, selectScriptLanguage as W, updatePromptOnly as X, selectAndInstallWorkflows as Y, ZCF_CONFIG_FILE as Z, version as _, importRecommendedEnv as a, handleGeneralError as a0, displayBannerWithInfo as a1, importRecommendedPermissions as b, commandExists as c, cleanupPermissions as d, CLAUDE_MD_FILE as e, ClAUDE_CONFIG_FILE as f, getPlatform as g, SUPPORTED_LANGS as h, init as i, LANG_LABELS as j, isClaudeCodeInstalled as k, installClaudeCode as l, mergeAndCleanPermissions as m, ensureClaudeDir as n, openSettingsJson as o, backupExistingConfig as p, copyConfigFiles as q, configureApi as r, mergeConfigs as s, mergeSettingsFile as t, updateDefaultModel as u, getExistingApiConfig as v, applyAiLanguageDirective as w, getMcpConfigPath as x, readMcpConfig as y, writeMcpConfig as z };
3382
+ export { readCcrConfig as $, AI_OUTPUT_LANGUAGES as A, writeMcpConfig as B, CLAUDE_DIR as C, backupMcpConfig as D, mergeMcpServers as E, buildMcpServerConfig as F, fixWindowsMcpConfig as G, addCompletedOnboarding as H, I18N as I, getTranslation as J, addNumbersToChoices as K, LEGACY_ZCF_CONFIG_FILE as L, MCP_SERVICES as M, updateZcfConfig as N, readZcfConfig as O, configureAiPersonality as P, isWindows as Q, selectMcpServices as R, SETTINGS_FILE as S, formatApiKeyDisplay as T, modifyApiConfigPartially as U, isCcrInstalled as V, installCcr as W, setupCcrConfiguration as X, validateApiKey as Y, ZCF_CONFIG_FILE as Z, readZcfConfigAsync as _, importRecommendedEnv as a, configureCcrFeature as a0, handleExitPromptError as a1, handleGeneralError as a2, displayBanner as a3, selectScriptLanguage as a4, resolveAiOutputLanguage as a5, updatePromptOnly as a6, selectAndInstallWorkflows as a7, version as a8, displayBannerWithInfo as a9, checkAndUpdateTools as aa, prompts as ab, importRecommendedPermissions as b, commandExists as c, cleanupPermissions as d, CLAUDE_MD_FILE as e, ClAUDE_CONFIG_FILE as f, getPlatform as g, SUPPORTED_LANGS as h, init as i, LANG_LABELS as j, isClaudeCodeInstalled as k, installClaudeCode as l, mergeAndCleanPermissions as m, ensureClaudeDir as n, openSettingsJson as o, backupExistingConfig as p, copyConfigFiles as q, configureApi as r, mergeConfigs as s, mergeSettingsFile as t, updateDefaultModel as u, getExistingModelConfig as v, getExistingApiConfig as w, applyAiLanguageDirective as x, getMcpConfigPath as y, readMcpConfig as z };