ego (lite) is just a browser, ego is your personal agent across devices.
Join waitlist
Français

ego-browser

Le runtime d'automatisation navigateur que les agents IA utilisent pour piloter la session Chromium réelle d'ego lite.

llms.txt

ego-browser est le runtime d'automatisation navigateur livré par ego lite pour les agents IA. Il parle le Chrome DevTools Protocol avec la session Chromium réelle d'ego lite, et prend pour point d'entrée un script Node.js en heredoc : l'agent écrit tout son flux JS dans un seul envoi sur stdin, tous les helpers sont injectés d'avance dans la portée du script, et l'état du navigateur survit dans un Space.

ego-browser n'est pas conçu pour qu'un humain pilote un navigateur à la main, et ce n'est pas un remplaçant de Playwright ou Puppeteer. Le lecteur visé est un agent LLM.

À qui c'est destiné

  • Aux agents IA de coding qui doivent piloter un navigateur : Claude Code, Codex, Cursor, agents SDK maison.
  • Aux équipes qui construisent des agents verticaux : automatiser Lark, Google Docs, Salesforce et autres back-offices.
  • Aux flux web répétitifs : login, formulaire, export, recherche, lecture de tableaux.
  • À ceux qui ont déjà essayé de pousser un DOM entier ou une page de HTML dans un LLM et se sont heurtés au mur des tokens.

Installation

Livré avec ego lite — voir Démarrage rapide. Une fois installé, ego-browser est appelable depuis n'importe quel dossier.

La skill peut aussi être installée seule :

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

Boucle de base

Le rythme typique d'un agent qui pilote une page — tout dans un seul 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. Réutiliser ou créer un Task Space (à déclarer dans chaque heredoc — voir Space).
  2. Ouvrir la page cible.
  3. Lire le snapshot (snapshotText()) pour obtenir un arbre sémantique avec [ref=N, loc=..., url=...].
  4. Agir sur la page via @N ou un sélecteur CSS.
  5. Imprimer le résultat avec cliLog(...).

Dans le heredoc, vous êtes dans un processus Node.js. Dans js(...), vous êtes dans le contexte de la page. Ne mélangez pas les deux.

Référence des helpers

Tous les helpers sont disponibles dans la portée du script via leur nom camelCase. Pas besoin d'import.

Task Space

await listTaskSpaces()
const task = await useOrCreateTaskSpace('describe task')   // réutiliser ou créer
await completeTaskSpace(task.name)                         // fini, on garde l'onglet
await closeTaskSpace(task.name)                            // on ferme l'espace

name doit décrire la tâche en 3 à 6 mots, en langage naturel. Pas de 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()        // un task space tout neuf peut n'avoir aucun onglet

Observation

await snapshotText()                              // snapshot sémantique de toute la page (par défaut)
await snapshotText({ scope: 'only_within_viewport' })
await captureScreenshot('result.png')
await drainEvents()                               // consomme la file d'événements nav / réseau

Souris et scroll

click, doubleClick, hover et dragMouse acceptent le même format de cible (pixels CSS) :

  • 'string' : sélecteur CSS ou @ref. Clique au centre de l'élément.
  • [x, y] ou {x, y} : coordonnées dans le viewport.
  • {selector, x, y} : décalage relatif depuis le coin haut-gauche de l'élément.
  • options.label : description de 3 à 6 mots. Si fournie, l'action déclenche une animation de surlignage.
await click('@21', { label: 'vérifier la session' })
await click('button.primary', { label: 'cliquer sur Envoyer' })
await click([420, 260])
await hover('@5', { label: 'survoler le menu' })
await dragMouse([from, to], { label: 'déplacer la carte' })

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

Clavier et saisie

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

Fichiers et réseau

await uploadFile('input[type="file"]', '/absolute/path/to/file.pdf')
await httpGet('https://api.example.com/data')   // GET émis dans le contexte de la page

Attente

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

wait() et timeout sont en secondes. Seuls les paramètres terminant par Ms sont en millisecondes.

Exécution dans le navigateur

js(source) est en réalité un Runtime.evaluate et reçoit une chaîne. Ne lui passez pas une fonction et des arguments à la manière de Puppeteer — vous obtenez un warning, le tout est emballé dans un .toString(), et les variables capturées comme les arguments disparaissent.

Pour une logique multi-étapes, encapsulez tout dans une IIFE qui renvoie une seule valeur :

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

Sortie et auto-découverte

cliLog(value)                  // le seul canal de sortie dans un heredoc
cliLog(help('click'))          // consulter l'usage d'un helper

Workflow recommandé

Commencez par snapshotText plus ref / loc — la sémantique est préservée, et on évite la fragilité des coordonnées :

  1. Réutiliser ou créer le Task Space.
  2. Ouvrir ou changer de page (openOrReuseTab / gotoAndWait).
  3. snapshotText() pour obtenir l'arbre [ref=N, loc=..., url=...]. Les refs sont automatiquement enregistrées dans la refMap.
  4. Agir sur @N avec click / fillInput / elementEval, ou faire une extraction DOM en une fois dans js(...).
  5. cliLog(...) du résultat final.

D'autres trajectoires à combiner :

  • captureScreenshot + click([x, y]) : mises en page visuelles, UI à base de canvas, listes virtualisées, pages dont l'accessibilité est incomplète.
  • js / elementEval / cdp : extraire le DOM directement, inspecter l'état du navigateur, ou tout ce qui ne rentre pas proprement dans un helper standard.

Gardez navigation, observation, scroll, extraction, filtrage, agrégation et sortie dans un seul heredoc ego-browser nodejs. Ne réintroduisez pas un second script node local pour rejouer les mêmes données.

Portée des refs

@N n'est valide que pour la refMap du dernier snapshotText. Chaque snapshotText() reconstruit la refMap. Les numéros viennent du backendNodeId CDP de l'élément, donc le même élément garde généralement le même numéro d'un snapshot à l'autre — mais pour pouvoir utiliser @N, N doit figurer dans le dernier snapshot.

Causes classiques de Unknown ref :

  • L'élément est sorti du viewport.
  • Le DOM a été re-rendu.
  • Le tour précédent utilisait scope: 'only_within_viewport', et le suivant ne couvre pas cet élément.

Quand il faut référencer le même élément sur plusieurs tours, utilisez le sélecteur loc=... ou un sélecteur CSS. C'est aussi la base de l'accumulation d'expérience — voir Skills.

Skill workspace

ego-browser ne transporte pas d'expérience d'agent modifiable par défaut. Il charge les extensions de helper et l'expérience apprise depuis la bundle de skills du dépôt :

../../skills/ego-browser

À surcharger via une variable d'environnement :

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

L'expérience par site sous learnings/ est toujours active ; chaque appel de helper la relit. Le modèle d'écriture et de découverte est décrit dans Skills.

Vérifier les expériences apprises :

npm run validate:learnings

Arborescence

package/ego-browser/
├── src/                      # browser-runtime / helpers / run.js
│   ├── browser-runtime.js    # pont ego runtime côté navigateur
│   ├── helpers.js            # helpers exposés au script d'agent
│   ├── run.js                # point d'entrée CLI (exécute stdin)
│   └── learning/             # index d'expérience, validation domaine, validation format
├── artifacts/ego-browser/    # build ; npm bin pointe ici
└── test/                     # tests unitaires

skills/ego-browser/
├── SKILL.md / SKILL.zh.md    # point d'entrée pour l'agent
└── learnings/                # dossier d'expérience par site

Notes

  • snapshotText() est en scope: 'full_page' par défaut. Passez 'only_within_viewport' uniquement si vous voulez vraiment ne couvrir que la zone visible.
  • js() renvoie directement le résultat d'évaluation. Ne le JSON.parse(...) pas en plus.
  • Pour écrire un regex dans un template js(), doublez les antislashs (\\d, \\s) ou passez à String.raw.
  • Un return au top niveau est automatiquement enveloppé dans une IIFE. Un return dans un callback imbriqué peut déclencher la même chose, alors écrivez les expressions complexes directement en (() => { ... })().
  • Quand l'utilisateur demande explicitement ego-browser, le runtime est prêt. N'allez pas faire de pré-vérification (which ego-browser / node -v / dump de l'aide) — réservez ça à un cas où une première exécution a vraiment échoué.