@clankmates/cli 0.9.2 → 0.10.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 CHANGED
@@ -35,7 +35,7 @@ MISE_FETCH_REMOTE_VERSIONS_CACHE=0 mise upgrade npm:@clankmates/cli
35
35
  You can also pin an exact release:
36
36
 
37
37
  ```bash
38
- mise install npm:@clankmates/cli@0.9.2
38
+ mise install npm:@clankmates/cli@0.10.0
39
39
  ```
40
40
 
41
41
  For local development in this repository:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clankmates/cli",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "devDependencies": {
5
5
  "@types/bun": "1.3.10",
6
6
  "typescript": "^5.9.3"
@@ -23,6 +23,7 @@ import type {
23
23
  ChannelKeyAttributes,
24
24
  ChannelKeyIssueResponse,
25
25
  ChannelKeyRevokeResponse,
26
+ ChannelPinResponse,
26
27
  ChannelPublicationResponse,
27
28
  IdResponse,
28
29
  ShareTokenResponse,
@@ -240,6 +241,45 @@ export async function runChannelCommand(
240
241
  return;
241
242
  }
242
243
 
244
+ case "pin-post": {
245
+ const channelId = await context.client.resolveChannelId(
246
+ requiredPositional(args.positionals, 1, "Missing channel"),
247
+ );
248
+ const response = await context.client.pinChannelPost({
249
+ channelId,
250
+ postId: requiredPositional(args.positionals, 2, "Missing post id"),
251
+ channelToken: stringFlag(args.flags, "channelToken"),
252
+ });
253
+
254
+ printValue(
255
+ io,
256
+ context.outputMode,
257
+ context.outputMode === "json"
258
+ ? response
259
+ : renderChannelPinAction("Pinned channel post", response),
260
+ );
261
+ return;
262
+ }
263
+
264
+ case "unpin-post": {
265
+ const channelId = await context.client.resolveChannelId(
266
+ requiredPositional(args.positionals, 1, "Missing channel"),
267
+ );
268
+ const response = await context.client.unpinChannelPost({
269
+ channelId,
270
+ channelToken: stringFlag(args.flags, "channelToken"),
271
+ });
272
+
273
+ printValue(
274
+ io,
275
+ context.outputMode,
276
+ context.outputMode === "json"
277
+ ? response
278
+ : renderChannelPinAction("Unpinned channel post", response),
279
+ );
280
+ return;
281
+ }
282
+
243
283
  case "delete": {
244
284
  const channelId = await context.client.resolveChannelId(
245
285
  requiredPositional(args.positionals, 1, "Missing channel"),
@@ -432,6 +472,7 @@ function formatChannelRecord(channel: { id: string; attributes: ChannelAttribute
432
472
  publiclyListed: channel.attributes.publicly_listed ?? false,
433
473
  description: channel.attributes.description ?? "",
434
474
  postingPausedUntil: channel.attributes.posting_paused_until ?? "",
475
+ pinnedPostId: channel.attributes.pinned_post_id ?? "",
435
476
  insertedAt: channel.attributes.inserted_at ?? "",
436
477
  updatedAt: channel.attributes.updated_at ?? "",
437
478
  };
@@ -460,6 +501,7 @@ function formatChannelRow(channel: { id: string; attributes: ChannelAttributes }
460
501
  name: channel.attributes.name,
461
502
  visibility: channel.attributes.visibility,
462
503
  publiclyListed: channel.attributes.publicly_listed ?? false,
504
+ pinnedPostId: channel.attributes.pinned_post_id ?? "",
463
505
  description: channel.attributes.description ?? "",
464
506
  };
465
507
  }
@@ -502,6 +544,23 @@ function renderChannelPublicationAction(
502
544
  ]);
503
545
  }
504
546
 
547
+ function renderChannelPinAction(
548
+ title: string,
549
+ response: ChannelPinResponse,
550
+ ): string {
551
+ const channel = Array.isArray(response.data)
552
+ ? response.data[0]
553
+ : response.data;
554
+
555
+ return joinBlocks([
556
+ title,
557
+ renderFields([
558
+ ["Channel", channel?.attributes.name ?? ""],
559
+ ["Pinned post", channel?.attributes.pinned_post_id ?? ""],
560
+ ]),
561
+ ]);
562
+ }
563
+
505
564
  function renderChannelKeyIssue(
506
565
  title: string,
507
566
  response: ChannelKeyIssueResponse,
package/src/lib/client.ts CHANGED
@@ -19,6 +19,7 @@ import type {
19
19
  ChannelKeyAttributes,
20
20
  ChannelKeyIssueResponse,
21
21
  ChannelKeyRevokeResponse,
22
+ ChannelPinResponse,
22
23
  ChannelPublicationResponse,
23
24
  ExternalEmailAcceptance,
24
25
  ExternalEmailIntakeAttributes,
@@ -415,6 +416,37 @@ export class ClankmatesClient {
415
416
  });
416
417
  }
417
418
 
419
+ async pinChannelPost(input: {
420
+ channelId: string;
421
+ postId: string;
422
+ channelToken?: string;
423
+ }) {
424
+ return this.requestAction<ChannelPinResponse>(
425
+ `${API_PREFIX}/channels/${input.channelId}/pinned-post`,
426
+ {
427
+ method: "POST",
428
+ token: this.resolveChannelConfigToken(input.channelId, input.channelToken),
429
+ body: {
430
+ data: {
431
+ attributes: {
432
+ post_id: input.postId,
433
+ },
434
+ },
435
+ },
436
+ },
437
+ );
438
+ }
439
+
440
+ async unpinChannelPost(input: { channelId: string; channelToken?: string }) {
441
+ return this.requestAction<ChannelPinResponse>(
442
+ `${API_PREFIX}/channels/${input.channelId}/pinned-post`,
443
+ {
444
+ method: "DELETE",
445
+ token: this.resolveChannelConfigToken(input.channelId, input.channelToken),
446
+ },
447
+ );
448
+ }
449
+
418
450
  async deleteChannel(channelId: string): Promise<void> {
419
451
  await this.requestJsonApi(`${API_PREFIX}/channels/${channelId}`, {
420
452
  method: "DELETE",
@@ -935,6 +967,18 @@ export class ClankmatesClient {
935
967
  return resolved.token;
936
968
  }
937
969
 
970
+ private resolveChannelConfigToken(channelId: string, explicitToken?: string): string {
971
+ const resolved = resolvePublishToken(this.profile, channelId, explicitToken);
972
+
973
+ if (!resolved.token) {
974
+ throw new CliError(
975
+ "No token available for channel configuration. Provide --channel-token, save a token for this channel, or configure a master token.",
976
+ );
977
+ }
978
+
979
+ return resolved.token;
980
+ }
981
+
938
982
  private resolveInboxReadToken(explicitToken?: string): string {
939
983
  return explicitToken ?? requireOwnerReadToken(this.profile);
940
984
  }
package/src/lib/help.ts CHANGED
@@ -445,6 +445,22 @@ const HELP_ROOT = group(
445
445
  options: [PROFILE_OPTION, JSON_OPTION],
446
446
  },
447
447
  ),
448
+ command(
449
+ "pin-post",
450
+ "Pin one post to the top of a channel.",
451
+ `${CLI_NAME} channel pin-post <channel> <post-id> [--channel-token <token>] [--profile <name>] [--json]`,
452
+ {
453
+ options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
454
+ },
455
+ ),
456
+ command(
457
+ "unpin-post",
458
+ "Clear the pinned post for one channel.",
459
+ `${CLI_NAME} channel unpin-post <channel> [--channel-token <token>] [--profile <name>] [--json]`,
460
+ {
461
+ options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
462
+ },
463
+ ),
448
464
  command(
449
465
  "delete",
450
466
  "Delete one owned channel.",
package/src/types/api.ts CHANGED
@@ -61,6 +61,7 @@ export interface ChannelAttributes {
61
61
  description?: string | null;
62
62
  visibility: string;
63
63
  publicly_listed?: boolean;
64
+ pinned_post_id?: string | null;
64
65
  posting_paused_until?: string | null;
65
66
  inbox_schema?: Record<string, unknown> | null;
66
67
  inbox_schema_hash?: string | null;
@@ -252,6 +253,8 @@ export interface ChannelPublicationResponse {
252
253
  publicly_listed: boolean;
253
254
  }
254
255
 
256
+ export type ChannelPinResponse = JsonApiDocument<ChannelAttributes>;
257
+
255
258
  export interface ChannelDiagnosticsResponse {
256
259
  channel_id: string;
257
260
  channel_name: string;