@pingroom/cli 0.1.0 → 0.4.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -2
  3. package/bin/pingroom.js +1025 -48
  4. package/package.json +5 -2
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
@@ -1,7 +1,7 @@
1
1
  # @pingroom/cli
2
2
 
3
- Send PingRoom pings from CI, scripts, and agents deploy notifications in one line,
4
- delivered as push straight to your phone.
3
+ Send PingRoom pings and ask a human a question and block for their answer —
4
+ from CI, scripts, and agents. Delivered as push straight to your phone.
5
5
 
6
6
  Zero dependencies. Works anywhere Node ≥ 20 runs.
7
7
 
@@ -9,6 +9,11 @@ Zero dependencies. Works anywhere Node ≥ 20 runs.
9
9
  npx @pingroom/cli ping -w "$PINGROOM_WEBHOOK_URL" -m "Deploy succeeded ✅"
10
10
  ```
11
11
 
12
+ Commands: `ping` (send), `ask` (ask a human), `watch` (block on an existing
13
+ question), `list`, `cancel`, `handoff` (hand a decision to a specific human),
14
+ and `handoffs` (list open or recent Handoffs).
15
+ Run `pingroom --help` for the full reference.
16
+
12
17
  ## Getting a webhook URL
13
18
 
14
19
  In the PingRoom app, open a room → **Connections → Incoming webhooks → Add**. Copy the
@@ -23,6 +28,8 @@ pingroom ping [options]
23
28
  -t, --title <text> Ping title (<= 40 chars)
24
29
  -a, --action <1-4> Quick-action slot to attribute the ping to
25
30
  -d, --data <json> Extra JSON data, e.g. '{"commit":"abc123"}'
31
+ --require-ack Keep the ping open until an eligible recipient acknowledges it
32
+ --ack-timeout <s> Ack deadline in seconds (requires --require-ack)
26
33
  -w, --webhook <url> Room webhook URL (or env PINGROOM_WEBHOOK_URL)
27
34
  --token <token> Agent access token (or env PINGROOM_TOKEN)
28
35
  --room <code> Room invite code (used with --token)
@@ -30,6 +37,17 @@ pingroom ping [options]
30
37
  --json Print the raw JSON response
31
38
  ```
32
39
 
40
+ To make the ping actionable, add `--require-ack`. The first eligible recipient to
41
+ acknowledge it wins; `--ack-timeout` optionally expires it if nobody responds:
42
+
43
+ ```bash
44
+ pingroom ping -w "$PINGROOM_WEBHOOK_URL" -m "Production health check failed" \
45
+ --require-ack --ack-timeout 300
46
+ ```
47
+
48
+ Webhook timeouts accept 1–86400 seconds. Agent-token room pings accept
49
+ 60–86400 seconds.
50
+
33
51
  Exit codes: `0` success · `1` delivery failed · `2` bad usage. So CI fails loudly if a
34
52
  ping doesn't land.
35
53
 
@@ -52,8 +70,28 @@ ping doesn't land.
52
70
  title: 'CI failed'
53
71
  message: '❌ ${{ github.workflow }} failed on ${{ github.ref_name }}'
54
72
  action: '2'
73
+ require-ack: 'true'
74
+ ack-timeout: '300'
75
+
76
+ # Gate a job on a human handoff — the step fails (non-zero) on expiry, so the
77
+ # job stops unless someone answers. Read the decision from the step outputs.
78
+ - id: gate
79
+ uses: pingroom/cli@v0
80
+ with:
81
+ token: ${{ secrets.PINGROOM_TOKEN }}
82
+ message: 'Ship ${{ github.sha }} to production?'
83
+ handoff: 'true'
84
+ question: 'true'
85
+ options: 'deploy:Deploy,hold:Hold'
86
+ idempotency-key: 'deploy-${{ github.run_id }}'
87
+ wait: 'true'
88
+ - if: steps.gate.outputs.answer == 'deploy'
89
+ run: ./deploy-prod.sh
55
90
  ```
56
91
 
92
+ The handoff action exposes outputs `handoff-id`, `state`, `acknowledged-by`,
93
+ `answer`, and `delivery-state`.
94
+
57
95
  ## GitLab CI
58
96
 
59
97
  ```yaml
@@ -86,6 +124,113 @@ pingroom ping --token "$PINGROOM_TOKEN" --room ab12cd -m "Release shipped" \
86
124
  -d '{"version":"1.4.0"}'
87
125
  ```
88
126
 
127
+ ## Ask a human (Questions)
128
+
129
+ Turn a human decision into a shell gate. `ask --wait` blocks until someone taps
130
+ an answer on their phone, prints the chosen option **value** to stdout, and
131
+ encodes the outcome in the exit code — `0` answered, `3` expired, `4` cancelled.
132
+ Needs an agent token and a room.
133
+
134
+ ```bash
135
+ # Gate a production deploy on a lock-screen tap (Approve/Deny is the default):
136
+ if [ "$(pingroom ask --token "$PINGROOM_TOKEN" --room ab12cd --wait \
137
+ -p 'Deploy 1.4.0 to production?')" = approve ]; then
138
+ ./deploy-prod.sh
139
+ fi
140
+
141
+ # A multi-option question, answerable by anyone in the room:
142
+ pingroom ask --token "$PINGROOM_TOKEN" --room ab12cd --scope room --wait \
143
+ -p 'Which environment?' -o prod:Production -o staging:Staging -o cancel:Cancel
144
+
145
+ # Fire-and-forget (prints the question id), then watch it later:
146
+ ID=$(pingroom ask --token "$PINGROOM_TOKEN" --room ab12cd -p 'Merge PR #42?' --ttl 1800)
147
+ pingroom watch --token "$PINGROOM_TOKEN" "$ID"
148
+
149
+ pingroom list --token "$PINGROOM_TOKEN" --state pending
150
+ pingroom cancel --token "$PINGROOM_TOKEN" "$ID"
151
+ ```
152
+
153
+ Options are `value:label` pairs (repeat `-o` for 2–4). Omit them for the binary
154
+ Approve/Deny default — two options is the lock-screen fast path. `--ttl` sets the
155
+ expiry in seconds (default 1h; 30–86400). `--scope room` lets any eligible member
156
+ answer (first tap wins); the default `direct` asks your bound user.
157
+
158
+ ## Handoffs (agent → human)
159
+
160
+ `handoff` hands a single decision to a specific human — either a simple
161
+ **acknowledge** ("ack to proceed") or a **question** with options. It needs an
162
+ agent token whose consent grants `pingroom:handoffs:create`. Unlike `ask`, a
163
+ handoff targets a user directly (default `me`, the bound user) rather than a
164
+ room, and prints machine-readable `key=value` lines.
165
+
166
+ ```bash
167
+ # Ack handoff — block until the human acknowledges (exit 0), or it expires (3):
168
+ pingroom handoff --token "$PINGROOM_TOKEN" -m "Prod deploy 1.4.0 — ack to proceed" --wait
169
+
170
+ # Question handoff, blocking, branch in CI on the exit code:
171
+ pingroom handoff --token "$PINGROOM_TOKEN" --wait \
172
+ -m "Ship 1.4.0 to production?" --question -o deploy:Deploy -o hold:Hold
173
+ # exit 0 = answered (ANY value, incl. 'hold' — a negative human decision is not a failure)
174
+ # exit 3 = expired exit 4 = cancelled / recipient not ready exit 1 = error
175
+ ```
176
+
177
+ Flags: `--question` (or any `-o value:label`, 2–4) makes it a question, else it's
178
+ an ack. `--target me|<uuid>` picks the recipient. `--expires-in <s>` (120–86400,
179
+ default 900). `--urgency active|passive`. `--idempotency-key <key>` is sent as
180
+ the `Idempotency-Key` header so network retries collapse to one handoff (the
181
+ server 409s on a key reused with a different payload). `--correlation-id` /
182
+ `--reply-to` / `-d '{...}'` are echoed back. Add `--wait` to long-poll to a
183
+ terminal state; without it the command prints the created handoff and returns 0.
184
+
185
+ List unresolved Handoffs or bounded recent history without changing the legacy
186
+ question-only `list` command:
187
+
188
+ ```bash
189
+ pingroom handoffs --token "$PINGROOM_TOKEN" # open only
190
+ pingroom handoffs --token "$PINGROOM_TOKEN" --state all # recent, up to 200 per kind
191
+ ```
192
+
193
+ A negative answer (`hold`, `deny`, …) is a **successful** `answered` state and
194
+ exits `0` — branch on the printed `answer=` line, not on the exit code.
195
+
196
+ ## Claude Code integration (get pinged by your agent)
197
+
198
+ Wire PingRoom into [Claude Code](https://claude.com/claude-code) hooks so your
199
+ agent pings your phone when it finishes — and asks for your approval, on your
200
+ lock screen, before it runs a command. Approve or Deny with a tap; the agent
201
+ waits for your answer and continues.
202
+
203
+ Print a ready-to-paste config:
204
+
205
+ ```bash
206
+ pingroom hook --print-config
207
+ ```
208
+
209
+ Then set your credentials and merge the printed `hooks` block into
210
+ `~/.claude/settings.json`:
211
+
212
+ ```bash
213
+ export PINGROOM_TOKEN="<your agent token>" # a room the agent belongs to
214
+ export PINGROOM_ROOM="<room invite code>"
215
+ ```
216
+
217
+ `pingroom hook` reads the Claude Code hook event on stdin and reacts by event:
218
+
219
+ | Hook event | What happens |
220
+ | --- | --- |
221
+ | `Stop` / `SubagentStop` | Pings the room with the agent's last message (“Claude finished”). |
222
+ | `Notification` | Pings when the agent is idle or waiting for input (permission prompts are skipped — the `PreToolUse` question already covers those). |
223
+ | `SessionEnd` | Pings when a session ends (except `/clear`). |
224
+ | `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. |
225
+
226
+ **It always fails open.** If PingRoom is unreachable, the token/room is missing,
227
+ or the question expires, the hook defers to the normal local prompt
228
+ (`permissionDecision: "ask"`) and exits 0. It never auto-approves and never
229
+ blocks the agent. Because the `PreToolUse` hook holds the tool call open while it
230
+ waits for you, give it a generous `timeout` (the printed config uses 960s) and
231
+ tune the approval-question expiry with `--ttl <seconds>` (default 900).
232
+
233
+ For a fully typed client, use [`@pingroom/sdk`](https://www.npmjs.com/package/@pingroom/sdk).
89
234
  See <https://pingroom.io/connect-mcp.md> to connect Cursor, Claude Desktop, or Claude Code.
90
235
 
91
236
  ## License