Liu Terminal notifications. Get notified when AI agents finish tasks, with configurable hooks and sound alerts.
liu-notify is an optional standalone process, while the Liu GUI can host the same server in-process. It accepts a v1 24-byte header or the current v2 28-byte header with optional metadata over a local socket, throttles with a token bucket, dedups identical events, and routes a sound (or spoken line) per (agent, event) pair.
# Build alongside the main app
cmake --build build --target liu-notify
# Start the daemon
./build/liu-notify daemon
# Send a one-shot event
./build/liu-notify send --tool=claude --event=stop
Config lives at ~/.config/liu/notify.conf (or $XDG_CONFIG_HOME/liu/notify.conf) — separate from the main app's config.json so the daemon stays usable without Liu itself running.
When Liu is running, its GUI hosts the notification server in-process; the CLI does not need to start a second daemon. If no server is listening, send exits without delivering an event. Use the standalone daemon only when notifications must work without Liu.
Liu can install and remove lifecycle hooks for the agents below while the app is running. The hook is written into the agent’s own configuration, and Liu removes its managed entry when the hosting instance exits.
| Agent | Hook surface | Signals | Notes |
|---|---|---|---|
| Claude Code | ~/.claude/settings.json | start · notify · complete | UserPromptSubmit drives the working state; Notification and Stop can alert you. |
| Codex | ~/.codex/config.toml | complete | The Stop hook reports a completed turn. Run /hooks in Codex once to trust it. |
| Grok | ~/.grok/hooks/liu-notify.json | notify · complete | Notification and top-level Stop events are handled; sub-agent completions stay silent. |
| OpenCode | ~/.config/opencode/plugins/liu-notify.js | complete | The managed plugin listens for top-level session.idle events. |
| Antigravity | ~/.gemini/config/hooks.json | complete | A named Stop hook reports when the top-level execution loop ends. |
| Command Code | ~/.commandcode/settings.json | complete | Its Claude-compatible settings schema is wired to the top-level Stop event. |
Copilot and custom are accepted by the low-level notification protocol and sound matrix, but Liu does not install an automatic lifecycle hook for them yet. Other agents still run normally in Liu tabs; they simply do not receive automatic hook notifications.
Configure a sound rule for any combination. Unset cells fall through to the catch-all.
| start | stop | notify | error | complete | |
|---|---|---|---|---|---|
| claude | · | · | · | · | · |
| copilot | · | · | · | · | · |
| codex | · | · | · | · | · |
| custom | · | · | · | · | · |
The v1 header is 24 bytes; the current v2 header is 28 bytes because it appends meta_len. The complete frame is capped at 4096 bytes. Validation is fuzzed by tests/fuzz_notify_validate.c so malformed frames fail closed without taking the server down.
offset size field
0 4 magic ('N','T','F','Y')
4 2 version (1 or current 2)
6 2 flags (speak=1, bell=2)
8 1 tool_id (claude=1, copilot=2, codex=3, custom=255)
9 1 event_id (start=1, stop=2, notify=3, error=4, complete=5)
10 1 priority (reserved)
11 1 source (cli=0, stdin=1, test=2)
12 4 seq (client nonce — for dedup)
16 4 title_len (<= 256)
20 4 body_len (<= 3072)
24 4 meta_len (v2 only, <= 512)
payload title, body, then metadata key=value lines
total frame <= 4096 bytes
The v2 metadata section currently carries cwd=, session= and agent= lines. Hook senders use it to attribute an event to the correct pane; unknown keys are ignored for forward compatibility.
Same frame, different speaker. macOS uses AVSpeechSynthesizer and Linux shells out to notify-send. Each back-end lives in its own file (platform_notify_macos.m, platform_notify_linux.c) so swapping or stubbing one is a single-file change.
Türkçe · 简体中文 · 한국어 · Español · Deutsch · Italiano · Français · 日本語 · العربية