You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Replace auto-firing Notification/Stop hooks with a skill-driven model: Claude
runs the notifier itself (bin/speak.sh) right before it asks a question and once
at the end of a substantive turn, so the spoken line reflects what it did/needs.
- New default templates: "Hi, this is Claude. Project <name> needs you/is done. <body>"
(en + vi presets), with empty-body collapse in sag_render.
- Add bin/speak.sh (agent CLI: `needs-you`/`done` + body) reusing lib.sh; move
hooks/lib.sh -> bin/lib.sh; remove hooks.json, notify.sh, summary.sh.
- Drop now-unused summary_file config key (body is passed as a CLI arg).
- Rewrite sag-voice SKILL.md with the proactive protocol; refresh README,
setup/config/test commands; marketplace type hook -> skill.
BREAKING CHANGE: notifications no longer fire from hooks; enable the sag-voice
skill so Claude speaks proactively.
Co-Authored-By: duyetbot <duyetbot@users.noreply.github.com>
Copy file name to clipboardExpand all lines: .claude-plugin/marketplace.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -87,7 +87,7 @@
87
87
{
88
88
"name": "sag-notify",
89
89
"source": "./sag-notify",
90
-
"description": "Spoken voice notifications via sag (ElevenLabs TTS). Speaks a short alert when Claude needs you and a per-turn summary when work finishes, naming the project. Auto-injected hooks; configurable voice, language, and templates."
90
+
"description": "Spoken voice notifications via sag (ElevenLabs TTS). Skill-driven: Claude speaks a short alert just before it asks you something and a one-line summary when work finishes, naming the project. Configurable voice, language, and templates."
Copy file name to clipboardExpand all lines: marketplace.json
+3-3Lines changed: 3 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -150,9 +150,9 @@
150
150
{
151
151
"name": "sag-notify",
152
152
"id": "sag-notify@duyet-claude-plugins",
153
-
"description": "Spoken voice notifications via sag (ElevenLabs TTS): a short alert when Claude needs you and a per-turn summary when work finishes, naming the project. Configurable voice, language, and templates.",
154
-
"version": "1.0.0",
155
-
"type": "hook",
153
+
"description": "Spoken voice notifications via sag (ElevenLabs TTS): skill-driven, Claude speaks a short alert just before it asks you something and a one-line summary when work finishes, naming the project. Configurable voice, language, and templates.",
Copy file name to clipboardExpand all lines: sag-notify/.claude-plugin/plugin.json
+2-2Lines changed: 2 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
{
2
2
"name": "sag-notify",
3
-
"version": "1.1.0",
4
-
"description": "Spoken voice notifications via sag (ElevenLabs TTS). Speaks a short alert when Claude needs you, and a per-turn summary when work finishes — naming the project. Auto-injected hooks; configurable voice, multi-language presets, and message templates.",
3
+
"version": "2.0.0",
4
+
"description": "Spoken voice notifications via sag (ElevenLabs TTS). Skill-driven: Claude speaks a short alert just before it asks you something, and a one-line summary when work finishes — naming the project. Configurable voice, multi-language presets, and message templates.",
Copy file name to clipboardExpand all lines: sag-notify/README.md
+14-12Lines changed: 14 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,36 +2,38 @@
2
2
3
3
Spoken voice notifications for Claude Code via [`sag`](https://github.com/steipete/sag) (ElevenLabs TTS).
4
4
5
-
-**Needs you** → when Claude waits for permission/input, it speaks a short alert naming the project.
6
-
-**Turn done** → when Claude finishes substantive work, it speaks a one-line summary you author.
5
+
-**Needs you** → just before Claude asks you a question, it speaks a short alert naming the project.
6
+
-**Turn done** → when Claude finishes substantive work, it speaks a one-line summary of what it did.
7
7
8
-
Both are driven by **auto-injected hooks** — once the plugin is enabled, every session gets them, no per-session setup.
8
+
Both are **skill-driven**: Claude runs `sag` itself at the right moment (via the bundled `bin/speak.sh`), so the spoken line reflects what it actually did or needs. There are no auto-firing hooks — the **sag-voice** skill teaches Claude when and how to speak.
9
9
10
10
## Requirements
11
11
12
12
The `sag` CLI must be installed and authenticated. Install with `brew install steipete/tap/sag` and set `ELEVENLABS_API_KEY`. Full guide: [skills/sag-voice/references/sag-cli.md](skills/sag-voice/references/sag-cli.md).
13
13
14
14
## How it works
15
15
16
-
| Event | Hook | Behavior |
17
-
|-------|------|----------|
18
-
|`Notification`|`hooks/notify.sh`| Speaks the notification template, naming the project, in the configured language. (The harness message is English, so it is replaced by the template.) |
19
-
|`Stop`|`hooks/summary.sh`| Reads the body Claude wrote to `summary_file`, speaks it via the summary template, deletes the file. **Silent when no file exists**, so trivial turns are quiet. |
16
+
Claude calls one helper at two moments:
20
17
21
-
To make Claude speak a summary, it writes a short body to `~/.claude/.sag-summary` during a substantive turn. The hook adds the `This is <name>, reporting from project <name>:` framing automatically.
18
+
| Moment | Command | Speaks (default) |
19
+
|--------|---------|------------------|
20
+
| Right before asking you (e.g. before `AskUserQuestion`) |`bin/speak.sh needs-you "<reason>"`|`Hi, this is Claude. Project <name> needs you. <reason>`|
21
+
| End of a substantive turn |`bin/speak.sh done "<summary>"`|`Hi, this is Claude. Project <name> is done. <summary>`|
22
+
23
+
`bin/speak.sh` reads your config, names the project from `$PWD`, renders the configured template, and speaks in the background. It exits silently when `sag`/`jq`/the API key are missing or the plugin is disabled, so it never blocks a turn. Trivial turns stay quiet because Claude simply doesn't call it.
22
24
23
25
## Setup
24
26
25
27
1. Install + authenticate `sag` (see Requirements).
26
-
2. Enable the plugin (it ships `hooks/hooks.json`).
28
+
2. Enable the plugin and the **sag-voice** skill.
27
29
3. Run `/sag-notify:setup` — checks `sag`/`jq`, resolves the API key, picks a voice, writes config, and tests audio.
28
30
4. Or just rely on defaults: Brian voice, English templates, key from the `ELEVENLABS_API_KEY` environment variable.
29
31
30
32
`ELEVENLABS_API_KEY` must be resolvable — from the environment, or from an optional key file you point `key_file` at in your user config.
31
33
32
34
## Configuration
33
35
34
-
User config at `~/.config/sag-notify/config.json` overrides [`config.default.json`](config.default.json). See the **sag-voice** skill or run `/sag-notify:config`. Key settings: `enabled`, `events.{notification,summary}`, `voice_id`, `model_id`, `self_name`, `language`, `key_file`, `summary_file`, `error_log`, `max_chars`, `templates.{notification,summary}`, `languages.<lang>.{notification,summary}`.
36
+
User config at `~/.config/sag-notify/config.json` overrides [`config.default.json`](config.default.json). See the **sag-voice** skill or run `/sag-notify:config`. Key settings: `enabled`, `events.{notification,summary}`, `voice_id`, `model_id`, `self_name`, `language`, `key_file`, `error_log`, `max_chars`, `templates.{notification,summary}`, `languages.<lang>.{notification,summary}`.
-`/sag-notify:test` — fire both hooks and check `~/.claude/.sag-error.log`
54
+
-`/sag-notify:test` — fire both notification lines and check `~/.claude/.sag-error.log`
53
55
54
56
## Troubleshooting
55
57
56
-
Silent? Check `~/.claude/.sag-error.log` (the hooks log stderr there, never `/dev/null`). A `402` means a paid voice; switch to a premade one. Verify real audio in the **foreground** — the hooks background the call so their exit code can't tell you if sound played.
58
+
Silent? Check `~/.claude/.sag-error.log` (errors are logged there, never `/dev/null`). A `402` means a paid voice; switch to a premade one. Verify real audio in the **foreground** — `bin/speak.sh` backgrounds the call so its exit code can't tell you if sound played.
Copy file name to clipboardExpand all lines: sag-notify/commands/setup.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ Walk the user through setup, doing the work for them where possible:
8
8
9
9
1.**Check `sag` and `jq`** are installed: `command -v sag jq`. If `sag` is missing, point them at the install guide [skills/sag-voice/references/sag-cli.md](../skills/sag-voice/references/sag-cli.md) (`brew install steipete/tap/sag`) and stop.
10
10
11
-
2.**Resolve the API key.** Check `ELEVENLABS_API_KEY` in the environment. If absent, instruct the user to add it themselves (do NOT ask them to paste the secret in chat). Recommend their shell env file so non-interactive hook shells inherit it:
11
+
2.**Resolve the API key.** Check `ELEVENLABS_API_KEY` in the environment. If absent, instruct the user to add it themselves (do NOT ask them to paste the secret in chat). Recommend their shell env file so non-interactive shells (where `bin/speak.sh` runs) inherit it:
@@ -26,4 +26,4 @@ Walk the user through setup, doing the work for them where possible:
26
26
```
27
27
Empty/`OK` = success. A 402 means the voice needs a paid plan — go back to step 4.
28
28
29
-
7. Confirm the hooks are active and explain that summaries only speak when Claude writes a body to the configured `summary_file`.
29
+
7. Confirm the **sag-voice** skill is enabled and explain the model: Claude itself runs `${CLAUDE_PLUGIN_ROOT}/bin/speak.sh` — `needs-you` right before it asks a question, and `done` once at the end of a substantive turn. There are no auto-firing hooks, so trivial turns stay silent.
0 commit comments