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