How Ego-Lite Handles JavaScript Execution from stdin for Agents: A Technical Deep Dive
TLDR: Ego-Lite's ego-browser CLI reads agent JavaScript from stdin (or a stdinText option), wraps it in an isolated async function with a prebuilt helper context, and executes it while buffering all output for reliable, deterministic agent runs.
Ego-lite is an open-source browser automation toolkit that lets AI agents drive real browsers through a simple JavaScript API. A central feature is the ability to run agent-supplied JavaScript from stdin, giving LLM-driven workflows a clean, scriptable entry point. According to the citrolabs/ego-lite repository, this mechanism is implemented in package/ego-browser/src/run.ts and involves a multi-step pipeline that ensures every script runs in a fresh, isolated context with access to the same helper API as the embedded SDK.
The stdin Execution Pipeline in ego-lite
When you pipe JavaScript into ego-browser, the CLI doesn't just eval the code. It orchestrates a controlled execution environment through the runMain function. Here's how it works step by step.
1. CLI Argument Parsing
runMain first parses command-line flags like --help, --doctor, and --reload. If none are present, it proceeds to read the actual agent script from standard input. This happens early in the function (lines 61–71 in src/run.ts).
2. Reading the Script from stdin or a String
The script source is obtained either from an explicit stdinText option (for programmatic SDK use) or by consuming the entire stdin stream via a readAll utility. This dual-path approach means you can use ego-lite both as a shell tool and as a Node.js library.
3. Preparing the Execution Environment
Before running any agent code, ego-lite prepares a helper context. The executionContext() function loads the agent-facing helpers with helpers.loadAgentHelpers and assembles a public helper surface via helpers.helperContext. These helpers (like page, browser, goto, etc.) are then merged onto globalThis, so the agent script can call them directly without any imports.
4. Building an Isolated Async Function
The critical step is constructing a new function using the AsyncFunction constructor. Ego-lite builds a function whose parameters are the names of the helpers, and whose body is the supplied code prefixed with "use strict";. This technique:
- Prevents global pollution between runs
- Allows helper names to be scoped as function parameters
- Forces an async context so
awaitworks naturally
5. Executing and Cleaning Up
The constructed function is then invoked with the helper values. Any thrown errors are caught, and after execution ego-lite always stops any active screencast by calling helpers.stopScreencast before flushing buffered output (lines 17–31).
6. Output Handling with Buffering
console.log is overridden inside the script context to route messages through an internal output sink (bufferOutput). This buffered content is later flushed to the real stdout via flushSink once the script finishes or aborts. This guarantees that agent output appears in a correct order and is available even if the script ends abruptly.
Code Examples: Running Ego-Lite Scripts from stdin
The simplest way to use ego-lite's stdin execution is through your shell:
ego-browser <<'JS'
await page.waitForLoadState()
console.log('Page title:', await page.title())
JS
You can use the exact same logic programmatically by passing a stdinText option:
import { runMain } from "ego-browser";
const script = `
await page.waitForLoadState()
console.log('URL:', await page.url())
`;
await runMain({ stdinText: script });
For custom output handling, you can supply your own stdout/stderr streams:
import { runMain } from "ego-browser";
await runMain({
stdinText: `console.log('hello')`,
stdout: { write: (msg) => process.stdout.write("[OUT] " + msg) },
stderr: { write: (msg) => process.stderr.write("[ERR] " + msg) },
});
Key Files Behind the stdin Execution Feature
The execution pipeline touches several source files. Here's a quick mapping:
| File | Role |
|---|---|
src/run.ts |
Core CLI entry point; reads stdin, builds execution context, runs the agent script. |
src/helpers.ts |
Loads and assembles the helper API exposed to agents (helperContext). |
src/output-sink.ts |
Buffers and flushes console output from the agent script. |
src/index.ts |
Re-exports runMain for both CLI and SDK usage. |
Summary
- Ego-lite reads agent scripts from stdin or a property, wraps them in a dedicated
AsyncFunction, and injects a persisted helper context. - The environment is isolated: scripts run with
"use strict"and only see helper names as explicit parameters. - console output is intercepted and buffered, so all logs are captured regardless of how the script exits.
- The same
runMainworks both from theego-browserbinary and as a Node.js import, making it easy to embed agent execution into your own tools.
Frequently Asked Questions
Can I run JavaScript code from a string instead of stdin?
Yes. The runMain function accepts a stdinText option that holds the exact JavaScript source, which is identical to using stdin. This is useful for programmatic SDK usage where you already have the script in memory (see src/run.ts lines 94–99).
How does ego-lite ensure the agent script has access to browser helpers?
Before execution, executionContext loads all agent helpers via helpers.loadAgentHelpers and expose them in a context object. That object is merged into globalThis, but the constructed async function also has each helper as a named parameter, giving scripts multiple ways to reference them.
What happens if the agent script throws an error?
The runMain wrapper catches any errors thrown during execution, cleans up by calling helpers.stopScreencast(), and then flushes whatever buffered output was collected. Errors are logged to stderr, and the exit code is set accordingly, ensuring no silent failures.
Is the output buffered or streamed in real time?
Ego-lite buffers all console output from an agent script with an internal sink (bufferOutput) and flushes it only after the execution finishes or aborts via flushSink. This ensures the agent sees a complete, consistent view of the output in one block.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →