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

codex-app-server-bridge

v0.1.0

Published

TypeScript client / bridge for the local Codex app-server JSON-RPC protocol. Unofficial — does not provide, proxy, store, or share OpenAI credentials.

Readme

codex-app-server-bridge

日本語版は README_jp.md を参照。

TypeScript client / bridge for the local Codex app-server (JSON-RPC).

Unofficial. This project is an unofficial TypeScript client for local Codex app-server. It does not provide, proxy, store, or share OpenAI credentials. Each user must run and authenticate their own local Codex installation. Do not expose your Codex app-server or credentials to other users.

What this is

A TypeScript bridge that drives a local Codex CLI process (codex app-server) over its experimental JSON-RPC protocol. It provides:

  • Low-level CodexJsonRpcClient — direct method access (request / notification / server-initiated request handling, timeout, AbortSignal).
  • Higher-level CodexBridge — drives the initialize handshake, normalizes Codex notifications into a small event union, and routes the seven approval-request flavors through an ApprovalController with a kind-aware default-deny response factory.
  • Approval policy callback (onApprovalRequested) — pass a deterministic policy callback to the bridge to auto-accept routine operations and escalate only the genuinely uncertain ones. This is the design pivot that keeps the LLM out of the yes/no decision path and preserves a structured approval flow.
  • Transportsstdio and WebSocket (Unix-socket is a stub and not yet implemented).
  • Type-safe wrappers for initialize, thread/{start,resume,fork,list,read,archive}, turn/{start,interrupt}, plus a generic request<T>(method, params) escape hatch for everything else.
  • Full generated protocol types (src/protocol/generated/v2/) so any Codex method or notification can be addressed by name.

What this is not

  • Not a hosted Codex backend.
  • Not a credential proxy. The package never reads, stores, or transmits your OpenAI tokens — each user runs and authenticates their own local codex CLI.
  • Not a reimplementation of Codex. We only speak the documented app-server protocol.
  • Not an agent framework. Codex already exposes a complete agent (including internal AgentControl sub-agent spawning); a higher-level framework abstraction on top is redundant, so this package stays at the bridge layer.

Requirements

  • Node.js ≥ 22.13.0
  • Codex CLI ≥ 0.128, authenticated via the official codex login flow.
  • pnpm 10+ for local development.

Installation (planned for npm)

pnpm add codex-app-server-bridge

The package has a single runtime dependency (ws for WebSocket transport). No peer dependencies; nothing to install beyond the package itself.

Quick start

import { CodexBridge, StdioTransport } from "codex-app-server-bridge";

const bridge = new CodexBridge({
    transport: new StdioTransport({ command: "codex", args: ["app-server"] }),
});

await bridge.connect();

const threadResult = (await bridge.startThread({ cwd: process.cwd() } as never)) as {
    thread: { id: string };
};
const threadId = threadResult.thread.id;

for await (const event of bridge.startTurn({
    threadId,
    input: [{ type: "text", text: "Summarize this repository.", text_elements: [] }],
    cwd: process.cwd(),
} as never)) {
    if (event.type === "text-delta" && event.kind === "text") {
        process.stdout.write(event.text);
    } else if (event.type === "approval-requested") {
        bridge.denyApproval(event.requestId);
    }
}

await bridge.close();

Runnable example

  • examples/text-repl-agent/text REPL built directly on the Vercel AI SDK. Demonstrates the recommended approval pattern: a deterministic policy filter plus a forced toolChoice that keeps the LLM out of the yes/no decision path. Supports both stdio and ws transports via an environment variable.

Approval pattern (recommended)

Codex emits a server-initiated approval request every time it wants to run a destructive command, edit a file, etc. The design pivot of this library is to avoid turning those approvals into a free-form chat between the LLM and the user — that pattern fails in practice:

| Approach | How it works | Failure mode | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | ❌ Conversational (anti-pattern) | LLM paraphrases "shall I do this?" as text, user replies yes/no, LLM interprets and calls respond_to_approval | LLM hallucinates "done" without actually calling the function — approval sits unanswered, Codex stalls | | ✅ Structured (recommended) | (1) a deterministic policy filter auto-handles routine cases; (2) on escalation, the LLM is given a one-tool list and tool_choice is pinned to it | LLM cannot reply with plain text — the API itself rejects anything but the forced function call |

examples/text-repl-agent/ demonstrates the recommended pattern end-to-end.

Approval flow details

Codex's app-server sends server-initiated approval requests in seven flavors (the bridge folds the two v1 legacy methods into commandExecution):

| Kind | Server method | Response shape | | ------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------ | | commandExecution | item/commandExecution/requestApproval | { decision: "accept" \| "acceptForSession" \| "decline" \| "cancel" \| object variant } | | fileChange | item/fileChange/requestApproval | { decision: "accept" \| "acceptForSession" \| "decline" \| "cancel" } | | permissions | item/permissions/requestApproval | { permissions, scope, strictAutoReview? } | | toolUserInput | item/tool/requestUserInput | { answers: { ... } } | | mcpElicitation | mcpServer/elicitation/request | { action, content, _meta } | | v1 (legacy) | applyPatchApproval, execCommandApproval | { decision: "approved" \| "approved_for_session" \| "denied" \| "timed_out" \| "abort" } |

CodexBridge supplies a default-deny response automatically when:

  • the consumer calls bridge.denyApproval(requestId), or
  • an approval times out (default 30 s, configurable via CodexBridgeOptions.approvalTimeoutMs), or
  • the bridge is closed while approvals are still pending.

Use bridge.respondToApproval(requestId, response) to send an arbitrary kind-specific response.

Overriding the default behavior with a policy callback

const bridge = new CodexBridge({
    transport: ...,
    onApprovalRequested: async ({ kind, method, params }) => {
        // Auto-accept safe operations.
        if (isSafe(kind, params)) return { decision: "accept" };
        // Auto-refuse banned operations.
        if (isProhibited(kind, params)) return { decision: "refuse" };
        // Anything else: fall back to the normal event path so the host can
        // ask the user.
        return "escalate";
    },
});

Return values:

  • { decision: "accept" } / { decision: "refuse" } — bridge responds immediately, no approval-requested event is emitted.
  • { raw: <kind-specific JSON> } — bridge sends the exact JSON verbatim (advanced escape hatch).
  • "escalate" — bridge emits the event as usual; host responds via respondToApproval.

Transport options

stdio (default, recommended)

Spawns the local codex binary as a child process. No network exposure.

new StdioTransport({ command: "codex", args: ["app-server"], cwd });

WebSocket

WebSocketTransport connects to a separately-started codex app-server --listen ws://HOST:PORT. Loopback hosts (127.0.0.1, localhost, [::1]) are allowed without restriction. Remote hosts are refused by default — set allowRemote: true to override (and warnOnRemote: false to silence the startup warning).

import { WebSocketTransport } from "codex-app-server-bridge";

const transport = new WebSocketTransport({
    url: "ws://127.0.0.1:8089/",
    headers: { Authorization: "Bearer ..." }, // optional
});

Unix socket

Reserved for a follow-up issue; the current release ships a stub that throws CodexTransportError. Removal is also being considered since this transport is not part of the upstream spec — see Issue #1.

Linux sandbox note (Ubuntu 24.04+)

Codex's Linux sandbox uses bubblewrap, and Ubuntu 24.04+ blocks unprivileged user namespaces by default (kernel.apparmor_restrict_unprivileged_userns=1). When this is in effect, the codex app-server process itself fails at startup regardless of which transport you use — the bwrap call is internal to Codex, not to this bridge (stdio mode where this library spawns Codex, and ws mode where you spawn Codex yourself, are both affected equally).

Three workarounds (in order of preference):

  1. Pass -c sandbox_mode=danger-full-access as a Codex launch argument to disable Codex's own bubblewrap fence. Pair with -c approval_policy=on-request to keep a human gate on every command execution (this is what examples/text-repl-agent uses).
    • In stdio mode: new StdioTransport({ command: "codex", args: ["app-server", "-c", "sandbox_mode=danger-full-access", ...] }).
    • In ws mode: pass it on the command line you use to launch Codex yourself: codex app-server --listen ws://... -c sandbox_mode=danger-full-access.
  2. Install a bubblewrap-specific AppArmor profile under /etc/apparmor.d/bwrap (a one-time sudo step) so the kernel allows bwrap to create user namespaces. Once installed, Codex's own sandbox works for both stdio and ws without the -c flag.
  3. Run Codex inside a Docker container with user namespaces enabled, then connect to it via WebSocketTransport from the host.

Logging

The library is quiet by default. All output is either opt-in or overridable; the library itself writes nothing to stdout.

Direct console output (three narrow call sites)

| Site | Output | How to override | | -------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------- | | StdioTransport | [codex-stderr] <line> — forwards the spawned Codex process's stderr line-by-line via console.error | Pass StdioTransportOptions.onStderr to redirect | | WebSocketTransport | A single console.warn on first connection to a non-loopback host | WebSocketTransportOptions.warnOnRemote: false | | Internal fallback | [codex-json-rpc-client] internal error: ... (only fires on internal invariant breakage) | Not user-overridable; should not happen in practice |

In typical use only Codex's own stderr is visible.

Structured audit log (opt-in)

Pass a CodexAuditLogger to the bridge to receive structured events covering the connection lifecycle, thread / turn lifecycle, every approval request and decision, and policy-filter outcomes. The default is a no-op logger.

interface CodexAuditEvent {
    timestamp?: string;
    level: "info" | "warn" | "error";
    source: string; // "bridge", "transport", "codex-stderr", ...
    message: string;
    threadId?: string;
    turnId?: string;
    approvalKind?: ApprovalKind;
    approvalDecision?: CodexApprovalDecision;
    errorCode?: number;
    errorMessage?: string;
    payload?: Record<string, unknown>;
}

File-based JSONL logger (with credential redaction)

import { CodexBridge, createFileAuditLogger } from "codex-app-server-bridge";

const bridge = new CodexBridge({
    transport,
    auditLogger: createFileAuditLogger("./codex-audit.jsonl"),
});
  • Well-known credential-like keys (token, Authorization, OPENAI_*, password, secret, env, ...) are redacted before serialization.
  • JSONL output is easy to slice with jq.
  • The path argument is required to prevent accidental writes to unexpected locations.

Pipe into your own logger

import pino from "pino";
const log = pino();

new CodexBridge({
    transport,
    auditLogger: {
        log: (event) => log[event.level](event, event.message),
    },
});

pino, winston, an OpenTelemetry collector, or any other host-side logger plugs in here — no changes to the library.

Design intent

  • The library stays quiet; the host wires up its own logger.
  • The library never writes to stdout.
  • stderr only carries the Codex subprocess passthrough plus one connection warning, both suppressible.
  • Structured logs are fully injection-style — pino / winston / OpenTelemetry / a custom transport all work the same way.

Security notes

  • The Codex CLI handles its own credentials. This package never reads ~/.codex/auth.json or any environment variable that may contain a credential.
  • The WebSocket transport refuses non-loopback hosts unless allowRemote: true is explicitly set.
  • The optional audit log redacts well-known credential-like keys (see the Logging section for the redaction list). Use createFileAuditLogger(path)path is required to prevent accidental writes to unexpected locations.

Known improvement candidates

See Issue #1 for the full audit. Highlights:

  • 🔴 JSON-RPC error -32001 Server overloaded retry (per spec) is not yet implemented.
  • 🔴 No dedicated WebSocket Bearer-auth helper (works today via the generic headers field, but a bearerToken shortcut would improve DX).
  • 🟡 account/chatgptAuthTokens/refresh server-request handler not implemented.
  • 🟡 Typed wrappers cover ~9 of ~70 methods (generated types are available for the rest; users fall back to the generic request<T> for now).
  • 🟡 Bridge currently surfaces ~16 turn-centric notifications; ~21 thread/account/mcpServer lifecycle notifications are not yet normalized.
  • 🟢 Unix-socket transport decision (remove vs. mark as stub).

The text-repl-agent example use case is production-ready as-is; the above are quality-of-life and edge-case items.

Generated protocol types

src/protocol/generated/ is produced by codex app-server generate-ts and checked into the repo. The currently committed types track Codex 0.128. Regenerate after upgrading Codex:

pnpm generate:codex-types

These generated files are derived from openai/codex (Apache-2.0). The rest of this package is MIT-licensed; see LICENSE, LICENSE-APACHE, and src/protocol/generated/NOTICE.md for the attribution required by Apache-2.0.

Supported Codex versions

This package follows a single-latest-major support policy. The current release (0.x) supports Codex 0.128 and newer. When Codex bumps to 1.0 (or any future major), this package will be updated to track the new major and the change will be announced in CHANGELOG.md.

Development

pnpm install
pnpm typecheck
pnpm lint
pnpm test
pnpm build

Integration tests against the real codex app-server are off by default. To run them locally:

RUN_CODEX_INTEGRATION=1 pnpm test

CI never runs integration tests (Codex credentials are intentionally kept off the CI host).

License

This package is licensed under MIT.

Generated protocol types under src/protocol/generated/ are derived from openai/codex (Apache-2.0). See LICENSE-APACHE and src/protocol/generated/NOTICE.md for attribution.