harness-browser 0.1.1__tar.gz
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.
- harness_browser-0.1.1/.codebuddy/skills/pypi-publish/SKILL.md +307 -0
- harness_browser-0.1.1/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
- harness_browser-0.1.1/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
- harness_browser-0.1.1/.github/dependabot.yml +12 -0
- harness_browser-0.1.1/.github/pull_request_template.md +9 -0
- harness_browser-0.1.1/.github/workflows/ci.yml +26 -0
- harness_browser-0.1.1/.github/workflows/codeql.yml +20 -0
- harness_browser-0.1.1/.github/workflows/release.yml +18 -0
- harness_browser-0.1.1/.gitignore +10 -0
- harness_browser-0.1.1/.pre-commit-config.yaml +12 -0
- harness_browser-0.1.1/.python-version +1 -0
- harness_browser-0.1.1/AGENTS.md +122 -0
- harness_browser-0.1.1/CHANGELOG.md +123 -0
- harness_browser-0.1.1/CONTRIBUTING.md +61 -0
- harness_browser-0.1.1/Makefile +26 -0
- harness_browser-0.1.1/PKG-INFO +344 -0
- harness_browser-0.1.1/README.md +319 -0
- harness_browser-0.1.1/README.zh.md +315 -0
- harness_browser-0.1.1/docs/superpowers/plans/2026-05-22-orckit-browser-use.md +3078 -0
- harness_browser-0.1.1/docs/superpowers/specs/2026-05-22-orckit-browser-use-design.md +552 -0
- harness_browser-0.1.1/pyproject.toml +55 -0
- harness_browser-0.1.1/skills/harness-browser/SKILL.md +115 -0
- harness_browser-0.1.1/skills/harness-browser-zh/SKILL.md +114 -0
- harness_browser-0.1.1/src/harness_browser/__init__.py +18 -0
- harness_browser-0.1.1/src/harness_browser/actions/__init__.py +0 -0
- harness_browser-0.1.1/src/harness_browser/actions/capture.py +228 -0
- harness_browser-0.1.1/src/harness_browser/actions/interact.py +163 -0
- harness_browser-0.1.1/src/harness_browser/actions/js_eval.py +59 -0
- harness_browser-0.1.1/src/harness_browser/actions/navigate.py +76 -0
- harness_browser-0.1.1/src/harness_browser/cdp/__init__.py +0 -0
- harness_browser-0.1.1/src/harness_browser/cdp/client.py +135 -0
- harness_browser-0.1.1/src/harness_browser/cdp/launcher.py +337 -0
- harness_browser-0.1.1/src/harness_browser/dom/__init__.py +0 -0
- harness_browser-0.1.1/src/harness_browser/dom/builder.py +273 -0
- harness_browser-0.1.1/src/harness_browser/dom/refs.py +29 -0
- harness_browser-0.1.1/src/harness_browser/hooks.py +50 -0
- harness_browser-0.1.1/src/harness_browser/mcp_server.py +146 -0
- harness_browser-0.1.1/src/harness_browser/mode.py +75 -0
- harness_browser-0.1.1/src/harness_browser/models.py +42 -0
- harness_browser-0.1.1/src/harness_browser/profile.py +51 -0
- harness_browser-0.1.1/src/harness_browser/py.typed +0 -0
- harness_browser-0.1.1/src/harness_browser/session.py +516 -0
- harness_browser-0.1.1/src/harness_browser/settings.py +155 -0
- harness_browser-0.1.1/src/harness_browser/tool_interface.py +122 -0
- harness_browser-0.1.1/tests/__init__.py +0 -0
- harness_browser-0.1.1/tests/integration/__init__.py +0 -0
- harness_browser-0.1.1/tests/integration/conftest.py +12 -0
- harness_browser-0.1.1/tests/integration/test_session.py +37 -0
- harness_browser-0.1.1/tests/unit/__init__.py +0 -0
- harness_browser-0.1.1/tests/unit/test_capture.py +86 -0
- harness_browser-0.1.1/tests/unit/test_dom_builder.py +127 -0
- harness_browser-0.1.1/tests/unit/test_hooks.py +69 -0
- harness_browser-0.1.1/tests/unit/test_launcher.py +128 -0
- harness_browser-0.1.1/tests/unit/test_mode.py +61 -0
- harness_browser-0.1.1/tests/unit/test_models.py +43 -0
- harness_browser-0.1.1/tests/unit/test_profile.py +58 -0
- harness_browser-0.1.1/tests/unit/test_refs.py +36 -0
- harness_browser-0.1.1/tests/unit/test_settings.py +99 -0
- harness_browser-0.1.1/tests/unit/test_tool_interface.py +32 -0
- harness_browser-0.1.1/uv.lock +1911 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish
|
|
3
|
+
description: 当需要发布 Python 包到 PyPI 时使用 — 创建 release 分支、升版本号、更新 CHANGELOG、发布到 PyPI、打 tag、并创建合并请求到 main
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Publish
|
|
7
|
+
|
|
8
|
+
自动化 Python 包的完整发布流程。
|
|
9
|
+
|
|
10
|
+
**开始时宣告:** "正在使用 publish 技能发布版本 {VERSION}。"
|
|
11
|
+
|
|
12
|
+
## 配置项
|
|
13
|
+
|
|
14
|
+
以下配置有默认值,可在项目的 `.codebuddy/skills/publish/SKILL.md` 中覆盖。
|
|
15
|
+
|
|
16
|
+
| 配置项 | 默认值 | 说明 |
|
|
17
|
+
|--------|--------|------|
|
|
18
|
+
| `PUBLISH_CMD` | 自动检测(见步骤 5) | 发布到 PyPI 的命令 |
|
|
19
|
+
| `CHANGELOG_FILE` | `CHANGELOG.md` | 相对于仓库根目录的路径,文件不存在则跳过 |
|
|
20
|
+
| `VERSION_FILE` | `pyproject.toml` | 包含版本号的文件 |
|
|
21
|
+
| `VERSION_PATTERN` | `^\s*version\s*=\s*"[^"]+"` | 匹配版本行的正则表达式 |
|
|
22
|
+
| `TAG_PREFIX` | ``(空) | Git tag 前缀,设为 `v` 则生成 `v0.1.14` 风格标签 |
|
|
23
|
+
| `REMOTE` | `origin` | Git 远程仓库名 |
|
|
24
|
+
| `TARGET_BRANCH` | `main` | 合并请求的目标分支 |
|
|
25
|
+
| `RELEASE_BRANCH_PREFIX` | `release/` | release 分支名前缀 |
|
|
26
|
+
|
|
27
|
+
## 调用方式
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
/publish 0.1.14
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
目标版本号是唯一必填参数,其余均从配置或自动检测获取。
|
|
34
|
+
|
|
35
|
+
## 发布流程
|
|
36
|
+
|
|
37
|
+
### 步骤 1 — 读取配置并确认版本
|
|
38
|
+
|
|
39
|
+
1. 获取仓库根目录:`git rev-parse --show-toplevel`
|
|
40
|
+
2. 从 `VERSION_FILE` 读取当前版本:
|
|
41
|
+
```bash
|
|
42
|
+
grep -E '^\s*version\s*=\s*"[^"]+"' pyproject.toml
|
|
43
|
+
```
|
|
44
|
+
3. 检查未提交的更改:
|
|
45
|
+
```bash
|
|
46
|
+
git status --short
|
|
47
|
+
```
|
|
48
|
+
如果存在未提交的更改,它们将被包含在步骤 4 的发布提交中。这是有意为之 — 所有待处理的工作随版本一起发布。
|
|
49
|
+
4. 查找最近的 git tag:
|
|
50
|
+
```bash
|
|
51
|
+
git tag --sort=-creatordate | head -1
|
|
52
|
+
```
|
|
53
|
+
如果没有 tag,视为首次发布(在步骤 3 中使用从仓库初始到 HEAD 的所有提交)。
|
|
54
|
+
5. 展示确认信息:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
当前版本 (pyproject.toml): X.Y.Z
|
|
58
|
+
目标版本: A.B.C
|
|
59
|
+
上次发布 tag: X.Y.Z (YYYY-MM-DD)
|
|
60
|
+
Release 分支: release/A.B.C
|
|
61
|
+
目标分支 (MR): main
|
|
62
|
+
|
|
63
|
+
确认发布 X.Y.Z → A.B.C?[y/N]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
如果用户未输入 `y` 确认,立即中止。
|
|
67
|
+
|
|
68
|
+
### 步骤 2 — 创建 release 分支
|
|
69
|
+
|
|
70
|
+
从当前 HEAD 创建并切换到新的 release 分支:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git checkout -b {RELEASE_BRANCH_PREFIX}{version}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
如果分支已存在,中止:
|
|
77
|
+
```
|
|
78
|
+
✗ 分支 {RELEASE_BRANCH_PREFIX}{version} 已存在。
|
|
79
|
+
请手动删除:git branch -D {RELEASE_BRANCH_PREFIX}{version}
|
|
80
|
+
然后重新运行 /publish。
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 步骤 3 — 分析变更并生成 CHANGELOG 草稿
|
|
84
|
+
|
|
85
|
+
1. 获取上次 tag 以来的提交:
|
|
86
|
+
```bash
|
|
87
|
+
git log {last_tag}..HEAD --oneline
|
|
88
|
+
# 首次发布时:
|
|
89
|
+
git log --oneline
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
2. 如果没有找到提交:
|
|
93
|
+
```
|
|
94
|
+
⚠ 自上次发布 tag ({last_tag}) 以来没有新提交。
|
|
95
|
+
是否继续?[y/N]
|
|
96
|
+
```
|
|
97
|
+
用户未确认则中止。
|
|
98
|
+
|
|
99
|
+
3. 按提交前缀分类,生成 Keep a Changelog 格式的条目。
|
|
100
|
+
|
|
101
|
+
**CHANGELOG 内容必须用中文书写。** 将每个提交总结为简洁的中文要点 — 不要逐字翻译提交信息。适当合并相关提交。
|
|
102
|
+
|
|
103
|
+
分类规则:
|
|
104
|
+
- 以 `feat:` 或 `feat(` 开头 → **新增**
|
|
105
|
+
- 以 `fix:` 或 `fix(` 开头 → **修复**
|
|
106
|
+
- 以 `refactor:` 或 `perf:` 开头 → **变更**
|
|
107
|
+
- 包含 `!:` 或提交正文含 `BREAKING CHANGE:` → **变更**,加 `**Breaking:**` 前缀
|
|
108
|
+
- 以 `docs:` 开头 → **变更**
|
|
109
|
+
- 以 `chore:`、`test:`、`ci:` 开头 → 忽略(基础设施噪音)
|
|
110
|
+
- 其他所有提交 → **变更**
|
|
111
|
+
- 移除的功能 → **移除**
|
|
112
|
+
- 安全修复 → **安全**
|
|
113
|
+
|
|
114
|
+
输出格式:
|
|
115
|
+
```markdown
|
|
116
|
+
## [A.B.C] - YYYY-MM-DD
|
|
117
|
+
|
|
118
|
+
### 新增
|
|
119
|
+
- 中文描述新增功能
|
|
120
|
+
|
|
121
|
+
### 修复
|
|
122
|
+
- 中文描述修复内容
|
|
123
|
+
|
|
124
|
+
### 变更
|
|
125
|
+
- 中文描述行为变更
|
|
126
|
+
|
|
127
|
+
### 移除
|
|
128
|
+
- 中文描述移除内容
|
|
129
|
+
|
|
130
|
+
### 安全
|
|
131
|
+
- 中文描述安全修复
|
|
132
|
+
```
|
|
133
|
+
省略空的分类。日期使用 ISO 8601 格式(当天日期)。日期行与第一个分类之间、各分类之间保留空行。
|
|
134
|
+
|
|
135
|
+
4. 向用户展示草稿并请求确认:
|
|
136
|
+
```
|
|
137
|
+
CHANGELOG 草稿:
|
|
138
|
+
|
|
139
|
+
{draft}
|
|
140
|
+
|
|
141
|
+
添加到 CHANGELOG.md?[y/N/edit]
|
|
142
|
+
```
|
|
143
|
+
- `y` → 继续
|
|
144
|
+
- `n` → 中止
|
|
145
|
+
- `edit` 或其他反馈 → 询问用户:"需要什么修改?" — 等待回复后重新生成,再次展示确认。循环直到 `y` 或 `n`。
|
|
146
|
+
|
|
147
|
+
5. 如果 `CHANGELOG_FILE` 不存在,跳过此步骤(无需警告)。
|
|
148
|
+
|
|
149
|
+
### 步骤 4 — 更新文件、提交并推送 release 分支
|
|
150
|
+
|
|
151
|
+
按顺序执行:
|
|
152
|
+
|
|
153
|
+
**4a. 更新 CHANGELOG:**
|
|
154
|
+
|
|
155
|
+
在 `CHANGELOG_FILE` 中找到 `## [Unreleased]` 标题,在其后插入新版本条目(保持 `[Unreleased]` 为空):
|
|
156
|
+
|
|
157
|
+
```markdown
|
|
158
|
+
## [Unreleased]
|
|
159
|
+
|
|
160
|
+
## [A.B.C] - YYYY-MM-DD
|
|
161
|
+
### 新增
|
|
162
|
+
- ...
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
如果 `## [Unreleased]` 标题不存在,在 `# Changelog` 标题行之后插入新条目(若无标题则插入到文件顶部)。
|
|
166
|
+
|
|
167
|
+
**4b. 升级 VERSION_FILE 中的版本号:**
|
|
168
|
+
|
|
169
|
+
替换 `VERSION_PATTERN` 匹配到的版本行。对于 `pyproject.toml`:
|
|
170
|
+
```bash
|
|
171
|
+
# 查找当前行:
|
|
172
|
+
grep -n '^\s*version\s*=\s*"[^"]+"' pyproject.toml
|
|
173
|
+
# 用 Edit 工具将该行的 "X.Y.Z" 替换为 "A.B.C"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**4c. 提交:**
|
|
177
|
+
|
|
178
|
+
检查工作树状态:
|
|
179
|
+
```bash
|
|
180
|
+
git status --short
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- 如果有更改(已暂存或未暂存):暂存所有文件并提交:
|
|
184
|
+
```bash
|
|
185
|
+
git add -A
|
|
186
|
+
git commit -m "chore: release {version}"
|
|
187
|
+
```
|
|
188
|
+
- 如果工作树已干净:无需提交,跳过。
|
|
189
|
+
|
|
190
|
+
**4d. 推送 release 分支:**
|
|
191
|
+
```bash
|
|
192
|
+
git push -u {REMOTE} {RELEASE_BRANCH_PREFIX}{version}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
推送失败则中止。
|
|
196
|
+
|
|
197
|
+
### 步骤 5 — 发布到 PyPI
|
|
198
|
+
|
|
199
|
+
**自动检测 PUBLISH_CMD**(仅在配置未覆盖时):
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# 检查 Makefile 是否有 publish 目标
|
|
203
|
+
grep -q '^\s*publish\s*:' Makefile 2>/dev/null && echo "make publish" || echo "python -m build && python -m twine upload dist/*"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- 如果 `make publish` 目标存在 → `PUBLISH_CMD=make publish`
|
|
207
|
+
- 否则 → `PUBLISH_CMD=python -m build && python -m twine upload dist/*`
|
|
208
|
+
- 如果 `python -m build` 或 `python -m twine` 未安装,中止:"缺少构建工具。请安装:`pip install build twine`"
|
|
209
|
+
|
|
210
|
+
执行:
|
|
211
|
+
```bash
|
|
212
|
+
{PUBLISH_CMD}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
实时输出。如果退出码非零:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
✗ 发布失败(退出码 N)。已停止 — 不会创建 tag。
|
|
219
|
+
请检查上方输出,解决问题后重新运行 /publish。
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**不继续执行步骤 6。**
|
|
223
|
+
|
|
224
|
+
### 步骤 6 — 打 tag 并推送
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
git tag {TAG_PREFIX}{version}
|
|
228
|
+
git push {REMOTE} {TAG_PREFIX}{version}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
如果 `git tag` 因 tag 已存在而失败:
|
|
232
|
+
```
|
|
233
|
+
✗ Tag {TAG_PREFIX}{version} 已存在。
|
|
234
|
+
请手动删除:git tag -d {TAG_PREFIX}{version}
|
|
235
|
+
然后重新运行 /publish 从此步骤重试。
|
|
236
|
+
```
|
|
237
|
+
中止。
|
|
238
|
+
|
|
239
|
+
### 步骤 7 — 创建合并请求
|
|
240
|
+
|
|
241
|
+
使用工丰 MCP 工具从 release 分支创建到 `TARGET_BRANCH` 的合并请求:
|
|
242
|
+
|
|
243
|
+
1. 从 git remote 检测项目路径:
|
|
244
|
+
```bash
|
|
245
|
+
git remote get-url {REMOTE} | sed -E 's|.*[:/](.+/.+)\.git$|\1|'
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
2. 使用 `mcp__gongfeng__create_merge_request` 创建 MR:
|
|
249
|
+
- `project_id`:项目完整路径(如 `orcakit/finnie`)
|
|
250
|
+
- `title`:`chore: release {version}`
|
|
251
|
+
- `source_branch`:`{RELEASE_BRANCH_PREFIX}{version}`
|
|
252
|
+
- `target_branch`:`{TARGET_BRANCH}`
|
|
253
|
+
- `description`:步骤 3 生成的 CHANGELOG 条目
|
|
254
|
+
|
|
255
|
+
3. 成功时展示:
|
|
256
|
+
```
|
|
257
|
+
✓ 已发布 {package_name} {version} 到 PyPI
|
|
258
|
+
✓ 已创建并推送 tag {TAG_PREFIX}{version} 到 {REMOTE}
|
|
259
|
+
✓ 已创建合并请求:{RELEASE_BRANCH_PREFIX}{version} → {TARGET_BRANCH}
|
|
260
|
+
链接:{mr_url}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
4. MR 创建失败时展示警告(非致命 — 发布已成功):
|
|
264
|
+
```
|
|
265
|
+
⚠ 发布成功,但合并请求创建失败。
|
|
266
|
+
请手动创建:{RELEASE_BRANCH_PREFIX}{version} → {TARGET_BRANCH}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 步骤 8 — 切回原分支
|
|
270
|
+
|
|
271
|
+
返回创建 release 分支之前所在的分支:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
git checkout {original_branch}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
确保流程结束后用户不会停留在 release 分支上。
|
|
278
|
+
|
|
279
|
+
## 错误处理参考
|
|
280
|
+
|
|
281
|
+
| 场景 | 行为 |
|
|
282
|
+
|------|------|
|
|
283
|
+
| `VERSION_FILE` 未找到 | 中止:"找不到 VERSION_FILE:{path}" |
|
|
284
|
+
| 文件中未匹配到版本号 | 中止:"在 {VERSION_FILE} 中找不到匹配 {VERSION_PATTERN} 的版本行" |
|
|
285
|
+
| 没有 git tag(首次发布) | 使用从仓库初始到 HEAD 的所有提交;提示"首次发布 — 使用完整 git 历史" |
|
|
286
|
+
| 上次 tag 以来无提交 | 警告并询问是否继续 |
|
|
287
|
+
| Release 分支已存在 | 中止并给出删除指令 |
|
|
288
|
+
| 步骤 4 推送失败 | 中止:文件已在本地更新但未推送 |
|
|
289
|
+
| 步骤 5 发布失败 | 中止:不创建 tag、不创建 MR |
|
|
290
|
+
| 步骤 6 tag 已存在 | 中止并给出删除指令 |
|
|
291
|
+
| 步骤 7 MR 创建失败 | 仅警告(发布已成功) |
|
|
292
|
+
|
|
293
|
+
## 红线规则
|
|
294
|
+
|
|
295
|
+
**绝不:**
|
|
296
|
+
- 在发布成功前创建 tag
|
|
297
|
+
- 在发布成功前创建合并请求
|
|
298
|
+
- 跳过步骤 1 的用户确认
|
|
299
|
+
- 跳过步骤 3 的 CHANGELOG 确认
|
|
300
|
+
- 在任何步骤失败后继续执行(步骤 7 MR 失败除外)
|
|
301
|
+
- 流程结束后让用户留在 release 分支
|
|
302
|
+
|
|
303
|
+
**始终:**
|
|
304
|
+
- 任何失败立即中止(步骤 7 除外)
|
|
305
|
+
- 中止前展示完整错误输出
|
|
306
|
+
- 插入新版本条目后保持 `[Unreleased]` 为空
|
|
307
|
+
- 流程结束时切回原分支
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug Report
|
|
3
|
+
about: Report a bug
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
## Steps to Reproduce
|
|
10
|
+
|
|
11
|
+
1.
|
|
12
|
+
2.
|
|
13
|
+
|
|
14
|
+
## Expected Behavior
|
|
15
|
+
|
|
16
|
+
## Actual Behavior
|
|
17
|
+
|
|
18
|
+
## Environment
|
|
19
|
+
|
|
20
|
+
- OS:
|
|
21
|
+
- Python version:
|
|
22
|
+
- harness-browser version:
|
|
23
|
+
- Chrome version:
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [main]
|
|
7
|
+
jobs:
|
|
8
|
+
lint-and-test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python-version: ["3.11", "3.12"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v3
|
|
16
|
+
with:
|
|
17
|
+
python-version: ${{ matrix.python-version }}
|
|
18
|
+
- run: uv sync --extra dev
|
|
19
|
+
- run: uv run ruff check src/ tests/
|
|
20
|
+
- run: uv run mypy src/
|
|
21
|
+
- name: Install Chrome
|
|
22
|
+
run: sudo apt-get install -y chromium-browser
|
|
23
|
+
- run: uv run pytest tests/ -v --cov=harness_browser --cov-report=xml
|
|
24
|
+
- uses: codecov/codecov-action@v4
|
|
25
|
+
with:
|
|
26
|
+
files: coverage.xml
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CodeQL
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [main]
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "0 3 * * 1"
|
|
9
|
+
jobs:
|
|
10
|
+
analyze:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
security-events: write
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: github/codeql-action/init@v3
|
|
17
|
+
with:
|
|
18
|
+
languages: python
|
|
19
|
+
- uses: github/codeql-action/autobuild@v3
|
|
20
|
+
- uses: github/codeql-action/analyze@v3
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags:
|
|
5
|
+
- "v*.*.*"
|
|
6
|
+
jobs:
|
|
7
|
+
release:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
environment: pypi
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v3
|
|
15
|
+
- run: uv build
|
|
16
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
17
|
+
with:
|
|
18
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.4.4
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
9
|
+
rev: v1.9.0
|
|
10
|
+
hooks:
|
|
11
|
+
- id: mypy
|
|
12
|
+
additional_dependencies: [pydantic, websockets, aiohttp]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# CODEBUDDY.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to CodeBuddy Code when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
All targets run through `uv` and the Makefile:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
make test # full suite with coverage (uv run pytest tests/ -v --cov=harness_browser)
|
|
11
|
+
make lint # ruff check + mypy strict on src/
|
|
12
|
+
make format # ruff format src/ tests/
|
|
13
|
+
make build # uv build → wheel + sdist
|
|
14
|
+
make mcp # launch the MCP stdio server (python -m harness_browser.mcp_server)
|
|
15
|
+
make publish # uv publish (PyPI release)
|
|
16
|
+
make clean # remove dist/, .coverage, .mypy_cache/, .ruff_cache/
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Useful narrower invocations:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Unit tests only — no Chrome required
|
|
23
|
+
uv run pytest tests/unit/ -q
|
|
24
|
+
|
|
25
|
+
# A single test file or test
|
|
26
|
+
uv run pytest tests/unit/test_mode.py -q
|
|
27
|
+
uv run pytest tests/unit/test_settings.py::test_env_override_browser_mode -q
|
|
28
|
+
|
|
29
|
+
# Integration tests — auto-skipped when Chrome/Chromium is not on the host
|
|
30
|
+
uv run pytest tests/integration/ -v
|
|
31
|
+
|
|
32
|
+
# Type-check or lint just one path
|
|
33
|
+
uv run mypy src/harness_browser/session.py
|
|
34
|
+
uv run ruff check src/harness_browser/cdp/
|
|
35
|
+
|
|
36
|
+
# Install dev extras
|
|
37
|
+
uv sync --extra dev
|
|
38
|
+
pre-commit install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`pytest-asyncio` runs in `asyncio_mode = "auto"` (set in `pyproject.toml`) — async test functions need no decorator. `tests/integration/conftest.py` skips the whole integration suite when `find_chrome()` returns None, so unit work on machines without Chrome stays green.
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
This package wraps Chrome over **pure CDP** (WebSocket, no Playwright) and exposes it three ways: an async Python API, a stateless `browser_tool()` for AI frameworks, and an MCP server. Understanding the call chain matters when changing anything in the launch / connect / DOM path.
|
|
46
|
+
|
|
47
|
+
### Layered call chain
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
browser_tool() ──► BrowserSession ──► _InternalCDPSession ──► CDPClient (websockets) ──► Chrome
|
|
51
|
+
mcp_server.py ──┘ │
|
|
52
|
+
└─► launcher.launch_or_attach + get_page_ws_url
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `tool_interface.browser_tool(action, profile=..., mode=...)` — stateless dispatch keyed by `profile`. Maintains a `_registry: dict[str, BrowserSession]`; first call per profile creates a session, subsequent calls reuse it. Action names map directly to `BrowserSession` methods via `getattr`.
|
|
56
|
+
- `session.BrowserSession` — public async API. Mixes in `HooksMixin` (events: `before_action`, `after_action`, `action_error`, `page_navigated`) and accumulates running totals in `metrics_summary()`.
|
|
57
|
+
- `session._InternalCDPSession` — owns one `CDPClient` + one `RefCache` per page. Honors `cfg.cdp_ws_url`: if set, **bypasses the launcher entirely** and connects directly to the given WebSocket (used for remote/Docker Chrome).
|
|
58
|
+
- `cdp.client.CDPClient` — minimal CDP framing over `websockets.asyncio.client`. `send(method, params)` returns the result; `enable_domain()` enables `Page` / `DOM` / `Runtime` / `Input` after connect.
|
|
59
|
+
- `cdp.launcher` — `find_chrome()`, `launch_or_attach()` (re-attach if port is already serving CDP), `_get_ws_url()` polls `http://{cdp_host}:{port}/json/version`, `get_page_ws_url()` picks the first `type==page` target. All HTTP probes go through `aiohttp` and **always read `cdp_host` from settings** — never hardcode `localhost`.
|
|
60
|
+
|
|
61
|
+
### Configuration: HarnessSettings (single source of truth)
|
|
62
|
+
|
|
63
|
+
`settings.HarnessSettings` is a `dataclass` whose every field uses `field(default_factory=lambda: _env_*(...))`. This means **each instantiation re-reads env vars**, which is what `monkeypatch.setenv` in tests relies on. The module-level `settings` singleton (read at import time) is the default everywhere; tests should construct a fresh `HarnessSettings()` rather than mutating it.
|
|
64
|
+
|
|
65
|
+
Env vars (all optional):
|
|
66
|
+
|
|
67
|
+
| Variable | Default | Used in |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `BROWSER_USE_PROFILES_DIR` | `~/.harness-browser/profiles` | `ProfileManager.__init__` |
|
|
70
|
+
| `BROWSER_USE_CDP_HOST` | `localhost` | `launcher._get_ws_url`, `_port_in_use`, `get_page_ws_url`, `session.list_tabs`; also drives `--remote-debugging-address` when non-loopback |
|
|
71
|
+
| `BROWSER_USE_CDP_PORT_START` | `9222` | `ProfileManager` port assignment |
|
|
72
|
+
| `BROWSER_USE_MODE` | `auto` | resolved via `mode.resolve_headless()` |
|
|
73
|
+
| `BROWSER_USE_CHROME_BIN` | auto-detect | `launcher.find_chrome` (checked before path search) |
|
|
74
|
+
| `BROWSER_USE_CDP_TIMEOUT` | `30.0` | `CDPClient` send timeout |
|
|
75
|
+
| `BROWSER_USE_LAUNCH_RETRIES` / `BROWSER_USE_LAUNCH_DELAY` | `20` / `0.25` | launcher polling |
|
|
76
|
+
| `BROWSER_USE_CDP_WS_URL` | — | `_InternalCDPSession.connect` direct-connect path |
|
|
77
|
+
|
|
78
|
+
The MCP server has no per-tool `inputSchema` field for these — they are passed via the standard `mcpServers.<name>.env` block in client config. Do not add them to the tool schema.
|
|
79
|
+
|
|
80
|
+
### Browser mode resolution (harness_browser/mode.py)
|
|
81
|
+
|
|
82
|
+
Three modes — `auto` / `headed` / `headless`. Resolution priority in `BrowserSession.create()`:
|
|
83
|
+
|
|
84
|
+
1. Explicit `headless=True/False` argument (legacy escape hatch, wins outright)
|
|
85
|
+
2. Explicit `mode=` argument
|
|
86
|
+
3. `cfg.browser_mode` (from `HarnessSettings`, which reads `BROWSER_USE_MODE`)
|
|
87
|
+
|
|
88
|
+
`auto` calls `has_desktop_environment()`: True on macOS/Windows always; on Linux requires `$DISPLAY` or `$WAYLAND_DISPLAY`. The result is fed to `resolve_headless()`. `_build_flags` only appends `--headless=new` when the resolved bool is True.
|
|
89
|
+
|
|
90
|
+
### DOM output: 4 levels and the ref system
|
|
91
|
+
|
|
92
|
+
- `dom.builder.DOMBuilder` produces `minimal` (~50 tok) / `interactive` (~200–500 tok) / `full` (~1000–3000 tok) / `structured` (JSON) views from a CDP `DOM.getDocument` snapshot.
|
|
93
|
+
- `dom.refs.RefCache` issues stable refs like `btn_2`, `inp_search` mapping `ref → CDP nodeId`. **Refs are invalidated on navigation** — `nav_actions.navigate` / `go_back` / `go_forward` / `reload` clear the cache. Any new navigation-class action MUST do the same or you'll hand the agent stale node IDs.
|
|
94
|
+
- Token estimate is `len(content) // 4` — keep this when adding new actions that build text content.
|
|
95
|
+
|
|
96
|
+
### Profiles and login persistence
|
|
97
|
+
|
|
98
|
+
`profile.ProfileManager` allocates one `Chrome --user-data-dir` per profile name under `cfg.profiles_dir`, with `cdp_port = cfg.cdp_port_start + index`. This is the entire login-persistence story: same profile name → same data dir → cookies and storage reused. Don't add ad-hoc paths for cookies/auth — go through `Profile`.
|
|
99
|
+
|
|
100
|
+
### Hooks and metrics
|
|
101
|
+
|
|
102
|
+
`HooksMixin` is what `BrowserSession` mixes in. Every action goes:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
_fire("before_action", {...}) → action body → _record(result)
|
|
106
|
+
└─► _fire("after_action", metrics) (or "action_error")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`ActionMetrics` (`models.py`) carries `duration_ms`, `dom_nodes_scanned`, `estimated_tokens`, `screenshot_size_kb`. `BrowserSession.metrics_summary()` aggregates totals across the session.
|
|
110
|
+
|
|
111
|
+
### Skill files (Claude Code / agent-facing)
|
|
112
|
+
|
|
113
|
+
Live at `.codebuddy/skills/harness-browser/SKILL.md` (English) and `.codebuddy/skills/harness-browser-zh/SKILL.md` (Chinese). They use the standard YAML-frontmatter format (`name`, `description`, `allowed-tools`) and are designed to be `cp -r`'d into other agent projects. Keep these in sync when the public surface (env vars, modes, action list) changes.
|
|
114
|
+
|
|
115
|
+
## Project conventions
|
|
116
|
+
|
|
117
|
+
- **Docs language**: README is bilingual (`README.md` canonical English + `README.zh.md`); `CHANGELOG.md` and `CONTRIBUTING.md` are Chinese-only by deliberate choice.
|
|
118
|
+
- **ruff**: `line-length = 88`, lint selects `E F I N W`. Do not raise the line length to dodge wraps.
|
|
119
|
+
- **mypy**: `strict = true`, `python_version = "3.11"`. New code must type-check under strict mode; reach for `from __future__ import annotations` (already used everywhere) before adding `# type: ignore`.
|
|
120
|
+
- **No Playwright, no Selenium**: the dependency story is just `websockets`, `aiohttp`, `pydantic`, `mcp`. Don't add browser drivers.
|
|
121
|
+
- **`localhost` is never hardcoded** — always go through `cfg.cdp_host`. A dedicated regression hazard if you forget.
|
|
122
|
+
- **Repository URL** in docs is `https://git.woa.com/orcakit/browser-use.git`. PyPI badges still point at `github.com/harness/...` (publishing target).
|