Profile
Back to NewsBack
GitHub Trending 34 min
Reader Mode
omarshahine/HomeClaw: HomeKit smart home control via MCP — lights, locks, thermostats, and scenes for Claude Desktop, Claude Code, and OpenClaw

omarshahine/HomeClaw: HomeKit smart home control via MCP — lights, locks, thermostats, and scenes for Claude Desktop, Claude Code, and OpenClaw

4 hours ago

HomeClaw icon

HomeClaw

Control your Apple HomeKit smart home from AI assistants, the terminal, and automation tools.

Download on the Mac App Store Website MIT License

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 — HomeKit homes and accessories overview
Settings — every preference in one place

CLI — list every accessory in one command
CLI — every accessory, one command away

Terminal UI — accessory tree with live status
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:

Download on the Mac App Store

  1. Open in the Mac App Store and install
  2. Launch HomeClaw -- grant HomeKit access when prompted
  3. The menu bar icon appears. Click it to see your connected homes.
App Store builds are fully signed and notarized, so HomeKit works without any developer account setup.

TestFlight Beta

Want early access to in-flight builds before they ship to the App Store?

  1. Join the TestFlight Beta
  2. Install HomeClaw from TestFlight
  3. Launch the app -- grant HomeKit access when prompted
TestFlight builds are signed the same way as App Store builds, so HomeKit works out of the box. You'll get new features (like the automations work in v1.0.1) before they hit the App Store.

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 through HMHomeManager, which requires the com.apple.developer.homekit entitlement 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 warnings entry 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 emit service_name, service_id, and service_index on rows whose accessory has more than one service carrying that property, so their JSON round-trips through import-scene / update-scene without 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:

  1. Open Settings > Integrations and click Install in the OpenClaw section.
This handles all four steps automatically: installs the plugin, enables it, symlinks 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.

HomeKit settings tab

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.

Devices settings tab

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

Integrations settings tab

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
Or configure via CLI:
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/wake silently drops events (#33271). HomeClaw uses mapped webhooks (/hooks/homeclaw) which route through hooks.mappings and 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, the homeclaw plugin must be registered (/plugin install homeclaw@homeclaw). The homeclaw-cli binary 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 if homeclaw agent 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.json for a hooks.mappings.homeclaw entry
- If missing, add:
> "hooks": {
> "enabled": true, > "token": "${HOMECLAW_WEBHOOK_TOKEN}", > "mappings": { > "homeclaw": { > "agentId": "homeclaw", > "sessionKey": "hook:homeclaw", > "deliver": true, > "channel": "last", > "allowUnsafeExternalContent": true > } > } > } >
- Create a transform at ~/.openclaw/hooks/transforms/homeclaw-transform.js to convert HomeClaw state-change payloads into agent messages
- Check ~/.openclaw/.env for HOMECLAW_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.enabled is true, webhook.url is http://127.0.0.1:18789, webhook.token matches the HOMECLAW_WEBHOOK_TOKEN from step 2, and webhook_endpoint is /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 is closed and last_success is 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 (not install) is the correct CLI command. The --workspace flag 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 update total_delivered and last_http_status in the circuit breaker stats. After running --webhook-test, check homeclaw-cli config --json to 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 status shows 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)

Chat with me