@pingroom/cli 0.7.4 → 0.7.5
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/README.md +31 -3
- package/lib/commands/hook.js +16 -4
- package/lib/commands/ping.js +51 -1
- package/lib/constants.js +5 -0
- package/lib/help.js +7 -0
- package/lib/parser.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -159,6 +159,9 @@ pingroom ping [options]
|
|
|
159
159
|
-d, --data <json> Extra JSON data, e.g. '{"commit":"abc123"}'
|
|
160
160
|
--url <https-url> Make the ping a tappable link (absolute http(s) URL)
|
|
161
161
|
--button-label <t> Link button text (<= 26 chars; requires --url)
|
|
162
|
+
--location <lat,lng> Attach a map location (latitude,longitude)
|
|
163
|
+
--location-label <t> Location label (<= 100 chars; requires --location)
|
|
164
|
+
--location-address <t> Address (<= 255 chars; requires --location)
|
|
162
165
|
--require-ack Keep the ping open until an eligible recipient acknowledges it
|
|
163
166
|
--ack-timeout <s> Ack deadline in seconds (requires --require-ack)
|
|
164
167
|
--attach <path> Attach a file; repeat for up to 4 (requires --token)
|
|
@@ -174,6 +177,23 @@ in private rooms and 160 in public rooms. A room code or webhook URL does not
|
|
|
174
177
|
reveal room visibility, so the CLI rejects only bodies over 160 locally; the
|
|
175
178
|
server applies the tighter 120-character private-room limit.
|
|
176
179
|
|
|
180
|
+
To send a location, pass decimal latitude and longitude as one comma-separated
|
|
181
|
+
value. Optional map text rides inside the same reserved `data.location` object:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pingroom ping --room ab12cd -m "Meet me here" \
|
|
185
|
+
--location "25.2048,55.2708" \
|
|
186
|
+
--location-label "Dubai Mall" \
|
|
187
|
+
--location-address "Downtown Dubai"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Latitude is inclusive -90..90 and longitude is inclusive -180..180. Labels are
|
|
191
|
+
limited to 100 Unicode characters and addresses to 255. The recipient can share
|
|
192
|
+
the point or open it in Waze, Google Maps, Apple Maps, or another installed map
|
|
193
|
+
app. These flags work with agent-token and incoming-webhook sends. When combined
|
|
194
|
+
with `--data`, the explicit flags replace only `data.location`; sibling keys are
|
|
195
|
+
preserved.
|
|
196
|
+
|
|
177
197
|
To make the ping actionable, add `--require-ack`. The first eligible recipient to
|
|
178
198
|
acknowledge it wins; `--ack-timeout` optionally expires it if nobody responds:
|
|
179
199
|
|
|
@@ -375,13 +395,15 @@ Full protocol: <https://pingroom.io/liveactivities.md>
|
|
|
375
395
|
run: ./deploy-prod.sh
|
|
376
396
|
|
|
377
397
|
# Or ask the whole room instead of one person. Same outputs, plus question-id.
|
|
378
|
-
#
|
|
398
|
+
# `scope: room` is what opens it to the room — without it a question is
|
|
399
|
+
# answerable only by the connecting account. Requires v0.7.5 or newer.
|
|
379
400
|
- id: env
|
|
380
|
-
uses: pingroom/cli@v0.7.
|
|
401
|
+
uses: pingroom/cli@v0.7.5
|
|
381
402
|
with:
|
|
382
403
|
token: ${{ secrets.PINGROOM_TOKEN }}
|
|
383
404
|
room: ab12cd
|
|
384
405
|
ask: 'true'
|
|
406
|
+
scope: 'room'
|
|
385
407
|
message: 'Which environment?'
|
|
386
408
|
context: 'build ${{ github.run_number }}'
|
|
387
409
|
options: |
|
|
@@ -398,7 +420,13 @@ including `@v0` before it is moved to v0.7.3 — GitHub treats them as unexpecte
|
|
|
398
420
|
inputs, warns, and drops them; the step then falls through to the plain `ping`
|
|
399
421
|
path, `outputs.answer` is never set, and any job gated on it silently proceeds.
|
|
400
422
|
|
|
401
|
-
`
|
|
423
|
+
`scope` was added in **v0.7.5**, and without it every `ask` goes out as a direct
|
|
424
|
+
question to the connecting account — the step still succeeds, so an Action meant
|
|
425
|
+
to poll the room silently polls one person instead.
|
|
426
|
+
|
|
427
|
+
`urgent` shipped in the **0.7.4 npm package**, but no `v0.7.4` Action tag was
|
|
428
|
+
ever cut, so **v0.7.5 is the first tag that carries it**. It has the same failure
|
|
429
|
+
mode on an older pin:
|
|
402
430
|
GitHub drops the unknown input and the Ping goes out at normal priority, which
|
|
403
431
|
looks like it worked. `require-ack` is not a substitute — as of the same release
|
|
404
432
|
it opens the acknowledgement lifecycle without raising the interruption level,
|
package/lib/commands/hook.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
|
|
3
|
-
import { EXIT,
|
|
3
|
+
import { EXIT, PRIVATE_PING_MESSAGE_MAX_LENGTH } from '../constants.js';
|
|
4
4
|
import { truncate } from '../util.js';
|
|
5
5
|
import { commandHelp } from '../help.js';
|
|
6
6
|
import { hookFetch, isSafeUrl } from '../http.js';
|
|
@@ -62,7 +62,14 @@ function summarizeTranscript(path) {
|
|
|
62
62
|
const msg = entry && entry.message;
|
|
63
63
|
if (!msg || msg.role !== 'assistant') continue;
|
|
64
64
|
const text = extractAssistantText(msg.content).replace(/\s+/g, ' ').trim();
|
|
65
|
-
|
|
65
|
+
// The tighter PRIVATE bound, not the public one. `ping --message` may sit at
|
|
66
|
+
// the public ceiling because the caller typed that text and the server is
|
|
67
|
+
// entitled to reject it. Here the CLI COMPOSES the body, and a hook room is
|
|
68
|
+
// private (max 120), so truncating at 160 would hand the server a body it
|
|
69
|
+
// rejects — and hookPing swallows the 422, silently dropping the ping. The
|
|
70
|
+
// text is already being truncated, so the tighter cut costs nothing and is
|
|
71
|
+
// valid in a public room too.
|
|
72
|
+
if (text) return truncate(text, PRIVATE_PING_MESSAGE_MAX_LENGTH);
|
|
66
73
|
}
|
|
67
74
|
return '';
|
|
68
75
|
}
|
|
@@ -185,7 +192,7 @@ async function hookNotify(event, name, { token, room, apiBase, args }) {
|
|
|
185
192
|
title = 'Claude finished';
|
|
186
193
|
message = summarizeTranscript(event.transcript_path) || 'Session finished — waiting for you.';
|
|
187
194
|
} else if (name === 'Notification') {
|
|
188
|
-
message = truncate(event.message || 'Claude is waiting for your input.',
|
|
195
|
+
message = truncate(event.message || 'Claude is waiting for your input.', PRIVATE_PING_MESSAGE_MAX_LENGTH);
|
|
189
196
|
// A PreToolUse hook already turns permission prompts into a question; skip
|
|
190
197
|
// the duplicate "needs your permission" Notification so you aren't paged twice.
|
|
191
198
|
if (/permission/i.test(message)) return EXIT.OK;
|
|
@@ -193,7 +200,12 @@ async function hookNotify(event, name, { token, room, apiBase, args }) {
|
|
|
193
200
|
} else if (name === 'SessionEnd') {
|
|
194
201
|
if (event.reason === 'clear') return EXIT.OK; // /clear isn't worth a ping
|
|
195
202
|
title = 'Session ended';
|
|
196
|
-
|
|
203
|
+
// `reason` is whatever the host put in the event, so bound the composed
|
|
204
|
+
// line rather than trusting it to stay short.
|
|
205
|
+
message = truncate(
|
|
206
|
+
`Claude Code session ended (${event.reason || 'unknown'}).`,
|
|
207
|
+
PRIVATE_PING_MESSAGE_MAX_LENGTH,
|
|
208
|
+
);
|
|
197
209
|
} else {
|
|
198
210
|
return EXIT.OK; // unknown event — stay silent rather than send noise
|
|
199
211
|
}
|
package/lib/commands/ping.js
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EXIT,
|
|
3
|
+
LOCATION_ADDRESS_MAX_LENGTH,
|
|
4
|
+
LOCATION_LABEL_MAX_LENGTH,
|
|
5
|
+
PING_TITLE_MAX_LENGTH,
|
|
6
|
+
PUBLIC_PING_MESSAGE_MAX_LENGTH,
|
|
7
|
+
} from '../constants.js';
|
|
2
8
|
import { fail, parseDataObject, requireMaxLength } from '../util.js';
|
|
3
9
|
import { commandHelp } from '../help.js';
|
|
4
10
|
import { apiDetail, httpJson, requireSafeUrl, uploadAttachments } from '../http.js';
|
|
5
11
|
import { requireStoredCredentialOrigin, resolveApiBase, resolveRoom, resolveToken } from '../config.js';
|
|
6
12
|
|
|
13
|
+
const DECIMAL_COORDINATE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
14
|
+
|
|
15
|
+
function parseLocation(value) {
|
|
16
|
+
const parts = String(value).split(',');
|
|
17
|
+
if (parts.length !== 2) {
|
|
18
|
+
fail('--location must contain exactly two coordinates formatted "latitude,longitude"', EXIT.USAGE);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const [latitudeText, longitudeText] = parts.map((part) => part.trim());
|
|
22
|
+
if (!DECIMAL_COORDINATE.test(latitudeText) || !DECIMAL_COORDINATE.test(longitudeText)) {
|
|
23
|
+
fail('--location coordinates must be finite numbers formatted "latitude,longitude"', EXIT.USAGE);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const latitude = Number(latitudeText);
|
|
27
|
+
const longitude = Number(longitudeText);
|
|
28
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
29
|
+
fail('--location coordinates must be finite numbers formatted "latitude,longitude"', EXIT.USAGE);
|
|
30
|
+
}
|
|
31
|
+
if (latitude < -90 || latitude > 90) {
|
|
32
|
+
fail('--location latitude must be between -90 and 90', EXIT.USAGE);
|
|
33
|
+
}
|
|
34
|
+
if (longitude < -180 || longitude > 180) {
|
|
35
|
+
fail('--location longitude must be between -180 and 180', EXIT.USAGE);
|
|
36
|
+
}
|
|
37
|
+
return { latitude, longitude };
|
|
38
|
+
}
|
|
39
|
+
|
|
7
40
|
export async function ping(args) {
|
|
8
41
|
if (args.help) { process.stdout.write(`${commandHelp('ping')}\n`); return EXIT.OK; }
|
|
9
42
|
|
|
@@ -59,6 +92,23 @@ export async function ping(args) {
|
|
|
59
92
|
if (args.button_label !== undefined) data.button_label = args.button_label;
|
|
60
93
|
}
|
|
61
94
|
|
|
95
|
+
// Location ping: explicit flags own the reserved data.location object. They
|
|
96
|
+
// replace a raw --data location while leaving every sibling key untouched.
|
|
97
|
+
if (args.location_label !== undefined && args.location === undefined) {
|
|
98
|
+
fail('--location-label requires --location', EXIT.USAGE);
|
|
99
|
+
}
|
|
100
|
+
if (args.location_address !== undefined && args.location === undefined) {
|
|
101
|
+
fail('--location-address requires --location', EXIT.USAGE);
|
|
102
|
+
}
|
|
103
|
+
if (args.location !== undefined) {
|
|
104
|
+
requireMaxLength(args.location_label, LOCATION_LABEL_MAX_LENGTH, '--location-label');
|
|
105
|
+
requireMaxLength(args.location_address, LOCATION_ADDRESS_MAX_LENGTH, '--location-address');
|
|
106
|
+
const location = parseLocation(args.location);
|
|
107
|
+
if (args.location_label !== undefined) location.label = args.location_label;
|
|
108
|
+
if (args.location_address !== undefined) location.address = args.location_address;
|
|
109
|
+
data = { ...(data || {}), location };
|
|
110
|
+
}
|
|
111
|
+
|
|
62
112
|
const webhook = args.webhook || process.env.PINGROOM_WEBHOOK_URL;
|
|
63
113
|
const token = resolveToken(args);
|
|
64
114
|
const apiBase = resolveApiBase(args);
|
package/lib/constants.js
CHANGED
|
@@ -12,3 +12,8 @@ export const EXIT = { OK: 0, ERROR: 1, USAGE: 2, EXPIRED: 3, CANCELLED: 4 };
|
|
|
12
12
|
export const PING_TITLE_MAX_LENGTH = 40;
|
|
13
13
|
export const PRIVATE_PING_MESSAGE_MAX_LENGTH = 120;
|
|
14
14
|
export const PUBLIC_PING_MESSAGE_MAX_LENGTH = 160;
|
|
15
|
+
|
|
16
|
+
// Reserved data.location display metadata. Coordinate ranges are part of the
|
|
17
|
+
// numeric format itself; these caps mirror the API's Unicode-aware string rules.
|
|
18
|
+
export const LOCATION_LABEL_MAX_LENGTH = 100;
|
|
19
|
+
export const LOCATION_ADDRESS_MAX_LENGTH = 255;
|
package/lib/help.js
CHANGED
|
@@ -41,6 +41,9 @@ export const HELP_PING = `ping options:
|
|
|
41
41
|
-d, --data <json> Extra JSON data object, e.g. '{"commit":"abc123"}'
|
|
42
42
|
--url <https-url> Make the ping a tappable link (absolute http(s) URL)
|
|
43
43
|
--button-label <t> Link button text (<= 26 chars; requires --url)
|
|
44
|
+
--location <lat,lng> Attach a map location (latitude,longitude)
|
|
45
|
+
--location-label <t> Location label (<= 100 chars; requires --location)
|
|
46
|
+
--location-address <t> Address (<= 255 chars; requires --location)
|
|
44
47
|
--urgent Deliver time-sensitive so it breaks through Focus / Do Not
|
|
45
48
|
Disturb. Delivery only - asks nothing of the recipient
|
|
46
49
|
--require-ack Keep the ping open until an eligible recipient acknowledges it,
|
|
@@ -207,6 +210,10 @@ Examples:
|
|
|
207
210
|
pingroom ping -w "$PINGROOM_WEBHOOK_URL" -m "Build 512 ready" \\
|
|
208
211
|
--url https://ci.example.com/builds/512 --button-label "Open build"
|
|
209
212
|
|
|
213
|
+
# Location ping — recipients can share it or open it in a maps app:
|
|
214
|
+
pingroom ping --room ab12cd -m "Meet me here" \\
|
|
215
|
+
--location "25.2048,55.2708" --location-label "Dubai Mall"
|
|
216
|
+
|
|
210
217
|
# Gate a deploy on a human tap — the chosen value prints to stdout:
|
|
211
218
|
if [ "$(pingroom ask --token "$T" --room ab12cd --wait \\
|
|
212
219
|
-p 'Deploy 1.4.0 to production?')" = approve ]; then ./deploy.sh; fi
|
package/lib/parser.js
CHANGED
|
@@ -61,6 +61,9 @@ export const parseArgs = makeParser({
|
|
|
61
61
|
'-w': 'webhook', '--webhook': 'webhook',
|
|
62
62
|
'--url': 'url',
|
|
63
63
|
'--button-label': 'button_label',
|
|
64
|
+
'--location': 'location',
|
|
65
|
+
'--location-label': 'location_label',
|
|
66
|
+
'--location-address': 'location_address',
|
|
64
67
|
'--require-ack': 'require_ack',
|
|
65
68
|
'--urgent': 'urgent',
|
|
66
69
|
'--ack-timeout': 'ack_timeout',
|