1
0
Fork 0
mirror of https://github.com/imjasonh/cnotes synced 2026-07-18 23:01:31 +00:00

Add notification hook with macOS speech synthesis

- Speaks Claude notifications using the 'say' command
- Handles both permission requests and regular notifications
- Only runs on macOS systems
- Non-blocking execution to avoid delays

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jason Hall 2025-07-21 10:17:58 -04:00
parent 2e7a2badba
commit f36813078c
Failed to extract signature
3 changed files with 76 additions and 0 deletions

View file

@ -0,0 +1,72 @@
package handlers
import (
"context"
"fmt"
"log/slog"
"os/exec"
"runtime"
"strings"
"github.com/imjasonh/hooks/internal/hooks"
)
func init() {
hooks.RegisterHook(hooks.EventNotification, "*", SpeakNotification)
}
func SpeakNotification(ctx context.Context, input hooks.HookInput) (hooks.HookOutput, error) {
// Only run on macOS
if runtime.GOOS != "darwin" {
return hooks.HookOutput{Decision: "continue"}, nil
}
// Check if say command is available
if _, err := exec.LookPath("say"); err != nil {
slog.Debug("say command not found", "error", err)
return hooks.HookOutput{Decision: "continue"}, nil
}
// Build the message to speak
var message string
if input.Notification.Permission != "" {
// Permission request
message = fmt.Sprintf("Claude is requesting permission to use %s. %s",
input.Notification.Tool,
input.Notification.Message)
} else {
// Regular notification
message = fmt.Sprintf("Notification from %s: %s",
input.Notification.Tool,
input.Notification.Message)
}
// Sanitize the message for speech
message = strings.ReplaceAll(message, "\n", " ")
message = strings.ReplaceAll(message, "\"", "'")
// Run say command with a shorter message if it's too long
if len(message) > 200 {
message = message[:197] + "..."
}
cmd := exec.CommandContext(ctx, "say", "-v", "Samantha", message)
if err := cmd.Start(); err != nil {
slog.Error("failed to start say command", "error", err)
return hooks.HookOutput{Decision: "continue"}, nil
}
// Don't wait for completion to avoid blocking
go func() {
if err := cmd.Wait(); err != nil {
slog.Debug("say command failed", "error", err)
}
}()
slog.Info("speaking notification",
"tool", input.Notification.Tool,
"permission", input.Notification.Permission != "",
"message_length", len(message))
return hooks.HookOutput{Decision: "continue"}, nil
}