ブログ
スマホから Claude Code をリモート操作する
Tactic Remote が Mac を常時稼働の AI コーディングサーバーに、iPhone を第一級の制御面に変える仕組み — マーケティング文句ではなく、実際のアーキテクチャの詳細。
このカテゴリのプロダクト/開発アップデートを確認できます。
カテゴリを見るClaude Code はターミナルベースの AI コーディングアシスタントです。Mac 上で動作し、コードベースを読み、コードを書き、テストを実行し、変更をコミットします。問題は、作業中ずっと Mac の前に座っていなければならないこと。
Tactic Remote はこの制約を打ち破るために存在します。Mac は実行エンジンとして維持され、iPhone が制御面になります。この記事では、それがどのように機能するか — アーキテクチャ、プロトコル、トレードオフ、セットアップ — を正確に説明します。曖昧な説明はありません。
要約: Mac 上の Node.js WebSocket サーバーが tmux を通じて Claude Code を管理します。iPhone は LAN または Cloudflare Tunnel 経由で接続し、コマンドを送信し、ストリーミングされたターミナル出力を受信します。Claude が完了したとき、または入力が必要なときにプッシュ通知でお知らせします。
アーキテクチャ
4つのコンポーネント、3つのネットワークパス:
Node.js サーバーのランタイム依存は ws npm パッケージただ1つです。HTTP サーバー、認証、レート制限、tmux 管理、ファイルブラウジング — それ以外はすべて Node.js の組み込み機能で構築されています。これは意図的な設計です。依存が少なければ壊れるものが少なく、サプライチェーンリスクが減り、攻撃対象面も小さくなります。
サーバーによる Claude Code の制御
ほとんどの人が驚くポイントはここです:サーバーと Claude Code の間に API は存在しません。 サーバーは、あなたと同じ方法で Claude Code を制御します — ターミナルに入力することで。
Claude Code は tmux セッション内で動作します。サーバーは tmux send-keys でキーストロークを送信し、tmux capture-pane で出力を読み取ります。SDK 統合ではなく、ターミナル自動化です。
セッションの作成
iPhone から新しいセッションを作成する場合:
send-keys の -l フラグは重要です — tmux に内容をリテラルとして解釈するよう指示し、特殊文字が tmux のキー名として扱われることを防ぎます。
50,000行のスクロールバックバッファは tmux のデフォルトの25倍です。Claude Code のセッションは膨大な出力を生成します — コンパイルログ、ファイル差分、テストスイート。これがなければ、負荷の高い作業では数分でコンテキストが失われます。
Claude へのメッセージ送信
iPhone でプロンプトを入力して送信すると:
最初の C-u は入力行をクリアします。これがないと、Claude が前回の中断された入力から部分的なテキストをプロンプトに残していた場合、メッセージがゴミデータに追加されてしまいます。
Claude の停止
Claude Code には異なる処理が必要な2つの状態があるため、停止は複数ステップのシーケンスです:
Claude が実行中なら Ctrl+C で中断します。Claude がすでにプロンプトにいる場合は、/exit が Claude Code の終了コマンドです。両方を送信することで両方の状態をカバーします。200ms のスリープは、次のシグナルが到着する前に Claude が各シグナルを処理する時間を確保します。
ターミナル出力ストリーミング
これはシステム全体で最も困難なエンジニアリング課題です。Claude Code はコンパイルやテスト実行時に毎秒数百行のテキストを出力します。その出力は、バッテリーを消耗させたりネットワークを圧迫したりすることなく、リアルタイムで iPhone に届ける必要があります。
ポーリングループ
サーバーは1秒間隔のポーリングでターミナル内容をキャプチャします:
これは ANSI エスケープコードを保持したまま、スクロールバックの最後の2,000行をプレーンテキストとして取得します。
なぜイベント駆動ではなくポーリングなのか?tmux には変更通知機能がありません。tmux の内部ソケットに対するファイルシステムウォッチャーを評価しましたが、macOS のバージョン間で不安定でした。1秒間隔のポーリングは良いバランスです:出力がリアルタイムに感じられるほど速く、セッションがアイドル時の CPU 使用率を最小限に抑えるほど遅い。
安定性検出
出力モニターは単に生のキャプチャをストリーミングするわけではありません。安定性検出アルゴリズムを実行します:
- 毎秒ターミナル内容をキャプチャ
- 前回のキャプチャと比較
- 異なる場合:iPhone に更新を送信し、安定性カウンターをリセット
- 5回連続で同一(5秒間安定)の場合:Claude が入力待ちかチェック
- Claude がプロンプトにいる場合:
final: true付きの最終更新を送信し、ポーリングを停止
これは Claude のプログレッシブレンダリングに対処します — 長い出力中はターミナルが急速に変化します。Claude が完了すると画面が安定します。5秒の安定性は、Claude が完了したことの信頼性の高いシグナルです。
モニターには120回(2分)のハードキャップがあります。2分後もまだ出力が変化している場合、モニターは停止し、クライアントは手動リフレッシュをリクエストできます。
Claude の状態検出方法
サーバーは Claude Code の TUI をスクリーンスクレイピングして何をしているか判断します。これは必然的にヒューリスティックです:
ターミナル出力の最後の数行をこれらのパターンでチェックします。Goodbye! や Session ended が見えたら Claude は終了しています。Claude 固有のインジケーターなしにシェルプロンプト(行末の $)が見えたら、セッションはシェルに戻っています。
これは脆弱か?ある程度は。Claude Code が TUI レイアウトを変更したら、ヒューリスティクスが壊れます。しかし代替案 — Claude Code にカスタム IPC チャネルを構築する — にはフォークの維持と上流の変更の追跡が必要です。既存の TUI のスクリーンスクレイピングは実用的です:Claude Code のすべてのバージョンで変更なしに動作します。
ANSI カラーの保持
重要な設計判断:サーバーは出力の ANSI エスケープコードを保持します。 問題のあるターミナルシーケンス(カーソル移動、OSC タイトルシーケンス、プライベートモード変更)は除去しますが、カラーコードは意図的に維持します:
iOS アプリはこれらのコードをネイティブの AttributedString スタイリングにパースします。これにより、Claude Code のフルカラー出力 — シンタックスハイライト、diff の色分け、ステータスインジケーター — がスマホ画面で忠実にレンダリングされます。パーサーは標準16色、256色モード、24ビットトゥルーカラーに対応しています。
WebSocket プロトコル
サーバーは同じ WebSocket 接続上で2つのプロトコルを同時に扱います — レガシーフラットプロトコルと、新しいリクエスト/レスポンス/イベントプロトコルです。
新プロトコル(推奨)
3つのフレームタイプ:
id フィールドにより適切な非同期リクエストの多重化が可能になります。クライアントは複数のインフライトリクエストを持ち、レスポンスを正しくマッチできます。レガシープロトコルにはリクエスト ID がありません — レスポンスは位置ベースで、2つのリクエストを素早く送信すると、どのレスポンスがどのリクエストに属するか判別できません。
メソッド一覧
| メソッド | 用途 |
|---|---|
list_sessions | 全 tmux セッションの名前、パス、アタッチ状態を取得 |
create_session | 指定パスに新しい tmux セッションを作成 |
attach_session | 既存セッションに切り替え、現在の出力を取得 |
delete_session | tmux セッションを即座に終了 |
kill_session | Claude をグレースフルに停止してからセッションを終了 |
start_claude | 現在のセッションで Claude Code を起動 |
stop_claude | Ctrl+C + /exit シーケンスを送信 |
send_to_claude | メッセージを入力して Enter を押す |
send_key | 単一キーを送信(Enter, Escape, Tab, y, n, Ctrl+C) |
get_output | 現在のターミナル内容と Claude の状態を取得 |
get_full_output | 最大5,000行のスクロールバックを取得 |
change_path | 作業ディレクトリを変更 |
list_directory | パスのファイルを閲覧 |
create_folder | 新しいディレクトリを作成 |
send_key メソッドには18キーの明示的な許可リストがあります。任意のキーシーケンスは送信できません — Claude Code のインターフェースとのやり取りに意味のあるキーのみが使えます。y と n キーは、スマホから Claude の権限プロンプトに応答するために特別に含まれています。
ネットワーク: LAN vs. Cloudflare Tunnel
2つの接続モードが2つの異なる問題を解決します。
ローカルネットワーク(LAN)
iPhone が WiFi 経由で Mac に直接接続します:
Mac コンパニオンアプリが Bonjour サービス(_ws._tcp.)を公開するので、iPhone がネットワーク上で自動検出できます。設定不要 — アプリを開いて、バナーをタップすれば接続完了です。
レイテンシ: 往復10ms未満。ターミナルストリーミングは瞬時に感じます。
制限: 同一ネットワークでのみ動作します。家の外に出ると接続が切れます。
Cloudflare Tunnel
リモートアクセスには、Mac コンパニオンアプリが Cloudflare Tunnel を開始できます:
トンネルは Mac からのアウトバウンドのみです。ファイアウォールのポートは一切開きません。ルーターの設定も不要です。Cloudflare が TLS 終端、DDoS 保護、WebSocket プロキシを処理します。
クイックトンネルのセットアップで、ランダムな *.trycloudflare.com サブドメインとランダムな32文字の API キーが生成されます:
Cloudflare アカウント不要。無料。約10秒で動作します。
レイテンシ: 通常30〜80ms、最寄りの Cloudflare エッジまでの距離に依存します。
トレードオフ: トンネルを再起動するたびに URL が変わります。固定 URL が必要な場合は、Cloudflare アカウントと名前付きトンネル(これも無料)が必要です。
QR コードクイック接続
手動で接続を設定するには、スマホのキーボードで IP アドレス、ポート、API キーを入力する必要があります。これはエラーが起きやすく面倒です。
Mac コンパニオンアプリは接続情報を JSON として含む QR コードを生成します:
iPhone アプリから Vision フレームワークのバーコード検出器を使ってスキャンします。1回のスキャンで接続全体 — モード、アドレス、認証 — がセットアップされます。約3秒で完了します。
通知パイプライン
プッシュ通知は、Tactic Remote の「セットしたら忘れる」ワークフローにとって最強の機能です。長いタスクを開始し、離席して、Claude が完了したとき、または入力が必要なときに通知を受け取れます。
仕組み
Claude Code は hooks をサポートしています — ライフサイクルイベントで実行されるシェルスクリプトです。Mac コンパニオンアプリはこれらの hooks をローカルサーバーに POST するよう自動設定します:
Claude がタスクを完了すると、この hook が発火します。サーバーが POST を受信し、接続中のすべてのクライアントに通知イベントをブロードキャストし、iOS アプリが UNNotificationCenter 経由でローカル通知を発火します。
イベント重複排除
Hook イベントは連続して急速に発火することがあります(例:Claude が複数のサブタスクを停止・再開する)。iOS アプリは event|project|session|timestamp をキーとする3秒のウィンドウを使ってイベントの重複を排除します。同じ論理イベントが3秒以内に2回到着した場合、重複はサイレントに破棄されます。
フォアグラウンド vs. バックグラウンド
アプリがフォアグラウンドにあるとき、通知イベントはターミナルバッファにビジュアルインジケーターを追加します:
アプリがバックグラウンドにあるとき、システム通知が発火します。タップするとセッションに直接戻れます。
セキュリティモデル
AI 生成コードをリモートで実行するには、厳格なセキュリティモデルが必要です。以下が私たちのモデルです。
認証
サーバーは2つのメカニズムで API キー認証をサポートします:
Authorization: Bearer <key>ヘッダー(標準)Sec-WebSocket-Protocol: <key>ヘッダー(アップグレードリクエストでカスタムヘッダーを設定できない iOS WebSocket ライブラリ向けのフォールバック — モバイルでよくある制約)
認証は接続時に一度だけチェックされます。失敗した場合、WebSocket はコード 1008(Policy Violation)で即座にクローズされます。
コマンドインジェクション防止
サーバーが実行するすべての外部コマンドは execSafe() を通り、child_process.spawn() を shell: false で呼び出します。引数は配列として渡され、文字列に補間されることはありません。シェルインジェクションは構造的に不可能です。
入力バリデーションは多層防御を追加します:
| 入力 | バリデーション |
|---|---|
| セッション名 | /^[a-zA-Z0-9_-]{1,64}$/ |
| Claude コマンド | /^claude(\s+--?[a-zA-Z0-9-]+)*$/ |
| キー名 | 18キーの明示的許可リスト |
| ファイルパス | 絶対パスに解決し、startsWith() でチェック |
パストラバーサル保護
ファイルブラウジングはユーザーのホームディレクトリツリーに制限されます。他の設定に関係なく、機密ディレクトリはブロックされます:
チェックはセグメント単位のマッチング(部分文字列マッチングではない)を使用するため、my-.ssh-backup という名前のディレクトリが誤検出を引き起こすことはありません。
レート制限
認証失敗のリクエストは、インメモリのスライディングウィンドウを使って IP あたり毎分30回にレート制限されます。制限を超えると、サーバーは retryAfter ヒント付きの 429 Too Many Requests を返します。
クラウド依存なし
コードは Mac から外に出ることはありません。Tactic Remote はコントロールプレーンであり、データプレーンではありません。WebSocket はターミナルテキスト — コマンド入力と出力文字列 — を運びます。ソースファイル、git 履歴、ビルド成果物はあなたのマシン上に留まります。
Cloudflare Tunnel を使用する場合、トンネルはトラフィックをエンドツーエンドで暗号化します。Cloudflare が見るのは暗号化された WebSocket フレームであり、あなたのコードではありません。
iOS アプリ:ターミナルエミュレータではない
開発初期に意識的な決断を下しました:iPhone アプリはダムターミナルエミュレータではありません。 フル 80x24 ターミナルを4インチのスマホ画面に詰め込むのは、ひどい体験です。
代わりに、アプリは以下を提供します:
- 会話スタイルの入力: ターミナルプロンプトではなく、テキストフィールドで Claude にメッセージを入力
- ターミナル出力ビュー: ANSI カラー対応のレンダリングテキスト、自動スクロール、ピンチでフォントサイズ変更(デフォルト8pt)
- クイックアクションボタン: Enter, Escape, Tab, Y, N, Ctrl+C — Claude Code とのやり取りで実際に必要なキー
- セッションピッカー: tmux セッションの閲覧と切り替え
- パスブラウザ: Mac のファイルシステムをナビゲートして作業ディレクトリを設定
- スラッシュコマンド:
/help、/clear、/compact、/cost、/model、/statusの自動補完
再接続戦略
モバイルアプリは厳しいネットワーク環境で動作します。スマホは WiFi とセルラーを切り替え、エレベーターに入り、スリープします。アプリの再接続戦略はこれらすべてに対処します:
- 接続切断の検出: WebSocket クローズイベントが発火
- 指数バックオフ:
min(attempts * 3, 15)秒後にリトライ、最大5回 - バックグラウンド認識: アプリがバックグラウンドなら再接続をスキップ。フォアグラウンド復帰時に再接続するフラグを設定
- セッション復元: 再接続時、セッション一覧取得 → 前回アクティブだったセッションを検索 → 再アタッチ → 0.2秒、0.7秒、1.5秒の遅延で出力をポーリング
再接続後の段階的な出力ポーリング(0.2秒、0.7秒、1.5秒)は、再アタッチ後に tmux がキャプチャを生成するまでに少し時間が必要なケースに対処します。即座の単一ポーリングでは空のコンテンツが返ることがよくあります。
ターミナルバッファ
TerminalBuffer は500,000文字、10,000行を上限とする循環バッファです。オーバーフロー時は先頭からコンテンツが切り詰められ、最寄りの改行境界にスナップするため、部分的な先頭行が表示されることはありません。
この上限が存在するのは、AttributedString のレンダリングがスケールに応じて高コストになるためです。500K 文字は Claude Code のヘビーな出力の約30分分 — あらゆる合理的なセッションに十分でありながら、UI のレスポンシブ性を維持します。
Mac コンパニオンアプリ
Mac コンパニオンアプリは、Node.js サーバー、Cloudflare Tunnel、システム設定を管理するメニューバーアプリケーションです。
サーバーバンドル
Node.js サーバー全体が Mac アプリ内にバンドルされています。初回起動時にサーバーファイルを ~/Library/Application Support/ClaudeCodeRemote/Server/ にコピーします。以降の起動では、ファイルをバイト単位で比較し、変更されたファイルのみを再コピーします。npm install は package.json が変更された場合のみ再実行されます。
これにより、ユーザーがサーバーのインストールや更新のためにターミナルを触る必要はありません。
依存関係の検出
アプリは必要なツール(Node.js, tmux, Homebrew)を、まず既知のパス(/opt/homebrew/bin/、/usr/local/bin/)をスキャンし、次に which にフォールバックしてチェックします。依存関係が不足している場合、セットアップ画面がインストールを案内します。
スリープ防止
サーバー実行中、アプリは IOPMAssertionCreateWithName を呼び出して macOS がスリープするのを防ぎます。これは不可欠です — スリープ中の Mac は WebSocket 接続を処理できません。
Hook 自動設定
コンパニオンアプリは ~/.claude/hooks/ にシェルスクリプトを書き込み、ライフサイクルイベントをローカルサーバーに POST します。これはサーバー起動時に自動的に行われ、手動設定は不要です。
私たちが作らなかったもの
意図的に行わないことを選択したものがいくつかあります:
アプリ内のターミナル多重化はなし。 サーバー側では tmux を使用していますが、iPhone アプリは一度に1つのセッションを表示します。スマホ画面でのスプリットペインは読めないでしょう。
ファイル編集はなし。 アプリはファイルを閲覧できますが、編集はできません。ファイル編集は Claude Code が行います — あなたはレビュアーであり、エディタではありません。モバイルコードエディタを追加することは、コア体験を劣化させるスコープクリープになります。
オフラインモードはなし。 Mac に到達できない場合、アプリは最後の既知の状態を表示しますが、有用なことはできません。そうでないふりはしません。
リモートデスクトップフォールバックはなし。 画面共有や VNC は存在しますが、このユースケースには10倍劣ります。Tactic Remote はピクセルではなく構造化テキストを送信します。これにより、帯域幅が低く、レイテンシが低く、縮小されたデスクトップではなくタスクに合わせて設計された UI を実現します。
クイックセットアップ
詳細をスキップしたい場合、動作させるための最小手順はこちら。
Mac で
- tacticremote.com/download から Mac コンパニオンアプリをインストール
- 起動 — 依存関係を自動インストールしてサーバーを起動
- アプリに表示される QR コードを確認
iPhone で
- App Store から Tactic Remote をインストール
- Mac アプリの QR コードをスキャン
- プロジェクトディレクトリを指定して セッションを作成
- Claude を起動して最初のプロンプトを送信
自宅ネットワーク外からのリモートアクセスには、Mac アプリの設定で Cloudflare Tunnel トグルを有効にしてください。アプリが自動的にパブリック URL を生成します。
通知の設定
Mac アプリでは、サーバー起動時に hooks が自動設定されます。iPhone では、プロンプトが表示されたら通知権限を許可してください。以上です。
FAQ
アプリを閉じても Claude は動き続けますか?
はい。Claude は Mac 上の tmux 内で動作します。iPhone アプリはそのセッションへのウィンドウです。アプリを閉じても、スマホをポケットに入れても — Claude は作業を続けます。アプリを再度開くと、不在中に起きたすべてが表示されます。
ネットワークが切断された場合は?
iPhone アプリは指数バックオフで自動再接続します(最大5回、約45秒)。再接続時、前回のセッションに再アタッチし、蓄積された出力を取得します。作業は失われません — tmux が Mac 側ですべてをバッファリングしています。
バッテリー消費はどれくらいですか?
iPhone アプリはバックグラウンドで永続的な接続を維持しません。バックグラウンド時はクリーンに切断します。実際に使用していないときのバッテリーへの影響は実質ゼロです。
他の AI コーディングツールでも使えますか?
Tactic Remote は Claude Code のターミナルインターフェースを中心に構築されていますが、基盤となるアーキテクチャ(tmux セッション管理、ターミナルストリーミング)はターミナルベースのツールなら何でも動作します。ターミナルで動くものなら、サーバーが管理できます。状態検出のヒューリスティクスは Claude Code 固有ですが、基本的な send-keys/capture-pane ループは汎用的です。
コードは安全ですか?
コードは Mac から外に出ることはありません。Tactic Remote は暗号化された WebSocket 接続を通じてターミナルテキストを伝送します。サーバーにはクラウドコンポーネント、テレメトリ、アナリティクスは一切ありません。Cloudflare Tunnel を使用する場合、トラフィックはエンドツーエンドで暗号化されます — Cloudflare がプロキシするのは暗号化されたフレームであり、ソースコードではありません。
特定のサブシステムをさらに深く掘り下げたい場合は、セッション基盤に tmux を選んだ理由、リアルタイムターミナルストリーミングの構築、セキュリティモデルのドキュメントをご覧ください。
Tactic Remote を試す
スマートフォンからコーディングエージェントを操作
Mac、Windows、Linux 上の Claude Code や Codex などに接続。iPhone や iPad で進捗を確認し、次の指示を送れます。