Skip to content

Tomsabay/cclaw

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CClaw

Remote control Claude Code sessions on your Mac from your iPhone.

CClaw connects to the Claude Code Agent SDK running on your Mac, letting you start conversations, view streaming responses, manage tool permissions, and handle multiple sessions — all from your phone.

Architecture

+──────────────+          +──────────────────+          +──────────────────+          +────────────+
│  iOS App     │── WSS ──▷│  Relay Server    │◁── WSS ──│  Bridge Server   │─────────▷│ Claude Code│
│  (SwiftUI)   │          │  (Cloud VPS)     │          │  (Your Mac)      │  SDK API  │  Agent SDK │
+──────────────+          +──────────────────+          +──────────────────+          +────────────+
Component Tech Role
iOS App SwiftUI, iOS 18+ Chat UI, session management, QR/code pairing
Relay Server Node.js + TypeScript Cloud relay for NAT traversal, JWT auth, APNS push
Bridge Server Node.js + TypeScript Runs on your Mac, wraps Claude Code Agent SDK

The relay server is a transparent message forwarder — it does not inspect or modify message content (E2E encryption is planned).


Features

  • Multi-session management — run multiple Claude Code conversations simultaneously
  • Real-time streaming — see Claude's output as it types, including extended thinking
  • Tool use visualization — file edits, bash commands, and other tool calls with colored indicators
  • Permission handling — approve or deny tool use requests from your phone
  • Markdown rendering — full GFM with syntax highlighting (Swift, TypeScript, Python, Bash)
  • QR code pairing — one-time setup, then automatic reconnection via persistent bridgeId
  • Message buffering — up to 500 messages / 5MB queued when the app is offline
  • APNS push notifications — notified when Claude finishes while the app is backgrounded
  • File transfer — upload files from iPhone to Mac, download files from Mac to iPhone
  • MCP server management — add/remove/monitor Model Context Protocol servers
  • Skills system — manage Claude Code skills (~/.claude/skills/)
  • Usage tracking — view token usage, costs, and API rate limits
  • Local mode — connect directly over LAN without a relay server
  • Auto-start on login — launchd integration for the bridge server on macOS

Prerequisites

Component Requirements
Relay Server Any VPS with a public IP, Node.js 18+, a domain name (optional but recommended)
Bridge Server macOS with Claude Code installed and authenticated, Node.js 18+
iOS App Xcode 15+, iOS 18+, Apple Developer account (free or paid)

Setup Guide

Step 1: Deploy the Relay Server

The relay server runs on a cloud VPS (any provider: AWS, GCP, Alibaba Cloud, DigitalOcean, etc.).

1.1 Clone and build

git clone https://github.com/Tomsabay/cclaw.git
cd cclaw/relay-server
npm install
npx tsc

1.2 Generate TLS certificates

The relay server requires TLS. You can use a self-signed cert or a proper one from Let's Encrypt.

Self-signed (quickest):

cd certs
openssl req -x509 -newkey rsa:2048 \
  -keyout key.pem -out cert.pem \
  -days 365 -nodes \
  -subj "/CN=your-server-ip-or-domain"
cd ..

Let's Encrypt (recommended for production):

# Install certbot, then:
sudo certbot certonly --standalone -d relay.yourdomain.com
# Copy certs to relay-server/certs/
cp /etc/letsencrypt/live/relay.yourdomain.com/privkey.pem certs/key.pem
cp /etc/letsencrypt/live/relay.yourdomain.com/fullchain.pem certs/cert.pem

1.3 Configure environment

cp .env.example .env

Edit .env:

PORT=3200                              # Listen port
JWT_SECRET=your-random-secret-here     # REQUIRED — used to sign JWT tokens
DATABASE_PATH=./relay-usage.db         # SQLite database path
FREE_MESSAGE_LIMIT=500                 # Monthly message limit for free tier
# ADMIN_SECRET=your-admin-secret       # Optional — needed for admin API endpoints

JWT_SECRET is required. The server will refuse to start without it. Generate one with: openssl rand -hex 32

1.4 Open firewall port

# Example for ufw:
sudo ufw allow 3200/tcp

# Example for firewalld:
sudo firewall-cmd --permanent --add-port=3200/tcp && sudo firewall-cmd --reload

# Example for Alibaba Cloud / AWS:
# Open port 3200 (TCP) in the security group rules via the web console

1.5 Start the relay server

Quick start:

node dist/index.js

Production (with PM2):

npm install -g pm2
pm2 start dist/index.js --name relay-server
pm2 save
pm2 startup   # Auto-start on boot

Production (with systemd):

sudo tee /etc/systemd/system/cclaw-relay.service > /dev/null <<EOF
[Unit]
Description=CClaw Relay Server
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/root/cclaw/relay-server
ExecStart=/usr/bin/node dist/index.js
Restart=always
RestartSec=5
EnvironmentFile=/root/cclaw/relay-server/.env

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable cclaw-relay
sudo systemctl start cclaw-relay

1.6 Verify

# From your local machine:
curl -k https://your-server-ip:3200/health
# Expected: {"status":"ok","totalRooms":0,"paired":0,"waitingForPair":0}

1.7 Create a user account

curl -k -X POST https://your-server-ip:3200/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"your-password-min-8-chars"}'
# Returns: {"token":"eyJ...","email":"you@example.com","plan":"free"}

Step 2: Install the Bridge Server (Mac)

The bridge server runs on your Mac and connects Claude Code to the relay.

Option A: One-line installer

RELAY_URL="wss://your-server-ip:3200" bash <(curl -sSL https://your-server-ip:3200/install.sh)

This will:

  1. Install Node.js via Homebrew (if not present)
  2. Clone the repo to ~/.cclaw-bridge/
  3. Build the bridge server
  4. Prompt for your relay URL, email, and password
  5. Create a macOS launchd service (auto-start on login)
  6. Display a QR code and 6-character pair code

Option B: Manual setup

git clone https://github.com/Tomsabay/cclaw.git
cd cclaw/bridge-server
npm install
npx tsc

cp .env.example .env

Edit .env:

RELAY_URL=wss://your-server-ip:3200
BRIDGE_EMAIL=you@example.com
BRIDGE_PASSWORD=your-password

Start:

node dist/index.js

The bridge server will:

  1. Log in to the relay server
  2. Register and receive a pair code (6 characters, displayed in the terminal)
  3. Display a QR code for scanning
  4. Wait for the iOS app to pair

Bridge ID is saved to ~/.cclaw-bridge-id. On restart, the bridge reclaims the same room — no re-pairing needed.

Managing the bridge server

# View logs
tail -f ~/.cclaw-bridge/bridge.log

# Stop (launchd)
launchctl bootout gui/$(id -u)/com.cclaw.bridge-server

# Start (launchd)
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.cclaw.bridge-server.plist

# Uninstall completely
launchctl bootout gui/$(id -u)/com.cclaw.bridge-server 2>/dev/null
rm -rf ~/.cclaw-bridge ~/Library/LaunchAgents/com.cclaw.bridge-server.plist ~/.cclaw-bridge-id ~/.cclaw-bridge-token

Step 3: Build and Install the iOS App

3.1 Configure the Xcode project

  1. Open zsffirstapp.xcodeproj in Xcode
  2. Select the zsffirstapp target
  3. Under Signing & Capabilities:
    • Set Team to your Apple Developer account
    • The bundle identifier will auto-generate, or set it to something like com.yourname.cclaw
  4. Connect your iPhone and select it as the build destination
  5. Build and Run (Cmd+R)

3.2 Configure the app

  1. Open CClaw on your iPhone
  2. Go to the Settings tab
  3. Enter your relay server URL: wss://your-server-ip:3200
  4. Enter your email and password (same as registered in Step 1.7)
  5. Tap Connect
  6. Enter the pair code displayed by the bridge server (or scan the QR code)

3.3 Start chatting

  1. Go to the Sessions tab
  2. Tap + to create a new session
  3. Choose a working directory (defaults to ~)
  4. Start typing — your prompts go to Claude Code on your Mac

How Pairing Works

Bridge Server                    Relay Server                    iOS App
     │                                │                              │
     │── relay.register ──────────────▷│                              │
     │◁── relay.registered ───────────│  (pair code: "AB3K")         │
     │                                │                              │
     │                                │◁── relay.join ───────────────│  (user enters "AB3K")
     │◁── relay.paired ──────────────│                              │
     │                                │── relay.paired ──────────────▷│  (includes bridgeId)
     │                                │                              │
     │◁══ messages forwarded both ways ══════════════════════════════▷│
  1. Bridge sends relay.register with its persistent bridgeId
  2. Relay creates a room, generates a 6-character pair code, responds with relay.registered
  3. User enters the pair code in the iOS app, which sends relay.join
  4. Relay links both WebSocket connections and sends relay.paired to both sides
  5. The bridgeId is saved by the app — on subsequent connections, it reconnects automatically without a pair code
  6. If the pair code expires (5 minutes), the relay auto-generates a new one and notifies the bridge

Configuration Reference

Relay Server Environment Variables

Variable Required Default Description
JWT_SECRET Yes Secret for JWT token signing. Server exits if missing.
PORT No 3200 HTTPS/WSS listen port
DATABASE_PATH No ./relay-usage.db SQLite database file path
FREE_MESSAGE_LIMIT No 500 Monthly message limit per free user
ADMIN_SECRET No Secret for admin API endpoints (users/ban/plans)
APNS_KEY_PATH No Path to Apple Push Notification .p8 key file
APNS_KEY_ID No APNs Key ID from Apple Developer portal
APNS_TEAM_ID No Apple Developer Team ID for push notifications
APNS_BUNDLE_ID No com.cclaw.app App bundle ID for push notifications

Bridge Server Environment Variables

Variable Required Default Description
RELAY_URL Yes* WebSocket URL of your relay server (e.g. wss://relay.example.com:3200)
BRIDGE_EMAIL Yes* Email for relay server authentication
BRIDGE_PASSWORD Yes* Password for relay server authentication
PORT No 3100 Local WebSocket listen port (used in local mode)

*Required for relay mode. If RELAY_URL is not set, the bridge runs in local mode (LAN-only, no relay needed).


Project Structure

cclaw/
├── zsffirstapp/                     # iOS App (SwiftUI)
│   ├── zsffirstappApp.swift         # App entry point, auto-connect logic
│   ├── Services/
│   │   ├── WebSocketService.swift   # WebSocket connection (relay + local mode)
│   │   ├── SessionManager.swift     # Session & message management
│   │   ├── SettingsStore.swift      # UserDefaults persistence
│   │   ├── AuthService.swift        # Relay authentication
│   │   ├── KeychainHelper.swift     # iOS Keychain for credentials
│   │   └── PersistenceManager.swift # Local data persistence
│   ├── Views/
│   │   ├── Chat/
│   │   │   ├── ChatView.swift       # Main chat interface
│   │   │   ├── ChatBubbleView.swift # Message bubbles
│   │   │   ├── MarkdownView.swift   # Markdown + syntax highlighting
│   │   │   ├── ToolUseView.swift    # Tool call visualization
│   │   │   ├── MessageInputView.swift
│   │   │   └── PermissionBannerView.swift
│   │   ├── Sessions/
│   │   │   ├── SessionListView.swift
│   │   │   └── NewSessionView.swift
│   │   ├── Settings/
│   │   │   ├── SettingsView.swift   # Server URL, auth, pair code
│   │   │   ├── QRScannerView.swift  # QR code scanner for pairing
│   │   │   └── ConnectionStatusView.swift
│   │   ├── MCP/                     # MCP server management
│   │   ├── Skills/                  # Skills browser
│   │   ├── Usage/                   # Usage & cost tracking
│   │   └── MainTabView.swift        # Tab navigation
│   └── Models/
│       ├── Session.swift
│       ├── ChatMessage.swift
│       └── BridgeProtocol.swift     # Message type definitions
│
├── bridge-server/                   # Mac Bridge Server
│   ├── src/
│   │   ├── index.ts                 # Entry point, WS server, message routing
│   │   ├── relay-client.ts          # Relay connection, reconnection, buffering
│   │   ├── claude-session.ts        # Claude Code Agent SDK wrapper
│   │   ├── session-manager.ts       # Session lifecycle management
│   │   ├── protocol.ts             # Message type definitions
│   │   ├── auth.ts                 # Local auth token management
│   │   ├── db.ts                   # SQLite session persistence
│   │   ├── mcp-manager.ts          # MCP server configuration
│   │   ├── skills.ts               # Skills management
│   │   └── file-handler.ts         # File upload/download
│   ├── install.sh                  # One-line installer script
│   ├── .env.example
│   ├── package.json
│   └── tsconfig.json
│
├── relay-server/                    # Cloud Relay Server
│   ├── src/
│   │   ├── index.ts                 # HTTPS + WSS server, HTTP routes
│   │   ├── relay.ts                 # Room pairing, message forwarding, buffering
│   │   ├── auth.ts                  # JWT token creation/verification (HS256)
│   │   ├── userService.ts           # User registration, bcrypt passwords, plans
│   │   ├── apns.ts                  # Apple Push Notification Service
│   │   ├── pair-code.ts             # Cryptographic pair code generation
│   │   ├── usage.ts                 # Monthly message usage tracking
│   │   ├── admin.ts                 # Admin API handlers
│   │   └── db.ts                    # SQLite database init
│   ├── certs/                       # TLS certificates (gitignored)
│   │   └── README.md               # Certificate generation instructions
│   ├── .env.example
│   ├── package.json
│   └── tsconfig.json
│
├── Info.plist                       # iOS app transport security config
├── LICENSE                          # MIT
└── README.md

Relay Server API Reference

HTTP Endpoints

Method Path Auth Description
GET /health None Server health check
POST /auth/register None Register new user (rate limited: 5/min)
POST /auth/login None Login, get JWT (rate limited: 10/min)
GET /user/info?token=<JWT> JWT Get user info (email, plan, usage)
POST /admin/set-plan Admin Set user plan type
POST /admin/ban Admin Ban/unban user
GET /admin/users Admin List all users

WebSocket Endpoints

Path Used by Description
/bridge?token=<JWT> Bridge Server Mac bridge connection
/app?token=<JWT> iOS App iPhone app connection

Local Mode (No Relay)

If you don't want to set up a relay server, the bridge can run in local mode:

  1. Don't set RELAY_URL in .env
  2. Start the bridge server: node dist/index.js
  3. It will display a QR code with local connection info
  4. Your iPhone must be on the same Wi-Fi network as your Mac
  5. In the iOS app, scan the QR code or enter the local address manually

Limitations of local mode:

  • No access when you're away from home
  • No push notifications
  • No message buffering when the app is offline

Security

Layer Mechanism
Transport All WebSocket connections use TLS (WSS)
Authentication JWT tokens (HS256, 30-day expiry) for all connections
Passwords bcrypt hashed (12 salt rounds) on the relay server
Credentials Stored in iOS Keychain on device
Local auth Cryptographically random 256-bit bearer token for local connections
Rate limiting IP-based sliding window on auth endpoints
Message size 20MB max WebSocket message, 4KB max HTTP body

Current limitations:

  • The relay server can read all message content in transit (it forwards raw JSON)
  • E2E encryption is planned — ECDH key exchange + AES-GCM between app and bridge
  • Self-signed TLS certificates require the iOS app to trust all certificates (acceptable for self-hosted relays)

Troubleshooting

Bridge server shows "FATAL: JWT_SECRET not set"

Set the JWT_SECRET environment variable on the relay server. This is required.

Bridge server can't connect to relay

  • Check that the relay server is running: curl -k https://your-server-ip:3200/health
  • Check firewall: port 3200 must be open
  • Check .env credentials are correct
  • Check RELAY_URL format: must be wss:// (not https://)

iOS app can't connect

  • Verify the relay URL in Settings matches your server (including port)
  • Ensure you registered an account on the relay and entered correct credentials
  • If using self-signed certs, the app handles this automatically

Pair code expired

Pair codes expire after 5 minutes. The bridge auto-generates a new one — check the bridge server logs for the latest code.

"Room already has connected app"

Another device/instance is already paired. The new connection will take over automatically.

Messages not arriving

  • Check bridge server logs: tail -f ~/.cclaw-bridge/bridge.log
  • Check relay health: curl -k https://your-server-ip:3200/health
  • Ensure both bridge and app show "paired" status

Contributing

Contributions are welcome! Please open an issue or pull request.

Areas where help is needed:

  • E2E encryption — ECDH + AES-GCM between iOS app and bridge server
  • Android client — native Android app or React Native
  • Web client — browser-based interface
  • Better TLS — Let's Encrypt integration / ACME in the relay server
  • Tests — unit and integration tests for all components

License

MIT

About

Remote control Claude Code、codex、openclaw sessions on your Mac from your iPhone

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors