gh-news
GitHub notifications TUI built with Rust and ratatui.
Screenshot
Features
- Terminal-based UI for GitHub notifications using Ratatui
- Installs as a native gh CLI extension
- Vim-style navigation with j/k keys
- Multi-select for batch operations on notifications
- Auto-refresh with configurable interva
- Preview notifications
- Regex filtering to filter specific notifications
- Pin important notifications
- Repository grouping with collapsible headers
- Notification hooks for custom commands
- Custom actions with command templates
- Mark notifications read/unread individually or in bulk
- Static display mode for scripting and pipelines
Installation
Install as a gh CLI extension (easiest):
Then run it:
Setup
You need a GitHub token. The app looks for it in this order:
GH_TOKENenv varGITHUB_TOKENenv var- Your
ghCLI config at~/.config/gh/hosts.yml(or$XDG_CONFIG_HOME/gh/hosts.yml) gh auth token(queries theghCLI, which reads from the system keyring on modern versions)
Easiest way is to just run gh auth login if you have the GitHub CLI installed. Otherwise set GH_TOKEN to your personal access token.
Usage
Just run it:
gh-news shows a loading screen while fetching notifications during start-up and manual refreshes.
Options
-a, --all- Show all notifications (not just unread)-c, --config <PATH>- Use a custom config file instead of the default-f, --filter <PATTERN>- Only show notifications matching this regex-n, --max-notifications <N>- Limit how many to fetch-p, --participating- Only show notifications where you're participating/mentioned-r, --mark-read- Mark all notifications as read (non-interactive)-s, --static-display- Print notifications and exit (for scripts)--no-auto-mark-read- Disable auto-marking notifications as read when navigating--no-auto-archive- Disable auto-archiving notifications when navigating--no-cache- Bypass notification cache and always fetch fresh from the API--state-file <PATH>- Use a custom state file path (overrides config and default)
Examples
|
Keybindings
Navigation
↑/↓orj/k- Navigate notifications, or repository headers when repositories are collapsedHome/End- Jump to first/last notificationPageUp/PageDown- Page navigation (or scroll preview if shown)
Actions
Enter- Open notification in browser and mark as read, or toggle repository collapse on headerso- Open notification in browser without marking as read.- Toggle read/unread statusd- Archive (done) notification — removes from inbox!- Pin/unpin notification (pinned appear at top)h- Collapse current repositoryx- Open action menu (run custom commands on notifications)
Multi-select
Space- Toggle selection on notification (magenta checkmark)Esc- Clear selection (or quit if no selection)Enter- Open all selected + mark as reado- Open all selected without marking as read.- Mark all selected as readd- Archive all selectedCtrl+A- Archive selected (or all if none selected)Ctrl+Alt+A- Toggle select all notifications in current repository
View & Filter
A- Toggle showing read notificationsE- Expunge read notifications/- Filter notifications (type to search, Enter to keep, Esc to clear)Tab- Cycle preview modes (Off → Horizontal → Vertical)J/K- Scroll preview (line by line)Shift+U/Shift+D- Scroll preview (5 lines)Ctrl+U/Ctrl+D- Scroll preview (page)1/2- Focus pane 1 (list) / pane 2 (preview)M- Toggle auto-mark-read on/off (persisted across sessions)
General
EscorqorCtrl+C- Quit application?- Show help
Help
↑/↓orj/k- Scroll helpPageUp/PageDown- Page scroll helpHome/End- Jump to top/bottom of help/- Search within help (type to filter, Enter to keep, Esc to clear)
Configuration
gh-news can be configured via a TOML file at ~/.config/gh-news/config.toml. All options are optional and have sensible defaults. CLI flags take precedence over config file settings.
Example Config
See also the example config file here.
# API & Network
= 120 # seconds, 0 to disable
= 30 # seconds
= 100 # limit notifications fetched
= 50 # notifications per API page
# Default filters (same as CLI flags)
= false # show read notifications (like --all)
= false # only participating (like --participating)
= "" # regex filter always applied
# Display
= "vertical" # "off", "horizontal", or "vertical"
= false # start with repos collapsed
= "auto" # "off", "auto", or "always"
# Behaviour
= true # mark notifications read when navigating to them
= false # archive notifications when navigating away (implies auto_mark_read)
# Notification cache (cached data is shown instantly on startup, then refreshed)
= "" # custom cache path (default: ~/.cache/gh-news/notifications_cache.json)
# External commands
= "" # custom browser, e.g. "firefox" (uses system default if empty)
# Notification hooks
= "" # command to run when new notifications appear
# GitHub Enterprise (optional)
= "github.com" # change for GHE, e.g. "github.mycompany.com"
Notification Hooks
Run a custom command when new notifications appear during auto-refresh:
= "/path/to/your/script.sh"
The command runs once per new notification with these environment variables:
| Variable | Description |
|---|---|
GH_NEWS_ID |
Notification ID |
GH_NEWS_TITLE |
Notification title |
GH_NEWS_REPO |
Repository name |
GH_NEWS_OWNER |
Repository owner |
GH_NEWS_TYPE |
Type (Issue, PullRequest, Discussion, etc.) |
GH_NEWS_REASON |
Reason (mention, review_requested, comment, etc.) |
GH_NEWS_URL |
Web URL (if available) |
GH_NEWS_UNREAD |
Read status (true/false) |
GH_NEWS_UPDATED_AT |
ISO 8601 timestamp (if available) |
Example: Desktop notification (Linux)
#!/bin/bash
Example: Sound alert
= "paplay /usr/share/sounds/freedesktop/stereo/message.oga"
Example: Conditional action
#!/bin/bash
if [; then
fi
Note: For commands with complex arguments or shell features, use a wrapper script.
Custom Actions
Define custom actions that can be run on notifications via the action menu (press x):
[[]]
= "Copy URL"
= "echo {url} | xclip -selection clipboard"
[[]]
= "Open in editor"
= "code --goto {url}"
[[]]
= "Add to TODO"
= "echo '* TODO {title}' >> ~/todo.org"
[[]]
= "Browse with fzf"
= "echo {url} | fzf --preview 'curl -s {}'"
= true # Suspend TUI for interactive commands
Actions support placeholder substitution:
| Placeholder | Description |
|---|---|
{id} |
Notification ID |
{title} |
Notification title |
{url} |
Web URL for the notification |
{repo} |
Repository name (without owner) |
{owner} |
Repository owner |
{full_name} |
Full repository name (owner/repo) |
{type} |
Notification type (Issue, PullRequest, etc.) |
{reason} |
Notification reason (mention, review_requested, etc.) |
{unread} |
Read status (true/false) |
Batch Placeholders (plural forms):
Use plural placeholders to run a single command with all selected notifications:
| Placeholder | Description |
|---|---|
{ids} |
All notification IDs, space-separated |
{titles} |
All notification titles, space-separated |
{urls} |
All web URLs, space-separated |
{repos} |
All repository names, space-separated |
{owners} |
All repository owners, space-separated |
{full_names} |
All full repository names, space-separated |
{types} |
All notification types, space-separated |
{reasons} |
All notification reasons, space-separated |
Example batch action:
[[]]
= "Open all in browser"
= "firefox {urls}"
= true
When you select multiple notifications and run this action, it executes once as firefox 'url1' 'url2' 'url3'.
Action Options:
| Option | Default | Description |
|---|---|---|
name |
required | Display name in the action menu |
command |
required | Command template with placeholders |
interactive |
false |
Suspend TUI and run command with full terminal access (for TUI tools like fzf, vim) |
Actions work with multi-select: select multiple notifications with Space, then press x to run an action on all of them. With singular placeholders, the command runs once per notification. With plural placeholders (e.g., {urls}), the command runs once with all values.
Environment Variables
GH_TOKEN- GitHub personal access token (takes precedence overGITHUB_TOKEN)GITHUB_TOKEN- GitHub personal access token (fallback ifGH_TOKENnot set)GH_NEWS_AUTO_REFRESH_INTERVAL- Auto-refresh interval in seconds (default: 120). Set to 0 to disable.