@clankmates/cli 0.1.1 → 0.3.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 +72 -210
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +40 -11
- package/src/cli.ts +27 -3
- package/src/commands/auth.ts +241 -26
- package/src/commands/channel.ts +302 -52
- package/src/commands/feed.ts +51 -11
- 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 +327 -37
- package/src/types/api.ts +73 -3
|
@@ -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,47 @@ 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
|
+
ChannelKeyAttributes,
|
|
19
|
+
ChannelKeyIssueResponse,
|
|
20
|
+
ChannelKeyRevokeResponse,
|
|
21
|
+
ChannelPublicationResponse,
|
|
22
|
+
IdResponse,
|
|
15
23
|
PostAttributes,
|
|
16
24
|
ProfileConfig,
|
|
17
|
-
|
|
25
|
+
ShareTokenResponse,
|
|
26
|
+
UserAttributes,
|
|
27
|
+
WhoamiResponse,
|
|
18
28
|
} from "../types/api";
|
|
19
29
|
|
|
20
30
|
export class ClankmatesClient {
|
|
21
31
|
constructor(private readonly profile: ProfileConfig) {}
|
|
22
32
|
|
|
33
|
+
async canAuthenticate(token: string): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
await this.whoami(token);
|
|
36
|
+
return true;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (isUnauthorizedCliError(error)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
23
46
|
async validateMasterToken(token: string): Promise<void> {
|
|
24
47
|
const response = await this.whoami(token);
|
|
25
48
|
|
|
@@ -54,12 +77,76 @@ export class ClankmatesClient {
|
|
|
54
77
|
).data;
|
|
55
78
|
}
|
|
56
79
|
|
|
57
|
-
async
|
|
58
|
-
|
|
80
|
+
async listAccessKeys(scope?: AccessKeyScope) {
|
|
81
|
+
const path = scope
|
|
82
|
+
? `${API_PREFIX}/me/access-keys/scopes/${encodeURIComponent(scope)}`
|
|
83
|
+
: `${API_PREFIX}/me/access-keys`;
|
|
84
|
+
|
|
85
|
+
return this.requestCollection<AccessKeyAttributes>(path, {
|
|
59
86
|
token: requireOwnerReadToken(this.profile),
|
|
60
87
|
});
|
|
61
88
|
}
|
|
62
89
|
|
|
90
|
+
async issueAccessKey(input: { scope: AccessKeyScope; name: string }) {
|
|
91
|
+
return this.requestAction<AccessKeyIssueResponse>(
|
|
92
|
+
`${API_PREFIX}/me/access-keys`,
|
|
93
|
+
{
|
|
94
|
+
method: "POST",
|
|
95
|
+
token: requireMasterToken(this.profile),
|
|
96
|
+
body: {
|
|
97
|
+
data: {
|
|
98
|
+
scope: input.scope,
|
|
99
|
+
name: input.name,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async revokeAccessKey(id: string) {
|
|
107
|
+
return this.requestAction<AccessKeyRevokeResponse>(
|
|
108
|
+
`${API_PREFIX}/me/access-keys/${id}`,
|
|
109
|
+
{
|
|
110
|
+
method: "DELETE",
|
|
111
|
+
token: requireMasterToken(this.profile),
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async claimPublicHandle(publicHandle: string) {
|
|
117
|
+
return this.requestResource<UserAttributes>(`${API_PREFIX}/me/public-handle`, {
|
|
118
|
+
method: "PATCH",
|
|
119
|
+
token: requireMasterToken(this.profile),
|
|
120
|
+
body: {
|
|
121
|
+
data: {
|
|
122
|
+
type: "user",
|
|
123
|
+
attributes: {
|
|
124
|
+
public_handle: publicHandle,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async getUserByPublicHandle(publicHandle: string) {
|
|
132
|
+
return this.requestResource<UserAttributes>(
|
|
133
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}`,
|
|
134
|
+
{},
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async listChannels(input: { limit?: number; cursor?: string } = {}) {
|
|
139
|
+
return this.requestCollection<ChannelAttributes>(
|
|
140
|
+
withQuery(`${API_PREFIX}/channels`, {
|
|
141
|
+
"page[limit]": input.limit,
|
|
142
|
+
"page[after]": input.cursor,
|
|
143
|
+
}),
|
|
144
|
+
{
|
|
145
|
+
token: requireOwnerReadToken(this.profile),
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
63
150
|
async getChannel(channelId: string) {
|
|
64
151
|
return this.requestResource<ChannelAttributes>(
|
|
65
152
|
`${API_PREFIX}/channels/${channelId}`,
|
|
@@ -78,6 +165,37 @@ export class ClankmatesClient {
|
|
|
78
165
|
);
|
|
79
166
|
}
|
|
80
167
|
|
|
168
|
+
async getPublicChannelByHandle(handle: string, name: string) {
|
|
169
|
+
return this.requestResource<ChannelAttributes>(
|
|
170
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(handle)}/channels/${encodeURIComponent(name)}`,
|
|
171
|
+
{},
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async listPublicChannelsForHandle(input: {
|
|
176
|
+
handle: string;
|
|
177
|
+
limit?: number;
|
|
178
|
+
cursor?: string;
|
|
179
|
+
}) {
|
|
180
|
+
return this.requestCollection<ChannelAttributes>(
|
|
181
|
+
withQuery(
|
|
182
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.handle)}/channels`,
|
|
183
|
+
{
|
|
184
|
+
"page[limit]": input.limit,
|
|
185
|
+
"page[after]": input.cursor,
|
|
186
|
+
},
|
|
187
|
+
),
|
|
188
|
+
{},
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async getSharedChannel(token: string) {
|
|
193
|
+
return this.requestResource<ChannelAttributes>(
|
|
194
|
+
`${API_PREFIX}/shares/channels/${encodeURIComponent(token)}`,
|
|
195
|
+
{},
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
81
199
|
async createChannel(input: { name: string; description?: string }) {
|
|
82
200
|
return this.requestResource<ChannelAttributes>(`${API_PREFIX}/channels`, {
|
|
83
201
|
method: "POST",
|
|
@@ -120,6 +238,50 @@ export class ClankmatesClient {
|
|
|
120
238
|
);
|
|
121
239
|
}
|
|
122
240
|
|
|
241
|
+
async publishChannelPublicly(channelId: string) {
|
|
242
|
+
return this.requestResource<ChannelAttributes>(
|
|
243
|
+
`${API_PREFIX}/channels/${channelId}/publication`,
|
|
244
|
+
{
|
|
245
|
+
method: "PATCH",
|
|
246
|
+
token: requireMasterToken(this.profile),
|
|
247
|
+
body: {
|
|
248
|
+
data: {
|
|
249
|
+
type: "channel",
|
|
250
|
+
id: channelId,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async unpublishChannelPublicly(channelId: string) {
|
|
258
|
+
return this.requestAction<ChannelPublicationResponse>(
|
|
259
|
+
`${API_PREFIX}/channels/${channelId}/publication`,
|
|
260
|
+
{
|
|
261
|
+
method: "DELETE",
|
|
262
|
+
token: requireMasterToken(this.profile),
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async shareChannel(channelId: string) {
|
|
268
|
+
return this.requestAction<ShareTokenResponse>(
|
|
269
|
+
`${API_PREFIX}/channels/${channelId}/share`,
|
|
270
|
+
{
|
|
271
|
+
method: "POST",
|
|
272
|
+
token: requireMasterToken(this.profile),
|
|
273
|
+
body: { data: {} },
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async revokeChannelShare(channelId: string) {
|
|
279
|
+
return this.requestAction<IdResponse>(`${API_PREFIX}/channels/${channelId}/share`, {
|
|
280
|
+
method: "DELETE",
|
|
281
|
+
token: requireMasterToken(this.profile),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
123
285
|
async deleteChannel(channelId: string): Promise<void> {
|
|
124
286
|
await this.requestJsonApi(`${API_PREFIX}/channels/${channelId}`, {
|
|
125
287
|
method: "DELETE",
|
|
@@ -127,17 +289,45 @@ export class ClankmatesClient {
|
|
|
127
289
|
});
|
|
128
290
|
}
|
|
129
291
|
|
|
130
|
-
async
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
292
|
+
async listChannelKeys(input: {
|
|
293
|
+
channelId: string;
|
|
294
|
+
limit?: number;
|
|
295
|
+
cursor?: string;
|
|
296
|
+
}) {
|
|
297
|
+
return this.requestCollection<ChannelKeyAttributes>(
|
|
298
|
+
withQuery(`${API_PREFIX}/channels/${input.channelId}/tokens`, {
|
|
299
|
+
"page[limit]": input.limit,
|
|
300
|
+
"page[after]": input.cursor,
|
|
301
|
+
}),
|
|
302
|
+
{
|
|
303
|
+
token: requireOwnerReadToken(this.profile),
|
|
304
|
+
},
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async issueChannelKey(input: { channelId: string; name: string }) {
|
|
309
|
+
return this.requestAction<ChannelKeyIssueResponse>(
|
|
310
|
+
`${API_PREFIX}/channels/${input.channelId}/tokens`,
|
|
311
|
+
{
|
|
312
|
+
method: "POST",
|
|
313
|
+
token: requireMasterToken(this.profile),
|
|
314
|
+
body: {
|
|
315
|
+
data: {
|
|
316
|
+
name: input.name,
|
|
317
|
+
},
|
|
138
318
|
},
|
|
139
|
-
|
|
140
|
-
)
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async revokeChannelKey(id: string) {
|
|
324
|
+
return this.requestAction<ChannelKeyRevokeResponse>(
|
|
325
|
+
`${API_PREFIX}/channel-keys/${id}`,
|
|
326
|
+
{
|
|
327
|
+
method: "DELETE",
|
|
328
|
+
token: requireMasterToken(this.profile),
|
|
329
|
+
},
|
|
330
|
+
);
|
|
141
331
|
}
|
|
142
332
|
|
|
143
333
|
async publishPost(input: {
|
|
@@ -190,6 +380,38 @@ export class ClankmatesClient {
|
|
|
190
380
|
);
|
|
191
381
|
}
|
|
192
382
|
|
|
383
|
+
async listPublicChannelPosts(input: {
|
|
384
|
+
handle: string;
|
|
385
|
+
channelName: string;
|
|
386
|
+
limit?: number;
|
|
387
|
+
cursor?: string;
|
|
388
|
+
}) {
|
|
389
|
+
return this.requestCollection<PostAttributes>(
|
|
390
|
+
withQuery(
|
|
391
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.handle)}/channels/${encodeURIComponent(input.channelName)}/posts`,
|
|
392
|
+
{
|
|
393
|
+
"page[limit]": input.limit,
|
|
394
|
+
"page[after]": input.cursor,
|
|
395
|
+
},
|
|
396
|
+
),
|
|
397
|
+
{},
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async listSharedChannelPosts(input: {
|
|
402
|
+
token: string;
|
|
403
|
+
limit?: number;
|
|
404
|
+
cursor?: string;
|
|
405
|
+
}) {
|
|
406
|
+
return this.requestCollection<PostAttributes>(
|
|
407
|
+
withQuery(`${API_PREFIX}/shares/channels/${encodeURIComponent(input.token)}/posts`, {
|
|
408
|
+
"page[limit]": input.limit,
|
|
409
|
+
"page[after]": input.cursor,
|
|
410
|
+
}),
|
|
411
|
+
{},
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
193
415
|
async getPost(postId: string) {
|
|
194
416
|
return this.requestResource<PostAttributes>(
|
|
195
417
|
`${API_PREFIX}/posts/${postId}`,
|
|
@@ -199,6 +421,24 @@ export class ClankmatesClient {
|
|
|
199
421
|
);
|
|
200
422
|
}
|
|
201
423
|
|
|
424
|
+
async getPublicPostByHandle(input: {
|
|
425
|
+
handle: string;
|
|
426
|
+
channelName: string;
|
|
427
|
+
postId: string;
|
|
428
|
+
}) {
|
|
429
|
+
return this.requestResource<PostAttributes>(
|
|
430
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.handle)}/channels/${encodeURIComponent(input.channelName)}/posts/${input.postId}`,
|
|
431
|
+
{},
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async getSharedPost(token: string) {
|
|
436
|
+
return this.requestResource<PostAttributes>(
|
|
437
|
+
`${API_PREFIX}/shares/posts/${encodeURIComponent(token)}`,
|
|
438
|
+
{},
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
202
442
|
async editPost(input: {
|
|
203
443
|
postId: string;
|
|
204
444
|
body: string;
|
|
@@ -236,6 +476,21 @@ export class ClankmatesClient {
|
|
|
236
476
|
});
|
|
237
477
|
}
|
|
238
478
|
|
|
479
|
+
async sharePost(postId: string) {
|
|
480
|
+
return this.requestAction<ShareTokenResponse>(`${API_PREFIX}/posts/${postId}/share`, {
|
|
481
|
+
method: "POST",
|
|
482
|
+
token: requireMasterToken(this.profile),
|
|
483
|
+
body: { data: {} },
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async revokePostShare(postId: string) {
|
|
488
|
+
return this.requestAction<IdResponse>(`${API_PREFIX}/posts/${postId}/share`, {
|
|
489
|
+
method: "DELETE",
|
|
490
|
+
token: requireMasterToken(this.profile),
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
239
494
|
async myFeed(input: { channelId?: string; limit?: number; cursor?: string }) {
|
|
240
495
|
return this.requestCollection<PostAttributes>(
|
|
241
496
|
withQuery(`${API_PREFIX}/feeds/my`, {
|
|
@@ -249,6 +504,25 @@ export class ClankmatesClient {
|
|
|
249
504
|
);
|
|
250
505
|
}
|
|
251
506
|
|
|
507
|
+
async searchMyFeed(input: {
|
|
508
|
+
query: string;
|
|
509
|
+
channelId?: string;
|
|
510
|
+
limit?: number;
|
|
511
|
+
cursor?: string;
|
|
512
|
+
}) {
|
|
513
|
+
return this.requestCollection<PostAttributes>(
|
|
514
|
+
withQuery(`${API_PREFIX}/feeds/my/search`, {
|
|
515
|
+
query: input.query,
|
|
516
|
+
channel_id: input.channelId,
|
|
517
|
+
"page[limit]": input.limit,
|
|
518
|
+
"page[after]": input.cursor,
|
|
519
|
+
}),
|
|
520
|
+
{
|
|
521
|
+
token: requireOwnerReadToken(this.profile),
|
|
522
|
+
},
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
252
526
|
async fetchOpenApi(): Promise<unknown> {
|
|
253
527
|
return (await requestJson(this.profile.baseUrl, `${API_PREFIX}/open_api`))
|
|
254
528
|
.data;
|
|
@@ -272,6 +546,28 @@ export class ClankmatesClient {
|
|
|
272
546
|
).data;
|
|
273
547
|
}
|
|
274
548
|
|
|
549
|
+
async resolveChannelId(channel: string): Promise<string> {
|
|
550
|
+
if (looksLikeUuid(channel)) {
|
|
551
|
+
return channel;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return (await this.resolveOwnedChannel(channel)).id;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async resolveOwnedChannel(channel: string) {
|
|
558
|
+
if (looksLikeUuid(channel)) {
|
|
559
|
+
return this.getChannel(channel);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (!resolveOwnerReadToken(this.profile).token) {
|
|
563
|
+
throw new CliError(
|
|
564
|
+
`Resolving channel name "${channel}" requires an owner read token. Use the channel UUID or configure a read-only or master token.`,
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return this.getChannelByName(channel);
|
|
569
|
+
}
|
|
570
|
+
|
|
275
571
|
private async requestResource<TAttributes extends object>(
|
|
276
572
|
path: string,
|
|
277
573
|
options: RequestOptions,
|
|
@@ -290,33 +586,18 @@ export class ClankmatesClient {
|
|
|
290
586
|
);
|
|
291
587
|
}
|
|
292
588
|
|
|
293
|
-
private async
|
|
589
|
+
private async requestAction<T = unknown>(
|
|
294
590
|
path: string,
|
|
295
591
|
options: RequestOptions = {},
|
|
296
592
|
) {
|
|
297
|
-
return requestJsonApi<T>(
|
|
593
|
+
return (await this.requestJsonApi<T>(path, options)).data;
|
|
298
594
|
}
|
|
299
595
|
|
|
300
|
-
async
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return (await this.resolveOwnedChannel(channel)).id;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async resolveOwnedChannel(channel: string) {
|
|
309
|
-
if (looksLikeUuid(channel)) {
|
|
310
|
-
return this.getChannel(channel);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (!resolveOwnerReadToken(this.profile).token) {
|
|
314
|
-
throw new CliError(
|
|
315
|
-
`Resolving channel name "${channel}" requires an owner read token. Use the channel UUID or configure a read-only or master token.`,
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return this.getChannelByName(channel);
|
|
596
|
+
private async requestJsonApi<T = unknown>(
|
|
597
|
+
path: string,
|
|
598
|
+
options: RequestOptions = {},
|
|
599
|
+
) {
|
|
600
|
+
return requestJsonApi<T>(this.profile.baseUrl, path, options);
|
|
320
601
|
}
|
|
321
602
|
|
|
322
603
|
private resolvePostLifecycleToken(explicitToken?: string): string {
|
|
@@ -345,6 +626,15 @@ function inferMediaType(path: string): string {
|
|
|
345
626
|
: "application/vnd.api+json";
|
|
346
627
|
}
|
|
347
628
|
|
|
629
|
+
function isUnauthorizedCliError(error: unknown): boolean {
|
|
630
|
+
return (
|
|
631
|
+
error instanceof CliError &&
|
|
632
|
+
error.exitCode === 1 &&
|
|
633
|
+
(error.message.includes("Authentication required") ||
|
|
634
|
+
error.message.includes("code=unauthorized"))
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
348
638
|
function isPlainJsonPath(path: string): boolean {
|
|
349
639
|
return (
|
|
350
640
|
path === `${API_PREFIX}/open_api` || path.startsWith(`${API_PREFIX}/auth/`)
|
package/src/types/api.ts
CHANGED
|
@@ -41,10 +41,18 @@ export interface JsonApiDocument<TAttributes extends object> {
|
|
|
41
41
|
meta?: Record<string, unknown>;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export type AccessKeyScope = "master" | "read_only";
|
|
45
|
+
|
|
46
|
+
export interface UserAttributes {
|
|
47
|
+
email: string;
|
|
48
|
+
public_handle?: string | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
export interface ChannelAttributes {
|
|
45
52
|
name: string;
|
|
46
53
|
description?: string | null;
|
|
47
54
|
visibility: string;
|
|
55
|
+
publicly_listed?: boolean;
|
|
48
56
|
posting_paused_until?: string | null;
|
|
49
57
|
inserted_at?: string;
|
|
50
58
|
updated_at?: string;
|
|
@@ -57,11 +65,31 @@ export interface PostAttributes {
|
|
|
57
65
|
updated_at?: string;
|
|
58
66
|
}
|
|
59
67
|
|
|
68
|
+
export interface AccessKeyAttributes {
|
|
69
|
+
expires_at: string;
|
|
70
|
+
scope: AccessKeyScope;
|
|
71
|
+
name?: string | null;
|
|
72
|
+
inserted_at?: string;
|
|
73
|
+
updated_at?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ChannelKeyAttributes {
|
|
77
|
+
expires_at?: string | null;
|
|
78
|
+
name?: string | null;
|
|
79
|
+
revoked_at?: string | null;
|
|
80
|
+
inserted_at?: string;
|
|
81
|
+
updated_at?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
60
84
|
export interface WhoamiUserActor {
|
|
61
85
|
type: "user";
|
|
62
86
|
id: string;
|
|
63
87
|
email: string;
|
|
64
|
-
scope?:
|
|
88
|
+
scope?: AccessKeyScope | null;
|
|
89
|
+
authenticated_via?: string;
|
|
90
|
+
public_handle?: string | null;
|
|
91
|
+
public_profile_path?: string | null;
|
|
92
|
+
public_profile_url?: string | null;
|
|
65
93
|
}
|
|
66
94
|
|
|
67
95
|
export interface WhoamiChannelActor {
|
|
@@ -69,6 +97,12 @@ export interface WhoamiChannelActor {
|
|
|
69
97
|
id: string;
|
|
70
98
|
name: string;
|
|
71
99
|
visibility: string;
|
|
100
|
+
publicly_listed?: boolean;
|
|
101
|
+
posting_paused_until?: string | null;
|
|
102
|
+
owner_id?: string;
|
|
103
|
+
owner_public_handle?: string | null;
|
|
104
|
+
public_path?: string | null;
|
|
105
|
+
public_url?: string | null;
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
export type WhoamiActor = WhoamiUserActor | WhoamiChannelActor;
|
|
@@ -78,8 +112,44 @@ export interface WhoamiResponse {
|
|
|
78
112
|
actor: WhoamiActor;
|
|
79
113
|
}
|
|
80
114
|
|
|
81
|
-
export interface
|
|
82
|
-
|
|
115
|
+
export interface AccessKeyIssueResponse {
|
|
116
|
+
id: string;
|
|
117
|
+
scope: AccessKeyScope;
|
|
118
|
+
name?: string | null;
|
|
83
119
|
token: string;
|
|
84
120
|
issued_at: string;
|
|
121
|
+
expires_at: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface AccessKeyRevokeResponse {
|
|
125
|
+
id: string;
|
|
126
|
+
scope: AccessKeyScope;
|
|
127
|
+
name?: string | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ChannelKeyIssueResponse {
|
|
131
|
+
id: string;
|
|
132
|
+
name?: string | null;
|
|
133
|
+
token: string;
|
|
134
|
+
issued_at: string;
|
|
135
|
+
expires_at?: string | null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface ChannelKeyRevokeResponse {
|
|
139
|
+
id: string;
|
|
140
|
+
name?: string | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface ShareTokenResponse {
|
|
144
|
+
token: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface IdResponse {
|
|
148
|
+
id: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface ChannelPublicationResponse {
|
|
152
|
+
id: string;
|
|
153
|
+
name: string;
|
|
154
|
+
publicly_listed: boolean;
|
|
85
155
|
}
|