设置备用 Mac 供 Claude Code 完全控制:分步指南
英文标题:🔶 Setting up your spare Mac for Claude Code to control, a step-by-step guide
这篇指南详细展示了如何将一台闲置 Mac 变成 Claude Code 可完全操控的常驻环境,让开发者把高风险任务隔离在独立机器上,并随时通过手机或主 Mac 进行远程控制。作者解释了为何不采用容器或 OpenClaw 等替代方案,并提供了从系统抹除、创建无 Apple ID 账户、配置 SSH 与免密 sudo,到安装 Claude Code、同步剪贴板,直至启用「电脑使用」功能的完整操作步骤。最终,Claude 代理能够截图、点击、拖拽真实桌面应用,适合游戏开发等需要完整 macOS 环境的工作流,对追求 AI 自主执行和安全的开发者有很强的参考价值。
中文
如何将备用 Mac 设置为让 Claude Code 完全操控——一份分步指南
这里有一份完整的分步指南,教你如何将闲置 Mac 变成一台可被 Claude Code 完全控制的常驻机器,并启用“电脑使用”功能。你将能从手机上通过 Claude 应用与它对话,或者从主 Mac 通过 SSH 进行控制。
如果你是在 GitHub Pages 上阅读,这里是 仓库版本。
为什么要这样做?
我想为 Claude Code 创建一个独立的运行环境,这样就能把某些不想在自己电脑上执行的任务——比如特定的研究任务和开发任务——委派出去。
Claude Code 本身,尤其是在加上了 --dangerously-skip-permissions 标志之后,在主机器上运行会带来固有风险。你可以在备用 Mac 上为它配备所需的一切,从而消除或降低这些风险。
此外,还有一个额外的好处:你可以随时随地通过手机和 Claude Code 对话。我个人觉得这非常有用,因为我常常更愿意跟 Claude Code 交流,而不是移动应用里的普通版 Claude——Claude Code 往往能力更强。
以下指南假设你有一台主 Mac 和一台可以配置的备用 Mac,不过你也可以从中获取灵感,将其运用到任意两台机器的组合上。
为什么选择这样的方案?
首先,让我们快速回应几个你可能有的疑问。
为什么不在容器中运行?
我非常支持在容器里运行,甚至还专门构建了 一个方便运行的完整环境。不过我发现它有一些局限。第一,它仍然运行在你的主机器上,所以并不是完全隔离。比如,它发出的网络请求还是会经过你的主机器。
第二,容器本身的能力存在限制。例如,我曾想让我的代理能够运行 Unity 进行游戏开发,但在容器中无法轻松做到。对于任何只在 Mac 上可用的应用也是如此——你会失去对这些应用的访问。而如果你希望 Claude Code 通过“电脑使用”来控制这些应用——点击、拖拽等等,这一点就尤其重要。
为什么不使用 OpenClaw 之类的东西?
我个人喜欢能用到 Claude Code 完整且最新的功能。我也喜欢能够通过 Claude 应用来控制它——我发现这非常方便。而且如果你碰巧有 Claude 订阅,还能用到你的订阅额度,这是一个额外的好处。
归根结底,让一个权限广泛的代理在一台毫无损失可言的机器上运行更加安全——同时你还能获得使用完整 Mac 而非容器的所有好处。我们的做法是:
- 使用一台旧/备用 Mac,而不是你的主机器。
- 创建一个没有个人数据,且未登录 Apple ID 的全新本地账户,这样代理就接触不到任何敏感信息。
- 通过 SSH 从主 Mac 驱动它,并可从手机进行控制。
你需要准备什么
- 一台备用 Mac(目标机)。
- 一台你日常使用的 Mac(来源机),并连接在同一个 Wi-Fi 下。
1. 在目标 Mac 上全新开始
先抹掉数据(如果它存有个人数据)
你将会给代理授予这台机器的完整访问权限,所以它能访问机器上存储的任何内容。如果有你不想让它接触的现有数据,请先抹掉磁盘:
- 支持该功能的 Mac: 系统设置 -> 通用 -> 传输或还原 -> 抹掉所有内容和设置。
- 较旧的 Intel Mac: 重启进入恢复模式(开机时按住 Cmd-R),使用 磁盘工具 抹掉内置硬盘,然后重新安装 macOS。
之后可选择性更新到最新版 macOS(系统设置 -> 通用 -> 软件更新)。
创建一个全新的、隔离的账户
- 创建一个 新的本地用户账户(系统设置 -> 用户与群组)。
- 我建议不要登录 Apple ID。 在设置时跳过它。
将账户设为管理员(如果你还未这么做)
账户需要管理员权限,否则 sudo 将无法运行。
- 系统设置 -> 用户与群组 -> 将该账户设置为 允许此用户管理这台电脑。
- 如果今后需要从另一个管理员账户进行修复:
sudo dseditgroup -o edit -a <user> -t user admin
2. 在目标 Mac 上启用远程登录(SSH)
在 目标机 上,打开 SSH 以便来源机能连接过去:
sudo systemsetup -setremotelogin on
如果命令失败并提示 Turning Remote Login on or off requires Full Disk Access privileges,请先赋予你的终端应用“完全磁盘访问权限”:
- 系统设置 -> 隐私与安全性 -> 完全磁盘访问权限。
- 点击 +,然后在文件选择器中前往 应用程序 -> 实用工具 -> 终端 并添加。
- 退出并重新打开终端,然后重新运行命令。
3. 为目标账户配置免密码 sudo
这样代理(以及你的 SSH 命令)就可以在每次不用输入密码的情况下执行管理任务。在目标机上执行一次以下命令,这将一次性要求你输入登录密码:
echo " ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/-nopasswd >/dev/null sudo chmod 440 /etc/sudoers.d/-nopasswd sudo visudo -cf /etc/sudoers.d/-nopasswd # 验证 - 必须输出 'parsed OK'
这会在 /etc/sudoers.d/ 下创建一个小型规则文件,告诉 Mac 允许 <user> 无需密码提示即可运行 sudo:
- 第一行 将规则写入
/etc/sudoers.d/。 - 第二行 设定为只读——否则 sudo 会忽略该文件。
- 第三行 验证语法;sudoers 文件中的拼写错误可能让你完全无法使用
sudo,所以必须看到输出parsed OK。
完成之后,sudo 就可以无提示运行——用 sudo -n true 测试,如果免密码 sudo 设置成功,该命令会静默成功。
4. 找到目标机的地址(主机名或 IP)
你可以通过主机名或者 IP 来访问目标机。我推荐使用主机名:它保持不变,而 IP 可能会变。
主机名(推荐)。 在目标机上运行:
scutil --get LocalHostName # 打印主机名,例如 MacBook-Pro
再在后面加上 .local 来构成地址:<target-host>.local。你也可以从系统设置 -> 通用 -> 共享中查看,那里会显示“本地主机名”。
给目标机一个唯一的名称。 每台 Mac 都需要一个在你的网络上唯一的
.local名称。如果两台机器共享同一个名称,地址可能会指向错误的 Mac。确保目标机的名称是唯一的——如果需要可以重命名:sudo scutil --set LocalHostName newmacbook # -> newmacbook.local
IP 地址(不推荐)。 在目标机上运行:
ipconfig getifaddr en0 # 例如 192.168.1.80
注意,重启后或者经过一段时间后,IP 地址可能会发生变化。
在本指南后续部分,请将 <user> 替换为目标账户名,将 <target-host> 替换为上述主机名,这样地址就形如 <user>@<target-host>.local。你也可以用 IP 地址代替 <target-host>.local。
5. 从来源 Mac 设置免密码 SSH
在来源 Mac 上,创建 SSH 密钥(如果已存在则跳过):
将你的公钥安装到目标机。这将一次性要求目标账户的登录密码:
ssh-copy-id @.local
测试——以下命令应该无密码提示地打印出目标机的用户名:
ssh @.local whoami
6. 让目标机保持唤醒
默认情况下,macOS 在闲置约 10 分钟后会进入睡眠,即使插着电源也一样,这会让它脱离网络。要让电脑永不睡眠,在目标机上(或通过 SSH 从来源机)运行:
sudo pmset -c sleep 0 # 插电时永不系统睡眠(-c = 使用充电器时) sudo pmset -c disablesleep 1 # 合盖时(翻盖模式)也防止睡眠 sudo pmset -c displaysleep 0 # 同时保持显示器开启
验证:
pmset -g | grep -iE 'sleep'
输出中的 sleep 0、SleepDisabled 1 和 displaysleep 0 表示设置成功。
如果机器有时会用电池运行,可以将 -c 换成 -a 以应用于所有电源模式(代价是电池消耗)。
屏幕保护程序启动时,屏幕仍然可能锁定。要阻止屏幕保护程序自动启动,从而避免自行锁定:
defaults -currentHost write com.apple.screensaver idleTime 0
7. 通过 SSH 同步剪贴板
macOS 自带了 pbcopy(写入剪贴板)和 pbpaste(读取剪贴板)。通过 SSH 管道,它们可以在两台机器间传递剪贴板内容——加密、点对点,不需要 Apple ID 或第三方服务。
clip.sh 将这些功能封装为一个包含两个子命令的命令,并在 pbcopy/pbpaste(仅限文本)之上增加了图像支持。将它作为脚本安装到来源 Mac 的 PATH 中,并用 IC_BOX(“ic” 代表 “isolated claude”)指向目标主机:
curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/clip.sh -o ~/.local/bin/clip chmod +x ~/.local/bin/clip export IC_BOX="@.local" # 添加到 ~/.zshrc
用法:
clip send– 将本机的剪贴板 → 发送到目标机(文本或图片)。对于图片,你可以直接将其粘贴进目标机上 Claude Code 会话中,按 Ctrl-V。clip get– 将目标机的剪贴板 → 取回到本机(文本或图片)。
8. 在目标 Mac 上安装 Claude Code
将安装命令发送过去并运行。你可以从来源 Mac 直接将命令推送到目标机的剪贴板,或者远程执行。以下命令安装一个特定版本,但你也可以按需安装 latest 或 stable:
ssh @.local 'curl -fsSL https://claude.ai/install.sh | bash -s -- 2.1.201'
原生安装器可能会警告 ~/.local/bin 不在 PATH 中。在目标机上通过把以下内容添加到 ~/.zshenv(而不是 ~/.zshrc)来修复——.zshenv 会被 每一个 zsh 读取,包括非交互式的 zsh:
ssh @.local 'echo '''export PATH="$HOME/.local/bin:$PATH"''' >> ~/.zshenv'
9. 搭建一个定制化的、对 Claude Code 友好的环境(可选)
这个可选步骤通过 setup-claude-env.sh 应用一组定制化的默认设置——包括 shell 别名、DX 插件、settings.json 调整、GitHub CLI,以及(可选的)Playwright MCP 和 yt-dlp。每个项目都是可选的;详见 claude-env-components.md 中的完整列表。
在目标机上交互运行——会显示一个包含每个项目的清单(核心项已预选,可选项目未选中),你可以选择任意组合。将脚本下载到目标机上并运行:
ssh -t @.local
'curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/setup-claude-env.sh -o setup-claude-env.sh && bash setup-claude-env.sh'
非交互运行——如果脚本检测到非交互环境或你提供了至少一个标志,就会进入此模式。没有提示;默认只安装核心组件,你也可以用标志选择需要的部分(--yt-dlp、--playwright、--all、--core):
ssh @.local
'curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/setup-claude-env.sh -o setup-claude-env.sh && bash setup-claude-env.sh --all'
脚本是幂等的(重复运行没有问题)。
10. 登录 Claude 和 GitHub
这两个登录都是交互式的,因此先 SSH 登录:
ssh @.local
然后在目标机上运行 claude——它会进入 Anthropic 账户的登录流程。根据提示操作(浏览器/设备码流程,你可以在主 Mac 的浏览器上完成)。
GitHub——可选,但强烈推荐,这样代理就可以与仓库协作:
如果你还未从上一步安装 GitHub CLI,请先确保完成安装。
我个人建议使用一个单独的 GitHub 账户,而不是你的主账户,以免它搞乱你的主要账号。
11. 通过 SSH 使用电脑操控(可选)
这项功能能让目标机上的交互式 claude 会话通过 SSH 来查看(截图)并控制(鼠标/键盘)它自己的桌面。
这一操作无法开箱即用——SSH 和 macOS 的权限模型会带来阻碍,因此需要以下设置来绕过这些限制。
为何需要变通方案: macOS 将屏幕录制和输入辅助功能的权限与 GUI 登录会话绑定,因此 SSH 进程无法触及显示器。解决方法:一个 LaunchAgent 在一个固定的套接字上维持一个运行于 GUI 会话 内部 的 tmux 服务器;所有在那里创建的 claude 会话都会落在此服务器上并继承 GUI 会话,从而能够访问显示器。你通过 SSH 进行连接。
脚本化设置
在目标机上运行 setup-computer-use.sh:
ssh -t @.local
'curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/setup-computer-use.sh -o setup-computer-use.sh && bash setup-computer-use.sh'
这会安装 LaunchAgent(包含锚定会话 cc 的持久化 tmux 服务器),并在 ~/.claude.json 中启用内置的 computer-use 工具。需要 tmux(brew install tmux)和一个 Claude Pro 或 Max 方案。可重复运行;使用 --uninstall 来移除。
从你的 Mac 使用它
在来源 Mac 上安装 ic.sh(ic = “isolated claude”):
curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/ic.sh -o ~/.local/bin/ic chmod +x ~/.local/bin/ic echo 'export IC_BOX="@.local"' >> ~/.zshrc # 或者编辑脚本中的默认值
每个 ic 都会生成自己的 claude 会话
English / 原文
How to set up your spare Mac for Claude Code to fully control - a step-by-step guide
Here’s a full step-by-step guide on how to turn your spare Mac into an always-on machine Claude Code can fully control, with computer use enabled. You’ll be able to talk to it from your phone through the Claude app, or from your main Mac over SSH.
In case you’re reading this on GitHub Pages, here’s the repo version.
Why do this?
I wanted to create a separate environment Claude Code can control on its own, so I can delegate tasks I don’t necessarily want to run on my own machine - certain types of research tasks, and development tasks.
Claude Code, especially with the --dangerously-skip-permissions flag on, carries inherent risk when run on your main machine. You can eliminate / mitigate these risks by creating a separate environment on your spare Mac with everything it needs to have access to.
It has an added bonus of being able to talk to Claude Code anytime, anywhere from your phone. I’ve personally found it really useful because I often prefer to talk to Claude Code instead of regular Claude on the mobile app - Claude Code is often more capable.
The following guide assumes you have your main Mac as well as a spare Mac you can set up for this, but you should be able to take inspiration from it and apply it to any combination of two machines.
Why this setup?
First, let’s quickly address a few questions you might have.
Why not run it in a container?
I’m a big proponent of running it in a container - I even built an entire environment for doing so conveniently. However, I’ve found it has a few limitations. First, it still runs on your main machine, so it’s not completely separated. For example, network requests it sends still go through your main machine.
Second, there are limitations to the container’s capabilities. For example, I wanted my agent to be able to run Unity for game development, and there’s no easy way to do that in a container. The same goes for any other app that’s only available on a Mac - you won’t have access to it. That’s especially relevant if you want Claude Code to control these apps through computer use - clicking, dragging, and so on.
Why not use something like OpenClaw?
I personally like having access to the full, latest features of Claude Code. I also like being able to control it from the Claude app - I’ve found it really convenient. And you get to use your Claude subscription usage if you happen to have one, which is an added bonus.
At the end of the day, running an agent with broad permissions is safer on a machine that has nothing to lose - but you get the benefit of being able to use a full Mac instead of a container. The approach here:
- Use an old/spare Mac , not your main one.
- Create a fresh local account with no personal data and no Apple ID signed in, so the agent has nothing sensitive to reach.
- Drive it over SSH from your main Mac on your local network, and control it from your phone.
What you’ll need
- A spare Mac (the target).
- Your everyday Mac (the source), on the same Wi-Fi.
1. Start fresh on the target Mac
Wipe it first (if it has any personal data)
You’ll be giving the agent full access to this machine, so it can reach anything stored on it. If there’s existing data you don’t want it to have access to, erase the machine first:
- Macs that support it: System Settings -> General -> Transfer or Reset -> Erase All Content and Settings.
- Older Intel Macs: restart into Recovery (hold Cmd-R at boot), use Disk Utility to erase the internal drive, then reinstall macOS.
Optionally update to the latest macOS afterward (System Settings -> General -> Software Update).
Create a fresh, isolated account
- Create a new local user account (System Settings -> Users & Groups).
- I recommend not signing into an Apple ID. Skip it during setup.
Make the account an admin (if you haven’t already)
The account needs admin rights or sudo will refuse to run.
- System Settings -> Users & Groups -> set the account to Allow this user to administer this computer.
- If you ever need to repair it from another admin account:
sudo dseditgroup -o edit -a <user> -t user admin
2. Enable Remote Login (SSH) on the target Mac
On the target , turn on SSH so the source Mac can connect:
sudo systemsetup -setremotelogin on
If the command fails with Turning Remote Login on or off requires Full Disk Access privileges, give your terminal app Full Disk Access first:
- System Settings -> Privacy & Security -> Full Disk Access.
- Click + , then in the file picker go to Applications - > Utilities -> Terminal and add it.
- Quit and reopen the terminal, then rerun the command.
3. Passwordless sudo for the target account
This is so the agent (and your SSH commands) can run admin tasks without a password prompt each time. Run this once on the target. It asks for the login password this one time:
echo "<user> ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/<user>-nopasswd >/dev/null
sudo chmod 440 /etc/sudoers.d/<user>-nopasswd
sudo visudo -cf /etc/sudoers.d/<user>-nopasswd # validate - must print 'parsed OK'
This creates a small rule file telling the Mac that <user> can run sudo without a password prompt:
- line 1 writes the rule into
/etc/sudoers.d/. - line 2 makes it read-only - sudo ignores the file otherwise.
- line 3 validates the syntax; a typo in a sudoers file can lock you out of
sudoentirely, so it must printparsed OK.
After this, sudo runs with no prompt - test with sudo -n true, which succeeds silently if passwordless sudo works.
4. Find the target’s address (hostname or IP)
You can reach the target by either a hostname or an IP. I recommend using the hostname: it stays the same, while the IP can change.
Hostname (recommended). Run on the target:
scutil --get LocalHostName # prints the hostname, e.g. MacBook-Pro
Add .local to form the address: <target-host>.local. You can also read it from System Settings -> General -> Sharing, shown as Local hostname.
Give the target a unique name. Each Mac needs a
.localname that’s unique on your network. If two machines share a name, the address can point to the wrong Mac. Make sure the target’s name is unique - rename it if needed:sudo scutil --set LocalHostName newmacbook # -> newmacbook.local
IP address (not recommended). Run on the target:
ipconfig getifaddr en0 # e.g. 192.168.1.80
Note that the IP can change after a reboot or after a certain amount of time.
Throughout the rest of this guide, replace <user> with the target account name and <target-host> with the hostname from above, so the address is <user>@<target-host>.local. You could also instead use an IP in place of <target-host>.local.
5. Set up passwordless SSH from the source Mac
On the source Mac, create an SSH key (skip if you already have one):
Install your public key on the target. This asks for the target account’s login password once:
ssh-copy-id <user>@<target-host>.local
Test it - this should print the target username with no password prompt:
ssh <user>@<target-host>.local whoami
6. Keep the target awake
By default macOS sleeps after ~10 minutes idle, even when plugged in, which takes it off the network. To make it never sleep, run this on the target (or over SSH from the source):
sudo pmset -c sleep 0 # never system-sleep while plugged in (-c = on charger)
sudo pmset -c disablesleep 1 # also prevents sleep with the lid closed (clamshell)
sudo pmset -c displaysleep 0 # keep the display on too
Verify:
pmset -g | grep -iE 'sleep'
sleep 0, SleepDisabled 1, and displaysleep 0 in the output confirm it worked.
If the machine runs on battery sometimes, use -a instead of -c to apply to all power sources (at the cost of battery drain).
The screen can still lock when the screen saver kicks in. Stop the screen saver from ever starting so it never locks on its own:
defaults -currentHost write com.apple.screensaver idleTime 0
7. Clipboard sync over SSH
macOS ships pbcopy (write clipboard) and pbpaste (read clipboard). Piped over SSH, they can move the clipboard between machines - encrypted, peer-to-peer, no Apple ID or third-party service.
clip.sh wraps this into one command with two subcommands, and adds image support on top of pbcopy/pbpaste (which are text-only). Install it as a script on your PATH on the source Mac, and point it at the target host with IC_BOX (“ic” standing for “isolated claude”):
curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/clip.sh -o ~/.local/bin/clip
chmod +x ~/.local/bin/clip
export IC_BOX="<user>@<target-host>.local" # add to ~/.zshrc
Usage:
clip send- this Mac’s clipboard → the target (text or image). For an image you can paste it straight into a Claude Code session on the target with Ctrl-V.clip get- the target’s clipboard → this Mac (text or image).
8. Install Claude Code on the target Mac
Send the install command over and run it. From the source Mac you can push it straight to the target’s clipboard, or run it remotely. The following command installs a specific version, but you can also install latest or stable if you’d like:
ssh <user>@<target-host>.local 'curl -fsSL https://claude.ai/install.sh | bash -s -- 2.1.201'
The native installer may warn that ~/.local/bin is not on PATH. Fix it on the target by adding it to ~/.zshenv (not ~/.zshrc) - .zshenv is read by every zsh, including non-interactive ones:
ssh <user>@<target-host>.local 'echo '\''export PATH="$HOME/.local/bin:$PATH"'\'' >> ~/.zshenv'
9. Set up an opinionated, Claude Code-friendly environment (optional)
This optional step applies a set of opinionated defaults via setup-claude-env.sh - shell aliases, the DX plugin, settings.json tweaks, the GitHub CLI, and (opt-in) Playwright MCP and yt-dlp. Every item is toggleable; see the full list in claude-env-components.md.
Interactively on the target - shows a checklist of every item (core pre-checked, opt-ins unchecked) so you can pick any combination. Download the script onto the target and run it:
ssh -t <user>@<target-host>.local \
'curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/setup-claude-env.sh -o setup-claude-env.sh && bash setup-claude-env.sh'
Non-interactively - the script goes into this mode if it detects a non-interactive environment or you supply at least one flag. No prompt; installs core only by default, or pick what you want with flags (--yt-dlp, --playwright, --all, --core):
ssh <user>@<target-host>.local \
'curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/setup-claude-env.sh -o setup-claude-env.sh && bash setup-claude-env.sh --all'
The script is idempotent (OK to re-run).
10. Log in to Claude and GitHub
Both logins are interactive, so SSH in:
ssh <user>@<target-host>.local
Then run claude on the target - it drops into the login for your Anthropic account. Follow the prompts (a browser/device-code flow you can finish from a browser on your main Mac).
GitHub - optional, but highly recommended so the agent can work with repos:
If you haven’t installed the GitHub CLI from the previous step, make sure to do so first.
I personally recommend using a separate GitHub account, not your main one, so it doesn’t mess up your main account.
11. Computer use over SSH (optional)
This lets an interactive claude session on the target see (screenshots) and control (mouse/keyboard) its own desktop, driven over SSH.
This doesn’t work out of the box - SSH and macOS’s permission model get in the way, so the setup below exists to route around that.
Why it needs a workaround: macOS gates screen capture and input behind Screen Recording and Accessibility permissions that are tied to the GUI login session, so an SSH process can’t reach the display. Fix: a LaunchAgent keeps a tmux server alive inside the GUI session on a fixed socket; every claude session created there lands on that server and inherits the GUI session, so it can reach the display. You attach over SSH.
Scriptable setup
Run setup-computer-use.sh on the target:
ssh -t <user>@<target-host>.local \
'curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/setup-computer-use.sh -o setup-computer-use.sh && bash setup-computer-use.sh'
This installs the LaunchAgent (persistent tmux server with anchor session cc) and enables the built-in computer-use tool in ~/.claude.json. Requires tmux (brew install tmux) and a Claude Pro or Max plan. Re-runnable; --uninstall to remove.
Use it from your Mac
Install ic.sh (ic = “isolated claude”) on the source Mac:
curl -fsSL https://raw.githubusercontent.com/ykdojo/claude-controls-mac/main/ic.sh -o ~/.local/bin/ic
chmod +x ~/.local/bin/ic
echo 'export IC_BOX="<user>@<target-host>.local"' >> ~/.zshrc # or edit the default in the script
Each ic spawns its own claude session on the box (run several at once) and attaches. Flags mirror claude, and it adds several more subcommands:
ic # new claude session
ic -c # continue the most recent conversation (forwards to: claude -c)
ic -r # resume picker (forwards to: claude -r)
ic --chrome # with Claude in Chrome (forwards to: claude --chrome)
ic sh # a plain shell on the box, no claude (alias: ic shell)
ic vnc # open Screen Sharing (VNC) to the box (see step 15)
ic rc # Remote Control: drive the box from your phone (claude remote-control)
ic history # stored conversations: count, location, recent w/ previews (alias: hist)
ic ls # list live sessions (state, age, what's running, conversation)
ic attach <id> # attach a running session (alias: ic a)
ic kill <id> # kill a session (alias: ic k)
ic kill-all # kill all sessions
ic kill-except <id> <id> ... # kill all sessions except the listed ones
ic -h # help
All ic sessions run with --dangerously-skip-permissions (and ic rc uses --permission-mode bypassPermissions).
Copying text out: sessions run in tmux, and Terminal.app can’t receive the clipboard escape sequences (OSC52) that claude emits, so mouse-selecting a snippet won’t reliably reach your Mac clipboard. Quick workaround: Cmd-A then Cmd-C copies the whole visible screen.
Bonus: let your Mac’s Claude drive the box’s Claude
Because every ic session lives on the box’s tmux server at a fixed socket, a Claude Code session on your source Mac can prompt one directly over SSH - useful for delegating work to the box and checking on it, agent to agent:
# find the session (ic ls prints the ids)
ic ls
# type a prompt into it, then confirm with a second Enter - with longer prompts,
# an Enter sent in the same burst as the text doesn't register as submit and the
# prompt sits in the input box (short prompts usually submit fine)
ssh <user>@<target-host>.local "tmux -S /tmp/cc-tmux.sock send-keys -t <session> 'Switch to main and pull; PR #4 is merged.' Enter"
sleep 2
ssh <user>@<target-host>.local "tmux -S /tmp/cc-tmux.sock send-keys -t <session> Enter"
# read the reply (re-run / poll until the spinner is gone)
ssh <user>@<target-host>.local "tmux -S /tmp/cc-tmux.sock capture-pane -t <session> -p" | tail -30
One-time grants (can’t be scripted)
Screen Recording and Accessibility can only be granted in the GUI, and a human has to do it at the machine (in person or via Screen Sharing) - macOS blocks synthetic clicks on these prompts. On first capture you’ll also Allow a “bypass the window picker” prompt (recurs ~monthly).
The grants go ontmux, not claude. macOS attributes capture/control to the responsible process in the chain, which here is the tmux server (claude runs as its child). So:
- Grant
tmux(/usr/local/bin/tmux, or/opt/homebrew/bin/tmuxon Apple Silicon) under both Screen Recording and Accessibility - Screen Recording covers screenshots, Accessibility covers mouse/keyboard control. - Restart the tmux server after granting - a running process caches its permission state at launch, so a grant won’t take effect until the server restarts:
tmux -S /tmp/cc-tmux.sock kill-server(the LaunchAgent respawns the anchor within seconds). claude sessions started after the restart pick up the new grant.
To make the entries appear in System Settings in the first place, trigger a computer-use action (ic, then ask Claude to “take a screenshot”) - macOS adds tmux to the list (toggled off) so you can switch it on.
Full Disk Access stops the “access data from other apps” prompts. On newer macOS, tmux triggers a separate “tmux would like to access data from other apps” prompt for each app whose data anything inside it touches. Granting tmux Full Disk Access (same Privacy & Security pane) suppresses these prompts entirely: use the + button and add the tmux binary (Cmd-Shift-G to type the path). Restart the tmux server afterward, as above.
12. Install a VPN, or any other app (optional)
I personally like to run a VPN on the box so its traffic goes out separately from my local IP. I use Proton VPN - it has a free option and I’ve been using them for a long time - but of course there are other options.
You can just ask the box’s Claude to do it if you finished walking through the previous step: ic in and say “install Proton VPN”. It’ll download and install the app. The parts it can’t do alone:
- Credentials. Signing in is required even for the free tier, and that’s yours to enter. Send the password over securely with
clip sendfrom step 7: copy it on your Mac, runclip send, then paste into the sign-in field on the box. - macOS permission prompts. The first connect pops a system prompt to allow a VPN / network configuration (and may ask for the Mac password) - approve it at the machine.
- Computer use for the GUI. The app is GUI-only, so driving it relies on step 11 being set up. Once signed in, the agent can connect and switch servers itself.
This general flow works not just for a VPN app - it should work for pretty much any other application too.
13. Control it from your phone
Remote Control lets you drive Claude Code from your phone through the Claude app. There are two ways to use it:
- Run
/remote-control(or/rcfor short) inside an existing session and follow the instructions - you can then drive that same session from your phone, going back and forth between the two. - Start a server with
claude remote-control, which also lets you start brand new sessions from your phone, not just attach to one you already have open.
If you went through step 11, ic rc starts that server for you in a way that the sessions you spawn from your phone have access to computer use too.
14. Set up Claude in Chrome (optional)
Computer use from step 11 deliberately won’t control a browser - browsers get a restricted tier where Claude can see what’s on screen but can’t click or type in it. The Claude in Chrome extension fills that gap with proper browser control: navigating, clicking, filling forms, reading console logs and network requests. And unlike Playwright MCP, it drives your regular Chrome profile, so the agent can use any logged-in state you set up on the box.
It requires Chrome on the target and a direct Anthropic plan (Pro, Max, Team, or Enterprise).
You can ask the box’s Claude to install Chrome and open the extension’s Chrome Web Store page. The parts it can’t do alone (do these at the machine, in person or via Screen Sharing):
- Click “Add to Chrome”. Optionally, you can pin the extension to the toolbar by going to
chrome://extensions. - Log into your Claude account in the extension. You can send the login info with
clip sendfrom step 7.
Then enable it in Claude Code on the target: run /chrome in a session and select “Enabled by default” to have the browser tools in every session, or skip that and use claude --chrome per session (ic --chrome from your source Mac). If the connection doesn’t work, try restarting Chrome once.
To make browser use efficient (element refs from the accessibility tree instead of coordinates, no unrequested screenshots), the environment setup from step 9 adds a “Claude for Chrome” guidance section to ~/.claude/CLAUDE.md on the target. If you skipped that step, re-run the script - it’s idempotent.
If your source Mac’s Chrome also has the extension signed into the same account, Claude Code can connect to either browser. It prompts you to pick when both are connected, but its local-machine detection can be wrong (#74667). To avoid that, you can temporarily uninstall the extension from the source Mac’s Chrome - or just quit Chrome on the source while the box is working.
Note that sessions spawned from your phone via step 13 don’t currently get the browser tools (#74671) - the workaround is to start the session in the terminal and attach with /rc.
15. Enable Screen Sharing on the target Mac (optional)
This lets you see the target’s screen live from the source Mac - and take over its mouse and keyboard - using macOS’s built-in Screen Sharing.
This has to be enabled in the GUI at the machine - since macOS 12.1 it can’t be enabled from the command line.
On the target: System Settings - > General -> Sharing -> Screen Sharing on. If Remote Management is on, the Screen Sharing toggle may be hidden - turn it off first.
Then connect from the source Mac:
open vnc://<user>@<target-host>.local
Log in with the target account’s login password and tick Remember this password in my keychain.
16. Access it from anywhere with Tailscale (optional)
.local names only resolve on your LAN, so everything so far is local-network only (except phone control from step 13, which relays through Anthropic). Tailscale fixes that: it connects your machines with peer-to-peer, end-to-end encrypted WireGuard tunnels, so SSH, ic, clip, and Screen Sharing work from any network with nothing exposed to the public internet. On your home network it takes a direct LAN path, so local use stays as fast as before. And joining the network only gives a device reachability - SSH and Screen Sharing still require your key or password on top.
Install on the target (the Homebrew formula works headless over SSH; sign in at the URL the last command prints - a free account with a social login is fine):
ssh <user>@<target-host>.local 'brew install tailscale'
ssh <user>@<target-host>.local 'sudo brew services start tailscale'
ssh <user>@<target-host>.local 'sudo tailscale up --operator=<user>'
Install on the source, then open the Tailscale app and log in with the same account - devices only see each other within one account’s network:
brew install --cask tailscale-app
With MagicDNS (on by default) the target is reachable by bare hostname from anywhere - the same address, minus .local. Switch IC_BOX in ~/.zshrc, keeping the original name for reference:
export IC_BOX="<user>@<target-host>" # Tailscale - works remotely too
# export IC_BOX="<user>@<target-host>.local" # original - local network only
Screen Sharing works the same way: open vnc://<user>@<target-host>.
Recommended, in the admin console: device approval (Settings -> Device management), so a compromised Tailscale login alone can’t add a device to your network, and disable key expiry on the target (Machines -> …), so the box doesn’t drop off the network when its key expires after ~180 days.
To confirm remote access really works, put the source Mac on a different network (e.g. a phone hotspot) and run ic ls.
If you want more Claude Code and agentic coding tips, feel free to subscribe to my newsletter, Agentic Coding with Discipline and Skill.