@nexus-aethra/dshell-std 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nexus Aethra
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.
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Every dshell wire contract: the `/api/dshell/*` paths and the shapes that
3
+ * cross them.
4
+ *
5
+ * Layering rule for this file, and for this package as a whole: it declares
6
+ * FACTS, not behaviour. No dsh packages, no runtime state, and no imports except
7
+ * types from this package's own sibling modules — so both halves of every plugin
8
+ * (host route and browser face) can import the same declaration, and a contract
9
+ * change is a compile error on both sides instead of a silent drift. The browser
10
+ * faces used to restate these shapes by hand precisely because there was nowhere
11
+ * shared to put them.
12
+ *
13
+ * Sections below were moved verbatim from the packages' own protocol modules,
14
+ * which now re-export from here; the package-local headers are kept because they
15
+ * carry the reasoning for each shape.
16
+ */
17
+ /**
18
+ * The file navigator's wire vocabulary, shared by the host route and the
19
+ * browser face.
20
+ *
21
+ * Deliberately free of value imports: the browser half imports this module, and
22
+ * anything it pulls in would be bundled into `client.js`.
23
+ *
24
+ * A path on the wire is always the CANONICAL absolute path **in the session's
25
+ * own execution world** — the host path for a local session, the device path
26
+ * for a device-bound one. That is the string the client shows, splits into
27
+ * segments, trims for `..`, and hands back to list again, so both ends agree on
28
+ * one spelling without either knowing which world produced it.
29
+ */
30
+ /** The exact `/api` path the file navigator talks to. */
31
+ export const DSHELL_FILES_PATH = '/api/dshell/files';
32
+ // ─── files — the two-world transfer view ───
33
+ // moved from packages/dshell/files/src/transfer-protocol.ts
34
+ /**
35
+ * The file-transfer wire vocabulary: one device session's two file trees, and
36
+ * the copies between them.
37
+ *
38
+ * Separate from `./protocol.ts` because it is a different route with a
39
+ * different subject. The navigator's route answers about ONE world (the
40
+ * session's); this one is about the pair — this machine and the device the
41
+ * session runs on — and about work that outlives one request.
42
+ *
43
+ * Deliberately free of value imports: the browser face imports this module, and
44
+ * anything it pulled in would be bundled into `client.js`. `Buffer`-style host
45
+ * types are not needed on the browser side at all.
46
+ */
47
+ /** The exact `/api` path the transfer view talks to. */
48
+ export const DSHELL_TRANSFER_PATH = '/api/dshell/transfer';
49
+ // ─── buffer — the cross-session pipe: links, tickets, grants, transfers ───
50
+ // moved from packages/dshell/buffer/src/protocol.ts
51
+ /**
52
+ * The buffer's wire vocabulary, shared by the host route and the browser face.
53
+ *
54
+ * Deliberately free of value imports: the browser half imports this module, and
55
+ * anything it pulls in would be bundled into `client.js`. Identifiers that the
56
+ * host validates against (a request `action`, a ticket `state`) are plain
57
+ * strings here and narrowed on the host side.
58
+ *
59
+ * Session ids travel as plain strings rather than the branded `SessionId`
60
+ * because this is a JSON boundary; the host converts at the edges.
61
+ */
62
+ /** The exact `/api` path the pipe UI talks to. */
63
+ export const DSHELL_BUFFER_PATH = '/api/dshell/buffer';
64
+ /** The plugin name every buffer-authored message carries as its provenance. */
65
+ export const BUFFER_PLUGIN = 'dshell-buffer';
66
+ /** States after which a ticket is settled and never changes again. */
67
+ export const SETTLED_STATES = ['done', 'failed', 'timeout', 'cancelled'];
68
+ // ─── ssh — devices, bindings, their requests/responses ───
69
+ // moved from packages/dshell/ssh/src/protocol.ts
70
+ /**
71
+ * dshell-ssh wire — one exact `/api/dshell/ssh` route, plus the device shapes
72
+ * both halves agree on.
73
+ *
74
+ * The route carries what must not travel through the settings document: the
75
+ * private keys themselves. A device's durable record is small JSON; its key is
76
+ * a separate file written 0600 under `$DSH_HOME/dshell/ssh/keys/`.
77
+ *
78
+ * This module is imported by the browser half through the package's
79
+ * `./protocol` subpath, so it must stay free of value imports: anything it
80
+ * pulls in would be bundled into the client.
81
+ */
82
+ /** Exact `/api` route path owned by the SSH device registry. */
83
+ export const DSHELL_SSH_PATH = '/api/dshell/ssh';
84
+ /** Settings namespace owned by this plugin; also the device card's slot key. */
85
+ export const SSH_SETTINGS_NAMESPACE = 'dshell-ssh';
86
+ // ─── workspace — the session archive routes ───
87
+ // moved from packages/dshell/workspace/src/protocol.ts
88
+ /**
89
+ * dshell session-panel wire — design 4.7 follow-up.
90
+ *
91
+ * The sidebar's session rows need two things dsh does not offer: an archive
92
+ * tag that hides a session without touching its log, and a history purge.
93
+ * Both are dshell concepts, so they travel over dshell's own exact route on
94
+ * the shared `/api` channel rather than through the Typert Remote table
95
+ * (whose client artifacts are generated from dsh's own packages).
96
+ *
97
+ * The path sits below the shared channel exactly like file-upload's: the
98
+ * physical carrier applies dsh's trust and authentication policy before the
99
+ * handler ever runs, so the route itself does no authorization.
100
+ *
101
+ * Shared by both halves of the package, so the constant and the shapes live
102
+ * in one file that neither the host nor the client bundle owns.
103
+ */
104
+ /** Exact `/api` route path owned by the dshell session panel. */
105
+ export const DSHELL_SESSIONS_PATH = '/api/dshell/sessions';
106
+ // ─── terminal-bridge — the shell's history read ───
107
+ // moved from packages/dshell/terminal-bridge/src/route.ts
108
+ export const DSHELL_PTY_PATH = '/api/dshell/pty';
109
+ // ─── terminal-bridge — the frame stream over the shared API channel ───
110
+ /**
111
+ * Downstream half of the frame stream: a long-lived GET whose response body is
112
+ * newline-delimited JSON, one bridge frame per line.
113
+ *
114
+ * The wire itself is unchanged — these are the very frames the ws upgrade
115
+ * carries — but a Response body is reachable from any composition the
116
+ * `connection` service is in, while `registerUpgrade` needs `webServer`, which
117
+ * the desktop shell deliberately does not compose. Identity is explicit because
118
+ * an HTTP stream has no socket to key on: both halves carry the client's own
119
+ * `clientId`, and the GET carries the bind (`sessionId` + `stream`) that a ws
120
+ * would have sent as a frame.
121
+ */
122
+ export const DSHELL_STREAM_PATH = '/api/dshell/stream';
123
+ /**
124
+ * Upstream half: one POST per control frame (`input`, `resize`, `signal`,
125
+ * `agent-open`, `reconnect`), identified by the same `clientId` the GET opened
126
+ * with. Answers with no body; the stream is the only place results appear.
127
+ */
128
+ export const DSHELL_STREAM_SEND_PATH = '/api/dshell/stream/send';
129
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH;;;;;;;;;;;;GAYG;AAEH,yDAAyD;AACzD,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAA;AA2KpD,8CAA8C;AAC9C,4DAA4D;AAE5D;;;;;;;;;;;;GAYG;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AAsH1D,6EAA6E;AAC7E,oDAAoD;AAEpD;;;;;;;;;;GAUG;AAEH,kDAAkD;AAClD,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAA;AAEtD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAA;AAW5C,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAAiC,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,CAAA;AAyLtG,4DAA4D;AAC5D,iDAAiD;AAEjD;;;;;;;;;;;GAWG;AAEH,gEAAgE;AAChE,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAA;AAEhD,gFAAgF;AAChF,MAAM,CAAC,MAAM,sBAAsB,GAAG,YAAY,CAAA;AA6GlD,iDAAiD;AACjD,uDAAuD;AAEvD;;;;;;;;;;;;;;;GAeG;AAEH,iEAAiE;AACjE,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAA;AA0B1D,qDAAqD;AACrD,0DAA0D;AAE1D,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAA;AAuChD,yEAAyE;AAEzE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAA;AAEtD;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAA"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The host copy direction: the contract for text a HOST writes that the user
3
+ * reads.
4
+ *
5
+ * Browser faces learn their language from dsh's `ctx.locale`, which the host
6
+ * cannot see. Host-composed text — route refusals, device errors, the delegated
7
+ * request a peer's model reads, a command's result line — therefore needs its
8
+ * own statement of "which language", and it needs it from the browser that is
9
+ * actually on screen. Two signals stand in for it, in order: what the browser
10
+ * last reported to {@link DSHELL_LOCALE_PATH} (exact, and it covers a language
11
+ * the browser picked for itself when the user never chose one), then the durable
12
+ * `locale.preference` dsh's own Language row writes. With neither, the host
13
+ * writes the project's source-of-truth language, `zh`.
14
+ *
15
+ * The rules here follow this package's layering: declaration only — the shapes
16
+ * and the path — with no imports, no dsh packages and no runtime state. The
17
+ * service that RESOLVES the language is provided by `dshell-mode`
18
+ * (`ctx.dshellHostCopy`), so every package binds its own host dictionaries to it
19
+ * instead of re-implementing the lookup.
20
+ */
21
+ /** Where the browser reports the locale it actually resolved. */
22
+ export const DSHELL_LOCALE_PATH = '/api/dshell/locale';
23
+ /** The languages a host message can be written in, in report order. */
24
+ export const HOST_LOCALE_IDS = ['zh', 'en'];
25
+ //# sourceMappingURL=host-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-copy.js","sourceRoot":"","sources":["../src/host-copy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAA;AAEtD,uEAAuE;AACvE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,IAAI,CAAU,CAAA"}
package/lib/index.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * dshell's standard layer.
3
+ *
4
+ * Everything here is shared machinery that must not belong to any one feature:
5
+ * the wire contracts ({@link ./contracts.ts}), the host copy direction
6
+ * ({@link ./host-copy.ts}) that a host half binds its own dictionaries to, the
7
+ * storage contract ({@link ./storage.ts}) that a feature calls without knowing
8
+ * its medium, and as the refactor continues, the dsh seam adapters (route
9
+ * definition, session/world addressing, capability probing) that today live
10
+ * duplicated inside feature packages.
11
+ *
12
+ * The rule that makes this layer worth having: it is the ONLY dshell package
13
+ * allowed to care about how dsh spells things. A feature package imports from
14
+ * here and keeps to its own product logic, so a dsh interface change is
15
+ * absorbed in one place instead of re-implemented per plugin.
16
+ */
17
+ export * from './contracts.js';
18
+ export * from './host-copy.js';
19
+ export * from './shell-line.js';
20
+ export * from './storage.js';
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA"}
@@ -0,0 +1,275 @@
1
+ /**
2
+ * The shell line, tokenized and classified — the rule table behind Tab.
3
+ *
4
+ * The composer's completion has to know WHICH position the caret is in before it
5
+ * can know what to offer, because a position is a property of the LINE, not of
6
+ * the word: `dock` is a command in `dock`, an argument in `cd dock`, and a file
7
+ * in `tee > dock`. bash's own grammar is what defines it (the start of a *simple
8
+ * command*), and that start is introduced by an operator, a keyword, or a prefix
9
+ * word — never by "it happens to be first".
10
+ *
11
+ * This module is that rule table, kept pure: no services, no filesystem, no
12
+ * process. Both halves of the feature depend on it — the host decides which
13
+ * source answers, the browser decides when an empty answer is worth showing —
14
+ * and `packages/dshell/std/tests/shell-line.spec.ts` drives it with real lines.
15
+ *
16
+ * Deliberately NOT a shell parser. bash's grammar is unbounded (`eval`, nested
17
+ * substitutions, heredocs, functions), so this walk answers the shapes it can
18
+ * PROVE and admits what it cannot: a blank line or an operator under the caret
19
+ * reports "nothing to complete" rather than guessing, and the caller decides
20
+ * what no-answer means. There is no "maybe" position — a caller cannot act on
21
+ * one, and a wrong KIND of candidate (`-launcher.sh` offered for `-la<Tab>`) is
22
+ * worse than silence.
23
+ */
24
+ /** Characters that end a word. */
25
+ const BLANKS = new Set([' ', '\t', '\n', '\r']);
26
+ /**
27
+ * Words after which a simple command begins, so the NEXT word is that command's
28
+ * name: wrapper commands (`sudo`, `env`, `time`, `xargs`, … — bash's own
29
+ * bash-completion registers several of these, e.g. `complete -F _comp_command
30
+ * time xargs nohup`) and the keywords that open a command list.
31
+ */
32
+ const BEFORE_COMMAND = new Set([
33
+ 'if', 'then', 'else', 'elif', 'while', 'until', 'do', '!', 'time', '{',
34
+ 'sudo', 'doas', 'env', 'nice', 'ionice', 'nohup', 'setsid', 'strace',
35
+ 'command', 'builtin', 'exec', 'xargs', 'watch', 'timeout', 'noglob', 'nocorrect',
36
+ ]);
37
+ /**
38
+ * Keywords that END a command list rather than open one. `in` is here on
39
+ * purpose: what follows it are words (`for f in a b`), not a command.
40
+ *
41
+ * The walk does not need this split — a keyword in the command position is never
42
+ * the command itself, whatever list it belongs to — so this set only decides
43
+ * which words are tokens of kind `reserved` rather than plain words.
44
+ */
45
+ const AFTER_COMMAND = new Set(['fi', 'done', 'esac', '}', 'in', 'case', 'select', 'function', 'for']);
46
+ /** One operator form. Order matters: the longest/dup forms must be tried first. */
47
+ const OPERATORS = [
48
+ { re: /^&&/u, kind: 'separator', target: false },
49
+ { re: /^\|\|/u, kind: 'separator', target: false },
50
+ { re: /^\|&/u, kind: 'separator', target: false },
51
+ { re: /^;/u, kind: 'separator', target: false },
52
+ { re: /^\|/u, kind: 'separator', target: false },
53
+ // A file-descriptor duplication (`2>&1`, `>&2`) names a descriptor, not a
54
+ // file — it must be matched before the plain redirections below.
55
+ { re: /^[0-9]*>&[0-9-]+/u, kind: 'redirection', target: false },
56
+ { re: /^&>>?/u, kind: 'redirection', target: true },
57
+ { re: /^&/u, kind: 'separator', target: false },
58
+ { re: /^[0-9]*(?:>>|>\||>\|?|<[<>]?)/u, kind: 'redirection', target: true },
59
+ { re: /^\$\(/u, kind: 'open', target: false },
60
+ { re: /^\(/u, kind: 'open', target: false },
61
+ { re: /^\)/u, kind: 'close', target: false },
62
+ { re: /^`/u, kind: 'open', target: false },
63
+ ];
64
+ /** The operator at `index`, if any. */
65
+ function operatorAt(line, index) {
66
+ for (const form of OPERATORS) {
67
+ const matched = form.re.exec(line.slice(index));
68
+ if (matched !== null)
69
+ return { kind: form.kind, text: matched[0], target: form.target };
70
+ }
71
+ return undefined;
72
+ }
73
+ /** Whether a bare word is an assignment rather than a name. */
74
+ function isAssignment(text) {
75
+ return /^[A-Za-z_][A-Za-z0-9_]*=/u.test(text);
76
+ }
77
+ /** The kind of a word, from its own shape alone (its position decides the rest). */
78
+ function kindOfWord(text, quoted) {
79
+ if (!quoted && (text === '{' || text === '}'))
80
+ return text === '{' ? 'open' : 'close';
81
+ // An assignment tests the RAW text, not the quoting: what makes `FOO='a b'`
82
+ // an assignment is its unquoted `name=` head, and the quotes only hide the
83
+ // value. Quoting the NAME (`'FOO'=a`) makes it an ordinary word, which is
84
+ // exactly what anchoring the test at offset 0 already gives.
85
+ if (isAssignment(text))
86
+ return 'assign';
87
+ if (quoted)
88
+ return 'word';
89
+ if (BEFORE_COMMAND.has(text) || AFTER_COMMAND.has(text))
90
+ return 'reserved';
91
+ return 'word';
92
+ }
93
+ /**
94
+ * Split one line into tokens, stopping at nothing: a completion also runs on a
95
+ * half-written line, so quotes may be open and the last word may be cut off.
96
+ *
97
+ * Quote handling is what a completion needs, not what a shell needs: whitespace
98
+ * inside quotes does not split, an unterminated quote keeps its token whole (the
99
+ * reader is still typing it), and the operators that are not separated by spaces
100
+ * (`a>b`, `x;y`) still split — which the word scanner does by asking
101
+ * {@link operatorAt} before consuming a character.
102
+ *
103
+ * @param line - the line as written.
104
+ * @returns the tokens in order, with their offsets.
105
+ */
106
+ export function tokenizeShellLine(line) {
107
+ const tokens = [];
108
+ let index = 0;
109
+ while (index < line.length) {
110
+ if (BLANKS.has(line[index])) {
111
+ index += 1;
112
+ continue;
113
+ }
114
+ const operator = operatorAt(line, index);
115
+ if (operator !== undefined) {
116
+ tokens.push({
117
+ kind: operator.kind, text: operator.text, start: index, end: index + operator.text.length,
118
+ quoted: false, target: operator.target,
119
+ });
120
+ index += operator.text.length;
121
+ continue;
122
+ }
123
+ const start = index;
124
+ let text = '';
125
+ let quoted = false;
126
+ while (index < line.length) {
127
+ const char = line[index];
128
+ if (BLANKS.has(char))
129
+ break;
130
+ if (char === '\\' && index + 1 < line.length) {
131
+ text += line.slice(index, index + 2);
132
+ index += 2;
133
+ continue;
134
+ }
135
+ if (char === '\'' || char === '"' || char === '`') {
136
+ quoted = true;
137
+ text += char;
138
+ index += 1;
139
+ while (index < line.length && line[index] !== char) {
140
+ if (char === '"' && line[index] === '\\' && index + 1 < line.length) {
141
+ text += line.slice(index, index + 2);
142
+ index += 2;
143
+ continue;
144
+ }
145
+ text += line[index];
146
+ index += 1;
147
+ }
148
+ // An unterminated quote is a word still being written: keep it whole.
149
+ if (index < line.length) {
150
+ text += char;
151
+ index += 1;
152
+ }
153
+ continue;
154
+ }
155
+ if (operatorAt(line, index) !== undefined)
156
+ break;
157
+ text += char;
158
+ index += 1;
159
+ }
160
+ if (text.length === 0) {
161
+ index += 1;
162
+ continue;
163
+ }
164
+ tokens.push({ kind: kindOfWord(text, quoted), text, start, end: index, quoted, target: false });
165
+ }
166
+ return tokens;
167
+ }
168
+ /**
169
+ * Walk the tokens before the caret and report what the line expects there.
170
+ *
171
+ * `command` is the command the caret's word belongs to, so it is undefined
172
+ * wherever the line has not named one YET — which is why every operator, and
173
+ * every keyword sitting where a command would go, clears it again.
174
+ *
175
+ * @param tokens - the line's tokens, in order.
176
+ * @param index - the token under the caret, or the token count for a word that
177
+ * has not been typed yet (an empty token after whitespace).
178
+ * @returns the position, and the command it belongs to when there is one.
179
+ */
180
+ export function classifyShellPosition(tokens, index) {
181
+ let expecting = 'command';
182
+ let command;
183
+ for (let at = 0; at < index && at < tokens.length; at += 1) {
184
+ const token = tokens[at];
185
+ if (token.kind === 'redirection') {
186
+ // A duplication (`2>&1`) names no file, so it leaves the expectation
187
+ // alone; only a real redirection opens the file slot that follows it.
188
+ expecting = token.target ? 'file' : expecting;
189
+ continue;
190
+ }
191
+ if (token.kind === 'separator' || token.kind === 'open') {
192
+ expecting = 'command';
193
+ command = undefined;
194
+ continue;
195
+ }
196
+ if (token.kind === 'close') {
197
+ expecting = 'argument';
198
+ continue;
199
+ }
200
+ if (token.kind === 'assign')
201
+ continue;
202
+ if (token.kind === 'reserved' && expecting === 'command') {
203
+ // A keyword or a wrapper word in the command position does not become the
204
+ // command: `if`/`then`/`do` open a list and `fi`/`done` close one (either
205
+ // way a command follows), while `sudo`/`env`/`time` hand the position on
206
+ // to the next word. So all of them clear `command` and keep waiting.
207
+ command = undefined;
208
+ continue;
209
+ }
210
+ if (expecting === 'command') {
211
+ command = token.text;
212
+ expecting = 'argument';
213
+ continue;
214
+ }
215
+ if (expecting === 'file')
216
+ expecting = 'argument';
217
+ }
218
+ const caret = tokens[index];
219
+ // No word under the caret yet: the expectation IS the answer, with the file
220
+ // slot reported as the `redir` position it always was.
221
+ if (caret === undefined)
222
+ return { position: expecting === 'file' ? 'redir' : expecting, command };
223
+ if (expecting === 'file')
224
+ return { position: 'redir', command };
225
+ if (expecting === 'command')
226
+ return { position: 'command', command };
227
+ if (caret.kind === 'word' && caret.text.length > 1 && caret.text.startsWith('-')) {
228
+ return { position: 'flag', command };
229
+ }
230
+ return { position: 'argument', command };
231
+ }
232
+ /**
233
+ * Everything the completion needs to know about one caret: where the word is,
234
+ * which part of it is the name, and what the line expects there.
235
+ *
236
+ * @param line - the line as written (the draft).
237
+ * @param cursor - the caret offset within it.
238
+ * @returns the caret reading, or undefined when there is nothing to complete —
239
+ * a blank line, or an operator under the caret, which names no word (a
240
+ * redirection WITH a target is the exception: `ls >` is a file slot with an
241
+ * empty word in it, and the reader reports it as such).
242
+ */
243
+ export function readShellCaret(line, cursor) {
244
+ const before = line.slice(0, Math.max(0, Math.min(cursor, line.length)));
245
+ const tokens = tokenizeShellLine(before);
246
+ const last = tokens[tokens.length - 1];
247
+ let index = tokens.length;
248
+ let start = before.length;
249
+ let dirPart = '';
250
+ let prefix = '';
251
+ let empty = true;
252
+ if (last !== undefined && last.end === before.length && last.kind === 'redirection' && last.target) {
253
+ // `ls >` — the operator is complete and the word it names is empty, so the
254
+ // walk (which now sees that redirection) reports the file slot. bash reads
255
+ // it the same way: Tab right after `>` lists the directory.
256
+ }
257
+ else if (last !== undefined && last.end === before.length) {
258
+ // An operator under the caret names nothing: there is no word being typed.
259
+ if (last.kind !== 'word' && last.kind !== 'assign' && last.kind !== 'reserved')
260
+ return undefined;
261
+ const slash = last.text.lastIndexOf('/');
262
+ dirPart = slash < 0 ? '' : last.text.slice(0, slash + 1);
263
+ prefix = last.text.slice(slash + 1);
264
+ start = last.start + dirPart.length;
265
+ index = tokens.length - 1;
266
+ empty = false;
267
+ }
268
+ else if (before.trim().length === 0) {
269
+ // A blank line completes nothing, which is what a terminal does too.
270
+ return undefined;
271
+ }
272
+ const { position, command } = classifyShellPosition(tokens, index);
273
+ return { position, command, start, end: before.length, dirPart, prefix, empty };
274
+ }
275
+ //# sourceMappingURL=shell-line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-line.js","sourceRoot":"","sources":["../src/shell-line.ts"],"names":[],"mappings":"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"}
package/lib/storage.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The storage contract: what a dshell store *is*, with no medium attached.
3
+ *
4
+ * dshell has one durable store for shell history today and a reason to expect
5
+ * more (agent-facing records that want indexes). The split that keeps that from
6
+ * becoming each feature's private file format:
7
+ *
8
+ * - **the contract** lives here — the record shape, the store surface, the
9
+ * on-disk naming and layout version, and the failure vocabulary;
10
+ * - **the medium** lives in `@nexus-aethra/dshell-storage` (`node:sqlite`);
11
+ * - **a feature** only calls the store, and never names a file, a pragma or a
12
+ * schema.
13
+ *
14
+ * Nothing here may import a Node builtin: `std` is inlined into client bundles,
15
+ * so this file has to stay an isomorphic declaration. The engine is host-only
16
+ * for exactly that reason — `node:sqlite` cannot cross into a browser bundle.
17
+ */
18
+ /** File name of the shared database inside a log directory. */
19
+ export const HISTORY_STORE_FILENAME = 'history.sqlite';
20
+ /**
21
+ * Physical layout version of the history database, stamped in
22
+ * `PRAGMA user_version`.
23
+ *
24
+ * The engine migrates the layouts it knows: 1 dropped `commands_at(at)`, an
25
+ * index for a cross-session time query that was never built and that nothing
26
+ * read; 2 added `command_output`, the per-command output the agent-facing read
27
+ * tool addresses by offset. A database stamped with anything else is rejected:
28
+ * this code is the only producer, and history is a convenience the user may
29
+ * lose without losing a session.
30
+ */
31
+ export const HISTORY_STORE_SCHEMA_VERSION = 3;
32
+ /** A store that cannot be opened, or was written by another layout. */
33
+ export class HistoryStoreError extends Error {
34
+ code;
35
+ constructor(code, message) {
36
+ super(message);
37
+ this.code = code;
38
+ this.name = 'HistoryStoreError';
39
+ }
40
+ }
41
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,+DAA+D;AAC/D,MAAM,CAAC,MAAM,sBAAsB,GAAG,gBAAgB,CAAA;AAEtD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAA;AAK7C,uEAAuE;AACvE,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACrB;IAArB,YAAqB,IAA2B,EAAE,OAAe;QAC/D,KAAK,CAAC,OAAO,CAAC,CAAA;QADK,SAAI,GAAJ,IAAI,CAAuB;QAE9C,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;IACjC,CAAC;CACF"}