# How stdin JavaScript Becomes Browser Commands in ego-lite: Complete Data Flow Explained

> Explore the 7-stage pipeline of stdin JavaScript to browser commands in ego-lite. Understand the complete data flow from input to Chrome DevTools Protocol execution.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: internals
- Published: 2026-08-01

---

**stdin JavaScript in ego-lite flows through a 7-stage pipeline—entry point wrapping, helper context injection, driver layer transformation, CDP payload construction, host bridge transmission, event queue handling, and ref-map resolution—to execute as Chrome DevTools Protocol commands against the embedded browser.**

The `citrolabs/ego-lite` repository implements a TypeScript runtime that transforms ordinary JavaScript scripts into browser automation commands without spawning external processes. This article traces the complete data flow from the moment code enters through stdin to when CDP responses return to your script.

## Entry Point: Receiving and Wrapping stdin Input

The data flow begins in [`package/ego-browser/src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts), where the CLI consumes the entire stdin stream and dispatches execution through one of two paths.

When invoked as a CLI tool, `runMain()` receives the raw script string, wraps it in an async function, injects helper bindings, and passes the result to the execution layer. When imported as a module, `installEgoSdk()` performs equivalent initialization for programmatic use.

```typescript
// From src/index.ts - conceptual flow
runMain(stdinScript) → wraps in async IIFE → injects helpers → calls run()

```

The wrapping process ensures top-level await works correctly and establishes the lexical scope where helper references (`nav`, `pointer`, `js`, etc.) resolve to the injected context.

## Script Execution: Building the Helper Context

The `run()` function in [`package/ego-browser/src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/run.ts) orchestrates script execution. It creates a fresh **helper context** by calling `helperContext()` from [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts).

This context object contains every public automation primitive:

- `nav` — navigation operations (goto, reload, back, forward)
- `pointer` — mouse interactions (click, move, drag)
- `keyboard` — key input and combinations
- `js` — JavaScript evaluation in page context
- `$` — element locator and ref resolution
- `observe` — snapshot and state inspection

The user script executes with this context as its lexical environment, so direct calls like `await nav.goto(url)` immediately invoke the bound helper methods.

## Driver Layer: Transforming Helpers to CDP Semantics

Each helper delegates to domain-specific drivers under `package/ego-browser/src/driver/`. These drivers translate high-level intentions into CDP method calls.

**Navigation** ([`src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/nav.ts)):

```typescript
// User writes:
await nav.goto('https://example.com');

// Driver builds:
{ method: 'Page.navigate', params: { url: 'https://example.com' }}

```

**Pointer** ([`src/driver/pointer.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/pointer.ts)):

```typescript
// User writes:
await pointer.click(100, 200);

// Driver builds:
{ method: 'Input.dispatchMouseEvent', 
  params: { type: 'mousePressed', x: 100, y: 200, ... }}

```

Drivers also handle retry logic and error classification. Failures wrap in `ElementResolutionError` with a `transient` boolean flag, enabling automatic retry by surrounding `waits` infrastructure.

## Runtime Bridge: From JavaScript Objects to CDP Transport

All driver outputs converge on `browserRuntime()` in [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts). This singleton manages:

- The active CDP session reference
- A 10,000-capacity event queue for buffering responses
- Pending command promises awaiting resolution

The runtime constructs final CDP request objects and forwards them through `ego.sendCDPMessage`—the sole boundary to the closed-source host environment that manages the actual browser instance.

## Low-Level CDP Construction: cdp-eval.ts

For operations requiring direct protocol access, [`package/ego-browser/src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/cdp-eval.ts) provides two primitives:

- **`cdp(method, params)`** — sends any raw CDP method with arbitrary parameters
- **`js(expression)`** — evaluates JavaScript in page context, automatically wrapping expressions lacking explicit `return` in an IIFE

```typescript
// From cdp-eval.ts usage patterns
await cdp('Runtime.evaluate', { expression: 'document.title' });

await js`document.title`;  // Equivalent, with automatic IIFE wrapping

```

The `js` tagged template literal preserves source location information while handling the return-value extraction automatically.

## Ref Resolution and Snapshot Lifecycle

After navigation or DOM-mutation operations, the runtime triggers snapshot capture in [`package/ego-browser/src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/ref-map.ts). This rebuilds the **ref-map** that associates numeric refs (e.g., `@21`) with backend node IDs.

When user scripts reference refs:

```typescript
const element = await $`@12`;  // Resolves numeric ref via ref-map

```

[`package/ego-browser/src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts) performs the lookup. If the ref-map is stale, it automatically re-snapshots before resolution. The resolver also classifies errors: transient failures (network, timing) trigger retry, while permanent failures (invalid selectors, detached nodes) propagate immediately.

## Response Handling and Promise Resolution

CDP responses return through the host bridge into `browserRuntime()`, which matches responses to pending promises by message ID. The runtime resolves helper promises with extracted values, completing the asynchronous flow back to the user script.

The event queue maintains history for `observe` introspection and debugging, with automatic eviction at the 10,000-entry cap to prevent unbounded growth.

## Complete Data Flow Diagram

```

stdin
  ↓
src/index.ts (runMain/installEgoSdk) — wrap script, inject bindings
  ↓
src/run.ts (run) — create helperContext()
  ↓
src/helpers.ts — {nav, pointer, keyboard, js, $, observe}
  ↓
src/driver/*.ts — build CDP payloads, handle retries
  ↓
src/browser-runtime.ts — manage session, queue, ego.sendCDPMessage
  ↓
src/cdp-eval.ts (cdp/js) — final protocol serialization
  ↓
[Host bridge: ego.sendCDPMessage] → Embedded browser (CDP)
  ↑
Event queue ← CDP responses/events
  ↑
src/ref-map.ts + src/element-resolver.ts — ref resolution, re-snapshot
  ↑
Helper promises resolve → User script continues

```

## Key Source Files and Responsibilities

| File Path | Primary Responsibility |
|-----------|------------------------|
| [`package/ego-browser/src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts) | CLI entry, stdio handling, script wrapping |
| [`package/ego-browser/src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/run.ts) | Execution orchestration, context injection |
| [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) | Public API surface construction |
| [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts) | CDP session state, host bridge communication |
| [`package/ego-browser/src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/cdp-eval.ts) | Low-level CDP and JS evaluation primitives |
| [`package/ego-browser/src/driver/nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/nav.ts) | Navigation domain implementation |
| [`package/ego-browser/src/driver/pointer.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/pointer.ts) | Mouse input domain implementation |
| [`package/ego-browser/src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/ref-map.ts) | Snapshot and ref-to-nodeID mapping |
| [`package/ego-browser/src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/element-resolver.ts) | Locator/ref resolution, error classification |
| [`package/ego-browser/src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) | Global mutable runtime state |

## Summary

- **stdin JavaScript enters** through [`src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/index.ts), which wraps and prepares scripts for execution
- **Helper context injection** in [`src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/run.ts) and [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) exposes the automation API surface
- **Driver layer transformation** in `src/driver/*.ts` converts intention to CDP semantics with retry logic
- **Browser runtime** in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) manages session state and host bridge communication
- **CDP primitives** in [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts) handle final protocol serialization for raw methods and JS evaluation
- **Ref resolution pipeline** maintains DOM synchronization through snapshots in [`src/ref-map.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ref-map.ts) and [`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts)

## Frequently Asked Questions

### What makes ego-lite's stdin-to-browser pipeline different from Puppeteer or Playwright?

ego-lite eliminates the external browser process entirely. Rather than launching Chrome via Node's child_process, it relies on the host environment's `ego.sendCDPMessage` bridge—making the runtime a pure TypeScript translation layer without OS-level process management. This architecture removes networking overhead between Node and browser but requires the closed-source host to provide the actual CDP-capable instance.

### How does ego-lite handle top-level await in scripts received through stdin?

The `runMain()` function in [`src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/index.ts) automatically wraps incoming scripts in an async IIFE before execution. This transformation occurs before the helper context injection, ensuring that top-level `await` expressions resolve correctly against the injected helper promises without requiring manual async function scoping from users.

### What happens when a ref like `@21` cannot be resolved in the current snapshot?

[`src/element-resolver.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.ts) implements automatic recovery: when a ref lookup misses, it triggers `observe.snapshot()` to rebuild the ref-map from current DOM state, then retries resolution. If the ref remains unresolvable after snapshot refresh, the resolver throws `ElementResolutionError` with `transient: false`, indicating permanent failure rather than a retriable timing issue.

### Where does the 10,000 event queue limit originate, and what happens when exceeded?

The cap is defined in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) within the singleton state managed by `browserRuntime()`. When the event queue exceeds 10,000 entries, oldest events evict via standard array shift behavior. This prevents unbounded memory growth during long-running scripts while preserving recent history for `observe` debugging and event replay scenarios.