ego (lite) is just a browser, ego is your personal agent across devices.
Join waitlist
Español (Latam)

ego-browser

El runtime de automatización de navegador que los agentes de IA usan para manejar la sesión real de Chromium dentro de ego lite.

llms.txt

ego-browser es el runtime de automatización de navegador que ego lite entrega a los agentes de IA. Habla Chrome DevTools Protocol con la sesión real de Chromium dentro de ego lite y recibe como punto de entrada un script Node.js en heredoc: el agente escribe todo el flujo JS en una sola entrega por stdin, todos los helpers ya están inyectados en el scope, y el estado del navegador queda vivo dentro de un Space.

ego-browser no fue pensado para que una persona maneje un navegador a mano, y tampoco es un reemplazo de Playwright o Puppeteer. El lector objetivo es un agente LLM.

Para quién es

  • Agentes IA de coding que necesitan manejar un navegador: Claude Code, Codex, Cursor, agentes SDK propios.
  • Equipos que construyen agentes verticales: automatizar Lark, Google Docs, Salesforce y back-offices parecidos.
  • Flujos web que se repiten: login, llenar formularios, exportar, buscar, leer tablas.
  • Quien alguna vez intentó meter el DOM completo o una página de HTML en un LLM y chocó con el límite de tokens.

Instalar

Viene con ego lite — ver Inicio rápido. Después de instalar, ego-browser se llama desde cualquier directorio.

La skill también se puede instalar por separado:

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

Ciclo central

El ritmo típico del agente operando una página, todo en un ú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 o crear un Task Space (declarado en cada heredoc — ver Space).
  2. Abrir la página objetivo.
  3. Leer el snapshot (snapshotText()) y obtener el árbol semántico con [ref=N, loc=..., url=...].
  4. Actuar sobre la página por @N o selector CSS.
  5. Imprimir el resultado con cliLog(...).

Dentro del heredoc estás en un proceso Node.js; dentro de js(...) estás en el contexto de la página. No los mezcles.

Referencia de helpers

Todos los helpers están disponibles en el scope del script con su nombre camelCase. Sin import.

Task Space

await listTaskSpaces()
const task = await useOrCreateTaskSpace('describe task')   // reusar o crear
await completeTaskSpace(task.name)                         // listo, mantener la pestaña
await closeTaskSpace(task.name)                            // cerrar el space

name describe la tarea en 3 a 6 palabras, en lenguaje natural. Nada de 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()        // un task space recién creado puede no tener pestaña aún

Observación

await snapshotText()                              // snapshot semántico de la página completa (por defecto)
await snapshotText({ scope: 'only_within_viewport' })
await captureScreenshot('result.png')
await drainEvents()                               // consumir la cola de eventos de navegación / red

Mouse y scroll

click, doubleClick, hover y dragMouse aceptan el mismo formato de target (píxeles CSS):

  • 'string': selector CSS o @ref. Hace clic en el centro del elemento.
  • [x, y] o {x, y}: coordenadas del viewport.
  • {selector, x, y}: offset desde la esquina superior izquierda del elemento.
  • options.label: descripción en 3 a 6 palabras; si se pasa, la acción dispara animación de resaltado.
await click('@21', { label: 'revisar el login' })
await click('button.primary', { label: 'hacer clic en Enviar' })
await click([420, 260])
await hover('@5', { label: 'pasar el mouse por el menú' })
await dragMouse([from, to], { label: 'arrastrar la tarjeta' })

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 y entrada

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

Archivos y red

await uploadFile('input[type="file"]', '/absolute/path/to/file.pdf')
await httpGet('https://api.example.com/data')   // GET emitido en el contexto de la página

Esperas

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

wait() y timeout son en segundos. Solo los parámetros que terminan en Ms son milisegundos.

Ejecución en el navegador

js(source) en el fondo es Runtime.evaluate y recibe un string. No le pases función + argumentos al estilo Puppeteer — saca warning, lo envuelve en .toString(), y las variables capturadas y el canal de argumentos se pierden.

Para lógica multi-paso, envuelve todo en una IIFE que devuelve una sola 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' })

Salida y autodescubrimiento

cliLog(value)                  // el único canal de salida dentro de un heredoc
cliLog(help('click'))          // consultar el uso de un helper

Flujo recomendado

Empieza con snapshotText + ref / loc — conserva la semántica y evita la fragilidad de las coordenadas:

  1. Reusar o crear el Task Space.
  2. Abrir o cambiar de página (openOrReuseTab / gotoAndWait).
  3. snapshotText() para obtener el árbol [ref=N, loc=..., url=...]. Las refs se registran automáticamente en el refMap.
  4. Actuar sobre @N con click / fillInput / elementEval, o hacer una extracción de DOM en un solo js(...).
  5. cliLog(...) con el resultado final.

Otras rutas para combinar:

  • captureScreenshot + click([x, y]): layouts visuales, UIs de canvas, listas virtualizadas, páginas con accesibilidad incompleta.
  • js / elementEval / cdp: extraer DOM directo, inspeccionar el estado del navegador, o cualquier cosa que no encaja limpio en un helper estándar.

Mantén navegación, observación, scroll, extracción, filtrado, agregación y salida dentro de un único heredoc ego-browser nodejs. No pases los mismos datos a un segundo script node local.

Alcance de las refs

@N solo es válido para el refMap del último snapshotText. Cada snapshotText() reconstruye el refMap. Los números de ref vienen del backendNodeId CDP del elemento, así que el mismo elemento suele conservar el mismo número entre snapshots — pero para operar @N, N tiene que aparecer en el snapshot más reciente.

Causas habituales de Unknown ref:

  • El elemento salió del viewport.
  • El DOM volvió a renderizarse.
  • La ronda anterior usó scope: 'only_within_viewport' y la actual no cubre al elemento.

Cuando necesitas referenciar de forma estable el mismo elemento a lo largo de varias rondas, usa el loc=... del snapshot o escribe un selector CSS. Es también la base de la acumulación de Experience — ver Skills.

Skill workspace

ego-browser por sí solo no carga experiencia mutable de agente. Por defecto carga extensiones de helper y experiencia aprendida sobre sitios desde el bundle de skills del repo:

../../skills/ego-browser

Se puede sobrescribir con variable de entorno:

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

La experiencia por sitio en learnings/ está siempre activa y se lee en cada llamada a un helper. El modelo de escritura y descubrimiento de Experience está en Skills.

Validar la experiencia aprendida:

npm run validate:learnings

Estructura de carpetas

package/ego-browser/
├── src/                      # browser-runtime / helpers / run.js
│   ├── browser-runtime.js    # puente ego runtime en el lado del navegador
│   ├── helpers.js            # helpers expuestos al script del agente
│   ├── run.js                # entrada CLI (ejecuta stdin)
│   └── learning/             # índice de experiencia, validación de dominio, validación de formato
├── artifacts/ego-browser/    # resultado de build; npm bin apunta acá
└── test/                     # pruebas unitarias

skills/ego-browser/
├── SKILL.md / SKILL.zh.md    # punto de entrada del agente
└── learnings/                # directorio de experiencia por sitio

Notas

  • snapshotText() por defecto usa scope: 'full_page' y cubre toda la página. Solo pasa 'only_within_viewport' si de verdad necesitas únicamente el área visible.
  • js() devuelve directo el resultado de la expresión; no lo vuelvas a JSON.parse(...).
  • Cuando escribas regex dentro de un template string de js(), duplica las contrabarras (\\d, \\s) o usa String.raw.
  • Un return en el nivel superior se envuelve automáticamente en IIFE. Un return dentro de un callback anidado puede disparar lo mismo, así que escribe las expresiones complejas como (() => { ... })() de entrada.
  • Cuando el usuario pide explícitamente ego-browser, el runtime ya está listo. No hagas precheck (which ego-browser / node -v / dump de help) salvo que una primera ejecución realmente falle.