@d3lm/pr-stats 0.2.12 → 0.2.14
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.
- package/LICENSE +21 -0
- package/README.md +90 -41
- package/dist/tui-app.mjs +2511 -1943
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dominic Elm
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
# pr-stats
|
|
2
2
|
|
|
3
|
-
An interactive terminal UI, built with [OpenTUI](https://github.com/anomalyco/opentui) and React
|
|
3
|
+
An interactive terminal UI for GitHub PR statistics, built with [OpenTUI](https://github.com/anomalyco/opentui) and React. It shows time to review, PR size, and comment stats for any repository your GitHub login can see, plus a live queue of the PRs waiting on you.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
## Requirements
|
|
8
8
|
|
|
9
|
-
The TUI renders through native FFI
|
|
10
|
-
|
|
11
|
-
- An authenticated gh CLI. Run `gh auth login` if you have not set that up yet.
|
|
12
|
-
- A GitHub access token, passed through `--token` or the `GITHUB_TOKEN` or `GH_TOKEN` environment variable. When a token is present, the tool calls the GitHub API directly and does not need the gh CLI at all. The token needs the `repo` scope to see private repositories.
|
|
9
|
+
- Node 26.4+ or Bun 1.3+. The TUI renders through native FFI. Node gates that behind `--experimental-ffi`, and the launcher re-executes itself with the flag, so a plain `pr-stats` works on both runtimes.
|
|
10
|
+
- Either an authenticated gh CLI (`gh auth login`) or a GitHub access token via `--token`, `GITHUB_TOKEN`, or `GH_TOKEN`. With a token, the tool calls the GitHub API directly and does not need the gh CLI. See [Tokens](#tokens) for the access a token needs.
|
|
13
11
|
|
|
14
12
|
## Install
|
|
15
13
|
|
|
@@ -17,7 +15,7 @@ The TUI renders through native FFI, so it needs either Node 26.4 or newer, or [B
|
|
|
17
15
|
npm install -g @d3lm/pr-stats
|
|
18
16
|
```
|
|
19
17
|
|
|
20
|
-
You can also run it without installing through `npx @d3lm/pr-stats
|
|
18
|
+
You can also run it without installing through `npx @d3lm/pr-stats` or `bunx --bun @d3lm/pr-stats`.
|
|
21
19
|
|
|
22
20
|
## Usage
|
|
23
21
|
|
|
@@ -25,19 +23,41 @@ You can also run it without installing through `npx @d3lm/pr-stats`. The bunx eq
|
|
|
25
23
|
pr-stats
|
|
26
24
|
```
|
|
27
25
|
|
|
28
|
-
Without flags, it
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
When the data spans multiple repos, every tab opens on a repo picker that drills into one repo or the aggregate
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
All flags pre-seed the options modal
|
|
26
|
+
Without flags, it covers PRs from the last 90 days across all repositories you can access.
|
|
27
|
+
|
|
28
|
+
- **Queue** lists the open PRs awaiting your review with their wait time, and below them the open PRs you already reviewed. A fresh review request moves a PR back into the awaiting list.
|
|
29
|
+
- **Your PRs** splits into your open PRs and a merged-and-closed report with merge-time, first-review, backlog, and outcome charts plus a reviewer leaderboard. The `t` key switches the sub-tabs.
|
|
30
|
+
- **Time to review** charts your review times as a histogram, trend, heatmap, and weekly volume, plus review cycles, verdicts, an off-hours gauge, and the requests still waiting on you.
|
|
31
|
+
- **PR size** carries the same charts for PR sizes and adds a weekly net-lines trend.
|
|
32
|
+
- **Comments** holds a histogram of comments per PR, a scatter against PR size, and the most commented PRs.
|
|
33
|
+
|
|
34
|
+
When the data spans multiple repos, every tab opens on a repo picker that drills into one repo or the aggregate. On the queue lists, `g` groups the aggregate by repo, and on stats tabs, `x` lifts the row cap on comparison cards.
|
|
35
|
+
|
|
36
|
+
## Options
|
|
37
|
+
|
|
38
|
+
All flags pre-seed the options modal. Run `pr-stats --help` for full details.
|
|
39
|
+
|
|
40
|
+
| Flag | Default | Description |
|
|
41
|
+
| ----------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `-s, --since <value>` | `90d` | Only include PRs created after this point. Accepts an ISO date or a relative value like `30d`, `8w`, `6m`, `1y`. |
|
|
43
|
+
| `-r, --repo <name>` | all repos | Restrict the search to a repository, repeatable. Accepts `owner/name` or a bare name. |
|
|
44
|
+
| `-u, --user <login>` | you | Compute stats for this user instead of the authenticated one. |
|
|
45
|
+
| `--token <token>` | | Authenticate with a GitHub access token instead of the gh CLI. |
|
|
46
|
+
| `-t, --target <value>` | | Report how many reviews finished within this time, like `24h`, `2d`, or `90m`. |
|
|
47
|
+
| `--target-percentile <value>` | `90` | Check the target against this percentile of your review times, like `90` or `p90`. |
|
|
48
|
+
| `--size-target <value>` | | Report how many authored PRs fit within this size, like `400`, `400l`, `20f`, or `400l,20f`. |
|
|
49
|
+
| `--tz <zone>` | system | Set the IANA timezone for the weekend and working-hours math. |
|
|
50
|
+
| `--work-days <days>` | `mon-fri` | Count only these days as working days, like `mon-fri`, `sun-thu`, or `mon,wed,fri`. |
|
|
51
|
+
| `-w, --work-hours <value>` | `0-24` | Count only these working hours, like `9-17`, `8:30-16:30`, or `9am-6pm`. |
|
|
52
|
+
| `--wall-clock` | off | Measure raw elapsed time, including weekends. |
|
|
53
|
+
| `--include-drafts` | off | Include PRs that are currently drafts. |
|
|
54
|
+
| `--review-types <list>` | all | Count only these review types. Takes a comma-separated list of `approve`, `comment`, and `request-changes`. |
|
|
55
|
+
| `--no-cache` | off | Refetch every PR instead of reading the disk cache. |
|
|
56
|
+
| `--json` | off | Print every stat as JSON to stdout instead of starting the TUI. |
|
|
57
|
+
| `--debug <path>` | | Serve canned data from a fake `gh` binary in a testdata directory. |
|
|
58
|
+
| `-h, --help` | | Show the help page. |
|
|
59
|
+
|
|
60
|
+
A few examples:
|
|
41
61
|
|
|
42
62
|
```bash
|
|
43
63
|
# Limit the search to one repository and a start date
|
|
@@ -46,49 +66,74 @@ pr-stats --repo owner/name --since 2026-01-01
|
|
|
46
66
|
# Report how many reviews finished within one working day
|
|
47
67
|
pr-stats --work-hours 9-17 --target 1d
|
|
48
68
|
|
|
49
|
-
# Report how many authored PRs stayed under 400 changed lines
|
|
50
|
-
pr-stats --size-target 400
|
|
51
|
-
|
|
52
69
|
# Count only approvals and change requests as reviews
|
|
53
70
|
pr-stats --review-types approve,request-changes
|
|
54
|
-
|
|
55
|
-
# Authenticate with an access token instead of the gh CLI
|
|
56
|
-
pr-stats --token your-access-token
|
|
57
71
|
```
|
|
58
72
|
|
|
59
|
-
|
|
73
|
+
## Tokens
|
|
74
|
+
|
|
75
|
+
The `--token` flag and the `GITHUB_TOKEN` and `GH_TOKEN` environment variables switch the tool from the gh CLI to direct GitHub API calls. The PR list comes from the GitHub search API, which silently leaves out every repository the token cannot see, so a token with too little access shows an empty view instead of an error.
|
|
76
|
+
|
|
77
|
+
A classic token needs the `repo` scope, or `public_repo` when every repository is public.
|
|
78
|
+
|
|
79
|
+
A fine-grained token needs more care, because it only covers one resource owner and only the repositories you select for it.
|
|
80
|
+
|
|
81
|
+
- Pick the organization as the resource owner when the PRs live in an organization's repositories. A token owned by your user account never sees them.
|
|
82
|
+
- Grant access to all repositories or select the ones you care about. The public repositories option hides every private one.
|
|
83
|
+
- Grant read access to `Metadata`, `Pull requests`, and `Contents`. Searching pull requests in private repositories needs `Contents` on top of `Pull requests`, even though GitHub's permission tables do not mention it.
|
|
84
|
+
- Ask an organization owner to approve the token when the organization requires approval for fine-grained tokens. Until then, the token sees nothing in that organization.
|
|
85
|
+
|
|
86
|
+
To check a token, run `pr-stats --token <token> --repo owner/name` against a repository you expect to see. Naming the repository makes the search fail with a `422 Validation Failed` error when the token cannot see it, where a search without `--repo` would just return nothing.
|
|
60
87
|
|
|
61
88
|
## JSON export
|
|
62
89
|
|
|
63
|
-
The `--json` flag prints
|
|
90
|
+
The `--json` flag prints the full report to stdout instead of starting the TUI. Every other flag applies the same way, and progress renders on stderr, so a piped stdout stays pure JSON.
|
|
64
91
|
|
|
65
92
|
```bash
|
|
66
93
|
pr-stats --json | jq '.review.reviewTimeHours'
|
|
67
94
|
```
|
|
68
95
|
|
|
69
|
-
The report
|
|
70
|
-
|
|
71
|
-
The settings dialog has an export row that writes the same report to `pr-stats.json` in the directory pr-stats was started from, built from the data currently on screen and the live options.
|
|
96
|
+
The report holds a `review`, `authored`, and `comments` object with one entry per PR. Summaries report the count, mean, p50, p90, min, and max, and every duration respects the configured time mode. The settings dialog has an export row that writes the same report to `pr-stats.json` in the current directory.
|
|
72
97
|
|
|
73
98
|
## Caching
|
|
74
99
|
|
|
75
|
-
|
|
100
|
+
Closed and merged PRs are cached on disk per PR, because their timelines and sizes no longer change. Searches and open PRs are fetched fresh on each run. The TUI also snapshots the last successful load, so a later start with the same options renders instantly while the real load refreshes in the background.
|
|
76
101
|
|
|
77
|
-
The
|
|
102
|
+
The cache lives in `~/Library/Caches/pr-stats` on macOS and in `$XDG_CACHE_HOME/pr-stats` or `~/.cache/pr-stats` elsewhere. The `PR_STATS_CACHE_DIR` environment variable overrides the location. `--no-cache` skips every cache read for one run, and the settings dialog can disable the cache permanently.
|
|
78
103
|
|
|
79
|
-
The
|
|
104
|
+
The options modal saves the current options to the cache directory with the `s` key. Later runs start from the saved options wherever no flag was given, and flags always take precedence.
|
|
80
105
|
|
|
81
|
-
|
|
106
|
+
## Auto Reload
|
|
82
107
|
|
|
83
|
-
The
|
|
108
|
+
The `r` key reloads the data by hand, and `R` refetches everything past the cache. The TUI can also keep itself fresh while it stays open. The Auto reload row in the settings dialog turns background reloads on, and the Reload interval row below it sets how long the TUI waits after one load finishes before the next one starts, as a value in seconds, minutes, or hours like `30s`, `10m`, or `2h`. The header shows the interval next to the refresh time while it is on. Both settings persist in `settings.json` in the cache directory.
|
|
84
109
|
|
|
85
|
-
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"autoReload": true,
|
|
113
|
+
"reloadInterval": "10m"
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Notifications
|
|
86
118
|
|
|
87
|
-
The
|
|
119
|
+
The Desktop notifications row in the settings dialog makes the TUI send a desktop notification whenever a load finds a PR newly awaiting your review or a review re-requested from you after you already reviewed the PR. Every load diffs its review requests against the load before it, so the first load of a session only records what is already waiting and never notifies about it. Manual reloads count as well, but the feature pairs naturally with auto reload, which lets the TUI watch your review queue from a spare terminal. The setting persists in `settings.json` in the cache directory.
|
|
88
120
|
|
|
89
|
-
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"notifications": true,
|
|
124
|
+
"notifyChannel": "auto"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
90
127
|
|
|
91
|
-
|
|
128
|
+
Notifications go through the terminal itself when it supports a notification escape sequence, which covers iTerm2, Kitty, Ghostty, WezTerm, and most VTE-based terminals like GNOME Terminal. The terminal posts the notification under its own notification permission and the sequence travels through SSH, so this path needs no setup. Some terminals only show the banner while their window is unfocused. Terminals without such support get the platform's own command instead, `osascript` on macOS and `notify-send` on Linux. A notification that fails to send reports in the footer, for example when `notify-send` is missing on a Linux machine.
|
|
129
|
+
|
|
130
|
+
The "Notification channel" row picks the path, and the choice persists as `notifyChannel` with the values `auto`, `terminal`, and `command`. On `auto` the TUI tries the terminal first and falls back to the platform command, while the other two force one path. The dialog shows the `command` value as the command's name on this platform. Forcing the command pays off inside editor terminals like the one in VS Code, which render the terminal path as a small in-editor toast instead of a system notification. The "Send test notification" row below it names the channel the next send takes and sends a sample notification, so you can check that your desktop displays it before relying on it.
|
|
131
|
+
|
|
132
|
+
The macOS fallback posts through the built-in Script Editor, and since macOS 15 those notifications stay invisible until Script Editor holds notification permission, without any prompt appearing. To grant it once, open the Script Editor app, run the one-line script `display notification "test"`, and allow the prompt that appears. Script Editor then shows up under Notifications in the System Settings like any other app.
|
|
133
|
+
|
|
134
|
+
## Theming
|
|
135
|
+
|
|
136
|
+
The TUI ships five built-in themes, the warm amber default plus green, blue, purple, and yellow variants. The Theme row in the settings dialog cycles through them, and the Edit colors row below it edits individual colors, which creates a custom theme that joins the cycle as a sixth entry. Everything persists in a `theme` object in `settings.json` in the cache directory, which you can also edit by hand.
|
|
92
137
|
|
|
93
138
|
```json
|
|
94
139
|
{
|
|
@@ -101,8 +146,12 @@ The custom theme joins the Theme row cycle as a sixth entry and persists across
|
|
|
101
146
|
}
|
|
102
147
|
```
|
|
103
148
|
|
|
104
|
-
The `preset` key names the active theme, one of `default`, `green`, `blue`, `purple`, `yellow`, and `custom`. The `base` key names the built-in theme the custom colors start from
|
|
149
|
+
The `preset` key names the active theme, one of `default`, `green`, `blue`, `purple`, `yellow`, and `custom`. The `base` key names the built-in theme the custom colors start from. The color keys are `bg`, `border`, `text`, `muted`, `dim`, `accent`, `selectedBg`, `inputBg`, `inputFocusedBg`, `warn`, `error`, `success`, `chartBar`, `chartLine`, `chartDim`, and `heat`, where `heat` takes four heatmap colors from cool to hot. An invalid key or color fails the start with a message naming the mistake.
|
|
105
150
|
|
|
106
151
|
## Development
|
|
107
152
|
|
|
108
|
-
The TUI runs with `bun tui/main.tsx
|
|
153
|
+
The TUI runs with `bun tui/main.tsx`, or under Node with `pnpm tui:node`. The `--debug tui/testdata` flag serves canned data from the fake `gh` binary in that directory instead of fetching from GitHub, and `bun test tui` drives the whole pipeline against it. `pnpm build` bundles everything into `dist/`, where `tui.mjs` is the launcher and `tui-app.mjs` is the app bundle it loads.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
OpenTUI is licensed under the [MIT License](LICENSE).
|