typescript-virtual-container 1.1.3 → 1.1.4

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 (38) hide show
  1. package/dist/SSHMimic/exec.d.ts.map +1 -1
  2. package/dist/SSHMimic/exec.js +8 -2
  3. package/dist/SSHMimic/index.d.ts +1 -0
  4. package/dist/SSHMimic/index.d.ts.map +1 -1
  5. package/dist/SSHMimic/index.js +9 -3
  6. package/dist/SSHMimic/sftp.d.ts +46 -0
  7. package/dist/SSHMimic/sftp.d.ts.map +1 -0
  8. package/dist/SSHMimic/sftp.js +576 -0
  9. package/dist/VirtualFileSystem/index.d.ts +6 -4
  10. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  11. package/dist/VirtualFileSystem/index.js +144 -153
  12. package/dist/VirtualShell/index.d.ts +6 -0
  13. package/dist/VirtualShell/index.d.ts.map +1 -1
  14. package/dist/VirtualShell/index.js +16 -4
  15. package/dist/VirtualShell/shell.d.ts.map +1 -1
  16. package/dist/VirtualShell/shell.js +7 -0
  17. package/dist/VirtualUserManager/index.d.ts +1 -0
  18. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  19. package/dist/VirtualUserManager/index.js +15 -0
  20. package/dist/commands/exit.d.ts.map +1 -1
  21. package/dist/commands/exit.js +1 -0
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -2
  25. package/dist/standalone.js +10 -1
  26. package/package.json +1 -1
  27. package/src/SSHMimic/exec.ts +18 -12
  28. package/src/SSHMimic/index.ts +16 -7
  29. package/src/SSHMimic/sftp.ts +833 -0
  30. package/src/VirtualFileSystem/index.ts +158 -188
  31. package/src/VirtualShell/index.ts +19 -8
  32. package/src/VirtualShell/shell.ts +7 -0
  33. package/src/VirtualUserManager/index.ts +20 -0
  34. package/src/commands/exit.ts +1 -0
  35. package/src/index.ts +2 -1
  36. package/src/standalone.ts +11 -1
  37. package/tests/sftp.test.ts +319 -0
  38. package/tests/ssh-exec.test.ts +45 -0
@@ -1,10 +1,7 @@
1
- import { promises as fs } from "node:fs";
1
+ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { gunzipSync, gzipSync } from "node:zlib";
4
- import { archiveExists, createTarBuffer, readSnapshotFromTar } from "./archive";
5
- import { getNode, getParentDirectory, normalizePath, splitPath } from "./path";
6
- import { applySnapshot, createSnapshot } from "./snapshot";
7
- import { renderTree } from "./tree";
4
+ import { normalizePath } from "./path";
8
5
  /**
9
6
  * In-memory virtual filesystem with tar.gz mirror persistence.
10
7
  *
@@ -13,35 +10,72 @@ import { renderTree } from "./tree";
13
10
  * {@link VirtualFileSystem.flushMirror} to persist pending changes.
14
11
  */
15
12
  class VirtualFileSystem {
16
- root;
17
- archivePath;
18
- dirty = false;
19
- computeNodeUsageBytes(node) {
20
- if (node.type === "file") {
21
- return node.content.length;
13
+ mirrorRoot;
14
+ ensureMirrorRoot() {
15
+ fs.mkdirSync(this.mirrorRoot, { recursive: true, mode: 0o755 });
16
+ }
17
+ resolveFsPath(targetPath) {
18
+ const normalized = normalizePath(targetPath);
19
+ const relativePath = normalized.slice(1);
20
+ const resolved = path.resolve(this.mirrorRoot, relativePath || ".");
21
+ const relative = path.relative(this.mirrorRoot, resolved);
22
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
23
+ throw new Error(`Invalid path '${targetPath}'.`);
24
+ }
25
+ return resolved;
26
+ }
27
+ detectGzipFile(targetPath) {
28
+ const fd = fs.openSync(targetPath, "r");
29
+ try {
30
+ const header = Buffer.alloc(2);
31
+ const bytesRead = fs.readSync(fd, header, 0, 2, 0);
32
+ return bytesRead === 2 && header[0] === 0x1f && header[1] === 0x8b;
33
+ }
34
+ finally {
35
+ fs.closeSync(fd);
36
+ }
37
+ }
38
+ computeDiskUsageBytes(targetPath) {
39
+ const stats = fs.statSync(targetPath);
40
+ if (stats.isFile()) {
41
+ return stats.size;
22
42
  }
23
43
  let total = 0;
24
- for (const child of node.children.values()) {
25
- total += this.computeNodeUsageBytes(child);
44
+ for (const entry of fs.readdirSync(targetPath)) {
45
+ total += this.computeDiskUsageBytes(path.join(targetPath, entry));
26
46
  }
27
47
  return total;
28
48
  }
49
+ renderTreeLines(targetPath, label) {
50
+ const lines = [label];
51
+ const walk = (currentPath, prefix) => {
52
+ const entries = fs
53
+ .readdirSync(currentPath, { withFileTypes: true })
54
+ .map((entry) => entry.name)
55
+ .sort((left, right) => left.localeCompare(right));
56
+ for (let i = 0; i < entries.length; i += 1) {
57
+ const name = entries[i];
58
+ const isLast = i === entries.length - 1;
59
+ const connector = isLast ? "└── " : "├── ";
60
+ const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
61
+ const entryPath = path.join(currentPath, name);
62
+ const isDirectory = fs.statSync(entryPath).isDirectory();
63
+ lines.push(`${prefix}${connector}${name}`);
64
+ if (isDirectory) {
65
+ walk(entryPath, nextPrefix);
66
+ }
67
+ }
68
+ };
69
+ walk(targetPath, "");
70
+ return lines.join("\n");
71
+ }
29
72
  /**
30
73
  * Creates a virtual filesystem instance.
31
74
  *
32
75
  * @param baseDir Base directory used to resolve mirror archive location.
33
76
  */
34
77
  constructor(baseDir = process.cwd()) {
35
- const now = new Date();
36
- this.archivePath = path.resolve(baseDir, ".vfs", "mirror.tar.gz");
37
- this.root = {
38
- type: "directory",
39
- name: "",
40
- mode: 0o755,
41
- createdAt: now,
42
- updatedAt: now,
43
- children: new Map(),
44
- };
78
+ this.mirrorRoot = path.resolve(baseDir, ".vfs", "mirror");
45
79
  }
46
80
  /**
47
81
  * Restores filesystem state from mirror archive.
@@ -49,19 +83,7 @@ class VirtualFileSystem {
49
83
  * If archive does not exist or cannot be read, creates fresh mirror file.
50
84
  */
51
85
  async restoreMirror() {
52
- await fs.mkdir(path.dirname(this.archivePath), { recursive: true });
53
- try {
54
- const compressed = await fs.readFile(this.archivePath);
55
- const tarBuffer = gunzipSync(compressed);
56
- const snapshot = await readSnapshotFromTar(tarBuffer);
57
- applySnapshot(this.root, snapshot);
58
- this.dirty = false;
59
- return;
60
- }
61
- catch {
62
- console.warn(`No valid mirror archive found at '${this.archivePath}'. Starting with empty filesystem.`);
63
- await this.flushMirror();
64
- }
86
+ this.ensureMirrorRoot();
65
87
  }
66
88
  /**
67
89
  * Persists current filesystem state to mirror archive.
@@ -69,15 +91,7 @@ class VirtualFileSystem {
69
91
  * No-op when nothing changed and archive already exists.
70
92
  */
71
93
  async flushMirror() {
72
- if (!this.dirty && (await archiveExists(this.archivePath))) {
73
- return;
74
- }
75
- await fs.mkdir(path.dirname(this.archivePath), { recursive: true });
76
- const snapshotJson = JSON.stringify(createSnapshot(this.root), null, 2);
77
- const tarBuffer = await createTarBuffer(snapshotJson);
78
- const compressed = gzipSync(tarBuffer);
79
- await fs.writeFile(this.archivePath, compressed);
80
- this.dirty = false;
94
+ this.ensureMirrorRoot();
81
95
  }
82
96
  /**
83
97
  * Creates directory and any missing parent directories.
@@ -86,32 +100,12 @@ class VirtualFileSystem {
86
100
  * @param mode POSIX-like mode bits for new directories.
87
101
  */
88
102
  mkdir(targetPath, mode = 0o755) {
89
- const normalized = normalizePath(targetPath);
90
- const parts = splitPath(normalized);
91
- let current = this.root;
92
- for (const part of parts) {
93
- const existing = current.children.get(part);
94
- if (!existing) {
95
- const now = new Date();
96
- const nextDir = {
97
- type: "directory",
98
- name: part,
99
- mode,
100
- createdAt: now,
101
- updatedAt: now,
102
- children: new Map(),
103
- };
104
- current.children.set(part, nextDir);
105
- current.updatedAt = now;
106
- this.dirty = true;
107
- current = nextDir;
108
- continue;
109
- }
110
- if (existing.type !== "directory") {
111
- throw new Error(`Cannot create directory '${normalized}': '${part}' is a file.`);
112
- }
113
- current = existing;
103
+ this.ensureMirrorRoot();
104
+ const fsPath = this.resolveFsPath(targetPath);
105
+ if (fs.existsSync(fsPath) && !fs.statSync(fsPath).isDirectory()) {
106
+ throw new Error(`Cannot create directory '${normalizePath(targetPath)}': path is a file.`);
114
107
  }
108
+ fs.mkdirSync(fsPath, { recursive: true, mode });
115
109
  }
116
110
  /**
117
111
  * Writes UTF-8 text or binary content into file.
@@ -123,31 +117,21 @@ class VirtualFileSystem {
123
117
  * @param options Optional write behavior (mode, compression).
124
118
  */
125
119
  writeFile(targetPath, content, options = {}) {
120
+ this.ensureMirrorRoot();
126
121
  const normalized = normalizePath(targetPath);
127
- const { parent, name } = getParentDirectory(this.root, normalized, true, (pathToCreate) => this.mkdir(pathToCreate));
128
- const now = new Date();
122
+ const fsPath = this.resolveFsPath(normalized);
123
+ const parentPath = path.dirname(fsPath);
124
+ fs.mkdirSync(parentPath, { recursive: true, mode: 0o755 });
129
125
  const rawContent = Buffer.isBuffer(content)
130
126
  ? content
131
127
  : Buffer.from(content, "utf8");
132
128
  const shouldCompress = options.compress ?? false;
133
129
  const storedContent = shouldCompress ? gzipSync(rawContent) : rawContent;
134
- const existing = parent.children.get(name);
135
- if (existing && existing.type === "directory") {
130
+ if (fs.existsSync(fsPath) && fs.statSync(fsPath).isDirectory()) {
136
131
  throw new Error(`Cannot write file '${normalized}': path is a directory.`);
137
132
  }
138
- const createdAt = existing?.type === "file" ? existing.createdAt : now;
139
- const mode = options.mode ?? (existing?.type === "file" ? existing.mode : 0o644);
140
- parent.children.set(name, {
141
- type: "file",
142
- name,
143
- mode,
144
- createdAt,
145
- updatedAt: now,
146
- content: storedContent,
147
- compressed: shouldCompress,
148
- });
149
- parent.updatedAt = now;
150
- this.dirty = true;
133
+ fs.writeFileSync(fsPath, storedContent);
134
+ fs.chmodSync(fsPath, options.mode ?? 0o644);
151
135
  }
152
136
  /**
153
137
  * Reads file content as UTF-8 text.
@@ -158,11 +142,13 @@ class VirtualFileSystem {
158
142
  * @returns UTF-8 string content.
159
143
  */
160
144
  readFile(targetPath) {
161
- const node = getNode(this.root, targetPath);
162
- if (node.type !== "file") {
145
+ this.ensureMirrorRoot();
146
+ const fsPath = this.resolveFsPath(targetPath);
147
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
163
148
  throw new Error(`Cannot read '${targetPath}': not a file.`);
164
149
  }
165
- const raw = node.compressed ? gunzipSync(node.content) : node.content;
150
+ const stored = fs.readFileSync(fsPath);
151
+ const raw = this.detectGzipFile(fsPath) ? gunzipSync(stored) : stored;
166
152
  return raw.toString("utf8");
167
153
  }
168
154
  /**
@@ -173,8 +159,8 @@ class VirtualFileSystem {
173
159
  */
174
160
  exists(targetPath) {
175
161
  try {
176
- getNode(this.root, targetPath);
177
- return true;
162
+ const fsPath = this.resolveFsPath(targetPath);
163
+ return fs.existsSync(fsPath);
178
164
  }
179
165
  catch {
180
166
  return false;
@@ -187,10 +173,11 @@ class VirtualFileSystem {
187
173
  * @param mode New POSIX-like mode.
188
174
  */
189
175
  chmod(targetPath, mode) {
190
- const node = getNode(this.root, targetPath);
191
- node.mode = mode;
192
- node.updatedAt = new Date();
193
- this.dirty = true;
176
+ const fsPath = this.resolveFsPath(targetPath);
177
+ if (!fs.existsSync(fsPath)) {
178
+ throw new Error(`Path '${normalizePath(targetPath)}' does not exist.`);
179
+ }
180
+ fs.chmodSync(fsPath, mode);
194
181
  }
195
182
  /**
196
183
  * Returns metadata for file or directory.
@@ -199,28 +186,35 @@ class VirtualFileSystem {
199
186
  * @returns Typed stat object based on node type.
200
187
  */
201
188
  stat(targetPath) {
189
+ this.ensureMirrorRoot();
202
190
  const normalized = normalizePath(targetPath);
203
- const node = getNode(this.root, normalized);
204
- if (node.type === "file") {
191
+ const fsPath = this.resolveFsPath(normalized);
192
+ if (!fs.existsSync(fsPath)) {
193
+ throw new Error(`Path '${normalized}' does not exist.`);
194
+ }
195
+ const stats = fs.statSync(fsPath);
196
+ const mode = stats.mode & 0o777;
197
+ const name = normalized === "/" ? "" : path.posix.basename(normalized);
198
+ if (stats.isFile()) {
205
199
  return {
206
200
  type: "file",
207
- name: node.name,
201
+ name,
208
202
  path: normalized,
209
- mode: node.mode,
210
- createdAt: node.createdAt,
211
- updatedAt: node.updatedAt,
212
- compressed: node.compressed,
213
- size: node.content.length,
203
+ mode,
204
+ createdAt: stats.birthtime,
205
+ updatedAt: stats.mtime,
206
+ compressed: this.detectGzipFile(fsPath),
207
+ size: stats.size,
214
208
  };
215
209
  }
216
210
  return {
217
211
  type: "directory",
218
- name: node.name,
212
+ name,
219
213
  path: normalized,
220
- mode: node.mode,
221
- createdAt: node.createdAt,
222
- updatedAt: node.updatedAt,
223
- childrenCount: node.children.size,
214
+ mode,
215
+ createdAt: stats.birthtime,
216
+ updatedAt: stats.mtime,
217
+ childrenCount: fs.readdirSync(fsPath).length,
224
218
  };
225
219
  }
226
220
  /**
@@ -230,11 +224,11 @@ class VirtualFileSystem {
230
224
  * @returns Sorted child names.
231
225
  */
232
226
  list(dirPath = "/") {
233
- const node = getNode(this.root, dirPath);
234
- if (node.type !== "directory") {
227
+ const fsPath = this.resolveFsPath(dirPath);
228
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isDirectory()) {
235
229
  throw new Error(`Cannot list '${dirPath}': not a directory.`);
236
230
  }
237
- return Array.from(node.children.keys()).sort();
231
+ return fs.readdirSync(fsPath).sort();
238
232
  }
239
233
  /**
240
234
  * Renders ASCII tree view of directory hierarchy.
@@ -243,12 +237,12 @@ class VirtualFileSystem {
243
237
  * @returns Multi-line tree string.
244
238
  */
245
239
  tree(dirPath = "/") {
246
- const node = getNode(this.root, dirPath);
247
- if (node.type !== "directory") {
240
+ const fsPath = this.resolveFsPath(dirPath);
241
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isDirectory()) {
248
242
  throw new Error(`Cannot render tree for '${dirPath}': not a directory.`);
249
243
  }
250
244
  const rootLabel = dirPath === "/" ? "/" : path.posix.basename(normalizePath(dirPath));
251
- return renderTree(node, rootLabel);
245
+ return this.renderTreeLines(fsPath, rootLabel);
252
246
  }
253
247
  /**
254
248
  * Computes total stored file bytes under a path.
@@ -260,8 +254,11 @@ class VirtualFileSystem {
260
254
  * @returns Total byte usage for file content under target path.
261
255
  */
262
256
  getUsageBytes(targetPath = "/") {
263
- const node = getNode(this.root, targetPath);
264
- return this.computeNodeUsageBytes(node);
257
+ const fsPath = this.resolveFsPath(targetPath);
258
+ if (!fs.existsSync(fsPath)) {
259
+ throw new Error(`Path '${normalizePath(targetPath)}' does not exist.`);
260
+ }
261
+ return this.computeDiskUsageBytes(fsPath);
265
262
  }
266
263
  /**
267
264
  * Compresses file content with gzip and flags node as compressed.
@@ -269,15 +266,13 @@ class VirtualFileSystem {
269
266
  * @param targetPath Path to file.
270
267
  */
271
268
  compressFile(targetPath) {
272
- const node = getNode(this.root, targetPath);
273
- if (node.type !== "file") {
269
+ const fsPath = this.resolveFsPath(targetPath);
270
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
274
271
  throw new Error(`Cannot compress '${targetPath}': not a file.`);
275
272
  }
276
- if (!node.compressed) {
277
- node.content = gzipSync(node.content);
278
- node.compressed = true;
279
- node.updatedAt = new Date();
280
- this.dirty = true;
273
+ if (!this.detectGzipFile(fsPath)) {
274
+ const content = fs.readFileSync(fsPath);
275
+ fs.writeFileSync(fsPath, gzipSync(content));
281
276
  }
282
277
  }
283
278
  /**
@@ -286,15 +281,13 @@ class VirtualFileSystem {
286
281
  * @param targetPath Path to file.
287
282
  */
288
283
  decompressFile(targetPath) {
289
- const node = getNode(this.root, targetPath);
290
- if (node.type !== "file") {
284
+ const fsPath = this.resolveFsPath(targetPath);
285
+ if (!fs.existsSync(fsPath) || !fs.statSync(fsPath).isFile()) {
291
286
  throw new Error(`Cannot decompress '${targetPath}': not a file.`);
292
287
  }
293
- if (node.compressed) {
294
- node.content = gunzipSync(node.content);
295
- node.compressed = false;
296
- node.updatedAt = new Date();
297
- this.dirty = true;
288
+ if (this.detectGzipFile(fsPath)) {
289
+ const content = fs.readFileSync(fsPath);
290
+ fs.writeFileSync(fsPath, gunzipSync(content));
298
291
  }
299
292
  }
300
293
  /**
@@ -308,19 +301,23 @@ class VirtualFileSystem {
308
301
  if (normalized === "/") {
309
302
  throw new Error("Cannot remove root directory.");
310
303
  }
311
- const { parent, name } = getParentDirectory(this.root, normalized, false, () => undefined);
312
- const node = parent.children.get(name);
313
- if (!node) {
304
+ const fsPath = this.resolveFsPath(normalized);
305
+ if (!fs.existsSync(fsPath)) {
314
306
  throw new Error(`Path '${normalized}' does not exist.`);
315
307
  }
316
- if (node.type === "directory" &&
317
- node.children.size > 0 &&
318
- !options.recursive) {
319
- throw new Error(`Directory '${normalized}' is not empty. Use recursive option.`);
308
+ const stats = fs.statSync(fsPath);
309
+ if (stats.isDirectory() && !options.recursive) {
310
+ const entries = fs.readdirSync(fsPath);
311
+ if (entries.length > 0) {
312
+ throw new Error(`Directory '${normalized}' is not empty. Use recursive option.`);
313
+ }
314
+ }
315
+ if (stats.isDirectory()) {
316
+ fs.rmSync(fsPath, { recursive: options.recursive ?? false });
317
+ }
318
+ else {
319
+ fs.rmSync(fsPath);
320
320
  }
321
- parent.children.delete(name);
322
- parent.updatedAt = new Date();
323
- this.dirty = true;
324
321
  }
325
322
  /**
326
323
  * Moves or renames node to destination path.
@@ -334,22 +331,16 @@ class VirtualFileSystem {
334
331
  if (fromNormalized === "/" || toNormalized === "/") {
335
332
  throw new Error("Cannot move root directory.");
336
333
  }
337
- const { parent: fromParent, name: fromName } = getParentDirectory(this.root, fromNormalized, false, () => undefined);
338
- const node = fromParent.children.get(fromName);
339
- if (!node) {
334
+ const fromFsPath = this.resolveFsPath(fromNormalized);
335
+ const toFsPath = this.resolveFsPath(toNormalized);
336
+ if (!fs.existsSync(fromFsPath)) {
340
337
  throw new Error(`Path '${fromNormalized}' does not exist.`);
341
338
  }
342
- const { parent: toParent, name: toName } = getParentDirectory(this.root, toNormalized, true, (pathToCreate) => this.mkdir(pathToCreate));
343
- if (toParent.children.has(toName)) {
339
+ if (fs.existsSync(toFsPath)) {
344
340
  throw new Error(`Destination '${toNormalized}' already exists.`);
345
341
  }
346
- fromParent.children.delete(fromName);
347
- node.name = toName;
348
- node.updatedAt = new Date();
349
- toParent.children.set(toName, node);
350
- fromParent.updatedAt = new Date();
351
- toParent.updatedAt = new Date();
352
- this.dirty = true;
342
+ fs.mkdirSync(path.dirname(toFsPath), { recursive: true, mode: 0o755 });
343
+ fs.renameSync(fromFsPath, toFsPath);
353
344
  }
354
345
  }
355
346
  export default VirtualFileSystem;
@@ -19,6 +19,7 @@ declare class VirtualShell {
19
19
  users: VirtualUserManager;
20
20
  hostname: string;
21
21
  properties: ShellProperties;
22
+ private initialized;
22
23
  /**
23
24
  * Creates a new virtual shell instance.
24
25
  *
@@ -27,6 +28,11 @@ declare class VirtualShell {
27
28
  * @param basePath Optional base path for the virtual filesystem (defaults to process.cwd()).
28
29
  */
29
30
  constructor(hostname: string, properties?: ShellProperties, basePath?: string);
31
+ /**
32
+ * Ensures initialization is complete before allowing operations.
33
+ * Call this before any authentication or command execution.
34
+ */
35
+ ensureInitialized(): Promise<void>;
30
36
  /**
31
37
  * Registers a new command in the shell runtime.
32
38
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;GAKG;AACH,cAAM,YAAY;IACjB,QAAQ,EAAE,MAAM,CAAO;IACvB,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;IAE5B;;;;;;OAMG;gBAEF,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,eAAe,EAC5B,QAAQ,CAAC,EAAE,MAAM;IAqBlB;;;;;;OAMG;IACH,UAAU,CACT,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GACvE,IAAI;IASP;;;;;;OAMG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAIrE;;;;;;;OAOG;IAEH,uBAAuB,CACtB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC1C,IAAI;IAcP;;;;OAIG;IACI,MAAM,IAAI,iBAAiB,GAAG,IAAI;IAIzC;;;;OAIG;IACI,QAAQ,IAAI,kBAAkB,GAAG,IAAI;IAI5C;;;;OAIG;IACI,WAAW,IAAI,MAAM;IAI5B;;;;;;OAMG;IACI,eAAe,CACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,GACtB,IAAI;CAIP;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;GAKG;AACH,cAAM,YAAY;IACjB,QAAQ,EAAE,MAAM,CAAO;IACvB,GAAG,EAAE,iBAAiB,CAAC;IACvB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,CAAC,WAAW,CAAgB;IAEnC;;;;;;OAMG;gBAEF,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,eAAe,EAC5B,QAAQ,CAAC,EAAE,MAAM;IAuBlB;;;OAGG;IACU,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/C;;;;;;OAMG;IACH,UAAU,CACT,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,GACvE,IAAI;IASP;;;;;;OAMG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAIrE;;;;;;;OAOG;IAEH,uBAAuB,CACtB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC1C,IAAI;IAcP;;;;OAIG;IACI,MAAM,IAAI,iBAAiB,GAAG,IAAI;IAIzC;;;;OAIG;IACI,QAAQ,IAAI,kBAAkB,GAAG,IAAI;IAI5C;;;;OAIG;IACI,WAAW,IAAI,MAAM;IAI5B;;;;;;OAMG;IACI,eAAe,CACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,GACtB,IAAI;CAIP;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -36,6 +36,7 @@ class VirtualShell {
36
36
  users;
37
37
  hostname;
38
38
  properties;
39
+ initialized;
39
40
  /**
40
41
  * Creates a new virtual shell instance.
41
42
  *
@@ -49,10 +50,21 @@ class VirtualShell {
49
50
  this.basePath = basePath || ".";
50
51
  this.vfs = new VirtualFileSystem(this.basePath);
51
52
  this.users = new VirtualUserManager(this.vfs, resolveRootPassword(), resolveAutoSudoForNewUsers());
52
- this.vfs.restoreMirror().then(() => {
53
- this.users = new VirtualUserManager(this.vfs, resolveRootPassword(), resolveAutoSudoForNewUsers());
54
- this.users.initialize();
55
- });
53
+ // Store references to avoid TypeScript "used before assigned" errors
54
+ const vfs = this.vfs;
55
+ const users = this.users;
56
+ // Initialize both VFS mirror and users, ensuring all is ready before auth
57
+ this.initialized = (async () => {
58
+ await vfs.restoreMirror();
59
+ await users.initialize();
60
+ })();
61
+ }
62
+ /**
63
+ * Ensures initialization is complete before allowing operations.
64
+ * Call this before any authentication or command execution.
65
+ */
66
+ async ensureInitialized() {
67
+ await this.initialized;
56
68
  }
57
69
  /**
58
70
  * Registers a new command in the shell runtime.
@@ -1 +1 @@
1
- {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,CAulBN"}
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAmBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,CA8lBN"}
@@ -312,6 +312,13 @@ export function startShell(properties, stream, authUser, hostname, sessionId, re
312
312
  for (let i = 0; i < input.length; i += 1) {
313
313
  const ch = input[i];
314
314
  if (ch === "\u0004") {
315
+ lineBuffer = "";
316
+ cursorPos = 0;
317
+ historyIndex = null;
318
+ historyDraft = "";
319
+ stream.write("bye\r\n");
320
+ pushHistory("bye");
321
+ await shell.vfs.flushMirror();
315
322
  stream.write("logout\r\n");
316
323
  stream.exit(0);
317
324
  stream.end();
@@ -49,6 +49,7 @@ export declare class VirtualUserManager {
49
49
  constructor(vfs: VirtualFileSystem, defaultRootPassword?: string, autoSudoForNewUsers?: boolean);
50
50
  /**
51
51
  * Loads users/sudoers from disk and ensures root account exists.
52
+ * Also creates the current system user if not already present.
52
53
  */
53
54
  initialize(): Promise<void>;
54
55
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}
@@ -32,6 +32,7 @@ export class VirtualUserManager {
32
32
  }
33
33
  /**
34
34
  * Loads users/sudoers from disk and ensures root account exists.
35
+ * Also creates the current system user if not already present.
35
36
  */
36
37
  async initialize() {
37
38
  this.loadFromVfs();
@@ -39,6 +40,20 @@ export class VirtualUserManager {
39
40
  this.loadQuotasFromVfs();
40
41
  this.users.set("root", this.createRecord("root", this.defaultRootPassword));
41
42
  this.sudoers.add("root");
43
+ // Auto-create current system user for easier authentication
44
+ const currentUser = process.env.USER || process.env.USERNAME;
45
+ if (currentUser && currentUser !== "root" && !this.users.has(currentUser)) {
46
+ // Use same password as root for convenience, or a generic default
47
+ const userPassword = this.defaultRootPassword;
48
+ this.users.set(currentUser, this.createRecord(currentUser, userPassword));
49
+ this.sudoers.add(currentUser);
50
+ // Create home directory for the system user
51
+ const homePath = `/home/${currentUser}`;
52
+ if (!this.vfs.exists(homePath)) {
53
+ this.vfs.mkdir(homePath, 0o755);
54
+ this.vfs.writeFile(`${homePath}/README.txt`, `Welcome to the virtual environment, ${currentUser}`);
55
+ }
56
+ }
42
57
  await this.persist();
43
58
  }
44
59
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"exit.d.ts","sourceRoot":"","sources":["../../src/commands/exit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,WAAW,EAAE,WAIzB,CAAC"}
1
+ {"version":3,"file":"exit.d.ts","sourceRoot":"","sources":["../../src/commands/exit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,WAAW,EAAE,WAKzB,CAAC"}
@@ -1,5 +1,6 @@
1
1
  export const exitCommand = {
2
2
  name: "exit",
3
+ aliases: ["bye"],
3
4
  params: [],
4
5
  run: () => ({ closeSession: true, exitCode: 0 }),
5
6
  };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { SshClient } from "./SSHClient";
2
- import { SshMimic } from "./SSHMimic/index";
2
+ import { SftpMimic, SshMimic } from "./SSHMimic/index";
3
3
  import VirtualFileSystem from "./VirtualFileSystem";
4
4
  import { VirtualShell } from "./VirtualShell";
5
5
  import { VirtualUserManager } from "./VirtualUserManager";
6
6
  export type { CommandContext, CommandMode, CommandOutcome, CommandResult, NanoEditorSession, ShellModule, SudoChallenge, } from "./types/commands";
7
7
  export type { ExecStream, ShellStream } from "./types/streams";
8
8
  export type { RemoveOptions, VfsBaseNode, VfsDirectoryNode, VfsFileNode, VfsNodeStats, VfsNodeType, VfsSnapshot, VfsSnapshotBaseNode, VfsSnapshotDirectoryNode, VfsSnapshotFileNode, VfsSnapshotNode, WriteFileOptions, } from "./types/vfs";
9
- export { SshClient, VirtualFileSystem, VirtualShell, SshMimic as VirtualSshServer, VirtualUserManager, };
9
+ export { SshClient, VirtualFileSystem, SftpMimic as VirtualSftpServer, VirtualShell, SshMimic as VirtualSshServer, VirtualUserManager, };
10
10
  export { getArg, getFlag, ifFlag, } from "./commands/command-helpers";
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,YAAY,EACX,cAAc,EACd,WAAW,EACX,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,aAAa,GACb,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC/D,YAAY,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,GAChB,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,QAAQ,IAAI,gBAAgB,EAC5B,kBAAkB,GAClB,CAAC;AAEF,OAAO,EACN,MAAM,EACN,OAAO,EACP,MAAM,GACN,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,YAAY,EACX,cAAc,EACd,WAAW,EACX,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,aAAa,GACb,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC/D,YAAY,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,GAChB,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,SAAS,EACT,iBAAiB,EACjB,SAAS,IAAI,iBAAiB,EAC9B,YAAY,EACZ,QAAQ,IAAI,gBAAgB,EAC5B,kBAAkB,GAClB,CAAC;AAEF,OAAO,EACN,MAAM,EACN,OAAO,EACP,MAAM,GACN,MAAM,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SshClient } from "./SSHClient";
2
- import { SshMimic } from "./SSHMimic/index";
2
+ import { SftpMimic, SshMimic } from "./SSHMimic/index";
3
3
  import VirtualFileSystem from "./VirtualFileSystem";
4
4
  import { VirtualShell } from "./VirtualShell";
5
5
  import { VirtualUserManager } from "./VirtualUserManager";
6
- export { SshClient, VirtualFileSystem, VirtualShell, SshMimic as VirtualSshServer, VirtualUserManager, };
6
+ export { SshClient, VirtualFileSystem, SftpMimic as VirtualSftpServer, VirtualShell, SshMimic as VirtualSshServer, VirtualUserManager, };
7
7
  export { getArg, getFlag, ifFlag, } from "./commands/command-helpers";