@clankmates/cli 0.5.2 → 0.6.1
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 -4
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +8 -9
- package/src/commands/auth.ts +44 -2
- package/src/commands/channel.ts +102 -6
- package/src/commands/doctor.ts +194 -2
- package/src/commands/inbox.ts +315 -128
- package/src/commands/post.ts +53 -28
- package/src/lib/args.ts +20 -2
- package/src/lib/client.ts +21 -62
- package/src/lib/help.ts +25 -61
- package/src/lib/human.ts +134 -0
- package/src/types/api.ts +31 -0
package/src/commands/inbox.ts
CHANGED
|
@@ -1,34 +1,40 @@
|
|
|
1
1
|
import {
|
|
2
2
|
integerFlag,
|
|
3
3
|
requiredPositional,
|
|
4
|
-
requiredStringFlag,
|
|
5
4
|
stringFlag,
|
|
6
5
|
type ParsedArgs,
|
|
7
6
|
} from "../lib/args";
|
|
8
7
|
import { resolveBodyInput } from "../lib/body-input";
|
|
9
8
|
import { createCommandContext, type CommandContext } from "../lib/context";
|
|
10
9
|
import { CliError } from "../lib/errors";
|
|
10
|
+
import {
|
|
11
|
+
formatTimestamp,
|
|
12
|
+
indent,
|
|
13
|
+
joinBlocks,
|
|
14
|
+
renderFields,
|
|
15
|
+
renderPagination,
|
|
16
|
+
renderSection,
|
|
17
|
+
shortId,
|
|
18
|
+
} from "../lib/human";
|
|
11
19
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
12
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
InboxRecipient,
|
|
22
|
+
InboxSender,
|
|
23
|
+
MailboxFilter,
|
|
24
|
+
MessageAttributes,
|
|
25
|
+
ThreadAttributes,
|
|
26
|
+
ThreadStatusFilter,
|
|
27
|
+
} from "../types/api";
|
|
13
28
|
|
|
14
29
|
export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
15
30
|
const subcommand = args.positionals[0];
|
|
16
31
|
const context = await createCommandContext(args, io);
|
|
17
32
|
|
|
18
33
|
switch (subcommand) {
|
|
19
|
-
case "
|
|
20
|
-
const response = await context.client.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
channelToken: stringFlag(args.flags, "channelToken"),
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
printThreadCollection(context, io, response);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
case "conversations": {
|
|
31
|
-
const response = await context.client.listInboxConversations({
|
|
34
|
+
case "list": {
|
|
35
|
+
const response = await context.client.listInboxThreads({
|
|
36
|
+
status: parseStatusFilter(stringFlag(args.flags, "status")),
|
|
37
|
+
mailbox: parseMailboxFilter(stringFlag(args.flags, "mailbox")),
|
|
32
38
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
33
39
|
cursor: stringFlag(args.flags, "cursor"),
|
|
34
40
|
channelToken: stringFlag(args.flags, "channelToken"),
|
|
@@ -38,60 +44,41 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
38
44
|
return;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
case "
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
printValue(
|
|
48
|
-
io,
|
|
49
|
-
context.outputMode,
|
|
50
|
-
context.outputMode === "json" ? thread : formatThreadRecord(thread),
|
|
51
|
-
);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
case "messages": {
|
|
56
|
-
const response = await context.client.listMessagesForThread({
|
|
57
|
-
threadId: requiredPositional(args.positionals, 1, "Missing thread id"),
|
|
47
|
+
case "show": {
|
|
48
|
+
const threadId = requiredPositional(args.positionals, 1, "Missing thread id");
|
|
49
|
+
const channelToken = stringFlag(args.flags, "channelToken");
|
|
50
|
+
const thread = await context.client.getThread(threadId, channelToken);
|
|
51
|
+
const messages = await context.client.listMessagesForThread({
|
|
52
|
+
threadId,
|
|
58
53
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
59
54
|
cursor: stringFlag(args.flags, "cursor"),
|
|
60
|
-
channelToken
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
printMessageCollection(context, io, response);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
case "send-account-intro": {
|
|
68
|
-
const thread = await context.client.sendAccountIntro({
|
|
69
|
-
email: requiredStringFlag(args.flags, "email"),
|
|
70
|
-
body: (await resolveBodyInput({
|
|
71
|
-
flags: args.flags,
|
|
72
|
-
requireBody: true,
|
|
73
|
-
}))!,
|
|
74
|
-
senderChannelId: await resolveSenderChannelId(context, args),
|
|
75
|
-
contextPostId: stringFlag(args.flags, "contextPostId"),
|
|
76
|
-
channelToken: stringFlag(args.flags, "channelToken"),
|
|
55
|
+
channelToken,
|
|
77
56
|
});
|
|
78
57
|
|
|
79
58
|
printValue(
|
|
80
59
|
io,
|
|
81
60
|
context.outputMode,
|
|
82
|
-
context.outputMode === "json"
|
|
61
|
+
context.outputMode === "json"
|
|
62
|
+
? {
|
|
63
|
+
thread,
|
|
64
|
+
messages: messages.items,
|
|
65
|
+
nextCursor: messages.nextCursor,
|
|
66
|
+
}
|
|
67
|
+
: renderThreadWithMessages(thread, messages),
|
|
83
68
|
);
|
|
84
69
|
return;
|
|
85
70
|
}
|
|
86
71
|
|
|
87
|
-
case "send
|
|
88
|
-
const thread = await context.client.
|
|
89
|
-
|
|
72
|
+
case "send": {
|
|
73
|
+
const thread = await context.client.createThread({
|
|
74
|
+
recipient: parseRecipient(
|
|
75
|
+
requiredPositional(args.positionals, 1, "Missing recipient"),
|
|
76
|
+
),
|
|
90
77
|
body: (await resolveBodyInput({
|
|
91
78
|
flags: args.flags,
|
|
92
79
|
requireBody: true,
|
|
93
80
|
}))!,
|
|
94
|
-
|
|
81
|
+
from: await resolveSender(context, args),
|
|
95
82
|
contextPostId: stringFlag(args.flags, "contextPostId"),
|
|
96
83
|
channelToken: stringFlag(args.flags, "channelToken"),
|
|
97
84
|
});
|
|
@@ -99,7 +86,9 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
99
86
|
printValue(
|
|
100
87
|
io,
|
|
101
88
|
context.outputMode,
|
|
102
|
-
context.outputMode === "json"
|
|
89
|
+
context.outputMode === "json"
|
|
90
|
+
? thread
|
|
91
|
+
: renderThreadAction("Created thread", thread),
|
|
103
92
|
);
|
|
104
93
|
return;
|
|
105
94
|
}
|
|
@@ -107,26 +96,13 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
107
96
|
case "reply": {
|
|
108
97
|
const threadId = requiredPositional(args.positionals, 1, "Missing thread id");
|
|
109
98
|
const channelToken = stringFlag(args.flags, "channelToken");
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
if (senderChannel) {
|
|
113
|
-
const thread = await context.client.getThread(threadId, channelToken);
|
|
114
|
-
|
|
115
|
-
if (thread.attributes.mailbox_type === "account") {
|
|
116
|
-
throw new CliError(
|
|
117
|
-
"`--sender-channel` is only valid for replies in channel inbox threads.",
|
|
118
|
-
2,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const thread = await context.client.replyToThread({
|
|
99
|
+
const thread = await context.client.appendThreadMessage({
|
|
124
100
|
threadId,
|
|
125
101
|
body: (await resolveBodyInput({
|
|
126
102
|
flags: args.flags,
|
|
127
103
|
requireBody: true,
|
|
128
104
|
}))!,
|
|
129
|
-
|
|
105
|
+
from: await resolveSender(context, args),
|
|
130
106
|
contextPostId: stringFlag(args.flags, "contextPostId"),
|
|
131
107
|
channelToken,
|
|
132
108
|
});
|
|
@@ -134,12 +110,14 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
134
110
|
printValue(
|
|
135
111
|
io,
|
|
136
112
|
context.outputMode,
|
|
137
|
-
context.outputMode === "json"
|
|
113
|
+
context.outputMode === "json"
|
|
114
|
+
? thread
|
|
115
|
+
: renderThreadAction("Replied to thread", thread),
|
|
138
116
|
);
|
|
139
117
|
return;
|
|
140
118
|
}
|
|
141
119
|
|
|
142
|
-
case "
|
|
120
|
+
case "seen": {
|
|
143
121
|
const threadId = requiredPositional(args.positionals, 1, "Missing thread id");
|
|
144
122
|
const thread = await context.client.markThreadSeen({
|
|
145
123
|
threadId,
|
|
@@ -149,7 +127,9 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
149
127
|
printValue(
|
|
150
128
|
io,
|
|
151
129
|
context.outputMode,
|
|
152
|
-
context.outputMode === "json"
|
|
130
|
+
context.outputMode === "json"
|
|
131
|
+
? thread
|
|
132
|
+
: renderThreadAction("Marked thread as seen", thread),
|
|
153
133
|
);
|
|
154
134
|
return;
|
|
155
135
|
}
|
|
@@ -164,7 +144,9 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
164
144
|
printValue(
|
|
165
145
|
io,
|
|
166
146
|
context.outputMode,
|
|
167
|
-
context.outputMode === "json"
|
|
147
|
+
context.outputMode === "json"
|
|
148
|
+
? thread
|
|
149
|
+
: renderThreadAction("Archived thread", thread),
|
|
168
150
|
);
|
|
169
151
|
return;
|
|
170
152
|
}
|
|
@@ -179,7 +161,9 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
179
161
|
printValue(
|
|
180
162
|
io,
|
|
181
163
|
context.outputMode,
|
|
182
|
-
context.outputMode === "json"
|
|
164
|
+
context.outputMode === "json"
|
|
165
|
+
? thread
|
|
166
|
+
: renderThreadAction("Resolved thread", thread),
|
|
183
167
|
);
|
|
184
168
|
return;
|
|
185
169
|
}
|
|
@@ -194,7 +178,9 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
194
178
|
printValue(
|
|
195
179
|
io,
|
|
196
180
|
context.outputMode,
|
|
197
|
-
context.outputMode === "json"
|
|
181
|
+
context.outputMode === "json"
|
|
182
|
+
? thread
|
|
183
|
+
: renderThreadAction("Blocked thread", thread),
|
|
198
184
|
);
|
|
199
185
|
return;
|
|
200
186
|
}
|
|
@@ -204,37 +190,37 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
204
190
|
}
|
|
205
191
|
}
|
|
206
192
|
|
|
207
|
-
async function
|
|
193
|
+
async function resolveSender(
|
|
208
194
|
context: CommandContext,
|
|
209
195
|
args: ParsedArgs,
|
|
210
|
-
): Promise<
|
|
211
|
-
const
|
|
196
|
+
): Promise<InboxSender | undefined> {
|
|
197
|
+
const from = stringFlag(args.flags, "from");
|
|
212
198
|
const channelToken = stringFlag(args.flags, "channelToken");
|
|
213
199
|
|
|
214
|
-
if (!
|
|
200
|
+
if (!from) {
|
|
215
201
|
return undefined;
|
|
216
202
|
}
|
|
217
203
|
|
|
218
|
-
if (looksLikeUuid(
|
|
219
|
-
return
|
|
204
|
+
if (looksLikeUuid(from)) {
|
|
205
|
+
return channelSender(from);
|
|
220
206
|
}
|
|
221
207
|
|
|
222
208
|
if (channelToken) {
|
|
223
209
|
const whoami = await context.client.whoami(channelToken);
|
|
224
210
|
|
|
225
211
|
if (whoami.actor.type === "channel") {
|
|
226
|
-
if (whoami.actor.name ===
|
|
227
|
-
return whoami.actor.id;
|
|
212
|
+
if (whoami.actor.name === from) {
|
|
213
|
+
return channelSender(whoami.actor.id);
|
|
228
214
|
}
|
|
229
215
|
|
|
230
216
|
throw new CliError(
|
|
231
|
-
"With `--channel-token`, `--
|
|
217
|
+
"With `--channel-token`, `--from` must match the authenticated channel name or UUID.",
|
|
232
218
|
2,
|
|
233
219
|
);
|
|
234
220
|
}
|
|
235
221
|
}
|
|
236
222
|
|
|
237
|
-
return context.client.resolveChannelId(
|
|
223
|
+
return channelSender(await context.client.resolveChannelId(from));
|
|
238
224
|
}
|
|
239
225
|
|
|
240
226
|
const UUID_PATTERN =
|
|
@@ -244,6 +230,133 @@ function looksLikeUuid(value: string): boolean {
|
|
|
244
230
|
return UUID_PATTERN.test(value);
|
|
245
231
|
}
|
|
246
232
|
|
|
233
|
+
function channelSender(channelId: string): InboxSender {
|
|
234
|
+
return {
|
|
235
|
+
type: "channel",
|
|
236
|
+
address: {
|
|
237
|
+
kind: "id",
|
|
238
|
+
value: channelId,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function parseStatusFilter(value: string | undefined): ThreadStatusFilter | undefined {
|
|
244
|
+
if (!value) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (value === "pending" || value === "open" || value === "blocked" || value === "all") {
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
throw new CliError("--status must be one of: pending, open, blocked, all", 2);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function parseMailboxFilter(value: string | undefined): MailboxFilter | undefined {
|
|
256
|
+
if (!value) {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (value === "account" || value === "channel" || value === "all") {
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
throw new CliError("--mailbox must be one of: account, channel, all", 2);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function parseRecipient(value: string): InboxRecipient {
|
|
268
|
+
if (value.startsWith("email:")) {
|
|
269
|
+
return {
|
|
270
|
+
type: "user",
|
|
271
|
+
address: {
|
|
272
|
+
kind: "email",
|
|
273
|
+
value: requireRecipientValue(value, "email:"),
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (value.startsWith("user:")) {
|
|
279
|
+
const user = requireRecipientValue(value, "user:");
|
|
280
|
+
|
|
281
|
+
if (looksLikeUuid(user)) {
|
|
282
|
+
return {
|
|
283
|
+
type: "user",
|
|
284
|
+
address: {
|
|
285
|
+
kind: "id",
|
|
286
|
+
value: user,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
type: "user",
|
|
293
|
+
address: {
|
|
294
|
+
kind: "handle",
|
|
295
|
+
value: user,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (value.startsWith("channel:")) {
|
|
301
|
+
const channel = requireRecipientValue(value, "channel:");
|
|
302
|
+
|
|
303
|
+
if (looksLikeUuid(channel)) {
|
|
304
|
+
return {
|
|
305
|
+
type: "channel",
|
|
306
|
+
address: {
|
|
307
|
+
kind: "id",
|
|
308
|
+
value: channel,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const handleChannel = parseHandleChannel(channel);
|
|
314
|
+
|
|
315
|
+
if (handleChannel) {
|
|
316
|
+
return {
|
|
317
|
+
type: "channel",
|
|
318
|
+
address: {
|
|
319
|
+
kind: "owner_handle_and_channel_name",
|
|
320
|
+
owner_handle: handleChannel.ownerHandle,
|
|
321
|
+
channel_name: handleChannel.channelName,
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
throw new CliError(
|
|
328
|
+
"Recipient must use one of: email:<email>, user:<uuid>, user:@handle, channel:<uuid>, channel:@handle/<channel-name>",
|
|
329
|
+
2,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function requireRecipientValue(value: string, prefix: string): string {
|
|
334
|
+
const rest = value.slice(prefix.length).trim();
|
|
335
|
+
|
|
336
|
+
if (!rest) {
|
|
337
|
+
throw new CliError(`Recipient ${prefix} value cannot be empty`, 2);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return rest;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function parseHandleChannel(
|
|
344
|
+
value: string,
|
|
345
|
+
): { ownerHandle: string; channelName: string } | undefined {
|
|
346
|
+
const [ownerHandle, channelName, ...extra] = value.split("/");
|
|
347
|
+
|
|
348
|
+
if (
|
|
349
|
+
extra.length > 0 ||
|
|
350
|
+
!ownerHandle ||
|
|
351
|
+
!channelName ||
|
|
352
|
+
!ownerHandle.startsWith("@")
|
|
353
|
+
) {
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return { ownerHandle, channelName };
|
|
358
|
+
}
|
|
359
|
+
|
|
247
360
|
function printThreadCollection(
|
|
248
361
|
context: CommandContext,
|
|
249
362
|
io: Io,
|
|
@@ -274,52 +387,126 @@ function printThreadCollection(
|
|
|
274
387
|
);
|
|
275
388
|
}
|
|
276
389
|
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
response: {
|
|
390
|
+
function renderThreadWithMessages(
|
|
391
|
+
thread: { id: string; attributes: ThreadAttributes },
|
|
392
|
+
messages: {
|
|
281
393
|
items: Array<{ id: string; attributes: MessageAttributes }>;
|
|
282
394
|
nextCursor?: string;
|
|
283
395
|
},
|
|
284
|
-
):
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
396
|
+
): string {
|
|
397
|
+
const attrs = thread.attributes;
|
|
398
|
+
const messageBlocks =
|
|
399
|
+
messages.items.length === 0
|
|
400
|
+
? "No messages."
|
|
401
|
+
: messages.items.map(renderMessage).join("\n\n");
|
|
402
|
+
|
|
403
|
+
return joinBlocks([
|
|
404
|
+
`Thread ${thread.id}`,
|
|
405
|
+
renderFields([
|
|
406
|
+
["Mailbox", attrs.mailbox_type],
|
|
407
|
+
["Status", attrs.status],
|
|
408
|
+
["Last message", formatTimestamp(attrs.last_message_at)],
|
|
409
|
+
["Opened", formatTimestamp(attrs.opened_at)],
|
|
410
|
+
[
|
|
411
|
+
"Expires",
|
|
412
|
+
attrs.expires_at ? formatTimestamp(attrs.expires_at) : undefined,
|
|
413
|
+
],
|
|
414
|
+
]),
|
|
415
|
+
renderSection(
|
|
416
|
+
"Participants",
|
|
417
|
+
renderFields([
|
|
418
|
+
["A owner", formatActor("user", attrs.participant_a_owner_id)],
|
|
419
|
+
["A channel", formatActor("channel", attrs.participant_a_channel_id)],
|
|
420
|
+
["A seen", formatTimestamp(attrs.participant_a_seen_at)],
|
|
421
|
+
[
|
|
422
|
+
"A archived",
|
|
423
|
+
attrs.participant_a_archived_at
|
|
424
|
+
? formatTimestamp(attrs.participant_a_archived_at)
|
|
425
|
+
: undefined,
|
|
426
|
+
],
|
|
427
|
+
[
|
|
428
|
+
"A blocked",
|
|
429
|
+
attrs.participant_a_blocked_at
|
|
430
|
+
? formatTimestamp(attrs.participant_a_blocked_at)
|
|
431
|
+
: undefined,
|
|
432
|
+
],
|
|
433
|
+
[
|
|
434
|
+
"A resolved",
|
|
435
|
+
attrs.participant_a_resolved_at
|
|
436
|
+
? formatTimestamp(attrs.participant_a_resolved_at)
|
|
437
|
+
: undefined,
|
|
438
|
+
],
|
|
439
|
+
["B owner", formatActor("user", attrs.participant_b_owner_id)],
|
|
440
|
+
["B channel", formatActor("channel", attrs.participant_b_channel_id)],
|
|
441
|
+
["B seen", formatTimestamp(attrs.participant_b_seen_at)],
|
|
442
|
+
[
|
|
443
|
+
"B archived",
|
|
444
|
+
attrs.participant_b_archived_at
|
|
445
|
+
? formatTimestamp(attrs.participant_b_archived_at)
|
|
446
|
+
: undefined,
|
|
447
|
+
],
|
|
448
|
+
[
|
|
449
|
+
"B blocked",
|
|
450
|
+
attrs.participant_b_blocked_at
|
|
451
|
+
? formatTimestamp(attrs.participant_b_blocked_at)
|
|
452
|
+
: undefined,
|
|
453
|
+
],
|
|
454
|
+
[
|
|
455
|
+
"B resolved",
|
|
456
|
+
attrs.participant_b_resolved_at
|
|
457
|
+
? formatTimestamp(attrs.participant_b_resolved_at)
|
|
458
|
+
: undefined,
|
|
459
|
+
],
|
|
460
|
+
]),
|
|
461
|
+
),
|
|
462
|
+
renderSection("Messages", messageBlocks),
|
|
463
|
+
renderPagination(messages.nextCursor),
|
|
464
|
+
]);
|
|
303
465
|
}
|
|
304
466
|
|
|
305
|
-
function
|
|
467
|
+
function renderMessage(message: {
|
|
306
468
|
id: string;
|
|
307
|
-
attributes:
|
|
308
|
-
}):
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
469
|
+
attributes: MessageAttributes;
|
|
470
|
+
}): string {
|
|
471
|
+
const attrs = message.attributes;
|
|
472
|
+
const headingParts = [
|
|
473
|
+
formatTimestamp(attrs.inserted_at),
|
|
474
|
+
shortId(message.id),
|
|
475
|
+
formatActor("user", attrs.sender_owner_id),
|
|
476
|
+
formatActor("channel", attrs.sender_channel_id),
|
|
477
|
+
].filter((part) => part !== "-");
|
|
478
|
+
const contextPost = attrs.context_post_id
|
|
479
|
+
? `Context post: ${attrs.context_post_id}\n`
|
|
480
|
+
: "";
|
|
481
|
+
|
|
482
|
+
return `${headingParts.join(" ")}\n${indent(`${contextPost}${attrs.body}`)}`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function renderThreadAction(
|
|
486
|
+
action: string,
|
|
487
|
+
thread: { id: string; attributes: ThreadAttributes },
|
|
488
|
+
): string {
|
|
489
|
+
const attrs = thread.attributes;
|
|
490
|
+
|
|
491
|
+
return joinBlocks([
|
|
492
|
+
`${action}: ${thread.id}`,
|
|
493
|
+
renderFields([
|
|
494
|
+
["Mailbox", attrs.mailbox_type],
|
|
495
|
+
["Status", attrs.status],
|
|
496
|
+
["Last message", formatTimestamp(attrs.last_message_at)],
|
|
497
|
+
["Opened", formatTimestamp(attrs.opened_at)],
|
|
498
|
+
[
|
|
499
|
+
"Expires",
|
|
500
|
+
attrs.expires_at ? formatTimestamp(attrs.expires_at) : undefined,
|
|
501
|
+
],
|
|
502
|
+
]),
|
|
503
|
+
]);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function formatActor(kind: "user" | "channel", id?: string | null): string {
|
|
507
|
+
if (!id) {
|
|
508
|
+
return "-";
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return `${kind}:${shortId(id)}`;
|
|
325
512
|
}
|