ccjk 13.3.18 → 13.3.19

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.
Files changed (44) hide show
  1. package/dist/chunks/api-cli.mjs +1 -1
  2. package/dist/chunks/api.mjs +1 -1
  3. package/dist/chunks/auto-fix.mjs +4 -4
  4. package/dist/chunks/boost.mjs +1 -1
  5. package/dist/chunks/ccjk-mcp.mjs +2 -2
  6. package/dist/chunks/ccr.mjs +24 -20
  7. package/dist/chunks/check-updates.mjs +2 -1
  8. package/dist/chunks/claude-code-config-manager.mjs +4 -4
  9. package/dist/chunks/claude-code-incremental-manager.mjs +5 -5
  10. package/dist/chunks/claude-config.mjs +2 -2
  11. package/dist/chunks/claude-wrapper.mjs +1 -1
  12. package/dist/chunks/codex-config-switch.mjs +4 -3
  13. package/dist/chunks/codex-provider-manager.mjs +2 -1
  14. package/dist/chunks/codex.mjs +4 -332
  15. package/dist/chunks/config-switch.mjs +2 -1
  16. package/dist/chunks/config.mjs +424 -358
  17. package/dist/chunks/config2.mjs +358 -424
  18. package/dist/chunks/config3.mjs +6 -1
  19. package/dist/chunks/doctor.mjs +2 -2
  20. package/dist/chunks/features.mjs +812 -0
  21. package/dist/chunks/index.mjs +1 -1
  22. package/dist/chunks/init.mjs +43 -89
  23. package/dist/chunks/installer.mjs +1 -1
  24. package/dist/chunks/mcp-cli.mjs +18 -17
  25. package/dist/chunks/mcp.mjs +5 -4
  26. package/dist/chunks/menu-hierarchical.mjs +27 -23
  27. package/dist/chunks/menu.mjs +17 -791
  28. package/dist/chunks/onboarding-wizard.mjs +2 -2
  29. package/dist/chunks/package.mjs +1 -1
  30. package/dist/chunks/platform.mjs +1 -1
  31. package/dist/chunks/quick-setup.mjs +14 -11
  32. package/dist/chunks/slash-commands.mjs +1 -1
  33. package/dist/chunks/uninstall.mjs +1 -1
  34. package/dist/chunks/update.mjs +17 -15
  35. package/dist/cli.mjs +1 -1
  36. package/dist/index.mjs +1 -1
  37. package/dist/shared/ccjk.0aJQmVxS.mjs +336 -0
  38. package/dist/shared/ccjk.C3o4BXvM.mjs +444 -0
  39. package/dist/shared/{ccjk.DfZKjHvG.mjs → ccjk.Dgq22o6V.mjs} +2 -438
  40. package/dist/shared/{ccjk.KpFl2RDA.mjs → ccjk.LTONy3IS.mjs} +4 -3
  41. package/dist/shared/ccjk.Si-T_ccK.mjs +75 -0
  42. package/dist/shared/{ccjk.I6IuYdc_.mjs → ccjk.Xla_pE3y.mjs} +1 -1
  43. package/dist/shared/{ccjk.DZ4ehAHg.mjs → ccjk.byom1b8z.mjs} +1 -1
  44. package/package.json +1 -1
@@ -1,478 +1,412 @@
1
- import { exec } from 'node:child_process';
2
- import { existsSync, copyFileSync, mkdirSync } from 'node:fs';
3
- import { homedir } from 'node:os';
4
- import process__default from 'node:process';
5
- import { promisify } from 'node:util';
1
+ import { fileURLToPath } from 'node:url';
6
2
  import a from './index2.mjs';
7
3
  import { d as dayjs } from '../shared/ccjk.RyizuzOI.mjs';
8
4
  import { i as inquirer } from './index3.mjs';
9
- import { SETTINGS_FILE } from './constants.mjs';
5
+ import { CLAUDE_DIR, SETTINGS_FILE, CLAUDE_VSC_CONFIG_FILE, AI_OUTPUT_LANGUAGES } from './constants.mjs';
10
6
  import { ensureI18nInitialized, i18n } from './index5.mjs';
11
- import { s as setPrimaryApiKey, a as addCompletedOnboarding } from './claude-config.mjs';
12
- import { b as backupExistingConfig } from './config.mjs';
7
+ import { s as setPrimaryApiKey, c as addCompletedOnboarding, d as deepMerge } from './claude-config.mjs';
8
+ import { exists, ensureDir, copyDir, writeFileAtomic, copyFile } from './fs-operations.mjs';
13
9
  import { readJsonConfig, writeJsonConfig } from './json-config.mjs';
14
- import { p as promptBoolean } from '../shared/ccjk.BWFpnOr3.mjs';
15
- import { j as join } from '../shared/ccjk.bQ7Dh1g4.mjs';
16
- import '../shared/ccjk.BAGoDD49.mjs';
17
- import 'node:readline';
18
- import 'stream';
19
- import 'node:tty';
20
- import 'node:async_hooks';
21
- import '../shared/ccjk.Cjgrln_h.mjs';
22
- import 'tty';
23
- import 'fs';
24
- import 'child_process';
25
- import 'node:path';
26
- import 'node:crypto';
27
- import 'buffer';
28
- import 'string_decoder';
29
- import 'node:url';
30
- import './platform.mjs';
31
- import './main.mjs';
32
- import 'module';
33
- import 'node:stream';
34
- import './fs-operations.mjs';
35
- import 'node:fs/promises';
36
- import '../shared/ccjk.DScm_NnL.mjs';
37
- import '../shared/ccjk.DeWpAShp.mjs';
10
+ import { m as mergeAndCleanPermissions } from '../shared/ccjk.DScm_NnL.mjs';
11
+ import { j as join, d as dirname } from '../shared/ccjk.bQ7Dh1g4.mjs';
38
12
 
39
- const PROVIDER_PRESETS_URL = "https://pub-0dc3e1677e894f07bbea11b17a29e032.r2.dev/providers.json";
40
- async function fetchProviderPresets() {
41
- try {
42
- const controller = new AbortController();
43
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
44
- const response = await fetch(PROVIDER_PRESETS_URL, {
45
- signal: controller.signal
46
- });
47
- clearTimeout(timeoutId);
48
- if (!response.ok) {
49
- throw new Error(`HTTP error! status: ${response.status}`);
50
- }
51
- const data = await response.json();
52
- const presets = [];
53
- if (Array.isArray(data)) {
54
- for (const provider of data) {
55
- if (provider && typeof provider === "object") {
56
- presets.push({
57
- name: provider.name || "",
58
- provider: provider.name || "",
59
- baseURL: provider.api_base_url || provider.baseURL || provider.url,
60
- requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
61
- models: provider.models || [],
62
- description: provider.description || provider.name || "",
63
- transformer: provider.transformer
64
- });
65
- }
66
- }
67
- } else if (data && typeof data === "object") {
68
- for (const [key, value] of Object.entries(data)) {
69
- if (typeof value === "object" && value !== null) {
70
- const provider = value;
71
- presets.push({
72
- name: provider.name || key,
73
- provider: key,
74
- baseURL: provider.api_base_url || provider.baseURL || provider.url,
75
- requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
76
- models: provider.models || [],
77
- description: provider.description || "",
78
- transformer: provider.transformer
79
- });
80
- }
81
- }
13
+ const MODEL_ENV_KEYS = [
14
+ "ANTHROPIC_MODEL",
15
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
16
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
17
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
18
+ // Deprecated but still cleaned to avoid stale values
19
+ "ANTHROPIC_SMALL_FAST_MODEL"
20
+ ];
21
+ function clearModelEnv(env, mode = "reset") {
22
+ for (const key of MODEL_ENV_KEYS) {
23
+ if (mode === "reset" && key === "ANTHROPIC_MODEL") {
24
+ env[key] = "";
25
+ } else {
26
+ delete env[key];
82
27
  }
83
- return presets;
84
- } catch {
85
- return getFallbackPresets();
86
28
  }
87
29
  }
88
- function getFallbackPresets() {
89
- return [
90
- {
91
- name: "dashscope",
92
- provider: "dashscope",
93
- baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
94
- requiresApiKey: true,
95
- models: ["qwen3-coder-plus"],
96
- description: "Alibaba DashScope",
97
- transformer: {
98
- "use": [["maxtoken", { max_tokens: 65536 }]],
99
- "qwen3-coder-plus": {
100
- use: ["enhancetool"]
101
- }
102
- }
103
- },
104
- {
105
- name: "deepseek",
106
- provider: "deepseek",
107
- baseURL: "https://api.deepseek.com/chat/completions",
108
- requiresApiKey: true,
109
- models: ["deepseek-chat", "deepseek-reasoner"],
110
- description: "DeepSeek AI models",
111
- transformer: {
112
- "use": ["deepseek"],
113
- "deepseek-chat": {
114
- use: ["tooluse"]
115
- }
116
- }
117
- },
118
- {
119
- name: "gemini",
120
- provider: "gemini",
121
- baseURL: "https://generativelanguage.googleapis.com/v1beta/models/",
122
- requiresApiKey: true,
123
- models: ["gemini-2.5-flash", "gemini-2.5-pro"],
124
- description: "Google Gemini models",
125
- transformer: {
126
- use: ["gemini"]
127
- }
128
- },
129
- {
130
- name: "modelscope",
131
- provider: "modelscope",
132
- baseURL: "https://api-inference.modelscope.cn/v1/chat/completions",
133
- requiresApiKey: true,
134
- models: ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507", "ZhipuAI/GLM-4.5"],
135
- description: "ModelScope AI models",
136
- transformer: {
137
- "use": [["maxtoken", { max_tokens: 65536 }]],
138
- "Qwen/Qwen3-Coder-480B-A35B-Instruct": {
139
- use: ["enhancetool"]
140
- },
141
- "Qwen/Qwen3-235B-A22B-Thinking-2507": {
142
- use: ["reasoning"]
143
- }
144
- }
145
- },
146
- {
147
- name: "openrouter",
148
- provider: "openrouter",
149
- baseURL: "https://openrouter.ai/api/v1/chat/completions",
150
- requiresApiKey: true,
151
- models: [
152
- "google/gemini-2.5-pro-preview",
153
- "anthropic/claude-sonnet-4",
154
- "anthropic/claude-3.5-sonnet",
155
- "anthropic/claude-3.7-sonnet:thinking"
156
- ],
157
- description: "OpenRouter API",
158
- transformer: {
159
- use: ["openrouter"]
160
- }
161
- },
162
- {
163
- name: "siliconflow",
164
- provider: "siliconflow",
165
- baseURL: "https://api.siliconflow.cn/v1/chat/completions",
166
- requiresApiKey: true,
167
- models: ["moonshotai/Kimi-K2-Instruct"],
168
- description: "SiliconFlow AI",
169
- transformer: {
170
- use: [["maxtoken", { max_tokens: 16384 }]]
171
- }
172
- },
173
- {
174
- name: "volcengine",
175
- provider: "volcengine",
176
- baseURL: "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
177
- requiresApiKey: true,
178
- models: ["deepseek-v3-250324", "deepseek-r1-250528"],
179
- description: "Volcengine AI",
180
- transformer: {
181
- use: ["deepseek"]
182
- }
183
- }
184
- ];
185
- }
186
30
 
187
- const execAsync = promisify(exec);
188
- const CCR_CONFIG_DIR = join(homedir(), ".claude-code-router");
189
- const CCR_CONFIG_FILE = join(CCR_CONFIG_DIR, "config.json");
190
- const CCR_BACKUP_DIR = CCR_CONFIG_DIR;
191
- function ensureCcrConfigDir() {
192
- if (!existsSync(CCR_CONFIG_DIR)) {
193
- mkdirSync(CCR_CONFIG_DIR, { recursive: true });
194
- }
31
+ function ensureClaudeDir() {
32
+ ensureDir(CLAUDE_DIR);
195
33
  }
196
- async function backupCcrConfig() {
197
- ensureI18nInitialized();
198
- try {
199
- if (!existsSync(CCR_CONFIG_FILE)) {
200
- return null;
201
- }
202
- const timestamp = `${dayjs().format("YYYY-MM-DDTHH-mm-ss-SSS")}Z`;
203
- const backupFileName = `config.json.${timestamp}.bak`;
204
- const backupPath = join(CCR_BACKUP_DIR, backupFileName);
205
- console.log(a.green(`${i18n.t("ccr:backupCcrConfig")}`));
206
- copyFileSync(CCR_CONFIG_FILE, backupPath);
207
- console.log(a.green(`\u2714 ${i18n.t("ccr:ccrBackupSuccess").replace("{path}", backupPath)}`));
208
- return backupPath;
209
- } catch (error) {
210
- console.error(a.red(`${i18n.t("ccr:ccrBackupFailed")}:`), error.message);
34
+ function backupExistingConfig() {
35
+ if (!exists(CLAUDE_DIR)) {
211
36
  return null;
212
37
  }
38
+ const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
39
+ const backupBaseDir = join(CLAUDE_DIR, "backup");
40
+ const backupDir = join(backupBaseDir, `backup_${timestamp}`);
41
+ ensureDir(backupDir);
42
+ const filter = (path) => {
43
+ return !path.includes("/backup");
44
+ };
45
+ copyDir(CLAUDE_DIR, backupDir, { filter });
46
+ return backupDir;
213
47
  }
214
- function readCcrConfig() {
215
- if (!existsSync(CCR_CONFIG_FILE)) {
216
- return null;
48
+ function copyConfigFiles(onlyMd = false) {
49
+ const currentFilePath = fileURLToPath(import.meta.url);
50
+ const distDir = dirname(dirname(currentFilePath));
51
+ const rootDir = dirname(distDir);
52
+ const baseTemplateDir = join(rootDir, "templates", "claude-code");
53
+ if (!onlyMd) {
54
+ const baseSettingsPath = join(baseTemplateDir, "common", "settings.json");
55
+ const destSettingsPath = join(CLAUDE_DIR, "settings.json");
56
+ if (exists(baseSettingsPath)) {
57
+ mergeSettingsFile(baseSettingsPath, destSettingsPath);
58
+ }
217
59
  }
218
- return readJsonConfig(CCR_CONFIG_FILE);
219
- }
220
- function writeCcrConfig(config) {
221
- ensureCcrConfigDir();
222
- writeJsonConfig(CCR_CONFIG_FILE, config);
223
60
  }
224
- async function configureCcrProxy(ccrConfig) {
225
- const settings = readJsonConfig(SETTINGS_FILE) || {};
226
- const host = ccrConfig.HOST || "127.0.0.1";
227
- const port = ccrConfig.PORT || 3456;
228
- const apiKey = ccrConfig.APIKEY || "sk-ccjk-x-ccr";
229
- if (!settings.env) {
230
- settings.env = {};
231
- }
232
- delete settings.env.ANTHROPIC_AUTH_TOKEN;
233
- settings.env.ANTHROPIC_BASE_URL = `http://${host}:${port}`;
234
- settings.env.ANTHROPIC_API_KEY = apiKey;
235
- writeJsonConfig(SETTINGS_FILE, settings);
61
+ function getDefaultSettings() {
236
62
  try {
237
- setPrimaryApiKey();
63
+ const currentFilePath = fileURLToPath(import.meta.url);
64
+ const distDir = dirname(dirname(currentFilePath));
65
+ const rootDir = dirname(distDir);
66
+ const templateSettingsPath = join(rootDir, "templates", "claude-code", "common", "settings.json");
67
+ return readJsonConfig(templateSettingsPath) || {};
238
68
  } catch (error) {
239
- ensureI18nInitialized();
240
- console.error(i18n.t("mcp:primaryApiKeySetFailed"), error);
69
+ console.error("Failed to read template settings", error);
70
+ return {};
241
71
  }
242
72
  }
243
- async function selectCcrPreset() {
244
- ensureI18nInitialized();
245
- console.log(a.green(`${i18n.t("ccr:fetchingPresets")}`));
246
- const presets = await fetchProviderPresets();
247
- if (!presets || presets.length === 0) {
248
- console.log(a.yellow(`${i18n.t("ccr:noPresetsAvailable")}`));
73
+ function configureApi(apiConfig) {
74
+ if (!apiConfig)
249
75
  return null;
76
+ const existingSettings = readJsonConfig(SETTINGS_FILE);
77
+ let settings;
78
+ if (existingSettings) {
79
+ settings = existingSettings;
80
+ } else {
81
+ settings = getDefaultSettings();
250
82
  }
251
- try {
252
- const choices = [
253
- {
254
- name: `1. ${i18n.t("ccr:skipOption")}`,
255
- value: "skip"
256
- },
257
- ...presets.map((p, index) => ({
258
- name: `${index + 2}. ${p.name}`,
259
- value: p
260
- }))
261
- ];
262
- const { preset } = await inquirer.prompt({
263
- type: "list",
264
- name: "preset",
265
- message: i18n.t("ccr:selectCcrPreset"),
266
- choices
267
- });
268
- return preset;
269
- } catch (error) {
270
- if (error.name === "ExitPromptError") {
271
- console.log(a.yellow(i18n.t("common:cancelled")));
272
- return null;
273
- }
274
- throw error;
83
+ if (!settings.env) {
84
+ settings.env = {};
275
85
  }
276
- }
277
- async function configureCcrWithPreset(preset) {
278
- ensureI18nInitialized();
279
- const provider = {
280
- name: preset.name,
281
- // Use the original name from JSON
282
- api_base_url: preset.baseURL || "",
283
- api_key: "",
284
- models: preset.models
285
- };
286
- if (preset.transformer) {
287
- provider.transformer = preset.transformer;
86
+ if (apiConfig.authType === "api_key") {
87
+ settings.env.ANTHROPIC_API_KEY = apiConfig.key;
88
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
89
+ } else if (apiConfig.authType === "auth_token") {
90
+ settings.env.ANTHROPIC_AUTH_TOKEN = apiConfig.key;
91
+ delete settings.env.ANTHROPIC_API_KEY;
288
92
  }
289
- if (preset.requiresApiKey) {
290
- try {
291
- const { apiKey } = await inquirer.prompt({
292
- type: "input",
293
- name: "apiKey",
294
- message: i18n.t("ccr:enterApiKeyForProvider").replace("{provider}", preset.name),
295
- validate: async (value) => !!value || i18n.t("api:keyRequired")
296
- });
297
- provider.api_key = apiKey;
298
- } catch (error) {
299
- if (error.name === "ExitPromptError") {
300
- throw error;
301
- }
302
- throw error;
303
- }
304
- } else {
305
- provider.api_key = "sk-free";
93
+ if (apiConfig.url) {
94
+ settings.env.ANTHROPIC_BASE_URL = apiConfig.url;
306
95
  }
307
- let defaultModel = preset.models[0];
308
- if (preset.models.length > 1) {
96
+ writeJsonConfig(SETTINGS_FILE, settings);
97
+ if (apiConfig.authType) {
309
98
  try {
310
- const { model } = await inquirer.prompt({
311
- type: "list",
312
- name: "model",
313
- message: i18n.t("ccr:selectDefaultModelForProvider").replace("{provider}", preset.name),
314
- choices: preset.models.map((m, index) => ({
315
- name: `${index + 1}. ${m}`,
316
- value: m
317
- }))
318
- });
319
- defaultModel = model;
99
+ setPrimaryApiKey();
320
100
  } catch (error) {
321
- if (error.name === "ExitPromptError") {
322
- throw error;
323
- }
324
- throw error;
101
+ ensureI18nInitialized();
102
+ console.error(i18n.t("mcp:primaryApiKeySetFailed"), error);
325
103
  }
326
104
  }
327
- const router = {
328
- default: `${preset.name},${defaultModel}`,
329
- // Use the original name
330
- background: `${preset.name},${defaultModel}`,
331
- think: `${preset.name},${defaultModel}`,
332
- longContext: `${preset.name},${defaultModel}`,
333
- longContextThreshold: 6e4,
334
- webSearch: `${preset.name},${defaultModel}`
335
- };
336
- const config = {
337
- LOG: true,
338
- CLAUDE_PATH: "",
339
- HOST: "127.0.0.1",
340
- PORT: 3456,
341
- APIKEY: "sk-ccjk-x-ccr",
342
- API_TIMEOUT_MS: "600000",
343
- PROXY_URL: "",
344
- transformers: [],
345
- Providers: [provider],
346
- Router: router
347
- };
348
- return config;
349
- }
350
- async function restartAndCheckCcrStatus() {
351
- ensureI18nInitialized();
352
105
  try {
353
- console.log(a.green(`${i18n.t("ccr:restartingCcr")}`));
354
- await execAsync("ccr restart");
355
- console.log(a.green(`\u2714 ${i18n.t("ccr:ccrRestartSuccess")}`));
356
- console.log(a.green(`${i18n.t("ccr:checkingCcrStatus")}`));
357
- const { stdout } = await execAsync("ccr status");
358
- console.log(a.gray(stdout));
106
+ addCompletedOnboarding();
359
107
  } catch (error) {
360
- console.error(a.red(`${i18n.t("ccr:ccrRestartFailed")}:`), error.message || error);
361
- if (process__default.env.DEBUG) {
362
- console.error("Full error:", error);
108
+ console.error("Failed to set onboarding flag", error);
109
+ }
110
+ const verification = readJsonConfig(SETTINGS_FILE);
111
+ if (verification?.env) {
112
+ const envKey = apiConfig.authType === "api_key" ? "ANTHROPIC_API_KEY" : "ANTHROPIC_AUTH_TOKEN";
113
+ if (verification.env[envKey] !== apiConfig.key) {
114
+ console.error(a.red("\u26A0 API config write verification failed \u2014 retrying..."));
115
+ const freshSettings = readJsonConfig(SETTINGS_FILE) || settings;
116
+ if (!freshSettings.env)
117
+ freshSettings.env = {};
118
+ if (apiConfig.authType === "api_key") {
119
+ freshSettings.env.ANTHROPIC_API_KEY = apiConfig.key;
120
+ delete freshSettings.env.ANTHROPIC_AUTH_TOKEN;
121
+ } else if (apiConfig.authType === "auth_token") {
122
+ freshSettings.env.ANTHROPIC_AUTH_TOKEN = apiConfig.key;
123
+ delete freshSettings.env.ANTHROPIC_API_KEY;
124
+ }
125
+ if (apiConfig.url) {
126
+ freshSettings.env.ANTHROPIC_BASE_URL = apiConfig.url;
127
+ }
128
+ writeJsonConfig(SETTINGS_FILE, freshSettings);
363
129
  }
364
130
  }
131
+ return apiConfig;
365
132
  }
366
- async function showConfigurationTips(apiKey) {
367
- ensureI18nInitialized();
368
- console.log(a.bold.cyan(`
369
- \u{1F4CC} ${i18n.t("ccr:configTips")}:`));
370
- console.log(a.green(` \u2022 ${i18n.t("ccr:advancedConfigTip")}`));
371
- console.log(a.green(` \u2022 ${i18n.t("ccr:manualConfigTip")}`));
372
- console.log(a.bold.yellow(` \u2022 ${i18n.t("ccr:useClaudeCommand")}`));
373
- if (apiKey) {
374
- console.log(a.bold.green(` \u2022 ${i18n.t("ccr:ccrUiApiKey") || "CCR UI API Key"}: ${apiKey}`));
375
- console.log(a.gray(` ${i18n.t("ccr:ccrUiApiKeyHint") || "Use this API key to login to CCR UI"}`));
376
- }
377
- console.log("");
378
- }
379
- function createDefaultCcrConfig() {
380
- return {
381
- LOG: false,
382
- CLAUDE_PATH: "",
383
- HOST: "127.0.0.1",
384
- PORT: 3456,
385
- APIKEY: "sk-ccjk-x-ccr",
386
- API_TIMEOUT_MS: "600000",
387
- PROXY_URL: "",
388
- transformers: [],
389
- Providers: [],
390
- // Empty providers array - user configures in UI
391
- Router: {}
392
- // Empty router configuration - user configures in UI
133
+ function configureHooks(hooks) {
134
+ let settings = getDefaultSettings();
135
+ const existingSettings = readJsonConfig(SETTINGS_FILE);
136
+ if (existingSettings) {
137
+ settings = existingSettings;
138
+ }
139
+ settings.hooks = {
140
+ ...settings.hooks || {},
141
+ ...hooks
393
142
  };
143
+ writeJsonConfig(SETTINGS_FILE, settings);
394
144
  }
395
- async function setupCcrConfiguration() {
396
- ensureI18nInitialized();
145
+ function mergeConfigs(sourceFile, targetFile) {
146
+ if (!exists(sourceFile))
147
+ return;
148
+ const target = readJsonConfig(targetFile) || {};
149
+ const source = readJsonConfig(sourceFile) || {};
150
+ const merged = deepMerge(target, source);
151
+ writeJsonConfig(targetFile, merged);
152
+ }
153
+ function updateCustomModel(primaryModel, haikuModel, sonnetModel, opusModel) {
154
+ if (!primaryModel?.trim() && !haikuModel?.trim() && !sonnetModel?.trim() && !opusModel?.trim()) {
155
+ return;
156
+ }
157
+ let settings = getDefaultSettings();
158
+ const existingSettings = readJsonConfig(SETTINGS_FILE);
159
+ if (existingSettings) {
160
+ settings = existingSettings;
161
+ }
162
+ delete settings.model;
163
+ settings.env = settings.env || {};
164
+ clearModelEnv(settings.env, "override");
165
+ if (primaryModel?.trim()) {
166
+ settings.env.ANTHROPIC_MODEL = primaryModel.trim();
167
+ }
168
+ if (haikuModel?.trim()) {
169
+ settings.env.ANTHROPIC_SMALL_FAST_MODEL = haikuModel.trim();
170
+ settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = haikuModel.trim();
171
+ }
172
+ if (sonnetModel?.trim())
173
+ settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL = sonnetModel.trim();
174
+ if (opusModel?.trim())
175
+ settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = opusModel.trim();
176
+ writeJsonConfig(SETTINGS_FILE, settings);
177
+ }
178
+ function updateDefaultModel(model) {
179
+ let settings = getDefaultSettings();
180
+ const existingSettings = readJsonConfig(SETTINGS_FILE);
181
+ if (existingSettings) {
182
+ settings = existingSettings;
183
+ }
184
+ if (!settings.env) {
185
+ settings.env = {};
186
+ }
187
+ if (model !== "custom") {
188
+ clearModelEnv(settings.env);
189
+ }
190
+ if (model === "default" || model === "custom") {
191
+ delete settings.model;
192
+ } else {
193
+ settings.model = model;
194
+ }
195
+ writeJsonConfig(SETTINGS_FILE, settings);
196
+ }
197
+ function mergeSettingsFile(templatePath, targetPath) {
397
198
  try {
398
- const existingConfig = readCcrConfig();
399
- if (existingConfig) {
400
- console.log(a.green(`\u2139 ${i18n.t("ccr:existingCcrConfig")}`));
401
- let shouldBackupAndReconfigure = false;
402
- try {
403
- shouldBackupAndReconfigure = await promptBoolean({
404
- message: i18n.t("ccr:overwriteCcrConfig"),
405
- defaultValue: false
406
- });
407
- } catch (error) {
408
- if (error.name === "ExitPromptError") {
409
- console.log(a.yellow(i18n.t("common:cancelled")));
410
- return false;
411
- }
412
- throw error;
413
- }
414
- if (!shouldBackupAndReconfigure) {
415
- console.log(a.yellow(`${i18n.t("ccr:keepingExistingConfig")}`));
416
- await configureCcrProxy(existingConfig);
417
- try {
418
- const { manageApiKeyApproval } = await import('./claude-config.mjs').then(function (n) { return n.h; });
419
- const apiKey = existingConfig.APIKEY || "sk-ccjk-x-ccr";
420
- manageApiKeyApproval(apiKey);
421
- console.log(a.green(`\u2714 ${i18n.t("ccr:apiKeyApprovalSuccess")}`));
422
- } catch (error) {
423
- console.error(a.red(`${i18n.t("ccr:apiKeyApprovalFailed")}:`), error);
199
+ const templateSettings = readJsonConfig(templatePath);
200
+ if (!templateSettings) {
201
+ console.error("Failed to read template settings");
202
+ return;
203
+ }
204
+ if (!exists(targetPath)) {
205
+ copyFile(templatePath, targetPath);
206
+ return;
207
+ }
208
+ const existingSettings = readJsonConfig(targetPath) || {};
209
+ const mergedEnv = {
210
+ ...templateSettings.env || {},
211
+ // Template env vars first
212
+ ...existingSettings.env || {}
213
+ // User's env vars override (preserving API keys, etc.)
214
+ };
215
+ const mergedSettings = { ...templateSettings };
216
+ const schemaCriticalFields = [
217
+ "$schema",
218
+ "attribution",
219
+ "fileSuggestion",
220
+ "permissions"
221
+ ];
222
+ const removeNullFields = [
223
+ "plansDirectory",
224
+ "contextFiles"
225
+ ];
226
+ for (const [key, value] of Object.entries(existingSettings)) {
227
+ if (!schemaCriticalFields.includes(key)) {
228
+ if (removeNullFields.includes(key) && value === null) {
229
+ continue;
424
230
  }
425
- return true;
231
+ mergedSettings[key] = value;
426
232
  }
427
- backupCcrConfig();
428
233
  }
429
- const preset = await selectCcrPreset();
430
- if (!preset) {
431
- return false;
234
+ mergedSettings.env = mergedEnv;
235
+ if (mergedSettings.permissions && mergedSettings.permissions.allow) {
236
+ mergedSettings.permissions.allow = mergeAndCleanPermissions(
237
+ templateSettings.permissions?.allow,
238
+ existingSettings.permissions?.allow
239
+ );
432
240
  }
433
- let config;
434
- if (preset === "skip") {
435
- console.log(a.yellow(`${i18n.t("ccr:skipConfiguring")}`));
436
- config = createDefaultCcrConfig();
241
+ if (mergedSettings.plansDirectory === null) {
242
+ delete mergedSettings.plansDirectory;
243
+ }
244
+ writeJsonConfig(targetPath, mergedSettings);
245
+ } catch (error) {
246
+ console.error("Failed to merge settings", error);
247
+ if (exists(targetPath)) {
248
+ console.log("Preserving existing settings");
437
249
  } else {
438
- config = await configureCcrWithPreset(preset);
250
+ copyFile(templatePath, targetPath);
439
251
  }
440
- writeCcrConfig(config);
441
- console.log(a.green(`\u2714 ${i18n.t("ccr:ccrConfigSuccess")}`));
442
- await configureCcrProxy(config);
443
- console.log(a.green(`\u2714 ${i18n.t("ccr:proxyConfigSuccess")}`));
444
- await restartAndCheckCcrStatus();
445
- await showConfigurationTips(config.APIKEY);
446
- try {
447
- addCompletedOnboarding();
448
- } catch (error) {
449
- console.error(a.red(i18n.t("errors:failedToSetOnboarding")), error);
252
+ }
253
+ }
254
+ function getExistingModelConfig() {
255
+ const settings = readJsonConfig(SETTINGS_FILE);
256
+ if (!settings) {
257
+ return null;
258
+ }
259
+ const hasModelEnv = MODEL_ENV_KEYS.some((key) => {
260
+ const value = settings.env?.[key];
261
+ return value !== void 0 && value !== null && value !== "";
262
+ });
263
+ if (hasModelEnv) {
264
+ return "custom";
265
+ }
266
+ if (!settings.model) {
267
+ return "default";
268
+ }
269
+ const validModels = ["opus", "sonnet", "sonnet[1m]"];
270
+ if (validModels.includes(settings.model)) {
271
+ return settings.model;
272
+ }
273
+ return "default";
274
+ }
275
+ function getExistingCustomModelConfig() {
276
+ const settings = readJsonConfig(SETTINGS_FILE);
277
+ if (!settings || !settings.env) {
278
+ return null;
279
+ }
280
+ const {
281
+ ANTHROPIC_MODEL,
282
+ ANTHROPIC_SMALL_FAST_MODEL,
283
+ ANTHROPIC_DEFAULT_HAIKU_MODEL,
284
+ ANTHROPIC_DEFAULT_SONNET_MODEL,
285
+ ANTHROPIC_DEFAULT_OPUS_MODEL
286
+ } = settings.env;
287
+ if (!ANTHROPIC_MODEL && !ANTHROPIC_DEFAULT_HAIKU_MODEL && !ANTHROPIC_DEFAULT_SONNET_MODEL && !ANTHROPIC_DEFAULT_OPUS_MODEL) {
288
+ return null;
289
+ }
290
+ return {
291
+ primaryModel: ANTHROPIC_MODEL,
292
+ haikuModel: ANTHROPIC_DEFAULT_HAIKU_MODEL || ANTHROPIC_SMALL_FAST_MODEL,
293
+ sonnetModel: ANTHROPIC_DEFAULT_SONNET_MODEL,
294
+ opusModel: ANTHROPIC_DEFAULT_OPUS_MODEL
295
+ };
296
+ }
297
+ function getExistingApiConfig() {
298
+ const settings = readJsonConfig(SETTINGS_FILE);
299
+ if (!settings || !settings.env) {
300
+ return null;
301
+ }
302
+ const { ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL } = settings.env;
303
+ if (!ANTHROPIC_BASE_URL && !ANTHROPIC_API_KEY && !ANTHROPIC_AUTH_TOKEN) {
304
+ return null;
305
+ }
306
+ let authType;
307
+ let key;
308
+ if (ANTHROPIC_AUTH_TOKEN) {
309
+ authType = "auth_token";
310
+ key = ANTHROPIC_AUTH_TOKEN;
311
+ } else if (ANTHROPIC_API_KEY) {
312
+ authType = "api_key";
313
+ key = ANTHROPIC_API_KEY;
314
+ }
315
+ return {
316
+ url: ANTHROPIC_BASE_URL || "",
317
+ key: key || "",
318
+ authType
319
+ };
320
+ }
321
+ function applyAiLanguageDirective(aiOutputLang) {
322
+ const claudeFile = join(CLAUDE_DIR, "CLAUDE.md");
323
+ let directive = "";
324
+ if (aiOutputLang === "custom") {
325
+ return;
326
+ } else if (AI_OUTPUT_LANGUAGES[aiOutputLang]) {
327
+ directive = AI_OUTPUT_LANGUAGES[aiOutputLang].directive;
328
+ } else {
329
+ directive = `Always respond in ${aiOutputLang}`;
330
+ }
331
+ const frontmatter = "---\nscope: user\n---";
332
+ writeFileAtomic(claudeFile, `${frontmatter}
333
+
334
+ ${directive}`);
335
+ }
336
+ function switchToOfficialLogin() {
337
+ try {
338
+ ensureI18nInitialized();
339
+ const settings = readJsonConfig(SETTINGS_FILE) || {};
340
+ if (settings.env) {
341
+ delete settings.env.ANTHROPIC_BASE_URL;
342
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
343
+ delete settings.env.ANTHROPIC_API_KEY;
450
344
  }
451
- try {
452
- const { manageApiKeyApproval } = await import('./claude-config.mjs').then(function (n) { return n.h; });
453
- const apiKey = config.APIKEY || "sk-ccjk-x-ccr";
454
- manageApiKeyApproval(apiKey);
455
- console.log(a.green(`\u2714 ${i18n.t("ccr:apiKeyApprovalSuccess")}`));
456
- } catch (error) {
457
- console.error(a.red(`${i18n.t("ccr:apiKeyApprovalFailed")}:`), error);
345
+ writeJsonConfig(SETTINGS_FILE, settings);
346
+ const vscConfig = readJsonConfig(CLAUDE_VSC_CONFIG_FILE);
347
+ if (vscConfig) {
348
+ delete vscConfig.primaryApiKey;
349
+ writeJsonConfig(CLAUDE_VSC_CONFIG_FILE, vscConfig);
458
350
  }
351
+ console.log(i18n.t("api:officialLoginConfigured"));
459
352
  return true;
460
353
  } catch (error) {
461
- if (error.name === "ExitPromptError") {
462
- console.log(a.yellow(i18n.t("common:cancelled")));
463
- return false;
464
- }
465
- console.error(a.red(`${i18n.t("ccr:ccrConfigFailed")}:`), error);
354
+ ensureI18nInitialized();
355
+ console.error(i18n.t("api:officialLoginFailed"), error);
466
356
  return false;
467
357
  }
468
358
  }
469
- async function configureCcrFeature() {
359
+ async function promptApiConfigurationAction() {
470
360
  ensureI18nInitialized();
471
- const backupDir = backupExistingConfig();
472
- if (backupDir) {
473
- console.log(a.gray(`\u2714 ${i18n.t("configuration:backupSuccess")}: ${backupDir}`));
361
+ const existingConfig = getExistingApiConfig();
362
+ if (!existingConfig) {
363
+ return null;
474
364
  }
475
- await setupCcrConfiguration();
365
+ console.log(`
366
+ ${a.green(`\u2139 ${i18n.t("api:existingApiConfig")}`)}`);
367
+ console.log(a.gray(` ${i18n.t("api:apiConfigUrl")}: ${existingConfig.url || "N/A"}`));
368
+ console.log(a.gray(` ${i18n.t("api:apiConfigKey")}: ${existingConfig.key ? `***${existingConfig.key.slice(-4)}` : "N/A"}`));
369
+ console.log(a.gray(` ${i18n.t("api:apiConfigAuthType")}: ${existingConfig.authType || "N/A"}
370
+ `));
371
+ const { choice } = await inquirer.prompt({
372
+ type: "list",
373
+ name: "choice",
374
+ message: i18n.t("api:selectCustomConfigAction"),
375
+ choices: [
376
+ {
377
+ name: i18n.t("api:modifyPartialConfig"),
378
+ value: "modify-partial"
379
+ },
380
+ {
381
+ name: i18n.t("api:modifyAllConfig"),
382
+ value: "modify-all"
383
+ },
384
+ {
385
+ name: i18n.t("api:keepExistingConfig"),
386
+ value: "keep-existing"
387
+ }
388
+ ]
389
+ });
390
+ return choice || null;
476
391
  }
477
392
 
478
- export { backupCcrConfig, configureCcrFeature, configureCcrProxy, configureCcrWithPreset, createDefaultCcrConfig, ensureCcrConfigDir, readCcrConfig, restartAndCheckCcrStatus, selectCcrPreset, setupCcrConfiguration, showConfigurationTips, writeCcrConfig };
393
+ const config = {
394
+ __proto__: null,
395
+ applyAiLanguageDirective: applyAiLanguageDirective,
396
+ backupExistingConfig: backupExistingConfig,
397
+ configureApi: configureApi,
398
+ configureHooks: configureHooks,
399
+ copyConfigFiles: copyConfigFiles,
400
+ ensureClaudeDir: ensureClaudeDir,
401
+ getExistingApiConfig: getExistingApiConfig,
402
+ getExistingCustomModelConfig: getExistingCustomModelConfig,
403
+ getExistingModelConfig: getExistingModelConfig,
404
+ mergeConfigs: mergeConfigs,
405
+ mergeSettingsFile: mergeSettingsFile,
406
+ promptApiConfigurationAction: promptApiConfigurationAction,
407
+ switchToOfficialLogin: switchToOfficialLogin,
408
+ updateCustomModel: updateCustomModel,
409
+ updateDefaultModel: updateDefaultModel
410
+ };
411
+
412
+ export { getExistingCustomModelConfig as a, backupExistingConfig as b, copyConfigFiles as c, updateDefaultModel as d, applyAiLanguageDirective as e, getExistingApiConfig as f, getExistingModelConfig as g, configureApi as h, clearModelEnv as i, ensureClaudeDir as j, config as k, promptApiConfigurationAction as p, switchToOfficialLogin as s, updateCustomModel as u };