@dolusoft/command-console-core 1.0.0 → 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
@@ -71,6 +71,10 @@ no optional method, in its type and at run time. The feature set is fixed for th
71
71
  | `isSensitive(line)` | marks more lines sensitive, OR-ed with the commands' `sensitive`; a throw counts as sensitive |
72
72
  | `recallLimit` | recall: the most lines kept; default 100 (`Infinity` keeps all, a fraction is rounded down) |
73
73
  | `shouldRecord(line)` | recall: whether a submitted line is recorded; default every line |
74
+ | `gate` | sends described commands as envelopes and runs them from their echo (below); fixed for the console's life |
75
+ | `activeView()` | the view a typed line (or a `{ line }` request) targets, read when the line ends |
76
+ | `echoTimeoutMs` | how long an envelope waits for its echo, a finite number above 0 (else a `TypeError`); default 5000 |
77
+ | `createEnvelopeId()` | makes envelope ids; default `crypto.randomUUID()`, else a v4 UUID from `crypto.getRandomValues` (below) |
74
78
 
75
79
  Run ids come from `createId` too, so with the default counter the first command's output is `c3` (`c1` the input entry,
76
80
  `c2` the run). An id `createId` returns that is already present throws from the method that caused it, as a duplicate
@@ -115,6 +119,7 @@ Every console has:
115
119
  empty line, a parse error, an unknown command), `false` without doing anything while a run exists.
116
120
  - `run(line)`: replaces the live input with `line` and submits it; the same result as `submit()`.
117
121
  - `cancel()`: interrupts the running command, else ends the live line as cancelled.
122
+ - `request(input)`: runs a command for an agent (below); resolves a `CommandOutcome`, never rejects.
118
123
  - `handleKey(input: KeyInput)`: applies the fixed key map; `true` when the key was handled, so the adapter prevents the
119
124
  default.
120
125
  - `appendEntries(entries)` (an id already present throws), `clearEntries()`.
@@ -193,6 +198,96 @@ the sequence of frame ids differs; a new array with the same ids is not one.
193
198
  - `dispose()`: ends the running command the same way, runs every feature's cleanup once (an exception in one does not
194
199
  stop the others), clears the internal state, and turns every later method into a no-op. Two consoles share nothing.
195
200
 
201
+ ## Commands with an input schema
202
+
203
+ A command with `input` (an `InputSchema`, checked with `checkInputSchema` when the command is registered) is typed as
204
+ `name --field value …` (`parseOptions`). A command with `danger: 'irreversible'`, with `input` or without, cannot be
205
+ run from the line (`operation-failed` with `not-allowed`, before its options are read). Options that do not match
206
+ append an `error` entry `invalid-args` with the first error's `path` and `reason`, and nothing runs. Otherwise the
207
+ run starts: `normalize(input)` (optional, pure) maps the value, which is checked again, and the command's
208
+ `CommandRequest` carries it as `input`, the target view as `view` (none for an empty or `null` view), and `args`,
209
+ `rest` and `line` of the canonical line (`formatCommandLine`). `getCommands()` copies `input` and `danger`.
210
+
211
+ ## The gate
212
+
213
+ ```ts
214
+ interface ConsoleGate {
215
+ tab: string
216
+ send(envelope: Envelope): Promise<void>
217
+ subscribe(listener: (raw: unknown) => void): () => void
218
+ schedule(ms: number, fn: () => void): () => void
219
+ }
220
+ ```
221
+
222
+ The consumer's transport implements the gate; the core never touches the network, and `schedule` is its only clock
223
+ (ADR 0012). With a gate, a line of a command that has `input` and `danger` and is not sensitive does not run at once:
224
+
225
+ 1. `beforeRun` is asked as before; the run stays `running` and the console locked.
226
+ 2. An envelope (`v: 1`, `kind: 'request'`, the actor, `from.tab` and `target.tab` the gate's tab, `target.view`,
227
+ the canonical name, the normalized input) is sent as a copy: the transport cannot change the local record. Above
228
+ `envelopeMaxBytes` it is not sent (`too-large`).
229
+ 3. Its echo, delivered through `subscribe`, runs the command with the local record when `command`, `args` and `target`
230
+ match; a different echo ends the run with `id-conflict` and `onError`. Other traffic is ignored.
231
+ 4. A `send` that rejects, or a `subscribe`, `schedule` or `send` that throws, ends the run with `connection-lost`;
232
+ no echo within `echoTimeoutMs` with `timeout`. `cancel()`, `reset()` and a context change end it as cancelled.
233
+
234
+ These ends append an `operation-failed` entry with the code. The last 500 closed ids are remembered, so a late echo
235
+ runs nothing. The console subscribes on its first envelope, and `dispose()` unsubscribes and cancels the timer. A
236
+ command without `input` or `danger`, or a sensitive one, runs locally as without a gate.
237
+
238
+ Envelope ids come from `createEnvelopeId`, else `crypto.randomUUID()`, else a version 4 UUID made from
239
+ `crypto.getRandomValues` (a page served over plain `http` on a LAN address has only the latter). A console with a gate
240
+ and none of these throws on creation.
241
+
242
+ ## Requests and agent tools
243
+
244
+ `request({ command, args, view?, actor })` or `request({ line, actor })` runs a command for a caller other than the
245
+ user's Enter. It resolves `busy` while a run exists, `unknown-command` (for a `hidden` command too), `invalid-args`,
246
+ or `not-allowed` for a command without `input` or `danger`, a sensitive or `irreversible` one, and in this release
247
+ for every actor other than `external-agent` and every `danger` other than `read`. These leave no entry. Otherwise an
248
+ input entry with the canonical line and the `actor` is appended and the line takes the path above. The outcome is
249
+ `ok` with the response's `summary` (cut to 200 code points), `failed`, `cancelled`, `not-allowed` (`beforeRun`
250
+ returned `false`), the code of a `CommandError`, or the gate's code. A response with `tone: 'error'` (from the command
251
+ or from `beforeRun`) is drawn as usual but its outcome is `failed`, without `summary`. A line the user types carries no
252
+ `actor`.
253
+
254
+ A command ends its run with a code of its own by throwing (or rejecting) a `CommandError`:
255
+
256
+ ```ts
257
+ import { CommandError } from '@dolusoft/command-console-core'
258
+
259
+ const show: CommandDefinition = {
260
+ name: 'show',
261
+ input: showInput,
262
+ danger: 'read',
263
+ run: request => {
264
+ const tab = findTab(request.view)
265
+ if (!tab) {
266
+ throw new CommandError('target-not-found', 'No such tab.')
267
+ }
268
+ return tab.render(request.input)
269
+ }
270
+ }
271
+ ```
272
+
273
+ The run fails as with any error (`onError` gets it, the entry is `formatError`'s or `command-failed`), and the
274
+ outcome is `{ status: 'error', code }`. A command may give `target-not-found`, `invalid-args` or `failed`; any other
275
+ code (`busy`, `timeout`, `id-conflict`, … belong to the core) gives `failed`, and so does a `CommandError` thrown by
276
+ `beforeRun`, `normalize` or `createEnvelopeId`.
277
+
278
+ `agentTools(console, { context })` returns the `context`, `help` and `run` tools as plain objects in the shape of a
279
+ WebMCP tool (`name`, `description`, `inputSchema`, `annotations`, `execute`). The consumer registers them:
280
+
281
+ ```ts
282
+ const controller = new AbortController()
283
+ for (const tool of agentTools(console, { context: () => describeViews() })) {
284
+ document.modelContext?.registerTool(tool, { signal: controller.signal })
285
+ }
286
+ ```
287
+
288
+ `help` lists the commands that have `input` and `danger` and are neither `hidden` nor `sensitive`; `run` calls
289
+ `request` as `external-agent`. No `execute` rejects.
290
+
196
291
  ## Contract
197
292
 
198
293
  The behavior rules are in [`contract/rules.md`](https://github.com/dolusoft/command-console/blob/main/contract/rules.md);