How to Integrate ego-browser in a Host Application: The Complete SDK Embedding Pattern
Call the installEgoSdk function from the ego-browser npm package to inject browser automation helpers directly into your application's scope, enabling seamless programmatic control without spawning a separate CLI process.
The ego-browser SDK from citrolabs/ego-lite provides a library-style integration that lets host applications embed browser automation capabilities directly. Unlike CLI-based usage, this pattern injects all helpers—openOrReuseTab, click, snapshotText, and more—onto a target object so your code can invoke them as native functions.
Installing the SDK in Your Host Application
The entry point for all embedding scenarios is src/index.ts. Import installEgoSdk and call it with a target object and optional configuration:
// host-app.js (Node)
import { installEgoSdk } from 'ego-browser';
function hostLogger(...args) {
console.info('[ego]', ...args);
}
// Install onto globalThis for universal access
installEgoSdk(globalThis, { cliLog: hostLogger });
Once installed, all helpers become available on the target without additional imports. This matches the ergonomics agents experience when writing CLI heredoc scripts.
How installEgoSdk Works Internally
The installEgoSdk function in src/index.ts performs six sequential operations:
| Step | Implementation | Purpose |
|---|---|---|
| Create helper context | Calls helpers.helperContext() from src/helpers.ts |
Gathers all public helpers and their JSDoc metadata |
| Wrap with ready signal | Wraps each helper to await an optional ready promise |
Ensures helpers only execute after browser runtime initialization |
| Inject onto target | Uses Object.defineProperty with enumerable: false |
Adds helpers as non-enumerable properties to avoid pollution |
| Bind output sink | Replaces console.log via createBufferedLog |
Routes helper output to cliLog or a default buffer |
| Expose ego runtime | Attaches target.ego.helpers and target.ego.learnings |
Enables low-level CDP access when needed |
| Prevent double-wrapping | Marks runtime with EGO_WRAPPED symbol |
Skips re-wrapping createTab and task-space APIs on subsequent calls |
This architecture lets you treat ego-browser as an in-process library rather than an external tool.
Global Scope vs. Dedicated Namespace Integration
Global Scope Pattern
For maximum convenience—especially when migrating existing CLI scripts—install onto globalThis:
import { installEgoSdk } from 'ego-browser';
installEgoSdk(globalThis, { cliLog: console.log });
async function demo() {
const task = await useOrCreateTaskSpace('demo-space');
await openOrReuseTab('https://example.com', { wait: true });
const text = await snapshotText();
console.log('Extracted:', text);
}
demo();
Dedicated Namespace Pattern
For type safety and isolation, use a custom object:
import { installEgoSdk } from 'ego-browser';
const ego = {};
installEgoSdk(ego, { cliLog: console.log });
await ego.openOrReuseTab('https://example.com');
await ego.click('button.primary');
Both approaches preserve identical helper semantics; only the binding target differs.
TypeScript Service Integration Example
For production services requiring clean types and structured logging:
// service.ts
import { installEgoSdk } from 'ego-browser';
import { logger } from './logging';
export const ego = {} as Record<string, unknown>;
installEgoSdk(ego, {
cliLog: (...args) => logger.info('[ego]', ...args),
});
export async function browseAndExtract(url: string): Promise<string> {
const space = await ego.useOrCreateTaskSpace!('extract-data');
await ego.openOrReuseTab!(url, { wait: true });
const text = await ego.snapshotText!();
await ego.completeTaskSpace!(space.id, { keep: false });
return text as string;
}
Type assertions (!) or a proper interface definition can eliminate the need for as casts.
Serverless Function Pattern
For ephemeral execution environments where global state must not persist between invocations:
// lambda.js
import { installEgoSdk } from 'ego-browser';
export async function handler(event) {
const ego = {}; // Fresh target per invocation
installEgoSdk(ego, { cliLog: console.log });
const { url } = JSON.parse(event.body);
await ego.useOrCreateTaskSpace('lambda-run');
await ego.openOrReuseTab(url, { wait: true });
const result = await ego.snapshotText();
await ego.completeTaskSpace('lambda-run', { keep: false });
return {
statusCode: 200,
body: JSON.stringify({ text: result })
};
}
This pattern ensures complete isolation between concurrent function executions.
Key Source Files for Integration Reference
| File | Role | Link |
|---|---|---|
src/index.ts |
installEgoSdk implementation, helper injection, output sink binding |
View source |
src/helpers.ts |
Public helper definitions (snapshotText, click, fill, etc.) |
View source |
src/browser-runtime.ts |
Low-level CDP transport and session management | View source |
src/ego-errors.ts |
Custom error types (ElementResolutionError, etc.) |
View source |
Summary
- Single entry point: Import
installEgoSdkfromego-browser(src/index.ts) - Flexible targeting: Install onto
globalThisfor convenience or a custom object for isolation - Non-enumerable injection: Helpers attach without polluting property enumerations
- Ready-signal wrapping: Automatic synchronization with browser runtime initialization
- Configurable logging: Route helper output through
cliLogto integrate with your logging stack - Low-level access: Reach
target.ego.helpersandtarget.ego.learningsfor CDP-level operations - Idempotent installation:
EGO_WRAPPEDsymbol prevents harmful double-wrapping
Frequently Asked Questions
What is the minimum code required to embed ego-browser?
Import installEgoSdk and call it with any target object. The shortest valid integration is:
import { installEgoSdk } from 'ego-browser';
installEgoSdk(globalThis);
await openOrReuseTab('https://example.com');
This assumes the default buffered logger is acceptable.
Can I use ego-browser without polluting global scope?
Yes. Pass a dedicated object as the first argument to installEgoSdk:
const ego = {};
installEgoSdk(ego);
// All helpers available as ego.openOrReuseTab, etc.
This pattern is recommended for library authors and test suites.
How does helper output get captured by my application?
Supply a cliLog function in the options object. This replaces the default buffered sink:
installEgoSdk(globalThis, {
cliLog: (level, ...args) => myLogger.log(level, args.join(' '))
});
According to src/index.ts, all console.log calls inside helpers route through this sink.
What happens if I call installEgoSdk twice on the same target?
The function checks for the EGO_WRAPPED symbol on the target's ego property and returns early if already present. Mutating methods like createTab are not double-wrapped, preventing subtle bugs in re-entrant scenarios.
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 →