Skip to content

Repository files navigation

opencode-rundev

npm version CI license

The VS Code "Run & Debug" for the terminal-first world.

rundev brings a repo's dev environment up and down from OpenCode — containers, dev servers, the emulator or simulator, and a browser with the project's own profile. No LLM in the loop, no Ctrl+F5, no orphaned processes left behind.

/rundev up            # start whatever is missing (idempotent, non-blocking)
/rundev status        # what is up, who owns it, what was left orphaned
/rundev down          # stop what rundev started, verify, report
/rundev init          # scan the repo and draft the manifest (once per repo)
/rundev logs api      # follow a service log
/rundev doctor        # validate the manifest and the environment

Demo

rundev in action

The checkbox picker on the left (space toggles, enter brings up the chosen members) next to the live sidebar on the right, as a sibling of OpenCode's own blocks. In the same workspace, below, the agent answers with rundev_status — no configuration was written by hand.

More screenshots (sidebar · report · picker)

Sidebar

Sidebar

One block per repo, only the repos with something running, every service with its port and state. Inside a repo it collapses to that repo's own services.

Report

Report dialog

Every verb reports the same way, and the report is also mirrored into the transcript as a shell message, so the model reads it as context.

Picker

Picker

All screenshots come from a throwaway workspace, never from a real project: docs/demo/setup.sh builds three fake repos in /tmp whose services are sleep processes (so the pids and the liveness in the sidebar are real), and docs/demo.tape records them with vhs.

Full flow

Why

Working with an agent in the terminal is great until you have to bring the project up:

  • the agent improvises: it reads prose, guesses commands, waits too long and leaves things hanging;
  • what the agent started cannot always be stopped later;
  • opening the app ends up using your browser, with your session and your history.

rundev moves that into a declarative per-repo manifest plus a deterministic engine. The agent decides what it needs; the command knows how it starts and how it stops.

Install

// opencode.json(c)
{
  "plugins": ["opencode-rundev"]
}

Requirements: OpenCode ≥ 2.0, macOS. The terminal panel strategy targets Ghostty (shift+cmd+D split); anywhere else it falls back to the clipboard / a new window.

The manifest: .opencode/rundev.json

Every service declares how it is checked and how it is stopped. If it cannot be verified or stopped, it does not belong in the manifest.

{
  "version": 1,
  "default": ["db", "api"],
  "services": {
    "db": { "kind": "compose", "file": "docker-compose.yml", "service": "mysql", "port": 3306 },
    "api": {
      "kind": "process",
      "up": "yarn dev",
      "port": 4000,
      "health": "http://localhost:4000/health"
    },
    "web": { "kind": "process", "up": "yarn dev -- --port 5173 --strictPort", "port": 5173 },
    "browser": { "kind": "browser", "url": "http://localhost:5173", "profile": ".opencode/.chrome-profile" },
    "app": {
      "kind": "interactive",
      "defaultTarget": "android",
      "targets": {
        "android": { "device": "emulator", "envSection": "ANDROID", "requires": ["emulator"], "check": "adb shell pidof com.example.app" },
        "ios": { "device": "simulator", "envSection": "IOS", "requires": ["simulator"] }
      }
    },
    "emulator": { "kind": "emulator", "avd": "pixel_8_api_36", "waitMs": 180000 },
    "simulator": { "kind": "simulator", "device": "iPhone 16", "waitMs": 90000 }
  }
}

Kinds

kind What it is How it is checked How it is stopped
compose A docker compose service docker compose ps docker compose stop (never -v)
process A host server (yarn dev, deno task dev) pidfile + check/health SIGTERM to the group, then verify
browser Chrome with the project profile pgrep by profile closes only that profile
interactive Something that opens a panel (flutter run) check when declared, otherwise the launch marker rundev forgets the panel
emulator An Android AVD adb devices adb emu kill, fire-and-forget (it may save a quick-boot snapshot)
simulator An iOS simulator xcrun simctl list booted xcrun simctl shutdown, fire-and-forget

Machine overrides

Machine-specific values are detected for you: init runs flutter emulators / xcrun simctl list and writes the AVD and simulator it finds into .opencode/rundev.local.json (gitignored). You can still edit that file by hand — it just is not required.

{ "services": { "app": { "targets": { "android": { "device": "pixel_8_api_36" } } } } }

Uninstall

/rundev uninstall --dry-run     # see what would be removed
/rundev uninstall               # stop what is running, remove state, chrome profile, .gitignore entries
/rundev uninstall --all         # also remove .opencode/rundev.json (the manifest you wrote)

It never touches your files: only the state it generated, the profile it created and the .gitignore lines it added. The global bits (the plugins entry in your OpenCode config) are printed as instructions, not modified.

House rules

  • up is idempotent and non-blocking: it starts and returns; whatever is already up is left alone. That includes panels: rundev remembers the panel it opened, so a second up never opens a duplicate. down app forgets it and up app --force relaunches it on purpose. Adding check to an interactive service (or to a target, for a service that switches device) makes the panel verifiable, and then up relaunches by itself when the app is gone.
  • down only stops what rundev started. A process of yours holding the port is reported, never killed.
  • It never deletes volumes or data.
  • .env is only verified: if the active section does not match the requested target, up stops and tells you. Switching it is explicit (/rundev env IOS).
  • requires orders the launch: the app pulls its emulator/simulator first, waiting (bounded) for boot before opening the panel.
  • A terminal panel inherits the cwd of the focused panel, so every typed command starts with cd '<repo-root>' && — validated before typing.

Sidebar

The TUI plugin renders a live block in the sidebar with the repo's services and their state (from the engine's snapshot plus pid liveness). Reports open in a dialog; progress shows up as toasts.

Workspace (sibling repos / monorepo)

When several repos live together, each with its own manifest, drop a rundev.workspace.json at the parent (/rundev init there writes one for you):

{
  "name": "acme",
  "members": ["acme-api", "acme-web", "acme-app"],
  "dependencies": { "acme-app": ["acme-api"] }
}
  • members are only used with --all: /rundev status --all, /rundev up --all, /rundev down --all.
  • dependencies are only honoured in workspace mode: /rundev up inside acme-app brings acme-api up first (its own default set).
  • Outside a workspace there is no cross-repo awareness at all: a repo only ever sees its own manifest.

Timeline

Every report is mirrored into the session as a shell message (!cat …), exactly like running a command with ! in the composer: it stays in the history and the model reads it as context, with no extra model turn. That is why synthetic messages are not used for this — those queue as user messages and would be answered in the next turn.

Tools for the agent

The same engine is exposed as tools, so the agent can start what it needs without burning turns guessing: rundev_status, rundev_up, rundev_down, rundev_logs.

Development

npm run typecheck                                          # types, including the TUI JSX
npm run smoke                                              # engine smoke test (no docker, no keystrokes)
node --experimental-strip-types dev/harness.ts <dir> "<verb>"   # plugin pre-flight, no server

The package ships TypeScript source (src/): OpenCode's plugin runtime loads it directly, the same way it loads .opencode/plugins/*.ts.

Status

v0.1 — developed and tested on macOS with Ghostty.

About

Run & Debug for the terminal-first world: bring a repo's dev environment up and down from OpenCode — containers, dev servers, emulators — with no LLM in the loop.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages