@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/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: string;
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: string;
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(config: ConfigFile, profileName?: string): { profileName: string; profile: ProfileConfig } {
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(undefined, profile.baseUrl)
131
- }
134
+ baseUrl: resolveBaseUrl(baseUrl, profile.baseUrl),
135
+ },
132
136
  };
133
137
  }
134
138
 
@@ -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(config, stringFlag(args.flags, "profile"));
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 <keyset>",
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 <keyset>] [--profile <name>] [--json]`,
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 <keyset>] [--profile <name>] [--json]`,
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 <keyset>] [--profile <name>] [--json]`,
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 <keyset>] [--profile <name>] [--json]`,
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 <keyset>] [--profile <name>] [--json]`,
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 <keyset>] [--profile <name>] [--json]`,
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 <keyset>] [--profile <name>] [--json]`,
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 <keyset>] [--profile <name>] [--json]`,
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 <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
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 <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
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 <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
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 <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
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 <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
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(nextCursor?: string | null): string {
84
- return nextCursor ? `More results: --cursor ${nextCursor}` : "";
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;