@cairn-tool/cli-schema 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bryan Stockus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ Portable CLI-description types, JSON Schema, validator, usage string, and text renderer. No commander or System.CommandLine dependency.
2
+
3
+ ```js
4
+ import { validate, cliSchema, buildUsage, renderText } from "@cairn-tool/cli-schema";
5
+ ```
6
+
7
+ To walk a live commander tree, install `@cairn-tool/cli-schema-commander`.
@@ -0,0 +1,3 @@
1
+ /** Drops `tool.version` and rewrites the payload in spec key order. */
2
+ export declare function canonicalize(payload: unknown): unknown;
3
+ export declare function canonicalizeJson(payload: unknown): string;
@@ -0,0 +1,108 @@
1
+ const OBJECT_KEYS = {
2
+ envelope: ["schemaVersion", "tool", "formatShorthands", "advisoryOutput", "schemas", "commands"],
3
+ tool: ["name", "version"],
4
+ advisoryOutput: ["description", "stream", "suppressedWhen", "optOutEnv"],
5
+ schemaRef: ["id", "uri", "title", "commands"],
6
+ command: [
7
+ "id",
8
+ "path",
9
+ "description",
10
+ "usage",
11
+ "arguments",
12
+ "options",
13
+ "subcommands",
14
+ "formats",
15
+ "defaultFormat",
16
+ "formatConfigurable",
17
+ "outputSchema",
18
+ "jsonlSchema",
19
+ "sarifSchema",
20
+ "exitCodes",
21
+ "exitCodePassthrough",
22
+ "stream",
23
+ "writes",
24
+ "stability",
25
+ "notes",
26
+ ],
27
+ argument: ["name", "description", "arity", "valueType", "allowedValues", "default"],
28
+ option: [
29
+ "name",
30
+ "aliases",
31
+ "description",
32
+ "valueName",
33
+ "arity",
34
+ "required",
35
+ "negatable",
36
+ "valueType",
37
+ "allowedValues",
38
+ "recursive",
39
+ "default",
40
+ ],
41
+ arity: ["min", "max"],
42
+ exitCode: ["code", "meaning"],
43
+ passthrough: ["min", "max", "description"],
44
+ stream: ["success", "findings"],
45
+ };
46
+ function reorder(value, keys) {
47
+ const ordered = {};
48
+ for (const key of keys) {
49
+ if (Object.prototype.hasOwnProperty.call(value, key))
50
+ ordered[key] = value[key];
51
+ }
52
+ for (const key of Object.keys(value)) {
53
+ if (!Object.prototype.hasOwnProperty.call(ordered, key))
54
+ ordered[key] = value[key];
55
+ }
56
+ return ordered;
57
+ }
58
+ function walk(value, kind) {
59
+ if (Array.isArray(value)) {
60
+ const itemKind = kind === "commands"
61
+ ? "command"
62
+ : kind === "arguments"
63
+ ? "argument"
64
+ : kind === "options"
65
+ ? "option"
66
+ : kind === "schemas"
67
+ ? "schemaRef"
68
+ : kind === "exitCodes"
69
+ ? "exitCode"
70
+ : "";
71
+ return value.map((item) => walk(item, itemKind));
72
+ }
73
+ if (value === null || typeof value !== "object")
74
+ return value;
75
+ const record = value;
76
+ const keys = OBJECT_KEYS[kind];
77
+ const ordered = keys ? reorder(record, keys) : record;
78
+ const next = {};
79
+ for (const [key, child] of Object.entries(ordered)) {
80
+ const childKind = key === "tool"
81
+ ? "tool"
82
+ : key === "advisoryOutput"
83
+ ? "advisoryOutput"
84
+ : key === "arity"
85
+ ? "arity"
86
+ : key === "exitCodePassthrough"
87
+ ? "passthrough"
88
+ : key === "stream"
89
+ ? "stream"
90
+ : key === "formatShorthands"
91
+ ? ""
92
+ : key;
93
+ next[key] = walk(child, childKind);
94
+ }
95
+ return next;
96
+ }
97
+ /** Drops `tool.version` and rewrites the payload in spec key order. */
98
+ export function canonicalize(payload) {
99
+ const clone = structuredClone(payload);
100
+ const tool = clone.tool;
101
+ if (tool && typeof tool === "object")
102
+ delete tool.version;
103
+ return walk(clone, "envelope");
104
+ }
105
+ export function canonicalizeJson(payload) {
106
+ return JSON.stringify(canonicalize(payload), null, 2) + "\n";
107
+ }
108
+ //# sourceMappingURL=canonicalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalize.js","sourceRoot":"","sources":["../src/canonicalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,187 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/cairn-tool/cli-schema/v1/cli-schema.json",
4
+ "title": "CLI contract description",
5
+ "description": "The static contract of a command-line interface. Project configuration is not applied. Consumers must ignore properties they do not recognize.",
6
+ "type": "object",
7
+ "required": ["schemaVersion", "tool", "formatShorthands", "schemas", "commands"],
8
+ "properties": {
9
+ "schemaVersion": { "type": "string" },
10
+ "tool": {
11
+ "type": "object",
12
+ "required": ["name", "version"],
13
+ "properties": {
14
+ "name": { "type": "string" },
15
+ "version": { "type": "string" }
16
+ }
17
+ },
18
+ "formatShorthands": {
19
+ "type": "object",
20
+ "additionalProperties": { "type": "string" },
21
+ "description": "argv token to the --format value it expands to before parsing."
22
+ },
23
+ "advisoryOutput": {
24
+ "type": "object",
25
+ "description": "Machine-stream guarantees for an advisory notice. Omitted when the tool has none.",
26
+ "required": ["description", "stream", "suppressedWhen", "optOutEnv"],
27
+ "properties": {
28
+ "description": { "type": "string" },
29
+ "stream": { "enum": ["stdout", "stderr"] },
30
+ "suppressedWhen": { "type": "array", "items": { "type": "string" } },
31
+ "optOutEnv": { "type": "string" }
32
+ }
33
+ },
34
+ "schemas": {
35
+ "type": "array",
36
+ "items": {
37
+ "type": "object",
38
+ "required": ["id", "uri", "title", "commands"],
39
+ "properties": {
40
+ "id": { "type": "string" },
41
+ "uri": { "type": "string" },
42
+ "title": { "type": "string" },
43
+ "commands": { "type": "array", "items": { "type": "string" } }
44
+ }
45
+ }
46
+ },
47
+ "commands": {
48
+ "type": "array",
49
+ "items": { "$ref": "#/$defs/command" }
50
+ }
51
+ },
52
+ "$defs": {
53
+ "arity": {
54
+ "type": "object",
55
+ "required": ["min", "max"],
56
+ "properties": {
57
+ "min": { "type": "integer", "minimum": 0 },
58
+ "max": { "type": ["integer", "null"], "minimum": 0 }
59
+ }
60
+ },
61
+ "valueType": {
62
+ "type": ["string", "null"],
63
+ "enum": ["boolean", "string", "integer", "number", null]
64
+ },
65
+ "command": {
66
+ "type": "object",
67
+ "required": [
68
+ "id",
69
+ "path",
70
+ "description",
71
+ "usage",
72
+ "arguments",
73
+ "options",
74
+ "subcommands",
75
+ "formats",
76
+ "defaultFormat",
77
+ "formatConfigurable",
78
+ "outputSchema",
79
+ "exitCodes",
80
+ "stream",
81
+ "writes",
82
+ "stability"
83
+ ],
84
+ "properties": {
85
+ "id": {
86
+ "type": "string",
87
+ "description": "Space-joined command path, e.g. 'md graph'."
88
+ },
89
+ "path": { "type": "array", "items": { "type": "string" } },
90
+ "description": { "type": "string" },
91
+ "usage": { "type": "string" },
92
+ "arguments": {
93
+ "type": "array",
94
+ "items": { "$ref": "#/$defs/argument" }
95
+ },
96
+ "options": {
97
+ "type": "array",
98
+ "items": { "$ref": "#/$defs/option" }
99
+ },
100
+ "subcommands": { "type": "array", "items": { "type": "string" } },
101
+ "formats": { "type": ["array", "null"], "items": { "type": "string" } },
102
+ "defaultFormat": { "type": ["string", "null"] },
103
+ "formatConfigurable": { "type": "boolean" },
104
+ "outputSchema": { "type": ["string", "null"] },
105
+ "jsonlSchema": { "type": ["string", "null"] },
106
+ "sarifSchema": { "type": ["string", "null"] },
107
+ "exitCodes": {
108
+ "type": "array",
109
+ "items": {
110
+ "type": "object",
111
+ "required": ["code", "meaning"],
112
+ "properties": {
113
+ "code": { "type": "integer" },
114
+ "meaning": { "type": "string" }
115
+ }
116
+ }
117
+ },
118
+ "exitCodePassthrough": {
119
+ "type": "object",
120
+ "description": "Present only when the command forwards a child process's exit status verbatim.",
121
+ "required": ["min", "max", "description"],
122
+ "properties": {
123
+ "min": { "type": "integer" },
124
+ "max": { "type": "integer" },
125
+ "description": { "type": "string" }
126
+ }
127
+ },
128
+ "stream": {
129
+ "type": ["object", "null"],
130
+ "properties": {
131
+ "success": { "enum": ["stdout", "stderr"] },
132
+ "findings": { "enum": ["stdout", "stderr"] }
133
+ }
134
+ },
135
+ "writes": { "type": ["boolean", "null"] },
136
+ "stability": { "enum": ["stable", "experimental", "undeclared"] },
137
+ "notes": { "type": "string" }
138
+ }
139
+ },
140
+ "argument": {
141
+ "type": "object",
142
+ "required": ["name", "description", "arity", "valueType", "allowedValues"],
143
+ "properties": {
144
+ "name": { "type": "string" },
145
+ "description": { "type": "string" },
146
+ "arity": { "$ref": "#/$defs/arity" },
147
+ "valueType": { "$ref": "#/$defs/valueType" },
148
+ "allowedValues": {
149
+ "type": ["array", "null"],
150
+ "items": { "type": "string" }
151
+ },
152
+ "default": true
153
+ }
154
+ },
155
+ "option": {
156
+ "type": "object",
157
+ "required": [
158
+ "name",
159
+ "aliases",
160
+ "description",
161
+ "valueName",
162
+ "arity",
163
+ "required",
164
+ "negatable",
165
+ "valueType",
166
+ "allowedValues",
167
+ "recursive"
168
+ ],
169
+ "properties": {
170
+ "name": { "type": "string" },
171
+ "aliases": { "type": "array", "items": { "type": "string" } },
172
+ "description": { "type": "string" },
173
+ "valueName": { "type": ["string", "null"] },
174
+ "arity": { "$ref": "#/$defs/arity" },
175
+ "required": { "type": "boolean" },
176
+ "negatable": { "type": "boolean" },
177
+ "valueType": { "$ref": "#/$defs/valueType" },
178
+ "allowedValues": {
179
+ "type": ["array", "null"],
180
+ "items": { "type": "string" }
181
+ },
182
+ "recursive": { "type": "boolean" },
183
+ "default": true
184
+ }
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,8 @@
1
+ export { CONTRACT_VERSION, isRequired, isVariadic } from "./types.js";
2
+ export type { AdvisoryOutput, Arity, CommandContract, CommandContractRegistry, ContractStream, DescribeResult, DescribedArgument, DescribedCommand, DescribedOption, ExitCodeMeaning, SchemaRef, Stability, ValueType, } from "./types.js";
3
+ export { buildUsage } from "./usage.js";
4
+ export { renderText } from "./render.js";
5
+ export { cliSchema } from "./schema.js";
6
+ export { validate } from "./validate.js";
7
+ export type { ValidationResult } from "./validate.js";
8
+ export { canonicalize, canonicalizeJson } from "./canonicalize.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { CONTRACT_VERSION, isRequired, isVariadic } from "./types.js";
2
+ export { buildUsage } from "./usage.js";
3
+ export { renderText } from "./render.js";
4
+ export { cliSchema } from "./schema.js";
5
+ export { validate } from "./validate.js";
6
+ export { canonicalize, canonicalizeJson } from "./canonicalize.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgBtE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { DescribeResult } from "./types.js";
2
+ /** `llm` and `human` share one renderer; `human` adds ANSI bold and dim. */
3
+ export declare function renderText(result: DescribeResult, human: boolean): string;
package/dist/render.js ADDED
@@ -0,0 +1,43 @@
1
+ const BOLD = "\x1b[1m";
2
+ const DIM = "\x1b[2m";
3
+ const RESET = "\x1b[0m";
4
+ function renderCommand(command, human) {
5
+ const name = human ? `${BOLD}${command.id}${RESET}` : command.id;
6
+ const lines = [name, ` ${command.description}`, ` usage: ${command.usage}`];
7
+ if (command.formats?.length)
8
+ lines.push(` formats: ${command.formats.join(", ")} (default ${command.defaultFormat})`);
9
+ if (command.outputSchema)
10
+ lines.push(` json schema: ${command.outputSchema}`);
11
+ if (command.jsonlSchema)
12
+ lines.push(` jsonl schema: ${command.jsonlSchema}`);
13
+ if (command.stream)
14
+ lines.push(` stream: ${command.stream.success} on success` +
15
+ (command.stream.findings ? `, ${command.stream.findings} on findings` : ""));
16
+ if (command.writes)
17
+ lines.push(" writes: may modify files");
18
+ for (const exit of command.exitCodes)
19
+ lines.push(` exit ${exit.code}: ${exit.meaning}`);
20
+ if (command.stability === "undeclared")
21
+ lines.push(human ? ` ${DIM}contract: undeclared${RESET}` : " contract: undeclared");
22
+ if (command.notes)
23
+ lines.push(` note: ${command.notes}`);
24
+ return lines;
25
+ }
26
+ /** `llm` and `human` share one renderer; `human` adds ANSI bold and dim. */
27
+ export function renderText(result, human) {
28
+ const lines = [
29
+ `${result.tool.name} ${result.tool.version}`,
30
+ `contract schema version: ${result.schemaVersion}`,
31
+ `format shorthands: ${Object.entries(result.formatShorthands)
32
+ .map(([flag, value]) => `${flag} = ${value}`)
33
+ .join(", ")}`,
34
+ ];
35
+ if (result.advisoryOutput) {
36
+ lines.push("", `update notice: ${result.advisoryOutput.description}`, ...result.advisoryOutput.suppressedWhen.map((condition) => ` suppressed when ${condition}`));
37
+ }
38
+ lines.push("", `commands (${result.commands.length}):`, "");
39
+ for (const command of result.commands)
40
+ lines.push(...renderCommand(command, human), "");
41
+ return lines.join("\n").trimEnd() + "\n";
42
+ }
43
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ /** Frozen JSON Schema 2020-12 document. `$id` is an identifier, not a URL. */
2
+ export declare const cliSchema: Record<string, unknown>;
package/dist/schema.js ADDED
@@ -0,0 +1,7 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const here = dirname(fileURLToPath(import.meta.url));
5
+ /** Frozen JSON Schema 2020-12 document. `$id` is an identifier, not a URL. */
6
+ export const cliSchema = Object.freeze(JSON.parse(readFileSync(join(here, "cli-schema.json"), "utf8")));
7
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,SAAS,GAA4B,MAAM,CAAC,MAAM,CAC7D,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAA4B,CAC3F,CAAC"}
@@ -0,0 +1,109 @@
1
+ export declare const CONTRACT_VERSION = "1";
2
+ export type ContractStream = "stdout" | "stderr";
3
+ export type ValueType = "boolean" | "string" | "integer" | "number";
4
+ export type Stability = "stable" | "experimental" | "undeclared";
5
+ export interface Arity {
6
+ min: number;
7
+ /** `null` is unbounded. */
8
+ max: number | null;
9
+ }
10
+ export interface ExitCodeMeaning {
11
+ code: number;
12
+ meaning: string;
13
+ }
14
+ export interface AdvisoryOutput {
15
+ description: string;
16
+ stream: ContractStream;
17
+ suppressedWhen: string[];
18
+ optOutEnv: string;
19
+ }
20
+ export interface SchemaRef {
21
+ id: string;
22
+ uri: string;
23
+ title: string;
24
+ commands: string[];
25
+ }
26
+ export interface CommandContract {
27
+ formats: readonly string[] | null;
28
+ defaultFormat: string | null;
29
+ formatConfigurable: boolean;
30
+ outputSchema: string | null;
31
+ jsonlSchema?: string | null;
32
+ sarifSchema?: string | null;
33
+ exitCodes: ExitCodeMeaning[];
34
+ exitCodePassthrough?: {
35
+ min: number;
36
+ max: number;
37
+ description: string;
38
+ };
39
+ stream: {
40
+ success: ContractStream;
41
+ findings?: ContractStream;
42
+ } | null;
43
+ writes: boolean | null;
44
+ stability: "stable" | "experimental";
45
+ notes?: string;
46
+ }
47
+ export type CommandContractRegistry = Record<string, CommandContract>;
48
+ export interface DescribedArgument {
49
+ name: string;
50
+ description: string;
51
+ arity: Arity;
52
+ valueType: ValueType | null;
53
+ allowedValues: string[] | null;
54
+ default?: unknown;
55
+ }
56
+ export interface DescribedOption {
57
+ name: string;
58
+ aliases: string[];
59
+ description: string;
60
+ valueName: string | null;
61
+ arity: Arity;
62
+ required: boolean;
63
+ negatable: boolean;
64
+ valueType: ValueType | null;
65
+ allowedValues: string[] | null;
66
+ recursive: boolean;
67
+ default?: unknown;
68
+ }
69
+ export interface DescribedCommand {
70
+ id: string;
71
+ path: string[];
72
+ description: string;
73
+ usage: string;
74
+ arguments: DescribedArgument[];
75
+ options: DescribedOption[];
76
+ subcommands: string[];
77
+ formats: string[] | null;
78
+ defaultFormat: string | null;
79
+ formatConfigurable: boolean;
80
+ outputSchema: string | null;
81
+ jsonlSchema?: string | null;
82
+ sarifSchema?: string | null;
83
+ exitCodes: ExitCodeMeaning[];
84
+ exitCodePassthrough?: {
85
+ min: number;
86
+ max: number;
87
+ description: string;
88
+ };
89
+ stream: {
90
+ success: ContractStream;
91
+ findings?: ContractStream;
92
+ } | null;
93
+ writes: boolean | null;
94
+ stability: Stability;
95
+ notes?: string;
96
+ }
97
+ export interface DescribeResult {
98
+ schemaVersion: string;
99
+ tool: {
100
+ name: string;
101
+ version: string;
102
+ };
103
+ formatShorthands: Record<string, string>;
104
+ advisoryOutput?: AdvisoryOutput;
105
+ schemas: SchemaRef[];
106
+ commands: DescribedCommand[];
107
+ }
108
+ export declare function isRequired(arity: Arity): boolean;
109
+ export declare function isVariadic(arity: Arity): boolean;
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ export const CONTRACT_VERSION = "1";
2
+ export function isRequired(arity) {
3
+ return arity.min >= 1;
4
+ }
5
+ export function isVariadic(arity) {
6
+ return arity.max === null || arity.max > 1;
7
+ }
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAsGpC,MAAM,UAAU,UAAU,CAAC,KAAY;IACrC,OAAO,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAY;IACrC,OAAO,KAAK,CAAC,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type DescribedArgument, type DescribedOption } from "./types.js";
2
+ /**
3
+ * Spec-defined usage string. A pure function of the rest of the command, so two
4
+ * emitters cannot disagree on a required field.
5
+ */
6
+ export declare function buildUsage(toolName: string, path: string[], options: readonly Pick<DescribedOption, "name">[], args: readonly DescribedArgument[]): string;
package/dist/usage.js ADDED
@@ -0,0 +1,21 @@
1
+ import { isRequired, isVariadic } from "./types.js";
2
+ /**
3
+ * Spec-defined usage string. A pure function of the rest of the command, so two
4
+ * emitters cannot disagree on a required field.
5
+ */
6
+ export function buildUsage(toolName, path, options, args) {
7
+ const parts = [toolName, ...path];
8
+ const rest = [];
9
+ if (options.length)
10
+ rest.push("[options]");
11
+ for (const argument of args) {
12
+ const variadic = isVariadic(argument.arity);
13
+ const required = isRequired(argument.arity);
14
+ if (variadic)
15
+ rest.push(required ? `<${argument.name}...>` : `[${argument.name}...]`);
16
+ else
17
+ rest.push(required ? `<${argument.name}>` : `[${argument.name}]`);
18
+ }
19
+ return [...parts, ...rest].join(" ");
20
+ }
21
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAgD,MAAM,YAAY,CAAC;AAElG;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,QAAgB,EAChB,IAAc,EACd,OAAiD,EACjD,IAAkC;IAElC,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC;IAClC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,OAAO,CAAC,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3C,KAAK,MAAM,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,QAAQ;YAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC;;YACjF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type ErrorObject } from "ajv/dist/2020.js";
2
+ export interface ValidationResult {
3
+ valid: boolean;
4
+ errors: ErrorObject[];
5
+ }
6
+ export declare function validate(payload: unknown): ValidationResult;
@@ -0,0 +1,9 @@
1
+ import { Ajv2020 } from "ajv/dist/2020.js";
2
+ import { cliSchema } from "./schema.js";
3
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
4
+ const validateFn = ajv.compile(cliSchema);
5
+ export function validate(payload) {
6
+ const valid = validateFn(payload);
7
+ return { valid: Boolean(valid), errors: validateFn.errors ? [...validateFn.errors] : [] };
8
+ }
9
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAoB,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5D,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAO1C,MAAM,UAAU,QAAQ,CAAC,OAAgB;IACvC,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAC5F,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@cairn-tool/cli-schema",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Portable CLI-description types, JSON Schema, validator, and text renderer",
6
+ "license": "MIT",
7
+ "author": "Bryan Stockus",
8
+ "keywords": [
9
+ "cli",
10
+ "schema",
11
+ "describe"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/cairn-tool/cli-schema.git",
16
+ "directory": "packages/cli-schema"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/cairn-tool/cli-schema/issues"
20
+ },
21
+ "homepage": "https://github.com/cairn-tool/cli-schema#readme",
22
+ "engines": {
23
+ "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
24
+ },
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ },
40
+ "scripts": {
41
+ "build": "tsc && cp src/cli-schema.json dist/cli-schema.json",
42
+ "clean": "rm -rf dist",
43
+ "typecheck": "tsc --noEmit",
44
+ "pretest": "npm run build",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "prepublishOnly": "npm run clean && npm run build"
48
+ },
49
+ "dependencies": {
50
+ "ajv": "^8.20.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^22.15.0",
54
+ "typescript": "^5.7.0",
55
+ "vitest": "^5.0.0"
56
+ }
57
+ }