typescript-virtual-container 1.2.8 → 1.3.0

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 (307) hide show
  1. package/.vscode/settings.json +0 -1
  2. package/README.md +462 -44
  3. package/biome.json +7 -0
  4. package/dist/SSHMimic/exec.d.ts.map +1 -1
  5. package/dist/SSHMimic/executor.d.ts.map +1 -1
  6. package/dist/SSHMimic/executor.js +35 -21
  7. package/dist/SSHMimic/index.d.ts.map +1 -1
  8. package/dist/SSHMimic/index.js +20 -6
  9. package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -1
  10. package/dist/VirtualFileSystem/binaryPack.js +29 -6
  11. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem/index.js +36 -13
  13. package/dist/VirtualPackageManager/index.d.ts +202 -0
  14. package/dist/VirtualPackageManager/index.d.ts.map +1 -0
  15. package/dist/VirtualPackageManager/index.js +825 -0
  16. package/dist/VirtualShell/index.d.ts +93 -12
  17. package/dist/VirtualShell/index.d.ts.map +1 -1
  18. package/dist/VirtualShell/index.js +95 -13
  19. package/dist/VirtualShell/shell.d.ts.map +1 -1
  20. package/dist/VirtualShell/shell.js +3 -1
  21. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  22. package/dist/VirtualUserManager/index.d.ts +52 -20
  23. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  24. package/dist/VirtualUserManager/index.js +54 -20
  25. package/dist/commands/adduser.d.ts +6 -0
  26. package/dist/commands/adduser.d.ts.map +1 -1
  27. package/dist/commands/adduser.js +6 -0
  28. package/dist/commands/alias.d.ts +9 -0
  29. package/dist/commands/alias.d.ts.map +1 -0
  30. package/dist/commands/alias.js +63 -0
  31. package/dist/commands/apt.d.ts +9 -0
  32. package/dist/commands/apt.d.ts.map +1 -0
  33. package/dist/commands/apt.js +205 -0
  34. package/dist/commands/awk.d.ts +11 -0
  35. package/dist/commands/awk.d.ts.map +1 -1
  36. package/dist/commands/awk.js +15 -2
  37. package/dist/commands/base64.d.ts +5 -0
  38. package/dist/commands/base64.d.ts.map +1 -1
  39. package/dist/commands/base64.js +9 -1
  40. package/dist/commands/cat.d.ts +5 -0
  41. package/dist/commands/cat.d.ts.map +1 -1
  42. package/dist/commands/cat.js +35 -8
  43. package/dist/commands/cd.d.ts +5 -0
  44. package/dist/commands/cd.d.ts.map +1 -1
  45. package/dist/commands/cd.js +5 -0
  46. package/dist/commands/chmod.d.ts +5 -0
  47. package/dist/commands/chmod.d.ts.map +1 -1
  48. package/dist/commands/chmod.js +57 -3
  49. package/dist/commands/command-helpers.d.ts +78 -4
  50. package/dist/commands/command-helpers.d.ts.map +1 -1
  51. package/dist/commands/command-helpers.js +78 -4
  52. package/dist/commands/cp.d.ts +5 -0
  53. package/dist/commands/cp.d.ts.map +1 -1
  54. package/dist/commands/cp.js +5 -0
  55. package/dist/commands/curl.d.ts +5 -0
  56. package/dist/commands/curl.d.ts.map +1 -1
  57. package/dist/commands/curl.js +106 -26
  58. package/dist/commands/cut.d.ts +5 -0
  59. package/dist/commands/cut.d.ts.map +1 -1
  60. package/dist/commands/cut.js +8 -1
  61. package/dist/commands/date.d.ts +5 -0
  62. package/dist/commands/date.d.ts.map +1 -1
  63. package/dist/commands/date.js +7 -1
  64. package/dist/commands/declare.d.ts +3 -0
  65. package/dist/commands/declare.d.ts.map +1 -0
  66. package/dist/commands/declare.js +39 -0
  67. package/dist/commands/diff.d.ts +5 -0
  68. package/dist/commands/diff.d.ts.map +1 -1
  69. package/dist/commands/diff.js +5 -0
  70. package/dist/commands/dpkg.d.ts +9 -0
  71. package/dist/commands/dpkg.d.ts.map +1 -0
  72. package/dist/commands/dpkg.js +161 -0
  73. package/dist/commands/du.d.ts.map +1 -1
  74. package/dist/commands/du.js +8 -2
  75. package/dist/commands/echo.d.ts +5 -0
  76. package/dist/commands/echo.d.ts.map +1 -1
  77. package/dist/commands/echo.js +33 -12
  78. package/dist/commands/env.d.ts +5 -0
  79. package/dist/commands/env.d.ts.map +1 -1
  80. package/dist/commands/env.js +11 -1
  81. package/dist/commands/exit.d.ts +5 -0
  82. package/dist/commands/exit.d.ts.map +1 -1
  83. package/dist/commands/exit.js +12 -2
  84. package/dist/commands/export.d.ts.map +1 -1
  85. package/dist/commands/export.js +3 -1
  86. package/dist/commands/find.d.ts +5 -0
  87. package/dist/commands/find.d.ts.map +1 -1
  88. package/dist/commands/find.js +5 -0
  89. package/dist/commands/free.d.ts +8 -0
  90. package/dist/commands/free.d.ts.map +1 -0
  91. package/dist/commands/free.js +43 -0
  92. package/dist/commands/grep.d.ts +5 -0
  93. package/dist/commands/grep.d.ts.map +1 -1
  94. package/dist/commands/grep.js +12 -2
  95. package/dist/commands/gzip.d.ts +5 -0
  96. package/dist/commands/gzip.d.ts.map +1 -1
  97. package/dist/commands/gzip.js +18 -2
  98. package/dist/commands/head.d.ts +5 -0
  99. package/dist/commands/head.d.ts.map +1 -1
  100. package/dist/commands/head.js +5 -0
  101. package/dist/commands/help.d.ts.map +1 -1
  102. package/dist/commands/help.js +98 -45
  103. package/dist/commands/helpers.d.ts +3 -0
  104. package/dist/commands/helpers.d.ts.map +1 -1
  105. package/dist/commands/helpers.js +3 -0
  106. package/dist/commands/history.d.ts +8 -0
  107. package/dist/commands/history.d.ts.map +1 -0
  108. package/dist/commands/history.js +26 -0
  109. package/dist/commands/hostname.d.ts +5 -0
  110. package/dist/commands/hostname.d.ts.map +1 -1
  111. package/dist/commands/hostname.js +5 -0
  112. package/dist/commands/id.d.ts.map +1 -1
  113. package/dist/commands/id.js +4 -1
  114. package/dist/commands/index.d.ts +2 -10
  115. package/dist/commands/index.d.ts.map +1 -1
  116. package/dist/commands/index.js +2 -231
  117. package/dist/commands/ls.d.ts.map +1 -1
  118. package/dist/commands/ls.js +6 -3
  119. package/dist/commands/lsb-release.d.ts +3 -0
  120. package/dist/commands/lsb-release.d.ts.map +1 -0
  121. package/dist/commands/lsb-release.js +56 -0
  122. package/dist/commands/man.d.ts +3 -0
  123. package/dist/commands/man.d.ts.map +1 -0
  124. package/dist/commands/man.js +155 -0
  125. package/dist/commands/nano.js +1 -1
  126. package/dist/commands/neofetch.d.ts.map +1 -1
  127. package/dist/commands/neofetch.js +6 -1
  128. package/dist/commands/node.d.ts +9 -0
  129. package/dist/commands/node.d.ts.map +1 -0
  130. package/dist/commands/node.js +316 -0
  131. package/dist/commands/npm.d.ts +19 -0
  132. package/dist/commands/npm.d.ts.map +1 -0
  133. package/dist/commands/npm.js +109 -0
  134. package/dist/commands/ping.d.ts.map +1 -1
  135. package/dist/commands/ping.js +7 -2
  136. package/dist/commands/printf.d.ts +3 -0
  137. package/dist/commands/printf.d.ts.map +1 -0
  138. package/dist/commands/printf.js +113 -0
  139. package/dist/commands/ps.d.ts.map +1 -1
  140. package/dist/commands/ps.js +30 -6
  141. package/dist/commands/python.d.ts +30 -0
  142. package/dist/commands/python.d.ts.map +1 -0
  143. package/dist/commands/python.js +2058 -0
  144. package/dist/commands/read.d.ts +3 -0
  145. package/dist/commands/read.d.ts.map +1 -0
  146. package/dist/commands/read.js +34 -0
  147. package/dist/commands/registry.d.ts +8 -0
  148. package/dist/commands/registry.d.ts.map +1 -0
  149. package/dist/commands/registry.js +229 -0
  150. package/dist/commands/runtime.d.ts +6 -0
  151. package/dist/commands/runtime.d.ts.map +1 -0
  152. package/dist/commands/runtime.js +280 -0
  153. package/dist/commands/sed.d.ts.map +1 -1
  154. package/dist/commands/sed.js +11 -3
  155. package/dist/commands/set.d.ts.map +1 -1
  156. package/dist/commands/set.js +9 -3
  157. package/dist/commands/sh.d.ts.map +1 -1
  158. package/dist/commands/sh.js +69 -30
  159. package/dist/commands/shift.d.ts +5 -0
  160. package/dist/commands/shift.d.ts.map +1 -0
  161. package/dist/commands/shift.js +52 -0
  162. package/dist/commands/sleep.d.ts.map +1 -1
  163. package/dist/commands/sort.d.ts.map +1 -1
  164. package/dist/commands/sort.js +4 -2
  165. package/dist/commands/source.d.ts +3 -0
  166. package/dist/commands/source.d.ts.map +1 -0
  167. package/dist/commands/source.js +34 -0
  168. package/dist/commands/sudo.js +1 -1
  169. package/dist/commands/tar.d.ts.map +1 -1
  170. package/dist/commands/tar.js +11 -3
  171. package/dist/commands/tee.d.ts.map +1 -1
  172. package/dist/commands/tee.js +8 -6
  173. package/dist/commands/test.d.ts +3 -0
  174. package/dist/commands/test.d.ts.map +1 -0
  175. package/dist/commands/test.js +114 -0
  176. package/dist/commands/tr.d.ts.map +1 -1
  177. package/dist/commands/tr.js +3 -1
  178. package/dist/commands/true.d.ts +4 -0
  179. package/dist/commands/true.d.ts.map +1 -0
  180. package/dist/commands/true.js +14 -0
  181. package/dist/commands/type.d.ts +3 -0
  182. package/dist/commands/type.d.ts.map +1 -0
  183. package/dist/commands/type.js +34 -0
  184. package/dist/commands/uname.d.ts.map +1 -1
  185. package/dist/commands/uname.js +4 -1
  186. package/dist/commands/uniq.d.ts.map +1 -1
  187. package/dist/commands/uptime.d.ts +3 -0
  188. package/dist/commands/uptime.d.ts.map +1 -0
  189. package/dist/commands/uptime.js +43 -0
  190. package/dist/commands/wget.d.ts.map +1 -1
  191. package/dist/commands/wget.js +92 -96
  192. package/dist/commands/which.d.ts +3 -0
  193. package/dist/commands/which.d.ts.map +1 -0
  194. package/dist/commands/which.js +32 -0
  195. package/dist/commands/xargs.d.ts.map +1 -1
  196. package/dist/commands/xargs.js +1 -1
  197. package/dist/index.d.ts +15 -11
  198. package/dist/index.d.ts.map +1 -1
  199. package/dist/index.js +9 -8
  200. package/dist/modules/linuxRootfs.d.ts +41 -0
  201. package/dist/modules/linuxRootfs.d.ts.map +1 -0
  202. package/dist/modules/linuxRootfs.js +440 -0
  203. package/dist/modules/neofetch.d.ts.map +1 -1
  204. package/dist/modules/neofetch.js +1 -0
  205. package/dist/standalone-wo-sftp.d.ts +2 -0
  206. package/dist/standalone-wo-sftp.d.ts.map +1 -0
  207. package/dist/standalone-wo-sftp.js +30 -0
  208. package/dist/utils/expand.d.ts +50 -0
  209. package/dist/utils/expand.d.ts.map +1 -0
  210. package/dist/utils/expand.js +183 -0
  211. package/dist/utils/vfsDiff.d.ts +90 -0
  212. package/dist/utils/vfsDiff.d.ts.map +1 -0
  213. package/dist/utils/vfsDiff.js +177 -0
  214. package/package.json +3 -1
  215. package/src/SSHMimic/exec.ts +10 -1
  216. package/src/SSHMimic/executor.ts +105 -21
  217. package/src/SSHMimic/index.ts +49 -15
  218. package/src/VirtualFileSystem/binaryPack.ts +35 -8
  219. package/src/VirtualFileSystem/index.ts +78 -28
  220. package/src/VirtualPackageManager/index.ts +979 -0
  221. package/src/VirtualShell/index.ts +133 -14
  222. package/src/VirtualShell/shell.ts +23 -3
  223. package/src/VirtualShell/shellParser.ts +134 -36
  224. package/src/VirtualUserManager/index.ts +62 -22
  225. package/src/commands/adduser.ts +6 -0
  226. package/src/commands/alias.ts +64 -0
  227. package/src/commands/apt.ts +228 -0
  228. package/src/commands/awk.ts +20 -6
  229. package/src/commands/base64.ts +13 -2
  230. package/src/commands/cat.ts +40 -8
  231. package/src/commands/cd.ts +5 -0
  232. package/src/commands/chmod.ts +53 -3
  233. package/src/commands/command-helpers.ts +78 -4
  234. package/src/commands/cp.ts +5 -0
  235. package/src/commands/curl.ts +118 -33
  236. package/src/commands/cut.ts +8 -1
  237. package/src/commands/date.ts +7 -1
  238. package/src/commands/declare.ts +44 -0
  239. package/src/commands/diff.ts +17 -3
  240. package/src/commands/dpkg.ts +180 -0
  241. package/src/commands/du.ts +17 -5
  242. package/src/commands/echo.ts +41 -12
  243. package/src/commands/env.ts +11 -1
  244. package/src/commands/exit.ts +12 -2
  245. package/src/commands/export.ts +3 -1
  246. package/src/commands/find.ts +5 -0
  247. package/src/commands/free.ts +47 -0
  248. package/src/commands/grep.ts +12 -2
  249. package/src/commands/gzip.ts +28 -4
  250. package/src/commands/head.ts +5 -0
  251. package/src/commands/help.ts +121 -47
  252. package/src/commands/helpers.ts +8 -0
  253. package/src/commands/history.ts +34 -0
  254. package/src/commands/hostname.ts +5 -0
  255. package/src/commands/id.ts +4 -1
  256. package/src/commands/index.ts +9 -255
  257. package/src/commands/ls.ts +6 -3
  258. package/src/commands/lsb-release.ts +58 -0
  259. package/src/commands/man.ts +166 -0
  260. package/src/commands/nano.ts +1 -1
  261. package/src/commands/neofetch.ts +6 -1
  262. package/src/commands/node.ts +341 -0
  263. package/src/commands/npm.ts +132 -0
  264. package/src/commands/ping.ts +10 -3
  265. package/src/commands/printf.ts +112 -0
  266. package/src/commands/ps.ts +40 -6
  267. package/src/commands/python.ts +2229 -0
  268. package/src/commands/read.ts +41 -0
  269. package/src/commands/registry.ts +244 -0
  270. package/src/commands/runtime.ts +353 -0
  271. package/src/commands/sed.ts +27 -9
  272. package/src/commands/set.ts +9 -3
  273. package/src/commands/sh.ts +170 -44
  274. package/src/commands/shift.ts +53 -0
  275. package/src/commands/sleep.ts +2 -1
  276. package/src/commands/sort.ts +10 -6
  277. package/src/commands/source.ts +47 -0
  278. package/src/commands/sudo.ts +1 -1
  279. package/src/commands/tar.ts +28 -7
  280. package/src/commands/tee.ts +7 -1
  281. package/src/commands/test.ts +135 -0
  282. package/src/commands/tr.ts +3 -1
  283. package/src/commands/true.ts +17 -0
  284. package/src/commands/type.ts +43 -0
  285. package/src/commands/uname.ts +5 -1
  286. package/src/commands/uniq.ts +8 -2
  287. package/src/commands/uptime.ts +49 -0
  288. package/src/commands/wget.ts +105 -119
  289. package/src/commands/which.ts +37 -0
  290. package/src/commands/xargs.ts +11 -2
  291. package/src/index.ts +27 -18
  292. package/src/modules/linuxRootfs.ts +642 -0
  293. package/src/modules/neofetch.ts +1 -0
  294. package/src/standalone-wo-sftp.ts +38 -0
  295. package/src/utils/expand.ts +238 -0
  296. package/src/utils/vfsDiff.ts +275 -0
  297. package/standalone-wo-sftp.js +507 -0
  298. package/standalone-wo-sftp.js.map +7 -0
  299. package/standalone.js +486 -109
  300. package/standalone.js.map +4 -4
  301. package/tests/bun-test-shim.ts +9 -1
  302. package/tests/command-helpers.test.ts +1 -5
  303. package/tests/new-features.test.ts +1036 -0
  304. package/tests/parser-executor.test.ts +27 -27
  305. package/tests/sftp.test.ts +122 -42
  306. package/tests/users.test.ts +23 -5
  307. package/CHANGELOG.md +0 -150
@@ -0,0 +1,47 @@
1
+ import * as os from "node:os";
2
+ import type { ShellModule } from "../types/commands";
3
+ import { ifFlag } from "./command-helpers";
4
+
5
+ /**
6
+ * Display memory usage information (human / MB / GB options).
7
+ * @category system
8
+ * @params ["[-h] [-m] [-g]"]
9
+ */
10
+ export const freeCommand: ShellModule = {
11
+ name: "free",
12
+ description: "Display amount of free and used memory",
13
+ category: "system",
14
+ params: ["[-h] [-m] [-g]"],
15
+ run: ({ args }) => {
16
+ const human = ifFlag(args, ["-h", "--human"]);
17
+ const mb = ifFlag(args, ["-m"]);
18
+ const gb = ifFlag(args, ["-g"]);
19
+
20
+ const osTotalB = os.totalmem();
21
+ const osFreeB = os.freemem();
22
+ const usedB = osTotalB - osFreeB;
23
+ const sharedB = Math.floor(osTotalB * 0.02);
24
+ const buffersB = Math.floor(osTotalB * 0.05);
25
+ const availableB = Math.floor(osFreeB * 0.95);
26
+ const swapB = Math.floor(osTotalB * 0.5);
27
+
28
+ const fmt = (bytes: number): string => {
29
+ if (human) {
30
+ if (bytes >= 1024 * 1024 * 1024)
31
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
32
+ if (bytes >= 1024 * 1024)
33
+ return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
34
+ return `${(bytes / 1024).toFixed(1)}K`;
35
+ }
36
+ if (gb) return String(Math.floor(bytes / (1024 * 1024 * 1024)));
37
+ if (mb) return String(Math.floor(bytes / (1024 * 1024)));
38
+ return String(Math.floor(bytes / 1024));
39
+ };
40
+
41
+ const header = ` total used free shared buff/cache available`;
42
+ const memRow = `Mem: ${fmt(osTotalB).padStart(12)} ${fmt(usedB).padStart(11)} ${fmt(osFreeB).padStart(11)} ${fmt(sharedB).padStart(11)} ${fmt(buffersB).padStart(11)} ${fmt(availableB).padStart(11)}`;
43
+ const swapRow = `Swap: ${fmt(swapB).padStart(12)} ${fmt(0).padStart(11)} ${fmt(swapB).padStart(11)}`;
44
+
45
+ return { stdout: [header, memRow, swapRow].join("\n"), exitCode: 0 };
46
+ },
47
+ };
@@ -2,13 +2,20 @@ import type { ShellModule } from "../types/commands";
2
2
  import { parseArgs } from "./command-helpers";
3
3
  import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
5
+ /**
6
+ * Search for a regex pattern in files or stdin with common flags.
7
+ * @category text
8
+ * @params ["[-i] [-v] [-n] [-r] <pattern> [file...]"]
9
+ */
5
10
  export const grepCommand: ShellModule = {
6
11
  name: "grep",
7
12
  description: "Search text patterns",
8
13
  category: "text",
9
14
  params: ["[-i] [-v] [-n] [-r] <pattern> [file...]"],
10
15
  run: ({ authUser, shell, cwd, args, stdin }) => {
11
- const { flags, positionals } = parseArgs(args, { flags: ["-i", "-v", "-n", "-r"] });
16
+ const { flags, positionals } = parseArgs(args, {
17
+ flags: ["-i", "-v", "-n", "-r"],
18
+ });
12
19
  const caseInsensitive = flags.has("-i");
13
20
  const invertMatch = flags.has("-v");
14
21
  const showLineNumbers = flags.has("-n");
@@ -80,7 +87,10 @@ export const grepCommand: ShellModule = {
80
87
  const prefix = resolvedPaths.length > 1 ? `${file}:` : "";
81
88
  results.push(...matchLines(content, prefix));
82
89
  } catch {
83
- return { stderr: `grep: ${file}: No such file or directory`, exitCode: 1 };
90
+ return {
91
+ stderr: `grep: ${file}: No such file or directory`,
92
+ exitCode: 1,
93
+ };
84
94
  }
85
95
  }
86
96
  }
@@ -1,6 +1,11 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
  import { resolvePath } from "./helpers";
3
3
 
4
+ /**
5
+ * Compress files using gzip (stores in VFS as compressed content).
6
+ * @category archive
7
+ * @params ["<file>"]
8
+ */
4
9
  export const gzipCommand: ShellModule = {
5
10
  name: "gzip",
6
11
  description: "Compress files",
@@ -10,12 +15,24 @@ export const gzipCommand: ShellModule = {
10
15
  const file = args[0];
11
16
  if (!file) return { stderr: "gzip: no file specified", exitCode: 1 };
12
17
  const p = resolvePath(cwd, file);
13
- try { shell.vfs.compressFile(p); return { exitCode: 0 }; }
14
- catch { return { stderr: `gzip: ${file}: No such file or directory`, exitCode: 1 }; }
18
+ try {
19
+ shell.vfs.compressFile(p);
20
+ return { exitCode: 0 };
21
+ } catch {
22
+ return {
23
+ stderr: `gzip: ${file}: No such file or directory`,
24
+ exitCode: 1,
25
+ };
26
+ }
15
27
  },
16
28
  };
17
29
 
18
30
  export const gunzipCommand: ShellModule = {
31
+ /**
32
+ * Decompress gzip files (or zcat alias).
33
+ * @category archive
34
+ * @params ["<file>"]
35
+ */
19
36
  name: "gunzip",
20
37
  description: "Decompress files",
21
38
  category: "archive",
@@ -25,7 +42,14 @@ export const gunzipCommand: ShellModule = {
25
42
  const file = args[0];
26
43
  if (!file) return { stderr: "gunzip: no file specified", exitCode: 1 };
27
44
  const p = resolvePath(cwd, file);
28
- try { shell.vfs.decompressFile(p); return { exitCode: 0 }; }
29
- catch { return { stderr: `gunzip: ${file}: No such file or directory`, exitCode: 1 }; }
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
+ };
53
+ }
30
54
  },
31
55
  };
@@ -2,6 +2,11 @@ import type { ShellModule } from "../types/commands";
2
2
  import { getFlag } from "./command-helpers";
3
3
  import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
5
+ /**
6
+ * Output the first part of files or stdin (head).
7
+ * @category text
8
+ * @params ["[-n <lines>] [file...]"]
9
+ */
5
10
  export const headCommand: ShellModule = {
6
11
  name: "head",
7
12
  description: "Output first lines",
@@ -1,78 +1,152 @@
1
1
  import type { ShellModule } from "../types/commands";
2
- import { getCommandModulesPublic } from "./index";
2
+ import { getCommandModulesPublic } from "./registry";
3
+
4
+ // ─── category config ──────────────────────────────────────────────────────────
5
+
6
+ const CATEGORY_ORDER = [
7
+ "navigation",
8
+ "files",
9
+ "text",
10
+ "archive",
11
+ "system",
12
+ "package",
13
+ "network",
14
+ "shell",
15
+ "users",
16
+ "misc",
17
+ ];
3
18
 
4
- const CATEGORY_ORDER = ["navigation", "files", "text", "archive", "system", "network", "shell", "users", "misc"];
5
19
  const CATEGORY_LABELS: Record<string, string> = {
6
20
  navigation: "Navigation",
7
21
  files: "Files & Filesystem",
8
22
  text: "Text Processing",
9
23
  archive: "Archive & Compression",
10
24
  system: "System",
25
+ package: "Package Management",
11
26
  network: "Network",
12
- shell: "Shell",
27
+ shell: "Shell & Scripting",
13
28
  users: "Users & Permissions",
14
29
  misc: "Miscellaneous",
15
30
  };
16
31
 
17
- function padRight(s: string, n: number): string {
32
+ // ─── formatting helpers ───────────────────────────────────────────────────────
33
+
34
+ const BOLD = "\x1b[1m";
35
+ const RESET = "\x1b[0m";
36
+ const CYAN = "\x1b[36m";
37
+ const YLW = "\x1b[33m";
38
+ const DIM = "\x1b[2m";
39
+ const GREEN = "\x1b[32m";
40
+
41
+ function pad(s: string, n: number): string {
18
42
  return s.length >= n ? s : s + " ".repeat(n - s.length);
19
43
  }
20
44
 
45
+ function formatCmdLine(mod: ShellModule): string {
46
+ const aliases = mod.aliases?.length
47
+ ? ` ${DIM}(${mod.aliases.join(", ")})${RESET}`
48
+ : "";
49
+ return ` ${CYAN}${pad(mod.name, 16)}${RESET}${aliases}${pad("", mod.aliases?.length ? 0 : 0)} ${mod.description ?? ""}`;
50
+ }
51
+
52
+ // ─── full grouped listing ─────────────────────────────────────────────────────
53
+
54
+ function renderFull(modules: ShellModule[]): string {
55
+ const grouped: Record<string, ShellModule[]> = {};
56
+ for (const mod of modules) {
57
+ const cat = mod.category ?? "misc";
58
+ if (!grouped[cat]) grouped[cat] = [];
59
+ grouped[cat]!.push(mod);
60
+ }
61
+
62
+ const lines: string[] = [
63
+ `${BOLD}Available commands${RESET}`,
64
+ `${DIM}Type 'help <command>' for detailed usage.${RESET}`,
65
+ "",
66
+ ];
67
+
68
+ const cats = [
69
+ ...CATEGORY_ORDER.filter((c) => grouped[c]),
70
+ ...Object.keys(grouped)
71
+ .filter((c) => !CATEGORY_ORDER.includes(c))
72
+ .sort(),
73
+ ];
74
+
75
+ for (const cat of cats) {
76
+ const mods = grouped[cat];
77
+ if (!mods?.length) continue;
78
+
79
+ lines.push(`${YLW}${CATEGORY_LABELS[cat] ?? cat}${RESET}`);
80
+ const sorted = [...mods].sort((a, b) => a.name.localeCompare(b.name));
81
+ for (const mod of sorted) {
82
+ lines.push(formatCmdLine(mod));
83
+ }
84
+ lines.push("");
85
+ }
86
+
87
+ const total = modules.length;
88
+ lines.push(`${DIM}${total} commands available.${RESET}`);
89
+
90
+ return lines.join("\n");
91
+ }
92
+
93
+ // ─── single-command detail ────────────────────────────────────────────────────
94
+
95
+ function renderDetail(mod: ShellModule): string {
96
+ const lines: string[] = [];
97
+
98
+ lines.push(
99
+ `${BOLD}${mod.name}${RESET} — ${mod.description ?? "no description"}`,
100
+ );
101
+
102
+ if (mod.aliases?.length) {
103
+ lines.push(`${DIM}Aliases: ${mod.aliases.join(", ")}${RESET}`);
104
+ }
105
+
106
+ lines.push("");
107
+ lines.push(`${GREEN}Usage:${RESET}`);
108
+ if (mod.params.length) {
109
+ for (const p of mod.params) {
110
+ lines.push(` ${mod.name} ${p}`);
111
+ }
112
+ } else {
113
+ lines.push(` ${mod.name}`);
114
+ }
115
+
116
+ const catLabel =
117
+ CATEGORY_LABELS[mod.category ?? "misc"] ?? mod.category ?? "misc";
118
+ lines.push("");
119
+ lines.push(`${DIM}Category: ${catLabel}${RESET}`);
120
+
121
+ return lines.join("\n");
122
+ }
123
+
124
+ // ─── export ───────────────────────────────────────────────────────────────────
125
+
21
126
  export function createHelpCommand(_getNames: () => string[]): ShellModule {
22
127
  return {
23
128
  name: "help",
24
- description: "Display this help message",
129
+ description: "List all commands, or show usage for a specific command",
25
130
  category: "shell",
26
131
  params: ["[command]"],
27
132
  run: ({ args }) => {
28
133
  const modules = getCommandModulesPublic();
29
134
 
30
- // help <command>
31
135
  if (args[0]) {
32
- const mod = modules.find((m) => m.name === args[0] || m.aliases?.includes(args[0]!));
33
- if (!mod) return { stderr: `help: no help for '${args[0]}'`, exitCode: 1 };
34
- const aliases = mod.aliases?.length ? ` aliases: ${mod.aliases.join(", ")}\n` : "";
35
- const params = mod.params.map((p) => ` ${mod.name} ${p}`).join("\n");
36
- return {
37
- stdout: [
38
- `\x1b[1m${mod.name}\x1b[0m ${mod.description ?? "no description"}`,
39
- aliases,
40
- "Usage:",
41
- params || ` ${mod.name}`,
42
- ].filter(Boolean).join("\n"),
43
- exitCode: 0,
44
- };
45
- }
46
-
47
- // Full help — grouped by category
48
- const grouped: Record<string, ShellModule[]> = {};
49
- for (const mod of modules) {
50
- const cat = mod.category ?? "misc";
51
- if (!grouped[cat]) grouped[cat] = [];
52
- grouped[cat]!.push(mod);
53
- }
54
-
55
- const lines: string[] = [];
56
- lines.push("\x1b[1mAvailable commands\x1b[0m");
57
- lines.push("");
58
-
59
- const cats = [
60
- ...CATEGORY_ORDER.filter((c) => grouped[c]),
61
- ...Object.keys(grouped).filter((c) => !CATEGORY_ORDER.includes(c)),
62
- ];
63
-
64
- for (const cat of cats) {
65
- const mods = grouped[cat];
66
- if (!mods || mods.length === 0) continue;
67
- lines.push(`\x1b[33m${CATEGORY_LABELS[cat] ?? cat}\x1b[0m`);
68
-
69
- const sorted = [...mods].sort((a, b) => a.name.localeCompare(b.name));
70
- for (const mod of sorted) {
71
- lines.push(` \x1b[36m${padRight(mod.name, 14)}\x1b[0m ${mod.description ?? ""}`);
136
+ const target = args[0].toLowerCase();
137
+ const mod = modules.find(
138
+ (m) => m.name === target || m.aliases?.includes(target),
139
+ );
140
+ if (!mod) {
141
+ return {
142
+ stderr: `help: no help entry for '${args[0]}'`,
143
+ exitCode: 1,
144
+ };
72
145
  }
146
+ return { stdout: renderDetail(mod), exitCode: 0 };
73
147
  }
74
148
 
75
- return { stdout: lines.join("\n"), exitCode: 0 };
149
+ return { stdout: renderFull(modules), exitCode: 0 };
76
150
  },
77
151
  };
78
152
  }
@@ -1,6 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import * as path from "node:path";
3
3
  import type VirtualFileSystem from "../VirtualFileSystem";
4
+ import type { VirtualPackageManager } from "../VirtualPackageManager";
5
+ import type { VirtualShell } from "../VirtualShell";
4
6
 
5
7
  const PROTECTED_PREFIXES = ["/virtual-env-js/.auth"] as const;
6
8
 
@@ -220,3 +222,9 @@ export function joinListWithType(
220
222
  })
221
223
  .join(" ");
222
224
  }
225
+
226
+ export function getPackageManager(
227
+ shell: VirtualShell,
228
+ ): VirtualPackageManager | undefined {
229
+ return shell.packageManager;
230
+ }
@@ -0,0 +1,34 @@
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 }) => {
14
+ // History is persisted in the VFS by the interactive shell
15
+ const histPath = "/virtual-env-js/.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,5 +1,10 @@
1
1
  import type { ShellModule } from "../types/commands";
2
2
 
3
+ /**
4
+ * Print the configured hostname for the virtual shell.
5
+ * @category system
6
+ * @params []
7
+ */
3
8
  export const hostnameCommand: ShellModule = {
4
9
  name: "hostname",
5
10
  description: "Print hostname",
@@ -11,6 +11,9 @@ export const idCommand: ShellModule = {
11
11
  const gid = uid;
12
12
  const isSudo = shell.users.isSudoer(target);
13
13
  const groups = isSudo ? `${gid}(${target}),0(root)` : `${gid}(${target})`;
14
- return { stdout: `uid=${uid}(${target}) gid=${gid}(${target}) groups=${groups}`, exitCode: 0 };
14
+ return {
15
+ stdout: `uid=${uid}(${target}) gid=${gid}(${target}) groups=${groups}`,
16
+ exitCode: 0,
17
+ };
15
18
  },
16
19
  };
@@ -1,255 +1,9 @@
1
- /** biome-ignore-all lint/style/useNamingConvention: ENV VARIABLES */
2
- import type { VirtualShell } from "../VirtualShell";
3
- import type {
4
- CommandContext,
5
- CommandMode,
6
- CommandResult,
7
- ShellEnv,
8
- ShellModule,
9
- } from "../types/commands";
10
- import { adduserCommand } from "./adduser";
11
- import { awkCommand } from "./awk";
12
- import { base64Command } from "./base64";
13
- import { catCommand } from "./cat";
14
- import { cdCommand } from "./cd";
15
- import { chmodCommand } from "./chmod";
16
- import { clearCommand } from "./clear";
17
- import { cpCommand } from "./cp";
18
- import { curlCommand } from "./curl";
19
- import { cutCommand } from "./cut";
20
- import { dateCommand } from "./date";
21
- import { deluserCommand } from "./deluser";
22
- import { dfCommand } from "./df";
23
- import { diffCommand } from "./diff";
24
- import { duCommand } from "./du";
25
- import { echoCommand } from "./echo";
26
- import { envCommand } from "./env";
27
- import { exitCommand } from "./exit";
28
- import { exportCommand } from "./export";
29
- import { findCommand } from "./find";
30
- import { grepCommand } from "./grep";
31
- import { groupsCommand } from "./groups";
32
- import { gunzipCommand, gzipCommand } from "./gzip";
33
- import { headCommand } from "./head";
34
- import { createHelpCommand } from "./help";
35
- import { hostnameCommand } from "./hostname";
36
- import { htopCommand } from "./htop";
37
- import { idCommand } from "./id";
38
- import { killCommand } from "./kill";
39
- import { lnCommand } from "./ln";
40
- import { lsCommand } from "./ls";
41
- import { mkdirCommand } from "./mkdir";
42
- import { mvCommand } from "./mv";
43
- import { nanoCommand } from "./nano";
44
- import { neofetchCommand } from "./neofetch";
45
- import { passwdCommand } from "./passwd";
46
- import { pingCommand } from "./ping";
47
- import { psCommand } from "./ps";
48
- import { pwdCommand } from "./pwd";
49
- import { rmCommand } from "./rm";
50
- import { sedCommand } from "./sed";
51
- import { setCommand } from "./set";
52
- import { shCommand } from "./sh";
53
- import { sleepCommand } from "./sleep";
54
- import { sortCommand } from "./sort";
55
- import { suCommand } from "./su";
56
- import { sudoCommand } from "./sudo";
57
- import { tailCommand } from "./tail";
58
- import { tarCommand } from "./tar";
59
- import { teeCommand } from "./tee";
60
- import { touchCommand } from "./touch";
61
- import { trCommand } from "./tr";
62
- import { treeCommand } from "./tree";
63
- import { unameCommand } from "./uname";
64
- import { uniqCommand } from "./uniq";
65
- import { unsetCommand } from "./unset";
66
- import { wcCommand } from "./wc";
67
- import { wgetCommand } from "./wget";
68
- import { whoCommand } from "./who";
69
- import { whoamiCommand } from "./whoami";
70
- import { xargsCommand } from "./xargs";
71
-
72
- const BASE_COMMANDS: ShellModule[] = [
73
- // Navigation
74
- pwdCommand, cdCommand, lsCommand, treeCommand,
75
- // Files
76
- catCommand, touchCommand, rmCommand, mkdirCommand, cpCommand, mvCommand, lnCommand,
77
- chmodCommand, findCommand,
78
- // Text processing
79
- grepCommand, sedCommand, awkCommand, sortCommand, uniqCommand, wcCommand,
80
- headCommand, tailCommand, cutCommand, trCommand, teeCommand, xargsCommand,
81
- diffCommand,
82
- // Archives
83
- tarCommand, gzipCommand, gunzipCommand, base64Command,
84
- // System info
85
- whoamiCommand, whoCommand, hostnameCommand, idCommand, groupsCommand, unameCommand,
86
- psCommand, killCommand, dfCommand, duCommand, dateCommand, sleepCommand, pingCommand,
87
- // Shell
88
- echoCommand, envCommand, exportCommand, setCommand, unsetCommand, shCommand,
89
- clearCommand, exitCommand,
90
- // Editors
91
- nanoCommand, htopCommand,
92
- // Network
93
- curlCommand, wgetCommand,
94
- // Users
95
- adduserCommand, passwdCommand, deluserCommand, sudoCommand, suCommand,
96
- // Misc
97
- neofetchCommand,
98
- ];
99
-
100
- const customCommands: ShellModule[] = [];
101
- const commandRegistry = new Map<string, ShellModule>();
102
- let cachedCommandNames: string[] | null = null;
103
-
104
- const helpCommand = createHelpCommand(() => getCommandModules().map((cmd) => cmd.name));
105
-
106
- function buildCache(): void {
107
- commandRegistry.clear();
108
- for (const mod of getCommandModules()) {
109
- commandRegistry.set(mod.name, mod);
110
- for (const alias of mod.aliases ?? []) commandRegistry.set(alias, mod);
111
- }
112
- cachedCommandNames = Array.from(commandRegistry.keys()).sort();
113
- }
114
-
115
- function getCommandModules(): ShellModule[] {
116
- return [...BASE_COMMANDS, ...customCommands, helpCommand];
117
- }
118
-
119
- export function registerCommand(module: ShellModule): void {
120
- const normalized: ShellModule = {
121
- ...module,
122
- name: module.name.trim().toLowerCase(),
123
- aliases: module.aliases?.map((a) => a.trim().toLowerCase()),
124
- };
125
- const names = [normalized.name, ...(normalized.aliases ?? [])];
126
- if (names.some((n) => n.length === 0 || /\s/.test(n))) {
127
- throw new Error("Command names must be non-empty and contain no spaces");
128
- }
129
- customCommands.push(normalized);
130
- buildCache();
131
- }
132
-
133
- export function createCustomCommand(
134
- name: string,
135
- params: string[],
136
- run: (ctx: CommandContext) => CommandResult | Promise<CommandResult>,
137
- ): ShellModule {
138
- return { name, params, run };
139
- }
140
-
141
- export function getCommandNames(): string[] {
142
- if (!cachedCommandNames) buildCache();
143
- return cachedCommandNames!;
144
- }
145
-
146
- export function getCommandModulesPublic(): ShellModule[] {
147
- return getCommandModules();
148
- }
149
-
150
- export function resolveModule(name: string): ShellModule | undefined {
151
- if (!cachedCommandNames) buildCache();
152
- return commandRegistry.get(name.toLowerCase());
153
- }
154
-
155
- function splitArgsRespectingQuotes(input: string): string[] {
156
- const tokens: string[] = [];
157
- let current = "";
158
- let inQuotes = false;
159
- let quoteChar = "";
160
-
161
- for (let i = 0; i < input.length; i++) {
162
- const ch = input[i] || "";
163
- const prev = i > 0 ? input[i - 1] : "";
164
- if ((ch === '"' || ch === "'") && prev !== "\\") {
165
- if (!inQuotes) { inQuotes = true; quoteChar = ch; continue; }
166
- if (ch === quoteChar) { inQuotes = false; quoteChar = ""; continue; }
167
- }
168
- if (/\s/.test(ch) && !inQuotes) {
169
- if (current.length > 0) { tokens.push(current); current = ""; }
170
- continue;
171
- }
172
- current += ch;
173
- }
174
- if (current.length > 0) tokens.push(current);
175
- return tokens;
176
- }
177
-
178
- function parseInput(rawInput: string): { commandName: string; args: string[] } {
179
- const parts = splitArgsRespectingQuotes(rawInput.trim());
180
- return { commandName: parts[0]?.toLowerCase() ?? "", args: parts.slice(1) };
181
- }
182
-
183
- export function makeDefaultEnv(authUser: string, hostname: string): ShellEnv {
184
- return {
185
- vars: {
186
- PATH: "/usr/local/bin:/usr/bin:/bin",
187
- HOME: `/home/${authUser}`,
188
- USER: authUser,
189
- LOGNAME: authUser,
190
- SHELL: "/bin/sh",
191
- TERM: "xterm-256color",
192
- HOSTNAME: hostname,
193
- PS1: "\\u@\\h:\\w\\$ ",
194
- },
195
- lastExitCode: 0,
196
- };
197
- }
198
-
199
- export async function runCommand(
200
- rawInput: string,
201
- authUser: string,
202
- hostname: string,
203
- mode: CommandMode,
204
- cwd: string,
205
- shell: VirtualShell,
206
- stdin?: string,
207
- env?: ShellEnv,
208
- ): Promise<CommandResult> {
209
- const trimmed = rawInput.trim();
210
- if (trimmed.length === 0) return { exitCode: 0 };
211
-
212
- const shellEnv: ShellEnv = env ?? makeDefaultEnv(authUser, hostname);
213
-
214
- // Detect shell operators
215
- if (
216
- /(?<![|&])[|](?![|])/.test(trimmed) ||
217
- trimmed.includes(">") ||
218
- trimmed.includes("<") ||
219
- trimmed.includes("&&") ||
220
- trimmed.includes("||") ||
221
- trimmed.includes(";")
222
- ) {
223
- const { parseScript } = await import("../VirtualShell/shellParser");
224
- const { executeStatements } = await import("../SSHMimic/executor");
225
- const script = parseScript(trimmed);
226
- if (!script.isValid) return { stderr: script.error || "Syntax error", exitCode: 1 };
227
- try {
228
- return await executeStatements(script.statements, authUser, hostname, mode, cwd, shell, shellEnv);
229
- } catch (error: unknown) {
230
- return { stderr: error instanceof Error ? error.message : "Execution failed", exitCode: 1 };
231
- }
232
- }
233
-
234
- const { commandName, args } = parseInput(trimmed);
235
- const mod = resolveModule(commandName);
236
-
237
- if (!mod) return { stderr: `${commandName}: command not found`, exitCode: 127 };
238
-
239
- try {
240
- return await mod.run({
241
- authUser,
242
- hostname,
243
- activeSessions: shell.users.listActiveSessions(),
244
- rawInput: trimmed,
245
- mode,
246
- args,
247
- stdin,
248
- cwd,
249
- shell,
250
- env: shellEnv,
251
- });
252
- } catch (error: unknown) {
253
- return { stderr: error instanceof Error ? error.message : "Command failed", exitCode: 1 };
254
- }
255
- }
1
+ export {
2
+ createCustomCommand,
3
+ getCommandModulesPublic,
4
+ getCommandNames,
5
+ registerCommand,
6
+ resolveModule
7
+ } from "./registry";
8
+
9
+ export { makeDefaultEnv, runCommand, runCommandDirect } from "./runtime";