// Command antidriftd is the AntiDrift focus daemon: it serves a local web UI // and owns the commitment state machine. package main import ( "context" "log" "os" "os/exec" "runtime" "time" "antidrift/internal/ai" "antidrift/internal/evidence" "antidrift/internal/session" "antidrift/internal/store" "antidrift/internal/web" ) const addr = "localhost:7777" func main() { path, err := store.DefaultPath() if err != nil { log.Fatalf("resolve snapshot path: %v", err) } ctrl, err := session.New(path) if err != nil { log.Fatalf("load session: %v", err) } srv := web.NewServer(ctrl) srv.Init() // re-arm or expire a restored Active session // Wire the AI planning coach. Backend selectable via ANTIDRIFT_AI_BACKEND // (claude default, or codex). Misconfiguration degrades to no coach rather // than failing startup — manual planning still works. if backend, err := ai.NewBackend(os.Getenv("ANTIDRIFT_AI_BACKEND")); err != nil { log.Printf("ai disabled: %v", err) } else { svc := ai.NewService(backend) ctrl.SetCoach(svc) ctrl.SetDriftJudge(svc) ctrl.SetNudge(svc) log.Printf("ai: %s backend (coach + drift judge + nudge)", backend.Name()) } src := evidence.NewSource() go src.Watch(context.Background(), ctrl.RecordWindow) go openBrowser("http://" + addr) log.Printf("antidriftd listening on http://%s", addr) if err := srv.Router().Run(addr); err != nil { log.Fatalf("server: %v", err) } } // openBrowser best-effort launches the default browser after a short delay so // the server is listening first. Failures are logged, not fatal. func openBrowser(url string) { time.Sleep(300 * time.Millisecond) var cmd string var args []string switch runtime.GOOS { case "linux": cmd, args = "xdg-open", []string{url} case "darwin": cmd, args = "open", []string{url} case "windows": cmd, args = "rundll32", []string{"url.dll,FileProtocolHandler", url} default: log.Printf("open browser manually: %s", url) return } if err := exec.Command(cmd, args...).Start(); err != nil { log.Printf("could not open browser (%v); visit %s", err, url) } }