@d3lm/pr-stats 0.2.12 → 0.2.13

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -45
  3. package/dist/tui-app.mjs +1988 -1854
  4. 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, that shows statistics for a GitHub user, e.g., time to review, size of authored PRs, or comments received on them. It works with any repository your GitHub login can see. It has a queue tab for the open PRs on your reviewing plate, split into those awaiting your review and those you already reviewed, a Your PRs tab that splits into your open PRs and a merged-and-closed report, chart tabs for the review-time, size, and comments reports, and a live options modal.
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
  ![pr-stats](./assets/screenshot.png)
6
6
 
7
7
  ## Requirements
8
8
 
9
- The TUI renders through native FFI, so it needs either Node 26.4 or newer, or [Bun](https://bun.sh) 1.3 or newer. You also need one of two ways to authenticate:
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. Private repositories need the `repo` scope.
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`. The bunx equivalent is `bunx --bun @d3lm/pr-stats`, where `--bun` runs the TUI on Bun directly instead of going through Node.
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 looks at PRs from the last 90 days across all repositories you can access. The tabs hold these views.
29
-
30
- - The queue tab, which it opens on, shows two lists. The open PRs awaiting your review come first with how long each has been waiting, and below them sit the open PRs you already reviewed or commented on with how long ago that was, so a PR stays visible until it merges or closes. A fresh review request moves a PR from the reviewing list back into the awaiting one. The `--review-types` flag narrows what counts as a review to a subset of `approve`, `comment`, and `request-changes`, so with `--review-types approve,request-changes` a comment alone no longer answers a request and the PR stays in the awaiting list.
31
- - The Your PRs tab has two sub-tabs, which the `t` key switches. The first lists your own authored PRs that are still open with their age and size. The second reports how your authored PRs got created, merged, and closed, telling a merge apart from a close without one. It charts time-to-merge percentiles, a histogram and trend, a merge-time heatmap, and a scatter of merge time against PR size. It also measures how long your PRs wait for their first review from someone else, with a histogram and trend of the time from creation to that review and a histogram of how long the open PRs still without one have waited. It also plots a merge-rate trend over the concluded PRs, cumulative created and merged lines whose gap shows the backlog, weekly created and merged volumes, an outcome gauge, and the most recently merged and closed PRs. A reviewer leaderboard ranks who reviews your PRs by distinct PRs reviewed, and a review-coverage gauge counts the merged PRs that never received a review. Your own replies to review threads never count as a review for any of them.
32
- - The time-to-review report pairs its histogram, trend, heatmap, and weekly volume with a scatter of review time against PR size, the completed review cycles per PR, and a verdict gauge splitting approvals from change requests. It also shows the age of the requests still waiting on you, how old PRs already were when the request reached you, and an off-hours gauge that splits weekdays into work hours and after hours once `--work-hours` is set. On the aggregate view it additionally compares median review times by repo.
33
- - The PR size report carries the same histogram, trend, heatmap, and weekly volume for PR sizes and adds a net-lines trend that sums additions minus deletions per week.
34
- - The comments report holds a histogram of comments per PR, a scatter of comments against PR size, and the most commented PRs.
35
-
36
- When the data spans multiple repos, every tab opens on a repo picker that drills into one repo or the aggregate across all of them, and on the two queue lists the `g` key groups the aggregate list by repo. Comparison cards like the reviewer leaderboard cap themselves at eight rows and fold the rest into an overflow line, and the `x` key lifts the cap on the open stats tab and restores it. The footer names the key whenever the tab has a capped card.
37
-
38
- Node gates the FFI that OpenTUI renders through behind the `--experimental-ffi` flag, and the launcher re-executes itself with that flag when it is missing, so a plain `pr-stats` works without extra flags on both runtimes.
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,31 @@ 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
- Run `pr-stats --help` for the full list of options.
60
-
61
73
  ## JSON export
62
74
 
63
- The `--json` flag prints every stat as JSON to stdout instead of starting the TUI, so the output pipes into jq or lands in a file through a redirect. Every other flag applies to the report the same way it seeds the TUI, and load progress renders on stderr, so a piped stdout stays pure JSON.
75
+ 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
76
 
65
77
  ```bash
66
78
  pr-stats --json | jq '.review.reviewTimeHours'
67
79
  ```
68
80
 
69
- The report carries the same data the tabs derive. The `review` object holds the counts, the review-time summary, the verdicts, the per-repo medians, the optional target gauge, and one entry per completed, pending, and reviewing cycle. The `authored` object holds the outcome counts, the size, merge-time, and first-review summaries, the optional size-target gauge, the reviewer leaderboard with the review coverage, and one entry per authored PR with its size, comment counts, reviewers, and merge, close, and first-review times. The `comments` object summarizes the comments per PR. Summaries report the count, mean, p50, p90, min, and max, and every duration respects the configured time mode, so a working-hours setup reports counted hours instead of wall-clock hours.
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.
81
+ 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
82
 
73
83
  ## Caching
74
84
 
75
- The expensive part of a run is fetching the per-PR review timelines and the size and comment counters, so those get cached on disk per PR. Only closed and merged PRs are cached, because their timelines and sizes no longer change. The searches and every open PR are fetched fresh on each run, which keeps the results correct while skipping most of the API calls after the first run.
76
-
77
- The login of the authenticated user is cached for a day as well, which skips one round trip per run. A configured `--user` never touches that cache. The TUI additionally snapshots the last successful load, and a later start with the same data options renders the snapshot instantly while the real load refreshes it in the background. A narrower `--since` window also hits the snapshot, because the subset gets cut from it by PR creation date.
85
+ 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.
78
86
 
79
- 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. Pass `--no-cache` to skip every cache read, which refetches everything, including the login, and rewrites the cache with fresh data. Runs with `--debug` never read or write the cache, so canned test data stays out of it.
87
+ 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.
80
88
 
81
- The options modal can also save 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 on the command line always take precedence. The modal labels whether the current options match the saved ones, and clearing the cache from the settings dialog keeps the saved options.
82
-
83
- The settings dialog can also disable the cache permanently. The toggle persists to `settings.json` in the cache directory, so every later run refetches everything the way `--no-cache` does. An explicit `--no-cache` flag always wins over the saved setting. The same dialog switches the color theme, described under Theming below. The dialog can also reset the settings, which deletes `settings.json` together with a saved theme, so future runs start from the defaults.
89
+ 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.
84
90
 
85
91
  ## Theming
86
92
 
87
- The TUI ships five built-in themes, the warm amber default plus green, blue, purple, and yellow variants. Each variant recolors the accent and chart colors at the same saturation and lightness as the default, so every theme keeps the same contrast, while the neutral text and background grays stay shared. The Theme row in the settings dialog cycles through them with the arrow keys and applies the choice immediately.
88
-
89
- The Edit colors row below it opens a list of every theme color with its current hex value. Enter edits the selected color in place, and a committed value applies immediately. The first edit creates a custom theme that starts from the built-in theme on screen, and the Theme row switches to `custom`. Further edits refine the custom theme, and an empty value returns a color to the built-in the custom theme started from. Clearing the last custom color dissolves the custom theme back into that built-in.
90
-
91
- The custom theme joins the Theme row cycle as a sixth entry and persists across switches, so picking a built-in theme renders it pure while the custom colors stay saved, and cycling back to `custom` restores them. Everything lands in a `theme` object in `settings.json` in the cache directory, which you can also edit by hand:
93
+ 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
94
 
93
95
  ```json
94
96
  {
@@ -101,8 +103,12 @@ The custom theme joins the Theme row cycle as a sixth entry and persists across
101
103
  }
102
104
  ```
103
105
 
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 and defaults to `default`, and every other key sets one custom color on top of that base. The color keys are `bg`, `border`, `text`, `muted`, `dim`, `accent`, `selectedBg`, `inputBg`, `inputFocusedBg`, `warn`, `error`, `success`, `chartBar`, `chartLine`, `chartDim`, and `heat`. The `heat` key colors the weekly heatmap and takes four colors from cool to hot. A file that sets colors without a `preset` activates the custom theme they define, and a `custom` preset without any colors falls back to its `base` theme. A typo in a key or a color fails the start with a message naming the mistake, so a broken theme never renders.
106
+ 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
107
 
106
108
  ## Development
107
109
 
108
- The TUI runs with `bun tui/main.tsx`. To try it under Node instead, run `pnpm tui:node`, which rebuilds the bundles and starts the built launcher from `dist/`. The entry point accepts `--debug <path>` with a path to a testdata directory, for example `bun tui/main.tsx --debug tui/testdata`, which serves canned data from the fake `gh` binary in that directory instead of fetching from GitHub. The TUI has a test that drives the whole pipeline against the same fake binary, which `bun test tui` runs. `pnpm build` bundles the binary into `dist/`, where `tui.mjs` is the runtime launcher and `tui-app.mjs` is the app bundle it loads.
110
+ 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.
111
+
112
+ ## License
113
+
114
+ OpenTUI is licensed under the [MIT License](LICENSE).