6fdb4bbbf8
The module path, the CI pipeline and the self-updater all named GitHub. They now name the Gitea instance everything else already runs on. The workflows are rewritten rather than translated, for the reason recorded in ci.yaml: Gitea's runner image is ubuntu:22.04, whose nodejs is Node 12, so no JS action runs there -- actions/checkout@v4 dies with a SyntaxError before doing anything. Every step is shell and checkout is a plain clone, which this public repo needs no credential for. upload-artifact/download-artifact are JS actions too, and there is no artifact store here, so the job that builds the binaries is the job that publishes them. internal/updater keeps its release and asset types unchanged: Gitea's release payload carries the same tag_name, and its attachments the same name and browser_download_url, so only the URL, the Accept header and one error string move. The asset naming in release.yaml is load-bearing for that matching. This does strand already-installed binaries, which still poll api.github.com. The GitHub repository is left in place and untouched, so they report themselves up to date rather than erroring; its last release is the bridge, and crossing it is a one-time manual download.
102 lines
4.0 KiB
Markdown
102 lines
4.0 KiB
Markdown
# terdut-tui
|
|
|
|
TUI client for [terdut-server](https://git.ryuvia.com/niklas/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) ·
|
|
`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 |
|
|
|
|
<!-- 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 -->
|