# How ego‑lite's installEgoSdk Injects Helper Functions into the Global Scope

> Discover how ego-lite's installEgoSdk injects helper functions into the global scope. Learn about readiness logic and Object.defineProperty for non-enumerable descriptors. Understand ego-lite's inner workings.

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

---

**`installEgoSdk` creates a helper context, wraps every function with readiness logic, and attaches each helper to `globalThis` using `Object.defineProperty` with non‑enumerable descriptors.**

The `ego-lite` browser automation SDK exposes dozens of helper functions—`navigate`, `click`, `snapshot`, `page.title`, and more—directly to agent scripts without requiring explicit imports. This global injection is orchestrated by the `installEgoSdk` function in [`package/ego-browser/src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts). According to the citrolabs/ego-lite source code, the process combines context generation, asynchronous readiness handling, and careful property definition to produce a clean, testable API.

## What installEgoSdk Does

At its core, `installEgoSdk` transforms a target object (typically `globalThis`) into a populated runtime environment. The function accepts two parameters:

- `target` – the object to decorate (defaults to `globalThis`)
- `options` – configuration including a `ready` promise for initialization gating

The typical call pattern appears throughout the codebase:

```typescript
import { installEgoSdk } from 'ego-browser';

// Default installation on globalThis
installEgoSdk(globalThis, { ready: cdpConnectionPromise });

```

## Step 1: Building the Helper Context

Before any injection occurs, `installEgoSdk` generates the full set of available helpers by calling `helperContext()` from [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts). This function returns a structured object containing:

- **Page helpers**: `navigate`, `goBack`, `reload`, `screenshot`
- **Interaction helpers**: `click`, `type`, `select`, `scroll`
- **Observation helpers**: `snapshot`, `query`, `waitFor`
- **Namespace objects**: `page`, `taskSpace`, `observe` with nested methods

The `helperContext` implementation spans lines 822–862 in [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts), dynamically assembling the helper tree based on available protocol capabilities.

## Step 2: Wrapping Helpers with Readiness Logic

Not all helpers execute immediately. If the SDK awaits a CDP connection or browser initialization, every asynchronous helper must pause until the `ready` promise resolves. `installEgoSdk` handles this through the `wrapReady` utility (lines 19–38 in [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts)):

```typescript
// Simplified excerpt from package/ego-browser/src/index.ts
function wrapReady<T>(fn: T, ready: Promise<void>, isFactory = false): T {
  if (typeof fn !== 'function') {
    // Recurse into namespace objects
    return Object.fromEntries(
      Object.entries(fn).map(([k, v]) => [k, wrapReady(v, ready, SYNC_HELPERS.has(k))])
    ) as T;
  }
  
  return (async (...args: unknown[]) => {
    await ready;           // Wait for initialization
    return (fn as Function)(...args);
  }) as T;
}

```

The wrapper distinguishes between:
- **Synchronous helpers** (`SYNC_HELPERS` set) – exposed directly without wrapping
- **Synchronous factory helpers** (`SYNC_FACTORY_HELPERS` set) – wrapped with `isFactory: true` to control argument evaluation timing
- **Asynchronous helpers** – fully wrapped to await the readiness promise and propagate initialization errors

## Step 3: Defining Properties on the Target

With wrapped helpers prepared, `installEgoSdk` iterates the context entries and attaches each to the target object (lines 44–73 in [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts)):

```typescript
const target = providedTarget ?? globalThis;

for (const [name, value] of Object.entries(helperContext())) {
  const isSync = SYNC_HELPERS.has(name) || SYNC_FACTORY_HELPERS.has(name);
  const finalValue = isSync ? value : wrapReady(value, options.ready);
  
  Object.defineProperty(target, name, {
    value: finalValue,
    writable: true,
    configurable: true,
    enumerable: false   // Hides from for...in loops
  });
}

```

The **non‑enumerable** descriptor is deliberate—helpers don't appear in object enumerations, reducing noise when scripts inspect `globalThis` or log variables.

## Step 4: Buffering Console Output

The SDK intercepts `console.log` to capture script output for host consumption (lines 76–84 in [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts)):

```typescript
const originalLog = console.log;
const logBuffer: unknown[] = [];

console.log = (...args: unknown[]) => {
  logBuffer.push(args);
  originalLog.apply(console, args);
};

// Expose flush mechanism
(target as any).flushLogs = () => {
  const logs = logBuffer.splice(0, logBuffer.length);
  return logs;
};

```

This redirection ensures that even early `console.log` calls before host attachment are preserved and can be flushed on demand.

## Step 5: Integrating with the ego Runtime

When `installEgoSdk` detects an existing `ego` object on the target (common when running inside the full ego runtime), it performs additional setup (lines 87–104 in [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts)):

```typescript
if (target.ego) {
  // Emit browser version information
  emitUpdateNotice(target.ego);
  
  // Store reference to helpers for runtime introspection
  target.ego.helpers = helperContext();
  
  // One‑time wrapping of mutating methods
  if (!target[EGO_WRAPPED]) {
    target[EGO_WRAPPED] = true;
    
    const originalCreateTab = target.ego.createTab;
    target.ego.createTab = async (...args: unknown[]) => {
      const tab = await originalCreateTab(...args);
      // Re‑inject helpers into new tab's context
      installEgoSdk(tab, { ready: options.ready });
      return tab;
    };
  }
}

```

The `EGO_WRAPPED` symbol prevents double‑wrapping if `installEgoSdk` is called multiple times during the same session.

## Complete Usage Example

```typescript
import { installEgoSdk } from 'ego-browser';

// Standard installation
installEgoSdk(globalThis, { 
  ready: connectToBrowser() 
});

// Helpers are now globally available
await navigate('https://example.com');
await click('button#submit');

const title = await page.title();
console.log(`Loaded: ${title}`);  // Captured by buffered sink

// Custom target for isolated testing
const sandbox: Record<string, unknown> = {};
installEgoSdk(sandbox, { ready: Promise.resolve() });

await (sandbox.navigate as Function)('https://test.local');

```

## Key Design Decisions in the Injection Mechanism

| Aspect | Implementation | Rationale |
|--------|---------------|-----------|
| **Default target** | `globalThis` | Maximizes convenience for agent scripts |
| **Property descriptors** | `writable: true`, `configurable: true`, `enumerable: false` | Allows mutation and cleanup while hiding from enumeration |
| **Readiness wrapping** | Per‑helper async wrappers | Fine‑grained error propagation; sync helpers remain unwrapped for performance |
| **Console interception** | Buffer + flush pattern | Reliable log capture without breaking existing code |
| **EGO_WRAPPED symbol** | Guard property on target | Idempotent installation prevents method stacking |

## Summary

- `installEgoSdk` generates helpers via `helperContext()` from [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) and attaches them to a target object, defaulting to `globalThis`.
- Asynchronous helpers are wrapped with readiness logic through the `wrapReady` utility, ensuring the SDK only executes after initialization completes.
- Properties are defined with `writable`, `configurable`, and `enumerable: false` descriptors for a clean, non‑polluting global API.
- Console output is buffered for host retrieval, and integration with the full `ego` runtime adds version notices and tab‑aware re‑injection.

## Frequently Asked Questions

### Can I install ego‑lite helpers on an object other than globalThis?

Yes. Pass any object as the first argument to `installEgoSdk`. This is commonly used in tests to create isolated sandbox environments without modifying the real global scope.

### Why are the injected helpers non‑enumerable?

The `enumerable: false` descriptor prevents helpers from appearing in `for…in` loops and `Object.keys()` results. This reduces noise when agent scripts iterate over variables or log the global object, while still allowing direct access by name.

### What happens if installEgoSdk is called multiple times?

The function is idempotent. The `EGO_WRAPPED` symbol guards against double‑wrapping runtime methods, and `Object.defineProperty` silently overwrites existing properties (since `configurable: true`). The helpers are re‑injected fresh each time, which is useful when spawning new browser tabs.