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.
- gh_prs-0.7.0/.github/workflows/python-publish.yml +68 -0
- gh_prs-0.7.0/.gitignore +10 -0
- gh_prs-0.7.0/.python-version +1 -0
- gh_prs-0.7.0/CLAUDE.md +172 -0
- gh_prs-0.7.0/LICENSE +21 -0
- gh_prs-0.7.0/PKG-INFO +151 -0
- gh_prs-0.7.0/README.md +140 -0
- gh_prs-0.7.0/gh_prs/__init__.py +0 -0
- gh_prs-0.7.0/gh_prs/cli.py +530 -0
- gh_prs-0.7.0/gh_prs/config.py +85 -0
- gh_prs-0.7.0/gh_prs/gh.py +592 -0
- gh_prs-0.7.0/gh_prs/snooze.py +199 -0
- gh_prs-0.7.0/pyproject.toml +25 -0
- gh_prs-0.7.0/tests/test_cli.py +549 -0
- gh_prs-0.7.0/tests/test_config.py +91 -0
- gh_prs-0.7.0/tests/test_gh.py +856 -0
- gh_prs-0.7.0/tests/test_snooze.py +246 -0
- gh_prs-0.7.0/uv.lock +146 -0
|
@@ -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
|
gh_prs-0.7.0/.gitignore
ADDED
|
@@ -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
|