@relaymessenger/cli 0.4.4 → 0.5.0-staging.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 (91) hide show
  1. package/README.md +108 -118
  2. package/dist/cli.js +2 -172
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client.d.ts +8 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +13 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/config.d.ts +39 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +206 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/doctor.d.ts +20 -1
  13. package/dist/doctor.d.ts.map +1 -1
  14. package/dist/doctor.js +73 -112
  15. package/dist/doctor.js.map +1 -1
  16. package/dist/event-listen.d.ts +11 -0
  17. package/dist/event-listen.d.ts.map +1 -0
  18. package/dist/event-listen.js +37 -0
  19. package/dist/event-listen.js.map +1 -0
  20. package/dist/output.d.ts +4 -0
  21. package/dist/output.d.ts.map +1 -0
  22. package/dist/output.js +26 -0
  23. package/dist/output.js.map +1 -0
  24. package/dist/program.d.ts +14 -0
  25. package/dist/program.d.ts.map +1 -0
  26. package/dist/program.js +655 -0
  27. package/dist/program.js.map +1 -0
  28. package/package.json +31 -37
  29. package/claude-plugin/marketplace/.claude-plugin/marketplace.json +0 -14
  30. package/claude-plugin/marketplace/plugins/relay/.claude-plugin/plugin.json +0 -27
  31. package/claude-plugin/marketplace/plugins/relay/LICENSE +0 -21
  32. package/claude-plugin/marketplace/plugins/relay/README.md +0 -173
  33. package/claude-plugin/marketplace/plugins/relay/commands/configure.md +0 -71
  34. package/claude-plugin/marketplace/plugins/relay/runtime/server.mjs +0 -26197
  35. package/dist/api.d.ts +0 -114
  36. package/dist/api.d.ts.map +0 -1
  37. package/dist/api.js +0 -162
  38. package/dist/api.js.map +0 -1
  39. package/dist/claude-settings.d.ts +0 -14
  40. package/dist/claude-settings.d.ts.map +0 -1
  41. package/dist/claude-settings.js +0 -47
  42. package/dist/claude-settings.js.map +0 -1
  43. package/dist/codex.d.ts +0 -25
  44. package/dist/codex.d.ts.map +0 -1
  45. package/dist/codex.js +0 -237
  46. package/dist/codex.js.map +0 -1
  47. package/dist/engine/acp.d.ts +0 -74
  48. package/dist/engine/acp.d.ts.map +0 -1
  49. package/dist/engine/acp.js +0 -413
  50. package/dist/engine/acp.js.map +0 -1
  51. package/dist/engine/catalog.d.ts +0 -26
  52. package/dist/engine/catalog.d.ts.map +0 -1
  53. package/dist/engine/catalog.js +0 -41
  54. package/dist/engine/catalog.js.map +0 -1
  55. package/dist/engine/process.d.ts +0 -12
  56. package/dist/engine/process.d.ts.map +0 -1
  57. package/dist/engine/process.js +0 -54
  58. package/dist/engine/process.js.map +0 -1
  59. package/dist/engine/types.d.ts +0 -57
  60. package/dist/engine/types.d.ts.map +0 -1
  61. package/dist/engine/types.js +0 -8
  62. package/dist/engine/types.js.map +0 -1
  63. package/dist/flags.d.ts +0 -9
  64. package/dist/flags.d.ts.map +0 -1
  65. package/dist/flags.js +0 -34
  66. package/dist/flags.js.map +0 -1
  67. package/dist/install.d.ts +0 -69
  68. package/dist/install.d.ts.map +0 -1
  69. package/dist/install.js +0 -513
  70. package/dist/install.js.map +0 -1
  71. package/dist/mcp.d.ts +0 -16
  72. package/dist/mcp.d.ts.map +0 -1
  73. package/dist/mcp.js +0 -127
  74. package/dist/mcp.js.map +0 -1
  75. package/dist/pair.d.ts +0 -20
  76. package/dist/pair.d.ts.map +0 -1
  77. package/dist/pair.js +0 -135
  78. package/dist/pair.js.map +0 -1
  79. package/dist/permissions.d.ts +0 -90
  80. package/dist/permissions.d.ts.map +0 -1
  81. package/dist/permissions.js +0 -361
  82. package/dist/permissions.js.map +0 -1
  83. package/dist/receive.d.ts +0 -113
  84. package/dist/receive.d.ts.map +0 -1
  85. package/dist/receive.js +0 -596
  86. package/dist/receive.js.map +0 -1
  87. package/dist/store.d.ts +0 -301
  88. package/dist/store.d.ts.map +0 -1
  89. package/dist/store.js +0 -551
  90. package/dist/store.js.map +0 -1
  91. package/openclaw-plugin/relaymessenger-openclaw-plugin-0.3.3.tgz +0 -0
@@ -0,0 +1,655 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import Relay, { RELAY_WEBHOOK_EVENT_TYPES, } from "@relaymessenger/sdk";
3
+ import { Command, CommanderError, InvalidArgumentError, } from "commander";
4
+ import { createClientContext } from "./client.js";
5
+ import { DEFAULT_API_URL, collectConfiguredTokens, configPath, readConfig, resolveAuth, validateApiURL, validateProfileName, validateToken, writeConfig, } from "./config.js";
6
+ import { runDoctor } from "./doctor.js";
7
+ import { errorText, jsonText } from "./output.js";
8
+ import { listenForAgentEvents } from "./event-listen.js";
9
+ const integer = (value) => {
10
+ const parsed = Number(value);
11
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
12
+ throw new InvalidArgumentError("Expected a non-negative integer.");
13
+ }
14
+ return parsed;
15
+ };
16
+ const positiveInteger = (value) => {
17
+ const parsed = integer(value);
18
+ if (parsed < 1)
19
+ throw new InvalidArgumentError("Expected a positive integer.");
20
+ return parsed;
21
+ };
22
+ const handle = (value) => {
23
+ const normalized = value.trim();
24
+ if (!normalized || normalized.startsWith("@") || /\s/.test(normalized)) {
25
+ throw new InvalidArgumentError("Handles must be non-empty, contain no spaces, and omit the leading @.");
26
+ }
27
+ return normalized;
28
+ };
29
+ const nonempty = (name, value) => {
30
+ const normalized = value.trim();
31
+ if (!normalized)
32
+ throw new Error(`${name} cannot be empty.`);
33
+ return normalized;
34
+ };
35
+ const events = (values) => {
36
+ if (values.length === 0)
37
+ throw new Error("At least one --event is required.");
38
+ const allowed = new Set(RELAY_WEBHOOK_EVENT_TYPES);
39
+ for (const value of values) {
40
+ if (!allowed.has(value))
41
+ throw new Error(`Unknown Relay event type: ${value}`);
42
+ }
43
+ return values;
44
+ };
45
+ const globals = (command) => command.optsWithGlobals();
46
+ const textContent = (text, idempotencyKey) => ({
47
+ parts: [{ type: "text", value: nonempty("Message text", text) }],
48
+ ...(idempotencyKey
49
+ ? { idempotency_key: nonempty("Idempotency key", idempotencyKey) }
50
+ : {}),
51
+ });
52
+ const voidResult = { ok: true };
53
+ export const createProgram = (dependencies = {}) => {
54
+ const stdout = dependencies.stdout ?? ((value) => process.stdout.write(value));
55
+ const stderr = dependencies.stderr ?? ((value) => process.stderr.write(value));
56
+ const configContext = dependencies.configContext ?? {};
57
+ const resolveClient = dependencies.resolveClient
58
+ ?? ((profile) => createClientContext(profile, configContext));
59
+ const output = (value) => stdout(jsonText(value));
60
+ const clientFor = async (command) => (await resolveClient(globals(command).profile)).client;
61
+ const program = new Command()
62
+ .name("relay")
63
+ .description("Official CLI for Relay v1 Agent resources.")
64
+ .version("0.5.0-staging.0")
65
+ .option("--profile <name>", "local Relay profile", process.env.RELAY_PROFILE);
66
+ program.exitOverride();
67
+ program.configureOutput({
68
+ writeOut: stdout,
69
+ writeErr: stderr,
70
+ });
71
+ const auth = program.command("auth").description("Manage local Agent Token authentication.");
72
+ auth
73
+ .command("login")
74
+ .description("Store an Agent Token from stdin or RELAY_AGENT_TOKEN.")
75
+ .option("--token-stdin", "read the token from stdin")
76
+ .option("--from-env", "read the token from RELAY_AGENT_TOKEN")
77
+ .option("--api-url <url>", "set the profile API origin")
78
+ .action(async (options, command) => {
79
+ if (options.tokenStdin === options.fromEnv) {
80
+ throw new Error("Choose exactly one of --token-stdin or --from-env.");
81
+ }
82
+ const env = configContext.env ?? process.env;
83
+ const raw = options.fromEnv
84
+ ? env.RELAY_AGENT_TOKEN
85
+ : await (dependencies.readStdin
86
+ ?? (async () => {
87
+ const chunks = [];
88
+ for await (const chunk of process.stdin)
89
+ chunks.push(Buffer.from(chunk));
90
+ return Buffer.concat(chunks).toString("utf8");
91
+ }))();
92
+ if (!raw)
93
+ throw new Error("RELAY_AGENT_TOKEN is not set.");
94
+ const token = validateToken(raw);
95
+ const config = await readConfig(configContext);
96
+ const profile = validateProfileName(globals(command).profile ?? config.current_profile);
97
+ const previous = config.profiles[profile] ?? {};
98
+ config.profiles[profile] = {
99
+ api_url: validateApiURL(options.apiUrl ?? previous.api_url ?? DEFAULT_API_URL),
100
+ agent_token: token,
101
+ };
102
+ await writeConfig(config, configContext);
103
+ output({
104
+ ok: true,
105
+ profile,
106
+ api_url: config.profiles[profile].api_url,
107
+ token: "stored",
108
+ });
109
+ });
110
+ auth
111
+ .command("status")
112
+ .description("Show token resolution without revealing the token.")
113
+ .action(async (_options, command) => {
114
+ const resolved = await resolveAuth(globals(command).profile, configContext);
115
+ output({
116
+ authenticated: true,
117
+ profile: resolved.profile,
118
+ api_url: resolved.apiURL,
119
+ token_source: resolved.tokenSource,
120
+ config_path: resolved.configPath,
121
+ });
122
+ });
123
+ auth
124
+ .command("logout")
125
+ .description("Remove the selected profile's stored token.")
126
+ .action(async (_options, command) => {
127
+ const config = await readConfig(configContext);
128
+ const profile = validateProfileName(globals(command).profile ?? config.current_profile);
129
+ const selected = config.profiles[profile];
130
+ if (!selected)
131
+ throw new Error(`Relay profile ${profile} does not exist.`);
132
+ const { agent_token: _removed, ...withoutToken } = selected;
133
+ config.profiles[profile] = withoutToken;
134
+ await writeConfig(config, configContext);
135
+ output({ ok: true, profile, token: "removed" });
136
+ });
137
+ const profiles = program.command("profiles").description("Manage local Relay profiles.");
138
+ profiles
139
+ .command("add")
140
+ .argument("<name>", "profile name", validateProfileName)
141
+ .requiredOption("--api-url <url>", "Relay API origin", validateApiURL)
142
+ .description("Add a profile without storing a token.")
143
+ .action(async (name, options) => {
144
+ const config = await readConfig(configContext);
145
+ if (config.profiles[name])
146
+ throw new Error(`Relay profile ${name} already exists.`);
147
+ config.profiles[name] = { api_url: options.apiUrl };
148
+ await writeConfig(config, configContext);
149
+ output({ ok: true, profile: name, api_url: options.apiUrl });
150
+ });
151
+ profiles
152
+ .command("use")
153
+ .argument("<name>", "profile name", validateProfileName)
154
+ .description("Select the default local profile.")
155
+ .action(async (name) => {
156
+ const config = await readConfig(configContext);
157
+ if (!config.profiles[name])
158
+ throw new Error(`Relay profile ${name} does not exist.`);
159
+ config.current_profile = name;
160
+ await writeConfig(config, configContext);
161
+ output({ ok: true, current_profile: name });
162
+ });
163
+ profiles
164
+ .command("remove")
165
+ .argument("<name>", "profile name", validateProfileName)
166
+ .description("Remove a non-current profile and its token.")
167
+ .action(async (name) => {
168
+ const config = await readConfig(configContext);
169
+ if (name === config.current_profile) {
170
+ throw new Error("Cannot remove the current profile; select another first.");
171
+ }
172
+ if (!config.profiles[name])
173
+ throw new Error(`Relay profile ${name} does not exist.`);
174
+ delete config.profiles[name];
175
+ await writeConfig(config, configContext);
176
+ output({ ok: true, removed: name });
177
+ });
178
+ profiles
179
+ .command("list")
180
+ .description("List profiles without revealing tokens.")
181
+ .action(async () => {
182
+ const config = await readConfig(configContext);
183
+ output({
184
+ current_profile: config.current_profile,
185
+ profiles: Object.entries(config.profiles).map(([name, profile]) => ({
186
+ name,
187
+ current: name === config.current_profile,
188
+ api_url: profile.api_url ?? DEFAULT_API_URL,
189
+ has_token: Boolean(profile.agent_token),
190
+ })),
191
+ });
192
+ });
193
+ program
194
+ .command("config-path")
195
+ .description("Print the local Relay config path.")
196
+ .action(() => output({ path: configPath(configContext) }));
197
+ program
198
+ .command("doctor")
199
+ .description("Check runtime, auth, config security, SDK contract, and API.")
200
+ .option("--offline", "skip the read-only API request")
201
+ .action(async (options, command) => {
202
+ const report = await runDoctor({
203
+ ...(globals(command).profile
204
+ ? { profile: globals(command).profile }
205
+ : {}),
206
+ offline: options.offline ?? false,
207
+ }, {
208
+ configContext,
209
+ createClient: (resolved) => new Relay({
210
+ apiKey: resolved.token,
211
+ baseURL: resolved.apiURL,
212
+ }),
213
+ });
214
+ output(report);
215
+ if (!report.ok)
216
+ throw new Error("Relay doctor found failing checks.");
217
+ });
218
+ const chats = program.command("chats").description("Read and update Chats.");
219
+ chats
220
+ .command("list")
221
+ .option("--cursor <cursor>")
222
+ .option("--limit <number>", "page size", positiveInteger)
223
+ .action(async (options, command) => {
224
+ const page = await (await clientFor(command)).chats.listChats(options);
225
+ output({ chats: page.chats, next_cursor: page.nextCursor });
226
+ });
227
+ chats
228
+ .command("get")
229
+ .argument("<chat-id>")
230
+ .action(async (chatID, _options, command) => output(await (await clientFor(command)).chats.retrieve(chatID)));
231
+ chats
232
+ .command("create")
233
+ .requiredOption("--from <handle>", "sender Handle", handle)
234
+ .requiredOption("--to <handles...>", "recipient Handles", (value) => handle(value))
235
+ .requiredOption("--text <text>")
236
+ .requiredOption("--idempotency-key <key>")
237
+ .action(async (options, command) => {
238
+ const body = {
239
+ from: options.from,
240
+ to: options.to,
241
+ message: textContent(options.text, options.idempotencyKey),
242
+ };
243
+ output(await (await clientFor(command)).chats.create(body));
244
+ });
245
+ chats
246
+ .command("update")
247
+ .argument("<chat-id>")
248
+ .option("--display-name <name>")
249
+ .option("--group-icon <attachment-id>")
250
+ .option("--clear-group-icon")
251
+ .action(async (chatID, options, command) => {
252
+ if (options.groupIcon && options.clearGroupIcon) {
253
+ throw new Error("Choose --group-icon or --clear-group-icon, not both.");
254
+ }
255
+ const body = {
256
+ ...(options.displayName === undefined
257
+ ? {}
258
+ : { display_name: nonempty("Display name", options.displayName) }),
259
+ ...(options.groupIcon
260
+ ? { group_chat_icon: options.groupIcon }
261
+ : options.clearGroupIcon
262
+ ? { group_chat_icon: null }
263
+ : {}),
264
+ };
265
+ if (Object.keys(body).length === 0)
266
+ throw new Error("No Chat update was provided.");
267
+ output(await (await clientFor(command)).chats.update(chatID, body));
268
+ });
269
+ chats
270
+ .command("leave")
271
+ .argument("<chat-id>")
272
+ .action(async (chatID, _options, command) => output(await (await clientFor(command)).chats.leaveChat(chatID)));
273
+ chats
274
+ .command("read")
275
+ .argument("<chat-id>")
276
+ .action(async (chatID, _options, command) => {
277
+ await (await clientFor(command)).chats.markAsRead(chatID);
278
+ output(voidResult);
279
+ });
280
+ const typing = chats.command("typing").description("Manage Chat typing state.");
281
+ typing
282
+ .command("start")
283
+ .argument("<chat-id>")
284
+ .action(async (chatID, _options, command) => {
285
+ await (await clientFor(command)).chats.startTyping(chatID);
286
+ output(voidResult);
287
+ });
288
+ typing
289
+ .command("stop")
290
+ .argument("<chat-id>")
291
+ .action(async (chatID, _options, command) => {
292
+ await (await clientFor(command)).chats.stopTyping(chatID);
293
+ output(voidResult);
294
+ });
295
+ const participants = chats.command("participants").description("Manage Chat participants.");
296
+ participants
297
+ .command("add")
298
+ .argument("<chat-id>")
299
+ .argument("<handle>", "participant Handle", handle)
300
+ .action(async (chatID, participantHandle, _options, command) => output(await (await clientFor(command)).chats.participants.add(chatID, { handle: participantHandle })));
301
+ participants
302
+ .command("remove")
303
+ .argument("<chat-id>")
304
+ .argument("<handle>", "participant Handle", handle)
305
+ .action(async (chatID, participantHandle, _options, command) => output(await (await clientFor(command)).chats.participants.remove(chatID, { handle: participantHandle })));
306
+ const chatMessages = chats.command("messages").description("Read and send Chat Messages.");
307
+ chatMessages
308
+ .command("list")
309
+ .argument("<chat-id>")
310
+ .option("--cursor <cursor>")
311
+ .option("--limit <number>", "page size", positiveInteger)
312
+ .action(async (chatID, options, command) => {
313
+ const page = await (await clientFor(command)).chats.messages.list(chatID, options);
314
+ output({ messages: page.messages, next_cursor: page.nextCursor });
315
+ });
316
+ chatMessages
317
+ .command("send")
318
+ .argument("<chat-id>")
319
+ .requiredOption("--text <text>")
320
+ .requiredOption("--idempotency-key <key>")
321
+ .action(async (chatID, options, command) => {
322
+ const body = {
323
+ message: textContent(options.text, options.idempotencyKey),
324
+ };
325
+ output(await (await clientFor(command)).chats.messages.send(chatID, body));
326
+ });
327
+ chats
328
+ .command("voice-memo")
329
+ .argument("<chat-id>")
330
+ .option("--attachment-id <id>")
331
+ .option("--url <url>")
332
+ .action(async (chatID, options, command) => {
333
+ if (Boolean(options.attachmentId) === Boolean(options.url)) {
334
+ throw new Error("Choose exactly one of --attachment-id or --url.");
335
+ }
336
+ const body = options.attachmentId
337
+ ? { attachment_id: options.attachmentId }
338
+ : { voice_memo_url: options.url };
339
+ output(await (await clientFor(command)).chats.sendVoicememo(chatID, body));
340
+ });
341
+ const messages = program.command("messages").description("Read, send, and react to Messages.");
342
+ messages
343
+ .command("send")
344
+ .requiredOption("--to <handles...>", "recipient Handles", (value) => handle(value))
345
+ .requiredOption("--text <text>")
346
+ .requiredOption("--idempotency-key <key>")
347
+ .action(async (options, command) => {
348
+ const body = {
349
+ to: options.to,
350
+ message: textContent(options.text, options.idempotencyKey),
351
+ };
352
+ output(await (await clientFor(command)).messages.create(body));
353
+ });
354
+ messages
355
+ .command("get")
356
+ .argument("<message-id>")
357
+ .action(async (messageID, _options, command) => output(await (await clientFor(command)).messages.retrieve(messageID)));
358
+ messages
359
+ .command("thread")
360
+ .argument("<message-id>")
361
+ .option("--cursor <cursor>")
362
+ .option("--limit <number>", "page size", positiveInteger)
363
+ .option("--order <order>", "asc or desc")
364
+ .action(async (messageID, options, command) => {
365
+ if (options.order && options.order !== "asc" && options.order !== "desc") {
366
+ throw new Error("--order must be asc or desc.");
367
+ }
368
+ const page = await (await clientFor(command)).messages.listMessagesThread(messageID, {
369
+ ...(options.cursor ? { cursor: options.cursor } : {}),
370
+ ...(options.limit ? { limit: options.limit } : {}),
371
+ ...(options.order
372
+ ? { order: options.order }
373
+ : {}),
374
+ });
375
+ output({ messages: page.messages, next_cursor: page.nextCursor });
376
+ });
377
+ messages
378
+ .command("react")
379
+ .argument("<message-id>")
380
+ .requiredOption("--operation <operation>", "add or remove")
381
+ .requiredOption("--type <type>")
382
+ .option("--custom-emoji <emoji>")
383
+ .option("--part-index <number>", "zero-based part index", integer)
384
+ .action(async (messageID, options, command) => {
385
+ const operations = new Set(["add", "remove"]);
386
+ const types = new Set([
387
+ "love",
388
+ "like",
389
+ "dislike",
390
+ "laugh",
391
+ "emphasize",
392
+ "question",
393
+ "custom",
394
+ ]);
395
+ if (!operations.has(options.operation))
396
+ throw new Error("Invalid reaction operation.");
397
+ if (!types.has(options.type))
398
+ throw new Error("Invalid reaction type.");
399
+ if (options.type === "custom" && !options.customEmoji) {
400
+ throw new Error("--custom-emoji is required for a custom reaction.");
401
+ }
402
+ if (options.type !== "custom" && options.customEmoji) {
403
+ throw new Error("--custom-emoji is valid only for a custom reaction.");
404
+ }
405
+ const body = {
406
+ operation: options.operation,
407
+ type: options.type,
408
+ ...(options.customEmoji ? { custom_emoji: options.customEmoji } : {}),
409
+ ...(options.partIndex === undefined ? {} : { part_index: options.partIndex }),
410
+ };
411
+ output(await (await clientFor(command)).messages.addReaction(messageID, body));
412
+ });
413
+ const attachments = program.command("attachments").description("Allocate, upload, and manage Attachments.");
414
+ attachments
415
+ .command("allocate")
416
+ .requiredOption("--filename <name>")
417
+ .requiredOption("--content-type <type>")
418
+ .requiredOption("--size <bytes>", "file size", positiveInteger)
419
+ .action(async (options, command) => output(await (await clientFor(command)).attachments.create({
420
+ filename: nonempty("Filename", options.filename),
421
+ content_type: nonempty("Content type", options.contentType),
422
+ size_bytes: options.size,
423
+ })));
424
+ attachments
425
+ .command("upload")
426
+ .argument("<file>")
427
+ .requiredOption("--content-type <type>")
428
+ .action(async (file, options, command) => {
429
+ const client = await clientFor(command);
430
+ const metadata = await stat(file);
431
+ if (!metadata.isFile())
432
+ throw new Error("Attachment path is not a file.");
433
+ const allocation = await client.attachments.create({
434
+ filename: file.split(/[\\/]/).pop() ?? "attachment",
435
+ content_type: nonempty("Content type", options.contentType),
436
+ size_bytes: metadata.size,
437
+ });
438
+ await client.attachments.upload(allocation, await readFile(file));
439
+ output(allocation);
440
+ });
441
+ attachments
442
+ .command("get")
443
+ .argument("<attachment-id>")
444
+ .action(async (attachmentID, _options, command) => output(await (await clientFor(command)).attachments.retrieve(attachmentID)));
445
+ attachments
446
+ .command("delete")
447
+ .argument("<attachment-id>")
448
+ .action(async (attachmentID, _options, command) => {
449
+ await (await clientFor(command)).attachments.delete(attachmentID);
450
+ output(voidResult);
451
+ });
452
+ const blocked = program.command("blocked-handles").description("Manage blocked Handles.");
453
+ blocked
454
+ .command("list")
455
+ .action(async (_options, command) => output(await (await clientFor(command)).blockedHandles.list()));
456
+ blocked
457
+ .command("add")
458
+ .argument("<handle>", "Handle", handle)
459
+ .option("--reason <reason>")
460
+ .action(async (blockedHandle, options, command) => output(await (await clientFor(command)).blockedHandles.block({
461
+ handle: blockedHandle,
462
+ ...(options.reason ? { reason: options.reason } : {}),
463
+ })));
464
+ blocked
465
+ .command("remove")
466
+ .argument("<handle>", "Handle", handle)
467
+ .action(async (blockedHandle, _options, command) => {
468
+ await (await clientFor(command)).blockedHandles.unblock({
469
+ handle: blockedHandle,
470
+ });
471
+ output(voidResult);
472
+ });
473
+ const webhooks = program.command("webhooks").description("Read and manage Webhook metadata.");
474
+ webhooks
475
+ .command("events")
476
+ .action(async (_options, command) => output(await (await clientFor(command)).webhookEvents.list()));
477
+ program
478
+ .command("events")
479
+ .description("Receive acknowledged Agent events over WebSocket.")
480
+ .command("listen")
481
+ .option("--forward-to <loopback-url>")
482
+ .requiredOption("--acknowledge-events", "confirm that this dedicated non-production Agent may advance its checkpoint")
483
+ .description("Print Agent events or forward them unsigned to loopback.")
484
+ .action(async (options, command) => {
485
+ const requestedProfile = globals(command).profile;
486
+ if (!requestedProfile) {
487
+ throw new Error("Agent event listening requires an explicit --profile for a dedicated non-production Agent.");
488
+ }
489
+ const context = await resolveClient(requestedProfile);
490
+ if (context.auth.apiURL === DEFAULT_API_URL) {
491
+ throw new Error("Agent event listening refuses the production Relay API origin.");
492
+ }
493
+ const controller = new AbortController();
494
+ const abort = () => controller.abort();
495
+ process.once("SIGINT", abort);
496
+ process.once("SIGTERM", abort);
497
+ try {
498
+ await listenForAgentEvents(context.client, {
499
+ ...(options.forwardTo ? { forwardTo: options.forwardTo } : {}),
500
+ signal: controller.signal,
501
+ ...(dependencies.fetch ? { fetch: dependencies.fetch } : {}),
502
+ }, { stdout, stderr });
503
+ }
504
+ finally {
505
+ process.off("SIGINT", abort);
506
+ process.off("SIGTERM", abort);
507
+ }
508
+ });
509
+ const subscriptions = webhooks.command("subscriptions").description("Manage webhook subscriptions.");
510
+ subscriptions
511
+ .command("list")
512
+ .action(async (_options, command) => output(await (await clientFor(command)).webhookSubscriptions.list()));
513
+ subscriptions
514
+ .command("get")
515
+ .argument("<subscription-id>")
516
+ .action(async (subscriptionID, _options, command) => output(await (await clientFor(command)).webhookSubscriptions.retrieve(subscriptionID)));
517
+ subscriptions
518
+ .command("create")
519
+ .requiredOption("--target-url <url>")
520
+ .requiredOption("--event <events...>")
521
+ .action(async (options, command) => output(await (await clientFor(command)).webhookSubscriptions.create({
522
+ target_url: options.targetUrl,
523
+ subscribed_events: events(options.event),
524
+ })));
525
+ subscriptions
526
+ .command("update")
527
+ .argument("<subscription-id>")
528
+ .option("--target-url <url>")
529
+ .option("--event <events...>")
530
+ .option("--active")
531
+ .option("--inactive")
532
+ .action(async (subscriptionID, options, command) => {
533
+ if (options.active && options.inactive) {
534
+ throw new Error("Choose --active or --inactive, not both.");
535
+ }
536
+ const body = {
537
+ ...(options.targetUrl ? { target_url: options.targetUrl } : {}),
538
+ ...(options.event ? { subscribed_events: events(options.event) } : {}),
539
+ ...(options.active
540
+ ? { is_active: true }
541
+ : options.inactive
542
+ ? { is_active: false }
543
+ : {}),
544
+ };
545
+ if (Object.keys(body).length === 0) {
546
+ throw new Error("No webhook subscription update was provided.");
547
+ }
548
+ output(await (await clientFor(command)).webhookSubscriptions.update(subscriptionID, body));
549
+ });
550
+ subscriptions
551
+ .command("delete")
552
+ .argument("<subscription-id>")
553
+ .action(async (subscriptionID, _options, command) => {
554
+ await (await clientFor(command)).webhookSubscriptions.delete(subscriptionID);
555
+ output(voidResult);
556
+ });
557
+ const contactCard = program.command("contact-card").description("Configure and share the Agent Contact Card.");
558
+ contactCard
559
+ .command("get")
560
+ .option("--handle <handle>", "Agent Handle", handle)
561
+ .action(async (options, command) => output(await (await clientFor(command)).contactCard.retrieve(options)));
562
+ contactCard
563
+ .command("setup")
564
+ .requiredOption("--handle <handle>", "Agent Handle", handle)
565
+ .requiredOption("--first-name <name>")
566
+ .option("--last-name <name>")
567
+ .option("--image-url <url>")
568
+ .action(async (options, command) => {
569
+ const body = {
570
+ handle: options.handle,
571
+ first_name: nonempty("First name", options.firstName),
572
+ ...(options.lastName ? { last_name: options.lastName } : {}),
573
+ ...(options.imageUrl ? { image_url: options.imageUrl } : {}),
574
+ };
575
+ output(await (await clientFor(command)).contactCard.create(body));
576
+ });
577
+ contactCard
578
+ .command("update")
579
+ .requiredOption("--handle <handle>", "Agent Handle", handle)
580
+ .option("--first-name <name>")
581
+ .option("--last-name <name>")
582
+ .option("--clear-last-name")
583
+ .option("--image-url <url>")
584
+ .option("--clear-image-url")
585
+ .action(async (options, command) => {
586
+ if (options.lastName && options.clearLastName) {
587
+ throw new Error("Choose --last-name or --clear-last-name, not both.");
588
+ }
589
+ if (options.imageUrl && options.clearImageUrl) {
590
+ throw new Error("Choose --image-url or --clear-image-url, not both.");
591
+ }
592
+ const body = {
593
+ handle: options.handle,
594
+ ...(options.firstName ? { first_name: options.firstName } : {}),
595
+ ...(options.lastName
596
+ ? { last_name: options.lastName }
597
+ : options.clearLastName
598
+ ? { last_name: null }
599
+ : {}),
600
+ ...(options.imageUrl
601
+ ? { image_url: options.imageUrl }
602
+ : options.clearImageUrl
603
+ ? { image_url: null }
604
+ : {}),
605
+ };
606
+ if (Object.keys(body).length === 1) {
607
+ throw new Error("No Contact Card update was provided.");
608
+ }
609
+ output(await (await clientFor(command)).contactCard.update(body));
610
+ });
611
+ contactCard
612
+ .command("share")
613
+ .argument("<chat-id>")
614
+ .action(async (chatID, _options, command) => {
615
+ await (await clientFor(command)).chats.shareContactCard(chatID);
616
+ output(voidResult);
617
+ });
618
+ program
619
+ .command("contact-requests")
620
+ .description("Create a Contact request.")
621
+ .command("create")
622
+ .argument("<handle>", "user Handle", handle)
623
+ .action(async (contactHandle, _options, command) => output(await (await clientFor(command)).contactRequests.create({
624
+ handle: contactHandle,
625
+ })));
626
+ return program;
627
+ };
628
+ export const runCLI = async (argv, dependencies = {}) => {
629
+ const stderr = dependencies.stderr ?? ((value) => process.stderr.write(value));
630
+ try {
631
+ await createProgram(dependencies).parseAsync(argv, { from: "user" });
632
+ return 0;
633
+ }
634
+ catch (error) {
635
+ if (error instanceof CommanderError) {
636
+ if (error.code === "commander.helpDisplayed"
637
+ || error.code === "commander.version") {
638
+ return 0;
639
+ }
640
+ return error.exitCode;
641
+ }
642
+ let secrets = [];
643
+ try {
644
+ secrets = await collectConfiguredTokens(dependencies.configContext);
645
+ }
646
+ catch {
647
+ const env = dependencies.configContext?.env ?? process.env;
648
+ if (env.RELAY_AGENT_TOKEN)
649
+ secrets = [env.RELAY_AGENT_TOKEN];
650
+ }
651
+ stderr(`Error: ${errorText(error, secrets)}\n`);
652
+ return 1;
653
+ }
654
+ };
655
+ //# sourceMappingURL=program.js.map