Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Contributing

Thanks for helping improve the Sprites plugin for OpenCode.

## Requirements

- Node.js 20 or newer
- OpenCode 1.18.23 or newer in the 1.x series for integration testing
- Bun when running the Bun smoke test locally

## Set up the repository

Install the locked development dependencies:

```sh
npm ci
```

Point OpenCode at the checkout while developing:

```json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["file:///absolute/path/to/sprites-opencode-plugin"]
}
```

The directory form lets OpenCode resolve the package's `./server` export and enforce its `engines.opencode` range.

For a quick drop-in test, you can instead copy `index.js` to `.opencode/plugins/sprites.js`; global drop-ins live at `~/.config/opencode/plugins/`. OpenCode loads drop-ins as bare file specifiers, so they cannot receive a plugin options object. Use the package-directory form when testing non-default options.

## Run checks

Run the same primary checks used in CI:

```sh
npm run check
npm pack --dry-run
```

`npm run check` verifies formatting, runs strict TypeScript checking, and executes the Node test suite. The integration tests run `opencode debug config` in isolated XDG directories so configuration is exercised by the real OpenCode CLI.

OpenCode installs npm plugins with Bun, so CI also runs an import and configuration smoke test under Bun:

```sh
npm run test:bun
```

CI runs the Node suite on Node.js 20 and 24.

## Implementation notes

- `index.js` is the package entry point and exports the preferred OpenCode v1 plugin module. The `./server` export supports package-directory loading.
- The plugin registers the Sprites MCP server, commands, permission defaults, session-scoped system guidance, compaction guidance, and event handling.
- Guidance activates only for explicit Sprites signals or Sprites tool calls. Active state propagates to child sessions and is removed when a session is deleted.
- OpenCode always prefixes MCP tools with the server name. Destructive permission patterns intentionally cover both the verified raw Sprites tool names and redundantly prefixed variants.
- OpenCode's system-transform hook does not identify small-model calls. After a session becomes active, title or summary generation that reuses the session ID may also receive the Sprites guidance.
- OpenCode's MCP timeout controls both initial connection and tool discovery. Keep changes to the 60-second default mindful of startup stalls when the endpoint is unavailable.
- The MCP status cache is deliberately longer than one model step and is invalidated by MCP tool-change events.

When changing hooks or configuration behavior, update the unit tests and the SDK contract assertions in `test/type-contract.ts`. When changing package loading or option handling, update the real OpenCode integration tests as well.

## Pull requests

Keep changes focused, explain user-visible behavior, and include tests for behavior changes. Before opening a pull request, run the checks above and ensure `git diff --check` reports no whitespace errors.

## Release process

Publishing requires access to the `@flydotio` npm organization.

For the initial package publish, configure an `NPM_TOKEN` secret in the repository's `npm` GitHub environment. Publishing a GitHub release then runs the release workflow with provenance.

After the package exists on npmjs.com, configure npm trusted publishing for this repository and remove the `NODE_AUTH_TOKEN` fallback from `.github/workflows/release.yml`.
114 changes: 55 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
# Sprites for OpenCode

Use [Fly.io Sprites](https://sprites.dev) from OpenCode as persistent, isolated Linux development environments for builds, tests, sandboxes, previews, and long-running services.
Use [Fly.io Sprites](https://sprites.dev) from OpenCode as persistent, isolated Linux environments for builds, tests, sandboxes, previews, and long-running services.

The plugin uses OpenCode's current v1 plugin API. It registers the hosted Sprites MCP server, relies on OpenCode's OAuth support, adds session-scoped workflow guidance and slash commands, and asks for approval before destructive remote operations. No Sprites CLI or API token setup is required.
The plugin connects OpenCode to the hosted Sprites MCP server, uses OpenCode's browser-based OAuth flow, adds Sprites commands and workflow guidance, and asks for approval before destructive remote operations. You do not need to install the Sprites CLI or create an API token.

## Requirements

- OpenCode 1.18.23 or newer in the 1.x series
- Node.js 20 or newer for package development

## Install

Add the npm package to `opencode.json`:
Add the package to your `opencode.json`:

```json
{
Expand All @@ -20,46 +19,66 @@ Add the npm package to `opencode.json`:
}
```

OpenCode installs npm plugins automatically with Bun at startup. Restart OpenCode after changing plugin configuration.
OpenCode installs npm plugins automatically with Bun at startup. Restart OpenCode after changing the configuration.

The package name above is the intended public name. Until the first npm release, use the [local development](#local-development) setup.
If the package is not yet available from npm, clone this repository and point OpenCode at the checkout instead:

## First use
```json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["file:///absolute/path/to/sprites-opencode-plugin"]
}
```

Use an absolute `file://` URL. This installation form has the same features as the npm package.

## Authenticate

Run `/sprites-status` or ask OpenCode to list your Sprites. The first MCP call should start OpenCode's browser OAuth flow. If it does not, authenticate the plugin-provided server explicitly:
Run `/sprites-status` or ask OpenCode to list your Sprites. The first Sprites request should start OpenCode's browser OAuth flow.

If the browser does not open, authenticate the plugin-provided server explicitly:

```sh
opencode mcp auth sprites
```

Then retry the original request. An empty Sprite list is a successful authenticated result.

## What it provides
## Use Sprites

- Hosted MCP access at `https://sprites.dev/mcp`.
- Sprites workflow and safety guidance only in sessions that mention `sprites.dev`, say “fly sprites,” reference a `sprites_*` tool, invoke a Sprites command or tool, or inherit an active Sprites parent session.
- `/sprites-status` for a read-only connectivity and authentication check.
- `/sprites-smoke` for a list → create → exec smoke test; cleanup still requires explicit intent.
- OpenCode permission prompts for Sprite destruction, checkpoint restore, and complete network-policy replacement.
- Compaction guidance that preserves active Sprite names, service state, checkpoints, and pending approvals in relevant sessions.
You can ask OpenCode to:

With the plugin enabled, OpenCode can list and create Sprites, run remote commands and builds, manage long-running services, create and restore filesystem checkpoints, and inspect or update outbound network policy.
- List or create Sprites.
- Clone a repository into a Sprite and run its build or test suite remotely.
- Start a long-running development server or preview as a Sprite service.
- Create a filesystem checkpoint before a risky change and restore it later.
- Inspect or update a Sprite's outbound network policy.

## How it works
The plugin also adds two slash commands:

OpenCode runs outside the Sprite. The plugin configures Sprites MCP as the remote control plane:
- `/sprites-status` performs a read-only connectivity and authentication check.
- `/sprites-smoke` walks through a list → create → exec smoke test. Destroying the test Sprite still requires explicit intent.

OpenCode itself continues to run outside the Sprite. Local workspace and shell operations stay on your machine; Sprites MCP tools perform remote work. In particular:

- Local workspace and shell commands remain on your machine.
- One-off remote commands use `sprites_exec`.
- Long-running remote processes use `sprites_service_*` tools.
- Reversible remote filesystem snapshots use `sprites_checkpoint_*` tools.
- Reversible filesystem snapshots use `sprites_checkpoint_*` tools.
- Outbound access is governed by `sprites_policy_network_*` tools.

There is no dedicated MCP file-write tool. Prefer cloning a repository into a Sprite. For small generated files, the guidance recommends base64 transfer rather than fragile nested shell quoting.
There is no dedicated MCP file-write tool. For substantial work, clone a repository into the Sprite. For small generated files, ask OpenCode to transfer base64-encoded content rather than relying on nested shell quoting.

## What the plugin adds

- Hosted MCP access at `https://sprites.dev/mcp`.
- Sprites workflow and safety guidance in relevant sessions without adding that context to unrelated work.
- Session inheritance so a subagent working with Sprites receives the same guidance as its parent.
- Approval prompts for Sprite destruction, checkpoint restore, and complete network-policy replacement.
- Compaction guidance that preserves active Sprite names, service state, checkpoints, and pending approvals.

## Options

OpenCode's v1 plugin format accepts an options object alongside the package specifier:
OpenCode accepts an options object alongside the package specifier:

```json
{
Expand Down Expand Up @@ -95,7 +114,7 @@ OpenCode's v1 plugin format accepts an options object alongside the package spec

When `mcp` is `false`, guidance is active only if an MCP entry with the configured `mcpName` already exists.

OpenCode currently uses the same MCP timeout for the initial remote connection and discovery. The 60-second default deliberately limits startup stalls when `sprites.dev` is unreachable; increase it only when a slower endpoint warrants the longer connection wait.
OpenCode uses the same timeout for the initial remote connection and tool discovery. The 60-second default limits startup stalls when `sprites.dev` is unreachable; increase it only when a slower endpoint warrants the longer connection wait.

## Configuration and permissions

Expand All @@ -105,60 +124,37 @@ The plugin adds defaults without overwriting user-owned values:
- Existing `sprites-status` or `sprites-smoke` commands win.
- Existing exact permission rules win.
- A global `"deny"` is preserved.
- OpenCode normalizes a top-level permission string such as `"allow"` into `{ "*": "allow" }`. The plugin appends `"ask"` suffix patterns for its destructive MCP tools, which take precedence over that wildcard.
- A broad rule such as `"sprites_*": "allow"` is also overridden by the later destructive-tool patterns. Use exact rules for the guarded tools, or set `permissions` to `false`, when blanket approval is intentional.
- A broad rule such as `"sprites_*": "allow"` is overridden by the plugin's later destructive-tool patterns. Use exact rules for the guarded tools, or set `permissions` to `false`, when blanket approval is intentional.
- Setting the configured MCP server's `enabled` field to `false` suppresses injected guidance.

The default guarded patterns are `sprites_*destroy_sprite`, `sprites_*checkpoint_restore`, and `sprites_*policy_network_update`. They match both OpenCode's normal server prefix plus the verified Sprites MCP raw tool names and a redundantly prefixed raw name. Custom MCP names are sanitized the same way OpenCode sanitizes MCP tool IDs.
The guarded patterns are `sprites_*destroy_sprite`, `sprites_*checkpoint_restore`, and `sprites_*policy_network_update`.

`opencode run` rejects `ask` permissions in non-interactive mode unless `--auto` is supplied; `--auto` approves them. For automation, set exact tool permissions intentionally and review the safety consequences rather than relying on an interactive prompt.

The MCP definition includes fixed, privacy-safe client attribution headers:
The MCP connection sends fixed client-attribution headers:

```text
Fly-Client-Agent: opencode
Fly-Client-Interactive: false
```

They contain nothing user-, machine-, organization-, repository-, or prompt-specific. They are not used for authentication or authorization.
These headers contain no user-, machine-, organization-, repository-, or prompt-specific information. They are not used for authentication or authorization.

## OAuth name restrictions
## OAuth access restrictions

Restricted connector tokens use a non-empty Sprite-name prefix and may cap how many Sprites the connector can create. The common default is `mcp-`, but the prefix may be customized during OAuth. The plugin tells OpenCode to learn the actual restriction from the API and retry a failed create once with the required prefix.
Restricted connector tokens use a non-empty Sprite-name prefix and may limit how many Sprites the connector can create. The common default is `mcp-`, but the prefix can be customized during OAuth. The plugin tells OpenCode to learn the actual restriction from the API and retry a failed creation once with the required prefix.

Choosing full access during OAuth removes the prefix restriction and grants access to every Sprite in the organization. Use it only when organization-wide control is intentional.

## Safety

Sprite state is durable. Destroying a Sprite permanently deletes its filesystem, services, checkpoints, and URL. Restoring a checkpoint discards newer filesystem state. Updating a network policy replaces the complete rule set rather than merging it. These constraints are included in the session guidance because OpenCode's `tool.definition` hook does not run for MCP tools.

Treat anything served through a Sprite URL as potentially internet-accessible. Never expose secrets, environment variables, tokens, arbitrary files, admin/debug endpoints, or unfiltered logs over HTTP.

## Local development

Point OpenCode at the package directory from `opencode.json`:

```json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["file:///absolute/path/to/sprites-opencode-plugin"]
}
```

The directory form lets OpenCode resolve the package's `./server` export and enforce its `engines.opencode` range. To make a drop-in project plugin instead, copy `index.js` to `.opencode/plugins/sprites.js`; global drop-ins live at `~/.config/opencode/plugins/`.

Drop-in plugins are loaded as bare file specifiers and cannot receive the options object shown above. Use npm or package-directory configuration when you need non-default options.

Install dependencies and run all checks:

```sh
npm ci
npm run check
```
Sprite state is durable:

The Node test suite includes a real `opencode debug config` integration test in isolated XDG directories. CI also runs a Bun import/configuration smoke test because OpenCode installs npm plugins with Bun.
- Destroying a Sprite permanently deletes its filesystem, services, checkpoints, and URL.
- Restoring a checkpoint discards newer filesystem state.
- Updating a network policy replaces the complete rule set rather than merging it.

OpenCode's system-transform hook does not identify small-model calls. Once a session is active, OpenCode title or summary generation that reuses the same session ID may therefore receive the Sprites guidance too; unrelated sessions remain unaffected.
Treat anything served through a Sprite URL as potentially internet-accessible. Never expose secrets, environment variables, tokens, arbitrary files, admin or debug endpoints, or unfiltered logs over HTTP.

## Troubleshooting

Expand All @@ -173,9 +169,9 @@ Replace `sprites` in those commands if you configured another `mcpName`.

Do not install the Sprites CLI, use raw Sprites API calls, invent access tokens, or register a second Sprites MCP server as an authentication workaround.

## Release
## Contributing

Publishing requires access to the `@flydotio` npm organization. For the initial package publish, configure an `NPM_TOKEN` secret in the repository's `npm` GitHub environment. Publishing a GitHub release then runs the release workflow with provenance. After the package exists on npmjs.com, configure trusted publishing for this repository and remove the `NODE_AUTH_TOKEN` fallback from the workflow.
See [CONTRIBUTING.md](CONTRIBUTING.md) for local development, testing, implementation notes, and the release process.

## License

Expand Down
Loading