1314mc-helper 0.2.21
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 +160 -0
- package/bin/cli.js +328 -0
- package/package.json +56 -0
- package/src/index.js +359 -0
- package/src/tools/claude.js +200 -0
- package/src/tools/cline.js +103 -0
- package/src/tools/codex.js +154 -0
- package/src/tools/continue.js +149 -0
- package/src/tools/cursor.js +99 -0
- package/src/tools/droid.js +253 -0
- package/src/tools/openclaw.js +295 -0
- package/src/tools/opencode.js +227 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { configureClaudeCode } from './tools/claude.js';
|
|
4
|
+
import { configureOpenClaw } from './tools/openclaw.js';
|
|
5
|
+
import { configureCursor } from './tools/cursor.js';
|
|
6
|
+
import { configureCline } from './tools/cline.js';
|
|
7
|
+
import { configureCodex } from './tools/codex.js';
|
|
8
|
+
import { configureContinue } from './tools/continue.js';
|
|
9
|
+
import { configureOpenCode } from './tools/opencode.js';
|
|
10
|
+
import { configureDroid } from './tools/droid.js';
|
|
11
|
+
|
|
12
|
+
// API 配置
|
|
13
|
+
export const API_CONFIG = {
|
|
14
|
+
baseUrl: 'http://www.1314mc.net:3333',
|
|
15
|
+
// 动态模型列表(运行时填充)
|
|
16
|
+
models: null,
|
|
17
|
+
// 原始模型数据(包含 endpoint 信息)
|
|
18
|
+
rawModels: null,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function normalizeBaseUrl(url) {
|
|
22
|
+
return `${url || ''}`.replace(/\/+$/, '');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function withV1(url) {
|
|
26
|
+
const normalized = normalizeBaseUrl(url);
|
|
27
|
+
return normalized.endsWith('/v1') ? normalized : `${normalized}/v1`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function withoutV1(url) {
|
|
31
|
+
const normalized = normalizeBaseUrl(url);
|
|
32
|
+
return normalized.endsWith('/v1') ? normalized.slice(0, -3) : normalized;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getApiBaseForProtocol(protocol = '') {
|
|
36
|
+
const normalizedProtocol = `${protocol}`.toLowerCase();
|
|
37
|
+
|
|
38
|
+
const openaiProtocols = new Set([
|
|
39
|
+
'openai',
|
|
40
|
+
'openai-response',
|
|
41
|
+
'openai-responses',
|
|
42
|
+
'openai-completion',
|
|
43
|
+
'openai-completions',
|
|
44
|
+
'responses',
|
|
45
|
+
'chat',
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
if (openaiProtocols.has(normalizedProtocol)) {
|
|
49
|
+
return withV1(API_CONFIG.baseUrl);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return withoutV1(API_CONFIG.baseUrl);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 根据模型 ID 和支持的 endpoint 类型判断可用的 API 协议(返回数组)
|
|
57
|
+
* - Claude/Gemini 模型:只用 anthropic-messages
|
|
58
|
+
* - 其他模型只支持 openai:只用 openai-completions
|
|
59
|
+
* - 其他模型同时支持两个协议:返回两个,让各工具模块生成两条配置
|
|
60
|
+
*/
|
|
61
|
+
function resolveApiProtocols(id, endpoints) {
|
|
62
|
+
const hasAnthropic = endpoints.includes('anthropic');
|
|
63
|
+
const hasOpenai = endpoints.includes('openai') ||
|
|
64
|
+
endpoints.includes('openai-response') ||
|
|
65
|
+
endpoints.includes('openai-completion') ||
|
|
66
|
+
endpoints.includes('openai-completions');
|
|
67
|
+
|
|
68
|
+
// Claude 只用 Anthropic
|
|
69
|
+
if (id.startsWith('claude')) {
|
|
70
|
+
return ['anthropic-messages'];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 只支持 openai
|
|
74
|
+
if (hasOpenai && !hasAnthropic) {
|
|
75
|
+
if (endpoints.includes('openai-response')) return ['openai-responses'];
|
|
76
|
+
return ['openai-completions'];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 两个都支持 → 都返回
|
|
80
|
+
if (hasOpenai && hasAnthropic) {
|
|
81
|
+
const protocols = [];
|
|
82
|
+
if (endpoints.includes('openai-response')) {
|
|
83
|
+
protocols.push('openai-responses');
|
|
84
|
+
} else {
|
|
85
|
+
protocols.push('openai-completions');
|
|
86
|
+
}
|
|
87
|
+
protocols.push('anthropic-messages');
|
|
88
|
+
return protocols;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 兜底
|
|
92
|
+
if (hasAnthropic) return ['anthropic-messages'];
|
|
93
|
+
return ['openai-completions'];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 从 pricing 页面获取公开模型 ID 集合
|
|
98
|
+
* 返回 null 表示获取失败,调用方应跳过过滤
|
|
99
|
+
*/
|
|
100
|
+
async function fetchPublicModelIds(apiKey) {
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(`${API_CONFIG.baseUrl}/pricing`, {
|
|
103
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) return null;
|
|
106
|
+
const html = await response.text();
|
|
107
|
+
const ids = new Set();
|
|
108
|
+
for (const m of (html.match(/###\s+([^\n\r]+)/g) || [])) {
|
|
109
|
+
ids.add(m.replace(/^###\s+/, '').trim());
|
|
110
|
+
}
|
|
111
|
+
return ids.size > 0 ? ids : null;
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 从 API 获取模型列表
|
|
119
|
+
*/
|
|
120
|
+
export async function fetchModels(apiKey) {
|
|
121
|
+
try {
|
|
122
|
+
const [response, publicIds] = await Promise.all([
|
|
123
|
+
fetch(`${API_CONFIG.baseUrl}/v1/models`, {
|
|
124
|
+
headers: {
|
|
125
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
126
|
+
'Content-Type': 'application/json',
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
fetchPublicModelIds(apiKey),
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw new Error(`HTTP ${response.status}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const data = await response.json();
|
|
137
|
+
|
|
138
|
+
// 保存原始数据
|
|
139
|
+
API_CONFIG.rawModels = data.data || [];
|
|
140
|
+
|
|
141
|
+
// 解析模型列表,根据 ID 和 supported_endpoint_types 分类
|
|
142
|
+
const models = { claude: [], gpt: [], gemini: [], other: [] };
|
|
143
|
+
|
|
144
|
+
if (data.data && Array.isArray(data.data)) {
|
|
145
|
+
for (const model of data.data) {
|
|
146
|
+
const id = model.id;
|
|
147
|
+
// 跳过未在 pricing 页面公开的隐藏模型
|
|
148
|
+
if (publicIds && !publicIds.has(id)) continue;
|
|
149
|
+
// 跳过含 image 的模型(图像生成类,不支持对话)
|
|
150
|
+
if (id.toLowerCase().includes('image')) continue;
|
|
151
|
+
const endpoints = model.supported_endpoint_types || [];
|
|
152
|
+
|
|
153
|
+
// 根据模型 ID 和支持的 endpoint 类型判断可用协议(可能多个)
|
|
154
|
+
const apis = resolveApiProtocols(id, endpoints);
|
|
155
|
+
|
|
156
|
+
if (id.startsWith('gpt')) {
|
|
157
|
+
models.gpt.push({ id, name: id, apis, endpoints });
|
|
158
|
+
} else if (id.startsWith('claude')) {
|
|
159
|
+
models.claude.push({ id, name: id, apis, endpoints });
|
|
160
|
+
} else if (id.startsWith('gemini')) {
|
|
161
|
+
models.gemini.push({ id, name: id, apis, endpoints });
|
|
162
|
+
} else if (id.startsWith('glm')) {
|
|
163
|
+
models.other.push({ id, name: id, apis, endpoints });
|
|
164
|
+
} else {
|
|
165
|
+
models.other.push({ id, name: id, apis, endpoints });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 缓存结果
|
|
171
|
+
API_CONFIG.models = models;
|
|
172
|
+
return models;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.log(chalk.yellow(`⚠ 无法获取模型列表: ${error.message}`));
|
|
175
|
+
return { claude: [], gpt: [], gemini: [], other: [] };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 获取模型列表(优先使用缓存)
|
|
181
|
+
*/
|
|
182
|
+
export function getModels() {
|
|
183
|
+
return API_CONFIG.models || { claude: [], gpt: [], gemini: [], other: [] };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 获取原始模型数据
|
|
188
|
+
*/
|
|
189
|
+
export function getRawModels() {
|
|
190
|
+
return API_CONFIG.rawModels || [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 工具配置器映射
|
|
194
|
+
const toolConfigurators = {
|
|
195
|
+
claude: configureClaudeCode,
|
|
196
|
+
openclaw: configureOpenClaw,
|
|
197
|
+
cursor: configureCursor,
|
|
198
|
+
cline: configureCline,
|
|
199
|
+
codex: configureCodex,
|
|
200
|
+
continue: configureContinue,
|
|
201
|
+
opencode: configureOpenCode,
|
|
202
|
+
droid: configureDroid,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// 工具信息
|
|
206
|
+
const toolInfo = {
|
|
207
|
+
claude: {
|
|
208
|
+
name: 'Claude Code',
|
|
209
|
+
description: 'Anthropic 官方 CLI 工具(自动配置)',
|
|
210
|
+
configPath: '~/.claude/settings.json',
|
|
211
|
+
},
|
|
212
|
+
droid: {
|
|
213
|
+
name: 'Droid',
|
|
214
|
+
description: 'Factory 的 AI 编程工具(自动配置)',
|
|
215
|
+
configPath: '~/.factory/config.json',
|
|
216
|
+
},
|
|
217
|
+
openclaw: {
|
|
218
|
+
name: 'OpenClaw / Moltbot',
|
|
219
|
+
description: '多平台 AI 助手网关(自动配置)',
|
|
220
|
+
configPath: '~/.openclaw/openclaw.json',
|
|
221
|
+
},
|
|
222
|
+
opencode: {
|
|
223
|
+
name: 'OpenCode',
|
|
224
|
+
description: '终端 AI 编码助手(自动配置)',
|
|
225
|
+
configPath: '~/.config/opencode/opencode.json',
|
|
226
|
+
},
|
|
227
|
+
continue: {
|
|
228
|
+
name: 'Continue',
|
|
229
|
+
description: 'VS Code/JetBrains AI 插件(自动配置)',
|
|
230
|
+
configPath: '~/.continue/config.json',
|
|
231
|
+
},
|
|
232
|
+
codex: {
|
|
233
|
+
name: 'Codex CLI',
|
|
234
|
+
description: 'OpenAI Codex 命令行工具(自动配置)',
|
|
235
|
+
configPath: '~/.codex/config.toml',
|
|
236
|
+
},
|
|
237
|
+
cursor: {
|
|
238
|
+
name: 'Cursor',
|
|
239
|
+
description: 'AI 代码编辑器(需手动配置,需 Pro 订阅)',
|
|
240
|
+
configPath: 'Cursor Settings → Models',
|
|
241
|
+
},
|
|
242
|
+
cline: {
|
|
243
|
+
name: 'Cline',
|
|
244
|
+
description: 'VS Code AI 编程插件(需手动配置)',
|
|
245
|
+
configPath: 'Cline 插件设置',
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 配置所有选中的工具
|
|
251
|
+
*/
|
|
252
|
+
export async function configureAll(apiKey, tools) {
|
|
253
|
+
const results = [];
|
|
254
|
+
|
|
255
|
+
for (const tool of tools) {
|
|
256
|
+
const configurator = toolConfigurators[tool];
|
|
257
|
+
if (configurator) {
|
|
258
|
+
try {
|
|
259
|
+
const result = await configurator(apiKey);
|
|
260
|
+
results.push({ tool: toolInfo[tool]?.name || tool, ...result });
|
|
261
|
+
} catch (error) {
|
|
262
|
+
results.push({
|
|
263
|
+
tool: toolInfo[tool]?.name || tool,
|
|
264
|
+
success: false,
|
|
265
|
+
message: error.message,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
results.push({
|
|
270
|
+
tool,
|
|
271
|
+
success: false,
|
|
272
|
+
message: '暂不支持该工具',
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return results;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 配置单个工具
|
|
282
|
+
*/
|
|
283
|
+
export async function configureTool(tool, apiKey) {
|
|
284
|
+
const configurator = toolConfigurators[tool];
|
|
285
|
+
if (!configurator) {
|
|
286
|
+
return { success: false, message: `不支持的工具: ${tool}` };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return await configurator(apiKey);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 列出支持的工具
|
|
294
|
+
*/
|
|
295
|
+
export function listTools() {
|
|
296
|
+
console.log(chalk.cyan('\n📋 支持的工具列表:\n'));
|
|
297
|
+
|
|
298
|
+
for (const [key, info] of Object.entries(toolInfo)) {
|
|
299
|
+
console.log(chalk.green(` ${key.padEnd(12)}`), chalk.white(info.name));
|
|
300
|
+
console.log(chalk.gray(` ${info.description}`));
|
|
301
|
+
console.log(chalk.gray(` 配置路径: ${info.configPath}\n`));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 显示当前配置状态
|
|
307
|
+
*/
|
|
308
|
+
export async function showStatus() {
|
|
309
|
+
console.log(chalk.cyan('\n📊 配置状态:\n'));
|
|
310
|
+
|
|
311
|
+
for (const [tool, info] of Object.entries(toolInfo)) {
|
|
312
|
+
const configurator = toolConfigurators[tool];
|
|
313
|
+
if (configurator && configurator.checkStatus) {
|
|
314
|
+
try {
|
|
315
|
+
const status = await configurator.checkStatus();
|
|
316
|
+
const icon = status.configured ? chalk.green('✓') : chalk.gray('○');
|
|
317
|
+
console.log(` ${icon} ${info.name.padEnd(20)} ${status.message || ''}`);
|
|
318
|
+
} catch {
|
|
319
|
+
console.log(` ${chalk.gray('○')} ${info.name.padEnd(20)} ${chalk.gray('未检测到')}`);
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
console.log(` ${chalk.gray('?')} ${info.name.padEnd(20)} ${chalk.gray('状态检测暂不支持')}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
console.log('');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 撤销配置
|
|
330
|
+
*/
|
|
331
|
+
export async function revokeKey(tool) {
|
|
332
|
+
const spinner = ora('正在撤销配置...').start();
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
if (tool) {
|
|
336
|
+
const configurator = toolConfigurators[tool];
|
|
337
|
+
if (configurator && configurator.revoke) {
|
|
338
|
+
await configurator.revoke();
|
|
339
|
+
spinner.succeed(`已撤销 ${toolInfo[tool]?.name || tool} 的配置`);
|
|
340
|
+
} else {
|
|
341
|
+
spinner.warn(`${tool} 暂不支持撤销操作`);
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
// 撤销所有
|
|
345
|
+
for (const [key, configurator] of Object.entries(toolConfigurators)) {
|
|
346
|
+
if (configurator.revoke) {
|
|
347
|
+
try {
|
|
348
|
+
await configurator.revoke();
|
|
349
|
+
} catch {
|
|
350
|
+
// 忽略单个工具的错误
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
spinner.succeed('已撤销所有配置');
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
spinner.fail(`撤销失败: ${error.message}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { API_CONFIG, getModels } from '../index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 获取 Claude Code 配置路径
|
|
8
|
+
*/
|
|
9
|
+
function getClaudeConfigPaths() {
|
|
10
|
+
const home = os.homedir();
|
|
11
|
+
return {
|
|
12
|
+
// Claude Code 配置目录
|
|
13
|
+
configDir: path.join(home, '.claude'),
|
|
14
|
+
// 主配置文件 (环境变量)
|
|
15
|
+
settingsFile: path.join(home, '.claude', 'settings.json'),
|
|
16
|
+
// 登录状态文件
|
|
17
|
+
claudeJson: path.join(home, '.claude.json'),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 配置 Claude Code
|
|
23
|
+
* 使用官方推荐的 settings.json env 方式配置
|
|
24
|
+
*/
|
|
25
|
+
export async function configureClaudeCode(apiKey) {
|
|
26
|
+
const paths = getClaudeConfigPaths();
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// 确保目录存在
|
|
30
|
+
await fs.ensureDir(paths.configDir);
|
|
31
|
+
|
|
32
|
+
// 1. 读取现有 settings.json(如果存在)
|
|
33
|
+
let settings = {};
|
|
34
|
+
if (await fs.pathExists(paths.settingsFile)) {
|
|
35
|
+
try {
|
|
36
|
+
const content = await fs.readFile(paths.settingsFile, 'utf-8');
|
|
37
|
+
settings = JSON.parse(content);
|
|
38
|
+
} catch {
|
|
39
|
+
settings = {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2. 清理旧的 Duojie 配置(如果有)
|
|
44
|
+
// 保留用户其他配置,只更新 env 中的 Duojie 相关项
|
|
45
|
+
if (!settings.env) {
|
|
46
|
+
settings.env = {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 3. 获取可用的 Claude 模型
|
|
50
|
+
const models = getModels();
|
|
51
|
+
const claudeModels = models.claude || [];
|
|
52
|
+
|
|
53
|
+
// 查找各类型模型(优先查找最新 kiro 版本)
|
|
54
|
+
let sonnetModel = null;
|
|
55
|
+
let opusModel = null;
|
|
56
|
+
|
|
57
|
+
// 优先级:opus-4-6-kiro > opus-4-6 > opus+kiro > opus > 第一个opus
|
|
58
|
+
const opusKiro46 = claudeModels.find(m => m.id.includes('opus-4-6') && m.id.includes('kiro'));
|
|
59
|
+
const opus46 = claudeModels.find(m => m.id.includes('opus-4-6'));
|
|
60
|
+
const opusKiro = claudeModels.find(m => m.id.includes('opus') && m.id.includes('kiro'));
|
|
61
|
+
const opusAny = claudeModels.find(m => m.id.includes('opus'));
|
|
62
|
+
opusModel = opusKiro46?.id || opus46?.id || opusKiro?.id || opusAny?.id || null;
|
|
63
|
+
|
|
64
|
+
// sonnet 模型
|
|
65
|
+
sonnetModel = claudeModels.find(m => m.id.includes('sonnet'))?.id || null;
|
|
66
|
+
|
|
67
|
+
// 4. 设置 1314mc API 环境变量
|
|
68
|
+
settings.env.ANTHROPIC_API_KEY = apiKey;
|
|
69
|
+
settings.env.ANTHROPIC_BASE_URL = API_CONFIG.baseUrl;
|
|
70
|
+
settings.env.API_TIMEOUT_MS = "300000";
|
|
71
|
+
settings.env.ANTHROPIC_MAX_TOKENS = "16384";
|
|
72
|
+
settings.env.DISABLE_TELEMETRY = "1";
|
|
73
|
+
|
|
74
|
+
// 配置各类型模型(不配置 haiku/fast 模型)
|
|
75
|
+
if (sonnetModel) {
|
|
76
|
+
settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL = sonnetModel;
|
|
77
|
+
}
|
|
78
|
+
if (opusModel) {
|
|
79
|
+
settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = opusModel;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 默认主模型优先级:claude-sonnet-4-6 > opus > 其他 claude
|
|
83
|
+
const sonnet46 = claudeModels.find(m => m.id.includes('sonnet-4-6'))?.id || null;
|
|
84
|
+
const defaultModel = sonnet46 || opusModel || sonnetModel || claudeModels[0]?.id;
|
|
85
|
+
if (defaultModel) {
|
|
86
|
+
settings.model = defaultModel;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 标记为 Duojie 配置
|
|
90
|
+
settings._mc1314 = {
|
|
91
|
+
configured: true,
|
|
92
|
+
configuredAt: new Date().toISOString(),
|
|
93
|
+
models: {
|
|
94
|
+
default: defaultModel,
|
|
95
|
+
sonnet: sonnetModel,
|
|
96
|
+
opus: opusModel,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// 5. 写入 settings.json
|
|
101
|
+
await fs.writeJson(paths.settingsFile, settings, { spaces: 2 });
|
|
102
|
+
|
|
103
|
+
// 6. 创建/更新 .claude.json(跳过登录流程)
|
|
104
|
+
let claudeJson = {};
|
|
105
|
+
if (await fs.pathExists(paths.claudeJson)) {
|
|
106
|
+
try {
|
|
107
|
+
claudeJson = await fs.readJson(paths.claudeJson);
|
|
108
|
+
} catch {
|
|
109
|
+
claudeJson = {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
claudeJson.hasCompletedOnboarding = true;
|
|
114
|
+
await fs.writeJson(paths.claudeJson, claudeJson, { spaces: 2 });
|
|
115
|
+
|
|
116
|
+
// 统计配置的模型数量
|
|
117
|
+
const configuredModels = [sonnetModel, opusModel].filter(Boolean).length;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
message: `已配置 ${configuredModels} 个模型 → ${paths.settingsFile}`,
|
|
122
|
+
configPath: paths.settingsFile,
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
message: `配置失败: ${error.message}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 检查 Claude Code 配置状态
|
|
134
|
+
*/
|
|
135
|
+
configureClaudeCode.checkStatus = async function() {
|
|
136
|
+
const paths = getClaudeConfigPaths();
|
|
137
|
+
|
|
138
|
+
if (await fs.pathExists(paths.settingsFile)) {
|
|
139
|
+
try {
|
|
140
|
+
const settings = await fs.readJson(paths.settingsFile);
|
|
141
|
+
|
|
142
|
+
// 检查是否有 Duojie 配置
|
|
143
|
+
if (settings._mc1314?.configured ||
|
|
144
|
+
settings.env?.ANTHROPIC_BASE_URL?.includes('duojie') ||
|
|
145
|
+
settings.env?.ANTHROPIC_BASE_URL?.includes('1314mc')) {
|
|
146
|
+
return {
|
|
147
|
+
configured: true,
|
|
148
|
+
message: '已配置 1314mc API',
|
|
149
|
+
};
|
|
150
|
+
} else if (settings.env?.ANTHROPIC_API_KEY || settings.env?.ANTHROPIC_AUTH_TOKEN) {
|
|
151
|
+
return {
|
|
152
|
+
configured: true,
|
|
153
|
+
message: '已配置(非 1314mc)',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// ignore
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { configured: false };
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 撤销 Claude Code 配置
|
|
166
|
+
*/
|
|
167
|
+
configureClaudeCode.revoke = async function() {
|
|
168
|
+
const paths = getClaudeConfigPaths();
|
|
169
|
+
|
|
170
|
+
if (await fs.pathExists(paths.settingsFile)) {
|
|
171
|
+
try {
|
|
172
|
+
const settings = await fs.readJson(paths.settingsFile);
|
|
173
|
+
|
|
174
|
+
// 只删除 Duojie 相关的环境变量
|
|
175
|
+
if (settings.env) {
|
|
176
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
177
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
178
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
179
|
+
delete settings.env.API_TIMEOUT_MS;
|
|
180
|
+
delete settings.env.ANTHROPIC_MAX_TOKENS;
|
|
181
|
+
delete settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
182
|
+
delete settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
183
|
+
|
|
184
|
+
// 如果 env 为空,删除整个 env
|
|
185
|
+
if (Object.keys(settings.env).length === 0) {
|
|
186
|
+
delete settings.env;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
delete settings.model;
|
|
191
|
+
delete settings._mc1314;
|
|
192
|
+
|
|
193
|
+
await fs.writeJson(paths.settingsFile, settings, { spaces: 2 });
|
|
194
|
+
} catch {
|
|
195
|
+
// ignore
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export default configureClaudeCode;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { API_CONFIG } from '../index.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 获取 Cline 配置路径 (VS Code 插件)
|
|
9
|
+
*/
|
|
10
|
+
function getClineConfigPaths() {
|
|
11
|
+
const home = os.homedir();
|
|
12
|
+
const platform = os.platform();
|
|
13
|
+
|
|
14
|
+
let vscodeDir;
|
|
15
|
+
if (platform === 'win32') {
|
|
16
|
+
vscodeDir = path.join(process.env.APPDATA || home, 'Code', 'User');
|
|
17
|
+
} else if (platform === 'darwin') {
|
|
18
|
+
vscodeDir = path.join(home, 'Library', 'Application Support', 'Code', 'User');
|
|
19
|
+
} else {
|
|
20
|
+
vscodeDir = path.join(home, '.config', 'Code', 'User');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
configDir: vscodeDir,
|
|
25
|
+
settingsFile: path.join(vscodeDir, 'settings.json'),
|
|
26
|
+
// Cline 全局存储
|
|
27
|
+
globalStorageDir: path.join(vscodeDir, 'globalStorage', 'saoudrizwan.claude-dev'),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 配置 Cline
|
|
33
|
+
* Cline 需要通过 UI 手动配置,这里输出配置信息
|
|
34
|
+
*/
|
|
35
|
+
export async function configureCline(apiKey) {
|
|
36
|
+
const baseUrl = API_CONFIG.baseUrl;
|
|
37
|
+
|
|
38
|
+
// 生成配置说明
|
|
39
|
+
const instructions = `
|
|
40
|
+
${chalk.yellow('Cline 需要在 VS Code 中手动配置:')}
|
|
41
|
+
|
|
42
|
+
${chalk.cyan('步骤 1:')} 打开 VS Code,点击左侧 Cline 图标
|
|
43
|
+
${chalk.cyan('步骤 2:')} 点击设置图标 (⚙️)
|
|
44
|
+
${chalk.cyan('步骤 3:')} 选择 "Use your own API Key"
|
|
45
|
+
${chalk.cyan('步骤 4:')} 填入以下配置:
|
|
46
|
+
|
|
47
|
+
${chalk.green('API Provider:')} OpenAI Compatible
|
|
48
|
+
${chalk.green('Base URL:')} ${baseUrl}
|
|
49
|
+
${chalk.green('API Key:')} ${apiKey}
|
|
50
|
+
${chalk.green('Model:')} 选择 "Enter model ID" 并输入模型名称
|
|
51
|
+
例如: gpt-5.4 或 claude-opus-4-6
|
|
52
|
+
|
|
53
|
+
${chalk.cyan('步骤 5:')} 可选配置:
|
|
54
|
+
• ${chalk.green('Max Tokens:')} 16384 (推荐)
|
|
55
|
+
• Context Window Size: 200000
|
|
56
|
+
• 取消勾选 "Support Images"(如果模型不支持)
|
|
57
|
+
|
|
58
|
+
${chalk.gray('提示: 配置完成后即可在 Cline 中使用 1314mc API')}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
manual: true,
|
|
64
|
+
message: '需要手动配置',
|
|
65
|
+
instructions: instructions,
|
|
66
|
+
configInfo: {
|
|
67
|
+
provider: 'OpenAI Compatible',
|
|
68
|
+
baseUrl: baseUrl,
|
|
69
|
+
apiKey: apiKey,
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 检查 Cline 配置状态
|
|
76
|
+
*/
|
|
77
|
+
configureCline.checkStatus = async function() {
|
|
78
|
+
const paths = getClineConfigPaths();
|
|
79
|
+
|
|
80
|
+
// Cline 配置存储在插件的 globalStorage 中,不在 settings.json
|
|
81
|
+
// 这里只能检测插件是否安装
|
|
82
|
+
if (await fs.pathExists(paths.globalStorageDir)) {
|
|
83
|
+
return {
|
|
84
|
+
configured: true,
|
|
85
|
+
message: 'Cline 插件已安装(配置状态需在 VS Code 中查看)',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
configured: false,
|
|
91
|
+
message: 'Cline 插件未安装或未使用过',
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 撤销 Cline 配置
|
|
97
|
+
*/
|
|
98
|
+
configureCline.revoke = async function() {
|
|
99
|
+
// Cline 配置在插件内部,无法通过文件操作撤销
|
|
100
|
+
console.log(chalk.yellow('Cline 配置需要在 VS Code 的 Cline 插件设置中手动清除'));
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export default configureCline;
|