@clankmates/cli 0.2.0 → 0.3.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 +72 -216
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +40 -11
- package/src/cli.ts +26 -3
- package/src/commands/auth.ts +241 -26
- package/src/commands/channel.ts +302 -52
- package/src/commands/doctor.ts +121 -0
- package/src/commands/post.ts +124 -17
- package/src/commands/user.ts +52 -0
- package/src/lib/args.ts +1 -0
- package/src/lib/client.ts +318 -37
- package/src/types/api.ts +89 -3
package/src/commands/post.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
booleanFlag,
|
|
2
3
|
integerFlag,
|
|
3
4
|
requiredChannelFlag,
|
|
4
5
|
requiredPositional,
|
|
@@ -9,6 +10,7 @@ import { resolveBodyInput } from "../lib/body-input";
|
|
|
9
10
|
import { createCommandContext } from "../lib/context";
|
|
10
11
|
import { CliError } from "../lib/errors";
|
|
11
12
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
13
|
+
import type { PostAttributes } from "../types/api";
|
|
12
14
|
|
|
13
15
|
export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
14
16
|
const subcommand = args.positionals[0];
|
|
@@ -53,24 +55,34 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
53
55
|
cursor: stringFlag(args.flags, "cursor"),
|
|
54
56
|
});
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
nextCursor: response.nextCursor,
|
|
60
|
-
});
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
58
|
+
printPostCollection(context.outputMode, io, response);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
})
|
|
73
|
-
|
|
62
|
+
case "public-list": {
|
|
63
|
+
const response = await context.client.listPublicChannelPosts({
|
|
64
|
+
handle: requiredPositional(args.positionals, 1, "Missing public handle"),
|
|
65
|
+
channelName: requiredPositional(
|
|
66
|
+
args.positionals,
|
|
67
|
+
2,
|
|
68
|
+
"Missing public channel name",
|
|
69
|
+
),
|
|
70
|
+
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
71
|
+
cursor: stringFlag(args.flags, "cursor"),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
printPostCollection(context.outputMode, io, response);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case "shared-list": {
|
|
79
|
+
const response = await context.client.listSharedChannelPosts({
|
|
80
|
+
token: requiredPositional(args.positionals, 1, "Missing share token"),
|
|
81
|
+
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
82
|
+
cursor: stringFlag(args.flags, "cursor"),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
printPostCollection(context.outputMode, io, response);
|
|
74
86
|
return;
|
|
75
87
|
}
|
|
76
88
|
|
|
@@ -134,7 +146,102 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
134
146
|
return;
|
|
135
147
|
}
|
|
136
148
|
|
|
149
|
+
case "public-get": {
|
|
150
|
+
const post = await context.client.getPublicPostByHandle({
|
|
151
|
+
handle: requiredPositional(args.positionals, 1, "Missing public handle"),
|
|
152
|
+
channelName: requiredPositional(
|
|
153
|
+
args.positionals,
|
|
154
|
+
2,
|
|
155
|
+
"Missing public channel name",
|
|
156
|
+
),
|
|
157
|
+
postId: requiredPositional(args.positionals, 3, "Missing post id"),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
printValue(
|
|
161
|
+
io,
|
|
162
|
+
context.outputMode,
|
|
163
|
+
context.outputMode === "json"
|
|
164
|
+
? post
|
|
165
|
+
: {
|
|
166
|
+
id: post.id,
|
|
167
|
+
source: post.attributes.source,
|
|
168
|
+
body: post.attributes.body,
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case "shared-get": {
|
|
175
|
+
const post = await context.client.getSharedPost(
|
|
176
|
+
requiredPositional(args.positionals, 1, "Missing share token"),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
printValue(
|
|
180
|
+
io,
|
|
181
|
+
context.outputMode,
|
|
182
|
+
context.outputMode === "json"
|
|
183
|
+
? post
|
|
184
|
+
: {
|
|
185
|
+
id: post.id,
|
|
186
|
+
source: post.attributes.source,
|
|
187
|
+
body: post.attributes.body,
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
case "share": {
|
|
194
|
+
const response = await context.client.sharePost(
|
|
195
|
+
requiredPositional(args.positionals, 1, "Missing post id"),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (booleanFlag(args.flags, "tokenOnly")) {
|
|
199
|
+
io.stdout(response.token);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
printValue(io, context.outputMode, response);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case "revoke-share": {
|
|
208
|
+
const response = await context.client.revokePostShare(
|
|
209
|
+
requiredPositional(args.positionals, 1, "Missing post id"),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
printValue(io, context.outputMode, response);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
137
216
|
default:
|
|
138
217
|
throw new CliError("Unknown post subcommand", 2);
|
|
139
218
|
}
|
|
140
219
|
}
|
|
220
|
+
|
|
221
|
+
function printPostCollection(
|
|
222
|
+
outputMode: "json" | "table",
|
|
223
|
+
io: Io,
|
|
224
|
+
response: {
|
|
225
|
+
items: Array<{ id: string; attributes: PostAttributes }>;
|
|
226
|
+
nextCursor?: string;
|
|
227
|
+
},
|
|
228
|
+
): void {
|
|
229
|
+
if (outputMode === "json") {
|
|
230
|
+
printJson(io, {
|
|
231
|
+
items: response.items,
|
|
232
|
+
nextCursor: response.nextCursor,
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
printValue(
|
|
238
|
+
io,
|
|
239
|
+
outputMode,
|
|
240
|
+
response.items.map((item) => ({
|
|
241
|
+
id: item.id,
|
|
242
|
+
source: item.attributes.source,
|
|
243
|
+
date: item.attributes.updated_at ?? item.attributes.inserted_at ?? "",
|
|
244
|
+
body: item.attributes.body,
|
|
245
|
+
})),
|
|
246
|
+
);
|
|
247
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { requiredPositional, type ParsedArgs } from "../lib/args";
|
|
2
|
+
import { createCommandContext } from "../lib/context";
|
|
3
|
+
import { CliError } from "../lib/errors";
|
|
4
|
+
import { printValue, type Io } from "../lib/output";
|
|
5
|
+
|
|
6
|
+
export async function runUserCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
7
|
+
const subcommand = args.positionals[0];
|
|
8
|
+
const context = await createCommandContext(args, io);
|
|
9
|
+
|
|
10
|
+
switch (subcommand) {
|
|
11
|
+
case "get": {
|
|
12
|
+
const user = await context.client.getUserByPublicHandle(
|
|
13
|
+
requiredPositional(args.positionals, 1, "Missing public handle"),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
printValue(
|
|
17
|
+
io,
|
|
18
|
+
context.outputMode,
|
|
19
|
+
context.outputMode === "json"
|
|
20
|
+
? user
|
|
21
|
+
: {
|
|
22
|
+
id: user.id,
|
|
23
|
+
email: user.attributes.email,
|
|
24
|
+
publicHandle: user.attributes.public_handle ?? "",
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
case "claim-handle": {
|
|
31
|
+
const user = await context.client.claimPublicHandle(
|
|
32
|
+
requiredPositional(args.positionals, 1, "Missing public handle"),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
printValue(
|
|
36
|
+
io,
|
|
37
|
+
context.outputMode,
|
|
38
|
+
context.outputMode === "json"
|
|
39
|
+
? user
|
|
40
|
+
: {
|
|
41
|
+
id: user.id,
|
|
42
|
+
email: user.attributes.email,
|
|
43
|
+
publicHandle: user.attributes.public_handle ?? "",
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
default:
|
|
50
|
+
throw new CliError("Unknown user subcommand", 2);
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/lib/args.ts
CHANGED
|
@@ -15,6 +15,7 @@ const CLI_OPTIONS = {
|
|
|
15
15
|
"master-token": { type: "string" },
|
|
16
16
|
readOnlyToken: { type: "string" },
|
|
17
17
|
"read-only-token": { type: "string" },
|
|
18
|
+
scope: { type: "string" },
|
|
18
19
|
name: { type: "string" },
|
|
19
20
|
description: { type: "string" },
|
|
20
21
|
save: { type: "boolean" },
|
package/src/lib/client.ts
CHANGED
|
@@ -2,24 +2,48 @@ import { expectCollection, expectResource } from "./json_api";
|
|
|
2
2
|
import { requestJson, requestJsonApi, type RequestOptions } from "./http";
|
|
3
3
|
import { CliError } from "./errors";
|
|
4
4
|
import {
|
|
5
|
-
requireOwnerReadToken,
|
|
6
5
|
requireMasterToken,
|
|
6
|
+
requireOwnerReadToken,
|
|
7
7
|
resolveChannelActorOrMasterToken,
|
|
8
8
|
resolveMasterToken,
|
|
9
9
|
resolveOwnerReadToken,
|
|
10
10
|
resolvePublishToken,
|
|
11
11
|
} from "./tokens";
|
|
12
12
|
import type {
|
|
13
|
-
|
|
13
|
+
AccessKeyAttributes,
|
|
14
|
+
AccessKeyIssueResponse,
|
|
15
|
+
AccessKeyRevokeResponse,
|
|
16
|
+
AccessKeyScope,
|
|
14
17
|
ChannelAttributes,
|
|
18
|
+
ChannelDiagnosticsResponse,
|
|
19
|
+
ChannelKeyAttributes,
|
|
20
|
+
ChannelKeyIssueResponse,
|
|
21
|
+
ChannelKeyRevokeResponse,
|
|
22
|
+
ChannelPublicationResponse,
|
|
23
|
+
IdResponse,
|
|
15
24
|
PostAttributes,
|
|
16
25
|
ProfileConfig,
|
|
17
|
-
|
|
26
|
+
ShareTokenResponse,
|
|
27
|
+
UserAttributes,
|
|
28
|
+
WhoamiResponse,
|
|
18
29
|
} from "../types/api";
|
|
19
30
|
|
|
20
31
|
export class ClankmatesClient {
|
|
21
32
|
constructor(private readonly profile: ProfileConfig) {}
|
|
22
33
|
|
|
34
|
+
async canAuthenticate(token: string): Promise<boolean> {
|
|
35
|
+
try {
|
|
36
|
+
await this.whoami(token);
|
|
37
|
+
return true;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (isUnauthorizedCliError(error)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
23
47
|
async validateMasterToken(token: string): Promise<void> {
|
|
24
48
|
const response = await this.whoami(token);
|
|
25
49
|
|
|
@@ -54,12 +78,76 @@ export class ClankmatesClient {
|
|
|
54
78
|
).data;
|
|
55
79
|
}
|
|
56
80
|
|
|
57
|
-
async
|
|
58
|
-
|
|
81
|
+
async listAccessKeys(scope?: AccessKeyScope) {
|
|
82
|
+
const path = scope
|
|
83
|
+
? `${API_PREFIX}/me/access-keys/scopes/${encodeURIComponent(scope)}`
|
|
84
|
+
: `${API_PREFIX}/me/access-keys`;
|
|
85
|
+
|
|
86
|
+
return this.requestCollection<AccessKeyAttributes>(path, {
|
|
59
87
|
token: requireOwnerReadToken(this.profile),
|
|
60
88
|
});
|
|
61
89
|
}
|
|
62
90
|
|
|
91
|
+
async issueAccessKey(input: { scope: AccessKeyScope; name: string }) {
|
|
92
|
+
return this.requestAction<AccessKeyIssueResponse>(
|
|
93
|
+
`${API_PREFIX}/me/access-keys`,
|
|
94
|
+
{
|
|
95
|
+
method: "POST",
|
|
96
|
+
token: requireMasterToken(this.profile),
|
|
97
|
+
body: {
|
|
98
|
+
data: {
|
|
99
|
+
scope: input.scope,
|
|
100
|
+
name: input.name,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async revokeAccessKey(id: string) {
|
|
108
|
+
return this.requestAction<AccessKeyRevokeResponse>(
|
|
109
|
+
`${API_PREFIX}/me/access-keys/${id}`,
|
|
110
|
+
{
|
|
111
|
+
method: "DELETE",
|
|
112
|
+
token: requireMasterToken(this.profile),
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async claimPublicHandle(publicHandle: string) {
|
|
118
|
+
return this.requestResource<UserAttributes>(`${API_PREFIX}/me/public-handle`, {
|
|
119
|
+
method: "PATCH",
|
|
120
|
+
token: requireMasterToken(this.profile),
|
|
121
|
+
body: {
|
|
122
|
+
data: {
|
|
123
|
+
type: "user",
|
|
124
|
+
attributes: {
|
|
125
|
+
public_handle: publicHandle,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getUserByPublicHandle(publicHandle: string) {
|
|
133
|
+
return this.requestResource<UserAttributes>(
|
|
134
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}`,
|
|
135
|
+
{},
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async listChannels(input: { limit?: number; cursor?: string } = {}) {
|
|
140
|
+
return this.requestCollection<ChannelAttributes>(
|
|
141
|
+
withQuery(`${API_PREFIX}/channels`, {
|
|
142
|
+
"page[limit]": input.limit,
|
|
143
|
+
"page[after]": input.cursor,
|
|
144
|
+
}),
|
|
145
|
+
{
|
|
146
|
+
token: requireOwnerReadToken(this.profile),
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
63
151
|
async getChannel(channelId: string) {
|
|
64
152
|
return this.requestResource<ChannelAttributes>(
|
|
65
153
|
`${API_PREFIX}/channels/${channelId}`,
|
|
@@ -69,6 +157,15 @@ export class ClankmatesClient {
|
|
|
69
157
|
);
|
|
70
158
|
}
|
|
71
159
|
|
|
160
|
+
async getChannelDiagnostics(channelId: string) {
|
|
161
|
+
return this.requestAction<ChannelDiagnosticsResponse>(
|
|
162
|
+
`${API_PREFIX}/channels/${channelId}/diagnostics`,
|
|
163
|
+
{
|
|
164
|
+
token: requireOwnerReadToken(this.profile),
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
72
169
|
async getChannelByName(channelName: string) {
|
|
73
170
|
return this.requestResource<ChannelAttributes>(
|
|
74
171
|
`${API_PREFIX}/channels/by-name/${encodeURIComponent(channelName)}`,
|
|
@@ -78,6 +175,37 @@ export class ClankmatesClient {
|
|
|
78
175
|
);
|
|
79
176
|
}
|
|
80
177
|
|
|
178
|
+
async getPublicChannelByHandle(handle: string, name: string) {
|
|
179
|
+
return this.requestResource<ChannelAttributes>(
|
|
180
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(handle)}/channels/${encodeURIComponent(name)}`,
|
|
181
|
+
{},
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async listPublicChannelsForHandle(input: {
|
|
186
|
+
handle: string;
|
|
187
|
+
limit?: number;
|
|
188
|
+
cursor?: string;
|
|
189
|
+
}) {
|
|
190
|
+
return this.requestCollection<ChannelAttributes>(
|
|
191
|
+
withQuery(
|
|
192
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.handle)}/channels`,
|
|
193
|
+
{
|
|
194
|
+
"page[limit]": input.limit,
|
|
195
|
+
"page[after]": input.cursor,
|
|
196
|
+
},
|
|
197
|
+
),
|
|
198
|
+
{},
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async getSharedChannel(token: string) {
|
|
203
|
+
return this.requestResource<ChannelAttributes>(
|
|
204
|
+
`${API_PREFIX}/shares/channels/${encodeURIComponent(token)}`,
|
|
205
|
+
{},
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
81
209
|
async createChannel(input: { name: string; description?: string }) {
|
|
82
210
|
return this.requestResource<ChannelAttributes>(`${API_PREFIX}/channels`, {
|
|
83
211
|
method: "POST",
|
|
@@ -120,6 +248,50 @@ export class ClankmatesClient {
|
|
|
120
248
|
);
|
|
121
249
|
}
|
|
122
250
|
|
|
251
|
+
async publishChannelPublicly(channelId: string) {
|
|
252
|
+
return this.requestResource<ChannelAttributes>(
|
|
253
|
+
`${API_PREFIX}/channels/${channelId}/publication`,
|
|
254
|
+
{
|
|
255
|
+
method: "PATCH",
|
|
256
|
+
token: requireMasterToken(this.profile),
|
|
257
|
+
body: {
|
|
258
|
+
data: {
|
|
259
|
+
type: "channel",
|
|
260
|
+
id: channelId,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async unpublishChannelPublicly(channelId: string) {
|
|
268
|
+
return this.requestAction<ChannelPublicationResponse>(
|
|
269
|
+
`${API_PREFIX}/channels/${channelId}/publication`,
|
|
270
|
+
{
|
|
271
|
+
method: "DELETE",
|
|
272
|
+
token: requireMasterToken(this.profile),
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async shareChannel(channelId: string) {
|
|
278
|
+
return this.requestAction<ShareTokenResponse>(
|
|
279
|
+
`${API_PREFIX}/channels/${channelId}/share`,
|
|
280
|
+
{
|
|
281
|
+
method: "POST",
|
|
282
|
+
token: requireMasterToken(this.profile),
|
|
283
|
+
body: { data: {} },
|
|
284
|
+
},
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async revokeChannelShare(channelId: string) {
|
|
289
|
+
return this.requestAction<IdResponse>(`${API_PREFIX}/channels/${channelId}/share`, {
|
|
290
|
+
method: "DELETE",
|
|
291
|
+
token: requireMasterToken(this.profile),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
123
295
|
async deleteChannel(channelId: string): Promise<void> {
|
|
124
296
|
await this.requestJsonApi(`${API_PREFIX}/channels/${channelId}`, {
|
|
125
297
|
method: "DELETE",
|
|
@@ -127,17 +299,45 @@ export class ClankmatesClient {
|
|
|
127
299
|
});
|
|
128
300
|
}
|
|
129
301
|
|
|
130
|
-
async
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
302
|
+
async listChannelKeys(input: {
|
|
303
|
+
channelId: string;
|
|
304
|
+
limit?: number;
|
|
305
|
+
cursor?: string;
|
|
306
|
+
}) {
|
|
307
|
+
return this.requestCollection<ChannelKeyAttributes>(
|
|
308
|
+
withQuery(`${API_PREFIX}/channels/${input.channelId}/tokens`, {
|
|
309
|
+
"page[limit]": input.limit,
|
|
310
|
+
"page[after]": input.cursor,
|
|
311
|
+
}),
|
|
312
|
+
{
|
|
313
|
+
token: requireOwnerReadToken(this.profile),
|
|
314
|
+
},
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async issueChannelKey(input: { channelId: string; name: string }) {
|
|
319
|
+
return this.requestAction<ChannelKeyIssueResponse>(
|
|
320
|
+
`${API_PREFIX}/channels/${input.channelId}/tokens`,
|
|
321
|
+
{
|
|
322
|
+
method: "POST",
|
|
323
|
+
token: requireMasterToken(this.profile),
|
|
324
|
+
body: {
|
|
325
|
+
data: {
|
|
326
|
+
name: input.name,
|
|
327
|
+
},
|
|
138
328
|
},
|
|
139
|
-
|
|
140
|
-
)
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async revokeChannelKey(id: string) {
|
|
334
|
+
return this.requestAction<ChannelKeyRevokeResponse>(
|
|
335
|
+
`${API_PREFIX}/channel-keys/${id}`,
|
|
336
|
+
{
|
|
337
|
+
method: "DELETE",
|
|
338
|
+
token: requireMasterToken(this.profile),
|
|
339
|
+
},
|
|
340
|
+
);
|
|
141
341
|
}
|
|
142
342
|
|
|
143
343
|
async publishPost(input: {
|
|
@@ -190,6 +390,38 @@ export class ClankmatesClient {
|
|
|
190
390
|
);
|
|
191
391
|
}
|
|
192
392
|
|
|
393
|
+
async listPublicChannelPosts(input: {
|
|
394
|
+
handle: string;
|
|
395
|
+
channelName: string;
|
|
396
|
+
limit?: number;
|
|
397
|
+
cursor?: string;
|
|
398
|
+
}) {
|
|
399
|
+
return this.requestCollection<PostAttributes>(
|
|
400
|
+
withQuery(
|
|
401
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.handle)}/channels/${encodeURIComponent(input.channelName)}/posts`,
|
|
402
|
+
{
|
|
403
|
+
"page[limit]": input.limit,
|
|
404
|
+
"page[after]": input.cursor,
|
|
405
|
+
},
|
|
406
|
+
),
|
|
407
|
+
{},
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async listSharedChannelPosts(input: {
|
|
412
|
+
token: string;
|
|
413
|
+
limit?: number;
|
|
414
|
+
cursor?: string;
|
|
415
|
+
}) {
|
|
416
|
+
return this.requestCollection<PostAttributes>(
|
|
417
|
+
withQuery(`${API_PREFIX}/shares/channels/${encodeURIComponent(input.token)}/posts`, {
|
|
418
|
+
"page[limit]": input.limit,
|
|
419
|
+
"page[after]": input.cursor,
|
|
420
|
+
}),
|
|
421
|
+
{},
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
193
425
|
async getPost(postId: string) {
|
|
194
426
|
return this.requestResource<PostAttributes>(
|
|
195
427
|
`${API_PREFIX}/posts/${postId}`,
|
|
@@ -199,6 +431,24 @@ export class ClankmatesClient {
|
|
|
199
431
|
);
|
|
200
432
|
}
|
|
201
433
|
|
|
434
|
+
async getPublicPostByHandle(input: {
|
|
435
|
+
handle: string;
|
|
436
|
+
channelName: string;
|
|
437
|
+
postId: string;
|
|
438
|
+
}) {
|
|
439
|
+
return this.requestResource<PostAttributes>(
|
|
440
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.handle)}/channels/${encodeURIComponent(input.channelName)}/posts/${input.postId}`,
|
|
441
|
+
{},
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async getSharedPost(token: string) {
|
|
446
|
+
return this.requestResource<PostAttributes>(
|
|
447
|
+
`${API_PREFIX}/shares/posts/${encodeURIComponent(token)}`,
|
|
448
|
+
{},
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
202
452
|
async editPost(input: {
|
|
203
453
|
postId: string;
|
|
204
454
|
body: string;
|
|
@@ -236,6 +486,21 @@ export class ClankmatesClient {
|
|
|
236
486
|
});
|
|
237
487
|
}
|
|
238
488
|
|
|
489
|
+
async sharePost(postId: string) {
|
|
490
|
+
return this.requestAction<ShareTokenResponse>(`${API_PREFIX}/posts/${postId}/share`, {
|
|
491
|
+
method: "POST",
|
|
492
|
+
token: requireMasterToken(this.profile),
|
|
493
|
+
body: { data: {} },
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async revokePostShare(postId: string) {
|
|
498
|
+
return this.requestAction<IdResponse>(`${API_PREFIX}/posts/${postId}/share`, {
|
|
499
|
+
method: "DELETE",
|
|
500
|
+
token: requireMasterToken(this.profile),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
239
504
|
async myFeed(input: { channelId?: string; limit?: number; cursor?: string }) {
|
|
240
505
|
return this.requestCollection<PostAttributes>(
|
|
241
506
|
withQuery(`${API_PREFIX}/feeds/my`, {
|
|
@@ -291,6 +556,28 @@ export class ClankmatesClient {
|
|
|
291
556
|
).data;
|
|
292
557
|
}
|
|
293
558
|
|
|
559
|
+
async resolveChannelId(channel: string): Promise<string> {
|
|
560
|
+
if (looksLikeUuid(channel)) {
|
|
561
|
+
return channel;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return (await this.resolveOwnedChannel(channel)).id;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async resolveOwnedChannel(channel: string) {
|
|
568
|
+
if (looksLikeUuid(channel)) {
|
|
569
|
+
return this.getChannel(channel);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (!resolveOwnerReadToken(this.profile).token) {
|
|
573
|
+
throw new CliError(
|
|
574
|
+
`Resolving channel name "${channel}" requires an owner read token. Use the channel UUID or configure a read-only or master token.`,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return this.getChannelByName(channel);
|
|
579
|
+
}
|
|
580
|
+
|
|
294
581
|
private async requestResource<TAttributes extends object>(
|
|
295
582
|
path: string,
|
|
296
583
|
options: RequestOptions,
|
|
@@ -309,33 +596,18 @@ export class ClankmatesClient {
|
|
|
309
596
|
);
|
|
310
597
|
}
|
|
311
598
|
|
|
312
|
-
private async
|
|
599
|
+
private async requestAction<T = unknown>(
|
|
313
600
|
path: string,
|
|
314
601
|
options: RequestOptions = {},
|
|
315
602
|
) {
|
|
316
|
-
return requestJsonApi<T>(
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
async resolveChannelId(channel: string): Promise<string> {
|
|
320
|
-
if (looksLikeUuid(channel)) {
|
|
321
|
-
return channel;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return (await this.resolveOwnedChannel(channel)).id;
|
|
603
|
+
return (await this.requestJsonApi<T>(path, options)).data;
|
|
325
604
|
}
|
|
326
605
|
|
|
327
|
-
async
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (!resolveOwnerReadToken(this.profile).token) {
|
|
333
|
-
throw new CliError(
|
|
334
|
-
`Resolving channel name "${channel}" requires an owner read token. Use the channel UUID or configure a read-only or master token.`,
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return this.getChannelByName(channel);
|
|
606
|
+
private async requestJsonApi<T = unknown>(
|
|
607
|
+
path: string,
|
|
608
|
+
options: RequestOptions = {},
|
|
609
|
+
) {
|
|
610
|
+
return requestJsonApi<T>(this.profile.baseUrl, path, options);
|
|
339
611
|
}
|
|
340
612
|
|
|
341
613
|
private resolvePostLifecycleToken(explicitToken?: string): string {
|
|
@@ -364,6 +636,15 @@ function inferMediaType(path: string): string {
|
|
|
364
636
|
: "application/vnd.api+json";
|
|
365
637
|
}
|
|
366
638
|
|
|
639
|
+
function isUnauthorizedCliError(error: unknown): boolean {
|
|
640
|
+
return (
|
|
641
|
+
error instanceof CliError &&
|
|
642
|
+
error.exitCode === 1 &&
|
|
643
|
+
(error.message.includes("Authentication required") ||
|
|
644
|
+
error.message.includes("code=unauthorized"))
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
367
648
|
function isPlainJsonPath(path: string): boolean {
|
|
368
649
|
return (
|
|
369
650
|
path === `${API_PREFIX}/open_api` || path.startsWith(`${API_PREFIX}/auth/`)
|