argsbarg 4.0.3 → 4.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 +42 -1
- package/README.md +6 -6
- package/docs/ai-skills.md +9 -4
- package/docs/bundled-docs.md +1 -0
- package/docs/cli-program.md +4 -4
- package/docs/config-schema.md +1 -4
- package/docs/developing.md +1 -1
- package/docs/install.md +160 -39
- package/docs/mcp.md +38 -11
- package/docs/templates/cursor/rules/cli-program.mdc +1 -1
- package/examples/config-app/program.ts +0 -3
- package/examples/consumer-app/README.md +1 -1
- package/examples/consumer-app/src/program.ts +5 -13
- package/examples/mcp-test.ts +14 -24
- package/index.d.ts +60 -5
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +20 -4
- package/src/builtins/config.test.ts +31 -25
- package/src/builtins/config.ts +4 -3
- package/src/builtins/install.ts +50 -57
- package/src/builtins/mcp.ts +1 -1
- package/src/capabilities.ts +4 -4
- package/src/cli.ts +2 -0
- package/src/config/bootstrap.ts +167 -66
- package/src/config/context.test.ts +22 -36
- package/src/config/context.ts +5 -4
- package/src/config/file.test.ts +66 -56
- package/src/config/file.ts +33 -25
- package/src/config/resolve.test.ts +25 -1
- package/src/config/resolve.ts +43 -8
- package/src/config.integration.test.ts +17 -10
- package/src/docs/api-guide.test.ts +1 -1
- package/src/docs/docs.test.ts +1 -1
- package/src/docs/mcp-guide.ts +15 -4
- package/src/docs/mcp-resources.test.ts +63 -0
- package/src/docs/mcp-resources.ts +68 -0
- package/src/hidden-mcpb.test.ts +41 -1
- package/src/index.ts +4 -0
- package/src/install/{binary.ts → app.ts} +13 -13
- package/src/install/bootstrap.ts +22 -0
- package/src/install/detect-installed.ts +2 -97
- package/src/install/index.ts +187 -110
- package/src/install/install-validate.test.ts +61 -0
- package/src/install/install.test.ts +138 -42
- package/src/install/mcp-openclaw.test.ts +40 -0
- package/src/install/mcp-openclaw.ts +106 -0
- package/src/install/normalize.ts +35 -0
- package/src/install/paths.ts +27 -13
- package/src/install/plan.ts +30 -259
- package/src/install/shell.ts +2 -2
- package/src/install/status.test.ts +85 -0
- package/src/install/status.ts +22 -9
- package/src/install/target-base.ts +93 -0
- package/src/install/target-detect.ts +20 -0
- package/src/install/target-effective.ts +131 -0
- package/src/install/target-mcp-cli.ts +149 -0
- package/src/install/target-mcp-json.ts +130 -0
- package/src/install/target-plan-build.ts +67 -0
- package/src/install/target-registry.ts +57 -0
- package/src/install/target-scope.ts +266 -0
- package/src/install/target-skill.ts +104 -0
- package/src/install/target-types.ts +145 -0
- package/src/install/targets/app.ts +69 -0
- package/src/install/targets/chatgpt-mcp.ts +12 -0
- package/src/install/targets/claude-code-mcp.ts +15 -0
- package/src/install/targets/claude-desktop-mcp.ts +12 -0
- package/src/install/targets/claude-skill.ts +16 -0
- package/src/install/targets/codex-mcp.ts +25 -0
- package/src/install/targets/codex-skill.ts +14 -0
- package/src/install/targets/completions.ts +133 -0
- package/src/install/targets/configure.ts +59 -0
- package/src/install/targets/cursor-mcp.ts +15 -0
- package/src/install/targets/cursor-skill.ts +16 -0
- package/src/install/targets/index.ts +53 -0
- package/src/install/targets/openclaw-mcp.ts +25 -0
- package/src/install/targets/openclaw-skill.ts +17 -0
- package/src/install/targets/opencode-mcp.ts +101 -0
- package/src/install/targets/opencode-skill.ts +15 -0
- package/src/install/targets.test.ts +136 -0
- package/src/install/uninstall.ts +16 -152
- package/src/install/update.test.ts +17 -2
- package/src/install/update.ts +2 -5
- package/src/invoke.test.ts +7 -1
- package/src/mcp/bundle.ts +16 -4
- package/src/mcp/claude.test.ts +23 -4
- package/src/mcp/claude.ts +18 -13
- package/src/mcp/tools.ts +4 -1
- package/src/mcp/zip.test.ts +17 -0
- package/src/mcp/zip.ts +62 -9
- package/src/mcp.integration.test.ts +18 -1
- package/src/parse.test.ts +57 -4
- package/src/paths/host.ts +11 -11
- package/src/paths/remove-empty-dir.ts +13 -0
- package/src/skill/generate.ts +89 -5
- package/src/skill/hint.ts +5 -0
- package/src/skill/install.ts +33 -6
- package/src/skill/naming.ts +28 -0
- package/src/types.ts +65 -4
- package/src/validate.ts +79 -4
package/src/parse.test.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
} from "./mcp/tools.ts";
|
|
21
21
|
import { ParseKind, parse, postParseValidate } from "./parse.ts";
|
|
22
22
|
import { cliSchemaJson } from "./schema.ts";
|
|
23
|
-
import { generateSkillBundle } from "./skill/generate.ts";
|
|
23
|
+
import { generatePluginSkillBundle, generateSkillBundle } from "./skill/generate.ts";
|
|
24
24
|
import { cliSkillInstall } from "./skill/install.ts";
|
|
25
25
|
import {
|
|
26
26
|
enumMcpFixture,
|
|
@@ -614,7 +614,7 @@ test("cliSchemaExport resolves program key in install notes", () => {
|
|
|
614
614
|
|
|
615
615
|
const json = cliSchemaJson(root);
|
|
616
616
|
expect(json).not.toContain("{argsbarg:program}");
|
|
617
|
-
expect(json).toContain("myapp install --
|
|
617
|
+
expect(json).toContain("myapp install --yes");
|
|
618
618
|
});
|
|
619
619
|
|
|
620
620
|
test("cliSchemaExport resolves {argsbarg:program} in consumer notes", () => {
|
|
@@ -835,7 +835,7 @@ test("cliValidateProgram rejects resource URI matching default schema URI", () =
|
|
|
835
835
|
},
|
|
836
836
|
commands: [{ key: "x", description: "", handler: () => {} }],
|
|
837
837
|
});
|
|
838
|
-
expect(() => cliValidateProgram(root)).toThrow(/conflicts with
|
|
838
|
+
expect(() => cliValidateProgram(root)).toThrow(/conflicts with built-in schema resource/);
|
|
839
839
|
});
|
|
840
840
|
|
|
841
841
|
test("cliValidateProgram rejects resource URI matching schemaResourceUri", () => {
|
|
@@ -849,7 +849,21 @@ test("cliValidateProgram rejects resource URI matching schemaResourceUri", () =>
|
|
|
849
849
|
},
|
|
850
850
|
commands: [{ key: "x", description: "", handler: () => {} }],
|
|
851
851
|
});
|
|
852
|
-
expect(() => cliValidateProgram(root)).toThrow(/conflicts with
|
|
852
|
+
expect(() => cliValidateProgram(root)).toThrow(/conflicts with built-in schema resource/);
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
test("cliValidateProgram rejects resource URI matching auto docs topic", () => {
|
|
856
|
+
const root = testProgram({
|
|
857
|
+
key: "app",
|
|
858
|
+
description: "",
|
|
859
|
+
docs: { enabled: true, topics: { readme: { text: "# r\n" } } },
|
|
860
|
+
mcpServer: {
|
|
861
|
+
enabled: true,
|
|
862
|
+
resources: [{ uri: "app://docs/readme", name: "dup", load: () => "" }],
|
|
863
|
+
},
|
|
864
|
+
commands: [{ key: "x", description: "", handler: () => {} }],
|
|
865
|
+
});
|
|
866
|
+
expect(() => cliValidateProgram(root)).toThrow(/conflicts with auto docs topic resource/);
|
|
853
867
|
});
|
|
854
868
|
|
|
855
869
|
test("allMcpResources includes custom resources", () => {
|
|
@@ -867,6 +881,20 @@ test("allMcpResources includes custom resources", () => {
|
|
|
867
881
|
expect(resources.map((r) => r.uri)).toContain("test://x");
|
|
868
882
|
});
|
|
869
883
|
|
|
884
|
+
test("allMcpResources includes docs topic resources", () => {
|
|
885
|
+
const root = testProgram({
|
|
886
|
+
key: "app",
|
|
887
|
+
description: "",
|
|
888
|
+
docs: { enabled: true, topics: { readme: { text: "# hi\n" } } },
|
|
889
|
+
mcpServer: { enabled: true },
|
|
890
|
+
commands: [{ key: "leaf", description: "", handler: () => {} }],
|
|
891
|
+
});
|
|
892
|
+
const resources = allMcpResources(root);
|
|
893
|
+
expect(resources.map((r) => r.uri)).toContain("app://docs/readme");
|
|
894
|
+
const readme = resources.find((r) => r.uri === "app://docs/readme");
|
|
895
|
+
expect(readme?.load()).toBe("# hi\n");
|
|
896
|
+
});
|
|
897
|
+
|
|
870
898
|
test("applyShellEnv merges PATH and preserves host vars", () => {
|
|
871
899
|
const origPath = process.env.PATH ?? "";
|
|
872
900
|
const origHome = process.env.HOME;
|
|
@@ -1096,6 +1124,17 @@ test("install config on non-root node is rejected", () => {
|
|
|
1096
1124
|
expect(() => cliValidateProgram(root)).toThrow(/install is only supported on the program root/);
|
|
1097
1125
|
});
|
|
1098
1126
|
|
|
1127
|
+
test("install.prefix is rejected", () => {
|
|
1128
|
+
const root = {
|
|
1129
|
+
key: "app",
|
|
1130
|
+
version: "0.0.0",
|
|
1131
|
+
description: "",
|
|
1132
|
+
install: { prefix: "/opt/bin" },
|
|
1133
|
+
handler: () => {},
|
|
1134
|
+
} as unknown as CliProgram;
|
|
1135
|
+
expect(() => cliValidateProgram(root)).toThrow(/install\.prefix removed/);
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1099
1138
|
test("generateSkillBundle includes frontmatter and compact command index", () => {
|
|
1100
1139
|
const bundle = generateSkillBundle(nestedMcpFixture, "cursor");
|
|
1101
1140
|
expect(bundle.dirName).toBe("nested_ts");
|
|
@@ -1116,6 +1155,20 @@ test("generateSkillBundle includes frontmatter and compact command index", () =>
|
|
|
1116
1155
|
expect(bundle.referenceMd).not.toContain("```json");
|
|
1117
1156
|
});
|
|
1118
1157
|
|
|
1158
|
+
test("generatePluginSkillBundle is MCP routing stub without shell catalog", () => {
|
|
1159
|
+
const bundle = generatePluginSkillBundle(nestedMcpFixture);
|
|
1160
|
+
expect(bundle.dirName).toBe("nested_ts");
|
|
1161
|
+
expect(bundle.skillMd).toMatch(/^---\nname: nested_ts\n/);
|
|
1162
|
+
expect(bundle.skillMd).toContain("MCP toolset");
|
|
1163
|
+
expect(bundle.skillMd).toContain("Server id: `nested_ts`");
|
|
1164
|
+
expect(bundle.skillMd).toContain("nested_ts://schema");
|
|
1165
|
+
expect(bundle.skillMd).toContain("tools/list");
|
|
1166
|
+
expect(bundle.skillMd).not.toContain("Invoke via shell");
|
|
1167
|
+
expect(bundle.skillMd).not.toContain("reference.md");
|
|
1168
|
+
expect(bundle.skillMd).not.toContain("`nested.ts stat owner lookup <path>`");
|
|
1169
|
+
expect(bundle.skillMd).not.toContain("## Commands");
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1119
1172
|
test("cliSkillInstall writes project Cursor skill files", () => {
|
|
1120
1173
|
const cwd = mkdtempSync(join(tmpdir(), "argsbarg-skill-"));
|
|
1121
1174
|
const prev = process.cwd();
|
package/src/paths/host.ts
CHANGED
|
@@ -21,20 +21,20 @@ export function expandTilde(path: string): string {
|
|
|
21
21
|
return path;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** Display path using `~/` when under the user home directory. */
|
|
25
|
+
export function displayHomePath(absolutePath: string, home = userHome()): string {
|
|
26
|
+
if (home.length > 0 && absolutePath.startsWith(home)) {
|
|
27
|
+
return `~${absolutePath.slice(home.length)}`;
|
|
28
|
+
}
|
|
29
|
+
return absolutePath;
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
/** XDG config base directory (`$XDG_CONFIG_HOME` or `~/.config`). */
|
|
25
33
|
export function xdgConfigHome(home = userHome()): string {
|
|
26
34
|
return process.env.XDG_CONFIG_HOME ?? join(home, ".config");
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
/**
|
|
30
|
-
export function
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** OS-appropriate base directory for app config files. */
|
|
35
|
-
export function appConfigHome(home = userHome()): string {
|
|
36
|
-
if (process.platform === "win32") {
|
|
37
|
-
return appDataHome(home);
|
|
38
|
-
}
|
|
39
|
-
return xdgConfigHome(home);
|
|
37
|
+
/** App config library root (`~/.local/lib`). */
|
|
38
|
+
export function appConfigLibHome(home = userHome()): string {
|
|
39
|
+
return join(home, ".local", "lib");
|
|
40
40
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { existsSync, readdirSync, rmdirSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
/** Removes a directory when it exists and is empty. Returns true when removed. */
|
|
4
|
+
export function removeEmptyDir(path: string): boolean {
|
|
5
|
+
if (!existsSync(path)) return false;
|
|
6
|
+
try {
|
|
7
|
+
if (readdirSync(path).length > 0) return false;
|
|
8
|
+
rmdirSync(path);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/skill/generate.ts
CHANGED
|
@@ -4,11 +4,19 @@ This module generates Agent Skills content (SKILL.md + reference.md) from a CLI
|
|
|
4
4
|
|
|
5
5
|
import { defaultConfigEntryTitle } from "../config/entry.ts";
|
|
6
6
|
import { generateApiGuide } from "../docs/api-guide.ts";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
collectMcpTools,
|
|
9
|
+
type McpToolDef,
|
|
10
|
+
mcpServerId,
|
|
11
|
+
resolveMcpSchemaUri,
|
|
12
|
+
sanitizeToolSegment,
|
|
13
|
+
} from "../mcp/tools.ts";
|
|
8
14
|
import { collectOptionDefs } from "../parse.ts";
|
|
9
15
|
import { CliOptionKind, type CliProgram } from "../types.ts";
|
|
16
|
+
import type { SkillTarget } from "./naming.ts";
|
|
17
|
+
import { skillDirNameForTarget, skillFrontmatterName } from "./naming.ts";
|
|
10
18
|
|
|
11
|
-
export type SkillTarget
|
|
19
|
+
export type { SkillTarget } from "./naming.ts";
|
|
12
20
|
|
|
13
21
|
export interface SkillBundle {
|
|
14
22
|
dirName: string;
|
|
@@ -16,12 +24,28 @@ export interface SkillBundle {
|
|
|
16
24
|
referenceMd: string;
|
|
17
25
|
}
|
|
18
26
|
|
|
27
|
+
/** MCP routing skill for Claude Code plugin zips (SKILL.md only). */
|
|
28
|
+
export interface PluginSkillBundle {
|
|
29
|
+
dirName: string;
|
|
30
|
+
skillMd: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
/** Truncates text to maxLen with ellipsis. */
|
|
20
34
|
function truncate(text: string, maxLen: number): string {
|
|
21
35
|
if (text.length <= maxLen) return text;
|
|
22
36
|
return `${text.slice(0, maxLen - 1)}…`;
|
|
23
37
|
}
|
|
24
38
|
|
|
39
|
+
/** Builds MCP-oriented skill description for Claude plugin YAML frontmatter. */
|
|
40
|
+
function pluginSkillDescription(root: CliProgram): string {
|
|
41
|
+
const tools = collectMcpTools(root);
|
|
42
|
+
const paths = tools.map((t) => (t.path.length > 0 ? t.path.join(" ") : root.key));
|
|
43
|
+
const sample = paths.slice(0, 5).join(", ");
|
|
44
|
+
const more = paths.length > 5 ? `, and ${paths.length - 5} more` : "";
|
|
45
|
+
const desc = `Use the ${root.key} MCP toolset (${sample}${more}). Use when the user mentions ${root.key}${paths.length > 0 ? `, ${paths.slice(0, 3).join(", ")}` : ""}, or related tasks.`;
|
|
46
|
+
return truncate(desc, 1024);
|
|
47
|
+
}
|
|
48
|
+
|
|
25
49
|
/** Builds third-person skill description for YAML frontmatter. */
|
|
26
50
|
function skillDescription(root: CliProgram): string {
|
|
27
51
|
const tools = collectMcpTools(root);
|
|
@@ -81,7 +105,7 @@ function buildConfigurationSection(root: CliProgram): string[] {
|
|
|
81
105
|
|
|
82
106
|
/** Builds SKILL.md body for the given target. */
|
|
83
107
|
function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): string {
|
|
84
|
-
const name =
|
|
108
|
+
const name = skillFrontmatterName(root.key, target);
|
|
85
109
|
const description = skillDescription(root);
|
|
86
110
|
const tools = collectMcpTools(root);
|
|
87
111
|
|
|
@@ -139,7 +163,7 @@ function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): s
|
|
|
139
163
|
"Do not install under `~/.cursor/skills-cursor/` (reserved for Cursor built-ins).",
|
|
140
164
|
"",
|
|
141
165
|
);
|
|
142
|
-
} else {
|
|
166
|
+
} else if (target === "claude") {
|
|
143
167
|
lines.push(
|
|
144
168
|
"## Claude Code",
|
|
145
169
|
"",
|
|
@@ -149,6 +173,18 @@ function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): s
|
|
|
149
173
|
`- Bundled files in this directory are available via \`\${CLAUDE_SKILL_DIR}\` when the skill runs.`,
|
|
150
174
|
"",
|
|
151
175
|
);
|
|
176
|
+
} else if (target === "codex") {
|
|
177
|
+
lines.push(
|
|
178
|
+
"## Codex",
|
|
179
|
+
"",
|
|
180
|
+
`- Global: \`~/.codex/skills/${dirName}/\``,
|
|
181
|
+
"- Enable skills in `~/.codex/config.toml` if required.",
|
|
182
|
+
"",
|
|
183
|
+
);
|
|
184
|
+
} else if (target === "opencode") {
|
|
185
|
+
lines.push("## OpenCode", "", `- Global: \`~/.config/opencode/skills/${dirName}/\``, "");
|
|
186
|
+
} else {
|
|
187
|
+
lines.push("## OpenClaw", "", `- Global: \`~/.openclaw/skills/${dirName}/\``, "");
|
|
152
188
|
}
|
|
153
189
|
|
|
154
190
|
return lines.join("\n");
|
|
@@ -159,9 +195,57 @@ function buildReferenceMd(root: CliProgram): string {
|
|
|
159
195
|
return generateApiGuide(root);
|
|
160
196
|
}
|
|
161
197
|
|
|
198
|
+
/** Builds MCP routing SKILL.md for Claude Code plugin zips. */
|
|
199
|
+
function buildPluginSkillMd(root: CliProgram, dirName: string): string {
|
|
200
|
+
const name = sanitizeToolSegment(root.key);
|
|
201
|
+
const description = pluginSkillDescription(root);
|
|
202
|
+
const serverId = mcpServerId(root);
|
|
203
|
+
const schemaUri = resolveMcpSchemaUri(root);
|
|
204
|
+
|
|
205
|
+
const lines: string[] = [
|
|
206
|
+
"---",
|
|
207
|
+
`name: ${name}`,
|
|
208
|
+
`description: ${description}`,
|
|
209
|
+
"---",
|
|
210
|
+
"",
|
|
211
|
+
`# ${root.key}`,
|
|
212
|
+
"",
|
|
213
|
+
root.description,
|
|
214
|
+
"",
|
|
215
|
+
"## Execution",
|
|
216
|
+
"",
|
|
217
|
+
"This plugin bundles an MCP server. Use MCP tools to fulfill requests.",
|
|
218
|
+
"",
|
|
219
|
+
`- Server id: \`${serverId}\` (configured in plugin \`.mcp.json\`)`,
|
|
220
|
+
"- Tool names and argument shapes come from MCP `tools/list`",
|
|
221
|
+
`- Full schema: \`${schemaUri}\` (same as \`${root.key} docs schema\`)`,
|
|
222
|
+
"",
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
lines.push(...buildConfigurationSection(root));
|
|
226
|
+
|
|
227
|
+
lines.push(
|
|
228
|
+
"## Claude Code plugin",
|
|
229
|
+
"",
|
|
230
|
+
`Invoke with \`/${dirName}\` or let Claude auto-match from the description.`,
|
|
231
|
+
"",
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
return lines.join("\n");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Generates MCP routing SKILL.md for Claude Code plugin zips. */
|
|
238
|
+
export function generatePluginSkillBundle(root: CliProgram): PluginSkillBundle {
|
|
239
|
+
const dirName = sanitizeToolSegment(root.key);
|
|
240
|
+
return {
|
|
241
|
+
dirName,
|
|
242
|
+
skillMd: buildPluginSkillMd(root, dirName),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
162
246
|
/** Generates SKILL.md and reference.md for Cursor or Claude Code. */
|
|
163
247
|
export function generateSkillBundle(root: CliProgram, target: SkillTarget): SkillBundle {
|
|
164
|
-
const dirName =
|
|
248
|
+
const dirName = skillDirNameForTarget(root.key, target);
|
|
165
249
|
return {
|
|
166
250
|
dirName,
|
|
167
251
|
skillMd: buildSkillMd(root, target, dirName),
|
package/src/skill/hint.ts
CHANGED
|
@@ -58,3 +58,8 @@ export function applySkillBundleHints(
|
|
|
58
58
|
referenceMd: insertGeneratedHint(referenceMd, hint),
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
/** Applies bundle hint to plugin SKILL.md (after frontmatter). */
|
|
63
|
+
export function applyPluginSkillHint(program: CliProgram, skillMd: string): string {
|
|
64
|
+
return insertGeneratedHint(skillMd, skillBundleHint(program), { afterFrontmatter: true });
|
|
65
|
+
}
|
package/src/skill/install.ts
CHANGED
|
@@ -5,19 +5,28 @@ import type { CliProgram } from "../types.ts";
|
|
|
5
5
|
import { generateSkillBundle, type SkillTarget } from "./generate.ts";
|
|
6
6
|
import { applySkillInstallHints } from "./hint.ts";
|
|
7
7
|
|
|
8
|
+
export { skillDirNameForTarget, skillFrontmatterName, skillSlug } from "./naming.ts";
|
|
9
|
+
|
|
8
10
|
export interface SkillInstallOpts {
|
|
9
11
|
global?: boolean;
|
|
10
|
-
/** When true, remove an existing skill directory before writing. */
|
|
11
12
|
rimraf?: boolean;
|
|
12
|
-
/** When true, skip writes but return paths that would change. */
|
|
13
13
|
dry?: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean): string {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const home = userHome();
|
|
18
|
+
switch (target) {
|
|
19
|
+
case "cursor":
|
|
20
|
+
return join(global ? home : process.cwd(), ".cursor", "skills", dirName);
|
|
21
|
+
case "claude":
|
|
22
|
+
return join(global ? home : process.cwd(), ".claude", "skills", dirName);
|
|
23
|
+
case "codex":
|
|
24
|
+
return join(global ? home : process.cwd(), ".codex", "skills", dirName);
|
|
25
|
+
case "opencode":
|
|
26
|
+
return join(global ? home : process.cwd(), ".config", "opencode", "skills", dirName);
|
|
27
|
+
case "openclaw":
|
|
28
|
+
return join(global ? home : process.cwd(), ".openclaw", "skills", dirName);
|
|
29
|
+
}
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
/** Writes SKILL.md and reference.md; returns changed file paths. */
|
|
@@ -47,3 +56,21 @@ export function cliSkillInstall(
|
|
|
47
56
|
changed.push(skillPath, refPath);
|
|
48
57
|
return changed;
|
|
49
58
|
}
|
|
59
|
+
|
|
60
|
+
/** Maps install action kind to skill target. */
|
|
61
|
+
export function skillTargetFromActionKind(kind: string): SkillTarget | undefined {
|
|
62
|
+
switch (kind) {
|
|
63
|
+
case "cursor-skill":
|
|
64
|
+
return "cursor";
|
|
65
|
+
case "claude-skill":
|
|
66
|
+
return "claude";
|
|
67
|
+
case "codex-skill":
|
|
68
|
+
return "codex";
|
|
69
|
+
case "opencode-skill":
|
|
70
|
+
return "opencode";
|
|
71
|
+
case "openclaw-skill":
|
|
72
|
+
return "openclaw";
|
|
73
|
+
default:
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Agent skill install targets. */
|
|
2
|
+
export type SkillTarget = "cursor" | "claude" | "codex" | "opencode" | "openclaw";
|
|
3
|
+
|
|
4
|
+
import { sanitizeToolSegment } from "../mcp/tools.ts";
|
|
5
|
+
|
|
6
|
+
/** Kebab-case skill folder + frontmatter name for agents that require hyphen slugs. */
|
|
7
|
+
export function skillSlug(programKey: string): string {
|
|
8
|
+
return programKey
|
|
9
|
+
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
10
|
+
.replace(/^-+|-+$/g, "")
|
|
11
|
+
.toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Directory name for a skill target (underscore vs kebab conventions). */
|
|
15
|
+
export function skillDirNameForTarget(programKey: string, target: SkillTarget): string {
|
|
16
|
+
if (target === "cursor" || target === "claude") {
|
|
17
|
+
return sanitizeToolSegment(programKey);
|
|
18
|
+
}
|
|
19
|
+
return skillSlug(programKey);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Frontmatter `name` for SKILL.md by target. */
|
|
23
|
+
export function skillFrontmatterName(programKey: string, target: SkillTarget): string {
|
|
24
|
+
if (target === "cursor" || target === "claude") {
|
|
25
|
+
return sanitizeToolSegment(programKey);
|
|
26
|
+
}
|
|
27
|
+
return skillSlug(programKey);
|
|
28
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -133,6 +133,10 @@ export interface CliMcpBundleConfig {
|
|
|
133
133
|
export interface CliMcpServerConfig {
|
|
134
134
|
/** When `true`, enables the `mcp` built-in and MCP stdio server. */
|
|
135
135
|
enabled: boolean;
|
|
136
|
+
/** When `true`, `mcp bundle` writes `dist/<key>.mcpb` for Claude Desktop. Default false. */
|
|
137
|
+
mcpd?: boolean;
|
|
138
|
+
/** When `true`, `mcp bundle` also writes `dist/claude-plugin/<name>.zip`. Default false. */
|
|
139
|
+
claudePlugin?: boolean;
|
|
136
140
|
/** Resource URI for schema export (default: `<sanitized root key>://schema`). */
|
|
137
141
|
schemaResourceUri?: string;
|
|
138
142
|
/**
|
|
@@ -224,8 +228,6 @@ export interface CliAppConfigEntry {
|
|
|
224
228
|
* App configuration block on the program root ({@link CliProgram.appConfig}).
|
|
225
229
|
*/
|
|
226
230
|
export interface CliAppConfig {
|
|
227
|
-
/** Default: `~/.config/<sanitized-key>/config` (or `%APPDATA%/<key>/config` on Windows). */
|
|
228
|
-
path?: string;
|
|
229
231
|
/** Built-in `config get` / `config set`. Default: enabled when `appConfig` is set. */
|
|
230
232
|
commands?: boolean | { enabled?: boolean; mcpSet?: boolean };
|
|
231
233
|
/** Block JSON Schema (draft-07). When omitted, synthesize all-string schema from `entries`. */
|
|
@@ -237,8 +239,15 @@ export interface CliAppConfig {
|
|
|
237
239
|
export interface CliInstallConfig {
|
|
238
240
|
/** When `false`, hide/disable `install` (default: enabled). */
|
|
239
241
|
enabled?: boolean;
|
|
240
|
-
/**
|
|
241
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Default agent integration for full install (`install --all`).
|
|
244
|
+
* - `'mcp'` when `mcpServer.enabled` (default): MCP targets in `--all`; paired skills excluded.
|
|
245
|
+
* - `'skill'` when MCP is off (default): skill targets in `--all`; paired MCP excluded.
|
|
246
|
+
* - `'both'`: install MCP and skill for the same host when both are available.
|
|
247
|
+
*/
|
|
248
|
+
agentIntegration?: InstallAgentIntegration;
|
|
249
|
+
/** Per-artifact gates for full install/uninstall. See {@link resolveEffectiveInstallTargets}. */
|
|
250
|
+
targets?: CliInstallTargets;
|
|
242
251
|
/**
|
|
243
252
|
* When set, enables `install --update` on the program root.
|
|
244
253
|
* Should download or locate the latest release binary and return its path.
|
|
@@ -246,6 +255,58 @@ export interface CliInstallConfig {
|
|
|
246
255
|
updateGetLatest?: CliUpdateGetLatest;
|
|
247
256
|
}
|
|
248
257
|
|
|
258
|
+
/** Agent integration mode for install — MCP vs shell skill per host. */
|
|
259
|
+
export type InstallAgentIntegration = "mcp" | "skill" | "both";
|
|
260
|
+
|
|
261
|
+
/** Boolean or structured gate for one install artifact. */
|
|
262
|
+
export type InstallTargetSpec =
|
|
263
|
+
| boolean
|
|
264
|
+
| {
|
|
265
|
+
/** When false, artifact is never installed (even with scoped CLI flags). Default true. */
|
|
266
|
+
enabled?: boolean;
|
|
267
|
+
/** When true, included in bare `install` / `install --all`. Default varies by key. */
|
|
268
|
+
includedInAll?: boolean;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export interface ResolvedInstallTarget {
|
|
272
|
+
enabled: boolean;
|
|
273
|
+
includedInAll: boolean;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Per-artifact gates for full install/uninstall. See {@link resolveEffectiveInstallTargets}. */
|
|
277
|
+
export interface CliInstallTargets {
|
|
278
|
+
/** Copy app to `~/.local/bin/<key>`. Default includedInAll true (opt-out). */
|
|
279
|
+
app?: InstallTargetSpec;
|
|
280
|
+
/** ChatGPT desktop MCP. Default false. */
|
|
281
|
+
chatgptMcp?: InstallTargetSpec;
|
|
282
|
+
/** Claude Code MCP (`~/.claude.json`). Default false. */
|
|
283
|
+
claudeCodeMcp?: InstallTargetSpec;
|
|
284
|
+
/** Claude Desktop MCP. Default false. */
|
|
285
|
+
claudeDesktopMcp?: InstallTargetSpec;
|
|
286
|
+
/** Claude Code skill. Default false. */
|
|
287
|
+
claudeSkill?: InstallTargetSpec;
|
|
288
|
+
/** Codex MCP (`codex mcp add`). Default false. */
|
|
289
|
+
codexMcp?: InstallTargetSpec;
|
|
290
|
+
/** Codex skill. Default false. */
|
|
291
|
+
codexSkill?: InstallTargetSpec;
|
|
292
|
+
/** Shell completions for detected shells. Default includedInAll true (opt-out). */
|
|
293
|
+
completions?: InstallTargetSpec;
|
|
294
|
+
/** App config: wizard on install, file removal on uninstall. Default includedInAll true. */
|
|
295
|
+
configure?: InstallTargetSpec;
|
|
296
|
+
/** Cursor MCP. Default false. */
|
|
297
|
+
cursorMcp?: InstallTargetSpec;
|
|
298
|
+
/** Cursor skill. Default false. */
|
|
299
|
+
cursorSkill?: InstallTargetSpec;
|
|
300
|
+
/** OpenClaw MCP. Default false. */
|
|
301
|
+
openclawMcp?: InstallTargetSpec;
|
|
302
|
+
/** OpenClaw skill. Default false. */
|
|
303
|
+
openclawSkill?: InstallTargetSpec;
|
|
304
|
+
/** OpenCode MCP. Default false. */
|
|
305
|
+
opencodeMcp?: InstallTargetSpec;
|
|
306
|
+
/** OpenCode skill. Default false. */
|
|
307
|
+
opencodeSkill?: InstallTargetSpec;
|
|
308
|
+
}
|
|
309
|
+
|
|
249
310
|
/**
|
|
250
311
|
* One bundled documentation topic for the `docs` built-in (program root only).
|
|
251
312
|
*/
|
package/src/validate.ts
CHANGED
|
@@ -3,8 +3,10 @@ This module validates CLI schemas before execution.
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { reservedCommandNames, resolveCapabilities } from "./capabilities.ts";
|
|
6
|
+
import { reservedDocsTopicResourceUris } from "./docs/mcp-resources.ts";
|
|
6
7
|
import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
|
|
7
8
|
import { validateFormatValue } from "./formats.ts";
|
|
9
|
+
import { AGENT_PAIRS } from "./install/target-registry.ts";
|
|
8
10
|
import { resolveMcpSchemaUri } from "./mcp/tools.ts";
|
|
9
11
|
import {
|
|
10
12
|
type CliLeaf,
|
|
@@ -13,6 +15,8 @@ import {
|
|
|
13
15
|
type CliProgram,
|
|
14
16
|
CliSchemaValidationError,
|
|
15
17
|
CliValueFormat,
|
|
18
|
+
type InstallAgentIntegration,
|
|
19
|
+
type InstallTargetSpec,
|
|
16
20
|
isCliLeaf,
|
|
17
21
|
isCliRouter,
|
|
18
22
|
} from "./types.ts";
|
|
@@ -104,6 +108,69 @@ function validateConfigBlock(appConfigBlock: import("./types.ts").CliAppConfig):
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
const PAIR_HOST_LABELS: Record<string, string> = {
|
|
112
|
+
cursorMcp: "cursor",
|
|
113
|
+
claudeCodeMcp: "claudeCode",
|
|
114
|
+
codexMcp: "codex",
|
|
115
|
+
opencodeMcp: "opencode",
|
|
116
|
+
openclawMcp: "openclaw",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function installTargetExplicitTruthy(spec: InstallTargetSpec | undefined): boolean {
|
|
120
|
+
if (spec === undefined || spec === false) return false;
|
|
121
|
+
if (spec === true) return true;
|
|
122
|
+
return spec.enabled !== false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Validates `program.install` targets and agentIntegration. */
|
|
126
|
+
function validateInstallConfig(program: CliProgram): void {
|
|
127
|
+
const install = program.install;
|
|
128
|
+
if (!install) return;
|
|
129
|
+
|
|
130
|
+
if ("prefix" in install) {
|
|
131
|
+
throw new CliSchemaValidationError(
|
|
132
|
+
"install.prefix removed; app installs to ~/.local/bin/<key>",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!install.targets) return;
|
|
137
|
+
|
|
138
|
+
const targets = install.targets;
|
|
139
|
+
if ("allSkills" in targets || "allMcps" in targets) {
|
|
140
|
+
throw new CliSchemaValidationError(
|
|
141
|
+
"install.targets.allSkills/allMcps removed; use agentIntegration and per-key targets",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const integration: InstallAgentIntegration =
|
|
146
|
+
install.agentIntegration ?? (program.mcpServer?.enabled === true ? "mcp" : "skill");
|
|
147
|
+
|
|
148
|
+
for (const [mcpKey, skillKey] of AGENT_PAIRS) {
|
|
149
|
+
const mcpSpec = targets[mcpKey];
|
|
150
|
+
const skillSpec = targets[skillKey];
|
|
151
|
+
const mcpOn = installTargetExplicitTruthy(mcpSpec);
|
|
152
|
+
const skillOn = installTargetExplicitTruthy(skillSpec);
|
|
153
|
+
const host = PAIR_HOST_LABELS[mcpKey] ?? mcpKey;
|
|
154
|
+
|
|
155
|
+
if (mcpOn && skillOn && integration !== "both") {
|
|
156
|
+
throw new CliSchemaValidationError(
|
|
157
|
+
`install.targets: ${host} has both MCP and skill configured; set agentIntegration: 'both' or disable one side`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (integration === "skill" && mcpOn) {
|
|
162
|
+
throw new CliSchemaValidationError(
|
|
163
|
+
`install.targets.${mcpKey} requires agentIntegration: 'both' when agentIntegration is 'skill'`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (integration === "mcp" && skillOn) {
|
|
167
|
+
throw new CliSchemaValidationError(
|
|
168
|
+
`install.targets.${skillKey} requires agentIntegration: 'both' when agentIntegration is 'mcp'`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
107
174
|
/** Validates a program schema. */
|
|
108
175
|
export function cliValidateProgram(program: CliProgram): void {
|
|
109
176
|
if (!program.version || program.version.trim().length === 0) {
|
|
@@ -141,6 +208,10 @@ export function cliValidateProgram(program: CliProgram): void {
|
|
|
141
208
|
}
|
|
142
209
|
}
|
|
143
210
|
|
|
211
|
+
if (program.install !== undefined) {
|
|
212
|
+
validateInstallConfig(program);
|
|
213
|
+
}
|
|
214
|
+
|
|
144
215
|
const caps = resolveCapabilities(program);
|
|
145
216
|
const reserved = reservedCommandNames(caps);
|
|
146
217
|
|
|
@@ -209,11 +280,15 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
209
280
|
|
|
210
281
|
if (isRoot && program.mcpServer?.enabled === true && program.mcpServer.resources) {
|
|
211
282
|
const schemaUri = resolveMcpSchemaUri(program);
|
|
283
|
+
const reserved = new Set([schemaUri, ...reservedDocsTopicResourceUris(program)]);
|
|
212
284
|
const uris = program.mcpServer.resources.map((r) => r.uri);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
285
|
+
for (const uri of uris) {
|
|
286
|
+
if (reserved.has(uri)) {
|
|
287
|
+
const kind = uri === schemaUri ? "built-in schema resource" : "auto docs topic resource";
|
|
288
|
+
throw new CliSchemaValidationError(
|
|
289
|
+
`mcpServer.resources URI '${uri}' conflicts with ${kind}`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
217
292
|
}
|
|
218
293
|
if (new Set(uris).size !== uris.length) {
|
|
219
294
|
throw new CliSchemaValidationError("mcpServer.resources URIs must be unique");
|