typescript-virtual-container 1.4.3 → 1.4.5

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 (382) hide show
  1. package/.vscode/settings.json +2 -1
  2. package/README.md +137 -19
  3. package/benchmark-results.txt +21 -21
  4. package/builds/self-standalone.js +322 -266
  5. package/builds/self-standalone.js.map +4 -4
  6. package/builds/standalone-wo-sftp.js +268 -212
  7. package/builds/standalone-wo-sftp.js.map +4 -4
  8. package/builds/standalone.js +268 -212
  9. package/builds/standalone.js.map +4 -4
  10. package/builds/web-full-api.min.js +5 -5
  11. package/builds/web-full-api.min.js.map +3 -3
  12. package/builds/web.min.js +5 -5
  13. package/builds/web.min.js.map +3 -3
  14. package/bun.lock +101 -6
  15. package/dist/Honeypot/index.js +1 -0
  16. package/dist/Honeypot/index.js.map +1 -0
  17. package/dist/SSHClient/index.js +1 -0
  18. package/dist/SSHClient/index.js.map +1 -0
  19. package/dist/SSHMimic/exec.js +1 -0
  20. package/dist/SSHMimic/exec.js.map +1 -0
  21. package/dist/SSHMimic/executor.js +1 -0
  22. package/dist/SSHMimic/executor.js.map +1 -0
  23. package/dist/SSHMimic/hostKey.js +1 -0
  24. package/dist/SSHMimic/hostKey.js.map +1 -0
  25. package/dist/SSHMimic/index.js +1 -0
  26. package/dist/SSHMimic/index.js.map +1 -0
  27. package/dist/SSHMimic/loginBanner.js +1 -0
  28. package/dist/SSHMimic/loginBanner.js.map +1 -0
  29. package/dist/SSHMimic/loginFormat.js +1 -0
  30. package/dist/SSHMimic/loginFormat.js.map +1 -0
  31. package/dist/SSHMimic/prompt.js +1 -0
  32. package/dist/SSHMimic/prompt.js.map +1 -0
  33. package/dist/SSHMimic/sftp.js +1 -0
  34. package/dist/SSHMimic/sftp.js.map +1 -0
  35. package/dist/VirtualFileSystem/binaryPack.js +1 -0
  36. package/dist/VirtualFileSystem/binaryPack.js.map +1 -0
  37. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  38. package/dist/VirtualFileSystem/index.js +1 -0
  39. package/dist/VirtualFileSystem/index.js.map +1 -0
  40. package/dist/VirtualFileSystem/internalTypes.js +1 -0
  41. package/dist/VirtualFileSystem/internalTypes.js.map +1 -0
  42. package/dist/VirtualFileSystem/path.js +1 -0
  43. package/dist/VirtualFileSystem/path.js.map +1 -0
  44. package/dist/VirtualPackageManager/index.js +1 -0
  45. package/dist/VirtualPackageManager/index.js.map +1 -0
  46. package/dist/VirtualShell/index.js +1 -0
  47. package/dist/VirtualShell/index.js.map +1 -0
  48. package/dist/VirtualShell/shell.js +1 -0
  49. package/dist/VirtualShell/shell.js.map +1 -0
  50. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  51. package/dist/VirtualShell/shellParser.js +3 -1
  52. package/dist/VirtualShell/shellParser.js.map +1 -0
  53. package/dist/VirtualUserManager/index.js +1 -0
  54. package/dist/VirtualUserManager/index.js.map +1 -0
  55. package/dist/commands/adduser.js +1 -0
  56. package/dist/commands/adduser.js.map +1 -0
  57. package/dist/commands/alias.js +1 -0
  58. package/dist/commands/alias.js.map +1 -0
  59. package/dist/commands/apt.js +1 -0
  60. package/dist/commands/apt.js.map +1 -0
  61. package/dist/commands/awk.d.ts.map +1 -1
  62. package/dist/commands/awk.js +2 -2
  63. package/dist/commands/awk.js.map +1 -0
  64. package/dist/commands/base64.js +1 -0
  65. package/dist/commands/base64.js.map +1 -0
  66. package/dist/commands/cat.js +1 -0
  67. package/dist/commands/cat.js.map +1 -0
  68. package/dist/commands/cd.js +3 -2
  69. package/dist/commands/cd.js.map +1 -0
  70. package/dist/commands/chmod.js +1 -0
  71. package/dist/commands/chmod.js.map +1 -0
  72. package/dist/commands/clear.js +1 -0
  73. package/dist/commands/clear.js.map +1 -0
  74. package/dist/commands/command-helpers.js +1 -0
  75. package/dist/commands/command-helpers.js.map +1 -0
  76. package/dist/commands/cp.js +1 -0
  77. package/dist/commands/cp.js.map +1 -0
  78. package/dist/commands/curl.js +1 -0
  79. package/dist/commands/curl.js.map +1 -0
  80. package/dist/commands/cut.js +1 -0
  81. package/dist/commands/cut.js.map +1 -0
  82. package/dist/commands/date.js +1 -0
  83. package/dist/commands/date.js.map +1 -0
  84. package/dist/commands/declare.js +1 -0
  85. package/dist/commands/declare.js.map +1 -0
  86. package/dist/commands/deluser.js +1 -0
  87. package/dist/commands/deluser.js.map +1 -0
  88. package/dist/commands/df.js +1 -0
  89. package/dist/commands/df.js.map +1 -0
  90. package/dist/commands/diff.js +1 -0
  91. package/dist/commands/diff.js.map +1 -0
  92. package/dist/commands/dpkg.js +1 -0
  93. package/dist/commands/dpkg.js.map +1 -0
  94. package/dist/commands/du.js +1 -0
  95. package/dist/commands/du.js.map +1 -0
  96. package/dist/commands/echo.js +1 -0
  97. package/dist/commands/echo.js.map +1 -0
  98. package/dist/commands/env.js +1 -0
  99. package/dist/commands/env.js.map +1 -0
  100. package/dist/commands/exit.js +1 -0
  101. package/dist/commands/exit.js.map +1 -0
  102. package/dist/commands/export.js +1 -0
  103. package/dist/commands/export.js.map +1 -0
  104. package/dist/commands/find.js +1 -0
  105. package/dist/commands/find.js.map +1 -0
  106. package/dist/commands/free.js +1 -0
  107. package/dist/commands/free.js.map +1 -0
  108. package/dist/commands/grep.js +1 -0
  109. package/dist/commands/grep.js.map +1 -0
  110. package/dist/commands/groups.js +1 -0
  111. package/dist/commands/groups.js.map +1 -0
  112. package/dist/commands/gzip.js +1 -0
  113. package/dist/commands/gzip.js.map +1 -0
  114. package/dist/commands/head.js +1 -0
  115. package/dist/commands/head.js.map +1 -0
  116. package/dist/commands/help.js +1 -0
  117. package/dist/commands/help.js.map +1 -0
  118. package/dist/commands/helpers.d.ts.map +1 -1
  119. package/dist/commands/helpers.js +4 -0
  120. package/dist/commands/helpers.js.map +1 -0
  121. package/dist/commands/history.js +1 -0
  122. package/dist/commands/history.js.map +1 -0
  123. package/dist/commands/hostname.js +1 -0
  124. package/dist/commands/hostname.js.map +1 -0
  125. package/dist/commands/htop.js +1 -0
  126. package/dist/commands/htop.js.map +1 -0
  127. package/dist/commands/id.js +1 -0
  128. package/dist/commands/id.js.map +1 -0
  129. package/dist/commands/index.js +1 -0
  130. package/dist/commands/index.js.map +1 -0
  131. package/dist/commands/kill.js +1 -0
  132. package/dist/commands/kill.js.map +1 -0
  133. package/dist/commands/ln.js +1 -0
  134. package/dist/commands/ln.js.map +1 -0
  135. package/dist/commands/ls.d.ts.map +1 -1
  136. package/dist/commands/ls.js +171 -37
  137. package/dist/commands/ls.js.map +1 -0
  138. package/dist/commands/lsb-release.js +1 -0
  139. package/dist/commands/lsb-release.js.map +1 -0
  140. package/dist/commands/man.js +1 -0
  141. package/dist/commands/man.js.map +1 -0
  142. package/dist/commands/mkdir.js +1 -0
  143. package/dist/commands/mkdir.js.map +1 -0
  144. package/dist/commands/mv.js +1 -0
  145. package/dist/commands/mv.js.map +1 -0
  146. package/dist/commands/nano.js +1 -0
  147. package/dist/commands/nano.js.map +1 -0
  148. package/dist/commands/neofetch.js +1 -0
  149. package/dist/commands/neofetch.js.map +1 -0
  150. package/dist/commands/node.js +1 -0
  151. package/dist/commands/node.js.map +1 -0
  152. package/dist/commands/npm.js +1 -0
  153. package/dist/commands/npm.js.map +1 -0
  154. package/dist/commands/passwd.js +1 -0
  155. package/dist/commands/passwd.js.map +1 -0
  156. package/dist/commands/ping.js +1 -0
  157. package/dist/commands/ping.js.map +1 -0
  158. package/dist/commands/printf.js +1 -0
  159. package/dist/commands/printf.js.map +1 -0
  160. package/dist/commands/ps.js +1 -0
  161. package/dist/commands/ps.js.map +1 -0
  162. package/dist/commands/pwd.js +1 -0
  163. package/dist/commands/pwd.js.map +1 -0
  164. package/dist/commands/python.js +1 -0
  165. package/dist/commands/python.js.map +1 -0
  166. package/dist/commands/read.js +1 -0
  167. package/dist/commands/read.js.map +1 -0
  168. package/dist/commands/registry.js +1 -0
  169. package/dist/commands/registry.js.map +1 -0
  170. package/dist/commands/rm.js +1 -0
  171. package/dist/commands/rm.js.map +1 -0
  172. package/dist/commands/runtime.d.ts.map +1 -1
  173. package/dist/commands/runtime.js +37 -0
  174. package/dist/commands/runtime.js.map +1 -0
  175. package/dist/commands/sed.js +1 -0
  176. package/dist/commands/sed.js.map +1 -0
  177. package/dist/commands/seq.js +1 -0
  178. package/dist/commands/seq.js.map +1 -0
  179. package/dist/commands/set.js +1 -0
  180. package/dist/commands/set.js.map +1 -0
  181. package/dist/commands/sh.d.ts.map +1 -1
  182. package/dist/commands/sh.js +9 -4
  183. package/dist/commands/sh.js.map +1 -0
  184. package/dist/commands/shift.js +1 -0
  185. package/dist/commands/shift.js.map +1 -0
  186. package/dist/commands/sleep.js +1 -0
  187. package/dist/commands/sleep.js.map +1 -0
  188. package/dist/commands/sort.js +1 -0
  189. package/dist/commands/sort.js.map +1 -0
  190. package/dist/commands/source.js +1 -0
  191. package/dist/commands/source.js.map +1 -0
  192. package/dist/commands/stat.js +1 -0
  193. package/dist/commands/stat.js.map +1 -0
  194. package/dist/commands/su.js +1 -0
  195. package/dist/commands/su.js.map +1 -0
  196. package/dist/commands/sudo.js +1 -0
  197. package/dist/commands/sudo.js.map +1 -0
  198. package/dist/commands/tail.js +1 -0
  199. package/dist/commands/tail.js.map +1 -0
  200. package/dist/commands/tar.js +1 -0
  201. package/dist/commands/tar.js.map +1 -0
  202. package/dist/commands/tee.js +1 -0
  203. package/dist/commands/tee.js.map +1 -0
  204. package/dist/commands/test.d.ts.map +1 -1
  205. package/dist/commands/test.js +1 -0
  206. package/dist/commands/test.js.map +1 -0
  207. package/dist/commands/touch.js +1 -0
  208. package/dist/commands/touch.js.map +1 -0
  209. package/dist/commands/tr.js +1 -0
  210. package/dist/commands/tr.js.map +1 -0
  211. package/dist/commands/tree.js +1 -0
  212. package/dist/commands/tree.js.map +1 -0
  213. package/dist/commands/true.js +1 -0
  214. package/dist/commands/true.js.map +1 -0
  215. package/dist/commands/type.js +1 -0
  216. package/dist/commands/type.js.map +1 -0
  217. package/dist/commands/uname.js +1 -0
  218. package/dist/commands/uname.js.map +1 -0
  219. package/dist/commands/uniq.js +1 -0
  220. package/dist/commands/uniq.js.map +1 -0
  221. package/dist/commands/unset.js +1 -0
  222. package/dist/commands/unset.js.map +1 -0
  223. package/dist/commands/uptime.js +1 -0
  224. package/dist/commands/uptime.js.map +1 -0
  225. package/dist/commands/wc.js +2 -1
  226. package/dist/commands/wc.js.map +1 -0
  227. package/dist/commands/wget.js +1 -0
  228. package/dist/commands/wget.js.map +1 -0
  229. package/dist/commands/which.js +1 -0
  230. package/dist/commands/which.js.map +1 -0
  231. package/dist/commands/who.js +1 -0
  232. package/dist/commands/who.js.map +1 -0
  233. package/dist/commands/whoami.js +1 -0
  234. package/dist/commands/whoami.js.map +1 -0
  235. package/dist/commands/xargs.js +1 -0
  236. package/dist/commands/xargs.js.map +1 -0
  237. package/dist/index.js +1 -0
  238. package/dist/index.js.map +1 -0
  239. package/dist/modules/linuxRootfs.d.ts +35 -17
  240. package/dist/modules/linuxRootfs.d.ts.map +1 -1
  241. package/dist/modules/linuxRootfs.js +332 -152
  242. package/dist/modules/linuxRootfs.js.map +1 -0
  243. package/dist/modules/neofetch.js +1 -0
  244. package/dist/modules/neofetch.js.map +1 -0
  245. package/dist/modules/shellInteractive.js +1 -0
  246. package/dist/modules/shellInteractive.js.map +1 -0
  247. package/dist/modules/shellRuntime.js +1 -0
  248. package/dist/modules/shellRuntime.js.map +1 -0
  249. package/dist/self-standalone.js +1 -0
  250. package/dist/self-standalone.js.map +1 -0
  251. package/dist/standalone-wo-sftp.js +1 -0
  252. package/dist/standalone-wo-sftp.js.map +1 -0
  253. package/dist/standalone.js +1 -0
  254. package/dist/standalone.js.map +1 -0
  255. package/dist/types/commands.d.ts +1 -1
  256. package/dist/types/commands.d.ts.map +1 -1
  257. package/dist/types/commands.js +1 -0
  258. package/dist/types/commands.js.map +1 -0
  259. package/dist/types/pipeline.js +1 -0
  260. package/dist/types/pipeline.js.map +1 -0
  261. package/dist/types/streams.js +1 -0
  262. package/dist/types/streams.js.map +1 -0
  263. package/dist/types/vfs.js +1 -0
  264. package/dist/types/vfs.js.map +1 -0
  265. package/dist/utils/expand.d.ts +2 -2
  266. package/dist/utils/expand.d.ts.map +1 -1
  267. package/dist/utils/expand.js +336 -124
  268. package/dist/utils/expand.js.map +1 -0
  269. package/dist/utils/perfLogger.js +1 -0
  270. package/dist/utils/perfLogger.js.map +1 -0
  271. package/dist/utils/tokenize.js +1 -0
  272. package/dist/utils/tokenize.js.map +1 -0
  273. package/dist/utils/vfsDiff.js +1 -0
  274. package/dist/utils/vfsDiff.js.map +1 -0
  275. package/dist/web-api.js +1 -0
  276. package/dist/web-api.js.map +1 -0
  277. package/dist/web-full.js +1 -0
  278. package/dist/web-full.js.map +1 -0
  279. package/dist/web.js +1 -0
  280. package/dist/web.js.map +1 -0
  281. package/docs/.nojekyll +1 -0
  282. package/docs/assets/hierarchy.js +1 -0
  283. package/docs/assets/highlight.css +162 -0
  284. package/docs/assets/icons.js +18 -0
  285. package/docs/assets/icons.svg +1 -0
  286. package/docs/assets/main.js +60 -0
  287. package/docs/assets/navigation.js +1 -0
  288. package/docs/assets/search.js +1 -0
  289. package/docs/assets/style.css +1633 -0
  290. package/docs/classes/HoneyPot.html +31 -0
  291. package/docs/classes/SshClient.html +66 -0
  292. package/docs/classes/VirtualFileSystem.html +262 -0
  293. package/docs/classes/VirtualPackageManager.html +63 -0
  294. package/docs/classes/VirtualSftpServer.html +169 -0
  295. package/docs/classes/VirtualShell.html +265 -0
  296. package/docs/classes/VirtualSshServer.html +177 -0
  297. package/docs/classes/VirtualUserManager.html +276 -0
  298. package/docs/docs/.nojekyll +1 -0
  299. package/docs/docs/assets/hierarchy.js +1 -0
  300. package/docs/docs/assets/highlight.css +162 -0
  301. package/docs/docs/assets/icons.js +18 -0
  302. package/docs/docs/assets/icons.svg +1 -0
  303. package/docs/docs/assets/main.js +60 -0
  304. package/docs/docs/assets/navigation.js +1 -0
  305. package/docs/docs/assets/search.js +1 -0
  306. package/docs/docs/assets/style.css +1633 -0
  307. package/docs/docs/hierarchy.html +1 -0
  308. package/docs/docs/index.html +1842 -0
  309. package/docs/docs/media/LICENSE +21 -0
  310. package/docs/docs/modules.html +1 -0
  311. package/docs/functions/assertDiff.html +6 -0
  312. package/docs/functions/diffSnapshots.html +7 -0
  313. package/docs/functions/formatDiff.html +6 -0
  314. package/docs/functions/getArg.html +13 -0
  315. package/docs/functions/getFlag.html +15 -0
  316. package/docs/functions/ifFlag.html +11 -0
  317. package/docs/hierarchy.html +1 -0
  318. package/docs/index.html +1842 -0
  319. package/docs/interfaces/AuditLogEntry.html +6 -0
  320. package/docs/interfaces/CommandContext.html +22 -0
  321. package/docs/interfaces/CommandResult.html +26 -0
  322. package/docs/interfaces/ExecStream.html +11 -0
  323. package/docs/interfaces/HoneyPotStats.html +14 -0
  324. package/docs/interfaces/InstalledPackage.html +20 -0
  325. package/docs/interfaces/NanoEditorSession.html +8 -0
  326. package/docs/interfaces/PackageDefinition.html +30 -0
  327. package/docs/interfaces/PackageFile.html +8 -0
  328. package/docs/interfaces/RemoveOptions.html +4 -0
  329. package/docs/interfaces/ShellEnv.html +6 -0
  330. package/docs/interfaces/ShellModule.html +14 -0
  331. package/docs/interfaces/ShellProperties.html +14 -0
  332. package/docs/interfaces/ShellStream.html +11 -0
  333. package/docs/interfaces/SudoChallenge.html +24 -0
  334. package/docs/interfaces/VfsBaseNode.html +12 -0
  335. package/docs/interfaces/VfsDiff.html +10 -0
  336. package/docs/interfaces/VfsDiffEntry.html +6 -0
  337. package/docs/interfaces/VfsDiffModified.html +10 -0
  338. package/docs/interfaces/VfsDirectoryNode.html +15 -0
  339. package/docs/interfaces/VfsFileNode.html +17 -0
  340. package/docs/interfaces/VfsOptions.html +12 -0
  341. package/docs/interfaces/VfsSnapshot.html +3 -0
  342. package/docs/interfaces/VfsSnapshotBaseNode.html +8 -0
  343. package/docs/interfaces/VfsSnapshotDirectoryNode.html +10 -0
  344. package/docs/interfaces/VfsSnapshotFileNode.html +12 -0
  345. package/docs/interfaces/WriteFileOptions.html +6 -0
  346. package/docs/media/LICENSE +21 -0
  347. package/docs/modules.html +1 -0
  348. package/docs/types/CommandMode.html +2 -0
  349. package/docs/types/CommandOutcome.html +2 -0
  350. package/docs/types/VfsNodeStats.html +2 -0
  351. package/docs/types/VfsNodeType.html +2 -0
  352. package/docs/types/VfsPersistenceMode.html +5 -0
  353. package/docs/types/VfsSnapshotNode.html +2 -0
  354. package/examples/web.min.js +5 -5
  355. package/package.json +7 -4
  356. package/src/VirtualFileSystem/index.ts +11 -9
  357. package/src/VirtualShell/shellParser.ts +3 -2
  358. package/src/bun.d.ts +1 -0
  359. package/src/commands/awk.ts +1 -2
  360. package/src/commands/cd.ts +2 -2
  361. package/src/commands/helpers.ts +3 -0
  362. package/src/commands/ls.ts +210 -41
  363. package/src/commands/runtime.ts +56 -3
  364. package/src/commands/sh.ts +7 -4
  365. package/src/commands/test.ts +4 -2
  366. package/src/commands/wc.ts +1 -1
  367. package/src/modules/linuxRootfs.ts +420 -231
  368. package/src/types/commands.ts +1 -1
  369. package/src/utils/expand.ts +256 -76
  370. package/tests/command-helpers.test.ts +80 -0
  371. package/tests/commands-admin-net.test.ts +441 -0
  372. package/tests/commands-advanced.test.ts +456 -0
  373. package/tests/commands-core.test.ts +562 -0
  374. package/tests/commands-missing.test.ts +570 -0
  375. package/tests/commands-specific-units.test.ts +327 -0
  376. package/tests/commands-text-sys.test.ts +445 -0
  377. package/tests/expand.test.ts +170 -0
  378. package/tests/helpers.test.ts +75 -0
  379. package/tests/test-helper.ts +79 -0
  380. package/tsconfig.json +3 -0
  381. package/typedoc.json +8 -0
  382. package/tests/bun-test-shim.ts +0 -9
@@ -1,13 +1,13 @@
1
- var k=Object.defineProperty;var O=(o,e,t)=>e in o?k(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var u=(o,e,t)=>O(o,typeof e!="symbol"?e+"":e,t);function x(o){let e=[],t="",r=!1,n="",s=0;for(;s<o.length;){let i=o[s],a=o[s+1];if((i==='"'||i==="'")&&!r){r=!0,n=i,s++;continue}if(r&&i===n){r=!1,n="",s++;continue}if(r){t+=i,s++;continue}if(i===" "){t&&(e.push(t),t=""),s++;continue}if((i===">"||i==="<")&&!r){t&&(e.push(t),t=""),i===">"&&a===">"?(e.push(">>"),s+=2):(e.push(i),s++);continue}t+=i,s++}return t&&e.push(t),e}function w(o){let e=o.trim();if(!e)return{statements:[],isValid:!0};try{return{statements:z(e),isValid:!0}}catch(t){return{statements:[],isValid:!1,error:t.message}}}function z(o){let e=A(o),t=[];for(let r of e){let s={pipeline:{commands:R(r.text.trim()),isValid:!0}};r.op&&(s.op=r.op),t.push(s)}return t}function A(o){let e=[],t="",r=0,n=!1,s="",i=0,a=c=>{t.trim()&&e.push({text:t,op:c}),t=""};for(;i<o.length;){let c=o[i],d=o.slice(i,i+2);if((c==='"'||c==="'")&&!n){n=!0,s=c,t+=c,i++;continue}if(n&&c===s){n=!1,t+=c,i++;continue}if(n){t+=c,i++;continue}if(c==="("){r++,t+=c,i++;continue}if(c===")"){r--,t+=c,i++;continue}if(r>0){t+=c,i++;continue}if(d==="&&"){a("&&"),i+=2;continue}if(d==="||"){a("||"),i+=2;continue}if(c===";"){a(";"),i++;continue}t+=c,i++}return a(),e}function R(o){return F(o).map(L)}function F(o){let e=[],t="",r=!1,n="";for(let i=0;i<o.length;i++){let a=o[i];if((a==='"'||a==="'")&&!r){r=!0,n=a,t+=a;continue}if(r&&a===n){r=!1,t+=a;continue}if(r){t+=a;continue}if(a==="|"&&o[i+1]!=="|"){if(!t.trim())throw new Error("Syntax error near unexpected token '|'");e.push(t.trim()),t=""}else t+=a}let s=t.trim();if(!s&&e.length>0)throw new Error("Syntax error near unexpected token '|'");return s&&e.push(s),e}function L(o){let e=x(o);if(e.length===0)return{name:"",args:[]};let t=[],r,n,s=!1,i=0;for(;i<e.length;){let c=e[i];if(c==="<"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after <");r=e[i],i++}else if(c===">>"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after >>");n=e[i],s=!0,i++}else if(c===">"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after >");n=e[i],s=!1,i++}else t.push(c),i++}return{name:(t[0]??"").toLowerCase(),args:t.slice(1),inputFile:r,outputFile:n,appendOutput:s}}function I(o,e){let t=o.replace(/\b([A-Za-z_][A-Za-z0-9_]*)\b/g,(r,n)=>{let s=e[n];return s!==void 0&&s!==""?s:"0"});if(!/^[\d\s+\-*/%()^!&|<>=,. ]+$/.test(t))return NaN;try{let r=Function(`"use strict"; return (${t.replace(/\*\*/g,"**")});`)();return typeof r=="number"?Math.trunc(r):NaN}catch{return NaN}}function M(o,e){let t=[],r=0;for(;r<o.length;){let n=o.indexOf("'",r);if(n===-1){t.push(e(o.slice(r)));break}t.push(e(o.slice(r,n)));let s=o.indexOf("'",n+1);if(s===-1){t.push(o.slice(n));break}t.push(o.slice(n,s+1)),r=s+1}return t.join("")}function p(o,e,t=0,r){let n=r??e.HOME??"/home/user";return M(o,s=>{let i=s;return i=i.replace(/(^|[\s:])~(\/|$)/g,(a,c,d)=>`${c}${n}${d}`),i=i.replace(/\$\?/g,String(t)),i=i.replace(/\$\$/g,"1"),i=i.replace(/\$#/g,"0"),i=i.replace(/\$\(\(([^)]+(?:\([^)]*\)[^)]*)*)\)\)/g,(a,c)=>{let d=I(c,e);return Number.isNaN(d)?"0":String(d)}),i=i.replace(/\$\{#([A-Za-z_][A-Za-z0-9_]*)\}/g,(a,c)=>String((e[c]??"").length)),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):-([^}]*)\}/g,(a,c,d)=>e[c]!==void 0&&e[c]!==""?e[c]:d),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):=([^}]*)\}/g,(a,c,d)=>((e[c]===void 0||e[c]==="")&&(e[c]=d),e[c])),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):\+([^}]*)\}/g,(a,c,d)=>e[c]!==void 0&&e[c]!==""?d:""),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(a,c)=>e[c]??""),i=i.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g,(a,c)=>e[c]??""),i})}async function C(o,e,t,r){if(o.includes("$(")){let n="",s=!1,i=0;for(;i<o.length;){let a=o[i];if(a==="'"&&!s){s=!0,n+=a,i++;continue}if(a==="'"&&s){s=!1,n+=a,i++;continue}if(!s&&a==="$"&&o[i+1]==="("){if(o[i+2]==="("){n+=a,i++;continue}let c=0,d=i+1;for(;d<o.length;){if(o[d]==="(")c++;else if(o[d]===")"&&(c--,c===0))break;d++}let g=o.slice(i+2,d).trim(),P=(await r(g)).replace(/\n$/,"");n+=P,i=d+1;continue}n+=a,i++}o=n}return p(o,e,t)}var _=new TextEncoder,B=new TextDecoder;function j(o){let e="";for(let t of o)e+=String.fromCharCode(t);return btoa(e)}function N(o){let e=atob(o),t=new Uint8Array(e.length);for(let r=0;r<e.length;r+=1)t[r]=e.charCodeAt(r);return t}function l(o,e="/"){let r=(o.startsWith("/")?o:`${e}/${o}`).split("/"),n=[];for(let s of r)if(!(!s||s===".")){if(s===".."){n.pop();continue}n.push(s)}return`/${n.join("/")}`||"/"}function h(o){let e=l(o);if(e==="/")return"/";let t=e.split("/").filter(Boolean);return t.pop(),t.length>0?`/${t.join("/")}`:"/"}function f(o){let e=l(o);return e==="/"?"/":e.split("/").filter(Boolean).at(-1)??"/"}function D(o){return o.type==="file"?{...o,contentBase64:o.contentBase64}:{...o,children:o.children.map(e=>D(e))}}function W(o,e){let t=new Date().toISOString();return{type:"directory",name:o,mode:e,createdAt:t,updatedAt:t,children:[]}}function T(o,e,t){let r=new Date().toISOString();return{type:"file",name:o,mode:t,createdAt:r,updatedAt:r,contentBase64:j(e)}}function b(o,e){return o.children.find(t=>t.name===e)}function m(o,e){let t=o.children.findIndex(r=>r.name===e.name);if(t===-1){o.children.push(e);return}o.children[t]=e}function S(o,e){o.children=o.children.filter(t=>t.name!==e)}function $(o){return l(o).split("/").filter(Boolean)}var V=globalThis;function E(o){return new Promise((e,t)=>{o.addEventListener("success",()=>e(o.result)),o.addEventListener("error",()=>t(o.error))})}var y=class{constructor(e={}){u(this,"databaseName");u(this,"storeName");u(this,"key");u(this,"root");this.databaseName=e.databaseName??"typescript-virtual-container-web",this.storeName=e.storeName??"snapshots",this.key=e.key??"current",this.root=W("",493)}async openDatabase(){return new Promise((e,t)=>{let r=V.indexedDB;if(!r){t(new Error("IndexedDB is not available in this environment"));return}let n=r.open(this.databaseName,1);n.addEventListener("upgradeneeded",()=>{let s=n.result;s.objectStoreNames.contains(this.storeName)||s.createObjectStore(this.storeName)}),n.addEventListener("success",()=>e(n.result)),n.addEventListener("error",()=>t(n.error))})}async readSnapshot(){let e=await this.openDatabase();try{let n=e.transaction(this.storeName,"readonly").objectStore(this.storeName).get(this.key),s=await E(n);return s?JSON.parse(s):null}finally{e.close()}}async writeSnapshot(e){let t=await this.openDatabase();try{let r=t.transaction(this.storeName,"readwrite"),n=r.objectStore(this.storeName);await E(n.put(JSON.stringify(e),this.key)),await new Promise((s,i)=>{r.addEventListener("complete",()=>s()),r.addEventListener("error",()=>i(r.error)),r.addEventListener("abort",()=>i(r.error))})}finally{t.close()}}serializeNode(e){return e.type==="file"?{...e}:{...e,children:e.children.map(t=>this.serializeNode(t))}}deserializeNode(e){return e.type==="file"?{...e}:{...e,children:e.children.map(t=>this.deserializeNode(t))}}getNode(e){let t=l(e);if(t==="/")return this.root;let r=$(t),n=this.root;for(let s of r){if(n.type!=="directory")throw new Error(`Not a directory: ${t}`);let i=b(n,s);if(!i)throw new Error(`No such file or directory: ${t}`);n=i}return n}ensureDirectory(e,t){let r=l(e);if(r==="/")return this.root;let n=$(r),s=this.root;for(let i of n){let a=b(s,i);if(!a){let c=W(i,t);m(s,c),s=c;continue}if(a.type!=="directory")throw new Error(`Cannot create directory '${r}': path is a file.`);s=a}return s}removeNode(e,t){let r=l(e);if(r==="/")throw new Error("Cannot remove root directory");let n=this.getNode(h(r));if(n.type!=="directory")throw new Error(`Not a directory: ${h(r)}`);let s=f(r),i=b(n,s);if(!i)throw new Error(`No such file or directory: ${r}`);if(i.type==="directory"&&i.children.length>0&&!t)throw new Error(`Cannot remove '${r}': directory not empty.`);S(n,s)}copyNode(e){return e.type==="file"?{...e,contentBase64:e.contentBase64}:{...e,children:e.children.map(t=>this.copyNode(t))}}async restoreMirror(){let e=await this.readSnapshot();e&&(this.root=this.deserializeNode(e.root))}async flushMirror(){await this.writeSnapshot({root:this.serializeNode(this.root)})}exists(e){try{return this.getNode(e),!0}catch{return!1}}list(e){let t=this.getNode(e);if(t.type!=="directory")throw new Error(`Not a directory: ${e}`);return t.children.map(r=>r.name).sort((r,n)=>r.localeCompare(n))}stat(e){let t=this.getNode(e);return t.type==="file"?{type:"file",mode:t.mode,size:N(t.contentBase64).byteLength,name:t.name}:{type:"directory",mode:t.mode,size:0,name:t.name}}readFile(e){let t=this.getNode(e);if(t.type!=="file")throw new Error(`Is a directory: ${e}`);return B.decode(N(t.contentBase64))}writeFile(e,t,r=420){let n=l(e),s=this.ensureDirectory(h(n),493),i=typeof t=="string"?_.encode(t):t,a=T(f(n),i,r);m(s,a)}mkdir(e,t=493){this.ensureDirectory(e,t)}touch(e){this.exists(e)||this.writeFile(e,"")}move(e,t){let r=this.getNode(e),n=this.getNode(h(e)),s=this.ensureDirectory(h(t),493);if(n.type!=="directory")throw new Error(`Not a directory: ${h(e)}`);S(n,f(e));let i=D(r);i.name=f(t),m(s,i)}copy(e,t){let r=this.getNode(e),n=this.ensureDirectory(h(t),493),s=this.copyNode(r);s.name=f(t),m(n,s)}remove(e,t={}){this.removeNode(e,t.recursive??!1)}exportSnapshot(){return{root:this.serializeNode(this.root)}}importSnapshot(e){this.root=this.deserializeNode(e.root)}},v=class{constructor(e,t={}){u(this,"hostname");u(this,"vfs");u(this,"env");u(this,"cwd");u(this,"commands",new Map);u(this,"initialized",!1);this.hostname=e,this.cwd=t.cwd??"/home/root",this.env={vars:{PATH:"/usr/bin:/bin",HOME:"/home/root",USER:"root",LOGNAME:"root",SHELL:"/bin/sh",HOSTNAME:e,PWD:this.cwd},lastExitCode:0},this.vfs=new y(t.vfs),this.registerBuiltins()}register(e){this.commands.set(e.name.toLowerCase(),e);for(let t of e.aliases??[])this.commands.set(t.toLowerCase(),e)}registerBuiltins(){this.register({name:"help",description:"List available web commands",params:[],run:()=>({stdout:`${this.listCommands().join(`
1
+ var R=Object.defineProperty;var F=(o,e,t)=>e in o?R(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var m=(o,e,t)=>F(o,typeof e!="symbol"?e+"":e,t);function S(o){let e=[],t="",r=!1,n="",s=0;for(;s<o.length;){let i=o[s],a=o[s+1];if((i==='"'||i==="'")&&!r){r=!0,n=i,s++;continue}if(r&&i===n){r=!1,n="",s++;continue}if(r){t+=i,s++;continue}if(i===" "){t&&(e.push(t),t=""),s++;continue}if(!r&&i==="2"&&a===">"){let c=o.slice(s+1);if(c.startsWith(">>&1")||c.startsWith(">> &1")){t&&(e.push(t),t=""),e.push("2>>&1"),s+=5;continue}if(c.startsWith(">&1")){t&&(e.push(t),t=""),e.push("2>&1"),s+=4;continue}if(c.startsWith(">>")){t&&(e.push(t),t=""),e.push("2>>"),s+=3;continue}if(c.startsWith(">")){t&&(e.push(t),t=""),e.push("2>"),s+=2;continue}}if((i===">"||i==="<")&&!r){t&&(e.push(t),t=""),i===">"&&a===">"?(e.push(">>"),s+=2):(e.push(i),s++);continue}t+=i,s++}return t&&e.push(t),e}function $(o){let e=o.trim();if(!e)return{statements:[],isValid:!0};try{return{statements:L(e),isValid:!0}}catch(t){return{statements:[],isValid:!1,error:t.message}}}function L(o){let e=I(o),t=[];for(let r of e){let s={pipeline:{commands:M(r.text.trim()),isValid:!0}};r.op&&(s.op=r.op),t.push(s)}return t}function I(o){let e=[],t="",r=0,n=!1,s="",i=0,a=c=>{t.trim()&&e.push({text:t,op:c}),t=""};for(;i<o.length;){let c=o[i],d=o.slice(i,i+2);if((c==='"'||c==="'")&&!n){n=!0,s=c,t+=c,i++;continue}if(n&&c===s){n=!1,t+=c,i++;continue}if(n){t+=c,i++;continue}if(c==="("){r++,t+=c,i++;continue}if(c===")"){r--,t+=c,i++;continue}if(r>0){t+=c,i++;continue}if(d==="&&"){a("&&"),i+=2;continue}if(d==="||"){a("||"),i+=2;continue}if(c===";"){a(";"),i++;continue}t+=c,i++}return a(),e}function M(o){return B(o).map(_)}function B(o){let e=[],t="",r=!1,n="";for(let i=0;i<o.length;i++){let a=o[i];if((a==='"'||a==="'")&&!r){r=!0,n=a,t+=a;continue}if(r&&a===n){r=!1,t+=a;continue}if(r){t+=a;continue}if(a==="|"&&o[i+1]!=="|"){if(!t.trim())throw new Error("Syntax error near unexpected token '|'");e.push(t.trim()),t=""}else t+=a}let s=t.trim();if(!s&&e.length>0)throw new Error("Syntax error near unexpected token '|'");return s&&e.push(s),e}function _(o){let e=S(o);if(e.length===0)return{name:"",args:[]};let t=[],r,n,s=!1,i=0,a,c=!1,d=!1;for(;i<e.length;){let f=e[i];if(f==="<"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after <");r=e[i],i++}else if(f===">>"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after >>");n=e[i],s=!0,i++}else if(f===">"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after >");n=e[i],s=!1,i++}else if(f==="2>&1")d=!0,i++;else if(f==="2>>"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after 2>>");a=e[i],c=!0,i++}else if(f==="2>"){if(i++,i>=e.length)throw new Error("Syntax error: expected filename after 2>");a=e[i],c=!1,i++}else t.push(f),i++}let h=t[0]??"";return{name:/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.test(h)?h:h.toLowerCase(),args:t.slice(1),inputFile:r,outputFile:n,appendOutput:s,stderrFile:a,stderrAppend:c,stderrToStdout:d}}function T(o,e){let t=[],r=0;for(;r<o.length;){let n=o[r];if(/\s/.test(n)){r++;continue}if(n==="+"){t.push({type:"plus"}),r++;continue}if(n==="-"){t.push({type:"minus"}),r++;continue}if(n==="*"){if(o[r+1]==="*"){t.push({type:"pow"}),r+=2;continue}t.push({type:"mul"}),r++;continue}if(n==="/"){t.push({type:"div"}),r++;continue}if(n==="%"){t.push({type:"mod"}),r++;continue}if(n==="("){t.push({type:"lparen"}),r++;continue}if(n===")"){t.push({type:"rparen"}),r++;continue}if(/\d/.test(n)){let s=r+1;for(;s<o.length&&/\d/.test(o[s]);)s++;t.push({type:"number",value:Number(o.slice(r,s))}),r=s;continue}if(/[A-Za-z_]/.test(n)){let s=r+1;for(;s<o.length&&/[A-Za-z0-9_]/.test(o[s]);)s++;let i=o.slice(r,s),a=e[i],c=a===void 0||a===""?0:Number(a);t.push({type:"number",value:Number.isFinite(c)?c:0}),r=s;continue}return[]}return t}function j(o,e){let t=o.trim();if(t.length===0||t.length>1024)return NaN;let r=T(t,e);if(r.length===0)return NaN;let n=0,s=()=>r[n],i=()=>r[n++],a=()=>{let l=i();if(!l)return NaN;if(l.type==="number")return l.value;if(l.type==="lparen"){let p=g();return r[n]?.type!=="rparen"?NaN:(n++,p)}return NaN},c=()=>{let l=s();return l?.type==="plus"?(i(),c()):l?.type==="minus"?(i(),-c()):a()},d=()=>{let l=c();for(;s()?.type==="pow";){i();let p=c();l=l**p}return l},h=()=>{let l=d();for(;;){let p=s();if(p?.type==="mul"){i(),l*=d();continue}if(p?.type==="div"){i();let x=d();l=x===0?NaN:l/x;continue}if(p?.type==="mod"){i();let x=d();l=x===0?NaN:l%x;continue}return l}},g=()=>{let l=h();for(;;){let p=s();if(p?.type==="plus"){i(),l+=h();continue}if(p?.type==="minus"){i(),l-=h();continue}return l}},f=g();return!Number.isFinite(f)||n!==r.length?NaN:Math.trunc(f)}function V(o,e){let t=[],r=0;for(;r<o.length;){let n=o.indexOf("'",r);if(n===-1){t.push(e(o.slice(r)));break}t.push(e(o.slice(r,n)));let s=o.indexOf("'",n+1);if(s===-1){t.push(o.slice(n));break}t.push(o.slice(n,s+1)),r=s+1}return t.join("")}function U(o,e){let t="",r=0;for(;r<o.length;){if(o[r]==="$"&&o[r+1]==="("&&o[r+2]==="("){let n=r+3,s=0;for(;n<o.length;){let i=o[n];if(i==="(")s++;else if(i===")"){if(s>0)s--;else if(o[n+1]===")"){let a=o.slice(r+3,n),c=j(a,e);t+=Number.isNaN(c)?"0":String(c),r=n+2;break}}n++}if(n>=o.length){t+=o.slice(r);break}continue}t+=o[r],r++}return t}function w(o,e,t=0,r){let n=r??e.HOME??"/home/user";return V(o,s=>{let i=s;return i=i.replace(/(^|[\s:])~(\/|$)/g,(a,c,d)=>`${c}${n}${d}`),i=i.replace(/\$\?/g,String(t)),i=i.replace(/\$\$/g,"1"),i=i.replace(/\$#/g,"0"),i=U(i,e),i=i.replace(/\$\{#([A-Za-z_][A-Za-z0-9_]*)\}/g,(a,c)=>String((e[c]??"").length)),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):-([^}]*)\}/g,(a,c,d)=>e[c]!==void 0&&e[c]!==""?e[c]:d),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):=([^}]*)\}/g,(a,c,d)=>((e[c]===void 0||e[c]==="")&&(e[c]=d),e[c])),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*):\+([^}]*)\}/g,(a,c,d)=>e[c]!==void 0&&e[c]!==""?d:""),i=i.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(a,c)=>e[c]??""),i=i.replace(/\$([A-Za-z_][A-Za-z0-9_]*|\d+)/g,(a,c)=>e[c]??""),i})}async function E(o,e,t,r){let n="__shellExpandDepth",i=Number(e[n]??"0");if(i>=8)return w(o,e,t);e[n]=String(i+1);try{if(o.includes("$(")){let a="",c=!1,d=0;for(;d<o.length;){let h=o[d];if(h==="'"&&!c){c=!0,a+=h,d++;continue}if(h==="'"&&c){c=!1,a+=h,d++;continue}if(!c&&h==="$"&&o[d+1]==="("){if(o[d+2]==="("){a+=h,d++;continue}let g=0,f=d+1;for(;f<o.length;){if(o[f]==="(")g++;else if(o[f]===")"&&(g--,g===0))break;f++}let l=o.slice(d+2,f).trim(),p=(await r(l)).replace(/\n$/,"");a+=p,d=f+1;continue}a+=h,d++}o=a}return w(o,e,t)}finally{i<=0?delete e[n]:e[n]=String(i)}}var Z=new TextEncoder,q=new TextDecoder;function H(o){let e="";for(let t of o)e+=String.fromCharCode(t);return btoa(e)}function D(o){let e=atob(o),t=new Uint8Array(e.length);for(let r=0;r<e.length;r+=1)t[r]=e.charCodeAt(r);return t}function u(o,e="/"){let r=(o.startsWith("/")?o:`${e}/${o}`).split("/"),n=[];for(let s of r)if(!(!s||s===".")){if(s===".."){n.pop();continue}n.push(s)}return`/${n.join("/")}`||"/"}function y(o){let e=u(o);if(e==="/")return"/";let t=e.split("/").filter(Boolean);return t.pop(),t.length>0?`/${t.join("/")}`:"/"}function b(o){let e=u(o);return e==="/"?"/":e.split("/").filter(Boolean).at(-1)??"/"}function O(o){return o.type==="file"?{...o,contentBase64:o.contentBase64}:{...o,children:o.children.map(e=>O(e))}}function k(o,e){let t=new Date().toISOString();return{type:"directory",name:o,mode:e,createdAt:t,updatedAt:t,children:[]}}function Q(o,e,t){let r=new Date().toISOString();return{type:"file",name:o,mode:t,createdAt:r,updatedAt:r,contentBase64:H(e)}}function C(o,e){return o.children.find(t=>t.name===e)}function v(o,e){let t=o.children.findIndex(r=>r.name===e.name);if(t===-1){o.children.push(e);return}o.children[t]=e}function A(o,e){o.children=o.children.filter(t=>t.name!==e)}function z(o){return u(o).split("/").filter(Boolean)}var G=globalThis;function P(o){return new Promise((e,t)=>{o.addEventListener("success",()=>e(o.result)),o.addEventListener("error",()=>t(o.error))})}var N=class{constructor(e={}){m(this,"databaseName");m(this,"storeName");m(this,"key");m(this,"root");this.databaseName=e.databaseName??"typescript-virtual-container-web",this.storeName=e.storeName??"snapshots",this.key=e.key??"current",this.root=k("",493)}async openDatabase(){return new Promise((e,t)=>{let r=G.indexedDB;if(!r){t(new Error("IndexedDB is not available in this environment"));return}let n=r.open(this.databaseName,1);n.addEventListener("upgradeneeded",()=>{let s=n.result;s.objectStoreNames.contains(this.storeName)||s.createObjectStore(this.storeName)}),n.addEventListener("success",()=>e(n.result)),n.addEventListener("error",()=>t(n.error))})}async readSnapshot(){let e=await this.openDatabase();try{let n=e.transaction(this.storeName,"readonly").objectStore(this.storeName).get(this.key),s=await P(n);return s?JSON.parse(s):null}finally{e.close()}}async writeSnapshot(e){let t=await this.openDatabase();try{let r=t.transaction(this.storeName,"readwrite"),n=r.objectStore(this.storeName);await P(n.put(JSON.stringify(e),this.key)),await new Promise((s,i)=>{r.addEventListener("complete",()=>s()),r.addEventListener("error",()=>i(r.error)),r.addEventListener("abort",()=>i(r.error))})}finally{t.close()}}serializeNode(e){return e.type==="file"?{...e}:{...e,children:e.children.map(t=>this.serializeNode(t))}}deserializeNode(e){return e.type==="file"?{...e}:{...e,children:e.children.map(t=>this.deserializeNode(t))}}getNode(e){let t=u(e);if(t==="/")return this.root;let r=z(t),n=this.root;for(let s of r){if(n.type!=="directory")throw new Error(`Not a directory: ${t}`);let i=C(n,s);if(!i)throw new Error(`No such file or directory: ${t}`);n=i}return n}ensureDirectory(e,t){let r=u(e);if(r==="/")return this.root;let n=z(r),s=this.root;for(let i of n){let a=C(s,i);if(!a){let c=k(i,t);v(s,c),s=c;continue}if(a.type!=="directory")throw new Error(`Cannot create directory '${r}': path is a file.`);s=a}return s}removeNode(e,t){let r=u(e);if(r==="/")throw new Error("Cannot remove root directory");let n=this.getNode(y(r));if(n.type!=="directory")throw new Error(`Not a directory: ${y(r)}`);let s=b(r),i=C(n,s);if(!i)throw new Error(`No such file or directory: ${r}`);if(i.type==="directory"&&i.children.length>0&&!t)throw new Error(`Cannot remove '${r}': directory not empty.`);A(n,s)}copyNode(e){return e.type==="file"?{...e,contentBase64:e.contentBase64}:{...e,children:e.children.map(t=>this.copyNode(t))}}async restoreMirror(){let e=await this.readSnapshot();e&&(this.root=this.deserializeNode(e.root))}async flushMirror(){await this.writeSnapshot({root:this.serializeNode(this.root)})}exists(e){try{return this.getNode(e),!0}catch{return!1}}list(e){let t=this.getNode(e);if(t.type!=="directory")throw new Error(`Not a directory: ${e}`);return t.children.map(r=>r.name).sort((r,n)=>r.localeCompare(n))}stat(e){let t=this.getNode(e);return t.type==="file"?{type:"file",mode:t.mode,size:D(t.contentBase64).byteLength,name:t.name}:{type:"directory",mode:t.mode,size:0,name:t.name}}readFile(e){let t=this.getNode(e);if(t.type!=="file")throw new Error(`Is a directory: ${e}`);return q.decode(D(t.contentBase64))}writeFile(e,t,r=420){let n=u(e),s=this.ensureDirectory(y(n),493),i=typeof t=="string"?Z.encode(t):t,a=Q(b(n),i,r);v(s,a)}mkdir(e,t=493){this.ensureDirectory(e,t)}touch(e){this.exists(e)||this.writeFile(e,"")}move(e,t){let r=this.getNode(e),n=this.getNode(y(e)),s=this.ensureDirectory(y(t),493);if(n.type!=="directory")throw new Error(`Not a directory: ${y(e)}`);A(n,b(e));let i=O(r);i.name=b(t),v(s,i)}copy(e,t){let r=this.getNode(e),n=this.ensureDirectory(y(t),493),s=this.copyNode(r);s.name=b(t),v(n,s)}remove(e,t={}){this.removeNode(e,t.recursive??!1)}exportSnapshot(){return{root:this.serializeNode(this.root)}}importSnapshot(e){this.root=this.deserializeNode(e.root)}},W=class{constructor(e,t={}){m(this,"hostname");m(this,"vfs");m(this,"env");m(this,"cwd");m(this,"commands",new Map);m(this,"initialized",!1);this.hostname=e,this.cwd=t.cwd??"/home/root",this.env={vars:{PATH:"/usr/bin:/bin",HOME:"/home/root",USER:"root",LOGNAME:"root",SHELL:"/bin/sh",HOSTNAME:e,PWD:this.cwd},lastExitCode:0},this.vfs=new N(t.vfs),this.registerBuiltins()}register(e){this.commands.set(e.name.toLowerCase(),e);for(let t of e.aliases??[])this.commands.set(t.toLowerCase(),e)}registerBuiltins(){this.register({name:"help",description:"List available web commands",params:[],run:()=>({stdout:`${this.listCommands().join(`
2
2
  `)}
3
3
  `,exitCode:0})}),this.register({name:"pwd",description:"Print current directory",params:[],run:()=>({stdout:`${this.cwd}
4
- `,exitCode:0})}),this.register({name:"cd",description:"Change current directory",params:["[dir]"],run:({args:e})=>{let t=e[0]?l(e[0],this.cwd):"/home/root";return!this.vfs.exists(t)||this.vfs.stat(t).type!=="directory"?{stderr:`cd: no such file or directory: ${t}`,exitCode:1}:(this.cwd=t,this.env.vars.PWD=t,{exitCode:0,nextCwd:t})}}),this.register({name:"echo",description:"Display text",params:["[-n] [-e] [text...]"],run:({args:e,stdin:t})=>{let r=e.includes("-n"),n=e.filter(a=>a!=="-n"&&a!=="-e"&&a!=="-E"),s=n.length>0?n.join(" "):t??"",i=p(s,this.env.vars,this.env.lastExitCode,this.env.vars.HOME);return{stdout:r?i:`${i}
4
+ `,exitCode:0})}),this.register({name:"cd",description:"Change current directory",params:["[dir]"],run:({args:e})=>{let t=e[0]?u(e[0],this.cwd):"/home/root";return!this.vfs.exists(t)||this.vfs.stat(t).type!=="directory"?{stderr:`cd: no such file or directory: ${t}`,exitCode:1}:(this.cwd=t,this.env.vars.PWD=t,{exitCode:0,nextCwd:t})}}),this.register({name:"echo",description:"Display text",params:["[-n] [-e] [text...]"],run:({args:e,stdin:t})=>{let r=e.includes("-n"),n=e.filter(a=>a!=="-n"&&a!=="-e"&&a!=="-E"),s=n.length>0?n.join(" "):t??"",i=w(s,this.env.vars,this.env.lastExitCode,this.env.vars.HOME);return{stdout:r?i:`${i}
5
5
  `,exitCode:0}}}),this.register({name:"env",description:"Print environment variables",params:[],run:()=>({stdout:`${Object.entries(this.env.vars).map(([e,t])=>`${e}=${t}`).join(`
6
6
  `)}
7
- `,exitCode:0})}),this.register({name:"export",description:"Set environment variables",params:["KEY=VALUE..."],run:({args:e})=>{for(let t of e){let r=t.indexOf("=");if(r===-1)continue;let n=t.slice(0,r).trim(),s=t.slice(r+1);n&&(this.env.vars[n]=s)}return{exitCode:0}}}),this.register({name:"unset",description:"Unset environment variables",params:["NAME..."],run:({args:e})=>{for(let t of e)delete this.env.vars[t];return{exitCode:0}}}),this.register({name:"mkdir",description:"Create directories",params:["[-p] dir..."],run:async({args:e})=>{let t=e.filter(r=>r!=="-p");for(let r of t)this.vfs.mkdir(l(r,this.cwd));return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"touch",description:"Create files",params:["file..."],run:async({args:e})=>{for(let t of e)this.vfs.touch(l(t,this.cwd));return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"rm",description:"Remove files or directories",params:["[-r] [-f] path..."],run:async({args:e})=>{let t=e.includes("-r"),r=e.filter(n=>n!=="-r"&&n!=="-f");for(let n of r)this.vfs.remove(l(n,this.cwd),{recursive:t});return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"cp",description:"Copy files or directories",params:["[-r] source destination"],run:async({args:e})=>{let t=e.includes("-r"),r=e.filter(s=>s!=="-r");if(r.length<2)return{stderr:"cp: missing destination file operand",exitCode:1};let n=l(r.at(-1),this.cwd);for(let s of r.slice(0,-1)){let i=l(s,this.cwd);if(!t&&this.vfs.stat(i).type==="directory")return{stderr:`cp: -r not specified; omitting directory '${i}'`,exitCode:1};this.vfs.copy(i,n)}return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"mv",description:"Move or rename files",params:["source destination"],run:async({args:e})=>{if(e.length<2)return{stderr:"mv: missing destination file operand",exitCode:1};let t=l(e[0],this.cwd),r=l(e[1],this.cwd);return this.vfs.move(t,r),await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"cat",description:"Concatenate files",params:["[file...]"],run:({args:e,stdin:t})=>{if(e.length===0)return{stdout:t??"",exitCode:0};let r="";for(let n of e)r+=this.vfs.readFile(l(n,this.cwd));return{stdout:r,exitCode:0}}}),this.register({name:"ls",description:"List files",params:["[path]"],run:({args:e})=>{let t=l(e[0]??".",this.cwd);return{stdout:`${this.vfs.list(t).join(" ")}
8
- `,exitCode:0}}}),this.register({name:"tee",description:"Read from stdin and write to files",params:["[-a] file..."],run:async({args:e,stdin:t})=>{let r=e.includes("-a"),n=e.filter(i=>i!=="-a"),s=t??"";for(let i of n){let a=l(i,this.cwd);if(r&&this.vfs.exists(a)){let c=this.vfs.readFile(a);this.vfs.writeFile(a,`${c}${s}`)}else this.vfs.writeFile(a,s)}return await this.vfs.flushMirror(),{stdout:s,exitCode:0}}}),this.register({name:"curl",description:"Fetch a URL and optionally write to a file",params:["[-o file] URL"],run:async({args:e})=>{let t=e.indexOf("-o"),r=t!==-1?e[t+1]:void 0,s=e.filter((c,d)=>c!=="-o"&&d!==t+1).at(-1);if(!s)return{stderr:"curl: missing URL",exitCode:2};let i=await fetch(s),a=await i.text();return r?(this.vfs.writeFile(l(r,this.cwd),a),await this.vfs.flushMirror(),{exitCode:i.ok?0:1}):{stdout:a,exitCode:i.ok?0:1}}}),this.register({name:"wget",description:"Fetch a URL and optionally write to a file",params:["[-O file] URL"],run:async({args:e})=>{let t=e.indexOf("-O"),r=t!==-1?e[t+1]:void 0,s=e.filter((d,g)=>d!=="-O"&&g!==t+1).at(-1);if(!s)return{stderr:"wget: missing URL",exitCode:2};let i=await fetch(s),a=await i.text(),c=r??f(new URL(s).pathname||"index.html");return this.vfs.writeFile(l(c,this.cwd),a),await this.vfs.flushMirror(),{exitCode:i.ok?0:1}}}),this.register({name:"true",description:"Return success",params:[],run:()=>({exitCode:0})}),this.register({name:"false",description:"Return failure",params:[],run:()=>({exitCode:1})})}listCommands(){let e=new Map;for(let t of this.commands.values())e.set(t.name,t);return Array.from(e.values()).sort((t,r)=>t.name.localeCompare(r.name)).map(t=>`${t.name}${t.params.length>0?` ${t.params.join(" ")}`:""}`)}resolveCommand(e){return this.commands.get(e.toLowerCase())}async ensureInitialized(){this.initialized||(await this.vfs.restoreMirror(),this.vfs.exists("/home")||this.vfs.mkdir("/home"),this.vfs.exists("/home/root")||(this.vfs.mkdir("/home/root"),this.vfs.writeFile("/home/root/README.txt",`Welcome to ${this.hostname}
7
+ `,exitCode:0})}),this.register({name:"export",description:"Set environment variables",params:["KEY=VALUE..."],run:({args:e})=>{for(let t of e){let r=t.indexOf("=");if(r===-1)continue;let n=t.slice(0,r).trim(),s=t.slice(r+1);n&&(this.env.vars[n]=s)}return{exitCode:0}}}),this.register({name:"unset",description:"Unset environment variables",params:["NAME..."],run:({args:e})=>{for(let t of e)delete this.env.vars[t];return{exitCode:0}}}),this.register({name:"mkdir",description:"Create directories",params:["[-p] dir..."],run:async({args:e})=>{let t=e.filter(r=>r!=="-p");for(let r of t)this.vfs.mkdir(u(r,this.cwd));return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"touch",description:"Create files",params:["file..."],run:async({args:e})=>{for(let t of e)this.vfs.touch(u(t,this.cwd));return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"rm",description:"Remove files or directories",params:["[-r] [-f] path..."],run:async({args:e})=>{let t=e.includes("-r"),r=e.filter(n=>n!=="-r"&&n!=="-f");for(let n of r)this.vfs.remove(u(n,this.cwd),{recursive:t});return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"cp",description:"Copy files or directories",params:["[-r] source destination"],run:async({args:e})=>{let t=e.includes("-r"),r=e.filter(s=>s!=="-r");if(r.length<2)return{stderr:"cp: missing destination file operand",exitCode:1};let n=u(r.at(-1),this.cwd);for(let s of r.slice(0,-1)){let i=u(s,this.cwd);if(!t&&this.vfs.stat(i).type==="directory")return{stderr:`cp: -r not specified; omitting directory '${i}'`,exitCode:1};this.vfs.copy(i,n)}return await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"mv",description:"Move or rename files",params:["source destination"],run:async({args:e})=>{if(e.length<2)return{stderr:"mv: missing destination file operand",exitCode:1};let t=u(e[0],this.cwd),r=u(e[1],this.cwd);return this.vfs.move(t,r),await this.vfs.flushMirror(),{exitCode:0}}}),this.register({name:"cat",description:"Concatenate files",params:["[file...]"],run:({args:e,stdin:t})=>{if(e.length===0)return{stdout:t??"",exitCode:0};let r="";for(let n of e)r+=this.vfs.readFile(u(n,this.cwd));return{stdout:r,exitCode:0}}}),this.register({name:"ls",description:"List files",params:["[path]"],run:({args:e})=>{let t=u(e[0]??".",this.cwd);return{stdout:`${this.vfs.list(t).join(" ")}
8
+ `,exitCode:0}}}),this.register({name:"tee",description:"Read from stdin and write to files",params:["[-a] file..."],run:async({args:e,stdin:t})=>{let r=e.includes("-a"),n=e.filter(i=>i!=="-a"),s=t??"";for(let i of n){let a=u(i,this.cwd);if(r&&this.vfs.exists(a)){let c=this.vfs.readFile(a);this.vfs.writeFile(a,`${c}${s}`)}else this.vfs.writeFile(a,s)}return await this.vfs.flushMirror(),{stdout:s,exitCode:0}}}),this.register({name:"curl",description:"Fetch a URL and optionally write to a file",params:["[-o file] URL"],run:async({args:e})=>{let t=e.indexOf("-o"),r=t!==-1?e[t+1]:void 0,s=e.filter((c,d)=>c!=="-o"&&d!==t+1).at(-1);if(!s)return{stderr:"curl: missing URL",exitCode:2};let i=await fetch(s),a=await i.text();return r?(this.vfs.writeFile(u(r,this.cwd),a),await this.vfs.flushMirror(),{exitCode:i.ok?0:1}):{stdout:a,exitCode:i.ok?0:1}}}),this.register({name:"wget",description:"Fetch a URL and optionally write to a file",params:["[-O file] URL"],run:async({args:e})=>{let t=e.indexOf("-O"),r=t!==-1?e[t+1]:void 0,s=e.filter((d,h)=>d!=="-O"&&h!==t+1).at(-1);if(!s)return{stderr:"wget: missing URL",exitCode:2};let i=await fetch(s),a=await i.text(),c=r??b(new URL(s).pathname||"index.html");return this.vfs.writeFile(u(c,this.cwd),a),await this.vfs.flushMirror(),{exitCode:i.ok?0:1}}}),this.register({name:"true",description:"Return success",params:[],run:()=>({exitCode:0})}),this.register({name:"false",description:"Return failure",params:[],run:()=>({exitCode:1})})}listCommands(){let e=new Map;for(let t of this.commands.values())e.set(t.name,t);return Array.from(e.values()).sort((t,r)=>t.name.localeCompare(r.name)).map(t=>`${t.name}${t.params.length>0?` ${t.params.join(" ")}`:""}`)}resolveCommand(e){return this.commands.get(e.toLowerCase())}async ensureInitialized(){this.initialized||(await this.vfs.restoreMirror(),this.vfs.exists("/home")||this.vfs.mkdir("/home"),this.vfs.exists("/home/root")||(this.vfs.mkdir("/home/root"),this.vfs.writeFile("/home/root/README.txt",`Welcome to ${this.hostname}
9
9
  `)),this.vfs.exists("/tmp")||this.vfs.mkdir("/tmp"),this.vfs.exists("/etc")||this.vfs.mkdir("/etc"),this.vfs.exists("/etc/hostname")||this.vfs.writeFile("/etc/hostname",`${this.hostname}
10
10
  `),this.vfs.exists("/etc/hosts")||this.vfs.writeFile("/etc/hosts",`127.0.0.1 localhost
11
11
  ::1 localhost
12
- `),this.initialized=!0)}getCurrentWorkingDirectory(){return this.cwd}async executeCommandLine(e,t=!0){await this.ensureInitialized();let r=e.trim();if(!r)return{exitCode:0};let n=await C(r,this.env.vars,this.env.lastExitCode,a=>this.executeCommandLine(a,!1).then(c=>c.stdout??"")),s=w(n),i=await this.executeStatements(s.statements);return this.env.lastExitCode=i.exitCode??0,t&&await this.vfs.flushMirror(),i}async executeStatements(e){let t={exitCode:0},r=0;for(;r<e.length;){let n=e[r];if(t=await this.executePipeline(n.pipeline.commands),this.env.lastExitCode=t.exitCode??0,t.closeSession||t.switchUser)return t;let s=n.op;if(!(!s||s===";")){if(s==="&&"){if((t.exitCode??0)!==0)for(;r<e.length&&e[r]?.op==="&&";)r+=1}else if(s==="||"&&(t.exitCode??0)===0)for(;r<e.length&&e[r]?.op==="||";)r+=1}r+=1}return t}async executePipeline(e){return e.length===0?{exitCode:0}:e.length===1?this.executeSingleCommandWithRedirections(e[0]):this.executePipelineChain(e)}async executeSingleCommandWithRedirections(e){let t;if(e.inputFile){let n=l(e.inputFile,this.cwd);try{t=this.vfs.readFile(n)}catch{return{stderr:`${e.inputFile}: No such file or directory`,exitCode:1}}}let r=await this.executeCommand(e.name,e.args,t);if(e.outputFile){let n=l(e.outputFile,this.cwd),s=r.stdout??"";if(e.appendOutput&&this.vfs.exists(n)){let i=this.vfs.readFile(n);this.vfs.writeFile(n,`${i}${s}`)}else this.vfs.writeFile(n,s);return{...r,stdout:""}}return r}async executePipelineChain(e){let t="",r=0;for(let n=0;n<e.length;n+=1){let s=e[n];if(n===0&&s.inputFile){let a=l(s.inputFile,this.cwd);try{t=this.vfs.readFile(a)}catch{return{stderr:`${s.inputFile}: No such file or directory`,exitCode:1}}}let i=await this.executeCommand(s.name,s.args,t);t=i.stdout??"",r=i.exitCode??0}return{stdout:t,exitCode:r}}async executeCommand(e,t,r){let n=this.resolveCommand(e);if(!n)return{stderr:`${e}: command not found`,exitCode:127};let i={args:t.map(a=>p(a,this.env.vars,this.env.lastExitCode,this.env.vars.HOME)),stdin:r,cwd:this.cwd,env:this.env,rawInput:`${e} ${t.join(" ")}`.trim(),shell:this};try{let a=await n.run(i);return a.nextCwd&&(this.cwd=a.nextCwd,this.env.vars.PWD=a.nextCwd),a}catch(a){return{stderr:a instanceof Error?a.message:String(a),exitCode:1}}}};function K(o="typescript-vm",e={}){return new v(o,e)}export{y as IndexedDbMirrorVfs,v as WebShell,K as createWebShell};
12
+ `),this.initialized=!0)}getCurrentWorkingDirectory(){return this.cwd}async executeCommandLine(e,t=!0){await this.ensureInitialized();let r=e.trim();if(!r)return{exitCode:0};let n=await E(r,this.env.vars,this.env.lastExitCode,a=>this.executeCommandLine(a,!1).then(c=>c.stdout??"")),s=$(n),i=await this.executeStatements(s.statements);return this.env.lastExitCode=i.exitCode??0,t&&await this.vfs.flushMirror(),i}async executeStatements(e){let t={exitCode:0},r=0;for(;r<e.length;){let n=e[r];if(t=await this.executePipeline(n.pipeline.commands),this.env.lastExitCode=t.exitCode??0,t.closeSession||t.switchUser)return t;let s=n.op;if(!(!s||s===";")){if(s==="&&"){if((t.exitCode??0)!==0)for(;r<e.length&&e[r]?.op==="&&";)r+=1}else if(s==="||"&&(t.exitCode??0)===0)for(;r<e.length&&e[r]?.op==="||";)r+=1}r+=1}return t}async executePipeline(e){return e.length===0?{exitCode:0}:e.length===1?this.executeSingleCommandWithRedirections(e[0]):this.executePipelineChain(e)}async executeSingleCommandWithRedirections(e){let t;if(e.inputFile){let n=u(e.inputFile,this.cwd);try{t=this.vfs.readFile(n)}catch{return{stderr:`${e.inputFile}: No such file or directory`,exitCode:1}}}let r=await this.executeCommand(e.name,e.args,t);if(e.outputFile){let n=u(e.outputFile,this.cwd),s=r.stdout??"";if(e.appendOutput&&this.vfs.exists(n)){let i=this.vfs.readFile(n);this.vfs.writeFile(n,`${i}${s}`)}else this.vfs.writeFile(n,s);return{...r,stdout:""}}return r}async executePipelineChain(e){let t="",r=0;for(let n=0;n<e.length;n+=1){let s=e[n];if(n===0&&s.inputFile){let a=u(s.inputFile,this.cwd);try{t=this.vfs.readFile(a)}catch{return{stderr:`${s.inputFile}: No such file or directory`,exitCode:1}}}let i=await this.executeCommand(s.name,s.args,t);t=i.stdout??"",r=i.exitCode??0}return{stdout:t,exitCode:r}}async executeCommand(e,t,r){let n=this.resolveCommand(e);if(!n)return{stderr:`${e}: command not found`,exitCode:127};let i={args:t.map(a=>w(a,this.env.vars,this.env.lastExitCode,this.env.vars.HOME)),stdin:r,cwd:this.cwd,env:this.env,rawInput:`${e} ${t.join(" ")}`.trim(),shell:this};try{let a=await n.run(i);return a.nextCwd&&(this.cwd=a.nextCwd,this.env.vars.PWD=a.nextCwd),a}catch(a){return{stderr:a instanceof Error?a.message:String(a),exitCode:1}}}};function ie(o="typescript-vm",e={}){return new W(o,e)}export{N as IndexedDbMirrorVfs,W as WebShell,ie as createWebShell};
13
13
  //# sourceMappingURL=web.min.js.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
7
- "version": "1.4.3",
7
+ "version": "1.4.5",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
@@ -24,8 +24,9 @@
24
24
  "check": "bunx --bun @biomejs/biome check ./src",
25
25
  "lint": "bunx --bun @biomejs/biome lint ./src",
26
26
  "lint:write": "bunx --bun @biomejs/biome lint --write ./src",
27
- "test": "bunx --bun @biomejs/biome test ./src",
27
+ "test": "bun run test-salve",
28
28
  "test-battery": "bun test tests/",
29
+ "test-salve": "for f in tests/*.test.ts; do echo \"\\n🧪 Testing $f...\"; bun test \"$f\" --timeout 10000; sleep 0.25; done",
29
30
  "build": "tsc --project tsconfig.json",
30
31
  "deploy:npm": "npm publish --access public",
31
32
  "bench": "rm -rf .benchmark-shells/ && bun benchmark-virtualshell.ts",
@@ -38,13 +39,15 @@
38
39
  "publish-package": "bash ./scripts/publish-package.sh",
39
40
  "self-standalone-build": "bunx esbuild src/self-standalone.ts --bundle --platform=node --format=esm --target=node18 --outfile=builds/self-standalone.js --tree-shaking=true --minify --sourcemap --banner:js='#!/usr/bin/env node'",
40
41
  "standalone-build": "bunx esbuild src/standalone.ts --bundle --platform=node --target=node18 --outfile=builds/standalone.js --tree-shaking=true --minify --sourcemap",
41
- "build-all": "bun run self-standalone-build && bun run standalone-build && bun run standalone-build:wo-sftp && bun run web-build && bun run web-full-build && bun run example-build"
42
+ "build-all": "bun run self-standalone-build && bun run standalone-build && bun run standalone-build:wo-sftp && bun run web-build && bun run web-full-build && bun run example-build",
43
+ "publish-doc": "npx typedoc && npx gh-pages -d docs"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@biomejs/biome": "^2.4.15",
45
47
  "@types/bun": "^1.3.13",
46
- "@types/node": "^25.6.2",
48
+ "@types/node": "^25.7.0",
47
49
  "@types/ssh2": "^1.15.5",
50
+ "gh-pages": "^6.3.0",
48
51
  "typescript": "^6.0.3"
49
52
  },
50
53
  "peerDependencies": {
@@ -2,15 +2,10 @@ import { EventEmitter } from "node:events";
2
2
  import * as fsSync from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { gunzipSync, gzipSync } from "node:zlib";
5
- import type {
6
- InternalDirectoryNode,
7
- InternalFileNode,
8
- InternalNode,
9
- } from "./internalTypes";
10
- import { decodeVfs, encodeVfs, isBinarySnapshot } from "./binaryPack";
11
- import { getNode, getParentDirectory, normalizePath } from "./path";
12
5
  import type {
13
6
  RemoveOptions,
7
+ VfsDirectoryNode,
8
+ VfsFileNode,
14
9
  VfsNodeStats,
15
10
  VfsSnapshot,
16
11
  VfsSnapshotDirectoryNode,
@@ -18,6 +13,13 @@ import type {
18
13
  VfsSnapshotNode,
19
14
  WriteFileOptions,
20
15
  } from "../types/vfs";
16
+ import { decodeVfs, encodeVfs, isBinarySnapshot } from "./binaryPack";
17
+ import type {
18
+ InternalDirectoryNode,
19
+ InternalFileNode,
20
+ InternalNode,
21
+ } from "./internalTypes";
22
+ import { getNode, getParentDirectory, normalizePath } from "./path";
21
23
 
22
24
  // ── Persistence options ───────────────────────────────────────────────────────
23
25
 
@@ -460,7 +462,7 @@ class VirtualFileSystem extends EventEmitter {
460
462
  createdAt: hst.birthtime,
461
463
  updatedAt: now,
462
464
  childrenCount: fsSync.readdirSync(m.fullHostPath).length,
463
- } satisfies import("../types/vfs").VfsDirectoryNode;
465
+ } satisfies VfsDirectoryNode;
464
466
  }
465
467
  return {
466
468
  type: "file",
@@ -471,7 +473,7 @@ class VirtualFileSystem extends EventEmitter {
471
473
  updatedAt: now,
472
474
  compressed: false,
473
475
  size: hst.size,
474
- } satisfies import("../types/vfs").VfsFileNode;
476
+ } satisfies VfsFileNode;
475
477
  }
476
478
  const normalized = normalizePath(targetPath);
477
479
  const node = getNode(this.root, normalized);
@@ -1,9 +1,9 @@
1
1
  import type {
2
+ LogicalOp,
2
3
  Pipeline,
3
4
  PipelineCommand,
4
5
  Script,
5
6
  Statement,
6
- LogicalOp,
7
7
  } from "../types/pipeline";
8
8
  import { tokenizeCommand } from "../utils/tokenize";
9
9
 
@@ -274,7 +274,8 @@ function parseCommandWithRedirections(token: string): PipelineCommand {
274
274
  }
275
275
  }
276
276
 
277
- const name = (cmdParts[0] ?? "").toLowerCase();
277
+ const rawName = cmdParts[0] ?? "";
278
+ const name = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.test(rawName) ? rawName : rawName.toLowerCase();
278
279
  return {
279
280
  name, args: cmdParts.slice(1),
280
281
  inputFile, outputFile, appendOutput,
package/src/bun.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference types="@types/bun" />
@@ -1,6 +1,6 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
  import { getFlag } from "./command-helpers";
3
- import { resolvePath, assertPathAccess } from "./helpers";
3
+ import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
5
5
  /**
6
6
  * Minimal awk-like pattern scanner.
@@ -92,7 +92,6 @@ export const awkCommand: ShellModule = {
92
92
  // Arithmetic NR+1, NF-1
93
93
  const arith = expr.replace(/\bNR\b/g, String(nr)).replace(/\bNF\b/g, String(nf));
94
94
  if (/^[\d\s+\-*/()]+$/.test(arith)) {
95
- // biome-ignore lint/security/noGlobalEval: safe arithmetic — input contains only digits and operators after variable substitution
96
95
  try { return String(Function(`"use strict"; return (${arith});`)()); } catch {} }
97
96
  return expr.replace(/"/g, "");
98
97
  };
@@ -11,8 +11,8 @@ export const cdCommand: ShellModule = {
11
11
  description: "Change directory",
12
12
  category: "navigation",
13
13
  params: ["[path]"],
14
- run: ({ authUser, shell, cwd, args, mode }) => {
15
- const target = resolvePath(cwd, args[0] ?? "/virtual-env-js");
14
+ run: ({ authUser, shell, cwd, args }) => {
15
+ const target = resolvePath(cwd, args[0] ?? `~`);
16
16
  assertPathAccess(authUser, target, "cd");
17
17
  const stats = shell.vfs.stat(target);
18
18
  if (stats.type !== "directory") {
@@ -32,6 +32,9 @@ export function resolvePath(cwd: string, inputPath: string): string {
32
32
  if (!inputPath || inputPath.trim() === "") {
33
33
  return cwd;
34
34
  }
35
+ if (inputPath.startsWith("~")) {
36
+ return path.posix.normalize(`/home/${inputPath.slice(1)}`);
37
+ }
35
38
  return inputPath.startsWith("/")
36
39
  ? path.posix.normalize(inputPath)
37
40
  : path.posix.normalize(path.posix.join(cwd, inputPath));
@@ -1,74 +1,243 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
  import { getArg, ifFlag } from "./command-helpers";
3
- import { assertPathAccess, joinListWithType, resolvePath } from "./helpers";
4
-
5
- function formatPermissions(mode: number, isDirectory: boolean): string {
6
- const fileType = isDirectory ? "d" : "-";
7
- const permissionBits = [
8
- [0o400, "r"],
9
- [0o200, "w"],
10
- [0o100, "x"],
11
- [0o040, "r"],
12
- [0o020, "w"],
13
- [0o010, "x"],
14
- [0o004, "r"],
15
- [0o002, "w"],
16
- [0o001, "x"],
17
- ] as const;
18
- const permissions = permissionBits
19
- .map(([bit, symbol]) => (mode & bit ? symbol : "-"))
20
- .join("");
21
-
22
- return `${fileType}${permissions}`;
3
+ import { assertPathAccess, resolvePath } from "./helpers";
4
+
5
+ // ─── ANSI color codes (matches GNU ls --color=auto / LS_COLORS defaults) ────
6
+
7
+ const RESET = "\x1b[0m";
8
+
9
+ // Entry-type colors
10
+ const C_DIR = "\x1b[1;34m"; // bold blue — directory
11
+ const C_LINK = "\x1b[1;36m"; // bold cyan — symlink
12
+ const C_EXEC = "\x1b[1;32m"; // bold green — executable file
13
+ const C_NORMAL = ""; // no color — regular file
14
+
15
+ // Special-mode backgrounds (GNU ls corner cases)
16
+ const C_STICKY_WX = "\x1b[30;42m"; // black on green — dir sticky + world-writable (/tmp)
17
+ const C_STICKY = "\x1b[37;44m"; // white on blue — dir sticky, NOT world-writable
18
+ const C_OTHER_WX = "\x1b[34;42m"; // blue on green — dir world-writable, not sticky
19
+
20
+ // ─── helpers ────────────────────────────────────────────────────────────────
21
+
22
+ function colorize(name: string, color: string): string {
23
+ return color ? `${color}${name}${RESET}` : name;
23
24
  }
24
25
 
26
+ function entryColor(mode: number, type: "file" | "directory", isLink: boolean): string {
27
+ if (isLink) return C_LINK;
28
+ if (type === "directory") {
29
+ const sticky = !!(mode & 0o1000);
30
+ const worldW = !!(mode & 0o002);
31
+ if (sticky && worldW) return C_STICKY_WX;
32
+ if (sticky) return C_STICKY;
33
+ if (worldW) return C_OTHER_WX;
34
+ return C_DIR;
35
+ }
36
+ if (mode & 0o111) return C_EXEC;
37
+ return C_NORMAL;
38
+ }
39
+
40
+ // ─── permissions string ──────────────────────────────────────────────────────
41
+
42
+ function formatPermissions(mode: number, type: "file" | "directory", isLink: boolean): string {
43
+ let ft: string;
44
+ if (isLink) ft = "l";
45
+ else if (type === "directory") ft = "d";
46
+ else ft = "-";
47
+
48
+ const r = (bit: number) => (mode & bit ? "r" : "-");
49
+ const w = (bit: number) => (mode & bit ? "w" : "-");
50
+
51
+ const xOwner = (() => {
52
+ const exec = !!(mode & 0o100);
53
+ if (mode & 0o4000) return exec ? "s" : "S";
54
+ return exec ? "x" : "-";
55
+ })();
56
+ const xGroup = (() => {
57
+ const exec = !!(mode & 0o010);
58
+ if (mode & 0o2000) return exec ? "s" : "S";
59
+ return exec ? "x" : "-";
60
+ })();
61
+ const xOther = (() => {
62
+ const exec = !!(mode & 0o001);
63
+ if (type === "directory" && (mode & 0o1000)) return exec ? "t" : "T";
64
+ return exec ? "x" : "-";
65
+ })();
66
+
67
+ return `${ft}${r(0o400)}${w(0o200)}${xOwner}${r(0o040)}${w(0o020)}${xGroup}${r(0o004)}${w(0o002)}${xOther}`;
68
+ }
69
+
70
+ // ─── date formatting (GNU ls + French locale) ────────────────────────────────
71
+
72
+ const MONTHS_EN = [
73
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
74
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
75
+ ];
76
+
25
77
  function formatDate(date: Date): string {
26
- return date.toISOString().replace("T", " ").slice(0, 16);
78
+ const now = new Date();
79
+ const sixMo = 6 * 30 * 24 * 3600 * 1000;
80
+ const recent = Math.abs(now.getTime() - date.getTime()) < sixMo;
81
+ const day = String(date.getDate()).padStart(2, " ");
82
+ const month = MONTHS_EN[date.getMonth()] ?? "";
83
+ if (recent) {
84
+ const hh = String(date.getHours()).padStart(2, "0");
85
+ const mm = String(date.getMinutes()).padStart(2, "0");
86
+ return `${day} ${month.padEnd(3)} ${hh}:${mm}`;
87
+ }
88
+ return `${day} ${month.padEnd(3)} ${date.getFullYear()}`;
89
+ }
90
+
91
+ // ─── symlink target ──────────────────────────────────────────────────────────
92
+
93
+ function readlinkTarget(vfs: { readFile: (p: string) => string }, path: string): string {
94
+ try { return vfs.readFile(path); } catch { return "?"; }
95
+ }
96
+
97
+ // ─── short listing ───────────────────────────────────────────────────────────
98
+
99
+ function shortListing(
100
+ vfs: {
101
+ stat: (p: string) => { mode: number; type: "file" | "directory" };
102
+ isSymlink: (p: string) => boolean;
103
+ },
104
+ dir: string,
105
+ items: string[],
106
+ ): string {
107
+ const base = dir === "/" ? "" : dir;
108
+ return items.map((name) => {
109
+ const childPath = `${base}/${name}`;
110
+ const isLink = vfs.isSymlink(childPath);
111
+ let stat: { mode: number; type: "file" | "directory" };
112
+ try { stat = vfs.stat(childPath); } catch { return name; }
113
+ const color = entryColor(stat.mode, stat.type, isLink);
114
+ return colorize(name, color);
115
+ }).join(" ");
27
116
  }
28
117
 
118
+ // ─── long listing ────────────────────────────────────────────────────────────
119
+
120
+ type VfsStat = {
121
+ mode: number;
122
+ type: "file" | "directory";
123
+ updatedAt: Date;
124
+ size?: number;
125
+ childrenCount?: number;
126
+ };
127
+
128
+ function longListing(
129
+ vfs: {
130
+ stat: (p: string) => VfsStat;
131
+ isSymlink: (p: string) => boolean;
132
+ readFile: (p: string) => string;
133
+ },
134
+ dir: string,
135
+ items: string[],
136
+ ): string {
137
+ const base = dir === "/" ? "" : dir;
138
+
139
+ type Row = { perms: string; nlink: string; size: string; date: string; label: string };
140
+
141
+ const rows: Row[] = items.map((name) => {
142
+ const childPath = `${base}/${name}`;
143
+ const isLink = vfs.isSymlink(childPath);
144
+ let stat: VfsStat;
145
+ try { stat = vfs.stat(childPath); } catch {
146
+ return {
147
+ perms: "----------", nlink: "1", size: "0",
148
+ date: formatDate(new Date()), label: name,
149
+ };
150
+ }
151
+
152
+ const mode = isLink ? 0o120777 : stat.mode;
153
+ const perms = formatPermissions(mode, stat.type, isLink);
154
+
155
+ // nlink: dirs = children + 2 (. and ..), files/links = 1
156
+ const nlink = stat.type === "directory"
157
+ ? String((stat.childrenCount ?? 0) + 2)
158
+ : "1";
159
+
160
+ // size: links → target path length, files → bytes, dirs → children×4096
161
+ const rawSize = isLink
162
+ ? readlinkTarget(vfs, childPath).length
163
+ : stat.type === "file"
164
+ ? (stat.size ?? 0)
165
+ : (stat.childrenCount ?? 0) * 4096;
166
+ const size = String(rawSize);
167
+
168
+ const date = formatDate(stat.updatedAt);
169
+ const color = entryColor(mode, stat.type, isLink);
170
+
171
+ const label = isLink
172
+ ? `${colorize(name, color)} -> ${readlinkTarget(vfs, childPath)}`
173
+ : colorize(name, color);
174
+
175
+ return { perms, nlink, size, date, label };
176
+ });
177
+
178
+ const wNlink = Math.max(...rows.map((r) => r.nlink.length));
179
+ const wSize = Math.max(...rows.map((r) => r.size.length));
180
+ const owner = "root";
181
+ const group = "root";
182
+
183
+ const total = items.length * 8;
184
+ const lines = rows.map((r) =>
185
+ `${r.perms} ${r.nlink.padStart(wNlink)} ${owner} ${group} ${r.size.padStart(wSize)} ${r.date} ${r.label}`,
186
+ );
187
+
188
+ return `total ${total}\n${lines.join("\n")}`;
189
+ }
190
+
191
+ // ─── command ─────────────────────────────────────────────────────────────────
192
+
29
193
  export const lsCommand: ShellModule = {
30
194
  name: "ls",
31
195
  description: "List directory contents",
32
196
  category: "navigation",
33
197
  params: ["[-la] [path]"],
34
198
  run: ({ authUser, shell, cwd, args }) => {
35
- const longFormat = ifFlag(args, ["-l", "--long"]);
36
- const showHidden = ifFlag(args, ["-a", "--all"]);
199
+ const longFormat = ifFlag(args, ["-l", "--long", "-la", "-al"]);
200
+ const showHidden = ifFlag(args, ["-a", "--all", "-la", "-al"]);
201
+
37
202
  const targetArg = getArg(args, 0, {
38
203
  flags: ["-l", "--long", "-a", "--all", "-la", "-al"],
39
204
  });
40
205
  const target = resolvePath(cwd, targetArg ?? cwd);
41
206
  assertPathAccess(authUser, target, "ls");
42
207
 
43
- // If target is a file, show its info directly (ls -l /etc/passwd)
208
+ // Single file or symlink
44
209
  if (shell.vfs.exists(target)) {
45
- const st = shell.vfs.stat(target);
46
- if (st.type === "file" || shell.vfs.isSymlink(target)) {
210
+ const st = shell.vfs.stat(target);
211
+ const isLink = shell.vfs.isSymlink(target);
212
+ if (st.type === "file" || isLink) {
213
+ const name = target.split("/").pop() ?? target;
214
+ const color = entryColor(isLink ? 0o120777 : st.mode, st.type, isLink);
47
215
  if (longFormat) {
48
- const name = target.split("/").pop() ?? target;
49
- const size = st.type === "file" ? st.size : 0;
216
+ const mode = isLink ? 0o120777 : st.mode;
217
+ const size = isLink
218
+ ? readlinkTarget(shell.vfs, target).length
219
+ : (st as { size?: number }).size ?? 0;
220
+ const perms = formatPermissions(mode, st.type, isLink);
221
+ const label = isLink
222
+ ? `${colorize(name, color)} -> ${readlinkTarget(shell.vfs, target)}`
223
+ : colorize(name, color);
50
224
  return {
51
- stdout: `${formatPermissions(st.mode, false)} 1 root root ${size} ${formatDate(st.updatedAt)} ${name}\n`,
225
+ stdout: `${perms} 1 root root ${size} ${formatDate(st.updatedAt)} ${label}\n`,
52
226
  exitCode: 0,
53
227
  };
54
228
  }
55
- return { stdout: target.split("/").pop() ?? target, exitCode: 0 };
229
+ return { stdout: `${colorize(name, color)}\n`, exitCode: 0 };
56
230
  }
57
231
  }
58
232
 
59
233
  const items = shell.vfs
60
234
  .list(target)
61
235
  .filter((name) => showHidden || !name.startsWith("."));
236
+
62
237
  const rendered = longFormat
63
- ? items
64
- .map((name) => {
65
- const childPath = resolvePath(target, name);
66
- const stat = shell.vfs.stat(childPath);
67
- const size = stat.type === "file" ? stat.size : stat.childrenCount;
68
- return `${formatPermissions(stat.mode, stat.type === "directory")} 1 ${size} ${formatDate(stat.updatedAt)} ${name}${stat.type === "directory" ? "/" : ""}`;
69
- })
70
- .join("\n")
71
- : joinListWithType(target, items, (p) => shell.vfs.stat(p));
72
- return { stdout: rendered, exitCode: 0 };
238
+ ? longListing(shell.vfs, target, items)
239
+ : shortListing(shell.vfs, target, items);
240
+
241
+ return { stdout: `${rendered}\n`, exitCode: 0 };
73
242
  },
74
- };
243
+ };
@@ -3,9 +3,9 @@ import { executeStatements } from "../SSHMimic/executor";
3
3
  import type { VirtualShell } from "../VirtualShell";
4
4
  import { parseScript } from "../VirtualShell/shellParser";
5
5
  import type {
6
- CommandMode,
7
- CommandResult,
8
- ShellEnv,
6
+ CommandMode,
7
+ CommandResult,
8
+ ShellEnv,
9
9
  } from "../types/commands";
10
10
  import { expandAsync, expandBraces } from "../utils/expand";
11
11
  import { tokenizeCommand } from "../utils/tokenize";
@@ -77,6 +77,44 @@ export async function runCommandDirect(
77
77
  stdin: string | undefined,
78
78
  env: ShellEnv,
79
79
  ): Promise<CommandResult> {
80
+ const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
81
+ const invocation = [name, ...args];
82
+ let assignCount = 0;
83
+ while (assignCount < invocation.length && assignRe.test(invocation[assignCount]!)) {
84
+ assignCount += 1;
85
+ }
86
+ if (assignCount > 0) {
87
+ const assignments = invocation.slice(0, assignCount).map((token) => token.match(assignRe)!);
88
+ const remaining = invocation.slice(assignCount);
89
+ const restored: Array<[string, string | undefined]> = [];
90
+ for (const [, key, value] of assignments) {
91
+ restored.push([key!, env.vars[key!]]);
92
+ env.vars[key!] = value!;
93
+ }
94
+ if (remaining.length === 0) {
95
+ return { exitCode: 0 };
96
+ }
97
+ try {
98
+ const result = await runCommandDirect(
99
+ remaining[0]!,
100
+ remaining.slice(1),
101
+ authUser,
102
+ hostname,
103
+ mode,
104
+ cwd,
105
+ shell,
106
+ stdin,
107
+ env,
108
+ );
109
+ return result;
110
+ } finally {
111
+ for (const [key, value] of restored) {
112
+ if (value === undefined) delete env.vars[key];
113
+ else env.vars[key] = value;
114
+ }
115
+ }
116
+ }
117
+
80
118
  const aliasVal = env.vars[`__alias_${name}`];
81
119
  if (aliasVal) {
82
120
  return runCommand(
@@ -250,6 +288,21 @@ export async function runCommand(
250
288
  );
251
289
 
252
290
  const parts = tokenizeCommand(expanded.trim());
291
+ if (parts.length === 0) return { exitCode: 0 };
292
+ const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
293
+ if (assignRe.test(parts[0]!)) {
294
+ return runCommandDirect(
295
+ parts[0]!,
296
+ parts.slice(1),
297
+ authUser,
298
+ hostname,
299
+ mode,
300
+ cwd,
301
+ shell,
302
+ stdin,
303
+ shellEnv,
304
+ );
305
+ }
253
306
  const commandName = parts[0]?.toLowerCase() ?? "";
254
307
  // Apply brace expansion to each arg token
255
308
  const args = parts.slice(1).flatMap(expandBraces);