Files
ai-agent/.claude/skills/notify/SKILL.md
Gabriel Vidal 7ac7fe6c17 feat(worker): generic Mac installer (cwd + account prompts) and opt-in autoRoute per worker
The worker installer no longer assumes the Orus work seat. It asks for the
default working dir — the directory it is run from, confirmed with `y`, or a
typed path (taken as is without a terminal) — and for the hub-side account,
proposed from `claude auth status` (a gmail login → personal, else work).
`--update` keeps the installed agent's cwd and account. Remote Control stays
off on every worker.

Pairing a personal Mac on `personal` would have taken every personal spawn
while it was awake (for_account), so workers now carry an `autoRoute` flag:
off at pairing (the lab stays the runner; the worker only gets what the
composer's Runs-on chip pins to it), toggled from the Settings → Workers card
or the pairing dialog. Records from before the flag keep auto-routing.

Docs genericized (worker README, CLAUDE.md Workers, notify SKILL, GOAL).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 16:41:26 +02:00

8.6 KiB
Raw Permalink Blame History

name, description, trigger_words
name description trigger_words
notify Reach Gabriel from an agent session through the ai-agent hub — a push notification to his phone (notify send), a 2-3-button choice he taps (notify ask), or a structured form he fills in the viewer (notify form) — and collect the answer without blocking the turn (notify wait). Works identically on the lab host and on a remote worker. Use when work is finished, when the user wants to be pinged, or when you need a decision or details from him while he may be away from the terminal.
notify
notify me
ping me
let me know when
ask me
clarifying questions

notify — push · ask · form, through the hub

One CLI, cli/notify.py in the ai-agent repo, is how a session talks to Gabriel. Every call goes to the ai-agent hub, the notification source of truth: it records the event, links it to this conversation, forwards it to Home Assistant (phone push + the desk phone), and takes the answer back.

command what Gabriel gets you get back
notify send [TITLE] MESSAGE a push (+ the desk phone reads it) nothing — fire and forget
notify ask "Q?" "A" "B" ["C"] a push with 2–3 tappable buttons the chosen label
notify form --title … --fields … a form inline in the conversation the answers as JSON
notify wait <id> — the answer of an ask_… / frm_…
notify cancel <frm_id> the form is dismissed —

The old names still work and mean exactly the same thing: notify-done = notify send, notify-ask = notify ask, ask-form = notify form (with its own wait / cancel). Pick whichever the task's guidelines name.

Where it runs

The CLI is stdlib Python, and the sidecar puts cli/ on every run's PATH, so it is available wherever the hub launched the session:

  • On the lab host it POSTs to the hub directly (AI_AGENT_URL, default http://127.0.0.1:8096).
  • On a remote worker (a paired Mac) the sidecar exports AI_AGENT_OUTBOX_URL: the call is queued in that sidecar's outbox and the hub — which polls every paired worker every ~2 s — collects it, records it, forwards it to the phone and pushes the answer back. The worker never calls the hub. Latency is a couple of seconds; wait behaves the same.

Ids (ntf_…, ask_…, frm_…) are minted by the CLI, so the id printed on a worker is the id the hub, the phone and the viewer use.

1. Push — notify send

notify send "Deploy finished ✅"                                  # title defaults to "Claude Code"
notify send "homelab" "Traefik reloaded, all services up ✅" --type service
notify send "gabvdl deployed ✅" "live now" --type deploy --url https://www.dev.gabvdl.xyz
notify send "Task done ✅" "PR opened for review" --type git --final \
  --say "La PR est ouverte, tout est prêt pour la relecture."
  • --url is optional. Omitted, the hub links this conversation in the viewer (it resolves the session's transcript itself — worker sessions included). Pass a more relevant page when there is one (a deployed site, a dashboard, a kanban ticket).
  • --type picks the icon, colour and Android channel: deploy code build git service ai music media research logs dns auth mail success error warn backup. --channel / --importance / --vibration override a single push.
  • --final marks the task's closing push: the hub stamps the session finished (what the terminal's DONE line means). Use it on the last notification of a task, not on progress pings.
  • --say / --spoken — one short, factual, hopeful French sentence the desk phone reads aloud instead of the screen text (Pocket-TTS, no emotion markup: the tone is in the wording).
  • --image <url|/local/file> shows a picture. A --type deploy push with a public --url auto-attaches that site's /og-image.png when it answers 200 (lab hosts are skipped — the phone can't validate their certs; HA_NOTIFY_NO_OG=1 disables it).
  • --action-cmd "Title::shell command" adds a button; tapping it runs the command (a detached waiter long-polls the hub for up to 6 h). This is how commit-project's Merge to main button works.
  • The cost line (Claude Code · $1.57 · 78k tokens · 8m 38s) is appended automatically from this session's transcript; --no-cost drops it.

2. Choice — notify ask

For a decision Gabriel can make with one tap. The first arg is one sentence that names every option; the rest are the button labels (2 or 3, short verbs: Rebuild, Skip, Cancel — Android shows at most 3).

Never block the turn. Send with --background, arm a Monitor on the wait, end the turn; the answer wakes you:

notify ask --background \
  "Where should I deploy the build: staging, production, or cancel?" \
  "Staging" "Production" "Cancel" --type deploy --timeout 900
# → ASK_ID=ask_4f2c91ab77de
Monitor: command = "notify wait ask_4f2c91ab77de", persistent = true,
         description = "waiting for the deploy-target choice"
  • notify wait <ask_id> prints the chosen label and exits 0 the moment a button is tapped; 3 when the ask expires (--timeout, default 180 s — give a backgrounded ask a generous one).
  • While pending, the conversation sits in the viewer's Waiting for feedback section, answerable from the card as well as from the phone.
  • When the answer lands, TaskStop the monitor if still armed. If you stop waiting, TaskStop it too — the ask expires on its own.
  • Without --background the call waits inline and prints the label — fine in a shell script, wrong in an agent turn.
  • Only for questions that genuinely block the work while Gabriel is plausibly away. For anything richer than 2–3 buttons, use a form.

3. Form — notify form

notify form --title "Project setup" --description "A few details before I scaffold." \
  --type ai --fields '[
    {"type":"markdown","label":"Context","text":"I found **two** candidate stacks."},
    {"key":"name","label":"Project name","type":"text","required":true},
    {"key":"stack","label":"Stack","type":"select","required":true,
     "options":["Vite + React","Next.js","Astro"]},
    {"key":"features","label":"Features","type":"multiselect",
     "options":[{"value":"auth","label":"Auth"},{"value":"db","label":"Database"}]},
    {"key":"deploy","label":"Deploy after scaffold","type":"checkbox"},
    {"key":"polish","label":"Polish level","type":"slider","min":1,"max":5,"default":3},
    {"key":"logo","label":"Logo","type":"file","accept":"image/*"},
    {"key":"deadline","label":"Deadline","type":"date"}
  ]'
# → FORM_ID=frm_…  + the focus URL; exits immediately

Field types: text textarea number select multiselect radio checkbox slider file date, plus markdown display blocks ({"type":"markdown","text":"…"}, optional label heading). Options are strings or {value,label}. Description, help, labels and options render markdown. --fields-file <path> or JSON on stdin for big specs.

The hub pushes the "📋 title" notification itself, deep-linked to the form (?form=<id> opens it full-screen in the PWA). Then, as for an ask:

Monitor: command = "notify wait frm_XXXX", persistent = true,
         description = "waiting for form answers"
  • wait prints one JSON line keyed by field key on submit (exit 0); cancelled ⇒ 3; --timeout N reached ⇒ 4 (the form stays pending — notify cancel <id> it or keep waiting).
  • multiselect answers are arrays of option values; checkbox a boolean; file answers [{name, repoPath, url}] (the upload lands under services/ai-agent/data/uploads/).
  • Keep working on everything that doesn't depend on the answers; fold them in when they land. A stale pending form sits in Waiting for feedback forever, so cancel what you give up on.
  • The same field spec is what the plan skill embeds under ## Questions.

Notes

  • notify cost prints the cost line alone; --dry-run on send / ask / form prints the exact payload instead of sending.
  • Env: AI_AGENT_URL (hub), AI_AGENT_OUTBOX_URL + AI_AGENT_OUTBOX_TOKEN (set by a worker's sidecar), CLAUDE_SESSION_ID (set by the sidecar for every run; the lab's shims resolve it otherwise), HA_NOTIFY_TYPE / HA_NOTIFY_URL / HA_NOTIFY_IMAGE / HA_NOTIFY_SPOKEN as flag defaults.
  • There is no direct Home-Assistant fallback any more: if the hub is down the call fails loudly (exit 1) rather than pushing twice later.
  • Claude Code's interactive AskUserQuestion is disabled for spawned sessions — ask and form are its replacement.