argsbarg 2.1.1 → 3.1.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 +25 -1
- package/README.md +11 -7
- package/bun.lock +21 -0
- package/docs/ai-skills.md +7 -3
- package/docs/bundled-docs.md +77 -0
- package/docs/install.md +2 -2
- package/docs/mcp.md +18 -13
- package/examples/mcp-test.ts +2 -2
- package/examples/minimal.ts +2 -0
- package/examples/nested.ts +9 -1
- package/examples/option-required.ts +2 -0
- package/index.d.ts +36 -7
- package/package.json +4 -1
- package/src/builtins/builtins.test.ts +8 -3
- package/src/builtins/dispatch.ts +35 -1
- package/src/builtins/export.ts +10 -1
- package/src/builtins/install.ts +2 -1
- package/src/builtins/presentation.ts +10 -1
- package/src/builtins/version.ts +10 -0
- package/src/capabilities.ts +7 -2
- package/src/docs/builtin.ts +58 -0
- package/src/docs/docs.test.ts +121 -0
- package/src/docs/mcp-guide.ts +118 -0
- package/src/docs/resolve.ts +88 -0
- package/src/index.test.ts +186 -123
- package/src/index.ts +2 -0
- package/src/install/index.ts +2 -1
- package/src/install/install.test.ts +2 -1
- package/src/install/paths.ts +2 -2
- package/src/install/plan.ts +3 -2
- package/src/install/uninstall.ts +2 -1
- package/src/invoke.ts +16 -4
- package/src/mcp/tools.ts +21 -24
- package/src/runtime.ts +6 -1
- package/src/schema.ts +1 -1
- package/src/skill/generate.ts +3 -36
- package/src/types.test.ts +3 -2
- package/src/types.ts +38 -7
- package/src/validate.ts +55 -3
package/src/index.ts
CHANGED
package/src/install/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readSync } from "node:fs";
|
|
2
|
+
import { resolveCapabilities } from "../capabilities.ts";
|
|
2
3
|
import { CliProgram } from "../types.ts";
|
|
3
4
|
import { cliSkillInstall } from "../skill/install.ts";
|
|
4
5
|
import { checkMcpConflict, expectedMcpEntry } from "./mcp-config.ts";
|
|
@@ -130,7 +131,7 @@ export async function cliInstall(root: CliProgram, rawOpts: Record<string, strin
|
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
// MCP conflict checks before planning
|
|
133
|
-
if (!opts.uninstall && root.
|
|
134
|
+
if (!opts.uninstall && resolveCapabilities(root).mcp && (opts.all || opts.mcp)) {
|
|
134
135
|
const entry = expectedMcpEntry(root);
|
|
135
136
|
const yes = !!opts.yes;
|
|
136
137
|
for (const p of [paths.cursorMcpPath, paths.claudeMcpPath]) {
|
package/src/install/paths.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { CliProgram } from "../types.ts";
|
|
4
|
-
import { sanitizeToolSegment } from "../mcp/tools.ts";
|
|
4
|
+
import { sanitizeToolSegment, mcpServerId } from "../mcp/tools.ts";
|
|
5
5
|
|
|
6
6
|
export interface InstallPaths {
|
|
7
7
|
bindir: string;
|
|
@@ -63,7 +63,7 @@ export function resolveInstallPaths(root: CliProgram, opts: { prefix?: string })
|
|
|
63
63
|
claudeMcpPath: join(home, ".claude.json"),
|
|
64
64
|
bashRc: join(home, ".bashrc"),
|
|
65
65
|
zshRc: join(home, ".zshrc"),
|
|
66
|
-
mcpName: root
|
|
66
|
+
mcpName: mcpServerId(root),
|
|
67
67
|
skillDirName,
|
|
68
68
|
};
|
|
69
69
|
}
|
package/src/install/plan.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { resolveCapabilities } from "../capabilities.ts";
|
|
3
4
|
import { CliProgram } from "../types.ts";
|
|
4
5
|
import { installBinary } from "./binary.ts";
|
|
5
6
|
import { installCompletions } from "./completions.ts";
|
|
@@ -46,7 +47,7 @@ function wantsSkill(opts: InstallOpts): boolean {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
function wantsMcp(opts: InstallOpts, root: CliProgram): boolean {
|
|
49
|
-
return !!(opts.
|
|
50
|
+
return !!(opts.mcp || opts.all) && resolveCapabilities(root).mcp;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/** Builds install actions for normal mode (--all / scoped targets). */
|
|
@@ -154,7 +155,7 @@ export function buildUpdatePlan(root: CliProgram, paths: InstallPaths, opts: Ins
|
|
|
154
155
|
bin: true,
|
|
155
156
|
completions: detected.bashCompletion || detected.zshCompletion || detected.fishCompletion,
|
|
156
157
|
skill: detected.cursorSkill || detected.claudeSkill,
|
|
157
|
-
mcp: (detected.cursorMcp || detected.claudeMcp) && root.
|
|
158
|
+
mcp: (detected.cursorMcp || detected.claudeMcp) && resolveCapabilities(root).mcp,
|
|
158
159
|
dry: opts.dry,
|
|
159
160
|
};
|
|
160
161
|
const plan = buildInstallPlan(root, paths, scoped);
|
package/src/install/uninstall.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, rmSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { resolveCapabilities } from "../capabilities.ts";
|
|
3
4
|
import { CliProgram } from "../types.ts";
|
|
4
5
|
import { uninstallBinary } from "./binary.ts";
|
|
5
6
|
import { uninstallCompletions } from "./completions.ts";
|
|
@@ -77,7 +78,7 @@ export function buildUninstallPlan(
|
|
|
77
78
|
});
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
if ((all || opts.mcp) && root.
|
|
81
|
+
if ((all || opts.mcp) && resolveCapabilities(root).mcp) {
|
|
81
82
|
if (detected.cursorMcp) {
|
|
82
83
|
actions.push({
|
|
83
84
|
summary: `cursor mcp: ${paths.cursorMcpPath}`,
|
package/src/invoke.ts
CHANGED
|
@@ -5,8 +5,10 @@ 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. */
|
|
@@ -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 {
|
|
@@ -82,7 +94,7 @@ export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliIn
|
|
|
82
94
|
};
|
|
83
95
|
}
|
|
84
96
|
|
|
85
|
-
let current:
|
|
97
|
+
let current: CliNode = parseRoot;
|
|
86
98
|
for (const seg of pr.path) {
|
|
87
99
|
if (!isCliRouter(current)) {
|
|
88
100
|
return {
|
package/src/mcp/tools.ts
CHANGED
|
@@ -3,14 +3,24 @@ This module maps CliProgram leaf nodes to MCP tool definitions and converts
|
|
|
3
3
|
flat JSON tool arguments into argv for cliInvoke.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { readFileSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
8
6
|
import { collectOptionDefs } from "../parse.ts";
|
|
9
7
|
import { cliSchemaJson } from "../schema.ts";
|
|
10
8
|
import { CliProgram, CliLeaf, CliNode, CliOption, CliOptionKind, CliPositional, isCliLeaf, isCliRouter } from "../types.ts";
|
|
11
9
|
|
|
12
|
-
/** Default URI for the CLI schema MCP resource. */
|
|
13
|
-
export
|
|
10
|
+
/** Default URI pattern for the CLI schema MCP resource (`<mcpId>://schema`). */
|
|
11
|
+
export function defaultMcpSchemaUri(mcpId: string): string {
|
|
12
|
+
return `${mcpId}://schema`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Sanitizes a command key segment for MCP tool names and server identity. */
|
|
16
|
+
export function sanitizeToolSegment(key: string): string {
|
|
17
|
+
return key.replace(/[^a-zA-Z0-9]/g, "_");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** MCP server id derived from the program root key (sanitized). */
|
|
21
|
+
export function mcpServerId(root: CliProgram): string {
|
|
22
|
+
return sanitizeToolSegment(root.key);
|
|
23
|
+
}
|
|
14
24
|
|
|
15
25
|
/** One MCP tool derived from a leaf CLI command. */
|
|
16
26
|
export interface McpToolDef {
|
|
@@ -32,11 +42,6 @@ export function mcpToolDescription(path: string[], rootKey: string, description:
|
|
|
32
42
|
return `${prefix} — ${description}`;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
|
-
/** Sanitizes a command key segment for MCP tool names. */
|
|
36
|
-
export function sanitizeToolSegment(key: string): string {
|
|
37
|
-
return key.replace(/[^a-zA-Z0-9]/g, "_");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
45
|
/** Builds the MCP tool name for a leaf at the given path. */
|
|
41
46
|
export function mcpToolName(root: CliProgram, path: string[]): string {
|
|
42
47
|
if (path.length === 0) {
|
|
@@ -150,7 +155,7 @@ export function collectMcpTools(root: CliProgram): McpToolDef[] {
|
|
|
150
155
|
/** Walks the command tree and appends leaf tools. */
|
|
151
156
|
function walk(cmd: CliNode, path: string[]): void {
|
|
152
157
|
if (isCliLeaf(cmd)) {
|
|
153
|
-
if (cmd.key === "completion" || cmd.key === "install" || cmd.key === "mcp") {
|
|
158
|
+
if (cmd.key === "completion" || cmd.key === "install" || cmd.key === "mcp" || cmd.key === "version") {
|
|
154
159
|
return;
|
|
155
160
|
}
|
|
156
161
|
if (cmd.mcpTool?.enabled === false) {
|
|
@@ -181,28 +186,20 @@ export function collectMcpTools(root: CliProgram): McpToolDef[] {
|
|
|
181
186
|
return out;
|
|
182
187
|
}
|
|
183
188
|
|
|
184
|
-
/** Reads package.json version from cwd synchronously. */
|
|
185
|
-
function resolveMcpVersionFromPackageJson(): string | undefined {
|
|
186
|
-
try {
|
|
187
|
-
const text = readFileSync(join(process.cwd(), "package.json"), "utf8");
|
|
188
|
-
const version = (JSON.parse(text) as { version?: string }).version;
|
|
189
|
-
return typeof version === "string" ? version : undefined;
|
|
190
|
-
} catch {
|
|
191
|
-
return undefined;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
189
|
/** Resolves MCP server name and version for initialize. */
|
|
196
190
|
export function resolveMcpServerInfo(root: CliProgram): { name: string; version: string } {
|
|
197
191
|
return {
|
|
198
|
-
name: root
|
|
199
|
-
version: root.
|
|
192
|
+
name: mcpServerId(root),
|
|
193
|
+
version: root.version,
|
|
200
194
|
};
|
|
201
195
|
}
|
|
202
196
|
|
|
203
197
|
/** Resolves the schema resource URI for this app. */
|
|
204
198
|
export function resolveMcpSchemaUri(root: CliProgram): string {
|
|
205
|
-
|
|
199
|
+
if (root.mcpServer?.schemaResourceUri) {
|
|
200
|
+
return root.mcpServer.schemaResourceUri;
|
|
201
|
+
}
|
|
202
|
+
return defaultMcpSchemaUri(mcpServerId(root));
|
|
206
203
|
}
|
|
207
204
|
|
|
208
205
|
/** Converts flat MCP tool arguments to argv for cliInvoke. */
|
package/src/runtime.ts
CHANGED
|
@@ -32,7 +32,7 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
|
|
|
32
32
|
const caps = resolveCapabilities(program);
|
|
33
33
|
|
|
34
34
|
if (argv.length >= 1 && argv[0] === "mcp" && !caps.mcp) {
|
|
35
|
-
process.stderr.write("MCP is not enabled. Set mcpServer on the program root.\n");
|
|
35
|
+
process.stderr.write("MCP is not enabled. Set mcpServer: { enabled: true } on the program root.\n");
|
|
36
36
|
process.exit(1);
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -41,6 +41,11 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
|
|
|
41
41
|
process.exit(1);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
if (argv.length >= 1 && argv[0] === "docs" && !caps.docs) {
|
|
45
|
+
process.stderr.write("docs is not enabled. Set docs: { enabled: true } on the program root.\n");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
let parseRoot: CliNode;
|
|
45
50
|
let completionParseRoot: CliRouter = cliRootMergedWithBuiltins(program);
|
|
46
51
|
let isLeafCompletionIntercept = false;
|
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"]);
|
|
9
9
|
|
|
10
10
|
function exportCommand(cmd: CliNode): CliSchemaExport {
|
|
11
11
|
const out: CliSchemaExport = {
|
package/src/skill/generate.ts
CHANGED
|
@@ -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 !== undefined) {
|
|
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
|
-
[root.mcpServer.name ?? root.key]: {
|
|
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,7 +92,6 @@ 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",
|
package/src/types.test.ts
CHANGED
|
@@ -18,8 +18,9 @@ const _leafOnly: CliLeaf = {
|
|
|
18
18
|
|
|
19
19
|
const _program: CliProgram = {
|
|
20
20
|
key: "app",
|
|
21
|
+
version: "0.0.0",
|
|
21
22
|
description: "",
|
|
22
|
-
mcpServer: {},
|
|
23
|
+
mcpServer: { enabled: true },
|
|
23
24
|
commands: [],
|
|
24
25
|
};
|
|
25
26
|
|
|
@@ -27,7 +28,7 @@ const _badMcpOnNode = {
|
|
|
27
28
|
key: "x",
|
|
28
29
|
description: "",
|
|
29
30
|
// @ts-expect-error mcpServer is program-root only
|
|
30
|
-
mcpServer: {},
|
|
31
|
+
mcpServer: { enabled: true },
|
|
31
32
|
commands: [],
|
|
32
33
|
} satisfies CliNode;
|
|
33
34
|
|
package/src/types.ts
CHANGED
|
@@ -89,13 +89,12 @@ export interface CliPositional {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Enables `myapp mcp` and MCP stdio server metadata (program root only).
|
|
92
|
+
* Must include `enabled: true`; omit `mcpServer` entirely to disable MCP.
|
|
92
93
|
*/
|
|
93
94
|
export interface CliMcpServerConfig {
|
|
94
|
-
/** `
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
version?: string;
|
|
98
|
-
/** Resource URI for schema export (default: `"argsbarg://schema"`). */
|
|
95
|
+
/** When `true`, enables the `mcp` built-in and MCP stdio server. */
|
|
96
|
+
enabled: boolean;
|
|
97
|
+
/** Resource URI for schema export (default: `<sanitized root key>://schema`). */
|
|
99
98
|
schemaResourceUri?: string;
|
|
100
99
|
/**
|
|
101
100
|
* Capture the user's login shell environment at MCP server start and merge it
|
|
@@ -110,7 +109,7 @@ export interface CliMcpServerConfig {
|
|
|
110
109
|
*/
|
|
111
110
|
envFile?: string;
|
|
112
111
|
/**
|
|
113
|
-
* Custom MCP resources exposed alongside the built-in
|
|
112
|
+
* Custom MCP resources exposed alongside the built-in schema resource.
|
|
114
113
|
* URIs must be unique and must not equal schemaResourceUri.
|
|
115
114
|
*/
|
|
116
115
|
resources?: CliMcpResource[];
|
|
@@ -161,6 +160,34 @@ export interface CliInstallConfig {
|
|
|
161
160
|
prefix?: string;
|
|
162
161
|
}
|
|
163
162
|
|
|
163
|
+
/**
|
|
164
|
+
* One bundled documentation topic for the `docs` built-in (program root only).
|
|
165
|
+
*/
|
|
166
|
+
export interface CliDocsTopic {
|
|
167
|
+
/** Bundled markdown (use compile-time text imports in the consumer). */
|
|
168
|
+
text: string;
|
|
169
|
+
/** Leaf help text for `myapp docs <key> -h`. Auto-generated from key when omitted. */
|
|
170
|
+
description?: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Enables `myapp docs` and bundled markdown topics (program root only).
|
|
175
|
+
* Must include `enabled: true`; omit `docs` entirely to disable.
|
|
176
|
+
*/
|
|
177
|
+
export interface CliDocsConfig {
|
|
178
|
+
/** When `true`, enables the `docs` built-in command group. */
|
|
179
|
+
enabled: boolean;
|
|
180
|
+
/** Router description for `myapp docs` (default: "Print bundled CLI documentation."). */
|
|
181
|
+
description?: string;
|
|
182
|
+
/**
|
|
183
|
+
* Subcommand for bare `myapp docs` (maps to router `fallbackCommand`).
|
|
184
|
+
* When omitted, uses the first key in `topics` (insertion order).
|
|
185
|
+
*/
|
|
186
|
+
defaultTopic?: string;
|
|
187
|
+
/** Topic key → bundled markdown. Reserved keys: `mcp`, `all` (supplied by the built-in). */
|
|
188
|
+
topics: Record<string, CliDocsTopic>;
|
|
189
|
+
}
|
|
190
|
+
|
|
164
191
|
/**
|
|
165
192
|
* Base properties shared by all nodes in the user command tree.
|
|
166
193
|
*/
|
|
@@ -209,10 +236,14 @@ export type CliNode = CliLeaf | CliRouter;
|
|
|
209
236
|
* May be a leaf or router, plus optional program-level MCP and install config.
|
|
210
237
|
*/
|
|
211
238
|
export type CliProgram = CliNode & {
|
|
212
|
-
/**
|
|
239
|
+
/** Program version (printed by the `version` built-in and MCP serverInfo). */
|
|
240
|
+
version: string;
|
|
241
|
+
/** When set with `enabled: true`, enables the `mcp` built-in subcommand. */
|
|
213
242
|
mcpServer?: CliMcpServerConfig;
|
|
214
243
|
/** Opt-out and defaults for `install`. */
|
|
215
244
|
install?: CliInstallConfig;
|
|
245
|
+
/** When set with `enabled: true`, enables the `docs` built-in command group. */
|
|
246
|
+
docs?: CliDocsConfig;
|
|
216
247
|
};
|
|
217
248
|
|
|
218
249
|
/** True when the node is a leaf (has a handler). */
|
package/src/validate.ts
CHANGED
|
@@ -12,10 +12,57 @@ import {
|
|
|
12
12
|
isCliLeaf,
|
|
13
13
|
isCliRouter,
|
|
14
14
|
} from "./types.ts";
|
|
15
|
-
import {
|
|
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 {
|
|
46
|
+
if (!program.version || program.version.trim().length === 0) {
|
|
47
|
+
throw new CliSchemaValidationError("CliProgram.version is required");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (program.mcpServer !== undefined && program.mcpServer.enabled !== true) {
|
|
51
|
+
throw new CliSchemaValidationError(
|
|
52
|
+
"mcpServer requires enabled: true; omit mcpServer to disable MCP",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
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
|
+
|
|
19
66
|
const caps = resolveCapabilities(program);
|
|
20
67
|
const reserved = reservedCommandNames(caps);
|
|
21
68
|
|
|
@@ -43,6 +90,11 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
43
90
|
"install is only supported on the program root (not on " + node.key + ")",
|
|
44
91
|
);
|
|
45
92
|
}
|
|
93
|
+
if (rogue.docs !== undefined) {
|
|
94
|
+
throw new CliSchemaValidationError(
|
|
95
|
+
"docs is only supported on the program root (not on " + node.key + ")",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
46
98
|
}
|
|
47
99
|
|
|
48
100
|
if (isCliLeaf(node)) {
|
|
@@ -58,8 +110,8 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
58
110
|
}
|
|
59
111
|
}
|
|
60
112
|
|
|
61
|
-
if (isRoot && program.mcpServer?.resources) {
|
|
62
|
-
const schemaUri = program
|
|
113
|
+
if (isRoot && program.mcpServer?.enabled === true && program.mcpServer.resources) {
|
|
114
|
+
const schemaUri = resolveMcpSchemaUri(program);
|
|
63
115
|
const uris = program.mcpServer.resources.map((r) => r.uri);
|
|
64
116
|
if (uris.includes(schemaUri)) {
|
|
65
117
|
throw new CliSchemaValidationError(
|