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.
This commit is contained in:
@@ -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.
|
||||
|
||||
[](https://go.dev)
|
||||
[](LICENSE)
|
||||
[](https://github.com/charmbracelet/bubbletea)
|
||||
[](#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_<version>_<arch>.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 /<topic>[,<topic>...]/json` returning NDJSON, with a `since`
|
||||
query parameter per stream.
|
||||
- **Publish** — `POST /<topic>` 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 |
|
||||
|
||||
Reference in New Issue
Block a user