typescript-virtual-container 1.2.8 → 1.2.9

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 (120) hide show
  1. package/README.md +359 -32
  2. package/dist/SSHMimic/executor.js +3 -5
  3. package/dist/VirtualPackageManager/index.d.ts +202 -0
  4. package/dist/VirtualPackageManager/index.d.ts.map +1 -0
  5. package/dist/VirtualPackageManager/index.js +676 -0
  6. package/dist/VirtualShell/index.d.ts +87 -12
  7. package/dist/VirtualShell/index.d.ts.map +1 -1
  8. package/dist/VirtualShell/index.js +83 -12
  9. package/dist/VirtualUserManager/index.d.ts +52 -20
  10. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  11. package/dist/VirtualUserManager/index.js +54 -20
  12. package/dist/commands/alias.d.ts +4 -0
  13. package/dist/commands/alias.d.ts.map +1 -0
  14. package/dist/commands/alias.js +58 -0
  15. package/dist/commands/apt.d.ts +4 -0
  16. package/dist/commands/apt.d.ts.map +1 -0
  17. package/dist/commands/apt.js +182 -0
  18. package/dist/commands/cat.d.ts.map +1 -1
  19. package/dist/commands/cat.js +27 -8
  20. package/dist/commands/chmod.d.ts.map +1 -1
  21. package/dist/commands/chmod.js +52 -3
  22. package/dist/commands/command-helpers.d.ts +78 -4
  23. package/dist/commands/command-helpers.d.ts.map +1 -1
  24. package/dist/commands/command-helpers.js +78 -4
  25. package/dist/commands/curl.d.ts.map +1 -1
  26. package/dist/commands/curl.js +81 -29
  27. package/dist/commands/dpkg.d.ts +4 -0
  28. package/dist/commands/dpkg.d.ts.map +1 -0
  29. package/dist/commands/dpkg.js +144 -0
  30. package/dist/commands/echo.d.ts.map +1 -1
  31. package/dist/commands/echo.js +24 -12
  32. package/dist/commands/free.d.ts +3 -0
  33. package/dist/commands/free.d.ts.map +1 -0
  34. package/dist/commands/free.js +38 -0
  35. package/dist/commands/helpers.d.ts +3 -0
  36. package/dist/commands/helpers.d.ts.map +1 -1
  37. package/dist/commands/helpers.js +3 -0
  38. package/dist/commands/history.d.ts +3 -0
  39. package/dist/commands/history.d.ts.map +1 -0
  40. package/dist/commands/history.js +21 -0
  41. package/dist/commands/index.d.ts +8 -1
  42. package/dist/commands/index.d.ts.map +1 -1
  43. package/dist/commands/index.js +120 -11
  44. package/dist/commands/ls.d.ts.map +1 -1
  45. package/dist/commands/ls.js +4 -3
  46. package/dist/commands/lsb-release.d.ts +3 -0
  47. package/dist/commands/lsb-release.d.ts.map +1 -0
  48. package/dist/commands/lsb-release.js +50 -0
  49. package/dist/commands/man.d.ts +3 -0
  50. package/dist/commands/man.d.ts.map +1 -0
  51. package/dist/commands/man.js +155 -0
  52. package/dist/commands/neofetch.d.ts.map +1 -1
  53. package/dist/commands/neofetch.js +5 -0
  54. package/dist/commands/ping.d.ts.map +1 -1
  55. package/dist/commands/ping.js +5 -2
  56. package/dist/commands/ps.d.ts.map +1 -1
  57. package/dist/commands/ps.js +27 -6
  58. package/dist/commands/sh.d.ts.map +1 -1
  59. package/dist/commands/sh.js +29 -11
  60. package/dist/commands/source.d.ts +3 -0
  61. package/dist/commands/source.d.ts.map +1 -0
  62. package/dist/commands/source.js +31 -0
  63. package/dist/commands/test.d.ts +3 -0
  64. package/dist/commands/test.d.ts.map +1 -0
  65. package/dist/commands/test.js +92 -0
  66. package/dist/commands/type.d.ts +3 -0
  67. package/dist/commands/type.d.ts.map +1 -0
  68. package/dist/commands/type.js +34 -0
  69. package/dist/commands/uptime.d.ts +3 -0
  70. package/dist/commands/uptime.d.ts.map +1 -0
  71. package/dist/commands/uptime.js +40 -0
  72. package/dist/commands/wget.d.ts.map +1 -1
  73. package/dist/commands/wget.js +71 -100
  74. package/dist/commands/which.d.ts +3 -0
  75. package/dist/commands/which.d.ts.map +1 -0
  76. package/dist/commands/which.js +32 -0
  77. package/dist/index.d.ts +5 -2
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +2 -1
  80. package/dist/modules/linuxRootfs.d.ts +24 -0
  81. package/dist/modules/linuxRootfs.d.ts.map +1 -0
  82. package/dist/modules/linuxRootfs.js +297 -0
  83. package/dist/modules/neofetch.d.ts.map +1 -1
  84. package/dist/modules/neofetch.js +1 -0
  85. package/package.json +2 -1
  86. package/src/SSHMimic/executor.ts +3 -5
  87. package/src/VirtualPackageManager/index.ts +820 -0
  88. package/src/VirtualShell/index.ts +104 -13
  89. package/src/VirtualUserManager/index.ts +55 -20
  90. package/src/commands/alias.ts +60 -0
  91. package/src/commands/apt.ts +198 -0
  92. package/src/commands/cat.ts +32 -8
  93. package/src/commands/chmod.ts +48 -3
  94. package/src/commands/command-helpers.ts +78 -4
  95. package/src/commands/curl.ts +78 -37
  96. package/src/commands/dpkg.ts +158 -0
  97. package/src/commands/echo.ts +30 -14
  98. package/src/commands/free.ts +40 -0
  99. package/src/commands/helpers.ts +8 -0
  100. package/src/commands/history.ts +29 -0
  101. package/src/commands/index.ts +116 -11
  102. package/src/commands/ls.ts +5 -4
  103. package/src/commands/lsb-release.ts +52 -0
  104. package/src/commands/man.ts +166 -0
  105. package/src/commands/neofetch.ts +5 -0
  106. package/src/commands/ping.ts +5 -2
  107. package/src/commands/ps.ts +28 -6
  108. package/src/commands/sh.ts +33 -11
  109. package/src/commands/source.ts +35 -0
  110. package/src/commands/test.ts +100 -0
  111. package/src/commands/type.ts +40 -0
  112. package/src/commands/uptime.ts +46 -0
  113. package/src/commands/wget.ts +70 -123
  114. package/src/commands/which.ts +34 -0
  115. package/src/index.ts +10 -0
  116. package/src/modules/linuxRootfs.ts +439 -0
  117. package/src/modules/neofetch.ts +1 -0
  118. package/standalone.js +418 -103
  119. package/standalone.js.map +4 -4
  120. package/tests/new-features.test.ts +626 -0
package/README.md CHANGED
@@ -1,6 +1,26 @@
1
1
  # `typescript-virtual-container`
2
2
 
3
- > Pure in-memory SSH/SFTP server with a virtual filesystem, a real shell interpreter, and a typed programmatic API for testing, automation, honeypots, and interactive shell scripting in TypeScript/JavaScript.
3
+ > Pure in-memory SSH/SFTP server with a realistic Linux rootfs, a virtual package manager, a real shell interpreter, and a typed programmatic API for testing, automation, honeypots, and interactive shell scripting in TypeScript/JavaScript.
4
+
5
+ ---
6
+
7
+ > **Release notes — what changed in this drop**
8
+ >
9
+ > **Linux rootfs** — full `/etc`, `/proc`, `/sys`, `/dev`, `/usr`, `/var` hierarchy bootstrapped at init. `neofetch` now shows real uptime and package count. `/proc/meminfo`, `/proc/cpuinfo`, `/proc/version` populated from live system data. `/etc/passwd`, `/etc/group`, `/etc/shadow` synced from `VirtualUserManager`.
10
+ >
11
+ > **Virtual package manager** — `apt install vim git nodejs python3` works. 25 packages in the built-in registry. Dependency resolution, file installation into VFS, `/var/lib/dpkg/status` persistence, `dpkg -l|-s|-L`, `apt-cache search|show|policy`. `neofetch` reflects installed count.
12
+ >
13
+ > **`curl` / `wget` — pure `fetch()`** — host binary no longer spawned. Full flag support (`-o`, `-X`, `-d`, `-H`, `-s`, `-I`, `-L`, `-v` for curl; `-O`, `-P`, `-q` for wget). Zero host filesystem access.
14
+ >
15
+ > **New commands** — `which`, `type`, `man` (with built-in pages), `uptime`, `free`, `lsb_release`, `alias`, `unalias`.
16
+ >
17
+ > **`$(cmd)` command substitution** — `echo $(whoami)`, `mkdir /tmp/$(date +%s)`, nested subs, all work.
18
+ >
19
+ > **Alias expansion** — `alias ll='ls -la'` then `ll /etc` resolves transparently in the dispatcher.
20
+ >
21
+ > *— written by Claude because explaining every change manually is a waste of everyone's time*
22
+
23
+ ---
4
24
 
5
25
  [![npm version](https://badge.fury.io/js/typescript-virtual-container.svg)](https://www.npmjs.com/package/typescript-virtual-container)
6
26
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -23,10 +43,13 @@
23
43
  - [VirtualFileSystem](#virtualfilesystem)
24
44
  - [VFSB Binary Format](#vfsb-binary-format)
25
45
  - [VirtualUserManager](#virtualusermanager)
46
+ - [VirtualPackageManager](#virtualpackagemanager)
26
47
  - [HoneyPot](#honeypot)
27
48
  - [SshClient](#sshclient-programmatic-api)
28
49
  - [Key Types](#key-types)
29
50
  - [Usage Examples](#usage-examples)
51
+ - [Linux Rootfs](#linux-rootfs)
52
+ - [Package Manager (apt/dpkg)](#package-manager-aptdpkg)
30
53
  - [Built-in Commands](#built-in-commands)
31
54
  - [Shell Scripting](#shell-scripting)
32
55
  - [Configuration](#configuration)
@@ -57,7 +80,11 @@
57
80
  - **`.bashrc` support**: Loaded automatically at interactive session start from `/home/<user>/.bashrc`.
58
81
  - **Event-Driven Architecture**: All core classes extend `EventEmitter` for lifecycle and operation tracking.
59
82
  - **Security Auditing**: Built-in `HoneyPot` utility for comprehensive activity logging, event tracking, statistics collection, and anomaly detection across all components.
60
- - **60+ Built-in Commands**: Full navigation, text processing, archiving, system info, and user management commandsgrouped and documented in the interactive `help` system.
83
+ - **Linux rootfs on boot**: Realistic `/etc`, `/proc`, `/sys`, `/dev`, `/usr`, `/var` hierarchy populated at startup`os-release`, `passwd`, `hosts`, `resolv.conf`, `/proc/meminfo`, `/proc/cpuinfo`, and more.
84
+ - **Virtual package manager**: `apt install`, `apt remove`, `apt search`, `dpkg -l`, `dpkg -s` — 25 packages in the built-in registry (vim, git, nodejs, python3, curl, openssh, gcc…). Writes files into VFS, tracks state in `/var/lib/dpkg/status`.
85
+ - **90+ Built-in Commands**: Full navigation, text processing, archiving, system info, package management, and user management commands — grouped and documented in the interactive `help` system.
86
+ - **`$(cmd)` command substitution**: Nested command execution in any argument position.
87
+ - **Alias support**: `alias`, `unalias` — persisted in session environment.
61
88
  - **Full TypeScript Support**: Complete JSDoc coverage, exported types, and first-class async/await for all operations.
62
89
 
63
90
  ---
@@ -74,10 +101,10 @@
74
101
  ### What This Is Not
75
102
 
76
103
  - Not a fully isolated container runtime.
77
- - Not a security sandbox — the VFS does not sandbox host filesystem access by spawned child processes (e.g. `wget`, `curl` delegate to the host binary).
78
104
  - Not a kernel-level isolation boundary (unlike Docker/VM-based isolation).
105
+ - Package stubs (e.g. `node`, `python3`) write files into the VFS and are visible to `which`/`dpkg -L`, but do not execute real binaries — the shell is pure TypeScript with no `execvp`.
79
106
 
80
- This project emulates shell behavior for developer workflows. It is designed for realism and productivity, not hard security isolation.
107
+ This project emulates shell behavior for developer workflows. `curl` and `wget` use the native `fetch()` API (no host binary). All other network and execution primitives are simulated. It is designed for realism and deployability, not kernel-level security isolation.
81
108
 
82
109
  ---
83
110
 
@@ -382,12 +409,14 @@ new VirtualShell(
382
409
 
383
410
  ```typescript
384
411
  interface ShellProperties {
385
- kernel: string; // e.g. "1.0.0+itsrealfortune+1-amd64"
386
- os: string; // e.g. "Fortune GNU/Linux x64"
387
- arch: string; // e.g. "x86_64"
412
+ kernel: string; // Kernel version string — shown by uname, neofetch, /proc/version
413
+ os: string; // Full OS description — shown by neofetch, /etc/os-release, lsb_release
414
+ arch: string; // CPU architecture label — shown by uname -m, neofetch
388
415
  }
389
416
  ```
390
417
 
418
+ Fields map directly to the values reported by `uname -a`, `neofetch`, `lsb_release`, `/etc/os-release`, and `/proc/version` inside the shell. Changing them after construction has no effect — pass them to the constructor.
419
+
391
420
  **Example:**
392
421
 
393
422
  ```typescript
@@ -407,13 +436,26 @@ const shell = new VirtualShell("typescript-vm", {
407
436
  |--------|-------------|
408
437
  | `ensureInitialized(): Promise<void>` | Await this before using the shell programmatically. |
409
438
  | `addCommand(name, params, callback)` | Register a custom shell command. |
410
- | `executeCommand(rawInput, authUser, cwd)` | Run a raw command string. |
411
- | `startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize)` | Start an SSH interactive session. |
412
- | `writeFileAsUser(authUser, path, content)` | Write a file with quota enforcement. |
439
+ | `executeCommand(rawInput, authUser, cwd)` | Run a raw command string (supports `&&`, `\|`, `$(cmd)`, aliases). |
440
+ | `startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize)` | Attach an interactive PTY session to this shell. |
441
+ | `writeFileAsUser(authUser, path, content)` | Write a file on behalf of a user with quota enforcement. |
442
+ | `refreshProcFs(): void` | Refresh `/proc/uptime`, `/proc/meminfo`, `/proc/cpuinfo`, etc. from live host data. |
443
+ | `syncPasswd(): void` | Sync `/etc/passwd`, `/etc/group`, `/etc/shadow` from `VirtualUserManager` state. |
413
444
  | `getVfs(): VirtualFileSystem \| null` | Access the VFS instance. |
414
445
  | `getUsers(): VirtualUserManager \| null` | Access the user manager. |
415
446
  | `getHostname(): string` | Returns the configured hostname. |
416
447
 
448
+ #### Public fields
449
+
450
+ | Field | Type | Description |
451
+ |-------|------|-------------|
452
+ | `vfs` | `VirtualFileSystem` | Backing virtual filesystem — use for direct path operations. |
453
+ | `users` | `VirtualUserManager` | Virtual user database — auth, quotas, and session tracking. |
454
+ | `packageManager` | `VirtualPackageManager` | APT/dpkg package manager backed by the built-in registry. |
455
+ | `hostname` | `string` | Hostname shown in the shell prompt and SSH ident string. |
456
+ | `properties` | `ShellProperties` | Distro identity strings surfaced by `uname`, `neofetch`, etc. |
457
+ | `startTime` | `number` | Unix ms timestamp of shell creation — used by `uptime` and `/proc/uptime`. |
458
+
417
459
  **Custom command example:**
418
460
 
419
461
  ```typescript
@@ -665,25 +707,27 @@ new VirtualUserManager(
665
707
  |--------|-------------|
666
708
  | `initialize(): Promise<void>` | Load users/sudoers from VFS, ensure root exists. Call once on startup. |
667
709
  | `verifyPassword(username, password): boolean` | Check plaintext password against stored hash. |
668
- | `hasPassword(username): boolean` | Returns true if a password is set for the user. |
669
- | `hashPassword(password): string` | Hash a password using the configured algorithm. |
710
+ | `hasPassword(username): boolean` | Returns `true` if a non-empty password is set for the user. |
711
+ | `hashPassword(password): string` | Hash a password using scrypt (or SHA-256 with `SSH_MIMIC_FAST_PASSWORD_HASH=1`). |
712
+ | `getPasswordHash(username): string \| null` | Returns the raw stored hash for a user, or `null` if not found. |
670
713
  | `addUser(username, password): Promise<void>` | Create user with home directory. |
671
- | `deleteUser(username): Promise<void>` | Delete user. Cannot delete root. |
672
- | `setPassword(username, password): Promise<void>` | Update password for an existing user. |
673
- | `isSudoer(username): boolean` | Check if user has sudo privileges. |
674
- | `addSudoer(username): Promise<void>` | Grant sudo privileges. |
675
- | `removeSudoer(username): Promise<void>` | Revoke sudo privileges. Cannot remove root. |
714
+ | `deleteUser(username): Promise<void>` | Delete user. Throws when user is `root` or does not exist. |
715
+ | `setPassword(username, password): Promise<void>` | Update password for an existing user. Throws when user does not exist. |
716
+ | `isSudoer(username): boolean` | Returns `true` when the user has sudo privileges. |
717
+ | `addSudoer(username): Promise<void>` | Grant sudo privileges. Throws when user does not exist. |
718
+ | `removeSudoer(username): Promise<void>` | Revoke sudo privileges. Throws when username is `root`. |
676
719
  | `setQuotaBytes(username, maxBytes): Promise<void>` | Set per-user write quota (bytes under `/home/<user>`). |
677
- | `clearQuota(username): Promise<void>` | Remove quota limit. |
678
- | `getQuotaBytes(username): number \| null` | Returns quota in bytes, or null if unlimited. |
720
+ | `clearQuota(username): Promise<void>` | Remove quota limit for a user. |
721
+ | `getQuotaBytes(username): number \| null` | Returns quota in bytes, or `null` if unlimited. |
679
722
  | `getUsageBytes(username): number` | Returns current usage in bytes under `/home/<user>`. |
680
723
  | `assertWriteWithinQuota(username, path, content)` | Throws if the write would exceed the user's quota. |
724
+ | `listUsers(): string[]` | Returns a sorted list of all registered usernames. |
681
725
  | `addAuthorizedKey(username, algo, data)` | Register an SSH public key for the user. |
682
726
  | `getAuthorizedKeys(username)` | Returns the list of authorized keys for a user. |
683
727
  | `removeAuthorizedKeys(username)` | Revoke all authorized keys for a user. |
684
- | `registerSession(username, remoteAddress): VirtualActiveSession` | Start session tracking, returns session descriptor. |
685
- | `unregisterSession(sessionId): void` | End session. Safe to call with null. |
686
- | `updateSession(sessionId, username, remoteAddress): void` | Update session metadata (used by `su`/`sudo`). |
728
+ | `registerSession(username, remoteAddress): VirtualActiveSession` | Register an active session and allocate a virtual TTY. |
729
+ | `unregisterSession(sessionId): void` | Remove a session record on disconnect. Safe to call with `null`. |
730
+ | `updateSession(sessionId, username, remoteAddress): void` | Update session metadata after `su`/`sudo` identity change. |
687
731
  | `listActiveSessions(): VirtualActiveSession[]` | Returns all active sessions sorted by start time. |
688
732
 
689
733
  #### Events
@@ -712,6 +756,54 @@ users.on("session:register", ({ sessionId, username, remoteAddress }) => {
712
756
 
713
757
  ---
714
758
 
759
+
760
+ ### `VirtualPackageManager`
761
+
762
+ Simulates APT/dpkg package management backed by a built-in registry. Accessed via `shell.packageManager`.
763
+
764
+ #### Constructor
765
+
766
+ Instantiated automatically by `VirtualShell`. Not constructed directly.
767
+
768
+ #### Methods
769
+
770
+ | Method | Description |
771
+ |--------|-------------|
772
+ | `load()` | Load installed packages from `/var/lib/dpkg/status` (called on shell init) |
773
+ | `install(names, opts?)` | Install packages (resolves deps, writes files to VFS). Returns `{ output, exitCode }` |
774
+ | `remove(names, opts?)` | Remove packages. `opts.purge` also removes config files |
775
+ | `search(term)` | Search available packages by name or description |
776
+ | `show(name)` | Show dpkg-style metadata block for a package |
777
+ | `listInstalled()` | List all installed packages as `InstalledPackage[]` |
778
+ | `listAvailable()` | List all packages in the registry |
779
+ | `isInstalled(name)` | Returns `true` if package is installed |
780
+ | `installedCount()` | Count of installed packages (used by `neofetch`) |
781
+ | `findInRegistry(name)` | Look up a `PackageDefinition` by name |
782
+
783
+ #### Types
784
+
785
+ ```ts
786
+ interface PackageDefinition {
787
+ name: string;
788
+ version: string;
789
+ description: string;
790
+ files?: PackageFile[]; // written to VFS on install
791
+ depends?: string[]; // resolved recursively
792
+ onInstall?: (vfs, users) => void;
793
+ onRemove?: (vfs) => void;
794
+ }
795
+
796
+ interface InstalledPackage {
797
+ name: string;
798
+ version: string;
799
+ architecture: string;
800
+ installedAt: string; // ISO-8601
801
+ files: string[]; // paths written to VFS
802
+ }
803
+ ```
804
+
805
+ ---
806
+
715
807
  ### `HoneyPot`
716
808
 
717
809
  Comprehensive security auditing and event tracking utility. Attaches listeners to all core components to log activity, track statistics, and detect anomalies.
@@ -966,6 +1058,49 @@ interface VfsSnapshot {
966
1058
 
967
1059
  ---
968
1060
 
1061
+ ### Command Helpers
1062
+
1063
+ Three utility functions are exported from `typescript-virtual-container` to assist with argument parsing inside custom command handlers.
1064
+
1065
+ #### `ifFlag(args, flags): boolean`
1066
+
1067
+ Returns `true` when any of the given flags appear in the argument array. Matches both standalone tokens (`-s`, `--silent`) and inline forms (`--output=file`).
1068
+
1069
+ ```typescript
1070
+ import { ifFlag } from "typescript-virtual-container";
1071
+
1072
+ const recursive = ifFlag(args, ["-r", "--recursive"]);
1073
+ const silent = ifFlag(args, "-s");
1074
+ ```
1075
+
1076
+ #### `getFlag(args, flags): string | true | undefined`
1077
+
1078
+ Returns the value of a flag, `true` if the flag has no value, or `undefined` if absent.
1079
+
1080
+ ```typescript
1081
+ import { getFlag } from "typescript-virtual-container";
1082
+
1083
+ const output = getFlag(args, ["-o", "--output"]);
1084
+ // args = ["--output", "file.txt"] → "file.txt"
1085
+ // args = ["--output=file.txt"] → "file.txt"
1086
+ // args = ["--verbose"] → true (when "verbose" is in flags list)
1087
+ // args = [] → undefined
1088
+ ```
1089
+
1090
+ #### `getArg(args, index, options?): string | undefined`
1091
+
1092
+ Returns the positional argument at a given zero-based index, skipping known flags and their values.
1093
+
1094
+ ```typescript
1095
+ import { getArg } from "typescript-virtual-container";
1096
+
1097
+ // args = ["-r", "src", "dest"]
1098
+ const src = getArg(args, 0, { flags: ["-r"] }); // "src"
1099
+ const dest = getArg(args, 1, { flags: ["-r"] }); // "dest"
1100
+ ```
1101
+
1102
+ ---
1103
+
969
1104
  ## Usage Examples
970
1105
 
971
1106
  ### Example 1: Basic SSH Server
@@ -1316,6 +1451,145 @@ const [r1, r2] = await Promise.all([
1316
1451
  ]);
1317
1452
  ```
1318
1453
 
1454
+ ---
1455
+
1456
+ ## Linux Rootfs
1457
+
1458
+ On every `VirtualShell` init, a realistic Linux directory hierarchy is bootstrapped into the VFS. All paths are created idempotently — FS-mode snapshots survive restarts without duplication.
1459
+
1460
+ ### Directory layout
1461
+
1462
+ ```
1463
+ /
1464
+ ├── bin -> /usr/bin (symlink, Debian-style)
1465
+ ├── dev/ null, zero, random, urandom, pts/, shm/
1466
+ ├── etc/
1467
+ │ ├── apt/sources.list Fortune package sources
1468
+ │ ├── debian_version
1469
+ │ ├── group synced from VirtualUserManager
1470
+ │ ├── hostname
1471
+ │ ├── hosts 127.0.0.1 localhost + VM hostname
1472
+ │ ├── motd
1473
+ │ ├── os-release NAME="Fortune GNU/Linux" + ShellProperties
1474
+ │ ├── passwd synced from VirtualUserManager
1475
+ │ ├── resolv.conf 1.1.1.1 + 8.8.8.8
1476
+ │ └── shadow (mode 0o640)
1477
+ ├── proc/
1478
+ │ ├── cpuinfo real host CPU info
1479
+ │ ├── loadavg
1480
+ │ ├── meminfo real host memory
1481
+ │ ├── net/dev eth0 + lo
1482
+ │ ├── uptime shell uptime in seconds
1483
+ │ └── version kernel from ShellProperties
1484
+ ├── root/ root home + .bashrc
1485
+ ├── sys/devices/virtual/dmi/id/
1486
+ │ ├── sys_vendor "Fortune Systems"
1487
+ │ └── product_name "VirtualContainer v1"
1488
+ ├── tmp/ (mode 0o1777 sticky)
1489
+ ├── usr/bin/ stubs for all built-in commands
1490
+ ├── var/
1491
+ │ ├── lib/dpkg/status managed by VirtualPackageManager
1492
+ │ └── log/ syslog, auth.log, dpkg.log, apt/
1493
+ └── ...
1494
+ ```
1495
+
1496
+ ### API
1497
+
1498
+ ```ts
1499
+ shell.refreshProcFs(); // refresh /proc/* with current system state
1500
+ shell.syncPasswd(); // sync /etc/passwd|group|shadow from VirtualUserManager
1501
+ ```
1502
+
1503
+ `syncPasswd()` is called automatically on `bootstrapLinuxRootfs`. Call it again after `users.addUser()` / `users.deleteUser()` to keep `/etc/passwd` consistent.
1504
+
1505
+ ---
1506
+
1507
+ ## Package Manager (apt/dpkg)
1508
+
1509
+ A pure-TypeScript APT/dpkg simulation backed by a built-in package registry. No external process is spawned.
1510
+
1511
+ ### Workflow
1512
+
1513
+ ```
1514
+ apt install <pkg> → resolves deps → writes files to VFS → updates /var/lib/dpkg/status
1515
+ apt remove <pkg> → removes VFS files → updates status
1516
+ dpkg -l → reads installed packages from VirtualPackageManager
1517
+ ```
1518
+
1519
+ ### Built-in package registry (25 packages)
1520
+
1521
+ | Package | Version | Section |
1522
+ |---------|---------|---------|
1523
+ | `vim` | 2:9.0.1378-2 | editors |
1524
+ | `git` | 1:2.39.2-1 | vcs |
1525
+ | `python3` | 3.11.2-1 | python |
1526
+ | `nodejs` | 18.19.0 | javascript |
1527
+ | `npm` | 9.2.0 | javascript |
1528
+ | `curl` | 7.88.1-10 | web |
1529
+ | `wget` | 1.21.3-1 | web |
1530
+ | `htop` | 3.2.2-2 | utils |
1531
+ | `openssh-client` | 1:9.2p1-2 | net |
1532
+ | `openssh-server` | 1:9.2p1-2 | net |
1533
+ | `net-tools` | 2.10-0.1 | net |
1534
+ | `iputils-ping` | 3:20221126-1 | net |
1535
+ | `jq` | 1.6-2.1 | utils |
1536
+ | `build-essential` | 12.9 | devel |
1537
+ | `gcc` | 4:12.2.0-3 | devel |
1538
+ | `g++` | 4:12.2.0-3 | devel |
1539
+ | `make` | 4.3-4.1 | devel |
1540
+ | `less` | 590-2 | text |
1541
+ | `unzip` | 6.0-28 | utils |
1542
+ | `rsync` | 3.2.7-1 | net |
1543
+ | `tmux` | 3.3a-3 | utils |
1544
+ | `tree` | 2.1.0-1 | utils |
1545
+ | `ca-certificates` | 20230311 | misc |
1546
+ | `sudo` | 1.9.13p3-1 | admin |
1547
+ | `systemd` | 252.22-1 | admin |
1548
+
1549
+ > **Note on package stubs**: installing `nodejs` or `python3` writes stubs into `/usr/bin/` and makes them discoverable via `which` and `dpkg -L`. The stubs print a version line but do not execute real binaries — the shell runtime is pure TypeScript with no `execvp`. This makes them useful for testing install workflows and `which`/`dpkg -L` queries, not for running actual scripts.
1550
+
1551
+ ### `VirtualPackageManager` API
1552
+
1553
+ ```ts
1554
+ // Access via shell instance
1555
+ const pm = shell.packageManager;
1556
+
1557
+ pm.install(["vim", "git"], { quiet: false }) // → { output, exitCode }
1558
+ pm.remove(["vim"], { purge: false }) // → { output, exitCode }
1559
+ pm.search("editor") // → PackageDefinition[]
1560
+ pm.show("vim") // → string (dpkg-style block)
1561
+ pm.listInstalled() // → InstalledPackage[]
1562
+ pm.listAvailable() // → PackageDefinition[]
1563
+ pm.isInstalled("vim") // → boolean
1564
+ pm.installedCount() // → number
1565
+ pm.findInRegistry("nodejs") // → PackageDefinition | undefined
1566
+ ```
1567
+
1568
+ ### Registering custom packages
1569
+
1570
+ ```ts
1571
+ import { VirtualPackageManager } from "typescript-virtual-container";
1572
+
1573
+ // Custom packages can be added to the registry before shell init
1574
+ // by extending PACKAGE_REGISTRY or by calling install() with custom defs.
1575
+
1576
+ // Or: register a post-install hook via onInstall
1577
+ const customPkg = {
1578
+ name: "myapp",
1579
+ version: "1.0.0",
1580
+ description: "My application",
1581
+ files: [
1582
+ { path: "/usr/bin/myapp", content: "#!/bin/sh\necho myapp v1.0.0\n", mode: 0o755 },
1583
+ { path: "/etc/myapp/config.json", content: JSON.stringify({ port: 3000 }) },
1584
+ ],
1585
+ onInstall: (vfs) => {
1586
+ vfs.mkdir("/var/lib/myapp", 0o755);
1587
+ vfs.mkdir("/var/log/myapp", 0o755);
1588
+ },
1589
+ };
1590
+ ```
1591
+
1592
+
1319
1593
  ---
1320
1594
 
1321
1595
  ## Built-in Commands
@@ -1327,7 +1601,7 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
1327
1601
  | Command | Flags | Description |
1328
1602
  |---------|-------|-------------|
1329
1603
  | `cd <path>` | | Change directory |
1330
- | `ls [path]` | `-l` | List directory contents |
1604
+ | `ls [path]` | `-l` `-a` | List directory contents (`-a` shows dotfiles) |
1331
1605
  | `pwd` | | Print working directory |
1332
1606
  | `tree [path]` | | ASCII directory tree |
1333
1607
 
@@ -1335,8 +1609,8 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
1335
1609
 
1336
1610
  | Command | Flags | Description |
1337
1611
  |---------|-------|-------------|
1338
- | `cat <path>` | | Print file contents |
1339
- | `chmod <mode> <file>` | | Change file permissions (octal) |
1612
+ | `cat <path...>` | `-n` `-b` | Concatenate and print files; `-n` numbers all lines, `-b` numbers non-blank lines |
1613
+ | `chmod <mode> <file>` | | Change file permissions — octal (`755`) or symbolic (`+x`, `u+x`, `go-w`, `a=rx`) |
1340
1614
  | `cp <src> <dest>` | `-r` | Copy file or directory |
1341
1615
  | `find [path]` | `-name <pat>` `-type f\|d` | Search for files |
1342
1616
  | `ln <target> <link>` | `-s` | Create hard or symbolic link |
@@ -1384,9 +1658,12 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
1384
1658
  | `htop` | | System monitor (mock) |
1385
1659
  | `id [user]` | | Print user identity (uid/gid/groups) |
1386
1660
  | `kill [-9] <pid>` | | Send signal to process (mock) |
1387
- | `neofetch` | | System info display (mock) |
1661
+ | `free` | `-h` `-m` `-g` | Display free and used memory |
1662
+ | `lsb_release` | `-a` `-i` `-d` `-r` `-c` | Print distribution information |
1663
+ | `neofetch` | | System info display (shows real packages/uptime) |
1664
+ | `uptime` | `-p` `-s` | Tell how long the system has been running |
1388
1665
  | `ping [-c <n>] <host>` | | Send ICMP ECHO_REQUEST (mock) |
1389
- | `ps` | `-a` `-u` `-x` | Report process status |
1666
+ | `ps` | `-a` `-u` `-x` `aux` | Report process status; `-u` / `aux` shows USER/PID/%CPU/%MEM columns |
1390
1667
  | `sleep <seconds>` | | Delay execution |
1391
1668
  | `uname` | `-a` `-r` `-m` | Print system information |
1392
1669
  | `who` | | List active sessions |
@@ -1396,23 +1673,50 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
1396
1673
 
1397
1674
  | Command | Flags | Description |
1398
1675
  |---------|-------|-------------|
1399
- | `curl <url>` | | HTTP client (delegates to host binary) |
1400
- | `wget <url>` | | File downloader (delegates to host binary) |
1676
+ | `curl <url>` | `-o` `-X` `-d` `-H` `-s` `-I` `-L` `-v` | HTTP client (pure `fetch()`, no host binary) |
1677
+ | `wget <url>` | `-O` `-P` `-q` | File downloader (pure `fetch()`, no host binary) |
1401
1678
 
1402
1679
  ### Shell
1403
1680
 
1404
1681
  | Command | Flags | Description |
1405
1682
  |---------|-------|-------------|
1683
+ | `alias [name=value]` | | Define or display aliases |
1406
1684
  | `clear` | | Clear terminal screen (full ANSI reset) |
1407
- | `echo <text>` | | Display text |
1685
+ | `echo <text>` | `-n` `-e` | Display text; `-n` suppresses newline, `-e` interprets escape sequences (`\n`, `\t`, `\r`, `\\`) |
1408
1686
  | `env` | | Print session environment variables |
1409
1687
  | `exit [code]` | | Exit session |
1410
1688
  | `export NAME=VALUE` | | Set shell variable in current session |
1411
1689
  | `help [command]` | | List commands (grouped) or show command details |
1412
1690
  | `set [VAR=val]` | | Display or set shell variables |
1413
- | `sh` | `-c <script>` `[file]` | Execute shell script (supports if/for/while) |
1691
+ | `sh` | `-c <script>` `[file]` | Execute shell script (supports if/for/while/do/done); `$(cmd)` substitution inside single-quoted args is preserved |
1692
+ | `history [n]` | | Display command history (last N entries) |
1693
+ | `source <file>` | | Execute file in current shell environment (aliases and exports persist) |
1694
+ | `. <file>` | | Alias for `source` |
1695
+ | `test <expr>` | | Evaluate conditional expression |
1696
+ | `[ <expr> ]` | | Alias for `test` — supports `-f`, `-d`, `-e`, `-z`, `-n`, `=`, `!=`, `-eq`, `-lt`, `-gt`, etc. |
1697
+ | `unalias <name>` | `-a` | Remove alias definitions |
1414
1698
  | `unset <VAR>` | | Remove shell variable |
1415
1699
 
1700
+ ### Package Management
1701
+
1702
+ | Command | Flags | Description |
1703
+ |---------|-------|-------------|
1704
+ | `apt <cmd> [pkg...]` | | Package manager (`install`, `remove`, `purge`, `update`, `upgrade`, `search`, `show`, `list`) |
1705
+ | `apt-get <cmd>` | | Alias for `apt` |
1706
+ | `apt-cache <cmd>` | | Query package cache (`search`, `show`, `policy`) |
1707
+ | `dpkg` | `-l` `-s` `-L` `-r` `-P` | Debian package manager low-level tool |
1708
+ | `dpkg-query` | `-W` `-l` | Show information about installed packages |
1709
+
1710
+ ### Shell (extended)
1711
+
1712
+ | Command | Flags | Description |
1713
+ |---------|-------|-------------|
1714
+ | `alias [name=value]` | | Define or display aliases |
1715
+ | `man <command>` | | Display command manual page |
1716
+ | `type <command>` | | Describe how command is interpreted |
1717
+ | `unalias <name>` | `-a` | Remove alias definitions |
1718
+ | `which <command>` | | Locate command in PATH |
1719
+
1416
1720
  ### Users & Permissions
1417
1721
 
1418
1722
  | Command | Flags | Description |
@@ -1789,9 +2093,32 @@ MIT — see [LICENSE](./LICENSE).
1789
2093
  - [x] New commands: `sort`, `uniq`, `tee`, `cut`, `tr`, `xargs`, `diff`, `sed`, `awk`, `tar`, `gzip`, `gunzip`, `base64`, `date`, `sleep`, `id`, `groups`, `uname`, `ps`, `kill`, `df`, `du`, `ping`
1790
2094
  - [x] Structured event hooks (session open/close, file write, sudo challenge)
1791
2095
  - [x] Binary snapshot format (VFSB) — replaces JSON+base64, ~27% smaller, no string parsing overhead, backward-compatible JSON migration
2096
+ - [x] Linux rootfs on boot — `/etc`, `/proc`, `/sys`, `/dev`, `/usr`, `/var` populated at init; `os-release`, `passwd`, `hosts`, `/proc/meminfo`, `/proc/cpuinfo`, `/proc/version`, `/proc/uptime`, `/sys/devices/virtual/dmi/`, symlinks `/bin`→`/usr/bin`
2097
+ - [x] Virtual package manager — `apt install/remove/purge/update/upgrade/search/show/list`, `apt-get`, `apt-cache`, `dpkg`, `dpkg-query`; 25 packages in registry; writes files to VFS; `/var/lib/dpkg/status` persistence
2098
+ - [x] `curl` / `wget` reimplemented as pure `fetch()` — no host binary spawned, full isolation
2099
+ - [x] New commands: `which`, `type`, `man`, `uptime`, `free`, `lsb_release`, `alias`, `unalias`
2100
+ - [x] `$(cmd)` command substitution in variable expansion
2101
+ - [x] Alias expansion in command dispatch
2102
+ - [x] `neofetch` shows real package count and shell uptime
2103
+ - [x] `syncPasswd()` / `refreshProcFs()` public API on `VirtualShell`
2104
+ - [x] `test` / `[` — full POSIX conditional expressions (`-f`, `-d`, `-e`, `-z`, `-n`, `-x`, `-s`, `-L`, `=`, `!=`, `-eq`, `-lt`, `-gt`, `-le`, `-ge`, `!`, `-a`, `-o`)
2105
+ - [x] `source` / `.` — execute file in current shell env (aliases and exports persist across commands)
2106
+ - [x] `history [n]` — display command history from VFS `.bash_history`
2107
+ - [x] `echo -e` / `echo -n` — escape sequence interpretation and newline suppression
2108
+ - [x] `ls -a` — show dotfiles
2109
+ - [x] `chmod` symbolic modes — `+x`, `u+x`, `go-w`, `a=rx`, comma-separated
2110
+ - [x] `cat -n` / `-b` — line numbering, multi-file concatenation, stdin support
2111
+ - [x] `ping -c N` — respects packet count flag
2112
+ - [x] `ps -u` / `ps aux` — extended format with USER/PID/%CPU/%MEM/VSZ/RSS columns
2113
+ - [x] `$(cmd)` in single-quoted args preserved — `sh -c 'echo $(whoami)'` now works correctly
2114
+ - [x] Pipeline executor: `runCommandDirect` — args with `;`, `|`, `>` no longer re-parsed
2115
+ - [x] `>>` append redirect fixed — was broken because `echo` lacked terminal newline
2116
+ - [x] `export A=1 && echo $A` — env vars visible to subsequent commands in same pipeline
2117
+ - [x] `echo` uses session `env.vars` (not stale global store)
1792
2118
  - [ ] Snapshot diff tooling for test assertions
1793
2119
  - [ ] WebSocket-based remote shell client (experimental)
1794
- - [ ] `$(cmd)` command substitution in variable expansion
2120
+ - [ ] Package stubs that simulate REPL behavior (node, python3)
2121
+ - [ ] `/proc/self` and `/proc/<pid>` per-session process entries
1795
2122
 
1796
2123
  ---
1797
2124
 
@@ -1,4 +1,4 @@
1
- import { runCommand as runSingleCommand } from "../commands";
1
+ import { runCommandDirect } from "../commands";
2
2
  import { resolvePath } from "../commands/helpers";
3
3
  // ── Script executor (handles &&/||/;) ────────────────────────────────────────
4
4
  export async function executeScript(script, authUser, hostname, mode, cwd, shell, env) {
@@ -71,8 +71,7 @@ async function executeSingleCommandWithRedirections(cmd, authUser, hostname, mod
71
71
  return { stderr: `${cmd.inputFile}: No such file or directory`, exitCode: 1 };
72
72
  }
73
73
  }
74
- const rawInput = [cmd.name, ...cmd.args].join(" ");
75
- const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, stdin, env);
74
+ const result = await runCommandDirect(cmd.name, cmd.args, authUser, hostname, mode, cwd, shell, stdin, env);
76
75
  if (cmd.outputFile) {
77
76
  const outputPath = resolvePath(cwd, cmd.outputFile);
78
77
  const output = result.stdout || "";
@@ -111,8 +110,7 @@ async function executePipelineChain(commands, authUser, hostname, mode, cwd, she
111
110
  return { stderr: `${cmd.inputFile}: No such file or directory`, exitCode: 1 };
112
111
  }
113
112
  }
114
- const rawInput = [cmd.name, ...cmd.args].join(" ");
115
- const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, currentOutput, env);
113
+ const result = await runCommandDirect(cmd.name, cmd.args, authUser, hostname, mode, cwd, shell, currentOutput, env);
116
114
  exitCode = result.exitCode ?? 0;
117
115
  if (i === commands.length - 1 && cmd.outputFile) {
118
116
  const outputPath = resolvePath(cwd, cmd.outputFile);