cli/notify.py merges the lab's notify-done, notify-ask and ask-form scripts
into one stdlib-only CLI shipped with this repo (the aliases stay as shims),
so a session on a remote worker has it too. Two transports: the hub directly
on the lab, or — on a worker — the sidecar's new /outbox, which the hub's
OutboxMirror polls every 2 s, creating the record through the same functions
/api/notify, /api/ask and /api/forms run and pushing the answer back. The
worker still never calls the hub.
The hub now owns what the CLI used to compute: the default conversation URL
(worker transcripts included), the `notified` meta stamp, and a `--final`
push marks the session finished. A push's --action-cmd button answers on
/api/notify/{id}/action like an ask (HA integration updated). Ids are minted
by the CLI (ntf_/ask_/frm_) so viewer, phone and worker agree. Dropped: the
direct-HA fallback, the DONE button, the Forge cost line, the dashboard
mirror. The viewer's widgets also parse the `notify send|ask|form|wait`
spelling; the worker installer links the skill into ~/.claude/skills.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ai_agent — Home Assistant custom integration
The Home Assistant side of the ai-agent notification hub. It registers a
webhook (/api/webhook/ai_agent) that the ai-agent backend forwards its
notify and ask events to, and turns them into Android companion-app
notifications:
- notify — a normal push on the per-type channel (
Claude · Deploy,Claude · Error, …) with the same icon/color/vibration map the notify-done skill used to apply client-side. Explicit payload fields (icon,channel,vibrationPattern, …) always win over the type defaults. - ask — a persistent notification on the dedicated
Claude · Askchannel with the ". .. .._" morse vibration pattern (0, 100, 350, 100, 120, 100, 350, 100, 120, 100, 120, 450) and one action button per option. When a button is tapped the integration POSTs{"index": n}back to the backend (POST /api/ask/{id}/answer) and clears the notification from the phone. Whatever is long-pollingGET /api/ask/{id}?waitSecs=…(normallyask.sh) then unblocks with the chosen label.
Android locks a channel's importance/vibration when the channel is first
created — to re-tune the ask buzz, rename ASK_CHANNEL in const.py (or
clear the companion app's storage).
The voice leg (phone_service)
A notify has two destinations in the homelab: the screen (the Pixel push above)
and the AI desk phone (services/phone, a Yealink that reads updates aloud
over an auto-answer call). Both go through this one webhook: when
phone_service: <domain>.<service> is configured, every notify is also
handed to that HA service with the phone-relevant fields —
{"spoken": …, "type": …, "title": …, "message": …, "audio": …}
— non-blocking and best-effort, so a ringing (or absent) phone never delays the
push nor fails the hub's webhook. The homelab points it at
script.ai_agent_phone_notify (services/home-assistant/config/packages/ai_agent_phone.yaml),
which POSTs to the phone bridge; that script is where HA-side routing policy
belongs. Leave phone_service unset for screen-only notifications.
Asks have no voice leg on purpose — answering one means tapping a button.
Events on the bus
Every handled payload is also fired on the HA event bus as ai_agent_notify /
ai_agent_ask (the raw webhook JSON as event data), so automations can react
to agent activity — flash a light on type: error, count deploys, mirror asks
to another device — without re-implementing the webhook.
Install
./install.sh --restart # copy component + config block, restart HA
HA_CONFIG overrides the target config dir (default:
services/home-assistant/config). The config block it adds:
ai_agent:
webhook_id: ai_agent # -> POST /api/webhook/ai_agent
notify_target: mobile_app_pixel_9 # notify.<target>
callback_url: http://127.0.0.1:8096 # ai-agent backend (host-published port)
phone_service: script.ai_agent_phone_notify # voice leg (see below)
HA runs on the host network, so 127.0.0.1:8096 reaches the ai-agent
container's published port; the backend's trusted-caller gate accepts the
connection because it arrives from the docker gateway.
Verify
docker logs home-assistant 2>&1 | grep ai_agent # "ai_agent ready: …"
curl -s -X POST http://127.0.0.1:8123/api/webhook/ai_agent \
-H 'Content-Type: application/json' \
-d '{"event":"notify","title":"test","message":"hello","type":"success"}'