watcheroni 0.0.0-stage → 0.2.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 paul cannon
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,3 +1,226 @@
1
- # Temporary Holding Version
1
+ # watcheroni
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Wait for, stream, or act on what happens to your GitHub pull requests:
4
+ CI finishing or failing, reviews, comments, pushes, merges. It's built
5
+ for scripts and coding agents that open a PR and then need to know when
6
+ there's something to do about it, without polling GitHub in a loop.
7
+
8
+ ```
9
+ $ npx watcheroni wait
10
+ octo/widgets#123: 2 new events
11
+ - check.failed: "test (ubuntu-latest)" (failure) on 4f9c2e1
12
+ gh run view 18342210911 -R octo/widgets --log-failed
13
+ - review.submitted by mona: changes_requested
14
+ gh api repos/octo/widgets/pulls/123/reviews/3374120415 --jq '.state, .body'
15
+
16
+ octo/widgets#123 is still open. Run `watcheroni wait` again to keep watching.
17
+ ```
18
+
19
+ Requires Node.js 22 or later.
20
+
21
+ ## Getting started
22
+
23
+ 1. **Install the GitHub App** on the repositories you want to watch:
24
+ <https://github.com/apps/watcheroni/installations/new>. It asks for
25
+ read-only access to pull requests, checks and their metadata, and
26
+ sends their webhooks to the watcheroni server at
27
+ <https://watcheroni.dev>.
28
+ 2. **Sign in** from your machine. This runs GitHub's device flow (a code
29
+ to enter at github.com) and saves an API key for the server:
30
+
31
+ ```
32
+ npx watcheroni login
33
+ ```
34
+ 3. **Wait** for something to happen to the PR for your current branch
35
+ (or name one: `npx watcheroni wait octo/widgets#123`):
36
+
37
+ ```
38
+ npx watcheroni wait
39
+ ```
40
+
41
+ It exits once there's news, printing each event with the command that
42
+ fetches its details, so run it again to keep watching.
43
+
44
+ To skip npx's registry check on every run, install it:
45
+ `npm install -g watcheroni`.
46
+
47
+ **No App, no server?** Everything also works without signing in: the CLI
48
+ then polls GitHub itself every 2 minutes with your `gh` login (or
49
+ `GH_TOKEN` / `GITHUB_TOKEN`), and that token never leaves your machine.
50
+ See "Local mode" below.
51
+
52
+ ## Claude Code
53
+
54
+ The watcheroni plugin for Claude Code has a session keep watching the PRs
55
+ it opens or pushes to, in the background, and deal with failing checks
56
+ and review feedback as they arrive:
57
+
58
+ ```
59
+ claude plugin marketplace add thepaul/watcheroni
60
+ claude plugin install watcheroni@watcheroni
61
+ ```
62
+
63
+ It runs this CLI through npx unless it's installed. See
64
+ [the plugin's README](https://github.com/thepaul/watcheroni/tree/main/plugin)
65
+ for what it does and doesn't do.
66
+
67
+ ## Commands
68
+
69
+ ```
70
+ watcheroni wait [target...] [--timeout 1h] [--settle 20s] [--format text|json]
71
+ watcheroni stream [target...]
72
+ watcheroni exec [target...] [--on kinds] [--batch] -- command [args...]
73
+ watcheroni watch [target...]
74
+ watcheroni unwatch <target...>
75
+ watcheroni ls
76
+ ```
77
+
78
+ Targets are `owner/repo#123` or PR URLs. With none, it's the open PR for
79
+ the current git branch, found through the checkout's GitHub remotes.
80
+ `owner/repo` watches a whole repository, in server mode only (see
81
+ below).
82
+
83
+ - **`wait`** polls (every 2 minutes; `--interval`) until something
84
+ happens, waits `--settle` for related events, prints them with the
85
+ command that fetches each one's content, and exits. Exit codes: `0`
86
+ events, `2` timeout, `3` every watched PR has ended, `4` no GitHub
87
+ credentials, `1` other errors.
88
+ - **`stream`** prints each event as a JSON line until killed.
89
+ - **`exec`** runs a command per event, with the event as JSON on stdin and
90
+ `WATCHERONI_KIND`, `_REPO`, `_PR`, `_URL` and `_EVENT_ID` in its
91
+ environment. An event is acknowledged only when the command exits 0.
92
+ `--batch` runs it once per settled batch, with a JSON array on stdin.
93
+
94
+ ## Recipes
95
+
96
+ Desktop and phone notifications, Slack, only CI failures, and running
97
+ `exec` as a background service:
98
+ [docs/recipes.md](https://github.com/thepaul/watcheroni/blob/main/docs/recipes.md).
99
+
100
+ ## Server mode
101
+
102
+ Sign in, which runs GitHub's device flow and saves an API key in
103
+ `~/.config/watcheroni/credentials.json` (mode 0600). It opens the browser
104
+ at GitHub's code page when you're at this machine's own terminal; over
105
+ SSH, or with `WATCHERONI_NO_BROWSER` set, it just prints the address:
106
+
107
+ ```
108
+ watcheroni login # https://watcheroni.dev
109
+ WATCHERONI_SERVER=https://... watcheroni login
110
+ ```
111
+
112
+ Once you're signed in to the default server, the CLI uses it. To use
113
+ another, name it in `WATCHERONI_SERVER` or `~/.config/watcheroni/config.json`
114
+ (`{"server": "https://..."}`). For CI, `WATCHERONI_API_KEY` stands in for
115
+ the credentials file. The environment wins, and `WATCHERONI_SERVER=`
116
+ (empty) forces local mode. `WATCHERONI_CONFIG_DIR` moves both files, and
117
+ `WATCHERONI_CREDENTIALS` moves just the credentials. `watcheroni logout`
118
+ revokes the key and forgets it.
119
+
120
+ To run your own server, with your own GitHub App, on Cloudflare's free
121
+ plan: `watcheroni selfhost` in a clone of the repository (see
122
+ [the server's README](https://github.com/thepaul/watcheroni/tree/main/packages/server#run-your-own)). It also sets
123
+ `server` in `config.json`.
124
+
125
+ `wait`, `stream` and `exec` then:
126
+
127
+ 1. Create the inbox on the server (`PUT /v1/inboxes/{name}`), which also
128
+ checks the key. A rejected key exits 4.
129
+ 2. Read it over a WebSocket (subprotocol `watcheroni.v1`: `hello` with the
130
+ last seq received, `ready`, `events`, `ack`, and a `ping` every 30 s).
131
+ If no socket can be opened but HTTP works, they long-poll
132
+ `GET /v1/inboxes/{name}/events?after=&wait=300` instead. Lost
133
+ connections are retried with backoff (1 s doubling to 60 s, with
134
+ jitter); close code 4001 exits 4, 4004 exits 1, and 4029 switches to
135
+ long-polling. Events seen before are dropped by `id`.
136
+ 3. Acknowledge on the server as local mode does: `wait` once it has
137
+ printed, `stream` after each line, `exec` only after the command exits
138
+ 0. A new process starts from the server's acknowledged position, so a
139
+ killed `wait` loses nothing.
140
+
141
+ Targets: each PR named on the command line (or the current branch's PR)
142
+ is subscribed on the server (`POST /v1/inboxes/{name}/subscriptions`),
143
+ which then feeds the inbox from GitHub's webhooks, and this machine stops
144
+ polling it. The request carries no GitHub token unless the server answers
145
+ `github_token_required`, meaning the repo is private; then it's sent again
146
+ with the GitHub token `watcheroni login` saved, renewed first through the
147
+ server's refresh proxy if it has expired (and saved again). The server
148
+ uses the token for one check and forgets it.
149
+
150
+ A PR the server won't watch stays polled from this machine, as in local
151
+ mode, with its events merged with the server inbox's: when the watcheroni
152
+ App isn't installed on the repo (stderr says where to install it), when
153
+ there's no usable GitHub token for a private repo (`WATCHERONI_API_KEY`
154
+ alone has none), or when the server refuses for another reason. Such a PR
155
+ is offered to the server again at most hourly. When the server ends a
156
+ subscription, a `watch.ended` or `watch.expired` stops the watch here too;
157
+ after a `watch.revoked` (the App uninstalled or the repo removed from it)
158
+ the PR goes back to being polled from this machine.
159
+
160
+ A whole repository (`owner/repo`) is watched by the server or not at all,
161
+ since local mode would have to poll every PR in it. It needs `--kinds`,
162
+ which the subscription carries (and which, as always, also filters what
163
+ `wait` prints), and may narrow to PRs against one branch (`--base main`)
164
+ or opened by one user (`--author alice`):
165
+
166
+ ```
167
+ watcheroni wait octo/widgets --kinds pr.opened --base main
168
+ watcheroni wait octo/widgets --kinds check.failed --author alice
169
+ ```
170
+
171
+ If the server won't take it (the App isn't installed, say) or can't be
172
+ reached, the command exits 1. Once subscribed it's remembered in the
173
+ inbox (`ls` lists it, `unwatch octo/widgets` ends every whole-repository
174
+ watch on it, on the server too), and while it lasts `wait` never exits 3:
175
+ it exits 0 on events and 2 on timeout. A `watch.*` event from the server
176
+ (`watch.expired` after a week unread, `watch.revoked`) ends it; name it
177
+ again to start over.
178
+
179
+ Because an inbox may be fed by subscriptions made elsewhere, which can't
180
+ be listed yet, `wait` with no PR watched here reads the inbox until
181
+ `--timeout` instead of exiting 3. It exits 3 when the PRs it was watching
182
+ have all ended.
183
+
184
+ **Fallback.** If the server can't be reached, the CLI says so on stderr
185
+ and polls the named PRs from this machine, subscribed or not, while it
186
+ keeps trying to reconnect. A `wait` with nothing to poll locally exits 1 instead of
187
+ waiting on a server that's down; `stream` and `exec` keep retrying.
188
+
189
+ ## Local mode
190
+
191
+ Each poll is one GraphQL query per PR (cost: 1 point of GitHub's 5,000 an
192
+ hour). The CLI compares it with the last snapshot and turns the
193
+ difference into events (`src/diff.ts`). The first poll of a PR is the
194
+ starting point: what was already true then isn't reported, as with a
195
+ server subscription. A PR that has already ended is reported as
196
+ `watch.ended` straight away.
197
+
198
+ State lives in `~/.local/state/watcheroni/inboxes/<inbox>.json`
199
+ (`$WATCHERONI_STATE_DIR` to move it). Events are saved before they're
200
+ printed and removed only once acknowledged, so a `wait` that gets killed
201
+ loses nothing: the next one prints the same events. One process at a time
202
+ uses an inbox. The inbox is `--inbox`, or `$WATCHERONI_INBOX`, or
203
+ `cc-$CLAUDE_CODE_SESSION_ID` inside Claude Code, or `default`.
204
+
205
+ What local mode can't see that the server will: events between polls
206
+ that undo each other (a thread resolved and then unresolved within one
207
+ interval), more than the last 100 reviews, threads or comments, and
208
+ repository-wide targets.
209
+
210
+ ## More
211
+
212
+ How it's built, and the API other clients can use:
213
+ [DESIGN.md](https://github.com/thepaul/watcheroni/blob/main/DESIGN.md),
214
+ [PROTOCOL.md](https://github.com/thepaul/watcheroni/blob/main/PROTOCOL.md)
215
+ (and its types, [`@watcheroni/protocol`](https://www.npmjs.com/package/@watcheroni/protocol)).
216
+ Issues: <https://github.com/thepaul/watcheroni/issues>. MIT licensed.
217
+
218
+ ## Develop
219
+
220
+ From a clone of the repository, in `packages/cli`:
221
+
222
+ ```
223
+ npm test # Vitest, no network; server mode runs against a fake server (test/fake-server.ts)
224
+ npm run dev -- wait octo/widgets#123
225
+ npm run build # dist/watcheroni.mjs, one file, no dependencies
226
+ ```