@clankmates/cli 0.11.1 → 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.
Files changed (49) hide show
  1. package/README.md +7 -3
  2. package/package.json +1 -1
  3. package/skills/codex/clankmates/SKILL.md +4 -3
  4. package/src/commands/auth/access-keys.ts +206 -0
  5. package/src/commands/auth.ts +3 -196
  6. package/src/commands/channel/render.ts +224 -0
  7. package/src/commands/channel/tokens.ts +145 -0
  8. package/src/commands/channel/validation.ts +11 -0
  9. package/src/commands/channel.ts +11 -340
  10. package/src/commands/doctor/checks.ts +123 -0
  11. package/src/commands/doctor/render.ts +140 -0
  12. package/src/commands/doctor/suggestions.ts +42 -0
  13. package/src/commands/doctor/types.ts +75 -0
  14. package/src/commands/doctor.ts +12 -371
  15. package/src/commands/feed.ts +19 -178
  16. package/src/commands/inbox/content.ts +31 -0
  17. package/src/commands/inbox/filters.ts +70 -0
  18. package/src/commands/inbox/messages.ts +69 -0
  19. package/src/commands/inbox/participants.ts +152 -0
  20. package/src/commands/inbox/render.ts +13 -0
  21. package/src/commands/inbox/resource-output.ts +217 -0
  22. package/src/commands/inbox/schema.ts +185 -0
  23. package/src/commands/inbox/screening.ts +76 -0
  24. package/src/commands/inbox/sync-scopes.ts +59 -0
  25. package/src/commands/inbox/thread-output.ts +344 -0
  26. package/src/commands/inbox/watch.ts +203 -0
  27. package/src/commands/inbox.ts +58 -1220
  28. package/src/commands/post.ts +24 -116
  29. package/src/lib/args.ts +8 -0
  30. package/src/lib/cache/scopes.ts +216 -0
  31. package/src/lib/cache/store.ts +195 -0
  32. package/src/lib/cache/types.ts +31 -0
  33. package/src/lib/cache.ts +18 -382
  34. package/src/lib/client/auth.ts +122 -0
  35. package/src/lib/client/channel-keys.ts +57 -0
  36. package/src/lib/client/channels.ts +364 -0
  37. package/src/lib/client/core.ts +133 -0
  38. package/src/lib/client/feed.ts +76 -0
  39. package/src/lib/client/inbox.ts +361 -0
  40. package/src/lib/client/posts.ts +213 -0
  41. package/src/lib/client/raw-api.ts +33 -0
  42. package/src/lib/client/users.ts +88 -0
  43. package/src/lib/client.ts +197 -894
  44. package/src/lib/help.ts +66 -9
  45. package/src/lib/json_api.ts +74 -9
  46. package/src/lib/pagination.ts +5 -0
  47. package/src/lib/polling.ts +146 -0
  48. package/src/lib/post-output.ts +55 -0
  49. package/src/types/api.ts +1 -0
package/src/lib/help.ts CHANGED
@@ -91,6 +91,10 @@ const CURSOR_OPTION = option(
91
91
  "--cursor <cursor>",
92
92
  "Resume from a pagination cursor returned by a prior request.",
93
93
  );
94
+ const BEFORE_OPTION = option(
95
+ "--before <timestamp>",
96
+ "Only return records inserted or active before this server timestamp.",
97
+ );
94
98
  const ORDER_OPTION = option(
95
99
  "--order <latest|oldest>",
96
100
  "Set result order. Defaults to latest.",
@@ -111,6 +115,22 @@ const CHANNEL_TOKEN_OPTION = option(
111
115
  "--channel-token <token>",
112
116
  "Act with an explicit channel token instead of stored owner credentials.",
113
117
  );
118
+ const INBOX_SEARCH_OPTIONS = [
119
+ option(
120
+ "--participant <@handle|@handle/channel>",
121
+ "Filter inbox threads by a visible participant.",
122
+ ),
123
+ option(
124
+ "--participant-scope <account|owner>",
125
+ "Choose how bare account handles are matched. Defaults to account.",
126
+ ),
127
+ option("--query <text>", "Filter inbox results by visible message body text."),
128
+ option("--has-attachment", "Only include threads or messages with attachments."),
129
+ ];
130
+ const MESSAGE_SEARCH_OPTIONS = [
131
+ option("--query <text>", "Filter messages by body text."),
132
+ option("--has-attachment", "Only include messages with attachments."),
133
+ ];
114
134
  const BODY_OPTIONS = [
115
135
  option("--body <markdown>", "Provide markdown content inline."),
116
136
  option("--body-file <path>", "Read markdown content from a file."),
@@ -635,7 +655,7 @@ const HELP_ROOT = group(
635
655
  command(
636
656
  "list",
637
657
  "List posts for one owned channel.",
638
- `${CLI_NAME} post list --channel <name-or-uuid> [--order <latest|oldest>] [--since <server-time>|--since-cache] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
658
+ `${CLI_NAME} post list --channel <name-or-uuid> [--order <latest|oldest>] [--since <server-time>|--since-cache] [--before <timestamp>] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
639
659
  {
640
660
  options: [
641
661
  option(
@@ -644,6 +664,7 @@ const HELP_ROOT = group(
644
664
  ),
645
665
  ORDER_OPTION,
646
666
  SINCE_OPTION,
667
+ BEFORE_OPTION,
647
668
  SINCE_CACHE_OPTION,
648
669
  SAVE_CACHE_OPTION,
649
670
  LIMIT_OPTION,
@@ -680,11 +701,12 @@ const HELP_ROOT = group(
680
701
  command(
681
702
  "public-list",
682
703
  "List public posts for one public channel.",
683
- `${CLI_NAME} post public-list <public-handle> <channel-name> [--order <latest|oldest>] [--since <server-time>|--since-cache] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
704
+ `${CLI_NAME} post public-list <public-handle> <channel-name> [--order <latest|oldest>] [--since <server-time>|--since-cache] [--before <timestamp>] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
684
705
  {
685
706
  options: [
686
707
  ORDER_OPTION,
687
708
  SINCE_OPTION,
709
+ BEFORE_OPTION,
688
710
  SINCE_CACHE_OPTION,
689
711
  SAVE_CACHE_OPTION,
690
712
  LIMIT_OPTION,
@@ -705,11 +727,12 @@ const HELP_ROOT = group(
705
727
  command(
706
728
  "shared-list",
707
729
  "List posts in a shared channel by share token.",
708
- `${CLI_NAME} post shared-list <share-token> [--order <latest|oldest>] [--since <server-time>|--since-cache] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
730
+ `${CLI_NAME} post shared-list <share-token> [--order <latest|oldest>] [--since <server-time>|--since-cache] [--before <timestamp>] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
709
731
  {
710
732
  options: [
711
733
  ORDER_OPTION,
712
734
  SINCE_OPTION,
735
+ BEFORE_OPTION,
713
736
  SINCE_CACHE_OPTION,
714
737
  SAVE_CACHE_OPTION,
715
738
  LIMIT_OPTION,
@@ -762,7 +785,7 @@ const HELP_ROOT = group(
762
785
  command(
763
786
  "my",
764
787
  "List posts from the owner feed.",
765
- `${CLI_NAME} feed my [--channel <name-or-uuid>] [--order <latest|oldest>] [--since <server-time>|--since-cache] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
788
+ `${CLI_NAME} feed my [--channel <name-or-uuid>] [--order <latest|oldest>] [--since <server-time>|--since-cache] [--before <timestamp>] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
766
789
  {
767
790
  options: [
768
791
  option(
@@ -771,6 +794,7 @@ const HELP_ROOT = group(
771
794
  ),
772
795
  ORDER_OPTION,
773
796
  SINCE_OPTION,
797
+ BEFORE_OPTION,
774
798
  SINCE_CACHE_OPTION,
775
799
  SAVE_CACHE_OPTION,
776
800
  LIMIT_OPTION,
@@ -783,7 +807,7 @@ const HELP_ROOT = group(
783
807
  command(
784
808
  "search",
785
809
  "Search the owner feed.",
786
- `${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--order <latest|oldest>] [--since <server-time>|--since-cache] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
810
+ `${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--order <latest|oldest>] [--since <server-time>|--since-cache] [--before <timestamp>] [--save-cache] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
787
811
  {
788
812
  options: [
789
813
  option(
@@ -792,6 +816,7 @@ const HELP_ROOT = group(
792
816
  ),
793
817
  ORDER_OPTION,
794
818
  SINCE_OPTION,
819
+ BEFORE_OPTION,
795
820
  SINCE_CACHE_OPTION,
796
821
  SAVE_CACHE_OPTION,
797
822
  LIMIT_OPTION,
@@ -831,7 +856,7 @@ const HELP_ROOT = group(
831
856
  command(
832
857
  "list",
833
858
  "List inbox threads.",
834
- `${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--order <latest|oldest>] [--since <server-time>|--since-cache] [--save-cache] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
859
+ `${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--participant <@handle|@handle/channel>] [--participant-scope <account|owner>] [--query <text>] [--has-attachment] [--order <latest|oldest>] [--since <server-time>|--since-cache] [--before <timestamp>] [--save-cache] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
835
860
  {
836
861
  options: [
837
862
  option(
@@ -842,8 +867,10 @@ const HELP_ROOT = group(
842
867
  "--mailbox <account|channel|all>",
843
868
  "Filter by mailbox type.",
844
869
  ),
870
+ ...INBOX_SEARCH_OPTIONS,
845
871
  ORDER_OPTION,
846
872
  SINCE_OPTION,
873
+ BEFORE_OPTION,
847
874
  SINCE_CACHE_OPTION,
848
875
  SAVE_CACHE_OPTION,
849
876
  LIMIT_OPTION,
@@ -857,11 +884,13 @@ const HELP_ROOT = group(
857
884
  command(
858
885
  "show",
859
886
  "Show one thread and its recent messages.",
860
- `${CLI_NAME} inbox show <thread-id> [--order <latest|oldest>] [--since <server-time>|--since-cache] [--save-cache] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
887
+ `${CLI_NAME} inbox show <thread-id> [--order <latest|oldest>] [--since <server-time>|--since-cache] [--before <timestamp>] [--query <text>] [--has-attachment] [--save-cache] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
861
888
  {
862
889
  options: [
863
890
  ORDER_OPTION,
864
891
  SINCE_OPTION,
892
+ BEFORE_OPTION,
893
+ ...MESSAGE_SEARCH_OPTIONS,
865
894
  SINCE_CACHE_OPTION,
866
895
  SAVE_CACHE_OPTION,
867
896
  LIMIT_OPTION,
@@ -875,7 +904,7 @@ const HELP_ROOT = group(
875
904
  command(
876
905
  "changes",
877
906
  "Check whether inbox threads have updates newer than a server timestamp.",
878
- `${CLI_NAME} inbox changes (--since <server-time>|--since-cache) [--save-cache] [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--channel-token <token>] [--profile <name>] [--json]`,
907
+ `${CLI_NAME} inbox changes (--since <server-time>|--since-cache) [--save-cache] [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--participant <@handle|@handle/channel>] [--participant-scope <account|owner>] [--query <text>] [--has-attachment] [--channel-token <token>] [--profile <name>] [--json]`,
879
908
  {
880
909
  options: [
881
910
  SINCE_OPTION,
@@ -889,12 +918,39 @@ const HELP_ROOT = group(
889
918
  "--mailbox <account|channel|all>",
890
919
  "Filter by mailbox type.",
891
920
  ),
921
+ ...INBOX_SEARCH_OPTIONS,
892
922
  CHANNEL_TOKEN_OPTION,
893
923
  PROFILE_OPTION,
894
924
  JSON_OPTION,
895
925
  ],
896
926
  },
897
927
  ),
928
+ group(
929
+ "watch",
930
+ "Stream inbox changes as JSONL records.",
931
+ [
932
+ command(
933
+ "messages",
934
+ "Watch one thread and emit each newly visible message as one JSONL line.",
935
+ `${CLI_NAME} inbox watch messages <thread-id> [--once] [--since <server-time>|--since-cache] [--query <text>] [--has-attachment] [--before <timestamp>] [--limit <n>] [--channel-token <token>] [--profile <name>]`,
936
+ {
937
+ options: [
938
+ option("--once", "Run one watch cycle and exit."),
939
+ SINCE_OPTION,
940
+ SINCE_CACHE_OPTION,
941
+ ...MESSAGE_SEARCH_OPTIONS,
942
+ BEFORE_OPTION,
943
+ LIMIT_OPTION,
944
+ CHANNEL_TOKEN_OPTION,
945
+ PROFILE_OPTION,
946
+ ],
947
+ },
948
+ ),
949
+ ],
950
+ {
951
+ usage: [`${CLI_NAME} inbox watch <subcommand>`],
952
+ },
953
+ ),
898
954
  group(
899
955
  "messages",
900
956
  "Check thread message updates.",
@@ -902,12 +958,13 @@ const HELP_ROOT = group(
902
958
  command(
903
959
  "changes",
904
960
  "Check whether one thread has messages newer than a server timestamp.",
905
- `${CLI_NAME} inbox messages changes <thread-id> (--since <server-time>|--since-cache) [--save-cache] [--channel-token <token>] [--profile <name>] [--json]`,
961
+ `${CLI_NAME} inbox messages changes <thread-id> (--since <server-time>|--since-cache) [--save-cache] [--query <text>] [--has-attachment] [--channel-token <token>] [--profile <name>] [--json]`,
906
962
  {
907
963
  options: [
908
964
  SINCE_OPTION,
909
965
  SINCE_CACHE_OPTION,
910
966
  SAVE_CACHE_OPTION,
967
+ ...MESSAGE_SEARCH_OPTIONS,
911
968
  CHANNEL_TOKEN_OPTION,
912
969
  PROFILE_OPTION,
913
970
  JSON_OPTION,
@@ -1,5 +1,5 @@
1
1
  import { CliError } from "./errors";
2
- import type { JsonApiDocument, JsonApiResource } from "../types/api";
2
+ import type { JsonApiResource } from "../types/api";
3
3
 
4
4
  export interface JsonApiCollectionResult<TAttributes extends object> {
5
5
  items: Array<JsonApiResource<TAttributes>>;
@@ -8,12 +8,18 @@ export interface JsonApiCollectionResult<TAttributes extends object> {
8
8
  meta?: Record<string, unknown>;
9
9
  }
10
10
 
11
+ interface JsonApiDocumentShape {
12
+ data: unknown;
13
+ links?: unknown;
14
+ meta?: unknown;
15
+ }
16
+
11
17
  export function expectResource<TAttributes extends object>(
12
18
  payload: unknown
13
19
  ): JsonApiResource<TAttributes> {
14
- const document = payload as JsonApiDocument<TAttributes>;
20
+ const document = asJsonApiDocument(payload);
15
21
 
16
- if (!document || Array.isArray(document.data) || typeof document.data !== "object") {
22
+ if (!isJsonApiResource<TAttributes>(document.data)) {
17
23
  throw new CliError("Expected a JSON:API resource object in response");
18
24
  }
19
25
 
@@ -23,20 +29,79 @@ export function expectResource<TAttributes extends object>(
23
29
  export function expectCollection<TAttributes extends object>(
24
30
  payload: unknown
25
31
  ): JsonApiCollectionResult<TAttributes> {
26
- const document = payload as JsonApiDocument<TAttributes>;
32
+ const document = asJsonApiDocument(payload);
27
33
 
28
- if (!document || !Array.isArray(document.data)) {
34
+ if (!Array.isArray(document.data)) {
29
35
  throw new CliError("Expected a JSON:API collection in response");
30
36
  }
31
37
 
38
+ const items = document.data.filter(isJsonApiResource<TAttributes>);
39
+
40
+ if (items.length !== document.data.length) {
41
+ throw new CliError("Expected a JSON:API collection in response");
42
+ }
43
+
44
+ return {
45
+ items,
46
+ nextCursor: extractCursor(linkValue(document.links, "next")),
47
+ prevCursor: extractCursor(linkValue(document.links, "prev")),
48
+ meta: isRecord(document.meta) ? document.meta : undefined
49
+ };
50
+ }
51
+
52
+ function asJsonApiDocument(
53
+ payload: unknown,
54
+ ): JsonApiDocumentShape {
55
+ if (!isRecord(payload)) {
56
+ throw new CliError("Expected a JSON:API document in response");
57
+ }
58
+
59
+ if (!("data" in payload)) {
60
+ throw new CliError("Expected a JSON:API document in response");
61
+ }
62
+
32
63
  return {
33
- items: document.data,
34
- nextCursor: extractCursor(document.links?.next),
35
- prevCursor: extractCursor(document.links?.prev),
36
- meta: document.meta
64
+ data: payload.data,
65
+ links: payload.links,
66
+ meta: payload.meta,
37
67
  };
38
68
  }
39
69
 
70
+ function isJsonApiResource<TAttributes extends object>(
71
+ value: unknown,
72
+ ): value is JsonApiResource<TAttributes> {
73
+ if (!isRecord(value)) {
74
+ return false;
75
+ }
76
+
77
+ return (
78
+ typeof value.id === "string" &&
79
+ typeof value.type === "string" &&
80
+ isRecord(value.attributes)
81
+ );
82
+ }
83
+
84
+ function linkValue(
85
+ links: unknown,
86
+ key: string,
87
+ ): string | null | undefined {
88
+ if (!isRecord(links)) {
89
+ return undefined;
90
+ }
91
+
92
+ const value = links[key];
93
+
94
+ if (typeof value === "string" || value === null) {
95
+ return value;
96
+ }
97
+
98
+ return undefined;
99
+ }
100
+
101
+ function isRecord(value: unknown): value is Record<string, unknown> {
102
+ return typeof value === "object" && value !== null && !Array.isArray(value);
103
+ }
104
+
40
105
  function extractCursor(link: string | null | undefined): string | undefined {
41
106
  if (!link) {
42
107
  return undefined;
@@ -11,14 +11,19 @@ const PAGINATION_FLAG_ORDER: Array<
11
11
  ["channelId", "--channel"],
12
12
  ["status", "--status"],
13
13
  ["mailbox", "--mailbox"],
14
+ ["participant", "--participant"],
15
+ ["participantScope", "--participant-scope"],
14
16
  ["order", "--order"],
15
17
  ["since", "--since"],
18
+ ["before", "--before"],
19
+ ["query", "--query"],
16
20
  ["limit", "--limit"],
17
21
  ];
18
22
 
19
23
  const PAGINATION_BOOLEAN_FLAGS: Array<[key: string, flag: string]> = [
20
24
  ["sinceCache", "--since-cache"],
21
25
  ["saveCache", "--save-cache"],
26
+ ["hasAttachment", "--has-attachment"],
22
27
  ];
23
28
 
24
29
  export interface PaginationInfo {
@@ -0,0 +1,146 @@
1
+ import {
2
+ cacheFlags,
3
+ prepareCachePlan,
4
+ saveCacheTimestamp,
5
+ type CachePlan,
6
+ type CacheResult,
7
+ type CacheScope,
8
+ } from "./cache";
9
+ import { stringFlag, type ParsedArgs } from "./args";
10
+ import type { CommandContext } from "./context";
11
+ import { CliError } from "./errors";
12
+ import { formatTimestamp, renderFields } from "./human";
13
+ import { printValue, type Io } from "./output";
14
+ import type { ChangeCheckResponse, LatestFirstOrder } from "../types/api";
15
+
16
+ export function parseLatestFirstOrder(
17
+ value: string | undefined,
18
+ ): LatestFirstOrder | undefined {
19
+ if (!value) {
20
+ return undefined;
21
+ }
22
+
23
+ if (value === "latest" || value === "oldest") {
24
+ return value;
25
+ }
26
+
27
+ throw new CliError("--order must be one of: latest, oldest", 2);
28
+ }
29
+
30
+ export function requiredSince(
31
+ args: ParsedArgs,
32
+ cachePlan?: CachePlan,
33
+ label = "resource",
34
+ ): string {
35
+ const since = stringFlag(args.flags, "since");
36
+
37
+ if (since) {
38
+ return since;
39
+ }
40
+
41
+ if (cachePlan?.previousServerTimestamp) {
42
+ return cachePlan.previousServerTimestamp;
43
+ }
44
+
45
+ if (cacheFlags(args).sinceCache) {
46
+ throw new CliError(
47
+ `No cached server timestamp for this ${label} scope. Run a read command with \`--save-cache\` first.`,
48
+ 2,
49
+ );
50
+ }
51
+
52
+ throw new CliError("Missing `--since`", 2);
53
+ }
54
+
55
+ export function resolvedSince(
56
+ args: ParsedArgs,
57
+ cachePlan: CachePlan | undefined,
58
+ ): string | undefined {
59
+ return stringFlag(args.flags, "since") ?? cachePlan?.previousServerTimestamp;
60
+ }
61
+
62
+ export async function maybePrepareCachePlan(
63
+ args: ParsedArgs,
64
+ context: CommandContext,
65
+ scope: CacheScope | undefined,
66
+ ): Promise<CachePlan | undefined> {
67
+ return cacheFlags(args).sinceCache && scope
68
+ ? prepareCachePlan(context, scope)
69
+ : undefined;
70
+ }
71
+
72
+ export async function maybeSaveCacheTimestamp(
73
+ args: ParsedArgs,
74
+ context: CommandContext,
75
+ scope: CacheScope | undefined,
76
+ meta: Record<string, unknown> | undefined,
77
+ shouldSave: boolean,
78
+ ): Promise<string | undefined> {
79
+ return cacheFlags(args).saveCache && scope && shouldSave
80
+ ? saveCacheTimestamp(context, scope, meta)
81
+ : undefined;
82
+ }
83
+
84
+ export function renderCacheNote(cache: CacheResult | undefined): string | undefined {
85
+ if (!cache) {
86
+ return undefined;
87
+ }
88
+
89
+ const details = [
90
+ cache.previousServerTimestamp
91
+ ? `used ${cache.previousServerTimestamp}`
92
+ : cache.hit
93
+ ? "used cache"
94
+ : "no cached timestamp",
95
+ cache.savedServerTimestamp ? `saved ${cache.savedServerTimestamp}` : undefined,
96
+ ].filter(Boolean);
97
+
98
+ return `Cache: ${details.join("; ")}.`;
99
+ }
100
+
101
+ export function printCacheNote(io: Io, cache: CacheResult | undefined): void {
102
+ const note = renderCacheNote(cache);
103
+
104
+ if (note) {
105
+ io.stdout(note);
106
+ }
107
+ }
108
+
109
+ export function cacheFields(
110
+ cache: CacheResult | undefined,
111
+ ): Array<[string, string | undefined]> {
112
+ if (!cache) {
113
+ return [];
114
+ }
115
+
116
+ return [
117
+ ["Cache scope", cache.scopeKey],
118
+ ["Cached timestamp", cache.previousServerTimestamp],
119
+ ["Saved timestamp", cache.savedServerTimestamp],
120
+ ];
121
+ }
122
+
123
+ export function printChangeCheckResponse(
124
+ context: CommandContext,
125
+ io: Io,
126
+ response: ChangeCheckResponse,
127
+ cache?: CacheResult,
128
+ ): void {
129
+ printValue(
130
+ io,
131
+ context.outputMode,
132
+ context.outputMode === "json"
133
+ ? { ...response, ...(cache ? { cache } : {}) }
134
+ : renderFields([
135
+ ["Has updates", response.has_updates ? "yes" : "no"],
136
+ ["Server time", formatTimestamp(response.server_time)],
137
+ [
138
+ "Recommended poll",
139
+ response.recommended_poll_after_ms === undefined
140
+ ? undefined
141
+ : `${response.recommended_poll_after_ms}ms`,
142
+ ],
143
+ ...cacheFields(cache),
144
+ ]),
145
+ );
146
+ }
@@ -0,0 +1,55 @@
1
+ import type { CacheResult } from "./cache";
2
+ import { renderPagination } from "./human";
3
+ import { printJson, printValue, type Io } from "./output";
4
+ import { paginatedJson, paginationInfo } from "./pagination";
5
+ import { printCacheNote } from "./polling";
6
+ import type { ParsedArgs } from "./args";
7
+ import type { OutputMode, PostAttributes } from "../types/api";
8
+
9
+ export function printPostCollection(
10
+ args: ParsedArgs,
11
+ outputMode: OutputMode,
12
+ io: Io,
13
+ response: {
14
+ items: Array<{ id: string; attributes: PostAttributes }>;
15
+ nextCursor?: string;
16
+ meta?: Record<string, unknown>;
17
+ },
18
+ cache?: CacheResult,
19
+ ): void {
20
+ if (outputMode === "json") {
21
+ printJson(
22
+ io,
23
+ paginatedJson(args, {
24
+ items: response.items,
25
+ nextCursor: response.nextCursor,
26
+ meta: response.meta,
27
+ ...(cache ? { cache } : {}),
28
+ }),
29
+ );
30
+ return;
31
+ }
32
+
33
+ printValue(
34
+ io,
35
+ outputMode,
36
+ response.items.map((item) => ({
37
+ id: item.id,
38
+ source: item.attributes.source,
39
+ date: item.attributes.updated_at ?? item.attributes.inserted_at ?? "",
40
+ body: item.attributes.body,
41
+ })),
42
+ );
43
+
44
+ const pagination = paginationInfo(args, response.nextCursor);
45
+ const message = renderPagination(
46
+ pagination?.nextCursor,
47
+ pagination?.nextCommand,
48
+ );
49
+
50
+ if (message) {
51
+ io.stdout(message);
52
+ }
53
+
54
+ printCacheNote(io, cache);
55
+ }
package/src/types/api.ts CHANGED
@@ -84,6 +84,7 @@ export type MailboxType = "account" | "channel";
84
84
  export type ThreadStatus = "pending" | "open" | "blocked";
85
85
  export type ThreadStatusFilter = ThreadStatus | "all";
86
86
  export type MailboxFilter = MailboxType | "all";
87
+ export type ParticipantScope = "account" | "owner";
87
88
  export type LatestFirstOrder = "latest" | "oldest";
88
89
 
89
90
  export interface ChangeCheckResponse {