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

Switch to terminal-notifier for visual notifications

- Replaced go-toast with terminal-notifier for better reliability
- Visual notifications now require: brew install terminal-notifier
- Speech notifications continue to work with built-in say command
- Cleaner implementation with no deprecation warnings

🤖 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:26:59 -04:00
parent f36813078c
commit 9cfbafb584
Failed to extract signature
4 changed files with 67 additions and 40 deletions

View file

@ -21,52 +21,73 @@ func SpeakNotification(ctx context.Context, input hooks.HookInput) (hooks.HookOu
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
// Build the notification content
var title, message, subtitle string
if input.Notification.Permission != "" {
// Permission request
message = fmt.Sprintf("Claude is requesting permission to use %s. %s",
input.Notification.Tool,
input.Notification.Message)
title = "Claude Permission Request"
subtitle = fmt.Sprintf("Tool: %s", input.Notification.Tool)
message = fmt.Sprintf("Requesting permission to %s", input.Notification.Message)
} else {
// Regular notification
message = fmt.Sprintf("Notification from %s: %s",
input.Notification.Tool,
input.Notification.Message)
title = "Claude Notification"
subtitle = fmt.Sprintf("Tool: %s", input.Notification.Tool)
message = 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)
// Show notification using terminal-notifier if available
if _, err := exec.LookPath("terminal-notifier"); err == nil {
args := []string{
"-title", title,
"-subtitle", subtitle,
"-message", message,
"-sound", "default",
"-group", "claude-hooks",
}
}()
cmd := exec.CommandContext(ctx, "terminal-notifier", args...)
if err := cmd.Start(); err != nil {
slog.Error("failed to show notification", "error", err)
} else {
// Don't wait for completion
go func() {
if err := cmd.Wait(); err != nil {
slog.Debug("terminal-notifier failed", "error", err)
}
}()
slog.Info("showed notification",
"tool", input.Notification.Tool,
"permission", input.Notification.Permission != "")
}
} else {
slog.Debug("terminal-notifier not found, install with: brew install terminal-notifier")
}
slog.Info("speaking notification",
"tool", input.Notification.Tool,
"permission", input.Notification.Permission != "",
"message_length", len(message))
// Also speak the notification if say is available
if _, err := exec.LookPath("say"); err == nil {
spokenMessage := fmt.Sprintf("%s. %s", subtitle, message)
// Sanitize for speech
spokenMessage = strings.ReplaceAll(spokenMessage, "\n", " ")
spokenMessage = strings.ReplaceAll(spokenMessage, "\"", "'")
// Truncate if too long
if len(spokenMessage) > 200 {
spokenMessage = spokenMessage[:197] + "..."
}
cmd := exec.CommandContext(ctx, "say", "-v", "Samantha", spokenMessage)
if err := cmd.Start(); err != nil {
slog.Debug("failed to start say command", "error", err)
} else {
// Don't wait for completion to avoid blocking
go func() {
if err := cmd.Wait(); err != nil {
slog.Debug("say command failed", "error", err)
}
}()
}
}
return hooks.HookOutput{Decision: "continue"}, nil
}