Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
ghtui
A keyboard-driven terminal client for GitHub, written in Rust.
The aim is simple: never open github.com for day-to-day work. You browse repositories, issues, pull requests, Actions and profiles the way you would on the website, and you review pull requests in a diff viewer that does more than GitHub's. It looks like GitHub, in flat Material 3 colors adapted to the terminal.
Where it stands
ghtui is pre-release, but it's usable every day.
- Browsing covers most of GitHub: your home page, repositories (files, README, branches, tags, releases, commits, blame, comparisons), issues, pull requests, Actions runs and job logs, discussions, wikis, milestones, deployments, security advisories, gists, teams, profiles and search. Every github.com link opens in ghtui unless it can only work in a browser (a download, settings, notifications), and then it says why.
- Reviewing is where ghtui goes furthest. Diffs are computed locally with git, highlighted with tree-sitter, and shown unified or split. You can reply, resolve, comment on lines or ranges, suggest changes, and submit a review.
- Acting works from a page or from a row in a list: merge, approve, mark ready for review, update a branch, close or reopen, re-run or cancel a workflow run, comment and star.
Not there yet: notifications, projects, GitHub Enterprise Server, code navigation and structural diffs.
Goals
- Replace the website for daily use. If you'd reach for the browser, that's a gap worth closing.
- A better diff than GitHub's. Show what really changed, and only what you haven't seen.
- Say what GitHub said. A list cut short says what it left out. A failure says why. A change you made shows once GitHub shows it, not before.
- One verb per key. A key means the same thing on every screen. Where it doesn't apply, it says so instead of doing something else.
Install
You need Rust 1.97 or newer and git 2.36 or newer.
Usage
ghtui takes its token from GH_TOKEN, then GITHUB_TOKEN, then gh auth token. If none works, it asks you to run gh auth login. The token stays
in memory: it's never logged, written to disk, or put on a command line.
Reading needs read access. Acting needs write access where you act (with a
classic token, the repo scope covers it all).
A short tour
The screen is laid out like GitHub's:
- The header shows where you are, a search field and your account.
- A page's tabs sit under the header: Code, Issues, Pull requests and so on.
- The content is in GitHub's boxes, with a sidebar on wide screens.
- The status bar names the keys that do something here.
Space lists everything you can do where you are. : opens a command
palette that also goes places (owner/repo#123, @user, a URL).
Home is your own saved searches: by default your review requests, your
open pull requests and your repositories. Save any list to Home from its
Space menu, and rename, reorder or remove sections in place.
Tabs work like a browser's: T opens the selection in a new tab, [
and ] switch between them, and they come back next time you start.
The diff viewer (a pull request's Files changed) adds what GitHub's doesn't:
- changed words within a line, and changed letters within a word;
- moved code, with a jump between its two ends;
- formatting-only changes folded away;
- scope names in hunk headers (
impl Doc › fn offset); - "changes since your last review", even across force-pushes and rebases;
- local per-change "reviewed" marks beside GitHub's per-file "viewed".
Comments are kept on disk until you submit, so a crash never loses them. Threads that GitHub calls outdated are moved to their line when it still exists.
Acting on GitHub asks first wherever it matters (merging, closing, cancelling, updating a branch). It asks GitHub what you may do and says why when you can't. After a change, the page follows GitHub until it shows it.
Keys
Arrows move, Enter opens, Esc goes back, and each letter is one verb.
Less common actions have no key; they're in the Space menu. Every key can
be rebound (see Configuration).
| Keys | Action |
|---|---|
↑ ↓ (j k) |
Move |
← → (h l) |
Previous / next tab |
Enter |
Open the selection (on a comment: quote reply) |
Esc ⌫ alt-← |
Back (clears a search or selection first) |
alt-→ |
Forward |
PgUp PgDn |
Page up / down |
Home End (g G) |
Top / bottom |
1–9 |
Tab by number |
Space ? |
Everything you can do here |
/ |
Search (on a list, the diff or a log: search that) |
f |
Find a file |
b |
Switch branches or tags |
c |
Comment (on a thread: reply) |
s |
Star / unstar |
M |
Merge |
A |
Approve |
W |
Mark ready for review |
B |
Update the branch from its base |
X |
Close or reopen; cancel a running workflow run |
ctrl-r |
Re-run the workflow run, its failed jobs, or the job |
alt-↑ alt-↓ |
Move a Home section up / down |
Delete |
Delete a Home section or a draft comment |
ctrl-z |
Bring back what was just deleted |
n p |
Next / previous search match in a job's log |
o |
Open in the browser |
y |
Copy the link |
i |
Follow a link by its letters |
I |
Follow a link by its letters, in the browser |
u |
Up a level |
T |
Open the selection in a new tab |
[ ] |
Previous / next open tab |
alt-1–alt-9 |
Open tab by number |
ctrl-w |
Close the tab |
H |
Home |
r |
Refresh |
: ctrl-k |
Command palette |
q ctrl-c |
Quit |
Reviewing, in a pull request's Files changed:
| Keys | Action |
|---|---|
n p |
Next / previous change |
N P |
Next / previous unresolved thread |
⇧↓ ⇧↑ |
Next / previous file |
⇧← ⇧→ |
Split view: the old / new half of the row |
⇥ |
Switch between the file tree and the diff |
U |
Next unviewed file |
v |
Mark the file viewed (synced with GitHub) |
m |
Mark the change reviewed (local) |
c |
Comment on the line or selection; on a thread, reply |
C |
Comment on the whole file |
R |
Resolve or unresolve the thread |
x |
Select lines |
e |
Show more context |
F |
Show the whole file |
S |
Split or unified view |
t |
Show or hide the file tree |
J |
Jump to the other end of moved code |
w |
Ignore whitespace changes |
a |
Submit your review |
Delete |
Delete the draft comment |
ctrl-z |
Bring back the draft just deleted |
In the comment editor, ctrl-s adds the comment, ctrl-e continues in
$EDITOR, and Esc cancels.
Configuration
~/.config/ghtui/config.toml, or --config <path>. Everything is
optional, and an unknown key is an error, so typos don't go unnoticed.
[]
= "auto" # auto | light | dark
= "#3f6fb5" # the whole color scheme is generated from this
= "auto" # auto | truecolor | 256
[]
= false
= "auto" # auto | compact | comfortable
[] # each action listed replaces its default keys
= ["j", "<Down>", "<C-n>"]
= ["O"]
[[]] # a Home section: a title and a GitHub search
= "Bugs in our repos"
= "is:open label:bug org:my-org sort:updated-desc"
= 10
A section lists pulls, issues or repos, and shows up to 30 rows; its
last row opens the rest. Without any [[home]] tables, Home shows your
review requests, your open pull requests and your repositories. Action
names for [keys] are in the actions! table in
src/keymap.rs.
Design
An Elm-style app. Keys and network replies become messages. A single
update changes the state and returns commands, and the view is a pure
function of the state. Network and git work runs in background tasks, so
the screen never waits on them.
Wrong states are made hard to write. Most of ghtui's bugs would be GitHub's data read wrongly: a list cut short shown as whole, the oldest page taken for the newest, a diff from the wrong base. So:
- a partial list is a type that knows what it left out;
- each GraphQL query is checked against GitHub's schema;
- an answer that doesn't add up is reported, not hidden;
- where two places must agree (what a page reserves and what fills it, what a change is about and what shows it), one of them owns the fact.
Non-test code can't panic: the lints reject unwrap, indexing and the like.
Diffs come from git, not from GitHub. ghtui uses your clone if you start it inside one, and otherwise a bare partial clone in its cache. It diffs from the merge base, as GitHub does, and can also rebuild the diff as it was at your last review. Comments still anchor to GitHub's own hunks, so they land where GitHub expects them. Git never sees the token on a command line or in a config file.
Fast from the cache. The first paint comes from a local cache (redb), then everything is refreshed in the background. REST requests revalidate with ETags, so unchanged data costs nothing against the rate limit.
Colors from one seed. Light and dark schemes are generated with Material 3's tonal palettes. Every foreground and background pair the UI uses is tested for contrast in both schemes, in truecolor and 256 colors.
It's one crate. The top of src/ is the app (routes, state, keys, view,
runtime) and the binary that runs it; the rest are its modules:
| Module | What it does |
|---|---|
ui |
Pages and widgets, pure functions of data |
api |
GitHub over GraphQL (cynic) and REST: auth, retries, limits |
schema |
GitHub's GraphQL schema, which the queries are typed against |
store |
The cache, and review drafts kept outside it |
git |
The git CLI: clones, fetches, credentials, blobs |
diff |
Hunks (imara-diff), highlighting (tree-sitter), the diff model |
theme |
Color schemes, contrast, terminal detection |
Development
The tests come at GitHub's data from several sides:
- every query is checked against the schema;
- recorded GitHub responses are replayed offline;
- git is the oracle for diffs, on real repositories over
file://; - full screens are snapshotted in light, dark and 256 colors
(
INSTA_UPDATE=always cargo testto accept changes, then review them); - property tests throw random keys, resizes and replies at the app.
A contract suite checks ghtui's assumptions against live GitHub, read-only
(cargo test --test contract -- --ignored contract). Every
kind of github.com link is listed with what it must open in
tests/github_urls.txt.
demo/record.sh records a captioned tour of ghtui to a video, on a hidden
Hyprland monitor (it needs foot, grim and ffmpeg, and an unlocked session). It
uses a scratch config and cache, and only reads public GitHub.
Timing tests (cargo test --release -- --ignored --nocapture)
hold scrolling a 500-file pull request to under 8ms a frame (about 0.5ms
today), and diffing a 50,000-line file to under 100ms (about 32ms).
Known limitations
- github.com only.
- Long diff lines are cut at the pane's edge; there's no wrapping yet.
- Comments in diff threads show as plain text, not rendered Markdown.
- Markdown in the terminal: images show their alt text, and HTML is reduced to its text and links.
- The cache isn't kept per account. After switching accounts, the old account's pages show until they refresh.
- Merging doesn't offer auto-merge or merge queues.