# Offsite documentation Complete contents of https://offsite.sh/docs, one page per section. # Offsite for agents: in daily use Source: https://offsite.sh/docs/agents/daily-use For an AI agent running inside an Offsite session. How to write output the user can act on, which Offsite tools to use for what, and what the host and client split means for how you work. This page is for an AI agent running inside an Offsite session. Offsite's system prompt points here, and the host keeps a copy of it next to that prompt, so you can read it without network. ## Where you are Your session is a tmux session on the user's **host**, a Mac or a Linux machine. The user sees it as a card on a board in the Offsite app on their Mac or iPhone, often from somewhere else, often on the phone. The user's other sessions are cards on the same board. What follows from that: - **Nobody may be at the host's screen.** A dialog there (a permission prompt, a sudo window, an installer) can wait forever. Avoid it, or say exactly which dialog to expect before you trigger it. - **Never `open` a file or URL on the host.** It lands on the host's screen, and in a sandboxed session it fails. Print links for the user; look at pages yourself in the Agent Browser. Leave other apps alone: no activating, no windows to the front, no AppleEvents (`osascript`). - **Leave Offsite's tmux server alone.** `$TMUX` points at it and its sessions are the user's cards. A task that needs tmux gets its own server: `unset TMUX; tmux -L …`. - **Long-running processes go to the background**, with your tool's background mechanism or `nohup … > log 2>&1 &`. Check them through the log or the Agent Browser. - **A sandboxed session has no network from its shell, localhost included.** The Agent Browser runs on the host and reaches every local port. If starting a server fails with `Operation not permitted`, the sandbox also blocks listening: ask the user to start it, or to change the session's permission mode. - **Offsite's internals belong to the host.** Drive Offsite through the tools below; the `offsite-host` binary, its RPC and the files under its state directory are the host's. ### Your environment | Variable | Meaning | |---|---| | `OFFSITE_SESSION_ID` | this session's id | | `OFFSITE_WORKSPACE_ID` | the workspace root path, the directory you started in | | `OFFSITE_TOOL` | `claude`, `codex`, `grok` or `terminal` | | `OFFSITE_SESSION_KIND` | `agent` or `terminal` | | `OFFSITE_WORKTREE_PATH`, `OFFSITE_WORKTREE_BRANCH`, `OFFSITE_WORKTREE_BASE_COMMIT`, `OFFSITE_SOURCE_CHECKOUT`, `OFFSITE_PORT_BASE`, `OFFSITE_PORT_BASE_END` | set in a [worktree](/work/worktrees/) session; apps started with `worktree_run` also get `OFFSITE_PORT`, `OFFSITE_RUN__PORT` and `OFFSITE_RUN_NAME` ([ports](/work/worktrees/ports/#the-environment)) | ### Your tools The MCP server `offsite_computer` is already in your tool list. Each tool's description says what it takes and returns; this is the map. | Tool | When | What happens | |---|---|---| | `session_name`, `session_recap` | first response; end of every turn | your card's name and one-line recap | | `session_finish` | the user asked you to close the session | the card is archived after this turn; the user brings it back from the archive at any time | | `web_open`, `web_snapshot`, `web_click`, … | to look at or test a web page | the Agent Browser on your card | | `worktree_create` | the user or the task says to do work in a separate worktree; in an Offsite session this is how such a worktree is made, `git worktree add` makes one Offsite does not manage | a worktree of this repository with its own board, branch and ports, and an agent in it with the task; you report what you created and carry on | | `session_start` | the user asks you to delegate work to another agent, or to continue this work in a new session | a new agent card in your workspace (or a named one) with the task as its first message; `context: "handoff"` makes it read your transcript first; you report what you started and carry on | | `agents_list` | the user asks what the other agents are doing, or names a session | the user's running agent sessions: id, card name, tool, workspace, activity, latest recap | | `agents_context` | the user asks what a session did, found or decided | that session's transcript, newest last; `from` is an id, an id prefix or the card name | | `worktree_status`, `worktree_run`, `worktree_update_from_base`, `worktree_publish`, `worktree_create_pr`, `worktree_merge` | inside a worktree session | the worktree's state, its Run card, and the steps the user asked for | | `worktree_setup_declare`, `worktree_setup_done` | inside a worktree session, when the user asks you to work out its setup | writes `.offsite/worktree.json` in the worktree, then reports the setup to the user | | `screenshot`, `left_click`, `type`, … | computer use on a Mac host | the host's screen and input | The `offsite` CLI (the system prompt names its full path) reads the user's other sessions (`offsite agents`) and is the fallback for your card and the Agent Browser when the MCP server is missing (`offsite session`, `offsite web`). `offsite --help` lists its commands. ## Output the user can act on ### File paths Write every file you mention as a path the user can open with one click: ⌘-click on the Mac, a tap on the iPhone. Offsite opens it in a Files card (the one already showing the file, else the one the user works in, else a preview card) at the line you named. | You write | The user gets | |---|---| | `ios/App/Login.swift` | a file in the workspace, relative to its root | | `./README.md`, `./Makefile`, `./scripts/build` | a file in the root, or a path without a dot; it needs the `./` | | `/etc/hosts`, `~/notes/plan.md` | a file outside the workspace, by its path on the host | | `ios/App/Login.swift:42`, `ios/App/Login.swift:42:9` | the file at line 42 (the column is ignored) | | `ios/App/Login.swift:42-58`, `ios/App/Login.swift#L42-L58` | the file at lines 42 to 58 | - A link needs a `/`, so a file in the root starts with `./`. A file name alone such as `Login.swift` stays plain text. - Relative paths resolve against the workspace root, whatever directory your shell has moved to. In a worktree session the root is the worktree. - Write the path itself as the visible text, not as the label of a Markdown link: the click reads what is on screen. - Punctuation after a path (a full stop, a comma, a closing parenthesis) stays outside the link. ### Show it in the Agent Browser Anything you can show with web tech (an animation, a UI concept, a chart, an HTML fragment) you can put in front of the user right away. `web_open ` docks your Agent Browser onto your own card as a tile the user can enlarge, and they see it on every device at once. - **A page your tool makes for you.** If your tool can make such a page itself, for example Claude's artifacts, use it: it is faster and better than building one by hand. Open the result with `web_open` when its address loads without a sign-in. The Agent Browser has its own profile with none of the user's logins, so a page that needs their account (a private artifact on claude.ai does) shows a login page there: print its link instead, for the user to open in their own browser. - **A page you write yourself.** Put it in a scratch folder, serve that folder on a free local port in the background (`python3 -m http.server 8642 --bind 127.0.0.1`), then `web_open http://localhost:8642/`. In a worktree, take the port from your window, `$OFFSITE_PORT_BASE` to `$OFFSITE_PORT_BASE_END`. - The Agent Browser loads `http` and `https` addresses; a `file://` path is refused. - `web_viewport iphone-15` (or another device preset) shows the page at a phone's size; `web_viewport responsive` returns to the card's own size. - Say what you opened and what to look at. ### Links Print URLs as plain text. On the Mac a ⌘-click opens them in a Browser card; on the iPhone a tap offers the Browser card, Safari, or Copy. A `localhost` address opens in the Browser card through a tunnel to the host's own port, so `http://localhost:5173/` is the link to give for a dev server. ## Your session card Your card sits on the board next to every other session, and the user's notifications carry its name. - In your first response, before the work: `session_name` with at most three words (about 15 characters). If it answers that the user named the card, keep their name for the rest of the session. - At the end of every turn: `session_recap` with one line under 30 words, what just finished and what is next or blocking. Check the name in the same step and call `session_name` again when the work has moved on. - Both in the language the user writes to you in. - Only the user-facing main agent calls them. A subagent, a background, memory or review agent never does. - Without the MCP server: `offsite session name ''` and `offsite session recap ''`. More: [Cards that name themselves](/work/self-naming/). ## The user's other sessions ```bash offsite agents list # agent sessions with activity, workspace, name, recap offsite agents list --state attention # filter: --workspace, --tool, --state, --json offsite agents context --from # that session's transcript, newest last offsite agents context --from --tail 50 ``` - The MCP tools `agents_list` and `agents_context` do the same and work inside a sandbox too; the CLI commands remain for any terminal. - Reading is a read from disk: the other session is never contacted and does not notice. Your own session is marked `(this session)`. - You see every agent session on the host, not only your workspace's. A workspace organises work, it is not a wall: a session that must stay unseen runs on another host or user account, and that is the user's call, not yours. - Sessions are read-only to you. There is no `send`, and `tmux send-keys` into another pane is off limits: text typed into a running agent merges with whatever the user has half typed there and is submitted with their authority. - To move work on, suggest the user's own actions on a card's **More Actions** menu: **Hand off to a NEW session** starts a fresh session that pulls this one's context; **Ask another session** answers a question from another session's transcript. More: [Hand off and ask](/work/handoff-and-ask/). ## What the user sends you - **Files.** The user drops files on your card or uploads them from the iPhone. They land on the host, in an upload folder for your session, and their paths arrive in your prompt. Read them there. - **Marked images.** An image can come with numbered markers and notes, as a block between `----- Attached images -----` and `----- end of attached images -----`: one section per image with its path, `Overall` for the whole picture, then `Marker 1`, `Marker 2`, matching the numbers drawn on it. An image marked in a browser card adds `Where this comes from` with the page URL, a CSS selector and the `web_open` line that shows you the page. More: [Marking up what you send](/board/annotate/). ## Testing a web app in the Agent Browser The Agent Browser is a Chromium on the host, the only browser you have there. Use it for the app you build; leave Playwright, Puppeteer, a bundled browser plugin and `open` aside. 1. `web_open http://localhost:`. The address is the host's own port. 2. `web_snapshot` returns the page as text, one element per line, with refs like `[e12] button "Save"`. 3. Work the page by ref, CSS selector or `x,y`: `web_click e12`, `web_fill e3 `, `web_select e5 `, `web_type`, `web_press Enter`. 4. `web_screenshot` when only pixels answer the question; `web_eval ''` to assert state; `web_wait` for load or a selector. - Refs stay valid until the next navigation or snapshot. - You have one docked browser: `web_open` again navigates it. `detached: true` makes a free card next to yours. `web_list` lists the workspace's Agent Browser cards, and every verb takes `card` to address one of them. - The viewport follows the card the user sees. Switch to a device only for a layout test, and back to `responsive` afterwards. - The user can click into the page and take over; your next command continues in the same page. - When the web tools refuse, Browser Use is off in the host's settings and only the user can turn it on. When the host has no browser for it, tell the user what to install. More: [Agent Browser](/cards/agent-browser/). ## Worktrees In a worktree session Offsite adds a block to your prompt with the branch, its base and the rules. In short: work only in the worktree and stay on its branch; start the app with `worktree_run`, which runs it on a Run card on a port from the worktree's window; use `worktree_update_from_base`, `worktree_publish`, `worktree_create_pr` and `worktree_merge` when the user asks for that step; commit, push or merge only when asked. Deleting a worktree is the user's. `worktree_create` makes a worktree of this repository the way the user would (workspace, board, the repository's saved setup, port window) and starts an agent in it with the task you pass; `tool: "terminal"` makes one without an agent. From a worktree session it makes a sibling worktree of the same repository. Write the task complete in itself: that agent has no access to your conversation. Tell the user the worktree's name, branch and path, then carry on with your own work; the new agent reports to the user on its own card. `worktree_status`, `worktree_run`, `worktree_setup_declare` and `worktree_setup_done` run straight away. `worktree_create`, `worktree_update_from_base`, `worktree_publish`, `worktree_create_pr` and `worktree_merge` go through your CLI's permission prompt unless the session runs without one. More: [Worktrees](/work/worktrees/). ## Closing your session When the user asks you to close the session (often together with a commit), finish the work, call `session_finish` with a last recap line, say goodbye and end your turn. The host archives the card once the turn is over; the user restores it from the archive whenever they want. ## Computer use on a Mac host Screen and input go through the `offsite_computer` tools (`screenshot`, `left_click`, `type`, `key`, `scroll`, `zoom` …), which run in the signed Offsite Agent Bridge that holds the Mac's Screen Recording and Accessibility permissions. When scripting an app is unavoidable, tell the user first and use `run_applescript`, never raw `osascript`. When a call reports that computer use is locked, ask the user to tap the monitor icon in your card's toolbar, then retry. More: [Computer Use](/computer-use/computer-use/). ## iOS Simulator on a Mac host The user mirrors a Simulator running on the host in a Simulator card and picks the device there; the card shows whatever runs in it and sends their taps back. Build and install with the usual Xcode tools (`xcodebuild`, `xcrun simctl`) and say which device you used. More: [Simulator](/cards/simulator/). ## Questions about Offsite When the user asks why a message appeared or why something behaves as it does, check [Troubleshooting](/help/troubleshooting/) first: it lists the system messages, quirks and errors already explained, as Symptom, Why and Fix, including a macOS notice that names Offsite for a program run inside a session. The rest of the documentation is at [offsite.sh/docs](https://offsite.sh/docs). --- # Offsite for agents Source: https://offsite.sh/docs/agents/for-agents A dense, single-page briefing for AI agents asked about Offsite, what it needs, how to install it, and where to send a user. This page is written for AI agents answering questions about Offsite. An agent running inside an Offsite session reads [Offsite for agents: in daily use](/agents/daily-use/) instead. If you are a human, everything here is covered in more depth elsewhere in these docs. ## What Offsite is An agentic development environment. A **host** (macOS or Linux) runs coding-agent and terminal sessions inside tmux; native **clients** (macOS, iOS) attach to them live. Sessions are host-owned, so they survive client disconnects, network changes, sleep and reboot. Offsite starts the real vendor CLIs — `claude`, `codex`, `grok` — under the user's own subscription. It is not a wrapper and not an API proxy; no token passes through Offsite. ## Requirements | | | |---|---| | Host | macOS 14+ on Apple Silicon, or Linux (glibc ≥ 2.28 — Ubuntu 20.04+/Debian 10+) | | Linux CPU caveat | Claude Code needs **AVX2** on x86_64. Check: `grep -q avx2 /proc/cpuinfo`. Codex and Grok do not. | | Host needs | `tmux`, `git`, `python3`, at least one agent CLI installed and logged in | | Clients | macOS 26+, iOS 18+ | | Not required | an account, Remote Login/`sshd`, an inbound port, an admin password for standard setup | ## Install Offsite is in private beta; the Mac app comes with the invitation from the [waitlist](https://offsite.sh/#waitlist). It sets up This Mac as a host, and remote Macs and Linux hosts over SSH. Full walkthrough: [Install & set up](/start/install/). ## Feature map | Capability | Page | Host requirement | |---|---|---| | Agent sessions (Claude/Codex/Grok), permission modes | [Agent sessions](/work/agent-sessions/) | any | | Terminal sessions | [Sessions](/work/sessions/) | any | | Git review, stage, commit, push | [Git card](/cards/git/) | any | | File browse and edit (host-side tabs and drafts; the user sees your writes live) | [Files card](/cards/files/) | any | | The user's own browser — no agent access, but they can mark a page and send it to you | [Browser card](/cards/browser/) | any | | Browser you drive with the `web_*` tools, docked to your card | [Agent Browser](/cards/agent-browser/) | any (macOS and x86-64 Linux hosts bring their own; ARM Linux needs an installed Chromium-family browser) | | iOS Simulator mirror | [Simulator card](/cards/simulator/) | macOS | | Install an iOS build on the user's phone | [Deploy to this phone](/cards/deploy-to-phone/) | macOS | | Screen control for a session | [Computer Use](/computer-use/computer-use/) | macOS | | Read another session, hand context to a new one | [Hand off and ask](/work/handoff-and-ask/) | any | | Marked-up images the user sends you (numbered markers + notes) | [Marking up what you send](/board/annotate/) | any | | Saved commands, schedules, agent prompts | [Workspace Actions](/cards/workspace-actions/) | any | | Push notifications | [Notifications](/mobile/notifications/) | any | ## Running inside an Offsite session Every agent session starts with a short briefing from Offsite, and the briefing names [Offsite for agents: in daily use](/agents/daily-use/): how to write file paths and links the user can open with one tap, how to show a page in the Agent Browser, the session card, reading other sessions, and what the host and client split means for an agent's work. The host keeps a copy of that page on disk, so an agent in a session reads it without network. ## When something does not work **Before you diagnose:** [Troubleshooting](/help/troubleshooting/) lists the system messages, quirks and errors already explained, each as Symptom → Why → Fix. When the user pastes a message or asks why something appeared, check it first; the answer is often there, including the case where a macOS notice names Offsite for a program the user ran inside a session. ## Nothing of the user's on our side - No account, no server-side user record. - No telemetry: no analytics SDK, no crash reporter. - No model of its own and no token metering. - No writing into your global tmux configuration. - No cross-host orchestration — each host owns its own sessions. ## Where to send a user | Question | Page | |---|---| | "What is this?" | [The big picture](/start/big-picture/) | | "How do I install it?" | [Install & set up](/start/install/) | | "What will macOS ask me?" | [What macOS will ask you](/start/what-macos-asks/) | | "Is it safe / what leaves my machine?" | [Security and privacy](/reference/security/) | | "How does it actually work?" | [Architecture](/reference/architecture/) | | "Something is broken" | [Troubleshooting](/help/troubleshooting/) | | "Why does macOS show this message / why does it name Offsite?" | [Troubleshooting](/help/troubleshooting/), section *Messages from macOS* | --- # Marking up what you send Source: https://offsite.sh/docs/board/annotate Circle it, number it, write one line per number. The agent gets the marked image and your notes as text, instead of guessing what "the blue one" meant. "Make this bigger and the blue one darker" is a sentence an agent cannot act on. So you write a paragraph describing where things are, or you send a screenshot and hope the agent finds what you meant. Offsite puts a small editor in front of every image you send. Draw a shape, Offsite numbers it, you write one line per number. The agent gets the marked picture **and** the notes as text ("1: make this bigger, 2: dark blue"), with the numbers on the image matching the numbers in the list. [Figure: The annotation editor on the Mac over a checkout page: a circle around the Pay button, an arrow to the total, and a line for each number] ## Where it appears The editor sits in front of the upload, on every path that sends an image: | You do this | On | |---|---| | Drop images onto an agent card | macOS | | **Upload Files** from the card's action pill | macOS | | Upload from the command bar: Photo Library, Camera, Files | iPhone, iPad | | **Mark** an area in a [Browser](/cards/browser/) or [Agent Browser](/cards/agent-browser/) card | macOS, iPhone, iPad | Non-images skip it entirely and upload as before. ## Using it Pick a tool (**circle, rectangle, arrow or freehand**) and drag it over the part you mean. A click without dragging drops the shape at default size. Every shape gets a numbered dot in its centre, in the order you drew them; delete one and the rest renumber, so the agent never reads a list with gaps. Each shape takes one of five colours, so a marker stays visible on any picture. **Blur** hides what nobody should read, a token or a name: it pixelates the region in the image. A blurred region gets no number and no comment, and the text block leaves it out. On the Mac, the comments sit in a panel beside the picture, one field per marker. On the iPhone, tap the speech bubble in a marker's centre to write its line. A marker with a comment turns green in the editor, so you can see at a glance what you have already described. The first field is **marker 0: the whole image** (on the iPhone, the bubble on the image's thumbnail). Use it for the sentence that applies to everything, like "home screen after login, dark mode", with or without any shapes at all. Sending several images at once puts a strip of thumbnails in the editor. Each image keeps its own shapes and its own comments; switching between them saves as you go. Then **Insert** (on the Mac **Insert 3** for three images, or `⌘Return`) puts the batch into the session. On macOS: `V` for select, `1` to `5` for circle, rectangle, arrow, freehand and blur, `⌘Z` / `⇧⌘Z`, `⌫` to delete the selected shape, `←` / `→` to walk through the images, `⌘Return` to insert. ## What the agent receives The shapes are burned into the image at full resolution, and the comments travel as a plain block of text that is pasted into the session next to the file paths: ``` ===== Attached images ===== A detail from a page in my browser, plus the page around it. 2 images; the numbered markers in each picture match the notes below it. Image 1: browser-mark-….png /…/uploads/s-…/3f9c2a1b-browser-mark-….png Overall: home screen after login, dark mode Marker 1: make this bigger Marker 2: dark blue Marker 3: (marked — see the picture) Image 2: … /…/uploads/s-…/…png (for reference, no notes) Where this comes from: Page: Bloom — http://localhost:5173/ Image 1 is the part I marked, 420×260 at (64,180) in the viewport. Image 2 shows the whole viewport so you can place it. Selector: main > section.featured To see it yourself: web_open http://localhost:5173/, then web_click main > section.featured … in your Agent Browser. ===== end of attached images ===== ``` The block is fenced so an agent reads it as an attachment rather than as a truncated prompt. And when the image came from a browser card, it carries the page it came from plus a line telling the agent how to open that page in its [own browser](/cards/agent-browser/). Nothing is submitted. The text lands in the session's prompt, and you press Enter, as with every other upload. If you add no comment anywhere in the batch, no text block is generated at all: you get the plain paths, the way an upload has always worked. Your drawings are still burned into the image. ## Turning it off **Settings → Annotate images before sending**, on macOS and on iPhone. Off, every entry point uploads directly again. The editor has the same switch, **Use annotation**, so you can turn it off in the moment it gets in your way. ## Details - Images are PNG, JPEG, HEIC, WebP or TIFF. Anything else never sees the editor. - The marked image is the one that travels: what you see in the editor is the file the agent gets. - Images larger than 2048 px on the long edge are scaled down after the shapes are burned in, so the numbers stay sharp while a 10 MB retina screenshot does not have to travel. - Shapes are stored in normalized coordinates and rendered by the same code that draws the preview and the final file, so what you see is what the agent gets. - In the file the agent gets, marker numbers are white dots with a black border, black once commented. Those colours are fixed so they stay readable on any screenshot; the green of a commented marker belongs to the editor only. --- # The board Source: https://offsite.sh/docs/board/board Why Offsite uses a grid instead of tabs, how cards move between board, tabs and Icebox, and what Tidy board does. Tabs were designed for a world where you write every line yourself and jump between files. With agents, that flips: several things run at once, most of them without you, and the question is no longer "where am I?" but "who needs me?". So Offsite gives you a **grid board** instead. Cards sit side by side, all live, all visible at a glance. ## Cards **New Card** adds one. Seven kinds: | Card | What it is | |---|---| | Agent session | Claude, Codex or Grok | | Terminal | a plain login shell | | [Git](/cards/git/) | the workspace's changes | | [Files](/cards/files/) | browse and edit files on the host, in tabs, live | | [Browser](/cards/browser/) | your own browser on this device, pointed at the host | | [Agent Browser](/cards/agent-browser/) | a browser on the host that your agent drives | | [Simulator](/cards/simulator/) | a mirrored iOS Simulator | Drag a card to move it, drag its edges to resize. The grid keeps itself fully packed — there are no gaps and no overlaps, ever. Cards shrunk past a certain size drop their live view for a compact one: name, type, and — for agent sessions — the agent's own one-line recap of what it is doing. That is why a dense board still reads at a glance; see [Cards that name themselves](/work/self-naming/). ## Where a card can be [Figure: A card lives on the board, in the tab bar, or in the Icebox, where an agent session is parked with its conversation. Focus mode is a presentation of a card, not a fourth place.] ## Tidy board Dragging and resizing eventually produces a mess. **Tidy board** re-lays the cards: - **Equal** — every card the same size. - **Hero left** — one large card on the left, the rest tiled beside it. Reflow only touches unpinned cards, and needs at least three of them to be useful. [Figure: A board grid with a pinned full-height column on the left. Tidy board rearranges the other cards into equal tiles, then into hero left with one large card, and the pinned column never moves.] ## Pinned cards **Pin Card** freezes a card in place. Pinning never moves anything — it means "leave this exactly here while I rearrange everything else". A card can only be pinned when it forms a **full edge strip**: a full-height column at the left or right edge, or a full-width row at the top or bottom. That constraint is what keeps the rest of the board a clean rectangle that can still be reflowed. Pins nest: pin the outermost column, and the next column in becomes pinnable. Dragging a pinned card unpins it, rather than refusing the drag. ## Search `⌘K` opens search. It looks across your workspaces, sessions and cards and jumps to the match. Agent cards run their CLI in fullscreen mode, which means the CLI owns its own scrollback — so search finds *sessions*, not text inside an agent's history. ## Details - The board grid is quantized to at most 24 columns and rows on the host, so the same layout renders identically from a phone to a Studio Display. - Reflow modes on the wire are `equal` and `hero_left`; the hero slot always goes to the first *unpinned* card in reading order. - Pin semantics are evaluated against the **free region** (board minus pinned strips), which is what makes nesting work. Pins live on the edges, which is what keeps the rest of the board a clean rectangle. - Cards that support hidden chrome (`terminal`, `browser`, `mirror`) can hide their controls entirely — **Hide card controls** on the card menu. - **Add to Tabs** and **Move to Icebox** live on the card menu of a board card. --- # Focus mode and the card strip Source: https://offsite.sh/docs/board/fullscreen Giving one card the whole window without losing sight of the others. Some work needs the whole window. **Focus mode** gives one card the full area and moves everything else into a **strip** along the edge, so the board is still there — small, live, one click away. [Figure: Focus mode turns the grid into one large card with the remaining cards as a live strip along the bottom; one of them finishes and its badge changes in the strip; the same shortcut, Command-Option-Return, toggles back.] ## Entering and leaving - Double-click a card, or **Focus Mode** on its menu. - `⌘⌥Return` toggles focus for the selected card — in and out, the same shortcut. - **Close Focus Mode** returns to the grid. ## The strip The other cards become thumbnails along the bottom. They keep updating: an agent that finishes while you are focused elsewhere shows it in the strip, and its status badge changes. `⌘⌥←` / `⌘⌥→` move along the strip, so you can walk through your sessions without leaving focus mode. ## When to use it - Reading a long agent turn, or a diff, where board-sized cards are too cramped. - Typing a substantial prompt. - Driving a Simulator or a browser target, where you want real pixels. For everything else the grid wins: several agents at once, all visible. ## Details - Focus mode is a presentation of a card, not a placement. The card stays where it was on the board or in the tabs, and the session is untouched. - Focus is per device. Focusing a card on your Mac does not change what your iPhone shows. - Agent CLIs render in fullscreen (alternate-screen) mode, so a focused agent card has no separate scrollback for Offsite to search — the CLI manages its own history, and that is why card search is not offered for those cards. - Placement changes — **Add to Tabs**, **Move to Icebox** — are board moves: leave fullscreen and use the card menu on the board. --- # Keyboard control Source: https://offsite.sh/docs/board/keyboard Driving the board without the mouse — and why every Offsite shortcut contains Command. The board is fully drivable from the keyboard on macOS. ## The shortcuts | Keys | Does | |---|---| | `⌘⌥` + arrows | move between cards — 2D on the grid, left/right in the focus strip | | `⌘⌥Return` | focus mode in and out | | `⇧⌘⌥` + arrows | swap the selected card with its neighbour | | `⌃⌘↑` / `⌃⌘↓` | cycle workspaces | | `⌘K` | search | | `⌥⌘C` | copy the last command's output | [Figure: Board navigation is two-dimensional: Command-Option plus an arrow key moves the selection to the neighbouring card in that direction, and Shift-Command-Option plus an arrow swaps the selected card with its neighbour.] ## The New Workspace panel The stepper that opens from **+** next to *Workspaces* is fully keyboard-driven with the same `⌘⌥` chords as the board — plain arrows, Return and Esc stay with whatever card or field has them. A hint at its top-right names the chords of the page you are on. | Keys | Page | What happens | |---|---|---| | `⌘⌥↑` `⌘⌥↓` | every page | move the cursor: source tiles, folders, projects, repositories | | `⌘⌥Return` or `⌘⌥→` | tiles, projects, repositories | open the tile, add the project, choose the repository | | `⌘⌥Return` or `⌘⌥→` | folder browser | enter the highlighted folder | | `⌘⌥←` | folder browser | go up one folder | | `⇧⌘⌥Return` | File System, clone target | Create the workspace / start the clone | | `⌘⌥Return` | failed clone | try again | | `⌘⌥Delete` | every page | back one step; from the first page out of the panel | | `Tab` | pages with a text field | into the name / filter / URL field as usual | When the new workspace lands on its empty board, the cursor is already on the start options: `⌘⌥↑` `⌘⌥↓` pick a tool, `⌘⌥Return` starts it — the whole way from **+** to a running agent without touching the mouse. ## Why ⌘⌥ and not plain ⌘ Two rules shaped the layout, and they explain why the shortcuts run slightly long: **Every Offsite shortcut contains Command.** macOS reserves Command for the application, so a combination containing it is never delivered to the terminal. That means Offsite can never eat a keybinding your shell, your editor or your agent CLI depends on. Bare `Esc`, `Ctrl-C`, `Ctrl-R` and friends always belong to the session. **Option is there because Command alone collided with the caret.** `⌘←` and `⌘→` are line-start and line-end in any text field, so board navigation with plain `⌘`+arrows did nothing while you were editing a commit message or a file. Moving to `⌘⌥` fixed it: those are not caret commands, so Offsite can intercept them even over a focused editor, and plain `⌘`+arrows fall through to the editor untouched. Arrows and Return are layout-independent, so the shortcuts work the same on a Swiss or a German keyboard. [Figure: Where a key goes. Command-Option combinations go to the board, even while a text field has focus. Command plus an arrow moves the caret in a text field. Bare Esc, Ctrl-C and Ctrl-R always go to the session.] ## Details - Implemented as a local `NSEvent` monitor on the board, which sees the key before the first responder. That is what lets `⌘⌥` combos win over a focused text view by construction rather than by luck. - `⌘⌥Esc` is deliberately never bound — that is macOS Force Quit. - The old shell prompt-jump binding (`⌥⌘↑`/`⌥⌘↓`) was removed because it collided with navigation. - Plain `↑`/`↓` navigation inside the Git card's file list is unchanged and does not need modifiers. - iOS has no keyboard shortcut layer; use the [command bar](/mobile/command-bar/) instead. --- # Tabs Source: https://offsite.sh/docs/board/tabs The tab bar for cards you want reachable but not on screen. Not every card deserves screen space all the time. The **tab bar** holds cards you want one click away without them competing for board area. ## Using it - **Add to Tabs** on a card moves it off the board into the tab bar. - Clicking a tab shows that card large; clicking it again returns you to the board. - **Move to Board** puts it back into the grid. - **New Tab** creates a card directly in the bar. - **Hide tabs** / **Show tabs** collapses the bar when you want the full height. A tabbed card is still running. This is a presentation choice, not a lifecycle one. ## Tabs versus Icebox They look similar and mean different things: | | Tabs | [Icebox](/work/icebox/) | |---|---|---| | For | cards you switch to often | sessions you are not working with now | | Card types | any | agent sessions only | | Lives in | the bar above the board | a toolbar button on the Mac, the workspace pane on the iPhone | | Coming back | click the tab | **Move to Board**, at the board edge a new session takes | Rule of thumb: tabs are for *attention*, the Icebox is for *storage*. :::note **Pin** means one thing: [freezing a card at a board edge](/board/board/). The strip at the top is Tabs. ::: ## Details - Tabs are per workspace and stored with the board, so they survive restarts and appear identically on every device. - A card is either on the board or in the tabs, never both; the host rejects a board grid and tab list that name the same card twice. - Tab order is the order you added them; drag to reorder. - A card in the tab bar keeps running like any other; switching to its tab shows exactly what it did while you were elsewhere. --- # Dark mode, light mode and themes Source: https://offsite.sh/docs/board/themes Appearance, the built-in themes, and the difference between app colours and terminal colours. ## Appearance Offsite follows the system by default and can be pinned: | Mode | Behaviour | |---|---| | Automatic | follows macOS / iOS appearance | | Light | always light | | Dark | always dark | The setting is per device, so your Mac can stay dark while your phone follows the sun. ## Themes Three themes ship with the app — **Classic**, **Blue** and **Her** — and you can edit them in the **Command Center**. A theme is two separate palettes, and the split matters: | Palette | Controls | Example | |---|---|---| | **App theme** | cards, sidebar, dialogs, buttons, badges, dividers | the chrome around your work | | **Terminal theme** | the terminal surface only — foreground, background, cursor, the 16 ANSI colours, dim opacity | what your CLI draws | They are never mixed. Terminal colours are for terminal content; app colours are for app chrome. That is why editing your terminal background does not tint the sidebar, and why an app accent colour can be near-black without making text disappear. [Figure: A schematic card in light and dark appearance. The outer surface is painted by the app theme; the inner terminal surface, including the ANSI colours, is painted by the separate terminal theme.] ## The terminal palette matters more than it looks Agent CLIs use indexed and dim ANSI colours heavily — Claude's question text is a dim colour, not the default foreground. Offsite installs the full 16-colour palette plus foreground, background and cursor into every live terminal, on both platforms. If you edit those values, edit them for both appearances: a colour that only reads on dark will be invisible in light mode. ## Details - Appearance is stored per device under the key `offsite.client.theme_appearance_mode`; values `light`, `dark`, `automatic`. - Theme profiles ship as JSON (`classic`, `blue`, `her`) and the Command Center writes your edits on top of the selected profile. - App colours have roles that are enforced in the codebase: canvas colours are for backgrounds and fills, text colours for text, icons and carets. The caret and selection tint always come from the primary text colour, never the accent — the accent is a canvas-group colour and may legitimately be very dark. - Diff and code surfaces resolve their own palette, with addition and deletion colours defined separately from the general UI. - The docs site you are reading uses the same palette family, which is why lime appears as a darkened olive in light mode: raw lime fails contrast on paper. --- # Sending files to a session Source: https://offsite.sh/docs/board/upload Getting screenshots, logs and documents from your Mac or phone into an agent's hands. Agents work with what they can see. Uploading puts a file on the **host**, next to the session, and hands the agent its path, so "look at this screenshot" or "here is the failing log" works from any device. ## On macOS - **Drag and drop** files onto a session card. Several at once is fine. - Or click the paperclip, **Upload Files**, in the card's action pill and pick one file or several. An overlay shows progress with an `index/total` count while a batch runs. ## On iPhone and iPad The [command bar](/mobile/command-bar/) has an upload button with three sources: | Source | For | |---|---| | Photo Library | screenshots and images you already took | | Camera | photograph something in front of you, right now | | Files | anything already on the device or in iCloud Drive | The camera path takes about five seconds: a whiteboard, a device screen or a sheet of paper, photographed straight into the agent's context. ### From the share sheet Offsite is a share target. In Photos, Files, Safari or any other app, tap Share and pick **Offsite**: the sheet shows the files as cards to swipe through (the image, a PDF's first page, the file's icon for the rest), then the host, the workspace and the agent session the app last had in front, each changeable with a tap. Send copies the files over, opens Offsite and runs the same path as a drop on a session card: images stop in the annotation editor, the rest goes straight up, the paths land in the session's prompt. - The session list offers the workspace's agent sessions (Claude, Codex, Grok), its worktrees' sessions in their own groups, and **New agent session**: pick the agent, model and effort, and Offsite starts the session for the files when it opens. A plain terminal has no agent to read the files. - Each file can be up to 30 MB; a larger one is marked in the sheet and stays behind, the rest is still sent. - A host the app has not been connected to since the share target exists is listed too; its workspace and session are chosen in Offsite after the jump. - If the chosen session is gone by the time Offsite runs the share, the app asks for a session with the files still in hand. ## Images stop for a moment first An image on its way to a session opens in the [annotation editor](/board/annotate/): circle what you mean, number it, write a line per number. The agent gets the marked picture and your notes as text. Anything that is not an image uploads directly, and the editor can be switched off in Settings. The film on [Marking up what you send](/board/annotate/) shows the whole way, from the drop on a card to the prompt. ## What the agent sees Each session has its own upload directory on the host. Uploaded files land there with a short random prefix and a sanitized filename, and the path is pasted into the session's prompt so the agent can read it directly. Nothing is submitted; you add your words and press Enter. Because the file is on the host, the agent can do real work with it: open it, diff it, run it through a tool, instead of only seeing an inlined image. ## Details - Upload path: `~/.offsite/uploads//<8 hex>-` on the host, for example `uploads/s-1a2b3c4d/3f9c2a1b-crash.log`. The prefix keeps two uploads with the same name apart. - Filenames are sanitized to `A-Z a-z 0-9 . _ -`; a name that reduces to nothing becomes `upload`. Directory components are stripped, so an upload can never escape its session directory. - One file can be up to 32 MB. For anything larger, put it on the host with the [Files card](/cards/files/) and give the agent its path. - Uploads take files; a folder dropped on a card is skipped. - Uploads are user-initiated only. An agent cannot pull a file off your device. - The transport gives no per-byte progress, which is why a batch shows a spinner plus a file count rather than a percentage. - Uploads are not cleaned up automatically; the directory is yours to prune. --- # The Agent Browser card Source: https://offsite.sh/docs/cards/agent-browser A real browser on your host that your agent drives directly, with no extension or automation stack to install. You can watch it work and take over whenever you want. An agent that writes a web app cannot see it. It can read its own code, run its own tests, and still ship a page with the submit button behind the keyboard, because at no point did anything look at the rendered page. The **Agent Browser** is a real browser, running on your host, that the agent drives directly: it opens a URL, reads the page, clicks, types, and looks at the result. No browser extension, no plugin, no separate automation stack to install and keep alive. It is part of Offsite, and the agent already has it. It is also a card. So while the agent works, you watch the page move on your board, and you can reach in and take over at any point. [Figure: The Agent Browser as a tile on the card of a Claude session: the shop page the agent just built] [Figure: The Browser card renders a tunnelled page separately on each device, so every device has its own private session. The Agent Browser runs one browser on the host that the agent drives, and streams the same picture to every device.] ## What it is good for **The agent checks its own work.** It opens the dev server it just started, reads the page, clicks through the form and looks at the result. The round trip that used to need you, *look at this and tell me what broke*, runs without you. **Nothing to install.** No Chrome extension, no signed-in browser profile handed to a robot, no Playwright or Puppeteer setup that has to be maintained next to your project. The browser and the tools that drive it come with the Offsite host: it installs and updates its own copy of Chrome, the Offsite Agent Browser, on the machine the agent is working on, and your own Chrome stays untouched. **You watch, and you can take over.** The stream shows a pointer and a click ripple wherever the agent acts, so the card tells you what is happening to your app right now, not afterwards in a summary. Click into it and it is a browser: scroll, type, navigate. The agent's next command continues in the same page. **Every screen size, without the hardware.** **Responsive** is the default and behaves like a browser window: the page reflows to whatever size the card has. The agent switches to a device when it tests a layout: iPhone SE, iPhone 15, 15 Pro Max, iPad mini, iPad, iPad Pro 11″ or 13″, Desktop 1280×800, Desktop HD, each with the touch and mobile behaviour the real device has. The card's settings take it back to Responsive. **One browser, all your devices.** Log into your staging environment on the Mac, pick up your phone, and you are still logged in, on the same page, at the same scroll position. Close the app entirely and come back: the page is where you left it. **It runs where there is no screen.** The browser is headless, so a Linux VPS with no desktop is a perfectly good host. ## It lives on the agent's card By default the Agent Browser is not a separate card at all: it docks to the card of the agent that opened it, as a small picture-in-picture tile. Ten agent sessions mean ten browsers, each on its own card, and you still know at a glance which one belongs to whom. The tile has a pill that switches how much room it gets: | Mode | What you get | |---|---| | **PiP** | a tile at the card's top right. On the Mac it is the live browser: click, type and scroll in it, and drag its corner to resize it. On the iPhone it is a light preview; tap it and the page opens full screen | | **Dock Right** / **Dock Bottom** | the browser as its own card beside the agent's output, fully interactive | | **Full** | the browser fills the session card | The same menu has **Open as its own card**. The globe in the card's action pill hides the browser and brings it back. The mode is yours, per device: your Mac can sit in Dock Right while your phone keeps the same browser as a tile. Offsite suggests the edge that fits the card's shape (wide cards dock right, tall cards dock bottom), and if a card gets too small for the docked layout, the tile takes over until there is room again. Shrink the card further and the browser becomes a small globe badge that pulses while the agent is working, with no stream behind it. On the iPhone the tile sends a couple of frames a second and only when the picture changes; the full stream runs once you open the page. [Figure: An agent's card with its browser docked to it: first as a picture-in-picture tile at the top right, then as Dock Right beside the output, Dock Bottom below it, and Full across the whole card. When the card shrinks, the browser becomes a small globe badge that pulses while the agent works.] An agent's browser follows its agent. End the session and its docked browser ends with it, profile included. Move the session to the Icebox and the browser stops but keeps its profile, ready when you come back. Detach the browser into a free board card from the same pill, and it outlives the session. `offsite web open --detached` (or `web_open(detached: true)`) creates that free card straight away, for the case where you want a browser that is not tied to one agent. ## What the agent can do with it The verbs reach the agent as tools, from Offsite's own MCP server, so an agent that supports MCP has them directly, without a wrapper or a sandbox exception: | Tool | What it does | |---|---| | `web_open` | open a URL in a card next to the agent's own | | `web_snapshot` | the page as text — the accessibility tree, one line per element, with refs like `[e12]` | | `web_click`, `web_fill`, `web_select`, `web_type`, `web_press` | work the page by ref, CSS selector or coordinates | | `web_screenshot` | a PNG, when only pixels answer the question | | `web_goto`, `web_back`, `web_forward`, `web_reload`, `web_url` | navigation | | `web_eval`, `web_wait` | run JavaScript, wait for load or for a selector | | `web_viewport`, `web_settings` | switch device, quality, frame rate, cursor | | `web_close` | end the card and its browser | `web_snapshot` is the one that matters most: a text tree with stable refs is far cheaper and far more reliable than asking a model to find a button in a screenshot. Screenshots are for the questions only pixels answer. The same verbs exist as a command, for agents without MCP and for you: ```bash offsite web open http://localhost:3000 offsite web snapshot offsite web click e12 offsite web fill e3 "ada@example.com" offsite web screenshot --full ``` Cards belong to a workspace, so an agent can only reach the Agent Browsers in its own. ## Using it yourself You do not have to wait for an agent: add an **Agent Browser** card yourself and it opens at once, with no dialog, on a start page that lists the web servers running in the workspace; click one to open it. Addresses are resolved **on the host**, so `http://localhost:3000` means the host's own port 3000, with no tunnel and no exposed port involved. A card you made this way belongs to no agent, and any agent in that workspace can drive it. Whatever you see in the card, you can also mark: the **Mark area** button captures the page, opens the [annotation editor](/board/annotate/), and hands the marked picture plus your notes to an agent session, on the same path the [Browser card](/cards/browser/) uses. The card is the agent's screen, so it has no address bar: the agent navigates. On the Mac, **Settings** and **Mark area** float over the page at its bottom right. On the iPhone, the page opened full screen has Open in my browser, Open as its own card, Settings and Close Browser at the top, and a keyboard button for typing into the page next to Mark. The settings open these controls: | Setting | Options | Why you would touch it | |---|---|---| | **Page** (Mac) | Screenshot, Open in my browser | send the whole page to an agent, or open it in your own browser through the host's tunnel | | **Screen** | Back to responsive, shown while the agent has set a device | return to a page that follows the card | | **Quality** | HD, Balanced, Fast | Fast halves the resolution — the real lever over a slow link | | **Frame rate** | 10, 24, 30, 60 fps | lower costs less bandwidth on a remote host | | **Agent cursor** | on / off | show or hide the pointer and click ripple | If nothing is listening on the URL yet, the card says so and offers a retry instead of showing you a stale page from an hour ago. ## Agent Browser or Browser? Two browser cards, two different jobs. | | [Browser](/cards/browser/) | Agent Browser | |---|---|---| | Whose browser it is | yours | the agent's — you are welcome in it | | Runs on | the device in your hand | the host | | Reaches the dev server via | a tunnel over your existing connection | nothing — it is on the same machine | | Cookies and logins | per device, yours | one profile on the host, shared by your devices | | Your personal accounts | yes, this is where they belong | none | | After a reconnect | reloads the page | the page is still there, scrolled where you left it | | Device emulation | no | a catalog of phones and tablets | | Agent access | never | full, through the web tools | Use the Browser for documentation, a deployed site, anything you are personally logged into. Use the Agent Browser for the app you are building, and let the agent have that one instead of anything of yours. ## What it needs - On a Mac or an x86-64 Linux host, nothing extra: the host installs its own browser for the Agent Browser and keeps it current with every update. A fresh Linux VPS may need Chrome's system libraries once, and the card names the exact `apt-get` line. On an ARM Linux host the Agent Browser uses an installed Chromium, Google Chrome, Brave or Microsoft Edge, and the card names the install command for that machine. - **Browser Use** in Host Settings, which is on by default. It is the switch that decides whether agents get the web tools at all, and it can stay on because this browser holds none of your personal logins. Each card owns its browser and its profile: a card is a clean session that starts where you point it and ends when you close it, so an agent always works in a known state. Give a second page its own card. - Both apps need to be reasonably current: the card itself needs protocol 24, the docked browser needs 25. An older client does not show what it cannot render, rather than showing something broken. ## Details - The host runs the browser with `--headless=new` and its own user data directory under the host's state directory, and talks to it over the Chrome DevTools Protocol. The browser is Google's Chrome for Testing, unmodified and checked against its hash, pulled with the host as the `chrome_runtime` component; it has its own app identity, so it never collides with your own Chrome. On a host without that component, discovery takes the first installed Google Chrome, Chromium, Brave or Microsoft Edge. `OFFSITE_AGENT_BROWSER_BROWSER` points at a specific binary and wins over both. - The picture is a JPEG screencast on the same channel the [Simulator card](/cards/simulator/) uses; input travels back over a separate socket. Chromium only sends a frame when something changed, so an idle page costs nothing. - **Responsive** means the client reports the card's real size in CSS points plus its pixel ratio, and the host applies that as the device metrics, so the page reflows the way a browser window does rather than zooming. - The agent cursor and the click ripple are injected into the page for the stream only. They are marked `aria-hidden` and are not part of your app. - Refs from `web_snapshot` stay valid until the next navigation or snapshot. - On the iPhone, the tile and the opened page are two different streams: a scaled screenshot at about two frames a second that only sends when the picture changed, and the full screencast at the quality you picked. On the Mac the tile runs the full screencast. - The dock mode is stored per device, not on the host, so it never travels to someone else's screen. - Every action is written to the host's runtime log for that card. --- # The Browser card Source: https://offsite.sh/docs/cards/browser Your own browser, on the device in your hand, pointed at the dev servers on your host, with a tunnel per port, tabs synced across your devices, dev tools, and a way to mark what is wrong and hand it to an agent. Your agent starts a dev server. It listens on `localhost` **on the host**, which is a machine you may not be sitting at. Normally that is where remote work stops. The **Browser** card forwards that loopback port over the connection you already have and renders the page, on your Mac and on your phone. It is your browser: your cookies, your logins, your session. No agent can see it or touch it. [Figure: The Browser card on an iPhone and on a Mac, both on the same tab of the shop at localhost:5173] ## Using it Add a Browser card and it opens straight away, on your workspace's start page if it has one, else on the last URL you used in a Browser card of that workspace. No dialog, no setup step. The bar across the top is a real browser bar: | Control | | |---|---| | Back, forward, reload | `⌘[`, `⌘]`, `⌘R` — and `⇧⌘R` reloads past the cache, for dev servers that serve stale bundles. `Esc` stops a load | | History | `⌘Y`; `⌘`-click an entry to open it in a new tab. Rows carry the site's icon | | The address field | type a URL, or type words to search. `⌘L` focuses it. The star adds the page to the workspace's favorites | | Paper plane | send the page's link to an agent, into a running session's prompt or as the first prompt of a new one | | **+** | a new tab, on the workspace's start page if it has one — `⌘T`, close with `⌘W`, switch with `⌃Tab` and `⌃⇧Tab`, `⌥⌘→` and `⌥⌘←`, or `⇧⌘]` and `⇧⌘[` on a US layout; jump with `⌘1` to `⌘8`, `⌘9` is the last tab | | Camera | send the whole page to an agent — `⇧⌘S` | | Marquee | mark an area of the page and send that | | Wrench | Developer Tools — `⌥⌘I`, in their own window | | Sliders | browser settings: the workspace's start page, its favorites, your browsing data | Buttons that do not fit a narrow card move into a **…** menu. `⌘F` opens find-in-page. Under the bar, the workspace's favorites stand in a row of their own once the card is tall enough; in a lower card they are a button in the bar. `⌘`-click a favorite to open it in a new tab. Every host address a tab uses gets a tunnel of its own, so `localhost:5173` and `localhost:8787` are open side by side, each in its own tab; tabs on the same port share one. When a page reaches for a further port of the host, a frontend calling its API for one, the card asks: **Allow**, **Allow all ports** or **Block**. The answer holds for that site until you remove it in Host Settings › Browser. A page that asks for a permission gets the same kind of question. [Figure: Two tabs, localhost:5173 and localhost:8787, each run through a tunnel of their own to the host. The page on 5173 reaches for port 3001, the card asks Allow, Allow all ports or Block, and after Allow port 3001 gets its own tunnel.] On a host that is not this Mac, the address field carries a **TUNNEL** chip: `localhost` is the host's. Click it to send that origin to this Mac instead (**LOCAL**), for a server you run on the Mac itself. When nothing is answering yet, the card says exactly that and offers a retry, instead of turning into a broken card that stays broken after the server comes up. Shrink the card and it turns into a launchpad: the current page as a cover with its title and an address box, then your tabs, the workspace's favorites, the servers running in the workspace and recent pages, each with the site's icon. A server your agents start lists itself there with the session that runs it (`:5173 node · Claude`); a click opens it. Public pages show the preview image the site publishes for links, the same one messaging apps show; your dev servers show a live capture of the page instead. ## The same card on every device The card's tabs, the tab that is open and the address of each tab belong to the host, so every device shows the same card. Open a tab on the Mac and it is on your phone; pick a tab on the phone and the Mac switches to it; follow a link on one and the other loads it. Pick up your phone and keep going where you were at your desk. Each device keeps its own scroll position. The start page and the favorites belong to the workspace, and they are the same on every device too. ## Mark what is wrong, hand it to an agent You are looking at the page the agent just built, and something is off. On the Mac, instead of describing where it is: 1. Hit the marquee button and drag a rectangle around it, or `⇧⌘S` for the whole page. 2. The [annotation editor](/board/annotate/) opens: circle things, number them, write one line per number. 3. Pick the target session, an **intent** (fix, change, question or approve) and add a note. Offsite uploads the marked image to the host and pastes a block into that session: your notes, the page URL, the CSS selector of the element you marked, its surrounding HTML, and in a dev build of a React app the component and the `file:line` it came from. Plus a line telling the agent how to open the same page in its [own browser](/cards/agent-browser/). Nothing is submitted. The text sits in the prompt; you press Enter. If no agent session is running, the target picker offers to start one. ## This one is yours The Browser renders on **your** device, with your cookies and your logins, and no agent can reach it — not through a tool, not through the CLI, not by asking. Data moves in one direction only: from you to the agent, when you send it. Your browsing data (cookies, cache, logins) stays on each device. By default the workspaces of a host share one profile there; give a workspace its own in Host Settings › Browser, for customer or test accounts, or for a dev app whose cookies clash with another one on `localhost`. **Logins…** in the same place copies a site's logins from another profile, and on the Mac from another browser on it, Safari included. The [Agent Browser](/cards/agent-browser/) is the opposite: one browser on the host that the agent drives, showing the same page on all your devices. One is yours, the other is the agent's, and you are welcome to watch. ## On iPhone and iPad The card is a browser of its own, with the page running to the top edge and the controls at the bottom, where your thumb is: the tab strip with **+**, then Close, Back, Forward, History, Reload, **Send to agent** (the page's link into a session's prompt), **Open in Safari**, and a menu with Add to Favorites and Browser Settings. Open in Safari takes a `localhost` page through the same tunnel. On the board, the card is its launchpad: the page as a cover, then your tabs, the favorites, the running servers and recent pages. Tap the cover to open the browser. ## Details - macOS forwards with `ssh -L`; iOS opens a `direct-tcpip` channel natively over its own SSH implementation. Both ride the connection the app already has, so nothing new is authenticated, no port is opened on the host, and the dev server stays private to your own devices. There is one tunnel per host address a tab uses. - Forwarding targets the host's loopback interface, so a server bound to `127.0.0.1` is enough. - The macOS card renders in a bundled Chromium (CEF) rather than WKWebView, because third-party sign-in popups do not work reliably in the latter. Screen captures and page JavaScript run through the Chrome DevTools Protocol. The iPhone card is built on WebKit. - Browsing data lives on each device, never on the host. Tabs, the open tab, favorites and the start page live on the host. - A new card opens on the workspace's start page, else the last URL used in a Browser card of that workspace on this device, else the workspace's newest Browser card, else the Offsite start page. - Port discovery scans the host's socket table every few seconds and maps listeners to sessions through the process tree; servers started outside a session are listed for the workspace instead. - Stale forwards from a previous run are cleaned up when the Mac app launches. --- # Deploy to this phone Source: https://offsite.sh/docs/cards/deploy-to-phone Building an iOS app on the host and installing it on the iPhone in your hand — no TestFlight, no cable. Your agent just built an iOS app. It is sitting on a Mac somewhere. You are not. **Deploy to this phone** builds the project on the host and installs the result on the iPhone you are holding — over the network, with no cable, no TestFlight round trip and no App Store review. Available on **macOS hosts only**. ## Using it From the iPhone app, pick the Xcode project in the workspace and start a deploy. The host builds it, packages it, and your phone offers to install it. The build runs on the host's Xcode with the host's signing setup. That is where the requirements come from. [Figure: The host builds and signs the app with its own Xcode and uploads the package to Offsite's file storage at an unguessable address, with an install manifest. The iPhone installs it through Apple's own install mechanism, and the package is deleted after 48 hours.] ## What you need | | | |---|---| | An Apple developer account signed in to Xcode on the host | the build has to be signed | | Your iPhone registered with that account | a provisioning profile has to include it | | An Xcode project or workspace in the Offsite workspace | Offsite scans a few levels deep for it | | Push notifications registered from this iPhone | the install link is delivered as a push; if the host reports no registration, reconnect or restart the app once | A free personal team works, with Apple's usual limits: apps expire after seven days and some capabilities are unavailable. ## When it fails Signing is where deploys fail, and the failure is reported as a specific kind rather than a wall of build log: - **No signing account** — Xcode on the host has no Apple ID. - **Free team limitation** — the project uses a capability a personal team cannot sign. - **Device not registered** — this iPhone is not in the provisioning profile. - **Generic signing failure** — everything else, with the tail of the log. ## Details - Deploy state lives in `~/.offsite/deploys/` on the host. Builds use a private derived-data directory (`~/.offsite/xcode-derived-data`) rather than the shared Xcode one, both to avoid macOS App Management restrictions and to keep your own Xcode caches untouched. - Install path: the host uploads the IPA with a 30-minute upload token to Offsite's file storage, at an unguessable address; a manifest is published and verified, and the phone installs through `itms-services://?action=download-manifest`. Apple's own mechanism, no sideloading tricks. - Limits: IPA up to **500 MB**, artefacts expire after **48 hours**, the last **10** deploy jobs are retained. - Project discovery scans up to 3 directory levels and skips `.git`, `node_modules`, `Pods`, `DerivedData`, `Carthage`, `.build` and `.swiftpm`. - Deploys preserve the installed app's container — Offsite does not uninstall first, so your app's data survives a redeploy. - A failed deploy keeps its log and a retry control at the end of the card, so a second attempt is one tap once you have fixed the cause. --- # The Files card Source: https://offsite.sh/docs/cards/files Browsing and editing files on the host, with tabs, live updates when the agent writes, and a real diff when your edit and the agent's collide. The **Files** card is a file browser and editor for the workspace, running against the host's filesystem. It exists for the small interventions: fix a typo the agent introduced, read a config it keeps getting wrong, check what a generated file actually contains, without switching to a terminal card and without being at the machine. ## What it does - Browse the workspace tree, with hidden files on or off. - Open files in **tabs**. A path an agent mentions in its terminal (`docs/PLAN.md`, `Foo.swift:42`) is one click away and opens at that line. - Open files **outside the workspace** the same way: a plan under `~/.claude/plans`, a scratch file in `/tmp`, a file of another repository. It opens as an editable tab; the tree stays in the workspace and shows the workspace root with that one file under it, and the header names the real folder. Rename, Duplicate, Move to Trash, Copy and Cut into the workspace, Quick Look, Save As…, Share, Open in Finder and a terminal in its folder work on it like on any file. Such a file is never handed to another app. - Edit in a syntax-highlighted editor. Unsaved work is a **draft**, marked with a dot on the tab. - **Star** files you keep coming back to, and filter the tree down to starred files or to files with drafts. - Search for a file by name. - Create files and folders, delete entries, and **Open in Finder** on macOS hosts when you happen to be at the machine. Tabs, drafts and stars live on the **host**, not in the app. Open three files on the Mac, pick up your phone, and the same three tabs are there — with your unsaved edits. ## It keeps up with the agent The card watches the folders it is showing. When the agent, a terminal, or anything else on that machine creates, renames or deletes a file in one of them, it appears in the tree within a second or two. No refresh button, no wondering whether what you see is current. The file you have open is checked the same way, but cheaply: the card asks "has this changed since version X?", and an unchanged file answers in a few hundred bytes instead of resending itself. ## When you and the agent edit the same file The draft remembers which version of the file it started from. If the file changed on disk before you save, the card stops and shows you a **diff of the host's version against yours**, with two explicit choices: - **Keep my draft (overwrite host)** - **Take host version (discard my draft)** [Figure: Your draft starts from version 1 of a file on the host. The agent writes version 2. When you save, the card stops at the change, shows the host's version against yours, and offers Keep my draft (overwrite host) or Take host version (discard my draft).] Nothing is decided for you, and nothing is merged behind your back. ## Speed The card was rebuilt around the fact that its host may be a VPS on the other side of a relay: - Opening a file is **one round trip** instead of four. - Directory listings and file contents are fetched **conditionally**: unchanged means "not modified" instead of a resend. - Expanded folders load in parallel, and only the folders that actually changed reload. - File search skips the places nobody means (`.git`, `node_modules`, `DerivedData`, `.build`, `Pods`, `__pycache__`), and stops after about a second rather than walking a monorepo to the end. - Images over ~1 MB are scaled down **on the host** and arrive as a preview, with the original one button away, so a 12 MB screenshot no longer blocks the rest of your board. - Typing in the editor no longer makes every other device reload its tree. ## Where it fits Use it for the quick, surgical edit — the typo, the config value, the file you want to read before you answer the agent. Structural work belongs in the card next to it: describing the refactor to an agent that has the whole repository in its head beats doing it by hand on a phone. ## Details - The tree, search and stars stay inside the workspace root. A file outside it opens only from a link, by its absolute path; it has no row in the tree, no star and no Git marks, everything else works on it, each action resolving the path again on the host. On a Mac host a file in a protected folder (Documents, Desktop, Downloads, cloud folders) asks for your permission first, through the same card a workspace in such a folder uses. - Saving writes through a temporary file and an atomic replace, so a failed save cannot leave a half-written file. - Drafts live in the host's state database with the file version they started from, which is what makes both cross-device editing and the conflict check possible. - The host watches at most 48 directories per connection, polling their modification times about once a second, and pushes only the paths that changed. Content changes inside the file you have open are caught by the versioned refresh instead. - Image previews cap at 2048 px on the long edge; GIFs are always sent as-is so they keep animating. - Files are classified from their first few kilobytes: text is editable, images preview, anything else is listed but not opened. - File contents travel over the same connection as everything else; there is no separate file service and no port to open. - Live updates and conditional fetches need host and app on protocol 28 or newer, which is anything current. --- # The Git card Source: https://offsite.sh/docs/cards/git Reviewing what your agent changed, staging it, committing and pushing — from any device. An agent that works unattended produces a diff you did not write. Reviewing that diff is the job, and it is the job you most want to do from the couch. The **Git card** shows the workspace's repository: changed files, the diff for the selected file, history and branches. On the Mac it is a narrow column with the diff beside it; the column carries the whole way from a change to a merged pull request. ## What you can do | | | |---|---| | Review | per-file diff with additions and deletions highlighted | | Stage / unstage | per file, or the whole group | | Commit | with a message you type in the card | | Push / pull | push to the tracked remote, pull fast-forward only; sync does both, force push with lease after a rebase | | Pull request | create it from the card, follow its checks, merge it with the method you choose, or let GitHub auto-merge when the checks pass; request reviews and comment from the card | | Commit message | the host's agent drafts it from the diff when you click the sparkle; which agent and model answer is set in Host Settings, Agents, Background tasks | | Update from base | merge the base branch into yours when it moved on | | Undo last commit | soft reset, changes stay in your tree | | Discard changes | restore a tracked file, or delete an untracked one | | Branches | list and switch, including tracking a remote branch | | History | recent commits | ## The column From top to bottom the column reads as the state of your branch: - **Create PR**, or **PR #n** with its state once one exists: checks running, ready to merge, changes requested, merged. Clicking it opens the pull request's checks, merge and conversation beside the column. - **Branch → base** with how many commits wait to be pushed and how far the base has moved on, and a link to the branch on GitHub. - The **commit message** and one **action button** that shows the most likely next step: stage everything, commit, push, create the pull request. Every other action sits in its menu, greyed out with a reason when the state does not allow it. Nothing is locked: you can keep editing after the pull request exists, and the button follows. - **Changes**, **Staged** and **Committed on branch**. The first two hold the working tree, with stage, unstage and discard on each row. The third, folded by default, lists what the branch already carries against its base, as files or as commits; a click shows that diff. Merging happens only in the pull request's checks view, with GitHub's own verdict: the merge button opens when GitHub reports the branch as mergeable, asks first while checks are still running, and stays closed when a check failed. ## History The history shows the commit graph: lanes for branches and merges, pills for the branches and tags that point at a commit, HEAD marked. A commit unfolds its files, and a click on one shows that commit's change beside the list. ## Actions on the remote The action button's menu carries the remote actions: push, pull (fast-forward only), sync (pull then push), fetch, force push with lease after a rebase, update from the base, and rebase onto the base. A rebase that would stop on conflicts is undone at once; update from the base merges instead, and the conflicts can be handed to an agent. Everything runs `git` on the host, in the repository, as you. There is no Offsite-specific git state and nothing to clean up if you stop using the card. ## Reading a diff on a phone The card is built to be readable at phone width: file list first, then the diff for the file you pick, with line numbers and hunk headers in the muted colour and additions and deletions in their own palette. ## If the workspace is not a repository The card offers to run `git init`. A [workspace](/work/workspaces/) does not have to be a repository — if it is not, the card has nothing to show until you initialise one. ## Details Actions map to exactly these commands, run in the repository root: | Action | Command | |---|---| | stage | `git add -- ` | | unstage | `git restore --staged -- ` | | discard tracked | `git restore --worktree -- ` | | delete untracked | `git clean -f -d -- ` | | commit | `git commit -m ` | | undo last commit | `git reset --soft HEAD~1` | | switch branch | `git switch `, or `git switch --track origin/` when only a remote exists | | pull | `git pull --ff-only` | | push | `git push` | - Pull is **fast-forward only**: a merge that needs a decision should not happen behind a tap on a phone. - Undo requires `HEAD` to have a parent; on a repository with a single commit it refuses rather than doing something surprising. - Branch names starting with `-` are rejected, and every path is passed after `--`, so a filename can never be read as a flag. - The workspace root is resolved to the repository root, so a workspace pointing at a subfolder still shows the whole repository. - Anything beyond review, stage, commit, push and switching branches is a sentence to the agent in the next card — it has the repository open anyway. --- # The Simulator card Source: https://offsite.sh/docs/cards/simulator A live iOS Simulator from the host, mirrored onto your board and driven with your taps. If your agent is building an iOS app, the fastest feedback loop is watching it run. The **Simulator** card mirrors a Simulator running on the host onto your board — and your taps and typing go back into it. Available on **macOS hosts only**; a Linux host has no Simulator to mirror. ## Using it Add a Simulator card and pick a device. The card streams the Simulator's screen live and forwards your input: - Tap and drag with mouse or finger. - Type with your keyboard, or the phone's. - **High Definition** trades bandwidth for a sharper image — worth it on a fast local network, less so over the relay. Because the Simulator runs on the host, the build the agent just produced is the build you are looking at. There is no upload step and no separate device. ## From the phone An iOS Simulator running on your Mac at home, mirrored onto the iPhone in your hand, driven by your thumb. Good for "does the layout break on this screen?", and for marking what to fix instead of describing it. ## Details - The Simulator is driven by a small broker that Offsite installs as a LaunchAgent in the host's **GUI login session**. That is a requirement, not an implementation detail: `simctl` and the Simulator app need a real window server, so a host that never logs in graphically cannot mirror. - The screen arrives as an AVCC video stream; input goes back over a separate channel, so a slow frame never delays a tap. - Only `simulator` is a valid mirror target today. The card type on the wire is `mirror`. - If the card says *Simulator unavailable*, the usual causes are: no Simulator runtime installed, the host is not logged in graphically, or the broker has not been installed yet. - Related: [Deploy to this phone](/cards/deploy-to-phone/) puts the same build on real hardware, and [Testing what you build](/testing/testing/) ties both together. --- # Workspace Actions Source: https://offsite.sh/docs/cards/workspace-actions Saved commands and agent prompts per workspace — run them by tap, on a schedule, or when the host restarts. Every project has commands you run over and over: the test suite, the dev server, the deploy script, the "check what broke overnight" prompt. **Workspace Actions** are those commands, saved per workspace and startable with one tap — including from your phone, where typing them would be miserable. [Figure: At 03:00, with Offsite open on the Mac, the scheduled action Nightly check runs the tests and the UI tests in the Simulator, and its card shows on the iPhone] ## Two kinds | Kind | Runs | Good for | |---|---|---| | **Terminal** | a shell command in a terminal card | tests, builds, dev servers, scripts | | **Agent** | a prompt in an agent session | recurring review, triage, summaries | An agent action can pin the tool, the model and the [permission mode](/work/agent-sessions/), so "run the nightly cleanup in YOLO with Codex" is one saved thing. ## When they run Four triggers: - **Manual** — you tap it. - **Schedule** — every *N* minutes, or daily at a fixed time. - **Restart** — when the host comes back up. - **Card** — from the card the action belongs to. [Figure: Four triggers can start the same saved action: a tap, a schedule, a host restart, or its own card. The action carries its command or prompt plus its tool, model and permission mode, and every run lands in the run log.] The scheduler runs on the host and fires due actions while one of your devices is connected to it: a Mac with the Offsite app open overnight is enough for a nightly run. The restart trigger fires the same way, once per boot of the host. Runs missed while nothing was connected are not repeated; a daily action whose time has passed runs once when a device connects later that day. [Figure: One night from 18:00 to 12:00. A device is connected until 01:00 and again from 09:00. An action every 2 hours runs while a device is connected; the runs it missed in between are not repeated. A daily action at 03:00 falls into the gap and runs once when a device connects at 09:00.] ## Pause An action that runs on its own (on a schedule, or an agent action after a host restart) can be paused. On the Mac the pause button sits next to Run in the Actions list; on the iPhone, **Pause** is in the row's **…** menu. While an action is paused, the scheduler starts no run of it, and its row says "Paused" in front of the schedule. Nothing else changes: a run that is already going finishes, its card stays, and you can still start the action yourself. **Resume** starts nothing. It switches the scheduler back on, and the clock starts fresh: an interval counts from the moment you resume, and a daily time that has already passed today runs tomorrow. Press Run for a run right away. A paused action keeps its prompt, its settings and its memory, so a nightly job can rest for a while without being edited or deleted. On the host: `offsite-host workspaces pause-action --workspace-id --action-id `, and `resume-action` with the same arguments. ## The rerun loop A terminal action does not just run and die. When it finishes, the card waits at a rerun prompt: press Enter and it runs again, in the same card, with the same scrollback above it. That turns "run the tests" into a loop you can drive with one key, and it is why a live action card is reused instead of piling up new cards. ## Action memory Every action has a memory file on the host. An agent action is told to read it before starting and may write durable learnings back into it. This is what makes a recurring agent action get *better*: the third nightly run knows what the first two discovered, without you re-explaining it. ## Run log The host records each run with what triggered it, when it started and finished, and its exit status. On the host, `offsite-host actions list-runs --action-id ` lists the latest runs (20 by default, `--limit` up to 200), and `offsite-host workspaces list --json` shows each action's id. That log is how you find out that the scheduled job has been failing since Tuesday. ## Details - Triggers on the wire: `manual`, `schedule`, `restart`, `card`. - Schedules are either `{kind: interval, minutes: N}` (N ≥ 1) or `{kind: daily, at: "HH:MM"}` in 24-hour form. Anything else is rejected. - Action memory lives at `~/.offsite/action-memory/.md` and is seeded with a short header. Deleting the action deletes its memory file. - The scheduler holds a lock file (`~/.offsite/action-scheduler.lock`) so two host processes cannot double-fire an action. - A paused action carries `paused_at`; the scheduler skips it. A terminal action's restart autorun belongs to its restored card, a session that already exists, so a pause leaves it alone. - Run mode `rerun_on_enter` requires a command; the loop is only re-triggered while its prompt is actually up, so a busy card is never interrupted. - A terminal action can choose what happens when it completes — keep the card, or close it. - An empty Actions list opens straight into **New Action…**, so the first action is one dialog away. --- # Computer Use Source: https://offsite.sh/docs/computer-use/computer-use Letting an agent see and drive the host's screen — off by default, locked per session, revocable. Some tasks need eyes. An agent that just built a Mac app cannot verify it from a terminal; it needs to look at the window and click things. **Computer Use** gives a session screenshots of the host's screen and the ability to click, type and scroll on it — through a signed helper called the **Offsite Agent Bridge**. Available on **macOS hosts only**. ## Off by default, twice This is the widest access Offsite can hand an agent, so it is gated twice: 1. **The feature is off** until you enable it, which requires granting macOS Screen Recording and Accessibility to the Agent Bridge. 2. **Every session starts locked by default**, even with the feature enabled. You unlock one specific session when you want it to have the screen. The host setting **New sessions start unlocked** changes that default; it applies to the next session you start. The card's pill switches a session at once and shows its live state — you can always see which sessions can touch your screen. [Figure: Two independent gates stand between a session and your screen: Computer Use must be enabled for the host, and that specific session must be unlocked. Failing either one refuses the request.] ## The two macOS grants | Grant | Why | |---|---| | Screen Recording | to take screenshots | | Accessibility | to move the pointer, click and type | macOS attributes both to *Offsite Agent Bridge*, the signed helper that holds them, so the dialog names something you can recognise and check. Offsite asks for these while you are **setting computer use up**, not in the middle of a session, so the prompt never arrives on a screen nobody is watching. ## Automation prompts The first time an agent scripts a specific application, macOS asks once per target app: *"Offsite Agent Bridge wants to control …"*. The grant covers that app and nothing else. ## Revoking System Settings › Privacy & Security › Screen Recording / Accessibility, and remove the Agent Bridge. Nothing else in Offsite depends on those grants. ## Details - The Bridge is a separately signed helper with its own identity; the host pins that identity and refuses to talk to a binary whose signature changed unexpectedly. - Communication is token-authenticated over a local channel; the token lives in `~/.offsite/agent-bridge/`. - The tool surface is the standard computer-use set plus a zoom tool — screenshot, click, type, key, scroll, drag, cursor position and friends. - Computer use runs on macOS hosts, through the signed Agent Bridge. - Capture needs a screen the Mac is actually driving: a display attached, or the built-in one open. - The lock is per session: if a call reports that computer use is locked, unlock that session from its card. --- # Device access Source: https://offsite.sh/docs/connect/devices See whether a device is authorised on a host, revoke its access in two clicks, and what a revoked device shows. Every device that reaches a host through Offsite's own paths has an entry on that host: a record of its key, its name and since when it may connect. How a device gets one is on [Pairing devices](/connect/pairing/). This page is about seeing it and taking it back. ## See a device's status Open the host's menu: the **⋯** on the host row on the Mac, the host row in the iPhone's host list. A line tells you where this device stands: | Line | Meaning | |---|---| | **This device is authorized since 15 Oct 2026** | the host accepts this device's key (on the iPhone: *This iPhone is authorized since …*) | | **This device is not authorized** | on the Mac, you connect with a password or your SSH key only, and **Authorize this device…** adds the key; an iPhone pairs with a fresh code | | **Access revoked** | the host no longer accepts this device's key | | **Revocation unconfirmed** | you revoked, and the host's answer did not arrive | [This Mac](/hosts/this-mac/) is the host itself; its menu has no such line. ## Revoke a device Two clicks from the open menu: **Revoke this device's access…** (on the iPhone **Revoke this iPhone's access…**), then **Revoke access**. The device has to be connected to the host for this. What happens at once: - The host stops accepting the device's key, on every path: local network, Tailscale and relay. - The device's open connections end within a few seconds. - The host replaces its current pairing code, so no code shown before the revoke works afterwards. - Your sessions keep running on the host. The relay does not need to be reachable for any of this. A revoked key stays revoked when the relay comes back, after a restart of the host, and when an enrolment the relay held while the host was offline arrives late. The dialog names the way back before you confirm: - a device that was **paired** with a QR or a host code pairs again with a fresh code; - a device with the host's **password** or your **SSH key** can still connect that way, and the app offers to authorise it again. ## After a revoke Once a device knows its access is revoked, it stops connecting. The connection pill reads **Access revoked**, and the message says what to do: - *host* **no longer accepts this iPhone's key. Pair again with a fresh code.** - On a password host: *host* **no longer accepts this Mac's key. Connect with the password, then authorize this Mac again from the host menu.** The same happens when the access was revoked somewhere else, for example on the host itself: an iPhone sees it on its next connect, a Mac when the host's relay is on. ## When the host does not answer A revoke needs the host's confirmation. If none arrives within 15 seconds, the menu shows **Revocation unconfirmed**. The app then does not retry, does not reconnect over the device key and does not claim success, and the state survives a restart of the app. Check the access on the host with `offsite-host relay list --json`: a revoked record carries the time of the revoke. ## Forget a host **Forget Host…** on an authorised host comes with **Also revoke this device's access on *host*** already ticked (on the Mac, while it is connected). With the tick, the host stops accepting the device first; then the host is removed from this device, saved password included. Without it, the host keeps accepting the device's key. **Delete Saved Password…** on the Mac (**Clear Saved Password** on the iPhone) removes only the password and leaves the authorisation as it is. ## Revoke a device from the host A lost phone cannot revoke itself. On the host, list every authorisation with its name, source, fingerprint and dates, then revoke the one you mean by its key: ```bash offsite-host relay list --json offsite-host relay remove --pubkey ``` The effect is the same as a revoke from the app: the key is refused at once and its connections close within seconds. ## Details - The host keeps the list in `~/.offsite/device-authorizations.json`. A revoke marks the record with the time; records are never deleted, and a later pairing of the same device creates a new record. - A device that was paired before host 0.9.0 keeps its access and appears in the list with an unknown start date. - Revoking changes nothing about the host's identity: the host key your devices pinned stays valid. --- # Pairing devices Source: https://offsite.sh/docs/connect/pairing How a phone or a second Mac gets access to a host, with a QR code, a host code, or one question after a password login. Offsite has no account, so there is nothing to log in to. A device reaches a host because **you authorised it on that host**. Each host keeps its own list, and you can take any entry back: see [Device access](/connect/devices/). There are three ways in. Pairing with a QR code or a host code authorises the device in the same step. A host you add with a password or your own SSH key asks you once. ## Pair with a QR code 1. In the Mac app, open the host's **⋯** menu and choose **Show phone QR**. 2. On the iPhone, choose **Scan from Mac** (on first launch, or from the **+** in the host list) and point the camera at the code on the *Scan on your phone* sheet. The phone gets the host's address and a one-time pairing secret, and the host records the phone's own key. From then on the phone connects by itself, over whichever path is reachable: local network, Tailscale or the relay. **Passwords are never in the QR code.** ## Pair with a host code A host code is eight characters, shown as `X3YK-47Z3`. It works where a camera does not: a server in a terminal, a host in another room, a second Mac. - **Where it is shown:** on the host, `offsite-host setup connect` prints the current code next to a QR code and updates it live. A host-only install (`setup.sh`) ends on the same screen. A host with its relay on shows a code, and so does a Mac host for devices on its local network. - **Where you type it:** on the iPhone, into the **Host code** field on the first screen, or choose **Enter Host Code** from the **+** in the host list. On a Mac, **Add Host** → **Connect to Existing Host…**, which also takes a pasted QR payload. The app looks the code up through the relay, and on the local network it asks every Mac host it can see. Only the host that issued the code accepts it. ## One code, one device, ten minutes The QR secret and the host code belong together, and both are **single-use**. The host replaces them: - right after a device paired with them, - after about **ten minutes**, whether anyone used them or not, - after you revoke a device, - whenever the host's relay service starts. [Figure: A host code, X3YK-47Z3, with a ten-minute ring. The host replaces it with a new code after a device paired with it, when about ten minutes have passed, after a device was revoked and whenever its relay service starts.] A photo of yesterday's code is worth nothing. For each device, read the current code or show a fresh QR. ## Hosts you add with a password or an SSH key When you add a server with **Add Remote Host…**, Offsite connects with the password or the SSH key you gave it, and then asks one question: **Authorize this device on *host*?** **Authorize this device** gives the host a key for this device. Offsite then connects faster, switches networks on its own, reaches the host from anywhere when the host's relay is on, and needs no password for that path. The password keeps working as before. **Not now** keeps the plain SSH connection. You can authorise later from the host's **⋯** menu with **Authorize this device…**. ## This Mac The Mac you set up as a host is the host itself and reaches it through a local key. Its host menu shows no authorisation line and no revoke. ## Per host, not per fleet Each device pairs with each host separately. Pairing your iPhone with the Mac at home says nothing about the VPS: pair with that host too. ## If pairing fails - **"The host code was already used or has expired"**, or **"No host on this network accepted this code"**: the code changed since you read it. Read the current one from `offsite-host setup connect` and enter it again, or show a fresh QR. - **"Access revoked"**: the host no longer accepts this device. Pair again with a fresh code; see [Device access](/connect/devices/#after-a-revoke). - An error mentioning **Remote Login** comes from an older message text. On a current host Remote Login plays no part, and the message means this device is not authorised yet. ## Details - The host's relay service redeems the pairing secret on every path (local network, Tailscale and relay), so the first connect works whichever path wins. Hosts with the relay switched off pair too. - Before the app sends a code or a token, it checks the host's key against the pairing ID it was given, so a code only ever reaches the host it belongs to. - Authorisations live in `~/.offsite/device-authorizations.json` on the host: one record per device with name, how it was added (`app`, `pairing`, `relay`, `this-mac` for a Mac host's own key, `migration` for devices paired before host 0.9.0), and since when. A revoked record stays, marked revoked. - `~/.offsite/devices.json` is a different list: the devices that receive [notifications](/mobile/notifications/), with their per-device settings. - A host key that changes, for example after a VPS rebuild, needs your confirmation; the app never reconnects to a changed key on its own. --- # How your devices reach your host Source: https://offsite.sh/docs/connect/paths Every path to your host raced against each other, a new race when one drops, a live move off the relay, and a pill that names the path in use. Your host is sometimes on the same Wi-Fi as you, sometimes behind a router in another country, and sometimes both within the same hour. Offsite does not ask you to know which. [Figure: A phone in a hotel's Wi-Fi reaches the Mac at home over the relay; the app menu shows the host on Relay] ## The paths | Path | When it works | Typical latency | |---|---|---| | **Local network** | your device and the host share a network | single-digit milliseconds | | **Tailscale** | both machines are on the same tailnet | tens of milliseconds | | **Direct** | the host answers at an address of its own: a public IP (a VPS without NAT) or the SSH address you added it by | set by the distance to the host | | **Relay** | always, as long as both sides can reach the internet | roughly 90 ms; the pill shows yours | [Figure: The device tries the local network, Tailscale, the host's direct address and the relay at the same time and uses the first that answers. When that path drops, the paths race again. When a direct path comes up while the device is on the relay, the iPhone moves to it live, and the Mac moves after an SSH check once the connection is idle.] ## Racing, not configuring The client attempts every path it knows at the same time and uses whichever answers first. There is no "connection type" for you to pick and no wrong choice to make. When the path in use goes away, because you walked out of the Wi-Fi or the network changed under you, the app races the paths again and carries on over the first that answers. Your sessions keep running on the host the whole time. When you are on the relay and a direct path comes up (your local network, your Tailscale or the host's public address), Offsite moves you to it: - **iPhone:** live. It connects over the new path and opens your session's channel there before it lets go of the relay, so the terminal goes on where it was. - **Mac:** it checks the new path with a real SSH round trip, then reconnects over it as soon as the connection is idle. The Mac also moves from Tailscale to your local network. ## The pill never lies The connection pill in the app names the path in use and its latency: LAN, Tailscale, Direct or Relay, and This Mac when the host is the Mac you are using. If it says relay, you are on the relay; if it says LAN at 7 ms, that is a measurement, not a label. So when something feels slow, the pill tells you whether the network or the agent is responsible. ## Turning the relay off The relay is optional and switchable **per host**. Switch it off and that host is reachable only on your own network and your tailnet. Nothing else changes. See [The relay](/connect/relay/) for what it can and cannot see. ## Details - Endpoint kinds on the wire are `lan`, `tailscale` and `relay`; a host profile additionally records how it authenticates (`relay`, `directSSH`, `tailscale`). - LAN endpoints are discovered with Bonjour, which is why macOS asks for local network permission on first launch. Decline it and everything still works — connections take a slower path. - Every path terminates in the same place: Offsite's embedded agent on the host, over an SSH-backed RPC channel. There is no separate "relay mode" protocol, which is why a path switch does not reset your session. - Both clients enforce connect deadlines (15 s and 30 s) so a black-holed socket fails instead of hanging forever. - macOS localhost connections use the same architecture — the local host is not a special case. --- # The relay Source: https://offsite.sh/docs/connect/relay What the relay is, exactly what it can and cannot see, and how to switch it off. Sometimes neither your local network nor a tailnet connects you to your host — a hotel network, a locked-down office, cellular behind carrier NAT. The **relay** is the path that works anyway. It is optional, switchable per host, and deliberately built so that switching it on does not require trusting us with your work. ## What it is A thin pipe. Both your device and your host make an **outbound** connection to it on port 443, and it forwards bytes between them. That is the whole job. It does not terminate your session, does not run anything, and does not store your work. [Figure: Your device and your host both dial outbound to the relay on port 443, so the relay path needs no inbound port on the host. The SSH session runs end to end between device and host; the relay only forwards ciphertext it cannot decrypt and sees how many bytes flowed.] ## What runs inside it **Unmodified SSH.** The relay carries an SSH connection between your device and your host's agent, exactly the same connection that runs over your local network — the transport underneath is the only thing that changed. So the relay sees SSH ciphertext. It cannot read your terminal, your prompts, your code or your keys, because it never holds the keys that would decrypt them. Your clients additionally **pin the host key**. A compromised relay could not impersonate your host even if it tried: the pin would not match and the client would refuse. ## What it can see Being precise about this is more useful than reassurance: | Can see | Cannot see | |---|---| | That a pairing connected, and when | Anything inside the session | | How many bytes flowed | What those bytes are | | The IP addresses of both ends | Your code, prompts, output or credentials | The realistic worst case for a compromised relay is **denial of service and connection metadata** — never content. ## No inbound port for the relay Because both sides dial out, the relay path needs no inbound port on your host: your router needs no forwarding rule, and macOS Remote Login stays off. For the other paths, your local network, your tailnet and a host's own address, your devices connect to the SSH server of the host's relay agent, which admits only paired devices. On a host with a public address, such as a VPS, a firewall can close that port to the internet, and the relay still reaches the host. See [What Offsite puts on your machine](/reference/on-your-machine/#ports). ## Switching it off Per host, in the host's settings. With the relay off, that host is reachable on your local network and over Tailscale only — everything else works unchanged, including [pairing](/connect/pairing/). ## Details - Implementation: Cloudflare Workers plus one Durable Object per pairing (`idFromName(pairingId)`), acting as a byte pipe with a small buffer for whichever side arrives first. - Access requires pairing authentication — challenge/response against the Ed25519 device keys your devices already use, with the registry in the Durable Object's storage. An unknown device cannot open a pipe. - Latency through the relay was a median of roughly 90 ms in a measurement with both ends on the same machine (four legs). Your own number depends on where your device and your host are, and the [connection pill](/connect/paths/) shows it. - The relay is transport-neutral by design — the protocol is trivial enough that moving it onto our own servers later would change nothing for your clients. - Push notifications are a separate channel (Firebase) and, unlike the relay, not end-to-end blind: they carry readable text, because a lock screen has to display it. Mostly that is a fixed sentence with the session and workspace names; for a finished agent task or worktree setup, it is the agent's short summary. Never terminal content, prompts, files or code. See [Notifications](/mobile/notifications/). - Offsite runs **no analytics SDK and no crash reporter**. See [Security and privacy](/reference/security/). --- # FAQ Source: https://offsite.sh/docs/help/faq Short answers to the questions people ask before and just after installing. ## Do I need an account? No. There is no sign-up and no server-side user. Devices are authorised against your host by [pairing](/connect/pairing/) with a QR code or a host code. ## Does Offsite charge for tokens? No, and it could not. The agent CLIs call your provider directly from your host under your own subscription — Offsite is not in that path. See [The big picture](/start/big-picture/). ## Is my code sent anywhere? No. Session traffic goes between your own devices, encrypted; if it takes the relay, the relay sees ciphertext only. The one exception is a push notification: it carries readable text — the session and workspace names, so your lock screen can show which agent wants you — through Firebase and Apple. Never code, prompts or output. Full list: [Security and privacy](/reference/security/). ## Do I have to enable Remote Login / SSH? Not for [This Mac](/hosts/this-mac/) and not for normal operation. A [remote Mac](/hosts/remote-mac/) needs it once, for the initial install. ## Does it work when my Mac is closed or asleep? Sessions survive sleep and continue on wake. If you need the host to keep working with the lid closed, use [Keep the host awake](/hosts/keep-awake/); if you need it to come back after a restart, use [Always reachable](/hosts/always-reachable/). ## What happens if I lose connection mid-turn? Nothing, to the agent. It runs on the host and keeps going; you reconnect and find the output waiting. See [Sessions](/work/sessions/). ## After a restart, why does my agent's history start in the middle? The agent kept its full context and the transcript is complete on disk. What the card redraws after a restart is the agent's current context window, which begins at its last compaction. Details and where to find the full transcript: [Troubleshooting](/help/troubleshooting/#only-part-of-the-agents-history-is-back-after-a-restart). ## Can I use several agents at once? Yes — Claude, Codex and Grok side by side on one board, each in its own card and its own [permission mode](/work/agent-sessions/). ## Does my `AGENTS.md` / MCP setup / skills work? Yes. Offsite starts the real CLI in your workspace folder with your shell environment, so everything the CLI would find on its own, it finds. ## Can I run it on Linux? As a **host**, yes — see [A Linux host](/hosts/linux/). There is no Linux client; you drive a Linux host from the Mac and iPhone apps. Note that Claude Code needs AVX2 on x86_64. ## Can an agent see my screen? Only if you turn on [Computer Use](/computer-use/computer-use/) *and* unlock a specific session for it. Off by default, macOS hosts only, revocable in System Settings. ## Can one agent message another? It can *read* another session and it can hand context to a **new** session, but it can never type into a running one. The reason is a real incident, described in [Hand off and ask](/work/handoff-and-ask/). ## Can I test what the agent built, from my phone? Yes: a tunnelled dev server, a mirrored iOS Simulator, or the build installed on the phone in your hand — see [Testing what you build](/testing/testing/). ## Which machines can be a host? Apple Silicon Macs, and Linux on `x86_64` or `arm64` — your own box, a machine in the other room, or a VPS. See [A Linux host](/hosts/linux/). ## Is there telemetry? No analytics SDK and no crash reporter. Feedback reaches us only when you send it from the app. ## Something is broken [Troubleshooting](/help/troubleshooting/) first, then [Get help](/help/get-help/). --- # Get help Source: https://offsite.sh/docs/help/get-help Where to send a problem, and what to include so it can actually be fixed. Offsite is in beta and the fastest fixes come from reports that contain enough to reproduce the problem. ## Send feedback from the app The in-app feedback form is the direct line. It reaches us with what you wrote — and nothing else, because there is no telemetry attached to it. Use it for bugs, confusing behaviour, and things that should exist and do not. ## What to include A report that contains these gets fixed much faster than one that does not: | | | |---|---| | App and host version | both, from the host row in the sidebar | | Host type | This Mac, a remote Mac, or Linux | | Which device | Mac or iPhone | | Connection path | what the [pill](/connect/paths/) said — LAN, Tailscale or relay | | What you did | the click path, not the conclusion | | What happened | including the exact message, if there was one | The version pair matters more than it looks: several symptoms that read like network bugs are really an app and host that no longer speak the same protocol. See [Updating Offsite](/start/updating/). ## Before you send Two checks that resolve a good share of reports on their own: 1. **Are the app and the host on the same release?** If not, update both and fully restart the app — a running app keeps its old protocol even after the bundle on disk was replaced. 2. **Is it in [Troubleshooting](/help/troubleshooting/)?** Known issues are listed there with what actually fixes them. ## Release notes Every version is described at [offsite.sh/releases](https://offsite.sh/releases). If something changed under you, that is where it is written down. --- # Troubleshooting Source: https://offsite.sh/docs/help/troubleshooting Known messages, quirks and errors around Offsite, each with the reason and what fixes it. Everything on this page is a symptom we have seen, with the reason behind it and the step that resolves it. It grows with what we find and what you report. Your agent knows this page. Paste a message into a session and ask why it appeared; the agent finds the entry and walks you through the fix. If a symptom is missing here, [send it in](/help/get-help/) with the exact text. ## Messages from macOS ### "Support Ending for Intel-based Apps" names Offsite Host Service ![macOS notification titled Support Ending for Intel-based Apps. It reads: This version of "Offsite Host Service" includes a component that will not work with a future release of macOS.](../../../assets/help/macos-intel-apps-notice.png) **Symptom:** macOS Tahoe shows this notification, and the app it names is *Offsite Host Service*. **Why:** Apple is retiring Rosetta, the layer that runs Intel-only programs on Apple silicon. Since macOS Tahoe, the system posts this notice whenever an Intel-only program starts, and it credits the notice to the process responsible for that program. Everything you run inside an Offsite session (a terminal command, an agent, a tool the agent calls) runs under the Offsite Host Service, so when one of those programs is Intel-only, the notice names Offsite. The host service itself is an Apple silicon build. The usual culprits are tools installed years ago as standalone Intel binaries: a CLI downloaded from a vendor's installer, an old Homebrew under `/usr/local`, a Node package that shipped a prebuilt Intel binary, a project toolchain fetched for the wrong architecture. **Fix:** find the Intel-only program and replace it with an Apple silicon build. Package managers ship the right architecture, so `brew install …` or `npm install -g …` over the old copy resolves it. To check a single tool: ```bash file "$(which firebase)" # "Mach-O 64-bit executable x86_64" → Intel-only, runs under Rosetta # "Mach-O universal binary … x86_64 arm64" → fine ``` To list every Intel-only tool on the paths a session uses: ```bash for f in /usr/local/bin/* ~/.local/bin/*; do [ -x "$f" ] && lipo -archs "$f" 2>/dev/null | grep -qv arm64 && echo "$f" done ``` The notice is informational. Until Apple removes Rosetta, the Intel-only tool keeps working exactly as before. ## Setup and pairing ### "The host code was already used or has expired" **Symptom:** you enter a host code and the app answers with this message, or with **No host on this network accepted this code**. **Why:** a host code works once and for about ten minutes. The host replaces it after a device paired with it, when the ten minutes are up, and after a revoke. **Fix:** read the current code on the host (`offsite-host setup connect` shows it live) and enter it again, or scan a fresh QR from the Mac app (host **⋯** menu → **Show phone QR**). See [Pairing devices](/connect/pairing/). ### "Code not found. Join the host's network…" **Symptom:** you type a host code on the iPhone and get **Code not found. Join the host's network (or enable its relay)…** **Why:** the relay does not know the code, and the iPhone sees no Offsite host on the network it is on. That happens when the host's relay is off and the iPhone is on another network, or when iOS does not let Offsite look for local devices. **Fix:** join the host's Wi-Fi, or turn on the host's relay. Check **Settings → Privacy & Security → Local Network** and allow Offsite. Or scan the QR instead: it carries the host's address. See [Pairing devices](/connect/pairing/). ### "Access revoked" **Symptom:** the connection pill reads **Access revoked**, and the app stops connecting to that host. **Why:** the host no longer accepts this device's key. Someone revoked it: from this device, from the host, or with **Forget Host…** and its revoke tick. **Fix:** a paired device pairs again with a fresh code or QR. On a host you added with a password, connect with the password, then choose **Authorize this device…** in the host's **⋯** menu. See [Device access](/connect/devices/). ### "Check that Remote Login is healthy on the Mac" This older error text can appear when a connection is rejected during login. On hosts set up with current Offsite versions, Remote Login is **not** involved. The message usually means your device is not authorised yet: pair it with a fresh QR (host **⋯** menu → **Show phone QR**) or the current host code. ## Sessions ### Only part of the agent's history is back after a restart **Symptom:** the host restarted (a macOS update, a power cut, a reboot you chose). Every card came back and the agent continues where it was, but when you scroll up in a Claude card, the conversation starts somewhere in the middle. Earlier parts you remember reading are not on the screen any more. **Why:** two things are at work, and neither loses anything. The card's scrollback belongs to the terminal session on the host. A restart ends that terminal session, and Offsite creates a fresh one, so the screen starts empty. Offsite then resumes the conversation with the CLI's own resume command (`claude --resume` for Claude), and the CLI redraws what it holds in its context window: the summary it wrote at the last compaction and every message after that. Everything before that compaction is not redrawn. A long conversation that has been compacted several times therefore comes back showing only its most recent stretch. Before the restart, those older messages were still on screen only because the terminal had kept them from the moment they were first printed. [Figure: A conversation with two compactions. Before the restart the terminal shows everything printed so far; after the restart the card shows the summary from the last compaction and everything since, and the complete transcript stays on disk.] The agent itself works with exactly the context it had before the restart, and the complete transcript, including everything before each compaction, stays on disk. For Claude that is the session's `.jsonl` file under `~/.claude/projects//`. Offsite also keeps its own archive copy of that file under `~/.offsite/session-archives/`, so a cleanup on the CLI's side cannot take it away. **Fix:** nothing to repair. Keep working in the card as before. To read an older part of the conversation, open the transcript file named above, or ask the agent in that card: it can read its own transcript and quote the part you are after. A card that never compacted comes back with its full history on screen. --- # Uninstalling and resetting Source: https://offsite.sh/docs/help/uninstall Removing Offsite from a host and from a device, in the order that leaves nothing behind. Offsite keeps itself in one directory plus a few named services, so removing it is short. Do it in this order — stopping the services after deleting their files leaves orphaned jobs behind. ## Remove a host ### 1. End your sessions From the app, end the sessions you care about cleanly. Anything still running in tmux will otherwise be killed with the agent. ### 2. Turn the opt-in services back off In the app, switch off [always reachable](/hosts/always-reachable/) and hand sleep control back with **Allow host sleep**. That is the supported path and it removes the services properly. If the app is already gone, clean up by hand. Find what is actually installed rather than guessing at names — Offsite installs several helpers and their labels differ between builds: ```bash # macOS — what is registered launchctl list | grep -i offsite ls ~/Library/LaunchAgents | grep -i offsite ls /Library/LaunchDaemons | grep -i offsite ``` Then unload and delete what you found: ```bash # user agents launchctl bootout "gui/$(id -u)/