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 interval
- Preview notifications with rich details (GraphQL-powered for Issues, PRs, Discussions, Commits)
- Persisted preview cache for faster repeated detail views
- Regex filtering to filter specific notifications
- Pin important notifications
- Repository grouping with collapsible headers
- Notification hooks for custom commands
- Mute threads and repositories via the GitHub API
- Snooze notifications locally until a chosen time
- Custom actions with command templates
- Named views for instant filter preset switching
- Saved triage sessions for switching complete work contexts
- Mark notifications read/unread individually or in bulk
- Static display mode for scripting and pipelines
- Configuration doctor for validating filters, actions, token access, and views
- Progress reporting for supporting terminals via OSC
9;4 - GitHub Actions workflow run notifications (opt-in)
- GitHub Activity Events feed (opt-in)
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
Press Ctrl+R to force a refresh. gh-news shows the current refresh stage in the loading panel while it refreshes notifications and any enabled extra sources. When GitHub returns rate-limit headers, the status bar shows the remaining API quota.
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 (matched against repo, title, type, reason, and author)-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)--mark-read-archive- Mark all notifications as read and archive them (non-interactive)-s, --static-display- Print notifications and exit (for scripts)--check-config- Validate config, custom actions, filters, views, and GitHub authentication, then exit--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 readO- Open URL menu (open/copy/print) for the current selection.- Toggle read/unread statusd- Archive (done) notification — removes from inbox!- Pin/unpin notification (pinned appear at top)h- Collapse current repositoryx- Open action menu (built-in and custom actions 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- Act on selected notifications, or on the current filtered list if none are selectedCtrl+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)V- Switch named view (built-in and custom filter presets)S- Switch saved triage sessionTab- 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 (matched against: repo title type reason author)
# Structured exclude filters
= ["CheckSuite"] # by type: Issue, PR, Release, CheckSuite, etc.
= ["subscribed"] # by reason: subscribed, ci_activity, etc.
= ["noisy-org/*"] # by repo: exact or glob pattern
= ["^Bump ", "\\[bot\\]"] # by title: regex patterns (case-insensitive)
# Theme
= "tokyo_night" # see Themes section below for all options
# [theme_colors] # override individual palette colours (hex)
# blue = "#7aa2f7"
# Display
= "vertical" # "off", "horizontal", or "vertical"
= false # start with repos collapsed
= "auto" # "off", "auto", or "always"
= "right_aligned" # "right_aligned", "icon_only", or "two_line"
# Behaviour
= false # mark notifications read when navigating to them (disabled by default)
= 400 # dwell time (ms) before marking as read (only when auto_mark_read is enabled)
= false # archive notifications when navigating away (implies auto_mark_read)
= true # mark notifications read when opening them in the browser
# Notification cache (cached data is shown instantly on startup, then refreshed).
# Preview details are also cached locally and reused until the notification changes.
= "" # custom cache path (default: ~/.cache/gh-news/notifications_cache.json)
# External commands
= "" # custom browser, e.g. "firefox" (uses system default if empty)
= "builtin" # how `o`/Enter delivers URLs: "builtin" (browser), "osc" (OSC 52 clipboard), "print" (suspend UI, print to stdout)
# Notification hooks
= "" # command to run when new notifications appear
# GitHub Enterprise (optional)
= "github.com" # change for GHE, e.g. "github.mycompany.com"
# GitHub Actions workflow notifications (opt-in)
= false # set to true to show workflow run notifications
= true # only show failed/cancelled runs (default when actions enabled)
= [] # repos to watch (empty = derive from your notifications)
# GitHub Activity Events feed (opt-in)
= false # set to true to show activity events
= [] # filter event types, e.g. ["WatchEvent", "ForkEvent"] (empty = all)
# Repository event watching (opt-in)
= [] # repos to watch for all events, e.g. ["owner/repo", "org/*"]
# supports glob patterns; event_types filter applies here too
Themes
gh-news ships with built-in colour themes. Set the theme key in your
config to switch:
| Name | Style |
|---|---|
tokyo_night (default) |
Dark blue with soft white text |
catppuccin_mocha |
Warm dark (Catppuccin dark variant) |
catppuccin_latte |
Light (Catppuccin light variant) |
nord |
Arctic blue-grey |
dracula |
Dark with vivid accents |
gruvbox_dark |
Retro warm colours |
dracula_light |
Dracula light variant |
narna |
Balanced dark theme with blue accents |
clean_light |
Optimized for light terminal backgrounds |
rose_pine_dawn |
Rosé Pine Dawn (Light) |
one_light |
Atom One Light |
everforest_light |
Everforest Light (Medium) |
everforest_dark |
Everforest Dark (Medium) |
one_dark |
One Dark |
rose_pine |
Rosé Pine (Dark) |
ayu_mirage |
Ayu Mirage |
modern |
Sleek, modern dark theme with vibrant accents |
kanagawa |
Kanagawa (Wave) |
solarized_dark |
Solarized dark |
solarized_light |
Solarized light |
gruvbox_light |
Gruvbox light |
monokai |
Monokai |
= "catppuccin_mocha"
You can also override individual palette colours on top of any theme using the
[theme_colors] table. Each value is a hex string ("#rrggbb" or "rrggbb"):
= "nord"
[]
= "#7aa2f7"
= "#ff0000"
Available colour fields: bg, bg_dark, bg_highlight, fg, fg_muted,
fg_dim, blue, cyan, green, yellow, red, magenta, orange.
Theme Screenshots
rose_pine_dawn
one_light
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.
Built-in Actions
The action menu (press x) always includes these built-in actions:
| Action | Description |
|---|---|
| Mute Thread | Sets the thread subscription to ignored via the GitHub API. Future notifications for the thread are suppressed until you comment or are @mentioned again. |
| Mute Repository | Sets the repository subscription to ignored via the GitHub API. Suppresses all notifications from that repository. |
| Snooze (4 hours) | Hides the notification until 4 hours from now. |
| Snooze (tomorrow 09:00) | Hides the notification until 09:00 the following day. |
| Snooze (next week) | Hides the notification until 09:00 one week from now. |
Snoozed notifications are hidden from the default view and stored locally. They reappear automatically once the snooze period expires. Mute actions are reflected back to GitHub immediately.
Custom Actions
Define custom actions that can be run on notifications via the action menu (press x):
[[]]
= "Copy URL"
= "echo {url} | xclip -selection clipboard"
= 1 # Lower numbers sort earlier in the action menu
[[]]
= "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 |
{number} |
PR/issue/discussion number (empty for other types) |
{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 |
priority |
unset | Lower numbers sort earlier in the action menu |
interactive |
false |
Suspend TUI and run command with full terminal access (for TUI tools like fzf, vim) |
show_output |
false |
Capture command output and display it in a scrollable TUI popup (incompatible with interactive) |
With singular placeholders, the command runs once per selected notification. With plural placeholders (e.g., {urls}), it runs once with all values.
Named Views
Named views are saved filter presets you can switch between instantly with V. Several built-in views are always available, and you can add your own in config.toml.
Built-in views:
| View | What it shows |
|---|---|
| Participating | Everything except passive subscriptions and CI noise (subscribed, ci_activity reasons excluded) |
| Mentions | Direct @mention and team mention notifications |
| Review Requests | PRs where your review has been requested |
| Assigned | Issues and PRs assigned to you |
| My Activity | Notifications on threads you opened or created |
| Security | Security alerts and advisories |
| Dependabot | Dependabot version-bump PRs (titles matching "Bump X from Y to Z") and any notification where "dependabot" appears |
| Bots | Activity from bots whose login ends in [bot] (matched against the enriched author field) |
Custom views:
Define your own in config.toml using [[views]] sections. All filter fields are optional — unset fields inherit from the global config defaults.
[[]]
= "Fires"
= ["Release", "RepositoryVulnerabilityAlert"]
= ["subscribed"]
[[]]
= "My Org"
= ["*"] # exclude everything …
= "my-org/" # … that doesn't match this repo pattern
[[]]
= "Dependabot"
= "dependabot"
View fields:
| Field | Description |
|---|---|
name |
Display name shown in the picker (required) |
filter |
Regex applied to repo title type reason author (author populated by background enrichment) |
exclude_types |
Override global exclude_types for this view |
exclude_reasons |
Override global exclude_reasons for this view |
exclude_repos |
Override global exclude_repos for this view (glob patterns) |
exclude_subjects |
Override global exclude_subjects for this view (regex, case-insensitive) |
User-defined views appear after the built-in views in the picker. Selecting 0. Default clears the active view and restores the session's base filter. The / search works within the active view, and bulk actions (Ctrl+A) apply to the visible filtered result set.
Saved Triage Sessions
Saved sessions are named work contexts you can switch to with S. They can activate a view, layer on an extra regex filter, change whether read notifications are shown, set the preview mode, and collapse repositories.
[[]]
= "Reviews"
= "Review Requests"
= "my-org/"
= false
= "vertical"
= true
Session fields:
| Field | Description |
|---|---|
name |
Display name shown in the session picker (required) |
view |
Built-in or custom view name to activate |
filter |
Extra regex filter layered on top of the selected view/default filter |
show_read |
Override whether read notifications are fetched and shown |
preview_mode |
Override preview mode: off, horizontal, or vertical |
repos_collapsed |
Start the session with repositories collapsed or expanded |
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.