ego (lite) is just a browser, ego is your personal agent across devices.
Join waitlist
中文

ego-browser

面向 AI Agent 的浏览器自动化运行时,连接 ego lite 的真实 Chromium 会话。

llms.txt

ego-browser 是 ego lite 为 AI Agent 提供的浏览器自动化运行时。它通过 Chrome DevTools Protocol 连接到 ego lite 的真实 Chromium 会话,以 Node.js heredoc 脚本为入口:Agent 在一次 stdin 投递中写完整段 JS 流程,所有 helper 都已预注入到脚本作用域,浏览器状态在 Space 中持续保留。

ego-browser 不是给人手动操作浏览器用的,也不是 Playwright / Puppeteer 的替代品,目标读者是 LLM Agent。

适合谁用

  • 需要自动操作浏览器的 AI 编程 Agent:Claude Code、Codex、Cursor、自定义 SDK Agent。
  • 构建垂直 Agent 的团队:自动操作飞书、Google Docs、Salesforce 等后台系统。
  • 反复执行固定网页流程:登录、填表、导出、检索、读取表格。
  • 曾经把完整 DOM 或 HTML 塞进 LLM、被 token 限制卡住的人。

安装

随 ego lite 一起安装,参见 快速开始。安装后即可在任意目录用 ego-browser 命令调用。

也可以单独安装 Skill:

npx skills add github:CitroLabs/ego-lite/skills/ego-browser

核心循环

Agent 操作网页的典型节奏,所有命令都在一次 heredoc 内完成:

ego-browser nodejs <<'EOF'
const task = await useOrCreateTaskSpace('search github issues')

await openOrReuseTab('https://github.com/issues', { wait: true, timeout: 20 })

cliLog(await snapshotText())

EOF
  1. 复用或创建 Task Space(每次 heredoc 都要声明,详见 Space)。
  2. 打开目标页面。
  3. 读取快照(snapshotText())拿到带 [ref=N, loc=..., url=...] 的语义树。
  4. 基于 @N ref 或 CSS selector 执行动作。
  5. cliLog(...) 输出最终结果。

heredoc 体内是 Node.js 进程;js(...) 内才是浏览器页面上下文。两者不要混用。

Helper 参考

所有 helper 在脚本作用域内以 camelCase 直接可用,无需 import

Task Space

await listTaskSpaces()
const task = await useOrCreateTaskSpace('describe task')   // 复用或创建
await completeTaskSpace(task.name)                         // 任务完成、保留 tab
await closeTaskSpace(task.name)                            // 不再需要展示,关闭空间

name 用 3-6 词自然语言描述当前任务,不要用占位符。

导航与状态

await listTabs()
await openOrReuseTab(url, { wait: true, timeout: 20 })
await gotoAndWait(url, { timeout: 20, settle: 1 })
await newTab(url)
await switchTab(tabId)
await currentTab()
await pageInfo()
await ensureRealTab()        // 任务空间刚创建时可能无 tab

观察

await snapshotText()                              // 整页语义快照(默认)
await snapshotText({ scope: 'only_within_viewport' })
await captureScreenshot('result.png')
await drainEvents()                               // 消费导航 / 网络事件队列

鼠标与滚动

click / doubleClick / hover / dragMouse 接受统一的 target 格式(CSS 像素):

  • 'string':CSS selector 或 @ref,点击元素中心。
  • [x, y]{x, y}:viewport 坐标。
  • {selector, x, y}:以元素左上角为基准,叠加偏移。
  • options.label:3-6 词描述,传入后触发视觉高亮动画。
await click('@21', { label: '查看登录态' })
await click('button.primary', { label: '点击提交按钮' })
await click([420, 260])
await hover('@5', { label: '悬停查看菜单' })
await dragMouse([from, to], { label: '拖拽卡片' })

await scrollBy(900)
await scroll({ dy: 900 })
await scrollToBottomUntil(
  async () => await js(String.raw`document.querySelectorAll('article').length`) >= 20,
  { step: 900, wait: 1, maxSteps: 20 },
)

键盘与输入

await typeText('hello world')
await fillInput('@2', 'user@test.com')
await pressKey('Enter')
await dispatchKey({ ... })

文件与网络

await uploadFile('input[type="file"]', '/absolute/path/to/file.pdf')
await httpGet('https://api.example.com/data')   // 浏览器上下文发起的 GET

等待

await wait(1)                                    // 秒
await waitForLoad()
await waitForElement('@1')
await waitForNetworkIdle()

wait()timeout 单位是秒;只有以 Ms 结尾的参数才是毫秒。

浏览器执行

js(source) 本质是 Runtime.evaluate,接受字符串。不要像 Puppeteer 那样传函数加参数,会触发 warning 并被 .toString() 包裹,闭包变量与参数通道都会丢失。

多步逻辑封装成一个 IIFE 一次返回:

const data = await js(String.raw`(() => {
  const items = [...document.querySelectorAll('article')]
  return items.map(el => ({
    text: el.innerText,
    links: [...el.querySelectorAll('a')].map(a => a.href),
  }))
})()`)

await elementEval('@1', el => el.getBoundingClientRect())
await cdp('Page.captureScreenshot', { format: 'png' })

输出与自查

cliLog(value)                  // heredoc 中唯一的输出方式
cliLog(help('click'))          // 查看 helper 用法

推荐工作流

通常先从 snapshotText + ref / loc 入手,因为它保留语义结构,并显著降低坐标脆弱性:

  1. 复用或创建 Task Space。
  2. 打开或切换页面(openOrReuseTab / gotoAndWait)。
  3. snapshotText() 获取 [ref=N, loc=..., url=...] 语义树,ref 自动注册到 refMap。
  4. @N 配合 click / fillInput / elementEval 执行动作,或在 js(...) 内一次性完成 DOM 抽取。
  5. cliLog(...) 输出最终结果。

可组合的其他路径:

  • captureScreenshot + click([x, y]):视觉布局、canvas 类界面、虚拟列表、accessibility 不完整的页面。
  • js / elementEval / cdp:直接抽取 DOM、查看浏览器状态,或常规 helper 不够直接的场景。

优先把导航、观察、滚动、抽取、过滤、聚合、输出写在一段 ego-browser nodejs heredoc 里完成,不要再用第二个本地 node 脚本处理同一批数据。

ref 的有效范围

@N 仅对最近一次 snapshotText 的 refMap 有效。每次 snapshotText() 都会重建 refMap。ref 编号来自元素的 CDP backendNodeId,同一元素在多次 snapshot 中编号通常一致;但要操作 @N,N 必须出现在最近一次 snapshot 输出中。

触发 Unknown ref 的常见原因:

  • 元素被滚出 viewport。
  • DOM 重渲染。
  • 上一轮 scope: 'only_within_viewport' 而下一轮未覆盖该元素。

需要跨多轮稳定引用同一个元素时,使用 snapshot 输出里的 loc=... 作为稳定 selector,或直接写 CSS selector。这也是 Experience 沉淀的基础(见 Skills)。

Skill Workspace

ego-browser 不自带可变 agent experience。默认从仓库 skill 包加载 helper 增强与已学站点经验:

../../skills/ego-browser

通过环境变量覆盖:

EGO_BROWSER_AGENT_WORKSPACE=/path/to/ego-browser ego-browser nodejs <<'EOF'
cliLog(await siteSkills())
EOF

learnings/ 目录下的站点经验始终生效,每次 helper 调用都会读取一次。Experience 写入与发现机制见 Skills

校验已学经验:

npm run validate:learnings

目录结构

package/ego-browser/
├── src/                      # browser-runtime / helpers / run.js
│   ├── browser-runtime.js    # 浏览器侧 ego runtime 桥接
│   ├── helpers.js            # 暴露给 Agent 脚本的 helper
│   ├── run.js                # CLI 入口(执行 stdin)
│   └── learning/             # 经验索引、域名校验、格式校验
├── artifacts/ego-browser/    # 构建产物,npm bin 指向这里
└── test/                     # 单元测试

skills/ego-browser/
├── SKILL.md / SKILL.zh.md    # Agent 调用入口
└── learnings/                # 站点经验目录

注意事项

  • snapshotText() 默认 scope: 'full_page',覆盖整页。仅在只需可见区时才传 'only_within_viewport'
  • js() 返回的是表达式求值结果,不要再 JSON.parse(...)
  • js() 模板字符串里写正则时,反斜杠要写两次(\\d\\s),或改用 String.raw
  • 顶层 return 会被自动包成 IIFE;嵌套回调中的 return 也可能误触发,复杂表达式优先写成 (() => { ... })()
  • 用户明确要求使用 ego-browser 时,默认运行时已就绪,不要预先 which ego-browser / node -v / 查看 help,除非首次运行报错。