@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.
- package/README.md +108 -118
- package/dist/cli.js +2 -172
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +13 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +39 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +206 -0
- package/dist/config.js.map +1 -0
- package/dist/doctor.d.ts +20 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +73 -112
- package/dist/doctor.js.map +1 -1
- package/dist/event-listen.d.ts +11 -0
- package/dist/event-listen.d.ts.map +1 -0
- package/dist/event-listen.js +37 -0
- package/dist/event-listen.js.map +1 -0
- package/dist/output.d.ts +4 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +26 -0
- package/dist/output.js.map +1 -0
- package/dist/program.d.ts +14 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +655 -0
- package/dist/program.js.map +1 -0
- package/package.json +31 -37
- package/claude-plugin/marketplace/.claude-plugin/marketplace.json +0 -14
- package/claude-plugin/marketplace/plugins/relay/.claude-plugin/plugin.json +0 -27
- package/claude-plugin/marketplace/plugins/relay/LICENSE +0 -21
- package/claude-plugin/marketplace/plugins/relay/README.md +0 -173
- package/claude-plugin/marketplace/plugins/relay/commands/configure.md +0 -71
- package/claude-plugin/marketplace/plugins/relay/runtime/server.mjs +0 -26197
- package/dist/api.d.ts +0 -114
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -162
- package/dist/api.js.map +0 -1
- package/dist/claude-settings.d.ts +0 -14
- package/dist/claude-settings.d.ts.map +0 -1
- package/dist/claude-settings.js +0 -47
- package/dist/claude-settings.js.map +0 -1
- package/dist/codex.d.ts +0 -25
- package/dist/codex.d.ts.map +0 -1
- package/dist/codex.js +0 -237
- package/dist/codex.js.map +0 -1
- package/dist/engine/acp.d.ts +0 -74
- package/dist/engine/acp.d.ts.map +0 -1
- package/dist/engine/acp.js +0 -413
- package/dist/engine/acp.js.map +0 -1
- package/dist/engine/catalog.d.ts +0 -26
- package/dist/engine/catalog.d.ts.map +0 -1
- package/dist/engine/catalog.js +0 -41
- package/dist/engine/catalog.js.map +0 -1
- package/dist/engine/process.d.ts +0 -12
- package/dist/engine/process.d.ts.map +0 -1
- package/dist/engine/process.js +0 -54
- package/dist/engine/process.js.map +0 -1
- package/dist/engine/types.d.ts +0 -57
- package/dist/engine/types.d.ts.map +0 -1
- package/dist/engine/types.js +0 -8
- package/dist/engine/types.js.map +0 -1
- package/dist/flags.d.ts +0 -9
- package/dist/flags.d.ts.map +0 -1
- package/dist/flags.js +0 -34
- package/dist/flags.js.map +0 -1
- package/dist/install.d.ts +0 -69
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js +0 -513
- package/dist/install.js.map +0 -1
- package/dist/mcp.d.ts +0 -16
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js +0 -127
- package/dist/mcp.js.map +0 -1
- package/dist/pair.d.ts +0 -20
- package/dist/pair.d.ts.map +0 -1
- package/dist/pair.js +0 -135
- package/dist/pair.js.map +0 -1
- package/dist/permissions.d.ts +0 -90
- package/dist/permissions.d.ts.map +0 -1
- package/dist/permissions.js +0 -361
- package/dist/permissions.js.map +0 -1
- package/dist/receive.d.ts +0 -113
- package/dist/receive.d.ts.map +0 -1
- package/dist/receive.js +0 -596
- package/dist/receive.js.map +0 -1
- package/dist/store.d.ts +0 -301
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -551
- package/dist/store.js.map +0 -1
- package/openclaw-plugin/relaymessenger-openclaw-plugin-0.3.3.tgz +0 -0
package/dist/program.js
ADDED
|
@@ -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
|