claude-coder 1.7.1 → 1.8.1

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 (58) hide show
  1. package/README.md +80 -46
  2. package/bin/cli.js +41 -43
  3. package/package.json +7 -2
  4. package/src/{auth.js → commands/auth.js} +41 -46
  5. package/src/commands/setup-modules/helpers.js +99 -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 +206 -0
  13. package/src/{config.js → common/config.js} +17 -93
  14. package/src/common/constants.js +56 -0
  15. package/src/{indicator.js → common/indicator.js} +45 -56
  16. package/src/common/interaction.js +170 -0
  17. package/src/common/logging.js +78 -0
  18. package/src/common/sdk.js +51 -0
  19. package/src/common/tasks.js +88 -0
  20. package/src/common/utils.js +162 -0
  21. package/src/core/coding.js +55 -0
  22. package/src/core/context.js +117 -0
  23. package/src/core/harness.js +484 -0
  24. package/src/core/hooks.js +533 -0
  25. package/src/{init.js → core/init.js} +31 -12
  26. package/src/core/plan.js +325 -0
  27. package/src/core/prompts.js +226 -0
  28. package/src/core/query.js +50 -0
  29. package/src/core/repair.js +46 -0
  30. package/src/core/runner.js +195 -0
  31. package/src/core/scan.js +89 -0
  32. package/src/core/session.js +57 -0
  33. package/src/core/simplify.js +52 -0
  34. package/templates/bash-process.md +12 -0
  35. package/templates/codingSystem.md +65 -0
  36. package/templates/codingUser.md +17 -0
  37. package/templates/coreProtocol.md +29 -0
  38. package/templates/guidance.json +35 -0
  39. package/templates/planSystem.md +78 -0
  40. package/templates/planUser.md +9 -0
  41. package/templates/playwright.md +17 -0
  42. package/templates/requirements.example.md +4 -3
  43. package/templates/scanSystem.md +120 -0
  44. package/templates/scanUser.md +10 -0
  45. package/prompts/ADD_GUIDE.md +0 -98
  46. package/prompts/CLAUDE.md +0 -199
  47. package/prompts/SCAN_PROTOCOL.md +0 -118
  48. package/prompts/add_user.md +0 -24
  49. package/prompts/coding_user.md +0 -31
  50. package/prompts/scan_user.md +0 -17
  51. package/src/hooks.js +0 -166
  52. package/src/prompts.js +0 -295
  53. package/src/runner.js +0 -396
  54. package/src/scanner.js +0 -62
  55. package/src/session.js +0 -354
  56. package/src/setup.js +0 -579
  57. package/src/tasks.js +0 -172
  58. package/src/validator.js +0 -181
package/README.md CHANGED
@@ -2,9 +2,14 @@
2
2
 
3
3
  **中文** | [English](docs/README.en.md)
4
4
 
5
- [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) 启发,Claude Coder 是一个**自主编码 harness**,依托 Claude Agent SDK `query()` 接口,提供项目扫描、任务分解、多 session 编排、自动校验与 git 回滚的能力,支持一句话需求或 `requirements.md` 驱动,兼容所有 Anthropic API 兼容的模型提供商。
5
+ 一个**长时间自运行的自主编码 Agent Harness**:基于 Claude Agent SDK,通过 Hook 提示注入引导模型行为,倒计时活跃度监控保障稳定运行,多 session 编排实现一句话需求到完整项目的全自动交付。
6
6
 
7
- **核心思路**:AI Agent 单次会话上下文有限,大型需求容易丢失进度或产出不可用代码。本工具将 Agent 包装为一个**可靠的、可重试的函数** — harness 管理任务状态、校验每次产出、失败时自动回滚,Agent 只需专注于编码。
7
+ ### 亮点
8
+
9
+ - **Harness 生命周期管理**:统一的 Harness 类封装环境准备、任务调度、校验、回滚、推送全生命周期,含 AI 驱动的 JSON 自愈修复
10
+ - **Hook 提示注入**:通过 JSON 配置在工具调用时向模型注入上下文引导,零代码修改即可扩展规则([机制详解](design/hook-mechanism.md))
11
+ - **长时间自循环编码**:多 session 编排 + 倒计时活跃度监控 + git 回滚重试,Agent 可持续编码数小时不中断([守护机制](design/session-guard.md))
12
+ - **配置驱动**:支持 Claude 官方、Coding Plan 多模型路由、DeepSeek 等任意 Anthropic 兼容 API
8
13
 
9
14
  ---
10
15
 
@@ -20,11 +25,35 @@ npm install -g claude-coder
20
25
  # 配置模型
21
26
  claude-coder setup
22
27
 
23
- # 开始自动编码
28
+ # 进入项目目录
24
29
  cd your-project
30
+
31
+ # 初始化项目(扫描技术栈、生成 profile)
32
+ claude-coder init
33
+
34
+ # 开始自动编码
25
35
  claude-coder run "实现用户注册和登录功能"
26
36
  ```
27
37
 
38
+ ## 命令列表
39
+
40
+ | 命令 | 说明 |
41
+ |------|------|
42
+ | `claude-coder setup` | 交互式配置(模型、MCP、安全限制、自动审查) |
43
+ | `claude-coder init` | 初始化项目环境(扫描技术栈、生成 profile) |
44
+ | `claude-coder plan "需求"` | 生成计划方案 |
45
+ | `claude-coder plan -r [file]` | 从需求文件生成计划 |
46
+ | `claude-coder plan --planOnly` | 仅生成计划文档,不分解任务 |
47
+ | `claude-coder plan -i "需求"` | 交互模式,允许模型提问 |
48
+ | `claude-coder run [需求]` | 自动编码循环 |
49
+ | `claude-coder run --max 1` | 单次执行 |
50
+ | `claude-coder run --dry-run` | 预览模式(查看任务队列) |
51
+ | `claude-coder simplify [focus]` | 代码审查和简化 |
52
+ | `claude-coder auth [url]` | 导出 Playwright 登录状态 |
53
+ | `claude-coder status` | 查看进度和成本 |
54
+
55
+ **选项**:`--max N` 限制 session 数(默认 50),`--pause N` 每 N 个 session 暂停确认,`--model M` 指定模型。
56
+
28
57
  ## 工作原理
29
58
 
30
59
  ```
@@ -33,34 +62,29 @@ claude-coder run "实现用户注册和登录功能"
33
62
  ┌──────┴──────┐
34
63
  │ Session N │
35
64
  │ Claude SDK │
36
- 6 步流程 │
65
+ 3 步流程 │
37
66
  └──────┬──────┘
38
67
 
39
- harness 校验
68
+ Harness 校验
69
+ (含 AI 自愈)
40
70
 
41
- 通过 → 下一个任务
71
+ 通过 → simplify? → push → 下一个任务
42
72
  失败 → git 回滚 + 重试
43
73
  ```
44
74
 
45
- 每个 session 内,Agent 自主执行 6 步:恢复上下文 环境检查选任务 编码测试 收尾(git commit)。
75
+ 每个 session 内,Agent 自主执行 3 步:**实现**(任务上下文由 harness 注入,编码实现)**验证**(按 category 选最轻量测试)**收尾**(git commit + 写 session_result.json)。
46
76
 
47
- ## 命令
77
+ Harness 在 session 结束后自动校验 `session_result.json` + git 进度。校验失败时 AI 自动尝试修复损坏的 JSON 文件(`repair.js`),仍失败则回滚代码并重试。
48
78
 
49
- | 命令 | 说明 |
50
- |------|------|
51
- | `claude-coder setup` | 交互式模型配置 |
52
- | `claude-coder run [需求]` | 自动编码循环 |
53
- | `claude-coder run --max 1` | 单次执行 |
54
- | `claude-coder run --dry-run` | 预览模式 |
55
- | `claude-coder init` | 初始化项目环境 |
56
- | `claude-coder add "指令"` | 追加任务 |
57
- | `claude-coder add -r [file]` | 从需求文件追加任务 |
58
- | `claude-coder add "..." --model M` | 指定模型追加任务 |
59
- | `claude-coder auth [url]` | 导出 Playwright 登录状态 |
60
- | `claude-coder validate` | 手动校验 |
61
- | `claude-coder status` | 查看进度和成本 |
79
+ ## 机制文档
62
80
 
63
- **选项**:`--max N` 限制 session 数(默认 50),`--pause N` 每 N 个 session 暂停确认(默认不暂停)。
81
+ | 文档 | 说明 |
82
+ |------|------|
83
+ | [技术架构](design/ARCHITECTURE.md) | 核心设计规则、Harness 类职责、模块关系、Prompt 注入架构 |
84
+ | [Hook 注入机制](design/hook-mechanism.md) | SDK Hook 调研、GuidanceInjector 三级匹配、配置格式、副作用评估 |
85
+ | [Session 守护机制](design/session-guard.md) | 中断策略、倒计时活跃度检测、工具运行状态追踪、防刷屏 |
86
+ | [测试凭证方案](docs/PLAYWRIGHT_CREDENTIALS.md) | Playwright 登录态导出、API Key 持久化 |
87
+ | [SDK 使用指南](docs/CLAUDE_AGENT_SDK_GUIDE.md) | Claude Agent SDK 接口参考 |
64
88
 
65
89
  ## 使用场景
66
90
 
@@ -68,21 +92,37 @@ claude-coder run "实现用户注册和登录功能"
68
92
 
69
93
  **已有项目**:`claude-coder run "新增头像上传功能"` — 先扫描现有代码和技术栈,再增量开发。
70
94
 
71
- **需求文档驱动**:在项目根目录创建 `requirements.md`,运行 `claude-coder run` — 需求变更后用 `claude-coder add -r` 同步新任务。
72
-
73
- **追加任务**:`claude-coder add "新增管理员后台"` 或 `claude-coder add -r requirements.md` — 仅追加到任务列表,下次 run 时执行。
95
+ **需求文档驱动**:在项目根目录创建 `requirements.md`,运行 `claude-coder run`。需求变更后 `claude-coder add -r` 同步新任务。
74
96
 
75
- **自动测试 + 凭证持久化**:`claude-coder auth http://localhost:3000` — 导出浏览器登录态(cookies + localStorage),Agent 测试时自动使用。缺 API Key 时 Agent 会自行记录到 `test.env` 并继续推进,不会停工。详见 [测试凭证持久化方案](docs/PLAYWRIGHT_CREDENTIALS.md)。
97
+ **自动测试 + 凭证持久化**:`claude-coder auth http://localhost:3000` — 导出浏览器登录态,Agent 测试时自动使用。详见 [测试凭证方案](docs/PLAYWRIGHT_CREDENTIALS.md)。
76
98
 
77
99
  ## 模型支持
78
100
 
79
101
  | 提供商 | 说明 |
80
102
  |--------|------|
81
- | Claude 官方 | 默认,Anthropic 原版 API |
82
- | GLM (智谱/Z.AI) | GLM 4.7 / GLM 5 |
83
- | 阿里云百炼 | qwen3-coder-plus / glm-5 |
84
- | DeepSeek | deepseek-chat / reasoner |
85
- | 自定义 | 任何 Anthropic 兼容 API |
103
+ | 默认 | Claude 官方模型,使用系统登录态 |
104
+ | Coding Plan | 自建 API,推荐的多模型路由配置 |
105
+ | API | DeepSeek 或其他 Anthropic 兼容 API |
106
+
107
+ ## 建议配置
108
+
109
+ ### 长时间自运行 Agent(最稳)
110
+
111
+ ```bash
112
+ ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5
113
+ ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder-next
114
+ ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder-plus
115
+ ANTHROPIC_MODEL=kimi-k2.5
116
+ ```
117
+
118
+ ### 自用 Claude Code(最强)
119
+
120
+ ```bash
121
+ ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-max-2026-01-23
122
+ ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder-next
123
+ ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder-plus
124
+ ANTHROPIC_MODEL=glm-5
125
+ ```
86
126
 
87
127
  ## 项目结构
88
128
 
@@ -92,33 +132,27 @@ your-project/
92
132
  .env # 模型配置
93
133
  project_profile.json # 项目扫描结果
94
134
  tasks.json # 任务列表 + 状态
95
- session_result.json # 上次 session 结果(扁平)
135
+ session_result.json # 上次 session 结果
96
136
  progress.json # 会话历史 + 成本
97
- tests.json # 验证记录
98
- test.env # 测试凭证(API Key 等,可选)
99
- playwright-auth.json # 登录状态快照(isolated 模式,auth 命令生成)
100
- .runtime/ # 临时文件
101
- logs/ # 每 session 独立日志(含工具调用记录)
102
- browser-profile/ # 持久化浏览器 Profile(persistent 模式,auth 命令生成)
103
- requirements.md # 需求文档(可选)
137
+ test.env # 测试凭证(可选)
138
+ .runtime/
139
+ harness_state.json # Harness 状态(session 计数等)
140
+ logs/ # 每 session 独立日志
104
141
  ```
105
142
 
106
143
  ## 常见问题
107
144
 
108
145
  **"Credit balance is too low"**:运行 `claude-coder setup` 重新配置 API Key。
109
146
 
110
- **中断恢复**:直接重新运行 `claude-coder run`,会从上次中断处继续。
147
+ **中断恢复**:直接重新运行 `claude-coder run`,从上次中断处继续。
111
148
 
112
- **长时间无响应**:模型处理复杂文件时可能出现 10-20 分钟的思考间隔(spinner 会显示红色警告),这是正常行为。超过 20 分钟无工具调用时 Harness 会自动中断并重试。可通过 `.env` 中 `SESSION_STALL_TIMEOUT=秒数` 调整阈值。
149
+ **长时间无响应**:模型处理复杂任务时可能出现长思考间隔(indicator 显示黄色"工具执行中"或红色"无响应"),这是正常行为。超过阈值后 harness 自动中断并重试。通过 `claude-coder setup` 的安全限制配置或 `.env` 中 `SESSION_STALL_TIMEOUT=秒数` 调整。
113
150
 
114
151
  **跳过任务**:将 `.claude-coder/tasks.json` 中该任务的 `status` 改为 `done`。
115
152
 
116
- **Windows 支持**:完全支持,纯 Node.js 实现。
117
-
118
- ## 文档
153
+ ## 参考文章
119
154
 
120
- - [技术架构](docs/ARCHITECTURE.md) — 核心设计规则、模块职责、提示语注入架构、注意力机制、Hook 数据流
121
- - [测试凭证持久化方案](docs/PLAYWRIGHT_CREDENTIALS.md) — 自动测试的凭证管理:Playwright 登录态导出、API Key 持久化、Agent 缺凭证时的行为策略
155
+ [Anthropic: Effective harnesses for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents)
122
156
 
123
157
  ## License
124
158
 
package/bin/cli.js CHANGED
@@ -1,16 +1,16 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
4
  const pkg = require('../package.json');
5
5
 
6
6
  const COMMANDS = {
7
- run: { desc: '自动编码循环', usage: 'claude-coder run [--max N] [--pause N] [--dry-run]' },
8
- setup: { desc: '交互式模型配置', usage: 'claude-coder setup' },
9
- init: { desc: '初始化项目环境', usage: 'claude-coder init' },
10
- add: { desc: '追加任务并可选自动执行', usage: 'claude-coder add "指令" [--model M] | add -r [file]' },
11
- auth: { desc: '导出 Playwright 登录状态', usage: 'claude-coder auth [url]' },
12
- validate: { desc: '手动校验上次 session', usage: 'claude-coder validate' },
13
- status: { desc: '查看任务进度和成本', usage: 'claude-coder status' },
7
+ run: { desc: '自动编码循环', usage: 'claude-coder run [--max N] [--pause N] [--dry-run]' },
8
+ setup: { desc: '交互式模型配置', usage: 'claude-coder setup' },
9
+ init: { desc: '初始化项目环境', usage: 'claude-coder init' },
10
+ plan: { desc: '生成计划方案', usage: 'claude-coder plan "需求" | plan -r requirements.md [--planOnly] [-i]' },
11
+ simplify: { desc: '代码审查和简化', usage: 'claude-coder simplify [focus]' },
12
+ auth: { desc: '导出 Playwright 登录状态', usage: 'claude-coder auth [url]' },
13
+ status: { desc: '查看任务进度和成本', usage: 'claude-coder status' },
14
14
  };
15
15
 
16
16
  function showHelp() {
@@ -22,15 +22,18 @@ function showHelp() {
22
22
  }
23
23
  console.log('\n示例:');
24
24
  console.log(' claude-coder setup 配置模型和 API Key');
25
- console.log(' claude-coder add "实现用户登录" 添加任务(询问是否自动执行)');
25
+ console.log(' claude-coder plan "实现用户登录" 生成计划方案');
26
+ console.log(' claude-coder plan -r requirements.md 从文件读取需求');
27
+ console.log(' claude-coder plan --planOnly 仅生成计划文档');
28
+ console.log(' claude-coder plan -i "优化系统" 交互模式,允许模型提问');
26
29
  console.log(' claude-coder run 执行所有待处理任务');
27
30
  console.log(' claude-coder run --max 1 单次执行');
28
31
  console.log(' claude-coder run --max 5 --pause 5 每 5 个 session 暂停确认');
29
32
  console.log(' claude-coder run --dry-run 预览模式');
30
- console.log(' claude-coder add -r 从 requirements.md 追加任务');
31
- console.log(' claude-coder add "..." --model opus-4 指定模型追加任务');
33
+ console.log(' claude-coder simplify 代码审查和简化');
34
+ console.log(' claude-coder simplify "内存效率" 聚焦特定领域审查');
32
35
  console.log(' claude-coder auth 导出 Playwright 登录状态');
33
- console.log(' claude-coder auth http://localhost:8080 指定登录 URL');
36
+ console.log(' claude-coder auth http://localhost:8080 指定登录 URL');
34
37
  console.log(' claude-coder status 查看进度和成本');
35
38
  console.log(`\n前置条件: npm install -g @anthropic-ai/claude-agent-sdk`);
36
39
  }
@@ -38,7 +41,7 @@ function showHelp() {
38
41
  function parseArgs(argv) {
39
42
  const args = argv.slice(2);
40
43
  const command = args[0];
41
- const opts = { max: 50, pause: 0, dryRun: false, readFile: null, model: null };
44
+ const opts = { max: 50, pause: 0, dryRun: false, readFile: null, model: null, n: 3, planOnly: false, interactive: false };
42
45
  const positional = [];
43
46
 
44
47
  for (let i = 1; i < args.length; i++) {
@@ -55,6 +58,10 @@ function parseArgs(argv) {
55
58
  case '--model':
56
59
  opts.model = args[++i] || null;
57
60
  break;
61
+ case '-n':
62
+ case '--n':
63
+ opts.n = parseInt(args[++i], 10) || 3;
64
+ break;
58
65
  case '-r': {
59
66
  const next = args[i + 1];
60
67
  if (next && !next.startsWith('-')) {
@@ -65,6 +72,13 @@ function parseArgs(argv) {
65
72
  }
66
73
  break;
67
74
  }
75
+ case '--planOnly':
76
+ opts.planOnly = true;
77
+ break;
78
+ case '-i':
79
+ case '--interactive':
80
+ opts.interactive = true;
81
+ break;
68
82
  case '--help':
69
83
  case '-h':
70
84
  showHelp();
@@ -96,54 +110,38 @@ async function main() {
96
110
 
97
111
  switch (command) {
98
112
  case 'run': {
99
- const runner = require('../src/runner');
113
+ const runner = require('../src/core/runner');
100
114
  await runner.run(opts);
101
115
  break;
102
116
  }
103
117
  case 'setup': {
104
- const setup = require('../src/setup');
118
+ const setup = require('../src/commands/setup');
105
119
  await setup.setup();
106
120
  break;
107
121
  }
108
122
  case 'init': {
109
- const { init } = require('../src/init');
123
+ const { init } = require('../src/core/init');
110
124
  await init();
111
125
  break;
112
126
  }
113
- case 'add': {
114
- const fs = require('fs');
115
- const nodePath = require('path');
116
- let instruction = positional[0] || '';
117
- if (opts.readFile) {
118
- const reqPath = nodePath.resolve(opts.readFile);
119
- if (!fs.existsSync(reqPath)) {
120
- console.error(`文件不存在: ${reqPath}`);
121
- process.exit(1);
122
- }
123
- instruction = fs.readFileSync(reqPath, 'utf8');
124
- console.log(`已读取需求文件: ${opts.readFile}`);
125
- }
126
- if (!instruction) {
127
- console.error('用法: claude-coder add "任务描述" 或 claude-coder add -r [requirements.md]');
128
- process.exit(1);
129
- }
130
- const runner = require('../src/runner');
131
- await runner.add(instruction, opts);
127
+ case 'plan': {
128
+ const { run: planRun } = require('../src/core/plan');
129
+ const input = positional[0] || '';
130
+ await planRun(input, opts);
132
131
  break;
133
132
  }
134
- case 'auth': {
135
- const { auth } = require('../src/auth');
136
- await auth(positional[0] || null);
133
+ case 'simplify': {
134
+ const { simplify } = require('../src/core/simplify');
135
+ await simplify(positional[0] || null, { n: opts.n });
137
136
  break;
138
137
  }
139
- case 'validate': {
140
- const validator = require('../src/validator');
141
- const result = await validator.validate();
142
- process.exit(result.fatal ? 1 : 0);
138
+ case 'auth': {
139
+ const { auth } = require('../src/commands/auth');
140
+ await auth(positional[0] || null);
143
141
  break;
144
142
  }
145
143
  case 'status': {
146
- const tasks = require('../src/tasks');
144
+ const tasks = require('../src/common/tasks');
147
145
  tasks.showStatus();
148
146
  break;
149
147
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-coder",
3
- "version": "1.7.1",
3
+ "version": "1.8.1",
4
4
  "description": "Claude Coder — Autonomous coding agent harness powered by Claude Code SDK. Scan, plan, code, validate, git-commit in a loop.",
5
5
  "bin": {
6
6
  "claude-coder": "bin/cli.js"
@@ -8,9 +8,11 @@
8
8
  "files": [
9
9
  "bin/",
10
10
  "src/",
11
- "prompts/",
12
11
  "templates/"
13
12
  ],
13
+ "scripts": {
14
+ "test": "node test/complete.test.js && node test/integration.test.js && node test/flow.test.js"
15
+ },
14
16
  "keywords": [
15
17
  "claude-coder",
16
18
  "claude",
@@ -43,5 +45,8 @@
43
45
  },
44
46
  "optionalDependencies": {
45
47
  "playwright": "^1.58.2"
48
+ },
49
+ "devDependencies": {
50
+ "@anthropic-ai/claude-agent-sdk": "^0.2.71"
46
51
  }
47
52
  }
@@ -4,39 +4,37 @@ const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
6
  const { execSync } = require('child_process');
7
- const { paths, loadConfig, log, getProjectRoot, ensureLoopDir } = require('./config');
7
+ const { loadConfig, log } = require('../common/config');
8
+ const { assets } = require('../common/assets');
9
+ const { appendGitignore } = require('../common/utils');
8
10
 
9
11
  function updateGitignore(entry) {
10
- const gitignorePath = path.join(getProjectRoot(), '.gitignore');
11
- let content = '';
12
- if (fs.existsSync(gitignorePath)) {
13
- content = fs.readFileSync(gitignorePath, 'utf8');
12
+ if (appendGitignore(assets.projectRoot, entry)) {
13
+ log('ok', `.gitignore 已添加: ${entry}`);
14
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
15
  }
21
16
 
22
- function updateMcpConfig(p, mode) {
17
+ function updateMcpConfig(mcpPath, mode) {
23
18
  let mcpConfig = {};
24
- if (fs.existsSync(p.mcpConfig)) {
25
- try { mcpConfig = JSON.parse(fs.readFileSync(p.mcpConfig, 'utf8')); } catch {}
19
+ if (fs.existsSync(mcpPath)) {
20
+ try { mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf8')); } catch {}
26
21
  }
27
22
 
28
23
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
29
24
 
30
25
  const args = ['@playwright/mcp@latest'];
26
+ const projectRoot = assets.projectRoot;
31
27
 
32
28
  switch (mode) {
33
29
  case 'persistent': {
34
- const relProfile = path.relative(getProjectRoot(), p.browserProfile).split(path.sep).join('/');
30
+ const browserProfilePath = assets.path('browserProfile');
31
+ const relProfile = path.relative(projectRoot, browserProfilePath).split(path.sep).join('/');
35
32
  args.push(`--user-data-dir=${relProfile}`);
36
33
  break;
37
34
  }
38
35
  case 'isolated': {
39
- const relAuth = path.relative(getProjectRoot(), p.playwrightAuth).split(path.sep).join('/');
36
+ const playwrightAuthPath = assets.path('playwrightAuth');
37
+ const relAuth = path.relative(projectRoot, playwrightAuthPath).split(path.sep).join('/');
40
38
  args.push('--isolated', `--storage-state=${relAuth}`);
41
39
  break;
42
40
  }
@@ -46,29 +44,27 @@ function updateMcpConfig(p, mode) {
46
44
  }
47
45
 
48
46
  mcpConfig.mcpServers.playwright = { command: 'npx', args };
49
- fs.writeFileSync(p.mcpConfig, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
47
+ fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
50
48
  log('ok', `.mcp.json 已配置 Playwright MCP (${mode} 模式)`);
51
49
  }
52
50
 
53
51
  function enableMcpPlaywrightEnv() {
54
- const p = paths();
55
- if (!fs.existsSync(p.envFile)) return;
52
+ const envPath = assets.path('env');
53
+ if (!envPath || !fs.existsSync(envPath)) return;
56
54
 
57
- let content = fs.readFileSync(p.envFile, 'utf8');
55
+ let content = fs.readFileSync(envPath, 'utf8');
58
56
  if (/^MCP_PLAYWRIGHT=/m.test(content)) {
59
57
  content = content.replace(/^MCP_PLAYWRIGHT=.*/m, 'MCP_PLAYWRIGHT=true');
60
58
  } else {
61
59
  const suffix = content.endsWith('\n') ? '' : '\n';
62
60
  content += `${suffix}MCP_PLAYWRIGHT=true\n`;
63
61
  }
64
- fs.writeFileSync(p.envFile, content, 'utf8');
62
+ fs.writeFileSync(envPath, content, 'utf8');
65
63
  log('ok', '.claude-coder/.env 已设置 MCP_PLAYWRIGHT=true');
66
64
  }
67
65
 
68
- // ── persistent 模式:启动持久化浏览器让用户登录 ──
69
-
70
- async function authPersistent(url, p) {
71
- const profileDir = p.browserProfile;
66
+ async function authPersistent(url) {
67
+ const profileDir = assets.path('browserProfile');
72
68
  if (!fs.existsSync(profileDir)) fs.mkdirSync(profileDir, { recursive: true });
73
69
 
74
70
  const lockFile = path.join(profileDir, 'SingletonLock');
@@ -112,17 +108,17 @@ async function authPersistent(url, p) {
112
108
  const helperModules = path.join(__dirname, '..', 'node_modules');
113
109
  const existingNodePath = process.env.NODE_PATH || '';
114
110
  const nodePath = existingNodePath ? `${helperModules}:${existingNodePath}` : helperModules;
111
+ const projectRoot = assets.projectRoot;
115
112
 
116
113
  let scriptOk = false;
117
114
  try {
118
115
  execSync(`node "${tmpScript}"`, {
119
116
  stdio: 'inherit',
120
- cwd: getProjectRoot(),
117
+ cwd: projectRoot,
121
118
  env: { ...process.env, NODE_PATH: nodePath },
122
119
  });
123
120
  scriptOk = true;
124
121
  } catch {
125
- // 浏览器关闭时可能返回非零退出码,只要 profile 目录有内容就认为成功
126
122
  const profileFiles = fs.readdirSync(profileDir);
127
123
  scriptOk = profileFiles.length > 2;
128
124
  if (!scriptOk) {
@@ -136,21 +132,23 @@ async function authPersistent(url, p) {
136
132
 
137
133
  try { fs.unlinkSync(tmpScript); } catch {}
138
134
 
135
+ const mcpPath = assets.path('mcpConfig');
139
136
  log('ok', '登录状态已保存到持久化配置');
140
- updateMcpConfig(p, 'persistent');
137
+ updateMcpConfig(mcpPath, 'persistent');
141
138
  updateGitignore('.claude-coder/.runtime/browser-profile');
142
139
  enableMcpPlaywrightEnv();
143
140
 
144
141
  console.log('');
145
142
  log('ok', '配置完成!');
146
- const relProfile = path.relative(getProjectRoot(), profileDir);
143
+ const relProfile = path.relative(projectRoot, profileDir);
147
144
  log('info', `MCP 使用 persistent 模式 (user-data-dir: ${relProfile})`);
148
145
  log('info', '如需更新登录状态,重新运行 claude-coder auth');
149
146
  }
150
147
 
151
- // ── isolated 模式:使用 codegen 录制 storage-state ──
148
+ async function authIsolated(url) {
149
+ const playwrightAuthPath = assets.path('playwrightAuth');
150
+ const projectRoot = assets.projectRoot;
152
151
 
153
- async function authIsolated(url, p) {
154
152
  console.log('操作步骤:');
155
153
  console.log(' 1. 浏览器将自动打开,请手动完成登录');
156
154
  console.log(' 2. 登录成功后关闭浏览器窗口');
@@ -160,24 +158,25 @@ async function authIsolated(url, p) {
160
158
 
161
159
  try {
162
160
  execSync(
163
- `npx playwright codegen --save-storage="${p.playwrightAuth}" "${url}"`,
164
- { stdio: 'inherit', cwd: getProjectRoot() }
161
+ `npx playwright codegen --save-storage="${playwrightAuthPath}" "${url}"`,
162
+ { stdio: 'inherit', cwd: projectRoot }
165
163
  );
166
164
  } catch (err) {
167
- if (!fs.existsSync(p.playwrightAuth)) {
165
+ if (!fs.existsSync(playwrightAuthPath)) {
168
166
  log('error', `Playwright 登录状态导出失败: ${err.message}`);
169
167
  log('info', '请确保已安装: npx playwright install chromium');
170
168
  return;
171
169
  }
172
170
  }
173
171
 
174
- if (!fs.existsSync(p.playwrightAuth)) {
172
+ if (!fs.existsSync(playwrightAuthPath)) {
175
173
  log('error', '未检测到导出的登录状态文件');
176
174
  return;
177
175
  }
178
176
 
177
+ const mcpPath = assets.path('mcpConfig');
179
178
  log('ok', '登录状态已保存到 playwright-auth.json');
180
- updateMcpConfig(p, 'isolated');
179
+ updateMcpConfig(mcpPath, 'isolated');
181
180
  updateGitignore('.claude-coder/playwright-auth.json');
182
181
  enableMcpPlaywrightEnv();
183
182
 
@@ -188,9 +187,7 @@ async function authIsolated(url, p) {
188
187
  log('info', '如需更新登录状态,重新运行 claude-coder auth');
189
188
  }
190
189
 
191
- // ── extension 模式:连接真实浏览器 ──
192
-
193
- function authExtension(p) {
190
+ function authExtension() {
194
191
  console.log('Extension 模式说明:');
195
192
  console.log('');
196
193
  console.log(' 此模式通过 Chrome 扩展连接到您正在运行的浏览器。');
@@ -203,7 +200,8 @@ function authExtension(p) {
203
200
  console.log(' 3. 无需额外认证操作,您的浏览器登录态将自动可用');
204
201
  console.log('');
205
202
 
206
- updateMcpConfig(p, 'extension');
203
+ const mcpPath = assets.path('mcpConfig');
204
+ updateMcpConfig(mcpPath, 'extension');
207
205
  enableMcpPlaywrightEnv();
208
206
 
209
207
  console.log('');
@@ -212,12 +210,9 @@ function authExtension(p) {
212
210
  log('info', '确保 Chrome/Edge 已运行且 Playwright MCP Bridge 扩展已启用');
213
211
  }
214
212
 
215
- // ── 主入口 ──
216
-
217
213
  async function auth(url) {
218
- ensureLoopDir();
214
+ assets.ensureDirs();
219
215
  const config = loadConfig();
220
- const p = paths();
221
216
  const mode = config.playwrightMode;
222
217
  const targetUrl = url || 'http://localhost:3000';
223
218
 
@@ -227,13 +222,13 @@ async function auth(url) {
227
222
 
228
223
  switch (mode) {
229
224
  case 'persistent':
230
- await authPersistent(targetUrl, p);
225
+ await authPersistent(targetUrl);
231
226
  break;
232
227
  case 'isolated':
233
- await authIsolated(targetUrl, p);
228
+ await authIsolated(targetUrl);
234
229
  break;
235
230
  case 'extension':
236
- authExtension(p);
231
+ authExtension();
237
232
  break;
238
233
  default:
239
234
  log('error', `未知的 Playwright 模式: ${mode}`);