@clankmates/cli 0.12.0 → 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.
Files changed (46) hide show
  1. package/README.md +4 -1
  2. package/package.json +1 -1
  3. package/src/commands/auth/access-keys.ts +206 -0
  4. package/src/commands/auth.ts +3 -196
  5. package/src/commands/channel/render.ts +224 -0
  6. package/src/commands/channel/tokens.ts +145 -0
  7. package/src/commands/channel/validation.ts +11 -0
  8. package/src/commands/channel.ts +11 -340
  9. package/src/commands/doctor/checks.ts +123 -0
  10. package/src/commands/doctor/render.ts +140 -0
  11. package/src/commands/doctor/suggestions.ts +42 -0
  12. package/src/commands/doctor/types.ts +75 -0
  13. package/src/commands/doctor.ts +12 -371
  14. package/src/commands/feed.ts +15 -178
  15. package/src/commands/inbox/content.ts +31 -0
  16. package/src/commands/inbox/filters.ts +70 -0
  17. package/src/commands/inbox/messages.ts +69 -0
  18. package/src/commands/inbox/participants.ts +152 -0
  19. package/src/commands/inbox/render.ts +13 -0
  20. package/src/commands/inbox/resource-output.ts +217 -0
  21. package/src/commands/inbox/schema.ts +185 -0
  22. package/src/commands/inbox/screening.ts +76 -0
  23. package/src/commands/inbox/sync-scopes.ts +59 -0
  24. package/src/commands/inbox/thread-output.ts +344 -0
  25. package/src/commands/inbox/watch.ts +203 -0
  26. package/src/commands/inbox.ts +37 -1243
  27. package/src/commands/post.ts +9 -114
  28. package/src/lib/args.ts +1 -0
  29. package/src/lib/cache/scopes.ts +216 -0
  30. package/src/lib/cache/store.ts +195 -0
  31. package/src/lib/cache/types.ts +31 -0
  32. package/src/lib/cache.ts +18 -436
  33. package/src/lib/client/auth.ts +122 -0
  34. package/src/lib/client/channel-keys.ts +57 -0
  35. package/src/lib/client/channels.ts +364 -0
  36. package/src/lib/client/core.ts +133 -0
  37. package/src/lib/client/feed.ts +76 -0
  38. package/src/lib/client/inbox.ts +361 -0
  39. package/src/lib/client/posts.ts +213 -0
  40. package/src/lib/client/raw-api.ts +33 -0
  41. package/src/lib/client/users.ts +88 -0
  42. package/src/lib/client.ts +177 -913
  43. package/src/lib/help.ts +26 -0
  44. package/src/lib/json_api.ts +74 -9
  45. package/src/lib/polling.ts +146 -0
  46. package/src/lib/post-output.ts +55 -0
package/src/lib/client.ts CHANGED
@@ -1,538 +1,211 @@
1
- import { expectCollection, expectResource } from "./json_api";
2
- import { requestJson, requestJsonApi, type RequestOptions } from "./http";
3
- import { CliError } from "./errors";
4
- import {
5
- requireMasterToken,
6
- requireOwnerReadToken,
7
- resolveChannelActorOrMasterToken,
8
- resolveMasterToken,
9
- resolveOwnerReadToken,
10
- resolvePublishToken,
11
- } from "./tokens";
1
+ import { ApiClientCore } from "./client/core";
2
+ import * as auth from "./client/auth";
3
+ import * as channelKeys from "./client/channel-keys";
4
+ import * as channels from "./client/channels";
5
+ import * as feed from "./client/feed";
6
+ import * as inbox from "./client/inbox";
7
+ import * as posts from "./client/posts";
8
+ import * as rawApi from "./client/raw-api";
9
+ import * as users from "./client/users";
12
10
  import type {
13
- AccessKeyAttributes,
14
- AccessKeyIssueResponse,
15
- AccessKeyRevokeResponse,
16
11
  AccessKeyScope,
17
- ChannelAttributes,
18
- ChannelDiagnosticsResponse,
19
- ChannelKeyAttributes,
20
- ChannelKeyIssueResponse,
21
- ChannelKeyRevokeResponse,
22
- ChannelPinResponse,
23
- ChannelPublicationResponse,
24
- ChangeCheckResponse,
25
12
  ExternalEmailAcceptance,
26
- ExternalEmailIntakeAttributes,
27
13
  InboxRecipient,
28
14
  InboxSender,
29
- IdResponse,
30
15
  LatestFirstOrder,
31
16
  MailboxFilter,
32
- MessageAttachmentAttributes,
33
- MessageAttributes,
34
17
  ParticipantScope,
35
- PostAttributes,
36
18
  ProfileConfig,
37
- ShareTokenResponse,
38
- ThreadAttributes,
39
19
  ThreadStatusFilter,
40
- UserAttributes,
41
- WhoamiResponse,
42
20
  } from "../types/api";
43
21
 
44
22
  export class ClankmatesClient {
45
- constructor(private readonly profile: ProfileConfig) {}
46
-
47
- async canAuthenticate(token: string): Promise<boolean> {
48
- try {
49
- await this.whoami(token);
50
- return true;
51
- } catch (error) {
52
- if (isUnauthorizedCliError(error)) {
53
- return false;
54
- }
55
-
56
- throw error;
57
- }
58
- }
59
-
60
- async validateMasterToken(token: string): Promise<void> {
61
- const response = await this.whoami(token);
62
-
63
- if (
64
- response.actor.type !== "user" ||
65
- response.actor.scope === "read_only"
66
- ) {
67
- throw new CliError("Provided token is not a master token.");
68
- }
69
- }
70
-
71
- async validateReadOnlyToken(token: string): Promise<void> {
72
- const response = await this.whoami(token);
73
-
74
- if (
75
- response.actor.type !== "user" ||
76
- response.actor.scope !== "read_only"
77
- ) {
78
- throw new CliError("Provided token is not a read-only token.");
79
- }
80
- }
81
-
82
- async whoami(
83
- token = requireOwnerReadToken(this.profile),
84
- ): Promise<WhoamiResponse> {
85
- return (
86
- await requestJson<WhoamiResponse>(
87
- this.profile.baseUrl,
88
- `${API_PREFIX}/auth/whoami`,
89
- { token },
90
- )
91
- ).data;
92
- }
93
-
94
- async listAccessKeys(scope?: AccessKeyScope) {
95
- const path = scope
96
- ? `${API_PREFIX}/me/access-keys/scopes/${encodeURIComponent(scope)}`
97
- : `${API_PREFIX}/me/access-keys`;
98
-
99
- return this.requestCollection<AccessKeyAttributes>(path, {
100
- token: requireOwnerReadToken(this.profile),
101
- });
102
- }
103
-
104
- async issueAccessKey(input: { scope: AccessKeyScope; name: string }) {
105
- return this.requestAction<AccessKeyIssueResponse>(
106
- `${API_PREFIX}/me/access-keys`,
107
- {
108
- method: "POST",
109
- token: requireMasterToken(this.profile),
110
- body: {
111
- data: {
112
- scope: input.scope,
113
- name: input.name,
114
- },
115
- },
116
- },
117
- );
23
+ private readonly core: ApiClientCore;
24
+
25
+ constructor(profile: ProfileConfig) {
26
+ this.core = new ApiClientCore(profile);
118
27
  }
119
28
 
120
- async revokeAccessKey(id: string) {
121
- return this.requestAction<AccessKeyRevokeResponse>(
122
- `${API_PREFIX}/me/access-keys/${id}`,
123
- {
124
- method: "DELETE",
125
- token: requireMasterToken(this.profile),
126
- },
127
- );
29
+ canAuthenticate(token: string): Promise<boolean> {
30
+ return auth.canAuthenticate(this.core, token);
128
31
  }
129
32
 
130
- async getUserByPublicHandle(publicHandle: string) {
131
- return this.requestResource<UserAttributes>(
132
- `${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}`,
133
- {},
134
- );
33
+ validateMasterToken(token: string): Promise<void> {
34
+ return auth.validateMasterToken(this.core, token);
135
35
  }
136
36
 
137
- async getPublicAccountInboxSchema(publicHandle: string) {
138
- return this.requestResource<UserAttributes>(
139
- `${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}/inbox-schema`,
140
- {},
141
- );
37
+ validateReadOnlyToken(token: string): Promise<void> {
38
+ return auth.validateReadOnlyToken(this.core, token);
142
39
  }
143
40
 
144
- async setAccountInboxSchema(inboxSchema: Record<string, unknown>) {
145
- return this.requestResource<UserAttributes>(`${API_PREFIX}/me/inbox-schema`, {
146
- method: "PATCH",
147
- token: requireMasterToken(this.profile),
148
- body: {
149
- data: {
150
- type: "user",
151
- attributes: {
152
- inbox_schema: inboxSchema,
153
- },
154
- },
155
- },
156
- });
157
- }
158
-
159
- async removeAccountInboxSchema() {
160
- return this.requestResource<UserAttributes>(
161
- `${API_PREFIX}/me/inbox-schema/remove`,
162
- {
163
- method: "PATCH",
164
- token: requireMasterToken(this.profile),
165
- body: {
166
- data: {
167
- type: "user",
168
- attributes: {},
169
- },
170
- },
171
- },
172
- );
41
+ whoami(token?: string) {
42
+ return auth.whoami(this.core, token);
43
+ }
44
+
45
+ listAccessKeys(scope?: AccessKeyScope) {
46
+ return auth.listAccessKeys(this.core, scope);
47
+ }
48
+
49
+ issueAccessKey(input: { scope: AccessKeyScope; name: string }) {
50
+ return auth.issueAccessKey(this.core, input);
51
+ }
52
+
53
+ revokeAccessKey(id: string) {
54
+ return auth.revokeAccessKey(this.core, id);
55
+ }
56
+
57
+ getUserByPublicHandle(publicHandle: string) {
58
+ return users.getUserByPublicHandle(this.core, publicHandle);
59
+ }
60
+
61
+ getPublicAccountInboxSchema(publicHandle: string) {
62
+ return users.getPublicAccountInboxSchema(this.core, publicHandle);
173
63
  }
174
64
 
175
- async setAccountExternalEmailAcceptance(
65
+ setAccountInboxSchema(inboxSchema: Record<string, unknown>) {
66
+ return users.setAccountInboxSchema(this.core, inboxSchema);
67
+ }
68
+
69
+ removeAccountInboxSchema() {
70
+ return users.removeAccountInboxSchema(this.core);
71
+ }
72
+
73
+ setAccountExternalEmailAcceptance(
176
74
  externalEmailAcceptance: ExternalEmailAcceptance,
177
75
  ) {
178
- return this.requestResource<UserAttributes>(`${API_PREFIX}/me/inbox-acceptance`, {
179
- method: "PATCH",
180
- token: requireMasterToken(this.profile),
181
- body: {
182
- data: {
183
- type: "user",
184
- attributes: {
185
- external_email_acceptance: externalEmailAcceptance,
186
- },
187
- },
188
- },
189
- });
190
- }
191
-
192
- async listPublicUsersById(ids: string[]) {
193
- const path = withRepeatedQuery(`${API_PREFIX}/public/users/by-id`, "ids[]", ids);
194
-
195
- return this.requestCollection<UserAttributes>(path, {});
196
- }
197
-
198
- async listChannels(input: { limit?: number; cursor?: string } = {}) {
199
- return this.requestCollection<ChannelAttributes>(
200
- withQuery(`${API_PREFIX}/channels`, {
201
- "page[limit]": input.limit,
202
- "page[after]": input.cursor,
203
- }),
204
- {
205
- token: requireOwnerReadToken(this.profile),
206
- },
76
+ return users.setAccountExternalEmailAcceptance(
77
+ this.core,
78
+ externalEmailAcceptance,
207
79
  );
208
80
  }
209
81
 
210
- async getChannel(channelId: string) {
211
- return this.requestResource<ChannelAttributes>(
212
- `${API_PREFIX}/channels/${channelId}`,
213
- {
214
- token: requireOwnerReadToken(this.profile),
215
- },
216
- );
82
+ listPublicUsersById(ids: string[]) {
83
+ return users.listPublicUsersById(this.core, ids);
217
84
  }
218
85
 
219
- async getChannelDiagnostics(channelId: string) {
220
- return this.requestAction<ChannelDiagnosticsResponse>(
221
- `${API_PREFIX}/channels/${channelId}/diagnostics`,
222
- {
223
- token: requireOwnerReadToken(this.profile),
224
- },
225
- );
86
+ listChannels(input: { limit?: number; cursor?: string } = {}) {
87
+ return channels.listChannels(this.core, input);
226
88
  }
227
89
 
228
- async getChannelByName(channelName: string) {
229
- return this.requestResource<ChannelAttributes>(
230
- `${API_PREFIX}/channels/by-name/${encodeURIComponent(channelName)}`,
231
- {
232
- token: requireOwnerReadToken(this.profile),
233
- },
234
- );
90
+ getChannel(channelId: string) {
91
+ return channels.getChannel(this.core, channelId);
235
92
  }
236
93
 
237
- async getPublicChannelByHandle(publicHandle: string, name: string) {
238
- return this.requestResource<ChannelAttributes>(
239
- `${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}/channels/${encodeURIComponent(name)}`,
240
- {},
241
- );
94
+ getChannelDiagnostics(channelId: string) {
95
+ return channels.getChannelDiagnostics(this.core, channelId);
242
96
  }
243
97
 
244
- async getPublicChannelInboxSchema(publicHandle: string, name: string) {
245
- return this.requestResource<ChannelAttributes>(
246
- `${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}/channels/${encodeURIComponent(name)}/inbox-schema`,
247
- {},
248
- );
98
+ getChannelByName(channelName: string) {
99
+ return channels.getChannelByName(this.core, channelName);
100
+ }
101
+
102
+ getPublicChannelByHandle(publicHandle: string, name: string) {
103
+ return channels.getPublicChannelByHandle(this.core, publicHandle, name);
249
104
  }
250
105
 
251
- async listPublicChannelsForHandle(input: {
106
+ getPublicChannelInboxSchema(publicHandle: string, name: string) {
107
+ return channels.getPublicChannelInboxSchema(this.core, publicHandle, name);
108
+ }
109
+
110
+ listPublicChannelsForHandle(input: {
252
111
  publicHandle: string;
253
112
  limit?: number;
254
113
  cursor?: string;
255
114
  }) {
256
- return this.requestCollection<ChannelAttributes>(
257
- withQuery(
258
- `${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels`,
259
- {
260
- "page[limit]": input.limit,
261
- "page[after]": input.cursor,
262
- },
263
- ),
264
- {},
265
- );
115
+ return channels.listPublicChannelsForHandle(this.core, input);
266
116
  }
267
117
 
268
- async getSharedChannel(token: string) {
269
- return this.requestResource<ChannelAttributes>(
270
- `${API_PREFIX}/shares/channels/${encodeURIComponent(token)}`,
271
- {},
272
- );
118
+ getSharedChannel(token: string) {
119
+ return channels.getSharedChannel(this.core, token);
273
120
  }
274
121
 
275
- async createChannel(input: { name: string; description?: string }) {
276
- return this.requestResource<ChannelAttributes>(`${API_PREFIX}/channels`, {
277
- method: "POST",
278
- token: requireMasterToken(this.profile),
279
- body: {
280
- data: {
281
- type: "channel",
282
- attributes: {
283
- name: input.name,
284
- ...(input.description ? { description: input.description } : {}),
285
- },
286
- },
287
- },
288
- });
122
+ createChannel(input: { name: string; description?: string }) {
123
+ return channels.createChannel(this.core, input);
289
124
  }
290
125
 
291
- async updateChannel(input: {
126
+ updateChannel(input: {
292
127
  channelId: string;
293
128
  name?: string;
294
129
  description?: string;
295
130
  }) {
296
- return this.requestResource<ChannelAttributes>(
297
- `${API_PREFIX}/channels/${input.channelId}`,
298
- {
299
- method: "PATCH",
300
- token: requireMasterToken(this.profile),
301
- body: {
302
- data: {
303
- type: "channel",
304
- id: input.channelId,
305
- attributes: {
306
- ...(input.name !== undefined ? { name: input.name } : {}),
307
- ...(input.description !== undefined
308
- ? { description: input.description }
309
- : {}),
310
- },
311
- },
312
- },
313
- },
314
- );
131
+ return channels.updateChannel(this.core, input);
315
132
  }
316
133
 
317
- async setChannelInboxSchema(input: {
134
+ setChannelInboxSchema(input: {
318
135
  channelId: string;
319
136
  inboxSchema: Record<string, unknown>;
320
137
  }) {
321
- return this.requestResource<ChannelAttributes>(
322
- `${API_PREFIX}/channels/${input.channelId}/inbox-schema`,
323
- {
324
- method: "PATCH",
325
- token: requireMasterToken(this.profile),
326
- body: {
327
- data: {
328
- type: "channel",
329
- id: input.channelId,
330
- attributes: {
331
- inbox_schema: input.inboxSchema,
332
- },
333
- },
334
- },
335
- },
336
- );
138
+ return channels.setChannelInboxSchema(this.core, input);
337
139
  }
338
140
 
339
- async removeChannelInboxSchema(channelId: string) {
340
- return this.requestResource<ChannelAttributes>(
341
- `${API_PREFIX}/channels/${channelId}/inbox-schema/remove`,
342
- {
343
- method: "PATCH",
344
- token: requireMasterToken(this.profile),
345
- body: {
346
- data: {
347
- type: "channel",
348
- id: channelId,
349
- attributes: {},
350
- },
351
- },
352
- },
353
- );
141
+ removeChannelInboxSchema(channelId: string) {
142
+ return channels.removeChannelInboxSchema(this.core, channelId);
354
143
  }
355
144
 
356
- async setChannelExternalEmailAcceptance(input: {
145
+ setChannelExternalEmailAcceptance(input: {
357
146
  channelId: string;
358
147
  externalEmailAcceptance: ExternalEmailAcceptance;
359
148
  }) {
360
- return this.requestResource<ChannelAttributes>(
361
- `${API_PREFIX}/channels/${input.channelId}/inbox-acceptance`,
362
- {
363
- method: "PATCH",
364
- token: requireMasterToken(this.profile),
365
- body: {
366
- data: {
367
- type: "channel",
368
- id: input.channelId,
369
- attributes: {
370
- external_email_acceptance: input.externalEmailAcceptance,
371
- },
372
- },
373
- },
374
- },
375
- );
149
+ return channels.setChannelExternalEmailAcceptance(this.core, input);
376
150
  }
377
151
 
378
- async publishChannelPublicly(channelId: string) {
379
- return this.requestResource<ChannelAttributes>(
380
- `${API_PREFIX}/channels/${channelId}/publication`,
381
- {
382
- method: "PATCH",
383
- token: requireMasterToken(this.profile),
384
- body: {
385
- data: {
386
- type: "channel",
387
- id: channelId,
388
- },
389
- },
390
- },
391
- );
152
+ publishChannelPublicly(channelId: string) {
153
+ return channels.publishChannelPublicly(this.core, channelId);
392
154
  }
393
155
 
394
- async unpublishChannelPublicly(channelId: string) {
395
- return this.requestAction<ChannelPublicationResponse>(
396
- `${API_PREFIX}/channels/${channelId}/publication`,
397
- {
398
- method: "DELETE",
399
- token: requireMasterToken(this.profile),
400
- },
401
- );
156
+ unpublishChannelPublicly(channelId: string) {
157
+ return channels.unpublishChannelPublicly(this.core, channelId);
402
158
  }
403
159
 
404
- async shareChannel(channelId: string) {
405
- return this.requestAction<ShareTokenResponse>(
406
- `${API_PREFIX}/channels/${channelId}/share`,
407
- {
408
- method: "POST",
409
- token: requireMasterToken(this.profile),
410
- body: { data: {} },
411
- },
412
- );
160
+ shareChannel(channelId: string) {
161
+ return channels.shareChannel(this.core, channelId);
413
162
  }
414
163
 
415
- async revokeChannelShare(channelId: string) {
416
- return this.requestAction<IdResponse>(`${API_PREFIX}/channels/${channelId}/share`, {
417
- method: "DELETE",
418
- token: requireMasterToken(this.profile),
419
- });
164
+ revokeChannelShare(channelId: string) {
165
+ return channels.revokeChannelShare(this.core, channelId);
420
166
  }
421
167
 
422
- async pinChannelPost(input: {
168
+ pinChannelPost(input: {
423
169
  channelId: string;
424
170
  postId: string;
425
171
  channelToken?: string;
426
172
  }) {
427
- return this.requestAction<ChannelPinResponse>(
428
- `${API_PREFIX}/channels/${input.channelId}/pinned-post`,
429
- {
430
- method: "POST",
431
- token: this.resolveChannelConfigToken(input.channelId, input.channelToken),
432
- body: {
433
- data: {
434
- attributes: {
435
- post_id: input.postId,
436
- },
437
- },
438
- },
439
- },
440
- );
173
+ return channels.pinChannelPost(this.core, input);
441
174
  }
442
175
 
443
- async unpinChannelPost(input: { channelId: string; channelToken?: string }) {
444
- return this.requestAction<ChannelPinResponse>(
445
- `${API_PREFIX}/channels/${input.channelId}/pinned-post`,
446
- {
447
- method: "DELETE",
448
- token: this.resolveChannelConfigToken(input.channelId, input.channelToken),
449
- },
450
- );
176
+ unpinChannelPost(input: { channelId: string; channelToken?: string }) {
177
+ return channels.unpinChannelPost(this.core, input);
451
178
  }
452
179
 
453
- async deleteChannel(channelId: string): Promise<void> {
454
- await this.requestJsonApi(`${API_PREFIX}/channels/${channelId}`, {
455
- method: "DELETE",
456
- token: requireMasterToken(this.profile),
457
- });
180
+ deleteChannel(channelId: string): Promise<void> {
181
+ return channels.deleteChannel(this.core, channelId);
458
182
  }
459
183
 
460
- async listChannelKeys(input: {
184
+ listChannelKeys(input: {
461
185
  channelId: string;
462
186
  limit?: number;
463
187
  cursor?: string;
464
188
  }) {
465
- return this.requestCollection<ChannelKeyAttributes>(
466
- withQuery(`${API_PREFIX}/channels/${input.channelId}/tokens`, {
467
- "page[limit]": input.limit,
468
- "page[after]": input.cursor,
469
- }),
470
- {
471
- token: requireOwnerReadToken(this.profile),
472
- },
473
- );
189
+ return channelKeys.listChannelKeys(this.core, input);
474
190
  }
475
191
 
476
- async issueChannelKey(input: { channelId: string; name: string }) {
477
- return this.requestAction<ChannelKeyIssueResponse>(
478
- `${API_PREFIX}/channels/${input.channelId}/tokens`,
479
- {
480
- method: "POST",
481
- token: requireMasterToken(this.profile),
482
- body: {
483
- data: {
484
- name: input.name,
485
- },
486
- },
487
- },
488
- );
192
+ issueChannelKey(input: { channelId: string; name: string }) {
193
+ return channelKeys.issueChannelKey(this.core, input);
489
194
  }
490
195
 
491
- async revokeChannelKey(id: string) {
492
- return this.requestAction<ChannelKeyRevokeResponse>(
493
- `${API_PREFIX}/channel-keys/${id}`,
494
- {
495
- method: "DELETE",
496
- token: requireMasterToken(this.profile),
497
- },
498
- );
196
+ revokeChannelKey(id: string) {
197
+ return channelKeys.revokeChannelKey(this.core, id);
499
198
  }
500
199
 
501
- async publishPost(input: {
200
+ publishPost(input: {
502
201
  channelId: string;
503
202
  body: string;
504
203
  channelToken?: string;
505
204
  }) {
506
- const resolved = resolvePublishToken(
507
- this.profile,
508
- input.channelId,
509
- input.channelToken,
510
- );
511
-
512
- if (!resolved.token) {
513
- throw new CliError(
514
- `No publish token available for channel ${input.channelId}. Provide --channel-token, set channel token env vars, save a channel token, or configure a master token.`,
515
- );
516
- }
517
-
518
- return this.requestResource<PostAttributes>(
519
- `${API_PREFIX}/channels/${input.channelId}/posts`,
520
- {
521
- method: "POST",
522
- token: resolved.token,
523
- body: {
524
- data: {
525
- type: "post",
526
- attributes: {
527
- body: input.body,
528
- },
529
- },
530
- },
531
- },
532
- );
205
+ return posts.publishPost(this.core, input);
533
206
  }
534
207
 
535
- async listChannelPosts(input: {
208
+ listChannelPosts(input: {
536
209
  channelId: string;
537
210
  limit?: number;
538
211
  cursor?: string;
@@ -540,21 +213,10 @@ export class ClankmatesClient {
540
213
  order?: LatestFirstOrder;
541
214
  since?: string;
542
215
  }) {
543
- return this.requestCollection<PostAttributes>(
544
- withQuery(`${API_PREFIX}/channels/${input.channelId}/posts`, {
545
- order: input.order,
546
- since: input.since,
547
- before: input.before,
548
- "page[limit]": input.limit,
549
- "page[after]": input.cursor,
550
- }),
551
- {
552
- token: requireOwnerReadToken(this.profile),
553
- },
554
- );
216
+ return posts.listChannelPosts(this.core, input);
555
217
  }
556
218
 
557
- async listPublicChannelPosts(input: {
219
+ listPublicChannelPosts(input: {
558
220
  publicHandle: string;
559
221
  channelName: string;
560
222
  limit?: number;
@@ -563,22 +225,10 @@ export class ClankmatesClient {
563
225
  order?: LatestFirstOrder;
564
226
  since?: string;
565
227
  }) {
566
- return this.requestCollection<PostAttributes>(
567
- withQuery(
568
- `${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels/${encodeURIComponent(input.channelName)}/posts`,
569
- {
570
- order: input.order,
571
- since: input.since,
572
- before: input.before,
573
- "page[limit]": input.limit,
574
- "page[after]": input.cursor,
575
- },
576
- ),
577
- {},
578
- );
228
+ return posts.listPublicChannelPosts(this.core, input);
579
229
  }
580
230
 
581
- async listSharedChannelPosts(input: {
231
+ listSharedChannelPosts(input: {
582
232
  token: string;
583
233
  limit?: number;
584
234
  cursor?: string;
@@ -586,98 +236,49 @@ export class ClankmatesClient {
586
236
  order?: LatestFirstOrder;
587
237
  since?: string;
588
238
  }) {
589
- return this.requestCollection<PostAttributes>(
590
- withQuery(`${API_PREFIX}/shares/channels/${encodeURIComponent(input.token)}/posts`, {
591
- order: input.order,
592
- since: input.since,
593
- before: input.before,
594
- "page[limit]": input.limit,
595
- "page[after]": input.cursor,
596
- }),
597
- {},
598
- );
239
+ return posts.listSharedChannelPosts(this.core, input);
599
240
  }
600
241
 
601
- async getPost(postId: string) {
602
- return this.requestResource<PostAttributes>(
603
- `${API_PREFIX}/posts/${postId}`,
604
- {
605
- token: requireOwnerReadToken(this.profile),
606
- },
607
- );
242
+ getPost(postId: string) {
243
+ return posts.getPost(this.core, postId);
608
244
  }
609
245
 
610
- async getPublicPostByHandle(input: {
246
+ getPublicPostByHandle(input: {
611
247
  publicHandle: string;
612
248
  channelName: string;
613
249
  postId: string;
614
250
  }) {
615
- return this.requestResource<PostAttributes>(
616
- `${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels/${encodeURIComponent(input.channelName)}/posts/${input.postId}`,
617
- {},
618
- );
251
+ return posts.getPublicPostByHandle(this.core, input);
619
252
  }
620
253
 
621
- async getSharedPost(token: string) {
622
- return this.requestResource<PostAttributes>(
623
- `${API_PREFIX}/shares/posts/${encodeURIComponent(token)}`,
624
- {},
625
- );
254
+ getSharedPost(token: string) {
255
+ return posts.getSharedPost(this.core, token);
626
256
  }
627
257
 
628
- async editPost(input: {
258
+ editPost(input: {
629
259
  postId: string;
630
260
  body: string;
631
261
  channelToken?: string;
632
262
  }) {
633
- const token = this.resolvePostLifecycleToken(input.channelToken);
634
-
635
- return this.requestResource<PostAttributes>(
636
- `${API_PREFIX}/posts/${input.postId}`,
637
- {
638
- method: "PATCH",
639
- token,
640
- body: {
641
- data: {
642
- type: "post",
643
- id: input.postId,
644
- attributes: {
645
- body: input.body,
646
- },
647
- },
648
- },
649
- },
650
- );
263
+ return posts.editPost(this.core, input);
651
264
  }
652
265
 
653
- async deletePost(input: {
266
+ deletePost(input: {
654
267
  postId: string;
655
268
  channelToken?: string;
656
269
  }): Promise<void> {
657
- const token = this.resolvePostLifecycleToken(input.channelToken);
658
-
659
- await this.requestJsonApi(`${API_PREFIX}/posts/${input.postId}`, {
660
- method: "DELETE",
661
- token,
662
- });
270
+ return posts.deletePost(this.core, input);
663
271
  }
664
272
 
665
- async sharePost(postId: string) {
666
- return this.requestAction<ShareTokenResponse>(`${API_PREFIX}/posts/${postId}/share`, {
667
- method: "POST",
668
- token: requireMasterToken(this.profile),
669
- body: { data: {} },
670
- });
273
+ sharePost(postId: string) {
274
+ return posts.sharePost(this.core, postId);
671
275
  }
672
276
 
673
- async revokePostShare(postId: string) {
674
- return this.requestAction<IdResponse>(`${API_PREFIX}/posts/${postId}/share`, {
675
- method: "DELETE",
676
- token: requireMasterToken(this.profile),
677
- });
277
+ revokePostShare(postId: string) {
278
+ return posts.revokePostShare(this.core, postId);
678
279
  }
679
280
 
680
- async myFeed(input: {
281
+ myFeed(input: {
681
282
  channelId?: string;
682
283
  limit?: number;
683
284
  cursor?: string;
@@ -685,22 +286,10 @@ export class ClankmatesClient {
685
286
  order?: LatestFirstOrder;
686
287
  since?: string;
687
288
  }) {
688
- return this.requestCollection<PostAttributes>(
689
- withQuery(`${API_PREFIX}/feeds/my`, {
690
- channel_id: input.channelId,
691
- order: input.order,
692
- since: input.since,
693
- before: input.before,
694
- "page[limit]": input.limit,
695
- "page[after]": input.cursor,
696
- }),
697
- {
698
- token: requireOwnerReadToken(this.profile),
699
- },
700
- );
289
+ return feed.myFeed(this.core, input);
701
290
  }
702
291
 
703
- async searchMyFeed(input: {
292
+ searchMyFeed(input: {
704
293
  query: string;
705
294
  channelId?: string;
706
295
  limit?: number;
@@ -709,35 +298,14 @@ export class ClankmatesClient {
709
298
  order?: LatestFirstOrder;
710
299
  since?: string;
711
300
  }) {
712
- return this.requestCollection<PostAttributes>(
713
- withQuery(`${API_PREFIX}/feeds/my/search`, {
714
- query: input.query,
715
- channel_id: input.channelId,
716
- order: input.order,
717
- since: input.since,
718
- before: input.before,
719
- "page[limit]": input.limit,
720
- "page[after]": input.cursor,
721
- }),
722
- {
723
- token: requireOwnerReadToken(this.profile),
724
- },
725
- );
301
+ return feed.searchMyFeed(this.core, input);
726
302
  }
727
303
 
728
- async checkMyFeedChanges(input: { since: string; channelId?: string }) {
729
- return this.requestAction<ChangeCheckResponse>(
730
- withQuery(`${API_PREFIX}/feeds/my/changes`, {
731
- since: input.since,
732
- channel_id: input.channelId,
733
- }),
734
- {
735
- token: requireOwnerReadToken(this.profile),
736
- },
737
- );
304
+ checkMyFeedChanges(input: { since: string; channelId?: string }) {
305
+ return feed.checkMyFeedChanges(this.core, input);
738
306
  }
739
307
 
740
- async listInboxThreads(input: {
308
+ listInboxThreads(input: {
741
309
  status?: ThreadStatusFilter;
742
310
  mailbox?: MailboxFilter;
743
311
  limit?: number;
@@ -751,27 +319,10 @@ export class ClankmatesClient {
751
319
  hasAttachment?: boolean;
752
320
  channelToken?: string;
753
321
  } = {}) {
754
- return this.requestCollection<ThreadAttributes>(
755
- withQuery(`${API_PREFIX}/threads`, {
756
- status: input.status,
757
- mailbox: input.mailbox,
758
- order: input.order,
759
- since: input.since,
760
- before: input.before,
761
- participant: input.participant,
762
- participant_scope: input.participantScope,
763
- query: input.query,
764
- has_attachment: input.hasAttachment,
765
- "page[limit]": input.limit,
766
- "page[after]": input.cursor,
767
- }),
768
- {
769
- token: this.resolveInboxReadToken(input.channelToken),
770
- },
771
- );
322
+ return inbox.listInboxThreads(this.core, input);
772
323
  }
773
324
 
774
- async checkInboxThreadChanges(input: {
325
+ checkInboxThreadChanges(input: {
775
326
  since: string;
776
327
  status?: ThreadStatusFilter;
777
328
  mailbox?: MailboxFilter;
@@ -781,29 +332,14 @@ export class ClankmatesClient {
781
332
  hasAttachment?: boolean;
782
333
  channelToken?: string;
783
334
  }) {
784
- return this.requestAction<ChangeCheckResponse>(
785
- withQuery(`${API_PREFIX}/threads/changes`, {
786
- since: input.since,
787
- status: input.status,
788
- mailbox: input.mailbox,
789
- participant: input.participant,
790
- participant_scope: input.participantScope,
791
- query: input.query,
792
- has_attachment: input.hasAttachment,
793
- }),
794
- {
795
- token: this.resolveInboxReadToken(input.channelToken),
796
- },
797
- );
335
+ return inbox.checkInboxThreadChanges(this.core, input);
798
336
  }
799
337
 
800
- async getThread(threadId: string, channelToken?: string) {
801
- return this.requestResource<ThreadAttributes>(`${API_PREFIX}/threads/${threadId}`, {
802
- token: this.resolveInboxReadToken(channelToken),
803
- });
338
+ getThread(threadId: string, channelToken?: string) {
339
+ return inbox.getThread(this.core, threadId, channelToken);
804
340
  }
805
341
 
806
- async listMessagesForThread(input: {
342
+ listMessagesForThread(input: {
807
343
  threadId: string;
808
344
  limit?: number;
809
345
  cursor?: string;
@@ -814,91 +350,45 @@ export class ClankmatesClient {
814
350
  hasAttachment?: boolean;
815
351
  channelToken?: string;
816
352
  }) {
817
- return this.requestCollection<MessageAttributes>(
818
- withQuery(`${API_PREFIX}/threads/${input.threadId}/messages`, {
819
- order: input.order,
820
- since: input.since,
821
- before: input.before,
822
- query: input.query,
823
- has_attachment: input.hasAttachment,
824
- "page[limit]": input.limit,
825
- "page[after]": input.cursor,
826
- }),
827
- {
828
- token: this.resolveInboxReadToken(input.channelToken),
829
- },
830
- );
353
+ return inbox.listMessagesForThread(this.core, input);
831
354
  }
832
355
 
833
- async checkThreadMessageChanges(input: {
356
+ checkThreadMessageChanges(input: {
834
357
  threadId: string;
835
358
  since: string;
836
359
  query?: string;
837
360
  hasAttachment?: boolean;
838
361
  channelToken?: string;
839
362
  }) {
840
- return this.requestAction<ChangeCheckResponse>(
841
- withQuery(`${API_PREFIX}/threads/${input.threadId}/messages/changes`, {
842
- since: input.since,
843
- query: input.query,
844
- has_attachment: input.hasAttachment,
845
- }),
846
- {
847
- token: this.resolveInboxReadToken(input.channelToken),
848
- },
849
- );
363
+ return inbox.checkThreadMessageChanges(this.core, input);
850
364
  }
851
365
 
852
- async listMessageAttachments(input: {
366
+ listMessageAttachments(input: {
853
367
  messageId: string;
854
368
  limit?: number;
855
369
  cursor?: string;
856
370
  channelToken?: string;
857
371
  }) {
858
- return this.requestCollection<MessageAttachmentAttributes>(
859
- withQuery(`${API_PREFIX}/messages/${input.messageId}/attachments`, {
860
- "page[limit]": input.limit,
861
- "page[after]": input.cursor,
862
- }),
863
- {
864
- token: this.resolveInboxReadToken(input.channelToken),
865
- },
866
- );
372
+ return inbox.listMessageAttachments(this.core, input);
867
373
  }
868
374
 
869
- async listEmailScreeningIntakes(input: {
375
+ listEmailScreeningIntakes(input: {
870
376
  limit?: number;
871
377
  cursor?: string;
872
378
  channelToken?: string;
873
379
  } = {}) {
874
- return this.requestCollection<ExternalEmailIntakeAttributes>(
875
- withQuery(`${API_PREFIX}/email-intakes/screening`, {
876
- "page[limit]": input.limit,
877
- "page[after]": input.cursor,
878
- }),
879
- {
880
- token: this.resolveInboxReadToken(input.channelToken),
881
- },
882
- );
380
+ return inbox.listEmailScreeningIntakes(this.core, input);
883
381
  }
884
382
 
885
- async listEmailProcessingIntakes(input: {
383
+ listEmailProcessingIntakes(input: {
886
384
  limit?: number;
887
385
  cursor?: string;
888
386
  channelToken?: string;
889
387
  } = {}) {
890
- return this.requestCollection<ExternalEmailIntakeAttributes>(
891
- withQuery(`${API_PREFIX}/email-intakes/processing`, {
892
- "page[limit]": input.limit,
893
- "page[after]": input.cursor,
894
- }),
895
- {
896
- token: this.resolveInboxReadToken(input.channelToken),
897
- },
898
- );
388
+ return inbox.listEmailProcessingIntakes(this.core, input);
899
389
  }
900
390
 
901
- async createThread(input: {
391
+ createThread(input: {
902
392
  recipient: InboxRecipient;
903
393
  body?: string;
904
394
  payload?: Record<string, unknown>;
@@ -906,27 +396,10 @@ export class ClankmatesClient {
906
396
  contextPostId?: string;
907
397
  channelToken?: string;
908
398
  }) {
909
- return this.requestResource<ThreadAttributes>(`${API_PREFIX}/threads`, {
910
- method: "POST",
911
- token: this.resolveInboxWriteToken(input.channelToken),
912
- body: {
913
- data: {
914
- type: "thread",
915
- attributes: {
916
- recipient: input.recipient,
917
- ...(input.body !== undefined ? { body: input.body } : {}),
918
- ...(input.payload !== undefined ? { payload: input.payload } : {}),
919
- ...(input.from ? { from: input.from } : {}),
920
- ...(input.contextPostId
921
- ? { context_post_id: input.contextPostId }
922
- : {}),
923
- },
924
- },
925
- },
926
- });
927
- }
928
-
929
- async appendThreadMessage(input: {
399
+ return inbox.createThread(this.core, input);
400
+ }
401
+
402
+ appendThreadMessage(input: {
930
403
  threadId: string;
931
404
  body?: string;
932
405
  payload?: Record<string, unknown>;
@@ -934,264 +407,55 @@ export class ClankmatesClient {
934
407
  contextPostId?: string;
935
408
  channelToken?: string;
936
409
  }) {
937
- return this.requestResource<ThreadAttributes>(
938
- `${API_PREFIX}/threads/${input.threadId}/messages`,
939
- {
940
- method: "POST",
941
- token: this.resolveInboxWriteToken(input.channelToken),
942
- body: {
943
- data: {
944
- type: "thread",
945
- attributes: {
946
- ...(input.body !== undefined ? { body: input.body } : {}),
947
- ...(input.payload !== undefined ? { payload: input.payload } : {}),
948
- ...(input.from ? { from: input.from } : {}),
949
- ...(input.contextPostId
950
- ? { context_post_id: input.contextPostId }
951
- : {}),
952
- },
953
- },
954
- },
955
- },
956
- );
410
+ return inbox.appendThreadMessage(this.core, input);
957
411
  }
958
412
 
959
- async markThreadSeen(input: { threadId: string; channelToken?: string }) {
960
- return this.updateThreadLifecycle(`${API_PREFIX}/threads/${input.threadId}/seen`, input);
413
+ markThreadSeen(input: { threadId: string; channelToken?: string }) {
414
+ return inbox.markThreadSeen(this.core, input);
961
415
  }
962
416
 
963
- async archiveThread(input: { threadId: string; channelToken?: string }) {
964
- return this.updateThreadLifecycle(`${API_PREFIX}/threads/${input.threadId}/archive`, input);
417
+ archiveThread(input: { threadId: string; channelToken?: string }) {
418
+ return inbox.archiveThread(this.core, input);
965
419
  }
966
420
 
967
- async resolveThread(input: { threadId: string; channelToken?: string }) {
968
- return this.updateThreadLifecycle(`${API_PREFIX}/threads/${input.threadId}/resolve`, input);
421
+ resolveThread(input: { threadId: string; channelToken?: string }) {
422
+ return inbox.resolveThread(this.core, input);
969
423
  }
970
424
 
971
- async blockThread(input: { threadId: string; channelToken?: string }) {
972
- return this.updateThreadLifecycle(`${API_PREFIX}/threads/${input.threadId}/block`, input);
425
+ blockThread(input: { threadId: string; channelToken?: string }) {
426
+ return inbox.blockThread(this.core, input);
973
427
  }
974
428
 
975
- async approveEmailIntake(input: { intakeId: string; channelToken?: string }) {
976
- return this.updateEmailIntake(
977
- `${API_PREFIX}/email-intakes/${input.intakeId}/approve`,
978
- input,
979
- );
429
+ approveEmailIntake(input: { intakeId: string; channelToken?: string }) {
430
+ return inbox.approveEmailIntake(this.core, input);
980
431
  }
981
432
 
982
- async approveEmailIntakeOnce(input: { intakeId: string; channelToken?: string }) {
983
- return this.updateEmailIntake(
984
- `${API_PREFIX}/email-intakes/${input.intakeId}/approve-once`,
985
- input,
986
- );
433
+ approveEmailIntakeOnce(input: { intakeId: string; channelToken?: string }) {
434
+ return inbox.approveEmailIntakeOnce(this.core, input);
987
435
  }
988
436
 
989
- async ignoreEmailIntake(input: { intakeId: string; channelToken?: string }) {
990
- return this.updateEmailIntake(
991
- `${API_PREFIX}/email-intakes/${input.intakeId}/ignore`,
992
- input,
993
- );
437
+ ignoreEmailIntake(input: { intakeId: string; channelToken?: string }) {
438
+ return inbox.ignoreEmailIntake(this.core, input);
994
439
  }
995
440
 
996
- async fetchOpenApi(): Promise<unknown> {
997
- return (await requestJson(this.profile.baseUrl, `${API_PREFIX}/open_api`))
998
- .data;
441
+ fetchOpenApi(): Promise<unknown> {
442
+ return rawApi.fetchOpenApi(this.core);
999
443
  }
1000
444
 
1001
- async apiRequest(input: {
445
+ apiRequest(input: {
1002
446
  method: string;
1003
447
  path: string;
1004
448
  body?: string;
1005
449
  channelToken?: string;
1006
450
  }): Promise<unknown> {
1007
- return (
1008
- await requestJson(this.profile.baseUrl, input.path, {
1009
- method: input.method,
1010
- token: input.channelToken ?? resolveMasterToken(this.profile).token,
1011
- rawBody: input.body,
1012
- accept: inferMediaType(input.path),
1013
- contentType:
1014
- input.body === undefined ? undefined : inferMediaType(input.path),
1015
- })
1016
- ).data;
451
+ return rawApi.apiRequest(this.core, input);
1017
452
  }
1018
453
 
1019
- async resolveChannelId(channel: string): Promise<string> {
1020
- if (looksLikeUuid(channel)) {
1021
- return channel;
1022
- }
1023
-
1024
- return (await this.resolveOwnedChannel(channel)).id;
1025
- }
1026
-
1027
- async resolveOwnedChannel(channel: string) {
1028
- if (looksLikeUuid(channel)) {
1029
- return this.getChannel(channel);
1030
- }
1031
-
1032
- if (!resolveOwnerReadToken(this.profile).token) {
1033
- throw new CliError(
1034
- `Resolving channel name "${channel}" requires an owner read token. Use the channel UUID or configure a read-only or master token.`,
1035
- );
1036
- }
1037
-
1038
- return this.getChannelByName(channel);
454
+ resolveChannelId(channel: string): Promise<string> {
455
+ return channels.resolveChannelId(this.core, channel);
1039
456
  }
1040
457
 
1041
- private async requestResource<TAttributes extends object>(
1042
- path: string,
1043
- options: RequestOptions,
1044
- ) {
1045
- return expectResource<TAttributes>(
1046
- (await this.requestJsonApi(path, options)).data,
1047
- );
458
+ resolveOwnedChannel(channel: string) {
459
+ return channels.resolveOwnedChannel(this.core, channel);
1048
460
  }
1049
-
1050
- private async requestCollection<TAttributes extends object>(
1051
- path: string,
1052
- options: RequestOptions,
1053
- ) {
1054
- return expectCollection<TAttributes>(
1055
- (await this.requestJsonApi(path, options)).data,
1056
- );
1057
- }
1058
-
1059
- private async requestAction<T = unknown>(
1060
- path: string,
1061
- options: RequestOptions = {},
1062
- ) {
1063
- return (await this.requestJsonApi<T>(path, options)).data;
1064
- }
1065
-
1066
- private async requestJsonApi<T = unknown>(
1067
- path: string,
1068
- options: RequestOptions = {},
1069
- ) {
1070
- return requestJsonApi<T>(this.profile.baseUrl, path, options);
1071
- }
1072
-
1073
- private resolvePostLifecycleToken(explicitToken?: string): string {
1074
- const resolved = resolveChannelActorOrMasterToken(
1075
- this.profile,
1076
- explicitToken,
1077
- );
1078
-
1079
- if (!resolved.token) {
1080
- throw new CliError(
1081
- "No token available for post edit/delete. Provide --channel-token, set CLANKMATES_CHANNEL_TOKEN, configure a single saved channel token, or configure a master token.",
1082
- );
1083
- }
1084
-
1085
- return resolved.token;
1086
- }
1087
-
1088
- private resolveChannelConfigToken(channelId: string, explicitToken?: string): string {
1089
- const resolved = resolvePublishToken(this.profile, channelId, explicitToken);
1090
-
1091
- if (!resolved.token) {
1092
- throw new CliError(
1093
- "No token available for channel configuration. Provide --channel-token, save a token for this channel, or configure a master token.",
1094
- );
1095
- }
1096
-
1097
- return resolved.token;
1098
- }
1099
-
1100
- private resolveInboxReadToken(explicitToken?: string): string {
1101
- return explicitToken ?? requireOwnerReadToken(this.profile);
1102
- }
1103
-
1104
- private resolveInboxWriteToken(explicitToken?: string): string {
1105
- return explicitToken ?? requireMasterToken(this.profile);
1106
- }
1107
-
1108
- private async updateThreadLifecycle(
1109
- path: string,
1110
- input: { threadId: string; channelToken?: string },
1111
- ) {
1112
- return this.requestResource<ThreadAttributes>(path, {
1113
- method: "PATCH",
1114
- token: this.resolveInboxWriteToken(input.channelToken),
1115
- body: {
1116
- data: {
1117
- type: "thread",
1118
- id: input.threadId,
1119
- attributes: {},
1120
- },
1121
- },
1122
- });
1123
- }
1124
-
1125
- private async updateEmailIntake(
1126
- path: string,
1127
- input: { intakeId: string; channelToken?: string },
1128
- ) {
1129
- return this.requestResource<ExternalEmailIntakeAttributes>(path, {
1130
- method: "PATCH",
1131
- token: this.resolveInboxWriteToken(input.channelToken),
1132
- body: {
1133
- data: {
1134
- type: "external_email_intake",
1135
- id: input.intakeId,
1136
- attributes: {},
1137
- },
1138
- },
1139
- });
1140
- }
1141
- }
1142
-
1143
- const API_PREFIX = "/api/v1";
1144
- const UUID_PATTERN =
1145
- /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1146
-
1147
- function inferMediaType(path: string): string {
1148
- return isPlainJsonPath(path)
1149
- ? "application/json"
1150
- : "application/vnd.api+json";
1151
- }
1152
-
1153
- function isUnauthorizedCliError(error: unknown): boolean {
1154
- return (
1155
- error instanceof CliError &&
1156
- error.exitCode === 1 &&
1157
- (error.message.includes("Authentication required") ||
1158
- error.message.includes("code=unauthorized"))
1159
- );
1160
- }
1161
-
1162
- function isPlainJsonPath(path: string): boolean {
1163
- return (
1164
- path === `${API_PREFIX}/open_api` || path.startsWith(`${API_PREFIX}/auth/`)
1165
- );
1166
- }
1167
-
1168
- function looksLikeUuid(value: string): boolean {
1169
- return UUID_PATTERN.test(value);
1170
- }
1171
-
1172
- function withQuery(
1173
- path: string,
1174
- params: Record<string, string | number | boolean | undefined>,
1175
- ): string {
1176
- const search = new URLSearchParams();
1177
-
1178
- for (const [key, value] of Object.entries(params)) {
1179
- if (value !== undefined) {
1180
- search.set(key, String(value));
1181
- }
1182
- }
1183
-
1184
- const query = search.toString();
1185
- return query ? `${path}?${query}` : path;
1186
- }
1187
-
1188
- function withRepeatedQuery(path: string, key: string, values: string[]): string {
1189
- const search = new URLSearchParams();
1190
-
1191
- for (const value of values) {
1192
- search.append(key, value);
1193
- }
1194
-
1195
- const query = search.toString();
1196
- return query ? `${path}?${query}` : path;
1197
461
  }