Skip to content
    ↑↓ select↵ openesc close
    angeloper86

    Rundev

    opencode-rundev·v0.1.4·Tools & Commands

    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.

    GitHub stars

    1

    Monthly installs

    844

    844 in 7 days

    Composite score

    40.4

    Multi-signal model

    Last commit

    3 days ago

    2026-10-02

    Install and configure

    opencode.json

    Writes to this project's opencode.json — applies to this repository only.

    opencode.json

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

    OpenCode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.

    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.

    Similar plugins