argsbarg 7.0.10 → 7.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +26 -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 +20 -7
  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/plugin.json +11 -0
  19. package/examples/mcp-plugin/.cursor/hooks/run-tests-on-stop.ts +56 -0
  20. package/examples/mcp-plugin/.cursor/hooks.json +12 -0
  21. package/examples/mcp-plugin/.cursor-plugin/plugin.json +12 -0
  22. package/examples/mcp-plugin/.mcp.json +6 -0
  23. package/examples/mcp-plugin/AGENTS.md +90 -0
  24. package/examples/mcp-plugin/CLAUDE.md +1 -0
  25. package/examples/mcp-plugin/README.md +70 -0
  26. package/examples/mcp-plugin/biome.json +20 -0
  27. package/examples/mcp-plugin/bun.lock +48 -0
  28. package/examples/mcp-plugin/bunfig.toml +3 -0
  29. package/examples/mcp-plugin/docs/README.md +27 -0
  30. package/examples/mcp-plugin/docs/cli-schema.json +2085 -0
  31. package/examples/mcp-plugin/docs/cli.md +2026 -0
  32. package/examples/mcp-plugin/docs/http.md +92 -0
  33. package/examples/mcp-plugin/docs/mcp.md +116 -0
  34. package/examples/mcp-plugin/docs/openapi.json +1243 -0
  35. package/examples/mcp-plugin/justfile +89 -0
  36. package/examples/mcp-plugin/mcp.json +8 -0
  37. package/examples/mcp-plugin/package.json +23 -0
  38. package/examples/mcp-plugin/scripts/create-identity.ts +12 -0
  39. package/examples/mcp-plugin/scripts/mcp.mjs +11106 -0
  40. package/examples/mcp-plugin/scripts/release.ts +225 -0
  41. package/examples/mcp-plugin/skills/mcp-plugin/SKILL.md +59 -0
  42. package/examples/mcp-plugin/src/commands/echo/command.ts +26 -0
  43. package/examples/mcp-plugin/src/commands/render-json/__generated__/RenderJsonInputSchema.json +15 -0
  44. package/examples/mcp-plugin/src/commands/render-json/__generated__/index.ts +5 -0
  45. package/examples/mcp-plugin/src/commands/render-json/command.test.ts +46 -0
  46. package/examples/mcp-plugin/src/commands/render-json/command.ts +30 -0
  47. package/examples/mcp-plugin/src/commands/render-json/types.ts +9 -0
  48. package/examples/mcp-plugin/src/commands/status/__generated__/StatusJsonOutputSchema.json +15 -0
  49. package/examples/mcp-plugin/src/commands/status/__generated__/index.ts +5 -0
  50. package/examples/mcp-plugin/src/commands/status/command.test.ts +10 -0
  51. package/examples/mcp-plugin/src/commands/status/command.ts +28 -0
  52. package/examples/mcp-plugin/src/commands/status/types.ts +6 -0
  53. package/examples/mcp-plugin/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +15 -0
  54. package/examples/mcp-plugin/src/commands/workspaces/__generated__/index.ts +5 -0
  55. package/examples/mcp-plugin/src/commands/workspaces/command.test.ts +58 -0
  56. package/examples/mcp-plugin/src/commands/workspaces/command.ts +94 -0
  57. package/examples/mcp-plugin/src/commands/workspaces/types.ts +6 -0
  58. package/examples/mcp-plugin/src/db/index.test.ts +86 -0
  59. package/examples/mcp-plugin/src/db/index.ts +77 -0
  60. package/examples/mcp-plugin/src/db/tables/workspaces.ts +58 -0
  61. package/examples/mcp-plugin/src/index.ts +10 -0
  62. package/examples/mcp-plugin/src/program.ts +32 -0
  63. package/examples/mcp-plugin/src/types/argsbarg.d.ts +11 -0
  64. package/examples/mcp-plugin/src/types/md.d.ts +4 -0
  65. package/examples/mcp-plugin/tsconfig.json +17 -0
  66. package/index.d.ts +12 -0
  67. package/package.json +1 -1
  68. package/src/builtins/mcp.ts +1 -1
  69. package/src/cli-tool/create.test.ts +5 -0
  70. package/src/cli-tool/create.ts +9 -1
  71. package/src/config/manifest.ts +56 -0
  72. package/src/config/resolve.test.ts +12 -12
  73. package/src/configure/configure.test.ts +5 -3
  74. package/src/core/parse.test.ts +6 -5
  75. package/src/core/types.ts +12 -0
  76. package/src/exports/mcp.ts +12 -0
  77. package/src/help.test.ts +36 -0
  78. package/src/help.ts +32 -19
  79. package/src/mcp/bundle.ts +12 -3
  80. package/src/mcp/claude.test.ts +2 -7
  81. package/src/mcp/claude.ts +49 -67
  82. package/src/mcp/cursor.test.ts +151 -0
  83. package/src/mcp/cursor.ts +155 -0
  84. package/src/mcp/hidden-mcpb.test.ts +34 -1
  85. package/src/mcp/plugin-shared.ts +107 -0
  86. package/src/mcp/result.ts +5 -1
  87. package/src/mcp/server.ts +3 -2
  88. package/src/test/fixtures.ts +13 -0
  89. package/src/test/integration/mcp.test.ts +8 -7
  90. package/examples/full-example/scripts/print-identity.ts +0 -28
  91. package/examples/full-example-json/scripts/print-identity.ts +0 -28
@@ -0,0 +1,86 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import type { ReadinessContext } from "argsbarg";
3
+ import { AppDb } from ".";
4
+
5
+ describe("AppDb", () => {
6
+ let appDb: AppDb;
7
+
8
+ beforeEach(() => {
9
+ appDb = AppDb.open();
10
+ });
11
+
12
+ afterEach(() => {
13
+ appDb.close();
14
+ });
15
+
16
+ test("workspace CRUD round trip", () => {
17
+ const workspaces = appDb.workspaces;
18
+ expect(workspaces.list()).toEqual([]);
19
+ const created = workspaces.create("alpha");
20
+ expect(workspaces.get(created.id)).toEqual(created);
21
+ expect(workspaces.list()).toEqual([created]);
22
+ expect(workspaces.patch(created.id, "beta")).toEqual({ ...created, name: "beta" });
23
+ expect(workspaces.replace(created.id, "gamma")).toEqual({ id: created.id, name: "gamma" });
24
+ expect(workspaces.delete(created.id)).toBe(true);
25
+ expect(workspaces.get(created.id)).toBeUndefined();
26
+ });
27
+
28
+ test("ping succeeds on open database", () => {
29
+ expect(() => appDb.ping()).not.toThrow();
30
+ });
31
+ });
32
+
33
+ describe("AppDb.openWithRetry", () => {
34
+ test("opens an in-memory database", () => {
35
+ const appDb = AppDb.openWithRetry(1);
36
+ try {
37
+ appDb.workspaces.create("retry-ok");
38
+ expect(appDb.workspaces.list()).toHaveLength(1);
39
+ } finally {
40
+ appDb.close();
41
+ }
42
+ });
43
+ });
44
+
45
+ describe("AppDb.attach", () => {
46
+ test("sets ctx.locals.db", () => {
47
+ const locals = {} as import("argsbarg").CliLocals;
48
+ AppDb.attach({ locals, invocation: "cli" });
49
+ locals.db.workspaces.create("attached");
50
+ expect(locals.db.workspaces.list()).toHaveLength(1);
51
+ });
52
+ });
53
+
54
+ describe("AppDb.checkReadiness", () => {
55
+ test("returns false before server database is initialized", () => {
56
+ const runtime = {
57
+ state: {},
58
+ program: { key: "t", description: "d" },
59
+ surface: "http" as const,
60
+ };
61
+ const ctx = {
62
+ program: runtime.program,
63
+ surface: "http" as const,
64
+ appConfig: { read: () => ({}) },
65
+ runtime,
66
+ } as unknown as ReadinessContext;
67
+ expect(AppDb.checkReadiness(ctx)).toBe(false);
68
+ });
69
+
70
+ test("returns true when sqlite responds", () => {
71
+ AppDb.resetForTests();
72
+ const runtime = {
73
+ state: { db: AppDb.open() },
74
+ program: { key: "t", description: "d" },
75
+ surface: "http" as const,
76
+ };
77
+ const ctx = {
78
+ program: runtime.program,
79
+ surface: "http" as const,
80
+ appConfig: { read: () => ({}) },
81
+ runtime,
82
+ } as unknown as ReadinessContext;
83
+ expect(AppDb.checkReadiness(ctx)).toBe(true);
84
+ (runtime.state.db as AppDb).close();
85
+ });
86
+ });
@@ -0,0 +1,77 @@
1
+ /*
2
+ App-wide in-memory database store.
3
+ */
4
+
5
+ import type { InvokeHookContext, ReadinessContext } from "argsbarg";
6
+ import { WorkspacesTable } from "./tables/workspaces.ts";
7
+
8
+ /**
9
+ * Single in-memory store for this app.
10
+ */
11
+ export class AppDb {
12
+ /** CLI singleton; server invocations use `runtime.state.db` instead. */
13
+ private static db: AppDb | undefined;
14
+
15
+ /** Workspace rows and queries for this connection. */
16
+ readonly workspaces: WorkspacesTable;
17
+
18
+ constructor() {
19
+ this.workspaces = new WorkspacesTable();
20
+ }
21
+
22
+ /** Open a fresh in-memory database. */
23
+ static open(): AppDb {
24
+ return new AppDb();
25
+ }
26
+
27
+ /** Open with bounded backoff (HTTP/MCP startup). */
28
+ static openWithRetry(_delayMs = 200): AppDb {
29
+ return AppDb.open();
30
+ }
31
+
32
+ /** Shared database for non-server invocations (lazy CLI singleton). */
33
+ static openDb(): AppDb {
34
+ AppDb.db ??= AppDb.open();
35
+ return AppDb.db;
36
+ }
37
+
38
+ /** Replace the CLI singleton with a fresh in-memory database (tests). */
39
+ static resetForTests(): void {
40
+ AppDb.db = AppDb.open();
41
+ }
42
+
43
+ /** Close connection resources. */
44
+ close(): void {}
45
+
46
+ /** Ping to verify readiness. */
47
+ ping(): void {}
48
+
49
+ /**
50
+ * Wire `ctx.locals.db` before handlers run.
51
+ * Server runtimes open with retry into `runtime.state.db`; CLI uses {@link openDb}.
52
+ */
53
+ static attach(
54
+ ctx: Pick<InvokeHookContext, "locals" | "invocation"> & { runtime?: InvokeHookContext["runtime"] },
55
+ ): void {
56
+ if (ctx.runtime && (ctx.invocation === "http" || ctx.invocation === "mcp")) {
57
+ ctx.runtime.state.db ??= AppDb.openWithRetry();
58
+ ctx.locals.db = ctx.runtime.state.db;
59
+ return;
60
+ }
61
+ ctx.locals.db = AppDb.openDb();
62
+ }
63
+
64
+ /** Readiness probe: ping `runtime.state.db` when the server database is open. */
65
+ static checkReadiness(ctx: ReadinessContext): boolean {
66
+ const appDb = ctx.runtime?.state?.db as AppDb | undefined;
67
+ if (!appDb) {
68
+ return false;
69
+ }
70
+ try {
71
+ appDb.ping();
72
+ return true;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,58 @@
1
+ /*
2
+ Workspace rows and in-memory CRUD operations.
3
+ */
4
+
5
+ /** One workspace row from the workspaces store. */
6
+ export interface Workspace {
7
+ id: string;
8
+ name: string;
9
+ }
10
+
11
+ /** In-memory workspace rows and CRUD queries. */
12
+ export class WorkspacesTable {
13
+ private readonly items = new Map<string, Workspace>();
14
+
15
+ /** All workspaces in insertion order. */
16
+ list(): Workspace[] {
17
+ return Array.from(this.items.values());
18
+ }
19
+
20
+ /** Lookup workspace by id, or undefined when missing. */
21
+ get(id: string): Workspace | undefined {
22
+ return this.items.get(id);
23
+ }
24
+
25
+ /** Create a workspace with a new id. */
26
+ create(name: string): Workspace {
27
+ const id = crypto.randomUUID();
28
+ const workspace: Workspace = { id, name };
29
+ this.items.set(id, workspace);
30
+ return workspace;
31
+ }
32
+
33
+ /** Replace workspace name when id exists; otherwise undefined. */
34
+ replace(id: string, name: string): Workspace | undefined {
35
+ if (!this.items.has(id)) {
36
+ return undefined;
37
+ }
38
+ const updated: Workspace = { id, name };
39
+ this.items.set(id, updated);
40
+ return updated;
41
+ }
42
+
43
+ /** Patch workspace name when id exists; otherwise undefined. */
44
+ patch(id: string, name: string): Workspace | undefined {
45
+ const existing = this.items.get(id);
46
+ if (!existing) {
47
+ return undefined;
48
+ }
49
+ const updated: Workspace = { ...existing, name };
50
+ this.items.set(id, updated);
51
+ return updated;
52
+ }
53
+
54
+ /** Delete workspace by id; true if deleted, false if not found. */
55
+ delete(id: string): boolean {
56
+ return this.items.delete(id);
57
+ }
58
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bun
2
+ /*
3
+ Thin CLI entry — delegates to argsbarg runtime.
4
+ */
5
+
6
+ import { Cli } from "argsbarg";
7
+ import { program } from "./program.ts";
8
+
9
+ const cli = new Cli(program);
10
+ await cli.run();
@@ -0,0 +1,32 @@
1
+ /*
2
+ Reference CliProgram — builtins on; command registration only.
3
+ */
4
+
5
+ import type { CliProgram } from "argsbarg";
6
+ import readmeText from "../README.md" with { type: "text" };
7
+ import { createIdentity } from "../scripts/create-identity.ts";
8
+ import { echoCommand } from "./commands/echo/command.ts";
9
+ import { renderJsonCommand } from "./commands/render-json/command.ts";
10
+ import { statusCommand } from "./commands/status/command.ts";
11
+ import { workspacesCommand } from "./commands/workspaces/command.ts";
12
+ import { AppDb } from "./db";
13
+
14
+ export const program = {
15
+ commands: [echoCommand, renderJsonCommand, statusCommand, workspacesCommand],
16
+ description: createIdentity.desc,
17
+ docs: {
18
+ topics: {
19
+ readme: {
20
+ text: readmeText,
21
+ },
22
+ },
23
+ },
24
+ hooks: {
25
+ beforeInvoke: AppDb.attach,
26
+ },
27
+ httpServer: { enabled: true },
28
+ key: createIdentity.key,
29
+ mcpServer: { enabled: true },
30
+ readiness: AppDb.checkReadiness,
31
+ version: "1.0.0",
32
+ } satisfies CliProgram;
@@ -0,0 +1,11 @@
1
+ import type { AppDb } from "../db/index.ts";
2
+
3
+ declare module "argsbarg" {
4
+ interface CliLocals {
5
+ db: AppDb;
6
+ }
7
+
8
+ interface ServerState {
9
+ db?: AppDb;
10
+ }
11
+ }
@@ -0,0 +1,4 @@
1
+ declare module "*.md" {
2
+ const text: string;
3
+ export default text;
4
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "allowImportingTsExtensions": true,
7
+ "resolveJsonModule": true,
8
+ "noEmit": true,
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "types": ["bun"],
12
+ "paths": {
13
+ "~/*": ["./src/*"]
14
+ }
15
+ },
16
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "scripts/**/*.ts"]
17
+ }
package/index.d.ts CHANGED
@@ -319,10 +319,20 @@ export interface CliMcpBundleConfig {
319
319
  email?: string;
320
320
  url?: string;
321
321
  };
322
+ /** Human-readable display name for plugin manifests. */
323
+ displayName?: string;
324
+ /** Homepage URL for plugin manifests. */
325
+ homepage?: string;
322
326
  /** Repo-relative path to a PNG icon copied into the bundle. */
323
327
  icon?: string;
328
+ /** Software license identifier (e.g. "MIT", "Apache-2.0"). */
329
+ license?: string;
324
330
  /** Manifest `long_description` (defaults to program description). */
325
331
  longDescription?: string;
332
+ /** Repository URL for plugin manifests. */
333
+ repository?: string;
334
+ /** Custom relative path to repository skills directory (defaults to `skills/<dirName>`). */
335
+ skillsDir?: string;
326
336
  }
327
337
  /**
328
338
  * Enables `myapp mcp` and MCP stdio server metadata (program root only).
@@ -340,6 +350,8 @@ export interface CliMcpServerConfig {
340
350
  mcpd?: boolean;
341
351
  /** When `true`, `mcp bundle` also writes `dist/claude-plugin/<name>.zip`. Default false. */
342
352
  claudePlugin?: boolean;
353
+ /** When `true`, `mcp bundle` also writes `dist/cursor-plugin/<name>.zip`. Default false. */
354
+ cursorPlugin?: boolean;
343
355
  /** Resource URI for schema export (default: `<sanitized root key>://schema`). */
344
356
  schemaResourceUri?: string;
345
357
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "7.0.10",
3
+ "version": "7.1.0",
4
4
  "main": "./src/index.ts",
5
5
  "module": "./src/index.ts",
6
6
  "dependencies": {
@@ -51,7 +51,7 @@ export function cliBuiltinMcpCommand(program: CliProgram): CliRouter {
51
51
 
52
52
  const bundle: CliLeaf = {
53
53
  key: "bundle",
54
- description: "Pack dist MCP artifacts (`.mcpb`, Claude Code plugin zip) from dist/<key>.",
54
+ description: "Pack dist MCP artifacts (`.mcpb`, Claude Code plugin zip, Cursor plugin zip) from dist/<key>.",
55
55
  handler: () => {},
56
56
  };
57
57
 
@@ -83,10 +83,15 @@ describe("argsbarg create", () => {
83
83
  const tree = renderCreateTree(baseOpts({ key: "testapp" }));
84
84
  expect(tree.has("justfile")).toBe(true);
85
85
  expect(tree.has("scripts/create-identity.ts")).toBe(true);
86
+ expect(tree.has("scripts/print-identity.ts")).toBe(false);
86
87
  expect(tree.has("skills/testapp/SKILL.md")).toBe(true);
87
88
  const identity = tree.get("scripts/create-identity.ts");
88
89
  expect(identity).toContain('key: "testapp"');
89
90
  expect(identity).toContain('template: "cli"');
91
+ const justfile = tree.get("justfile") ?? "";
92
+ expect(justfile).not.toContain("print-identity");
93
+ expect(justfile).toContain("testapp");
94
+ expect(justfile).toContain("local/testapp/testapp");
90
95
  expect(tree.has("src/commands/render-json/command.ts")).toBe(false);
91
96
  });
92
97
 
@@ -5,7 +5,7 @@ import { dirname, join, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import pkg from "../../package.json" with { type: "json" };
7
7
 
8
- export type CreateTemplateId = "cli" | "json";
8
+ export type CreateTemplateId = "cli" | "json" | "plugin";
9
9
 
10
10
  export interface CreateTemplateSpec {
11
11
  id: CreateTemplateId;
@@ -27,9 +27,16 @@ export const CREATE_TEMPLATES: CreateTemplateSpec[] = [
27
27
  displayName: "full-example-json",
28
28
  description: "Same shell plus @sg schemagen, input/outputSchema validation, JSON HTTP leaves, and REST CRUD demo.",
29
29
  },
30
+ {
31
+ id: "plugin",
32
+ dirName: "mcp-plugin",
33
+ displayName: "mcp-plugin",
34
+ description: "Agent MCP plugin copy template for Cursor and Claude Code marketplaces.",
35
+ },
30
36
  ];
31
37
 
32
38
  export function normalizeCreateTemplateId(value: string | undefined): CreateTemplateId {
39
+ if (value === "plugin" || value === "mcp-plugin") return "plugin";
33
40
  return value === "json" ? "json" : "cli";
34
41
  }
35
42
 
@@ -282,6 +289,7 @@ export function substituteTemplateContent(content: string, opts: CreateOptions):
282
289
  [tmpl.desc, opts.desc],
283
290
  [tmpl.className, opts.className],
284
291
  [tmpl.key, opts.key],
292
+ [`## ${tmpl.key} conventions`, `## ${opts.key} conventions`],
285
293
  [`**${tmpl.key} conventions:**`, `**${opts.key} conventions:**`],
286
294
  ["**App-specific conventions:**", `**${opts.key} conventions:**`],
287
295
  ];
@@ -60,3 +60,59 @@ export function buildPluginMcpEnvMapping(program: CliProgram): Record<string, st
60
60
  }
61
61
  return Object.keys(out).length > 0 ? out : undefined;
62
62
  }
63
+
64
+ /**
65
+ * Plugin mcp.json env mapping for Cursor from program.appConfig env entries.
66
+ */
67
+ export function buildCursorPluginMcpEnvMapping(
68
+ /** CLI program schema. */
69
+ program: CliProgram,
70
+ ): Record<string, string> | undefined {
71
+ const appConfig = program.appConfig;
72
+ if (!appConfig) {
73
+ return undefined;
74
+ }
75
+ const out: Record<string, string> = {};
76
+ for (const entry of Object.values(appConfig.entries)) {
77
+ if (!entry.env) {
78
+ continue;
79
+ }
80
+ out[entry.env] = `\${${entry.env}}`;
81
+ }
82
+ return Object.keys(out).length > 0 ? out : undefined;
83
+ }
84
+
85
+ /**
86
+ * Builds Cursor plugin variables schema from program.appConfig env-mapped entries.
87
+ */
88
+ export function buildCursorPluginVariables(
89
+ /** CLI program schema. */
90
+ program: CliProgram,
91
+ ): Record<string, unknown> | undefined {
92
+ const appConfig = program.appConfig;
93
+ if (!appConfig) {
94
+ return undefined;
95
+ }
96
+ const properties: Record<string, unknown> = {};
97
+ const required: string[] = [];
98
+ for (const entry of Object.values(appConfig.entries)) {
99
+ if (!entry.env) {
100
+ continue;
101
+ }
102
+ properties[entry.env] = {
103
+ type: "string",
104
+ ...(entry.description ? { description: entry.description } : {}),
105
+ };
106
+ if (entry.required) {
107
+ required.push(entry.env);
108
+ }
109
+ }
110
+ if (Object.keys(properties).length === 0) {
111
+ return undefined;
112
+ }
113
+ return {
114
+ type: "object",
115
+ properties,
116
+ ...(required.length > 0 ? { required } : {}),
117
+ };
118
+ }
@@ -120,9 +120,9 @@ describe("config/resolve", () => {
120
120
  const resolveProgram: CliProgram = {
121
121
  ...program,
122
122
  appConfig: {
123
- ...program.appConfig!,
123
+ ...program.appConfig,
124
124
  entries: {
125
- ...program.appConfig!.entries,
125
+ ...program.appConfig?.entries,
126
126
  apiToken: {
127
127
  description: "Token.",
128
128
  env: "API_TOKEN",
@@ -146,9 +146,9 @@ describe("config/resolve", () => {
146
146
  const resolveProgram: CliProgram = {
147
147
  ...program,
148
148
  appConfig: {
149
- ...program.appConfig!,
149
+ ...program.appConfig,
150
150
  entries: {
151
- ...program.appConfig!.entries,
151
+ ...program.appConfig?.entries,
152
152
  apiToken: {
153
153
  description: "Token.",
154
154
  env: "API_TOKEN",
@@ -173,9 +173,9 @@ describe("config/resolve", () => {
173
173
  const resolveProgram: CliProgram = {
174
174
  ...program,
175
175
  appConfig: {
176
- ...program.appConfig!,
176
+ ...program.appConfig,
177
177
  entries: {
178
- ...program.appConfig!.entries,
178
+ ...program.appConfig?.entries,
179
179
  apiToken: {
180
180
  description: "Token.",
181
181
  env: "API_TOKEN",
@@ -199,9 +199,9 @@ describe("config/resolve", () => {
199
199
  const resolveProgram: CliProgram = {
200
200
  ...program,
201
201
  appConfig: {
202
- ...program.appConfig!,
202
+ ...program.appConfig,
203
203
  entries: {
204
- ...program.appConfig!.entries,
204
+ ...program.appConfig?.entries,
205
205
  apiToken: {
206
206
  description: "Token.",
207
207
  env: "API_TOKEN",
@@ -227,9 +227,9 @@ describe("config/resolve", () => {
227
227
  const resolveProgram: CliProgram = {
228
228
  ...program,
229
229
  appConfig: {
230
- ...program.appConfig!,
230
+ ...program.appConfig,
231
231
  entries: {
232
- ...program.appConfig!.entries,
232
+ ...program.appConfig?.entries,
233
233
  apiToken: {
234
234
  description: "Token.",
235
235
  env: "API_TOKEN",
@@ -258,9 +258,9 @@ describe("config/resolve", () => {
258
258
  const resolveProgram: CliProgram = {
259
259
  ...program,
260
260
  appConfig: {
261
- ...program.appConfig!,
261
+ ...program.appConfig,
262
262
  entries: {
263
- ...program.appConfig!.entries,
263
+ ...program.appConfig?.entries,
264
264
  apiToken: {
265
265
  description: "Token.",
266
266
  env: "API_TOKEN",
@@ -156,14 +156,16 @@ describe("uninstall plan", () => {
156
156
  const detected = buildDetectedSnapshot(fixture, paths);
157
157
  const ctx = buildTargetPlanContext(fixture, paths, {}, detected);
158
158
  const target = installTargetForKey("skill");
159
- expect(target).toBeDefined();
160
- const actions = target!.planUninstall({
159
+ if (!target) throw new Error("expected skill install target");
160
+ const actions = target.planUninstall({
161
161
  ...ctx,
162
162
  mode: "uninstall-scoped",
163
163
  include: (key) => key === "skill",
164
164
  });
165
165
  expect(actions).toHaveLength(1);
166
- expect(actions[0]!.run().length).toBeGreaterThan(0);
166
+ const action = actions[0];
167
+ if (!action) throw new Error("expected uninstall action");
168
+ expect(action.run().length).toBeGreaterThan(0);
167
169
  expect(existsSync(paths.agentsSkillDir)).toBe(false);
168
170
  });
169
171
  });
@@ -15,6 +15,7 @@ import {
15
15
  enumMcpFixture,
16
16
  nestedDocsFallbackFixture,
17
17
  nestedMcpFixture,
18
+ requireMcpTool,
18
19
  testProgram,
19
20
  varargsReadFixture,
20
21
  } from "../test/fixtures.ts";
@@ -920,7 +921,7 @@ test("cliSchemaExport resolves {argsbarg:program} in consumer notes", () => {
920
921
 
921
922
  test("Enum option inputSchema includes enum array", () => {
922
923
  const tools = collectMcpTools(enumMcpFixture);
923
- const run = tools.find((t) => t.name === "run")!;
924
+ const run = requireMcpTool(tools, "run");
924
925
  const schema = run.inputSchema as { properties: { mode: { enum?: string[] } } };
925
926
  expect(schema.properties.mode.enum).toEqual(["dev", "prod"]);
926
927
  });
@@ -1291,28 +1292,28 @@ test("varargs unknown flag errors", async () => {
1291
1292
 
1292
1293
  test("mcpToolCallToArgv rejects comma-separated string for varargs", () => {
1293
1294
  const tools = collectMcpTools(nestedMcpFixture);
1294
- const read = tools.find((t) => t.name === "read")!;
1295
+ const read = requireMcpTool(tools, "read");
1295
1296
  const argv = mcpToolCallToArgv(nestedMcpFixture, read, { files: "a,b" });
1296
1297
  expect(argv).toEqual({ error: expect.stringContaining("JSON array") });
1297
1298
  });
1298
1299
 
1299
1300
  test("mcpToolCallToArgv rejects bare string for varargs", () => {
1300
1301
  const tools = collectMcpTools(nestedMcpFixture);
1301
- const read = tools.find((t) => t.name === "read")!;
1302
+ const read = requireMcpTool(tools, "read");
1302
1303
  const argv = mcpToolCallToArgv(nestedMcpFixture, read, { files: "a" });
1303
1304
  expect(argv).toEqual({ error: expect.stringContaining("JSON array") });
1304
1305
  });
1305
1306
 
1306
1307
  test("mcpToolCallToArgv array varargs unchanged", () => {
1307
1308
  const tools = collectMcpTools(nestedMcpFixture);
1308
- const read = tools.find((t) => t.name === "read")!;
1309
+ const read = requireMcpTool(tools, "read");
1309
1310
  const argv = mcpToolCallToArgv(nestedMcpFixture, read, { files: ["a", "b"] });
1310
1311
  expect(argv).toEqual(["read", "a", "b"]);
1311
1312
  });
1312
1313
 
1313
1314
  test("mcpToolCallToArgv empty array varargs errors when required", () => {
1314
1315
  const tools = collectMcpTools(nestedMcpFixture);
1315
- const read = tools.find((t) => t.name === "read")!;
1316
+ const read = requireMcpTool(tools, "read");
1316
1317
  const argv = mcpToolCallToArgv(nestedMcpFixture, read, { files: [] });
1317
1318
  expect(argv).toEqual({ error: "Missing argument: files" });
1318
1319
  });
package/src/core/types.ts CHANGED
@@ -163,10 +163,20 @@ export interface CliMcpBundleConfig {
163
163
  email?: string;
164
164
  url?: string;
165
165
  };
166
+ /** Human-readable display name for plugin manifests. */
167
+ displayName?: string;
168
+ /** Homepage URL for plugin manifests. */
169
+ homepage?: string;
166
170
  /** Repo-relative path to a PNG icon copied into the bundle. */
167
171
  icon?: string;
172
+ /** Software license identifier (e.g. "MIT", "Apache-2.0"). */
173
+ license?: string;
168
174
  /** Manifest `long_description` (defaults to program description). */
169
175
  longDescription?: string;
176
+ /** Repository URL for plugin manifests. */
177
+ repository?: string;
178
+ /** Custom relative path to repository skills directory (defaults to `skills/<dirName>`). */
179
+ skillsDir?: string;
170
180
  }
171
181
 
172
182
  /**
@@ -185,6 +195,8 @@ export interface CliMcpServerConfig {
185
195
  mcpd?: boolean;
186
196
  /** When `true`, `mcp bundle` also writes `dist/claude-plugin/<name>.zip`. Default false. */
187
197
  claudePlugin?: boolean;
198
+ /** When `true`, `mcp bundle` also writes `dist/cursor-plugin/<name>.zip`. Default false. */
199
+ cursorPlugin?: boolean;
188
200
  /** Resource URI for schema export (default: `<sanitized root key>://schema`). */
189
201
  schemaResourceUri?: string;
190
202
  /**
@@ -4,3 +4,15 @@ MCP bundle export (`argsbarg/mcp`). @experimental
4
4
 
5
5
  export type { McpBundlePaths, PackMcpBundleOpts } from "../mcp/bundle.ts";
6
6
  export { defaultMcpBundlePaths, generateMcpManifest, packMcpBundle } from "../mcp/bundle.ts";
7
+ export {
8
+ defaultClaudePluginPaths,
9
+ generatePluginManifest,
10
+ generatePluginMcpJson,
11
+ packClaudePlugin,
12
+ } from "../mcp/claude.ts";
13
+ export {
14
+ defaultCursorPluginPaths,
15
+ generateCursorPluginManifest,
16
+ generateCursorPluginMcpJson,
17
+ packCursorPlugin,
18
+ } from "../mcp/cursor.ts";