ego (lite) is just a browser, ego is your personal agent across devices.
Join waitlist
Português (Brasil)

ego-browser

O runtime de automação de navegador que os agentes de IA usam para dirigir a sessão Chromium real do ego lite.

llms.txt

O ego-browser é o runtime de automação de navegador que o ego lite entrega para os agentes de IA. Ele conversa pelo Chrome DevTools Protocol com a sessão Chromium real dentro do ego lite e recebe como ponto de entrada um script Node.js em heredoc: o agente escreve o fluxo JS inteiro em uma única entrega no stdin, todos os helpers já vêm injetados no escopo, e o estado do navegador segue vivo dentro de um Space.

ego-browser não foi pensado para uma pessoa dirigir o navegador na mão, e também não substitui Playwright ou Puppeteer. O leitor pretendido é um agente LLM.

Para quem é

  • Agentes de coding com IA que precisam dirigir um navegador: Claude Code, Codex, Cursor, agentes SDK próprios.
  • Times construindo agentes verticais: automatizar Lark, Google Docs, Salesforce e back-offices parecidos.
  • Fluxos web repetitivos: login, preencher formulário, exportar, buscar, ler tabela.
  • Quem já tentou enfiar um DOM inteiro ou uma página de HTML num LLM e bateu no limite de token.

Instalar

Vem junto com o ego lite — veja Início rápido. Após instalar, basta chamar ego-browser de qualquer diretório.

A skill também pode ser instalada sozinha:

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

Ciclo principal

O ritmo típico do agente operando uma página, tudo num único 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. Reaproveitar ou criar um Task Space (declare em cada heredoc — veja Space).
  2. Abrir a página alvo.
  3. Ler o snapshot (snapshotText()) para receber a árvore semântica com [ref=N, loc=..., url=...].
  4. Agir na página por @N ou seletor CSS.
  5. Imprimir o resultado com cliLog(...).

Dentro do heredoc, você está num processo Node.js. Dentro de js(...), você está no contexto da página. Não misture.

Referência de helpers

Todos os helpers estão disponíveis no escopo do script pelo nome camelCase. Não precisa de import.

Task Space

await listTaskSpaces()
const task = await useOrCreateTaskSpace('describe task')   // reusa ou cria
await completeTaskSpace(task.name)                         // concluído, mantém a aba
await closeTaskSpace(task.name)                            // não precisa mais, fecha o space

name deve descrever a tarefa em 3 a 6 palavras, em linguagem natural. Sem placeholder.

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()        // um task space recém-criado pode não ter aba ainda

Observação

await snapshotText()                              // snapshot semântico da página toda (padrão)
await snapshotText({ scope: 'only_within_viewport' })
await captureScreenshot('result.png')
await drainEvents()                               // consumir a fila de eventos de navegação / rede

Mouse e scroll

click, doubleClick, hover e dragMouse aceitam o mesmo formato de target (pixels CSS):

  • 'string': seletor CSS ou @ref. Clica no centro do elemento.
  • [x, y] ou {x, y}: coordenadas no viewport.
  • {selector, x, y}: deslocamento a partir do canto superior esquerdo do elemento.
  • options.label: descrição em 3 a 6 palavras; se passada, a ação dispara animação de destaque.
await click('@21', { label: 'conferir o login' })
await click('button.primary', { label: 'clicar no botão Enviar' })
await click([420, 260])
await hover('@5', { label: 'passar o mouse no menu' })
await dragMouse([from, to], { label: 'arrastar o card' })

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 },
)

Teclado e entrada

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

Arquivos e rede

await uploadFile('input[type="file"]', '/absolute/path/to/file.pdf')
await httpGet('https://api.example.com/data')   // GET disparado no contexto da página

Esperas

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

wait() e timeout são em segundos. Só parâmetros terminados em Ms são em milissegundos.

Execução no navegador

js(source) é, na prática, um Runtime.evaluate e aceita uma string. Não passe função + argumentos no estilo Puppeteer — ele dá warning, envolve tudo em .toString(), e as variáveis capturadas e o canal de argumentos somem.

Para lógica multi-step, embrulhe em uma IIFE que retorna uma única vez:

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' })

Saída e auto-descoberta

cliLog(value)                  // o único canal de saída dentro de um heredoc
cliLog(help('click'))          // consultar o uso de um helper

Fluxo recomendado

Comece por snapshotText + ref / loc — preserva a semântica e evita a fragilidade de coordenadas:

  1. Reaproveitar ou criar o Task Space.
  2. Abrir ou trocar de página (openOrReuseTab / gotoAndWait).
  3. snapshotText() para receber a árvore [ref=N, loc=..., url=...]. Refs são registradas automaticamente no refMap.
  4. Agir sobre @N com click / fillInput / elementEval, ou fazer extração de DOM em um único js(...).
  5. cliLog(...) com o resultado final.

Outros caminhos para combinar:

  • captureScreenshot + click([x, y]): layouts visuais, UIs em canvas, listas virtualizadas, páginas com acessibilidade incompleta.
  • js / elementEval / cdp: extrair DOM diretamente, inspecionar estado do navegador, ou qualquer coisa que não encaixa direito num helper padrão.

Mantenha navegação, observação, scroll, extração, filtragem, agregação e saída dentro de um único heredoc ego-browser nodejs. Não passe os mesmos dados para um segundo script node local.

Validade das refs

@N só vale para o refMap do último snapshotText. Cada snapshotText() reconstrói o refMap. Os números das refs vêm do backendNodeId CDP do elemento, então o mesmo elemento costuma manter o mesmo número entre snapshots — mas para operar @N, N precisa aparecer no snapshot mais recente.

Causas comuns de Unknown ref:

  • O elemento saiu do viewport.
  • O DOM re-renderizou.
  • A rodada anterior usou scope: 'only_within_viewport' e a atual não cobriu o elemento.

Quando precisar de uma referência estável ao mesmo elemento por várias rodadas, use o loc=... do snapshot ou escreva CSS selector direto. É também a base da acumulação de Experience — veja Skills.

Skill workspace

O ego-browser por si só não carrega experiência mutável de agente. Por padrão ele carrega extensões de helper e a experiência aprendida sobre sites a partir do bundle de skills do repo:

../../skills/ego-browser

Pode ser sobrescrito por variável de ambiente:

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

A experiência por site em learnings/ fica sempre ativa, e é lida em toda chamada de helper. O modelo de escrita e descoberta de Experience está em Skills.

Validar a experiência aprendida:

npm run validate:learnings

Estrutura de pastas

package/ego-browser/
├── src/                      # browser-runtime / helpers / run.js
│   ├── browser-runtime.js    # ponte ego runtime do lado do navegador
│   ├── helpers.js            # helpers expostos ao script do agente
│   ├── run.js                # entrada CLI (executa stdin)
│   └── learning/             # índice de experiência, validação de domínio, validação de formato
├── artifacts/ego-browser/    # resultado de build; npm bin aponta para cá
└── test/                     # testes unitários

skills/ego-browser/
├── SKILL.md / SKILL.zh.md    # ponto de entrada para o agente
└── learnings/                # diretório de experiência por site

Observações

  • snapshotText() usa scope: 'full_page' por padrão e cobre a página inteira. Só passe 'only_within_viewport' quando realmente precisar só da área visível.
  • js() devolve direto o resultado da expressão; não faça JSON.parse(...) por cima.
  • Quando escrever regex em template string dentro de js(), duplique as contrabarras (\\d, \\s) ou use String.raw.
  • return no topo é embrulhado em IIFE automaticamente. return em callback aninhado também pode disparar isso, então escreva expressões complexas como (() => { ... })() direto.
  • Quando o usuário pediu explicitamente o ego-browser, o runtime já está pronto. Não faça pré-checagem (which ego-browser / node -v / dump do help) a menos que uma execução de fato dê erro.