Files
agent-helm/helmd/config.go
claude f9c79ac84e
Some checks failed
build-and-push / build (push) Successful in 42s
release-client / build-release (push) Has been cancelled
helmd-release / build-release (push) Successful in 1m6s
helmd v2: Go-server som styr agent i tmux — REST+SSE, frågedetektor, fildelning, ntfy, CI-release
- tmux som sanningskälla (agentoberoende, överlever omstart via adoption)
- API: sessions, prompt, question/answer, keys, mode, config, shares, notify, events
- frågedetektor testad mot riktiga agy 1.1.9-dumpar + mock
- integrationstest: 26 tester i isolerad debian-container (scripts/run-integration.sh)
- e2e-verifierad mot riktig agy (trust-fråga -> svar -> prompt)
- .gitea/workflows/helmd-release.yaml -> rullande helmd-latest (x64+arm64)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 20:54:54 +02:00

136 lines
3.1 KiB
Go

package main
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
type NtfyConfig struct {
Server string `json:"server"`
Topic string `json:"topic"` // server events (questions etc.)
AllowedTopics []string `json:"allowed_topics"` // POST /api/notify may only use these
Enabled bool `json:"enabled"`
NotifyOnQuestion bool `json:"notify_on_question"`
}
type Config struct {
Listen string `json:"listen"`
Token string `json:"token"`
AgentCmd string `json:"agent_cmd"`
AgentArgs []string `json:"agent_args"`
Workdir string `json:"workdir"`
ShareDir string `json:"share_dir"`
ShareExts []string `json:"share_exts"`
PollMs int `json:"poll_ms"`
Cols int `json:"cols"`
Rows int `json:"rows"`
Ntfy NtfyConfig `json:"ntfy"`
path string
}
func defaultConfig() *Config {
home, _ := os.UserHomeDir()
return &Config{
Listen: "127.0.0.1:8788",
AgentCmd: "agy",
AgentArgs: []string{},
Workdir: home,
ShareDir: filepath.Join(home, ".local/share/helmd/shares"),
ShareExts: []string{".html", ".htm", ".md", ".txt", ".json", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".pdf"},
PollMs: 500,
Cols: 200,
Rows: 50,
Ntfy: NtfyConfig{
Server: "https://ntfy.brasse-pc.eu",
Topic: "agent-helm",
AllowedTopics: []string{"agent-helm", "claude"},
Enabled: true,
NotifyOnQuestion: true,
},
}
}
func configPath() string {
if p := os.Getenv("HELMD_CONFIG"); p != "" {
return p
}
dir, err := os.UserConfigDir()
if err != nil {
dir = "."
}
return filepath.Join(dir, "helmd", "config.json")
}
// loadConfig reads the config file, creating it with defaults (and a
// fresh token) on first run.
func loadConfig(path string) (*Config, error) {
cfg := defaultConfig()
cfg.path = path
data, err := os.ReadFile(path)
if os.IsNotExist(err) {
cfg.Token = newToken()
if err := cfg.Save(); err != nil {
return nil, err
}
fmt.Fprintf(os.Stderr, "helmd: skapade %s (ny token genererad)\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)
}
if cfg.Token == "" {
cfg.Token = newToken()
if err := cfg.Save(); err != nil {
return nil, err
}
}
return cfg, nil
}
func (c *Config) Save() error {
if err := os.MkdirAll(filepath.Dir(c.path), 0o755); err != nil {
return err
}
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return os.WriteFile(c.path, append(data, '\n'), 0o600)
}
func newToken() string {
b := make([]byte, 24)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return hex.EncodeToString(b)
}
func (c *Config) extAllowed(name string) bool {
ext := strings.ToLower(filepath.Ext(name))
for _, e := range c.ShareExts {
if ext == e {
return true
}
}
return false
}
func (c *Config) topicAllowed(topic string) bool {
for _, t := range c.Ntfy.AllowedTopics {
if topic == t {
return true
}
}
return false
}