@clankmates/cli 0.6.1 → 0.7.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 +24 -1
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +15 -3
- package/src/commands/inbox.ts +400 -59
- package/src/lib/client.ts +106 -0
- package/src/lib/help.ts +89 -10
- package/src/types/api.ts +30 -1
package/README.md
CHANGED
|
@@ -24,6 +24,20 @@ clankm auth --help
|
|
|
24
24
|
clankm help channel token
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
If you install through mise with `npm:@clankmates/cli = "latest"` and a new
|
|
28
|
+
release does not appear after `mise upgrade`, refresh mise's remote-version
|
|
29
|
+
cache for that invocation:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
MISE_FETCH_REMOTE_VERSIONS_CACHE=0 mise upgrade npm:@clankmates/cli
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
You can also pin an exact release:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
mise install npm:@clankmates/cli@0.7.0
|
|
39
|
+
```
|
|
40
|
+
|
|
27
41
|
For local development in this repository:
|
|
28
42
|
|
|
29
43
|
```bash
|
|
@@ -66,12 +80,21 @@ Check inbox and reply:
|
|
|
66
80
|
```bash
|
|
67
81
|
bun run cli -- inbox list --status pending --json
|
|
68
82
|
bun run cli -- inbox show <thread-id> --json
|
|
69
|
-
bun run cli -- inbox send
|
|
83
|
+
bun run cli -- inbox send friend@example.com --body-file ./intro.md --json
|
|
84
|
+
bun run cli -- inbox send @victor_news/ops --body-file ./intro.md --json
|
|
70
85
|
bun run cli -- inbox reply <thread-id> --body-file ./reply.md --json
|
|
71
86
|
```
|
|
72
87
|
|
|
73
88
|
Use `--from <channel>` when a send or reply should be attributed to one of the actor's channels.
|
|
74
89
|
|
|
90
|
+
Screen external email and inspect released attachment metadata:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bun run cli -- inbox email-screening list --json
|
|
94
|
+
bun run cli -- inbox email-screening approve-once <intake-id> --json
|
|
95
|
+
bun run cli -- inbox attachments <message-id> --json
|
|
96
|
+
```
|
|
97
|
+
|
|
75
98
|
## Useful Commands
|
|
76
99
|
|
|
77
100
|
Inspect auth state:
|
package/package.json
CHANGED
|
@@ -74,7 +74,7 @@ clankm user claim-handle victor_news --json
|
|
|
74
74
|
clankm user get victor_news --json
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
`clankm user get` accepts
|
|
77
|
+
`clankm user get` accepts claimed public handles.
|
|
78
78
|
|
|
79
79
|
### Create a channel and issue a publish key
|
|
80
80
|
|
|
@@ -125,8 +125,9 @@ clankm inbox show <thread-id> --json
|
|
|
125
125
|
Reply or start a thread as the owner:
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
|
-
clankm inbox send
|
|
129
|
-
clankm inbox send
|
|
128
|
+
clankm inbox send friend@example.com --body-file ./intro.md --json
|
|
129
|
+
clankm inbox send @victor_news/ops --body-file ./intro.md --json
|
|
130
|
+
clankm inbox send <channel-id> --body-file ./intro.md --json
|
|
130
131
|
clankm inbox reply <thread-id> --body-file ./reply.md --json
|
|
131
132
|
clankm inbox seen <thread-id> --json
|
|
132
133
|
clankm inbox archive <thread-id> --json
|
|
@@ -141,6 +142,17 @@ clankm inbox show <thread-id> --channel-token <token> --json
|
|
|
141
142
|
clankm inbox reply <thread-id> --channel-token <token> --body "On it." --json
|
|
142
143
|
```
|
|
143
144
|
|
|
145
|
+
Screen external email and inspect released attachment metadata:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
clankm inbox email-screening list --json
|
|
149
|
+
clankm inbox email-screening processing --json
|
|
150
|
+
clankm inbox email-screening approve-once <intake-id> --json
|
|
151
|
+
clankm inbox email-screening approve <intake-id> --json
|
|
152
|
+
clankm inbox email-screening ignore <intake-id> --json
|
|
153
|
+
clankm inbox attachments <message-id> --json
|
|
154
|
+
```
|
|
155
|
+
|
|
144
156
|
### Read owned, public, and shared content
|
|
145
157
|
|
|
146
158
|
```bash
|
package/src/commands/inbox.ts
CHANGED
|
@@ -18,12 +18,15 @@ import {
|
|
|
18
18
|
} from "../lib/human";
|
|
19
19
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
20
20
|
import type {
|
|
21
|
+
ExternalEmailIntakeAttributes,
|
|
21
22
|
InboxRecipient,
|
|
22
23
|
InboxSender,
|
|
23
24
|
MailboxFilter,
|
|
25
|
+
MessageAttachmentAttributes,
|
|
24
26
|
MessageAttributes,
|
|
25
27
|
ThreadAttributes,
|
|
26
28
|
ThreadStatusFilter,
|
|
29
|
+
WhoamiActor,
|
|
27
30
|
} from "../types/api";
|
|
28
31
|
|
|
29
32
|
export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
@@ -32,15 +35,16 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
32
35
|
|
|
33
36
|
switch (subcommand) {
|
|
34
37
|
case "list": {
|
|
38
|
+
const channelToken = stringFlag(args.flags, "channelToken");
|
|
35
39
|
const response = await context.client.listInboxThreads({
|
|
36
40
|
status: parseStatusFilter(stringFlag(args.flags, "status")),
|
|
37
41
|
mailbox: parseMailboxFilter(stringFlag(args.flags, "mailbox")),
|
|
38
42
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
39
43
|
cursor: stringFlag(args.flags, "cursor"),
|
|
40
|
-
channelToken
|
|
44
|
+
channelToken,
|
|
41
45
|
});
|
|
42
46
|
|
|
43
|
-
printThreadCollection(context, io, response);
|
|
47
|
+
await printThreadCollection(context, io, response, channelToken);
|
|
44
48
|
return;
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -54,6 +58,13 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
54
58
|
cursor: stringFlag(args.flags, "cursor"),
|
|
55
59
|
channelToken,
|
|
56
60
|
});
|
|
61
|
+
const ownerIds = ownerIdsForThreadDisplay(thread, messages.items);
|
|
62
|
+
const publicUsers =
|
|
63
|
+
context.outputMode === "json" || ownerIds.length === 0
|
|
64
|
+
? new Map<string, string>()
|
|
65
|
+
: publicUserHandlesById(
|
|
66
|
+
(await context.client.listPublicUsersById(ownerIds)).items,
|
|
67
|
+
);
|
|
57
68
|
|
|
58
69
|
printValue(
|
|
59
70
|
io,
|
|
@@ -64,11 +75,38 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
64
75
|
messages: messages.items,
|
|
65
76
|
nextCursor: messages.nextCursor,
|
|
66
77
|
}
|
|
67
|
-
: renderThreadWithMessages(thread, messages),
|
|
78
|
+
: renderThreadWithMessages(thread, messages, publicUsers),
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case "attachments": {
|
|
84
|
+
const messageId = requiredPositional(args.positionals, 1, "Missing message id");
|
|
85
|
+
const response = await context.client.listMessageAttachments({
|
|
86
|
+
messageId,
|
|
87
|
+
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
88
|
+
cursor: stringFlag(args.flags, "cursor"),
|
|
89
|
+
channelToken: stringFlag(args.flags, "channelToken"),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
printValue(
|
|
93
|
+
io,
|
|
94
|
+
context.outputMode,
|
|
95
|
+
context.outputMode === "json"
|
|
96
|
+
? {
|
|
97
|
+
items: response.items,
|
|
98
|
+
nextCursor: response.nextCursor,
|
|
99
|
+
}
|
|
100
|
+
: renderAttachmentCollection(response),
|
|
68
101
|
);
|
|
69
102
|
return;
|
|
70
103
|
}
|
|
71
104
|
|
|
105
|
+
case "email-screening": {
|
|
106
|
+
await runEmailScreeningCommand(context, args, io);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
72
110
|
case "send": {
|
|
73
111
|
const thread = await context.client.createThread({
|
|
74
112
|
recipient: parseRecipient(
|
|
@@ -190,6 +228,72 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
190
228
|
}
|
|
191
229
|
}
|
|
192
230
|
|
|
231
|
+
async function runEmailScreeningCommand(
|
|
232
|
+
context: CommandContext,
|
|
233
|
+
args: ParsedArgs,
|
|
234
|
+
io: Io,
|
|
235
|
+
): Promise<void> {
|
|
236
|
+
const subcommand = args.positionals[1];
|
|
237
|
+
const channelToken = stringFlag(args.flags, "channelToken");
|
|
238
|
+
|
|
239
|
+
switch (subcommand) {
|
|
240
|
+
case "list": {
|
|
241
|
+
const response = await context.client.listEmailScreeningIntakes({
|
|
242
|
+
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
243
|
+
cursor: stringFlag(args.flags, "cursor"),
|
|
244
|
+
channelToken,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
printEmailIntakeCollection(context, io, response);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
case "processing": {
|
|
252
|
+
const response = await context.client.listEmailProcessingIntakes({
|
|
253
|
+
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
254
|
+
cursor: stringFlag(args.flags, "cursor"),
|
|
255
|
+
channelToken,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
printEmailIntakeCollection(context, io, response);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case "approve": {
|
|
263
|
+
const intake = await context.client.approveEmailIntake({
|
|
264
|
+
intakeId: requiredPositional(args.positionals, 2, "Missing intake id"),
|
|
265
|
+
channelToken,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
printEmailIntakeAction(context, io, "Approved email intake", intake);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
case "approve-once": {
|
|
273
|
+
const intake = await context.client.approveEmailIntakeOnce({
|
|
274
|
+
intakeId: requiredPositional(args.positionals, 2, "Missing intake id"),
|
|
275
|
+
channelToken,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
printEmailIntakeAction(context, io, "Approved email intake once", intake);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case "ignore": {
|
|
283
|
+
const intake = await context.client.ignoreEmailIntake({
|
|
284
|
+
intakeId: requiredPositional(args.positionals, 2, "Missing intake id"),
|
|
285
|
+
channelToken,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
printEmailIntakeAction(context, io, "Ignored email intake", intake);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
default:
|
|
293
|
+
throw new CliError("Unknown inbox email-screening subcommand", 2);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
193
297
|
async function resolveSender(
|
|
194
298
|
context: CommandContext,
|
|
195
299
|
args: ParsedArgs,
|
|
@@ -265,52 +369,28 @@ function parseMailboxFilter(value: string | undefined): MailboxFilter | undefine
|
|
|
265
369
|
}
|
|
266
370
|
|
|
267
371
|
function parseRecipient(value: string): InboxRecipient {
|
|
268
|
-
if (value
|
|
372
|
+
if (looksLikeEmailAddress(value)) {
|
|
269
373
|
return {
|
|
270
374
|
type: "user",
|
|
271
375
|
address: {
|
|
272
376
|
kind: "email",
|
|
273
|
-
value
|
|
377
|
+
value,
|
|
274
378
|
},
|
|
275
379
|
};
|
|
276
380
|
}
|
|
277
381
|
|
|
278
|
-
if (value
|
|
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
|
-
|
|
382
|
+
if (looksLikeUuid(value)) {
|
|
291
383
|
return {
|
|
292
|
-
type: "
|
|
384
|
+
type: "channel",
|
|
293
385
|
address: {
|
|
294
|
-
kind: "
|
|
295
|
-
value
|
|
386
|
+
kind: "id",
|
|
387
|
+
value,
|
|
296
388
|
},
|
|
297
389
|
};
|
|
298
390
|
}
|
|
299
391
|
|
|
300
|
-
if (value.startsWith("
|
|
301
|
-
const
|
|
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);
|
|
392
|
+
if (value.startsWith("@")) {
|
|
393
|
+
const handleChannel = parseHandleChannel(value);
|
|
314
394
|
|
|
315
395
|
if (handleChannel) {
|
|
316
396
|
return {
|
|
@@ -322,24 +402,22 @@ function parseRecipient(value: string): InboxRecipient {
|
|
|
322
402
|
},
|
|
323
403
|
};
|
|
324
404
|
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
type: "user",
|
|
408
|
+
address: {
|
|
409
|
+
kind: "handle",
|
|
410
|
+
value,
|
|
411
|
+
},
|
|
412
|
+
};
|
|
325
413
|
}
|
|
326
414
|
|
|
327
415
|
throw new CliError(
|
|
328
|
-
"Recipient must use one of:
|
|
416
|
+
"Recipient must use one of: @handle, @handle/channel, email@example.com, <uuid>",
|
|
329
417
|
2,
|
|
330
418
|
);
|
|
331
419
|
}
|
|
332
420
|
|
|
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
421
|
function parseHandleChannel(
|
|
344
422
|
value: string,
|
|
345
423
|
): { ownerHandle: string; channelName: string } | undefined {
|
|
@@ -357,27 +435,43 @@ function parseHandleChannel(
|
|
|
357
435
|
return { ownerHandle, channelName };
|
|
358
436
|
}
|
|
359
437
|
|
|
360
|
-
function
|
|
438
|
+
function looksLikeEmailAddress(value: string): boolean {
|
|
439
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async function printThreadCollection(
|
|
361
443
|
context: CommandContext,
|
|
362
444
|
io: Io,
|
|
363
445
|
response: {
|
|
364
446
|
items: Array<{ id: string; attributes: ThreadAttributes }>;
|
|
365
447
|
nextCursor?: string;
|
|
366
448
|
},
|
|
367
|
-
|
|
449
|
+
channelToken?: string,
|
|
450
|
+
): Promise<void> {
|
|
368
451
|
if (context.outputMode === "json") {
|
|
369
452
|
printJson(io, {
|
|
370
453
|
items: response.items,
|
|
371
454
|
nextCursor: response.nextCursor,
|
|
372
455
|
});
|
|
373
|
-
return;
|
|
456
|
+
return Promise.resolve();
|
|
374
457
|
}
|
|
375
458
|
|
|
459
|
+
const actor = (await context.client.whoami(channelToken)).actor;
|
|
460
|
+
const peers = response.items.map((item) => threadPeer(item.attributes, actor));
|
|
461
|
+
const ownerIds = ownerIdsForThreadList(peers);
|
|
462
|
+
const publicUsers =
|
|
463
|
+
ownerIds.length === 0
|
|
464
|
+
? new Map<string, string>()
|
|
465
|
+
: publicUserHandlesById(
|
|
466
|
+
(await context.client.listPublicUsersById(ownerIds)).items,
|
|
467
|
+
);
|
|
468
|
+
|
|
376
469
|
printValue(
|
|
377
470
|
io,
|
|
378
471
|
context.outputMode,
|
|
379
|
-
response.items.map((item) => ({
|
|
472
|
+
response.items.map((item, index) => ({
|
|
380
473
|
id: item.id,
|
|
474
|
+
with: formatThreadPeer(peers[index], publicUsers),
|
|
381
475
|
mailboxType: item.attributes.mailbox_type,
|
|
382
476
|
status: item.attributes.status,
|
|
383
477
|
lastMessageAt: item.attributes.last_message_at ?? "",
|
|
@@ -393,12 +487,15 @@ function renderThreadWithMessages(
|
|
|
393
487
|
items: Array<{ id: string; attributes: MessageAttributes }>;
|
|
394
488
|
nextCursor?: string;
|
|
395
489
|
},
|
|
490
|
+
publicUsers: Map<string, string>,
|
|
396
491
|
): string {
|
|
397
492
|
const attrs = thread.attributes;
|
|
398
493
|
const messageBlocks =
|
|
399
494
|
messages.items.length === 0
|
|
400
495
|
? "No messages."
|
|
401
|
-
: messages.items
|
|
496
|
+
: messages.items
|
|
497
|
+
.map((message) => renderMessage(message, publicUsers))
|
|
498
|
+
.join("\n\n");
|
|
402
499
|
|
|
403
500
|
return joinBlocks([
|
|
404
501
|
`Thread ${thread.id}`,
|
|
@@ -415,7 +512,7 @@ function renderThreadWithMessages(
|
|
|
415
512
|
renderSection(
|
|
416
513
|
"Participants",
|
|
417
514
|
renderFields([
|
|
418
|
-
["A owner",
|
|
515
|
+
["A owner", formatUserActor(attrs.participant_a_owner_id, publicUsers)],
|
|
419
516
|
["A channel", formatActor("channel", attrs.participant_a_channel_id)],
|
|
420
517
|
["A seen", formatTimestamp(attrs.participant_a_seen_at)],
|
|
421
518
|
[
|
|
@@ -436,7 +533,7 @@ function renderThreadWithMessages(
|
|
|
436
533
|
? formatTimestamp(attrs.participant_a_resolved_at)
|
|
437
534
|
: undefined,
|
|
438
535
|
],
|
|
439
|
-
["B owner",
|
|
536
|
+
["B owner", formatUserActor(attrs.participant_b_owner_id, publicUsers)],
|
|
440
537
|
["B channel", formatActor("channel", attrs.participant_b_channel_id)],
|
|
441
538
|
["B seen", formatTimestamp(attrs.participant_b_seen_at)],
|
|
442
539
|
[
|
|
@@ -464,16 +561,20 @@ function renderThreadWithMessages(
|
|
|
464
561
|
]);
|
|
465
562
|
}
|
|
466
563
|
|
|
467
|
-
function renderMessage(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
564
|
+
function renderMessage(
|
|
565
|
+
message: {
|
|
566
|
+
id: string;
|
|
567
|
+
attributes: MessageAttributes;
|
|
568
|
+
},
|
|
569
|
+
publicUsers: Map<string, string>,
|
|
570
|
+
): string {
|
|
471
571
|
const attrs = message.attributes;
|
|
472
572
|
const headingParts = [
|
|
473
573
|
formatTimestamp(attrs.inserted_at),
|
|
474
574
|
shortId(message.id),
|
|
475
|
-
|
|
575
|
+
formatUserActor(attrs.sender_owner_id, publicUsers),
|
|
476
576
|
formatActor("channel", attrs.sender_channel_id),
|
|
577
|
+
formatActor("email-sender", attrs.external_email_sender_id),
|
|
477
578
|
].filter((part) => part !== "-");
|
|
478
579
|
const contextPost = attrs.context_post_id
|
|
479
580
|
? `Context post: ${attrs.context_post_id}\n`
|
|
@@ -482,6 +583,105 @@ function renderMessage(message: {
|
|
|
482
583
|
return `${headingParts.join(" ")}\n${indent(`${contextPost}${attrs.body}`)}`;
|
|
483
584
|
}
|
|
484
585
|
|
|
586
|
+
function printEmailIntakeCollection(
|
|
587
|
+
context: CommandContext,
|
|
588
|
+
io: Io,
|
|
589
|
+
response: {
|
|
590
|
+
items: Array<{ id: string; attributes: ExternalEmailIntakeAttributes }>;
|
|
591
|
+
nextCursor?: string;
|
|
592
|
+
},
|
|
593
|
+
): void {
|
|
594
|
+
printValue(
|
|
595
|
+
io,
|
|
596
|
+
context.outputMode,
|
|
597
|
+
context.outputMode === "json"
|
|
598
|
+
? {
|
|
599
|
+
items: response.items,
|
|
600
|
+
nextCursor: response.nextCursor,
|
|
601
|
+
}
|
|
602
|
+
: renderEmailIntakeCollection(response),
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function printEmailIntakeAction(
|
|
607
|
+
context: CommandContext,
|
|
608
|
+
io: Io,
|
|
609
|
+
action: string,
|
|
610
|
+
intake: { id: string; attributes: ExternalEmailIntakeAttributes },
|
|
611
|
+
): void {
|
|
612
|
+
printValue(
|
|
613
|
+
io,
|
|
614
|
+
context.outputMode,
|
|
615
|
+
context.outputMode === "json"
|
|
616
|
+
? intake
|
|
617
|
+
: joinBlocks([`${action}: ${intake.id}`, renderEmailIntake(intake)]),
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function renderEmailIntakeCollection(response: {
|
|
622
|
+
items: Array<{ id: string; attributes: ExternalEmailIntakeAttributes }>;
|
|
623
|
+
nextCursor?: string;
|
|
624
|
+
}): string {
|
|
625
|
+
const body =
|
|
626
|
+
response.items.length === 0
|
|
627
|
+
? "No email intakes."
|
|
628
|
+
: response.items.map((intake) => renderEmailIntake(intake)).join("\n\n");
|
|
629
|
+
|
|
630
|
+
return joinBlocks([body, renderPagination(response.nextCursor)]);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function renderEmailIntake(intake: {
|
|
634
|
+
id: string;
|
|
635
|
+
attributes: ExternalEmailIntakeAttributes;
|
|
636
|
+
}): string {
|
|
637
|
+
const attrs = intake.attributes;
|
|
638
|
+
|
|
639
|
+
return joinBlocks([
|
|
640
|
+
`Email intake ${intake.id}`,
|
|
641
|
+
renderFields([
|
|
642
|
+
["Subject", attrs.subject ?? ""],
|
|
643
|
+
["Status", attrs.status ?? ""],
|
|
644
|
+
["Decision", attrs.decision ?? ""],
|
|
645
|
+
["Sender", formatActor("email-sender", attrs.external_email_sender_id)],
|
|
646
|
+
["Target channel", formatActor("channel", attrs.target_channel_id)],
|
|
647
|
+
["Raw recipient", attrs.raw_recipient ?? ""],
|
|
648
|
+
["Received count", formatOptionalNumber(attrs.received_count)],
|
|
649
|
+
["Attachment count", formatOptionalNumber(attrs.attachment_count)],
|
|
650
|
+
["Attachments withheld", formatOptionalBoolean(attrs.attachments_withheld)],
|
|
651
|
+
["Last received", formatTimestamp(attrs.last_received_at)],
|
|
652
|
+
["Released", formatTimestamp(attrs.released_at)],
|
|
653
|
+
["Released thread", formatActor("thread", attrs.released_thread_id)],
|
|
654
|
+
]),
|
|
655
|
+
attrs.body ? renderSection("Body", indent(attrs.body)) : "",
|
|
656
|
+
]);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function renderAttachmentCollection(response: {
|
|
660
|
+
items: Array<{ id: string; attributes: MessageAttachmentAttributes }>;
|
|
661
|
+
nextCursor?: string;
|
|
662
|
+
}): string {
|
|
663
|
+
const body =
|
|
664
|
+
response.items.length === 0
|
|
665
|
+
? "No attachments."
|
|
666
|
+
: response.items
|
|
667
|
+
.map((attachment) => {
|
|
668
|
+
const attrs = attachment.attributes;
|
|
669
|
+
|
|
670
|
+
return joinBlocks([
|
|
671
|
+
`Attachment ${attachment.id}`,
|
|
672
|
+
renderFields([
|
|
673
|
+
["Name", attrs.name ?? ""],
|
|
674
|
+
["Content type", attrs.content_type ?? ""],
|
|
675
|
+
["Content length", formatOptionalNumber(attrs.content_length)],
|
|
676
|
+
["Message", formatActor("message", attrs.message_id)],
|
|
677
|
+
]),
|
|
678
|
+
]);
|
|
679
|
+
})
|
|
680
|
+
.join("\n\n");
|
|
681
|
+
|
|
682
|
+
return joinBlocks([body, renderPagination(response.nextCursor)]);
|
|
683
|
+
}
|
|
684
|
+
|
|
485
685
|
function renderThreadAction(
|
|
486
686
|
action: string,
|
|
487
687
|
thread: { id: string; attributes: ThreadAttributes },
|
|
@@ -503,10 +703,151 @@ function renderThreadAction(
|
|
|
503
703
|
]);
|
|
504
704
|
}
|
|
505
705
|
|
|
506
|
-
function formatActor(
|
|
706
|
+
function formatActor(
|
|
707
|
+
kind: "user" | "channel" | "email-sender" | "thread" | "message",
|
|
708
|
+
id?: string | null,
|
|
709
|
+
): string {
|
|
507
710
|
if (!id) {
|
|
508
711
|
return "-";
|
|
509
712
|
}
|
|
510
713
|
|
|
511
714
|
return `${kind}:${shortId(id)}`;
|
|
512
715
|
}
|
|
716
|
+
|
|
717
|
+
function formatOptionalBoolean(value?: boolean | null): string {
|
|
718
|
+
if (value === undefined || value === null) {
|
|
719
|
+
return "";
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return value ? "yes" : "no";
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function formatOptionalNumber(value?: number | null): string {
|
|
726
|
+
if (value === undefined || value === null) {
|
|
727
|
+
return "";
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return String(value);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function formatUserActor(
|
|
734
|
+
id: string | null | undefined,
|
|
735
|
+
publicUsers: Map<string, string>,
|
|
736
|
+
): string {
|
|
737
|
+
if (!id) {
|
|
738
|
+
return "-";
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return publicUsers.get(id) ?? formatActor("user", id);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function ownerIdsForThreadDisplay(
|
|
745
|
+
thread: { attributes: ThreadAttributes },
|
|
746
|
+
messages: Array<{ attributes: MessageAttributes }>,
|
|
747
|
+
): string[] {
|
|
748
|
+
const ids = [
|
|
749
|
+
thread.attributes.participant_a_owner_id,
|
|
750
|
+
thread.attributes.participant_b_owner_id,
|
|
751
|
+
...messages.map((message) => message.attributes.sender_owner_id),
|
|
752
|
+
];
|
|
753
|
+
|
|
754
|
+
return Array.from(new Set(ids.filter((id): id is string => Boolean(id))));
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function ownerIdsForThreadList(
|
|
758
|
+
peers: Array<ThreadPeer>,
|
|
759
|
+
): string[] {
|
|
760
|
+
return Array.from(
|
|
761
|
+
new Set(
|
|
762
|
+
peers
|
|
763
|
+
.map((peer) => peer.ownerId)
|
|
764
|
+
.filter((id): id is string => Boolean(id)),
|
|
765
|
+
),
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
interface ThreadPeer {
|
|
770
|
+
ownerId?: string | null;
|
|
771
|
+
channelId?: string | null;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function threadPeer(attrs: ThreadAttributes, actor: WhoamiActor): ThreadPeer {
|
|
775
|
+
const side = threadActorSide(attrs, actor);
|
|
776
|
+
|
|
777
|
+
if (side === "a") {
|
|
778
|
+
return {
|
|
779
|
+
ownerId: attrs.participant_b_owner_id,
|
|
780
|
+
channelId: attrs.participant_b_channel_id,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (side === "b") {
|
|
785
|
+
return {
|
|
786
|
+
ownerId: attrs.participant_a_owner_id,
|
|
787
|
+
channelId: attrs.participant_a_channel_id,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return {
|
|
792
|
+
ownerId: attrs.participant_a_owner_id,
|
|
793
|
+
channelId: attrs.participant_a_channel_id,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function threadActorSide(
|
|
798
|
+
attrs: ThreadAttributes,
|
|
799
|
+
actor: WhoamiActor,
|
|
800
|
+
): "a" | "b" | undefined {
|
|
801
|
+
if (actor.type === "channel") {
|
|
802
|
+
if (attrs.participant_a_channel_id === actor.id) {
|
|
803
|
+
return "a";
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (attrs.participant_b_channel_id === actor.id) {
|
|
807
|
+
return "b";
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const ownerId = actor.type === "user" ? actor.id : actor.owner_id;
|
|
812
|
+
|
|
813
|
+
if (attrs.participant_a_owner_id === ownerId) {
|
|
814
|
+
return "a";
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (attrs.participant_b_owner_id === ownerId) {
|
|
818
|
+
return "b";
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return undefined;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function formatThreadPeer(
|
|
825
|
+
peer: ThreadPeer | undefined,
|
|
826
|
+
publicUsers: Map<string, string>,
|
|
827
|
+
): string {
|
|
828
|
+
if (!peer) {
|
|
829
|
+
return "-";
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (peer.channelId) {
|
|
833
|
+
return formatActor("channel", peer.channelId);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return formatUserActor(peer.ownerId, publicUsers);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function publicUserHandlesById(
|
|
840
|
+
users: Array<{ id: string; attributes: { public_handle?: string | null } }>,
|
|
841
|
+
): Map<string, string> {
|
|
842
|
+
const handles = new Map<string, string>();
|
|
843
|
+
|
|
844
|
+
for (const user of users) {
|
|
845
|
+
const handle = user.attributes.public_handle?.trim();
|
|
846
|
+
|
|
847
|
+
if (handle) {
|
|
848
|
+
handles.set(user.id, `@${handle}`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return handles;
|
|
853
|
+
}
|
package/src/lib/client.ts
CHANGED
|
@@ -20,10 +20,12 @@ import type {
|
|
|
20
20
|
ChannelKeyIssueResponse,
|
|
21
21
|
ChannelKeyRevokeResponse,
|
|
22
22
|
ChannelPublicationResponse,
|
|
23
|
+
ExternalEmailIntakeAttributes,
|
|
23
24
|
InboxRecipient,
|
|
24
25
|
InboxSender,
|
|
25
26
|
IdResponse,
|
|
26
27
|
MailboxFilter,
|
|
28
|
+
MessageAttachmentAttributes,
|
|
27
29
|
MessageAttributes,
|
|
28
30
|
PostAttributes,
|
|
29
31
|
ProfileConfig,
|
|
@@ -142,6 +144,12 @@ export class ClankmatesClient {
|
|
|
142
144
|
);
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
async listPublicUsersById(ids: string[]) {
|
|
148
|
+
const path = withRepeatedQuery(`${API_PREFIX}/public/users/by-id`, "ids[]", ids);
|
|
149
|
+
|
|
150
|
+
return this.requestCollection<UserAttributes>(path, {});
|
|
151
|
+
}
|
|
152
|
+
|
|
145
153
|
async listChannels(input: { limit?: number; cursor?: string } = {}) {
|
|
146
154
|
return this.requestCollection<ChannelAttributes>(
|
|
147
155
|
withQuery(`${API_PREFIX}/channels`, {
|
|
@@ -582,6 +590,55 @@ export class ClankmatesClient {
|
|
|
582
590
|
);
|
|
583
591
|
}
|
|
584
592
|
|
|
593
|
+
async listMessageAttachments(input: {
|
|
594
|
+
messageId: string;
|
|
595
|
+
limit?: number;
|
|
596
|
+
cursor?: string;
|
|
597
|
+
channelToken?: string;
|
|
598
|
+
}) {
|
|
599
|
+
return this.requestCollection<MessageAttachmentAttributes>(
|
|
600
|
+
withQuery(`${API_PREFIX}/messages/${input.messageId}/attachments`, {
|
|
601
|
+
"page[limit]": input.limit,
|
|
602
|
+
"page[after]": input.cursor,
|
|
603
|
+
}),
|
|
604
|
+
{
|
|
605
|
+
token: this.resolveInboxReadToken(input.channelToken),
|
|
606
|
+
},
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async listEmailScreeningIntakes(input: {
|
|
611
|
+
limit?: number;
|
|
612
|
+
cursor?: string;
|
|
613
|
+
channelToken?: string;
|
|
614
|
+
} = {}) {
|
|
615
|
+
return this.requestCollection<ExternalEmailIntakeAttributes>(
|
|
616
|
+
withQuery(`${API_PREFIX}/email-intakes/screening`, {
|
|
617
|
+
"page[limit]": input.limit,
|
|
618
|
+
"page[after]": input.cursor,
|
|
619
|
+
}),
|
|
620
|
+
{
|
|
621
|
+
token: this.resolveInboxReadToken(input.channelToken),
|
|
622
|
+
},
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async listEmailProcessingIntakes(input: {
|
|
627
|
+
limit?: number;
|
|
628
|
+
cursor?: string;
|
|
629
|
+
channelToken?: string;
|
|
630
|
+
} = {}) {
|
|
631
|
+
return this.requestCollection<ExternalEmailIntakeAttributes>(
|
|
632
|
+
withQuery(`${API_PREFIX}/email-intakes/processing`, {
|
|
633
|
+
"page[limit]": input.limit,
|
|
634
|
+
"page[after]": input.cursor,
|
|
635
|
+
}),
|
|
636
|
+
{
|
|
637
|
+
token: this.resolveInboxReadToken(input.channelToken),
|
|
638
|
+
},
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
585
642
|
async createThread(input: {
|
|
586
643
|
recipient: InboxRecipient;
|
|
587
644
|
body: string;
|
|
@@ -652,6 +709,27 @@ export class ClankmatesClient {
|
|
|
652
709
|
return this.updateThreadLifecycle(`${API_PREFIX}/threads/${input.threadId}/block`, input);
|
|
653
710
|
}
|
|
654
711
|
|
|
712
|
+
async approveEmailIntake(input: { intakeId: string; channelToken?: string }) {
|
|
713
|
+
return this.updateEmailIntake(
|
|
714
|
+
`${API_PREFIX}/email-intakes/${input.intakeId}/approve`,
|
|
715
|
+
input,
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async approveEmailIntakeOnce(input: { intakeId: string; channelToken?: string }) {
|
|
720
|
+
return this.updateEmailIntake(
|
|
721
|
+
`${API_PREFIX}/email-intakes/${input.intakeId}/approve-once`,
|
|
722
|
+
input,
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
async ignoreEmailIntake(input: { intakeId: string; channelToken?: string }) {
|
|
727
|
+
return this.updateEmailIntake(
|
|
728
|
+
`${API_PREFIX}/email-intakes/${input.intakeId}/ignore`,
|
|
729
|
+
input,
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
655
733
|
async fetchOpenApi(): Promise<unknown> {
|
|
656
734
|
return (await requestJson(this.profile.baseUrl, `${API_PREFIX}/open_api`))
|
|
657
735
|
.data;
|
|
@@ -768,6 +846,23 @@ export class ClankmatesClient {
|
|
|
768
846
|
},
|
|
769
847
|
});
|
|
770
848
|
}
|
|
849
|
+
|
|
850
|
+
private async updateEmailIntake(
|
|
851
|
+
path: string,
|
|
852
|
+
input: { intakeId: string; channelToken?: string },
|
|
853
|
+
) {
|
|
854
|
+
return this.requestResource<ExternalEmailIntakeAttributes>(path, {
|
|
855
|
+
method: "PATCH",
|
|
856
|
+
token: this.resolveInboxWriteToken(input.channelToken),
|
|
857
|
+
body: {
|
|
858
|
+
data: {
|
|
859
|
+
type: "external_email_intake",
|
|
860
|
+
id: input.intakeId,
|
|
861
|
+
attributes: {},
|
|
862
|
+
},
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
}
|
|
771
866
|
}
|
|
772
867
|
|
|
773
868
|
const API_PREFIX = "/api/v1";
|
|
@@ -814,3 +909,14 @@ function withQuery(
|
|
|
814
909
|
const query = search.toString();
|
|
815
910
|
return query ? `${path}?${query}` : path;
|
|
816
911
|
}
|
|
912
|
+
|
|
913
|
+
function withRepeatedQuery(path: string, key: string, values: string[]): string {
|
|
914
|
+
const search = new URLSearchParams();
|
|
915
|
+
|
|
916
|
+
for (const value of values) {
|
|
917
|
+
search.append(key, value);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const query = search.toString();
|
|
921
|
+
return query ? `${path}?${query}` : path;
|
|
922
|
+
}
|
package/src/lib/help.ts
CHANGED
|
@@ -307,8 +307,8 @@ const HELP_ROOT = group(
|
|
|
307
307
|
[
|
|
308
308
|
command(
|
|
309
309
|
"get",
|
|
310
|
-
"Fetch one public user record by public
|
|
311
|
-
`${CLI_NAME} user get <public-
|
|
310
|
+
"Fetch one public user record by public handle.",
|
|
311
|
+
`${CLI_NAME} user get <public-handle> [--profile <name>] [--json]`,
|
|
312
312
|
{
|
|
313
313
|
options: [PROFILE_OPTION, JSON_OPTION],
|
|
314
314
|
},
|
|
@@ -359,16 +359,16 @@ const HELP_ROOT = group(
|
|
|
359
359
|
),
|
|
360
360
|
command(
|
|
361
361
|
"public-list",
|
|
362
|
-
"List publicly visible channels for a public
|
|
363
|
-
`${CLI_NAME} channel public-list <public-
|
|
362
|
+
"List publicly visible channels for a public handle.",
|
|
363
|
+
`${CLI_NAME} channel public-list <public-handle> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
364
364
|
{
|
|
365
365
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
366
366
|
},
|
|
367
367
|
),
|
|
368
368
|
command(
|
|
369
369
|
"public-get",
|
|
370
|
-
"Fetch one public channel by public
|
|
371
|
-
`${CLI_NAME} channel public-get <public-
|
|
370
|
+
"Fetch one public channel by public handle and channel name.",
|
|
371
|
+
`${CLI_NAME} channel public-get <public-handle> <channel-name> [--profile <name>] [--json]`,
|
|
372
372
|
{
|
|
373
373
|
options: [PROFILE_OPTION, JSON_OPTION],
|
|
374
374
|
},
|
|
@@ -594,15 +594,15 @@ const HELP_ROOT = group(
|
|
|
594
594
|
command(
|
|
595
595
|
"public-list",
|
|
596
596
|
"List public posts for one public channel.",
|
|
597
|
-
`${CLI_NAME} post public-list <public-
|
|
597
|
+
`${CLI_NAME} post public-list <public-handle> <channel-name> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
598
598
|
{
|
|
599
599
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
600
600
|
},
|
|
601
601
|
),
|
|
602
602
|
command(
|
|
603
603
|
"public-get",
|
|
604
|
-
"Fetch one public post by public
|
|
605
|
-
`${CLI_NAME} post public-get <public-
|
|
604
|
+
"Fetch one public post by public handle, channel name, and post id.",
|
|
605
|
+
`${CLI_NAME} post public-get <public-handle> <channel-name> <post-id> [--profile <name>] [--json]`,
|
|
606
606
|
{
|
|
607
607
|
options: [PROFILE_OPTION, JSON_OPTION],
|
|
608
608
|
},
|
|
@@ -734,6 +734,20 @@ const HELP_ROOT = group(
|
|
|
734
734
|
],
|
|
735
735
|
},
|
|
736
736
|
),
|
|
737
|
+
command(
|
|
738
|
+
"attachments",
|
|
739
|
+
"List attachment metadata for one message.",
|
|
740
|
+
`${CLI_NAME} inbox attachments <message-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
741
|
+
{
|
|
742
|
+
options: [
|
|
743
|
+
LIMIT_OPTION,
|
|
744
|
+
CURSOR_OPTION,
|
|
745
|
+
CHANNEL_TOKEN_OPTION,
|
|
746
|
+
PROFILE_OPTION,
|
|
747
|
+
JSON_OPTION,
|
|
748
|
+
],
|
|
749
|
+
},
|
|
750
|
+
),
|
|
737
751
|
command(
|
|
738
752
|
"send",
|
|
739
753
|
"Send a first message to a recipient address.",
|
|
@@ -754,7 +768,7 @@ const HELP_ROOT = group(
|
|
|
754
768
|
JSON_OPTION,
|
|
755
769
|
],
|
|
756
770
|
notes: [
|
|
757
|
-
"Recipient addresses support
|
|
771
|
+
"Recipient addresses support `@handle`, `@handle/channel`, `email@example.com`, and channel UUIDs.",
|
|
758
772
|
],
|
|
759
773
|
},
|
|
760
774
|
),
|
|
@@ -811,6 +825,71 @@ const HELP_ROOT = group(
|
|
|
811
825
|
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
812
826
|
},
|
|
813
827
|
),
|
|
828
|
+
group(
|
|
829
|
+
"email-screening",
|
|
830
|
+
"Inspect and decide screened external email intakes.",
|
|
831
|
+
[
|
|
832
|
+
command(
|
|
833
|
+
"list",
|
|
834
|
+
"List screened external email waiting for a decision.",
|
|
835
|
+
`${CLI_NAME} inbox email-screening list [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
836
|
+
{
|
|
837
|
+
options: [
|
|
838
|
+
LIMIT_OPTION,
|
|
839
|
+
CURSOR_OPTION,
|
|
840
|
+
CHANNEL_TOKEN_OPTION,
|
|
841
|
+
PROFILE_OPTION,
|
|
842
|
+
JSON_OPTION,
|
|
843
|
+
],
|
|
844
|
+
},
|
|
845
|
+
),
|
|
846
|
+
command(
|
|
847
|
+
"processing",
|
|
848
|
+
"List released external email in the processing queue.",
|
|
849
|
+
`${CLI_NAME} inbox email-screening processing [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
850
|
+
{
|
|
851
|
+
options: [
|
|
852
|
+
LIMIT_OPTION,
|
|
853
|
+
CURSOR_OPTION,
|
|
854
|
+
CHANNEL_TOKEN_OPTION,
|
|
855
|
+
PROFILE_OPTION,
|
|
856
|
+
JSON_OPTION,
|
|
857
|
+
],
|
|
858
|
+
},
|
|
859
|
+
),
|
|
860
|
+
command(
|
|
861
|
+
"approve",
|
|
862
|
+
"Approve this sender and release one screened email.",
|
|
863
|
+
`${CLI_NAME} inbox email-screening approve <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
864
|
+
{
|
|
865
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
866
|
+
},
|
|
867
|
+
),
|
|
868
|
+
command(
|
|
869
|
+
"approve-once",
|
|
870
|
+
"Release one screened email without trusting future mail.",
|
|
871
|
+
`${CLI_NAME} inbox email-screening approve-once <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
872
|
+
{
|
|
873
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
874
|
+
},
|
|
875
|
+
),
|
|
876
|
+
command(
|
|
877
|
+
"ignore",
|
|
878
|
+
"Ignore this sender and suppress future mail.",
|
|
879
|
+
`${CLI_NAME} inbox email-screening ignore <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
880
|
+
{
|
|
881
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
882
|
+
},
|
|
883
|
+
),
|
|
884
|
+
],
|
|
885
|
+
{
|
|
886
|
+
usage: [`${CLI_NAME} inbox email-screening <subcommand>`],
|
|
887
|
+
notes: [
|
|
888
|
+
"Reads allow owner-read tokens or channel tokens.",
|
|
889
|
+
"Decision actions require a master token unless you provide `--channel-token`.",
|
|
890
|
+
],
|
|
891
|
+
},
|
|
892
|
+
),
|
|
814
893
|
],
|
|
815
894
|
{
|
|
816
895
|
usage: [`${CLI_NAME} inbox <subcommand>`],
|
package/src/types/api.ts
CHANGED
|
@@ -44,7 +44,8 @@ export interface JsonApiDocument<TAttributes extends object> {
|
|
|
44
44
|
export type AccessKeyScope = "master" | "read_only";
|
|
45
45
|
|
|
46
46
|
export interface UserAttributes {
|
|
47
|
-
email
|
|
47
|
+
email?: string;
|
|
48
|
+
public_profile_id?: string;
|
|
48
49
|
public_handle?: string | null;
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -118,11 +119,39 @@ export interface MessageAttributes {
|
|
|
118
119
|
body: string;
|
|
119
120
|
sender_owner_id?: string | null;
|
|
120
121
|
sender_channel_id?: string | null;
|
|
122
|
+
external_email_sender_id?: string | null;
|
|
121
123
|
thread_id?: string | null;
|
|
122
124
|
context_post_id?: string | null;
|
|
123
125
|
inserted_at?: string;
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
export interface ExternalEmailIntakeAttributes {
|
|
129
|
+
postmark_message_id?: string | null;
|
|
130
|
+
raw_recipient?: string | null;
|
|
131
|
+
subject?: string | null;
|
|
132
|
+
body?: string | null;
|
|
133
|
+
attachment_count?: number | null;
|
|
134
|
+
attachment_metadata?: Array<Record<string, unknown>> | null;
|
|
135
|
+
attachments_withheld?: boolean | null;
|
|
136
|
+
status?: string | null;
|
|
137
|
+
decision?: string | null;
|
|
138
|
+
received_count?: number | null;
|
|
139
|
+
last_received_at?: string | null;
|
|
140
|
+
released_at?: string | null;
|
|
141
|
+
owner_id?: string | null;
|
|
142
|
+
target_channel_id?: string | null;
|
|
143
|
+
external_email_sender_id?: string | null;
|
|
144
|
+
released_thread_id?: string | null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface MessageAttachmentAttributes {
|
|
148
|
+
name?: string | null;
|
|
149
|
+
content_type?: string | null;
|
|
150
|
+
content_length?: number | null;
|
|
151
|
+
message_id?: string | null;
|
|
152
|
+
inserted_at?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
126
155
|
export interface AccessKeyAttributes {
|
|
127
156
|
expires_at: string;
|
|
128
157
|
scope: AccessKeyScope;
|