# CLI Mode vs Module Mode in ego-browser: How the ego-lite Runtime Works

> Understand ego-browser's CLI mode vs module mode. Learn how the ego-lite runtime executes JavaScript as a standalone program or integrates as a library. Explore the ego-lite runtime.

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

---

**CLI mode executes JavaScript from stdin as a standalone command-line program, while module mode installs SDK helpers onto a target object for programmatic library integration.**

The `ego-browser` package within the [citrolabs/ego-lite](https://github.com/citrolabs/ego-lite) repository offers a dual-mode architecture that determines how browser automation code is executed. Depending on whether you invoke the binary directly or import the package as a dependency, the runtime activates distinct entry points, stdin handling mechanisms, and output strategies.

## Entry Points and Execution Detection

The [`src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/index.ts) file serves as the central router that detects how the package is being consumed. It uses the `isDirectCli()` check to branch between two execution paths.

### CLI Mode: Direct Execution

When the file is executed directly as a command-line program (indicated by `#!/usr/bin/env node`), the runtime enters **CLI mode**. The `isDirectCli()` function returns true, triggering the `runMain()` function sourced from [`src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/run.ts).

According to the source code in [[`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts) lines 56-63](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts#L56-L63), this path reads JavaScript from **STDIN**, wraps it in an async execution context, and sets the process exit code upon completion. This design supports fire-and-forget automation scripts run from shell environments or CI pipelines.

### Module Mode: Library Import

When the package is imported via `import { installEgoSdk } from "ego-browser"`, the runtime falls back to the module branch at [[`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts) lines 64-66](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts#L64-L66). Instead of launching the CLI runner, it calls `installEgoSdk()`, which mounts the SDK helpers onto a target object—by default `globalThis`—without processing stdin or managing process lifecycles.

## How Code is Supplied and Executed

The fundamental difference between CLI mode and module mode lies in how you supply browser automation instructions.

**CLI mode** expects code via stdin. The `runMain()` function in [`src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/run.ts) consumes the entire input stream, injects the helper context built by [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts), and executes the script within a temporary async wrapper. For example:

```bash
echo "await page.goto('https://example.com'); console.log('Done')" | npx ego-browser

```

**Module mode** treats your project as the host. After calling `installEgoSdk()`, the helpers become available as ordinary functions on the target object, allowing you to write standard Node.js code that drives the browser directly:

```javascript
import { installEgoSdk } from "ego-browser";

// Install SDK onto globalThis (default)
installEgoSdk();

// Helpers are now available globally
await page.goto("https://example.com");
console.log("Done");

```

## Output Handling and Lifecycle Differences

CLI mode and module mode diverge significantly in how they handle console output and process lifecycles.

In **CLI mode**, the runtime replaces `console.log` with a buffered sink to ensure logs appear after the script's output rather than interleaving with browser protocol messages. As implemented in [[`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts) lines 75-82](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts#L75-L82), this buffer flushes when the process terminates. The lifecycle is short-lived: `runMain()` executes the stdin payload, sets `process.exitCode`, and terminates the Node process.

In **module mode**, the SDK accepts an optional `cliLog` function via the `installEgoSdk` options parameter. If not provided, it defaults to the same buffered sink, but the host application maintains full control over the logging surface. The SDK remains resident in memory, enabling repeated calls to browser helpers within the same Node runtime—ideal for test harnesses or long-running services.

## Practical Code Examples

Understanding the distinction requires seeing both patterns in practice.

### Running One-Off Scripts with CLI Mode

CLI mode excels at shell automation and ephemeral tasks. The [`src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/run.ts) implementation handles stdin reading and context injection automatically:

```bash

# Execute navigation via pipe

cat <<'EOF' | npx ego-browser
await page.goto('https://github.com/citrolabs/ego-lite');
const title = await page.title();
console.log(`Page title: ${title}`);
EOF

```

### Integrating as a Node.js Library

Module mode suits library integration. The `installEgoSdk()` function exposes the same helpers without stdin processing:

```javascript
import { installEgoSdk } from "ego-browser";
import { createServer } from "http";

// Install with custom logging target
installEgoSdk(globalThis, { 
  cliLog: (msg) => process.stdout.write(`[EGO] ${msg}\n`) 
});

// Reusable browser automation function
export async function scrapeUrl(url) {
  await page.goto(url);
  return await page.content();
}

```

## Summary

- **CLI mode** activates when executing the package directly, reads JavaScript from **stdin** via `runMain()` in [`src/run.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/run.ts), buffers console output, and exits the process after execution.
- **Module mode** activates when importing the package, calls `installEgoSdk()` to mount helpers onto `globalThis` or a custom target, and allows programmatic control without stdin processing.
- The detection logic resides in [`src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/index.ts), which branches based on `isDirectCli()` between lines 56-66.
- CLI mode suits one-off shell scripts and CI pipelines; module mode suits library authors and test frameworks requiring persistent browser sessions.

## Frequently Asked Questions

### Can I use ego-browser in both modes simultaneously within the same process?

No. The entry point in [`src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/index.ts) executes an exclusive branch: if `isDirectCli()` returns true, it immediately invokes `runMain()` and never exports the module interface. Conversely, importing the package as a module skips the CLI runner entirely. You must choose one activation pattern per Node process.

### How does ego-browser detect whether to run in CLI or module mode?

The runtime checks if the file is being executed directly using `isDirectCli()`. When the shebang (`#!/usr/bin/env node`) triggers direct execution, the condition evaluates true and the code path calls `runMain()`. When imported via `require()` or `import`, the condition fails, falling through to `installEgoSdk()` as shown in [[`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts) lines 64-66](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts#L64-L66).

### What happens to console.log output in CLI mode vs module mode?

In CLI mode, `console.log` is replaced with a buffered sink (implemented in [[`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts) lines 75-82](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts#L75-L82)) that flushes upon process exit to prevent log interleaving. In module mode, logging defaults to the same buffered behavior unless you override it by passing a custom `cliLog` function to `installEgoSdk()`, giving you explicit control over the output destination.

### Is there a performance difference between CLI mode and module mode?

Yes. CLI mode incurs the overhead of process startup and stdin stream processing for each execution, making it suitable for discrete tasks. Module mode maintains the SDK in-process, eliminating startup overhead for repeated automation calls and allowing connection reuse, which significantly improves performance in iterative scenarios such as test suites or web scraping applications.