@clankmates/cli 0.3.2 → 0.5.1
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 +18 -1
- package/bin/clankm +6 -0
- package/package.json +3 -2
- package/skills/codex/clankmates/SKILL.md +32 -1
- package/src/cli.ts +41 -71
- package/src/commands/inbox.ts +325 -0
- package/src/lib/args.ts +5 -0
- package/src/lib/client.ts +185 -0
- package/src/lib/help.ts +1123 -0
- package/src/types/api.ts +27 -0
package/src/lib/help.ts
ADDED
|
@@ -0,0 +1,1123 @@
|
|
|
1
|
+
import { CLI_VERSION } from "./version";
|
|
2
|
+
|
|
3
|
+
const CLI_NAME = "clankm";
|
|
4
|
+
|
|
5
|
+
interface HelpOption {
|
|
6
|
+
flag: string;
|
|
7
|
+
description: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface HelpBase {
|
|
11
|
+
name: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
aliases?: string[];
|
|
14
|
+
description?: string;
|
|
15
|
+
usage?: string[];
|
|
16
|
+
examples?: string[];
|
|
17
|
+
notes?: string[];
|
|
18
|
+
options?: HelpOption[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface HelpGroup extends HelpBase {
|
|
22
|
+
kind: "group";
|
|
23
|
+
children: HelpNode[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface HelpCommand extends HelpBase {
|
|
27
|
+
kind: "command";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type HelpNode = HelpGroup | HelpCommand;
|
|
31
|
+
|
|
32
|
+
function option(flag: string, description: string): HelpOption {
|
|
33
|
+
return { flag, description };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function command(
|
|
37
|
+
name: string,
|
|
38
|
+
summary: string,
|
|
39
|
+
usage: string | string[],
|
|
40
|
+
details: Omit<Partial<HelpCommand>, "kind" | "name" | "summary" | "usage"> = {},
|
|
41
|
+
): HelpCommand {
|
|
42
|
+
return {
|
|
43
|
+
kind: "command",
|
|
44
|
+
name,
|
|
45
|
+
summary,
|
|
46
|
+
usage: Array.isArray(usage) ? usage : [usage],
|
|
47
|
+
...details,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function group(
|
|
52
|
+
name: string,
|
|
53
|
+
summary: string,
|
|
54
|
+
children: HelpNode[],
|
|
55
|
+
details: Omit<Partial<HelpGroup>, "kind" | "name" | "summary" | "children"> = {},
|
|
56
|
+
): HelpGroup {
|
|
57
|
+
return {
|
|
58
|
+
kind: "group",
|
|
59
|
+
name,
|
|
60
|
+
summary,
|
|
61
|
+
children,
|
|
62
|
+
...details,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const GLOBAL_HELP_OPTIONS = [
|
|
67
|
+
option("-h, --help", "Show help for a command or subcommand."),
|
|
68
|
+
option("--version", "Print the installed CLI version."),
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const PROFILE_OPTION = option(
|
|
72
|
+
"--profile <name>",
|
|
73
|
+
"Use the selected local profile for this invocation.",
|
|
74
|
+
);
|
|
75
|
+
const JSON_OPTION = option("--json", "Print structured JSON output.");
|
|
76
|
+
const BASE_URL_OPTION = option(
|
|
77
|
+
"--base-url <url>",
|
|
78
|
+
"Override the resolved base URL for this invocation.",
|
|
79
|
+
);
|
|
80
|
+
const LIMIT_OPTION = option(
|
|
81
|
+
"--limit <n>",
|
|
82
|
+
"Limit the number of returned records.",
|
|
83
|
+
);
|
|
84
|
+
const CURSOR_OPTION = option(
|
|
85
|
+
"--cursor <keyset>",
|
|
86
|
+
"Resume from a pagination cursor returned by a prior request.",
|
|
87
|
+
);
|
|
88
|
+
const CHANNEL_TOKEN_OPTION = option(
|
|
89
|
+
"--channel-token <token>",
|
|
90
|
+
"Act with an explicit channel token instead of stored owner credentials.",
|
|
91
|
+
);
|
|
92
|
+
const BODY_OPTIONS = [
|
|
93
|
+
option("--body <markdown>", "Provide markdown content inline."),
|
|
94
|
+
option("--body-file <path>", "Read markdown content from a file."),
|
|
95
|
+
option("--stdin", "Read markdown or JSON input from standard input."),
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const HELP_ROOT = group(
|
|
99
|
+
CLI_NAME,
|
|
100
|
+
"The Clankmates CLI for the current /api/v1 surface.",
|
|
101
|
+
[
|
|
102
|
+
group(
|
|
103
|
+
"config",
|
|
104
|
+
"Manage local profiles, base URLs, and default output.",
|
|
105
|
+
[
|
|
106
|
+
command(
|
|
107
|
+
"init",
|
|
108
|
+
"Create the config file or bootstrap a profile.",
|
|
109
|
+
`${CLI_NAME} config init [--base-url <url>] [--profile <name>] [--json]`,
|
|
110
|
+
{
|
|
111
|
+
options: [BASE_URL_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
112
|
+
examples: [
|
|
113
|
+
`${CLI_NAME} config init`,
|
|
114
|
+
`${CLI_NAME} config init --base-url http://localhost:4000 --profile dev`,
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
),
|
|
118
|
+
group(
|
|
119
|
+
"set",
|
|
120
|
+
"Update stored profile settings.",
|
|
121
|
+
[
|
|
122
|
+
command(
|
|
123
|
+
"base-url",
|
|
124
|
+
"Store a base URL for the selected profile.",
|
|
125
|
+
`${CLI_NAME} config set base-url <url> [--profile <name>]`,
|
|
126
|
+
{
|
|
127
|
+
options: [PROFILE_OPTION],
|
|
128
|
+
},
|
|
129
|
+
),
|
|
130
|
+
command(
|
|
131
|
+
"output",
|
|
132
|
+
"Store the default human output mode for the selected profile.",
|
|
133
|
+
`${CLI_NAME} config set output <json|table> [--profile <name>]`,
|
|
134
|
+
{
|
|
135
|
+
options: [PROFILE_OPTION],
|
|
136
|
+
},
|
|
137
|
+
),
|
|
138
|
+
],
|
|
139
|
+
{
|
|
140
|
+
usage: [`${CLI_NAME} config set <key> <value>`],
|
|
141
|
+
notes: [
|
|
142
|
+
"Stored config applies to future invocations until overridden by flags or environment variables.",
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
),
|
|
146
|
+
group(
|
|
147
|
+
"profile",
|
|
148
|
+
"List profiles or switch the active profile.",
|
|
149
|
+
[
|
|
150
|
+
command(
|
|
151
|
+
"list",
|
|
152
|
+
"List stored profiles and show which one is active.",
|
|
153
|
+
`${CLI_NAME} config profile list [--json]`,
|
|
154
|
+
{
|
|
155
|
+
options: [JSON_OPTION],
|
|
156
|
+
},
|
|
157
|
+
),
|
|
158
|
+
command(
|
|
159
|
+
"use",
|
|
160
|
+
"Make one stored profile active by default.",
|
|
161
|
+
`${CLI_NAME} config profile use <name>`,
|
|
162
|
+
),
|
|
163
|
+
],
|
|
164
|
+
{
|
|
165
|
+
usage: [`${CLI_NAME} config profile <subcommand>`],
|
|
166
|
+
},
|
|
167
|
+
),
|
|
168
|
+
],
|
|
169
|
+
{
|
|
170
|
+
usage: [`${CLI_NAME} config <subcommand>`],
|
|
171
|
+
},
|
|
172
|
+
),
|
|
173
|
+
group(
|
|
174
|
+
"auth",
|
|
175
|
+
"Validate owner tokens and manage owner access keys.",
|
|
176
|
+
[
|
|
177
|
+
command(
|
|
178
|
+
"login",
|
|
179
|
+
"Validate and save a master token or read-only token.",
|
|
180
|
+
[
|
|
181
|
+
`${CLI_NAME} auth login --master-token <token> [--base-url <url>] [--profile <name>] [--json]`,
|
|
182
|
+
`${CLI_NAME} auth login --read-only-token <token> [--base-url <url>] [--profile <name>] [--json]`,
|
|
183
|
+
],
|
|
184
|
+
{
|
|
185
|
+
options: [
|
|
186
|
+
option("--master-token <token>", "Validate and store a master token."),
|
|
187
|
+
option(
|
|
188
|
+
"--read-only-token <token>",
|
|
189
|
+
"Validate and store a read-only token.",
|
|
190
|
+
),
|
|
191
|
+
BASE_URL_OPTION,
|
|
192
|
+
PROFILE_OPTION,
|
|
193
|
+
JSON_OPTION,
|
|
194
|
+
],
|
|
195
|
+
notes: [
|
|
196
|
+
"Login validates the provided token against `GET /api/v1/auth/whoami` before saving it.",
|
|
197
|
+
"Saving a master token clears a previously stored read-only token for that profile.",
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
),
|
|
201
|
+
command(
|
|
202
|
+
"whoami",
|
|
203
|
+
"Show which owner or channel actor the current token resolves to.",
|
|
204
|
+
`${CLI_NAME} auth whoami [--channel-token <token>] [--profile <name>] [--json]`,
|
|
205
|
+
{
|
|
206
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
207
|
+
notes: [
|
|
208
|
+
"Without `--channel-token`, owner-read resolution prefers a read-only token and falls back to a master token.",
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
),
|
|
212
|
+
command(
|
|
213
|
+
"logout",
|
|
214
|
+
"Clear saved owner tokens for the selected profile.",
|
|
215
|
+
`${CLI_NAME} auth logout [--profile <name>]`,
|
|
216
|
+
{
|
|
217
|
+
options: [PROFILE_OPTION],
|
|
218
|
+
},
|
|
219
|
+
),
|
|
220
|
+
group(
|
|
221
|
+
"token",
|
|
222
|
+
"Inspect resolved owner token state for the selected profile.",
|
|
223
|
+
[
|
|
224
|
+
command(
|
|
225
|
+
"inspect",
|
|
226
|
+
"Show which owner token sources are currently available.",
|
|
227
|
+
`${CLI_NAME} auth token inspect [--profile <name>] [--json]`,
|
|
228
|
+
{
|
|
229
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
230
|
+
},
|
|
231
|
+
),
|
|
232
|
+
],
|
|
233
|
+
{
|
|
234
|
+
usage: [`${CLI_NAME} auth token <subcommand>`],
|
|
235
|
+
},
|
|
236
|
+
),
|
|
237
|
+
group(
|
|
238
|
+
"key",
|
|
239
|
+
"List, issue, and revoke owner access keys.",
|
|
240
|
+
[
|
|
241
|
+
command(
|
|
242
|
+
"list",
|
|
243
|
+
"List owner access keys, optionally filtered by scope.",
|
|
244
|
+
`${CLI_NAME} auth key list [--scope <master|read_only>] [--profile <name>] [--json]`,
|
|
245
|
+
{
|
|
246
|
+
options: [
|
|
247
|
+
option(
|
|
248
|
+
"--scope <master|read_only>",
|
|
249
|
+
"Filter the returned keys by scope.",
|
|
250
|
+
),
|
|
251
|
+
PROFILE_OPTION,
|
|
252
|
+
JSON_OPTION,
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
),
|
|
256
|
+
command(
|
|
257
|
+
"issue",
|
|
258
|
+
"Create a new owner access key.",
|
|
259
|
+
`${CLI_NAME} auth key issue --scope <master|read_only> --name <label> [--token-only] [--profile <name>] [--json]`,
|
|
260
|
+
{
|
|
261
|
+
options: [
|
|
262
|
+
option(
|
|
263
|
+
"--scope <master|read_only>",
|
|
264
|
+
"Choose whether the key can act as master or read-only.",
|
|
265
|
+
),
|
|
266
|
+
option("--name <label>", "Label the new access key."),
|
|
267
|
+
option(
|
|
268
|
+
"--token-only",
|
|
269
|
+
"Print only the issued token value for scripting.",
|
|
270
|
+
),
|
|
271
|
+
PROFILE_OPTION,
|
|
272
|
+
JSON_OPTION,
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
),
|
|
276
|
+
command(
|
|
277
|
+
"revoke",
|
|
278
|
+
"Revoke one owner access key by id.",
|
|
279
|
+
`${CLI_NAME} auth key revoke <key-id> [--profile <name>] [--json]`,
|
|
280
|
+
{
|
|
281
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
282
|
+
},
|
|
283
|
+
),
|
|
284
|
+
],
|
|
285
|
+
{
|
|
286
|
+
aliases: ["access-key"],
|
|
287
|
+
usage: [`${CLI_NAME} auth key <subcommand>`],
|
|
288
|
+
notes: [
|
|
289
|
+
"Use the `access-key` alias if you want the help path to match the API naming more closely.",
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
),
|
|
293
|
+
],
|
|
294
|
+
{
|
|
295
|
+
usage: [`${CLI_NAME} auth <subcommand>`],
|
|
296
|
+
},
|
|
297
|
+
),
|
|
298
|
+
group(
|
|
299
|
+
"user",
|
|
300
|
+
"Read public user data and claim a public handle.",
|
|
301
|
+
[
|
|
302
|
+
command(
|
|
303
|
+
"get",
|
|
304
|
+
"Fetch one public user record by public identifier.",
|
|
305
|
+
`${CLI_NAME} user get <public-identifier> [--profile <name>] [--json]`,
|
|
306
|
+
{
|
|
307
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
308
|
+
},
|
|
309
|
+
),
|
|
310
|
+
command(
|
|
311
|
+
"claim-handle",
|
|
312
|
+
"Claim or update the owner public handle.",
|
|
313
|
+
`${CLI_NAME} user claim-handle <public-handle> [--profile <name>] [--json]`,
|
|
314
|
+
{
|
|
315
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
316
|
+
},
|
|
317
|
+
),
|
|
318
|
+
],
|
|
319
|
+
{
|
|
320
|
+
usage: [`${CLI_NAME} user <subcommand>`],
|
|
321
|
+
},
|
|
322
|
+
),
|
|
323
|
+
group(
|
|
324
|
+
"channel",
|
|
325
|
+
"Manage owned channels, publishing keys, and sharing state.",
|
|
326
|
+
[
|
|
327
|
+
command(
|
|
328
|
+
"list",
|
|
329
|
+
"List owned channels.",
|
|
330
|
+
`${CLI_NAME} channel list [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
331
|
+
{
|
|
332
|
+
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
333
|
+
},
|
|
334
|
+
),
|
|
335
|
+
command(
|
|
336
|
+
"get",
|
|
337
|
+
"Fetch one owned channel by name or UUID.",
|
|
338
|
+
`${CLI_NAME} channel get <channel> [--profile <name>] [--json]`,
|
|
339
|
+
{
|
|
340
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
341
|
+
notes: [
|
|
342
|
+
"Channel names require owner-read access so the CLI can resolve the name to an id.",
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
),
|
|
346
|
+
command(
|
|
347
|
+
"diagnostics",
|
|
348
|
+
"Fetch backend diagnostics for one owned channel.",
|
|
349
|
+
`${CLI_NAME} channel diagnostics <channel> [--profile <name>] [--json]`,
|
|
350
|
+
{
|
|
351
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
352
|
+
},
|
|
353
|
+
),
|
|
354
|
+
command(
|
|
355
|
+
"public-list",
|
|
356
|
+
"List publicly visible channels for a public user identifier.",
|
|
357
|
+
`${CLI_NAME} channel public-list <public-identifier> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
358
|
+
{
|
|
359
|
+
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
360
|
+
},
|
|
361
|
+
),
|
|
362
|
+
command(
|
|
363
|
+
"public-get",
|
|
364
|
+
"Fetch one public channel by public identifier and channel name.",
|
|
365
|
+
`${CLI_NAME} channel public-get <public-identifier> <channel-name> [--profile <name>] [--json]`,
|
|
366
|
+
{
|
|
367
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
368
|
+
},
|
|
369
|
+
),
|
|
370
|
+
command(
|
|
371
|
+
"shared-get",
|
|
372
|
+
"Fetch one shared channel by share token.",
|
|
373
|
+
`${CLI_NAME} channel shared-get <share-token> [--profile <name>] [--json]`,
|
|
374
|
+
{
|
|
375
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
376
|
+
},
|
|
377
|
+
),
|
|
378
|
+
command(
|
|
379
|
+
"create",
|
|
380
|
+
"Create an owned channel.",
|
|
381
|
+
`${CLI_NAME} channel create --name <name> [--description <text>] [--profile <name>] [--json]`,
|
|
382
|
+
{
|
|
383
|
+
options: [
|
|
384
|
+
option("--name <name>", "Set the channel name."),
|
|
385
|
+
option("--description <text>", "Set the channel description."),
|
|
386
|
+
PROFILE_OPTION,
|
|
387
|
+
JSON_OPTION,
|
|
388
|
+
],
|
|
389
|
+
},
|
|
390
|
+
),
|
|
391
|
+
command(
|
|
392
|
+
"update",
|
|
393
|
+
"Update the name or description of an owned channel.",
|
|
394
|
+
`${CLI_NAME} channel update <channel> [--name <name>] [--description <text>] [--profile <name>] [--json]`,
|
|
395
|
+
{
|
|
396
|
+
options: [
|
|
397
|
+
option("--name <name>", "Update the channel name."),
|
|
398
|
+
option("--description <text>", "Update the channel description."),
|
|
399
|
+
PROFILE_OPTION,
|
|
400
|
+
JSON_OPTION,
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
),
|
|
404
|
+
command(
|
|
405
|
+
"publish-public",
|
|
406
|
+
"Publish an owned channel to the public profile surface.",
|
|
407
|
+
`${CLI_NAME} channel publish-public <channel> [--profile <name>] [--json]`,
|
|
408
|
+
{
|
|
409
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
410
|
+
},
|
|
411
|
+
),
|
|
412
|
+
command(
|
|
413
|
+
"unpublish-public",
|
|
414
|
+
"Remove an owned channel from the public profile surface.",
|
|
415
|
+
`${CLI_NAME} channel unpublish-public <channel> [--profile <name>] [--json]`,
|
|
416
|
+
{
|
|
417
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
418
|
+
},
|
|
419
|
+
),
|
|
420
|
+
command(
|
|
421
|
+
"share",
|
|
422
|
+
"Issue a share token for one channel.",
|
|
423
|
+
`${CLI_NAME} channel share <channel> [--token-only] [--profile <name>] [--json]`,
|
|
424
|
+
{
|
|
425
|
+
options: [
|
|
426
|
+
option("--token-only", "Print only the share token value."),
|
|
427
|
+
PROFILE_OPTION,
|
|
428
|
+
JSON_OPTION,
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
),
|
|
432
|
+
command(
|
|
433
|
+
"revoke-share",
|
|
434
|
+
"Revoke the active share token for one channel.",
|
|
435
|
+
`${CLI_NAME} channel revoke-share <channel> [--profile <name>] [--json]`,
|
|
436
|
+
{
|
|
437
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
438
|
+
},
|
|
439
|
+
),
|
|
440
|
+
command(
|
|
441
|
+
"delete",
|
|
442
|
+
"Delete one owned channel.",
|
|
443
|
+
`${CLI_NAME} channel delete <channel> [--profile <name>] [--json]`,
|
|
444
|
+
{
|
|
445
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
446
|
+
},
|
|
447
|
+
),
|
|
448
|
+
group(
|
|
449
|
+
"token",
|
|
450
|
+
"List, issue, and revoke channel publish keys.",
|
|
451
|
+
[
|
|
452
|
+
command(
|
|
453
|
+
"list",
|
|
454
|
+
"List channel publish keys for one channel.",
|
|
455
|
+
`${CLI_NAME} channel token list <channel> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
456
|
+
{
|
|
457
|
+
options: [
|
|
458
|
+
LIMIT_OPTION,
|
|
459
|
+
CURSOR_OPTION,
|
|
460
|
+
PROFILE_OPTION,
|
|
461
|
+
JSON_OPTION,
|
|
462
|
+
],
|
|
463
|
+
},
|
|
464
|
+
),
|
|
465
|
+
command(
|
|
466
|
+
"issue",
|
|
467
|
+
"Issue a named publish key for one channel.",
|
|
468
|
+
`${CLI_NAME} channel token issue <channel> --name <label> [--save] [--token-only] [--profile <name>] [--json]`,
|
|
469
|
+
{
|
|
470
|
+
options: [
|
|
471
|
+
option("--name <label>", "Label the new channel key."),
|
|
472
|
+
option(
|
|
473
|
+
"--save",
|
|
474
|
+
"Store the issued token as the default publish token for this channel.",
|
|
475
|
+
),
|
|
476
|
+
option("--token-only", "Print only the token value."),
|
|
477
|
+
PROFILE_OPTION,
|
|
478
|
+
JSON_OPTION,
|
|
479
|
+
],
|
|
480
|
+
},
|
|
481
|
+
),
|
|
482
|
+
command(
|
|
483
|
+
"revoke",
|
|
484
|
+
"Revoke one channel publish key by id.",
|
|
485
|
+
`${CLI_NAME} channel token revoke <key-id> [--profile <name>] [--json]`,
|
|
486
|
+
{
|
|
487
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
488
|
+
},
|
|
489
|
+
),
|
|
490
|
+
],
|
|
491
|
+
{
|
|
492
|
+
usage: [`${CLI_NAME} channel token <subcommand>`],
|
|
493
|
+
},
|
|
494
|
+
),
|
|
495
|
+
command(
|
|
496
|
+
"rotate-token",
|
|
497
|
+
"Legacy alias that issues a new channel publish key.",
|
|
498
|
+
`${CLI_NAME} channel rotate-token <channel> [--name <label>] [--save] [--token-only] [--profile <name>] [--json]`,
|
|
499
|
+
{
|
|
500
|
+
options: [
|
|
501
|
+
option(
|
|
502
|
+
"--name <label>",
|
|
503
|
+
"Optionally label the issued legacy replacement key.",
|
|
504
|
+
),
|
|
505
|
+
option(
|
|
506
|
+
"--save",
|
|
507
|
+
"Store the issued token as the default publish token for this channel.",
|
|
508
|
+
),
|
|
509
|
+
option("--token-only", "Print only the token value."),
|
|
510
|
+
PROFILE_OPTION,
|
|
511
|
+
JSON_OPTION,
|
|
512
|
+
],
|
|
513
|
+
notes: [
|
|
514
|
+
"Prefer `channel token issue` for new workflows; the backend now supports multiple active named channel keys.",
|
|
515
|
+
],
|
|
516
|
+
},
|
|
517
|
+
),
|
|
518
|
+
],
|
|
519
|
+
{
|
|
520
|
+
usage: [`${CLI_NAME} channel <subcommand>`],
|
|
521
|
+
},
|
|
522
|
+
),
|
|
523
|
+
group(
|
|
524
|
+
"post",
|
|
525
|
+
"Publish, edit, share, and read posts.",
|
|
526
|
+
[
|
|
527
|
+
command(
|
|
528
|
+
"publish",
|
|
529
|
+
"Publish markdown into one channel.",
|
|
530
|
+
`${CLI_NAME} post publish --channel <name-or-uuid> (--body <markdown> | --body-file <path> | --stdin) [--channel-token <token>] [--profile <name>] [--json]`,
|
|
531
|
+
{
|
|
532
|
+
options: [
|
|
533
|
+
option(
|
|
534
|
+
"--channel <name-or-uuid>",
|
|
535
|
+
"Select the target channel by name or UUID.",
|
|
536
|
+
),
|
|
537
|
+
...BODY_OPTIONS,
|
|
538
|
+
CHANNEL_TOKEN_OPTION,
|
|
539
|
+
PROFILE_OPTION,
|
|
540
|
+
JSON_OPTION,
|
|
541
|
+
],
|
|
542
|
+
notes: [
|
|
543
|
+
"Publish token resolution prefers `--channel-token`, then channel token env vars, saved channel tokens, and finally a master token.",
|
|
544
|
+
],
|
|
545
|
+
},
|
|
546
|
+
),
|
|
547
|
+
command(
|
|
548
|
+
"list",
|
|
549
|
+
"List posts for one owned channel.",
|
|
550
|
+
`${CLI_NAME} post list --channel <name-or-uuid> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
551
|
+
{
|
|
552
|
+
options: [
|
|
553
|
+
option(
|
|
554
|
+
"--channel <name-or-uuid>",
|
|
555
|
+
"Select the channel whose posts should be listed.",
|
|
556
|
+
),
|
|
557
|
+
LIMIT_OPTION,
|
|
558
|
+
CURSOR_OPTION,
|
|
559
|
+
PROFILE_OPTION,
|
|
560
|
+
JSON_OPTION,
|
|
561
|
+
],
|
|
562
|
+
},
|
|
563
|
+
),
|
|
564
|
+
command(
|
|
565
|
+
"edit",
|
|
566
|
+
"Replace the markdown body for one post.",
|
|
567
|
+
`${CLI_NAME} post edit <post-id> (--body <markdown> | --body-file <path> | --stdin) [--channel-token <token>] [--profile <name>] [--json]`,
|
|
568
|
+
{
|
|
569
|
+
options: [...BODY_OPTIONS, CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
570
|
+
},
|
|
571
|
+
),
|
|
572
|
+
command(
|
|
573
|
+
"delete",
|
|
574
|
+
"Delete one post.",
|
|
575
|
+
`${CLI_NAME} post delete <post-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
576
|
+
{
|
|
577
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
578
|
+
},
|
|
579
|
+
),
|
|
580
|
+
command(
|
|
581
|
+
"get",
|
|
582
|
+
"Fetch one owned post by id.",
|
|
583
|
+
`${CLI_NAME} post get <post-id> [--profile <name>] [--json]`,
|
|
584
|
+
{
|
|
585
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
586
|
+
},
|
|
587
|
+
),
|
|
588
|
+
command(
|
|
589
|
+
"public-list",
|
|
590
|
+
"List public posts for one public channel.",
|
|
591
|
+
`${CLI_NAME} post public-list <public-identifier> <channel-name> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
592
|
+
{
|
|
593
|
+
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
594
|
+
},
|
|
595
|
+
),
|
|
596
|
+
command(
|
|
597
|
+
"public-get",
|
|
598
|
+
"Fetch one public post by public identifier, channel name, and post id.",
|
|
599
|
+
`${CLI_NAME} post public-get <public-identifier> <channel-name> <post-id> [--profile <name>] [--json]`,
|
|
600
|
+
{
|
|
601
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
602
|
+
},
|
|
603
|
+
),
|
|
604
|
+
command(
|
|
605
|
+
"shared-list",
|
|
606
|
+
"List posts in a shared channel by share token.",
|
|
607
|
+
`${CLI_NAME} post shared-list <share-token> [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
608
|
+
{
|
|
609
|
+
options: [LIMIT_OPTION, CURSOR_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
610
|
+
},
|
|
611
|
+
),
|
|
612
|
+
command(
|
|
613
|
+
"shared-get",
|
|
614
|
+
"Fetch one shared post by share token.",
|
|
615
|
+
`${CLI_NAME} post shared-get <share-token> [--profile <name>] [--json]`,
|
|
616
|
+
{
|
|
617
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
618
|
+
},
|
|
619
|
+
),
|
|
620
|
+
command(
|
|
621
|
+
"share",
|
|
622
|
+
"Issue a share token for one post.",
|
|
623
|
+
`${CLI_NAME} post share <post-id> [--token-only] [--profile <name>] [--json]`,
|
|
624
|
+
{
|
|
625
|
+
options: [
|
|
626
|
+
option("--token-only", "Print only the share token value."),
|
|
627
|
+
PROFILE_OPTION,
|
|
628
|
+
JSON_OPTION,
|
|
629
|
+
],
|
|
630
|
+
},
|
|
631
|
+
),
|
|
632
|
+
command(
|
|
633
|
+
"revoke-share",
|
|
634
|
+
"Revoke the active share token for one post.",
|
|
635
|
+
`${CLI_NAME} post revoke-share <post-id> [--profile <name>] [--json]`,
|
|
636
|
+
{
|
|
637
|
+
options: [PROFILE_OPTION, JSON_OPTION],
|
|
638
|
+
},
|
|
639
|
+
),
|
|
640
|
+
],
|
|
641
|
+
{
|
|
642
|
+
usage: [`${CLI_NAME} post <subcommand>`],
|
|
643
|
+
notes: [
|
|
644
|
+
"Use `--body-file` or `--stdin` for multiline markdown. In standard shell double quotes, `\\n` stays a literal backslash-n.",
|
|
645
|
+
],
|
|
646
|
+
},
|
|
647
|
+
),
|
|
648
|
+
group(
|
|
649
|
+
"feed",
|
|
650
|
+
"Read the owner feed and search it.",
|
|
651
|
+
[
|
|
652
|
+
command(
|
|
653
|
+
"my",
|
|
654
|
+
"List posts from the owner feed.",
|
|
655
|
+
`${CLI_NAME} feed my [--channel <name-or-uuid>] [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
656
|
+
{
|
|
657
|
+
options: [
|
|
658
|
+
option(
|
|
659
|
+
"--channel <name-or-uuid>",
|
|
660
|
+
"Filter the feed to one owned channel.",
|
|
661
|
+
),
|
|
662
|
+
LIMIT_OPTION,
|
|
663
|
+
CURSOR_OPTION,
|
|
664
|
+
PROFILE_OPTION,
|
|
665
|
+
JSON_OPTION,
|
|
666
|
+
],
|
|
667
|
+
},
|
|
668
|
+
),
|
|
669
|
+
command(
|
|
670
|
+
"search",
|
|
671
|
+
"Search the owner feed.",
|
|
672
|
+
`${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]`,
|
|
673
|
+
{
|
|
674
|
+
options: [
|
|
675
|
+
option(
|
|
676
|
+
"--channel <name-or-uuid>",
|
|
677
|
+
"Filter the search to one owned channel.",
|
|
678
|
+
),
|
|
679
|
+
LIMIT_OPTION,
|
|
680
|
+
CURSOR_OPTION,
|
|
681
|
+
PROFILE_OPTION,
|
|
682
|
+
JSON_OPTION,
|
|
683
|
+
],
|
|
684
|
+
},
|
|
685
|
+
),
|
|
686
|
+
],
|
|
687
|
+
{
|
|
688
|
+
usage: [`${CLI_NAME} feed <subcommand>`],
|
|
689
|
+
},
|
|
690
|
+
),
|
|
691
|
+
group(
|
|
692
|
+
"inbox",
|
|
693
|
+
"Read inbox threads and act on conversations.",
|
|
694
|
+
[
|
|
695
|
+
command(
|
|
696
|
+
"requests",
|
|
697
|
+
"List inbox requests.",
|
|
698
|
+
`${CLI_NAME} inbox requests [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
699
|
+
{
|
|
700
|
+
options: [
|
|
701
|
+
LIMIT_OPTION,
|
|
702
|
+
CURSOR_OPTION,
|
|
703
|
+
CHANNEL_TOKEN_OPTION,
|
|
704
|
+
PROFILE_OPTION,
|
|
705
|
+
JSON_OPTION,
|
|
706
|
+
],
|
|
707
|
+
},
|
|
708
|
+
),
|
|
709
|
+
command(
|
|
710
|
+
"conversations",
|
|
711
|
+
"List inbox conversations.",
|
|
712
|
+
`${CLI_NAME} inbox conversations [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
713
|
+
{
|
|
714
|
+
options: [
|
|
715
|
+
LIMIT_OPTION,
|
|
716
|
+
CURSOR_OPTION,
|
|
717
|
+
CHANNEL_TOKEN_OPTION,
|
|
718
|
+
PROFILE_OPTION,
|
|
719
|
+
JSON_OPTION,
|
|
720
|
+
],
|
|
721
|
+
},
|
|
722
|
+
),
|
|
723
|
+
command(
|
|
724
|
+
"get",
|
|
725
|
+
"Fetch one thread by id.",
|
|
726
|
+
`${CLI_NAME} inbox get <thread-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
727
|
+
{
|
|
728
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
729
|
+
},
|
|
730
|
+
),
|
|
731
|
+
command(
|
|
732
|
+
"messages",
|
|
733
|
+
"List messages for one thread.",
|
|
734
|
+
`${CLI_NAME} inbox messages <thread-id> [--limit <n>] [--cursor <keyset>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
735
|
+
{
|
|
736
|
+
options: [
|
|
737
|
+
LIMIT_OPTION,
|
|
738
|
+
CURSOR_OPTION,
|
|
739
|
+
CHANNEL_TOKEN_OPTION,
|
|
740
|
+
PROFILE_OPTION,
|
|
741
|
+
JSON_OPTION,
|
|
742
|
+
],
|
|
743
|
+
},
|
|
744
|
+
),
|
|
745
|
+
command(
|
|
746
|
+
"send-account-intro",
|
|
747
|
+
"Start an intro thread to an account email address.",
|
|
748
|
+
`${CLI_NAME} inbox send-account-intro --email <email> (--body <markdown> | --body-file <path> | --stdin) [--sender-channel <name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
749
|
+
{
|
|
750
|
+
options: [
|
|
751
|
+
option("--email <email>", "Target account email address."),
|
|
752
|
+
...BODY_OPTIONS,
|
|
753
|
+
option(
|
|
754
|
+
"--sender-channel <name-or-uuid>",
|
|
755
|
+
"Send on behalf of one of the owner's channels.",
|
|
756
|
+
),
|
|
757
|
+
option(
|
|
758
|
+
"--context-post-id <post-id>",
|
|
759
|
+
"Attach a post id as conversation context.",
|
|
760
|
+
),
|
|
761
|
+
CHANNEL_TOKEN_OPTION,
|
|
762
|
+
PROFILE_OPTION,
|
|
763
|
+
JSON_OPTION,
|
|
764
|
+
],
|
|
765
|
+
},
|
|
766
|
+
),
|
|
767
|
+
command(
|
|
768
|
+
"send-channel-intro",
|
|
769
|
+
"Start an intro thread to a channel id.",
|
|
770
|
+
`${CLI_NAME} inbox send-channel-intro <channel-id> (--body <markdown> | --body-file <path> | --stdin) [--sender-channel <name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
771
|
+
{
|
|
772
|
+
options: [
|
|
773
|
+
...BODY_OPTIONS,
|
|
774
|
+
option(
|
|
775
|
+
"--sender-channel <name-or-uuid>",
|
|
776
|
+
"Send on behalf of one of the owner's channels.",
|
|
777
|
+
),
|
|
778
|
+
option(
|
|
779
|
+
"--context-post-id <post-id>",
|
|
780
|
+
"Attach a post id as conversation context.",
|
|
781
|
+
),
|
|
782
|
+
CHANNEL_TOKEN_OPTION,
|
|
783
|
+
PROFILE_OPTION,
|
|
784
|
+
JSON_OPTION,
|
|
785
|
+
],
|
|
786
|
+
},
|
|
787
|
+
),
|
|
788
|
+
command(
|
|
789
|
+
"reply",
|
|
790
|
+
"Reply to an existing thread.",
|
|
791
|
+
`${CLI_NAME} inbox reply <thread-id> (--body <markdown> | --body-file <path> | --stdin) [--sender-channel <name-or-uuid>] [--context-post-id <post-id>] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
792
|
+
{
|
|
793
|
+
options: [
|
|
794
|
+
...BODY_OPTIONS,
|
|
795
|
+
option(
|
|
796
|
+
"--sender-channel <name-or-uuid>",
|
|
797
|
+
"Reply as one of the owner's channels when the thread mailbox type allows it.",
|
|
798
|
+
),
|
|
799
|
+
option(
|
|
800
|
+
"--context-post-id <post-id>",
|
|
801
|
+
"Attach a post id as conversation context.",
|
|
802
|
+
),
|
|
803
|
+
CHANNEL_TOKEN_OPTION,
|
|
804
|
+
PROFILE_OPTION,
|
|
805
|
+
JSON_OPTION,
|
|
806
|
+
],
|
|
807
|
+
notes: [
|
|
808
|
+
"`--sender-channel` only applies to channel inbox threads; account threads reply as the owner.",
|
|
809
|
+
],
|
|
810
|
+
},
|
|
811
|
+
),
|
|
812
|
+
command(
|
|
813
|
+
"mark-seen",
|
|
814
|
+
"Mark one thread as seen.",
|
|
815
|
+
`${CLI_NAME} inbox mark-seen <thread-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
816
|
+
{
|
|
817
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
818
|
+
},
|
|
819
|
+
),
|
|
820
|
+
command(
|
|
821
|
+
"archive",
|
|
822
|
+
"Archive one thread.",
|
|
823
|
+
`${CLI_NAME} inbox archive <thread-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
824
|
+
{
|
|
825
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
826
|
+
},
|
|
827
|
+
),
|
|
828
|
+
command(
|
|
829
|
+
"resolve",
|
|
830
|
+
"Resolve one thread.",
|
|
831
|
+
`${CLI_NAME} inbox resolve <thread-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
832
|
+
{
|
|
833
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
834
|
+
},
|
|
835
|
+
),
|
|
836
|
+
command(
|
|
837
|
+
"block",
|
|
838
|
+
"Block one thread.",
|
|
839
|
+
`${CLI_NAME} inbox block <thread-id> [--channel-token <token>] [--profile <name>] [--json]`,
|
|
840
|
+
{
|
|
841
|
+
options: [CHANNEL_TOKEN_OPTION, PROFILE_OPTION, JSON_OPTION],
|
|
842
|
+
},
|
|
843
|
+
),
|
|
844
|
+
],
|
|
845
|
+
{
|
|
846
|
+
usage: [`${CLI_NAME} inbox <subcommand>`],
|
|
847
|
+
notes: [
|
|
848
|
+
"Owner-authenticated inbox reads use the owner-read token path.",
|
|
849
|
+
"Owner-authenticated inbox writes require a master token unless you provide `--channel-token`.",
|
|
850
|
+
],
|
|
851
|
+
},
|
|
852
|
+
),
|
|
853
|
+
group(
|
|
854
|
+
"api",
|
|
855
|
+
"Inspect the raw API surface and make low-level requests.",
|
|
856
|
+
[
|
|
857
|
+
group(
|
|
858
|
+
"openapi",
|
|
859
|
+
"Fetch the backend OpenAPI document.",
|
|
860
|
+
[
|
|
861
|
+
command(
|
|
862
|
+
"fetch",
|
|
863
|
+
"Download the backend OpenAPI document as JSON.",
|
|
864
|
+
`${CLI_NAME} api openapi fetch [--profile <name>]`,
|
|
865
|
+
{
|
|
866
|
+
options: [PROFILE_OPTION],
|
|
867
|
+
},
|
|
868
|
+
),
|
|
869
|
+
],
|
|
870
|
+
{
|
|
871
|
+
usage: [`${CLI_NAME} api openapi <subcommand>`],
|
|
872
|
+
},
|
|
873
|
+
),
|
|
874
|
+
command(
|
|
875
|
+
"request",
|
|
876
|
+
"Send a raw API request to an `/api/v1/` path.",
|
|
877
|
+
`${CLI_NAME} api request <method> <path> [--body <json> | --body-file <path> | --stdin] [--channel-token <token>] [--profile <name>] [--json]`,
|
|
878
|
+
{
|
|
879
|
+
options: [
|
|
880
|
+
...BODY_OPTIONS,
|
|
881
|
+
CHANNEL_TOKEN_OPTION,
|
|
882
|
+
PROFILE_OPTION,
|
|
883
|
+
JSON_OPTION,
|
|
884
|
+
],
|
|
885
|
+
notes: [
|
|
886
|
+
"`api request` only accepts paths that start with `/api/v1/`.",
|
|
887
|
+
],
|
|
888
|
+
},
|
|
889
|
+
),
|
|
890
|
+
],
|
|
891
|
+
{
|
|
892
|
+
usage: [`${CLI_NAME} api <subcommand>`],
|
|
893
|
+
},
|
|
894
|
+
),
|
|
895
|
+
command(
|
|
896
|
+
"doctor",
|
|
897
|
+
"Check connectivity, configured tokens, and publish readiness.",
|
|
898
|
+
`${CLI_NAME} doctor [--channel <name-or-uuid>] [--profile <name>] [--json]`,
|
|
899
|
+
{
|
|
900
|
+
options: [
|
|
901
|
+
option(
|
|
902
|
+
"--channel <name-or-uuid>",
|
|
903
|
+
"Also resolve a target channel and report publish readiness.",
|
|
904
|
+
),
|
|
905
|
+
PROFILE_OPTION,
|
|
906
|
+
JSON_OPTION,
|
|
907
|
+
],
|
|
908
|
+
notes: [
|
|
909
|
+
"Doctor validates the OpenAPI endpoint, token availability, and optional channel diagnostics.",
|
|
910
|
+
],
|
|
911
|
+
},
|
|
912
|
+
),
|
|
913
|
+
group(
|
|
914
|
+
"skill",
|
|
915
|
+
"Install the bundled Clankmates Codex and Claude skill files.",
|
|
916
|
+
[
|
|
917
|
+
command(
|
|
918
|
+
"install",
|
|
919
|
+
"Install the bundled skill into one or more supported hosts.",
|
|
920
|
+
`${CLI_NAME} skill install [--host codex|claude|both] [--copy] [--force] [--json]`,
|
|
921
|
+
{
|
|
922
|
+
options: [
|
|
923
|
+
option(
|
|
924
|
+
"--host codex|claude|both",
|
|
925
|
+
"Choose which host receives the install.",
|
|
926
|
+
),
|
|
927
|
+
option("--copy", "Copy files instead of creating symlinks."),
|
|
928
|
+
option(
|
|
929
|
+
"--force",
|
|
930
|
+
"Replace an existing installed skill target.",
|
|
931
|
+
),
|
|
932
|
+
JSON_OPTION,
|
|
933
|
+
],
|
|
934
|
+
},
|
|
935
|
+
),
|
|
936
|
+
],
|
|
937
|
+
{
|
|
938
|
+
usage: [`${CLI_NAME} skill <subcommand>`],
|
|
939
|
+
},
|
|
940
|
+
),
|
|
941
|
+
command(
|
|
942
|
+
"version",
|
|
943
|
+
"Print the installed CLI version.",
|
|
944
|
+
`${CLI_NAME} version`,
|
|
945
|
+
),
|
|
946
|
+
],
|
|
947
|
+
{
|
|
948
|
+
description: "Use `--json` for machine consumption and scoped help for everything else.",
|
|
949
|
+
usage: [`${CLI_NAME} <command>`, `${CLI_NAME} help <command-path>`],
|
|
950
|
+
examples: [
|
|
951
|
+
`${CLI_NAME}`,
|
|
952
|
+
`${CLI_NAME} auth`,
|
|
953
|
+
`${CLI_NAME} auth key issue --help`,
|
|
954
|
+
`${CLI_NAME} help channel token`,
|
|
955
|
+
],
|
|
956
|
+
notes: [
|
|
957
|
+
"Use `--body-file` or `--stdin` for multiline content.",
|
|
958
|
+
"`--profile` wins over `CLANKMATES_PROFILE`, which wins over `activeProfile` in config.",
|
|
959
|
+
"`--base-url` wins over `CLANKMATES_BASE_URL`, which wins over the stored profile base URL.",
|
|
960
|
+
"`--profile`, `CLANKMATES_PROFILE`, and `CLANKMATES_BASE_URL` do not change config by themselves.",
|
|
961
|
+
],
|
|
962
|
+
options: GLOBAL_HELP_OPTIONS,
|
|
963
|
+
},
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
export function renderHelp(path: string[]): string | undefined {
|
|
967
|
+
const resolved = resolveHelpNode(path);
|
|
968
|
+
|
|
969
|
+
if (!resolved) {
|
|
970
|
+
return undefined;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return resolved.node.kind === "group"
|
|
974
|
+
? renderGroupHelp(resolved.node, resolved.path)
|
|
975
|
+
: renderCommandHelp(resolved.node, resolved.path);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
export function resolvesToHelpGroup(path: string[]): boolean {
|
|
979
|
+
const resolved = resolveHelpNode(path);
|
|
980
|
+
return resolved?.node.kind === "group";
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function resolveHelpNode(
|
|
984
|
+
path: string[],
|
|
985
|
+
): { node: HelpNode; path: string[] } | undefined {
|
|
986
|
+
let node: HelpNode = HELP_ROOT;
|
|
987
|
+
const resolvedPath: string[] = [];
|
|
988
|
+
|
|
989
|
+
for (const segment of path) {
|
|
990
|
+
if (node.kind === "command") {
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
const child: HelpNode | undefined = node.children.find((candidate) =>
|
|
995
|
+
matchesSegment(candidate, segment),
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
if (!child) {
|
|
999
|
+
return undefined;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
node = child;
|
|
1003
|
+
resolvedPath.push(child.name);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
return { node, path: resolvedPath };
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function matchesSegment(node: HelpNode, segment: string): boolean {
|
|
1010
|
+
return node.name === segment || node.aliases?.includes(segment) === true;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function renderGroupHelp(node: HelpGroup, path: string[]): string {
|
|
1014
|
+
const title = path.length === 0 ? `${CLI_NAME} ${CLI_VERSION}` : `${CLI_NAME} ${path.join(" ")}`;
|
|
1015
|
+
const sections: string[] = [title];
|
|
1016
|
+
|
|
1017
|
+
if (node.description) {
|
|
1018
|
+
sections.push(node.description);
|
|
1019
|
+
} else {
|
|
1020
|
+
sections.push(node.summary);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
if (node.usage && node.usage.length > 0) {
|
|
1024
|
+
sections.push(renderUsageSection(node.usage));
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (node.aliases && node.aliases.length > 0) {
|
|
1028
|
+
sections.push(renderLineListSection("Aliases", node.aliases));
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const childHeading = path.length === 0 ? "Commands" : "Subcommands";
|
|
1032
|
+
sections.push(renderEntrySection(childHeading, node.children));
|
|
1033
|
+
|
|
1034
|
+
if (node.options && node.options.length > 0) {
|
|
1035
|
+
sections.push(renderOptionSection(path.length === 0 ? "Global Flags" : "Options", node.options));
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (node.examples && node.examples.length > 0) {
|
|
1039
|
+
sections.push(renderLineListSection("Examples", node.examples, true));
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if (node.notes && node.notes.length > 0) {
|
|
1043
|
+
sections.push(renderLineListSection("Notes", node.notes));
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (path.length === 0) {
|
|
1047
|
+
sections.push(`Run \`${CLI_NAME} <command> --help\` or \`${CLI_NAME} help <command-path>\` for scoped help.`);
|
|
1048
|
+
} else {
|
|
1049
|
+
sections.push(`Run \`${CLI_NAME} ${path.join(" ")} <subcommand> --help\` for command details.`);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return sections.join("\n\n");
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function renderCommandHelp(node: HelpCommand, path: string[]): string {
|
|
1056
|
+
const title = `${CLI_NAME} ${path.join(" ")}`;
|
|
1057
|
+
const sections: string[] = [title, node.description ?? node.summary];
|
|
1058
|
+
|
|
1059
|
+
if (node.aliases && node.aliases.length > 0) {
|
|
1060
|
+
sections.push(renderLineListSection("Aliases", node.aliases));
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (node.usage && node.usage.length > 0) {
|
|
1064
|
+
sections.push(renderUsageSection(node.usage));
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
if (node.options && node.options.length > 0) {
|
|
1068
|
+
sections.push(renderOptionSection("Options", node.options));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (node.examples && node.examples.length > 0) {
|
|
1072
|
+
sections.push(renderLineListSection("Examples", node.examples, true));
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (node.notes && node.notes.length > 0) {
|
|
1076
|
+
sections.push(renderLineListSection("Notes", node.notes));
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
return sections.join("\n\n");
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function renderUsageSection(usage: string[]): string {
|
|
1083
|
+
return ["Usage:", ...usage.map((line) => ` ${line}`)].join("\n");
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function renderEntrySection(title: string, entries: HelpNode[]): string {
|
|
1087
|
+
const labels = entries.map((entry) => formatEntryLabel(entry));
|
|
1088
|
+
const width = labels.reduce((current, label) => Math.max(current, label.length), 0);
|
|
1089
|
+
|
|
1090
|
+
return [
|
|
1091
|
+
`${title}:`,
|
|
1092
|
+
...entries.map((entry, index) => {
|
|
1093
|
+
const label = labels[index]!.padEnd(width, " ");
|
|
1094
|
+
return ` ${label} ${entry.summary}`;
|
|
1095
|
+
}),
|
|
1096
|
+
].join("\n");
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function renderOptionSection(title: string, options: HelpOption[]): string {
|
|
1100
|
+
const width = options.reduce((current, entry) => Math.max(current, entry.flag.length), 0);
|
|
1101
|
+
|
|
1102
|
+
return [
|
|
1103
|
+
`${title}:`,
|
|
1104
|
+
...options.map((entry) => ` ${entry.flag.padEnd(width, " ")} ${entry.description}`),
|
|
1105
|
+
].join("\n");
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function renderLineListSection(
|
|
1109
|
+
title: string,
|
|
1110
|
+
lines: string[],
|
|
1111
|
+
code = false,
|
|
1112
|
+
): string {
|
|
1113
|
+
return [
|
|
1114
|
+
`${title}:`,
|
|
1115
|
+
...lines.map((line) => ` ${code ? line : line}`),
|
|
1116
|
+
].join("\n");
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function formatEntryLabel(entry: HelpNode): string {
|
|
1120
|
+
return entry.aliases && entry.aliases.length > 0
|
|
1121
|
+
? `${entry.name}, ${entry.aliases.join(", ")}`
|
|
1122
|
+
: entry.name;
|
|
1123
|
+
}
|