# How Helper Functions Are Injected Into Agent Scripts in Ego‑Lite

> Discover how Ego-Lite injects helper functions into agent scripts. Learn about the two-step process involving helperContext() and installEgoSdk() for global availability.

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

---

**Ego‑Lite injects helper functions into agent scripts through a two‑step process: `helperContext()` creates a unified API object in [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts), then `installEgoSdk()` copies these helpers onto `globalThis` in [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts), making them globally available without imports.**

Agent scripts in Ego‑Lite run in isolated execution environments where they need immediate access to browser automation tools like page control, networking, and CDP commands. The framework solves this by pre‑populating the global scope with a curated set of helpers rather than requiring explicit imports. This article explains exactly how that injection mechanism works, tracing the code from source to runtime.

---

## The Two‑Step Injection Architecture

Ego‑Lite's helper injection follows a clean separation between **definition** and **installation**. Understanding both phases is essential for debugging, extending, or bypassing the default behavior.

### Step 1: Building the Helper Context in [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts)

The `helperContext()` function in [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) serves as the single source of truth for all public APIs available to agents. It constructs and returns a plain object containing facades for every major subsystem:

```typescript
export function helperContext(extra: any = {}) {
  const all = {
    page: createPageFacade(),
    browser: createBrowserFacade(),
    taskSpaces: createTaskSpacesFacade(),
    site: createSiteFacade(),
    fetch: { server: serverFetch, browser: browserFetch },
    cdp,
    ...extra,
  };
  return {
    ...all,
    help: (...names) => { /* runtime help() implementation */ }
  };
}

```

Key properties available through this context include:

- **`page`** – Wrapper around Playwright's `Page` for navigation, clicking, and DOM queries
- **`browser`** – Browser‑level operations (new pages, contexts, etc.)
- **`taskSpaces`** – Workspace and task management utilities
- **`site`** – Site‑specific configurations and helpers
- **`fetch`** – Isomorphic fetch with `server` and `browser` variants
- **`cdp`** – Direct Chrome DevTools Protocol access
- **`help()`** – Runtime introspection for available helpers

The `extra` parameter allows external callers to augment the context with custom helpers before injection.

### Step 2: Installing Onto the Target Runtime in [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts)

The `installEgoSdk()` function in [`package/ego-browser/src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts) takes the context from step 1 and grafts it onto a target object—typically `globalThis`:

```typescript
export function installEgoSdk(
  target: InstallTarget = globalThis,
  options: InstallEgoSdkOptions = {}
) {
  const context = options.context || helpers.helperContext();   // step 1
  for (const [name, value] of Object.entries(context)) {
    const exposed = SYNC_HELPERS.has(name)
      ? value
      : wrapReady(value, readySignal, () => readyError, [name]);
    Object.defineProperty(target, name, {
      value: exposed,
      writable: true,
      configurable: true,
      enumerable: false,
    });
  }
  // expose the helpers on ego (if present) and clean up legacy globals
  if (target.ego && typeof target.ego === "object") {
    target.ego.helpers = installed;
  }
}

```

Critical implementation details:

1. **Property descriptors** – Helpers are installed with `enumerable: false` to avoid cluttering `Object.keys()` iterations
2. **Async safety** – Non‑sync helpers are wrapped with `wrapReady()` to defer execution until the runtime signals readiness
3. **Legacy cleanup** – The function removes stale global helpers before installation to prevent API drift
4. **Dual exposure** – Helpers become available both as direct properties of `target` and (if `ego` exists) via `target.ego.helpers`

---

## How the CLI Triggers Injection for Heredoc Scripts

When running scripts via the Ego‑Lite CLI, the injection happens in [`package/ego-browser/src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/run.ts) through the `executionContext()` function. This code path shows the practical application of the two‑step architecture:

```typescript
const context = helpers.helperContext(agentHelpers);
Object.assign(globalThis, context);

```

The `agentHelpers` parameter allows per‑agent customization of the helper set. After this assignment, any code evaluated in the same realm can reference `page`, `browser`, `taskSpaces`, and all other helpers directly:

```javascript
await page.goto('https://example.com');
console.log(await page.title());

```

No `import`, `require`, or module declaration is needed—the identifiers exist in the global lexical environment.

---

## Practical Code Examples

### Running a Script via CLI (Automatic Injection)

The simplest way to execute an agent script with full helper access:

```bash
echo "await page.goto('https://example.com'); console.log(await page.title());" | \
  node ./package/ego-browser/dist/out/index.js

```

The CLI handles `installEgoSdk()` invocation internally before evaluating the piped script.

### Manual SDK Installation in Node.js

For testing or custom orchestration, install helpers explicitly:

```javascript
import { installEgoSdk } from './package/ego-browser/src/index.js';

// Install helpers on the global object.
installEgoSdk();

// Helpers are now available globally.
await page.goto('https://example.com');
const title = await page.title();
console.log(title);

```

This pattern is useful when embedding Ego‑Lite into larger Node.js applications.

### Extending Context with Custom Helpers

Inject additional functionality alongside the standard API:

```javascript
function myHelper(msg) {
  console.log('My helper says:', msg);
}

// Import helperContext to customize the context.
import { helperContext } from './package/ego-browser/src/helpers.js';
import { installEgoSdk } from './package/ego-browser/src/index.js';

// Install with an extra helper.
installEgoSdk(globalThis, { context: helperContext({ myHelper }) });

myHelper('hello');   // works alongside page, browser, …

```

The `...extra` spread in `helperContext()` ensures custom helpers merge seamlessly with built‑ins.

---

## Helper Injection vs. Module Imports: Design Rationale

Ego‑Lite's global injection approach differs from conventional JavaScript module patterns. The design prioritizes:

| Concern | Global Injection Approach |
|--------|---------------------------|
| **Script portability** | Agents run unchanged across CLI, serverless, and embedded contexts |
| **Reduced ceremony** | No boilerplate imports for every script |
| **Sandbox isolation** | Helpers bind to a fresh `globalThis` per agent, preventing cross‑agent pollution |
| **Dynamic extensibility** | Runtime helper modification via `extra` parameter |

The trade‑off is intentional namespace pollution of `globalThis`. Ego‑Lite mitigates this by using `enumerable: false` and scoped execution contexts.

---

## Summary

- **`helperContext()` in [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts)** builds the complete API surface as a plain object with `page`, `browser`, `taskSpaces`, `site`, `fetch`, and `cdp`
- **`installEgoSdk()` in [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts)** copies these properties onto any target object, defaulting to `globalThis` with non‑enumerable, configurable descriptors
- **[`run.ts`](https://github.com/citrolabs/ego-lite/blob/main/run.ts) execution** spreads the context onto `globalThis` before evaluating agent scripts, enabling direct helper access without imports
- **Extensibility** comes through the `extra` parameter in `helperContext()` and the `context` option in `installEgoSdk()`

---

## Frequently Asked Questions

### What happens if a script defines its own `page` variable?

The injection uses `writable: true` in the property descriptor, so a script can reassign `page` without throwing. However, this shadows the Ego‑Lite facade for that scope. To preserve access, reference `globalThis.page` explicitly or avoid top‑level declarations of conflicting names.

### Can helpers be injected into a non‑global scope?

Yes. Pass any object as the `target` parameter to `installEgoSdk()`. This is useful for testing or when running multiple isolated agents in the same Node.js process with separate helper sets.

### How does Ego‑Lite prevent helpers from leaking between concurrent scripts?

Each script execution creates a fresh context via `executionContext()` in [`run.ts`](https://github.com/citrolabs/ego-lite/blob/main/run.ts), which calls `helperContext()` independently. The helpers are bound to that script's `globalThis`, not a shared singleton. No cross‑script state persists in the helper objects themselves.

### Is the `cdp` helper injected differently from `page` or `browser`?

No. All helpers pass through the same installation path in `installEgoSdk()`. The `SYNC_HELPERS` set determines whether a helper gets wrapped with `wrapReady()` for async initialization safety, but this is the only differential treatment in the injection pipeline.