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.
- package/CHANGELOG.md +26 -1
- package/README.md +3 -2
- package/docs/ai-skills.md +2 -2
- package/docs/cli-program.md +3 -5
- package/docs/developing.md +3 -3
- package/docs/mcp.md +20 -7
- package/docs/output-schema.md +1 -1
- package/examples/full-example/AGENTS.md +20 -12
- package/examples/full-example/README.md +57 -12
- package/examples/full-example/biome.json +5 -4
- package/examples/full-example/bunfig.toml +3 -0
- package/examples/full-example/justfile +22 -27
- package/examples/full-example-json/AGENTS.md +14 -12
- package/examples/full-example-json/README.md +63 -15
- package/examples/full-example-json/biome.json +1 -3
- package/examples/full-example-json/bunfig.toml +3 -0
- package/examples/full-example-json/justfile +22 -27
- package/examples/mcp-plugin/.claude-plugin/plugin.json +11 -0
- package/examples/mcp-plugin/.cursor/hooks/run-tests-on-stop.ts +56 -0
- package/examples/mcp-plugin/.cursor/hooks.json +12 -0
- package/examples/mcp-plugin/.cursor-plugin/plugin.json +12 -0
- package/examples/mcp-plugin/.mcp.json +6 -0
- package/examples/mcp-plugin/AGENTS.md +90 -0
- package/examples/mcp-plugin/CLAUDE.md +1 -0
- package/examples/mcp-plugin/README.md +70 -0
- package/examples/mcp-plugin/biome.json +20 -0
- package/examples/mcp-plugin/bun.lock +48 -0
- package/examples/mcp-plugin/bunfig.toml +3 -0
- package/examples/mcp-plugin/docs/README.md +27 -0
- package/examples/mcp-plugin/docs/cli-schema.json +2085 -0
- package/examples/mcp-plugin/docs/cli.md +2026 -0
- package/examples/mcp-plugin/docs/http.md +92 -0
- package/examples/mcp-plugin/docs/mcp.md +116 -0
- package/examples/mcp-plugin/docs/openapi.json +1243 -0
- package/examples/mcp-plugin/justfile +89 -0
- package/examples/mcp-plugin/mcp.json +8 -0
- package/examples/mcp-plugin/package.json +23 -0
- package/examples/mcp-plugin/scripts/create-identity.ts +12 -0
- package/examples/mcp-plugin/scripts/mcp.mjs +11106 -0
- package/examples/mcp-plugin/scripts/release.ts +225 -0
- package/examples/mcp-plugin/skills/mcp-plugin/SKILL.md +59 -0
- package/examples/mcp-plugin/src/commands/echo/command.ts +26 -0
- package/examples/mcp-plugin/src/commands/render-json/__generated__/RenderJsonInputSchema.json +15 -0
- package/examples/mcp-plugin/src/commands/render-json/__generated__/index.ts +5 -0
- package/examples/mcp-plugin/src/commands/render-json/command.test.ts +46 -0
- package/examples/mcp-plugin/src/commands/render-json/command.ts +30 -0
- package/examples/mcp-plugin/src/commands/render-json/types.ts +9 -0
- package/examples/mcp-plugin/src/commands/status/__generated__/StatusJsonOutputSchema.json +15 -0
- package/examples/mcp-plugin/src/commands/status/__generated__/index.ts +5 -0
- package/examples/mcp-plugin/src/commands/status/command.test.ts +10 -0
- package/examples/mcp-plugin/src/commands/status/command.ts +28 -0
- package/examples/mcp-plugin/src/commands/status/types.ts +6 -0
- package/examples/mcp-plugin/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +15 -0
- package/examples/mcp-plugin/src/commands/workspaces/__generated__/index.ts +5 -0
- package/examples/mcp-plugin/src/commands/workspaces/command.test.ts +58 -0
- package/examples/mcp-plugin/src/commands/workspaces/command.ts +94 -0
- package/examples/mcp-plugin/src/commands/workspaces/types.ts +6 -0
- package/examples/mcp-plugin/src/db/index.test.ts +86 -0
- package/examples/mcp-plugin/src/db/index.ts +77 -0
- package/examples/mcp-plugin/src/db/tables/workspaces.ts +58 -0
- package/examples/mcp-plugin/src/index.ts +10 -0
- package/examples/mcp-plugin/src/program.ts +32 -0
- package/examples/mcp-plugin/src/types/argsbarg.d.ts +11 -0
- package/examples/mcp-plugin/src/types/md.d.ts +4 -0
- package/examples/mcp-plugin/tsconfig.json +17 -0
- package/index.d.ts +12 -0
- package/package.json +1 -1
- package/src/builtins/mcp.ts +1 -1
- package/src/cli-tool/create.test.ts +5 -0
- package/src/cli-tool/create.ts +9 -1
- package/src/config/manifest.ts +56 -0
- package/src/config/resolve.test.ts +12 -12
- package/src/configure/configure.test.ts +5 -3
- package/src/core/parse.test.ts +6 -5
- package/src/core/types.ts +12 -0
- package/src/exports/mcp.ts +12 -0
- package/src/help.test.ts +36 -0
- package/src/help.ts +32 -19
- package/src/mcp/bundle.ts +12 -3
- package/src/mcp/claude.test.ts +2 -7
- package/src/mcp/claude.ts +49 -67
- package/src/mcp/cursor.test.ts +151 -0
- package/src/mcp/cursor.ts +155 -0
- package/src/mcp/hidden-mcpb.test.ts +34 -1
- package/src/mcp/plugin-shared.ts +107 -0
- package/src/mcp/result.ts +5 -1
- package/src/mcp/server.ts +3 -2
- package/src/test/fixtures.ts +13 -0
- package/src/test/integration/mcp.test.ts +8 -7
- package/examples/full-example/scripts/print-identity.ts +0 -28
- 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,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,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
package/src/builtins/mcp.ts
CHANGED
|
@@ -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
|
|
package/src/cli-tool/create.ts
CHANGED
|
@@ -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
|
];
|
package/src/config/manifest.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
160
|
-
const actions = target
|
|
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
|
-
|
|
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
|
});
|
package/src/core/parse.test.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
/**
|
package/src/exports/mcp.ts
CHANGED
|
@@ -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";
|