argsbarg 3.3.13 → 3.4.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 +3 -1
- package/CHANGELOG.md +19 -1
- package/docs/bundled-docs.md +1 -1
- package/docs/cli-program.md +19 -0
- package/docs/mcp.md +23 -3
- package/index.d.ts +47 -0
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +9 -3
- package/src/builtins/completion-group.ts +18 -17
- package/src/builtins/dispatch.ts +18 -7
- package/src/builtins/export.ts +31 -17
- package/src/builtins/index.ts +1 -1
- package/src/builtins/install.ts +2 -1
- package/src/builtins/mcp.ts +38 -8
- package/src/builtins/presentation.ts +49 -11
- package/src/docs/api-guide.test.ts +30 -0
- package/src/docs/api-guide.ts +18 -0
- package/src/docs/builtin.ts +8 -15
- package/src/docs/docs.test.ts +6 -4
- package/src/help.ts +5 -4
- package/src/hidden-mcpb.test.ts +154 -0
- package/src/hidden.ts +32 -0
- package/src/index.test.ts +180 -5
- package/src/index.ts +3 -0
- package/src/invoke.ts +2 -2
- package/src/mcp/bundle.ts +251 -0
- package/src/mcp/server.ts +1 -0
- package/src/mcp/tools.ts +27 -10
- package/src/runtime.ts +3 -3
- package/src/schema.ts +26 -6
- package/src/skill/generate.ts +3 -5
- package/src/types.ts +33 -0
- package/src/validate.ts +16 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CliCapabilities } from "../capabilities.ts";
|
|
2
2
|
import { resolveCapabilities } from "../capabilities.ts";
|
|
3
|
+
import { presentationNode, visibleOptions } from "../hidden.ts";
|
|
3
4
|
import type { CliLeaf, CliNode, CliProgram, CliRouter } from "../types.ts";
|
|
4
5
|
import { isCliLeaf, isCliRouter } from "../types.ts";
|
|
5
6
|
import { cliBuiltinCompletionGroup } from "./completion-group.ts";
|
|
@@ -8,10 +9,10 @@ import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
|
8
9
|
import { cliBuiltinVersionCommand } from "./version.ts";
|
|
9
10
|
import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
|
|
10
11
|
|
|
11
|
-
/**
|
|
12
|
-
export function
|
|
12
|
+
/** All built-in command nodes for argv parsing (includes hidden builtins). */
|
|
13
|
+
export function parseBuiltins(program: CliProgram, caps: CliCapabilities): CliNode[] {
|
|
13
14
|
const builtins: CliNode[] = [
|
|
14
|
-
cliBuiltinCompletionGroup(program
|
|
15
|
+
cliBuiltinCompletionGroup(program),
|
|
15
16
|
cliBuiltinVersionCommand(),
|
|
16
17
|
];
|
|
17
18
|
if (caps.install) {
|
|
@@ -22,14 +23,49 @@ export function presentationBuiltins(program: CliProgram, caps: CliCapabilities)
|
|
|
22
23
|
builtins.push(docsGroup);
|
|
23
24
|
}
|
|
24
25
|
if (caps.mcp) {
|
|
25
|
-
builtins.push(cliBuiltinMcpCommand());
|
|
26
|
+
builtins.push(cliBuiltinMcpCommand(program));
|
|
26
27
|
}
|
|
27
28
|
return builtins;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/** Built-in subtrees visible in help, schema, and completions (hidden builtins omitted). */
|
|
32
|
+
export function presentationBuiltins(program: CliProgram, caps: CliCapabilities): CliNode[] {
|
|
33
|
+
return parseBuiltins(program, caps).filter((b) => !b.hidden);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Full command tree for argv parsing, including hidden commands and builtins.
|
|
38
|
+
* Routing programs merge user commands with builtins; leaf programs wrap builtins only.
|
|
39
|
+
*/
|
|
40
|
+
export function cliParseRoot(program: CliProgram): CliRouter {
|
|
41
|
+
const caps = resolveCapabilities(program);
|
|
42
|
+
const builtins = parseBuiltins(program, caps);
|
|
43
|
+
|
|
44
|
+
if (isCliLeaf(program)) {
|
|
45
|
+
return {
|
|
46
|
+
key: program.key,
|
|
47
|
+
description: program.description,
|
|
48
|
+
notes: program.notes,
|
|
49
|
+
options: program.options,
|
|
50
|
+
commands: builtins,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
key: program.key,
|
|
56
|
+
description: program.description,
|
|
57
|
+
notes: program.notes,
|
|
58
|
+
options: program.options,
|
|
59
|
+
fallbackCommand: program.fallbackCommand,
|
|
60
|
+
fallbackMode: program.fallbackMode,
|
|
61
|
+
commands: [...program.commands, ...builtins],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
30
65
|
/**
|
|
31
66
|
* Returns a schema suitable for help display, including capability-built-in subtrees.
|
|
32
|
-
* Routing programs get builtins merged;
|
|
67
|
+
* Hidden commands and options are omitted. Routing programs get builtins merged;
|
|
68
|
+
* leaf programs are wrapped as a tiny router.
|
|
33
69
|
*/
|
|
34
70
|
export function cliPresentationRoot(program: CliProgram): CliRouter {
|
|
35
71
|
const caps = resolveCapabilities(program);
|
|
@@ -41,19 +77,23 @@ export function cliPresentationRoot(program: CliProgram): CliRouter {
|
|
|
41
77
|
key: program.key,
|
|
42
78
|
description: program.description,
|
|
43
79
|
notes,
|
|
44
|
-
options: program.options,
|
|
80
|
+
options: visibleOptions(program.options),
|
|
45
81
|
commands: builtins,
|
|
46
82
|
};
|
|
47
83
|
}
|
|
48
84
|
|
|
85
|
+
const userCommands = program.commands
|
|
86
|
+
.map((ch) => presentationNode(ch))
|
|
87
|
+
.filter((ch): ch is CliNode => ch !== null);
|
|
88
|
+
|
|
49
89
|
return {
|
|
50
90
|
key: program.key,
|
|
51
91
|
description: program.description,
|
|
52
92
|
notes,
|
|
53
|
-
options: program.options,
|
|
93
|
+
options: visibleOptions(program.options),
|
|
54
94
|
fallbackCommand: program.fallbackCommand,
|
|
55
95
|
fallbackMode: program.fallbackMode,
|
|
56
|
-
commands: [...
|
|
96
|
+
commands: [...userCommands, ...builtins],
|
|
57
97
|
};
|
|
58
98
|
}
|
|
59
99
|
|
|
@@ -64,8 +104,7 @@ export function presentationRootNotes(program: CliProgram, caps: CliCapabilities
|
|
|
64
104
|
parts.push(program.notes!.trim());
|
|
65
105
|
}
|
|
66
106
|
if (caps.docs) {
|
|
67
|
-
|
|
68
|
-
parts.push(`Agents: run \`${cmd}\` to learn how to use this app`);
|
|
107
|
+
parts.push(`For AI agents: \`${program.key} docs skill\`.`);
|
|
69
108
|
}
|
|
70
109
|
if (parts.length === 0) {
|
|
71
110
|
return undefined;
|
|
@@ -75,4 +114,3 @@ export function presentationRootNotes(program: CliProgram, caps: CliCapabilities
|
|
|
75
114
|
|
|
76
115
|
/** Presentation tree may include builtin leaf stubs. */
|
|
77
116
|
export type CliPresentationNode = CliNode | CliLeaf;
|
|
78
|
-
|
|
@@ -105,3 +105,33 @@ test("generateApiGuide resolves {argsbarg:program} in consumer notes", () => {
|
|
|
105
105
|
const md = generateApiGuide(fixture);
|
|
106
106
|
expect(md).toContain("Invoke `myapp run`.");
|
|
107
107
|
});
|
|
108
|
+
|
|
109
|
+
test("generateApiGuide and cliSchemaExport include leaf outputSchema", () => {
|
|
110
|
+
const fixture: CliProgram = {
|
|
111
|
+
key: "myapp",
|
|
112
|
+
version: "1.0.0",
|
|
113
|
+
description: "Demo app.",
|
|
114
|
+
commands: [
|
|
115
|
+
{
|
|
116
|
+
key: "run",
|
|
117
|
+
description: "Run.",
|
|
118
|
+
outputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: { id: { type: "string" } },
|
|
121
|
+
required: ["id"],
|
|
122
|
+
},
|
|
123
|
+
handler: () => {},
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
const schema = cliSchemaExport(fixture);
|
|
128
|
+
expect(schema.commands![0]!.outputSchema).toEqual({
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: { id: { type: "string" } },
|
|
131
|
+
required: ["id"],
|
|
132
|
+
});
|
|
133
|
+
const md = generateApiGuide(fixture);
|
|
134
|
+
expect(md).toContain("#### Output");
|
|
135
|
+
expect(md).toContain('"id"');
|
|
136
|
+
expect(md).toContain('"type": "string"');
|
|
137
|
+
});
|
package/src/docs/api-guide.ts
CHANGED
|
@@ -52,6 +52,20 @@ function formatNotesBlockquote(notes: string, appKey: string): string {
|
|
|
52
52
|
.join("\n");
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/** Markdown section for leaf outputSchema (docs api / skill reference). */
|
|
56
|
+
function formatOutputSchemaSection(schema: Record<string, unknown>): string[] {
|
|
57
|
+
return [
|
|
58
|
+
"#### Output",
|
|
59
|
+
"",
|
|
60
|
+
"JSON Schema for structured stdout (typically with `--json`, or via MCP when the handler emits JSON):",
|
|
61
|
+
"",
|
|
62
|
+
"```json",
|
|
63
|
+
JSON.stringify(schema, null, 2),
|
|
64
|
+
"```",
|
|
65
|
+
"",
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
/** Fallback routing note when present on a router node. */
|
|
56
70
|
function fallbackLine(node: CliSchemaExport): string | null {
|
|
57
71
|
if (node.fallbackCommand === undefined) {
|
|
@@ -103,6 +117,10 @@ function renderCommandNode(
|
|
|
103
117
|
lines.push("");
|
|
104
118
|
}
|
|
105
119
|
|
|
120
|
+
if (node.outputSchema !== undefined) {
|
|
121
|
+
lines.push(...formatOutputSchemaSection(node.outputSchema));
|
|
122
|
+
}
|
|
123
|
+
|
|
106
124
|
const children = node.commands ?? [];
|
|
107
125
|
if (children.length > 0) {
|
|
108
126
|
lines.push("#### Subcommands", "");
|
package/src/docs/builtin.ts
CHANGED
|
@@ -36,6 +36,11 @@ function docsLeaf(program: CliProgram, key: string, description: string): CliLea
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Help notes for the `docs` router. */
|
|
40
|
+
function docsRouterNotes(): string {
|
|
41
|
+
return "Topics print to stdout. Add --save to write files under ./docs/.";
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
/** Built-in `docs` router with bundled topic subcommands. */
|
|
40
45
|
export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
|
|
41
46
|
const docs = program.docs!;
|
|
@@ -54,26 +59,14 @@ export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
|
|
|
54
59
|
|
|
55
60
|
leaves.push(
|
|
56
61
|
docsLeaf(program, "schema", "Print the full command tree as JSON."),
|
|
57
|
-
docsLeaf(program, "api", "Print the command
|
|
58
|
-
|
|
59
|
-
key: "skill",
|
|
60
|
-
description: "Print generated SKILL.md (compact command index).",
|
|
61
|
-
notes: [
|
|
62
|
-
"Prefer `{argsbarg:program} install --skill --yes` for agents: it persists an optimized skill bundle",
|
|
63
|
-
"(`SKILL.md` index + `reference.md` full API) to your skill directory.",
|
|
64
|
-
"`docs skill` prints the index only; use `docs api` or installed `reference.md` for full detail.",
|
|
65
|
-
].join(" "),
|
|
66
|
-
options: [DOCS_SAVE_OPTION],
|
|
67
|
-
mcpTool: { enabled: false },
|
|
68
|
-
handler: (ctx) => {
|
|
69
|
-
runDocsTopic(program, "skill", ctx);
|
|
70
|
-
},
|
|
71
|
-
},
|
|
62
|
+
docsLeaf(program, "api", "Print the full command reference as markdown."),
|
|
63
|
+
docsLeaf(program, "skill", "Print a reference agent SKILL, use `install --skill` for optimized."),
|
|
72
64
|
);
|
|
73
65
|
|
|
74
66
|
return {
|
|
75
67
|
key: "docs",
|
|
76
68
|
description: docs.description ?? DOCS_ROUTER_DESCRIPTION,
|
|
69
|
+
notes: docsRouterNotes(),
|
|
77
70
|
options: [DOCS_SAVE_OPTION],
|
|
78
71
|
fallbackCommand: docsEffectiveDefaultTopic(docs),
|
|
79
72
|
fallbackMode: CliFallbackMode.MissingOnly,
|
package/src/docs/docs.test.ts
CHANGED
|
@@ -153,7 +153,7 @@ test("docs skill prints Cursor SKILL.md", async () => {
|
|
|
153
153
|
expect(result.stdout).toContain("---");
|
|
154
154
|
expect(result.stdout).toContain("name: myapp");
|
|
155
155
|
expect(result.stdout).toContain("## Commands");
|
|
156
|
-
expect(result.stdout).toContain("
|
|
156
|
+
expect(result.stdout).toContain("For full detail, open `reference.md`");
|
|
157
157
|
expect(result.stdout).not.toContain("#### Options");
|
|
158
158
|
expect(result.stdout).not.toContain("mcp.json");
|
|
159
159
|
});
|
|
@@ -164,9 +164,11 @@ test("docs skill help recommends install --skill", async () => {
|
|
|
164
164
|
expect(docsNode && "commands" in docsNode).toBe(true);
|
|
165
165
|
if (docsNode && "commands" in docsNode) {
|
|
166
166
|
const skill = docsNode.commands.find((c) => c.key === "skill");
|
|
167
|
-
expect(skill?.description).toContain("
|
|
168
|
-
expect(skill?.
|
|
169
|
-
expect(skill?.notes).
|
|
167
|
+
expect(skill?.description).toContain("reference agent SKILL");
|
|
168
|
+
expect(skill?.description).toContain("install --skill");
|
|
169
|
+
expect(skill?.notes).toBeUndefined();
|
|
170
|
+
expect(docsNode.notes).toContain("--save");
|
|
171
|
+
expect(docsNode.notes).not.toContain("install --skill");
|
|
170
172
|
}
|
|
171
173
|
});
|
|
172
174
|
|
package/src/help.ts
CHANGED
|
@@ -8,6 +8,7 @@ style no matter how help is reached.
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { CliNode, CliOption, CliOptionKind, CliPositional, CliRouter, isCliLeaf, isCliRouter } from "./types.ts";
|
|
11
|
+
import { visibleOptions, visibleSubcommands } from "./hidden.ts";
|
|
11
12
|
|
|
12
13
|
// ── ANSI Style Helpers ────────────────────────────────────────────────────────
|
|
13
14
|
|
|
@@ -372,9 +373,9 @@ function rowsForPositionals(defs: CliPositional[], color: boolean): HelpRow[] {
|
|
|
372
373
|
return defs.map((p) => ({ label: cliPositionalLabel(p, color), description: p.description }));
|
|
373
374
|
}
|
|
374
375
|
|
|
375
|
-
/** Table rows for subcommands, sorted by key. */
|
|
376
|
+
/** Table rows for subcommands, sorted by key (hidden commands omitted). */
|
|
376
377
|
function rowsForSubcommands(cmds: CliNode[]): HelpRow[] {
|
|
377
|
-
return cmds
|
|
378
|
+
return visibleSubcommands(cmds)
|
|
378
379
|
.sort((a, b) => a.key.localeCompare(b.key))
|
|
379
380
|
.map((c) => ({ label: c.key, description: c.description }));
|
|
380
381
|
}
|
|
@@ -420,7 +421,7 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr:
|
|
|
420
421
|
).join("\n"),
|
|
421
422
|
);
|
|
422
423
|
|
|
423
|
-
const optBox = renderTableBox("Options", rowsForOptions(schema.options
|
|
424
|
+
const optBox = renderTableBox("Options", rowsForOptions(visibleOptions(schema.options), color), hw, color);
|
|
424
425
|
if (optBox.length > 0) {
|
|
425
426
|
lines.push("");
|
|
426
427
|
lines.push(optBox.join("\n"));
|
|
@@ -470,7 +471,7 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr:
|
|
|
470
471
|
).join("\n"),
|
|
471
472
|
);
|
|
472
473
|
|
|
473
|
-
const optBox = renderTableBox("Options", rowsForOptions(node.options
|
|
474
|
+
const optBox = renderTableBox("Options", rowsForOptions(visibleOptions(node.options), color), hw, color);
|
|
474
475
|
if (optBox.length > 0) {
|
|
475
476
|
lines.push("");
|
|
476
477
|
lines.push(optBox.join("\n"));
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { cliPresentationRoot, cliParseRoot } from "./builtins/presentation.ts";
|
|
6
|
+
import { exportPresentationBuiltins } from "./builtins/export.ts";
|
|
7
|
+
import { cliHelpRender } from "./help.ts";
|
|
8
|
+
import { cliSchemaExport } from "./schema.ts";
|
|
9
|
+
import { collectMcpTools } from "./mcp/tools.ts";
|
|
10
|
+
import { defaultMcpBundlePaths, generateMcpManifest, packMcpBundle } from "./mcp/bundle.ts";
|
|
11
|
+
import { CliOptionKind, type CliProgram } from "./types.ts";
|
|
12
|
+
|
|
13
|
+
const hiddenFixture: CliProgram = {
|
|
14
|
+
key: "myapp",
|
|
15
|
+
version: "1.0.0",
|
|
16
|
+
description: "Hidden demo.",
|
|
17
|
+
mcpServer: { enabled: true },
|
|
18
|
+
commands: [
|
|
19
|
+
{
|
|
20
|
+
key: "public",
|
|
21
|
+
description: "Visible command.",
|
|
22
|
+
handler: () => {},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: "secret",
|
|
26
|
+
hidden: true,
|
|
27
|
+
description: "Hidden command.",
|
|
28
|
+
handler: () => {},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: "flags",
|
|
32
|
+
description: "Command with hidden option.",
|
|
33
|
+
options: [
|
|
34
|
+
{
|
|
35
|
+
name: "visible",
|
|
36
|
+
description: "Shown in help.",
|
|
37
|
+
kind: CliOptionKind.Presence,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "secret-flag",
|
|
41
|
+
hidden: true,
|
|
42
|
+
description: "Hidden option.",
|
|
43
|
+
kind: CliOptionKind.Presence,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
handler: () => {},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe("hidden commands and options", () => {
|
|
52
|
+
test("parse root includes hidden commands", () => {
|
|
53
|
+
const parse = cliParseRoot(hiddenFixture);
|
|
54
|
+
expect(parse.commands?.map((c) => c.key)).toContain("secret");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("presentation root omits hidden commands", () => {
|
|
58
|
+
const presentation = cliPresentationRoot(hiddenFixture);
|
|
59
|
+
const keys = presentation.commands?.map((c) => c.key) ?? [];
|
|
60
|
+
expect(keys).toContain("public");
|
|
61
|
+
expect(keys).not.toContain("secret");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("root help omits hidden commands", () => {
|
|
65
|
+
const help = cliHelpRender(cliParseRoot(hiddenFixture), [], false);
|
|
66
|
+
expect(help).toContain("public");
|
|
67
|
+
expect(help).not.toContain("secret");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("hidden command -h still works", () => {
|
|
71
|
+
const help = cliHelpRender(cliParseRoot(hiddenFixture), ["secret"], false);
|
|
72
|
+
expect(help).toContain("Hidden command.");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("help omits hidden options", () => {
|
|
76
|
+
const help = cliHelpRender(cliParseRoot(hiddenFixture), ["flags"], false);
|
|
77
|
+
expect(help).toContain("--visible");
|
|
78
|
+
expect(help).not.toContain("secret-flag");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("schema export omits hidden nodes and options", () => {
|
|
82
|
+
const schema = cliSchemaExport(hiddenFixture);
|
|
83
|
+
const keys = schema.commands?.map((c) => c.key) ?? [];
|
|
84
|
+
expect(keys).toContain("public");
|
|
85
|
+
expect(keys).not.toContain("secret");
|
|
86
|
+
const flags = schema.commands?.find((c) => c.key === "flags");
|
|
87
|
+
expect(flags?.options?.map((o) => o.name)).toEqual(["visible"]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("MCP tools omit hidden commands", () => {
|
|
91
|
+
const tools = collectMcpTools(hiddenFixture);
|
|
92
|
+
expect(tools.map((t) => t.name)).toEqual(["public", "flags"]);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("mcp router", () => {
|
|
97
|
+
test("presentation exposes mcp bundle but not hidden serve", () => {
|
|
98
|
+
const builtins = exportPresentationBuiltins(hiddenFixture);
|
|
99
|
+
const mcp = builtins.find((b) => b.key === "mcp");
|
|
100
|
+
expect(mcp).toBeDefined();
|
|
101
|
+
expect(mcp?.commands?.map((c) => c.key)).toEqual(["bundle"]);
|
|
102
|
+
expect(mcp?.fallbackCommand).toBe("serve");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("mcp help lists bundle", () => {
|
|
106
|
+
const help = cliHelpRender(cliParseRoot(hiddenFixture), ["mcp"], false);
|
|
107
|
+
expect(help).toContain("bundle");
|
|
108
|
+
expect(help).not.toMatch(/│ serve\s/);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("mcp bundle", () => {
|
|
113
|
+
test("generateMcpManifest uses mcpServerId and binary entry", () => {
|
|
114
|
+
const manifest = generateMcpManifest(hiddenFixture, "myapp");
|
|
115
|
+
expect(manifest.name).toBe("myapp");
|
|
116
|
+
expect(manifest.manifest_version).toBe("0.3");
|
|
117
|
+
expect((manifest.server as { type: string }).type).toBe("binary");
|
|
118
|
+
expect((manifest.server as { entry_point: string }).entry_point).toBe("myapp");
|
|
119
|
+
const mcpConfig = (manifest.server as { mcp_config: { command: string; args: string[] } }).mcp_config;
|
|
120
|
+
expect(mcpConfig.command).toBe("${__dirname}/myapp");
|
|
121
|
+
expect(mcpConfig.args).toEqual(["mcp"]);
|
|
122
|
+
expect((manifest.compatibility as { platforms: string[] }).platforms).toEqual(["darwin"]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("defaultMcpBundlePaths", () => {
|
|
126
|
+
const cwd = "/tmp/work";
|
|
127
|
+
const paths = defaultMcpBundlePaths(hiddenFixture, cwd);
|
|
128
|
+
expect(paths.binaryPath).toBe(join(cwd, "dist", "myapp"));
|
|
129
|
+
expect(paths.outPath).toBe(join(cwd, "dist", "myapp.mcpb"));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("packMcpBundle writes zip with manifest and binary on darwin", () => {
|
|
133
|
+
if (process.platform !== "darwin") {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const work = mkdtempSync(join(tmpdir(), "mcpb-test-"));
|
|
137
|
+
try {
|
|
138
|
+
const dist = join(work, "dist");
|
|
139
|
+
mkdirSync(dist, { recursive: true });
|
|
140
|
+
const binaryPath = join(dist, "myapp");
|
|
141
|
+
writeFileSync(binaryPath, "#!/bin/sh\necho hi\n", { mode: 0o755 });
|
|
142
|
+
|
|
143
|
+
const outPath = packMcpBundle(hiddenFixture, { cwd: work });
|
|
144
|
+
expect(outPath).toBe(join(dist, "myapp.mcpb"));
|
|
145
|
+
|
|
146
|
+
const zip = readFileSync(outPath);
|
|
147
|
+
expect(zip.length).toBeGreaterThan(0);
|
|
148
|
+
expect(zip.indexOf(Buffer.from("manifest.json"))).toBeGreaterThanOrEqual(0);
|
|
149
|
+
expect(zip.indexOf(Buffer.from("myapp"))).toBeGreaterThanOrEqual(0);
|
|
150
|
+
} finally {
|
|
151
|
+
rmSync(work, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
package/src/hidden.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Filters hidden commands and options from presentation surfaces (help, schema, completions).
|
|
3
|
+
Parsing still uses the full tree via cliParseRoot.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CliNode, CliOption, CliRouter } from "./types.ts";
|
|
7
|
+
import { isCliLeaf, isCliRouter } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
/** Options visible in help, schema, completions, and MCP tool schemas. */
|
|
10
|
+
export function visibleOptions(options: CliOption[] | undefined): CliOption[] {
|
|
11
|
+
return (options ?? []).filter((o) => !o.hidden);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Strips hidden commands and options from one node for presentation export. */
|
|
15
|
+
export function presentationNode(node: CliNode): CliNode | null {
|
|
16
|
+
if (node.hidden) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const options = visibleOptions(node.options);
|
|
20
|
+
if (isCliRouter(node)) {
|
|
21
|
+
const commands = node.commands
|
|
22
|
+
.map((ch) => presentationNode(ch))
|
|
23
|
+
.filter((ch): ch is CliNode => ch !== null);
|
|
24
|
+
return { ...node, options, commands };
|
|
25
|
+
}
|
|
26
|
+
return { ...node, options };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Subcommands visible in help listings (parent may be any node in the parse tree). */
|
|
30
|
+
export function visibleSubcommands(cmds: CliNode[]): CliNode[] {
|
|
31
|
+
return cmds.filter((c) => !c.hidden);
|
|
32
|
+
}
|