package main import ( "fmt" "net/http" "os" "woke/api" "woke/db" "woke/player" "woke/scheduler" "woke/ui" tea "github.com/charmbracelet/bubbletea" ) const helpText = `woke - TUI alarm clock with REST API Usage: woke Start the TUI (API server runs on :9119 in host mode) woke --help Show this help Modes: Host mode (default): Uses local SQLite, exposes REST API on :9119 Client mode: Connects to remote woke server, syncs alarms, fires locally Configure mode in Settings (press 'c'): - Server URL: empty = host mode, or http://host:9119 for client mode - Server pass: password to authenticate with remote server - API password: password required for incoming API requests (host mode) - Poll seconds: how often client polls server for alarm updates API endpoints (http://localhost:9119): List alarms: curl http://localhost:9119/api/alarms Get alarm: curl http://localhost:9119/api/alarms/1 Create alarm (one-shot): curl -X POST http://localhost:9119/api/alarms \ -H 'Content-Type: application/json' \ -d '{"name": "Standup", "time": "09:00", "trigger": "once"}' Create alarm (cron, weekdays at 7:30): curl -X POST http://localhost:9119/api/alarms \ -H 'Content-Type: application/json' \ -d '{"name": "Morning", "time": "07:30", "trigger": "30 7 * * 1-5"}' Update alarm: curl -X PUT http://localhost:9119/api/alarms/1 \ -H 'Content-Type: application/json' \ -d '{"name": "New name", "time": "08:00"}' Toggle alarm on/off: curl -X PATCH http://localhost:9119/api/alarms/1/toggle Delete alarm: curl -X DELETE http://localhost:9119/api/alarms/1 With authentication (if API password is set): curl -H 'Authorization: Bearer yourpassword' http://localhost:9119/api/alarms TUI keybindings: j/k Navigate alarms a Add alarm e Edit alarm d Delete alarm space Toggle enabled c Settings q Quit ` func main() { if len(os.Args) > 1 && (os.Args[1] == "--help" || os.Args[1] == "-h") { fmt.Print(helpText) os.Exit(0) } // Always open local store for settings store, err := db.Open() if err != nil { fmt.Fprintf(os.Stderr, "Failed to open database: %v\n", err) os.Exit(1) } defer store.Close() settings := store.LoadSettings() clientMode := settings.ServerURL != "" // Determine alarm store: local DB or remote API var alarmStore db.AlarmStore if clientMode { alarmStore = api.NewClient(settings.ServerURL, settings.ServerPassword) fmt.Fprintf(os.Stderr, "Client mode: connecting to %s\n", settings.ServerURL) } else { alarmStore = store } pl := player.New() sched := scheduler.New(alarmStore, clientMode) sched.Start() defer sched.Stop() model := ui.NewModel(store, alarmStore, sched, pl, clientMode) p := tea.NewProgram(model, tea.WithAltScreen()) // Start HTTP API server only in host mode if !clientMode { srv := api.New(store, settings.APIPassword, func() { p.Send(ui.AlarmsChangedMsg{}) }) go func() { addr := ":9119" fmt.Fprintf(os.Stderr, "API server listening on %s\n", addr) if err := http.ListenAndServe(addr, srv.Handler()); err != nil { fmt.Fprintf(os.Stderr, "API server error: %v\n", err) } }() } if _, err := p.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } }