typescript-virtual-container 1.3.4 → 1.4.1

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 (368) hide show
  1. package/.vscode/settings.json +0 -1
  2. package/README.md +674 -1504
  3. package/benchmark-results.txt +21 -21
  4. package/builds/self-standalone.js +282 -332
  5. package/builds/self-standalone.js.map +4 -4
  6. package/builds/standalone-wo-sftp.js +218 -282
  7. package/builds/standalone-wo-sftp.js.map +4 -4
  8. package/builds/standalone.js +271 -335
  9. package/builds/standalone.js.map +4 -4
  10. package/builds/web-full-api.min.js +3 -3
  11. package/builds/web-full-api.min.js.map +4 -4
  12. package/builds/web.min.js +2 -2
  13. package/builds/web.min.js.map +4 -4
  14. package/bun.lock +14 -12
  15. package/dist/SSHClient/index.d.ts.map +1 -1
  16. package/dist/SSHClient/index.js +5 -3
  17. package/dist/SSHMimic/executor.d.ts +1 -3
  18. package/dist/SSHMimic/executor.d.ts.map +1 -1
  19. package/dist/SSHMimic/executor.js +20 -22
  20. package/dist/SSHMimic/index.d.ts.map +1 -1
  21. package/dist/SSHMimic/index.js +5 -3
  22. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  23. package/dist/SSHMimic/sftp.js +26 -21
  24. package/dist/VirtualPackageManager/index.d.ts.map +1 -1
  25. package/dist/VirtualPackageManager/index.js +29 -1
  26. package/dist/VirtualShell/shell.d.ts.map +1 -1
  27. package/dist/VirtualShell/shell.js +25 -3
  28. package/dist/VirtualShell/shellParser.d.ts +1 -8
  29. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  30. package/dist/VirtualShell/shellParser.js +2 -81
  31. package/dist/VirtualUserManager/index.d.ts +7 -1
  32. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  33. package/dist/VirtualUserManager/index.js +47 -16
  34. package/dist/commands/adduser.d.ts +10 -4
  35. package/dist/commands/adduser.d.ts.map +1 -1
  36. package/dist/commands/adduser.js +75 -12
  37. package/dist/commands/alias.d.ts +5 -0
  38. package/dist/commands/alias.d.ts.map +1 -1
  39. package/dist/commands/alias.js +5 -0
  40. package/dist/commands/apt.d.ts +5 -0
  41. package/dist/commands/apt.d.ts.map +1 -1
  42. package/dist/commands/apt.js +5 -0
  43. package/dist/commands/awk.d.ts +10 -8
  44. package/dist/commands/awk.d.ts.map +1 -1
  45. package/dist/commands/awk.js +156 -28
  46. package/dist/commands/cd.d.ts.map +1 -1
  47. package/dist/commands/cd.js +0 -3
  48. package/dist/commands/clear.d.ts +5 -0
  49. package/dist/commands/clear.d.ts.map +1 -1
  50. package/dist/commands/clear.js +5 -0
  51. package/dist/commands/command-helpers.d.ts.map +1 -1
  52. package/dist/commands/command-helpers.js +8 -0
  53. package/dist/commands/curl.d.ts.map +1 -1
  54. package/dist/commands/curl.js +2 -1
  55. package/dist/commands/declare.d.ts +5 -0
  56. package/dist/commands/declare.d.ts.map +1 -1
  57. package/dist/commands/declare.js +5 -0
  58. package/dist/commands/deluser.d.ts +12 -0
  59. package/dist/commands/deluser.d.ts.map +1 -1
  60. package/dist/commands/deluser.js +72 -6
  61. package/dist/commands/df.d.ts +5 -0
  62. package/dist/commands/df.d.ts.map +1 -1
  63. package/dist/commands/df.js +5 -0
  64. package/dist/commands/du.d.ts +5 -0
  65. package/dist/commands/du.d.ts.map +1 -1
  66. package/dist/commands/du.js +5 -0
  67. package/dist/commands/export.d.ts +5 -0
  68. package/dist/commands/export.d.ts.map +1 -1
  69. package/dist/commands/export.js +5 -0
  70. package/dist/commands/grep.d.ts.map +1 -1
  71. package/dist/commands/grep.js +22 -4
  72. package/dist/commands/groups.d.ts +5 -0
  73. package/dist/commands/groups.d.ts.map +1 -1
  74. package/dist/commands/groups.js +5 -0
  75. package/dist/commands/gzip.d.ts +5 -2
  76. package/dist/commands/gzip.d.ts.map +1 -1
  77. package/dist/commands/gzip.js +54 -28
  78. package/dist/commands/head.d.ts.map +1 -1
  79. package/dist/commands/head.js +12 -3
  80. package/dist/commands/htop.d.ts +5 -0
  81. package/dist/commands/htop.d.ts.map +1 -1
  82. package/dist/commands/htop.js +5 -0
  83. package/dist/commands/kill.d.ts +5 -0
  84. package/dist/commands/kill.d.ts.map +1 -1
  85. package/dist/commands/kill.js +5 -0
  86. package/dist/commands/ln.d.ts +2 -0
  87. package/dist/commands/ln.d.ts.map +1 -1
  88. package/dist/commands/ln.js +22 -0
  89. package/dist/commands/ls.d.ts.map +1 -1
  90. package/dist/commands/ls.js +15 -0
  91. package/dist/commands/lsb-release.d.ts +5 -0
  92. package/dist/commands/lsb-release.d.ts.map +1 -1
  93. package/dist/commands/lsb-release.js +5 -0
  94. package/dist/commands/man.d.ts.map +1 -1
  95. package/dist/commands/man.js +30 -136
  96. package/dist/commands/mkdir.d.ts +5 -0
  97. package/dist/commands/mkdir.d.ts.map +1 -1
  98. package/dist/commands/mkdir.js +5 -0
  99. package/dist/commands/mv.d.ts +5 -0
  100. package/dist/commands/mv.d.ts.map +1 -1
  101. package/dist/commands/mv.js +5 -0
  102. package/dist/commands/nano.d.ts +5 -0
  103. package/dist/commands/nano.d.ts.map +1 -1
  104. package/dist/commands/nano.js +5 -0
  105. package/dist/commands/neofetch.d.ts +5 -0
  106. package/dist/commands/neofetch.d.ts.map +1 -1
  107. package/dist/commands/neofetch.js +14 -5
  108. package/dist/commands/passwd.d.ts +8 -0
  109. package/dist/commands/passwd.d.ts.map +1 -1
  110. package/dist/commands/passwd.js +32 -11
  111. package/dist/commands/ping.d.ts +5 -0
  112. package/dist/commands/ping.d.ts.map +1 -1
  113. package/dist/commands/ping.js +5 -0
  114. package/dist/commands/printf.d.ts +5 -0
  115. package/dist/commands/printf.d.ts.map +1 -1
  116. package/dist/commands/printf.js +43 -12
  117. package/dist/commands/ps.d.ts +5 -0
  118. package/dist/commands/ps.d.ts.map +1 -1
  119. package/dist/commands/ps.js +5 -0
  120. package/dist/commands/read.d.ts +5 -0
  121. package/dist/commands/read.d.ts.map +1 -1
  122. package/dist/commands/read.js +5 -0
  123. package/dist/commands/registry.d.ts.map +1 -1
  124. package/dist/commands/registry.js +4 -1
  125. package/dist/commands/rm.d.ts +5 -0
  126. package/dist/commands/rm.d.ts.map +1 -1
  127. package/dist/commands/rm.js +5 -0
  128. package/dist/commands/runtime.d.ts.map +1 -1
  129. package/dist/commands/runtime.js +1 -57
  130. package/dist/commands/sed.d.ts +5 -0
  131. package/dist/commands/sed.d.ts.map +1 -1
  132. package/dist/commands/sed.js +5 -0
  133. package/dist/commands/set.d.ts +5 -6
  134. package/dist/commands/set.d.ts.map +1 -1
  135. package/dist/commands/set.js +5 -22
  136. package/dist/commands/sh.d.ts +6 -0
  137. package/dist/commands/sh.d.ts.map +1 -1
  138. package/dist/commands/sh.js +6 -0
  139. package/dist/commands/shift.d.ts +10 -0
  140. package/dist/commands/shift.d.ts.map +1 -1
  141. package/dist/commands/shift.js +10 -0
  142. package/dist/commands/sleep.d.ts +5 -0
  143. package/dist/commands/sleep.d.ts.map +1 -1
  144. package/dist/commands/sleep.js +5 -0
  145. package/dist/commands/sort.d.ts +5 -0
  146. package/dist/commands/sort.d.ts.map +1 -1
  147. package/dist/commands/sort.js +5 -0
  148. package/dist/commands/source.d.ts +5 -0
  149. package/dist/commands/source.d.ts.map +1 -1
  150. package/dist/commands/source.js +5 -0
  151. package/dist/commands/stat.d.ts +7 -0
  152. package/dist/commands/stat.d.ts.map +1 -0
  153. package/dist/commands/stat.js +56 -0
  154. package/dist/commands/su.d.ts +13 -0
  155. package/dist/commands/su.d.ts.map +1 -1
  156. package/dist/commands/su.js +45 -14
  157. package/dist/commands/sudo.d.ts.map +1 -1
  158. package/dist/commands/sudo.js +5 -0
  159. package/dist/commands/tail.d.ts +5 -0
  160. package/dist/commands/tail.d.ts.map +1 -1
  161. package/dist/commands/tail.js +15 -3
  162. package/dist/commands/tar.d.ts +5 -0
  163. package/dist/commands/tar.d.ts.map +1 -1
  164. package/dist/commands/tar.js +40 -10
  165. package/dist/commands/tee.d.ts +5 -0
  166. package/dist/commands/tee.d.ts.map +1 -1
  167. package/dist/commands/tee.js +5 -0
  168. package/dist/commands/touch.d.ts +5 -0
  169. package/dist/commands/touch.d.ts.map +1 -1
  170. package/dist/commands/touch.js +5 -0
  171. package/dist/commands/tr.d.ts.map +1 -1
  172. package/dist/commands/tr.js +45 -10
  173. package/dist/commands/tree.d.ts +5 -0
  174. package/dist/commands/tree.d.ts.map +1 -1
  175. package/dist/commands/tree.js +5 -0
  176. package/dist/commands/true.d.ts +10 -0
  177. package/dist/commands/true.d.ts.map +1 -1
  178. package/dist/commands/true.js +10 -0
  179. package/dist/commands/type.d.ts +5 -0
  180. package/dist/commands/type.d.ts.map +1 -1
  181. package/dist/commands/type.js +5 -0
  182. package/dist/commands/uname.d.ts +5 -0
  183. package/dist/commands/uname.d.ts.map +1 -1
  184. package/dist/commands/uname.js +5 -0
  185. package/dist/commands/uniq.d.ts +5 -0
  186. package/dist/commands/uniq.d.ts.map +1 -1
  187. package/dist/commands/uniq.js +5 -0
  188. package/dist/commands/unset.d.ts +5 -0
  189. package/dist/commands/unset.d.ts.map +1 -1
  190. package/dist/commands/unset.js +5 -0
  191. package/dist/commands/uptime.d.ts +5 -0
  192. package/dist/commands/uptime.d.ts.map +1 -1
  193. package/dist/commands/uptime.js +5 -0
  194. package/dist/commands/wc.d.ts +5 -0
  195. package/dist/commands/wc.d.ts.map +1 -1
  196. package/dist/commands/wc.js +5 -0
  197. package/dist/commands/wget.d.ts +5 -0
  198. package/dist/commands/wget.d.ts.map +1 -1
  199. package/dist/commands/wget.js +16 -1
  200. package/dist/commands/who.d.ts +5 -0
  201. package/dist/commands/who.d.ts.map +1 -1
  202. package/dist/commands/who.js +5 -0
  203. package/dist/commands/whoami.d.ts +5 -0
  204. package/dist/commands/whoami.d.ts.map +1 -1
  205. package/dist/commands/whoami.js +5 -0
  206. package/dist/commands/xargs.d.ts +5 -0
  207. package/dist/commands/xargs.d.ts.map +1 -1
  208. package/dist/commands/xargs.js +5 -0
  209. package/dist/self-standalone.js +254 -30
  210. package/dist/types/commands.d.ts +36 -0
  211. package/dist/types/commands.d.ts.map +1 -1
  212. package/dist/utils/tokenize.d.ts +20 -0
  213. package/dist/utils/tokenize.d.ts.map +1 -0
  214. package/dist/utils/tokenize.js +74 -0
  215. package/examples/web.min.js +2 -2
  216. package/package.json +2 -2
  217. package/src/SSHClient/index.ts +6 -3
  218. package/src/SSHMimic/executor.ts +21 -44
  219. package/src/SSHMimic/index.ts +7 -5
  220. package/src/SSHMimic/sftp.ts +28 -21
  221. package/src/VirtualPackageManager/index.ts +29 -1
  222. package/src/VirtualShell/shell.ts +34 -4
  223. package/src/VirtualShell/shellParser.ts +2 -103
  224. package/src/VirtualUserManager/index.ts +43 -19
  225. package/src/commands/adduser.ts +86 -13
  226. package/src/commands/alias.ts +5 -0
  227. package/src/commands/apt.ts +5 -0
  228. package/src/commands/awk.ts +154 -29
  229. package/src/commands/cd.ts +0 -4
  230. package/src/commands/clear.ts +5 -0
  231. package/src/commands/command-helpers.ts +9 -0
  232. package/src/commands/curl.ts +2 -1
  233. package/src/commands/declare.ts +5 -0
  234. package/src/commands/deluser.ts +84 -7
  235. package/src/commands/df.ts +5 -0
  236. package/src/commands/du.ts +5 -0
  237. package/src/commands/export.ts +5 -0
  238. package/src/commands/grep.ts +21 -8
  239. package/src/commands/groups.ts +5 -0
  240. package/src/commands/gzip.ts +61 -28
  241. package/src/commands/head.ts +14 -4
  242. package/src/commands/htop.ts +5 -0
  243. package/src/commands/kill.ts +5 -0
  244. package/src/commands/ln.ts +22 -0
  245. package/src/commands/ls.ts +17 -0
  246. package/src/commands/lsb-release.ts +5 -0
  247. package/src/commands/man.ts +38 -143
  248. package/src/commands/manuals/adduser.txt +11 -0
  249. package/src/commands/manuals/apt-cache.txt +12 -0
  250. package/src/commands/manuals/apt.txt +20 -0
  251. package/src/commands/manuals/awk.txt +13 -0
  252. package/src/commands/manuals/cat.txt +14 -0
  253. package/src/commands/manuals/cd.txt +16 -0
  254. package/src/commands/manuals/chmod.txt +16 -0
  255. package/src/commands/manuals/clear.txt +10 -0
  256. package/src/commands/manuals/cp.txt +10 -0
  257. package/src/commands/manuals/curl.txt +20 -0
  258. package/src/commands/manuals/date.txt +14 -0
  259. package/src/commands/manuals/declare.txt +12 -0
  260. package/src/commands/manuals/deluser.txt +10 -0
  261. package/src/commands/manuals/df.txt +10 -0
  262. package/src/commands/manuals/dpkg-query.txt +11 -0
  263. package/src/commands/manuals/dpkg.txt +14 -0
  264. package/src/commands/manuals/du.txt +11 -0
  265. package/src/commands/manuals/echo.txt +11 -0
  266. package/src/commands/manuals/false.txt +10 -0
  267. package/src/commands/manuals/find.txt +11 -0
  268. package/src/commands/manuals/free.txt +12 -0
  269. package/src/commands/manuals/grep.txt +13 -0
  270. package/src/commands/manuals/groups.txt +10 -0
  271. package/src/commands/manuals/gzip.txt +11 -0
  272. package/src/commands/manuals/head.txt +10 -0
  273. package/src/commands/manuals/help.txt +11 -0
  274. package/src/commands/manuals/history.txt +11 -0
  275. package/src/commands/manuals/hostname.txt +10 -0
  276. package/src/commands/manuals/id.txt +10 -0
  277. package/src/commands/manuals/kill.txt +13 -0
  278. package/src/commands/manuals/ls.txt +20 -0
  279. package/src/commands/manuals/lsb_release.txt +14 -0
  280. package/src/commands/manuals/mkdir.txt +10 -0
  281. package/src/commands/manuals/mv.txt +10 -0
  282. package/src/commands/manuals/nano.txt +11 -0
  283. package/src/commands/manuals/neofetch.txt +10 -0
  284. package/src/commands/manuals/node.txt +13 -0
  285. package/src/commands/manuals/npm.txt +13 -0
  286. package/src/commands/manuals/npx.txt +13 -0
  287. package/src/commands/manuals/passwd.txt +11 -0
  288. package/src/commands/manuals/ping.txt +10 -0
  289. package/src/commands/manuals/printf.txt +11 -0
  290. package/src/commands/manuals/ps.txt +10 -0
  291. package/src/commands/manuals/pwd.txt +10 -0
  292. package/src/commands/manuals/python3.txt +13 -0
  293. package/src/commands/manuals/readlink.txt +10 -0
  294. package/src/commands/manuals/return.txt +10 -0
  295. package/src/commands/manuals/rm.txt +10 -0
  296. package/src/commands/manuals/sed.txt +11 -0
  297. package/src/commands/manuals/set.txt +11 -0
  298. package/src/commands/manuals/shift.txt +10 -0
  299. package/src/commands/manuals/sleep.txt +10 -0
  300. package/src/commands/manuals/sort.txt +12 -0
  301. package/src/commands/manuals/source.txt +11 -0
  302. package/src/commands/manuals/ssh.txt +11 -0
  303. package/src/commands/manuals/stat.txt +10 -0
  304. package/src/commands/manuals/su.txt +13 -0
  305. package/src/commands/manuals/sudo.txt +11 -0
  306. package/src/commands/manuals/tail.txt +10 -0
  307. package/src/commands/manuals/tar.txt +19 -0
  308. package/src/commands/manuals/tee.txt +10 -0
  309. package/src/commands/manuals/test.txt +11 -0
  310. package/src/commands/manuals/touch.txt +11 -0
  311. package/src/commands/manuals/tr.txt +10 -0
  312. package/src/commands/manuals/trap.txt +10 -0
  313. package/src/commands/manuals/true.txt +10 -0
  314. package/src/commands/manuals/type.txt +10 -0
  315. package/src/commands/manuals/uname.txt +12 -0
  316. package/src/commands/manuals/uniq.txt +12 -0
  317. package/src/commands/manuals/unset.txt +10 -0
  318. package/src/commands/manuals/uptime.txt +11 -0
  319. package/src/commands/manuals/wc.txt +12 -0
  320. package/src/commands/manuals/wget.txt +12 -0
  321. package/src/commands/manuals/which.txt +10 -0
  322. package/src/commands/manuals/whoami.txt +10 -0
  323. package/src/commands/manuals/xargs.txt +10 -0
  324. package/src/commands/mkdir.ts +5 -0
  325. package/src/commands/mv.ts +5 -0
  326. package/src/commands/nano.ts +5 -0
  327. package/src/commands/neofetch.ts +15 -6
  328. package/src/commands/passwd.ts +35 -12
  329. package/src/commands/ping.ts +5 -0
  330. package/src/commands/printf.ts +30 -13
  331. package/src/commands/ps.ts +5 -0
  332. package/src/commands/read.ts +5 -0
  333. package/src/commands/registry.ts +4 -1
  334. package/src/commands/rm.ts +5 -0
  335. package/src/commands/runtime.ts +1 -61
  336. package/src/commands/sed.ts +5 -0
  337. package/src/commands/set.ts +5 -24
  338. package/src/commands/sh.ts +9 -3
  339. package/src/commands/shift.ts +10 -0
  340. package/src/commands/sleep.ts +5 -0
  341. package/src/commands/sort.ts +5 -0
  342. package/src/commands/source.ts +5 -0
  343. package/src/commands/stat.ts +61 -0
  344. package/src/commands/su.ts +54 -16
  345. package/src/commands/sudo.ts +5 -0
  346. package/src/commands/tail.ts +17 -3
  347. package/src/commands/tar.ts +38 -15
  348. package/src/commands/tee.ts +5 -0
  349. package/src/commands/touch.ts +5 -0
  350. package/src/commands/tr.ts +54 -10
  351. package/src/commands/tree.ts +5 -0
  352. package/src/commands/true.ts +10 -0
  353. package/src/commands/type.ts +5 -0
  354. package/src/commands/uname.ts +5 -0
  355. package/src/commands/uniq.ts +5 -0
  356. package/src/commands/unset.ts +5 -0
  357. package/src/commands/uptime.ts +5 -0
  358. package/src/commands/wc.ts +5 -0
  359. package/src/commands/wget.ts +17 -1
  360. package/src/commands/who.ts +5 -0
  361. package/src/commands/whoami.ts +5 -0
  362. package/src/commands/xargs.ts +5 -0
  363. package/src/self-standalone.ts +316 -33
  364. package/src/types/commands.ts +37 -0
  365. package/src/utils/tokenize.ts +78 -0
  366. package/tests/new-features.test.ts +2 -2
  367. package/builds/web-iife.min.js +0 -13
  368. package/builds/web-iife.min.js.map +0 -7
@@ -0,0 +1,20 @@
1
+ /**
2
+ * tokenize.ts
3
+ *
4
+ * Shared shell tokenizer used by `shellParser.ts` and `runtime.ts`.
5
+ * Splits a shell input string into tokens respecting single and double
6
+ * quotes, and separates `>`, `>>`, `<` as standalone redirect tokens.
7
+ */
8
+ /**
9
+ * Tokenize a shell command line respecting quoted strings and redirect
10
+ * operators.
11
+ *
12
+ * - Single-quoted content is preserved verbatim.
13
+ * - Double-quoted content is preserved (expansion happens later).
14
+ * - `>`, `>>`, and `<` are emitted as standalone tokens.
15
+ *
16
+ * @param input Raw shell command string.
17
+ * @returns Array of string tokens.
18
+ */
19
+ export declare function tokenizeCommand(input: string): string[];
20
+ //# sourceMappingURL=tokenize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenize.d.ts","sourceRoot":"","sources":["../../src/utils/tokenize.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA0DvD"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * tokenize.ts
3
+ *
4
+ * Shared shell tokenizer used by `shellParser.ts` and `runtime.ts`.
5
+ * Splits a shell input string into tokens respecting single and double
6
+ * quotes, and separates `>`, `>>`, `<` as standalone redirect tokens.
7
+ */
8
+ /**
9
+ * Tokenize a shell command line respecting quoted strings and redirect
10
+ * operators.
11
+ *
12
+ * - Single-quoted content is preserved verbatim.
13
+ * - Double-quoted content is preserved (expansion happens later).
14
+ * - `>`, `>>`, and `<` are emitted as standalone tokens.
15
+ *
16
+ * @param input Raw shell command string.
17
+ * @returns Array of string tokens.
18
+ */
19
+ export function tokenizeCommand(input) {
20
+ const tokens = [];
21
+ let current = "";
22
+ let inQ = false;
23
+ let qChar = "";
24
+ let i = 0;
25
+ while (i < input.length) {
26
+ const ch = input[i];
27
+ const next = input[i + 1];
28
+ if ((ch === '"' || ch === "'") && !inQ) {
29
+ inQ = true;
30
+ qChar = ch;
31
+ i++;
32
+ continue;
33
+ }
34
+ if (inQ && ch === qChar) {
35
+ inQ = false;
36
+ qChar = "";
37
+ i++;
38
+ continue;
39
+ }
40
+ if (inQ) {
41
+ current += ch;
42
+ i++;
43
+ continue;
44
+ }
45
+ if (ch === " ") {
46
+ if (current) {
47
+ tokens.push(current);
48
+ current = "";
49
+ }
50
+ i++;
51
+ continue;
52
+ }
53
+ if ((ch === ">" || ch === "<") && !inQ) {
54
+ if (current) {
55
+ tokens.push(current);
56
+ current = "";
57
+ }
58
+ if (ch === ">" && next === ">") {
59
+ tokens.push(">>");
60
+ i += 2;
61
+ }
62
+ else {
63
+ tokens.push(ch);
64
+ i++;
65
+ }
66
+ continue;
67
+ }
68
+ current += ch;
69
+ i++;
70
+ }
71
+ if (current)
72
+ tokens.push(current);
73
+ return tokens;
74
+ }
@@ -1,4 +1,4 @@
1
- var P=Object.defineProperty;var A=(o,e,t)=>e in o?P(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var u=(o,e,t)=>A(o,typeof e!="symbol"?e+"":e,t);function v(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=O(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 O(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=_(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 _(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 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 k(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 k(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 w(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(),D=(await r(g)).replace(/\n$/,"");n+=D,i=d+1;continue}n+=a,i++}o=n}return p(o,e,t)}var M=new TextEncoder,B=new TextDecoder;function T(o){let e="";for(let t of o)e+=String.fromCharCode(t);return btoa(e)}function C(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 E(o){return o.type==="file"?{...o,contentBase64:o.contentBase64}:{...o,children:o.children.map(e=>E(e))}}function N(o,e){let t=new Date().toISOString();return{type:"directory",name:o,mode:e,createdAt:t,updatedAt:t,children:[]}}function j(o,e,t){let r=new Date().toISOString();return{type:"file",name:o,mode:t,createdAt:r,updatedAt:r,contentBase64:T(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 W(o,e){o.children=o.children.filter(t=>t.name!==e)}function S(o){return l(o).split("/").filter(Boolean)}var V=globalThis;function $(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=N("",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 $(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 $(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=S(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=S(r),s=this.root;for(let i of n){let a=b(s,i);if(!a){let c=N(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.`);W(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:C(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(C(t.contentBase64))}writeFile(e,t,r=420){let n=l(e),s=this.ensureDirectory(h(n),493),i=typeof t=="string"?M.encode(t):t,a=j(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)}`);W(n,f(e));let i=E(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)}},x=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 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(`
2
2
  `)}
3
3
  `,exitCode:0})}),this.register({name:"pwd",description:"Print current directory",params:[],run:()=>({stdout:`${this.cwd}
4
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}
@@ -9,5 +9,5 @@ var P=Object.defineProperty;var A=(o,e,t)=>e in o?P(o,e,{enumerable:!0,configura
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 w(r,this.env.vars,this.env.lastExitCode,a=>this.executeCommandLine(a,!1).then(c=>c.stdout??"")),s=v(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 G(o="typescript-vm",e={}){return new x(o,e)}export{y as IndexedDbMirrorVfs,x as WebShell,G 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 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};
13
13
  //# sourceMappingURL=web.min.js.map
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "typescript-virtual-container",
3
- "description": "In-memory SSH server with virtual filesystem and typed programmatic API",
3
+ "description": "Scalable Linux emulator with included SSH/SFTP server, virtual filesystem and typed programmatic API for testing, automation, and interactive shell scripting in TypeScript/JavaScript.",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
7
- "version": "1.3.4",
7
+ "version": "1.4.1",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
@@ -62,11 +62,14 @@ export class SshClient {
62
62
  );
63
63
 
64
64
  // Handle async results
65
- if (result instanceof Promise) {
66
- return await result;
65
+ const resolved = result instanceof Promise ? await result : result;
66
+
67
+ // Propagate cwd changes (cd, su, etc.)
68
+ if (resolved.nextCwd && (resolved.exitCode ?? 0) === 0) {
69
+ this.currentCwd = resolved.nextCwd;
67
70
  }
68
71
 
69
- return result;
72
+ return resolved;
70
73
  }
71
74
 
72
75
  /**
@@ -4,54 +4,13 @@ import type { CommandMode, CommandResult, ShellEnv } from "../types/commands";
4
4
  import type {
5
5
  Pipeline,
6
6
  PipelineCommand,
7
- Script,
8
7
  Statement,
9
8
  } from "../types/pipeline";
10
9
  import type { VirtualShell } from "../VirtualShell";
11
10
 
12
11
  // ── Script executor (handles &&/||/;) ────────────────────────────────────────
13
12
 
14
- export async function executeScript(
15
- script: Script,
16
- authUser: string,
17
- hostname: string,
18
- mode: CommandMode,
19
- cwd: string,
20
- shell: VirtualShell,
21
- env: ShellEnv,
22
- ): Promise<CommandResult> {
23
- if (!script.isValid)
24
- return { stderr: script.error || "Syntax error", exitCode: 1 };
25
-
26
- let lastResult: CommandResult = { exitCode: 0 };
27
13
 
28
- for (const stmt of script.statements) {
29
- // Decide whether to run this statement based on previous op
30
- lastResult = await executePipeline(
31
- stmt.pipeline,
32
- authUser,
33
- hostname,
34
- mode,
35
- cwd,
36
- shell,
37
- env,
38
- );
39
- env.lastExitCode = lastResult.exitCode ?? 0;
40
-
41
- // Propagate session-control signals
42
- if (
43
- lastResult.closeSession ||
44
- lastResult.switchUser ||
45
- lastResult.nextCwd
46
- ) {
47
- break;
48
- }
49
- }
50
-
51
- return lastResult;
52
- }
53
-
54
- /** Execute statements connected by &&/||/; */
55
14
  export async function executeStatements(
56
15
  statements: Statement[],
57
16
  authUser: string,
@@ -62,6 +21,8 @@ export async function executeStatements(
62
21
  env: ShellEnv,
63
22
  ): Promise<CommandResult> {
64
23
  let last: CommandResult = { exitCode: 0 };
24
+ const accumulatedStdout: string[] = [];
25
+ let currentCwd = cwd; // track cwd changes from cd, su, etc.
65
26
  let i = 0;
66
27
 
67
28
  while (i < statements.length) {
@@ -71,13 +32,26 @@ export async function executeStatements(
71
32
  authUser,
72
33
  hostname,
73
34
  mode,
74
- cwd,
35
+ currentCwd,
75
36
  shell,
76
37
  env,
77
38
  );
78
39
  env.lastExitCode = last.exitCode ?? 0;
79
40
 
80
- if (last.closeSession || last.switchUser) return last;
41
+ // Propagate cwd changes (cd, su -l, etc.)
42
+ if (last.nextCwd && (last.exitCode ?? 0) === 0) {
43
+ currentCwd = last.nextCwd;
44
+ }
45
+
46
+ // Collect stdout from each statement (for echo a; echo b → "a\nb\n")
47
+ if (last.stdout) accumulatedStdout.push(last.stdout);
48
+
49
+ if (last.closeSession || last.switchUser) {
50
+ return {
51
+ ...last,
52
+ stdout: accumulatedStdout.join("") || last.stdout,
53
+ };
54
+ }
81
55
 
82
56
  const op = stmt.op;
83
57
  if (!op || op === ";") {
@@ -95,7 +69,10 @@ export async function executeStatements(
95
69
  }
96
70
  i++;
97
71
  }
98
- return last;
72
+ // Merge accumulated stdout (for "echo a; echo b" → "a\nb\n")
73
+ const merged = accumulatedStdout.join("");
74
+ // Preserve the deepest cwd change across the whole pipeline
75
+ return { ...last, stdout: merged || last.stdout, nextCwd: currentCwd !== cwd ? currentCwd : undefined };
99
76
  }
100
77
 
101
78
  // ── Pipeline executor ─────────────────────────────────────────────────────────
@@ -21,6 +21,11 @@ import { loadOrCreateHostKey } from "./hostKey";
21
21
  */
22
22
  const perf: PerfLogger = createPerfLogger("SshMimic");
23
23
 
24
+ // ── Dev-mode logger ───────────────────────────────────────────────────────────
25
+ const DEV = !!process.env.DEV_MODE;
26
+ const devLog = DEV ? console.log.bind(console) : () => {};
27
+
28
+
24
29
  interface RateLimitEntry {
25
30
  attempts: number;
26
31
  lockedUntil: number;
@@ -152,9 +157,6 @@ class SshMimic extends EventEmitter {
152
157
  // ── Password auth ──────────────────────────────────────
153
158
  if (ctx.method === "password") {
154
159
  if (!shell.users.hasPassword(candidateUser)) {
155
- console.log(
156
- `User ${candidateUser} has no password set, allowing login without verification`,
157
- );
158
160
  authUser = candidateUser;
159
161
  sessionId = shell.users.registerSession(
160
162
  authUser,
@@ -297,7 +299,7 @@ class SshMimic extends EventEmitter {
297
299
  return new Promise<number>((resolve, reject) => {
298
300
  this.server?.once("error", (err: unknown) => reject(err));
299
301
  this.server?.listen(this.port, "0.0.0.0", () => {
300
- console.log(`SSH Mimic listening on port ${this.port}`);
302
+ devLog(`SSH Mimic listening on port ${this.port}`);
301
303
  this.emit("start", { port: this.port });
302
304
  resolve(this.port);
303
305
  });
@@ -311,7 +313,7 @@ class SshMimic extends EventEmitter {
311
313
  perf.mark("stop");
312
314
  if (this.server) {
313
315
  this.server.close(() => {
314
- console.log("SSH Mimic stopped");
316
+ devLog("SSH Mimic stopped");
315
317
  this.emit("stop");
316
318
  });
317
319
  }
@@ -10,6 +10,13 @@ import type VirtualFileSystem from "../VirtualFileSystem";
10
10
  import { VirtualShell } from "../VirtualShell";
11
11
  import type { VirtualUserManager } from "../VirtualUserManager";
12
12
  import { loadOrCreateHostKey } from "./hostKey";
13
+ // ── Dev-mode logger — silent in production ────────────────────────────────────
14
+ const DEV = !!process.env.DEV_MODE;
15
+ const devLog = DEV ? console.log.bind(console) : () => {};
16
+ const devWarn = DEV ? console.warn.bind(console) : () => {};
17
+ const devErr = DEV ? console.error.bind(console): () => {};
18
+
19
+
13
20
 
14
21
  const SFTP_STATUS_CODE = {
15
22
  OK: 0,
@@ -218,7 +225,7 @@ export class SftpMimic extends EventEmitter {
218
225
 
219
226
  // Add error handling for the client
220
227
  client.on("error", (error: unknown) => {
221
- console.error(`[SFTP] Client error:`, error);
228
+ devErr(`[SFTP] Client error:`, error);
222
229
  });
223
230
 
224
231
  const acceptSession = (username: string): void => {
@@ -248,7 +255,7 @@ export class SftpMimic extends EventEmitter {
248
255
  const candidateUser = ctx.username || "root";
249
256
  remoteAddress = (ctx as { ip?: string }).ip ?? remoteAddress;
250
257
 
251
- console.log(
258
+ devLog(
252
259
  `[SFTP] Auth attempt: user=${candidateUser}, method=${ctx.method}, ip=${remoteAddress}`,
253
260
  );
254
261
 
@@ -326,7 +333,7 @@ export class SftpMimic extends EventEmitter {
326
333
 
327
334
  // Add error handling for the session
328
335
  session.on("error", (error: unknown) => {
329
- console.error(
336
+ devErr(
330
337
  `[SFTP] Session error for user=${authUser}:`,
331
338
  error,
332
339
  );
@@ -349,7 +356,7 @@ export class SftpMimic extends EventEmitter {
349
356
  address && typeof address === "object" && "port" in address
350
357
  ? address.port
351
358
  : this.port;
352
- console.log(`SFTP Mimic listening on port ${actualPort}`);
359
+ devLog(`SFTP Mimic listening on port ${actualPort}`);
353
360
  this.emit("start", { port: actualPort });
354
361
  resolve(actualPort as number);
355
362
  });
@@ -360,7 +367,7 @@ export class SftpMimic extends EventEmitter {
360
367
  perf.mark("stop");
361
368
  if (this.server) {
362
369
  this.server.close(() => {
363
- console.log("SFTP Mimic stopped");
370
+ devLog("SFTP Mimic stopped");
364
371
  this.emit("stop");
365
372
  });
366
373
  }
@@ -452,7 +459,7 @@ export class SftpMimic extends EventEmitter {
452
459
 
453
460
  // Security: Confine to home directory
454
461
  if (!this.isPathWithinHome(targetPath, authUser)) {
455
- console.warn(
462
+ devWarn(
456
463
  `[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`,
457
464
  );
458
465
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -507,7 +514,7 @@ export class SftpMimic extends EventEmitter {
507
514
  });
508
515
  sftp.handle(reqid, handle);
509
516
  } catch (error) {
510
- console.error("SFTP OPEN error:", error);
517
+ devErr("SFTP OPEN error:", error);
511
518
  sftp.status(reqid, SFTP_STATUS_CODE.FAILURE);
512
519
  }
513
520
  });
@@ -580,7 +587,7 @@ export class SftpMimic extends EventEmitter {
580
587
  getVfs().writeFile(entry.path, entry.buffer);
581
588
  void getVfs().flushMirror();
582
589
  } catch (error) {
583
- console.error("SFTP CLOSE write error:", error);
590
+ devErr("SFTP CLOSE write error:", error);
584
591
  sftp.status(reqid, SFTP_STATUS_CODE.FAILURE);
585
592
  this.closeHandle(handle);
586
593
  return;
@@ -596,7 +603,7 @@ export class SftpMimic extends EventEmitter {
596
603
 
597
604
  // Security: Confine to home directory
598
605
  if (!this.isPathWithinHome(targetPath, authUser)) {
599
- console.warn(
606
+ devWarn(
600
607
  `[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`,
601
608
  );
602
609
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -649,7 +656,7 @@ export class SftpMimic extends EventEmitter {
649
656
 
650
657
  // Security: Confine to home directory
651
658
  if (!this.isPathWithinHome(targetPath, authUser)) {
652
- console.warn(
659
+ devWarn(
653
660
  `[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`,
654
661
  );
655
662
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -669,7 +676,7 @@ export class SftpMimic extends EventEmitter {
669
676
 
670
677
  // Security: Confine to home directory
671
678
  if (!this.isPathWithinHome(targetPath, authUser)) {
672
- console.warn(
679
+ devWarn(
673
680
  `[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`,
674
681
  );
675
682
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -711,7 +718,7 @@ export class SftpMimic extends EventEmitter {
711
718
 
712
719
  // Security: Confine to home directory
713
720
  if (!this.isPathWithinHome(targetPath, authUser)) {
714
- console.warn(
721
+ devWarn(
715
722
  `[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`,
716
723
  );
717
724
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -734,7 +741,7 @@ export class SftpMimic extends EventEmitter {
734
741
 
735
742
  // Security: Confine to home directory
736
743
  if (!this.isPathWithinHome(normalized, authUser)) {
737
- console.warn(
744
+ devWarn(
738
745
  `[SFTP] Path traversal attempt blocked: user=${authUser}, path=${normalized}`,
739
746
  );
740
747
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -762,7 +769,7 @@ export class SftpMimic extends EventEmitter {
762
769
 
763
770
  // Security: Confine to home directory
764
771
  if (!this.isPathWithinHome(targetPath, authUser)) {
765
- console.warn(
772
+ devWarn(
766
773
  `[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`,
767
774
  );
768
775
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -783,7 +790,7 @@ export class SftpMimic extends EventEmitter {
783
790
 
784
791
  // Security: Confine to home directory
785
792
  if (!this.isPathWithinHome(targetPath, authUser)) {
786
- console.warn(
793
+ devWarn(
787
794
  `[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`,
788
795
  );
789
796
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -804,7 +811,7 @@ export class SftpMimic extends EventEmitter {
804
811
 
805
812
  // Security: Confine to home directory
806
813
  if (!this.isPathWithinHome(targetPath, authUser)) {
807
- console.warn(
814
+ devWarn(
808
815
  `[SFTP] Path traversal attempt blocked: user=${authUser}, path=${targetPath}`,
809
816
  );
810
817
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -829,7 +836,7 @@ export class SftpMimic extends EventEmitter {
829
836
  !this.isPathWithinHome(fromPath, authUser) ||
830
837
  !this.isPathWithinHome(toPath, authUser)
831
838
  ) {
832
- console.warn(
839
+ devWarn(
833
840
  `[SFTP] Path traversal attempt blocked: user=${authUser}, from=${fromPath}, to=${toPath}`,
834
841
  );
835
842
  sftp.status(reqid, SFTP_STATUS_CODE.PERMISSION_DENIED);
@@ -854,21 +861,21 @@ export class SftpMimic extends EventEmitter {
854
861
  });
855
862
 
856
863
  sftp.on("error", (error: Error) => {
857
- console.error(`[SFTP] Stream error for user=${authUser}:`, error);
864
+ devErr(`[SFTP] Stream error for user=${authUser}:`, error);
858
865
  });
859
866
 
860
867
  sftp.on("close", () => {
861
- console.log(`[SFTP] Stream closed for user=${authUser}`);
868
+ devLog(`[SFTP] Stream closed for user=${authUser}`);
862
869
  this.handles.clear();
863
870
  });
864
871
 
865
872
  sftp.on("end", () => {
866
- console.log(`[SFTP] end event for user=${authUser}`);
873
+ devLog(`[SFTP] end event for user=${authUser}`);
867
874
  this.handles.clear();
868
875
  });
869
876
 
870
877
  sftp.on("END", () => {
871
- console.log(`[SFTP] END event for user=${authUser}`);
878
+ devLog(`[SFTP] END event for user=${authUser}`);
872
879
  this.handles.clear();
873
880
  });
874
881
  }
@@ -556,7 +556,35 @@ const PACKAGE_REGISTRY: PackageDefinition[] = [
556
556
  mode: 0o755,
557
557
  },
558
558
  ],
559
- },
559
+ },{
560
+ name: "gzip",
561
+ version: "1.12-2",
562
+ section: "utils",
563
+ description: "GNU compression utility",
564
+ shortDesc: "compression utility",
565
+ installedSizeKb: 128,
566
+ files: [
567
+ {
568
+ path: "/usr/bin/gzip",
569
+ content: "#!/bin/sh\necho 'gzip: virtual stub'\n",
570
+ mode: 0o755,
571
+ },
572
+ ]
573
+ }, {
574
+ name: "neofetch",
575
+ version: "7.1.0-1",
576
+ section: "utils",
577
+ description: "A command-line system information tool written in bash 3.2+",
578
+ shortDesc: "command-line system information tool",
579
+ installedSizeKb: 256,
580
+ files: [
581
+ {
582
+ path: "/usr/bin/neofetch",
583
+ content: "#!/bin/sh\necho 'neofetch: virtual stub'\n",
584
+ mode: 0o755,
585
+ },
586
+ ],
587
+ }
560
588
  ];
561
589
 
562
590
  /**
@@ -15,7 +15,7 @@ import {
15
15
  } from "../modules/shellRuntime";
16
16
  import { buildLoginBanner } from "../SSHMimic/loginBanner";
17
17
  import { buildPrompt } from "../SSHMimic/prompt";
18
- import type { ShellEnv } from "../types/commands";
18
+ import type { CommandResult, ShellEnv } from "../types/commands";
19
19
  import type { ShellStream } from "../types/streams";
20
20
  import type VirtualFileSystem from "../VirtualFileSystem";
21
21
 
@@ -33,6 +33,11 @@ interface PendingSudo {
33
33
  loginShell: boolean;
34
34
  prompt: string;
35
35
  buffer: string;
36
+ mode?: "sudo" | "passwd" | "confirm";
37
+ onPassword?: (input: string, shell: VirtualShell) => Promise<{
38
+ result: CommandResult | null;
39
+ nextPrompt?: string;
40
+ }>;
36
41
  }
37
42
 
38
43
  export function startShell(
@@ -110,6 +115,11 @@ export function startShell(
110
115
  commandLine: string | null;
111
116
  loginShell: boolean;
112
117
  prompt: string;
118
+ mode?: "sudo" | "passwd" | "confirm";
119
+ onPassword?: (input: string, shell: VirtualShell) => Promise<{
120
+ result: CommandResult | null;
121
+ nextPrompt?: string;
122
+ }>;
113
123
  }): void {
114
124
  pendingSudo = {
115
125
  ...challenge,
@@ -135,7 +145,9 @@ export function startShell(
135
145
 
136
146
  if (!challenge.commandLine) {
137
147
  authUser = challenge.targetUser;
138
- cwd = `/home/${authUser}`;
148
+ if (challenge.loginShell) {
149
+ cwd = `/home/${authUser}`;
150
+ }
139
151
  shell.users.updateSession(sessionId, authUser, remoteAddress);
140
152
  stream.write("\r\n");
141
153
  renderLine();
@@ -443,11 +455,29 @@ export function startShell(
443
455
  }
444
456
 
445
457
  if (ch === "\r" || ch === "\n") {
446
- const password = pendingSudo.buffer;
458
+ const typed = pendingSudo.buffer;
447
459
  pendingSudo.buffer = "";
460
+
461
+ // ── Generic onPassword handler (passwd / confirm modes) ────
462
+ if (pendingSudo.onPassword) {
463
+ const { result, nextPrompt } = await pendingSudo.onPassword(typed, shell);
464
+ stream.write("\r\n");
465
+ if (result !== null) {
466
+ pendingSudo = null;
467
+ if (result.stdout) stream.write(result.stdout.replace(/\n/g, "\r\n"));
468
+ if (result.stderr) stream.write(result.stderr.replace(/\n/g, "\r\n"));
469
+ renderLine();
470
+ } else {
471
+ if (nextPrompt) pendingSudo.prompt = nextPrompt;
472
+ stream.write(pendingSudo.prompt);
473
+ }
474
+ return;
475
+ }
476
+
477
+ // ── Default sudo mode — verify current user's password ─────
448
478
  const valid = shell.users.verifyPassword(
449
479
  pendingSudo.username,
450
- password,
480
+ typed,
451
481
  );
452
482
  await finishSudoPrompt(valid);
453
483
  return;