argsbarg 6.1.9 → 6.2.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 +17 -1
- package/README.md +187 -93
- package/docs/README.md +3 -2
- package/docs/ai-skills.md +36 -23
- package/docs/cli-program.md +12 -11
- package/docs/config-schema.md +3 -3
- package/docs/configure.md +12 -16
- package/docs/decisions.md +74 -37
- package/docs/developing.md +6 -6
- package/docs/distribution-homebrew.md +117 -104
- package/docs/mcp.md +21 -57
- package/docs/output-schema.md +2 -2
- package/examples/formats.ts +5 -6
- package/examples/full-example/README.md +7 -69
- package/examples/full-example/docs/cli-schema.json +1 -1659
- package/examples/full-example/docs/cli.md +2 -1538
- package/examples/full-example/docs/http.md +3 -8
- package/examples/full-example/docs/mcp.md +23 -59
- package/examples/full-example/docs/openapi.json +2 -782
- package/examples/full-example/docs/skill.md +18 -14
- package/examples/full-example/justfile +22 -14
- package/examples/full-example/scripts/create-identity.ts +2 -1
- package/examples/full-example/src/commands/status/command.test.ts +2 -2
- package/examples/full-example/src/commands/status/command.ts +7 -6
- package/examples/full-example/src/program.ts +4 -10
- package/examples/full-example-json/Formula/.gitkeep +0 -0
- package/examples/full-example-json/Formula/full-example-json.rb +35 -0
- package/examples/full-example-json/README.md +27 -0
- package/examples/full-example-json/biome.json +22 -0
- package/examples/full-example-json/bun.lock +48 -0
- package/examples/full-example-json/docs/README.md +27 -0
- package/examples/full-example-json/docs/cli-schema.json +2145 -0
- package/examples/full-example-json/docs/cli.md +1990 -0
- package/examples/full-example-json/docs/http.md +92 -0
- package/examples/full-example-json/docs/mcp.md +116 -0
- package/examples/full-example-json/docs/openapi.json +1246 -0
- package/examples/full-example-json/docs/skill.md +57 -0
- package/examples/full-example-json/justfile +171 -0
- package/examples/full-example-json/package.json +22 -0
- package/examples/full-example-json/scripts/create-identity.ts +12 -0
- package/examples/full-example-json/scripts/dev-formula.ts +97 -0
- package/examples/full-example-json/scripts/formula-shared.test.ts +68 -0
- package/examples/full-example-json/scripts/formula-shared.ts +170 -0
- package/examples/full-example-json/scripts/print-identity.ts +28 -0
- package/examples/full-example-json/scripts/release.ts +212 -0
- package/examples/full-example-json/src/commands/echo/command.ts +26 -0
- package/examples/full-example-json/src/commands/status/command.test.ts +10 -0
- package/examples/full-example-json/src/commands/status/command.ts +28 -0
- package/examples/full-example-json/src/index.ts +10 -0
- package/examples/full-example-json/src/program.ts +33 -0
- package/examples/full-example-json/src/types/md.d.ts +4 -0
- package/examples/full-example-json/tsconfig.json +17 -0
- package/examples/minimal.ts +17 -17
- package/examples/nested.ts +10 -10
- package/examples/option-required.ts +13 -13
- package/examples/servers.ts +10 -10
- package/index.d.ts +17 -43
- package/package.json +1 -1
- package/src/cli-tool/create.test.ts +44 -68
- package/src/cli-tool/create.ts +81 -17
- package/src/cli-tool/full-example-capabilities.test.ts +33 -18
- package/src/cli-tool/post-create.ts +31 -17
- package/src/cli-tool/program.ts +16 -7
- package/src/cli-tool/prompt.ts +27 -0
- package/src/cli-tool/run-create.ts +19 -7
- package/src/cli-tool/schemagen/schemagen.test.ts +3 -3
- package/src/configure/artifacts/install-validate.test.ts +20 -33
- package/src/configure/artifacts/paths.ts +9 -53
- package/src/configure/artifacts/status.test.ts +13 -16
- package/src/configure/artifacts/status.ts +5 -22
- package/src/configure/artifacts/target-base.ts +6 -15
- package/src/configure/artifacts/target-effective.ts +16 -54
- package/src/configure/artifacts/target-mcp-json.ts +2 -5
- package/src/configure/artifacts/target-registry.ts +0 -7
- package/src/configure/artifacts/target-scope.ts +7 -17
- package/src/configure/artifacts/target-skill.ts +6 -15
- package/src/configure/artifacts/target-types.ts +6 -54
- package/src/configure/artifacts/targets/agents-mcp.ts +11 -0
- package/src/configure/artifacts/targets/configure.ts +1 -5
- package/src/configure/artifacts/targets/index.ts +4 -44
- package/src/configure/artifacts/targets/skill.ts +12 -0
- package/src/configure/artifacts/targets.test.ts +21 -59
- package/src/configure/configure.test.ts +35 -46
- package/src/configure/index.ts +19 -19
- package/src/configure/prompt.ts +2 -12
- package/src/core/parse.test.ts +21 -32
- package/src/core/types.ts +18 -44
- package/src/core/validate.ts +28 -45
- package/src/docs/docs.test.ts +4 -4
- package/src/docs/http-guide.ts +1 -1
- package/src/docs/mcp-guide.ts +41 -71
- package/src/docs/resolve.ts +1 -1
- package/src/docs/save.ts +1 -1
- package/src/exports/cli.ts +1 -1
- package/src/index.ts +1 -1
- package/src/skill/generate.ts +26 -45
- package/src/skill/install.ts +18 -38
- package/src/skill/naming.ts +3 -27
- package/src/test/integration/config.test.ts +3 -3
- package/src/test/integration/mcp.test.ts +4 -4
- package/{examples/mcp-test.ts → src/test/mcp-integration-fixture.ts} +20 -22
- package/src/configure/artifacts/target-mcp-cli.ts +0 -127
- package/src/configure/artifacts/targets/chatgpt-mcp.ts +0 -12
- package/src/configure/artifacts/targets/claude-code-mcp.ts +0 -15
- package/src/configure/artifacts/targets/claude-desktop-mcp.ts +0 -12
- package/src/configure/artifacts/targets/claude-skill.ts +0 -16
- package/src/configure/artifacts/targets/codex-mcp.ts +0 -25
- package/src/configure/artifacts/targets/codex-skill.ts +0 -14
- package/src/configure/artifacts/targets/cursor-mcp.ts +0 -15
- package/src/configure/artifacts/targets/cursor-skill.ts +0 -16
- package/src/configure/artifacts/targets/openclaw-mcp.ts +0 -25
- package/src/configure/artifacts/targets/openclaw-skill.ts +0 -17
- package/src/configure/artifacts/targets/opencode-mcp.ts +0 -96
- package/src/configure/artifacts/targets/opencode-skill.ts +0 -15
- /package/examples/{full-example → full-example-json}/src/commands/render-json/__generated__/RenderJsonInputSchema.json +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/__generated__/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/command.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/command.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/types.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/status/__generated__/StatusJsonOutputSchema.json +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/status/__generated__/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/status/types.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/__generated__/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/command.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/command.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/types.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/index.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/migrate.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/migrate.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/migrations/001_workspaces.sql +0 -0
- /package/examples/{full-example → full-example-json}/src/db/tables/workspaces.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/types/argsbarg.d.ts +0 -0
package/src/skill/generate.ts
CHANGED
|
@@ -13,10 +13,7 @@ import {
|
|
|
13
13
|
resolveMcpSchemaUri,
|
|
14
14
|
sanitizeToolSegment,
|
|
15
15
|
} from "../mcp/tools.ts";
|
|
16
|
-
import
|
|
17
|
-
import { skillDirNameForTarget, skillFrontmatterName } from "./naming.ts";
|
|
18
|
-
|
|
19
|
-
export type { SkillTarget } from "./naming.ts";
|
|
16
|
+
import { skillDirName } from "./naming.ts";
|
|
20
17
|
|
|
21
18
|
export interface SkillBundle {
|
|
22
19
|
dirName: string;
|
|
@@ -103,16 +100,18 @@ function buildConfigurationSection(root: CliProgram): string[] {
|
|
|
103
100
|
return lines;
|
|
104
101
|
}
|
|
105
102
|
|
|
106
|
-
/** Builds SKILL.md body for the
|
|
107
|
-
function buildSkillMd(root: CliProgram,
|
|
108
|
-
const name =
|
|
103
|
+
/** Builds SKILL.md body for the agent skill bundle. */
|
|
104
|
+
function buildSkillMd(root: CliProgram, dirName: string): string {
|
|
105
|
+
const name = dirName;
|
|
109
106
|
const description = skillDescription(root);
|
|
110
107
|
const tools = collectMcpTools(root);
|
|
111
108
|
|
|
112
109
|
const lines: string[] = [
|
|
113
110
|
"---",
|
|
111
|
+
`id: ${dirName}`,
|
|
114
112
|
`name: ${name}`,
|
|
115
113
|
`description: ${description}`,
|
|
114
|
+
"enabled: true",
|
|
116
115
|
"---",
|
|
117
116
|
"",
|
|
118
117
|
`# ${root.key}`,
|
|
@@ -151,42 +150,24 @@ function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): s
|
|
|
151
150
|
"",
|
|
152
151
|
`For full detail, open \`reference.md\` in this skill directory (same as \`${root.key} docs cli\`).`,
|
|
153
152
|
"",
|
|
153
|
+
"## Install location",
|
|
154
|
+
"",
|
|
155
|
+
"Install follows the [.agents protocol](https://dotagentsprotocol.com/):",
|
|
156
|
+
"",
|
|
157
|
+
`- Auto-install: \`${root.key} configure --sync --yes\` when \`skill.enabled\` → \`~/.agents/skills/${dirName}/\``,
|
|
158
|
+
`- Cursor and most coding agents read \`~/.agents/skills/\` natively`,
|
|
159
|
+
"",
|
|
160
|
+
"**Claude Code (manual):** symlink or copy into Claude's skill directory:",
|
|
161
|
+
"",
|
|
162
|
+
"```bash",
|
|
163
|
+
"mkdir -p ~/.claude/skills",
|
|
164
|
+
`ln -sf ~/.agents/skills/${dirName} ~/.claude/skills/${dirName}`,
|
|
165
|
+
"```",
|
|
166
|
+
"",
|
|
167
|
+
`Project override (optional): \`.agents/skills/${dirName}/\``,
|
|
168
|
+
"",
|
|
154
169
|
);
|
|
155
170
|
|
|
156
|
-
if (target === "cursor") {
|
|
157
|
-
lines.push(
|
|
158
|
-
"## Cursor install location",
|
|
159
|
-
"",
|
|
160
|
-
`- Project: \`.cursor/skills/${dirName}/\``,
|
|
161
|
-
`- Global: \`~/.cursor/skills/${dirName}/\``,
|
|
162
|
-
"",
|
|
163
|
-
"Do not install under `~/.cursor/skills-cursor/` (reserved for Cursor built-ins).",
|
|
164
|
-
"",
|
|
165
|
-
);
|
|
166
|
-
} else if (target === "claude") {
|
|
167
|
-
lines.push(
|
|
168
|
-
"## Claude Code",
|
|
169
|
-
"",
|
|
170
|
-
`- Invoke with \`/${dirName}\` or let Claude auto-match from the description.`,
|
|
171
|
-
`- Project skills: \`.claude/skills/${dirName}/\``,
|
|
172
|
-
`- Global skills: \`~/.claude/skills/${dirName}/\``,
|
|
173
|
-
`- Bundled files in this directory are available via \`\${CLAUDE_SKILL_DIR}\` when the skill runs.`,
|
|
174
|
-
"",
|
|
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}/\``, "");
|
|
188
|
-
}
|
|
189
|
-
|
|
190
171
|
return lines.join("\n");
|
|
191
172
|
}
|
|
192
173
|
|
|
@@ -243,12 +224,12 @@ export function generatePluginSkillBundle(root: CliProgram): PluginSkillBundle {
|
|
|
243
224
|
};
|
|
244
225
|
}
|
|
245
226
|
|
|
246
|
-
/** Generates SKILL.md and reference.md for
|
|
247
|
-
export function generateSkillBundle(root: CliProgram
|
|
248
|
-
const dirName =
|
|
227
|
+
/** Generates SKILL.md and reference.md for agent skill install. */
|
|
228
|
+
export function generateSkillBundle(root: CliProgram): SkillBundle {
|
|
229
|
+
const dirName = skillDirName(root.key);
|
|
249
230
|
return {
|
|
250
231
|
dirName,
|
|
251
|
-
skillMd: buildSkillMd(root,
|
|
232
|
+
skillMd: buildSkillMd(root, dirName),
|
|
252
233
|
referenceMd: buildReferenceMd(root),
|
|
253
234
|
};
|
|
254
235
|
}
|
package/src/skill/install.ts
CHANGED
|
@@ -2,10 +2,11 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import type { CliProgram } from "../core/types.ts";
|
|
4
4
|
import { userHome } from "../paths/host.ts";
|
|
5
|
-
import { generateSkillBundle
|
|
5
|
+
import { generateSkillBundle } from "./generate.ts";
|
|
6
6
|
import { applySkillInstallHints } from "./hint.ts";
|
|
7
|
+
import { skillDirName } from "./naming.ts";
|
|
7
8
|
|
|
8
|
-
export {
|
|
9
|
+
export { skillDirName } from "./naming.ts";
|
|
9
10
|
|
|
10
11
|
export interface SkillInstallOpts {
|
|
11
12
|
global?: boolean;
|
|
@@ -13,60 +14,39 @@ export interface SkillInstallOpts {
|
|
|
13
14
|
dry?: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
}
|
|
17
|
+
/** Resolved skill directory for a program (`~/.agents/skills/<key>/` or project `.agents/skills/<key>/`). */
|
|
18
|
+
export function resolveAgentsSkillDir(root: CliProgram, global = true): string {
|
|
19
|
+
const base = global ? userHome() : process.cwd();
|
|
20
|
+
return join(base, ".agents", "skills", skillDirName(root.key));
|
|
30
21
|
}
|
|
31
22
|
|
|
32
|
-
/** Writes SKILL.md and reference.md; returns changed file paths. */
|
|
33
|
-
export function cliSkillInstall(root: CliProgram,
|
|
34
|
-
const bundle = generateSkillBundle(root
|
|
23
|
+
/** Writes skill.md, SKILL.md (compatibility copy), and reference.md; returns changed file paths. */
|
|
24
|
+
export function cliSkillInstall(root: CliProgram, opts: SkillInstallOpts): string[] {
|
|
25
|
+
const bundle = generateSkillBundle(root);
|
|
35
26
|
const { skillMd, referenceMd } = applySkillInstallHints(root, bundle.skillMd, bundle.referenceMd);
|
|
36
|
-
const dir =
|
|
27
|
+
const dir = resolveAgentsSkillDir(root, opts.global ?? true);
|
|
37
28
|
const changed: string[] = [];
|
|
38
29
|
|
|
39
30
|
if (opts.rimraf && existsSync(dir) && !opts.dry) {
|
|
40
31
|
rmSync(dir, { recursive: true, force: true });
|
|
41
32
|
}
|
|
42
33
|
|
|
43
|
-
const skillPath = join(dir, "
|
|
34
|
+
const skillPath = join(dir, "skill.md");
|
|
35
|
+
const skillCompatPath = join(dir, "SKILL.md");
|
|
44
36
|
const refPath = join(dir, "reference.md");
|
|
45
37
|
|
|
46
38
|
if (!opts.dry) {
|
|
47
39
|
mkdirSync(dir, { recursive: true });
|
|
48
40
|
writeFileSync(skillPath, skillMd, "utf8");
|
|
41
|
+
writeFileSync(skillCompatPath, skillMd, "utf8");
|
|
49
42
|
writeFileSync(refPath, referenceMd, "utf8");
|
|
50
43
|
}
|
|
51
44
|
|
|
52
|
-
changed.push(skillPath, refPath);
|
|
45
|
+
changed.push(skillPath, skillCompatPath, refPath);
|
|
53
46
|
return changed;
|
|
54
47
|
}
|
|
55
48
|
|
|
56
|
-
/**
|
|
57
|
-
export function
|
|
58
|
-
|
|
59
|
-
case "cursor-skill":
|
|
60
|
-
return "cursor";
|
|
61
|
-
case "claude-skill":
|
|
62
|
-
return "claude";
|
|
63
|
-
case "codex-skill":
|
|
64
|
-
return "codex";
|
|
65
|
-
case "opencode-skill":
|
|
66
|
-
return "opencode";
|
|
67
|
-
case "openclaw-skill":
|
|
68
|
-
return "openclaw";
|
|
69
|
-
default:
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
49
|
+
/** True when the plan action kind installs the agent skill bundle. */
|
|
50
|
+
export function isAgentSkillActionKind(kind: string): boolean {
|
|
51
|
+
return kind === "agent-skill";
|
|
72
52
|
}
|
package/src/skill/naming.ts
CHANGED
|
@@ -1,28 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export
|
|
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);
|
|
1
|
+
/** Skill directory and frontmatter name from program key (path-safe segment). */
|
|
2
|
+
export function skillDirName(programKey: string): string {
|
|
3
|
+
return programKey.replace(/[/\\\s]/g, "_");
|
|
28
4
|
}
|
|
@@ -55,7 +55,7 @@ test("MCP program.appConfig fails when required config missing", async () => {
|
|
|
55
55
|
params: { name: "echo_env", arguments: { name: "ARGS_TEST_SECRET" } },
|
|
56
56
|
},
|
|
57
57
|
],
|
|
58
|
-
{ script: "
|
|
58
|
+
{ script: "src/test/mcp-integration-fixture.ts", env: { ARGS_TEST_SECRET: "" } },
|
|
59
59
|
);
|
|
60
60
|
const res = responses.get(13) as { result: { isError: boolean; content: { text: string }[] } };
|
|
61
61
|
expect(res.result.isError).toBe(true);
|
|
@@ -73,7 +73,7 @@ test("MCP program.appConfig succeeds when env present", async () => {
|
|
|
73
73
|
params: { name: "echo_env", arguments: { name: "ARGS_TEST_SECRET" } },
|
|
74
74
|
},
|
|
75
75
|
],
|
|
76
|
-
{ script: "
|
|
76
|
+
{ script: "src/test/mcp-integration-fixture.ts", env: { ARGS_TEST_SECRET: "sekrit" } },
|
|
77
77
|
);
|
|
78
78
|
const res = responses.get(14) as {
|
|
79
79
|
result: {
|
|
@@ -105,7 +105,7 @@ test("MCP config file loads and exports vars for tool handlers", async () => {
|
|
|
105
105
|
},
|
|
106
106
|
],
|
|
107
107
|
{
|
|
108
|
-
script: "
|
|
108
|
+
script: "src/test/mcp-integration-fixture.ts",
|
|
109
109
|
env: { HOME: dir, ARGS_TEST_SECRET: "present" },
|
|
110
110
|
},
|
|
111
111
|
);
|
|
@@ -446,7 +446,7 @@ test("minimal.ts mcp without opt-in fails", async () => {
|
|
|
446
446
|
|
|
447
447
|
test("MCP resources/list includes custom resource", async () => {
|
|
448
448
|
const responses = await mcpRequest([{ jsonrpc: "2.0", id: 10, method: "resources/list", params: {} }], {
|
|
449
|
-
script: "
|
|
449
|
+
script: "src/test/mcp-integration-fixture.ts",
|
|
450
450
|
});
|
|
451
451
|
const res = responses.get(10) as { result: { resources: { uri: string }[] } };
|
|
452
452
|
const uris = res.result.resources.map((r) => r.uri);
|
|
@@ -466,7 +466,7 @@ test("MCP resources/read returns docs topic resource body", async () => {
|
|
|
466
466
|
params: { uri: "mcp_test://docs/readme" },
|
|
467
467
|
},
|
|
468
468
|
],
|
|
469
|
-
{ script: "
|
|
469
|
+
{ script: "src/test/mcp-integration-fixture.ts" },
|
|
470
470
|
);
|
|
471
471
|
const res = responses.get(13) as { result: { contents: { text: string }[] } };
|
|
472
472
|
expect(res.result.contents[0]?.text).toBe("# MCP test readme\n");
|
|
@@ -475,7 +475,7 @@ test("MCP resources/read returns docs topic resource body", async () => {
|
|
|
475
475
|
test("MCP resources/read returns custom resource body", async () => {
|
|
476
476
|
const responses = await mcpRequest(
|
|
477
477
|
[{ jsonrpc: "2.0", id: 11, method: "resources/read", params: { uri: "test://hello" } }],
|
|
478
|
-
{ script: "
|
|
478
|
+
{ script: "src/test/mcp-integration-fixture.ts" },
|
|
479
479
|
);
|
|
480
480
|
const res = responses.get(11) as { result: { contents: { text: string }[] } };
|
|
481
481
|
expect(res.result.contents[0]?.text).toBe("hello resource");
|
|
@@ -484,7 +484,7 @@ test("MCP resources/read returns custom resource body", async () => {
|
|
|
484
484
|
test("MCP resources/read unknown URI returns error", async () => {
|
|
485
485
|
const responses = await mcpRequest(
|
|
486
486
|
[{ jsonrpc: "2.0", id: 12, method: "resources/read", params: { uri: "missing://nope" } }],
|
|
487
|
-
{ script: "
|
|
487
|
+
{ script: "src/test/mcp-integration-fixture.ts" },
|
|
488
488
|
);
|
|
489
489
|
const res = responses.get(12) as { error: { code: number } };
|
|
490
490
|
expect(res.error.code).toBe(-32602);
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/*
|
|
3
|
-
MCP test fixture for subprocess
|
|
3
|
+
MCP integration test fixture for subprocess tests (not a public example).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Cli, CliOptionKind, type CliProgram } from "../
|
|
6
|
+
import { Cli, CliOptionKind, type CliProgram } from "../index.ts";
|
|
7
7
|
|
|
8
8
|
const program = {
|
|
9
|
-
key: "mcp-test",
|
|
10
|
-
version: "0.0.0-test",
|
|
11
|
-
description: "MCP integration test fixture.",
|
|
12
9
|
appConfig: {
|
|
13
10
|
entries: {
|
|
14
11
|
argsTestSecret: {
|
|
@@ -17,23 +14,6 @@ const program = {
|
|
|
17
14
|
},
|
|
18
15
|
},
|
|
19
16
|
},
|
|
20
|
-
mcpServer: {
|
|
21
|
-
enabled: true,
|
|
22
|
-
resources: [
|
|
23
|
-
{
|
|
24
|
-
uri: "test://hello",
|
|
25
|
-
name: "hello",
|
|
26
|
-
description: "Test resource.",
|
|
27
|
-
mimeType: "text/plain",
|
|
28
|
-
load: () => "hello resource",
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
},
|
|
32
|
-
docs: {
|
|
33
|
-
topics: {
|
|
34
|
-
readme: { text: "# MCP test readme\n" },
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
17
|
commands: [
|
|
38
18
|
{
|
|
39
19
|
key: "echo-env",
|
|
@@ -79,6 +59,24 @@ const program = {
|
|
|
79
59
|
},
|
|
80
60
|
},
|
|
81
61
|
],
|
|
62
|
+
description: "MCP integration test fixture.",
|
|
63
|
+
docs: {
|
|
64
|
+
topics: {
|
|
65
|
+
readme: { text: "# MCP test readme\n" },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
key: "mcp-test",
|
|
69
|
+
mcpServer: {
|
|
70
|
+
enabled: true,
|
|
71
|
+
resources: [
|
|
72
|
+
{
|
|
73
|
+
uri: "test://hello",
|
|
74
|
+
name: "hello",
|
|
75
|
+
load: () => "hello resource",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
version: "0.0.0-test",
|
|
82
80
|
} satisfies CliProgram;
|
|
83
81
|
|
|
84
82
|
const cli = new Cli(program);
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import type { CliProgram } from "../../core/types.ts";
|
|
2
|
-
import { checkCodexMcpConflict, codexMcpHasServer, mergeCodexMcpConfig, removeCodexMcpConfig } from "./mcp-codex.ts";
|
|
3
|
-
import { expectedMcpEntry } from "./mcp-config.ts";
|
|
4
|
-
import {
|
|
5
|
-
checkOpenclawMcpConflict,
|
|
6
|
-
mergeOpenclawMcpConfig,
|
|
7
|
-
openclawMcpHasServer,
|
|
8
|
-
removeOpenclawMcpConfig,
|
|
9
|
-
} from "./mcp-openclaw.ts";
|
|
10
|
-
import { displayInstallPath, type InstallPaths, userHome } from "./paths.ts";
|
|
11
|
-
import { InstallTarget } from "./target-base.ts";
|
|
12
|
-
import type {
|
|
13
|
-
CliInstallArtifactKey,
|
|
14
|
-
DetectedSnapshot,
|
|
15
|
-
InstallAction,
|
|
16
|
-
InstallActionKind,
|
|
17
|
-
InstalledArtifacts,
|
|
18
|
-
InstallStatus,
|
|
19
|
-
TargetPlanContext,
|
|
20
|
-
UninstallAction,
|
|
21
|
-
} from "./target-types.ts";
|
|
22
|
-
|
|
23
|
-
export interface McpCliHostSpec {
|
|
24
|
-
key: CliInstallArtifactKey;
|
|
25
|
-
actionKind: InstallActionKind;
|
|
26
|
-
label: string;
|
|
27
|
-
pairedSkillKey: CliInstallArtifactKey;
|
|
28
|
-
configPath: (paths: InstallPaths) => string;
|
|
29
|
-
detectedKey: "codexMcp" | "openclawMcp";
|
|
30
|
-
statusField: "codexMcp" | "openclawMcp";
|
|
31
|
-
isAvailable: (root: CliProgram, paths: InstallPaths) => boolean;
|
|
32
|
-
hasServer: (home: string, name: string) => boolean;
|
|
33
|
-
installMessage: string;
|
|
34
|
-
uninstallMessage: string;
|
|
35
|
-
merge: (home: string, name: string, entry: ReturnType<typeof expectedMcpEntry>, dry: boolean, yes: boolean) => string;
|
|
36
|
-
remove: (home: string, name: string, dry: boolean) => void;
|
|
37
|
-
preflight: (home: string, name: string, entry: ReturnType<typeof expectedMcpEntry>, yes: boolean) => string | null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** MCP host registered via an external CLI (codex, openclaw). */
|
|
41
|
-
export class McpCliInstallTarget extends InstallTarget {
|
|
42
|
-
readonly key: CliInstallArtifactKey;
|
|
43
|
-
readonly actionKind: InstallActionKind;
|
|
44
|
-
readonly category = "mcp" as const;
|
|
45
|
-
readonly pairedKey: CliInstallArtifactKey;
|
|
46
|
-
|
|
47
|
-
private readonly spec: McpCliHostSpec;
|
|
48
|
-
|
|
49
|
-
constructor(spec: McpCliHostSpec) {
|
|
50
|
-
super();
|
|
51
|
-
this.spec = spec;
|
|
52
|
-
this.key = spec.key;
|
|
53
|
-
this.actionKind = spec.actionKind;
|
|
54
|
-
this.pairedKey = spec.pairedSkillKey;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
isAvailable(root: CliProgram, paths: InstallPaths): boolean {
|
|
58
|
-
return this.spec.isAvailable(root, paths);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
isDetected(paths: InstallPaths, _root: CliProgram): boolean {
|
|
62
|
-
return this.spec.hasServer(userHome(), paths.mcpName);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
applyDetected(paths: InstallPaths, root: CliProgram, out: InstalledArtifacts): void {
|
|
66
|
-
out[this.spec.detectedKey] = this.isDetected(paths, root);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
protected isDetectedFromSnapshot(detected: DetectedSnapshot): boolean {
|
|
70
|
-
return detected[this.spec.detectedKey] ?? false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
protected formatStatusLine(paths: InstallPaths, _root: CliProgram): string {
|
|
74
|
-
return `${displayInstallPath(this.spec.configPath(paths))} (server "${paths.mcpName}")`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
protected assignStatusLine(status: InstallStatus, line: string): void {
|
|
78
|
-
status[this.spec.statusField] = line;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
preflight(ctx: TargetPlanContext): string | null {
|
|
82
|
-
const entry = expectedMcpEntry(ctx.root);
|
|
83
|
-
return this.spec.preflight(userHome(), ctx.paths.mcpName, entry, !!ctx.opts.yes);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
protected buildInstallActions(ctx: TargetPlanContext): InstallAction[] {
|
|
87
|
-
const entry = expectedMcpEntry(ctx.root);
|
|
88
|
-
const configPath = this.spec.configPath(ctx.paths);
|
|
89
|
-
return [
|
|
90
|
-
{
|
|
91
|
-
kind: this.actionKind,
|
|
92
|
-
summary: `${this.spec.label}: ${displayInstallPath(configPath)}`,
|
|
93
|
-
message: this.spec.installMessage.replace("{name}", ctx.paths.mcpName),
|
|
94
|
-
run: () => {
|
|
95
|
-
const written = this.spec.merge(userHome(), ctx.paths.mcpName, entry, ctx.dry, !!ctx.opts.yes);
|
|
96
|
-
return [written];
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
protected buildUninstallActions(ctx: TargetPlanContext): UninstallAction[] {
|
|
103
|
-
const configPath = this.spec.configPath(ctx.paths);
|
|
104
|
-
return [
|
|
105
|
-
{
|
|
106
|
-
kind: this.actionKind,
|
|
107
|
-
summary: `${this.spec.label}: ${displayInstallPath(configPath)}`,
|
|
108
|
-
message: this.spec.uninstallMessage.replace("{name}", ctx.paths.mcpName),
|
|
109
|
-
run: () => {
|
|
110
|
-
this.spec.remove(userHome(), ctx.paths.mcpName, ctx.dry);
|
|
111
|
-
return [configPath];
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
];
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export {
|
|
119
|
-
checkCodexMcpConflict,
|
|
120
|
-
checkOpenclawMcpConflict,
|
|
121
|
-
codexMcpHasServer,
|
|
122
|
-
mergeCodexMcpConfig,
|
|
123
|
-
mergeOpenclawMcpConfig,
|
|
124
|
-
openclawMcpHasServer,
|
|
125
|
-
removeCodexMcpConfig,
|
|
126
|
-
removeOpenclawMcpConfig,
|
|
127
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { chatGptDesktopPresent, userHome } from "../paths.ts";
|
|
2
|
-
import { McpJsonInstallTarget } from "../target-mcp-json.ts";
|
|
3
|
-
|
|
4
|
-
export const chatgptMcpTarget = new McpJsonInstallTarget({
|
|
5
|
-
key: "chatgptMcp",
|
|
6
|
-
actionKind: "chatgpt-desktop-mcp",
|
|
7
|
-
label: "chatgpt desktop mcp",
|
|
8
|
-
configPath: (p) => p.chatGptMcpPath,
|
|
9
|
-
detectedKey: "chatGptMcp",
|
|
10
|
-
statusField: "chatGptMcp",
|
|
11
|
-
isAvailable: (_root, paths) => chatGptDesktopPresent(userHome(), paths.chatGptMcpPath),
|
|
12
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { userHome } from "../paths.ts";
|
|
4
|
-
import { McpJsonInstallTarget } from "../target-mcp-json.ts";
|
|
5
|
-
|
|
6
|
-
export const claudeCodeMcpTarget = new McpJsonInstallTarget({
|
|
7
|
-
key: "claudeCodeMcp",
|
|
8
|
-
actionKind: "claude-mcp",
|
|
9
|
-
label: "claude code mcp",
|
|
10
|
-
pairedSkillKey: "claudeSkill",
|
|
11
|
-
configPath: (p) => p.claudeMcpPath,
|
|
12
|
-
detectedKey: "claudeMcp",
|
|
13
|
-
statusField: "claudeMcp",
|
|
14
|
-
isAvailable: () => existsSync(join(userHome(), ".claude")),
|
|
15
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { claudeDesktopPresent, userHome } from "../paths.ts";
|
|
2
|
-
import { McpJsonInstallTarget } from "../target-mcp-json.ts";
|
|
3
|
-
|
|
4
|
-
export const claudeDesktopMcpTarget = new McpJsonInstallTarget({
|
|
5
|
-
key: "claudeDesktopMcp",
|
|
6
|
-
actionKind: "claude-desktop-mcp",
|
|
7
|
-
label: "claude desktop mcp",
|
|
8
|
-
configPath: (p) => p.claudeDesktopMcpPath,
|
|
9
|
-
detectedKey: "claudeDesktopMcp",
|
|
10
|
-
statusField: "claudeDesktopMcp",
|
|
11
|
-
isAvailable: (_root, paths) => claudeDesktopPresent(userHome(), paths.claudeDesktopMcpPath),
|
|
12
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { userHome } from "../paths.ts";
|
|
4
|
-
import { SkillInstallTarget } from "../target-skill.ts";
|
|
5
|
-
|
|
6
|
-
export const claudeSkillTarget = new SkillInstallTarget({
|
|
7
|
-
key: "claudeSkill",
|
|
8
|
-
actionKind: "claude-skill",
|
|
9
|
-
label: "Claude Code",
|
|
10
|
-
uninstallPrefix: "claude skill",
|
|
11
|
-
pairedMcpKey: "claudeCodeMcp",
|
|
12
|
-
skillDir: (p) => p.claudeSkillDir,
|
|
13
|
-
detectedKey: "claudeSkill",
|
|
14
|
-
statusField: "claudeSkill",
|
|
15
|
-
isAvailable: () => existsSync(join(userHome(), ".claude")),
|
|
16
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { codexOnPath } from "../mcp-codex.ts";
|
|
2
|
-
import {
|
|
3
|
-
checkCodexMcpConflict,
|
|
4
|
-
codexMcpHasServer,
|
|
5
|
-
McpCliInstallTarget,
|
|
6
|
-
mergeCodexMcpConfig,
|
|
7
|
-
removeCodexMcpConfig,
|
|
8
|
-
} from "../target-mcp-cli.ts";
|
|
9
|
-
|
|
10
|
-
export const codexMcpTarget = new McpCliInstallTarget({
|
|
11
|
-
key: "codexMcp",
|
|
12
|
-
actionKind: "codex-mcp",
|
|
13
|
-
label: "codex mcp",
|
|
14
|
-
pairedSkillKey: "codexSkill",
|
|
15
|
-
configPath: (p) => p.codexConfigPath,
|
|
16
|
-
detectedKey: "codexMcp",
|
|
17
|
-
statusField: "codexMcp",
|
|
18
|
-
isAvailable: () => codexOnPath(),
|
|
19
|
-
hasServer: codexMcpHasServer,
|
|
20
|
-
installMessage: 'Registering MCP server "{name}" via codex mcp add',
|
|
21
|
-
uninstallMessage: 'Removing MCP server "{name}" from Codex via codex mcp remove',
|
|
22
|
-
merge: mergeCodexMcpConfig,
|
|
23
|
-
remove: removeCodexMcpConfig,
|
|
24
|
-
preflight: checkCodexMcpConflict,
|
|
25
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { codexOnPath } from "../mcp-codex.ts";
|
|
2
|
-
import { SkillInstallTarget } from "../target-skill.ts";
|
|
3
|
-
|
|
4
|
-
export const codexSkillTarget = new SkillInstallTarget({
|
|
5
|
-
key: "codexSkill",
|
|
6
|
-
actionKind: "codex-skill",
|
|
7
|
-
label: "Codex",
|
|
8
|
-
uninstallPrefix: "codex skill",
|
|
9
|
-
pairedMcpKey: "codexMcp",
|
|
10
|
-
skillDir: (p) => p.codexSkillDir,
|
|
11
|
-
detectedKey: "codexSkill",
|
|
12
|
-
statusField: "codexSkill",
|
|
13
|
-
isAvailable: () => codexOnPath(),
|
|
14
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { userHome } from "../paths.ts";
|
|
4
|
-
import { McpJsonInstallTarget } from "../target-mcp-json.ts";
|
|
5
|
-
|
|
6
|
-
export const cursorMcpTarget = new McpJsonInstallTarget({
|
|
7
|
-
key: "cursorMcp",
|
|
8
|
-
actionKind: "cursor-mcp",
|
|
9
|
-
label: "cursor mcp",
|
|
10
|
-
pairedSkillKey: "cursorSkill",
|
|
11
|
-
configPath: (p) => p.cursorMcpPath,
|
|
12
|
-
detectedKey: "cursorMcp",
|
|
13
|
-
statusField: "cursorMcp",
|
|
14
|
-
isAvailable: () => existsSync(join(userHome(), ".cursor")),
|
|
15
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { userHome } from "../paths.ts";
|
|
4
|
-
import { SkillInstallTarget } from "../target-skill.ts";
|
|
5
|
-
|
|
6
|
-
export const cursorSkillTarget = new SkillInstallTarget({
|
|
7
|
-
key: "cursorSkill",
|
|
8
|
-
actionKind: "cursor-skill",
|
|
9
|
-
label: "Cursor",
|
|
10
|
-
uninstallPrefix: "cursor skill",
|
|
11
|
-
pairedMcpKey: "cursorMcp",
|
|
12
|
-
skillDir: (p) => p.cursorSkillDir,
|
|
13
|
-
detectedKey: "cursorSkill",
|
|
14
|
-
statusField: "cursorSkill",
|
|
15
|
-
isAvailable: () => existsSync(join(userHome(), ".cursor")),
|
|
16
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { openclawOnPath } from "../mcp-openclaw.ts";
|
|
2
|
-
import {
|
|
3
|
-
checkOpenclawMcpConflict,
|
|
4
|
-
McpCliInstallTarget,
|
|
5
|
-
mergeOpenclawMcpConfig,
|
|
6
|
-
openclawMcpHasServer,
|
|
7
|
-
removeOpenclawMcpConfig,
|
|
8
|
-
} from "../target-mcp-cli.ts";
|
|
9
|
-
|
|
10
|
-
export const openclawMcpTarget = new McpCliInstallTarget({
|
|
11
|
-
key: "openclawMcp",
|
|
12
|
-
actionKind: "openclaw-mcp",
|
|
13
|
-
label: "openclaw mcp",
|
|
14
|
-
pairedSkillKey: "openclawSkill",
|
|
15
|
-
configPath: (p) => p.openclawConfigPath,
|
|
16
|
-
detectedKey: "openclawMcp",
|
|
17
|
-
statusField: "openclawMcp",
|
|
18
|
-
isAvailable: () => openclawOnPath(),
|
|
19
|
-
hasServer: openclawMcpHasServer,
|
|
20
|
-
installMessage: 'Registering MCP server "{name}" via openclaw mcp add',
|
|
21
|
-
uninstallMessage: 'Removing MCP server "{name}" from OpenClaw via openclaw mcp unset',
|
|
22
|
-
merge: mergeOpenclawMcpConfig,
|
|
23
|
-
remove: removeOpenclawMcpConfig,
|
|
24
|
-
preflight: checkOpenclawMcpConflict,
|
|
25
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { openclawOnPath } from "../mcp-openclaw.ts";
|
|
4
|
-
import { userHome } from "../paths.ts";
|
|
5
|
-
import { SkillInstallTarget } from "../target-skill.ts";
|
|
6
|
-
|
|
7
|
-
export const openclawSkillTarget = new SkillInstallTarget({
|
|
8
|
-
key: "openclawSkill",
|
|
9
|
-
actionKind: "openclaw-skill",
|
|
10
|
-
label: "OpenClaw",
|
|
11
|
-
uninstallPrefix: "openclaw skill",
|
|
12
|
-
pairedMcpKey: "openclawMcp",
|
|
13
|
-
skillDir: (p) => p.openclawSkillDir,
|
|
14
|
-
detectedKey: "openclawSkill",
|
|
15
|
-
statusField: "openclawSkill",
|
|
16
|
-
isAvailable: () => existsSync(join(userHome(), ".openclaw")) || openclawOnPath(),
|
|
17
|
-
});
|