@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.
- package/README.md +15 -2
- package/package.json +2 -1
- package/skills/codex/clankmates/SKILL.md +33 -3
- package/src/cli.ts +2 -0
- package/src/commands/cache.ts +124 -0
- package/src/commands/feed.ts +245 -6
- package/src/commands/inbox.ts +330 -4
- package/src/commands/post.ts +200 -18
- package/src/lib/args.ts +6 -0
- package/src/lib/cache.ts +499 -0
- package/src/lib/client.ts +80 -1
- package/src/lib/help.ts +168 -9
- package/src/lib/pagination.ts +13 -0
- package/src/lib/paths.ts +26 -0
- package/src/types/api.ts +7 -0
package/src/commands/inbox.ts
CHANGED
|
@@ -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
|
|
45
|
-
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(
|
|
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;
|
package/src/commands/post.ts
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
|
68
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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(
|