argsbarg 7.0.11 → 7.1.1

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 (93) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/README.md +3 -2
  3. package/docs/ai-skills.md +2 -2
  4. package/docs/cli-program.md +3 -5
  5. package/docs/developing.md +3 -3
  6. package/docs/mcp.md +21 -8
  7. package/docs/output-schema.md +1 -1
  8. package/examples/full-example/AGENTS.md +20 -12
  9. package/examples/full-example/README.md +57 -12
  10. package/examples/full-example/biome.json +5 -4
  11. package/examples/full-example/bunfig.toml +3 -0
  12. package/examples/full-example/justfile +22 -27
  13. package/examples/full-example-json/AGENTS.md +14 -12
  14. package/examples/full-example-json/README.md +63 -15
  15. package/examples/full-example-json/biome.json +1 -3
  16. package/examples/full-example-json/bunfig.toml +3 -0
  17. package/examples/full-example-json/justfile +22 -27
  18. package/examples/mcp-plugin/.claude-plugin/marketplace.json +13 -0
  19. package/examples/mcp-plugin/.claude-plugin/plugin.json +10 -0
  20. package/examples/mcp-plugin/.cursor/hooks/run-tests-on-stop.ts +56 -0
  21. package/examples/mcp-plugin/.cursor/hooks.json +12 -0
  22. package/examples/mcp-plugin/.cursor-plugin/plugin.json +11 -0
  23. package/examples/mcp-plugin/.mcp.json +6 -0
  24. package/examples/mcp-plugin/AGENTS.md +90 -0
  25. package/examples/mcp-plugin/CLAUDE.md +1 -0
  26. package/examples/mcp-plugin/README.md +72 -0
  27. package/examples/mcp-plugin/biome.json +20 -0
  28. package/examples/mcp-plugin/bun.lock +48 -0
  29. package/examples/mcp-plugin/bunfig.toml +3 -0
  30. package/examples/mcp-plugin/docs/README.md +27 -0
  31. package/examples/mcp-plugin/docs/cli-schema.json +2085 -0
  32. package/examples/mcp-plugin/docs/cli.md +2026 -0
  33. package/examples/mcp-plugin/docs/http.md +92 -0
  34. package/examples/mcp-plugin/docs/mcp.md +116 -0
  35. package/examples/mcp-plugin/docs/openapi.json +1243 -0
  36. package/examples/mcp-plugin/justfile +89 -0
  37. package/examples/mcp-plugin/mcp.json +8 -0
  38. package/examples/mcp-plugin/package.json +23 -0
  39. package/examples/mcp-plugin/scripts/create-identity.ts +12 -0
  40. package/examples/mcp-plugin/scripts/mcp.mjs +11106 -0
  41. package/examples/mcp-plugin/scripts/release.ts +225 -0
  42. package/examples/mcp-plugin/skills/mcp-plugin/SKILL.md +59 -0
  43. package/examples/mcp-plugin/src/commands/echo/command.ts +26 -0
  44. package/examples/mcp-plugin/src/commands/render-json/__generated__/RenderJsonInputSchema.json +15 -0
  45. package/examples/mcp-plugin/src/commands/render-json/__generated__/index.ts +5 -0
  46. package/examples/mcp-plugin/src/commands/render-json/command.test.ts +46 -0
  47. package/examples/mcp-plugin/src/commands/render-json/command.ts +30 -0
  48. package/examples/mcp-plugin/src/commands/render-json/types.ts +9 -0
  49. package/examples/mcp-plugin/src/commands/status/__generated__/StatusJsonOutputSchema.json +15 -0
  50. package/examples/mcp-plugin/src/commands/status/__generated__/index.ts +5 -0
  51. package/examples/mcp-plugin/src/commands/status/command.test.ts +10 -0
  52. package/examples/mcp-plugin/src/commands/status/command.ts +28 -0
  53. package/examples/mcp-plugin/src/commands/status/types.ts +6 -0
  54. package/examples/mcp-plugin/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +15 -0
  55. package/examples/mcp-plugin/src/commands/workspaces/__generated__/index.ts +5 -0
  56. package/examples/mcp-plugin/src/commands/workspaces/command.test.ts +58 -0
  57. package/examples/mcp-plugin/src/commands/workspaces/command.ts +94 -0
  58. package/examples/mcp-plugin/src/commands/workspaces/types.ts +6 -0
  59. package/examples/mcp-plugin/src/db/index.test.ts +86 -0
  60. package/examples/mcp-plugin/src/db/index.ts +77 -0
  61. package/examples/mcp-plugin/src/db/tables/workspaces.ts +58 -0
  62. package/examples/mcp-plugin/src/index.ts +10 -0
  63. package/examples/mcp-plugin/src/program.ts +32 -0
  64. package/examples/mcp-plugin/src/types/argsbarg.d.ts +11 -0
  65. package/examples/mcp-plugin/src/types/md.d.ts +4 -0
  66. package/examples/mcp-plugin/tsconfig.json +17 -0
  67. package/index.d.ts +12 -0
  68. package/package.json +1 -1
  69. package/src/builtins/mcp.ts +1 -1
  70. package/src/cli-tool/create.test.ts +5 -0
  71. package/src/cli-tool/create.ts +9 -1
  72. package/src/cli-tool/program.ts +1 -1
  73. package/src/config/manifest.ts +56 -0
  74. package/src/config/resolve.test.ts +12 -12
  75. package/src/configure/configure.test.ts +5 -3
  76. package/src/core/parse.test.ts +6 -5
  77. package/src/core/types.ts +12 -0
  78. package/src/exports/mcp.ts +12 -0
  79. package/src/headless/tool-call.test.ts +32 -0
  80. package/src/headless/tool-call.ts +19 -8
  81. package/src/mcp/bundle.ts +15 -5
  82. package/src/mcp/claude.test.ts +2 -7
  83. package/src/mcp/claude.ts +52 -68
  84. package/src/mcp/cursor.test.ts +151 -0
  85. package/src/mcp/cursor.ts +157 -0
  86. package/src/mcp/hidden-mcpb.test.ts +34 -1
  87. package/src/mcp/plugin-shared.ts +107 -0
  88. package/src/mcp/result.ts +5 -1
  89. package/src/mcp/server.ts +3 -2
  90. package/src/test/fixtures.ts +13 -0
  91. package/src/test/integration/mcp.test.ts +8 -7
  92. package/examples/full-example/scripts/print-identity.ts +0 -28
  93. package/examples/full-example-json/scripts/print-identity.ts +0 -28
@@ -222,11 +222,44 @@ describe("mcp bundle", () => {
222
222
  }
223
223
  });
224
224
 
225
+ /** RunMcpBundle with cursorPlugin only prints plugin path. */
226
+ test("runMcpBundle with cursorPlugin only prints plugin path", () => {
227
+ const work = mkdtempSync(join(tmpdir(), "mcpb-run-cursor-"));
228
+ const stdout: string[] = [];
229
+ const orig = process.stdout.write.bind(process.stdout);
230
+ process.stdout.write = ((chunk: string | Uint8Array) => {
231
+ stdout.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
232
+ return true;
233
+ }) as typeof process.stdout.write;
234
+ const fixture: CliProgram = {
235
+ ...hiddenFixture,
236
+ mcpServer: { enabled: true, cursorPlugin: true },
237
+ };
238
+ try {
239
+ const dist = join(work, "dist");
240
+ mkdirSync(dist, { recursive: true });
241
+ writeFileSync(join(dist, "myapp"), "#!/bin/sh\n", { mode: 0o755 });
242
+ const prevCwd = process.cwd();
243
+ process.chdir(work);
244
+ try {
245
+ runMcpBundle(fixture);
246
+ } finally {
247
+ process.chdir(prevCwd);
248
+ }
249
+ const lines = stdout.join("").trim().split("\n");
250
+ expect(lines).toHaveLength(1);
251
+ expect(lines[0]).toContain("cursor-plugin");
252
+ } finally {
253
+ process.stdout.write = orig;
254
+ rmSync(work, { recursive: true, force: true });
255
+ }
256
+ });
257
+
225
258
  test("runMcpBundle errors when no bundle flags enabled", () => {
226
259
  const fixture: CliProgram = {
227
260
  ...hiddenFixture,
228
261
  mcpServer: { enabled: true },
229
262
  };
230
- expect(() => runMcpBundle(fixture)).toThrow(/mcpd/);
263
+ expect(() => runMcpBundle(fixture)).toThrow(/cursorPlugin/);
231
264
  });
232
265
  });
@@ -0,0 +1,107 @@
1
+ /*
2
+ Shared utilities and staging helpers for Claude Code and Cursor plugin packages.
3
+ Internal module — not exported from index.ts.
4
+ */
5
+
6
+ import {
7
+ cpSync,
8
+ type Dirent,
9
+ existsSync,
10
+ mkdirSync,
11
+ readdirSync,
12
+ readFileSync,
13
+ statSync,
14
+ writeFileSync,
15
+ } from "node:fs";
16
+ import { join, relative, resolve } from "node:path";
17
+ import type { CliMcpBundleConfig, CliProgram } from "../core/types.ts";
18
+ import { generatePluginSkillBundle } from "../skill/generate.ts";
19
+ import { applyPluginSkillHint } from "../skill/hint.ts";
20
+ import { sanitizeToolSegment } from "./tools.ts";
21
+ import type { ZipFileEntry } from "./zip.ts";
22
+
23
+ /**
24
+ * Traverses a staging directory recursively and gathers ZIP entries preserving executable permissions.
25
+ */
26
+ export function collectZipEntries(
27
+ /** Directory containing the staged files. */
28
+ rootDir: string,
29
+ /** Subdirectory currently being traversed (defaults to rootDir). */
30
+ dir = rootDir,
31
+ ): ZipFileEntry[] {
32
+ const entries: ZipFileEntry[] = [];
33
+ for (const ent of readdirSync(dir, { withFileTypes: true }) as Dirent[]) {
34
+ const full = join(dir, ent.name);
35
+ if (ent.isDirectory()) {
36
+ entries.push(...collectZipEntries(rootDir, full));
37
+ continue;
38
+ }
39
+ if (!ent.isFile()) {
40
+ continue;
41
+ }
42
+ const rel = relative(rootDir, full).split("\\").join("/");
43
+ const stMode = statSync(full).mode;
44
+ const entry: ZipFileEntry = { name: rel, data: readFileSync(full) };
45
+ if (stMode & 0o111) {
46
+ entry.unixMode = stMode;
47
+ }
48
+ entries.push(entry);
49
+ }
50
+ return entries;
51
+ }
52
+
53
+ /**
54
+ * Returns author metadata from bundle config or fallback defaults.
55
+ */
56
+ export function defaultAuthor(
57
+ /** Optional bundle configuration. */
58
+ bundle?: CliMcpBundleConfig,
59
+ ): {
60
+ name: string;
61
+ email?: string;
62
+ url?: string;
63
+ } {
64
+ return bundle?.author ?? { name: "Unknown" };
65
+ }
66
+
67
+ /**
68
+ * Generates kebab-case plugin name for plugin manifests.
69
+ */
70
+ export function pluginName(
71
+ /** CLI program schema. */
72
+ program: CliProgram,
73
+ ): string {
74
+ return program.key
75
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
76
+ .replace(/[^a-zA-Z0-9]+/g, "-")
77
+ .replace(/^-+|-+$/g, "")
78
+ .toLowerCase();
79
+ }
80
+
81
+ /**
82
+ * Stages repository skill directory if present, or writes a generated MCP routing skill.
83
+ */
84
+ export function stagePluginSkills(
85
+ /** Plugin staging root directory. */
86
+ pluginRoot: string,
87
+ /** CLI program schema. */
88
+ program: CliProgram,
89
+ /** Working directory containing repository sources. */
90
+ cwd: string,
91
+ ): void {
92
+ const dirName = sanitizeToolSegment(program.key);
93
+ const repoSkillDir = program.mcpServer?.bundle?.skillsDir
94
+ ? resolve(cwd, program.mcpServer.bundle.skillsDir)
95
+ : join(cwd, "skills", dirName);
96
+
97
+ if (existsSync(repoSkillDir) && statSync(repoSkillDir).isDirectory()) {
98
+ const targetDir = join(pluginRoot, "skills", dirName);
99
+ mkdirSync(join(targetDir, ".."), { recursive: true });
100
+ cpSync(repoSkillDir, targetDir, { recursive: true });
101
+ } else {
102
+ const bundle = generatePluginSkillBundle(program);
103
+ const skillMd = applyPluginSkillHint(program, bundle.skillMd);
104
+ mkdirSync(join(pluginRoot, "skills", bundle.dirName), { recursive: true });
105
+ writeFileSync(join(pluginRoot, "skills", bundle.dirName, "SKILL.md"), skillMd);
106
+ }
107
+ }
package/src/mcp/result.ts CHANGED
@@ -38,6 +38,7 @@ export interface McpBinaryRespondContent {
38
38
  export function buildToolCallSuccessFromResponse(response: CliRespondOptions): McpToolCallSuccess {
39
39
  const { body, contentType = "application/json; charset=utf-8" } = response;
40
40
  let structuredContent: unknown;
41
+ let text = "";
41
42
 
42
43
  if (body instanceof Uint8Array) {
43
44
  structuredContent = {
@@ -45,17 +46,20 @@ export function buildToolCallSuccessFromResponse(response: CliRespondOptions): M
45
46
  contentType,
46
47
  encoding: "base64",
47
48
  } satisfies McpBinaryRespondContent;
49
+ text = `[Binary data: ${contentType}, ${body.length} bytes]`;
48
50
  } else if (typeof body === "string") {
49
51
  structuredContent = {
50
52
  content: body,
51
53
  contentType,
52
54
  } satisfies McpStringRespondContent;
55
+ text = body;
53
56
  } else {
54
57
  structuredContent = body;
58
+ text = typeof body === "object" && body !== null ? JSON.stringify(body, null, 2) : String(body ?? "");
55
59
  }
56
60
 
57
61
  return {
58
- content: [{ type: "text", text: "" }],
62
+ content: [{ type: "text", text }],
59
63
  structuredContent,
60
64
  isError: false,
61
65
  };
package/src/mcp/server.ts CHANGED
@@ -253,8 +253,9 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
253
253
  /** Runs the MCP NDJSON read loop on stdin until EOF. */
254
254
  export async function mcpServeStdioLoop(cli: Cli): Promise<void> {
255
255
  let buffer = "";
256
- for await (const chunk of Bun.stdin.stream()) {
257
- buffer += new TextDecoder().decode(chunk);
256
+ const decoder = new TextDecoder();
257
+ for await (const chunk of process.stdin) {
258
+ buffer += typeof chunk === "string" ? chunk : decoder.decode(chunk);
258
259
  let nl = buffer.indexOf("\n");
259
260
  while (nl !== -1) {
260
261
  const line = buffer.slice(0, nl).trim();
@@ -1,10 +1,23 @@
1
1
  import type { CliProgram } from "../core/types.ts";
2
2
  import { CliFallbackMode, CliOptionKind } from "../core/types.ts";
3
+ import type { McpToolDef } from "../mcp/tools.ts";
3
4
 
4
5
  export function testProgram(prog: Record<string, unknown> & { key: string; description: string }): CliProgram {
5
6
  return { version: "0.0.0", ...prog } as CliProgram;
6
7
  }
7
8
 
9
+ /** MCP tool with `name` from `collectMcpTools`, or throw. */
10
+ export function requireMcpTool(
11
+ /** Tools from `collectMcpTools`. */
12
+ tools: McpToolDef[],
13
+ /** MCP tool name to find. */
14
+ name: string,
15
+ ): McpToolDef {
16
+ const tool = tools.find((t) => t.name === name);
17
+ if (!tool) throw new Error(`expected MCP tool ${name}`);
18
+ return tool;
19
+ }
20
+
8
21
  export const nestedMcpFixture = testProgram({
9
22
  key: "nested.ts",
10
23
  description: "Nested groups demo.",
@@ -16,7 +16,7 @@ import {
16
16
  mcpToolName,
17
17
  sanitizeToolSegment,
18
18
  } from "../../mcp/tools.ts";
19
- import { mcpRequest, nestedMcpFixture, testProgram } from "../fixtures.ts";
19
+ import { mcpRequest, nestedMcpFixture, requireMcpTool, testProgram } from "../fixtures.ts";
20
20
 
21
21
  test("sanitizeToolSegment normalizes dotted app keys", () => {
22
22
  expect(sanitizeToolSegment("minimal.ts")).toBe("minimal_ts");
@@ -39,7 +39,7 @@ test("collectMcpTools lists user leaf commands only", () => {
39
39
  const tools = collectMcpTools(nestedMcpFixture);
40
40
  const names = tools.map((t) => t.name);
41
41
  expect(names).toContain("stat_owner_lookup");
42
- const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
42
+ const lookup = requireMcpTool(tools, "stat_owner_lookup");
43
43
  expect(lookup.description).toBe("stat owner lookup — Resolve owner info.");
44
44
  expect(names).toContain("read");
45
45
  expect(names).not.toContain("hidden");
@@ -155,7 +155,7 @@ test("outputSchema must be a JSON Schema object", () => {
155
155
 
156
156
  test("collectMcpTools uses leaf-local options in inputSchema", () => {
157
157
  const tools = collectMcpTools(nestedMcpFixture);
158
- const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
158
+ const lookup = requireMcpTool(tools, "stat_owner_lookup");
159
159
  const schema = lookup.inputSchema as { properties: Record<string, unknown>; required?: string[] };
160
160
  expect(schema.properties.json).toBeUndefined();
161
161
  expect(schema.properties["user-name"]).toBeDefined();
@@ -193,13 +193,13 @@ test("collectMcpTools includes outputSchema when set on leaf", () => {
193
193
 
194
194
  test("collectMcpTools omits outputSchema when leaf has none", () => {
195
195
  const tools = collectMcpTools(nestedMcpFixture);
196
- const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
196
+ const lookup = requireMcpTool(tools, "stat_owner_lookup");
197
197
  expect(lookup.outputSchema).toBeUndefined();
198
198
  });
199
199
 
200
200
  test("mcpToolCallToArgv builds nested lookup argv", () => {
201
201
  const tools = collectMcpTools(nestedMcpFixture);
202
- const lookup = tools.find((t) => t.name === "stat_owner_lookup")!;
202
+ const lookup = requireMcpTool(tools, "stat_owner_lookup");
203
203
  const argv = mcpToolCallToArgv(nestedMcpFixture, lookup, {
204
204
  "user-name": "alice",
205
205
  path: "./x",
@@ -209,7 +209,7 @@ test("mcpToolCallToArgv builds nested lookup argv", () => {
209
209
 
210
210
  test("mcpToolCallToArgv expands varargs positionals", () => {
211
211
  const tools = collectMcpTools(nestedMcpFixture);
212
- const read = tools.find((t) => t.name === "read")!;
212
+ const read = requireMcpTool(tools, "read");
213
213
  const argv = mcpToolCallToArgv(nestedMcpFixture, read, { files: ["a", "b"] });
214
214
  expect(argv).toEqual(["read", "a", "b"]);
215
215
  });
@@ -318,7 +318,7 @@ test("buildToolCallSuccessFromResponse maps JSON object", () => {
318
318
  const result = buildToolCallSuccessFromResponse({ body: { a: 1 } });
319
319
  expect(result.isError).toBe(false);
320
320
  expect(result.structuredContent).toEqual({ a: 1 });
321
- expect(result.content[0]?.text).toBe("");
321
+ expect(result.content[0]?.text).toBe(JSON.stringify({ a: 1 }, null, 2));
322
322
  });
323
323
 
324
324
  test("buildToolCallSuccessFromResponse maps string body", () => {
@@ -330,6 +330,7 @@ test("buildToolCallSuccessFromResponse maps string body", () => {
330
330
  content: "lookup user=x",
331
331
  contentType: "text/plain; charset=utf-8",
332
332
  });
333
+ expect(result.content[0]?.text).toBe("lookup user=x");
333
334
  });
334
335
 
335
336
  test("buildToolCallSuccessFromResponse maps binary body as base64", () => {
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env bun
2
- /** Print a field from scripts/create-identity.ts for justfile backticks. */
3
-
4
- import { createIdentity } from "./create-identity.ts";
5
-
6
- const field = process.argv[2];
7
- if (!field) {
8
- console.error("Usage: bun scripts/print-identity.ts <key|className|tap|tapOrg|tapRepo|releaseRepo|envPrefix>");
9
- process.exit(1);
10
- }
11
-
12
- const [tapOrg, tapRepo] = createIdentity.tap.split("/");
13
- const values: Record<string, string> = {
14
- key: createIdentity.key,
15
- className: createIdentity.className,
16
- tap: createIdentity.tap,
17
- tapOrg: tapOrg ?? "",
18
- tapRepo: tapRepo ?? "",
19
- envPrefix: createIdentity.envPrefix,
20
- releaseRepo: createIdentity.releaseRepo,
21
- };
22
-
23
- const value = values[field];
24
- if (value === undefined) {
25
- console.error(`Unknown field: ${field}`);
26
- process.exit(1);
27
- }
28
- process.stdout.write(value);
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env bun
2
- /** Print a field from scripts/create-identity.ts for justfile backticks. */
3
-
4
- import { createIdentity } from "./create-identity.ts";
5
-
6
- const field = process.argv[2];
7
- if (!field) {
8
- console.error("Usage: bun scripts/print-identity.ts <key|className|tap|tapOrg|tapRepo|releaseRepo|envPrefix>");
9
- process.exit(1);
10
- }
11
-
12
- const [tapOrg, tapRepo] = createIdentity.tap.split("/");
13
- const values: Record<string, string> = {
14
- key: createIdentity.key,
15
- className: createIdentity.className,
16
- tap: createIdentity.tap,
17
- tapOrg: tapOrg ?? "",
18
- tapRepo: tapRepo ?? "",
19
- envPrefix: createIdentity.envPrefix,
20
- releaseRepo: createIdentity.releaseRepo,
21
- };
22
-
23
- const value = values[field];
24
- if (value === undefined) {
25
- console.error(`Unknown field: ${field}`);
26
- process.exit(1);
27
- }
28
- process.stdout.write(value);