Skip to main content
Glama
README.md
# claudex

**A tiny, standalone bridge between [Claude Code](https://code.claude.com) and
[Codex](https://developers.openai.com/codex/cli/).** Run them side by side and let them talk
to each other — pass questions, hand off work, report back — through a shared local mailbox.

`claudex` = **Claude** + Cod**ex**. The two fused into one is the bridge.

- **No server, no network, no tokens.** Messages are plain JSON files under
  `~/.local/state/claudex/`. Both sides just read and write the same directory on local disk.
- **Symmetric over MCP.** You register one MCP server in *both* CLIs. Claude identifies as
  `claude`, Codex as `codex`, and each gets the same five tools.
- **Zero dependencies.** Pure Node built-ins (Node ≥ 18).
- **Fails open.** The Claude-side hooks never block, steer, or inject anything into Claude;
  if nothing is listening, they silently do nothing.

## How they talk

```
┌──────────────┐   bridge_send / bridge_inbox   ┌──────────────┐
│  Claude Code │ ─────────────▶  ◀───────────── │    Codex     │
│  (peer:claude)│      ~/.local/state/claudex/   │ (peer:codex) │
└──────────────┘        rooms/<room>/*.json      └──────────────┘
        │  SessionStart / Stop / Notification hooks (Claude → bridge, one way)
        └────────────────────────────▶ presence notes Codex can read
```

Both sessions share a single default room, so any Claude + any Codex on your machine are
connected out of the box. To isolate a project, set `CLAUDEX_ROOM=my-project` in both.

## Setup

Clone the repo, then wire it into each tool. `claudex init` prints the exact lines with
absolute paths filled in:

```sh
git clone https://github.com/arpan1221/claudex.git
cd claudex
node bin/claudex.mjs init
```

**Claude Code** — load the plugin (registers the MCP server *and* the presence hooks):

```sh
claude --plugin-dir /absolute/path/to/claudex
```

**Codex** — add the MCP server to `~/.codex/config.toml`:

```toml
[mcp_servers.claudex]
command = "node"
args = ["/absolute/path/to/claudex/scripts/mcp.mjs"]
env = { CLAUDEX_PEER = "codex" }
```

That's it. Start a Claude Code session and a Codex session (anywhere on the machine, or the
same folder if you set a shared `CLAUDEX_ROOM`) and they can message each other.

## The tools (in both Claude Code and Codex)

| Tool | What it does |
| --- | --- |
| `bridge_whoami` | Show your peer name, room, and where messages live. |
| `bridge_send` | Send a message. Omit `to` to broadcast; set `to: "codex"`/`"claude"` to direct it. |
| `bridge_inbox` | Read unread messages addressed to you (or broadcast). Reading marks them seen. |
| `bridge_peek` | View recent messages without marking anything seen. |
| `bridge_peers` | List who has posted in the room and when. |

Try asking one side: *"Use bridge_send to tell codex to take the frontend, then poll
bridge_inbox for its reply."*

## The CLI

`bin/claudex.mjs` (also `claudex` if you `npm link`) lets you watch and join from a terminal:

```sh
node bin/claudex.mjs watch          # live-print new messages as they arrive
node bin/claudex.mjs send "ping"    # post a message as peer "cli"
node bin/claudex.mjs read           # read your unread messages
node bin/claudex.mjs peers          # who's in the room
node bin/claudex.mjs init           # print Claude Code + Codex config
```

## Configuration

All optional; sensible defaults mean it works with nothing set.

| Variable | Default | Purpose |
| --- | --- | --- |
| `CLAUDEX_PEER` | `claude` (plugin) / `cli` | Your identity in the room. Codex config sets this to `codex`. |
| `CLAUDEX_ROOM` | `default` | Channel name. Same value on both sides = same conversation. |
| `CLAUDEX_HOME` | `~/.local/state/claudex` | Where message files are stored. |
| `CLAUDEX_TTL_MS` | 7 days | Messages older than this are pruned on the next read/send. |

## Design notes

- Each message is a single write-once JSON file (`msg-<ts>-<uuid>.json`) written atomically
  via temp-file + rename. The two peers only ever create distinct files, so there is no
  write contention and no lock.
- Each peer owns its own read cursor (`read-<peer>.json`) — only that peer writes it — so
  "mark as seen" is per-reader and race-free.
- Hooks are **Claude-side only** (Codex has no hook system); they forward *selected* metadata
  — session lifecycle and notification text — never prompt contents, source files, or tool
  output. Those selected fields can still contain context, so keep the mailbox on your own
  machine.
- This is a local, single-user coordination tool, not a secured or remotely exposed service.

## Test

```sh
node --test test/*.test.mjs
```

## License

[MIT](./LICENSE) © Arpan Nookala