@cli-schema/spec 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.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # @cli-schema/spec
2
+
3
+ TypeScript types, the vendored meta-schema, and a validator for the
4
+ [CLI Schema specification](https://github.com/cli-schema/cli-schema) v1 — the open standard for
5
+ describing command-line interfaces in a language-agnostic, machine-readable way.
6
+
7
+ This is the only package in the [`cli-schema-js`](https://github.com/cli-schema/cli-schema-js)
8
+ monorepo with no CLI-framework dependency. It defines the *shape* of a CLI Schema document;
9
+ [`@cli-schema/commander`](https://www.npmjs.com/package/@cli-schema/commander) is what actually
10
+ produces one from a real CLI.
11
+
12
+ ## Quickstart
13
+
14
+ ```sh
15
+ npm install @cli-schema/spec
16
+ ```
17
+
18
+ ```ts
19
+ import { validate, SCHEMA_VERSION, type CliSchema } from '@cli-schema/spec'
20
+
21
+ const doc: CliSchema = {
22
+ schemaVersion: SCHEMA_VERSION,
23
+ name: 'mytool',
24
+ version: '1.0.0',
25
+ }
26
+
27
+ const result = validate(doc)
28
+ if (!result.valid) throw new Error(JSON.stringify(result.errors, null, 2))
29
+ ```
30
+
31
+ That's the minimum: a document only needs `schemaVersion`, `name`, and `version` to be valid —
32
+ everything else (commands, parameters, environment, intent, ...) is optional enrichment.
33
+
34
+ ## Purpose
35
+
36
+ CLI help text is written for humans, completion scripts are hand-authored per shell, and AI
37
+ agents guess flags. CLI Schema fixes this by defining one JSON document format that describes a
38
+ program's commands, parameters, intent, auth requirements, environment dependencies, and output
39
+ capabilities — so agents, IDEs, shell-completion generators, and docs tooling can all read the
40
+ same document instead of re-deriving it.
41
+
42
+ `@cli-schema/spec` is the compile-time and runtime contract for that format: the types you build
43
+ a document against, and the validator you check it with before trusting it.
44
+
45
+ ## Features
46
+
47
+ - **Full v1 type coverage** — every object in the spec has a corresponding TypeScript interface:
48
+ `CliSchema` (root), `Command`, `Namespace`, `Parameter`, `Constraint`, `Intent`, `Output`,
49
+ `Environment`, `EnvVar`, `ConfigFile`, `DefaultHandler`, `Shortcut`, `Deprecation`.
50
+ - **The normative meta-schema, vendored** — `schema/cli-schema.meta-schema.json` from the spec
51
+ repo, re-exported at `@cli-schema/spec/meta-schema.json` for tools that want the raw JSON
52
+ Schema directly (e.g. to validate in a non-JS pipeline, or feed to a codegen tool).
53
+ - **A ready-to-use validator** — `validate()` / `assertValid()`, backed by
54
+ [ajv](https://ajv.js.org)'s 2020-12 dialect support. No need to wire up ajv yourself.
55
+ - **`inferIntentFromHttp`** — a small pure helper for CLIs that wrap HTTP APIs: derives a
56
+ reasonable {@link Intent} from an HTTP verb (`GET`/`HEAD` → safe & idempotent, `PUT` →
57
+ idempotent, `DELETE` → destructive & idempotent, `POST`/`PATCH` → unset, since their semantics
58
+ are endpoint-specific).
59
+ - **Vendor extensions typed** — every object that permits `x-` prefixed fields (spec §14) is
60
+ typed to accept them (`[extension: \`x-${string}\`]: unknown`), so `x-my-tool: {...}` type-checks
61
+ without `as any`.
62
+
63
+ ## Usage
64
+
65
+ ### Building a document by hand
66
+
67
+ The types double as documentation of the format — reach for the [spec
68
+ itself](https://github.com/cli-schema/cli-schema/blob/main/spec/v1/README.md) for full field
69
+ semantics, but a well-populated document looks like:
70
+
71
+ ```ts
72
+ import type { CliSchema } from '@cli-schema/spec'
73
+
74
+ const doc: CliSchema = {
75
+ schemaVersion: 1,
76
+ name: 'gh',
77
+ version: '2.45.0',
78
+ description: 'GitHub CLI — bring GitHub to your terminal',
79
+ requiresAuth: true,
80
+ authCommands: ['auth login', 'auth logout'],
81
+ reservedMetaCommands: ['__schema'],
82
+ commands: [
83
+ {
84
+ name: 'status',
85
+ summary: 'Print information about relevant issues, pull requests, and notifications',
86
+ intent: { destructive: false, idempotent: true, requiresAuth: true },
87
+ },
88
+ ],
89
+ namespaces: [
90
+ {
91
+ segment: 'repo',
92
+ commands: [
93
+ {
94
+ name: 'delete',
95
+ path: ['repo'],
96
+ parameters: [
97
+ { role: 'positional', name: 'repository', type: 'string', required: true },
98
+ { role: 'confirmationSkip', name: 'yes', type: 'boolean', required: false },
99
+ ],
100
+ intent: { destructive: true, idempotent: true, scope: 'global', requiresConfirmation: true },
101
+ },
102
+ ],
103
+ },
104
+ ],
105
+ }
106
+ ```
107
+
108
+ In practice you'll usually generate this from a live CLI rather than write it by hand — see
109
+ [`@cli-schema/commander`](https://www.npmjs.com/package/@cli-schema/commander).
110
+
111
+ ### Validating an untrusted document
112
+
113
+ Per spec §15, consumers should treat schema documents as untrusted input:
114
+
115
+ ```ts
116
+ import { validate, assertValid } from '@cli-schema/spec'
117
+
118
+ const { valid, errors } = validate(untrustedDoc)
119
+ // errors is an ajv ErrorObject[] — empty when valid
120
+
121
+ assertValid(untrustedDoc) // throws with a formatted message if invalid
122
+ ```
123
+
124
+ ### Reading the raw meta-schema
125
+
126
+ ```ts
127
+ import metaSchema from '@cli-schema/spec/meta-schema.json' with { type: 'json' }
128
+ ```
129
+
130
+ Useful if you want to validate documents with your own ajv instance, a different validator
131
+ entirely, or a non-JS tool.
132
+
133
+ ### Deriving intent from an HTTP verb
134
+
135
+ ```ts
136
+ import { inferIntentFromHttp } from '@cli-schema/spec'
137
+
138
+ inferIntentFromHttp('DELETE') // { destructive: true, idempotent: true, scope: 'global' }
139
+ inferIntentFromHttp('POST') // undefined — annotate explicitly, semantics vary per endpoint
140
+ ```
141
+
142
+ ## Related packages
143
+
144
+ - [`@cli-schema/zod`](https://www.npmjs.com/package/@cli-schema/zod) — derive `Parameter`s from Zod schemas
145
+ - [`@cli-schema/commander`](https://www.npmjs.com/package/@cli-schema/commander) — generate a full `CliSchema` from a live Commander tree
146
+
147
+ ## License
148
+
149
+ MIT
@@ -0,0 +1,4 @@
1
+ export * from './types.ts';
2
+ export * from './validate.ts';
3
+ export * from './intent.ts';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./validate.js";
3
+ export * from "./intent.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { Intent } from './types.ts';
2
+ export declare function inferIntentFromHttp(method: string): Intent | undefined;
3
+ //# sourceMappingURL=intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAWxC,wBAAgB,mBAAmB,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAYvE"}
package/dist/intent.js ADDED
@@ -0,0 +1,14 @@
1
+ export function inferIntentFromHttp(method) {
2
+ switch (method.toUpperCase()) {
3
+ case 'GET':
4
+ case 'HEAD':
5
+ return { destructive: false, idempotent: true, scope: 'global' };
6
+ case 'PUT':
7
+ return { destructive: false, idempotent: true, scope: 'global' };
8
+ case 'DELETE':
9
+ return { destructive: true, idempotent: true, scope: 'global' };
10
+ default:
11
+ return undefined;
12
+ }
13
+ }
14
+ //# sourceMappingURL=intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.js","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAgBA,MAAM,UAAU,mBAAmB,CAAE,MAAc;IACjD,QAAQ,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;QAC7B,KAAK,KAAK,CAAC;QACX,KAAK,MAAM;YACT,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;QAClE,KAAK,KAAK;YACR,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;QAClE,KAAK,QAAQ;YACX,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;QACjE;YACE,OAAO,SAAS,CAAA;IACpB,CAAC;AACH,CAAC"}
@@ -0,0 +1,213 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://cli-schema.github.io/cli-schema/schema/cli-schema.meta-schema.json",
4
+ "title": "CLI Schema",
5
+ "description": "Meta-schema for CLI Schema documents (specification v1)",
6
+ "type": "object",
7
+ "$defs": {
8
+ "deprecation": {
9
+ "oneOf": [
10
+ { "type": "boolean", "const": true },
11
+ {
12
+ "type": "object",
13
+ "properties": {
14
+ "message": { "type": "string" },
15
+ "since": { "type": "string" },
16
+ "removedIn": { "type": "string" }
17
+ },
18
+ "additionalProperties": false
19
+ }
20
+ ]
21
+ },
22
+ "constraint": {
23
+ "type": "object",
24
+ "required": ["kind"],
25
+ "properties": {
26
+ "kind": {
27
+ "type": "string",
28
+ "enum": [
29
+ "range", "timeSpanRange", "length", "count", "regex",
30
+ "allowed", "denied", "email", "url", "uriScheme",
31
+ "fileExtensions", "existing", "nonExisting", "rejectSymbolicLinks"
32
+ ]
33
+ },
34
+ "min": { "type": "string" },
35
+ "max": { "type": "string" },
36
+ "pattern": { "type": "string" },
37
+ "values": { "type": "array", "items": { "type": "string" } }
38
+ },
39
+ "additionalProperties": false
40
+ },
41
+ "parameter": {
42
+ "type": "object",
43
+ "required": ["role", "name", "type", "required"],
44
+ "properties": {
45
+ "role": {
46
+ "type": "string",
47
+ "enum": ["flag", "positional", "confirmationSkip", "dryRun"]
48
+ },
49
+ "name": { "type": "string" },
50
+ "shortName": { "type": "string", "maxLength": 1 },
51
+ "type": {
52
+ "type": "string",
53
+ "enum": ["string", "integer", "number", "boolean", "array", "enum"]
54
+ },
55
+ "required": { "type": "boolean" },
56
+ "summary": { "type": "string" },
57
+ "defaultValue": { "type": "string" },
58
+ "repeatable": { "type": "boolean", "default": false },
59
+ "separator": { "type": "string" },
60
+ "aliases": { "type": "array", "items": { "type": "string" } },
61
+ "enumValues": { "type": "array", "items": { "type": "string" } },
62
+ "elementType": {
63
+ "type": "string",
64
+ "enum": ["string", "integer", "number", "boolean"]
65
+ },
66
+ "hidden": { "type": "boolean", "default": false },
67
+ "variadic": { "type": "boolean", "default": false },
68
+ "deprecated": { "$ref": "#/$defs/deprecation" },
69
+ "validations": {
70
+ "type": "array",
71
+ "items": { "$ref": "#/$defs/constraint" }
72
+ }
73
+ },
74
+ "patternProperties": { "^x-": {} },
75
+ "additionalProperties": false
76
+ },
77
+ "intent": {
78
+ "type": "object",
79
+ "properties": {
80
+ "destructive": { "type": "boolean" },
81
+ "idempotent": { "type": "boolean" },
82
+ "scope": {
83
+ "type": "string",
84
+ "enum": ["file", "directory", "global"]
85
+ },
86
+ "requiresConfirmation": { "type": "boolean" },
87
+ "requiresAuth": { "type": "boolean" }
88
+ },
89
+ "additionalProperties": false
90
+ },
91
+ "output": {
92
+ "type": "object",
93
+ "properties": {
94
+ "formats": { "type": "array", "items": { "type": "string" } },
95
+ "formatFlag": { "type": "string" }
96
+ },
97
+ "additionalProperties": false
98
+ },
99
+ "envVar": {
100
+ "type": "object",
101
+ "required": ["name"],
102
+ "properties": {
103
+ "name": { "type": "string" },
104
+ "required": { "type": "boolean", "default": false },
105
+ "description": { "type": "string" },
106
+ "defaultValue": { "type": "string" }
107
+ },
108
+ "additionalProperties": false
109
+ },
110
+ "configFile": {
111
+ "type": "object",
112
+ "required": ["path"],
113
+ "properties": {
114
+ "path": { "type": "string" },
115
+ "description": { "type": "string" },
116
+ "required": { "type": "boolean", "default": false }
117
+ },
118
+ "additionalProperties": false
119
+ },
120
+ "environment": {
121
+ "type": "object",
122
+ "properties": {
123
+ "variables": { "type": "array", "items": { "$ref": "#/$defs/envVar" } },
124
+ "configFiles": { "type": "array", "items": { "$ref": "#/$defs/configFile" } }
125
+ },
126
+ "additionalProperties": false
127
+ },
128
+ "defaultHandler": {
129
+ "type": "object",
130
+ "required": ["kind"],
131
+ "properties": {
132
+ "kind": { "type": "string", "enum": ["root", "namespace"] },
133
+ "summary": { "type": "string" },
134
+ "notes": { "type": "string" },
135
+ "usage": { "type": "string" },
136
+ "examples": { "type": "array", "items": { "type": "string" } },
137
+ "parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
138
+ "hidden": { "type": "boolean", "default": false }
139
+ },
140
+ "patternProperties": { "^x-": {} },
141
+ "additionalProperties": false
142
+ },
143
+ "command": {
144
+ "type": "object",
145
+ "required": ["name"],
146
+ "properties": {
147
+ "path": { "type": "array", "items": { "type": "string" } },
148
+ "name": { "type": "string" },
149
+ "summary": { "type": "string" },
150
+ "notes": { "type": "string" },
151
+ "usage": { "type": "string" },
152
+ "examples": { "type": "array", "items": { "type": "string" } },
153
+ "parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
154
+ "aliases": { "type": "array", "items": { "type": "string" } },
155
+ "hidden": { "type": "boolean", "default": false },
156
+ "tags": { "type": "array", "items": { "type": "string" } },
157
+ "deprecated": { "$ref": "#/$defs/deprecation" },
158
+ "intent": { "$ref": "#/$defs/intent" },
159
+ "output": { "$ref": "#/$defs/output" },
160
+ "streaming": { "type": "boolean", "default": false },
161
+ "longRunning": { "type": "boolean", "default": false }
162
+ },
163
+ "patternProperties": { "^x-": {} },
164
+ "additionalProperties": false
165
+ },
166
+ "namespace": {
167
+ "type": "object",
168
+ "required": ["segment"],
169
+ "properties": {
170
+ "segment": { "type": "string" },
171
+ "summary": { "type": "string" },
172
+ "notes": { "type": "string" },
173
+ "options": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
174
+ "defaultCommand": { "$ref": "#/$defs/defaultHandler" },
175
+ "commands": { "type": "array", "items": { "$ref": "#/$defs/command" } },
176
+ "namespaces": { "type": "array", "items": { "$ref": "#/$defs/namespace" } }
177
+ },
178
+ "patternProperties": { "^x-": {} },
179
+ "additionalProperties": false
180
+ },
181
+ "shortcut": {
182
+ "type": "object",
183
+ "required": ["from", "to"],
184
+ "properties": {
185
+ "from": { "type": "string" },
186
+ "to": { "type": "array", "items": { "type": "string" }, "minItems": 1 }
187
+ },
188
+ "additionalProperties": false
189
+ }
190
+ },
191
+ "required": ["schemaVersion", "name", "version"],
192
+ "properties": {
193
+ "schemaVersion": {
194
+ "type": "integer",
195
+ "const": 1
196
+ },
197
+ "name": { "type": "string" },
198
+ "version": { "type": "string" },
199
+ "description": { "type": "string" },
200
+ "tags": { "type": "array", "items": { "type": "string" } },
201
+ "requiresAuth": { "type": "boolean" },
202
+ "authCommands": { "type": "array", "items": { "type": "string" } },
203
+ "environment": { "$ref": "#/$defs/environment" },
204
+ "reservedMetaCommands": { "type": "array", "items": { "type": "string" } },
205
+ "globalOptions": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
206
+ "rootDefault": { "$ref": "#/$defs/defaultHandler" },
207
+ "commands": { "type": "array", "items": { "$ref": "#/$defs/command" } },
208
+ "namespaces": { "type": "array", "items": { "$ref": "#/$defs/namespace" } },
209
+ "shortcuts": { "type": "array", "items": { "$ref": "#/$defs/shortcut" } }
210
+ },
211
+ "patternProperties": { "^x-": {} },
212
+ "additionalProperties": false
213
+ }
@@ -0,0 +1,122 @@
1
+ export declare const SCHEMA_VERSION: 1;
2
+ export type ParameterType = 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'enum';
3
+ export type ParameterElementType = 'string' | 'integer' | 'number' | 'boolean';
4
+ export type ParameterRole = 'flag' | 'positional' | 'confirmationSkip' | 'dryRun';
5
+ export type ConstraintKind = 'range' | 'timeSpanRange' | 'length' | 'count' | 'regex' | 'allowed' | 'denied' | 'email' | 'url' | 'uriScheme' | 'fileExtensions' | 'existing' | 'nonExisting' | 'rejectSymbolicLinks';
6
+ export interface Constraint {
7
+ kind: ConstraintKind;
8
+ min?: string;
9
+ max?: string;
10
+ pattern?: string;
11
+ values?: string[];
12
+ }
13
+ export type Deprecation = true | {
14
+ message?: string;
15
+ since?: string;
16
+ removedIn?: string;
17
+ };
18
+ export interface Parameter {
19
+ role: ParameterRole;
20
+ name: string;
21
+ type: ParameterType;
22
+ required: boolean;
23
+ shortName?: string;
24
+ summary?: string;
25
+ defaultValue?: string;
26
+ repeatable?: boolean;
27
+ separator?: string;
28
+ aliases?: string[];
29
+ enumValues?: string[];
30
+ elementType?: ParameterElementType;
31
+ hidden?: boolean;
32
+ variadic?: boolean;
33
+ deprecated?: Deprecation;
34
+ validations?: Constraint[];
35
+ [extension: `x-${string}`]: unknown;
36
+ }
37
+ export interface Intent {
38
+ destructive?: boolean;
39
+ idempotent?: boolean;
40
+ scope?: 'file' | 'directory' | 'global';
41
+ requiresConfirmation?: boolean;
42
+ requiresAuth?: boolean;
43
+ }
44
+ export interface Output {
45
+ formats?: string[];
46
+ formatFlag?: string;
47
+ }
48
+ export interface DefaultHandler {
49
+ kind: 'root' | 'namespace';
50
+ summary?: string;
51
+ notes?: string;
52
+ usage?: string;
53
+ examples?: string[];
54
+ parameters?: Parameter[];
55
+ hidden?: boolean;
56
+ [extension: `x-${string}`]: unknown;
57
+ }
58
+ export interface Command {
59
+ name: string;
60
+ path?: string[];
61
+ summary?: string;
62
+ notes?: string;
63
+ usage?: string;
64
+ examples?: string[];
65
+ parameters?: Parameter[];
66
+ aliases?: string[];
67
+ hidden?: boolean;
68
+ tags?: string[];
69
+ deprecated?: Deprecation;
70
+ intent?: Intent;
71
+ output?: Output;
72
+ streaming?: boolean;
73
+ longRunning?: boolean;
74
+ [extension: `x-${string}`]: unknown;
75
+ }
76
+ export interface Namespace {
77
+ segment: string;
78
+ summary?: string;
79
+ notes?: string;
80
+ options?: Parameter[];
81
+ defaultCommand?: DefaultHandler;
82
+ commands?: Command[];
83
+ namespaces?: Namespace[];
84
+ [extension: `x-${string}`]: unknown;
85
+ }
86
+ export interface EnvVar {
87
+ name: string;
88
+ required?: boolean;
89
+ description?: string;
90
+ defaultValue?: string;
91
+ }
92
+ export interface ConfigFile {
93
+ path: string;
94
+ description?: string;
95
+ required?: boolean;
96
+ }
97
+ export interface Environment {
98
+ variables?: EnvVar[];
99
+ configFiles?: ConfigFile[];
100
+ }
101
+ export interface Shortcut {
102
+ from: string;
103
+ to: string[];
104
+ }
105
+ export interface CliSchema {
106
+ schemaVersion: typeof SCHEMA_VERSION;
107
+ name: string;
108
+ version: string;
109
+ description?: string;
110
+ tags?: string[];
111
+ requiresAuth?: boolean;
112
+ authCommands?: string[];
113
+ environment?: Environment;
114
+ reservedMetaCommands?: string[];
115
+ globalOptions?: Parameter[];
116
+ rootDefault?: DefaultHandler;
117
+ commands?: Command[];
118
+ namespaces?: Namespace[];
119
+ shortcuts?: Shortcut[];
120
+ [extension: `x-${string}`]: unknown;
121
+ }
122
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export const SCHEMA_VERSION = 1;
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAU,CAAA"}
@@ -0,0 +1,8 @@
1
+ import type { ErrorObject } from 'ajv';
2
+ export interface ValidationResult {
3
+ valid: boolean;
4
+ errors: ErrorObject[];
5
+ }
6
+ export declare function validate(doc: unknown): ValidationResult;
7
+ export declare function assertValid(doc: unknown): void;
8
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AAGtC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,WAAW,EAAE,CAAA;CACtB;AAkBD,wBAAgB,QAAQ,CAAE,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAIxD;AAGD,wBAAgB,WAAW,CAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAM/C"}
@@ -0,0 +1,23 @@
1
+ import { Ajv2020 } from 'ajv/dist/2020.js';
2
+ import metaSchema from './meta-schema.json' with { type: 'json' };
3
+ let compiled;
4
+ function getValidator() {
5
+ if (compiled == null) {
6
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
7
+ compiled = ajv.compile(metaSchema);
8
+ }
9
+ return compiled;
10
+ }
11
+ export function validate(doc) {
12
+ const validator = getValidator();
13
+ const valid = validator(doc);
14
+ return { valid, errors: valid ? [] : [...(validator.errors ?? [])] };
15
+ }
16
+ export function assertValid(doc) {
17
+ const result = validate(doc);
18
+ if (!result.valid) {
19
+ const details = result.errors.map((e) => ` - ${e.instancePath || '/'}: ${e.message ?? 'invalid'}`).join('\n');
20
+ throw new Error(`Invalid CLI Schema document:\n${details}`);
21
+ }
22
+ }
23
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAE1C,OAAO,UAAU,MAAM,oBAAoB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAOjE,IAAI,QAAoD,CAAA;AAExD,SAAS,YAAY;IACnB,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;QAC3D,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IACpC,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAQD,MAAM,UAAU,QAAQ,CAAE,GAAY;IACpC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAY,CAAA;IACvC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,EAAE,CAAA;AACtE,CAAC;AAGD,MAAM,UAAU,WAAW,CAAE,GAAY;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,YAAY,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9G,MAAM,IAAI,KAAK,CAAC,iCAAiC,OAAO,EAAE,CAAC,CAAA;IAC7D,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@cli-schema/spec",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript types, meta-schema, and validator for the CLI Schema specification (https://github.com/cli-schema/cli-schema)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./meta-schema.json": "./dist/meta-schema.json"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -b",
23
+ "test": "node --import tsx/esm --test test/**/*.test.ts"
24
+ },
25
+ "dependencies": {
26
+ "ajv": "^8.17.1"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/cli-schema/cli-schema-js.git",
31
+ "directory": "packages/spec"
32
+ }
33
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ /*
2
+ * Copyright cli-schema contributors
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export * from './types.ts'
7
+ export * from './validate.ts'
8
+ export * from './intent.ts'
package/src/intent.ts ADDED
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Copyright cli-schema contributors
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import type { Intent } from './types.ts'
7
+
8
+ /**
9
+ * Derives an {@link Intent} from an HTTP method, for CLIs that wrap HTTP APIs.
10
+ *
11
+ * GET and HEAD are safe and idempotent.
12
+ * PUT is idempotent (upsert semantics).
13
+ * DELETE is destructive and idempotent.
14
+ * POST and PATCH are left `undefined` — their semantics depend on the specific endpoint
15
+ * and should be annotated explicitly by the caller.
16
+ */
17
+ export function inferIntentFromHttp (method: string): Intent | undefined {
18
+ switch (method.toUpperCase()) {
19
+ case 'GET':
20
+ case 'HEAD':
21
+ return { destructive: false, idempotent: true, scope: 'global' }
22
+ case 'PUT':
23
+ return { destructive: false, idempotent: true, scope: 'global' }
24
+ case 'DELETE':
25
+ return { destructive: true, idempotent: true, scope: 'global' }
26
+ default:
27
+ return undefined
28
+ }
29
+ }
@@ -0,0 +1,213 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://cli-schema.github.io/cli-schema/schema/cli-schema.meta-schema.json",
4
+ "title": "CLI Schema",
5
+ "description": "Meta-schema for CLI Schema documents (specification v1)",
6
+ "type": "object",
7
+ "$defs": {
8
+ "deprecation": {
9
+ "oneOf": [
10
+ { "type": "boolean", "const": true },
11
+ {
12
+ "type": "object",
13
+ "properties": {
14
+ "message": { "type": "string" },
15
+ "since": { "type": "string" },
16
+ "removedIn": { "type": "string" }
17
+ },
18
+ "additionalProperties": false
19
+ }
20
+ ]
21
+ },
22
+ "constraint": {
23
+ "type": "object",
24
+ "required": ["kind"],
25
+ "properties": {
26
+ "kind": {
27
+ "type": "string",
28
+ "enum": [
29
+ "range", "timeSpanRange", "length", "count", "regex",
30
+ "allowed", "denied", "email", "url", "uriScheme",
31
+ "fileExtensions", "existing", "nonExisting", "rejectSymbolicLinks"
32
+ ]
33
+ },
34
+ "min": { "type": "string" },
35
+ "max": { "type": "string" },
36
+ "pattern": { "type": "string" },
37
+ "values": { "type": "array", "items": { "type": "string" } }
38
+ },
39
+ "additionalProperties": false
40
+ },
41
+ "parameter": {
42
+ "type": "object",
43
+ "required": ["role", "name", "type", "required"],
44
+ "properties": {
45
+ "role": {
46
+ "type": "string",
47
+ "enum": ["flag", "positional", "confirmationSkip", "dryRun"]
48
+ },
49
+ "name": { "type": "string" },
50
+ "shortName": { "type": "string", "maxLength": 1 },
51
+ "type": {
52
+ "type": "string",
53
+ "enum": ["string", "integer", "number", "boolean", "array", "enum"]
54
+ },
55
+ "required": { "type": "boolean" },
56
+ "summary": { "type": "string" },
57
+ "defaultValue": { "type": "string" },
58
+ "repeatable": { "type": "boolean", "default": false },
59
+ "separator": { "type": "string" },
60
+ "aliases": { "type": "array", "items": { "type": "string" } },
61
+ "enumValues": { "type": "array", "items": { "type": "string" } },
62
+ "elementType": {
63
+ "type": "string",
64
+ "enum": ["string", "integer", "number", "boolean"]
65
+ },
66
+ "hidden": { "type": "boolean", "default": false },
67
+ "variadic": { "type": "boolean", "default": false },
68
+ "deprecated": { "$ref": "#/$defs/deprecation" },
69
+ "validations": {
70
+ "type": "array",
71
+ "items": { "$ref": "#/$defs/constraint" }
72
+ }
73
+ },
74
+ "patternProperties": { "^x-": {} },
75
+ "additionalProperties": false
76
+ },
77
+ "intent": {
78
+ "type": "object",
79
+ "properties": {
80
+ "destructive": { "type": "boolean" },
81
+ "idempotent": { "type": "boolean" },
82
+ "scope": {
83
+ "type": "string",
84
+ "enum": ["file", "directory", "global"]
85
+ },
86
+ "requiresConfirmation": { "type": "boolean" },
87
+ "requiresAuth": { "type": "boolean" }
88
+ },
89
+ "additionalProperties": false
90
+ },
91
+ "output": {
92
+ "type": "object",
93
+ "properties": {
94
+ "formats": { "type": "array", "items": { "type": "string" } },
95
+ "formatFlag": { "type": "string" }
96
+ },
97
+ "additionalProperties": false
98
+ },
99
+ "envVar": {
100
+ "type": "object",
101
+ "required": ["name"],
102
+ "properties": {
103
+ "name": { "type": "string" },
104
+ "required": { "type": "boolean", "default": false },
105
+ "description": { "type": "string" },
106
+ "defaultValue": { "type": "string" }
107
+ },
108
+ "additionalProperties": false
109
+ },
110
+ "configFile": {
111
+ "type": "object",
112
+ "required": ["path"],
113
+ "properties": {
114
+ "path": { "type": "string" },
115
+ "description": { "type": "string" },
116
+ "required": { "type": "boolean", "default": false }
117
+ },
118
+ "additionalProperties": false
119
+ },
120
+ "environment": {
121
+ "type": "object",
122
+ "properties": {
123
+ "variables": { "type": "array", "items": { "$ref": "#/$defs/envVar" } },
124
+ "configFiles": { "type": "array", "items": { "$ref": "#/$defs/configFile" } }
125
+ },
126
+ "additionalProperties": false
127
+ },
128
+ "defaultHandler": {
129
+ "type": "object",
130
+ "required": ["kind"],
131
+ "properties": {
132
+ "kind": { "type": "string", "enum": ["root", "namespace"] },
133
+ "summary": { "type": "string" },
134
+ "notes": { "type": "string" },
135
+ "usage": { "type": "string" },
136
+ "examples": { "type": "array", "items": { "type": "string" } },
137
+ "parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
138
+ "hidden": { "type": "boolean", "default": false }
139
+ },
140
+ "patternProperties": { "^x-": {} },
141
+ "additionalProperties": false
142
+ },
143
+ "command": {
144
+ "type": "object",
145
+ "required": ["name"],
146
+ "properties": {
147
+ "path": { "type": "array", "items": { "type": "string" } },
148
+ "name": { "type": "string" },
149
+ "summary": { "type": "string" },
150
+ "notes": { "type": "string" },
151
+ "usage": { "type": "string" },
152
+ "examples": { "type": "array", "items": { "type": "string" } },
153
+ "parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
154
+ "aliases": { "type": "array", "items": { "type": "string" } },
155
+ "hidden": { "type": "boolean", "default": false },
156
+ "tags": { "type": "array", "items": { "type": "string" } },
157
+ "deprecated": { "$ref": "#/$defs/deprecation" },
158
+ "intent": { "$ref": "#/$defs/intent" },
159
+ "output": { "$ref": "#/$defs/output" },
160
+ "streaming": { "type": "boolean", "default": false },
161
+ "longRunning": { "type": "boolean", "default": false }
162
+ },
163
+ "patternProperties": { "^x-": {} },
164
+ "additionalProperties": false
165
+ },
166
+ "namespace": {
167
+ "type": "object",
168
+ "required": ["segment"],
169
+ "properties": {
170
+ "segment": { "type": "string" },
171
+ "summary": { "type": "string" },
172
+ "notes": { "type": "string" },
173
+ "options": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
174
+ "defaultCommand": { "$ref": "#/$defs/defaultHandler" },
175
+ "commands": { "type": "array", "items": { "$ref": "#/$defs/command" } },
176
+ "namespaces": { "type": "array", "items": { "$ref": "#/$defs/namespace" } }
177
+ },
178
+ "patternProperties": { "^x-": {} },
179
+ "additionalProperties": false
180
+ },
181
+ "shortcut": {
182
+ "type": "object",
183
+ "required": ["from", "to"],
184
+ "properties": {
185
+ "from": { "type": "string" },
186
+ "to": { "type": "array", "items": { "type": "string" }, "minItems": 1 }
187
+ },
188
+ "additionalProperties": false
189
+ }
190
+ },
191
+ "required": ["schemaVersion", "name", "version"],
192
+ "properties": {
193
+ "schemaVersion": {
194
+ "type": "integer",
195
+ "const": 1
196
+ },
197
+ "name": { "type": "string" },
198
+ "version": { "type": "string" },
199
+ "description": { "type": "string" },
200
+ "tags": { "type": "array", "items": { "type": "string" } },
201
+ "requiresAuth": { "type": "boolean" },
202
+ "authCommands": { "type": "array", "items": { "type": "string" } },
203
+ "environment": { "$ref": "#/$defs/environment" },
204
+ "reservedMetaCommands": { "type": "array", "items": { "type": "string" } },
205
+ "globalOptions": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
206
+ "rootDefault": { "$ref": "#/$defs/defaultHandler" },
207
+ "commands": { "type": "array", "items": { "$ref": "#/$defs/command" } },
208
+ "namespaces": { "type": "array", "items": { "$ref": "#/$defs/namespace" } },
209
+ "shortcuts": { "type": "array", "items": { "$ref": "#/$defs/shortcut" } }
210
+ },
211
+ "patternProperties": { "^x-": {} },
212
+ "additionalProperties": false
213
+ }
package/src/types.ts ADDED
@@ -0,0 +1,184 @@
1
+ /*
2
+ * Copyright cli-schema contributors
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ /**
7
+ * TypeScript types for CLI Schema specification v1
8
+ * (https://github.com/cli-schema/cli-schema/blob/main/spec/v1/README.md).
9
+ *
10
+ * Field-for-field mirror of the meta-schema (`meta-schema.json`, vendored from the spec
11
+ * repo). Keep both in sync — the meta-schema is the normative source of truth; these types
12
+ * exist purely for compile-time ergonomics.
13
+ */
14
+
15
+ /** Always `1` for specification v1. */
16
+ export const SCHEMA_VERSION = 1 as const
17
+
18
+ /** JSON Schema primitive type names used by {@link Parameter.type} and {@link Parameter.elementType}. */
19
+ export type ParameterType = 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'enum'
20
+
21
+ /** Element type names for array-typed parameters (scalars only — see spec §6). */
22
+ export type ParameterElementType = 'string' | 'integer' | 'number' | 'boolean'
23
+
24
+ /** See spec §6.1 Roles. */
25
+ export type ParameterRole = 'flag' | 'positional' | 'confirmationSkip' | 'dryRun'
26
+
27
+ /** See spec §6.2 Constraints. */
28
+ export type ConstraintKind =
29
+ | 'range'
30
+ | 'timeSpanRange'
31
+ | 'length'
32
+ | 'count'
33
+ | 'regex'
34
+ | 'allowed'
35
+ | 'denied'
36
+ | 'email'
37
+ | 'url'
38
+ | 'uriScheme'
39
+ | 'fileExtensions'
40
+ | 'existing'
41
+ | 'nonExisting'
42
+ | 'rejectSymbolicLinks'
43
+
44
+ /** A validation rule applied to a parameter's value (spec §6.2). */
45
+ export interface Constraint {
46
+ kind: ConstraintKind
47
+ min?: string
48
+ max?: string
49
+ pattern?: string
50
+ values?: string[]
51
+ }
52
+
53
+ /** Structured deprecation metadata (spec §11.1). `true` signals deprecation without version detail. */
54
+ export type Deprecation = true | {
55
+ message?: string
56
+ since?: string
57
+ removedIn?: string
58
+ }
59
+
60
+ /** A single flag or positional argument (spec §6 Parameter Object). */
61
+ export interface Parameter {
62
+ role: ParameterRole
63
+ name: string
64
+ type: ParameterType
65
+ required: boolean
66
+ shortName?: string
67
+ summary?: string
68
+ defaultValue?: string
69
+ repeatable?: boolean
70
+ separator?: string
71
+ aliases?: string[]
72
+ enumValues?: string[]
73
+ elementType?: ParameterElementType
74
+ hidden?: boolean
75
+ variadic?: boolean
76
+ deprecated?: Deprecation
77
+ validations?: Constraint[]
78
+ [extension: `x-${string}`]: unknown
79
+ }
80
+
81
+ /** A command's side-effect profile, for agent risk reasoning (spec §11 Intent Object). */
82
+ export interface Intent {
83
+ destructive?: boolean
84
+ idempotent?: boolean
85
+ scope?: 'file' | 'directory' | 'global'
86
+ requiresConfirmation?: boolean
87
+ requiresAuth?: boolean
88
+ }
89
+
90
+ /** Machine-readable output formats a command supports (spec §12 Output Object). */
91
+ export interface Output {
92
+ formats?: string[]
93
+ formatFlag?: string
94
+ }
95
+
96
+ /** A handler invoked when a program or namespace is called with no subcommand (spec §10). */
97
+ export interface DefaultHandler {
98
+ kind: 'root' | 'namespace'
99
+ summary?: string
100
+ notes?: string
101
+ usage?: string
102
+ examples?: string[]
103
+ parameters?: Parameter[]
104
+ hidden?: boolean
105
+ [extension: `x-${string}`]: unknown
106
+ }
107
+
108
+ /** A single executable command (spec §7 Command Object). */
109
+ export interface Command {
110
+ name: string
111
+ path?: string[]
112
+ summary?: string
113
+ notes?: string
114
+ usage?: string
115
+ examples?: string[]
116
+ parameters?: Parameter[]
117
+ aliases?: string[]
118
+ hidden?: boolean
119
+ tags?: string[]
120
+ deprecated?: Deprecation
121
+ intent?: Intent
122
+ output?: Output
123
+ streaming?: boolean
124
+ longRunning?: boolean
125
+ [extension: `x-${string}`]: unknown
126
+ }
127
+
128
+ /** A named grouping of commands; namespaces may be nested arbitrarily (spec §8). */
129
+ export interface Namespace {
130
+ segment: string
131
+ summary?: string
132
+ notes?: string
133
+ options?: Parameter[]
134
+ defaultCommand?: DefaultHandler
135
+ commands?: Command[]
136
+ namespaces?: Namespace[]
137
+ [extension: `x-${string}`]: unknown
138
+ }
139
+
140
+ /** A single environment variable dependency (spec §9.1). */
141
+ export interface EnvVar {
142
+ name: string
143
+ required?: boolean
144
+ description?: string
145
+ defaultValue?: string
146
+ }
147
+
148
+ /** A single configuration file dependency (spec §9.2). */
149
+ export interface ConfigFile {
150
+ path: string
151
+ description?: string
152
+ required?: boolean
153
+ }
154
+
155
+ /** External context the program depends on (spec §9 Environment Object). */
156
+ export interface Environment {
157
+ variables?: EnvVar[]
158
+ configFiles?: ConfigFile[]
159
+ }
160
+
161
+ /** A root-level alias that transparently resolves to a deeper namespace or command path (spec §5.1). */
162
+ export interface Shortcut {
163
+ from: string
164
+ to: string[]
165
+ }
166
+
167
+ /** The root of a CLI Schema document (spec §5 Root Object). */
168
+ export interface CliSchema {
169
+ schemaVersion: typeof SCHEMA_VERSION
170
+ name: string
171
+ version: string
172
+ description?: string
173
+ tags?: string[]
174
+ requiresAuth?: boolean
175
+ authCommands?: string[]
176
+ environment?: Environment
177
+ reservedMetaCommands?: string[]
178
+ globalOptions?: Parameter[]
179
+ rootDefault?: DefaultHandler
180
+ commands?: Command[]
181
+ namespaces?: Namespace[]
182
+ shortcuts?: Shortcut[]
183
+ [extension: `x-${string}`]: unknown
184
+ }
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Copyright cli-schema contributors
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Ajv2020 } from 'ajv/dist/2020.js'
7
+ import type { ErrorObject } from 'ajv'
8
+ import metaSchema from './meta-schema.json' with { type: 'json' }
9
+
10
+ export interface ValidationResult {
11
+ valid: boolean
12
+ errors: ErrorObject[]
13
+ }
14
+
15
+ let compiled: ReturnType<Ajv2020['compile']> | undefined
16
+
17
+ function getValidator (): ReturnType<Ajv2020['compile']> {
18
+ if (compiled == null) {
19
+ const ajv = new Ajv2020({ allErrors: true, strict: false })
20
+ compiled = ajv.compile(metaSchema)
21
+ }
22
+ return compiled
23
+ }
24
+
25
+ /**
26
+ * Validates a candidate document against the CLI Schema v1 meta-schema.
27
+ *
28
+ * Per spec §15 (Security Considerations), consumers should treat schema documents as
29
+ * untrusted input and validate them before acting on their contents.
30
+ */
31
+ export function validate (doc: unknown): ValidationResult {
32
+ const validator = getValidator()
33
+ const valid = validator(doc) as boolean
34
+ return { valid, errors: valid ? [] : [...(validator.errors ?? [])] }
35
+ }
36
+
37
+ /** Throws with a formatted message if `doc` does not conform to the meta-schema. */
38
+ export function assertValid (doc: unknown): void {
39
+ const result = validate(doc)
40
+ if (!result.valid) {
41
+ const details = result.errors.map((e) => ` - ${e.instancePath || '/'}: ${e.message ?? 'invalid'}`).join('\n')
42
+ throw new Error(`Invalid CLI Schema document:\n${details}`)
43
+ }
44
+ }