burgee 0.11.0 → 0.11.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/README.md +7 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1 -0
- package/dist/commander/command.d.ts +11 -1
- package/dist/commander/command.js +8 -1
- package/dist/completions.js +1 -1
- package/dist/execute.js +3 -3
- package/dist/manifest.d.ts +7 -0
- package/dist/mcp.d.ts +5 -1
- package/dist/mcp.js +9 -7
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ run(defineCommand({
|
|
|
46
46
|
name: 'greet',
|
|
47
47
|
description: 'Greet someone by name',
|
|
48
48
|
options: { name: { type: 'string', required: true, description: 'who to greet' } },
|
|
49
|
+
effects: 'read_only', // what running it does to the world; required, and what --mcp reads
|
|
49
50
|
run: ({ options }) => ({ greeting: `hello, ${options.name}` }),
|
|
50
51
|
}));
|
|
51
52
|
```
|
|
@@ -55,7 +56,7 @@ $ node cli.mjs --name ada
|
|
|
55
56
|
greeting: hello, ada
|
|
56
57
|
|
|
57
58
|
$ node cli.mjs --json --name ada
|
|
58
|
-
{"ok":true,"data":{"greeting":"hello, ada"}}
|
|
59
|
+
{"ok":true,"data":{"greeting":"hello, ada"},"meta":{"provenance":{"name":{"source":"flag","location":"--name"}}}}
|
|
59
60
|
|
|
60
61
|
$ node cli.mjs # exit 2
|
|
61
62
|
error: missing required option --name
|
|
@@ -154,9 +155,11 @@ the declaration read by a different reader.
|
|
|
154
155
|
|
|
155
156
|
### How do I expose a CLI over MCP?
|
|
156
157
|
|
|
157
|
-
Run it with `--mcp`: the same manifest is served as MCP tools over stdio.
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
Run it with `--mcp`: the same manifest is served as MCP tools over stdio. Every runnable
|
|
159
|
+
command declares its `effects` — `read_only`, `idempotent` or `non_idempotent`, which become
|
|
160
|
+
MCP's hints, or `withheld`, which keeps it out of the tool list — so nothing reaches an
|
|
161
|
+
agent by accident. On `burgee/commander` and `burgee/yargs`, a command that declared
|
|
162
|
+
nothing is still listed, marked `effects: 'undeclared'`. Register it with any stdio client:
|
|
160
163
|
|
|
161
164
|
```json
|
|
162
165
|
{ "mcpServers": { "mytool": { "command": "npx", "args": ["mytool", "--mcp"] } } }
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -329,7 +329,17 @@ export declare class Command extends EventEmitter {
|
|
|
329
329
|
*/
|
|
330
330
|
get manifest(): Manifest;
|
|
331
331
|
_project(manifest: Manifest): void;
|
|
332
|
-
/**
|
|
332
|
+
/**
|
|
333
|
+
* Options as the manifest describes them, on a null-prototype record — keyed so that the
|
|
334
|
+
* spelling every surface derives from a key, `--${kebab(key)}`, is a flag commander accepts.
|
|
335
|
+
*
|
|
336
|
+
* Commander negates only what it was told to, where burgee's own parser negates every
|
|
337
|
+
* boolean. So a boolean is `negatable` here only when the program declared its `--no-` twin,
|
|
338
|
+
* which folds onto it rather than appearing twice; and a `--no-x` declared alone, which has
|
|
339
|
+
* no `--x`, is described as the switch it is, under `noX`. Before this, `--no-color` was
|
|
340
|
+
* published as `color` with `flag: '--color'`: completions offered `--color` and
|
|
341
|
+
* `--no-skip-blank`, and `--mcp` sent `--color` — each refused by the parser behind them.
|
|
342
|
+
*/
|
|
333
343
|
_optionSpecs(): Record<string, OptionSpec>;
|
|
334
344
|
/** burgee: declare what the command does to the world (N6). This is what exposes it as an MCP tool (N2). */
|
|
335
345
|
effects(value: DeclaredEffects): this;
|
|
@@ -5,6 +5,7 @@ import { stripVTControlCharacters } from 'node:util';
|
|
|
5
5
|
import * as crossSpawn from 'bellpull/cross-spawn';
|
|
6
6
|
import { ExitCode } from '../exit-code.js';
|
|
7
7
|
import { Manifest } from '../manifest.js';
|
|
8
|
+
import { camel } from '../names.js';
|
|
8
9
|
import { host } from '../runtime.js';
|
|
9
10
|
import { machineJson, schemaOf } from '../schema.js';
|
|
10
11
|
import { suggestSimilar } from '../suggest.js';
|
|
@@ -1413,6 +1414,10 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1413
1414
|
_optionSpecs() {
|
|
1414
1415
|
const specs = Object.create(null);
|
|
1415
1416
|
for (const option of this.options) {
|
|
1417
|
+
const name = option.attributeName();
|
|
1418
|
+
const paired = this.options.some((o) => o.negate !== option.negate && o.attributeName() === name);
|
|
1419
|
+
if (option.negate && paired)
|
|
1420
|
+
continue;
|
|
1416
1421
|
const spec = { type: option.required || option.optional ? 'string' : 'boolean' };
|
|
1417
1422
|
if (option.description)
|
|
1418
1423
|
spec.description = option.description;
|
|
@@ -1424,7 +1429,9 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
|
|
|
1424
1429
|
spec.default = option.defaultValue;
|
|
1425
1430
|
if (option.envVar)
|
|
1426
1431
|
spec.env = option.envVar;
|
|
1427
|
-
|
|
1432
|
+
if (spec.type === 'boolean')
|
|
1433
|
+
spec.negatable = paired;
|
|
1434
|
+
Object.defineProperty(specs, option.negate ? camel(option.name()) : name, { value: spec, enumerable: true, writable: true, configurable: true });
|
|
1428
1435
|
}
|
|
1429
1436
|
return specs;
|
|
1430
1437
|
}
|
package/dist/completions.js
CHANGED
|
@@ -16,7 +16,7 @@ function children(manifest, at) {
|
|
|
16
16
|
function nodeOf(manifest, c) {
|
|
17
17
|
const visible = Object.fromEntries(Object.entries(c.options)
|
|
18
18
|
.filter(([, spec]) => spec.hidden !== true)
|
|
19
|
-
.map(([name, spec]) => [name, spec.type === 'boolean'
|
|
19
|
+
.map(([name, spec]) => [name, { ...spec, negatable: spec.type === 'boolean' && spec.negatable !== false }]));
|
|
20
20
|
const isRoot = c.path.length === manifest.rootPath.length;
|
|
21
21
|
return {
|
|
22
22
|
path: c.path.slice(manifest.rootPath.length),
|
package/dist/execute.js
CHANGED
|
@@ -127,7 +127,7 @@ function toParseConfig(specs, withConfig) {
|
|
|
127
127
|
...(spec.short === undefined ? {} : { short: spec.short }),
|
|
128
128
|
...(spec.multiple === true ? { multiple: true } : {}),
|
|
129
129
|
};
|
|
130
|
-
if (spec.type === 'boolean')
|
|
130
|
+
if (spec.type === 'boolean' && spec.negatable !== false)
|
|
131
131
|
config[`${NO}${kebab(name)}`] = { type: 'boolean' };
|
|
132
132
|
}
|
|
133
133
|
return config;
|
|
@@ -238,7 +238,7 @@ async function describeFailure(cause, argv, node) {
|
|
|
238
238
|
const dash = explain.singleDashHint(argv);
|
|
239
239
|
if (dash !== undefined)
|
|
240
240
|
return { code: ExitCode.USAGE, message, hint: dash };
|
|
241
|
-
const better = explain.unknownOption(cause, Object.keys(node?.options ?? {}));
|
|
241
|
+
const better = explain.unknownOption(cause, Object.keys(node?.options ?? {}).map(kebab));
|
|
242
242
|
return { code: ExitCode.USAGE, message, hint: 'run --help to see the available options', ...better };
|
|
243
243
|
}
|
|
244
244
|
return { code: ExitCode.RUNTIME, message };
|
|
@@ -505,7 +505,7 @@ export async function run(target, opts = {}) {
|
|
|
505
505
|
manifest.rootPath = [target.name];
|
|
506
506
|
manifest.add({
|
|
507
507
|
path: [target.name],
|
|
508
|
-
...(target
|
|
508
|
+
...helpFields(target),
|
|
509
509
|
options: target.options ?? {},
|
|
510
510
|
...(target.run === undefined ? {} : { run: target.run }),
|
|
511
511
|
});
|
package/dist/manifest.d.ts
CHANGED
|
@@ -78,6 +78,13 @@ export interface OptionSpec {
|
|
|
78
78
|
/** `true` renders `(deprecated)`; a string names the replacement: `(deprecated: use --force)` (yargs #2248). */
|
|
79
79
|
deprecated?: boolean | string;
|
|
80
80
|
hidden?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* `boolean` only: whether `--no-<name>` is accepted. Every boolean is negatable unless this
|
|
83
|
+
* says `false`. burgee's own parser negates every boolean (see `toParseConfig`); the
|
|
84
|
+
* commander façade sets `false` where commander would refuse the negation, so completions
|
|
85
|
+
* and `--mcp` never offer or send a flag the parser behind them rejects.
|
|
86
|
+
*/
|
|
87
|
+
negatable?: boolean;
|
|
81
88
|
/** The shared set this option was copied from (M4); `--schema` carries it, help lists the option like any other. */
|
|
82
89
|
sharedFrom?: string;
|
|
83
90
|
}
|
package/dist/mcp.d.ts
CHANGED
|
@@ -34,7 +34,11 @@ export type Invoke = (argv: string[]) => Promise<{
|
|
|
34
34
|
* this command is not a `read_only` one, and it is not a `withheld` one either — nobody said.
|
|
35
35
|
*/
|
|
36
36
|
export declare function annotationsOf(effects?: Effects): ToolAnnotations;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* `config get` → `config_get`: MCP tool names are `[a-zA-Z0-9_-]`, one character or more. A
|
|
39
|
+
* program whose root runs — a single `run(defineCommand(…))`, a commander program with a root
|
|
40
|
+
* `.action()` — has no typed name at all, so its tool is named after the program.
|
|
41
|
+
*/
|
|
38
42
|
export declare const toolName: (node: CommandNode, root: string[]) => string;
|
|
39
43
|
/**
|
|
40
44
|
* The tool list: every runnable, visible command an author has not withheld.
|
package/dist/mcp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline';
|
|
2
2
|
import { WITHHELD } from './definition.js';
|
|
3
|
+
import { kebab } from './names.js';
|
|
3
4
|
import { inputSchemaOf, runnable, typedName } from './schema.js';
|
|
4
5
|
export const MCP_PROTOCOL_VERSION = '2025-06-18';
|
|
5
6
|
const JSON_RPC_INVALID_REQUEST = -32600;
|
|
@@ -14,7 +15,7 @@ export function annotationsOf(effects) {
|
|
|
14
15
|
destructiveHint: effects === 'non_idempotent',
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
|
-
export const toolName = (node, root) => typedName(node, root).replaceAll(' ', '_');
|
|
18
|
+
export const toolName = (node, root) => (typedName(node, root) || root.join(' ')).replaceAll(' ', '_');
|
|
18
19
|
function describe(node) {
|
|
19
20
|
const parts = [node.description ?? node.summary ?? ''];
|
|
20
21
|
for (const e of node.examples ?? [])
|
|
@@ -32,12 +33,13 @@ function optionArgs(node, args) {
|
|
|
32
33
|
const value = args[name];
|
|
33
34
|
if (value === undefined || value === null)
|
|
34
35
|
continue;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
const flag = `--${kebab(name)}`;
|
|
37
|
+
if (spec.type !== 'boolean')
|
|
38
|
+
out.push(flag, String(value));
|
|
39
|
+
else if (value === true)
|
|
40
|
+
out.push(flag);
|
|
41
|
+
else if (value === false && spec.negatable !== false && (spec.default === true || spec.env !== undefined))
|
|
42
|
+
out.push(`--no-${kebab(name)}`);
|
|
41
43
|
}
|
|
42
44
|
return out;
|
|
43
45
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "burgee",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "An agent-native CLI framework, drop-in compatible with commander and yargs. One declaration; help, --json, --schema, --mcp and completions all projected from it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -146,11 +146,11 @@
|
|
|
146
146
|
"provenance": true
|
|
147
147
|
},
|
|
148
148
|
"dependencies": {
|
|
149
|
-
"bellpull": "^0.3.
|
|
150
|
-
"closeout": "^0.5.
|
|
151
|
-
"linegauge": "^0.5.
|
|
152
|
-
"roundel": "^0.5.
|
|
153
|
-
"seniority": "^0.5.
|
|
149
|
+
"bellpull": "^0.3.1",
|
|
150
|
+
"closeout": "^0.5.1",
|
|
151
|
+
"linegauge": "^0.5.1",
|
|
152
|
+
"roundel": "^0.5.1",
|
|
153
|
+
"seniority": "^0.5.1"
|
|
154
154
|
},
|
|
155
155
|
"devDependencies": {
|
|
156
156
|
"vitest": "^5.0.1"
|