argsbarg 1.4.3 → 2.0.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/.cursor/plans/cliprogram_capabilities_refactor_081e1737.plan.md +224 -0
- package/.private/scratch.md +1 -1
- package/CHANGELOG.md +39 -1
- package/README.md +29 -21
- package/docs/ai-skills.md +24 -52
- package/docs/install.md +84 -0
- package/docs/mcp.md +8 -8
- package/examples/mcp-test.ts +3 -3
- package/examples/minimal.ts +3 -3
- package/examples/nested.ts +3 -3
- package/examples/option-required.ts +3 -3
- package/index.d.ts +44 -50
- 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 +138 -0
- package/src/builtins/export.ts +53 -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 +50 -0
- package/src/builtins/scopes.ts +46 -0
- package/src/builtins/shell-helpers.ts +24 -0
- package/src/capabilities.ts +32 -0
- package/src/completion.ts +10 -693
- package/src/context.ts +21 -6
- package/src/help.ts +21 -9
- package/src/index.test.ts +114 -118
- package/src/index.ts +2 -1
- 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/invoke.ts +14 -5
- package/src/mcp/server.ts +3 -3
- package/src/mcp/tools.ts +17 -17
- package/src/mcp.ts +2 -2
- package/src/parse.ts +55 -27
- package/src/runtime.ts +47 -100
- package/src/schema.ts +10 -52
- package/src/skill/generate.ts +10 -10
- package/src/skill/install.ts +21 -19
- package/src/types.test.ts +40 -0
- package/src/types.ts +59 -49
- package/src/validate.ts +89 -83
- package/src/ai.ts +0 -7
package/src/skill/install.ts
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
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
|
-
import {
|
|
4
|
+
import { CliProgram } from "../types.ts";
|
|
9
5
|
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:
|
|
26
|
+
/** Writes SKILL.md and reference.md; returns changed file paths. */
|
|
27
|
+
export function cliSkillInstall(root: CliProgram, 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
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Compile-only checks that invalid schema shapes fail type-checking.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CliLeaf, CliNode, CliProgram, CliRouter } from "./types.ts";
|
|
6
|
+
|
|
7
|
+
const _routerOnly: CliRouter = {
|
|
8
|
+
key: "app",
|
|
9
|
+
description: "",
|
|
10
|
+
commands: [],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const _leafOnly: CliLeaf = {
|
|
14
|
+
key: "run",
|
|
15
|
+
description: "",
|
|
16
|
+
handler: () => {},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const _program: CliProgram = {
|
|
20
|
+
key: "app",
|
|
21
|
+
description: "",
|
|
22
|
+
mcpServer: {},
|
|
23
|
+
commands: [],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const _badMcpOnNode = {
|
|
27
|
+
key: "x",
|
|
28
|
+
description: "",
|
|
29
|
+
// @ts-expect-error mcpServer is program-root only
|
|
30
|
+
mcpServer: {},
|
|
31
|
+
commands: [],
|
|
32
|
+
} satisfies CliNode;
|
|
33
|
+
|
|
34
|
+
const _badInstallOnNode = {
|
|
35
|
+
key: "x",
|
|
36
|
+
description: "",
|
|
37
|
+
// @ts-expect-error install is program-root only
|
|
38
|
+
install: { enabled: false },
|
|
39
|
+
handler: () => {},
|
|
40
|
+
} satisfies CliNode;
|
package/src/types.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
This module defines the CLI schema, option kinds, and fallback modes.
|
|
3
3
|
It is the shared declarative model that parsing, validation, help, and completion all
|
|
4
4
|
read from, so the package has one source of truth.
|
|
5
|
-
|
|
6
|
-
It gives the package one shared model for both library users and internal modules.
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
7
|
import type { CliContext } from "./context.ts";
|
|
@@ -47,7 +45,7 @@ export enum CliFallbackMode {
|
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
/**
|
|
50
|
-
* A named flag or value option (`--long`, `-short`), listed on `
|
|
48
|
+
* A named flag or value option (`--long`, `-short`), listed on command `options`.
|
|
51
49
|
*/
|
|
52
50
|
export interface CliOption {
|
|
53
51
|
/** Option name (e.g., "name", "verbose"). */
|
|
@@ -68,7 +66,7 @@ export interface CliOption {
|
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
/**
|
|
71
|
-
* An ordered positional argument slot, listed on `
|
|
69
|
+
* An ordered positional argument slot, listed on leaf `positionals`.
|
|
72
70
|
*/
|
|
73
71
|
export interface CliPositional {
|
|
74
72
|
/** Positional name (used in help and error messages). */
|
|
@@ -90,7 +88,7 @@ export interface CliPositional {
|
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
/**
|
|
93
|
-
*
|
|
91
|
+
* Enables `myapp mcp` and MCP stdio server metadata (program root only).
|
|
94
92
|
*/
|
|
95
93
|
export interface CliMcpServerConfig {
|
|
96
94
|
/** `initialize` serverInfo.name (default: root `key`). */
|
|
@@ -154,19 +152,19 @@ export interface CliMcpToolConfig {
|
|
|
154
152
|
}
|
|
155
153
|
|
|
156
154
|
/**
|
|
157
|
-
*
|
|
155
|
+
* Opt-out and defaults for the `install` built-in (compiled binaries only; program root only).
|
|
158
156
|
*/
|
|
159
|
-
export interface
|
|
160
|
-
/** When `false`, disable `
|
|
157
|
+
export interface CliInstallConfig {
|
|
158
|
+
/** When `false`, hide/disable `install` (default: enabled). */
|
|
161
159
|
enabled?: boolean;
|
|
162
|
-
/**
|
|
163
|
-
|
|
160
|
+
/** Default bin directory (default: `~/.local/bin`). Overridden by `INSTALL_PREFIX` env and `--prefix`. */
|
|
161
|
+
prefix?: string;
|
|
164
162
|
}
|
|
165
163
|
|
|
166
164
|
/**
|
|
167
|
-
* Base properties shared by all command
|
|
165
|
+
* Base properties shared by all nodes in the user command tree.
|
|
168
166
|
*/
|
|
169
|
-
export interface
|
|
167
|
+
export interface CliNodeBase {
|
|
170
168
|
/** Program or command key (e.g., "myapp", "stat", "owner"). */
|
|
171
169
|
key: string;
|
|
172
170
|
/** Short description shown in help. */
|
|
@@ -175,45 +173,57 @@ export interface CliCommandBase {
|
|
|
175
173
|
notes?: string;
|
|
176
174
|
/** Global or command-level flags/options. */
|
|
177
175
|
options?: CliOption[];
|
|
178
|
-
/** Root-only. When set, enables the `ai mcp` built-in subcommand. */
|
|
179
|
-
mcpServer?: CliMcpServerConfig;
|
|
180
|
-
/** Root-only. Opt out of `ai skill` install with `{ enabled: false }`. */
|
|
181
|
-
aiSkill?: CliAiSkillConfig;
|
|
182
|
-
/** Leaf-only. Per-tool MCP exposure and metadata. */
|
|
183
|
-
mcpTool?: CliMcpToolConfig;
|
|
184
176
|
}
|
|
185
177
|
|
|
186
178
|
/**
|
|
187
|
-
* A command node
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
179
|
+
* A leaf command node with a handler and optional positionals.
|
|
180
|
+
*/
|
|
181
|
+
export type CliLeaf = CliNodeBase & {
|
|
182
|
+
/** Handler function for leaf commands. */
|
|
183
|
+
handler: CliHandler;
|
|
184
|
+
/** Positional argument definitions. */
|
|
185
|
+
positionals?: CliPositional[];
|
|
186
|
+
/** Per-tool MCP exposure and metadata. */
|
|
187
|
+
mcpTool?: CliMcpToolConfig;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A routing command node with nested subcommands.
|
|
192
|
+
*/
|
|
193
|
+
export type CliRouter = CliNodeBase & {
|
|
194
|
+
/** Nested subcommands. */
|
|
195
|
+
commands: CliNode[];
|
|
196
|
+
/** Default subcommand when argv omits a command or uses an unknown token at this routing node. */
|
|
197
|
+
fallbackCommand?: string;
|
|
198
|
+
/** How fallbackCommand is applied at this routing node. */
|
|
199
|
+
fallbackMode?: CliFallbackMode;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* A node in the user-defined command tree (router or leaf).
|
|
204
|
+
*/
|
|
205
|
+
export type CliNode = CliLeaf | CliRouter;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Program root passed to `cliRun` / `cliInvoke`.
|
|
209
|
+
* May be a leaf or router, plus optional program-level MCP and install config.
|
|
210
|
+
*/
|
|
211
|
+
export type CliProgram = CliNode & {
|
|
212
|
+
/** When set, enables the `mcp` built-in subcommand. */
|
|
213
|
+
mcpServer?: CliMcpServerConfig;
|
|
214
|
+
/** Opt-out and defaults for `install` (compiled binaries only). */
|
|
215
|
+
install?: CliInstallConfig;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/** True when the node is a leaf (has a handler). */
|
|
219
|
+
export function isCliLeaf(node: CliNode): node is CliLeaf {
|
|
220
|
+
return "handler" in node && typeof node.handler === "function";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** True when the node is a router (has subcommands). */
|
|
224
|
+
export function isCliRouter(node: CliNode): node is CliRouter {
|
|
225
|
+
return "commands" in node && Array.isArray(node.commands);
|
|
226
|
+
}
|
|
217
227
|
|
|
218
228
|
/**
|
|
219
229
|
* Handler closure type for leaf commands.
|
|
@@ -222,7 +232,7 @@ export type CliCommand =
|
|
|
222
232
|
export type CliHandler = (ctx: CliContext) => void | Promise<void>;
|
|
223
233
|
|
|
224
234
|
/**
|
|
225
|
-
* Error thrown when the static
|
|
235
|
+
* Error thrown when the static CLI tree violates ArgsBarg rules.
|
|
226
236
|
*/
|
|
227
237
|
export class CliSchemaValidationError extends Error {
|
|
228
238
|
/** Creates a schema validation error with a human-readable rule violation. */
|
package/src/validate.ts
CHANGED
|
@@ -1,65 +1,69 @@
|
|
|
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
|
|
|
5
|
+
import { reservedCommandNames, resolveCapabilities } from "./capabilities.ts";
|
|
9
6
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
type CliLeaf,
|
|
8
|
+
type CliNode,
|
|
9
|
+
type CliProgram,
|
|
12
10
|
CliOptionKind,
|
|
13
11
|
CliSchemaValidationError,
|
|
12
|
+
isCliLeaf,
|
|
13
|
+
isCliRouter,
|
|
14
14
|
} from "./types.ts";
|
|
15
15
|
import { MCP_SCHEMA_URI_DEFAULT } from "./mcp/tools.ts";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* Throws CliSchemaValidationError if rules are violated.
|
|
22
|
-
*/
|
|
23
|
-
export function cliValidateRoot(root: CliCommand): void {
|
|
17
|
+
/** Validates a program schema. */
|
|
18
|
+
export function cliValidateProgram(program: CliProgram): void {
|
|
19
|
+
const caps = resolveCapabilities(program);
|
|
20
|
+
const reserved = reservedCommandNames(caps);
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
if (isCliRouter(program)) {
|
|
23
|
+
for (const child of program.commands) {
|
|
24
|
+
if (reserved.includes(child.key)) {
|
|
25
|
+
throw new CliSchemaValidationError(`Reserved command name: ${child.key}`);
|
|
26
|
+
}
|
|
29
27
|
}
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
walkCommand(root, true);
|
|
30
|
+
walkNode(program, program, true);
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
if (!isRoot && cmd.mcpServer !== undefined) {
|
|
39
|
-
throw new CliSchemaValidationError(
|
|
40
|
-
"mcpServer is only supported on the program root (not on " + cmd.key + ")",
|
|
41
|
-
);
|
|
42
|
-
}
|
|
33
|
+
/** @deprecated Internal alias — use cliValidateProgram */
|
|
34
|
+
export const cliValidateRoot = cliValidateProgram;
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
36
|
+
function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
37
|
+
if (!isRoot) {
|
|
38
|
+
const rogue = node as CliProgram;
|
|
39
|
+
if (rogue.mcpServer !== undefined) {
|
|
40
|
+
throw new CliSchemaValidationError(
|
|
41
|
+
"mcpServer is only supported on the program root (not on " + node.key + ")",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (rogue.install !== undefined) {
|
|
45
|
+
throw new CliSchemaValidationError(
|
|
46
|
+
"install is only supported on the program root (not on " + node.key + ")",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
if (isCliLeaf(node)) {
|
|
52
|
+
if (isRoot && node.mcpTool !== undefined) {
|
|
53
|
+
throw new CliSchemaValidationError("mcpTool is only supported on leaf commands");
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
const rogue = node as unknown as CliLeaf;
|
|
57
|
+
if (rogue.mcpTool !== undefined) {
|
|
58
|
+
throw new CliSchemaValidationError(
|
|
59
|
+
"mcpTool is only supported on leaf commands (not on " + node.key + ")",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
if (isRoot &&
|
|
61
|
-
const schemaUri =
|
|
62
|
-
const uris =
|
|
64
|
+
if (isRoot && program.mcpServer?.resources) {
|
|
65
|
+
const schemaUri = program.mcpServer.schemaResourceUri ?? MCP_SCHEMA_URI_DEFAULT;
|
|
66
|
+
const uris = program.mcpServer.resources.map((r) => r.uri);
|
|
63
67
|
if (uris.includes(schemaUri)) {
|
|
64
68
|
throw new CliSchemaValidationError(
|
|
65
69
|
`mcpServer.resources URI '${schemaUri}' conflicts with the built-in schema resource`,
|
|
@@ -70,55 +74,64 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
if (isCliRouter(node)) {
|
|
78
|
+
const seenNames = new Set<string>();
|
|
79
|
+
for (const child of node.commands) {
|
|
80
|
+
if (seenNames.has(child.key)) {
|
|
81
|
+
throw new CliSchemaValidationError(`Duplicate command name: ${child.key}`);
|
|
82
|
+
}
|
|
83
|
+
seenNames.add(child.key);
|
|
78
84
|
}
|
|
79
|
-
seenNames.add(child.key);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (cmd.fallbackMode !== undefined && cmd.fallbackCommand === undefined) {
|
|
83
|
-
throw new CliSchemaValidationError(
|
|
84
|
-
`fallbackMode requires fallbackCommand on '${cmd.key}'`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
const children = cmd.commands ?? [];
|
|
90
|
-
const valid = children.find((c) => c.key === cmd.fallbackCommand);
|
|
91
|
-
if (!valid) {
|
|
86
|
+
if (node.fallbackMode !== undefined && node.fallbackCommand === undefined) {
|
|
92
87
|
throw new CliSchemaValidationError(
|
|
93
|
-
`
|
|
88
|
+
`fallbackMode requires fallbackCommand on '${node.key}'`,
|
|
94
89
|
);
|
|
95
90
|
}
|
|
91
|
+
|
|
92
|
+
if (node.fallbackCommand !== undefined) {
|
|
93
|
+
const valid = node.commands.find((c) => c.key === node.fallbackCommand);
|
|
94
|
+
if (!valid) {
|
|
95
|
+
throw new CliSchemaValidationError(
|
|
96
|
+
`fallbackCommand '${node.fallbackCommand}' is not a child of '${node.key}'`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const child of node.commands) {
|
|
102
|
+
walkNode(child, program, false);
|
|
103
|
+
}
|
|
96
104
|
}
|
|
97
105
|
|
|
98
|
-
|
|
106
|
+
const positionals = isCliLeaf(node) ? (node.positionals ?? []) : [];
|
|
107
|
+
validateOptions(node.key, node.options ?? []);
|
|
108
|
+
validatePositionals(node.key, positionals);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function validateOptions(scopeKey: string, options: import("./types.ts").CliOption[]): void {
|
|
99
112
|
const seenShorts = new Set<string>();
|
|
100
|
-
for (const opt of
|
|
113
|
+
for (const opt of options) {
|
|
101
114
|
if (opt.required && opt.kind === CliOptionKind.Presence) {
|
|
102
115
|
throw new CliSchemaValidationError(
|
|
103
|
-
`Presence option cannot be required: ${
|
|
116
|
+
`Presence option cannot be required: ${scopeKey}/${opt.name}`,
|
|
104
117
|
);
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
if (opt.name === "schema") {
|
|
108
121
|
throw new CliSchemaValidationError(
|
|
109
|
-
`Option name "schema" is reserved for --schema: ${
|
|
122
|
+
`Option name "schema" is reserved for --schema: ${scopeKey}/${opt.name}`,
|
|
110
123
|
);
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
if (opt.shortName !== undefined) {
|
|
114
127
|
if (opt.shortName === "h") {
|
|
115
128
|
throw new CliSchemaValidationError(
|
|
116
|
-
`Short alias -h is reserved for help: ${
|
|
129
|
+
`Short alias -h is reserved for help: ${scopeKey}/${opt.name}`,
|
|
117
130
|
);
|
|
118
131
|
}
|
|
119
132
|
if (seenShorts.has(opt.shortName)) {
|
|
120
133
|
throw new CliSchemaValidationError(
|
|
121
|
-
`Duplicate short alias -${opt.shortName} in scope ${
|
|
134
|
+
`Duplicate short alias -${opt.shortName} in scope ${scopeKey}`,
|
|
122
135
|
);
|
|
123
136
|
}
|
|
124
137
|
seenShorts.add(opt.shortName);
|
|
@@ -127,70 +140,63 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
|
|
|
127
140
|
if (opt.kind === CliOptionKind.Enum) {
|
|
128
141
|
if (!opt.choices || opt.choices.length === 0) {
|
|
129
142
|
throw new CliSchemaValidationError(
|
|
130
|
-
`Option '${opt.name}' on '${
|
|
143
|
+
`Option '${opt.name}' on '${scopeKey}': Enum kind requires non-empty choices`,
|
|
131
144
|
);
|
|
132
145
|
}
|
|
133
146
|
if (new Set(opt.choices).size !== opt.choices.length) {
|
|
134
147
|
throw new CliSchemaValidationError(
|
|
135
|
-
`Option '${opt.name}' on '${
|
|
148
|
+
`Option '${opt.name}' on '${scopeKey}': Enum choices must be distinct`,
|
|
136
149
|
);
|
|
137
150
|
}
|
|
138
151
|
for (const choice of opt.choices) {
|
|
139
152
|
if (choice.length === 0) {
|
|
140
153
|
throw new CliSchemaValidationError(
|
|
141
|
-
`Option '${opt.name}' on '${
|
|
154
|
+
`Option '${opt.name}' on '${scopeKey}': Enum choices must be non-empty strings`,
|
|
142
155
|
);
|
|
143
156
|
}
|
|
144
157
|
}
|
|
145
158
|
} else if (opt.choices !== undefined) {
|
|
146
159
|
throw new CliSchemaValidationError(
|
|
147
|
-
`Option '${opt.name}' on '${
|
|
160
|
+
`Option '${opt.name}' on '${scopeKey}': choices is only valid for Enum kind`,
|
|
148
161
|
);
|
|
149
162
|
}
|
|
150
163
|
}
|
|
164
|
+
}
|
|
151
165
|
|
|
152
|
-
|
|
153
|
-
const positionals = cmd.positionals ?? [];
|
|
166
|
+
function validatePositionals(scopeKey: string, positionals: import("./types.ts").CliPositional[]): void {
|
|
154
167
|
for (const p of positionals) {
|
|
155
168
|
if (p.argMin !== undefined && p.argMin < 0) {
|
|
156
|
-
throw new CliSchemaValidationError(`argMin must be >= 0 for positional ${
|
|
169
|
+
throw new CliSchemaValidationError(`argMin must be >= 0 for positional ${scopeKey}/${p.name}`);
|
|
157
170
|
}
|
|
158
171
|
if (p.argMax !== undefined && p.argMax < 0) {
|
|
159
172
|
throw new CliSchemaValidationError(
|
|
160
|
-
`argMax must be >= 0 (use 0 for unlimited) for positional ${
|
|
173
|
+
`argMax must be >= 0 (use 0 for unlimited) for positional ${scopeKey}/${p.name}`,
|
|
161
174
|
);
|
|
162
175
|
}
|
|
163
176
|
const { argMin = 1, argMax = 1 } = p;
|
|
164
177
|
if (argMax > 0 && argMin > argMax) {
|
|
165
178
|
throw new CliSchemaValidationError(
|
|
166
|
-
`argMin must not exceed argMax for positional ${
|
|
179
|
+
`argMin must not exceed argMax for positional ${scopeKey}/${p.name}`,
|
|
167
180
|
);
|
|
168
181
|
}
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
// Check positional ordering: required before optional
|
|
172
184
|
let sawOptional = false;
|
|
173
185
|
for (const p of positionals) {
|
|
174
186
|
const { argMin = 1 } = p;
|
|
175
187
|
if (argMin === 0) {
|
|
176
188
|
sawOptional = true;
|
|
177
189
|
} else if (sawOptional) {
|
|
178
|
-
throw new CliSchemaValidationError(`Required positional after optional in scope ${
|
|
190
|
+
throw new CliSchemaValidationError(`Required positional after optional in scope ${scopeKey}`);
|
|
179
191
|
}
|
|
180
192
|
}
|
|
181
193
|
|
|
182
|
-
// Check unlimited positional must be last
|
|
183
194
|
for (let idx = 0; idx < positionals.length; idx++) {
|
|
184
195
|
const { argMax = 1 } = positionals[idx]!;
|
|
185
196
|
if (argMax === 0 && idx + 1 < positionals.length) {
|
|
186
197
|
throw new CliSchemaValidationError(
|
|
187
|
-
`Unlimited positional (argMax == 0) must be last in scope ${
|
|
198
|
+
`Unlimited positional (argMax == 0) must be last in scope ${scopeKey}`,
|
|
188
199
|
);
|
|
189
200
|
}
|
|
190
201
|
}
|
|
191
|
-
|
|
192
|
-
// Recurse into nested commands
|
|
193
|
-
for (const child of cmd.commands ?? []) {
|
|
194
|
-
walkCommand(child, false);
|
|
195
|
-
}
|
|
196
202
|
}
|
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";
|