@scelar/nodepod 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.
Files changed (134) hide show
  1. package/LICENSE +43 -0
  2. package/README.md +240 -0
  3. package/dist/child_process-BJOMsZje.js +8233 -0
  4. package/dist/child_process-BJOMsZje.js.map +1 -0
  5. package/dist/child_process-Cj8vOcuc.cjs +7434 -0
  6. package/dist/child_process-Cj8vOcuc.cjs.map +1 -0
  7. package/dist/index-Cb1Cgdnd.js +35308 -0
  8. package/dist/index-Cb1Cgdnd.js.map +1 -0
  9. package/dist/index-DsMGS-xc.cjs +37195 -0
  10. package/dist/index-DsMGS-xc.cjs.map +1 -0
  11. package/dist/index.cjs +65 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.mjs +59 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/package.json +95 -0
  16. package/src/__tests__/smoke.test.ts +11 -0
  17. package/src/constants/cdn-urls.ts +18 -0
  18. package/src/constants/config.ts +236 -0
  19. package/src/cross-origin.ts +26 -0
  20. package/src/engine-factory.ts +176 -0
  21. package/src/engine-types.ts +56 -0
  22. package/src/helpers/byte-encoding.ts +39 -0
  23. package/src/helpers/digest.ts +9 -0
  24. package/src/helpers/event-loop.ts +96 -0
  25. package/src/helpers/wasm-cache.ts +133 -0
  26. package/src/iframe-sandbox.ts +141 -0
  27. package/src/index.ts +192 -0
  28. package/src/isolation-helpers.ts +148 -0
  29. package/src/memory-volume.ts +941 -0
  30. package/src/module-transformer.ts +368 -0
  31. package/src/packages/archive-extractor.ts +248 -0
  32. package/src/packages/browser-bundler.ts +284 -0
  33. package/src/packages/installer.ts +396 -0
  34. package/src/packages/registry-client.ts +131 -0
  35. package/src/packages/version-resolver.ts +411 -0
  36. package/src/polyfills/assert.ts +384 -0
  37. package/src/polyfills/async_hooks.ts +144 -0
  38. package/src/polyfills/buffer.ts +628 -0
  39. package/src/polyfills/child_process.ts +2288 -0
  40. package/src/polyfills/chokidar.ts +336 -0
  41. package/src/polyfills/cluster.ts +106 -0
  42. package/src/polyfills/console.ts +136 -0
  43. package/src/polyfills/constants.ts +123 -0
  44. package/src/polyfills/crypto.ts +885 -0
  45. package/src/polyfills/dgram.ts +87 -0
  46. package/src/polyfills/diagnostics_channel.ts +76 -0
  47. package/src/polyfills/dns.ts +134 -0
  48. package/src/polyfills/domain.ts +68 -0
  49. package/src/polyfills/esbuild.ts +854 -0
  50. package/src/polyfills/events.ts +276 -0
  51. package/src/polyfills/fs.ts +2888 -0
  52. package/src/polyfills/fsevents.ts +79 -0
  53. package/src/polyfills/http.ts +1449 -0
  54. package/src/polyfills/http2.ts +199 -0
  55. package/src/polyfills/https.ts +76 -0
  56. package/src/polyfills/inspector.ts +62 -0
  57. package/src/polyfills/lightningcss.ts +105 -0
  58. package/src/polyfills/module.ts +191 -0
  59. package/src/polyfills/net.ts +353 -0
  60. package/src/polyfills/os.ts +238 -0
  61. package/src/polyfills/path.ts +206 -0
  62. package/src/polyfills/perf_hooks.ts +102 -0
  63. package/src/polyfills/process.ts +690 -0
  64. package/src/polyfills/punycode.ts +159 -0
  65. package/src/polyfills/querystring.ts +93 -0
  66. package/src/polyfills/quic.ts +118 -0
  67. package/src/polyfills/readdirp.ts +229 -0
  68. package/src/polyfills/readline.ts +692 -0
  69. package/src/polyfills/repl.ts +134 -0
  70. package/src/polyfills/rollup.ts +119 -0
  71. package/src/polyfills/sea.ts +33 -0
  72. package/src/polyfills/sqlite.ts +78 -0
  73. package/src/polyfills/stream.ts +1620 -0
  74. package/src/polyfills/string_decoder.ts +25 -0
  75. package/src/polyfills/tailwindcss-oxide.ts +309 -0
  76. package/src/polyfills/test.ts +197 -0
  77. package/src/polyfills/timers.ts +32 -0
  78. package/src/polyfills/tls.ts +105 -0
  79. package/src/polyfills/trace_events.ts +50 -0
  80. package/src/polyfills/tty.ts +71 -0
  81. package/src/polyfills/url.ts +174 -0
  82. package/src/polyfills/util.ts +559 -0
  83. package/src/polyfills/v8.ts +126 -0
  84. package/src/polyfills/vm.ts +132 -0
  85. package/src/polyfills/volume-registry.ts +15 -0
  86. package/src/polyfills/wasi.ts +44 -0
  87. package/src/polyfills/worker_threads.ts +326 -0
  88. package/src/polyfills/ws.ts +595 -0
  89. package/src/polyfills/zlib.ts +881 -0
  90. package/src/request-proxy.ts +716 -0
  91. package/src/script-engine.ts +3375 -0
  92. package/src/sdk/nodepod-fs.ts +93 -0
  93. package/src/sdk/nodepod-process.ts +86 -0
  94. package/src/sdk/nodepod-terminal.ts +350 -0
  95. package/src/sdk/nodepod.ts +509 -0
  96. package/src/sdk/types.ts +70 -0
  97. package/src/shell/commands/bun.ts +121 -0
  98. package/src/shell/commands/directory.ts +297 -0
  99. package/src/shell/commands/file-ops.ts +525 -0
  100. package/src/shell/commands/git.ts +2142 -0
  101. package/src/shell/commands/node.ts +80 -0
  102. package/src/shell/commands/npm.ts +198 -0
  103. package/src/shell/commands/pm-types.ts +45 -0
  104. package/src/shell/commands/pnpm.ts +82 -0
  105. package/src/shell/commands/search.ts +264 -0
  106. package/src/shell/commands/shell-env.ts +352 -0
  107. package/src/shell/commands/text-processing.ts +1152 -0
  108. package/src/shell/commands/yarn.ts +84 -0
  109. package/src/shell/shell-builtins.ts +19 -0
  110. package/src/shell/shell-helpers.ts +250 -0
  111. package/src/shell/shell-interpreter.ts +514 -0
  112. package/src/shell/shell-parser.ts +429 -0
  113. package/src/shell/shell-types.ts +85 -0
  114. package/src/syntax-transforms.ts +561 -0
  115. package/src/threading/engine-worker.ts +64 -0
  116. package/src/threading/inline-worker.ts +372 -0
  117. package/src/threading/offload-types.ts +112 -0
  118. package/src/threading/offload-worker.ts +383 -0
  119. package/src/threading/offload.ts +271 -0
  120. package/src/threading/process-context.ts +92 -0
  121. package/src/threading/process-handle.ts +275 -0
  122. package/src/threading/process-manager.ts +956 -0
  123. package/src/threading/process-worker-entry.ts +854 -0
  124. package/src/threading/shared-vfs.ts +352 -0
  125. package/src/threading/sync-channel.ts +135 -0
  126. package/src/threading/task-queue.ts +177 -0
  127. package/src/threading/vfs-bridge.ts +231 -0
  128. package/src/threading/worker-pool.ts +233 -0
  129. package/src/threading/worker-protocol.ts +358 -0
  130. package/src/threading/worker-vfs.ts +218 -0
  131. package/src/types/externals.d.ts +38 -0
  132. package/src/types/fs-streams.ts +142 -0
  133. package/src/types/manifest.ts +17 -0
  134. package/src/worker-sandbox.ts +90 -0
@@ -0,0 +1,93 @@
1
+ // Async facade over MemoryVolume. Returns Promises even though the
2
+ // underlying VFS is synchronous -- keeps the public API consistent.
3
+
4
+ import type { MemoryVolume } from "../memory-volume";
5
+ import type { StatResult } from "./types";
6
+
7
+ export class NodepodFS {
8
+ constructor(private _vol: MemoryVolume) {}
9
+
10
+ // Auto-creates parent dirs on write
11
+ async writeFile(path: string, data: string | Uint8Array): Promise<void> {
12
+ const dir = path.substring(0, path.lastIndexOf("/")) || "/";
13
+ if (dir !== "/" && !this._vol.existsSync(dir)) {
14
+ this._vol.mkdirSync(dir, { recursive: true });
15
+ }
16
+ this._vol.writeFileSync(path, data as any);
17
+ }
18
+
19
+ async readFile(path: string, encoding?: "utf-8" | "utf8"): Promise<string>;
20
+ async readFile(path: string): Promise<Uint8Array>;
21
+ async readFile(
22
+ path: string,
23
+ encoding?: string,
24
+ ): Promise<string | Uint8Array> {
25
+ if (encoding) return this._vol.readFileSync(path, "utf8") as string;
26
+ return this._vol.readFileSync(path) as any;
27
+ }
28
+
29
+ async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {
30
+ this._vol.mkdirSync(path, opts);
31
+ }
32
+
33
+ async readdir(path: string): Promise<string[]> {
34
+ return this._vol.readdirSync(path) as string[];
35
+ }
36
+
37
+ async exists(path: string): Promise<boolean> {
38
+ return this._vol.existsSync(path);
39
+ }
40
+
41
+ async stat(path: string): Promise<StatResult> {
42
+ const s = this._vol.statSync(path);
43
+ return {
44
+ isFile: s.isFile(),
45
+ isDirectory: s.isDirectory(),
46
+ size: s.size,
47
+ mtime: s.mtimeMs ?? Date.now(),
48
+ };
49
+ }
50
+
51
+ async unlink(path: string): Promise<void> {
52
+ this._vol.unlinkSync(path);
53
+ }
54
+
55
+ async rmdir(path: string, opts?: { recursive?: boolean }): Promise<void> {
56
+ if (opts?.recursive) {
57
+ this._removeRecursive(path);
58
+ } else {
59
+ this._vol.rmdirSync(path);
60
+ }
61
+ }
62
+
63
+ async rename(from: string, to: string): Promise<void> {
64
+ this._vol.renameSync(from, to);
65
+ }
66
+
67
+ watch(
68
+ path: string,
69
+ optionsOrCb?:
70
+ | { recursive?: boolean }
71
+ | ((event: string, filename: string | null) => void),
72
+ cb?: (event: string, filename: string | null) => void,
73
+ ): { close(): void } {
74
+ if (typeof optionsOrCb === "function") {
75
+ return this._vol.watch(path, optionsOrCb);
76
+ }
77
+ return this._vol.watch(path, optionsOrCb ?? {}, cb!);
78
+ }
79
+
80
+ get volume(): MemoryVolume {
81
+ return this._vol;
82
+ }
83
+
84
+ private _removeRecursive(dir: string): void {
85
+ for (const name of this._vol.readdirSync(dir) as string[]) {
86
+ const full = `${dir}/${name}`;
87
+ const st = this._vol.statSync(full);
88
+ if (st.isDirectory()) this._removeRecursive(full);
89
+ else this._vol.unlinkSync(full);
90
+ }
91
+ this._vol.rmdirSync(dir);
92
+ }
93
+ }
@@ -0,0 +1,86 @@
1
+ // Process handle returned by nodepod.spawn().
2
+ // Emits 'output', 'error', 'exit'. Has write() for stdin and kill() to abort.
3
+
4
+ import { EventEmitter } from "../polyfills/events";
5
+
6
+ export class NodepodProcess extends EventEmitter {
7
+ private _abortController = new AbortController();
8
+ private _resolve!: (r: {
9
+ stdout: string;
10
+ stderr: string;
11
+ exitCode: number;
12
+ }) => void;
13
+ private _stdout = "";
14
+ private _stderr = "";
15
+ private _exitCode: number | null = null;
16
+ private _sendStdinFn: ((data: string) => void) | null = null;
17
+ private _killFn: (() => void) | null = null;
18
+
19
+ // Resolves when the process exits -- use `await proc.completion`
20
+ readonly completion: Promise<{
21
+ stdout: string;
22
+ stderr: string;
23
+ exitCode: number;
24
+ }>;
25
+
26
+ constructor() {
27
+ super();
28
+ this.completion = new Promise((resolve) => {
29
+ this._resolve = resolve;
30
+ });
31
+ }
32
+
33
+ _setSendStdin(fn: (data: string) => void): void {
34
+ this._sendStdinFn = fn;
35
+ }
36
+
37
+ _setKillFn(fn: () => void): void {
38
+ this._killFn = fn;
39
+ }
40
+
41
+ _pushStdout(chunk: string): void {
42
+ this._stdout += chunk;
43
+ this.emit("output", chunk);
44
+ }
45
+
46
+ _pushStderr(chunk: string): void {
47
+ this._stderr += chunk;
48
+ this.emit("error", chunk);
49
+ }
50
+
51
+ // Idempotent -- safe to call twice
52
+ _finish(exitCode: number): void {
53
+ if (this._exitCode !== null) return;
54
+ this._exitCode = exitCode;
55
+ this.emit("exit", exitCode);
56
+ this._resolve({
57
+ stdout: this._stdout,
58
+ stderr: this._stderr,
59
+ exitCode,
60
+ });
61
+ }
62
+
63
+ get signal(): AbortSignal {
64
+ return this._abortController.signal;
65
+ }
66
+
67
+ get exited(): boolean {
68
+ return this._exitCode !== null;
69
+ }
70
+
71
+ write(data: string): void {
72
+ if (this._sendStdinFn) this._sendStdinFn(data);
73
+ }
74
+
75
+ kill(): void {
76
+ this._abortController.abort();
77
+ if (this._killFn) this._killFn();
78
+ }
79
+
80
+ on(event: "output", handler: (chunk: string) => void): this;
81
+ on(event: "error", handler: (chunk: string) => void): this;
82
+ on(event: "exit", handler: (code: number) => void): this;
83
+ on(event: string, handler: (...args: any[]) => void): this {
84
+ return super.on(event, handler) as this;
85
+ }
86
+ }
@@ -0,0 +1,350 @@
1
+ // Interactive terminal with line editing, history, raw/cooked mode, etc.
2
+ // xterm.js is a peer dep -- passed in via TerminalOptions, not imported here.
3
+
4
+ import type { TerminalOptions, TerminalTheme } from "./types";
5
+ import { DEFAULT_TERMINAL } from "../constants/config";
6
+
7
+ // GitHub Dark theme
8
+ const DEFAULT_THEME: TerminalTheme = {
9
+ background: "#0d1117",
10
+ foreground: "#c9d1d9",
11
+ cursor: "#58a6ff",
12
+ selectionBackground: "#264f78",
13
+ black: "#0d1117",
14
+ red: "#f85149",
15
+ green: "#3fb950",
16
+ yellow: "#d29922",
17
+ blue: "#58a6ff",
18
+ magenta: "#bc8cff",
19
+ cyan: "#39c5cf",
20
+ white: "#c9d1d9",
21
+ brightBlack: "#8b949e",
22
+ brightRed: "#f85149",
23
+ brightGreen: "#3fb950",
24
+ brightYellow: "#d29922",
25
+ brightBlue: "#58a6ff",
26
+ brightMagenta: "#bc8cff",
27
+ brightCyan: "#39c5cf",
28
+ brightWhite: "#ffffff",
29
+ };
30
+
31
+ const DEFAULT_PROMPT = (cwd: string) =>
32
+ `\x1b[36mnodepod\x1b[0m:\x1b[34m${cwd}\x1b[0m$ `;
33
+
34
+ // Wired by Nodepod.createTerminal()
35
+ export interface TerminalWiring {
36
+ onCommand: (cmd: string) => Promise<void>;
37
+ getSendStdin: () => ((data: string) => void) | null;
38
+ getIsStdinRaw: () => boolean;
39
+ getActiveAbort: () => AbortController | null;
40
+ setActiveAbort: (ac: AbortController | null) => void;
41
+ }
42
+
43
+ export class NodepodTerminal {
44
+ private _term: any = null;
45
+ private _fitAddon: any = null;
46
+ private _dataDisposable: any = null;
47
+ private _resizeHandler: (() => void) | null = null;
48
+
49
+ private _lineBuffer = "";
50
+ private _history: string[] = [];
51
+ private _historyIndex = -1;
52
+ private _savedLine = "";
53
+ private _running = false;
54
+ private _cwd = "/";
55
+
56
+ private _promptFn: (cwd: string) => string;
57
+ private _theme: TerminalTheme;
58
+ private _opts: TerminalOptions;
59
+ private _wiring: TerminalWiring | null = null;
60
+
61
+ constructor(opts: TerminalOptions) {
62
+ this._opts = opts;
63
+ this._theme = opts.theme ?? DEFAULT_THEME;
64
+ this._promptFn = opts.prompt ?? DEFAULT_PROMPT;
65
+ }
66
+
67
+ /* ---- Internal wiring ---- */
68
+
69
+ _wireExecution(wiring: TerminalWiring): void {
70
+ this._wiring = wiring;
71
+ }
72
+
73
+ _setRunning(running: boolean): void {
74
+ this._running = running;
75
+ }
76
+
77
+ _writePrompt(): void {
78
+ this._term?.write(this._promptFn(this._cwd));
79
+ }
80
+
81
+ _getCols(): number {
82
+ return this._term?.cols ?? 80;
83
+ }
84
+
85
+ _getRows(): number {
86
+ return this._term?.rows ?? 24;
87
+ }
88
+
89
+ _writeOutput(text: string, isError = false): void {
90
+ if (!this._term) return;
91
+ const escaped = text.replace(/\r?\n/g, "\r\n");
92
+ if (isError) {
93
+ this._term.write("\x1b[31m" + escaped + "\x1b[0m");
94
+ } else {
95
+ this._term.write(escaped);
96
+ }
97
+ }
98
+
99
+ /* ---- Public API ---- */
100
+
101
+ attach(target: HTMLElement | string): void {
102
+ const container =
103
+ typeof target === "string"
104
+ ? (document.querySelector(target) as HTMLElement)
105
+ : target;
106
+ if (!container) throw new Error(`Terminal target not found: ${target}`);
107
+
108
+ const TermCtor = this._opts.Terminal;
109
+
110
+ this._term = new TermCtor({
111
+ cursorBlink: true,
112
+ fontSize: this._opts.fontSize ?? DEFAULT_TERMINAL.FONT_SIZE,
113
+ fontFamily:
114
+ this._opts.fontFamily ??
115
+ '"Cascadia Code", "Fira Code", "Consolas", "Monaco", monospace',
116
+ theme: this._theme,
117
+ });
118
+
119
+ if (this._opts.FitAddon) {
120
+ this._fitAddon = new this._opts.FitAddon();
121
+ this._term.loadAddon(this._fitAddon);
122
+ }
123
+
124
+ this._term.open(container);
125
+
126
+ if (this._opts.WebglAddon) {
127
+ try {
128
+ this._term.loadAddon(new this._opts.WebglAddon());
129
+ } catch {
130
+ // canvas fallback is fine
131
+ }
132
+ }
133
+
134
+ if (this._fitAddon) {
135
+ // Defer fit() so the container has final layout dimensions,
136
+ // otherwise interactive CLIs get wrong cols/rows
137
+ const addon = this._fitAddon;
138
+ requestAnimationFrame(() => {
139
+ addon.fit();
140
+ setTimeout(() => addon.fit(), 100);
141
+ });
142
+ this._resizeHandler = () => this._fitAddon?.fit();
143
+ window.addEventListener("resize", this._resizeHandler);
144
+ }
145
+
146
+ this._dataDisposable = this._term.onData((data: string) =>
147
+ this._handleInput(data),
148
+ );
149
+
150
+ this._term.focus();
151
+ }
152
+
153
+ detach(): void {
154
+ if (this._dataDisposable) {
155
+ this._dataDisposable.dispose();
156
+ this._dataDisposable = null;
157
+ }
158
+ if (this._resizeHandler) {
159
+ window.removeEventListener("resize", this._resizeHandler);
160
+ this._resizeHandler = null;
161
+ }
162
+ if (this._term) {
163
+ this._term.dispose();
164
+ this._term = null;
165
+ }
166
+ this._fitAddon = null;
167
+ }
168
+
169
+ clear(): void {
170
+ if (!this._term) return;
171
+ this._term.clear();
172
+ if (!this._running) this._term.write(this._promptFn(this._cwd));
173
+ }
174
+
175
+ input(text: string): void {
176
+ if (!this._term) return;
177
+ for (const ch of text) {
178
+ this._handleInput(ch);
179
+ }
180
+ }
181
+
182
+ setTheme(theme: Partial<TerminalTheme>): void {
183
+ this._theme = { ...this._theme, ...theme };
184
+ if (this._term) this._term.options.theme = this._theme;
185
+ }
186
+
187
+ fit(): void {
188
+ this._fitAddon?.fit();
189
+ }
190
+
191
+ write(text: string): void {
192
+ this._term?.write(text);
193
+ }
194
+
195
+ writeln(text: string): void {
196
+ this._term?.writeln(text);
197
+ }
198
+
199
+ showPrompt(): void {
200
+ this._term?.write(this._promptFn(this._cwd));
201
+ }
202
+
203
+ setCwd(cwd: string): void {
204
+ this._cwd = cwd;
205
+ }
206
+
207
+ getCwd(): string {
208
+ return this._cwd;
209
+ }
210
+
211
+ get xterm(): any {
212
+ return this._term;
213
+ }
214
+
215
+ /* ---- Input handling ---- */
216
+
217
+ private _handleInput(data: string): void {
218
+ if (!this._term) return;
219
+
220
+ if (this._running) {
221
+ // Ctrl+C
222
+ if (data.includes("\x03")) {
223
+ const abort = this._wiring?.getActiveAbort();
224
+ if (abort) {
225
+ abort.abort();
226
+ // Don't clear activeAbort -- nodepod.ts checks it to skip duplicate prompt
227
+ }
228
+ this._term.write("^C\r\n");
229
+ this._running = false;
230
+ this._writePrompt();
231
+ return;
232
+ }
233
+
234
+ const isRaw = this._wiring?.getIsStdinRaw() ?? false;
235
+ const sendStdin = this._wiring?.getSendStdin();
236
+
237
+ if (isRaw && sendStdin) {
238
+ sendStdin(data);
239
+ } else if (sendStdin) {
240
+ // Cooked mode: local echo + line buffering
241
+ for (let i = 0; i < data.length; i++) {
242
+ const ch = data[i];
243
+ const code = ch.charCodeAt(0);
244
+ if (ch === "\r" || ch === "\n") {
245
+ this._term.write("\r\n");
246
+ sendStdin("\n");
247
+ } else if (code === 127 || code === 8) {
248
+ this._term.write("\b \b");
249
+ sendStdin("\x7f");
250
+ } else if (code >= 32) {
251
+ this._term.write(ch);
252
+ sendStdin(ch);
253
+ } else {
254
+ // Control chars -- send remainder as-is
255
+ sendStdin(data.slice(i));
256
+ break;
257
+ }
258
+ }
259
+ }
260
+ return;
261
+ }
262
+
263
+ // Line editing mode
264
+ for (let i = 0; i < data.length; i++) {
265
+ const ch = data[i];
266
+ const code = ch.charCodeAt(0);
267
+
268
+ if (ch === "\r" || ch === "\n") {
269
+ const cmd = this._lineBuffer;
270
+ this._lineBuffer = "";
271
+ this._historyIndex = -1;
272
+ this._executeCommand(cmd);
273
+ } else if (code === 127 || code === 8) {
274
+ if (this._lineBuffer.length > 0) {
275
+ this._lineBuffer = this._lineBuffer.slice(0, -1);
276
+ this._term.write("\b \b");
277
+ }
278
+ } else if (code === 3) {
279
+ this._lineBuffer = "";
280
+ this._term.write("^C");
281
+ this._writePrompt();
282
+ } else if (code === 12) {
283
+ this._term.clear();
284
+ this._term.write(this._promptFn(this._cwd) + this._lineBuffer);
285
+ } else if (ch === "\x1b" && i + 2 < data.length && data[i + 1] === "[") {
286
+ const arrow = data[i + 2];
287
+ i += 2;
288
+ if (arrow === "A") this._historyUp();
289
+ else if (arrow === "B") this._historyDown();
290
+ } else if (code >= 32) {
291
+ this._lineBuffer += ch;
292
+ this._term.write(ch);
293
+ }
294
+ }
295
+ }
296
+
297
+ /* ---- History navigation ---- */
298
+
299
+ private _historyUp(): void {
300
+ if (this._history.length === 0) return;
301
+ if (this._historyIndex === -1) {
302
+ this._savedLine = this._lineBuffer;
303
+ this._historyIndex = this._history.length - 1;
304
+ } else if (this._historyIndex > 0) {
305
+ this._historyIndex--;
306
+ } else {
307
+ return;
308
+ }
309
+ this._replaceLineWith(this._history[this._historyIndex]);
310
+ }
311
+
312
+ private _historyDown(): void {
313
+ if (this._historyIndex === -1) return;
314
+ if (this._historyIndex < this._history.length - 1) {
315
+ this._historyIndex++;
316
+ this._replaceLineWith(this._history[this._historyIndex]);
317
+ } else {
318
+ this._historyIndex = -1;
319
+ this._replaceLineWith(this._savedLine);
320
+ }
321
+ }
322
+
323
+ private _replaceLineWith(text: string): void {
324
+ const prompt = this._promptFn(this._cwd);
325
+ this._term.write(
326
+ "\r" + prompt + " ".repeat(this._lineBuffer.length) + "\r" + prompt,
327
+ );
328
+ this._lineBuffer = text;
329
+ this._term.write(text);
330
+ }
331
+
332
+ /* ---- Command execution ---- */
333
+
334
+ private async _executeCommand(cmd: string): Promise<void> {
335
+ if (!cmd.trim()) {
336
+ this._term?.write("\r\n" + this._promptFn(this._cwd));
337
+ return;
338
+ }
339
+ this._history.push(cmd);
340
+ this._historyIndex = -1;
341
+ this._running = true;
342
+
343
+ if (this._wiring?.onCommand) {
344
+ await this._wiring.onCommand(cmd);
345
+ } else {
346
+ this._running = false;
347
+ this._writePrompt();
348
+ }
349
+ }
350
+ }