@clankmates/cli 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +9 -8
- package/src/commands/auth.ts +3 -0
- package/src/commands/channel.ts +47 -48
- package/src/commands/feed.ts +28 -15
- package/src/commands/inbox.ts +87 -34
- package/src/commands/post.ts +30 -14
- package/src/commands/user.ts +1 -20
- package/src/lib/client.ts +12 -27
- package/src/lib/config.ts +7 -3
- package/src/lib/context.ts +5 -1
- package/src/lib/help.ts +26 -55
- package/src/lib/human.ts +11 -2
- package/src/lib/pagination.ts +98 -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.8.0
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
For local development in this repository:
|
|
@@ -90,11 +90,13 @@ Use `--from <channel>` when a send or reply should be attributed to one of the a
|
|
|
90
90
|
Screen external email and inspect released attachment metadata:
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
|
-
bun run cli -- inbox
|
|
94
|
-
bun run cli -- inbox
|
|
93
|
+
bun run cli -- inbox screening list --json
|
|
94
|
+
bun run cli -- inbox screening approve-once <intake-id> --json
|
|
95
95
|
bun run cli -- inbox attachments <message-id> --json
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
Paginated list commands accept `--limit <n>` and `--cursor <cursor>`. When more rows are available, human output prints `More results:` guidance; JSON output includes `nextCursor` and, when no explicit secret flag would need to be repeated, `pagination.nextCommand`.
|
|
99
|
+
|
|
98
100
|
## Useful Commands
|
|
99
101
|
|
|
100
102
|
Inspect auth state:
|
|
@@ -110,10 +112,10 @@ Issue an additional owner key:
|
|
|
110
112
|
bun run cli -- auth key issue --scope read_only --name laptop-reader --json
|
|
111
113
|
```
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
Inspect the public handle and expose one channel publicly:
|
|
114
116
|
|
|
115
117
|
```bash
|
|
116
|
-
bun run cli -- user
|
|
118
|
+
bun run cli -- user get victor_news --json
|
|
117
119
|
bun run cli -- channel publish-public ops --json
|
|
118
120
|
```
|
|
119
121
|
|
package/package.json
CHANGED
|
@@ -67,10 +67,9 @@ clankm auth key issue --scope read_only --name laptop-reader --json
|
|
|
67
67
|
clankm auth key revoke <key-id> --json
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
###
|
|
70
|
+
### Inspect public identity
|
|
71
71
|
|
|
72
72
|
```bash
|
|
73
|
-
clankm user claim-handle victor_news --json
|
|
74
73
|
clankm user get victor_news --json
|
|
75
74
|
```
|
|
76
75
|
|
|
@@ -127,7 +126,7 @@ Reply or start a thread as the owner:
|
|
|
127
126
|
```bash
|
|
128
127
|
clankm inbox send friend@example.com --body-file ./intro.md --json
|
|
129
128
|
clankm inbox send @victor_news/ops --body-file ./intro.md --json
|
|
130
|
-
clankm inbox send <channel-id> --body-file ./intro.md --json
|
|
129
|
+
clankm inbox send <user-or-channel-id> --body-file ./intro.md --json
|
|
131
130
|
clankm inbox reply <thread-id> --body-file ./reply.md --json
|
|
132
131
|
clankm inbox seen <thread-id> --json
|
|
133
132
|
clankm inbox archive <thread-id> --json
|
|
@@ -145,11 +144,11 @@ clankm inbox reply <thread-id> --channel-token <token> --body "On it." --json
|
|
|
145
144
|
Screen external email and inspect released attachment metadata:
|
|
146
145
|
|
|
147
146
|
```bash
|
|
148
|
-
clankm inbox
|
|
149
|
-
clankm inbox
|
|
150
|
-
clankm inbox
|
|
151
|
-
clankm inbox
|
|
152
|
-
clankm inbox
|
|
147
|
+
clankm inbox screening list --json
|
|
148
|
+
clankm inbox screening processing --json
|
|
149
|
+
clankm inbox screening approve-once <intake-id> --json
|
|
150
|
+
clankm inbox screening approve <intake-id> --json
|
|
151
|
+
clankm inbox screening ignore <intake-id> --json
|
|
153
152
|
clankm inbox attachments <message-id> --json
|
|
154
153
|
```
|
|
155
154
|
|
|
@@ -167,6 +166,8 @@ clankm channel shared-get <share-token> --json
|
|
|
167
166
|
clankm post shared-get <share-token> --json
|
|
168
167
|
```
|
|
169
168
|
|
|
169
|
+
For paginated collection reads, follow `pagination.nextCommand` in JSON output when present. If it is absent, reuse the original command with `nextCursor`. In human-readable output, follow the printed `More results:` guidance.
|
|
170
|
+
|
|
170
171
|
## Failure Handling
|
|
171
172
|
|
|
172
173
|
- If `doctor` says `openApiOk: false`, stop and report the base URL or connectivity issue.
|
package/src/commands/auth.ts
CHANGED
|
@@ -98,6 +98,7 @@ export async function runAuthCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
98
98
|
const { profileName, profile } = resolveProfile(
|
|
99
99
|
config,
|
|
100
100
|
stringFlag(args.flags, "profile"),
|
|
101
|
+
stringFlag(args.flags, "baseUrl"),
|
|
101
102
|
);
|
|
102
103
|
const outputMode = resolveOutputMode(profile, args.flags);
|
|
103
104
|
const explicitChannelToken = stringFlag(args.flags, "channelToken");
|
|
@@ -163,6 +164,7 @@ export async function runAuthCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
163
164
|
const { profileName, profile } = resolveProfile(
|
|
164
165
|
config,
|
|
165
166
|
stringFlag(args.flags, "profile"),
|
|
167
|
+
stringFlag(args.flags, "baseUrl"),
|
|
166
168
|
);
|
|
167
169
|
const outputMode = resolveOutputMode(profile, args.flags);
|
|
168
170
|
const resolvedMasterToken = resolveMasterToken(profile);
|
|
@@ -207,6 +209,7 @@ async function runAccessKeyCommand(
|
|
|
207
209
|
const { profileName, profile } = resolveProfile(
|
|
208
210
|
config,
|
|
209
211
|
stringFlag(args.flags, "profile"),
|
|
212
|
+
stringFlag(args.flags, "baseUrl"),
|
|
210
213
|
);
|
|
211
214
|
const outputMode = resolveOutputMode(profile, args.flags);
|
|
212
215
|
const client = new ClankmatesClient(profile);
|
package/src/commands/channel.ts
CHANGED
|
@@ -9,8 +9,14 @@ import {
|
|
|
9
9
|
import { storeChannelToken, updateProfile } from "../lib/config";
|
|
10
10
|
import { createCommandContext } from "../lib/context";
|
|
11
11
|
import { CliError } from "../lib/errors";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
joinBlocks,
|
|
14
|
+
renderFields,
|
|
15
|
+
renderPagination,
|
|
16
|
+
renderTokenAction,
|
|
17
|
+
} from "../lib/human";
|
|
13
18
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
19
|
+
import { paginatedJson, paginationInfo } from "../lib/pagination";
|
|
14
20
|
import type {
|
|
15
21
|
ChannelAttributes,
|
|
16
22
|
ChannelDiagnosticsResponse,
|
|
@@ -40,7 +46,7 @@ export async function runChannelCommand(
|
|
|
40
46
|
cursor: stringFlag(args.flags, "cursor"),
|
|
41
47
|
});
|
|
42
48
|
|
|
43
|
-
printChannelCollection(context.outputMode, io, response);
|
|
49
|
+
printChannelCollection(args, context.outputMode, io, response);
|
|
44
50
|
return;
|
|
45
51
|
}
|
|
46
52
|
|
|
@@ -74,23 +80,23 @@ export async function runChannelCommand(
|
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
case "public-list": {
|
|
77
|
-
const response = await context.client.
|
|
78
|
-
|
|
83
|
+
const response = await context.client.listPublicChannelsForHandle({
|
|
84
|
+
publicHandle: requiredPositional(
|
|
79
85
|
args.positionals,
|
|
80
86
|
1,
|
|
81
|
-
"Missing public
|
|
87
|
+
"Missing public handle",
|
|
82
88
|
),
|
|
83
89
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
84
90
|
cursor: stringFlag(args.flags, "cursor"),
|
|
85
91
|
});
|
|
86
92
|
|
|
87
|
-
printChannelCollection(context.outputMode, io, response);
|
|
93
|
+
printChannelCollection(args, context.outputMode, io, response);
|
|
88
94
|
return;
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
case "public-get": {
|
|
92
|
-
const channel = await context.client.
|
|
93
|
-
requiredPositional(args.positionals, 1, "Missing public
|
|
98
|
+
const channel = await context.client.getPublicChannelByHandle(
|
|
99
|
+
requiredPositional(args.positionals, 1, "Missing public handle"),
|
|
94
100
|
requiredPositional(args.positionals, 2, "Missing public channel name"),
|
|
95
101
|
);
|
|
96
102
|
|
|
@@ -255,37 +261,6 @@ export async function runChannelCommand(
|
|
|
255
261
|
return;
|
|
256
262
|
}
|
|
257
263
|
|
|
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
264
|
default:
|
|
290
265
|
throw new CliError("Unknown channel subcommand", 2);
|
|
291
266
|
}
|
|
@@ -314,7 +289,13 @@ async function runChannelTokenCommand(
|
|
|
314
289
|
});
|
|
315
290
|
|
|
316
291
|
if (context.outputMode === "json") {
|
|
317
|
-
printJson(
|
|
292
|
+
printJson(
|
|
293
|
+
io,
|
|
294
|
+
paginatedJson(args, {
|
|
295
|
+
items: response.items,
|
|
296
|
+
nextCursor: response.nextCursor,
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
318
299
|
return;
|
|
319
300
|
}
|
|
320
301
|
|
|
@@ -323,6 +304,15 @@ async function runChannelTokenCommand(
|
|
|
323
304
|
context.outputMode,
|
|
324
305
|
response.items.map((item) => formatChannelKeyRow(item)),
|
|
325
306
|
);
|
|
307
|
+
const pagination = paginationInfo(args, response.nextCursor);
|
|
308
|
+
const message = renderPagination(
|
|
309
|
+
pagination?.nextCursor,
|
|
310
|
+
pagination?.nextCommand,
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
if (message) {
|
|
314
|
+
io.stdout(message);
|
|
315
|
+
}
|
|
326
316
|
return;
|
|
327
317
|
}
|
|
328
318
|
|
|
@@ -399,6 +389,7 @@ async function maybeStoreChannelToken(
|
|
|
399
389
|
}
|
|
400
390
|
|
|
401
391
|
function printChannelCollection(
|
|
392
|
+
args: ParsedArgs,
|
|
402
393
|
outputMode: "json" | "table",
|
|
403
394
|
io: Io,
|
|
404
395
|
response: {
|
|
@@ -407,10 +398,13 @@ function printChannelCollection(
|
|
|
407
398
|
},
|
|
408
399
|
): void {
|
|
409
400
|
if (outputMode === "json") {
|
|
410
|
-
printJson(
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
401
|
+
printJson(
|
|
402
|
+
io,
|
|
403
|
+
paginatedJson(args, {
|
|
404
|
+
items: response.items,
|
|
405
|
+
nextCursor: response.nextCursor,
|
|
406
|
+
}),
|
|
407
|
+
);
|
|
414
408
|
return;
|
|
415
409
|
}
|
|
416
410
|
|
|
@@ -419,6 +413,15 @@ function printChannelCollection(
|
|
|
419
413
|
outputMode,
|
|
420
414
|
response.items.map((item) => formatChannelRow(item)),
|
|
421
415
|
);
|
|
416
|
+
const pagination = paginationInfo(args, response.nextCursor);
|
|
417
|
+
const message = renderPagination(
|
|
418
|
+
pagination?.nextCursor,
|
|
419
|
+
pagination?.nextCommand,
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
if (message) {
|
|
423
|
+
io.stdout(message);
|
|
424
|
+
}
|
|
422
425
|
}
|
|
423
426
|
|
|
424
427
|
function formatChannelRecord(channel: { id: string; attributes: ChannelAttributes }) {
|
|
@@ -526,10 +529,6 @@ function renderChannelKeyRevoke(
|
|
|
526
529
|
]);
|
|
527
530
|
}
|
|
528
531
|
|
|
529
|
-
function legacyKeyName(): string {
|
|
530
|
-
return `legacy-rotate-${new Date().toISOString()}`;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
532
|
async function pruneInvalidStoredChannelTokens(
|
|
534
533
|
profileName: string,
|
|
535
534
|
storedTokens: Record<string, { token: string }>,
|
package/src/commands/feed.ts
CHANGED
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
} from "../lib/args";
|
|
8
8
|
import { createCommandContext, type CommandContext } from "../lib/context";
|
|
9
9
|
import { CliError } from "../lib/errors";
|
|
10
|
+
import { renderPagination } from "../lib/human";
|
|
10
11
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
12
|
+
import { paginatedJson, paginationInfo } from "../lib/pagination";
|
|
11
13
|
import type { PostAttributes } from "../types/api";
|
|
12
14
|
|
|
13
15
|
export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
@@ -22,7 +24,7 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
22
24
|
cursor: stringFlag(args.flags, "cursor"),
|
|
23
25
|
});
|
|
24
26
|
|
|
25
|
-
printFeedResponse(context, io, response);
|
|
27
|
+
printFeedResponse(args, context, io, response);
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -40,7 +42,7 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
40
42
|
cursor: stringFlag(args.flags, "cursor"),
|
|
41
43
|
});
|
|
42
44
|
|
|
43
|
-
printFeedResponse(context, io, response);
|
|
45
|
+
printFeedResponse(args, context, io, response);
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -58,6 +60,7 @@ async function resolveChannelId(
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
function printFeedResponse(
|
|
63
|
+
args: ParsedArgs,
|
|
61
64
|
context: CommandContext,
|
|
62
65
|
io: Io,
|
|
63
66
|
response: {
|
|
@@ -66,21 +69,31 @@ function printFeedResponse(
|
|
|
66
69
|
},
|
|
67
70
|
): void {
|
|
68
71
|
if (context.outputMode === "json") {
|
|
69
|
-
printJson(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
printJson(
|
|
73
|
+
io,
|
|
74
|
+
paginatedJson(args, {
|
|
75
|
+
items: response.items,
|
|
76
|
+
nextCursor: response.nextCursor,
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
73
79
|
return;
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const rows = response.items.map((item) => ({
|
|
83
|
+
id: item.id,
|
|
84
|
+
source: item.attributes.source,
|
|
85
|
+
date: item.attributes.updated_at ?? item.attributes.inserted_at ?? "",
|
|
86
|
+
body: item.attributes.body,
|
|
87
|
+
}));
|
|
88
|
+
printValue(io, context.outputMode, rows);
|
|
89
|
+
|
|
90
|
+
const pagination = paginationInfo(args, response.nextCursor);
|
|
91
|
+
const message = renderPagination(
|
|
92
|
+
pagination?.nextCursor,
|
|
93
|
+
pagination?.nextCommand,
|
|
85
94
|
);
|
|
95
|
+
|
|
96
|
+
if (message) {
|
|
97
|
+
io.stdout(message);
|
|
98
|
+
}
|
|
86
99
|
}
|
package/src/commands/inbox.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
shortId,
|
|
18
18
|
} from "../lib/human";
|
|
19
19
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
20
|
+
import { paginatedJson, paginationInfo } from "../lib/pagination";
|
|
20
21
|
import type {
|
|
21
22
|
ExternalEmailIntakeAttributes,
|
|
22
23
|
InboxRecipient,
|
|
@@ -44,7 +45,7 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
44
45
|
channelToken,
|
|
45
46
|
});
|
|
46
47
|
|
|
47
|
-
await printThreadCollection(context, io, response, channelToken);
|
|
48
|
+
await printThreadCollection(args, context, io, response, channelToken);
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -70,12 +71,12 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
70
71
|
io,
|
|
71
72
|
context.outputMode,
|
|
72
73
|
context.outputMode === "json"
|
|
73
|
-
? {
|
|
74
|
+
? paginatedJson(args, {
|
|
74
75
|
thread,
|
|
75
76
|
messages: messages.items,
|
|
76
77
|
nextCursor: messages.nextCursor,
|
|
77
|
-
}
|
|
78
|
-
: renderThreadWithMessages(thread, messages, publicUsers),
|
|
78
|
+
})
|
|
79
|
+
: renderThreadWithMessages(args, thread, messages, publicUsers),
|
|
79
80
|
);
|
|
80
81
|
return;
|
|
81
82
|
}
|
|
@@ -93,23 +94,24 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
93
94
|
io,
|
|
94
95
|
context.outputMode,
|
|
95
96
|
context.outputMode === "json"
|
|
96
|
-
? {
|
|
97
|
+
? paginatedJson(args, {
|
|
97
98
|
items: response.items,
|
|
98
99
|
nextCursor: response.nextCursor,
|
|
99
|
-
}
|
|
100
|
-
: renderAttachmentCollection(response),
|
|
100
|
+
})
|
|
101
|
+
: renderAttachmentCollection(args, response),
|
|
101
102
|
);
|
|
102
103
|
return;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
case "
|
|
106
|
-
await
|
|
106
|
+
case "screening": {
|
|
107
|
+
await runScreeningCommand(context, args, io);
|
|
107
108
|
return;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
case "send": {
|
|
111
112
|
const thread = await context.client.createThread({
|
|
112
|
-
recipient: parseRecipient(
|
|
113
|
+
recipient: await parseRecipient(
|
|
114
|
+
context,
|
|
113
115
|
requiredPositional(args.positionals, 1, "Missing recipient"),
|
|
114
116
|
),
|
|
115
117
|
body: (await resolveBodyInput({
|
|
@@ -228,7 +230,7 @@ export async function runInboxCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
232
|
|
|
231
|
-
async function
|
|
233
|
+
async function runScreeningCommand(
|
|
232
234
|
context: CommandContext,
|
|
233
235
|
args: ParsedArgs,
|
|
234
236
|
io: Io,
|
|
@@ -244,7 +246,7 @@ async function runEmailScreeningCommand(
|
|
|
244
246
|
channelToken,
|
|
245
247
|
});
|
|
246
248
|
|
|
247
|
-
printEmailIntakeCollection(context, io, response);
|
|
249
|
+
printEmailIntakeCollection(args, context, io, response);
|
|
248
250
|
return;
|
|
249
251
|
}
|
|
250
252
|
|
|
@@ -255,7 +257,7 @@ async function runEmailScreeningCommand(
|
|
|
255
257
|
channelToken,
|
|
256
258
|
});
|
|
257
259
|
|
|
258
|
-
printEmailIntakeCollection(context, io, response);
|
|
260
|
+
printEmailIntakeCollection(args, context, io, response);
|
|
259
261
|
return;
|
|
260
262
|
}
|
|
261
263
|
|
|
@@ -290,7 +292,7 @@ async function runEmailScreeningCommand(
|
|
|
290
292
|
}
|
|
291
293
|
|
|
292
294
|
default:
|
|
293
|
-
throw new CliError("Unknown inbox
|
|
295
|
+
throw new CliError("Unknown inbox screening subcommand", 2);
|
|
294
296
|
}
|
|
295
297
|
}
|
|
296
298
|
|
|
@@ -368,7 +370,10 @@ function parseMailboxFilter(value: string | undefined): MailboxFilter | undefine
|
|
|
368
370
|
throw new CliError("--mailbox must be one of: account, channel, all", 2);
|
|
369
371
|
}
|
|
370
372
|
|
|
371
|
-
function parseRecipient(
|
|
373
|
+
async function parseRecipient(
|
|
374
|
+
context: CommandContext,
|
|
375
|
+
value: string,
|
|
376
|
+
): Promise<InboxRecipient> {
|
|
372
377
|
if (looksLikeEmailAddress(value)) {
|
|
373
378
|
return {
|
|
374
379
|
type: "user",
|
|
@@ -380,6 +385,16 @@ function parseRecipient(value: string): InboxRecipient {
|
|
|
380
385
|
}
|
|
381
386
|
|
|
382
387
|
if (looksLikeUuid(value)) {
|
|
388
|
+
if (await publicUserExists(context, value)) {
|
|
389
|
+
return {
|
|
390
|
+
type: "user",
|
|
391
|
+
address: {
|
|
392
|
+
kind: "id",
|
|
393
|
+
value,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
383
398
|
return {
|
|
384
399
|
type: "channel",
|
|
385
400
|
address: {
|
|
@@ -413,11 +428,19 @@ function parseRecipient(value: string): InboxRecipient {
|
|
|
413
428
|
}
|
|
414
429
|
|
|
415
430
|
throw new CliError(
|
|
416
|
-
"Recipient must use one of: @handle, @handle/channel, email@example.com,
|
|
431
|
+
"Recipient must use one of: @handle, @handle/channel, email@example.com, user UUID, or channel UUID",
|
|
417
432
|
2,
|
|
418
433
|
);
|
|
419
434
|
}
|
|
420
435
|
|
|
436
|
+
async function publicUserExists(
|
|
437
|
+
context: CommandContext,
|
|
438
|
+
id: string,
|
|
439
|
+
): Promise<boolean> {
|
|
440
|
+
const response = await context.client.listPublicUsersById([id]);
|
|
441
|
+
return response.items.some((user) => user.id === id);
|
|
442
|
+
}
|
|
443
|
+
|
|
421
444
|
function parseHandleChannel(
|
|
422
445
|
value: string,
|
|
423
446
|
): { ownerHandle: string; channelName: string } | undefined {
|
|
@@ -440,6 +463,7 @@ function looksLikeEmailAddress(value: string): boolean {
|
|
|
440
463
|
}
|
|
441
464
|
|
|
442
465
|
async function printThreadCollection(
|
|
466
|
+
args: ParsedArgs,
|
|
443
467
|
context: CommandContext,
|
|
444
468
|
io: Io,
|
|
445
469
|
response: {
|
|
@@ -449,10 +473,13 @@ async function printThreadCollection(
|
|
|
449
473
|
channelToken?: string,
|
|
450
474
|
): Promise<void> {
|
|
451
475
|
if (context.outputMode === "json") {
|
|
452
|
-
printJson(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
476
|
+
printJson(
|
|
477
|
+
io,
|
|
478
|
+
paginatedJson(args, {
|
|
479
|
+
items: response.items,
|
|
480
|
+
nextCursor: response.nextCursor,
|
|
481
|
+
}),
|
|
482
|
+
);
|
|
456
483
|
return Promise.resolve();
|
|
457
484
|
}
|
|
458
485
|
|
|
@@ -479,9 +506,19 @@ async function printThreadCollection(
|
|
|
479
506
|
expiresAt: item.attributes.expires_at ?? "",
|
|
480
507
|
})),
|
|
481
508
|
);
|
|
509
|
+
const pagination = paginationInfo(args, response.nextCursor);
|
|
510
|
+
const message = renderPagination(
|
|
511
|
+
pagination?.nextCursor,
|
|
512
|
+
pagination?.nextCommand,
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
if (message) {
|
|
516
|
+
io.stdout(message);
|
|
517
|
+
}
|
|
482
518
|
}
|
|
483
519
|
|
|
484
520
|
function renderThreadWithMessages(
|
|
521
|
+
args: ParsedArgs,
|
|
485
522
|
thread: { id: string; attributes: ThreadAttributes },
|
|
486
523
|
messages: {
|
|
487
524
|
items: Array<{ id: string; attributes: MessageAttributes }>;
|
|
@@ -496,6 +533,7 @@ function renderThreadWithMessages(
|
|
|
496
533
|
: messages.items
|
|
497
534
|
.map((message) => renderMessage(message, publicUsers))
|
|
498
535
|
.join("\n\n");
|
|
536
|
+
const pagination = paginationInfo(args, messages.nextCursor);
|
|
499
537
|
|
|
500
538
|
return joinBlocks([
|
|
501
539
|
`Thread ${thread.id}`,
|
|
@@ -557,7 +595,7 @@ function renderThreadWithMessages(
|
|
|
557
595
|
]),
|
|
558
596
|
),
|
|
559
597
|
renderSection("Messages", messageBlocks),
|
|
560
|
-
renderPagination(
|
|
598
|
+
renderPagination(pagination?.nextCursor, pagination?.nextCommand),
|
|
561
599
|
]);
|
|
562
600
|
}
|
|
563
601
|
|
|
@@ -584,6 +622,7 @@ function renderMessage(
|
|
|
584
622
|
}
|
|
585
623
|
|
|
586
624
|
function printEmailIntakeCollection(
|
|
625
|
+
args: ParsedArgs,
|
|
587
626
|
context: CommandContext,
|
|
588
627
|
io: Io,
|
|
589
628
|
response: {
|
|
@@ -595,11 +634,11 @@ function printEmailIntakeCollection(
|
|
|
595
634
|
io,
|
|
596
635
|
context.outputMode,
|
|
597
636
|
context.outputMode === "json"
|
|
598
|
-
? {
|
|
637
|
+
? paginatedJson(args, {
|
|
599
638
|
items: response.items,
|
|
600
639
|
nextCursor: response.nextCursor,
|
|
601
|
-
}
|
|
602
|
-
: renderEmailIntakeCollection(response),
|
|
640
|
+
})
|
|
641
|
+
: renderEmailIntakeCollection(args, response),
|
|
603
642
|
);
|
|
604
643
|
}
|
|
605
644
|
|
|
@@ -618,16 +657,23 @@ function printEmailIntakeAction(
|
|
|
618
657
|
);
|
|
619
658
|
}
|
|
620
659
|
|
|
621
|
-
function renderEmailIntakeCollection(
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
660
|
+
function renderEmailIntakeCollection(
|
|
661
|
+
args: ParsedArgs,
|
|
662
|
+
response: {
|
|
663
|
+
items: Array<{ id: string; attributes: ExternalEmailIntakeAttributes }>;
|
|
664
|
+
nextCursor?: string;
|
|
665
|
+
},
|
|
666
|
+
): string {
|
|
625
667
|
const body =
|
|
626
668
|
response.items.length === 0
|
|
627
669
|
? "No email intakes."
|
|
628
670
|
: response.items.map((intake) => renderEmailIntake(intake)).join("\n\n");
|
|
629
671
|
|
|
630
|
-
|
|
672
|
+
const pagination = paginationInfo(args, response.nextCursor);
|
|
673
|
+
return joinBlocks([
|
|
674
|
+
body,
|
|
675
|
+
renderPagination(pagination?.nextCursor, pagination?.nextCommand),
|
|
676
|
+
]);
|
|
631
677
|
}
|
|
632
678
|
|
|
633
679
|
function renderEmailIntake(intake: {
|
|
@@ -656,10 +702,13 @@ function renderEmailIntake(intake: {
|
|
|
656
702
|
]);
|
|
657
703
|
}
|
|
658
704
|
|
|
659
|
-
function renderAttachmentCollection(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
705
|
+
function renderAttachmentCollection(
|
|
706
|
+
args: ParsedArgs,
|
|
707
|
+
response: {
|
|
708
|
+
items: Array<{ id: string; attributes: MessageAttachmentAttributes }>;
|
|
709
|
+
nextCursor?: string;
|
|
710
|
+
},
|
|
711
|
+
): string {
|
|
663
712
|
const body =
|
|
664
713
|
response.items.length === 0
|
|
665
714
|
? "No attachments."
|
|
@@ -679,7 +728,11 @@ function renderAttachmentCollection(response: {
|
|
|
679
728
|
})
|
|
680
729
|
.join("\n\n");
|
|
681
730
|
|
|
682
|
-
|
|
731
|
+
const pagination = paginationInfo(args, response.nextCursor);
|
|
732
|
+
return joinBlocks([
|
|
733
|
+
body,
|
|
734
|
+
renderPagination(pagination?.nextCursor, pagination?.nextCommand),
|
|
735
|
+
]);
|
|
683
736
|
}
|
|
684
737
|
|
|
685
738
|
function renderThreadAction(
|
package/src/commands/post.ts
CHANGED
|
@@ -10,12 +10,14 @@ import { resolveBodyInput } from "../lib/body-input";
|
|
|
10
10
|
import { createCommandContext } from "../lib/context";
|
|
11
11
|
import { CliError } from "../lib/errors";
|
|
12
12
|
import {
|
|
13
|
-
formatTimestamp,
|
|
14
|
-
joinBlocks,
|
|
15
13
|
renderBodyBlock,
|
|
16
14
|
renderFields,
|
|
15
|
+
formatTimestamp,
|
|
16
|
+
joinBlocks,
|
|
17
|
+
renderPagination,
|
|
17
18
|
} from "../lib/human";
|
|
18
19
|
import { printJson, printValue, type Io } from "../lib/output";
|
|
20
|
+
import { paginatedJson, paginationInfo } from "../lib/pagination";
|
|
19
21
|
import type { PostAttributes, ShareTokenResponse } from "../types/api";
|
|
20
22
|
|
|
21
23
|
export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
@@ -56,16 +58,16 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
56
58
|
cursor: stringFlag(args.flags, "cursor"),
|
|
57
59
|
});
|
|
58
60
|
|
|
59
|
-
printPostCollection(context.outputMode, io, response);
|
|
61
|
+
printPostCollection(args, context.outputMode, io, response);
|
|
60
62
|
return;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
case "public-list": {
|
|
64
66
|
const response = await context.client.listPublicChannelPosts({
|
|
65
|
-
|
|
67
|
+
publicHandle: requiredPositional(
|
|
66
68
|
args.positionals,
|
|
67
69
|
1,
|
|
68
|
-
"Missing public
|
|
70
|
+
"Missing public handle",
|
|
69
71
|
),
|
|
70
72
|
channelName: requiredPositional(
|
|
71
73
|
args.positionals,
|
|
@@ -76,7 +78,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
76
78
|
cursor: stringFlag(args.flags, "cursor"),
|
|
77
79
|
});
|
|
78
80
|
|
|
79
|
-
printPostCollection(context.outputMode, io, response);
|
|
81
|
+
printPostCollection(args, context.outputMode, io, response);
|
|
80
82
|
return;
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -87,7 +89,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
87
89
|
cursor: stringFlag(args.flags, "cursor"),
|
|
88
90
|
});
|
|
89
91
|
|
|
90
|
-
printPostCollection(context.outputMode, io, response);
|
|
92
|
+
printPostCollection(args, context.outputMode, io, response);
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -144,11 +146,11 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
case "public-get": {
|
|
147
|
-
const post = await context.client.
|
|
148
|
-
|
|
149
|
+
const post = await context.client.getPublicPostByHandle({
|
|
150
|
+
publicHandle: requiredPositional(
|
|
149
151
|
args.positionals,
|
|
150
152
|
1,
|
|
151
|
-
"Missing public
|
|
153
|
+
"Missing public handle",
|
|
152
154
|
),
|
|
153
155
|
channelName: requiredPositional(
|
|
154
156
|
args.positionals,
|
|
@@ -218,6 +220,7 @@ export async function runPostCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
218
220
|
}
|
|
219
221
|
|
|
220
222
|
function printPostCollection(
|
|
223
|
+
args: ParsedArgs,
|
|
221
224
|
outputMode: "json" | "table",
|
|
222
225
|
io: Io,
|
|
223
226
|
response: {
|
|
@@ -226,10 +229,13 @@ function printPostCollection(
|
|
|
226
229
|
},
|
|
227
230
|
): void {
|
|
228
231
|
if (outputMode === "json") {
|
|
229
|
-
printJson(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
232
|
+
printJson(
|
|
233
|
+
io,
|
|
234
|
+
paginatedJson(args, {
|
|
235
|
+
items: response.items,
|
|
236
|
+
nextCursor: response.nextCursor,
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
233
239
|
return;
|
|
234
240
|
}
|
|
235
241
|
|
|
@@ -243,6 +249,16 @@ function printPostCollection(
|
|
|
243
249
|
body: item.attributes.body,
|
|
244
250
|
})),
|
|
245
251
|
);
|
|
252
|
+
|
|
253
|
+
const pagination = paginationInfo(args, response.nextCursor);
|
|
254
|
+
const message = renderPagination(
|
|
255
|
+
pagination?.nextCursor,
|
|
256
|
+
pagination?.nextCommand,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
if (message) {
|
|
260
|
+
io.stdout(message);
|
|
261
|
+
}
|
|
246
262
|
}
|
|
247
263
|
|
|
248
264
|
function renderPostDetail(
|
package/src/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
|
@@ -122,24 +122,9 @@ export class ClankmatesClient {
|
|
|
122
122
|
);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
async
|
|
126
|
-
return this.requestResource<UserAttributes>(`${API_PREFIX}/me/public-handle`, {
|
|
127
|
-
method: "PATCH",
|
|
128
|
-
token: requireMasterToken(this.profile),
|
|
129
|
-
body: {
|
|
130
|
-
data: {
|
|
131
|
-
type: "user",
|
|
132
|
-
attributes: {
|
|
133
|
-
public_handle: publicHandle,
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async getUserByPublicIdentifier(publicIdentifier: string) {
|
|
125
|
+
async getUserByPublicHandle(publicHandle: string) {
|
|
141
126
|
return this.requestResource<UserAttributes>(
|
|
142
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(
|
|
127
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}`,
|
|
143
128
|
{},
|
|
144
129
|
);
|
|
145
130
|
}
|
|
@@ -189,21 +174,21 @@ export class ClankmatesClient {
|
|
|
189
174
|
);
|
|
190
175
|
}
|
|
191
176
|
|
|
192
|
-
async
|
|
177
|
+
async getPublicChannelByHandle(publicHandle: string, name: string) {
|
|
193
178
|
return this.requestResource<ChannelAttributes>(
|
|
194
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(
|
|
179
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(publicHandle)}/channels/${encodeURIComponent(name)}`,
|
|
195
180
|
{},
|
|
196
181
|
);
|
|
197
182
|
}
|
|
198
183
|
|
|
199
|
-
async
|
|
200
|
-
|
|
184
|
+
async listPublicChannelsForHandle(input: {
|
|
185
|
+
publicHandle: string;
|
|
201
186
|
limit?: number;
|
|
202
187
|
cursor?: string;
|
|
203
188
|
}) {
|
|
204
189
|
return this.requestCollection<ChannelAttributes>(
|
|
205
190
|
withQuery(
|
|
206
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(input.
|
|
191
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels`,
|
|
207
192
|
{
|
|
208
193
|
"page[limit]": input.limit,
|
|
209
194
|
"page[after]": input.cursor,
|
|
@@ -405,14 +390,14 @@ export class ClankmatesClient {
|
|
|
405
390
|
}
|
|
406
391
|
|
|
407
392
|
async listPublicChannelPosts(input: {
|
|
408
|
-
|
|
393
|
+
publicHandle: string;
|
|
409
394
|
channelName: string;
|
|
410
395
|
limit?: number;
|
|
411
396
|
cursor?: string;
|
|
412
397
|
}) {
|
|
413
398
|
return this.requestCollection<PostAttributes>(
|
|
414
399
|
withQuery(
|
|
415
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(input.
|
|
400
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels/${encodeURIComponent(input.channelName)}/posts`,
|
|
416
401
|
{
|
|
417
402
|
"page[limit]": input.limit,
|
|
418
403
|
"page[after]": input.cursor,
|
|
@@ -445,13 +430,13 @@ export class ClankmatesClient {
|
|
|
445
430
|
);
|
|
446
431
|
}
|
|
447
432
|
|
|
448
|
-
async
|
|
449
|
-
|
|
433
|
+
async getPublicPostByHandle(input: {
|
|
434
|
+
publicHandle: string;
|
|
450
435
|
channelName: string;
|
|
451
436
|
postId: string;
|
|
452
437
|
}) {
|
|
453
438
|
return this.requestResource<PostAttributes>(
|
|
454
|
-
`${API_PREFIX}/public/users/${encodeURIComponent(input.
|
|
439
|
+
`${API_PREFIX}/public/users/${encodeURIComponent(input.publicHandle)}/channels/${encodeURIComponent(input.channelName)}/posts/${input.postId}`,
|
|
455
440
|
{},
|
|
456
441
|
);
|
|
457
442
|
}
|
package/src/lib/config.ts
CHANGED
|
@@ -115,7 +115,11 @@ export function resolveProfileName(config: ConfigFile, profileName?: string): st
|
|
|
115
115
|
return profileName ?? process.env.CLANKMATES_PROFILE ?? config.activeProfile;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
export function resolveProfile(
|
|
118
|
+
export function resolveProfile(
|
|
119
|
+
config: ConfigFile,
|
|
120
|
+
profileName?: string,
|
|
121
|
+
baseUrl?: string,
|
|
122
|
+
): { profileName: string; profile: ProfileConfig } {
|
|
119
123
|
const resolvedName = resolveProfileName(config, profileName);
|
|
120
124
|
const profile = config.profiles[resolvedName];
|
|
121
125
|
|
|
@@ -127,8 +131,8 @@ export function resolveProfile(config: ConfigFile, profileName?: string): { prof
|
|
|
127
131
|
profileName: resolvedName,
|
|
128
132
|
profile: {
|
|
129
133
|
...profile,
|
|
130
|
-
baseUrl: resolveBaseUrl(
|
|
131
|
-
}
|
|
134
|
+
baseUrl: resolveBaseUrl(baseUrl, profile.baseUrl),
|
|
135
|
+
},
|
|
132
136
|
};
|
|
133
137
|
}
|
|
134
138
|
|
package/src/lib/context.ts
CHANGED
|
@@ -18,7 +18,11 @@ export interface CommandContext {
|
|
|
18
18
|
export async function createCommandContext(args: ParsedArgs, io: Io): Promise<CommandContext> {
|
|
19
19
|
const configPath = getConfigPath();
|
|
20
20
|
const config = await loadConfig(configPath);
|
|
21
|
-
const { profileName, profile } = resolveProfile(
|
|
21
|
+
const { profileName, profile } = resolveProfile(
|
|
22
|
+
config,
|
|
23
|
+
stringFlag(args.flags, "profile"),
|
|
24
|
+
stringFlag(args.flags, "baseUrl"),
|
|
25
|
+
);
|
|
22
26
|
|
|
23
27
|
return {
|
|
24
28
|
config,
|
package/src/lib/help.ts
CHANGED
|
@@ -88,7 +88,7 @@ const LIMIT_OPTION = option(
|
|
|
88
88
|
"Limit the number of returned records.",
|
|
89
89
|
);
|
|
90
90
|
const CURSOR_OPTION = option(
|
|
91
|
-
"--cursor <
|
|
91
|
+
"--cursor <cursor>",
|
|
92
92
|
"Resume from a pagination cursor returned by a prior request.",
|
|
93
93
|
);
|
|
94
94
|
const CHANNEL_TOKEN_OPTION = option(
|
|
@@ -303,7 +303,7 @@ 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",
|
|
@@ -313,14 +313,6 @@ const HELP_ROOT = group(
|
|
|
313
313
|
options: [PROFILE_OPTION, JSON_OPTION],
|
|
314
314
|
},
|
|
315
315
|
),
|
|
316
|
-
command(
|
|
317
|
-
"claim-handle",
|
|
318
|
-
"Claim or update the owner public handle.",
|
|
319
|
-
`${CLI_NAME} user claim-handle <public-handle> [--profile <name>] [--json]`,
|
|
320
|
-
{
|
|
321
|
-
options: [PROFILE_OPTION, JSON_OPTION],
|
|
322
|
-
},
|
|
323
|
-
),
|
|
324
316
|
],
|
|
325
317
|
{
|
|
326
318
|
usage: [`${CLI_NAME} user <subcommand>`],
|
|
@@ -333,7 +325,7 @@ const HELP_ROOT = group(
|
|
|
333
325
|
command(
|
|
334
326
|
"list",
|
|
335
327
|
"List owned channels.",
|
|
336
|
-
`${CLI_NAME} channel list [--limit <n>] [--cursor <
|
|
328
|
+
`${CLI_NAME} channel list [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
337
329
|
{
|
|
338
330
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
339
331
|
},
|
|
@@ -360,7 +352,7 @@ const HELP_ROOT = group(
|
|
|
360
352
|
command(
|
|
361
353
|
"public-list",
|
|
362
354
|
"List publicly visible channels for a public handle.",
|
|
363
|
-
`${CLI_NAME} channel public-list <public-handle> [--limit <n>] [--cursor <
|
|
355
|
+
`${CLI_NAME} channel public-list <public-handle> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
364
356
|
{
|
|
365
357
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
366
358
|
},
|
|
@@ -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],
|
|
@@ -458,7 +450,7 @@ const HELP_ROOT = group(
|
|
|
458
450
|
command(
|
|
459
451
|
"list",
|
|
460
452
|
"List channel publish keys for one channel.",
|
|
461
|
-
`${CLI_NAME} channel token list <channel> [--limit <n>] [--cursor <
|
|
453
|
+
`${CLI_NAME} channel token list <channel> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
462
454
|
{
|
|
463
455
|
options: [
|
|
464
456
|
LIMIT_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>`],
|
|
@@ -553,7 +522,7 @@ const HELP_ROOT = group(
|
|
|
553
522
|
command(
|
|
554
523
|
"list",
|
|
555
524
|
"List posts for one owned channel.",
|
|
556
|
-
`${CLI_NAME} post list --channel <name-or-uuid> [--limit <n>] [--cursor <
|
|
525
|
+
`${CLI_NAME} post list --channel <name-or-uuid> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
557
526
|
{
|
|
558
527
|
options: [
|
|
559
528
|
option(
|
|
@@ -594,7 +563,7 @@ 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-handle> <channel-name> [--limit <n>] [--cursor <
|
|
566
|
+
`${CLI_NAME} post public-list <public-handle> <channel-name> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
598
567
|
{
|
|
599
568
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
600
569
|
},
|
|
@@ -610,7 +579,7 @@ const HELP_ROOT = group(
|
|
|
610
579
|
command(
|
|
611
580
|
"shared-list",
|
|
612
581
|
"List posts in a shared channel by share token.",
|
|
613
|
-
`${CLI_NAME} post shared-list <share-token> [--limit <n>] [--cursor <
|
|
582
|
+
`${CLI_NAME} post shared-list <share-token> [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
614
583
|
{
|
|
615
584
|
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
616
585
|
},
|
|
@@ -658,7 +627,7 @@ const HELP_ROOT = group(
|
|
|
658
627
|
command(
|
|
659
628
|
"my",
|
|
660
629
|
"List posts from the owner feed.",
|
|
661
|
-
`${CLI_NAME} feed my [--channel <name-or-uuid>] [--limit <n>] [--cursor <
|
|
630
|
+
`${CLI_NAME} feed my [--channel <name-or-uuid>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
662
631
|
{
|
|
663
632
|
options: [
|
|
664
633
|
option(
|
|
@@ -675,7 +644,7 @@ const HELP_ROOT = group(
|
|
|
675
644
|
command(
|
|
676
645
|
"search",
|
|
677
646
|
"Search the owner feed.",
|
|
678
|
-
`${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--limit <n>] [--cursor <
|
|
647
|
+
`${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--limit <n>] [--cursor <cursor>] [--profile <name>] [--json]`,
|
|
679
648
|
{
|
|
680
649
|
options: [
|
|
681
650
|
option(
|
|
@@ -701,7 +670,7 @@ const HELP_ROOT = group(
|
|
|
701
670
|
command(
|
|
702
671
|
"list",
|
|
703
672
|
"List inbox threads.",
|
|
704
|
-
`${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--limit <n>] [--cursor <
|
|
673
|
+
`${CLI_NAME} inbox list [--status <pending|open|blocked|all>] [--mailbox <account|channel|all>] [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
705
674
|
{
|
|
706
675
|
options: [
|
|
707
676
|
option(
|
|
@@ -723,7 +692,7 @@ const HELP_ROOT = group(
|
|
|
723
692
|
command(
|
|
724
693
|
"show",
|
|
725
694
|
"Show one thread and its recent messages.",
|
|
726
|
-
`${CLI_NAME} inbox show <thread-id> [--limit <n>] [--cursor <
|
|
695
|
+
`${CLI_NAME} inbox show <thread-id> [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
727
696
|
{
|
|
728
697
|
options: [
|
|
729
698
|
LIMIT_OPTION,
|
|
@@ -737,7 +706,7 @@ const HELP_ROOT = group(
|
|
|
737
706
|
command(
|
|
738
707
|
"attachments",
|
|
739
708
|
"List attachment metadata for one message.",
|
|
740
|
-
`${CLI_NAME} inbox attachments <message-id> [--limit <n>] [--cursor <
|
|
709
|
+
`${CLI_NAME} inbox attachments <message-id> [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
741
710
|
{
|
|
742
711
|
options: [
|
|
743
712
|
LIMIT_OPTION,
|
|
@@ -768,7 +737,8 @@ const HELP_ROOT = group(
|
|
|
768
737
|
JSON_OPTION,
|
|
769
738
|
],
|
|
770
739
|
notes: [
|
|
771
|
-
"Recipient addresses support `@handle`, `@handle/channel`, `email@example.com`, and channel UUIDs.",
|
|
740
|
+
"Recipient addresses support `@handle`, `@handle/channel`, `email@example.com`, user UUIDs, and channel UUIDs.",
|
|
741
|
+
"For bare UUIDs, the CLI treats a public user id as an account recipient and otherwise sends to a channel id.",
|
|
772
742
|
],
|
|
773
743
|
},
|
|
774
744
|
),
|
|
@@ -826,13 +796,13 @@ const HELP_ROOT = group(
|
|
|
826
796
|
},
|
|
827
797
|
),
|
|
828
798
|
group(
|
|
829
|
-
"
|
|
830
|
-
"Inspect
|
|
799
|
+
"screening",
|
|
800
|
+
"Inspect screened inbox intakes and apply decisions.",
|
|
831
801
|
[
|
|
832
802
|
command(
|
|
833
803
|
"list",
|
|
834
804
|
"List screened external email waiting for a decision.",
|
|
835
|
-
`${CLI_NAME} inbox
|
|
805
|
+
`${CLI_NAME} inbox screening list [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
836
806
|
{
|
|
837
807
|
options: [
|
|
838
808
|
LIMIT_OPTION,
|
|
@@ -846,7 +816,7 @@ const HELP_ROOT = group(
|
|
|
846
816
|
command(
|
|
847
817
|
"processing",
|
|
848
818
|
"List released external email in the processing queue.",
|
|
849
|
-
`${CLI_NAME} inbox
|
|
819
|
+
`${CLI_NAME} inbox screening processing [--limit <n>] [--cursor <cursor>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
850
820
|
{
|
|
851
821
|
options: [
|
|
852
822
|
LIMIT_OPTION,
|
|
@@ -860,7 +830,7 @@ const HELP_ROOT = group(
|
|
|
860
830
|
command(
|
|
861
831
|
"approve",
|
|
862
832
|
"Approve this sender and release one screened email.",
|
|
863
|
-
`${CLI_NAME} inbox
|
|
833
|
+
`${CLI_NAME} inbox screening approve <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
864
834
|
{
|
|
865
835
|
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
866
836
|
},
|
|
@@ -868,7 +838,7 @@ const HELP_ROOT = group(
|
|
|
868
838
|
command(
|
|
869
839
|
"approve-once",
|
|
870
840
|
"Release one screened email without trusting future mail.",
|
|
871
|
-
`${CLI_NAME} inbox
|
|
841
|
+
`${CLI_NAME} inbox screening approve-once <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
872
842
|
{
|
|
873
843
|
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
874
844
|
},
|
|
@@ -876,15 +846,16 @@ const HELP_ROOT = group(
|
|
|
876
846
|
command(
|
|
877
847
|
"ignore",
|
|
878
848
|
"Ignore this sender and suppress future mail.",
|
|
879
|
-
`${CLI_NAME} inbox
|
|
849
|
+
`${CLI_NAME} inbox screening ignore <intake-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
880
850
|
{
|
|
881
851
|
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
882
852
|
},
|
|
883
853
|
),
|
|
884
854
|
],
|
|
885
855
|
{
|
|
886
|
-
usage: [`${CLI_NAME} inbox
|
|
856
|
+
usage: [`${CLI_NAME} inbox screening <subcommand>`],
|
|
887
857
|
notes: [
|
|
858
|
+
"Use `inbox list --status pending` for pending first-contact threads.",
|
|
888
859
|
"Reads allow owner-read tokens or channel tokens.",
|
|
889
860
|
"Decision actions require a master token unless you provide `--channel-token`.",
|
|
890
861
|
],
|
package/src/lib/human.ts
CHANGED
|
@@ -80,8 +80,17 @@ export function renderBodyBlock(body: string): string {
|
|
|
80
80
|
return indent(normalized);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
export function renderPagination(
|
|
84
|
-
|
|
83
|
+
export function renderPagination(
|
|
84
|
+
nextCursor?: string | null,
|
|
85
|
+
nextCommand?: string,
|
|
86
|
+
): string {
|
|
87
|
+
if (!nextCursor) {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return nextCommand
|
|
92
|
+
? `More results: ${nextCommand}`
|
|
93
|
+
: `More results: --cursor ${nextCursor}`;
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
export function joinBlocks(blocks: Array<string | undefined | null | false>): string {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ParsedArgs } from "./args";
|
|
2
|
+
|
|
3
|
+
const CLI_NAME = "clankm";
|
|
4
|
+
|
|
5
|
+
const PAGINATION_FLAG_ORDER: Array<
|
|
6
|
+
[key: string, flag: string]
|
|
7
|
+
> = [
|
|
8
|
+
["profile", "--profile"],
|
|
9
|
+
["baseUrl", "--base-url"],
|
|
10
|
+
["channel", "--channel"],
|
|
11
|
+
["channelId", "--channel"],
|
|
12
|
+
["status", "--status"],
|
|
13
|
+
["mailbox", "--mailbox"],
|
|
14
|
+
["limit", "--limit"],
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export interface PaginationInfo {
|
|
18
|
+
nextCursor: string;
|
|
19
|
+
nextCommand?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function paginationInfo(
|
|
23
|
+
args: ParsedArgs,
|
|
24
|
+
nextCursor?: string | null,
|
|
25
|
+
options: { json?: boolean } = {},
|
|
26
|
+
): PaginationInfo | undefined {
|
|
27
|
+
if (!nextCursor) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
nextCursor,
|
|
33
|
+
nextCommand: args.flags.channelToken
|
|
34
|
+
? undefined
|
|
35
|
+
: nextCommand(args, nextCursor, options),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function paginatedJson<T extends object>(
|
|
40
|
+
args: ParsedArgs,
|
|
41
|
+
response: T & { nextCursor?: string },
|
|
42
|
+
): T & { nextCursor?: string; pagination?: PaginationInfo } {
|
|
43
|
+
return {
|
|
44
|
+
...response,
|
|
45
|
+
pagination: paginationInfo(args, response.nextCursor, { json: true }),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function paginationFlagParts(args: ParsedArgs): string[] {
|
|
50
|
+
const parts: string[] = [];
|
|
51
|
+
const seenFlags = new Set<string>();
|
|
52
|
+
|
|
53
|
+
for (const [key, flag] of PAGINATION_FLAG_ORDER) {
|
|
54
|
+
if (seenFlags.has(flag)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const value = args.flags[key];
|
|
59
|
+
|
|
60
|
+
if (typeof value !== "string") {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
parts.push(flag, value);
|
|
65
|
+
seenFlags.add(flag);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return parts;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function nextCommand(
|
|
72
|
+
args: ParsedArgs,
|
|
73
|
+
nextCursor: string,
|
|
74
|
+
options: { json?: boolean },
|
|
75
|
+
): string {
|
|
76
|
+
const parts = [
|
|
77
|
+
CLI_NAME,
|
|
78
|
+
...args.commandPath,
|
|
79
|
+
...args.positionals,
|
|
80
|
+
...paginationFlagParts(args),
|
|
81
|
+
"--cursor",
|
|
82
|
+
nextCursor,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
if (options.json) {
|
|
86
|
+
parts.push("--json");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return parts.map(shellQuote).join(" ");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function shellQuote(value: string): string {
|
|
93
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
98
|
+
}
|