SKILL.md

いつボックスを使い、どの順でツールを呼ぶかをエージェントに教える markdown ファイル 1 つ。Claude Code にインストールするか、任意のエージェントの指示に貼り付けます。

Claude Code にインストール

スキルは ~/.claude/skills/<name>/SKILL.md に置きます。下の内容は /skill/SKILL.md で配信しているものと同じファイルで、タスクが description に合致するとエージェントが自動で読み込みます。

mkdir -p ~/.claude/skills/parallelsandbox
curl -fsSL https://parallelsandbox.com/skill/SKILL.md -o ~/.claude/skills/parallelsandbox/SKILL.md

他のエージェント

Codex、Cursor、自作のループ:ファイルの内容をシステムプロンプトかプロジェクト指示に、MCP サーバー設定と並べて置きます。Claude 固有の内容は含まれていません。

内容

ParallelSandbox: your remote computer

Connect once: claude mcp add --transport http parallelsandbox https://mcp.parallelsandbox.com/mcp --header "Authorization: Bearer <API key>". When you need sandbox_sync (uploading uncommitted local files), run the stdio adapter instead: PARALLELSANDBOX_API_KEY=<key> npx -y parallelsandbox-mcp.

A box is a Linux machine: 2 vCPU, 16 GB memory, its own Docker, git, Node 22, Go, Python, an Xvfb virtual display :99 (1280x800) with Chromium, ffmpeg. Working directory /work. Boxes cannot see each other and carry no cloud credentials. Nothing on a box survives sandbox_stop or an interruption.

Rules:

  1. Edit code locally. Files in the box are copies: put them in with git clone (committed work) or sandbox_sync (uncommitted), run and test there, bring results back with sandbox_get. Never edit files in the box and forget to bring the change back.
  2. One box per task. Reuse it for the whole task; call sandbox_stop when done. Free accounts run 3 boxes at once, Pro 5, Max 20.
  3. A running box bills about 100 credits an hour, by the minute, with no time limit. Pass idleTimeoutMin at start if you might forget it; nothing else stops it except credits reaching zero.
  4. If a tool returns interrupted, the cloud reclaimed the machine. Start a new box and redo the steps from the beginning; do not retry on the old id.
  5. A box left idle may be frozen (its memory is snapshotted and it stops billing). Any tool call on it thaws it first, which adds a couple of seconds; nothing is lost. sandbox_stop works on a frozen box too.
  6. Secrets enter the box as environment variables only when named in sandbox_start.secrets. Never paste secret values into commands; use $NAME. Never put your ParallelSandbox API key in a box.

One pass

  1. sandbox_start with services: every name and port the repo exposes on the box, for example [{ "name": "api", "port": 3000 }]; the first service is what the scene URL reaches. Add secrets names if the repo needs credentials, externalBaseUrl if unchanged services live elsewhere, idleTimeoutMin if you might forget the box. You get id, sceneUrl (https://<id>.box.parallelsandbox.com), takeoverUrl, status and startedFrom (spare: a warm box, ready in seconds; snapshot: a microVM restored from a snapshot, a few seconds; cold: a machine was started for you, under 90 seconds). If status is not ready, call sandbox_status until it is.
  2. Put the code in:
    • sandbox_exec { "cmd": "git clone https://github.com/<owner>/<repo>.git" } for committed work. Private repos: store a token as a secret and clone with https://x-access-token:$GITHUB_TOKEN@github.com/..., then git remote set-url origin to the clean URL.
    • sandbox_sync { "localPath": ".", "dest": "<repo>" } for uncommitted work (stdio adapter only; node_modules, .git, dist, build are excluded). Sync again after every local change.
  3. Build and run: sandbox_exec { "cmd": "docker compose up -d --build --wait", "cwd": "<repo>", "timeoutSec": 600 }. Services without compose: build as usual and run with background: true; you get a bgId and a log file under /work/.sbx/ that you read with another sandbox_exec (tail -n 100 <logPath>).
  4. Wire: sandbox_wire { "service": "<name>", "mode": "box" } for each service you run in the box; inside the box the name now resolves to your container on its port. mode: "external" sends a name to externalBaseUrl instead. Without externalBaseUrl every service is box from the start. The result is the whole wiring table; check with sandbox_exec { "cmd": "curl -s http://<name>:<port>/" }.
  5. Test: run the repo's tests with sandbox_exec. Browser tests that should be visible run with DISPLAY=:99 HEADED=1.
  6. Show your work: sandbox_shot for a screenshot (also returned inline); record: "start" before a run and record: "stop" after for an mp4; sandbox_get for any file or directory under /work (directories come as tar.gz). All three return download URLs valid for one hour; put them in your answer.
  7. sandbox_stop.

Tools

Tool Use it for
sandbox_start new box; returns id, sceneUrl, takeoverUrl, status, startedFrom
sandbox_exec one shell command; cwd relative to /work; timeoutSec for foreground; background: true returns bgId, output under /work/.sbx/
sandbox_sync local directory into /work/<dest> (stdio adapter only)
sandbox_get file or directory out, as a download URL
sandbox_wire point a service name at the box or at externalBaseUrl; returns the whole wiring table
sandbox_shot screenshot of the display (target: "screen") or of a URL opened in a fresh Chromium (target: "url"); record: "start" / "stop" for mp4
sandbox_status state, boxd health (uptime, idle time, background processes, display), wiring, registry login, takeoverUrl, the open takeover, interrupted reason
sandbox_takeover ask a person to act on the live screen; blocks until they hand back (max 30 minutes) and returns their message
sandbox_secrets names of stored secrets, never values
sandbox_publish_version / sandbox_versions register an image you pushed to the tenant registry as a named version; list versions
sandbox_stop destroy the box
logs_search / logs_errors / logs_tail logs from pages using @parallelsandbox/log; logs_errors has stacks resolved through uploaded source maps

Seeing the screen

Anything started with DISPLAY=:99 shows on the virtual display. To look at a web page:

sandbox_exec { "cmd": "DISPLAY=:99 chromium --no-sandbox --kiosk --window-size=1280,800 --user-data-dir=/tmp/chrome http://localhost:3000/", "background": true }
sandbox_shot { "id": "<id>" }

Click with DISPLAY=:99 xdotool mousemove X Y click 1, type with xdotool type "...", using coordinates from the screenshot.

When a person is needed

Logins, two-factor codes, CAPTCHAs, payments, anything you must not do alone: sandbox_takeover { "id": "<id>", "note": "<what to do and why>" }. The person sees your note and the live screen in the ParallelSandbox app; the call blocks until they press Hand back (or 30 minutes pass) and returns the message they leave. While a person is connected, sandbox_exec returns a locked error; wait for the takeover to finish instead of retrying in a loop. If your client cuts the call off, the takeover stays open: sandbox_status shows it, and calling sandbox_takeover again with the same id resumes waiting. takeoverUrl from sandbox_status is the same live screen for a person you tell directly.

Secrets

sandbox_secrets lists the names the account owner stored in the app. List the ones you need in sandbox_start.secrets; they become environment variables of every sandbox_exec command and never appear in logs or health output.

Versions

A build another box should reuse: push the image to the tenant registry (sandbox_status.registry.imagePrefix is the exact prefix; the box is already logged in), then sandbox_publish_version { "service": "<name>", "label": "<label>", "image": "<prefix><label>", "gitSha": "<sha>" }. sandbox_versions lists what other boxes can docker run or reference from a compose override instead of rebuilding.

Logs from pages

If a page you run uses @parallelsandbox/log, query its project instead of asking the user for console output:

logs_errors { "project": "prj_...", "since": "30m", "boxId": "<id>" }
logs_search { "project": "prj_...", "since": "1h", "query": "<text>" }

logs_errors returns stacks resolved through the source maps uploaded for that release.

Trouble

  • sandbox_status first. healthError means boxd is not up yet; interrupted means start over on a new box.
  • status values: spare (warm, unclaimed), claimed (starting for you), ready, takeover (a person is on it), frozen (idle; the next call thaws it), stopping, terminated, interrupted.
  • A service does not answer through its wired name: check docker compose ps and that the port is published with ports: in compose; the wiring table must list the name.
  • sandbox_exec says locked: a person has the screen. Wait.
  • A 429 means you hit the plan's box limit; stop one first. A 402 means credits are at zero.
  • The scene URL returns 503 until the box is ready and 404 after it stops.
  • Downloads expired: the URLs last one hour; call sandbox_get or sandbox_shot again.