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