scripts/runtime.mjs
#!/usr/bin/env node
// Runs the checkout during development, or the published JS entrypoint after installation.
import { spawn } from "node:child_process"
import { existsSync, mkdtempSync, rmSync } from "node:fs"
import { tmpdir } from "node:os"
import { dirname, resolve } from "node:path"
import { fileURLToPath } from "node:url"
const checkout = process.env.D3K_RUNTIME_ROOT || resolve(dirname(fileURLToPath(import.meta.url)), "../../..")
const source = resolve(checkout, "src/runtime/cli.ts")
const built = resolve(checkout, "dist/runtime/cli.js")
const args = process.argv.slice(2)
const command = existsSync(source) ? "bun" : existsSync(built) ? process.execPath : "pnpm"
const prefix = existsSync(source) ? [source] : existsSync(built) ? [built] : ["--package=dev3000@>=0.0.184", "dlx", "d3k-runtime"]
// pnpm reads workspace configuration even for dlx. Acquire the package outside
// the app, then let the runtime restore the original project directory.
const bootstrap = command === "pnpm" ? mkdtempSync(resolve(tmpdir(), "d3k-bootstrap-")) : undefined
const cleanup = () => { if (bootstrap) rmSync(bootstrap, { recursive: true, force: true }) }
const child = spawn(command, [...prefix, ...args], {
stdio: "inherit",
cwd: bootstrap,
env: bootstrap ? { ...process.env, COREPACK_ENABLE_PROJECT_SPEC: "0", D3K_BOOTSTRAP_CWD: process.cwd() } : process.env
})
for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => child.kill(signal))
child.on("error", error => { cleanup(); console.error(error.message); process.exitCode = 1 })
child.on("exit", (code, signal) => { cleanup(); process.exitCode = code ?? (signal ? 1 : 0) })
SKILL.md
---
name: d3k
description: Test and debug a web app in its existing d3k browser, including local Chrome connected to a Vercel Sandbox over CDP. Use for UI checks, browser interactions, screenshots, and reproducing app issues when d3k is running.
---
# d3k
Own the project's dev server and monitored browser through a small background runtime. No global d3k installation is needed. Evidence stays in bounded memory; browser authentication stays in the project Chrome profile.
Use `node <skill-directory>/scripts/runtime.mjs` as `runtime` below, from the **target project root**. In a development checkout the helper runs the source with Bun; installed skills use the JavaScript package through `pnpm dlx` (Node.js 24+ and pnpm required). Package acquisition runs in an isolated temporary directory so the target app’s pnpm workspace settings do not affect it; runtime commands still run against the target project. To try unreleased source from another project, set `D3K_RUNTIME_ROOT` to the dev3000 checkout. Do not use a previously published package to test unreleased changes.
## Desktop MCP connection
When d3k MCP tools are available, use them directly instead of the shell runtime below. They manage a remote project without a local checkout. Call `sandbox_sessions`, then `sandbox_start` with `project: "team/project"` or `resume: "<saved-id>"`. Poll `sandbox_status` until `phase: "ready"`; startup is asynchronous. If another companion owns a saved session, report it instead of taking over or killing it.
An explicit request for a remote sandbox takes precedence over a linked local repository. Use `project` and the requested `branch` to create a fresh remote checkout. Reuse with `resume` or `attach` only when requested by the user. If creation fails, report its actual error; do not assume the repository is private, attach to an old checkout, discard the requested branch, or fall back to editing locally.
For a phone user or an explicit phone-preview request, use `sandbox_start` with `browser: "link"`, then present the ready `sandbox_status.preview.url` as a clickable link and its `expiresAt`. Never give the phone a `.localhost` URL. Companion Chrome runs headlessly for agent testing; it cannot control the phone's browser. The link grants session app access, so share it only in the user's chat and keep it out of PRs/public logs. It expires with the session and is regenerated on resume; use `browser: "link"` again when resuming. Local stdio carries no phone User-Agent: use explicit user/client context, not the emulated viewport. The macOS companion and access to its local MCP server are still required; this does not provide phone-only ChatGPT connectivity. App sign-in may require allowing the preview origin's exact callback URL with the auth provider.
Use `sandbox_exec`, `sandbox_read_file`, and `sandbox_write_file` for all project files and commands remotely. Read remote AGENTS.md before editing. Use `sandbox_runtime` with `args: ["status"]` and cursor-based events for evidence. Use `sandbox_browser` with `args: ["snapshot", "-i"]` and other agent-browser interaction arguments for the existing local Chrome; no CDP discovery or separate browser is needed. `sandbox_github` publishes Git objects and PRs through local repository-scoped GitHub authentication. Keep credentials out of remote commands. Do not clone or save project code/env files locally.
If the dev runtime stops or the app returns 502, use `sandbox_recover` with `action: "start"`, then poll `action: "status"` until recovered or failed. Recovery keeps the agent, local Chrome and remote files; it does not clone, reinstall, replay commands, renew Gateway keys or extend compute lifetime. Reread runtime status and reproduce afterward; old in-memory evidence is lost on restart.
Keep the session running during user testing. `sandbox_stop` returns stopping; poll until stopped. Stopping or disconnecting the MCP server closes Chrome and d3k-owned compute, preserves remote files, and leaves a resumable session. One active sandbox per server. No terminal harness or Gateway key is created; the desktop client supplies the model. The desktop conversation may retain source returned by tools.
For one-time desktop setup, use `d3k mcp --print-config`. Configure its local stdio command/args/environment in ChatGPT Desktop's MCP settings or merge the generated entry into Claude Desktop's `mcpServers` configuration, preserving other servers. The command uses absolute paths and a GUI-compatible PATH; regenerate it after replacing the installation. Local Vercel login and canonical Portless must be ready before starting a session. The CLI's `mcp` entry point also supplies these instructions when the standalone skill isn't installed.
## Prepare
For an already connected d3k session, use its existing runtime and browser. The dev server may be running remotely in the sandbox even when the harness UI is local. Inspect status through the connected session before inferring that a server needs starting; do not start a local server to verify the remote app. After a user-visible UI edit, verify the changed page in the paired browser and read the relevant events before reporting completion. Report the actual command/tool error if verification is blocked.
1. Run `runtime portless status`. Canonical HTTPS must be ready (`canonical: true`, `serviceInstalled: true`, `setupRequired: false`) before starting the app or browser. If needed run `runtime portless setup`, wait for the one-time administrator authorization, then check again. Stop if setup is unavailable; never silently substitute direct localhost.
2. Run `runtime status`. Reuse a ready runtime for this project. If it is starting, wait for readiness. Do not start another dev server.
3. Start `runtime start` in a retained background tool session. Keep its process handle. The runtime detects the package manager and `dev` script; use `--command '<dev command>'` when needed. Poll `runtime status` until `ready: true` and `browserConnected: true` with a port-free `https://...localhost` URL. Inspect `runtime events` if startup fails while the runtime is still alive, and the retained process output if it exits.
## Reproduce and inspect
- For “let me test,” give the user the app URL and leave the browser running while they reproduce the issue.
- For autonomous debugging, drive the same browser with `runtime browser snapshot -i`, `runtime browser click @e2`, and other agent-browser commands. The runtime supplies the CDP connection; never open a separate browser or profile.
- For page-load metrics, use `runtime browser metrics https://example.com/ --new-tab` (MCP: `sandbox_browser` with `args: ["metrics", "https://example.com/", "--new-tab"]`). This navigates a new tab with observers installed first and returns Web Vitals, navigation timing, long tasks, and resource summaries as JSON. Prefer this to custom eval scripts. Omit the URL to inspect the current page without reloading; CLS and long tasks then report unavailable. Metrics are a provisional snapshot using the existing cache and throttling, not Lighthouse or field data. `null` means unavailable; zero transfer bytes alone do not establish cache usage.
- After responsive/device testing, restore window-sized rendering with `runtime browser set viewport 0 0 0` (MCP: `sandbox_browser` with `args: ["set", "viewport", "0", "0", "0"]`). The zeros restore native dimensions and device scale. Verify the page fills the headed window before handing it back. Do not restore to a guessed desktop size such as 1280×900; that leaves a fixed viewport when the user resizes the window.
- Before a reproduction, read `runtime status` and keep its `sessionId` and `cursor`.
- After the interaction, read `runtime events --session <id> --after <cursor>`. Server output, console messages, exceptions, network requests, and navigation share sequence numbers. Requests and responses carry CDP request IDs; adjacent server lines are chronological context, not proof of causation.
- Network events include redacted headers, wire-header events, protocol/cache metadata, transfer size, and response timing. Derived durations are milliseconds; `cdpTiming` preserves CDP units (`requestTime` in monotonic seconds, phase offsets in milliseconds; `-1` means unavailable). App-origin Fetch/XHR JSON and form bodies have bounded, credential-redacted previews (8 KiB; bodies over 64 KiB are omitted). `response-body` events can arrive after `network-complete`; read a fresh interval after completion before concluding a body is absent. Cross-origin, binary/streaming, unavailable, and oversized bodies report omission reasons. Redaction is best-effort; payloads may still contain sensitive application data.
- Continue from the returned `cursor` while `hasMore` is true. Use `--limit 1..500`, `--source server|browser|runtime`, or `--level error` for focused reads. For error context, read the unfiltered interval as well. Filters advance the cursor through examined events; retain the original cursor if you need another view of the same interval.
- `reset: true` means the runtime restarted. `dropped` reports evicted events. Never claim complete evidence across either gap. Replay the interaction if necessary.
- Fix the code and repeat the same interaction, then inspect the new interval. Keep the runtime alive across edits.
## Pull requests
When the user asks for a PR in a cloud d3k sandbox, that authorizes committing the intended changes, publishing a feature branch and opening a draft PR. Proceed without another confirmation. Read project instructions, inspect the diff and status, and stage only intended files with explicit paths; leave unrelated changes and untracked artifacts out. Include newly created source files only when they are part of the requested change. Create a feature branch from the current base unless already on the task's feature branch; preserve intended working changes and do not reset or discard other work. Confirm `git rev-parse HEAD` before publishing.
Use the user's configured author/committer preference, including the Git identity supplied by the companion. Inspect the effective identity before committing. If no preference is configured, ask once for their name and email and remember it in the checkout's Git configuration for resumed sessions. Never invent a placeholder identity. Run project-required checks and an available build or lint; report any checks that were not run and why.
After committing, use the deterministic publisher from the remote project root:
```sh
/tmp/d3k-tools/node_modules/.bin/d3k-runtime publish --base main --title "Describe the change" --body-file /tmp/pr-body.md
```
Use the actual base branch and write the description file remotely. Local/desktop harnesses run this through `sandbox_exec`. This command reads committed Git objects directly, preserves message newlines and author/committer timestamps, verifies every uploaded SHA before updating a ref, and creates a draft PR or returns the existing open PR. It checks branch and PR state on retry, never force-pushes, and keeps GitHub credentials on the laptop. Do not manually reconstruct Git Data requests or try an unauthenticated push first. It publishes only the current committed feature branch; uncommitted changes and untracked files stay out. A connected laptop companion with working local GitHub authentication is required.
The publisher supports up to 100 linear, unsigned feature commits with UTF-8 commit text/paths, at most 64 MiB / 10,000 uploaded objects, and individual requests below the bridge's 1 MiB limit. Signed/merge commits, extra commit headers, oversized objects or SHA mismatches fail without changing the branch ref. Use existing managed Git authentication for those cases when available; report the concrete limitation rather than inventing alternate metadata or asking for tokens. Never force-push or run `gh auth login`. A requested PR does not authorize merging it.
## Lifecycle
Keep the runtime's project-stable profile for sign-in state. Use `--headless` for CI or when requested; `--servers-only` deliberately omits the browser. Automatic screenshots and persistent log files are off; request a screenshot through `runtime browser screenshot <absolute-path>` when visual evidence is needed.
Stop with `runtime stop` or the retained process handle when asked. Leaving the browser open for user testing means leaving the runtime alive. Events are lost when it exits; only profile and session discovery data persist. Console/server text may contain application secrets; inspect selectively and avoid copying sensitive values into reports.
## Vercel Sandbox with local Chrome
For a remote project with no local checkout, run `runtime sandbox --project <name-or-id> --scope <team> --name <sandbox-name> --no-shell` on the laptop in a retained background session. The launcher requires Vercel CLI login, Bun, Node.js 24+, and macOS. d3k uses the installed standard Google Chrome binary with a dedicated per-project profile and filesystem confinement; browser updates remain managed by the existing installation or IT. It creates a sandbox from the project's linked public GitHub repository, pulls Development env through Vercel CLI, installs dependencies, and starts the remote skill runtime. For a private repository, use `--attach <name>` with an existing sandbox that already contains the checkout. Private-repository access must be configured separately. Do not supply GitHub or Vercel management tokens to the app environment.
The app uses `https://<project>.d3k.localhost`; `--hostname songbook` selects `https://songbook.d3k.localhost`. Canonical Portless is required on the laptop before launch, with no Portless inside the VM. A loopback streaming proxy forwards verified HTTPS app traffic and a session token to the sandbox. Add the exact local auth callback URL to the provider allowlist. The laptop companion opens a dedicated local Chrome and maintains an authenticated outbound WSS bridge; project source and dev compute stay remote. Dev commands must honor `PORT`. Keep the companion alive throughout debugging.
For authorized pushes and PRs, follow the Pull requests section above: commit intended files, then run `d3k-runtime publish --base <actual-base> --title <title> --body-file <remote-description-file>` in the remote project root. Local/desktop harnesses use `sandbox_exec` for that command. The lower-level `sandbox_github` / `runtime github` API remains available for repository-scoped inspection; the publisher owns object construction, SHA verification, safe branch updates and draft PR creation. If the companion is disconnected, leave publishing pending until it reconnects.
Run status, browser, events, and stop commands **inside the sandbox's project directory**, using `/tmp/d3k-tools/node_modules/.bin/d3k-runtime`. For example, use `vercel sandbox exec <name> --project <project-id> --scope <team> --workdir <remote-project-root> -- /tmp/d3k-tools/node_modules/.bin/d3k-runtime browser snapshot -i`. Supply a fresh project OIDC token obtained from `vercel project token <project-id> --scope <team> --format json` as the CLI process's `VERCEL_OIDC_TOKEN` environment variable if needed; capture it without printing it or putting it in command arguments. The interactive launcher handles this authentication automatically. Read the remote `status` before driving Chrome and reuse that runtime; do not start another runtime or browser. New interactive `d3k --project team/project` (or `d3k sandbox`) sessions default to Eve, with remote execution and its native terminal UI locally. Eve uses pinned dependencies, a $1 Gateway cap and up to 30 minutes of access; `/model` switches models. Cross-launch conversation resume and laptop-close integration remain incomplete. `--no-shell` omits the harness. The following fx details apply when using fx or an existing plain-shell session: the shell enters the remote project directory and adds tools to PATH. Type `fx` there; d3k installs fx if absent and automatically connects it to the project’s team Gateway with a project-scoped key that expires automatically after `--timeout` minutes (default: 60, shared with sandbox compute); d3k prints its expiration time when closing the session. Its non-renewing budget is $5 including BYOK usage; use `--gateway-budget <usd>` to change it or `--no-gateway` for manual authentication. Laptop harness credentials and Vercel management tokens stay local. `--no-fx` skips installation. Project processes share the harness identity and can access its session Gateway key; use trusted projects. Without a TTY, use `vercel sandbox exec` or `vercel sandbox connect` separately. Remote Codex and Claude need their own authentication. The interactive shell does not inherit app env automatically.
App traffic streams through the local proxy to the sandbox, preserving the browser origin, redirects, cookies, and WebSockets. Batch browser operations to reduce CDP round trips, select a nearby sandbox region, and avoid continuous screenshots. HMR works through the in-VM proxy. A bridge disconnect records an evidence gap and closes pending channels without replaying commands; reread status and reproduce after reconnecting before claiming complete evidence.
Stopping the local launcher stops its runtime and a sandbox it created while preserving the sandbox filesystem. `--attach` leaves the existing VM running. The sandbox agent can control the dedicated Chrome profile, and the public sandbox app endpoint requires the companion session token; application authentication remains the app's responsibility. Keep agent credentials separate from infrastructure credentials; never describe remote browser control as adding no security risk.
The launcher retries failed runtime-status requests without interrupting the coding shell. A confirmed remote runtime exit keeps an already-open interactive shell alive; exit and resume to restore the dev server and browser bridge. The sandbox timeout still applies. Avoid broad process-kill commands or stopping the shared runtime during dependency upgrades.
The sandbox terminal sends WebSocket keepalives. A socket closure without a remote exit frame leaves the companion and sandbox running and prints a reconnect command. Reconnect from another terminal; do not automatically restart a harness or replay its commands because its last action may have completed. Ctrl-C in the retained launcher closes the session.
## CLI aliases and evidence views
`d3k`, `dev3000`, and `d3k-runtime` use the same implementation. Local startup is `start`; root options are for sandbox mode. Timeout flags use milliseconds. `logs --count <1..500> --type server|browser|runtime|all` and `errors --context` read recent events from the same live timeline; use `--json` to retain evidence-gap metadata. Prefer cursor-based `events` for reproductions. The runtime does not install agent packages, write log files, or automatically upgrade. Use `browser` rather than the removed `agent-browser` subcommand.
Sandbox resume: `d3k --resume` lists locally saved session IDs, projects, sandbox names, branches, URLs, and last-used times. `d3k --resume <id>` restores that sandbox checkout and hostname, refreshes Development env and dependencies, restarts the dev runtime, and pairs the retained browser profile using fresh bridge/proxy tokens. No repository checkout is needed on the laptop. Ctrl-C closes the browser and prints the resume command to stdout. Saved entries contain discovery metadata, not credentials or past terminal output; remote availability is checked on resume. Optional `--command`, `--hostname`, `--timeout`, `--harness-location`, `--no-shell`, `--no-fx`, `--no-gateway`, `--gateway-budget`, and `--headless` override launch settings. Sandboxes originally created by d3k stop compute again on resumed exit; externally attached sandboxes remain running.
Use `d3k start --harness codex` locally or `d3k --project my-team/my-project --harness fx` remotely to launch a coding harness after readiness. Eve is the default for new interactive sandbox sessions; `fx`, `codex`, and `claude` are alternatives. Eve always runs remotely with a local native client and does not accept `--harness-location sandbox`. For the alternatives, commands, paths, and extra arguments are rejected. An interactive terminal is required, and exiting the harness closes a session started by that invocation. For fx, Codex and Claude in a sandbox project, `--harness` runs locally by default; `--harness-location sandbox` selects the remote harness. Local fx uses a stable control-directory identity recorded per d3k session; other local harnesses use a temporary control directory. Both receive preloaded routing instructions and a remote-command helper; Codex/Claude also receive per-launch MCP configuration. Use sandbox_exec/read_file/write_file for remote code, sandbox_runtime for remote logs, and sandbox_browser for direct local Chrome control. Use sandbox_github for the linked repository’s Git Data and PR APIs with local gh authentication; do not forward GitHub credentials into the sandbox. No project checkout or env file is saved locally, but harness conversations may retain code returned by tools. Resume retains the selected location. The following remote installation and context details apply to `--harness-location sandbox` or the plain remote shell. Harnesses must already be installed and authenticated, except for existing sandbox fx setup. Codex/Claude receive the connected-session skill directly in their startup prompt. fx receives a temporary skill context through `--add-dir`; in the sandbox, a temporary managed block in `~/.fx/AGENTS.md` also preloads the connected-session skill, including the browser connection, exact remote commands, and evidence workflow, including when typing `fx` from the shell. Existing instruction content is preserved and the block is removed on exit. No project instruction files are overwritten. Local harnesses use local credentials; sandbox fx can use the temporary team Gateway key. `--resume <id>` remembers the harness choice and passes `fx -c` when launching fx, restoring its remembered conversation in the same workspace. fx owns conversation persistence. Other harness conversations are not automatically resumed; in a plain remote shell, type `fx -c` yourself. Local connection instructions are regenerated and broker credentials removed on exit.
Sandbox Chrome and its children run under macOS filesystem confinement. Do not bypass it or substitute an unrestricted browser. The outer policy replaces Chrome's inner sandbox because macOS cannot nest them. Only Chrome, required system files, and the dedicated profile and temporary files are readable; unrelated laptop files are denied. The profile uses a local password store rather than the laptop keychain. Browser sessions remain accessible to sandbox automation. Other platforms fail closed until equivalent isolation is available.