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

@daytona/opencode

v0.193.0

Published

OpenCode plugin that automatically runs all sessions in Daytona sandboxes for isolated, reproducible development environments

Readme

Daytona Sandbox Plugin for OpenCode

This is an OpenCode plugin that automatically runs OpenCode sessions in Daytona sandboxes. Each session has its own remote sandbox which is automatically synced to a local git branch.

Features

  • Securely isolate each OpenCode session in a sandbox environment
  • Preserves sandbox environments indefinitely until the OpenCode session is deleted
  • Generates live preview links when a server starts in the sandbox
  • Synchronizes each OpenCode session to a local git branch

Usage

Installation

To add the plugin to a project, edit opencode.json in the project directory:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["@daytona/opencode"]
}

Now that the Daytona plugin is in the plugins list, it will automatically be downloaded when OpenCode starts.

To install the plugin globally, edit ~/.config/opencode/opencode.json.

Environment Configuration

This plugin requires a Daytona account and Daytona API key to create sandboxes.

Set your Daytona API key as an environment variable:

export DAYTONA_API_KEY="your-api-key"

Or create a .env file in your project root:

DAYTONA_API_KEY=your-api-key

Creating sandboxes from a snapshot

By default, sandboxes are created from the Daytona default snapshot. To create them from a specific snapshot instead — for example one that pre-installs your project's toolchain and dependencies — set DAYTONA_SNAPSHOT to the snapshot name:

export DAYTONA_SNAPSHOT="my-snapshot"
DAYTONA_SNAPSHOT=my-snapshot

The snapshot must already exist and be active in your organization; create one via the Daytona Dashboard or the Daytona CLI. If the name doesn't resolve, sandbox creation fails with an error naming the missing snapshot rather than silently falling back to the default.

Leave DAYTONA_SNAPSHOT unset to keep the default behavior. The plugin still creates /home/daytona/project and syncs your git branch into it.

Verifying the sandbox SSH gateway host key

Git syncing transfers commits between your machine and the sandbox over SSH through the Daytona SSH gateway (ssh.app.daytona.io). Like any SSH server, the gateway identifies itself with a host key, and the plugin verifies it before any transfer. Verification works in one of three modes, checked in this order:

| Mode | When | What is trusted | | --- | --- | --- | | Manual pin | DAYTONA_SSH_KNOWN_HOSTS is set | Only the keys in that file | | Auto-pin (default) | Otherwise, unless DAYTONA_SSH_AUTO_PIN=false | Only the gateway host key published by the Daytona API (/api/config), pinned on first use into a plugin-managed file (storage/daytona/gateway_known_hosts) | | Inherited | Auto-pin is disabled, or no key is published and none is pinned | Your SSH client's normal host verification (~/.ssh/known_hosts, trust-on-first-use prompts) |

In the two pinned modes the pin file is the only trust root for sandbox transfers: system-wide known hosts are ignored and StrictHostKeyChecking=yes is set. SSH behavior for every other remote is unaffected.

Auto-pin is fail-closed against change. The API is consulted once per plugin start; the pin file is what connections use, so once a key has been pinned an API outage never weakens verification. (On a machine that has never pinned anything, an unreachable API means there is nothing to pin yet and the plugin falls back to inherited verification — set DAYTONA_SSH_KNOWN_HOSTS if a first run must already be strict.) If the API later publishes a host key that does not include the pinned one, transfers are refused with a message pointing here. That means either the gateway rotated its key — the security policy publishes old and new keys together during a rotation, so a healthy client should not hit this — or something between you and the API is not Daytona. Verify the published key against the security policy; if it is legitimate, delete the pin file to pin the new key.

Manual pin is for environments that want a human-verified trust root independent of the API — supervised agent runs, CI, compliance-driven setups. The gateway host key is published as a ready-to-use known_hosts line in Daytona's security policy; copy it into a file and point the plugin at it:

mkdir -p ~/.config/daytona
# paste the `ssh.app.daytona.io ssh-ed25519 ...` line from the security policy into:
$EDITOR ~/.config/daytona/known_hosts
export DAYTONA_SSH_KNOWN_HOSTS=~/.config/daytona/known_hosts

To cross-check that the live gateway presents the published key, compare fingerprints: ssh-keyscan ssh.app.daytona.io 2>/dev/null | ssh-keygen -lf - must print the fingerprint listed in the security policy. Do not build the file from ssh-keyscan alone — that trusts whatever answered on first connection, which is exactly what pinning is meant to avoid. If the manual file disagrees with the key the API publishes, the plugin logs a warning but keeps using your file.

The example above is for the shared gateway on the default SSH port. For a gateway on another host or port (self-hosted or dedicated regions — check sshGatewayHost and sshGatewayPort in GET /api/config), the known_hosts host field must use OpenSSH's [host]:port form, or the entry will never match:

[gateway.example.com]:2222 ssh-ed25519 AAAA...

and the cross-check becomes ssh-keyscan -p 2222 gateway.example.com | ssh-keygen -lf -. Auto-pin produces the correct form automatically.

Paths containing spaces are supported; a literal " in the path is rejected.

Running OpenCode

Before starting OpenCode, ensure that your project is a git repository:

git init

Now start OpenCode in your project using the OpenCode command:

opencode

To check that the plugin is working, type pwd in the chat. You should see a response like /home/daytona/project, and a toast notification that a new sandbox was created.

OpenCode will create new branches using the format opencode/1, opencode/2, etc. To work with these changes, use normal git commands in a separate terminal window. List branches:

git branch

Check out OpenCode's latest changes on your local system:

git checkout [branch]

To view live logs from the plugin for debugging, run this command in a separate terminal:

tail -f ~/.local/share/opencode/log/daytona.log

How It Works

File Synchronization

The plugin uses git to synchronize files between the sandbox and your local system. This happens automatically and in the background, keeping your copy of the code up-to-date without exposing your system to the agent.

Sandbox Setup

When a new Daytona sandbox is created:

  1. The plugin looks for a git repository in the local directory. If none is found, file synchronization will be disabled.
  2. A parallel repository is created in the sandbox with a single opencode branch, mirroring the checked out local branch.
  3. A new sandbox-N remote is added to the local repository, pointing at the sandbox repository over SSH. The remote URL contains no credentials: each transfer creates a short-lived SSH access token, supplies it only to that git invocation as the SSH user, and revokes it as soon as the transfer finishes. Remotes created by earlier plugin versions are rewritten to the credential-free form automatically. Sandbox transfers always run with a plugin-controlled OpenSSH command (your GIT_SSH_COMMAND, GIT_SSH, core.sshCommand and ssh.variant settings keep applying to all other remotes but are not used for sandbox transfers); to use a non-default OpenSSH client for them, set DAYTONA_SSH_BINARY to its absolute path.
  4. The HEAD of the local repository is pushed to opencode, and the sandbox repository is reset to match this initial state.
  5. Each sandbox is assigned a unique incrementing branch number (1, 2, 3, etc.) that persists across sessions.

Synchronization

Each time the agent makes changes:

  1. A new commit is created in the sandbox repository on the opencode branch.
  2. The plugin pulls the latest commits from the sandbox remote into a unique local branch named opencode/1, opencode/2, etc. This keeps both environments in sync while isolating changes from different sandboxes in separate local branches.

The plugin only synchronizes changes from the sandbox to your system. To pass local changes to the agent, commit them to a local branch, and start a new OpenCode session with that branch checked out.

[!CAUTION] When changes are synchronized to local opencode branches, any locally made changes will be overwritten.

Sync guarantees

The per-turn sync runs in the background: OpenCode dispatches the session.idle event without waiting for plugin work, so observing that event does not mean the changes have reached your local repository yet. The plugin provides three stronger boundaries:

  • gitSync tool — commits pending sandbox changes and pulls them into the local opencode/N branch, returning only after they are in the local repository. Failures are returned as tool errors, and its result also reports when a previous automatic sync or the initial git setup had failed. Automation that needs a reliable handoff (for example, a supervisor driving OpenCode through the SDK) should ask the agent to run gitSync as its final step and check the tool result instead of treating session.idle as proof that changes have landed.
  • Persistent git-return state — every transition of a session's return path is recorded in the session's entry in the per-project state file under gitReturn: pending (established, no sync yet), synced, failed (with the git error), setup-failed (the initial push at sandbox creation failed), or disabled (no local git repository). Failures on the automatic paths — initial setup, idle syncs, and a shutdown that exceeded the drain window — are all recorded there, so a host can verify a session's return completed by reading that field instead of parsing logs or trusting silence.
  • Session deletion — before deleting a sandbox, the plugin waits for any in-flight sync and pulls remaining changes from a running sandbox. If unsynced changes cannot be pulled — including when the local repository is no longer accessible — deletion is aborted and the sandbox is preserved. A sandbox that is not running is deleted without being started: anything in it was either synced while it ran or is abandoned by the explicit delete.
  • Shutdown — when OpenCode shuts down gracefully, it waits (up to 60 seconds) for the plugin to finish in-flight syncs before exiting.

Session to sandbox mapping

The plugin keeps track of which sandbox belongs to each OpenCode project using local state files. This data is stored in a separate JSON file for each project:

  • Default (when XDG_DATA_HOME is unset): ~/.local/share/opencode/storage/daytona/[projectid].json.
  • When XDG_DATA_HOME is set: $XDG_DATA_HOME/opencode/storage/daytona/[projectid].json.

Each JSON file contains the sandbox metadata for each session in the project, including when the sandbox was created, when it was last used, the worktree the session runs in, and the session's current gitReturn state (see Sync guarantees).

The plugin uses XDG Base Directory specifically to resolve the path to this directory, using the convention set by OpenCode.

Development

This package lives in the daytona/integrations monorepo under packages/opencode-plugin, and is self-contained — its own package.json, lockfile, and dependencies (no workspace tooling).

Setup

git clone https://github.com/daytona/integrations
cd integrations/packages/opencode-plugin
npm install

Development and Testing

To modify the plugin, edit the source code files in .opencode/plugin.

To test the OpenCode plugin, create a test project to run OpenCode in:

mkdir ~/myproject
cd myproject

Add a symlink from the project directory to the plugin source code:

ln -s [ABSOLUTE_PATH_TO_REPO]/packages/opencode-plugin/.opencode .opencode

Initialize git to enable file syncing:

git init

Start OpenCode in the test project:

opencode

Use the instructions from Running OpenCode above to check that the plugin is running and view live logs for debugging.

[!NOTE] When developing locally with a symlink, OpenCode loads the TypeScript source directly, so no build step is required.

Building

Build the plugin — tsc compiles .opencode/plugin/**/*.ts to .js + .d.ts in place:

npm run build

The published package contains the compiled .js/.d.ts; the .ts sources are stripped by .npmignore.

Test the built package

After building, create a test project and add a plugin file to load the built plugin (replace [ABSOLUTE_PATH_TO_REPO] with your clone path, e.g. /Users/you/integrations):

mkdir -p ~/myproject && cd ~/myproject
mkdir -p .opencode/plugins
cat > .opencode/plugins/daytona-local.js << 'EOF'
module.exports = require('[ABSOLUTE_PATH_TO_REPO]/packages/opencode-plugin/.opencode/plugin')
EOF

Initialize git to enable file syncing, and start OpenCode:

git init
opencode

Publishing

Releases are automated: merging this package's release-please Release PR builds it and publishes the compiled package to npm (public, with provenance) from the repo's release workflow — there is no manual publish step.

Project Structure

packages/opencode-plugin/
├── .opencode/plugin/              # Plugin source (TypeScript)
│   ├── daytona/                   # Main Daytona integration
│   └── index.ts                   # Plugin entry point (compiled to .js in place)
├── .gitignore
├── .npmignore
├── package.json                   # Package metadata (main/types + build script)
├── tsconfig.json                  # TypeScript config
└── README.md

License

Apache-2.0