Claude
Skills
Sign in
Back

claude-watch-apple-watch

Included with Lifetime
$97 forever

```markdown

Writing & Docs

What this skill does

```markdown
---
name: claude-watch-apple-watch
description: Control Claude Code AI coding sessions from your Apple Watch — stream terminal output, approve permissions, and send voice commands from your wrist
triggers:
  - "set up claude watch on my apple watch"
  - "control claude code from my watch"
  - "stream claude terminal output to watch"
  - "approve claude permissions from apple watch"
  - "connect claude code to apple watch"
  - "set up agent watch bridge server"
  - "install claude code hooks for watch"
  - "voice commands to claude from wrist"
---

# Claude Watch (Agent Watch)

> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.

Control Claude Code from your Apple Watch. Stream live terminal output, approve/deny permission prompts, answer dynamic questions, and send voice commands — all from your wrist.

## Architecture

```
Apple Watch  <==WCSession==>  iPhone  <==HTTP/SSE==>  Bridge Server (Mac)
 (SwiftUI)                   (Relay)                   (Node.js)
                                                            |
                                                  HTTP Hooks | PTY stdin
                                                            v
                                                   Claude Code Session
```

Three components:
- **Bridge Server** — Node.js on Mac, receives Claude Code hooks, streams via SSE, blocks on permission requests
- **iPhone App** — SwiftUI, discovers bridge via Bonjour, relays events to watch via WCSession
- **watchOS App** — SwiftUI, connects directly to bridge over Wi-Fi, renders terminal + permissions

## Installation

### Prerequisites
- macOS 13+, Node.js 18+, Xcode 16+
- Apple Watch (watchOS 10+) on **same Wi-Fi** as Mac
- Claude Code CLI 2.1+
- Apple Watch: **Settings > Wi-Fi > your network > Private Wi-Fi Address → Off**

### Step 1: Install Bridge

```bash
git clone https://github.com/shobhit99/claude-watch
cd claude-watch/skill/bridge
npm install
```

### Step 2: Install Claude Code Hooks

```bash
./skill/setup-hooks.sh
```

This writes to `~/.claude/settings.json`:

```json
{
  "hooks": {
    "PostToolUse": [
      { "type": "http", "url": "http://127.0.0.1:7860/hook/post-tool-use" }
    ],
    "PreToolUse": [
      { "type": "http", "url": "http://127.0.0.1:7860/hook/pre-tool-use" }
    ],
    "PermissionRequest": [
      { "type": "http", "url": "http://127.0.0.1:7860/hook/permission-request", "timeout": 600 }
    ],
    "Stop": [
      { "type": "http", "url": "http://127.0.0.1:7860/hook/stop" }
    ]
  }
}
```

To remove hooks later:
```bash
./skill/setup-hooks.sh --remove
```

### Step 3: Start Bridge Server

```bash
cd skill/bridge
node server.js
```

Output:
```
╔═══════════════════════════════════════╗
║        AGENT WATCH BRIDGE             ║
╠═══════════════════════════════════════╣
║  Pairing Code:  648505                ║
║  IP Address:    192.168.1.4           ║
║  Port:          7860                  ║
╚═══════════════════════════════════════╝
```

### Step 4: Build iOS + watchOS Apps

```bash
cd ios/ClaudeWatch
xcodegen generate
open ClaudeWatch.xcodeproj
```

In Xcode:
1. Set **Development Team** on both targets: `ClaudeWatch` and `ClaudeWatchWatch`
2. Run `ClaudeWatch` scheme → iPhone
3. Run `ClaudeWatchWatch` scheme → Apple Watch

### Step 5: Pair

- **iPhone**: Enter 6-digit code from bridge banner
- **Watch**: Auto-discovers via Bonjour; fallback: enter IP manually

## Key Commands

| Command | Description |
|---------|-------------|
| `node server.js` | Start bridge (port 7860, auto-increments to 7869) |
| `./skill/setup-hooks.sh` | Install Claude Code hooks globally |
| `./skill/setup-hooks.sh --remove` | Remove hooks |
| `xcodegen generate` | Regenerate Xcode project from `project.yml` |
| `curl http://127.0.0.1:7860/status` | Check bridge health |

## Bridge Server API

### HTTP Endpoints

```
GET  /status                  — Health check + connection counts
GET  /events                  — SSE stream (requires session token)
POST /hook/post-tool-use      — Claude tool use events (async)
POST /hook/pre-tool-use       — Claude pre-tool events (async)
POST /hook/permission-request — Permission prompts (BLOCKING, 10min timeout)
POST /hook/stop               — Claude session ended (async)
POST /approve                 — Submit permission decision from watch/phone
POST /pair                    — Pair with 6-digit code, returns session token
```

### SSE Event Types

```javascript
// Terminal output event
{
  "type": "tool-use",
  "tool": "Edit",           // Read | Edit | Bash | Grep | Write
  "path": "src/index.ts",
  "summary": "Editing src/index.ts"
}

// Permission request (watch shows approval sheet)
{
  "type": "permission-request",
  "id": "req_abc123",
  "question": "Do you want to edit this file?",
  "options": ["Yes", "Yes to all", "No"]
}

// AskUserQuestion (dynamic options)
{
  "type": "permission-request",
  "id": "req_xyz456",
  "question": "Which approach should I take?",
  "options": [
    { "label": "Refactor", "description": "Clean up existing code" },
    { "label": "Rewrite", "description": "Start fresh" }
  ]
}

// Session ended
{ "type": "stop", "exitCode": 0 }
```

### Pairing Flow

```swift
// iPhone BridgeClient.swift pattern
func pair(code: String) async throws -> String {
    let response = try await post("/pair", body: ["code": code])
    return response["sessionToken"] as! String
}

// All subsequent requests include session token
func streamEvents(token: String) -> AsyncStream<SSEEvent> {
    let url = URL(string: "\(bridgeURL)/events?token=\(token)")!
    return SSEClient(url: url).stream()
}
```

## Swift Code Examples

### Watch: Connecting to Bridge (Direct Wi-Fi)

```swift
// WatchBridgeClient.swift
import Foundation

class WatchBridgeClient: ObservableObject {
    private var sseTask: URLSessionDataTask?
    private let session = URLSession.shared
    
    func connect(to bridgeURL: URL, token: String) {
        let eventsURL = bridgeURL
            .appendingPathComponent("events")
            .appending(queryItems: [URLQueryItem(name: "token", value: token)])
        
        var request = URLRequest(url: eventsURL)
        request.setValue("text/event-stream", forHTTPHeaderField: "Accept")
        
        sseTask = session.dataTask(with: request) { [weak self] data, _, _ in
            guard let data, let text = String(data: data, encoding: .utf8) else { return }
            self?.parseSSEEvents(text)
        }
        sseTask?.resume()
    }
    
    func submitApproval(bridgeURL: URL, token: String, requestID: String, decision: String) async throws {
        var request = URLRequest(url: bridgeURL.appendingPathComponent("approve"))
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONSerialization.data(withJSONObject: [
            "id": requestID,
            "decision": decision,
            "token": token
        ])
        let (_, _) = try await URLSession.shared.data(for: request)
    }
}
```

### Watch: Permission Approval View

```swift
// ApprovalView.swift
import SwiftUI
import WatchKit

struct ApprovalView: View {
    let request: ApprovalRequest
    let onDecision: (String) -> Void
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 8) {
                Text(request.question)
                    .font(.headline)
                    .padding(.bottom, 4)
                
                ForEach(request.options, id: \.self) { option in
                    Button(action: {
                        WKInterfaceDevice.current().play(.click)
                        onDecision(option)
                    }) {
                        Text(option)
                            .frame(maxWidth: .infinity)
                            .padding(.vertical, 6)
                    }
                    .buttonStyle(.bordered)
                    .tint(option.lowercased().

Related in Writing & Docs