From 2e2b2187b957e9af78797a67ec5c6874615fae02 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sun, 8 Feb 2026 21:35:45 -1000 Subject: Initial project: task model, executor, API server, CLI, storage, reporter Claudomator automation toolkit for Claude Code with: - Task model with YAML parsing, validation, state machine (49 tests, 0 races) - SQLite storage for tasks and executions - Executor pool with bounded concurrency, timeout, cancellation - REST API + WebSocket for mobile PWA integration - Webhook/multi-notifier system - CLI: init, run, serve, list, status commands - Console, JSON, HTML reporters with cost tracking Co-Authored-By: Claude Opus 4.6 --- internal/notify/notify.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 internal/notify/notify.go (limited to 'internal/notify/notify.go') diff --git a/internal/notify/notify.go b/internal/notify/notify.go new file mode 100644 index 0000000..86e641f --- /dev/null +++ b/internal/notify/notify.go @@ -0,0 +1,95 @@ +package notify + +import ( + "bytes" + "encoding/json" + "fmt" + "log/slog" + "net/http" + "time" +) + +// Notifier sends notifications when tasks complete. +type Notifier interface { + Notify(event Event) error +} + +// Event represents a task completion event. +type Event struct { + TaskID string `json:"task_id"` + TaskName string `json:"task_name"` + Status string `json:"status"` + CostUSD float64 `json:"cost_usd"` + Duration string `json:"duration"` + Error string `json:"error,omitempty"` +} + +// WebhookNotifier sends POST requests to a configured URL. +type WebhookNotifier struct { + URL string + client *http.Client + logger *slog.Logger +} + +func NewWebhookNotifier(url string, logger *slog.Logger) *WebhookNotifier { + return &WebhookNotifier{ + URL: url, + client: &http.Client{Timeout: 10 * time.Second}, + logger: logger, + } +} + +func (w *WebhookNotifier) Notify(event Event) error { + body, err := json.Marshal(event) + if err != nil { + return fmt.Errorf("marshaling event: %w", err) + } + + resp, err := w.client.Post(w.URL, "application/json", bytes.NewReader(body)) + if err != nil { + return fmt.Errorf("sending webhook: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return fmt.Errorf("webhook returned %d", resp.StatusCode) + } + return nil +} + +// MultiNotifier fans out to multiple notifiers. +type MultiNotifier struct { + notifiers []Notifier + logger *slog.Logger +} + +func NewMultiNotifier(logger *slog.Logger, notifiers ...Notifier) *MultiNotifier { + return &MultiNotifier{notifiers: notifiers, logger: logger} +} + +func (m *MultiNotifier) Notify(event Event) error { + var lastErr error + for _, n := range m.notifiers { + if err := n.Notify(event); err != nil { + m.logger.Error("notification failed", "error", err) + lastErr = err + } + } + return lastErr +} + +// LogNotifier logs events (useful as a default/fallback). +type LogNotifier struct { + Logger *slog.Logger +} + +func (l *LogNotifier) Notify(event Event) error { + l.Logger.Info("task completed", + "task_id", event.TaskID, + "task_name", event.TaskName, + "status", event.Status, + "cost_usd", event.CostUSD, + "duration", event.Duration, + ) + return nil +} -- cgit v1.2.3