@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.
- package/README.md +7 -3
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +4 -3
- package/src/commands/auth/access-keys.ts +206 -0
- package/src/commands/auth.ts +3 -196
- package/src/commands/channel/render.ts +224 -0
- package/src/commands/channel/tokens.ts +145 -0
- package/src/commands/channel/validation.ts +11 -0
- package/src/commands/channel.ts +11 -340
- package/src/commands/doctor/checks.ts +123 -0
- package/src/commands/doctor/render.ts +140 -0
- package/src/commands/doctor/suggestions.ts +42 -0
- package/src/commands/doctor/types.ts +75 -0
- package/src/commands/doctor.ts +12 -371
- package/src/commands/feed.ts +19 -178
- package/src/commands/inbox/content.ts +31 -0
- package/src/commands/inbox/filters.ts +70 -0
- package/src/commands/inbox/messages.ts +69 -0
- package/src/commands/inbox/participants.ts +152 -0
- package/src/commands/inbox/render.ts +13 -0
- package/src/commands/inbox/resource-output.ts +217 -0
- package/src/commands/inbox/schema.ts +185 -0
- package/src/commands/inbox/screening.ts +76 -0
- package/src/commands/inbox/sync-scopes.ts +59 -0
- package/src/commands/inbox/thread-output.ts +344 -0
- package/src/commands/inbox/watch.ts +203 -0
- package/src/commands/inbox.ts +58 -1220
- package/src/commands/post.ts +24 -116
- package/src/lib/args.ts +8 -0
- package/src/lib/cache/scopes.ts +216 -0
- package/src/lib/cache/store.ts +195 -0
- package/src/lib/cache/types.ts +31 -0
- package/src/lib/cache.ts +18 -382
- package/src/lib/client/auth.ts +122 -0
- package/src/lib/client/channel-keys.ts +57 -0
- package/src/lib/client/channels.ts +364 -0
- package/src/lib/client/core.ts +133 -0
- package/src/lib/client/feed.ts +76 -0
- package/src/lib/client/inbox.ts +361 -0
- package/src/lib/client/posts.ts +213 -0
- package/src/lib/client/raw-api.ts +33 -0
- package/src/lib/client/users.ts +88 -0
- package/src/lib/client.ts +197 -894
- package/src/lib/help.ts +66 -9
- package/src/lib/json_api.ts +74 -9
- package/src/lib/pagination.ts +5 -0
- package/src/lib/polling.ts +146 -0
- package/src/lib/post-output.ts +55 -0
- 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,
|
package/src/lib/json_api.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CliError } from "./errors";
|
|
2
|
-
import type {
|
|
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
|
|
20
|
+
const document = asJsonApiDocument(payload);
|
|
15
21
|
|
|
16
|
-
if (!
|
|
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
|
|
32
|
+
const document = asJsonApiDocument(payload);
|
|
27
33
|
|
|
28
|
-
if (!
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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;
|
package/src/lib/pagination.ts
CHANGED
|
@@ -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 {
|