ego-browser
AI エージェント向けのブラウザ自動化ランタイム。ego lite の実 Chromium セッションを操作する。
ego-browser は、ego lite が AI エージェント向けに提供するブラウザ自動化ランタイムです。Chrome DevTools Protocol を介して ego lite の実 Chromium セッションに接続し、Node.js heredoc スクリプトをエントリポイントとして受け取ります。エージェントは 1 回の stdin 投入で一連の JS 処理を書き、すべての helper はスクリプトのスコープに事前注入されています。ブラウザの状態は Space の中で持続します。
ego-browser は人がブラウザを手で操作するためのものではなく、Playwright や Puppeteer の置き換えでもありません。読み手として想定しているのは LLM エージェントです。
誰のためのものか
- ブラウザを操作する必要がある AI コーディングエージェント: Claude Code、Codex、Cursor、独自 SDK エージェント。
- 縦割りのエージェントを作るチーム: 飛書(Lark)、Google Docs、Salesforce などの管理画面を自動化したい場合。
- 決まったウェブフローを繰り返し回したい場面: ログイン、フォーム入力、エクスポート、検索、表の読み取り。
- 一度は完全な DOM やページ HTML を LLM に詰め込み、トークン上限にぶつかった経験がある人。
インストール
ego lite と一緒に入ります(クイックスタート を参照)。インストール後、任意のディレクトリで ego-browser コマンドを呼べます。
Skill 単体でインストールも可能です:
npx skills add github:CitroLabs/ego-lite/skills/ego-browser
コアループ
ページを操作するエージェントの典型的なリズム。すべて 1 つの 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 セレクタでページを操作する。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() // ページ全体の意味スナップショット(デフォルト)
await snapshotText({ scope: 'only_within_viewport' })
await captureScreenshot('result.png')
await drainEvents() // ナビゲーション / ネットワークイベントキューを消費
マウスとスクロール
click / doubleClick / hover / dragMouse は共通の target 形式(CSS ピクセル)を受け取ります:
'string': CSS セレクタまたは@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 風に「関数 + 引数」を渡さないでください。警告が出て .toString() で包まれてしまい、クロージャの変数も引数経路も消えます。
複数手順のロジックは IIFE にまとめて 1 度だけ return します:
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 系 UI、仮想リスト、アクセシビリティが不完全なページに有効。js/elementEval/cdp: DOM を直接抽出する、ブラウザの状態を覗く、標準 helper では届きにくい場面。
ナビゲーション、観察、スクロール、抽出、フィルタ、集約、出力は、1 本の
ego-browser nodejsheredoc にまとめてください。同じデータを別のnodeスクリプトで再加工する形にしないこと。
ref の有効範囲
@N は最新の snapshotText の refMap に対してのみ有効です。snapshotText() を呼ぶたびに refMap は作り直されます。ref 番号は要素の CDP backendNodeId 由来なので、同じ要素は複数のスナップショットで同じ番号になることが多いです。ただし @N を操作するには、N が最新のスナップショット出力に含まれている必要があります。
Unknown ref が出る代表的な原因:
- 要素が viewport の外に出た。
- DOM が再レンダリングされた。
- 直前のラウンドが
scope: 'only_within_viewport'で、今回のラウンドが当該要素を含まなかった。
複数ラウンドにわたって同じ要素を安定的に参照したい場合は、スナップショット出力の loc=... を安定セレクタとして使うか、CSS セレクタを直接書きます。これは Experience 蓄積の土台にもなっています(Skills を参照)。
Skill ワークスペース
ego-browser 自体は可変のエージェント Experience を持ちません。デフォルトでは、リポジトリの skill バンドルから helper 拡張と学習済みのサイト経験を読み込みます:
../../skills/ego-browser
環境変数で上書き:
EGO_BROWSER_AGENT_WORKSPACE=/path/to/ego-browser ego-browser nodejs <<'EOF'
cliLog(await siteSkills())
EOF
learnings/ 配下のサイト経験は常に有効で、helper 呼び出しのたびに読まれます。書き込みと発見の仕組みは Skills を参照してください。
学習済みの経験を検証:
npm run validate:learnings
ディレクトリ構造
package/ego-browser/
├── src/ # browser-runtime / helpers / run.js
│ ├── browser-runtime.js # ブラウザ側 ego runtime ブリッジ
│ ├── helpers.js # エージェントスクリプトに公開する helper
│ ├── run.js # CLI エントリ(stdin を実行)
│ └── learning/ # 経験インデックス、ドメイン検証、形式検証
├── artifacts/ego-browser/ # ビルド成果物。npm bin はここを指す
└── test/ # ユニットテスト
skills/ego-browser/
├── SKILL.md / SKILL.zh.md # エージェントの呼び出し口
└── learnings/ # サイト経験ディレクトリ
注意点
snapshotText()のデフォルトはscope: 'full_page'(ページ全体)です。可視領域だけで足りる場合だけ'only_within_viewport'を渡してください。js()が返すのは評価結果そのものです。JSON.parse(...)で再パースしないでください。js()のテンプレート文字列内で正規表現を書くときは、バックスラッシュを 2 重にする(\\d、\\s)かString.rawを使ってください。- トップレベルの
returnは自動で IIFE に包まれます。ネストされたコールバック内のreturnも同様に誤発火することがあるので、複雑な式は最初から(() => { ... })()で書くのが安全です。 - ユーザーが ego-browser を明示的に指定した場合、ランタイムは既に整っています。初回実行でエラーが出るまで、
which ego-browser/node -v/ help ダンプを先に走らせる必要はありません。