typescript-virtual-container 1.0.8 → 1.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.
Files changed (34) hide show
  1. package/README.md +138 -87
  2. package/package.json +1 -1
  3. package/src/SSHMimic/client.ts +15 -18
  4. package/src/SSHMimic/exec.ts +5 -16
  5. package/src/SSHMimic/executor.ts +18 -29
  6. package/src/SSHMimic/index.ts +23 -85
  7. package/src/VirtualFileSystem/index.ts +0 -1
  8. package/src/VirtualShell/commands/adduser.ts +2 -2
  9. package/src/VirtualShell/commands/cat.ts +3 -3
  10. package/src/VirtualShell/commands/cd.ts +2 -2
  11. package/src/VirtualShell/commands/curl.ts +2 -2
  12. package/src/VirtualShell/commands/deluser.ts +2 -2
  13. package/src/VirtualShell/commands/grep.ts +2 -2
  14. package/src/VirtualShell/commands/index.ts +13 -107
  15. package/src/VirtualShell/commands/ls.ts +6 -4
  16. package/src/VirtualShell/commands/mkdir.ts +2 -2
  17. package/src/VirtualShell/commands/nano.ts +3 -3
  18. package/src/VirtualShell/commands/neofetch.ts +2 -2
  19. package/src/VirtualShell/commands/rm.ts +2 -2
  20. package/src/VirtualShell/commands/sh.ts +2 -13
  21. package/src/VirtualShell/commands/su.ts +2 -1
  22. package/src/VirtualShell/commands/sudo.ts +3 -6
  23. package/src/VirtualShell/commands/touch.ts +3 -3
  24. package/src/VirtualShell/commands/tree.ts +2 -2
  25. package/src/VirtualShell/commands/wget.ts +2 -2
  26. package/src/VirtualShell/commands/who.ts +2 -2
  27. package/src/VirtualShell/index.ts +114 -25
  28. package/src/VirtualShell/shell.ts +25 -35
  29. package/src/{SSHMimic/users.ts → VirtualUserManager/index.ts} +6 -3
  30. package/src/index.ts +4 -4
  31. package/src/standalone.ts +19 -14
  32. package/src/types/commands.ts +3 -11
  33. package/tests/parser-executor.test.ts +3 -6
  34. package/tests/users.test.ts +1 -1
package/README.md CHANGED
@@ -10,6 +10,7 @@
10
10
  ## Table of Contents
11
11
 
12
12
  - [Overview](#overview)
13
+ - [What This Is / What This Is Not](#what-this-is--what-this-is-not)
13
14
  - [Why This Package](#why-this-package)
14
15
  - [Installation](#installation)
15
16
  - [Compatibility](#compatibility)
@@ -42,6 +43,22 @@
42
43
  - **Built-in Commands**: `ls`, `cd`, `pwd`, `cat`, `mkdir`, `touch`, `rm`, `tree`, `whoami`, `hostname`, `who`, `sudo`, `su`, `adduser`, `deluser`, `nano` (text editor), `curl`, `wget`, and a growing set of additional commands. Not everything is implemented yet, and shell compatibility is still being expanded.
43
44
  - **Full TypeScript Support**: Complete JSDoc coverage, exported types, and first-class async/await for all operations.
44
45
 
46
+ ## What This Is / What This Is Not
47
+
48
+ ### What This Is
49
+
50
+ - A virtual shell runtime written in TypeScript.
51
+ - An in-memory environment with its own virtual filesystem, user management, and command runtime.
52
+ - A practical tool for deterministic testing, automation pipelines, and SSH-like workflows without running real containers.
53
+
54
+ ### What This Is Not
55
+
56
+ - Not a fully isolated container runtime.
57
+ - Not a security sandbox.
58
+ - Not a kernel-level isolation boundary (unlike Docker/VM-based isolation).
59
+
60
+ This project emulates shell behavior for developer workflows. It is designed for realism and productivity, not hard security isolation.
61
+
45
62
  ## Why This Package
46
63
 
47
64
  This package is designed for teams that need a realistic SSH-like runtime without spinning up real containers or VMs.
@@ -87,10 +104,10 @@ The virtual filesystem and shell behavior are intentionally portable and do not
87
104
  ### Running an SSH Server
88
105
 
89
106
  ```typescript
90
- import { VirtualMachine } from "typescript-virtual-container";
107
+ import { VirtualSshServer } from "typescript-virtual-container";
91
108
 
92
109
  // Create server on port 2222
93
- const ssh = new VirtualMachine({
110
+ const ssh = new VirtualSshServer({
94
111
  port: 2222,
95
112
  hostname: "my-container"
96
113
  });
@@ -112,13 +129,14 @@ process.on("SIGTERM", () => {
112
129
  ### Using the Programmatic Client API
113
130
 
114
131
  ```typescript
115
- import { VirtualMachine, SshClient } from "typescript-virtual-container";
132
+ import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
116
133
 
117
- const ssh = new VirtualMachine({ port: 2222 });
134
+ const shell = new VirtualShell("typescript-vm");
135
+ const ssh = new VirtualSshServer({ port: 2222, shell });
118
136
  await ssh.start();
119
137
 
120
138
  // Create authenticated client for specific user
121
- const client = new SshClient(ssh, "root");
139
+ const client = new SshClient(shell, "root");
122
140
 
123
141
  // Execute commands programmatically
124
142
  const list = await client.ls("/home");
@@ -178,7 +196,7 @@ ssh.stop();
178
196
 
179
197
  ### SshMimic (SSH Server)
180
198
 
181
- Main SSH server class. Manages virtual filesystem, user authentication, and session handlers.
199
+ Main SSH server class, exported as `VirtualSshServer` in the package entrypoint. It wires the virtual shell runtime into ssh2 sessions and manages authentication/session handlers.
182
200
 
183
201
  #### Constructor
184
202
 
@@ -186,17 +204,25 @@ Main SSH server class. Manages virtual filesystem, user authentication, and sess
186
204
  new SshMimic(options: {
187
205
  port: number; // TCP port to bind on localhost
188
206
  hostname?: string; // Virtual hostname (default: "typescript-vm")
189
- basePath?: string; // Base directory for VFS snapshot storage (default: ".")
207
+ shell?: VirtualShell; // Optional preconfigured shell instance
190
208
  })
191
209
  ```
192
210
 
211
+ - `hostname` controls the SSH ident label and the default hostname used by a generated shell.
212
+ - If `shell` is omitted, the server creates `new VirtualShell(hostname)` for you.
213
+
193
214
  **Example:**
194
215
 
195
216
  ```typescript
217
+ const virtualShell = new VirtualShell("my-lab", {
218
+ kernel: "1.0.0+itsrealfortune+1-amd64",
219
+ os: "Fortune GNU/Linux x64",
220
+ arch: "x86_64",
221
+ }, "./data");
196
222
  const ssh = new SshMimic({
197
223
  port: 2222,
198
224
  hostname: "my-lab",
199
- basePath: "./data" // Snapshots stored in ./data/.vfs/mirror.tar.gz
225
+ shell: virtualShell
200
226
  });
201
227
  ```
202
228
 
@@ -254,21 +280,22 @@ console.log(`Server name: ${ssh.getHostname()}`);
254
280
 
255
281
  ### SshClient (Programmatic Shell API)
256
282
 
257
- Execute shell commands as a specific user without SSH overhead. Maintains connection state (current working directory) across calls.
283
+ Execute shell commands against a `VirtualShell` instance without SSH overhead. Maintains connection state (current working directory) across calls.
258
284
 
259
285
  #### Constructor
260
286
 
261
287
  ```typescript
262
- new SshClient(ssh: SshMimic, username: string)
288
+ new SshClient(shell: VirtualShell, username: string)
263
289
  ```
264
290
 
265
- - **ssh**: Parent SSH server instance (must be started)
291
+ - **shell**: Parent virtual shell instance
266
292
  - **username**: User to authenticate as (no password required)
267
293
 
268
294
  **Example:**
269
295
 
270
296
  ```typescript
271
- const client = new SshClient(ssh, "alice");
297
+ const shell = new VirtualShell("typescript-vm");
298
+ const client = new SshClient(shell, "alice");
272
299
  ```
273
300
 
274
301
  #### Methods
@@ -419,30 +446,57 @@ console.log(client.getUsername()); // Username from constructor
419
446
 
420
447
  ### VirtualShell
421
448
 
422
- Encapsulates shell execution primitives used by the SSH runtime for command dispatch and interactive sessions.
449
+ Encapsulates shell execution primitives used by the SSH runtime for command dispatch, interactive sessions, and the programmatic client.
450
+
451
+ #### ShellProperties
452
+
453
+ ```typescript
454
+ interface ShellProperties {
455
+ kernel: string;
456
+ os: "Fortune GNU/Linux x64";
457
+ arch: "x86_64";
458
+ }
459
+
460
+ const defaultShellProperties: ShellProperties;
461
+ ```
462
+
463
+ - `kernel` is displayed in shell/system information output.
464
+ - `os` and `arch` are fixed labels used by the shell runtime.
423
465
 
424
466
  #### Constructor
425
467
 
426
468
  ```typescript
427
469
  new VirtualShell(
428
- vfs: VirtualFileSystem,
429
- users: VirtualUserManager,
430
470
  hostname: string,
471
+ properties?: ShellProperties,
472
+ basePath?: string,
431
473
  )
432
474
  ```
433
475
 
434
- - **vfs**: Virtual filesystem instance used by shell commands.
435
- - **users**: User manager for authentication/session-aware command behavior.
436
476
  - **hostname**: Hostname injected into command context and prompt behavior.
477
+ - **properties**: Optional shell metadata. Defaults to `defaultShellProperties`.
478
+ - **basePath**: Optional directory used to resolve `.vfs/mirror.tar.gz` (defaults to `.`).
437
479
 
438
480
  **Example:**
439
481
 
440
482
  ```typescript
441
- const shell = new VirtualShell(vfs, users, "typescript-vm");
483
+ const shell = new VirtualShell("typescript-vm", {
484
+ kernel: "1.0.0+itsrealfortune+1-amd64",
485
+ os: "Fortune GNU/Linux x64",
486
+ arch: "x86_64",
487
+ }, "./data");
442
488
  ```
443
489
 
444
490
  #### Methods
445
491
 
492
+ ##### `addCommand(name: string, params: string[], callback: (ctx: CommandContext) => CommandResult | Promise<CommandResult>): void`
493
+
494
+ Registers a custom command at runtime.
495
+
496
+ ```typescript
497
+ shell.addCommand("hello", [], () => ({ stdout: "hello", exitCode: 0 }));
498
+ ```
499
+
446
500
  ##### `executeCommand(rawInput: string, authUser: string, cwd: string): void`
447
501
 
448
502
  Runs one command input in shell mode for a given user and working directory.
@@ -617,11 +671,12 @@ User authentication, password hashing (scrypt), sudo privilege management, and s
617
671
  #### Constructor
618
672
 
619
673
  ```typescript
620
- new VirtualUserManager(vfs: VirtualFileSystem, defaultRootPassword?: string)
674
+ new VirtualUserManager(vfs: VirtualFileSystem, defaultRootPassword?: string, autoSudoForNewUsers?: boolean)
621
675
  ```
622
676
 
623
677
  - **vfs**: Virtual filesystem (for auth data persistence)
624
- - **defaultRootPassword**: Root password if creating new user (default: "root")
678
+ - **defaultRootPassword**: Root password used when root is created (default: "root")
679
+ - **autoSudoForNewUsers**: When true, new users are added to sudoers automatically (default: `true` unless `SSH_MIMIC_AUTO_SUDO_NEW_USERS` disables it)
625
680
 
626
681
  ```typescript
627
682
  const users = new VirtualUserManager(vfs, "SecureRootPass123");
@@ -801,9 +856,9 @@ interface VirtualActiveSession {
801
856
  Minimal server startup that accepts SSH connections:
802
857
 
803
858
  ```typescript
804
- import { VirtualMachine } from "typescript-virtual-container";
859
+ import { VirtualSshServer } from "typescript-virtual-container";
805
860
 
806
- const ssh = new VirtualMachine({
861
+ const ssh = new VirtualSshServer({
807
862
  port: 2222,
808
863
  hostname: "lab-environment"
809
864
  });
@@ -835,12 +890,13 @@ ssh root@localhost -p 2222
835
890
  Create, read, modify files without SSH:
836
891
 
837
892
  ```typescript
838
- import { VirtualMachine, SshClient } from "typescript-virtual-container";
893
+ import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
839
894
 
840
- const ssh = new VirtualMachine({ port: 2222 });
895
+ const shell = new VirtualShell("typescript-vm");
896
+ const ssh = new VirtualSshServer({ port: 2222, shell });
841
897
  await ssh.start();
842
898
 
843
- const client = new SshClient(ssh, "root");
899
+ const client = new SshClient(shell, "root");
844
900
 
845
901
  // Create structure
846
902
  await client.mkdir("/app/config", true);
@@ -874,9 +930,10 @@ ssh.stop();
874
930
  Create users, manage permissions, session tracking:
875
931
 
876
932
  ```typescript
877
- import { VirtualMachine, SshClient } from "typescript-virtual-container";
933
+ import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
878
934
 
879
- const ssh = new VirtualMachine({ port: 2222 });
935
+ const shell = new VirtualShell("typescript-vm");
936
+ const ssh = new VirtualSshServer({ port: 2222, shell });
880
937
  await ssh.start();
881
938
 
882
939
  const users = ssh.getUsers()!;
@@ -891,11 +948,11 @@ await users.removeSudoer("bob");
891
948
  await users.addSudoer("alice");
892
949
 
893
950
  // Alice: High privilege
894
- const alice = new SshClient(ssh, "alice");
951
+ const alice = new SshClient(shell, "alice");
895
952
  await alice.writeFile("/etc/important.conf", "secret=yes");
896
953
 
897
954
  // Bob: Regular user
898
- const bob = new SshClient(ssh, "bob");
955
+ const bob = new SshClient(shell, "bob");
899
956
  const result = await bob.cat("/etc/important.conf");
900
957
  console.log("Bob read file:", result.stderr);
901
958
 
@@ -909,12 +966,13 @@ ssh.stop();
909
966
  Save filesystem state between runs:
910
967
 
911
968
  ```typescript
912
- import { VirtualMachine } from "typescript-virtual-container";
969
+ import { VirtualSshServer, VirtualShell } from "typescript-virtual-container";
913
970
 
914
971
  // First run: Initialize
915
- const ssh1 = new VirtualMachine({
972
+ const shell1 = new VirtualShell("typescript-vm", undefined, "./container");
973
+ const ssh1 = new VirtualSshServer({
916
974
  port: 2222,
917
- basePath: "./container"
975
+ shell: shell1
918
976
  });
919
977
  await ssh1.start();
920
978
  const vfs1 = ssh1.getVfs()!;
@@ -927,9 +985,10 @@ ssh1.stop();
927
985
  console.log("State saved to ./container/.vfs/mirror.tar.gz");
928
986
 
929
987
  // Later: Reload and continue
930
- const ssh2 = new VirtualMachine({
988
+ const shell2 = new VirtualShell("typescript-vm", undefined, "./container");
989
+ const ssh2 = new VirtualSshServer({
931
990
  port: 2223,
932
- basePath: "./container"
991
+ shell: shell2
933
992
  });
934
993
  await ssh2.start();
935
994
  const vfs2 = ssh2.getVfs()!;
@@ -948,13 +1007,14 @@ ssh2.stop();
948
1007
  Simulate filesystem changes and verify outcomes:
949
1008
 
950
1009
  ```typescript
951
- import { VirtualMachine, SshClient } from "typescript-virtual-container";
1010
+ import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
952
1011
 
953
1012
  async function testDeployment() {
954
- const ssh = new VirtualMachine({ port: 2222 });
1013
+ const shell = new VirtualShell("typescript-vm");
1014
+ const ssh = new VirtualSshServer({ port: 2222, shell });
955
1015
  await ssh.start();
956
1016
 
957
- const client = new SshClient(ssh, "root");
1017
+ const client = new SshClient(shell, "root");
958
1018
 
959
1019
  // Pre-deployment: Set up base structure
960
1020
  await client.mkdir("/srv/app", true);
@@ -984,12 +1044,13 @@ testDeployment().catch(console.error);
984
1044
  Simulate shell workflows:
985
1045
 
986
1046
  ```typescript
987
- import { VirtualMachine, SshClient } from "typescript-virtual-container";
1047
+ import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
988
1048
 
989
- const ssh = new VirtualMachine({ port: 2222 });
1049
+ const shell = new VirtualShell("typescript-vm");
1050
+ const ssh = new VirtualSshServer({ port: 2222, shell });
990
1051
  await ssh.start();
991
1052
 
992
- const client = new SshClient(ssh, "root");
1053
+ const client = new SshClient(shell, "root");
993
1054
 
994
1055
  // Create nested structure
995
1056
  await client.mkdir("/home/user/projects/myapp/src", true);
@@ -1026,12 +1087,13 @@ ssh.stop();
1026
1087
  Graceful error handling in programmatic workflows:
1027
1088
 
1028
1089
  ```typescript
1029
- import { VirtualMachine, SshClient } from "typescript-virtual-container";
1090
+ import { VirtualSshServer, SshClient, VirtualShell } from "typescript-virtual-container";
1030
1091
 
1031
- const ssh = new VirtualMachine({ port: 2222 });
1092
+ const shell = new VirtualShell("typescript-vm");
1093
+ const ssh = new VirtualSshServer({ port: 2222, shell });
1032
1094
  await ssh.start();
1033
1095
 
1034
- const client = new SshClient(ssh, "root");
1096
+ const client = new SshClient(shell, "root");
1035
1097
 
1036
1098
  // Try read non-existent file
1037
1099
  const result = await client.readFile("/etc/nonexistent.conf");
@@ -1056,32 +1118,42 @@ ssh.stop();
1056
1118
 
1057
1119
  ## Built-in Commands
1058
1120
 
1059
- The following commands are available in both SSH shell mode and via `SshClient.exec()`. This list is intentionally incomplete: some commands, flags, and edge cases are still missing or only partially compatible with real shells, and that will continue to be worked on.
1121
+ The following commands are currently registered and available in both SSH shell mode and via `SshClient.exec()`. Some flags and edge-case behavior are still being expanded for shell compatibility.
1060
1122
 
1061
1123
  | Command | Purpose | Notes |
1062
1124
  |---------|---------|-------|
1063
- | `pwd` | Print working directory | No args |
1125
+ | `adduser <name> <pass>` | Create user | Root only |
1126
+ | `cat <path>` | Read file | Displays content |
1064
1127
  | `cd <path>` | Change directory | Updates client cwd |
1128
+ | `clear` | Clear screen | No args |
1129
+ | `curl <url>` | Fetch URL | Mock implementation |
1130
+ | `deluser <name>` | Delete user | Root only, not root |
1131
+ | `echo <text...>` | Print text | Supports shell-like argument output |
1132
+ | `env` | List environment variables | Shell environment view |
1133
+ | `exit [code]` | Close session | Optional exit code |
1134
+ | `export NAME=VALUE` | Set/export environment variable | Persists in shell env |
1135
+ | `grep <pattern> [path]` | Search for text | Simplified grep behavior |
1136
+ | `help` | List commands | No args |
1137
+ | `hostname` | Server hostname | No args |
1138
+ | `htop` | System monitor | Mock display |
1139
+ | `pwd` | Print working directory | No args |
1065
1140
  | `ls [path]` | List directory | Defaults to `.` |
1066
1141
  | `mkdir [-p] <path>` | Create directory | `-p` for parents |
1142
+ | `nano <path>` | Text editor | Interactive mode |
1143
+ | `neofetch` | Show system summary | Mock display |
1067
1144
  | `touch <path>` | Create empty file | Updates timestamps |
1068
- | `cat <path>` | Read file | Displays content |
1069
1145
  | `rm [-r] <path>` | Remove file/dir | `-r` for recursive |
1070
- | `tree [path]` | ASCII tree view | Defaults to `.` |
1071
- | `whoami` | Current user | No args |
1072
- | `hostname` | Server hostname | No args |
1073
- | `who` | Active sessions | No args |
1074
- | `sudo [-i] <cmd>` | Elevation | Requires sudoer status |
1146
+ | `set` | Show shell options/variables | Simplified behavior |
1147
+ | `sh <script>` | Run shell script | Simplified execution model |
1075
1148
  | `su <user>` | Switch user | Requires password/sudo |
1076
- | `adduser <name> <pass>` | Create user | Root only |
1077
- | `deluser <name>` | Delete user | Root only, not root |
1078
- | `curl <url>` | Fetch URL | Mock implementation |
1149
+ | `sudo [-i] <cmd>` | Elevation | Requires sudoer status |
1150
+ | `tree [path]` | ASCII tree view | Defaults to `.` |
1151
+ | `unset <name>` | Remove environment variable | Shell environment update |
1079
1152
  | `wget <url>` | Download | Mock implementation |
1080
- | `nano <path>` | Text editor | Interactive mode |
1081
- | `htop` | System monitor | Mock display |
1082
- | `clear` | Clear screen | No args |
1083
- | `exit [code]` | Close session | Optional exit code |
1084
- | `help` | List commands | No args |
1153
+ | `who` | Active sessions | No args |
1154
+ | `whoami` | Current user | No args |
1155
+
1156
+ Commands can be added via the VirtualShell addCommand() method for custom behavior.
1085
1157
 
1086
1158
  ---
1087
1159
 
@@ -1105,10 +1177,11 @@ npm run start
1105
1177
  ### Runtime Options
1106
1178
 
1107
1179
  ```typescript
1108
- const ssh = new VirtualMachine({
1180
+ const shell = new VirtualShell("my-container", undefined, "./data");
1181
+ const ssh = new VirtualSshServer({
1109
1182
  port: 2222, // Required
1110
1183
  hostname: "my-container", // Optional
1111
- basePath: "./data" // Optional, default: "."
1184
+ shell // Optional, prebuilt shell instance
1112
1185
  });
1113
1186
  ```
1114
1187
 
@@ -1131,8 +1204,9 @@ const ssh = new VirtualMachine({
1131
1204
  **Example:**
1132
1205
 
1133
1206
  ```typescript
1134
- const client1 = new SshClient(ssh, "alice");
1135
- const client2 = new SshClient(ssh, "bob");
1207
+ const shell = new VirtualShell("typescript-vm");
1208
+ const client1 = new SshClient(shell, "alice");
1209
+ const client2 = new SshClient(shell, "bob");
1136
1210
 
1137
1211
  const [result1, result2] = await Promise.all([
1138
1212
  client1.writeFile("/tmp/alice.txt", "..."),
@@ -1202,7 +1276,7 @@ Error: listen EADDRINUSE :::2222
1202
1276
  **Solution**: Use a different port
1203
1277
 
1204
1278
  ```typescript
1205
- const ssh = new VirtualMachine({ port: 3333 });
1279
+ const ssh = new VirtualSshServer({ port: 3333 });
1206
1280
  ```
1207
1281
 
1208
1282
  ### SSH Authentication Failed
@@ -1239,29 +1313,6 @@ await ssh.getVfs().flushMirror();
1239
1313
 
1240
1314
  ---
1241
1315
 
1242
- ## Migration Guide
1243
-
1244
- ### From v0.0.x to v1.x
1245
-
1246
- Old API:
1247
-
1248
- ```typescript
1249
- const ssh = new SshMimic(2222, "hostname");
1250
- ```
1251
-
1252
- New API:
1253
-
1254
- ```typescript
1255
- const ssh = new VirtualMachine({ port: 2222, hostname: "hostname" });
1256
- ```
1257
-
1258
- **Changes**:
1259
- - Object-based constructor
1260
- - Optional `basePath` parameter
1261
- - Exports renamed: `SshMimic` → `VirtualMachine` from main entry
1262
-
1263
- ---
1264
-
1265
1316
  ## Contributing
1266
1317
 
1267
1318
  1. Fork repository
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "In-memory SSH server with virtual filesystem and typed programmatic API",
4
4
  "module": "src/index.ts",
5
5
  "type": "module",
6
- "version": "1.0.8",
6
+ "version": "1.1.0",
7
7
  "license": "MIT",
8
8
  "keywords": [
9
9
  "ssh",
@@ -1,19 +1,17 @@
1
1
  import type { CommandResult } from "../types/commands";
2
+ import type { VirtualShell } from "../VirtualShell";
2
3
  import { runCommand } from "../VirtualShell/commands";
3
- import type { SshMimic } from "./index";
4
4
 
5
5
  /**
6
- * Programmatic SSH client to execute shell commands as a specific user.
6
+ * Programmatic client for executing shell commands against a virtual shell.
7
7
  *
8
- * Maintains connection state (cwd) across multiple command invocations.
9
- * All commands execute with implicit authentication (no password required).
8
+ * Maintains working-directory state across invocations and runs commands as a
9
+ * single authenticated user without SSH transport overhead.
10
10
  *
11
11
  * @example
12
12
  * ```ts
13
- * const ssh = new SshMimic(2222, "myhost");
14
- * await ssh.start();
15
- *
16
- * const client = new SshClient(ssh, "alice");
13
+ * const shell = new VirtualShell("typescript-vm");
14
+ * const client = new SshClient(shell, "alice");
17
15
  * const result = await client.cd("/tmp");
18
16
  * const list = await client.ls();
19
17
  * ```
@@ -22,13 +20,13 @@ export class SshClient {
22
20
  private currentCwd = "/";
23
21
 
24
22
  /**
25
- * Creates SSH client bound to user.
23
+ * Creates a programmatic client bound to a virtual shell and user.
26
24
  *
27
- * @param ssh Parent SSH server instance (must be started).
25
+ * @param shell Parent virtual shell instance.
28
26
  * @param username Login user for all commands.
29
27
  */
30
28
  constructor(
31
- private ssh: SshMimic,
29
+ private shell: VirtualShell,
32
30
  private username: string,
33
31
  ) {}
34
32
 
@@ -39,9 +37,9 @@ export class SshClient {
39
37
  * @returns Command result with stdout/stderr/exitCode.
40
38
  */
41
39
  async exec(command: string): Promise<CommandResult> {
42
- const vfs = this.ssh.getVfs();
43
- const users = this.ssh.getUsers();
44
- const hostname = this.ssh.getHostname();
40
+ const vfs = this.shell.getVfs();
41
+ const users = this.shell.getUsers();
42
+ const hostname = this.shell.getHostname();
45
43
 
46
44
  if (!vfs || !users) {
47
45
  throw new Error("SSH client not started");
@@ -51,10 +49,9 @@ export class SshClient {
51
49
  command,
52
50
  this.username,
53
51
  hostname,
54
- users,
55
52
  "exec",
56
53
  this.currentCwd,
57
- vfs,
54
+ this.shell,
58
55
  );
59
56
 
60
57
  // Handle async results
@@ -151,7 +148,7 @@ export class SshClient {
151
148
  * @returns Result from touch/write simulation.
152
149
  */
153
150
  async writeFile(path: string, content: string): Promise<CommandResult> {
154
- const vfs = this.ssh.getVfs();
151
+ const vfs = this.shell.getVfs();
155
152
  if (!vfs) {
156
153
  throw new Error("SSH client not started");
157
154
  }
@@ -174,7 +171,7 @@ export class SshClient {
174
171
  * @returns File content as string or error in result.
175
172
  */
176
173
  async readFile(path: string): Promise<CommandResult> {
177
- const vfs = this.ssh.getVfs();
174
+ const vfs = this.shell.getVfs();
178
175
  if (!vfs) {
179
176
  throw new Error("SSH client not started");
180
177
  }
@@ -1,8 +1,6 @@
1
1
  import type { ExecStream } from "../types/streams";
2
- import type VirtualFileSystem from "../VirtualFileSystem";
3
- import { defaultShellProperties } from "../VirtualShell";
2
+ import type { VirtualShell } from "../VirtualShell";
4
3
  import { runCommand } from "../VirtualShell/commands";
5
- import type { VirtualUserManager } from "./users";
6
4
 
7
5
  function toTtyLines(text: string): string {
8
6
  return text
@@ -16,20 +14,10 @@ export function runExec(
16
14
  cmd: string,
17
15
  authUser: string,
18
16
  hostname: string,
19
- users: VirtualUserManager,
20
- vfs: VirtualFileSystem,
17
+ shell: VirtualShell,
21
18
  ): void {
22
19
  Promise.resolve(
23
- runCommand(
24
- cmd,
25
- authUser,
26
- hostname,
27
- users,
28
- "exec",
29
- `/home/${authUser}`,
30
- defaultShellProperties,
31
- vfs,
32
- ),
20
+ runCommand(cmd, authUser, hostname, "exec", `/home/${authUser}`, shell),
33
21
  ).then((result) => {
34
22
  if (result.stdout) {
35
23
  stream.write(`${toTtyLines(result.stdout)}\r\n`);
@@ -40,7 +28,8 @@ export function runExec(
40
28
  }
41
29
 
42
30
  stream.exit(result.exitCode ?? 0);
43
- void vfs.flushMirror();
31
+ console.log(shell.vfs);
32
+ void shell.vfs.flushMirror();
44
33
  stream.end();
45
34
  });
46
35
  }