typescript-virtual-container 1.5.3 → 1.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +44 -532
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/SSHMimic/executor.js +23 -5
  4. package/dist/VirtualPackageManager/index.js +10 -0
  5. package/dist/VirtualShell/shell.js +158 -11
  6. package/dist/commands/basename.d.ts +13 -0
  7. package/dist/commands/basename.js +45 -0
  8. package/dist/commands/bc.d.ts +2 -0
  9. package/dist/commands/bc.js +28 -0
  10. package/dist/commands/file.d.ts +8 -0
  11. package/dist/commands/file.js +57 -0
  12. package/dist/commands/fun.d.ts +32 -0
  13. package/dist/commands/fun.js +172 -0
  14. package/dist/commands/ip.d.ts +7 -0
  15. package/dist/commands/ip.js +52 -0
  16. package/dist/commands/jobs.d.ts +4 -0
  17. package/dist/commands/jobs.js +27 -0
  18. package/dist/commands/last.d.ts +13 -0
  19. package/dist/commands/last.js +68 -0
  20. package/dist/commands/manuals-bundle.js +598 -6
  21. package/dist/commands/registry.js +30 -2
  22. package/dist/commands/runtime.js +24 -3
  23. package/dist/commands/set.js +20 -0
  24. package/dist/commands/sh.js +74 -1
  25. package/dist/commands/tput.d.ts +13 -0
  26. package/dist/commands/tput.js +76 -0
  27. package/dist/commands/w.d.ts +7 -0
  28. package/dist/commands/w.js +38 -0
  29. package/dist/utils/expand.d.ts +12 -0
  30. package/dist/utils/expand.js +87 -1
  31. package/package.json +9 -3
  32. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  33. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  34. package/.github/dependabot.yml +0 -27
  35. package/.github/pull_request_template.md +0 -21
  36. package/.github/workflows/create-pull-request.yml +0 -85
  37. package/.github/workflows/publish.yml +0 -25
  38. package/.github/workflows/test-battery.yml +0 -102
  39. package/.vscode/settings.json +0 -20
  40. package/CODE_OF_CONDUCT.md +0 -39
  41. package/CONTRIBUTING.md +0 -59
  42. package/HONEYPOT.md +0 -358
  43. package/SECURITY.md +0 -33
  44. package/benchmark-results.txt +0 -40
  45. package/benchmark-virtualshell.ts +0 -88
  46. package/biome.json +0 -37
  47. package/build.js +0 -22
  48. package/builds/fortune-nyx-v1.5.3-directbash-k6.1.0.mjs +0 -1764
  49. package/builds/fortune-nyx-v1.5.3-ssh-nosftp.js +0 -1764
  50. package/builds/fortune-nyx-v1.5.3-ssh.cjs +0 -1765
  51. package/builds/fortune-nyx-v1.5.3-web.min.js +0 -17036
  52. package/bun.lock +0 -244
  53. package/docs/.nojekyll +0 -1
  54. package/docs/app.js +0 -1751
  55. package/docs/assets/hierarchy.js +0 -1
  56. package/docs/assets/highlight.css +0 -162
  57. package/docs/assets/icons.js +0 -18
  58. package/docs/assets/icons.svg +0 -1
  59. package/docs/assets/main.js +0 -60
  60. package/docs/assets/navigation.js +0 -1
  61. package/docs/assets/search.js +0 -1
  62. package/docs/assets/style.css +0 -1633
  63. package/docs/classes/HoneyPot.html +0 -31
  64. package/docs/classes/IdleManager.html +0 -162
  65. package/docs/classes/SshClient.html +0 -66
  66. package/docs/classes/VirtualFileSystem.html +0 -279
  67. package/docs/classes/VirtualPackageManager.html +0 -63
  68. package/docs/classes/VirtualSftpServer.html +0 -169
  69. package/docs/classes/VirtualShell.html +0 -285
  70. package/docs/classes/VirtualSshServer.html +0 -182
  71. package/docs/classes/VirtualUserManager.html +0 -276
  72. package/docs/demo.html +0 -82
  73. package/docs/functions/assertDiff.html +0 -6
  74. package/docs/functions/diffSnapshots.html +0 -7
  75. package/docs/functions/formatDiff.html +0 -6
  76. package/docs/functions/getArg.html +0 -13
  77. package/docs/functions/getFlag.html +0 -15
  78. package/docs/functions/ifFlag.html +0 -11
  79. package/docs/hierarchy.html +0 -1
  80. package/docs/index.html +0 -1869
  81. package/docs/interfaces/AuditLogEntry.html +0 -6
  82. package/docs/interfaces/CommandContext.html +0 -22
  83. package/docs/interfaces/CommandResult.html +0 -26
  84. package/docs/interfaces/ExecStream.html +0 -11
  85. package/docs/interfaces/HoneyPotStats.html +0 -16
  86. package/docs/interfaces/IdleManagerOptions.html +0 -7
  87. package/docs/interfaces/InstalledPackage.html +0 -20
  88. package/docs/interfaces/NanoEditorSession.html +0 -8
  89. package/docs/interfaces/PackageDefinition.html +0 -30
  90. package/docs/interfaces/PackageFile.html +0 -8
  91. package/docs/interfaces/PasswordChallenge.html +0 -16
  92. package/docs/interfaces/RemoveOptions.html +0 -4
  93. package/docs/interfaces/ShellEnv.html +0 -6
  94. package/docs/interfaces/ShellModule.html +0 -14
  95. package/docs/interfaces/ShellProperties.html +0 -14
  96. package/docs/interfaces/ShellStream.html +0 -11
  97. package/docs/interfaces/SudoChallenge.html +0 -24
  98. package/docs/interfaces/VfsBaseNode.html +0 -12
  99. package/docs/interfaces/VfsDiff.html +0 -10
  100. package/docs/interfaces/VfsDiffEntry.html +0 -6
  101. package/docs/interfaces/VfsDiffModified.html +0 -10
  102. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  103. package/docs/interfaces/VfsFileNode.html +0 -17
  104. package/docs/interfaces/VfsOptions.html +0 -26
  105. package/docs/interfaces/VfsSnapshot.html +0 -3
  106. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  107. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  108. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  109. package/docs/interfaces/VirtualActiveSession.html +0 -12
  110. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  111. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  112. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  113. package/docs/interfaces/WriteFileOptions.html +0 -6
  114. package/docs/media/LICENSE +0 -21
  115. package/docs/modules.html +0 -1
  116. package/docs/types/ArgParseOptions.html +0 -4
  117. package/docs/types/CommandMode.html +0 -2
  118. package/docs/types/CommandOutcome.html +0 -2
  119. package/docs/types/IdleState.html +0 -1
  120. package/docs/types/VfsNodeStats.html +0 -2
  121. package/docs/types/VfsNodeType.html +0 -2
  122. package/docs/types/VfsPersistenceMode.html +0 -5
  123. package/docs/types/VfsSnapshotNode.html +0 -2
  124. package/examples/README.md +0 -288
  125. package/examples/app.js +0 -1751
  126. package/examples/app.ts +0 -299
  127. package/examples/build.js +0 -27
  128. package/examples/demo.html +0 -33
  129. package/examples/honeypot-audit.ts +0 -180
  130. package/examples/honeypot-export.ts +0 -253
  131. package/examples/honeypot-quickstart.ts +0 -110
  132. package/examples/index.html +0 -82
  133. package/examples/server.js +0 -55
  134. package/polyfills/buffer.js +0 -117
  135. package/polyfills/node_child_process/index.js +0 -2
  136. package/polyfills/node_crypto/index.js +0 -167
  137. package/polyfills/node_events/index.js +0 -9
  138. package/polyfills/node_fs/index.js +0 -202
  139. package/polyfills/node_fs/promises.js +0 -4
  140. package/polyfills/node_os/index.js +0 -9
  141. package/polyfills/node_path/index.js +0 -28
  142. package/polyfills/node_vm/index.js +0 -7
  143. package/polyfills/node_zlib/index.js +0 -3
  144. package/polyfills/process.js +0 -14
  145. package/polyfills/ssh2/index.js +0 -75
  146. package/scripts/build-all.mjs +0 -226
  147. package/scripts/build-names.mjs +0 -43
  148. package/scripts/generate-manuals-bundle.mjs +0 -49
  149. package/scripts/postinstall.js +0 -42
  150. package/scripts/publish-package.sh +0 -70
  151. package/src/Honeypot/index.ts +0 -457
  152. package/src/SSHClient/index.ts +0 -270
  153. package/src/SSHMimic/exec.ts +0 -49
  154. package/src/SSHMimic/executor.ts +0 -251
  155. package/src/SSHMimic/hostKey.ts +0 -21
  156. package/src/SSHMimic/index.ts +0 -337
  157. package/src/SSHMimic/loginBanner.ts +0 -36
  158. package/src/SSHMimic/loginFormat.ts +0 -10
  159. package/src/SSHMimic/prompt.ts +0 -14
  160. package/src/SSHMimic/sftp.ts +0 -883
  161. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  162. package/src/VirtualFileSystem/index.ts +0 -1193
  163. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  164. package/src/VirtualFileSystem/journal.ts +0 -171
  165. package/src/VirtualFileSystem/path.ts +0 -74
  166. package/src/VirtualPackageManager/index.ts +0 -996
  167. package/src/VirtualShell/idleManager.ts +0 -137
  168. package/src/VirtualShell/index.ts +0 -475
  169. package/src/VirtualShell/shell.ts +0 -700
  170. package/src/VirtualShell/shellParser.ts +0 -285
  171. package/src/VirtualUserManager/index.ts +0 -758
  172. package/src/bun.d.ts +0 -1
  173. package/src/commands/adduser.ts +0 -103
  174. package/src/commands/alias.ts +0 -69
  175. package/src/commands/apt.ts +0 -233
  176. package/src/commands/awk.ts +0 -168
  177. package/src/commands/base64.ts +0 -29
  178. package/src/commands/cat.ts +0 -52
  179. package/src/commands/cd.ts +0 -25
  180. package/src/commands/chmod.ts +0 -85
  181. package/src/commands/clear.ts +0 -15
  182. package/src/commands/command-helpers.ts +0 -286
  183. package/src/commands/cp.ts +0 -83
  184. package/src/commands/curl.ts +0 -147
  185. package/src/commands/cut.ts +0 -36
  186. package/src/commands/date.ts +0 -30
  187. package/src/commands/declare.ts +0 -49
  188. package/src/commands/deluser.ts +0 -98
  189. package/src/commands/df.ts +0 -23
  190. package/src/commands/diff.ts +0 -43
  191. package/src/commands/dpkg.ts +0 -180
  192. package/src/commands/du.ts +0 -56
  193. package/src/commands/echo.ts +0 -58
  194. package/src/commands/env.ts +0 -23
  195. package/src/commands/exit.ts +0 -18
  196. package/src/commands/export.ts +0 -34
  197. package/src/commands/find.ts +0 -68
  198. package/src/commands/free.ts +0 -47
  199. package/src/commands/grep.ts +0 -116
  200. package/src/commands/groups.ts +0 -19
  201. package/src/commands/gzip.ts +0 -88
  202. package/src/commands/head.ts +0 -52
  203. package/src/commands/help.ts +0 -152
  204. package/src/commands/helpers.ts +0 -234
  205. package/src/commands/history.ts +0 -34
  206. package/src/commands/hostname.ts +0 -14
  207. package/src/commands/htop.ts +0 -20
  208. package/src/commands/id.ts +0 -19
  209. package/src/commands/index.ts +0 -9
  210. package/src/commands/kill.ts +0 -19
  211. package/src/commands/ln.ts +0 -71
  212. package/src/commands/ls.ts +0 -243
  213. package/src/commands/lsb-release.ts +0 -63
  214. package/src/commands/man.ts +0 -31
  215. package/src/commands/manuals/adduser.txt +0 -11
  216. package/src/commands/manuals/apt-cache.txt +0 -12
  217. package/src/commands/manuals/apt.txt +0 -20
  218. package/src/commands/manuals/awk.txt +0 -13
  219. package/src/commands/manuals/cat.txt +0 -14
  220. package/src/commands/manuals/cd.txt +0 -16
  221. package/src/commands/manuals/chmod.txt +0 -16
  222. package/src/commands/manuals/clear.txt +0 -10
  223. package/src/commands/manuals/cp.txt +0 -10
  224. package/src/commands/manuals/curl.txt +0 -20
  225. package/src/commands/manuals/date.txt +0 -14
  226. package/src/commands/manuals/declare.txt +0 -12
  227. package/src/commands/manuals/deluser.txt +0 -10
  228. package/src/commands/manuals/df.txt +0 -10
  229. package/src/commands/manuals/dpkg-query.txt +0 -11
  230. package/src/commands/manuals/dpkg.txt +0 -14
  231. package/src/commands/manuals/du.txt +0 -11
  232. package/src/commands/manuals/echo.txt +0 -11
  233. package/src/commands/manuals/false.txt +0 -10
  234. package/src/commands/manuals/find.txt +0 -11
  235. package/src/commands/manuals/free.txt +0 -12
  236. package/src/commands/manuals/grep.txt +0 -13
  237. package/src/commands/manuals/groups.txt +0 -10
  238. package/src/commands/manuals/gzip.txt +0 -11
  239. package/src/commands/manuals/head.txt +0 -10
  240. package/src/commands/manuals/help.txt +0 -11
  241. package/src/commands/manuals/history.txt +0 -11
  242. package/src/commands/manuals/hostname.txt +0 -10
  243. package/src/commands/manuals/id.txt +0 -10
  244. package/src/commands/manuals/kill.txt +0 -13
  245. package/src/commands/manuals/ls.txt +0 -20
  246. package/src/commands/manuals/lsb_release.txt +0 -14
  247. package/src/commands/manuals/mkdir.txt +0 -10
  248. package/src/commands/manuals/mv.txt +0 -10
  249. package/src/commands/manuals/nano.txt +0 -11
  250. package/src/commands/manuals/neofetch.txt +0 -10
  251. package/src/commands/manuals/node.txt +0 -13
  252. package/src/commands/manuals/npm.txt +0 -13
  253. package/src/commands/manuals/npx.txt +0 -13
  254. package/src/commands/manuals/passwd.txt +0 -11
  255. package/src/commands/manuals/ping.txt +0 -10
  256. package/src/commands/manuals/printf.txt +0 -11
  257. package/src/commands/manuals/ps.txt +0 -10
  258. package/src/commands/manuals/pwd.txt +0 -10
  259. package/src/commands/manuals/python3.txt +0 -13
  260. package/src/commands/manuals/readlink.txt +0 -10
  261. package/src/commands/manuals/return.txt +0 -10
  262. package/src/commands/manuals/rm.txt +0 -10
  263. package/src/commands/manuals/sed.txt +0 -11
  264. package/src/commands/manuals/set.txt +0 -11
  265. package/src/commands/manuals/shift.txt +0 -10
  266. package/src/commands/manuals/sleep.txt +0 -10
  267. package/src/commands/manuals/sort.txt +0 -12
  268. package/src/commands/manuals/source.txt +0 -11
  269. package/src/commands/manuals/ssh.txt +0 -11
  270. package/src/commands/manuals/stat.txt +0 -10
  271. package/src/commands/manuals/su.txt +0 -13
  272. package/src/commands/manuals/sudo.txt +0 -11
  273. package/src/commands/manuals/tail.txt +0 -10
  274. package/src/commands/manuals/tar.txt +0 -19
  275. package/src/commands/manuals/tee.txt +0 -10
  276. package/src/commands/manuals/test.txt +0 -11
  277. package/src/commands/manuals/touch.txt +0 -11
  278. package/src/commands/manuals/tr.txt +0 -10
  279. package/src/commands/manuals/trap.txt +0 -10
  280. package/src/commands/manuals/true.txt +0 -10
  281. package/src/commands/manuals/type.txt +0 -10
  282. package/src/commands/manuals/uname.txt +0 -12
  283. package/src/commands/manuals/uniq.txt +0 -12
  284. package/src/commands/manuals/unset.txt +0 -10
  285. package/src/commands/manuals/uptime.txt +0 -11
  286. package/src/commands/manuals/wc.txt +0 -12
  287. package/src/commands/manuals/wget.txt +0 -12
  288. package/src/commands/manuals/which.txt +0 -10
  289. package/src/commands/manuals/whoami.txt +0 -10
  290. package/src/commands/manuals/xargs.txt +0 -10
  291. package/src/commands/manuals-bundle.ts +0 -898
  292. package/src/commands/mkdir.ts +0 -31
  293. package/src/commands/mv.ts +0 -50
  294. package/src/commands/nano.ts +0 -38
  295. package/src/commands/neofetch.ts +0 -53
  296. package/src/commands/node.ts +0 -341
  297. package/src/commands/npm.ts +0 -132
  298. package/src/commands/passwd.ts +0 -50
  299. package/src/commands/ping.ts +0 -32
  300. package/src/commands/printf.ts +0 -129
  301. package/src/commands/ps.ts +0 -58
  302. package/src/commands/pwd.ts +0 -9
  303. package/src/commands/python.ts +0 -2229
  304. package/src/commands/read.ts +0 -46
  305. package/src/commands/registry.ts +0 -249
  306. package/src/commands/rm.ts +0 -42
  307. package/src/commands/runtime.ts +0 -421
  308. package/src/commands/sed.ts +0 -68
  309. package/src/commands/seq.ts +0 -43
  310. package/src/commands/set.ts +0 -29
  311. package/src/commands/sh.ts +0 -467
  312. package/src/commands/shift.ts +0 -63
  313. package/src/commands/sleep.ts +0 -20
  314. package/src/commands/sort.ts +0 -46
  315. package/src/commands/source.ts +0 -52
  316. package/src/commands/stat.ts +0 -61
  317. package/src/commands/su.ts +0 -72
  318. package/src/commands/sudo.ts +0 -76
  319. package/src/commands/tail.ts +0 -53
  320. package/src/commands/tar.ts +0 -102
  321. package/src/commands/tee.ts +0 -36
  322. package/src/commands/test.ts +0 -137
  323. package/src/commands/touch.ts +0 -28
  324. package/src/commands/tr.ts +0 -70
  325. package/src/commands/tree.ts +0 -20
  326. package/src/commands/true.ts +0 -27
  327. package/src/commands/type.ts +0 -48
  328. package/src/commands/uname.ts +0 -29
  329. package/src/commands/uniq.ts +0 -39
  330. package/src/commands/unset.ts +0 -17
  331. package/src/commands/uptime.ts +0 -54
  332. package/src/commands/wc.ts +0 -55
  333. package/src/commands/wget.ts +0 -148
  334. package/src/commands/which.ts +0 -37
  335. package/src/commands/who.ts +0 -25
  336. package/src/commands/whoami.ts +0 -14
  337. package/src/commands/xargs.ts +0 -31
  338. package/src/index.ts +0 -67
  339. package/src/modules/linuxRootfs.ts +0 -1961
  340. package/src/modules/neofetch.ts +0 -358
  341. package/src/modules/shellInteractive.ts +0 -57
  342. package/src/modules/shellRuntime.ts +0 -76
  343. package/src/self-standalone.ts +0 -542
  344. package/src/standalone-wo-sftp.ts +0 -38
  345. package/src/standalone.ts +0 -72
  346. package/src/types/commands.ts +0 -146
  347. package/src/types/pipeline.ts +0 -52
  348. package/src/types/streams.ts +0 -32
  349. package/src/types/tar-stream.d.ts +0 -38
  350. package/src/types/vfs.ts +0 -98
  351. package/src/utils/expand.ts +0 -491
  352. package/src/utils/perfLogger.ts +0 -72
  353. package/src/utils/tokenize.ts +0 -98
  354. package/src/utils/vfsDiff.ts +0 -275
  355. package/tests/command-helpers.test.ts +0 -116
  356. package/tests/commands-admin-net.test.ts +0 -441
  357. package/tests/commands-advanced.test.ts +0 -456
  358. package/tests/commands-core.test.ts +0 -562
  359. package/tests/commands-missing.test.ts +0 -570
  360. package/tests/commands-specific-units.test.ts +0 -327
  361. package/tests/commands-text-sys.test.ts +0 -445
  362. package/tests/expand.test.ts +0 -170
  363. package/tests/helpers.test.ts +0 -97
  364. package/tests/new-features.test.ts +0 -1036
  365. package/tests/parser-executor.test.ts +0 -37
  366. package/tests/sftp.test.ts +0 -323
  367. package/tests/ssh-exec.test.ts +0 -45
  368. package/tests/test-helper.ts +0 -79
  369. package/tests/users.test.ts +0 -86
  370. package/tsconfig.json +0 -49
  371. package/typedoc.json +0 -47
@@ -1,234 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import * as path from "node:path";
3
- import type VirtualFileSystem from "../VirtualFileSystem";
4
- import type { VirtualPackageManager } from "../VirtualPackageManager";
5
- import type { VirtualShell } from "../VirtualShell";
6
-
7
- const PROTECTED_PREFIXES = ["/.virtual-env-js/.auth", "/etc/htpasswd"] as const;
8
-
9
- function normalizeFetchUrl(input: string): string {
10
- if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(input)) {
11
- return input;
12
- }
13
-
14
- return `http://${input}`;
15
- }
16
-
17
- export function normalizeTerminalOutput(text: string): string {
18
- return text
19
- .replace(/\r\n/g, "\n")
20
- .replace(/\r/g, "\n")
21
- .replace(/\t/g, " ")
22
- .split("\n")
23
- .map((line) =>
24
- line.replace(/^[ \u00A0]{8,}/, " ").replace(/[ \u00A0]{3,}/g, " "),
25
- )
26
- .join("\n")
27
- .replace(/\n{3,}/g, "\n\n")
28
- .trimEnd();
29
- }
30
-
31
- export function resolvePath(cwd: string, inputPath: string, homeDir?: string): string {
32
- if (!inputPath || inputPath.trim() === "") {
33
- return cwd;
34
- }
35
- if (inputPath.startsWith("~")) {
36
- const home = homeDir ?? "/root";
37
- return path.posix.normalize(`${home}${inputPath.slice(1)}`);
38
- }
39
- return inputPath.startsWith("/")
40
- ? path.posix.normalize(inputPath)
41
- : path.posix.normalize(path.posix.join(cwd, inputPath));
42
- }
43
-
44
- function isProtectedPath(targetPath: string): boolean {
45
- const normalized = targetPath.startsWith("/")
46
- ? path.posix.normalize(targetPath)
47
- : path.posix.normalize(`/${targetPath}`);
48
-
49
- return PROTECTED_PREFIXES.some(
50
- (prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`),
51
- );
52
- }
53
-
54
- export function assertPathAccess(
55
- authUser: string,
56
- targetPath: string,
57
- operation: string,
58
- ): void {
59
- if (authUser === "root") {
60
- return;
61
- }
62
-
63
- if (isProtectedPath(targetPath)) {
64
- throw new Error(`${operation}: permission denied: ${targetPath}`);
65
- }
66
- }
67
-
68
- export function stripUrlFilename(url: string): string {
69
- const cleaned = url.split("?")[0]?.split("#")[0] ?? url;
70
- const lastPart = cleaned.split("/").filter(Boolean).pop();
71
- return lastPart && lastPart.length > 0 ? lastPart : "index.html";
72
- }
73
-
74
- export async function fetchResource(
75
- url: string,
76
- ): Promise<{ text: string; status: number; contentType: string | null }> {
77
- const response = await fetch(normalizeFetchUrl(url));
78
- const contentType = response.headers.get("content-type");
79
- return {
80
- text: await response.text(),
81
- status: response.status,
82
- contentType,
83
- };
84
- }
85
-
86
- /**
87
- * Run a host command like curl or wget and capture its output.
88
- * @param binary - The binary to execute (e.g., "curl", "wget").
89
- * @param args - Arguments to pass to the binary.
90
- * @returns Promise resolving with stdout, stderr, and exit code.
91
- */
92
- export function runHostCommand(
93
- binary: string,
94
- args: string[],
95
- ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
96
- return new Promise((resolve) => {
97
- let childProcess: ReturnType<typeof spawn>;
98
-
99
- try {
100
- childProcess = spawn(binary, args, {
101
- stdio: ["ignore", "pipe", "pipe"],
102
- });
103
- } catch (error) {
104
- resolve({
105
- stdout: "",
106
- stderr: `${binary}: ${error instanceof Error ? error.message : String(error)}`,
107
- exitCode: 1,
108
- });
109
- return;
110
- }
111
-
112
- let stdout = "";
113
- let stderr = "";
114
- const stdoutStream = childProcess.stdout;
115
- const stderrStream = childProcess.stderr;
116
-
117
- if (!stdoutStream || !stderrStream) {
118
- resolve({
119
- stdout: "",
120
- stderr: `${binary}: failed to capture process output`,
121
- exitCode: 1,
122
- });
123
- return;
124
- }
125
-
126
- stdoutStream.setEncoding("utf8");
127
- stderrStream.setEncoding("utf8");
128
-
129
- stdoutStream.on("data", (chunk: string) => {
130
- stdout += chunk;
131
- });
132
-
133
- stderrStream.on("data", (chunk: string) => {
134
- stderr += chunk;
135
- });
136
-
137
- childProcess.on("error", (error) => {
138
- const errorCode =
139
- error instanceof Error && "code" in error
140
- ? String((error as NodeJS.ErrnoException).code ?? "")
141
- : "";
142
- resolve({
143
- stdout: "",
144
- stderr: `${binary}: ${error.message}`,
145
- exitCode: errorCode === "ENOENT" ? 127 : 1,
146
- });
147
- });
148
-
149
- childProcess.on("close", (code) => {
150
- resolve({
151
- stdout,
152
- stderr,
153
- exitCode: code ?? 1,
154
- });
155
- });
156
- });
157
- }
158
-
159
- function levenshtein(a: string, b: string): number {
160
- const dp: number[][] = Array.from({ length: a.length + 1 }, () =>
161
- Array<number>(b.length + 1).fill(0),
162
- );
163
-
164
- for (let i = 0; i <= a.length; i += 1) {
165
- dp[i]![0] = i;
166
- }
167
- for (let j = 0; j <= b.length; j += 1) {
168
- dp[0]![j] = j;
169
- }
170
-
171
- for (let i = 1; i <= a.length; i += 1) {
172
- for (let j = 1; j <= b.length; j += 1) {
173
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
174
- dp[i]![j] = Math.min(
175
- dp[i - 1]![j]! + 1,
176
- dp[i]![j - 1]! + 1,
177
- dp[i - 1]![j - 1]! + cost,
178
- );
179
- }
180
- }
181
-
182
- return dp[a.length]![b.length]!;
183
- }
184
-
185
- export function resolveReadablePath(
186
- vfs: VirtualFileSystem,
187
- cwd: string,
188
- inputPath: string,
189
- ): string {
190
- const exactPath = resolvePath(cwd, inputPath);
191
- if (vfs.exists(exactPath)) {
192
- return exactPath;
193
- }
194
-
195
- const parent = path.posix.dirname(exactPath);
196
- const fileName = path.posix.basename(exactPath);
197
- const siblings = vfs.list(parent);
198
-
199
- const caseInsensitive = siblings.filter(
200
- (name) => name.toLowerCase() === fileName.toLowerCase(),
201
- );
202
- if (caseInsensitive.length === 1) {
203
- return path.posix.join(parent, caseInsensitive[0]!);
204
- }
205
-
206
- const near = siblings.filter(
207
- (name) => levenshtein(name.toLowerCase(), fileName.toLowerCase()) <= 1,
208
- );
209
- if (near.length === 1) {
210
- return path.posix.join(parent, near[0]!);
211
- }
212
-
213
- return exactPath;
214
- }
215
-
216
- export function joinListWithType(
217
- cwd: string,
218
- items: string[],
219
- statAt: (p: string) => { type: "file" | "directory" },
220
- ): string {
221
- return items
222
- .map((name) => {
223
- const childPath = resolvePath(cwd, name);
224
- const stats = statAt(childPath);
225
- return stats.type === "directory" ? `${name}/` : name;
226
- })
227
- .join(" ");
228
- }
229
-
230
- export function getPackageManager(
231
- shell: VirtualShell,
232
- ): VirtualPackageManager | undefined {
233
- return shell.packageManager;
234
- }
@@ -1,34 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
-
3
- /**
4
- * Display persisted command history for the session (from VFS).
5
- * @category shell
6
- * @params ["[n]"]
7
- */
8
- export const historyCommand: ShellModule = {
9
- name: "history",
10
- description: "Display command history",
11
- category: "shell",
12
- params: ["[n]"],
13
- run: ({ args, shell, authUser }) => {
14
- // History is persisted in the VFS by the interactive shell
15
- const histPath = `/home/${authUser}/.bash_history`;
16
- if (!shell.vfs.exists(histPath)) {
17
- return { stdout: "", exitCode: 0 };
18
- }
19
-
20
- const raw = shell.vfs.readFile(histPath);
21
- const lines = raw.split("\n").filter(Boolean);
22
-
23
- const nArg = args[0];
24
- const n = nArg ? parseInt(nArg, 10) : null;
25
- const slice = n && !Number.isNaN(n) ? lines.slice(-n) : lines;
26
-
27
- const offset = lines.length - slice.length + 1;
28
- const numbered = slice.map(
29
- (line, i) => `${String(offset + i).padStart(5)} ${line}`,
30
- );
31
-
32
- return { stdout: numbered.join("\n"), exitCode: 0 };
33
- },
34
- };
@@ -1,14 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
-
3
- /**
4
- * Print the configured hostname for the virtual shell.
5
- * @category system
6
- * @params []
7
- */
8
- export const hostnameCommand: ShellModule = {
9
- name: "hostname",
10
- description: "Print hostname",
11
- category: "system",
12
- params: [],
13
- run: ({ hostname }) => ({ stdout: hostname, exitCode: 0 }),
14
- };
@@ -1,20 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
-
3
- /**
4
- * Interactive system monitor (requires terminal interaction).
5
- * @category system
6
- * @params []
7
- */
8
- export const htopCommand: ShellModule = {
9
- name: "htop",
10
- description: "System monitor",
11
- category: "system",
12
- params: [],
13
- run: ({ mode }) => {
14
- if (mode === "exec") {
15
- return { stderr: "htop: interactive terminal required", exitCode: 1 };
16
- }
17
-
18
- return { openHtop: true, exitCode: 0 };
19
- },
20
- };
@@ -1,19 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
-
3
- export const idCommand: ShellModule = {
4
- name: "id",
5
- description: "Print user identity",
6
- category: "system",
7
- params: ["[user]"],
8
- run: ({ authUser, shell, args }) => {
9
- const target = args[0] ?? authUser;
10
- const uid = target === "root" ? 0 : 1000;
11
- const gid = uid;
12
- const isSudo = shell.users.isSudoer(target);
13
- const groups = isSudo ? `${gid}(${target}),0(root)` : `${gid}(${target})`;
14
- return {
15
- stdout: `uid=${uid}(${target}) gid=${gid}(${target}) groups=${groups}`,
16
- exitCode: 0,
17
- };
18
- },
19
- };
@@ -1,9 +0,0 @@
1
- export {
2
- createCustomCommand,
3
- getCommandModulesPublic,
4
- getCommandNames,
5
- registerCommand,
6
- resolveModule
7
- } from "./registry";
8
-
9
- export { makeDefaultEnv, runCommand, runCommandDirect, userHome } from "./runtime";
@@ -1,19 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
-
3
- /**
4
- * Send a signal to a process by PID.
5
- * @category system
6
- * @params ["[-9] <pid>"]
7
- */
8
- export const killCommand: ShellModule = {
9
- name: "kill",
10
- description: "Send signal to process",
11
- category: "system",
12
- params: ["[-9] <pid>"],
13
- run: ({ args }) => {
14
- const pid = args.find((a) => !a.startsWith("-"));
15
- if (!pid) return { stderr: "kill: no pid specified", exitCode: 1 };
16
- // In virtual env, we just acknowledge the kill
17
- return { stdout: ``, exitCode: 0 };
18
- },
19
- };
@@ -1,71 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { ifFlag } from "./command-helpers";
3
- import { assertPathAccess, resolvePath } from "./helpers";
4
-
5
- export const lnCommand: ShellModule = {
6
- name: "ln",
7
- description: "Create links",
8
- category: "files",
9
- params: ["[-s] <target> <link_name>"],
10
- run: ({ authUser, shell, cwd, args }) => {
11
- const symbolic = ifFlag(args, ["-s", "--symbolic"]);
12
- const positionals = args.filter((a) => !a.startsWith("-"));
13
- const [targetArg, linkArg] = positionals;
14
-
15
- if (!targetArg || !linkArg) {
16
- return { stderr: "ln: missing operand", exitCode: 1 };
17
- }
18
-
19
- const linkPath = resolvePath(cwd, linkArg);
20
- const targetPath = symbolic
21
- ? targetArg // keep relative for symlinks
22
- : resolvePath(cwd, targetArg);
23
-
24
- try {
25
- assertPathAccess(authUser, linkPath, "ln");
26
-
27
- if (!symbolic) {
28
- // Hard link — copy file contents
29
- const srcPath = resolvePath(cwd, targetArg);
30
- assertPathAccess(authUser, srcPath, "ln");
31
- if (!shell.vfs.exists(srcPath)) {
32
- return {
33
- stderr: `ln: ${targetArg}: No such file or directory`,
34
- exitCode: 1,
35
- };
36
- }
37
- const content = shell.vfs.readFile(srcPath);
38
- shell.writeFileAsUser(authUser, linkPath, content);
39
- } else {
40
- shell.vfs.symlink(targetPath, linkPath);
41
- }
42
-
43
- return { exitCode: 0 };
44
- } catch (err) {
45
- const msg = err instanceof Error ? err.message : String(err);
46
- return { stderr: `ln: ${msg}`, exitCode: 1 };
47
- }
48
- },
49
- };
50
-
51
- /** Shell command: print the value of a symbolic link. */
52
- export const readlinkCommand: ShellModule = {
53
- name: "readlink",
54
- description: "Print resolved path of symbolic link",
55
- category: "files",
56
- params: ["[-f] <path>"],
57
- run: ({ shell, cwd, args }) => {
58
- const follow = args.includes("-f") || args.includes("-e");
59
- const target = args.find((a) => !a.startsWith("-"));
60
- if (!target) return { stderr: "readlink: missing operand\n", exitCode: 1 };
61
- const p = resolvePath(cwd, target);
62
- if (!shell.vfs.exists(p)) {
63
- return { stderr: `readlink: ${target}: No such file or directory\n`, exitCode: 1 };
64
- }
65
- if (!shell.vfs.isSymlink(p)) {
66
- return { stderr: `readlink: ${target}: not a symbolic link\n`, exitCode: 1 };
67
- }
68
- const resolved = shell.vfs.resolveSymlink(follow ? p : p);
69
- return { stdout: `${resolved}\n`, exitCode: 0 };
70
- },
71
- };
@@ -1,243 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { getArg, ifFlag } from "./command-helpers";
3
- import { assertPathAccess, resolvePath } from "./helpers";
4
-
5
- // ─── ANSI color codes (matches GNU ls --color=auto / LS_COLORS defaults) ────
6
-
7
- const RESET = "\x1b[0m";
8
-
9
- // Entry-type colors
10
- const C_DIR = "\x1b[1;34m"; // bold blue — directory
11
- const C_LINK = "\x1b[1;36m"; // bold cyan — symlink
12
- const C_EXEC = "\x1b[1;32m"; // bold green — executable file
13
- const C_NORMAL = ""; // no color — regular file
14
-
15
- // Special-mode backgrounds (GNU ls corner cases)
16
- const C_STICKY_WX = "\x1b[30;42m"; // black on green — dir sticky + world-writable (/tmp)
17
- const C_STICKY = "\x1b[37;44m"; // white on blue — dir sticky, NOT world-writable
18
- const C_OTHER_WX = "\x1b[34;42m"; // blue on green — dir world-writable, not sticky
19
-
20
- // ─── helpers ────────────────────────────────────────────────────────────────
21
-
22
- function colorize(name: string, color: string): string {
23
- return color ? `${color}${name}${RESET}` : name;
24
- }
25
-
26
- function entryColor(mode: number, type: "file" | "directory", isLink: boolean): string {
27
- if (isLink) return C_LINK;
28
- if (type === "directory") {
29
- const sticky = !!(mode & 0o1000);
30
- const worldW = !!(mode & 0o002);
31
- if (sticky && worldW) return C_STICKY_WX;
32
- if (sticky) return C_STICKY;
33
- if (worldW) return C_OTHER_WX;
34
- return C_DIR;
35
- }
36
- if (mode & 0o111) return C_EXEC;
37
- return C_NORMAL;
38
- }
39
-
40
- // ─── permissions string ──────────────────────────────────────────────────────
41
-
42
- function formatPermissions(mode: number, type: "file" | "directory", isLink: boolean): string {
43
- let ft: string;
44
- if (isLink) ft = "l";
45
- else if (type === "directory") ft = "d";
46
- else ft = "-";
47
-
48
- const r = (bit: number) => (mode & bit ? "r" : "-");
49
- const w = (bit: number) => (mode & bit ? "w" : "-");
50
-
51
- const xOwner = (() => {
52
- const exec = !!(mode & 0o100);
53
- if (mode & 0o4000) return exec ? "s" : "S";
54
- return exec ? "x" : "-";
55
- })();
56
- const xGroup = (() => {
57
- const exec = !!(mode & 0o010);
58
- if (mode & 0o2000) return exec ? "s" : "S";
59
- return exec ? "x" : "-";
60
- })();
61
- const xOther = (() => {
62
- const exec = !!(mode & 0o001);
63
- if (type === "directory" && (mode & 0o1000)) return exec ? "t" : "T";
64
- return exec ? "x" : "-";
65
- })();
66
-
67
- return `${ft}${r(0o400)}${w(0o200)}${xOwner}${r(0o040)}${w(0o020)}${xGroup}${r(0o004)}${w(0o002)}${xOther}`;
68
- }
69
-
70
- // ─── date formatting (GNU ls + French locale) ────────────────────────────────
71
-
72
- const MONTHS_EN = [
73
- "Jan", "Feb", "Mar", "Apr", "May", "Jun",
74
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
75
- ];
76
-
77
- function formatDate(date: Date): string {
78
- const now = new Date();
79
- const sixMo = 6 * 30 * 24 * 3600 * 1000;
80
- const recent = Math.abs(now.getTime() - date.getTime()) < sixMo;
81
- const day = String(date.getDate()).padStart(2, " ");
82
- const month = MONTHS_EN[date.getMonth()] ?? "";
83
- if (recent) {
84
- const hh = String(date.getHours()).padStart(2, "0");
85
- const mm = String(date.getMinutes()).padStart(2, "0");
86
- return `${day} ${month.padEnd(3)} ${hh}:${mm}`;
87
- }
88
- return `${day} ${month.padEnd(3)} ${date.getFullYear()}`;
89
- }
90
-
91
- // ─── symlink target ──────────────────────────────────────────────────────────
92
-
93
- function readlinkTarget(vfs: { readFile: (p: string) => string }, path: string): string {
94
- try { return vfs.readFile(path); } catch { return "?"; }
95
- }
96
-
97
- // ─── short listing ───────────────────────────────────────────────────────────
98
-
99
- function shortListing(
100
- vfs: {
101
- stat: (p: string) => { mode: number; type: "file" | "directory" };
102
- isSymlink: (p: string) => boolean;
103
- },
104
- dir: string,
105
- items: string[],
106
- ): string {
107
- const base = dir === "/" ? "" : dir;
108
- return items.map((name) => {
109
- const childPath = `${base}/${name}`;
110
- const isLink = vfs.isSymlink(childPath);
111
- let stat: { mode: number; type: "file" | "directory" };
112
- try { stat = vfs.stat(childPath); } catch { return name; }
113
- const color = entryColor(stat.mode, stat.type, isLink);
114
- return colorize(name, color);
115
- }).join(" ");
116
- }
117
-
118
- // ─── long listing ────────────────────────────────────────────────────────────
119
-
120
- type VfsStat = {
121
- mode: number;
122
- type: "file" | "directory";
123
- updatedAt: Date;
124
- size?: number;
125
- childrenCount?: number;
126
- };
127
-
128
- function longListing(
129
- vfs: {
130
- stat: (p: string) => VfsStat;
131
- isSymlink: (p: string) => boolean;
132
- readFile: (p: string) => string;
133
- },
134
- dir: string,
135
- items: string[],
136
- ): string {
137
- const base = dir === "/" ? "" : dir;
138
-
139
- type Row = { perms: string; nlink: string; size: string; date: string; label: string };
140
-
141
- const rows: Row[] = items.map((name) => {
142
- const childPath = `${base}/${name}`;
143
- const isLink = vfs.isSymlink(childPath);
144
- let stat: VfsStat;
145
- try { stat = vfs.stat(childPath); } catch {
146
- return {
147
- perms: "----------", nlink: "1", size: "0",
148
- date: formatDate(new Date()), label: name,
149
- };
150
- }
151
-
152
- const mode = isLink ? 0o120777 : stat.mode;
153
- const perms = formatPermissions(mode, stat.type, isLink);
154
-
155
- // nlink: dirs = children + 2 (. and ..), files/links = 1
156
- const nlink = stat.type === "directory"
157
- ? String((stat.childrenCount ?? 0) + 2)
158
- : "1";
159
-
160
- // size: links → target path length, files → bytes, dirs → children×4096
161
- const rawSize = isLink
162
- ? readlinkTarget(vfs, childPath).length
163
- : stat.type === "file"
164
- ? (stat.size ?? 0)
165
- : (stat.childrenCount ?? 0) * 4096;
166
- const size = String(rawSize);
167
-
168
- const date = formatDate(stat.updatedAt);
169
- const color = entryColor(mode, stat.type, isLink);
170
-
171
- const label = isLink
172
- ? `${colorize(name, color)} -> ${readlinkTarget(vfs, childPath)}`
173
- : colorize(name, color);
174
-
175
- return { perms, nlink, size, date, label };
176
- });
177
-
178
- const wNlink = Math.max(...rows.map((r) => r.nlink.length));
179
- const wSize = Math.max(...rows.map((r) => r.size.length));
180
- const owner = "root";
181
- const group = "root";
182
-
183
- const total = items.length * 8;
184
- const lines = rows.map((r) =>
185
- `${r.perms} ${r.nlink.padStart(wNlink)} ${owner} ${group} ${r.size.padStart(wSize)} ${r.date} ${r.label}`,
186
- );
187
-
188
- return `total ${total}\n${lines.join("\n")}`;
189
- }
190
-
191
- // ─── command ─────────────────────────────────────────────────────────────────
192
-
193
- export const lsCommand: ShellModule = {
194
- name: "ls",
195
- description: "List directory contents",
196
- category: "navigation",
197
- params: ["[-la] [path]"],
198
- run: ({ authUser, shell, cwd, args }) => {
199
- const longFormat = ifFlag(args, ["-l", "--long", "-la", "-al"]);
200
- const showHidden = ifFlag(args, ["-a", "--all", "-la", "-al"]);
201
-
202
- const targetArg = getArg(args, 0, {
203
- flags: ["-l", "--long", "-a", "--all", "-la", "-al"],
204
- });
205
- const target = resolvePath(cwd, targetArg ?? cwd);
206
- assertPathAccess(authUser, target, "ls");
207
-
208
- // Single file or symlink
209
- if (shell.vfs.exists(target)) {
210
- const st = shell.vfs.stat(target);
211
- const isLink = shell.vfs.isSymlink(target);
212
- if (st.type === "file" || isLink) {
213
- const name = target.split("/").pop() ?? target;
214
- const color = entryColor(isLink ? 0o120777 : st.mode, st.type, isLink);
215
- if (longFormat) {
216
- const mode = isLink ? 0o120777 : st.mode;
217
- const size = isLink
218
- ? readlinkTarget(shell.vfs, target).length
219
- : (st as { size?: number }).size ?? 0;
220
- const perms = formatPermissions(mode, st.type, isLink);
221
- const label = isLink
222
- ? `${colorize(name, color)} -> ${readlinkTarget(shell.vfs, target)}`
223
- : colorize(name, color);
224
- return {
225
- stdout: `${perms} 1 root root ${size} ${formatDate(st.updatedAt)} ${label}\n`,
226
- exitCode: 0,
227
- };
228
- }
229
- return { stdout: `${colorize(name, color)}\n`, exitCode: 0 };
230
- }
231
- }
232
-
233
- const items = shell.vfs
234
- .list(target)
235
- .filter((name) => showHidden || !name.startsWith("."));
236
-
237
- const rendered = longFormat
238
- ? longListing(shell.vfs, target, items)
239
- : shortListing(shell.vfs, target, items);
240
-
241
- return { stdout: `${rendered}\n`, exitCode: 0 };
242
- },
243
- };