Operate your always-on Mac by talking to an AI agent —
and work on it remotely like you're sitting right in front of it.
Features • The problem • Requirements • Quickstart • How it works • What this is not
- 🤖 Drive it with an AI agent. Point Claude or Codex at
control/ops/and say "check on the box" — it SSHes in, reports uptime / disk / load / dev-session health in plain language, and revives a wedged session. No SSH commands typed by you. - 🖥️ Native remote terminal sessions. One command opens the remote as native, resizable panes — real
Cmd+D/Cmd+Tsplits connected to the remote, normal keys, auto-colored so you never confuse it with localhost. Works in iTerm2 (tmux -CC) and Ghostty (native splits), auto-detected. (how it's done →) - ⚡ Instant-feeling typing on slow links.
box-moshconnects over mosh for predictive local echo, so characters appear immediately even over high latency. - 🔐 Log in without Screen Sharing. Remote auth pages open on your Mac, and OAuth
localhostcallbacks reach the right machine — ports are forwarded automatically and remote browser opens are handed back to your laptop. (details →) - 📋 Clipboard bridge (optional). Enable
install-clip-watch.shand screenshots you copy locally auto-mirror to the remote's clipboard —Ctrl+Vthem straight into a tool running on the remote. Prefer no background watcher? Push on demand withbox-clip. - 🧑💻
code .for the remote. Runcode-boxin the remote session and your laptop's Cursor (or VS Code) opens in Remote-SSH mode at that folder — so the editor runs locally, on the remote's files. Orbox-codefrom a cold laptop shell, no session needed. Reuses the handoff listener. - 📦 Painless file transfer & forwarding.
box-push/box-pullmove files without typing the host;box-fwdforwards ad-hoc ports (including random OAuth callbacks). - 🔪 Reclaim a busy port in one keypress. Port management grew up and moved into its own project:
ports-cli — an interactive TUI of every local TCP
listener, grouped by category and aware of the ssh forwards this repo creates (your forwarded dev
server shows as
DEV (SSH), not mystery noise). One keypress kills the holder; it also manages auto-reconnectingkubectl port-forwards.brew install dupe-com/tap/ports-cli. A minimal fzf fallback ships incontrol/bin/ports.sh— the shell snippet only aliases it when ports-cli isn't installed. - 📊 Optional daily health digest to Slack / Discord / any webhook.
You've got a Mac that never sleeps — a Studio under the desk, an old MacBook on a shelf — and you want
to actually use it from your laptop. In practice that means a pile of ssh one-liners, Screen Sharing
that feels like molasses, remote shells that don't behave like local ones, and logins that break because
the OAuth callback points at the wrong machine. When the dev session wedges, you're the one SSHing in to
debug it.
agent-mac-ops turns that always-on Mac into something you operate by talking to an agent and connect to as a native-feeling session — as close to "sitting in front of it" as a remote box gets.
- macOS + iTerm2 or Ghostty on the control machine — for the native-window magic. iTerm2 uses
tmux -CC(Terminal.app won't do it, WezTerm only partially); Ghostty (≥ 1.2.0) uses native splits that each auto-ssh in.boxauto-detects which you're using. (The agent-ops half — status/logs/revive/digest — is plain SSH + bash and works against any host.) - tmux on the remote (
brew install tmux). - mosh (optional, for
box-mosh) on both ends —./setup.sh remoteinstalls it on the remote;brew install moshlocally. Needs UDP 60000–61000 reachable (Tailscale carries it). - SSH reachability to the remote. Tailscale is the recommended way (no
public exposure, works from anywhere) but anything your
sshcan reach is fine — just put a Host alias in~/.ssh/configand use its name. python3andcurlon the control machine (both ship with macOS) for the optional webhook digest.
git clone https://github.com/dupe-com/agent-mac-ops && cd agent-mac-ops
./setup.sh # prompts for host, hostname, dirs, alias, webhook → writes config.env
./setup.sh remote # pushes the dev-session launcher to the remote
# add to ~/.zshrc, BEFORE your iTerm2 shell-integration line:
source /path/to/agent-mac-ops/control/shell-snippet.sh
# iTerm2: follow SETUP.md for the profile + Automatic Profile Switching (the coloring)
# Ghostty: nothing else to do — see SETUP.md §4bNow:
box(or whatever alias you chose) → native, colored window into the remote (iTerm2-CCpanes, or Ghostty native splits — auto-detected). Ghostty addsbox-tmuxfor a persistent session that survives disconnect.box-mosh(either terminal) → connect over mosh for snappy, predictive typing when the link is laggy. Single session + tmux for persistence; forwards/handoff still ride the SSH master.- Point your agent at
control/ops/and say "check on the box." - Browser handoff:
control/bin/install-open-listener.shso remote auth pages open on your Mac. - Clipboard auto-mirror:
control/bin/install-clip-watch.shso screenshots you copy here land on the remote's clipboard automatically —Ctrl+Vthem in a tool on the remote, no command in between. - Open the remote in your local editor: inside the session,
code-box(current dir) opens Cursor/ VS Code on your Mac in Remote-SSH mode; from a local shell,box-code [path]. Needs the handoff listener (install-open-listener.sh) and the host in your~/.ssh/config. ports→ see every local TCP listener and kill the holder in one keypress. Install ports-cli (brew install dupe-com/tap/ports-cli) for the full TUI — it classifies the ssh forwards this repo creates asDEV (SSH)instead of system noise. Without it, the shell snippet falls back to the bundled fzf picker (control/bin/ports.sh).- Optional:
control/ops/bin/install-launchd.shfor a daily health digest.
control machine (your laptop) always-on Mac (the remote)
───────────────────────────── ──────────────────────────
shell-snippet.sh → `box` ───────── ssh -t ───▶ ~/dev-session.sh
(iTerm2) └─ tmux -CC attach ──▶ native iTerm2 windows
(Ghostty) ghostty-connect.sh ─── ssh -t ───▶ └─ login shell / tmux ─▶ native Ghostty splits
(mosh) `box-mosh` ───────── mosh/UDP ────▶ └─ tmux attach ───────▶ predictive-echo session
control/ops/AGENTS.md ← your agent reads this
control/ops/bin/remote-run.sh ── ssh + stdin ──▶ status.sh / logs.sh / revive.sh (run, then gone)
control/ops/bin/daily-check.sh ── launchd ──▶ webhook digest (Slack/Discord/…)
open-listener.py ◀── reverse tunnel (-R) ───── ~/bin/open shim (opens auth URLs on your Mac)
your localhost:3000 ◀── forward (-L) ───────── remote dev server / OAuth callback
- Config lives in one file.
config.env(gitignored, written bysetup.sh) holds the host, hostname, work dir, alias, webhook, etc. Every script sources it; the two*.tmplfiles are rendered from it. - The remote stays stateless. Only
~/dev-session.shlives there. The health scripts are shipped over SSH on stdin with config prepended, so there's nothing to install or keep in sync. - No secrets in git. The only optional secret is the notify webhook, and it lives in the
gitignored
config.env. Use a webhook URL, never a bare token.
Deliberately small. It does not sync dotfiles, manage packages, or sync editor settings — use
chezmoi / yadm / GNU stow for that.
This repo is just the above: agent-operable + a native-feeling remote session.
MIT — see LICENSE.
