@tkpdx01/ccc 1.2.7 → 1.3.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/Claude Code settings.txt +776 -776
- package/README.md +77 -0
- package/index.js +4 -2
- package/package.json +40 -39
- package/src/commands/delete.js +66 -66
- package/src/commands/edit.js +120 -120
- package/src/commands/help.js +50 -50
- package/src/commands/import.js +356 -356
- package/src/commands/index.js +11 -10
- package/src/commands/list.js +46 -46
- package/src/commands/new.js +109 -109
- package/src/commands/show.js +68 -68
- package/src/commands/sync.js +93 -93
- package/src/commands/use.js +19 -19
- package/src/commands/webdav.js +477 -0
- package/src/config.js +9 -9
- package/src/crypto.js +147 -0
- package/src/launch.js +69 -69
- package/src/parsers.js +154 -154
- package/src/profiles.js +182 -182
- package/src/utils.js +67 -67
- package/src/webdav.js +268 -0
- package/.claude/settings.local.json +0 -34
- package/nul +0 -1
package/src/launch.js
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
|
-
import { spawn } from 'child_process';
|
|
4
|
-
import {
|
|
5
|
-
getProfiles,
|
|
6
|
-
getDefaultProfile,
|
|
7
|
-
profileExists,
|
|
8
|
-
getProfilePath
|
|
9
|
-
} from './profiles.js';
|
|
10
|
-
|
|
11
|
-
// 启动 claude
|
|
12
|
-
export function launchClaude(profileName, dangerouslySkipPermissions = false) {
|
|
13
|
-
const profilePath = getProfilePath(profileName);
|
|
14
|
-
|
|
15
|
-
if (!profileExists(profileName)) {
|
|
16
|
-
console.log(chalk.red(`Profile "${profileName}" 不存在`));
|
|
17
|
-
console.log(chalk.yellow(`使用 "ccc list" 查看可用的 profiles`));
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const args = ['--settings', profilePath];
|
|
22
|
-
if (dangerouslySkipPermissions) {
|
|
23
|
-
args.push('--dangerously-skip-permissions');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
console.log(chalk.green(`启动 Claude Code,使用配置: ${profileName}`));
|
|
27
|
-
console.log(chalk.gray(`命令: claude ${args.join(' ')}`));
|
|
28
|
-
|
|
29
|
-
const child = spawn('claude', args, {
|
|
30
|
-
stdio: 'inherit',
|
|
31
|
-
shell: true
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
child.on('error', (err) => {
|
|
35
|
-
console.log(chalk.red(`启动失败: ${err.message}`));
|
|
36
|
-
process.exit(1);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 交互式选择 profile
|
|
41
|
-
export async function selectProfile(dangerouslySkipPermissions = false) {
|
|
42
|
-
const profiles = getProfiles();
|
|
43
|
-
|
|
44
|
-
if (profiles.length === 0) {
|
|
45
|
-
console.log(chalk.yellow('没有可用的 profiles'));
|
|
46
|
-
console.log(chalk.gray('使用 "ccc import" 导入配置'));
|
|
47
|
-
process.exit(0);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const defaultProfile = getDefaultProfile();
|
|
51
|
-
|
|
52
|
-
const choices = profiles.map((p, index) => ({
|
|
53
|
-
name: p === defaultProfile ? `${index + 1}. ${p} ${chalk.green('(默认)')}` : `${index + 1}. ${p}`,
|
|
54
|
-
value: p
|
|
55
|
-
}));
|
|
56
|
-
|
|
57
|
-
const { profile } = await inquirer.prompt([
|
|
58
|
-
{
|
|
59
|
-
type: 'list',
|
|
60
|
-
name: 'profile',
|
|
61
|
-
message: '选择要使用的配置:',
|
|
62
|
-
choices,
|
|
63
|
-
default: defaultProfile
|
|
64
|
-
}
|
|
65
|
-
]);
|
|
66
|
-
|
|
67
|
-
launchClaude(profile, dangerouslySkipPermissions);
|
|
68
|
-
}
|
|
69
|
-
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import {
|
|
5
|
+
getProfiles,
|
|
6
|
+
getDefaultProfile,
|
|
7
|
+
profileExists,
|
|
8
|
+
getProfilePath
|
|
9
|
+
} from './profiles.js';
|
|
10
|
+
|
|
11
|
+
// 启动 claude
|
|
12
|
+
export function launchClaude(profileName, dangerouslySkipPermissions = false) {
|
|
13
|
+
const profilePath = getProfilePath(profileName);
|
|
14
|
+
|
|
15
|
+
if (!profileExists(profileName)) {
|
|
16
|
+
console.log(chalk.red(`Profile "${profileName}" 不存在`));
|
|
17
|
+
console.log(chalk.yellow(`使用 "ccc list" 查看可用的 profiles`));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const args = ['--settings', profilePath];
|
|
22
|
+
if (dangerouslySkipPermissions) {
|
|
23
|
+
args.push('--dangerously-skip-permissions');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(chalk.green(`启动 Claude Code,使用配置: ${profileName}`));
|
|
27
|
+
console.log(chalk.gray(`命令: claude ${args.join(' ')}`));
|
|
28
|
+
|
|
29
|
+
const child = spawn('claude', args, {
|
|
30
|
+
stdio: 'inherit',
|
|
31
|
+
shell: true
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
child.on('error', (err) => {
|
|
35
|
+
console.log(chalk.red(`启动失败: ${err.message}`));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 交互式选择 profile
|
|
41
|
+
export async function selectProfile(dangerouslySkipPermissions = false) {
|
|
42
|
+
const profiles = getProfiles();
|
|
43
|
+
|
|
44
|
+
if (profiles.length === 0) {
|
|
45
|
+
console.log(chalk.yellow('没有可用的 profiles'));
|
|
46
|
+
console.log(chalk.gray('使用 "ccc import" 导入配置'));
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const defaultProfile = getDefaultProfile();
|
|
51
|
+
|
|
52
|
+
const choices = profiles.map((p, index) => ({
|
|
53
|
+
name: p === defaultProfile ? `${index + 1}. ${p} ${chalk.green('(默认)')}` : `${index + 1}. ${p}`,
|
|
54
|
+
value: p
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
const { profile } = await inquirer.prompt([
|
|
58
|
+
{
|
|
59
|
+
type: 'list',
|
|
60
|
+
name: 'profile',
|
|
61
|
+
message: '选择要使用的配置:',
|
|
62
|
+
choices,
|
|
63
|
+
default: defaultProfile
|
|
64
|
+
}
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
launchClaude(profile, dangerouslySkipPermissions);
|
|
68
|
+
}
|
|
69
|
+
|
package/src/parsers.js
CHANGED
|
@@ -1,154 +1,154 @@
|
|
|
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
|
-
|
|
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
|
+
|