npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

enginebay

v1.5.7

Published

Isolated bays for coding-agent CLIs (OpenCode, Claude Code, Cursor Agent, and later engines).

Readme

enginebay

Isolated bays for coding-agent CLIs.

enginebay runs a host-installed coding agent (OpenCode, Claude Code, Cursor Agent, and Codex) as a library or an interactive CLI: one process, one workspace, one MCP injection, and (for headless bays) a canonical event stream. It keeps the engine's config and session records out of the user's ordinary install, and inherits only provider login from the host.

It is not a product, a session loop, or a sandbox OS. Host applications — orchestrators, eval harnesses, adapters — stay thin.

Status: Interactive launch supports OpenCode, Claude Code, Cursor Agent, and Codex. The canonical headless API (openBay, doctor, BayEvent) supports OpenCode, Claude Code, and Cursor Agent. See docs/design.md.

Why

Each coding CLI stores config, transcripts, and auth in a different place (~/.claude, ~/.config/opencode, ~/.cursor, XDG, Keychain). Wrapping that knowledge inside every product duplicates the same bugs: polluted ~/.config, leaked host GH_TOKEN, missing login after HOME remap, ad-hoc stdout parsers.

enginebay owns that knowledge. A consumer owns when to run, which prompt, which tools, and what to do with events.

What it does

| Concern | enginebay | Consumer | | --- | --- | --- | | Spawn the CLI headless | yes | — | | Launch the native interactive CLI | yes | choose enginebay options and native argv | | Isolate config / session DB | yes | — | | Inherit provider auth from the host | yes | host login (opencode auth, claude login, agent login) | | Inject session-scoped MCP | yes | provide the stdio command | | Canonical events (text, thinking, tools, turn, session) | yes | map to product traces | | Workspace directory | ephemeral temp, named XDG, or a path you pass | choose id vs path; clone / destroy policy | | Day loop, ticks, personality, board | no | yes | | GitHub App token minting | no | pass a token via extraEnv if needed | | OS sandbox (Seatbelt, landlock, microVM) | not in v1 | optional later backend |

Intended use

import { doctor, openBay } from "enginebay";

const check = await doctor("opencode");
if (!check.ok) throw new Error(check.message);

const bay = await openBay({
  engine: "opencode",
  workDir: "/path/to/workspace",
  dataDir: "/path/to/session-data",
  auth: { sourceDir: "/path/to/opencode-auth" },
  model: "provider/model",
  agent: "eval",
  config: { plugins: ["opencode-gemini-auth@latest"] },
  recoveryAttempts: 0, // eval / harness: disable resume restarts (default is 2)
  extraEnv: {
    // Product flags only. Do not set HOME / XDG_* / OPENCODE_CONFIG_CONTENT.
    GH_TOKEN: mintedInstallationToken,
  },
});

for await (const event of bay.run("Read the briefing and set today's goals.", {
  timeoutMs: 120_000,
})) {
  // event.kind: "text" | "thinking" | "tool_call" | "tool_result" | "tokens" | "turn" | "session" | "diagnostic" | "error" | "exit"
  // exit.reason: "ok" | "error" | "abort" | "timeout" (timeout uses code 124)
}

await bay.close();

Each run() starts a fresh CLI process. Conversation continuity across successful turns is the consumer's job (a redrive prompt), not --continue inside the engine.

If the CLI dies mid-turn with a non-critical error and a session id was captured, run() restarts the process and resumes that engine session with a follow-up prompt (Continue.). Without a session id, it fails instead of re-sending the original prompt. That matches how the CLIs actually resume: a new process plus a new user turn on the persisted session, not a replay of a crashed generation.

Classification uses each engine's own error shape first:

  • OpenCode: error.name / error.data.isRetryable
  • Claude Code: result.subtype (error_during_execution vs max-turns / budget)
  • Cursor Agent: non-zero exit + stderr (the stream often has no terminal result on failure)

Auth failures, a missing binary, and abort() are critical; everything else is worth one more process start. A CLI that exits 0 is never resumed, even if the stream carried an error event.

Set recoveryAttempts: 0 on openBay to disable restart; it must be a non-negative integer. recoveryBackoffMs (default 250) delays each resume, multiplied by the attempt number. run(prompt, { timeoutMs }) kills the child the same way as abort() and ends with exit.reason: "timeout" and code: 124. Cancelling the run() iterator kills the child, escalating to SIGKILL if the CLI ignores SIGTERM.

Interactive CLI

The CLI exposes enginebay's common features consistently across every interactive engine:

enginebay opencode \
  --workspace-id my-project \
  --model provider/model \
  --instructions-file ./agent-instructions.md \
  --mcp-command node \
  --mcp-arg /path/to/mcp-proxy \
  --mcp-env API_URL=http://127.0.0.1:8787

enginebay claude --work-dir . --model sonnet -- --verbose
enginebay cursor --workspace-id my-project
enginebay codex --workspace-id my-project -- --search

Arguments before -- configure enginebay; arguments after -- are forwarded unchanged to the native CLI. This explicit boundary prevents future vendor flags from colliding with enginebay options.

Common options:

| Option | Purpose | | --- | --- | | --work-dir <path> | explicit workspace | | --workspace-id <id> | named persistent workspace | | --model <id> | engine model | | --instructions <text> / --instructions-file <path> | session instructions | | --mcp-command, --mcp-arg, --mcp-env, --mcp-name | session-scoped stdio MCP | | --env <KEY[=VALUE]> | copy or set child environment | | --git-committer-name <name> | isolated git identity when a GitHub token is supplied | | --isolation env | isolation backend |

--env NAME and --mcp-env NAME copy a value from the current environment, which avoids putting the secret itself in shell history. Host GH_TOKEN and GITHUB_TOKEN remain excluded unless explicitly supplied with --env.

The child receives terminal input/output directly. Its exit status is returned, and termination signals are forwarded before disposable config is removed. Without --work-dir or --workspace-id, the child uses the current directory.

The equivalent library primitive is:

import { launchEngine } from "enginebay";

const code = await launchEngine({
  engine: "opencode",
  workspaceId: "my-project",
  model: "provider/model",
  instructions: "Follow the tool protocol.",
  args: ["--verbose"],
  mcp: {
    command: process.execPath,
    args: ["/path/to/mcp-proxy"],
    env: { API_URL: "http://127.0.0.1:8787" },
  },
});

The same launchEngine() options apply to OpenCode, Claude Code, Cursor Agent, and Codex. Each driver translates MCP and instructions to its native mechanism without writing instruction files into the workspace. Cursor Agent has no system-prompt flag, so interactive instructions become its initial prompt, as the headless driver similarly prepends them to the run prompt.

Workspaces

openBay always has a cwd. How it is created:

| Call | Location | close() | | --- | --- | --- | | neither workDir nor workspaceId | temp (enginebay-work-*) | deletes it | | workspaceId: "…" | $XDG_DATA_HOME/enginebay/workspaces/<id> (default ~/.local/share/…) | keeps it | | workDir: "/path" | that path (mkdir if needed) | keeps it |

Do not pass both. IDs are a single path segment, NFC, lowercased; unicode is allowed (my-app-ミカ). Isolation dirs (engine config / session DB) stay disposable either way — a named workspace is the coding tree, not the engine's XDG.

Engines

Interactive launching supports OpenCode, Claude Code, Cursor Agent, and Codex. Canonical headless BayEvent streaming currently supports the first three.

| ID | CLI | Notes | | --- | --- | --- | | opencode | opencode | MCP via OPENCODE_CONFIG_CONTENT. Auth under ~/.local/share/opencode. | | claude-code | claude | MCP via --mcp-config --strict-mcp-config. Auth via host claude login / Keychain. | | cursor-agent | cursor-agent (agent) | MCP via isolated CURSOR_CONFIG_DIR/mcp.json + --approve-mcps. Auth via CURSOR_API_KEY or host agent login. | | codex | codex | Interactive launch: MCP and instructions via disposable CODEX_HOME; host auth.json attached. | | gemini | later | |

The CLI must already be on PATH. enginebay does not vendor engine binaries.

Isolation (v1: env)

Default backend remaps XDG / CURSOR_CONFIG_DIR (and engine-specific flags such as OPENCODE_DISABLE_GLOBAL_CONFIG) to a disposable directory, so host ~/.config/opencode, ~/.cursor/mcp.json, and ~/AGENTS.md are neither read nor written. Provider auth is reattached from the host by a narrow, engine-specific path (symlink or kept HOME for Keychain) — not by exposing the whole home directory.

Host GH_TOKEN / GITHUB_TOKEN are removed from the child environment unless the consumer puts them in extraEnv.

OS-level sandboxes (jai, Anthropic srt, Docker sbx) are optional backends, not this package's identity. See design §6.

Install

npm install enginebay
# or
pnpm add enginebay

Git installs (tag or SHA) run prepare so dist/ is built; dist/ is not committed. Requires Node.js 22+ and a TypeScript toolchain from this package's devDependencies:

npm install github:hskksk/enginebay#v1.3.0

npm publishes the built dist/ without a local compile. A GitHub tag may be ahead of the last approved npm stage (see Publish).

Development

pnpm install
pnpm test
pnpm typecheck
pnpm build

Live coding CLIs are not required for unit tests.

Publish

Every merge to main runs semantic-release, which bumps the version from Conventional Commits, creates a GitHub Release, and stages the package on npm (Release workflow). The version stays staged until a maintainer approves it with 2FA.

| PR / commit prefix | Release | | --- | --- | | fix: | patch (e.g. 0.1.0 → 0.1.1) | | feat: | minor (e.g. 0.1.0 → 0.2.0) | | BREAKING CHANGE: or feat!: | major | | Other (chore:, docs:, ci:, …) | patch |

Squash merge is the norm — the merged commit message is usually the PR title, so set the PR title to feat: … / fix: … form.

CI (stage) → maintainer (approve)

  1. The Release workflow runs npm stage publish (OIDC / trusted publishing).
  2. A maintainer reviews the staged package on npmjs.com or the CLI, then approves:
    • Web: npm → enginebay → Staged packages
    • CLI: npm stage list enginebay → npm stage approve <stage-id> (2FA required)

npm stage approve / reject cannot use OIDC. Run them from a local CLI or npmjs.com with 2FA.

Auth uses npm trusted publishers (GitHub Actions OIDC). Provenance is attached at stage time as well (public repo / public package).

Repository

Standalone npm package: unscoped name enginebay, repository hskksk/enginebay.

Documentation

  • Design — goals, API, isolation, events, consumers.

License

UNLICENSED. An OSS license will be chosen separately.