@clankmates/cli 0.7.1 → 0.9.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 +13 -1
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +15 -0
- package/src/commands/auth.ts +3 -0
- package/src/commands/channel.ts +42 -8
- package/src/commands/feed.ts +28 -15
- package/src/commands/inbox.ts +298 -36
- package/src/commands/post.ts +25 -9
- package/src/lib/args.ts +10 -0
- package/src/lib/client.ts +92 -4
- package/src/lib/config.ts +7 -3
- package/src/lib/context.ts +5 -1
- package/src/lib/help.ts +74 -16
- package/src/lib/human.ts +11 -2
- package/src/lib/json-input.ts +73 -0
- package/src/lib/pagination.ts +98 -0
- package/src/types/api.ts +7 -0
package/src/lib/client.ts
CHANGED
|
@@ -129,6 +129,44 @@ export class ClankmatesClient {
|
|
|
129
129
|
);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
async getPublicAccountInboxSchema(publicHandle: string) {
|
|
133
|
+
return this.requestResource<UserAttributes>(
|
|
134
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}/inbox-schema`,
|
|
135
|
+
{},
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async setAccountInboxSchema(inboxSchema: Record<string, unknown>) {
|
|
140
|
+
return this.requestResource<UserAttributes>(`${API_PREFIX}/me/inbox-schema`, {
|
|
141
|
+
method: "PATCH",
|
|
142
|
+
token: requireMasterToken(this.profile),
|
|
143
|
+
body: {
|
|
144
|
+
data: {
|
|
145
|
+
type: "user",
|
|
146
|
+
attributes: {
|
|
147
|
+
inbox_schema: inboxSchema,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async removeAccountInboxSchema() {
|
|
155
|
+
return this.requestResource<UserAttributes>(
|
|
156
|
+
`${API_PREFIX}/me/inbox-schema/remove`,
|
|
157
|
+
{
|
|
158
|
+
method: "PATCH",
|
|
159
|
+
token: requireMasterToken(this.profile),
|
|
160
|
+
body: {
|
|
161
|
+
data: {
|
|
162
|
+
type: "user",
|
|
163
|
+
attributes: {},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
132
170
|
async listPublicUsersById(ids: string[]) {
|
|
133
171
|
const path = withRepeatedQuery(`${API_PREFIX}/public/users/by-id`, "ids[]", ids);
|
|
134
172
|
|
|
@@ -181,6 +219,13 @@ export class ClankmatesClient {
|
|
|
181
219
|
);
|
|
182
220
|
}
|
|
183
221
|
|
|
222
|
+
async getPublicChannelInboxSchema(publicHandle: string, name: string) {
|
|
223
|
+
return this.requestResource<ChannelAttributes>(
|
|
224
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}/channels/${encodeURIComponent(name)}/inbox-schema`,
|
|
225
|
+
{},
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
184
229
|
async listPublicChannelsForHandle(input: {
|
|
185
230
|
publicHandle: string;
|
|
186
231
|
limit?: number;
|
|
@@ -247,6 +292,45 @@ export class ClankmatesClient {
|
|
|
247
292
|
);
|
|
248
293
|
}
|
|
249
294
|
|
|
295
|
+
async setChannelInboxSchema(input: {
|
|
296
|
+
channelId: string;
|
|
297
|
+
inboxSchema: Record<string, unknown>;
|
|
298
|
+
}) {
|
|
299
|
+
return this.requestResource<ChannelAttributes>(
|
|
300
|
+
`${API_PREFIX}/channels/${input.channelId}/inbox-schema`,
|
|
301
|
+
{
|
|
302
|
+
method: "PATCH",
|
|
303
|
+
token: requireMasterToken(this.profile),
|
|
304
|
+
body: {
|
|
305
|
+
data: {
|
|
306
|
+
type: "channel",
|
|
307
|
+
id: input.channelId,
|
|
308
|
+
attributes: {
|
|
309
|
+
inbox_schema: input.inboxSchema,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async removeChannelInboxSchema(channelId: string) {
|
|
318
|
+
return this.requestResource<ChannelAttributes>(
|
|
319
|
+
`${API_PREFIX}/channels/${channelId}/inbox-schema/remove`,
|
|
320
|
+
{
|
|
321
|
+
method: "PATCH",
|
|
322
|
+
token: requireMasterToken(this.profile),
|
|
323
|
+
body: {
|
|
324
|
+
data: {
|
|
325
|
+
type: "channel",
|
|
326
|
+
id: channelId,
|
|
327
|
+
attributes: {},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
250
334
|
async publishChannelPublicly(channelId: string) {
|
|
251
335
|
return this.requestResource<ChannelAttributes>(
|
|
252
336
|
`${API_PREFIX}/channels/${channelId}/publication`,
|
|
@@ -626,7 +710,8 @@ export class ClankmatesClient {
|
|
|
626
710
|
|
|
627
711
|
async createThread(input: {
|
|
628
712
|
recipient: InboxRecipient;
|
|
629
|
-
body
|
|
713
|
+
body?: string;
|
|
714
|
+
payload?: Record<string, unknown>;
|
|
630
715
|
from?: InboxSender;
|
|
631
716
|
contextPostId?: string;
|
|
632
717
|
channelToken?: string;
|
|
@@ -639,7 +724,8 @@ export class ClankmatesClient {
|
|
|
639
724
|
type: "thread",
|
|
640
725
|
attributes: {
|
|
641
726
|
recipient: input.recipient,
|
|
642
|
-
body: input.body,
|
|
727
|
+
...(input.body !== undefined ? { body: input.body } : {}),
|
|
728
|
+
...(input.payload !== undefined ? { payload: input.payload } : {}),
|
|
643
729
|
...(input.from ? { from: input.from } : {}),
|
|
644
730
|
...(input.contextPostId
|
|
645
731
|
? { context_post_id: input.contextPostId }
|
|
@@ -652,7 +738,8 @@ export class ClankmatesClient {
|
|
|
652
738
|
|
|
653
739
|
async appendThreadMessage(input: {
|
|
654
740
|
threadId: string;
|
|
655
|
-
body
|
|
741
|
+
body?: string;
|
|
742
|
+
payload?: Record<string, unknown>;
|
|
656
743
|
from?: InboxSender;
|
|
657
744
|
contextPostId?: string;
|
|
658
745
|
channelToken?: string;
|
|
@@ -666,7 +753,8 @@ export class ClankmatesClient {
|
|
|
666
753
|
data: {
|
|
667
754
|
type: "thread",
|
|
668
755
|
attributes: {
|
|
669
|
-
body: input.body,
|
|
756
|
+
...(input.body !== undefined ? { body: input.body } : {}),
|
|
757
|
+
...(input.payload !== undefined ? { payload: input.payload } : {}),
|
|
670
758
|
...(input.from ? { from: input.from } : {}),
|
|
671
759
|
...(input.contextPostId
|
|
672
760
|
? { context_post_id: input.contextPostId }
|
package/src/lib/config.ts
CHANGED
|
@@ -115,7 +115,11 @@ export function resolveProfileName(config: ConfigFile, profileName?: string): st
|
|
|
115
115
|
return profileName ?? process.env.CLANKMATES_PROFILE ?? config.activeProfile;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
export function resolveProfile(
|
|
118
|
+
export function resolveProfile(
|
|
119
|
+
config: ConfigFile,
|
|
120
|
+
profileName?: string,
|
|
121
|
+
baseUrl?: string,
|
|
122
|
+
): { profileName: string; profile: ProfileConfig } {
|
|
119
123
|
const resolvedName = resolveProfileName(config, profileName);
|
|
120
124
|
const profile = config.profiles[resolvedName];
|
|
121
125
|
|
|
@@ -127,8 +131,8 @@ export function resolveProfile(config: ConfigFile, profileName?: string): { prof
|
|
|
127
131
|
profileName: resolvedName,
|
|
128
132
|
profile: {
|
|
129
133
|
...profile,
|
|
130
|
-
baseUrl: resolveBaseUrl(
|
|
131
|
-
}
|
|
134
|
+
baseUrl: resolveBaseUrl(baseUrl, profile.baseUrl),
|
|
135
|
+
},
|
|
132
136
|
};
|
|
133
137
|
}
|
|
134
138
|
|
package/src/lib/context.ts
CHANGED
|
@@ -18,7 +18,11 @@ export interface CommandContext {
|
|
|
18
18
|
export async function createCommandContext(args: ParsedArgs, io: Io): Promise<CommandContext> {
|
|
19
19
|
const configPath = getConfigPath();
|
|
20
20
|
const config = await loadConfig(configPath);
|
|
21
|
-
const { profileName, profile } = resolveProfile(
|
|
21
|
+
const { profileName, profile } = resolveProfile(
|
|
22
|
+
config,
|
|
23
|
+
stringFlag(args.flags, "profile"),
|
|
24
|
+
stringFlag(args.flags, "baseUrl"),
|
|
25
|
+
);
|
|
22
26
|
|
|
23
27
|
return {
|
|
24
28
|
config,
|
package/src/lib/help.ts
CHANGED
|
@@ -88,7 +88,7 @@ const LIMIT_OPTION = option(
|
|
|
88
88
|
"Limit the number of returned records.",
|
|
89
89
|
);
|
|
90
90
|
const CURSOR_OPTION = option(
|
|
91
|
-
"--cursor <
|
|
91
|
+
"--cursor <cursor>",
|
|
92
92
|
"Resume from a pagination cursor returned by a prior request.",
|
|
93
93
|
);
|
|
94
94
|
const CHANNEL_TOKEN_OPTION = option(
|
|
@@ -100,6 +100,16 @@ const BODY_OPTIONS = [
|
|
|
100
100
|
option("--body-file <path>", "Read markdown content from a file."),
|
|
101
101
|
option("--stdin", "Read markdown or JSON input from standard input."),
|
|
102
102
|
];
|
|
103
|
+
const PAYLOAD_OPTIONS = [
|
|
104
|
+
option("--payload <json>", "Provide a typed inbox payload JSON object inline."),
|
|
105
|
+
option("--payload-file <path>", "Read a typed inbox payload JSON object from a file."),
|
|
106
|
+
option("--payload-stdin", "Read a typed inbox payload JSON object from standard input."),
|
|
107
|
+
];
|
|
108
|
+
const SCHEMA_OPTIONS = [
|
|
109
|
+
option("--schema <json>", "Provide a JSON Schema Draft 2020-12 document inline."),
|
|
110
|
+
option("--schema-file <path>", "Read a JSON Schema Draft 2020-12 document from a file."),
|
|
111
|
+
option("--schema-stdin", "Read a JSON Schema Draft 2020-12 document from standard input."),
|
|
112
|
+
];
|
|
103
113
|
|
|
104
114
|
const HELP_ROOT = group(
|
|
105
115
|
CLI_NAME,
|
|
@@ -325,7 +335,7 @@ const HELP_ROOT = group(
|
|
|
325
335
|
command(
|
|
326
336
|
"list",
|
|
327
337
|
"List owned channels.",
|
|
328
|
-
`${CLI_NAME} channel list [--limit <n>] [--cursor <
|
|
338
|
+
`${CLI_NAME} channel list [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
329
339
|
{
|
|
330
340
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
331
341
|
},
|
|
@@ -352,7 +362,7 @@ const HELP_ROOT = group(
|
|
|
352
362
|
command(
|
|
353
363
|
"public-list",
|
|
354
364
|
"List publicly visible channels for a public handle.",
|
|
355
|
-
`${CLI_NAME} channel public-list <public-handle> [--limit <n>] [--cursor <
|
|
365
|
+
`${CLI_NAME} channel public-list <public-handle> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
356
366
|
{
|
|
357
367
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
358
368
|
},
|
|
@@ -450,7 +460,7 @@ const HELP_ROOT = group(
|
|
|
450
460
|
command(
|
|
451
461
|
"list",
|
|
452
462
|
"List channel publish keys for one channel.",
|
|
453
|
-
`${CLI_NAME} channel token list <channel> [--limit <n>] [--cursor <
|
|
463
|
+
`${CLI_NAME} channel token list <channel> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
454
464
|
{
|
|
455
465
|
options: [
|
|
456
466
|
LIMIT_OPTION,
|
|
@@ -522,7 +532,7 @@ const HELP_ROOT = group(
|
|
|
522
532
|
command(
|
|
523
533
|
"list",
|
|
524
534
|
"List posts for one owned channel.",
|
|
525
|
-
`${CLI_NAME} post list --channel <name-or-uuid> [--limit <n>] [--cursor <
|
|
535
|
+
`${CLI_NAME} post list --channel <name-or-uuid> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
526
536
|
{
|
|
527
537
|
options: [
|
|
528
538
|
option(
|
|
@@ -563,7 +573,7 @@ const HELP_ROOT = group(
|
|
|
563
573
|
command(
|
|
564
574
|
"public-list",
|
|
565
575
|
"List public posts for one public channel.",
|
|
566
|
-
`${CLI_NAME} post public-list <public-handle> <channel-name> [--limit <n>] [--cursor <
|
|
576
|
+
`${CLI_NAME} post public-list <public-handle> <channel-name> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
567
577
|
{
|
|
568
578
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
569
579
|
},
|
|
@@ -579,7 +589,7 @@ const HELP_ROOT = group(
|
|
|
579
589
|
command(
|
|
580
590
|
"shared-list",
|
|
581
591
|
"List posts in a shared channel by share token.",
|
|
582
|
-
`${CLI_NAME} post shared-list <share-token> [--limit <n>] [--cursor <
|
|
592
|
+
`${CLI_NAME} post shared-list <share-token> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
583
593
|
{
|
|
584
594
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
585
595
|
},
|
|
@@ -627,7 +637,7 @@ const HELP_ROOT = group(
|
|
|
627
637
|
command(
|
|
628
638
|
"my",
|
|
629
639
|
"List posts from the owner feed.",
|
|
630
|
-
`${CLI_NAME} feed my [--channel <name-or-uuid>] [--limit <n>] [--cursor <
|
|
640
|
+
`${CLI_NAME} feed my [--channel <name-or-uuid>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
631
641
|
{
|
|
632
642
|
options: [
|
|
633
643
|
option(
|
|
@@ -644,7 +654,7 @@ const HELP_ROOT = group(
|
|
|
644
654
|
command(
|
|
645
655
|
"search",
|
|
646
656
|
"Search the owner feed.",
|
|
647
|
-
`${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--limit <n>] [--cursor <
|
|
657
|
+
`${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
648
658
|
{
|
|
649
659
|
options: [
|
|
650
660
|
option(
|
|
@@ -670,7 +680,7 @@ const HELP_ROOT = group(
|
|
|
670
680
|
command(
|
|
671
681
|
"list",
|
|
672
682
|
"List inbox threads.",
|
|
673
|
-
`${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--limit <n>] [--cursor <
|
|
683
|
+
`${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
674
684
|
{
|
|
675
685
|
options: [
|
|
676
686
|
option(
|
|
@@ -692,7 +702,7 @@ const HELP_ROOT = group(
|
|
|
692
702
|
command(
|
|
693
703
|
"show",
|
|
694
704
|
"Show one thread and its recent messages.",
|
|
695
|
-
`${CLI_NAME} inbox show <thread-id> [--limit <n>] [--cursor <
|
|
705
|
+
`${CLI_NAME} inbox show <thread-id> [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
696
706
|
{
|
|
697
707
|
options: [
|
|
698
708
|
LIMIT_OPTION,
|
|
@@ -706,7 +716,7 @@ const HELP_ROOT = group(
|
|
|
706
716
|
command(
|
|
707
717
|
"attachments",
|
|
708
718
|
"List attachment metadata for one message.",
|
|
709
|
-
`${CLI_NAME} inbox attachments <message-id> [--limit <n>] [--cursor <
|
|
719
|
+
`${CLI_NAME} inbox attachments <message-id> [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
710
720
|
{
|
|
711
721
|
options: [
|
|
712
722
|
LIMIT_OPTION,
|
|
@@ -720,10 +730,11 @@ const HELP_ROOT = group(
|
|
|
720
730
|
command(
|
|
721
731
|
"send",
|
|
722
732
|
"Send a first message to a recipient address.",
|
|
723
|
-
`${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]`,
|
|
733
|
+
`${CLI_NAME} inbox send <recipient> (--body <markdown> | --body-file <path> | --stdin | --payload <json> | --payload-file <path> | --payload-stdin) [--from <channel-name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
724
734
|
{
|
|
725
735
|
options: [
|
|
726
736
|
...BODY_OPTIONS,
|
|
737
|
+
...PAYLOAD_OPTIONS,
|
|
727
738
|
option(
|
|
728
739
|
"--from <channel-name-or-uuid>",
|
|
729
740
|
"Send on behalf of one of the owner's channels.",
|
|
@@ -739,16 +750,18 @@ const HELP_ROOT = group(
|
|
|
739
750
|
notes: [
|
|
740
751
|
"Recipient addresses support `@handle`, `@handle/channel`, `email@example.com`, user UUIDs, and channel UUIDs.",
|
|
741
752
|
"For bare UUIDs, the CLI treats a public user id as an account recipient and otherwise sends to a channel id.",
|
|
753
|
+
"Typed inboxes require `--payload`, `--payload-file`, or `--payload-stdin`; body text is optional when a payload is present.",
|
|
742
754
|
],
|
|
743
755
|
},
|
|
744
756
|
),
|
|
745
757
|
command(
|
|
746
758
|
"reply",
|
|
747
759
|
"Reply to an existing thread.",
|
|
748
|
-
`${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]`,
|
|
760
|
+
`${CLI_NAME} inbox reply <thread-id> (--body <markdown> | --body-file <path> | --stdin | --payload <json> | --payload-file <path> | --payload-stdin) [--from <channel-name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
749
761
|
{
|
|
750
762
|
options: [
|
|
751
763
|
...BODY_OPTIONS,
|
|
764
|
+
...PAYLOAD_OPTIONS,
|
|
752
765
|
option(
|
|
753
766
|
"--from <channel-name-or-uuid>",
|
|
754
767
|
"Reply as one of the owner's channels when the thread mailbox type allows it.",
|
|
@@ -761,6 +774,51 @@ const HELP_ROOT = group(
|
|
|
761
774
|
PROFILE_OPTION,
|
|
762
775
|
JSON_OPTION,
|
|
763
776
|
],
|
|
777
|
+
notes: [
|
|
778
|
+
"Typed inbox replies can include body text, a payload, or both.",
|
|
779
|
+
],
|
|
780
|
+
},
|
|
781
|
+
),
|
|
782
|
+
group(
|
|
783
|
+
"schema",
|
|
784
|
+
"Inspect and manage typed inbox JSON Schemas.",
|
|
785
|
+
[
|
|
786
|
+
command(
|
|
787
|
+
"show",
|
|
788
|
+
"Show a public account or channel inbox schema.",
|
|
789
|
+
`${CLI_NAME} inbox schema show <@handle|@handle/channel> [--profile <name>] [--json]`,
|
|
790
|
+
{
|
|
791
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
792
|
+
},
|
|
793
|
+
),
|
|
794
|
+
command(
|
|
795
|
+
"set",
|
|
796
|
+
"Set an account or channel inbox schema.",
|
|
797
|
+
[
|
|
798
|
+
`${CLI_NAME} inbox schema set account (--schema <json> | --schema-file <path> | --schema-stdin) [--profile <name>] [--json]`,
|
|
799
|
+
`${CLI_NAME} inbox schema set channel <channel-name-or-uuid> (--schema <json> | --schema-file <path> | --schema-stdin) [--profile <name>] [--json]`,
|
|
800
|
+
],
|
|
801
|
+
{
|
|
802
|
+
options: [...SCHEMA_OPTIONS, PROFILE_OPTION, JSON_OPTION],
|
|
803
|
+
notes: [
|
|
804
|
+
"The backend stores one JSON Schema Draft 2020-12 document per inbox; use `oneOf` or `anyOf` inside the schema for multiple accepted shapes.",
|
|
805
|
+
],
|
|
806
|
+
},
|
|
807
|
+
),
|
|
808
|
+
command(
|
|
809
|
+
"remove",
|
|
810
|
+
"Remove an account or channel inbox schema.",
|
|
811
|
+
[
|
|
812
|
+
`${CLI_NAME} inbox schema remove account [--profile <name>] [--json]`,
|
|
813
|
+
`${CLI_NAME} inbox schema remove channel <channel-name-or-uuid> [--profile <name>] [--json]`,
|
|
814
|
+
],
|
|
815
|
+
{
|
|
816
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
817
|
+
},
|
|
818
|
+
),
|
|
819
|
+
],
|
|
820
|
+
{
|
|
821
|
+
usage: [`${CLI_NAME} inbox schema <subcommand>`],
|
|
764
822
|
},
|
|
765
823
|
),
|
|
766
824
|
command(
|
|
@@ -802,7 +860,7 @@ const HELP_ROOT = group(
|
|
|
802
860
|
command(
|
|
803
861
|
"list",
|
|
804
862
|
"List screened external email waiting for a decision.",
|
|
805
|
-
`${CLI_NAME} inbox screening list [--limit <n>] [--cursor <
|
|
863
|
+
`${CLI_NAME} inbox screening list [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
806
864
|
{
|
|
807
865
|
options: [
|
|
808
866
|
LIMIT_OPTION,
|
|
@@ -816,7 +874,7 @@ const HELP_ROOT = group(
|
|
|
816
874
|
command(
|
|
817
875
|
"processing",
|
|
818
876
|
"List released external email in the processing queue.",
|
|
819
|
-
`${CLI_NAME} inbox screening processing [--limit <n>] [--cursor <
|
|
877
|
+
`${CLI_NAME} inbox screening processing [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
820
878
|
{
|
|
821
879
|
options: [
|
|
822
880
|
LIMIT_OPTION,
|
package/src/lib/human.ts
CHANGED
|
@@ -80,8 +80,17 @@ export function renderBodyBlock(body: string): string {
|
|
|
80
80
|
return indent(normalized);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
export function renderPagination(
|
|
84
|
-
|
|
83
|
+
export function renderPagination(
|
|
84
|
+
nextCursor?: string | null,
|
|
85
|
+
nextCommand?: string,
|
|
86
|
+
): string {
|
|
87
|
+
if (!nextCursor) {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return nextCommand
|
|
92
|
+
? `More results: ${nextCommand}`
|
|
93
|
+
: `More results: --cursor ${nextCursor}`;
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
export function joinBlocks(blocks: Array<string | undefined | null | false>): string {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import { booleanFlag, stringFlag, type ParsedArgs } from "./args";
|
|
4
|
+
import { CliError } from "./errors";
|
|
5
|
+
import { readStdin } from "./body-input";
|
|
6
|
+
|
|
7
|
+
export interface ResolveJsonInputOptions {
|
|
8
|
+
flags: ParsedArgs["flags"];
|
|
9
|
+
inlineKey: string;
|
|
10
|
+
fileKey: string;
|
|
11
|
+
stdinKey: string;
|
|
12
|
+
label: string;
|
|
13
|
+
requireObject?: boolean;
|
|
14
|
+
readFileText?: (path: string) => Promise<string>;
|
|
15
|
+
readStdinText?: () => Promise<string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function resolveJsonInput({
|
|
19
|
+
flags,
|
|
20
|
+
inlineKey,
|
|
21
|
+
fileKey,
|
|
22
|
+
stdinKey,
|
|
23
|
+
label,
|
|
24
|
+
requireObject = true,
|
|
25
|
+
readFileText = (path) => readFile(path, "utf8"),
|
|
26
|
+
readStdinText = () => readStdin(),
|
|
27
|
+
}: ResolveJsonInputOptions): Promise<Record<string, unknown> | undefined> {
|
|
28
|
+
const inlineJson = stringFlag(flags, inlineKey);
|
|
29
|
+
const jsonFile = stringFlag(flags, fileKey);
|
|
30
|
+
const useStdin = booleanFlag(flags, stdinKey);
|
|
31
|
+
const providedCount = [
|
|
32
|
+
inlineJson !== undefined,
|
|
33
|
+
jsonFile !== undefined,
|
|
34
|
+
useStdin,
|
|
35
|
+
].filter(Boolean).length;
|
|
36
|
+
|
|
37
|
+
if (providedCount === 0) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (providedCount > 1) {
|
|
42
|
+
throw new CliError(
|
|
43
|
+
`Use only one of \`--${kebab(inlineKey)}\`, \`--${kebab(fileKey)}\`, or \`--${kebab(stdinKey)}\``,
|
|
44
|
+
2,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const source =
|
|
49
|
+
inlineJson ?? (jsonFile !== undefined ? await readFileText(jsonFile) : await readStdinText());
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const value = JSON.parse(source);
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
requireObject &&
|
|
56
|
+
(!value || typeof value !== "object" || Array.isArray(value))
|
|
57
|
+
) {
|
|
58
|
+
throw new CliError(`${label} must be a JSON object`, 2);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return value as Record<string, unknown>;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error instanceof CliError) {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new CliError(`${label} must be valid JSON`, 2);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function kebab(value: string): string {
|
|
72
|
+
return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
73
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ParsedArgs } from "./args";
|
|
2
|
+
|
|
3
|
+
const CLI_NAME = "clankm";
|
|
4
|
+
|
|
5
|
+
const PAGINATION_FLAG_ORDER: Array<
|
|
6
|
+
[key: string, flag: string]
|
|
7
|
+
> = [
|
|
8
|
+
["profile", "--profile"],
|
|
9
|
+
["baseUrl", "--base-url"],
|
|
10
|
+
["channel", "--channel"],
|
|
11
|
+
["channelId", "--channel"],
|
|
12
|
+
["status", "--status"],
|
|
13
|
+
["mailbox", "--mailbox"],
|
|
14
|
+
["limit", "--limit"],
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export interface PaginationInfo {
|
|
18
|
+
nextCursor: string;
|
|
19
|
+
nextCommand?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function paginationInfo(
|
|
23
|
+
args: ParsedArgs,
|
|
24
|
+
nextCursor?: string | null,
|
|
25
|
+
options: { json?: boolean } = {},
|
|
26
|
+
): PaginationInfo | undefined {
|
|
27
|
+
if (!nextCursor) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
nextCursor,
|
|
33
|
+
nextCommand: args.flags.channelToken
|
|
34
|
+
? undefined
|
|
35
|
+
: nextCommand(args, nextCursor, options),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function paginatedJson<T extends object>(
|
|
40
|
+
args: ParsedArgs,
|
|
41
|
+
response: T & { nextCursor?: string },
|
|
42
|
+
): T & { nextCursor?: string; pagination?: PaginationInfo } {
|
|
43
|
+
return {
|
|
44
|
+
...response,
|
|
45
|
+
pagination: paginationInfo(args, response.nextCursor, { json: true }),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function paginationFlagParts(args: ParsedArgs): string[] {
|
|
50
|
+
const parts: string[] = [];
|
|
51
|
+
const seenFlags = new Set<string>();
|
|
52
|
+
|
|
53
|
+
for (const [key, flag] of PAGINATION_FLAG_ORDER) {
|
|
54
|
+
if (seenFlags.has(flag)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const value = args.flags[key];
|
|
59
|
+
|
|
60
|
+
if (typeof value !== "string") {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
parts.push(flag, value);
|
|
65
|
+
seenFlags.add(flag);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return parts;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function nextCommand(
|
|
72
|
+
args: ParsedArgs,
|
|
73
|
+
nextCursor: string,
|
|
74
|
+
options: { json?: boolean },
|
|
75
|
+
): string {
|
|
76
|
+
const parts = [
|
|
77
|
+
CLI_NAME,
|
|
78
|
+
...args.commandPath,
|
|
79
|
+
...args.positionals,
|
|
80
|
+
...paginationFlagParts(args),
|
|
81
|
+
"--cursor",
|
|
82
|
+
nextCursor,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
if (options.json) {
|
|
86
|
+
parts.push("--json");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return parts.map(shellQuote).join(" ");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function shellQuote(value: string): string {
|
|
93
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
98
|
+
}
|
package/src/types/api.ts
CHANGED
|
@@ -47,6 +47,9 @@ export interface UserAttributes {
|
|
|
47
47
|
email?: string;
|
|
48
48
|
public_profile_id?: string;
|
|
49
49
|
public_handle?: string | null;
|
|
50
|
+
inbox_schema?: Record<string, unknown> | null;
|
|
51
|
+
inbox_schema_hash?: string | null;
|
|
52
|
+
inbox_schema_updated_at?: string | null;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
export interface ChannelAttributes {
|
|
@@ -55,6 +58,9 @@ export interface ChannelAttributes {
|
|
|
55
58
|
visibility: string;
|
|
56
59
|
publicly_listed?: boolean;
|
|
57
60
|
posting_paused_until?: string | null;
|
|
61
|
+
inbox_schema?: Record<string, unknown> | null;
|
|
62
|
+
inbox_schema_hash?: string | null;
|
|
63
|
+
inbox_schema_updated_at?: string | null;
|
|
58
64
|
inserted_at?: string;
|
|
59
65
|
updated_at?: string;
|
|
60
66
|
}
|
|
@@ -117,6 +123,7 @@ export interface ThreadAttributes {
|
|
|
117
123
|
|
|
118
124
|
export interface MessageAttributes {
|
|
119
125
|
body: string;
|
|
126
|
+
payload?: Record<string, unknown> | null;
|
|
120
127
|
sender_owner_id?: string | null;
|
|
121
128
|
sender_channel_id?: string | null;
|
|
122
129
|
external_email_sender_id?: string | null;
|