Files
terdut-tui/CLAUDE.md
T
Niklas Ye 1140d773f8
Release / build (amd64, darwin) (push) Failing after 9s
Release / build (arm64, darwin) (push) Failing after 10s
Release / build (amd64, linux) (push) Failing after 10s
Release / release (push) Has been skipped
Release / build (arm64, linux) (push) Failing after 11s
feat!: incidents as the primary object
terdut-server v0.4.0 splits the alerts row into two objects, and the
endpoints this client drove for acknowledgement, comments and archiving
are gone. Pointing the same screens at the new paths would have missed
the point of the split: alerts are now Alertmanager's record, read-only
and carrying no human state, while the incident is the thing anyone
actually works on.

Incidents lead the section list and are what the client opens on. The
queue shows severity, status, assignee and age, and the detail view adds
what only exists server-side now: the group labels Alertmanager
correlated on, the member alerts, and an append-only timeline where
system events and notes are interleaved. That timeline is the whole
history the server keeps — alert rows are still mutated in place — so
rendering it in order matters more than styling it.

Actions all move onto the incident: a/A acknowledge, s assign, z/Z
snooze, c note, d delete note, x archive, R resolve.

Two of those need care rather than a keybinding:

  - R, not r, resolves, and it asks first. The server treats a manual
    resolve as terminal: a later occurrence opens a new incident instead
    of reopening this one, and an alert that never stops firing leaves
    the incident closed for good. A stray keypress is not recoverable,
    so the prompt says what it means.
  - x refuses on an open incident rather than archiving it, since
    archiving unresolved work only hides it. Snooze is offered as the
    "not now" answer, and the client treats a snoozed_until in the past
    as not snoozed, matching the server, which sweeps nothing.

Statistics lead with MTTA and MTTR, neither of which was computable
before. The server sends null until something has actually been
acknowledged or resolved, and that renders as — rather than 0: no data
is a different claim from instant.

Alerts keep a tab of their own as the raw feed — useful for asking what
Alertmanager is really sending — with an Incident column replacing Ack
By, and i in the detail view jumping to the incident where something can
be done about it. Archived now holds archived incidents; archiving an
alert is server-side housekeeping and no longer a user action.

BREAKING CHANGE: requires terdut-server v0.4.0 or later. Against an
older server every incident request 404s. Use terdut-tui v0.3.x with
servers before v0.4.0.
2026-07-30 21:54:54 +02:00

101 lines
3.9 KiB
Markdown

# terdut-tui
TUI client for [terdut-server](https://github.com/terdut-server), a Prometheus Alertmanager receiver and incident manager. Requires server **v0.4.0+**.
## 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`.
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, health check, 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/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 All lipgloss styles
internal/updater/updater.go Self-update via GitHub 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`.
## 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
```
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) ·
`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 |
<!-- graymatter:instructions:begin — managed by `graymatter init`; edits inside this block are overwritten -->
## 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 `<project>-<role>` (e.g. `myapp-backend`). Store conclusions, not conversation logs. Err on the side of remembering.
<!-- graymatter:instructions:end -->