12 KiB
claude-4-opencode
Run Claude Code plugins on opencode v2, and install them from Claude Code marketplaces.
It has two parts. c4o is a command-line tool that adds marketplaces and installs plugins, the way claude plugin does. The other part is an opencode plugin that loads what c4o installed and registers each plugin's skills, commands, agents, MCP servers and hooks with opencode. Plugins are used as published; nothing is converted or rewritten on disk.
Whether it works is checked end to end against the real opencode binary, using unmodified plugins from the most-installed marketplaces. See What is tested and COMPATIBILITY.md.
Getting started
Requires opencode 2.x, git, and Node 22 or later. Building needs Bun.
git clone https://github.com/Heretek-AI/claude-4-opencode
cd claude-4-opencode
bun install && bun run build
alias c4o="node $PWD/dist/cli.js"
c4o setup # adds the plugin to opencode's config
c4o marketplace add anthropics/claude-plugins-official
c4o install superpowers feature-dev commit-commands
Start opencode. The plugins' skills are available to the model, their commands run as /feature-dev:feature-dev, and their hooks are active. A running opencode picks up later installs without a restart.
c4o inspect <plugin> shows what a plugin contributes and what could not be carried over. Run it before installing a plugin you do not know: hooks and MCP servers run commands on your machine, exactly as they do in Claude Code.
Commands
| Command | What it does |
|---|---|
c4o marketplace add <source> |
Adds a marketplace: owner/repo, owner/repo@ref, a git URL, a marketplace.json URL, or a local path |
c4o marketplace list / update [name] / remove <name> |
Manages added marketplaces |
c4o install <plugin>[@marketplace] ... |
Installs plugins. The marketplace can be left out when only one lists the plugin |
c4o update [plugin ...] |
Refreshes marketplaces and reinstalls plugins at what they now point to |
c4o enable / disable / uninstall <plugin> |
Turns a plugin on or off, or removes it |
c4o list |
Shows installed plugins |
c4o inspect <plugin or directory> |
Reports a plugin's components, hook support, and anything not translated |
c4o setup |
Registers the opencode plugin in opencode's global config |
c4o doctor |
Checks git, opencode, registration, and that installed plugins load |
Add --json to any command for machine-readable output.
What is translated
| Claude Code | In opencode | Notes |
|---|---|---|
| Skills | Skills, named plugin:skill |
Also runnable as /plugin:skill args, as in Claude Code |
| Commands | Commands, named plugin:command |
$ARGUMENTS, $0..$N, named arguments, !`shell` and fenced ! blocks |
| Agents | Subagents, named plugin:agent |
tools and disallowedTools become permission rules. model is ignored; agents use the session's model |
| MCP servers | MCP servers, named plugin_server |
stdio and remote. ${VAR} and ${VAR:-default} are expanded from the environment |
| Hooks | See the table in COMPATIBILITY.md | command and http handlers run. prompt, agent and mcp_tool handlers do not |
bin/ |
On PATH for shell tool calls and command interpolation |
|
${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}, ${CLAUDE_PROJECT_DIR}, ${user_config.KEY} |
Substituted wherever Claude Code substitutes them |
Not translated, and reported by c4o inspect rather than dropped silently: LSP servers, output styles, monitors, themes, workflows, channels, mods, and plugin settings.json.
Hooks are written against Claude Code's tool names and input fields. They receive opencode tool calls in that vocabulary (shell arrives as Bash with command, edit as Edit with file_path, a plugin's MCP tool as mcp__plugin_<plugin>_<server>__<tool>), plus a transcript file in Claude Code's JSONL format.
Differences worth knowing
- A
UserPromptSubmithook that blocks a prompt does stop it from reaching the model, but opencode reports a generic error instead of the hook's message. - A hook that asks for user confirmation (
permissionDecision: "ask") blocks the tool call, because a plugin cannot open a prompt in opencode. - A hook's
systemMessageis passed to the model as a note. Claude Code shows it to the user instead. - When a command is run with no arguments,
$1-style placeholders are left untouched so that shell snippets such asawk '{print $1}'in a command body are not blanked. Claude Code blanks them. - Marketplace names follow Claude Code's rules, including the names reserved for Anthropic's own marketplaces.
Configuration
Options go in opencode's config, alongside the plugin entry:
{
"plugins": [
{
"package": "/path/to/claude-4-opencode/dist",
"options": {
"pluginDirs": ["./my-plugin"], // load plugin directories directly, like claude --plugin-dir
"disable": ["mcp", "hooks"], // turn off whole kinds: skills, commands, agents, mcp, hooks, bin
},
},
],
}
| Environment variable | Meaning |
|---|---|
C4O_HOME |
Where marketplaces and plugins are stored. Default: $XDG_DATA_HOME/opencode/claude-plugins |
C4O_PLUGIN_DIRS |
Extra plugin directories, separated like PATH |
C4O_DISABLE |
Comma-separated kinds to turn off |
C4O_LOG |
File to write diagnostics to, including each hook's command, exit code and output |
The store never touches ~/.claude.
What is tested
Every tier runs the real opencode binary in a throwaway home directory. Marketplaces are pinned to the commits in e2e/targets.lock.json, so a run is repeatable.
| Tier | What it shows | When it runs |
|---|---|---|
| Unit | Parsing, translation and the hook protocol | Every push and pull request |
| Conformance | Every entry of every pinned marketplace resolves and loads, and opencode lists every component | Every push and pull request |
| Golden | Component names match what real Claude Code lists after installing the same plugins from the same commits | Every push and pull request |
| Scripted | A stub plays the model with scripted tool calls: skills load, commands render, agents run restricted, MCP tools answer, hooks block and inject | Every push and pull request |
| External | Every plugin that lives outside its marketplace's repository is fetched at its pinned commit and loaded | Nightly |
| Live | A real model drives the same plugins | Nightly, when the OPENCODE_API_KEY secret is set |
| Drift | Pins move to current commits, goldens are recaptured, and everything runs against the newest opencode | Weekly |
bun run test # unit
bun run e2e # conformance, golden and scripted, against the opencode on PATH
bun run e2e:report # print the compatibility report from the last run
bun run e2e:oracle # recapture golden files with your installed Claude Code
OPENCODE_API_KEY=... bun test ./e2e/live # live tier
C4O_E2E_FULL=1 bun test ./e2e/conformance.test.ts # include external plugins
Set OPENCODE_BIN to test a different opencode build. Do not run a bare bun test: it would also pick up anything under review/.
The golden files come from real Claude Code, run in an empty config directory with a dummy API key, hooks disabled and plugin MCP servers ignored. It installs the plugins and prints its startup inventory; it makes no model request and runs no plugin code.
Layout
src/spec/ reads Claude Code's plugin and marketplace formats
src/store/ marketplaces, installs, enabled state
src/translate/ turns a loaded plugin into opencode values, and reports what it could not
src/runtime/ the opencode plugin, including the hook engine
src/cli.ts c4o
e2e/ sandbox, stub model, pinned targets, golden files, scenarios
docs/research/ prior art and what was learned about opencode v2's plugin API
Background
docs/research/prior-art.md covers earlier projects in this space and how this one differs. docs/research/opencode-v2-findings.md records how opencode v2's plugin API behaves in the places this project depends on.
The implementation is written from Claude Code's public documentation and checked against Claude Code's observable behavior. It contains no code from Claude Code or from reimplementations of it.