argsbarg 3.0.0 → 3.2.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/CHANGELOG.md +28 -1
- package/README.md +6 -6
- package/bun.lock +21 -0
- package/docs/ai-skills.md +8 -4
- package/docs/bundled-docs.md +91 -0
- package/docs/install.md +16 -4
- package/docs/mcp.md +4 -6
- package/examples/minimal.ts +6 -0
- package/examples/nested.ts +6 -0
- package/index.d.ts +46 -1
- package/package.json +4 -1
- package/src/builtins/completion-bash.ts +1 -8
- package/src/builtins/completion-fish.ts +0 -5
- package/src/builtins/completion-zsh.ts +1 -9
- package/src/builtins/dispatch.ts +40 -0
- package/src/builtins/export.ts +9 -0
- package/src/builtins/install.ts +9 -3
- package/src/builtins/presentation.ts +9 -0
- package/src/builtins/shell-helpers.ts +0 -2
- package/src/builtins/update.ts +14 -0
- package/src/capabilities.ts +12 -1
- package/src/docs/api-guide.test.ts +55 -0
- package/src/docs/api-guide.ts +129 -0
- package/src/docs/builtin.ts +61 -0
- package/src/docs/docs.test.ts +167 -0
- package/src/docs/mcp-guide.ts +118 -0
- package/src/docs/resolve.ts +119 -0
- package/src/help.ts +3 -7
- package/src/index.test.ts +40 -66
- package/src/index.ts +4 -0
- package/src/install/binary.ts +8 -3
- package/src/install/index.ts +55 -30
- package/src/install/plan.ts +5 -3
- package/src/install/update.test.ts +106 -0
- package/src/install/update.ts +55 -0
- package/src/invoke.ts +17 -15
- package/src/mcp/tools.ts +1 -1
- package/src/parse.ts +0 -27
- package/src/runtime.ts +12 -6
- package/src/schema.ts +7 -2
- package/src/skill/generate.ts +6 -39
- package/src/types.ts +47 -0
- package/src/validate.ts +53 -6
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import type { CliProgram } from "../types.ts";
|
|
3
|
+
import { runInstallMutation } from "./index.ts";
|
|
4
|
+
import { installErr } from "./status.ts";
|
|
5
|
+
|
|
6
|
+
/** Downloads the latest release and reinstalls installed artifacts (`myapp update`). */
|
|
7
|
+
export async function cliUpdate(root: CliProgram): Promise<never> {
|
|
8
|
+
const hook = root.install?.updateGetLatest;
|
|
9
|
+
if (!hook) {
|
|
10
|
+
installErr("update is not configured. Set install.updateGetLatest on the program root.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let artifact: Awaited<ReturnType<typeof hook>>;
|
|
15
|
+
try {
|
|
16
|
+
artifact = await hook({ version: root.version });
|
|
17
|
+
} catch (err) {
|
|
18
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
19
|
+
installErr(message);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!artifact.path || !existsSync(artifact.path)) {
|
|
24
|
+
installErr(`updateGetLatest returned missing binary: ${JSON.stringify(artifact.path)}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (artifact.version !== undefined && artifact.version === root.version) {
|
|
29
|
+
process.stdout.write(`Already at v${root.version}\n`);
|
|
30
|
+
await artifact.cleanup?.();
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const currentVersion = root.version;
|
|
35
|
+
try {
|
|
36
|
+
await runInstallMutation(root, {
|
|
37
|
+
reinstall: "1",
|
|
38
|
+
yes: "1",
|
|
39
|
+
quiet: "1",
|
|
40
|
+
from: artifact.path,
|
|
41
|
+
});
|
|
42
|
+
} catch (err) {
|
|
43
|
+
await artifact.cleanup?.();
|
|
44
|
+
installErr(err instanceof Error ? err.message : String(err));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await artifact.cleanup?.();
|
|
49
|
+
|
|
50
|
+
if (artifact.version !== undefined && artifact.version !== currentVersion) {
|
|
51
|
+
process.stdout.write(`Updated ${root.key} ${currentVersion} → ${artifact.version}\n`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
package/src/invoke.ts
CHANGED
|
@@ -5,12 +5,14 @@ process.exit so MCP tool calls can run handlers repeatedly.
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { CliContext } from "./context.ts";
|
|
8
|
+
import { builtinInterceptRoot } from "./builtins/dispatch.ts";
|
|
9
|
+
import { cliPresentationRoot } from "./builtins/presentation.ts";
|
|
8
10
|
import { parse, postParseValidate, ParseKind } from "./parse.ts";
|
|
9
|
-
import { type CliNode, type CliProgram, isCliRouter } from "./types.ts";
|
|
11
|
+
import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
|
|
10
12
|
import { format } from "node:util";
|
|
11
13
|
|
|
12
14
|
/** Outcome of a non-exiting CLI invocation. */
|
|
13
|
-
export type CliInvokeKind = "ok" | "help" | "
|
|
15
|
+
export type CliInvokeKind = "ok" | "help" | "error";
|
|
14
16
|
|
|
15
17
|
/** Result of cliInvoke: captured output and exit metadata without process.exit. */
|
|
16
18
|
export interface CliInvokeResult {
|
|
@@ -49,8 +51,18 @@ function findChild(cmds: CliNode[], name: string): CliNode | undefined {
|
|
|
49
51
|
* Never calls process.exit.
|
|
50
52
|
*/
|
|
51
53
|
export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliInvokeResult> {
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
+
let parseRoot: CliNode = root;
|
|
55
|
+
if (isCliLeaf(root)) {
|
|
56
|
+
const intercept = builtinInterceptRoot(root, argv);
|
|
57
|
+
if (intercept.parseRoot !== root) {
|
|
58
|
+
parseRoot = intercept.parseRoot;
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
parseRoot = cliPresentationRoot(root);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let pr = parse(parseRoot, argv);
|
|
65
|
+
pr = postParseValidate(parseRoot, pr);
|
|
54
66
|
|
|
55
67
|
if (pr.kind === ParseKind.Help) {
|
|
56
68
|
return {
|
|
@@ -62,16 +74,6 @@ export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliIn
|
|
|
62
74
|
};
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
if (pr.kind === ParseKind.Schema) {
|
|
66
|
-
return {
|
|
67
|
-
kind: "schema",
|
|
68
|
-
exitCode: 1,
|
|
69
|
-
stdout: "",
|
|
70
|
-
stderr: "",
|
|
71
|
-
errorMsg: "Schema export is not available via MCP tool calls.",
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
77
|
if (pr.kind === ParseKind.Error) {
|
|
76
78
|
return {
|
|
77
79
|
kind: "error",
|
|
@@ -82,7 +84,7 @@ export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliIn
|
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
let current: CliNode =
|
|
87
|
+
let current: CliNode = parseRoot;
|
|
86
88
|
for (const seg of pr.path) {
|
|
87
89
|
if (!isCliRouter(current)) {
|
|
88
90
|
return {
|
package/src/mcp/tools.ts
CHANGED
|
@@ -134,7 +134,7 @@ export function allMcpResources(root: CliProgram): McpResourceEntry[] {
|
|
|
134
134
|
const builtIn: McpResourceEntry = {
|
|
135
135
|
uri: schemaUri,
|
|
136
136
|
name: "cli-schema",
|
|
137
|
-
description: "Full CLI command tree (same as
|
|
137
|
+
description: "Full CLI command tree (same as docs schema).",
|
|
138
138
|
mimeType: "application/json",
|
|
139
139
|
load: () => cliSchemaJson(root),
|
|
140
140
|
};
|
package/src/parse.ts
CHANGED
|
@@ -29,8 +29,6 @@ export enum ParseKind {
|
|
|
29
29
|
Ok = "ok",
|
|
30
30
|
/** User requested help (explicit or implicit). */
|
|
31
31
|
Help = "help",
|
|
32
|
-
/** User requested machine-readable schema export (`--schema`). */
|
|
33
|
-
Schema = "schema",
|
|
34
32
|
/** User error (unknown command, bad option, etc.). */
|
|
35
33
|
Error = "error",
|
|
36
34
|
}
|
|
@@ -59,18 +57,12 @@ export interface ParseResult {
|
|
|
59
57
|
|
|
60
58
|
const helpShort = "-h";
|
|
61
59
|
const helpLong = "--help";
|
|
62
|
-
const schemaLong = "--schema";
|
|
63
60
|
|
|
64
61
|
/** Returns true if the argv token is `-h` or `--help`. */
|
|
65
62
|
function isHelpTok(tok: string): boolean {
|
|
66
63
|
return tok === helpShort || tok === helpLong;
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
/** Returns true if the argv token is `--schema`. */
|
|
70
|
-
function isSchemaTok(tok: string): boolean {
|
|
71
|
-
return tok === schemaLong;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
66
|
/** Looks up a subcommand or routing node by `key`. */
|
|
75
67
|
function findChild(cmds: CliNode[], name: string): CliNode | undefined {
|
|
76
68
|
return cmds.find((c) => c.key === name);
|
|
@@ -195,7 +187,6 @@ function consumeOptions(
|
|
|
195
187
|
const tok = argv[idx];
|
|
196
188
|
|
|
197
189
|
if (isHelpTok(tok)) break;
|
|
198
|
-
if (isSchemaTok(tok)) break;
|
|
199
190
|
if (!tok.startsWith("-")) break;
|
|
200
191
|
|
|
201
192
|
if (tok === "--") {
|
|
@@ -371,20 +362,6 @@ function helpResult(p: string[], explicit: boolean): ParseResult {
|
|
|
371
362
|
};
|
|
372
363
|
}
|
|
373
364
|
|
|
374
|
-
/** Builds a schema-export result for the program root. */
|
|
375
|
-
function schemaResult(): ParseResult {
|
|
376
|
-
return {
|
|
377
|
-
kind: ParseKind.Schema,
|
|
378
|
-
path: [],
|
|
379
|
-
opts: {},
|
|
380
|
-
args: [],
|
|
381
|
-
helpExplicit: false,
|
|
382
|
-
helpPath: [],
|
|
383
|
-
errorMsg: "",
|
|
384
|
-
errorHelpPath: [],
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
365
|
/**
|
|
389
366
|
* Parses `argv` against the program root, routing into subcommands and filling `opts` / `args`.
|
|
390
367
|
*/
|
|
@@ -420,10 +397,6 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
420
397
|
return helpResult([], true);
|
|
421
398
|
}
|
|
422
399
|
|
|
423
|
-
if (i < argv.length && !forcePositionals && isSchemaTok(argv[i])) {
|
|
424
|
-
return schemaResult();
|
|
425
|
-
}
|
|
426
|
-
|
|
427
400
|
// Determine which subcommand to route to
|
|
428
401
|
let cmdName: string;
|
|
429
402
|
let node: CliNode | undefined;
|
package/src/runtime.ts
CHANGED
|
@@ -10,7 +10,6 @@ import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.t
|
|
|
10
10
|
import { CliContext } from "./context.ts";
|
|
11
11
|
import { cliHelpRender } from "./help.ts";
|
|
12
12
|
import { parse, postParseValidate, ParseKind } from "./parse.ts";
|
|
13
|
-
import { cliSchemaJson } from "./schema.ts";
|
|
14
13
|
import { cliValidateProgram } from "./validate.ts";
|
|
15
14
|
|
|
16
15
|
function cliRootMergedWithBuiltins(program: CliProgram): CliRouter {
|
|
@@ -41,6 +40,18 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
|
|
|
41
40
|
process.exit(1);
|
|
42
41
|
}
|
|
43
42
|
|
|
43
|
+
if (argv.length >= 1 && argv[0] === "update" && !caps.update) {
|
|
44
|
+
process.stderr.write(
|
|
45
|
+
"update is not enabled. Set install.updateGetLatest on the program root.\n",
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (argv.length >= 1 && argv[0] === "docs" && !caps.docs) {
|
|
51
|
+
process.stderr.write("docs is not enabled. Set docs: { enabled: true } on the program root.\n");
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
let parseRoot: CliNode;
|
|
45
56
|
let completionParseRoot: CliRouter = cliRootMergedWithBuiltins(program);
|
|
46
57
|
let isLeafCompletionIntercept = false;
|
|
@@ -68,11 +79,6 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
|
|
|
68
79
|
process.exit(pr.helpExplicit ? 0 : 1);
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
if (pr.kind === ParseKind.Schema) {
|
|
72
|
-
process.stdout.write(cliSchemaJson(program));
|
|
73
|
-
process.exit(0);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
82
|
if (pr.kind === "error") {
|
|
77
83
|
const color = process.stderr.isTTY;
|
|
78
84
|
const msg = color ? `\u001B[31m${pr.errorMsg}\u001B[0m` : pr.errorMsg;
|
package/src/schema.ts
CHANGED
|
@@ -5,7 +5,7 @@ This module serializes the CLI schema tree to JSON for machine-readable introspe
|
|
|
5
5
|
import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
|
|
6
6
|
import { exportPresentationBuiltins, type CliSchemaExport } from "./builtins/export.ts";
|
|
7
7
|
|
|
8
|
-
const RESERVED = new Set(["completion", "install", "mcp"]);
|
|
8
|
+
const RESERVED = new Set(["completion", "install", "docs", "mcp", "version", "update"]);
|
|
9
9
|
|
|
10
10
|
function exportCommand(cmd: CliNode): CliSchemaExport {
|
|
11
11
|
const out: CliSchemaExport = {
|
|
@@ -44,8 +44,13 @@ function exportCommand(cmd: CliNode): CliSchemaExport {
|
|
|
44
44
|
return out;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/** Returns the JSON-safe command tree (handlers omitted). */
|
|
48
|
+
export function cliSchemaExport(root: CliProgram): CliSchemaExport {
|
|
49
|
+
return exportCommand(root);
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
export function cliSchemaJson(root: CliProgram): string {
|
|
48
|
-
return JSON.stringify(
|
|
53
|
+
return JSON.stringify(cliSchemaExport(root), null, 2) + "\n";
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export type { CliSchemaExport };
|
package/src/skill/generate.ts
CHANGED
|
@@ -4,7 +4,7 @@ This module generates Agent Skills content (SKILL.md + reference.md) from a CLI
|
|
|
4
4
|
|
|
5
5
|
import { collectOptionDefs } from "../parse.ts";
|
|
6
6
|
import { cliSchemaJson } from "../schema.ts";
|
|
7
|
-
import { collectMcpTools,
|
|
7
|
+
import { collectMcpTools, sanitizeToolSegment } from "../mcp/tools.ts";
|
|
8
8
|
import { CliProgram, CliOptionKind } from "../types.ts";
|
|
9
9
|
|
|
10
10
|
export type SkillTarget = "cursor" | "claude";
|
|
@@ -69,46 +69,14 @@ function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): s
|
|
|
69
69
|
"",
|
|
70
70
|
"## When to use",
|
|
71
71
|
"",
|
|
72
|
-
`Use this skill when working with **${root.key}** — shell commands
|
|
72
|
+
`Use this skill when working with **${root.key}** — shell commands and automation for this application.`,
|
|
73
73
|
"",
|
|
74
74
|
"## Execution",
|
|
75
75
|
"",
|
|
76
|
+
"Invoke via shell:",
|
|
77
|
+
"",
|
|
76
78
|
];
|
|
77
79
|
|
|
78
|
-
if (root.mcpServer?.enabled === true) {
|
|
79
|
-
lines.push(
|
|
80
|
-
"**Prefer MCP** when a host has the server connected:",
|
|
81
|
-
"",
|
|
82
|
-
"```bash",
|
|
83
|
-
`${root.key} mcp`,
|
|
84
|
-
"```",
|
|
85
|
-
"",
|
|
86
|
-
"Example Cursor `mcp.json` entry:",
|
|
87
|
-
"",
|
|
88
|
-
"```json",
|
|
89
|
-
JSON.stringify(
|
|
90
|
-
{
|
|
91
|
-
mcpServers: {
|
|
92
|
-
[mcpServerId(root)]: {
|
|
93
|
-
command: root.key,
|
|
94
|
-
args: ["mcp"],
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
null,
|
|
99
|
-
2,
|
|
100
|
-
),
|
|
101
|
-
"```",
|
|
102
|
-
"",
|
|
103
|
-
"When MCP tools are available, use `tools/call` with flat JSON arguments. Read the schema resource for full shapes.",
|
|
104
|
-
"",
|
|
105
|
-
"Otherwise invoke via shell:",
|
|
106
|
-
"",
|
|
107
|
-
);
|
|
108
|
-
} else {
|
|
109
|
-
lines.push("Invoke via shell:", "");
|
|
110
|
-
}
|
|
111
|
-
|
|
112
80
|
lines.push("```bash", `${root.key} <subcommand> [options] [args]`, "```", "", "## Commands", "");
|
|
113
81
|
|
|
114
82
|
if (tools.length === 0) {
|
|
@@ -124,12 +92,11 @@ function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): s
|
|
|
124
92
|
"## Pitfalls",
|
|
125
93
|
"",
|
|
126
94
|
"- Use `--` before tokens that look like flags when they are positional arguments.",
|
|
127
|
-
"- Under MCP (`ctx.invocation === \"mcp\"`), child processes must not inherit stdout — use piped stdout.",
|
|
128
95
|
"- Required environment variables are listed per command in descriptions (`requires env`).",
|
|
129
96
|
"",
|
|
130
97
|
"## Reference",
|
|
131
98
|
"",
|
|
132
|
-
"See `reference.md` in this skill directory for the full
|
|
99
|
+
"See `reference.md` in this skill directory for the full `docs schema` JSON export.",
|
|
133
100
|
"",
|
|
134
101
|
);
|
|
135
102
|
|
|
@@ -163,7 +130,7 @@ function buildReferenceMd(root: CliProgram): string {
|
|
|
163
130
|
return [
|
|
164
131
|
`# ${root.key} — CLI reference`,
|
|
165
132
|
"",
|
|
166
|
-
"Generated from the program
|
|
133
|
+
"Generated from the program `docs schema` export. Handlers and runtime-only nodes are omitted.",
|
|
167
134
|
"",
|
|
168
135
|
"```json",
|
|
169
136
|
cliSchemaJson(root).trimEnd(),
|
package/src/types.ts
CHANGED
|
@@ -153,11 +153,56 @@ export interface CliMcpToolConfig {
|
|
|
153
153
|
/**
|
|
154
154
|
* Opt-out and defaults for the `install` built-in (program root only).
|
|
155
155
|
*/
|
|
156
|
+
export interface CliUpdateArtifact {
|
|
157
|
+
/** Path to an executable binary to copy into the install location. */
|
|
158
|
+
path: string;
|
|
159
|
+
/** Release version of `path` (used for already-current checks and success messages). */
|
|
160
|
+
version?: string;
|
|
161
|
+
/** Called after reinstall completes (e.g. remove a temp download directory). */
|
|
162
|
+
cleanup?: () => void | Promise<void>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Fetches the latest release binary for the `update` built-in. */
|
|
166
|
+
export type CliUpdateGetLatest = (ctx: { version: string }) => Promise<CliUpdateArtifact>;
|
|
167
|
+
|
|
156
168
|
export interface CliInstallConfig {
|
|
157
169
|
/** When `false`, hide/disable `install` (default: enabled). */
|
|
158
170
|
enabled?: boolean;
|
|
159
171
|
/** Default bin directory (default: `~/.local/bin`). Overridden by `INSTALL_PREFIX` env and `--prefix`. */
|
|
160
172
|
prefix?: string;
|
|
173
|
+
/**
|
|
174
|
+
* When set, enables the `update` built-in (`myapp update`).
|
|
175
|
+
* Should download or locate the latest release binary and return its path.
|
|
176
|
+
*/
|
|
177
|
+
updateGetLatest?: CliUpdateGetLatest;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* One bundled documentation topic for the `docs` built-in (program root only).
|
|
182
|
+
*/
|
|
183
|
+
export interface CliDocsTopic {
|
|
184
|
+
/** Bundled markdown (use compile-time text imports in the consumer). */
|
|
185
|
+
text: string;
|
|
186
|
+
/** Leaf help text for `myapp docs <key> -h`. Auto-generated from key when omitted. */
|
|
187
|
+
description?: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Enables `myapp docs` and bundled markdown topics (program root only).
|
|
192
|
+
* Must include `enabled: true`; omit `docs` entirely to disable.
|
|
193
|
+
*/
|
|
194
|
+
export interface CliDocsConfig {
|
|
195
|
+
/** When `true`, enables the `docs` built-in command group. */
|
|
196
|
+
enabled: boolean;
|
|
197
|
+
/** Router description for `myapp docs` (default: "Print bundled CLI documentation."). */
|
|
198
|
+
description?: string;
|
|
199
|
+
/**
|
|
200
|
+
* Subcommand for bare `myapp docs` (maps to router `fallbackCommand`).
|
|
201
|
+
* When omitted, uses the first key in `topics` (insertion order).
|
|
202
|
+
*/
|
|
203
|
+
defaultTopic?: string;
|
|
204
|
+
/** Topic key → bundled markdown. Reserved keys: `mcp`, `all` (supplied by the built-in). */
|
|
205
|
+
topics: Record<string, CliDocsTopic>;
|
|
161
206
|
}
|
|
162
207
|
|
|
163
208
|
/**
|
|
@@ -214,6 +259,8 @@ export type CliProgram = CliNode & {
|
|
|
214
259
|
mcpServer?: CliMcpServerConfig;
|
|
215
260
|
/** Opt-out and defaults for `install`. */
|
|
216
261
|
install?: CliInstallConfig;
|
|
262
|
+
/** When set with `enabled: true`, enables the `docs` built-in command group. */
|
|
263
|
+
docs?: CliDocsConfig;
|
|
217
264
|
};
|
|
218
265
|
|
|
219
266
|
/** True when the node is a leaf (has a handler). */
|
package/src/validate.ts
CHANGED
|
@@ -13,6 +13,33 @@ import {
|
|
|
13
13
|
isCliRouter,
|
|
14
14
|
} from "./types.ts";
|
|
15
15
|
import { resolveMcpSchemaUri } from "./mcp/tools.ts";
|
|
16
|
+
import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
|
|
17
|
+
|
|
18
|
+
/** Validates `docs` configuration on the program root. */
|
|
19
|
+
function validateDocsConfig(docs: import("./types.ts").CliDocsConfig): void {
|
|
20
|
+
const keys = Object.keys(docs.topics);
|
|
21
|
+
if (keys.length === 0) {
|
|
22
|
+
throw new CliSchemaValidationError("docs.topics must be non-empty");
|
|
23
|
+
}
|
|
24
|
+
for (const reserved of DOCS_BUILTIN_TOPIC_KEYS) {
|
|
25
|
+
if (reserved in docs.topics) {
|
|
26
|
+
throw new CliSchemaValidationError(
|
|
27
|
+
`docs.topics key '${reserved}' is reserved for the docs built-in`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (docs.defaultTopic !== undefined && !(docs.defaultTopic in docs.topics)) {
|
|
32
|
+
throw new CliSchemaValidationError(
|
|
33
|
+
`docs.defaultTopic '${docs.defaultTopic}' is not a key in docs.topics`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const text = docs.topics[key]?.text;
|
|
38
|
+
if (text === undefined || text.length === 0) {
|
|
39
|
+
throw new CliSchemaValidationError(`docs.topics['${key}'].text must be non-empty`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
16
43
|
|
|
17
44
|
/** Validates a program schema. */
|
|
18
45
|
export function cliValidateProgram(program: CliProgram): void {
|
|
@@ -26,6 +53,27 @@ export function cliValidateProgram(program: CliProgram): void {
|
|
|
26
53
|
);
|
|
27
54
|
}
|
|
28
55
|
|
|
56
|
+
if (program.docs !== undefined && program.docs.enabled !== true) {
|
|
57
|
+
throw new CliSchemaValidationError(
|
|
58
|
+
"docs requires enabled: true; omit docs to disable bundled documentation",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (program.docs?.enabled === true) {
|
|
63
|
+
validateDocsConfig(program.docs);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (program.install?.updateGetLatest !== undefined) {
|
|
67
|
+
if (program.install.enabled === false) {
|
|
68
|
+
throw new CliSchemaValidationError(
|
|
69
|
+
"install.updateGetLatest requires install to be enabled (omit install.enabled: false)",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (typeof program.install.updateGetLatest !== "function") {
|
|
73
|
+
throw new CliSchemaValidationError("install.updateGetLatest must be a function");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
29
77
|
const caps = resolveCapabilities(program);
|
|
30
78
|
const reserved = reservedCommandNames(caps);
|
|
31
79
|
|
|
@@ -53,6 +101,11 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
53
101
|
"install is only supported on the program root (not on " + node.key + ")",
|
|
54
102
|
);
|
|
55
103
|
}
|
|
104
|
+
if (rogue.docs !== undefined) {
|
|
105
|
+
throw new CliSchemaValidationError(
|
|
106
|
+
"docs is only supported on the program root (not on " + node.key + ")",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
56
109
|
}
|
|
57
110
|
|
|
58
111
|
if (isCliLeaf(node)) {
|
|
@@ -124,12 +177,6 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
|
|
|
124
177
|
);
|
|
125
178
|
}
|
|
126
179
|
|
|
127
|
-
if (opt.name === "schema") {
|
|
128
|
-
throw new CliSchemaValidationError(
|
|
129
|
-
`Option name "schema" is reserved for --schema: ${scopeKey}/${opt.name}`,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
180
|
if (opt.shortName !== undefined) {
|
|
134
181
|
if (opt.shortName === "h") {
|
|
135
182
|
throw new CliSchemaValidationError(
|