typescript-virtual-container 1.2.8 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/.vscode/settings.json +0 -1
  2. package/README.md +462 -44
  3. package/biome.json +7 -0
  4. package/dist/SSHMimic/exec.d.ts.map +1 -1
  5. package/dist/SSHMimic/executor.d.ts.map +1 -1
  6. package/dist/SSHMimic/executor.js +35 -21
  7. package/dist/SSHMimic/index.d.ts.map +1 -1
  8. package/dist/SSHMimic/index.js +20 -6
  9. package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
  10. package/dist/VirtualFileSystem/binaryPack.js +29 -6
  11. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem/index.js +36 -13
  13. package/dist/VirtualPackageManager/index.d.ts +202 -0
  14. package/dist/VirtualPackageManager/index.d.ts.map +1 -0
  15. package/dist/VirtualPackageManager/index.js +825 -0
  16. package/dist/VirtualShell/index.d.ts +93 -12
  17. package/dist/VirtualShell/index.d.ts.map +1 -1
  18. package/dist/VirtualShell/index.js +95 -13
  19. package/dist/VirtualShell/shell.d.ts.map +1 -1
  20. package/dist/VirtualShell/shell.js +3 -1
  21. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  22. package/dist/VirtualUserManager/index.d.ts +52 -20
  23. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  24. package/dist/VirtualUserManager/index.js +54 -20
  25. package/dist/commands/adduser.d.ts +6 -0
  26. package/dist/commands/adduser.d.ts.map +1 -1
  27. package/dist/commands/adduser.js +6 -0
  28. package/dist/commands/alias.d.ts +9 -0
  29. package/dist/commands/alias.d.ts.map +1 -0
  30. package/dist/commands/alias.js +63 -0
  31. package/dist/commands/apt.d.ts +9 -0
  32. package/dist/commands/apt.d.ts.map +1 -0
  33. package/dist/commands/apt.js +205 -0
  34. package/dist/commands/awk.d.ts +11 -0
  35. package/dist/commands/awk.d.ts.map +1 -1
  36. package/dist/commands/awk.js +15 -2
  37. package/dist/commands/base64.d.ts +5 -0
  38. package/dist/commands/base64.d.ts.map +1 -1
  39. package/dist/commands/base64.js +9 -1
  40. package/dist/commands/cat.d.ts +5 -0
  41. package/dist/commands/cat.d.ts.map +1 -1
  42. package/dist/commands/cat.js +35 -8
  43. package/dist/commands/cd.d.ts +5 -0
  44. package/dist/commands/cd.d.ts.map +1 -1
  45. package/dist/commands/cd.js +5 -0
  46. package/dist/commands/chmod.d.ts +5 -0
  47. package/dist/commands/chmod.d.ts.map +1 -1
  48. package/dist/commands/chmod.js +57 -3
  49. package/dist/commands/command-helpers.d.ts +78 -4
  50. package/dist/commands/command-helpers.d.ts.map +1 -1
  51. package/dist/commands/command-helpers.js +78 -4
  52. package/dist/commands/cp.d.ts +5 -0
  53. package/dist/commands/cp.d.ts.map +1 -1
  54. package/dist/commands/cp.js +5 -0
  55. package/dist/commands/curl.d.ts +5 -0
  56. package/dist/commands/curl.d.ts.map +1 -1
  57. package/dist/commands/curl.js +106 -26
  58. package/dist/commands/cut.d.ts +5 -0
  59. package/dist/commands/cut.d.ts.map +1 -1
  60. package/dist/commands/cut.js +8 -1
  61. package/dist/commands/date.d.ts +5 -0
  62. package/dist/commands/date.d.ts.map +1 -1
  63. package/dist/commands/date.js +7 -1
  64. package/dist/commands/declare.d.ts +3 -0
  65. package/dist/commands/declare.d.ts.map +1 -0
  66. package/dist/commands/declare.js +39 -0
  67. package/dist/commands/diff.d.ts +5 -0
  68. package/dist/commands/diff.d.ts.map +1 -1
  69. package/dist/commands/diff.js +5 -0
  70. package/dist/commands/dpkg.d.ts +9 -0
  71. package/dist/commands/dpkg.d.ts.map +1 -0
  72. package/dist/commands/dpkg.js +161 -0
  73. package/dist/commands/du.d.ts.map +1 -1
  74. package/dist/commands/du.js +8 -2
  75. package/dist/commands/echo.d.ts +5 -0
  76. package/dist/commands/echo.d.ts.map +1 -1
  77. package/dist/commands/echo.js +33 -12
  78. package/dist/commands/env.d.ts +5 -0
  79. package/dist/commands/env.d.ts.map +1 -1
  80. package/dist/commands/env.js +11 -1
  81. package/dist/commands/exit.d.ts +5 -0
  82. package/dist/commands/exit.d.ts.map +1 -1
  83. package/dist/commands/exit.js +12 -2
  84. package/dist/commands/export.d.ts.map +1 -1
  85. package/dist/commands/export.js +3 -1
  86. package/dist/commands/find.d.ts +5 -0
  87. package/dist/commands/find.d.ts.map +1 -1
  88. package/dist/commands/find.js +5 -0
  89. package/dist/commands/free.d.ts +8 -0
  90. package/dist/commands/free.d.ts.map +1 -0
  91. package/dist/commands/free.js +43 -0
  92. package/dist/commands/grep.d.ts +5 -0
  93. package/dist/commands/grep.d.ts.map +1 -1
  94. package/dist/commands/grep.js +12 -2
  95. package/dist/commands/gzip.d.ts +5 -0
  96. package/dist/commands/gzip.d.ts.map +1 -1
  97. package/dist/commands/gzip.js +18 -2
  98. package/dist/commands/head.d.ts +5 -0
  99. package/dist/commands/head.d.ts.map +1 -1
  100. package/dist/commands/head.js +5 -0
  101. package/dist/commands/help.d.ts.map +1 -1
  102. package/dist/commands/help.js +98 -45
  103. package/dist/commands/helpers.d.ts +3 -0
  104. package/dist/commands/helpers.d.ts.map +1 -1
  105. package/dist/commands/helpers.js +3 -0
  106. package/dist/commands/history.d.ts +8 -0
  107. package/dist/commands/history.d.ts.map +1 -0
  108. package/dist/commands/history.js +26 -0
  109. package/dist/commands/hostname.d.ts +5 -0
  110. package/dist/commands/hostname.d.ts.map +1 -1
  111. package/dist/commands/hostname.js +5 -0
  112. package/dist/commands/id.d.ts.map +1 -1
  113. package/dist/commands/id.js +4 -1
  114. package/dist/commands/index.d.ts +2 -10
  115. package/dist/commands/index.d.ts.map +1 -1
  116. package/dist/commands/index.js +2 -231
  117. package/dist/commands/ls.d.ts.map +1 -1
  118. package/dist/commands/ls.js +6 -3
  119. package/dist/commands/lsb-release.d.ts +3 -0
  120. package/dist/commands/lsb-release.d.ts.map +1 -0
  121. package/dist/commands/lsb-release.js +56 -0
  122. package/dist/commands/man.d.ts +3 -0
  123. package/dist/commands/man.d.ts.map +1 -0
  124. package/dist/commands/man.js +155 -0
  125. package/dist/commands/nano.js +1 -1
  126. package/dist/commands/neofetch.d.ts.map +1 -1
  127. package/dist/commands/neofetch.js +6 -1
  128. package/dist/commands/node.d.ts +9 -0
  129. package/dist/commands/node.d.ts.map +1 -0
  130. package/dist/commands/node.js +316 -0
  131. package/dist/commands/npm.d.ts +19 -0
  132. package/dist/commands/npm.d.ts.map +1 -0
  133. package/dist/commands/npm.js +109 -0
  134. package/dist/commands/ping.d.ts.map +1 -1
  135. package/dist/commands/ping.js +7 -2
  136. package/dist/commands/printf.d.ts +3 -0
  137. package/dist/commands/printf.d.ts.map +1 -0
  138. package/dist/commands/printf.js +113 -0
  139. package/dist/commands/ps.d.ts.map +1 -1
  140. package/dist/commands/ps.js +30 -6
  141. package/dist/commands/python.d.ts +30 -0
  142. package/dist/commands/python.d.ts.map +1 -0
  143. package/dist/commands/python.js +2058 -0
  144. package/dist/commands/read.d.ts +3 -0
  145. package/dist/commands/read.d.ts.map +1 -0
  146. package/dist/commands/read.js +34 -0
  147. package/dist/commands/registry.d.ts +8 -0
  148. package/dist/commands/registry.d.ts.map +1 -0
  149. package/dist/commands/registry.js +229 -0
  150. package/dist/commands/runtime.d.ts +6 -0
  151. package/dist/commands/runtime.d.ts.map +1 -0
  152. package/dist/commands/runtime.js +280 -0
  153. package/dist/commands/sed.d.ts.map +1 -1
  154. package/dist/commands/sed.js +11 -3
  155. package/dist/commands/set.d.ts.map +1 -1
  156. package/dist/commands/set.js +9 -3
  157. package/dist/commands/sh.d.ts.map +1 -1
  158. package/dist/commands/sh.js +69 -30
  159. package/dist/commands/shift.d.ts +5 -0
  160. package/dist/commands/shift.d.ts.map +1 -0
  161. package/dist/commands/shift.js +52 -0
  162. package/dist/commands/sleep.d.ts.map +1 -1
  163. package/dist/commands/sort.d.ts.map +1 -1
  164. package/dist/commands/sort.js +4 -2
  165. package/dist/commands/source.d.ts +3 -0
  166. package/dist/commands/source.d.ts.map +1 -0
  167. package/dist/commands/source.js +34 -0
  168. package/dist/commands/sudo.js +1 -1
  169. package/dist/commands/tar.d.ts.map +1 -1
  170. package/dist/commands/tar.js +11 -3
  171. package/dist/commands/tee.d.ts.map +1 -1
  172. package/dist/commands/tee.js +8 -6
  173. package/dist/commands/test.d.ts +3 -0
  174. package/dist/commands/test.d.ts.map +1 -0
  175. package/dist/commands/test.js +114 -0
  176. package/dist/commands/tr.d.ts.map +1 -1
  177. package/dist/commands/tr.js +3 -1
  178. package/dist/commands/true.d.ts +4 -0
  179. package/dist/commands/true.d.ts.map +1 -0
  180. package/dist/commands/true.js +14 -0
  181. package/dist/commands/type.d.ts +3 -0
  182. package/dist/commands/type.d.ts.map +1 -0
  183. package/dist/commands/type.js +34 -0
  184. package/dist/commands/uname.d.ts.map +1 -1
  185. package/dist/commands/uname.js +4 -1
  186. package/dist/commands/uniq.d.ts.map +1 -1
  187. package/dist/commands/uptime.d.ts +3 -0
  188. package/dist/commands/uptime.d.ts.map +1 -0
  189. package/dist/commands/uptime.js +43 -0
  190. package/dist/commands/wget.d.ts.map +1 -1
  191. package/dist/commands/wget.js +92 -96
  192. package/dist/commands/which.d.ts +3 -0
  193. package/dist/commands/which.d.ts.map +1 -0
  194. package/dist/commands/which.js +32 -0
  195. package/dist/commands/xargs.d.ts.map +1 -1
  196. package/dist/commands/xargs.js +1 -1
  197. package/dist/index.d.ts +15 -11
  198. package/dist/index.d.ts.map +1 -1
  199. package/dist/index.js +9 -8
  200. package/dist/modules/linuxRootfs.d.ts +41 -0
  201. package/dist/modules/linuxRootfs.d.ts.map +1 -0
  202. package/dist/modules/linuxRootfs.js +440 -0
  203. package/dist/modules/neofetch.d.ts.map +1 -1
  204. package/dist/modules/neofetch.js +1 -0
  205. package/dist/standalone-wo-sftp.d.ts +2 -0
  206. package/dist/standalone-wo-sftp.d.ts.map +1 -0
  207. package/dist/standalone-wo-sftp.js +30 -0
  208. package/dist/utils/expand.d.ts +50 -0
  209. package/dist/utils/expand.d.ts.map +1 -0
  210. package/dist/utils/expand.js +183 -0
  211. package/dist/utils/vfsDiff.d.ts +90 -0
  212. package/dist/utils/vfsDiff.d.ts.map +1 -0
  213. package/dist/utils/vfsDiff.js +177 -0
  214. package/package.json +3 -1
  215. package/src/SSHMimic/exec.ts +10 -1
  216. package/src/SSHMimic/executor.ts +105 -21
  217. package/src/SSHMimic/index.ts +49 -15
  218. package/src/VirtualFileSystem/binaryPack.ts +35 -8
  219. package/src/VirtualFileSystem/index.ts +78 -28
  220. package/src/VirtualPackageManager/index.ts +979 -0
  221. package/src/VirtualShell/index.ts +133 -14
  222. package/src/VirtualShell/shell.ts +23 -3
  223. package/src/VirtualShell/shellParser.ts +134 -36
  224. package/src/VirtualUserManager/index.ts +62 -22
  225. package/src/commands/adduser.ts +6 -0
  226. package/src/commands/alias.ts +64 -0
  227. package/src/commands/apt.ts +228 -0
  228. package/src/commands/awk.ts +20 -6
  229. package/src/commands/base64.ts +13 -2
  230. package/src/commands/cat.ts +40 -8
  231. package/src/commands/cd.ts +5 -0
  232. package/src/commands/chmod.ts +53 -3
  233. package/src/commands/command-helpers.ts +78 -4
  234. package/src/commands/cp.ts +5 -0
  235. package/src/commands/curl.ts +118 -33
  236. package/src/commands/cut.ts +8 -1
  237. package/src/commands/date.ts +7 -1
  238. package/src/commands/declare.ts +44 -0
  239. package/src/commands/diff.ts +17 -3
  240. package/src/commands/dpkg.ts +180 -0
  241. package/src/commands/du.ts +17 -5
  242. package/src/commands/echo.ts +41 -12
  243. package/src/commands/env.ts +11 -1
  244. package/src/commands/exit.ts +12 -2
  245. package/src/commands/export.ts +3 -1
  246. package/src/commands/find.ts +5 -0
  247. package/src/commands/free.ts +47 -0
  248. package/src/commands/grep.ts +12 -2
  249. package/src/commands/gzip.ts +28 -4
  250. package/src/commands/head.ts +5 -0
  251. package/src/commands/help.ts +121 -47
  252. package/src/commands/helpers.ts +8 -0
  253. package/src/commands/history.ts +34 -0
  254. package/src/commands/hostname.ts +5 -0
  255. package/src/commands/id.ts +4 -1
  256. package/src/commands/index.ts +9 -255
  257. package/src/commands/ls.ts +6 -3
  258. package/src/commands/lsb-release.ts +58 -0
  259. package/src/commands/man.ts +166 -0
  260. package/src/commands/nano.ts +1 -1
  261. package/src/commands/neofetch.ts +6 -1
  262. package/src/commands/node.ts +341 -0
  263. package/src/commands/npm.ts +132 -0
  264. package/src/commands/ping.ts +10 -3
  265. package/src/commands/printf.ts +112 -0
  266. package/src/commands/ps.ts +40 -6
  267. package/src/commands/python.ts +2229 -0
  268. package/src/commands/read.ts +41 -0
  269. package/src/commands/registry.ts +244 -0
  270. package/src/commands/runtime.ts +353 -0
  271. package/src/commands/sed.ts +27 -9
  272. package/src/commands/set.ts +9 -3
  273. package/src/commands/sh.ts +170 -44
  274. package/src/commands/shift.ts +53 -0
  275. package/src/commands/sleep.ts +2 -1
  276. package/src/commands/sort.ts +10 -6
  277. package/src/commands/source.ts +47 -0
  278. package/src/commands/sudo.ts +1 -1
  279. package/src/commands/tar.ts +28 -7
  280. package/src/commands/tee.ts +7 -1
  281. package/src/commands/test.ts +135 -0
  282. package/src/commands/tr.ts +3 -1
  283. package/src/commands/true.ts +17 -0
  284. package/src/commands/type.ts +43 -0
  285. package/src/commands/uname.ts +5 -1
  286. package/src/commands/uniq.ts +8 -2
  287. package/src/commands/uptime.ts +49 -0
  288. package/src/commands/wget.ts +105 -119
  289. package/src/commands/which.ts +37 -0
  290. package/src/commands/xargs.ts +11 -2
  291. package/src/index.ts +27 -18
  292. package/src/modules/linuxRootfs.ts +642 -0
  293. package/src/modules/neofetch.ts +1 -0
  294. package/src/standalone-wo-sftp.ts +38 -0
  295. package/src/utils/expand.ts +238 -0
  296. package/src/utils/vfsDiff.ts +275 -0
  297. package/standalone-wo-sftp.js +507 -0
  298. package/standalone-wo-sftp.js.map +7 -0
  299. package/standalone.js +486 -109
  300. package/standalone.js.map +4 -4
  301. package/tests/bun-test-shim.ts +9 -1
  302. package/tests/command-helpers.test.ts +1 -5
  303. package/tests/new-features.test.ts +1036 -0
  304. package/tests/parser-executor.test.ts +27 -27
  305. package/tests/sftp.test.ts +122 -42
  306. package/tests/users.test.ts +23 -5
  307. package/CHANGELOG.md +0 -150
@@ -0,0 +1,135 @@
1
+ import type { ShellModule } from "../types/commands";
2
+
3
+ /**
4
+ * Evaluate a POSIX test expression.
5
+ * Supports: -f, -d, -e, -r, -w, -x, -s, -z, -n,
6
+ * string =, !=, numeric -eq -ne -lt -le -gt -ge,
7
+ * ! (negate), -a (and), -o (or).
8
+ */
9
+ function evalTest(
10
+ tokens: string[],
11
+ shell: import("../VirtualShell").VirtualShell,
12
+ cwd: string,
13
+ ): boolean {
14
+ // When called via [ command, ] is the last arg — strip it
15
+ // When called via test command, no brackets present
16
+ if (tokens[tokens.length - 1] === "]") {
17
+ tokens = tokens.slice(0, -1);
18
+ }
19
+ // Also strip leading [ if present (shouldn't normally happen but be safe)
20
+ if (tokens[0] === "[") {
21
+ tokens = tokens.slice(1);
22
+ }
23
+
24
+ if (tokens.length === 0) return false;
25
+
26
+ // Negation
27
+ if (tokens[0] === "!") return !evalTest(tokens.slice(1), shell, cwd);
28
+
29
+ // Boolean -a / -o (simple left-right, no precedence)
30
+ const andIdx = tokens.indexOf("-a");
31
+ if (andIdx !== -1) {
32
+ return (
33
+ evalTest(tokens.slice(0, andIdx), shell, cwd) &&
34
+ evalTest(tokens.slice(andIdx + 1), shell, cwd)
35
+ );
36
+ }
37
+ const orIdx = tokens.indexOf("-o");
38
+ if (orIdx !== -1) {
39
+ return (
40
+ evalTest(tokens.slice(0, orIdx), shell, cwd) ||
41
+ evalTest(tokens.slice(orIdx + 1), shell, cwd)
42
+ );
43
+ }
44
+
45
+ // Unary file tests
46
+ if (tokens.length === 2) {
47
+ const [flag, operand = ""] = tokens;
48
+ const resolvePath = (p: string) =>
49
+ p.startsWith("/") ? p : `${cwd}/${p}`.replace(/\/+/g, "/");
50
+ const path = resolvePath(operand);
51
+
52
+ switch (flag) {
53
+ case "-e":
54
+ return shell.vfs.exists(path);
55
+ case "-f":
56
+ return shell.vfs.exists(path) && shell.vfs.stat(path).type === "file";
57
+ case "-d":
58
+ return (
59
+ shell.vfs.exists(path) && shell.vfs.stat(path).type === "directory"
60
+ );
61
+ case "-r":
62
+ return shell.vfs.exists(path); // all readable in virtual env
63
+ case "-w":
64
+ return shell.vfs.exists(path);
65
+ case "-x":
66
+ return shell.vfs.exists(path) && !!(shell.vfs.stat(path).mode & 0o111);
67
+ case "-s":
68
+ return (
69
+ shell.vfs.exists(path) &&
70
+ shell.vfs.stat(path).type === "file" &&
71
+ (shell.vfs.stat(path) as import("../types/vfs").VfsFileNode).size > 0
72
+ );
73
+ case "-z":
74
+ return operand.length === 0;
75
+ case "-n":
76
+ return operand.length > 0;
77
+ case "-L":
78
+ return shell.vfs.isSymlink(path);
79
+ }
80
+ }
81
+
82
+ // Binary comparisons
83
+ if (tokens.length === 3) {
84
+ const [left = "", op, right = ""] = tokens;
85
+ const leftN = Number(left);
86
+ const rightN = Number(right);
87
+
88
+ switch (op) {
89
+ // String
90
+ case "=":
91
+ case "==":
92
+ return left === right;
93
+ case "!=":
94
+ return left !== right;
95
+ case "<":
96
+ return left < right;
97
+ case ">":
98
+ return left > right;
99
+ // Numeric
100
+ case "-eq":
101
+ return leftN === rightN;
102
+ case "-ne":
103
+ return leftN !== rightN;
104
+ case "-lt":
105
+ return leftN < rightN;
106
+ case "-le":
107
+ return leftN <= rightN;
108
+ case "-gt":
109
+ return leftN > rightN;
110
+ case "-ge":
111
+ return leftN >= rightN;
112
+ }
113
+ }
114
+
115
+ // Single string (truthy if non-empty)
116
+ if (tokens.length === 1) return (tokens[0] ?? "").length > 0;
117
+
118
+ return false;
119
+ }
120
+
121
+ export const testCommand: ShellModule = {
122
+ name: "test",
123
+ aliases: ["["],
124
+ description: "Evaluate conditional expression",
125
+ category: "shell",
126
+ params: ["<expression>"],
127
+ run: ({ args, shell, cwd }) => {
128
+ try {
129
+ const result = evalTest([...args], shell, cwd);
130
+ return { exitCode: result ? 0 : 1 };
131
+ } catch {
132
+ return { stderr: "test: malformed expression", exitCode: 2 };
133
+ }
134
+ },
135
+ };
@@ -16,7 +16,9 @@ export const trCommand: ShellModule = {
16
16
  for (const c of set1) input = input.split(c).join("");
17
17
  } else if (set2) {
18
18
  for (let i = 0; i < set1.length; i++) {
19
- input = input.split(set1[i]!).join(set2[i] ?? set2[set2.length - 1] ?? "");
19
+ input = input
20
+ .split(set1[i]!)
21
+ .join(set2[i] ?? set2[set2.length - 1] ?? "");
20
22
  }
21
23
  }
22
24
  return { stdout: input, exitCode: 0 };
@@ -0,0 +1,17 @@
1
+ import type { ShellModule } from "../types/commands";
2
+
3
+ export const trueCommand: ShellModule = {
4
+ name: "true",
5
+ description: "Return success exit code",
6
+ category: "shell",
7
+ params: [],
8
+ run: () => ({ exitCode: 0 }),
9
+ };
10
+
11
+ export const falseCommand: ShellModule = {
12
+ name: "false",
13
+ description: "Return failure exit code",
14
+ category: "shell",
15
+ params: [],
16
+ run: () => ({ exitCode: 1 }),
17
+ };
@@ -0,0 +1,43 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ import { resolveModule } from "./registry";
3
+
4
+ export const typeCommand: ShellModule = {
5
+ name: "type",
6
+ description: "Describe how a command would be interpreted",
7
+ category: "shell",
8
+ params: ["<command...>"],
9
+ run: ({ args, shell, env }) => {
10
+ if (args.length === 0)
11
+ return { stderr: "type: missing argument", exitCode: 1 };
12
+
13
+ const pathDirs = (env?.vars?.PATH ?? "/usr/local/bin:/usr/bin:/bin").split(
14
+ ":",
15
+ );
16
+ const lines: string[] = [];
17
+ let exitCode = 0;
18
+
19
+ for (const name of args) {
20
+ if (resolveModule(name)) {
21
+ lines.push(`${name} is a shell builtin`);
22
+ continue;
23
+ }
24
+
25
+ let found = false;
26
+ for (const dir of pathDirs) {
27
+ const full = `${dir}/${name}`;
28
+ if (shell.vfs.exists(full)) {
29
+ lines.push(`${name} is ${full}`);
30
+ found = true;
31
+ break;
32
+ }
33
+ }
34
+
35
+ if (!found) {
36
+ lines.push(`${name}: not found`);
37
+ exitCode = 1;
38
+ }
39
+ }
40
+
41
+ return { stdout: lines.join("\n"), exitCode };
42
+ },
43
+ };
@@ -12,7 +12,11 @@ export const unameCommand: ShellModule = {
12
12
  const release = shell.properties?.kernel ?? "5.15.0";
13
13
  const machine = shell.properties?.arch ?? "x86_64";
14
14
  const hostname = shell.hostname;
15
- if (all) return { stdout: `${sysname} ${hostname} ${release} #1 SMP ${machine} GNU/Linux`, exitCode: 0 };
15
+ if (all)
16
+ return {
17
+ stdout: `${sysname} ${hostname} ${release} #1 SMP ${machine} GNU/Linux`,
18
+ exitCode: 0,
19
+ };
16
20
  if (ifFlag(args, ["-r"])) return { stdout: release, exitCode: 0 };
17
21
  if (ifFlag(args, ["-m"])) return { stdout: machine, exitCode: 0 };
18
22
  return { stdout: sysname, exitCode: 0 };
@@ -18,8 +18,14 @@ export const uniqCommand: ShellModule = {
18
18
  while (j < lines.length && lines[j] === lines[i]) j++;
19
19
  const n = j - i;
20
20
  const line = lines[i]!;
21
- if (dupOnly && n === 1) { i = j; continue; }
22
- if (uniqOnly && n > 1) { i = j; continue; }
21
+ if (dupOnly && n === 1) {
22
+ i = j;
23
+ continue;
24
+ }
25
+ if (uniqOnly && n > 1) {
26
+ i = j;
27
+ continue;
28
+ }
23
29
  out.push(count ? `${String(n).padStart(4)} ${line}` : line);
24
30
  i = j;
25
31
  }
@@ -0,0 +1,49 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ import { ifFlag } from "./command-helpers";
3
+
4
+ export const uptimeCommand: ShellModule = {
5
+ name: "uptime",
6
+ description: "Tell how long the system has been running",
7
+ category: "system",
8
+ params: ["[-p] [-s]"],
9
+ run: ({ args, shell }) => {
10
+ const pretty = ifFlag(args, ["-p"]);
11
+ const since = ifFlag(args, ["-s"]);
12
+
13
+ const uptimeSec = Math.floor((Date.now() - shell.startTime) / 1000);
14
+ const days = Math.floor(uptimeSec / 86400);
15
+ const hours = Math.floor((uptimeSec % 86400) / 3600);
16
+ const mins = Math.floor((uptimeSec % 3600) / 60);
17
+
18
+ if (since) {
19
+ return {
20
+ stdout: new Date(shell.startTime)
21
+ .toISOString()
22
+ .slice(0, 19)
23
+ .replace("T", " "),
24
+ exitCode: 0,
25
+ };
26
+ }
27
+
28
+ if (pretty) {
29
+ const parts: string[] = [];
30
+ if (days > 0) parts.push(`${days} day${days > 1 ? "s" : ""}`);
31
+ if (hours > 0) parts.push(`${hours} hour${hours > 1 ? "s" : ""}`);
32
+ parts.push(`${mins} minute${mins !== 1 ? "s" : ""}`);
33
+ return { stdout: `up ${parts.join(", ")}`, exitCode: 0 };
34
+ }
35
+
36
+ const timeStr = new Date().toTimeString().slice(0, 8);
37
+ const uptimeStr =
38
+ days > 0
39
+ ? `${days} day${days > 1 ? "s" : ""}, ${String(hours).padStart(2)}:${String(mins).padStart(2, "0")}`
40
+ : `${String(hours).padStart(2)}:${String(mins).padStart(2, "0")}`;
41
+ const sessions = shell.users.listActiveSessions().length;
42
+ const load = (Math.random() * 0.5).toFixed(2);
43
+
44
+ return {
45
+ stdout: ` ${timeStr} up ${uptimeStr}, ${sessions} user${sessions !== 1 ? "s" : ""}, load average: ${load}, ${load}, ${load}`,
46
+ exitCode: 0,
47
+ };
48
+ },
49
+ };
@@ -1,146 +1,132 @@
1
- import { spawn } from "node:child_process";
2
- import { mkdtemp, readFile, rm } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
1
  import type { ShellModule } from "../types/commands";
6
2
  import { ifFlag, parseArgs } from "./command-helpers";
7
- import {
8
- assertPathAccess,
9
- normalizeTerminalOutput,
10
- resolvePath,
11
- runHostCommand,
12
- stripUrlFilename,
13
- } from "./helpers";
14
-
15
- function runHostWget(args: string[]): Promise<{
16
- stdout: string;
17
- stderr: string;
18
- exitCode: number;
19
- }> {
20
- return new Promise((resolve) => {
21
- let childProcess: ReturnType<typeof spawn>;
22
-
23
- try {
24
- childProcess = spawn("wget", args, {
25
- stdio: ["ignore", "pipe", "pipe"],
26
- });
27
- } catch (error) {
28
- resolve({
29
- stdout: "",
30
- stderr: `wget: ${error instanceof Error ? error.message : String(error)}`,
31
- exitCode: 1,
32
- });
33
- return;
34
- }
35
-
36
- let stdout = "";
37
- let stderr = "";
38
- const stdoutStream = childProcess.stdout;
39
- const stderrStream = childProcess.stderr;
40
-
41
- if (!stdoutStream || !stderrStream) {
42
- resolve({
43
- stdout: "",
44
- stderr: "wget: failed to capture process output",
45
- exitCode: 1,
46
- });
47
- return;
48
- }
49
-
50
- stdoutStream.setEncoding("utf8");
51
- stderrStream.setEncoding("utf8");
52
-
53
- stdoutStream.on("data", (chunk: string) => {
54
- stdout += chunk;
55
- });
56
-
57
- stderrStream.on("data", (chunk: string) => {
58
- stderr += chunk;
59
- });
60
-
61
- childProcess.on("error", (error) => {
62
- const errorCode =
63
- error instanceof Error && "code" in error
64
- ? String((error as NodeJS.ErrnoException).code ?? "")
65
- : "";
66
- resolve({
67
- stdout: "",
68
- stderr: `wget: ${error.message}`,
69
- exitCode: errorCode === "ENOENT" ? 127 : 1,
70
- });
71
- });
72
-
73
- childProcess.on("close", (code) => {
74
- resolve({
75
- stdout,
76
- stderr,
77
- exitCode: code ?? 1,
78
- });
79
- });
80
- });
81
- }
3
+ import { assertPathAccess, resolvePath, stripUrlFilename } from "./helpers";
82
4
 
83
5
  export const wgetCommand: ShellModule = {
84
6
  name: "wget",
85
- description: "File downloader",
7
+ description: "File downloader (pure fetch)",
86
8
  category: "network",
87
- params: ["[url]"],
9
+ params: ["[options] <url>"],
88
10
  run: async ({ authUser, cwd, args, shell }) => {
89
11
  const { flagsWithValues, positionals } = parseArgs(args, {
90
- flagsWithValue: ["-o", "-O", "--output", "--output-document"],
12
+ flagsWithValue: [
13
+ "-O",
14
+ "--output-document",
15
+ "-o",
16
+ "--output-file",
17
+ "-P",
18
+ "--directory-prefix",
19
+ "--tries",
20
+ "--timeout",
21
+ ],
91
22
  });
92
- const outputPath =
93
- flagsWithValues.get("-o") ||
94
- flagsWithValues.get("-O") ||
95
- flagsWithValues.get("--output") ||
96
- flagsWithValues.get("--output-document") ||
97
- null;
98
- const url = positionals[0];
99
23
 
100
- if (!url) {
101
- return { stderr: "wget: missing URL", exitCode: 1 };
24
+ if (ifFlag(args, ["-h", "--help"])) {
25
+ return {
26
+ stdout: [
27
+ "Usage: wget [option]... [URL]...",
28
+ " -O, --output-document=FILE Write to FILE ('-' for stdout)",
29
+ " -P, --directory-prefix=DIR Save files in DIR",
30
+ " -q, --quiet Quiet mode",
31
+ " -v, --verbose Verbose output (default)",
32
+ " -c, --continue Continue partial download",
33
+ " --tries=N Retry N times",
34
+ " --timeout=N Timeout in seconds",
35
+ ].join("\n"),
36
+ exitCode: 0,
37
+ };
102
38
  }
103
39
 
104
- const isHelpLike = ifFlag(args, ["-h", "--help", "-V", "--version"]);
105
-
106
- if (isHelpLike) {
107
- const result = await runHostWget(args);
40
+ if (ifFlag(args, ["-V", "--version"])) {
108
41
  return {
109
- stdout: normalizeTerminalOutput(result.stdout),
110
- stderr: result.stderr
111
- ? normalizeTerminalOutput(result.stderr)
112
- : undefined,
113
- exitCode: result.exitCode,
42
+ stdout: "GNU Wget 1.21.3 (virtual) built on Fortune GNU/Linux.",
43
+ exitCode: 0,
114
44
  };
115
45
  }
116
46
 
117
- const tempDir = await mkdtemp(join(tmpdir(), "virtual-env-js-wget-"));
118
- const tempFile = join(tempDir, "download");
47
+ const url = positionals[0];
48
+ if (!url)
49
+ return {
50
+ stderr: "wget: missing URL\nUsage: wget [OPTION]... [URL]...",
51
+ exitCode: 1,
52
+ };
53
+
54
+ const outputArg =
55
+ flagsWithValues.get("-O") ??
56
+ flagsWithValues.get("--output-document") ??
57
+ null;
58
+ const dirPrefix =
59
+ flagsWithValues.get("-P") ??
60
+ flagsWithValues.get("--directory-prefix") ??
61
+ null;
62
+ const quiet = ifFlag(args, ["-q", "--quiet"]);
63
+
64
+ // Derive target filename
65
+ const filename =
66
+ outputArg === "-" ? null : (outputArg ?? stripUrlFilename(url));
67
+ const targetPath = filename
68
+ ? resolvePath(cwd, dirPrefix ? `${dirPrefix}/${filename}` : filename)
69
+ : null;
70
+
71
+ if (targetPath) assertPathAccess(authUser, targetPath, "wget");
72
+
73
+ const stderrLines: string[] = [];
74
+ if (!quiet) {
75
+ stderrLines.push(`--${new Date().toISOString()}-- ${url}`);
76
+ stderrLines.push(`Resolving ${new URL(url).host}...`);
77
+ stderrLines.push(`Connecting to ${new URL(url).host}...`);
78
+ }
119
79
 
80
+ let response: Response;
120
81
  try {
121
- const hostArgs = [...positionals, "-O", tempFile];
122
- const result = await runHostCommand("wget", hostArgs);
82
+ response = await fetch(url, {
83
+ headers: { "User-Agent": "Wget/1.21.3 (Fortune GNU/Linux)" },
84
+ });
85
+ } catch (err) {
86
+ const msg = err instanceof Error ? err.message : String(err);
87
+ stderrLines.push(`wget: unable to resolve host: ${msg}`);
88
+ return { stderr: stderrLines.join("\n"), exitCode: 4 };
89
+ }
90
+
91
+ if (!response.ok) {
92
+ stderrLines.push(`ERROR ${response.status}: ${response.statusText}`);
93
+ return { stderr: stderrLines.join("\n"), exitCode: 8 };
94
+ }
123
95
 
124
- if (result.exitCode !== 0) {
125
- return {
126
- stderr: normalizeTerminalOutput(
127
- result.stderr || `wget: exited with code ${result.exitCode}`,
128
- ),
129
- exitCode: result.exitCode,
130
- };
131
- }
96
+ let body: string;
97
+ try {
98
+ body = await response.text();
99
+ } catch {
100
+ return { stderr: "wget: failed to read response", exitCode: 1 };
101
+ }
132
102
 
133
- const content = await readFile(tempFile, "utf8");
134
- const target = resolvePath(cwd, outputPath || stripUrlFilename(url));
135
- assertPathAccess(authUser, target, "wget");
136
- shell.writeFileAsUser(authUser, target, content);
103
+ if (!quiet) {
104
+ const ct =
105
+ response.headers.get("content-type") ?? "application/octet-stream";
106
+ stderrLines.push(
107
+ `HTTP request sent, awaiting response... ${response.status} ${response.statusText}`,
108
+ );
109
+ stderrLines.push(`Length: ${body.length} [${ct}]`);
110
+ }
137
111
 
112
+ // Output to stdout (pipe) or file
113
+ if (outputArg === "-") {
138
114
  return {
139
- stdout: `saved ${target}`,
115
+ stdout: body,
116
+ stderr: stderrLines.join("\n") || undefined,
140
117
  exitCode: 0,
141
118
  };
142
- } finally {
143
- await rm(tempDir, { recursive: true, force: true });
144
119
  }
120
+
121
+ if (targetPath) {
122
+ shell.writeFileAsUser(authUser, targetPath, body);
123
+ if (!quiet)
124
+ stderrLines.push(
125
+ `Saving to: '${targetPath}'\n${targetPath} 100%[==================>] ${body.length} B`,
126
+ );
127
+ return { stderr: stderrLines.join("\n") || undefined, exitCode: 0 };
128
+ }
129
+
130
+ return { stdout: body, exitCode: 0 };
145
131
  },
146
132
  };
@@ -0,0 +1,37 @@
1
+ import type { ShellModule } from "../types/commands";
2
+
3
+ export const whichCommand: ShellModule = {
4
+ name: "which",
5
+ description: "Locate a command in PATH",
6
+ category: "shell",
7
+ params: ["<command...>"],
8
+ run: ({ args, shell, env }) => {
9
+ if (args.length === 0)
10
+ return { stderr: "which: missing argument", exitCode: 1 };
11
+
12
+ const pathDirs = (env?.vars?.PATH ?? "/usr/local/bin:/usr/bin:/bin").split(
13
+ ":",
14
+ );
15
+ const lines: string[] = [];
16
+ let anyMissing = false;
17
+
18
+ for (const name of args) {
19
+ let found = false;
20
+ for (const dir of pathDirs) {
21
+ const full = `${dir}/${name}`;
22
+ if (shell.vfs.exists(full)) {
23
+ const st = shell.vfs.stat(full);
24
+ if (st.type === "file") {
25
+ lines.push(full);
26
+ found = true;
27
+ break;
28
+ }
29
+ }
30
+ }
31
+ if (!found) anyMissing = true;
32
+ }
33
+
34
+ if (lines.length === 0) return { exitCode: 1 };
35
+ return { stdout: lines.join("\n"), exitCode: anyMissing ? 1 : 0 };
36
+ },
37
+ };
@@ -1,5 +1,5 @@
1
1
  import type { ShellModule } from "../types/commands";
2
- import { runCommand } from "./index";
2
+ import { runCommand } from "./runtime";
3
3
 
4
4
  export const xargsCommand: ShellModule = {
5
5
  name: "xargs",
@@ -12,6 +12,15 @@ export const xargsCommand: ShellModule = {
12
12
  const items = (stdin ?? "").trim().split(/\s+/).filter(Boolean);
13
13
  if (items.length === 0) return { exitCode: 0 };
14
14
  const fullCmd = [baseCmd, ...extraArgs, ...items].join(" ");
15
- return runCommand(fullCmd, authUser, hostname, mode, cwd, shell, undefined, env);
15
+ return runCommand(
16
+ fullCmd,
17
+ authUser,
18
+ hostname,
19
+ mode,
20
+ cwd,
21
+ shell,
22
+ undefined,
23
+ env,
24
+ );
16
25
  },
17
26
  };
package/src/index.ts CHANGED
@@ -1,13 +1,14 @@
1
- import { HoneyPot } from "./Honeypot/index";
2
- import { SshClient } from "./SSHClient/index";
3
- import { SftpMimic, SshMimic } from "./SSHMimic/index";
4
- import VirtualFileSystem from "./VirtualFileSystem/index";
5
- import { VirtualShell } from "./VirtualShell/index";
6
- import { VirtualUserManager } from "./VirtualUserManager/index";
1
+ export { HoneyPot } from "./Honeypot/index";
2
+ export { SshClient } from "./SSHClient/index";
3
+ export { SftpMimic as VirtualSftpServer, SshMimic as VirtualSshServer } from "./SSHMimic/index";
4
+ export { default as VirtualFileSystem } from "./VirtualFileSystem/index";
5
+ export { VirtualPackageManager } from "./VirtualPackageManager/index";
6
+ export { VirtualShell } from "./VirtualShell/index";
7
+ export { VirtualUserManager } from "./VirtualUserManager/index";
7
8
 
8
9
  export type {
9
10
  AuditLogEntry,
10
- HoneyPotStats,
11
+ HoneyPotStats
11
12
  } from "./Honeypot/index";
12
13
  export type {
13
14
  CommandContext,
@@ -15,8 +16,9 @@ export type {
15
16
  CommandOutcome,
16
17
  CommandResult,
17
18
  NanoEditorSession,
19
+ ShellEnv,
18
20
  ShellModule,
19
- SudoChallenge,
21
+ SudoChallenge
20
22
  } from "./types/commands";
21
23
  export type { ExecStream, ShellStream } from "./types/streams";
22
24
  export type {
@@ -31,22 +33,29 @@ export type {
31
33
  VfsSnapshotDirectoryNode,
32
34
  VfsSnapshotFileNode,
33
35
  VfsSnapshotNode,
34
- WriteFileOptions,
36
+ WriteFileOptions
35
37
  } from "./types/vfs";
36
38
  export type { VfsOptions, VfsPersistenceMode } from "./VirtualFileSystem/index";
39
+ export type { ShellProperties } from "./VirtualShell/index";
40
+
41
+ export type {
42
+ InstalledPackage, PackageDefinition,
43
+ PackageFile
44
+ } from "./VirtualPackageManager/index";
37
45
 
38
46
  export {
39
- HoneyPot,
40
- SshClient,
41
- VirtualFileSystem,
42
- SftpMimic as VirtualSftpServer,
43
- VirtualShell,
44
- SshMimic as VirtualSshServer,
45
- VirtualUserManager,
46
- };
47
+ assertDiff, diffSnapshots,
48
+ formatDiff
49
+ } from "./utils/vfsDiff";
50
+ export type {
51
+ VfsDiff,
52
+ VfsDiffEntry,
53
+ VfsDiffModified
54
+ } from "./utils/vfsDiff";
47
55
 
48
56
  export {
49
57
  getArg,
50
58
  getFlag,
51
- ifFlag,
59
+ ifFlag
52
60
  } from "./commands/command-helpers";
61
+