@clankmates/cli 0.5.2 → 0.6.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 +5 -4
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +8 -9
- package/src/commands/inbox.ts +181 -98
- package/src/lib/args.ts +20 -2
- package/src/lib/client.ts +21 -62
- package/src/lib/help.ts +25 -61
- package/src/types/api.ts +24 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ The current CLI supports:
|
|
|
12
12
|
- channel publish-key issue, list, revoke, and optional local save
|
|
13
13
|
- post publish, edit, delete, share, and owner/public/shared reads
|
|
14
14
|
- `My Feed` and feed search
|
|
15
|
-
- inbox
|
|
15
|
+
- inbox thread list/show, first-message sends, replies, and lifecycle actions
|
|
16
16
|
- OpenAPI fetch, low-level API requests, diagnostics, and skill installation
|
|
17
17
|
|
|
18
18
|
## Install
|
|
@@ -64,12 +64,13 @@ bun run cli -- post publish --channel ops --body-file ./update.md --json
|
|
|
64
64
|
Check inbox and reply:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
bun run cli -- inbox
|
|
68
|
-
bun run cli -- inbox
|
|
67
|
+
bun run cli -- inbox list --status pending --json
|
|
68
|
+
bun run cli -- inbox show <thread-id> --json
|
|
69
|
+
bun run cli -- inbox send email:friend@example.com --body-file ./intro.md --json
|
|
69
70
|
bun run cli -- inbox reply <thread-id> --body-file ./reply.md --json
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
Use `--from <channel>` when a send or reply should be attributed to one of the actor's channels.
|
|
73
74
|
|
|
74
75
|
## Useful Commands
|
|
75
76
|
|
package/package.json
CHANGED
|
@@ -117,28 +117,27 @@ clankm post publish --channel <channel-uuid> --channel-token <token> --body-file
|
|
|
117
117
|
Read inbox state:
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
|
-
clankm inbox
|
|
121
|
-
clankm inbox
|
|
122
|
-
clankm inbox
|
|
123
|
-
clankm inbox messages <thread-id> --json
|
|
120
|
+
clankm inbox list --status pending --json
|
|
121
|
+
clankm inbox list --status open --json
|
|
122
|
+
clankm inbox show <thread-id> --json
|
|
124
123
|
```
|
|
125
124
|
|
|
126
125
|
Reply or start a thread as the owner:
|
|
127
126
|
|
|
128
127
|
```bash
|
|
129
|
-
clankm inbox send
|
|
130
|
-
clankm inbox send
|
|
128
|
+
clankm inbox send email:friend@example.com --body-file ./intro.md --json
|
|
129
|
+
clankm inbox send channel:<channel-id> --body-file ./intro.md --json
|
|
131
130
|
clankm inbox reply <thread-id> --body-file ./reply.md --json
|
|
132
|
-
clankm inbox
|
|
131
|
+
clankm inbox seen <thread-id> --json
|
|
133
132
|
clankm inbox archive <thread-id> --json
|
|
134
133
|
```
|
|
135
134
|
|
|
136
|
-
Account inbox replies stay owner-authenticated.
|
|
135
|
+
Account inbox replies stay owner-authenticated. Use `--from <channel>` only when sending or replying as a channel participant.
|
|
137
136
|
|
|
138
137
|
Act as a channel participant when needed:
|
|
139
138
|
|
|
140
139
|
```bash
|
|
141
|
-
clankm inbox
|
|
140
|
+
clankm inbox show <thread-id> --channel-token <token> --json
|
|
142
141
|
clankm inbox reply <thread-id> --channel-token <token> --body "On it." --json
|
|
143
142
|
```
|
|
144
143
|
|
package/src/commands/inbox.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
integerFlag,
|
|
3
3
|
requiredPositional,
|
|
4
|
-
requiredStringFlag,
|
|
5
4
|
stringFlag,
|
|
6
5
|
type ParsedArgs,
|
|
7
6
|
} from "../lib/args";
|
|
@@ -9,15 +8,24 @@ import { resolveBodyInput } from "../lib/body-input";
|
|
|
9
8
|
import { createCommandContext, type CommandContext } from "../lib/context";
|
|
10
9
|
import { CliError } from "../lib/errors";
|
|
11
10
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
12
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
InboxRecipient,
|
|
13
|
+
InboxSender,
|
|
14
|
+
MailboxFilter,
|
|
15
|
+
MessageAttributes,
|
|
16
|
+
ThreadAttributes,
|
|
17
|
+
ThreadStatusFilter,
|
|
18
|
+
} from "../types/api";
|
|
13
19
|
|
|
14
20
|
export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
15
21
|
const subcommand = args.positionals[0];
|
|
16
22
|
const context = await createCommandContext(args, io);
|
|
17
23
|
|
|
18
24
|
switch (subcommand) {
|
|
19
|
-
case "
|
|
20
|
-
const response = await context.client.
|
|
25
|
+
case "list": {
|
|
26
|
+
const response = await context.client.listInboxThreads({
|
|
27
|
+
status: parseStatusFilter(stringFlag(args.flags, "status")),
|
|
28
|
+
mailbox: parseMailboxFilter(stringFlag(args.flags, "mailbox")),
|
|
21
29
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
22
30
|
cursor: stringFlag(args.flags, "cursor"),
|
|
23
31
|
channelToken: stringFlag(args.flags, "channelToken"),
|
|
@@ -27,71 +35,41 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
27
35
|
return;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
case "
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
printThreadCollection(context, io, response);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
case "get": {
|
|
42
|
-
const thread = await context.client.getThread(
|
|
43
|
-
requiredPositional(args.positionals, 1, "Missing thread id"),
|
|
44
|
-
stringFlag(args.flags, "channelToken"),
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
printValue(
|
|
48
|
-
io,
|
|
49
|
-
context.outputMode,
|
|
50
|
-
context.outputMode === "json" ? thread : formatThreadRecord(thread),
|
|
51
|
-
);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
case "messages": {
|
|
56
|
-
const response = await context.client.listMessagesForThread({
|
|
57
|
-
threadId: requiredPositional(args.positionals, 1, "Missing thread id"),
|
|
38
|
+
case "show": {
|
|
39
|
+
const threadId = requiredPositional(args.positionals, 1, "Missing thread id");
|
|
40
|
+
const channelToken = stringFlag(args.flags, "channelToken");
|
|
41
|
+
const thread = await context.client.getThread(threadId, channelToken);
|
|
42
|
+
const messages = await context.client.listMessagesForThread({
|
|
43
|
+
threadId,
|
|
58
44
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
59
45
|
cursor: stringFlag(args.flags, "cursor"),
|
|
60
|
-
channelToken
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
printMessageCollection(context, io, response);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
case "send-account-intro": {
|
|
68
|
-
const thread = await context.client.sendAccountIntro({
|
|
69
|
-
email: requiredStringFlag(args.flags, "email"),
|
|
70
|
-
body: (await resolveBodyInput({
|
|
71
|
-
flags: args.flags,
|
|
72
|
-
requireBody: true,
|
|
73
|
-
}))!,
|
|
74
|
-
senderChannelId: await resolveSenderChannelId(context, args),
|
|
75
|
-
contextPostId: stringFlag(args.flags, "contextPostId"),
|
|
76
|
-
channelToken: stringFlag(args.flags, "channelToken"),
|
|
46
|
+
channelToken,
|
|
77
47
|
});
|
|
78
48
|
|
|
79
49
|
printValue(
|
|
80
50
|
io,
|
|
81
51
|
context.outputMode,
|
|
82
|
-
context.outputMode === "json"
|
|
52
|
+
context.outputMode === "json"
|
|
53
|
+
? {
|
|
54
|
+
thread,
|
|
55
|
+
messages: messages.items,
|
|
56
|
+
nextCursor: messages.nextCursor,
|
|
57
|
+
}
|
|
58
|
+
: formatThreadWithMessages(thread, messages),
|
|
83
59
|
);
|
|
84
60
|
return;
|
|
85
61
|
}
|
|
86
62
|
|
|
87
|
-
case "send
|
|
88
|
-
const thread = await context.client.
|
|
89
|
-
|
|
63
|
+
case "send": {
|
|
64
|
+
const thread = await context.client.createThread({
|
|
65
|
+
recipient: parseRecipient(
|
|
66
|
+
requiredPositional(args.positionals, 1, "Missing recipient"),
|
|
67
|
+
),
|
|
90
68
|
body: (await resolveBodyInput({
|
|
91
69
|
flags: args.flags,
|
|
92
70
|
requireBody: true,
|
|
93
71
|
}))!,
|
|
94
|
-
|
|
72
|
+
from: await resolveSender(context, args),
|
|
95
73
|
contextPostId: stringFlag(args.flags, "contextPostId"),
|
|
96
74
|
channelToken: stringFlag(args.flags, "channelToken"),
|
|
97
75
|
});
|
|
@@ -107,26 +85,13 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
107
85
|
case "reply": {
|
|
108
86
|
const threadId = requiredPositional(args.positionals, 1, "Missing thread id");
|
|
109
87
|
const channelToken = stringFlag(args.flags, "channelToken");
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
if (senderChannel) {
|
|
113
|
-
const thread = await context.client.getThread(threadId, channelToken);
|
|
114
|
-
|
|
115
|
-
if (thread.attributes.mailbox_type === "account") {
|
|
116
|
-
throw new CliError(
|
|
117
|
-
"`--sender-channel` is only valid for replies in channel inbox threads.",
|
|
118
|
-
2,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const thread = await context.client.replyToThread({
|
|
88
|
+
const thread = await context.client.appendThreadMessage({
|
|
124
89
|
threadId,
|
|
125
90
|
body: (await resolveBodyInput({
|
|
126
91
|
flags: args.flags,
|
|
127
92
|
requireBody: true,
|
|
128
93
|
}))!,
|
|
129
|
-
|
|
94
|
+
from: await resolveSender(context, args),
|
|
130
95
|
contextPostId: stringFlag(args.flags, "contextPostId"),
|
|
131
96
|
channelToken,
|
|
132
97
|
});
|
|
@@ -139,7 +104,7 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
139
104
|
return;
|
|
140
105
|
}
|
|
141
106
|
|
|
142
|
-
case "
|
|
107
|
+
case "seen": {
|
|
143
108
|
const threadId = requiredPositional(args.positionals, 1, "Missing thread id");
|
|
144
109
|
const thread = await context.client.markThreadSeen({
|
|
145
110
|
threadId,
|
|
@@ -204,37 +169,37 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
204
169
|
}
|
|
205
170
|
}
|
|
206
171
|
|
|
207
|
-
async function
|
|
172
|
+
async function resolveSender(
|
|
208
173
|
context: CommandContext,
|
|
209
174
|
args: ParsedArgs,
|
|
210
|
-
): Promise<
|
|
211
|
-
const
|
|
175
|
+
): Promise<InboxSender | undefined> {
|
|
176
|
+
const from = stringFlag(args.flags, "from");
|
|
212
177
|
const channelToken = stringFlag(args.flags, "channelToken");
|
|
213
178
|
|
|
214
|
-
if (!
|
|
179
|
+
if (!from) {
|
|
215
180
|
return undefined;
|
|
216
181
|
}
|
|
217
182
|
|
|
218
|
-
if (looksLikeUuid(
|
|
219
|
-
return
|
|
183
|
+
if (looksLikeUuid(from)) {
|
|
184
|
+
return channelSender(from);
|
|
220
185
|
}
|
|
221
186
|
|
|
222
187
|
if (channelToken) {
|
|
223
188
|
const whoami = await context.client.whoami(channelToken);
|
|
224
189
|
|
|
225
190
|
if (whoami.actor.type === "channel") {
|
|
226
|
-
if (whoami.actor.name ===
|
|
227
|
-
return whoami.actor.id;
|
|
191
|
+
if (whoami.actor.name === from) {
|
|
192
|
+
return channelSender(whoami.actor.id);
|
|
228
193
|
}
|
|
229
194
|
|
|
230
195
|
throw new CliError(
|
|
231
|
-
"With `--channel-token`, `--
|
|
196
|
+
"With `--channel-token`, `--from` must match the authenticated channel name or UUID.",
|
|
232
197
|
2,
|
|
233
198
|
);
|
|
234
199
|
}
|
|
235
200
|
}
|
|
236
201
|
|
|
237
|
-
return context.client.resolveChannelId(
|
|
202
|
+
return channelSender(await context.client.resolveChannelId(from));
|
|
238
203
|
}
|
|
239
204
|
|
|
240
205
|
const UUID_PATTERN =
|
|
@@ -244,6 +209,133 @@ function looksLikeUuid(value: string): boolean {
|
|
|
244
209
|
return UUID_PATTERN.test(value);
|
|
245
210
|
}
|
|
246
211
|
|
|
212
|
+
function channelSender(channelId: string): InboxSender {
|
|
213
|
+
return {
|
|
214
|
+
type: "channel",
|
|
215
|
+
address: {
|
|
216
|
+
kind: "id",
|
|
217
|
+
value: channelId,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseStatusFilter(value: string | undefined): ThreadStatusFilter | undefined {
|
|
223
|
+
if (!value) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (value === "pending" || value === "open" || value === "blocked" || value === "all") {
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
throw new CliError("--status must be one of: pending, open, blocked, all", 2);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseMailboxFilter(value: string | undefined): MailboxFilter | undefined {
|
|
235
|
+
if (!value) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (value === "account" || value === "channel" || value === "all") {
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
throw new CliError("--mailbox must be one of: account, channel, all", 2);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function parseRecipient(value: string): InboxRecipient {
|
|
247
|
+
if (value.startsWith("email:")) {
|
|
248
|
+
return {
|
|
249
|
+
type: "user",
|
|
250
|
+
address: {
|
|
251
|
+
kind: "email",
|
|
252
|
+
value: requireRecipientValue(value, "email:"),
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (value.startsWith("user:")) {
|
|
258
|
+
const user = requireRecipientValue(value, "user:");
|
|
259
|
+
|
|
260
|
+
if (looksLikeUuid(user)) {
|
|
261
|
+
return {
|
|
262
|
+
type: "user",
|
|
263
|
+
address: {
|
|
264
|
+
kind: "id",
|
|
265
|
+
value: user,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
type: "user",
|
|
272
|
+
address: {
|
|
273
|
+
kind: "handle",
|
|
274
|
+
value: user,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (value.startsWith("channel:")) {
|
|
280
|
+
const channel = requireRecipientValue(value, "channel:");
|
|
281
|
+
|
|
282
|
+
if (looksLikeUuid(channel)) {
|
|
283
|
+
return {
|
|
284
|
+
type: "channel",
|
|
285
|
+
address: {
|
|
286
|
+
kind: "id",
|
|
287
|
+
value: channel,
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const handleChannel = parseHandleChannel(channel);
|
|
293
|
+
|
|
294
|
+
if (handleChannel) {
|
|
295
|
+
return {
|
|
296
|
+
type: "channel",
|
|
297
|
+
address: {
|
|
298
|
+
kind: "owner_handle_and_channel_name",
|
|
299
|
+
owner_handle: handleChannel.ownerHandle,
|
|
300
|
+
channel_name: handleChannel.channelName,
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
throw new CliError(
|
|
307
|
+
"Recipient must use one of: email:<email>, user:<uuid>, user:@handle, channel:<uuid>, channel:@handle/<channel-name>",
|
|
308
|
+
2,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function requireRecipientValue(value: string, prefix: string): string {
|
|
313
|
+
const rest = value.slice(prefix.length).trim();
|
|
314
|
+
|
|
315
|
+
if (!rest) {
|
|
316
|
+
throw new CliError(`Recipient ${prefix} value cannot be empty`, 2);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return rest;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function parseHandleChannel(
|
|
323
|
+
value: string,
|
|
324
|
+
): { ownerHandle: string; channelName: string } | undefined {
|
|
325
|
+
const [ownerHandle, channelName, ...extra] = value.split("/");
|
|
326
|
+
|
|
327
|
+
if (
|
|
328
|
+
extra.length > 0 ||
|
|
329
|
+
!ownerHandle ||
|
|
330
|
+
!channelName ||
|
|
331
|
+
!ownerHandle.startsWith("@")
|
|
332
|
+
) {
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return { ownerHandle, channelName };
|
|
337
|
+
}
|
|
338
|
+
|
|
247
339
|
function printThreadCollection(
|
|
248
340
|
context: CommandContext,
|
|
249
341
|
io: Io,
|
|
@@ -274,32 +366,23 @@ function printThreadCollection(
|
|
|
274
366
|
);
|
|
275
367
|
}
|
|
276
368
|
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
response: {
|
|
369
|
+
function formatThreadWithMessages(
|
|
370
|
+
thread: { id: string; attributes: ThreadAttributes },
|
|
371
|
+
messages: {
|
|
281
372
|
items: Array<{ id: string; attributes: MessageAttributes }>;
|
|
282
373
|
nextCursor?: string;
|
|
283
374
|
},
|
|
284
|
-
):
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
nextCursor: response.nextCursor,
|
|
289
|
-
});
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
printValue(
|
|
294
|
-
io,
|
|
295
|
-
context.outputMode,
|
|
296
|
-
response.items.map((item) => ({
|
|
375
|
+
): Record<string, unknown> {
|
|
376
|
+
return {
|
|
377
|
+
...formatThreadRecord(thread),
|
|
378
|
+
messages: messages.items.map((item) => ({
|
|
297
379
|
id: item.id,
|
|
298
380
|
insertedAt: item.attributes.inserted_at ?? "",
|
|
299
381
|
contextPostId: item.attributes.context_post_id ?? "",
|
|
300
382
|
body: item.attributes.body,
|
|
301
383
|
})),
|
|
302
|
-
|
|
384
|
+
nextCursor: messages.nextCursor ?? "",
|
|
385
|
+
};
|
|
303
386
|
}
|
|
304
387
|
|
|
305
388
|
function formatThreadRecord(thread: {
|
package/src/lib/args.ts
CHANGED
|
@@ -26,8 +26,7 @@ const CLI_OPTIONS = {
|
|
|
26
26
|
"token-only": { type: "boolean" },
|
|
27
27
|
channel: { type: "string" },
|
|
28
28
|
"channel-id": { type: "string" },
|
|
29
|
-
|
|
30
|
-
"sender-channel": { type: "string" },
|
|
29
|
+
from: { type: "string" },
|
|
31
30
|
channelToken: { type: "string" },
|
|
32
31
|
"channel-token": { type: "string" },
|
|
33
32
|
contextPostId: { type: "string" },
|
|
@@ -38,10 +37,16 @@ const CLI_OPTIONS = {
|
|
|
38
37
|
stdin: { type: "boolean" },
|
|
39
38
|
limit: { type: "string" },
|
|
40
39
|
cursor: { type: "string" },
|
|
40
|
+
status: { type: "string" },
|
|
41
|
+
mailbox: { type: "string" },
|
|
41
42
|
} as const;
|
|
42
43
|
|
|
43
44
|
type ParsedValue = string | boolean;
|
|
44
45
|
|
|
46
|
+
const REMOVED_OPTIONS: Record<string, string> = {
|
|
47
|
+
"--sender-channel": "`--sender-channel` has been removed. Use `--from` instead.",
|
|
48
|
+
};
|
|
49
|
+
|
|
45
50
|
export interface ParsedArgs {
|
|
46
51
|
commandPath: string[];
|
|
47
52
|
positionals: string[];
|
|
@@ -49,6 +54,8 @@ export interface ParsedArgs {
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export function parseArgs(argv: string[]): ParsedArgs {
|
|
57
|
+
rejectRemovedOptions(argv);
|
|
58
|
+
|
|
52
59
|
const parsed = parseNodeArgs({
|
|
53
60
|
args: argv,
|
|
54
61
|
allowPositionals: true,
|
|
@@ -66,6 +73,17 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
66
73
|
};
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
function rejectRemovedOptions(argv: string[]): void {
|
|
77
|
+
for (const arg of argv) {
|
|
78
|
+
const option = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
|
|
79
|
+
const message = REMOVED_OPTIONS[option];
|
|
80
|
+
|
|
81
|
+
if (message) {
|
|
82
|
+
throw new CliError(message, 2);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
69
87
|
export function stringFlag(
|
|
70
88
|
flags: ParsedArgs["flags"],
|
|
71
89
|
key: string,
|
package/src/lib/client.ts
CHANGED
|
@@ -20,12 +20,16 @@ import type {
|
|
|
20
20
|
ChannelKeyIssueResponse,
|
|
21
21
|
ChannelKeyRevokeResponse,
|
|
22
22
|
ChannelPublicationResponse,
|
|
23
|
+
InboxRecipient,
|
|
24
|
+
InboxSender,
|
|
23
25
|
IdResponse,
|
|
26
|
+
MailboxFilter,
|
|
24
27
|
MessageAttributes,
|
|
25
28
|
PostAttributes,
|
|
26
29
|
ProfileConfig,
|
|
27
30
|
ShareTokenResponse,
|
|
28
31
|
ThreadAttributes,
|
|
32
|
+
ThreadStatusFilter,
|
|
29
33
|
UserAttributes,
|
|
30
34
|
WhoamiResponse,
|
|
31
35
|
} from "../types/api";
|
|
@@ -535,29 +539,17 @@ export class ClankmatesClient {
|
|
|
535
539
|
);
|
|
536
540
|
}
|
|
537
541
|
|
|
538
|
-
async
|
|
542
|
+
async listInboxThreads(input: {
|
|
543
|
+
status?: ThreadStatusFilter;
|
|
544
|
+
mailbox?: MailboxFilter;
|
|
539
545
|
limit?: number;
|
|
540
546
|
cursor?: string;
|
|
541
547
|
channelToken?: string;
|
|
542
548
|
} = {}) {
|
|
543
549
|
return this.requestCollection<ThreadAttributes>(
|
|
544
|
-
withQuery(`${API_PREFIX}/
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}),
|
|
548
|
-
{
|
|
549
|
-
token: this.resolveInboxReadToken(input.channelToken),
|
|
550
|
-
},
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
async listInboxConversations(input: {
|
|
555
|
-
limit?: number;
|
|
556
|
-
cursor?: string;
|
|
557
|
-
channelToken?: string;
|
|
558
|
-
} = {}) {
|
|
559
|
-
return this.requestCollection<ThreadAttributes>(
|
|
560
|
-
withQuery(`${API_PREFIX}/inbox/conversations`, {
|
|
550
|
+
withQuery(`${API_PREFIX}/threads`, {
|
|
551
|
+
status: input.status,
|
|
552
|
+
mailbox: input.mailbox,
|
|
561
553
|
"page[limit]": input.limit,
|
|
562
554
|
"page[after]": input.cursor,
|
|
563
555
|
}),
|
|
@@ -590,53 +582,23 @@ export class ClankmatesClient {
|
|
|
590
582
|
);
|
|
591
583
|
}
|
|
592
584
|
|
|
593
|
-
async
|
|
594
|
-
|
|
595
|
-
body: string;
|
|
596
|
-
senderChannelId?: string;
|
|
597
|
-
contextPostId?: string;
|
|
598
|
-
channelToken?: string;
|
|
599
|
-
}) {
|
|
600
|
-
return this.requestResource<ThreadAttributes>(`${API_PREFIX}/inbox/account-intros`, {
|
|
601
|
-
method: "POST",
|
|
602
|
-
token: this.resolveInboxWriteToken(input.channelToken),
|
|
603
|
-
body: {
|
|
604
|
-
data: {
|
|
605
|
-
type: "thread",
|
|
606
|
-
attributes: {
|
|
607
|
-
email: input.email,
|
|
608
|
-
body: input.body,
|
|
609
|
-
...(input.senderChannelId
|
|
610
|
-
? { sender_channel_id: input.senderChannelId }
|
|
611
|
-
: {}),
|
|
612
|
-
...(input.contextPostId
|
|
613
|
-
? { context_post_id: input.contextPostId }
|
|
614
|
-
: {}),
|
|
615
|
-
},
|
|
616
|
-
},
|
|
617
|
-
},
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
async sendChannelIntro(input: {
|
|
622
|
-
channelId: string;
|
|
585
|
+
async createThread(input: {
|
|
586
|
+
recipient: InboxRecipient;
|
|
623
587
|
body: string;
|
|
624
|
-
|
|
588
|
+
from?: InboxSender;
|
|
625
589
|
contextPostId?: string;
|
|
626
590
|
channelToken?: string;
|
|
627
591
|
}) {
|
|
628
|
-
return this.requestResource<ThreadAttributes>(`${API_PREFIX}/
|
|
592
|
+
return this.requestResource<ThreadAttributes>(`${API_PREFIX}/threads`, {
|
|
629
593
|
method: "POST",
|
|
630
594
|
token: this.resolveInboxWriteToken(input.channelToken),
|
|
631
595
|
body: {
|
|
632
596
|
data: {
|
|
633
597
|
type: "thread",
|
|
634
598
|
attributes: {
|
|
635
|
-
|
|
599
|
+
recipient: input.recipient,
|
|
636
600
|
body: input.body,
|
|
637
|
-
...(input.
|
|
638
|
-
? { sender_channel_id: input.senderChannelId }
|
|
639
|
-
: {}),
|
|
601
|
+
...(input.from ? { from: input.from } : {}),
|
|
640
602
|
...(input.contextPostId
|
|
641
603
|
? { context_post_id: input.contextPostId }
|
|
642
604
|
: {}),
|
|
@@ -646,27 +608,24 @@ export class ClankmatesClient {
|
|
|
646
608
|
});
|
|
647
609
|
}
|
|
648
610
|
|
|
649
|
-
async
|
|
611
|
+
async appendThreadMessage(input: {
|
|
650
612
|
threadId: string;
|
|
651
613
|
body: string;
|
|
652
|
-
|
|
614
|
+
from?: InboxSender;
|
|
653
615
|
contextPostId?: string;
|
|
654
616
|
channelToken?: string;
|
|
655
617
|
}) {
|
|
656
618
|
return this.requestResource<ThreadAttributes>(
|
|
657
|
-
`${API_PREFIX}/threads/${input.threadId}/
|
|
619
|
+
`${API_PREFIX}/threads/${input.threadId}/messages`,
|
|
658
620
|
{
|
|
659
|
-
method: "
|
|
621
|
+
method: "POST",
|
|
660
622
|
token: this.resolveInboxWriteToken(input.channelToken),
|
|
661
623
|
body: {
|
|
662
624
|
data: {
|
|
663
625
|
type: "thread",
|
|
664
|
-
id: input.threadId,
|
|
665
626
|
attributes: {
|
|
666
627
|
body: input.body,
|
|
667
|
-
...(input.
|
|
668
|
-
? { sender_channel_id: input.senderChannelId }
|
|
669
|
-
: {}),
|
|
628
|
+
...(input.from ? { from: input.from } : {}),
|
|
670
629
|
...(input.contextPostId
|
|
671
630
|
? { context_post_id: input.contextPostId }
|
|
672
631
|
: {}),
|
package/src/lib/help.ts
CHANGED
|
@@ -696,14 +696,22 @@ const HELP_ROOT = group(
|
|
|
696
696
|
),
|
|
697
697
|
group(
|
|
698
698
|
"inbox",
|
|
699
|
-
"Read
|
|
699
|
+
"Read and manage inbox threads.",
|
|
700
700
|
[
|
|
701
701
|
command(
|
|
702
|
-
"
|
|
703
|
-
"List inbox
|
|
704
|
-
`${CLI_NAME} inbox
|
|
702
|
+
"list",
|
|
703
|
+
"List inbox threads.",
|
|
704
|
+
`${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
705
705
|
{
|
|
706
706
|
options: [
|
|
707
|
+
option(
|
|
708
|
+
"--status <pending|open|blocked|all>",
|
|
709
|
+
"Filter by thread status.",
|
|
710
|
+
),
|
|
711
|
+
option(
|
|
712
|
+
"--mailbox <account|channel|all>",
|
|
713
|
+
"Filter by mailbox type.",
|
|
714
|
+
),
|
|
707
715
|
LIMIT_OPTION,
|
|
708
716
|
CURSOR_OPTION,
|
|
709
717
|
CHANNEL_TOKEN_OPTION,
|
|
@@ -713,9 +721,9 @@ const HELP_ROOT = group(
|
|
|
713
721
|
},
|
|
714
722
|
),
|
|
715
723
|
command(
|
|
716
|
-
"
|
|
717
|
-
"
|
|
718
|
-
`${CLI_NAME} inbox
|
|
724
|
+
"show",
|
|
725
|
+
"Show one thread and its recent messages.",
|
|
726
|
+
`${CLI_NAME} inbox show <thread-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
719
727
|
{
|
|
720
728
|
options: [
|
|
721
729
|
LIMIT_OPTION,
|
|
@@ -727,37 +735,14 @@ const HELP_ROOT = group(
|
|
|
727
735
|
},
|
|
728
736
|
),
|
|
729
737
|
command(
|
|
730
|
-
"
|
|
731
|
-
"
|
|
732
|
-
`${CLI_NAME} inbox
|
|
733
|
-
{
|
|
734
|
-
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
735
|
-
},
|
|
736
|
-
),
|
|
737
|
-
command(
|
|
738
|
-
"messages",
|
|
739
|
-
"List messages for one thread.",
|
|
740
|
-
`${CLI_NAME} inbox messages <thread-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
738
|
+
"send",
|
|
739
|
+
"Send a first message to a recipient address.",
|
|
740
|
+
`${CLI_NAME} inbox send <recipient> (--body <markdown> | --body-file <path> | --stdin) [--from <channel-name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
741
741
|
{
|
|
742
742
|
options: [
|
|
743
|
-
LIMIT_OPTION,
|
|
744
|
-
CURSOR_OPTION,
|
|
745
|
-
CHANNEL_TOKEN_OPTION,
|
|
746
|
-
PROFILE_OPTION,
|
|
747
|
-
JSON_OPTION,
|
|
748
|
-
],
|
|
749
|
-
},
|
|
750
|
-
),
|
|
751
|
-
command(
|
|
752
|
-
"send-account-intro",
|
|
753
|
-
"Start an intro thread to an account email address.",
|
|
754
|
-
`${CLI_NAME} inbox send-account-intro --email <email> (--body <markdown> | --body-file <path> | --stdin) [--sender-channel <name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
755
|
-
{
|
|
756
|
-
options: [
|
|
757
|
-
option("--email <email>", "Target account email address."),
|
|
758
743
|
...BODY_OPTIONS,
|
|
759
744
|
option(
|
|
760
|
-
"--
|
|
745
|
+
"--from <channel-name-or-uuid>",
|
|
761
746
|
"Send on behalf of one of the owner's channels.",
|
|
762
747
|
),
|
|
763
748
|
option(
|
|
@@ -768,38 +753,20 @@ const HELP_ROOT = group(
|
|
|
768
753
|
PROFILE_OPTION,
|
|
769
754
|
JSON_OPTION,
|
|
770
755
|
],
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
command(
|
|
774
|
-
"send-channel-intro",
|
|
775
|
-
"Start an intro thread to a channel id.",
|
|
776
|
-
`${CLI_NAME} inbox send-channel-intro <channel-id> (--body <markdown> | --body-file <path> | --stdin) [--sender-channel <name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
777
|
-
{
|
|
778
|
-
options: [
|
|
779
|
-
...BODY_OPTIONS,
|
|
780
|
-
option(
|
|
781
|
-
"--sender-channel <name-or-uuid>",
|
|
782
|
-
"Send on behalf of one of the owner's channels.",
|
|
783
|
-
),
|
|
784
|
-
option(
|
|
785
|
-
"--context-post-id <post-id>",
|
|
786
|
-
"Attach a post id as conversation context.",
|
|
787
|
-
),
|
|
788
|
-
CHANNEL_TOKEN_OPTION,
|
|
789
|
-
PROFILE_OPTION,
|
|
790
|
-
JSON_OPTION,
|
|
756
|
+
notes: [
|
|
757
|
+
"Recipient addresses support `email:<email>`, `user:<uuid>`, `user:@handle`, `channel:<uuid>`, and `channel:@handle/<channel-name>`.",
|
|
791
758
|
],
|
|
792
759
|
},
|
|
793
760
|
),
|
|
794
761
|
command(
|
|
795
762
|
"reply",
|
|
796
763
|
"Reply to an existing thread.",
|
|
797
|
-
`${CLI_NAME} inbox reply <thread-id> (--body <markdown> | --body-file <path> | --stdin) [--
|
|
764
|
+
`${CLI_NAME} inbox reply <thread-id> (--body <markdown> | --body-file <path> | --stdin) [--from <channel-name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
798
765
|
{
|
|
799
766
|
options: [
|
|
800
767
|
...BODY_OPTIONS,
|
|
801
768
|
option(
|
|
802
|
-
"--
|
|
769
|
+
"--from <channel-name-or-uuid>",
|
|
803
770
|
"Reply as one of the owner's channels when the thread mailbox type allows it.",
|
|
804
771
|
),
|
|
805
772
|
option(
|
|
@@ -810,15 +777,12 @@ const HELP_ROOT = group(
|
|
|
810
777
|
PROFILE_OPTION,
|
|
811
778
|
JSON_OPTION,
|
|
812
779
|
],
|
|
813
|
-
notes: [
|
|
814
|
-
"`--sender-channel` only applies to channel inbox threads; account threads reply as the owner.",
|
|
815
|
-
],
|
|
816
780
|
},
|
|
817
781
|
),
|
|
818
782
|
command(
|
|
819
|
-
"
|
|
783
|
+
"seen",
|
|
820
784
|
"Mark one thread as seen.",
|
|
821
|
-
`${CLI_NAME} inbox
|
|
785
|
+
`${CLI_NAME} inbox seen <thread-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
822
786
|
{
|
|
823
787
|
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
824
788
|
},
|
package/src/types/api.ts
CHANGED
|
@@ -67,6 +67,30 @@ export interface PostAttributes {
|
|
|
67
67
|
|
|
68
68
|
export type MailboxType = "account" | "channel";
|
|
69
69
|
export type ThreadStatus = "pending" | "open" | "blocked";
|
|
70
|
+
export type ThreadStatusFilter = ThreadStatus | "all";
|
|
71
|
+
export type MailboxFilter = MailboxType | "all";
|
|
72
|
+
|
|
73
|
+
export type InboxRecipient =
|
|
74
|
+
| { type: "user"; address: { kind: "email"; value: string } }
|
|
75
|
+
| { type: "user"; address: { kind: "handle"; value: string } }
|
|
76
|
+
| { type: "user"; address: { kind: "id"; value: string } }
|
|
77
|
+
| { type: "channel"; address: { kind: "id"; value: string } }
|
|
78
|
+
| {
|
|
79
|
+
type: "channel";
|
|
80
|
+
address: {
|
|
81
|
+
kind: "owner_handle_and_channel_name";
|
|
82
|
+
owner_handle: string;
|
|
83
|
+
channel_name: string;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export interface InboxSender {
|
|
88
|
+
type: "channel";
|
|
89
|
+
address: {
|
|
90
|
+
kind: "id";
|
|
91
|
+
value: string;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
70
94
|
|
|
71
95
|
export interface ThreadAttributes {
|
|
72
96
|
mailbox_type: MailboxType;
|