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.
- package/CHANGELOG.md +35 -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 +21 -8
- 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/marketplace.json +13 -0
- package/examples/mcp-plugin/.claude-plugin/plugin.json +10 -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 +11 -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 +72 -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/cli-tool/program.ts +1 -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/headless/tool-call.test.ts +32 -0
- package/src/headless/tool-call.ts +19 -8
- package/src/mcp/bundle.ts +15 -5
- package/src/mcp/claude.test.ts +2 -7
- package/src/mcp/claude.ts +52 -68
- package/src/mcp/cursor.test.ts +151 -0
- package/src/mcp/cursor.ts +157 -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,225 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/*
|
|
3
|
+
Bump version, build standalone Node MCP server, and publish release tag.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import { $ } from "bun";
|
|
8
|
+
|
|
9
|
+
/** Allowed semver bump kinds for `scripts/release.ts`. */
|
|
10
|
+
type Bump = "major" | "minor" | "patch";
|
|
11
|
+
|
|
12
|
+
/** Parsed command-line options for release. */
|
|
13
|
+
interface ReleaseOptions {
|
|
14
|
+
/** Semver segment to increment. */
|
|
15
|
+
bump?: Bump;
|
|
16
|
+
/** Whether running in dry-run mode. */
|
|
17
|
+
dryRun: boolean;
|
|
18
|
+
/** Whether confirmation is skipped. */
|
|
19
|
+
yes: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Path to program entrypoint defining the version. */
|
|
23
|
+
const programPath = "src/program.ts";
|
|
24
|
+
|
|
25
|
+
/** Path to Cursor plugin manifest. */
|
|
26
|
+
const cursorManifestPath = ".cursor-plugin/plugin.json";
|
|
27
|
+
|
|
28
|
+
/** Path to Claude Code plugin manifest. */
|
|
29
|
+
const claudeManifestPath = ".claude-plugin/plugin.json";
|
|
30
|
+
|
|
31
|
+
/** Entry point: release bump. */
|
|
32
|
+
async function main(): Promise<void> {
|
|
33
|
+
const options = parseOptions(process.argv.slice(2));
|
|
34
|
+
if (!options.bump) {
|
|
35
|
+
usage();
|
|
36
|
+
}
|
|
37
|
+
await runRelease(options.bump, options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Prints usage and exits. */
|
|
41
|
+
function usage(): never {
|
|
42
|
+
process.stderr.write(
|
|
43
|
+
"Usage:\n" +
|
|
44
|
+
" bun scripts/release.ts <major|minor|patch> [--yes] [--dry-run]\n",
|
|
45
|
+
);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parses argv into release options.
|
|
51
|
+
*/
|
|
52
|
+
function parseOptions(
|
|
53
|
+
/** Command line arguments. */
|
|
54
|
+
argv: string[],
|
|
55
|
+
): ReleaseOptions {
|
|
56
|
+
const yes = argv.includes("--yes");
|
|
57
|
+
const dryRun = argv.includes("--dry-run");
|
|
58
|
+
const bump = argv.find((a): a is Bump => a === "major" || a === "minor" || a === "patch");
|
|
59
|
+
for (const arg of argv) {
|
|
60
|
+
if (arg.startsWith("--") && arg !== "--yes" && arg !== "--dry-run") {
|
|
61
|
+
usage();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!bump) {
|
|
65
|
+
usage();
|
|
66
|
+
}
|
|
67
|
+
return { bump, dryRun, yes };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Full release pipeline for a semver bump.
|
|
72
|
+
*/
|
|
73
|
+
async function runRelease(
|
|
74
|
+
/** Increment segment. */
|
|
75
|
+
bump: Bump,
|
|
76
|
+
/** Parsed CLI options. */
|
|
77
|
+
options: ReleaseOptions,
|
|
78
|
+
): Promise<void> {
|
|
79
|
+
const testResult = await $`just test`.nothrow();
|
|
80
|
+
if (testResult.exitCode !== 0) process.exit(testResult.exitCode);
|
|
81
|
+
|
|
82
|
+
const currentVersion = readCurrentVersion();
|
|
83
|
+
const newVersion = applyBump(currentVersion, bump);
|
|
84
|
+
console.log(`Releasing ${currentVersion} → ${newVersion}`);
|
|
85
|
+
|
|
86
|
+
updateVersion(newVersion);
|
|
87
|
+
updateChangelog(newVersion);
|
|
88
|
+
|
|
89
|
+
const buildResult = await $`just build`.nothrow();
|
|
90
|
+
if (buildResult.exitCode !== 0) process.exit(buildResult.exitCode);
|
|
91
|
+
|
|
92
|
+
const docgenResult = await $`just docgen`.nothrow();
|
|
93
|
+
if (docgenResult.exitCode !== 0) process.exit(docgenResult.exitCode);
|
|
94
|
+
|
|
95
|
+
if (options.dryRun) {
|
|
96
|
+
console.log(`[dry-run] Would commit, tag v${newVersion}, and create GitHub release.`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await commitAndTag(newVersion);
|
|
101
|
+
await createGithubRelease(`v${newVersion}`);
|
|
102
|
+
|
|
103
|
+
console.log(`Released v${newVersion}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Reads the current version string from src/program.ts.
|
|
108
|
+
*/
|
|
109
|
+
function readCurrentVersion(): string {
|
|
110
|
+
const content = fs.readFileSync(programPath, "utf-8");
|
|
111
|
+
const match = /version:\s*"([^"]+)"/.exec(content);
|
|
112
|
+
if (!match) {
|
|
113
|
+
process.stderr.write(`Could not read version from ${programPath}\n`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const version = match[1];
|
|
117
|
+
if (!version) {
|
|
118
|
+
process.stderr.write(`Could not read version from ${programPath}\n`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const parts = version.split(".").map(Number);
|
|
122
|
+
if (parts.length !== 3 || parts.some(Number.isNaN)) {
|
|
123
|
+
process.stderr.write(`Invalid semver in ${programPath}: ${version}\n`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
return version;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Increments semver according to the specified bump level.
|
|
131
|
+
*/
|
|
132
|
+
function applyBump(
|
|
133
|
+
/** Existing version string. */
|
|
134
|
+
version: string,
|
|
135
|
+
/** Semver segment to increment. */
|
|
136
|
+
bump: Bump,
|
|
137
|
+
): string {
|
|
138
|
+
const parts = version.split(".").map(Number) as [number, number, number];
|
|
139
|
+
if (bump === "major") {
|
|
140
|
+
return `${parts[0] + 1}.0.0`;
|
|
141
|
+
}
|
|
142
|
+
if (bump === "minor") {
|
|
143
|
+
return `${parts[0]}.${parts[1] + 1}.0`;
|
|
144
|
+
}
|
|
145
|
+
return `${parts[0]}.${parts[1]}.${parts[2] + 1}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Updates version in package.json, program.ts, and plugin manifests.
|
|
150
|
+
*/
|
|
151
|
+
function updateVersion(
|
|
152
|
+
/** Incremented version string. */
|
|
153
|
+
newVersion: string,
|
|
154
|
+
): void {
|
|
155
|
+
const pkgPath = "package.json";
|
|
156
|
+
const pkgContent = fs.readFileSync(pkgPath, "utf-8");
|
|
157
|
+
fs.writeFileSync(
|
|
158
|
+
pkgPath,
|
|
159
|
+
pkgContent.replace(/"version":\s*"[^"]+"/, `"version": "${newVersion}"`),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const progContent = fs.readFileSync(programPath, "utf-8");
|
|
163
|
+
fs.writeFileSync(
|
|
164
|
+
programPath,
|
|
165
|
+
progContent.replace(/version:\s*"[^"]+"/, `version: "${newVersion}"`),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (fs.existsSync(cursorManifestPath)) {
|
|
169
|
+
const cursorContent = fs.readFileSync(cursorManifestPath, "utf-8");
|
|
170
|
+
fs.writeFileSync(
|
|
171
|
+
cursorManifestPath,
|
|
172
|
+
cursorContent.replace(/"version":\s*"[^"]+"/, `"version": "${newVersion}"`),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (fs.existsSync(claudeManifestPath)) {
|
|
177
|
+
const claudeContent = fs.readFileSync(claudeManifestPath, "utf-8");
|
|
178
|
+
fs.writeFileSync(
|
|
179
|
+
claudeManifestPath,
|
|
180
|
+
claudeContent.replace(/"version":\s*"[^"]+"/, `"version": "${newVersion}"`),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Prepends the new release section with date under [Unreleased] in CHANGELOG.md.
|
|
187
|
+
*/
|
|
188
|
+
function updateChangelog(
|
|
189
|
+
/** Incremented version string. */
|
|
190
|
+
newVersion: string,
|
|
191
|
+
): void {
|
|
192
|
+
const changelogPath = "CHANGELOG.md";
|
|
193
|
+
const content = fs.readFileSync(changelogPath, "utf-8");
|
|
194
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
195
|
+
fs.writeFileSync(
|
|
196
|
+
changelogPath,
|
|
197
|
+
content.replace(/^## \[Unreleased\]/m, `## [Unreleased]\n\n## [${newVersion}] - ${date}`),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Stages all changes, creates a release commit, tags it, and pushes to remote.
|
|
203
|
+
*/
|
|
204
|
+
async function commitAndTag(
|
|
205
|
+
/** Incremented version string. */
|
|
206
|
+
newVersion: string,
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
await $`git add -A`;
|
|
209
|
+
await $`git commit -m ${`chore: release v${newVersion}`}`;
|
|
210
|
+
await $`git tag v${newVersion}`;
|
|
211
|
+
await $`git push`;
|
|
212
|
+
await $`git push origin v${newVersion}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Creates a release on GitHub.
|
|
217
|
+
*/
|
|
218
|
+
async function createGithubRelease(
|
|
219
|
+
/** Git release tag. */
|
|
220
|
+
tag: string,
|
|
221
|
+
): Promise<void> {
|
|
222
|
+
await $`gh release create ${tag} --title ${tag} --generate-notes`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await main();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: mcp-plugin
|
|
3
|
+
name: mcp-plugin
|
|
4
|
+
description: Operates the mcp-plugin CLI (echo, render-json, status, workspaces :id delete, workspaces :id get, and 4 more). Use when the user mentions mcp-plugin, echo, render-json, status, or related tasks.
|
|
5
|
+
enabled: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# mcp-plugin
|
|
9
|
+
|
|
10
|
+
Argsbarg MCP plugin template for Cursor and Claude Code marketplaces (@sg schemagen, JSON schemas, REST CRUD)
|
|
11
|
+
|
|
12
|
+
## Execution
|
|
13
|
+
|
|
14
|
+
Invoke via shell:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
mcp-plugin <subcommand> [options] [args]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Options & Help Discovery
|
|
21
|
+
|
|
22
|
+
- Run `mcp-plugin <subcommand> --help` to inspect flags, choices, and positional arguments before running unfamiliar subcommands.
|
|
23
|
+
- Run `mcp-plugin --help` at the root for top-level options and command routing.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
- **`mcp-plugin echo`** — Echo a message (MCP-friendly leaf).
|
|
28
|
+
- **`mcp-plugin render-json`** — Echo a JSON message (schema-first JSON leaf demo).
|
|
29
|
+
- **`mcp-plugin status`** — Show app version.
|
|
30
|
+
- **`mcp-plugin workspaces :id delete`** — Delete a workspace.
|
|
31
|
+
- **`mcp-plugin workspaces :id get`** — Get one workspace.
|
|
32
|
+
- **`mcp-plugin workspaces :id patch`** — Patch a workspace name.
|
|
33
|
+
- **`mcp-plugin workspaces :id put`** — Replace a workspace.
|
|
34
|
+
- **`mcp-plugin workspaces get`** — List workspaces.
|
|
35
|
+
- **`mcp-plugin workspaces post`** — Create a workspace.
|
|
36
|
+
|
|
37
|
+
## Workflow & Pitfalls
|
|
38
|
+
|
|
39
|
+
- Always run `mcp-plugin <subcommand> --help` instead of guessing options or reading large doc files.
|
|
40
|
+
- Pass `--` before arguments that look like flags.
|
|
41
|
+
- Pass `--yes` for non-interactive execution when confirmation is required.
|
|
42
|
+
- Pass `--json` when machine-readable structured output is supported.
|
|
43
|
+
|
|
44
|
+
## Install location
|
|
45
|
+
|
|
46
|
+
Install follows the https://dotagentsprotocol.com:
|
|
47
|
+
|
|
48
|
+
- Auto-install: `mcp-plugin configure install` when `skill.enabled` → `~/.agents/skills/mcp-plugin/`
|
|
49
|
+
- Cursor and most coding agents read `~/.agents/skills/` natively
|
|
50
|
+
|
|
51
|
+
**Claude Code (manual):** symlink or copy into Claude's skill directory:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
mkdir -p ~/.claude/skills
|
|
55
|
+
ln -sf ~/.agents/skills/full-example-json ~/.claude/skills/full-example-json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Project override (optional): `.agents/skills/full-example-json/`
|
|
59
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Echo leaf — minimal MCP-friendly command.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type CliLeaf, CliOptionKind } from "argsbarg";
|
|
6
|
+
|
|
7
|
+
export const echoCommand = {
|
|
8
|
+
key: "echo",
|
|
9
|
+
description: "Echo a message (MCP-friendly leaf).",
|
|
10
|
+
options: [
|
|
11
|
+
{
|
|
12
|
+
name: "message",
|
|
13
|
+
description: "Text to print.",
|
|
14
|
+
kind: CliOptionKind.String,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
handler: (ctx) => {
|
|
19
|
+
const message = ctx.stringOpt("message") ?? "";
|
|
20
|
+
if (ctx.invocation === "cli") {
|
|
21
|
+
console.log(message);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
return message;
|
|
25
|
+
},
|
|
26
|
+
} satisfies CliLeaf;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"message": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "Message to echo back."
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"required": [
|
|
11
|
+
"message"
|
|
12
|
+
],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"definitions": {}
|
|
15
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { Cli, type CliProgram } from "argsbarg";
|
|
3
|
+
import { renderJsonCommand, renderJsonTestProgram } from "./command.ts";
|
|
4
|
+
|
|
5
|
+
const baseProgram = {
|
|
6
|
+
key: "full-example",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
description: "Demo.",
|
|
9
|
+
httpServer: { enabled: true },
|
|
10
|
+
commands: [],
|
|
11
|
+
} satisfies CliProgram;
|
|
12
|
+
|
|
13
|
+
describe("render-json command", () => {
|
|
14
|
+
const program = renderJsonTestProgram(baseProgram);
|
|
15
|
+
const cli = new Cli(program);
|
|
16
|
+
|
|
17
|
+
test("HTTP invoke returns echoed message", async () => {
|
|
18
|
+
const result = await cli.invoke(["render-json"], {
|
|
19
|
+
invocation: "http",
|
|
20
|
+
toolArgs: { message: "hello" },
|
|
21
|
+
});
|
|
22
|
+
expect(result.kind).toBe("ok");
|
|
23
|
+
expect(result.response?.body).toEqual({ message: "hello" });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("rejects invalid input before handler via inputSchema", async () => {
|
|
27
|
+
let handlerCalled = false;
|
|
28
|
+
const badProgram = renderJsonTestProgram({
|
|
29
|
+
...baseProgram,
|
|
30
|
+
commands: [
|
|
31
|
+
{
|
|
32
|
+
...renderJsonCommand,
|
|
33
|
+
handler: () => {
|
|
34
|
+
handlerCalled = true;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
const result = await new Cli(badProgram).invoke(["render-json"], {
|
|
40
|
+
invocation: "http",
|
|
41
|
+
toolArgs: { message: 123 },
|
|
42
|
+
});
|
|
43
|
+
expect(result.kind).toBe("error");
|
|
44
|
+
expect(handlerCalled).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Render-json leaf — JSON body demo with schemagen inputSchema and ctx.inputsAs.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CliLeaf, CliProgram } from "argsbarg";
|
|
6
|
+
import { RenderJsonInputSchema } from "./__generated__";
|
|
7
|
+
import type { RenderJsonInput } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
export const renderJsonCommand = {
|
|
10
|
+
key: "render-json",
|
|
11
|
+
description: "Echo a JSON message (schema-first JSON leaf demo).",
|
|
12
|
+
kind: "json",
|
|
13
|
+
inputSchema: RenderJsonInputSchema,
|
|
14
|
+
handler: (ctx) => {
|
|
15
|
+
const { message } = ctx.inputsAs<RenderJsonInput>();
|
|
16
|
+
if (ctx.invocation === "cli") {
|
|
17
|
+
console.log(message);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
return { message };
|
|
21
|
+
},
|
|
22
|
+
} satisfies CliLeaf;
|
|
23
|
+
|
|
24
|
+
/** Program stub for colocated tests. */
|
|
25
|
+
export function renderJsonTestProgram(base: CliProgram): CliProgram {
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
commands: [renderJsonCommand],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"version": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "App version from program root."
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"required": [
|
|
11
|
+
"version"
|
|
12
|
+
],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"definitions": {}
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { statusCommand } from "./command.ts";
|
|
3
|
+
|
|
4
|
+
describe("status command", () => {
|
|
5
|
+
test("exports outputSchema and json option", () => {
|
|
6
|
+
expect(statusCommand.key).toBe("status");
|
|
7
|
+
expect(statusCommand.outputSchema).toBeDefined();
|
|
8
|
+
expect(statusCommand.options?.some((o) => o.name === "json")).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Status leaf — demonstrates outputSchema.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type CliLeaf, CliOptionKind } from "argsbarg";
|
|
6
|
+
import { StatusJsonOutputSchema } from "./__generated__";
|
|
7
|
+
import type { StatusJsonOutput } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
export const statusCommand = {
|
|
10
|
+
key: "status",
|
|
11
|
+
description: "Show app version.",
|
|
12
|
+
options: [
|
|
13
|
+
{
|
|
14
|
+
name: "json",
|
|
15
|
+
description: "Emit JSON.",
|
|
16
|
+
kind: CliOptionKind.Presence,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
outputSchema: StatusJsonOutputSchema,
|
|
20
|
+
handler: (ctx) => {
|
|
21
|
+
const out: StatusJsonOutput = { version: ctx.program.version };
|
|
22
|
+
if (ctx.hasFlag("json")) {
|
|
23
|
+
console.log(JSON.stringify(out, null, 2));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(`version=${out.version}`);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
} satisfies CliLeaf;
|
package/examples/mcp-plugin/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"name": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "Workspace display name."
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"required": [
|
|
11
|
+
"name"
|
|
12
|
+
],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"definitions": {}
|
|
15
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { Cli, type CliProgram } from "argsbarg";
|
|
3
|
+
import { AppDb } from "../../db";
|
|
4
|
+
import { workspacesTestProgram } from "./command.ts";
|
|
5
|
+
|
|
6
|
+
const baseProgram = {
|
|
7
|
+
key: "full-example",
|
|
8
|
+
version: "1.0.0",
|
|
9
|
+
description: "Demo.",
|
|
10
|
+
httpServer: { enabled: true },
|
|
11
|
+
commands: [],
|
|
12
|
+
hooks: {
|
|
13
|
+
beforeInvoke: AppDb.attach,
|
|
14
|
+
},
|
|
15
|
+
} satisfies CliProgram;
|
|
16
|
+
|
|
17
|
+
describe("workspaces command", () => {
|
|
18
|
+
const program = workspacesTestProgram(baseProgram);
|
|
19
|
+
const cli = new Cli(program);
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
AppDb.resetForTests();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("GET workspaces lists empty collection", async () => {
|
|
26
|
+
const result = await cli.invoke(["workspaces", "get"], { invocation: "http" });
|
|
27
|
+
expect(result.kind).toBe("ok");
|
|
28
|
+
expect(result.response?.body).toEqual({ workspaces: [] });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("POST workspaces creates resource", async () => {
|
|
32
|
+
const created = await cli.invoke(["workspaces", "post"], {
|
|
33
|
+
invocation: "http",
|
|
34
|
+
toolArgs: { name: "qa2" },
|
|
35
|
+
});
|
|
36
|
+
expect(created.kind).toBe("ok");
|
|
37
|
+
const body = created.response?.body as { id: string; name: string };
|
|
38
|
+
expect(body.name).toBe("qa2");
|
|
39
|
+
expect(body.id.length).toBeGreaterThan(0);
|
|
40
|
+
|
|
41
|
+
const got = await cli.invoke(["workspaces", body.id, "get"], { invocation: "http" });
|
|
42
|
+
expect(got.kind).toBe("ok");
|
|
43
|
+
expect(got.response?.body).toEqual(body);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("CLI workspaces :id get resolves path param", async () => {
|
|
47
|
+
const created = await cli.invoke(["workspaces", "post"], {
|
|
48
|
+
invocation: "http",
|
|
49
|
+
toolArgs: { name: "cli-ws" },
|
|
50
|
+
});
|
|
51
|
+
expect(created.kind).toBe("ok");
|
|
52
|
+
const id = (created.response?.body as { id: string }).id;
|
|
53
|
+
|
|
54
|
+
const got = await cli.invoke(["workspaces", id, "get"], { invocation: "http" });
|
|
55
|
+
expect(got.kind).toBe("ok");
|
|
56
|
+
expect(got.response?.body).toEqual({ id, name: "cli-ws" });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Workspaces CRUD — demonstrates REST verbs, :id param routers, and ctx.inputs path params.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type CliProgram, type CliRouter, cliErrWithHelp } from "argsbarg";
|
|
6
|
+
import { WorkspaceNameInputSchema } from "./__generated__";
|
|
7
|
+
|
|
8
|
+
function notFound(ctx: Parameters<typeof cliErrWithHelp>[0], id: string): never {
|
|
9
|
+
cliErrWithHelp(ctx, `Workspace not found: ${id}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const workspacesCommand = {
|
|
13
|
+
key: "workspaces",
|
|
14
|
+
description: "Workspace collection and CRUD.",
|
|
15
|
+
commands: [
|
|
16
|
+
{
|
|
17
|
+
key: "get",
|
|
18
|
+
description: "List workspaces.",
|
|
19
|
+
handler: (ctx) => ({ workspaces: ctx.locals.db.workspaces.list() }),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: "post",
|
|
23
|
+
description: "Create a workspace.",
|
|
24
|
+
inputSchema: WorkspaceNameInputSchema,
|
|
25
|
+
handler: (ctx) => {
|
|
26
|
+
const { name } = ctx.inputsAs<{ name: string }>();
|
|
27
|
+
return ctx.locals.db.workspaces.create(name);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: ":id",
|
|
32
|
+
description: "One workspace by id.",
|
|
33
|
+
commands: [
|
|
34
|
+
{
|
|
35
|
+
key: "get",
|
|
36
|
+
description: "Get one workspace.",
|
|
37
|
+
handler: (ctx) => {
|
|
38
|
+
const id = ctx.inputsAs<{ id: string }>().id;
|
|
39
|
+
const ws = ctx.locals.db.workspaces.get(id);
|
|
40
|
+
if (!ws) {
|
|
41
|
+
notFound(ctx, id);
|
|
42
|
+
}
|
|
43
|
+
return ws;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: "put",
|
|
48
|
+
description: "Replace a workspace.",
|
|
49
|
+
inputSchema: WorkspaceNameInputSchema,
|
|
50
|
+
handler: (ctx) => {
|
|
51
|
+
const { id, name } = ctx.inputsAs<{ id: string; name: string }>();
|
|
52
|
+
const ws = ctx.locals.db.workspaces.replace(id, name);
|
|
53
|
+
if (!ws) {
|
|
54
|
+
notFound(ctx, id);
|
|
55
|
+
}
|
|
56
|
+
return ws;
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: "patch",
|
|
61
|
+
description: "Patch a workspace name.",
|
|
62
|
+
inputSchema: WorkspaceNameInputSchema,
|
|
63
|
+
handler: (ctx) => {
|
|
64
|
+
const { id, name } = ctx.inputsAs<{ id: string; name: string }>();
|
|
65
|
+
const ws = ctx.locals.db.workspaces.patch(id, name);
|
|
66
|
+
if (!ws) {
|
|
67
|
+
notFound(ctx, id);
|
|
68
|
+
}
|
|
69
|
+
return ws;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: "delete",
|
|
74
|
+
description: "Delete a workspace.",
|
|
75
|
+
handler: (ctx) => {
|
|
76
|
+
const id = ctx.inputsAs<{ id: string }>().id;
|
|
77
|
+
if (!ctx.locals.db.workspaces.delete(id)) {
|
|
78
|
+
notFound(ctx, id);
|
|
79
|
+
}
|
|
80
|
+
ctx.respond({ status: 204, body: "" });
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
} satisfies CliRouter;
|
|
87
|
+
|
|
88
|
+
/** Test program with workspaces registered. */
|
|
89
|
+
export function workspacesTestProgram(base: CliProgram): CliProgram {
|
|
90
|
+
return {
|
|
91
|
+
...base,
|
|
92
|
+
commands: [workspacesCommand],
|
|
93
|
+
};
|
|
94
|
+
}
|