burgee 0.0.0 → 0.2.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 (114) 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 +51 -0
  7. package/dist/cli.js +105 -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 +345 -0
  11. package/dist/commander-command.js +1605 -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/execute.d.ts +97 -0
  29. package/dist/execute.js +433 -0
  30. package/dist/exit-code.d.ts +18 -0
  31. package/dist/exit-code.js +12 -0
  32. package/dist/help.d.ts +22 -0
  33. package/dist/help.js +158 -0
  34. package/dist/index.d.ts +16 -1
  35. package/dist/index.js +9 -2
  36. package/dist/manifest.d.ts +192 -0
  37. package/dist/manifest.js +55 -0
  38. package/dist/mcp.d.ts +40 -0
  39. package/dist/mcp.js +111 -0
  40. package/dist/names.d.ts +5 -0
  41. package/dist/names.js +6 -0
  42. package/dist/pkg.d.ts +5 -0
  43. package/dist/pkg.js +21 -0
  44. package/dist/precedence.d.ts +55 -0
  45. package/dist/precedence.js +100 -0
  46. package/dist/runtime.d.ts +27 -0
  47. package/dist/runtime.js +17 -0
  48. package/dist/schema.d.ts +71 -0
  49. package/dist/schema.js +108 -0
  50. package/dist/testing-helpers.d.ts +62 -0
  51. package/dist/testing-helpers.js +110 -0
  52. package/dist/testing.d.ts +10 -0
  53. package/dist/testing.js +3 -0
  54. package/dist/validate.d.ts +27 -0
  55. package/dist/validate.js +133 -0
  56. package/dist/yargs-burgee.d.ts +50 -0
  57. package/dist/yargs-burgee.js +104 -0
  58. package/dist/yargs-cliui.d.ts +56 -0
  59. package/dist/yargs-cliui.js +421 -0
  60. package/dist/yargs-command.d.ts +82 -0
  61. package/dist/yargs-command.js +414 -0
  62. package/dist/yargs-completion.d.ts +41 -0
  63. package/dist/yargs-completion.js +271 -0
  64. package/dist/yargs-factory.d.ts +193 -0
  65. package/dist/yargs-factory.js +1606 -0
  66. package/dist/yargs-helpers.d.ts +6 -0
  67. package/dist/yargs-helpers.js +2 -0
  68. package/dist/yargs-middleware.d.ts +32 -0
  69. package/dist/yargs-middleware.js +81 -0
  70. package/dist/yargs-parser.d.ts +41 -0
  71. package/dist/yargs-parser.js +929 -0
  72. package/dist/yargs-shim.d.ts +54 -0
  73. package/dist/yargs-shim.js +84 -0
  74. package/dist/yargs-usage.d.ts +42 -0
  75. package/dist/yargs-usage.js +479 -0
  76. package/dist/yargs-utils.d.ts +33 -0
  77. package/dist/yargs-utils.js +209 -0
  78. package/dist/yargs-validation.d.ts +26 -0
  79. package/dist/yargs-validation.js +261 -0
  80. package/dist/yargs-y18n.d.ts +21 -0
  81. package/dist/yargs-y18n.js +117 -0
  82. package/dist/yargs.d.ts +6 -0
  83. package/dist/yargs.js +8 -0
  84. package/locales/be.json +46 -0
  85. package/locales/cs.json +51 -0
  86. package/locales/de.json +46 -0
  87. package/locales/en.json +55 -0
  88. package/locales/es.json +46 -0
  89. package/locales/fi.json +49 -0
  90. package/locales/fr.json +53 -0
  91. package/locales/he.json +55 -0
  92. package/locales/hi.json +49 -0
  93. package/locales/hu.json +46 -0
  94. package/locales/id.json +50 -0
  95. package/locales/it.json +46 -0
  96. package/locales/ja.json +51 -0
  97. package/locales/ka.json +55 -0
  98. package/locales/ko.json +49 -0
  99. package/locales/nb.json +44 -0
  100. package/locales/nl.json +49 -0
  101. package/locales/nn.json +44 -0
  102. package/locales/pirate.json +13 -0
  103. package/locales/pl.json +49 -0
  104. package/locales/pt.json +45 -0
  105. package/locales/pt_BR.json +48 -0
  106. package/locales/ru.json +51 -0
  107. package/locales/th.json +46 -0
  108. package/locales/tr.json +48 -0
  109. package/locales/uk_UA.json +51 -0
  110. package/locales/uz.json +52 -0
  111. package/locales/zh_CN.json +48 -0
  112. package/locales/zh_TW.json +51 -0
  113. package/package.json +61 -8
  114. package/dist/index.js.map +0 -1
package/dist/help.js ADDED
@@ -0,0 +1,158 @@
1
+ import { kebab } from './names.js';
2
+ const DEFAULT_WIDTH = 100;
3
+ const INDENT = ' ';
4
+ const GUTTER = 2;
5
+ const TERM_SHARE = 0.4;
6
+ const GLOBAL = [
7
+ { term: '--json', text: 'machine-readable output' },
8
+ { term: '--help', text: 'show this help' },
9
+ ];
10
+ function deprecation(d) {
11
+ if (d === undefined || d === false)
12
+ return '';
13
+ return d === true ? ' (deprecated)' : ` (deprecated: use ${d})`;
14
+ }
15
+ function annotate(text, spec, verbose) {
16
+ const parts = [text];
17
+ if (spec.required === true)
18
+ parts.push('(required)');
19
+ if (spec.default !== undefined)
20
+ parts.push(`(default: ${String(spec.default)})`);
21
+ if (spec.choices !== undefined)
22
+ parts.push(`(one of: ${spec.choices.join(', ')})`);
23
+ if (spec.multiple === true)
24
+ parts.push('(repeatable)');
25
+ if (spec.env !== undefined)
26
+ parts.push(`[env: ${spec.env}]`);
27
+ if (verbose)
28
+ parts.push(`[${spec.type}]`);
29
+ return `${parts.filter((p) => p !== '').join(' ')}${deprecation(spec.deprecated)}`.trim();
30
+ }
31
+ function placeholder(spec) {
32
+ if (spec.placeholder !== undefined)
33
+ return spec.placeholder;
34
+ return spec.type === 'number' ? 'n' : 'value';
35
+ }
36
+ function optionTerm(name, spec) {
37
+ const short = spec.short === undefined ? '' : `-${spec.short}, `;
38
+ const value = spec.type === 'boolean' ? '' : ` <${placeholder(spec)}>`;
39
+ return `${short}--${kebab(name)}${value}`;
40
+ }
41
+ function optionRows(options, verbose) {
42
+ return Object.entries(options)
43
+ .filter(([, spec]) => spec.hidden !== true)
44
+ .map(([name, spec]) => ({ term: optionTerm(name, spec), text: annotate(spec.description ?? '', spec, verbose) }));
45
+ }
46
+ const argumentTerm = (a) => {
47
+ const name = a.variadic === true ? `${a.name}...` : a.name;
48
+ return a.required === false ? `[${name}]` : `<${name}>`;
49
+ };
50
+ function argumentRows(args) {
51
+ return args.map((a) => ({
52
+ term: argumentTerm(a),
53
+ text: [a.description ?? '', a.default === undefined ? '' : `(default: ${a.default})`].filter((p) => p !== '').join(' '),
54
+ }));
55
+ }
56
+ function commandSections(manifest, node) {
57
+ 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));
58
+ const groups = new Map();
59
+ for (const c of children) {
60
+ const heading = c.group ?? 'Commands:';
61
+ const rows = groups.get(heading) ?? [];
62
+ rows.push({ term: c.path[c.path.length - 1] ?? '', text: `${c.summary ?? c.description ?? ''}${deprecation(c.deprecated)}`.trim() });
63
+ groups.set(heading, rows);
64
+ }
65
+ return [...groups].map(([title, rows]) => ({ title, rows }));
66
+ }
67
+ function environmentRows(options) {
68
+ return Object.entries(options)
69
+ .filter(([, spec]) => spec.env !== undefined && spec.hidden !== true)
70
+ .map(([name, spec]) => ({ term: spec.env ?? '', text: `--${kebab(name)}` }));
71
+ }
72
+ function usageLine(node, root, hasChildren) {
73
+ const shown = node.path.slice(root.length).join(' ') || node.path.join(' ');
74
+ const parts = [shown];
75
+ if (hasChildren)
76
+ parts.push('<command>');
77
+ parts.push('[options]');
78
+ for (const a of node.arguments ?? [])
79
+ parts.push(argumentTerm(a));
80
+ return `Usage: ${parts.join(' ')}`;
81
+ }
82
+ export function wrap(text, width) {
83
+ const out = [];
84
+ for (const line of text.split('\n')) {
85
+ if (/^\s/.test(line) || line.length <= width) {
86
+ out.push(line);
87
+ continue;
88
+ }
89
+ let current = '';
90
+ for (const word of line.split(' ')) {
91
+ if (current !== '' && current.length + 1 + word.length > width) {
92
+ out.push(current);
93
+ current = word;
94
+ }
95
+ else
96
+ current = current === '' ? word : `${current} ${word}`;
97
+ }
98
+ out.push(current);
99
+ }
100
+ return out;
101
+ }
102
+ function termColumn(rows, width) {
103
+ const longest = Math.max(0, ...rows.map((r) => r.term.length));
104
+ return Math.min(longest, Math.floor(width * TERM_SHARE));
105
+ }
106
+ function layout(rows, width, column) {
107
+ const textWidth = Math.max(1, width - INDENT.length - column - GUTTER);
108
+ const lines = [];
109
+ for (const { term, text } of rows) {
110
+ if (text === '') {
111
+ lines.push(`${INDENT}${term}`);
112
+ continue;
113
+ }
114
+ const wrapped = wrap(text, textWidth);
115
+ const continuation = INDENT + ' '.repeat(column + GUTTER);
116
+ if (term.length > column) {
117
+ lines.push(`${INDENT}${term}`, ...wrapped.map((l) => `${continuation}${l}`));
118
+ continue;
119
+ }
120
+ lines.push(`${INDENT}${term.padEnd(column + GUTTER)}${wrapped[0] ?? ''}`, ...wrapped.slice(1).map((l) => `${continuation}${l}`));
121
+ }
122
+ return lines;
123
+ }
124
+ function exampleLines(examples, width) {
125
+ const lines = [];
126
+ for (const e of examples) {
127
+ lines.push(`${INDENT}$ ${e.command}`);
128
+ if (e.description !== undefined)
129
+ lines.push(...wrap(e.description, width - INDENT.length * 2).map((l) => `${INDENT}${INDENT}${l}`));
130
+ }
131
+ return lines;
132
+ }
133
+ function section(title, body) {
134
+ return body.length === 0 ? [] : [title, ...body, ''];
135
+ }
136
+ export function renderHelp(manifest, node, opts = {}) {
137
+ const width = opts.width ?? DEFAULT_WIDTH;
138
+ const verbose = opts.verbose === true;
139
+ const root = manifest.rootPath;
140
+ const commands = commandSections(manifest, node);
141
+ const args = argumentRows(node.arguments ?? []);
142
+ const options = optionRows(node.options, verbose);
143
+ const env = environmentRows(node.options);
144
+ const column = termColumn([...args, ...options, ...GLOBAL, ...commands.flatMap((s) => s.rows), ...env], width);
145
+ const lines = [usageLine(node, root, commands.length > 0), ''];
146
+ if (node.description !== undefined)
147
+ lines.push(...wrap(`${node.description}${deprecation(node.deprecated)}`, width), '');
148
+ lines.push(...section('Arguments:', layout(args, width, column)));
149
+ lines.push(...section('Options:', layout(options, width, column)));
150
+ lines.push(...section('Global options:', layout(GLOBAL, width, column)));
151
+ for (const s of commands)
152
+ lines.push(...section(s.title, layout(s.rows, width, column)));
153
+ lines.push(...section('Examples:', exampleLines(node.examples ?? [], width)));
154
+ lines.push(...section('Environment:', layout(env, width, column)));
155
+ if (node.epilogue !== undefined)
156
+ lines.push(...wrap(node.epilogue, width), '');
157
+ return `${lines.join('\n').trimEnd()}\n`;
158
+ }
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, run, type AnyCommand, type Command, type CommandContext, type InferOptions, type OptionSpecs, type Program, type RunOptions, } 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 } 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 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, run, } 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,192 @@
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
+ }
57
+ /**
58
+ * What running a command does to the world (N6). Declared, never inferred: it decides
59
+ * whether the command is exposed as an MCP tool at all (N2) and generates the tool's
60
+ * `readOnlyHint` / `idempotentHint` / `destructiveHint` — MCP defaults `destructiveHint`
61
+ * to true, so silence is the dangerous reading.
62
+ */
63
+ export type Effects = 'read_only' | 'idempotent' | 'non_idempotent';
64
+ /**
65
+ * A relationship between options, validated after parsing and before choices and the
66
+ * handler (S2, S6). `implies` takes a second option name, or a predicate over the values.
67
+ */
68
+ export type Relation = {
69
+ exactlyOneOf: readonly string[];
70
+ } | {
71
+ atLeastOneOf: readonly string[];
72
+ } | {
73
+ atMostOneOf: readonly string[];
74
+ } | {
75
+ conflicts: readonly string[];
76
+ } | {
77
+ implies: readonly [string, string | ((values: Record<string, unknown>) => boolean)];
78
+ };
79
+ /** A positional, as help documents it (yargs #2012). */
80
+ export interface ArgumentSpec {
81
+ name: string;
82
+ description?: string;
83
+ required?: boolean;
84
+ variadic?: boolean;
85
+ default?: string;
86
+ }
87
+ /** One example: a single copy-pasteable command line, the description below it (H2). */
88
+ export interface Example {
89
+ command: string;
90
+ description?: string;
91
+ }
92
+ /** What a handler receives. `passthrough` is everything after `--`, verbatim (G5). */
93
+ /** What a caller must do before the command can continue (N11): synthesised into the envelope. */
94
+ export interface ActionRequiredSpec {
95
+ /** A short machine-readable reason: `login`, `confirm`, `missing-config` … */
96
+ reason: string;
97
+ message: string;
98
+ /** Runnable commands, each with when to run it; the engine prefixes the program and carries the caller's flags. */
99
+ next?: readonly {
100
+ command: string;
101
+ when: string;
102
+ }[];
103
+ hint?: string;
104
+ }
105
+ export interface RunContext {
106
+ options: Record<string, unknown>;
107
+ positionals: string[];
108
+ passthrough: string[];
109
+ env: Record<string, string | undefined>;
110
+ /** Exit with an E1 code. Unwinds cleanly: the code is honoured and nothing is printed. */
111
+ exit: (code: number) => never;
112
+ /** A person may be prompted (N12): a terminal, no detected agent, or `FORCE_TTY=1`. */
113
+ interactive: boolean;
114
+ /** The agent the environment names, if any (N12). */
115
+ agent?: string;
116
+ /** Stop and tell the caller what to do instead of blocking on a prompt (N11). */
117
+ actionRequired: (spec: ActionRequiredSpec) => never;
118
+ }
119
+ export interface CommandNode {
120
+ path: string[];
121
+ description?: string;
122
+ /** Shown in command lists instead of the description (yargs #1265). */
123
+ summary?: string;
124
+ options: Record<string, OptionSpec>;
125
+ relations?: readonly Relation[];
126
+ arguments?: ArgumentSpec[];
127
+ examples?: Example[];
128
+ /** Heading this command is listed under in its parent's help (yargs #684). */
129
+ group?: string;
130
+ epilogue?: string;
131
+ hidden?: boolean;
132
+ deprecated?: boolean | string;
133
+ /** Required for a command to be served as an MCP tool (N2, N6). */
134
+ effects?: Effects;
135
+ run?: (ctx: RunContext) => unknown;
136
+ /** Which plugin contributed this, if any. Declared, never diffed (M3). */
137
+ plugin?: string;
138
+ }
139
+ /** A hook may declare which commands it applies to, as data. */
140
+ export interface HookFilter {
141
+ command?: RegExp;
142
+ }
143
+ export interface Hook {
144
+ filter?: HookFilter;
145
+ handler: (ctx: {
146
+ command: string;
147
+ options: Record<string, unknown>;
148
+ }) => void | Promise<void>;
149
+ }
150
+ export interface Plugin {
151
+ name: string;
152
+ commands?: CommandNode[];
153
+ hooks?: {
154
+ preRun?: Hook;
155
+ postRun?: Hook;
156
+ onError?: Hook;
157
+ };
158
+ enforce?: 'pre' | 'post';
159
+ }
160
+ export declare function definePlugin(plugin: Plugin): Plugin;
161
+ /** Rolldown's lesson: evaluate the filter before crossing the boundary. */
162
+ export declare function hookApplies(hook: Hook | undefined, command: string): hook is Hook;
163
+ export declare class Manifest {
164
+ readonly commands: CommandNode[];
165
+ readonly plugins: Plugin[];
166
+ /** The program's own name, which the user never types; `execute` strips it. */
167
+ rootPath: string[];
168
+ /** Reported by `--schema`, `--version` and the MCP handshake; the owning package.json otherwise (V4). */
169
+ version?: string;
170
+ /** With a prefix, every option reads `PREFIX_OPTION_NAME` unless it names its own env (V2). */
171
+ envPrefix?: string;
172
+ /** Config discovery is opt-in; the name is the file stem and the package.json field (V6). */
173
+ config?: {
174
+ name: string;
175
+ };
176
+ /** Characters of `--schema` output above which it is summarised (N13). */
177
+ schemaBudget?: number;
178
+ add(node: CommandNode): void;
179
+ use(plugin: Plugin): void;
180
+ /** `enforce: 'pre'` first, then unordered, then `'post'` — the Vite/Rolldown convention. */
181
+ private ordered;
182
+ fire(stage: 'preRun' | 'postRun' | 'onError', command: string, options: Record<string, unknown>): Promise<void>;
183
+ find(path: string[]): CommandNode | undefined;
184
+ /**
185
+ * Longest-prefix match of argv against declared command paths. The root's own
186
+ * name is not typed by the user, so it is skipped when matching.
187
+ */
188
+ resolve(argv: string[], root?: string[]): {
189
+ node: CommandNode | undefined;
190
+ rest: string[];
191
+ };
192
+ }
@@ -0,0 +1,55 @@
1
+ export function definePlugin(plugin) {
2
+ return plugin;
3
+ }
4
+ export function hookApplies(hook, command) {
5
+ if (hook === undefined)
6
+ return false;
7
+ return hook.filter?.command === undefined || hook.filter.command.test(command);
8
+ }
9
+ const ORDER = { pre: 0, post: 2 };
10
+ export class Manifest {
11
+ commands = [];
12
+ plugins = [];
13
+ rootPath = [];
14
+ version;
15
+ envPrefix;
16
+ config;
17
+ schemaBudget;
18
+ add(node) {
19
+ this.commands.push(node);
20
+ }
21
+ use(plugin) {
22
+ this.plugins.push(plugin);
23
+ for (const command of plugin.commands ?? []) {
24
+ this.add({ ...command, plugin: plugin.name });
25
+ }
26
+ }
27
+ ordered() {
28
+ return [...this.plugins].sort((a, b) => (a.enforce === undefined ? 1 : ORDER[a.enforce]) - (b.enforce === undefined ? 1 : ORDER[b.enforce]));
29
+ }
30
+ async fire(stage, command, options) {
31
+ for (const plugin of this.ordered()) {
32
+ const hook = plugin.hooks?.[stage];
33
+ if (hookApplies(hook, command))
34
+ await hook.handler({ command, options });
35
+ }
36
+ }
37
+ find(path) {
38
+ const key = path.join(' ');
39
+ return this.commands.find((c) => c.path.join(' ') === key);
40
+ }
41
+ resolve(argv, root = []) {
42
+ let best;
43
+ let depth = 0;
44
+ for (const node of this.commands) {
45
+ const typed = node.path.slice(root.length);
46
+ if (typed.length > argv.length)
47
+ continue;
48
+ if (typed.every((seg, i) => argv[i] === seg) && typed.length >= depth) {
49
+ best = node;
50
+ depth = typed.length;
51
+ }
52
+ }
53
+ return { node: best, rest: argv.slice(depth) };
54
+ }
55
+ }
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,40 @@
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
+ /**
37
+ * Serve until the input closes. Notifications (no `id`) get no reply; a malformed line gets
38
+ * a JSON-RPC error with a null id, as the spec asks.
39
+ */
40
+ export declare function serveMcp(manifest: Manifest, opts: ServeOptions): Promise<void>;
package/dist/mcp.js ADDED
@@ -0,0 +1,111 @@
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 async function serveMcp(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
+ for await (const line of createInterface({ input: opts.input, crlfDelay: Infinity })) {
92
+ if (line.trim() === '')
93
+ continue;
94
+ let request;
95
+ try {
96
+ request = JSON.parse(line);
97
+ }
98
+ catch {
99
+ reply({ id: null, error: { code: JSON_RPC_INVALID_REQUEST, message: 'invalid JSON' } });
100
+ continue;
101
+ }
102
+ if (typeof request.method !== 'string') {
103
+ reply({ id: request.id ?? null, error: { code: JSON_RPC_INVALID_REQUEST, message: 'missing method' } });
104
+ continue;
105
+ }
106
+ if (request.id === undefined)
107
+ continue;
108
+ const outcome = (await handle(session, request));
109
+ reply({ id: request.id, ...outcome });
110
+ }
111
+ }
@@ -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
+ }
package/dist/pkg.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export interface Package {
2
+ path: string;
3
+ data: Record<string, unknown>;
4
+ }
5
+ export declare function nearestPackage(from: string): Package | undefined;
package/dist/pkg.js ADDED
@@ -0,0 +1,21 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ export function nearestPackage(from) {
4
+ let dir = from;
5
+ for (let i = 0; i < 64; i++) {
6
+ const at = join(dir, 'package.json');
7
+ if (existsSync(at)) {
8
+ try {
9
+ return { path: at, data: JSON.parse(readFileSync(at, 'utf8')) };
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ }
15
+ const up = dirname(dir);
16
+ if (up === dir)
17
+ return undefined;
18
+ dir = up;
19
+ }
20
+ return undefined;
21
+ }