diff --git a/.opencode/skills/opencode-drive/SKILL.md b/.opencode/skills/opencode-drive/SKILL.md new file mode 100644 index 00000000000..6633526b109 --- /dev/null +++ b/.opencode/skills/opencode-drive/SKILL.md @@ -0,0 +1,254 @@ +--- +name: opencode-drive +description: Use when an agent needs drive OpenCode via a script or interact with an isolated instance +--- + +# OpenCode Drive + +Use `opencode-drive` to launch an isolated OpenCode instance and control it via commands or a script. + +There are two modes. Always default to using a script unless specifically directed to be interactive (connect +to an existing running instance, or start a new one, and make a few changes to the UI and read it, and iterate +on changes). + +Scripts allow you to run a full walkthrough in one run. When the script is done opencode-drive exits, +stops all processes, and cleans up all artifacts. + +# Prepare The Environment + +Use `init` when files must be added to the isolated home or project before OpenCode starts. It prints the artifact directory without launching OpenCode. A later `start` with the same name reuses it. + +```bash +artifacts=$(opencode-drive init --name demo) +cp -R ./fixtures/home/. "$artifacts/" +cp -R ./fixtures/project/. "$artifacts/files/" +opencode-drive start --name demo --dev ~/projects/opencode +``` + +The simulated project is under `$artifacts/files`. Running `start` without a prior `init` initializes the artifacts automatically. + +# Scripted usage + +You can write scripts that walk through entire flows, and gives you full access to controlling +the backend too. See examples of the script API at the bottom of this file. + +After creating or editing a script, always typecheck it before running. Never skip this step: + +```bash +opencode-drive check ./reproduce-stale-exploring-empty.ts +``` + +Run it by passing `--script` to start: + +```bash +opencode-drive start --name auto-stop-reproduction --script ./reproduce-stale-exploring-empty.ts +``` + +It will output information about the run, including paths to log files which you can read +to inspect what happened. If you need to dig into failures that aren't clear, read those log +files. If the script is unsuccessful, automatically fix the script and run it again. + +Scripts use one typed definition object. `setup` runs before OpenCode starts, +and `fs.writeFile` always writes inside the simulated project. + +You can read the full typed API here: https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/src/script/types.ts + +```ts +import { defineScript } from "opencode-drive" + +export default defineScript({ + async setup({ fs, config }) { + config.autoupdate = false + await fs.writeFile("src/example.ts", "export const value = 1\n") + }, + + async run({ ui, llm }) { + await ui.submit("Open src/example.ts") + await llm.send(llm.text("The file exports `value`.")) + await ui.waitFor("The file exports `value`.") + }, +}) +``` + +`setup` receives the current OpenCode config object, which starts from the +default drive config unless the prepared instance already has one. When a script +needs custom config, mutate this `config` parameter instead of generating and +writing a new config object from scratch, so the script keeps the default +provider/model settings unless it intentionally changes them. + +Note that the simulated model is a GPT model type, and opencode uses the `patch` tool for working with files Do not use a `edit` or `write` tool to edit files. + +Use `launch: "manual"` when the script needs to launch the server and every TUI +itself (this is extremely rare, do not use this unless explicitly asked). In this +mode `ui` is typed as `null`; call `server.launch()` exactly +once before launching clients. Each `clients.launch(name)` result provides the +same UI methods as the automatic client. You can see an example of this API +here: https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/examples/multiple-clients.ts + +Use the exported `wait(milliseconds)` utility for an unconditional delay. + +`await llm.send(...)` waits for the next request and resolves after OpenCode +acknowledges its complete response. `llm.queue(...)` declares responses in +advance. Chunks may be built with `text`, `reasoning`, `toolCall`, `raw`, +`finish`, and `disconnect`. A normal response receives `finish("stop")` +automatically unless it yields or queues an explicit terminal event. + +`llm.text(text, { delay, chunkSize })` defaults to a 2 ms delay and a +15-character target varied by plus or minus 5 per chunk. + +`llm.reasoning` accepts the same options, and `llm.pause(milliseconds)` adds a +delay between any two outputs. + +Use `llm.serve` for an ongoing typed response generator: + +```ts +llm.serve(async function* (request, index) { + yield llm.reasoning(`Handling request ${index + 1}`) + yield llm.text(`Received ${request.id}`) + yield llm.finish("stop") +}) +``` + +The backend connection, response cleanup, cancellation, and recording +completion are automatic. + +You can see some example scripts here: + +- https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/examples/simple.ts +- https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/examples/serve.ts + +## Prune + +- `prune` removes artifact directories. These are always cleaned up after running a script + successfully, but leftover on failed runs. Always call this if a script fails. + +```bash +opencode-drive prune --name demo + +// --force cleans up all artifcat directories +opencode-dirve prune --force +``` + +# Live interaction usage + +- Always give headless instances a unique `--name`. Visible instances may omit it. +- A normal headless `start` detaches automatically and returns after the instance is ready. +- Do not add `&`; the long-running owner already runs in the background. +- Configure simulated model responses after startup when needed. +- Send ordered UI commands with `send`. +- Always stop the instance when finished. + +```bash +opencode-drive start --name demo + +opencode-drive send --name demo \ + --command.ui.type '{"text":"Explain this project"}' \ + --command.ui.enter + +opencode-drive stop --name demo +``` + +## Send UI Commands + +- Every `send` opens a connection to the named instance, runs its commands in order, and exits. +- Combine typing and Enter in one command when submitting a prompt. +- JSON-valued commands require one JSON argument. +- Multiple command flags execute from left to right. + +Commands: + +- `--command.ui.type ` types into the focused editor. Arguments: `text` string. +- `--command.ui.press ` presses a key. Arguments: `key` string; optional `modifiers` object with boolean `ctrl`, `shift`, `meta`, `super`, or `hyper`. +- `--command.ui.enter` presses Enter. Arguments: none. +- `--command.ui.arrow ` presses an arrow key. Arguments: `direction` is `up`, `down`, `left`, or `right`. +- `--command.ui.focus ` focuses an element. Arguments: `target` is the numeric element `num` returned by `ui.state`. +- `--command.ui.click ` clicks an element. Arguments: numeric `target`, `x`, and `y`; use the element `num` returned by `ui.state` as `target`. +- `--command.ui.state` prints focus and interactive element metadata as JSON. Arguments: none. +- `--command.ui.matches ` prints whether literal, case-sensitive text appears on screen. Arguments: `text` string. + +```bash +opencode-drive send --name demo \ + --command.ui.type '{"text":"Find the relevant code and explain it"}' \ + --command.ui.enter + +opencode-drive send --name demo \ + --command.ui.press '{"key":"p","modifiers":{"ctrl":true}}' + +opencode-drive send --name demo \ + --command.ui.arrow '{"direction":"down"}' + +opencode-drive send --name demo \ + --command.ui.focus '{"target":12}' + +opencode-drive send --name demo \ + --command.ui.click '{"target":12,"x":4,"y":1}' + +opencode-drive send --name demo \ + --command.ui.matches '{"text":"OpenCode"}' +``` + +To read the UI state and see information about interactable elements, use the `ui.state` command: + +```bash +opencode-drive send --name demo --command.ui.state +``` + +## Configure LLM Responses + +- `responses` controls what the LLM responds with +- Only use this if you are wanting to reproduce an exact type of response +- Defaults are `text,reasoning,diff,tool` with `write,apply_patch`. +- Supported types are `text`, `reasoning`, `diff`, and `tool`. +- `--tools` limits generated tool calls to names offered by OpenCode. + +```bash +opencode-drive responses --name demo \ + --types text,reasoning,diff,tool \ + --tools write,apply_patch + +opencode-drive responses --name demo \ + --types tool \ + --tools read,glob,grep +``` + +## Inspect The UI + +- `ui.state` prints focus and interactive element metadata as JSON. +- `ui.matches` checks for literal, case-sensitive screen text. +- `screenshot` prints the generated image path. + +```bash +opencode-drive screenshot --name demo +``` + +## Lifecycle + +- `stop` waits for recording export and owner cleanup before returning. + +```bash +opencode-drive stop --name demo +``` + +# Record The UI + +- Start with `--record` to capture a headless instance from its first rendered frame. +- `stop` finishes the recording, exports an MP4, and prints its path. + +```bash +opencode-drive start --name demo --record + +opencode-drive send --name demo \ + --command.ui.type '{"text":"Show me the current architecture"}' \ + --command.ui.enter + +opencode-drive stop --name demo +``` + +# Artifacts dir + +- `dir` prints the artifact directory for the instance. + +```bash +opencode-drive dir --name demo +``` +