Skip to content
TomzxCodePublic

About

An extended GitHub CLI. It caches issues, pull requests, and comments to disk to minimise API calls, and provides PR/issue comment operations beyond the standard gh CLI.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

ghx

An extended GitHub CLI. It caches issues, pull requests, and comments to disk to minimise API calls, and provides PR/issue comment operations beyond the standard gh CLI: inline review comments, line-range comments, thread replies, pending reviews, and local comment stashes.

Issue/PR cache lives at ~/.cache/ghx/cache/ (a single cache.db by default, or per-repository JSON with --storage file). Review-comment stashes live at ~/.cache/ghx/stash/<owner>/<repo>/<pr>.

Installation

go install github.com/tomzxcode/ghx@main

Or download a pre-built binary from the releases page.

Build from source with make

Requires Go 1.26 or later.

make build                        # produces ./ghx
make install                      # installs to ~/.local/bin/ghx, no sudo
sudo make install PREFIX=/usr/local   # system-wide install to /usr/local/bin/ghx

make install installs under $(PREFIX)/bin. PREFIX defaults to $(HOME)/.local (the standard per-user location on Linux), so a plain make install needs no sudo. Set PREFIX=/usr/local for a system-wide install. make uninstall removes the binary from the same prefix.

Authentication

Set a GitHub personal access token in your environment:

export GH_TOKEN=ghp_...

GITHUB_TOKEN is also supported and checked as a fallback after GH_TOKEN.

Alternatively, if you have the GitHub CLI installed and authenticated, ghx will use it as a fallback (gh auth token).

Usage

All commands accept --repo [HOST/]OWNER/REPO. When omitted, the repository is detected from the origin remote of the current directory.

Cache

Pre-populate the local cache with all issues and PRs (including comments):

ghx cache
ghx cache --repo cli/cli
ghx cache --cache-duration 120   # record a 2-hour freshness window for reads
ghx cache --if-stale             # do nothing while the cache is still fresh
ghx cache --since 2026-09-01     # refresh entries created/updated since a date
ghx cache --type issues            # refresh only issues (or prs)
ghx cache --force                  # force full re-fetch

Example output (first-time run):

Caching issues for octocat/hello-world...
Cached 42 issue(s).
Caching pull requests for octocat/hello-world...
Cached 15 pull request(s).
Cache updated. Valid for 60 minute(s).

Example output (subsequent run, delta fetch):

Fetching issues updated since 2025-01-15 10:30 for octocat/hello-world...
Cached 3 issue(s).
Fetching PRs updated since 2025-01-15 10:30 for octocat/hello-world...
Cached 1 pull request(s).
Cache updated. Valid for 60 minute(s).

List and view commands serve from the cache when it is fresh, and fall back to the GitHub API otherwise. When they fall back, they also write what they fetch into the cache (using the same records ghx cache produces), so browsing incrementally warms the cache.

Storage backends

By default the cache uses an indexed SQLite database (cache.db), which is several times faster than the legacy file backend for listing, filtering, and searching on large repositories. The file backend (one JSON file per issue/PR) is still available:

ghx --storage file cache --repo cli/cli   # or set GHX_STORAGE=file
ghx cache migrate                         # copy an existing file cache into SQLite

An existing file-based cache is not read by the default backend until it is migrated. cache migrate is non-destructive (the file cache is kept) and idempotent. See the cache guide for details.

Issues

# List open issues (default)
ghx issue list

# Filtering
ghx issue list --state all
ghx issue list --state closed
ghx issue list --author alice
ghx issue list --assignee bob
ghx issue list --label bug
ghx issue list --label bug --label p1   # AND logic
ghx issue list --milestone v2.0
ghx issue list --mention carol
ghx issue list --app dependabot
ghx issue list --search "memory leak"
ghx issue list --limit 10

# View a single issue
ghx issue view 42
ghx issue view 42 --comments
ghx issue view 42 --refresh   # force fetch from GitHub and update cache

Example output (issue list):

#42  Fix login bug                          bug, priority     5  2025-01-15
#41  Update README                          documentation     2  2025-01-14
#40  Very long title that will be truncat…  enhancement       0  2025-01-13

Example output (issue view 42 --comments):

#42 Fix login bug
OPEN • opened by octocat • 5 comment(s)

Labels:    bug, priority
Assignees: alice
Milestone: v1.0
Created:   2025-01-10 14:30
URL:       https://github.com/octocat/hello-world/issues/42

The login page crashes when...

── Comment 1 by reviewer1 (2025-01-12 09:15) ──

I can reproduce this on Firefox.

── Comment 2 by octocat (2025-01-12 10:30) ──

Thanks, I'll look into it.

Pull Requests

# List open PRs (default)
ghx pr list

# Filtering
ghx pr list --state all
ghx pr list --state merged
ghx pr list --author alice
ghx pr list --assignee bob
ghx pr list --label enhancement
ghx pr list --base main
ghx pr list --head feat/dark-mode
ghx pr list --draft
ghx pr list --app dependabot
ghx pr list --search "fix crash"
ghx pr list --limit 10

# JSON output, gh-compatible field selection
ghx pr list --state merged --head feat/dark-mode --json number,headRefOid,mergedAt
ghx pr list --json          # every supported field

# View a single PR
ghx pr view 10
ghx pr view 10 --comments
ghx pr view 10 --refresh      # force fetch from GitHub and update cache

Example output (pr list):

#10  Add new feature         feature → main    approved          3  2025-01-15
#9   Fix edge case [draft]   fix-bug → dev     review required   1  2025-01-14

Example output (pr view 10 --comments):

#10 Add new feature
OPEN • opened by octocat • 3 comment(s)

Branch:    feature → main
Review:    approved
Labels:    enhancement
Assignees: alice, bob
Created:   2025-01-08 11:00
URL:       https://github.com/octocat/hello-world/pull/10

This PR adds a new feature...

── Comment 1 by reviewer1 (2025-01-09 08:30) ──

Looks good, approved!

── Comment 2 by octocat (2025-01-09 09:00) ──

Thanks for the review.

Repositories

List locally cached repositories:

ghx repo list

Example output:

REPO                      ISSUES  PRS  CACHED AGE  STATUS  ISSUE-LIST  ISSUE-VIEW  PR-LIST  PR-VIEW
octocat/hello-world       42      15   2h30m       fresh   92% (13)    80% (5)     75% (8)  60% (5)
alice/another-repo        10      3    1d          stale   —           —           —        —

The four hit-rate columns show how often each retrieval operation was served from the local cache instead of calling GitHub, with the total number of recorded retrievals in parentheses. They come from the local telemetry database and are split by operation: issue list, issue view, PR list, and PR view. A dash means no retrieval of that kind has been recorded for the repository, for example when telemetry is disabled or was cleared.

Delete the cached data for one repository:

ghx repo delete octocat/hello-world

Web UI

Browse the cache in a GitHub-like web UI (repo switcher, issue/PR lists and details, Ctrl+P search palette):

ghx cache && ghx serve
ghx serve --host 127.0.0.1 --port 9000 --open

JSON-RPC API

Run a local JSON-RPC 2.0 server so clients can call the CLI without spawning a process per call:

ghx rpc serve                                   # POST /rpc on 127.0.0.1:8090
ghx rpc methods                                 # list methods
ghx rpc call repo.list
ghx rpc call issue.list '{"repo":"cli/cli","state":"open","limit":20}'
ghx rpc call pr.view '{"repo":"cli/cli","number":1234}'

Typed methods cover the read and cache commands (repo.list, repo.delete, issue.list, issue.view, pr.list, pr.view, pr.threads, cache.refresh). The cli.run method executes any other ghx command and returns its stdout, stderr, and exit code:

ghx rpc call cli.run '{"args":["pr","comment","10","--body","Looks good"]}'
ghx rpc exec -- pr comment 10 --body "Looks good"

Any command can run against a server with --remote:

ghx --remote http://127.0.0.1:8090 issue list --state open

ghx serve also exposes the API at /rpc on its web UI port. See the JSON-RPC guide for the method reference, authentication, and security notes.

PR comments (inline, replies, pending, stash)

# Top-level comment
ghx pr comment 10 --body "Looks good"

# Inline comment on a line or range
ghx pr comment 10 --file src/main.go --line 42 --body "Nit"
ghx pr comment 10 --file src/main.go --line 42-45 --body "Consider extracting"

# File-level comment (no --line)
ghx pr comment 10 --file src/main.go --body "Overall looks clean"

# Reply to an existing review thread
ghx pr comment 10 --reply-thread <thread-id> --body "Agreed"

# Add to a pending review instead of posting immediately
ghx pr comment 10 --file src/main.go --line 42 --body "Nit" --pending

# Save to a local stash entry instead of calling the API
ghx pr comment 10 --file src/main.go --line 42 --body "Nit" --stash

# Body from stdin or a file
ghx pr comment 10 --body-file -
ghx pr comment 10 --body-file comment.txt

Edit / delete comments

ghx pr comment edit   <comment-id> --body "Updated text"
ghx pr comment delete <comment-id>
ghx issue comment edit   <comment-id> --body "Updated text"
ghx issue comment delete <comment-id>

Use ghx pr threads <number> --ids or ghx issue view <number> --ids to find comment IDs.

Review threads

ghx pr threads 10                       # open threads, with comments
ghx pr threads 10 --thread <thread-id>  # show one thread
ghx pr threads 10 --ids                 # include comment IDs
ghx pr threads 10 --state all
ghx pr threads 10 --state resolved

Pending reviews

ghx pr review create  10
ghx pr review list    10
ghx pr review submit  10 --event COMMENT
ghx pr review submit  10 --event APPROVE --body "LGTM"
ghx pr review submit  10 --event REQUEST_CHANGES --body "See comments"
ghx pr review discard <review-id>

Review-comment stashes

ghx pr review stash push 10 -m "nit comments"   # save pending -> stash@{0}
ghx pr review stash list 10                     # list stash entries
ghx pr review stash pop  10                     # restore stash@{0} into a pending review
ghx pr review stash pop  10 --stash 1           # restore stash@{1}
ghx pr review stash drop 10                     # discard stash@{0}

Issue comments

ghx issue comment 42 --body "Fixed in #50"
ghx issue comment 42 --body-file -
ghx issue view 42 --ids                 # show comment IDs

Stats

Generate an HTML report of pull request activity (lead times, review engagement, PR sizes, trends) for one or more repositories:

ghx cache                                            # populate the cache first
ghx stats --from 2026-05-01 --to 2026-05-31          # current repository
ghx stats acme/widget acme/gadget --from 2026-05-01 --to 2026-05-31 \
    --author alice,carol --reviewer alice,bob -o may-report.html

The report includes summary cards, a monthly trend chart, an author × reviewer matrix, per-author lead times (time to merge, time in draft, time to first review, time to approve, time from approval to merge), contribution and PR size breakdowns, reviewer engagement with review-request response times, a PR size versus merge time table, peak-activity and size-distribution charts, notable PR lists, and an optional appendix of individual PRs (--list-prs). Run ghx cache --force once after upgrading so reports can use the review, timeline and size data collected during caching.

Performance telemetry

Record per-call API and cache timings locally, then inspect or export them:

ghx cache --repo cli/cli                  # recording is on by default
ghx telemetry summary --repo cli/cli        # counts, p50/p95, retry rate by operation kind
ghx telemetry export --kind pr_full_page -o pages.json
ghx telemetry disable                       # turn recording off globally
ghx telemetry enable                        # turn it back on
ghx telemetry status                        # show the effective state
ghx telemetry clear --before 2026-09-01     # delete old events

Records are local-only and never uploaded. Turn recording off globally with ghx telemetry disable, or for one run with --telemetry=false (GHX_TELEMETRY=0); --telemetry-db / GHX_TELEMETRY_DB override the database path. A failure to record never fails or slows a command. The mock server can inject latency fitted from recorded events, so generated scenarios reproduce measured cost:

ghx mock serve --preset default --latency-from ~/.local/share/ghx/telemetry.db

Benchmarking against gh

scripts/benchmark.py times equivalent read commands from ghx and gh across warm-cache, cold-cache and forced-refresh scenarios, then writes a self-contained HTML report:

python3 scripts/benchmark.py --repo cli/cli            # benchmark a specific repo
python3 scripts/benchmark.py --runs 7 -o report.html   # more samples, custom output
python3 scripts/benchmark.py --demo                    # preview the report offline

The ghx side runs against the backend selected by --storage (sqlite, the default, or file). Run the script once per backend to compare them:

python3 scripts/benchmark.py --repo cli/cli --storage sqlite -o sqlite.html
python3 scripts/benchmark.py --repo cli/cli --storage file   -o file.html

Or compare the two backends head-to-head in a single report with --compare-backends, which runs ghx with each backend and shows them side by side (it drops the gh comparison):

python3 scripts/benchmark.py --repo cli/cli --compare-backends -o backends.html

On small repositories the file backend can win warm-cache reads because there is little to index; SQLite's advantage grows with the number of cached items.

It is standard-library-only, and ghx runs against a throwaway --cache-dir, so the real ~/.cache/ghx cache is never touched. The report lands in benchmark-report.html by default.

Because the tools do not always do the same work (ghx serves from cache; gh fetches project items and a richer field set), every scenario carries a fairness badge and note ("Different work", "ghx does more", "ghx does less") so a speedup is not read as a like-for-like win.

How caching works

Command Cache behaviour
cache Fetches everything (all states, with comments) and writes it to the selected backend (SQLite by default, or one JSON file per issue/PR for the file backend). Each run delta-refreshes from the last cache write; --if-stale skips the run while the cache is fresh. Supports --since windowed refresh and --type issues/prs partial refresh.
issue list / pr list Reads the cache and applies filters when it is fresh (indexed SQL predicates for SQLite); falls back to the GitHub API with server-side filters otherwise. When it falls back, each entry not already cached is fetched in full (comments, reviews, timeline) and written to the cache.
issue view / pr view Serves the individual cached item when it is less than 60 minutes old, or the full cache is fresh; fetches from the API and updates the cache otherwise. An item missing from an otherwise-fresh cache is fetched and cached rather than reported as missing. When the number is cached under the other type (a pull request viewed as an issue, or the reverse), it reports that type and exits without querying the API. --refresh bypasses the same-type cache checks.

Run a read command with --log-level debug to see where its time goes (repository resolution, cache store open, token resolution, the list call, and the cache backfill stages).

Repository coordinates are matched case-insensitively (as GitHub does) and stored lower-case, so a repository has one cache entry however its name is spelled.

Flag reference

Every command and flag is listed in the generated user guide flag reference; run ghx <command> --help for the authoritative list from the binary in front of you.

Related projects

About

An extended GitHub CLI. It caches issues, pull requests, and comments to disk to minimise API calls, and provides PR/issue comment operations beyond the standard gh CLI.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages