@dolusoft/command-protocol 1.0.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # @dolusoft/command-protocol
2
2
 
3
- The JSON types of [Command Console](https://github.com/dolusoft/command-console) and its line tokenizer. No
4
- dependencies, no framework, no DOM.
3
+ The JSON types of [Command Console](https://github.com/dolusoft/command-console), its line tokenizer, and the pure
4
+ functions over command input schemas, option lines and the envelope. No dependencies, no framework, no DOM.
5
5
 
6
6
  ```sh
7
7
  pnpm add @dolusoft/command-protocol
@@ -17,7 +17,8 @@ a value survives `JSON.parse(JSON.stringify(x))` unchanged. `JsonValue`, `JsonPr
17
17
  check it. Functions (a command's `run` and `complete`, callbacks) live in the core, never here.
18
18
 
19
19
  - **Entries** of the scrollback: `ConsoleEntry` is an `InputEntry` (`id`, `prompt` when the line ended, `text` or
20
- `null` when redacted, `redacted`, `status: 'submitted' | 'cancelled'`) or an `OutputEntry` (`id`,
20
+ `null` when redacted, `redacted`, `status: 'submitted' | 'cancelled'`, optional `actor`: `'user'`, `'assistant'` or
21
+ `'external-agent'`, a label for the history and never a permission) or an `OutputEntry` (`id`,
21
22
  `tone: 'default' | 'error' | 'notice'`, `blocks`, optional `inputId` of the line it answers, optional `quiet: true`
22
23
  for output a live region should not read out).
23
24
  - **Blocks** of an output entry (`OutputBlock`), never HTML:
@@ -33,10 +34,17 @@ check it. Functions (a command's `run` and `complete`, callbacks) live in the co
33
34
 
34
35
  - **Messages** (`MessageCode`, `MessageParams`): the library writes no text, only codes. `command-not-found`
35
36
  (`{ name }`), `command-failed` (no params: the error's text is never shown), `unterminated-quote` (`{ index }`),
36
- `interrupted`. For a sensitive line `name` and `index` are `null`.
37
+ `interrupted`, `invalid-args` (`{ path, reason }`: the first error of a command's options, `reason` an
38
+ `InputErrorCode`) and `operation-failed` (`{ code }`: an `ErrorCode`, for an operation that ended without running its
39
+ command). For a sensitive line `name`, `index` and `path` are `null`.
37
40
  - **Commands**: `CommandSpec` is the JSON part of a command (`name`, `aliases`, `summary`, `usage`, `hidden`,
38
- `sensitive: boolean | { fromArg }`). `CommandResponse` is what a command may return (`output`, `tone`, `quiet`,
39
- `effects`).
41
+ `sensitive: boolean | { fromArg }`, `input` (its input schema, below), `danger: 'read' | 'change' | 'irreversible'`).
42
+ `CommandResponse` is what a command may return (`output`, `tone`, `quiet`, `effects`, and `summary`, a short result
43
+ of at most 200 characters for the caller of an operation).
44
+ - **Outcomes** (`CommandOutcome`): `{ status: 'ok', summary? }` or `{ status: 'error', code: ErrorCode, message? }`,
45
+ how an operation ended for its caller. `ErrorCode` is `invalid-args`, `unknown-command`, `not-allowed`,
46
+ `target-not-found`, `id-conflict`, `unsupported-version`, `too-large`, `busy`, `timeout`, `failed`,
47
+ `connection-lost` or `cancelled`.
40
48
  - **Effects** (`ConsoleEffect`): `clear`, `close`, `push-context` (`frame`), `pop-context` (`count?`, default 1),
41
49
  `reset-context`, `clear-recall`. They apply in order, before the response's output.
42
50
  - **Context frames** (`ContextFrame`): `{ id, prompt, data? }`. The top frame's `prompt` is the prompt of the next line.
@@ -70,6 +78,101 @@ console refuses such a line; completion works on the tolerant tokens. Nothing is
70
78
  `quoteArgument(value)` returns the shortest of: the bare value, `'value'`, or `"value"` with escapes; it always
71
79
  tokenizes back to `value`.
72
80
 
81
+ ## Input schemas
82
+
83
+ A command's `input` is a small subset of JSON Schema. The root is `type: 'object'`; a node is one of:
84
+
85
+ | `type` | Keywords besides `description` and `default` |
86
+ | ------------------- | -------------------------------------------- |
87
+ | `string` | `enum`, `minLength`, `maxLength`, `pattern` |
88
+ | `number`, `integer` | `enum`, `minimum`, `maximum` |
89
+ | `boolean` | none |
90
+ | `array` | `items` (required), `maxItems`; no `default` |
91
+ | `object` | `properties`, `required` |
92
+
93
+ `checkInputSchema(schema)` throws a `TypeError` naming the path for anything else: another keyword, a keyword on a
94
+ type it does not apply to, a property name that does not match `^[A-Za-z][A-Za-z0-9_-]*$`, a `pattern` that does
95
+ not compile with the `u` flag, a `required` name that is not a property, a `default` the node rejects, or objects and
96
+ arrays nested deeper than the input limits allow. Nothing is ignored.
97
+
98
+ `validateInput(schema, value)` returns `{ ok: true, value }` with a new value (an optional field missing from an
99
+ object takes its `default`, an absent optional array is `[]`) or `{ ok: false, errors }` with every error as
100
+ `{ path, code }`: `path` is `ip`, `filter.ip`, `tags[2]` or `''` for the root, and the value is never in it. The codes
101
+ are `type` (an `integer` wants a whole number), `required`, `additional` (a field not in `properties`), `limit` and
102
+ the name of the failed keyword. Lengths count code points; a `pattern` is not anchored.
103
+
104
+ Every value is held to `inputLimits` whatever its schema allows: strings of at most 1000 code points
105
+ (`maxTextLength`), arrays of at most 50 items (`maxArrayLength`), objects and arrays at most 3 deep, the root object
106
+ being 1 (`maxDepth`). Beyond them the code is `limit`.
107
+
108
+ ### Option lines
109
+
110
+ A command with a schema is written `name --field value …`. `parseOptions(args, schema)` takes the token values after
111
+ the name and returns what `validateInput` returns:
112
+
113
+ ```ts
114
+ import {
115
+ formatCommandLine,
116
+ type InputSchema,
117
+ parseOptions,
118
+ tokenize
119
+ } from '@dolusoft/command-protocol'
120
+
121
+ const schema: InputSchema = {
122
+ type: 'object',
123
+ properties: {
124
+ ip: { type: 'string' },
125
+ limit: { type: 'integer', default: 100 },
126
+ verbose: { type: 'boolean' },
127
+ tags: { type: 'array', items: { type: 'string' } }
128
+ },
129
+ required: ['ip']
130
+ }
131
+
132
+ const args = tokenize('search --ip 10.0.0.5 --verbose --tags a --tags b')
133
+ .tokens.slice(1)
134
+ .map(t => t.value)
135
+ parseOptions(args, schema)
136
+ // { ok: true, value: { ip: '10.0.0.5', limit: 100, verbose: true, tags: ['a', 'b'] } }
137
+
138
+ formatCommandLine('search', { ip: '10.0.0.5', tags: [] }, schema)
139
+ // 'search --ip 10.0.0.5 --limit 100'
140
+ ```
141
+
142
+ A value converts to its field's type: a number as JSON writes it, an object (or an array item that is an object or
143
+ an array) as JSON text, a string as is even when it starts with `--`. A boolean field alone is `true` and also takes
144
+ `true` or `false`. An array field repeats its name; an absent one is `[]`. An unknown name, or a token that is not a
145
+ name, is `additional`; a name without its value, or a second value of a field that is not an array, is `type`.
146
+
147
+ `formatCommandLine(command, value, schema)` writes the canonical line: the fields of the validated value in the order
148
+ of `properties`, every token through `quoteArgument`, `true` as the bare `--name` and `false` as `--name false`. Its
149
+ tokens after the name read back with `parseOptions` to the same value. A value the schema rejects throws.
150
+
151
+ ## Envelope
152
+
153
+ An `Envelope` carries one request between the tabs of a user; the consumer's transport moves it, the protocol only
154
+ describes and checks it:
155
+
156
+ ```json
157
+ {
158
+ "v": 1,
159
+ "id": "3f0c6c1e-6d1b-4a8e-9b7e-2a4c1f2d9e10",
160
+ "kind": "request",
161
+ "actor": "user",
162
+ "from": { "tab": "t-5b21" },
163
+ "target": { "tab": "t-5b21", "view": "v-91c0" },
164
+ "command": "dataview.search",
165
+ "args": { "ip": "10.0.0.5", "from": "2026-11-04T18:40:00Z" }
166
+ }
167
+ ```
168
+
169
+ `parseEnvelope(raw)` reads what came from the transport and never changes it. It returns `{ ok: true, envelope }`
170
+ with a new envelope that holds only the known fields, or `{ ok: false, code }`: `unsupported-version` for a plain
171
+ object whose `v` is a number other than 1, `too-large` when its UTF-8 JSON is over `envelopeMaxBytes` (16384,
172
+ measured on the raw value), `invalid` for a missing or mistyped field, another `kind`, or `args` that is not a JSON
173
+ object within the input limits. A `null` `target.view`, which a typed transport may write back, is no view and is
174
+ left out. `envelopeBytes(envelope)` is the UTF-8 length of its JSON, for a sender to measure before it sends.
175
+
73
176
  ## License
74
177
 
75
178
  MIT, see [LICENSE.md](./LICENSE.md).
@@ -22,16 +22,16 @@ function check(value, path) {
22
22
  const object = value;
23
23
  if (path.includes(object)) return false;
24
24
  path.push(object);
25
- const result = Array.isArray(object) ? checkArray(object, path) : checkObject(object, path);
25
+ const result = Array.isArray(object) ? checkArray$1(object, path) : checkObject$1(object, path);
26
26
  path.pop();
27
27
  return result;
28
28
  }
29
- function checkArray(array, path) {
29
+ function checkArray$1(array, path) {
30
30
  if (Object.getPrototypeOf(array) !== Array.prototype) return false;
31
31
  for (let i = 0; i < array.length; i++) if (!(i in array) || !check(array[i], path)) return false;
32
32
  return true;
33
33
  }
34
- function checkObject(object, path) {
34
+ function checkObject$1(object, path) {
35
35
  const prototype = Object.getPrototypeOf(object);
36
36
  if (prototype !== Object.prototype && prototype !== null) return false;
37
37
  if (Object.getOwnPropertySymbols(object).length > 0) return false;
@@ -39,6 +39,350 @@ function checkObject(object, path) {
39
39
  for (const key of Object.keys(record)) if (!check(record[key], path)) return false;
40
40
  return true;
41
41
  }
42
+ // src/input/schema.ts
43
+ /**
44
+ * The limits every input value is held to, whatever its schema allows
45
+ * (`limit` errors): a string's code points, an array's items, and how deep
46
+ * objects and arrays nest (the root object is depth 1). The envelope's `args`
47
+ * are held to them too.
48
+ */
49
+ var inputLimits = Object.freeze({
50
+ maxTextLength: 1e3,
51
+ maxDepth: 3,
52
+ maxArrayLength: 50
53
+ });
54
+ // src/input/validate.ts
55
+ /**
56
+ * Checks `value` against `schema` (one that `checkInputSchema` accepted) and
57
+ * the input limits. Returns a new value, never the input: an optional field
58
+ * missing from an object takes its `default`, an absent optional array is
59
+ * `[]`. Every error is returned, not the first only.
60
+ */
61
+ function validateInput(schema, value) {
62
+ const errors = [];
63
+ const result = validateNode(schema, value, "", 1, errors);
64
+ return errors.length === 0 ? {
65
+ ok: true,
66
+ value: result
67
+ } : {
68
+ ok: false,
69
+ errors
70
+ };
71
+ }
72
+ /**
73
+ * The copy of `value` checked against `node` at `depth` (the root object is
74
+ * 1); errors are pushed to `errors`. On an error the copy is incomplete.
75
+ */
76
+ function validateNode(node, value, path, depth, errors) {
77
+ switch (node.type) {
78
+ case "string": return validateString(node, value, path, errors);
79
+ case "number":
80
+ case "integer": return validateNumber(node, value, path, errors);
81
+ case "boolean":
82
+ if (typeof value !== "boolean") {
83
+ errors.push({
84
+ path,
85
+ code: "type"
86
+ });
87
+ return null;
88
+ }
89
+ return value;
90
+ case "array": return validateArray(node, value, path, depth, errors);
91
+ case "object": return validateObject(node, value, path, depth, errors);
92
+ }
93
+ }
94
+ /**
95
+ * Sets an own, enumerable field, so that a name such as `__proto__` is a
96
+ * field and never the prototype.
97
+ */
98
+ function define(target, name, value) {
99
+ Object.defineProperty(target, name, {
100
+ value,
101
+ enumerable: true,
102
+ writable: true,
103
+ configurable: true
104
+ });
105
+ }
106
+ /** The number of code points of `text`. */
107
+ function codePoints(text) {
108
+ let count = 0;
109
+ for (let i = 0; i < text.length; i++) {
110
+ const unit = text.charCodeAt(i);
111
+ if (unit >= 55296 && unit <= 56319) {
112
+ const next = text.charCodeAt(i + 1);
113
+ if (next >= 56320 && next <= 57343) i++;
114
+ }
115
+ count++;
116
+ }
117
+ return count;
118
+ }
119
+ /** Whether `value` is a plain object (prototype `Object.prototype` or null). */
120
+ function isPlainObject(value) {
121
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
122
+ const prototype = Object.getPrototypeOf(value);
123
+ return prototype === Object.prototype || prototype === null;
124
+ }
125
+ function validateString(node, value, path, errors) {
126
+ if (typeof value !== "string") {
127
+ errors.push({
128
+ path,
129
+ code: "type"
130
+ });
131
+ return null;
132
+ }
133
+ const length = codePoints(value);
134
+ if (length > inputLimits.maxTextLength) {
135
+ errors.push({
136
+ path,
137
+ code: "limit"
138
+ });
139
+ return null;
140
+ }
141
+ if (node.enum !== void 0 && !node.enum.includes(value)) errors.push({
142
+ path,
143
+ code: "enum"
144
+ });
145
+ if (node.minLength !== void 0 && length < node.minLength) errors.push({
146
+ path,
147
+ code: "minLength"
148
+ });
149
+ if (node.maxLength !== void 0 && length > node.maxLength) errors.push({
150
+ path,
151
+ code: "maxLength"
152
+ });
153
+ if (node.pattern !== void 0 && !new RegExp(node.pattern, "u").test(value)) errors.push({
154
+ path,
155
+ code: "pattern"
156
+ });
157
+ return value;
158
+ }
159
+ function validateNumber(node, value, path, errors) {
160
+ if (typeof value !== "number" || !Number.isFinite(value) || node.type === "integer" && !Number.isInteger(value)) {
161
+ errors.push({
162
+ path,
163
+ code: "type"
164
+ });
165
+ return null;
166
+ }
167
+ if (node.enum !== void 0 && !node.enum.includes(value)) errors.push({
168
+ path,
169
+ code: "enum"
170
+ });
171
+ if (node.minimum !== void 0 && value < node.minimum) errors.push({
172
+ path,
173
+ code: "minimum"
174
+ });
175
+ if (node.maximum !== void 0 && value > node.maximum) errors.push({
176
+ path,
177
+ code: "maximum"
178
+ });
179
+ return value;
180
+ }
181
+ function validateArray(node, value, path, depth, errors) {
182
+ if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
183
+ errors.push({
184
+ path,
185
+ code: "type"
186
+ });
187
+ return null;
188
+ }
189
+ const items = value;
190
+ if (depth > inputLimits.maxDepth || items.length > inputLimits.maxArrayLength) {
191
+ errors.push({
192
+ path,
193
+ code: "limit"
194
+ });
195
+ return null;
196
+ }
197
+ if (node.maxItems !== void 0 && items.length > node.maxItems) errors.push({
198
+ path,
199
+ code: "maxItems"
200
+ });
201
+ const result = [];
202
+ for (let i = 0; i < items.length; i++) result.push(validateNode(node.items, items[i], `${path}[${i}]`, depth + 1, errors));
203
+ return result;
204
+ }
205
+ function validateObject(node, value, path, depth, errors) {
206
+ if (!isPlainObject(value)) {
207
+ errors.push({
208
+ path,
209
+ code: "type"
210
+ });
211
+ return null;
212
+ }
213
+ if (depth > inputLimits.maxDepth) {
214
+ errors.push({
215
+ path,
216
+ code: "limit"
217
+ });
218
+ return null;
219
+ }
220
+ const properties = node.properties ?? {};
221
+ const result = {};
222
+ for (const name of Object.keys(properties)) {
223
+ const child = properties[name];
224
+ const at = path === "" ? name : `${path}.${name}`;
225
+ if (Object.hasOwn(value, name)) define(result, name, validateNode(child, value[name], at, depth + 1, errors));
226
+ else if (node.required?.includes(name) === true) errors.push({
227
+ path: at,
228
+ code: "required"
229
+ });
230
+ else if (child.type === "array") define(result, name, []);
231
+ else if (child.default !== void 0) define(result, name, validateNode(child, child.default, at, depth + 1, errors));
232
+ }
233
+ for (const name of Object.keys(value)) if (!Object.hasOwn(properties, name)) errors.push({
234
+ path: path === "" ? name : `${path}.${name}`,
235
+ code: "additional"
236
+ });
237
+ return result;
238
+ }
239
+ // src/input/check.ts
240
+ /** The keywords each type takes; every other keyword throws. */
241
+ var keywords = {
242
+ string: [
243
+ "type",
244
+ "description",
245
+ "default",
246
+ "enum",
247
+ "minLength",
248
+ "maxLength",
249
+ "pattern"
250
+ ],
251
+ number: [
252
+ "type",
253
+ "description",
254
+ "default",
255
+ "enum",
256
+ "minimum",
257
+ "maximum"
258
+ ],
259
+ integer: [
260
+ "type",
261
+ "description",
262
+ "default",
263
+ "enum",
264
+ "minimum",
265
+ "maximum"
266
+ ],
267
+ boolean: [
268
+ "type",
269
+ "description",
270
+ "default"
271
+ ],
272
+ array: [
273
+ "type",
274
+ "description",
275
+ "items",
276
+ "maxItems"
277
+ ],
278
+ object: [
279
+ "type",
280
+ "description",
281
+ "default",
282
+ "properties",
283
+ "required"
284
+ ]
285
+ };
286
+ var propertyName = /^[A-Za-z][A-Za-z0-9_-]*$/;
287
+ /**
288
+ * Throws a `TypeError` unless `schema` is an input schema of the subset: a
289
+ * root of `type: 'object'`; per type only its keywords; property names that
290
+ * match `^[A-Za-z][A-Za-z0-9_-]*$`; a `pattern` that compiles with the `u`
291
+ * flag; `required` names that are properties; containers no deeper than
292
+ * `inputLimits.maxDepth`; a `default` the node accepts, and none on an array.
293
+ * The message names the path of the node. Nothing is ignored. Called when a
294
+ * command is registered.
295
+ */
296
+ function checkInputSchema(schema) {
297
+ if (!isPlainObject(schema) || schema.type !== "object") throw new TypeError("Input schema: the root must be an object schema ({ type: \"object\" })");
298
+ checkNode(schema, "", 1);
299
+ }
300
+ function fail(path, problem) {
301
+ throw new TypeError(`Input schema at ${path === "" ? "the root" : `"${path}"`}: ${problem}`);
302
+ }
303
+ function checkNode(node, path, depth) {
304
+ if (!isPlainObject(node)) fail(path, "a node must be a plain object");
305
+ const type = node.type;
306
+ if (typeof type !== "string" || !Object.hasOwn(keywords, type)) fail(path, `"type" must be one of ${Object.keys(keywords).join(", ")}`);
307
+ const nodeType = type;
308
+ const allowed = keywords[nodeType];
309
+ for (const key of Object.keys(node)) if (!allowed.includes(key)) fail(path, Object.values(keywords).some((list) => list.includes(key)) ? `the keyword "${key}" does not apply to type "${nodeType}"` : `the keyword "${key}" is not supported`);
310
+ if (node.description !== void 0 && typeof node.description !== "string") fail(path, "\"description\" must be a string");
311
+ switch (nodeType) {
312
+ case "string":
313
+ checkString(node, path);
314
+ break;
315
+ case "number":
316
+ case "integer":
317
+ checkNumber(node, path, nodeType === "integer");
318
+ break;
319
+ case "boolean": break;
320
+ case "array":
321
+ checkArray(node, path, depth);
322
+ break;
323
+ case "object": checkObject(node, path, depth);
324
+ }
325
+ if (node.default !== void 0) {
326
+ const errors = [];
327
+ validateNode(node, node.default, path, depth, errors);
328
+ if (errors.length > 0) fail(path, `the "default" does not match the node (${errors[0].code} at "${errors[0].path}")`);
329
+ }
330
+ }
331
+ function isCount(value) {
332
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
333
+ }
334
+ function checkCount(node, key, path) {
335
+ if (node[key] !== void 0 && !isCount(node[key])) fail(path, `"${key}" must be a whole number of at least 0`);
336
+ }
337
+ function checkEnum(node, path, accepts) {
338
+ if (node.enum === void 0) return;
339
+ if (!Array.isArray(node.enum) || node.enum.length === 0 || !node.enum.every(accepts)) fail(path, "\"enum\" must be a non-empty list of values of the node's type");
340
+ }
341
+ function checkString(node, path) {
342
+ checkCount(node, "minLength", path);
343
+ checkCount(node, "maxLength", path);
344
+ if (node.minLength !== void 0 && node.maxLength !== void 0 && node.minLength > node.maxLength) fail(path, "\"minLength\" is above \"maxLength\"");
345
+ checkEnum(node, path, (item) => typeof item === "string");
346
+ if (node.pattern !== void 0) {
347
+ if (typeof node.pattern !== "string") fail(path, "\"pattern\" must be a string");
348
+ try {
349
+ new RegExp(node.pattern, "u");
350
+ } catch {
351
+ fail(path, "\"pattern\" is not a valid regular expression (u flag)");
352
+ }
353
+ }
354
+ }
355
+ function checkNumber(node, path, integer) {
356
+ for (const key of ["minimum", "maximum"]) if (node[key] !== void 0 && !(typeof node[key] === "number" && Number.isFinite(node[key]))) fail(path, `"${key}" must be a finite number`);
357
+ if (node.minimum !== void 0 && node.maximum !== void 0 && node.minimum > node.maximum) fail(path, "\"minimum\" is above \"maximum\"");
358
+ checkEnum(node, path, (item) => typeof item === "number" && Number.isFinite(item) && (!integer || Number.isInteger(item)));
359
+ }
360
+ function checkDepth(path, depth) {
361
+ if (depth > inputLimits.maxDepth) fail(path, `objects and arrays nest deeper than ${inputLimits.maxDepth}`);
362
+ }
363
+ function checkArray(node, path, depth) {
364
+ checkDepth(path, depth);
365
+ checkCount(node, "maxItems", path);
366
+ if (node.items === void 0) fail(path, "an array needs \"items\"");
367
+ checkNode(node.items, `${path}[]`, depth + 1);
368
+ }
369
+ function checkObject(node, path, depth) {
370
+ checkDepth(path, depth);
371
+ const properties = node.properties ?? {};
372
+ if (!isPlainObject(properties)) fail(path, "\"properties\" must be an object");
373
+ for (const name of Object.keys(properties)) {
374
+ const at = path === "" ? name : `${path}.${name}`;
375
+ if (!propertyName.test(name)) fail(at, `the property name must match ${String(propertyName)}`);
376
+ checkNode(properties[name], at, depth + 1);
377
+ }
378
+ const required = node.required ?? [];
379
+ if (!Array.isArray(required)) fail(path, "\"required\" must be a list of property names");
380
+ const seen = /* @__PURE__ */ new Set();
381
+ for (const name of required) {
382
+ if (typeof name !== "string" || !Object.hasOwn(properties, name) || seen.has(name)) fail(path, `"required" names "${String(name)}", which is not a property or is listed twice`);
383
+ seen.add(name);
384
+ }
385
+ }
42
386
  // src/syntax/tokenize.ts
43
387
  var whitespace = /\s/;
44
388
  /** Whether `char` separates tokens (`\s`, so tabs and line breaks too). */
@@ -161,6 +505,204 @@ function doubleQuote(value) {
161
505
  }
162
506
  return `${result}"`;
163
507
  }
164
- export { isJsonValue, quoteArgument, tokenize };
508
+ // src/syntax/options.ts
509
+ var jsonNumber = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
510
+ /**
511
+ * Reads the tokens after the command name (`tokenize` values) into the
512
+ * object `schema` describes. Each field is a `--name value` pair; the value
513
+ * converts to the field's type: a number as JSON writes it, an object (or an
514
+ * array item that is an object or an array) as JSON text, a string as is,
515
+ * even when it starts with `--`. A boolean field alone is `true` and also
516
+ * takes a following `true` or `false`. An array field repeats its name; an
517
+ * absent one is `[]`. An unknown name, or a token that is not a name, is
518
+ * `additional`; a name without its value, or a second value of a field that
519
+ * is not an array, is `type`. The result is then checked by `validateInput`,
520
+ * and its errors follow the line's.
521
+ */
522
+ function parseOptions(args, schema) {
523
+ const properties = schema.properties ?? {};
524
+ const errors = [];
525
+ const value = {};
526
+ let i = 0;
527
+ while (i < args.length) {
528
+ const token = args[i++];
529
+ if (!token.startsWith("--") || token === "--") {
530
+ errors.push({
531
+ path: "",
532
+ code: "additional"
533
+ });
534
+ continue;
535
+ }
536
+ const name = token.slice(2);
537
+ if (!Object.hasOwn(properties, name)) {
538
+ errors.push({
539
+ path: name,
540
+ code: "additional"
541
+ });
542
+ if (i < args.length && !args[i].startsWith("--")) i++;
543
+ continue;
544
+ }
545
+ const node = properties[name];
546
+ let field;
547
+ if (node.type === "boolean") {
548
+ const next = args[i];
549
+ field = next === "false" ? false : true;
550
+ if (next === "true" || next === "false") i++;
551
+ } else if (i < args.length) field = convert(node.type === "array" ? node.items : node, args[i++]);
552
+ else {
553
+ errors.push({
554
+ path: name,
555
+ code: "type"
556
+ });
557
+ continue;
558
+ }
559
+ if (node.type === "array") {
560
+ const list = Object.hasOwn(value, name) ? value[name] : [];
561
+ list.push(field);
562
+ define(value, name, list);
563
+ } else if (Object.hasOwn(value, name)) errors.push({
564
+ path: name,
565
+ code: "type"
566
+ });
567
+ else define(value, name, field);
568
+ }
569
+ for (const name of Object.keys(properties)) if (properties[name].type === "array" && !Object.hasOwn(value, name)) define(value, name, []);
570
+ const result = validateInput(schema, value);
571
+ if (errors.length === 0) return result;
572
+ return {
573
+ ok: false,
574
+ errors: result.ok ? errors : [...errors, ...result.errors]
575
+ };
576
+ }
577
+ /**
578
+ * The value of one token for `node`. A token that does not convert stays a
579
+ * string, which `validateInput` then reports as a `type` error.
580
+ */
581
+ function convert(node, token) {
582
+ switch (node.type) {
583
+ case "number":
584
+ case "integer": return jsonNumber.test(token) ? Number(token) : token;
585
+ case "boolean": return token === "true" ? true : token === "false" ? false : token;
586
+ case "array":
587
+ case "object": try {
588
+ return JSON.parse(token);
589
+ } catch {
590
+ return token;
591
+ }
592
+ case "string": return token;
593
+ }
594
+ }
595
+ /**
596
+ * The token of one value: a string as is, anything else as JSON (for a
597
+ * number or a boolean that is `String(value)`).
598
+ */
599
+ function written(value) {
600
+ return typeof value === "string" ? value : JSON.stringify(value);
601
+ }
602
+ /**
603
+ * The canonical line of a command call: the command name, then each field
604
+ * of the validated value (defaults filled) in the order of the schema's
605
+ * `properties`, every token through `quoteArgument`. `true` is the bare
606
+ * `--name`, `false` is `--name false`, an array repeats its name per item.
607
+ * `parseOptions` reads its tokens after the first back to the validated
608
+ * value. Throws a `TypeError` when `validateInput` rejects `value`.
609
+ */
610
+ function formatCommandLine(command, value, schema) {
611
+ const checked = validateInput(schema, value);
612
+ if (!checked.ok) throw new TypeError(`formatCommandLine: the value does not match the schema (${checked.errors.map((e) => `${e.code} at "${e.path}"`).join(", ")})`);
613
+ const properties = schema.properties ?? {};
614
+ const parts = [quoteArgument(command)];
615
+ for (const name of Object.keys(properties)) {
616
+ if (!Object.hasOwn(checked.value, name)) continue;
617
+ const node = properties[name];
618
+ const field = checked.value[name];
619
+ const option = quoteArgument(`--${name}`);
620
+ if (node.type === "boolean") parts.push(field === true ? option : `${option} false`);
621
+ else if (node.type === "array") for (const item of field) parts.push(option, quoteArgument(written(item)));
622
+ else parts.push(option, quoteArgument(written(field)));
623
+ }
624
+ return parts.join(" ");
625
+ }
626
+ // src/envelope.ts
627
+ /** The most bytes an envelope's UTF-8 JSON may take. */
628
+ var envelopeMaxBytes = 16384;
629
+ var actors = [
630
+ "user",
631
+ "assistant",
632
+ "external-agent"
633
+ ];
634
+ /** The length in bytes of the UTF-8 JSON of `envelope`. */
635
+ function envelopeBytes(envelope) {
636
+ return jsonBytes(envelope);
637
+ }
638
+ function jsonBytes(value) {
639
+ return new TextEncoder().encode(JSON.stringify(value)).length;
640
+ }
641
+ function isName(value) {
642
+ return typeof value === "string" && value !== "";
643
+ }
644
+ /**
645
+ * Reads a value that came from the transport. A plain object whose `v` is a
646
+ * number other than 1 is `unsupported-version`; one whose JSON is larger
647
+ * than `envelopeMaxBytes` (unknown fields included) is `too-large`; a missing
648
+ * or mistyped field, another `kind`, or `args` that is not a JSON object
649
+ * within `inputLimits` is `invalid`. A `null` `target.view` is no view (a
650
+ * typed transport may write one back). Unknown fields at the top and inside
651
+ * `from` and `target` are left out of the new envelope it returns; `raw` is
652
+ * never changed.
653
+ */
654
+ function parseEnvelope(raw) {
655
+ if (!isPlainObject(raw) || typeof raw.v !== "number") return {
656
+ ok: false,
657
+ code: "invalid"
658
+ };
659
+ if (raw.v !== 1) return {
660
+ ok: false,
661
+ code: "unsupported-version"
662
+ };
663
+ let bytes;
664
+ try {
665
+ bytes = jsonBytes(raw);
666
+ } catch {
667
+ return {
668
+ ok: false,
669
+ code: "invalid"
670
+ };
671
+ }
672
+ if (bytes > 16384) return {
673
+ ok: false,
674
+ code: "too-large"
675
+ };
676
+ const { id, kind, actor, from, target, command, args } = raw;
677
+ if (!isName(id) || kind !== "request" || !actors.includes(actor) || !isPlainObject(from) || !isName(from.tab) || !isPlainObject(target) || !isName(target.tab) || target.view !== void 0 && target.view !== null && !isName(target.view) || !isName(command) || !isPlainObject(args) || !isJsonValue(args) || !withinLimits(args, 1)) return {
678
+ ok: false,
679
+ code: "invalid"
680
+ };
681
+ return {
682
+ ok: true,
683
+ envelope: {
684
+ v: 1,
685
+ id,
686
+ kind,
687
+ actor,
688
+ from: { tab: from.tab },
689
+ target: target.view === void 0 || target.view === null ? { tab: target.tab } : {
690
+ tab: target.tab,
691
+ view: target.view
692
+ },
693
+ command,
694
+ args: JSON.parse(JSON.stringify(args))
695
+ }
696
+ };
697
+ }
698
+ /** Whether a JSON value at `depth` keeps to `inputLimits` (C-72). */
699
+ function withinLimits(value, depth) {
700
+ if (typeof value === "string") return codePoints(value) <= inputLimits.maxTextLength;
701
+ if (typeof value !== "object" || value === null) return true;
702
+ if (depth > inputLimits.maxDepth) return false;
703
+ if (Array.isArray(value)) return value.length <= inputLimits.maxArrayLength && value.every((item) => withinLimits(item, depth + 1));
704
+ return Object.values(value).every((item) => withinLimits(item, depth + 1));
705
+ }
706
+ export { checkInputSchema, envelopeBytes, envelopeMaxBytes, formatCommandLine, inputLimits, isJsonValue, parseEnvelope, parseOptions, quoteArgument, tokenize, validateInput };
165
707
 
166
708
  //# sourceMappingURL=command-protocol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"command-protocol.js","names":[],"sources":["../src/json.ts","../src/syntax/tokenize.ts","../src/syntax/quote.ts"],"sourcesContent":["/** A JSON leaf. Numbers are finite: `NaN` and the infinities are not JSON. */\nexport type JsonPrimitive = string | number | boolean | null\n\n/**\n * A JSON value: a primitive, an array of values or a plain object of values.\n * Every protocol type is made of these (P4).\n */\nexport type JsonValue =\n JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }\n\n/**\n * Whether `value` is a JSON value that survives\n * `JSON.parse(JSON.stringify(value))` unchanged: strings, booleans, `null`,\n * finite numbers, dense arrays and plain objects (prototype `Object.prototype`\n * or `null`) of such values, without cycles. `undefined`, functions, symbols,\n * bigints, class instances (`Date`, `Map`, …), sparse arrays and non-finite\n * numbers are not. `-0` is accepted; it reads back as `0`.\n */\nexport function isJsonValue(value: unknown): value is JsonValue {\n return check(value, [])\n}\n\nfunction check(value: unknown, path: object[]): boolean {\n if (value === null) {\n return true\n }\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return true\n case 'number':\n return Number.isFinite(value)\n case 'object':\n break\n default:\n return false\n }\n const object = value\n if (path.includes(object)) {\n return false\n }\n path.push(object)\n const result = Array.isArray(object)\n ? checkArray(object as unknown[], path)\n : checkObject(object, path)\n path.pop()\n return result\n}\n\nfunction checkArray(array: unknown[], path: object[]): boolean {\n if (Object.getPrototypeOf(array) !== Array.prototype) {\n return false\n }\n for (let i = 0; i < array.length; i++) {\n if (!(i in array) || !check(array[i], path)) {\n return false\n }\n }\n return true\n}\n\nfunction checkObject(object: object, path: object[]): boolean {\n const prototype: unknown = Object.getPrototypeOf(object)\n if (prototype !== Object.prototype && prototype !== null) {\n return false\n }\n if (Object.getOwnPropertySymbols(object).length > 0) {\n return false\n }\n const record = object as Record<string, unknown>\n for (const key of Object.keys(record)) {\n if (!check(record[key], path)) {\n return false\n }\n }\n return true\n}\n","// The line syntax (ADR 0007): shell-like quoting, `\\` literal outside double\n// quotes, no continuation. The tokenizer never drops a character: every\n// non-whitespace character of the line is inside exactly one token's span.\n\n/** One token of a line. */\nexport interface Token {\n /** The token's text with grouping quotes and escapes removed. */\n value: string\n /** UTF-16 offset of the token's first raw character in the line. */\n start: number\n /** UTF-16 offset after the token's last raw character (exclusive). */\n end: number\n /** True when any part of the token is quoted (`'…'` or `\"…\"`). */\n quoted: boolean\n /**\n * True when the token ends in a quote that is not closed; it then runs to\n * the end of the line. Only the last token can be open.\n */\n open: boolean\n}\n\n/** The error of a line whose quote is not closed. */\nexport interface TokenizeError {\n code: 'unterminated-quote'\n /** UTF-16 offset of the opening quote that is not closed. */\n index: number\n}\n\n/**\n * The tokens of a line. With `ok: false` the tokens are still returned\n * (tolerant): the last one has `open: true`. Dispatch refuses such a line;\n * completion works on its tokens.\n */\nexport type TokenizeResult =\n | { ok: true; tokens: Token[] }\n | { ok: false; tokens: Token[]; error: TokenizeError }\n\nconst whitespace = /\\s/\n\n/** Whether `char` separates tokens (`\\s`, so tabs and line breaks too). */\nexport function isSeparator(char: string): boolean {\n return whitespace.test(char)\n}\n\n/**\n * Splits a line into tokens. Runs of whitespace separate. `'…'` groups\n * literally. `\"…\"` groups, and inside it `\\\"` and `\\\\` are the only escapes;\n * any other `\\` is kept. Outside quotes `\\` is an ordinary character. Adjacent\n * parts join one token (`a\"b c\"d` is `ab cd`); `''` and `\"\"` give an empty\n * token. `start`/`end` cover the raw text of each token, quotes included.\n */\nexport function tokenize(line: string): TokenizeResult {\n const tokens: Token[] = []\n let error: TokenizeError | null = null\n let i = 0\n const length = line.length\n\n while (i < length) {\n if (isSeparator(line[i])) {\n i++\n continue\n }\n const start = i\n let value = ''\n let quoted = false\n let open = false\n\n while (i < length && !isSeparator(line[i])) {\n const char = line[i]\n if (char === \"'\") {\n quoted = true\n const close = line.indexOf(\"'\", i + 1)\n if (close === -1) {\n value += line.slice(i + 1)\n open = true\n error = { code: 'unterminated-quote', index: i }\n i = length\n break\n }\n value += line.slice(i + 1, close)\n i = close + 1\n } else if (char === '\"') {\n quoted = true\n const opening = i\n i++\n let closed = false\n while (i < length) {\n const inner = line[i]\n if (inner === '\"') {\n closed = true\n i++\n break\n }\n const next = line.charAt(i + 1)\n if (inner === '\\\\' && (next === '\"' || next === '\\\\')) {\n value += next\n i += 2\n } else {\n value += inner\n i++\n }\n }\n if (!closed) {\n open = true\n error = { code: 'unterminated-quote', index: opening }\n break\n }\n } else {\n value += char\n i++\n }\n }\n\n tokens.push({ value, start, end: i, quoted, open })\n }\n\n return error === null ? { ok: true, tokens } : { ok: false, tokens, error }\n}\n","import { isSeparator } from './tokenize'\n\n/**\n * The shortest form of `value` that `tokenize` reads back as one token with\n * that value: bare when it is non-empty and has no whitespace and no quote,\n * else `'…'` when it has no `'`, else `\"…\"` with `\"` written as `\\\"` and a\n * `\\` written as `\\\\` where it comes before a `\"`, a `\\` or the closing quote.\n */\nexport function quoteArgument(value: string): string {\n if (value === '' || needsQuotes(value)) {\n return value.includes(\"'\") ? doubleQuote(value) : `'${value}'`\n }\n return value\n}\n\nfunction needsQuotes(value: string): boolean {\n for (const char of value) {\n if (char === \"'\" || char === '\"' || isSeparator(char)) {\n return true\n }\n }\n return false\n}\n\nfunction doubleQuote(value: string): string {\n let result = '\"'\n for (let i = 0; i < value.length; i++) {\n const char = value[i]\n if (char === '\"') {\n result += '\\\\\"'\n } else if (char === '\\\\') {\n const next = value.charAt(i + 1)\n result += next === '' || next === '\"' || next === '\\\\' ? '\\\\\\\\' : '\\\\'\n } else {\n result += char\n }\n }\n return `${result}\"`\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"command-protocol.js","names":["checkArray","checkObject"],"sources":["../src/json.ts","../src/input/schema.ts","../src/input/validate.ts","../src/input/check.ts","../src/syntax/tokenize.ts","../src/syntax/quote.ts","../src/syntax/options.ts","../src/envelope.ts"],"sourcesContent":["/** A JSON leaf. Numbers are finite: `NaN` and the infinities are not JSON. */\nexport type JsonPrimitive = string | number | boolean | null\n\n/**\n * A JSON value: a primitive, an array of values or a plain object of values.\n * Every protocol type is made of these (P4).\n */\nexport type JsonValue =\n JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }\n\n/**\n * Whether `value` is a JSON value that survives\n * `JSON.parse(JSON.stringify(value))` unchanged: strings, booleans, `null`,\n * finite numbers, dense arrays and plain objects (prototype `Object.prototype`\n * or `null`) of such values, without cycles. `undefined`, functions, symbols,\n * bigints, class instances (`Date`, `Map`, …), sparse arrays and non-finite\n * numbers are not. `-0` is accepted; it reads back as `0`.\n */\nexport function isJsonValue(value: unknown): value is JsonValue {\n return check(value, [])\n}\n\nfunction check(value: unknown, path: object[]): boolean {\n if (value === null) {\n return true\n }\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return true\n case 'number':\n return Number.isFinite(value)\n case 'object':\n break\n default:\n return false\n }\n const object = value\n if (path.includes(object)) {\n return false\n }\n path.push(object)\n const result = Array.isArray(object)\n ? checkArray(object as unknown[], path)\n : checkObject(object, path)\n path.pop()\n return result\n}\n\nfunction checkArray(array: unknown[], path: object[]): boolean {\n if (Object.getPrototypeOf(array) !== Array.prototype) {\n return false\n }\n for (let i = 0; i < array.length; i++) {\n if (!(i in array) || !check(array[i], path)) {\n return false\n }\n }\n return true\n}\n\nfunction checkObject(object: object, path: object[]): boolean {\n const prototype: unknown = Object.getPrototypeOf(object)\n if (prototype !== Object.prototype && prototype !== null) {\n return false\n }\n if (Object.getOwnPropertySymbols(object).length > 0) {\n return false\n }\n const record = object as Record<string, unknown>\n for (const key of Object.keys(record)) {\n if (!check(record[key], path)) {\n return false\n }\n }\n return true\n}\n","import type { JsonValue } from '../json'\n\n// The input schema of a command: a small subset of JSON Schema, checked by\n// `checkInputSchema` and applied by `validateInput`. Every node is JSON (P4).\n\n/** A string field. Lengths count code points. */\nexport interface StringInputSchema {\n type: 'string'\n description?: string\n /** The only values allowed. */\n enum?: string[]\n minLength?: number\n maxLength?: number\n /** A regular expression (compiled with the `u` flag), not anchored. */\n pattern?: string\n default?: string\n}\n\n/** A number field; `integer` wants a whole number. */\nexport interface NumberInputSchema {\n type: 'number' | 'integer'\n description?: string\n /** The only values allowed. */\n enum?: number[]\n minimum?: number\n maximum?: number\n default?: number\n}\n\n/** A boolean field. */\nexport interface BooleanInputSchema {\n type: 'boolean'\n description?: string\n default?: boolean\n}\n\n/**\n * An array field. It takes no `default`: an absent optional array is `[]`,\n * so a line, which cannot write an empty array, reads back the same.\n */\nexport interface ArrayInputSchema {\n type: 'array'\n description?: string\n /** The schema of every item. */\n items: InputSchemaNode\n maxItems?: number\n}\n\n/**\n * An object: the root of a command's input, or a field that holds one. A\n * field that is not in `properties` is refused.\n */\nexport interface InputSchema {\n type: 'object'\n description?: string\n /** The fields, in the order the canonical line writes them. */\n properties?: { [name: string]: InputSchemaNode }\n /** The fields that must be given; each is in `properties`. */\n required?: string[]\n default?: { [name: string]: JsonValue }\n}\n\n/** One node of an input schema. */\nexport type InputSchemaNode =\n | StringInputSchema\n | NumberInputSchema\n | BooleanInputSchema\n | ArrayInputSchema\n | InputSchema\n\n/**\n * The limits every input value is held to, whatever its schema allows\n * (`limit` errors): a string's code points, an array's items, and how deep\n * objects and arrays nest (the root object is depth 1). The envelope's `args`\n * are held to them too.\n */\nexport const inputLimits: Readonly<{\n maxTextLength: number\n maxDepth: number\n maxArrayLength: number\n}> = Object.freeze({ maxTextLength: 1000, maxDepth: 3, maxArrayLength: 50 })\n","import type { JsonValue } from '../json'\nimport {\n type ArrayInputSchema,\n inputLimits,\n type InputSchema,\n type InputSchemaNode,\n type NumberInputSchema,\n type StringInputSchema\n} from './schema'\n\n/**\n * Why a value failed: `type` (wrong type, or a fraction for an `integer`),\n * `required` (a required field is missing), `additional` (a field the schema\n * does not know), `limit` (beyond `inputLimits`), or the name of the keyword\n * that failed.\n */\nexport type InputErrorCode =\n | 'type'\n | 'required'\n | 'enum'\n | 'minimum'\n | 'maximum'\n | 'minLength'\n | 'maxLength'\n | 'pattern'\n | 'maxItems'\n | 'additional'\n | 'limit'\n\n/**\n * One error of an input value. `path` names the field (`ip`, `filter.ip`,\n * `tags[2]`; `''` for the root). It never carries the value, which may be a\n * secret.\n */\nexport interface InputError {\n path: string\n code: InputErrorCode\n}\n\n/** The checked copy of an input value, or every error found in it. */\nexport type InputResult =\n | { ok: true; value: Record<string, JsonValue> }\n | { ok: false; errors: InputError[] }\n\n/**\n * Checks `value` against `schema` (one that `checkInputSchema` accepted) and\n * the input limits. Returns a new value, never the input: an optional field\n * missing from an object takes its `default`, an absent optional array is\n * `[]`. Every error is returned, not the first only.\n */\nexport function validateInput(\n schema: InputSchema,\n value: unknown\n): InputResult {\n const errors: InputError[] = []\n const result = validateNode(schema, value, '', 1, errors)\n return errors.length === 0\n ? { ok: true, value: result as Record<string, JsonValue> }\n : { ok: false, errors }\n}\n\n/**\n * The copy of `value` checked against `node` at `depth` (the root object is\n * 1); errors are pushed to `errors`. On an error the copy is incomplete.\n */\nexport function validateNode(\n node: InputSchemaNode,\n value: unknown,\n path: string,\n depth: number,\n errors: InputError[]\n): JsonValue {\n switch (node.type) {\n case 'string':\n return validateString(node, value, path, errors)\n case 'number':\n case 'integer':\n return validateNumber(node, value, path, errors)\n case 'boolean':\n if (typeof value !== 'boolean') {\n errors.push({ path, code: 'type' })\n return null\n }\n return value\n case 'array':\n return validateArray(node, value, path, depth, errors)\n case 'object':\n return validateObject(node, value, path, depth, errors)\n }\n}\n\n/**\n * Sets an own, enumerable field, so that a name such as `__proto__` is a\n * field and never the prototype.\n */\nexport function define(\n target: Record<string, JsonValue>,\n name: string,\n value: JsonValue\n): void {\n Object.defineProperty(target, name, {\n value,\n enumerable: true,\n writable: true,\n configurable: true\n })\n}\n\n/** The number of code points of `text`. */\nexport function codePoints(text: string): number {\n let count = 0\n for (let i = 0; i < text.length; i++) {\n const unit = text.charCodeAt(i)\n // A high surrogate followed by a low one is one code point.\n if (unit >= 0xd800 && unit <= 0xdbff) {\n const next = text.charCodeAt(i + 1)\n if (next >= 0xdc00 && next <= 0xdfff) {\n i++\n }\n }\n count++\n }\n return count\n}\n\n/** Whether `value` is a plain object (prototype `Object.prototype` or null). */\nexport function isPlainObject(\n value: unknown\n): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false\n }\n const prototype: unknown = Object.getPrototypeOf(value)\n return prototype === Object.prototype || prototype === null\n}\n\nfunction validateString(\n node: StringInputSchema,\n value: unknown,\n path: string,\n errors: InputError[]\n): JsonValue {\n if (typeof value !== 'string') {\n errors.push({ path, code: 'type' })\n return null\n }\n const length = codePoints(value)\n if (length > inputLimits.maxTextLength) {\n errors.push({ path, code: 'limit' })\n return null\n }\n if (node.enum !== undefined && !node.enum.includes(value)) {\n errors.push({ path, code: 'enum' })\n }\n if (node.minLength !== undefined && length < node.minLength) {\n errors.push({ path, code: 'minLength' })\n }\n if (node.maxLength !== undefined && length > node.maxLength) {\n errors.push({ path, code: 'maxLength' })\n }\n if (\n node.pattern !== undefined &&\n !new RegExp(node.pattern, 'u').test(value)\n ) {\n errors.push({ path, code: 'pattern' })\n }\n return value\n}\n\nfunction validateNumber(\n node: NumberInputSchema,\n value: unknown,\n path: string,\n errors: InputError[]\n): JsonValue {\n if (\n typeof value !== 'number' ||\n !Number.isFinite(value) ||\n (node.type === 'integer' && !Number.isInteger(value))\n ) {\n errors.push({ path, code: 'type' })\n return null\n }\n if (node.enum !== undefined && !node.enum.includes(value)) {\n errors.push({ path, code: 'enum' })\n }\n if (node.minimum !== undefined && value < node.minimum) {\n errors.push({ path, code: 'minimum' })\n }\n if (node.maximum !== undefined && value > node.maximum) {\n errors.push({ path, code: 'maximum' })\n }\n return value\n}\n\nfunction validateArray(\n node: ArrayInputSchema,\n value: unknown,\n path: string,\n depth: number,\n errors: InputError[]\n): JsonValue {\n if (\n !Array.isArray(value) ||\n Object.getPrototypeOf(value) !== Array.prototype\n ) {\n errors.push({ path, code: 'type' })\n return null\n }\n const items = value as unknown[]\n if (\n depth > inputLimits.maxDepth ||\n items.length > inputLimits.maxArrayLength\n ) {\n errors.push({ path, code: 'limit' })\n return null\n }\n if (node.maxItems !== undefined && items.length > node.maxItems) {\n errors.push({ path, code: 'maxItems' })\n }\n const result: JsonValue[] = []\n for (let i = 0; i < items.length; i++) {\n // A hole reads as `undefined`, which no node accepts.\n result.push(\n validateNode(node.items, items[i], `${path}[${i}]`, depth + 1, errors)\n )\n }\n return result\n}\n\nfunction validateObject(\n node: InputSchema,\n value: unknown,\n path: string,\n depth: number,\n errors: InputError[]\n): JsonValue {\n if (!isPlainObject(value)) {\n errors.push({ path, code: 'type' })\n return null\n }\n if (depth > inputLimits.maxDepth) {\n errors.push({ path, code: 'limit' })\n return null\n }\n const properties = node.properties ?? {}\n const result: Record<string, JsonValue> = {}\n for (const name of Object.keys(properties)) {\n const child = properties[name]\n const at = path === '' ? name : `${path}.${name}`\n if (Object.hasOwn(value, name)) {\n define(\n result,\n name,\n validateNode(child, value[name], at, depth + 1, errors)\n )\n } else if (node.required?.includes(name) === true) {\n errors.push({ path: at, code: 'required' })\n } else if (child.type === 'array') {\n define(result, name, [])\n } else if (child.default !== undefined) {\n // Validated as a value, so an object default is a new copy with its\n // own defaults filled.\n define(\n result,\n name,\n validateNode(child, child.default, at, depth + 1, errors)\n )\n }\n }\n for (const name of Object.keys(value)) {\n if (!Object.hasOwn(properties, name)) {\n errors.push({\n path: path === '' ? name : `${path}.${name}`,\n code: 'additional'\n })\n }\n }\n return result\n}\n","import { inputLimits, type InputSchema, type InputSchemaNode } from './schema'\nimport { type InputError, isPlainObject, validateNode } from './validate'\n\ntype NodeType = InputSchemaNode['type']\n\n/** The keywords each type takes; every other keyword throws. */\nconst keywords: Record<NodeType, readonly string[]> = {\n string: [\n 'type',\n 'description',\n 'default',\n 'enum',\n 'minLength',\n 'maxLength',\n 'pattern'\n ],\n number: ['type', 'description', 'default', 'enum', 'minimum', 'maximum'],\n integer: ['type', 'description', 'default', 'enum', 'minimum', 'maximum'],\n boolean: ['type', 'description', 'default'],\n array: ['type', 'description', 'items', 'maxItems'],\n object: ['type', 'description', 'default', 'properties', 'required']\n}\n\nconst propertyName = /^[A-Za-z][A-Za-z0-9_-]*$/\n\n/**\n * Throws a `TypeError` unless `schema` is an input schema of the subset: a\n * root of `type: 'object'`; per type only its keywords; property names that\n * match `^[A-Za-z][A-Za-z0-9_-]*$`; a `pattern` that compiles with the `u`\n * flag; `required` names that are properties; containers no deeper than\n * `inputLimits.maxDepth`; a `default` the node accepts, and none on an array.\n * The message names the path of the node. Nothing is ignored. Called when a\n * command is registered.\n */\nexport function checkInputSchema(\n schema: unknown\n): asserts schema is InputSchema {\n if (!isPlainObject(schema) || schema.type !== 'object') {\n throw new TypeError(\n 'Input schema: the root must be an object schema ({ type: \"object\" })'\n )\n }\n checkNode(schema, '', 1)\n}\n\nfunction fail(path: string, problem: string): never {\n throw new TypeError(\n `Input schema at ${path === '' ? 'the root' : `\"${path}\"`}: ${problem}`\n )\n}\n\nfunction checkNode(node: unknown, path: string, depth: number): void {\n if (!isPlainObject(node)) {\n fail(path, 'a node must be a plain object')\n }\n const type = node.type\n if (typeof type !== 'string' || !Object.hasOwn(keywords, type)) {\n fail(path, `\"type\" must be one of ${Object.keys(keywords).join(', ')}`)\n }\n const nodeType = type as NodeType\n const allowed = keywords[nodeType]\n for (const key of Object.keys(node)) {\n if (!allowed.includes(key)) {\n const known = Object.values(keywords).some(list => list.includes(key))\n fail(\n path,\n known\n ? `the keyword \"${key}\" does not apply to type \"${nodeType}\"`\n : `the keyword \"${key}\" is not supported`\n )\n }\n }\n if (node.description !== undefined && typeof node.description !== 'string') {\n fail(path, '\"description\" must be a string')\n }\n switch (nodeType) {\n case 'string':\n checkString(node, path)\n break\n case 'number':\n case 'integer':\n checkNumber(node, path, nodeType === 'integer')\n break\n case 'boolean':\n break\n case 'array':\n checkArray(node, path, depth)\n break\n case 'object':\n checkObject(node, path, depth)\n break\n }\n if (node.default !== undefined) {\n const errors: InputError[] = []\n validateNode(\n node as unknown as InputSchemaNode,\n node.default,\n path,\n depth,\n errors\n )\n if (errors.length > 0) {\n fail(\n path,\n `the \"default\" does not match the node (${errors[0].code} at \"${errors[0].path}\")`\n )\n }\n }\n}\n\nfunction isCount(value: unknown): boolean {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0\n}\n\nfunction checkCount(\n node: Record<string, unknown>,\n key: string,\n path: string\n): void {\n if (node[key] !== undefined && !isCount(node[key])) {\n fail(path, `\"${key}\" must be a whole number of at least 0`)\n }\n}\n\nfunction checkEnum(\n node: Record<string, unknown>,\n path: string,\n accepts: (item: unknown) => boolean\n): void {\n if (node.enum === undefined) {\n return\n }\n if (\n !Array.isArray(node.enum) ||\n node.enum.length === 0 ||\n !node.enum.every(accepts)\n ) {\n fail(path, '\"enum\" must be a non-empty list of values of the node\\'s type')\n }\n}\n\nfunction checkString(node: Record<string, unknown>, path: string): void {\n checkCount(node, 'minLength', path)\n checkCount(node, 'maxLength', path)\n if (\n node.minLength !== undefined &&\n node.maxLength !== undefined &&\n (node.minLength as number) > (node.maxLength as number)\n ) {\n fail(path, '\"minLength\" is above \"maxLength\"')\n }\n checkEnum(node, path, item => typeof item === 'string')\n if (node.pattern !== undefined) {\n if (typeof node.pattern !== 'string') {\n fail(path, '\"pattern\" must be a string')\n }\n try {\n new RegExp(node.pattern, 'u')\n } catch {\n fail(path, '\"pattern\" is not a valid regular expression (u flag)')\n }\n }\n}\n\nfunction checkNumber(\n node: Record<string, unknown>,\n path: string,\n integer: boolean\n): void {\n for (const key of ['minimum', 'maximum']) {\n if (\n node[key] !== undefined &&\n !(typeof node[key] === 'number' && Number.isFinite(node[key]))\n ) {\n fail(path, `\"${key}\" must be a finite number`)\n }\n }\n if (\n node.minimum !== undefined &&\n node.maximum !== undefined &&\n (node.minimum as number) > (node.maximum as number)\n ) {\n fail(path, '\"minimum\" is above \"maximum\"')\n }\n checkEnum(\n node,\n path,\n item =>\n typeof item === 'number' &&\n Number.isFinite(item) &&\n (!integer || Number.isInteger(item))\n )\n}\n\nfunction checkDepth(path: string, depth: number): void {\n if (depth > inputLimits.maxDepth) {\n fail(path, `objects and arrays nest deeper than ${inputLimits.maxDepth}`)\n }\n}\n\nfunction checkArray(\n node: Record<string, unknown>,\n path: string,\n depth: number\n): void {\n checkDepth(path, depth)\n checkCount(node, 'maxItems', path)\n if (node.items === undefined) {\n fail(path, 'an array needs \"items\"')\n }\n checkNode(node.items, `${path}[]`, depth + 1)\n}\n\nfunction checkObject(\n node: Record<string, unknown>,\n path: string,\n depth: number\n): void {\n checkDepth(path, depth)\n const properties = node.properties ?? {}\n if (!isPlainObject(properties)) {\n fail(path, '\"properties\" must be an object')\n }\n for (const name of Object.keys(properties)) {\n const at = path === '' ? name : `${path}.${name}`\n if (!propertyName.test(name)) {\n fail(at, `the property name must match ${String(propertyName)}`)\n }\n checkNode(properties[name], at, depth + 1)\n }\n const required = node.required ?? []\n if (!Array.isArray(required)) {\n fail(path, '\"required\" must be a list of property names')\n }\n const seen = new Set<unknown>()\n for (const name of required as unknown[]) {\n if (\n typeof name !== 'string' ||\n !Object.hasOwn(properties, name) ||\n seen.has(name)\n ) {\n fail(\n path,\n `\"required\" names \"${String(name)}\", which is not a property or is listed twice`\n )\n }\n seen.add(name)\n }\n}\n","// The line syntax (ADR 0007): shell-like quoting, `\\` literal outside double\n// quotes, no continuation. The tokenizer never drops a character: every\n// non-whitespace character of the line is inside exactly one token's span.\n\n/** One token of a line. */\nexport interface Token {\n /** The token's text with grouping quotes and escapes removed. */\n value: string\n /** UTF-16 offset of the token's first raw character in the line. */\n start: number\n /** UTF-16 offset after the token's last raw character (exclusive). */\n end: number\n /** True when any part of the token is quoted (`'…'` or `\"…\"`). */\n quoted: boolean\n /**\n * True when the token ends in a quote that is not closed; it then runs to\n * the end of the line. Only the last token can be open.\n */\n open: boolean\n}\n\n/** The error of a line whose quote is not closed. */\nexport interface TokenizeError {\n code: 'unterminated-quote'\n /** UTF-16 offset of the opening quote that is not closed. */\n index: number\n}\n\n/**\n * The tokens of a line. With `ok: false` the tokens are still returned\n * (tolerant): the last one has `open: true`. Dispatch refuses such a line;\n * completion works on its tokens.\n */\nexport type TokenizeResult =\n | { ok: true; tokens: Token[] }\n | { ok: false; tokens: Token[]; error: TokenizeError }\n\nconst whitespace = /\\s/\n\n/** Whether `char` separates tokens (`\\s`, so tabs and line breaks too). */\nexport function isSeparator(char: string): boolean {\n return whitespace.test(char)\n}\n\n/**\n * Splits a line into tokens. Runs of whitespace separate. `'…'` groups\n * literally. `\"…\"` groups, and inside it `\\\"` and `\\\\` are the only escapes;\n * any other `\\` is kept. Outside quotes `\\` is an ordinary character. Adjacent\n * parts join one token (`a\"b c\"d` is `ab cd`); `''` and `\"\"` give an empty\n * token. `start`/`end` cover the raw text of each token, quotes included.\n */\nexport function tokenize(line: string): TokenizeResult {\n const tokens: Token[] = []\n let error: TokenizeError | null = null\n let i = 0\n const length = line.length\n\n while (i < length) {\n if (isSeparator(line[i])) {\n i++\n continue\n }\n const start = i\n let value = ''\n let quoted = false\n let open = false\n\n while (i < length && !isSeparator(line[i])) {\n const char = line[i]\n if (char === \"'\") {\n quoted = true\n const close = line.indexOf(\"'\", i + 1)\n if (close === -1) {\n value += line.slice(i + 1)\n open = true\n error = { code: 'unterminated-quote', index: i }\n i = length\n break\n }\n value += line.slice(i + 1, close)\n i = close + 1\n } else if (char === '\"') {\n quoted = true\n const opening = i\n i++\n let closed = false\n while (i < length) {\n const inner = line[i]\n if (inner === '\"') {\n closed = true\n i++\n break\n }\n const next = line.charAt(i + 1)\n if (inner === '\\\\' && (next === '\"' || next === '\\\\')) {\n value += next\n i += 2\n } else {\n value += inner\n i++\n }\n }\n if (!closed) {\n open = true\n error = { code: 'unterminated-quote', index: opening }\n break\n }\n } else {\n value += char\n i++\n }\n }\n\n tokens.push({ value, start, end: i, quoted, open })\n }\n\n return error === null ? { ok: true, tokens } : { ok: false, tokens, error }\n}\n","import { isSeparator } from './tokenize'\n\n/**\n * The shortest form of `value` that `tokenize` reads back as one token with\n * that value: bare when it is non-empty and has no whitespace and no quote,\n * else `'…'` when it has no `'`, else `\"…\"` with `\"` written as `\\\"` and a\n * `\\` written as `\\\\` where it comes before a `\"`, a `\\` or the closing quote.\n */\nexport function quoteArgument(value: string): string {\n if (value === '' || needsQuotes(value)) {\n return value.includes(\"'\") ? doubleQuote(value) : `'${value}'`\n }\n return value\n}\n\nfunction needsQuotes(value: string): boolean {\n for (const char of value) {\n if (char === \"'\" || char === '\"' || isSeparator(char)) {\n return true\n }\n }\n return false\n}\n\nfunction doubleQuote(value: string): string {\n let result = '\"'\n for (let i = 0; i < value.length; i++) {\n const char = value[i]\n if (char === '\"') {\n result += '\\\\\"'\n } else if (char === '\\\\') {\n const next = value.charAt(i + 1)\n result += next === '' || next === '\"' || next === '\\\\' ? '\\\\\\\\' : '\\\\'\n } else {\n result += char\n }\n }\n return `${result}\"`\n}\n","// The line form of a command with an input schema (C-73, C-74):\n// `dataview.search --ip 10.0.0.5 --limit 50 --verbose --tags a --tags b`.\n\nimport type { InputSchema, InputSchemaNode } from '../input/schema'\nimport {\n define,\n type InputError,\n type InputResult,\n validateInput\n} from '../input/validate'\nimport type { JsonValue } from '../json'\nimport { quoteArgument } from './quote'\n\n// A number as JSON writes it (and `String(number)` gives it back).\nconst jsonNumber = /^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$/\n\n/**\n * Reads the tokens after the command name (`tokenize` values) into the\n * object `schema` describes. Each field is a `--name value` pair; the value\n * converts to the field's type: a number as JSON writes it, an object (or an\n * array item that is an object or an array) as JSON text, a string as is,\n * even when it starts with `--`. A boolean field alone is `true` and also\n * takes a following `true` or `false`. An array field repeats its name; an\n * absent one is `[]`. An unknown name, or a token that is not a name, is\n * `additional`; a name without its value, or a second value of a field that\n * is not an array, is `type`. The result is then checked by `validateInput`,\n * and its errors follow the line's.\n */\nexport function parseOptions(\n args: readonly string[],\n schema: InputSchema\n): InputResult {\n const properties = schema.properties ?? {}\n const errors: InputError[] = []\n const value: Record<string, JsonValue> = {}\n let i = 0\n while (i < args.length) {\n const token = args[i++]\n if (!token.startsWith('--') || token === '--') {\n errors.push({ path: '', code: 'additional' })\n continue\n }\n const name = token.slice(2)\n if (!Object.hasOwn(properties, name)) {\n errors.push({ path: name, code: 'additional' })\n // The unknown name's value goes with it.\n if (i < args.length && !args[i].startsWith('--')) {\n i++\n }\n continue\n }\n const node = properties[name]\n let field: JsonValue\n if (node.type === 'boolean') {\n const next = args[i]\n field = next === 'false' ? false : true\n if (next === 'true' || next === 'false') {\n i++\n }\n } else if (i < args.length) {\n field = convert(node.type === 'array' ? node.items : node, args[i++])\n } else {\n errors.push({ path: name, code: 'type' })\n continue\n }\n if (node.type === 'array') {\n const list = Object.hasOwn(value, name)\n ? (value[name] as JsonValue[])\n : []\n list.push(field)\n define(value, name, list)\n } else if (Object.hasOwn(value, name)) {\n errors.push({ path: name, code: 'type' })\n } else {\n define(value, name, field)\n }\n }\n for (const name of Object.keys(properties)) {\n if (properties[name].type === 'array' && !Object.hasOwn(value, name)) {\n define(value, name, [])\n }\n }\n const result = validateInput(schema, value)\n if (errors.length === 0) {\n return result\n }\n return {\n ok: false,\n errors: result.ok ? errors : [...errors, ...result.errors]\n }\n}\n\n/**\n * The value of one token for `node`. A token that does not convert stays a\n * string, which `validateInput` then reports as a `type` error.\n */\nfunction convert(node: InputSchemaNode, token: string): JsonValue {\n switch (node.type) {\n case 'number':\n case 'integer':\n return jsonNumber.test(token) ? Number(token) : token\n case 'boolean':\n return token === 'true' ? true : token === 'false' ? false : token\n case 'array':\n case 'object':\n try {\n return JSON.parse(token) as JsonValue\n } catch {\n return token\n }\n case 'string':\n return token\n }\n}\n\n/**\n * The token of one value: a string as is, anything else as JSON (for a\n * number or a boolean that is `String(value)`).\n */\nfunction written(value: JsonValue): string {\n return typeof value === 'string' ? value : JSON.stringify(value)\n}\n\n/**\n * The canonical line of a command call: the command name, then each field\n * of the validated value (defaults filled) in the order of the schema's\n * `properties`, every token through `quoteArgument`. `true` is the bare\n * `--name`, `false` is `--name false`, an array repeats its name per item.\n * `parseOptions` reads its tokens after the first back to the validated\n * value. Throws a `TypeError` when `validateInput` rejects `value`.\n */\nexport function formatCommandLine(\n command: string,\n value: Record<string, JsonValue>,\n schema: InputSchema\n): string {\n const checked = validateInput(schema, value)\n if (!checked.ok) {\n throw new TypeError(\n `formatCommandLine: the value does not match the schema (${checked.errors\n .map(e => `${e.code} at \"${e.path}\"`)\n .join(', ')})`\n )\n }\n const properties = schema.properties ?? {}\n const parts = [quoteArgument(command)]\n for (const name of Object.keys(properties)) {\n if (!Object.hasOwn(checked.value, name)) {\n continue\n }\n const node = properties[name]\n const field = checked.value[name]\n const option = quoteArgument(`--${name}`)\n if (node.type === 'boolean') {\n parts.push(field === true ? option : `${option} false`)\n } else if (node.type === 'array') {\n for (const item of field as JsonValue[]) {\n parts.push(option, quoteArgument(written(item)))\n }\n } else {\n parts.push(option, quoteArgument(written(field)))\n }\n }\n return parts.join(' ')\n}\n","// The envelope a command travels in between the tabs of one user (ADR 0012).\n// It is JSON and knows no transport: the consumer's gate sends and receives\n// it.\n\nimport type { Actor } from './entry'\nimport { inputLimits } from './input/schema'\nimport { codePoints, isPlainObject } from './input/validate'\nimport { isJsonValue, type JsonValue } from './json'\n\n/**\n * A request to run `command` with `args` in the tab (and view) of `target`.\n * Version 1 has only `kind: 'request'`.\n */\nexport interface Envelope {\n /** The envelope's version. A receiver does not run a version it does not know. */\n v: 1\n /** The operation's id (a UUID), not the run's id. */\n id: string\n kind: 'request'\n /** Who sent it, as the sender declares; a label for the history, never a permission. */\n actor: Actor\n /** The sending tab. */\n from: { tab: string }\n /** The tab and the view instance that run it; no `view` means the tab. */\n target: { tab: string; view?: string }\n /** The command's canonical name. */\n command: string\n /** The validated, normalized input object. */\n args: Record<string, JsonValue>\n}\n\n/** The most bytes an envelope's UTF-8 JSON may take. */\nexport const envelopeMaxBytes = 16384\n\n/** Why `parseEnvelope` refused a value. */\nexport type EnvelopeErrorCode = 'unsupported-version' | 'invalid' | 'too-large'\n\n/** An envelope read from the transport, or why it was refused. */\nexport type EnvelopeResult =\n { ok: true; envelope: Envelope } | { ok: false; code: EnvelopeErrorCode }\n\nconst actors: readonly unknown[] = ['user', 'assistant', 'external-agent']\n\n/** The length in bytes of the UTF-8 JSON of `envelope`. */\nexport function envelopeBytes(envelope: Envelope): number {\n return jsonBytes(envelope)\n}\n\nfunction jsonBytes(value: unknown): number {\n return new TextEncoder().encode(JSON.stringify(value)).length\n}\n\nfunction isName(value: unknown): value is string {\n return typeof value === 'string' && value !== ''\n}\n\n/**\n * Reads a value that came from the transport. A plain object whose `v` is a\n * number other than 1 is `unsupported-version`; one whose JSON is larger\n * than `envelopeMaxBytes` (unknown fields included) is `too-large`; a missing\n * or mistyped field, another `kind`, or `args` that is not a JSON object\n * within `inputLimits` is `invalid`. A `null` `target.view` is no view (a\n * typed transport may write one back). Unknown fields at the top and inside\n * `from` and `target` are left out of the new envelope it returns; `raw` is\n * never changed.\n */\nexport function parseEnvelope(raw: unknown): EnvelopeResult {\n if (!isPlainObject(raw) || typeof raw.v !== 'number') {\n return { ok: false, code: 'invalid' }\n }\n if (raw.v !== 1) {\n return { ok: false, code: 'unsupported-version' }\n }\n let bytes: number\n try {\n bytes = jsonBytes(raw)\n } catch {\n // A cycle or a bigint.\n return { ok: false, code: 'invalid' }\n }\n if (bytes > envelopeMaxBytes) {\n return { ok: false, code: 'too-large' }\n }\n const { id, kind, actor, from, target, command, args } = raw\n if (\n !isName(id) ||\n kind !== 'request' ||\n !actors.includes(actor) ||\n !isPlainObject(from) ||\n !isName(from.tab) ||\n !isPlainObject(target) ||\n !isName(target.tab) ||\n (target.view !== undefined &&\n target.view !== null &&\n !isName(target.view)) ||\n !isName(command) ||\n !isPlainObject(args) ||\n !isJsonValue(args) ||\n !withinLimits(args, 1)\n ) {\n return { ok: false, code: 'invalid' }\n }\n return {\n ok: true,\n envelope: {\n v: 1,\n id,\n kind,\n actor: actor as Actor,\n from: { tab: from.tab },\n target:\n target.view === undefined || target.view === null\n ? { tab: target.tab }\n : { tab: target.tab, view: target.view },\n command,\n args: JSON.parse(JSON.stringify(args)) as Record<string, JsonValue>\n }\n }\n}\n\n/** Whether a JSON value at `depth` keeps to `inputLimits` (C-72). */\nfunction withinLimits(value: JsonValue, depth: number): boolean {\n if (typeof value === 'string') {\n return codePoints(value) <= inputLimits.maxTextLength\n }\n if (typeof value !== 'object' || value === null) {\n return true\n }\n if (depth > inputLimits.maxDepth) {\n return false\n }\n if (Array.isArray(value)) {\n return (\n value.length <= inputLimits.maxArrayLength &&\n value.every(item => withinLimits(item, depth + 1))\n )\n }\n return Object.values(value).every(item => withinLimits(item, depth + 1))\n}\n"],"mappings":"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"}
@@ -1,4 +1,6 @@
1
+ import type { InputErrorCode } from './input/validate.js';
1
2
  import type { JsonPrimitive, JsonValue } from './json.js';
3
+ import type { ErrorCode } from './outcome.js';
2
4
  /** Plain text, rendered preformatted. */
3
5
  export interface TextBlock {
4
6
  type: 'text';
@@ -50,8 +52,12 @@ export type OutputBlock = TextBlock | JsonBlock | TableBlock | ListBlock | Messa
50
52
  * shown (S-06).
51
53
  * - `unterminated-quote`: the line has a quote that is not closed.
52
54
  * - `interrupted`: a running command was cancelled.
55
+ * - `invalid-args`: the options of a command with an input schema do not
56
+ * match it; the first error's field (`path`) and why (`reason`).
57
+ * - `operation-failed`: a command's operation ended without running it
58
+ * (`not-allowed`, `too-large`, `timeout`, `connection-lost`, `id-conflict`).
53
59
  */
54
- export type MessageCode = 'command-not-found' | 'command-failed' | 'unterminated-quote' | 'interrupted';
60
+ export type MessageCode = 'command-not-found' | 'command-failed' | 'unterminated-quote' | 'interrupted' | 'invalid-args' | 'operation-failed';
55
61
  /**
56
62
  * The parameters of each message code. For a sensitive line `name` and
57
63
  * `index` are `null`. A code without parameters has an empty record; its
@@ -66,4 +72,13 @@ export interface MessageParams {
66
72
  index: number | null;
67
73
  };
68
74
  interrupted: Record<string, never>;
75
+ /** `path` is `null` when the line is sensitive; `''` names the root. */
76
+ 'invalid-args': {
77
+ path: string | null;
78
+ reason: InputErrorCode;
79
+ };
80
+ /** The code the operation ended with. */
81
+ 'operation-failed': {
82
+ code: ErrorCode;
83
+ };
69
84
  }
@@ -1,6 +1,7 @@
1
1
  import type { OutputBlock } from './block.js';
2
2
  import type { ContextFrame } from './context.js';
3
3
  import type { OutputEntry } from './entry.js';
4
+ import type { InputSchema } from './input/schema.js';
4
5
  /**
5
6
  * The JSON part of a command: its names and how it is listed and redacted.
6
7
  * The core adds the functions (`run`, `complete`) to it.
@@ -23,7 +24,19 @@ export interface CommandSpec {
23
24
  sensitive?: boolean | {
24
25
  fromArg: number;
25
26
  };
27
+ /**
28
+ * The schema of the command's input (`checkInputSchema`). A command with a
29
+ * schema is called as `name --field value …` (`parseOptions`).
30
+ */
31
+ input?: InputSchema;
32
+ /** What running the command does to the application. */
33
+ danger?: Danger;
26
34
  }
35
+ /**
36
+ * What running a command does: `read` only reads, `change` changes the
37
+ * application or sends data out, `irreversible` cannot be undone.
38
+ */
39
+ export type Danger = 'read' | 'change' | 'irreversible';
27
40
  /** What a command returns, as JSON. */
28
41
  export interface CommandResponse {
29
42
  /** A string is one text block. */
@@ -34,6 +47,11 @@ export interface CommandResponse {
34
47
  quiet?: true;
35
48
  /** Applied in order, before the output is appended. */
36
49
  effects?: ConsoleEffect[];
50
+ /**
51
+ * A short result for the caller of an operation (`CommandOutcome`), such
52
+ * as "342 rows"; at most 200 characters.
53
+ */
54
+ summary?: string;
37
55
  }
38
56
  /**
39
57
  * A change a command asks the console to make:
@@ -13,7 +13,15 @@ export interface InputEntry {
13
13
  redacted: boolean;
14
14
  /** `cancelled` when the line was ended with Ctrl+C. */
15
15
  status: 'submitted' | 'cancelled';
16
+ /** Who the line came from, as a label for the history. */
17
+ actor?: Actor;
16
18
  }
19
+ /**
20
+ * Who a line or a request came from: the user, the application's own
21
+ * assistant, or an agent outside the application (such as a browser agent).
22
+ * A label, never a permission.
23
+ */
24
+ export type Actor = 'user' | 'assistant' | 'external-agent';
17
25
  /** What a command or the library wrote. */
18
26
  export interface OutputEntry {
19
27
  id: string;
@@ -0,0 +1,53 @@
1
+ import type { Actor } from './entry.js';
2
+ import { type JsonValue } from './json.js';
3
+ /**
4
+ * A request to run `command` with `args` in the tab (and view) of `target`.
5
+ * Version 1 has only `kind: 'request'`.
6
+ */
7
+ export interface Envelope {
8
+ /** The envelope's version. A receiver does not run a version it does not know. */
9
+ v: 1;
10
+ /** The operation's id (a UUID), not the run's id. */
11
+ id: string;
12
+ kind: 'request';
13
+ /** Who sent it, as the sender declares; a label for the history, never a permission. */
14
+ actor: Actor;
15
+ /** The sending tab. */
16
+ from: {
17
+ tab: string;
18
+ };
19
+ /** The tab and the view instance that run it; no `view` means the tab. */
20
+ target: {
21
+ tab: string;
22
+ view?: string;
23
+ };
24
+ /** The command's canonical name. */
25
+ command: string;
26
+ /** The validated, normalized input object. */
27
+ args: Record<string, JsonValue>;
28
+ }
29
+ /** The most bytes an envelope's UTF-8 JSON may take. */
30
+ export declare const envelopeMaxBytes = 16384;
31
+ /** Why `parseEnvelope` refused a value. */
32
+ export type EnvelopeErrorCode = 'unsupported-version' | 'invalid' | 'too-large';
33
+ /** An envelope read from the transport, or why it was refused. */
34
+ export type EnvelopeResult = {
35
+ ok: true;
36
+ envelope: Envelope;
37
+ } | {
38
+ ok: false;
39
+ code: EnvelopeErrorCode;
40
+ };
41
+ /** The length in bytes of the UTF-8 JSON of `envelope`. */
42
+ export declare function envelopeBytes(envelope: Envelope): number;
43
+ /**
44
+ * Reads a value that came from the transport. A plain object whose `v` is a
45
+ * number other than 1 is `unsupported-version`; one whose JSON is larger
46
+ * than `envelopeMaxBytes` (unknown fields included) is `too-large`; a missing
47
+ * or mistyped field, another `kind`, or `args` that is not a JSON object
48
+ * within `inputLimits` is `invalid`. A `null` `target.view` is no view (a
49
+ * typed transport may write one back). Unknown fields at the top and inside
50
+ * `from` and `target` are left out of the new envelope it returns; `raw` is
51
+ * never changed.
52
+ */
53
+ export declare function parseEnvelope(raw: unknown): EnvelopeResult;
@@ -1,9 +1,18 @@
1
1
  export type { JsonPrimitive, JsonValue } from './json.js';
2
2
  export { isJsonValue } from './json.js';
3
- export type { ConsoleEntry, InputEntry, OutputEntry } from './entry.js';
3
+ export type { Actor, ConsoleEntry, InputEntry, OutputEntry } from './entry.js';
4
4
  export type { CustomBlock, JsonBlock, ListBlock, MessageBlock, MessageCode, MessageParams, OutputBlock, TableBlock, TableColumn, TextBlock } from './block.js';
5
- export type { CommandResponse, CommandSpec, ConsoleEffect } from './command.js';
5
+ export type { CommandResponse, CommandSpec, ConsoleEffect, Danger } from './command.js';
6
6
  export type { ContextFrame } from './context.js';
7
+ export type { CommandOutcome, ErrorCode } from './outcome.js';
8
+ export type { ArrayInputSchema, BooleanInputSchema, InputSchema, InputSchemaNode, NumberInputSchema, StringInputSchema } from './input/schema.js';
9
+ export { inputLimits } from './input/schema.js';
10
+ export { checkInputSchema } from './input/check.js';
11
+ export type { InputError, InputErrorCode, InputResult } from './input/validate.js';
12
+ export { validateInput } from './input/validate.js';
7
13
  export type { Token, TokenizeError, TokenizeResult } from './syntax/tokenize.js';
8
14
  export { tokenize } from './syntax/tokenize.js';
9
15
  export { quoteArgument } from './syntax/quote.js';
16
+ export { formatCommandLine, parseOptions } from './syntax/options.js';
17
+ export type { Envelope, EnvelopeErrorCode, EnvelopeResult } from './envelope.js';
18
+ export { envelopeBytes, envelopeMaxBytes, parseEnvelope } from './envelope.js';
@@ -0,0 +1,11 @@
1
+ import { type InputSchema } from './schema.js';
2
+ /**
3
+ * Throws a `TypeError` unless `schema` is an input schema of the subset: a
4
+ * root of `type: 'object'`; per type only its keywords; property names that
5
+ * match `^[A-Za-z][A-Za-z0-9_-]*$`; a `pattern` that compiles with the `u`
6
+ * flag; `required` names that are properties; containers no deeper than
7
+ * `inputLimits.maxDepth`; a `default` the node accepts, and none on an array.
8
+ * The message names the path of the node. Nothing is ignored. Called when a
9
+ * command is registered.
10
+ */
11
+ export declare function checkInputSchema(schema: unknown): asserts schema is InputSchema;
@@ -0,0 +1,70 @@
1
+ import type { JsonValue } from '../json.js';
2
+ /** A string field. Lengths count code points. */
3
+ export interface StringInputSchema {
4
+ type: 'string';
5
+ description?: string;
6
+ /** The only values allowed. */
7
+ enum?: string[];
8
+ minLength?: number;
9
+ maxLength?: number;
10
+ /** A regular expression (compiled with the `u` flag), not anchored. */
11
+ pattern?: string;
12
+ default?: string;
13
+ }
14
+ /** A number field; `integer` wants a whole number. */
15
+ export interface NumberInputSchema {
16
+ type: 'number' | 'integer';
17
+ description?: string;
18
+ /** The only values allowed. */
19
+ enum?: number[];
20
+ minimum?: number;
21
+ maximum?: number;
22
+ default?: number;
23
+ }
24
+ /** A boolean field. */
25
+ export interface BooleanInputSchema {
26
+ type: 'boolean';
27
+ description?: string;
28
+ default?: boolean;
29
+ }
30
+ /**
31
+ * An array field. It takes no `default`: an absent optional array is `[]`,
32
+ * so a line, which cannot write an empty array, reads back the same.
33
+ */
34
+ export interface ArrayInputSchema {
35
+ type: 'array';
36
+ description?: string;
37
+ /** The schema of every item. */
38
+ items: InputSchemaNode;
39
+ maxItems?: number;
40
+ }
41
+ /**
42
+ * An object: the root of a command's input, or a field that holds one. A
43
+ * field that is not in `properties` is refused.
44
+ */
45
+ export interface InputSchema {
46
+ type: 'object';
47
+ description?: string;
48
+ /** The fields, in the order the canonical line writes them. */
49
+ properties?: {
50
+ [name: string]: InputSchemaNode;
51
+ };
52
+ /** The fields that must be given; each is in `properties`. */
53
+ required?: string[];
54
+ default?: {
55
+ [name: string]: JsonValue;
56
+ };
57
+ }
58
+ /** One node of an input schema. */
59
+ export type InputSchemaNode = StringInputSchema | NumberInputSchema | BooleanInputSchema | ArrayInputSchema | InputSchema;
60
+ /**
61
+ * The limits every input value is held to, whatever its schema allows
62
+ * (`limit` errors): a string's code points, an array's items, and how deep
63
+ * objects and arrays nest (the root object is depth 1). The envelope's `args`
64
+ * are held to them too.
65
+ */
66
+ export declare const inputLimits: Readonly<{
67
+ maxTextLength: number;
68
+ maxDepth: number;
69
+ maxArrayLength: number;
70
+ }>;
@@ -0,0 +1,47 @@
1
+ import type { JsonValue } from '../json.js';
2
+ import { type InputSchema, type InputSchemaNode } from './schema.js';
3
+ /**
4
+ * Why a value failed: `type` (wrong type, or a fraction for an `integer`),
5
+ * `required` (a required field is missing), `additional` (a field the schema
6
+ * does not know), `limit` (beyond `inputLimits`), or the name of the keyword
7
+ * that failed.
8
+ */
9
+ export type InputErrorCode = 'type' | 'required' | 'enum' | 'minimum' | 'maximum' | 'minLength' | 'maxLength' | 'pattern' | 'maxItems' | 'additional' | 'limit';
10
+ /**
11
+ * One error of an input value. `path` names the field (`ip`, `filter.ip`,
12
+ * `tags[2]`; `''` for the root). It never carries the value, which may be a
13
+ * secret.
14
+ */
15
+ export interface InputError {
16
+ path: string;
17
+ code: InputErrorCode;
18
+ }
19
+ /** The checked copy of an input value, or every error found in it. */
20
+ export type InputResult = {
21
+ ok: true;
22
+ value: Record<string, JsonValue>;
23
+ } | {
24
+ ok: false;
25
+ errors: InputError[];
26
+ };
27
+ /**
28
+ * Checks `value` against `schema` (one that `checkInputSchema` accepted) and
29
+ * the input limits. Returns a new value, never the input: an optional field
30
+ * missing from an object takes its `default`, an absent optional array is
31
+ * `[]`. Every error is returned, not the first only.
32
+ */
33
+ export declare function validateInput(schema: InputSchema, value: unknown): InputResult;
34
+ /**
35
+ * The copy of `value` checked against `node` at `depth` (the root object is
36
+ * 1); errors are pushed to `errors`. On an error the copy is incomplete.
37
+ */
38
+ export declare function validateNode(node: InputSchemaNode, value: unknown, path: string, depth: number, errors: InputError[]): JsonValue;
39
+ /**
40
+ * Sets an own, enumerable field, so that a name such as `__proto__` is a
41
+ * field and never the prototype.
42
+ */
43
+ export declare function define(target: Record<string, JsonValue>, name: string, value: JsonValue): void;
44
+ /** The number of code points of `text`. */
45
+ export declare function codePoints(text: string): number;
46
+ /** Whether `value` is a plain object (prototype `Object.prototype` or null). */
47
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Why an operation ended without running its command, or why it failed:
3
+ *
4
+ * - `invalid-args`: the input does not match the command's schema.
5
+ * - `unknown-command`: no command has that name.
6
+ * - `not-allowed`: the entry it came from may not run the command.
7
+ * - `target-not-found`: the target tab or view does not exist.
8
+ * - `id-conflict`: the echo of the operation differs from its local record.
9
+ * - `unsupported-version`: an envelope version the receiver does not know.
10
+ * - `too-large`: the envelope is larger than `envelopeMaxBytes`.
11
+ * - `busy`: the console is already running a command.
12
+ * - `timeout`: the echo did not come in time.
13
+ * - `failed`: the command threw or rejected.
14
+ * - `connection-lost`: the transport failed.
15
+ * - `cancelled`: the run was cancelled.
16
+ */
17
+ export type ErrorCode = 'invalid-args' | 'unknown-command' | 'not-allowed' | 'target-not-found' | 'id-conflict' | 'unsupported-version' | 'too-large' | 'busy' | 'timeout' | 'failed' | 'connection-lost' | 'cancelled';
18
+ /**
19
+ * How an operation ended, as its caller sees it. `summary` is the command's
20
+ * short result (`CommandResponse.summary`).
21
+ */
22
+ export type CommandOutcome = {
23
+ status: 'ok';
24
+ summary?: string;
25
+ } | {
26
+ status: 'error';
27
+ code: ErrorCode;
28
+ message?: string;
29
+ };
@@ -0,0 +1,25 @@
1
+ import type { InputSchema } from '../input/schema.js';
2
+ import { type InputResult } from '../input/validate.js';
3
+ import type { JsonValue } from '../json.js';
4
+ /**
5
+ * Reads the tokens after the command name (`tokenize` values) into the
6
+ * object `schema` describes. Each field is a `--name value` pair; the value
7
+ * converts to the field's type: a number as JSON writes it, an object (or an
8
+ * array item that is an object or an array) as JSON text, a string as is,
9
+ * even when it starts with `--`. A boolean field alone is `true` and also
10
+ * takes a following `true` or `false`. An array field repeats its name; an
11
+ * absent one is `[]`. An unknown name, or a token that is not a name, is
12
+ * `additional`; a name without its value, or a second value of a field that
13
+ * is not an array, is `type`. The result is then checked by `validateInput`,
14
+ * and its errors follow the line's.
15
+ */
16
+ export declare function parseOptions(args: readonly string[], schema: InputSchema): InputResult;
17
+ /**
18
+ * The canonical line of a command call: the command name, then each field
19
+ * of the validated value (defaults filled) in the order of the schema's
20
+ * `properties`, every token through `quoteArgument`. `true` is the bare
21
+ * `--name`, `false` is `--name false`, an array repeats its name per item.
22
+ * `parseOptions` reads its tokens after the first back to the validated
23
+ * value. Throws a `TypeError` when `validateInput` rejects `value`.
24
+ */
25
+ export declare function formatCommandLine(command: string, value: Record<string, JsonValue>, schema: InputSchema): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dolusoft/command-protocol",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "The JSON of a command console: entries, output blocks, command specs, responses, effects, context frames, and the line tokenizer. No dependencies.",
5
5
  "private": false,
6
6
  "type": "module",