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

@koriit/opencode-claude-bridge

v0.3.0

Published

An OpenCode plugin that bridges enabled Claude Code plugins (commands, agents, skills) into OpenCode at runtime, namespaced so they never shadow your existing items.

Readme

opencode-claude-bridge

Use your Claude Code plugins inside OpenCode — no porting, no copying, no second install.

If you already manage plugins with claude plugin install, this bridge makes their commands, agents, and skills available in OpenCode too. It reads Claude's enabled-plugin state at OpenCode launch and injects the components live, namespaced so they never shadow anything you already have.

It is a single plugin — no wrapper binary, no generated files, no lockfile. You run plain opencode; the bridge reads Claude's own settings to determine which plugins are enabled, resolves each one's on-disk location, and injects the components on each launch.

Example. You install a plugin in Claude:

claude plugin install code-tools@acme

Start OpenCode in that project, and the plugin's audit command, reviewer agent, and SKILL.md skills are already there — /audit, the reviewer subagent, and so on. Nothing else to do. If a name is already taken by one of your own items, the bridge's copy is renamed (e.g. /code-tools-audit) — your item is never touched.

Status: early development. Commands, agents, and skills from enabled Claude plugins are supported.

MCP and LSP servers are not supported. Two problems make a straight bridge unreliable:

  1. OAuth client mismatch. A plugin's .mcp.json OAuth registration is tied to Claude's OAuth client — those credentials don't carry over to OpenCode, which authenticates as its own client. Copying the config across does not produce a working authenticated server.
  2. Claude-specific porting. MCP/LSP servers ship with Claude-specific setup and porting instructions that don't translate cleanly to OpenCode, so injecting the raw .mcp.json / .lsp.json is not enough to make them work.

The feature has been removed for now. Track this in the project issues if you need it.

Contents

Requirements

  • OpenCode >=1.15.0 <1.16.0 (see Why the version is pinned).
  • The claude CLI on your PATH at OpenCode runtime. The bridge calls claude plugin marketplace list --json to resolve where each marketplace is installed on disk. If claude is missing the bridge logs a warning and falls back to whatever it can resolve from your settings files alone; OpenCode still starts normally.

Windows is best-effort only. The bridge is developed and tested on Linux/macOS. Core features (commands, agents, skills) should work, but path handling and ${CLAUDE_PLUGIN_ROOT} / ${CLAUDE_PLUGIN_DATA} resolution have not been validated on Windows.

Install

Add the plugin to your global ~/.config/opencode/opencode.json so it applies across all projects. The bare-string form uses all defaults:

{
  "plugin": ["@koriit/opencode-claude-bridge"]
}

The tuple form lets you set options (all shown here at their defaults):

{
  "plugin": [
    [
      "@koriit/opencode-claude-bridge",
      {
        "blockedPlugins": []
      }
    ]
  ]
}

That's the whole install. OpenCode fetches the package with ignoreScripts: true, so no build step runs — the package entry points at src/index.ts on purpose, and OpenCode's Bun runtime imports the TypeScript directly. The bridge has zero runtime dependencies (every @opencode-ai/plugin import is import type, erased at runtime).

Updating

OpenCode resolves the bare @koriit/opencode-claude-bridge spec to @latest and then caches the resolved package, so a new release on npm is not picked up automatically. To force an update, clear the bridge's entry from OpenCode's package cache and restart:

rm -rf ~/.cache/opencode/packages/@koriit/opencode-claude-bridge@latest

On the next launch OpenCode re-fetches the latest published version. (If you pinned a specific version in your config — e.g. @koriit/[email protected] — bump that version string instead; the cache key includes the version.)

The path above is the per-package cache directory OpenCode creates for npm plugins (~/.cache/opencode/packages/<sanitized-spec>/). Removing it is safe — it is regenerated on the next start.

Configuration

All keys are optional. Unknown keys and ill-typed values are ignored with a warning.

| Key | Type | Default | Meaning | | ---------------- | ---------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | blockedPlugins | string[] | [] | Plugin ids (name@marketplace) to never inject — any component type. | | strict | boolean | false | Promote fatal warnings (e.g. parse failures) to hard errors. A missing or failing claude CLI is non-fatal — the bridge resolves from settings files alone. | | mode | string | mirror-claude | The only accepted mode: mirror exactly Claude's enabled set. |

What gets bridged

The bridge runs in mirror-claude mode (the only mode). It determines the enabled set exactly the way Claude Code does — by merging the enabledPlugins maps from Claude's settings layers, in precedence order (later layers override earlier ones):

  1. ~/.claude/settings.json (global / user scope)
  2. <project>/.claude/settings.json (project scope)
  3. <project>/.claude/settings.local.json (project scope)

A plugin is bridged when its final merged value is true. This honors both ways of enabling a plugin in Claude: via the claude plugin CLI and by hand-editing enabledPlugins in a settings.json. (Relying on claude plugin list --json alone would miss the hand-edited case — it only reports plugins installed through the CLI.)

  • A later enabledPlugins layer setting false disables a plugin an earlier layer enabled.
  • ids listed in blockedPlugins are never bridged.

Once a plugin is enabled, its on-disk location is resolved from the marketplace manifest (claude plugin marketplace list --json<installLocation>/.claude-plugin/marketplace.json), falling back to the claude plugin list --json entry's installPath for sources the manifest cannot resolve directly (e.g. git-subdir plugins). Plugins whose location cannot be resolved by either method are skipped with a log line.

What gets injected

| Component | Default | Source & mapping | | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Commands | On | commands/**/*.mdcfg.command; $ARGUMENTS / $1..n pass through. | | Agents | On | agents/*.mdcfg.agent; uses the prompt field; mode defaults to subagent (also primary/all); temperature, top_p, steps, hidden, color, variant pass through (sanitized). | | Skills | On | skills/<name>/SKILL.md dirs → cfg.skills.paths and cfg.command by default (see dual routing below); user-invocable: false → skill only; disable-model-invocation: true → command only; both set → skipped. Zero files copied in the common case. |

Commands, agents, and skills are plain text prompts and are always bridged.

MCP and LSP servers are not bridged. See the status note above — Claude's MCP OAuth client doesn't carry over to OpenCode, and the servers need Claude-specific porting that doesn't translate, so the feature was removed.


How it works (internals)

This section is reference material for maintainers and the curious — you don't need it to use the bridge. It documents the naming rules, the skills cache, variable substitution, and the OpenCode-internal behavior the bridge depends on.

No-shadowing & naming

Injected items are namespaced so they never shadow your existing OpenCode commands, agents, or skills (including OpenCode's built-ins). On a name collision the bridge's item is renamed — the native/existing item is never touched. The rename ladder, tried in order until a free slot is found:

  1. <name> — the bare name (e.g. audit)
  2. <plugin>-<name> (e.g. code-tools-audit)
  3. <marketplace>-<plugin>-<name> if still colliding
  4. <marketplace>-<plugin>-<name>-<8hex> — deterministic SHA-256 tiebreak

<plugin> and <marketplace> are the two halves of the plugin id name@marketplace. Processing order is sorted by plugin id, so the first claimant of a bare name wins deterministically across runs. Every injected item's description is suffixed with [plugin-id] for traceability.

Skills: no-copy in the common case, bridge cache on collision

Each skill in a plugin's skills/<name>/ directory is discovered by reading its SKILL.md frontmatter name. In the common case — no collision — the plugin's own skill directory is pushed directly onto cfg.skills.paths: zero files are copied.

On collision (the bare name is already taken by a native OpenCode skill, a built-in, or an earlier-processed plugin), the entire skill directory is copied to the bridge cache:

~/.cache/opencode-claude-bridge/skills/<marketplace>/<plugin>/<version>/<allocatedName>/

The copy's SKILL.md frontmatter name is patched to the prefixed name; all other files (assets, sub-directories) are preserved so relative references keep working. .git and other dot-directories are excluded. Copies are keyed by <marketplace>/<plugin>/<version>/<allocatedName> and regenerated when the source is newer; when the plugin version changes, old version directories are pruned (GC). This cache is distinct from OpenCode's own ~/.cache/opencode/skills.

Override the cache location with the OPENCODE_CLAUDE_BRIDGE_CACHE_ROOT environment variable (set before starting OpenCode) — useful in CI or test environments.

URL-sourced skills: if your opencode.json lists entries in cfg.skills.urls, the bridge cannot detect collisions against them at hook time (fetching URL skills would force the lazy Skill service to load before our injected paths). A warning is logged when URLs are present.

Variable substitution

The bridge resolves these variables in injected content before OpenCode sees it.

| Variable | Resolves to | Available in | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | ${CLAUDE_PLUGIN_ROOT} | Plugin resolved on-disk install directory — resolved from the marketplace manifest (or the claude plugin list --json installPath fallback) | Commands, agents, skills (body + SKILL.md) | | ${CLAUDE_PLUGIN_DATA} | Plugin persistent data dir — ~/.claude/plugins/data/<sanitized-id> | Commands, agents, skills (body + SKILL.md) | | ${CLAUDE_SKILL_DIR} | Skill source directory (dirname of SKILL.md) | Plugin skills only (body + SKILL.md) | | ${CLAUDE_SESSION_ID} | The literal <use Session ID from context> (see note) | Commands, agents, skills (body + SKILL.md) |

<sanitized-id> is the plugin id with all characters outside [a-zA-Z0-9_-] replaced by - (e.g. my-plugin@acmemy-plugin-acme).

${CLAUDE_SESSION_ID} limitation. The bridge runs in the config hook, which produces a single config object shared by all sessions in the same directory. Baking a concrete session ID into content there would leak one session's ID into every other session that reuses the config. So ${CLAUDE_SESSION_ID} is replaced with the literal <use Session ID from context>, which instructs the model to read the real ID from the system prompt. The bridge injects a Session ID: <id> line into every message's system prompt via the per-session experimental.chat.system.transform hook, making the current ID available whenever the model needs it.

Security model

  • Commands, agents, and skills are text prompts (lower risk) and are always bridged — but always namespaced so they can never shadow your own items.
  • MCP and LSP servers (which would spawn processes / open connections) are not bridged — see the status note.
  • blockedPlugins hard-excludes plugin ids from all bridge injection (commands, agents, skills). It governs what the bridge injects; it cannot suppress commands that OpenCode's own native Claude-plugin integration may load independently of the bridge.
  • Disabled plugins are always skipped.

Why the version is pinned

>=1.15.0 <1.16.0

Verified against OpenCode 1.15.13. The bridge relies on OpenCode-internal behavior that is not a documented public contract (config hook shape, cfg.skills object layout, skill discovery paths), so it pins a conservative same-minor window.

This range is documentation only — the bridge does NOT read the running OpenCode version and does NOT warn at runtime. It relies on OpenCode-internal behavior verified against the version above and works across versions until something actually breaks. The range is widened only after the end-to-end suite passes against a new version.

Schema-safety invariant

Every field copied from a Claude component into OpenCode's config is schema-validated or sanitized before write — never raw passthrough. This is not cosmetic: OpenCode validates the merged config when the TUI issues config.get at startup, outside the bridge's config hook try/catch. A Claude field that violates OpenCode's schema would therefore not be caught by the bridge's non-throw guard — it would crash the whole instance later. Injecting nothing is always safer than injecting an invalid value. (Known cases handled: agent color name → hex mapping, finite temperature/top_p.)

Diagnostics

Bridge log lines are written through OpenCode's own logging endpoint (client.app.log) under the opencode-claude-bridge service, so they land in OpenCode's server logs alongside everything else and honor OpenCode's log configuration. Logging is fire-and-forget — a logging failure can never disrupt injection.

The bridge surfaces no in-TUI notifications. Many of its warnings reflect issues in the plugin author's definitions and aren't actionable by you, so a per-session toast would be noise. To stream the bridge's service=opencode-claude-bridge lines to stderr, run with --print-logs:

opencode --print-logs

Without --print-logs the entries still go to OpenCode's server log file; the flag just mirrors them to stderr. Check there whenever a component you expect is missing or misbehaving.

Development

A Bun + TypeScript package.

bun install              # install dev dependencies
bun run typecheck        # type-check src + tests
bun run build            # emit dist/
bun run test             # unit tests (sets OCB_TMPDIR=.tmp)
bun run test:e2e         # end-to-end tests (sets OCB_TMPDIR=.tmp; launches real opencode)
bun run test:all         # unit + e2e
bun run test:coverage    # unit tests with line/function coverage report

Always use bun run test (the npm script), not bun test test/ directly. The scripts set OCB_TMPDIR=.tmp to keep test scratch off a potentially small system /tmp.

The end-to-end suite launches a real opencode serve with the plugin loaded and a fake claude CLI on PATH, then asserts behavior against the live HTTP API. It also acts as the version-compatibility canary: run it after every OpenCode upgrade before widening the supported range.

Releasing (maintainer)

  1. Bump version in package.json, commit.
  2. Create a GitHub Release with tag v<version> (e.g. v0.1.1).
  3. The publish workflow runs the test gates and publishes to npm automatically (requires the NPM_TOKEN repo secret — see the workflow file header for setup).

License

MIT