
coding-agent-runner
ACP-friendly Node.js SDK for local coding agents: run Codex, Claude Code, Cursor, OpenCode, and Pi with streaming, abort, and session resume.
Install with your AI
Paste into Claude Code, Cursor, or any agent — it reads the repo and wires the tool into your project.
Install and set up coding-agent-runner (npm project) into my current project. Found on https://claudeers.com/coding-agent-runner Repo: https://github.com/yinguangyao/coding-agent-runner Homepage/docs: — Detected install method: npm → npm install coding-agent-runner Category: devtools. Platforms: cli, api, desktop, web. Read the repo's README for exact setup and env vars, then install it and wire it into my project. Claudeers Health Verdict: active; community-verified: false. Confirm the source before running anything.
npm install coding-agent-runner
git clone https://github.com/yinguangyao/coding-agent-runner
// compatibility
| Platforms | cli, api, desktop, web |
|---|---|
| Operating systems | — |
| AI compatibility | claude |
| License | MIT |
| Pricing | open-source |
| Language | TypeScript |
coding-agent-runner
Run local coding-agent CLIs from Node.js through one small Node-only API.
coding-agent-runner wraps the process and protocol details for:
codex: Codex CLI viacodex app-server --listen stdio://claude: Claude Code via nativestream-jsoncursor: Cursor Agent viacursor-agent acpopencode: OpenCode viaopencode acppi: Pi viapi-acp
It is positioned next to ACP-style local agent interoperability, but with a friendlier application API. Instead of making every app deal with provider-specific commands, stdio transports, JSON-RPC shapes, stream-json differences, cancellation, and session ids directly, this package exposes a small SDK surface: pick a provider, pass cwd and prompt, then consume normalized events or a final result.
It does not provide a UI, database, task queue, sandbox, credential manager, or memory layer. It starts local CLI processes, speaks their stdio protocols, normalizes streaming events, and returns turn results that local apps can render or store however they want.
When To Use It
Use this package when you are building a local app, desktop app, daemon, or developer tool that needs to call installed coding agents from Node.js.
Good fits:
- Run Codex or Claude Code from a desktop app.
- Stream Cursor/OpenCode/Pi tool progress into your own UI.
- Detect which coding CLIs are installed on a user's machine.
- Keep a lightweight multi-turn session without adopting a larger agent platform.
Not a fit:
- Browser-only applications.
- Hosted LLM API calls.
- Sandboxed execution by default.
- Provider installation, login, billing, or credential management.
Install
npm install coding-agent-runner
Install and authenticate the underlying CLI separately. For example, codex, claude, cursor-agent, opencode, or pi-acp must be available on PATH.
Runtime requirements:
- Node.js 20 or newer
- ESM project or dynamic
import() - At least one supported provider CLI installed locally
Quick Start
import { runCliAgent } from "coding-agent-runner";
const result = await runCliAgent({
provider: "codex",
cwd: process.cwd(),
model: "gpt-5.5",
prompt: "Inspect this repository and summarize the test command.",
});
console.log(result.output);
runCliAgent() is the simplest API. It creates a provider process, runs one prompt, consumes the stream, closes the process, and returns the final output.
Model Selection
Pass model on the top-level runner options:
await runCliAgent({
provider: "claude",
cwd: process.cwd(),
model: "sonnet",
prompt: "Review the staged diff.",
});
model is passed directly to Codex thread startup and Claude Code's --model flag. ACP providers (cursor, opencode, and pi) expose model switching inconsistently today; when a provider supports a model flag or env var, pass it through spawn.args or spawn.env.
System Prompt
Pass systemPrompt on the top-level runner options:
await runCliAgent({
provider: "codex",
cwd: process.cwd(),
model: "gpt-5.5",
systemPrompt: "You are a concise senior TypeScript reviewer.",
prompt: "Review this repository.",
});
Provider behavior:
- Codex: mapped to app-server
developerInstructions. - Claude: mapped to Claude Code
--append-system-prompt. - ACP providers (
cursor,opencode, andpi): wrapped into the prompt as explicit<system>and<user>blocks because ACP does not currently define a portable system-prompt field.
MCP Servers
Pass local stdio MCP servers with mcpServers when a turn needs extra tools or context:
await runCliAgent({
provider: "claude",
cwd: process.cwd(),
model: "sonnet",
mcpServers: [{
name: "docs",
command: "npx",
args: ["-y", "@acme/docs-mcp"],
env: { DOCS_TOKEN: process.env.DOCS_TOKEN },
}],
prompt: "Use the docs MCP server and review this module.",
});
Provider behavior:
- Codex: mapped to app-server
config.mcp_servers. - Claude: written to a temporary Claude Code
--mcp-configfile. - ACP providers (
cursor,opencode, andpi): passed to ACP session setup as stdio MCP servers.
undefined environment values are dropped before provider config is created.
Skills
Pass local skill references with skills:
await runCliAgent({
provider: "codex",
cwd: process.cwd(),
skills: [{ name: "code-review", path: "/path/to/skills/code-review" }],
prompt: "Review the staged diff.",
});
Provider behavior:
- Codex: sent as structured app-server
skillinput blocks before the user text and also referenced indeveloperInstructionsfor compatibility with CLI builds that do not materialize ad-hoc skills automatically. - Claude and ACP providers: added to the system prompt as explicit skill references, instructing the provider to read the skill path when relevant.
Streaming
import { streamCliAgent } from "coding-agent-runner";
for await (const event of streamCliAgent({
provider: "claude",
cwd: process.cwd(),
prompt: "Add tests for the auth module.",
})) {
if (event.type === "text_delta") process.stdout.write(event.text);
if (event.type === "tool_start") console.log("tool:", event.name);
}
streamCliAgent() is still one-shot, but it yields normalized progress events as the provider runs.
Stateful Multi-Turn Runner
import { createCodingAgentRunner } from "coding-agent-runner";
const runner = await createCodingAgentRunner({
provider: "cursor",
cwd: "/path/to/project",
});
try {
for await (const event of runner.stream({ prompt: "Inspect the codebase." })) {
console.log(event);
}
for await (const event of runner.stream({ prompt: "Now make the change." })) {
console.log(event);
}
} finally {
await runner.close();
}
The stateful runner keeps the last provider session id in memory and reuses it on the next turn.
Detect Local CLIs
import { detectCliAgents } from "coding-agent-runner";
const agents = await detectCliAgents();
console.table(agents);
Detection is best-effort. It scans PATH, runs each provider's version command with a timeout, and does not try to log in or mutate provider state.
Command Overrides
import { runCliAgent } from "coding-agent-runner";
await runCliAgent({
provider: "opencode",
cwd: process.cwd(),
prompt: "Summarize this package.",
spawn: {
command: "/custom/bin/opencode",
args: ["acp"],
},
});
Use command overrides when your host app bundles a CLI adapter or stores it outside PATH.
Cancellation
import { runCliAgent } from "coding-agent-runner";
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(new Error("Timed out")), 30_000);
try {
await runCliAgent({
provider: "claude",
cwd: process.cwd(),
prompt: "Run the test suite and fix failures.",
signal: controller.signal,
});
} finally {
clearTimeout(timeout);
}
Cancellation is best-effort and provider-specific. The package forwards the abort signal to the active transport and cleans up child processes it owns.
Wrappers And Sandboxes
This package does not sandbox provider processes. If you need isolation, pass a wrapper command:
await runCliAgent({
provider: "codex",
cwd: process.cwd(),
prompt: "Inspect this repository.",
spawn: {
wrapper: {
command: "sandbox-exec",
args: ["-f", "/path/to/profile.sb"],
},
},
});
The wrapper is invoked as:
<wrapper.command> <wrapper.args...> <provider.command> <provider.args...>
Events
The friendly streaming API emits:
type CodingAgentEvent =
| { type: "text_delta"; text: string }
| { type: "thinking_start"; id: string }
| { type: "thinking_delta"; text: string }
| { type: "thinking_end"; id: string }
| { type: "tool_start"; id: string; name: string; input?: unknown }
| { type: "tool_update"; id: string; name: string; input?: unknown; output?: string }
| { type: "tool_end"; id: string; name: string; output: string; isError: boolean }
| { type: "done"; output: string; sessionId: string | null; stopReason: string }
| { type: "error"; error: Error };
Lower-Level API
The package also exports the lower-level building blocks:
runAgentTurn()for the existing one-shot provider dispatcherrunCodexTurn()andacquireCodexAppServer()runClaudeNative()AcpConnectionmapStreamEventToAgentEvents()buildDefaultSpawn()andlistProviderConfigs()
Lower-level APIs use the internal provider ids: codex-cli, claude-code-cli, cursor-cli, opencode-cli, and pi-cli.
Provider IDs
Friendly APIs accept short provider ids:
| Public id | Default command | Internal id |
|---|---|---|
codex | codex app-server --listen stdio:// | codex-cli |
claude | claude -p --output-format stream-json --input-format stream-json --verbose | claude-code-cli |
cursor | cursor-agent acp | cursor-cli |
opencode | opencode acp | opencode-cli |
pi | pi-acp | pi-cli |
Safety
This library does not sandbox provider processes. The spawned CLI runs with the working directory, environment, credentials, and filesystem permissions you give it. If you need isolation, pass a wrapper command in spawn options or run this package inside your own sandbox/container.
Claude Code permission bypass is not enabled by default. To add --dangerously-skip-permissions, call runClaudeNative() with dangerouslySkipPermissions: true.
Development
npm install
npm run typecheck
npm test
npm run build
npm pack --dry-run
npm run check runs typecheck, tests, and build.
Interactive Demo
Use the demo when you want to type real prompts and watch normalized runner events as they happen, similar to inspecting a lightweight TUI trace:
npm run demo -- codex --model gpt-5.5
npm run demo -- claude --model sonnet
npm run demo -- codex --model gpt-5.5 --system-prompt "You are concise"
Inside the demo, type a prompt and press Enter. It prints streamed answer text plus process events such as thinking_start, thinking_delta, thinking_end, tool_start, tool_update, tool_end, done, sessionId, and elapsed time. The same runner instance is reused, so follow-up prompts continue the same session when the provider supports it.
Codex often emits thinking_start and thinking_end lifecycle events without exposing private reasoning text; the demo shows those lifecycle markers instead of inventing hidden chain-of-thought content.
To pass one input and exit:
npm run demo -- codex --model gpt-5.5 --prompt "Reply with exactly DEMO_OK"
Useful demo commands:
/session Show the current provider session id
/cwd Show the working directory
/help Show interactive commands
/exit Quit
For machine-readable traces:
npm run demo -- codex --model gpt-5.5 --prompt "List two test commands" --json-events
Real CLI Smoke Tests
The default test suite uses mocks and protocol fixtures so CI does not need local agent logins. To verify a real installed provider on your machine, run the manual smoke scripts:
npm run smoke:claude
npm run smoke:codex
npm run smoke:cursor
npm run smoke:opencode
npm run smoke:pi
Each smoke script builds the package, starts the real local CLI, checks streaming output, verifies one session resume turn, and checks cancellation. The scripts require the underlying CLI to be installed, authenticated, and available on PATH.
To run only the streaming check:
npm run smoke:claude -- --stream-only
To run with an explicit model for Codex or Claude:
npm run smoke:claude -- --model sonnet
npm run smoke:codex -- --model gpt-5.5
To test a CLI outside PATH, pass a command override:
CAR_OPENCODE_COMMAND=/custom/bin/opencode npm run smoke:opencode
PI_ACP_BIN=/custom/bin/pi-acp npm run smoke:pi
Real Feature Smoke Tests
Use these when you want a real, non-mocked check for higher-level runner options:
npm run smoke:real
npm run smoke:real:codex
npm run smoke:real:claude
smoke:real defaults to Codex and Claude Code. It builds the package, starts the real installed CLI, and verifies:
systemPromptexact-token behaviorskillsbehavior through a temporary localSKILL.md
The script requires the selected CLIs to be installed, authenticated, and available on PATH. You can tune models and providers:
npm run smoke:real -- --providers codex,claude --codex-model gpt-5.5 --claude-model sonnet
npm run smoke:real -- --providers cursor,opencode,pi --skip-missing
To include a real MCP server config in the smoke run:
npm run smoke:real:claude -- \
--mcp-name docs \
--mcp-command npx \
--mcp-args-json '["-y","@acme/docs-mcp"]' \
--mcp-prompt "Use the docs MCP server and reply with DOCS_OK" \
--mcp-expected DOCS_OK
License
MIT
// faq
What is coding-agent-runner?
ACP-friendly Node.js SDK for local coding agents: run Codex, Claude Code, Cursor, OpenCode, and Pi with streaming, abort, and session resume.. It is open-source on GitHub.
Is coding-agent-runner free to use?
coding-agent-runner is open-source under the MIT license, so it is free to use.
What category does coding-agent-runner belong to?
coding-agent-runner is listed under devtools in the Claudeers registry of Claude-compatible tools.
// embed badge
[](https://claudeers.com/coding-agent-runner)
// retro hit counter
[](https://claudeers.com/coding-agent-runner)
// reviews
// guestbook
// related in Developer Tools
The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Curs…
Use Garry Tan's exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA
AI coding assistant skill (Claude Code, Codex, OpenCode, Cursor, Gemini CLI, and more). Turn any folder of code, SQL schemas, R scripts, shell scripts, docs,…
Makes your AI agent think like the laziest senior dev in the room. The best code is the code you never wrote.