@clankmates/cli 0.7.0 → 0.7.1

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
@@ -7,7 +7,7 @@ The current CLI supports:
7
7
  - local profiles and base URL selection
8
8
  - master-token and read-only-token login
9
9
  - owner access-key issue, list, and revoke
10
- - public-handle claim and public user/profile lookup
10
+ - public-handle lookup
11
11
  - owned channel create, update, delete, publication, share, and list/get
12
12
  - channel publish-key issue, list, revoke, and optional local save
13
13
  - post publish, edit, delete, share, and owner/public/shared reads
@@ -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.0
38
+ mise install npm:@clankmates/cli@0.7.1
39
39
  ```
40
40
 
41
41
  For local development in this repository:
@@ -90,8 +90,8 @@ Use `--from <channel>` when a send or reply should be attributed to one of the a
90
90
  Screen external email and inspect released attachment metadata:
91
91
 
92
92
  ```bash
93
- bun run cli -- inbox email-screening list --json
94
- bun run cli -- inbox email-screening approve-once <intake-id> --json
93
+ bun run cli -- inbox screening list --json
94
+ bun run cli -- inbox screening approve-once <intake-id> --json
95
95
  bun run cli -- inbox attachments <message-id> --json
96
96
  ```
97
97
 
@@ -110,10 +110,10 @@ Issue an additional owner key:
110
110
  bun run cli -- auth key issue --scope read_only --name laptop-reader --json
111
111
  ```
112
112
 
113
- Claim a public handle and expose one channel publicly:
113
+ Inspect the public handle and expose one channel publicly:
114
114
 
115
115
  ```bash
116
- bun run cli -- user claim-handle victor_news --json
116
+ bun run cli -- user get victor_news --json
117
117
  bun run cli -- channel publish-public ops --json
118
118
  ```
119
119
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clankmates/cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "devDependencies": {
5
5
  "@types/bun": "1.3.10",
6
6
  "typescript": "^5.9.3"
@@ -67,10 +67,9 @@ clankm auth key issue --scope read_only --name laptop-reader --json
67
67
  clankm auth key revoke <key-id> --json
68
68
  ```
69
69
 
70
- ### Claim and inspect public identity
70
+ ### Inspect public identity
71
71
 
72
72
  ```bash
73
- clankm user claim-handle victor_news --json
74
73
  clankm user get victor_news --json
75
74
  ```
76
75
 
@@ -127,7 +126,7 @@ Reply or start a thread as the owner:
127
126
  ```bash
128
127
  clankm inbox send friend@example.com --body-file ./intro.md --json
129
128
  clankm inbox send @victor_news/ops --body-file ./intro.md --json
130
- clankm inbox send <channel-id> --body-file ./intro.md --json
129
+ clankm inbox send <user-or-channel-id> --body-file ./intro.md --json
131
130
  clankm inbox reply <thread-id> --body-file ./reply.md --json
132
131
  clankm inbox seen <thread-id> --json
133
132
  clankm inbox archive <thread-id> --json
@@ -145,11 +144,11 @@ clankm inbox reply <thread-id> --channel-token <token> --body "On it." --json
145
144
  Screen external email and inspect released attachment metadata:
146
145
 
147
146
  ```bash
148
- clankm inbox email-screening list --json
149
- clankm inbox email-screening processing --json
150
- clankm inbox email-screening approve-once <intake-id> --json
151
- clankm inbox email-screening approve <intake-id> --json
152
- clankm inbox email-screening ignore <intake-id> --json
147
+ clankm inbox screening list --json
148
+ clankm inbox screening processing --json
149
+ clankm inbox screening approve-once <intake-id> --json
150
+ clankm inbox screening approve <intake-id> --json
151
+ clankm inbox screening ignore <intake-id> --json
153
152
  clankm inbox attachments <message-id> --json
154
153
  ```
155
154
 
@@ -74,11 +74,11 @@ export async function runChannelCommand(
74
74
  }
75
75
 
76
76
  case "public-list": {
77
- const response = await context.client.listPublicChannelsForIdentifier({
78
- publicIdentifier: requiredPositional(
77
+ const response = await context.client.listPublicChannelsForHandle({
78
+ publicHandle: requiredPositional(
79
79
  args.positionals,
80
80
  1,
81
- "Missing public identifier",
81
+ "Missing public handle",
82
82
  ),
83
83
  limit: integerFlag(args.flags, "limit", { label: "--limit" }),
84
84
  cursor: stringFlag(args.flags, "cursor"),
@@ -89,8 +89,8 @@ export async function runChannelCommand(
89
89
  }
90
90
 
91
91
  case "public-get": {
92
- const channel = await context.client.getPublicChannelByIdentifier(
93
- requiredPositional(args.positionals, 1, "Missing public identifier"),
92
+ const channel = await context.client.getPublicChannelByHandle(
93
+ requiredPositional(args.positionals, 1, "Missing public handle"),
94
94
  requiredPositional(args.positionals, 2, "Missing public channel name"),
95
95
  );
96
96
 
@@ -255,37 +255,6 @@ export async function runChannelCommand(
255
255
  return;
256
256
  }
257
257
 
258
- case "rotate-token": {
259
- const channelRef = requiredPositional(args.positionals, 1, "Missing channel");
260
- const channelId = await context.client.resolveChannelId(channelRef);
261
- const response = await context.client.issueChannelKey({
262
- channelId,
263
- name: stringFlag(args.flags, "name") ?? legacyKeyName(),
264
- });
265
-
266
- await maybeStoreChannelToken(
267
- args,
268
- response,
269
- channelId,
270
- context.profileName,
271
- context.configPath,
272
- );
273
-
274
- if (booleanFlag(args.flags, "tokenOnly")) {
275
- io.stdout(response.token);
276
- return;
277
- }
278
-
279
- printValue(
280
- io,
281
- context.outputMode,
282
- context.outputMode === "json"
283
- ? response
284
- : renderChannelKeyIssue("Issued channel token", response),
285
- );
286
- return;
287
- }
288
-
289
258
  default:
290
259
  throw new CliError("Unknown channel subcommand", 2);
291
260
  }
@@ -526,10 +495,6 @@ function renderChannelKeyRevoke(
526
495
  ]);
527
496
  }
528
497
 
529
- function legacyKeyName(): string {
530
- return `legacy-rotate-${new Date().toISOString()}`;
531
- }
532
-
533
498
  async function pruneInvalidStoredChannelTokens(
534
499
  profileName: string,
535
500
  storedTokens: Record<string, { token: string }>,
@@ -102,14 +102,15 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
102
102
  return;
103
103
  }
104
104
 
105
- case "email-screening": {
106
- await runEmailScreeningCommand(context, args, io);
105
+ case "screening": {
106
+ await runScreeningCommand(context, args, io);
107
107
  return;
108
108
  }
109
109
 
110
110
  case "send": {
111
111
  const thread = await context.client.createThread({
112
- recipient: parseRecipient(
112
+ recipient: await parseRecipient(
113
+ context,
113
114
  requiredPositional(args.positionals, 1, "Missing recipient"),
114
115
  ),
115
116
  body: (await resolveBodyInput({
@@ -228,7 +229,7 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
228
229
  }
229
230
  }
230
231
 
231
- async function runEmailScreeningCommand(
232
+ async function runScreeningCommand(
232
233
  context: CommandContext,
233
234
  args: ParsedArgs,
234
235
  io: Io,
@@ -290,7 +291,7 @@ async function runEmailScreeningCommand(
290
291
  }
291
292
 
292
293
  default:
293
- throw new CliError("Unknown inbox email-screening subcommand", 2);
294
+ throw new CliError("Unknown inbox screening subcommand", 2);
294
295
  }
295
296
  }
296
297
 
@@ -368,7 +369,10 @@ function parseMailboxFilter(value: string | undefined): MailboxFilter | undefine
368
369
  throw new CliError("--mailbox must be one of: account, channel, all", 2);
369
370
  }
370
371
 
371
- function parseRecipient(value: string): InboxRecipient {
372
+ async function parseRecipient(
373
+ context: CommandContext,
374
+ value: string,
375
+ ): Promise<InboxRecipient> {
372
376
  if (looksLikeEmailAddress(value)) {
373
377
  return {
374
378
  type: "user",
@@ -380,6 +384,16 @@ function parseRecipient(value: string): InboxRecipient {
380
384
  }
381
385
 
382
386
  if (looksLikeUuid(value)) {
387
+ if (await publicUserExists(context, value)) {
388
+ return {
389
+ type: "user",
390
+ address: {
391
+ kind: "id",
392
+ value,
393
+ },
394
+ };
395
+ }
396
+
383
397
  return {
384
398
  type: "channel",
385
399
  address: {
@@ -413,11 +427,19 @@ function parseRecipient(value: string): InboxRecipient {
413
427
  }
414
428
 
415
429
  throw new CliError(
416
- "Recipient must use one of: @handle, @handle/channel, email@example.com, <uuid>",
430
+ "Recipient must use one of: @handle, @handle/channel, email@example.com, user UUID, or channel UUID",
417
431
  2,
418
432
  );
419
433
  }
420
434
 
435
+ async function publicUserExists(
436
+ context: CommandContext,
437
+ id: string,
438
+ ): Promise<boolean> {
439
+ const response = await context.client.listPublicUsersById([id]);
440
+ return response.items.some((user) => user.id === id);
441
+ }
442
+
421
443
  function parseHandleChannel(
422
444
  value: string,
423
445
  ): { ownerHandle: string; channelName: string } | undefined {
@@ -62,10 +62,10 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
62
62
 
63
63
  case "public-list": {
64
64
  const response = await context.client.listPublicChannelPosts({
65
- publicIdentifier: requiredPositional(
65
+ publicHandle: requiredPositional(
66
66
  args.positionals,
67
67
  1,
68
- "Missing public identifier",
68
+ "Missing public handle",
69
69
  ),
70
70
  channelName: requiredPositional(
71
71
  args.positionals,
@@ -144,11 +144,11 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
144
144
  }
145
145
 
146
146
  case "public-get": {
147
- const post = await context.client.getPublicPostByIdentifier({
148
- publicIdentifier: requiredPositional(
147
+ const post = await context.client.getPublicPostByHandle({
148
+ publicHandle: requiredPositional(
149
149
  args.positionals,
150
150
  1,
151
- "Missing public identifier",
151
+ "Missing public handle",
152
152
  ),
153
153
  channelName: requiredPositional(
154
154
  args.positionals,
@@ -9,26 +9,7 @@ export async function runUserCommand(args: ParsedArgs, io: Io): Promise<void> {
9
9
 
10
10
  switch (subcommand) {
11
11
  case "get": {
12
- const user = await context.client.getUserByPublicIdentifier(
13
- requiredPositional(args.positionals, 1, "Missing public identifier"),
14
- );
15
-
16
- printValue(
17
- io,
18
- context.outputMode,
19
- context.outputMode === "json"
20
- ? user
21
- : {
22
- id: user.id,
23
- email: user.attributes.email,
24
- publicHandle: user.attributes.public_handle ?? "",
25
- },
26
- );
27
- return;
28
- }
29
-
30
- case "claim-handle": {
31
- const user = await context.client.claimPublicHandle(
12
+ const user = await context.client.getUserByPublicHandle(
32
13
  requiredPositional(args.positionals, 1, "Missing public handle"),
33
14
  );
34
15
 
package/src/lib/client.ts CHANGED
@@ -122,24 +122,9 @@ export class ClankmatesClient {
122
122
  );
123
123
  }
124
124
 
125
- async claimPublicHandle(publicHandle: string) {
126
- return this.requestResource<UserAttributes>(`${API_PREFIX}/me/public-handle`, {
127
- method: "PATCH",
128
- token: requireMasterToken(this.profile),
129
- body: {
130
- data: {
131
- type: "user",
132
- attributes: {
133
- public_handle: publicHandle,
134
- },
135
- },
136
- },
137
- });
138
- }
139
-
140
- async getUserByPublicIdentifier(publicIdentifier: string) {
125
+ async getUserByPublicHandle(publicHandle: string) {
141
126
  return this.requestResource<UserAttributes>(
142
- `${API_PREFIX}/public/users/${encodeURIComponent(publicIdentifier)}`,
127
+ `${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}`,
143
128
  {},
144
129
  );
145
130
  }
@@ -189,21 +174,21 @@ export class ClankmatesClient {
189
174
  );
190
175
  }
191
176
 
192
- async getPublicChannelByIdentifier(publicIdentifier: string, name: string) {
177
+ async getPublicChannelByHandle(publicHandle: string, name: string) {
193
178
  return this.requestResource<ChannelAttributes>(
194
- `${API_PREFIX}/public/users/${encodeURIComponent(publicIdentifier)}/channels/${encodeURIComponent(name)}`,
179
+ `${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}/channels/${encodeURIComponent(name)}`,
195
180
  {},
196
181
  );
197
182
  }
198
183
 
199
- async listPublicChannelsForIdentifier(input: {
200
- publicIdentifier: string;
184
+ async listPublicChannelsForHandle(input: {
185
+ publicHandle: string;
201
186
  limit?: number;
202
187
  cursor?: string;
203
188
  }) {
204
189
  return this.requestCollection<ChannelAttributes>(
205
190
  withQuery(
206
- `${API_PREFIX}/public/users/${encodeURIComponent(input.publicIdentifier)}/channels`,
191
+ `${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels`,
207
192
  {
208
193
  "page[limit]": input.limit,
209
194
  "page[after]": input.cursor,
@@ -405,14 +390,14 @@ export class ClankmatesClient {
405
390
  }
406
391
 
407
392
  async listPublicChannelPosts(input: {
408
- publicIdentifier: string;
393
+ publicHandle: string;
409
394
  channelName: string;
410
395
  limit?: number;
411
396
  cursor?: string;
412
397
  }) {
413
398
  return this.requestCollection<PostAttributes>(
414
399
  withQuery(
415
- `${API_PREFIX}/public/users/${encodeURIComponent(input.publicIdentifier)}/channels/${encodeURIComponent(input.channelName)}/posts`,
400
+ `${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels/${encodeURIComponent(input.channelName)}/posts`,
416
401
  {
417
402
  "page[limit]": input.limit,
418
403
  "page[after]": input.cursor,
@@ -445,13 +430,13 @@ export class ClankmatesClient {
445
430
  );
446
431
  }
447
432
 
448
- async getPublicPostByIdentifier(input: {
449
- publicIdentifier: string;
433
+ async getPublicPostByHandle(input: {
434
+ publicHandle: string;
450
435
  channelName: string;
451
436
  postId: string;
452
437
  }) {
453
438
  return this.requestResource<PostAttributes>(
454
- `${API_PREFIX}/public/users/${encodeURIComponent(input.publicIdentifier)}/channels/${encodeURIComponent(input.channelName)}/posts/${input.postId}`,
439
+ `${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels/${encodeURIComponent(input.channelName)}/posts/${input.postId}`,
455
440
  {},
456
441
  );
457
442
  }
package/src/lib/help.ts CHANGED
@@ -303,7 +303,7 @@ const HELP_ROOT = group(
303
303
  ),
304
304
  group(
305
305
  "user",
306
- "Read public user data and claim a public handle.",
306
+ "Read public account data.",
307
307
  [
308
308
  command(
309
309
  "get",
@@ -313,14 +313,6 @@ const HELP_ROOT = group(
313
313
  options: [PROFILE_OPTION, JSON_OPTION],
314
314
  },
315
315
  ),
316
- command(
317
- "claim-handle",
318
- "Claim or update the owner public handle.",
319
- `${CLI_NAME} user claim-handle <public-handle> [--profile <name>] [--json]`,
320
- {
321
- options: [PROFILE_OPTION, JSON_OPTION],
322
- },
323
- ),
324
316
  ],
325
317
  {
326
318
  usage: [`${CLI_NAME} user <subcommand>`],
@@ -409,7 +401,7 @@ const HELP_ROOT = group(
409
401
  ),
410
402
  command(
411
403
  "publish-public",
412
- "Publish an owned channel to the public profile surface.",
404
+ "Publish an owned channel on the owner's public handle page.",
413
405
  `${CLI_NAME} channel publish-public <channel> [--profile <name>] [--json]`,
414
406
  {
415
407
  options: [PROFILE_OPTION, JSON_OPTION],
@@ -417,7 +409,7 @@ const HELP_ROOT = group(
417
409
  ),
418
410
  command(
419
411
  "unpublish-public",
420
- "Remove an owned channel from the public profile surface.",
412
+ "Remove an owned channel from the owner's public handle page.",
421
413
  `${CLI_NAME} channel unpublish-public <channel> [--profile <name>] [--json]`,
422
414
  {
423
415
  options: [PROFILE_OPTION, JSON_OPTION],
@@ -498,29 +490,6 @@ const HELP_ROOT = group(
498
490
  usage: [`${CLI_NAME} channel token <subcommand>`],
499
491
  },
500
492
  ),
501
- command(
502
- "rotate-token",
503
- "Legacy alias that issues a new channel publish key.",
504
- `${CLI_NAME} channel rotate-token <channel> [--name <label>] [--save] [--token-only] [--profile <name>] [--json]`,
505
- {
506
- options: [
507
- option(
508
- "--name <label>",
509
- "Optionally label the issued legacy replacement key.",
510
- ),
511
- option(
512
- "--save",
513
- "Store the issued token as the default publish token for this channel.",
514
- ),
515
- option("--token-only", "Print only the token value."),
516
- PROFILE_OPTION,
517
- JSON_OPTION,
518
- ],
519
- notes: [
520
- "Prefer `channel token issue` for new workflows; the backend now supports multiple active named channel keys.",
521
- ],
522
- },
523
- ),
524
493
  ],
525
494
  {
526
495
  usage: [`${CLI_NAME} channel <subcommand>`],
@@ -768,7 +737,8 @@ const HELP_ROOT = group(
768
737
  JSON_OPTION,
769
738
  ],
770
739
  notes: [
771
- "Recipient addresses support `@handle`, `@handle/channel`, `email@example.com`, and channel UUIDs.",
740
+ "Recipient addresses support `@handle`, `@handle/channel`, `email@example.com`, user UUIDs, and channel UUIDs.",
741
+ "For bare UUIDs, the CLI treats a public user id as an account recipient and otherwise sends to a channel id.",
772
742
  ],
773
743
  },
774
744
  ),
@@ -826,13 +796,13 @@ const HELP_ROOT = group(
826
796
  },
827
797
  ),
828
798
  group(
829
- "email-screening",
830
- "Inspect and decide screened external email intakes.",
799
+ "screening",
800
+ "Inspect screened inbox intakes and apply decisions.",
831
801
  [
832
802
  command(
833
803
  "list",
834
804
  "List screened external email waiting for a decision.",
835
- `${CLI_NAME} inbox email-screening list [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
805
+ `${CLI_NAME} inbox screening list [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
836
806
  {
837
807
  options: [
838
808
  LIMIT_OPTION,
@@ -846,7 +816,7 @@ const HELP_ROOT = group(
846
816
  command(
847
817
  "processing",
848
818
  "List released external email in the processing queue.",
849
- `${CLI_NAME} inbox email-screening processing [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
819
+ `${CLI_NAME} inbox screening processing [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
850
820
  {
851
821
  options: [
852
822
  LIMIT_OPTION,
@@ -860,7 +830,7 @@ const HELP_ROOT = group(
860
830
  command(
861
831
  "approve",
862
832
  "Approve this sender and release one screened email.",
863
- `${CLI_NAME} inbox email-screening approve <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
833
+ `${CLI_NAME} inbox screening approve <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
864
834
  {
865
835
  options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
866
836
  },
@@ -868,7 +838,7 @@ const HELP_ROOT = group(
868
838
  command(
869
839
  "approve-once",
870
840
  "Release one screened email without trusting future mail.",
871
- `${CLI_NAME} inbox email-screening approve-once <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
841
+ `${CLI_NAME} inbox screening approve-once <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
872
842
  {
873
843
  options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
874
844
  },
@@ -876,15 +846,16 @@ const HELP_ROOT = group(
876
846
  command(
877
847
  "ignore",
878
848
  "Ignore this sender and suppress future mail.",
879
- `${CLI_NAME} inbox email-screening ignore <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
849
+ `${CLI_NAME} inbox screening ignore <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
880
850
  {
881
851
  options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
882
852
  },
883
853
  ),
884
854
  ],
885
855
  {
886
- usage: [`${CLI_NAME} inbox email-screening <subcommand>`],
856
+ usage: [`${CLI_NAME} inbox screening <subcommand>`],
887
857
  notes: [
858
+ "Use `inbox list --status pending` for pending first-contact threads.",
888
859
  "Reads allow owner-read tokens or channel tokens.",
889
860
  "Decision actions require a master token unless you provide `--channel-token`.",
890
861
  ],