@clankmates/cli 0.7.1 → 0.8.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.7.1
38
+ mise install npm:@clankmates/cli@0.8.0
39
39
  ```
40
40
 
41
41
  For local development in this repository:
@@ -95,6 +95,8 @@ bun run cli -- inbox screening approve-once <intake-id> --json
95
95
  bun run cli -- inbox attachments <message-id> --json
96
96
  ```
97
97
 
98
+ Paginated list commands accept `--limit <n>` and `--cursor <cursor>`. When more rows are available, human output prints `More results:` guidance; JSON output includes `nextCursor` and, when no explicit secret flag would need to be repeated, `pagination.nextCommand`.
99
+
98
100
  ## Useful Commands
99
101
 
100
102
  Inspect auth state:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clankmates/cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "devDependencies": {
5
5
  "@types/bun": "1.3.10",
6
6
  "typescript": "^5.9.3"
@@ -166,6 +166,8 @@ clankm channel shared-get <share-token> --json
166
166
  clankm post shared-get <share-token> --json
167
167
  ```
168
168
 
169
+ For paginated collection reads, follow `pagination.nextCommand` in JSON output when present. If it is absent, reuse the original command with `nextCursor`. In human-readable output, follow the printed `More results:` guidance.
170
+
169
171
  ## Failure Handling
170
172
 
171
173
  - If `doctor` says `openApiOk: false`, stop and report the base URL or connectivity issue.
@@ -98,6 +98,7 @@ export async function runAuthCommand(args: ParsedArgs, io: Io): Promise<void> {
98
98
  const { profileName, profile } = resolveProfile(
99
99
  config,
100
100
  stringFlag(args.flags, "profile"),
101
+ stringFlag(args.flags, "baseUrl"),
101
102
  );
102
103
  const outputMode = resolveOutputMode(profile, args.flags);
103
104
  const explicitChannelToken = stringFlag(args.flags, "channelToken");
@@ -163,6 +164,7 @@ export async function runAuthCommand(args: ParsedArgs, io: Io): Promise<void> {
163
164
  const { profileName, profile } = resolveProfile(
164
165
  config,
165
166
  stringFlag(args.flags, "profile"),
167
+ stringFlag(args.flags, "baseUrl"),
166
168
  );
167
169
  const outputMode = resolveOutputMode(profile, args.flags);
168
170
  const resolvedMasterToken = resolveMasterToken(profile);
@@ -207,6 +209,7 @@ async function runAccessKeyCommand(
207
209
  const { profileName, profile } = resolveProfile(
208
210
  config,
209
211
  stringFlag(args.flags, "profile"),
212
+ stringFlag(args.flags, "baseUrl"),
210
213
  );
211
214
  const outputMode = resolveOutputMode(profile, args.flags);
212
215
  const client = new ClankmatesClient(profile);
@@ -9,8 +9,14 @@ import {
9
9
  import { storeChannelToken, updateProfile } from "../lib/config";
10
10
  import { createCommandContext } from "../lib/context";
11
11
  import { CliError } from "../lib/errors";
12
- import { joinBlocks, renderFields, renderTokenAction } from "../lib/human";
12
+ import {
13
+ joinBlocks,
14
+ renderFields,
15
+ renderPagination,
16
+ renderTokenAction,
17
+ } from "../lib/human";
13
18
  import { printJson, printValue, type Io } from "../lib/output";
19
+ import { paginatedJson, paginationInfo } from "../lib/pagination";
14
20
  import type {
15
21
  ChannelAttributes,
16
22
  ChannelDiagnosticsResponse,
@@ -40,7 +46,7 @@ export async function runChannelCommand(
40
46
  cursor: stringFlag(args.flags, "cursor"),
41
47
  });
42
48
 
43
- printChannelCollection(context.outputMode, io, response);
49
+ printChannelCollection(args, context.outputMode, io, response);
44
50
  return;
45
51
  }
46
52
 
@@ -84,7 +90,7 @@ export async function runChannelCommand(
84
90
  cursor: stringFlag(args.flags, "cursor"),
85
91
  });
86
92
 
87
- printChannelCollection(context.outputMode, io, response);
93
+ printChannelCollection(args, context.outputMode, io, response);
88
94
  return;
89
95
  }
90
96
 
@@ -283,7 +289,13 @@ async function runChannelTokenCommand(
283
289
  });
284
290
 
285
291
  if (context.outputMode === "json") {
286
- printJson(io, { items: response.items, nextCursor: response.nextCursor });
292
+ printJson(
293
+ io,
294
+ paginatedJson(args, {
295
+ items: response.items,
296
+ nextCursor: response.nextCursor,
297
+ }),
298
+ );
287
299
  return;
288
300
  }
289
301
 
@@ -292,6 +304,15 @@ async function runChannelTokenCommand(
292
304
  context.outputMode,
293
305
  response.items.map((item) => formatChannelKeyRow(item)),
294
306
  );
307
+ const pagination = paginationInfo(args, response.nextCursor);
308
+ const message = renderPagination(
309
+ pagination?.nextCursor,
310
+ pagination?.nextCommand,
311
+ );
312
+
313
+ if (message) {
314
+ io.stdout(message);
315
+ }
295
316
  return;
296
317
  }
297
318
 
@@ -368,6 +389,7 @@ async function maybeStoreChannelToken(
368
389
  }
369
390
 
370
391
  function printChannelCollection(
392
+ args: ParsedArgs,
371
393
  outputMode: "json" | "table",
372
394
  io: Io,
373
395
  response: {
@@ -376,10 +398,13 @@ function printChannelCollection(
376
398
  },
377
399
  ): void {
378
400
  if (outputMode === "json") {
379
- printJson(io, {
380
- items: response.items,
381
- nextCursor: response.nextCursor,
382
- });
401
+ printJson(
402
+ io,
403
+ paginatedJson(args, {
404
+ items: response.items,
405
+ nextCursor: response.nextCursor,
406
+ }),
407
+ );
383
408
  return;
384
409
  }
385
410
 
@@ -388,6 +413,15 @@ function printChannelCollection(
388
413
  outputMode,
389
414
  response.items.map((item) => formatChannelRow(item)),
390
415
  );
416
+ const pagination = paginationInfo(args, response.nextCursor);
417
+ const message = renderPagination(
418
+ pagination?.nextCursor,
419
+ pagination?.nextCommand,
420
+ );
421
+
422
+ if (message) {
423
+ io.stdout(message);
424
+ }
391
425
  }
392
426
 
393
427
  function formatChannelRecord(channel: { id: string; attributes: ChannelAttributes }) {
@@ -7,7 +7,9 @@ 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
11
  import { printJson, printValue, type Io } from "../lib/output";
12
+ import { paginatedJson, paginationInfo } from "../lib/pagination";
11
13
  import type { PostAttributes } from "../types/api";
12
14
 
13
15
  export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
@@ -22,7 +24,7 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
22
24
  cursor: stringFlag(args.flags, "cursor"),
23
25
  });
24
26
 
25
- printFeedResponse(context, io, response);
27
+ printFeedResponse(args, context, io, response);
26
28
  return;
27
29
  }
28
30
 
@@ -40,7 +42,7 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
40
42
  cursor: stringFlag(args.flags, "cursor"),
41
43
  });
42
44
 
43
- printFeedResponse(context, io, response);
45
+ printFeedResponse(args, context, io, response);
44
46
  return;
45
47
  }
46
48
 
@@ -58,6 +60,7 @@ async function resolveChannelId(
58
60
  }
59
61
 
60
62
  function printFeedResponse(
63
+ args: ParsedArgs,
61
64
  context: CommandContext,
62
65
  io: Io,
63
66
  response: {
@@ -66,21 +69,31 @@ function printFeedResponse(
66
69
  },
67
70
  ): void {
68
71
  if (context.outputMode === "json") {
69
- printJson(io, {
70
- items: response.items,
71
- nextCursor: response.nextCursor,
72
- });
72
+ printJson(
73
+ io,
74
+ paginatedJson(args, {
75
+ items: response.items,
76
+ nextCursor: response.nextCursor,
77
+ }),
78
+ );
73
79
  return;
74
80
  }
75
81
 
76
- printValue(
77
- io,
78
- context.outputMode,
79
- response.items.map((item) => ({
80
- id: item.id,
81
- source: item.attributes.source,
82
- date: item.attributes.updated_at ?? item.attributes.inserted_at ?? "",
83
- body: item.attributes.body,
84
- })),
82
+ const rows = response.items.map((item) => ({
83
+ id: item.id,
84
+ source: item.attributes.source,
85
+ date: item.attributes.updated_at ?? item.attributes.inserted_at ?? "",
86
+ body: item.attributes.body,
87
+ }));
88
+ printValue(io, context.outputMode, rows);
89
+
90
+ const pagination = paginationInfo(args, response.nextCursor);
91
+ const message = renderPagination(
92
+ pagination?.nextCursor,
93
+ pagination?.nextCommand,
85
94
  );
95
+
96
+ if (message) {
97
+ io.stdout(message);
98
+ }
86
99
  }
@@ -17,6 +17,7 @@ import {
17
17
  shortId,
18
18
  } from "../lib/human";
19
19
  import { printJson, printValue, type Io } from "../lib/output";
20
+ import { paginatedJson, paginationInfo } from "../lib/pagination";
20
21
  import type {
21
22
  ExternalEmailIntakeAttributes,
22
23
  InboxRecipient,
@@ -44,7 +45,7 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
44
45
  channelToken,
45
46
  });
46
47
 
47
- await printThreadCollection(context, io, response, channelToken);
48
+ await printThreadCollection(args, context, io, response, channelToken);
48
49
  return;
49
50
  }
50
51
 
@@ -70,12 +71,12 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
70
71
  io,
71
72
  context.outputMode,
72
73
  context.outputMode === "json"
73
- ? {
74
+ ? paginatedJson(args, {
74
75
  thread,
75
76
  messages: messages.items,
76
77
  nextCursor: messages.nextCursor,
77
- }
78
- : renderThreadWithMessages(thread, messages, publicUsers),
78
+ })
79
+ : renderThreadWithMessages(args, thread, messages, publicUsers),
79
80
  );
80
81
  return;
81
82
  }
@@ -93,11 +94,11 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
93
94
  io,
94
95
  context.outputMode,
95
96
  context.outputMode === "json"
96
- ? {
97
+ ? paginatedJson(args, {
97
98
  items: response.items,
98
99
  nextCursor: response.nextCursor,
99
- }
100
- : renderAttachmentCollection(response),
100
+ })
101
+ : renderAttachmentCollection(args, response),
101
102
  );
102
103
  return;
103
104
  }
@@ -245,7 +246,7 @@ async function runScreeningCommand(
245
246
  channelToken,
246
247
  });
247
248
 
248
- printEmailIntakeCollection(context, io, response);
249
+ printEmailIntakeCollection(args, context, io, response);
249
250
  return;
250
251
  }
251
252
 
@@ -256,7 +257,7 @@ async function runScreeningCommand(
256
257
  channelToken,
257
258
  });
258
259
 
259
- printEmailIntakeCollection(context, io, response);
260
+ printEmailIntakeCollection(args, context, io, response);
260
261
  return;
261
262
  }
262
263
 
@@ -462,6 +463,7 @@ function looksLikeEmailAddress(value: string): boolean {
462
463
  }
463
464
 
464
465
  async function printThreadCollection(
466
+ args: ParsedArgs,
465
467
  context: CommandContext,
466
468
  io: Io,
467
469
  response: {
@@ -471,10 +473,13 @@ async function printThreadCollection(
471
473
  channelToken?: string,
472
474
  ): Promise<void> {
473
475
  if (context.outputMode === "json") {
474
- printJson(io, {
475
- items: response.items,
476
- nextCursor: response.nextCursor,
477
- });
476
+ printJson(
477
+ io,
478
+ paginatedJson(args, {
479
+ items: response.items,
480
+ nextCursor: response.nextCursor,
481
+ }),
482
+ );
478
483
  return Promise.resolve();
479
484
  }
480
485
 
@@ -501,9 +506,19 @@ async function printThreadCollection(
501
506
  expiresAt: item.attributes.expires_at ?? "",
502
507
  })),
503
508
  );
509
+ const pagination = paginationInfo(args, response.nextCursor);
510
+ const message = renderPagination(
511
+ pagination?.nextCursor,
512
+ pagination?.nextCommand,
513
+ );
514
+
515
+ if (message) {
516
+ io.stdout(message);
517
+ }
504
518
  }
505
519
 
506
520
  function renderThreadWithMessages(
521
+ args: ParsedArgs,
507
522
  thread: { id: string; attributes: ThreadAttributes },
508
523
  messages: {
509
524
  items: Array<{ id: string; attributes: MessageAttributes }>;
@@ -518,6 +533,7 @@ function renderThreadWithMessages(
518
533
  : messages.items
519
534
  .map((message) => renderMessage(message, publicUsers))
520
535
  .join("\n\n");
536
+ const pagination = paginationInfo(args, messages.nextCursor);
521
537
 
522
538
  return joinBlocks([
523
539
  `Thread ${thread.id}`,
@@ -579,7 +595,7 @@ function renderThreadWithMessages(
579
595
  ]),
580
596
  ),
581
597
  renderSection("Messages", messageBlocks),
582
- renderPagination(messages.nextCursor),
598
+ renderPagination(pagination?.nextCursor, pagination?.nextCommand),
583
599
  ]);
584
600
  }
585
601
 
@@ -606,6 +622,7 @@ function renderMessage(
606
622
  }
607
623
 
608
624
  function printEmailIntakeCollection(
625
+ args: ParsedArgs,
609
626
  context: CommandContext,
610
627
  io: Io,
611
628
  response: {
@@ -617,11 +634,11 @@ function printEmailIntakeCollection(
617
634
  io,
618
635
  context.outputMode,
619
636
  context.outputMode === "json"
620
- ? {
637
+ ? paginatedJson(args, {
621
638
  items: response.items,
622
639
  nextCursor: response.nextCursor,
623
- }
624
- : renderEmailIntakeCollection(response),
640
+ })
641
+ : renderEmailIntakeCollection(args, response),
625
642
  );
626
643
  }
627
644
 
@@ -640,16 +657,23 @@ function printEmailIntakeAction(
640
657
  );
641
658
  }
642
659
 
643
- function renderEmailIntakeCollection(response: {
644
- items: Array<{ id: string; attributes: ExternalEmailIntakeAttributes }>;
645
- nextCursor?: string;
646
- }): string {
660
+ function renderEmailIntakeCollection(
661
+ args: ParsedArgs,
662
+ response: {
663
+ items: Array<{ id: string; attributes: ExternalEmailIntakeAttributes }>;
664
+ nextCursor?: string;
665
+ },
666
+ ): string {
647
667
  const body =
648
668
  response.items.length === 0
649
669
  ? "No email intakes."
650
670
  : response.items.map((intake) => renderEmailIntake(intake)).join("\n\n");
651
671
 
652
- return joinBlocks([body, renderPagination(response.nextCursor)]);
672
+ const pagination = paginationInfo(args, response.nextCursor);
673
+ return joinBlocks([
674
+ body,
675
+ renderPagination(pagination?.nextCursor, pagination?.nextCommand),
676
+ ]);
653
677
  }
654
678
 
655
679
  function renderEmailIntake(intake: {
@@ -678,10 +702,13 @@ function renderEmailIntake(intake: {
678
702
  ]);
679
703
  }
680
704
 
681
- function renderAttachmentCollection(response: {
682
- items: Array<{ id: string; attributes: MessageAttachmentAttributes }>;
683
- nextCursor?: string;
684
- }): string {
705
+ function renderAttachmentCollection(
706
+ args: ParsedArgs,
707
+ response: {
708
+ items: Array<{ id: string; attributes: MessageAttachmentAttributes }>;
709
+ nextCursor?: string;
710
+ },
711
+ ): string {
685
712
  const body =
686
713
  response.items.length === 0
687
714
  ? "No attachments."
@@ -701,7 +728,11 @@ function renderAttachmentCollection(response: {
701
728
  })
702
729
  .join("\n\n");
703
730
 
704
- return joinBlocks([body, renderPagination(response.nextCursor)]);
731
+ const pagination = paginationInfo(args, response.nextCursor);
732
+ return joinBlocks([
733
+ body,
734
+ renderPagination(pagination?.nextCursor, pagination?.nextCommand),
735
+ ]);
705
736
  }
706
737
 
707
738
  function renderThreadAction(
@@ -10,12 +10,14 @@ import { resolveBodyInput } from "../lib/body-input";
10
10
  import { createCommandContext } from "../lib/context";
11
11
  import { CliError } from "../lib/errors";
12
12
  import {
13
- formatTimestamp,
14
- joinBlocks,
15
13
  renderBodyBlock,
16
14
  renderFields,
15
+ formatTimestamp,
16
+ joinBlocks,
17
+ renderPagination,
17
18
  } from "../lib/human";
18
19
  import { printJson, printValue, type Io } from "../lib/output";
20
+ import { paginatedJson, paginationInfo } from "../lib/pagination";
19
21
  import type { PostAttributes, ShareTokenResponse } from "../types/api";
20
22
 
21
23
  export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
@@ -56,7 +58,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
56
58
  cursor: stringFlag(args.flags, "cursor"),
57
59
  });
58
60
 
59
- printPostCollection(context.outputMode, io, response);
61
+ printPostCollection(args, context.outputMode, io, response);
60
62
  return;
61
63
  }
62
64
 
@@ -76,7 +78,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
76
78
  cursor: stringFlag(args.flags, "cursor"),
77
79
  });
78
80
 
79
- printPostCollection(context.outputMode, io, response);
81
+ printPostCollection(args, context.outputMode, io, response);
80
82
  return;
81
83
  }
82
84
 
@@ -87,7 +89,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
87
89
  cursor: stringFlag(args.flags, "cursor"),
88
90
  });
89
91
 
90
- printPostCollection(context.outputMode, io, response);
92
+ printPostCollection(args, context.outputMode, io, response);
91
93
  return;
92
94
  }
93
95
 
@@ -218,6 +220,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
218
220
  }
219
221
 
220
222
  function printPostCollection(
223
+ args: ParsedArgs,
221
224
  outputMode: "json" | "table",
222
225
  io: Io,
223
226
  response: {
@@ -226,10 +229,13 @@ function printPostCollection(
226
229
  },
227
230
  ): void {
228
231
  if (outputMode === "json") {
229
- printJson(io, {
230
- items: response.items,
231
- nextCursor: response.nextCursor,
232
- });
232
+ printJson(
233
+ io,
234
+ paginatedJson(args, {
235
+ items: response.items,
236
+ nextCursor: response.nextCursor,
237
+ }),
238
+ );
233
239
  return;
234
240
  }
235
241
 
@@ -243,6 +249,16 @@ function printPostCollection(
243
249
  body: item.attributes.body,
244
250
  })),
245
251
  );
252
+
253
+ const pagination = paginationInfo(args, response.nextCursor);
254
+ const message = renderPagination(
255
+ pagination?.nextCursor,
256
+ pagination?.nextCommand,
257
+ );
258
+
259
+ if (message) {
260
+ io.stdout(message);
261
+ }
246
262
  }
247
263
 
248
264
  function renderPostDetail(
package/src/lib/config.ts CHANGED
@@ -115,7 +115,11 @@ export function resolveProfileName(config: ConfigFile, profileName?: string): st
115
115
  return profileName ?? process.env.CLANKMATES_PROFILE ?? config.activeProfile;
116
116
  }
117
117
 
118
- export function resolveProfile(config: ConfigFile, profileName?: string): { profileName: string; profile: ProfileConfig } {
118
+ export function resolveProfile(
119
+ config: ConfigFile,
120
+ profileName?: string,
121
+ baseUrl?: string,
122
+ ): { profileName: string; profile: ProfileConfig } {
119
123
  const resolvedName = resolveProfileName(config, profileName);
120
124
  const profile = config.profiles[resolvedName];
121
125
 
@@ -127,8 +131,8 @@ export function resolveProfile(config: ConfigFile, profileName?: string): { prof
127
131
  profileName: resolvedName,
128
132
  profile: {
129
133
  ...profile,
130
- baseUrl: resolveBaseUrl(undefined, profile.baseUrl)
131
- }
134
+ baseUrl: resolveBaseUrl(baseUrl, profile.baseUrl),
135
+ },
132
136
  };
133
137
  }
134
138
 
@@ -18,7 +18,11 @@ export interface CommandContext {
18
18
  export async function createCommandContext(args: ParsedArgs, io: Io): Promise<CommandContext> {
19
19
  const configPath = getConfigPath();
20
20
  const config = await loadConfig(configPath);
21
- const { profileName, profile } = resolveProfile(config, stringFlag(args.flags, "profile"));
21
+ const { profileName, profile } = resolveProfile(
22
+ config,
23
+ stringFlag(args.flags, "profile"),
24
+ stringFlag(args.flags, "baseUrl"),
25
+ );
22
26
 
23
27
  return {
24
28
  config,
package/src/lib/help.ts CHANGED
@@ -88,7 +88,7 @@ const LIMIT_OPTION = option(
88
88
  "Limit the number of returned records.",
89
89
  );
90
90
  const CURSOR_OPTION = option(
91
- "--cursor <keyset>",
91
+ "--cursor <cursor>",
92
92
  "Resume from a pagination cursor returned by a prior request.",
93
93
  );
94
94
  const CHANNEL_TOKEN_OPTION = option(
@@ -325,7 +325,7 @@ const HELP_ROOT = group(
325
325
  command(
326
326
  "list",
327
327
  "List owned channels.",
328
- `${CLI_NAME} channel list [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
328
+ `${CLI_NAME} channel list [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
329
329
  {
330
330
  options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
331
331
  },
@@ -352,7 +352,7 @@ const HELP_ROOT = group(
352
352
  command(
353
353
  "public-list",
354
354
  "List publicly visible channels for a public handle.",
355
- `${CLI_NAME} channel public-list <public-handle> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
355
+ `${CLI_NAME} channel public-list <public-handle> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
356
356
  {
357
357
  options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
358
358
  },
@@ -450,7 +450,7 @@ const HELP_ROOT = group(
450
450
  command(
451
451
  "list",
452
452
  "List channel publish keys for one channel.",
453
- `${CLI_NAME} channel token list <channel> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
453
+ `${CLI_NAME} channel token list <channel> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
454
454
  {
455
455
  options: [
456
456
  LIMIT_OPTION,
@@ -522,7 +522,7 @@ const HELP_ROOT = group(
522
522
  command(
523
523
  "list",
524
524
  "List posts for one owned channel.",
525
- `${CLI_NAME} post list --channel <name-or-uuid> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
525
+ `${CLI_NAME} post list --channel <name-or-uuid> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
526
526
  {
527
527
  options: [
528
528
  option(
@@ -563,7 +563,7 @@ const HELP_ROOT = group(
563
563
  command(
564
564
  "public-list",
565
565
  "List public posts for one public channel.",
566
- `${CLI_NAME} post public-list <public-handle> <channel-name> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
566
+ `${CLI_NAME} post public-list <public-handle> <channel-name> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
567
567
  {
568
568
  options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
569
569
  },
@@ -579,7 +579,7 @@ const HELP_ROOT = group(
579
579
  command(
580
580
  "shared-list",
581
581
  "List posts in a shared channel by share token.",
582
- `${CLI_NAME} post shared-list <share-token> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
582
+ `${CLI_NAME} post shared-list <share-token> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
583
583
  {
584
584
  options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
585
585
  },
@@ -627,7 +627,7 @@ const HELP_ROOT = group(
627
627
  command(
628
628
  "my",
629
629
  "List posts from the owner feed.",
630
- `${CLI_NAME} feed my [--channel <name-or-uuid>] [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
630
+ `${CLI_NAME} feed my [--channel <name-or-uuid>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
631
631
  {
632
632
  options: [
633
633
  option(
@@ -644,7 +644,7 @@ const HELP_ROOT = group(
644
644
  command(
645
645
  "search",
646
646
  "Search the owner feed.",
647
- `${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
647
+ `${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
648
648
  {
649
649
  options: [
650
650
  option(
@@ -670,7 +670,7 @@ const HELP_ROOT = group(
670
670
  command(
671
671
  "list",
672
672
  "List inbox threads.",
673
- `${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
673
+ `${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
674
674
  {
675
675
  options: [
676
676
  option(
@@ -692,7 +692,7 @@ const HELP_ROOT = group(
692
692
  command(
693
693
  "show",
694
694
  "Show one thread and its recent messages.",
695
- `${CLI_NAME} inbox show <thread-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
695
+ `${CLI_NAME} inbox show <thread-id> [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
696
696
  {
697
697
  options: [
698
698
  LIMIT_OPTION,
@@ -706,7 +706,7 @@ const HELP_ROOT = group(
706
706
  command(
707
707
  "attachments",
708
708
  "List attachment metadata for one message.",
709
- `${CLI_NAME} inbox attachments <message-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
709
+ `${CLI_NAME} inbox attachments <message-id> [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
710
710
  {
711
711
  options: [
712
712
  LIMIT_OPTION,
@@ -802,7 +802,7 @@ const HELP_ROOT = group(
802
802
  command(
803
803
  "list",
804
804
  "List screened external email waiting for a decision.",
805
- `${CLI_NAME} inbox screening list [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
805
+ `${CLI_NAME} inbox screening list [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
806
806
  {
807
807
  options: [
808
808
  LIMIT_OPTION,
@@ -816,7 +816,7 @@ const HELP_ROOT = group(
816
816
  command(
817
817
  "processing",
818
818
  "List released external email in the processing queue.",
819
- `${CLI_NAME} inbox screening processing [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
819
+ `${CLI_NAME} inbox screening processing [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
820
820
  {
821
821
  options: [
822
822
  LIMIT_OPTION,
package/src/lib/human.ts CHANGED
@@ -80,8 +80,17 @@ export function renderBodyBlock(body: string): string {
80
80
  return indent(normalized);
81
81
  }
82
82
 
83
- export function renderPagination(nextCursor?: string | null): string {
84
- return nextCursor ? `More results: --cursor ${nextCursor}` : "";
83
+ export function renderPagination(
84
+ nextCursor?: string | null,
85
+ nextCommand?: string,
86
+ ): string {
87
+ if (!nextCursor) {
88
+ return "";
89
+ }
90
+
91
+ return nextCommand
92
+ ? `More results: ${nextCommand}`
93
+ : `More results: --cursor ${nextCursor}`;
85
94
  }
86
95
 
87
96
  export function joinBlocks(blocks: Array<string | undefined | null | false>): string {
@@ -0,0 +1,98 @@
1
+ import type { ParsedArgs } from "./args";
2
+
3
+ const CLI_NAME = "clankm";
4
+
5
+ const PAGINATION_FLAG_ORDER: Array<
6
+ [key: string, flag: string]
7
+ > = [
8
+ ["profile", "--profile"],
9
+ ["baseUrl", "--base-url"],
10
+ ["channel", "--channel"],
11
+ ["channelId", "--channel"],
12
+ ["status", "--status"],
13
+ ["mailbox", "--mailbox"],
14
+ ["limit", "--limit"],
15
+ ];
16
+
17
+ export interface PaginationInfo {
18
+ nextCursor: string;
19
+ nextCommand?: string;
20
+ }
21
+
22
+ export function paginationInfo(
23
+ args: ParsedArgs,
24
+ nextCursor?: string | null,
25
+ options: { json?: boolean } = {},
26
+ ): PaginationInfo | undefined {
27
+ if (!nextCursor) {
28
+ return undefined;
29
+ }
30
+
31
+ return {
32
+ nextCursor,
33
+ nextCommand: args.flags.channelToken
34
+ ? undefined
35
+ : nextCommand(args, nextCursor, options),
36
+ };
37
+ }
38
+
39
+ export function paginatedJson<T extends object>(
40
+ args: ParsedArgs,
41
+ response: T & { nextCursor?: string },
42
+ ): T & { nextCursor?: string; pagination?: PaginationInfo } {
43
+ return {
44
+ ...response,
45
+ pagination: paginationInfo(args, response.nextCursor, { json: true }),
46
+ };
47
+ }
48
+
49
+ function paginationFlagParts(args: ParsedArgs): string[] {
50
+ const parts: string[] = [];
51
+ const seenFlags = new Set<string>();
52
+
53
+ for (const [key, flag] of PAGINATION_FLAG_ORDER) {
54
+ if (seenFlags.has(flag)) {
55
+ continue;
56
+ }
57
+
58
+ const value = args.flags[key];
59
+
60
+ if (typeof value !== "string") {
61
+ continue;
62
+ }
63
+
64
+ parts.push(flag, value);
65
+ seenFlags.add(flag);
66
+ }
67
+
68
+ return parts;
69
+ }
70
+
71
+ function nextCommand(
72
+ args: ParsedArgs,
73
+ nextCursor: string,
74
+ options: { json?: boolean },
75
+ ): string {
76
+ const parts = [
77
+ CLI_NAME,
78
+ ...args.commandPath,
79
+ ...args.positionals,
80
+ ...paginationFlagParts(args),
81
+ "--cursor",
82
+ nextCursor,
83
+ ];
84
+
85
+ if (options.json) {
86
+ parts.push("--json");
87
+ }
88
+
89
+ return parts.map(shellQuote).join(" ");
90
+ }
91
+
92
+ function shellQuote(value: string): string {
93
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) {
94
+ return value;
95
+ }
96
+
97
+ return `'${value.replace(/'/g, "'\\''")}'`;
98
+ }