# terdut-tui TUI client for [terdut-server](https://git.ryuvia.com/niklas/terdut-server), a Prometheus Alertmanager receiver and incident manager. Requires server **v0.20.0+** (team-scoped API). ## Domain model The server splits alerts from incidents, and this client mirrors it: - **Alert** — Alertmanager's record. Firing or resolved, read-only, no workflow state. - **Incident** — the work item: triggered → acknowledged → resolved, with an assignee, snooze, notes and an append-only timeline. Many alerts to one incident, correlated by Alertmanager's `groupKey`. The server is multi-team: incidents, alerts and the schedule belong to a team, and the caller only sees their own teams. `Model.activeTeamID` (0 = all) narrows the incident and alert lists via `team_id`; the schedule is per team and uses `Model.scheduleTeam()`. Users have `is_admin`, and the TUI mirrors the server's permission rules up front (`canEditSchedule`, `canManageUser`, `isAdmin`) so a 403 is explained before the round trip, not after. The server has no version endpoint; an old one is recognised by `GET /api/teams` answering 404 (`errServerTooOld`). All user actions target incidents. Two server behaviours the UI has to respect: manual resolve is **terminal** (hence the confirmation prompt), and snooze is the non-destructive "not now" alternative. ## Tech stack - Go 1.25+ - [Bubbletea](https://github.com/charmbracelet/bubbletea) — TUI framework (strict Elm architecture) - [Lipgloss](https://github.com/charmbracelet/lipgloss) — styles (all in `internal/tui/styles.go`, never inline) - [Bubbles](https://github.com/charmbracelet/bubbles) — table, textinput, help components ## Project layout ``` main.go CLI entry point: flags, config load, start TUI internal/api/client.go REST API client — one method per endpoint internal/config/config.go Config loader (~/.config/terdut-tui/config.yaml) internal/theme/ Colour themes: semantic tokens, built-ins, user file loader internal/tui/ Bubbletea UI model.go Model struct, mode/section constants, Init(), tea.Cmd constructors update.go Update() — dispatch only, no API calls inline view.go View() — pure rendering keys.go keyMap (bubbles/key pattern) styles.go Styles struct — every lipgloss style, built from a theme internal/updater/updater.go Self-update via Gitea releases ``` ## Architecture rules 1. **Never call API inside `Update()`** — return `tea.Cmd` instead; the runtime runs it async. 2. **`View()` is pure** — no side effects, no state mutations. 3. **All state in `Model`** — no globals. 4. **All styles in `styles.go`** — never use lipgloss inline in `view.go`. Styles live on `Model.styles`, built once by `newStyles(theme.Theme)`; the handful of free functions in `view.go` take a `Styles` as their first argument. No colour literal appears outside `internal/theme`. ## Config Location: `~/.config/terdut-tui/config.yaml` ```yaml server_url: https://terdut.example.com api_key: <64-char hex key> refresh_interval: 30 # seconds, optional, default 30 theme: gruvbox-dark # optional, default gruvbox-dark team: Ops # optional, team name or id to start on, default all ``` Built-in themes are `gruvbox-dark` and `gruvbox-light`; user themes are YAML files in `~/.config/terdut-tui/themes/`, optionally `extends:`-ing a built-in. See the README for the token list. The API key is a one-time secret generated by terdut-server (`POST /api/users/{id}/api-keys`). ## Running ```bash go run . go run . --version go run . --self-update ``` ## Building ```bash go build -ldflags="-X main.version=v0.1.0" -o terdut-tui . ``` ## Sections `Incidents` (the queue, and the default) · `Alerts` (raw read-only feed) · `Stats` (MTTA/MTTR and alert frequency charts) · `Archived` (archived incidents) · `Schedule` · `Users` ## Development stages | Stage | Feature | |-------|---------| | 1 | Scaffold, config, health check, placeholder TUI | | 2 | Alert dashboard with auto-refresh and stats | | 3 | Alert detail: acknowledge, comment, statistics charts | | 4 | On-call schedule calendar view | | 5 | User management and API key lifecycle | | 6 | Incidents: queue, timeline, ack/assign/snooze/resolve, MTTA/MTTR | | 7 | Teams: `T` switcher, per-team schedule, admin/disabled markers (server v0.20) | ## Memory (GrayMatter) This project has persistent agent memory via the `graymatter` MCP tools: - `memory_search` (`agent_id`, `query`) — call at the **start of a task** when prior context might matter. - `memory_add` (`agent_id`, `text`) — call whenever you learn something **durable**: user preferences, decisions, conventions, gotchas. - `memory_reflect` (`action`, `agent`, `text`/`target`) — update or forget stale facts. ⚠ takes `agent`, not `agent_id`. - `checkpoint_save` / `checkpoint_resume` (`agent_id`) — snapshot/restore session state before major refactors or across restarts. Use a stable `agent_id` of the form `-` (e.g. `myapp-backend`). Store conclusions, not conversation logs. Err on the side of remembering.