Skip to content

chmouel/gh-news

Repository files navigation

gh-news

GitHub notifications TUI built with Rust and ratatui.

Screenshot

ghnews

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 with rich details (GraphQL-powered for Issues, PRs, Discussions, Commits)
  • 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
  • Mark notifications read/unread individually or in bulk
  • Static display mode for scripting and pipelines
  • GitHub Actions workflow run notifications (opt-in)
  • GitHub Activity Events feed (opt-in)

Installation

Install as a gh CLI extension (easiest):

gh extension install chmouel/gh-news

Then run it:

gh news

Setup

You need a GitHub token. The app looks for it in this order:

  1. GH_TOKEN env var
  2. GITHUB_TOKEN env var
  3. Your gh CLI config at ~/.config/gh/hosts.yml (or $XDG_CONFIG_HOME/gh/hosts.yml)
  4. gh auth token (queries the gh CLI, 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

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 (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)
  • -s, --static-display - Print notifications and exit (for scripts)
  • --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

gh news --filter "my-org/my-repo" # Filter to specific repos
gh news --participating # Only things you're involved in
gh news --mark-read # Mark everything read:
gh news --static-display | grep "something" # List notifications without TUI

Keybindings

Navigation

  • / or j/k - Navigate notifications, or repository headers when repositories are collapsed
  • Home/End - Jump to first/last notification
  • PageUp/PageDown - Page navigation (or scroll preview if shown)

Actions

  • Enter - Open notification in browser and mark as read, or toggle repository collapse on headers
  • o - Open notification in browser without marking as read
  • O - Open URL menu (open/copy/print) for the current selection
  • . - Toggle read/unread status
  • d - Archive (done) notification — removes from inbox
  • ! - Pin/unpin notification (pinned appear at top)
  • h - Collapse current repository
  • x - 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 read
  • o - Open all selected without marking as read
  • . - Mark all selected as read
  • d - Archive all selected
  • Ctrl+A - Act on selected notifications, or on the current filtered list if none are selected
  • Ctrl+Alt+A - Toggle select all notifications in current repository

View & Filter

  • A - Toggle showing read notifications
  • E - Expunge read notifications
  • / - Filter notifications (type to search, Enter to keep, Esc to clear)
  • V - Switch named view (built-in and custom filter presets)
  • 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

  • Esc or q or Ctrl+C - Quit application
  • ? - Show help

Help

  • / or j/k - Scroll help
  • PageUp/PageDown - Page scroll help
  • Home/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
auto_refresh_interval = 120  # seconds, 0 to disable
api_timeout = 30             # seconds
max_notifications = 100      # limit notifications fetched
pagination_size = 50         # notifications per API page

# Default filters (same as CLI flags)
show_read = false            # show read notifications (like --all)
participating_only = false   # only participating (like --participating)
default_filter = ""          # regex filter always applied (matched against: repo title type reason author)

# Structured exclude filters
exclude_types = ["CheckSuite"]           # by type: Issue, PR, Release, CheckSuite, etc.
exclude_reasons = ["subscribed"]         # by reason: subscribed, ci_activity, etc.
exclude_repos = ["noisy-org/*"]          # by repo: exact or glob pattern
exclude_subjects = ["^Bump ", "\\[bot\\]"] # by title: regex patterns (case-insensitive)

# Theme
theme = "tokyo_night"            # "tokyo_night", "catppuccin_mocha", "catppuccin_latte", "nord", "dracula", "gruvbox_dark"
# [theme_colors]                 # override individual palette colours (hex)
# blue = "#7aa2f7"

# Display
default_preview_mode = "vertical"    # "off", "horizontal", or "vertical"
repos_collapsed = false              # start with repos collapsed
org_grouping = "auto"               # "off", "auto", or "always"
list_layout = "right_aligned"       # "right_aligned", "icon_only", or "two_line"

# Behaviour
auto_mark_read = false                # mark notifications read when navigating to them (disabled by default)
auto_mark_read_delay_ms = 400        # dwell time (ms) before marking as read (only when auto_mark_read is enabled)
auto_archive = false                 # archive notifications when navigating away (implies auto_mark_read)
auto_mark_on_open = true             # mark notifications read when opening them in the browser

# Notification cache (cached data is shown instantly on startup, then refreshed)
cache_file = ""              # custom cache path (default: ~/.cache/gh-news/notifications_cache.json)

# External commands
browser_command = ""         # custom browser, e.g. "firefox" (uses system default if empty)
open_method = "builtin"      # how `o`/Enter delivers URLs: "builtin" (browser), "osc" (OSC 52 clipboard), "print" (suspend UI, print to stdout)

# Notification hooks
on_new_notification_command = ""  # command to run when new notifications appear

# GitHub Enterprise (optional)
github_host = "github.com"   # change for GHE, e.g. "github.mycompany.com"

# GitHub Actions workflow notifications (opt-in)
enable_actions = false       # set to true to show workflow run notifications
actions_failed_only = true   # only show failed/cancelled runs (default when actions enabled)
actions_repos = []           # repos to watch (empty = derive from your notifications)

# GitHub Activity Events feed (opt-in)
enable_events = false        # set to true to show activity events
event_types = []             # filter event types, e.g. ["WatchEvent", "ForkEvent"] (empty = all)

# Repository event watching (opt-in)
watch_repos = []             # 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 six 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
theme = "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"):

theme = "nord"

[theme_colors]
blue = "#7aa2f7"
red  = "#ff0000"

Available colour fields: bg, bg_dark, bg_highlight, fg, fg_muted, fg_dim, blue, cyan, green, yellow, red, magenta, orange.

Notification Hooks

Run a custom command when new notifications appear during auto-refresh:

on_new_notification_command = "/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
notify-send "GitHub: $GH_NEWS_TYPE" "$GH_NEWS_TITLE"

Example: Sound alert

on_new_notification_command = "paplay /usr/share/sounds/freedesktop/stereo/message.oga"

Example: Conditional action

#!/bin/bash
if [ "$GH_NEWS_REASON" = "review_requested" ]; then
    notify-send -u critical "Review Requested" "$GH_NEWS_TITLE"
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.

All built-in actions support multi-select: select notifications with Space, then press x and choose an action to apply it to all selected notifications.

Without a manual selection, bulk actions such as Ctrl+A apply to the current filtered list, including any active named view and / search.

Custom Actions

Define custom actions that can be run on notifications via the action menu (press x):

[[actions]]
name = "Copy URL"
command = "echo {url} | xclip -selection clipboard"
priority = 1  # Lower numbers sort earlier in the action menu

[[actions]]
name = "Open in editor"
command = "code --goto {url}"

[[actions]]
name = "Add to TODO"
command = "echo '* TODO {title}' >> ~/todo.org"

[[actions]]
name = "Browse with fzf"
command = "echo {url} | fzf --preview 'curl -s {}'"
interactive = true  # Suspend TUI for interactive commands

Custom actions appear after the built-in actions by default. Set priority to a number to move a custom action earlier in the menu; lower numbers sort first. For example, priority = 1 places an action above the built-in mute and snooze entries.

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:

[[actions]]
name = "Open all in browser"
command = "firefox {urls}"
interactive = 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)

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.

Named Views

Named views are saved filter presets you can switch between instantly with V. Six 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 GitHub login ends in [bot] (e.g. copilot-pull-request-reviewer[bot], dependabot[bot], github-actions[bot]). Matched against the author field — notifications appear here after background enrichment completes.

When a view is active, the list panel title and border turn cyan and show the view name, so it is always clear what is being filtered.

Custom views:

Define your own in config.toml using [[views]] sections. All filter fields are optional — unset fields inherit from the global config defaults.

[[views]]
name = "Fires"
exclude_types = ["Release", "RepositoryVulnerabilityAlert"]
exclude_reasons = ["subscribed"]

[[views]]
name = "My Org"
exclude_repos = ["*"]          # exclude everything …
filter = "my-org/"             # … that doesn't match this repo pattern

[[views]]
name = "Dependabot"
filter = "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. Press V to open the picker, navigate with j/k or press the item number, and Esc to close without changing. Selecting 0. Default clears the active view and restores the session's base filter, including any CLI --filter supplied at launch.

The / search filter works within the active view — pressing Esc in search mode returns to the view filter rather than clearing it entirely.

Bulk actions without a manual selection also follow the current view/search filter, so Ctrl+A acts on the visible filtered result set rather than the whole inbox.

Environment Variables

  • GH_TOKEN - GitHub personal access token (takes precedence over GITHUB_TOKEN)
  • GITHUB_TOKEN - GitHub personal access token (fallback if GH_TOKEN not set)
  • GH_NEWS_AUTO_REFRESH_INTERVAL - Auto-refresh interval in seconds (default: 120). Set to 0 to disable.

Theme

rose_pine_dawn

image

one_light

image

License

Apache 2.0