Skip to content

Repository files navigation

splice

Type claudex instead of claude — Claude Code on your ChatGPT, Grok, or Kimi subscription, on loopback.

Install · Quick start · How it works · Providers · Trade-offs · Changelog · Security

ci release releases attested license

splice is a local, loopback-only proxy stack. A single Kotlin daemon (spliced) sits between Claude Code and one or more model backends, translating Anthropic's Messages API into each backend's own wire dialect. Each backend is exposed as a head — a thin Claude Code wrapper on its own loopback port (claude-splice, claudex, claude-grok, claude-kimi, claude-openrouter, …). Provider-native reasoning remains visible as thinking blocks; splice does not synthesize or mirror a reasoning summary into the transcript.

Not affiliated

Important

splice is an independent, personal project. It is not affiliated with, endorsed by, or sponsored by Anthropic, OpenAI, xAI, Moonshot, or OpenRouter. All product names and trademarks belong to their respective owners. Anthropic identifies routing Claude Code to non-Claude models through a custom gateway as unsupported. splice is exactly that kind of gateway; use it with that in mind, at your own risk. No warranty: see License, and why you might not want splice.

When something is wrong, splice doctor names the exact fix:

splice doctor output: every failing check prints its fix

Why it exists

Long coding-agent sessions bleed tokens and lose the thread. splice goes after both:

  • The prompt cache stays warm. A stable cache key and compaction that runs on the session's own model and reasoning effort keep the cache warm across a long session. Opaque encrypted reasoning-item replay is an explicit, default-off trade-off: it can add cache warmth, but measurements showed it also made fresh reasoning substantially thinner. A mismatched compaction model or effort silently invalidates the cache and re-reads the entire transcript uncached.
  • Reasoning stays provider-native. Readable reasoning fields returned by a backend are streamed as thinking blocks. splice does not manufacture a summary or feed synthetic reasoning text back into later turns.
  • One instrument panel for the fleet. The daemon serves a single dashboard over every head: live status, start/stop/restart, layered config with provenance, per-head 5-hour usage soft-warnings, auth, and logs.

What you get:

How it works

flowchart LR
    subgraph machine["your machine: everything binds 127.0.0.1"]
        CC["Claude Code<br/>(claude-openrouter · claudex · …)"]
        HEAD["head<br/>:3101"]
        D["spliced daemon<br/>dashboard + control :3096"]
        CC -- "Anthropic Messages API" --> HEAD
        HEAD --- D
    end
    HEAD -- "provider wire dialect" --> API["backend API<br/>(OpenRouter · Moonshot · …)"]
Loading

Each wrapper is an argv[0] symlink to the shared launch shim bin/splice-launch: it cold-starts the daemon if needed, asks it for an exec recipe over the loopback control plane, and execs the real claude pointed at the head's port. Only the head talks to the backend; the dashboard and every control endpoint are bearer-guarded and loopback-only. Adding a backend is a TOML edit, not code. See config/splice.example.toml for the full sample topology.

Requirements

Platforms: Linux and macOS natively; Windows via WSL2 (run wsl --install in PowerShell once, then do everything below inside the WSL shell; it behaves exactly like Linux). Native Windows shells are refused by the installer with the same guidance: the launch shim and daemon are Unix programs.

Dependency Why If missing
Java 21+ the spliced daemon ships as a fat jar apt install openjdk-21-jre-headless · brew install --cask temurin@21 · adoptium.net
Node 24 Claude Code's own runtime nodejs.org or nvm install 24
Claude Code splice wraps it — claude must resolve on PATH npm install -g @anthropic-ai/claude-code
Python 3 the launch shim parses the daemon's JSON launch recipe apt install python3 · preinstalled on macOS
curl + bash the launch shim and installer preinstalled almost everywhere
GitHub CLI, authenticated release installs verify build-provenance attestations via the GitHub API gh auth login once (cli.github.com); building from a checkout does not need it

You don't have to pre-check any of this: install.sh verifies every dependency up front, prints the exact fix for your machine's package manager, and, on an interactive terminal, offers to run each fix for you (always with consent). splice doctor re-verifies everything at any time.

Install

Option 1: the release one-liner. Verifies checksums and GitHub build-provenance attestations before anything goes live, so authenticate gh once first:

gh auth login   # once
curl -fsSL https://github.com/torad-labs/splice/releases/latest/download/install.sh | bash

To pin one version instead of following latest (prereleases never become latest):

curl -fsSL https://github.com/torad-labs/splice/releases/download/v0.3.2/install.sh \
  | env SPLICE_VERSION=v0.3.2 bash

Option 2: from source (no gh needed):

git clone https://github.com/torad-labs/splice.git
cd splice
./install.sh

Option 3: let your agent do it. Give this prompt to any coding agent with shell access:

Install splice (https://github.com/torad-labs/splice) on this machine and verify it works:
1. Check prerequisites: bash, curl, python3, Java 21+, Node 24, and Claude Code
   (`claude` on PATH). Install anything missing with this machine's package manager —
   show me each install command and ask before running it.
2. Install from source: `git clone https://github.com/torad-labs/splice && cd splice
   && ./install.sh` (or, if `gh auth status` shows I'm authenticated, use the release
   one-liner from the README instead).
3. Make sure ~/.local/bin is on my PATH (add it to my shell rc if not).
4. Ask me for an OpenRouter API key (I can create one at https://openrouter.ai/keys),
   export it as OPENROUTER_API_KEY, then run `splice setup`.
5. Run `splice doctor` and fix anything it flags — every failing check prints its own
   fix command. Repeat until it reports no blockers.
6. Tell me it's ready and that `claude-openrouter` launches Claude Code through OpenRouter.

The agent can drive that loop for the same reason you can: splice doctor prints the fix for every failing check.

Quick start

export OPENROUTER_API_KEY="…"     # vendor-issued pay-per-token API key
splice setup                      # write the supported API-key starter and install wrappers
claude-openrouter                          # Claude Code through OpenRouter on loopback (:3101)

No export handy? There are two other ways to get the key in — both land in ~/.config/splice/keys.toml (0600), which every later daemon start reads from any shell:

  • claude-openrouter login — a masked terminal prompt (the key never hits shell history, ps, or a session transcript).
  • Inside a claude-openrouter session, while the key is missing, splice offers to capture it: paste the key as a bare message (nothing else in the text) and it is stored and blocked before it reaches the model — it never travels upstream. The session transcript still records the paste, so the masked claude-openrouter login stays the zero-trace path.

An explicit OPENROUTER_API_KEY in the daemon's environment always wins over the store. splice key set|list|unset manages the store directly (--stdin for scripts).

install.sh builds the fat jar from a checkout (or fetches a release), installs the shared launch shim, links the wrapper commands into ~/.local/bin, and finishes by running splice doctor, so the install ends with a checked report.

splice was built for ChatGPT, Grok, and Kimi subscriptions. Copy the matching provider and head from config/splice.example.toml into ~/.config/splice/splice.toml, run splice install --all, then sign in with that head's login command (claudex login, claude-grok login, claude-kimi login). These routes are unofficial: they reuse each vendor's own CLI OAuth client identity, which no vendor documents for third parties. Use them at your own risk; the API-key starter above is the zero-config alternative.

For Claude itself, claude-splice preserves Claude Code's native Anthropic login while routing through splice; splice stores no Claude credential. Use Claude Code's own /login inside that head.

Admin verbs go through the splice command:

splice status         # per-head status
splice doctor         # check the whole install; every failing check prints its fix
splice restart        # restart the daemon with this shell's environment
splice dashboard      # open the control dashboard (loopback :3096)
splice init           # write the supported OpenRouter API-key starter topology
splice install --all  # (re)link the wrapper commands
<head> login          # sign in a subscription head (claudex, claude-grok, claude-kimi)

The dashboard and every control endpoint are bearer-guarded and loopback-only. The unlock key lives at ~/.claude-codex/state/mgmt-key.

Heads that see each other

Claude Code can list the other Claude Code sessions on a machine and send them messages. It discovers peers by reading the sessions directory inside its own config dir, and every splice head runs in its own config dir, so out of the box a claudex session would only ever see other claudex sessions.

splice closes that gap on the first launch of every head. The head's sessions directory becomes a link to the one registry under ~/.claude/sessions, which splice creates if plain claude has never run on the machine. From then on a claudex session, a claude-grok session, a claude-openrouter session and a plain claude session all appear in each other's ListAgents, and SendMessage reaches any of them. A session on one backend can hand work to a session on another backend and read the reply, which is how one splice install becomes a fleet of agents on different models that coordinate with each other.

There is nothing to configure. sessions is in the default [claude].share list. Put it in a head's isolate list to wall that head off, or remove it from share to turn the feature off everywhere. The fresh-machine e2e checks the link on both heads of a clean install.

Troubleshooting

splice doctor checks prerequisites, install integrity, config, daemon, and auth, then prints the exact fix under every failing check.

The daemon reads API-key env vars from its own environment. Export a key after the daemon has started and the shell sees it but the daemon does not: launches warn, requests fail upstream. splice restart restarts the daemon with your current shell's environment; splice doctor detects this state explicitly. Keys in ~/.config/splice/keys.toml sidestep the whole class: the store is re-read per request, so a claude-openrouter login or splice key set lands on the next request, no restart required.

Credential locations

Each of these is a password-equivalent secret: anything that can read the file (or the environment variable) can spend against your account. Keep files 600, never commit them, never paste them.

Splice signs in on its own. Each OAuth head keeps its own credential file under ~/.config/splice/auth/, written by splice login <head>, and it may be a different account from the one the vendor's own CLI or desktop app uses. The native apps' files (~/.codex/auth.json, ~/.grok/auth.json, ~/.kimi/credentials/kimi-code.json) are never read unless you name one in auth.file. Sharing a file with the native app is a trap: a refresh rotates the refresh token, so the app and splice invalidate each other's session, and the head has no credential while the other side rewrites the file. splice doctor warns when a head still names one.

Backend / route Auth kind Location Notes
Claude (claude-splice) client Claude Code's native credential store forwarded by Claude Code; splice stores no credential
codex (ChatGPT) chatgpt-oauth ~/.config/splice/auth/codex.json splice's own OAuth tokens (splice login claudex); ~/.codex/auth.json only by explicit auth.file
grok (xAI) grok-oauth ~/.config/splice/auth/grok.json splice's own OAuth tokens (claude-grok login); ~/.grok/auth.json only by explicit auth.file
kimi (Moonshot) kimi-oauth ~/.config/splice/auth/kimi.json (+ device_id beside it) splice's own device-flow token (claude-kimi login); the app's file only by explicit auth.file
OpenRouter api-key $OPENROUTER_API_KEY (env) or ~/.config/splice/keys.toml API key — password-equivalent
Moonshot (pay-per-token) api-key $MOONSHOT_API_KEY (env) or ~/.config/splice/keys.toml API key — password-equivalent
splice api-key store — ~/.config/splice/keys.toml (0600) env wins over the store — password-equivalent
splice control plane — ~/.claude-codex/state/mgmt-key dashboard/API unlock key — password-equivalent

Provider support

Route Auth Status
Claude (claude-splice) client (Claude Code native login) Primary — Anthropic passthrough; splice stores no credential
OpenRouter api-key (OPENROUTER_API_KEY) Supported — pay-per-token, any OpenAI-compatible vendor
Moonshot api-key (MOONSHOT_API_KEY) Supported — pay-per-token Anthropic base
codex (ChatGPT) chatgpt-oauth Primary — what splice was built for; unofficial, at your own risk
grok (xAI) grok-oauth Primary — unofficial, at your own risk
kimi (Moonshot) kimi-oauth Primary — unofficial, at your own risk

The OAuth-identity routes are the reason splice exists: they run Claude Code on the subscription you already pay for. They are also unofficial: they authenticate by reusing the public OAuth client identity of each vendor's own CLI, not a documented third-party integration, and a vendor could object or break them at any time. Use them at your own risk. The api-key routes are ordinary pay-per-token API access with none of that ambiguity, and make the best zero-config starter.

Beta: code mode for ChatGPT

Code mode is default-off and exclusive to Claudex-compatible providers (auth.kind = "chatgpt-oauth", dialect = "openai-responses"), including custom head names. In the provider's existing quirks section:

[providers.codex.quirks]
code_mode = true # beta; false or omitted disables both runner and guidance

Enabling it automatically appends orchestration guidance to the caller's instructions and exposes splice's bundled JavaScript runner on eligible GPT-6 Astra/Sol turns. Compaction, toolless turns, and forced named-tool choices keep the ordinary path. Direct tools remain available; all real operations use Claude Code's permission-checked client handlers. No Codex or Node installation is required. Child JVMs bound workers, time, and heap and deny guest host/I/O access; Graal community is not an OS-hardened sandbox against same-user attackers.

Four scripts can be paused at once, each in its own worker JVM. A paused script whose client calls go unanswered for 30 minutes is closed, and when all four slots are held the oldest one paused over 2 minutes is evicted for a newer script; a script that cannot get a slot reports that in its own output so the model calls the tools directly. A closed script is never rerun; its evidence (results so far, unresolved calls, the reason) is what the model sees. That evidence is bounded only by the 1 MiB output ceiling; past it, each result is cut to an equal share behind a [truncated N chars] marker rather than the turn failing. A code-mode failure that no retry can change ends the turn with a readable ⚠ splice: line instead of an API error, because Claude Code either retries error events identically or hides their message once content has streamed.

If a completed script's history can no longer be placed (the record aged out, the session switched model, the conversation moved underneath a running script), splice sends the client's own history upstream instead, where the script's client calls are ordinary tool calls, and logs one [code-mode] line for the head. The conversation continues; only that script's batching is lost from the model's view.

Every head using that provider shares the setting. Topology is read only when the daemon boots: finish ongoing work, edit TOML, then run splice restart for a full daemon restart. A head restart alone does not reload TOML. Finish code-mode work before toggling or restarting: pending JavaScript execution cannot survive a daemon restart, and splice never reruns the lost source automatically.

In a bounded real-Astra test on synthetic tasks, guidance improved batching without reducing graded correctness. That is not a guarantee of better output or less redundant investigation on arbitrary projects; the feature remains beta.

Why you might not want splice

Reasons to walk away:

  • An unsupported gateway. Anthropic identifies this class of tool as unsupported, and a Claude Code update can break splice at any time. The version handshake makes the break loud instead of corrupting a session mid-turn.
  • Legally unsettled OAuth. The Codex, Grok, and Kimi routes reuse each vendor's own CLI OAuth client identity. No vendor documents that reuse; it may violate terms of service, and a vendor could cut it off without notice. The primary routes are also the biggest risk.
  • Single-user by design. There is no multi-user story, remote access, or TLS. A team wanting a shared model gateway should run one built for that job (LiteLLM, for example).
  • A JVM daemon. Java 21 is a hard dependency, and the daemon holds a bounded 2 GB heap while serving.
  • A one-person project. No warranty, no SLA. The release gates are strict: every release is checksummed, provenance-attested, and installed hermetically in CI before it ships. It is still one person.

Backends and protocols

splice speaks several upstream wire dialects (openai-responses, openai-chat, anthropic-passthrough), selected per provider in the topology.

The codex backend at https://chatgpt.com/backend-api/codex is a ChatGPT / Codex backend that speaks a Responses-STYLE protocol: the internal endpoint the ChatGPT Codex product itself uses. It is not the public OpenAI Responses API, and nothing here should be read as targeting that public API.

Reasoning

"Reasoning" here means one of three narrow things, never the model's raw private chain-of-thought:

  • Provider-generated reasoning summaries: a short summary the backend itself produces and returns.
  • Readable reasoning fields: supplied explicitly by the backend on the wire (e.g. reasoning_text / summary fields).
  • Opaque encrypted reasoning-item replay: carrying the backend's own encrypted reasoning items forward into a later request, verbatim and unread.

splice never has, exposes, or reconstructs the model's raw chain-of-thought or exact reasoning. Provider-native readable fields may be displayed as thinking blocks, but mirror_reasoning is locked off after every configuration layer: TOML, state, environment variables, and runtime PATCH cannot enable a synthetic transcript summary.

Opaque replay also ships off. Set CLAUDEX_REPLAY_REASONING=1 only if you deliberately prefer additional replay/cache warmth over the deeper fresh reasoning observed without replay.

The cache-replay experiment

An A/B run in July 2026 asked one question: does replaying opaque encrypted reasoning items back into a request bust the prompt cache? Two isolated real Claude Code sessions ran the same fixed multi-turn workload on a side port, only the replay toggled, and a captured, sanitized transcript replayed it without live credentials. Its harness was retired with the Node proxy it drove; the result is what matters:

The cache effect remains workload-dependent, but the reasoning-depth result was strong enough to make replay default-off: replay caused the model to reuse prior thinking, reducing output and making reasoning thin. Read experiments/cache-replay/README.md for the caveats and run it yourself.

Layout

gateway/       Kotlin daemon (spliced) — Gradle multi-module, JDK 21; the PRIMARY stack
config/        splice.example.toml — the sample multi-provider topology
bin/           splice-launch (the installed wrapper) + claudex (the codex-head entry)
install.sh     fetch/build the jar, install the shim, link wrapper commands
webui/         React 19 + Vite + Zustand dashboard, single-file build
checks/        the gate (`npm run gate`) and its legs: wall routing, the concentration ratchet,
               release acceptance, the OSS ladder, the e2e harnesses
.rules/        ast-grep "walls" enforced write-time AND at the commit gate (same rules twice)
.claude/       the hook orchestrator that runs the walls on every agent write, and its tests
.dev/          campaign ledgers with their walls and oracle (`gate:campaign`, `oracle:*`),
               research notes, release material
.docs/         design specs and plans, README assets

The gateway/ Kotlin daemon is the only stack. The legacy server/ Node proxy and its bin/claudex-next shim were deleted on 2026-08-10 (P8-CUT), after the Kotlin daemon had owned the production ports for three days and 32,326 turns at 99.14% clean. The wire behaviour it established survives as 11 byte-exact fixtures in the migration oracle (npm run oracle:replay), whose mock upstream is vendored so it no longer depends on the deleted tree.

Development

npm ci
npm run gate   # Gradle, walls/hooks, server, webui, release acceptance, OSS checks

Contracts and invariants live in AGENTS.md; the change log in CHANGELOG.md; the wall doctrine in .rules/README.md.

License

MIT.

About

Run Claude Code on your ChatGPT, Grok, Kimi or Muse plan, on OpenRouter, or on a local model, and let sessions on different models work as a team. Unofficial; use at your own risk.

Resources

Code of conduct

Contributing

Security policy

Stars

17 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages