claude-coder 1.7.0 → 1.8.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.
Files changed (52) hide show
  1. package/README.md +177 -125
  2. package/bin/cli.js +159 -161
  3. package/package.json +52 -47
  4. package/src/commands/auth.js +294 -0
  5. package/src/commands/setup-modules/helpers.js +105 -0
  6. package/src/commands/setup-modules/index.js +26 -0
  7. package/src/commands/setup-modules/mcp.js +95 -0
  8. package/src/commands/setup-modules/provider.js +261 -0
  9. package/src/commands/setup-modules/safety.js +62 -0
  10. package/src/commands/setup-modules/simplify.js +53 -0
  11. package/src/commands/setup.js +172 -0
  12. package/src/common/assets.js +192 -0
  13. package/src/{config.js → common/config.js} +138 -201
  14. package/src/common/constants.js +57 -0
  15. package/src/{indicator.js → common/indicator.js} +222 -217
  16. package/src/common/interaction.js +170 -0
  17. package/src/common/logging.js +77 -0
  18. package/src/common/sdk.js +51 -0
  19. package/src/{tasks.js → common/tasks.js} +157 -172
  20. package/src/common/utils.js +147 -0
  21. package/src/core/base.js +54 -0
  22. package/src/core/coding.js +55 -0
  23. package/src/core/context.js +132 -0
  24. package/src/core/hooks.js +529 -0
  25. package/src/{init.js → core/init.js} +163 -144
  26. package/src/core/plan.js +318 -0
  27. package/src/core/prompts.js +253 -0
  28. package/src/core/query.js +48 -0
  29. package/src/core/repair.js +58 -0
  30. package/src/{runner.js → core/runner.js} +352 -420
  31. package/src/core/scan.js +89 -0
  32. package/src/core/simplify.js +59 -0
  33. package/src/core/validator.js +138 -0
  34. package/{prompts/ADD_GUIDE.md → templates/addGuide.md} +98 -98
  35. package/templates/addUser.md +26 -0
  36. package/{prompts/CLAUDE.md → templates/agentProtocol.md} +195 -199
  37. package/templates/bash-process.md +5 -0
  38. package/{prompts/coding_user.md → templates/codingUser.md} +31 -23
  39. package/templates/guidance.json +35 -0
  40. package/templates/playwright.md +17 -0
  41. package/templates/requirements.example.md +56 -56
  42. package/{prompts/SCAN_PROTOCOL.md → templates/scanProtocol.md} +118 -118
  43. package/{prompts/scan_user.md → templates/scanUser.md} +17 -17
  44. package/templates/test_rule.md +194 -194
  45. package/prompts/add_user.md +0 -24
  46. package/src/auth.js +0 -245
  47. package/src/hooks.js +0 -160
  48. package/src/prompts.js +0 -295
  49. package/src/scanner.js +0 -62
  50. package/src/session.js +0 -352
  51. package/src/setup.js +0 -579
  52. package/src/validator.js +0 -181
package/package.json CHANGED
@@ -1,47 +1,52 @@
1
- {
2
- "name": "claude-coder",
3
- "version": "1.7.0",
4
- "description": "Claude Coder — Autonomous coding agent harness powered by Claude Code SDK. Scan, plan, code, validate, git-commit in a loop.",
5
- "bin": {
6
- "claude-coder": "bin/cli.js"
7
- },
8
- "files": [
9
- "bin/",
10
- "src/",
11
- "prompts/",
12
- "templates/"
13
- ],
14
- "keywords": [
15
- "claude-coder",
16
- "claude",
17
- "claude-code",
18
- "ai",
19
- "agent",
20
- "autonomous",
21
- "automation",
22
- "coding",
23
- "harness",
24
- "loop",
25
- "agent-harness",
26
- "task-decomposition",
27
- "code-generation"
28
- ],
29
- "author": "lk19940215",
30
- "license": "MIT",
31
- "publishConfig": {
32
- "registry": "https://registry.npmjs.org/"
33
- },
34
- "repository": {
35
- "type": "git",
36
- "url": "https://github.com/lk19940215/claude-coder.git"
37
- },
38
- "engines": {
39
- "node": ">=18.0.0"
40
- },
41
- "peerDependencies": {
42
- "@anthropic-ai/claude-agent-sdk": ">=0.1.0"
43
- },
44
- "optionalDependencies": {
45
- "playwright": "^1.58.2"
46
- }
47
- }
1
+ {
2
+ "name": "claude-coder",
3
+ "version": "1.8.0",
4
+ "description": "Claude Coder — Autonomous coding agent harness powered by Claude Code SDK. Scan, plan, code, validate, git-commit in a loop.",
5
+ "bin": {
6
+ "claude-coder": "bin/cli.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "src/",
11
+ "templates/"
12
+ ],
13
+ "scripts": {
14
+ "test": "node test/complete.test.js && node test/integration.test.js && node test/flow.test.js"
15
+ },
16
+ "keywords": [
17
+ "claude-coder",
18
+ "claude",
19
+ "claude-code",
20
+ "ai",
21
+ "agent",
22
+ "autonomous",
23
+ "automation",
24
+ "coding",
25
+ "harness",
26
+ "loop",
27
+ "agent-harness",
28
+ "task-decomposition",
29
+ "code-generation"
30
+ ],
31
+ "author": "lk19940215",
32
+ "license": "MIT",
33
+ "publishConfig": {
34
+ "registry": "https://registry.npmjs.org/"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/lk19940215/claude-coder.git"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "peerDependencies": {
44
+ "@anthropic-ai/claude-agent-sdk": ">=0.1.0"
45
+ },
46
+ "optionalDependencies": {
47
+ "playwright": "^1.58.2"
48
+ },
49
+ "devDependencies": {
50
+ "@anthropic-ai/claude-agent-sdk": "^0.2.71"
51
+ }
52
+ }
@@ -0,0 +1,294 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { execSync } = require('child_process');
7
+ const { loadConfig, log } = require('../common/config');
8
+ const { assets } = require('../common/assets');
9
+ const { appendGitignore } = require('../common/utils');
10
+
11
+ function resolvePlaywright() {
12
+ const { createRequire } = require('module');
13
+ const pkg = 'playwright';
14
+
15
+ try {
16
+ return path.dirname(require.resolve(`${pkg}/package.json`));
17
+ } catch {}
18
+
19
+ try {
20
+ const r = createRequire(path.join(process.cwd(), 'noop.js'));
21
+ return path.dirname(r.resolve(`${pkg}/package.json`));
22
+ } catch {}
23
+
24
+ try {
25
+ const globalRoot = execSync('npm root -g', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
26
+ const pkgJsonPath = path.join(globalRoot, pkg, 'package.json');
27
+ if (fs.existsSync(pkgJsonPath)) return path.join(globalRoot, pkg);
28
+ } catch {}
29
+
30
+ return null;
31
+ }
32
+
33
+ function normalizeUrl(url) {
34
+ if (!url) return null;
35
+ return /^https?:\/\//.test(url) ? url : `http://${url}`;
36
+ }
37
+
38
+ function updateGitignore(entry) {
39
+ if (appendGitignore(assets.projectRoot, entry)) {
40
+ log('ok', `.gitignore 已添加: ${entry}`);
41
+ }
42
+ }
43
+
44
+ function updateMcpConfig(mcpPath, mode) {
45
+ let mcpConfig = {};
46
+ if (fs.existsSync(mcpPath)) {
47
+ try { mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf8')); } catch {}
48
+ }
49
+
50
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
51
+
52
+ const args = ['@playwright/mcp@latest'];
53
+ const projectRoot = assets.projectRoot;
54
+
55
+ switch (mode) {
56
+ case 'persistent': {
57
+ const browserProfilePath = assets.path('browserProfile');
58
+ const relProfile = path.relative(projectRoot, browserProfilePath).split(path.sep).join('/');
59
+ args.push(`--user-data-dir=${relProfile}`);
60
+ break;
61
+ }
62
+ case 'isolated': {
63
+ const playwrightAuthPath = assets.path('playwrightAuth');
64
+ const relAuth = path.relative(projectRoot, playwrightAuthPath).split(path.sep).join('/');
65
+ args.push('--isolated', `--storage-state=${relAuth}`);
66
+ break;
67
+ }
68
+ case 'extension':
69
+ args.push('--extension');
70
+ break;
71
+ }
72
+
73
+ mcpConfig.mcpServers.playwright = { command: 'npx', args };
74
+ fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
75
+ log('ok', `.mcp.json 已配置 Playwright MCP (${mode} 模式)`);
76
+ }
77
+
78
+ function enableMcpPlaywrightEnv() {
79
+ const envPath = assets.path('env');
80
+ if (!envPath || !fs.existsSync(envPath)) return;
81
+
82
+ let content = fs.readFileSync(envPath, 'utf8');
83
+ if (/^MCP_PLAYWRIGHT=/m.test(content)) {
84
+ content = content.replace(/^MCP_PLAYWRIGHT=.*/m, 'MCP_PLAYWRIGHT=true');
85
+ } else {
86
+ const suffix = content.endsWith('\n') ? '' : '\n';
87
+ content += `${suffix}MCP_PLAYWRIGHT=true\n`;
88
+ }
89
+ fs.writeFileSync(envPath, content, 'utf8');
90
+ log('ok', '.claude-coder/.env 已设置 MCP_PLAYWRIGHT=true');
91
+ }
92
+
93
+ // ─────────────────────────────────────────────────────────────
94
+ // 浏览器脚本(session cookie 自动持久化)
95
+ // ─────────────────────────────────────────────────────────────
96
+
97
+ function buildBrowserScript(playwrightDir, profileDir, url) {
98
+ const thirtyDays = Math.floor(Date.now() / 1000) + 86400 * 30;
99
+ return [
100
+ `const { chromium } = require(${JSON.stringify(playwrightDir)});`,
101
+ `(async () => {`,
102
+ ` const ctx = await chromium.launchPersistentContext(${JSON.stringify(profileDir)}, { headless: false });`,
103
+ ` const page = ctx.pages()[0] || await ctx.newPage();`,
104
+ ` try { await page.goto(${JSON.stringify(url)}); } catch {}`,
105
+ ` console.log('请在浏览器中完成操作后关闭窗口...');`,
106
+ ` const persistSessionCookies = async () => {`,
107
+ ` try {`,
108
+ ` const cookies = await ctx.cookies();`,
109
+ ` const session = cookies.filter(c => c.expires === -1);`,
110
+ ` if (session.length > 0) {`,
111
+ ` await ctx.addCookies(session.map(c => ({ ...c, expires: ${thirtyDays} })));`,
112
+ ` console.log('已将 ' + session.length + ' 个 session cookie 转为持久化');`,
113
+ ` }`,
114
+ ` } catch {}`,
115
+ ` };`,
116
+ ` ctx.on('page', p => p.on('close', () => persistSessionCookies()));`,
117
+ ` for (const p of ctx.pages()) p.on('close', () => persistSessionCookies());`,
118
+ ` await new Promise(r => {`,
119
+ ` ctx.on('close', r);`,
120
+ ` const t = setInterval(async () => {`,
121
+ ` try {`,
122
+ ` if (!ctx.pages().length) { clearInterval(t); await persistSessionCookies(); r(); }`,
123
+ ` } catch { clearInterval(t); r(); }`,
124
+ ` }, 2000);`,
125
+ ` });`,
126
+ ` try { await ctx.close(); } catch {}`,
127
+ `})().then(() => process.exit(0)).catch(() => process.exit(0));`,
128
+ ].join('\n');
129
+ }
130
+
131
+ function runBrowserScript(script, cwd) {
132
+ const tmpScript = path.join(os.tmpdir(), `pw-auth-${Date.now()}.js`);
133
+ fs.writeFileSync(tmpScript, script);
134
+ try {
135
+ execSync(`node "${tmpScript}"`, { stdio: 'inherit', cwd });
136
+ return true;
137
+ } catch {
138
+ return false;
139
+ } finally {
140
+ try { fs.unlinkSync(tmpScript); } catch {}
141
+ }
142
+ }
143
+
144
+ // ─────────────────────────────────────────────────────────────
145
+ // auth 模式实现
146
+ // ─────────────────────────────────────────────────────────────
147
+
148
+ async function authPersistent(url) {
149
+ const playwrightDir = resolvePlaywright();
150
+ if (!playwrightDir) {
151
+ log('error', '未找到 playwright 模块');
152
+ log('info', '请安装: npm install -g playwright && npx playwright install chromium');
153
+ return;
154
+ }
155
+
156
+ const profileDir = assets.path('browserProfile');
157
+ if (!fs.existsSync(profileDir)) fs.mkdirSync(profileDir, { recursive: true });
158
+
159
+ const lockFile = path.join(profileDir, 'SingletonLock');
160
+ if (fs.existsSync(lockFile)) {
161
+ fs.unlinkSync(lockFile);
162
+ log('warn', '已清理残留的 SingletonLock(上次浏览器未正常关闭)');
163
+ }
164
+
165
+ console.log('操作步骤:');
166
+ console.log(' 1. 浏览器将自动打开,请手动完成登录');
167
+ console.log(' 2. 登录成功后关闭浏览器窗口');
168
+ console.log(' 3. 登录状态将保存在持久化配置中(session cookie 自动转持久化)');
169
+ console.log(' 4. MCP 后续会话自动复用此登录状态');
170
+ console.log('');
171
+
172
+ const script = buildBrowserScript(playwrightDir, profileDir, url);
173
+ const projectRoot = assets.projectRoot;
174
+
175
+ const ok = runBrowserScript(script, projectRoot);
176
+ if (!ok) {
177
+ const profileFiles = fs.readdirSync(profileDir);
178
+ if (profileFiles.length <= 2) {
179
+ log('error', 'Playwright 启动失败,且未检测到有效的浏览器配置');
180
+ log('info', '请确保已安装 Chromium: npx playwright install chromium');
181
+ return;
182
+ }
183
+ log('warn', '浏览器退出码非零,但已检测到有效配置,继续...');
184
+ }
185
+
186
+ const mcpPath = assets.path('mcpConfig');
187
+ log('ok', '登录状态已保存到持久化配置');
188
+ updateMcpConfig(mcpPath, 'persistent');
189
+ updateGitignore('.claude-coder/.runtime/browser-profile');
190
+ enableMcpPlaywrightEnv();
191
+
192
+ console.log('');
193
+ log('ok', '配置完成!');
194
+ const relProfile = path.relative(projectRoot, profileDir);
195
+ log('info', `MCP 使用 persistent 模式 (user-data-dir: ${relProfile})`);
196
+ log('info', '验证: 再次运行 claude-coder auth <URL>,浏览器应直接进入已登录状态');
197
+ }
198
+
199
+ async function authIsolated(url) {
200
+ const playwrightAuthPath = assets.path('playwrightAuth');
201
+ const projectRoot = assets.projectRoot;
202
+
203
+ console.log('操作步骤:');
204
+ console.log(' 1. 浏览器将自动打开,请手动完成登录');
205
+ console.log(' 2. 登录成功后关闭浏览器窗口');
206
+ console.log(' 3. 登录状态(cookies + localStorage)将保存到 playwright-auth.json');
207
+ console.log(' 4. MCP 每次会话自动从此文件加载初始状态');
208
+ console.log('');
209
+
210
+ try {
211
+ execSync(
212
+ `npx playwright codegen --save-storage="${playwrightAuthPath}" "${url}"`,
213
+ { stdio: 'inherit', cwd: projectRoot }
214
+ );
215
+ } catch (err) {
216
+ if (!fs.existsSync(playwrightAuthPath)) {
217
+ log('error', `Playwright 登录状态导出失败: ${err.message}`);
218
+ log('info', '请确保已安装: npx playwright install chromium');
219
+ return;
220
+ }
221
+ }
222
+
223
+ if (!fs.existsSync(playwrightAuthPath)) {
224
+ log('error', '未检测到导出的登录状态文件');
225
+ return;
226
+ }
227
+
228
+ const mcpPath = assets.path('mcpConfig');
229
+ log('ok', '登录状态已保存到 playwright-auth.json');
230
+ updateMcpConfig(mcpPath, 'isolated');
231
+ updateGitignore('.claude-coder/playwright-auth.json');
232
+ enableMcpPlaywrightEnv();
233
+
234
+ console.log('');
235
+ log('ok', '配置完成!');
236
+ log('info', 'MCP 使用 isolated 模式 (storage-state)');
237
+ log('info', 'cookies 和 localStorage 每次会话自动从 playwright-auth.json 加载');
238
+ }
239
+
240
+ function authExtension() {
241
+ console.log('Extension 模式说明:');
242
+ console.log('');
243
+ console.log(' 此模式通过 Chrome 扩展连接到您正在运行的浏览器。');
244
+ console.log(' MCP 将直接使用浏览器中已有的登录态和扩展。');
245
+ console.log('');
246
+ console.log(' 前置条件:');
247
+ console.log(' 1. 安装 "Playwright MCP Bridge" Chrome/Edge 扩展');
248
+ console.log(' https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm');
249
+ console.log(' 2. 确保浏览器已启动且扩展已启用');
250
+ console.log(' 3. 无需额外认证操作,您的浏览器登录态将自动可用');
251
+ console.log('');
252
+
253
+ const mcpPath = assets.path('mcpConfig');
254
+ updateMcpConfig(mcpPath, 'extension');
255
+ enableMcpPlaywrightEnv();
256
+
257
+ console.log('');
258
+ log('ok', '配置完成!');
259
+ log('info', 'MCP 使用 extension 模式(连接真实浏览器)');
260
+ log('info', '确保 Chrome/Edge 已运行且 Playwright MCP Bridge 扩展已启用');
261
+ }
262
+
263
+ // ─────────────────────────────────────────────────────────────
264
+ // 入口
265
+ // ─────────────────────────────────────────────────────────────
266
+
267
+ async function auth(url) {
268
+ assets.ensureDirs();
269
+ const config = loadConfig();
270
+ const mode = config.playwrightMode;
271
+ const targetUrl = normalizeUrl(url) || 'http://localhost:3000';
272
+
273
+ log('info', `Playwright 模式: ${mode}`);
274
+ log('info', `目标 URL: ${targetUrl}`);
275
+ console.log('');
276
+
277
+ switch (mode) {
278
+ case 'persistent':
279
+ await authPersistent(targetUrl);
280
+ break;
281
+ case 'isolated':
282
+ await authIsolated(targetUrl);
283
+ break;
284
+ case 'extension':
285
+ authExtension();
286
+ break;
287
+ default:
288
+ log('error', `未知的 Playwright 模式: ${mode}`);
289
+ log('info', '请运行 claude-coder setup 重新配置');
290
+ return;
291
+ }
292
+ }
293
+
294
+ module.exports = { auth, updateMcpConfig };
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+ const { log, COLOR } = require('../../common/config');
7
+ const { appendGitignore } = require('../../common/utils');
8
+ const { assets } = require('../../common/assets');
9
+
10
+ function createInterface() {
11
+ return readline.createInterface({ input: process.stdin, output: process.stdout });
12
+ }
13
+
14
+ function ask(rl, question) {
15
+ return new Promise(resolve => rl.question(question, resolve));
16
+ }
17
+
18
+ function askChoice(rl, prompt, min, max, defaultVal) {
19
+ return new Promise(async (resolve) => {
20
+ while (true) {
21
+ const raw = await ask(rl, prompt);
22
+ const val = raw.trim() || String(defaultVal ?? '');
23
+ const num = parseInt(val, 10);
24
+ if (num >= min && num <= max) return resolve(num);
25
+ console.log(`请输入 ${min}-${max}`);
26
+ }
27
+ });
28
+ }
29
+
30
+ async function askApiKey(rl, platform, apiUrl, existingKey) {
31
+ if (existingKey) {
32
+ console.log('回车保留当前 API Key,输入新 Key 更新,输入 q 返回上层菜单:');
33
+ } else {
34
+ console.log(`请输入 ${platform} 的 API Key:`);
35
+ }
36
+ if (apiUrl) {
37
+ console.log(` ${COLOR.blue}获取入口: ${apiUrl}${COLOR.reset}`);
38
+ console.log('');
39
+ }
40
+ const key = await ask(rl, ' API Key: ');
41
+ const trimmed = key.trim();
42
+ if (trimmed.toLowerCase() === 'q') {
43
+ return null;
44
+ }
45
+ if (!trimmed) {
46
+ if (existingKey) return existingKey;
47
+ console.error('API Key 不能为空');
48
+ process.exit(1);
49
+ }
50
+ return trimmed;
51
+ }
52
+
53
+ function writeConfig(filePath, lines) {
54
+ const dir = path.dirname(filePath);
55
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
56
+
57
+ if (fs.existsSync(filePath)) {
58
+ const ts = new Date().toISOString().replace(/[:\-T]/g, '').slice(0, 14);
59
+ const backup = `${filePath}.bak.${ts}`;
60
+ fs.copyFileSync(filePath, backup);
61
+ log('info', `已备份旧配置到: ${backup}`);
62
+ }
63
+
64
+ fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf8');
65
+ }
66
+
67
+ function ensureGitignore() {
68
+ const projectRoot = assets.projectRoot;
69
+ const patterns = ['.claude-coder/.env', '.claude-coder/.runtime/', '.claude-coder/test.env'];
70
+ let added = false;
71
+ for (const pattern of patterns) {
72
+ if (appendGitignore(projectRoot, pattern)) added = true;
73
+ }
74
+ if (added) {
75
+ log('info', '已更新 .gitignore');
76
+ }
77
+ }
78
+
79
+ function showCurrentConfig(existing) {
80
+ console.log('');
81
+ console.log(`${COLOR.blue}当前配置:${COLOR.reset}`);
82
+ console.log(` 提供商: ${existing.MODEL_PROVIDER || '未配置'}`);
83
+ console.log(` BASE_URL: ${existing.ANTHROPIC_BASE_URL || '默认'}`);
84
+ console.log(` 模型: ${existing.ANTHROPIC_MODEL || '默认'}`);
85
+ console.log(` MCP: ${existing.MCP_PLAYWRIGHT === 'true' ? `已启用 (${existing.MCP_PLAYWRIGHT_MODE || 'persistent'})` : '未启用'}`);
86
+ const compTimeout = existing.SESSION_COMPLETION_TIMEOUT || '300';
87
+ const turns = existing.SESSION_MAX_TURNS || '0';
88
+ console.log(` 停顿超时: ${existing.SESSION_STALL_TIMEOUT || '600'} 秒`);
89
+ console.log(` 完成检测: ${compTimeout} 秒`);
90
+ console.log(` 工具轮次: ${turns === '0' ? '无限制' : turns}`);
91
+ const simplifyInterval = existing.SIMPLIFY_INTERVAL ?? '5';
92
+ const simplifyCommits = existing.SIMPLIFY_COMMITS ?? '5';
93
+ console.log(` 自动审查: ${simplifyInterval === '0' ? '禁用' : `每 ${simplifyInterval} 个 session`}${simplifyInterval !== '0' ? `,审查 ${simplifyCommits} 个 commit` : ''}`);
94
+ console.log('');
95
+ }
96
+
97
+ module.exports = {
98
+ createInterface,
99
+ ask,
100
+ askChoice,
101
+ askApiKey,
102
+ writeConfig,
103
+ ensureGitignore,
104
+ showCurrentConfig,
105
+ };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ // ── setup 子模块统一入口 ──
4
+
5
+ const helpers = require('./helpers');
6
+ const provider = require('./provider');
7
+ const mcp = require('./mcp');
8
+ const safety = require('./safety');
9
+ const simplify = require('./simplify');
10
+
11
+ module.exports = {
12
+ createInterface: helpers.createInterface,
13
+ ask: helpers.ask,
14
+ askChoice: helpers.askChoice,
15
+ askApiKey: helpers.askApiKey,
16
+ writeConfig: helpers.writeConfig,
17
+ ensureGitignore: helpers.ensureGitignore,
18
+ showCurrentConfig: helpers.showCurrentConfig,
19
+ selectProvider: provider.selectProvider,
20
+ updateApiKeyOnly: provider.updateApiKeyOnly,
21
+ configureMCP: mcp.configureMCP,
22
+ appendMcpConfig: mcp.appendMcpConfig,
23
+ updateMCPOnly: mcp.updateMCPOnly,
24
+ updateSafetyLimits: safety.updateSafetyLimits,
25
+ updateSimplifyConfig: simplify.updateSimplifyConfig,
26
+ };
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ const { ask, askChoice } = require('./helpers');
4
+ const { log, COLOR, updateEnvVar } = require('../../common/config');
5
+ const { assets } = require('../../common/assets');
6
+
7
+ // ── MCP 配置 ──
8
+
9
+ async function configureMCP(rl) {
10
+ console.log('');
11
+ console.log('是否启用 Playwright MCP(浏览器自动化测试)?');
12
+ console.log('');
13
+ console.log(' Playwright MCP 由微软官方维护 (github.com/microsoft/playwright-mcp)');
14
+ console.log(' 提供 browser_click、browser_snapshot 等 25+ 浏览器自动化工具');
15
+ console.log(' 适用于有 Web 前端的项目,Agent 可用它做端到端测试');
16
+ console.log('');
17
+ console.log(' 1) 是 - 启用 Playwright MCP(项目有 Web 前端)');
18
+ console.log(' 2) 否 - 跳过(纯后端 / CLI 项目)');
19
+ console.log('');
20
+
21
+ const mcpChoice = await askChoice(rl, '选择 [1-2]: ', 1, 2);
22
+
23
+ const mcpConfig = { enabled: false, mode: null };
24
+
25
+ if (mcpChoice === 1) {
26
+ mcpConfig.enabled = true;
27
+ console.log('');
28
+ console.log('请选择 Playwright MCP 浏览器模式:');
29
+ console.log('');
30
+ console.log(' 1) persistent - 懒人模式(默认,推荐)');
31
+ console.log(' 登录一次永久生效,适合 Google SSO、企业内网 API 拉取等日常开发');
32
+ console.log('');
33
+ console.log(' 2) isolated - 开发模式');
34
+ console.log(' 每次会话从快照加载,适合验证登录流程的自动化测试');
35
+ console.log('');
36
+ console.log(' 3) extension - 连接真实浏览器(实验性)');
37
+ console.log(' 通过 Chrome 扩展复用已有登录态和插件');
38
+ console.log(' 需要安装 "Playwright MCP Bridge" 扩展');
39
+ console.log('');
40
+
41
+ const modeChoice = await askChoice(rl, '选择 [1-3,默认 1]: ', 1, 3, 1);
42
+ const modeMap = { 1: 'persistent', 2: 'isolated', 3: 'extension' };
43
+ mcpConfig.mode = modeMap[modeChoice];
44
+
45
+ console.log('');
46
+ if (mcpConfig.mode === 'extension') {
47
+ console.log(` ${COLOR.yellow}⚠ 前置条件:安装 Playwright MCP Bridge 浏览器扩展${COLOR.reset}`);
48
+ console.log(` ${COLOR.blue} https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm${COLOR.reset}`);
49
+ console.log('');
50
+ console.log(' 安装扩展后,运行 claude-coder auth 生成 .mcp.json 配置');
51
+ } else if (mcpConfig.mode === 'persistent') {
52
+ console.log(' 使用 claude-coder auth <URL> 打开浏览器完成首次登录');
53
+ console.log(' 登录状态将持久保存,后续 MCP 会话自动复用');
54
+ console.log('');
55
+ console.log(' 请确保已安装 Playwright:');
56
+ console.log(` ${COLOR.blue}npx playwright install chromium${COLOR.reset}`);
57
+ } else {
58
+ console.log(' 使用 claude-coder auth <URL> 录制登录状态到 playwright-auth.json');
59
+ console.log(' MCP 每次会话从此文件加载初始 cookies/localStorage');
60
+ }
61
+ }
62
+
63
+ return mcpConfig;
64
+ }
65
+
66
+ // ── MCP 配置追加到 lines ──
67
+
68
+ function appendMcpConfig(lines, mcpConfig) {
69
+ lines.push('', '# MCP 工具配置');
70
+ if (mcpConfig.enabled) {
71
+ lines.push('MCP_PLAYWRIGHT=true');
72
+ if (mcpConfig.mode) lines.push(`MCP_PLAYWRIGHT_MODE=${mcpConfig.mode}`);
73
+ } else {
74
+ lines.push('MCP_PLAYWRIGHT=false');
75
+ }
76
+ }
77
+
78
+ // ── 仅更新 MCP 配置 ──
79
+
80
+ async function updateMCPOnly(rl) {
81
+ const mcpConfig = await configureMCP(rl);
82
+ updateEnvVar('MCP_PLAYWRIGHT', mcpConfig.enabled ? 'true' : 'false');
83
+ if (mcpConfig.enabled && mcpConfig.mode) {
84
+ updateEnvVar('MCP_PLAYWRIGHT_MODE', mcpConfig.mode);
85
+ const { updateMcpConfig } = require('../auth');
86
+ updateMcpConfig(assets.path('mcpConfig'), mcpConfig.mode);
87
+ }
88
+ log('ok', 'MCP 配置已更新');
89
+ }
90
+
91
+ module.exports = {
92
+ configureMCP,
93
+ appendMcpConfig,
94
+ updateMCPOnly,
95
+ };