@clankmates/cli 0.12.0 → 0.13.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 +4 -1
- package/package.json +1 -1
- package/src/commands/auth/access-keys.ts +206 -0
- package/src/commands/auth.ts +3 -196
- package/src/commands/channel/render.ts +224 -0
- package/src/commands/channel/tokens.ts +145 -0
- package/src/commands/channel/validation.ts +11 -0
- package/src/commands/channel.ts +11 -340
- package/src/commands/doctor/checks.ts +123 -0
- package/src/commands/doctor/render.ts +140 -0
- package/src/commands/doctor/suggestions.ts +42 -0
- package/src/commands/doctor/types.ts +75 -0
- package/src/commands/doctor.ts +12 -371
- package/src/commands/feed.ts +15 -178
- package/src/commands/inbox/content.ts +31 -0
- package/src/commands/inbox/filters.ts +70 -0
- package/src/commands/inbox/messages.ts +69 -0
- package/src/commands/inbox/participants.ts +152 -0
- package/src/commands/inbox/render.ts +13 -0
- package/src/commands/inbox/resource-output.ts +217 -0
- package/src/commands/inbox/schema.ts +185 -0
- package/src/commands/inbox/screening.ts +76 -0
- package/src/commands/inbox/sync-scopes.ts +59 -0
- package/src/commands/inbox/thread-output.ts +344 -0
- package/src/commands/inbox/watch.ts +203 -0
- package/src/commands/inbox.ts +37 -1243
- package/src/commands/post.ts +9 -114
- package/src/lib/args.ts +1 -0
- package/src/lib/cache/scopes.ts +216 -0
- package/src/lib/cache/store.ts +195 -0
- package/src/lib/cache/types.ts +31 -0
- package/src/lib/cache.ts +18 -436
- package/src/lib/client/auth.ts +122 -0
- package/src/lib/client/channel-keys.ts +57 -0
- package/src/lib/client/channels.ts +364 -0
- package/src/lib/client/core.ts +133 -0
- package/src/lib/client/feed.ts +76 -0
- package/src/lib/client/inbox.ts +361 -0
- package/src/lib/client/posts.ts +213 -0
- package/src/lib/client/raw-api.ts +33 -0
- package/src/lib/client/users.ts +88 -0
- package/src/lib/client.ts +177 -913
- package/src/lib/help.ts +26 -0
- package/src/lib/json_api.ts +74 -9
- package/src/lib/polling.ts +146 -0
- package/src/lib/post-output.ts +55 -0
package/src/commands/inbox.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertSinceFlags,
|
|
3
|
-
authenticatedActorKey,
|
|
4
|
-
cacheFlags,
|
|
5
3
|
cacheResult,
|
|
6
4
|
changeResponseMeta,
|
|
7
|
-
inboxMessagesScope,
|
|
8
|
-
inboxThreadsScope,
|
|
9
|
-
prepareCachePlan,
|
|
10
|
-
saveCacheTimestamp,
|
|
11
|
-
type CachePlan,
|
|
12
|
-
type CacheResult,
|
|
13
|
-
type CacheScope,
|
|
14
5
|
} from "../lib/cache";
|
|
15
6
|
import {
|
|
16
7
|
booleanFlag,
|
|
@@ -19,35 +10,45 @@ import {
|
|
|
19
10
|
stringFlag,
|
|
20
11
|
type ParsedArgs,
|
|
21
12
|
} from "../lib/args";
|
|
22
|
-
import { resolveBodyInput } from "../lib/body-input";
|
|
23
13
|
import { createCommandContext, type CommandContext } from "../lib/context";
|
|
24
14
|
import { CliError } from "../lib/errors";
|
|
15
|
+
import { printValue, type Io } from "../lib/output";
|
|
16
|
+
import { paginatedJson } from "../lib/pagination";
|
|
25
17
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} from "
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
maybePrepareCachePlan,
|
|
19
|
+
maybeSaveCacheTimestamp,
|
|
20
|
+
parseLatestFirstOrder,
|
|
21
|
+
printChangeCheckResponse,
|
|
22
|
+
requiredSince,
|
|
23
|
+
resolvedSince,
|
|
24
|
+
} from "../lib/polling";
|
|
25
|
+
import { resolveMessageContent } from "./inbox/content";
|
|
26
|
+
import {
|
|
27
|
+
parseMailboxFilter,
|
|
28
|
+
parseParticipantScope,
|
|
29
|
+
parseStatusFilter,
|
|
30
|
+
} from "./inbox/filters";
|
|
31
|
+
import {
|
|
32
|
+
parseRecipient,
|
|
33
|
+
resolveSender,
|
|
34
|
+
} from "./inbox/participants";
|
|
35
|
+
import {
|
|
36
|
+
ownerIdsForThreadDisplay,
|
|
37
|
+
publicUserHandlesById,
|
|
38
|
+
printThreadCollection,
|
|
39
|
+
renderAttachmentCollection,
|
|
40
|
+
renderThreadAction,
|
|
41
|
+
renderThreadWithMessages,
|
|
42
|
+
} from "./inbox/render";
|
|
43
|
+
import { runInboxMessagesCommand } from "./inbox/messages";
|
|
44
|
+
import { runSchemaCommand } from "./inbox/schema";
|
|
45
|
+
import { runScreeningCommand } from "./inbox/screening";
|
|
46
|
+
import { maybeInboxMessagesScope, maybeInboxThreadsScope } from "./inbox/sync-scopes";
|
|
47
|
+
import { runInboxWatchCommand } from "./inbox/watch";
|
|
37
48
|
import type {
|
|
38
|
-
ChangeCheckResponse,
|
|
39
|
-
ExternalEmailAcceptance,
|
|
40
|
-
ExternalEmailIntakeAttributes,
|
|
41
|
-
InboxRecipient,
|
|
42
|
-
InboxSender,
|
|
43
|
-
LatestFirstOrder,
|
|
44
49
|
MailboxFilter,
|
|
45
|
-
MessageAttachmentAttributes,
|
|
46
|
-
MessageAttributes,
|
|
47
50
|
ParticipantScope,
|
|
48
|
-
ThreadAttributes,
|
|
49
51
|
ThreadStatusFilter,
|
|
50
|
-
WhoamiActor,
|
|
51
52
|
} from "../types/api";
|
|
52
53
|
|
|
53
54
|
export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
@@ -204,6 +205,11 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
204
205
|
return;
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
case "watch": {
|
|
209
|
+
await runInboxWatchCommand(context, args, io);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
207
213
|
case "messages": {
|
|
208
214
|
await runInboxMessagesCommand(context, args, io);
|
|
209
215
|
return;
|
|
@@ -360,1215 +366,3 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
360
366
|
throw new CliError("Unknown inbox subcommand", 2);
|
|
361
367
|
}
|
|
362
368
|
}
|
|
363
|
-
|
|
364
|
-
async function resolveMessageContent(args: ParsedArgs): Promise<{
|
|
365
|
-
body?: string;
|
|
366
|
-
payload?: Record<string, unknown>;
|
|
367
|
-
}> {
|
|
368
|
-
if (booleanFlag(args.flags, "stdin") && booleanFlag(args.flags, "payloadStdin")) {
|
|
369
|
-
throw new CliError("Use only one of `--stdin` or `--payload-stdin`", 2);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const body = await resolveBodyInput({ flags: args.flags });
|
|
373
|
-
const payload = await resolveJsonInput({
|
|
374
|
-
flags: args.flags,
|
|
375
|
-
inlineKey: "payload",
|
|
376
|
-
fileKey: "payloadFile",
|
|
377
|
-
stdinKey: "payloadStdin",
|
|
378
|
-
label: "Payload",
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
if (body === undefined && payload === undefined) {
|
|
382
|
-
throw new CliError(
|
|
383
|
-
"Provide at least one of `--body`, `--body-file`, `--stdin`, `--payload`, `--payload-file`, or `--payload-stdin`",
|
|
384
|
-
2,
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return { body, payload };
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
async function runSchemaCommand(
|
|
392
|
-
context: CommandContext,
|
|
393
|
-
args: ParsedArgs,
|
|
394
|
-
io: Io,
|
|
395
|
-
): Promise<void> {
|
|
396
|
-
const subcommand = args.positionals[1];
|
|
397
|
-
|
|
398
|
-
switch (subcommand) {
|
|
399
|
-
case "show": {
|
|
400
|
-
const target = requiredPositional(
|
|
401
|
-
args.positionals,
|
|
402
|
-
2,
|
|
403
|
-
"Missing schema target",
|
|
404
|
-
);
|
|
405
|
-
const schema = await getPublicInboxSchema(context, target);
|
|
406
|
-
printSchemaResource(context, io, schema);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
case "set": {
|
|
411
|
-
const scope = requiredPositional(
|
|
412
|
-
args.positionals,
|
|
413
|
-
2,
|
|
414
|
-
"Missing schema scope",
|
|
415
|
-
);
|
|
416
|
-
const inboxSchema = await requiredInboxSchema(args);
|
|
417
|
-
|
|
418
|
-
if (scope === "account") {
|
|
419
|
-
printSchemaResource(
|
|
420
|
-
context,
|
|
421
|
-
io,
|
|
422
|
-
await context.client.setAccountInboxSchema(inboxSchema),
|
|
423
|
-
"Updated account inbox schema",
|
|
424
|
-
);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (scope === "channel") {
|
|
429
|
-
const channelRef = requiredPositional(
|
|
430
|
-
args.positionals,
|
|
431
|
-
3,
|
|
432
|
-
"Missing channel name or id",
|
|
433
|
-
);
|
|
434
|
-
const channelId = await context.client.resolveChannelId(channelRef);
|
|
435
|
-
printSchemaResource(
|
|
436
|
-
context,
|
|
437
|
-
io,
|
|
438
|
-
await context.client.setChannelInboxSchema({
|
|
439
|
-
channelId,
|
|
440
|
-
inboxSchema,
|
|
441
|
-
}),
|
|
442
|
-
"Updated channel inbox schema",
|
|
443
|
-
);
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
throw new CliError("Schema scope must be `account` or `channel`", 2);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
case "remove": {
|
|
451
|
-
const scope = requiredPositional(
|
|
452
|
-
args.positionals,
|
|
453
|
-
2,
|
|
454
|
-
"Missing schema scope",
|
|
455
|
-
);
|
|
456
|
-
|
|
457
|
-
if (scope === "account") {
|
|
458
|
-
printSchemaResource(
|
|
459
|
-
context,
|
|
460
|
-
io,
|
|
461
|
-
await context.client.removeAccountInboxSchema(),
|
|
462
|
-
"Removed account inbox schema",
|
|
463
|
-
);
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (scope === "channel") {
|
|
468
|
-
const channelRef = requiredPositional(
|
|
469
|
-
args.positionals,
|
|
470
|
-
3,
|
|
471
|
-
"Missing channel name or id",
|
|
472
|
-
);
|
|
473
|
-
const channelId = await context.client.resolveChannelId(channelRef);
|
|
474
|
-
printSchemaResource(
|
|
475
|
-
context,
|
|
476
|
-
io,
|
|
477
|
-
await context.client.removeChannelInboxSchema(channelId),
|
|
478
|
-
"Removed channel inbox schema",
|
|
479
|
-
);
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
throw new CliError("Schema scope must be `account` or `channel`", 2);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
case "acceptance": {
|
|
487
|
-
const scope = requiredPositional(
|
|
488
|
-
args.positionals,
|
|
489
|
-
2,
|
|
490
|
-
"Missing schema acceptance scope",
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
if (scope === "account") {
|
|
494
|
-
const externalEmailAcceptance = parseExternalEmailAcceptance(
|
|
495
|
-
requiredPositional(
|
|
496
|
-
args.positionals,
|
|
497
|
-
3,
|
|
498
|
-
"Missing external email acceptance policy",
|
|
499
|
-
),
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
printSchemaResource(
|
|
503
|
-
context,
|
|
504
|
-
io,
|
|
505
|
-
await context.client.setAccountExternalEmailAcceptance(
|
|
506
|
-
externalEmailAcceptance,
|
|
507
|
-
),
|
|
508
|
-
"Updated account inbox acceptance",
|
|
509
|
-
);
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
if (scope === "channel") {
|
|
514
|
-
const channelRef = requiredPositional(
|
|
515
|
-
args.positionals,
|
|
516
|
-
3,
|
|
517
|
-
"Missing channel name or id",
|
|
518
|
-
);
|
|
519
|
-
const externalEmailAcceptance = parseExternalEmailAcceptance(
|
|
520
|
-
requiredPositional(
|
|
521
|
-
args.positionals,
|
|
522
|
-
4,
|
|
523
|
-
"Missing external email acceptance policy",
|
|
524
|
-
),
|
|
525
|
-
);
|
|
526
|
-
const channelId = await context.client.resolveChannelId(channelRef);
|
|
527
|
-
printSchemaResource(
|
|
528
|
-
context,
|
|
529
|
-
io,
|
|
530
|
-
await context.client.setChannelExternalEmailAcceptance({
|
|
531
|
-
channelId,
|
|
532
|
-
externalEmailAcceptance,
|
|
533
|
-
}),
|
|
534
|
-
"Updated channel inbox acceptance",
|
|
535
|
-
);
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
throw new CliError("Schema acceptance scope must be `account` or `channel`", 2);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
default:
|
|
543
|
-
throw new CliError("Unknown inbox schema subcommand", 2);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
async function requiredInboxSchema(
|
|
548
|
-
args: ParsedArgs,
|
|
549
|
-
): Promise<Record<string, unknown>> {
|
|
550
|
-
const schema = await resolveJsonInput({
|
|
551
|
-
flags: args.flags,
|
|
552
|
-
inlineKey: "schema",
|
|
553
|
-
fileKey: "schemaFile",
|
|
554
|
-
stdinKey: "schemaStdin",
|
|
555
|
-
label: "Inbox schema",
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
if (!schema) {
|
|
559
|
-
throw new CliError(
|
|
560
|
-
"Provide exactly one of `--schema`, `--schema-file`, or `--schema-stdin`",
|
|
561
|
-
2,
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
return schema;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
function parseExternalEmailAcceptance(value: string): ExternalEmailAcceptance {
|
|
569
|
-
if (
|
|
570
|
-
value === "screen_unknown_senders" ||
|
|
571
|
-
value === "screen-unknown-senders"
|
|
572
|
-
) {
|
|
573
|
-
return "screen_unknown_senders";
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
value === "accept_valid_typed_email" ||
|
|
578
|
-
value === "accept-valid-typed-email"
|
|
579
|
-
) {
|
|
580
|
-
return "accept_valid_typed_email";
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
throw new CliError(
|
|
584
|
-
"External email acceptance policy must be one of: screen-unknown-senders, accept-valid-typed-email",
|
|
585
|
-
2,
|
|
586
|
-
);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
async function runScreeningCommand(
|
|
590
|
-
context: CommandContext,
|
|
591
|
-
args: ParsedArgs,
|
|
592
|
-
io: Io,
|
|
593
|
-
): Promise<void> {
|
|
594
|
-
const subcommand = args.positionals[1];
|
|
595
|
-
const channelToken = stringFlag(args.flags, "channelToken");
|
|
596
|
-
|
|
597
|
-
switch (subcommand) {
|
|
598
|
-
case "list": {
|
|
599
|
-
const response = await context.client.listEmailScreeningIntakes({
|
|
600
|
-
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
601
|
-
cursor: stringFlag(args.flags, "cursor"),
|
|
602
|
-
channelToken,
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
printEmailIntakeCollection(args, context, io, response);
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
case "processing": {
|
|
610
|
-
const response = await context.client.listEmailProcessingIntakes({
|
|
611
|
-
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
612
|
-
cursor: stringFlag(args.flags, "cursor"),
|
|
613
|
-
channelToken,
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
printEmailIntakeCollection(args, context, io, response);
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
case "approve": {
|
|
621
|
-
const intake = await context.client.approveEmailIntake({
|
|
622
|
-
intakeId: requiredPositional(args.positionals, 2, "Missing intake id"),
|
|
623
|
-
channelToken,
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
printEmailIntakeAction(context, io, "Approved email intake", intake);
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
case "approve-once": {
|
|
631
|
-
const intake = await context.client.approveEmailIntakeOnce({
|
|
632
|
-
intakeId: requiredPositional(args.positionals, 2, "Missing intake id"),
|
|
633
|
-
channelToken,
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
printEmailIntakeAction(context, io, "Approved email intake once", intake);
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
case "ignore": {
|
|
641
|
-
const intake = await context.client.ignoreEmailIntake({
|
|
642
|
-
intakeId: requiredPositional(args.positionals, 2, "Missing intake id"),
|
|
643
|
-
channelToken,
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
printEmailIntakeAction(context, io, "Ignored email intake", intake);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
default:
|
|
651
|
-
throw new CliError("Unknown inbox screening subcommand", 2);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
async function resolveSender(
|
|
656
|
-
context: CommandContext,
|
|
657
|
-
args: ParsedArgs,
|
|
658
|
-
): Promise<InboxSender | undefined> {
|
|
659
|
-
const from = stringFlag(args.flags, "from");
|
|
660
|
-
const channelToken = stringFlag(args.flags, "channelToken");
|
|
661
|
-
|
|
662
|
-
if (!from) {
|
|
663
|
-
return undefined;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
if (looksLikeUuid(from)) {
|
|
667
|
-
return channelSender(from);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
if (channelToken) {
|
|
671
|
-
const whoami = await context.client.whoami(channelToken);
|
|
672
|
-
|
|
673
|
-
if (whoami.actor.type === "channel") {
|
|
674
|
-
if (whoami.actor.name === from) {
|
|
675
|
-
return channelSender(whoami.actor.id);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
throw new CliError(
|
|
679
|
-
"With `--channel-token`, `--from` must match the authenticated channel name or UUID.",
|
|
680
|
-
2,
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
return channelSender(await context.client.resolveChannelId(from));
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const UUID_PATTERN =
|
|
689
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
690
|
-
|
|
691
|
-
function looksLikeUuid(value: string): boolean {
|
|
692
|
-
return UUID_PATTERN.test(value);
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function channelSender(channelId: string): InboxSender {
|
|
696
|
-
return {
|
|
697
|
-
type: "channel",
|
|
698
|
-
address: {
|
|
699
|
-
kind: "id",
|
|
700
|
-
value: channelId,
|
|
701
|
-
},
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
function parseStatusFilter(value: string | undefined): ThreadStatusFilter | undefined {
|
|
706
|
-
if (!value) {
|
|
707
|
-
return undefined;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (value === "pending" || value === "open" || value === "blocked" || value === "all") {
|
|
711
|
-
return value;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
throw new CliError("--status must be one of: pending, open, blocked, all", 2);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
function parseMailboxFilter(value: string | undefined): MailboxFilter | undefined {
|
|
718
|
-
if (!value) {
|
|
719
|
-
return undefined;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (value === "account" || value === "channel" || value === "all") {
|
|
723
|
-
return value;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
throw new CliError("--mailbox must be one of: account, channel, all", 2);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function parseParticipantScope(value: string | undefined): ParticipantScope | undefined {
|
|
730
|
-
if (!value) {
|
|
731
|
-
return undefined;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
if (value === "account" || value === "owner") {
|
|
735
|
-
return value;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
throw new CliError("--participant-scope must be one of: account, owner", 2);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
function parseLatestFirstOrder(value: string | undefined): LatestFirstOrder | undefined {
|
|
742
|
-
if (!value) {
|
|
743
|
-
return undefined;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
if (value === "latest" || value === "oldest") {
|
|
747
|
-
return value;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
throw new CliError("--order must be one of: latest, oldest", 2);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
function requiredSince(
|
|
754
|
-
args: ParsedArgs,
|
|
755
|
-
cachePlan?: CachePlan,
|
|
756
|
-
label = "resource",
|
|
757
|
-
): string {
|
|
758
|
-
const since = stringFlag(args.flags, "since");
|
|
759
|
-
|
|
760
|
-
if (since) {
|
|
761
|
-
return since;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
if (cachePlan?.previousServerTimestamp) {
|
|
765
|
-
return cachePlan.previousServerTimestamp;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
if (cacheFlags(args).sinceCache) {
|
|
769
|
-
throw new CliError(
|
|
770
|
-
`No cached server timestamp for this ${label} scope. Run a read command with \`--save-cache\` first.`,
|
|
771
|
-
2,
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
if (!since) {
|
|
776
|
-
throw new CliError("Missing `--since`", 2);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
return since;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
async function runInboxMessagesCommand(
|
|
783
|
-
context: CommandContext,
|
|
784
|
-
args: ParsedArgs,
|
|
785
|
-
io: Io,
|
|
786
|
-
): Promise<void> {
|
|
787
|
-
const subcommand = args.positionals[1];
|
|
788
|
-
|
|
789
|
-
switch (subcommand) {
|
|
790
|
-
case "changes": {
|
|
791
|
-
assertSinceFlags(args);
|
|
792
|
-
const threadId = requiredPositional(args.positionals, 2, "Missing thread id");
|
|
793
|
-
const channelToken = stringFlag(args.flags, "channelToken");
|
|
794
|
-
const cacheScope = await maybeInboxMessagesScope(
|
|
795
|
-
args,
|
|
796
|
-
context,
|
|
797
|
-
channelToken,
|
|
798
|
-
threadId,
|
|
799
|
-
);
|
|
800
|
-
const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
|
|
801
|
-
const response = await context.client.checkThreadMessageChanges({
|
|
802
|
-
threadId,
|
|
803
|
-
since: requiredSince(args, cachePlan, "inbox message"),
|
|
804
|
-
query: stringFlag(args.flags, "query"),
|
|
805
|
-
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
806
|
-
channelToken,
|
|
807
|
-
});
|
|
808
|
-
const savedServerTimestamp = await maybeSaveCacheTimestamp(
|
|
809
|
-
args,
|
|
810
|
-
context,
|
|
811
|
-
cacheScope,
|
|
812
|
-
changeResponseMeta(response),
|
|
813
|
-
response.has_updates === false,
|
|
814
|
-
);
|
|
815
|
-
|
|
816
|
-
printChangeCheckResponse(
|
|
817
|
-
context,
|
|
818
|
-
io,
|
|
819
|
-
response,
|
|
820
|
-
cacheResult(cachePlan, savedServerTimestamp),
|
|
821
|
-
);
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
default:
|
|
826
|
-
throw new CliError("Unknown inbox messages subcommand", 2);
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
function printChangeCheckResponse(
|
|
831
|
-
context: CommandContext,
|
|
832
|
-
io: Io,
|
|
833
|
-
response: ChangeCheckResponse,
|
|
834
|
-
cache?: CacheResult,
|
|
835
|
-
): void {
|
|
836
|
-
printValue(
|
|
837
|
-
io,
|
|
838
|
-
context.outputMode,
|
|
839
|
-
context.outputMode === "json"
|
|
840
|
-
? { ...response, ...(cache ? { cache } : {}) }
|
|
841
|
-
: renderFields([
|
|
842
|
-
["Has updates", response.has_updates ? "yes" : "no"],
|
|
843
|
-
["Server time", formatTimestamp(response.server_time)],
|
|
844
|
-
[
|
|
845
|
-
"Recommended poll",
|
|
846
|
-
response.recommended_poll_after_ms === undefined
|
|
847
|
-
? undefined
|
|
848
|
-
: `${response.recommended_poll_after_ms}ms`,
|
|
849
|
-
],
|
|
850
|
-
...cacheFields(cache),
|
|
851
|
-
]),
|
|
852
|
-
);
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
async function parseRecipient(
|
|
856
|
-
context: CommandContext,
|
|
857
|
-
value: string,
|
|
858
|
-
): Promise<InboxRecipient> {
|
|
859
|
-
if (looksLikeUuid(value)) {
|
|
860
|
-
if (await publicUserExists(context, value)) {
|
|
861
|
-
return {
|
|
862
|
-
type: "user",
|
|
863
|
-
address: {
|
|
864
|
-
kind: "id",
|
|
865
|
-
value,
|
|
866
|
-
},
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
return {
|
|
871
|
-
type: "channel",
|
|
872
|
-
address: {
|
|
873
|
-
kind: "id",
|
|
874
|
-
value,
|
|
875
|
-
},
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
if (value.startsWith("@")) {
|
|
880
|
-
const handleChannel = parseHandleChannel(value);
|
|
881
|
-
|
|
882
|
-
if (handleChannel) {
|
|
883
|
-
return {
|
|
884
|
-
type: "channel",
|
|
885
|
-
address: {
|
|
886
|
-
kind: "owner_handle_and_channel_name",
|
|
887
|
-
owner_handle: handleChannel.ownerHandle,
|
|
888
|
-
channel_name: handleChannel.channelName,
|
|
889
|
-
},
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
return {
|
|
894
|
-
type: "user",
|
|
895
|
-
address: {
|
|
896
|
-
kind: "handle",
|
|
897
|
-
value,
|
|
898
|
-
},
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
throw new CliError(
|
|
903
|
-
"Recipient must use one of: @handle, @handle/channel, user UUID, or channel UUID",
|
|
904
|
-
2,
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
async function publicUserExists(
|
|
909
|
-
context: CommandContext,
|
|
910
|
-
id: string,
|
|
911
|
-
): Promise<boolean> {
|
|
912
|
-
const response = await context.client.listPublicUsersById([id]);
|
|
913
|
-
return response.items.some((user) => user.id === id);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function parseHandleChannel(
|
|
917
|
-
value: string,
|
|
918
|
-
): { ownerHandle: string; channelName: string } | undefined {
|
|
919
|
-
const [ownerHandle, channelName, ...extra] = value.split("/");
|
|
920
|
-
|
|
921
|
-
if (
|
|
922
|
-
extra.length > 0 ||
|
|
923
|
-
!ownerHandle ||
|
|
924
|
-
!channelName ||
|
|
925
|
-
!ownerHandle.startsWith("@")
|
|
926
|
-
) {
|
|
927
|
-
return undefined;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
return { ownerHandle, channelName };
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
async function getPublicInboxSchema(
|
|
934
|
-
context: CommandContext,
|
|
935
|
-
target: string,
|
|
936
|
-
) {
|
|
937
|
-
const handleChannel = parseHandleChannel(target);
|
|
938
|
-
|
|
939
|
-
if (handleChannel) {
|
|
940
|
-
return context.client.getPublicChannelInboxSchema(
|
|
941
|
-
handleChannel.ownerHandle,
|
|
942
|
-
handleChannel.channelName,
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
if (target.startsWith("@")) {
|
|
947
|
-
return context.client.getPublicAccountInboxSchema(target);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
throw new CliError("Schema target must be `@handle` or `@handle/channel`", 2);
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
async function printThreadCollection(
|
|
954
|
-
args: ParsedArgs,
|
|
955
|
-
context: CommandContext,
|
|
956
|
-
io: Io,
|
|
957
|
-
response: {
|
|
958
|
-
items: Array<{ id: string; attributes: ThreadAttributes }>;
|
|
959
|
-
nextCursor?: string;
|
|
960
|
-
meta?: Record<string, unknown>;
|
|
961
|
-
},
|
|
962
|
-
channelToken?: string,
|
|
963
|
-
cache?: CacheResult,
|
|
964
|
-
): Promise<void> {
|
|
965
|
-
if (context.outputMode === "json") {
|
|
966
|
-
printJson(
|
|
967
|
-
io,
|
|
968
|
-
paginatedJson(args, {
|
|
969
|
-
items: response.items,
|
|
970
|
-
nextCursor: response.nextCursor,
|
|
971
|
-
meta: response.meta,
|
|
972
|
-
...(cache ? { cache } : {}),
|
|
973
|
-
}),
|
|
974
|
-
);
|
|
975
|
-
return Promise.resolve();
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
const actor = (await context.client.whoami(channelToken)).actor;
|
|
979
|
-
const peers = response.items.map((item) => threadPeer(item.attributes, actor));
|
|
980
|
-
const ownerIds = ownerIdsForThreadList(peers);
|
|
981
|
-
const publicUsers =
|
|
982
|
-
ownerIds.length === 0
|
|
983
|
-
? new Map<string, string>()
|
|
984
|
-
: publicUserHandlesById(
|
|
985
|
-
(await context.client.listPublicUsersById(ownerIds)).items,
|
|
986
|
-
);
|
|
987
|
-
|
|
988
|
-
printValue(
|
|
989
|
-
io,
|
|
990
|
-
context.outputMode,
|
|
991
|
-
response.items.map((item, index) => ({
|
|
992
|
-
id: item.id,
|
|
993
|
-
with: formatThreadPeer(peers[index], publicUsers),
|
|
994
|
-
mailboxType: item.attributes.mailbox_type,
|
|
995
|
-
status: item.attributes.status,
|
|
996
|
-
lastMessageAt: item.attributes.last_message_at ?? "",
|
|
997
|
-
openedAt: item.attributes.opened_at ?? "",
|
|
998
|
-
expiresAt: item.attributes.expires_at ?? "",
|
|
999
|
-
})),
|
|
1000
|
-
);
|
|
1001
|
-
const pagination = paginationInfo(args, response.nextCursor);
|
|
1002
|
-
const message = renderPagination(
|
|
1003
|
-
pagination?.nextCursor,
|
|
1004
|
-
pagination?.nextCommand,
|
|
1005
|
-
);
|
|
1006
|
-
|
|
1007
|
-
if (message) {
|
|
1008
|
-
io.stdout(message);
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
printCacheNote(io, cache);
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
function renderThreadWithMessages(
|
|
1015
|
-
args: ParsedArgs,
|
|
1016
|
-
thread: { id: string; attributes: ThreadAttributes },
|
|
1017
|
-
messages: {
|
|
1018
|
-
items: Array<{ id: string; attributes: MessageAttributes }>;
|
|
1019
|
-
nextCursor?: string;
|
|
1020
|
-
},
|
|
1021
|
-
publicUsers: Map<string, string>,
|
|
1022
|
-
cache?: CacheResult,
|
|
1023
|
-
): string {
|
|
1024
|
-
const attrs = thread.attributes;
|
|
1025
|
-
const messageBlocks =
|
|
1026
|
-
messages.items.length === 0
|
|
1027
|
-
? "No messages."
|
|
1028
|
-
: messages.items
|
|
1029
|
-
.map((message) => renderMessage(message, publicUsers))
|
|
1030
|
-
.join("\n\n");
|
|
1031
|
-
const pagination = paginationInfo(args, messages.nextCursor);
|
|
1032
|
-
|
|
1033
|
-
return joinBlocks([
|
|
1034
|
-
`Thread ${thread.id}`,
|
|
1035
|
-
renderFields([
|
|
1036
|
-
["Mailbox", attrs.mailbox_type],
|
|
1037
|
-
["Status", attrs.status],
|
|
1038
|
-
["Last message", formatTimestamp(attrs.last_message_at)],
|
|
1039
|
-
["Opened", formatTimestamp(attrs.opened_at)],
|
|
1040
|
-
[
|
|
1041
|
-
"Expires",
|
|
1042
|
-
attrs.expires_at ? formatTimestamp(attrs.expires_at) : undefined,
|
|
1043
|
-
],
|
|
1044
|
-
]),
|
|
1045
|
-
renderSection(
|
|
1046
|
-
"Participants",
|
|
1047
|
-
renderFields([
|
|
1048
|
-
["A owner", formatUserActor(attrs.participant_a_owner_id, publicUsers)],
|
|
1049
|
-
["A channel", formatActor("channel", attrs.participant_a_channel_id)],
|
|
1050
|
-
["A seen", formatTimestamp(attrs.participant_a_seen_at)],
|
|
1051
|
-
[
|
|
1052
|
-
"A archived",
|
|
1053
|
-
attrs.participant_a_archived_at
|
|
1054
|
-
? formatTimestamp(attrs.participant_a_archived_at)
|
|
1055
|
-
: undefined,
|
|
1056
|
-
],
|
|
1057
|
-
[
|
|
1058
|
-
"A blocked",
|
|
1059
|
-
attrs.participant_a_blocked_at
|
|
1060
|
-
? formatTimestamp(attrs.participant_a_blocked_at)
|
|
1061
|
-
: undefined,
|
|
1062
|
-
],
|
|
1063
|
-
[
|
|
1064
|
-
"A resolved",
|
|
1065
|
-
attrs.participant_a_resolved_at
|
|
1066
|
-
? formatTimestamp(attrs.participant_a_resolved_at)
|
|
1067
|
-
: undefined,
|
|
1068
|
-
],
|
|
1069
|
-
["B owner", formatUserActor(attrs.participant_b_owner_id, publicUsers)],
|
|
1070
|
-
["B channel", formatActor("channel", attrs.participant_b_channel_id)],
|
|
1071
|
-
["B seen", formatTimestamp(attrs.participant_b_seen_at)],
|
|
1072
|
-
[
|
|
1073
|
-
"B archived",
|
|
1074
|
-
attrs.participant_b_archived_at
|
|
1075
|
-
? formatTimestamp(attrs.participant_b_archived_at)
|
|
1076
|
-
: undefined,
|
|
1077
|
-
],
|
|
1078
|
-
[
|
|
1079
|
-
"B blocked",
|
|
1080
|
-
attrs.participant_b_blocked_at
|
|
1081
|
-
? formatTimestamp(attrs.participant_b_blocked_at)
|
|
1082
|
-
: undefined,
|
|
1083
|
-
],
|
|
1084
|
-
[
|
|
1085
|
-
"B resolved",
|
|
1086
|
-
attrs.participant_b_resolved_at
|
|
1087
|
-
? formatTimestamp(attrs.participant_b_resolved_at)
|
|
1088
|
-
: undefined,
|
|
1089
|
-
],
|
|
1090
|
-
]),
|
|
1091
|
-
),
|
|
1092
|
-
renderSection("Messages", messageBlocks),
|
|
1093
|
-
renderPagination(pagination?.nextCursor, pagination?.nextCommand),
|
|
1094
|
-
renderCacheNote(cache),
|
|
1095
|
-
]);
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
function renderMessage(
|
|
1099
|
-
message: {
|
|
1100
|
-
id: string;
|
|
1101
|
-
attributes: MessageAttributes;
|
|
1102
|
-
},
|
|
1103
|
-
publicUsers: Map<string, string>,
|
|
1104
|
-
): string {
|
|
1105
|
-
const attrs = message.attributes;
|
|
1106
|
-
const headingParts = [
|
|
1107
|
-
formatTimestamp(attrs.inserted_at),
|
|
1108
|
-
shortId(message.id),
|
|
1109
|
-
formatUserActor(attrs.sender_owner_id, publicUsers),
|
|
1110
|
-
formatActor("channel", attrs.sender_channel_id),
|
|
1111
|
-
formatActor("email-sender", attrs.external_email_sender_id),
|
|
1112
|
-
].filter((part) => part !== "-");
|
|
1113
|
-
const contextPost = attrs.context_post_id
|
|
1114
|
-
? `Context post: ${attrs.context_post_id}\n`
|
|
1115
|
-
: "";
|
|
1116
|
-
const payload = attrs.payload
|
|
1117
|
-
? `\n\nPayload\n${indent(JSON.stringify(attrs.payload, null, 2))}`
|
|
1118
|
-
: "";
|
|
1119
|
-
|
|
1120
|
-
return `${headingParts.join(" ")}\n${indent(`${contextPost}${attrs.body}`)}${payload}`;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
function printSchemaResource(
|
|
1124
|
-
context: CommandContext,
|
|
1125
|
-
io: Io,
|
|
1126
|
-
resource: {
|
|
1127
|
-
id: string;
|
|
1128
|
-
attributes: {
|
|
1129
|
-
public_handle?: string | null;
|
|
1130
|
-
name?: string | null;
|
|
1131
|
-
inbox_schema?: Record<string, unknown> | null;
|
|
1132
|
-
inbox_schema_hash?: string | null;
|
|
1133
|
-
inbox_schema_updated_at?: string | null;
|
|
1134
|
-
external_email_acceptance?: ExternalEmailAcceptance | null;
|
|
1135
|
-
};
|
|
1136
|
-
},
|
|
1137
|
-
action?: string,
|
|
1138
|
-
): void {
|
|
1139
|
-
printValue(
|
|
1140
|
-
io,
|
|
1141
|
-
context.outputMode,
|
|
1142
|
-
context.outputMode === "json"
|
|
1143
|
-
? resource
|
|
1144
|
-
: renderSchemaResource(resource, action),
|
|
1145
|
-
);
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
async function maybePrepareCachePlan(
|
|
1149
|
-
args: ParsedArgs,
|
|
1150
|
-
context: CommandContext,
|
|
1151
|
-
scope: CacheScope | undefined,
|
|
1152
|
-
): Promise<CachePlan | undefined> {
|
|
1153
|
-
return cacheFlags(args).sinceCache && scope
|
|
1154
|
-
? prepareCachePlan(context, scope)
|
|
1155
|
-
: undefined;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
async function maybeSaveCacheTimestamp(
|
|
1159
|
-
args: ParsedArgs,
|
|
1160
|
-
context: CommandContext,
|
|
1161
|
-
scope: CacheScope | undefined,
|
|
1162
|
-
meta: Record<string, unknown> | undefined,
|
|
1163
|
-
shouldSave: boolean,
|
|
1164
|
-
): Promise<string | undefined> {
|
|
1165
|
-
return cacheFlags(args).saveCache && scope && shouldSave
|
|
1166
|
-
? saveCacheTimestamp(context, scope, meta)
|
|
1167
|
-
: undefined;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
async function maybeInboxThreadsScope(
|
|
1171
|
-
args: ParsedArgs,
|
|
1172
|
-
context: CommandContext,
|
|
1173
|
-
channelToken: string | undefined,
|
|
1174
|
-
status: ThreadStatusFilter | undefined,
|
|
1175
|
-
mailbox: MailboxFilter | undefined,
|
|
1176
|
-
participantScope: ParticipantScope | undefined,
|
|
1177
|
-
): Promise<CacheScope | undefined> {
|
|
1178
|
-
if (!cacheFlags(args).sinceCache && !cacheFlags(args).saveCache) {
|
|
1179
|
-
return undefined;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
return inboxThreadsScope({
|
|
1183
|
-
context,
|
|
1184
|
-
actorKey: await authenticatedActorKey(context, channelToken),
|
|
1185
|
-
status,
|
|
1186
|
-
mailbox,
|
|
1187
|
-
participant: stringFlag(args.flags, "participant"),
|
|
1188
|
-
participantScope,
|
|
1189
|
-
query: stringFlag(args.flags, "query"),
|
|
1190
|
-
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
1191
|
-
before: stringFlag(args.flags, "before"),
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
async function maybeInboxMessagesScope(
|
|
1196
|
-
args: ParsedArgs,
|
|
1197
|
-
context: CommandContext,
|
|
1198
|
-
channelToken: string | undefined,
|
|
1199
|
-
threadId: string,
|
|
1200
|
-
): Promise<CacheScope | undefined> {
|
|
1201
|
-
if (!cacheFlags(args).sinceCache && !cacheFlags(args).saveCache) {
|
|
1202
|
-
return undefined;
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
return inboxMessagesScope({
|
|
1206
|
-
context,
|
|
1207
|
-
actorKey: await authenticatedActorKey(context, channelToken),
|
|
1208
|
-
threadId,
|
|
1209
|
-
query: stringFlag(args.flags, "query"),
|
|
1210
|
-
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
1211
|
-
before: stringFlag(args.flags, "before"),
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
function resolvedSince(
|
|
1216
|
-
args: ParsedArgs,
|
|
1217
|
-
cachePlan: CachePlan | undefined,
|
|
1218
|
-
): string | undefined {
|
|
1219
|
-
return stringFlag(args.flags, "since") ?? cachePlan?.previousServerTimestamp;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
function printCacheNote(io: Io, cache: CacheResult | undefined): void {
|
|
1223
|
-
const note = renderCacheNote(cache);
|
|
1224
|
-
|
|
1225
|
-
if (note) {
|
|
1226
|
-
io.stdout(note);
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
function renderCacheNote(cache: CacheResult | undefined): string | undefined {
|
|
1231
|
-
if (!cache) {
|
|
1232
|
-
return undefined;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
const details = [
|
|
1236
|
-
cache.previousServerTimestamp
|
|
1237
|
-
? `used ${cache.previousServerTimestamp}`
|
|
1238
|
-
: cache.hit
|
|
1239
|
-
? "used cache"
|
|
1240
|
-
: "no cached timestamp",
|
|
1241
|
-
cache.savedServerTimestamp ? `saved ${cache.savedServerTimestamp}` : undefined,
|
|
1242
|
-
].filter(Boolean);
|
|
1243
|
-
|
|
1244
|
-
return `Cache: ${details.join("; ")}.`;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
function cacheFields(cache: CacheResult | undefined): Array<[string, string | undefined]> {
|
|
1248
|
-
if (!cache) {
|
|
1249
|
-
return [];
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
return [
|
|
1253
|
-
["Cache scope", cache.scopeKey],
|
|
1254
|
-
["Cached timestamp", cache.previousServerTimestamp],
|
|
1255
|
-
["Saved timestamp", cache.savedServerTimestamp],
|
|
1256
|
-
];
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
function renderSchemaResource(
|
|
1260
|
-
resource: {
|
|
1261
|
-
id: string;
|
|
1262
|
-
attributes: {
|
|
1263
|
-
public_handle?: string | null;
|
|
1264
|
-
name?: string | null;
|
|
1265
|
-
inbox_schema?: Record<string, unknown> | null;
|
|
1266
|
-
inbox_schema_hash?: string | null;
|
|
1267
|
-
inbox_schema_updated_at?: string | null;
|
|
1268
|
-
external_email_acceptance?: ExternalEmailAcceptance | null;
|
|
1269
|
-
};
|
|
1270
|
-
},
|
|
1271
|
-
action?: string,
|
|
1272
|
-
): string {
|
|
1273
|
-
const attrs = resource.attributes;
|
|
1274
|
-
const schema = attrs.inbox_schema
|
|
1275
|
-
? JSON.stringify(attrs.inbox_schema, null, 2)
|
|
1276
|
-
: "No inbox schema.";
|
|
1277
|
-
|
|
1278
|
-
return joinBlocks([
|
|
1279
|
-
action,
|
|
1280
|
-
`Inbox schema ${resource.id}`,
|
|
1281
|
-
renderFields([
|
|
1282
|
-
["Handle", attrs.public_handle],
|
|
1283
|
-
["Channel", attrs.name],
|
|
1284
|
-
["Email acceptance", attrs.external_email_acceptance],
|
|
1285
|
-
["Hash", attrs.inbox_schema_hash],
|
|
1286
|
-
["Updated", formatTimestamp(attrs.inbox_schema_updated_at)],
|
|
1287
|
-
]),
|
|
1288
|
-
renderSection("Schema", indent(schema)),
|
|
1289
|
-
]);
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
function printEmailIntakeCollection(
|
|
1293
|
-
args: ParsedArgs,
|
|
1294
|
-
context: CommandContext,
|
|
1295
|
-
io: Io,
|
|
1296
|
-
response: {
|
|
1297
|
-
items: Array<{ id: string; attributes: ExternalEmailIntakeAttributes }>;
|
|
1298
|
-
nextCursor?: string;
|
|
1299
|
-
},
|
|
1300
|
-
): void {
|
|
1301
|
-
printValue(
|
|
1302
|
-
io,
|
|
1303
|
-
context.outputMode,
|
|
1304
|
-
context.outputMode === "json"
|
|
1305
|
-
? paginatedJson(args, {
|
|
1306
|
-
items: response.items,
|
|
1307
|
-
nextCursor: response.nextCursor,
|
|
1308
|
-
})
|
|
1309
|
-
: renderEmailIntakeCollection(args, response),
|
|
1310
|
-
);
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
function printEmailIntakeAction(
|
|
1314
|
-
context: CommandContext,
|
|
1315
|
-
io: Io,
|
|
1316
|
-
action: string,
|
|
1317
|
-
intake: { id: string; attributes: ExternalEmailIntakeAttributes },
|
|
1318
|
-
): void {
|
|
1319
|
-
printValue(
|
|
1320
|
-
io,
|
|
1321
|
-
context.outputMode,
|
|
1322
|
-
context.outputMode === "json"
|
|
1323
|
-
? intake
|
|
1324
|
-
: joinBlocks([`${action}: ${intake.id}`, renderEmailIntake(intake)]),
|
|
1325
|
-
);
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
function renderEmailIntakeCollection(
|
|
1329
|
-
args: ParsedArgs,
|
|
1330
|
-
response: {
|
|
1331
|
-
items: Array<{ id: string; attributes: ExternalEmailIntakeAttributes }>;
|
|
1332
|
-
nextCursor?: string;
|
|
1333
|
-
},
|
|
1334
|
-
): string {
|
|
1335
|
-
const body =
|
|
1336
|
-
response.items.length === 0
|
|
1337
|
-
? "No email intakes."
|
|
1338
|
-
: response.items.map((intake) => renderEmailIntake(intake)).join("\n\n");
|
|
1339
|
-
|
|
1340
|
-
const pagination = paginationInfo(args, response.nextCursor);
|
|
1341
|
-
return joinBlocks([
|
|
1342
|
-
body,
|
|
1343
|
-
renderPagination(pagination?.nextCursor, pagination?.nextCommand),
|
|
1344
|
-
]);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
function renderEmailIntake(intake: {
|
|
1348
|
-
id: string;
|
|
1349
|
-
attributes: ExternalEmailIntakeAttributes;
|
|
1350
|
-
}): string {
|
|
1351
|
-
const attrs = intake.attributes;
|
|
1352
|
-
|
|
1353
|
-
return joinBlocks([
|
|
1354
|
-
`Email intake ${intake.id}`,
|
|
1355
|
-
renderFields([
|
|
1356
|
-
["Subject", attrs.subject ?? ""],
|
|
1357
|
-
["Status", attrs.status ?? ""],
|
|
1358
|
-
["Decision", attrs.decision ?? ""],
|
|
1359
|
-
["Sender", formatActor("email-sender", attrs.external_email_sender_id)],
|
|
1360
|
-
["Target channel", formatActor("channel", attrs.target_channel_id)],
|
|
1361
|
-
["Raw recipient", attrs.raw_recipient ?? ""],
|
|
1362
|
-
["Received count", formatOptionalNumber(attrs.received_count)],
|
|
1363
|
-
["Attachment count", formatOptionalNumber(attrs.attachment_count)],
|
|
1364
|
-
["Attachments withheld", formatOptionalBoolean(attrs.attachments_withheld)],
|
|
1365
|
-
["Last received", formatTimestamp(attrs.last_received_at)],
|
|
1366
|
-
["Released", formatTimestamp(attrs.released_at)],
|
|
1367
|
-
["Released thread", formatActor("thread", attrs.released_thread_id)],
|
|
1368
|
-
]),
|
|
1369
|
-
attrs.body ? renderSection("Body", indent(attrs.body)) : "",
|
|
1370
|
-
]);
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
function renderAttachmentCollection(
|
|
1374
|
-
args: ParsedArgs,
|
|
1375
|
-
response: {
|
|
1376
|
-
items: Array<{ id: string; attributes: MessageAttachmentAttributes }>;
|
|
1377
|
-
nextCursor?: string;
|
|
1378
|
-
},
|
|
1379
|
-
): string {
|
|
1380
|
-
const body =
|
|
1381
|
-
response.items.length === 0
|
|
1382
|
-
? "No attachments."
|
|
1383
|
-
: response.items
|
|
1384
|
-
.map((attachment) => {
|
|
1385
|
-
const attrs = attachment.attributes;
|
|
1386
|
-
|
|
1387
|
-
return joinBlocks([
|
|
1388
|
-
`Attachment ${attachment.id}`,
|
|
1389
|
-
renderFields([
|
|
1390
|
-
["Name", attrs.name ?? ""],
|
|
1391
|
-
["Content type", attrs.content_type ?? ""],
|
|
1392
|
-
["Content length", formatOptionalNumber(attrs.content_length)],
|
|
1393
|
-
["Message", formatActor("message", attrs.message_id)],
|
|
1394
|
-
]),
|
|
1395
|
-
]);
|
|
1396
|
-
})
|
|
1397
|
-
.join("\n\n");
|
|
1398
|
-
|
|
1399
|
-
const pagination = paginationInfo(args, response.nextCursor);
|
|
1400
|
-
return joinBlocks([
|
|
1401
|
-
body,
|
|
1402
|
-
renderPagination(pagination?.nextCursor, pagination?.nextCommand),
|
|
1403
|
-
]);
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
function renderThreadAction(
|
|
1407
|
-
action: string,
|
|
1408
|
-
thread: { id: string; attributes: ThreadAttributes },
|
|
1409
|
-
): string {
|
|
1410
|
-
const attrs = thread.attributes;
|
|
1411
|
-
|
|
1412
|
-
return joinBlocks([
|
|
1413
|
-
`${action}: ${thread.id}`,
|
|
1414
|
-
renderFields([
|
|
1415
|
-
["Mailbox", attrs.mailbox_type],
|
|
1416
|
-
["Status", attrs.status],
|
|
1417
|
-
["Last message", formatTimestamp(attrs.last_message_at)],
|
|
1418
|
-
["Opened", formatTimestamp(attrs.opened_at)],
|
|
1419
|
-
[
|
|
1420
|
-
"Expires",
|
|
1421
|
-
attrs.expires_at ? formatTimestamp(attrs.expires_at) : undefined,
|
|
1422
|
-
],
|
|
1423
|
-
]),
|
|
1424
|
-
]);
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
function formatActor(
|
|
1428
|
-
kind: "user" | "channel" | "email-sender" | "thread" | "message",
|
|
1429
|
-
id?: string | null,
|
|
1430
|
-
): string {
|
|
1431
|
-
if (!id) {
|
|
1432
|
-
return "-";
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
return `${kind}:${shortId(id)}`;
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
function formatOptionalBoolean(value?: boolean | null): string {
|
|
1439
|
-
if (value === undefined || value === null) {
|
|
1440
|
-
return "";
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
return value ? "yes" : "no";
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
function formatOptionalNumber(value?: number | null): string {
|
|
1447
|
-
if (value === undefined || value === null) {
|
|
1448
|
-
return "";
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
return String(value);
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
function formatUserActor(
|
|
1455
|
-
id: string | null | undefined,
|
|
1456
|
-
publicUsers: Map<string, string>,
|
|
1457
|
-
): string {
|
|
1458
|
-
if (!id) {
|
|
1459
|
-
return "-";
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
return publicUsers.get(id) ?? formatActor("user", id);
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
function ownerIdsForThreadDisplay(
|
|
1466
|
-
thread: { attributes: ThreadAttributes },
|
|
1467
|
-
messages: Array<{ attributes: MessageAttributes }>,
|
|
1468
|
-
): string[] {
|
|
1469
|
-
const ids = [
|
|
1470
|
-
thread.attributes.participant_a_owner_id,
|
|
1471
|
-
thread.attributes.participant_b_owner_id,
|
|
1472
|
-
...messages.map((message) => message.attributes.sender_owner_id),
|
|
1473
|
-
];
|
|
1474
|
-
|
|
1475
|
-
return Array.from(new Set(ids.filter((id): id is string => Boolean(id))));
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
function ownerIdsForThreadList(
|
|
1479
|
-
peers: Array<ThreadPeer>,
|
|
1480
|
-
): string[] {
|
|
1481
|
-
return Array.from(
|
|
1482
|
-
new Set(
|
|
1483
|
-
peers
|
|
1484
|
-
.map((peer) => peer.ownerId)
|
|
1485
|
-
.filter((id): id is string => Boolean(id)),
|
|
1486
|
-
),
|
|
1487
|
-
);
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
interface ThreadPeer {
|
|
1491
|
-
ownerId?: string | null;
|
|
1492
|
-
channelId?: string | null;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
function threadPeer(attrs: ThreadAttributes, actor: WhoamiActor): ThreadPeer {
|
|
1496
|
-
const side = threadActorSide(attrs, actor);
|
|
1497
|
-
|
|
1498
|
-
if (side === "a") {
|
|
1499
|
-
return {
|
|
1500
|
-
ownerId: attrs.participant_b_owner_id,
|
|
1501
|
-
channelId: attrs.participant_b_channel_id,
|
|
1502
|
-
};
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
if (side === "b") {
|
|
1506
|
-
return {
|
|
1507
|
-
ownerId: attrs.participant_a_owner_id,
|
|
1508
|
-
channelId: attrs.participant_a_channel_id,
|
|
1509
|
-
};
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
return {
|
|
1513
|
-
ownerId: attrs.participant_a_owner_id,
|
|
1514
|
-
channelId: attrs.participant_a_channel_id,
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
function threadActorSide(
|
|
1519
|
-
attrs: ThreadAttributes,
|
|
1520
|
-
actor: WhoamiActor,
|
|
1521
|
-
): "a" | "b" | undefined {
|
|
1522
|
-
if (actor.type === "channel") {
|
|
1523
|
-
if (attrs.participant_a_channel_id === actor.id) {
|
|
1524
|
-
return "a";
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
if (attrs.participant_b_channel_id === actor.id) {
|
|
1528
|
-
return "b";
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
const ownerId = actor.type === "user" ? actor.id : actor.owner_id;
|
|
1533
|
-
|
|
1534
|
-
if (attrs.participant_a_owner_id === ownerId) {
|
|
1535
|
-
return "a";
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
if (attrs.participant_b_owner_id === ownerId) {
|
|
1539
|
-
return "b";
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
return undefined;
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
function formatThreadPeer(
|
|
1546
|
-
peer: ThreadPeer | undefined,
|
|
1547
|
-
publicUsers: Map<string, string>,
|
|
1548
|
-
): string {
|
|
1549
|
-
if (!peer) {
|
|
1550
|
-
return "-";
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
if (peer.channelId) {
|
|
1554
|
-
return formatActor("channel", peer.channelId);
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
return formatUserActor(peer.ownerId, publicUsers);
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
function publicUserHandlesById(
|
|
1561
|
-
users: Array<{ id: string; attributes: { public_handle?: string | null } }>,
|
|
1562
|
-
): Map<string, string> {
|
|
1563
|
-
const handles = new Map<string, string>();
|
|
1564
|
-
|
|
1565
|
-
for (const user of users) {
|
|
1566
|
-
const handle = user.attributes.public_handle?.trim();
|
|
1567
|
-
|
|
1568
|
-
if (handle) {
|
|
1569
|
-
handles.set(user.id, `@${handle}`);
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
return handles;
|
|
1574
|
-
}
|