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.
Files changed (134) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +187 -93
  3. package/docs/README.md +3 -2
  4. package/docs/ai-skills.md +36 -23
  5. package/docs/cli-program.md +12 -11
  6. package/docs/config-schema.md +3 -3
  7. package/docs/configure.md +12 -16
  8. package/docs/decisions.md +74 -37
  9. package/docs/developing.md +6 -6
  10. package/docs/distribution-homebrew.md +117 -104
  11. package/docs/mcp.md +21 -57
  12. package/docs/output-schema.md +2 -2
  13. package/examples/formats.ts +5 -6
  14. package/examples/full-example/README.md +7 -69
  15. package/examples/full-example/docs/cli-schema.json +1 -1659
  16. package/examples/full-example/docs/cli.md +2 -1538
  17. package/examples/full-example/docs/http.md +3 -8
  18. package/examples/full-example/docs/mcp.md +23 -59
  19. package/examples/full-example/docs/openapi.json +2 -782
  20. package/examples/full-example/docs/skill.md +18 -14
  21. package/examples/full-example/justfile +22 -14
  22. package/examples/full-example/scripts/create-identity.ts +2 -1
  23. package/examples/full-example/src/commands/status/command.test.ts +2 -2
  24. package/examples/full-example/src/commands/status/command.ts +7 -6
  25. package/examples/full-example/src/program.ts +4 -10
  26. package/examples/full-example-json/Formula/.gitkeep +0 -0
  27. package/examples/full-example-json/Formula/full-example-json.rb +35 -0
  28. package/examples/full-example-json/README.md +27 -0
  29. package/examples/full-example-json/biome.json +22 -0
  30. package/examples/full-example-json/bun.lock +48 -0
  31. package/examples/full-example-json/docs/README.md +27 -0
  32. package/examples/full-example-json/docs/cli-schema.json +2145 -0
  33. package/examples/full-example-json/docs/cli.md +1990 -0
  34. package/examples/full-example-json/docs/http.md +92 -0
  35. package/examples/full-example-json/docs/mcp.md +116 -0
  36. package/examples/full-example-json/docs/openapi.json +1246 -0
  37. package/examples/full-example-json/docs/skill.md +57 -0
  38. package/examples/full-example-json/justfile +171 -0
  39. package/examples/full-example-json/package.json +22 -0
  40. package/examples/full-example-json/scripts/create-identity.ts +12 -0
  41. package/examples/full-example-json/scripts/dev-formula.ts +97 -0
  42. package/examples/full-example-json/scripts/formula-shared.test.ts +68 -0
  43. package/examples/full-example-json/scripts/formula-shared.ts +170 -0
  44. package/examples/full-example-json/scripts/print-identity.ts +28 -0
  45. package/examples/full-example-json/scripts/release.ts +212 -0
  46. package/examples/full-example-json/src/commands/echo/command.ts +26 -0
  47. package/examples/full-example-json/src/commands/status/command.test.ts +10 -0
  48. package/examples/full-example-json/src/commands/status/command.ts +28 -0
  49. package/examples/full-example-json/src/index.ts +10 -0
  50. package/examples/full-example-json/src/program.ts +33 -0
  51. package/examples/full-example-json/src/types/md.d.ts +4 -0
  52. package/examples/full-example-json/tsconfig.json +17 -0
  53. package/examples/minimal.ts +17 -17
  54. package/examples/nested.ts +10 -10
  55. package/examples/option-required.ts +13 -13
  56. package/examples/servers.ts +10 -10
  57. package/index.d.ts +17 -43
  58. package/package.json +1 -1
  59. package/src/cli-tool/create.test.ts +44 -68
  60. package/src/cli-tool/create.ts +81 -17
  61. package/src/cli-tool/full-example-capabilities.test.ts +33 -18
  62. package/src/cli-tool/post-create.ts +31 -17
  63. package/src/cli-tool/program.ts +16 -7
  64. package/src/cli-tool/prompt.ts +27 -0
  65. package/src/cli-tool/run-create.ts +19 -7
  66. package/src/cli-tool/schemagen/schemagen.test.ts +3 -3
  67. package/src/configure/artifacts/install-validate.test.ts +20 -33
  68. package/src/configure/artifacts/paths.ts +9 -53
  69. package/src/configure/artifacts/status.test.ts +13 -16
  70. package/src/configure/artifacts/status.ts +5 -22
  71. package/src/configure/artifacts/target-base.ts +6 -15
  72. package/src/configure/artifacts/target-effective.ts +16 -54
  73. package/src/configure/artifacts/target-mcp-json.ts +2 -5
  74. package/src/configure/artifacts/target-registry.ts +0 -7
  75. package/src/configure/artifacts/target-scope.ts +7 -17
  76. package/src/configure/artifacts/target-skill.ts +6 -15
  77. package/src/configure/artifacts/target-types.ts +6 -54
  78. package/src/configure/artifacts/targets/agents-mcp.ts +11 -0
  79. package/src/configure/artifacts/targets/configure.ts +1 -5
  80. package/src/configure/artifacts/targets/index.ts +4 -44
  81. package/src/configure/artifacts/targets/skill.ts +12 -0
  82. package/src/configure/artifacts/targets.test.ts +21 -59
  83. package/src/configure/configure.test.ts +35 -46
  84. package/src/configure/index.ts +19 -19
  85. package/src/configure/prompt.ts +2 -12
  86. package/src/core/parse.test.ts +21 -32
  87. package/src/core/types.ts +18 -44
  88. package/src/core/validate.ts +28 -45
  89. package/src/docs/docs.test.ts +4 -4
  90. package/src/docs/http-guide.ts +1 -1
  91. package/src/docs/mcp-guide.ts +41 -71
  92. package/src/docs/resolve.ts +1 -1
  93. package/src/docs/save.ts +1 -1
  94. package/src/exports/cli.ts +1 -1
  95. package/src/index.ts +1 -1
  96. package/src/skill/generate.ts +26 -45
  97. package/src/skill/install.ts +18 -38
  98. package/src/skill/naming.ts +3 -27
  99. package/src/test/integration/config.test.ts +3 -3
  100. package/src/test/integration/mcp.test.ts +4 -4
  101. package/{examples/mcp-test.ts → src/test/mcp-integration-fixture.ts} +20 -22
  102. package/src/configure/artifacts/target-mcp-cli.ts +0 -127
  103. package/src/configure/artifacts/targets/chatgpt-mcp.ts +0 -12
  104. package/src/configure/artifacts/targets/claude-code-mcp.ts +0 -15
  105. package/src/configure/artifacts/targets/claude-desktop-mcp.ts +0 -12
  106. package/src/configure/artifacts/targets/claude-skill.ts +0 -16
  107. package/src/configure/artifacts/targets/codex-mcp.ts +0 -25
  108. package/src/configure/artifacts/targets/codex-skill.ts +0 -14
  109. package/src/configure/artifacts/targets/cursor-mcp.ts +0 -15
  110. package/src/configure/artifacts/targets/cursor-skill.ts +0 -16
  111. package/src/configure/artifacts/targets/openclaw-mcp.ts +0 -25
  112. package/src/configure/artifacts/targets/openclaw-skill.ts +0 -17
  113. package/src/configure/artifacts/targets/opencode-mcp.ts +0 -96
  114. package/src/configure/artifacts/targets/opencode-skill.ts +0 -15
  115. /package/examples/{full-example → full-example-json}/src/commands/render-json/__generated__/RenderJsonInputSchema.json +0 -0
  116. /package/examples/{full-example → full-example-json}/src/commands/render-json/__generated__/index.ts +0 -0
  117. /package/examples/{full-example → full-example-json}/src/commands/render-json/command.test.ts +0 -0
  118. /package/examples/{full-example → full-example-json}/src/commands/render-json/command.ts +0 -0
  119. /package/examples/{full-example → full-example-json}/src/commands/render-json/types.ts +0 -0
  120. /package/examples/{full-example → full-example-json}/src/commands/status/__generated__/StatusJsonOutputSchema.json +0 -0
  121. /package/examples/{full-example → full-example-json}/src/commands/status/__generated__/index.ts +0 -0
  122. /package/examples/{full-example → full-example-json}/src/commands/status/types.ts +0 -0
  123. /package/examples/{full-example → full-example-json}/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +0 -0
  124. /package/examples/{full-example → full-example-json}/src/commands/workspaces/__generated__/index.ts +0 -0
  125. /package/examples/{full-example → full-example-json}/src/commands/workspaces/command.test.ts +0 -0
  126. /package/examples/{full-example → full-example-json}/src/commands/workspaces/command.ts +0 -0
  127. /package/examples/{full-example → full-example-json}/src/commands/workspaces/types.ts +0 -0
  128. /package/examples/{full-example → full-example-json}/src/db/index.test.ts +0 -0
  129. /package/examples/{full-example → full-example-json}/src/db/index.ts +0 -0
  130. /package/examples/{full-example → full-example-json}/src/db/migrate.test.ts +0 -0
  131. /package/examples/{full-example → full-example-json}/src/db/migrate.ts +0 -0
  132. /package/examples/{full-example → full-example-json}/src/db/migrations/001_workspaces.sql +0 -0
  133. /package/examples/{full-example → full-example-json}/src/db/tables/workspaces.ts +0 -0
  134. /package/examples/{full-example → full-example-json}/src/types/argsbarg.d.ts +0 -0
@@ -13,10 +13,7 @@ import {
13
13
  resolveMcpSchemaUri,
14
14
  sanitizeToolSegment,
15
15
  } from "../mcp/tools.ts";
16
- import type { SkillTarget } from "./naming.ts";
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 given target. */
107
- function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): string {
108
- const name = skillFrontmatterName(root.key, target);
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 Cursor or Claude Code. */
247
- export function generateSkillBundle(root: CliProgram, target: SkillTarget): SkillBundle {
248
- const dirName = skillDirNameForTarget(root.key, target);
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, target, dirName),
232
+ skillMd: buildSkillMd(root, dirName),
252
233
  referenceMd: buildReferenceMd(root),
253
234
  };
254
235
  }
@@ -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, type SkillTarget } from "./generate.ts";
5
+ import { generateSkillBundle } from "./generate.ts";
6
6
  import { applySkillInstallHints } from "./hint.ts";
7
+ import { skillDirName } from "./naming.ts";
7
8
 
8
- export { skillDirNameForTarget, skillFrontmatterName, skillSlug } from "./naming.ts";
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
- function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean): string {
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
- }
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, target: SkillTarget, opts: SkillInstallOpts): string[] {
34
- const bundle = generateSkillBundle(root, target);
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 = resolveSkillDir(target, bundle.dirName, opts.global ?? false);
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, "SKILL.md");
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
- /** Maps install action kind to skill target. */
57
- export function skillTargetFromActionKind(kind: string): SkillTarget | undefined {
58
- switch (kind) {
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
  }
@@ -1,28 +1,4 @@
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);
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: "examples/mcp-test.ts", env: { ARGS_TEST_SECRET: "" } },
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: "examples/mcp-test.ts", env: { ARGS_TEST_SECRET: "sekrit" } },
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: "examples/mcp-test.ts",
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: "examples/mcp-test.ts",
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: "examples/mcp-test.ts" },
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: "examples/mcp-test.ts" },
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: "examples/mcp-test.ts" },
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 integration tests only.
3
+ MCP integration test fixture for subprocess tests (not a public example).
4
4
  */
5
5
 
6
- import { Cli, CliOptionKind, type CliProgram } from "../src/index";
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
- });