@tkpdx01/ccc 1.3.6 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,15 +4,6 @@ Claude Code Settings Launcher - Launch Claude Code with different settings profi
4
4
 
5
5
  Claude Code 设置启动器 - 使用不同的 settings profile 文件启动 Claude Code,可同时运行多个使用不同 API 配置的 Claude 实例。
6
6
 
7
- ## Compatibility / 兼容性
8
-
9
- Tested export file versions / 已测试的导出文件版本:
10
-
11
- | Tool | Version | Export Format |
12
- |------|---------|---------------|
13
- | [cc-switch](https://github.com/farion1231/cc-switch) | 3.8.2 | SQL |
14
- | [All API Hub](https://github.com/qixing-jk/all-api-hub) | v2.26.1 | JSON |
15
-
16
7
  ## Installation / 安装
17
8
 
18
9
  ```bash
@@ -38,7 +29,6 @@ ccc list -v # List with URLs / 显示 API URLs
38
29
  ccc show [profile] # Show config / 显示完整配置
39
30
  ccc use <profile> # Set default / 设置默认
40
31
  ccc new [name] # Create from template / 从模板创建
41
- ccc import # Import from cc-switch SQL or All API Hub JSON / 导入 cc-switch SQL 或 All API Hub JSON
42
32
  ccc sync [profile] # Sync from template / 从模板同步
43
33
  ccc sync -a # Sync all / 同步所有
44
34
  ccc edit [profile] # Edit profile / 编辑配置
@@ -58,9 +48,8 @@ ccc webdav status # View sync status / 查看同步状态
58
48
 
59
49
  - **Multiple Profiles / 多配置**: Manage different API configurations
60
50
  - **Template Support / 模板**: Based on `~/.claude/settings.json`
61
- - **Smart Import / 智能导入**: Auto-detect API URL and token
62
51
  - **Sync Settings / 同步**: Update from template, preserve credentials
63
- - **Claude Env Defaults / Claude 环境变量默认值**: Auto-ensure these values in the `env` section of both `~/.claude/settings.json` and each profile: `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, `CLAUDE_CODE_ATTRIBUTION_HEADER=0`, `DISABLE_INSTALLATION_CHECKS=1`
52
+ - **Claude Env Defaults / Claude 环境变量默认值**: Auto-ensure these values in the `env` section of both `~/.claude/settings.json` and each profile: `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, `CLAUDE_CODE_ATTRIBUTION_HEADER=0`, `DISABLE_INSTALLATION_CHECKS=1`, `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`
64
53
  - **WebDAV Cloud Sync / 云同步**: Encrypted sync across devices
65
54
 
66
55
  ## Sync Command / 同步命令
package/index.js CHANGED
@@ -12,7 +12,6 @@ import {
12
12
  listCommand,
13
13
  useCommand,
14
14
  showCommand,
15
- importCommand,
16
15
  newCommand,
17
16
  editCommand,
18
17
  deleteCommand,
@@ -27,13 +26,12 @@ const program = new Command();
27
26
  program
28
27
  .name('ccc')
29
28
  .description('Claude Code Settings Launcher - 管理多个 Claude Code 配置文件')
30
- .version('1.3.3');
29
+ .version('1.5.0');
31
30
 
32
31
  // 注册所有命令
33
32
  listCommand(program);
34
33
  useCommand(program);
35
34
  showCommand(program);
36
- importCommand(program);
37
35
  newCommand(program);
38
36
  editCommand(program);
39
37
  deleteCommand(program);
@@ -51,7 +49,7 @@ program
51
49
 
52
50
  if (profile) {
53
51
  // 检查是否是子命令
54
- if (['list', 'ls', 'use', 'show', 'import', 'if', 'new', 'edit', 'delete', 'rm', 'sync', 'webdav', 'help'].includes(profile)) {
52
+ if (['list', 'ls', 'use', 'show', 'new', 'edit', 'delete', 'rm', 'sync', 'webdav', 'help'].includes(profile)) {
55
53
  return; // 让子命令处理
56
54
  }
57
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tkpdx01/ccc",
3
- "version": "1.3.6",
3
+ "version": "1.5.0",
4
4
  "description": "Claude Code Settings Launcher - Manage multiple Claude Code profiles",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -16,7 +16,6 @@ export function showHelp() {
16
16
  console.log(chalk.gray(' ccc show [profile] ') + '显示完整配置');
17
17
  console.log(chalk.gray(' ccc use <profile> ') + '设置默认配置');
18
18
  console.log(chalk.gray(' ccc new [name] ') + '创建新的影子配置');
19
- console.log(chalk.gray(' ccc import <file> ') + '从文件导入(自动识别格式)');
20
19
  console.log(chalk.gray(' ccc sync [profile] ') + '从模板同步配置(保留 API 凭证)');
21
20
  console.log(chalk.gray(' ccc sync --all ') + '同步所有配置');
22
21
  console.log(chalk.gray(' ccc edit [profile] ') + '编辑配置');
@@ -37,17 +36,11 @@ export function showHelp() {
37
36
  console.log(chalk.gray(' ~/.ccc/.sync_key ') + '本地密码缓存(机器指纹加密)');
38
37
  console.log();
39
38
 
40
- console.log(chalk.yellow(' 支持的导入格式:'));
41
- console.log(chalk.gray(' CC-Switch SQL ') + '自动识别 INSERT INTO providers 语句');
42
- console.log(chalk.gray(' All API Hub JSON ') + '自动识别 accounts.accounts 结构');
43
- console.log();
44
-
45
39
  console.log(chalk.yellow(' 示例:'));
46
40
  console.log(chalk.gray(' ccc ls ') + '查看配置列表和序号');
47
41
  console.log(chalk.gray(' ccc 3 ') + '启动第 3 个配置');
48
42
  console.log(chalk.gray(' ccc 3 -d ') + '启动第 3 个配置 + 跳过权限');
49
43
  console.log(chalk.gray(' ccc kfc ') + '使用名称启动');
50
- console.log(chalk.gray(' ccc import export.sql ') + '从文件导入配置');
51
44
  console.log(chalk.gray(' ccc webdav push ') + '推送配置到云端');
52
45
  console.log();
53
46
  }
@@ -1,7 +1,6 @@
1
1
  export { listCommand } from './list.js';
2
2
  export { useCommand } from './use.js';
3
3
  export { showCommand } from './show.js';
4
- export { importCommand } from './import.js';
5
4
  export { newCommand } from './new.js';
6
5
  export { editCommand } from './edit.js';
7
6
  export { deleteCommand } from './delete.js';
package/src/profiles.js CHANGED
@@ -135,7 +135,8 @@ export function ensureRequiredClaudeEnvSettings() {
135
135
  return ensureClaudeEnvSettings({
136
136
  CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
137
137
  CLAUDE_CODE_ATTRIBUTION_HEADER: '0',
138
- DISABLE_INSTALLATION_CHECKS: '1'
138
+ DISABLE_INSTALLATION_CHECKS: '1',
139
+ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1'
139
140
  });
140
141
  }
141
142
 
@@ -157,6 +158,12 @@ export function applyClaudeSettingsExtras(target) {
157
158
 
158
159
  let changed = false;
159
160
 
161
+ // 确保 hasCompletedOnboarding 为 true
162
+ if (target.hasCompletedOnboarding !== true) {
163
+ target.hasCompletedOnboarding = true;
164
+ changed = true;
165
+ }
166
+
160
167
  // 确保 attribution 禁用(commit/pr 为空字符串)
161
168
  if (!target.attribution || typeof target.attribution !== 'object' || Array.isArray(target.attribution)) {
162
169
  target.attribution = { commit: '', pr: '' };
@@ -246,6 +253,7 @@ export function createProfileFromTemplate(name, apiUrl, apiKey) {
246
253
  template.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = '1';
247
254
  template.env.CLAUDE_CODE_ATTRIBUTION_HEADER = '0';
248
255
  template.env.DISABLE_INSTALLATION_CHECKS = '1';
256
+ template.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';
249
257
 
250
258
  // 只设置 API 凭证到 env
251
259
  template.env.ANTHROPIC_AUTH_TOKEN = apiKey;
@@ -284,6 +292,7 @@ export function syncProfileWithTemplate(name) {
284
292
  newProfile.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = '1';
285
293
  newProfile.env.CLAUDE_CODE_ATTRIBUTION_HEADER = '0';
286
294
  newProfile.env.DISABLE_INSTALLATION_CHECKS = '1';
295
+ newProfile.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';
287
296
  applyClaudeSettingsExtras(newProfile);
288
297
 
289
298
  saveProfile(name, newProfile);
package/src/utils.js CHANGED
@@ -1,56 +1,3 @@
1
- // 从文本中提取 URL 和 token
2
- export function extractFromText(text) {
3
- // 提取 URL
4
- const urlRegex = /https?:\/\/[^\s"'<>]+/gi;
5
- const urls = text.match(urlRegex) || [];
6
-
7
- // 提取 sk token
8
- const tokenRegex = /sk-[a-zA-Z0-9_-]+/g;
9
- const tokens = text.match(tokenRegex) || [];
10
-
11
- return { urls, tokens };
12
- }
13
-
14
- // 从 URL 获取域名作为名称(去掉协议和www子域名)
15
- export function getDomainName(url) {
16
- try {
17
- const urlObj = new URL(url);
18
- // 获取完整域名
19
- let hostname = urlObj.hostname;
20
- // 移除 www. 前缀
21
- hostname = hostname.replace(/^www\./, '');
22
- return hostname;
23
- } catch {
24
- return null;
25
- }
26
- }
27
-
28
- // 生成安全的 profile 名称
29
- export function sanitizeProfileName(name) {
30
- return name
31
- .replace(/[<>:"/\\|?*]/g, '_') // 替换 Windows 非法字符
32
- .replace(/\s+/g, '_') // 替换空格
33
- .replace(/_+/g, '_') // 合并多个下划线
34
- .replace(/^_|_$/g, '') // 去除首尾下划线
35
- .substring(0, 50); // 限制长度
36
- }
37
-
38
- // 将导入的配置转换为影子配置格式(只包含 API 凭证)
39
- export function convertToClaudeSettings(provider, template) {
40
- const config = provider.settingsConfig || {};
41
-
42
- // 从 env 中提取 API 信息
43
- const env = config.env || {};
44
- const apiKey = env.ANTHROPIC_AUTH_TOKEN || env.ANTHROPIC_API_KEY || config.apiKey || '';
45
- const apiUrl = env.ANTHROPIC_BASE_URL || config.apiUrl || provider.websiteUrl || '';
46
-
47
- // 影子配置只存储 API 凭证,不用 env 包裹
48
- return {
49
- ANTHROPIC_AUTH_TOKEN: apiKey,
50
- ANTHROPIC_BASE_URL: apiUrl
51
- };
52
- }
53
-
54
1
  // 格式化显示配置值
55
2
  export function formatValue(key, value) {
56
3
  if ((key === 'apiKey' || key === 'ANTHROPIC_AUTH_TOKEN') && value) {
@@ -1,357 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import chalk from 'chalk';
4
- import inquirer from 'inquirer';
5
- import readline from 'readline';
6
- import Table from 'cli-table3';
7
- import {
8
- ensureDirs,
9
- getProfiles,
10
- getProfilePath,
11
- setDefaultProfile,
12
- profileExists,
13
- createProfileFromTemplate
14
- } from '../profiles.js';
15
- import { parseCCSwitchSQL, parseAllApiHubJSON, detectFileFormat } from '../parsers.js';
16
- import { extractFromText, getDomainName, sanitizeProfileName } from '../utils.js';
17
-
18
- // 生成唯一的 profile 名称(如果重复则加后缀 token2, token3...)
19
- function getUniqueProfileName(baseName, usedNames) {
20
- if (!usedNames.has(baseName)) {
21
- usedNames.add(baseName);
22
- return baseName;
23
- }
24
-
25
- let suffix = 2;
26
- let newName = `${baseName}-token${suffix}`;
27
- while (usedNames.has(newName)) {
28
- suffix++;
29
- newName = `${baseName}-token${suffix}`;
30
- }
31
- usedNames.add(newName);
32
- return newName;
33
- }
34
-
35
- // 交互式导入命令
36
- export function importCommand(program) {
37
- // ccc import <file> - 从文件导入(自动识别格式)
38
- program
39
- .command('import <file>')
40
- .aliases(['if'])
41
- .description('从文件导入配置(自动识别 CC-Switch SQL 或 All API Hub JSON)')
42
- .action(async (file) => {
43
- // 检查文件是否存在
44
- const filePath = path.resolve(file);
45
- if (!fs.existsSync(filePath)) {
46
- console.log(chalk.red(`文件不存在: ${filePath}`));
47
- process.exit(1);
48
- }
49
-
50
- console.log(chalk.cyan(`读取文件: ${filePath}\n`));
51
- const content = fs.readFileSync(filePath, 'utf-8');
52
-
53
- // 自动检测格式
54
- const format = detectFileFormat(content);
55
-
56
- if (!format) {
57
- console.log(chalk.red('无法识别文件格式'));
58
- console.log(chalk.gray('支持的格式:'));
59
- console.log(chalk.gray(' - CC-Switch SQL 导出文件 (.sql)'));
60
- console.log(chalk.gray(' - All API Hub JSON 导出文件 (.json)'));
61
- process.exit(1);
62
- }
63
-
64
- let providers = [];
65
- let formatName = '';
66
-
67
- if (format === 'ccswitch') {
68
- formatName = 'CC-Switch SQL';
69
- providers = parseCCSwitchSQL(content);
70
- } else if (format === 'allapihub') {
71
- formatName = 'All API Hub JSON';
72
- providers = parseAllApiHubJSON(content);
73
- }
74
-
75
- if (providers.length === 0) {
76
- console.log(chalk.yellow('未找到有效的配置'));
77
- process.exit(0);
78
- }
79
-
80
- console.log(chalk.green(`✓ 识别到 ${formatName} 格式`));
81
- console.log(chalk.green(`✓ 找到 ${providers.length} 个配置\n`));
82
-
83
- // 显示找到的配置
84
- const table = new Table({
85
- head: [chalk.cyan('#'), chalk.cyan('Profile 名称'), chalk.cyan('API URL'), chalk.cyan('备注')],
86
- style: { head: [], border: [] }
87
- });
88
-
89
- // 用于跟踪已使用的名称(预览阶段)
90
- const previewUsedNames = new Set();
91
-
92
- providers.forEach((p, i) => {
93
- const url = p.settingsConfig?.env?.ANTHROPIC_BASE_URL || p.websiteUrl || '(未设置)';
94
- // 使用 API URL 生成 profile 名称,重复时加后缀
95
- const baseName = sanitizeProfileName(getDomainName(url) || p.name);
96
- const profileName = getUniqueProfileName(baseName, previewUsedNames);
97
- let note = '';
98
-
99
- if (format === 'ccswitch') {
100
- note = p.settingsConfig?.model || '(默认模型)';
101
- } else if (format === 'allapihub') {
102
- note = p.meta?.health === 'healthy' ? chalk.green('健康') :
103
- p.meta?.health === 'warning' ? chalk.yellow('警告') :
104
- p.meta?.health === 'error' ? chalk.red('错误') : chalk.gray('未知');
105
- }
106
-
107
- table.push([i + 1, profileName, url, note]);
108
- });
109
-
110
- console.log(table.toString());
111
- console.log();
112
-
113
- // All API Hub 特殊警告
114
- if (format === 'allapihub') {
115
- console.log(chalk.yellow('⚠ 注意: All API Hub 的 access_token 格式可能需要手动调整'));
116
- console.log(chalk.gray(' 导入后可使用 "ccc edit <profile>" 修改 API Key\n'));
117
- }
118
-
119
- // 确认导入
120
- const { confirmImport } = await inquirer.prompt([
121
- {
122
- type: 'confirm',
123
- name: 'confirmImport',
124
- message: `确定要导入这 ${providers.length} 个配置吗?`,
125
- default: true
126
- }
127
- ]);
128
-
129
- if (!confirmImport) {
130
- console.log(chalk.yellow('已取消'));
131
- process.exit(0);
132
- }
133
-
134
- // 选择要导入的配置
135
- // 重新计算名称用于选择列表
136
- const selectionUsedNames = new Set();
137
- const { selection } = await inquirer.prompt([
138
- {
139
- type: 'checkbox',
140
- name: 'selection',
141
- message: '选择要导入的配置 (空格选择,回车确认):',
142
- choices: providers.map((p, i) => {
143
- const url = p.settingsConfig?.env?.ANTHROPIC_BASE_URL || p.websiteUrl || '';
144
- // 使用 API URL 生成 profile 名称,重复时加后缀
145
- const baseName = sanitizeProfileName(getDomainName(url) || p.name);
146
- const profileName = getUniqueProfileName(baseName, selectionUsedNames);
147
- return {
148
- name: `${profileName} (${url})`,
149
- value: i,
150
- checked: format === 'allapihub' ? p.meta?.health === 'healthy' : true
151
- };
152
- })
153
- }
154
- ]);
155
-
156
- const selectedProviders = selection.map(i => providers[i]);
157
-
158
- if (selectedProviders.length === 0) {
159
- console.log(chalk.yellow('未选择任何配置'));
160
- process.exit(0);
161
- }
162
-
163
- // 导入选中的配置
164
- ensureDirs();
165
- let imported = 0;
166
- let skipped = 0;
167
-
168
- // 用于跟踪导入时已使用的名称(包括已存在的 profiles)
169
- const importUsedNames = new Set(getProfiles());
170
-
171
- for (const provider of selectedProviders) {
172
- const url = provider.settingsConfig?.env?.ANTHROPIC_BASE_URL || provider.websiteUrl || '';
173
- // 使用 API URL 生成 profile 名称,重复时加后缀
174
- const baseName = sanitizeProfileName(getDomainName(url) || provider.name);
175
- const profileName = getUniqueProfileName(baseName, importUsedNames);
176
- const profilePath = getProfilePath(profileName);
177
-
178
- // 检查是否已存在
179
- if (fs.existsSync(profilePath)) {
180
- const { overwrite } = await inquirer.prompt([
181
- {
182
- type: 'confirm',
183
- name: 'overwrite',
184
- message: `配置 "${profileName}" 已存在,是否覆盖?`,
185
- default: false
186
- }
187
- ]);
188
-
189
- if (!overwrite) {
190
- skipped++;
191
- continue;
192
- }
193
- }
194
-
195
- // 从 provider 中提取 API 凭证
196
- const config = provider.settingsConfig || {};
197
- const env = config.env || {};
198
- const apiKey = env.ANTHROPIC_AUTH_TOKEN || env.ANTHROPIC_API_KEY || config.apiKey || '';
199
- const apiUrl = env.ANTHROPIC_BASE_URL || config.apiUrl || provider.websiteUrl || '';
200
-
201
- // 使用主配置模板创建完整的 profile(确保有 env 对象)
202
- createProfileFromTemplate(profileName, apiUrl, apiKey);
203
- console.log(chalk.green(`✓ ${profileName}`));
204
- imported++;
205
- }
206
-
207
- console.log(chalk.green(`\n✓ 导入完成: ${imported} 个成功` + (skipped > 0 ? `, ${skipped} 个跳过` : '')));
208
-
209
- // 如果是第一次导入,设置默认
210
- const profiles = getProfiles();
211
- if (profiles.length === imported && imported > 0) {
212
- const { setDefault } = await inquirer.prompt([
213
- {
214
- type: 'confirm',
215
- name: 'setDefault',
216
- message: '是否设置第一个配置为默认?',
217
- default: true
218
- }
219
- ]);
220
-
221
- if (setDefault) {
222
- setDefaultProfile(profiles[0]);
223
- console.log(chalk.green(`✓ 已设置 "${profiles[0]}" 为默认配置`));
224
- }
225
- }
226
- });
227
- }
228
-
229
- // 交互式粘贴导入(原 importProfile 功能,可选添加)
230
- export async function interactiveImport() {
231
- console.log(chalk.cyan('请粘贴包含 API URL 和 SK Token 的文本,然后按两次回车确认:'));
232
- console.log(chalk.gray('(支持自动识别 URL 和 sk-xxx 格式的 token)'));
233
- console.log();
234
-
235
- const rl = readline.createInterface({
236
- input: process.stdin,
237
- output: process.stdout
238
- });
239
-
240
- let inputText = '';
241
- let emptyLineCount = 0;
242
-
243
- const text = await new Promise((resolve) => {
244
- rl.on('line', (line) => {
245
- if (line === '') {
246
- emptyLineCount++;
247
- if (emptyLineCount >= 2) {
248
- rl.close();
249
- resolve(inputText);
250
- }
251
- } else {
252
- emptyLineCount = 0;
253
- inputText += line + '\n';
254
- }
255
- });
256
- });
257
-
258
- const { urls, tokens } = extractFromText(text);
259
-
260
- if (urls.length === 0 && tokens.length === 0) {
261
- console.log(chalk.red('未找到有效的 URL 或 Token'));
262
- process.exit(1);
263
- }
264
-
265
- console.log();
266
- console.log(chalk.green('识别到的内容:'));
267
-
268
- if (urls.length > 0) {
269
- console.log(chalk.cyan('URLs:'));
270
- urls.forEach(u => console.log(` - ${u}`));
271
- }
272
-
273
- if (tokens.length > 0) {
274
- console.log(chalk.cyan('Tokens:'));
275
- tokens.forEach(t => console.log(` - ${t.substring(0, 10)}...`));
276
- }
277
-
278
- // 使用第一个 URL 的域名作为默认名称
279
- let defaultName = 'custom';
280
- if (urls.length > 0) {
281
- const domainName = getDomainName(urls[0]);
282
- if (domainName) {
283
- defaultName = domainName;
284
- }
285
- }
286
-
287
- const { profileName, apiUrl, apiKey } = await inquirer.prompt([
288
- {
289
- type: 'input',
290
- name: 'profileName',
291
- message: 'Profile 名称:',
292
- default: defaultName
293
- },
294
- {
295
- type: 'list',
296
- name: 'apiUrl',
297
- message: '选择 API URL:',
298
- choices: urls.length > 0 ? urls : ['https://api.anthropic.com'],
299
- when: urls.length > 0
300
- },
301
- {
302
- type: 'input',
303
- name: 'apiUrl',
304
- message: 'API URL:',
305
- default: 'https://api.anthropic.com',
306
- when: urls.length === 0
307
- },
308
- {
309
- type: 'list',
310
- name: 'apiKey',
311
- message: '选择 API Key:',
312
- choices: tokens.map(t => ({ name: `${t.substring(0, 15)}...`, value: t })),
313
- when: tokens.length > 1
314
- },
315
- {
316
- type: 'input',
317
- name: 'apiKey',
318
- message: 'API Key:',
319
- default: tokens[0] || '',
320
- when: tokens.length <= 1
321
- }
322
- ]);
323
-
324
- const finalApiUrl = apiUrl || 'https://api.anthropic.com';
325
- const finalApiKey = apiKey || tokens[0] || '';
326
-
327
- ensureDirs();
328
- const profilePath = getProfilePath(profileName);
329
-
330
- if (fs.existsSync(profilePath)) {
331
- const { overwrite } = await inquirer.prompt([
332
- {
333
- type: 'confirm',
334
- name: 'overwrite',
335
- message: `Profile "${profileName}" 已存在,是否覆盖?`,
336
- default: false
337
- }
338
- ]);
339
-
340
- if (!overwrite) {
341
- console.log(chalk.yellow('已取消'));
342
- process.exit(0);
343
- }
344
- }
345
-
346
- // 使用主配置模板创建完整的 profile(确保有 env 对象)
347
- createProfileFromTemplate(profileName, finalApiUrl, finalApiKey);
348
- console.log(chalk.green(`\n✓ Profile "${profileName}" 已保存到 ${profilePath}`));
349
-
350
- // 如果是第一个 profile,设为默认
351
- const profiles = getProfiles();
352
- if (profiles.length === 1) {
353
- setDefaultProfile(profileName);
354
- console.log(chalk.green(`✓ 已设为默认 profile`));
355
- }
356
- }
357
-
package/src/parsers.js DELETED
@@ -1,154 +0,0 @@
1
- // 解析 CC-Switch SQL 导出文件
2
- export function parseCCSwitchSQL(content) {
3
- const providers = [];
4
- // 匹配 INSERT INTO "providers" 语句
5
- const insertRegex = /INSERT INTO "providers" \([^)]+\) VALUES \(([^;]+)\);/g;
6
- let match;
7
-
8
- while ((match = insertRegex.exec(content)) !== null) {
9
- try {
10
- const valuesStr = match[1];
11
- // 解析 VALUES 中的各个字段
12
- // 格式: 'id', 'app_type', 'name', 'settings_config', 'website_url', ...
13
- const values = [];
14
- let current = '';
15
- let inQuote = false;
16
- let quoteChar = '';
17
- let depth = 0;
18
-
19
- for (let i = 0; i < valuesStr.length; i++) {
20
- const char = valuesStr[i];
21
-
22
- if (!inQuote && (char === "'" || char === '"')) {
23
- inQuote = true;
24
- quoteChar = char;
25
- current += char;
26
- } else if (inQuote && char === quoteChar && valuesStr[i-1] !== '\\') {
27
- // 检查是否是转义的引号 ''
28
- if (valuesStr[i+1] === quoteChar) {
29
- current += char;
30
- i++; // 跳过下一个引号
31
- current += valuesStr[i];
32
- } else {
33
- inQuote = false;
34
- quoteChar = '';
35
- current += char;
36
- }
37
- } else if (!inQuote && char === ',' && depth === 0) {
38
- values.push(current.trim());
39
- current = '';
40
- } else {
41
- current += char;
42
- }
43
- }
44
- if (current.trim()) {
45
- values.push(current.trim());
46
- }
47
-
48
- // 清理值(去除引号)
49
- const cleanValue = (v) => {
50
- if (!v || v === 'NULL') return null;
51
- if ((v.startsWith("'") && v.endsWith("'")) || (v.startsWith('"') && v.endsWith('"'))) {
52
- return v.slice(1, -1).replace(/''/g, "'");
53
- }
54
- return v;
55
- };
56
-
57
- const id = cleanValue(values[0]);
58
- const appType = cleanValue(values[1]);
59
- const name = cleanValue(values[2]);
60
- const settingsConfigStr = cleanValue(values[3]);
61
- const websiteUrl = cleanValue(values[4]);
62
-
63
- // 只处理 claude 类型
64
- if (appType === 'claude' && settingsConfigStr) {
65
- try {
66
- const settingsConfig = JSON.parse(settingsConfigStr);
67
- providers.push({
68
- id,
69
- name,
70
- websiteUrl,
71
- settingsConfig
72
- });
73
- } catch (e) {
74
- // JSON 解析失败,跳过
75
- }
76
- }
77
- } catch (e) {
78
- // 解析失败,跳过
79
- }
80
- }
81
-
82
- return providers;
83
- }
84
-
85
- // 解析 All API Hub JSON 导出文件
86
- export function parseAllApiHubJSON(content) {
87
- try {
88
- const data = JSON.parse(content);
89
- const accounts = data.accounts?.accounts || [];
90
-
91
- return accounts.map(account => {
92
- // 从 site_url 提取 base URL
93
- let baseUrl = account.site_url;
94
- if (!baseUrl.startsWith('http')) {
95
- baseUrl = 'https://' + baseUrl;
96
- }
97
-
98
- // access_token 需要解码(Base64)然后作为 API key
99
- let apiKey = '';
100
- if (account.account_info?.access_token) {
101
- // All API Hub 的 access_token 是加密的,我们使用原始值
102
- // 实际上需要生成 sk- 格式的 token
103
- // 这里我们用 site_url + username 来生成一个标识
104
- apiKey = `sk-${account.account_info.access_token.replace(/[^a-zA-Z0-9]/g, '')}`;
105
- }
106
-
107
- return {
108
- id: account.id,
109
- name: account.site_name,
110
- websiteUrl: baseUrl,
111
- settingsConfig: {
112
- env: {
113
- ANTHROPIC_AUTH_TOKEN: apiKey,
114
- ANTHROPIC_BASE_URL: baseUrl
115
- }
116
- },
117
- // 额外的元数据
118
- meta: {
119
- siteType: account.site_type,
120
- health: account.health?.status,
121
- quota: account.account_info?.quota,
122
- username: account.account_info?.username
123
- }
124
- };
125
- });
126
- } catch (e) {
127
- return [];
128
- }
129
- }
130
-
131
- // 检测文件格式
132
- export function detectFileFormat(content) {
133
- // 检测 CC-Switch SQL 格式
134
- if (content.includes('INSERT INTO "providers"') && content.includes('app_type')) {
135
- return 'ccswitch';
136
- }
137
-
138
- // 检测 All API Hub JSON 格式
139
- try {
140
- const data = JSON.parse(content);
141
- if (data.accounts?.accounts && Array.isArray(data.accounts.accounts)) {
142
- // 检查是否有 All API Hub 特有的字段
143
- const firstAccount = data.accounts.accounts[0];
144
- if (firstAccount && (firstAccount.site_name || firstAccount.site_url || firstAccount.account_info)) {
145
- return 'allapihub';
146
- }
147
- }
148
- } catch {
149
- // 不是有效的 JSON
150
- }
151
-
152
- return null;
153
- }
154
-