offstage

Your agent gets its own Mac desktop. You keep yours.

offstage gives your coding agent a second macOS account, logged in behind yours. Simulators, Xcode UI tests and the app it just built open on that account's desktop. Your windows, keyboard and mouse stay yours.

claude mcp add offstage -- npx -y --package=@viraatdas/offstage@latest offstage-mcp
# ~/.codex/config.toml
[mcp_servers.offstage]
command = "npx"
args = ["-y", "--package=@viraatdas/offstage@latest", "offstage-mcp"]
// opencode.json
"mcp": {
  "offstage": {
    "type": "local",
    "command": ["npx", "-y", "--package=@viraatdas/offstage@latest", "offstage-mcp"]
  }
}
npm i -g @viraatdas/offstage

Free and MIT licensed. For Macs on Apple Silicon with Node 20 or newer. The helper account takes one setup command.

One Mac, two desktops, at the same time

Your account The screen you are looking at. Claude Code checks on the helper account and gets off your screen: yes.
A Ghostty window in your own account. Claude Code ran offstage session status: the computeruse account is logged in, off your screen, with Screen Recording and Accessibility granted, and i2Message running in it.
The computeruse account Logged in behind yours, a minute later. Another Claude Code session is testing i2Message here.
The computeruse account's own desktop: i2Message open with a search for coffee, the Dock along the bottom. None of this was on the console user's screen.

Both captures are real, taken on one Mac on 2026-09-22. The console user was watching a video in a browser the whole time. The moving cursor above illustrates the agent's loop: screenshot, decide, click, screenshot.

Every command goes somewhere that is not your screen

Agents also run tests and browsers. offstage reads each command before it runs and sends it to the cheapest place that keeps it off your display.

When your agent runs offstage runs it
xcodebuild test, xcrun simctl, XCUITests, open -a, osascript, a built .app In the second macOS account. It has its own desktop, window server and input. This is why offstage exists.
--headed, headless: false, cypress open, WebGL and GPU flags In a Linux container with a virtual display, if Docker is installed. It's a real display, just not yours.
npm test, vitest, headless Playwright and Puppeteer Right where it is. These never open a window, so wrapping them would only cost time.
An installer, a .pkg, a .dmg, hdiutil Nowhere. Both accounts share one machine, so offstage refuses these, and no flag overrides that.

Paste this into your agent

Send it as a message in Claude Code, Codex or opencode. The agent connects offstage itself and keeps GUI work off your screen from then on. Put it in your project's AGENTS.md or CLAUDE.md to make it stick.

Use offstage (https://github.com/viraatdas/offstage) for any command that
could open a window or take focus on my Mac. It runs GUI work in a second,
logged-in macOS account, so nothing appears on my screen.

Set it up if the offstage_* MCP tools are not already available:
- Claude Code: claude mcp add offstage -- npx -y --package=@viraatdas/offstage@latest offstage-mcp
- Codex: add to ~/.codex/config.toml
    [mcp_servers.offstage]
    command = "npx"
    args = ["-y", "--package=@viraatdas/offstage@latest", "offstage-mcp"]
- opencode: add to opencode.json
    "mcp": { "offstage": { "type": "local",
      "command": ["npx", "-y", "--package=@viraatdas/offstage@latest", "offstage-mcp"] } }
The tools appear once I restart you. Until then, or if MCP is not an option,
install the CLI (npm i -g @viraatdas/offstage) and use `offstage route -- <cmd>`
and `offstage run -- <cmd>`; they are the same code path as the tools.

Rules:
1. Never run these directly: Playwright, Puppeteer, Cypress, or WebDriver
   with --headed or headless: false; screen or video capture; xcodebuild;
   xcrun simctl; XCUITests; simulators; open -a; osascript; or a built .app.
   Call offstage_route to see which lane the command gets, then offstage_run
   to run it there. Commands that are already headless (npm test, npx
   playwright test) run in place at no cost, and that is the right answer.
2. To test an app with a GUI: offstage_session_launch (it waits until the app
   registers and returns its pid), then offstage_session_screenshot, decide,
   offstage_session_input, and screenshot again to confirm. Coordinates are
   points, not pixels: divide a pixel coordinate by the screenshot's scale.
   offstage_session_quit closes the app when you are done.
3. status "skipped" means nothing ran anywhere. Show me the fix line from
   diagnostics and stop. Never re-run the command outside offstage to get
   past it: that puts it on my screen.
4. "Refused" (an installer, .pkg, .dmg, or hdiutil) means offstage will not
   run it on any lane, and no flag overrides that. Tell me; running it is my
   call, not yours.
5. Two things only I can do, so ask instead of working around them: if a run
   errors because the helper account cannot read my repository, I run
   `offstage session share <dir>`; if the session lane is not set up, I run
   `offstage session setup --create` in a terminal (it needs sudo).

Set up the helper account once

Your agent can't do this part, because it needs sudo and one click from you.

  1. Install the CLI

    npm i -g @viraatdas/offstage
    offstage doctor

    doctor tells you which lanes work on this Mac and how to fix the rest.

  2. Create the helper account

    sudo offstage session setup --create

    This creates an ordinary account called computeruse and builds a small Swift daemon for it. If your terminal has Full Disk Access, it also grants that daemon Screen Recording and Accessibility; if not, it tells you which two toggles to flip. It prints the whole root script before running it.

  3. Switch to it once, then switch back

    offstage session status

    Use the user menu in the menu bar. That first login starts the daemon, and the account stays logged in behind yours until the Mac restarts. status exits 0 when it's ready.

  4. Let it read your project

    offstage session share ~/code/myapp

    If a run fails because the helper account can't read your project, share that folder with it. Sharing is read-only, one folder at a time, and unshare takes it back.

Questions people ask first

Is this a virtual machine?

No. It's a second user account on the Mac you already have, with no guest OS and nothing to boot. It takes about 3 GB of disk, where the macOS VM image we measured was a 68.8 GB download. The trade-off is that both accounts share the Mac's CPU, memory and disk.

Can the agent ever click on my screen?

The daemon posts input to its own session only, never to the global input stream that feeds your screen. If its session is ever the one on the console, it refuses to send input at all. In testing, the window server's own log showed every synthetic event landing in the helper session and none reaching the console.

What can the helper account read?

It's an ordinary second user on your Mac, so it can't write to your files. It can read what macOS lets any other local account read. offstage session share gives it read-only access to a folder it can't otherwise reach, and each run writes its output to its own artifacts folder.

Which agents does it work with?

Claude Code, Codex and opencode, through MCP, and Claude Code also as a plugin. Anything else that can run a shell command can use the offstage CLI, which runs the same code.