@dolusoft/command-console-core 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +202 -2
  3. package/dist/command-console-core.js +1732 -0
  4. package/dist/command-console-core.js.map +1 -0
  5. package/dist/types/core/console/console.types.d.ts +232 -0
  6. package/dist/types/core/console/console.utils.d.ts +13 -0
  7. package/dist/types/core/console/consoleFeature.d.ts +13 -0
  8. package/dist/types/core/console/constructConsole.d.ts +7 -0
  9. package/dist/types/core/dispatch/dispatch.types.d.ts +90 -0
  10. package/dist/types/core/dispatch/dispatch.utils.d.ts +17 -0
  11. package/dist/types/core/dispatch/dispatchFeature.d.ts +2 -0
  12. package/dist/types/core/dispatch/sensitivity.d.ts +20 -0
  13. package/dist/types/core/entries/entries.utils.d.ts +8 -0
  14. package/dist/types/core/entries/entriesFeature.d.ts +13 -0
  15. package/dist/types/core/input/inputFeature.d.ts +13 -0
  16. package/dist/types/features/completion/completion.types.d.ts +23 -0
  17. package/dist/types/features/completion/completion.utils.d.ts +32 -0
  18. package/dist/types/features/completion/completionFeature.d.ts +11 -0
  19. package/dist/types/features/context/context.utils.d.ts +17 -0
  20. package/dist/types/features/context/contextFeature.d.ts +13 -0
  21. package/dist/types/features/recall/recall.utils.d.ts +13 -0
  22. package/dist/types/features/recall/recallFeature.d.ts +12 -0
  23. package/dist/types/helpers/consoleFeatures.d.ts +18 -0
  24. package/dist/types/helpers/coreFeatures.d.ts +3 -0
  25. package/dist/types/index.d.ts +8 -0
  26. package/dist/types/shared/effects.d.ts +8 -0
  27. package/dist/types/shared/hooks.d.ts +172 -0
  28. package/dist/types/shared/index.d.ts +13 -0
  29. package/dist/types/shared/keys.d.ts +30 -0
  30. package/dist/types/shared/lifecycle.d.ts +30 -0
  31. package/dist/types/shared/registry.d.ts +27 -0
  32. package/dist/types/store/action.d.ts +8 -0
  33. package/dist/types/store/createStore.d.ts +43 -0
  34. package/package.json +37 -3
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dolusoft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,203 @@
1
- # Temporary Holding Version
1
+ # @dolusoft/command-console-core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The engine of [Command Console](https://github.com/dolusoft/command-console): a headless console that ends lines,
4
+ runs commands, keeps the scrollback, recalls and completes lines, and redacts sensitive lines. No framework, no DOM, no
5
+ timer; it depends on `@dolusoft/command-protocol` only. The Vue package `@dolusoft/command-console` is built on it;
6
+ use the core directly for another framework or a test.
7
+
8
+ ```sh
9
+ pnpm add @dolusoft/command-console-core
10
+ ```
11
+
12
+ The repository README describes the behavior a user sees (commands, returns and effects, completion, recall, keys,
13
+ sensitive lines). This page describes the engine.
14
+
15
+ ## Creating a console
16
+
17
+ ```ts
18
+ import {
19
+ completionFeature,
20
+ consoleFeatures,
21
+ contextFeature,
22
+ createConsole,
23
+ recallFeature
24
+ } from '@dolusoft/command-console-core'
25
+
26
+ const console = createConsole({
27
+ features: consoleFeatures({
28
+ recallFeature,
29
+ completionFeature,
30
+ contextFeature
31
+ }),
32
+ prompt: 'app>',
33
+ commands: [
34
+ { name: 'echo', run: request => request.rest },
35
+ // From argument 1 on the line is secret: it is never kept or handed out.
36
+ {
37
+ name: 'set',
38
+ sensitive: { fromArg: 1 },
39
+ run: async (request, run) => save(request.args, run.signal)
40
+ }
41
+ ],
42
+ onError: (error, info) => report(error, info)
43
+ })
44
+
45
+ const unsubscribe = console.subscribe(state => render(state))
46
+ console.setInput('echo hello')
47
+ await console.submit() // or: await console.run('echo hello')
48
+ ```
49
+
50
+ The core features (`console`, `entries`, `input`, `dispatch`) are always present. `recallFeature`, `completionFeature`
51
+ and `contextFeature` are opt-in, so a console that does not use them does not ship them. `consoleFeatures()` keeps the
52
+ object's type: the instance type is computed from the features given, and a console created without `features` has
53
+ no optional method, in its type and at run time. The feature set is fixed for the life of the console.
54
+
55
+ ## Options
56
+
57
+ | Option | Meaning |
58
+ | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
59
+ | `features` | the optional features, keyed `recallFeature` / `completionFeature` / `contextFeature` |
60
+ | `commands` | the `CommandDefinition`s; validated at creation and on every `setOptions` (a violation throws) |
61
+ | `prompt` | the root prompt; default `'>'` |
62
+ | `initialState` | the first `entries`, `recall`, `context`; every other slice starts empty |
63
+ | `state` | a slice given here is controlled (below); it wins over `initialState` |
64
+ | `onEntriesChange`, `onRecallChange`, `onContextChange` | the reports of a controlled slice: one updater per action |
65
+ | `createId` | makes entry and run ids; default a per-console counter (`c1`, `c2`, …) that skips ids already in `entries` |
66
+ | `maxEntries` | drops the oldest entries beyond this, in the same action; default unbounded |
67
+ | `beforeRun(info, signal)` | asked before a command runs; `undefined` goes on, `false` stops silently, a response ends the run with it |
68
+ | `onError(error, info)` | a command threw or rejected (`info` is its `RunInfo`), or a callback threw (`info` is `null`) |
69
+ | `formatError(error, info)` | turns a command's error into its `CommandResponse`; default a `command-failed` entry |
70
+ | `onClose()` | the `close` effect |
71
+ | `isSensitive(line)` | marks more lines sensitive, OR-ed with the commands' `sensitive`; a throw counts as sensitive |
72
+ | `recallLimit` | recall: the most lines kept; default 100 (`Infinity` keeps all, a fraction is rounded down) |
73
+ | `shouldRecord(line)` | recall: whether a submitted line is recorded; default every line |
74
+
75
+ Run ids come from `createId` too, so with the default counter the first command's output is `c3` (`c1` the input entry,
76
+ `c2` the run). An id `createId` returns that is already present throws from the method that caused it, as a duplicate
77
+ id in `appendEntries` does.
78
+
79
+ ## State
80
+
81
+ ```ts
82
+ interface ConsoleState {
83
+ entries: ConsoleEntry[]
84
+ input: { value: string; caret: number; revision: number }
85
+ running: {
86
+ runId: string
87
+ command: string
88
+ inputId: string
89
+ phase: 'authorizing' | 'running'
90
+ } | null
91
+ recall: string[] // oldest → newest (recall)
92
+ recallCursor: number | null // recall
93
+ recallDraft: string | null // recall
94
+ completion: CompletionSession | null // completion: candidates, active, the token's span, its original text
95
+ context: ContextFrame[] // bottom → top, root implicit (context)
96
+ }
97
+ ```
98
+
99
+ `getState()` returns the committed state, the same object until the next change. `running.phase` is `authorizing`
100
+ while `beforeRun` has not answered. `input.revision` changes when the input element must be replaced: after a
101
+ sensitive line ends, on `reset()` when the input was not empty, and on any programmatic clear of a non-empty input. An
102
+ adapter keys its input element by it, so the element that held the text goes together with its undo history.
103
+
104
+ ## Methods
105
+
106
+ Every console has:
107
+
108
+ - `getState()`, `subscribe(listener)` (returns the unsubscribe function), `setOptions(updater)`.
109
+ - `setInput(value, caret?)`: the consumer's programmatic write; the caret defaults to the end. Clearing a non-empty
110
+ input increments `input.revision`.
111
+ - `editInput(value, caret)`: reports a user edit or caret move the element already shows; never increments
112
+ `input.revision` (a user backspacing to empty keeps the element). `setInput` and `editInput` do nothing while a
113
+ command runs.
114
+ - `submit()`: ends the live input as Enter does. Resolves `true` after the run's end (at once when nothing runs: an
115
+ empty line, a parse error, an unknown command), `false` without doing anything while a run exists.
116
+ - `run(line)`: replaces the live input with `line` and submits it; the same result as `submit()`.
117
+ - `cancel()`: interrupts the running command, else ends the live line as cancelled.
118
+ - `handleKey(input: KeyInput)`: applies the fixed key map; `true` when the key was handled, so the adapter prevents the
119
+ default.
120
+ - `appendEntries(entries)` (an id already present throws), `clearEntries()`.
121
+ - `getPrompt()`: the prompt of the next input entry. `isSensitive(line)`: whether `line` would be redacted.
122
+ - `getCommands()`: the `CommandSpec` of each command that is not `hidden`, in registration order, each with `aliases`
123
+ (`[]` when it has none).
124
+ - `reset()`, `dispose()` (below).
125
+
126
+ The optional features add:
127
+
128
+ - recall: `recallPrevious()`, `recallNext()` (each `false` when nothing changed), `clearRecall()`;
129
+ - completion: `complete()`, `completeNext()`, `completePrevious()`, `acceptCompletion()`, `closeCompletion()` (each
130
+ `false` when it did nothing);
131
+ - context: `pushContext(frame)`, `popContext(count?)`, `resetContext()`. `popContext` rounds `count` down; a count
132
+ below 1 does nothing, `Infinity` returns to the root. Frames are stored as given; the console does not check that
133
+ `data` is JSON.
134
+
135
+ `KeyInput` is `{ key, ctrlKey, shiftKey, altKey, metaKey, isComposing, selection }`, where `selection` is `'none'`,
136
+ `'input'` (text selected in the input) or `'document'` (text selected elsewhere inside the console). The adapter builds
137
+ it from its event; the core never sees a DOM event. Tab on a non-empty input is always handled, even where nothing can
138
+ be completed, so focus stays on the line.
139
+
140
+ ## Actions and notification
141
+
142
+ An **action** is one synchronous change that starts from one trigger: a key, a method call, the settling of
143
+ a promise (`beforeRun`, a command's `run`) or a `write()` call. A command run is several actions: its start, each
144
+ asynchronous `write()`, and its end (fulfil, reject or cancel). Each action makes its changes on a working copy; at its
145
+ end:
146
+
147
+ 1. each controlled slice that changed gets exactly one `on<Slice>Change(updater)` call, whose updater composes every
148
+ change of the action, in order, onto the value it is given;
149
+ 2. the internal slices are committed;
150
+ 3. subscribers are notified once.
151
+
152
+ Nothing changes and nothing is called on creation. A method call or `write()` made while an action is open (synchronously
153
+ from `run`, `beforeRun` or `isSensitive`) joins it; a synchronous return of `beforeRun` or `run` continues it. A call
154
+ made from a change handler, `onClose`, `onError` or a subscriber comes after the commit and is an action of its own.
155
+ An exception in a callback is reported through `onError(error, null)` and does not stop the other callbacks or the
156
+ action's cleanup; without `onError`, or when `onError` is the thrower, it is rethrown asynchronously.
157
+
158
+ A settle, `write()` or effect of a run that is no longer current (cancelled, reset, disposed, or replaced) changes
159
+ nothing and calls nothing.
160
+
161
+ ## Controlled slices
162
+
163
+ `entries`, `recall` and `context` are controlled when `options.state[slice]` is defined. The console then reads the
164
+ slice from the options, never writes it, and reports each action's change through `on<Slice>Change(updater)`, where an
165
+ updater is a value or a function of the old value. Hand the new value back with `setOptions`:
166
+
167
+ ```ts
168
+ let entries: ConsoleEntry[] = []
169
+ const console = createConsole({
170
+ commands,
171
+ state: { entries },
172
+ onEntriesChange: updater => {
173
+ entries = typeof updater === 'function' ? updater(entries) : updater
174
+ console.setOptions(old => ({ ...old, state: { ...old.state, entries } }))
175
+ }
176
+ })
177
+ ```
178
+
179
+ An action starts from the value the console last reported for the slice, until `setOptions` delivers a new
180
+ `state[slice]` (that value wins, whether it accepts, refuses or edits the report; a defined `state[slice]` counts as
181
+ delivered even when it is the same array) or the microtask after the report runs. That microtask is the only scheduling
182
+ the core does. After it the base is `options.state[slice]` again. So two actions in one tick never lose each other's
183
+ change, and a consumer that ignores a report gets its own value back on the next tick.
184
+
185
+ An external change of `recall` ends recall browsing. An external change of `context` counts as a context change when
186
+ the sequence of frame ids differs; a new array with the same ids is not one.
187
+
188
+ ## Lifecycle
189
+
190
+ - `reset()` (one action): ends the running command as cancelled without an `interrupted` entry (the run is invalidated,
191
+ then its signal aborted), closes completion, and returns `entries`, `recall`, `context` and the input to their
192
+ initial values (`initialState`, else empty; for a controlled slice too). Each controlled slice gets one report.
193
+ - `dispose()`: ends the running command the same way, runs every feature's cleanup once (an exception in one does not
194
+ stop the others), clears the internal state, and turns every later method into a no-op. Two consoles share nothing.
195
+
196
+ ## Contract
197
+
198
+ The behavior rules are in [`contract/rules.md`](https://github.com/dolusoft/command-console/blob/main/contract/rules.md);
199
+ the public API is in [`etc/command-console-core.api.md`](./etc/command-console-core.api.md).
200
+
201
+ ## License
202
+
203
+ MIT, see [LICENSE.md](./LICENSE.md).