@pingroom/cli 0.8.1 → 0.9.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/README.md +5 -0
- package/lib/commands/manage.js +41 -6
- package/lib/help.js +9 -3
- package/lib/http.js +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -257,7 +257,12 @@ it **silently**, `end` closes it with one completion alert.
|
|
|
257
257
|
The management nouns cover the rest of the agent surface (agent token required;
|
|
258
258
|
`--room` where noted):
|
|
259
259
|
|
|
260
|
+
Room `--icon` takes a v3 catalog id (`bell`, `globe`, `terminal`, …), never an
|
|
261
|
+
emoji — the server rejects anything off-catalog. Browse ids with
|
|
262
|
+
`pingroom rooms icons`. Quick-action `--icon` is the opposite: it takes an emoji.
|
|
263
|
+
|
|
260
264
|
```bash
|
|
265
|
+
pingroom rooms icons # browse the v3 room-icon catalog
|
|
261
266
|
pingroom rooms list # rooms this account belongs to
|
|
262
267
|
pingroom rooms get GZNFB6BZGJIH
|
|
263
268
|
pingroom rooms create -n "Deploys" --icon bell --color "#e33122"
|
package/lib/commands/manage.js
CHANGED
|
@@ -38,9 +38,21 @@ function printJsonOr(args, text, lines) {
|
|
|
38
38
|
|
|
39
39
|
export async function rooms(args) {
|
|
40
40
|
if (args.help) { process.stdout.write(`${commandHelp('rooms')}\n`); return EXIT.OK; }
|
|
41
|
-
const action = sub(args, ['list', 'get', 'create', 'join'], 'rooms');
|
|
41
|
+
const action = sub(args, ['list', 'get', 'create', 'join', 'icons'], 'rooms');
|
|
42
42
|
const { token, apiBase } = agentContext(args);
|
|
43
43
|
|
|
44
|
+
if (action === 'icons') {
|
|
45
|
+
const { text, json } = await requireOk(
|
|
46
|
+
httpJson('GET', `${apiBase}/api/agent/room-icons`, { headers: auth(token) }),
|
|
47
|
+
'rooms icons',
|
|
48
|
+
);
|
|
49
|
+
if (args.json) { process.stdout.write(`${text}\n`); return EXIT.OK; }
|
|
50
|
+
const categories = Array.isArray(json.categories) ? json.categories : [];
|
|
51
|
+
const lines = categories.map((c) => `${c.label ?? c.id}: ${(c.icons ?? []).join(' ')}`);
|
|
52
|
+
process.stdout.write(`${lines.join('\n') || '(no icons)'}\n`);
|
|
53
|
+
return EXIT.OK;
|
|
54
|
+
}
|
|
55
|
+
|
|
44
56
|
if (action === 'list') {
|
|
45
57
|
const { text, json } = await requireOk(
|
|
46
58
|
httpJson('GET', `${apiBase}/api/agent/rooms`, { headers: auth(token) }),
|
|
@@ -69,17 +81,18 @@ export async function rooms(args) {
|
|
|
69
81
|
httpJson('POST', `${apiBase}/api/agent/rooms/join`, { headers: auth(token), body: { invite_code: code } }),
|
|
70
82
|
'rooms join',
|
|
71
83
|
);
|
|
72
|
-
|
|
84
|
+
// The server returns the room flat, so `json.room` is never set and the
|
|
85
|
+
// name always fell back to the invite code.
|
|
86
|
+
printJsonOr(args, text, `joined ${json.room?.name ?? json.name ?? code}`);
|
|
73
87
|
return EXIT.OK;
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
// create — private by default; --public requires --handle and uses the
|
|
77
91
|
// dedicated consent scope on the server.
|
|
78
92
|
if (!args.name) fail('rooms create needs --name', EXIT.USAGE);
|
|
79
|
-
if (!args.icon || !args.color) fail('rooms create needs --icon and --color
|
|
93
|
+
if (!args.icon || !args.color) fail('rooms create needs --icon and --color. --icon is a v3 catalog id, not an emoji — run "pingroom rooms icons" to browse, e.g. --icon bell --color "#e33122"', EXIT.USAGE);
|
|
80
94
|
|
|
81
95
|
const body = { name: args.name, icon: args.icon, color: args.color };
|
|
82
|
-
if (args.description) body.description = args.description;
|
|
83
96
|
|
|
84
97
|
let url = `${apiBase}/api/agent/rooms`;
|
|
85
98
|
if (args.public) {
|
|
@@ -88,6 +101,17 @@ export async function rooms(args) {
|
|
|
88
101
|
url = `${apiBase}/api/agent/rooms/public`;
|
|
89
102
|
}
|
|
90
103
|
|
|
104
|
+
// The ordinary rooms:write scope creates a MINIMAL private room: the server
|
|
105
|
+
// marks description (with is_public, handle, category, actions and friends)
|
|
106
|
+
// `prohibited` there and answers 422. Only the public create carries them.
|
|
107
|
+
// Say so here rather than posting a request that cannot succeed.
|
|
108
|
+
if (args.description !== undefined) {
|
|
109
|
+
if (!args.public) {
|
|
110
|
+
fail('--description is only accepted on a public room (add --public --handle <handle>). A private agent room is created minimal; set its description from the app.', EXIT.USAGE);
|
|
111
|
+
}
|
|
112
|
+
body.description = args.description;
|
|
113
|
+
}
|
|
114
|
+
|
|
91
115
|
const { text, json } = await requireOk(httpJson('POST', url, { headers: auth(token), body }), 'rooms create');
|
|
92
116
|
printJsonOr(args, text, `created ${json.room?.invite_code ?? json.invite_code ?? ''} ${args.name}`.trim());
|
|
93
117
|
return EXIT.OK;
|
|
@@ -128,7 +152,13 @@ export async function webhooks(args) {
|
|
|
128
152
|
if (!body.name) fail('webhooks create needs --name', EXIT.USAGE);
|
|
129
153
|
const { text, json } = await requireOk(httpJson('POST', base, { headers: auth(token), body }), 'webhooks create');
|
|
130
154
|
// The trigger URL carries the webhook secret — print it once, like the app.
|
|
131
|
-
|
|
155
|
+
// The server answers with the webhook FLAT plus `webhook_url`; the nested
|
|
156
|
+
// shapes are only kept as fallbacks. Reading `json.webhook.*` alone printed
|
|
157
|
+
// a bare "created" and swallowed the one credential this command exists to
|
|
158
|
+
// hand over.
|
|
159
|
+
const created = json.webhook ?? json;
|
|
160
|
+
const url = created.webhook_url ?? created.url ?? json.webhook_url ?? json.url ?? '';
|
|
161
|
+
printJsonOr(args, text, `created ${created.id ?? ''}\n${url}`.trim());
|
|
132
162
|
return EXIT.OK;
|
|
133
163
|
}
|
|
134
164
|
|
|
@@ -169,7 +199,12 @@ export async function actions(args) {
|
|
|
169
199
|
return EXIT.OK;
|
|
170
200
|
}
|
|
171
201
|
|
|
172
|
-
|
|
202
|
+
// A Ping's title is optional — its emoji can be the whole name — so
|
|
203
|
+
// `--label ""` is a deliberate value, not a missing flag. The emoji is the
|
|
204
|
+
// half that must be there.
|
|
205
|
+
if (args.label === undefined || !args.icon) {
|
|
206
|
+
fail('actions set needs --label (may be empty) and --icon', EXIT.USAGE);
|
|
207
|
+
}
|
|
173
208
|
const body = { label: args.label, icon: args.icon };
|
|
174
209
|
if (args.sound !== undefined) body.sound = args.sound;
|
|
175
210
|
if (args.require_ack) body.requires_ack = true;
|
package/lib/help.js
CHANGED
|
@@ -32,7 +32,8 @@ Commands:
|
|
|
32
32
|
Claude Desktop
|
|
33
33
|
skills List the PingRoom agent skills, or install them for Claude Code
|
|
34
34
|
activate Retry Agent Inbox activation with the saved QR-paired credential
|
|
35
|
-
rooms List, inspect, create, or join rooms
|
|
35
|
+
rooms List, inspect, create, or join rooms; browse the icon catalog
|
|
36
|
+
(rooms list|get|create|join|icons)
|
|
36
37
|
webhooks Manage a room's incoming webhooks (webhooks list|create|update|delete)
|
|
37
38
|
actions List, configure, or trigger a room's quick actions (actions list|set|trigger)
|
|
38
39
|
approval Send an approve/deny request; with --wait, block on the decision
|
|
@@ -313,10 +314,14 @@ export const COMMAND_HELP_SECTIONS = {
|
|
|
313
314
|
rooms: `rooms (agent token required):
|
|
314
315
|
pingroom rooms list List the rooms this account belongs to
|
|
315
316
|
pingroom rooms get <code> Show one room
|
|
317
|
+
pingroom rooms icons Browse the v3 room-icon catalog
|
|
316
318
|
pingroom rooms create -n <name> --icon <id> --color <hex>
|
|
317
|
-
Create a private room
|
|
319
|
+
Create a minimal private room.
|
|
320
|
+
--icon is a v3 catalog id from "rooms icons"
|
|
321
|
+
(e.g. bell), NOT an emoji;
|
|
318
322
|
--public --handle <handle> creates a public
|
|
319
|
-
room under its own consent scope
|
|
323
|
+
room under its own consent scope, and is
|
|
324
|
+
the only create that takes --description
|
|
320
325
|
pingroom rooms join <code> Join a room by invite code`,
|
|
321
326
|
webhooks: `webhooks (agent token + --room required):
|
|
322
327
|
pingroom webhooks list --room <code>
|
|
@@ -329,6 +334,7 @@ export const COMMAND_HELP_SECTIONS = {
|
|
|
329
334
|
actions: `actions (agent token + --room required):
|
|
330
335
|
pingroom actions list --room <code>
|
|
331
336
|
pingroom actions set <1-4> --room <code> --label <text> --icon <emoji>
|
|
337
|
+
(--label "" makes an emoji-only Ping)
|
|
332
338
|
Optional: --sound, --require-ack
|
|
333
339
|
pingroom actions trigger <1-4> --room <code>`,
|
|
334
340
|
approval: `approval (agent token + --room required):
|
package/lib/http.js
CHANGED
|
@@ -35,7 +35,13 @@ export function apiDetail(res, json) {
|
|
|
35
35
|
const base = stripControlChars(
|
|
36
36
|
(json && (json.message || json.error || json.code)) || `HTTP ${res ? res.status : 'error'}`,
|
|
37
37
|
);
|
|
38
|
-
|
|
38
|
+
let hint = json && typeof json.code === 'string' ? API_HINTS[json.code] : undefined;
|
|
39
|
+
// A 401 means the stored credential is dead (revoked in the app, or the
|
|
40
|
+
// pairing was cleaned up). Without this line the CLI prints a bare
|
|
41
|
+
// "Unauthenticated." and leaves the user to guess the remedy.
|
|
42
|
+
if (!hint && res && res.status === 401) {
|
|
43
|
+
hint = 'This credential is no longer valid — run "pingroom reconnect" to pair again.';
|
|
44
|
+
}
|
|
39
45
|
return hint ? `${base}\n ${hint}` : base;
|
|
40
46
|
}
|
|
41
47
|
|