typescript-virtual-container 1.2.8 → 1.3.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 (307) hide show
  1. package/.vscode/settings.json +0 -1
  2. package/README.md +462 -44
  3. package/biome.json +7 -0
  4. package/dist/SSHMimic/exec.d.ts.map +1 -1
  5. package/dist/SSHMimic/executor.d.ts.map +1 -1
  6. package/dist/SSHMimic/executor.js +35 -21
  7. package/dist/SSHMimic/index.d.ts.map +1 -1
  8. package/dist/SSHMimic/index.js +20 -6
  9. package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
  10. package/dist/VirtualFileSystem/binaryPack.js +29 -6
  11. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem/index.js +36 -13
  13. package/dist/VirtualPackageManager/index.d.ts +202 -0
  14. package/dist/VirtualPackageManager/index.d.ts.map +1 -0
  15. package/dist/VirtualPackageManager/index.js +825 -0
  16. package/dist/VirtualShell/index.d.ts +93 -12
  17. package/dist/VirtualShell/index.d.ts.map +1 -1
  18. package/dist/VirtualShell/index.js +95 -13
  19. package/dist/VirtualShell/shell.d.ts.map +1 -1
  20. package/dist/VirtualShell/shell.js +3 -1
  21. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  22. package/dist/VirtualUserManager/index.d.ts +52 -20
  23. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  24. package/dist/VirtualUserManager/index.js +54 -20
  25. package/dist/commands/adduser.d.ts +6 -0
  26. package/dist/commands/adduser.d.ts.map +1 -1
  27. package/dist/commands/adduser.js +6 -0
  28. package/dist/commands/alias.d.ts +9 -0
  29. package/dist/commands/alias.d.ts.map +1 -0
  30. package/dist/commands/alias.js +63 -0
  31. package/dist/commands/apt.d.ts +9 -0
  32. package/dist/commands/apt.d.ts.map +1 -0
  33. package/dist/commands/apt.js +205 -0
  34. package/dist/commands/awk.d.ts +11 -0
  35. package/dist/commands/awk.d.ts.map +1 -1
  36. package/dist/commands/awk.js +15 -2
  37. package/dist/commands/base64.d.ts +5 -0
  38. package/dist/commands/base64.d.ts.map +1 -1
  39. package/dist/commands/base64.js +9 -1
  40. package/dist/commands/cat.d.ts +5 -0
  41. package/dist/commands/cat.d.ts.map +1 -1
  42. package/dist/commands/cat.js +35 -8
  43. package/dist/commands/cd.d.ts +5 -0
  44. package/dist/commands/cd.d.ts.map +1 -1
  45. package/dist/commands/cd.js +5 -0
  46. package/dist/commands/chmod.d.ts +5 -0
  47. package/dist/commands/chmod.d.ts.map +1 -1
  48. package/dist/commands/chmod.js +57 -3
  49. package/dist/commands/command-helpers.d.ts +78 -4
  50. package/dist/commands/command-helpers.d.ts.map +1 -1
  51. package/dist/commands/command-helpers.js +78 -4
  52. package/dist/commands/cp.d.ts +5 -0
  53. package/dist/commands/cp.d.ts.map +1 -1
  54. package/dist/commands/cp.js +5 -0
  55. package/dist/commands/curl.d.ts +5 -0
  56. package/dist/commands/curl.d.ts.map +1 -1
  57. package/dist/commands/curl.js +106 -26
  58. package/dist/commands/cut.d.ts +5 -0
  59. package/dist/commands/cut.d.ts.map +1 -1
  60. package/dist/commands/cut.js +8 -1
  61. package/dist/commands/date.d.ts +5 -0
  62. package/dist/commands/date.d.ts.map +1 -1
  63. package/dist/commands/date.js +7 -1
  64. package/dist/commands/declare.d.ts +3 -0
  65. package/dist/commands/declare.d.ts.map +1 -0
  66. package/dist/commands/declare.js +39 -0
  67. package/dist/commands/diff.d.ts +5 -0
  68. package/dist/commands/diff.d.ts.map +1 -1
  69. package/dist/commands/diff.js +5 -0
  70. package/dist/commands/dpkg.d.ts +9 -0
  71. package/dist/commands/dpkg.d.ts.map +1 -0
  72. package/dist/commands/dpkg.js +161 -0
  73. package/dist/commands/du.d.ts.map +1 -1
  74. package/dist/commands/du.js +8 -2
  75. package/dist/commands/echo.d.ts +5 -0
  76. package/dist/commands/echo.d.ts.map +1 -1
  77. package/dist/commands/echo.js +33 -12
  78. package/dist/commands/env.d.ts +5 -0
  79. package/dist/commands/env.d.ts.map +1 -1
  80. package/dist/commands/env.js +11 -1
  81. package/dist/commands/exit.d.ts +5 -0
  82. package/dist/commands/exit.d.ts.map +1 -1
  83. package/dist/commands/exit.js +12 -2
  84. package/dist/commands/export.d.ts.map +1 -1
  85. package/dist/commands/export.js +3 -1
  86. package/dist/commands/find.d.ts +5 -0
  87. package/dist/commands/find.d.ts.map +1 -1
  88. package/dist/commands/find.js +5 -0
  89. package/dist/commands/free.d.ts +8 -0
  90. package/dist/commands/free.d.ts.map +1 -0
  91. package/dist/commands/free.js +43 -0
  92. package/dist/commands/grep.d.ts +5 -0
  93. package/dist/commands/grep.d.ts.map +1 -1
  94. package/dist/commands/grep.js +12 -2
  95. package/dist/commands/gzip.d.ts +5 -0
  96. package/dist/commands/gzip.d.ts.map +1 -1
  97. package/dist/commands/gzip.js +18 -2
  98. package/dist/commands/head.d.ts +5 -0
  99. package/dist/commands/head.d.ts.map +1 -1
  100. package/dist/commands/head.js +5 -0
  101. package/dist/commands/help.d.ts.map +1 -1
  102. package/dist/commands/help.js +98 -45
  103. package/dist/commands/helpers.d.ts +3 -0
  104. package/dist/commands/helpers.d.ts.map +1 -1
  105. package/dist/commands/helpers.js +3 -0
  106. package/dist/commands/history.d.ts +8 -0
  107. package/dist/commands/history.d.ts.map +1 -0
  108. package/dist/commands/history.js +26 -0
  109. package/dist/commands/hostname.d.ts +5 -0
  110. package/dist/commands/hostname.d.ts.map +1 -1
  111. package/dist/commands/hostname.js +5 -0
  112. package/dist/commands/id.d.ts.map +1 -1
  113. package/dist/commands/id.js +4 -1
  114. package/dist/commands/index.d.ts +2 -10
  115. package/dist/commands/index.d.ts.map +1 -1
  116. package/dist/commands/index.js +2 -231
  117. package/dist/commands/ls.d.ts.map +1 -1
  118. package/dist/commands/ls.js +6 -3
  119. package/dist/commands/lsb-release.d.ts +3 -0
  120. package/dist/commands/lsb-release.d.ts.map +1 -0
  121. package/dist/commands/lsb-release.js +56 -0
  122. package/dist/commands/man.d.ts +3 -0
  123. package/dist/commands/man.d.ts.map +1 -0
  124. package/dist/commands/man.js +155 -0
  125. package/dist/commands/nano.js +1 -1
  126. package/dist/commands/neofetch.d.ts.map +1 -1
  127. package/dist/commands/neofetch.js +6 -1
  128. package/dist/commands/node.d.ts +9 -0
  129. package/dist/commands/node.d.ts.map +1 -0
  130. package/dist/commands/node.js +316 -0
  131. package/dist/commands/npm.d.ts +19 -0
  132. package/dist/commands/npm.d.ts.map +1 -0
  133. package/dist/commands/npm.js +109 -0
  134. package/dist/commands/ping.d.ts.map +1 -1
  135. package/dist/commands/ping.js +7 -2
  136. package/dist/commands/printf.d.ts +3 -0
  137. package/dist/commands/printf.d.ts.map +1 -0
  138. package/dist/commands/printf.js +113 -0
  139. package/dist/commands/ps.d.ts.map +1 -1
  140. package/dist/commands/ps.js +30 -6
  141. package/dist/commands/python.d.ts +30 -0
  142. package/dist/commands/python.d.ts.map +1 -0
  143. package/dist/commands/python.js +2058 -0
  144. package/dist/commands/read.d.ts +3 -0
  145. package/dist/commands/read.d.ts.map +1 -0
  146. package/dist/commands/read.js +34 -0
  147. package/dist/commands/registry.d.ts +8 -0
  148. package/dist/commands/registry.d.ts.map +1 -0
  149. package/dist/commands/registry.js +229 -0
  150. package/dist/commands/runtime.d.ts +6 -0
  151. package/dist/commands/runtime.d.ts.map +1 -0
  152. package/dist/commands/runtime.js +280 -0
  153. package/dist/commands/sed.d.ts.map +1 -1
  154. package/dist/commands/sed.js +11 -3
  155. package/dist/commands/set.d.ts.map +1 -1
  156. package/dist/commands/set.js +9 -3
  157. package/dist/commands/sh.d.ts.map +1 -1
  158. package/dist/commands/sh.js +69 -30
  159. package/dist/commands/shift.d.ts +5 -0
  160. package/dist/commands/shift.d.ts.map +1 -0
  161. package/dist/commands/shift.js +52 -0
  162. package/dist/commands/sleep.d.ts.map +1 -1
  163. package/dist/commands/sort.d.ts.map +1 -1
  164. package/dist/commands/sort.js +4 -2
  165. package/dist/commands/source.d.ts +3 -0
  166. package/dist/commands/source.d.ts.map +1 -0
  167. package/dist/commands/source.js +34 -0
  168. package/dist/commands/sudo.js +1 -1
  169. package/dist/commands/tar.d.ts.map +1 -1
  170. package/dist/commands/tar.js +11 -3
  171. package/dist/commands/tee.d.ts.map +1 -1
  172. package/dist/commands/tee.js +8 -6
  173. package/dist/commands/test.d.ts +3 -0
  174. package/dist/commands/test.d.ts.map +1 -0
  175. package/dist/commands/test.js +114 -0
  176. package/dist/commands/tr.d.ts.map +1 -1
  177. package/dist/commands/tr.js +3 -1
  178. package/dist/commands/true.d.ts +4 -0
  179. package/dist/commands/true.d.ts.map +1 -0
  180. package/dist/commands/true.js +14 -0
  181. package/dist/commands/type.d.ts +3 -0
  182. package/dist/commands/type.d.ts.map +1 -0
  183. package/dist/commands/type.js +34 -0
  184. package/dist/commands/uname.d.ts.map +1 -1
  185. package/dist/commands/uname.js +4 -1
  186. package/dist/commands/uniq.d.ts.map +1 -1
  187. package/dist/commands/uptime.d.ts +3 -0
  188. package/dist/commands/uptime.d.ts.map +1 -0
  189. package/dist/commands/uptime.js +43 -0
  190. package/dist/commands/wget.d.ts.map +1 -1
  191. package/dist/commands/wget.js +92 -96
  192. package/dist/commands/which.d.ts +3 -0
  193. package/dist/commands/which.d.ts.map +1 -0
  194. package/dist/commands/which.js +32 -0
  195. package/dist/commands/xargs.d.ts.map +1 -1
  196. package/dist/commands/xargs.js +1 -1
  197. package/dist/index.d.ts +15 -11
  198. package/dist/index.d.ts.map +1 -1
  199. package/dist/index.js +9 -8
  200. package/dist/modules/linuxRootfs.d.ts +41 -0
  201. package/dist/modules/linuxRootfs.d.ts.map +1 -0
  202. package/dist/modules/linuxRootfs.js +440 -0
  203. package/dist/modules/neofetch.d.ts.map +1 -1
  204. package/dist/modules/neofetch.js +1 -0
  205. package/dist/standalone-wo-sftp.d.ts +2 -0
  206. package/dist/standalone-wo-sftp.d.ts.map +1 -0
  207. package/dist/standalone-wo-sftp.js +30 -0
  208. package/dist/utils/expand.d.ts +50 -0
  209. package/dist/utils/expand.d.ts.map +1 -0
  210. package/dist/utils/expand.js +183 -0
  211. package/dist/utils/vfsDiff.d.ts +90 -0
  212. package/dist/utils/vfsDiff.d.ts.map +1 -0
  213. package/dist/utils/vfsDiff.js +177 -0
  214. package/package.json +3 -1
  215. package/src/SSHMimic/exec.ts +10 -1
  216. package/src/SSHMimic/executor.ts +105 -21
  217. package/src/SSHMimic/index.ts +49 -15
  218. package/src/VirtualFileSystem/binaryPack.ts +35 -8
  219. package/src/VirtualFileSystem/index.ts +78 -28
  220. package/src/VirtualPackageManager/index.ts +979 -0
  221. package/src/VirtualShell/index.ts +133 -14
  222. package/src/VirtualShell/shell.ts +23 -3
  223. package/src/VirtualShell/shellParser.ts +134 -36
  224. package/src/VirtualUserManager/index.ts +62 -22
  225. package/src/commands/adduser.ts +6 -0
  226. package/src/commands/alias.ts +64 -0
  227. package/src/commands/apt.ts +228 -0
  228. package/src/commands/awk.ts +20 -6
  229. package/src/commands/base64.ts +13 -2
  230. package/src/commands/cat.ts +40 -8
  231. package/src/commands/cd.ts +5 -0
  232. package/src/commands/chmod.ts +53 -3
  233. package/src/commands/command-helpers.ts +78 -4
  234. package/src/commands/cp.ts +5 -0
  235. package/src/commands/curl.ts +118 -33
  236. package/src/commands/cut.ts +8 -1
  237. package/src/commands/date.ts +7 -1
  238. package/src/commands/declare.ts +44 -0
  239. package/src/commands/diff.ts +17 -3
  240. package/src/commands/dpkg.ts +180 -0
  241. package/src/commands/du.ts +17 -5
  242. package/src/commands/echo.ts +41 -12
  243. package/src/commands/env.ts +11 -1
  244. package/src/commands/exit.ts +12 -2
  245. package/src/commands/export.ts +3 -1
  246. package/src/commands/find.ts +5 -0
  247. package/src/commands/free.ts +47 -0
  248. package/src/commands/grep.ts +12 -2
  249. package/src/commands/gzip.ts +28 -4
  250. package/src/commands/head.ts +5 -0
  251. package/src/commands/help.ts +121 -47
  252. package/src/commands/helpers.ts +8 -0
  253. package/src/commands/history.ts +34 -0
  254. package/src/commands/hostname.ts +5 -0
  255. package/src/commands/id.ts +4 -1
  256. package/src/commands/index.ts +9 -255
  257. package/src/commands/ls.ts +6 -3
  258. package/src/commands/lsb-release.ts +58 -0
  259. package/src/commands/man.ts +166 -0
  260. package/src/commands/nano.ts +1 -1
  261. package/src/commands/neofetch.ts +6 -1
  262. package/src/commands/node.ts +341 -0
  263. package/src/commands/npm.ts +132 -0
  264. package/src/commands/ping.ts +10 -3
  265. package/src/commands/printf.ts +112 -0
  266. package/src/commands/ps.ts +40 -6
  267. package/src/commands/python.ts +2229 -0
  268. package/src/commands/read.ts +41 -0
  269. package/src/commands/registry.ts +244 -0
  270. package/src/commands/runtime.ts +353 -0
  271. package/src/commands/sed.ts +27 -9
  272. package/src/commands/set.ts +9 -3
  273. package/src/commands/sh.ts +170 -44
  274. package/src/commands/shift.ts +53 -0
  275. package/src/commands/sleep.ts +2 -1
  276. package/src/commands/sort.ts +10 -6
  277. package/src/commands/source.ts +47 -0
  278. package/src/commands/sudo.ts +1 -1
  279. package/src/commands/tar.ts +28 -7
  280. package/src/commands/tee.ts +7 -1
  281. package/src/commands/test.ts +135 -0
  282. package/src/commands/tr.ts +3 -1
  283. package/src/commands/true.ts +17 -0
  284. package/src/commands/type.ts +43 -0
  285. package/src/commands/uname.ts +5 -1
  286. package/src/commands/uniq.ts +8 -2
  287. package/src/commands/uptime.ts +49 -0
  288. package/src/commands/wget.ts +105 -119
  289. package/src/commands/which.ts +37 -0
  290. package/src/commands/xargs.ts +11 -2
  291. package/src/index.ts +27 -18
  292. package/src/modules/linuxRootfs.ts +642 -0
  293. package/src/modules/neofetch.ts +1 -0
  294. package/src/standalone-wo-sftp.ts +38 -0
  295. package/src/utils/expand.ts +238 -0
  296. package/src/utils/vfsDiff.ts +275 -0
  297. package/standalone-wo-sftp.js +507 -0
  298. package/standalone-wo-sftp.js.map +7 -0
  299. package/standalone.js +486 -109
  300. package/standalone.js.map +4 -4
  301. package/tests/bun-test-shim.ts +9 -1
  302. package/tests/command-helpers.test.ts +1 -5
  303. package/tests/new-features.test.ts +1036 -0
  304. package/tests/parser-executor.test.ts +27 -27
  305. package/tests/sftp.test.ts +122 -42
  306. package/tests/users.test.ts +23 -5
  307. package/CHANGELOG.md +0 -150
@@ -12,7 +12,6 @@
12
12
 
13
13
  "LICENSE": true,
14
14
  "*.md": true,
15
- "biome.json": true,
16
15
  "tsconfig.tsbuildinfo": true
17
16
  }
18
17
  }
package/README.md CHANGED
@@ -1,6 +1,6 @@
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
4
 
5
5
  [![npm version](https://badge.fury.io/js/typescript-virtual-container.svg)](https://www.npmjs.com/package/typescript-virtual-container)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -23,10 +23,13 @@
23
23
  - [VirtualFileSystem](#virtualfilesystem)
24
24
  - [VFSB Binary Format](#vfsb-binary-format)
25
25
  - [VirtualUserManager](#virtualusermanager)
26
+ - [VirtualPackageManager](#virtualpackagemanager)
26
27
  - [HoneyPot](#honeypot)
27
28
  - [SshClient](#sshclient-programmatic-api)
28
29
  - [Key Types](#key-types)
29
30
  - [Usage Examples](#usage-examples)
31
+ - [Linux Rootfs](#linux-rootfs)
32
+ - [Package Manager (apt/dpkg)](#package-manager-aptdpkg)
30
33
  - [Built-in Commands](#built-in-commands)
31
34
  - [Shell Scripting](#shell-scripting)
32
35
  - [Configuration](#configuration)
@@ -57,7 +60,11 @@
57
60
  - **`.bashrc` support**: Loaded automatically at interactive session start from `/home/<user>/.bashrc`.
58
61
  - **Event-Driven Architecture**: All core classes extend `EventEmitter` for lifecycle and operation tracking.
59
62
  - **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.
63
+ - **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.
64
+ - **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`.
65
+ - **87 Built-in Commands**: Full navigation, text processing, archiving, system info, package management, and user management commands — grouped and documented in the interactive `help` system.
66
+ - **`$(cmd)` command substitution**: Nested command execution in any argument position.
67
+ - **Alias support**: `alias`, `unalias` — persisted in session environment.
61
68
  - **Full TypeScript Support**: Complete JSDoc coverage, exported types, and first-class async/await for all operations.
62
69
 
63
70
  ---
@@ -74,10 +81,10 @@
74
81
  ### What This Is Not
75
82
 
76
83
  - 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
84
  - Not a kernel-level isolation boundary (unlike Docker/VM-based isolation).
85
+ - 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
86
 
80
- This project emulates shell behavior for developer workflows. It is designed for realism and productivity, not hard security isolation.
87
+ 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
88
 
82
89
  ---
83
90
 
@@ -382,12 +389,14 @@ new VirtualShell(
382
389
 
383
390
  ```typescript
384
391
  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"
392
+ kernel: string; // Kernel version string — shown by uname, neofetch, /proc/version
393
+ os: string; // Full OS description — shown by neofetch, /etc/os-release, lsb_release
394
+ arch: string; // CPU architecture label — shown by uname -m, neofetch
388
395
  }
389
396
  ```
390
397
 
398
+ 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.
399
+
391
400
  **Example:**
392
401
 
393
402
  ```typescript
@@ -407,13 +416,26 @@ const shell = new VirtualShell("typescript-vm", {
407
416
  |--------|-------------|
408
417
  | `ensureInitialized(): Promise<void>` | Await this before using the shell programmatically. |
409
418
  | `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. |
419
+ | `executeCommand(rawInput, authUser, cwd)` | Run a raw command string (supports `&&`, `\|`, `$(cmd)`, aliases). |
420
+ | `startInteractiveSession(stream, authUser, sessionId, remoteAddress, terminalSize)` | Attach an interactive PTY session to this shell. |
421
+ | `writeFileAsUser(authUser, path, content)` | Write a file on behalf of a user with quota enforcement. |
422
+ | `refreshProcFs(): void` | Refresh `/proc/uptime`, `/proc/meminfo`, `/proc/cpuinfo`, etc. from live host data. |
423
+ | `syncPasswd(): void` | Sync `/etc/passwd`, `/etc/group`, `/etc/shadow` from `VirtualUserManager` state. |
413
424
  | `getVfs(): VirtualFileSystem \| null` | Access the VFS instance. |
414
425
  | `getUsers(): VirtualUserManager \| null` | Access the user manager. |
415
426
  | `getHostname(): string` | Returns the configured hostname. |
416
427
 
428
+ #### Public fields
429
+
430
+ | Field | Type | Description |
431
+ |-------|------|-------------|
432
+ | `vfs` | `VirtualFileSystem` | Backing virtual filesystem — use for direct path operations. |
433
+ | `users` | `VirtualUserManager` | Virtual user database — auth, quotas, and session tracking. |
434
+ | `packageManager` | `VirtualPackageManager` | APT/dpkg package manager backed by the built-in registry. |
435
+ | `hostname` | `string` | Hostname shown in the shell prompt and SSH ident string. |
436
+ | `properties` | `ShellProperties` | Distro identity strings surfaced by `uname`, `neofetch`, etc. |
437
+ | `startTime` | `number` | Unix ms timestamp of shell creation — used by `uptime` and `/proc/uptime`. |
438
+
417
439
  **Custom command example:**
418
440
 
419
441
  ```typescript
@@ -665,25 +687,27 @@ new VirtualUserManager(
665
687
  |--------|-------------|
666
688
  | `initialize(): Promise<void>` | Load users/sudoers from VFS, ensure root exists. Call once on startup. |
667
689
  | `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. |
690
+ | `hasPassword(username): boolean` | Returns `true` if a non-empty password is set for the user. |
691
+ | `hashPassword(password): string` | Hash a password using scrypt (or SHA-256 with `SSH_MIMIC_FAST_PASSWORD_HASH=1`). |
692
+ | `getPasswordHash(username): string \| null` | Returns the raw stored hash for a user, or `null` if not found. |
670
693
  | `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. |
694
+ | `deleteUser(username): Promise<void>` | Delete user. Throws when user is `root` or does not exist. |
695
+ | `setPassword(username, password): Promise<void>` | Update password for an existing user. Throws when user does not exist. |
696
+ | `isSudoer(username): boolean` | Returns `true` when the user has sudo privileges. |
697
+ | `addSudoer(username): Promise<void>` | Grant sudo privileges. Throws when user does not exist. |
698
+ | `removeSudoer(username): Promise<void>` | Revoke sudo privileges. Throws when username is `root`. |
676
699
  | `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. |
700
+ | `clearQuota(username): Promise<void>` | Remove quota limit for a user. |
701
+ | `getQuotaBytes(username): number \| null` | Returns quota in bytes, or `null` if unlimited. |
679
702
  | `getUsageBytes(username): number` | Returns current usage in bytes under `/home/<user>`. |
680
703
  | `assertWriteWithinQuota(username, path, content)` | Throws if the write would exceed the user's quota. |
704
+ | `listUsers(): string[]` | Returns a sorted list of all registered usernames. |
681
705
  | `addAuthorizedKey(username, algo, data)` | Register an SSH public key for the user. |
682
706
  | `getAuthorizedKeys(username)` | Returns the list of authorized keys for a user. |
683
707
  | `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`). |
708
+ | `registerSession(username, remoteAddress): VirtualActiveSession` | Register an active session and allocate a virtual TTY. |
709
+ | `unregisterSession(sessionId): void` | Remove a session record on disconnect. Safe to call with `null`. |
710
+ | `updateSession(sessionId, username, remoteAddress): void` | Update session metadata after `su`/`sudo` identity change. |
687
711
  | `listActiveSessions(): VirtualActiveSession[]` | Returns all active sessions sorted by start time. |
688
712
 
689
713
  #### Events
@@ -712,6 +736,125 @@ users.on("session:register", ({ sessionId, username, remoteAddress }) => {
712
736
 
713
737
  ---
714
738
 
739
+
740
+ ### `VirtualPackageManager`
741
+
742
+ Simulates APT/dpkg package management backed by a built-in registry. Accessed via `shell.packageManager`.
743
+
744
+ #### Constructor
745
+
746
+ Instantiated automatically by `VirtualShell`. Not constructed directly.
747
+
748
+ #### Methods
749
+
750
+ | Method | Description |
751
+ |--------|-------------|
752
+ | `load()` | Load installed packages from `/var/lib/dpkg/status` (called on shell init) |
753
+ | `install(names, opts?)` | Install packages (resolves deps, writes files to VFS). Returns `{ output, exitCode }` |
754
+ | `remove(names, opts?)` | Remove packages. `opts.purge` also removes config files |
755
+ | `search(term)` | Search available packages by name or description |
756
+ | `show(name)` | Show dpkg-style metadata block for a package |
757
+ | `listInstalled()` | List all installed packages as `InstalledPackage[]` |
758
+ | `listAvailable()` | List all packages in the registry |
759
+ | `isInstalled(name)` | Returns `true` if package is installed |
760
+ | `installedCount()` | Count of installed packages (used by `neofetch`) |
761
+ | `findInRegistry(name)` | Look up a `PackageDefinition` by name |
762
+
763
+ #### Types
764
+
765
+ ```ts
766
+ interface PackageDefinition {
767
+ name: string;
768
+ version: string;
769
+ description: string;
770
+ files?: PackageFile[]; // written to VFS on install
771
+ depends?: string[]; // resolved recursively
772
+ onInstall?: (vfs, users) => void;
773
+ onRemove?: (vfs) => void;
774
+ }
775
+
776
+ interface InstalledPackage {
777
+ name: string;
778
+ version: string;
779
+ architecture: string;
780
+ installedAt: string; // ISO-8601
781
+ files: string[]; // paths written to VFS
782
+ }
783
+ ```
784
+
785
+ ---
786
+
787
+ ### Snapshot Diff Tooling
788
+
789
+ Three utility functions for comparing VFS snapshots in tests and deployment verification.
790
+
791
+ ```typescript
792
+ import {
793
+ diffSnapshots,
794
+ formatDiff,
795
+ assertDiff,
796
+ } from "typescript-virtual-container";
797
+ ```
798
+
799
+ #### `diffSnapshots(before, after, options?): VfsDiff`
800
+
801
+ Compares two snapshots and returns structured diff results.
802
+
803
+ ```ts
804
+ const before = shell.vfs.toSnapshot();
805
+ await client.exec("apt install vim && mkdir -p /app");
806
+ const after = shell.vfs.toSnapshot();
807
+
808
+ const diff = diffSnapshots(before, after, {
809
+ ignore: ["/proc", "/var/log"], // skip volatile paths
810
+ });
811
+
812
+ diff.clean; // false
813
+ diff.added; // [{ path: "/usr/bin/vim", type: "file" }, ...]
814
+ diff.removed; // []
815
+ diff.modified; // [{ path: "/var/lib/dpkg/status", before: "...", after: "..." }]
816
+ ```
817
+
818
+ #### `formatDiff(diff, options?): string`
819
+
820
+ Human-readable output similar to `git diff --stat`.
821
+
822
+ ```ts
823
+ console.log(formatDiff(diff));
824
+ // + /app [directory]
825
+ // + /usr/bin/vim [file]
826
+ // ~ /var/lib/dpkg/status [modified]
827
+ //
828
+ // 2 added, 1 modified
829
+
830
+ console.log(formatDiff(diff, { showContent: true, maxContentChars: 80 }));
831
+ ```
832
+
833
+ #### `assertDiff(diff, expected): void`
834
+
835
+ Throws on mismatch — designed for test suites.
836
+
837
+ ```ts
838
+ assertDiff(diff, {
839
+ added: ["/app", "/usr/bin/vim"],
840
+ modified: ["/var/lib/dpkg/status"],
841
+ });
842
+ // throws if any expected path is not in the diff
843
+ ```
844
+
845
+ #### Types
846
+
847
+ ```ts
848
+ interface VfsDiff {
849
+ added: VfsDiffEntry[]; // { path, type }
850
+ removed: VfsDiffEntry[]; // { path, type }
851
+ modified: VfsDiffModified[]; // { path, type, before, after }
852
+ clean: boolean;
853
+ }
854
+ ```
855
+
856
+ ---
857
+
715
858
  ### `HoneyPot`
716
859
 
717
860
  Comprehensive security auditing and event tracking utility. Attaches listeners to all core components to log activity, track statistics, and detect anomalies.
@@ -966,6 +1109,49 @@ interface VfsSnapshot {
966
1109
 
967
1110
  ---
968
1111
 
1112
+ ### Command Helpers
1113
+
1114
+ Three utility functions are exported from `typescript-virtual-container` to assist with argument parsing inside custom command handlers.
1115
+
1116
+ #### `ifFlag(args, flags): boolean`
1117
+
1118
+ Returns `true` when any of the given flags appear in the argument array. Matches both standalone tokens (`-s`, `--silent`) and inline forms (`--output=file`).
1119
+
1120
+ ```typescript
1121
+ import { ifFlag } from "typescript-virtual-container";
1122
+
1123
+ const recursive = ifFlag(args, ["-r", "--recursive"]);
1124
+ const silent = ifFlag(args, "-s");
1125
+ ```
1126
+
1127
+ #### `getFlag(args, flags): string | true | undefined`
1128
+
1129
+ Returns the value of a flag, `true` if the flag has no value, or `undefined` if absent.
1130
+
1131
+ ```typescript
1132
+ import { getFlag } from "typescript-virtual-container";
1133
+
1134
+ const output = getFlag(args, ["-o", "--output"]);
1135
+ // args = ["--output", "file.txt"] → "file.txt"
1136
+ // args = ["--output=file.txt"] → "file.txt"
1137
+ // args = ["--verbose"] → true (when "verbose" is in flags list)
1138
+ // args = [] → undefined
1139
+ ```
1140
+
1141
+ #### `getArg(args, index, options?): string | undefined`
1142
+
1143
+ Returns the positional argument at a given zero-based index, skipping known flags and their values.
1144
+
1145
+ ```typescript
1146
+ import { getArg } from "typescript-virtual-container";
1147
+
1148
+ // args = ["-r", "src", "dest"]
1149
+ const src = getArg(args, 0, { flags: ["-r"] }); // "src"
1150
+ const dest = getArg(args, 1, { flags: ["-r"] }); // "dest"
1151
+ ```
1152
+
1153
+ ---
1154
+
969
1155
  ## Usage Examples
970
1156
 
971
1157
  ### Example 1: Basic SSH Server
@@ -1316,6 +1502,148 @@ const [r1, r2] = await Promise.all([
1316
1502
  ]);
1317
1503
  ```
1318
1504
 
1505
+ ---
1506
+
1507
+ ## Linux Rootfs
1508
+
1509
+ 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.
1510
+
1511
+ ### Directory layout
1512
+
1513
+ ```
1514
+ /
1515
+ ├── bin -> /usr/bin (symlink, Debian-style)
1516
+ ├── dev/ null, zero, random, urandom, pts/, shm/
1517
+ ├── etc/
1518
+ │ ├── apt/sources.list Fortune package sources
1519
+ │ ├── debian_version
1520
+ │ ├── group synced from VirtualUserManager
1521
+ │ ├── hostname
1522
+ │ ├── hosts 127.0.0.1 localhost + VM hostname
1523
+ │ ├── motd
1524
+ │ ├── os-release NAME="Fortune GNU/Linux" + ShellProperties
1525
+ │ ├── passwd synced from VirtualUserManager
1526
+ │ ├── resolv.conf 1.1.1.1 + 8.8.8.8
1527
+ │ └── shadow (mode 0o640)
1528
+ ├── proc/
1529
+ │ ├── 1/ init process (cmdline, status, comm, environ, fd/)
1530
+ │ ├── <pid>/ one entry per active session (based on pts/* TTY)
1531
+ │ ├── self/ mirrors the most recent session's /proc/<pid>/
1532
+ │ ├── cpuinfo real host CPU info
1533
+ │ ├── loadavg
1534
+ │ ├── meminfo real host memory
1535
+ │ ├── net/dev eth0 + lo
1536
+ │ ├── uptime shell uptime in seconds
1537
+ │ └── version kernel from ShellProperties
1538
+ ├── root/ root home + .bashrc
1539
+ ├── sys/devices/virtual/dmi/id/
1540
+ │ ├── sys_vendor "Fortune Systems"
1541
+ │ └── product_name "VirtualContainer v1"
1542
+ ├── tmp/ (mode 0o1777 sticky)
1543
+ ├── usr/bin/ stubs for all built-in commands
1544
+ ├── var/
1545
+ │ ├── lib/dpkg/status managed by VirtualPackageManager
1546
+ │ └── log/ syslog, auth.log, dpkg.log, apt/
1547
+ └── ...
1548
+ ```
1549
+
1550
+ ### API
1551
+
1552
+ ```ts
1553
+ shell.refreshProcFs(); // refresh /proc/* with current system state
1554
+ shell.syncPasswd(); // sync /etc/passwd|group|shadow from VirtualUserManager
1555
+ ```
1556
+
1557
+ `syncPasswd()` is called automatically on `bootstrapLinuxRootfs`. Call it again after `users.addUser()` / `users.deleteUser()` to keep `/etc/passwd` consistent.
1558
+
1559
+ ---
1560
+
1561
+ ## Package Manager (apt/dpkg)
1562
+
1563
+ A pure-TypeScript APT/dpkg simulation backed by a built-in package registry. No external process is spawned.
1564
+
1565
+ ### Workflow
1566
+
1567
+ ```
1568
+ apt install <pkg> → resolves deps → writes files to VFS → updates /var/lib/dpkg/status
1569
+ apt remove <pkg> → removes VFS files → updates status
1570
+ dpkg -l → reads installed packages from VirtualPackageManager
1571
+ ```
1572
+
1573
+ ### Built-in package registry (25 packages)
1574
+
1575
+ | Package | Version | Section |
1576
+ |---------|---------|---------|
1577
+ | `vim` | 2:9.0.1378-2 | editors |
1578
+ | `git` | 1:2.39.2-1 | vcs |
1579
+ | `python3` | 3.11.2-1 | python |
1580
+ | `nodejs` | 18.19.0 | javascript |
1581
+ | `npm` | 9.2.0 | javascript |
1582
+ | `curl` | 7.88.1-10 | web |
1583
+ | `wget` | 1.21.3-1 | web |
1584
+ | `htop` | 3.2.2-2 | utils |
1585
+ | `openssh-client` | 1:9.2p1-2 | net |
1586
+ | `openssh-server` | 1:9.2p1-2 | net |
1587
+ | `net-tools` | 2.10-0.1 | net |
1588
+ | `iputils-ping` | 3:20221126-1 | net |
1589
+ | `jq` | 1.6-2.1 | utils |
1590
+ | `build-essential` | 12.9 | devel |
1591
+ | `gcc` | 4:12.2.0-3 | devel |
1592
+ | `g++` | 4:12.2.0-3 | devel |
1593
+ | `make` | 4.3-4.1 | devel |
1594
+ | `less` | 590-2 | text |
1595
+ | `unzip` | 6.0-28 | utils |
1596
+ | `rsync` | 3.2.7-1 | net |
1597
+ | `tmux` | 3.3a-3 | utils |
1598
+ | `tree` | 2.1.0-1 | utils |
1599
+ | `ca-certificates` | 20230311 | misc |
1600
+ | `sudo` | 1.9.13p3-1 | admin |
1601
+ | `systemd` | 252.22-1 | admin |
1602
+
1603
+ > **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.
1604
+
1605
+ ### `VirtualPackageManager` API
1606
+
1607
+ ```ts
1608
+ // Access via shell instance
1609
+ const pm = shell.packageManager;
1610
+
1611
+ pm.install(["vim", "git"], { quiet: false }) // → { output, exitCode }
1612
+ pm.remove(["vim"], { purge: false }) // → { output, exitCode }
1613
+ pm.search("editor") // → PackageDefinition[]
1614
+ pm.show("vim") // → string (dpkg-style block)
1615
+ pm.listInstalled() // → InstalledPackage[]
1616
+ pm.listAvailable() // → PackageDefinition[]
1617
+ pm.isInstalled("vim") // → boolean
1618
+ pm.installedCount() // → number
1619
+ pm.findInRegistry("nodejs") // → PackageDefinition | undefined
1620
+ ```
1621
+
1622
+ ### Registering custom packages
1623
+
1624
+ ```ts
1625
+ import { VirtualPackageManager } from "typescript-virtual-container";
1626
+
1627
+ // Custom packages can be added to the registry before shell init
1628
+ // by extending PACKAGE_REGISTRY or by calling install() with custom defs.
1629
+
1630
+ // Or: register a post-install hook via onInstall
1631
+ const customPkg = {
1632
+ name: "myapp",
1633
+ version: "1.0.0",
1634
+ description: "My application",
1635
+ files: [
1636
+ { path: "/usr/bin/myapp", content: "#!/bin/sh\necho myapp v1.0.0\n", mode: 0o755 },
1637
+ { path: "/etc/myapp/config.json", content: JSON.stringify({ port: 3000 }) },
1638
+ ],
1639
+ onInstall: (vfs) => {
1640
+ vfs.mkdir("/var/lib/myapp", 0o755);
1641
+ vfs.mkdir("/var/log/myapp", 0o755);
1642
+ },
1643
+ };
1644
+ ```
1645
+
1646
+
1319
1647
  ---
1320
1648
 
1321
1649
  ## Built-in Commands
@@ -1327,7 +1655,7 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
1327
1655
  | Command | Flags | Description |
1328
1656
  |---------|-------|-------------|
1329
1657
  | `cd <path>` | | Change directory |
1330
- | `ls [path]` | `-l` | List directory contents |
1658
+ | `ls [path]` | `-l` `-a` | List directory contents (`-a` shows dotfiles) |
1331
1659
  | `pwd` | | Print working directory |
1332
1660
  | `tree [path]` | | ASCII directory tree |
1333
1661
 
@@ -1335,14 +1663,15 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
1335
1663
 
1336
1664
  | Command | Flags | Description |
1337
1665
  |---------|-------|-------------|
1338
- | `cat <path>` | | Print file contents |
1339
- | `chmod <mode> <file>` | | Change file permissions (octal) |
1666
+ | `cat <path...>` | `-n` `-b` | Concatenate and print files; `-n` numbers all lines, `-b` numbers non-blank lines |
1667
+ | `chmod <mode> <file>` | | Change file permissions — octal (`755`) or symbolic (`+x`, `u+x`, `go-w`, `a=rx`) |
1340
1668
  | `cp <src> <dest>` | `-r` | Copy file or directory |
1341
1669
  | `find [path]` | `-name <pat>` `-type f\|d` | Search for files |
1342
1670
  | `ln <target> <link>` | `-s` | Create hard or symbolic link |
1343
1671
  | `mkdir <path>` | `-p` | Create directory |
1344
1672
  | `mv <src> <dest>` | | Move or rename |
1345
1673
  | `rm <path>` | `-r` | Remove file or directory |
1674
+ | `nano <path>` | | Interactive text editor |
1346
1675
  | `touch <path>` | | Create or update file |
1347
1676
 
1348
1677
  ### Text Processing
@@ -1384,9 +1713,14 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
1384
1713
  | `htop` | | System monitor (mock) |
1385
1714
  | `id [user]` | | Print user identity (uid/gid/groups) |
1386
1715
  | `kill [-9] <pid>` | | Send signal to process (mock) |
1387
- | `neofetch` | | System info display (mock) |
1716
+ | `free` | `-h` `-m` `-g` | Display free and used memory |
1717
+ | `lsb_release` | `-a` `-i` `-d` `-r` `-c` | Print distribution information |
1718
+ | `neofetch` | | System info display (shows real packages/uptime) |
1719
+ | `node` | `--version` `-e` `-p` | JavaScript runtime (virtual REPL — evaluates expressions and VFS `.js` files) |
1720
+ | `python3` | `--version` `-c` `-V` | Python 3 interpreter (virtual REPL — evaluates expressions and VFS `.py` files); alias `python` |
1721
+ | `uptime` | `-p` `-s` | Tell how long the system has been running |
1388
1722
  | `ping [-c <n>] <host>` | | Send ICMP ECHO_REQUEST (mock) |
1389
- | `ps` | `-a` `-u` `-x` | Report process status |
1723
+ | `ps` | `-a` `-u` `-x` `aux` | Report process status; `-u` / `aux` shows USER/PID/%CPU/%MEM columns |
1390
1724
  | `sleep <seconds>` | | Delay execution |
1391
1725
  | `uname` | `-a` `-r` `-m` | Print system information |
1392
1726
  | `who` | | List active sessions |
@@ -1396,22 +1730,51 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
1396
1730
 
1397
1731
  | Command | Flags | Description |
1398
1732
  |---------|-------|-------------|
1399
- | `curl <url>` | | HTTP client (delegates to host binary) |
1400
- | `wget <url>` | | File downloader (delegates to host binary) |
1733
+ | `curl <url>` | `-o` `-X` `-d` `-H` `-s` `-I` `-L` `-v` | HTTP client (pure `fetch()`, no host binary) |
1734
+ | `wget <url>` | `-O` `-P` `-q` | File downloader (pure `fetch()`, no host binary) |
1401
1735
 
1402
1736
  ### Shell
1403
1737
 
1404
1738
  | Command | Flags | Description |
1405
1739
  |---------|-------|-------------|
1740
+ | `alias [name=value]` | | Define or display aliases |
1406
1741
  | `clear` | | Clear terminal screen (full ANSI reset) |
1407
- | `echo <text>` | | Display text |
1742
+ | `declare [name=value]` | `-i` `-r` `-x` | Declare variables with attributes; aliases `local`, `typeset` |
1743
+ | `echo <text>` | `-n` `-e` | Display text; `-n` suppresses newline, `-e` interprets `\n` `\t` `\r` `\\` |
1408
1744
  | `env` | | Print session environment variables |
1409
- | `exit [code]` | | Exit session |
1745
+ | `exit [code]` | | Exit session with optional exit code |
1410
1746
  | `export NAME=VALUE` | | Set shell variable in current session |
1411
- | `help [command]` | | List commands (grouped) or show command details |
1747
+ | `help [command]` | | List commands grouped by category, or show usage for a specific command |
1748
+ | `history [n]` | | Display command history (last N entries) |
1749
+ | `man <command>` | | Display command reference manual |
1750
+ | `printf <fmt> [args...]` | | Format and print data (`%s` `%d` `%f` `%x` `\n` `\t`) |
1751
+ | `read [-r] <var...>` | `-r` `-p` | Read a line from stdin into variable(s) |
1752
+ | `return [n]` | | Return from a shell function with optional exit code |
1412
1753
  | `set [VAR=val]` | | Display or set shell variables |
1413
- | `sh` | `-c <script>` `[file]` | Execute shell script (supports if/for/while) |
1754
+ | `sh` | `-c <script>` `[file]` | Execute shell script supports `if`/`for`/`while`/`case`/functions, bare `VAR=val` assignments, `$((expr))` arithmetic; single-quoted args are never expanded |
1755
+ | `shift [n]` | | Shift positional parameters left by n (default 1) |
1756
+ | `source <file>` | | Execute file in current shell environment; aliases and exports persist |
1757
+ | `. <file>` | | Alias for `source` |
1758
+ | `test <expr>` | | Evaluate POSIX conditional expression |
1759
+ | `[ <expr> ]` | | Alias for `test`; supports `-f` `-d` `-e` `-z` `-n` `-x` `-s` `=` `!=` `-eq` `-lt` `-gt` `-le` `-ge` `!` `-a` `-o` |
1760
+ | `trap [action] [signal]` | | Register handler for shell signals; supports `EXIT` |
1761
+ | `true` | | Return success exit code (0) |
1762
+ | `false` | | Return failure exit code (1) |
1763
+ | `type <command>` | | Describe how a command would be interpreted (builtin vs PATH) |
1764
+ | `unalias <name>` | `-a` | Remove alias definitions |
1414
1765
  | `unset <VAR>` | | Remove shell variable |
1766
+ | `which <command>` | | Locate a command in the session PATH |
1767
+
1768
+ ### Package Management
1769
+
1770
+ | Command | Flags | Description |
1771
+ |---------|-------|-------------|
1772
+ | `apt <cmd> [pkg...]` | | Package manager (`install`, `remove`, `purge`, `update`, `upgrade`, `search`, `show`, `list`) |
1773
+ | `apt-get <cmd>` | | Alias for `apt` |
1774
+ | `apt-cache <cmd>` | | Query package cache (`search`, `show`, `policy`) |
1775
+ | `dpkg` | `-l` `-s` `-L` `-r` `-P` | Debian package manager low-level tool |
1776
+ | `dpkg-query` | `-W` `-l` | Show information about installed packages |
1777
+
1415
1778
 
1416
1779
  ### Users & Permissions
1417
1780
 
@@ -1419,7 +1782,6 @@ All commands are available in SSH shell mode and via `SshClient.exec()`. Type `h
1419
1782
  |---------|-------|-------------|
1420
1783
  | `adduser <name> <pass>` | | Create user (root only) |
1421
1784
  | `deluser <name>` | | Delete user (root only) |
1422
- | `nano <path>` | | Interactive text editor |
1423
1785
  | `passwd [user]` | | Change password |
1424
1786
  | `su [user]` | | Switch user |
1425
1787
  | `sudo <cmd>` | `-i` | Run as root |
@@ -1454,11 +1816,34 @@ cat /tmp/out.txt >> /tmp/log.txt
1454
1816
  ```bash
1455
1817
  export NAME=world
1456
1818
  echo "Hello $NAME" # Hello world
1819
+ echo "${NAME}" # world (braced form)
1457
1820
  echo "${NAME:-fallback}" # world (or fallback if unset)
1458
- echo "${UNSET:-default}" # default
1459
- echo "Exit: $?" # last exit code
1821
+ echo "${UNSET:-default}" # default (assign-on-read: use ${UNSET:=default})
1822
+ echo "${NAME:+alternate}" # alternate (only if NAME is set)
1823
+ echo "${#NAME}" # 5 (string length)
1824
+ echo "$?" # last exit code
1825
+ echo "~" # /home/<user> (tilde expansion)
1826
+ ```
1827
+
1828
+ ### Arithmetic Expansion
1829
+
1830
+ ```bash
1831
+ echo $((2 + 3)) # 5
1832
+ echo $((10 % 3)) # 1
1833
+ X=4; echo $((X * 2)) # 8
1834
+ i=0; i=$((i + 1)); echo $i # 1
1835
+
1836
+ # In loops (via sh):
1837
+ sh -c 'i=0
1838
+ while [ $i -lt 5 ]; do
1839
+ echo $i
1840
+ i=$((i + 1))
1841
+ done'
1460
1842
  ```
1461
1843
 
1844
+ > **Single-quote isolation** — variable and arithmetic expansion never occurs inside single quotes.
1845
+ > `echo '$NAME'` always outputs the literal string `$NAME`.
1846
+
1462
1847
  ### Conditionals
1463
1848
 
1464
1849
  ```bash
@@ -1789,12 +2174,45 @@ MIT — see [LICENSE](./LICENSE).
1789
2174
  - [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
2175
  - [x] Structured event hooks (session open/close, file write, sudo challenge)
1791
2176
  - [x] Binary snapshot format (VFSB) — replaces JSON+base64, ~27% smaller, no string parsing overhead, backward-compatible JSON migration
1792
- - [ ] Snapshot diff tooling for test assertions
2177
+ - [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`
2178
+ - [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
2179
+ - [x] `curl` / `wget` reimplemented as pure `fetch()` — no host binary spawned, full isolation
2180
+ - [x] New commands: `which`, `type`, `man`, `uptime`, `free`, `lsb_release`, `alias`, `unalias`
2181
+ - [x] `$(cmd)` command substitution in variable expansion
2182
+ - [x] Alias expansion in command dispatch
2183
+ - [x] `neofetch` shows real package count and shell uptime
2184
+ - [x] `syncPasswd()` / `refreshProcFs()` public API on `VirtualShell`
2185
+ - [x] `test` / `[` — full POSIX conditional expressions (`-f`, `-d`, `-e`, `-z`, `-n`, `-x`, `-s`, `-L`, `=`, `!=`, `-eq`, `-lt`, `-gt`, `-le`, `-ge`, `!`, `-a`, `-o`)
2186
+ - [x] `source` / `.` — execute file in current shell env (aliases and exports persist across commands)
2187
+ - [x] `history [n]` — display command history from VFS `.bash_history`
2188
+ - [x] `echo -e` / `echo -n` — escape sequence interpretation and newline suppression
2189
+ - [x] `ls -a` — show dotfiles
2190
+ - [x] `chmod` symbolic modes — `+x`, `u+x`, `go-w`, `a=rx`, comma-separated
2191
+ - [x] `cat -n` / `-b` — line numbering, multi-file concatenation, stdin support
2192
+ - [x] `ping -c N` — respects packet count flag
2193
+ - [x] `ps -u` / `ps aux` — extended format with USER/PID/%CPU/%MEM/VSZ/RSS columns
2194
+ - [x] `$(cmd)` in single-quoted args preserved — `sh -c 'echo $(whoami)'` now works correctly
2195
+ - [x] Pipeline executor: `runCommandDirect` — args with `;`, `|`, `>` no longer re-parsed
2196
+ - [x] `>>` append redirect fixed — was broken because `echo` lacked terminal newline
2197
+ - [x] `export A=1 && echo $A` — env vars visible to subsequent commands in same pipeline
2198
+ - [x] `echo` uses session `env.vars` (not stale global store)
2199
+ - [x] `printf` — format string with `%s` `%d` `%f` `%x` `\n` `\t`
2200
+ - [x] `read` — read stdin into variables (supports multiple vars, splits on whitespace)
2201
+ - [x] `declare` / `local` / `typeset` — variable declaration with `-i` integer, `-r` readonly, `-x` export
2202
+ - [x] `shift [n]` — shift positional parameters
2203
+ - [x] `trap [action] [signal]` — signal handlers with `EXIT` support
2204
+ - [x] `return [n]` — return from shell functions
2205
+ - [x] `exit [code]` — optional exit code
2206
+ - [x] `help` rewrite — Package Management category, aliases shown inline, `help <cmd>` shows category
2207
+ - [x] Category corrections — `neofetch`→system, `nano`→files, `apt`/`dpkg`→package
2208
+ - [x] `src/utils/expand.ts` — centralised expansion module used by `runCommand`, `echo`, and `sh.ts`
2209
+ - [x] `${#VAR}` string length, `$((expr))` arithmetic, `~` tilde expansion
2210
+ - [x] `${VAR:=assign}` assign-on-read, `${VAR:+alternate}` alternate-if-set
2211
+ - [x] Single-quote isolation in expansion — `$VAR` never expanded inside `'...'`
2212
+ - [x] `true` / `false` builtins
2213
+ - [x] Bare `VAR=val` assignments in `sh` scripts (`i=0`, `i=$((i+1))`)
2214
+ - [x] `$?` reflects last command exit code correctly across `&&` / `;` chains
2215
+ - [x] `node` / `python3` virtual REPL stubs — `node -e`, `node <file>`, `python3 -c`, `python3 <file>`, `--version` flags
2216
+ - [x] `/proc/self` and `/proc/<pid>` per-session process entries — `comm`, `status`, `cmdline`, `environ`, `cwd`, `exe`, `fd/`
2217
+ - [x] Snapshot diff tooling — `diffSnapshots()`, `formatDiff()`, `assertDiff()` exported from `typescript-virtual-container`
1793
2218
  - [ ] WebSocket-based remote shell client (experimental)
1794
- - [ ] `$(cmd)` command substitution in variable expansion
1795
-
1796
- ---
1797
-
1798
- ## Changelog
1799
-
1800
- See [CHANGELOG.md](./CHANGELOG.md).