@clankmates/cli 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +4 -3
- package/src/cli.ts +2 -0
- package/src/commands/cache.ts +124 -0
- package/src/commands/feed.ts +192 -11
- package/src/commands/inbox.ts +290 -16
- package/src/commands/post.ts +195 -20
- package/src/lib/args.ts +11 -0
- package/src/lib/cache.ts +553 -0
- package/src/lib/client.ts +40 -1
- package/src/lib/help.ts +109 -10
- package/src/lib/pagination.ts +16 -0
- package/src/lib/paths.ts +26 -0
- package/src/types/api.ts +1 -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,
|
|
@@ -30,6 +44,7 @@ import type {
|
|
|
30
44
|
MailboxFilter,
|
|
31
45
|
MessageAttachmentAttributes,
|
|
32
46
|
MessageAttributes,
|
|
47
|
+
ParticipantScope,
|
|
33
48
|
ThreadAttributes,
|
|
34
49
|
ThreadStatusFilter,
|
|
35
50
|
WhoamiActor,
|
|
@@ -42,32 +57,85 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
42
57
|
switch (subcommand) {
|
|
43
58
|
case "list": {
|
|
44
59
|
const channelToken = stringFlag(args.flags, "channelToken");
|
|
60
|
+
assertSinceFlags(args);
|
|
61
|
+
const status = parseStatusFilter(stringFlag(args.flags, "status"));
|
|
62
|
+
const mailbox = parseMailboxFilter(stringFlag(args.flags, "mailbox"));
|
|
63
|
+
const participantScope = parseParticipantScope(
|
|
64
|
+
stringFlag(args.flags, "participantScope"),
|
|
65
|
+
);
|
|
66
|
+
const cacheScope = await maybeInboxThreadsScope(
|
|
67
|
+
args,
|
|
68
|
+
context,
|
|
69
|
+
channelToken,
|
|
70
|
+
status,
|
|
71
|
+
mailbox,
|
|
72
|
+
participantScope,
|
|
73
|
+
);
|
|
74
|
+
const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
|
|
45
75
|
const response = await context.client.listInboxThreads({
|
|
46
|
-
status
|
|
47
|
-
mailbox
|
|
76
|
+
status,
|
|
77
|
+
mailbox,
|
|
48
78
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
49
79
|
cursor: stringFlag(args.flags, "cursor"),
|
|
80
|
+
before: stringFlag(args.flags, "before"),
|
|
50
81
|
order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
|
|
51
|
-
since:
|
|
82
|
+
since: resolvedSince(args, cachePlan),
|
|
83
|
+
participant: stringFlag(args.flags, "participant"),
|
|
84
|
+
participantScope,
|
|
85
|
+
query: stringFlag(args.flags, "query"),
|
|
86
|
+
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
52
87
|
channelToken,
|
|
53
88
|
});
|
|
89
|
+
const savedServerTimestamp = await maybeSaveCacheTimestamp(
|
|
90
|
+
args,
|
|
91
|
+
context,
|
|
92
|
+
cacheScope,
|
|
93
|
+
response.meta,
|
|
94
|
+
response.nextCursor === undefined,
|
|
95
|
+
);
|
|
54
96
|
|
|
55
|
-
await printThreadCollection(
|
|
97
|
+
await printThreadCollection(
|
|
98
|
+
args,
|
|
99
|
+
context,
|
|
100
|
+
io,
|
|
101
|
+
response,
|
|
102
|
+
channelToken,
|
|
103
|
+
cacheResult(cachePlan, savedServerTimestamp),
|
|
104
|
+
);
|
|
56
105
|
return;
|
|
57
106
|
}
|
|
58
107
|
|
|
59
108
|
case "show": {
|
|
60
109
|
const threadId = requiredPositional(args.positionals, 1, "Missing thread id");
|
|
61
110
|
const channelToken = stringFlag(args.flags, "channelToken");
|
|
111
|
+
assertSinceFlags(args);
|
|
112
|
+
const cacheScope = await maybeInboxMessagesScope(
|
|
113
|
+
args,
|
|
114
|
+
context,
|
|
115
|
+
channelToken,
|
|
116
|
+
threadId,
|
|
117
|
+
);
|
|
118
|
+
const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
|
|
62
119
|
const thread = await context.client.getThread(threadId, channelToken);
|
|
63
120
|
const messages = await context.client.listMessagesForThread({
|
|
64
121
|
threadId,
|
|
65
122
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
66
123
|
cursor: stringFlag(args.flags, "cursor"),
|
|
124
|
+
before: stringFlag(args.flags, "before"),
|
|
67
125
|
order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
|
|
68
|
-
since:
|
|
126
|
+
since: resolvedSince(args, cachePlan),
|
|
127
|
+
query: stringFlag(args.flags, "query"),
|
|
128
|
+
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
69
129
|
channelToken,
|
|
70
130
|
});
|
|
131
|
+
const savedServerTimestamp = await maybeSaveCacheTimestamp(
|
|
132
|
+
args,
|
|
133
|
+
context,
|
|
134
|
+
cacheScope,
|
|
135
|
+
messages.meta,
|
|
136
|
+
messages.nextCursor === undefined,
|
|
137
|
+
);
|
|
138
|
+
const cache = cacheResult(cachePlan, savedServerTimestamp);
|
|
71
139
|
const ownerIds = ownerIdsForThreadDisplay(thread, messages.items);
|
|
72
140
|
const publicUsers =
|
|
73
141
|
context.outputMode === "json" || ownerIds.length === 0
|
|
@@ -85,22 +153,54 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
85
153
|
messages: messages.items,
|
|
86
154
|
nextCursor: messages.nextCursor,
|
|
87
155
|
meta: messages.meta,
|
|
156
|
+
...(cache ? { cache } : {}),
|
|
88
157
|
})
|
|
89
|
-
: renderThreadWithMessages(args, thread, messages, publicUsers),
|
|
158
|
+
: renderThreadWithMessages(args, thread, messages, publicUsers, cache),
|
|
90
159
|
);
|
|
91
160
|
return;
|
|
92
161
|
}
|
|
93
162
|
|
|
94
163
|
case "changes": {
|
|
95
164
|
const channelToken = stringFlag(args.flags, "channelToken");
|
|
165
|
+
assertSinceFlags(args);
|
|
166
|
+
const status = parseStatusFilter(stringFlag(args.flags, "status"));
|
|
167
|
+
const mailbox = parseMailboxFilter(stringFlag(args.flags, "mailbox"));
|
|
168
|
+
const participantScope = parseParticipantScope(
|
|
169
|
+
stringFlag(args.flags, "participantScope"),
|
|
170
|
+
);
|
|
171
|
+
const cacheScope = await maybeInboxThreadsScope(
|
|
172
|
+
args,
|
|
173
|
+
context,
|
|
174
|
+
channelToken,
|
|
175
|
+
status,
|
|
176
|
+
mailbox,
|
|
177
|
+
participantScope,
|
|
178
|
+
);
|
|
179
|
+
const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
|
|
96
180
|
const response = await context.client.checkInboxThreadChanges({
|
|
97
|
-
since: requiredSince(args),
|
|
98
|
-
status
|
|
99
|
-
mailbox
|
|
181
|
+
since: requiredSince(args, cachePlan, "inbox thread"),
|
|
182
|
+
status,
|
|
183
|
+
mailbox,
|
|
184
|
+
participant: stringFlag(args.flags, "participant"),
|
|
185
|
+
participantScope,
|
|
186
|
+
query: stringFlag(args.flags, "query"),
|
|
187
|
+
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
100
188
|
channelToken,
|
|
101
189
|
});
|
|
190
|
+
const savedServerTimestamp = await maybeSaveCacheTimestamp(
|
|
191
|
+
args,
|
|
192
|
+
context,
|
|
193
|
+
cacheScope,
|
|
194
|
+
changeResponseMeta(response),
|
|
195
|
+
response.has_updates === false,
|
|
196
|
+
);
|
|
102
197
|
|
|
103
|
-
printChangeCheckResponse(
|
|
198
|
+
printChangeCheckResponse(
|
|
199
|
+
context,
|
|
200
|
+
io,
|
|
201
|
+
response,
|
|
202
|
+
cacheResult(cachePlan, savedServerTimestamp),
|
|
203
|
+
);
|
|
104
204
|
return;
|
|
105
205
|
}
|
|
106
206
|
|
|
@@ -626,6 +726,18 @@ function parseMailboxFilter(value: string | undefined): MailboxFilter | undefine
|
|
|
626
726
|
throw new CliError("--mailbox must be one of: account, channel, all", 2);
|
|
627
727
|
}
|
|
628
728
|
|
|
729
|
+
function parseParticipantScope(value: string | undefined): ParticipantScope | undefined {
|
|
730
|
+
if (!value) {
|
|
731
|
+
return undefined;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (value === "account" || value === "owner") {
|
|
735
|
+
return value;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
throw new CliError("--participant-scope must be one of: account, owner", 2);
|
|
739
|
+
}
|
|
740
|
+
|
|
629
741
|
function parseLatestFirstOrder(value: string | undefined): LatestFirstOrder | undefined {
|
|
630
742
|
if (!value) {
|
|
631
743
|
return undefined;
|
|
@@ -638,9 +750,28 @@ function parseLatestFirstOrder(value: string | undefined): LatestFirstOrder | un
|
|
|
638
750
|
throw new CliError("--order must be one of: latest, oldest", 2);
|
|
639
751
|
}
|
|
640
752
|
|
|
641
|
-
function requiredSince(
|
|
753
|
+
function requiredSince(
|
|
754
|
+
args: ParsedArgs,
|
|
755
|
+
cachePlan?: CachePlan,
|
|
756
|
+
label = "resource",
|
|
757
|
+
): string {
|
|
642
758
|
const since = stringFlag(args.flags, "since");
|
|
643
759
|
|
|
760
|
+
if (since) {
|
|
761
|
+
return since;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (cachePlan?.previousServerTimestamp) {
|
|
765
|
+
return cachePlan.previousServerTimestamp;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (cacheFlags(args).sinceCache) {
|
|
769
|
+
throw new CliError(
|
|
770
|
+
`No cached server timestamp for this ${label} scope. Run a read command with \`--save-cache\` first.`,
|
|
771
|
+
2,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
644
775
|
if (!since) {
|
|
645
776
|
throw new CliError("Missing `--since`", 2);
|
|
646
777
|
}
|
|
@@ -657,13 +788,37 @@ async function runInboxMessagesCommand(
|
|
|
657
788
|
|
|
658
789
|
switch (subcommand) {
|
|
659
790
|
case "changes": {
|
|
791
|
+
assertSinceFlags(args);
|
|
792
|
+
const threadId = requiredPositional(args.positionals, 2, "Missing thread id");
|
|
793
|
+
const channelToken = stringFlag(args.flags, "channelToken");
|
|
794
|
+
const cacheScope = await maybeInboxMessagesScope(
|
|
795
|
+
args,
|
|
796
|
+
context,
|
|
797
|
+
channelToken,
|
|
798
|
+
threadId,
|
|
799
|
+
);
|
|
800
|
+
const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
|
|
660
801
|
const response = await context.client.checkThreadMessageChanges({
|
|
661
|
-
threadId
|
|
662
|
-
since: requiredSince(args),
|
|
663
|
-
|
|
802
|
+
threadId,
|
|
803
|
+
since: requiredSince(args, cachePlan, "inbox message"),
|
|
804
|
+
query: stringFlag(args.flags, "query"),
|
|
805
|
+
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
806
|
+
channelToken,
|
|
664
807
|
});
|
|
808
|
+
const savedServerTimestamp = await maybeSaveCacheTimestamp(
|
|
809
|
+
args,
|
|
810
|
+
context,
|
|
811
|
+
cacheScope,
|
|
812
|
+
changeResponseMeta(response),
|
|
813
|
+
response.has_updates === false,
|
|
814
|
+
);
|
|
665
815
|
|
|
666
|
-
printChangeCheckResponse(
|
|
816
|
+
printChangeCheckResponse(
|
|
817
|
+
context,
|
|
818
|
+
io,
|
|
819
|
+
response,
|
|
820
|
+
cacheResult(cachePlan, savedServerTimestamp),
|
|
821
|
+
);
|
|
667
822
|
return;
|
|
668
823
|
}
|
|
669
824
|
|
|
@@ -676,12 +831,13 @@ function printChangeCheckResponse(
|
|
|
676
831
|
context: CommandContext,
|
|
677
832
|
io: Io,
|
|
678
833
|
response: ChangeCheckResponse,
|
|
834
|
+
cache?: CacheResult,
|
|
679
835
|
): void {
|
|
680
836
|
printValue(
|
|
681
837
|
io,
|
|
682
838
|
context.outputMode,
|
|
683
839
|
context.outputMode === "json"
|
|
684
|
-
? response
|
|
840
|
+
? { ...response, ...(cache ? { cache } : {}) }
|
|
685
841
|
: renderFields([
|
|
686
842
|
["Has updates", response.has_updates ? "yes" : "no"],
|
|
687
843
|
["Server time", formatTimestamp(response.server_time)],
|
|
@@ -691,6 +847,7 @@ function printChangeCheckResponse(
|
|
|
691
847
|
? undefined
|
|
692
848
|
: `${response.recommended_poll_after_ms}ms`,
|
|
693
849
|
],
|
|
850
|
+
...cacheFields(cache),
|
|
694
851
|
]),
|
|
695
852
|
);
|
|
696
853
|
}
|
|
@@ -803,6 +960,7 @@ async function printThreadCollection(
|
|
|
803
960
|
meta?: Record<string, unknown>;
|
|
804
961
|
},
|
|
805
962
|
channelToken?: string,
|
|
963
|
+
cache?: CacheResult,
|
|
806
964
|
): Promise<void> {
|
|
807
965
|
if (context.outputMode === "json") {
|
|
808
966
|
printJson(
|
|
@@ -811,6 +969,7 @@ async function printThreadCollection(
|
|
|
811
969
|
items: response.items,
|
|
812
970
|
nextCursor: response.nextCursor,
|
|
813
971
|
meta: response.meta,
|
|
972
|
+
...(cache ? { cache } : {}),
|
|
814
973
|
}),
|
|
815
974
|
);
|
|
816
975
|
return Promise.resolve();
|
|
@@ -848,6 +1007,8 @@ async function printThreadCollection(
|
|
|
848
1007
|
if (message) {
|
|
849
1008
|
io.stdout(message);
|
|
850
1009
|
}
|
|
1010
|
+
|
|
1011
|
+
printCacheNote(io, cache);
|
|
851
1012
|
}
|
|
852
1013
|
|
|
853
1014
|
function renderThreadWithMessages(
|
|
@@ -858,6 +1019,7 @@ function renderThreadWithMessages(
|
|
|
858
1019
|
nextCursor?: string;
|
|
859
1020
|
},
|
|
860
1021
|
publicUsers: Map<string, string>,
|
|
1022
|
+
cache?: CacheResult,
|
|
861
1023
|
): string {
|
|
862
1024
|
const attrs = thread.attributes;
|
|
863
1025
|
const messageBlocks =
|
|
@@ -929,6 +1091,7 @@ function renderThreadWithMessages(
|
|
|
929
1091
|
),
|
|
930
1092
|
renderSection("Messages", messageBlocks),
|
|
931
1093
|
renderPagination(pagination?.nextCursor, pagination?.nextCommand),
|
|
1094
|
+
renderCacheNote(cache),
|
|
932
1095
|
]);
|
|
933
1096
|
}
|
|
934
1097
|
|
|
@@ -982,6 +1145,117 @@ function printSchemaResource(
|
|
|
982
1145
|
);
|
|
983
1146
|
}
|
|
984
1147
|
|
|
1148
|
+
async function maybePrepareCachePlan(
|
|
1149
|
+
args: ParsedArgs,
|
|
1150
|
+
context: CommandContext,
|
|
1151
|
+
scope: CacheScope | undefined,
|
|
1152
|
+
): Promise<CachePlan | undefined> {
|
|
1153
|
+
return cacheFlags(args).sinceCache && scope
|
|
1154
|
+
? prepareCachePlan(context, scope)
|
|
1155
|
+
: undefined;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
async function maybeSaveCacheTimestamp(
|
|
1159
|
+
args: ParsedArgs,
|
|
1160
|
+
context: CommandContext,
|
|
1161
|
+
scope: CacheScope | undefined,
|
|
1162
|
+
meta: Record<string, unknown> | undefined,
|
|
1163
|
+
shouldSave: boolean,
|
|
1164
|
+
): Promise<string | undefined> {
|
|
1165
|
+
return cacheFlags(args).saveCache && scope && shouldSave
|
|
1166
|
+
? saveCacheTimestamp(context, scope, meta)
|
|
1167
|
+
: undefined;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
async function maybeInboxThreadsScope(
|
|
1171
|
+
args: ParsedArgs,
|
|
1172
|
+
context: CommandContext,
|
|
1173
|
+
channelToken: string | undefined,
|
|
1174
|
+
status: ThreadStatusFilter | undefined,
|
|
1175
|
+
mailbox: MailboxFilter | undefined,
|
|
1176
|
+
participantScope: ParticipantScope | undefined,
|
|
1177
|
+
): Promise<CacheScope | undefined> {
|
|
1178
|
+
if (!cacheFlags(args).sinceCache && !cacheFlags(args).saveCache) {
|
|
1179
|
+
return undefined;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
return inboxThreadsScope({
|
|
1183
|
+
context,
|
|
1184
|
+
actorKey: await authenticatedActorKey(context, channelToken),
|
|
1185
|
+
status,
|
|
1186
|
+
mailbox,
|
|
1187
|
+
participant: stringFlag(args.flags, "participant"),
|
|
1188
|
+
participantScope,
|
|
1189
|
+
query: stringFlag(args.flags, "query"),
|
|
1190
|
+
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
1191
|
+
before: stringFlag(args.flags, "before"),
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
async function maybeInboxMessagesScope(
|
|
1196
|
+
args: ParsedArgs,
|
|
1197
|
+
context: CommandContext,
|
|
1198
|
+
channelToken: string | undefined,
|
|
1199
|
+
threadId: string,
|
|
1200
|
+
): Promise<CacheScope | undefined> {
|
|
1201
|
+
if (!cacheFlags(args).sinceCache && !cacheFlags(args).saveCache) {
|
|
1202
|
+
return undefined;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
return inboxMessagesScope({
|
|
1206
|
+
context,
|
|
1207
|
+
actorKey: await authenticatedActorKey(context, channelToken),
|
|
1208
|
+
threadId,
|
|
1209
|
+
query: stringFlag(args.flags, "query"),
|
|
1210
|
+
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
1211
|
+
before: stringFlag(args.flags, "before"),
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function resolvedSince(
|
|
1216
|
+
args: ParsedArgs,
|
|
1217
|
+
cachePlan: CachePlan | undefined,
|
|
1218
|
+
): string | undefined {
|
|
1219
|
+
return stringFlag(args.flags, "since") ?? cachePlan?.previousServerTimestamp;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function printCacheNote(io: Io, cache: CacheResult | undefined): void {
|
|
1223
|
+
const note = renderCacheNote(cache);
|
|
1224
|
+
|
|
1225
|
+
if (note) {
|
|
1226
|
+
io.stdout(note);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function renderCacheNote(cache: CacheResult | undefined): string | undefined {
|
|
1231
|
+
if (!cache) {
|
|
1232
|
+
return undefined;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const details = [
|
|
1236
|
+
cache.previousServerTimestamp
|
|
1237
|
+
? `used ${cache.previousServerTimestamp}`
|
|
1238
|
+
: cache.hit
|
|
1239
|
+
? "used cache"
|
|
1240
|
+
: "no cached timestamp",
|
|
1241
|
+
cache.savedServerTimestamp ? `saved ${cache.savedServerTimestamp}` : undefined,
|
|
1242
|
+
].filter(Boolean);
|
|
1243
|
+
|
|
1244
|
+
return `Cache: ${details.join("; ")}.`;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function cacheFields(cache: CacheResult | undefined): Array<[string, string | undefined]> {
|
|
1248
|
+
if (!cache) {
|
|
1249
|
+
return [];
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
return [
|
|
1253
|
+
["Cache scope", cache.scopeKey],
|
|
1254
|
+
["Cached timestamp", cache.previousServerTimestamp],
|
|
1255
|
+
["Saved timestamp", cache.savedServerTimestamp],
|
|
1256
|
+
];
|
|
1257
|
+
}
|
|
1258
|
+
|
|
985
1259
|
function renderSchemaResource(
|
|
986
1260
|
resource: {
|
|
987
1261
|
id: string;
|