@andocorp/cli 0.1.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 +136 -0
- package/package.json +42 -0
- package/src/adapters.test.ts +50 -0
- package/src/adapters.ts +215 -0
- package/src/args.test.ts +28 -0
- package/src/args.ts +98 -0
- package/src/cli-helpers.test.ts +82 -0
- package/src/cli-helpers.ts +149 -0
- package/src/client.test.ts +235 -0
- package/src/client.ts +378 -0
- package/src/components/prompt-line.ts +179 -0
- package/src/components/transcript-pane.test.ts +26 -0
- package/src/components/transcript-pane.ts +457 -0
- package/src/config.ts +53 -0
- package/src/emoji-suggestions.ts +152 -0
- package/src/format.test.ts +54 -0
- package/src/format.ts +611 -0
- package/src/help.test.ts +13 -0
- package/src/help.ts +48 -0
- package/src/index.ts +466 -0
- package/src/interactive.ts +832 -0
- package/src/test-helpers.ts +207 -0
- package/src/types.ts +24 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { getStringFlag, hasFlag, parseArgs } from "./args.js";
|
|
3
|
+
import {
|
|
4
|
+
getConversationLabel,
|
|
5
|
+
getWorkspaceLabel,
|
|
6
|
+
isChannel,
|
|
7
|
+
resolveConversation,
|
|
8
|
+
resolveWorkspace,
|
|
9
|
+
} from "./cli-helpers.js";
|
|
10
|
+
import { createAndoCliClient } from "./client.js";
|
|
11
|
+
import { getConfigPath, readSavedConfig, saveConfig } from "./config.js";
|
|
12
|
+
import { promptLine } from "./components/prompt-line.js";
|
|
13
|
+
import { formatDateTime, formatMessageLine } from "./format.js";
|
|
14
|
+
import { buildHelpText } from "./help.js";
|
|
15
|
+
import { createInteractiveApp } from "./interactive.js";
|
|
16
|
+
import { Membership, Message, ParsedArgs } from "./types.js";
|
|
17
|
+
|
|
18
|
+
const DEFAULT_CONVEX_URL = "https://quick-caiman-763.convex.cloud";
|
|
19
|
+
const LOGIN_REQUIRED_ERROR = `OAuth session is required. Run "ando login" first.`;
|
|
20
|
+
|
|
21
|
+
function getConfiguredConvexUrl(parsedArgs: ParsedArgs, savedConvexUrl?: string | null) {
|
|
22
|
+
return (
|
|
23
|
+
getStringFlag(parsedArgs, "convex-url") ??
|
|
24
|
+
process.env.ANDO_CONVEX_URL ??
|
|
25
|
+
process.env.VITE_CONVEX_URL ??
|
|
26
|
+
process.env.NEXT_PUBLIC_CONVEX_URL ??
|
|
27
|
+
savedConvexUrl ??
|
|
28
|
+
DEFAULT_CONVEX_URL
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function loadConfig(parsedArgs: ParsedArgs) {
|
|
33
|
+
const savedConfig = await readSavedConfig();
|
|
34
|
+
const convexUrl = getConfiguredConvexUrl(parsedArgs, savedConfig?.convexUrl ?? null);
|
|
35
|
+
const sessionToken =
|
|
36
|
+
process.env.ANDO_SESSION_JWT ??
|
|
37
|
+
savedConfig?.sessionToken ??
|
|
38
|
+
null;
|
|
39
|
+
|
|
40
|
+
if (sessionToken == null || sessionToken.trim() === "") {
|
|
41
|
+
throw new Error(LOGIN_REQUIRED_ERROR);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const anonymousClient = createAndoCliClient({ convexUrl });
|
|
45
|
+
const refreshedSessionToken = await anonymousClient.refreshSessionJwt(sessionToken);
|
|
46
|
+
|
|
47
|
+
if (refreshedSessionToken == null || refreshedSessionToken.trim() === "") {
|
|
48
|
+
throw new Error(LOGIN_REQUIRED_ERROR);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
savedConfig?.sessionToken !== refreshedSessionToken ||
|
|
53
|
+
savedConfig.convexUrl !== convexUrl
|
|
54
|
+
) {
|
|
55
|
+
await saveConfig({
|
|
56
|
+
convexUrl,
|
|
57
|
+
sessionToken: refreshedSessionToken,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
convexUrl,
|
|
63
|
+
sessionToken: refreshedSessionToken,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function ensureConfig(parsedArgs: ParsedArgs, command: string | undefined) {
|
|
68
|
+
try {
|
|
69
|
+
return await loadConfig(parsedArgs);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (
|
|
72
|
+
error instanceof Error &&
|
|
73
|
+
error.message === LOGIN_REQUIRED_ERROR &&
|
|
74
|
+
command !== "login"
|
|
75
|
+
) {
|
|
76
|
+
await runLogin(parsedArgs);
|
|
77
|
+
return await loadConfig(parsedArgs);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printJson(value: unknown) {
|
|
85
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function printHelp() {
|
|
89
|
+
process.stdout.write(`${buildHelpText()}\n`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function runLogin(parsedArgs: ParsedArgs) {
|
|
93
|
+
const savedConfig = await readSavedConfig();
|
|
94
|
+
const convexUrl = getConfiguredConvexUrl(parsedArgs, savedConfig?.convexUrl ?? null);
|
|
95
|
+
const email = getStringFlag(parsedArgs, "email") ?? (await promptLine("Email: "));
|
|
96
|
+
|
|
97
|
+
if (email.trim() === "") {
|
|
98
|
+
throw new Error("login requires an email address.");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const client = createAndoCliClient({ convexUrl });
|
|
102
|
+
await client.sendEmailOtp({ email });
|
|
103
|
+
process.stdout.write(`Sent OTP to ${email}\n`);
|
|
104
|
+
|
|
105
|
+
const code =
|
|
106
|
+
getStringFlag(parsedArgs, "code") ?? (await promptLine("Verification code: "));
|
|
107
|
+
if (code.trim() === "") {
|
|
108
|
+
throw new Error("login requires a verification code.");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const discovery = await client.discoverWorkspaces({
|
|
112
|
+
email,
|
|
113
|
+
code,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (discovery.requires_workspace_creation || discovery.workspaces.length === 0) {
|
|
117
|
+
throw new Error("No existing workspaces matched this login. Create one in the web app first.");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let workspaceQuery = getStringFlag(parsedArgs, "workspace", "w") ?? null;
|
|
121
|
+
if (workspaceQuery == null && discovery.workspaces.length > 1) {
|
|
122
|
+
process.stdout.write("Workspaces:\n");
|
|
123
|
+
discovery.workspaces.forEach((workspace, index) => {
|
|
124
|
+
process.stdout.write(` [${index + 1}] ${getWorkspaceLabel(workspace)}\n`);
|
|
125
|
+
});
|
|
126
|
+
const answer = await promptLine("Select workspace: ");
|
|
127
|
+
const asIndex = Number.parseInt(answer, 10);
|
|
128
|
+
if (Number.isFinite(asIndex) && asIndex >= 1 && asIndex <= discovery.workspaces.length) {
|
|
129
|
+
workspaceQuery = discovery.workspaces[asIndex - 1]?.id ?? null;
|
|
130
|
+
} else {
|
|
131
|
+
workspaceQuery = answer;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const workspace = resolveWorkspace(discovery.workspaces, workspaceQuery);
|
|
136
|
+
const response = await client.selectWorkspace({
|
|
137
|
+
workspace_id: workspace.id,
|
|
138
|
+
intermediate_session_token: discovery.intermediate_session_token,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await saveConfig({
|
|
142
|
+
convexUrl,
|
|
143
|
+
sessionToken: response.session_jwt,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
process.stdout.write(`Saved CLI session to ${getConfigPath()}\n`);
|
|
147
|
+
process.stdout.write(`Selected workspace: ${workspace.name}\n`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function printMemberships(memberships: Membership[]) {
|
|
151
|
+
const sortedMemberships = [...memberships].sort((left, right) => {
|
|
152
|
+
const leftDate = new Date(
|
|
153
|
+
left.conversation.last_message_at ?? left.conversation.updated_at
|
|
154
|
+
).getTime();
|
|
155
|
+
const rightDate = new Date(
|
|
156
|
+
right.conversation.last_message_at ?? right.conversation.updated_at
|
|
157
|
+
).getTime();
|
|
158
|
+
|
|
159
|
+
return rightDate - leftDate;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
for (const membership of sortedMemberships) {
|
|
163
|
+
const lastActivity = formatDateTime(
|
|
164
|
+
membership.conversation.last_message_at ?? membership.conversation.updated_at
|
|
165
|
+
);
|
|
166
|
+
process.stdout.write(
|
|
167
|
+
`${getConversationLabel(membership)}\t${membership.conversation.id}\t${lastActivity}\n`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function printMessages(
|
|
173
|
+
messages: Message[],
|
|
174
|
+
currentMemberId?: string | null
|
|
175
|
+
) {
|
|
176
|
+
for (const message of messages) {
|
|
177
|
+
process.stdout.write(
|
|
178
|
+
`${message.id}\t${formatMessageLine(message, { currentMemberId })}\n`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function runInteractiveCommand(client: ReturnType<typeof createAndoCliClient>) {
|
|
184
|
+
const me = await client.getMe();
|
|
185
|
+
if (me == null) {
|
|
186
|
+
throw new Error("Failed to fetch user details. Please sign in again with \"ando login\".");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
process.stdout.write(
|
|
190
|
+
`Signed in as ${me.display_name ?? me.email ?? me.id} (${me.workspace.name})\n`
|
|
191
|
+
);
|
|
192
|
+
const app = createInteractiveApp({ client, currentMemberId: me.id });
|
|
193
|
+
await app.run();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function runListChannelsCommand(
|
|
197
|
+
client: ReturnType<typeof createAndoCliClient>,
|
|
198
|
+
jsonFlag: boolean
|
|
199
|
+
) {
|
|
200
|
+
const memberships = (await client.getAllMemberships()).filter(isChannel);
|
|
201
|
+
if (jsonFlag) {
|
|
202
|
+
printJson(memberships);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
printMemberships(memberships);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function runListDmsCommand(
|
|
210
|
+
client: ReturnType<typeof createAndoCliClient>,
|
|
211
|
+
jsonFlag: boolean
|
|
212
|
+
) {
|
|
213
|
+
const memberships = (await client.getAllMemberships()).filter(
|
|
214
|
+
(membership) => !isChannel(membership)
|
|
215
|
+
);
|
|
216
|
+
if (jsonFlag) {
|
|
217
|
+
printJson(memberships);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
printMemberships(memberships);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function runListMessagesCommand(
|
|
225
|
+
client: ReturnType<typeof createAndoCliClient>,
|
|
226
|
+
parsedArgs: ParsedArgs,
|
|
227
|
+
jsonFlag: boolean
|
|
228
|
+
) {
|
|
229
|
+
const channelQuery = getStringFlag(parsedArgs, "channel", "c");
|
|
230
|
+
const dmQuery = getStringFlag(parsedArgs, "dm", "d");
|
|
231
|
+
const conversationQuery = getStringFlag(parsedArgs, "conversation");
|
|
232
|
+
const limitValue = getStringFlag(parsedArgs, "n", "n") ?? getStringFlag(parsedArgs, "limit");
|
|
233
|
+
const limit = limitValue == null ? 10 : Number.parseInt(limitValue, 10);
|
|
234
|
+
|
|
235
|
+
const query = channelQuery ?? dmQuery ?? conversationQuery;
|
|
236
|
+
if (query == null) {
|
|
237
|
+
throw new Error("list-messages requires --channel, --dm, or --conversation.");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const memberships = await client.getAllMemberships();
|
|
241
|
+
const membership = resolveConversation(memberships, {
|
|
242
|
+
allowChannels: dmQuery == null,
|
|
243
|
+
allowDms: channelQuery == null,
|
|
244
|
+
query,
|
|
245
|
+
});
|
|
246
|
+
const response = await client.getConversationMessages(membership.conversation.id, limit);
|
|
247
|
+
|
|
248
|
+
if (jsonFlag) {
|
|
249
|
+
printJson(response.items);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const me = await client.getMe();
|
|
254
|
+
if (me == null) {
|
|
255
|
+
throw new Error("Failed to fetch user details. Please sign in again with \"ando login\".");
|
|
256
|
+
}
|
|
257
|
+
printMessages(response.items, me.id);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function runThreadCommand(
|
|
261
|
+
client: ReturnType<typeof createAndoCliClient>,
|
|
262
|
+
parsedArgs: ParsedArgs,
|
|
263
|
+
jsonFlag: boolean
|
|
264
|
+
) {
|
|
265
|
+
const messageId = getStringFlag(parsedArgs, "message-id", "m");
|
|
266
|
+
if (messageId == null) {
|
|
267
|
+
throw new Error("thread requires --message-id.");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const root = await client.getMessage(messageId);
|
|
271
|
+
if (root == null) {
|
|
272
|
+
throw new Error(`Message ${messageId} was not found.`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const threadRootId = root.thread_root_id ?? root.id;
|
|
276
|
+
const threadRoot = root.thread_root_id == null ? root : await client.getMessage(threadRootId);
|
|
277
|
+
const replies = await client.getThreadReplies(threadRootId);
|
|
278
|
+
|
|
279
|
+
if (jsonFlag) {
|
|
280
|
+
printJson({
|
|
281
|
+
root: threadRoot,
|
|
282
|
+
replies,
|
|
283
|
+
});
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const me = await client.getMe();
|
|
288
|
+
if (me == null) {
|
|
289
|
+
throw new Error("Failed to fetch user details. Please sign in again with \"ando login\".");
|
|
290
|
+
}
|
|
291
|
+
if (threadRoot != null) {
|
|
292
|
+
process.stdout.write(
|
|
293
|
+
`ROOT\t${threadRoot.id}\t${formatMessageLine(threadRoot, {
|
|
294
|
+
currentMemberId: me.id,
|
|
295
|
+
})}\n`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (const reply of replies) {
|
|
300
|
+
process.stdout.write(
|
|
301
|
+
`REPLY\t${reply.id}\t${formatMessageLine(reply, {
|
|
302
|
+
currentMemberId: me.id,
|
|
303
|
+
})}\n`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function runPostMessageCommand(
|
|
309
|
+
client: ReturnType<typeof createAndoCliClient>,
|
|
310
|
+
parsedArgs: ParsedArgs,
|
|
311
|
+
jsonFlag: boolean
|
|
312
|
+
) {
|
|
313
|
+
const channelQuery = getStringFlag(parsedArgs, "channel", "c");
|
|
314
|
+
const dmQuery = getStringFlag(parsedArgs, "dm", "d");
|
|
315
|
+
const conversationQuery = getStringFlag(parsedArgs, "conversation");
|
|
316
|
+
const text = getStringFlag(parsedArgs, "text", "t");
|
|
317
|
+
|
|
318
|
+
if (text == null || text.trim() === "") {
|
|
319
|
+
throw new Error("post-message requires --text.");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const query = channelQuery ?? dmQuery ?? conversationQuery;
|
|
323
|
+
if (query == null) {
|
|
324
|
+
throw new Error("post-message requires --channel, --dm, or --conversation.");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const memberships = await client.getAllMemberships();
|
|
328
|
+
const membership = resolveConversation(memberships, {
|
|
329
|
+
allowChannels: dmQuery == null,
|
|
330
|
+
allowDms: channelQuery == null,
|
|
331
|
+
query,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const message = await client.postMessage({
|
|
335
|
+
conversationId: membership.conversation.id,
|
|
336
|
+
markdownContent: text,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
if (jsonFlag) {
|
|
340
|
+
printJson(message);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
process.stdout.write(`Posted ${message.id} to ${getConversationLabel(membership)}\n`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function runReplyCommand(
|
|
348
|
+
client: ReturnType<typeof createAndoCliClient>,
|
|
349
|
+
parsedArgs: ParsedArgs,
|
|
350
|
+
jsonFlag: boolean
|
|
351
|
+
) {
|
|
352
|
+
const messageId = getStringFlag(parsedArgs, "message-id", "m");
|
|
353
|
+
const text = getStringFlag(parsedArgs, "text", "t");
|
|
354
|
+
if (messageId == null) {
|
|
355
|
+
throw new Error("reply requires --message-id.");
|
|
356
|
+
}
|
|
357
|
+
if (text == null || text.trim() === "") {
|
|
358
|
+
throw new Error("reply requires --text.");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const message = await client.getMessage(messageId);
|
|
362
|
+
if (message == null) {
|
|
363
|
+
throw new Error(`Message ${messageId} was not found.`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const reply = await client.postMessage({
|
|
367
|
+
conversationId: message.conversation_id,
|
|
368
|
+
markdownContent: text,
|
|
369
|
+
threadRootId: message.thread_root_id ?? message.id,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (jsonFlag) {
|
|
373
|
+
printJson(reply);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
process.stdout.write(`Posted reply ${reply.id}\n`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function runReactCommand(
|
|
381
|
+
client: ReturnType<typeof createAndoCliClient>,
|
|
382
|
+
parsedArgs: ParsedArgs,
|
|
383
|
+
jsonFlag: boolean
|
|
384
|
+
) {
|
|
385
|
+
const messageId = getStringFlag(parsedArgs, "message-id", "m");
|
|
386
|
+
const emoji = getStringFlag(parsedArgs, "emoji", "e");
|
|
387
|
+
if (messageId == null) {
|
|
388
|
+
throw new Error("react requires --message-id.");
|
|
389
|
+
}
|
|
390
|
+
if (emoji == null || emoji.trim() === "") {
|
|
391
|
+
throw new Error("react requires --emoji.");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const result = await client.addReaction(messageId, emoji);
|
|
395
|
+
if (jsonFlag) {
|
|
396
|
+
printJson(result.data);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
process.stdout.write(`Added reaction ${emoji} to ${messageId}\n`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function runCommand() {
|
|
404
|
+
const parsedArgs = parseArgs(process.argv.slice(2));
|
|
405
|
+
const command = parsedArgs.positionals[0];
|
|
406
|
+
|
|
407
|
+
if (command === "help" || hasFlag(parsedArgs, "help", "h")) {
|
|
408
|
+
printHelp();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (command === "login") {
|
|
413
|
+
await runLogin(parsedArgs);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const client = createAndoCliClient(await ensureConfig(parsedArgs, command));
|
|
418
|
+
const jsonFlag = hasFlag(parsedArgs, "json");
|
|
419
|
+
|
|
420
|
+
if (command == null) {
|
|
421
|
+
await runInteractiveCommand(client);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (command === "list-channels") {
|
|
426
|
+
await runListChannelsCommand(client, jsonFlag);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (command === "list-dms") {
|
|
431
|
+
await runListDmsCommand(client, jsonFlag);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (command === "list-messages") {
|
|
436
|
+
await runListMessagesCommand(client, parsedArgs, jsonFlag);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (command === "thread") {
|
|
441
|
+
await runThreadCommand(client, parsedArgs, jsonFlag);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (command === "post-message") {
|
|
446
|
+
await runPostMessageCommand(client, parsedArgs, jsonFlag);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (command === "reply") {
|
|
451
|
+
await runReplyCommand(client, parsedArgs, jsonFlag);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (command === "react") {
|
|
456
|
+
await runReactCommand(client, parsedArgs, jsonFlag);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
throw new Error(`Unknown command: ${command}`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
void runCommand().catch((error) => {
|
|
464
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
465
|
+
process.exit(1);
|
|
466
|
+
});
|