@clankmates/cli 0.10.3 → 0.11.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.
@@ -1,3 +1,17 @@
1
+ import {
2
+ assertSinceFlags,
3
+ authenticatedActorKey,
4
+ cacheFlags,
5
+ cacheResult,
6
+ changeResponseMeta,
7
+ inboxMessagesScope,
8
+ inboxThreadsScope,
9
+ prepareCachePlan,
10
+ saveCacheTimestamp,
11
+ type CachePlan,
12
+ type CacheResult,
13
+ type CacheScope,
14
+ } from "../lib/cache";
1
15
  import {
2
16
  booleanFlag,
3
17
  integerFlag,
@@ -21,10 +35,12 @@ import { resolveJsonInput } from "../lib/json-input";
21
35
  import { printJson, printValue, type Io } from "../lib/output";
22
36
  import { paginatedJson, paginationInfo } from "../lib/pagination";
23
37
  import type {
38
+ ChangeCheckResponse,
24
39
  ExternalEmailAcceptance,
25
40
  ExternalEmailIntakeAttributes,
26
41
  InboxRecipient,
27
42
  InboxSender,
43
+ LatestFirstOrder,
28
44
  MailboxFilter,
29
45
  MessageAttachmentAttributes,
30
46
  MessageAttributes,
@@ -40,28 +56,73 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
40
56
  switch (subcommand) {
41
57
  case "list": {
42
58
  const channelToken = stringFlag(args.flags, "channelToken");
59
+ assertSinceFlags(args);
60
+ const status = parseStatusFilter(stringFlag(args.flags, "status"));
61
+ const mailbox = parseMailboxFilter(stringFlag(args.flags, "mailbox"));
62
+ const cacheScope = await maybeInboxThreadsScope(
63
+ args,
64
+ context,
65
+ channelToken,
66
+ status,
67
+ mailbox,
68
+ );
69
+ const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
43
70
  const response = await context.client.listInboxThreads({
44
- status: parseStatusFilter(stringFlag(args.flags, "status")),
45
- mailbox: parseMailboxFilter(stringFlag(args.flags, "mailbox")),
71
+ status,
72
+ mailbox,
46
73
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
47
74
  cursor: stringFlag(args.flags, "cursor"),
75
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
76
+ since: resolvedSince(args, cachePlan),
48
77
  channelToken,
49
78
  });
79
+ const savedServerTimestamp = await maybeSaveCacheTimestamp(
80
+ args,
81
+ context,
82
+ cacheScope,
83
+ response.meta,
84
+ response.nextCursor === undefined,
85
+ );
50
86
 
51
- await printThreadCollection(args, context, io, response, channelToken);
87
+ await printThreadCollection(
88
+ args,
89
+ context,
90
+ io,
91
+ response,
92
+ channelToken,
93
+ cacheResult(cachePlan, savedServerTimestamp),
94
+ );
52
95
  return;
53
96
  }
54
97
 
55
98
  case "show": {
56
99
  const threadId = requiredPositional(args.positionals, 1, "Missing thread id");
57
100
  const channelToken = stringFlag(args.flags, "channelToken");
101
+ assertSinceFlags(args);
102
+ const cacheScope = await maybeInboxMessagesScope(
103
+ args,
104
+ context,
105
+ channelToken,
106
+ threadId,
107
+ );
108
+ const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
58
109
  const thread = await context.client.getThread(threadId, channelToken);
59
110
  const messages = await context.client.listMessagesForThread({
60
111
  threadId,
61
112
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
62
113
  cursor: stringFlag(args.flags, "cursor"),
114
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
115
+ since: resolvedSince(args, cachePlan),
63
116
  channelToken,
64
117
  });
118
+ const savedServerTimestamp = await maybeSaveCacheTimestamp(
119
+ args,
120
+ context,
121
+ cacheScope,
122
+ messages.meta,
123
+ messages.nextCursor === undefined,
124
+ );
125
+ const cache = cacheResult(cachePlan, savedServerTimestamp);
65
126
  const ownerIds = ownerIdsForThreadDisplay(thread, messages.items);
66
127
  const publicUsers =
67
128
  context.outputMode === "json" || ownerIds.length === 0
@@ -78,12 +139,55 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
78
139
  thread,
79
140
  messages: messages.items,
80
141
  nextCursor: messages.nextCursor,
142
+ meta: messages.meta,
143
+ ...(cache ? { cache } : {}),
81
144
  })
82
- : renderThreadWithMessages(args, thread, messages, publicUsers),
145
+ : renderThreadWithMessages(args, thread, messages, publicUsers, cache),
83
146
  );
84
147
  return;
85
148
  }
86
149
 
150
+ case "changes": {
151
+ const channelToken = stringFlag(args.flags, "channelToken");
152
+ assertSinceFlags(args);
153
+ const status = parseStatusFilter(stringFlag(args.flags, "status"));
154
+ const mailbox = parseMailboxFilter(stringFlag(args.flags, "mailbox"));
155
+ const cacheScope = await maybeInboxThreadsScope(
156
+ args,
157
+ context,
158
+ channelToken,
159
+ status,
160
+ mailbox,
161
+ );
162
+ const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
163
+ const response = await context.client.checkInboxThreadChanges({
164
+ since: requiredSince(args, cachePlan, "inbox thread"),
165
+ status,
166
+ mailbox,
167
+ channelToken,
168
+ });
169
+ const savedServerTimestamp = await maybeSaveCacheTimestamp(
170
+ args,
171
+ context,
172
+ cacheScope,
173
+ changeResponseMeta(response),
174
+ response.has_updates === false,
175
+ );
176
+
177
+ printChangeCheckResponse(
178
+ context,
179
+ io,
180
+ response,
181
+ cacheResult(cachePlan, savedServerTimestamp),
182
+ );
183
+ return;
184
+ }
185
+
186
+ case "messages": {
187
+ await runInboxMessagesCommand(context, args, io);
188
+ return;
189
+ }
190
+
87
191
  case "attachments": {
88
192
  const messageId = requiredPositional(args.positionals, 1, "Missing message id");
89
193
  const response = await context.client.listMessageAttachments({
@@ -601,6 +705,118 @@ function parseMailboxFilter(value: string | undefined): MailboxFilter | undefine
601
705
  throw new CliError("--mailbox must be one of: account, channel, all", 2);
602
706
  }
603
707
 
708
+ function parseLatestFirstOrder(value: string | undefined): LatestFirstOrder | undefined {
709
+ if (!value) {
710
+ return undefined;
711
+ }
712
+
713
+ if (value === "latest" || value === "oldest") {
714
+ return value;
715
+ }
716
+
717
+ throw new CliError("--order must be one of: latest, oldest", 2);
718
+ }
719
+
720
+ function requiredSince(
721
+ args: ParsedArgs,
722
+ cachePlan?: CachePlan,
723
+ label = "resource",
724
+ ): string {
725
+ const since = stringFlag(args.flags, "since");
726
+
727
+ if (since) {
728
+ return since;
729
+ }
730
+
731
+ if (cachePlan?.previousServerTimestamp) {
732
+ return cachePlan.previousServerTimestamp;
733
+ }
734
+
735
+ if (cacheFlags(args).sinceCache) {
736
+ throw new CliError(
737
+ `No cached server timestamp for this ${label} scope. Run a read command with \`--save-cache\` first.`,
738
+ 2,
739
+ );
740
+ }
741
+
742
+ if (!since) {
743
+ throw new CliError("Missing `--since`", 2);
744
+ }
745
+
746
+ return since;
747
+ }
748
+
749
+ async function runInboxMessagesCommand(
750
+ context: CommandContext,
751
+ args: ParsedArgs,
752
+ io: Io,
753
+ ): Promise<void> {
754
+ const subcommand = args.positionals[1];
755
+
756
+ switch (subcommand) {
757
+ case "changes": {
758
+ assertSinceFlags(args);
759
+ const threadId = requiredPositional(args.positionals, 2, "Missing thread id");
760
+ const channelToken = stringFlag(args.flags, "channelToken");
761
+ const cacheScope = await maybeInboxMessagesScope(
762
+ args,
763
+ context,
764
+ channelToken,
765
+ threadId,
766
+ );
767
+ const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
768
+ const response = await context.client.checkThreadMessageChanges({
769
+ threadId,
770
+ since: requiredSince(args, cachePlan, "inbox message"),
771
+ channelToken,
772
+ });
773
+ const savedServerTimestamp = await maybeSaveCacheTimestamp(
774
+ args,
775
+ context,
776
+ cacheScope,
777
+ changeResponseMeta(response),
778
+ response.has_updates === false,
779
+ );
780
+
781
+ printChangeCheckResponse(
782
+ context,
783
+ io,
784
+ response,
785
+ cacheResult(cachePlan, savedServerTimestamp),
786
+ );
787
+ return;
788
+ }
789
+
790
+ default:
791
+ throw new CliError("Unknown inbox messages subcommand", 2);
792
+ }
793
+ }
794
+
795
+ function printChangeCheckResponse(
796
+ context: CommandContext,
797
+ io: Io,
798
+ response: ChangeCheckResponse,
799
+ cache?: CacheResult,
800
+ ): void {
801
+ printValue(
802
+ io,
803
+ context.outputMode,
804
+ context.outputMode === "json"
805
+ ? { ...response, ...(cache ? { cache } : {}) }
806
+ : renderFields([
807
+ ["Has updates", response.has_updates ? "yes" : "no"],
808
+ ["Server time", formatTimestamp(response.server_time)],
809
+ [
810
+ "Recommended poll",
811
+ response.recommended_poll_after_ms === undefined
812
+ ? undefined
813
+ : `${response.recommended_poll_after_ms}ms`,
814
+ ],
815
+ ...cacheFields(cache),
816
+ ]),
817
+ );
818
+ }
819
+
604
820
  async function parseRecipient(
605
821
  context: CommandContext,
606
822
  value: string,
@@ -706,8 +922,10 @@ async function printThreadCollection(
706
922
  response: {
707
923
  items: Array<{ id: string; attributes: ThreadAttributes }>;
708
924
  nextCursor?: string;
925
+ meta?: Record<string, unknown>;
709
926
  },
710
927
  channelToken?: string,
928
+ cache?: CacheResult,
711
929
  ): Promise<void> {
712
930
  if (context.outputMode === "json") {
713
931
  printJson(
@@ -715,6 +933,8 @@ async function printThreadCollection(
715
933
  paginatedJson(args, {
716
934
  items: response.items,
717
935
  nextCursor: response.nextCursor,
936
+ meta: response.meta,
937
+ ...(cache ? { cache } : {}),
718
938
  }),
719
939
  );
720
940
  return Promise.resolve();
@@ -752,6 +972,8 @@ async function printThreadCollection(
752
972
  if (message) {
753
973
  io.stdout(message);
754
974
  }
975
+
976
+ printCacheNote(io, cache);
755
977
  }
756
978
 
757
979
  function renderThreadWithMessages(
@@ -762,6 +984,7 @@ function renderThreadWithMessages(
762
984
  nextCursor?: string;
763
985
  },
764
986
  publicUsers: Map<string, string>,
987
+ cache?: CacheResult,
765
988
  ): string {
766
989
  const attrs = thread.attributes;
767
990
  const messageBlocks =
@@ -833,6 +1056,7 @@ function renderThreadWithMessages(
833
1056
  ),
834
1057
  renderSection("Messages", messageBlocks),
835
1058
  renderPagination(pagination?.nextCursor, pagination?.nextCommand),
1059
+ renderCacheNote(cache),
836
1060
  ]);
837
1061
  }
838
1062
 
@@ -886,6 +1110,108 @@ function printSchemaResource(
886
1110
  );
887
1111
  }
888
1112
 
1113
+ async function maybePrepareCachePlan(
1114
+ args: ParsedArgs,
1115
+ context: CommandContext,
1116
+ scope: CacheScope | undefined,
1117
+ ): Promise<CachePlan | undefined> {
1118
+ return cacheFlags(args).sinceCache && scope
1119
+ ? prepareCachePlan(context, scope)
1120
+ : undefined;
1121
+ }
1122
+
1123
+ async function maybeSaveCacheTimestamp(
1124
+ args: ParsedArgs,
1125
+ context: CommandContext,
1126
+ scope: CacheScope | undefined,
1127
+ meta: Record<string, unknown> | undefined,
1128
+ shouldSave: boolean,
1129
+ ): Promise<string | undefined> {
1130
+ return cacheFlags(args).saveCache && scope && shouldSave
1131
+ ? saveCacheTimestamp(context, scope, meta)
1132
+ : undefined;
1133
+ }
1134
+
1135
+ async function maybeInboxThreadsScope(
1136
+ args: ParsedArgs,
1137
+ context: CommandContext,
1138
+ channelToken: string | undefined,
1139
+ status: ThreadStatusFilter | undefined,
1140
+ mailbox: MailboxFilter | undefined,
1141
+ ): Promise<CacheScope | undefined> {
1142
+ if (!cacheFlags(args).sinceCache && !cacheFlags(args).saveCache) {
1143
+ return undefined;
1144
+ }
1145
+
1146
+ return inboxThreadsScope({
1147
+ context,
1148
+ actorKey: await authenticatedActorKey(context, channelToken),
1149
+ status,
1150
+ mailbox,
1151
+ });
1152
+ }
1153
+
1154
+ async function maybeInboxMessagesScope(
1155
+ args: ParsedArgs,
1156
+ context: CommandContext,
1157
+ channelToken: string | undefined,
1158
+ threadId: string,
1159
+ ): Promise<CacheScope | undefined> {
1160
+ if (!cacheFlags(args).sinceCache && !cacheFlags(args).saveCache) {
1161
+ return undefined;
1162
+ }
1163
+
1164
+ return inboxMessagesScope({
1165
+ context,
1166
+ actorKey: await authenticatedActorKey(context, channelToken),
1167
+ threadId,
1168
+ });
1169
+ }
1170
+
1171
+ function resolvedSince(
1172
+ args: ParsedArgs,
1173
+ cachePlan: CachePlan | undefined,
1174
+ ): string | undefined {
1175
+ return stringFlag(args.flags, "since") ?? cachePlan?.previousServerTimestamp;
1176
+ }
1177
+
1178
+ function printCacheNote(io: Io, cache: CacheResult | undefined): void {
1179
+ const note = renderCacheNote(cache);
1180
+
1181
+ if (note) {
1182
+ io.stdout(note);
1183
+ }
1184
+ }
1185
+
1186
+ function renderCacheNote(cache: CacheResult | undefined): string | undefined {
1187
+ if (!cache) {
1188
+ return undefined;
1189
+ }
1190
+
1191
+ const details = [
1192
+ cache.previousServerTimestamp
1193
+ ? `used ${cache.previousServerTimestamp}`
1194
+ : cache.hit
1195
+ ? "used cache"
1196
+ : "no cached timestamp",
1197
+ cache.savedServerTimestamp ? `saved ${cache.savedServerTimestamp}` : undefined,
1198
+ ].filter(Boolean);
1199
+
1200
+ return `Cache: ${details.join("; ")}.`;
1201
+ }
1202
+
1203
+ function cacheFields(cache: CacheResult | undefined): Array<[string, string | undefined]> {
1204
+ if (!cache) {
1205
+ return [];
1206
+ }
1207
+
1208
+ return [
1209
+ ["Cache scope", cache.scopeKey],
1210
+ ["Cached timestamp", cache.previousServerTimestamp],
1211
+ ["Saved timestamp", cache.savedServerTimestamp],
1212
+ ];
1213
+ }
1214
+
889
1215
  function renderSchemaResource(
890
1216
  resource: {
891
1217
  id: string;
@@ -1,3 +1,17 @@
1
+ import {
2
+ assertSinceFlags,
3
+ authenticatedActorKey,
4
+ cacheFlags,
5
+ cacheResult,
6
+ ownedPostsScope,
7
+ prepareCachePlan,
8
+ publicPostsScope,
9
+ saveCacheTimestamp,
10
+ sharedPostsScope,
11
+ type CachePlan,
12
+ type CacheResult,
13
+ type CacheScope,
14
+ } from "../lib/cache";
1
15
  import {
2
16
  booleanFlag,
3
17
  integerFlag,
@@ -18,7 +32,7 @@ import {
18
32
  } from "../lib/human";
19
33
  import { printJson, printValue, type Io } from "../lib/output";
20
34
  import { paginatedJson, paginationInfo } from "../lib/pagination";
21
- import type { PostAttributes, ShareTokenResponse } from "../types/api";
35
+ import type { LatestFirstOrder, PostAttributes, ShareTokenResponse } from "../types/api";
22
36
 
23
37
  export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
24
38
  const subcommand = args.positionals[0];
@@ -50,46 +64,109 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
50
64
  }
51
65
 
52
66
  case "list": {
67
+ assertSinceFlags(args);
68
+ const channelId = await context.client.resolveChannelId(
69
+ requiredChannelFlag(args.flags),
70
+ );
71
+ const cacheScope = await maybeOwnedPostsScope(args, context, channelId);
72
+ const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
53
73
  const response = await context.client.listChannelPosts({
54
- channelId: await context.client.resolveChannelId(
55
- requiredChannelFlag(args.flags),
56
- ),
74
+ channelId,
57
75
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
58
76
  cursor: stringFlag(args.flags, "cursor"),
77
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
78
+ since: resolvedSince(args, cachePlan),
59
79
  });
80
+ const savedServerTimestamp = await maybeSaveCacheTimestamp(
81
+ args,
82
+ context,
83
+ cacheScope,
84
+ response.meta,
85
+ response.nextCursor === undefined,
86
+ );
60
87
 
61
- printPostCollection(args, context.outputMode, io, response);
88
+ printPostCollection(
89
+ args,
90
+ context.outputMode,
91
+ io,
92
+ response,
93
+ cacheResult(cachePlan, savedServerTimestamp),
94
+ );
62
95
  return;
63
96
  }
64
97
 
65
98
  case "public-list": {
99
+ assertSinceFlags(args);
100
+ const publicHandle = requiredPositional(
101
+ args.positionals,
102
+ 1,
103
+ "Missing public handle",
104
+ );
105
+ const channelName = requiredPositional(
106
+ args.positionals,
107
+ 2,
108
+ "Missing public channel name",
109
+ );
110
+ const cacheScope = maybePublicPostsScope(
111
+ args,
112
+ context,
113
+ publicHandle,
114
+ channelName,
115
+ );
116
+ const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
66
117
  const response = await context.client.listPublicChannelPosts({
67
- publicHandle: requiredPositional(
68
- args.positionals,
69
- 1,
70
- "Missing public handle",
71
- ),
72
- channelName: requiredPositional(
73
- args.positionals,
74
- 2,
75
- "Missing public channel name",
76
- ),
118
+ publicHandle,
119
+ channelName,
77
120
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
78
121
  cursor: stringFlag(args.flags, "cursor"),
122
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
123
+ since: resolvedSince(args, cachePlan),
79
124
  });
125
+ const savedServerTimestamp = await maybeSaveCacheTimestamp(
126
+ args,
127
+ context,
128
+ cacheScope,
129
+ response.meta,
130
+ response.nextCursor === undefined,
131
+ );
80
132
 
81
- printPostCollection(args, context.outputMode, io, response);
133
+ printPostCollection(
134
+ args,
135
+ context.outputMode,
136
+ io,
137
+ response,
138
+ cacheResult(cachePlan, savedServerTimestamp),
139
+ );
82
140
  return;
83
141
  }
84
142
 
85
143
  case "shared-list": {
144
+ assertSinceFlags(args);
145
+ const token = requiredPositional(args.positionals, 1, "Missing share token");
146
+ const cacheScope = maybeSharedPostsScope(args, context, token);
147
+ const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
86
148
  const response = await context.client.listSharedChannelPosts({
87
- token: requiredPositional(args.positionals, 1, "Missing share token"),
149
+ token,
88
150
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
89
151
  cursor: stringFlag(args.flags, "cursor"),
152
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
153
+ since: resolvedSince(args, cachePlan),
90
154
  });
155
+ const savedServerTimestamp = await maybeSaveCacheTimestamp(
156
+ args,
157
+ context,
158
+ cacheScope,
159
+ response.meta,
160
+ response.nextCursor === undefined,
161
+ );
91
162
 
92
- printPostCollection(args, context.outputMode, io, response);
163
+ printPostCollection(
164
+ args,
165
+ context.outputMode,
166
+ io,
167
+ response,
168
+ cacheResult(cachePlan, savedServerTimestamp),
169
+ );
93
170
  return;
94
171
  }
95
172
 
@@ -226,7 +303,9 @@ function printPostCollection(
226
303
  response: {
227
304
  items: Array<{ id: string; attributes: PostAttributes }>;
228
305
  nextCursor?: string;
306
+ meta?: Record<string, unknown>;
229
307
  },
308
+ cache?: CacheResult,
230
309
  ): void {
231
310
  if (outputMode === "json") {
232
311
  printJson(
@@ -234,6 +313,8 @@ function printPostCollection(
234
313
  paginatedJson(args, {
235
314
  items: response.items,
236
315
  nextCursor: response.nextCursor,
316
+ meta: response.meta,
317
+ ...(cache ? { cache } : {}),
237
318
  }),
238
319
  );
239
320
  return;
@@ -259,6 +340,107 @@ function printPostCollection(
259
340
  if (message) {
260
341
  io.stdout(message);
261
342
  }
343
+
344
+ printCacheNote(io, cache);
345
+ }
346
+
347
+ function parseLatestFirstOrder(value: string | undefined): LatestFirstOrder | undefined {
348
+ if (!value) {
349
+ return undefined;
350
+ }
351
+
352
+ if (value === "latest" || value === "oldest") {
353
+ return value;
354
+ }
355
+
356
+ throw new CliError("--order must be one of: latest, oldest", 2);
357
+ }
358
+
359
+ async function maybePrepareCachePlan(
360
+ args: ParsedArgs,
361
+ context: Awaited<ReturnType<typeof createCommandContext>>,
362
+ scope: CacheScope | undefined,
363
+ ): Promise<CachePlan | undefined> {
364
+ return cacheFlags(args).sinceCache && scope
365
+ ? prepareCachePlan(context, scope)
366
+ : undefined;
367
+ }
368
+
369
+ async function maybeSaveCacheTimestamp(
370
+ args: ParsedArgs,
371
+ context: Awaited<ReturnType<typeof createCommandContext>>,
372
+ scope: CacheScope | undefined,
373
+ meta: Record<string, unknown> | undefined,
374
+ shouldSave: boolean,
375
+ ): Promise<string | undefined> {
376
+ return cacheFlags(args).saveCache && scope && shouldSave
377
+ ? saveCacheTimestamp(context, scope, meta)
378
+ : undefined;
379
+ }
380
+
381
+ async function maybeOwnedPostsScope(
382
+ args: ParsedArgs,
383
+ context: Awaited<ReturnType<typeof createCommandContext>>,
384
+ channelId: string,
385
+ ): Promise<CacheScope | undefined> {
386
+ if (!cacheFlags(args).sinceCache && !cacheFlags(args).saveCache) {
387
+ return undefined;
388
+ }
389
+
390
+ return ownedPostsScope({
391
+ context,
392
+ actorKey: await authenticatedActorKey(context),
393
+ channelId,
394
+ });
395
+ }
396
+
397
+ function maybePublicPostsScope(
398
+ args: ParsedArgs,
399
+ context: Awaited<ReturnType<typeof createCommandContext>>,
400
+ publicHandle: string,
401
+ channelName: string,
402
+ ): CacheScope | undefined {
403
+ if (!cacheFlags(args).sinceCache && !cacheFlags(args).saveCache) {
404
+ return undefined;
405
+ }
406
+
407
+ return publicPostsScope({ context, publicHandle, channelName });
408
+ }
409
+
410
+ function maybeSharedPostsScope(
411
+ args: ParsedArgs,
412
+ context: Awaited<ReturnType<typeof createCommandContext>>,
413
+ shareToken: string,
414
+ ): CacheScope | undefined {
415
+ if (!cacheFlags(args).sinceCache && !cacheFlags(args).saveCache) {
416
+ return undefined;
417
+ }
418
+
419
+ return sharedPostsScope({ context, shareToken });
420
+ }
421
+
422
+ function resolvedSince(
423
+ args: ParsedArgs,
424
+ cachePlan: CachePlan | undefined,
425
+ ): string | undefined {
426
+ return stringFlag(args.flags, "since") ?? cachePlan?.previousServerTimestamp;
427
+ }
428
+
429
+ function printCacheNote(io: Io, cache: CacheResult | undefined): void {
430
+ if (!cache) {
431
+ return;
432
+ }
433
+
434
+ const details = [
435
+ cache.previousServerTimestamp
436
+ ? `used ${cache.previousServerTimestamp}`
437
+ : cache.hit
438
+ ? "used cache"
439
+ : "no cached timestamp",
440
+ cache.savedServerTimestamp ? `saved ${cache.savedServerTimestamp}` : undefined,
441
+ ].filter(Boolean);
442
+
443
+ io.stdout(`Cache: ${details.join("; ")}.`);
262
444
  }
263
445
 
264
446
  function renderPostDetail(