claude-coder 1.6.2 → 1.7.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 +125 -127
- package/bin/cli.js +161 -197
- package/package.json +47 -44
- package/prompts/ADD_GUIDE.md +98 -0
- package/{templates → prompts}/CLAUDE.md +199 -238
- package/{templates → prompts}/SCAN_PROTOCOL.md +118 -123
- package/prompts/add_user.md +24 -0
- package/prompts/coding_user.md +23 -0
- package/prompts/scan_user.md +17 -0
- package/src/auth.js +245 -245
- package/src/config.js +201 -223
- package/src/hooks.js +160 -96
- package/src/indicator.js +217 -160
- package/src/init.js +144 -144
- package/src/prompts.js +295 -339
- package/src/runner.js +420 -394
- package/src/scanner.js +62 -62
- package/src/session.js +352 -320
- package/src/setup.js +579 -397
- package/src/tasks.js +172 -172
- package/src/validator.js +181 -170
- package/templates/requirements.example.md +56 -56
- package/templates/test_rule.md +194 -157
- package/docs/ARCHITECTURE.md +0 -516
- package/docs/PLAYWRIGHT_CREDENTIALS.md +0 -178
- package/docs/README.en.md +0 -103
package/src/setup.js
CHANGED
|
@@ -1,397 +1,579 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const readline = require('readline');
|
|
6
|
-
const { ensureLoopDir, paths, log, COLOR, getProjectRoot } = require('./config');
|
|
7
|
-
|
|
8
|
-
function createInterface() {
|
|
9
|
-
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function ask(rl, question) {
|
|
13
|
-
return new Promise(resolve => rl.question(question, resolve));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function askChoice(rl, prompt, min, max, defaultVal) {
|
|
17
|
-
while (true) {
|
|
18
|
-
const raw = await ask(rl, prompt);
|
|
19
|
-
const val = raw.trim() || String(defaultVal || '');
|
|
20
|
-
const num = parseInt(val, 10);
|
|
21
|
-
if (num >= min && num <= max) return num;
|
|
22
|
-
console.log(`请输入 ${min}-${max}`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function askApiKey(rl, platform, apiUrl, existingKey) {
|
|
27
|
-
if (existingKey) {
|
|
28
|
-
console.log('保留当前 API Key 请直接回车,或输入新 Key:');
|
|
29
|
-
} else {
|
|
30
|
-
console.log(`请输入 ${platform} 的 API Key:`);
|
|
31
|
-
}
|
|
32
|
-
if (apiUrl) {
|
|
33
|
-
console.log(` ${COLOR.blue}获取入口: ${apiUrl}${COLOR.reset}`);
|
|
34
|
-
console.log('');
|
|
35
|
-
}
|
|
36
|
-
const key = await ask(rl, ' API Key: ');
|
|
37
|
-
if (!key.trim()) {
|
|
38
|
-
if (existingKey) return existingKey;
|
|
39
|
-
console.error('API Key 不能为空');
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
return key.trim();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function writeConfig(filePath, lines) {
|
|
46
|
-
const dir = path.dirname(filePath);
|
|
47
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.log('');
|
|
92
|
-
console.log('
|
|
93
|
-
console.log('
|
|
94
|
-
console.log('');
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
log(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
console.log('
|
|
324
|
-
console.log(
|
|
325
|
-
console.log(
|
|
326
|
-
console.log(
|
|
327
|
-
console.log(
|
|
328
|
-
console.log('');
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
console.log('');
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const { ensureLoopDir, paths, log, COLOR, getProjectRoot, parseEnvFile, updateEnvVar } = require('./config');
|
|
7
|
+
|
|
8
|
+
function createInterface() {
|
|
9
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function ask(rl, question) {
|
|
13
|
+
return new Promise(resolve => rl.question(question, resolve));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function askChoice(rl, prompt, min, max, defaultVal) {
|
|
17
|
+
while (true) {
|
|
18
|
+
const raw = await ask(rl, prompt);
|
|
19
|
+
const val = raw.trim() || String(defaultVal || '');
|
|
20
|
+
const num = parseInt(val, 10);
|
|
21
|
+
if (num >= min && num <= max) return num;
|
|
22
|
+
console.log(`请输入 ${min}-${max}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function askApiKey(rl, platform, apiUrl, existingKey) {
|
|
27
|
+
if (existingKey) {
|
|
28
|
+
console.log('保留当前 API Key 请直接回车,或输入新 Key:');
|
|
29
|
+
} else {
|
|
30
|
+
console.log(`请输入 ${platform} 的 API Key:`);
|
|
31
|
+
}
|
|
32
|
+
if (apiUrl) {
|
|
33
|
+
console.log(` ${COLOR.blue}获取入口: ${apiUrl}${COLOR.reset}`);
|
|
34
|
+
console.log('');
|
|
35
|
+
}
|
|
36
|
+
const key = await ask(rl, ' API Key: ');
|
|
37
|
+
if (!key.trim()) {
|
|
38
|
+
if (existingKey) return existingKey;
|
|
39
|
+
console.error('API Key 不能为空');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
return key.trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeConfig(filePath, lines) {
|
|
46
|
+
const dir = path.dirname(filePath);
|
|
47
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
48
|
+
|
|
49
|
+
if (fs.existsSync(filePath)) {
|
|
50
|
+
const ts = new Date().toISOString().replace(/[:\-T]/g, '').slice(0, 14);
|
|
51
|
+
const backup = `${filePath}.bak.${ts}`;
|
|
52
|
+
fs.copyFileSync(filePath, backup);
|
|
53
|
+
log('info', `已备份旧配置到: ${backup}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf8');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ensureGitignore() {
|
|
60
|
+
const gitignore = path.join(getProjectRoot(), '.gitignore');
|
|
61
|
+
const patterns = ['.claude-coder/.env', '.claude-coder/.runtime/'];
|
|
62
|
+
let content = '';
|
|
63
|
+
if (fs.existsSync(gitignore)) {
|
|
64
|
+
content = fs.readFileSync(gitignore, 'utf8');
|
|
65
|
+
}
|
|
66
|
+
const toAdd = patterns.filter(p => !content.includes(p));
|
|
67
|
+
if (toAdd.length > 0) {
|
|
68
|
+
const block = '\n# Claude Coder(含 API Key 和临时文件)\n' + toAdd.join('\n') + '\n';
|
|
69
|
+
fs.appendFileSync(gitignore, block, 'utf8');
|
|
70
|
+
log('info', '已将 .claude-coder/.env 添加到 .gitignore');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// === 提供商配置模块 ===
|
|
75
|
+
|
|
76
|
+
async function configureClaude(rl) {
|
|
77
|
+
return {
|
|
78
|
+
lines: [
|
|
79
|
+
'# Claude Coder 模型配置',
|
|
80
|
+
'# 提供商: Claude 官方',
|
|
81
|
+
'',
|
|
82
|
+
'MODEL_PROVIDER=claude',
|
|
83
|
+
'API_TIMEOUT_MS=3000000',
|
|
84
|
+
'MCP_TOOL_TIMEOUT=30000',
|
|
85
|
+
],
|
|
86
|
+
summary: 'Claude 官方模型',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function configureGLM(rl, existing) {
|
|
91
|
+
console.log('请选择 GLM 平台:');
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log(' 1) 智谱开放平台 (open.bigmodel.cn) - 国内直连');
|
|
94
|
+
console.log(' 2) Z.AI (api.z.ai) - 海外节点');
|
|
95
|
+
console.log('');
|
|
96
|
+
const platChoice = await askChoice(rl, '选择 [1-2,默认 1]: ', 1, 2, 1);
|
|
97
|
+
const isBigmodel = platChoice === 1;
|
|
98
|
+
const glmProvider = isBigmodel ? 'glm-bigmodel' : 'glm-zai';
|
|
99
|
+
const glmBaseUrl = isBigmodel
|
|
100
|
+
? 'https://open.bigmodel.cn/api/anthropic'
|
|
101
|
+
: 'https://api.z.ai/api/anthropic';
|
|
102
|
+
const glmApiUrl = isBigmodel
|
|
103
|
+
? 'https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys'
|
|
104
|
+
: 'https://z.ai/manage-apikey/apikey-list';
|
|
105
|
+
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log('请选择 GLM 模型版本:');
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(' 1) GLM 4.7 - 旗舰模型,推理与代码能力强');
|
|
110
|
+
console.log(' 2) GLM 5 - 最新模型(2026),能力更强');
|
|
111
|
+
console.log('');
|
|
112
|
+
const modelChoice = await askChoice(rl, '选择 [1-2,默认 1]: ', 1, 2, 1);
|
|
113
|
+
const glmModel = modelChoice === 1 ? 'glm-4.7' : 'glm-5';
|
|
114
|
+
|
|
115
|
+
const existingKey = existing.MODEL_PROVIDER === glmProvider ? existing.ANTHROPIC_API_KEY : '';
|
|
116
|
+
const apiKey = await askApiKey(rl, glmProvider, glmApiUrl, existingKey);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
lines: [
|
|
120
|
+
'# Claude Coder 模型配置',
|
|
121
|
+
`# 提供商: GLM (${glmProvider})`,
|
|
122
|
+
`# 模型: ${glmModel}`,
|
|
123
|
+
'',
|
|
124
|
+
`MODEL_PROVIDER=${glmProvider}`,
|
|
125
|
+
`ANTHROPIC_MODEL=${glmModel}`,
|
|
126
|
+
`ANTHROPIC_BASE_URL=${glmBaseUrl}`,
|
|
127
|
+
`ANTHROPIC_API_KEY=${apiKey}`,
|
|
128
|
+
'API_TIMEOUT_MS=3000000',
|
|
129
|
+
'MCP_TOOL_TIMEOUT=30000',
|
|
130
|
+
],
|
|
131
|
+
summary: `GLM (${glmProvider}, ${glmModel})`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function configureAliyun(rl, existing) {
|
|
136
|
+
console.log('请选择阿里云百炼区域:');
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(' 1) 国内版 (coding.dashscope.aliyuncs.com)');
|
|
139
|
+
console.log(' 2) 国际版 (coding-intl.dashscope.aliyuncs.com)');
|
|
140
|
+
console.log('');
|
|
141
|
+
const regionChoice = await askChoice(rl, '选择 [1-2,默认 1]: ', 1, 2, 1);
|
|
142
|
+
const aliyunBaseUrl = regionChoice === 1
|
|
143
|
+
? 'https://coding.dashscope.aliyuncs.com/apps/anthropic'
|
|
144
|
+
: 'https://coding-intl.dashscope.aliyuncs.com/apps/anthropic';
|
|
145
|
+
|
|
146
|
+
const existingKey = existing.MODEL_PROVIDER === 'aliyun-coding' ? existing.ANTHROPIC_API_KEY : '';
|
|
147
|
+
const apiKey = await askApiKey(rl, '阿里云百炼', 'https://bailian.console.aliyun.com/?tab=model#/api-key', existingKey);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
lines: [
|
|
151
|
+
'# Claude Coder 模型配置',
|
|
152
|
+
'# 提供商: 阿里云 Coding Plan (百炼)',
|
|
153
|
+
'# Opus: glm-5 | Sonnet/Haiku: qwen3-coder-plus | Fallback: qwen3.5-plus',
|
|
154
|
+
'',
|
|
155
|
+
'MODEL_PROVIDER=aliyun-coding',
|
|
156
|
+
`ANTHROPIC_BASE_URL=${aliyunBaseUrl}`,
|
|
157
|
+
`ANTHROPIC_API_KEY=${apiKey}`,
|
|
158
|
+
'',
|
|
159
|
+
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1',
|
|
160
|
+
'# Planner (规划/推理) → glm-5',
|
|
161
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5',
|
|
162
|
+
'# Executor (写代码/编辑/工具调用) → qwen3-coder-plus',
|
|
163
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder-plus',
|
|
164
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder-plus',
|
|
165
|
+
'ANTHROPIC_SMALL_FAST_MODEL=qwen3-coder-plus',
|
|
166
|
+
'# Fallback (通用) → qwen3.5-plus',
|
|
167
|
+
'ANTHROPIC_MODEL=qwen3.5-plus',
|
|
168
|
+
'API_TIMEOUT_MS=3000000',
|
|
169
|
+
'MCP_TOOL_TIMEOUT=30000',
|
|
170
|
+
],
|
|
171
|
+
summary: '阿里云 Coding Plan (百炼)',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function configureDeepSeek(rl, existing) {
|
|
176
|
+
const existingKey = existing.MODEL_PROVIDER === 'deepseek' ? existing.ANTHROPIC_API_KEY : '';
|
|
177
|
+
const apiKey = await askApiKey(rl, 'DeepSeek', 'https://platform.deepseek.com/api_keys', existingKey);
|
|
178
|
+
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log('请选择 DeepSeek 模型:');
|
|
181
|
+
console.log('');
|
|
182
|
+
console.log(' 1) deepseek-chat - 通用对话 (V3),速度快成本低 [推荐日常使用]');
|
|
183
|
+
console.log(' 2) deepseek-reasoner - 纯推理模式 (R1),全链路使用 R1,成本最高 [适合攻坚]');
|
|
184
|
+
console.log(' 3) deepseek-hybrid - 混合模式 (R1 + V3),规划用 R1,执行用 V3 [性价比之选]');
|
|
185
|
+
console.log('');
|
|
186
|
+
const dsChoice = await askChoice(rl, '选择 [1-3,默认 1]: ', 1, 3, 1);
|
|
187
|
+
const dsModel = ['deepseek-chat', 'deepseek-reasoner', 'deepseek-hybrid'][dsChoice - 1];
|
|
188
|
+
|
|
189
|
+
const lines = [
|
|
190
|
+
'# Claude Coder 模型配置',
|
|
191
|
+
`# 提供商: DeepSeek`,
|
|
192
|
+
`# 模型: ${dsModel} | API_TIMEOUT_MS=600000 防止长输出超时(10分钟)`,
|
|
193
|
+
'',
|
|
194
|
+
'MODEL_PROVIDER=deepseek',
|
|
195
|
+
'ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic',
|
|
196
|
+
`ANTHROPIC_API_KEY=${apiKey}`,
|
|
197
|
+
`ANTHROPIC_AUTH_TOKEN=${apiKey}`,
|
|
198
|
+
'',
|
|
199
|
+
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1',
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
if (dsModel === 'deepseek-chat') {
|
|
203
|
+
lines.push(
|
|
204
|
+
'# [DeepSeek Chat 降本策略]',
|
|
205
|
+
'ANTHROPIC_MODEL=deepseek-chat',
|
|
206
|
+
'ANTHROPIC_SMALL_FAST_MODEL=deepseek-chat',
|
|
207
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-chat',
|
|
208
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-chat',
|
|
209
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-chat'
|
|
210
|
+
);
|
|
211
|
+
} else if (dsModel === 'deepseek-reasoner') {
|
|
212
|
+
lines.push(
|
|
213
|
+
'# [DeepSeek Pure Reasoner 模式]',
|
|
214
|
+
'ANTHROPIC_MODEL=deepseek-reasoner',
|
|
215
|
+
'ANTHROPIC_SMALL_FAST_MODEL=deepseek-reasoner',
|
|
216
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-reasoner',
|
|
217
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-reasoner',
|
|
218
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-reasoner'
|
|
219
|
+
);
|
|
220
|
+
} else {
|
|
221
|
+
lines.push(
|
|
222
|
+
'# [DeepSeek Hybrid 混合模式]',
|
|
223
|
+
'ANTHROPIC_MODEL=deepseek-reasoner',
|
|
224
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-reasoner',
|
|
225
|
+
'ANTHROPIC_SMALL_FAST_MODEL=deepseek-chat',
|
|
226
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-chat',
|
|
227
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-chat'
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
lines.push('API_TIMEOUT_MS=600000', 'MCP_TOOL_TIMEOUT=30000');
|
|
232
|
+
|
|
233
|
+
return { lines, summary: `DeepSeek (${dsModel})` };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function configureCustom(rl, existing) {
|
|
237
|
+
const defaultUrl = existing.MODEL_PROVIDER === 'custom' ? existing.ANTHROPIC_BASE_URL || '' : '';
|
|
238
|
+
console.log(`请输入 Anthropic 兼容的 BASE_URL${defaultUrl ? `(回车保留: ${defaultUrl})` : ''}:`);
|
|
239
|
+
let baseUrl = await ask(rl, ' URL: ');
|
|
240
|
+
baseUrl = baseUrl.trim() || defaultUrl;
|
|
241
|
+
console.log('');
|
|
242
|
+
|
|
243
|
+
const existingKey = existing.MODEL_PROVIDER === 'custom' ? existing.ANTHROPIC_API_KEY : '';
|
|
244
|
+
const apiKey = await askApiKey(rl, '自定义平台', '', existingKey);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
lines: [
|
|
248
|
+
'# Claude Coder 模型配置',
|
|
249
|
+
'# 提供商: 自定义',
|
|
250
|
+
'',
|
|
251
|
+
'MODEL_PROVIDER=custom',
|
|
252
|
+
`ANTHROPIC_BASE_URL=${baseUrl}`,
|
|
253
|
+
`ANTHROPIC_API_KEY=${apiKey}`,
|
|
254
|
+
'API_TIMEOUT_MS=3000000',
|
|
255
|
+
'MCP_TOOL_TIMEOUT=30000',
|
|
256
|
+
],
|
|
257
|
+
summary: `自定义 (${baseUrl})`,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// === MCP 配置模块 ===
|
|
262
|
+
|
|
263
|
+
async function configureMCP(rl) {
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log('是否启用 Playwright MCP(浏览器自动化测试)?');
|
|
266
|
+
console.log('');
|
|
267
|
+
console.log(' Playwright MCP 由微软官方维护 (github.com/microsoft/playwright-mcp)');
|
|
268
|
+
console.log(' 提供 browser_click、browser_snapshot 等 25+ 浏览器自动化工具');
|
|
269
|
+
console.log(' 适用于有 Web 前端的项目,Agent 可用它做端到端测试');
|
|
270
|
+
console.log('');
|
|
271
|
+
console.log(' 1) 是 - 启用 Playwright MCP(项目有 Web 前端)');
|
|
272
|
+
console.log(' 2) 否 - 跳过(纯后端 / CLI 项目)');
|
|
273
|
+
console.log('');
|
|
274
|
+
|
|
275
|
+
const mcpChoice = await askChoice(rl, '选择 [1-2]: ', 1, 2);
|
|
276
|
+
|
|
277
|
+
const mcpConfig = { enabled: false, mode: null };
|
|
278
|
+
|
|
279
|
+
if (mcpChoice === 1) {
|
|
280
|
+
mcpConfig.enabled = true;
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log('请选择 Playwright MCP 浏览器模式:');
|
|
283
|
+
console.log('');
|
|
284
|
+
console.log(' 1) persistent - 懒人模式(默认,推荐)');
|
|
285
|
+
console.log(' 登录一次永久生效,适合 Google SSO、企业内网 API 拉取等日常开发');
|
|
286
|
+
console.log('');
|
|
287
|
+
console.log(' 2) isolated - 开发模式');
|
|
288
|
+
console.log(' 每次会话从快照加载,适合验证登录流程的自动化测试');
|
|
289
|
+
console.log('');
|
|
290
|
+
console.log(' 3) extension - 连接真实浏览器(实验性)');
|
|
291
|
+
console.log(' 通过 Chrome 扩展复用已有登录态和插件');
|
|
292
|
+
console.log(' 需要安装 "Playwright MCP Bridge" 扩展');
|
|
293
|
+
console.log('');
|
|
294
|
+
|
|
295
|
+
const modeChoice = await askChoice(rl, '选择 [1-3,默认 1]: ', 1, 3, 1);
|
|
296
|
+
const modeMap = { 1: 'persistent', 2: 'isolated', 3: 'extension' };
|
|
297
|
+
mcpConfig.mode = modeMap[modeChoice];
|
|
298
|
+
|
|
299
|
+
console.log('');
|
|
300
|
+
if (mcpConfig.mode === 'extension') {
|
|
301
|
+
console.log(` ${COLOR.yellow}⚠ 前置条件:安装 Playwright MCP Bridge 浏览器扩展${COLOR.reset}`);
|
|
302
|
+
console.log(` ${COLOR.blue} https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm${COLOR.reset}`);
|
|
303
|
+
console.log('');
|
|
304
|
+
console.log(' 安装扩展后,运行 claude-coder auth 生成 .mcp.json 配置');
|
|
305
|
+
} else if (mcpConfig.mode === 'persistent') {
|
|
306
|
+
console.log(' 使用 claude-coder auth <URL> 打开浏览器完成首次登录');
|
|
307
|
+
console.log(' 登录状态将持久保存,后续 MCP 会话自动复用');
|
|
308
|
+
console.log('');
|
|
309
|
+
console.log(' 请确保已安装 Playwright:');
|
|
310
|
+
console.log(` ${COLOR.blue}npx playwright install chromium${COLOR.reset}`);
|
|
311
|
+
} else {
|
|
312
|
+
console.log(' 使用 claude-coder auth <URL> 录制登录状态到 playwright-auth.json');
|
|
313
|
+
console.log(' MCP 每次会话从此文件加载初始 cookies/localStorage');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return mcpConfig;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// === 显示当前配置 ===
|
|
321
|
+
|
|
322
|
+
function showCurrentConfig(existing) {
|
|
323
|
+
console.log('');
|
|
324
|
+
console.log(`${COLOR.blue}当前配置:${COLOR.reset}`);
|
|
325
|
+
console.log(` 提供商: ${existing.MODEL_PROVIDER || '未配置'}`);
|
|
326
|
+
console.log(` BASE_URL: ${existing.ANTHROPIC_BASE_URL || '默认'}`);
|
|
327
|
+
console.log(` 模型: ${existing.ANTHROPIC_MODEL || '默认'}`);
|
|
328
|
+
console.log(` MCP: ${existing.MCP_PLAYWRIGHT === 'true' ? `已启用 (${existing.MCP_PLAYWRIGHT_MODE || 'persistent'})` : '未启用'}`);
|
|
329
|
+
const compTimeout = existing.SESSION_COMPLETION_TIMEOUT || '300';
|
|
330
|
+
const turns = existing.SESSION_MAX_TURNS || '0';
|
|
331
|
+
console.log(` 停顿超时: ${existing.SESSION_STALL_TIMEOUT || '1200'} 秒`);
|
|
332
|
+
console.log(` 完成检测: ${compTimeout} 秒`);
|
|
333
|
+
console.log(` 工具轮次: ${turns === '0' ? '无限制' : turns}`);
|
|
334
|
+
console.log('');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// === 提供商选择 ===
|
|
338
|
+
|
|
339
|
+
const PROVIDER_MENU = `
|
|
340
|
+
请选择模型提供商:
|
|
341
|
+
|
|
342
|
+
1) Claude 官方
|
|
343
|
+
2) GLM Coding Plan (智谱/Z.AI) ${COLOR.blue}https://open.bigmodel.cn${COLOR.reset}
|
|
344
|
+
3) 阿里云 Coding Plan (百炼) ${COLOR.blue}https://bailian.console.aliyun.com${COLOR.reset}
|
|
345
|
+
4) DeepSeek ${COLOR.blue}https://platform.deepseek.com${COLOR.reset}
|
|
346
|
+
5) 自定义 (Anthropic 兼容)
|
|
347
|
+
`;
|
|
348
|
+
|
|
349
|
+
const PROVIDER_CONFIG = [configureClaude, configureGLM, configureAliyun, configureDeepSeek, configureCustom];
|
|
350
|
+
|
|
351
|
+
async function selectProvider(rl, existing, showHeader = true) {
|
|
352
|
+
if (showHeader) console.log(PROVIDER_MENU);
|
|
353
|
+
const choice = await askChoice(rl, '选择 [1-5]: ', 1, 5);
|
|
354
|
+
console.log('');
|
|
355
|
+
return PROVIDER_CONFIG[choice - 1](rl, existing);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// === MCP 配置追加 ===
|
|
359
|
+
|
|
360
|
+
function appendMcpConfig(lines, mcpConfig) {
|
|
361
|
+
lines.push('', '# MCP 工具配置');
|
|
362
|
+
if (mcpConfig.enabled) {
|
|
363
|
+
lines.push('MCP_PLAYWRIGHT=true');
|
|
364
|
+
if (mcpConfig.mode) lines.push(`MCP_PLAYWRIGHT_MODE=${mcpConfig.mode}`);
|
|
365
|
+
} else {
|
|
366
|
+
lines.push('MCP_PLAYWRIGHT=false');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// === 更新单项配置 ===
|
|
371
|
+
|
|
372
|
+
async function updateApiKeyOnly(rl, existing) {
|
|
373
|
+
const provider = existing.MODEL_PROVIDER;
|
|
374
|
+
if (!provider || provider === 'claude') {
|
|
375
|
+
log('warn', 'Claude 官方无需配置 API Key(使用系统登录态)');
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const apiUrlMap = {
|
|
380
|
+
'glm-bigmodel': 'https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys',
|
|
381
|
+
'glm-zai': 'https://z.ai/manage-apikey/apikey-list',
|
|
382
|
+
'aliyun-coding': 'https://bailian.console.aliyun.com/?tab=model#/api-key',
|
|
383
|
+
'deepseek': 'https://platform.deepseek.com/api_keys',
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const apiKey = await askApiKey(rl, provider, apiUrlMap[provider] || '', existing.ANTHROPIC_API_KEY);
|
|
387
|
+
updateEnvVar('ANTHROPIC_API_KEY', apiKey);
|
|
388
|
+
if (provider === 'deepseek') {
|
|
389
|
+
updateEnvVar('ANTHROPIC_AUTH_TOKEN', apiKey);
|
|
390
|
+
}
|
|
391
|
+
log('ok', 'API Key 已更新');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function updateMCPOnly(rl) {
|
|
395
|
+
const mcpConfig = await configureMCP(rl);
|
|
396
|
+
updateEnvVar('MCP_PLAYWRIGHT', mcpConfig.enabled ? 'true' : 'false');
|
|
397
|
+
if (mcpConfig.enabled && mcpConfig.mode) {
|
|
398
|
+
updateEnvVar('MCP_PLAYWRIGHT_MODE', mcpConfig.mode);
|
|
399
|
+
const { updateMcpConfig } = require('./auth');
|
|
400
|
+
updateMcpConfig(paths(), mcpConfig.mode);
|
|
401
|
+
}
|
|
402
|
+
log('ok', 'MCP 配置已更新');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function updateSafetyLimits(rl, existing) {
|
|
406
|
+
const currentStall = existing.SESSION_STALL_TIMEOUT || '1200';
|
|
407
|
+
const currentCompletion = existing.SESSION_COMPLETION_TIMEOUT || '300';
|
|
408
|
+
const currentTurns = existing.SESSION_MAX_TURNS || '0';
|
|
409
|
+
|
|
410
|
+
console.log(`${COLOR.blue}当前安全限制:${COLOR.reset}`);
|
|
411
|
+
console.log(` 停顿超时: ${currentStall} 秒 (${Math.floor(parseInt(currentStall) / 60)} 分钟)`);
|
|
412
|
+
console.log(` 完成检测超时: ${currentCompletion} 秒 (${Math.ceil(parseInt(currentCompletion) / 60)} 分钟)`);
|
|
413
|
+
console.log(` 最大工具轮次: ${currentTurns === '0' ? '无限制' : currentTurns}`);
|
|
414
|
+
console.log('');
|
|
415
|
+
console.log(`${COLOR.yellow}说明:${COLOR.reset}`);
|
|
416
|
+
console.log(' 完成检测 — 模型写入 session_result.json 后缩短等待,解决"完成但不退出"');
|
|
417
|
+
console.log(' 停顿超时 — 长时间无工具调用时自动中断(通用兜底)');
|
|
418
|
+
console.log(' 最大轮次 — 限制总轮次,仅 CI 推荐(默认 0 = 无限制)');
|
|
419
|
+
console.log('');
|
|
420
|
+
|
|
421
|
+
const stallInput = await ask(rl, `停顿超时秒数(回车保留 ${currentStall}): `);
|
|
422
|
+
if (stallInput.trim()) {
|
|
423
|
+
const seconds = parseInt(stallInput.trim(), 10);
|
|
424
|
+
if (isNaN(seconds) || seconds < 60) {
|
|
425
|
+
log('warn', '停顿超时需 >= 60 秒,跳过');
|
|
426
|
+
} else {
|
|
427
|
+
updateEnvVar('SESSION_STALL_TIMEOUT', String(seconds));
|
|
428
|
+
log('ok', `停顿超时已设置为 ${seconds} 秒 (${Math.floor(seconds / 60)} 分钟)`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
console.log('');
|
|
433
|
+
const compInput = await ask(rl, `完成检测超时秒数(回车保留 ${currentCompletion}): `);
|
|
434
|
+
if (compInput.trim()) {
|
|
435
|
+
const seconds = parseInt(compInput.trim(), 10);
|
|
436
|
+
if (isNaN(seconds) || seconds < 30) {
|
|
437
|
+
log('warn', '完成检测超时需 >= 30 秒,跳过');
|
|
438
|
+
} else {
|
|
439
|
+
updateEnvVar('SESSION_COMPLETION_TIMEOUT', String(seconds));
|
|
440
|
+
log('ok', `完成检测超时已设置为 ${seconds} 秒`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
console.log('');
|
|
445
|
+
const turnsInput = await ask(rl, `最大工具轮次(回车保留 ${currentTurns === '0' ? '无限制' : currentTurns},输入 0 = 无限制): `);
|
|
446
|
+
if (turnsInput.trim()) {
|
|
447
|
+
const turns = parseInt(turnsInput.trim(), 10);
|
|
448
|
+
if (isNaN(turns) || turns < 0) {
|
|
449
|
+
log('warn', '请输入 >= 0 的整数,跳过');
|
|
450
|
+
} else {
|
|
451
|
+
updateEnvVar('SESSION_MAX_TURNS', String(turns));
|
|
452
|
+
log('ok', `最大工具轮次已设置为 ${turns === 0 ? '无限制' : turns}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// === 主函数 ===
|
|
458
|
+
|
|
459
|
+
async function setup() {
|
|
460
|
+
const p = paths();
|
|
461
|
+
ensureLoopDir();
|
|
462
|
+
const rl = createInterface();
|
|
463
|
+
|
|
464
|
+
// 加载现有配置
|
|
465
|
+
let existing = {};
|
|
466
|
+
if (fs.existsSync(p.envFile)) {
|
|
467
|
+
existing = parseEnvFile(p.envFile);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
console.log('');
|
|
471
|
+
console.log('============================================');
|
|
472
|
+
console.log(' Claude Coder 配置');
|
|
473
|
+
console.log('============================================');
|
|
474
|
+
|
|
475
|
+
// 首次配置:引导完整流程
|
|
476
|
+
if (!fs.existsSync(p.envFile) || !existing.MODEL_PROVIDER) {
|
|
477
|
+
console.log('');
|
|
478
|
+
console.log(' 检测到未配置,开始初始化...');
|
|
479
|
+
console.log('');
|
|
480
|
+
|
|
481
|
+
const configResult = await selectProvider(rl, existing);
|
|
482
|
+
const mcpConfig = await configureMCP(rl);
|
|
483
|
+
|
|
484
|
+
appendMcpConfig(configResult.lines, mcpConfig);
|
|
485
|
+
writeConfig(p.envFile, configResult.lines);
|
|
486
|
+
ensureGitignore();
|
|
487
|
+
|
|
488
|
+
// 如果启用了 MCP,生成 .mcp.json
|
|
489
|
+
if (mcpConfig.enabled && mcpConfig.mode) {
|
|
490
|
+
const { updateMcpConfig } = require('./auth');
|
|
491
|
+
updateMcpConfig(p, mcpConfig.mode);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
console.log('');
|
|
495
|
+
log('ok', `配置完成!提供商: ${configResult.summary}`);
|
|
496
|
+
console.log('');
|
|
497
|
+
console.log(` 配置文件: ${p.envFile}`);
|
|
498
|
+
console.log(' 使用方式: claude-coder run "你的需求"');
|
|
499
|
+
console.log(' 重新配置: claude-coder setup');
|
|
500
|
+
console.log('');
|
|
501
|
+
console.log(` ${COLOR.yellow}安全限制: 默认 20 分钟无工具调用自动中断,写入 session_result 后 5 分钟${COLOR.reset}`);
|
|
502
|
+
console.log(` ${COLOR.yellow}调整方式: claude-coder setup → 配置安全限制${COLOR.reset}`);
|
|
503
|
+
console.log('');
|
|
504
|
+
|
|
505
|
+
rl.close();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// 已有配置:菜单选择
|
|
510
|
+
while (true) {
|
|
511
|
+
existing = parseEnvFile(p.envFile);
|
|
512
|
+
showCurrentConfig(existing);
|
|
513
|
+
|
|
514
|
+
console.log('请选择要执行的操作:');
|
|
515
|
+
console.log('');
|
|
516
|
+
console.log(' 1) 切换模型提供商');
|
|
517
|
+
console.log(' 2) 更新 API Key');
|
|
518
|
+
console.log(' 3) 配置 MCP');
|
|
519
|
+
console.log(' 4) 配置安全限制');
|
|
520
|
+
console.log(' 5) 完全重新配置');
|
|
521
|
+
console.log(' 6) 退出');
|
|
522
|
+
console.log('');
|
|
523
|
+
|
|
524
|
+
const action = await askChoice(rl, '选择 [1-6]: ', 1, 6);
|
|
525
|
+
console.log('');
|
|
526
|
+
|
|
527
|
+
if (action === 6) {
|
|
528
|
+
log('info', '退出配置');
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
switch (action) {
|
|
533
|
+
case 1: {
|
|
534
|
+
const configResult = await selectProvider(rl, existing);
|
|
535
|
+
appendMcpConfig(configResult.lines, {
|
|
536
|
+
enabled: existing.MCP_PLAYWRIGHT === 'true',
|
|
537
|
+
mode: existing.MCP_PLAYWRIGHT_MODE || null,
|
|
538
|
+
});
|
|
539
|
+
writeConfig(p.envFile, configResult.lines);
|
|
540
|
+
log('ok', `已切换到: ${configResult.summary}`);
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
case 2: {
|
|
544
|
+
await updateApiKeyOnly(rl, existing);
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
case 3: {
|
|
548
|
+
await updateMCPOnly(rl);
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
case 4: {
|
|
552
|
+
await updateSafetyLimits(rl, existing);
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
case 5: {
|
|
556
|
+
const configResult = await selectProvider(rl, existing);
|
|
557
|
+
const mcpConfig = await configureMCP(rl);
|
|
558
|
+
appendMcpConfig(configResult.lines, mcpConfig);
|
|
559
|
+
writeConfig(p.envFile, configResult.lines);
|
|
560
|
+
|
|
561
|
+
if (mcpConfig.enabled && mcpConfig.mode) {
|
|
562
|
+
const { updateMcpConfig } = require('./auth');
|
|
563
|
+
updateMcpConfig(p, mcpConfig.mode);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
log('ok', '配置已更新');
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
console.log('');
|
|
572
|
+
const cont = await ask(rl, '继续配置其他项?(y/N) ');
|
|
573
|
+
if (!/^[Yy]/.test(cont.trim())) break;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
rl.close();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
module.exports = { setup };
|