typescript-virtual-container 1.5.2 → 1.5.4

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 (364) hide show
  1. package/README.md +43 -23
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/SSHMimic/executor.js +23 -5
  4. package/dist/commands/basename.d.ts +13 -0
  5. package/dist/commands/basename.js +45 -0
  6. package/dist/commands/file.d.ts +8 -0
  7. package/dist/commands/file.js +57 -0
  8. package/dist/commands/fun.d.ts +32 -0
  9. package/dist/commands/fun.js +172 -0
  10. package/dist/commands/ifconfig.d.ts +7 -0
  11. package/dist/commands/ifconfig.js +52 -0
  12. package/dist/commands/last.d.ts +13 -0
  13. package/dist/commands/last.js +68 -0
  14. package/dist/commands/manuals-bundle.js +598 -6
  15. package/dist/commands/registry.js +24 -2
  16. package/dist/commands/runtime.js +159 -106
  17. package/dist/commands/sh.js +5 -0
  18. package/dist/commands/tput.d.ts +13 -0
  19. package/dist/commands/tput.js +76 -0
  20. package/dist/commands/w.d.ts +7 -0
  21. package/dist/commands/w.js +38 -0
  22. package/dist/utils/expand.d.ts +12 -0
  23. package/dist/utils/expand.js +84 -0
  24. package/package.json +9 -3
  25. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  26. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  27. package/.github/dependabot.yml +0 -27
  28. package/.github/pull_request_template.md +0 -21
  29. package/.github/workflows/create-pull-request.yml +0 -85
  30. package/.github/workflows/publish.yml +0 -25
  31. package/.github/workflows/test-battery.yml +0 -102
  32. package/.vscode/settings.json +0 -20
  33. package/CODE_OF_CONDUCT.md +0 -39
  34. package/CONTRIBUTING.md +0 -59
  35. package/HONEYPOT.md +0 -358
  36. package/SECURITY.md +0 -33
  37. package/benchmark-results.txt +0 -40
  38. package/benchmark-virtualshell.ts +0 -88
  39. package/biome.json +0 -37
  40. package/build.js +0 -22
  41. package/builds/fortune-nyx-v1.5.1-directbash-k6.1.0.mjs +0 -1768
  42. package/builds/fortune-nyx-v1.5.1-ssh-nosftp.js +0 -1768
  43. package/builds/fortune-nyx-v1.5.1-ssh.cjs +0 -1769
  44. package/builds/fortune-nyx-v1.5.1-web.min.js +0 -17022
  45. package/bun.lock +0 -244
  46. package/docs/.nojekyll +0 -1
  47. package/docs/app.js +0 -1755
  48. package/docs/assets/hierarchy.js +0 -1
  49. package/docs/assets/highlight.css +0 -162
  50. package/docs/assets/icons.js +0 -18
  51. package/docs/assets/icons.svg +0 -1
  52. package/docs/assets/main.js +0 -60
  53. package/docs/assets/navigation.js +0 -1
  54. package/docs/assets/search.js +0 -1
  55. package/docs/assets/style.css +0 -1633
  56. package/docs/classes/HoneyPot.html +0 -31
  57. package/docs/classes/IdleManager.html +0 -162
  58. package/docs/classes/SshClient.html +0 -66
  59. package/docs/classes/VirtualFileSystem.html +0 -279
  60. package/docs/classes/VirtualPackageManager.html +0 -63
  61. package/docs/classes/VirtualSftpServer.html +0 -169
  62. package/docs/classes/VirtualShell.html +0 -285
  63. package/docs/classes/VirtualSshServer.html +0 -182
  64. package/docs/classes/VirtualUserManager.html +0 -276
  65. package/docs/demo.html +0 -82
  66. package/docs/functions/assertDiff.html +0 -6
  67. package/docs/functions/diffSnapshots.html +0 -7
  68. package/docs/functions/formatDiff.html +0 -6
  69. package/docs/functions/getArg.html +0 -13
  70. package/docs/functions/getFlag.html +0 -15
  71. package/docs/functions/ifFlag.html +0 -11
  72. package/docs/hierarchy.html +0 -1
  73. package/docs/index.html +0 -1869
  74. package/docs/interfaces/AuditLogEntry.html +0 -6
  75. package/docs/interfaces/CommandContext.html +0 -22
  76. package/docs/interfaces/CommandResult.html +0 -26
  77. package/docs/interfaces/ExecStream.html +0 -11
  78. package/docs/interfaces/HoneyPotStats.html +0 -16
  79. package/docs/interfaces/IdleManagerOptions.html +0 -7
  80. package/docs/interfaces/InstalledPackage.html +0 -20
  81. package/docs/interfaces/NanoEditorSession.html +0 -8
  82. package/docs/interfaces/PackageDefinition.html +0 -30
  83. package/docs/interfaces/PackageFile.html +0 -8
  84. package/docs/interfaces/PasswordChallenge.html +0 -16
  85. package/docs/interfaces/RemoveOptions.html +0 -4
  86. package/docs/interfaces/ShellEnv.html +0 -6
  87. package/docs/interfaces/ShellModule.html +0 -14
  88. package/docs/interfaces/ShellProperties.html +0 -14
  89. package/docs/interfaces/ShellStream.html +0 -11
  90. package/docs/interfaces/SudoChallenge.html +0 -24
  91. package/docs/interfaces/VfsBaseNode.html +0 -12
  92. package/docs/interfaces/VfsDiff.html +0 -10
  93. package/docs/interfaces/VfsDiffEntry.html +0 -6
  94. package/docs/interfaces/VfsDiffModified.html +0 -10
  95. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  96. package/docs/interfaces/VfsFileNode.html +0 -17
  97. package/docs/interfaces/VfsOptions.html +0 -26
  98. package/docs/interfaces/VfsSnapshot.html +0 -3
  99. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  100. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  101. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  102. package/docs/interfaces/VirtualActiveSession.html +0 -12
  103. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  104. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  105. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  106. package/docs/interfaces/WriteFileOptions.html +0 -6
  107. package/docs/media/LICENSE +0 -21
  108. package/docs/modules.html +0 -1
  109. package/docs/types/ArgParseOptions.html +0 -4
  110. package/docs/types/CommandMode.html +0 -2
  111. package/docs/types/CommandOutcome.html +0 -2
  112. package/docs/types/IdleState.html +0 -1
  113. package/docs/types/VfsNodeStats.html +0 -2
  114. package/docs/types/VfsNodeType.html +0 -2
  115. package/docs/types/VfsPersistenceMode.html +0 -5
  116. package/docs/types/VfsSnapshotNode.html +0 -2
  117. package/examples/README.md +0 -288
  118. package/examples/app.js +0 -1755
  119. package/examples/app.ts +0 -299
  120. package/examples/build.js +0 -27
  121. package/examples/demo.html +0 -33
  122. package/examples/honeypot-audit.ts +0 -180
  123. package/examples/honeypot-export.ts +0 -253
  124. package/examples/honeypot-quickstart.ts +0 -110
  125. package/examples/index.html +0 -82
  126. package/examples/server.js +0 -55
  127. package/polyfills/buffer.js +0 -117
  128. package/polyfills/node_child_process/index.js +0 -2
  129. package/polyfills/node_crypto/index.js +0 -167
  130. package/polyfills/node_events/index.js +0 -9
  131. package/polyfills/node_fs/index.js +0 -202
  132. package/polyfills/node_fs/promises.js +0 -4
  133. package/polyfills/node_os/index.js +0 -9
  134. package/polyfills/node_path/index.js +0 -28
  135. package/polyfills/node_vm/index.js +0 -7
  136. package/polyfills/node_zlib/index.js +0 -3
  137. package/polyfills/process.js +0 -14
  138. package/polyfills/ssh2/index.js +0 -75
  139. package/scripts/build-all.mjs +0 -226
  140. package/scripts/build-names.mjs +0 -43
  141. package/scripts/generate-manuals-bundle.mjs +0 -49
  142. package/scripts/postinstall.js +0 -42
  143. package/scripts/publish-package.sh +0 -70
  144. package/src/Honeypot/index.ts +0 -457
  145. package/src/SSHClient/index.ts +0 -270
  146. package/src/SSHMimic/exec.ts +0 -49
  147. package/src/SSHMimic/executor.ts +0 -251
  148. package/src/SSHMimic/hostKey.ts +0 -21
  149. package/src/SSHMimic/index.ts +0 -337
  150. package/src/SSHMimic/loginBanner.ts +0 -36
  151. package/src/SSHMimic/loginFormat.ts +0 -10
  152. package/src/SSHMimic/prompt.ts +0 -14
  153. package/src/SSHMimic/sftp.ts +0 -883
  154. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  155. package/src/VirtualFileSystem/index.ts +0 -1193
  156. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  157. package/src/VirtualFileSystem/journal.ts +0 -171
  158. package/src/VirtualFileSystem/path.ts +0 -74
  159. package/src/VirtualPackageManager/index.ts +0 -1006
  160. package/src/VirtualShell/idleManager.ts +0 -137
  161. package/src/VirtualShell/index.ts +0 -475
  162. package/src/VirtualShell/shell.ts +0 -700
  163. package/src/VirtualShell/shellParser.ts +0 -285
  164. package/src/VirtualUserManager/index.ts +0 -758
  165. package/src/bun.d.ts +0 -1
  166. package/src/commands/adduser.ts +0 -103
  167. package/src/commands/alias.ts +0 -69
  168. package/src/commands/apt.ts +0 -233
  169. package/src/commands/awk.ts +0 -168
  170. package/src/commands/base64.ts +0 -29
  171. package/src/commands/cat.ts +0 -52
  172. package/src/commands/cd.ts +0 -25
  173. package/src/commands/chmod.ts +0 -85
  174. package/src/commands/clear.ts +0 -15
  175. package/src/commands/command-helpers.ts +0 -286
  176. package/src/commands/cp.ts +0 -83
  177. package/src/commands/curl.ts +0 -147
  178. package/src/commands/cut.ts +0 -36
  179. package/src/commands/date.ts +0 -30
  180. package/src/commands/declare.ts +0 -49
  181. package/src/commands/deluser.ts +0 -98
  182. package/src/commands/df.ts +0 -23
  183. package/src/commands/diff.ts +0 -43
  184. package/src/commands/dpkg.ts +0 -180
  185. package/src/commands/du.ts +0 -56
  186. package/src/commands/echo.ts +0 -58
  187. package/src/commands/env.ts +0 -23
  188. package/src/commands/exit.ts +0 -18
  189. package/src/commands/export.ts +0 -34
  190. package/src/commands/find.ts +0 -68
  191. package/src/commands/free.ts +0 -47
  192. package/src/commands/grep.ts +0 -116
  193. package/src/commands/groups.ts +0 -19
  194. package/src/commands/gzip.ts +0 -88
  195. package/src/commands/head.ts +0 -52
  196. package/src/commands/help.ts +0 -152
  197. package/src/commands/helpers.ts +0 -234
  198. package/src/commands/history.ts +0 -34
  199. package/src/commands/hostname.ts +0 -14
  200. package/src/commands/htop.ts +0 -20
  201. package/src/commands/id.ts +0 -19
  202. package/src/commands/index.ts +0 -9
  203. package/src/commands/kill.ts +0 -19
  204. package/src/commands/ln.ts +0 -71
  205. package/src/commands/ls.ts +0 -243
  206. package/src/commands/lsb-release.ts +0 -63
  207. package/src/commands/man.ts +0 -31
  208. package/src/commands/manuals/adduser.txt +0 -11
  209. package/src/commands/manuals/apt-cache.txt +0 -12
  210. package/src/commands/manuals/apt.txt +0 -20
  211. package/src/commands/manuals/awk.txt +0 -13
  212. package/src/commands/manuals/cat.txt +0 -14
  213. package/src/commands/manuals/cd.txt +0 -16
  214. package/src/commands/manuals/chmod.txt +0 -16
  215. package/src/commands/manuals/clear.txt +0 -10
  216. package/src/commands/manuals/cp.txt +0 -10
  217. package/src/commands/manuals/curl.txt +0 -20
  218. package/src/commands/manuals/date.txt +0 -14
  219. package/src/commands/manuals/declare.txt +0 -12
  220. package/src/commands/manuals/deluser.txt +0 -10
  221. package/src/commands/manuals/df.txt +0 -10
  222. package/src/commands/manuals/dpkg-query.txt +0 -11
  223. package/src/commands/manuals/dpkg.txt +0 -14
  224. package/src/commands/manuals/du.txt +0 -11
  225. package/src/commands/manuals/echo.txt +0 -11
  226. package/src/commands/manuals/false.txt +0 -10
  227. package/src/commands/manuals/find.txt +0 -11
  228. package/src/commands/manuals/free.txt +0 -12
  229. package/src/commands/manuals/grep.txt +0 -13
  230. package/src/commands/manuals/groups.txt +0 -10
  231. package/src/commands/manuals/gzip.txt +0 -11
  232. package/src/commands/manuals/head.txt +0 -10
  233. package/src/commands/manuals/help.txt +0 -11
  234. package/src/commands/manuals/history.txt +0 -11
  235. package/src/commands/manuals/hostname.txt +0 -10
  236. package/src/commands/manuals/id.txt +0 -10
  237. package/src/commands/manuals/kill.txt +0 -13
  238. package/src/commands/manuals/ls.txt +0 -20
  239. package/src/commands/manuals/lsb_release.txt +0 -14
  240. package/src/commands/manuals/mkdir.txt +0 -10
  241. package/src/commands/manuals/mv.txt +0 -10
  242. package/src/commands/manuals/nano.txt +0 -11
  243. package/src/commands/manuals/neofetch.txt +0 -10
  244. package/src/commands/manuals/node.txt +0 -13
  245. package/src/commands/manuals/npm.txt +0 -13
  246. package/src/commands/manuals/npx.txt +0 -13
  247. package/src/commands/manuals/passwd.txt +0 -11
  248. package/src/commands/manuals/ping.txt +0 -10
  249. package/src/commands/manuals/printf.txt +0 -11
  250. package/src/commands/manuals/ps.txt +0 -10
  251. package/src/commands/manuals/pwd.txt +0 -10
  252. package/src/commands/manuals/python3.txt +0 -13
  253. package/src/commands/manuals/readlink.txt +0 -10
  254. package/src/commands/manuals/return.txt +0 -10
  255. package/src/commands/manuals/rm.txt +0 -10
  256. package/src/commands/manuals/sed.txt +0 -11
  257. package/src/commands/manuals/set.txt +0 -11
  258. package/src/commands/manuals/shift.txt +0 -10
  259. package/src/commands/manuals/sleep.txt +0 -10
  260. package/src/commands/manuals/sort.txt +0 -12
  261. package/src/commands/manuals/source.txt +0 -11
  262. package/src/commands/manuals/ssh.txt +0 -11
  263. package/src/commands/manuals/stat.txt +0 -10
  264. package/src/commands/manuals/su.txt +0 -13
  265. package/src/commands/manuals/sudo.txt +0 -11
  266. package/src/commands/manuals/tail.txt +0 -10
  267. package/src/commands/manuals/tar.txt +0 -19
  268. package/src/commands/manuals/tee.txt +0 -10
  269. package/src/commands/manuals/test.txt +0 -11
  270. package/src/commands/manuals/touch.txt +0 -11
  271. package/src/commands/manuals/tr.txt +0 -10
  272. package/src/commands/manuals/trap.txt +0 -10
  273. package/src/commands/manuals/true.txt +0 -10
  274. package/src/commands/manuals/type.txt +0 -10
  275. package/src/commands/manuals/uname.txt +0 -12
  276. package/src/commands/manuals/uniq.txt +0 -12
  277. package/src/commands/manuals/unset.txt +0 -10
  278. package/src/commands/manuals/uptime.txt +0 -11
  279. package/src/commands/manuals/wc.txt +0 -12
  280. package/src/commands/manuals/wget.txt +0 -12
  281. package/src/commands/manuals/which.txt +0 -10
  282. package/src/commands/manuals/whoami.txt +0 -10
  283. package/src/commands/manuals/xargs.txt +0 -10
  284. package/src/commands/manuals-bundle.ts +0 -898
  285. package/src/commands/mkdir.ts +0 -31
  286. package/src/commands/mv.ts +0 -50
  287. package/src/commands/nano.ts +0 -38
  288. package/src/commands/neofetch.ts +0 -53
  289. package/src/commands/node.ts +0 -341
  290. package/src/commands/npm.ts +0 -132
  291. package/src/commands/passwd.ts +0 -50
  292. package/src/commands/ping.ts +0 -32
  293. package/src/commands/printf.ts +0 -129
  294. package/src/commands/ps.ts +0 -58
  295. package/src/commands/pwd.ts +0 -9
  296. package/src/commands/python.ts +0 -2229
  297. package/src/commands/read.ts +0 -46
  298. package/src/commands/registry.ts +0 -249
  299. package/src/commands/rm.ts +0 -42
  300. package/src/commands/runtime.ts +0 -378
  301. package/src/commands/sed.ts +0 -68
  302. package/src/commands/seq.ts +0 -43
  303. package/src/commands/set.ts +0 -29
  304. package/src/commands/sh.ts +0 -467
  305. package/src/commands/shift.ts +0 -63
  306. package/src/commands/sleep.ts +0 -20
  307. package/src/commands/sort.ts +0 -46
  308. package/src/commands/source.ts +0 -52
  309. package/src/commands/stat.ts +0 -61
  310. package/src/commands/su.ts +0 -72
  311. package/src/commands/sudo.ts +0 -76
  312. package/src/commands/tail.ts +0 -53
  313. package/src/commands/tar.ts +0 -102
  314. package/src/commands/tee.ts +0 -36
  315. package/src/commands/test.ts +0 -137
  316. package/src/commands/touch.ts +0 -28
  317. package/src/commands/tr.ts +0 -70
  318. package/src/commands/tree.ts +0 -20
  319. package/src/commands/true.ts +0 -27
  320. package/src/commands/type.ts +0 -48
  321. package/src/commands/uname.ts +0 -29
  322. package/src/commands/uniq.ts +0 -39
  323. package/src/commands/unset.ts +0 -17
  324. package/src/commands/uptime.ts +0 -54
  325. package/src/commands/wc.ts +0 -55
  326. package/src/commands/wget.ts +0 -148
  327. package/src/commands/which.ts +0 -37
  328. package/src/commands/who.ts +0 -25
  329. package/src/commands/whoami.ts +0 -14
  330. package/src/commands/xargs.ts +0 -31
  331. package/src/index.ts +0 -67
  332. package/src/modules/linuxRootfs.ts +0 -1961
  333. package/src/modules/neofetch.ts +0 -358
  334. package/src/modules/shellInteractive.ts +0 -57
  335. package/src/modules/shellRuntime.ts +0 -76
  336. package/src/self-standalone.ts +0 -542
  337. package/src/standalone-wo-sftp.ts +0 -38
  338. package/src/standalone.ts +0 -72
  339. package/src/types/commands.ts +0 -146
  340. package/src/types/pipeline.ts +0 -52
  341. package/src/types/streams.ts +0 -32
  342. package/src/types/tar-stream.d.ts +0 -38
  343. package/src/types/vfs.ts +0 -98
  344. package/src/utils/expand.ts +0 -491
  345. package/src/utils/perfLogger.ts +0 -72
  346. package/src/utils/tokenize.ts +0 -98
  347. package/src/utils/vfsDiff.ts +0 -275
  348. package/tests/command-helpers.test.ts +0 -116
  349. package/tests/commands-admin-net.test.ts +0 -441
  350. package/tests/commands-advanced.test.ts +0 -456
  351. package/tests/commands-core.test.ts +0 -562
  352. package/tests/commands-missing.test.ts +0 -570
  353. package/tests/commands-specific-units.test.ts +0 -327
  354. package/tests/commands-text-sys.test.ts +0 -445
  355. package/tests/expand.test.ts +0 -170
  356. package/tests/helpers.test.ts +0 -97
  357. package/tests/new-features.test.ts +0 -1036
  358. package/tests/parser-executor.test.ts +0 -37
  359. package/tests/sftp.test.ts +0 -323
  360. package/tests/ssh-exec.test.ts +0 -45
  361. package/tests/test-helper.ts +0 -79
  362. package/tests/users.test.ts +0 -86
  363. package/tsconfig.json +0 -49
  364. 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
- };