自宅のMacで動いているClaude CodeやCodexに、外出先のiPhoneから指示を出す環境を作りました。
月額はゼロで、スマホには専用アプリを入れません。ブラウザでURLを開くだけです。
なぜherdrを選んだかは前の記事に書いたので、ここでは手順を記載します。
macOSで2回つまずいたので、その原因と直し方も載せています。
構成
iPhone (Safari / ホーム画面に追加)
↓ Tailscale(WireGuard・自分のネットワーク内だけ)
Mac :17930 (HTTPS)
↓ tailscale serve
herdr-web :7930(127.0.0.1限定)
↓ socket API
herdrサーバー(常駐・ログイン時に自動起動)
↓
Claude Code / Codex / 他20種
herdrがターミナルを所有し、herdr-webがそれをWeb UIとして出し、
Tailscaleがスマホとの通信をつなぐ。役割はこの3つに分かれています。
環境
- macOS 26.6.2 / Apple M4
- Node v22.17.0(herdr-webの要件は18以上)
- herdr 0.9.1(herdr-webの要件は0.7以上)
1. herdrを入れる
brew install herdr
brew services start herdr
brew servicesで起動しておくと、ログインするたびに自動で復帰します。
herdrを選んだ理由の1つが常駐性なので、ここは入れておきます。
次にエージェントとの連携を入れます。
herdr integration install claude
herdr integration install codex
入るのは~/.claude/hooks/herdr-agent-state.shのようなシェルスクリプトのフックです。
エージェントの状態(working / blocked / idle)をherdrのソケットに通知するだけで、
プロンプトにトークンを足すものではありません。
スクリプトの中身を見るとHERDR_ENV=1でなければすぐexitするので、
herdrの外でclaudeを起動したときは何もしません。
サーバーが動いているか確認します。
herdr status
server:
status: running
socket: /Users/xxx/.config/herdr/herdr.sock
2. herdr-webを入れる
スマホから見るためのWeb UIです。herdrのプラグインとして入ります。
herdr plugin install eyalev/herdr-web --yes
依存はexpressとwsの2つだけ、ライセンスはMITです。
ソースを見ると、待ち受けるアドレスはこうなっています。
const BIND = process.env.HERDR_WEB_BIND || '127.0.0.1';
localhost固定です。自分でHERDR_WEB_BIND=0.0.0.0を設定しない限り外には出ません。
起動しようとしたら、2回失敗しました。
つまずき1:setsidがない
scripts/plugin-start.sh: line 22: setsid: command not found
setsidはLinuxのutil-linuxに入っているコマンドで、macOSには存在しません。
起動スクリプトがサーバーをデタッチするのに使っていました。
macOSではnohupとdisownで同じことができるので、分岐を足します。
対象は~/.config/herdr/plugins/github/eyalev.herdr-web-*/scripts/plugin-start.shです。
# macOS has no setsid; fall back to nohup + disown.
if command -v setsid >/dev/null 2>&1; then
setsid nohup node "$ROOT/server.js" >> "$STATE_DIR/server.log" 2>&1 < /dev/null &
else
nohup node "$ROOT/server.js" >> "$STATE_DIR/server.log" 2>&1 < /dev/null &
disown "$!" 2>/dev/null || true
fi
echo $! > "$STATE_DIR/server.pid"
つまずき2:nodeが見つからない
直して再実行すると、別のエラーになりました。
nohup: node: No such file or directory
ターミナルではnode -vが通ります。
原因は1つ目の手順にあります。brew services start herdrでサーバーを常駐させたので、
herdrはlaunchd経由で起動しています。launchdは最小限のPATHしか持たないため、
nvm管理下のnodeを見つけられません。
同じスクリプトのPORT=の行の直前に、nodeを探す処理を足します。
# launchd gives the herdr server a minimal PATH; make sure node is findable.
PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
if ! command -v node >/dev/null 2>&1; then
for d in "$HOME"/.nvm/versions/node/*/bin; do
[ -x "$d/node" ] && PATH="$d:$PATH" && break
done
fi
export PATH
これで起動しました。
{"level":"info","event":"listening","port":7930,"bind":"127.0.0.1"}
この2つのパッチはherdr plugin installでプラグインを更新すると上書きされます。
元のファイルを.origで残しておくと、次に当て直すときに比較できます。
3. Tailscaleを入れる
スマホとMacをつなぐ部分です。
brew install --cask tailscale-app
Macのパスワードを聞かれます。システム機能拡張がブロックされたと出たら、
システム設定のプライバシーとセキュリティで許可します。
起動してサインインします。ここで引っかかりやすい設定が2つあります。
セットアップ中に出る「Start at login」は、初期状態がOFFです。
OFFのままだとMacを再起動したときにTailscaleが立ち上がらず、
外出先でスマホを開くまで気づけません。
herdr側はログイン時に自動起動する設定にしたので、ここを揃えないと片方だけ復帰します。
もう1つ、スマホにも同じアカウントでサインインします。
別のアカウントにすると別のネットワークになり、Macが見えません。
HTTPSを有効にする
管理画面 https://login.tailscale.com/admin/dns を開いて、2つ有効にします。
- MagicDNS
- HTTPS Certificates
HTTPS CertificatesはDNSページの一番下にあります。Add nameserverボタンのさらに下です。
有効化すると、マシン名とテイルネットのDNS名が証明書の公開台帳(証明書トランスペアレンシーログ)に
載ります。マシン名に個人が特定できる情報を入れている場合は、先に変えておきます。
有効にしないままtailscale serveを打つと、コマンドが返ってこなくなります。
原因を確かめるには証明書を直接取りに行くと分かりやすいです。
tailscale cert <マシン名>.tailXXXXXX.ts.net
500 Internal Server Error: your Tailscale account does not support getting TLS certs
このメッセージが出たら、HTTPSがまだ有効になっていません。
なお、このコマンドはカレントディレクトリに証明書と秘密鍵を書き出します。
Gitリポジトリの中で実行すると鍵がリポジトリに残るので、確認後に消しておきます。tailscale serveは自分で証明書を管理するので、手動で作ったものは要りません。
スマホ側
App StoreからTailscaleを入れて、Macと同じアカウントでサインインし、Connectを押します。
iOSからVPN構成の追加を求められるので許可します。
一般的なVPNサービスと違い、通信がどこかのサーバーを経由するわけではありません。
iOSの仕組み上VPN構成として登録が必要なだけで、実際にはMacと直接つながります。
2台が乗ったか、Macから確認できます。
tailscale status
100.x.x.x macbook-xxx xxx@ macOS -
100.x.x.x iphonexxx xxx@ iOS -
4. 公開する
tailscale serve --bg --https=17930 http://127.0.0.1:7930
Available within your tailnet:
https://<マシン名>.tailXXXXXX.ts.net:17930/
|-- proxy http://127.0.0.1:7930
Available within your tailnet と出ていれば、自分のデバイスからしか見えません。
herdr-webには認証がない
herdr-webには認証がありません。READMEにもこう書かれています。
全ペインのターミナルを無制限に操作できる。認証なし。localhost専用の設計
安全性はTailscaleのネットワーク層に依存しています。ルールは1つです。
tailscale serve… 自分のネットワーク内のデバイスにしか見えない ← これだけを使うtailscale funnel… インターネット全体に公開される ← 使わない
確認はこれでできます。
tailscale funnel status
(tailnet only) 以外が出たら、すぐtailscale serve --https=17930 offで止めます。
設定そのものを見たい場合はこちらです。AllowFunnelが存在しなければ大丈夫です。
tailscale serve status --json
5. セッションを作る
ここまでで接続はできましたが、herdrにセッションがないとスマホ側は空です。
ターミナルで、作業したいディレクトリに移動して起動します。
cd ~/Documents/myproject
herdr
herdrの画面が立ち上がります。その中でclaudeやcodexを起動すると、
herdrが自動で検出してタブに状態が出ます。
| 操作 | キー |
|---|---|
| 右に分割 | Ctrl+B → V |
| 下に分割 | Ctrl+B → - |
| 新しいタブ | Ctrl+B → C |
| 抜ける | Ctrl+B → Q |
マウスでも操作できます。ペインをクリックしてフォーカス、境界をドラッグでリサイズ、
右クリックでメニュー。tmuxのようにキーを暗記しなくても使えます。
Ctrl+B → Q で抜けても、プロセスは動き続けます。
ターミナルのウィンドウを閉じても同じです。戻るときはまたherdrと打つだけ。
Claude CodeとCodexを並べた状態で、herdrが認識しているものを見てみます。
herdr pane list
agent="claude" status=idle ✳ Claude Code
agent="codex" status=idle
2つが別々のエージェントとして並びました。これがスマホからも見えます。
6. スマホで開く
Safariでhttps://<マシン名>.tailXXXXXX.ts.net:17930を開き、
共有ボタンからホーム画面に追加します。
PWAとして登録されるので、アプリのように起動でき、
エージェントが入力待ち(blocked)になったときにシステム通知が届きます。
herdr-webの画面には、タブ・ターミナル表示のほかに、Esc Tab Ctrl ↑↓←→ Enter といったソフトキーと、Type to the agent... の入力欄があります。
過去の会話に戻りたいときは、ペインでclaude --resumeを打てば一覧が出るので、
矢印キーで選べます。
つまずきの一覧
同じことをやる人向けにまとめておきます。
| 症状 | 原因 |
|---|---|
| プラグインが起動しない | macOSにsetsidがない |
| 同上(2回目) | launchdの最小PATHでnodeが見つからない |
tailscale serveが返ってこない | HTTPS Certificatesが未有効 |
| HTTPSの設定場所が分からない | DNSページの最下部にある |
| 証明書がリポジトリに残った | tailscale certはカレントディレクトリに書く |
| Codexが起動時に警告を出す・完了音が2回鳴る | 後述 |
最後の1つは、しばらく使ってから気づきました。
herdr integration install codexは~/.codex/hooks.jsonと~/.codex/config.tomlの
両方に書き込みます。config.toml側に自分で書いたフックが既にあると、
二重読み込みになってCodexが起動時に警告を出します。
loading hooks from both ~/.codex/hooks.json and ~/.codex/config.toml;
prefer a single representation for this layer
私の場合は両方にStopフックがあったので、完了音が2回鳴っていました。
herdrが管理するのはhooks.json側なので、config.tomlから[[hooks.*]]を消して
hooks.jsonに寄せます。逆向きに寄せると、herdr integration installを実行するたびに再発します。


コメント