125 lines
3.3 KiB
Go
125 lines
3.3 KiB
Go
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)
|
|
}
|
|
}
|