@clankmates/cli 0.5.1 → 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/cli.ts +36 -4
- 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 +95 -86
- package/src/lib/output.ts +5 -1
- 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/cli.ts
CHANGED
|
@@ -50,7 +50,9 @@ export async function runCli(
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
if (command === "help") {
|
|
53
|
-
const helpText = renderHelp(parsed.positionals
|
|
53
|
+
const helpText = renderHelp(parsed.positionals, {
|
|
54
|
+
boldSectionTitles: shouldBoldHelpSections(io),
|
|
55
|
+
});
|
|
54
56
|
|
|
55
57
|
if (!helpText) {
|
|
56
58
|
throw new CliError(formatUnknownHelpTopic(parsed.positionals), 2);
|
|
@@ -61,12 +63,18 @@ export async function runCli(
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
if (!command) {
|
|
64
|
-
io.stdout(
|
|
66
|
+
io.stdout(
|
|
67
|
+
renderHelp([], {
|
|
68
|
+
boldSectionTitles: shouldBoldHelpSections(io),
|
|
69
|
+
})!,
|
|
70
|
+
);
|
|
65
71
|
return 0;
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
if (parsed.flags.help === true) {
|
|
69
|
-
const helpText = renderHelp([command, ...parsed.positionals]
|
|
75
|
+
const helpText = renderHelp([command, ...parsed.positionals], {
|
|
76
|
+
boldSectionTitles: shouldBoldHelpSections(io),
|
|
77
|
+
});
|
|
70
78
|
|
|
71
79
|
if (!helpText) {
|
|
72
80
|
throw new CliError(
|
|
@@ -80,7 +88,11 @@ export async function runCli(
|
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
if (resolvesToHelpGroup([command, ...parsed.positionals])) {
|
|
83
|
-
io.stdout(
|
|
91
|
+
io.stdout(
|
|
92
|
+
renderHelp([command, ...parsed.positionals], {
|
|
93
|
+
boldSectionTitles: shouldBoldHelpSections(io),
|
|
94
|
+
})!,
|
|
95
|
+
);
|
|
84
96
|
return 0;
|
|
85
97
|
}
|
|
86
98
|
|
|
@@ -103,6 +115,26 @@ export async function runCli(
|
|
|
103
115
|
}
|
|
104
116
|
}
|
|
105
117
|
|
|
118
|
+
function shouldBoldHelpSections(io: Io): boolean {
|
|
119
|
+
if (io.stdoutIsTTY !== true) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (process.env.NO_COLOR !== undefined) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (process.env.CLICOLOR === "0" || process.env.FORCE_COLOR === "0") {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (process.env.TERM === "dumb") {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
106
138
|
function formatUnknownHelpTopic(path: string[]): string {
|
|
107
139
|
const topic = path.join(" ");
|
|
108
140
|
return topic
|
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
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { CLI_VERSION } from "./version";
|
|
2
2
|
|
|
3
3
|
const CLI_NAME = "clankm";
|
|
4
|
+
const ANSI_BOLD = "\u001b[1m";
|
|
5
|
+
const ANSI_RESET = "\u001b[0m";
|
|
6
|
+
|
|
7
|
+
interface HelpRenderOptions {
|
|
8
|
+
boldSectionTitles?: boolean;
|
|
9
|
+
}
|
|
4
10
|
|
|
5
11
|
interface HelpOption {
|
|
6
12
|
flag: string;
|
|
@@ -690,28 +696,22 @@ const HELP_ROOT = group(
|
|
|
690
696
|
),
|
|
691
697
|
group(
|
|
692
698
|
"inbox",
|
|
693
|
-
"Read
|
|
699
|
+
"Read and manage inbox threads.",
|
|
694
700
|
[
|
|
695
701
|
command(
|
|
696
|
-
"
|
|
697
|
-
"List inbox
|
|
698
|
-
`${CLI_NAME} inbox
|
|
699
|
-
{
|
|
700
|
-
options: [
|
|
701
|
-
LIMIT_OPTION,
|
|
702
|
-
CURSOR_OPTION,
|
|
703
|
-
CHANNEL_TOKEN_OPTION,
|
|
704
|
-
PROFILE_OPTION,
|
|
705
|
-
JSON_OPTION,
|
|
706
|
-
],
|
|
707
|
-
},
|
|
708
|
-
),
|
|
709
|
-
command(
|
|
710
|
-
"conversations",
|
|
711
|
-
"List inbox conversations.",
|
|
712
|
-
`${CLI_NAME} inbox conversations [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
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]`,
|
|
713
705
|
{
|
|
714
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
|
+
),
|
|
715
715
|
LIMIT_OPTION,
|
|
716
716
|
CURSOR_OPTION,
|
|
717
717
|
CHANNEL_TOKEN_OPTION,
|
|
@@ -721,17 +721,9 @@ const HELP_ROOT = group(
|
|
|
721
721
|
},
|
|
722
722
|
),
|
|
723
723
|
command(
|
|
724
|
-
"
|
|
725
|
-
"
|
|
726
|
-
`${CLI_NAME} inbox
|
|
727
|
-
{
|
|
728
|
-
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
729
|
-
},
|
|
730
|
-
),
|
|
731
|
-
command(
|
|
732
|
-
"messages",
|
|
733
|
-
"List messages for one thread.",
|
|
734
|
-
`${CLI_NAME} inbox messages <thread-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
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]`,
|
|
735
727
|
{
|
|
736
728
|
options: [
|
|
737
729
|
LIMIT_OPTION,
|
|
@@ -743,15 +735,14 @@ const HELP_ROOT = group(
|
|
|
743
735
|
},
|
|
744
736
|
),
|
|
745
737
|
command(
|
|
746
|
-
"send
|
|
747
|
-
"
|
|
748
|
-
`${CLI_NAME} inbox send
|
|
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]`,
|
|
749
741
|
{
|
|
750
742
|
options: [
|
|
751
|
-
option("--email <email>", "Target account email address."),
|
|
752
743
|
...BODY_OPTIONS,
|
|
753
744
|
option(
|
|
754
|
-
"--
|
|
745
|
+
"--from <channel-name-or-uuid>",
|
|
755
746
|
"Send on behalf of one of the owner's channels.",
|
|
756
747
|
),
|
|
757
748
|
option(
|
|
@@ -762,38 +753,20 @@ const HELP_ROOT = group(
|
|
|
762
753
|
PROFILE_OPTION,
|
|
763
754
|
JSON_OPTION,
|
|
764
755
|
],
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
command(
|
|
768
|
-
"send-channel-intro",
|
|
769
|
-
"Start an intro thread to a channel id.",
|
|
770
|
-
`${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]`,
|
|
771
|
-
{
|
|
772
|
-
options: [
|
|
773
|
-
...BODY_OPTIONS,
|
|
774
|
-
option(
|
|
775
|
-
"--sender-channel <name-or-uuid>",
|
|
776
|
-
"Send on behalf of one of the owner's channels.",
|
|
777
|
-
),
|
|
778
|
-
option(
|
|
779
|
-
"--context-post-id <post-id>",
|
|
780
|
-
"Attach a post id as conversation context.",
|
|
781
|
-
),
|
|
782
|
-
CHANNEL_TOKEN_OPTION,
|
|
783
|
-
PROFILE_OPTION,
|
|
784
|
-
JSON_OPTION,
|
|
756
|
+
notes: [
|
|
757
|
+
"Recipient addresses support `email:<email>`, `user:<uuid>`, `user:@handle`, `channel:<uuid>`, and `channel:@handle/<channel-name>`.",
|
|
785
758
|
],
|
|
786
759
|
},
|
|
787
760
|
),
|
|
788
761
|
command(
|
|
789
762
|
"reply",
|
|
790
763
|
"Reply to an existing thread.",
|
|
791
|
-
`${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]`,
|
|
792
765
|
{
|
|
793
766
|
options: [
|
|
794
767
|
...BODY_OPTIONS,
|
|
795
768
|
option(
|
|
796
|
-
"--
|
|
769
|
+
"--from <channel-name-or-uuid>",
|
|
797
770
|
"Reply as one of the owner's channels when the thread mailbox type allows it.",
|
|
798
771
|
),
|
|
799
772
|
option(
|
|
@@ -804,15 +777,12 @@ const HELP_ROOT = group(
|
|
|
804
777
|
PROFILE_OPTION,
|
|
805
778
|
JSON_OPTION,
|
|
806
779
|
],
|
|
807
|
-
notes: [
|
|
808
|
-
"`--sender-channel` only applies to channel inbox threads; account threads reply as the owner.",
|
|
809
|
-
],
|
|
810
780
|
},
|
|
811
781
|
),
|
|
812
782
|
command(
|
|
813
|
-
"
|
|
783
|
+
"seen",
|
|
814
784
|
"Mark one thread as seen.",
|
|
815
|
-
`${CLI_NAME} inbox
|
|
785
|
+
`${CLI_NAME} inbox seen <thread-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
816
786
|
{
|
|
817
787
|
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
818
788
|
},
|
|
@@ -945,7 +915,7 @@ const HELP_ROOT = group(
|
|
|
945
915
|
),
|
|
946
916
|
],
|
|
947
917
|
{
|
|
948
|
-
description: "
|
|
918
|
+
description: "Work with clankmates.com from the command line.",
|
|
949
919
|
usage: [`${CLI_NAME} <command>`, `${CLI_NAME} help <command-path>`],
|
|
950
920
|
examples: [
|
|
951
921
|
`${CLI_NAME}`,
|
|
@@ -963,7 +933,10 @@ const HELP_ROOT = group(
|
|
|
963
933
|
},
|
|
964
934
|
);
|
|
965
935
|
|
|
966
|
-
export function renderHelp(
|
|
936
|
+
export function renderHelp(
|
|
937
|
+
path: string[],
|
|
938
|
+
options: HelpRenderOptions = {},
|
|
939
|
+
): string | undefined {
|
|
967
940
|
const resolved = resolveHelpNode(path);
|
|
968
941
|
|
|
969
942
|
if (!resolved) {
|
|
@@ -971,8 +944,8 @@ export function renderHelp(path: string[]): string | undefined {
|
|
|
971
944
|
}
|
|
972
945
|
|
|
973
946
|
return resolved.node.kind === "group"
|
|
974
|
-
? renderGroupHelp(resolved.node, resolved.path)
|
|
975
|
-
: renderCommandHelp(resolved.node, resolved.path);
|
|
947
|
+
? renderGroupHelp(resolved.node, resolved.path, options)
|
|
948
|
+
: renderCommandHelp(resolved.node, resolved.path, options);
|
|
976
949
|
}
|
|
977
950
|
|
|
978
951
|
export function resolvesToHelpGroup(path: string[]): boolean {
|
|
@@ -1010,7 +983,11 @@ function matchesSegment(node: HelpNode, segment: string): boolean {
|
|
|
1010
983
|
return node.name === segment || node.aliases?.includes(segment) === true;
|
|
1011
984
|
}
|
|
1012
985
|
|
|
1013
|
-
function renderGroupHelp(
|
|
986
|
+
function renderGroupHelp(
|
|
987
|
+
node: HelpGroup,
|
|
988
|
+
path: string[],
|
|
989
|
+
options: HelpRenderOptions,
|
|
990
|
+
): string {
|
|
1014
991
|
const title = path.length === 0 ? `${CLI_NAME} ${CLI_VERSION}` : `${CLI_NAME} ${path.join(" ")}`;
|
|
1015
992
|
const sections: string[] = [title];
|
|
1016
993
|
|
|
@@ -1021,26 +998,32 @@ function renderGroupHelp(node: HelpGroup, path: string[]): string {
|
|
|
1021
998
|
}
|
|
1022
999
|
|
|
1023
1000
|
if (node.usage && node.usage.length > 0) {
|
|
1024
|
-
sections.push(renderUsageSection(node.usage));
|
|
1001
|
+
sections.push(renderUsageSection(node.usage, options));
|
|
1025
1002
|
}
|
|
1026
1003
|
|
|
1027
1004
|
if (node.aliases && node.aliases.length > 0) {
|
|
1028
|
-
sections.push(renderLineListSection("Aliases", node.aliases));
|
|
1005
|
+
sections.push(renderLineListSection("Aliases", node.aliases, false, options));
|
|
1029
1006
|
}
|
|
1030
1007
|
|
|
1031
1008
|
const childHeading = path.length === 0 ? "Commands" : "Subcommands";
|
|
1032
|
-
sections.push(renderEntrySection(childHeading, node.children));
|
|
1009
|
+
sections.push(renderEntrySection(childHeading, node.children, options));
|
|
1033
1010
|
|
|
1034
1011
|
if (node.options && node.options.length > 0) {
|
|
1035
|
-
sections.push(
|
|
1012
|
+
sections.push(
|
|
1013
|
+
renderOptionSection(
|
|
1014
|
+
path.length === 0 ? "Global Flags" : "Options",
|
|
1015
|
+
node.options,
|
|
1016
|
+
options,
|
|
1017
|
+
),
|
|
1018
|
+
);
|
|
1036
1019
|
}
|
|
1037
1020
|
|
|
1038
1021
|
if (node.examples && node.examples.length > 0) {
|
|
1039
|
-
sections.push(renderLineListSection("Examples", node.examples, true));
|
|
1022
|
+
sections.push(renderLineListSection("Examples", node.examples, true, options));
|
|
1040
1023
|
}
|
|
1041
1024
|
|
|
1042
1025
|
if (node.notes && node.notes.length > 0) {
|
|
1043
|
-
sections.push(renderLineListSection("Notes", node.notes));
|
|
1026
|
+
sections.push(renderLineListSection("Notes", node.notes, false, options));
|
|
1044
1027
|
}
|
|
1045
1028
|
|
|
1046
1029
|
if (path.length === 0) {
|
|
@@ -1052,43 +1035,56 @@ function renderGroupHelp(node: HelpGroup, path: string[]): string {
|
|
|
1052
1035
|
return sections.join("\n\n");
|
|
1053
1036
|
}
|
|
1054
1037
|
|
|
1055
|
-
function renderCommandHelp(
|
|
1038
|
+
function renderCommandHelp(
|
|
1039
|
+
node: HelpCommand,
|
|
1040
|
+
path: string[],
|
|
1041
|
+
options: HelpRenderOptions,
|
|
1042
|
+
): string {
|
|
1056
1043
|
const title = `${CLI_NAME} ${path.join(" ")}`;
|
|
1057
1044
|
const sections: string[] = [title, node.description ?? node.summary];
|
|
1058
1045
|
|
|
1059
1046
|
if (node.aliases && node.aliases.length > 0) {
|
|
1060
|
-
sections.push(renderLineListSection("Aliases", node.aliases));
|
|
1047
|
+
sections.push(renderLineListSection("Aliases", node.aliases, false, options));
|
|
1061
1048
|
}
|
|
1062
1049
|
|
|
1063
1050
|
if (node.usage && node.usage.length > 0) {
|
|
1064
|
-
sections.push(renderUsageSection(node.usage));
|
|
1051
|
+
sections.push(renderUsageSection(node.usage, options));
|
|
1065
1052
|
}
|
|
1066
1053
|
|
|
1067
1054
|
if (node.options && node.options.length > 0) {
|
|
1068
|
-
sections.push(renderOptionSection("Options", node.options));
|
|
1055
|
+
sections.push(renderOptionSection("Options", node.options, options));
|
|
1069
1056
|
}
|
|
1070
1057
|
|
|
1071
1058
|
if (node.examples && node.examples.length > 0) {
|
|
1072
|
-
sections.push(renderLineListSection("Examples", node.examples, true));
|
|
1059
|
+
sections.push(renderLineListSection("Examples", node.examples, true, options));
|
|
1073
1060
|
}
|
|
1074
1061
|
|
|
1075
1062
|
if (node.notes && node.notes.length > 0) {
|
|
1076
|
-
sections.push(renderLineListSection("Notes", node.notes));
|
|
1063
|
+
sections.push(renderLineListSection("Notes", node.notes, false, options));
|
|
1077
1064
|
}
|
|
1078
1065
|
|
|
1079
1066
|
return sections.join("\n\n");
|
|
1080
1067
|
}
|
|
1081
1068
|
|
|
1082
|
-
function renderUsageSection(
|
|
1083
|
-
|
|
1069
|
+
function renderUsageSection(
|
|
1070
|
+
usage: string[],
|
|
1071
|
+
options: HelpRenderOptions,
|
|
1072
|
+
): string {
|
|
1073
|
+
return [formatSectionTitle("USAGE", options), ...usage.map((line) => ` ${line}`)].join(
|
|
1074
|
+
"\n",
|
|
1075
|
+
);
|
|
1084
1076
|
}
|
|
1085
1077
|
|
|
1086
|
-
function renderEntrySection(
|
|
1087
|
-
|
|
1078
|
+
function renderEntrySection(
|
|
1079
|
+
title: string,
|
|
1080
|
+
entries: HelpNode[],
|
|
1081
|
+
options: HelpRenderOptions,
|
|
1082
|
+
): string {
|
|
1083
|
+
const labels = entries.map((entry) => `${formatEntryLabel(entry)}:`);
|
|
1088
1084
|
const width = labels.reduce((current, label) => Math.max(current, label.length), 0);
|
|
1089
1085
|
|
|
1090
1086
|
return [
|
|
1091
|
-
|
|
1087
|
+
formatSectionTitle(title, options),
|
|
1092
1088
|
...entries.map((entry, index) => {
|
|
1093
1089
|
const label = labels[index]!.padEnd(width, " ");
|
|
1094
1090
|
return ` ${label} ${entry.summary}`;
|
|
@@ -1096,11 +1092,15 @@ function renderEntrySection(title: string, entries: HelpNode[]): string {
|
|
|
1096
1092
|
].join("\n");
|
|
1097
1093
|
}
|
|
1098
1094
|
|
|
1099
|
-
function renderOptionSection(
|
|
1095
|
+
function renderOptionSection(
|
|
1096
|
+
title: string,
|
|
1097
|
+
options: HelpOption[],
|
|
1098
|
+
renderOptions: HelpRenderOptions,
|
|
1099
|
+
): string {
|
|
1100
1100
|
const width = options.reduce((current, entry) => Math.max(current, entry.flag.length), 0);
|
|
1101
1101
|
|
|
1102
1102
|
return [
|
|
1103
|
-
|
|
1103
|
+
formatSectionTitle(title, renderOptions),
|
|
1104
1104
|
...options.map((entry) => ` ${entry.flag.padEnd(width, " ")} ${entry.description}`),
|
|
1105
1105
|
].join("\n");
|
|
1106
1106
|
}
|
|
@@ -1109,9 +1109,10 @@ function renderLineListSection(
|
|
|
1109
1109
|
title: string,
|
|
1110
1110
|
lines: string[],
|
|
1111
1111
|
code = false,
|
|
1112
|
+
options: HelpRenderOptions,
|
|
1112
1113
|
): string {
|
|
1113
1114
|
return [
|
|
1114
|
-
|
|
1115
|
+
formatSectionTitle(title, options),
|
|
1115
1116
|
...lines.map((line) => ` ${code ? line : line}`),
|
|
1116
1117
|
].join("\n");
|
|
1117
1118
|
}
|
|
@@ -1121,3 +1122,11 @@ function formatEntryLabel(entry: HelpNode): string {
|
|
|
1121
1122
|
? `${entry.name}, ${entry.aliases.join(", ")}`
|
|
1122
1123
|
: entry.name;
|
|
1123
1124
|
}
|
|
1125
|
+
|
|
1126
|
+
function formatSectionTitle(
|
|
1127
|
+
title: string,
|
|
1128
|
+
options: HelpRenderOptions = {},
|
|
1129
|
+
): string {
|
|
1130
|
+
const heading = title.toUpperCase();
|
|
1131
|
+
return options.boldSectionTitles ? `${ANSI_BOLD}${heading}${ANSI_RESET}` : heading;
|
|
1132
|
+
}
|
package/src/lib/output.ts
CHANGED
|
@@ -3,12 +3,16 @@ import type { OutputMode } from "../types/api";
|
|
|
3
3
|
export interface Io {
|
|
4
4
|
stdout: (message: string) => void;
|
|
5
5
|
stderr: (message: string) => void;
|
|
6
|
+
stdoutIsTTY?: boolean;
|
|
7
|
+
stderrIsTTY?: boolean;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export function defaultIo(): Io {
|
|
9
11
|
return {
|
|
10
12
|
stdout: (message) => process.stdout.write(`${message}\n`),
|
|
11
|
-
stderr: (message) => process.stderr.write(`${message}\n`)
|
|
13
|
+
stderr: (message) => process.stderr.write(`${message}\n`),
|
|
14
|
+
stdoutIsTTY: process.stdout.isTTY,
|
|
15
|
+
stderrIsTTY: process.stderr.isTTY,
|
|
12
16
|
};
|
|
13
17
|
}
|
|
14
18
|
|
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;
|