burgee 0.0.0 → 0.3.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 (116) hide show
  1. package/README.md +28 -1
  2. package/dist/agent.d.ts +19 -0
  3. package/dist/agent.js +25 -0
  4. package/dist/brand.d.ts +186 -0
  5. package/dist/brand.js +232 -0
  6. package/dist/cli.d.ts +62 -0
  7. package/dist/cli.js +121 -0
  8. package/dist/commander-argument.d.ts +22 -0
  9. package/dist/commander-argument.js +72 -0
  10. package/dist/commander-command.d.ts +355 -0
  11. package/dist/commander-command.js +1621 -0
  12. package/dist/commander-error.d.ts +10 -0
  13. package/dist/commander-error.js +20 -0
  14. package/dist/commander-help.d.ts +67 -0
  15. package/dist/commander-help.js +319 -0
  16. package/dist/commander-option.d.ts +58 -0
  17. package/dist/commander-option.js +164 -0
  18. package/dist/commander-suggest.d.ts +2 -0
  19. package/dist/commander-suggest.js +59 -0
  20. package/dist/commander.d.ts +18 -0
  21. package/dist/commander.js +12 -0
  22. package/dist/completions.d.ts +39 -0
  23. package/dist/completions.js +224 -0
  24. package/dist/config.d.ts +28 -0
  25. package/dist/config.js +98 -0
  26. package/dist/contrast.d.ts +70 -0
  27. package/dist/contrast.js +93 -0
  28. package/dist/dev.d.ts +47 -0
  29. package/dist/dev.js +132 -0
  30. package/dist/execute.d.ts +118 -0
  31. package/dist/execute.js +469 -0
  32. package/dist/exit-code.d.ts +18 -0
  33. package/dist/exit-code.js +12 -0
  34. package/dist/help.d.ts +34 -0
  35. package/dist/help.js +187 -0
  36. package/dist/index.d.ts +16 -1
  37. package/dist/index.js +9 -2
  38. package/dist/manifest.d.ts +207 -0
  39. package/dist/manifest.js +66 -0
  40. package/dist/mcp.d.ts +52 -0
  41. package/dist/mcp.js +124 -0
  42. package/dist/names.d.ts +5 -0
  43. package/dist/names.js +6 -0
  44. package/dist/pkg.d.ts +5 -0
  45. package/dist/pkg.js +21 -0
  46. package/dist/precedence.d.ts +55 -0
  47. package/dist/precedence.js +100 -0
  48. package/dist/runtime.d.ts +39 -0
  49. package/dist/runtime.js +24 -0
  50. package/dist/schema.d.ts +79 -0
  51. package/dist/schema.js +116 -0
  52. package/dist/testing-helpers.d.ts +79 -0
  53. package/dist/testing-helpers.js +145 -0
  54. package/dist/testing.d.ts +10 -0
  55. package/dist/testing.js +3 -0
  56. package/dist/validate.d.ts +27 -0
  57. package/dist/validate.js +133 -0
  58. package/dist/yargs-burgee.d.ts +50 -0
  59. package/dist/yargs-burgee.js +104 -0
  60. package/dist/yargs-cliui.d.ts +56 -0
  61. package/dist/yargs-cliui.js +421 -0
  62. package/dist/yargs-command.d.ts +82 -0
  63. package/dist/yargs-command.js +414 -0
  64. package/dist/yargs-completion.d.ts +41 -0
  65. package/dist/yargs-completion.js +271 -0
  66. package/dist/yargs-factory.d.ts +193 -0
  67. package/dist/yargs-factory.js +1606 -0
  68. package/dist/yargs-helpers.d.ts +6 -0
  69. package/dist/yargs-helpers.js +2 -0
  70. package/dist/yargs-middleware.d.ts +32 -0
  71. package/dist/yargs-middleware.js +81 -0
  72. package/dist/yargs-parser.d.ts +41 -0
  73. package/dist/yargs-parser.js +929 -0
  74. package/dist/yargs-shim.d.ts +54 -0
  75. package/dist/yargs-shim.js +84 -0
  76. package/dist/yargs-usage.d.ts +42 -0
  77. package/dist/yargs-usage.js +479 -0
  78. package/dist/yargs-utils.d.ts +33 -0
  79. package/dist/yargs-utils.js +209 -0
  80. package/dist/yargs-validation.d.ts +26 -0
  81. package/dist/yargs-validation.js +261 -0
  82. package/dist/yargs-y18n.d.ts +21 -0
  83. package/dist/yargs-y18n.js +117 -0
  84. package/dist/yargs.d.ts +6 -0
  85. package/dist/yargs.js +8 -0
  86. package/locales/be.json +46 -0
  87. package/locales/cs.json +51 -0
  88. package/locales/de.json +46 -0
  89. package/locales/en.json +55 -0
  90. package/locales/es.json +46 -0
  91. package/locales/fi.json +49 -0
  92. package/locales/fr.json +53 -0
  93. package/locales/he.json +55 -0
  94. package/locales/hi.json +49 -0
  95. package/locales/hu.json +46 -0
  96. package/locales/id.json +50 -0
  97. package/locales/it.json +46 -0
  98. package/locales/ja.json +51 -0
  99. package/locales/ka.json +55 -0
  100. package/locales/ko.json +49 -0
  101. package/locales/nb.json +44 -0
  102. package/locales/nl.json +49 -0
  103. package/locales/nn.json +44 -0
  104. package/locales/pirate.json +13 -0
  105. package/locales/pl.json +49 -0
  106. package/locales/pt.json +45 -0
  107. package/locales/pt_BR.json +48 -0
  108. package/locales/ru.json +51 -0
  109. package/locales/th.json +46 -0
  110. package/locales/tr.json +48 -0
  111. package/locales/uk_UA.json +51 -0
  112. package/locales/uz.json +52 -0
  113. package/locales/zh_CN.json +48 -0
  114. package/locales/zh_TW.json +51 -0
  115. package/package.json +62 -9
  116. package/dist/index.js.map +0 -1
package/dist/help.js ADDED
@@ -0,0 +1,187 @@
1
+ import { styleText } from 'node:util';
2
+ import { kebab } from './names.js';
3
+ const identity = (s) => s;
4
+ const PLAIN = { heading: identity, command: identity, flag: identity, value: identity };
5
+ const DEFAULTS = {
6
+ heading: (s) => styleText('bold', s, { validateStream: false }),
7
+ command: (s) => styleText('bold', s, { validateStream: false }),
8
+ flag: (s) => styleText('cyan', s, { validateStream: false }),
9
+ value: (s) => styleText('dim', s, { validateStream: false }),
10
+ };
11
+ function painterFor(paint, kind) {
12
+ if (kind === 'command')
13
+ return paint.command;
14
+ return kind === 'flag' ? paint.flag : paint.value;
15
+ }
16
+ function paintOf(opts) {
17
+ if (opts.color !== true)
18
+ return PLAIN;
19
+ const theme = opts.theme ?? {};
20
+ return {
21
+ heading: theme.heading ?? DEFAULTS.heading,
22
+ command: theme.command ?? DEFAULTS.command,
23
+ flag: theme.flag ?? DEFAULTS.flag,
24
+ value: theme.value ?? DEFAULTS.value,
25
+ };
26
+ }
27
+ const DEFAULT_WIDTH = 100;
28
+ const INDENT = ' ';
29
+ const GUTTER = 2;
30
+ const TERM_SHARE = 0.4;
31
+ const GLOBAL = [
32
+ { term: '--json', text: 'machine-readable output', kind: 'flag' },
33
+ { term: '--help', text: 'show this help', kind: 'flag' },
34
+ ];
35
+ function deprecation(d) {
36
+ if (d === undefined || d === false)
37
+ return '';
38
+ return d === true ? ' (deprecated)' : ` (deprecated: use ${d})`;
39
+ }
40
+ function annotate(text, spec, verbose) {
41
+ const parts = [text];
42
+ if (spec.required === true)
43
+ parts.push('(required)');
44
+ if (spec.default !== undefined)
45
+ parts.push(`(default: ${String(spec.default)})`);
46
+ if (spec.choices !== undefined)
47
+ parts.push(`(one of: ${spec.choices.join(', ')})`);
48
+ if (spec.multiple === true)
49
+ parts.push('(repeatable)');
50
+ if (spec.env !== undefined)
51
+ parts.push(`[env: ${spec.env}]`);
52
+ if (verbose)
53
+ parts.push(`[${spec.type}]`);
54
+ return `${parts.filter((p) => p !== '').join(' ')}${deprecation(spec.deprecated)}`.trim();
55
+ }
56
+ function placeholder(spec) {
57
+ if (spec.placeholder !== undefined)
58
+ return spec.placeholder;
59
+ return spec.type === 'number' ? 'n' : 'value';
60
+ }
61
+ function optionTerm(name, spec) {
62
+ const short = spec.short === undefined ? '' : `-${spec.short}, `;
63
+ const value = spec.type === 'boolean' ? '' : ` <${placeholder(spec)}>`;
64
+ return `${short}--${kebab(name)}${value}`;
65
+ }
66
+ function optionRows(options, verbose) {
67
+ return Object.entries(options)
68
+ .filter(([, spec]) => spec.hidden !== true)
69
+ .map(([name, spec]) => ({ term: optionTerm(name, spec), text: annotate(spec.description ?? '', spec, verbose), kind: 'flag' }));
70
+ }
71
+ const argumentTerm = (a) => {
72
+ const name = a.variadic === true ? `${a.name}...` : a.name;
73
+ return a.required === false ? `[${name}]` : `<${name}>`;
74
+ };
75
+ function argumentRows(args) {
76
+ return args.map((a) => ({
77
+ term: argumentTerm(a),
78
+ text: [a.description ?? '', a.default === undefined ? '' : `(default: ${a.default})`].filter((p) => p !== '').join(' '),
79
+ kind: 'value',
80
+ }));
81
+ }
82
+ function commandSections(manifest, node) {
83
+ const children = manifest.commands.filter((c) => c.hidden !== true && c.path.length === node.path.length + 1 && node.path.every((seg, i) => c.path[i] === seg));
84
+ const groups = new Map();
85
+ for (const c of children) {
86
+ const heading = c.group ?? 'Commands:';
87
+ const rows = groups.get(heading) ?? [];
88
+ rows.push({ term: c.path[c.path.length - 1] ?? '', text: `${c.summary ?? c.description ?? ''}${deprecation(c.deprecated)}`.trim(), kind: 'command' });
89
+ groups.set(heading, rows);
90
+ }
91
+ return [...groups].map(([title, rows]) => ({ title, rows }));
92
+ }
93
+ function environmentRows(options) {
94
+ return Object.entries(options)
95
+ .filter(([, spec]) => spec.env !== undefined && spec.hidden !== true)
96
+ .map(([name, spec]) => ({ term: spec.env ?? '', text: `--${kebab(name)}`, kind: 'value' }));
97
+ }
98
+ function usageLine(node, root, hasChildren, paint) {
99
+ const shown = node.path.slice(root.length).join(' ') || node.path.join(' ');
100
+ const parts = [shown];
101
+ if (hasChildren)
102
+ parts.push('<command>');
103
+ parts.push('[options]');
104
+ for (const a of node.arguments ?? [])
105
+ parts.push(argumentTerm(a));
106
+ return `${paint.heading('Usage:')} ${parts.join(' ')}`;
107
+ }
108
+ export function wrap(text, width) {
109
+ const out = [];
110
+ for (const line of text.split('\n')) {
111
+ if (/^\s/.test(line) || line.length <= width) {
112
+ out.push(line);
113
+ continue;
114
+ }
115
+ let current = '';
116
+ for (const word of line.split(' ')) {
117
+ if (current !== '' && current.length + 1 + word.length > width) {
118
+ out.push(current);
119
+ current = word;
120
+ }
121
+ else
122
+ current = current === '' ? word : `${current} ${word}`;
123
+ }
124
+ out.push(current);
125
+ }
126
+ return out;
127
+ }
128
+ function termColumn(rows, width) {
129
+ const longest = Math.max(0, ...rows.map((r) => r.term.length));
130
+ return Math.min(longest, Math.floor(width * TERM_SHARE));
131
+ }
132
+ function layout(rows, width, column, paint) {
133
+ const textWidth = Math.max(1, width - INDENT.length - column - GUTTER);
134
+ const lines = [];
135
+ for (const { term, text, kind } of rows) {
136
+ const cell = painterFor(paint, kind)(term);
137
+ if (text === '') {
138
+ lines.push(`${INDENT}${cell}`);
139
+ continue;
140
+ }
141
+ const wrapped = wrap(text, textWidth);
142
+ const continuation = INDENT + ' '.repeat(column + GUTTER);
143
+ if (term.length > column) {
144
+ lines.push(`${INDENT}${cell}`, ...wrapped.map((l) => `${continuation}${l}`));
145
+ continue;
146
+ }
147
+ const pad = ' '.repeat(column + GUTTER - term.length);
148
+ lines.push(`${INDENT}${cell}${pad}${wrapped[0] ?? ''}`, ...wrapped.slice(1).map((l) => `${continuation}${l}`));
149
+ }
150
+ return lines;
151
+ }
152
+ function exampleLines(examples, width) {
153
+ const lines = [];
154
+ for (const e of examples) {
155
+ lines.push(`${INDENT}$ ${e.command}`);
156
+ if (e.description !== undefined)
157
+ lines.push(...wrap(e.description, width - INDENT.length * 2).map((l) => `${INDENT}${INDENT}${l}`));
158
+ }
159
+ return lines;
160
+ }
161
+ function section(title, body, paint) {
162
+ return body.length === 0 ? [] : [paint.heading(title), ...body, ''];
163
+ }
164
+ export function renderHelp(manifest, node, opts = {}) {
165
+ const width = opts.width ?? DEFAULT_WIDTH;
166
+ const verbose = opts.verbose === true;
167
+ const paint = paintOf(opts);
168
+ const root = manifest.rootPath;
169
+ const commands = commandSections(manifest, node);
170
+ const args = argumentRows(node.arguments ?? []);
171
+ const options = optionRows(node.options, verbose);
172
+ const env = environmentRows(node.options);
173
+ const column = termColumn([...args, ...options, ...GLOBAL, ...commands.flatMap((s) => s.rows), ...env], width);
174
+ const lines = [usageLine(node, root, commands.length > 0, paint), ''];
175
+ if (node.description !== undefined)
176
+ lines.push(...wrap(`${node.description}${deprecation(node.deprecated)}`, width), '');
177
+ lines.push(...section('Arguments:', layout(args, width, column, paint), paint));
178
+ lines.push(...section('Options:', layout(options, width, column, paint), paint));
179
+ lines.push(...section('Global options:', layout(GLOBAL, width, column, paint), paint));
180
+ for (const s of commands)
181
+ lines.push(...section(s.title, layout(s.rows, width, column, paint), paint));
182
+ lines.push(...section('Examples:', exampleLines(node.examples ?? [], width), paint));
183
+ lines.push(...section('Environment:', layout(env, width, column, paint), paint));
184
+ if (node.epilogue !== undefined)
185
+ lines.push(...wrap(node.epilogue, width), '');
186
+ return `${lines.join('\n').trimEnd()}\n`;
187
+ }
package/dist/index.d.ts CHANGED
@@ -1 +1,16 @@
1
- export declare const VERSION = "0.0.0";
1
+ /**
2
+ * burgee — a command declares itself once; every surface is that declaration
3
+ * read by a different reader.
4
+ *
5
+ * The public entry, and only that. The execution core lives in execute.ts so a
6
+ * façade can import it without pulling this barrel.
7
+ */
8
+ export { ExitCode, isExitCode, type ExitCode as ExitCodeValue } from './exit-code.js';
9
+ export { defineCommand, defineProgram, execute, resolveCommand, run, runCommand, sharedOptions, type AnyCommand, type Command, type CommandContext, type InferOptions, type OptionSpecs, type Program, type RunOptions, type RunResult, } from './execute.js';
10
+ export { camel, checkDefinition, kebab, UsageError } from './validate.js';
11
+ export { AGENT_PROBES, detectAgent, type AgentProbe, type Detection } from './agent.js';
12
+ export { renderHelp, type HelpOptions, type HelpTheme, type HelpToken } from './help.js';
13
+ export { annotationsOf, MCP_PROTOCOL_VERSION, serveMcp, toolsOf, type Invoke, type ServeOptions, type Tool, type ToolAnnotations } from './mcp.js';
14
+ export { ConfigError, envName, explain, resolve, screaming, type Candidate, type Layers, type Provenance, type Resolution, type Source } from './precedence.js';
15
+ export { commandSchemaOf, inputSchemaOf, schemaOf, summaryOf, type CommandSchema, type JsonSchema, type ProgramSchema, type SchemaSummary } from './schema.js';
16
+ export { definePlugin, Manifest, type ArgumentSpec, type CommandNode, type Effects, type Example, type Hook, type LazyModule, type OptionSpec, type ActionRequiredSpec, type Plugin, type Relation, type RunContext, type StandardResult, type StandardSchemaV1, } from './manifest.js';
package/dist/index.js CHANGED
@@ -1,2 +1,9 @@
1
- export const VERSION = "0.0.0";
2
- //# sourceMappingURL=index.js.map
1
+ export { ExitCode, isExitCode } from './exit-code.js';
2
+ export { defineCommand, defineProgram, execute, resolveCommand, run, runCommand, sharedOptions, } from './execute.js';
3
+ export { camel, checkDefinition, kebab, UsageError } from './validate.js';
4
+ export { AGENT_PROBES, detectAgent } from './agent.js';
5
+ export { renderHelp } from './help.js';
6
+ export { annotationsOf, MCP_PROTOCOL_VERSION, serveMcp, toolsOf } from './mcp.js';
7
+ export { ConfigError, envName, explain, resolve, screaming } from './precedence.js';
8
+ export { commandSchemaOf, inputSchemaOf, schemaOf, summaryOf } from './schema.js';
9
+ export { definePlugin, Manifest, } from './manifest.js';
@@ -0,0 +1,207 @@
1
+ /**
2
+ * The manifest — the one thing every surface is a projection of.
3
+ *
4
+ * Commands reach it through a façade (`burgee/commander`, `burgee/yargs`) or
5
+ * natively, and it does not record which. That is the whole reason a plugin
6
+ * written once works on every rung of the adoption ladder (J7, J8).
7
+ */
8
+ /**
9
+ * The Standard Schema interface (standardschema.dev), declared here so any implementation
10
+ * — zod, valibot, arktype — is accepted as an option's `schema` without a dependency (S1).
11
+ */
12
+ export interface StandardSchemaV1<Output = unknown> {
13
+ readonly '~standard': {
14
+ readonly version: 1;
15
+ readonly vendor: string;
16
+ readonly validate: (value: unknown) => StandardResult<Output> | Promise<StandardResult<Output>>;
17
+ };
18
+ }
19
+ export type StandardResult<Output> = {
20
+ readonly value: Output;
21
+ readonly issues?: undefined;
22
+ } | {
23
+ readonly issues: readonly {
24
+ readonly message: string;
25
+ }[];
26
+ };
27
+ /**
28
+ * One option, declared once (S1). The key is the canonical camelCase name the handler
29
+ * reads; the CLI form is derived as kebab-case (S5): `dryRun` is typed `--dry-run`.
30
+ */
31
+ export interface OptionSpec {
32
+ /** `boolean` never consumes a value (S7); `number` rejects NaN and Infinity (S3). */
33
+ type: 'string' | 'boolean' | 'number';
34
+ description?: string;
35
+ required?: boolean;
36
+ short?: string;
37
+ default?: string | boolean | number | readonly string[] | readonly number[];
38
+ /** Environment variable consulted when the flag is absent (V2). Read from the injected env, never process.env directly. */
39
+ env?: string;
40
+ /** Allowed values, enforced and shown as `(one of: a, b)` (yargs #1408, #1186). */
41
+ choices?: readonly string[];
42
+ /** Repeatable, and split on `separator` (`,` unless declared): `--tag a --tag b,c` → `['a', 'b', 'c']` (S8). */
43
+ multiple?: boolean;
44
+ separator?: string;
45
+ /** `number` only. */
46
+ minimum?: number;
47
+ maximum?: number;
48
+ integer?: boolean;
49
+ /** Any Standard Schema, run on the parsed value; its issues become a usage error (S1). */
50
+ schema?: StandardSchemaV1;
51
+ /** The value's name in help: `--id <dataset-id>` (yargs #833). */
52
+ placeholder?: string;
53
+ /** `true` renders `(deprecated)`; a string names the replacement: `(deprecated: use --force)` (yargs #2248). */
54
+ deprecated?: boolean | string;
55
+ hidden?: boolean;
56
+ /** The shared set this option was copied from (M4); `--schema` carries it, help lists the option like any other. */
57
+ sharedFrom?: string;
58
+ }
59
+ /** What a lazily loaded command module exports: the handler as `run` or as the default export (M2). */
60
+ export interface LazyModule {
61
+ default?: (ctx: RunContext) => unknown;
62
+ run?: (ctx: RunContext) => unknown;
63
+ }
64
+ /**
65
+ * What running a command does to the world (N6). Declared, never inferred: it decides
66
+ * whether the command is exposed as an MCP tool at all (N2) and generates the tool's
67
+ * `readOnlyHint` / `idempotentHint` / `destructiveHint` — MCP defaults `destructiveHint`
68
+ * to true, so silence is the dangerous reading.
69
+ */
70
+ export type Effects = 'read_only' | 'idempotent' | 'non_idempotent';
71
+ /**
72
+ * A relationship between options, validated after parsing and before choices and the
73
+ * handler (S2, S6). `implies` takes a second option name, or a predicate over the values.
74
+ */
75
+ export type Relation = {
76
+ exactlyOneOf: readonly string[];
77
+ } | {
78
+ atLeastOneOf: readonly string[];
79
+ } | {
80
+ atMostOneOf: readonly string[];
81
+ } | {
82
+ conflicts: readonly string[];
83
+ } | {
84
+ implies: readonly [string, string | ((values: Record<string, unknown>) => boolean)];
85
+ };
86
+ /** A positional, as help documents it (yargs #2012). */
87
+ export interface ArgumentSpec {
88
+ name: string;
89
+ description?: string;
90
+ required?: boolean;
91
+ variadic?: boolean;
92
+ default?: string;
93
+ }
94
+ /** One example: a single copy-pasteable command line, the description below it (H2). */
95
+ export interface Example {
96
+ command: string;
97
+ description?: string;
98
+ }
99
+ /** What a handler receives. `passthrough` is everything after `--`, verbatim (G5). */
100
+ /** What a caller must do before the command can continue (N11): synthesised into the envelope. */
101
+ export interface ActionRequiredSpec {
102
+ /** A short machine-readable reason: `login`, `confirm`, `missing-config` … */
103
+ reason: string;
104
+ message: string;
105
+ /** Runnable commands, each with when to run it; the engine prefixes the program and carries the caller's flags. */
106
+ next?: readonly {
107
+ command: string;
108
+ when: string;
109
+ }[];
110
+ hint?: string;
111
+ }
112
+ export interface RunContext {
113
+ options: Record<string, unknown>;
114
+ positionals: string[];
115
+ passthrough: string[];
116
+ env: Record<string, string | undefined>;
117
+ /** Exit with an E1 code. Unwinds cleanly: the code is honoured and nothing is printed. */
118
+ exit: (code: number) => never;
119
+ /** A person may be prompted (N12): a terminal, no detected agent, or `FORCE_TTY=1`. */
120
+ interactive: boolean;
121
+ /** The agent the environment names, if any (N12). */
122
+ agent?: string;
123
+ /** Stop and tell the caller what to do instead of blocking on a prompt (N11). */
124
+ actionRequired: (spec: ActionRequiredSpec) => never;
125
+ }
126
+ export interface CommandNode {
127
+ path: string[];
128
+ description?: string;
129
+ /** Shown in command lists instead of the description (yargs #1265). */
130
+ summary?: string;
131
+ options: Record<string, OptionSpec>;
132
+ relations?: readonly Relation[];
133
+ arguments?: ArgumentSpec[];
134
+ examples?: Example[];
135
+ /** Heading this command is listed under in its parent's help (yargs #684). */
136
+ group?: string;
137
+ epilogue?: string;
138
+ hidden?: boolean;
139
+ deprecated?: boolean | string;
140
+ /** Required for a command to be served as an MCP tool (N2, N6). */
141
+ effects?: Effects;
142
+ run?: (ctx: RunContext) => unknown;
143
+ /**
144
+ * The handler's module, imported on dispatch only (M2): the manifest — help, schema,
145
+ * completions, MCP tool list — is complete from this node without loading it. A node
146
+ * with `load` and no `run` gets a `run` that imports on first call.
147
+ */
148
+ load?: () => Promise<LazyModule>;
149
+ /** Which plugin contributed this, if any. Declared, never diffed (M3). */
150
+ plugin?: string;
151
+ }
152
+ /** `run` for a lazy node: the module is imported on the first call and never before (M2). */
153
+ export declare function lazyRun(load: () => Promise<LazyModule>): (ctx: RunContext) => unknown;
154
+ /** A hook may declare which commands it applies to, as data. */
155
+ export interface HookFilter {
156
+ command?: RegExp;
157
+ }
158
+ export interface Hook {
159
+ filter?: HookFilter;
160
+ handler: (ctx: {
161
+ command: string;
162
+ options: Record<string, unknown>;
163
+ }) => void | Promise<void>;
164
+ }
165
+ export interface Plugin {
166
+ name: string;
167
+ commands?: CommandNode[];
168
+ hooks?: {
169
+ preRun?: Hook;
170
+ postRun?: Hook;
171
+ onError?: Hook;
172
+ };
173
+ enforce?: 'pre' | 'post';
174
+ }
175
+ export declare function definePlugin(plugin: Plugin): Plugin;
176
+ /** Rolldown's lesson: evaluate the filter before crossing the boundary. */
177
+ export declare function hookApplies(hook: Hook | undefined, command: string): hook is Hook;
178
+ export declare class Manifest {
179
+ readonly commands: CommandNode[];
180
+ readonly plugins: Plugin[];
181
+ /** The program's own name, which the user never types; `execute` strips it. */
182
+ rootPath: string[];
183
+ /** Reported by `--schema`, `--version` and the MCP handshake; the owning package.json otherwise (V4). */
184
+ version?: string;
185
+ /** With a prefix, every option reads `PREFIX_OPTION_NAME` unless it names its own env (V2). */
186
+ envPrefix?: string;
187
+ /** Config discovery is opt-in; the name is the file stem and the package.json field (V6). */
188
+ config?: {
189
+ name: string;
190
+ };
191
+ /** Characters of `--schema` output above which it is summarised (N13). */
192
+ schemaBudget?: number;
193
+ add(node: CommandNode): void;
194
+ use(plugin: Plugin): void;
195
+ /** `enforce: 'pre'` first, then unordered, then `'post'` — the Vite/Rolldown convention. */
196
+ private ordered;
197
+ fire(stage: 'preRun' | 'postRun' | 'onError', command: string, options: Record<string, unknown>): Promise<void>;
198
+ find(path: string[]): CommandNode | undefined;
199
+ /**
200
+ * Longest-prefix match of argv against declared command paths. The root's own
201
+ * name is not typed by the user, so it is skipped when matching.
202
+ */
203
+ resolve(argv: string[], root?: string[]): {
204
+ node: CommandNode | undefined;
205
+ rest: string[];
206
+ };
207
+ }
@@ -0,0 +1,66 @@
1
+ export function lazyRun(load) {
2
+ let loaded;
3
+ return async (ctx) => {
4
+ loaded ??= load();
5
+ const mod = await loaded;
6
+ const handler = mod.run ?? mod.default;
7
+ if (handler === undefined)
8
+ throw new Error('burgee: a lazy command module must export its handler as run or as the default export');
9
+ return await handler(ctx);
10
+ };
11
+ }
12
+ export function definePlugin(plugin) {
13
+ return plugin;
14
+ }
15
+ export function hookApplies(hook, command) {
16
+ if (hook === undefined)
17
+ return false;
18
+ return hook.filter?.command === undefined || hook.filter.command.test(command);
19
+ }
20
+ const ORDER = { pre: 0, post: 2 };
21
+ export class Manifest {
22
+ commands = [];
23
+ plugins = [];
24
+ rootPath = [];
25
+ version;
26
+ envPrefix;
27
+ config;
28
+ schemaBudget;
29
+ add(node) {
30
+ this.commands.push(node.load !== undefined && node.run === undefined ? { ...node, run: lazyRun(node.load) } : node);
31
+ }
32
+ use(plugin) {
33
+ this.plugins.push(plugin);
34
+ for (const command of plugin.commands ?? []) {
35
+ this.add({ ...command, plugin: plugin.name });
36
+ }
37
+ }
38
+ ordered() {
39
+ return [...this.plugins].sort((a, b) => (a.enforce === undefined ? 1 : ORDER[a.enforce]) - (b.enforce === undefined ? 1 : ORDER[b.enforce]));
40
+ }
41
+ async fire(stage, command, options) {
42
+ for (const plugin of this.ordered()) {
43
+ const hook = plugin.hooks?.[stage];
44
+ if (hookApplies(hook, command))
45
+ await hook.handler({ command, options });
46
+ }
47
+ }
48
+ find(path) {
49
+ const key = path.join(' ');
50
+ return this.commands.find((c) => c.path.join(' ') === key);
51
+ }
52
+ resolve(argv, root = []) {
53
+ let best;
54
+ let depth = 0;
55
+ for (const node of this.commands) {
56
+ const typed = node.path.slice(root.length);
57
+ if (typed.length > argv.length)
58
+ continue;
59
+ if (typed.every((seg, i) => argv[i] === seg) && typed.length >= depth) {
60
+ best = node;
61
+ depth = typed.length;
62
+ }
63
+ }
64
+ return { node: best, rest: argv.slice(depth) };
65
+ }
66
+ }
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { type CommandNode, type Effects, type Manifest } from './manifest.js';
2
+ import { type JsonSchema } from './schema.js';
3
+ export declare const MCP_PROTOCOL_VERSION = "2025-06-18";
4
+ export interface ToolAnnotations {
5
+ readOnlyHint: boolean;
6
+ idempotentHint: boolean;
7
+ destructiveHint: boolean;
8
+ }
9
+ export interface Tool {
10
+ name: string;
11
+ description: string;
12
+ inputSchema: JsonSchema;
13
+ annotations: ToolAnnotations;
14
+ }
15
+ /** What a tool call runs: the same execute a `--json` caller reaches, with the streams captured. */
16
+ export type Invoke = (argv: string[]) => Promise<{
17
+ stdout: string;
18
+ stderr: string;
19
+ code: number;
20
+ }>;
21
+ /** MCP's hints, from the declared effects. `destructiveHint` is only ever false by declaration. */
22
+ export declare function annotationsOf(effects: Effects): ToolAnnotations;
23
+ /** `config get` → `config_get`: MCP tool names are `[a-zA-Z0-9_-]`. */
24
+ export declare const toolName: (node: CommandNode, root: string[]) => string;
25
+ /** The tool list: every runnable, visible command that declared its effects. */
26
+ export declare function toolsOf(manifest: Manifest): Tool[];
27
+ /** A tool call's arguments back into argv: the command, its options, `--json`, then positionals in declared order. */
28
+ export declare function argvOf(node: CommandNode, root: string[], args: Record<string, unknown>): string[];
29
+ export interface ServeOptions {
30
+ input: NodeJS.ReadableStream;
31
+ output: {
32
+ write: (s: string) => unknown;
33
+ };
34
+ invoke: Invoke;
35
+ }
36
+ /** A running server: `done` settles when the input closes; `swap` serves a new manifest and says so (W2). */
37
+ export interface McpServer {
38
+ done: Promise<void>;
39
+ /**
40
+ * Serve this manifest (and its invoke) from the next request on, and emit
41
+ * `notifications/tools/list_changed` so a connected client re-lists. A call already in
42
+ * flight finishes against the manifest it started on.
43
+ */
44
+ swap: (manifest: Manifest, invoke?: Invoke) => void;
45
+ }
46
+ /**
47
+ * Start serving; `done` settles when the input closes. Notifications (no `id`) get no
48
+ * reply; a malformed line gets a JSON-RPC error with a null id, as the spec asks.
49
+ */
50
+ export declare function startMcp(manifest: Manifest, opts: ServeOptions): McpServer;
51
+ /** Serve until the input closes. */
52
+ export declare function serveMcp(manifest: Manifest, opts: ServeOptions): Promise<void>;
package/dist/mcp.js ADDED
@@ -0,0 +1,124 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { inputSchemaOf, runnable, typedName } from './schema.js';
3
+ export const MCP_PROTOCOL_VERSION = '2025-06-18';
4
+ const JSON_RPC_INVALID_REQUEST = -32600;
5
+ const JSON_RPC_METHOD_NOT_FOUND = -32601;
6
+ const JSON_RPC_INVALID_PARAMS = -32602;
7
+ export function annotationsOf(effects) {
8
+ return {
9
+ readOnlyHint: effects === 'read_only',
10
+ idempotentHint: effects !== 'non_idempotent',
11
+ destructiveHint: effects === 'non_idempotent',
12
+ };
13
+ }
14
+ export const toolName = (node, root) => typedName(node, root).replaceAll(' ', '_');
15
+ function describe(node) {
16
+ const parts = [node.description ?? node.summary ?? ''];
17
+ for (const e of node.examples ?? [])
18
+ parts.push(`Example: ${e.command}${e.description === undefined ? '' : ` — ${e.description}`}`);
19
+ return parts.filter((p) => p !== '').join('\n');
20
+ }
21
+ export function toolsOf(manifest) {
22
+ return runnable(manifest)
23
+ .filter((c) => c.effects !== undefined)
24
+ .map((c) => ({ name: toolName(c, manifest.rootPath), description: describe(c), inputSchema: inputSchemaOf(c), annotations: annotationsOf(c.effects) }));
25
+ }
26
+ function optionArgs(node, args) {
27
+ const out = [];
28
+ for (const [name, spec] of Object.entries(node.options)) {
29
+ const value = args[name];
30
+ if (value === undefined || value === null)
31
+ continue;
32
+ if (spec.type === 'boolean') {
33
+ if (value === true)
34
+ out.push(`--${name}`);
35
+ }
36
+ else
37
+ out.push(`--${name}`, String(value));
38
+ }
39
+ return out;
40
+ }
41
+ function positionalArgs(node, args) {
42
+ const out = [];
43
+ for (const a of node.arguments ?? []) {
44
+ const value = args[a.name];
45
+ if (value === undefined || value === null)
46
+ continue;
47
+ out.push(...(Array.isArray(value) ? value.map(String) : [String(value)]));
48
+ }
49
+ return out;
50
+ }
51
+ export function argvOf(node, root, args) {
52
+ const command = typedName(node, root).split(' ').filter((s) => s !== '');
53
+ return [...command, ...optionArgs(node, args), '--json', ...positionalArgs(node, args)];
54
+ }
55
+ async function callTool(session, params) {
56
+ const { manifest, invoke } = session;
57
+ const root = manifest.rootPath;
58
+ const given = params ?? {};
59
+ const raw = given['name'];
60
+ const name = typeof raw === 'string' ? raw : '';
61
+ const exposed = new Set(toolsOf(manifest).map((t) => t.name));
62
+ const node = exposed.has(name) ? runnable(manifest).find((c) => toolName(c, root) === name) : undefined;
63
+ if (node === undefined)
64
+ return { error: { code: JSON_RPC_INVALID_PARAMS, message: `unknown tool "${name}"` } };
65
+ const args = (given['arguments'] ?? {});
66
+ const { stdout, stderr, code } = await invoke(argvOf(node, root, args));
67
+ const text = stdout.trim() !== '' ? stdout.trim() : stderr.trim();
68
+ return { result: { content: [{ type: 'text', text }], isError: code !== 0 } };
69
+ }
70
+ async function handle(session, request) {
71
+ switch (request.method) {
72
+ case 'initialize':
73
+ return { result: { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: { tools: {} }, serverInfo: session.serverInfo } };
74
+ case 'ping':
75
+ return { result: {} };
76
+ case 'tools/list':
77
+ return { result: { tools: toolsOf(session.manifest) } };
78
+ case 'tools/call':
79
+ return await callTool(session, request.params);
80
+ default:
81
+ return { error: { code: JSON_RPC_METHOD_NOT_FOUND, message: `method not found: ${request.method}` } };
82
+ }
83
+ }
84
+ export function startMcp(manifest, opts) {
85
+ const session = {
86
+ manifest,
87
+ invoke: opts.invoke,
88
+ serverInfo: { name: manifest.rootPath.join(' ') || 'burgee', version: manifest.version ?? '0.0.0' },
89
+ };
90
+ const reply = (body) => void opts.output.write(`${JSON.stringify({ jsonrpc: '2.0', ...body })}\n`);
91
+ const swap = (next, invoke) => {
92
+ session.manifest = next;
93
+ if (invoke !== undefined)
94
+ session.invoke = invoke;
95
+ reply({ method: 'notifications/tools/list_changed' });
96
+ };
97
+ const done = serve(session, opts.input, reply);
98
+ return { done, swap };
99
+ }
100
+ export async function serveMcp(manifest, opts) {
101
+ return await startMcp(manifest, opts).done;
102
+ }
103
+ async function serve(session, input, reply) {
104
+ for await (const line of createInterface({ input, crlfDelay: Infinity })) {
105
+ if (line.trim() === '')
106
+ continue;
107
+ let request;
108
+ try {
109
+ request = JSON.parse(line);
110
+ }
111
+ catch {
112
+ reply({ id: null, error: { code: JSON_RPC_INVALID_REQUEST, message: 'invalid JSON' } });
113
+ continue;
114
+ }
115
+ if (typeof request.method !== 'string') {
116
+ reply({ id: request.id ?? null, error: { code: JSON_RPC_INVALID_REQUEST, message: 'missing method' } });
117
+ continue;
118
+ }
119
+ if (request.id === undefined)
120
+ continue;
121
+ const outcome = (await handle(session, request));
122
+ reply({ id: request.id, ...outcome });
123
+ }
124
+ }
@@ -0,0 +1,5 @@
1
+ /** One canonical camelCase key per option; kebab-case on the command line (S5, yargs #1679). */
2
+ /** `dryRun` → `dry-run`; a kebab key stays as it is. */
3
+ export declare function kebab(name: string): string;
4
+ /** `--dry-run` on the command line reaches the handler as `dryRun`. */
5
+ export declare function camel(flag: string): string;
package/dist/names.js ADDED
@@ -0,0 +1,6 @@
1
+ export function kebab(name) {
2
+ return name.replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
3
+ }
4
+ export function camel(flag) {
5
+ return flag.replaceAll(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
6
+ }