gh-prs 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ # Publishes the package to PyPI when a GitHub release is published, then
2
+ # triggers the Homebrew tap (denrou/homebrew-gh-prs) to regenerate its formula.
3
+ # Same pipeline as denrou/ggit.
4
+
5
+ name: Upload Python Package
6
+
7
+ on:
8
+ release:
9
+ types: [published]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ release-build:
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.x"
24
+
25
+ - name: Build release distributions
26
+ run: |
27
+ python -m pip install build
28
+ python -m build
29
+
30
+ - name: Upload distributions
31
+ uses: actions/upload-artifact@v4
32
+ with:
33
+ name: release-dists
34
+ path: dist/
35
+
36
+ pypi-publish:
37
+ runs-on: ubuntu-latest
38
+ needs:
39
+ - release-build
40
+ permissions:
41
+ # IMPORTANT: this permission is mandatory for trusted publishing
42
+ id-token: write
43
+
44
+ environment:
45
+ name: pypi
46
+ url: https://pypi.org/project/gh-prs/${{ github.event.release.name }}
47
+
48
+ steps:
49
+ - name: Retrieve release distributions
50
+ uses: actions/download-artifact@v4
51
+ with:
52
+ name: release-dists
53
+ path: dist/
54
+
55
+ - name: Publish release distributions to PyPI
56
+ uses: pypa/gh-action-pypi-publish@release/v1
57
+ with:
58
+ packages-dir: dist/
59
+
60
+ update-homebrew:
61
+ runs-on: ubuntu-latest
62
+ needs:
63
+ - pypi-publish
64
+ steps:
65
+ - name: Trigger Homebrew tap update
66
+ env:
67
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
68
+ run: gh workflow run update-formula.yml --repo denrou/homebrew-gh-prs
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.14
gh_prs-0.7.0/CLAUDE.md ADDED
@@ -0,0 +1,172 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ uv run gh-prs # Run the CLI (default: PRs needing attention)
9
+ uv run gh-prs -c # PRs you created
10
+ uv run gh-prs -r # PRs awaiting your review
11
+ uv run pytest # Run tests
12
+ uv run ruff check . # Lint
13
+ uv run ruff format . # Format
14
+ uv add <pkg> # Add dependency
15
+ uv add --dev <pkg> # Add dev dependency
16
+ ```
17
+
18
+ ## Architecture
19
+
20
+ Four-module design inside `gh_prs/`:
21
+
22
+ - **`gh.py`** — Stateless wrapper around the `gh` CLI, relying on the user's
23
+ existing `gh auth` session. Exposes a `PullRequest` dataclass plus
24
+ `fetch_prs()`, `count_prs()`, `fetch_pr_head()`, `ALL_QUALIFIERS`,
25
+ `DEFAULT_STALE_AFTER`, and the `GhError` exception.
26
+ - **`snooze.py`** — Local per-PR snooze store (`{PR url: {oid, until}}` JSON
27
+ at `$XDG_CONFIG_HOME/gh-prs/snooze.json`). Pure I/O + partitioning helpers;
28
+ no `gh` calls. Raises `SnoozeError`.
29
+ - **`config.py`** — Human-authored settings (`{stale_after}` JSON at
30
+ `$XDG_CONFIG_HOME/gh-prs/config.json`), kept separate from the
31
+ machine-managed snooze store so a hand-edit can't corrupt snooze state.
32
+ Reuses `snooze.parse_duration`; missing file → defaults; raises
33
+ `ConfigError`.
34
+ - **`cli.py`** — Command-line interface (argparse + [rich](https://rich.readthedocs.io/)).
35
+ Fetches and prints grouped/colored tables. Entry point is `gh_prs.cli:main`.
36
+
37
+ ### Loading (single GraphQL round-trip per qualifier)
38
+
39
+ `fetch_prs(qualifiers)` runs one `gh api graphql` search per qualifier
40
+ (`author`, `review-requested`, `reviewed-by`, `assignee`, `involves`) in
41
+ parallel threads.
42
+ Each search fetches everything in one shot — review decision, mergeability,
43
+ CI rollup state, `latestReviews`, `reviewRequests`, plus the viewer's login —
44
+ so there is no per-PR enrichment phase. `attention_reasons` is computed by the
45
+ pure `_attention_reasons()` helper (unit-tested in `tests/test_gh.py`).
46
+
47
+ Performance notes (measured once; exact figures drift, the ratios hold):
48
+
49
+ - GitHub executes aliased search blocks _sequentially_ within one GraphQL
50
+ request — that's why each qualifier gets its own parallel request (cost =
51
+ slowest search, not the sum).
52
+ - GitHub also throttles concurrent searches per token; `-a` is bounded by
53
+ `involves:@me`, the slowest search by far.
54
+ - Node _hydration_ dominates search cost, not the search itself — a
55
+ count-only `issueCount` query is roughly an order of magnitude faster than
56
+ a hydrated one. `count_prs()` exploits this for single-qualifier `--count`
57
+ (`-c`/`-r`), the status-bar polling path.
58
+ - Each search is capped at `_SEARCH_LIMIT` (100) nodes; searches are
59
+ `sort:updated-desc`, so truncation keeps the most recently updated PRs, and
60
+ when `issueCount` exceeds the cap `fetch_prs()` reports the truncation
61
+ through its `on_warning` callback (the CLI prints it to stderr). Counts
62
+ from `count_prs()` are exact regardless of the cap.
63
+
64
+ ### Error handling
65
+
66
+ "Error" must never look like "nothing to do" (critical for `--count` in status
67
+ bars). All `gh` failures raise `GhError` — including per-qualifier search
68
+ failures (partial results would silently hide PRs), subprocess timeouts
69
+ (60 s), and any deviation from the expected GraphQL response envelope
70
+ (validated in `_graphql()`/`_search()`). The same fail-safe direction applies
71
+ to per-PR fields: unknown CI states map to `PENDING`, and "ready" requires a
72
+ positive `MERGEABLE` (GitHub reports `UNKNOWN` while recomputing
73
+ mergeability). The CLI prints errors to stderr and exits non-zero (130 on
74
+ Ctrl-C).
75
+
76
+ ### Attention logic (`_attention_reasons`)
77
+
78
+ A non-draft PR needs attention when any of these hold:
79
+
80
+ - **review** — your review is requested (or your prior review was dismissed)
81
+ and you have no active approval / changes-requested. Hidden when: the PR is
82
+ conflicting (a review would be staled by the rebase); the overall decision
83
+ is `CHANGES_REQUESTED` (author is reworking it); or it's `APPROVED` —
84
+ mergeable without you — unless you are personally on the
85
+ requested-reviewers list (`review_requested_explicitly`, i.e. requested as
86
+ a User, not through a Team).
87
+ - **new-commits** — you reviewed someone else's PR (`APPROVED`,
88
+ `CHANGES_REQUESTED`, `COMMENTED`, or `DISMISSED` — the latter for repos
89
+ that auto-dismiss stale reviews on push) and the head oid no longer
90
+ matches the oid your review was submitted against (`latestReviews.commit`
91
+ vs `headRefOid`) — new commits or a rebase the author forgot to re-request
92
+ review for. Commit identity is compared, not `committedDate`: committer
93
+ timestamps are mutable metadata. A missing oid on either side counts as
94
+ "moved" (unknown must never read as "nothing to do"); only both-missing
95
+ stays quiet. Hidden when: the PR is conflicting (more commits are coming);
96
+ the **review** reason already fired (no double listing); or you authored
97
+ the PR (a comment review on your own PR must not self-flag). Surfaced by
98
+ the `reviewed-by:@me` search in the default view — review requests
99
+ disappear once fulfilled, so these PRs match no other attention qualifier.
100
+ When the `latestReviews` 50-node cap hides your review on a `reviewed-by`
101
+ PR, `fetch_prs` reports the contradiction through `on_warning` instead of
102
+ silently skipping the PR.
103
+ - **ready** — you authored it, it's `APPROVED`, CI is green (or none), and it's
104
+ not conflicting.
105
+ - **ci-failed** — you authored it and a check is failing.
106
+ - **conflict** — you authored it and it has merge conflicts (independent of
107
+ `ci-failed`; a PR can have both).
108
+ - **stale** — a soft nudge: you authored it, it's still awaiting review (not
109
+ yet `APPROVED`, and not `CHANGES_REQUESTED` — the author isn't reworking
110
+ it), nothing else actionable fired (`not reasons`, so no `ready`/`ci-failed`
111
+ /`conflict`), and it has gone untouched (`updatedAt`) longer than the
112
+ staleness threshold — time to ping the reviewers. The threshold is
113
+ `DEFAULT_STALE_AFTER` (3 days), overridable via `config.json`'s
114
+ `stale_after` or the `--stale-after` flag. This reason is the one place
115
+ that **inverts** the house fail-safe: `_is_stale` treats a missing /
116
+ unparseable / naive `updatedAt` as _not_ stale, because a nudge is additive
117
+ and non-actionable — defaulting an unknown age to "stale" would fabricate a
118
+ reason on a possibly-fresh PR. Disabled entirely when `stale_after` is
119
+ `None` (config `null`) or `now`/`stale_after` aren't passed to
120
+ `_attention_reasons` (so a bare `_attention_reasons(pr)` never returns it).
121
+
122
+ ### Configuration (`config.py`, applied in `cli.py`)
123
+
124
+ User settings live in `$XDG_CONFIG_HOME/gh-prs/config.json`, separate from the
125
+ machine-managed `snooze.json` (opposite fail-safe needs; a hand-edit must not
126
+ be able to corrupt snooze state). Today the only key is `stale_after` — a
127
+ duration string (`"3d"`, `"1w"`) parsed by `snooze.parse_duration`, or `null`
128
+ to disable the **stale** nudge. Only the view path reads it, and it degrades
129
+ to defaults with an on-stderr warning on any error (the tool never writes it,
130
+ so there is nothing to clobber). Resolution order for the threshold:
131
+ `--stale-after` flag → `config.json` → `DEFAULT_STALE_AFTER`. A bad flag value
132
+ is a hard error (explicit user input); a bad config file only warns.
133
+
134
+ ### Snoozing (`snooze.py`, applied in `cli.py`)
135
+
136
+ `--snooze <pr>...` records each PR's head oid plus an expiry timestamp
137
+ (default 24h, `--for 12h/3d/1w`); the default attention view (table and
138
+ `--count`) then hides the PR while _both_ hold: head unchanged and window
139
+ open. The same fail-safe direction as everywhere else applies: an unknown
140
+ oid, an uncomparable timestamp, a moved head, an elapsed window, or an
141
+ unreadable store all _show_ the PR (a corrupt store only warns on the view
142
+ path, but is fatal for `--snooze`/`--unsnooze`, which must not clobber the
143
+ file). Dead entries are pruned — with an on-stderr "snooze expired" warning
144
+ when the PR actually resurfaced — and the view reports how many
145
+ attention-worthy PRs it withheld. Explicit views (`-c`/`-r`/`-a`), fast
146
+ counts, and `--json` never consult the store — their output stays exact.
147
+ Entries whose PR no longer appears in any search are kept while their window
148
+ is open (the PR may be closed _or_ merely beyond the 100-node cap; deleting
149
+ on absence would lose live snoozes) and pruned quietly once it elapses.
150
+
151
+ `--snooze`/`--unsnooze` each take one or more PR references, following `gh`'s
152
+ own conventions: a bare number (`123`) or a full URL. A bare number is scoped
153
+ by `-R/--repo owner/repo`, or — when that's omitted — the repository of the
154
+ current directory. Bare numbers are resolved through `gh.resolve_pr()` (a
155
+ `gh pr view` call), so their canonical URL and host come straight from `gh`
156
+ and enterprise instances work without host-specific URL construction; a full
157
+ URL is canonicalized offline by `normalize_pr_url()` (keeping `snooze.py`
158
+ free of `gh` calls), so `--unsnooze <url>` needs no network. The old
159
+ `owner/repo/123` / `owner/repo#123` shorthand was removed in favor of a
160
+ number plus `--repo`; both forms now hard-error. References resolve
161
+ independently: a bad or not-snoozed one is reported to stderr and skipped
162
+ while the rest are applied, the store is written once, and a partial batch
163
+ exits non-zero — never clobbering the file.
164
+
165
+ ## Notes
166
+
167
+ - `ruff` rule `E501` (line length) is not enforced.
168
+ - GraphQL `statusCheckRollup.state` is normalized via `_ROLLUP_STATE`; unknown
169
+ future states map to `PENDING` so "unrecognized" never counts as passing.
170
+ - PR titles are attacker-controlled: they are stripped of control characters
171
+ at ingestion (`from_graphql`) and markup-escaped at render (`_title_cell`).
172
+ Keep both when touching those paths.
gh_prs-0.7.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Denis Roussel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gh_prs-0.7.0/PKG-INFO ADDED
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: gh-prs
3
+ Version: 0.7.0
4
+ Summary: CLI for listing GitHub pull requests that need your attention, powered by gh CLI
5
+ Author-email: Denis Roussel <deroussel@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.14
9
+ Requires-Dist: rich>=14.0.0
10
+ Description-Content-Type: text/markdown
11
+
12
+ # gh-prs
13
+
14
+ A simple CLI that lists the GitHub pull requests you need to act on, powered by
15
+ the `gh` CLI. No TUI — just readable, colored, grouped output.
16
+
17
+ By default it shows only the PRs that need your attention:
18
+
19
+ - **Needs your review** — PRs where your review is requested and still needed:
20
+ once the PR is approved (mergeable without you) it is hidden unless you are
21
+ personally on the requested-reviewers list (not just through a team), and it
22
+ is also hidden while changes are requested (the author is reworking it).
23
+ Drafts are excluded (not ready for review), as are conflicting PRs (a review
24
+ would be staled by the rebase). A PR also resurfaces here when your previous
25
+ review was dismissed.
26
+ - **New commits since your review** — PRs you already reviewed (approved,
27
+ requested changes, left a review comment, or had your review dismissed by a
28
+ push) whose head commit is no longer the one you reviewed — new commits or
29
+ a rebase the author forgot to re-request review for; the case that is
30
+ otherwise easy to miss. Hidden while the PR is conflicting (more commits
31
+ are coming anyway). A PR never appears both here and in **Needs your
32
+ review**: whenever it qualifies there (e.g. a re-request after a
33
+ comment-only review), that section wins; a re-request after your
34
+ still-standing approval keeps it here.
35
+ - **Ready to ship** — PRs you created that are approved, with CI green (or no
36
+ checks) and no conflicts.
37
+ - **CI failed** — PRs you created where a check is failing.
38
+ - **Conflicts to resolve** — PRs you created that have merge conflicts.
39
+ - **Waiting on review — time to nudge** — PRs you created that are still
40
+ awaiting review and have gone quiet longer than the staleness threshold
41
+ (3 days by default). There's nothing for _you_ to do — the code is fine, CI
42
+ is green, no conflicts — but it has been sitting long enough that pinging the
43
+ reviewers is warranted. Any new activity (a comment, a commit) resets the
44
+ clock, so it won't nag while there's discussion. Change the threshold with
45
+ `--stale-after 5d` or the `stale_after` config setting; set it to `null` in
46
+ the config to turn the nudge off entirely.
47
+
48
+ ## Prerequisites
49
+
50
+ - [GitHub CLI](https://cli.github.com/) (`gh`) installed and authenticated (`gh auth login`)
51
+ - Python 3.14+
52
+ - [uv](https://docs.astral.sh/uv/)
53
+
54
+ ## Install
55
+
56
+ With [Homebrew](https://brew.sh/):
57
+
58
+ ```bash
59
+ brew install denrou/gh-prs/gh-prs
60
+ ```
61
+
62
+ Or from [PyPI](https://pypi.org/project/gh-prs/):
63
+
64
+ ```bash
65
+ uv tool install gh-prs
66
+ ```
67
+
68
+ Or straight from the repository:
69
+
70
+ ```bash
71
+ uv tool install gh-prs --from git+https://github.com/denrou/gh-prs.git
72
+ ```
73
+
74
+ ### As a `gh` alias
75
+
76
+ ```bash
77
+ gh alias set --shell prs 'gh-prs'
78
+ ```
79
+
80
+ Then simply run:
81
+
82
+ ```bash
83
+ gh prs
84
+ ```
85
+
86
+ ## Usage
87
+
88
+ ```bash
89
+ gh prs # PRs that need your attention (default)
90
+ gh prs -c/--created # every open PR you created
91
+ gh prs -r/--review # every PR awaiting your review
92
+ gh prs -a/--all # every PR you are involved with
93
+ gh prs --json # raw JSON (for scripting)
94
+ gh prs --count # print only the PR count for the selected view
95
+ # (attention count by default; handy for status bars)
96
+ gh prs --no-color # disable colored output
97
+
98
+ gh prs --snooze 123 # hide a PR (of the current repo) for 24h
99
+ gh prs --snooze 123 -R o/r # …of another repo (owner/repo)
100
+ gh prs --snooze 12 34 --for 3d # …several at once, for a custom window (12h, 3d, 1w)
101
+ gh prs --unsnooze 123 # remove a PR's snooze
102
+ gh prs --snoozed # list snoozed PRs
103
+
104
+ gh prs --stale-after 5d # flag your review-waiting PRs quiet this long
105
+ ```
106
+
107
+ `--count` exits non-zero when fetching fails, so status-bar scripts can tell
108
+ "no PRs" apart from "the lookup broke". With `-c` or `-r` it uses a fast
109
+ count-only query (well under a second) — ideal for frequent polling.
110
+
111
+ ### Snoozing
112
+
113
+ Sometimes a PR legitimately needs _someone's_ attention but not yours — say a
114
+ dependency bump routed to you through a team when a teammate is the natural
115
+ reviewer. `gh prs --snooze <pr>...` hides one or more PRs from the default
116
+ attention view. Reference a PR the way `gh` does: a bare number, scoped by
117
+ `-R/--repo owner/repo` (or the repository of the current directory when
118
+ omitted), or a full URL. Bare numbers are resolved through `gh`, so Enterprise
119
+ hosts work too.
120
+
121
+ A snooze lasts 24 hours by default (`--for 12h`/`3d`/`1w` to change) and is
122
+ also tied to the PR's head commit at snooze time: whichever comes first — the
123
+ window elapsing or new commits landing — resurfaces the PR with a warning and
124
+ drops the snooze, so you acknowledge a specific state for a bounded time,
125
+ never future work. The attention view prints how many snoozed PRs it withheld
126
+ on stderr — hiding is visible, never silent. Explicit views (`-c`/`-r`/`-a`),
127
+ `--count` for those views, and `--json` ignore snoozes entirely, so scripts
128
+ and exact counts are unaffected.
129
+
130
+ Snoozes are stored locally in `~/.config/gh-prs/snooze.json` (honors
131
+ `$XDG_CONFIG_HOME`); they never touch the PR on GitHub.
132
+
133
+ ### Configuration
134
+
135
+ Settings live in `~/.config/gh-prs/config.json` (honors `$XDG_CONFIG_HOME`),
136
+ separate from the snooze store. It's optional — every setting has a default.
137
+ Today the only key is `stale_after`, the silence threshold for the
138
+ **Waiting on review** nudge:
139
+
140
+ ```json
141
+ { "stale_after": "5d" }
142
+ ```
143
+
144
+ Accepts the same duration syntax as `--for`/`--stale-after` (`12h`, `3d`,
145
+ `1w`), or `null` to disable the nudge. The `--stale-after` flag overrides the
146
+ file for a single run. An unreadable or invalid config only warns and falls
147
+ back to the 3-day default, so a typo never breaks the tool.
148
+
149
+ For status bars, prefer the `uv tool install` binary (`~/.local/bin/gh-prs`)
150
+ over `uv run` inside the repo — it skips ~250 ms of project resolution per
151
+ invocation.
gh_prs-0.7.0/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # gh-prs
2
+
3
+ A simple CLI that lists the GitHub pull requests you need to act on, powered by
4
+ the `gh` CLI. No TUI — just readable, colored, grouped output.
5
+
6
+ By default it shows only the PRs that need your attention:
7
+
8
+ - **Needs your review** — PRs where your review is requested and still needed:
9
+ once the PR is approved (mergeable without you) it is hidden unless you are
10
+ personally on the requested-reviewers list (not just through a team), and it
11
+ is also hidden while changes are requested (the author is reworking it).
12
+ Drafts are excluded (not ready for review), as are conflicting PRs (a review
13
+ would be staled by the rebase). A PR also resurfaces here when your previous
14
+ review was dismissed.
15
+ - **New commits since your review** — PRs you already reviewed (approved,
16
+ requested changes, left a review comment, or had your review dismissed by a
17
+ push) whose head commit is no longer the one you reviewed — new commits or
18
+ a rebase the author forgot to re-request review for; the case that is
19
+ otherwise easy to miss. Hidden while the PR is conflicting (more commits
20
+ are coming anyway). A PR never appears both here and in **Needs your
21
+ review**: whenever it qualifies there (e.g. a re-request after a
22
+ comment-only review), that section wins; a re-request after your
23
+ still-standing approval keeps it here.
24
+ - **Ready to ship** — PRs you created that are approved, with CI green (or no
25
+ checks) and no conflicts.
26
+ - **CI failed** — PRs you created where a check is failing.
27
+ - **Conflicts to resolve** — PRs you created that have merge conflicts.
28
+ - **Waiting on review — time to nudge** — PRs you created that are still
29
+ awaiting review and have gone quiet longer than the staleness threshold
30
+ (3 days by default). There's nothing for _you_ to do — the code is fine, CI
31
+ is green, no conflicts — but it has been sitting long enough that pinging the
32
+ reviewers is warranted. Any new activity (a comment, a commit) resets the
33
+ clock, so it won't nag while there's discussion. Change the threshold with
34
+ `--stale-after 5d` or the `stale_after` config setting; set it to `null` in
35
+ the config to turn the nudge off entirely.
36
+
37
+ ## Prerequisites
38
+
39
+ - [GitHub CLI](https://cli.github.com/) (`gh`) installed and authenticated (`gh auth login`)
40
+ - Python 3.14+
41
+ - [uv](https://docs.astral.sh/uv/)
42
+
43
+ ## Install
44
+
45
+ With [Homebrew](https://brew.sh/):
46
+
47
+ ```bash
48
+ brew install denrou/gh-prs/gh-prs
49
+ ```
50
+
51
+ Or from [PyPI](https://pypi.org/project/gh-prs/):
52
+
53
+ ```bash
54
+ uv tool install gh-prs
55
+ ```
56
+
57
+ Or straight from the repository:
58
+
59
+ ```bash
60
+ uv tool install gh-prs --from git+https://github.com/denrou/gh-prs.git
61
+ ```
62
+
63
+ ### As a `gh` alias
64
+
65
+ ```bash
66
+ gh alias set --shell prs 'gh-prs'
67
+ ```
68
+
69
+ Then simply run:
70
+
71
+ ```bash
72
+ gh prs
73
+ ```
74
+
75
+ ## Usage
76
+
77
+ ```bash
78
+ gh prs # PRs that need your attention (default)
79
+ gh prs -c/--created # every open PR you created
80
+ gh prs -r/--review # every PR awaiting your review
81
+ gh prs -a/--all # every PR you are involved with
82
+ gh prs --json # raw JSON (for scripting)
83
+ gh prs --count # print only the PR count for the selected view
84
+ # (attention count by default; handy for status bars)
85
+ gh prs --no-color # disable colored output
86
+
87
+ gh prs --snooze 123 # hide a PR (of the current repo) for 24h
88
+ gh prs --snooze 123 -R o/r # …of another repo (owner/repo)
89
+ gh prs --snooze 12 34 --for 3d # …several at once, for a custom window (12h, 3d, 1w)
90
+ gh prs --unsnooze 123 # remove a PR's snooze
91
+ gh prs --snoozed # list snoozed PRs
92
+
93
+ gh prs --stale-after 5d # flag your review-waiting PRs quiet this long
94
+ ```
95
+
96
+ `--count` exits non-zero when fetching fails, so status-bar scripts can tell
97
+ "no PRs" apart from "the lookup broke". With `-c` or `-r` it uses a fast
98
+ count-only query (well under a second) — ideal for frequent polling.
99
+
100
+ ### Snoozing
101
+
102
+ Sometimes a PR legitimately needs _someone's_ attention but not yours — say a
103
+ dependency bump routed to you through a team when a teammate is the natural
104
+ reviewer. `gh prs --snooze <pr>...` hides one or more PRs from the default
105
+ attention view. Reference a PR the way `gh` does: a bare number, scoped by
106
+ `-R/--repo owner/repo` (or the repository of the current directory when
107
+ omitted), or a full URL. Bare numbers are resolved through `gh`, so Enterprise
108
+ hosts work too.
109
+
110
+ A snooze lasts 24 hours by default (`--for 12h`/`3d`/`1w` to change) and is
111
+ also tied to the PR's head commit at snooze time: whichever comes first — the
112
+ window elapsing or new commits landing — resurfaces the PR with a warning and
113
+ drops the snooze, so you acknowledge a specific state for a bounded time,
114
+ never future work. The attention view prints how many snoozed PRs it withheld
115
+ on stderr — hiding is visible, never silent. Explicit views (`-c`/`-r`/`-a`),
116
+ `--count` for those views, and `--json` ignore snoozes entirely, so scripts
117
+ and exact counts are unaffected.
118
+
119
+ Snoozes are stored locally in `~/.config/gh-prs/snooze.json` (honors
120
+ `$XDG_CONFIG_HOME`); they never touch the PR on GitHub.
121
+
122
+ ### Configuration
123
+
124
+ Settings live in `~/.config/gh-prs/config.json` (honors `$XDG_CONFIG_HOME`),
125
+ separate from the snooze store. It's optional — every setting has a default.
126
+ Today the only key is `stale_after`, the silence threshold for the
127
+ **Waiting on review** nudge:
128
+
129
+ ```json
130
+ { "stale_after": "5d" }
131
+ ```
132
+
133
+ Accepts the same duration syntax as `--for`/`--stale-after` (`12h`, `3d`,
134
+ `1w`), or `null` to disable the nudge. The `--stale-after` flag overrides the
135
+ file for a single run. An unreadable or invalid config only warns and falls
136
+ back to the 3-day default, so a typo never breaks the tool.
137
+
138
+ For status bars, prefer the `uv tool install` binary (`~/.local/bin/gh-prs`)
139
+ over `uv run` inside the repo — it skips ~250 ms of project resolution per
140
+ invocation.
File without changes