argsbarg 1.4.3 → 1.5.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/.private/scratch.md +1 -1
- package/CHANGELOG.md +16 -1
- package/README.md +17 -13
- package/docs/ai-skills.md +24 -52
- package/docs/install.md +84 -0
- package/docs/mcp.md +5 -5
- package/index.d.ts +9 -18
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +101 -0
- package/src/builtins/completion-bash.ts +240 -0
- package/src/builtins/completion-fish.ts +73 -0
- package/src/builtins/completion-group.ts +50 -0
- package/src/builtins/completion-zsh.ts +244 -0
- package/src/builtins/dispatch.ts +123 -0
- package/src/builtins/export.ts +46 -0
- package/src/builtins/index.ts +10 -0
- package/src/builtins/install.ts +99 -0
- package/src/builtins/mcp.ts +13 -0
- package/src/builtins/presentation.ts +39 -0
- package/src/builtins/scopes.ts +45 -0
- package/src/builtins/shell-helpers.ts +24 -0
- package/src/completion.ts +10 -693
- package/src/index.test.ts +44 -55
- package/src/index.ts +1 -0
- package/src/install/binary.ts +82 -0
- package/src/install/compiled.ts +15 -0
- package/src/install/completions.ts +52 -0
- package/src/install/detect-installed.ts +67 -0
- package/src/install/index.ts +196 -0
- package/src/install/install.test.ts +124 -0
- package/src/install/mcp-config.ts +70 -0
- package/src/install/paths.ts +69 -0
- package/src/install/plan.ts +183 -0
- package/src/install/shell.ts +56 -0
- package/src/install/status.ts +63 -0
- package/src/install/uninstall.ts +111 -0
- package/src/mcp/tools.ts +1 -1
- package/src/runtime.ts +21 -83
- package/src/schema.ts +7 -49
- package/src/skill/generate.ts +4 -4
- package/src/skill/install.ts +20 -18
- package/src/types.ts +9 -9
- package/src/validate.ts +10 -22
- package/src/ai.ts +0 -7
package/src/runtime.ts
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
/*
|
|
2
2
|
This module runs parsed commands, help, errors, completion, and leaf handlers.
|
|
3
|
-
It owns the top-level control flow after parsing, including validation failures,
|
|
4
|
-
shell completion dispatch, and leaf handler invocation.
|
|
5
|
-
|
|
6
|
-
It keeps execution flow out of the public barrel so the exported API stays small and
|
|
7
|
-
the runtime responsibilities remain easy to reason about.
|
|
8
3
|
*/
|
|
9
4
|
|
|
10
|
-
import {
|
|
5
|
+
import { builtinInterceptRoot, dispatchBuiltin } from "./builtins/dispatch.ts";
|
|
6
|
+
import { cliPresentationRoot } from "./builtins/presentation.ts";
|
|
7
|
+
import { isCompiledExecutable } from "./install/compiled.ts";
|
|
11
8
|
import { CliContext } from "./context.ts";
|
|
12
9
|
import { cliHelpRender } from "./help.ts";
|
|
13
|
-
import { cliSkillInstall } from "./skill/install.ts";
|
|
14
|
-
import { cliMcpServeStdio } from "./mcp.ts";
|
|
15
10
|
import { parse, postParseValidate, ParseKind } from "./parse.ts";
|
|
16
11
|
import { cliSchemaJson } from "./schema.ts";
|
|
17
12
|
import { CliCommand } from "./types.ts";
|
|
18
13
|
import { cliValidateRoot } from "./validate.ts";
|
|
19
14
|
|
|
20
|
-
/**
|
|
21
|
-
* Merges the caller's program root with the reserved `completion` subtree.
|
|
22
|
-
*/
|
|
23
15
|
function cliRootMergedWithBuiltins(root: CliCommand): CliCommand {
|
|
24
16
|
if (root.handler) {
|
|
25
17
|
return root;
|
|
@@ -27,12 +19,6 @@ function cliRootMergedWithBuiltins(root: CliCommand): CliCommand {
|
|
|
27
19
|
return cliPresentationRoot(root);
|
|
28
20
|
}
|
|
29
21
|
|
|
30
|
-
/**
|
|
31
|
-
* Validates the schema, parses argv, prints help or errors, runs completion or the leaf handler, then exits.
|
|
32
|
-
*
|
|
33
|
-
* @param root The root CliCommand.
|
|
34
|
-
* @param argv Override the default argv (process.argv.slice(2)).
|
|
35
|
-
*/
|
|
36
22
|
export async function cliRun(root: CliCommand, argv: string[] = process.argv.slice(2)): Promise<never> {
|
|
37
23
|
try {
|
|
38
24
|
cliValidateRoot(root);
|
|
@@ -45,27 +31,27 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
|
|
|
45
31
|
process.exit(1);
|
|
46
32
|
}
|
|
47
33
|
|
|
48
|
-
if (argv.length >=
|
|
34
|
+
if (argv.length >= 1 && argv[0] === "mcp" && !root.mcpServer) {
|
|
49
35
|
process.stderr.write("MCP is not enabled. Set mcpServer on the program root.\n");
|
|
50
36
|
process.exit(1);
|
|
51
37
|
}
|
|
52
38
|
|
|
53
|
-
|
|
39
|
+
if (argv.length >= 1 && argv[0] === "install" && !isCompiledExecutable()) {
|
|
40
|
+
process.stderr.write("install is only available in compiled binaries (bun build --compile).\n");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let parseRoot: CliCommand;
|
|
54
45
|
let isLeafCompletionIntercept = false;
|
|
55
46
|
|
|
56
|
-
if (root.handler
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
parseRoot = {
|
|
65
|
-
key: root.key,
|
|
66
|
-
description: root.description,
|
|
67
|
-
commands: [cliBuiltinCompletionGroup(root.key)],
|
|
68
|
-
} as any;
|
|
47
|
+
if (root.handler) {
|
|
48
|
+
const intercept = builtinInterceptRoot(root, argv);
|
|
49
|
+
if (intercept.isLeafCompletionIntercept || intercept.parseRoot !== root) {
|
|
50
|
+
parseRoot = intercept.parseRoot;
|
|
51
|
+
isLeafCompletionIntercept = intercept.isLeafCompletionIntercept;
|
|
52
|
+
} else {
|
|
53
|
+
parseRoot = root;
|
|
54
|
+
}
|
|
69
55
|
} else {
|
|
70
56
|
parseRoot = cliRootMergedWithBuiltins(root);
|
|
71
57
|
}
|
|
@@ -91,53 +77,8 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
|
|
|
91
77
|
process.exit(1);
|
|
92
78
|
}
|
|
93
79
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (pr.path[0] === "completion") {
|
|
97
|
-
// If we intercepted a leaf, we MUST pass the original `root` to generate completions
|
|
98
|
-
// because `parseRoot` is just a dummy router!
|
|
99
|
-
const schemaForCompletion = isLeafCompletionIntercept ? root : parseRoot;
|
|
100
|
-
|
|
101
|
-
if (pr.path[1] === "bash") {
|
|
102
|
-
process.stdout.write(completionBashScript(schemaForCompletion));
|
|
103
|
-
process.exit(0);
|
|
104
|
-
}
|
|
105
|
-
if (pr.path[1] === "zsh") {
|
|
106
|
-
process.stdout.write(completionZshScript(schemaForCompletion));
|
|
107
|
-
process.exit(0);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (pr.path[0] === "ai") {
|
|
112
|
-
if (pr.path[1] === "mcp") {
|
|
113
|
-
if (!root.mcpServer) {
|
|
114
|
-
process.stderr.write("MCP is not enabled. Set mcpServer on the program root.\n");
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
if (pr.path.length !== 2) {
|
|
118
|
-
process.stderr.write("Unknown subcommand: ai " + pr.path.slice(1).join(" ") + "\n");
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
await cliMcpServeStdio(root);
|
|
122
|
-
} else if (pr.path[1] === "skill" && (pr.path[2] === "cursor" || pr.path[2] === "claude")) {
|
|
123
|
-
if (root.aiSkill?.enabled === false) {
|
|
124
|
-
process.stderr.write("AI skills are disabled. Remove aiSkill.enabled: false from the program root.\n");
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
if (pr.path.length !== 3) {
|
|
128
|
-
process.stderr.write("Unknown subcommand: ai " + pr.path.slice(1).join(" ") + "\n");
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
const msg = cliSkillInstall(root, pr.path[2], {
|
|
132
|
-
global: pr.opts.global === "1",
|
|
133
|
-
force: pr.opts.force === "1",
|
|
134
|
-
});
|
|
135
|
-
process.stderr.write(msg + "\n");
|
|
136
|
-
process.exit(0);
|
|
137
|
-
} else {
|
|
138
|
-
process.stderr.write("Unknown subcommand: ai " + pr.path.slice(1).join(" ") + "\n");
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
80
|
+
if (pr.kind === ParseKind.Ok) {
|
|
81
|
+
await dispatchBuiltin(root, pr, { isLeafCompletionIntercept, parseRoot });
|
|
141
82
|
}
|
|
142
83
|
|
|
143
84
|
let current = parseRoot;
|
|
@@ -167,13 +108,10 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
|
|
|
167
108
|
}
|
|
168
109
|
}
|
|
169
110
|
|
|
170
|
-
/**
|
|
171
|
-
* Prints a red error line and contextual help on stderr, then exits with status 1.
|
|
172
|
-
*/
|
|
173
111
|
export function cliErrWithHelp(ctx: CliContext, msg: string): never {
|
|
174
112
|
const color = process.stderr.isTTY;
|
|
175
113
|
const line = color ? `\u001B[31m${msg}\u001B[0m` : msg;
|
|
176
114
|
process.stderr.write(line + "\n");
|
|
177
115
|
process.stderr.write(cliHelpRender(cliPresentationRoot(ctx.schema), ctx.commandPath, true));
|
|
178
116
|
process.exit(1);
|
|
179
|
-
}
|
|
117
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -1,55 +1,12 @@
|
|
|
1
1
|
/*
|
|
2
2
|
This module serializes the CLI schema tree to JSON for machine-readable introspection.
|
|
3
|
-
It strips handlers and runtime-only nodes so agents can discover commands, options,
|
|
4
|
-
and positionals in one shot.
|
|
5
|
-
|
|
6
|
-
It keeps schema export aligned with the declarative CliCommand model that drives help
|
|
7
|
-
and completion.
|
|
8
3
|
*/
|
|
9
4
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
CliFallbackMode,
|
|
13
|
-
CliOption,
|
|
14
|
-
CliPositional,
|
|
15
|
-
} from "./types.ts";
|
|
16
|
-
import { cliBuiltinCompletionGroup } from "./completion.ts";
|
|
17
|
-
|
|
18
|
-
/** JSON-safe command node (no handlers). */
|
|
19
|
-
export interface CliSchemaExport {
|
|
20
|
-
/** Program or command key. */
|
|
21
|
-
key: string;
|
|
22
|
-
/** Short description shown in help. */
|
|
23
|
-
description: string;
|
|
24
|
-
/** Additional notes shown in help (supports {app} placeholder). */
|
|
25
|
-
notes?: string;
|
|
26
|
-
/** Global or command-level flags/options. */
|
|
27
|
-
options?: CliOption[];
|
|
28
|
-
/** Default top-level subcommand (program root only). */
|
|
29
|
-
fallbackCommand?: string;
|
|
30
|
-
/** How fallbackCommand is applied (program root only). */
|
|
31
|
-
fallbackMode?: CliFallbackMode;
|
|
32
|
-
/** Nested subcommands (routing nodes only). */
|
|
33
|
-
commands?: CliSchemaExport[];
|
|
34
|
-
/** Positional argument definitions (leaf nodes only). */
|
|
35
|
-
positionals?: CliPositional[];
|
|
36
|
-
}
|
|
5
|
+
import { CliCommand } from "./types.ts";
|
|
6
|
+
import { exportPresentationBuiltins, type CliSchemaExport } from "./builtins/export.ts";
|
|
37
7
|
|
|
38
|
-
|
|
39
|
-
function exportBuiltinCompletionGroup(appName: string): CliSchemaExport {
|
|
40
|
-
const group = cliBuiltinCompletionGroup(appName);
|
|
41
|
-
return {
|
|
42
|
-
key: group.key,
|
|
43
|
-
description: group.description,
|
|
44
|
-
commands: (group.commands ?? []).map((ch) => ({
|
|
45
|
-
key: ch.key,
|
|
46
|
-
description: ch.description,
|
|
47
|
-
...((ch.notes ?? "").length > 0 ? { notes: ch.notes } : {}),
|
|
48
|
-
})),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
8
|
+
const RESERVED = new Set(["completion", "install", "mcp"]);
|
|
51
9
|
|
|
52
|
-
/** Converts one `CliCommand` node into a JSON-safe export (handlers omitted). */
|
|
53
10
|
function exportCommand(cmd: CliCommand): CliSchemaExport {
|
|
54
11
|
const out: CliSchemaExport = {
|
|
55
12
|
key: cmd.key,
|
|
@@ -68,7 +25,7 @@ function exportCommand(cmd: CliCommand): CliSchemaExport {
|
|
|
68
25
|
if ((cmd.positionals ?? []).length > 0) {
|
|
69
26
|
out.positionals = cmd.positionals;
|
|
70
27
|
}
|
|
71
|
-
out.commands =
|
|
28
|
+
out.commands = exportPresentationBuiltins(cmd);
|
|
72
29
|
return out;
|
|
73
30
|
}
|
|
74
31
|
|
|
@@ -79,7 +36,7 @@ function exportCommand(cmd: CliCommand): CliSchemaExport {
|
|
|
79
36
|
out.fallbackMode = cmd.fallbackMode;
|
|
80
37
|
}
|
|
81
38
|
|
|
82
|
-
const children = (cmd.commands ?? []).filter((ch) => ch.key
|
|
39
|
+
const children = (cmd.commands ?? []).filter((ch) => !RESERVED.has(ch.key));
|
|
83
40
|
if (children.length > 0) {
|
|
84
41
|
out.commands = children.map(exportCommand);
|
|
85
42
|
}
|
|
@@ -87,7 +44,8 @@ function exportCommand(cmd: CliCommand): CliSchemaExport {
|
|
|
87
44
|
return out;
|
|
88
45
|
}
|
|
89
46
|
|
|
90
|
-
/** Returns pretty-printed JSON for the full program schema (trailing newline). */
|
|
91
47
|
export function cliSchemaJson(root: CliCommand): string {
|
|
92
48
|
return JSON.stringify(exportCommand(root), null, 2) + "\n";
|
|
93
49
|
}
|
|
50
|
+
|
|
51
|
+
export type { CliSchemaExport };
|
package/src/skill/generate.ts
CHANGED
|
@@ -53,7 +53,7 @@ function formatCommandEntry(root: CliCommand, tool: ReturnType<typeof collectMcp
|
|
|
53
53
|
|
|
54
54
|
/** Builds SKILL.md body for the given target. */
|
|
55
55
|
function buildSkillMd(root: CliCommand, target: SkillTarget, dirName: string): string {
|
|
56
|
-
const name =
|
|
56
|
+
const name = sanitizeToolSegment(root.key);
|
|
57
57
|
const description = skillDescription(root);
|
|
58
58
|
const tools = collectMcpTools(root);
|
|
59
59
|
|
|
@@ -80,7 +80,7 @@ function buildSkillMd(root: CliCommand, target: SkillTarget, dirName: string): s
|
|
|
80
80
|
"**Prefer MCP** when a host has the server connected:",
|
|
81
81
|
"",
|
|
82
82
|
"```bash",
|
|
83
|
-
`${root.key}
|
|
83
|
+
`${root.key} mcp`,
|
|
84
84
|
"```",
|
|
85
85
|
"",
|
|
86
86
|
"Example Cursor `mcp.json` entry:",
|
|
@@ -91,7 +91,7 @@ function buildSkillMd(root: CliCommand, target: SkillTarget, dirName: string): s
|
|
|
91
91
|
mcpServers: {
|
|
92
92
|
[root.mcpServer.name ?? root.key]: {
|
|
93
93
|
command: root.key,
|
|
94
|
-
args: ["
|
|
94
|
+
args: ["mcp"],
|
|
95
95
|
},
|
|
96
96
|
},
|
|
97
97
|
},
|
|
@@ -174,7 +174,7 @@ function buildReferenceMd(root: CliCommand): string {
|
|
|
174
174
|
|
|
175
175
|
/** Generates SKILL.md and reference.md for Cursor or Claude Code. */
|
|
176
176
|
export function generateSkillBundle(root: CliCommand, target: SkillTarget): SkillBundle {
|
|
177
|
-
const dirName =
|
|
177
|
+
const dirName = sanitizeToolSegment(root.key);
|
|
178
178
|
return {
|
|
179
179
|
dirName,
|
|
180
180
|
skillMd: buildSkillMd(root, target, dirName),
|
package/src/skill/install.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
This module installs generated Agent Skills to Cursor or Claude Code skill directories.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
2
|
import { homedir } from "node:os";
|
|
7
3
|
import { join } from "node:path";
|
|
8
4
|
import { CliCommand } from "../types.ts";
|
|
@@ -10,15 +6,16 @@ import { generateSkillBundle, type SkillTarget } from "./generate.ts";
|
|
|
10
6
|
|
|
11
7
|
export interface SkillInstallOpts {
|
|
12
8
|
global?: boolean;
|
|
13
|
-
|
|
9
|
+
/** When true, remove an existing skill directory before writing. */
|
|
10
|
+
rimraf?: boolean;
|
|
11
|
+
/** When true, skip writes but return paths that would change. */
|
|
12
|
+
dry?: boolean;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
/** Resolves the user home directory (`$HOME` when set). */
|
|
17
15
|
function userHome(): string {
|
|
18
16
|
return process.env.HOME ?? homedir();
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
/** Resolves the install directory for a skill target. */
|
|
22
19
|
function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean): string {
|
|
23
20
|
const base = global
|
|
24
21
|
? join(userHome(), target === "cursor" ? ".cursor" : ".claude", "skills")
|
|
@@ -26,20 +23,25 @@ function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean):
|
|
|
26
23
|
return join(base, dirName);
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
/** Writes SKILL.md and reference.md
|
|
30
|
-
export function cliSkillInstall(root: CliCommand, target: SkillTarget, opts: SkillInstallOpts): string {
|
|
26
|
+
/** Writes SKILL.md and reference.md; returns changed file paths. */
|
|
27
|
+
export function cliSkillInstall(root: CliCommand, target: SkillTarget, opts: SkillInstallOpts): string[] {
|
|
31
28
|
const bundle = generateSkillBundle(root, target);
|
|
32
29
|
const dir = resolveSkillDir(target, bundle.dirName, opts.global ?? false);
|
|
30
|
+
const changed: string[] = [];
|
|
33
31
|
|
|
34
|
-
if (existsSync(dir) && !opts.
|
|
35
|
-
|
|
36
|
-
process.exit(1);
|
|
32
|
+
if (opts.rimraf && existsSync(dir) && !opts.dry) {
|
|
33
|
+
rmSync(dir, { recursive: true, force: true });
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
const skillPath = join(dir, "SKILL.md");
|
|
37
|
+
const refPath = join(dir, "reference.md");
|
|
38
|
+
|
|
39
|
+
if (!opts.dry) {
|
|
40
|
+
mkdirSync(dir, { recursive: true });
|
|
41
|
+
writeFileSync(skillPath, bundle.skillMd, "utf8");
|
|
42
|
+
writeFileSync(refPath, bundle.referenceMd, "utf8");
|
|
43
|
+
}
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
return
|
|
45
|
+
changed.push(skillPath, refPath);
|
|
46
|
+
return changed;
|
|
45
47
|
}
|
package/src/types.ts
CHANGED
|
@@ -90,7 +90,7 @@ export interface CliPositional {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
|
-
* Root-only. Enables `myapp
|
|
93
|
+
* Root-only. Enables `myapp mcp` and MCP stdio server metadata.
|
|
94
94
|
*/
|
|
95
95
|
export interface CliMcpServerConfig {
|
|
96
96
|
/** `initialize` serverInfo.name (default: root `key`). */
|
|
@@ -154,13 +154,13 @@ export interface CliMcpToolConfig {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* Root-only. Opt
|
|
157
|
+
* Root-only. Opt-out and defaults for the `install` built-in (compiled binaries only).
|
|
158
158
|
*/
|
|
159
|
-
export interface
|
|
160
|
-
/** When `false`, disable `
|
|
159
|
+
export interface CliInstallConfig {
|
|
160
|
+
/** When `false`, hide/disable `install` (default: enabled). */
|
|
161
161
|
enabled?: boolean;
|
|
162
|
-
/**
|
|
163
|
-
|
|
162
|
+
/** Default bin directory (default: `~/.local/bin`). Overridden by `INSTALL_PREFIX` env and `--prefix`. */
|
|
163
|
+
prefix?: string;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
/**
|
|
@@ -175,10 +175,10 @@ export interface CliCommandBase {
|
|
|
175
175
|
notes?: string;
|
|
176
176
|
/** Global or command-level flags/options. */
|
|
177
177
|
options?: CliOption[];
|
|
178
|
-
/** Root-only. When set, enables the `
|
|
178
|
+
/** Root-only. When set, enables the `mcp` built-in subcommand. */
|
|
179
179
|
mcpServer?: CliMcpServerConfig;
|
|
180
|
-
/** Root-only. Opt
|
|
181
|
-
|
|
180
|
+
/** Root-only. Opt-out and defaults for `install` (compiled binaries only). */
|
|
181
|
+
install?: CliInstallConfig;
|
|
182
182
|
/** Leaf-only. Per-tool MCP exposure and metadata. */
|
|
183
183
|
mcpTool?: CliMcpToolConfig;
|
|
184
184
|
}
|
package/src/validate.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
2
|
This module validates CLI schemas before execution.
|
|
3
|
-
It checks reserved command names, handler placement, fallback rules, duplicate names,
|
|
4
|
-
and positional ordering before the runtime starts.
|
|
5
|
-
|
|
6
|
-
It fails early on structural problems so invalid trees never reach parsing or dispatch.
|
|
7
3
|
*/
|
|
8
4
|
|
|
9
5
|
import {
|
|
@@ -14,26 +10,24 @@ import {
|
|
|
14
10
|
} from "./types.ts";
|
|
15
11
|
import { MCP_SCHEMA_URI_DEFAULT } from "./mcp/tools.ts";
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
function reservedCommandNames(root: CliCommand): string[] {
|
|
14
|
+
const names = ["completion", "install"];
|
|
15
|
+
if (root.mcpServer !== undefined) {
|
|
16
|
+
names.push("mcp");
|
|
17
|
+
}
|
|
18
|
+
return names;
|
|
19
|
+
}
|
|
18
20
|
|
|
19
|
-
/**
|
|
20
|
-
* Validates the static CliCommand tree against ArgBarg rules.
|
|
21
|
-
* Throws CliSchemaValidationError if rules are violated.
|
|
22
|
-
*/
|
|
23
21
|
export function cliValidateRoot(root: CliCommand): void {
|
|
24
|
-
|
|
25
|
-
// Check for reserved command names at root
|
|
26
22
|
for (const child of root.commands ?? []) {
|
|
27
|
-
if (reservedCommandNames.includes(child.key)) {
|
|
23
|
+
if (reservedCommandNames(root).includes(child.key)) {
|
|
28
24
|
throw new CliSchemaValidationError(`Reserved command name: ${child.key}`);
|
|
29
25
|
}
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
// Recursively validate
|
|
33
28
|
walkCommand(root, true);
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
/** Recursively validates a command node: handlers vs children, options, and positionals. */
|
|
37
31
|
function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
38
32
|
if (!isRoot && cmd.mcpServer !== undefined) {
|
|
39
33
|
throw new CliSchemaValidationError(
|
|
@@ -41,9 +35,9 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
|
41
35
|
);
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
if (!isRoot && cmd.
|
|
38
|
+
if (!isRoot && cmd.install !== undefined) {
|
|
45
39
|
throw new CliSchemaValidationError(
|
|
46
|
-
"
|
|
40
|
+
"install is only supported on the program root (not on " + cmd.key + ")",
|
|
47
41
|
);
|
|
48
42
|
}
|
|
49
43
|
|
|
@@ -70,7 +64,6 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
|
70
64
|
}
|
|
71
65
|
}
|
|
72
66
|
|
|
73
|
-
// Check for duplicate child names
|
|
74
67
|
const seenNames = new Set<string>();
|
|
75
68
|
for (const child of cmd.commands ?? []) {
|
|
76
69
|
if (seenNames.has(child.key)) {
|
|
@@ -95,7 +88,6 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
|
95
88
|
}
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
// Validate options (short name uniqueness, reserved -h, required presence)
|
|
99
91
|
const seenShorts = new Set<string>();
|
|
100
92
|
for (const opt of cmd.options ?? []) {
|
|
101
93
|
if (opt.required && opt.kind === CliOptionKind.Presence) {
|
|
@@ -149,7 +141,6 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
|
149
141
|
}
|
|
150
142
|
}
|
|
151
143
|
|
|
152
|
-
// Validate positionals
|
|
153
144
|
const positionals = cmd.positionals ?? [];
|
|
154
145
|
for (const p of positionals) {
|
|
155
146
|
if (p.argMin !== undefined && p.argMin < 0) {
|
|
@@ -168,7 +159,6 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
|
168
159
|
}
|
|
169
160
|
}
|
|
170
161
|
|
|
171
|
-
// Check positional ordering: required before optional
|
|
172
162
|
let sawOptional = false;
|
|
173
163
|
for (const p of positionals) {
|
|
174
164
|
const { argMin = 1 } = p;
|
|
@@ -179,7 +169,6 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
|
179
169
|
}
|
|
180
170
|
}
|
|
181
171
|
|
|
182
|
-
// Check unlimited positional must be last
|
|
183
172
|
for (let idx = 0; idx < positionals.length; idx++) {
|
|
184
173
|
const { argMax = 1 } = positionals[idx]!;
|
|
185
174
|
if (argMax === 0 && idx + 1 < positionals.length) {
|
|
@@ -189,7 +178,6 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
|
189
178
|
}
|
|
190
179
|
}
|
|
191
180
|
|
|
192
|
-
// Recurse into nested commands
|
|
193
181
|
for (const child of cmd.commands ?? []) {
|
|
194
182
|
walkCommand(child, false);
|
|
195
183
|
}
|
package/src/ai.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
This module re-exports AI agent integration entry points (skill install).
|
|
3
|
-
MCP stdio serving remains in mcp.ts.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export { cliSkillInstall, type SkillInstallOpts } from "./skill/install.ts";
|
|
7
|
-
export { generateSkillBundle, type SkillBundle, type SkillTarget } from "./skill/generate.ts";
|