はじめに

初めまして。 『DApps開発入門』という本や色々記事を書いているかるでねです。

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!

今回は「iPhoneからPC上のClaude Codeをいつでもどこでも直接操作できる環境」についてまとめていきます。

自宅のPC(Mac mini、MacBookなど)上でClaude Code UI(Cloud CLI) を常時稼働させ、Tailscaleで閉域ネットワークを構築し、iPhoneのブラウザからWeb UIにアクセスします。 さらにClaude Codeのhooks機能でntfyにPush通知を飛ばし、タスクの完了や権限リクエストをリアルタイムに把握できる構成です。

この記事で実現すること

この記事を読み終えると、以下の環境が手に入ります。

  • iPhoneのブラウザからClaude Code UIを操作できる
  • Tailscaleの閉域ネットワーク内でのみアクセスでき、インターネットには公開されない
  • pm2によるプロセス常駐化で、UIが落ちても自動復帰する
  • Claude Codeのタスク完了時や権限リクエスト時にiPhoneへPush通知が届く

全体構成図

まず、この構成の全体像をmermaid図で示します。

flowchart TD
    subgraph iPhone
        A[ブラウザ] -->|HTTPS| B[Tailscale VPN]
        D[ntfyアプリ]
    end

    subgraph ホストPC
        E[Tailscale] --> S[tailscale serve<br/>HTTPS → localhost]
        S --> F[Claude Code UI<br/>cloudcli]
        F --> G[Claude Code CLI]
        G --> H[transcript保存]
        G --> I[Stopイベント発火]
        I --> J[hooks → 通知スクリプト]
        J -->|POST| K[ntfyサーバー]
        L[pm2] -->|プロセス管理| F
    end

    B --> E
    K --> D

iPhone側では、ブラウザでWeb UIにアクセスし、ntfyアプリで通知を受け取ります。 ホストPC側では、Tailscaleが閉域ネットワークの入り口となり、Claude Code UIがCLIのオーケストレーターとして動作し、pm2がプロセスの生存を保証します。 Claude Codeのhooksがイベント発生をntfyへ通知する役割を担います。

構成要素の役割

各ツールの責務を以下の表にまとめます。

iPhone側

ツール 役割
Tailscaleアプリ VPNトンネルを張り、Tailnetに参加してホストPCへ到達可能にする
ブラウザ Claude Code UIのWeb画面にアクセスする
ntfyアプリ ntfyのTopicを購読し、Claude Codeの通知を受け取る

ホストPC側

ツール 役割
Tailscale + tailscale serve ホストPCをTailnetに参加させ、tailscale serve でClaude Code UIをHTTPS公開する
Claude Code UI(Cloud CLI) Web UIサーバーとして動作し、ブラウザからClaude Code CLIを操作する
Claude Code CLI 実際にAI処理を実行し、セッション履歴を ~/.claude/ 配下に保存する
pm2 Claude Code UIを常駐化し、クラッシュ時の自動再起動とOS起動後の自動復帰を行う
Claude hooks + ntfy.sh 各種イベント(Stop、PermissionRequest、Notificationなど)でスクリプトを実行し、ntfyへ通知する

ネットワーク設計

Tailscaleによる閉域アクセス

この構成のセキュリティの要はTailscaleです。 TailscaleはWireGuardベースのメッシュVPNサービスで、個人利用は無料です。

Tailscaleを導入すると、各デバイスに 100.x.x.x 形式のTailnet内IPが割り当てられます。 以下は割り当ての例です。

デバイス Tailnet内IP(例)
ホストPC 100.x.x.x
iPhone 100.y.y.y

この 100.x.x.x はTailnet内でのみ有効な仮想IPです。 インターネットから直接到達できないため、ルーターのポート開放は一切不要です。 Tailnetに参加しているデバイス同士でしか通信できず、第三者がアクセスする手段がありません。

tailscale serve によるHTTPSアクセス

Claude Code UIはデフォルトで localhost:3001 で待ち受けますが(--port で変更可能)、そのままではiPhoneから到達できません。 ここでTailscale Serveを使います。

tailscale serve はローカルで動作しているサービスを、Tailnet内のMagicDNSホスト名でHTTPS公開するリバースプロキシ機能です。 以下のコマンドを実行すると、ローカルで動作しているClaude Code UIがTailnet内にHTTPSで公開されます。 今回はデフォルトポートの 3001 を使用していますが、--port で変更した場合はそのポート番号に読み替えてください。

bash
tailscale serve --bg http://localhost:3001

実行後、以下のようなURLが表示されます。

text
https://<ホスト名>.tail<Tailnet ID>.ts.net/

このURLがiPhoneのブラウザからアクセスする先です。 TailscaleがHTTPS証明書を自動発行するため、ドメイン取得や証明書の手動管理は不要です。 Tailscaleが両デバイスで起動していれば、自宅でも外出先でもこのURLで到達できます。

tailscale serve を停止する場合は以下を実行します。

bash
tailscale serve --https=443 off

このコマンドで tailscale serve のHTTPSプロキシが解除され、Tailnet内からのアクセスが停止します。

Claude Code UIとは

概要

Claude Code UI(Cloud CLI) は、Claude Code CLIにWeb UIを提供するOSSツールです。

Claude Code、Cursor CLI、Codexに対応しており、ブラウザからプロジェクトとセッションを管理できます。 ただのビューアーではなく、バックエンドでCLIプロセスをspawnしてWebSocket経由でストリーミング応答を返すオーケストレーターです。

主な機能

Claude Code UIが提供する機能を以下の表にまとめます。

機能 説明
レスポンシブデザイン デスクトップ、タブレット、スマホすべてに対応
チャットインターフェース シンタックスハイライト付きのコード表示と会話履歴
ファイルエクスプローラー プロジェクトのファイルツリーを閲覧・編集できる
Git統合 ステージング、コミット、ブランチ切り替えをUI上で行える
セッション管理 複数セッションの作成、resume、履歴管理
シェルターミナル UI内蔵のシェルからCLIを直接操作できる

内部的な動作

UIがどのようにCLIと連携しているかを整理します。

UIは ~/.claude/projects/ 配下のディレクトリを参照して、既存セッションをプロジェクト単位で一覧表示します。 ユーザーがUIから「新規セッション」や「resume」を選択すると、バックエンドがClaude Code CLIプロセスをspawnし、WebSocketで応答をブラウザへストリーミングします。

つまりUIサーバーはCLIの起動・管理・通信を一手に担うミドルウェアとして動作しています。

iPhoneから使うのに適している理由

「SSHでCLIを直接叩けばいいのでは?」と思うかもしれません。 しかしiPhoneのターミナル操作はデスクトップと比べて制約が多いです。

具体的には、スクロールバックが制限されるため会話履歴の確認が困難で、タブ補完やキーボードショートカットも使いにくく、セッションの切り替えにもコマンドを打つ必要があります。

Claude Code UIを使えば、セッション一覧をタップで切り替え、会話履歴をスクロールで確認でき、プロジェクトの選択もタッチ操作で完結します。 iPhoneでの操作コストが大幅に下がるため、UIを挟む価値は十分にあります。

環境構築手順

前提条件

以下のツールが必要です。

ツール バージョン インストール方法
Node.js v22以上 公式サイトまたはnvm
Claude Code CLI 最新版 npm install -g @anthropic-ai/claude-code
Tailscale 最新版 公式サイトからPC版・iOS版
ntfyアプリ 最新版 App Storeから取得
jq 最新版 brew install jq(ntfy.shスクリプトで使用)

リモートログイン(SSH)の有効化

ホストPC側でSSHを有効にしておくと、トラブル時の復旧やメンテナンス時にiPhoneから直接操作できます。 pm2の状態確認やログ確認など、日常的な運用でもSSH接続が必要になる場面があるため、最初に設定しておくことを推奨します。

macOSの場合

  1. 「システム設定」→「一般」→「共有」を開く
  2. 「リモートログイン」をONにする
  3. アクセスを許可するユーザーを指定する

接続確認

iPhone側にSSHクライアント(Termiusなど)をインストールし、Tailnet内IPで接続できることを確認します。

bash
# iPhoneのSSHクライアントから実行
ssh username@100.x.x.x

100.x.x.x はホストPCのTailnet内IPです。 Tailscaleアプリの管理画面で確認できます。

接続できれば、以降の手順で問題が発生した際にiPhoneから直接復旧できます。

Claude Code UIのインストール

グローバルインストールして cloudcli コマンドを使えるようにします。

bash
npm install -g @siteboon/claude-code-ui

このコマンドでClaude Code UIがグローバルにインストールされ、cloudcli と claude-code-ui の2つのコマンドが使えるようになります。

インストールせず試したい場合は、npx で直接実行することもできます。

bash
npx @siteboon/claude-code-ui

インストール後、以下のコマンドでサーバーが起動します。

bash
cloudcli

デフォルトで http://localhost:3001 で待ち受けます。 ポートを変更したい場合は --port オプションを指定します。

bash
cloudcli --port 8080

この時点ではまだlocalhost経由でしかアクセスできないため、続いてTailscaleの設定を行います。

Claude Code UIはインストール直後、すべてのClaude Codeツール(ファイル読み書き、コマンド実行など)が無効化されています。 UIのサイドバーにある歯車アイコンからTools Settingsを開き、必要なツールを手動で有効化してください。

Tailscaleのセットアップ

ホストPC側

以下はmacOSでの手順です。 WindowsやLinuxの場合もTailscale公式サイトから対応版をインストールし、同様にアカウントにログインしてください。

  1. Tailscale公式サイトからインストールします
  2. Tailscaleアプリを起動し、アカウントにログインします
  3. 「Connected」になっていることを確認します

iPhone側

  1. App StoreからTailscaleをインストールします
  2. アプリを開き、ホストPCと同じアカウントでログインします
  3. VPNの許可ダイアログが出るので許可します

tailscale serve の設定

Claude Code UIをpm2で起動した後、tailscale serve でHTTPS公開します。 ポート番号はClaude Code UIが待ち受けているポートに合わせてください(デフォルトは 3001)。

bash
tailscale serve --bg http://localhost:3001

実行すると以下のような出力が表示されます。

text
Available within your tailnet:

https://<ホスト名>.tail<Tailnet ID>.ts.net/
|-- proxy http://localhost:3001

この https://... のURLをiPhoneのブラウザで開き、Claude Code UIが表示されれば接続完了です。

初回実行時にTailscale側でServe機能を有効化するURLが表示される場合があります。 表示されたURLにアクセスして有効化してから再度コマンドを実行してください。

pm2による常駐化

Claude Code UIをpm2で常駐化し、プロセスが落ちても自動復帰する構成にします。

pm2のインストールと起動

bash
npm install -g pm2
pm2 start cloudcli --name "claude-code-ui"

1行目でpm2をグローバルインストールし、2行目でClaude Code UIをpm2の管理下に登録して起動しています。 --name で指定した名前は、以降の pm2 restart や pm2 logs で使います。

ポートを変更している場合は、-- の後にオプションを渡します。

bash
pm2 start cloudcli --name "claude-code-ui" -- --port 8080

-- の後に書いたオプションがそのまま cloudcli に渡されます。

OS起動後の自動復帰

pm2にはstartup機能があり、OS再起動後に自動でプロセスを復元できます。

bash
pm2 startup
pm2 save

pm2 startup を実行するとコマンドが表示されるので、その内容をコピーして実行します。 pm2 save で現在のプロセスリストを保存すると、次回のOS起動時にpm2が自動でClaude Code UIを立ち上げます。

日常的に使うpm2コマンド

コマンド 用途
pm2 status プロセスの状態を確認する
pm2 restart claude-code-ui Claude Code UIを再起動する
pm2 logs claude-code-ui ログをリアルタイムで確認する
pm2 stop claude-code-ui プロセスを停止する

Claude hooks + ntfyで通知を飛ばす

Claude Codeにタスクを投げて放置しておくと、完了したことに気づけません。 ここでは、Claude Codeのhooks機能とntfyを組み合わせて、タスク完了時にiPhoneへPush通知を送る仕組みを構築します。

ntfyとは

ntfy(読み方は "notify")は、HTTP PUTまたはPOSTでメッセージを送るだけでPush通知が届くシンプルな通知サービスです。

iPhoneにntfyアプリをインストールし、任意のTopic名を購読するだけで通知を受け取れます。 サーバー登録やAPI Key取得は不要で、Topic名を知っていれば誰でも送受信できる仕組みです。

ntfyアプリの設定

iPhoneのApp Storeからntfyアプリをインストールし、任意のTopic名を購読します。 Topic名は推測されにくいランダムな文字列を使ってください。 ntfyは無料プランだとパブリックサーバーを利用するため、Topic名がわかれば誰でも購読・投稿が可能です。 Topic名はUUIDのようなランダムな値にし、機密情報を通知に含めないよう注意してください。

Claude hooksの仕組み

Claude Codeにはhooks機能があり、特定のイベント発生時に外部コマンドを実行できます。 hooksはスクリプトの標準入力(stdin) にイベント情報をJSON形式で渡します。

通知を飛ばせるイベントは複数あり、タスク完了以外にも権限リクエストやツール実行時などに対応できます。 以下の表に主要なイベントをまとめます。

イベント 発火タイミング
Stop Claude Codeがタスクを完了して停止した時
PermissionRequest ツール実行の許可を求めている時
PreToolUse ツールを実行する直前
Notification 権限プロンプトやアイドル状態などの通知時

hooks設定ファイル

~/.claude/settings.json に以下のhooks設定を追加します。 この例では Stop、PermissionRequest、Notification の3つのイベントで通知スクリプトを実行しています。

~/.claude/settings.json
{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/ntfy.sh"
          }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/ntfy.sh"
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "permission_prompt, idle_prompt, auth_success, elicitation_dialog",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/ntfy.sh"
          }
        ]
      }
    ]
  }
}

設定の構造を説明します。 各イベント名の配列内に matcher と hooks を定義します。 matcher はイベントのフィルタ条件で、空文字列や * を指定するとすべてが対象になります。 Notification の matcher には permission_prompt や idle_prompt などカンマ区切りで通知種別を指定できます。

同じ構造で PreToolUse など他のイベントにもhooksを追加できます。 たとえば PreToolUse に設定すれば、ツールが実行される直前にも通知を受け取れます。 ただしPreToolUseはツール実行のたびに発火するため、通知量が多くなる点に注意してください。

hookからスクリプトへ渡されるデータ

hooksはイベントごとに異なるJSONをstdinに渡します。

イベント 主なJSONフィールド
Stop transcript_path(会話ログのフルパス)
PreToolUse tool_name, tool_input
Notification message

この違いを利用して、ntfy.shスクリプト内でイベントの種類を判定し、通知内容を出し分けることができます。

ntfy.shスクリプト

ホストPC上に通知スクリプトを作成します。 以下のスクリプトは、stdinのJSONに含まれるフィールドを見てイベントの種類を判定し、通知のタイトルと本文を切り替えます。

~/.claude/ntfy.sh
#!/bin/bash
set -euo pipefail

DEFAULT_TOPIC_NAME="your-random-topic-name"
TOPIC_NAME="${NTFY_TOPIC:-$DEFAULT_TOPIC_NAME}"

# stdinからhook情報取得
HOOK_DATA=$(cat)

# 各イベントのフィールドを取得
TOOL_NAME=$(echo "$HOOK_DATA" | jq -r '.tool_name // empty')
TRANSCRIPT=$(echo "$HOOK_DATA" | jq -r '.transcript_path // empty')
NOTIFICATION_MSG=$(echo "$HOOK_DATA" | jq -r '.message // empty')

if [ -n "$TOOL_NAME" ]; then
  # PreToolUse / PermissionRequest
  TITLE="🔧 Tool: ${TOOL_NAME}"
  BODY=$(echo "$HOOK_DATA" | jq -r '.tool_input // empty' | head -c 300)
  if [ -z "$BODY" ]; then
    BODY="Permission requested"
  fi

elif [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
  # Stop
  TITLE=$(jq -r '
    select(.type=="human")
    | .message.content
    | select(type=="string")
  ' "$TRANSCRIPT" | tail -1)
  TITLE="${TITLE:0:100}"
  if [ -z "$TITLE" ]; then
    TITLE="✅ Task completed"
  else
    TITLE="✅ ${TITLE}"
  fi

  BODY=$(jq -r '
    select(.type=="assistant")
    | .message.content[]
    | select(.type=="text")
    | .text
  ' "$TRANSCRIPT" | tail -1)
  BODY="${BODY:0:800}"

elif [ -n "$NOTIFICATION_MSG" ]; then
  # Notification
  TITLE="💬 Claude Code"
  BODY="$NOTIFICATION_MSG"

else
  exit 0
fi

if [ -n "${BODY:-}" ]; then
  printf "%s" "$BODY" | \
  curl -sS \
    -H "Title: ${TITLE}" \
    -H "Priority: default" \
    --data-binary @- \
    "https://ntfy.sh/${TOPIC_NAME}" \
    >/dev/null
fi

スクリプトに実行権限を付与します。

bash
chmod +x ~/.claude/ntfy.sh

このスクリプトの動作を説明します。 set -euo pipefail でエラー時に即座に停止する安全な設定を入れています。 stdinのJSON全体を変数に読み込み、tool_name、transcript_path、message の3つのフィールドを抽出します。 どのフィールドに値が入っているかでイベントの種類を判定し、通知のタイトルと本文を切り替えています。

イベント 通知タイトル 通知本文
PreToolUse / PermissionRequest 🔧 Tool: ツール名 ツールの入力パラメータ(300文字まで)
Stop ✅ 直近のユーザーメッセージ 最新のClaude応答(800文字まで)
Notification 💬 Claude Code 通知メッセージ

タイトルに絵文字プレフィックスを付けることで、iPhoneの通知一覧からどのイベントが発生したかを一目で区別できます。 送信には --data-binary を使い、日本語などのマルチバイト文字も安全に送信しています。

データフローの全体像

Stopイベントで通知が届くまでの流れをmermaid図で示します。

flowchart TD
    A[iPhoneのブラウザでUI操作] --> B[Tailscale VPN]
    B --> C[ホストPC]
    C --> D[Claude Code UI]
    D --> E[Claude Code CLIを起動]
    E --> F[Claudeが応答を生成]
    F --> G[transcriptへ保存]
    F --> H[Stopイベント発火]
    H --> I[hooksがntfy.sh起動]
    I --> J[stdinのJSONからtranscript_path取得]
    J --> K[transcriptから最新メッセージ抽出]
    K --> L[ntfyサーバーへPOST]
    L --> M[iPhone ntfyでPush通知]

ユーザーがブラウザからタスクを投げると、Tailscale経由でホストPCのClaude Code UIに到達します。 UIがCLIを起動してClaudeが応答を生成し、transcriptに保存されます。 CLIが停止するとStopイベントが発火し、hooksがntfy.shを起動します。 スクリプトがtranscriptから最新メッセージを抽出してntfyにPOSTし、iPhoneのntfyアプリにPush通知が届きます。

日常の使い方

セットアップが完了した後の日常的な使い方をまとめます。

  1. iPhoneでTailscaleアプリを開き、VPNがONになっていることを確認します
  2. ブラウザでClaude Code UIのURLにアクセスします
  3. Claude Code UIからプロジェクトを選択し、新規セッションを作成するか既存セッションをresumeします
  4. タスクを投げて放置し、ntfyの通知が届いたら確認します
  5. 必要に応じて追加のタスクを投げます

ホーム画面にショートカットを追加すると、PWAのようにアプリ感覚で使えます。 ブラウザでClaude Code UIを開いた状態で、共有ボタンから「ホーム画面に追加」を選択してください。

トラブルシューティング

ブラウザで開けない

接続できない場合、以下の順序で切り分けます。

  1. iPhoneのTailscaleアプリがONになっているか確認します
  2. 別のブラウザでも試してみます
  3. それでもダメなら、SSHでホストPCに接続して pm2 status を確認します

PCがスリープしている

ホストPCがスリープすると、TailscaleもClaude Code UIも停止します。 常時稼働させるなら、macOSの場合はシステム設定の「省エネルギー」からディスプレイがオフの時にスリープさせない設定を有効にしてください。

一時的な対策としては、SSH接続後に caffeinate コマンド(macOS)でスリープを防止できます。

bash
caffeinate -s &

-s オプションはAC電源接続時にシステムスリープを防止します。

セキュリティ

Tailscaleを使った閉域ネットワーク構成により、以下のセキュリティ特性が得られています。

  • インターネットからWeb UIへ直接到達できない
  • Tailnetに参加しているデバイスだけがアクセスできる
  • tailscale serve によりHTTPS通信が自動で確保されている
  • ルーターのポート開放が不要なため、設定ミスによる外部露出のリスクがない

Tailscaleアカウントには多要素認証(MFA)を設定し、iPhoneには強いパスコードと生体認証を入れておくことを推奨します。

SSH経由での復旧

Claude Code UIやpm2に問題が発生し、ブラウザからアクセスできなくなった場合は、SSHでホストPCに接続して復旧できます。 リモートログインの設定は「環境構築手順」の最初で実施済みのため、Termiusなどのモバイル向けSSHクライアントからすぐに接続できます。

復旧手順

SSHでホストPCに入れた場合、以下の手順で復旧します。

  1. pm2 status でプロセスの状態を確認します
  2. ステータスが online でなければ pm2 restart claude-code-ui で再起動します
  3. プロセス自体が存在しない場合は再登録します
bash
pm2 start cloudcli --name "claude-code-ui"
pm2 save

再登録しても起動しない場合は、ポートの競合を疑います。 使用しているポート番号(デフォルトは 3001)を指定して確認します。

bash
lsof -i :<ポート番号>

別のプロセスがそのポートを使用していれば、PIDを確認して停止してから再起動します。

最後に

今回は「iPhoneからPC上のClaude Codeをいつでもどこでも直接操作できる環境」についてまとめてきました。

この構成は、tailscale serve でHTTPS公開し、Claude Code UIをpm2で常駐化して可用性を担保し、Claude Codeのhooksでntfy通知を実装するものです。 モバイルからの快適な操作と運用安全性を両立でき、Mac mini、MacBook、その他のPCでも同じ構成で運用できます。

「自宅のPCを閉域ネットワーク内の個人AIサーバーとして運用する」ための現実的な構成として、ぜひ試してみてください。

他でも色々記事を書いているのでぜひよろしければ読んでいってください!

参考