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/src/auth.js CHANGED
@@ -1,245 +1,245 @@
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 { paths, loadConfig, log, getProjectRoot, ensureLoopDir } = require('./config');
8
-
9
- function updateGitignore(entry) {
10
- const gitignorePath = path.join(getProjectRoot(), '.gitignore');
11
- let content = '';
12
- if (fs.existsSync(gitignorePath)) {
13
- content = fs.readFileSync(gitignorePath, 'utf8');
14
- }
15
- if (content.includes(entry)) return;
16
-
17
- const suffix = content.endsWith('\n') || content === '' ? '' : '\n';
18
- fs.appendFileSync(gitignorePath, `${suffix}${entry}\n`, 'utf8');
19
- log('ok', `.gitignore 已添加: ${entry}`);
20
- }
21
-
22
- function updateMcpConfig(p, mode) {
23
- let mcpConfig = {};
24
- if (fs.existsSync(p.mcpConfig)) {
25
- try { mcpConfig = JSON.parse(fs.readFileSync(p.mcpConfig, 'utf8')); } catch {}
26
- }
27
-
28
- if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
29
-
30
- const args = ['@playwright/mcp@latest'];
31
-
32
- switch (mode) {
33
- case 'persistent': {
34
- const relProfile = path.relative(getProjectRoot(), p.browserProfile).split(path.sep).join('/');
35
- args.push(`--user-data-dir=${relProfile}`);
36
- break;
37
- }
38
- case 'isolated': {
39
- const relAuth = path.relative(getProjectRoot(), p.playwrightAuth).split(path.sep).join('/');
40
- args.push('--isolated', `--storage-state=${relAuth}`);
41
- break;
42
- }
43
- case 'extension':
44
- args.push('--extension');
45
- break;
46
- }
47
-
48
- mcpConfig.mcpServers.playwright = { command: 'npx', args };
49
- fs.writeFileSync(p.mcpConfig, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
50
- log('ok', `.mcp.json 已配置 Playwright MCP (${mode} 模式)`);
51
- }
52
-
53
- function enableMcpPlaywrightEnv() {
54
- const p = paths();
55
- if (!fs.existsSync(p.envFile)) return;
56
-
57
- let content = fs.readFileSync(p.envFile, 'utf8');
58
- if (/^MCP_PLAYWRIGHT=/m.test(content)) {
59
- content = content.replace(/^MCP_PLAYWRIGHT=.*/m, 'MCP_PLAYWRIGHT=true');
60
- } else {
61
- const suffix = content.endsWith('\n') ? '' : '\n';
62
- content += `${suffix}MCP_PLAYWRIGHT=true\n`;
63
- }
64
- fs.writeFileSync(p.envFile, content, 'utf8');
65
- log('ok', '.claude-coder/.env 已设置 MCP_PLAYWRIGHT=true');
66
- }
67
-
68
- // ── persistent 模式:启动持久化浏览器让用户登录 ──
69
-
70
- async function authPersistent(url, p) {
71
- const profileDir = p.browserProfile;
72
- if (!fs.existsSync(profileDir)) fs.mkdirSync(profileDir, { recursive: true });
73
-
74
- const lockFile = path.join(profileDir, 'SingletonLock');
75
- if (fs.existsSync(lockFile)) {
76
- fs.unlinkSync(lockFile);
77
- log('warn', '已清理残留的 SingletonLock(上次浏览器未正常关闭)');
78
- }
79
-
80
- console.log('操作步骤:');
81
- console.log(' 1. 浏览器将自动打开,请手动完成登录');
82
- console.log(' 2. 登录成功后关闭浏览器窗口');
83
- console.log(' 3. 登录状态将保存在持久化配置中');
84
- console.log(' 4. MCP 后续会话自动复用此登录状态');
85
- console.log('');
86
-
87
- const scriptContent = [
88
- `let chromium;`,
89
- `try { chromium = require('playwright').chromium; } catch {`,
90
- ` try { chromium = require('@playwright/test').chromium; } catch {`,
91
- ` console.error('错误: 未找到 playwright 模块');`,
92
- ` console.error('请安装: npx playwright install chromium');`,
93
- ` process.exit(1);`,
94
- ` }`,
95
- `}`,
96
- `(async () => {`,
97
- ` const ctx = await chromium.launchPersistentContext(${JSON.stringify(profileDir)}, { headless: false });`,
98
- ` const page = ctx.pages()[0] || await ctx.newPage();`,
99
- ` try { await page.goto(${JSON.stringify(url)}); } catch {}`,
100
- ` console.log('请在浏览器中完成登录后关闭窗口...');`,
101
- ` await new Promise(r => {`,
102
- ` ctx.on('close', r);`,
103
- ` const t = setInterval(() => { try { if (!ctx.pages().length) { clearInterval(t); r(); } } catch { clearInterval(t); r(); } }, 2000);`,
104
- ` });`,
105
- ` try { await ctx.close(); } catch {}`,
106
- `})().then(() => process.exit(0)).catch(() => process.exit(0));`,
107
- ].join('\n');
108
-
109
- const tmpScript = path.join(os.tmpdir(), `pw-auth-${Date.now()}.js`);
110
- fs.writeFileSync(tmpScript, scriptContent);
111
-
112
- const helperModules = path.join(__dirname, '..', 'node_modules');
113
- const existingNodePath = process.env.NODE_PATH || '';
114
- const nodePath = existingNodePath ? `${helperModules}:${existingNodePath}` : helperModules;
115
-
116
- let scriptOk = false;
117
- try {
118
- execSync(`node "${tmpScript}"`, {
119
- stdio: 'inherit',
120
- cwd: getProjectRoot(),
121
- env: { ...process.env, NODE_PATH: nodePath },
122
- });
123
- scriptOk = true;
124
- } catch {
125
- // 浏览器关闭时可能返回非零退出码,只要 profile 目录有内容就认为成功
126
- const profileFiles = fs.readdirSync(profileDir);
127
- scriptOk = profileFiles.length > 2;
128
- if (!scriptOk) {
129
- log('error', 'Playwright 启动失败,且未检测到有效的浏览器配置');
130
- log('info', '请确保已安装 Chromium: npx playwright install chromium');
131
- try { fs.unlinkSync(tmpScript); } catch {}
132
- return;
133
- }
134
- log('warn', '浏览器退出码非零,但已检测到有效配置,继续...');
135
- }
136
-
137
- try { fs.unlinkSync(tmpScript); } catch {}
138
-
139
- log('ok', '登录状态已保存到持久化配置');
140
- updateMcpConfig(p, 'persistent');
141
- updateGitignore('.claude-coder/.runtime/browser-profile');
142
- enableMcpPlaywrightEnv();
143
-
144
- console.log('');
145
- log('ok', '配置完成!');
146
- const relProfile = path.relative(getProjectRoot(), profileDir);
147
- log('info', `MCP 使用 persistent 模式 (user-data-dir: ${relProfile})`);
148
- log('info', '如需更新登录状态,重新运行 claude-coder auth');
149
- }
150
-
151
- // ── isolated 模式:使用 codegen 录制 storage-state ──
152
-
153
- async function authIsolated(url, p) {
154
- console.log('操作步骤:');
155
- console.log(' 1. 浏览器将自动打开,请手动完成登录');
156
- console.log(' 2. 登录成功后关闭浏览器窗口');
157
- console.log(' 3. 登录状态(cookies + localStorage)将保存到 playwright-auth.json');
158
- console.log(' 4. MCP 每次会话自动从此文件加载初始状态');
159
- console.log('');
160
-
161
- try {
162
- execSync(
163
- `npx playwright codegen --save-storage="${p.playwrightAuth}" "${url}"`,
164
- { stdio: 'inherit', cwd: getProjectRoot() }
165
- );
166
- } catch (err) {
167
- if (!fs.existsSync(p.playwrightAuth)) {
168
- log('error', `Playwright 登录状态导出失败: ${err.message}`);
169
- log('info', '请确保已安装: npx playwright install chromium');
170
- return;
171
- }
172
- }
173
-
174
- if (!fs.existsSync(p.playwrightAuth)) {
175
- log('error', '未检测到导出的登录状态文件');
176
- return;
177
- }
178
-
179
- log('ok', '登录状态已保存到 playwright-auth.json');
180
- updateMcpConfig(p, 'isolated');
181
- updateGitignore('.claude-coder/playwright-auth.json');
182
- enableMcpPlaywrightEnv();
183
-
184
- console.log('');
185
- log('ok', '配置完成!');
186
- log('info', 'MCP 使用 isolated 模式 (storage-state)');
187
- log('info', 'cookies 和 localStorage 每次会话自动从 playwright-auth.json 加载');
188
- log('info', '如需更新登录状态,重新运行 claude-coder auth');
189
- }
190
-
191
- // ── extension 模式:连接真实浏览器 ──
192
-
193
- function authExtension(p) {
194
- console.log('Extension 模式说明:');
195
- console.log('');
196
- console.log(' 此模式通过 Chrome 扩展连接到您正在运行的浏览器。');
197
- console.log(' MCP 将直接使用浏览器中已有的登录态和扩展。');
198
- console.log('');
199
- console.log(' 前置条件:');
200
- console.log(' 1. 安装 "Playwright MCP Bridge" Chrome/Edge 扩展');
201
- console.log(' https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm');
202
- console.log(' 2. 确保浏览器已启动且扩展已启用');
203
- console.log(' 3. 无需额外认证操作,您的浏览器登录态将自动可用');
204
- console.log('');
205
-
206
- updateMcpConfig(p, 'extension');
207
- enableMcpPlaywrightEnv();
208
-
209
- console.log('');
210
- log('ok', '配置完成!');
211
- log('info', 'MCP 使用 extension 模式(连接真实浏览器)');
212
- log('info', '确保 Chrome/Edge 已运行且 Playwright MCP Bridge 扩展已启用');
213
- }
214
-
215
- // ── 主入口 ──
216
-
217
- async function auth(url) {
218
- ensureLoopDir();
219
- const config = loadConfig();
220
- const p = paths();
221
- const mode = config.playwrightMode;
222
- const targetUrl = url || 'http://localhost:3000';
223
-
224
- log('info', `Playwright 模式: ${mode}`);
225
- log('info', `目标 URL: ${targetUrl}`);
226
- console.log('');
227
-
228
- switch (mode) {
229
- case 'persistent':
230
- await authPersistent(targetUrl, p);
231
- break;
232
- case 'isolated':
233
- await authIsolated(targetUrl, p);
234
- break;
235
- case 'extension':
236
- authExtension(p);
237
- break;
238
- default:
239
- log('error', `未知的 Playwright 模式: ${mode}`);
240
- log('info', '请运行 claude-coder setup 重新配置');
241
- return;
242
- }
243
- }
244
-
245
- module.exports = { auth, updateMcpConfig };
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 { paths, loadConfig, log, getProjectRoot, ensureLoopDir } = require('./config');
8
+
9
+ function updateGitignore(entry) {
10
+ const gitignorePath = path.join(getProjectRoot(), '.gitignore');
11
+ let content = '';
12
+ if (fs.existsSync(gitignorePath)) {
13
+ content = fs.readFileSync(gitignorePath, 'utf8');
14
+ }
15
+ if (content.includes(entry)) return;
16
+
17
+ const suffix = content.endsWith('\n') || content === '' ? '' : '\n';
18
+ fs.appendFileSync(gitignorePath, `${suffix}${entry}\n`, 'utf8');
19
+ log('ok', `.gitignore 已添加: ${entry}`);
20
+ }
21
+
22
+ function updateMcpConfig(p, mode) {
23
+ let mcpConfig = {};
24
+ if (fs.existsSync(p.mcpConfig)) {
25
+ try { mcpConfig = JSON.parse(fs.readFileSync(p.mcpConfig, 'utf8')); } catch {}
26
+ }
27
+
28
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
29
+
30
+ const args = ['@playwright/mcp@latest'];
31
+
32
+ switch (mode) {
33
+ case 'persistent': {
34
+ const relProfile = path.relative(getProjectRoot(), p.browserProfile).split(path.sep).join('/');
35
+ args.push(`--user-data-dir=${relProfile}`);
36
+ break;
37
+ }
38
+ case 'isolated': {
39
+ const relAuth = path.relative(getProjectRoot(), p.playwrightAuth).split(path.sep).join('/');
40
+ args.push('--isolated', `--storage-state=${relAuth}`);
41
+ break;
42
+ }
43
+ case 'extension':
44
+ args.push('--extension');
45
+ break;
46
+ }
47
+
48
+ mcpConfig.mcpServers.playwright = { command: 'npx', args };
49
+ fs.writeFileSync(p.mcpConfig, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
50
+ log('ok', `.mcp.json 已配置 Playwright MCP (${mode} 模式)`);
51
+ }
52
+
53
+ function enableMcpPlaywrightEnv() {
54
+ const p = paths();
55
+ if (!fs.existsSync(p.envFile)) return;
56
+
57
+ let content = fs.readFileSync(p.envFile, 'utf8');
58
+ if (/^MCP_PLAYWRIGHT=/m.test(content)) {
59
+ content = content.replace(/^MCP_PLAYWRIGHT=.*/m, 'MCP_PLAYWRIGHT=true');
60
+ } else {
61
+ const suffix = content.endsWith('\n') ? '' : '\n';
62
+ content += `${suffix}MCP_PLAYWRIGHT=true\n`;
63
+ }
64
+ fs.writeFileSync(p.envFile, content, 'utf8');
65
+ log('ok', '.claude-coder/.env 已设置 MCP_PLAYWRIGHT=true');
66
+ }
67
+
68
+ // ── persistent 模式:启动持久化浏览器让用户登录 ──
69
+
70
+ async function authPersistent(url, p) {
71
+ const profileDir = p.browserProfile;
72
+ if (!fs.existsSync(profileDir)) fs.mkdirSync(profileDir, { recursive: true });
73
+
74
+ const lockFile = path.join(profileDir, 'SingletonLock');
75
+ if (fs.existsSync(lockFile)) {
76
+ fs.unlinkSync(lockFile);
77
+ log('warn', '已清理残留的 SingletonLock(上次浏览器未正常关闭)');
78
+ }
79
+
80
+ console.log('操作步骤:');
81
+ console.log(' 1. 浏览器将自动打开,请手动完成登录');
82
+ console.log(' 2. 登录成功后关闭浏览器窗口');
83
+ console.log(' 3. 登录状态将保存在持久化配置中');
84
+ console.log(' 4. MCP 后续会话自动复用此登录状态');
85
+ console.log('');
86
+
87
+ const scriptContent = [
88
+ `let chromium;`,
89
+ `try { chromium = require('playwright').chromium; } catch {`,
90
+ ` try { chromium = require('@playwright/test').chromium; } catch {`,
91
+ ` console.error('错误: 未找到 playwright 模块');`,
92
+ ` console.error('请安装: npx playwright install chromium');`,
93
+ ` process.exit(1);`,
94
+ ` }`,
95
+ `}`,
96
+ `(async () => {`,
97
+ ` const ctx = await chromium.launchPersistentContext(${JSON.stringify(profileDir)}, { headless: false });`,
98
+ ` const page = ctx.pages()[0] || await ctx.newPage();`,
99
+ ` try { await page.goto(${JSON.stringify(url)}); } catch {}`,
100
+ ` console.log('请在浏览器中完成登录后关闭窗口...');`,
101
+ ` await new Promise(r => {`,
102
+ ` ctx.on('close', r);`,
103
+ ` const t = setInterval(() => { try { if (!ctx.pages().length) { clearInterval(t); r(); } } catch { clearInterval(t); r(); } }, 2000);`,
104
+ ` });`,
105
+ ` try { await ctx.close(); } catch {}`,
106
+ `})().then(() => process.exit(0)).catch(() => process.exit(0));`,
107
+ ].join('\n');
108
+
109
+ const tmpScript = path.join(os.tmpdir(), `pw-auth-${Date.now()}.js`);
110
+ fs.writeFileSync(tmpScript, scriptContent);
111
+
112
+ const helperModules = path.join(__dirname, '..', 'node_modules');
113
+ const existingNodePath = process.env.NODE_PATH || '';
114
+ const nodePath = existingNodePath ? `${helperModules}:${existingNodePath}` : helperModules;
115
+
116
+ let scriptOk = false;
117
+ try {
118
+ execSync(`node "${tmpScript}"`, {
119
+ stdio: 'inherit',
120
+ cwd: getProjectRoot(),
121
+ env: { ...process.env, NODE_PATH: nodePath },
122
+ });
123
+ scriptOk = true;
124
+ } catch {
125
+ // 浏览器关闭时可能返回非零退出码,只要 profile 目录有内容就认为成功
126
+ const profileFiles = fs.readdirSync(profileDir);
127
+ scriptOk = profileFiles.length > 2;
128
+ if (!scriptOk) {
129
+ log('error', 'Playwright 启动失败,且未检测到有效的浏览器配置');
130
+ log('info', '请确保已安装 Chromium: npx playwright install chromium');
131
+ try { fs.unlinkSync(tmpScript); } catch {}
132
+ return;
133
+ }
134
+ log('warn', '浏览器退出码非零,但已检测到有效配置,继续...');
135
+ }
136
+
137
+ try { fs.unlinkSync(tmpScript); } catch {}
138
+
139
+ log('ok', '登录状态已保存到持久化配置');
140
+ updateMcpConfig(p, 'persistent');
141
+ updateGitignore('.claude-coder/.runtime/browser-profile');
142
+ enableMcpPlaywrightEnv();
143
+
144
+ console.log('');
145
+ log('ok', '配置完成!');
146
+ const relProfile = path.relative(getProjectRoot(), profileDir);
147
+ log('info', `MCP 使用 persistent 模式 (user-data-dir: ${relProfile})`);
148
+ log('info', '如需更新登录状态,重新运行 claude-coder auth');
149
+ }
150
+
151
+ // ── isolated 模式:使用 codegen 录制 storage-state ──
152
+
153
+ async function authIsolated(url, p) {
154
+ console.log('操作步骤:');
155
+ console.log(' 1. 浏览器将自动打开,请手动完成登录');
156
+ console.log(' 2. 登录成功后关闭浏览器窗口');
157
+ console.log(' 3. 登录状态(cookies + localStorage)将保存到 playwright-auth.json');
158
+ console.log(' 4. MCP 每次会话自动从此文件加载初始状态');
159
+ console.log('');
160
+
161
+ try {
162
+ execSync(
163
+ `npx playwright codegen --save-storage="${p.playwrightAuth}" "${url}"`,
164
+ { stdio: 'inherit', cwd: getProjectRoot() }
165
+ );
166
+ } catch (err) {
167
+ if (!fs.existsSync(p.playwrightAuth)) {
168
+ log('error', `Playwright 登录状态导出失败: ${err.message}`);
169
+ log('info', '请确保已安装: npx playwright install chromium');
170
+ return;
171
+ }
172
+ }
173
+
174
+ if (!fs.existsSync(p.playwrightAuth)) {
175
+ log('error', '未检测到导出的登录状态文件');
176
+ return;
177
+ }
178
+
179
+ log('ok', '登录状态已保存到 playwright-auth.json');
180
+ updateMcpConfig(p, 'isolated');
181
+ updateGitignore('.claude-coder/playwright-auth.json');
182
+ enableMcpPlaywrightEnv();
183
+
184
+ console.log('');
185
+ log('ok', '配置完成!');
186
+ log('info', 'MCP 使用 isolated 模式 (storage-state)');
187
+ log('info', 'cookies 和 localStorage 每次会话自动从 playwright-auth.json 加载');
188
+ log('info', '如需更新登录状态,重新运行 claude-coder auth');
189
+ }
190
+
191
+ // ── extension 模式:连接真实浏览器 ──
192
+
193
+ function authExtension(p) {
194
+ console.log('Extension 模式说明:');
195
+ console.log('');
196
+ console.log(' 此模式通过 Chrome 扩展连接到您正在运行的浏览器。');
197
+ console.log(' MCP 将直接使用浏览器中已有的登录态和扩展。');
198
+ console.log('');
199
+ console.log(' 前置条件:');
200
+ console.log(' 1. 安装 "Playwright MCP Bridge" Chrome/Edge 扩展');
201
+ console.log(' https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm');
202
+ console.log(' 2. 确保浏览器已启动且扩展已启用');
203
+ console.log(' 3. 无需额外认证操作,您的浏览器登录态将自动可用');
204
+ console.log('');
205
+
206
+ updateMcpConfig(p, 'extension');
207
+ enableMcpPlaywrightEnv();
208
+
209
+ console.log('');
210
+ log('ok', '配置完成!');
211
+ log('info', 'MCP 使用 extension 模式(连接真实浏览器)');
212
+ log('info', '确保 Chrome/Edge 已运行且 Playwright MCP Bridge 扩展已启用');
213
+ }
214
+
215
+ // ── 主入口 ──
216
+
217
+ async function auth(url) {
218
+ ensureLoopDir();
219
+ const config = loadConfig();
220
+ const p = paths();
221
+ const mode = config.playwrightMode;
222
+ const targetUrl = url || 'http://localhost:3000';
223
+
224
+ log('info', `Playwright 模式: ${mode}`);
225
+ log('info', `目标 URL: ${targetUrl}`);
226
+ console.log('');
227
+
228
+ switch (mode) {
229
+ case 'persistent':
230
+ await authPersistent(targetUrl, p);
231
+ break;
232
+ case 'isolated':
233
+ await authIsolated(targetUrl, p);
234
+ break;
235
+ case 'extension':
236
+ authExtension(p);
237
+ break;
238
+ default:
239
+ log('error', `未知的 Playwright 模式: ${mode}`);
240
+ log('info', '请运行 claude-coder setup 重新配置');
241
+ return;
242
+ }
243
+ }
244
+
245
+ module.exports = { auth, updateMcpConfig };