Thanks for your interest in contributing to ego-browser (ego-lite)! This guide is aimed at developers who want to build on top of the project or submit patches upstream. It covers the architecture, local development workflow, code conventions, and PR process.
For the project vision, see
README.md. For the agent-facing runbook, seeskills/ego-browser/SKILL.md(orSKILL.zh.md). For repo-level guidance, seeAGENTS.md.
- 1. What This Project Is
- 2. Repository Layout
- 3. Tech Stack & Runtime
- 4. Local Development Setup
- 5. Architecture Overview
- 6. Key Modules
- 7. Site Learnings
- 8. Testing & Quality
- 9. Code Style & Conventions
- 10. Commit & PR Process
- 11. Release & CI
- 12. Design Principles (The Four Principles)
ego-browser is a Chromium browser designed for collaboration between humans and AI agents. This repository (ego-lite) provides the Node.js helper runtime and agent skill package that run on top of that browser.
- This repo does not ship the browser binary; the browser must be installed separately.
- Agents invoke the CLI by running
ego-browser nodejs <<'EOF' ... EOFfrom inside the browser. Each heredoc runs in a fresh Node process with all helpers injected into scope. - State (task spaces, tabs, login sessions) lives on the browser side, not in the Node process.
ego-lite/
├── package/ego-browser/ # The runnable npm package (TypeScript)
│ ├── src/ # Core source
│ │ ├── index.ts # SDK / CLI bootstrap
│ │ ├── run.ts # stdin executor, CLI entry
│ │ ├── helpers.ts # Public helper surface composition
│ │ ├── browser-runtime.ts # CDP transport and session cache
│ │ ├── element-resolver.ts # @eN / CSS / XPath / ARIA resolution
│ │ ├── cdp-eval.ts # cdp() / js() helpers
│ │ ├── state.ts # Shared mutable runtime state (singleton)
│ │ ├── env.ts # Environment variables
│ │ ├── driver/ # Capability-scoped driver modules
│ │ │ ├── pointer.ts # Click / hover / drag / scroll
│ │ │ ├── keyboard.ts # Typing / key dispatch
│ │ │ ├── nav.ts # Page and tab navigation
│ │ │ ├── observe.ts # Snapshot / screenshot / events
│ │ │ ├── waits.ts # Wait primitives
│ │ │ └── files.ts # File upload
│ │ └── learning/ # Site skill loading and validation
│ ├── scripts/
│ │ ├── build.mjs # esbuild + rollup bundler
│ │ └── validate-site-skills.ts
│ ├── test/ # node --test suites
│ ├── artifacts/ # Build output (published to GitHub Release by CI)
│ ├── dist/ # tsc output (gitignored)
│ ├── package.json
│ └── tsconfig.json
├── skills/ego-browser/ # Agent skill package
│ ├── SKILL.md / SKILL.zh.md # Agent usage guide
│ └── learnings/<site>/ # Per-site knowledge packs (github / google / x-com ...)
├── spec/ # Spec references
├── public/ # Demo assets
├── .github/workflows/ci.yml # CI (test + release)
├── .claude-plugin/ # Claude Code plugin marketplace manifest
├── AGENTS.md # Repo-level agent / contributor guidance
└── README.md
| Item | Choice |
|---|---|
| Language | TypeScript (tsc --noEmit for typecheck only) |
| Runtime | Node.js >= 22, ESM only ("type": "module") |
| Package manager | npm (commit package-lock.json) |
| Bundler | esbuild + rollup (output: artifacts/ego-browser/index.js) |
| Tests | Node built-in node --test + node:assert/strict |
| Runtime deps | Only acorn (lightweight parsing) |
| Browser transport | Chrome DevTools Protocol (CDP) directly — no Puppeteer / Playwright |
All commands run from
package/ego-browser/.
# 1. Install dependencies
cd package/ego-browser
npm ci
# 2. Build (produces dist/ and artifacts/ego-browser/index.js)
npm run build
# 3. Typecheck
npm run typecheck
# 4. Run tests (automatically includes build + typecheck)
npm test
# 5. Validate site learnings
npm run validate:site-skills # alias: validate:learningsCalling the CLI directly (for local debugging):
node artifacts/ego-browser/index.js <<'JS'
await waitForLoadState()
console.log(await pageInfo())
JSCLI debug flags (see src/run.ts):
--help/-h: print usage--doctor: check browser and connection state--reload: force-rebuild the CDP connection on next call--debug-clicks: equivalent toEGO_BROWSER_DEBUG_CLICKS=1
Key environment variables:
| Variable | Purpose |
|---|---|
EGO_BROWSER_AGENT_WORKSPACE |
Override skill workspace root (defaults to skills/ego-browser inside the repo) |
EGO_BROWSER_NAME |
Browser instance name (default default) |
EGO_BROWSER_DEBUG_CLICKS |
Enable click debug logging |
┌────────────────────────────────────────────────┐
│ stdin (JS code inside the heredoc) │
└──────────────────┬─────────────────────────────┘
│
▼
┌──────────────────────┐
│ run.ts runMain() │ wraps stdin in AsyncFunction, injects helpers
└──────────┬───────────┘
│
▼
┌──────────────────────────────┐
│ helpers.ts (public API) │
│ ├─ task-space helpers │
│ ├─ driver/* capabilities │
│ ├─ cdp() / js() │
│ └─ learning helpers │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ browser-runtime.ts │
│ CDP transport / sessions / │
│ events │
└──────────────┬───────────────┘
│ Chrome DevTools Protocol
▼
┌──────────────┐
│ ego-browser │ browser holds tabs / task spaces / login state
└──────────────┘
Core data flow:
stdin JS → runMain() → injected helpers → CDP / DOM / AX resolution → optional Site Skill
A Task Space is an isolated browsing context provided by ego-browser: it owns its own tab set but inherits the user's login state.
Because every heredoc runs in a fresh Node process, the agent must call useOrCreateTaskSpace(name) at the start of every heredoc to re-attach to the same task space, and end with completeTaskSpace(name, { keep }) in the final round.
Control (agent ↔ user) is handed off via the handOffTaskSpace / takeOverTaskSpace / waitForAgentControl protocol — for example, when the user needs to log in manually or solve a CAPTCHA.
| File | Responsibility |
|---|---|
src/index.ts |
SDK injection (installEgoSdk); decides whether to run as CLI or be imported as a library |
src/run.ts |
Reads stdin, builds an AsyncFunction, and invokes it with helpers as named arguments |
src/helpers.ts |
Composes and exports the helper set exposed to heredocs |
src/browser-runtime.ts |
Maintains the CDP connection, session cache, and event buffer for the browser's ego runtime |
src/element-resolver.ts |
Resolves @eN refs, CSS, XPath, and ARIA/role to backend nodeIds |
src/cdp-eval.ts |
cdp() raw CDP calls + js() in-page evaluation |
src/state.ts |
Shared mutable state singleton (send, platform, agentWorkspace, session caches). Tests can inject stubs via setOverrides() |
src/driver/* |
Minimal-dependency primitives per capability; only call into cdp() |
src/learning/index.ts |
Discovers and loads learnings/<site>/, exposes runSiteTool / runSiteBrowserTool |
src/learning/validate-learning-format.ts |
Site manifest validator |
scripts/build.mjs |
Uses .build.lock to prevent concurrent builds; esbuild transform + rollup bundle into a single file |
Historical note: the repo is migrating from
.jsto.ts. IfAGENTS.mdstill mentions.jsfiles, defer to the currentsrc/*.ts.
Each site learning pack lives under skills/ego-browser/learnings/<site>/, with this shape:
learnings/<site>/
├── manifest.json # Site metadata, domain matching, declared tools and parameter schemas
├── notes/*.md # Entry points, structure, edge cases — human-readable notes
├── tools/*.js # Node-side tools (run inside the CLI process)
└── browser-tools/*.js # Browser-side tools (injected and run in the page)
Adding a new site learning pack:
- Copy the structure from an existing pack (recommend
learnings/github/). - Write
manifest.jsonwithid,name,domains[],notes[],nodeTools{},browserTools{}, and parameter schemas. - Implement
tools/*.jsandbrowser-tools/*.js. - Validate:
cd package/ego-browser npm run validate:site-skills - Add at least one behavior test in the
test/site-skills.test.jsstyle.
Hard constraints for learning packs:
- Use stable URLs and stable selectors (CSS / ARIA / text) only
- Never write pixel coordinates, secrets/tokens, or task narration
- Capture the shape of the site, not your task — a "map", not a "diary"
- Test framework:
node --testwithnode:assert/strict - Test files:
package/ego-browser/test/*.test.js, split by responsibility (runtime / helpers / resolver / nav-driver / site-skills / build / state ...) - Style: behavior-driven, using temp workspaces +
setOverrides()for stub injection — no real browser launches
Minimum pre-submit bar:
cd package/ego-browser
npm test # must pass
npm run validate:site-skills # if learnings changedWhen to add/extend tests:
- Changes to session / connection handling → add cases in
browser-runtime.test.js/session-injection.test.js - Changes to the resolver →
element-resolver.test.js - New helper →
helpers.test.jsor the matching driver test - Changes to learning loading →
site-skills.test.js/validate-site-skills.test.js
- ESM only:
importpaths use the.jsextension (NodeNext resolution) - Node 22+
- All public helpers are camelCase — do not provide
snake_casealiases - Async helpers start with a verb:
runMain,ensureSession,siteSkillsForUrl,runSiteTool, ... - TypeScript non-strict mode:
strict: false, but keep explicit type signatures - Shared state goes through the
state.tssingleton — do not threadconnection/sendthrough function parameters - Helpers are injected, not imported: agent scripts do not
import; all helpers are placed in scope byrun.ts - Snapshot refs (
@eN) are short-lived: re-snapshot after any DOM mutation; for long-lived values useloc=...or stable CSS / ARIA - No lint / prettier: style is enforced by convention and code review. When editing, blend in with the surrounding code instead of introducing a new style
- Branch from latest
main:git checkout -b <type>/<short-description> - Follow Conventional Commits; see recent history for tone:
fix(ego-browser): format object-shaped ego errors with message/JSON test(ego-browser): expand e2e coverage for handoff and control probing - Common
types:feat/fix/refactor/test/docs/chore/ci scopeis usuallyego-browseror the learning pack name (e.g.learnings/github)
A PR description should include at minimum:
- What — one-sentence summary of the change
- Why — motivation / linked issue
- How to verify — repro / verification steps; attach screenshots or heredoc examples for UI behavior
- Impact callout (does it touch the helper surface? does the agent side need updates?)
Add at least one release-note label so generated releases are grouped correctly:
feat / fix / docs / chore / ci / refactor.
-
npm testis green - Typecheck passes (
npm run typecheck) - If learnings changed,
npm run validate:site-skillspasses - Change is "minimal surgical edit" (see §12)
- No undeclared runtime dependencies introduced
- Public helper names / docs are kept in sync (update both
SKILL.mdandSKILL.zh.md)
- CI config:
.github/workflows/ci.yml- Every push / PR: on Node 22 + ubuntu-latest, runs
npm ci→npm test→npm run validate:site-skills - Tags matching
vX.Y.Z-beta.N: build a beta prerelease - Tags matching
vX.Y.Z: build a stable release and mark it as latest
- Every push / PR: on Node 22 + ubuntu-latest, runs
- Release notes are generated automatically from merged PRs and grouped by
.github/release.ymllabels: Features, Fixes, Documentation, Maintenance, and Other Changes. - Normal flow: merge features into
dev, cut beta tags fromdev, then mergedevtomainand cut stablevX.Y.Ztags frommain. - The build script
scripts/build.mjsuses.build.lockto prevent concurrent builds.
This repo strongly endorses the four principles below (see AGENTS.md). Both human contributors and AI agents should apply them on every change:
| Principle | Meaning |
|---|---|
| Think Before Coding | Don't assume, don't paper over confusion; surface trade-offs explicitly and push back when needed |
| Simplicity First | The smallest amount of code that solves the problem; no speculative abstractions or configuration |
| Surgical Changes | Change only what needs to change; preserve the existing style; do not opportunistically "improve" unrelated code |
| Goal-Driven Execution | Translate the task into a verifiable goal; write the check first, then iterate until it passes |
"Every changed line should trace back to the requirement of this task."
- Issues / discussions: https://github.com/CitroLabs/ego-lite/issues
- License: MIT © 2026 CitroLabs
Issues, PRs, and new site learnings are all welcome — make ego-browser smarter with your next contribution.