chore(naming): remove Iris codename

This commit is contained in:
Peter Steinberger
2025-12-18 13:18:33 +01:00
parent 790079c3b6
commit d182f7e4b2
16 changed files with 59 additions and 60 deletions

View File

@@ -7,7 +7,7 @@
## 2.0.0-beta1 — 2025-12-14
First Clawdis release post rebrand. This is a semver-major because we dropped legacy providers/agents and moved defaults to new paths while adding a full macOS companion app, a WebSocket Gateway, and an iOS node (Iris).
First Clawdis release post rebrand. This is a semver-major because we dropped legacy providers/agents and moved defaults to new paths while adding a full macOS companion app, a WebSocket Gateway, and an iOS node.
### Breaking
- Renamed to **Clawdis**: defaults now live under `~/.clawdis` (sessions in `~/.clawdis/sessions/`, IPC at `~/.clawdis/clawdis.sock`, logs in `/tmp/clawdis`). Launchd labels and config filenames follow the new name; legacy stores are copied forward on first run.
@@ -19,7 +19,7 @@ First Clawdis release post rebrand. This is a semver-major because we dropped le
### Gateway, nodes, and automation
- New typed Gateway WS protocol (JSON schema validated) with `clawdis gateway {health,status,send,agent,call}` helpers and structured presence/instance updates for all clients.
- Optional LAN-facing bridge (`tcp://0.0.0.0:18790`) keeps the Gateway loopback-only while enabling direct Bonjour-discovered connections for paired nodes.
- Node pairing + management via `clawdis nodes {pending,approve,reject,invoke}` (used by the iOS node “Iris” and future remote nodes).
- Node pairing + management via `clawdis nodes {pending,approve,reject,invoke}` (used by the iOS node and future remote nodes).
- Cron jobs are Gateway-owned (`clawdis cron …`) with run history stored as JSONL and support for “isolated summary” posting into the main session.
### macOS companion app
@@ -29,10 +29,10 @@ First Clawdis release post rebrand. This is a semver-major because we dropped le
- **Browser control**: manage clawds dedicated Chrome/Chromium with tab listing/open/focus/close, screenshots, DOM query/dump, and “AI snapshots” (aria/domSnapshot/ai) via `clawdis browser …` and UI controls.
- **Remote gateway control**: Bonjour discovery for local masters plus SSH-tunnel fallback for remote control when multicast is unavailable.
### iOS node (Iris)
### iOS node
- New iOS companion app that pairs to the Gateway bridge, reports presence as a node, and exposes a WKWebView “Canvas” for agent-driven UI.
- `clawdis nodes invoke` supports `canvas.eval` and `canvas.snapshot` to drive and verify the iOS Canvas (fails fast when Iris is backgrounded).
- Voice wake words are configurable in-app; Iris reconnects to the last bridge when credentials are still present in Keychain.
- `clawdis nodes invoke` supports `canvas.eval` and `canvas.snapshot` to drive and verify the iOS Canvas (fails fast when the iOS node is backgrounded).
- Voice wake words are configurable in-app; the iOS node reconnects to the last bridge when credentials are still present in Keychain.
### WhatsApp & agent experience
- Group chats fully supported: mention-gated triggers (including media-only captions), sender attribution, session primer with subject/member roster, allowlist bypass when youre @mentioned, and safer handling of view-once/ephemeral media.

View File

@@ -30,7 +30,7 @@ WhatsApp / Telegram
├─ CLI (clawdis …)
├─ WebChat (loopback UI)
├─ macOS app (Clawdis.app)
└─ iOS node (Iris) via Bridge + pairing
└─ iOS node via Bridge + pairing
```
## Why "CLAWDIS"?
@@ -53,7 +53,7 @@ Because every space lobster needs a time-and-space machine. The Doctor has a TAR
- 🎤 **Voice & transcription hooks** — Voice Wake (macOS/iOS) + optional transcription pipeline
- 🔧 **Tool Streaming** — Real-time display (💻📄✍️📝)
- 🖥️ **macOS Companion (Clawdis.app)** — Menu bar controls, Voice Wake, WebChat, onboarding, remote gateway control
- 📱 **iOS Node (Iris)** — Pairs as a node, exposes a Canvas surface, forwards voice wake transcripts
- 📱 **iOS node** — Pairs as a node, exposes a Canvas surface, forwards voice wake transcripts
Only the Pi CLI is supported now; legacy Claude/Codex/Gemini paths have been removed.
@@ -69,7 +69,7 @@ Only the Pi CLI is supported now; legacy Claude/Codex/Gemini paths have been rem
- **TypeScript (ESM)**: CLI + Gateway live in `src/` and run on Node ≥ 22.
- **macOS app (Swift)**: menu bar companion lives in `apps/macos/`.
- **iOS app (Swift)**: Iris node prototype lives in `apps/ios/`.
- **iOS app (Swift)**: iOS node prototype lives in `apps/ios/`.
## Quick Start
@@ -118,9 +118,9 @@ If delivery fails (e.g. WhatsApp disconnected / Telegram token missing), Clawdis
Build/run the mac app with `./scripts/restart-mac.sh` (packages, installs, and launches), or `swift build --package-path apps/macos && open dist/Clawdis.app`.
### iOS Node (Iris) (internal)
### iOS node (internal)
Iris is an internal/prototype iOS app that connects as a **remote node**:
The iOS node app is an internal/prototype app that connects as a **remote node**:
- **Voice trigger:** forwards transcripts into the Gateway (agent runs + wakeups).
- **Canvas screen:** a WKWebView + `<canvas>` surface the agent can control (via `canvas.eval` / `canvas.snapshot` over `node.invoke`).
@@ -164,7 +164,7 @@ Optional: enable/configure clawds dedicated browser control (defaults are alr
- [Troubleshooting](./docs/troubleshooting.md)
- [The Lore](./docs/lore.md) 🦞
- [Telegram (Bot API)](./docs/telegram.md)
- [iOS node runbook (Iris)](./docs/ios/connect.md)
- [iOS node runbook](./docs/ios/connect.md)
- [macOS app spec](./docs/clawdis-mac.md)
## Clawd

View File

@@ -1,6 +1,6 @@
## Clawdis Node (Android) (internal)
Modern Android node app (Iris parity): connects to the **Gateway-owned bridge** (`_clawdis-bridge._tcp`) over TCP and exposes **Canvas + Chat + Camera**.
Modern Android node app: connects to the **Gateway-owned bridge** (`_clawdis-bridge._tcp`) over TCP and exposes **Canvas + Chat + Camera**.
Notes:
- The node keeps the connection alive via a **foreground service** (persistent notification with a Disconnect action).

View File

@@ -6,12 +6,12 @@ import Testing
@Suite struct BridgeEndpointIDTests {
@Test func stableIDForServiceDecodesAndNormalizesName() {
let endpoint = NWEndpoint.service(
name: "Clawdis\\032Bridge \\032 Iris\n",
name: "Clawdis\\032Bridge \\032 Node\n",
type: "_clawdis-bridge._tcp",
domain: "local.",
interface: nil)
#expect(BridgeEndpointID.stableID(endpoint) == "_clawdis-bridge._tcp|local.|Clawdis Bridge Iris")
#expect(BridgeEndpointID.stableID(endpoint) == "_clawdis-bridge._tcp|local.|Clawdis Bridge Node")
}
@Test func stableIDForNonServiceUsesEndpointDescription() {

View File

@@ -8,7 +8,7 @@ import Testing
nodes: [
ControlRequestHandler.GatewayNodeListPayload.Node(
nodeId: "n1",
displayName: "Iris",
displayName: "Node",
platform: "iOS",
version: "1.0",
deviceFamily: "iPad",
@@ -36,10 +36,10 @@ import Testing
#expect(res.pairedNodeIds.sorted() == ["n1", "n2"])
#expect(res.connectedNodeIds == ["n1"])
let iris = res.nodes.first { $0.nodeId == "n1" }
#expect(iris?.remoteAddress == "192.168.0.88")
#expect(iris?.deviceFamily == "iPad")
#expect(iris?.modelIdentifier == "iPad14,5")
#expect(iris?.capabilities?.sorted() == ["camera", "canvas"])
let node = res.nodes.first { $0.nodeId == "n1" }
#expect(node?.remoteAddress == "192.168.0.88")
#expect(node?.deviceFamily == "iPad")
#expect(node?.modelIdentifier == "iPad14,5")
#expect(node?.capabilities?.sorted() == ["camera", "canvas"])
}
}

View File

@@ -10,7 +10,7 @@ Clawdis uses Bonjour (mDNS / DNS-SD) as a **LAN-only convenience** to discover a
## Wide-Area Bonjour (Unicast DNS-SD) over Tailscale
If you want Iris/iPad auto-discovery while the Gateway is on another network (e.g. Vienna ⇄ London), you can keep the `NWBrowser` UX but switch discovery from multicast mDNS (`local.`) to **unicast DNS-SD** (“Wide-Area Bonjour”) over Tailscale.
If you want iOS node auto-discovery while the Gateway is on another network (e.g. Vienna ⇄ London), you can keep the `NWBrowser` UX but switch discovery from multicast mDNS (`local.`) to **unicast DNS-SD** (“Wide-Area Bonjour”) over Tailscale.
High level:
@@ -59,7 +59,7 @@ In the Tailscale admin console:
- Add a nameserver pointing at the gateways tailnet IP (UDP/TCP 53).
- Add split DNS so the domain `clawdis.internal` uses that nameserver.
Once clients accept tailnet DNS, Iris can browse `_clawdis-bridge._tcp` in `clawdis.internal.` without multicast.
Once clients accept tailnet DNS, iOS nodes can browse `_clawdis-bridge._tcp` in `clawdis.internal.` without multicast.
### Bridge listener security (recommended)
@@ -82,7 +82,7 @@ Only the **Node Gateway** (`clawd` / `clawdis gateway`) advertises Bonjour beaco
## Service types
- `_clawdis-master._tcp` — “master gateway” discovery beacon (primarily for macOS remote-control UX).
- `_clawdis-bridge._tcp` — bridge transport beacon (used by Iris/iOS nodes).
- `_clawdis-bridge._tcp` — bridge transport beacon (used by iOS/Android nodes).
## TXT keys (non-secret hints)
@@ -93,7 +93,7 @@ The Gateway advertises small non-secret hints to make UI flows convenient:
- `sshPort=<port>` (defaults to 22 when not overridden)
- `gatewayPort=<port>` (informational; the Gateway WS is typically loopback-only)
- `bridgePort=<port>` (only when bridge is enabled)
- `canvasPort=<port>` (only when the optional canvas host is enabled; default `18793`)
- `canvasPort=<port>` (only when the canvas host is running; enabled by default; default `18793`)
- `tailnetDns=<magicdns>` (optional hint; may be absent)
## Debugging on macOS
@@ -119,9 +119,9 @@ Look for `bonjour:` lines, especially:
- `bonjour: ... name conflict resolved` / `hostname conflict resolved`
- `bonjour: watchdog detected non-announced service; attempting re-advertise ...` (self-heal attempt after sleep/interface churn)
## Debugging on iOS (Iris)
## Debugging on iOS node
Iris discovers bridges via `NWBrowser` browsing `_clawdis-bridge._tcp`.
The iOS node app discovers bridges via `NWBrowser` browsing `_clawdis-bridge._tcp`.
To capture what the browser is doing:

View File

@@ -178,9 +178,9 @@ When enabled, the server:
}
```
### `bridge` (Iris/node bridge server)
### `bridge` (node bridge server)
The Gateway can expose a simple TCP bridge for nodes (iOS/Android “Iris”), typically on port `18790`.
The Gateway can expose a simple TCP bridge for nodes (iOS/Android), typically on port `18790`.
Defaults:
- enabled: `true`

View File

@@ -10,7 +10,7 @@ read_when:
Clawdis has two distinct problems that look similar on the surface:
1) **Operator remote control**: the macOS menu bar app controlling a “master” gateway running elsewhere.
2) **Node pairing**: Iris/iOS (and future nodes) finding a gateway and pairing securely.
2) **Node pairing**: iOS/Android (and future nodes) finding a gateway and pairing securely.
The design goal is to keep all network discovery/advertising in the **Node Gateway** (`clawd` / `clawdis gateway`) and keep clients (mac app, iOS) as consumers.
@@ -55,7 +55,7 @@ Troubleshooting and beacon details: `docs/bonjour.md`.
- `sshPort=22` (or whatever is advertised)
- `gatewayPort=18789` (loopback WS port; informational)
- `bridgePort=18790` (when bridge is enabled)
- `canvasPort=18793` (when the optional canvas host is enabled)
- `canvasPort=18793` (when the canvas host is running; enabled by default)
- `tailnetDns=<magicdns>` (optional hint)
Disable/override:

View File

@@ -34,15 +34,15 @@ WhatsApp / Telegram
┌──────────────────────────┐
│ Gateway │ ws://127.0.0.1:18789 (loopback-only)
│ (single source) │ tcp://0.0.0.0:18790 (optional Bridge)
│ │ http://0.0.0.0:18793 (optional Canvas host)
│ (single source) │ tcp://0.0.0.0:18790 (Bridge)
│ │ http://0.0.0.0:18793 (Canvas host)
└───────────┬───────────────┘
├─ Pi agent (RPC)
├─ CLI (clawdis …)
├─ Chat UI (SwiftUI)
├─ macOS app (Clawdis.app)
└─ iOS node (Iris) via Bridge + pairing
└─ iOS node via Bridge + pairing
```
Most operations flow through the **Gateway** (`clawdis gateway`), a single long-running process that owns provider connections and the WebSocket control plane.
@@ -52,7 +52,7 @@ Most operations flow through the **Gateway** (`clawdis gateway`), a single long-
- **One Gateway per host**: it is the only process allowed to own the WhatsApp Web session.
- **Loopback-first**: Gateway WS is `ws://127.0.0.1:18789` (not exposed on the LAN).
- **Bridge for nodes**: optional LAN/tailnet-facing bridge on `tcp://0.0.0.0:18790` for paired nodes (Bonjour-discoverable).
- **Canvas host (optional)**: LAN/tailnet HTTP file server (default `18793`) for node WebViews; see `docs/configuration.md` (`canvasHost`).
- **Canvas host**: LAN/tailnet HTTP file server (default `18793`) for node WebViews; see `docs/configuration.md` (`canvasHost`).
- **Remote use**: SSH tunnel or tailnet/VPN; see `docs/remote.md` and `docs/discovery.md`.
## Features (high level)
@@ -65,7 +65,7 @@ Most operations flow through the **Gateway** (`clawdis gateway`), a single long-
- 📎 **Media Support** — Send and receive images, audio, documents
- 🎤 **Voice notes** — Optional transcription hook
- 🖥️ **WebChat + macOS app** — Local UI + menu bar companion for ops and voice wake
- 📱 **iOS node (Iris)** — Pairs as a node and exposes a Canvas surface
- 📱 **iOS node** — Pairs as a node and exposes a Canvas surface
Note: legacy Claude/Codex/Gemini/Opencode paths have been removed; Pi is the only coding-agent path.

View File

@@ -8,7 +8,7 @@ read_when:
This repo supports “remote over SSH” by keeping a single Gateway (the master) running on a host (e.g., your Mac Studio) and connecting clients to it.
- For **operators (you / the macOS app)**: SSH tunneling is the universal fallback.
- For **nodes (Iris/iOS and future devices)**: prefer the Gateway **Bridge** when on the same LAN/tailnet (see `docs/discovery.md`).
- For **nodes (iOS/Android and future devices)**: prefer the Gateway **Bridge** when on the same LAN/tailnet (see `docs/discovery.md`).
## The core idea

View File

@@ -50,7 +50,7 @@ Who receives it:
- Uses the global list to gate `VoiceWakeRuntime` triggers.
- Editing “Trigger words” in Voice Wake settings calls `voicewake.set` and then relies on the broadcast to keep other clients in sync.
### iOS node (Iris)
### iOS node
- Uses the global list for `VoiceWakeManager` trigger detection.
- Editing Wake Words in Settings calls `voicewake.set` (over the bridge) and also keeps local wake-word detection responsive.
@@ -59,4 +59,3 @@ Who receives it:
- Exposes a Wake Words editor in Settings.
- Calls `voicewake.set` over the bridge so edits sync everywhere.

View File

@@ -76,7 +76,7 @@ export type BridgeConfig = {
enabled?: boolean;
port?: number;
/**
* Bind address policy for the Iris bridge server.
* Bind address policy for the node bridge server.
* - auto: prefer tailnet IP when present, else LAN (0.0.0.0)
* - lan: 0.0.0.0 (reachable on local network + any forwarded interfaces)
* - tailnet: bind only to the Tailscale interface IP (100.64.0.0/10)

View File

@@ -405,7 +405,7 @@ describe("gateway server", () => {
type: "req",
id: "pair-req-1",
method: "node.pair.request",
params: { nodeId: "n1", displayName: "Iris" },
params: { nodeId: "n1", displayName: "Node" },
}),
);
const res1 = await onceMessage<{
@@ -432,7 +432,7 @@ describe("gateway server", () => {
type: "req",
id: "pair-req-2",
method: "node.pair.request",
params: { nodeId: "n1", displayName: "Iris" },
params: { nodeId: "n1", displayName: "Node" },
}),
);
const res2 = await onceMessage<{
@@ -827,7 +827,7 @@ describe("gateway server", () => {
(p) =>
typeof p === "object" &&
p !== null &&
(p as { instanceId?: unknown }).instanceId === "iris-1" &&
(p as { instanceId?: unknown }).instanceId === "node-1" &&
(p as { reason?: unknown }).reason === reason,
);
},
@@ -835,20 +835,20 @@ describe("gateway server", () => {
);
};
const presenceConnectedP = waitPresenceReason("iris-connected");
const presenceConnectedP = waitPresenceReason("node-connected");
await bridgeCall?.onAuthenticated?.({
nodeId: "iris-1",
displayName: "Iris",
nodeId: "node-1",
displayName: "Node",
platform: "ios",
version: "1.0",
remoteIp: "10.0.0.10",
});
await presenceConnectedP;
const presenceDisconnectedP = waitPresenceReason("iris-disconnected");
const presenceDisconnectedP = waitPresenceReason("node-disconnected");
await bridgeCall?.onDisconnected?.({
nodeId: "iris-1",
displayName: "Iris",
nodeId: "node-1",
displayName: "Node",
platform: "ios",
version: "1.0",
remoteIp: "10.0.0.10",

View File

@@ -1263,7 +1263,7 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
thinking: p.thinking,
deliver: p.deliver,
timeout: Math.ceil(timeoutMs / 1000).toString(),
surface: `Iris(${nodeId})`,
surface: `Node(${nodeId})`,
abortSignal: abortController.signal,
},
defaultRuntime,
@@ -1367,7 +1367,7 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
sessionId,
thinking: "low",
deliver: false,
surface: "Iris",
surface: "Node",
},
defaultRuntime,
deps,
@@ -1442,7 +1442,7 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
typeof link?.timeoutSeconds === "number"
? link.timeoutSeconds.toString()
: undefined,
surface: "Iris",
surface: "Node",
},
defaultRuntime,
deps,
@@ -1508,7 +1508,7 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
const platform = node.platform?.trim() || undefined;
const deviceFamily = node.deviceFamily?.trim() || undefined;
const modelIdentifier = node.modelIdentifier?.trim() || undefined;
const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason iris-connected`;
const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason node-connected`;
upsertPresence(node.nodeId, {
host,
ip,
@@ -1517,7 +1517,7 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
deviceFamily,
modelIdentifier,
mode: "remote",
reason: "iris-connected",
reason: "node-connected",
lastInputSeconds: 0,
instanceId: node.nodeId,
text,
@@ -1554,7 +1554,7 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
const platform = node.platform?.trim() || undefined;
const deviceFamily = node.deviceFamily?.trim() || undefined;
const modelIdentifier = node.modelIdentifier?.trim() || undefined;
const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason iris-disconnected`;
const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason node-disconnected`;
upsertPresence(node.nodeId, {
host,
ip,
@@ -1563,7 +1563,7 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
deviceFamily,
modelIdentifier,
mode: "remote",
reason: "iris-disconnected",
reason: "node-disconnected",
lastInputSeconds: 0,
instanceId: node.nodeId,
text,
@@ -1589,7 +1589,7 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
if (started.port > 0) {
bridge = started;
defaultRuntime.log(
`bridge listening on tcp://${bridgeHost}:${bridge.port} (Iris)`,
`bridge listening on tcp://${bridgeHost}:${bridge.port} (node)`,
);
}
} catch (err) {

View File

@@ -137,7 +137,7 @@ export async function startGatewayBonjourAdvertiser(
svc: master as unknown as BonjourService,
});
// Optional bridge beacon (same type used by Iris/iOS today).
// Optional bridge beacon (same type used by iOS/Android nodes today).
if (typeof opts.bridgePort === "number" && opts.bridgePort > 0) {
const bridge = responder.createService({
name: safeServiceName(instanceName),

View File

@@ -263,7 +263,7 @@ describe("node bridge server", () => {
sendLine(socket, {
type: "pair-request",
nodeId: "n4",
displayName: "Iris",
displayName: "Node",
platform: "ios",
version: "1.0",
deviceFamily: "iPad",
@@ -315,7 +315,7 @@ describe("node bridge server", () => {
expect(lastAuthed?.nodeId).toBe("n4");
// Prefer paired metadata over hello payload (token verifies the stored node record).
expect(lastAuthed?.displayName).toBe("Iris");
expect(lastAuthed?.displayName).toBe("Node");
expect(lastAuthed?.platform).toBe("ios");
expect(lastAuthed?.version).toBe("1.0");
expect(lastAuthed?.deviceFamily).toBe("iPad");
@@ -425,7 +425,7 @@ describe("node bridge server", () => {
sendLine(socket, {
type: "pair-request",
nodeId: "n-caps",
displayName: "Iris",
displayName: "Node",
platform: "ios",
version: "1.0",
deviceFamily: "iPad",