@dolusoft/command-console-core 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +202 -2
- package/dist/command-console-core.js +1732 -0
- package/dist/command-console-core.js.map +1 -0
- package/dist/types/core/console/console.types.d.ts +232 -0
- package/dist/types/core/console/console.utils.d.ts +13 -0
- package/dist/types/core/console/consoleFeature.d.ts +13 -0
- package/dist/types/core/console/constructConsole.d.ts +7 -0
- package/dist/types/core/dispatch/dispatch.types.d.ts +90 -0
- package/dist/types/core/dispatch/dispatch.utils.d.ts +17 -0
- package/dist/types/core/dispatch/dispatchFeature.d.ts +2 -0
- package/dist/types/core/dispatch/sensitivity.d.ts +20 -0
- package/dist/types/core/entries/entries.utils.d.ts +8 -0
- package/dist/types/core/entries/entriesFeature.d.ts +13 -0
- package/dist/types/core/input/inputFeature.d.ts +13 -0
- package/dist/types/features/completion/completion.types.d.ts +23 -0
- package/dist/types/features/completion/completion.utils.d.ts +32 -0
- package/dist/types/features/completion/completionFeature.d.ts +11 -0
- package/dist/types/features/context/context.utils.d.ts +17 -0
- package/dist/types/features/context/contextFeature.d.ts +13 -0
- package/dist/types/features/recall/recall.utils.d.ts +13 -0
- package/dist/types/features/recall/recallFeature.d.ts +12 -0
- package/dist/types/helpers/consoleFeatures.d.ts +18 -0
- package/dist/types/helpers/coreFeatures.d.ts +3 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/shared/effects.d.ts +8 -0
- package/dist/types/shared/hooks.d.ts +172 -0
- package/dist/types/shared/index.d.ts +13 -0
- package/dist/types/shared/keys.d.ts +30 -0
- package/dist/types/shared/lifecycle.d.ts +30 -0
- package/dist/types/shared/registry.d.ts +27 -0
- package/dist/types/store/action.d.ts +8 -0
- package/dist/types/store/createStore.d.ts +43 -0
- 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
|
-
#
|
|
1
|
+
# @dolusoft/command-console-core
|
|
2
2
|
|
|
3
|
-
|
|
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).
|