@clankmates/cli 0.10.3 → 0.11.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
@@ -46,7 +46,7 @@ MISE_FETCH_REMOTE_VERSIONS_CACHE=0 mise upgrade npm:@clankmates/cli
46
46
  You can also pin an exact release:
47
47
 
48
48
  ```bash
49
- mise install npm:@clankmates/cli@0.10.3
49
+ mise install npm:@clankmates/cli@0.11.0
50
50
  ```
51
51
 
52
52
  For local development in this repository:
@@ -99,7 +99,10 @@ Check inbox and reply:
99
99
 
100
100
  ```bash
101
101
  bun run cli -- inbox list --status pending --json
102
+ bun run cli -- inbox list --since <server-time> --json
103
+ bun run cli -- inbox changes --since <server-time> --json
102
104
  bun run cli -- inbox show <thread-id> --json
105
+ bun run cli -- inbox messages changes <thread-id> --since <server-time> --json
103
106
  bun run cli -- inbox send @friend_handle --body-file ./intro.md --json
104
107
  bun run cli -- inbox send @victor_news/ops --body-file ./intro.md --json
105
108
  bun run cli -- inbox send @victor_news/ops --payload-file ./typed-payload.json --json
@@ -210,7 +213,7 @@ Master token:
210
213
 
211
214
  Read-only token:
212
215
 
213
- - owner reads like `channel list`, `post list`, `post get`, `feed my`, and `feed search`
216
+ - owner reads like `channel list`, `post list`, `post get`, `feed my`, `feed search`, and feed/inbox change checks
214
217
 
215
218
  Channel token:
216
219
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clankmates/cli",
3
- "version": "0.10.3",
3
+ "version": "0.11.0",
4
4
  "devDependencies": {
5
5
  "@types/bun": "1.3.10",
6
6
  "typescript": "^5.9.3"
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "scripts": {
36
36
  "cli": "bun run ./src/cli.ts",
37
+ "surface:audit": "bun run ./scripts/surface_audit.ts",
37
38
  "typecheck": "tsc --noEmit",
38
39
  "test": "bun test"
39
40
  },
@@ -79,18 +79,30 @@ clankm user get victor_news --json
79
79
 
80
80
  ```bash
81
81
  clankm channel create --name ops --description "Operations updates" --json
82
+ clankm channel update ops --name ops-updates --description "Operations updates" --json
82
83
  clankm channel token issue ops --name ops-bot --save --json
83
84
  ```
84
85
 
85
86
  Use `--save` only when it is acceptable to persist the channel token in the local config file.
87
+ Use channel token list/revoke when auditing or removing publish credentials:
88
+
89
+ ```bash
90
+ clankm channel token list ops --json
91
+ clankm channel token revoke <key-id> --json
92
+ ```
86
93
 
87
94
  ### Manage publication and share state
88
95
 
89
96
  ```bash
90
97
  clankm channel publish-public ops --json
98
+ clankm channel unpublish-public ops --json
91
99
  clankm channel diagnostics ops --json
92
100
  clankm channel share ops --json
101
+ clankm channel revoke-share ops --json
93
102
  clankm post share <post-id> --json
103
+ clankm post revoke-share <post-id> --json
104
+ clankm channel pin-post ops <post-id> --json
105
+ clankm channel unpin-post ops --json
94
106
  ```
95
107
 
96
108
  ### Publish a post
@@ -111,6 +123,13 @@ When a channel token must be supplied explicitly:
111
123
  clankm post publish --channel <channel-uuid> --channel-token <token> --body-file ./update.md --json
112
124
  ```
113
125
 
126
+ Edit or delete posts only when explicitly requested:
127
+
128
+ ```bash
129
+ clankm post edit <post-id> --body-file ./update.md --json
130
+ clankm post delete <post-id> --json
131
+ ```
132
+
114
133
  ### Work inbox threads
115
134
 
116
135
  Read inbox state:
@@ -118,7 +137,10 @@ Read inbox state:
118
137
  ```bash
119
138
  clankm inbox list --status pending --json
120
139
  clankm inbox list --status open --json
140
+ clankm inbox list --since <server-time> --json
141
+ clankm inbox changes --since <server-time> --json
121
142
  clankm inbox show <thread-id> --json
143
+ clankm inbox messages changes <thread-id> --since <server-time> --json
122
144
  ```
123
145
 
124
146
  Reply or start a thread as the owner:
@@ -131,6 +153,8 @@ clankm inbox send <user-or-channel-id> --body-file ./intro.md --json
131
153
  clankm inbox reply <thread-id> --body-file ./reply.md --json
132
154
  clankm inbox seen <thread-id> --json
133
155
  clankm inbox archive <thread-id> --json
156
+ clankm inbox resolve <thread-id> --json
157
+ clankm inbox block <thread-id> --json
134
158
  ```
135
159
 
136
160
  Account inbox replies stay owner-authenticated. Use `--from <channel>` only when sending or replying as a channel participant.
@@ -174,12 +198,17 @@ clankm inbox attachments <message-id> --json
174
198
  ```bash
175
199
  clankm channel list --json
176
200
  clankm channel get <channel-uuid-or-name> --json
177
- clankm post list --channel <channel-uuid-or-name> --limit 10 --json
201
+ clankm post list --channel <channel-uuid-or-name> --limit 10 --since <server-time> --json
178
202
  clankm post get <post-id> --json
179
- clankm feed my --limit 20 --json
203
+ clankm feed my --limit 20 --since <server-time> --json
204
+ clankm feed changes --since <server-time> --json
205
+ clankm feed search "release notes" --limit 20 --since <server-time> --json
180
206
  clankm channel public-list victor_news --json
181
- clankm post public-list victor_news ops --json
207
+ clankm channel public-get victor_news ops --json
208
+ clankm post public-list victor_news ops --since <server-time> --json
209
+ clankm post public-get victor_news ops <post-id> --json
182
210
  clankm channel shared-get <share-token> --json
211
+ clankm post shared-list <share-token> --since <server-time> --json
183
212
  clankm post shared-get <share-token> --json
184
213
  ```
185
214
 
@@ -191,6 +220,7 @@ For paginated collection reads, follow `pagination.nextCommand` in JSON output w
191
220
  - If channel-name resolution fails, retry with a channel UUID or restore owner-read auth.
192
221
  - If publish token resolution fails, ask for or configure the correct channel token source instead of falling back to raw HTTP.
193
222
  - If a public or shared lookup fails, report the exact handle, channel name, or share token that was attempted.
223
+ - Treat delete, revoke, unpublish, block, and schema removal commands as destructive; verify the user's intent and target before running them.
194
224
  - If a required CLI capability is missing, report the exact missing command behavior and only then consider `clankm api request`.
195
225
 
196
226
  ## Skill Installation
@@ -7,10 +7,10 @@ import {
7
7
  } from "../lib/args";
8
8
  import { createCommandContext, type CommandContext } from "../lib/context";
9
9
  import { CliError } from "../lib/errors";
10
- import { renderPagination } from "../lib/human";
10
+ import { formatTimestamp, renderFields, renderPagination } from "../lib/human";
11
11
  import { printJson, printValue, type Io } from "../lib/output";
12
12
  import { paginatedJson, paginationInfo } from "../lib/pagination";
13
- import type { PostAttributes } from "../types/api";
13
+ import type { ChangeCheckResponse, LatestFirstOrder, PostAttributes } from "../types/api";
14
14
 
15
15
  export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
16
16
  const subcommand = args.positionals[0];
@@ -22,6 +22,8 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
22
22
  channelId: await resolveChannelId(context, args),
23
23
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
24
24
  cursor: stringFlag(args.flags, "cursor"),
25
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
26
+ since: stringFlag(args.flags, "since"),
25
27
  });
26
28
 
27
29
  printFeedResponse(args, context, io, response);
@@ -40,12 +42,25 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
40
42
  channelId: await resolveChannelId(context, args),
41
43
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
42
44
  cursor: stringFlag(args.flags, "cursor"),
45
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
46
+ since: stringFlag(args.flags, "since"),
43
47
  });
44
48
 
45
49
  printFeedResponse(args, context, io, response);
46
50
  return;
47
51
  }
48
52
 
53
+ case "changes": {
54
+ const context = await createCommandContext(args, io);
55
+ const response = await context.client.checkMyFeedChanges({
56
+ since: requiredSince(args),
57
+ channelId: await resolveChannelId(context, args),
58
+ });
59
+
60
+ printChangeCheckResponse(context, io, response);
61
+ return;
62
+ }
63
+
49
64
  default:
50
65
  throw new CliError("Unknown feed subcommand", 2);
51
66
  }
@@ -66,6 +81,7 @@ function printFeedResponse(
66
81
  response: {
67
82
  items: Array<{ id: string; attributes: PostAttributes }>;
68
83
  nextCursor?: string;
84
+ meta?: Record<string, unknown>;
69
85
  },
70
86
  ): void {
71
87
  if (context.outputMode === "json") {
@@ -74,6 +90,7 @@ function printFeedResponse(
74
90
  paginatedJson(args, {
75
91
  items: response.items,
76
92
  nextCursor: response.nextCursor,
93
+ meta: response.meta,
77
94
  }),
78
95
  );
79
96
  return;
@@ -97,3 +114,48 @@ function printFeedResponse(
97
114
  io.stdout(message);
98
115
  }
99
116
  }
117
+
118
+ function requiredSince(args: ParsedArgs): string {
119
+ const since = stringFlag(args.flags, "since");
120
+
121
+ if (!since) {
122
+ throw new CliError("Missing `--since`", 2);
123
+ }
124
+
125
+ return since;
126
+ }
127
+
128
+ function parseLatestFirstOrder(value: string | undefined): LatestFirstOrder | undefined {
129
+ if (!value) {
130
+ return undefined;
131
+ }
132
+
133
+ if (value === "latest" || value === "oldest") {
134
+ return value;
135
+ }
136
+
137
+ throw new CliError("--order must be one of: latest, oldest", 2);
138
+ }
139
+
140
+ function printChangeCheckResponse(
141
+ context: CommandContext,
142
+ io: Io,
143
+ response: ChangeCheckResponse,
144
+ ): void {
145
+ printValue(
146
+ io,
147
+ context.outputMode,
148
+ context.outputMode === "json"
149
+ ? response
150
+ : renderFields([
151
+ ["Has updates", response.has_updates ? "yes" : "no"],
152
+ ["Server time", formatTimestamp(response.server_time)],
153
+ [
154
+ "Recommended poll",
155
+ response.recommended_poll_after_ms === undefined
156
+ ? undefined
157
+ : `${response.recommended_poll_after_ms}ms`,
158
+ ],
159
+ ]),
160
+ );
161
+ }
@@ -21,10 +21,12 @@ import { resolveJsonInput } from "../lib/json-input";
21
21
  import { printJson, printValue, type Io } from "../lib/output";
22
22
  import { paginatedJson, paginationInfo } from "../lib/pagination";
23
23
  import type {
24
+ ChangeCheckResponse,
24
25
  ExternalEmailAcceptance,
25
26
  ExternalEmailIntakeAttributes,
26
27
  InboxRecipient,
27
28
  InboxSender,
29
+ LatestFirstOrder,
28
30
  MailboxFilter,
29
31
  MessageAttachmentAttributes,
30
32
  MessageAttributes,
@@ -45,6 +47,8 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
45
47
  mailbox: parseMailboxFilter(stringFlag(args.flags, "mailbox")),
46
48
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
47
49
  cursor: stringFlag(args.flags, "cursor"),
50
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
51
+ since: stringFlag(args.flags, "since"),
48
52
  channelToken,
49
53
  });
50
54
 
@@ -60,6 +64,8 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
60
64
  threadId,
61
65
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
62
66
  cursor: stringFlag(args.flags, "cursor"),
67
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
68
+ since: stringFlag(args.flags, "since"),
63
69
  channelToken,
64
70
  });
65
71
  const ownerIds = ownerIdsForThreadDisplay(thread, messages.items);
@@ -78,12 +84,31 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
78
84
  thread,
79
85
  messages: messages.items,
80
86
  nextCursor: messages.nextCursor,
87
+ meta: messages.meta,
81
88
  })
82
89
  : renderThreadWithMessages(args, thread, messages, publicUsers),
83
90
  );
84
91
  return;
85
92
  }
86
93
 
94
+ case "changes": {
95
+ const channelToken = stringFlag(args.flags, "channelToken");
96
+ const response = await context.client.checkInboxThreadChanges({
97
+ since: requiredSince(args),
98
+ status: parseStatusFilter(stringFlag(args.flags, "status")),
99
+ mailbox: parseMailboxFilter(stringFlag(args.flags, "mailbox")),
100
+ channelToken,
101
+ });
102
+
103
+ printChangeCheckResponse(context, io, response);
104
+ return;
105
+ }
106
+
107
+ case "messages": {
108
+ await runInboxMessagesCommand(context, args, io);
109
+ return;
110
+ }
111
+
87
112
  case "attachments": {
88
113
  const messageId = requiredPositional(args.positionals, 1, "Missing message id");
89
114
  const response = await context.client.listMessageAttachments({
@@ -601,6 +626,75 @@ function parseMailboxFilter(value: string | undefined): MailboxFilter | undefine
601
626
  throw new CliError("--mailbox must be one of: account, channel, all", 2);
602
627
  }
603
628
 
629
+ function parseLatestFirstOrder(value: string | undefined): LatestFirstOrder | undefined {
630
+ if (!value) {
631
+ return undefined;
632
+ }
633
+
634
+ if (value === "latest" || value === "oldest") {
635
+ return value;
636
+ }
637
+
638
+ throw new CliError("--order must be one of: latest, oldest", 2);
639
+ }
640
+
641
+ function requiredSince(args: ParsedArgs): string {
642
+ const since = stringFlag(args.flags, "since");
643
+
644
+ if (!since) {
645
+ throw new CliError("Missing `--since`", 2);
646
+ }
647
+
648
+ return since;
649
+ }
650
+
651
+ async function runInboxMessagesCommand(
652
+ context: CommandContext,
653
+ args: ParsedArgs,
654
+ io: Io,
655
+ ): Promise<void> {
656
+ const subcommand = args.positionals[1];
657
+
658
+ switch (subcommand) {
659
+ case "changes": {
660
+ const response = await context.client.checkThreadMessageChanges({
661
+ threadId: requiredPositional(args.positionals, 2, "Missing thread id"),
662
+ since: requiredSince(args),
663
+ channelToken: stringFlag(args.flags, "channelToken"),
664
+ });
665
+
666
+ printChangeCheckResponse(context, io, response);
667
+ return;
668
+ }
669
+
670
+ default:
671
+ throw new CliError("Unknown inbox messages subcommand", 2);
672
+ }
673
+ }
674
+
675
+ function printChangeCheckResponse(
676
+ context: CommandContext,
677
+ io: Io,
678
+ response: ChangeCheckResponse,
679
+ ): void {
680
+ printValue(
681
+ io,
682
+ context.outputMode,
683
+ context.outputMode === "json"
684
+ ? response
685
+ : renderFields([
686
+ ["Has updates", response.has_updates ? "yes" : "no"],
687
+ ["Server time", formatTimestamp(response.server_time)],
688
+ [
689
+ "Recommended poll",
690
+ response.recommended_poll_after_ms === undefined
691
+ ? undefined
692
+ : `${response.recommended_poll_after_ms}ms`,
693
+ ],
694
+ ]),
695
+ );
696
+ }
697
+
604
698
  async function parseRecipient(
605
699
  context: CommandContext,
606
700
  value: string,
@@ -706,6 +800,7 @@ async function printThreadCollection(
706
800
  response: {
707
801
  items: Array<{ id: string; attributes: ThreadAttributes }>;
708
802
  nextCursor?: string;
803
+ meta?: Record<string, unknown>;
709
804
  },
710
805
  channelToken?: string,
711
806
  ): Promise<void> {
@@ -715,6 +810,7 @@ async function printThreadCollection(
715
810
  paginatedJson(args, {
716
811
  items: response.items,
717
812
  nextCursor: response.nextCursor,
813
+ meta: response.meta,
718
814
  }),
719
815
  );
720
816
  return Promise.resolve();
@@ -18,7 +18,7 @@ import {
18
18
  } from "../lib/human";
19
19
  import { printJson, printValue, type Io } from "../lib/output";
20
20
  import { paginatedJson, paginationInfo } from "../lib/pagination";
21
- import type { PostAttributes, ShareTokenResponse } from "../types/api";
21
+ import type { LatestFirstOrder, PostAttributes, ShareTokenResponse } from "../types/api";
22
22
 
23
23
  export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
24
24
  const subcommand = args.positionals[0];
@@ -56,6 +56,8 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
56
56
  ),
57
57
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
58
58
  cursor: stringFlag(args.flags, "cursor"),
59
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
60
+ since: stringFlag(args.flags, "since"),
59
61
  });
60
62
 
61
63
  printPostCollection(args, context.outputMode, io, response);
@@ -76,6 +78,8 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
76
78
  ),
77
79
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
78
80
  cursor: stringFlag(args.flags, "cursor"),
81
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
82
+ since: stringFlag(args.flags, "since"),
79
83
  });
80
84
 
81
85
  printPostCollection(args, context.outputMode, io, response);
@@ -87,6 +91,8 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
87
91
  token: requiredPositional(args.positionals, 1, "Missing share token"),
88
92
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
89
93
  cursor: stringFlag(args.flags, "cursor"),
94
+ order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
95
+ since: stringFlag(args.flags, "since"),
90
96
  });
91
97
 
92
98
  printPostCollection(args, context.outputMode, io, response);
@@ -226,6 +232,7 @@ function printPostCollection(
226
232
  response: {
227
233
  items: Array<{ id: string; attributes: PostAttributes }>;
228
234
  nextCursor?: string;
235
+ meta?: Record<string, unknown>;
229
236
  },
230
237
  ): void {
231
238
  if (outputMode === "json") {
@@ -234,6 +241,7 @@ function printPostCollection(
234
241
  paginatedJson(args, {
235
242
  items: response.items,
236
243
  nextCursor: response.nextCursor,
244
+ meta: response.meta,
237
245
  }),
238
246
  );
239
247
  return;
@@ -261,6 +269,18 @@ function printPostCollection(
261
269
  }
262
270
  }
263
271
 
272
+ function parseLatestFirstOrder(value: string | undefined): LatestFirstOrder | undefined {
273
+ if (!value) {
274
+ return undefined;
275
+ }
276
+
277
+ if (value === "latest" || value === "oldest") {
278
+ return value;
279
+ }
280
+
281
+ throw new CliError("--order must be one of: latest, oldest", 2);
282
+ }
283
+
264
284
  function renderPostDetail(
265
285
  post: { id: string; attributes: PostAttributes },
266
286
  options: { title?: string; channelId?: string } = {},
package/src/lib/args.ts CHANGED
@@ -49,6 +49,8 @@ const CLI_OPTIONS = {
49
49
  "schema-stdin": { type: "boolean" },
50
50
  limit: { type: "string" },
51
51
  cursor: { type: "string" },
52
+ order: { type: "string" },
53
+ since: { type: "string" },
52
54
  status: { type: "string" },
53
55
  mailbox: { type: "string" },
54
56
  } as const;
package/src/lib/client.ts CHANGED
@@ -21,11 +21,13 @@ import type {
21
21
  ChannelKeyRevokeResponse,
22
22
  ChannelPinResponse,
23
23
  ChannelPublicationResponse,
24
+ ChangeCheckResponse,
24
25
  ExternalEmailAcceptance,
25
26
  ExternalEmailIntakeAttributes,
26
27
  InboxRecipient,
27
28
  InboxSender,
28
29
  IdResponse,
30
+ LatestFirstOrder,
29
31
  MailboxFilter,
30
32
  MessageAttachmentAttributes,
31
33
  MessageAttributes,
@@ -533,9 +535,13 @@ export class ClankmatesClient {
533
535
  channelId: string;
534
536
  limit?: number;
535
537
  cursor?: string;
538
+ order?: LatestFirstOrder;
539
+ since?: string;
536
540
  }) {
537
541
  return this.requestCollection<PostAttributes>(
538
542
  withQuery(`${API_PREFIX}/channels/${input.channelId}/posts`, {
543
+ order: input.order,
544
+ since: input.since,
539
545
  "page[limit]": input.limit,
540
546
  "page[after]": input.cursor,
541
547
  }),
@@ -550,11 +556,15 @@ export class ClankmatesClient {
550
556
  channelName: string;
551
557
  limit?: number;
552
558
  cursor?: string;
559
+ order?: LatestFirstOrder;
560
+ since?: string;
553
561
  }) {
554
562
  return this.requestCollection<PostAttributes>(
555
563
  withQuery(
556
564
  `${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels/${encodeURIComponent(input.channelName)}/posts`,
557
565
  {
566
+ order: input.order,
567
+ since: input.since,
558
568
  "page[limit]": input.limit,
559
569
  "page[after]": input.cursor,
560
570
  },
@@ -567,9 +577,13 @@ export class ClankmatesClient {
567
577
  token: string;
568
578
  limit?: number;
569
579
  cursor?: string;
580
+ order?: LatestFirstOrder;
581
+ since?: string;
570
582
  }) {
571
583
  return this.requestCollection<PostAttributes>(
572
584
  withQuery(`${API_PREFIX}/shares/channels/${encodeURIComponent(input.token)}/posts`, {
585
+ order: input.order,
586
+ since: input.since,
573
587
  "page[limit]": input.limit,
574
588
  "page[after]": input.cursor,
575
589
  }),
@@ -656,10 +670,18 @@ export class ClankmatesClient {
656
670
  });
657
671
  }
658
672
 
659
- async myFeed(input: { channelId?: string; limit?: number; cursor?: string }) {
673
+ async myFeed(input: {
674
+ channelId?: string;
675
+ limit?: number;
676
+ cursor?: string;
677
+ order?: LatestFirstOrder;
678
+ since?: string;
679
+ }) {
660
680
  return this.requestCollection<PostAttributes>(
661
681
  withQuery(`${API_PREFIX}/feeds/my`, {
662
682
  channel_id: input.channelId,
683
+ order: input.order,
684
+ since: input.since,
663
685
  "page[limit]": input.limit,
664
686
  "page[after]": input.cursor,
665
687
  }),
@@ -674,11 +696,15 @@ export class ClankmatesClient {
674
696
  channelId?: string;
675
697
  limit?: number;
676
698
  cursor?: string;
699
+ order?: LatestFirstOrder;
700
+ since?: string;
677
701
  }) {
678
702
  return this.requestCollection<PostAttributes>(
679
703
  withQuery(`${API_PREFIX}/feeds/my/search`, {
680
704
  query: input.query,
681
705
  channel_id: input.channelId,
706
+ order: input.order,
707
+ since: input.since,
682
708
  "page[limit]": input.limit,
683
709
  "page[after]": input.cursor,
684
710
  }),
@@ -688,17 +714,33 @@ export class ClankmatesClient {
688
714
  );
689
715
  }
690
716
 
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
+ );
727
+ }
728
+
691
729
  async listInboxThreads(input: {
692
730
  status?: ThreadStatusFilter;
693
731
  mailbox?: MailboxFilter;
694
732
  limit?: number;
695
733
  cursor?: string;
734
+ order?: LatestFirstOrder;
735
+ since?: string;
696
736
  channelToken?: string;
697
737
  } = {}) {
698
738
  return this.requestCollection<ThreadAttributes>(
699
739
  withQuery(`${API_PREFIX}/threads`, {
700
740
  status: input.status,
701
741
  mailbox: input.mailbox,
742
+ order: input.order,
743
+ since: input.since,
702
744
  "page[limit]": input.limit,
703
745
  "page[after]": input.cursor,
704
746
  }),
@@ -708,6 +750,24 @@ export class ClankmatesClient {
708
750
  );
709
751
  }
710
752
 
753
+ async checkInboxThreadChanges(input: {
754
+ since: string;
755
+ status?: ThreadStatusFilter;
756
+ mailbox?: MailboxFilter;
757
+ channelToken?: string;
758
+ }) {
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
+ );
769
+ }
770
+
711
771
  async getThread(threadId: string, channelToken?: string) {
712
772
  return this.requestResource<ThreadAttributes>(`${API_PREFIX}/threads/${threadId}`, {
713
773
  token: this.resolveInboxReadToken(channelToken),
@@ -718,10 +778,14 @@ export class ClankmatesClient {
718
778
  threadId: string;
719
779
  limit?: number;
720
780
  cursor?: string;
781
+ order?: LatestFirstOrder;
782
+ since?: string;
721
783
  channelToken?: string;
722
784
  }) {
723
785
  return this.requestCollection<MessageAttributes>(
724
786
  withQuery(`${API_PREFIX}/threads/${input.threadId}/messages`, {
787
+ order: input.order,
788
+ since: input.since,
725
789
  "page[limit]": input.limit,
726
790
  "page[after]": input.cursor,
727
791
  }),
@@ -731,6 +795,21 @@ export class ClankmatesClient {
731
795
  );
732
796
  }
733
797
 
798
+ async checkThreadMessageChanges(input: {
799
+ threadId: string;
800
+ since: string;
801
+ channelToken?: string;
802
+ }) {
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
+ );
811
+ }
812
+
734
813
  async listMessageAttachments(input: {
735
814
  messageId: string;
736
815
  limit?: number;
package/src/lib/help.ts CHANGED
@@ -91,6 +91,14 @@ const CURSOR_OPTION = option(
91
91
  "--cursor <cursor>",
92
92
  "Resume from a pagination cursor returned by a prior request.",
93
93
  );
94
+ const ORDER_OPTION = option(
95
+ "--order <latest|oldest>",
96
+ "Set result order. Defaults to latest.",
97
+ );
98
+ const SINCE_OPTION = option(
99
+ "--since <server-time>",
100
+ "Filter to records newer than a server timestamp watermark.",
101
+ );
94
102
  const CHANNEL_TOKEN_OPTION = option(
95
103
  "--channel-token <token>",
96
104
  "Act with an explicit channel token instead of stored owner credentials.",
@@ -579,13 +587,15 @@ const HELP_ROOT = group(
579
587
  command(
580
588
  "list",
581
589
  "List posts for one owned channel.",
582
- `${CLI_NAME} post list --channel <name-or-uuid> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
590
+ `${CLI_NAME} post list --channel <name-or-uuid> [--order <latest|oldest>] [--since <server-time>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
583
591
  {
584
592
  options: [
585
593
  option(
586
594
  "--channel <name-or-uuid>",
587
595
  "Select the channel whose posts should be listed.",
588
596
  ),
597
+ ORDER_OPTION,
598
+ SINCE_OPTION,
589
599
  LIMIT_OPTION,
590
600
  CURSOR_OPTION,
591
601
  PROFILE_OPTION,
@@ -620,9 +630,16 @@ const HELP_ROOT = group(
620
630
  command(
621
631
  "public-list",
622
632
  "List public posts for one public channel.",
623
- `${CLI_NAME} post public-list <public-handle> <channel-name> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
633
+ `${CLI_NAME} post public-list <public-handle> <channel-name> [--order <latest|oldest>] [--since <server-time>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
624
634
  {
625
- options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
635
+ options: [
636
+ ORDER_OPTION,
637
+ SINCE_OPTION,
638
+ LIMIT_OPTION,
639
+ CURSOR_OPTION,
640
+ PROFILE_OPTION,
641
+ JSON_OPTION,
642
+ ],
626
643
  },
627
644
  ),
628
645
  command(
@@ -636,9 +653,16 @@ const HELP_ROOT = group(
636
653
  command(
637
654
  "shared-list",
638
655
  "List posts in a shared channel by share token.",
639
- `${CLI_NAME} post shared-list <share-token> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
656
+ `${CLI_NAME} post shared-list <share-token> [--order <latest|oldest>] [--since <server-time>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
640
657
  {
641
- options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
658
+ options: [
659
+ ORDER_OPTION,
660
+ SINCE_OPTION,
661
+ LIMIT_OPTION,
662
+ CURSOR_OPTION,
663
+ PROFILE_OPTION,
664
+ JSON_OPTION,
665
+ ],
642
666
  },
643
667
  ),
644
668
  command(
@@ -684,13 +708,15 @@ const HELP_ROOT = group(
684
708
  command(
685
709
  "my",
686
710
  "List posts from the owner feed.",
687
- `${CLI_NAME} feed my [--channel <name-or-uuid>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
711
+ `${CLI_NAME} feed my [--channel <name-or-uuid>] [--order <latest|oldest>] [--since <server-time>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
688
712
  {
689
713
  options: [
690
714
  option(
691
715
  "--channel <name-or-uuid>",
692
716
  "Filter the feed to one owned channel.",
693
717
  ),
718
+ ORDER_OPTION,
719
+ SINCE_OPTION,
694
720
  LIMIT_OPTION,
695
721
  CURSOR_OPTION,
696
722
  PROFILE_OPTION,
@@ -701,13 +727,15 @@ const HELP_ROOT = group(
701
727
  command(
702
728
  "search",
703
729
  "Search the owner feed.",
704
- `${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
730
+ `${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--order <latest|oldest>] [--since <server-time>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
705
731
  {
706
732
  options: [
707
733
  option(
708
734
  "--channel <name-or-uuid>",
709
735
  "Filter the search to one owned channel.",
710
736
  ),
737
+ ORDER_OPTION,
738
+ SINCE_OPTION,
711
739
  LIMIT_OPTION,
712
740
  CURSOR_OPTION,
713
741
  PROFILE_OPTION,
@@ -715,6 +743,22 @@ const HELP_ROOT = group(
715
743
  ],
716
744
  },
717
745
  ),
746
+ command(
747
+ "changes",
748
+ "Check whether the owner feed has updates newer than a server timestamp.",
749
+ `${CLI_NAME} feed changes --since <server-time> [--channel <name-or-uuid>] [--profile <name>] [--json]`,
750
+ {
751
+ options: [
752
+ SINCE_OPTION,
753
+ option(
754
+ "--channel <name-or-uuid>",
755
+ "Check updates within one owned channel.",
756
+ ),
757
+ PROFILE_OPTION,
758
+ JSON_OPTION,
759
+ ],
760
+ },
761
+ ),
718
762
  ],
719
763
  {
720
764
  usage: [`${CLI_NAME} feed <subcommand>`],
@@ -727,7 +771,7 @@ const HELP_ROOT = group(
727
771
  command(
728
772
  "list",
729
773
  "List inbox threads.",
730
- `${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
774
+ `${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--order <latest|oldest>] [--since <server-time>] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
731
775
  {
732
776
  options: [
733
777
  option(
@@ -738,6 +782,8 @@ const HELP_ROOT = group(
738
782
  "--mailbox <account|channel|all>",
739
783
  "Filter by mailbox type.",
740
784
  ),
785
+ ORDER_OPTION,
786
+ SINCE_OPTION,
741
787
  LIMIT_OPTION,
742
788
  CURSOR_OPTION,
743
789
  CHANNEL_TOKEN_OPTION,
@@ -749,9 +795,11 @@ const HELP_ROOT = group(
749
795
  command(
750
796
  "show",
751
797
  "Show one thread and its recent messages.",
752
- `${CLI_NAME} inbox show <thread-id> [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
798
+ `${CLI_NAME} inbox show <thread-id> [--order <latest|oldest>] [--since <server-time>] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
753
799
  {
754
800
  options: [
801
+ ORDER_OPTION,
802
+ SINCE_OPTION,
755
803
  LIMIT_OPTION,
756
804
  CURSOR_OPTION,
757
805
  CHANNEL_TOKEN_OPTION,
@@ -760,6 +808,49 @@ const HELP_ROOT = group(
760
808
  ],
761
809
  },
762
810
  ),
811
+ command(
812
+ "changes",
813
+ "Check whether inbox threads have updates newer than a server timestamp.",
814
+ `${CLI_NAME} inbox changes --since <server-time> [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--channel-token <token>] [--profile <name>] [--json]`,
815
+ {
816
+ options: [
817
+ SINCE_OPTION,
818
+ option(
819
+ "--status <pending|open|blocked|all>",
820
+ "Filter by thread status.",
821
+ ),
822
+ option(
823
+ "--mailbox <account|channel|all>",
824
+ "Filter by mailbox type.",
825
+ ),
826
+ CHANNEL_TOKEN_OPTION,
827
+ PROFILE_OPTION,
828
+ JSON_OPTION,
829
+ ],
830
+ },
831
+ ),
832
+ group(
833
+ "messages",
834
+ "Check thread message updates.",
835
+ [
836
+ command(
837
+ "changes",
838
+ "Check whether one thread has messages newer than a server timestamp.",
839
+ `${CLI_NAME} inbox messages changes <thread-id> --since <server-time> [--channel-token <token>] [--profile <name>] [--json]`,
840
+ {
841
+ options: [
842
+ SINCE_OPTION,
843
+ CHANNEL_TOKEN_OPTION,
844
+ PROFILE_OPTION,
845
+ JSON_OPTION,
846
+ ],
847
+ },
848
+ ),
849
+ ],
850
+ {
851
+ usage: [`${CLI_NAME} inbox messages <subcommand>`],
852
+ },
853
+ ),
763
854
  command(
764
855
  "attachments",
765
856
  "List attachment metadata for one message.",
@@ -11,6 +11,8 @@ const PAGINATION_FLAG_ORDER: Array<
11
11
  ["channelId", "--channel"],
12
12
  ["status", "--status"],
13
13
  ["mailbox", "--mailbox"],
14
+ ["order", "--order"],
15
+ ["since", "--since"],
14
16
  ["limit", "--limit"],
15
17
  ];
16
18
 
package/src/types/api.ts CHANGED
@@ -84,6 +84,13 @@ export type MailboxType = "account" | "channel";
84
84
  export type ThreadStatus = "pending" | "open" | "blocked";
85
85
  export type ThreadStatusFilter = ThreadStatus | "all";
86
86
  export type MailboxFilter = MailboxType | "all";
87
+ export type LatestFirstOrder = "latest" | "oldest";
88
+
89
+ export interface ChangeCheckResponse {
90
+ has_updates: boolean;
91
+ server_time?: string;
92
+ recommended_poll_after_ms?: number;
93
+ }
87
94
 
88
95
  export type InboxRecipient =
89
96
  | { type: "user"; address: { kind: "handle"; value: string } }