Communitygithub.com

ego-browser 轻量浏览器代理技能

为AI代理提供轻量级、高性能的Chromium浏览器自动化能力,支持通过CLI和Node.js运行时直接操作网页。

ego-browser 轻量浏览器代理技能 是什么?

ego-browser 是一个专为AI代理设计的轻量级浏览器技能,基于Playwright风格的API,通过CLI可访问的Node.js运行时暴露真实的Chromium浏览器。它提供了`page`、`page.locator(...)`、`browser`和`taskSpaces`等核心接口,支持包括导航、语义定位、截图、网络请求拦截在内的多种浏览器操作。该技能强调在单个Bash调用(heredoc)中完成完整的浏览器任务,避免多次进程间交互。它采用“任务空间”概念隔离浏览上下文,并提供了明确的所有权模型(agent-owned / user-owned)。开发者可将所有可预测的观察、动作、等待、提取、验证和有限替代方案编码在脚本中,直至任务完成,再通过专用的Bash调用来提交任务完成状态。支持浏览器控制权交接(handoff)机制,适用于需要人工介入的场景。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add fourth3950/ego-lite

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

ego-browser

ego-browser 通过CLI可访问的Node.js运行时暴露一个真实的Chromium浏览器。其预置的 page、page.locator(...)、browser 和 taskSpaces 外观遵循Playwright风格的命名和调用形式;taskSpaces、site、fetch 和 cdp 提供ego-browser特有的能力。

有关设置、安装或连接问题,请阅读 references/install.md。

使用 Bash 工具以 ego-browser nodejs <<'EOF' ... EOF 的方式运行浏览器任务。将JavaScript直接放在heredoc中;不要创建 .js 文件、导入Playwright、启动另一个浏览器或发明辅助程序名称。

heredoc只是JavaScript容器;Bash调用是执行回合。默认情况下,整个浏览器任务只使用一次Bash调用。 每个 await 都是内部操作,而不是步骤边界。在启动之前,将每个可预测的观察、动作、等待、提取、验证和有边界的替代方案都编码在脚本中。在JavaScript中立即使用浏览器结果,并持续在进程内调整,直到任务完成;不要仅为了检查中间输出或规划下一步操作而退出。仅在需要用户或外部控制、无法在进程内完成的视觉检查、或脚本无法恢复的进程级故障时,才启动另一个Bash命令。

在选择检查页面控件之前,优先选择状态最少、最可靠的路径。 当任务指定了结果或约束,但未指定必需的交互时,首选已经正确的状态,或已知能直接编码这些状态的稳定URL或站点路径;验证结果目标状态,而不是重放等效的筛选、排序或UI导航。当用户要求进行交互、交互本身正在测试中、或没有已知的可靠等效项时,才使用页面控件。切勿发明脆弱的路径。

将已满足的后置条件视为已完成的工作。 在操作一个可能已显示所需值的控件之前,仅执行最小的读取操作以确定该状态。如果匹配,不要打开其编辑器、重放交互或再次读取它;直接继续处理其余未满足的结果。“设置”、“选择”或“确保”等词语描述了所需的最终状态,除非用户明确要求转换或交互本身正在测试中。

将浏览器工作与终端完成分离。 useOrCreate 开始或恢复一个用户目标;保留其返回的 task.id,并重复使用相同的id或完全相同的名称,直到该目标终结。将所有可预测的观察、动作、等待、提取和验证保持紧凑,但不要在仍在确定目标是否满足的Bash调用中调用 taskSpaces.complete(...)。首先完成浏览器工作并打印证据,证明每个请求的结果以及任何必需的范围或覆盖边界都已得到验证。仅在审查了之前的输出后,才可使用专用的最终Bash调用来完成原始任务空间;它不执行 page 或 browser 工作。这种单一的生命周期提交通常是默认单次调用的例外,而不是一个浏览器步骤或回合。非空或看似合理的部分结果、停滞的页面、耗尽的重试或后备尝试都不是完成证据。keep: true 为用户保留终端结果;它不会让未完成的任务保持活动状态。

冻结当前或相对日期工作的时间窗口。 在收集记录之前,从用户/任务环境或明确验证的当前页面状态中一次性建立“今天/当前/最新”。将内容时间戳视为数据,而不是时钟。滚动、虚拟化、重新加载、缓存或更改的结果批次所揭示的旧记录不得替换该锚点。继续根据原始窗口评估记录;不要默默地将任务重新基准到最新观察到的内容日期。

快速开始

此技能中的每个示例都是刻意组合的。请根据用户任务调整其URL、选择器和数据。

ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('inspect example page')
await browser.openOrReuseTab('https://example.com', { wait: true, timeout: 20000 })

const heading = await page.getByRole('heading').first().innerText()
const info = await page.info()
if (!heading || !('url' in info)) throw new Error('Example page was not ready')

const result = { taskSpaceId: task.id, heading, url: info.url }
console.log(JSON.stringify(result, null, 2))
EOF

将所有可预测的工作保留在脚本内部,直到任务完成。使用 console.log(...) 输出最终结果。

复合模式

提取、选择、导航、验证

在列表或搜索页面上,先提取结构化候选对象再进行选择。在同一个Bash调用中保持提取、选择、动作、等待、验证和清理。

ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('compare search results')
await browser.openOrReuseTab('https://example.com/search?q=browser+automation', {
  wait: true,
  timeout: 20000,
})

const cards = page.locator('article')
const items = await cards.evaluateAll((nodes) =>
  nodes.map((node) => ({
    title: node.querySelector('h2')?.textContent?.trim(),
    href: node.querySelector('a')?.href,
  })),
)
const chosenIndex = items.findIndex((item) => item.title && item.href)
if (chosenIndex < 0) throw new Error('No usable result: ' + JSON.stringify(items))

const before = await page.url()
const navigation = page.waitForURL((url) => url.href !== before, { timeout: 15000 })
await cards.nth(chosenIndex).getByRole('link').first().click()
if (!(await navigation)) throw new Error('Chosen result did not navigate')

const info = await page.info()
if (!('url' in info) || info.url === before) throw new Error('Navigation was not verified')
const result = { chosen: items[chosenIndex], opened: info.url }
console.log(JSON.stringify(result, null, 2))
EOF

填充、触发、等待、回读

在触发请求/响应的操作之前注册等待,然后验证生成的页面状态,而不是将点击视为成功。

ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('search orders')
await browser.openOrReuseTab('https://example.com/orders', { wait: true, timeout: 20000 })

const responsePromise = page.waitForResponse(
  (response) => response.url().includes('/api/orders') && response.ok(),
  { timeout: 15000 },
)
await page.getByLabel('Search orders').fill('pending')
await page.getByRole('button', { name: /search/i }).click()
const response = await responsePromise

const rows = await page.locator('table tbody tr').allInnerTexts()
if (!rows.length) throw new Error('Search completed but returned no visible rows')
const result = { status: response.status(), rows }
console.log(JSON.stringify(result, null, 2))
EOF

刷新标签页句柄、切换、检查

将 targetId 视为短期句柄。在同一个Bash调用中发现、验证并使用它。

ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('review generated report')
const tabs = await browser.listTabs({ includeChrome: false })
const reportTab = tabs.find((tab) => tab.url.includes('/reports/'))
if (!reportTab?.targetId) throw new Error('Report tab not found: ' + JSON.stringify(tabs))

await browser.switchTab(reportTab.targetId)
const info = await page.info()
const heading = await page.getByRole('heading').first().innerText()
if (!('url' in info) || !info.url.includes('/reports/')) throw new Error('Wrong tab selected')
const result = { taskSpaceId: task.id, heading, url: info.url }
console.log(JSON.stringify(result, null, 2))
EOF

运行时映射

  • page:导航和状态 (goto, reload, url, title, info)、语义定位器、等待、snapshot、screenshot、screencast、evaluate、keyboard、mouse、下载和事件排空。
  • page.locator(selector):链式调用和过滤;first / nth / last;点击、悬停、dragTo、scrollIntoViewIfNeeded、表单、键盘、上传、状态读取、集合、元素评估、截图和等待方法。
  • browser:listTabs、currentTab、switchTab、openOrReuseTab、closeTab、ensureRealTab、iframeTarget。
  • taskSpaces:list、switch、new、useOrCreate、claim、complete、handOff、takeOver、waitForAgentControl。
  • fetch.server 执行Node端请求;fetch.browser 在当前页面来源中执行请求。仅将 cdp 用作逃生舱。
  • console.log 是输出通道。当确切签名不清楚时,使用 console.log(help('page'))、console.log(help('locator')) 或其他 help(name) 调用。

执行规则

  • page.url() 在ego-browser中是异步的;始终使用 await page.url()。page.waitForURL(...) 谓词接收一个 URL 对象,因此检查 url.href、url.pathname 或 url.searchParams。默认等待 load 事件;仅在故意在加载前继续执行时使用 waitUntil: 'commit'。
  • page.waitForURL、page.waitForLoadState、page.waitForSelector、定位器 waitFor 和 page.waitForFunction 在超时时返回假值。在继续之前检查结果或立即验证所需状态。
  • 在触发请求、响应或导航等待的操作之前注册它们。优先使用基于状态的等待;仅在短暂的视觉稳定时使用 page.waitForTimeout(...),并保持在2000毫秒或以下。
  • 优先使用稳定的语义定位器。当页面结构未知时,使用 evaluateAll、allInnerTexts 或其他有界读取一次性收集相关控件或候选对象,在JavaScript中派生下一个动作,并继续在同一个heredoc中执行,而不是跨命令枚举选择器猜测。
  • 单元素操作和必需的读取(包括原始CSS和原始 xpath= 定位器)是严格的并自动等待。对于零匹配,在纠正定位器之前确认加载状态、活动标签页和模态/覆盖层状态。对于多个匹配,检查 count() / allInnerTexts(),使用语义方式或 filter(...) 缩小范围,并且仅在确认重复项是合法的之后才使用 first() / nth()。让成功的操作推动脚本前进;在需要确定分支时读取状态,并且仅读取一次所需的任务最终后置条件,而不是在每个动作之后读取。已满足的所需状态无需重放。
  • 在失败时,使用一次有针对性的观察来实质性地改变策略。不要重复几乎相同的定位器或命令;根据证据切换到稳定的语义、DOM或视觉路径。
  • 保留用户明确要求的可见转换和停止边界。当所需的点击可能导航当前标签页或打开另一个标签页时,点击一次,然后通过 await page.url() 加上刷新的 browser.listTabs() 在同一个脚本中解析结果;不要仅仅因为目标已知就用直接导航替换点击。不要吞没必需操作的失败。

任务空间

任务空间是一个隔离的浏览上下文,拥有自己的标签页,并继承用户的登录状态。在第一个Bash脚本的开头附近使用 taskSpaces.useOrCreate(nameOrId) 选择它一次。如果外部依赖使得后续命令不可避免,则在继续之前选择相同的返回的数值 task.id 或完全相同的短目标名称;仅为单独的用户目标创建新空间。保留跨命令已验证的事实,而不是重新启动设置。

useOrCreate 重用或创建代理拥有的空间。如果匹配的空间是用户拥有的,则在不声明的情况下选择该空间,因此浏览器工作会碰到用户控制的硬停止。在获得用户明确确认后,使用 taskSpaces.list() → taskSpaces.claim(id) → browser.listTabs() → browser.switchTab(targetId)。

每个空间都有 ownership: 'agent' | 'agentDelegatedToUser' | 'user':

对用户拥有空间的操作行为
taskSpaces.switch抛出异常;它仅切换代理拥有的空间
taskSpaces.claim将所有权转移给代理并选择该空间
taskSpaces.handOff / complete(..., { keep: true })跳过并返回 { done: false, skipped: 'user-owned' }
taskSpaces.complete(..., { keep: false })声明,然后关闭空间
taskSpaces.takeOver / waitForAgentControl不执行所有权检查

在声明成功之前,检查 handOff 和 complete 的 done 结果。

将完成视为与浏览器执行分开的终端提交。在捕获并打印最终URL、值和其他证据后,不调用完成而结束工作的Bash调用。审查该输出:每个请求的后置条件以及任何必需的范围或覆盖边界必须得到验证,而不仅仅是可能。如果已验证,则运行一个专门调用的最终Bash调用,该调用最多调用一次 taskSpaces.complete(nameOrId, { keep })(针对原始id或确切名称),检查 done,并且不执行任何 page 或 browser 工作。如果任何内容未满足或未验证,则改为在同一个原始任务空间中继续;更正、重试或后期阶段不是新目标。keep 是必需的。默认值为 false;仅在用户要求保留已完成的页面、必须手动在其中操作、或结果无法作为URL、文件、工件或摘要交付时,才使用 true。使用过程中关闭临时标签页,仅保留用户需要的标签页。

切勿硬编码、手动复制或将 targetId 重命名为 id。在当前Bash调用内部获取并使用它。如果确实需要另一个命令,请在切换或关闭之前刷新 browser.listTabs() 并验证 find(...) 的结果。browser.iframeTarget(...) 返回一个target-id字符串或 null,而不是一个对象。

控制权交接

“用户正在控制”、“不活动”或“未分配”错误是整个任务的硬停止。不要重试、绕过它或自动调用 taskSpaces.takeOver。询问用户并等待。

对于登录、验证码或另一个手动步骤,请完成...

相关技能