@clankmates/cli 0.11.1 → 0.13.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 +7 -3
- package/package.json +1 -1
- package/skills/codex/clankmates/SKILL.md +4 -3
- package/src/commands/auth/access-keys.ts +206 -0
- package/src/commands/auth.ts +3 -196
- package/src/commands/channel/render.ts +224 -0
- package/src/commands/channel/tokens.ts +145 -0
- package/src/commands/channel/validation.ts +11 -0
- package/src/commands/channel.ts +11 -340
- package/src/commands/doctor/checks.ts +123 -0
- package/src/commands/doctor/render.ts +140 -0
- package/src/commands/doctor/suggestions.ts +42 -0
- package/src/commands/doctor/types.ts +75 -0
- package/src/commands/doctor.ts +12 -371
- package/src/commands/feed.ts +19 -178
- package/src/commands/inbox/content.ts +31 -0
- package/src/commands/inbox/filters.ts +70 -0
- package/src/commands/inbox/messages.ts +69 -0
- package/src/commands/inbox/participants.ts +152 -0
- package/src/commands/inbox/render.ts +13 -0
- package/src/commands/inbox/resource-output.ts +217 -0
- package/src/commands/inbox/schema.ts +185 -0
- package/src/commands/inbox/screening.ts +76 -0
- package/src/commands/inbox/sync-scopes.ts +59 -0
- package/src/commands/inbox/thread-output.ts +344 -0
- package/src/commands/inbox/watch.ts +203 -0
- package/src/commands/inbox.ts +58 -1220
- package/src/commands/post.ts +24 -116
- package/src/lib/args.ts +8 -0
- package/src/lib/cache/scopes.ts +216 -0
- package/src/lib/cache/store.ts +195 -0
- package/src/lib/cache/types.ts +31 -0
- package/src/lib/cache.ts +18 -382
- package/src/lib/client/auth.ts +122 -0
- package/src/lib/client/channel-keys.ts +57 -0
- package/src/lib/client/channels.ts +364 -0
- package/src/lib/client/core.ts +133 -0
- package/src/lib/client/feed.ts +76 -0
- package/src/lib/client/inbox.ts +361 -0
- package/src/lib/client/posts.ts +213 -0
- package/src/lib/client/raw-api.ts +33 -0
- package/src/lib/client/users.ts +88 -0
- package/src/lib/client.ts +197 -894
- package/src/lib/help.ts +66 -9
- package/src/lib/json_api.ts +74 -9
- package/src/lib/pagination.ts +5 -0
- package/src/lib/polling.ts +146 -0
- package/src/lib/post-output.ts +55 -0
- package/src/types/api.ts +1 -0
package/src/commands/feed.ts
CHANGED
|
@@ -3,14 +3,10 @@ import {
|
|
|
3
3
|
assertSinceFlags,
|
|
4
4
|
cacheFlags,
|
|
5
5
|
cacheResult,
|
|
6
|
-
changeResponseMeta,
|
|
7
6
|
feedMyScope,
|
|
8
7
|
feedSearchScope,
|
|
9
|
-
prepareCachePlan,
|
|
10
|
-
saveCacheTimestamp,
|
|
11
|
-
type CachePlan,
|
|
12
|
-
type CacheResult,
|
|
13
8
|
type CacheScope,
|
|
9
|
+
changeResponseMeta,
|
|
14
10
|
} from "../lib/cache";
|
|
15
11
|
import {
|
|
16
12
|
channelFlag,
|
|
@@ -21,10 +17,16 @@ import {
|
|
|
21
17
|
} from "../lib/args";
|
|
22
18
|
import { createCommandContext, type CommandContext } from "../lib/context";
|
|
23
19
|
import { CliError } from "../lib/errors";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
import type { Io } from "../lib/output";
|
|
21
|
+
import {
|
|
22
|
+
maybePrepareCachePlan,
|
|
23
|
+
maybeSaveCacheTimestamp,
|
|
24
|
+
parseLatestFirstOrder,
|
|
25
|
+
printChangeCheckResponse,
|
|
26
|
+
requiredSince,
|
|
27
|
+
resolvedSince,
|
|
28
|
+
} from "../lib/polling";
|
|
29
|
+
import { printPostCollection } from "../lib/post-output";
|
|
28
30
|
|
|
29
31
|
export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
30
32
|
const subcommand = args.positionals[0];
|
|
@@ -40,6 +42,7 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
40
42
|
channelId,
|
|
41
43
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
42
44
|
cursor: stringFlag(args.flags, "cursor"),
|
|
45
|
+
before: stringFlag(args.flags, "before"),
|
|
43
46
|
order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
|
|
44
47
|
since: resolvedSince(args, cachePlan),
|
|
45
48
|
});
|
|
@@ -51,9 +54,9 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
51
54
|
response.nextCursor === undefined,
|
|
52
55
|
);
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
printPostCollection(
|
|
55
58
|
args,
|
|
56
|
-
context,
|
|
59
|
+
context.outputMode,
|
|
57
60
|
io,
|
|
58
61
|
response,
|
|
59
62
|
cacheResult(cachePlan, savedServerTimestamp),
|
|
@@ -77,6 +80,7 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
77
80
|
channelId,
|
|
78
81
|
limit: integerFlag(args.flags, "limit", { label: "--limit" }),
|
|
79
82
|
cursor: stringFlag(args.flags, "cursor"),
|
|
83
|
+
before: stringFlag(args.flags, "before"),
|
|
80
84
|
order: parseLatestFirstOrder(stringFlag(args.flags, "order")),
|
|
81
85
|
since: resolvedSince(args, cachePlan),
|
|
82
86
|
});
|
|
@@ -88,9 +92,9 @@ export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
88
92
|
response.nextCursor === undefined,
|
|
89
93
|
);
|
|
90
94
|
|
|
91
|
-
|
|
95
|
+
printPostCollection(
|
|
92
96
|
args,
|
|
93
|
-
context,
|
|
97
|
+
context.outputMode,
|
|
94
98
|
io,
|
|
95
99
|
response,
|
|
96
100
|
cacheResult(cachePlan, savedServerTimestamp),
|
|
@@ -138,135 +142,6 @@ async function resolveChannelId(
|
|
|
138
142
|
return channel ? context.client.resolveChannelId(channel) : undefined;
|
|
139
143
|
}
|
|
140
144
|
|
|
141
|
-
function printFeedResponse(
|
|
142
|
-
args: ParsedArgs,
|
|
143
|
-
context: CommandContext,
|
|
144
|
-
io: Io,
|
|
145
|
-
response: {
|
|
146
|
-
items: Array<{ id: string; attributes: PostAttributes }>;
|
|
147
|
-
nextCursor?: string;
|
|
148
|
-
meta?: Record<string, unknown>;
|
|
149
|
-
},
|
|
150
|
-
cache?: CacheResult,
|
|
151
|
-
): void {
|
|
152
|
-
if (context.outputMode === "json") {
|
|
153
|
-
printJson(
|
|
154
|
-
io,
|
|
155
|
-
paginatedJson(args, {
|
|
156
|
-
items: response.items,
|
|
157
|
-
nextCursor: response.nextCursor,
|
|
158
|
-
meta: response.meta,
|
|
159
|
-
...(cache ? { cache } : {}),
|
|
160
|
-
}),
|
|
161
|
-
);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const rows = response.items.map((item) => ({
|
|
166
|
-
id: item.id,
|
|
167
|
-
source: item.attributes.source,
|
|
168
|
-
date: item.attributes.updated_at ?? item.attributes.inserted_at ?? "",
|
|
169
|
-
body: item.attributes.body,
|
|
170
|
-
}));
|
|
171
|
-
printValue(io, context.outputMode, rows);
|
|
172
|
-
|
|
173
|
-
const pagination = paginationInfo(args, response.nextCursor);
|
|
174
|
-
const message = renderPagination(
|
|
175
|
-
pagination?.nextCursor,
|
|
176
|
-
pagination?.nextCommand,
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
if (message) {
|
|
180
|
-
io.stdout(message);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
printCacheNote(io, cache);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function requiredSince(args: ParsedArgs, cachePlan?: CachePlan): string {
|
|
187
|
-
const since = stringFlag(args.flags, "since");
|
|
188
|
-
|
|
189
|
-
if (since) {
|
|
190
|
-
return since;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (cachePlan?.previousServerTimestamp) {
|
|
194
|
-
return cachePlan.previousServerTimestamp;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (cacheFlags(args).sinceCache) {
|
|
198
|
-
throw new CliError(
|
|
199
|
-
"No cached server timestamp for this feed scope. Run a read command with `--save-cache` first.",
|
|
200
|
-
2,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (!since) {
|
|
205
|
-
throw new CliError("Missing `--since`", 2);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return since;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function parseLatestFirstOrder(value: string | undefined): LatestFirstOrder | undefined {
|
|
212
|
-
if (!value) {
|
|
213
|
-
return undefined;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (value === "latest" || value === "oldest") {
|
|
217
|
-
return value;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
throw new CliError("--order must be one of: latest, oldest", 2);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function printChangeCheckResponse(
|
|
224
|
-
context: CommandContext,
|
|
225
|
-
io: Io,
|
|
226
|
-
response: ChangeCheckResponse,
|
|
227
|
-
cache?: CacheResult,
|
|
228
|
-
): void {
|
|
229
|
-
printValue(
|
|
230
|
-
io,
|
|
231
|
-
context.outputMode,
|
|
232
|
-
context.outputMode === "json"
|
|
233
|
-
? { ...response, ...(cache ? { cache } : {}) }
|
|
234
|
-
: renderFields([
|
|
235
|
-
["Has updates", response.has_updates ? "yes" : "no"],
|
|
236
|
-
["Server time", formatTimestamp(response.server_time)],
|
|
237
|
-
[
|
|
238
|
-
"Recommended poll",
|
|
239
|
-
response.recommended_poll_after_ms === undefined
|
|
240
|
-
? undefined
|
|
241
|
-
: `${response.recommended_poll_after_ms}ms`,
|
|
242
|
-
],
|
|
243
|
-
...cacheFields(cache),
|
|
244
|
-
]),
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async function maybePrepareCachePlan(
|
|
249
|
-
args: ParsedArgs,
|
|
250
|
-
context: CommandContext,
|
|
251
|
-
scope: CacheScope | undefined,
|
|
252
|
-
): Promise<CachePlan | undefined> {
|
|
253
|
-
return cacheFlags(args).sinceCache && scope
|
|
254
|
-
? prepareCachePlan(context, scope)
|
|
255
|
-
: undefined;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async function maybeSaveCacheTimestamp(
|
|
259
|
-
args: ParsedArgs,
|
|
260
|
-
context: CommandContext,
|
|
261
|
-
scope: CacheScope | undefined,
|
|
262
|
-
meta: Record<string, unknown> | undefined,
|
|
263
|
-
shouldSave: boolean,
|
|
264
|
-
): Promise<string | undefined> {
|
|
265
|
-
return cacheFlags(args).saveCache && scope && shouldSave
|
|
266
|
-
? saveCacheTimestamp(context, scope, meta)
|
|
267
|
-
: undefined;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
145
|
async function maybeFeedMyScope(
|
|
271
146
|
args: ParsedArgs,
|
|
272
147
|
context: CommandContext,
|
|
@@ -280,6 +155,7 @@ async function maybeFeedMyScope(
|
|
|
280
155
|
context,
|
|
281
156
|
actorKey: await authenticatedActorKey(context),
|
|
282
157
|
channelId,
|
|
158
|
+
before: stringFlag(args.flags, "before"),
|
|
283
159
|
});
|
|
284
160
|
}
|
|
285
161
|
|
|
@@ -298,41 +174,6 @@ async function maybeFeedSearchScope(
|
|
|
298
174
|
actorKey: await authenticatedActorKey(context),
|
|
299
175
|
query,
|
|
300
176
|
channelId,
|
|
177
|
+
before: stringFlag(args.flags, "before"),
|
|
301
178
|
});
|
|
302
179
|
}
|
|
303
|
-
|
|
304
|
-
function resolvedSince(
|
|
305
|
-
args: ParsedArgs,
|
|
306
|
-
cachePlan: CachePlan | undefined,
|
|
307
|
-
): string | undefined {
|
|
308
|
-
return stringFlag(args.flags, "since") ?? cachePlan?.previousServerTimestamp;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function printCacheNote(io: Io, cache: CacheResult | undefined): void {
|
|
312
|
-
if (!cache) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const details = [
|
|
317
|
-
cache.previousServerTimestamp
|
|
318
|
-
? `used ${cache.previousServerTimestamp}`
|
|
319
|
-
: cache.hit
|
|
320
|
-
? "used cache"
|
|
321
|
-
: "no cached timestamp",
|
|
322
|
-
cache.savedServerTimestamp ? `saved ${cache.savedServerTimestamp}` : undefined,
|
|
323
|
-
].filter(Boolean);
|
|
324
|
-
|
|
325
|
-
io.stdout(`Cache: ${details.join("; ")}.`);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function cacheFields(cache: CacheResult | undefined): Array<[string, string | undefined]> {
|
|
329
|
-
if (!cache) {
|
|
330
|
-
return [];
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return [
|
|
334
|
-
["Cache scope", cache.scopeKey],
|
|
335
|
-
["Cached timestamp", cache.previousServerTimestamp],
|
|
336
|
-
["Saved timestamp", cache.savedServerTimestamp],
|
|
337
|
-
];
|
|
338
|
-
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { booleanFlag, type ParsedArgs } from "../../lib/args";
|
|
2
|
+
import { resolveBodyInput } from "../../lib/body-input";
|
|
3
|
+
import { CliError } from "../../lib/errors";
|
|
4
|
+
import { resolveJsonInput } from "../../lib/json-input";
|
|
5
|
+
|
|
6
|
+
export async function resolveMessageContent(args: ParsedArgs): Promise<{
|
|
7
|
+
body?: string;
|
|
8
|
+
payload?: Record<string, unknown>;
|
|
9
|
+
}> {
|
|
10
|
+
if (booleanFlag(args.flags, "stdin") && booleanFlag(args.flags, "payloadStdin")) {
|
|
11
|
+
throw new CliError("Use only one of `--stdin` or `--payload-stdin`", 2);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const body = await resolveBodyInput({ flags: args.flags });
|
|
15
|
+
const payload = await resolveJsonInput({
|
|
16
|
+
flags: args.flags,
|
|
17
|
+
inlineKey: "payload",
|
|
18
|
+
fileKey: "payloadFile",
|
|
19
|
+
stdinKey: "payloadStdin",
|
|
20
|
+
label: "Payload",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (body === undefined && payload === undefined) {
|
|
24
|
+
throw new CliError(
|
|
25
|
+
"Provide at least one of `--body`, `--body-file`, `--stdin`, `--payload`, `--payload-file`, or `--payload-stdin`",
|
|
26
|
+
2,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { body, payload };
|
|
31
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CliError } from "../../lib/errors";
|
|
2
|
+
import type {
|
|
3
|
+
ExternalEmailAcceptance,
|
|
4
|
+
MailboxFilter,
|
|
5
|
+
ParticipantScope,
|
|
6
|
+
ThreadStatusFilter,
|
|
7
|
+
} from "../../types/api";
|
|
8
|
+
|
|
9
|
+
export function parseStatusFilter(
|
|
10
|
+
value: string | undefined,
|
|
11
|
+
): ThreadStatusFilter | undefined {
|
|
12
|
+
if (!value) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (value === "pending" || value === "open" || value === "blocked" || value === "all") {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
throw new CliError("--status must be one of: pending, open, blocked, all", 2);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseMailboxFilter(
|
|
24
|
+
value: string | undefined,
|
|
25
|
+
): MailboxFilter | undefined {
|
|
26
|
+
if (!value) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (value === "account" || value === "channel" || value === "all") {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new CliError("--mailbox must be one of: account, channel, all", 2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseParticipantScope(
|
|
38
|
+
value: string | undefined,
|
|
39
|
+
): ParticipantScope | undefined {
|
|
40
|
+
if (!value) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (value === "account" || value === "owner") {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new CliError("--participant-scope must be one of: account, owner", 2);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function parseExternalEmailAcceptance(value: string): ExternalEmailAcceptance {
|
|
52
|
+
if (
|
|
53
|
+
value === "screen_unknown_senders" ||
|
|
54
|
+
value === "screen-unknown-senders"
|
|
55
|
+
) {
|
|
56
|
+
return "screen_unknown_senders";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
value === "accept_valid_typed_email" ||
|
|
61
|
+
value === "accept-valid-typed-email"
|
|
62
|
+
) {
|
|
63
|
+
return "accept_valid_typed_email";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
throw new CliError(
|
|
67
|
+
"External email acceptance policy must be one of: screen-unknown-senders, accept-valid-typed-email",
|
|
68
|
+
2,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertSinceFlags,
|
|
3
|
+
cacheResult,
|
|
4
|
+
changeResponseMeta,
|
|
5
|
+
} from "../../lib/cache";
|
|
6
|
+
import {
|
|
7
|
+
booleanFlag,
|
|
8
|
+
requiredPositional,
|
|
9
|
+
stringFlag,
|
|
10
|
+
type ParsedArgs,
|
|
11
|
+
} from "../../lib/args";
|
|
12
|
+
import type { CommandContext } from "../../lib/context";
|
|
13
|
+
import { CliError } from "../../lib/errors";
|
|
14
|
+
import type { Io } from "../../lib/output";
|
|
15
|
+
import {
|
|
16
|
+
maybePrepareCachePlan,
|
|
17
|
+
maybeSaveCacheTimestamp,
|
|
18
|
+
printChangeCheckResponse,
|
|
19
|
+
requiredSince,
|
|
20
|
+
} from "../../lib/polling";
|
|
21
|
+
import { maybeInboxMessagesScope } from "./sync-scopes";
|
|
22
|
+
|
|
23
|
+
export async function runInboxMessagesCommand(
|
|
24
|
+
context: CommandContext,
|
|
25
|
+
args: ParsedArgs,
|
|
26
|
+
io: Io,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
const subcommand = args.positionals[1];
|
|
29
|
+
|
|
30
|
+
switch (subcommand) {
|
|
31
|
+
case "changes": {
|
|
32
|
+
assertSinceFlags(args);
|
|
33
|
+
const threadId = requiredPositional(args.positionals, 2, "Missing thread id");
|
|
34
|
+
const channelToken = stringFlag(args.flags, "channelToken");
|
|
35
|
+
const cacheScope = await maybeInboxMessagesScope(
|
|
36
|
+
args,
|
|
37
|
+
context,
|
|
38
|
+
channelToken,
|
|
39
|
+
threadId,
|
|
40
|
+
);
|
|
41
|
+
const cachePlan = await maybePrepareCachePlan(args, context, cacheScope);
|
|
42
|
+
const response = await context.client.checkThreadMessageChanges({
|
|
43
|
+
threadId,
|
|
44
|
+
since: requiredSince(args, cachePlan, "inbox message"),
|
|
45
|
+
query: stringFlag(args.flags, "query"),
|
|
46
|
+
hasAttachment: booleanFlag(args.flags, "hasAttachment") || undefined,
|
|
47
|
+
channelToken,
|
|
48
|
+
});
|
|
49
|
+
const savedServerTimestamp = await maybeSaveCacheTimestamp(
|
|
50
|
+
args,
|
|
51
|
+
context,
|
|
52
|
+
cacheScope,
|
|
53
|
+
changeResponseMeta(response),
|
|
54
|
+
response.has_updates === false,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
printChangeCheckResponse(
|
|
58
|
+
context,
|
|
59
|
+
io,
|
|
60
|
+
response,
|
|
61
|
+
cacheResult(cachePlan, savedServerTimestamp),
|
|
62
|
+
);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
default:
|
|
67
|
+
throw new CliError("Unknown inbox messages subcommand", 2);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { stringFlag, type ParsedArgs } from "../../lib/args";
|
|
2
|
+
import type { CommandContext } from "../../lib/context";
|
|
3
|
+
import { CliError } from "../../lib/errors";
|
|
4
|
+
import type { InboxRecipient, InboxSender } from "../../types/api";
|
|
5
|
+
|
|
6
|
+
export async function resolveSender(
|
|
7
|
+
context: CommandContext,
|
|
8
|
+
args: ParsedArgs,
|
|
9
|
+
): Promise<InboxSender | undefined> {
|
|
10
|
+
const from = stringFlag(args.flags, "from");
|
|
11
|
+
const channelToken = stringFlag(args.flags, "channelToken");
|
|
12
|
+
|
|
13
|
+
if (!from) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (looksLikeUuid(from)) {
|
|
18
|
+
return channelSender(from);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (channelToken) {
|
|
22
|
+
const whoami = await context.client.whoami(channelToken);
|
|
23
|
+
|
|
24
|
+
if (whoami.actor.type === "channel") {
|
|
25
|
+
if (whoami.actor.name === from) {
|
|
26
|
+
return channelSender(whoami.actor.id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
throw new CliError(
|
|
30
|
+
"With `--channel-token`, `--from` must match the authenticated channel name or UUID.",
|
|
31
|
+
2,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return channelSender(await context.client.resolveChannelId(from));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function parseRecipient(
|
|
40
|
+
context: CommandContext,
|
|
41
|
+
value: string,
|
|
42
|
+
): Promise<InboxRecipient> {
|
|
43
|
+
if (looksLikeUuid(value)) {
|
|
44
|
+
if (await publicUserExists(context, value)) {
|
|
45
|
+
return {
|
|
46
|
+
type: "user",
|
|
47
|
+
address: {
|
|
48
|
+
kind: "id",
|
|
49
|
+
value,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
type: "channel",
|
|
56
|
+
address: {
|
|
57
|
+
kind: "id",
|
|
58
|
+
value,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (value.startsWith("@")) {
|
|
64
|
+
const handleChannel = parseHandleChannel(value);
|
|
65
|
+
|
|
66
|
+
if (handleChannel) {
|
|
67
|
+
return {
|
|
68
|
+
type: "channel",
|
|
69
|
+
address: {
|
|
70
|
+
kind: "owner_handle_and_channel_name",
|
|
71
|
+
owner_handle: handleChannel.ownerHandle,
|
|
72
|
+
channel_name: handleChannel.channelName,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
type: "user",
|
|
79
|
+
address: {
|
|
80
|
+
kind: "handle",
|
|
81
|
+
value,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
throw new CliError(
|
|
87
|
+
"Recipient must use one of: @handle, @handle/channel, user UUID, or channel UUID",
|
|
88
|
+
2,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function getPublicInboxSchema(
|
|
93
|
+
context: CommandContext,
|
|
94
|
+
target: string,
|
|
95
|
+
) {
|
|
96
|
+
const handleChannel = parseHandleChannel(target);
|
|
97
|
+
|
|
98
|
+
if (handleChannel) {
|
|
99
|
+
return context.client.getPublicChannelInboxSchema(
|
|
100
|
+
handleChannel.ownerHandle,
|
|
101
|
+
handleChannel.channelName,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (target.startsWith("@")) {
|
|
106
|
+
return context.client.getPublicAccountInboxSchema(target);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw new CliError("Schema target must be `@handle` or `@handle/channel`", 2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function publicUserExists(
|
|
113
|
+
context: CommandContext,
|
|
114
|
+
id: string,
|
|
115
|
+
): Promise<boolean> {
|
|
116
|
+
const response = await context.client.listPublicUsersById([id]);
|
|
117
|
+
return response.items.some((user) => user.id === id);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function channelSender(channelId: string): InboxSender {
|
|
121
|
+
return {
|
|
122
|
+
type: "channel",
|
|
123
|
+
address: {
|
|
124
|
+
kind: "id",
|
|
125
|
+
value: channelId,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseHandleChannel(
|
|
131
|
+
value: string,
|
|
132
|
+
): { ownerHandle: string; channelName: string } | undefined {
|
|
133
|
+
const [ownerHandle, channelName, ...extra] = value.split("/");
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
extra.length > 0 ||
|
|
137
|
+
!ownerHandle ||
|
|
138
|
+
!channelName ||
|
|
139
|
+
!ownerHandle.startsWith("@")
|
|
140
|
+
) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { ownerHandle, channelName };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const UUID_PATTERN =
|
|
148
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
149
|
+
|
|
150
|
+
function looksLikeUuid(value: string): boolean {
|
|
151
|
+
return UUID_PATTERN.test(value);
|
|
152
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ownerIdsForThreadDisplay,
|
|
3
|
+
printThreadCollection,
|
|
4
|
+
publicUserHandlesById,
|
|
5
|
+
renderThreadAction,
|
|
6
|
+
renderThreadWithMessages,
|
|
7
|
+
} from "./thread-output";
|
|
8
|
+
export {
|
|
9
|
+
printEmailIntakeAction,
|
|
10
|
+
printEmailIntakeCollection,
|
|
11
|
+
printSchemaResource,
|
|
12
|
+
renderAttachmentCollection,
|
|
13
|
+
} from "./resource-output";
|