ego-browser
面向 AI Agent 的瀏覽器自動化執行環境,連接 ego (lite) 的真實 Chromium 工作階段。
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
- 複用或建立 Task Space(每次 heredoc 都要聲明,詳見 Space)。
- 打開目標頁面。
- 讀取快照(
snapshotText())拿到帶[ref=N, loc=..., url=...]的語義樹。 - 基於
@Nref 或 CSS selector 執行動作。 - 用
cliLog(...)輸出最終結果。
heredoc 內是 Node.js 程序;
js(...)內才是瀏覽器頁面上下文。兩者不要混用。
Helper 參考
所有 helper 在腳本作用域內以 camelCase 直接可用,無需 import。
Task Space
await listTaskSpaces()
const task = await useOrCreateTaskSpace('describe task') // 複用或建立
await completeTaskSpace(task.name) // 任務完成、保留分頁
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() // 新建的 Task Space 可能還沒有分頁
觀察
await snapshotText() // 整頁語義 Snapshot(預設)
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 入手,因為它保留語義結構,並顯著降低座標脆弱性:
- 複用或建立 Task Space。
- 打開或切換頁面(
openOrReuseTab/gotoAndWait)。 snapshotText()獲取[ref=N, loc=..., url=...]語義樹,ref 自動註冊到 refMap。- 用
@N配合click/fillInput/elementEval執行動作,或在js(...)內一次性完成 DOM 抽取。 cliLog(...)輸出最終結果。
可組合的其他路徑:
captureScreenshot+click([x, y]):視覺版面、canvas 類介面、虛擬清單、accessibility 不完整的頁面。js/elementEval/cdp:直接抽取 DOM、查看瀏覽器狀態,或常規 helper 不夠直接的場景。
優先把導航、觀察、滾動、抽取、過濾、聚合、輸出寫在一段
ego-browser nodejsheredoc 裡完成,不要再用第二個本機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 擴充功能和已累積的網站 Experience:
../../skills/ego-browser
可透過環境變數覆寫:
EGO_BROWSER_AGENT_WORKSPACE=/path/to/ego-browser ego-browser nodejs <<'EOF'
cliLog(await siteSkills())
EOF
learnings/ 目錄下的網站 Experience 會持續生效,每次呼叫 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/ # 網站 Experience 目錄
注意事項
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。