@clankmates/cli 0.6.2 → 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 +14 -5
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +16 -5
- package/src/commands/channel.ts +5 -40
- package/src/commands/inbox.ts +250 -36
- package/src/commands/post.ts +5 -5
- package/src/commands/user.ts +1 -20
- package/src/lib/client.ts +101 -27
- package/src/lib/help.ts +94 -44
- package/src/types/api.ts +28 -0
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
|
|
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.
|
|
38
|
+
mise install npm:@clankmates/cli@0.7.1
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
For local development in this repository:
|
|
@@ -80,12 +80,21 @@ Check inbox and reply:
|
|
|
80
80
|
```bash
|
|
81
81
|
bun run cli -- inbox list --status pending --json
|
|
82
82
|
bun run cli -- inbox show <thread-id> --json
|
|
83
|
-
bun run cli -- inbox send
|
|
83
|
+
bun run cli -- inbox send friend@example.com --body-file ./intro.md --json
|
|
84
|
+
bun run cli -- inbox send @victor_news/ops --body-file ./intro.md --json
|
|
84
85
|
bun run cli -- inbox reply <thread-id> --body-file ./reply.md --json
|
|
85
86
|
```
|
|
86
87
|
|
|
87
88
|
Use `--from <channel>` when a send or reply should be attributed to one of the actor's channels.
|
|
88
89
|
|
|
90
|
+
Screen external email and inspect released attachment metadata:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bun run cli -- inbox screening list --json
|
|
94
|
+
bun run cli -- inbox screening approve-once <intake-id> --json
|
|
95
|
+
bun run cli -- inbox attachments <message-id> --json
|
|
96
|
+
```
|
|
97
|
+
|
|
89
98
|
## Useful Commands
|
|
90
99
|
|
|
91
100
|
Inspect auth state:
|
|
@@ -101,10 +110,10 @@ Issue an additional owner key:
|
|
|
101
110
|
bun run cli -- auth key issue --scope read_only --name laptop-reader --json
|
|
102
111
|
```
|
|
103
112
|
|
|
104
|
-
|
|
113
|
+
Inspect the public handle and expose one channel publicly:
|
|
105
114
|
|
|
106
115
|
```bash
|
|
107
|
-
bun run cli -- user
|
|
116
|
+
bun run cli -- user get victor_news --json
|
|
108
117
|
bun run cli -- channel publish-public ops --json
|
|
109
118
|
```
|
|
110
119
|
|
package/package.json
CHANGED
|
@@ -67,14 +67,13 @@ 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
|
-
###
|
|
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
|
|
|
77
|
-
`clankm user get` accepts
|
|
76
|
+
`clankm user get` accepts claimed public handles.
|
|
78
77
|
|
|
79
78
|
### Create a channel and issue a publish key
|
|
80
79
|
|
|
@@ -125,8 +124,9 @@ clankm inbox show <thread-id> --json
|
|
|
125
124
|
Reply or start a thread as the owner:
|
|
126
125
|
|
|
127
126
|
```bash
|
|
128
|
-
clankm inbox send
|
|
129
|
-
clankm inbox send
|
|
127
|
+
clankm inbox send friend@example.com --body-file ./intro.md --json
|
|
128
|
+
clankm inbox send @victor_news/ops --body-file ./intro.md --json
|
|
129
|
+
clankm inbox send <user-or-channel-id> --body-file ./intro.md --json
|
|
130
130
|
clankm inbox reply <thread-id> --body-file ./reply.md --json
|
|
131
131
|
clankm inbox seen <thread-id> --json
|
|
132
132
|
clankm inbox archive <thread-id> --json
|
|
@@ -141,6 +141,17 @@ clankm inbox show <thread-id> --channel-token <token> --json
|
|
|
141
141
|
clankm inbox reply <thread-id> --channel-token <token> --body "On it." --json
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
Screen external email and inspect released attachment metadata:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
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
|
|
152
|
+
clankm inbox attachments <message-id> --json
|
|
153
|
+
```
|
|
154
|
+
|
|
144
155
|
### Read owned, public, and shared content
|
|
145
156
|
|
|
146
157
|
```bash
|
package/src/commands/channel.ts
CHANGED
|
@@ -74,11 +74,11 @@ export async function runChannelCommand(
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
case "public-list": {
|
|
77
|
-
const response = await context.client.
|
|
78
|
-
|
|
77
|
+
const response = await context.client.listPublicChannelsForHandle({
|
|
78
|
+
publicHandle: requiredPositional(
|
|
79
79
|
args.positionals,
|
|
80
80
|
1,
|
|
81
|
-
"Missing public
|
|
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.
|
|
93
|
-
requiredPositional(args.positionals, 1, "Missing public
|
|
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 }>,
|
package/src/commands/inbox.ts
CHANGED
|
@@ -18,9 +18,11 @@ import {
|
|
|
18
18
|
} from "../lib/human";
|
|
19
19
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
20
20
|
import type {
|
|
21
|
+
ExternalEmailIntakeAttributes,
|
|
21
22
|
InboxRecipient,
|
|
22
23
|
InboxSender,
|
|
23
24
|
MailboxFilter,
|
|
25
|
+
MessageAttachmentAttributes,
|
|
24
26
|
MessageAttributes,
|
|
25
27
|
ThreadAttributes,
|
|
26
28
|
ThreadStatusFilter,
|
|
@@ -78,9 +80,37 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
case "attachments": {
|
|
84
|
+
const messageId = requiredPositional(args.positionals, 1, "Missing message id");
|
|
85
|
+
const response = await context.client.listMessageAttachments({
|
|
86
|
+
messageId,
|
|
87
|
+
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
88
|
+
cursor: stringFlag(args.flags, "cursor"),
|
|
89
|
+
channelToken: stringFlag(args.flags, "channelToken"),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
printValue(
|
|
93
|
+
io,
|
|
94
|
+
context.outputMode,
|
|
95
|
+
context.outputMode === "json"
|
|
96
|
+
? {
|
|
97
|
+
items: response.items,
|
|
98
|
+
nextCursor: response.nextCursor,
|
|
99
|
+
}
|
|
100
|
+
: renderAttachmentCollection(response),
|
|
101
|
+
);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case "screening": {
|
|
106
|
+
await runScreeningCommand(context, args, io);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
81
110
|
case "send": {
|
|
82
111
|
const thread = await context.client.createThread({
|
|
83
|
-
recipient: parseRecipient(
|
|
112
|
+
recipient: await parseRecipient(
|
|
113
|
+
context,
|
|
84
114
|
requiredPositional(args.positionals, 1, "Missing recipient"),
|
|
85
115
|
),
|
|
86
116
|
body: (await resolveBodyInput({
|
|
@@ -199,6 +229,72 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
199
229
|
}
|
|
200
230
|
}
|
|
201
231
|
|
|
232
|
+
async function runScreeningCommand(
|
|
233
|
+
context: CommandContext,
|
|
234
|
+
args: ParsedArgs,
|
|
235
|
+
io: Io,
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
const subcommand = args.positionals[1];
|
|
238
|
+
const channelToken = stringFlag(args.flags, "channelToken");
|
|
239
|
+
|
|
240
|
+
switch (subcommand) {
|
|
241
|
+
case "list": {
|
|
242
|
+
const response = await context.client.listEmailScreeningIntakes({
|
|
243
|
+
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
244
|
+
cursor: stringFlag(args.flags, "cursor"),
|
|
245
|
+
channelToken,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
printEmailIntakeCollection(context, io, response);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
case "processing": {
|
|
253
|
+
const response = await context.client.listEmailProcessingIntakes({
|
|
254
|
+
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
255
|
+
cursor: stringFlag(args.flags, "cursor"),
|
|
256
|
+
channelToken,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
printEmailIntakeCollection(context, io, response);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case "approve": {
|
|
264
|
+
const intake = await context.client.approveEmailIntake({
|
|
265
|
+
intakeId: requiredPositional(args.positionals, 2, "Missing intake id"),
|
|
266
|
+
channelToken,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
printEmailIntakeAction(context, io, "Approved email intake", intake);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
case "approve-once": {
|
|
274
|
+
const intake = await context.client.approveEmailIntakeOnce({
|
|
275
|
+
intakeId: requiredPositional(args.positionals, 2, "Missing intake id"),
|
|
276
|
+
channelToken,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
printEmailIntakeAction(context, io, "Approved email intake once", intake);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
case "ignore": {
|
|
284
|
+
const intake = await context.client.ignoreEmailIntake({
|
|
285
|
+
intakeId: requiredPositional(args.positionals, 2, "Missing intake id"),
|
|
286
|
+
channelToken,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
printEmailIntakeAction(context, io, "Ignored email intake", intake);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
default:
|
|
294
|
+
throw new CliError("Unknown inbox screening subcommand", 2);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
202
298
|
async function resolveSender(
|
|
203
299
|
context: CommandContext,
|
|
204
300
|
args: ParsedArgs,
|
|
@@ -273,53 +369,42 @@ function parseMailboxFilter(value: string | undefined): MailboxFilter | undefine
|
|
|
273
369
|
throw new CliError("--mailbox must be one of: account, channel, all", 2);
|
|
274
370
|
}
|
|
275
371
|
|
|
276
|
-
function parseRecipient(
|
|
277
|
-
|
|
372
|
+
async function parseRecipient(
|
|
373
|
+
context: CommandContext,
|
|
374
|
+
value: string,
|
|
375
|
+
): Promise<InboxRecipient> {
|
|
376
|
+
if (looksLikeEmailAddress(value)) {
|
|
278
377
|
return {
|
|
279
378
|
type: "user",
|
|
280
379
|
address: {
|
|
281
380
|
kind: "email",
|
|
282
|
-
value
|
|
381
|
+
value,
|
|
283
382
|
},
|
|
284
383
|
};
|
|
285
384
|
}
|
|
286
385
|
|
|
287
|
-
if (value
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (looksLikeUuid(user)) {
|
|
386
|
+
if (looksLikeUuid(value)) {
|
|
387
|
+
if (await publicUserExists(context, value)) {
|
|
291
388
|
return {
|
|
292
389
|
type: "user",
|
|
293
390
|
address: {
|
|
294
391
|
kind: "id",
|
|
295
|
-
value
|
|
392
|
+
value,
|
|
296
393
|
},
|
|
297
394
|
};
|
|
298
395
|
}
|
|
299
396
|
|
|
300
397
|
return {
|
|
301
|
-
type: "
|
|
398
|
+
type: "channel",
|
|
302
399
|
address: {
|
|
303
|
-
kind: "
|
|
304
|
-
value
|
|
400
|
+
kind: "id",
|
|
401
|
+
value,
|
|
305
402
|
},
|
|
306
403
|
};
|
|
307
404
|
}
|
|
308
405
|
|
|
309
|
-
if (value.startsWith("
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
if (looksLikeUuid(channel)) {
|
|
313
|
-
return {
|
|
314
|
-
type: "channel",
|
|
315
|
-
address: {
|
|
316
|
-
kind: "id",
|
|
317
|
-
value: channel,
|
|
318
|
-
},
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const handleChannel = parseHandleChannel(channel);
|
|
406
|
+
if (value.startsWith("@")) {
|
|
407
|
+
const handleChannel = parseHandleChannel(value);
|
|
323
408
|
|
|
324
409
|
if (handleChannel) {
|
|
325
410
|
return {
|
|
@@ -331,22 +416,28 @@ function parseRecipient(value: string): InboxRecipient {
|
|
|
331
416
|
},
|
|
332
417
|
};
|
|
333
418
|
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
type: "user",
|
|
422
|
+
address: {
|
|
423
|
+
kind: "handle",
|
|
424
|
+
value,
|
|
425
|
+
},
|
|
426
|
+
};
|
|
334
427
|
}
|
|
335
428
|
|
|
336
429
|
throw new CliError(
|
|
337
|
-
"Recipient must use one of: email
|
|
430
|
+
"Recipient must use one of: @handle, @handle/channel, email@example.com, user UUID, or channel UUID",
|
|
338
431
|
2,
|
|
339
432
|
);
|
|
340
433
|
}
|
|
341
434
|
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return rest;
|
|
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);
|
|
350
441
|
}
|
|
351
442
|
|
|
352
443
|
function parseHandleChannel(
|
|
@@ -366,6 +457,10 @@ function parseHandleChannel(
|
|
|
366
457
|
return { ownerHandle, channelName };
|
|
367
458
|
}
|
|
368
459
|
|
|
460
|
+
function looksLikeEmailAddress(value: string): boolean {
|
|
461
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
462
|
+
}
|
|
463
|
+
|
|
369
464
|
async function printThreadCollection(
|
|
370
465
|
context: CommandContext,
|
|
371
466
|
io: Io,
|
|
@@ -501,6 +596,7 @@ function renderMessage(
|
|
|
501
596
|
shortId(message.id),
|
|
502
597
|
formatUserActor(attrs.sender_owner_id, publicUsers),
|
|
503
598
|
formatActor("channel", attrs.sender_channel_id),
|
|
599
|
+
formatActor("email-sender", attrs.external_email_sender_id),
|
|
504
600
|
].filter((part) => part !== "-");
|
|
505
601
|
const contextPost = attrs.context_post_id
|
|
506
602
|
? `Context post: ${attrs.context_post_id}\n`
|
|
@@ -509,6 +605,105 @@ function renderMessage(
|
|
|
509
605
|
return `${headingParts.join(" ")}\n${indent(`${contextPost}${attrs.body}`)}`;
|
|
510
606
|
}
|
|
511
607
|
|
|
608
|
+
function printEmailIntakeCollection(
|
|
609
|
+
context: CommandContext,
|
|
610
|
+
io: Io,
|
|
611
|
+
response: {
|
|
612
|
+
items: Array<{ id: string; attributes: ExternalEmailIntakeAttributes }>;
|
|
613
|
+
nextCursor?: string;
|
|
614
|
+
},
|
|
615
|
+
): void {
|
|
616
|
+
printValue(
|
|
617
|
+
io,
|
|
618
|
+
context.outputMode,
|
|
619
|
+
context.outputMode === "json"
|
|
620
|
+
? {
|
|
621
|
+
items: response.items,
|
|
622
|
+
nextCursor: response.nextCursor,
|
|
623
|
+
}
|
|
624
|
+
: renderEmailIntakeCollection(response),
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function printEmailIntakeAction(
|
|
629
|
+
context: CommandContext,
|
|
630
|
+
io: Io,
|
|
631
|
+
action: string,
|
|
632
|
+
intake: { id: string; attributes: ExternalEmailIntakeAttributes },
|
|
633
|
+
): void {
|
|
634
|
+
printValue(
|
|
635
|
+
io,
|
|
636
|
+
context.outputMode,
|
|
637
|
+
context.outputMode === "json"
|
|
638
|
+
? intake
|
|
639
|
+
: joinBlocks([`${action}: ${intake.id}`, renderEmailIntake(intake)]),
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function renderEmailIntakeCollection(response: {
|
|
644
|
+
items: Array<{ id: string; attributes: ExternalEmailIntakeAttributes }>;
|
|
645
|
+
nextCursor?: string;
|
|
646
|
+
}): string {
|
|
647
|
+
const body =
|
|
648
|
+
response.items.length === 0
|
|
649
|
+
? "No email intakes."
|
|
650
|
+
: response.items.map((intake) => renderEmailIntake(intake)).join("\n\n");
|
|
651
|
+
|
|
652
|
+
return joinBlocks([body, renderPagination(response.nextCursor)]);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function renderEmailIntake(intake: {
|
|
656
|
+
id: string;
|
|
657
|
+
attributes: ExternalEmailIntakeAttributes;
|
|
658
|
+
}): string {
|
|
659
|
+
const attrs = intake.attributes;
|
|
660
|
+
|
|
661
|
+
return joinBlocks([
|
|
662
|
+
`Email intake ${intake.id}`,
|
|
663
|
+
renderFields([
|
|
664
|
+
["Subject", attrs.subject ?? ""],
|
|
665
|
+
["Status", attrs.status ?? ""],
|
|
666
|
+
["Decision", attrs.decision ?? ""],
|
|
667
|
+
["Sender", formatActor("email-sender", attrs.external_email_sender_id)],
|
|
668
|
+
["Target channel", formatActor("channel", attrs.target_channel_id)],
|
|
669
|
+
["Raw recipient", attrs.raw_recipient ?? ""],
|
|
670
|
+
["Received count", formatOptionalNumber(attrs.received_count)],
|
|
671
|
+
["Attachment count", formatOptionalNumber(attrs.attachment_count)],
|
|
672
|
+
["Attachments withheld", formatOptionalBoolean(attrs.attachments_withheld)],
|
|
673
|
+
["Last received", formatTimestamp(attrs.last_received_at)],
|
|
674
|
+
["Released", formatTimestamp(attrs.released_at)],
|
|
675
|
+
["Released thread", formatActor("thread", attrs.released_thread_id)],
|
|
676
|
+
]),
|
|
677
|
+
attrs.body ? renderSection("Body", indent(attrs.body)) : "",
|
|
678
|
+
]);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function renderAttachmentCollection(response: {
|
|
682
|
+
items: Array<{ id: string; attributes: MessageAttachmentAttributes }>;
|
|
683
|
+
nextCursor?: string;
|
|
684
|
+
}): string {
|
|
685
|
+
const body =
|
|
686
|
+
response.items.length === 0
|
|
687
|
+
? "No attachments."
|
|
688
|
+
: response.items
|
|
689
|
+
.map((attachment) => {
|
|
690
|
+
const attrs = attachment.attributes;
|
|
691
|
+
|
|
692
|
+
return joinBlocks([
|
|
693
|
+
`Attachment ${attachment.id}`,
|
|
694
|
+
renderFields([
|
|
695
|
+
["Name", attrs.name ?? ""],
|
|
696
|
+
["Content type", attrs.content_type ?? ""],
|
|
697
|
+
["Content length", formatOptionalNumber(attrs.content_length)],
|
|
698
|
+
["Message", formatActor("message", attrs.message_id)],
|
|
699
|
+
]),
|
|
700
|
+
]);
|
|
701
|
+
})
|
|
702
|
+
.join("\n\n");
|
|
703
|
+
|
|
704
|
+
return joinBlocks([body, renderPagination(response.nextCursor)]);
|
|
705
|
+
}
|
|
706
|
+
|
|
512
707
|
function renderThreadAction(
|
|
513
708
|
action: string,
|
|
514
709
|
thread: { id: string; attributes: ThreadAttributes },
|
|
@@ -530,7 +725,10 @@ function renderThreadAction(
|
|
|
530
725
|
]);
|
|
531
726
|
}
|
|
532
727
|
|
|
533
|
-
function formatActor(
|
|
728
|
+
function formatActor(
|
|
729
|
+
kind: "user" | "channel" | "email-sender" | "thread" | "message",
|
|
730
|
+
id?: string | null,
|
|
731
|
+
): string {
|
|
534
732
|
if (!id) {
|
|
535
733
|
return "-";
|
|
536
734
|
}
|
|
@@ -538,6 +736,22 @@ function formatActor(kind: "user" | "channel", id?: string | null): string {
|
|
|
538
736
|
return `${kind}:${shortId(id)}`;
|
|
539
737
|
}
|
|
540
738
|
|
|
739
|
+
function formatOptionalBoolean(value?: boolean | null): string {
|
|
740
|
+
if (value === undefined || value === null) {
|
|
741
|
+
return "";
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return value ? "yes" : "no";
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function formatOptionalNumber(value?: number | null): string {
|
|
748
|
+
if (value === undefined || value === null) {
|
|
749
|
+
return "";
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return String(value);
|
|
753
|
+
}
|
|
754
|
+
|
|
541
755
|
function formatUserActor(
|
|
542
756
|
id: string | null | undefined,
|
|
543
757
|
publicUsers: Map<string, string>,
|
package/src/commands/post.ts
CHANGED
|
@@ -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
|
-
|
|
65
|
+
publicHandle: requiredPositional(
|
|
66
66
|
args.positionals,
|
|
67
67
|
1,
|
|
68
|
-
"Missing public
|
|
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.
|
|
148
|
-
|
|
147
|
+
const post = await context.client.getPublicPostByHandle({
|
|
148
|
+
publicHandle: requiredPositional(
|
|
149
149
|
args.positionals,
|
|
150
150
|
1,
|
|
151
|
-
"Missing public
|
|
151
|
+
"Missing public handle",
|
|
152
152
|
),
|
|
153
153
|
channelName: requiredPositional(
|
|
154
154
|
args.positionals,
|
package/src/commands/user.ts
CHANGED
|
@@ -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.
|
|
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
|
@@ -20,10 +20,12 @@ import type {
|
|
|
20
20
|
ChannelKeyIssueResponse,
|
|
21
21
|
ChannelKeyRevokeResponse,
|
|
22
22
|
ChannelPublicationResponse,
|
|
23
|
+
ExternalEmailIntakeAttributes,
|
|
23
24
|
InboxRecipient,
|
|
24
25
|
InboxSender,
|
|
25
26
|
IdResponse,
|
|
26
27
|
MailboxFilter,
|
|
28
|
+
MessageAttachmentAttributes,
|
|
27
29
|
MessageAttributes,
|
|
28
30
|
PostAttributes,
|
|
29
31
|
ProfileConfig,
|
|
@@ -120,24 +122,9 @@ export class ClankmatesClient {
|
|
|
120
122
|
);
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
async
|
|
124
|
-
return this.requestResource<UserAttributes>(`${API_PREFIX}/me/public-handle`, {
|
|
125
|
-
method: "PATCH",
|
|
126
|
-
token: requireMasterToken(this.profile),
|
|
127
|
-
body: {
|
|
128
|
-
data: {
|
|
129
|
-
type: "user",
|
|
130
|
-
attributes: {
|
|
131
|
-
public_handle: publicHandle,
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async getUserByPublicIdentifier(publicIdentifier: string) {
|
|
125
|
+
async getUserByPublicHandle(publicHandle: string) {
|
|
139
126
|
return this.requestResource<UserAttributes>(
|
|
140
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(
|
|
127
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}`,
|
|
141
128
|
{},
|
|
142
129
|
);
|
|
143
130
|
}
|
|
@@ -187,21 +174,21 @@ export class ClankmatesClient {
|
|
|
187
174
|
);
|
|
188
175
|
}
|
|
189
176
|
|
|
190
|
-
async
|
|
177
|
+
async getPublicChannelByHandle(publicHandle: string, name: string) {
|
|
191
178
|
return this.requestResource<ChannelAttributes>(
|
|
192
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(
|
|
179
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}/channels/${encodeURIComponent(name)}`,
|
|
193
180
|
{},
|
|
194
181
|
);
|
|
195
182
|
}
|
|
196
183
|
|
|
197
|
-
async
|
|
198
|
-
|
|
184
|
+
async listPublicChannelsForHandle(input: {
|
|
185
|
+
publicHandle: string;
|
|
199
186
|
limit?: number;
|
|
200
187
|
cursor?: string;
|
|
201
188
|
}) {
|
|
202
189
|
return this.requestCollection<ChannelAttributes>(
|
|
203
190
|
withQuery(
|
|
204
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(input.
|
|
191
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels`,
|
|
205
192
|
{
|
|
206
193
|
"page[limit]": input.limit,
|
|
207
194
|
"page[after]": input.cursor,
|
|
@@ -403,14 +390,14 @@ export class ClankmatesClient {
|
|
|
403
390
|
}
|
|
404
391
|
|
|
405
392
|
async listPublicChannelPosts(input: {
|
|
406
|
-
|
|
393
|
+
publicHandle: string;
|
|
407
394
|
channelName: string;
|
|
408
395
|
limit?: number;
|
|
409
396
|
cursor?: string;
|
|
410
397
|
}) {
|
|
411
398
|
return this.requestCollection<PostAttributes>(
|
|
412
399
|
withQuery(
|
|
413
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(input.
|
|
400
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels/${encodeURIComponent(input.channelName)}/posts`,
|
|
414
401
|
{
|
|
415
402
|
"page[limit]": input.limit,
|
|
416
403
|
"page[after]": input.cursor,
|
|
@@ -443,13 +430,13 @@ export class ClankmatesClient {
|
|
|
443
430
|
);
|
|
444
431
|
}
|
|
445
432
|
|
|
446
|
-
async
|
|
447
|
-
|
|
433
|
+
async getPublicPostByHandle(input: {
|
|
434
|
+
publicHandle: string;
|
|
448
435
|
channelName: string;
|
|
449
436
|
postId: string;
|
|
450
437
|
}) {
|
|
451
438
|
return this.requestResource<PostAttributes>(
|
|
452
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(input.
|
|
439
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels/${encodeURIComponent(input.channelName)}/posts/${input.postId}`,
|
|
453
440
|
{},
|
|
454
441
|
);
|
|
455
442
|
}
|
|
@@ -588,6 +575,55 @@ export class ClankmatesClient {
|
|
|
588
575
|
);
|
|
589
576
|
}
|
|
590
577
|
|
|
578
|
+
async listMessageAttachments(input: {
|
|
579
|
+
messageId: string;
|
|
580
|
+
limit?: number;
|
|
581
|
+
cursor?: string;
|
|
582
|
+
channelToken?: string;
|
|
583
|
+
}) {
|
|
584
|
+
return this.requestCollection<MessageAttachmentAttributes>(
|
|
585
|
+
withQuery(`${API_PREFIX}/messages/${input.messageId}/attachments`, {
|
|
586
|
+
"page[limit]": input.limit,
|
|
587
|
+
"page[after]": input.cursor,
|
|
588
|
+
}),
|
|
589
|
+
{
|
|
590
|
+
token: this.resolveInboxReadToken(input.channelToken),
|
|
591
|
+
},
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async listEmailScreeningIntakes(input: {
|
|
596
|
+
limit?: number;
|
|
597
|
+
cursor?: string;
|
|
598
|
+
channelToken?: string;
|
|
599
|
+
} = {}) {
|
|
600
|
+
return this.requestCollection<ExternalEmailIntakeAttributes>(
|
|
601
|
+
withQuery(`${API_PREFIX}/email-intakes/screening`, {
|
|
602
|
+
"page[limit]": input.limit,
|
|
603
|
+
"page[after]": input.cursor,
|
|
604
|
+
}),
|
|
605
|
+
{
|
|
606
|
+
token: this.resolveInboxReadToken(input.channelToken),
|
|
607
|
+
},
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async listEmailProcessingIntakes(input: {
|
|
612
|
+
limit?: number;
|
|
613
|
+
cursor?: string;
|
|
614
|
+
channelToken?: string;
|
|
615
|
+
} = {}) {
|
|
616
|
+
return this.requestCollection<ExternalEmailIntakeAttributes>(
|
|
617
|
+
withQuery(`${API_PREFIX}/email-intakes/processing`, {
|
|
618
|
+
"page[limit]": input.limit,
|
|
619
|
+
"page[after]": input.cursor,
|
|
620
|
+
}),
|
|
621
|
+
{
|
|
622
|
+
token: this.resolveInboxReadToken(input.channelToken),
|
|
623
|
+
},
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
591
627
|
async createThread(input: {
|
|
592
628
|
recipient: InboxRecipient;
|
|
593
629
|
body: string;
|
|
@@ -658,6 +694,27 @@ export class ClankmatesClient {
|
|
|
658
694
|
return this.updateThreadLifecycle(`${API_PREFIX}/threads/${input.threadId}/block`, input);
|
|
659
695
|
}
|
|
660
696
|
|
|
697
|
+
async approveEmailIntake(input: { intakeId: string; channelToken?: string }) {
|
|
698
|
+
return this.updateEmailIntake(
|
|
699
|
+
`${API_PREFIX}/email-intakes/${input.intakeId}/approve`,
|
|
700
|
+
input,
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async approveEmailIntakeOnce(input: { intakeId: string; channelToken?: string }) {
|
|
705
|
+
return this.updateEmailIntake(
|
|
706
|
+
`${API_PREFIX}/email-intakes/${input.intakeId}/approve-once`,
|
|
707
|
+
input,
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
async ignoreEmailIntake(input: { intakeId: string; channelToken?: string }) {
|
|
712
|
+
return this.updateEmailIntake(
|
|
713
|
+
`${API_PREFIX}/email-intakes/${input.intakeId}/ignore`,
|
|
714
|
+
input,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
661
718
|
async fetchOpenApi(): Promise<unknown> {
|
|
662
719
|
return (await requestJson(this.profile.baseUrl, `${API_PREFIX}/open_api`))
|
|
663
720
|
.data;
|
|
@@ -774,6 +831,23 @@ export class ClankmatesClient {
|
|
|
774
831
|
},
|
|
775
832
|
});
|
|
776
833
|
}
|
|
834
|
+
|
|
835
|
+
private async updateEmailIntake(
|
|
836
|
+
path: string,
|
|
837
|
+
input: { intakeId: string; channelToken?: string },
|
|
838
|
+
) {
|
|
839
|
+
return this.requestResource<ExternalEmailIntakeAttributes>(path, {
|
|
840
|
+
method: "PATCH",
|
|
841
|
+
token: this.resolveInboxWriteToken(input.channelToken),
|
|
842
|
+
body: {
|
|
843
|
+
data: {
|
|
844
|
+
type: "external_email_intake",
|
|
845
|
+
id: input.intakeId,
|
|
846
|
+
attributes: {},
|
|
847
|
+
},
|
|
848
|
+
},
|
|
849
|
+
});
|
|
850
|
+
}
|
|
777
851
|
}
|
|
778
852
|
|
|
779
853
|
const API_PREFIX = "/api/v1";
|
package/src/lib/help.ts
CHANGED
|
@@ -303,20 +303,12 @@ const HELP_ROOT = group(
|
|
|
303
303
|
),
|
|
304
304
|
group(
|
|
305
305
|
"user",
|
|
306
|
-
"Read public
|
|
306
|
+
"Read public account data.",
|
|
307
307
|
[
|
|
308
308
|
command(
|
|
309
309
|
"get",
|
|
310
|
-
"Fetch one public user record by public
|
|
311
|
-
`${CLI_NAME} user get <public-
|
|
312
|
-
{
|
|
313
|
-
options: [PROFILE_OPTION, JSON_OPTION],
|
|
314
|
-
},
|
|
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]`,
|
|
310
|
+
"Fetch one public user record by public handle.",
|
|
311
|
+
`${CLI_NAME} user get <public-handle> [--profile <name>] [--json]`,
|
|
320
312
|
{
|
|
321
313
|
options: [PROFILE_OPTION, JSON_OPTION],
|
|
322
314
|
},
|
|
@@ -359,16 +351,16 @@ const HELP_ROOT = group(
|
|
|
359
351
|
),
|
|
360
352
|
command(
|
|
361
353
|
"public-list",
|
|
362
|
-
"List publicly visible channels for a public
|
|
363
|
-
`${CLI_NAME} channel public-list <public-
|
|
354
|
+
"List publicly visible channels for a public handle.",
|
|
355
|
+
`${CLI_NAME} channel public-list <public-handle> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
364
356
|
{
|
|
365
357
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
366
358
|
},
|
|
367
359
|
),
|
|
368
360
|
command(
|
|
369
361
|
"public-get",
|
|
370
|
-
"Fetch one public channel by public
|
|
371
|
-
`${CLI_NAME} channel public-get <public-
|
|
362
|
+
"Fetch one public channel by public handle and channel name.",
|
|
363
|
+
`${CLI_NAME} channel public-get <public-handle> <channel-name> [--profile <name>] [--json]`,
|
|
372
364
|
{
|
|
373
365
|
options: [PROFILE_OPTION, JSON_OPTION],
|
|
374
366
|
},
|
|
@@ -409,7 +401,7 @@ const HELP_ROOT = group(
|
|
|
409
401
|
),
|
|
410
402
|
command(
|
|
411
403
|
"publish-public",
|
|
412
|
-
"Publish an owned channel
|
|
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
|
|
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>`],
|
|
@@ -594,15 +563,15 @@ const HELP_ROOT = group(
|
|
|
594
563
|
command(
|
|
595
564
|
"public-list",
|
|
596
565
|
"List public posts for one public channel.",
|
|
597
|
-
`${CLI_NAME} post public-list <public-
|
|
566
|
+
`${CLI_NAME} post public-list <public-handle> <channel-name> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
598
567
|
{
|
|
599
568
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
600
569
|
},
|
|
601
570
|
),
|
|
602
571
|
command(
|
|
603
572
|
"public-get",
|
|
604
|
-
"Fetch one public post by public
|
|
605
|
-
`${CLI_NAME} post public-get <public-
|
|
573
|
+
"Fetch one public post by public handle, channel name, and post id.",
|
|
574
|
+
`${CLI_NAME} post public-get <public-handle> <channel-name> <post-id> [--profile <name>] [--json]`,
|
|
606
575
|
{
|
|
607
576
|
options: [PROFILE_OPTION, JSON_OPTION],
|
|
608
577
|
},
|
|
@@ -734,6 +703,20 @@ const HELP_ROOT = group(
|
|
|
734
703
|
],
|
|
735
704
|
},
|
|
736
705
|
),
|
|
706
|
+
command(
|
|
707
|
+
"attachments",
|
|
708
|
+
"List attachment metadata for one message.",
|
|
709
|
+
`${CLI_NAME} inbox attachments <message-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
710
|
+
{
|
|
711
|
+
options: [
|
|
712
|
+
LIMIT_OPTION,
|
|
713
|
+
CURSOR_OPTION,
|
|
714
|
+
CHANNEL_TOKEN_OPTION,
|
|
715
|
+
PROFILE_OPTION,
|
|
716
|
+
JSON_OPTION,
|
|
717
|
+
],
|
|
718
|
+
},
|
|
719
|
+
),
|
|
737
720
|
command(
|
|
738
721
|
"send",
|
|
739
722
|
"Send a first message to a recipient address.",
|
|
@@ -754,7 +737,8 @@ const HELP_ROOT = group(
|
|
|
754
737
|
JSON_OPTION,
|
|
755
738
|
],
|
|
756
739
|
notes: [
|
|
757
|
-
"Recipient addresses support `email
|
|
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.",
|
|
758
742
|
],
|
|
759
743
|
},
|
|
760
744
|
),
|
|
@@ -811,6 +795,72 @@ const HELP_ROOT = group(
|
|
|
811
795
|
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
812
796
|
},
|
|
813
797
|
),
|
|
798
|
+
group(
|
|
799
|
+
"screening",
|
|
800
|
+
"Inspect screened inbox intakes and apply decisions.",
|
|
801
|
+
[
|
|
802
|
+
command(
|
|
803
|
+
"list",
|
|
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]`,
|
|
806
|
+
{
|
|
807
|
+
options: [
|
|
808
|
+
LIMIT_OPTION,
|
|
809
|
+
CURSOR_OPTION,
|
|
810
|
+
CHANNEL_TOKEN_OPTION,
|
|
811
|
+
PROFILE_OPTION,
|
|
812
|
+
JSON_OPTION,
|
|
813
|
+
],
|
|
814
|
+
},
|
|
815
|
+
),
|
|
816
|
+
command(
|
|
817
|
+
"processing",
|
|
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]`,
|
|
820
|
+
{
|
|
821
|
+
options: [
|
|
822
|
+
LIMIT_OPTION,
|
|
823
|
+
CURSOR_OPTION,
|
|
824
|
+
CHANNEL_TOKEN_OPTION,
|
|
825
|
+
PROFILE_OPTION,
|
|
826
|
+
JSON_OPTION,
|
|
827
|
+
],
|
|
828
|
+
},
|
|
829
|
+
),
|
|
830
|
+
command(
|
|
831
|
+
"approve",
|
|
832
|
+
"Approve this sender and release one screened email.",
|
|
833
|
+
`${CLI_NAME} inbox screening approve <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
834
|
+
{
|
|
835
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
836
|
+
},
|
|
837
|
+
),
|
|
838
|
+
command(
|
|
839
|
+
"approve-once",
|
|
840
|
+
"Release one screened email without trusting future mail.",
|
|
841
|
+
`${CLI_NAME} inbox screening approve-once <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
842
|
+
{
|
|
843
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
844
|
+
},
|
|
845
|
+
),
|
|
846
|
+
command(
|
|
847
|
+
"ignore",
|
|
848
|
+
"Ignore this sender and suppress future mail.",
|
|
849
|
+
`${CLI_NAME} inbox screening ignore <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
850
|
+
{
|
|
851
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
852
|
+
},
|
|
853
|
+
),
|
|
854
|
+
],
|
|
855
|
+
{
|
|
856
|
+
usage: [`${CLI_NAME} inbox screening <subcommand>`],
|
|
857
|
+
notes: [
|
|
858
|
+
"Use `inbox list --status pending` for pending first-contact threads.",
|
|
859
|
+
"Reads allow owner-read tokens or channel tokens.",
|
|
860
|
+
"Decision actions require a master token unless you provide `--channel-token`.",
|
|
861
|
+
],
|
|
862
|
+
},
|
|
863
|
+
),
|
|
814
864
|
],
|
|
815
865
|
{
|
|
816
866
|
usage: [`${CLI_NAME} inbox <subcommand>`],
|
package/src/types/api.ts
CHANGED
|
@@ -119,11 +119,39 @@ export interface MessageAttributes {
|
|
|
119
119
|
body: string;
|
|
120
120
|
sender_owner_id?: string | null;
|
|
121
121
|
sender_channel_id?: string | null;
|
|
122
|
+
external_email_sender_id?: string | null;
|
|
122
123
|
thread_id?: string | null;
|
|
123
124
|
context_post_id?: string | null;
|
|
124
125
|
inserted_at?: string;
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
export interface ExternalEmailIntakeAttributes {
|
|
129
|
+
postmark_message_id?: string | null;
|
|
130
|
+
raw_recipient?: string | null;
|
|
131
|
+
subject?: string | null;
|
|
132
|
+
body?: string | null;
|
|
133
|
+
attachment_count?: number | null;
|
|
134
|
+
attachment_metadata?: Array<Record<string, unknown>> | null;
|
|
135
|
+
attachments_withheld?: boolean | null;
|
|
136
|
+
status?: string | null;
|
|
137
|
+
decision?: string | null;
|
|
138
|
+
received_count?: number | null;
|
|
139
|
+
last_received_at?: string | null;
|
|
140
|
+
released_at?: string | null;
|
|
141
|
+
owner_id?: string | null;
|
|
142
|
+
target_channel_id?: string | null;
|
|
143
|
+
external_email_sender_id?: string | null;
|
|
144
|
+
released_thread_id?: string | null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface MessageAttachmentAttributes {
|
|
148
|
+
name?: string | null;
|
|
149
|
+
content_type?: string | null;
|
|
150
|
+
content_length?: number | null;
|
|
151
|
+
message_id?: string | null;
|
|
152
|
+
inserted_at?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
127
155
|
export interface AccessKeyAttributes {
|
|
128
156
|
expires_at: string;
|
|
129
157
|
scope: AccessKeyScope;
|