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,61 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { resolvePath } from "./helpers";
3
-
4
- /**
5
- * Display file or filesystem status.
6
- * Outputs: path, size, mode, type, timestamps — similar to `stat` on Linux.
7
- */
8
- export const statCommand: ShellModule = {
9
- name: "stat",
10
- description: "Display file status",
11
- category: "files",
12
- params: ["[-c <format>] <file>"],
13
- run: ({ shell, cwd, args }) => {
14
- const fmtIdx = args.findIndex((a) => a === "-c" || a === "--format");
15
- const fmt = fmtIdx !== -1 ? args[fmtIdx + 1] : undefined;
16
- const file = args.find((a) => !a.startsWith("-") && a !== fmt);
17
- if (!file) return { stderr: "stat: missing operand\n", exitCode: 1 };
18
-
19
- const p = resolvePath(cwd, file);
20
- if (!shell.vfs.exists(p)) {
21
- return { stderr: `stat: cannot stat '${file}': No such file or directory\n`, exitCode: 1 };
22
- }
23
-
24
- const st = shell.vfs.stat(p);
25
- const isDir = st.type === "directory";
26
- const _isLink = shell.vfs.isSymlink(p);
27
- const isSymlink = shell.vfs.isSymlink(p);
28
- const modePerm = (mode: number): string => {
29
- const bits = [0o400,0o200,0o100,0o040,0o020,0o010,0o004,0o002,0o001];
30
- const syms = ["r","w","x","r","w","x","r","w","x"];
31
- return (isDir ? "d" : isSymlink ? "l" : "-") +
32
- bits.map((b, i) => mode & b ? syms[i] : "-").join("");
33
- };
34
- const octal = (st.mode).toString(8).padStart(4, "0");
35
- const modeStr = modePerm(st.mode);
36
- const size = "size" in st ? (st as {size: number}).size : 0;
37
- const ts = (d: Date) => d.toISOString().replace("T", " ").replace(/\.\d+Z$/, " +0000");
38
-
39
- // -c format string support
40
- if (fmt) {
41
- const out = fmt
42
- .replace("%n", file)
43
- .replace("%s", String(size))
44
- .replace("%a", octal.slice(1)) // access in octal (no leading 0)
45
- .replace("%A", modeStr)
46
- .replace("%F", isSymlink ? "symbolic link" : isDir ? "directory" : "regular file")
47
- .replace("%y", ts(st.updatedAt))
48
- .replace("%z", ts(st.updatedAt));
49
- return { stdout: `${out}\n`, exitCode: 0 };
50
- }
51
-
52
- const out = [
53
- ` File: ${file}${isSymlink ? ` -> ${shell.vfs.resolveSymlink(p)}` : ""}`,
54
- ` Size: ${size}${"\t".repeat(3)}${isSymlink ? "symbolic link" : isDir ? "directory" : "regular file"}`,
55
- `Access: (${octal}/${modeStr}) Uid: ( 0/ root) Gid: ( 0/ root)`,
56
- `Modify: ${ts(st.updatedAt)}`,
57
- `Change: ${ts(st.updatedAt)}`,
58
- ].join("\n");
59
- return { stdout: `${out}\n`, exitCode: 0 };
60
- },
61
- };
@@ -1,72 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { runCommand } from "./runtime";
3
-
4
- /**
5
- * Switch to another user account.
6
- *
7
- * Usage:
8
- * su [username] — switch to username (defaults to root)
9
- * su - [username] — login shell (changes cwd to target home)
10
- * su -c 'cmd' [user] — run command as user
11
- *
12
- * - Root can switch to any user without a password.
13
- * - Non-root sudoers must enter the target user's password.
14
- * - Non-sudoers are denied.
15
- * - Switching to a non-existent user returns an error immediately.
16
- */
17
- export const suCommand: ShellModule = {
18
- name: "su",
19
- description: "Switch user",
20
- category: "users",
21
- params: ["[-] [-c <cmd>] [username]"],
22
- run: async ({ authUser, shell, args, hostname, mode, cwd }) => {
23
- const loginShellFlag = args.includes("-") || args.includes("-l") || args.includes("--login");
24
- const cIdx = args.indexOf("-c");
25
- const cmdLine = cIdx !== -1 ? args[cIdx + 1] : undefined;
26
- const filteredArgs = args.filter((_, i) =>
27
- i !== cIdx && i !== cIdx + 1
28
- ).filter((a) => a !== "-" && a !== "-l" && a !== "--login");
29
- const targetUser = filteredArgs.find((a) => !a.startsWith("-")) ?? "root";
30
-
31
- // Verify target user exists
32
- if (!shell.users.listUsers().includes(targetUser)) {
33
- return { stderr: `su: user '${targetUser}' does not exist\n`, exitCode: 1 };
34
- }
35
-
36
- // Root switches freely without any password
37
- if (authUser === "root") {
38
- if (cmdLine) {
39
- return runCommand(
40
- cmdLine,
41
- targetUser,
42
- hostname,
43
- mode,
44
- loginShellFlag ? `/home/${targetUser}` : cwd,
45
- shell,
46
- );
47
- }
48
- return {
49
- switchUser: targetUser,
50
- nextCwd: loginShellFlag ? `/home/${targetUser}` : undefined,
51
- exitCode: 0,
52
- };
53
- }
54
-
55
- // Non-sudoers denied
56
- if (!shell.users.isSudoer(authUser)) {
57
- return { stderr: "su: permission denied\n", exitCode: 1 };
58
- }
59
-
60
- // Sudoers must enter target user's password via challenge
61
- return {
62
- sudoChallenge: {
63
- username: targetUser,
64
- targetUser,
65
- commandLine: cmdLine ?? null,
66
- loginShell: loginShellFlag,
67
- prompt: "Password: ",
68
- },
69
- exitCode: 0,
70
- };
71
- },
72
- };
@@ -1,76 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { parseArgs } from "./command-helpers";
3
- import { runCommand } from "./runtime";
4
-
5
- /**
6
- * Execute a command as another user (superuser by default).
7
- * @category users
8
- * @params ["[-u user] [-c cmd] [command]"]
9
- */
10
- function parseSudoArgs(args: string[]): {
11
- targetUser: string;
12
- loginShell: boolean;
13
- commandLine: string | null;
14
- } {
15
- const { flags, flagsWithValues, positionals } = parseArgs(args, {
16
- flags: ["-i", "-S"],
17
- flagsWithValue: ["-u", "--user"],
18
- });
19
-
20
- const loginShell = flags.has("-i");
21
- const targetUser =
22
- flagsWithValues.get("-u") || flagsWithValues.get("--user") || "root";
23
- const commandLine = positionals.length > 0 ? positionals.join(" ") : null;
24
-
25
- return { targetUser, loginShell, commandLine };
26
- }
27
- export const sudoCommand: ShellModule = {
28
- name: "sudo",
29
- description: "Execute as superuser",
30
- category: "users",
31
- params: ["<command...>"],
32
- run: async ({ authUser, hostname, mode, cwd, shell, args }) => {
33
- const { targetUser, loginShell, commandLine } = parseSudoArgs(args);
34
-
35
- if (authUser !== "root" && !shell.users.isSudoer(authUser)) {
36
- return { stderr: "sudo: permission denied", exitCode: 1 };
37
- }
38
-
39
- const effectiveUser = targetUser || "root";
40
- const prompt = `[sudo] password for ${authUser}: `;
41
-
42
- if (authUser === "root") {
43
- if (!commandLine && loginShell) {
44
- return {
45
- switchUser: effectiveUser,
46
- nextCwd: `/home/${effectiveUser}`,
47
- exitCode: 0,
48
- };
49
- }
50
-
51
- if (!commandLine) {
52
- return { stderr: "sudo: missing command", exitCode: 1 };
53
- }
54
-
55
- return runCommand(
56
- commandLine,
57
- effectiveUser,
58
- hostname,
59
- mode,
60
- loginShell ? `/home/${effectiveUser}` : cwd,
61
- shell,
62
- );
63
- }
64
-
65
- return {
66
- sudoChallenge: {
67
- username: authUser,
68
- targetUser: effectiveUser,
69
- commandLine,
70
- loginShell,
71
- prompt,
72
- },
73
- exitCode: 0,
74
- };
75
- },
76
- };
@@ -1,53 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { getFlag } from "./command-helpers";
3
- import { assertPathAccess, resolvePath } from "./helpers";
4
-
5
- /**
6
- * Output the last part of files or stdin.
7
- * @category text
8
- * @params ["[-n <lines>] [file...]"]
9
- */
10
- export const tailCommand: ShellModule = {
11
- name: "tail",
12
- description: "Output last lines",
13
- category: "text",
14
- params: ["[-n <lines>] [file...]"],
15
- run: ({ authUser, shell, cwd, args, stdin }) => {
16
- const nArg = getFlag(args, ["-n"]);
17
- const shortN = args.find((a) => /^-\d+$/.test(a));
18
- const n = typeof nArg === "string"
19
- ? parseInt(nArg, 10)
20
- : shortN ? parseInt(shortN.slice(1), 10) : 10;
21
- const positionals = args.filter(
22
- (a) => !a.startsWith("-") && a !== nArg && a !== String(n),
23
- );
24
-
25
- const take = (content: string) => {
26
- const lines = content.split("\n");
27
- // If content ends with \n, last element is ""; exclude from count
28
- const hasTrailingNewline = content.endsWith("\n");
29
- const meaningful = hasTrailingNewline ? lines.slice(0, -1) : lines;
30
- const sliced = meaningful.slice(Math.max(0, meaningful.length - n));
31
- return sliced.join("\n") + (hasTrailingNewline ? "\n" : "");
32
- };
33
-
34
- if (positionals.length === 0) {
35
- return { stdout: take(stdin ?? ""), exitCode: 0 };
36
- }
37
-
38
- const results: string[] = [];
39
- for (const file of positionals) {
40
- const filePath = resolvePath(cwd, file);
41
- try {
42
- assertPathAccess(authUser, filePath, "tail");
43
- results.push(take(shell.vfs.readFile(filePath)));
44
- } catch {
45
- return {
46
- stderr: `tail: ${file}: No such file or directory`,
47
- exitCode: 1,
48
- };
49
- }
50
- }
51
- return { stdout: results.join("\n"), exitCode: 0 };
52
- },
53
- };
@@ -1,102 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { resolvePath } from "./helpers";
3
-
4
- /**
5
- * Archive or extract files with tar and optional gzip compression.
6
- * @category archive
7
- * @params ["[-czf|-xzf|-tf] <archive> [files...]"]
8
- */
9
- export const tarCommand: ShellModule = {
10
- name: "tar",
11
- description: "Archive utility",
12
- category: "archive",
13
- params: ["[-czf|-xzf|-tf] <archive> [files...]"],
14
- run: ({ authUser, shell, cwd, args }) => {
15
- // Expand combined flags: -czf or czf (bare mode string) → ["-c", "-z", "-f"]
16
- const expanded: string[] = [];
17
- let foundModeStr = false;
18
- for (const a of args) {
19
- if (/^-[a-zA-Z]{2,}$/.test(a)) {
20
- // -czf style
21
- for (const ch of a.slice(1)) expanded.push(`-${ch}`);
22
- } else if (!foundModeStr && /^[cxtdru]{1,}[a-zA-Z]*$/.test(a) && !a.includes("/") && !a.startsWith("-")) {
23
- // czf bare style (first non-path arg)
24
- foundModeStr = true;
25
- for (const ch of a) expanded.push(`-${ch}`);
26
- } else {
27
- expanded.push(a);
28
- }
29
- }
30
-
31
- const create = expanded.includes("-c");
32
- const extract = expanded.includes("-x");
33
- const list = expanded.includes("-t");
34
- const fIdx = expanded.indexOf("-f");
35
- const archiveName = fIdx !== -1
36
- ? expanded[fIdx + 1]
37
- : expanded.find((a) => a.endsWith(".tar") || a.endsWith(".tar.gz") || a.endsWith(".tgz"));
38
-
39
- if (!create && !extract && !list) {
40
- return { stderr: "tar: must specify -c, -x, or -t\n", exitCode: 1 };
41
- }
42
-
43
- if (!archiveName)
44
- return { stderr: "tar: no archive specified\n", exitCode: 1 };
45
- const archivePath = resolvePath(cwd, archiveName);
46
-
47
- if (create) {
48
- // Skip flags and archive name from file list
49
- const skipNext2 = new Set<number>();
50
- if (fIdx !== -1) skipNext2.add(fIdx + 1);
51
- const fileArgs = expanded.filter((a, i) =>
52
- !a.startsWith("-") && a !== archiveName && !skipNext2.has(i),
53
- );
54
- const entries: Record<string, string> = {};
55
- for (const f of fileArgs) {
56
- const p = resolvePath(cwd, f);
57
- try {
58
- const stat = shell.vfs.stat(p);
59
- if (stat.type === "file") entries[f] = shell.vfs.readFile(p);
60
- else {
61
- const walk = (dir: string, prefix: string) => {
62
- for (const e of shell.vfs.list(dir)) {
63
- const full = `${dir}/${e}`,
64
- rel = `${prefix}/${e}`;
65
- const s = shell.vfs.stat(full);
66
- if (s.type === "file") entries[rel] = shell.vfs.readFile(full);
67
- else walk(full, rel);
68
- }
69
- };
70
- walk(p, f);
71
- }
72
- } catch {
73
- return {
74
- stderr: `tar: ${f}: No such file or directory`,
75
- exitCode: 1,
76
- };
77
- }
78
- }
79
- shell.writeFileAsUser(authUser, archivePath, JSON.stringify(entries));
80
- return { exitCode: 0 };
81
- }
82
-
83
- if (list || extract) {
84
- let entries: Record<string, string>;
85
- try {
86
- entries = JSON.parse(shell.vfs.readFile(archivePath));
87
- } catch {
88
- return {
89
- stderr: `tar: ${archiveName}: cannot open archive`,
90
- exitCode: 1,
91
- };
92
- }
93
- if (list) return { stdout: Object.keys(entries).join("\n"), exitCode: 0 };
94
- for (const [name, content] of Object.entries(entries)) {
95
- shell.writeFileAsUser(authUser, resolvePath(cwd, name), content);
96
- }
97
- return { exitCode: 0 };
98
- }
99
-
100
- return { stderr: "tar: must specify -c, -x, or -t", exitCode: 1 };
101
- },
102
- };
@@ -1,36 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { ifFlag } from "./command-helpers";
3
- import { resolvePath } from "./helpers";
4
-
5
- /**
6
- * Read stdin and write to stdout and files simultaneously.
7
- * @category text
8
- * @params ["[-a] <file...>"]
9
- */
10
- export const teeCommand: ShellModule = {
11
- name: "tee",
12
- description: "Read stdin, write to stdout and files",
13
- category: "text",
14
- params: ["[-a] <file...>"],
15
- run: ({ authUser, shell, cwd, args, stdin }) => {
16
- const append = ifFlag(args, ["-a"]);
17
- const files = args.filter((a) => !a.startsWith("-"));
18
- const input = stdin ?? "";
19
- for (const f of files) {
20
- const p = resolvePath(cwd, f);
21
- if (append) {
22
- const existing = (() => {
23
- try {
24
- return shell.vfs.readFile(p);
25
- } catch {
26
- return "";
27
- }
28
- })();
29
- shell.writeFileAsUser(authUser, p, existing + input);
30
- } else {
31
- shell.writeFileAsUser(authUser, p, input);
32
- }
33
- }
34
- return { stdout: input, exitCode: 0 };
35
- },
36
- };
@@ -1,137 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import type { VfsFileNode } from "../types/vfs";
3
- import type { VirtualShell } from "../VirtualShell";
4
-
5
- /**
6
- * Evaluate a POSIX test expression.
7
- * Supports: -f, -d, -e, -r, -w, -x, -s, -z, -n,
8
- * string =, !=, numeric -eq -ne -lt -le -gt -ge,
9
- * ! (negate), -a (and), -o (or).
10
- */
11
- function evalTest(
12
- tokens: string[],
13
- shell: VirtualShell,
14
- cwd: string,
15
- ): boolean {
16
- // When called via [ command, ] is the last arg — strip it
17
- // When called via test command, no brackets present
18
- if (tokens[tokens.length - 1] === "]") {
19
- tokens = tokens.slice(0, -1);
20
- }
21
- // Also strip leading [ if present (shouldn't normally happen but be safe)
22
- if (tokens[0] === "[") {
23
- tokens = tokens.slice(1);
24
- }
25
-
26
- if (tokens.length === 0) return false;
27
-
28
- // Negation
29
- if (tokens[0] === "!") return !evalTest(tokens.slice(1), shell, cwd);
30
-
31
- // Boolean -a / -o (simple left-right, no precedence)
32
- const andIdx = tokens.indexOf("-a");
33
- if (andIdx !== -1) {
34
- return (
35
- evalTest(tokens.slice(0, andIdx), shell, cwd) &&
36
- evalTest(tokens.slice(andIdx + 1), shell, cwd)
37
- );
38
- }
39
- const orIdx = tokens.indexOf("-o");
40
- if (orIdx !== -1) {
41
- return (
42
- evalTest(tokens.slice(0, orIdx), shell, cwd) ||
43
- evalTest(tokens.slice(orIdx + 1), shell, cwd)
44
- );
45
- }
46
-
47
- // Unary file tests
48
- if (tokens.length === 2) {
49
- const [flag, operand = ""] = tokens;
50
- const resolvePath = (p: string) =>
51
- p.startsWith("/") ? p : `${cwd}/${p}`.replace(/\/+/g, "/");
52
- const path = resolvePath(operand);
53
-
54
- switch (flag) {
55
- case "-e":
56
- return shell.vfs.exists(path);
57
- case "-f":
58
- return shell.vfs.exists(path) && shell.vfs.stat(path).type === "file";
59
- case "-d":
60
- return (
61
- shell.vfs.exists(path) && shell.vfs.stat(path).type === "directory"
62
- );
63
- case "-r":
64
- return shell.vfs.exists(path); // all readable in virtual env
65
- case "-w":
66
- return shell.vfs.exists(path);
67
- case "-x":
68
- return shell.vfs.exists(path) && !!(shell.vfs.stat(path).mode & 0o111);
69
- case "-s":
70
- return (
71
- shell.vfs.exists(path) &&
72
- shell.vfs.stat(path).type === "file" &&
73
- (shell.vfs.stat(path) as VfsFileNode).size > 0
74
- );
75
- case "-z":
76
- return operand.length === 0;
77
- case "-n":
78
- return operand.length > 0;
79
- case "-L":
80
- return shell.vfs.isSymlink(path);
81
- }
82
- }
83
-
84
- // Binary comparisons
85
- if (tokens.length === 3) {
86
- const [left = "", op, right = ""] = tokens;
87
- const leftN = Number(left);
88
- const rightN = Number(right);
89
-
90
- switch (op) {
91
- // String
92
- case "=":
93
- case "==":
94
- return left === right;
95
- case "!=":
96
- return left !== right;
97
- case "<":
98
- return left < right;
99
- case ">":
100
- return left > right;
101
- // Numeric
102
- case "-eq":
103
- return leftN === rightN;
104
- case "-ne":
105
- return leftN !== rightN;
106
- case "-lt":
107
- return leftN < rightN;
108
- case "-le":
109
- return leftN <= rightN;
110
- case "-gt":
111
- return leftN > rightN;
112
- case "-ge":
113
- return leftN >= rightN;
114
- }
115
- }
116
-
117
- // Single string (truthy if non-empty)
118
- if (tokens.length === 1) return (tokens[0] ?? "").length > 0;
119
-
120
- return false;
121
- }
122
-
123
- export const testCommand: ShellModule = {
124
- name: "test",
125
- aliases: ["["],
126
- description: "Evaluate conditional expression",
127
- category: "shell",
128
- params: ["<expression>"],
129
- run: ({ args, shell, cwd }) => {
130
- try {
131
- const result = evalTest([...args], shell, cwd);
132
- return { exitCode: result ? 0 : 1 };
133
- } catch {
134
- return { stderr: "test: malformed expression", exitCode: 2 };
135
- }
136
- },
137
- };
@@ -1,28 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { assertPathAccess, resolvePath } from "./helpers";
3
-
4
- /**
5
- * Create empty files or update file timestamps.
6
- * @category files
7
- * @params ["<file>"]
8
- */
9
- export const touchCommand: ShellModule = {
10
- name: "touch",
11
- description: "Create or update files",
12
- category: "files",
13
- params: ["<file>"],
14
- run: ({ authUser, shell, cwd, args }) => {
15
- if (args.length === 0) {
16
- return { stderr: "touch: missing file operand", exitCode: 1 };
17
- }
18
-
19
- for (const file of args) {
20
- const target = resolvePath(cwd, file);
21
- assertPathAccess(authUser, target, "touch");
22
- if (!shell.vfs.exists(target)) {
23
- shell.writeFileAsUser(authUser, target, "");
24
- }
25
- }
26
- return { exitCode: 0 };
27
- },
28
- };
@@ -1,70 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { ifFlag } from "./command-helpers";
3
-
4
- function unescapeTrSet(s: string): string {
5
- return s
6
- .replace(/\\n/g, "\n")
7
- .replace(/\\t/g, "\t")
8
- .replace(/\\r/g, "\r")
9
- .replace(/\\\\/g, "\\");
10
- }
11
-
12
- function expandTrSet(s: string): string[] {
13
- const chars: string[] = [];
14
- const unescaped = unescapeTrSet(s);
15
- let i = 0;
16
- while (i < unescaped.length) {
17
- // Range: a-z, A-Z, 0-9
18
- if (i + 2 < unescaped.length && unescaped[i + 1] === "-") {
19
- const from = unescaped.charCodeAt(i);
20
- const to = unescaped.charCodeAt(i + 2);
21
- if (from <= to) {
22
- for (let c = from; c <= to; c++) chars.push(String.fromCharCode(c));
23
- i += 3;
24
- continue;
25
- }
26
- }
27
- chars.push(unescaped[i]!);
28
- i++;
29
- }
30
- return chars;
31
- }
32
-
33
- export const trCommand: ShellModule = {
34
- name: "tr",
35
- description: "Translate or delete characters",
36
- category: "text",
37
- params: ["[-d] [-s] <set1> [set2]"],
38
- run: ({ args, stdin }) => {
39
- const del = ifFlag(args, ["-d"]);
40
- const squeeze = ifFlag(args, ["-s"]);
41
- const positionals = args.filter((a) => !a.startsWith("-"));
42
- const set1chars = expandTrSet(positionals[0] ?? "");
43
- const set2chars = expandTrSet(positionals[1] ?? "");
44
-
45
- let input = stdin ?? "";
46
-
47
- if (del) {
48
- const deleteSet = new Set(set1chars);
49
- input = [...input].filter((c) => !deleteSet.has(c)).join("");
50
- } else if (set2chars.length > 0) {
51
- // Build translation map
52
- const map = new Map<string, string>();
53
- for (let i = 0; i < set1chars.length; i++) {
54
- map.set(
55
- set1chars[i]!,
56
- set2chars[i] ?? set2chars[set2chars.length - 1] ?? "",
57
- );
58
- }
59
- input = [...input].map((c) => map.get(c) ?? c).join("");
60
- }
61
-
62
- if (squeeze && set2chars.length > 0) {
63
- // Squeeze repeated characters in set2
64
- const squeezeSet = new Set(set2chars);
65
- input = input.replace(/(.)\1+/g, (_, c) => squeezeSet.has(c) ? c : _);
66
- }
67
-
68
- return { stdout: input, exitCode: 0 };
69
- },
70
- };
@@ -1,20 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { getArg } from "./command-helpers";
3
- import { assertPathAccess, resolvePath } from "./helpers";
4
-
5
- /**
6
- * Display directory structure in a tree format.
7
- * @category navigation
8
- * @params ["[path]"]
9
- */
10
- export const treeCommand: ShellModule = {
11
- name: "tree",
12
- description: "Display directory tree",
13
- category: "navigation",
14
- params: ["[path]"],
15
- run: ({ authUser, shell, cwd, args }) => {
16
- const target = resolvePath(cwd, getArg(args, 0) ?? cwd);
17
- assertPathAccess(authUser, target, "tree");
18
- return { stdout: shell.vfs.tree(target), exitCode: 0 };
19
- },
20
- };
@@ -1,27 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
-
3
- /**
4
- * Always return success (exit code 0).
5
- * @category shell
6
- * @params []
7
- */
8
- export const trueCommand: ShellModule = {
9
- name: "true",
10
- description: "Return success exit code",
11
- category: "shell",
12
- params: [],
13
- run: () => ({ exitCode: 0 }),
14
- };
15
-
16
- /**
17
- * Always return failure (exit code 1).
18
- * @category shell
19
- * @params []
20
- */
21
- export const falseCommand: ShellModule = {
22
- name: "false",
23
- description: "Return failure exit code",
24
- category: "shell",
25
- params: [],
26
- run: () => ({ exitCode: 1 }),
27
- };