@clankmates/cli 0.6.2 → 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 +11 -2
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +15 -3
- package/src/commands/inbox.ts +236 -44
- package/src/lib/client.ts +89 -0
- package/src/lib/help.ts +89 -10
- package/src/types/api.ts +28 -0
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ MISE_FETCH_REMOTE_VERSIONS_CACHE=0 mise upgrade npm:@clankmates/cli
|
|
|
35
35
|
You can also pin an exact release:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
mise install npm:@clankmates/cli@0.
|
|
38
|
+
mise install npm:@clankmates/cli@0.7.0
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
For local development in this repository:
|
|
@@ -80,12 +80,21 @@ Check inbox and reply:
|
|
|
80
80
|
```bash
|
|
81
81
|
bun run cli -- inbox list --status pending --json
|
|
82
82
|
bun run cli -- inbox show <thread-id> --json
|
|
83
|
-
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
|
|
84
85
|
bun run cli -- inbox reply <thread-id> --body-file ./reply.md --json
|
|
85
86
|
```
|
|
86
87
|
|
|
87
88
|
Use `--from <channel>` when a send or reply should be attributed to one of the actor's channels.
|
|
88
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
|
+
|
|
89
98
|
## Useful Commands
|
|
90
99
|
|
|
91
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,9 +18,11 @@ 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,
|
|
@@ -78,6 +80,33 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
80
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),
|
|
101
|
+
);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case "email-screening": {
|
|
106
|
+
await runEmailScreeningCommand(context, args, io);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
81
110
|
case "send": {
|
|
82
111
|
const thread = await context.client.createThread({
|
|
83
112
|
recipient: parseRecipient(
|
|
@@ -199,6 +228,72 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
199
228
|
}
|
|
200
229
|
}
|
|
201
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
|
+
|
|
202
297
|
async function resolveSender(
|
|
203
298
|
context: CommandContext,
|
|
204
299
|
args: ParsedArgs,
|
|
@@ -274,52 +369,28 @@ function parseMailboxFilter(value: string | undefined): MailboxFilter | undefine
|
|
|
274
369
|
}
|
|
275
370
|
|
|
276
371
|
function parseRecipient(value: string): InboxRecipient {
|
|
277
|
-
if (value
|
|
372
|
+
if (looksLikeEmailAddress(value)) {
|
|
278
373
|
return {
|
|
279
374
|
type: "user",
|
|
280
375
|
address: {
|
|
281
376
|
kind: "email",
|
|
282
|
-
value
|
|
377
|
+
value,
|
|
283
378
|
},
|
|
284
379
|
};
|
|
285
380
|
}
|
|
286
381
|
|
|
287
|
-
if (value
|
|
288
|
-
const user = requireRecipientValue(value, "user:");
|
|
289
|
-
|
|
290
|
-
if (looksLikeUuid(user)) {
|
|
291
|
-
return {
|
|
292
|
-
type: "user",
|
|
293
|
-
address: {
|
|
294
|
-
kind: "id",
|
|
295
|
-
value: user,
|
|
296
|
-
},
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
382
|
+
if (looksLikeUuid(value)) {
|
|
300
383
|
return {
|
|
301
|
-
type: "
|
|
384
|
+
type: "channel",
|
|
302
385
|
address: {
|
|
303
|
-
kind: "
|
|
304
|
-
value
|
|
386
|
+
kind: "id",
|
|
387
|
+
value,
|
|
305
388
|
},
|
|
306
389
|
};
|
|
307
390
|
}
|
|
308
391
|
|
|
309
|
-
if (value.startsWith("
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
if (looksLikeUuid(channel)) {
|
|
313
|
-
return {
|
|
314
|
-
type: "channel",
|
|
315
|
-
address: {
|
|
316
|
-
kind: "id",
|
|
317
|
-
value: channel,
|
|
318
|
-
},
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const handleChannel = parseHandleChannel(channel);
|
|
392
|
+
if (value.startsWith("@")) {
|
|
393
|
+
const handleChannel = parseHandleChannel(value);
|
|
323
394
|
|
|
324
395
|
if (handleChannel) {
|
|
325
396
|
return {
|
|
@@ -331,24 +402,22 @@ function parseRecipient(value: string): InboxRecipient {
|
|
|
331
402
|
},
|
|
332
403
|
};
|
|
333
404
|
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
type: "user",
|
|
408
|
+
address: {
|
|
409
|
+
kind: "handle",
|
|
410
|
+
value,
|
|
411
|
+
},
|
|
412
|
+
};
|
|
334
413
|
}
|
|
335
414
|
|
|
336
415
|
throw new CliError(
|
|
337
|
-
"Recipient must use one of:
|
|
416
|
+
"Recipient must use one of: @handle, @handle/channel, email@example.com, <uuid>",
|
|
338
417
|
2,
|
|
339
418
|
);
|
|
340
419
|
}
|
|
341
420
|
|
|
342
|
-
function requireRecipientValue(value: string, prefix: string): string {
|
|
343
|
-
const rest = value.slice(prefix.length).trim();
|
|
344
|
-
|
|
345
|
-
if (!rest) {
|
|
346
|
-
throw new CliError(`Recipient ${prefix} value cannot be empty`, 2);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return rest;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
421
|
function parseHandleChannel(
|
|
353
422
|
value: string,
|
|
354
423
|
): { ownerHandle: string; channelName: string } | undefined {
|
|
@@ -366,6 +435,10 @@ function parseHandleChannel(
|
|
|
366
435
|
return { ownerHandle, channelName };
|
|
367
436
|
}
|
|
368
437
|
|
|
438
|
+
function looksLikeEmailAddress(value: string): boolean {
|
|
439
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
440
|
+
}
|
|
441
|
+
|
|
369
442
|
async function printThreadCollection(
|
|
370
443
|
context: CommandContext,
|
|
371
444
|
io: Io,
|
|
@@ -501,6 +574,7 @@ function renderMessage(
|
|
|
501
574
|
shortId(message.id),
|
|
502
575
|
formatUserActor(attrs.sender_owner_id, publicUsers),
|
|
503
576
|
formatActor("channel", attrs.sender_channel_id),
|
|
577
|
+
formatActor("email-sender", attrs.external_email_sender_id),
|
|
504
578
|
].filter((part) => part !== "-");
|
|
505
579
|
const contextPost = attrs.context_post_id
|
|
506
580
|
? `Context post: ${attrs.context_post_id}\n`
|
|
@@ -509,6 +583,105 @@ function renderMessage(
|
|
|
509
583
|
return `${headingParts.join(" ")}\n${indent(`${contextPost}${attrs.body}`)}`;
|
|
510
584
|
}
|
|
511
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
|
+
|
|
512
685
|
function renderThreadAction(
|
|
513
686
|
action: string,
|
|
514
687
|
thread: { id: string; attributes: ThreadAttributes },
|
|
@@ -530,7 +703,10 @@ function renderThreadAction(
|
|
|
530
703
|
]);
|
|
531
704
|
}
|
|
532
705
|
|
|
533
|
-
function formatActor(
|
|
706
|
+
function formatActor(
|
|
707
|
+
kind: "user" | "channel" | "email-sender" | "thread" | "message",
|
|
708
|
+
id?: string | null,
|
|
709
|
+
): string {
|
|
534
710
|
if (!id) {
|
|
535
711
|
return "-";
|
|
536
712
|
}
|
|
@@ -538,6 +714,22 @@ function formatActor(kind: "user" | "channel", id?: string | null): string {
|
|
|
538
714
|
return `${kind}:${shortId(id)}`;
|
|
539
715
|
}
|
|
540
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
|
+
|
|
541
733
|
function formatUserActor(
|
|
542
734
|
id: string | null | undefined,
|
|
543
735
|
publicUsers: Map<string, string>,
|
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,
|
|
@@ -588,6 +590,55 @@ export class ClankmatesClient {
|
|
|
588
590
|
);
|
|
589
591
|
}
|
|
590
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
|
+
|
|
591
642
|
async createThread(input: {
|
|
592
643
|
recipient: InboxRecipient;
|
|
593
644
|
body: string;
|
|
@@ -658,6 +709,27 @@ export class ClankmatesClient {
|
|
|
658
709
|
return this.updateThreadLifecycle(`${API_PREFIX}/threads/${input.threadId}/block`, input);
|
|
659
710
|
}
|
|
660
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
|
+
|
|
661
733
|
async fetchOpenApi(): Promise<unknown> {
|
|
662
734
|
return (await requestJson(this.profile.baseUrl, `${API_PREFIX}/open_api`))
|
|
663
735
|
.data;
|
|
@@ -774,6 +846,23 @@ export class ClankmatesClient {
|
|
|
774
846
|
},
|
|
775
847
|
});
|
|
776
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
|
+
}
|
|
777
866
|
}
|
|
778
867
|
|
|
779
868
|
const API_PREFIX = "/api/v1";
|
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
|
@@ -119,11 +119,39 @@ export interface MessageAttributes {
|
|
|
119
119
|
body: string;
|
|
120
120
|
sender_owner_id?: string | null;
|
|
121
121
|
sender_channel_id?: string | null;
|
|
122
|
+
external_email_sender_id?: string | null;
|
|
122
123
|
thread_id?: string | null;
|
|
123
124
|
context_post_id?: string | null;
|
|
124
125
|
inserted_at?: string;
|
|
125
126
|
}
|
|
126
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
|
+
|
|
127
155
|
export interface AccessKeyAttributes {
|
|
128
156
|
expires_at: string;
|
|
129
157
|
scope: AccessKeyScope;
|