typescript-virtual-container 1.3.4 → 1.4.1

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 (368) hide show
  1. package/.vscode/settings.json +0 -1
  2. package/README.md +674 -1504
  3. package/benchmark-results.txt +21 -21
  4. package/builds/self-standalone.js +282 -332
  5. package/builds/self-standalone.js.map +4 -4
  6. package/builds/standalone-wo-sftp.js +218 -282
  7. package/builds/standalone-wo-sftp.js.map +4 -4
  8. package/builds/standalone.js +271 -335
  9. package/builds/standalone.js.map +4 -4
  10. package/builds/web-full-api.min.js +3 -3
  11. package/builds/web-full-api.min.js.map +4 -4
  12. package/builds/web.min.js +2 -2
  13. package/builds/web.min.js.map +4 -4
  14. package/bun.lock +14 -12
  15. package/dist/SSHClient/index.d.ts.map +1 -1
  16. package/dist/SSHClient/index.js +5 -3
  17. package/dist/SSHMimic/executor.d.ts +1 -3
  18. package/dist/SSHMimic/executor.d.ts.map +1 -1
  19. package/dist/SSHMimic/executor.js +20 -22
  20. package/dist/SSHMimic/index.d.ts.map +1 -1
  21. package/dist/SSHMimic/index.js +5 -3
  22. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  23. package/dist/SSHMimic/sftp.js +26 -21
  24. package/dist/VirtualPackageManager/index.d.ts.map +1 -1
  25. package/dist/VirtualPackageManager/index.js +29 -1
  26. package/dist/VirtualShell/shell.d.ts.map +1 -1
  27. package/dist/VirtualShell/shell.js +25 -3
  28. package/dist/VirtualShell/shellParser.d.ts +1 -8
  29. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  30. package/dist/VirtualShell/shellParser.js +2 -81
  31. package/dist/VirtualUserManager/index.d.ts +7 -1
  32. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  33. package/dist/VirtualUserManager/index.js +47 -16
  34. package/dist/commands/adduser.d.ts +10 -4
  35. package/dist/commands/adduser.d.ts.map +1 -1
  36. package/dist/commands/adduser.js +75 -12
  37. package/dist/commands/alias.d.ts +5 -0
  38. package/dist/commands/alias.d.ts.map +1 -1
  39. package/dist/commands/alias.js +5 -0
  40. package/dist/commands/apt.d.ts +5 -0
  41. package/dist/commands/apt.d.ts.map +1 -1
  42. package/dist/commands/apt.js +5 -0
  43. package/dist/commands/awk.d.ts +10 -8
  44. package/dist/commands/awk.d.ts.map +1 -1
  45. package/dist/commands/awk.js +156 -28
  46. package/dist/commands/cd.d.ts.map +1 -1
  47. package/dist/commands/cd.js +0 -3
  48. package/dist/commands/clear.d.ts +5 -0
  49. package/dist/commands/clear.d.ts.map +1 -1
  50. package/dist/commands/clear.js +5 -0
  51. package/dist/commands/command-helpers.d.ts.map +1 -1
  52. package/dist/commands/command-helpers.js +8 -0
  53. package/dist/commands/curl.d.ts.map +1 -1
  54. package/dist/commands/curl.js +2 -1
  55. package/dist/commands/declare.d.ts +5 -0
  56. package/dist/commands/declare.d.ts.map +1 -1
  57. package/dist/commands/declare.js +5 -0
  58. package/dist/commands/deluser.d.ts +12 -0
  59. package/dist/commands/deluser.d.ts.map +1 -1
  60. package/dist/commands/deluser.js +72 -6
  61. package/dist/commands/df.d.ts +5 -0
  62. package/dist/commands/df.d.ts.map +1 -1
  63. package/dist/commands/df.js +5 -0
  64. package/dist/commands/du.d.ts +5 -0
  65. package/dist/commands/du.d.ts.map +1 -1
  66. package/dist/commands/du.js +5 -0
  67. package/dist/commands/export.d.ts +5 -0
  68. package/dist/commands/export.d.ts.map +1 -1
  69. package/dist/commands/export.js +5 -0
  70. package/dist/commands/grep.d.ts.map +1 -1
  71. package/dist/commands/grep.js +22 -4
  72. package/dist/commands/groups.d.ts +5 -0
  73. package/dist/commands/groups.d.ts.map +1 -1
  74. package/dist/commands/groups.js +5 -0
  75. package/dist/commands/gzip.d.ts +5 -2
  76. package/dist/commands/gzip.d.ts.map +1 -1
  77. package/dist/commands/gzip.js +54 -28
  78. package/dist/commands/head.d.ts.map +1 -1
  79. package/dist/commands/head.js +12 -3
  80. package/dist/commands/htop.d.ts +5 -0
  81. package/dist/commands/htop.d.ts.map +1 -1
  82. package/dist/commands/htop.js +5 -0
  83. package/dist/commands/kill.d.ts +5 -0
  84. package/dist/commands/kill.d.ts.map +1 -1
  85. package/dist/commands/kill.js +5 -0
  86. package/dist/commands/ln.d.ts +2 -0
  87. package/dist/commands/ln.d.ts.map +1 -1
  88. package/dist/commands/ln.js +22 -0
  89. package/dist/commands/ls.d.ts.map +1 -1
  90. package/dist/commands/ls.js +15 -0
  91. package/dist/commands/lsb-release.d.ts +5 -0
  92. package/dist/commands/lsb-release.d.ts.map +1 -1
  93. package/dist/commands/lsb-release.js +5 -0
  94. package/dist/commands/man.d.ts.map +1 -1
  95. package/dist/commands/man.js +30 -136
  96. package/dist/commands/mkdir.d.ts +5 -0
  97. package/dist/commands/mkdir.d.ts.map +1 -1
  98. package/dist/commands/mkdir.js +5 -0
  99. package/dist/commands/mv.d.ts +5 -0
  100. package/dist/commands/mv.d.ts.map +1 -1
  101. package/dist/commands/mv.js +5 -0
  102. package/dist/commands/nano.d.ts +5 -0
  103. package/dist/commands/nano.d.ts.map +1 -1
  104. package/dist/commands/nano.js +5 -0
  105. package/dist/commands/neofetch.d.ts +5 -0
  106. package/dist/commands/neofetch.d.ts.map +1 -1
  107. package/dist/commands/neofetch.js +14 -5
  108. package/dist/commands/passwd.d.ts +8 -0
  109. package/dist/commands/passwd.d.ts.map +1 -1
  110. package/dist/commands/passwd.js +32 -11
  111. package/dist/commands/ping.d.ts +5 -0
  112. package/dist/commands/ping.d.ts.map +1 -1
  113. package/dist/commands/ping.js +5 -0
  114. package/dist/commands/printf.d.ts +5 -0
  115. package/dist/commands/printf.d.ts.map +1 -1
  116. package/dist/commands/printf.js +43 -12
  117. package/dist/commands/ps.d.ts +5 -0
  118. package/dist/commands/ps.d.ts.map +1 -1
  119. package/dist/commands/ps.js +5 -0
  120. package/dist/commands/read.d.ts +5 -0
  121. package/dist/commands/read.d.ts.map +1 -1
  122. package/dist/commands/read.js +5 -0
  123. package/dist/commands/registry.d.ts.map +1 -1
  124. package/dist/commands/registry.js +4 -1
  125. package/dist/commands/rm.d.ts +5 -0
  126. package/dist/commands/rm.d.ts.map +1 -1
  127. package/dist/commands/rm.js +5 -0
  128. package/dist/commands/runtime.d.ts.map +1 -1
  129. package/dist/commands/runtime.js +1 -57
  130. package/dist/commands/sed.d.ts +5 -0
  131. package/dist/commands/sed.d.ts.map +1 -1
  132. package/dist/commands/sed.js +5 -0
  133. package/dist/commands/set.d.ts +5 -6
  134. package/dist/commands/set.d.ts.map +1 -1
  135. package/dist/commands/set.js +5 -22
  136. package/dist/commands/sh.d.ts +6 -0
  137. package/dist/commands/sh.d.ts.map +1 -1
  138. package/dist/commands/sh.js +6 -0
  139. package/dist/commands/shift.d.ts +10 -0
  140. package/dist/commands/shift.d.ts.map +1 -1
  141. package/dist/commands/shift.js +10 -0
  142. package/dist/commands/sleep.d.ts +5 -0
  143. package/dist/commands/sleep.d.ts.map +1 -1
  144. package/dist/commands/sleep.js +5 -0
  145. package/dist/commands/sort.d.ts +5 -0
  146. package/dist/commands/sort.d.ts.map +1 -1
  147. package/dist/commands/sort.js +5 -0
  148. package/dist/commands/source.d.ts +5 -0
  149. package/dist/commands/source.d.ts.map +1 -1
  150. package/dist/commands/source.js +5 -0
  151. package/dist/commands/stat.d.ts +7 -0
  152. package/dist/commands/stat.d.ts.map +1 -0
  153. package/dist/commands/stat.js +56 -0
  154. package/dist/commands/su.d.ts +13 -0
  155. package/dist/commands/su.d.ts.map +1 -1
  156. package/dist/commands/su.js +45 -14
  157. package/dist/commands/sudo.d.ts.map +1 -1
  158. package/dist/commands/sudo.js +5 -0
  159. package/dist/commands/tail.d.ts +5 -0
  160. package/dist/commands/tail.d.ts.map +1 -1
  161. package/dist/commands/tail.js +15 -3
  162. package/dist/commands/tar.d.ts +5 -0
  163. package/dist/commands/tar.d.ts.map +1 -1
  164. package/dist/commands/tar.js +40 -10
  165. package/dist/commands/tee.d.ts +5 -0
  166. package/dist/commands/tee.d.ts.map +1 -1
  167. package/dist/commands/tee.js +5 -0
  168. package/dist/commands/touch.d.ts +5 -0
  169. package/dist/commands/touch.d.ts.map +1 -1
  170. package/dist/commands/touch.js +5 -0
  171. package/dist/commands/tr.d.ts.map +1 -1
  172. package/dist/commands/tr.js +45 -10
  173. package/dist/commands/tree.d.ts +5 -0
  174. package/dist/commands/tree.d.ts.map +1 -1
  175. package/dist/commands/tree.js +5 -0
  176. package/dist/commands/true.d.ts +10 -0
  177. package/dist/commands/true.d.ts.map +1 -1
  178. package/dist/commands/true.js +10 -0
  179. package/dist/commands/type.d.ts +5 -0
  180. package/dist/commands/type.d.ts.map +1 -1
  181. package/dist/commands/type.js +5 -0
  182. package/dist/commands/uname.d.ts +5 -0
  183. package/dist/commands/uname.d.ts.map +1 -1
  184. package/dist/commands/uname.js +5 -0
  185. package/dist/commands/uniq.d.ts +5 -0
  186. package/dist/commands/uniq.d.ts.map +1 -1
  187. package/dist/commands/uniq.js +5 -0
  188. package/dist/commands/unset.d.ts +5 -0
  189. package/dist/commands/unset.d.ts.map +1 -1
  190. package/dist/commands/unset.js +5 -0
  191. package/dist/commands/uptime.d.ts +5 -0
  192. package/dist/commands/uptime.d.ts.map +1 -1
  193. package/dist/commands/uptime.js +5 -0
  194. package/dist/commands/wc.d.ts +5 -0
  195. package/dist/commands/wc.d.ts.map +1 -1
  196. package/dist/commands/wc.js +5 -0
  197. package/dist/commands/wget.d.ts +5 -0
  198. package/dist/commands/wget.d.ts.map +1 -1
  199. package/dist/commands/wget.js +16 -1
  200. package/dist/commands/who.d.ts +5 -0
  201. package/dist/commands/who.d.ts.map +1 -1
  202. package/dist/commands/who.js +5 -0
  203. package/dist/commands/whoami.d.ts +5 -0
  204. package/dist/commands/whoami.d.ts.map +1 -1
  205. package/dist/commands/whoami.js +5 -0
  206. package/dist/commands/xargs.d.ts +5 -0
  207. package/dist/commands/xargs.d.ts.map +1 -1
  208. package/dist/commands/xargs.js +5 -0
  209. package/dist/self-standalone.js +254 -30
  210. package/dist/types/commands.d.ts +36 -0
  211. package/dist/types/commands.d.ts.map +1 -1
  212. package/dist/utils/tokenize.d.ts +20 -0
  213. package/dist/utils/tokenize.d.ts.map +1 -0
  214. package/dist/utils/tokenize.js +74 -0
  215. package/examples/web.min.js +2 -2
  216. package/package.json +2 -2
  217. package/src/SSHClient/index.ts +6 -3
  218. package/src/SSHMimic/executor.ts +21 -44
  219. package/src/SSHMimic/index.ts +7 -5
  220. package/src/SSHMimic/sftp.ts +28 -21
  221. package/src/VirtualPackageManager/index.ts +29 -1
  222. package/src/VirtualShell/shell.ts +34 -4
  223. package/src/VirtualShell/shellParser.ts +2 -103
  224. package/src/VirtualUserManager/index.ts +43 -19
  225. package/src/commands/adduser.ts +86 -13
  226. package/src/commands/alias.ts +5 -0
  227. package/src/commands/apt.ts +5 -0
  228. package/src/commands/awk.ts +154 -29
  229. package/src/commands/cd.ts +0 -4
  230. package/src/commands/clear.ts +5 -0
  231. package/src/commands/command-helpers.ts +9 -0
  232. package/src/commands/curl.ts +2 -1
  233. package/src/commands/declare.ts +5 -0
  234. package/src/commands/deluser.ts +84 -7
  235. package/src/commands/df.ts +5 -0
  236. package/src/commands/du.ts +5 -0
  237. package/src/commands/export.ts +5 -0
  238. package/src/commands/grep.ts +21 -8
  239. package/src/commands/groups.ts +5 -0
  240. package/src/commands/gzip.ts +61 -28
  241. package/src/commands/head.ts +14 -4
  242. package/src/commands/htop.ts +5 -0
  243. package/src/commands/kill.ts +5 -0
  244. package/src/commands/ln.ts +22 -0
  245. package/src/commands/ls.ts +17 -0
  246. package/src/commands/lsb-release.ts +5 -0
  247. package/src/commands/man.ts +38 -143
  248. package/src/commands/manuals/adduser.txt +11 -0
  249. package/src/commands/manuals/apt-cache.txt +12 -0
  250. package/src/commands/manuals/apt.txt +20 -0
  251. package/src/commands/manuals/awk.txt +13 -0
  252. package/src/commands/manuals/cat.txt +14 -0
  253. package/src/commands/manuals/cd.txt +16 -0
  254. package/src/commands/manuals/chmod.txt +16 -0
  255. package/src/commands/manuals/clear.txt +10 -0
  256. package/src/commands/manuals/cp.txt +10 -0
  257. package/src/commands/manuals/curl.txt +20 -0
  258. package/src/commands/manuals/date.txt +14 -0
  259. package/src/commands/manuals/declare.txt +12 -0
  260. package/src/commands/manuals/deluser.txt +10 -0
  261. package/src/commands/manuals/df.txt +10 -0
  262. package/src/commands/manuals/dpkg-query.txt +11 -0
  263. package/src/commands/manuals/dpkg.txt +14 -0
  264. package/src/commands/manuals/du.txt +11 -0
  265. package/src/commands/manuals/echo.txt +11 -0
  266. package/src/commands/manuals/false.txt +10 -0
  267. package/src/commands/manuals/find.txt +11 -0
  268. package/src/commands/manuals/free.txt +12 -0
  269. package/src/commands/manuals/grep.txt +13 -0
  270. package/src/commands/manuals/groups.txt +10 -0
  271. package/src/commands/manuals/gzip.txt +11 -0
  272. package/src/commands/manuals/head.txt +10 -0
  273. package/src/commands/manuals/help.txt +11 -0
  274. package/src/commands/manuals/history.txt +11 -0
  275. package/src/commands/manuals/hostname.txt +10 -0
  276. package/src/commands/manuals/id.txt +10 -0
  277. package/src/commands/manuals/kill.txt +13 -0
  278. package/src/commands/manuals/ls.txt +20 -0
  279. package/src/commands/manuals/lsb_release.txt +14 -0
  280. package/src/commands/manuals/mkdir.txt +10 -0
  281. package/src/commands/manuals/mv.txt +10 -0
  282. package/src/commands/manuals/nano.txt +11 -0
  283. package/src/commands/manuals/neofetch.txt +10 -0
  284. package/src/commands/manuals/node.txt +13 -0
  285. package/src/commands/manuals/npm.txt +13 -0
  286. package/src/commands/manuals/npx.txt +13 -0
  287. package/src/commands/manuals/passwd.txt +11 -0
  288. package/src/commands/manuals/ping.txt +10 -0
  289. package/src/commands/manuals/printf.txt +11 -0
  290. package/src/commands/manuals/ps.txt +10 -0
  291. package/src/commands/manuals/pwd.txt +10 -0
  292. package/src/commands/manuals/python3.txt +13 -0
  293. package/src/commands/manuals/readlink.txt +10 -0
  294. package/src/commands/manuals/return.txt +10 -0
  295. package/src/commands/manuals/rm.txt +10 -0
  296. package/src/commands/manuals/sed.txt +11 -0
  297. package/src/commands/manuals/set.txt +11 -0
  298. package/src/commands/manuals/shift.txt +10 -0
  299. package/src/commands/manuals/sleep.txt +10 -0
  300. package/src/commands/manuals/sort.txt +12 -0
  301. package/src/commands/manuals/source.txt +11 -0
  302. package/src/commands/manuals/ssh.txt +11 -0
  303. package/src/commands/manuals/stat.txt +10 -0
  304. package/src/commands/manuals/su.txt +13 -0
  305. package/src/commands/manuals/sudo.txt +11 -0
  306. package/src/commands/manuals/tail.txt +10 -0
  307. package/src/commands/manuals/tar.txt +19 -0
  308. package/src/commands/manuals/tee.txt +10 -0
  309. package/src/commands/manuals/test.txt +11 -0
  310. package/src/commands/manuals/touch.txt +11 -0
  311. package/src/commands/manuals/tr.txt +10 -0
  312. package/src/commands/manuals/trap.txt +10 -0
  313. package/src/commands/manuals/true.txt +10 -0
  314. package/src/commands/manuals/type.txt +10 -0
  315. package/src/commands/manuals/uname.txt +12 -0
  316. package/src/commands/manuals/uniq.txt +12 -0
  317. package/src/commands/manuals/unset.txt +10 -0
  318. package/src/commands/manuals/uptime.txt +11 -0
  319. package/src/commands/manuals/wc.txt +12 -0
  320. package/src/commands/manuals/wget.txt +12 -0
  321. package/src/commands/manuals/which.txt +10 -0
  322. package/src/commands/manuals/whoami.txt +10 -0
  323. package/src/commands/manuals/xargs.txt +10 -0
  324. package/src/commands/mkdir.ts +5 -0
  325. package/src/commands/mv.ts +5 -0
  326. package/src/commands/nano.ts +5 -0
  327. package/src/commands/neofetch.ts +15 -6
  328. package/src/commands/passwd.ts +35 -12
  329. package/src/commands/ping.ts +5 -0
  330. package/src/commands/printf.ts +30 -13
  331. package/src/commands/ps.ts +5 -0
  332. package/src/commands/read.ts +5 -0
  333. package/src/commands/registry.ts +4 -1
  334. package/src/commands/rm.ts +5 -0
  335. package/src/commands/runtime.ts +1 -61
  336. package/src/commands/sed.ts +5 -0
  337. package/src/commands/set.ts +5 -24
  338. package/src/commands/sh.ts +9 -3
  339. package/src/commands/shift.ts +10 -0
  340. package/src/commands/sleep.ts +5 -0
  341. package/src/commands/sort.ts +5 -0
  342. package/src/commands/source.ts +5 -0
  343. package/src/commands/stat.ts +61 -0
  344. package/src/commands/su.ts +54 -16
  345. package/src/commands/sudo.ts +5 -0
  346. package/src/commands/tail.ts +17 -3
  347. package/src/commands/tar.ts +38 -15
  348. package/src/commands/tee.ts +5 -0
  349. package/src/commands/touch.ts +5 -0
  350. package/src/commands/tr.ts +54 -10
  351. package/src/commands/tree.ts +5 -0
  352. package/src/commands/true.ts +10 -0
  353. package/src/commands/type.ts +5 -0
  354. package/src/commands/uname.ts +5 -0
  355. package/src/commands/uniq.ts +5 -0
  356. package/src/commands/unset.ts +5 -0
  357. package/src/commands/uptime.ts +5 -0
  358. package/src/commands/wc.ts +5 -0
  359. package/src/commands/wget.ts +17 -1
  360. package/src/commands/who.ts +5 -0
  361. package/src/commands/whoami.ts +5 -0
  362. package/src/commands/xargs.ts +5 -0
  363. package/src/self-standalone.ts +316 -33
  364. package/src/types/commands.ts +37 -0
  365. package/src/utils/tokenize.ts +78 -0
  366. package/tests/new-features.test.ts +2 -2
  367. package/builds/web-iife.min.js +0 -13
  368. package/builds/web-iife.min.js.map +0 -7
@@ -1,21 +1,98 @@
1
- import type { ShellModule } from "../types/commands";
1
+ import type { CommandResult, ShellModule } from "../types/commands";
2
+ import type { VirtualShell } from "../VirtualShell";
2
3
 
4
+ /**
5
+ * Delete a user. Root-only.
6
+ *
7
+ * Without `-f`: prompts for confirmation — the user must type the exact
8
+ * username to proceed.
9
+ *
10
+ * With `-f` / `--force`: deletes without confirmation.
11
+ *
12
+ * Usage:
13
+ * deluser <username>
14
+ * deluser -f <username>
15
+ */
3
16
  export const deluserCommand: ShellModule = {
4
17
  name: "deluser",
5
18
  description: "Delete a user",
6
19
  category: "users",
7
- params: ["<username>"],
20
+ params: ["[-f] <username>"],
8
21
  run: async ({ authUser, args, shell }) => {
9
22
  if (authUser !== "root") {
10
- return { stderr: "deluser: permission denied", exitCode: 1 };
23
+ return { stderr: "deluser: permission denied\n", exitCode: 1 };
11
24
  }
12
25
 
13
- const [username] = args;
26
+ const force =
27
+ args.includes("-f") ||
28
+ args.includes("--force") ||
29
+ args.includes("-y");
30
+ const username = args.find((a) => !a.startsWith("-"));
31
+
14
32
  if (!username) {
15
- return { stderr: "deluser: usage: deluser <username>", exitCode: 1 };
33
+ return {
34
+ stderr: "Usage: deluser [-f] <username>\n",
35
+ exitCode: 1,
36
+ };
37
+ }
38
+
39
+ if (!shell.users.listUsers().includes(username)) {
40
+ return {
41
+ stderr: `deluser: user '${username}' does not exist\n`,
42
+ exitCode: 1,
43
+ };
44
+ }
45
+
46
+ if (username === "root") {
47
+ return {
48
+ stderr: "deluser: cannot remove the root account\n",
49
+ exitCode: 1,
50
+ };
16
51
  }
17
52
 
18
- await shell.users.deleteUser(username);
19
- return { stdout: `deluser: user '${username}' deleted`, exitCode: 0 };
53
+ // Force mode — delete without confirmation
54
+ if (force) {
55
+ await shell.users.deleteUser(username);
56
+ return {
57
+ stdout: `Removing user '${username}' ...\ndeluser: done.\n`,
58
+ exitCode: 0,
59
+ };
60
+ }
61
+
62
+ // Interactive confirmation
63
+ const onPassword = async (
64
+ input: string,
65
+ sh: VirtualShell,
66
+ ): Promise<{ result: CommandResult | null; nextPrompt?: string }> => {
67
+ if (input.trim() !== username) {
68
+ return {
69
+ result: {
70
+ stderr: "deluser: confirmation did not match — user not deleted\n",
71
+ exitCode: 1,
72
+ },
73
+ };
74
+ }
75
+
76
+ await sh.users.deleteUser(username);
77
+ return {
78
+ result: {
79
+ stdout: `Removing user '${username}' ...\ndeluser: done.\n`,
80
+ exitCode: 0,
81
+ },
82
+ };
83
+ };
84
+
85
+ return {
86
+ sudoChallenge: {
87
+ username,
88
+ targetUser: username,
89
+ commandLine: null,
90
+ loginShell: false,
91
+ prompt: `Warning: deleting user '${username}'.\nType the username to confirm: `,
92
+ mode: "confirm",
93
+ onPassword,
94
+ },
95
+ exitCode: 0,
96
+ };
20
97
  },
21
98
  };
@@ -1,5 +1,10 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
 
3
+ /**
4
+ * Report filesystem disk space usage.
5
+ * @category system
6
+ * @params ["[-h]"]
7
+ */
3
8
  export const dfCommand: ShellModule = {
4
9
  name: "df",
5
10
  description: "Report filesystem disk space usage",
@@ -2,6 +2,11 @@ import type { ShellModule } from "../types/commands";
2
2
  import { ifFlag } from "./command-helpers";
3
3
  import { resolvePath } from "./helpers";
4
4
 
5
+ /**
6
+ * Estimate file and directory space usage.
7
+ * @category system
8
+ * @params ["[-h] [-s] [path]"]
9
+ */
5
10
  export const duCommand: ShellModule = {
6
11
  name: "du",
7
12
  description: "Estimate file space usage",
@@ -1,5 +1,10 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
 
3
+ /**
4
+ * Set or display shell environment variables for child processes.
5
+ * @category shell
6
+ * @params ["[VAR=value]"]
7
+ */
3
8
  export const exportCommand: ShellModule = {
4
9
  name: "export",
5
10
  description: "Set shell environment variable",
@@ -14,12 +14,15 @@ export const grepCommand: ShellModule = {
14
14
  params: ["[-i] [-v] [-n] [-r] <pattern> [file...]"],
15
15
  run: ({ authUser, shell, cwd, args, stdin }) => {
16
16
  const { flags, positionals } = parseArgs(args, {
17
- flags: ["-i", "-v", "-n", "-r"],
17
+ flags: ["-i", "-v", "-n", "-r", "-c", "-l", "-L", "-q", "--quiet", "--silent"],
18
18
  });
19
- const caseInsensitive = flags.has("-i");
20
- const invertMatch = flags.has("-v");
21
- const showLineNumbers = flags.has("-n");
22
- const recursive = flags.has("-r");
19
+ const caseInsensitive = flags.has("-i");
20
+ const invertMatch = flags.has("-v");
21
+ const showLineNumbers = flags.has("-n");
22
+ const recursive = flags.has("-r");
23
+ const countOnly = flags.has("-c");
24
+ const filesWithMatches = flags.has("-l");
25
+ const quiet = flags.has("-q") || flags.has("--quiet") || flags.has("--silent");
23
26
  const pattern = positionals[0];
24
27
  const files = positionals.slice(1);
25
28
 
@@ -73,7 +76,10 @@ export const grepCommand: ShellModule = {
73
76
 
74
77
  if (files.length === 0) {
75
78
  if (!stdin) return { stdout: "", exitCode: 1 };
76
- results.push(...matchLines(stdin));
79
+ const matched = matchLines(stdin);
80
+ if (countOnly) return { stdout: `${matched.length}\n`, exitCode: matched.length > 0 ? 0 : 1 };
81
+ if (quiet) return { exitCode: matched.length > 0 ? 0 : 1 };
82
+ results.push(...matched);
77
83
  } else {
78
84
  const resolvedPaths = files.flatMap((f) => {
79
85
  const target = resolvePath(cwd, f);
@@ -85,7 +91,14 @@ export const grepCommand: ShellModule = {
85
91
  assertPathAccess(authUser, filePath, "grep");
86
92
  const content = shell.vfs.readFile(filePath);
87
93
  const prefix = resolvedPaths.length > 1 ? `${file}:` : "";
88
- results.push(...matchLines(content, prefix));
94
+ const matched = matchLines(content, prefix);
95
+ if (countOnly) {
96
+ results.push(resolvedPaths.length > 1 ? `${file}:${matched.length}` : String(matched.length));
97
+ } else if (filesWithMatches) {
98
+ if (matched.length > 0) results.push(file);
99
+ } else {
100
+ results.push(...matched);
101
+ }
89
102
  } catch {
90
103
  return {
91
104
  stderr: `grep: ${file}: No such file or directory`,
@@ -96,7 +109,7 @@ export const grepCommand: ShellModule = {
96
109
  }
97
110
 
98
111
  return {
99
- stdout: results.length > 0 ? results.join("\n") : "",
112
+ stdout: results.length > 0 ? `${results.join("\n")}\n` : "",
100
113
  exitCode: results.length > 0 ? 0 : 1,
101
114
  };
102
115
  },
@@ -1,5 +1,10 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
 
3
+ /**
4
+ * Print group memberships for a user.
5
+ * @category system
6
+ * @params ["[user]"]
7
+ */
3
8
  export const groupsCommand: ShellModule = {
4
9
  name: "groups",
5
10
  description: "Print group memberships",
@@ -2,54 +2,87 @@ import type { ShellModule } from "../types/commands";
2
2
  import { resolvePath } from "./helpers";
3
3
 
4
4
  /**
5
- * Compress files using gzip (stores in VFS as compressed content).
5
+ * Compress files using gzip renames file to `<file>.gz`, removes original.
6
6
  * @category archive
7
- * @params ["<file>"]
8
7
  */
9
8
  export const gzipCommand: ShellModule = {
10
9
  name: "gzip",
11
10
  description: "Compress files",
12
11
  category: "archive",
13
- params: ["<file>"],
12
+ params: ["[-k] [-d] <file>"],
14
13
  run: ({ shell, cwd, args }) => {
15
- const file = args[0];
16
- if (!file) return { stderr: "gzip: no file specified", exitCode: 1 };
17
- const p = resolvePath(cwd, file);
18
- try {
19
- shell.vfs.compressFile(p);
20
- return { exitCode: 0 };
21
- } catch {
14
+ if (!shell.packageManager.isInstalled("gzip")) {
22
15
  return {
23
- stderr: `gzip: ${file}: No such file or directory`,
24
- exitCode: 1,
16
+ stderr:
17
+ "bash: gzip: command not found\nHint: install it with: apt install gzip\n",
18
+ exitCode: 127,
25
19
  };
26
20
  }
21
+ const keepOrig = args.includes("-k") || args.includes("--keep");
22
+ const decompress = args.includes("-d");
23
+ const file = args.find((a) => !a.startsWith("-"));
24
+ if (!file) return { stderr: "gzip: no file specified\n", exitCode: 1 };
25
+
26
+ const p = resolvePath(cwd, file);
27
+
28
+ if (decompress) {
29
+ // gzip -d = gunzip
30
+ if (!file.endsWith(".gz")) {
31
+ return { stderr: `gzip: ${file}: unknown suffix -- ignored\n`, exitCode: 1 };
32
+ }
33
+ if (!shell.vfs.exists(p)) {
34
+ return { stderr: `gzip: ${file}: No such file or directory\n`, exitCode: 1 };
35
+ }
36
+ const content = shell.vfs.readFile(p);
37
+ const dest = p.slice(0, -3);
38
+ shell.vfs.writeFile(dest, content);
39
+ if (!keepOrig) shell.vfs.remove(p);
40
+ return { exitCode: 0 };
41
+ }
42
+
43
+ if (!shell.vfs.exists(p)) {
44
+ return { stderr: `gzip: ${file}: No such file or directory\n`, exitCode: 1 };
45
+ }
46
+ if (file.endsWith(".gz")) {
47
+ return { stderr: `gzip: ${file}: already has .gz suffix -- unchanged\n`, exitCode: 1 };
48
+ }
49
+
50
+ const rawContent = shell.vfs.readFileRaw(p);
51
+ const gzPath = `${p}.gz`;
52
+ shell.vfs.writeFile(gzPath, rawContent, { compress: true });
53
+ if (!keepOrig) shell.vfs.remove(p);
54
+ return { exitCode: 0 };
27
55
  },
28
56
  };
29
57
 
58
+ /**
59
+ * Decompress gzip files — renames `<file>.gz` to `<file>`, removes original.
60
+ * @category archive
61
+ */
30
62
  export const gunzipCommand: ShellModule = {
31
- /**
32
- * Decompress gzip files (or zcat alias).
33
- * @category archive
34
- * @params ["<file>"]
35
- */
36
63
  name: "gunzip",
37
64
  description: "Decompress files",
38
65
  category: "archive",
39
- params: ["<file>"],
40
66
  aliases: ["zcat"],
67
+ params: ["[-k] <file>"],
41
68
  run: ({ shell, cwd, args }) => {
42
- const file = args[0];
43
- if (!file) return { stderr: "gunzip: no file specified", exitCode: 1 };
69
+ const keepOrig = args.includes("-k") || args.includes("--keep");
70
+ const file = args.find((a) => !a.startsWith("-"));
71
+ if (!file) return { stderr: "gunzip: no file specified\n", exitCode: 1 };
72
+
44
73
  const p = resolvePath(cwd, file);
45
- try {
46
- shell.vfs.decompressFile(p);
47
- return { exitCode: 0 };
48
- } catch {
49
- return {
50
- stderr: `gunzip: ${file}: No such file or directory`,
51
- exitCode: 1,
52
- };
74
+
75
+ if (!shell.vfs.exists(p)) {
76
+ return { stderr: `gunzip: ${file}: No such file or directory\n`, exitCode: 1 };
53
77
  }
78
+ if (!file.endsWith(".gz")) {
79
+ return { stderr: `gunzip: ${file}: unknown suffix -- ignored\n`, exitCode: 1 };
80
+ }
81
+
82
+ const content = shell.vfs.readFile(p);
83
+ const dest = p.slice(0, -3);
84
+ shell.vfs.writeFile(dest, content);
85
+ if (!keepOrig) shell.vfs.remove(p);
86
+ return { exitCode: 0 };
54
87
  },
55
88
  };
@@ -14,11 +14,21 @@ export const headCommand: ShellModule = {
14
14
  params: ["[-n <lines>] [file...]"],
15
15
  run: ({ authUser, shell, cwd, args, stdin }) => {
16
16
  const nArg = getFlag(args, ["-n"]);
17
- const n = typeof nArg === "string" ? parseInt(nArg, 10) : 10;
18
- const positionals = args.filter((a) => !a.startsWith("-") && a !== nArg);
17
+ // Support both -n N and -N shorthand (head -2, head -10)
18
+ const shortN = args.find((a) => /^-\d+$/.test(a));
19
+ const n = typeof nArg === "string"
20
+ ? parseInt(nArg, 10)
21
+ : shortN ? parseInt(shortN.slice(1), 10) : 10;
22
+ const positionals = args.filter(
23
+ (a) => !a.startsWith("-") && a !== nArg && a !== String(n),
24
+ );
19
25
 
20
- const take = (content: string) =>
21
- content.split("\n").slice(0, n).join("\n");
26
+ const take = (content: string) => {
27
+ const lines = content.split("\n");
28
+ // Preserve trailing newline
29
+ const sliced = lines.slice(0, n);
30
+ return sliced.join("\n") + (content.endsWith("\n") && sliced.length === lines.slice(0, n).length ? "\n" : "");
31
+ };
22
32
 
23
33
  if (positionals.length === 0) {
24
34
  return { stdout: take(stdin ?? ""), exitCode: 0 };
@@ -1,5 +1,10 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
 
3
+ /**
4
+ * Interactive system monitor (requires terminal interaction).
5
+ * @category system
6
+ * @params []
7
+ */
3
8
  export const htopCommand: ShellModule = {
4
9
  name: "htop",
5
10
  description: "System monitor",
@@ -1,5 +1,10 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
 
3
+ /**
4
+ * Send a signal to a process by PID.
5
+ * @category system
6
+ * @params ["[-9] <pid>"]
7
+ */
3
8
  export const killCommand: ShellModule = {
4
9
  name: "kill",
5
10
  description: "Send signal to process",
@@ -47,3 +47,25 @@ export const lnCommand: ShellModule = {
47
47
  }
48
48
  },
49
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
+ };
@@ -39,6 +39,23 @@ export const lsCommand: ShellModule = {
39
39
  });
40
40
  const target = resolvePath(cwd, targetArg ?? cwd);
41
41
  assertPathAccess(authUser, target, "ls");
42
+
43
+ // If target is a file, show its info directly (ls -l /etc/passwd)
44
+ if (shell.vfs.exists(target)) {
45
+ const st = shell.vfs.stat(target);
46
+ if (st.type === "file" || shell.vfs.isSymlink(target)) {
47
+ if (longFormat) {
48
+ const name = target.split("/").pop() ?? target;
49
+ const size = st.type === "file" ? st.size : 0;
50
+ return {
51
+ stdout: `${formatPermissions(st.mode, false)} 1 root root ${size} ${formatDate(st.updatedAt)} ${name}\n`,
52
+ exitCode: 0,
53
+ };
54
+ }
55
+ return { stdout: target.split("/").pop() ?? target, exitCode: 0 };
56
+ }
57
+ }
58
+
42
59
  const items = shell.vfs
43
60
  .list(target)
44
61
  .filter((name) => showHidden || !name.startsWith("."));
@@ -1,6 +1,11 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
  import { ifFlag } from "./command-helpers";
3
3
 
4
+ /**
5
+ * Print Linux distribution information.
6
+ * @category system
7
+ * @params ["[-a] [-i] [-d] [-r] [-c]"]
8
+ */
4
9
  export const lsbReleaseCommand: ShellModule = {
5
10
  name: "lsb_release",
6
11
  description: "Print distribution-specific information",
@@ -1,154 +1,49 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
 
3
- const MAN_PAGES: Record<string, string> = {
4
- ls: `LS(1) User Commands LS(1)
5
-
6
- NAME
7
- ls - list directory contents
8
-
9
- SYNOPSIS
10
- ls [OPTION]... [FILE]...
11
-
12
- DESCRIPTION
13
- List information about the FILEs (the current directory by default).
14
-
15
- OPTIONS
16
- -l use a long listing format
17
- -a do not ignore entries starting with .
18
- -h with -l, print human readable sizes
19
- -r reverse order while sorting
20
- -t sort by modification time
21
-
22
- AUTHOR
23
- Written by Richard M. Stallman and David MacKenzie.`,
24
-
25
- cat: `CAT(1) User Commands CAT(1)
26
-
27
- NAME
28
- cat - concatenate files and print on the standard output
29
-
30
- SYNOPSIS
31
- cat [OPTION]... [FILE]...
32
-
33
- DESCRIPTION
34
- Concatenate FILE(s) to standard output.
35
-
36
- OPTIONS
37
- -n, --number number all output lines
38
- -b, --number-nonblank number nonempty output lines`,
39
-
40
- grep: `GREP(1) User Commands GREP(1)
41
-
42
- NAME
43
- grep, egrep, fgrep - print lines that match patterns
44
-
45
- SYNOPSIS
46
- grep [OPTION]... PATTERNS [FILE]...
47
-
48
- OPTIONS
49
- -i, --ignore-case ignore case distinctions in patterns and data
50
- -v, --invert-match select non-matching lines
51
- -n, --line-number print line number with output lines
52
- -r, --recursive read all files under each directory, recursively`,
53
-
54
- apt: `APT(8) APT APT(8)
55
-
56
- NAME
57
- apt - command-line interface
58
-
59
- SYNOPSIS
60
- apt [options] command
61
-
62
- DESCRIPTION
63
- apt provides a high-level commandline interface for the package
64
- management system.
65
-
66
- COMMANDS
67
- install pkg... Install packages
68
- remove pkg... Remove packages
69
- update Download package information
70
- upgrade Upgrade installed packages
71
- search term Search in package descriptions
72
- show pkg Show package information
73
- list List packages`,
74
-
75
- ssh: `SSH(1) OpenSSH SSH(1)
76
-
77
- NAME
78
- ssh - OpenSSH remote login client
79
-
80
- SYNOPSIS
81
- ssh [-p port] [user@]hostname [command]
82
-
83
- DESCRIPTION
84
- ssh (SSH client) is a program for logging into a remote machine and
85
- for executing commands on a remote machine.`,
86
-
87
- curl: `CURL(1) User Commands CURL(1)
88
-
89
- NAME
90
- curl - transfer a URL
91
-
92
- SYNOPSIS
93
- curl [options / URLs]
94
-
95
- DESCRIPTION
96
- curl is a tool for transferring data with URL syntax.
97
-
98
- OPTIONS
99
- -o, --output <file> Write output to <file>
100
- -X, --request <method> Specify request method
101
- -d, --data <data> HTTP POST data
102
- -H, --header <header> Pass custom header
103
- -s, --silent Silent mode
104
- -I, --head Show document info only
105
- -L, --location Follow redirects
106
- -v, --verbose Make the operation more talkative`,
107
-
108
- chmod: `CHMOD(1) User Commands CHMOD(1)
109
-
110
- NAME
111
- chmod - change file mode bits
112
-
113
- SYNOPSIS
114
- chmod [OPTION]... MODE[,MODE]... FILE...
115
- chmod [OPTION]... OCTAL-MODE FILE...
116
-
117
- DESCRIPTION
118
- Change the file mode bits of each given file according to MODE.
119
-
120
- EXAMPLES
121
- chmod 755 script.sh rwxr-xr-x
122
- chmod 644 file.txt rw-r--r--
123
- chmod +x script.sh add execute permission`,
124
-
125
- tar: `TAR(1) GNU tar Manual TAR(1)
126
-
127
- NAME
128
- tar - an archiving utility
129
-
130
- SYNOPSIS
131
- tar [OPTION...] [FILE]...
132
-
133
- DESCRIPTION
134
- tar saves many files together into a single tape or disk archive,
135
- and can restore individual files from the archive.
136
-
137
- OPTIONS
138
- -c, --create create a new archive
139
- -x, --extract extract files from an archive
140
- -z, --gzip filter the archive through gzip
141
- -f, --file=ARCHIVE use archive file or device ARCHIVE
142
- -v, --verbose verbosely list files processed
143
- -t, --list list the contents of an archive`,
3
+ const MANUAL_ALIASES: Record<string, string> = {
4
+ gunzip: "gzip",
144
5
  };
145
6
 
7
+ const manualCache = new Map<string, string | null>();
8
+ const manualsBaseUrl = new URL("./manuals/", import.meta.url);
9
+
10
+ async function dynamicImport(specifier: string): Promise<unknown> {
11
+ const importer = new Function(
12
+ "moduleName",
13
+ "return import(moduleName)",
14
+ ) as (moduleName: string) => Promise<unknown>;
15
+ return importer(specifier);
16
+ }
17
+
18
+ async function loadBundledManual(commandName: string): Promise<string | null> {
19
+ const normalized = commandName.toLowerCase();
20
+ const lookupName = MANUAL_ALIASES[normalized] ?? normalized;
21
+ const cacheKey = `builtin:${lookupName}`;
22
+ if (manualCache.has(cacheKey)) {
23
+ return manualCache.get(cacheKey) ?? null;
24
+ }
25
+
26
+ try {
27
+ const fsModule = (await dynamicImport("node:fs/promises")) as {
28
+ readFile: (path: URL, encoding: "utf8") => Promise<string>;
29
+ };
30
+ const manualUrl = new URL(`${lookupName}.txt`, manualsBaseUrl);
31
+ const content = await fsModule.readFile(manualUrl, "utf8");
32
+ const page = content.replace(/\n$/, "");
33
+ manualCache.set(cacheKey, page);
34
+ return page;
35
+ } catch {
36
+ manualCache.set(cacheKey, null);
37
+ return null;
38
+ }
39
+ }
40
+
146
41
  export const manCommand: ShellModule = {
147
42
  name: "man",
148
43
  description: "Interface to the system reference manuals",
149
44
  category: "shell",
150
45
  params: ["<command>"],
151
- run: ({ args, shell }) => {
46
+ run: async ({ args, shell }) => {
152
47
  const name = args[0];
153
48
  if (!name) return { stderr: "What manual page do you want?", exitCode: 1 };
154
49
 
@@ -158,7 +53,7 @@ export const manCommand: ShellModule = {
158
53
  return { stdout: shell.vfs.readFile(manPath), exitCode: 0 };
159
54
  }
160
55
 
161
- const page = MAN_PAGES[name.toLowerCase()];
56
+ const page = await loadBundledManual(name);
162
57
  if (page) return { stdout: page, exitCode: 0 };
163
58
 
164
59
  return { stderr: `No manual entry for ${name}`, exitCode: 16 };
@@ -0,0 +1,11 @@
1
+ ADDUSER(8) User Commands ADDUSER(8)
2
+
3
+ NAME
4
+ adduser - add a user to the system
5
+
6
+ SYNOPSIS
7
+ adduser USERNAME
8
+
9
+ DESCRIPTION
10
+ Create a new user account with a home directory.
11
+ In this environment, prompts for a password interactively.
@@ -0,0 +1,12 @@
1
+ APT-CACHE(8) APT APT-CACHE(8)
2
+
3
+ NAME
4
+ apt-cache - query APT package cache
5
+
6
+ SYNOPSIS
7
+ apt-cache command [args]
8
+
9
+ COMMANDS
10
+ search term search package names/descriptions
11
+ show pkg show package metadata
12
+ policy pkg show package policy and candidate versions