Skip to content
CADCAMFUN
Docs

Documentation

Pair the iOS companion

Connect a native iOS build to a running desktop, verify the certificate, and control device access.

Check the two ends of the connection

The companion is a client of a running desktop sidecar. It does not host the project database, geometry kernel, model provider, or workflow runner. Before pairing, confirm that the desktop opens its projects and that the phone can route to the desktop through Wi-Fi, Personal Hotspot, or an already configured overlay/VPN network.

Use a native iOS build containing modules/companion-net. Expo Go lacks this module and cannot make the pinned connection. The package also has a browser development fallback, but browser fetch does not implement the native pinning policy. Android configuration in the manifest does not supply an Android equivalent of the Swift module.

Native development entry points, from the repository root
bun install
bun run --cwd apps/mobile typecheck
bun run --cwd apps/mobile test
bun run --cwd apps/mobile ios

The iOS run command requires macOS, Xcode, and CocoaPods. A physical-device build requires a trusted device and signing team. bun run --cwd apps/mobile build exports JavaScript; it does not create an installable IPA. See the mobile reference for layout and transport implementation.

Enable the desktop gateway

Open desktop Settings → Devices and its Mobile companion panel, or use /settings/companion. Turn the gateway on, then create a named pairing and show its QR code. This is a separate listener from the ordinary loopback API: HTTPS on IPv4 0.0.0.0, port 8443 by default.

The service accepts integer ports 1024–65535. If another application occupies the selected port, enabling fails; choose a free port and create a link with the resulting address. Allow the connection through the desktop firewall for the network you intend to use. The application does not create a public relay, port forwarding, or a VPN.

Enabled state and port are persisted. After a sidecar restart, the gateway attempts to resume once the local API is ready. Certificate and private key files live under the sidecar data directory's companion/ subdirectory; the private key is written with owner-only permissions. The native app trusts the certificate from pairing, not a generic promise that every local certificate is safe.

Inspect and enable through the desktop API

For a reproducible development setup, use the launch token of an isolated sidecar you control. Replace port 43111 with its ready-event port. These are desktop-management calls; the phone token is not allowed to administer its own gateway.

Status and enable requests
CADCAM_URL=http://127.0.0.1:43111
CADCAM_TOKEN=local-development-token
CADCAM_PAIR_DIR=$(mktemp -d /tmp/cadcamfun-pair-guide.XXXXXX)

curl --fail-with-body "$CADCAM_URL/api/companion"   -H "Authorization: Bearer $CADCAM_TOKEN"

curl --fail-with-body -X POST "$CADCAM_URL/api/companion/enable"   -H "Authorization: Bearer $CADCAM_TOKEN"   -H 'Content-Type: application/json'   -d '{"port":18443}'

Status contains enabled state, nullable port/fingerprint, suggested host, and device entries. The fingerprint can be null before a certificate exists. A public health probe establishes loopback process health; it does not prove that the HTTPS gateway is enabled, reachable, or accepting a device token. Use the Companion API for exact management schemas.

Create one named pairing

Save the credential-bearing result in a private temporary directory
curl --fail-with-body -X POST "$CADCAM_URL/api/companion/pair"   -H "Authorization: Bearer $CADCAM_TOKEN"   -H 'Content-Type: application/json'   -d '{"name":"Workshop iPhone","host":"192.168.1.20"}'   -o "$CADCAM_PAIR_DIR/pairing.json"

CADCAM_DEVICE_ID=$(bun --no-env-file -e   'console.write((await Bun.file(process.argv[1]).json()).id)'   "$CADCAM_PAIR_DIR/pairing.json")

Replace the example host with an address the phone can reach, or omit the optional host to use discovery. The name is trimmed and must contain 1–60 characters. Success is HTTP 201 with id and link. Creating a token does not enable the listener; enable it separately.

The link carries the device token, certificate fingerprint, primary HTTPS origin, name, and optional alternate hosts. Display or transfer it privately to the phone; it is a credential. The desktop stores the token hash rather than its plaintext. The record has no expiry field in this implementation, so revoke an obsolete device explicitly rather than assuming it will expire.

Load the pairing on iOS

  1. Open the installed CADCAMFUN app's Connect screen.
  2. Choose Scan the pairing QR code and grant camera access, or use Paste a pairing link.
  3. For manual entry, provide the desktop address, token, and complete certificate fingerprint through the connection form.
  4. Allow local-network access when iOS prompts. Camera access and network access are separate permissions.
  5. Wait for the health/bootstrap probe to succeed and the app to open Projects.

The parser requires an HTTPS origin without user information, path content, query, or fragment. Tokens must be 16–4096 characters without whitespace/control characters; fingerprints are exactly 64 hexadecimal characters. Invalid links are refused before requests start.

Discovered alternate origins are deduplicated. The client races candidates, uses the first successful connection, aborts the others, and saves the successful origin. Discovery advertises up to six suitable private IPv4 addresses, including Wi-Fi, hotspot, and Tailscale-style IPv4 ranges; the current IPv4 listener does not advertise discovered IPv6 hosts.

What certificate pinning checks

The Swift transport uses ephemeral URLSession instances and a custom trust delegate. It obtains the leaf certificate's DER bytes, calculates SHA-256, and accepts the connection only when that hash equals the paired fingerprint. HTTPS is required and redirects are refused. Native callers do not fall back silently to unpinned fetch if the module is missing.

This supports the desktop's self-signed certificate without accepting arbitrary self-signed servers. The trust decision comes from the pairing QR/link. Replacing the desktop certificate changes that decision; create a fresh trusted pairing instead of suppressing the mismatch.

Ordinary browser/curl trust errors are not equivalent to testing the app's native delegate. A browser may reject a correctly pinned self-signed server because it uses a different trust store. Verify through the installed app and inspect the actual failure rather than disabling certificate validation globally.

After successful device authentication, the gateway records a last-seen timestamp for its current service state. Refresh desktop device status to confirm authenticated traffic. This indicates token use; it is not proof that every project action succeeded or a durable security audit trail.

Understand the device token scope

Allowed through the gatewayKept on the desktop API
Project list/detail; project eventsDirect filesystem, file uploads, and credential management
VCS history/status and currently commit/branch/checkout/restoreGitHub remote configuration, push, and pull endpoints
Pull-request review/merge/close and workflow operationsPlugin and billing administration
Notifications and assistant status/models/agentsCompanion enable/pair/revoke administration
Streaming chat, queue/abort, permission replies, session state/controlOther methods/paths absent from the allow-list

The device token is limited by the method/path allow-list. It is not inherently read-only and does not select one project. Accepted requests are forwarded under the desktop's application identity and remain subject to endpoint authorization. Never describe pairing as merely granting passive monitoring.

Assistant permissions and pending questions are separate session interactions. Answer them in the mobile UI when requested. A token that can reach the reply endpoint does not mean every tool action has already been approved.

Use sessions and recover connectivity

Choose a project to inspect its overview, history, pulls, and workflow runs. Assistant sessions execute on the desktop with its provider settings. Configure the provider there first. The phone remembers a selected project and last session per project for the paired desktop; changing desktop identity clears that mapping.

Foreground checks probe with a 15-second budget. Failed checks back off up to 30 seconds, and bringing the app forward triggers another probe. Background state is tracked separately from offline state. Tap the desktop row in phone Settings to recheck manually.

Queued assistant messages are persisted with a maximum of 50 entries, dropping the oldest beyond that limit. They retry later and may retain their session identifier. This is bounded message delivery support, not offline CAD computation or exactly-once execution. After an interrupted write/turn, inspect the desktop session and project before repeating the same change.

Revoke or unpair deliberately

Revoke this development device, then inspect status
curl --fail-with-body -X DELETE   "$CADCAM_URL/api/companion/devices/$CADCAM_DEVICE_ID"   -H "Authorization: Bearer $CADCAM_TOKEN"

curl --fail-with-body "$CADCAM_URL/api/companion"   -H "Authorization: Bearer $CADCAM_TOKEN"

Tokens are checked on every new request, so subsequent requests using the revoked token fail. Revocation does not explicitly terminate an already forwarded stream. Disabling the gateway stops the listener; POST /api/companion/disable is the administrative endpoint.

Phone Settings → Unpair this phone deletes the local saved pairing and returns to Connect. It does not call the desktop revoke endpoint. Use both actions when retiring a device. To rotate only the device credential, revoke it and issue a new pairing; replacing the whole certificate also changes every phone's pin.

Diagnose failures by layer

ResultNext check
No native pinning moduleOpen a custom native iOS build instead of Expo Go or an unsupported native platform.
Desktop did not answerGateway enabled, host/port correct, desktop awake, firewall, local-network permission, and Wi-Fi/VPN route.
Certificate mismatchVerify the current desktop identity and create a new trusted pairing after certificate replacement.
401Missing, incorrect, or revoked device token.
403Method/path outside the route allow-list; an endpoint's own authorization can also refuse work.
429Repeated failed authentication from the same address; the gateway tracks ten failures within a 60-second window.
502The HTTPS listener could not reach its local upstream API.

Native CI builds an unsigned simulator app and checks real gateway pairing/project listing. Separate CI checks typecheck, logic tests, and JavaScript export. These do not prove physical-device signing, TestFlight distribution, remote-network routing, or a real model provider.

Repeated bad tokens are throttled: ten failed attempts from one IP address within 60 seconds make further requests from it return HTTP 429 until the window expires. A 429 after re-pairing usually means an old device token is still being retried.

Implementation map

The management contract is packages/api/src/groups/companion.ts. Sidecar lifecycle and route policy are in services/companion.ts and companion-gateway.ts. Phone parsing, connection recovery, storage, and UI are in lib/pairing.ts, server.tsx, storage.ts, and app/connect.tsx. The native trust delegate is modules/companion-net/ios/CompanionNetModule.swift.

For a support report, retain the app version, connection method, non-secret gateway host/port, exact error/status, and whether the native module is present. Redact tokens, pairing links, model credentials, and private keys. Add the System API version result when comparing two development processes.

What a paired phone can call

Every request through the gateway must carry a device token and match this allow-list. Everything else, including credentials, plugins, files, billing, and /api/companion itself, is refused. Admitted requests are forwarded to the loopback API with the application token, which the phone never sees.

MethodsPath patternPurpose
GET/api/healthReachability check.
GET/api/projects, /api/projects/:idProject list and header.
GET, POST/api/projects/:id/vcs/* (except remote, push, pull), /pulls/*, /actions/*History, pull requests, reviews, and workflow runs. GitHub sync stays on the desktop because it needs the owner's GitHub token.
GET/api/projects/:id/eventsLive SSE stream of history, workflow, and assistant activity; the phone's only push channel.
GET, POST, PUT, DELETE/api/notifications, /mark-all-read, /:idRead, mark read, and delete notifications.
GET/api/ai/status, /models, /agentsAssistant discovery.
POST/api/ai/chat/stream, chat/abort, chat/queue, permission/reply, sessions/state, sessions/controlAssistant sessions.

Current scope of history writes

The history pattern currently also matches commit, checkout, and restore. Treat a paired phone as able to change project history, and revoke devices you no longer control.

Gateway administration API

RouteBodyResult
GET /api/companion—enabled, port, fingerprint, host, and devices (id, name, createdAt, lastSeen).
POST /api/companion/enableoptional port (default 8443, or 1024–65535)Starts the HTTPS listener on 0.0.0.0 and persists the setting so it resumes at next launch.
POST /api/companion/disable—Stops the listener.
POST /api/companion/pairname, optional host{ id, link }.
DELETE /api/companion/devices/:id—Revokes one device token immediately.

Failed bearer attempts are throttled per IP address: after 10 failures within 60 seconds, the gateway answers HTTP 429 ("Too many failed attempts") until the window passes. A wrong or revoked token otherwise gets HTTP 401.

Gateway status, captured

GET /api/companion on a fresh installation
{ "enabled": false, "port": null, "fingerprint": null, "host": "172.30.0.2", "devices": [] }
FieldMeaning
enabledWhether the HTTPS gateway is running; off by default
portGateway port while enabled, otherwise null
fingerprintSHA-256 fingerprint of the gateway certificate the phone pins; null until a certificate exists
hostThe LAN address the pairing link will advertise
devicesPaired devices; each can be revoked on its own

If host is not the address your phone can reach (for example a container or VPN interface), pairing will fail at the network layer even though everything else is correct.

In 3D

Companion pairing path Drag to orbit · scroll to zoom · right-drag to pan
Processes at run time. The Tauri shell spawns the sidecar and waits for its {”event”:”ready”,”port”:N} line on stdout (sidecar.rs); the webview then talks HTTP to 127.0.0.1 on that port with the bearer token. The sidecar owns the SQLite database, the embedded nikcli AI host (CADCAMFUN_AI_HOST=nikcli, the default) and plugins; the mobile app reaches it through companion pairing.