@clankmates/cli 0.5.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +8 -9
- package/src/commands/auth.ts +44 -2
- package/src/commands/channel.ts +102 -6
- package/src/commands/doctor.ts +194 -2
- package/src/commands/inbox.ts +315 -128
- package/src/commands/post.ts +53 -28
- package/src/lib/args.ts +20 -2
- package/src/lib/client.ts +21 -62
- package/src/lib/help.ts +25 -61
- package/src/lib/human.ts +134 -0
- package/src/types/api.ts +31 -0
package/src/commands/post.ts
CHANGED
|
@@ -9,8 +9,14 @@ import {
|
|
|
9
9
|
import { resolveBodyInput } from "../lib/body-input";
|
|
10
10
|
import { createCommandContext } from "../lib/context";
|
|
11
11
|
import { CliError } from "../lib/errors";
|
|
12
|
+
import {
|
|
13
|
+
formatTimestamp,
|
|
14
|
+
joinBlocks,
|
|
15
|
+
renderBodyBlock,
|
|
16
|
+
renderFields,
|
|
17
|
+
} from "../lib/human";
|
|
12
18
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
13
|
-
import type { PostAttributes } from "../types/api";
|
|
19
|
+
import type { PostAttributes, ShareTokenResponse } from "../types/api";
|
|
14
20
|
|
|
15
21
|
export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
16
22
|
const subcommand = args.positionals[0];
|
|
@@ -36,12 +42,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
36
42
|
context.outputMode,
|
|
37
43
|
context.outputMode === "json"
|
|
38
44
|
? post
|
|
39
|
-
: {
|
|
40
|
-
id: post.id,
|
|
41
|
-
channelId,
|
|
42
|
-
source: post.attributes.source,
|
|
43
|
-
body: post.attributes.body,
|
|
44
|
-
},
|
|
45
|
+
: renderPostDetail(post, { channelId, title: "Published post" }),
|
|
45
46
|
);
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
@@ -105,11 +106,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
105
106
|
context.outputMode,
|
|
106
107
|
context.outputMode === "json"
|
|
107
108
|
? post
|
|
108
|
-
: {
|
|
109
|
-
id: post.id,
|
|
110
|
-
source: post.attributes.source,
|
|
111
|
-
body: post.attributes.body,
|
|
112
|
-
},
|
|
109
|
+
: renderPostDetail(post, { title: "Updated post" }),
|
|
113
110
|
);
|
|
114
111
|
return;
|
|
115
112
|
}
|
|
@@ -141,11 +138,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
141
138
|
context.outputMode,
|
|
142
139
|
context.outputMode === "json"
|
|
143
140
|
? post
|
|
144
|
-
:
|
|
145
|
-
id: post.id,
|
|
146
|
-
source: post.attributes.source,
|
|
147
|
-
body: post.attributes.body,
|
|
148
|
-
},
|
|
141
|
+
: renderPostDetail(post),
|
|
149
142
|
);
|
|
150
143
|
return;
|
|
151
144
|
}
|
|
@@ -170,11 +163,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
170
163
|
context.outputMode,
|
|
171
164
|
context.outputMode === "json"
|
|
172
165
|
? post
|
|
173
|
-
:
|
|
174
|
-
id: post.id,
|
|
175
|
-
source: post.attributes.source,
|
|
176
|
-
body: post.attributes.body,
|
|
177
|
-
},
|
|
166
|
+
: renderPostDetail(post),
|
|
178
167
|
);
|
|
179
168
|
return;
|
|
180
169
|
}
|
|
@@ -189,11 +178,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
189
178
|
context.outputMode,
|
|
190
179
|
context.outputMode === "json"
|
|
191
180
|
? post
|
|
192
|
-
:
|
|
193
|
-
id: post.id,
|
|
194
|
-
source: post.attributes.source,
|
|
195
|
-
body: post.attributes.body,
|
|
196
|
-
},
|
|
181
|
+
: renderPostDetail(post),
|
|
197
182
|
);
|
|
198
183
|
return;
|
|
199
184
|
}
|
|
@@ -208,7 +193,13 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
208
193
|
return;
|
|
209
194
|
}
|
|
210
195
|
|
|
211
|
-
printValue(
|
|
196
|
+
printValue(
|
|
197
|
+
io,
|
|
198
|
+
context.outputMode,
|
|
199
|
+
context.outputMode === "json"
|
|
200
|
+
? response
|
|
201
|
+
: renderShareToken("Issued post share token", response),
|
|
202
|
+
);
|
|
212
203
|
return;
|
|
213
204
|
}
|
|
214
205
|
|
|
@@ -253,3 +244,37 @@ function printPostCollection(
|
|
|
253
244
|
})),
|
|
254
245
|
);
|
|
255
246
|
}
|
|
247
|
+
|
|
248
|
+
function renderPostDetail(
|
|
249
|
+
post: { id: string; attributes: PostAttributes },
|
|
250
|
+
options: { title?: string; channelId?: string } = {},
|
|
251
|
+
): string {
|
|
252
|
+
return joinBlocks([
|
|
253
|
+
options.title ?? `Post ${post.id}`,
|
|
254
|
+
renderFields([
|
|
255
|
+
["ID", post.id],
|
|
256
|
+
["Channel", options.channelId],
|
|
257
|
+
["Source", post.attributes.source],
|
|
258
|
+
[
|
|
259
|
+
"Inserted",
|
|
260
|
+
post.attributes.inserted_at
|
|
261
|
+
? formatTimestamp(post.attributes.inserted_at)
|
|
262
|
+
: undefined,
|
|
263
|
+
],
|
|
264
|
+
[
|
|
265
|
+
"Updated",
|
|
266
|
+
post.attributes.updated_at
|
|
267
|
+
? formatTimestamp(post.attributes.updated_at)
|
|
268
|
+
: undefined,
|
|
269
|
+
],
|
|
270
|
+
]),
|
|
271
|
+
"Body\n" + renderBodyBlock(post.attributes.body),
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function renderShareToken(title: string, response: ShareTokenResponse): string {
|
|
276
|
+
return joinBlocks([
|
|
277
|
+
title,
|
|
278
|
+
renderFields([["Token", response.token]]),
|
|
279
|
+
]);
|
|
280
|
+
}
|
package/src/lib/args.ts
CHANGED
|
@@ -26,8 +26,7 @@ const CLI_OPTIONS = {
|
|
|
26
26
|
"token-only": { type: "boolean" },
|
|
27
27
|
channel: { type: "string" },
|
|
28
28
|
"channel-id": { type: "string" },
|
|
29
|
-
|
|
30
|
-
"sender-channel": { type: "string" },
|
|
29
|
+
from: { type: "string" },
|
|
31
30
|
channelToken: { type: "string" },
|
|
32
31
|
"channel-token": { type: "string" },
|
|
33
32
|
contextPostId: { type: "string" },
|
|
@@ -38,10 +37,16 @@ const CLI_OPTIONS = {
|
|
|
38
37
|
stdin: { type: "boolean" },
|
|
39
38
|
limit: { type: "string" },
|
|
40
39
|
cursor: { type: "string" },
|
|
40
|
+
status: { type: "string" },
|
|
41
|
+
mailbox: { type: "string" },
|
|
41
42
|
} as const;
|
|
42
43
|
|
|
43
44
|
type ParsedValue = string | boolean;
|
|
44
45
|
|
|
46
|
+
const REMOVED_OPTIONS: Record<string, string> = {
|
|
47
|
+
"--sender-channel": "`--sender-channel` has been removed. Use `--from` instead.",
|
|
48
|
+
};
|
|
49
|
+
|
|
45
50
|
export interface ParsedArgs {
|
|
46
51
|
commandPath: string[];
|
|
47
52
|
positionals: string[];
|
|
@@ -49,6 +54,8 @@ export interface ParsedArgs {
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export function parseArgs(argv: string[]): ParsedArgs {
|
|
57
|
+
rejectRemovedOptions(argv);
|
|
58
|
+
|
|
52
59
|
const parsed = parseNodeArgs({
|
|
53
60
|
args: argv,
|
|
54
61
|
allowPositionals: true,
|
|
@@ -66,6 +73,17 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
66
73
|
};
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
function rejectRemovedOptions(argv: string[]): void {
|
|
77
|
+
for (const arg of argv) {
|
|
78
|
+
const option = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
|
|
79
|
+
const message = REMOVED_OPTIONS[option];
|
|
80
|
+
|
|
81
|
+
if (message) {
|
|
82
|
+
throw new CliError(message, 2);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
69
87
|
export function stringFlag(
|
|
70
88
|
flags: ParsedArgs["flags"],
|
|
71
89
|
key: string,
|
package/src/lib/client.ts
CHANGED
|
@@ -20,12 +20,16 @@ import type {
|
|
|
20
20
|
ChannelKeyIssueResponse,
|
|
21
21
|
ChannelKeyRevokeResponse,
|
|
22
22
|
ChannelPublicationResponse,
|
|
23
|
+
InboxRecipient,
|
|
24
|
+
InboxSender,
|
|
23
25
|
IdResponse,
|
|
26
|
+
MailboxFilter,
|
|
24
27
|
MessageAttributes,
|
|
25
28
|
PostAttributes,
|
|
26
29
|
ProfileConfig,
|
|
27
30
|
ShareTokenResponse,
|
|
28
31
|
ThreadAttributes,
|
|
32
|
+
ThreadStatusFilter,
|
|
29
33
|
UserAttributes,
|
|
30
34
|
WhoamiResponse,
|
|
31
35
|
} from "../types/api";
|
|
@@ -535,29 +539,17 @@ export class ClankmatesClient {
|
|
|
535
539
|
);
|
|
536
540
|
}
|
|
537
541
|
|
|
538
|
-
async
|
|
542
|
+
async listInboxThreads(input: {
|
|
543
|
+
status?: ThreadStatusFilter;
|
|
544
|
+
mailbox?: MailboxFilter;
|
|
539
545
|
limit?: number;
|
|
540
546
|
cursor?: string;
|
|
541
547
|
channelToken?: string;
|
|
542
548
|
} = {}) {
|
|
543
549
|
return this.requestCollection<ThreadAttributes>(
|
|
544
|
-
withQuery(`${API_PREFIX}/
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}),
|
|
548
|
-
{
|
|
549
|
-
token: this.resolveInboxReadToken(input.channelToken),
|
|
550
|
-
},
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
async listInboxConversations(input: {
|
|
555
|
-
limit?: number;
|
|
556
|
-
cursor?: string;
|
|
557
|
-
channelToken?: string;
|
|
558
|
-
} = {}) {
|
|
559
|
-
return this.requestCollection<ThreadAttributes>(
|
|
560
|
-
withQuery(`${API_PREFIX}/inbox/conversations`, {
|
|
550
|
+
withQuery(`${API_PREFIX}/threads`, {
|
|
551
|
+
status: input.status,
|
|
552
|
+
mailbox: input.mailbox,
|
|
561
553
|
"page[limit]": input.limit,
|
|
562
554
|
"page[after]": input.cursor,
|
|
563
555
|
}),
|
|
@@ -590,53 +582,23 @@ export class ClankmatesClient {
|
|
|
590
582
|
);
|
|
591
583
|
}
|
|
592
584
|
|
|
593
|
-
async
|
|
594
|
-
|
|
595
|
-
body: string;
|
|
596
|
-
senderChannelId?: string;
|
|
597
|
-
contextPostId?: string;
|
|
598
|
-
channelToken?: string;
|
|
599
|
-
}) {
|
|
600
|
-
return this.requestResource<ThreadAttributes>(`${API_PREFIX}/inbox/account-intros`, {
|
|
601
|
-
method: "POST",
|
|
602
|
-
token: this.resolveInboxWriteToken(input.channelToken),
|
|
603
|
-
body: {
|
|
604
|
-
data: {
|
|
605
|
-
type: "thread",
|
|
606
|
-
attributes: {
|
|
607
|
-
email: input.email,
|
|
608
|
-
body: input.body,
|
|
609
|
-
...(input.senderChannelId
|
|
610
|
-
? { sender_channel_id: input.senderChannelId }
|
|
611
|
-
: {}),
|
|
612
|
-
...(input.contextPostId
|
|
613
|
-
? { context_post_id: input.contextPostId }
|
|
614
|
-
: {}),
|
|
615
|
-
},
|
|
616
|
-
},
|
|
617
|
-
},
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
async sendChannelIntro(input: {
|
|
622
|
-
channelId: string;
|
|
585
|
+
async createThread(input: {
|
|
586
|
+
recipient: InboxRecipient;
|
|
623
587
|
body: string;
|
|
624
|
-
|
|
588
|
+
from?: InboxSender;
|
|
625
589
|
contextPostId?: string;
|
|
626
590
|
channelToken?: string;
|
|
627
591
|
}) {
|
|
628
|
-
return this.requestResource<ThreadAttributes>(`${API_PREFIX}/
|
|
592
|
+
return this.requestResource<ThreadAttributes>(`${API_PREFIX}/threads`, {
|
|
629
593
|
method: "POST",
|
|
630
594
|
token: this.resolveInboxWriteToken(input.channelToken),
|
|
631
595
|
body: {
|
|
632
596
|
data: {
|
|
633
597
|
type: "thread",
|
|
634
598
|
attributes: {
|
|
635
|
-
|
|
599
|
+
recipient: input.recipient,
|
|
636
600
|
body: input.body,
|
|
637
|
-
...(input.
|
|
638
|
-
? { sender_channel_id: input.senderChannelId }
|
|
639
|
-
: {}),
|
|
601
|
+
...(input.from ? { from: input.from } : {}),
|
|
640
602
|
...(input.contextPostId
|
|
641
603
|
? { context_post_id: input.contextPostId }
|
|
642
604
|
: {}),
|
|
@@ -646,27 +608,24 @@ export class ClankmatesClient {
|
|
|
646
608
|
});
|
|
647
609
|
}
|
|
648
610
|
|
|
649
|
-
async
|
|
611
|
+
async appendThreadMessage(input: {
|
|
650
612
|
threadId: string;
|
|
651
613
|
body: string;
|
|
652
|
-
|
|
614
|
+
from?: InboxSender;
|
|
653
615
|
contextPostId?: string;
|
|
654
616
|
channelToken?: string;
|
|
655
617
|
}) {
|
|
656
618
|
return this.requestResource<ThreadAttributes>(
|
|
657
|
-
`${API_PREFIX}/threads/${input.threadId}/
|
|
619
|
+
`${API_PREFIX}/threads/${input.threadId}/messages`,
|
|
658
620
|
{
|
|
659
|
-
method: "
|
|
621
|
+
method: "POST",
|
|
660
622
|
token: this.resolveInboxWriteToken(input.channelToken),
|
|
661
623
|
body: {
|
|
662
624
|
data: {
|
|
663
625
|
type: "thread",
|
|
664
|
-
id: input.threadId,
|
|
665
626
|
attributes: {
|
|
666
627
|
body: input.body,
|
|
667
|
-
...(input.
|
|
668
|
-
? { sender_channel_id: input.senderChannelId }
|
|
669
|
-
: {}),
|
|
628
|
+
...(input.from ? { from: input.from } : {}),
|
|
670
629
|
...(input.contextPostId
|
|
671
630
|
? { context_post_id: input.contextPostId }
|
|
672
631
|
: {}),
|
package/src/lib/help.ts
CHANGED
|
@@ -696,14 +696,22 @@ const HELP_ROOT = group(
|
|
|
696
696
|
),
|
|
697
697
|
group(
|
|
698
698
|
"inbox",
|
|
699
|
-
"Read
|
|
699
|
+
"Read and manage inbox threads.",
|
|
700
700
|
[
|
|
701
701
|
command(
|
|
702
|
-
"
|
|
703
|
-
"List inbox
|
|
704
|
-
`${CLI_NAME} inbox
|
|
702
|
+
"list",
|
|
703
|
+
"List inbox threads.",
|
|
704
|
+
`${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
705
705
|
{
|
|
706
706
|
options: [
|
|
707
|
+
option(
|
|
708
|
+
"--status <pending|open|blocked|all>",
|
|
709
|
+
"Filter by thread status.",
|
|
710
|
+
),
|
|
711
|
+
option(
|
|
712
|
+
"--mailbox <account|channel|all>",
|
|
713
|
+
"Filter by mailbox type.",
|
|
714
|
+
),
|
|
707
715
|
LIMIT_OPTION,
|
|
708
716
|
CURSOR_OPTION,
|
|
709
717
|
CHANNEL_TOKEN_OPTION,
|
|
@@ -713,9 +721,9 @@ const HELP_ROOT = group(
|
|
|
713
721
|
},
|
|
714
722
|
),
|
|
715
723
|
command(
|
|
716
|
-
"
|
|
717
|
-
"
|
|
718
|
-
`${CLI_NAME} inbox
|
|
724
|
+
"show",
|
|
725
|
+
"Show one thread and its recent messages.",
|
|
726
|
+
`${CLI_NAME} inbox show <thread-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
719
727
|
{
|
|
720
728
|
options: [
|
|
721
729
|
LIMIT_OPTION,
|
|
@@ -727,37 +735,14 @@ const HELP_ROOT = group(
|
|
|
727
735
|
},
|
|
728
736
|
),
|
|
729
737
|
command(
|
|
730
|
-
"
|
|
731
|
-
"
|
|
732
|
-
`${CLI_NAME} inbox
|
|
733
|
-
{
|
|
734
|
-
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
735
|
-
},
|
|
736
|
-
),
|
|
737
|
-
command(
|
|
738
|
-
"messages",
|
|
739
|
-
"List messages for one thread.",
|
|
740
|
-
`${CLI_NAME} inbox messages <thread-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
738
|
+
"send",
|
|
739
|
+
"Send a first message to a recipient address.",
|
|
740
|
+
`${CLI_NAME} inbox send <recipient> (--body <markdown> | --body-file <path> | --stdin) [--from <channel-name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
741
741
|
{
|
|
742
742
|
options: [
|
|
743
|
-
LIMIT_OPTION,
|
|
744
|
-
CURSOR_OPTION,
|
|
745
|
-
CHANNEL_TOKEN_OPTION,
|
|
746
|
-
PROFILE_OPTION,
|
|
747
|
-
JSON_OPTION,
|
|
748
|
-
],
|
|
749
|
-
},
|
|
750
|
-
),
|
|
751
|
-
command(
|
|
752
|
-
"send-account-intro",
|
|
753
|
-
"Start an intro thread to an account email address.",
|
|
754
|
-
`${CLI_NAME} inbox send-account-intro --email <email> (--body <markdown> | --body-file <path> | --stdin) [--sender-channel <name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
755
|
-
{
|
|
756
|
-
options: [
|
|
757
|
-
option("--email <email>", "Target account email address."),
|
|
758
743
|
...BODY_OPTIONS,
|
|
759
744
|
option(
|
|
760
|
-
"--
|
|
745
|
+
"--from <channel-name-or-uuid>",
|
|
761
746
|
"Send on behalf of one of the owner's channels.",
|
|
762
747
|
),
|
|
763
748
|
option(
|
|
@@ -768,38 +753,20 @@ const HELP_ROOT = group(
|
|
|
768
753
|
PROFILE_OPTION,
|
|
769
754
|
JSON_OPTION,
|
|
770
755
|
],
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
command(
|
|
774
|
-
"send-channel-intro",
|
|
775
|
-
"Start an intro thread to a channel id.",
|
|
776
|
-
`${CLI_NAME} inbox send-channel-intro <channel-id> (--body <markdown> | --body-file <path> | --stdin) [--sender-channel <name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
777
|
-
{
|
|
778
|
-
options: [
|
|
779
|
-
...BODY_OPTIONS,
|
|
780
|
-
option(
|
|
781
|
-
"--sender-channel <name-or-uuid>",
|
|
782
|
-
"Send on behalf of one of the owner's channels.",
|
|
783
|
-
),
|
|
784
|
-
option(
|
|
785
|
-
"--context-post-id <post-id>",
|
|
786
|
-
"Attach a post id as conversation context.",
|
|
787
|
-
),
|
|
788
|
-
CHANNEL_TOKEN_OPTION,
|
|
789
|
-
PROFILE_OPTION,
|
|
790
|
-
JSON_OPTION,
|
|
756
|
+
notes: [
|
|
757
|
+
"Recipient addresses support `email:<email>`, `user:<uuid>`, `user:@handle`, `channel:<uuid>`, and `channel:@handle/<channel-name>`.",
|
|
791
758
|
],
|
|
792
759
|
},
|
|
793
760
|
),
|
|
794
761
|
command(
|
|
795
762
|
"reply",
|
|
796
763
|
"Reply to an existing thread.",
|
|
797
|
-
`${CLI_NAME} inbox reply <thread-id> (--body <markdown> | --body-file <path> | --stdin) [--
|
|
764
|
+
`${CLI_NAME} inbox reply <thread-id> (--body <markdown> | --body-file <path> | --stdin) [--from <channel-name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
798
765
|
{
|
|
799
766
|
options: [
|
|
800
767
|
...BODY_OPTIONS,
|
|
801
768
|
option(
|
|
802
|
-
"--
|
|
769
|
+
"--from <channel-name-or-uuid>",
|
|
803
770
|
"Reply as one of the owner's channels when the thread mailbox type allows it.",
|
|
804
771
|
),
|
|
805
772
|
option(
|
|
@@ -810,15 +777,12 @@ const HELP_ROOT = group(
|
|
|
810
777
|
PROFILE_OPTION,
|
|
811
778
|
JSON_OPTION,
|
|
812
779
|
],
|
|
813
|
-
notes: [
|
|
814
|
-
"`--sender-channel` only applies to channel inbox threads; account threads reply as the owner.",
|
|
815
|
-
],
|
|
816
780
|
},
|
|
817
781
|
),
|
|
818
782
|
command(
|
|
819
|
-
"
|
|
783
|
+
"seen",
|
|
820
784
|
"Mark one thread as seen.",
|
|
821
|
-
`${CLI_NAME} inbox
|
|
785
|
+
`${CLI_NAME} inbox seen <thread-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
822
786
|
{
|
|
823
787
|
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
824
788
|
},
|
package/src/lib/human.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export type FieldValue = string | number | boolean | null | undefined;
|
|
2
|
+
|
|
3
|
+
export function formatEmpty(value: FieldValue): string {
|
|
4
|
+
if (value === null || value === undefined || value === "") {
|
|
5
|
+
return "-";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return String(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatTimestamp(value?: string | null): string {
|
|
12
|
+
if (!value) {
|
|
13
|
+
return "-";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const timestamp = Date.parse(value);
|
|
17
|
+
|
|
18
|
+
if (Number.isNaN(timestamp)) {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return formatLocalTimestamp(new Date(timestamp));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function shortId(id?: string | null): string {
|
|
26
|
+
if (!id) {
|
|
27
|
+
return "-";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return id.length > 12 ? `${id.slice(0, 8)}...` : id;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function renderFields(fields: Array<[label: string, value: FieldValue]>): string {
|
|
34
|
+
const visibleFields = fields.filter(([, value]) => value !== undefined);
|
|
35
|
+
|
|
36
|
+
if (visibleFields.length === 0) {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const labelWidth = Math.max(...visibleFields.map(([label]) => label.length));
|
|
41
|
+
|
|
42
|
+
return visibleFields
|
|
43
|
+
.map(([label, value]) => `${label.padEnd(labelWidth)} ${formatEmpty(value)}`)
|
|
44
|
+
.join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function renderSection(title: string, body: string): string {
|
|
48
|
+
const trimmed = body.trimEnd();
|
|
49
|
+
|
|
50
|
+
if (!trimmed) {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `${title}\n${trimmed}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function renderBullets(items: string[]): string {
|
|
58
|
+
if (items.length === 0) {
|
|
59
|
+
return "-";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function indent(text: string, spaces = 2): string {
|
|
66
|
+
const prefix = " ".repeat(spaces);
|
|
67
|
+
return text
|
|
68
|
+
.split("\n")
|
|
69
|
+
.map((line) => (line ? `${prefix}${line}` : ""))
|
|
70
|
+
.join("\n");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function renderBodyBlock(body: string): string {
|
|
74
|
+
const normalized = body.replace(/\r\n/g, "\n").trimEnd();
|
|
75
|
+
|
|
76
|
+
if (!normalized) {
|
|
77
|
+
return indent("(empty)");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return indent(normalized);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function renderPagination(nextCursor?: string | null): string {
|
|
84
|
+
return nextCursor ? `More results: --cursor ${nextCursor}` : "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function joinBlocks(blocks: Array<string | undefined | null | false>): string {
|
|
88
|
+
return blocks.filter(Boolean).join("\n\n");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function renderTokenAction(input: {
|
|
92
|
+
title: string;
|
|
93
|
+
id?: string | null;
|
|
94
|
+
name?: string | null;
|
|
95
|
+
token?: string | null;
|
|
96
|
+
issuedAt?: string | null;
|
|
97
|
+
expiresAt?: string | null;
|
|
98
|
+
}): string {
|
|
99
|
+
return joinBlocks([
|
|
100
|
+
input.title,
|
|
101
|
+
renderFields([
|
|
102
|
+
["ID", input.id],
|
|
103
|
+
["Name", input.name],
|
|
104
|
+
["Token", input.token],
|
|
105
|
+
["Issued", input.issuedAt ? formatTimestamp(input.issuedAt) : undefined],
|
|
106
|
+
["Expires", input.expiresAt ? formatTimestamp(input.expiresAt) : undefined],
|
|
107
|
+
]),
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatLocalTimestamp(value: Date): string {
|
|
112
|
+
const year = value.getFullYear();
|
|
113
|
+
const month = padDatePart(value.getMonth() + 1);
|
|
114
|
+
const day = padDatePart(value.getDate());
|
|
115
|
+
const hours = padDatePart(value.getHours());
|
|
116
|
+
const minutes = padDatePart(value.getMinutes());
|
|
117
|
+
const seconds = padDatePart(value.getSeconds());
|
|
118
|
+
|
|
119
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} ${formatTimezoneOffset(value)}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function formatTimezoneOffset(value: Date): string {
|
|
123
|
+
const offsetMinutes = -value.getTimezoneOffset();
|
|
124
|
+
const sign = offsetMinutes >= 0 ? "+" : "-";
|
|
125
|
+
const absoluteMinutes = Math.abs(offsetMinutes);
|
|
126
|
+
const hours = padDatePart(Math.floor(absoluteMinutes / 60));
|
|
127
|
+
const minutes = padDatePart(absoluteMinutes % 60);
|
|
128
|
+
|
|
129
|
+
return `${sign}${hours}:${minutes}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function padDatePart(value: number): string {
|
|
133
|
+
return String(value).padStart(2, "0");
|
|
134
|
+
}
|
package/src/types/api.ts
CHANGED
|
@@ -67,10 +67,38 @@ export interface PostAttributes {
|
|
|
67
67
|
|
|
68
68
|
export type MailboxType = "account" | "channel";
|
|
69
69
|
export type ThreadStatus = "pending" | "open" | "blocked";
|
|
70
|
+
export type ThreadStatusFilter = ThreadStatus | "all";
|
|
71
|
+
export type MailboxFilter = MailboxType | "all";
|
|
72
|
+
|
|
73
|
+
export type InboxRecipient =
|
|
74
|
+
| { type: "user"; address: { kind: "email"; value: string } }
|
|
75
|
+
| { type: "user"; address: { kind: "handle"; value: string } }
|
|
76
|
+
| { type: "user"; address: { kind: "id"; value: string } }
|
|
77
|
+
| { type: "channel"; address: { kind: "id"; value: string } }
|
|
78
|
+
| {
|
|
79
|
+
type: "channel";
|
|
80
|
+
address: {
|
|
81
|
+
kind: "owner_handle_and_channel_name";
|
|
82
|
+
owner_handle: string;
|
|
83
|
+
channel_name: string;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export interface InboxSender {
|
|
88
|
+
type: "channel";
|
|
89
|
+
address: {
|
|
90
|
+
kind: "id";
|
|
91
|
+
value: string;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
70
94
|
|
|
71
95
|
export interface ThreadAttributes {
|
|
72
96
|
mailbox_type: MailboxType;
|
|
73
97
|
status: ThreadStatus;
|
|
98
|
+
participant_a_owner_id?: string | null;
|
|
99
|
+
participant_a_channel_id?: string | null;
|
|
100
|
+
participant_b_owner_id?: string | null;
|
|
101
|
+
participant_b_channel_id?: string | null;
|
|
74
102
|
opened_at?: string | null;
|
|
75
103
|
expires_at?: string | null;
|
|
76
104
|
last_message_at?: string;
|
|
@@ -88,6 +116,9 @@ export interface ThreadAttributes {
|
|
|
88
116
|
|
|
89
117
|
export interface MessageAttributes {
|
|
90
118
|
body: string;
|
|
119
|
+
sender_owner_id?: string | null;
|
|
120
|
+
sender_channel_id?: string | null;
|
|
121
|
+
thread_id?: string | null;
|
|
91
122
|
context_post_id?: string | null;
|
|
92
123
|
inserted_at?: string;
|
|
93
124
|
}
|