notifyr: ntfy send/read client for agents
send (title/priority/tags), read (poll mode, greppable one-liners), topics (known homelab buses from config). Config with homelab defaults on first run. Unit tests against httptest; smoked against the real ntfy on topic agent-tools-test. Spec: doc/tool-parity.md 3.3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011KikHkfCiC3yELbsMN8fT9
This commit is contained in:
78
notifyr/notify/config.go
Normal file
78
notifyr/notify/config.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Config is ~/.config/notifyr/config.json, created with homelab
|
||||
// defaults on first run. NOTIFYR_CONFIG overrides the path.
|
||||
type Config struct {
|
||||
Server string `json:"server"`
|
||||
Token string `json:"token"`
|
||||
DefaultTopic string `json:"default_topic"`
|
||||
Topics map[string]string `json:"topics"` // known topics -> what they carry (informational)
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Server: "https://ntfy.brasse-pc.eu",
|
||||
Token: "",
|
||||
DefaultTopic: "claude",
|
||||
Topics: map[string]string{
|
||||
"claude": "agents' direct notes to Björn",
|
||||
"agent-helm": "agent-helm events (question waiting, session died)",
|
||||
"Info": "*arr system events",
|
||||
"media-hamtningar": "media grabbed for download",
|
||||
"media-nytt": "new media landed in Jellyfin",
|
||||
"pi5-server": "server maintenance (reboots, watchtower)",
|
||||
"pi5-server-fel": "server problems: failed units, disk space",
|
||||
"ci-fel": "failed Gitea Actions builds",
|
||||
"monitoring": "Uptime Kuma up/down alerts",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ConfigPath() string {
|
||||
if p := os.Getenv("NOTIFYR_CONFIG"); p != "" {
|
||||
return p
|
||||
}
|
||||
dir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
dir = "."
|
||||
}
|
||||
return filepath.Join(dir, "notifyr", "config.json")
|
||||
}
|
||||
|
||||
// LoadConfig reads the config, creating it with defaults on first run.
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
cfg := DefaultConfig()
|
||||
data, err := os.ReadFile(path)
|
||||
if os.IsNotExist(err) {
|
||||
if err := SaveConfig(path, cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "notifyr: created %s\n", path)
|
||||
return cfg, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(data, cfg); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func SaveConfig(path string, cfg *Config) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, append(data, '\n'), 0o600)
|
||||
}
|
||||
Reference in New Issue
Block a user