@clidoc/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +14 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/commands/completion.d.ts +10 -0
  8. package/dist/commands/completion.d.ts.map +1 -0
  9. package/dist/commands/completion.js +24 -0
  10. package/dist/commands/completion.js.map +1 -0
  11. package/dist/commands/docgen.d.ts +6 -0
  12. package/dist/commands/docgen.d.ts.map +1 -0
  13. package/dist/commands/docgen.js +19 -0
  14. package/dist/commands/docgen.js.map +1 -0
  15. package/dist/commands/generate.d.ts +8 -0
  16. package/dist/commands/generate.d.ts.map +1 -0
  17. package/dist/commands/generate.js +40 -0
  18. package/dist/commands/generate.js.map +1 -0
  19. package/dist/commands/markdown.d.ts +6 -0
  20. package/dist/commands/markdown.d.ts.map +1 -0
  21. package/dist/commands/markdown.js +15 -0
  22. package/dist/commands/markdown.js.map +1 -0
  23. package/dist/commands/mcp.d.ts +16 -0
  24. package/dist/commands/mcp.d.ts.map +1 -0
  25. package/dist/commands/mcp.js +39 -0
  26. package/dist/commands/mcp.js.map +1 -0
  27. package/dist/commands/validate.d.ts +4 -0
  28. package/dist/commands/validate.d.ts.map +1 -0
  29. package/dist/commands/validate.js +17 -0
  30. package/dist/commands/validate.js.map +1 -0
  31. package/dist/definition.d.ts +48 -0
  32. package/dist/definition.d.ts.map +1 -0
  33. package/dist/definition.js +28 -0
  34. package/dist/definition.js.map +1 -0
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +21 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/io.d.ts +2 -0
  40. package/dist/io.d.ts.map +1 -0
  41. package/dist/io.js +12 -0
  42. package/dist/io.js.map +1 -0
  43. package/dist/mcp/compile.d.ts +10 -0
  44. package/dist/mcp/compile.d.ts.map +1 -0
  45. package/dist/mcp/compile.js +133 -0
  46. package/dist/mcp/compile.js.map +1 -0
  47. package/dist/mcp/index.d.ts +15 -0
  48. package/dist/mcp/index.d.ts.map +1 -0
  49. package/dist/mcp/index.js +133 -0
  50. package/dist/mcp/index.js.map +1 -0
  51. package/package.json +72 -0
  52. package/src/bin.ts +12 -0
  53. package/src/commands/completion.ts +28 -0
  54. package/src/commands/docgen.ts +21 -0
  55. package/src/commands/generate.ts +42 -0
  56. package/src/commands/markdown.ts +16 -0
  57. package/src/commands/mcp.ts +42 -0
  58. package/src/commands/validate.ts +18 -0
  59. package/src/definition.ts +29 -0
  60. package/src/index.ts +21 -0
  61. package/src/io.ts +12 -0
  62. package/src/mcp/compile.ts +126 -0
  63. package/src/mcp/index.ts +137 -0
@@ -0,0 +1,133 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { Ajv2020 } from 'ajv/dist/2020.js';
3
+ import { validate } from '@clidoc/core';
4
+ const required = (item) => item.required === true || (item.minItems ?? 0) > 0;
5
+ const ownValue = (values, key) => values && Object.hasOwn(values, key) ? values[key] : undefined;
6
+ const identifier = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
7
+ function objectSchema(items) {
8
+ const properties = Object.fromEntries(items.map((item) => {
9
+ if (item.name === '__proto__')
10
+ throw new Error('Unsupported parameter name __proto__');
11
+ const scalar = { type: item.type ?? 'string' };
12
+ if (item.choices?.length)
13
+ scalar.enum = item.choices.map((choice) => choice.value);
14
+ const schema = item.variadic
15
+ ? {
16
+ type: 'array',
17
+ items: scalar,
18
+ minItems: item.minItems ?? (required(item) ? 1 : 0),
19
+ ...(item.maxItems === undefined ? {} : { maxItems: item.maxItems }),
20
+ }
21
+ : scalar;
22
+ if (item.description ?? item.summary)
23
+ schema.description = item.description ?? item.summary;
24
+ // Defaults are descriptive only: the executable remains responsible for applying them.
25
+ if ('default' in item && item.default !== undefined && !item.variadic)
26
+ schema.default = item.default;
27
+ return [item.name, schema];
28
+ }));
29
+ return {
30
+ type: 'object',
31
+ properties,
32
+ required: items.filter(required).map((item) => item.name),
33
+ additionalProperties: false,
34
+ };
35
+ }
36
+ /** Compile the supported OpenCLI dialect to MCP tools without executing or modifying the spec. */
37
+ export function compileMcpTools(source) {
38
+ const result = validate(source);
39
+ if (!result.valid)
40
+ throw new Error(`Invalid OpenCLI document: ${result.errors.join('; ')}`);
41
+ const document = structuredClone(source);
42
+ const ajv = new Ajv2020({ allErrors: true, strict: false, ownProperties: true });
43
+ const binary = document.info.binary;
44
+ const names = new Set();
45
+ return Object.entries(document.commands ?? {})
46
+ .toSorted(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
47
+ .filter(([, command]) => !command.hidden && command.kind !== 'group')
48
+ .map(([path, command]) => {
49
+ if (path !== binary && !path.startsWith(`${binary} `))
50
+ throw new Error(`${path}: command must start with binary ${binary}`);
51
+ const suffix = path.slice(binary.length).trim();
52
+ const words = suffix ? suffix.split(' ') : [];
53
+ if (words.some((word) => !identifier.test(word)))
54
+ throw new Error(`${path}: unsupported command path`);
55
+ const args = command.args ?? [];
56
+ const flagsByName = new Map();
57
+ for (const flag of document.global?.flags ?? []) {
58
+ if (flagsByName.has(flag.name))
59
+ throw new Error(`${path}: duplicate global flag ${flag.name}`);
60
+ flagsByName.set(flag.name, flag);
61
+ }
62
+ for (const flag of command.flags ?? [])
63
+ flagsByName.set(flag.name, flag);
64
+ const allFlags = [...flagsByName.values()];
65
+ for (const flag of allFlags) {
66
+ if (!identifier.test(flag.name))
67
+ throw new Error(`${path}: unsupported flag name ${flag.name}`);
68
+ if (flag.hidden && required(flag))
69
+ throw new Error(`${path}: hidden required flag ${flag.name} cannot be exposed`);
70
+ if (flag.variadic && flag.type === 'boolean')
71
+ throw new Error(`${path}: variadic boolean flag ${flag.name} is unsupported`);
72
+ }
73
+ const flags = allFlags.filter((flag) => !flag.hidden);
74
+ const argNames = new Set();
75
+ args.forEach((arg, index) => {
76
+ if (argNames.has(arg.name))
77
+ throw new Error(`${path}: duplicate argument ${arg.name}`);
78
+ argNames.add(arg.name);
79
+ if (arg.passthrough)
80
+ throw new Error(`${path}: passthrough argument ${arg.name} is unsupported`);
81
+ if (arg.variadic && index !== args.length - 1)
82
+ throw new Error(`${path}: variadic argument must be last`);
83
+ });
84
+ const inputSchema = {
85
+ type: 'object',
86
+ properties: { arguments: objectSchema(args), flags: objectSchema(flags) },
87
+ required: [...(args.some(required) ? ['arguments'] : []), ...(flags.some(required) ? ['flags'] : [])],
88
+ additionalProperties: false,
89
+ };
90
+ const check = ajv.compile(inputSchema);
91
+ const readable = path.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 43);
92
+ const name = `${readable}_${createHash('sha256').update(path).digest('hex').slice(0, 20)}`;
93
+ if (names.has(name))
94
+ throw new Error(`${path}: MCP tool name collision`);
95
+ names.add(name);
96
+ return {
97
+ tool: { name, title: path, description: command.description ?? command.summary ?? path, inputSchema },
98
+ argv(input) {
99
+ if (!check(input))
100
+ throw new Error(`Invalid tool arguments: ${ajv.errorsText(check.errors)}`);
101
+ const values = input;
102
+ const argv = [...words];
103
+ for (const flag of flags) {
104
+ const value = ownValue(values.flags, flag.name);
105
+ if (value === undefined)
106
+ continue;
107
+ for (const entry of Array.isArray(value) ? value : [value]) {
108
+ // Explicit false must override a CLI default of true.
109
+ argv.push(flag.type === 'boolean' && entry === true ? `--${flag.name}` : `--${flag.name}=${String(entry)}`);
110
+ }
111
+ }
112
+ const positional = [];
113
+ let gap = false;
114
+ for (const arg of args) {
115
+ const value = ownValue(values.arguments, arg.name);
116
+ if (value === undefined) {
117
+ gap = true;
118
+ continue;
119
+ }
120
+ if (gap)
121
+ throw new Error(`Cannot supply ${arg.name} after an omitted positional argument`);
122
+ positional.push(...(Array.isArray(value) ? value : [value]).map(String));
123
+ }
124
+ if (positional.length)
125
+ argv.push('--', ...positional);
126
+ if (argv.some((value) => value.includes('\0')))
127
+ throw new Error('CLI arguments cannot contain NUL bytes');
128
+ return argv;
129
+ },
130
+ };
131
+ });
132
+ }
133
+ //# sourceMappingURL=compile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/mcp/compile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import type { OpenCliDocument } from '@clidoc/core';
3
+ export { compileMcpTools, type CompiledMcpTool } from './compile.js';
4
+ /** Executable and process limits are fixed by the host, never by tool inputs. */
5
+ export type McpServerOptions = {
6
+ executable: string;
7
+ cwd?: string;
8
+ timeoutMs?: number;
9
+ maxOutputBytes?: number;
10
+ };
11
+ /** Create an MCP server for a trusted local CLI. Connect it to a transport or call serveMcp. */
12
+ export declare function createMcpServer(document: OpenCliDocument, options: McpServerOptions): Server;
13
+ /** Connect a server to stdin/stdout; callers can close the returned server. */
14
+ export declare function serveMcp(document: OpenCliDocument, options: McpServerOptions): Promise<Server>;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAGnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAErE,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAyDF,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAyC5F;AAED,+EAA+E;AAC/E,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBpG"}
@@ -0,0 +1,133 @@
1
+ import { createRequire } from 'node:module';
2
+ import { spawn } from 'node:child_process';
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
6
+ import { compileMcpTools } from './compile.js';
7
+ export { compileMcpTools } from './compile.js';
8
+ const packageJson = createRequire(import.meta.url)('../../package.json');
9
+ const failure = (text) => ({ content: [{ type: 'text', text }], isError: true });
10
+ function execute(argv, options, signal) {
11
+ if (signal.aborted)
12
+ return Promise.resolve(failure('CLI invocation canceled'));
13
+ return new Promise((resolve) => {
14
+ const child = spawn(options.executable, argv, {
15
+ cwd: options.cwd,
16
+ shell: false,
17
+ stdio: ['ignore', 'pipe', 'pipe'],
18
+ });
19
+ const stdout = [];
20
+ const stderr = [];
21
+ let size = 0;
22
+ let finished = false;
23
+ const finish = (result) => {
24
+ if (finished)
25
+ return;
26
+ finished = true;
27
+ clearTimeout(timer);
28
+ signal.removeEventListener('abort', cancel);
29
+ resolve(result);
30
+ };
31
+ const stop = (message) => {
32
+ child.kill('SIGKILL');
33
+ child.stdout.destroy();
34
+ child.stderr.destroy();
35
+ finish(failure(message));
36
+ };
37
+ const cancel = () => stop('CLI invocation canceled');
38
+ const timer = setTimeout(() => stop(`CLI timed out after ${options.timeoutMs} ms`), options.timeoutMs);
39
+ signal.addEventListener('abort', cancel, { once: true });
40
+ const collect = (chunks, chunk) => {
41
+ if (finished)
42
+ return;
43
+ size += chunk.length;
44
+ if (size > options.maxOutputBytes)
45
+ stop(`CLI output exceeded ${options.maxOutputBytes} bytes`);
46
+ else
47
+ chunks.push(chunk);
48
+ };
49
+ child.stdout.on('data', (chunk) => collect(stdout, chunk));
50
+ child.stderr.on('data', (chunk) => collect(stderr, chunk));
51
+ child.on('error', (error) => finish(failure(`Cannot run CLI: ${error.message}`)));
52
+ child.on('close', (code, exitSignal) => {
53
+ const output = {
54
+ stdout: Buffer.concat(stdout).toString('utf8'),
55
+ stderr: Buffer.concat(stderr).toString('utf8'),
56
+ exitCode: code,
57
+ signal: exitSignal,
58
+ };
59
+ finish({ content: [{ type: 'text', text: JSON.stringify(output) }], isError: code !== 0 });
60
+ });
61
+ });
62
+ }
63
+ /** Create an MCP server for a trusted local CLI. Connect it to a transport or call serveMcp. */
64
+ export function createMcpServer(document, options) {
65
+ if (!options.executable || options.executable.includes('\0'))
66
+ throw new Error('An explicit executable is required');
67
+ const limits = {
68
+ ...options,
69
+ timeoutMs: options.timeoutMs ?? 30000,
70
+ maxOutputBytes: options.maxOutputBytes ?? 1048576,
71
+ };
72
+ for (const [name, value] of [
73
+ ['timeoutMs', limits.timeoutMs],
74
+ ['maxOutputBytes', limits.maxOutputBytes],
75
+ ])
76
+ if (!Number.isSafeInteger(value) || value < 1 || value > 2147483647)
77
+ throw new Error(`${name} must be an integer between 1 and 2147483647`);
78
+ const compiled = compileMcpTools(document);
79
+ const tools = new Map(compiled.map((entry) => [entry.tool.name, entry]));
80
+ const server = new Server({ name: 'clidoc-mcp', version: packageJson.version }, { capabilities: { tools: {} } });
81
+ const active = new Set();
82
+ // The SDK exposes onclose as a callback, not an EventTarget.
83
+ // oxlint-disable-next-line unicorn/prefer-add-event-listener
84
+ server.onclose = () => {
85
+ for (const controller of active)
86
+ controller.abort();
87
+ };
88
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: compiled.map((entry) => entry.tool) }));
89
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
90
+ const entry = tools.get(request.params.name);
91
+ if (!entry)
92
+ return failure(`Unknown tool: ${request.params.name}`);
93
+ const controller = new AbortController();
94
+ const cancel = () => controller.abort();
95
+ extra.signal.addEventListener('abort', cancel, { once: true });
96
+ if (extra.signal.aborted)
97
+ controller.abort();
98
+ active.add(controller);
99
+ try {
100
+ return await execute(entry.argv(request.params.arguments ?? {}), limits, controller.signal);
101
+ }
102
+ catch (error) {
103
+ return failure(error instanceof Error ? error.message : String(error));
104
+ }
105
+ finally {
106
+ extra.signal.removeEventListener('abort', cancel);
107
+ active.delete(controller);
108
+ }
109
+ });
110
+ return server;
111
+ }
112
+ /** Connect a server to stdin/stdout; callers can close the returned server. */
113
+ export async function serveMcp(document, options) {
114
+ const server = createMcpServer(document, options);
115
+ await server.connect(new StdioServerTransport());
116
+ const onclose = server.onclose;
117
+ const shutdown = () => {
118
+ void server.close();
119
+ };
120
+ // The SDK exposes onclose as a callback, not an EventTarget.
121
+ // oxlint-disable-next-line unicorn/prefer-add-event-listener
122
+ server.onclose = () => {
123
+ process.stdin.removeListener('end', shutdown);
124
+ process.removeListener('SIGINT', shutdown);
125
+ process.removeListener('SIGTERM', shutdown);
126
+ onclose();
127
+ };
128
+ process.stdin.once('end', shutdown);
129
+ process.once('SIGINT', shutdown);
130
+ process.once('SIGTERM', shutdown);
131
+ return server;
132
+ }
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@clidoc/cli",
3
+ "version": "0.1.0",
4
+ "description": "Generate and validate OpenCLI documentation",
5
+ "keywords": [
6
+ "cli",
7
+ "docs",
8
+ "documentation",
9
+ "opencli",
10
+ "yargs"
11
+ ],
12
+ "homepage": "https://clidoc.dev",
13
+ "bugs": {
14
+ "url": "https://github.com/bhouston/clidoc/issues"
15
+ },
16
+ "license": "MIT",
17
+ "author": "Ben Houston <ben@ben3d.ca> (https://ben3d.ca)",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/bhouston/clidoc.git",
21
+ "directory": "packages/cli"
22
+ },
23
+ "bin": {
24
+ "clidoc": "./dist/bin.js"
25
+ },
26
+ "files": [
27
+ "dist/**/*.js",
28
+ "dist/**/*.js.map",
29
+ "dist/**/*.d.ts",
30
+ "dist/**/*.d.ts.map",
31
+ "src/**/*.ts",
32
+ "!src/**/*.test.ts",
33
+ "CHANGELOG.md"
34
+ ],
35
+ "type": "module",
36
+ "main": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "import": "./dist/index.js"
42
+ },
43
+ "./mcp": {
44
+ "types": "./dist/mcp/index.d.ts",
45
+ "import": "./dist/mcp/index.js"
46
+ }
47
+ },
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "registry": "https://registry.npmjs.org/"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc",
54
+ "tsc": "tsc --noEmit"
55
+ },
56
+ "dependencies": {
57
+ "@clidoc/adapter-commander": "^0.1.0",
58
+ "@clidoc/adapter-oclif": "^0.1.0",
59
+ "@clidoc/adapter-yargs": "^0.1.0",
60
+ "@clidoc/core": "^0.1.0",
61
+ "@modelcontextprotocol/sdk": "^1.30.0",
62
+ "ajv": "^8.20.0",
63
+ "yargs": "^18.1.0",
64
+ "yargs-file-commands": "^2.0.1"
65
+ },
66
+ "devDependencies": {
67
+ "@types/yargs": "^17.0.35"
68
+ },
69
+ "engines": {
70
+ "node": ">=22.12.0"
71
+ }
72
+ }
package/src/bin.ts ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { hideBin } from 'yargs/helpers';
3
+ import { handleOpenCliRequest } from '@clidoc/core';
4
+ import { cliDocument, runCli } from './index.js';
5
+
6
+ try {
7
+ const argv = hideBin(process.argv);
8
+ if (!(await handleOpenCliRequest(argv, cliDocument))) await runCli(argv);
9
+ } catch (error) {
10
+ console.error(error instanceof Error ? error.message : String(error));
11
+ process.exitCode = 1;
12
+ }
@@ -0,0 +1,28 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { generateCompletion, parse, type CompletionShell } from '@clidoc/core';
3
+ import { defineCommand } from 'yargs-file-commands';
4
+ import { cliDocument } from '../definition.js';
5
+ import { output } from '../io.js';
6
+
7
+ export const command = defineCommand({
8
+ command: 'completion <shell>',
9
+ describe: 'Generate a standalone shell completion script',
10
+ builder: (yargs) =>
11
+ yargs
12
+ .positional('shell', {
13
+ type: 'string',
14
+ choices: ['bash', 'zsh', 'fish'] as const,
15
+ demandOption: true,
16
+ describe: 'Target shell',
17
+ })
18
+ .option('input', { type: 'string', alias: 'i', describe: 'OpenCLI JSON or YAML file; defaults to clidoc itself' })
19
+ .option('binary', { type: 'string', describe: 'Executable name override for completion registration' })
20
+ .option('output', { type: 'string', alias: 'o', describe: 'Output file; defaults to stdout' }),
21
+ handler: async (argv) => {
22
+ const document = argv.input ? parse(await readFile(argv.input, 'utf8')) : cliDocument();
23
+ await output(
24
+ generateCompletion(document, { shell: argv.shell as CompletionShell, binary: argv.binary }),
25
+ argv.output,
26
+ );
27
+ },
28
+ });
@@ -0,0 +1,21 @@
1
+ import { writeOpenCliDocument } from '@clidoc/core';
2
+ import type { DocumentFormat } from '@clidoc/core';
3
+ import { defineCommand } from 'yargs-file-commands';
4
+ import { cliDocument } from '../definition.js';
5
+
6
+ export const command = defineCommand({
7
+ command: 'docgen',
8
+ describe: 'Write clidoc’s own OpenCLI document',
9
+ builder: (yargs) =>
10
+ yargs
11
+ .option('format', {
12
+ type: 'string',
13
+ choices: ['json', 'yaml', 'markdown'] as const,
14
+ default: 'json',
15
+ describe: 'Output format',
16
+ })
17
+ .option('output', { type: 'string', alias: 'o', describe: 'Output file; defaults to stdout' }),
18
+ handler: async (argv) => {
19
+ await writeOpenCliDocument(cliDocument(), argv.output, argv.format as DocumentFormat);
20
+ },
21
+ });
@@ -0,0 +1,42 @@
1
+ import { resolve } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { parse } from '@clidoc/core';
4
+ import { fromYargs } from '@clidoc/adapter-yargs';
5
+ import { fromCommander } from '@clidoc/adapter-commander';
6
+ import { fromOclif } from '@clidoc/adapter-oclif';
7
+ import { defineCommand } from 'yargs-file-commands';
8
+ import { output } from '../io.js';
9
+
10
+ export const command = defineCommand({
11
+ command: 'generate <module>',
12
+ describe: 'Import a trusted framework definition module and generate OpenCLI JSON',
13
+ builder: (yargs) =>
14
+ yargs
15
+ .positional('module', {
16
+ type: 'string',
17
+ demandOption: true,
18
+ describe: 'Trusted JS module exporting default metadata and info',
19
+ })
20
+ .option('adapter', {
21
+ type: 'string',
22
+ choices: ['yargs', 'commander', 'oclif'] as const,
23
+ demandOption: true,
24
+ describe: 'Framework adapter',
25
+ })
26
+ .option('output', { type: 'string', alias: 'o', describe: 'Output JSON file; defaults to stdout' }),
27
+ handler: async (argv) => {
28
+ // Importing code is intentional: this command accepts trusted local modules only.
29
+ const source = await import(pathToFileURL(resolve(argv.module)).href);
30
+ if (!source.default || !source.info)
31
+ throw new Error('Module must export default framework metadata and named info');
32
+ const document =
33
+ argv.adapter === 'yargs'
34
+ ? fromYargs(source.default, source.info)
35
+ : argv.adapter === 'commander'
36
+ ? fromCommander(source.default, source.info)
37
+ : fromOclif(source.default, source.info);
38
+ const json = JSON.stringify(document, null, 2) + '\n';
39
+ parse(json);
40
+ await output(json, argv.output);
41
+ },
42
+ });
@@ -0,0 +1,16 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { parse, renderMarkdown } from '@clidoc/core';
3
+ import { defineCommand } from 'yargs-file-commands';
4
+ import { output } from '../io.js';
5
+
6
+ export const command = defineCommand({
7
+ command: 'markdown <input>',
8
+ describe: 'Render an OpenCLI document as Markdown',
9
+ builder: (yargs) =>
10
+ yargs
11
+ .positional('input', { type: 'string', demandOption: true, describe: 'OpenCLI JSON or YAML filename' })
12
+ .option('output', { type: 'string', alias: 'o', describe: 'Output file; defaults to stdout' }),
13
+ handler: async (argv) => {
14
+ await output(renderMarkdown(parse(await readFile(argv.input, 'utf8'))), argv.output);
15
+ },
16
+ });
@@ -0,0 +1,42 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { parse } from '@clidoc/core';
3
+ import { defineCommand } from 'yargs-file-commands';
4
+ import { compileMcpTools, serveMcp } from '../mcp/index.js';
5
+ import { output } from '../io.js';
6
+
7
+ export const command = defineCommand({
8
+ command: 'mcp <input>',
9
+ describe: 'Export MCP tools or serve a trusted CLI over MCP stdio',
10
+ builder: (yargs) =>
11
+ yargs
12
+ .positional('input', { type: 'string', demandOption: true, describe: 'OpenCLI JSON or YAML filename' })
13
+ .option('output', { type: 'string', alias: 'o', describe: 'Tool catalog output file; defaults to stdout' })
14
+ .option('serve', { type: 'boolean', default: false, describe: 'Serve MCP over stdin/stdout' })
15
+ .option('executable', { type: 'string', describe: 'Trusted executable to run; required with --serve' })
16
+ .option('cwd', { type: 'string', describe: 'Working directory for CLI invocations' })
17
+ .option('timeout-ms', { type: 'number', describe: 'Invocation timeout in milliseconds (default: 30000)' })
18
+ .option('max-output-bytes', { type: 'number', describe: 'Combined stdout/stderr limit (default: 1048576)' }),
19
+ handler: async (argv) => {
20
+ if (argv.serve && argv.output) throw new Error('--output cannot be used with --serve');
21
+ if (argv.serve && !argv.executable) throw new Error('--serve requires --executable');
22
+ if (
23
+ !argv.serve &&
24
+ [argv.executable, argv.cwd, argv.timeoutMs, argv.maxOutputBytes].some((value) => value !== undefined)
25
+ )
26
+ throw new Error('Execution options require --serve');
27
+ const document = parse(await readFile(argv.input, 'utf8'));
28
+ if (argv.serve) {
29
+ await serveMcp(document, {
30
+ executable: argv.executable!,
31
+ cwd: argv.cwd,
32
+ timeoutMs: argv.timeoutMs,
33
+ maxOutputBytes: argv.maxOutputBytes,
34
+ });
35
+ } else {
36
+ await output(
37
+ JSON.stringify({ tools: compileMcpTools(document).map((entry) => entry.tool) }, null, 2) + '\n',
38
+ argv.output,
39
+ );
40
+ }
41
+ },
42
+ });
@@ -0,0 +1,18 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { parse } from '@clidoc/core';
3
+ import { defineCommand } from 'yargs-file-commands';
4
+
5
+ export const command = defineCommand({
6
+ command: 'validate <input>',
7
+ describe: 'Validate an OpenCLI JSON or YAML document',
8
+ builder: (yargs) =>
9
+ yargs.positional('input', {
10
+ type: 'string',
11
+ demandOption: true,
12
+ describe: 'OpenCLI document filename',
13
+ }),
14
+ handler: async ({ input }) => {
15
+ parse(await readFile(input, 'utf8'));
16
+ process.stdout.write('Valid OpenCLI document\n');
17
+ },
18
+ });
@@ -0,0 +1,29 @@
1
+ import { createRequire } from 'node:module';
2
+ import { fromYargs } from '@clidoc/adapter-yargs';
3
+ import { command as completion } from './commands/completion.js';
4
+ import { command as docgen } from './commands/docgen.js';
5
+ import { command as generate } from './commands/generate.js';
6
+ import { command as mcp } from './commands/mcp.js';
7
+ import { command as markdown } from './commands/markdown.js';
8
+ import { command as validate } from './commands/validate.js';
9
+
10
+ const packageJson = createRequire(import.meta.url)('../package.json') as { version: string };
11
+
12
+ export const info = {
13
+ title: 'clidoc',
14
+ binary: 'clidoc',
15
+ version: packageJson.version,
16
+ summary: 'Generate, validate, and publish CLI documentation through OpenCLI.',
17
+ };
18
+ export const commands = [generate, markdown, validate, docgen, completion, mcp];
19
+ /** Documentation and runtime share the exact same command definitions. */
20
+ export function cliDocument() {
21
+ const document = fromYargs(commands, info);
22
+ document.global = {
23
+ flags: [
24
+ { name: 'help', type: 'boolean', summary: 'Show help' },
25
+ { name: 'version', type: 'boolean', summary: 'Show version number' },
26
+ ],
27
+ };
28
+ return document;
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import yargs from 'yargs';
3
+ import { fileCommands } from 'yargs-file-commands';
4
+ import { info } from './definition.js';
5
+ export { cliDocument } from './definition.js';
6
+
7
+ /** Run without terminating the embedding process. Commands live in individual files. */
8
+ export async function runCli(argv: string[]): Promise<void> {
9
+ await yargs(argv)
10
+ .scriptName('clidoc')
11
+ .command(await fileCommands({ commandDirs: [fileURLToPath(new URL('./commands', import.meta.url))] }))
12
+ .demandCommand(1)
13
+ .strict()
14
+ .help()
15
+ .version(info.version)
16
+ .exitProcess(false)
17
+ .fail((message, error) => {
18
+ throw error ?? new Error(message);
19
+ })
20
+ .parseAsync();
21
+ }
package/src/io.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+
4
+ export async function output(content: string, filename?: string): Promise<void> {
5
+ if (!filename) {
6
+ process.stdout.write(content);
7
+ return;
8
+ }
9
+ const destination = resolve(filename);
10
+ await mkdir(dirname(destination), { recursive: true });
11
+ await writeFile(destination, content);
12
+ }