@pingroom/cli 0.2.0 → 0.6.0
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 +268 -2
- package/bin/pingroom.js +1835 -45
- package/package.json +4 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PingRoom
|
|
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
|
@@ -3,14 +3,69 @@
|
|
|
3
3
|
Send PingRoom pings — and ask a human a question and block for their answer —
|
|
4
4
|
from CI, scripts, and agents. Delivered as push straight to your phone.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
One dependency (`qrcode-terminal`, used only to draw the pairing QR). Works
|
|
7
|
+
anywhere Node ≥ 20 runs.
|
|
7
8
|
|
|
8
9
|
```bash
|
|
9
10
|
npx @pingroom/cli ping -w "$PINGROOM_WEBHOOK_URL" -m "Deploy succeeded ✅"
|
|
10
11
|
```
|
|
11
12
|
|
|
12
13
|
Commands: `ping` (send), `ask` (ask a human), `watch` (block on an existing
|
|
13
|
-
question), `list`, `cancel
|
|
14
|
+
question), `list`, `cancel`, `handoff` (hand a decision to a specific human),
|
|
15
|
+
`handoffs` (list open or recent Handoffs), `live` (lock-screen progress card),
|
|
16
|
+
`hook` (Claude Code), `config`, and `logout`.
|
|
17
|
+
Run `pingroom --help` for the full reference.
|
|
18
|
+
|
|
19
|
+
## Connecting
|
|
20
|
+
|
|
21
|
+
Run `pingroom` with no arguments. It prints a QR code — scan it with the PingRoom
|
|
22
|
+
app and pick both the account and the room the agent delivers to — or take the
|
|
23
|
+
emailed-code fallback.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
$ pingroom
|
|
27
|
+
Not connected. How do you want to connect?
|
|
28
|
+
1) Scan a QR code with the PingRoom app
|
|
29
|
+
2) Email me a code
|
|
30
|
+
Choose [1]:
|
|
31
|
+
|
|
32
|
+
[QR]
|
|
33
|
+
Or open: https://pingroom.io/app/agents/pair?token=…
|
|
34
|
+
Waiting for approval… ✓ Connected as @agt_ab12cd34ef → #Project X
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
There is deliberately no `login` command: being unconnected is a state the tool
|
|
38
|
+
resolves, not one you have to discover. Once connected, bare `pingroom` prints
|
|
39
|
+
that status line followed by the usual help.
|
|
40
|
+
|
|
41
|
+
The credential lands in `~/.pingroom/credentials.json` (mode `0600`, inside a
|
|
42
|
+
`0700` directory). `PINGROOM_HOME` moves that directory; `pingroom logout`
|
|
43
|
+
clears it.
|
|
44
|
+
|
|
45
|
+
**CI is unaffected.** `PINGROOM_TOKEN` in the environment always outranks the
|
|
46
|
+
stored credential, and a non-interactive shell never prompts and never draws a
|
|
47
|
+
QR — a command that needs a credential and has none fails with exit `2` pointing
|
|
48
|
+
at `PINGROOM_TOKEN`.
|
|
49
|
+
|
|
50
|
+
### Local settings
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pingroom config set default_room ab12cd # fallback for --room
|
|
54
|
+
pingroom config set api_url https://api.pingroom.io
|
|
55
|
+
pingroom config get default_room
|
|
56
|
+
pingroom config list
|
|
57
|
+
pingroom config set api_url "" # an empty value clears the key
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Precedence, highest first:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
explicit flag > env var > ~/.pingroom/config.json > built-in default
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
So `--room` beats `PINGROOM_ROOM` beats `default_room` (and, last of all, the
|
|
67
|
+
room the credential was paired to); `--api` beats `PINGROOM_API_URL` beats
|
|
68
|
+
`api_url`.
|
|
14
69
|
|
|
15
70
|
## Getting a webhook URL
|
|
16
71
|
|
|
@@ -26,6 +81,10 @@ pingroom ping [options]
|
|
|
26
81
|
-t, --title <text> Ping title (<= 40 chars)
|
|
27
82
|
-a, --action <1-4> Quick-action slot to attribute the ping to
|
|
28
83
|
-d, --data <json> Extra JSON data, e.g. '{"commit":"abc123"}'
|
|
84
|
+
--url <https-url> Make the ping a tappable link (absolute http(s) URL)
|
|
85
|
+
--button-label <t> Link button text (<= 26 chars; requires --url)
|
|
86
|
+
--require-ack Keep the ping open until an eligible recipient acknowledges it
|
|
87
|
+
--ack-timeout <s> Ack deadline in seconds (requires --require-ack)
|
|
29
88
|
-w, --webhook <url> Room webhook URL (or env PINGROOM_WEBHOOK_URL)
|
|
30
89
|
--token <token> Agent access token (or env PINGROOM_TOKEN)
|
|
31
90
|
--room <code> Room invite code (used with --token)
|
|
@@ -33,9 +92,121 @@ pingroom ping [options]
|
|
|
33
92
|
--json Print the raw JSON response
|
|
34
93
|
```
|
|
35
94
|
|
|
95
|
+
To make the ping actionable, add `--require-ack`. The first eligible recipient to
|
|
96
|
+
acknowledge it wins; `--ack-timeout` optionally expires it if nobody responds:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pingroom ping -w "$PINGROOM_WEBHOOK_URL" -m "Production health check failed" \
|
|
100
|
+
--require-ack --ack-timeout 300
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Webhook timeouts accept 1–86400 seconds. Agent-token room pings accept
|
|
104
|
+
60–86400 seconds.
|
|
105
|
+
|
|
106
|
+
To attach a tappable link button (a "link ping"), add `--url` and optionally
|
|
107
|
+
`--button-label`. They fold into the structured `data` object as
|
|
108
|
+
`{"url": ..., "button_label": ...}` — the same convention accepted raw via
|
|
109
|
+
`--data`:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pingroom ping -w "$PINGROOM_WEBHOOK_URL" -m "Build 512 ready" \
|
|
113
|
+
--url https://ci.example.com/builds/512 --button-label "Open build"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The URL must be absolute http(s) (≤ 2048 chars); the label caps at 26 chars.
|
|
117
|
+
|
|
36
118
|
Exit codes: `0` success · `1` delivery failed · `2` bad usage. So CI fails loudly if a
|
|
37
119
|
ping doesn't land.
|
|
38
120
|
|
|
121
|
+
## Live Activities (`pingroom live`)
|
|
122
|
+
|
|
123
|
+
A **live-status stream** is one long-running thing shown as a self-updating card
|
|
124
|
+
on the Lock Screen (iOS Live Activity / Dynamic Island, Android live update, and
|
|
125
|
+
a full inline card in the app). `start` opens it with one alert, `update` moves
|
|
126
|
+
it **silently**, `end` closes it with one completion alert.
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
pingroom live <start|update|end|get> [options]
|
|
130
|
+
|
|
131
|
+
-c, --correlation-id <id> The stream key — reuse it for every ping (required)
|
|
132
|
+
--template <name> start only: status | steps | progress | metrics |
|
|
133
|
+
countdown | question | matchup (fixed at creation)
|
|
134
|
+
--steps <a,b,c> start only: 2-8 comma-separated step labels
|
|
135
|
+
-m, --message <text> The card's live message line
|
|
136
|
+
--progress <0..1> Progress bar / Dynamic Island gauge
|
|
137
|
+
--step <n> Current step index (steps template)
|
|
138
|
+
--metric <label:value> Repeatable, up to 3 (metrics template)
|
|
139
|
+
--deadline-at <epoch> Countdown target (countdown template)
|
|
140
|
+
--eta-at <epoch> Live ETA (status/progress templates)
|
|
141
|
+
--prompt <text> The ask (question template)
|
|
142
|
+
--option <value:label> Repeatable, up to 4 (question template)
|
|
143
|
+
--left <label:value> Left side (matchup template)
|
|
144
|
+
--right <label:value> Right side (matchup template)
|
|
145
|
+
--center <text> Center score/clock, <= 40 (matchup template)
|
|
146
|
+
--accent-override <#rrggbb> Semantic accent for this frame
|
|
147
|
+
--failed end only: finish as failed instead of done
|
|
148
|
+
-t, --title <text> Card title (<= 40 chars)
|
|
149
|
+
-a, --action <1-4> Quick-action slot supplying the icon and sound
|
|
150
|
+
--require-ack Add an Acknowledge button
|
|
151
|
+
--ack-timeout <s> Ack deadline in seconds
|
|
152
|
+
-w, --webhook <url> Room webhook URL instead of a token
|
|
153
|
+
--token <token> Agent access token (or env PINGROOM_TOKEN)
|
|
154
|
+
--room <code> Room invite code (used with --token)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Works with either an agent token (`--token`, needs the `pingroom:live:write`
|
|
158
|
+
scope) or a room's incoming webhook (`--webhook`, Pro) — both speak the same
|
|
159
|
+
`live_status` contract.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Track a deploy end to end.
|
|
163
|
+
pingroom live start -c deploy-42 --template steps \
|
|
164
|
+
--steps "Build,Test,Deploy,Verify" -t "Release 1.4.0"
|
|
165
|
+
pingroom live update -c deploy-42 --step 2 -m "Deploying to prod"
|
|
166
|
+
pingroom live end -c deploy-42 -m "Shipped 1.4.0" # add --failed to fail it
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
All 7 templates are expressible:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# question — up to 4 options. A bare token is both value and label.
|
|
173
|
+
pingroom live start -c q1 --template question \
|
|
174
|
+
--prompt "Deploy where?" --option prod:Production --option staging:Staging
|
|
175
|
+
|
|
176
|
+
# matchup — two sides plus a center score/clock.
|
|
177
|
+
pingroom live start -c game-3 --template matchup \
|
|
178
|
+
--left ARS:2 --right CHE:1 --center "68'"
|
|
179
|
+
|
|
180
|
+
# metrics — up to 3 counters.
|
|
181
|
+
pingroom live start -c host-1 --template metrics --metric "CPU:45%" --metric "RPS:1.2k"
|
|
182
|
+
|
|
183
|
+
# countdown — a large live timer.
|
|
184
|
+
pingroom live start -c win-9 --template countdown --deadline-at 1750003600
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`--accent-override` takes `#rrggbb` **or** a bare `rrggbb` (case-insensitive;
|
|
188
|
+
it is normalized to lowercase with the `#` before it is sent). Pass it bare, or
|
|
189
|
+
quote it — an *unquoted* `#` starts a comment in `sh`, `bash` and `zsh`, which
|
|
190
|
+
eats the hex and the rest of the line, and the CLI then exits `2` with
|
|
191
|
+
`option --accent-override needs a value`:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
pingroom live update -c deploy-42 --accent-override e33122 # ok
|
|
195
|
+
pingroom live update -c deploy-42 --accent-override '#e33122' # ok
|
|
196
|
+
pingroom live update -c deploy-42 --accent-override #e33122 # shell eats it
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Always `end` a stream.** Terminal `done`/`failed` pings are never rate-limited
|
|
200
|
+
or quota-blocked, precisely so a card can't be metered into hanging open on
|
|
201
|
+
someone's Lock Screen. Abandoned streams are swept after ~15 minutes.
|
|
202
|
+
|
|
203
|
+
`--template` and `--steps` are fixed when the stream is created; passing them to
|
|
204
|
+
`update`/`end` is a usage error rather than a silent no-op. `pingroom live get`
|
|
205
|
+
(agent token only) reads a stream back — every stored field — so a restarted
|
|
206
|
+
producer reconciles instead of opening a duplicate.
|
|
207
|
+
|
|
208
|
+
Full protocol: <https://pingroom.io/liveactivities.md>
|
|
209
|
+
|
|
39
210
|
## GitHub Actions
|
|
40
211
|
|
|
41
212
|
```yaml
|
|
@@ -55,8 +226,28 @@ ping doesn't land.
|
|
|
55
226
|
title: 'CI failed'
|
|
56
227
|
message: '❌ ${{ github.workflow }} failed on ${{ github.ref_name }}'
|
|
57
228
|
action: '2'
|
|
229
|
+
require-ack: 'true'
|
|
230
|
+
ack-timeout: '300'
|
|
231
|
+
|
|
232
|
+
# Gate a job on a human handoff — the step fails (non-zero) on expiry, so the
|
|
233
|
+
# job stops unless someone answers. Read the decision from the step outputs.
|
|
234
|
+
- id: gate
|
|
235
|
+
uses: pingroom/cli@v0
|
|
236
|
+
with:
|
|
237
|
+
token: ${{ secrets.PINGROOM_TOKEN }}
|
|
238
|
+
message: 'Ship ${{ github.sha }} to production?'
|
|
239
|
+
handoff: 'true'
|
|
240
|
+
question: 'true'
|
|
241
|
+
options: 'deploy:Deploy,hold:Hold'
|
|
242
|
+
idempotency-key: 'deploy-${{ github.run_id }}'
|
|
243
|
+
wait: 'true'
|
|
244
|
+
- if: steps.gate.outputs.answer == 'deploy'
|
|
245
|
+
run: ./deploy-prod.sh
|
|
58
246
|
```
|
|
59
247
|
|
|
248
|
+
The handoff action exposes outputs `handoff-id`, `state`, `acknowledged-by`,
|
|
249
|
+
`answer`, and `delivery-state`.
|
|
250
|
+
|
|
60
251
|
## GitLab CI
|
|
61
252
|
|
|
62
253
|
```yaml
|
|
@@ -120,6 +311,81 @@ Approve/Deny default — two options is the lock-screen fast path. `--ttl` sets
|
|
|
120
311
|
expiry in seconds (default 1h; 30–86400). `--scope room` lets any eligible member
|
|
121
312
|
answer (first tap wins); the default `direct` asks your bound user.
|
|
122
313
|
|
|
314
|
+
## Handoffs (agent → human)
|
|
315
|
+
|
|
316
|
+
`handoff` hands a single decision to a specific human — either a simple
|
|
317
|
+
**acknowledge** ("ack to proceed") or a **question** with options. It needs an
|
|
318
|
+
agent token whose consent grants `pingroom:handoffs:create`. Unlike `ask`, a
|
|
319
|
+
handoff targets a user directly (default `me`, the bound user) rather than a
|
|
320
|
+
room, and prints machine-readable `key=value` lines.
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
# Ack handoff — block until the human acknowledges (exit 0), or it expires (3):
|
|
324
|
+
pingroom handoff --token "$PINGROOM_TOKEN" -m "Prod deploy 1.4.0 — ack to proceed" --wait
|
|
325
|
+
|
|
326
|
+
# Question handoff, blocking, branch in CI on the exit code:
|
|
327
|
+
pingroom handoff --token "$PINGROOM_TOKEN" --wait \
|
|
328
|
+
-m "Ship 1.4.0 to production?" --question -o deploy:Deploy -o hold:Hold
|
|
329
|
+
# exit 0 = answered (ANY value, incl. 'hold' — a negative human decision is not a failure)
|
|
330
|
+
# exit 3 = expired exit 4 = cancelled / recipient not ready exit 1 = error
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Flags: `--question` (or any `-o value:label`, 2–4) makes it a question, else it's
|
|
334
|
+
an ack. `--target me|<uuid>` picks the recipient. `--expires-in <s>` (120–86400,
|
|
335
|
+
default 900). `--urgency active|passive`. `--idempotency-key <key>` is sent as
|
|
336
|
+
the `Idempotency-Key` header so network retries collapse to one handoff (the
|
|
337
|
+
server 409s on a key reused with a different payload). `--correlation-id` /
|
|
338
|
+
`--reply-to` / `-d '{...}'` are echoed back. Add `--wait` to long-poll to a
|
|
339
|
+
terminal state; without it the command prints the created handoff and returns 0.
|
|
340
|
+
|
|
341
|
+
List unresolved Handoffs or bounded recent history without changing the legacy
|
|
342
|
+
question-only `list` command:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
pingroom handoffs --token "$PINGROOM_TOKEN" # open only
|
|
346
|
+
pingroom handoffs --token "$PINGROOM_TOKEN" --state all # recent, up to 200 per kind
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
A negative answer (`hold`, `deny`, …) is a **successful** `answered` state and
|
|
350
|
+
exits `0` — branch on the printed `answer=` line, not on the exit code.
|
|
351
|
+
|
|
352
|
+
## Claude Code integration (get pinged by your agent)
|
|
353
|
+
|
|
354
|
+
Wire PingRoom into [Claude Code](https://claude.com/claude-code) hooks so your
|
|
355
|
+
agent pings your phone when it finishes — and asks for your approval, on your
|
|
356
|
+
lock screen, before it runs a command. Approve or Deny with a tap; the agent
|
|
357
|
+
waits for your answer and continues.
|
|
358
|
+
|
|
359
|
+
Print a ready-to-paste config:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
pingroom hook --print-config
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Then set your credentials and merge the printed `hooks` block into
|
|
366
|
+
`~/.claude/settings.json`:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
export PINGROOM_TOKEN="<your agent token>" # a room the agent belongs to
|
|
370
|
+
export PINGROOM_ROOM="<room invite code>"
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`pingroom hook` reads the Claude Code hook event on stdin and reacts by event:
|
|
374
|
+
|
|
375
|
+
| Hook event | What happens |
|
|
376
|
+
| --- | --- |
|
|
377
|
+
| `Stop` / `SubagentStop` | Pings the room with the agent's last message (“Claude finished”). |
|
|
378
|
+
| `Notification` | Pings when the agent is idle or waiting for input (permission prompts are skipped — the `PreToolUse` question already covers those). |
|
|
379
|
+
| `SessionEnd` | Pings when a session ends (except `/clear`). |
|
|
380
|
+
| `PreToolUse` | Asks a PingRoom **question** and gates the tool call on your Approve/Deny tap. Which tools are gated is the settings.json `matcher` (default `Bash`) — not the CLI. |
|
|
381
|
+
|
|
382
|
+
**It always fails open.** If PingRoom is unreachable, the token/room is missing,
|
|
383
|
+
or the question expires, the hook defers to the normal local prompt
|
|
384
|
+
(`permissionDecision: "ask"`) and exits 0. It never auto-approves and never
|
|
385
|
+
blocks the agent. Because the `PreToolUse` hook holds the tool call open while it
|
|
386
|
+
waits for you, give it a generous `timeout` (the printed config uses 960s) and
|
|
387
|
+
tune the approval-question expiry with `--ttl <seconds>` (default 900).
|
|
388
|
+
|
|
123
389
|
For a fully typed client, use [`@pingroom/sdk`](https://www.npmjs.com/package/@pingroom/sdk).
|
|
124
390
|
See <https://pingroom.io/connect-mcp.md> to connect Cursor, Claude Desktop, or Claude Code.
|
|
125
391
|
|