@clankmates/cli 0.12.0 → 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 +4 -1
- package/package.json +1 -1
- 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 +15 -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 +37 -1243
- package/src/commands/post.ts +9 -114
- package/src/lib/args.ts +1 -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 -436
- 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 +177 -913
- package/src/lib/help.ts +26 -0
- package/src/lib/json_api.ts +74 -9
- package/src/lib/polling.ts +146 -0
- package/src/lib/post-output.ts +55 -0
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ MISE_FETCH_REMOTE_VERSIONS_CACHE=0 mise upgrade npm:@clankmates/cli
|
|
|
47
47
|
You can also pin an exact release:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
mise install npm:@clankmates/cli@0.
|
|
50
|
+
mise install npm:@clankmates/cli@0.13.0
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
For local development in this repository:
|
|
@@ -106,6 +106,7 @@ bun run cli -- inbox list --since-cache --save-cache --json
|
|
|
106
106
|
bun run cli -- inbox changes --since <server-time> --json
|
|
107
107
|
bun run cli -- inbox show <thread-id> --before <timestamp> --json
|
|
108
108
|
bun run cli -- inbox messages changes <thread-id> --since <server-time> --has-attachment --json
|
|
109
|
+
bun run cli -- inbox watch messages <thread-id> --once
|
|
109
110
|
bun run cli -- inbox send @friend_handle --body-file ./intro.md --json
|
|
110
111
|
bun run cli -- inbox send @victor_news/ops --body-file ./intro.md --json
|
|
111
112
|
bun run cli -- inbox send @victor_news/ops --payload-file ./typed-payload.json --json
|
|
@@ -145,6 +146,8 @@ bun run cli -- cache clear --json
|
|
|
145
146
|
bun run cli -- cache path
|
|
146
147
|
```
|
|
147
148
|
|
|
149
|
+
Watch commands are JSONL-only streams for agents and scripts. `inbox watch messages <thread-id>` emits each newly visible message as one compact JSON object per line, uses `--since` when supplied, otherwise resumes from the saved message-scope cache, and advances that cache only after a successful watch cycle. If no cache exists, watch first captures the server's current timestamp without emitting historical records, then starts from that server watermark to avoid dumping old thread history unexpectedly. Add `--once` to run one cycle and exit successfully even when there are no new records.
|
|
150
|
+
|
|
148
151
|
## Useful Commands
|
|
149
152
|
|
|
150
153
|
Inspect auth state:
|
package/package.json
CHANGED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
booleanFlag,
|
|
3
|
+
requiredPositional,
|
|
4
|
+
requiredStringFlag,
|
|
5
|
+
stringFlag,
|
|
6
|
+
type ParsedArgs,
|
|
7
|
+
} from "../../lib/args";
|
|
8
|
+
import { resolveOutputMode } from "../../lib/context";
|
|
9
|
+
import { loadConfig, resolveProfile, updateProfile } from "../../lib/config";
|
|
10
|
+
import { ClankmatesClient } from "../../lib/client";
|
|
11
|
+
import { CliError } from "../../lib/errors";
|
|
12
|
+
import { joinBlocks, renderFields, renderTokenAction } from "../../lib/human";
|
|
13
|
+
import { printJson, printValue, type Io } from "../../lib/output";
|
|
14
|
+
import { getConfigPath } from "../../lib/paths";
|
|
15
|
+
import type {
|
|
16
|
+
AccessKeyAttributes,
|
|
17
|
+
AccessKeyIssueResponse,
|
|
18
|
+
AccessKeyRevokeResponse,
|
|
19
|
+
AccessKeyScope,
|
|
20
|
+
ProfileConfig,
|
|
21
|
+
} from "../../types/api";
|
|
22
|
+
|
|
23
|
+
export async function runAccessKeyCommand(
|
|
24
|
+
args: ParsedArgs,
|
|
25
|
+
config: Awaited<ReturnType<typeof loadConfig>>,
|
|
26
|
+
io: Io,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
const keyCommand = requiredPositional(
|
|
29
|
+
args.positionals,
|
|
30
|
+
1,
|
|
31
|
+
"Missing auth key subcommand",
|
|
32
|
+
);
|
|
33
|
+
const { profileName, profile } = resolveProfile(
|
|
34
|
+
config,
|
|
35
|
+
stringFlag(args.flags, "profile"),
|
|
36
|
+
stringFlag(args.flags, "baseUrl"),
|
|
37
|
+
);
|
|
38
|
+
const outputMode = resolveOutputMode(profile, args.flags);
|
|
39
|
+
const client = new ClankmatesClient(profile);
|
|
40
|
+
const configPath = getConfigPath();
|
|
41
|
+
|
|
42
|
+
switch (keyCommand) {
|
|
43
|
+
case "list": {
|
|
44
|
+
const scope = parseAccessKeyScope(stringFlag(args.flags, "scope"), false);
|
|
45
|
+
const response = await client.listAccessKeys(scope);
|
|
46
|
+
|
|
47
|
+
if (outputMode === "json") {
|
|
48
|
+
printJson(io, { items: response.items });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
printValue(
|
|
53
|
+
io,
|
|
54
|
+
outputMode,
|
|
55
|
+
response.items.map((item) => formatAccessKeyRow(item)),
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
case "issue": {
|
|
61
|
+
const scope = requireAccessKeyScope(
|
|
62
|
+
requiredStringFlag(args.flags, "scope"),
|
|
63
|
+
);
|
|
64
|
+
const response = await client.issueAccessKey({
|
|
65
|
+
scope,
|
|
66
|
+
name: requiredStringFlag(args.flags, "name"),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (booleanFlag(args.flags, "tokenOnly")) {
|
|
70
|
+
io.stdout(response.token);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
printValue(
|
|
75
|
+
io,
|
|
76
|
+
outputMode,
|
|
77
|
+
outputMode === "json"
|
|
78
|
+
? response
|
|
79
|
+
: renderAccessKeyIssue("Issued owner access key", response),
|
|
80
|
+
);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
case "revoke": {
|
|
85
|
+
const response = await client.revokeAccessKey(
|
|
86
|
+
requiredPositional(args.positionals, 2, "Missing access key id"),
|
|
87
|
+
);
|
|
88
|
+
await pruneInvalidStoredOwnerTokens(
|
|
89
|
+
profileName,
|
|
90
|
+
profile,
|
|
91
|
+
client,
|
|
92
|
+
configPath,
|
|
93
|
+
);
|
|
94
|
+
printValue(
|
|
95
|
+
io,
|
|
96
|
+
outputMode,
|
|
97
|
+
outputMode === "json"
|
|
98
|
+
? response
|
|
99
|
+
: renderAccessKeyRevoke("Revoked owner access key", response),
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
default:
|
|
105
|
+
throw new CliError("Unknown auth key subcommand", 2);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseAccessKeyScope(
|
|
110
|
+
scope: string | undefined,
|
|
111
|
+
required: boolean,
|
|
112
|
+
): AccessKeyScope | undefined {
|
|
113
|
+
if (scope === undefined) {
|
|
114
|
+
if (required) {
|
|
115
|
+
throw new CliError("Missing `--scope`", 2);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (scope !== "master" && scope !== "read_only") {
|
|
122
|
+
throw new CliError("`--scope` must be either `master` or `read_only`.", 2);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return scope;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function requireAccessKeyScope(scope: string): AccessKeyScope {
|
|
129
|
+
const parsed = parseAccessKeyScope(scope, true);
|
|
130
|
+
|
|
131
|
+
if (!parsed) {
|
|
132
|
+
throw new CliError("Missing `--scope`", 2);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return parsed;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatAccessKeyRow(item: { id: string; attributes: AccessKeyAttributes }) {
|
|
139
|
+
return {
|
|
140
|
+
id: item.id,
|
|
141
|
+
scope: item.attributes.scope,
|
|
142
|
+
name: item.attributes.name ?? "",
|
|
143
|
+
expiresAt: item.attributes.expires_at,
|
|
144
|
+
issuedAt: item.attributes.inserted_at ?? "",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function renderAccessKeyIssue(
|
|
149
|
+
title: string,
|
|
150
|
+
response: AccessKeyIssueResponse,
|
|
151
|
+
): string {
|
|
152
|
+
return renderTokenAction({
|
|
153
|
+
title,
|
|
154
|
+
id: response.id,
|
|
155
|
+
name: response.name,
|
|
156
|
+
token: response.token,
|
|
157
|
+
issuedAt: response.issued_at,
|
|
158
|
+
expiresAt: response.expires_at,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function renderAccessKeyRevoke(
|
|
163
|
+
title: string,
|
|
164
|
+
response: AccessKeyRevokeResponse,
|
|
165
|
+
): string {
|
|
166
|
+
return joinBlocks([
|
|
167
|
+
title,
|
|
168
|
+
renderFields([
|
|
169
|
+
["ID", response.id],
|
|
170
|
+
["Scope", response.scope],
|
|
171
|
+
["Name", response.name],
|
|
172
|
+
]),
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function pruneInvalidStoredOwnerTokens(
|
|
177
|
+
profileName: string,
|
|
178
|
+
profile: ProfileConfig,
|
|
179
|
+
client: ClankmatesClient,
|
|
180
|
+
configPath: string,
|
|
181
|
+
): Promise<void> {
|
|
182
|
+
const invalidMasterToken = profile.masterToken
|
|
183
|
+
? !(await client.canAuthenticate(profile.masterToken))
|
|
184
|
+
: false;
|
|
185
|
+
const invalidReadOnlyToken = profile.readOnlyToken
|
|
186
|
+
? !(await client.canAuthenticate(profile.readOnlyToken))
|
|
187
|
+
: false;
|
|
188
|
+
|
|
189
|
+
if (!invalidMasterToken && !invalidReadOnlyToken) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
await updateProfile(
|
|
194
|
+
profileName,
|
|
195
|
+
(storedProfile) => {
|
|
196
|
+
if (invalidMasterToken) {
|
|
197
|
+
storedProfile.masterToken = undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (invalidReadOnlyToken) {
|
|
201
|
+
storedProfile.readOnlyToken = undefined;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
configPath,
|
|
205
|
+
);
|
|
206
|
+
}
|
package/src/commands/auth.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { resolveOutputMode } from "../lib/context";
|
|
|
2
2
|
import {
|
|
3
3
|
booleanFlag,
|
|
4
4
|
requiredPositional,
|
|
5
|
-
requiredStringFlag,
|
|
6
5
|
stringFlag,
|
|
7
6
|
type ParsedArgs,
|
|
8
7
|
} from "../lib/args";
|
|
@@ -15,22 +14,15 @@ import {
|
|
|
15
14
|
} from "../lib/config";
|
|
16
15
|
import { ClankmatesClient } from "../lib/client";
|
|
17
16
|
import { CliError } from "../lib/errors";
|
|
18
|
-
import {
|
|
19
|
-
import { printJson, printValue, type Io } from "../lib/output";
|
|
17
|
+
import { printValue, type Io } from "../lib/output";
|
|
20
18
|
import { getConfigPath } from "../lib/paths";
|
|
21
19
|
import {
|
|
22
20
|
resolveMasterToken,
|
|
23
21
|
resolveOwnerReadToken,
|
|
24
22
|
resolveReadOnlyToken,
|
|
25
23
|
} from "../lib/tokens";
|
|
26
|
-
import type {
|
|
27
|
-
|
|
28
|
-
AccessKeyIssueResponse,
|
|
29
|
-
AccessKeyRevokeResponse,
|
|
30
|
-
AccessKeyScope,
|
|
31
|
-
ProfileConfig,
|
|
32
|
-
WhoamiResponse,
|
|
33
|
-
} from "../types/api";
|
|
24
|
+
import type { ProfileConfig, WhoamiResponse } from "../types/api";
|
|
25
|
+
import { runAccessKeyCommand } from "./auth/access-keys";
|
|
34
26
|
|
|
35
27
|
export async function runAuthCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
36
28
|
const subcommand = args.positionals[0];
|
|
@@ -196,92 +188,6 @@ export async function runAuthCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
|
196
188
|
}
|
|
197
189
|
}
|
|
198
190
|
|
|
199
|
-
async function runAccessKeyCommand(
|
|
200
|
-
args: ParsedArgs,
|
|
201
|
-
config: Awaited<ReturnType<typeof loadConfig>>,
|
|
202
|
-
io: Io,
|
|
203
|
-
): Promise<void> {
|
|
204
|
-
const keyCommand = requiredPositional(
|
|
205
|
-
args.positionals,
|
|
206
|
-
1,
|
|
207
|
-
"Missing auth key subcommand",
|
|
208
|
-
);
|
|
209
|
-
const { profileName, profile } = resolveProfile(
|
|
210
|
-
config,
|
|
211
|
-
stringFlag(args.flags, "profile"),
|
|
212
|
-
stringFlag(args.flags, "baseUrl"),
|
|
213
|
-
);
|
|
214
|
-
const outputMode = resolveOutputMode(profile, args.flags);
|
|
215
|
-
const client = new ClankmatesClient(profile);
|
|
216
|
-
const configPath = getConfigPath();
|
|
217
|
-
|
|
218
|
-
switch (keyCommand) {
|
|
219
|
-
case "list": {
|
|
220
|
-
const scope = parseAccessKeyScope(stringFlag(args.flags, "scope"), false);
|
|
221
|
-
const response = await client.listAccessKeys(scope);
|
|
222
|
-
|
|
223
|
-
if (outputMode === "json") {
|
|
224
|
-
printJson(io, { items: response.items });
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
printValue(
|
|
229
|
-
io,
|
|
230
|
-
outputMode,
|
|
231
|
-
response.items.map((item) => formatAccessKeyRow(item)),
|
|
232
|
-
);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
case "issue": {
|
|
237
|
-
const scope = requireAccessKeyScope(
|
|
238
|
-
requiredStringFlag(args.flags, "scope"),
|
|
239
|
-
);
|
|
240
|
-
const response = await client.issueAccessKey({
|
|
241
|
-
scope,
|
|
242
|
-
name: requiredStringFlag(args.flags, "name"),
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
if (booleanFlag(args.flags, "tokenOnly")) {
|
|
246
|
-
io.stdout(response.token);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
printValue(
|
|
251
|
-
io,
|
|
252
|
-
outputMode,
|
|
253
|
-
outputMode === "json"
|
|
254
|
-
? response
|
|
255
|
-
: renderAccessKeyIssue("Issued owner access key", response),
|
|
256
|
-
);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
case "revoke": {
|
|
261
|
-
const response = await client.revokeAccessKey(
|
|
262
|
-
requiredPositional(args.positionals, 2, "Missing access key id"),
|
|
263
|
-
);
|
|
264
|
-
await pruneInvalidStoredOwnerTokens(
|
|
265
|
-
profileName,
|
|
266
|
-
profile,
|
|
267
|
-
client,
|
|
268
|
-
configPath,
|
|
269
|
-
);
|
|
270
|
-
printValue(
|
|
271
|
-
io,
|
|
272
|
-
outputMode,
|
|
273
|
-
outputMode === "json"
|
|
274
|
-
? response
|
|
275
|
-
: renderAccessKeyRevoke("Revoked owner access key", response),
|
|
276
|
-
);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
default:
|
|
281
|
-
throw new CliError("Unknown auth key subcommand", 2);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
191
|
function defaultProfileConfig(): ProfileConfig {
|
|
286
192
|
return {
|
|
287
193
|
baseUrl: resolveBaseUrl(),
|
|
@@ -332,102 +238,3 @@ function formatWhoamiOutput(
|
|
|
332
238
|
publicUrl: whoami.actor.public_url ?? "",
|
|
333
239
|
};
|
|
334
240
|
}
|
|
335
|
-
|
|
336
|
-
function parseAccessKeyScope(
|
|
337
|
-
scope: string | undefined,
|
|
338
|
-
required: boolean,
|
|
339
|
-
): AccessKeyScope | undefined {
|
|
340
|
-
if (scope === undefined) {
|
|
341
|
-
if (required) {
|
|
342
|
-
throw new CliError("Missing `--scope`", 2);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return undefined;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (scope !== "master" && scope !== "read_only") {
|
|
349
|
-
throw new CliError("`--scope` must be either `master` or `read_only`.", 2);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return scope;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function requireAccessKeyScope(scope: string): AccessKeyScope {
|
|
356
|
-
const parsed = parseAccessKeyScope(scope, true);
|
|
357
|
-
|
|
358
|
-
if (!parsed) {
|
|
359
|
-
throw new CliError("Missing `--scope`", 2);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return parsed;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function formatAccessKeyRow(item: { id: string; attributes: AccessKeyAttributes }) {
|
|
366
|
-
return {
|
|
367
|
-
id: item.id,
|
|
368
|
-
scope: item.attributes.scope,
|
|
369
|
-
name: item.attributes.name ?? "",
|
|
370
|
-
expiresAt: item.attributes.expires_at,
|
|
371
|
-
issuedAt: item.attributes.inserted_at ?? "",
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function renderAccessKeyIssue(
|
|
376
|
-
title: string,
|
|
377
|
-
response: AccessKeyIssueResponse,
|
|
378
|
-
): string {
|
|
379
|
-
return renderTokenAction({
|
|
380
|
-
title,
|
|
381
|
-
id: response.id,
|
|
382
|
-
name: response.name,
|
|
383
|
-
token: response.token,
|
|
384
|
-
issuedAt: response.issued_at,
|
|
385
|
-
expiresAt: response.expires_at,
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function renderAccessKeyRevoke(
|
|
390
|
-
title: string,
|
|
391
|
-
response: AccessKeyRevokeResponse,
|
|
392
|
-
): string {
|
|
393
|
-
return joinBlocks([
|
|
394
|
-
title,
|
|
395
|
-
renderFields([
|
|
396
|
-
["ID", response.id],
|
|
397
|
-
["Scope", response.scope],
|
|
398
|
-
["Name", response.name],
|
|
399
|
-
]),
|
|
400
|
-
]);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async function pruneInvalidStoredOwnerTokens(
|
|
404
|
-
profileName: string,
|
|
405
|
-
profile: ProfileConfig,
|
|
406
|
-
client: ClankmatesClient,
|
|
407
|
-
configPath: string,
|
|
408
|
-
): Promise<void> {
|
|
409
|
-
const invalidMasterToken = profile.masterToken
|
|
410
|
-
? !(await client.canAuthenticate(profile.masterToken))
|
|
411
|
-
: false;
|
|
412
|
-
const invalidReadOnlyToken = profile.readOnlyToken
|
|
413
|
-
? !(await client.canAuthenticate(profile.readOnlyToken))
|
|
414
|
-
: false;
|
|
415
|
-
|
|
416
|
-
if (!invalidMasterToken && !invalidReadOnlyToken) {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
await updateProfile(
|
|
421
|
-
profileName,
|
|
422
|
-
(storedProfile) => {
|
|
423
|
-
if (invalidMasterToken) {
|
|
424
|
-
storedProfile.masterToken = undefined;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (invalidReadOnlyToken) {
|
|
428
|
-
storedProfile.readOnlyToken = undefined;
|
|
429
|
-
}
|
|
430
|
-
},
|
|
431
|
-
configPath,
|
|
432
|
-
);
|
|
433
|
-
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
joinBlocks,
|
|
3
|
+
renderFields,
|
|
4
|
+
renderPagination,
|
|
5
|
+
renderTokenAction,
|
|
6
|
+
} from "../../lib/human";
|
|
7
|
+
import { printJson, printValue, type Io } from "../../lib/output";
|
|
8
|
+
import { paginatedJson, paginationInfo } from "../../lib/pagination";
|
|
9
|
+
import type { ParsedArgs } from "../../lib/args";
|
|
10
|
+
import type {
|
|
11
|
+
ChannelAttributes,
|
|
12
|
+
ChannelDiagnosticsResponse,
|
|
13
|
+
ChannelKeyAttributes,
|
|
14
|
+
ChannelKeyIssueResponse,
|
|
15
|
+
ChannelKeyRevokeResponse,
|
|
16
|
+
ChannelPinResponse,
|
|
17
|
+
ChannelPublicationResponse,
|
|
18
|
+
IdResponse,
|
|
19
|
+
ShareTokenResponse,
|
|
20
|
+
} from "../../types/api";
|
|
21
|
+
|
|
22
|
+
export function printChannelCollection(
|
|
23
|
+
args: ParsedArgs,
|
|
24
|
+
outputMode: "json" | "table",
|
|
25
|
+
io: Io,
|
|
26
|
+
response: {
|
|
27
|
+
items: Array<{ id: string; attributes: ChannelAttributes }>;
|
|
28
|
+
nextCursor?: string;
|
|
29
|
+
},
|
|
30
|
+
): void {
|
|
31
|
+
if (outputMode === "json") {
|
|
32
|
+
printJson(
|
|
33
|
+
io,
|
|
34
|
+
paginatedJson(args, {
|
|
35
|
+
items: response.items,
|
|
36
|
+
nextCursor: response.nextCursor,
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
printValue(
|
|
43
|
+
io,
|
|
44
|
+
outputMode,
|
|
45
|
+
response.items.map((item) => formatChannelRow(item)),
|
|
46
|
+
);
|
|
47
|
+
const pagination = paginationInfo(args, response.nextCursor);
|
|
48
|
+
const message = renderPagination(
|
|
49
|
+
pagination?.nextCursor,
|
|
50
|
+
pagination?.nextCommand,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (message) {
|
|
54
|
+
io.stdout(message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function printChannelKeyCollection(
|
|
59
|
+
args: ParsedArgs,
|
|
60
|
+
outputMode: "json" | "table",
|
|
61
|
+
io: Io,
|
|
62
|
+
response: {
|
|
63
|
+
items: Array<{ id: string; attributes: ChannelKeyAttributes }>;
|
|
64
|
+
nextCursor?: string;
|
|
65
|
+
},
|
|
66
|
+
): void {
|
|
67
|
+
if (outputMode === "json") {
|
|
68
|
+
printJson(
|
|
69
|
+
io,
|
|
70
|
+
paginatedJson(args, {
|
|
71
|
+
items: response.items,
|
|
72
|
+
nextCursor: response.nextCursor,
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
printValue(
|
|
79
|
+
io,
|
|
80
|
+
outputMode,
|
|
81
|
+
response.items.map((item) => formatChannelKeyRow(item)),
|
|
82
|
+
);
|
|
83
|
+
const pagination = paginationInfo(args, response.nextCursor);
|
|
84
|
+
const message = renderPagination(
|
|
85
|
+
pagination?.nextCursor,
|
|
86
|
+
pagination?.nextCommand,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (message) {
|
|
90
|
+
io.stdout(message);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function formatChannelRecord(channel: {
|
|
95
|
+
id: string;
|
|
96
|
+
attributes: ChannelAttributes;
|
|
97
|
+
}) {
|
|
98
|
+
return {
|
|
99
|
+
id: channel.id,
|
|
100
|
+
name: channel.attributes.name,
|
|
101
|
+
visibility: channel.attributes.visibility,
|
|
102
|
+
publiclyListed: channel.attributes.publicly_listed ?? false,
|
|
103
|
+
description: channel.attributes.description ?? "",
|
|
104
|
+
postingPausedUntil: channel.attributes.posting_paused_until ?? "",
|
|
105
|
+
pinnedPostId: channel.attributes.pinned_post_id ?? "",
|
|
106
|
+
insertedAt: channel.attributes.inserted_at ?? "",
|
|
107
|
+
updatedAt: channel.attributes.updated_at ?? "",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function formatChannelDiagnostics(
|
|
112
|
+
diagnostics: ChannelDiagnosticsResponse,
|
|
113
|
+
) {
|
|
114
|
+
return {
|
|
115
|
+
channelId: diagnostics.channel_id,
|
|
116
|
+
channelName: diagnostics.channel_name,
|
|
117
|
+
channelDescription: diagnostics.channel_description ?? "",
|
|
118
|
+
stateLabels: diagnostics.state_labels.join(", "),
|
|
119
|
+
activePublishKeyCount: diagnostics.active_publish_key_count,
|
|
120
|
+
lastPostedAt: diagnostics.last_posted_at ?? "",
|
|
121
|
+
postingPausedUntil: diagnostics.posting_paused_until ?? "",
|
|
122
|
+
latestBlockedWriteAt: diagnostics.latest_blocked_write_at ?? "",
|
|
123
|
+
latestBlockedWriteReason:
|
|
124
|
+
diagnostics.latest_blocked_write_reason_label ??
|
|
125
|
+
diagnostics.latest_blocked_write_reason ??
|
|
126
|
+
"",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function renderShareToken(
|
|
131
|
+
title: string,
|
|
132
|
+
response: ShareTokenResponse,
|
|
133
|
+
): string {
|
|
134
|
+
return joinBlocks([
|
|
135
|
+
title,
|
|
136
|
+
renderFields([["Token", response.token]]),
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function renderIdAction(title: string, response: IdResponse): string {
|
|
141
|
+
return joinBlocks([
|
|
142
|
+
title,
|
|
143
|
+
renderFields([["ID", response.id]]),
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function renderChannelPublicationAction(
|
|
148
|
+
title: string,
|
|
149
|
+
response: ChannelPublicationResponse,
|
|
150
|
+
): string {
|
|
151
|
+
return joinBlocks([
|
|
152
|
+
title,
|
|
153
|
+
renderFields([
|
|
154
|
+
["ID", response.id],
|
|
155
|
+
["Name", response.name],
|
|
156
|
+
["Publicly listed", response.publicly_listed],
|
|
157
|
+
]),
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function renderChannelPinAction(
|
|
162
|
+
title: string,
|
|
163
|
+
response: ChannelPinResponse,
|
|
164
|
+
): string {
|
|
165
|
+
const channel = Array.isArray(response.data)
|
|
166
|
+
? response.data[0]
|
|
167
|
+
: response.data;
|
|
168
|
+
|
|
169
|
+
return joinBlocks([
|
|
170
|
+
title,
|
|
171
|
+
renderFields([
|
|
172
|
+
["Channel", channel?.attributes.name ?? ""],
|
|
173
|
+
["Pinned post", channel?.attributes.pinned_post_id ?? ""],
|
|
174
|
+
]),
|
|
175
|
+
]);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function renderChannelKeyIssue(
|
|
179
|
+
title: string,
|
|
180
|
+
response: ChannelKeyIssueResponse,
|
|
181
|
+
): string {
|
|
182
|
+
return renderTokenAction({
|
|
183
|
+
title,
|
|
184
|
+
id: response.id,
|
|
185
|
+
name: response.name,
|
|
186
|
+
token: response.token,
|
|
187
|
+
issuedAt: response.issued_at,
|
|
188
|
+
expiresAt: response.expires_at,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function renderChannelKeyRevoke(
|
|
193
|
+
title: string,
|
|
194
|
+
response: ChannelKeyRevokeResponse,
|
|
195
|
+
): string {
|
|
196
|
+
return joinBlocks([
|
|
197
|
+
title,
|
|
198
|
+
renderFields([
|
|
199
|
+
["ID", response.id],
|
|
200
|
+
["Name", response.name],
|
|
201
|
+
]),
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function formatChannelRow(channel: { id: string; attributes: ChannelAttributes }) {
|
|
206
|
+
return {
|
|
207
|
+
id: channel.id,
|
|
208
|
+
name: channel.attributes.name,
|
|
209
|
+
visibility: channel.attributes.visibility,
|
|
210
|
+
publiclyListed: channel.attributes.publicly_listed ?? false,
|
|
211
|
+
pinnedPostId: channel.attributes.pinned_post_id ?? "",
|
|
212
|
+
description: channel.attributes.description ?? "",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function formatChannelKeyRow(item: { id: string; attributes: ChannelKeyAttributes }) {
|
|
217
|
+
return {
|
|
218
|
+
id: item.id,
|
|
219
|
+
name: item.attributes.name ?? "",
|
|
220
|
+
expiresAt: item.attributes.expires_at ?? "",
|
|
221
|
+
revokedAt: item.attributes.revoked_at ?? "",
|
|
222
|
+
issuedAt: item.attributes.inserted_at ?? "",
|
|
223
|
+
};
|
|
224
|
+
}
|