![]()
HomeClaw
Control your Apple HomeKit smart home from AI assistants, the terminal, and automation tools.
HomeClaw exposes your HomeKit accessories through a command-line tool, a stdio MCP server, and plugins for Claude Code and OpenClaw. It runs as a lightweight macOS menu bar app.
Screenshots
Settings — every preference in one place
CLI — every accessory, one command away
Terminal UI — for power users
Why HomeClaw?
Apple HomeKit has no public API, no CLI, and no way to integrate with AI assistants or automation pipelines. HomeClaw bridges that gap with a Mac Catalyst app that talks to HomeKit on your behalf and exposes a clean API surface.
- Ask Claude or OpenClaw to "turn off all the lights" or "set the thermostat to 72"
- Script your smart home from the terminal
- Build automations that go beyond what the Home app offers
- Search and control devices by name, room, category, or semantic type
Architecture
Claude Code --> Plugin (.claude-plugin/) --> stdio MCP server (Node.js) --+
Claude Desktop --> stdio MCP server (Node.js) ----------------------------+
OpenClaw --> Plugin (openclaw/) --> homeclaw-cli --------------------------+
v
Unix socket (JSON newline-delimited)
|
HomeClaw (Mac Catalyst app)
+-- HomeKitManager (direct, in-process)
+-- SocketServer (for CLI/MCP clients)
+-- macOSBridge.bundle (NSStatusItem menu bar)
Single-process design. Apple's HMHomeManager requires a UIKit/Catalyst app with the HomeKit entitlement. By making the entire app Catalyst, HomeKit access is direct (no IPC), signing is unified (single archive), and App Store submission is clean. The macOSBridge plugin bundle provides the native macOS menu bar via NSStatusItem.
Native MCP for Hermes
HomeClaw must be running before connecting. In Settings → Integrations, enable Streamable HTTP MCP. This persistent setting is off by default; turning it off stops the HTTP listener without disabling existing stdio or Unix-socket clients. When enabled, the Catalyst app owns a loopback-only Streamable HTTP endpoint:
http://127.0.0.1:9090/mcp
Loopback binding prevents remote network access; no bearer token or shell launch is required. Readiness is available at http://127.0.0.1:9090/healthz. In Hermes, add:
mcp_servers:
homeclaw:
url: http://127.0.0.1:9090/mcp
timeout: 120
connect_timeout: 30
The stdio MCP server remains supported for Claude Desktop, Claude Code, and OpenClaw. Use the existing npm run build:mcp and stdio setup when a client cannot use native HTTP.
Native transport development checks
swift test covers the CLI package only. To build and execute the full native
Catalyst HomeClawTests suite on macOS 26 or later:
npm install
node scripts/check-mcp-schema-parity.mjs
xcodegen generate
bash scripts/test-catalyst.sh
The test script saves the xcodebuild log and .xcresult under
.build/catalyst-test-evidence/ and fails on empty or skipped test results. For a
repeat run, set CATALYST_EVIDENCE_DIR to a fresh directory. Unit-test hosting
suppresses live HomeKit, socket, and menu-bar startup; these tests are not proof
of HomeKit provisioning or real accessory access.
lib/schemas.js is the canonical tool schema. The Swift descriptors are readable
JSON and CI compares every descriptor against the Node export and the XCTest
fixture. After an intentional schema edit, regenerate both reviewed copies with
node scripts/check-mcp-schema-parity.mjs --write, then inspect their diff. The
HTTP read-only allowlist remains separate and deny-by-default; adding a tool to
the canonical schema does not expose it over HTTP.
Install
Marketing site with screenshots and full pitch: homeclaw.omarknows.app.
Mac App Store (Recommended)
HomeClaw is live on the Mac App Store:
- Open in the Mac App Store and install
- Launch HomeClaw -- grant HomeKit access when prompted
- The menu bar icon appears. Click it to see your connected homes.
TestFlight Beta
Want early access to in-flight builds before they ship to the App Store?
- Join the TestFlight Beta
- Install HomeClaw from TestFlight
- Launch the app -- grant HomeKit access when prompted
After installing
Set up your AI integrations:
- Claude Desktop -- one-click install from Settings > Integrations, or add the MCP server config manually
- Claude Code -- install the plugin from GitHub
- OpenClaw -- one-click install from Settings > Integrations, or set up manually
Build from Source
Prerequisites and setup for building from source
Prerequisites
- macOS 26 (Tahoe) or later
- Xcode 26+ with Swift 6.2
- XcodeGen:
brew install xcodegen - Node.js 20+ (for the MCP server wrapper)
- Apple Developer account with HomeKit capability enabled
Why is a developer account required? Apple does not provide a public HomeKit API for macOS. The only way to access HomeKit is throughHMHomeManager, which requires thecom.apple.developer.homekitentitlement and a provisioning profile that covers your Mac's hardware UDID. Apple restricts this entitlement to development signing and App Store distribution -- it cannot be included in Developer ID (notarized) builds. This means every Mac that runs HomeClaw must be registered as a development device in your Apple Developer portal, and the app must be built with your team's signing identity. There is no way around this; it's an Apple platform restriction, not a HomeClaw limitation.
Setup
git clone https://github.com/omarshahine/HomeClaw.git
cd HomeClaw
Configure your Apple Developer Team ID (one-time setup)
echo "HOMEKIT_TEAM_ID=YOUR_TEAM_ID" > .env.local
Install Node.js dependencies
npm install
Build everything and install
scripts/build.sh --release --install
Find your Team ID at developer.apple.com/account under Membership Details.
Launch from /Applications or: open "/Applications/HomeClaw.app"
On first launch, grant HomeKit access when prompted. The menu bar icon appears -- click it to see your connected homes.
Note: Apple restricts the HomeKit entitlement to development signing and App Store distribution. Developer ID builds cannot access HomeKit. See Why Development Signing? for details.
MCP Tools
The stdio MCP server wraps homeclaw-cli and exposes these tools:
| Tool | Description |
|------|-------------|
| homekit_status | Check bridge connectivity and accessory count |
| homekit_accessories | List, get details, search, or control accessories |
| homekit_rooms | List rooms and their accessories |
| homekit_scenes | List, get details, trigger, import, or delete scenes |
| homekit_device_map | LLM-optimized device map with semantic types and aliases |
| homekit_manage | Manage home structure: rename accessories, individual services, and rooms; set a switch/outlet's Display As; list and manage accessory groups; create/remove rooms and zones, manage zone membership |
| homekit_automations | Manage automations: list, create button-press triggers (single/double/long), link to scenes, enable/disable |
| homekit_events | Query recent HomeKit events (characteristic changes, reachability changes, scene triggers, control actions) |
| homekit_webhook | Manage webhook configuration: setup (configure + auto-test), test, reset circuit breaker, status |
| homekit_config | View or update configuration (set active home, filtering) |
Connecting an MCP Client
Any MCP-compatible client can connect via the stdio server, which wraps homeclaw-cli and requires no authentication (the HomeClaw app must be running for the socket). Add this to your MCP client config (e.g. claude_desktop_config.json):
{
"mcpServers": {
"homeclaw": {
"command": "node",
"args": ["/Applications/HomeClaw.app/Contents/Resources/mcp-server.js"]
}
}
}
The mcp-server.js is bundled inside the app. You can also use the Integrations tab in Settings to install this automatically.
CLI
The homeclaw-cli command-line tool communicates directly over the Unix domain socket. All read commands support --json for machine-readable output. Day-to-day commands also accept --home to target a specific HomeKit home without changing the configured default (config --default-home).
# List accessories
homeclaw-cli list
homeclaw-cli list --room "Kitchen"
homeclaw-cli list --category thermostat
homeclaw-cli list --home "Cabin" --room "Kitchen" # Scope to a home (name or UUID)
Control devices
homeclaw-cli set "Living Room Light" brightness 75
homeclaw-cli set "Front Door Lock" lock_target_state locked
homeclaw-cli set "Thermostat" target_temperature 72
homeclaw-cli set "Kitchen Light" power true --home "Cabin"
Disambiguate when a characteristic exists on multiple services (e.g. bridged TVs)
homeclaw-cli set "TV" active 0 --service-type 000000D8-0000-1000-8000-0026BB765291
Get detailed device info
homeclaw-cli get "Kitchen Light" --json
homeclaw-cli get "Kitchen Light" --home "Cabin"
Search (optionally scoped to one home)
homeclaw-cli search "bedroom" --category lightbulb
homeclaw-cli search "lamp" --home "Cabin"
Scenes
homeclaw-cli scenes
homeclaw-cli scenes --home "Cabin"
homeclaw-cli get-scene "Good Night" --json # Full detail: all actions
homeclaw-cli trigger "Good Night"
homeclaw-cli trigger "Good Night" --home "Cabin"
Scene management
homeclaw-cli delete-scene "Old Scene"
homeclaw-cli import-scene scene.json --dry-run # Preview before creating
homeclaw-cli import-scene scene.json # Create scene from JSON
homeclaw-cli assign-rooms rooms.json --dry-run # Preview room assignments
homeclaw-cli assign-rooms rooms.json # Bulk-assign accessories to rooms
Home management
homeclaw-cli rename "Front Door" "Front Door Lock" # Rename accessory
homeclaw-cli rename "Dual Relay" "Downlight 2" --service-name "Switch 2" # Rename one gang's tile
homeclaw-cli set-display-as "Dual Relay" light --service-index 1 # Display As: light / fan / switch / outlet / default
homeclaw-cli groups # Home app accessory groups and members
homeclaw-cli groups create "Ceiling Lights" "Downlight 1" <service-uuid> # One tile for several lights (same kind)
homeclaw-cli groups add "Ceiling Lights" "Stairs LED" # Also: remove / rename / delete, --dry-run
homeclaw-cli rename-room "Bedroom" "Primary Bedroom" # Rename room
homeclaw-cli create-room "Nursery" # Create room
homeclaw-cli remove-room "Old Room" # Remove room
homeclaw-cli remove-accessory "Broken Sensor" # Remove accessory
homeclaw-cli create-zone "Upstairs" # Create zone
homeclaw-cli remove-zone "Old Zone" # Remove zone
homeclaw-cli add-room-to-zone "Bedroom" "Upstairs" # Add room to zone
homeclaw-cli remove-room-from-zone "Bedroom" "Upstairs" # Remove room from zone
homeclaw-cli rename "Front Door" "New Name" --dry-run # Preview any mutation
Automations (button programming)
homeclaw-cli automations # List all automations
homeclaw-cli automations get "<name-or-uuid>" # Detail view
Scene-based: trigger an existing scene
homeclaw-cli automations create --name "Gym Lights" \
--accessory "Gym Button" --scene "Gym On" --press single
Inline actions: use UUIDs for target accessories to avoid name collisions
homeclaw-cli automations create --name "Sarah's Room Open" \
--accessory "Office Button" \
--action "BE21C139-413A-50F9-B97F-B9BDA06302A8:power:true" \
--action "52195C6F-6FAA-5E52-AA56-840A6605EEAA:target_position:100" \
--press single --service-index 1
Multi-button preview
homeclaw-cli automations create --name "Open Blinds" \
--accessory "Blinds Button" --scene "Open" \
--service-index 1 --dry-run
One gang of a multi-gang accessory: JSON row form (see "Targeting one gang" below)
homeclaw-cli automations create --name "Table Light Off" \
--accessory "Dining Button" --press single \
--action-json '{"accessory":"<uuid>","property":"power","value":"false","service_index":2}'
homeclaw-cli automations delete "<name-or-uuid>" # Delete automation
homeclaw-cli automations enable "<name-or-uuid>" # Enable
homeclaw-cli automations disable "<name-or-uuid>" # Disable
Replacing an accessory: plan which automation actions move (writes nothing)
homeclaw-cli automations migrate-actions --replace-accessory "<old-uuid>=<new-uuid>[,<new-uuid>]"
homeclaw-cli automations migrate-actions --replace-accessory "<old>=<new>" --automation "<name-or-uuid>" --json
homeclaw-cli automations migrate-actions --replace-accessory "<old>=<new>" --apply # Writes visible scenes only
LLM-optimized device map
homeclaw-cli device-map
homeclaw-cli device-map --home "Cabin"
Status and configuration
homeclaw-cli status
homeclaw-cli config --default-home "Main House"
homeclaw-cli config --filter-mode allowlist
homeclaw-cli config --list-devices
Event log
homeclaw-cli events # Recent events (last 50)
homeclaw-cli events --since 1h # Events from the last hour
homeclaw-cli events --since 2d --json # Last 2 days, JSON output
homeclaw-cli events --type scene_triggered # Filter by event type
homeclaw-cli events --type reachability_change --since 1d # When accessories dropped off or came back
Webhook configuration
homeclaw-cli config --webhook-url "http://127.0.0.1:18789"
homeclaw-cli config --webhook-token "your-secret-token"
homeclaw-cli config --webhook-enabled true
homeclaw-cli config --webhook-test # Send test event, show HTTP response
homeclaw-cli config --webhook-reset # Reset circuit breaker without toggling
Webhook triggers
homeclaw-cli triggers # List all triggers
homeclaw-cli triggers add --label "Front Door" --accessory-id "<uuid>"
homeclaw-cli triggers add --label "Mailbox Open" --accessory-id "<uuid>" --characteristic contact_state
homeclaw-cli triggers add --label "Accessory offline" --characteristic reachable --value false
homeclaw-cli triggers update "<trigger-id>" --wake-mode now
homeclaw-cli triggers remove "<trigger-id>"
Dry-run mutations (validate without actuating)
homeclaw-cli set "Front Door" lock_target_state locked --dry-run
homeclaw-cli delete-scene "Movie Night" --dry-run
Auto-JSON: all commands output JSON when piped or when env var is set
homeclaw-cli status | jq . # Auto-detects non-TTY
OUTPUT_FORMAT=json homeclaw-cli list # Force JSON via env var
Button Programming Notes
Inline vs scene: Use --action for simple button-to-device mappings. This creates a scene named after the automation. Use --scene to trigger an existing scene instead. Note: Apple's Home app uses a private API for hidden automation-only action sets; inline actions created via HomeClaw will appear as visible scenes in the Home app.
Button modes: Some buttons (like Aqara AR009) support fast mode (2 buttons, single press each, instant response) or multi-event mode (1 button, single/double/long press, ~300ms delay). Use --service-index to target a specific button in fast mode.
Note that --service-index on automations create picks the trigger button. The service an inline action or condition writes to is picked per row, as below.
Targeting one gang of a multi-gang accessory
A dual relay or a 3-gang switch is one accessory with several services that all carry power. Any action or condition row can pick one of them with service_name, service_id, or service_index (the same selectors set takes). The positional accessory:property:value form has no room for a fourth field (it splits on the first two colons so values like 22:30 survive), so use the JSON row flags instead:
# Turn off both gangs of one dual relay
homeclaw-cli automations create --name "Dining Unoccupied" \
--accessory "Dining FP2" --characteristic occupancy_detected --trigger-value false \
--action-json '{"accessory":"ADA239D3-...","property":"power","value":"false","service_index":1}' \
--action-json '{"accessory":"ADA239D3-...","property":"power","value":"false","service_name":"Table Light"}' \
--dry-run
Conditions take the same keys (--condition-json on create / create-time, flags on add-condition)
homeclaw-cli automations add-condition "Porch Lights" \
--accessory "ADA239D3-..." --property power --value true --service-index 2
--action-json / --condition-json are repeatable, accept accessory, property (or characteristic), value, room, and the three selectors, and reject unknown keys. Rules for every surface (CLI, MCP, scene JSON):
- With a selector, the row resolves to exactly that service, or fails with an error listing the services that carry the property. It never falls back to another gang.
- Without a selector on a property that exists on several services, HomeClaw keeps the old behavior (the first service) so existing scene JSON keeps working, but the response carries a
warningsentry naming every candidate. Add a selector to silence it. - Two actions on the same accessory and property are allowed when they target different services. The same service twice is rejected.
get-scene,automations get, and dry-run previews emitservice_name,service_id, andservice_indexon rows whose accessory has more than one service carrying that property, so their JSON round-trips throughimport-scene/update-scenewithout collapsing gangs. Rows on single-service accessories are unchanged.
Replacing an Accessory in Automations
You swapped a bulb, a switch, or a shade. Now every automation that used the old one needs the new one. automations migrate-actions plans that move and tells you, per action set, what it can and can't do.
Each action set an automation runs is one of two kinds:
| Kind | What it is | Can HomeClaw edit it? |
|------|------------|-----------------------|
| visible_scene | A scene in the Home app's Scenes list that an automation points at | Yes, in place (the scene keeps its UUID). But it's shared: editing it changes every other automation that uses it, and the scene tile itself. The plan lists those in shared_with. |
| hidden | The action set the Home app creates inside one automation (most button and sensor automations made in the Home app) | No. Apple's HomeKit daemon only lets apps with Apple's private com.apple.homekit.private-spi-access entitlement write these. No third-party app can get it. See docs/PRIVATE_API.md. |
So hidden sets always come back "status": "unsupported" with "reason": "hidden_action_set_requires_private_spi", the exact accessories and values to change, and manual_steps you (or your assistant) can follow in the Home app: open the automation, remove the old accessory, add the new one, set each value, tap Done. Rerun the dry run afterwards and the automation drops out of the plan.
For each old action, the plan finds the same characteristic type (power, brightness, color temperature, hue, saturation, position, and anything else) on each replacement. A visible set is blocked instead of supported when no replacement can take a characteristic (it would be dropped), when a replacement has several services carrying it (the issue lists each candidate's service_name, service_id and service_index; migrate-actions doesn't take selectors itself, so edit that scene with update-scene rows that carry one), or when the new action would conflict with one already in the scene.
It's a dry run unless you pass --apply. With --apply, only supported visible scenes are written: the rollback snapshot (every current action) is logged and returned first, the new actions are added, the old ones removed, and the scene is re-read and checked to contain exactly the planned actions. Triggers, conditions, enabled state and recurrence are never touched. A failed or unverified write exits non-zero and reports what was written.
Automations that are only triggered by the old accessory (a motion sensor that turns on a light) are listed too, with "trigger_only": true and no action sets: there's nothing to move, but that trigger stops firing once the old accessory is gone, and this command never rewrites triggers. Recreate those in the Home app.
Migrate before you remove the old accessory from HomeKit. HomeKit usually drops a removed accessory's scene actions, and then there's nothing left to move. An old accessory that's already gone can still be named by UUID.
Interactive TUI
homeclaw-cli ui
Launches a full-screen, keyboard-driven view of your home. Browse rooms and accessories, see live state at a glance, and toggle anything that has a binary state (lights, switches, fans, outlets, locks, blinds).
🦞 HomeClaw 21 rooms · 112 accessories
┌──────────────────────────────────────────────────────────────────────┐
│ ▾ Living Room (4) │
│ ├─ ● Floor Lamp On │
│ ├─ ● TV Light Strip On │
│ ├─ ◐ Thermostat 70.5°F │
│ └─ ○ Window Blinds 100% │
│ │
│ ▾ Kitchen (3) │
│ ├─ ○ Counter Light Off │
│ ... │
└──────────────────────────────────────────────────────────────────────┘
^D quit ↑↓ navigate Enter toggle
| Key | Action |
|---|---|
| ↑ ↓ | Navigate between accessories |
| Enter | Toggle the focused accessory (lights/switches/outlets/fans flip on↔off, locks flip locked↔unlocked, blinds flip 0%↔100%) |
| Ctrl-D | Quit |
State color coding: lights on are yellow, outlets/switches cyan, fans blue, locks green when locked / red when unlocked, motion red, sensors cyan, off/idle dim.
The TUI works over SSH (no AppKit assumptions), runs against the same Unix socket as the rest of the CLI, and respects demo mode — launch HomeClaw with --ui-test-demo to render a synthetic home for screenshots or demos. Built on SwiftTUI. Scene picker, search, and help overlay are tracked in #53 — they need a different framework's key handling and will land when SwiftTUI gains app-level key bindings (or we switch).
Using with Claude Code
HomeClaw integrates with Claude Code as a plugin that provides MCP tools and a HomeKit skill for richer natural language understanding.
Installing the Plugin
Install from a local clone or directly from GitHub.
From a local clone:
# Clone if you haven't already
git clone https://github.com/omarshahine/HomeClaw.git ~/GitHub/HomeClaw
Inside Claude Code, register the marketplace and install
/plugin marketplace add ~/GitHub/HomeClaw
/plugin install homeclaw@homeclaw
From GitHub (no local clone needed):
# Inside Claude Code, add the GitHub repo as a marketplace
/plugin marketplace add https://github.com/omarshahine/HomeClaw
Install the plugin
/plugin install homeclaw@homeclaw
After installing, restart Claude Code. Then just ask:
"Turn on the kitchen lights and set them to 50% brightness"
"Lock all the doors"
"What's the thermostat set to?"
"Run the movie time scene"
"Which lights are on in the living room?"
Verifying the Connection
After installing, verify Claude can reach HomeKit:
# Check MCP server status inside Claude Code
/mcp
Using with OpenClaw
HomeClaw includes an OpenClaw plugin that registers a HomeKit skill on the gateway. The skill calls homeclaw-cli by name, so it must be in your PATH.
Same-Mac Install (Recommended)
If HomeClaw and OpenClaw run on the same Mac, use the one-click installer:
- Open Settings > Integrations and click Install in the OpenClaw section.
homeclaw-cli into your PATH, and restarts the gateway.
Or from the terminal:
# 1. Install the plugin from the bundled files
openclaw plugins install "/Applications/HomeClaw.app/Contents/Resources/openclaw/"
openclaw plugins enable homeclaw
2. Symlink the CLI into PATH (the skill calls homeclaw-cli by name)
Apple Silicon (M1/M2/M3/M4):
ln -sf '/Applications/HomeClaw.app/Contents/MacOS/homeclaw-cli' /opt/homebrew/bin/homeclaw-cli
Intel:
ln -sf '/Applications/HomeClaw.app/Contents/MacOS/homeclaw-cli' /usr/local/bin/homeclaw-cli
3. Restart the gateway to load the plugin
openclaw gateway restart
Remote Gateway
If OpenClaw runs on a different machine:
# Clone the repo on the gateway
git clone https://github.com/omarshahine/HomeClaw.git ~/GitHub/HomeClaw
Install the plugin
openclaw plugins install ~/GitHub/HomeClaw/openclaw
openclaw plugins enable homeclaw
Symlink the CLI into PATH
ln -sf /path/to/homeclaw-cli /opt/homebrew/bin/homeclaw-cli
Restart the gateway
openclaw gateway restart
Note: The homeclaw-cli binary must be accessible from the gateway (in PATH), and the HomeClaw app must be running on the same Mac (connected via Unix socket).
Enabling Control Tools
The plugin registers 20 agent tools. The read-only ones (homekit_status, homekit_device_map, homekit_list, homekit_get, homekit_search, homekit_scenes, homekit_get_scene, homekit_events, homekit_groups, homekit_automations_list, homekit_automations_get) are on by default, including under the coding and messaging tool profiles.
The nine tools that can change your home (homekit_set, homekit_trigger, homekit_import_scene, homekit_delete_scene, homekit_rename, homekit_set_display_as, homekit_manage_group, homekit_automations_create, homekit_automations_migrate_actions) are optional: OpenClaw hides them until you opt in. To allow all HomeClaw tools, add the plugin id to tools.alsoAllow in your OpenClaw config:
{
"tools": {
"alsoAllow": ["homeclaw"]
}
}
List individual tool names instead of homeclaw to enable only some of them, then restart the gateway.
Supported Accessories
HomeClaw supports the full range of HomeKit accessory categories:
| Category | Controllable Characteristics |
|----------|------------------------------|
| Lights | power, brightness (0-100), hue (0-360), saturation (0-100), color temperature (140-500 mireds) |
| Thermostats | target temperature, HVAC mode (off/heat/cool/auto), target humidity |
| Locks | lock/unlock (accepts locked, unlocked, 0, 1) |
| Doors & Garage Doors | open/close, obstruction detection (read-only) |
| Fans | active, rotation speed, rotation direction, swing mode |
| Window Coverings | target position (0-100%) |
| Switches & Outlets | power on/off |
| Sensors | motion, contact, temperature, humidity, light level, battery (all read-only) |
| Doorbells | ring detection via input_event (single/double/long press), motion (read-only) |
| Programmable Switches | button press detection (single/double/long press) (read-only) |
| Scenes | trigger by name or UUID, inspect actions, import from JSON, delete by name or UUID |
Scene Import Format
The import-scene command accepts a JSON file defining a scene and its actions:
{
"name": "Movie Night",
"actions": [
{"accessory": "Living Room Light", "room": "Living Room", "property": "brightness", "value": "30%"},
{"accessory": "TV Backlight", "room": "Living Room", "property": "power_state", "value": "ON"},
{"accessory": "Overhead", "room": "Living Room", "property": "power_state", "value": "OFF"}
]
}
Action objects also accept characteristic as an alias for property (read commands emit characteristic, so their output round-trips as input).
To target one gang of a multi-gang accessory, add service_name, service_id, or service_index to the action (service_index may be a JSON number or a string). get-scene --json emits these on exactly the rows that need them, so its output can be edited and fed back to update-scene:
{
"id": "D47A94C9-BCCA-553A-AA3C-E756E91D22FE",
"actions": [
{"accessory_id": "ADA239D3-...", "property": "power", "value": "0", "service_name": "Covelight", "service_index": "1"},
{"accessory_id": "ADA239D3-...", "property": "power", "value": "0", "service_name": "Table Light", "service_index": "2"}
]
}
See Targeting one gang of a multi-gang accessory for the resolution and warning rules.
The assign-rooms command accepts a JSON file mapping accessories to rooms. Use uuid for precise matching when multiple accessories share the same name (e.g., fan + light from a ceiling fan). A name that matches several accessories is skipped with status ambiguous rather than guessed. Every other command that takes an accessory name (including scene import/update actions and automation conditions) fails on a duplicate before changing anything, and lists each match's room and UUID:
[
{"accessory": "Kitchen Light", "room": "Kitchen"},
{"uuid": "790A359D-27A6-5738-A92E-F4380CC6BA07", "room": "Front Door"},
{"accessory": "Desk Lamp", "room": "Office"}
]
The same array wrapped as {"assignments": [...]} is also accepted.
All file-taking commands (import-scene, update-scene, assign-rooms) accept - in place of the file path to read JSON from stdin — the recommended path for the sandboxed App Store build, which can only read files from its own container:
echo '[{"accessory": "Desk Lamp", "room": "Office"}]' | homeclaw-cli assign-rooms -
Both commands support --dry-run to preview changes without modifying HomeKit.
Menu Bar App
The menu bar provides at-a-glance status and quick actions:
- HomeKit connection status -- shows home names when connected, or error states with reasons
- Launch at Login toggle
- Settings link and Quit
Settings
Five configuration tabs accessible from the menu bar:
| Tab | Features |
|-----|----------|
| HomeKit | Connection status, home list with accessory and room counts, active home selector |
| Devices | Filter mode (all/allowlist), per-device toggles with category icons and state badges, grouped by room, search, bulk select/deselect |
| Event Log | Enable/disable event logging, configure file rotation (size limit + backup count), view storage stats, purge logs, reveal in Finder |
| Webhook | Configure webhook base URL + bearer token, select which scenes and accessories trigger webhooks with category icons and state badges. Accessories with multiple characteristics (e.g., a sensor with both contact and motion) show individual toggles per characteristic; battery characteristics are excluded. Per-trigger delivery mode (Batched/Immediate). Circuit breaker banners with Reset button, delivery stats, and last HTTP status. Test webhook connectivity from CLI or manage triggers via homeclaw-cli triggers. |
| Integrations | One-click install for Claude Desktop, Claude Code plugin detection, OpenClaw gateway setup |
HomeKit
View connection status, browse your homes, and select which home is active for all MCP and CLI commands.

Devices
Control which accessories are exposed to MCP clients and the CLI. Switch between All Accessories (everything visible) and Selected Only (allowlist mode). Accessories are grouped by room with a search filter and room-level toggles for quick bulk selection.

Integrations
Install and manage connections to AI assistants. The app detects existing configurations and guides you through setup:
- Claude Desktop -- one-click install of the bundled stdio MCP server (requires Node.js)
- Claude Code -- detects the installed plugin (
homeclaw@homeclaw) - OpenClaw -- detects plugin configuration on the remote gateway and provides setup instructions

Event Log
HomeClaw records all HomeKit events to a JSONL file in ~/Library/Application Support/HomeClaw/events.jsonl. Events include characteristic changes (a light turned on), reachability changes (an accessory became unreachable or came back), scene triggers, and control actions from the CLI or MCP.
Configuration
Open Settings > Event Log to configure:
- Enable/disable event logging
- Max file size (10-500 MB) -- when the log reaches this size, it's rotated
- Rotated backups (0-10) -- how many old log files to keep before deleting the oldest
- Purge -- delete all event log files
- Show in Finder -- reveal the log directory
homeclaw-cli events # Show recent events
homeclaw-cli events --since 1h # Last hour
homeclaw-cli events --type characteristic_change # Filter by type
homeclaw-cli events --limit 200 --json # JSON output
Event types: characteristic_change, scene_triggered, accessory_controlled, homes_updated, reachability_change
A reachability_change event is logged when HomeKit reports that an accessory became reachable or unreachable. Repeated reports with no change are dropped.
{
"type": "reachability_change",
"timestamp": "2026-09-28T04:20:09Z",
"accessory": { "id": "…", "name": "B1", "room": "Kids", "matter_node_id": "000000000000002A" },
"reachable": false,
"home": { "id": "…", "name": "Home" }
}
accessory.matter_node_id appears only for Matter accessories, and home only when the accessory's home is known.
Matter node ids
list, search, get and device-map include matter_node_id for Matter accessories. It is the node id Apple Home assigned on its Matter fabric, written as 16 uppercase hex digits (a string, so the 64-bit value is not rounded by JSON parsers). Non-Matter accessories omit the key.
A Matter device advertises itself over mDNS as , so the node id joins a HomeKit accessory to its network entry:
homeclaw-cli get "B1" --json | jq -r .matter_node_id # e.g. 000000000000002A
dns-sd -B _matter._tcp # instance names end in -<node id>
An unreachable accessory whose node id is missing from dns-sd output has dropped off the network (for Thread, the mesh). One that is still advertised points at the controller side instead.
The --since flag accepts ISO 8601 timestamps or duration shorthand: 1h, 30m, 2d.
Webhook
HomeClaw pushes HomeKit events to OpenClaw via mapped webhooks. Only accessories and scenes with configured triggers fire webhooks -- untriggered events are logged to disk but not pushed. Reachability changes are opt-in: they fire only triggers whose characteristic is reachable (optionally with value false for drops only, and an accessory id for one device). Accessory triggers without that characteristic are not fired by reachability changes, so a flapping accessory does not flood them. A dedicated HomeClaw agent receives all events, classifies them by severity, and escalates noteworthy ones to the main agent.
Note: OpenClaw 2026.3.x has a known bug where/hooks/wakesilently drops events (#33271). HomeClaw uses mapped webhooks (/hooks/homeclaw) which route throughhooks.mappingsand are not affected.
How It Works
HomeClaw POSTs all triggered events to a single mapped endpoint: /hooks/homeclaw. OpenClaw's hooks.mappings config resolves the "homeclaw" key and routes each event to a dedicated HomeClaw agent that classifies and triages events.
HomeKit event --> HomeClaw event logger --> POST /hooks/homeclaw
--> OpenClaw hooks.mappings --> HomeClaw Agent (dedicated)
--> Classifies: CRITICAL / NOTABLE / AMBIENT
--> If notable/critical: a2a to main agent
--> If ambient: log to agent memory only
HomeClaw doesn't need to know about agentId, channel, or sessionKey -- all routing intelligence lives in the OpenClaw config.
Trigger Delivery Modes
Each trigger has a delivery mode that controls timing:
| Mode | Behavior | Set via | |------|----------|---------| | Batched (default) | Event queued until next heartbeat cycle | Settings UI segmented control | | Immediate | Event delivered right away | Settings UI segmented control |
In Settings > Webhook, each enabled trigger shows a Batched / Immediate picker. Use Immediate for events you want to react to right away (leak sensors, door locks, scene triggers). Use Batched for ambient events (light toggles, temperature changes) to avoid noise.
Setup with AI Assistant
Paste this prompt into OpenClaw or Claude Code to configure webhooks end-to-end. The prompt is idempotent -- it verifies each step and skips anything already configured:
Prerequisites: HomeClaw must be installed and running (menu bar icon visible). If using Claude Code, thehomeclawplugin must be registered (/plugin install homeclaw@homeclaw). Thehomeclaw-clibinary must be in your PATH.
> Set up HomeClaw mapped webhooks with a dedicated HomeClaw agent. Verify each step first -- skip any that are already configured.
> 1. HomeClaw agent workspace:
- Check ifhomeclawagent exists:openclaw agents list
- If missing, install from the bundled app: openclaw agents install homeclaw /Applications/HomeClaw.app/Contents/Resources/openclaw/agents/homeclaw
- Copy agent docs (IDENTITY/SOUL/AGENTS/TOOLS.md) from the app bundle to the local agent dir if newer
- Configure agent: model claude-sonnet-4, restricted tools (memory, sessions/a2a, read/write only -- no exec, no browser, no external services)
> 2. OpenClaw gateway hooks config:
- Check~/.openclaw/openclaw.jsonfor ahooks.mappings.homeclawentry
- If missing, add:
> "enabled": true, > "token": "${HOMECLAW_WEBHOOK_TOKEN}", > "mappings": { > "homeclaw": { > "agentId": "homeclaw", > "sessionKey": "hook:homeclaw", > "deliver": true, > "channel": "last", > "allowUnsafeExternalContent": true > } > } > } >> "hooks": {
- Create a transform at ~/.openclaw/hooks/transforms/homeclaw-transform.js to convert HomeClaw state-change payloads into agent messages
- Check~/.openclaw/.envforHOMECLAW_WEBHOOK_TOKEN. If missing, generate one:openssl rand -base64 24 | tr '+/' '-_' | tr -d '='and add it
- Remove any legacy defaultSessionKey entries that are no longer needed with dedicated mapping
- Restart gateway only if config changed: openclaw gateway restart
> 3. HomeClaw webhook config:
- Check current config: homeclaw-cli config --json
- Verify:webhook.enabledis true,webhook.urlishttp://127.0.0.1:18789,webhook.tokenmatches theHOMECLAW_WEBHOOK_TOKENfrom step 2, andwebhook_endpointis/hooks/homeclaw
- Fix any mismatches via HomeClaw Settings UI or CLI
- If circuit breaker shows old failures, reset it
> 4. End-to-end test:
- Run homeclaw-cli config --webhook-test -- expect HTTP 200
- Verify circuit state isclosedandlast_successis recent
> Report what was already configured, what was changed, and the test result.
Manual Setup
Step-by-step without an AI assistant
1. Create the Agent Workspace
The agent docs are bundled inside the HomeClaw app. Because the app bundle is read-only, you need a local copy for OpenClaw to use as the agent directory:
# Create local agent dir with workspace
mkdir -p ~/.openclaw/agents/homeclaw/{agent,workspace}
Copy agent docs from app bundle
cp /Applications/HomeClaw.app/Contents/Resources/openclaw/agents/homeclaw/*.md \
~/.openclaw/agents/homeclaw/agent/
Register the agent
openclaw agents add homeclaw \
--agent-dir ~/.openclaw/agents/homeclaw/agent \
--workspace ~/.openclaw/agents/homeclaw/workspace
openclaw agents list # verify it appears
Note:openclaw agents add(notinstall) is the correct CLI command. The--workspaceflag is required in non-interactive mode.
2. Configure OpenClaw
Add the hooks block with mappings to ~/.openclaw/openclaw.json:
"hooks": {
"enabled": true,
"token": "${HOMECLAW_WEBHOOK_TOKEN}",
"mappings": {
"homeclaw": {
"agentId": "homeclaw",
"sessionKey": "hook:homeclaw",
"deliver": true,
"channel": "last",
"allowUnsafeExternalContent": true
}
}
}
The mappings entry routes all /hooks/homeclaw POSTs to the dedicated HomeClaw agent in a persistent hook:homeclaw session.
Create a transform to convert HomeClaw payloads into agent messages. The hook mapping requires a message field in the payload -- without a transform, payloads with only text + mode will get a 400 error:
mkdir -p ~/.openclaw/hooks/transforms
cat > ~/.openclaw/hooks/transforms/homeclaw-transform.js << 'TRANSFORM'
// Convert HomeClaw webhook payload {text, mode} into agent message format
module.exports = function(payload) {
return {
message: payload.text || JSON.stringify(payload),
mode: payload.mode || "next-heartbeat"
};
};
TRANSFORM
Then reference the transform in the mapping:
"homeclaw": {
"agentId": "homeclaw",
"sessionKey": "hook:homeclaw",
"deliver": true,
"channel": "last",
"allowUnsafeExternalContent": true,
"transform": "homeclaw-transform"
}
Generate a token and add it to ~/.openclaw/.env:
# Generate a secure token
openssl rand -base64 24 | tr '+/' '-_' | tr -d '='
Add to .env
echo 'HOMECLAW_WEBHOOK_TOKEN=<your-generated-token>' >> ~/.openclaw/.env
Restart the gateway: openclaw gateway restart
Warning: The gateway rewrites openclaw.json on restart. Verify your mapping and transform entries survived by checking the file after restart. If a newly-added mapping is stripped, restart a second time -- there may be a race condition with first-time mappings.
3. Configure HomeClaw
Option A -- GUI: Open Settings > Webhook. Toggle Enable, enter http://127.0.0.1:18789 as the base URL, paste the same token from step 2.
Option B -- CLI (note: the CLI updates the running daemon only -- it does not persist to config.json. Use the Settings UI or edit the config file directly for persistent changes):
homeclaw-cli config --webhook-url "http://127.0.0.1:18789" \
--webhook-token "your-token" \
--webhook-enabled true
4. Test the Pipe
homeclaw-cli config --webhook-test
You should see an HTTP 200 response. If it fails, check the token matches and the OpenClaw gateway is running.
Note: Test events updatetotal_deliveredandlast_http_statusin the circuit breaker stats. After running--webhook-test, checkhomeclaw-cli config --jsonto confirm the values were updated.
5. Create Triggers
In Settings > Webhook, check the accessories and scenes you want to fire webhooks. Only checked items generate events. Accessories with multiple characteristics (e.g., a sensor with both contact state and motion) show individual toggles so you can choose exactly which state changes fire webhooks. Battery-related characteristics are automatically excluded.
Note: Trigger creation is GUI-only. The CLI can list and manage existing triggers (homeclaw-cli triggers list,triggers remove), but new triggers must be created in the HomeClaw app's Settings > Webhook tab.
6. Test End-to-End
# Verify HomeClaw is connected and webhook is healthy
homeclaw-cli status
Toggle a light from the Home app, then check events
homeclaw-cli events --since 5m
Check webhook delivery log
homeclaw-cli webhook-log
Check HomeClaw delivery logs
log show --predicate 'process == "HomeClaw" AND category == "webhook"' --last 5m --style compact
Circuit Breaker
The webhook system includes a tiered circuit breaker that prevents runaway delivery failures from hammering a down endpoint, while ensuring critical events are never silently dropped.
| State | Trigger | Behavior | Recovery |
|-------|---------|----------|----------|
| Normal | -- | All webhooks delivered | -- |
| Soft Open | 5 consecutive failures | Non-critical paused | Auto-resumes after 5 minutes |
| Hard Open | 3 soft trips without any success | All non-critical stopped | Reset button in Settings, --webhook-reset CLI, or toggle off/on |
Critical triggers (critical: true) always attempt delivery regardless of circuit state.
The circuit state is visible in:
- Menu bar -- warning icon when paused or disabled
- Settings > Webhook -- orange (paused) or red (disabled) banner with countdown
- CLI --
homeclaw-cli statusshows circuit state, dropped count, and recovery hint
How Events Flow
Home app / physical device / Siri
|
v
HomeKit (HMAccessoryDelegate callback)
|
+-- Cache warmup? --> Update cache only (no logging, no webhooks)
|
+-- Battery event? --> Update cache only (silently dropped)
|
v
HomeClaw event logger (writes to events.jsonl)
|
+-- Trigger matches? --> POST /hooks/homeclaw
|
+-- No trigger --> Logged to disk only (no webhook sent)
v (trigger matched)
OpenClaw gateway validates Bearer token
|
v
hooks.mappings resolves "homeclaw"
|
v
HomeClaw Agent (dedicated)
+-- Classifies: CRITICAL / NOTABLE / AMBIENT
+-- If notable/critical: a2a to main agent
+-- If ambient: log to agent memory only
Authentication
Uses Authorization: Bearer with idempotency headers (X-Request-ID, X-Event-Timestamp). See SKILL.md for the full trigger fields reference, scenario cookbook, and troubleshooting.
Device Filtering
Use the Devices tab in Settings or the CLI to control which accessories are exposed:
```bash homeclaw-cli config --filter-mode allowlist homeclaw-cli confi
... (README truncated for length)