Thank you for your help with the Sprites plugin for OpenCode.
- Node.js 20 or later
- Bun, to run the Bun smoke test locally
- The OpenCode 1 and OpenCode 2 CLIs, which
npm installadds as development dependencies
Install the locked development dependencies:
npm cinpm 12 and later block install scripts. The allowScripts field in package.json lets @opencode-ai/cli select its platform binary.
Point OpenCode at the checkout while you develop. Use plugin with a file:// URL for OpenCode 1, and plugins for OpenCode 2:
OpenCode 2 watches the plugin files and loads them again when they change. If a change does not become active, run opencode2 service restart.
One package serves both OpenCode releases through its export map:
| Path | Export | Release |
|---|---|---|
src/v1.js |
./server |
OpenCode 1 |
src/v2.js |
. |
OpenCode 2 |
src/shared.js |
not exported | both |
OpenCode 1 resolves the package's ./server subpath, and OpenCode 2 resolves ., so neither release loads the other's file. test/exports.test.js asserts that split through Node's own resolution.
src/shared.js holds what does not differ between the releases: the server defaults, the naming rules, the guidance text, the command templates, the trigger pattern, and the destructive tools. Put a Sprites rule there, and put an API difference in the entry point that needs it.
Run the same primary checks that CI runs:
npm run check
npm pack --dry-runnpm run check examines the format, runs strict TypeScript checks, and runs the Node test suite.
The Node suite has these parts:
test/exports.test.jsasserts that the export map gives each release its own entry point.test/v1.test.jsandtest/v2.test.jsdrive each entry point directly. The v2 tests use a fake plugin context fromtest/context.js.test/opencode1.integration.test.jsrunsopencode debug configin isolated XDG directories, so the real OpenCode 1 CLI resolves the configuration.test/opencode2.integration.test.jsstarts an isolated OpenCode 2 background service. It makes sure that the real runtime registers the MCP server and the commands. These tests are slow, because the service must start.
The integration tests fail when the opencode2 binary is absent. npm ci installs that binary, so an absent binary is an error. A silent skip would let npm run check report success while nothing runs against the real runtime. To skip these tests on purpose, set SPRITES_SKIP_OPENCODE_TESTS=1.
The fake context in test/context.js keeps the same rules as the runtime:
- It keeps the transforms and replays them each time it materializes the configuration. A test can therefore run
setuptwo times and find the same result as a plugin reload. - A registration removes its own contribution when you dispose it.
emitgives the event to the subscriber and waits until the plugin processes it. The tests do not use timers.- Each server has its own status, and
setStatuschanges it. The tests can therefore move a server between states, and can give two servers different states. - Each method has the type of its runtime signature, and
test/type-contract.tsasserts that the assembled context is assignable to the real contract. The type check fails if the fake becomes more permissive than the runtime.
OpenCode installs plugins with Bun, so CI also runs a Bun smoke test:
npm run test:bunsrc/v2.jsexports an OpenCode 2 plugin object with anidand asetupfunction.setupregisters the MCP server and the commands with transforms, and registers permission, tool, and session hooks. It returns a cleanup function.src/v1.jsexports an OpenCode 1 plugin module with anidand aserverfunction. It writes the MCP server, the commands, and the permission defaults into the configuration.- The plugin has no runtime dependencies. Types come from
@opencode-ai/pluginfor OpenCode 1, and from the@opencode-ai/plugin-v2alias of the same package for the OpenCode 2 beta. Both are development dependencies. - Command registration is unconditional. OpenCode replays the transform after a reload, and the replay sees the commands from the previous registration. A conditional registration therefore deletes its own commands.
- OpenCode 2 gives plugins no permission draft. The plugin changes a decision from
allowtoaskin thepermission.evaluatehook. A configureddenynever reaches the hook. - Guidance becomes active only for an explicit Sprites signal, a Sprites tool call, a Sprites permission decision, or a Sprites command. The active state moves to child sessions, and the plugin removes it when a session is deleted.
- The MCP status cache is longer than one model step. The
mcp.status.changedevent clears it. An empty server list counts as unknown, because MCP configuration can be later than the first check. - OpenCode 2 has no compaction hook. Under OpenCode 2 the compaction instruction is part of the system guidance instead. OpenCode 1 keeps its compaction hook.
- The plugin sets
codemodetofalseon its MCP server. Code Mode reaches MCP tools through a dispatcher, and the Sprites MCP server does not support that call shape yet. With Code Mode off, thesprites_*tools are on the model's own tool list. Remove the default when the server supports the dispatcher. - OpenCode does not enforce
engines.opencode. The field records intent, and it does not gate loading in either release. - The session
contexthook does not run for title or compaction requests. Those requests do not receive the guidance.
When you change hooks or configuration behavior, update the unit tests and the type assertions in test/type-contract.ts. When you change plugin loading or option handling, update the OpenCode 2 integration tests too.
Keep changes small, explain the user-visible behavior, and include tests for changes in behavior. Before you open a pull request, run the checks above, and make sure that git diff --check reports no whitespace errors.
Publication needs access to the @flydotio npm organization.
For the first publication, configure an NPM_TOKEN secret in the repository's npm GitHub environment. The release workflow then runs with provenance when you publish a GitHub release.
After the package is on npmjs.com, configure npm trusted publishing for this repository, and remove the NODE_AUTH_TOKEN fallback from .github/workflows/release.yml.
The OpenCode 2 plugin API is in beta. Publish a compatible plugin update when the v2 entry points or contracts change.
{ "$schema": "https://opencode.ai/config.json", "plugins": ["/absolute/path/to/sprites-opencode-plugin"], }