prhub 0.1.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.
prhub-0.1.0/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ .pytest_cache/
6
+ .coverage
7
+ coverage.xml
8
+ .ruff_cache/
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to prhub are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/),
4
+ and versions follow [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.0] - 2026-10-11
9
+
10
+ First public release.
11
+
12
+ ### Added
13
+
14
+ - `prhub daemon`: one poller per machine. One batched GraphQL request a minute reads every tracked PR and watched
15
+ commit; a discovery search finds your open PRs every few minutes. It slows down when the GitHub budget runs low
16
+ and waits for the reset when rate-limited.
17
+ - `prhub wait` for PRs with `done`, `green`, `checks`, `merged`, `closed`, `review` and `change`. `done` requires an
18
+ APPROVED review of the current head that is newer than the head commit.
19
+ - `prhub wait` for commits (`owner/repo@sha`) with `checks`, `green`, `deployed` and `change`, plus `--env` and
20
+ `--workflow`. `deployed` reads GitHub Deployments and `deployed/<env>` statuses, and counts a later deploy that
21
+ contains the commit.
22
+ - `prhub state`, `prhub list` (`--ready`, `--all`, `--json`), `prhub watch` / `unwatch` and `prhub status`, which
23
+ says `PAUSED` during a GitHub rate limit.
24
+ - `prhub hook claude`: a Claude Code `PreToolUse` hook that refuses GitHub polling loops and points to `prhub wait`.
25
+ - `prhub install-launchd` (macOS) and `prhub install-systemd` (Linux) to run the daemon as a service.
26
+ - Names for PRs and commits: URLs, `owner/repo#N`, `owner/repo@sha`, nicknames from the config and local clones,
27
+ and bare numbers or `@sha` inside a clone.
prhub-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Naves
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.
prhub-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.5
2
+ Name: prhub
3
+ Version: 0.1.0
4
+ Summary: One GitHub poller for many coding agents: they wait on local PR and deploy state instead of each polling the API
5
+ Project-URL: Homepage, https://github.com/danielnc/prhub
6
+ Project-URL: Documentation, https://github.com/danielnc/prhub#readme
7
+ Project-URL: Repository, https://github.com/danielnc/prhub
8
+ Project-URL: Issues, https://github.com/danielnc/prhub/issues
9
+ Project-URL: Changelog, https://github.com/danielnc/prhub/blob/main/CHANGELOG.md
10
+ Author: prhub contributors
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: ci,claude-code,cli,codex,coding-agents,deployments,github,pull-requests,rate-limit
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Topic :: Software Development :: Build Tools
26
+ Classifier: Topic :: Software Development :: Version Control :: Git
27
+ Requires-Python: >=3.11
28
+ Description-Content-Type: text/markdown
29
+
30
+ # prhub
31
+
32
+ **One GitHub poller for many coding agents.**
33
+
34
+ [![CI](https://github.com/danielnc/prhub/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/danielnc/prhub/actions/workflows/ci.yml)
35
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/danielnc/prhub/badge)](https://scorecard.dev/viewer/?uri=github.com/danielnc/prhub)
36
+ [![PyPI](https://img.shields.io/pypi/v/prhub)](https://pypi.org/project/prhub/)
37
+ [![Python versions](https://img.shields.io/pypi/pyversions/prhub)](https://pypi.org/project/prhub/)
38
+ [![Codecov](https://codecov.io/gh/danielnc/prhub/graph/badge.svg)](https://codecov.io/gh/danielnc/prhub)
39
+ [![CodeRabbit reviews](https://img.shields.io/coderabbit/prs/github/danielnc/prhub?label=CodeRabbit%20reviews)](https://coderabbit.ai)
40
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
41
+
42
+ Coding agents (Claude Code, Codex, Cursor, …) spend a lot of their time waiting: for CI on the PR they just pushed,
43
+ for a review, for a merge, for the deploy. Left alone, each one waits the same way, with its own polling loop:
44
+
45
+ ```sh
46
+ gh pr checks 2814 --watch
47
+ until gh pr view 2814 --json state | grep -q MERGED; do sleep 30; done
48
+ gh run watch 123456
49
+ ```
50
+
51
+ Every one of those loops spends the same budget: the **5,000 requests per hour** of the one GitHub token they all
52
+ share. With a dozen agents the budget is gone within the hour, and then *every* `gh` command fails for everyone,
53
+ you included, until it resets. On the author's machine, a single agent's monitoring loop once made about 190
54
+ GraphQL calls a minute and emptied the shared budget in under half an hour.
55
+
56
+ prhub replaces all of those loops with **one daemon per machine**. Once a minute it reads every PR and commit
57
+ anyone is waiting on in a single batched GraphQL request, and writes the state to local files. Agents block on
58
+ those files with `prhub wait`, which never calls GitHub.
59
+
60
+ ```sh
61
+ prhub wait api#2814 --until done # open, approved at the current head, CI green, mergeable
62
+ prhub wait api#2814 --until checks --timeout 1800 # CI finished, whatever the result
63
+ prhub wait acme/api@3a1e48e --until deployed # this commit is live in production
64
+ prhub state https://github.com/acme/api/pull/42 # current state, instantly
65
+ prhub list --ready # your PRs that are ready to merge, right now
66
+ prhub status # daemon health and the GitHub budget left
67
+ ```
68
+
69
+ ## How it works
70
+
71
+ ```
72
+ ┌──────────────── one machine ────────────────┐
73
+ GitHub API ◄── │ prhub daemon ──► ~/.local/state/prhub/*.json │ ◄── prhub wait / state / list
74
+ (1 batched │ every 60 s │ (any number of agents,
75
+ query/min) └──────────────────────────────────────────────┘ no API calls)
76
+ ```
77
+
78
+ - **Discovery.** Every few minutes the daemon runs one search for your open PRs (`is:pr is:open author:@me`, or
79
+ your own query), so agents don't have to register anything.
80
+ - **Polling.** Every minute, one GraphQL request reads every tracked PR (40 per request) and every watched commit,
81
+ together with each repo's recent deployments. With 30 to 40 open PRs that costs about 1 point per poll on the
82
+ author's machine, roughly 60 to 120 points an hour, however many agents are waiting.
83
+ - **Waiting.** `prhub wait` tracks whatever it is asked about, nudges the daemon for an early poll (at most one
84
+ every 15 s), then reads local files until the condition holds. Only data fetched *after* the wait started is
85
+ judged, so a stale file never ends a wait.
86
+ - **Backing off.** Below 1,000 points left the daemon slows to every 3 minutes, below 300 to every 10, and when
87
+ GitHub rate-limits it, it waits for the reset. During a rate limit `prhub status` says `PAUSED`, and waits keep
88
+ waiting rather than failing, so agents don't fall back to polling by hand against the same exhausted limit.
89
+
90
+ ## Install
91
+
92
+ Requires Python 3.11+ and the [GitHub CLI](https://cli.github.com), logged in (`gh auth login`). prhub reads the
93
+ token from `gh auth token` on every poll, never stores it, and has no other dependencies.
94
+
95
+ ```sh
96
+ uv tool install prhub # or: pipx install prhub
97
+ prhub install-launchd # macOS: run the daemon at login
98
+ prhub install-systemd # Linux: run it as a systemd user service
99
+ prhub status # "daemon: healthy" within a minute
100
+ ```
101
+
102
+ To try it without installing a service, run `prhub daemon` in a terminal. Uninstall: on macOS,
103
+ `launchctl bootout gui/$(id -u)/dev.prhub.daemon` and delete `~/Library/LaunchAgents/dev.prhub.daemon.plist`; on
104
+ Linux, `systemctl --user disable --now prhub` and delete `~/.config/systemd/user/prhub.service`.
105
+
106
+ ## Pull requests
107
+
108
+ `prhub wait <pr> --until <condition>[,<condition>…]` returns as soon as any one condition holds.
109
+
110
+ | condition | met when |
111
+ |---|---|
112
+ | `done` | open, not a draft, CI SUCCESS, merge state CLEAN (configurable), and an APPROVED review **of the current head** that is newer than the head commit |
113
+ | `green` | the CI rollup is SUCCESS |
114
+ | `checks` | CI finished, whatever the result |
115
+ | `merged` | the PR was merged |
116
+ | `closed` | the PR was merged or closed |
117
+ | `review` | a new review arrived after the wait started |
118
+ | `change` | anything relevant changed: state, draft, CI, review, merge state, head commit |
119
+
120
+ `done` deliberately does not trust GitHub's `reviewDecision` alone: it stays APPROVED after a new push unless the
121
+ repository dismisses stale reviews, and a force-push with an unchanged patch relabels an old review onto the new
122
+ head. `prhub state` marks such a PR `review=APPROVED(stale: no approval of this head)`.
123
+
124
+ When CI goes red, `state` and `wait` also print the names of the failing checks.
125
+
126
+ ## Commits: CI and deploys
127
+
128
+ The same daemon watches commits, in any repository, so agents stop polling `gh run watch` and the Actions API.
129
+
130
+ ```sh
131
+ prhub wait acme/api@3a1e48e --until green # every CI run on the commit passed
132
+ prhub wait api@3a1e48e --until green --workflow deploy # only runs whose name contains "deploy"
133
+ prhub wait https://github.com/acme/api/commit/3a1e48e --until deployed --env production
134
+ prhub state @3a1e48e # a commit of the clone you are in
135
+ ```
136
+
137
+ | condition | met when |
138
+ |---|---|
139
+ | `checks` | every run on the commit finished, whatever the result |
140
+ | `green` | every run passed (exits `3` as soon as one fails) |
141
+ | `deployed` | the commit is live in `--env` (default `production`, or `any`) |
142
+ | `change` | anything about the commit's runs or deploys changed |
143
+
144
+ It works the same whatever a repository's workflow names, default branch or release style:
145
+
146
+ - **CI** is every check suite that ran something on the commit. Suites from apps that never start a run (they sit
147
+ QUEUED forever) and cron-triggered runs (they attach to whatever the branch head is) are left out. A run
148
+ cancelled because a newer run of the same workflow replaced it is not a failure.
149
+ - **Deployed** reads [GitHub Deployments](https://docs.github.com/en/rest/deployments), plus commit statuses named
150
+ like `deployed/<env>`. A successful deploy of a *later* commit counts when that commit contains yours (asked once
151
+ per new deployment through the compare API), so nightly releases, batched deploys and a cancelled deploy followed
152
+ by a newer one all answer correctly. If a repository records no Deployments, use
153
+ `--until green --workflow <name of its deploy workflow>`.
154
+
155
+ A commit stays tracked for 6 hours after the last `state` or `wait` that asked about it.
156
+
157
+ ## Naming things
158
+
159
+ | you type | means |
160
+ |---|---|
161
+ | `https://github.com/acme/api/pull/42`, `acme/api#42` | a PR |
162
+ | `42` | PR 42 of the clone in the current directory |
163
+ | `api #42`, `api#42` | a PR, by repo name or nickname (see below) |
164
+ | `https://github.com/acme/api/commit/3a1e48e`, `acme/api@3a1e48e` | a commit (7 to 40 hex characters) |
165
+ | `api@3a1e48e`, `@3a1e48e` | a commit, by nickname, or in the current clone |
166
+
167
+ Nicknames come from `[aliases]` in the config, and from the names of the git clones found under `clone_roots`.
168
+
169
+ ## Exit codes
170
+
171
+ `0` the condition was met · `1` timed out · `2` bad arguments, or no such commit · `3` the condition can never be
172
+ met (the PR closed; for a commit, a run failed under `green` or its deploy failed) · `4` the daemon is not running.
173
+
174
+ ## Agent setup
175
+
176
+ **Tell your agents.** Add something like this to your agents' instructions (`CLAUDE.md`, `AGENTS.md`, …); a
177
+ longer version is in [docs/agents.md](docs/agents.md):
178
+
179
+ ```markdown
180
+ ## Waiting on GitHub: use prhub, never poll
181
+ Every agent on this machine shares one GitHub token. To wait for a PR, CI or a deploy, run
182
+ `prhub wait <pr or owner/repo@sha> --until <condition>` (in the background if you need to keep working), and use
183
+ `prhub state <pr>` or `prhub list --ready` for the current state. These read local files and cost nothing.
184
+ Never loop over `gh pr checks`, `gh pr view`, `gh run watch` or `gh api` with `sleep`.
185
+ If `prhub status` says PAUSED, that is a GitHub rate limit: keep waiting, don't poll by hand.
186
+ ```
187
+
188
+ **Enforce it (Claude Code).** `prhub hook claude` is a `PreToolUse` hook that refuses `gh pr checks --watch`,
189
+ `gh run watch`, `watch … gh …` and shell loops that `sleep` around `gh pr`, `gh run` or `gh api`, and tells the
190
+ agent which `prhub wait` to use instead. One-off reads and every write pass through untouched, and the hook stands
191
+ aside entirely when the daemon is not healthy. Add it to `~/.claude/settings.json`:
192
+
193
+ ```json
194
+ { "hooks": { "PreToolUse": [ { "matcher": "Bash",
195
+ "hooks": [ { "type": "command", "command": "prhub hook claude", "timeout": 5 } ] } ] } }
196
+ ```
197
+
198
+ Put `PRHUB_ALLOW=1` anywhere in a command to let one loop through on purpose.
199
+
200
+ ## Configuration
201
+
202
+ `~/.config/prhub/config.toml` (override the path with `PRHUB_CONFIG`). Every key is optional:
203
+
204
+ ```toml
205
+ search = "is:pr is:open author:@me org:acme" # what the daemon discovers by itself
206
+ interval = 60 # seconds between polls
207
+ discover_every = 180 # seconds between discovery searches
208
+ clone_roots = ["~/code"] # clones under here become repo nicknames
209
+
210
+ [aliases]
211
+ api = "acme/api" # nickname -> owner/repo, or the name of a local clone
212
+ web = "acme/web-app"
213
+
214
+ [done]
215
+ merge_states = ["CLEAN", "HAS_HOOKS"] # merge states that count as mergeable for `done`
216
+ ```
217
+
218
+ Any PR or commit an agent waits on is tracked automatically, even if the search would not find it (someone else's
219
+ PR, another organization). Closed PRs drop out an hour after they close.
220
+
221
+ ## Files
222
+
223
+ Everything lives in `~/.local/state/prhub` (override with `PRHUB_HOME`):
224
+
225
+ | path | what |
226
+ |---|---|
227
+ | `prs/<owner>__<repo>__<n>.json` | one record per PR: a summary plus the raw GraphQL fields |
228
+ | `commits/<owner>__<repo>__<sha>.json` | one record per watched commit |
229
+ | `watch.json`, `commit-watch.json` | what agents asked to track, and who asked (`PRHUB_CALLER`, else a tmux or Herdr pane id) |
230
+ | `events.jsonl` | every state change, one JSON line each |
231
+ | `daemon.json`, `daemon.log` | daemon health, the GitHub budget left, and the log |
232
+
233
+ Other tools can read these too. A dashboard, a merge queue or a status bar can use them without calling GitHub.
234
+
235
+ ## Limits
236
+
237
+ - github.com only for now; GitHub Enterprise Server is not supported.
238
+ - One daemon per machine and per GitHub token. Agents on other machines need their own daemon.
239
+ - The state is up to one poll interval old (60 s by default). `prhub state --fresh` asks for a poll first.
240
+ - `done` checks the review decision, CI and merge state that GitHub reports. If your merge rules need more (a
241
+ specific reviewer, resolved threads), check those before merging.
242
+
243
+ ## Contributing
244
+
245
+ Bug reports and pull requests are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md). Report security issues
246
+ privately, as described in [SECURITY.md](SECURITY.md).
247
+
248
+ ## License
249
+
250
+ [MIT](LICENSE)
prhub-0.1.0/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # prhub
2
+
3
+ **One GitHub poller for many coding agents.**
4
+
5
+ [![CI](https://github.com/danielnc/prhub/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/danielnc/prhub/actions/workflows/ci.yml)
6
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/danielnc/prhub/badge)](https://scorecard.dev/viewer/?uri=github.com/danielnc/prhub)
7
+ [![PyPI](https://img.shields.io/pypi/v/prhub)](https://pypi.org/project/prhub/)
8
+ [![Python versions](https://img.shields.io/pypi/pyversions/prhub)](https://pypi.org/project/prhub/)
9
+ [![Codecov](https://codecov.io/gh/danielnc/prhub/graph/badge.svg)](https://codecov.io/gh/danielnc/prhub)
10
+ [![CodeRabbit reviews](https://img.shields.io/coderabbit/prs/github/danielnc/prhub?label=CodeRabbit%20reviews)](https://coderabbit.ai)
11
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
12
+
13
+ Coding agents (Claude Code, Codex, Cursor, …) spend a lot of their time waiting: for CI on the PR they just pushed,
14
+ for a review, for a merge, for the deploy. Left alone, each one waits the same way, with its own polling loop:
15
+
16
+ ```sh
17
+ gh pr checks 2814 --watch
18
+ until gh pr view 2814 --json state | grep -q MERGED; do sleep 30; done
19
+ gh run watch 123456
20
+ ```
21
+
22
+ Every one of those loops spends the same budget: the **5,000 requests per hour** of the one GitHub token they all
23
+ share. With a dozen agents the budget is gone within the hour, and then *every* `gh` command fails for everyone,
24
+ you included, until it resets. On the author's machine, a single agent's monitoring loop once made about 190
25
+ GraphQL calls a minute and emptied the shared budget in under half an hour.
26
+
27
+ prhub replaces all of those loops with **one daemon per machine**. Once a minute it reads every PR and commit
28
+ anyone is waiting on in a single batched GraphQL request, and writes the state to local files. Agents block on
29
+ those files with `prhub wait`, which never calls GitHub.
30
+
31
+ ```sh
32
+ prhub wait api#2814 --until done # open, approved at the current head, CI green, mergeable
33
+ prhub wait api#2814 --until checks --timeout 1800 # CI finished, whatever the result
34
+ prhub wait acme/api@3a1e48e --until deployed # this commit is live in production
35
+ prhub state https://github.com/acme/api/pull/42 # current state, instantly
36
+ prhub list --ready # your PRs that are ready to merge, right now
37
+ prhub status # daemon health and the GitHub budget left
38
+ ```
39
+
40
+ ## How it works
41
+
42
+ ```
43
+ ┌──────────────── one machine ────────────────┐
44
+ GitHub API ◄── │ prhub daemon ──► ~/.local/state/prhub/*.json │ ◄── prhub wait / state / list
45
+ (1 batched │ every 60 s │ (any number of agents,
46
+ query/min) └──────────────────────────────────────────────┘ no API calls)
47
+ ```
48
+
49
+ - **Discovery.** Every few minutes the daemon runs one search for your open PRs (`is:pr is:open author:@me`, or
50
+ your own query), so agents don't have to register anything.
51
+ - **Polling.** Every minute, one GraphQL request reads every tracked PR (40 per request) and every watched commit,
52
+ together with each repo's recent deployments. With 30 to 40 open PRs that costs about 1 point per poll on the
53
+ author's machine, roughly 60 to 120 points an hour, however many agents are waiting.
54
+ - **Waiting.** `prhub wait` tracks whatever it is asked about, nudges the daemon for an early poll (at most one
55
+ every 15 s), then reads local files until the condition holds. Only data fetched *after* the wait started is
56
+ judged, so a stale file never ends a wait.
57
+ - **Backing off.** Below 1,000 points left the daemon slows to every 3 minutes, below 300 to every 10, and when
58
+ GitHub rate-limits it, it waits for the reset. During a rate limit `prhub status` says `PAUSED`, and waits keep
59
+ waiting rather than failing, so agents don't fall back to polling by hand against the same exhausted limit.
60
+
61
+ ## Install
62
+
63
+ Requires Python 3.11+ and the [GitHub CLI](https://cli.github.com), logged in (`gh auth login`). prhub reads the
64
+ token from `gh auth token` on every poll, never stores it, and has no other dependencies.
65
+
66
+ ```sh
67
+ uv tool install prhub # or: pipx install prhub
68
+ prhub install-launchd # macOS: run the daemon at login
69
+ prhub install-systemd # Linux: run it as a systemd user service
70
+ prhub status # "daemon: healthy" within a minute
71
+ ```
72
+
73
+ To try it without installing a service, run `prhub daemon` in a terminal. Uninstall: on macOS,
74
+ `launchctl bootout gui/$(id -u)/dev.prhub.daemon` and delete `~/Library/LaunchAgents/dev.prhub.daemon.plist`; on
75
+ Linux, `systemctl --user disable --now prhub` and delete `~/.config/systemd/user/prhub.service`.
76
+
77
+ ## Pull requests
78
+
79
+ `prhub wait <pr> --until <condition>[,<condition>…]` returns as soon as any one condition holds.
80
+
81
+ | condition | met when |
82
+ |---|---|
83
+ | `done` | open, not a draft, CI SUCCESS, merge state CLEAN (configurable), and an APPROVED review **of the current head** that is newer than the head commit |
84
+ | `green` | the CI rollup is SUCCESS |
85
+ | `checks` | CI finished, whatever the result |
86
+ | `merged` | the PR was merged |
87
+ | `closed` | the PR was merged or closed |
88
+ | `review` | a new review arrived after the wait started |
89
+ | `change` | anything relevant changed: state, draft, CI, review, merge state, head commit |
90
+
91
+ `done` deliberately does not trust GitHub's `reviewDecision` alone: it stays APPROVED after a new push unless the
92
+ repository dismisses stale reviews, and a force-push with an unchanged patch relabels an old review onto the new
93
+ head. `prhub state` marks such a PR `review=APPROVED(stale: no approval of this head)`.
94
+
95
+ When CI goes red, `state` and `wait` also print the names of the failing checks.
96
+
97
+ ## Commits: CI and deploys
98
+
99
+ The same daemon watches commits, in any repository, so agents stop polling `gh run watch` and the Actions API.
100
+
101
+ ```sh
102
+ prhub wait acme/api@3a1e48e --until green # every CI run on the commit passed
103
+ prhub wait api@3a1e48e --until green --workflow deploy # only runs whose name contains "deploy"
104
+ prhub wait https://github.com/acme/api/commit/3a1e48e --until deployed --env production
105
+ prhub state @3a1e48e # a commit of the clone you are in
106
+ ```
107
+
108
+ | condition | met when |
109
+ |---|---|
110
+ | `checks` | every run on the commit finished, whatever the result |
111
+ | `green` | every run passed (exits `3` as soon as one fails) |
112
+ | `deployed` | the commit is live in `--env` (default `production`, or `any`) |
113
+ | `change` | anything about the commit's runs or deploys changed |
114
+
115
+ It works the same whatever a repository's workflow names, default branch or release style:
116
+
117
+ - **CI** is every check suite that ran something on the commit. Suites from apps that never start a run (they sit
118
+ QUEUED forever) and cron-triggered runs (they attach to whatever the branch head is) are left out. A run
119
+ cancelled because a newer run of the same workflow replaced it is not a failure.
120
+ - **Deployed** reads [GitHub Deployments](https://docs.github.com/en/rest/deployments), plus commit statuses named
121
+ like `deployed/<env>`. A successful deploy of a *later* commit counts when that commit contains yours (asked once
122
+ per new deployment through the compare API), so nightly releases, batched deploys and a cancelled deploy followed
123
+ by a newer one all answer correctly. If a repository records no Deployments, use
124
+ `--until green --workflow <name of its deploy workflow>`.
125
+
126
+ A commit stays tracked for 6 hours after the last `state` or `wait` that asked about it.
127
+
128
+ ## Naming things
129
+
130
+ | you type | means |
131
+ |---|---|
132
+ | `https://github.com/acme/api/pull/42`, `acme/api#42` | a PR |
133
+ | `42` | PR 42 of the clone in the current directory |
134
+ | `api #42`, `api#42` | a PR, by repo name or nickname (see below) |
135
+ | `https://github.com/acme/api/commit/3a1e48e`, `acme/api@3a1e48e` | a commit (7 to 40 hex characters) |
136
+ | `api@3a1e48e`, `@3a1e48e` | a commit, by nickname, or in the current clone |
137
+
138
+ Nicknames come from `[aliases]` in the config, and from the names of the git clones found under `clone_roots`.
139
+
140
+ ## Exit codes
141
+
142
+ `0` the condition was met · `1` timed out · `2` bad arguments, or no such commit · `3` the condition can never be
143
+ met (the PR closed; for a commit, a run failed under `green` or its deploy failed) · `4` the daemon is not running.
144
+
145
+ ## Agent setup
146
+
147
+ **Tell your agents.** Add something like this to your agents' instructions (`CLAUDE.md`, `AGENTS.md`, …); a
148
+ longer version is in [docs/agents.md](docs/agents.md):
149
+
150
+ ```markdown
151
+ ## Waiting on GitHub: use prhub, never poll
152
+ Every agent on this machine shares one GitHub token. To wait for a PR, CI or a deploy, run
153
+ `prhub wait <pr or owner/repo@sha> --until <condition>` (in the background if you need to keep working), and use
154
+ `prhub state <pr>` or `prhub list --ready` for the current state. These read local files and cost nothing.
155
+ Never loop over `gh pr checks`, `gh pr view`, `gh run watch` or `gh api` with `sleep`.
156
+ If `prhub status` says PAUSED, that is a GitHub rate limit: keep waiting, don't poll by hand.
157
+ ```
158
+
159
+ **Enforce it (Claude Code).** `prhub hook claude` is a `PreToolUse` hook that refuses `gh pr checks --watch`,
160
+ `gh run watch`, `watch … gh …` and shell loops that `sleep` around `gh pr`, `gh run` or `gh api`, and tells the
161
+ agent which `prhub wait` to use instead. One-off reads and every write pass through untouched, and the hook stands
162
+ aside entirely when the daemon is not healthy. Add it to `~/.claude/settings.json`:
163
+
164
+ ```json
165
+ { "hooks": { "PreToolUse": [ { "matcher": "Bash",
166
+ "hooks": [ { "type": "command", "command": "prhub hook claude", "timeout": 5 } ] } ] } }
167
+ ```
168
+
169
+ Put `PRHUB_ALLOW=1` anywhere in a command to let one loop through on purpose.
170
+
171
+ ## Configuration
172
+
173
+ `~/.config/prhub/config.toml` (override the path with `PRHUB_CONFIG`). Every key is optional:
174
+
175
+ ```toml
176
+ search = "is:pr is:open author:@me org:acme" # what the daemon discovers by itself
177
+ interval = 60 # seconds between polls
178
+ discover_every = 180 # seconds between discovery searches
179
+ clone_roots = ["~/code"] # clones under here become repo nicknames
180
+
181
+ [aliases]
182
+ api = "acme/api" # nickname -> owner/repo, or the name of a local clone
183
+ web = "acme/web-app"
184
+
185
+ [done]
186
+ merge_states = ["CLEAN", "HAS_HOOKS"] # merge states that count as mergeable for `done`
187
+ ```
188
+
189
+ Any PR or commit an agent waits on is tracked automatically, even if the search would not find it (someone else's
190
+ PR, another organization). Closed PRs drop out an hour after they close.
191
+
192
+ ## Files
193
+
194
+ Everything lives in `~/.local/state/prhub` (override with `PRHUB_HOME`):
195
+
196
+ | path | what |
197
+ |---|---|
198
+ | `prs/<owner>__<repo>__<n>.json` | one record per PR: a summary plus the raw GraphQL fields |
199
+ | `commits/<owner>__<repo>__<sha>.json` | one record per watched commit |
200
+ | `watch.json`, `commit-watch.json` | what agents asked to track, and who asked (`PRHUB_CALLER`, else a tmux or Herdr pane id) |
201
+ | `events.jsonl` | every state change, one JSON line each |
202
+ | `daemon.json`, `daemon.log` | daemon health, the GitHub budget left, and the log |
203
+
204
+ Other tools can read these too. A dashboard, a merge queue or a status bar can use them without calling GitHub.
205
+
206
+ ## Limits
207
+
208
+ - github.com only for now; GitHub Enterprise Server is not supported.
209
+ - One daemon per machine and per GitHub token. Agents on other machines need their own daemon.
210
+ - The state is up to one poll interval old (60 s by default). `prhub state --fresh` asks for a poll first.
211
+ - `done` checks the review decision, CI and merge state that GitHub reports. If your merge rules need more (a
212
+ specific reviewer, resolved threads), check those before merging.
213
+
214
+ ## Contributing
215
+
216
+ Bug reports and pull requests are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md). Report security issues
217
+ privately, as described in [SECURITY.md](SECURITY.md).
218
+
219
+ ## License
220
+
221
+ [MIT](LICENSE)
@@ -0,0 +1,2 @@
1
+ """prhub: one GitHub poller for many coding agents."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())