From 54aa87676d4e982d79dcd7c54d85f8bed5dd9e3e Mon Sep 17 00:00:00 2001 From: Amir Husayn Panahifar Date: Sun, 11 Oct 2026 12:34:53 +0330 Subject: [PATCH] docs(readme): rewrite for a clearer, complete overview Add badges, a contents list, and a screenshot caption; document every flag in a table, topic-resolution rules, the --since replay window, the publish form, and the adaptive pane layout. Add How it works, History and storage, and Troubleshooting sections, and correct the go install instructions. --- README.md | 312 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 221 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index a2ee1db..82e93bf 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ # lazyntfy -A [lazygit](https://github.com/jesseduffield/lazygit)-style terminal client for -[ntfy](https://ntfy.sh): topics in the left pane, messages in the middle pane, -and the selected message in the right pane. +**A [lazygit](https://github.com/jesseduffield/lazygit)-style terminal client for [ntfy](https://ntfy.sh).** +Follow your topics, read and search messages, and publish — without leaving the terminal. + +[![Go](https://img.shields.io/badge/Go-1.26-00ADD8?logo=go&logoColor=white)](https://go.dev) +[![License: GPL-3.0-or-later](https://img.shields.io/badge/license-GPL--3.0--or--later-blue)](LICENSE) +[![Built with Bubble Tea](https://img.shields.io/badge/built%20with-Bubble%20Tea-ff69b4)](https://github.com/charmbracelet/bubbletea) +[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](#contributing) ``` ╭ topics ─────╮ ╭ messages ─────────────────╮ ╭ detail ───────────────╮ @@ -15,31 +19,49 @@ and the selected message in the right pane. lazyntfy alerts 12/12 ● connected ? help q quit ``` -lazyntfy talks to the ntfy HTTP API directly (`GET /topic/json` to subscribe, -`POST /topic` to publish). It reads `~/.config/ntfy/client.yml` for credentials -and subscriptions, groups topics that share a server and credentials onto one -stream, and reconnects automatically — resuming from the newest event it has -already seen so nothing is delivered twice. +lazyntfy is a keyboard-driven TUI for [ntfy](https://ntfy.sh): the left pane lists +topics, the middle pane lists messages, and the right pane shows the selected +message in full. It talks to the ntfy HTTP API directly, follows several topics +over a single stream, and keeps a local cache so your history is there the moment +you launch it. + +## Contents + +- [Features](#features) +- [Install](#install) +- [Quick start](#quick-start) +- [Usage](#usage) +- [Key bindings](#key-bindings) +- [Configuration](#configuration) +- [How it works](#how-it-works) +- [History and storage](#history-and-storage) +- [Troubleshooting](#troubleshooting) +- [Development](#development) +- [Packaging](#packaging) +- [Project layout](#project-layout) +- [Contributing](#contributing) +- [License](#license) ## Features -- **lazygit-style layout** — topics, messages, and message detail in three panes. -- **Account discovery** — with no topic on the command line it lists your - subscriptions, reservations, and ACL grants. -- **Alert-aware** — Grafana and Alertmanager webhook bodies are summarized - (status, alert name, severity, summary); `v` shows the raw JSON. -- **Persistent** — topics you add and the messages that arrive are stored in a - small SQLite file and restored on the next start. -- **Resilient** — reconnects with exponential backoff, honoring `message_delete` - and `message_clear` events. -- **Single binary, no CGo** — pure Go, including the SQLite driver. +- **Three-pane, lazygit-style layout** — topics, messages, and full detail side by side. +- **Follow many topics at once** — topics that share a server and credentials are + multiplexed onto a single stream. +- **Account discovery** — start with no topics and lazyntfy lists your + subscriptions, reserved topics, and ACL grants. +- **Alert-aware** — Grafana and Alertmanager webhook payloads are summarized into + status, alert name, severity, and summary; `v` reveals the raw JSON. +- **Persistent** — topics and messages are cached in a local SQLite database and + restored on the next start, so the list is populated as soon as the app opens. +- **Resilient** — reconnects with exponential backoff and resumes from the newest + event it has already seen, so nothing is delivered twice. +- **Publish from the terminal** — press `p` to send a title, priority, tags, and body. +- **Single static binary** — pure Go, no CGo, including the SQLite driver. -## Requirements +## Install -- Go 1.26 or newer (to build) -- A reachable ntfy server - -## Installation +lazyntfy needs a **Go 1.26+ toolchain only to build**. Prebuilt installs are +available for Arch Linux and Debian/Ubuntu. ### Arch Linux @@ -52,9 +74,8 @@ cd lazyntfy/packaging/archlinux makepkg -si ``` -This installs `/usr/bin/lazyntfy`, the man page under `/usr/share/man`, the -license, and the README, and pulls in `ca-certificates` so TLS to your ntfy -server works. +This installs `/usr/bin/lazyntfy` and the man page under `/usr/share/man`, and +pulls in `ca-certificates` so TLS to your ntfy server works. ### Debian and Ubuntu @@ -68,9 +89,9 @@ make deb # writes dist/lazyntfy__.deb sudo apt install ./dist/lazyntfy_*.deb ``` -`apt install ./file.deb` (or `dpkg -i` followed by `apt -f install`) resolves -the `ca-certificates` dependency. The packaging needs only the Go toolchain and -`dpkg-dev`; `make deb` passes `-d` so it also builds where `golang-go` is not +`apt install ./file.deb` (or `dpkg -i` followed by `apt -f install`) resolves the +`ca-certificates` dependency. The packaging needs only the Go toolchain and +`dpkg-dev`, and `make deb` passes `-d` so it also builds where `golang-go` is not installed as a distribution package. ### From source @@ -82,7 +103,7 @@ make install # into ~/.local, no root needed ``` `make install` honors `PREFIX`, `BINDIR`, `MANDIR`, `DOCDIR`, and `DESTDIR`. -Picking a system prefix installs for every user: +Choosing a system prefix installs for every user: ```sh sudo make install PREFIX=/usr/local # /usr/local/bin/lazyntfy, /usr/local/share/man/man1 @@ -104,46 +125,101 @@ From a checkout: go install ./cmd/lazyntfy # binary in $(go env GOPATH)/bin ``` -## Usage +## Quick start ```sh -lazyntfy # topics from the account, their history, and client.yml -lazyntfy alerts # one topic, expanded with default-host -lazyntfy --since 1h alerts # replay the last hour -lazyntfy --host https://ntfy.example alerts -lazyntfy --token tk_xxx alerts -lazyntfy --user name:pass alerts +lazyntfy alerts # follow one topic on the default host +lazyntfy # or start with no arguments to load every topic on your account ``` -With no topic on the command line, lazyntfy asks the server for your account -subscriptions, reserved topics, and ACL grants (`GET /v1/account` and, when -allowed, `GET /v1/users`), then adds the topics remembered from previous -sessions. You can always add a topic for the current session with `a`. +Then `j`/`k` to move, `enter` to open a message, `/` to filter, `p` to publish, +and `?` for help. + +## Usage + +``` +lazyntfy [flags] [topic...] +``` + +### Flags + +| Flag | Default | Description | +| --- | --- | --- | +| `--config path` | `~/.config/ntfy/client.yml` | Read the ntfy client config from `path`. | +| `--host url` | from the config file | Override `default-host`. | +| `--token token` | from the config file | Send `token` as a Bearer credential. | +| `--user user[:password]` | from the config file | Send HTTP basic credentials. | +| `--since value` | `all` | Replay window: `all`, a duration, a Unix timestamp, or a message id. | +| `--version` | | Print the version and exit. | +| `--help` | | Print a short usage summary and exit. | + +### Specifying topics + +Topics can be passed as positional arguments or listed in `client.yml`: + +- A **short name** is expanded with `default-host`: `lazyntfy alerts`. +- A **`host/topic`** or a **full URL** carries its own origin: + `lazyntfy ntfy.example:8443/alerts` or `lazyntfy https://other.example/backups`. +- Short names need a `default-host`; pass `--host` or use a `host/topic` form. + +With **no positional topics**, lazyntfy assembles the subscription list from, in +order: + +1. the `subscribe:` list in `client.yml`; +2. your account — subscriptions and reservations from `GET /v1/account`, and ACL + grants from `GET /v1/users` when the credential may read it; +3. topics remembered from previous sessions. + +Press `a` at any time to add a topic for the current session. + +### Replaying history + +`--since` sets the `since` query parameter for each stream: `all` (the default), +a duration such as `1h` or `30m`, a Unix timestamp, or a message id. + +When history was restored from disk and `--since` is left at its default, +lazyntfy sets `since` to the newest stored event instead, so the first connection +picks up where the last session stopped instead of replaying the server's whole +cache. An explicit `--since` always wins. + +### Publishing + +Press `p` to open a small form with four fields — **title**, **priority**, +**tags**, and **message**: + +- **priority** accepts `1`–`5` or the ntfy names `min`, `low`, `default`, `high`, `max` + (empty means `default`). +- **tags** is a comma-separated list. +- **message** is required; it is published as plain text with `Title`, `Priority`, + and `Tags` headers. ## Key bindings -Keys act on the focused pane. Use `tab` / `shift-tab` or `1` / `2` / `3` to -change panes. +Keys act on the focused pane. Use `tab` / `shift-tab` or `1` / `2` / `3` to change +panes. | Key | Action | | --- | --- | -| `j` / `k`, arrows | move in the focused pane | -| `g` / `G` | first / last | -| `ctrl-u` / `ctrl-d` | page up / down | -| `1` / `2` / `3` | focus topics, messages, detail | +| `j` / `k`, `↓` / `↑` | move in the focused pane | +| `g` / `G`, `home` / `end` | first / last | +| `ctrl-u` / `ctrl-d`, `pgup` / `pgdn` | page up / down | +| `1` / `2` / `3` | focus topics / messages / detail | | `tab` / `shift-tab` | next / previous pane | -| `enter` | topics → messages → detail | -| `esc` | back from detail, or clear the filter | -| `/` | filter title, body, tags, and id | -| `r` | reconnect | +| `enter` | descend: topics → messages → detail | +| `esc` | leave detail, or clear the filter | +| `/` | filter by title, body, tags, and message id | +| `r` | reconnect all streams | | `p` | publish to the focused topic | | `a` | subscribe to another topic for this session | -| `v` | toggle raw JSON | -| `o` | open the message click URL | -| `?` | help | -| `q` | quit | +| `v` | toggle the raw JSON view | +| `o` | open the message's click URL | +| `?` | in-app help | +| `q` / `ctrl-c` | quit | -On a narrow terminal the detail pane is hidden until it is focused. +The layout adapts to the terminal width: at 100 columns and wider all three panes +are shown; from 72–99 columns the topics pane is paired with the focused pane; +and below 72 columns only the focused pane is drawn. Terminals smaller than 40×8 +show `terminal too small`. ## Configuration @@ -153,26 +229,97 @@ lazyntfy reads the official ntfy client configuration at ```yaml default-host: https://ntfy.example default-token: tk_xxxxxxxxxxxxxxxx +# or, instead of a token: +# default-user: alice +# default-password: s3cret + subscribe: - topic: alerts - - topic: https://other.example/backups - user: alice - password: secret + - topic: https://other.example/backups # explicit origin + user: bob + password: hunter2 + - topic: private # per-topic token + token: tk_yyyyyyyyyyyyyyyy ``` -Positional topics on the command line replace the file's `subscribe` list. -Credentials are sent in the `Authorization` header and are never printed or -written back. See `man lazyntfy` for every flag. +**Precedence.** `--host`, `--token`, and `--user` override the file. A token beats +a user: passing `--user` clears the token, and passing `--token` clears the user. +A per-topic `token` or `user` in `subscribe` applies to that topic only. Credentials +are sent in the `Authorization` header (Bearer or Basic) and are never printed or +written to disk. + +If the default file is missing and you pass topics on the command line, lazyntfy +still runs. Point `--config` at a different file to load another one. + +## How it works + +- **Subscribe** — `GET /[,...]/json` returning NDJSON, with a `since` + query parameter per stream. +- **Publish** — `POST /` with a plain-text body and `Title` / `Priority` / + `Tags` headers. +- **Grouping** — topics that share an origin and credentials share one connection; + each group is supervised independently. +- **Reconnect** — on a dropped stream the group retries with exponential backoff + (1s, doubling up to 30s) and sets `since` to the newest event time it has already + seen, so messages are not delivered twice. +- **Events** — `message` adds a message, while `message_delete` and `message_clear` + remove messages locally as well. Malformed lines are skipped and reported in the + status bar. +- **Truncated cache** — if the server caps its replay it sends + `X-Messages-Truncated`, and lazyntfy notes that the cache was truncated to the + newest messages. ## History and storage -Topics you add and the messages that arrive are kept in a small SQLite database -at `$XDG_DATA_HOME/lazyntfy/history.db` -(`~/.local/share/lazyntfy/history.db` by default), so the last session is -restored on the next start. Each topic keeps its newest 1000 messages; expired -messages are pruned on startup. Credentials are never written to the database — -the current run's authentication is always used. History is best effort: if the -file cannot be opened, the client still runs. +Topics and messages are kept in a small SQLite database so the last session is +restored on the next start. + +- **Path** — `$XDG_DATA_HOME/lazyntfy/history.db`, defaulting to + `~/.local/share/lazyntfy/history.db`. +- **Contents** — remembered topics, the newest 1000 messages per topic, and a little + UI state such as the last selected topic. +- **Maintenance** — messages with an `expires` time are pruned on startup, and the + 1000-message cap is enforced as messages arrive. +- **Privacy** — credentials are never written to the database; the current run's + authentication is always used. +- **Best effort** — if the database cannot be opened, lazyntfy runs without history. + +To start clean, delete the file: + +```sh +rm -f "${XDG_DATA_HOME:-$HOME/.local/share}/lazyntfy/history.db" +``` + +## Troubleshooting + +| Symptom | What to do | +| --- | --- | +| `no topics yet — press a to add one` | Pass a topic, add `subscribe:` entries, or use a token so account discovery can list topics. | +| `HTTP 401/403: not authorized to list topics` | The credential cannot read `/v1/account`; pass topics explicitly instead. | +| `no default-host` | Short topic names need a host. Pass `--host`, or use `host/topic`. | +| `x509: certificate signed by unknown authority` | Install CA certificates (`ca-certificates`). | +| `terminal too small` | Resize to at least 40×8. | +| `o` does nothing | Opening the click URL uses `xdg-open`; install it or a desktop opener. | +| First launch shows a flood of messages | Narrow the window with `--since 1h`. | + +## Development + +```sh +make build # build ./lazyntfy +make test # go test ./... +make vet # go vet ./... +make fmt # gofmt -w . +make run TOPIC=alerts +make man # install just the man page +make clean # remove the binary and build output +``` + +Run one package's tests, or the full suite with the race detector: + +```sh +go test ./internal/history/... +go test -race ./... +``` ## Packaging @@ -187,38 +334,21 @@ archive from the Gitea instance. When cutting a release: -1. Bump the version in [`debian/changelog`](debian/changelog) and `pkgver` in - the `PKGBUILD`. +1. Bump the version in [`debian/changelog`](debian/changelog) and `pkgver` in the + `PKGBUILD`. 2. Tag the commit (`git tag -s vX.Y.Z`) and push it. 3. In `packaging/archlinux`, run `updpkgsums` to refresh the tarball checksum and `makepkg --printsrcinfo > .SRCINFO`. -## Development - -```sh -make build # build ./lazyntfy -make test # go test ./... -make vet # go vet ./... -make fmt # gofmt -w . -make run TOPIC=alerts -make man # install just the man page -``` - -Run a single package's tests, for example: - -```sh -go test ./internal/history/... -``` - ## Project layout | Path | Purpose | | --- | --- | | `cmd/lazyntfy` | flags and program startup | -| `internal/config` | `client.yml` plus CLI overrides | +| `internal/config` | `client.yml`, topic resolution, CLI overrides | | `internal/history` | on-disk topics and messages (SQLite) | -| `internal/ntfy` | HTTP subscribe, publish, reconnect | -| `internal/store` | newest-first messages, delete, clear | +| `internal/ntfy` | HTTP subscribe, publish, grouping, reconnect | +| `internal/store` | newest-first messages, delete, clear, filter | | `internal/alert` | Grafana / Alertmanager summary | | `internal/theme` | Catppuccin adaptive palette | | `internal/textutil` | width-aware text wrapping and clipping |