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

171 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: notify
description: 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.
trigger_words:
- 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`
```bash
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:
```bash
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`
```bash
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.