From 59bf75a4cc816e35a556c77f196ba745f0fdae88 Mon Sep 17 00:00:00 2001 From: xhwSkhizein Date: Wed, 15 Apr 2026 10:29:37 +0800 Subject: [PATCH 1/2] docs: add usage guides and install-skills docs --- README.md | 29 ++ docs/browser-cli-project-guide-zh.md | 249 +++++++++++++++++ docs/browser-cli-usage-guide-zh.md | 403 +++++++++++++++++++++++++++ docs/install-skills.md | 85 ++++++ docs/installed-with-uv.md | 43 ++- 5 files changed, 805 insertions(+), 4 deletions(-) create mode 100644 docs/browser-cli-project-guide-zh.md create mode 100644 docs/browser-cli-usage-guide-zh.md create mode 100644 docs/install-skills.md diff --git a/README.md b/README.md index 4291ac4..d50fc5f 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,35 @@ browser-cli --help Installed users should start with [`docs/installed-with-uv.md`](docs/installed-with-uv.md). For removal and local cleanup guidance, see [`docs/uninstall.md`](docs/uninstall.md). +## Install Browser CLI Skills + +Browser CLI ships with three packaged skills that can be installed into an +agent skills directory: + +- `browser-cli-converge` +- `browser-cli-delivery` +- `browser-cli-explore` + +Install them into the default skills root: + +```bash +browser-cli install-skills +``` + +By default, Browser CLI writes the packaged skills into `~/.agents/skills`. +Use `--dry-run` to preview the install and `--target` to override the +destination: + +```bash +browser-cli install-skills --dry-run +browser-cli install-skills --target ~/.codex/skills +``` + +You can rerun the command safely. Existing packaged Browser CLI skills at the +target path are replaced with the packaged versions from the installed wheel. +For a longer installed-user walkthrough, see +[`docs/install-skills.md`](docs/install-skills.md). + ## Development Clone the repository and sync the managed development environment: diff --git a/docs/browser-cli-project-guide-zh.md b/docs/browser-cli-project-guide-zh.md new file mode 100644 index 0000000..e42aec2 --- /dev/null +++ b/docs/browser-cli-project-guide-zh.md @@ -0,0 +1,249 @@ +# Browser CLI 项目导读 + +## 先看全貌 + +这个项目不是“给开发者写脚本用的一个浏览器库”,它更像一个站在命令行后面的浏览器操作员。你发出一句话式命令,它去开浏览器、找页面、点按钮、读内容,然后把结果用稳定的 JSON 或文本吐回来。 + +它做三件事,而且三件事分得很清楚。 + +1. `browser-cli read` 负责“一次性读取网页”。 +2. daemon-backed actions 负责“持续控制浏览器”。 +3. `task` 和 `automation` 负责“把一串操作固化成可复用任务”。 + +这三件事连起来,就是一个给 AI agent 用的浏览器工作台。入口在 [main.py](/home/hongv/workspace/browser-cli/src/browser_cli/cli/main.py),命令目录很小,后面的系统很厚。 + +--- + +## 先用一个具体场景理解它 + +假设你想做两件事。 + +第一件事,你只想知道一个页面渲染后的内容。页面里的文字要等 JavaScript 跑完才出现。你不想自己管 Chrome 进程、等待时机、滚动到底部。你只想打: + +```bash +browser-cli read https://example.com --scroll-bottom +``` + +这时项目走的是“短路径”: + +- CLI 收到命令,在 [read.py](/home/hongv/workspace/browser-cli/src/browser_cli/commands/read.py) 里把 URL 规范化。 +- 它调用任务运行时客户端 [client.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/client.py)。 +- 客户端走共享读取逻辑 [read.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/read.py)。 +- 这层把请求发给 daemon 的 `read-page`。 +- daemon 再让浏览器服务 [browser_service.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/browser_service.py) 新开一个临时页、等待渲染、必要时滚动、抓 HTML 或 snapshot,然后把这个临时页关掉。 + +测试里专门盯着这件事:`read` 读完不能把临时标签页泄漏出来,已有标签页也不能被打乱。你能在 [test_task_runtime_read.py](/home/hongv/workspace/browser-cli/tests/integration/test_task_runtime_read.py) 里看到这个约束。 + +第二件事,你想持续操作页面。比如: + +```bash +browser-cli open https://example.com +browser-cli snapshot +browser-cli click @8d4b03a9 +browser-cli fill @abcd1234 "hello" +``` + +这时项目走的是“长路径”: + +- CLI 仍然很薄,[action.py](/home/hongv/workspace/browser-cli/src/browser_cli/commands/action.py) 只负责把命令变成请求。 +- action catalog 在 [cli_specs.py](/home/hongv/workspace/browser-cli/src/browser_cli/actions/cli_specs.py)。这里列了 60 多个动作,像 `open`、`snapshot`、`click`、`network-start`、`verify-text`。 +- daemon client 在 [client.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/client.py) 里保证守护进程存在,然后把请求通过 Unix socket 发过去。 +- daemon app 在 [app.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/app.py) 里分发动作。 +- 真正干活的是 daemon 自己持有的浏览器服务 [browser_service.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/browser_service.py)。 + +所以,CLI 像前台。daemon 像总调度台。浏览器服务才像真正拿着鼠标和键盘的人。 + +--- + +## 它最重要的设计,不是“能开浏览器”,而是“把控制面收拢” + +很多浏览器自动化项目,一上来就让你直接写 Playwright API。这个项目刻意不这么做。它先冻结公共契约,再让实现去服从它。 + +你能从两个地方看出这种执拗。 + +- 产品契约守卫在 [product_contracts.py](/home/hongv/workspace/browser-cli/scripts/guards/product_contracts.py)。它会检查顶层命令必须有 `read`、`task`、`automation`、`status`、`reload`,而且不允许冒出 `explore` 或 `session` 这种表面。 +- 架构守卫在 [architecture.py](/home/hongv/workspace/browser-cli/scripts/guards/architecture.py)。它限制包之间的依赖方向,防止 CLI 直接摸浏览器底层,防止 driver 偷偷接受原始 `ref`。 + +这就像一栋楼先浇了承重墙。房间可以改,承重墙不能乱拆。 + +--- + +## daemon 是整套系统的脊梁 + +daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一条命令就从零启动一次。 + +如果没有 daemon,你执行 `open`、`click`、`html` 会变成三次独立进程。前一次打开的页,后一次根本不认识。daemon 让浏览器实例活着,让标签页、Cookie、localStorage、network capture、console capture 都延续下去。 + +这一层的关键文件有三组。 + +- 传输和拉起: [client.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/client.py) +- 命令分发: [app.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/app.py) +- 浏览器状态机: [browser_service.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/browser_service.py) + +`browser-cli status` 也很重要。它不是附属命令,而是“先看体温计”。它读 daemon 的运行信息,再问 daemon 当前活得怎么样,最后生成一份人能看懂的状态报告,入口在 [status.py](/home/hongv/workspace/browser-cli/src/browser_cli/commands/status.py)。状态分类逻辑集中在 [runtime_presentation.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/runtime_presentation.py)。 + +这意味着,状态语义不分散。CLI、扩展弹窗、后台都看同一份 runtime truth,而不是各自瞎猜。 + +--- + +## driver 层解决的不是“兼容多个后端”,而是“对外只暴露一个浏览器” + +项目底下有两套真实后端。 + +- Playwright driver,在 [playwright_driver.py](/home/hongv/workspace/browser-cli/src/browser_cli/drivers/playwright_driver.py) +- Chrome extension driver,在 [extension_driver.py](/home/hongv/workspace/browser-cli/src/browser_cli/drivers/extension_driver.py) + +它们都实现同一份抽象接口 [base.py](/home/hongv/workspace/browser-cli/src/browser_cli/drivers/base.py)。这个接口很长,因为项目把“浏览器能做的事”完整列了出来:开标签页、截图、抓网络、填表单、验证文本、录 trace、录视频。 + +这层的关键想法是:外面只有一个 `browser-cli`,里面可以换司机。 + +默认司机是 Playwright。它最稳,项目自己能管。底层浏览器服务在 [browser/service.py](/home/hongv/workspace/browser-cli/src/browser_cli/browser/service.py),它会用专用 Chrome 数据目录启动持久上下文。这个数据目录的发现和锁文件检测在 [discovery.py](/home/hongv/workspace/browser-cli/src/browser_cli/profiles/discovery.py)。 + +如果浏览器扩展连接上了,而且能力齐全,系统会优先切到 extension driver。扩展端入口在 [background.js](/home/hongv/workspace/browser-cli/browser-cli-extension/src/background.js),它通过 WebSocket 连回 daemon,协议定义在 [protocol.py](/home/hongv/workspace/browser-cli/src/browser_cli/extension/protocol.py) 和对应的 JS 文件里。 + +这里最值得你注意的,不是“双后端”,而是“安全切换”。项目不在命令执行一半时切 driver。它等到 safe point,也就是命令边界,再切。测试在 [test_daemon_browser_service.py](/home/hongv/workspace/browser-cli/tests/unit/test_daemon_browser_service.py) 里把这件事钉死了。 + +为什么?因为半路换司机,状态就会乱。标签页可能要重建,snapshot 一定会失效。所以系统把这种切换明确标成 `state_reset`。它不装作“什么都没发生”。 + +这很诚实,也很适合 agent。 + +--- + +## semantic ref 是这套系统最像“给 agent 设计”的地方 + +新手最容易问:为什么不直接用 CSS selector? + +因为 agent 常常先“看”,再“点”。它看到的是页面语义,不是开发者的类名。页面今天叫 `.btn-primary`,明天叫 `.btn-main`,agent 不该跟着碎掉。 + +这个项目的做法是: + +- 先抓一份 semantic snapshot。 +- snapshot 里给每个元素分配短 ref,比如 `@abcd1234`。 +- 这个 ref 不直接存 DOM 节点指针,而是存角色、名字、文本、层级、frame 路径等信息。 +- 真正执行 `click @abcd1234` 时,daemon 再把 ref 还原成 `LocatorSpec`,然后交给 driver。 + +生成和恢复都在 `refs` 包里: + +- 解析与重建在 [resolver.py](/home/hongv/workspace/browser-cli/src/browser_cli/refs/resolver.py) +- 模型在 `refs/models.py` +- 最新 snapshot 注册表在 `refs/registry.py` + +这里有一个很重要的边界:driver 不接受原始 ref。它只接受 daemon 算好的 `LocatorSpec`。守卫脚本也盯着这点。这样 Playwright 和 extension 才不会各自偷偷发明一套 ref 语义。 + +你可以把它想成这样:agent 先画一张地图,再拿地图编号去找门牌。地图由 daemon 统一画,司机只负责按门牌开车。 + +--- + +## `X_AGENT_ID` 让多个 agent 共用浏览器,又互相少踩脚 + +这部分很像“多人共用一个办公室,但每个人只看见自己的文件夹”。 + +`X_AGENT_ID` 在 [agent_scope/__init__.py](/home/hongv/workspace/browser-cli/src/browser_cli/agent_scope/__init__.py) 里解析。tab 的归属、活跃页、忙碌状态都由 [tabs/registry.py](/home/hongv/workspace/browser-cli/src/browser_cli/tabs/registry.py) 管。 + +结果是这样: + +- 多个 agent 可以共用同一个浏览器实例和存储状态。 +- 但每个 agent 默认只看见自己开的标签页。 +- 如果某个标签页正在被另一个请求操作,系统会报 busy,而不是两边一起抢鼠标。 + +这和传统“每个测试开一个浏览器”不同。它更像多用户操作系统,而不是一次性脚本。 + +--- + +## `task` 把临时命令串成一段可复用逻辑 + +当你从“手打一串命令”走到“我要反复做这件事”,项目就让你进入 `task` 层。 + +一个任务目录最少有两个文件: + +- `task.py`,写动作逻辑 +- `task.meta.json`,写结构化知识 + +任务入口装载和校验在 [entrypoint.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/entrypoint.py),元数据模型在 [models.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/models.py)。 + +先看一个很短的真实例子,[interactive_reveal_capture/task.py](/home/hongv/workspace/browser-cli/tasks/interactive_reveal_capture/task.py): + +- 打开 URL +- 抓 snapshot +- 找到名字叫 `Reveal Message` 的按钮 +- 点击 +- 等待文字 `Revealed` +- 导出 HTML 和 snapshot artifact + +这段逻辑读起来已经很像人在说话了。原因在 [flow.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/flow.py)。`Flow` 把底层命令包成更顺手的方法:`open()`、`snapshot()`、`click()`、`wait_text()`、`write_text_artifact()`。 + +`task.meta.json` 也很值得看,比如 [interactive_reveal_capture/task.meta.json](/home/hongv/workspace/browser-cli/tasks/interactive_reveal_capture/task.meta.json)。它不存聊天记录,而存稳定知识:输入、目标、成功路径、恢复提示、关键 ref、已知等待点。换句话说,它像任务说明书,不像流水账。 + +--- + +## `automation` 再往前走一步:把任务冻结成版本 + +`task` 还是活源码。你改 `task.py`,下次运行就变了。 + +`automation` 不一样。它强调“发布时冻结”。 + +发布逻辑在 [publisher.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/publisher.py)。它会把: + +- `task.py` +- `task.meta.json` +- `automation.toml` + +一起复制到 `~/.browser-cli/automations//versions//` 下面。这个版本以后不再改。你再发布一次,就生成新版本。 + +这里的三种文件分工很清楚。 + +- `task.py` 写“怎么做”。 +- `task.meta.json` 写“这件事是什么、怎么恢复、关键点在哪”。 +- `automation.toml` 写“什么时候跑、输出放哪、超时多久、钩子怎么配”。 + +清单结构在 [models.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/models.py),加载器在 [loader.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/loader.py)。 + +这层再往上,是一个常驻本地服务。CLI 入口在 [commands/automation.py](/home/hongv/workspace/browser-cli/src/browser_cli/commands/automation.py),HTTP API 在 [api/server.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/api/server.py),服务拉起逻辑在 [service/client.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/service/client.py)。 + +所以,`task` 像工作台上的草稿本,`automation` 像盖章入库的版本件。 + +--- + +## 扩展和弹窗不是第二套大脑,它们只是观察窗 + +浏览器扩展会做两件事。 + +- 它把真实 Chrome 的能力通过 WebSocket 接到 daemon。 +- 它提供一个 popup,让人看当前 runtime 是健康、降级、恢复中还是坏掉。 + +但 popup 不自己定义状态语义。它读 daemon 给出的 presentation。你能在 [popup_view.js](/home/hongv/workspace/browser-cli/browser-cli-extension/src/popup_view.js) 和 [runtime_presentation.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/runtime_presentation.py) 里看到这条线。 + +这点很重要。否则 UI 一套说法,CLI 一套说法,agent `meta` 再一套说法,系统很快就会自相矛盾。 + +--- + +## 如果你是第一次接触浏览器自动化,最该先抓住这五个概念 + +1. 页面不是静态文件。很多文字要等 JavaScript 跑完,`read` 就是在“等它真的长出来”。 +2. 浏览器自动化不只是“打开网址”。还包括标签页状态、等待时机、网络记录、截图、下载、表单、验证。 +3. 这个项目把“浏览器动作”变成命令,把“命令串”变成任务,把“任务发布版”变成自动化。 +4. semantic ref 不是 CSS 选择器的花哨替代品。它是给 agent 用的语义地图。 +5. dual driver 不是功能炫技。它是现实妥协:默认先稳,再在安全时机切到更接近真人 Chrome 的后端。 + +--- + +## 你可以按这个顺序理解整个仓库 + +1. 先读 [README.md](/home/hongv/workspace/browser-cli/README.md),建立产品感。 +2. 再读 [main.py](/home/hongv/workspace/browser-cli/src/browser_cli/cli/main.py) 和 [cli_specs.py](/home/hongv/workspace/browser-cli/src/browser_cli/actions/cli_specs.py),知道对外有哪些命令。 +3. 再读 [app.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/app.py) 和 [browser_service.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/browser_service.py),知道命令怎样落到浏览器。 +4. 然后读 [base.py](/home/hongv/workspace/browser-cli/src/browser_cli/drivers/base.py) 和两个 driver,理解“一个契约,两个后端”。 +5. 再读 [resolver.py](/home/hongv/workspace/browser-cli/src/browser_cli/refs/resolver.py),理解 ref 为什么能给 agent 用。 +6. 最后读 [flow.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/flow.py) 和示例任务,看看作者希望人怎样写自动化。 +7. 如果你想理解“为什么这些边界不能乱动”,看 [product_contracts.py](/home/hongv/workspace/browser-cli/scripts/guards/product_contracts.py) 和 [architecture.py](/home/hongv/workspace/browser-cli/scripts/guards/architecture.py)。 + +--- + +## 一句话收尾 + +这个项目的核心,不是“控制浏览器”四个字,而是“把浏览器控制整理成一套适合 agent 长期使用的公共基础设施”。它把命令面、状态面、驱动面、语义定位、任务复用、版本发布,都钉在了明确的位置上。 + +如果你愿意,我下一步可以继续做两件事里的任意一件: + +1. 用一张“分层架构图 + 一条命令时序图”把这套系统画出来。 +2. 按新手路线,带你从 `browser-cli read` 开始,一步步读懂 `open -> snapshot -> click -> task -> automation publish`。 diff --git a/docs/browser-cli-usage-guide-zh.md b/docs/browser-cli-usage-guide-zh.md new file mode 100644 index 0000000..d9886c9 --- /dev/null +++ b/docs/browser-cli-usage-guide-zh.md @@ -0,0 +1,403 @@ +# Browser CLI 使用导引 + +## 先把它当成什么 + +先把 Browser CLI 当成一个会替你操作浏览器的命令行助手。你不用先学 Playwright,也不用先写一百行脚本。你先打一条命令,它替你开页面、等页面渲染、点按钮、读结果。 + +这句话里有两个关键词。第一个词是“命令行”。你和它说话,主要靠 `browser-cli ...`。第二个词是“浏览器”。它真的会启动一个浏览器环境,而不是只抓原始 HTML。很多站点把内容写在 JavaScript 里,等页面跑起来才把文字塞进 DOM。这个项目正是来处理这件事。 + +如果你第一次接触浏览器自动化,先记住一句最实用的话:它不是在“下载网页”,它是在“使用网页”。 + +--- + +## 第一次上手,先跑通最短路径 + +先不要碰任务、自动化、扩展。你先证明这台机器能让 Browser CLI 成功打开一个页面。这一步最短,也最能排除环境问题。 + +项目建议的第一天路径很简单: + +1. 安装 Browser CLI。 +2. 跑 `browser-cli doctor`。 +3. 跑 `browser-cli paths`。 +4. 跑一次 `browser-cli read https://example.com`。 + +如果你用 `uv` 安装,命令是: + +```bash +uv tool install browser-control-and-automation-cli +browser-cli doctor +browser-cli paths +browser-cli read https://example.com +``` + +这里有一个容易混淆的小地方。发布到包仓库的名字叫 `browser-control-and-automation-cli`,真正安装后的命令仍然叫 `browser-cli`。前者像盒子上的商品名,后者像你每天按下去的开关。 + +`doctor` 会先查机器。它会看 Python 环境,也会看 Chrome 是否存在。`paths` 会告诉你 Browser CLI 把自己的运行目录、日志目录、自动化目录放在哪里。等你以后查问题,这条命令会很有用,因为它直接把路径亮给你看,而不是让你去猜。 + +--- + +## 第一条真正有用的命令是 `read` + +`read` 最适合用来理解这个项目,因为它做一件事,而且只做一件事:打开一个页面,等它渲染完成,然后把结果吐出来。 + +你可以先这样试: + +```bash +browser-cli read https://example.com +``` + +这条命令默认输出渲染后的 HTML。你得到的不是服务器刚返回的那一坨原始文本,而是浏览器已经执行脚本、已经把页面拼好之后的结果。 + +如果你想看另一种结果,用 `--snapshot`: + +```bash +browser-cli read https://example.com --snapshot +``` + +这里的 snapshot 不等于截图。它更像一份面向自动化的“页面结构摘要”。它会把按钮、标题、链接、输入框这些语义元素整理成一棵树。你把它读成“这页上有什么”,而不是“源码长什么样”。 + +如果页面靠下拉才会继续加载内容,再加 `--scroll-bottom`: + +```bash +browser-cli read https://example.com --scroll-bottom +``` + +这时 Browser CLI 会滚到页面底部,再停一停,再读。这个停顿很关键。很多页面在你滚到末尾之后才发第二批请求,你滚得太快,抓到的还是半页东西。 + +所以,`read` 像一支探针。它先帮你确认两件事: + +- 这个站点能不能被当前浏览器环境正常打开。 +- 你要的内容是应该读 HTML,还是应该读 snapshot。 + +--- + +## 当你想“操作页面”,就切到交互命令 + +`read` 只读不动手。你要点按钮、填表单、切标签页,就要用 daemon-backed commands。 + +最简单的一组命令是: + +```bash +browser-cli open https://example.com +browser-cli snapshot +browser-cli click @8d4b03a9 +browser-cli html +``` + +这组命令背后的动作很像一个人: + +- `open` 打开页面。 +- `snapshot` 看一眼页面,把可操作元素列出来。 +- `click @8d4b03a9` 去点刚才看到的那个元素。 +- `html` 再读一遍页面,看点击之后发生了什么。 + +你会立刻注意到一个陌生东西:`@8d4b03a9` 这样的短串。这个项目叫它 `ref`。你可以把它当成页面元素的临时编号。你先让系统给你一张地图,再拿地图上的编号去操作元素。 + +这比直接写 CSS selector 更适合入门,也更适合 agent。因为你面对的不是源码作者的类名,而是“按钮”“链接”“输入框”这些人能看懂的对象。 + +一个常见流程长这样: + +```bash +browser-cli open https://example.com/login +browser-cli snapshot +browser-cli fill @user_ref "alice" +browser-cli fill @password_ref "secret" +browser-cli click @submit_ref +browser-cli verify-text "Welcome back" +``` + +这里的节奏很重要。你先 `snapshot`,再 `fill` 或 `click`。你不要闭着眼去点。浏览器自动化里最常见的错误不是“代码不会运行”,而是“你点的元素已经变了,或者你根本没看到它”。 + +--- + +## `snapshot` 是你最该养成的习惯 + +如果你以后只记住一条使用建议,我会建议你记这条:动手之前,先 `snapshot`。 + +原因很简单。网页会变。按钮位置会变,文字会变,组件会重渲染,DOM 会抖一下再稳定。你如果直接拿旧编号去点,系统很可能会告诉你 `REF_NOT_FOUND` 或 `STALE_SNAPSHOT`。这不是它脾气坏,而是它在提醒你:地图过期了。 + +所以一个更稳的操作节奏是: + +```bash +browser-cli open https://example.com +browser-cli snapshot +browser-cli click @button_ref +browser-cli snapshot +browser-cli fill @input_ref "hello" +``` + +旧地图引出新地图。第一次点击改变了页面,第二次 snapshot 就刷新了你手里的坐标。 + +这件事听起来琐碎,做起来却省时间。你少和“为什么明明刚才能点现在不能点”打架,就会更快走到任务完成那一步。 + +--- + +## 你可以把 daemon 理解成“常驻的浏览器管家” + +交互命令之所以能连着用,是因为背后有一个 daemon。你不用手动启动它。你第一次执行 `open`、`snapshot`、`click` 这种命令时,Browser CLI 会按需拉起它。 + +这意味着什么?意味着浏览器状态会活着。 + +比如你先登录一个站点,再切到另一个命令读取页面。Cookie、标签页、当前页面、网络监听,这些东西不会在每条命令之间蒸发。你不是每次都拿到一个刚出生的浏览器,而是在和同一个会话继续说话。 + +你可以用这些命令观察它: + +```bash +browser-cli status +browser-cli tabs +browser-cli reload +``` + +`status` 用来看体温。它会告诉你 daemon 是否活着、当前 driver 是什么、扩展是否连上、工作区标签页状态怎样。`tabs` 用来看你手里有哪些页。`reload` 则更像“重启运行时”,不是页面里的普通刷新。页面里的刷新命令叫 `page-reload`,这是两件不同的事。 + +这个区分很值钱。一个刷新页面,一个重置浏览器运行时。你把扳手和重启按钮分开,排错时就不容易误伤。 + +--- + +## 当 `read` 不够时,就开始写 `task` + +你会很快遇到这样的场景:我不是只想点一次按钮,我想把这串动作反复执行。比如每天打开某个站点,点开一条详情,再把 HTML 存到文件里。命令一条条手打当然能做,但第二次你就会嫌烦。 + +这时就进入 `task`。 + +一个最小任务目录长这样: + +```text +my_task/ + task.py + task.meta.json + automation.toml +``` + +你先不用被三个文件吓到。先抓分工。 + +- `task.py` 写动作。 +- `task.meta.json` 写任务说明。 +- `automation.toml` 写发布和调度配置。 + +对初学者来说,先盯住 `task.py` 和 `task.meta.json` 就够了。 + +运行一个任务之前,你先验证: + +```bash +browser-cli task validate my_task +``` + +验证通过,再运行: + +```bash +browser-cli task run my_task --set url=https://example.com +``` + +这里的 `--set` 很直白。它把一个输入值塞给任务。你把它看成命令行版的函数参数就行。 + +--- + +## `task.py` 写的不是浏览器底层 API,而是一段动作剧本 + +项目给你一个 `Flow` 对象。你在 `task.py` 里主要和它打交道。它把常用动作包成了更容易读写的方法。 + +一个很短的任务可以写成这样: + +```python +from browser_cli.task_runtime.flow import Flow + + +def run(flow: Flow, inputs: dict) -> dict: + flow.open(inputs["url"]) + snapshot = flow.snapshot() + ref = snapshot.find_ref(role="button", name="Reveal Message") + flow.click(ref) + flow.wait_text("Revealed", timeout=5) + return {"html": flow.html()} +``` + +这段代码读起来像说明书: + +- 打开页面。 +- 抓一份 snapshot。 +- 找到名字叫 `Reveal Message` 的按钮。 +- 点击。 +- 等待 `Revealed` 出现。 +- 返回 HTML。 + +这就是项目刻意提供 `Flow` 的原因。它不逼你一开始就去接触低层浏览器对象。它先给你一套动词:`open`、`snapshot`、`click`、`wait_text`、`html`。这些词组合起来,已经能做很多事。 + +仓库里有几个很适合照着读的例子: + +- [interactive_reveal_capture/task.py](/home/hongv/workspace/browser-cli/tasks/interactive_reveal_capture/task.py) +- [lazy_scroll_capture/task.py](/home/hongv/workspace/browser-cli/tasks/lazy_scroll_capture/task.py) +- [douyin_video_download/task.py](/home/hongv/workspace/browser-cli/tasks/douyin_video_download/task.py) + +第一个例子教你点按钮。第二个例子教你滚动到底。第三个例子开始接真实站点,也开始处理 cookies、接口请求和下载文件。 + +--- + +## `task.meta.json` 不写代码,它写“人脑里的备忘录” + +很多人第一次看到 `task.meta.json`,会以为它是多余文件。其实它恰好补上了代码不适合写的那部分知识。 + +代码适合写动作。元数据适合写意图、输入、恢复路径、关键等待点、稳定的角色名称。你可以把它当成一页贴在任务旁边的纸条。纸条上写着: + +- 这个任务要什么输入。 +- 它成功时应该得到什么。 +- 哪一步最容易卡住。 +- 哪个按钮最关键。 +- 页面变了之后应该先重抓 snapshot。 + +这种信息很适合给人看,也适合给 agent 看。它不负责执行,但它能帮你少走弯路。 + +--- + +## 当任务准备长期运行时,再进入 `automation` + +`task` 像工作台上的源文件。你随时改,随时试,下一次运行就看到新行为。 + +`automation` 则像你把当前任务拍成一个冻结版本,放进档案柜里。你发布一次,系统就把当时的 `task.py`、`task.meta.json`、`automation.toml` 复制到 Browser CLI 自己的自动化目录里,形成一个不可变版本。 + +发布命令很直接: + +```bash +browser-cli automation publish my_task +``` + +发布后你通常会继续用这几条命令: + +```bash +browser-cli automation list +browser-cli automation inspect +browser-cli automation status +browser-cli automation ui +``` + +这里的使用心法也很简单。 + +- 你想改任务逻辑,回去改 `task.py`。 +- 你想看当前已经发布了什么,跑 `automation list`。 +- 你想看某个自动化现在用的是什么配置,跑 `automation inspect`。 +- 你想用浏览器界面点一点,跑 `automation ui`。 + +这套设计把“编辑中的东西”和“已经发布的东西”分开了。你不会一边调试,一边偷偷改坏线上用的版本。 + +--- + +## `automation.toml` 解决的是“怎么运行”,不是“怎么做动作” + +这是一个很值得早点抓住的分工。 + +如果你想让任务每小时跑一次,或者想把输出放到某个目录,或者想设置超时、钩子、重试,这些配置应该写进 `automation.toml`。如果你想点击按钮、抓页面、下载文件,这些动作应该留在 `task.py`。 + +这条边界能帮你避免一种常见混乱:把一份任务拆成两套逻辑,一半在 Python 里,一半在 TOML 里。项目故意不鼓励这种写法。它要你把动作留在代码,把运行策略留在配置。 + +你按这个习惯写,后面看的人会轻松得多。因为他一眼就知道:要改步骤,开 `task.py`;要改调度,开 `automation.toml`。 + +--- + +## 如果你需要更像真人 Chrome 的行为,再开 extension mode + +项目默认走 managed profile mode。对多数人来说,这条路最快,也最稳。你先把基本流程跑通,再考虑浏览器扩展。 + +什么时候值得折腾 extension mode?典型场景有两个。 + +- 你确实要靠真实 Chrome 的行为来复现问题。 +- 你遇到站点对普通自动化环境更敏感,想尽量贴近真实用户环境。 + +扩展接入方式在仓库里已经写得很直白: + +1. 打开 `chrome://extensions` +2. 开启开发者模式 +3. 点击 `Load unpacked` +4. 选择 `browser-cli-extension/` + +接上之后,再跑: + +```bash +browser-cli status +``` + +你要看的是 extension 是否 connected,capability 是否 complete。不要只看“扩展图标亮了没亮”。真正决定 Browser CLI 是否会切过去的是协议和能力,不是图标的心情。 + +--- + +## 多 agent 使用时,先理解 `X_AGENT_ID` + +如果你一个人手动用,通常不用管 `X_AGENT_ID`。如果你让多个 agent 共用 Browser CLI,这个环境变量就很关键。 + +你可以这样试: + +```bash +X_AGENT_ID=agent-a browser-cli open https://example.com +X_AGENT_ID=agent-a browser-cli tabs + +X_AGENT_ID=agent-b browser-cli open https://example.org +X_AGENT_ID=agent-b browser-cli tabs +``` + +这里最有意思的地方不是“能开两个标签页”,而是“每个 agent 只看见自己的页”。这让多个 agent 可以共用一个浏览器运行时,却不至于互相把活动标签页抢来抢去。 + +这对自动化特别重要。你不希望 agent A 正在填表,agent B 突然切走同一个活动页。`X_AGENT_ID` 就是在做这层隔离。 + +--- + +## 你可以按这个顺序真正学会使用它 + +如果你想把学习路线压成一条直线,我建议你按下面这七步走。 + +1. 先安装,跑通 `doctor`、`paths`、`read`。 +2. 再学 `open`、`snapshot`、`click`、`html` 这四个交互命令。 +3. 然后学会在每次页面变化后重新 `snapshot`。 +4. 接着学 `verify-text`、`wait`、`wait-network`,把“点一下试试”变成“点一下并确认结果”。 +5. 再写一个最小 `task.py`,让一串动作变成一次任务运行。 +6. 然后补上 `task.meta.json`,把任务知识写清楚。 +7. 最后再做 `automation publish`,把它变成可发布、可追踪的自动化版本。 + +这条路线像爬楼梯。你先学把门推开,再学在屋里走路。你不要站在楼下就讨论吊顶。 + +--- + +## 遇到问题时,先查这几样 + +浏览器自动化最容易卡住的地方,不在“语法”,而在“状态”。所以排错也要围着状态走。 + +先看环境: + +```bash +browser-cli doctor +browser-cli paths +``` + +再看运行时: + +```bash +browser-cli status +browser-cli tabs +``` + +如果命令连续异常,先不要急着杀进程,先试: + +```bash +browser-cli reload +``` + +如果是元素点不到,先别怀疑人生,先重新抓: + +```bash +browser-cli snapshot +``` + +如果是任务行为不对,先单独跑: + +```bash +browser-cli task validate my_task +browser-cli task run my_task --set url=https://example.com +``` + +这几条命令像你工具箱里最常用的扳手。很多问题,拧这几下就知道症结在哪。 + +--- + +## 最后用一句话把它钉住 + +Browser CLI 最适合这样使用:你先用 `read` 探路,再用交互命令摸清页面,再把动作写进 `task.py`,最后把稳定版本发布成 `automation`。 + +这样做的好处很具体。你先拿到结果,再整理流程,最后沉淀版本。你不是一上来就搭一套大而全的自动化系统,而是先让浏览器替你完成一件小事,再把这件小事磨成工具。 diff --git a/docs/install-skills.md b/docs/install-skills.md new file mode 100644 index 0000000..ee2e776 --- /dev/null +++ b/docs/install-skills.md @@ -0,0 +1,85 @@ +# Install Browser CLI Skills + +`browser-cli install-skills` copies the packaged Browser CLI skills from the +installed distribution into a skills directory on your machine. + +Use it when you want an agent runtime to discover the Browser CLI skills +without pointing that runtime at this repository checkout. + +## What the command installs + +Browser CLI currently ships exactly three packaged skills: + +- `browser-cli-converge` +- `browser-cli-delivery` +- `browser-cli-explore` + +Those skills live inside the installed wheel. The command does not scan your +repository checkout for loose files. It copies the packaged skill directories +that were published with the installed Browser CLI version. + +## Default install path + +Run the command with no extra flags: + +```bash +browser-cli install-skills +``` + +Browser CLI installs the packaged skills into: + +```text +~/.agents/skills +``` + +After the command finishes, you should see three directories under that target, +one for each packaged skill. + +## Preview before writing files + +Use `--dry-run` when you want to check what Browser CLI would install without +modifying the target directory: + +```bash +browser-cli install-skills --dry-run +``` + +That mode is useful when you are checking paths on a new machine or when you +want to confirm whether Browser CLI will install new directories or update +existing ones. + +## Choose a different target + +Use `--target` when your agent runtime reads skills from a different root: + +```bash +browser-cli install-skills --target ~/.codex/skills +``` + +Browser CLI treats the `--target` path as the skills root. It creates the +directory if needed, then writes one subdirectory per packaged skill. + +## What happens on rerun + +You can rerun `install-skills` safely. + +If the target already contains one of the packaged Browser CLI skill +directories, Browser CLI replaces that directory with the packaged version from +the installed distribution. This lets you refresh the installed skills after +you upgrade Browser CLI. + +## A typical flow + +If you installed Browser CLI with `uv`, a common sequence looks like this: + +```bash +uv tool install browser-control-and-automation-cli +browser-cli doctor +browser-cli install-skills --dry-run +browser-cli install-skills +browser-cli read https://example.com +``` + +That sequence checks the machine first, previews the skill install, installs +the packaged skills, then verifies that Browser CLI itself can open and read a +page. diff --git a/docs/installed-with-uv.md b/docs/installed-with-uv.md index 1bbe19e..ddd02ca 100644 --- a/docs/installed-with-uv.md +++ b/docs/installed-with-uv.md @@ -8,10 +8,11 @@ The first-day path should be: 1. Install Browser CLI with uv. 2. Run `browser-cli doctor`. 3. Run `browser-cli paths`. -4. Try `browser-cli read https://example.com`. -5. Create a task, then run `browser-cli task validate `. -6. Run `browser-cli task run `. -7. Publish with `browser-cli automation publish `. +4. Optionally run `browser-cli install-skills`. +5. Try `browser-cli read https://example.com`. +6. Create a task, then run `browser-cli task validate `. +7. Run `browser-cli task run `. +8. Publish with `browser-cli automation publish `. ## Recommended Starting Point @@ -45,6 +46,40 @@ with: uv tool dir --bin ``` +## Install Browser CLI Skills + +Use `install-skills` when you want Browser CLI to copy its packaged skills into +your agent skills directory. + +Install the packaged skills into the default target: + +```bash +browser-cli install-skills +``` + +By default, the command writes to `~/.agents/skills`. Preview the result +without writing files: + +```bash +browser-cli install-skills --dry-run +``` + +Choose a different destination root with `--target`: + +```bash +browser-cli install-skills --target ~/.codex/skills +``` + +Browser CLI currently installs exactly three packaged skills: + +- `browser-cli-converge` +- `browser-cli-delivery` +- `browser-cli-explore` + +If the target already contains those skill directories, Browser CLI replaces +them with the packaged versions from the installed distribution. For a longer +walkthrough, see [`docs/install-skills.md`](docs/install-skills.md). + ## First Read Use `read` to verify that Browser CLI can open a page and return output: From 9e6350c7afae79a55b2ae46aab142a5ff97dc0c1 Mon Sep 17 00:00:00 2001 From: xhwSkhizein Date: Wed, 15 Apr 2026 10:53:22 +0800 Subject: [PATCH 2/2] docs: replace absolute paths in Chinese guides --- docs/browser-cli-project-guide-zh.md | 80 ++++++++++++++-------------- docs/browser-cli-usage-guide-zh.md | 6 +-- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docs/browser-cli-project-guide-zh.md b/docs/browser-cli-project-guide-zh.md index e42aec2..dad07ea 100644 --- a/docs/browser-cli-project-guide-zh.md +++ b/docs/browser-cli-project-guide-zh.md @@ -10,7 +10,7 @@ 2. daemon-backed actions 负责“持续控制浏览器”。 3. `task` 和 `automation` 负责“把一串操作固化成可复用任务”。 -这三件事连起来,就是一个给 AI agent 用的浏览器工作台。入口在 [main.py](/home/hongv/workspace/browser-cli/src/browser_cli/cli/main.py),命令目录很小,后面的系统很厚。 +这三件事连起来,就是一个给 AI agent 用的浏览器工作台。入口在 [main.py](../src/browser_cli/cli/main.py),命令目录很小,后面的系统很厚。 --- @@ -26,13 +26,13 @@ browser-cli read https://example.com --scroll-bottom 这时项目走的是“短路径”: -- CLI 收到命令,在 [read.py](/home/hongv/workspace/browser-cli/src/browser_cli/commands/read.py) 里把 URL 规范化。 -- 它调用任务运行时客户端 [client.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/client.py)。 -- 客户端走共享读取逻辑 [read.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/read.py)。 +- CLI 收到命令,在 [read.py](../src/browser_cli/commands/read.py) 里把 URL 规范化。 +- 它调用任务运行时客户端 [client.py](../src/browser_cli/task_runtime/client.py)。 +- 客户端走共享读取逻辑 [read.py](../src/browser_cli/task_runtime/read.py)。 - 这层把请求发给 daemon 的 `read-page`。 -- daemon 再让浏览器服务 [browser_service.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/browser_service.py) 新开一个临时页、等待渲染、必要时滚动、抓 HTML 或 snapshot,然后把这个临时页关掉。 +- daemon 再让浏览器服务 [browser_service.py](../src/browser_cli/daemon/browser_service.py) 新开一个临时页、等待渲染、必要时滚动、抓 HTML 或 snapshot,然后把这个临时页关掉。 -测试里专门盯着这件事:`read` 读完不能把临时标签页泄漏出来,已有标签页也不能被打乱。你能在 [test_task_runtime_read.py](/home/hongv/workspace/browser-cli/tests/integration/test_task_runtime_read.py) 里看到这个约束。 +测试里专门盯着这件事:`read` 读完不能把临时标签页泄漏出来,已有标签页也不能被打乱。你能在 [test_task_runtime_read.py](../tests/integration/test_task_runtime_read.py) 里看到这个约束。 第二件事,你想持续操作页面。比如: @@ -45,11 +45,11 @@ browser-cli fill @abcd1234 "hello" 这时项目走的是“长路径”: -- CLI 仍然很薄,[action.py](/home/hongv/workspace/browser-cli/src/browser_cli/commands/action.py) 只负责把命令变成请求。 -- action catalog 在 [cli_specs.py](/home/hongv/workspace/browser-cli/src/browser_cli/actions/cli_specs.py)。这里列了 60 多个动作,像 `open`、`snapshot`、`click`、`network-start`、`verify-text`。 -- daemon client 在 [client.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/client.py) 里保证守护进程存在,然后把请求通过 Unix socket 发过去。 -- daemon app 在 [app.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/app.py) 里分发动作。 -- 真正干活的是 daemon 自己持有的浏览器服务 [browser_service.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/browser_service.py)。 +- CLI 仍然很薄,[action.py](../src/browser_cli/commands/action.py) 只负责把命令变成请求。 +- action catalog 在 [cli_specs.py](../src/browser_cli/actions/cli_specs.py)。这里列了 60 多个动作,像 `open`、`snapshot`、`click`、`network-start`、`verify-text`。 +- daemon client 在 [client.py](../src/browser_cli/daemon/client.py) 里保证守护进程存在,然后把请求通过 Unix socket 发过去。 +- daemon app 在 [app.py](../src/browser_cli/daemon/app.py) 里分发动作。 +- 真正干活的是 daemon 自己持有的浏览器服务 [browser_service.py](../src/browser_cli/daemon/browser_service.py)。 所以,CLI 像前台。daemon 像总调度台。浏览器服务才像真正拿着鼠标和键盘的人。 @@ -61,8 +61,8 @@ browser-cli fill @abcd1234 "hello" 你能从两个地方看出这种执拗。 -- 产品契约守卫在 [product_contracts.py](/home/hongv/workspace/browser-cli/scripts/guards/product_contracts.py)。它会检查顶层命令必须有 `read`、`task`、`automation`、`status`、`reload`,而且不允许冒出 `explore` 或 `session` 这种表面。 -- 架构守卫在 [architecture.py](/home/hongv/workspace/browser-cli/scripts/guards/architecture.py)。它限制包之间的依赖方向,防止 CLI 直接摸浏览器底层,防止 driver 偷偷接受原始 `ref`。 +- 产品契约守卫在 [product_contracts.py](../scripts/guards/product_contracts.py)。它会检查顶层命令必须有 `read`、`task`、`automation`、`status`、`reload`,而且不允许冒出 `explore` 或 `session` 这种表面。 +- 架构守卫在 [architecture.py](../scripts/guards/architecture.py)。它限制包之间的依赖方向,防止 CLI 直接摸浏览器底层,防止 driver 偷偷接受原始 `ref`。 这就像一栋楼先浇了承重墙。房间可以改,承重墙不能乱拆。 @@ -76,11 +76,11 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 这一层的关键文件有三组。 -- 传输和拉起: [client.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/client.py) -- 命令分发: [app.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/app.py) -- 浏览器状态机: [browser_service.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/browser_service.py) +- 传输和拉起: [client.py](../src/browser_cli/daemon/client.py) +- 命令分发: [app.py](../src/browser_cli/daemon/app.py) +- 浏览器状态机: [browser_service.py](../src/browser_cli/daemon/browser_service.py) -`browser-cli status` 也很重要。它不是附属命令,而是“先看体温计”。它读 daemon 的运行信息,再问 daemon 当前活得怎么样,最后生成一份人能看懂的状态报告,入口在 [status.py](/home/hongv/workspace/browser-cli/src/browser_cli/commands/status.py)。状态分类逻辑集中在 [runtime_presentation.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/runtime_presentation.py)。 +`browser-cli status` 也很重要。它不是附属命令,而是“先看体温计”。它读 daemon 的运行信息,再问 daemon 当前活得怎么样,最后生成一份人能看懂的状态报告,入口在 [status.py](../src/browser_cli/commands/status.py)。状态分类逻辑集中在 [runtime_presentation.py](../src/browser_cli/daemon/runtime_presentation.py)。 这意味着,状态语义不分散。CLI、扩展弹窗、后台都看同一份 runtime truth,而不是各自瞎猜。 @@ -90,18 +90,18 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 项目底下有两套真实后端。 -- Playwright driver,在 [playwright_driver.py](/home/hongv/workspace/browser-cli/src/browser_cli/drivers/playwright_driver.py) -- Chrome extension driver,在 [extension_driver.py](/home/hongv/workspace/browser-cli/src/browser_cli/drivers/extension_driver.py) +- Playwright driver,在 [playwright_driver.py](../src/browser_cli/drivers/playwright_driver.py) +- Chrome extension driver,在 [extension_driver.py](../src/browser_cli/drivers/extension_driver.py) -它们都实现同一份抽象接口 [base.py](/home/hongv/workspace/browser-cli/src/browser_cli/drivers/base.py)。这个接口很长,因为项目把“浏览器能做的事”完整列了出来:开标签页、截图、抓网络、填表单、验证文本、录 trace、录视频。 +它们都实现同一份抽象接口 [base.py](../src/browser_cli/drivers/base.py)。这个接口很长,因为项目把“浏览器能做的事”完整列了出来:开标签页、截图、抓网络、填表单、验证文本、录 trace、录视频。 这层的关键想法是:外面只有一个 `browser-cli`,里面可以换司机。 -默认司机是 Playwright。它最稳,项目自己能管。底层浏览器服务在 [browser/service.py](/home/hongv/workspace/browser-cli/src/browser_cli/browser/service.py),它会用专用 Chrome 数据目录启动持久上下文。这个数据目录的发现和锁文件检测在 [discovery.py](/home/hongv/workspace/browser-cli/src/browser_cli/profiles/discovery.py)。 +默认司机是 Playwright。它最稳,项目自己能管。底层浏览器服务在 [browser/service.py](../src/browser_cli/browser/service.py),它会用专用 Chrome 数据目录启动持久上下文。这个数据目录的发现和锁文件检测在 [discovery.py](../src/browser_cli/profiles/discovery.py)。 -如果浏览器扩展连接上了,而且能力齐全,系统会优先切到 extension driver。扩展端入口在 [background.js](/home/hongv/workspace/browser-cli/browser-cli-extension/src/background.js),它通过 WebSocket 连回 daemon,协议定义在 [protocol.py](/home/hongv/workspace/browser-cli/src/browser_cli/extension/protocol.py) 和对应的 JS 文件里。 +如果浏览器扩展连接上了,而且能力齐全,系统会优先切到 extension driver。扩展端入口在 [background.js](../browser-cli-extension/src/background.js),它通过 WebSocket 连回 daemon,协议定义在 [protocol.py](../src/browser_cli/extension/protocol.py) 和对应的 JS 文件里。 -这里最值得你注意的,不是“双后端”,而是“安全切换”。项目不在命令执行一半时切 driver。它等到 safe point,也就是命令边界,再切。测试在 [test_daemon_browser_service.py](/home/hongv/workspace/browser-cli/tests/unit/test_daemon_browser_service.py) 里把这件事钉死了。 +这里最值得你注意的,不是“双后端”,而是“安全切换”。项目不在命令执行一半时切 driver。它等到 safe point,也就是命令边界,再切。测试在 [test_daemon_browser_service.py](../tests/unit/test_daemon_browser_service.py) 里把这件事钉死了。 为什么?因为半路换司机,状态就会乱。标签页可能要重建,snapshot 一定会失效。所以系统把这种切换明确标成 `state_reset`。它不装作“什么都没发生”。 @@ -124,7 +124,7 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 生成和恢复都在 `refs` 包里: -- 解析与重建在 [resolver.py](/home/hongv/workspace/browser-cli/src/browser_cli/refs/resolver.py) +- 解析与重建在 [resolver.py](../src/browser_cli/refs/resolver.py) - 模型在 `refs/models.py` - 最新 snapshot 注册表在 `refs/registry.py` @@ -138,7 +138,7 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 这部分很像“多人共用一个办公室,但每个人只看见自己的文件夹”。 -`X_AGENT_ID` 在 [agent_scope/__init__.py](/home/hongv/workspace/browser-cli/src/browser_cli/agent_scope/__init__.py) 里解析。tab 的归属、活跃页、忙碌状态都由 [tabs/registry.py](/home/hongv/workspace/browser-cli/src/browser_cli/tabs/registry.py) 管。 +`X_AGENT_ID` 在 [agent_scope/__init__.py](../src/browser_cli/agent_scope/__init__.py) 里解析。tab 的归属、活跃页、忙碌状态都由 [tabs/registry.py](../src/browser_cli/tabs/registry.py) 管。 结果是这样: @@ -159,9 +159,9 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 - `task.py`,写动作逻辑 - `task.meta.json`,写结构化知识 -任务入口装载和校验在 [entrypoint.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/entrypoint.py),元数据模型在 [models.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/models.py)。 +任务入口装载和校验在 [entrypoint.py](../src/browser_cli/task_runtime/entrypoint.py),元数据模型在 [models.py](../src/browser_cli/task_runtime/models.py)。 -先看一个很短的真实例子,[interactive_reveal_capture/task.py](/home/hongv/workspace/browser-cli/tasks/interactive_reveal_capture/task.py): +先看一个很短的真实例子,[interactive_reveal_capture/task.py](../tasks/interactive_reveal_capture/task.py): - 打开 URL - 抓 snapshot @@ -170,9 +170,9 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 - 等待文字 `Revealed` - 导出 HTML 和 snapshot artifact -这段逻辑读起来已经很像人在说话了。原因在 [flow.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/flow.py)。`Flow` 把底层命令包成更顺手的方法:`open()`、`snapshot()`、`click()`、`wait_text()`、`write_text_artifact()`。 +这段逻辑读起来已经很像人在说话了。原因在 [flow.py](../src/browser_cli/task_runtime/flow.py)。`Flow` 把底层命令包成更顺手的方法:`open()`、`snapshot()`、`click()`、`wait_text()`、`write_text_artifact()`。 -`task.meta.json` 也很值得看,比如 [interactive_reveal_capture/task.meta.json](/home/hongv/workspace/browser-cli/tasks/interactive_reveal_capture/task.meta.json)。它不存聊天记录,而存稳定知识:输入、目标、成功路径、恢复提示、关键 ref、已知等待点。换句话说,它像任务说明书,不像流水账。 +`task.meta.json` 也很值得看,比如 [interactive_reveal_capture/task.meta.json](../tasks/interactive_reveal_capture/task.meta.json)。它不存聊天记录,而存稳定知识:输入、目标、成功路径、恢复提示、关键 ref、已知等待点。换句话说,它像任务说明书,不像流水账。 --- @@ -182,7 +182,7 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 `automation` 不一样。它强调“发布时冻结”。 -发布逻辑在 [publisher.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/publisher.py)。它会把: +发布逻辑在 [publisher.py](../src/browser_cli/automation/publisher.py)。它会把: - `task.py` - `task.meta.json` @@ -196,9 +196,9 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 - `task.meta.json` 写“这件事是什么、怎么恢复、关键点在哪”。 - `automation.toml` 写“什么时候跑、输出放哪、超时多久、钩子怎么配”。 -清单结构在 [models.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/models.py),加载器在 [loader.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/loader.py)。 +清单结构在 [models.py](../src/browser_cli/automation/models.py),加载器在 [loader.py](../src/browser_cli/automation/loader.py)。 -这层再往上,是一个常驻本地服务。CLI 入口在 [commands/automation.py](/home/hongv/workspace/browser-cli/src/browser_cli/commands/automation.py),HTTP API 在 [api/server.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/api/server.py),服务拉起逻辑在 [service/client.py](/home/hongv/workspace/browser-cli/src/browser_cli/automation/service/client.py)。 +这层再往上,是一个常驻本地服务。CLI 入口在 [commands/automation.py](../src/browser_cli/commands/automation.py),HTTP API 在 [api/server.py](../src/browser_cli/automation/api/server.py),服务拉起逻辑在 [service/client.py](../src/browser_cli/automation/service/client.py)。 所以,`task` 像工作台上的草稿本,`automation` 像盖章入库的版本件。 @@ -211,7 +211,7 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 - 它把真实 Chrome 的能力通过 WebSocket 接到 daemon。 - 它提供一个 popup,让人看当前 runtime 是健康、降级、恢复中还是坏掉。 -但 popup 不自己定义状态语义。它读 daemon 给出的 presentation。你能在 [popup_view.js](/home/hongv/workspace/browser-cli/browser-cli-extension/src/popup_view.js) 和 [runtime_presentation.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/runtime_presentation.py) 里看到这条线。 +但 popup 不自己定义状态语义。它读 daemon 给出的 presentation。你能在 [popup_view.js](../browser-cli-extension/src/popup_view.js) 和 [runtime_presentation.py](../src/browser_cli/daemon/runtime_presentation.py) 里看到这条线。 这点很重要。否则 UI 一套说法,CLI 一套说法,agent `meta` 再一套说法,系统很快就会自相矛盾。 @@ -229,13 +229,13 @@ daemon 解决一个很具体的问题:浏览器状态很贵,不能每敲一 ## 你可以按这个顺序理解整个仓库 -1. 先读 [README.md](/home/hongv/workspace/browser-cli/README.md),建立产品感。 -2. 再读 [main.py](/home/hongv/workspace/browser-cli/src/browser_cli/cli/main.py) 和 [cli_specs.py](/home/hongv/workspace/browser-cli/src/browser_cli/actions/cli_specs.py),知道对外有哪些命令。 -3. 再读 [app.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/app.py) 和 [browser_service.py](/home/hongv/workspace/browser-cli/src/browser_cli/daemon/browser_service.py),知道命令怎样落到浏览器。 -4. 然后读 [base.py](/home/hongv/workspace/browser-cli/src/browser_cli/drivers/base.py) 和两个 driver,理解“一个契约,两个后端”。 -5. 再读 [resolver.py](/home/hongv/workspace/browser-cli/src/browser_cli/refs/resolver.py),理解 ref 为什么能给 agent 用。 -6. 最后读 [flow.py](/home/hongv/workspace/browser-cli/src/browser_cli/task_runtime/flow.py) 和示例任务,看看作者希望人怎样写自动化。 -7. 如果你想理解“为什么这些边界不能乱动”,看 [product_contracts.py](/home/hongv/workspace/browser-cli/scripts/guards/product_contracts.py) 和 [architecture.py](/home/hongv/workspace/browser-cli/scripts/guards/architecture.py)。 +1. 先读 [README.md](../README.md),建立产品感。 +2. 再读 [main.py](../src/browser_cli/cli/main.py) 和 [cli_specs.py](../src/browser_cli/actions/cli_specs.py),知道对外有哪些命令。 +3. 再读 [app.py](../src/browser_cli/daemon/app.py) 和 [browser_service.py](../src/browser_cli/daemon/browser_service.py),知道命令怎样落到浏览器。 +4. 然后读 [base.py](../src/browser_cli/drivers/base.py) 和两个 driver,理解“一个契约,两个后端”。 +5. 再读 [resolver.py](../src/browser_cli/refs/resolver.py),理解 ref 为什么能给 agent 用。 +6. 最后读 [flow.py](../src/browser_cli/task_runtime/flow.py) 和示例任务,看看作者希望人怎样写自动化。 +7. 如果你想理解“为什么这些边界不能乱动”,看 [product_contracts.py](../scripts/guards/product_contracts.py) 和 [architecture.py](../scripts/guards/architecture.py)。 --- diff --git a/docs/browser-cli-usage-guide-zh.md b/docs/browser-cli-usage-guide-zh.md index d9886c9..8ed5837 100644 --- a/docs/browser-cli-usage-guide-zh.md +++ b/docs/browser-cli-usage-guide-zh.md @@ -225,9 +225,9 @@ def run(flow: Flow, inputs: dict) -> dict: 仓库里有几个很适合照着读的例子: -- [interactive_reveal_capture/task.py](/home/hongv/workspace/browser-cli/tasks/interactive_reveal_capture/task.py) -- [lazy_scroll_capture/task.py](/home/hongv/workspace/browser-cli/tasks/lazy_scroll_capture/task.py) -- [douyin_video_download/task.py](/home/hongv/workspace/browser-cli/tasks/douyin_video_download/task.py) +- [interactive_reveal_capture/task.py](../tasks/interactive_reveal_capture/task.py) +- [lazy_scroll_capture/task.py](../tasks/lazy_scroll_capture/task.py) +- [douyin_video_download/task.py](../tasks/douyin_video_download/task.py) 第一个例子教你点按钮。第二个例子教你滚动到底。第三个例子开始接真实站点,也开始处理 cookies、接口请求和下载文件。