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

ego-browser

O runtime de automatização de browser que os agentes IA usam para conduzir a sessão Chromium real do ego lite.

llms.txt

O ego-browser é o runtime de automatização de browser que o ego lite fornece aos agentes de IA. Fala o 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 numa única entrega no stdin, todos os helpers já estão injetados no scope do script, e o estado do browser persiste num Space.

O ego-browser não foi pensado para uma pessoa conduzir o browser à mão, e não substitui o Playwright nem o Puppeteer. O leitor visado é um agente LLM.

Para quem é

  • Agentes de coding IA que precisam de conduzir um browser: Claude Code, Codex, Cursor, agentes SDK próprios.
  • Equipas que constroem agentes verticais: automatizar Lark, Google Docs, Salesforce e back-offices semelhantes.
  • Fluxos web repetitivos: login, preenchimento de formulários, export, pesquisa, leitura de tabelas.
  • Quem já tentou enfiar um DOM completo ou uma página de HTML num LLM e bateu no limite de tokens.

Instalação

Vai junto com o ego lite — ver Início rápido. Depois de instalado, basta chamar ego-browser a partir de qualquer diretório.

A skill também pode ser instalada em separado:

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

Ciclo central

O ritmo típico do agente a conduzir 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. Reutilizar ou criar um Task Space (declarado em cada heredoc — ver Space).
  2. Abrir a página alvo.
  3. Ler o snapshot (snapshotText()) para obter a árvore semântica com [ref=N, loc=..., url=...].
  4. Agir sobre a página por @N ou seletor CSS.
  5. Imprimir o resultado final com cliLog(...).

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

Referência de helpers

Todos os helpers ficam disponíveis no scope do script pelo nome camelCase. Não é preciso import.

Task Space

await listTaskSpaces()
const task = await useOrCreateTaskSpace('describe task')   // reutilizar ou criar
await completeTaskSpace(task.name)                         // concluído, manter o separador
await closeTaskSpace(task.name)                            // fechar o espaço

name deve descrever a tarefa em 3 a 6 palavras, em linguagem natural. Não use placeholders.

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 ainda não ter separador

Observação

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

Rato e scroll

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

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

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 introdução de texto

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

Ficheiros 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 estão em segundos. Só os parâmetros terminados em Ms é que são em milissegundos.

Execução no browser

js(source) é, na prática, Runtime.evaluate e aceita uma string. Não lhe passe função mais argumentos como no Puppeteer — dá warning, é embrulhado em .toString(), e as variáveis capturadas e o canal de argumentos perdem-se.

Para lógica em vários passos, embrulhe tudo numa IIFE que devolve 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 mais ref / loc — mantém-se a semântica e evita-se a fragilidade das coordenadas:

  1. Reutilizar ou criar o Task Space.
  2. Abrir ou mudar de página (openOrReuseTab / gotoAndWait).
  3. snapshotText() para obter a árvore [ref=N, loc=..., url=...]. As refs ficam automaticamente registadas no refMap.
  4. Agir sobre @N com click / fillInput / elementEval, ou fazer uma extração de DOM em um único js(...).
  5. cliLog(...) do 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 o estado do browser, ou tudo o que não encaixa bem num helper standard.

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

Validade das refs

@N só é válido para o refMap do último snapshotText. Cada snapshotText() reconstrói o refMap. Os números das refs vêm do backendNodeId CDP do elemento, por isso o mesmo elemento costuma manter o mesmo número entre snapshots — mas para operar @N, N tem de aparecer no snapshot mais recente.

Causas habituais de Unknown ref:

  • O elemento saiu do viewport.
  • O DOM voltou a renderizar.
  • O snapshot anterior foi scope: 'only_within_viewport' e o atual não cobre o elemento.

Se precisa de uma referência estável ao mesmo elemento ao longo de várias rondas, use o loc=... da saída do snapshot ou escreva um seletor CSS. É também a base da acumulação de Experience — ver Skills.

Skill workspace

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

../../skills/ego-browser

Pode ser sobreposto 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/ está sempre ativa e é lida em cada 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 browser
│   ├── 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 aqui
└── 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

Notas

  • snapshotText() usa scope: 'full_page' por defeito e cobre a página toda. Só passe 'only_within_viewport' se realmente precisar apenas da área visível.
  • js() devolve diretamente o resultado da expressão; não faça JSON.parse(...) por cima.
  • Quando escrever regex dentro de uma template string de js(), duplique as barras invertidas (\\d, \\s) ou passe a String.raw.
  • Um return ao nível de topo é embrulhado automaticamente em IIFE. Um return dentro de um callback aninhado pode despoletar a mesma coisa, por isso escreva expressões complexas como (() => { ... })() à partida.
  • Quando o utilizador pediu explicitamente o ego-browser, o runtime já está pronto. Evite pré-verificações como which ego-browser / node -v / dump do help; só vá lá se uma primeira execução der erro.