typescript-virtual-container 1.2.5 → 1.2.6

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 (245) hide show
  1. package/README.md +387 -193
  2. package/benchmark-results.txt +21 -21
  3. package/dist/SSHMimic/exec.js +2 -2
  4. package/dist/SSHMimic/executor.d.ts +6 -7
  5. package/dist/SSHMimic/executor.d.ts.map +1 -1
  6. package/dist/SSHMimic/executor.js +77 -60
  7. package/dist/SSHMimic/index.d.ts.map +1 -1
  8. package/dist/SSHMimic/index.js +6 -20
  9. package/dist/SSHMimic/sftp.d.ts.map +1 -1
  10. package/dist/SSHMimic/sftp.js +14 -0
  11. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem/index.js +13 -36
  13. package/dist/VirtualShell/shell.d.ts.map +1 -1
  14. package/dist/VirtualShell/shell.js +19 -2
  15. package/dist/VirtualShell/shellParser.d.ts +20 -2
  16. package/dist/VirtualShell/shellParser.d.ts.map +1 -1
  17. package/dist/VirtualShell/shellParser.js +229 -120
  18. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  19. package/dist/commands/adduser.d.ts.map +1 -1
  20. package/dist/commands/adduser.js +2 -0
  21. package/dist/commands/awk.d.ts +3 -0
  22. package/dist/commands/awk.d.ts.map +1 -0
  23. package/dist/commands/awk.js +29 -0
  24. package/dist/commands/base64.d.ts +3 -0
  25. package/dist/commands/base64.d.ts.map +1 -0
  26. package/dist/commands/base64.js +20 -0
  27. package/dist/commands/cat.d.ts.map +1 -1
  28. package/dist/commands/cat.js +2 -0
  29. package/dist/commands/cd.d.ts.map +1 -1
  30. package/dist/commands/cd.js +2 -0
  31. package/dist/commands/chmod.d.ts.map +1 -1
  32. package/dist/commands/chmod.js +2 -0
  33. package/dist/commands/clear.d.ts.map +1 -1
  34. package/dist/commands/clear.js +4 -1
  35. package/dist/commands/cp.d.ts.map +1 -1
  36. package/dist/commands/cp.js +2 -0
  37. package/dist/commands/curl.d.ts.map +1 -1
  38. package/dist/commands/curl.js +2 -0
  39. package/dist/commands/cut.d.ts +3 -0
  40. package/dist/commands/cut.d.ts.map +1 -0
  41. package/dist/commands/cut.js +27 -0
  42. package/dist/commands/date.d.ts +3 -0
  43. package/dist/commands/date.d.ts.map +1 -0
  44. package/dist/commands/date.js +22 -0
  45. package/dist/commands/deluser.d.ts.map +1 -1
  46. package/dist/commands/deluser.js +2 -0
  47. package/dist/commands/df.d.ts +3 -0
  48. package/dist/commands/df.d.ts.map +1 -0
  49. package/dist/commands/df.js +16 -0
  50. package/dist/commands/diff.d.ts +3 -0
  51. package/dist/commands/diff.d.ts.map +1 -0
  52. package/dist/commands/diff.js +40 -0
  53. package/dist/commands/du.d.ts +3 -0
  54. package/dist/commands/du.d.ts.map +1 -0
  55. package/dist/commands/du.js +39 -0
  56. package/dist/commands/echo.d.ts.map +1 -1
  57. package/dist/commands/echo.js +2 -0
  58. package/dist/commands/env.d.ts.map +1 -1
  59. package/dist/commands/env.js +6 -14
  60. package/dist/commands/export.d.ts.map +1 -1
  61. package/dist/commands/export.js +11 -21
  62. package/dist/commands/find.d.ts.map +1 -1
  63. package/dist/commands/find.js +2 -0
  64. package/dist/commands/grep.d.ts.map +1 -1
  65. package/dist/commands/grep.js +4 -7
  66. package/dist/commands/groups.d.ts +3 -0
  67. package/dist/commands/groups.d.ts.map +1 -0
  68. package/dist/commands/groups.js +12 -0
  69. package/dist/commands/gzip.d.ts +4 -0
  70. package/dist/commands/gzip.d.ts.map +1 -0
  71. package/dist/commands/gzip.js +40 -0
  72. package/dist/commands/head.d.ts.map +1 -1
  73. package/dist/commands/head.js +2 -0
  74. package/dist/commands/help.d.ts +1 -1
  75. package/dist/commands/help.d.ts.map +1 -1
  76. package/dist/commands/help.js +75 -3
  77. package/dist/commands/hostname.d.ts.map +1 -1
  78. package/dist/commands/hostname.js +2 -0
  79. package/dist/commands/htop.d.ts.map +1 -1
  80. package/dist/commands/htop.js +2 -0
  81. package/dist/commands/id.d.ts +3 -0
  82. package/dist/commands/id.d.ts.map +1 -0
  83. package/dist/commands/id.js +14 -0
  84. package/dist/commands/index.d.ts +5 -2
  85. package/dist/commands/index.d.ts.map +1 -1
  86. package/dist/commands/index.js +89 -62
  87. package/dist/commands/kill.d.ts +3 -0
  88. package/dist/commands/kill.d.ts.map +1 -0
  89. package/dist/commands/kill.js +13 -0
  90. package/dist/commands/ln.d.ts.map +1 -1
  91. package/dist/commands/ln.js +2 -0
  92. package/dist/commands/ls.d.ts.map +1 -1
  93. package/dist/commands/ls.js +2 -0
  94. package/dist/commands/mkdir.d.ts.map +1 -1
  95. package/dist/commands/mkdir.js +2 -0
  96. package/dist/commands/mv.d.ts.map +1 -1
  97. package/dist/commands/mv.js +2 -0
  98. package/dist/commands/nano.d.ts.map +1 -1
  99. package/dist/commands/nano.js +2 -0
  100. package/dist/commands/neofetch.d.ts.map +1 -1
  101. package/dist/commands/neofetch.js +2 -0
  102. package/dist/commands/passwd.d.ts.map +1 -1
  103. package/dist/commands/passwd.js +2 -0
  104. package/dist/commands/ping.d.ts +3 -0
  105. package/dist/commands/ping.d.ts.map +1 -0
  106. package/dist/commands/ping.js +18 -0
  107. package/dist/commands/ps.d.ts +3 -0
  108. package/dist/commands/ps.d.ts.map +1 -0
  109. package/dist/commands/ps.js +17 -0
  110. package/dist/commands/pwd.d.ts.map +1 -1
  111. package/dist/commands/pwd.js +2 -0
  112. package/dist/commands/rm.d.ts.map +1 -1
  113. package/dist/commands/rm.js +2 -0
  114. package/dist/commands/sed.d.ts +3 -0
  115. package/dist/commands/sed.d.ts.map +1 -0
  116. package/dist/commands/sed.js +47 -0
  117. package/dist/commands/set.d.ts +3 -0
  118. package/dist/commands/set.d.ts.map +1 -1
  119. package/dist/commands/set.js +19 -46
  120. package/dist/commands/sh.d.ts +0 -1
  121. package/dist/commands/sh.d.ts.map +1 -1
  122. package/dist/commands/sh.js +228 -35
  123. package/dist/commands/sleep.d.ts +3 -0
  124. package/dist/commands/sleep.d.ts.map +1 -0
  125. package/dist/commands/sleep.js +13 -0
  126. package/dist/commands/sort.d.ts +3 -0
  127. package/dist/commands/sort.d.ts.map +1 -0
  128. package/dist/commands/sort.js +37 -0
  129. package/dist/commands/su.d.ts.map +1 -1
  130. package/dist/commands/su.js +2 -0
  131. package/dist/commands/sudo.d.ts.map +1 -1
  132. package/dist/commands/sudo.js +2 -0
  133. package/dist/commands/tail.d.ts.map +1 -1
  134. package/dist/commands/tail.js +2 -0
  135. package/dist/commands/tar.d.ts +3 -0
  136. package/dist/commands/tar.d.ts.map +1 -0
  137. package/dist/commands/tar.js +64 -0
  138. package/dist/commands/tee.d.ts +3 -0
  139. package/dist/commands/tee.d.ts.map +1 -0
  140. package/dist/commands/tee.js +29 -0
  141. package/dist/commands/touch.d.ts.map +1 -1
  142. package/dist/commands/touch.js +2 -0
  143. package/dist/commands/tr.d.ts +3 -0
  144. package/dist/commands/tr.d.ts.map +1 -0
  145. package/dist/commands/tr.js +24 -0
  146. package/dist/commands/tree.d.ts.map +1 -1
  147. package/dist/commands/tree.js +2 -0
  148. package/dist/commands/uname.d.ts +3 -0
  149. package/dist/commands/uname.d.ts.map +1 -0
  150. package/dist/commands/uname.js +21 -0
  151. package/dist/commands/uniq.d.ts +3 -0
  152. package/dist/commands/uniq.d.ts.map +1 -0
  153. package/dist/commands/uniq.js +33 -0
  154. package/dist/commands/unset.d.ts.map +1 -1
  155. package/dist/commands/unset.js +6 -10
  156. package/dist/commands/wc.d.ts.map +1 -1
  157. package/dist/commands/wc.js +2 -0
  158. package/dist/commands/wget.d.ts.map +1 -1
  159. package/dist/commands/wget.js +2 -0
  160. package/dist/commands/who.d.ts.map +1 -1
  161. package/dist/commands/who.js +2 -0
  162. package/dist/commands/whoami.d.ts.map +1 -1
  163. package/dist/commands/whoami.js +2 -0
  164. package/dist/commands/xargs.d.ts +3 -0
  165. package/dist/commands/xargs.d.ts.map +1 -0
  166. package/dist/commands/xargs.js +16 -0
  167. package/dist/types/commands.d.ts +13 -0
  168. package/dist/types/commands.d.ts.map +1 -1
  169. package/dist/types/pipeline.d.ts +20 -0
  170. package/dist/types/pipeline.d.ts.map +1 -1
  171. package/package.json +1 -1
  172. package/src/SSHMimic/exec.ts +2 -2
  173. package/src/SSHMimic/executor.ts +95 -98
  174. package/src/SSHMimic/index.ts +15 -49
  175. package/src/SSHMimic/sftp.ts +15 -0
  176. package/src/VirtualFileSystem/index.ts +27 -75
  177. package/src/VirtualShell/shell.ts +19 -2
  178. package/src/VirtualShell/shellParser.ts +202 -168
  179. package/src/VirtualUserManager/index.ts +2 -7
  180. package/src/commands/adduser.ts +2 -0
  181. package/src/commands/awk.ts +30 -0
  182. package/src/commands/base64.ts +18 -0
  183. package/src/commands/cat.ts +2 -0
  184. package/src/commands/cd.ts +2 -0
  185. package/src/commands/chmod.ts +2 -0
  186. package/src/commands/clear.ts +4 -1
  187. package/src/commands/cp.ts +2 -0
  188. package/src/commands/curl.ts +2 -0
  189. package/src/commands/cut.ts +29 -0
  190. package/src/commands/date.ts +24 -0
  191. package/src/commands/deluser.ts +2 -0
  192. package/src/commands/df.ts +18 -0
  193. package/src/commands/diff.ts +29 -0
  194. package/src/commands/du.ts +39 -0
  195. package/src/commands/echo.ts +2 -0
  196. package/src/commands/env.ts +6 -16
  197. package/src/commands/export.ts +11 -24
  198. package/src/commands/find.ts +2 -0
  199. package/src/commands/grep.ts +4 -7
  200. package/src/commands/groups.ts +14 -0
  201. package/src/commands/gzip.ts +31 -0
  202. package/src/commands/head.ts +2 -0
  203. package/src/commands/help.ts +81 -3
  204. package/src/commands/hostname.ts +2 -0
  205. package/src/commands/htop.ts +2 -0
  206. package/src/commands/id.ts +16 -0
  207. package/src/commands/index.ts +98 -99
  208. package/src/commands/kill.ts +14 -0
  209. package/src/commands/ln.ts +2 -0
  210. package/src/commands/ls.ts +2 -0
  211. package/src/commands/mkdir.ts +2 -0
  212. package/src/commands/mv.ts +2 -0
  213. package/src/commands/nano.ts +2 -0
  214. package/src/commands/neofetch.ts +2 -0
  215. package/src/commands/passwd.ts +2 -0
  216. package/src/commands/ping.ts +20 -0
  217. package/src/commands/ps.ts +19 -0
  218. package/src/commands/pwd.ts +2 -0
  219. package/src/commands/rm.ts +2 -0
  220. package/src/commands/sed.ts +45 -0
  221. package/src/commands/set.ts +19 -50
  222. package/src/commands/sh.ts +192 -43
  223. package/src/commands/sleep.ts +14 -0
  224. package/src/commands/sort.ts +37 -0
  225. package/src/commands/su.ts +2 -0
  226. package/src/commands/sudo.ts +2 -0
  227. package/src/commands/tail.ts +2 -0
  228. package/src/commands/tar.ts +58 -0
  229. package/src/commands/tee.ts +25 -0
  230. package/src/commands/touch.ts +2 -0
  231. package/src/commands/tr.ts +24 -0
  232. package/src/commands/tree.ts +2 -0
  233. package/src/commands/uname.ts +20 -0
  234. package/src/commands/uniq.ts +28 -0
  235. package/src/commands/unset.ts +5 -12
  236. package/src/commands/wc.ts +2 -0
  237. package/src/commands/wget.ts +2 -0
  238. package/src/commands/who.ts +2 -0
  239. package/src/commands/whoami.ts +2 -0
  240. package/src/commands/xargs.ts +17 -0
  241. package/src/types/commands.ts +14 -0
  242. package/src/types/pipeline.ts +23 -0
  243. package/standalone.js +92 -64
  244. package/standalone.js.map +4 -4
  245. package/tests/users.test.ts +5 -34
@@ -1,28 +1,41 @@
1
+ /** biome-ignore-all lint/style/useNamingConvention: ENV VARIABLES */
1
2
  import type { VirtualShell } from "../VirtualShell";
2
3
  import type {
3
4
  CommandContext,
4
5
  CommandMode,
5
6
  CommandResult,
7
+ ShellEnv,
6
8
  ShellModule,
7
9
  } from "../types/commands";
8
10
  import { adduserCommand } from "./adduser";
11
+ import { awkCommand } from "./awk";
12
+ import { base64Command } from "./base64";
9
13
  import { catCommand } from "./cat";
10
14
  import { cdCommand } from "./cd";
11
15
  import { chmodCommand } from "./chmod";
12
16
  import { clearCommand } from "./clear";
13
17
  import { cpCommand } from "./cp";
14
18
  import { curlCommand } from "./curl";
19
+ import { cutCommand } from "./cut";
20
+ import { dateCommand } from "./date";
15
21
  import { deluserCommand } from "./deluser";
22
+ import { dfCommand } from "./df";
23
+ import { diffCommand } from "./diff";
24
+ import { duCommand } from "./du";
16
25
  import { echoCommand } from "./echo";
17
26
  import { envCommand } from "./env";
18
27
  import { exitCommand } from "./exit";
19
28
  import { exportCommand } from "./export";
20
29
  import { findCommand } from "./find";
21
30
  import { grepCommand } from "./grep";
31
+ import { groupsCommand } from "./groups";
32
+ import { gunzipCommand, gzipCommand } from "./gzip";
22
33
  import { headCommand } from "./head";
23
34
  import { createHelpCommand } from "./help";
24
35
  import { hostnameCommand } from "./hostname";
25
36
  import { htopCommand } from "./htop";
37
+ import { idCommand } from "./id";
38
+ import { killCommand } from "./kill";
26
39
  import { lnCommand } from "./ln";
27
40
  import { lsCommand } from "./ls";
28
41
  import { mkdirCommand } from "./mkdir";
@@ -30,78 +43,71 @@ import { mvCommand } from "./mv";
30
43
  import { nanoCommand } from "./nano";
31
44
  import { neofetchCommand } from "./neofetch";
32
45
  import { passwdCommand } from "./passwd";
46
+ import { pingCommand } from "./ping";
47
+ import { psCommand } from "./ps";
33
48
  import { pwdCommand } from "./pwd";
34
49
  import { rmCommand } from "./rm";
50
+ import { sedCommand } from "./sed";
35
51
  import { setCommand } from "./set";
36
52
  import { shCommand } from "./sh";
53
+ import { sleepCommand } from "./sleep";
54
+ import { sortCommand } from "./sort";
37
55
  import { suCommand } from "./su";
38
56
  import { sudoCommand } from "./sudo";
39
57
  import { tailCommand } from "./tail";
58
+ import { tarCommand } from "./tar";
59
+ import { teeCommand } from "./tee";
40
60
  import { touchCommand } from "./touch";
61
+ import { trCommand } from "./tr";
41
62
  import { treeCommand } from "./tree";
63
+ import { unameCommand } from "./uname";
64
+ import { uniqCommand } from "./uniq";
42
65
  import { unsetCommand } from "./unset";
43
66
  import { wcCommand } from "./wc";
44
67
  import { wgetCommand } from "./wget";
45
68
  import { whoCommand } from "./who";
46
69
  import { whoamiCommand } from "./whoami";
70
+ import { xargsCommand } from "./xargs";
47
71
 
48
72
  const BASE_COMMANDS: ShellModule[] = [
49
- pwdCommand,
50
- whoamiCommand,
51
- whoCommand,
52
- hostnameCommand,
53
- lsCommand,
54
- cdCommand,
55
- catCommand,
56
- echoCommand,
57
- mkdirCommand,
58
- touchCommand,
59
- rmCommand,
60
- treeCommand,
61
- nanoCommand,
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
62
97
  neofetchCommand,
63
- htopCommand,
64
- adduserCommand,
65
- passwdCommand,
66
- deluserCommand,
67
- sudoCommand,
68
- suCommand,
69
- curlCommand,
70
- envCommand,
71
- wgetCommand,
72
- grepCommand,
73
- exportCommand,
74
- setCommand,
75
- unsetCommand,
76
- shCommand,
77
- clearCommand,
78
- exitCommand,
79
- cpCommand,
80
- mvCommand,
81
- lnCommand,
82
- findCommand,
83
- wcCommand,
84
- headCommand,
85
- tailCommand,
86
- chmodCommand,
87
98
  ];
88
99
 
89
100
  const customCommands: ShellModule[] = [];
90
-
91
- const helpCommand = createHelpCommand(() =>
92
- getCommandModules().map((cmd) => cmd.name),
93
- );
94
-
95
101
  const commandRegistry = new Map<string, ShellModule>();
96
102
  let cachedCommandNames: string[] | null = null;
97
103
 
104
+ const helpCommand = createHelpCommand(() => getCommandModules().map((cmd) => cmd.name));
105
+
98
106
  function buildCache(): void {
99
107
  commandRegistry.clear();
100
108
  for (const mod of getCommandModules()) {
101
109
  commandRegistry.set(mod.name, mod);
102
- for (const alias of mod.aliases ?? []) {
103
- commandRegistry.set(alias, mod);
104
- }
110
+ for (const alias of mod.aliases ?? []) commandRegistry.set(alias, mod);
105
111
  }
106
112
  cachedCommandNames = Array.from(commandRegistry.keys()).sort();
107
113
  }
@@ -114,16 +120,12 @@ export function registerCommand(module: ShellModule): void {
114
120
  const normalized: ShellModule = {
115
121
  ...module,
116
122
  name: module.name.trim().toLowerCase(),
117
- aliases: module.aliases?.map((alias) => alias.trim().toLowerCase()),
123
+ aliases: module.aliases?.map((a) => a.trim().toLowerCase()),
118
124
  };
119
-
120
125
  const names = [normalized.name, ...(normalized.aliases ?? [])];
121
- if (names.some((name) => name.length === 0 || /\s/.test(name))) {
122
- throw new Error(
123
- "Command names and aliases must be non-empty and contain no spaces",
124
- );
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");
125
128
  }
126
-
127
129
  customCommands.push(normalized);
128
130
  buildCache();
129
131
  }
@@ -141,6 +143,10 @@ export function getCommandNames(): string[] {
141
143
  return cachedCommandNames!;
142
144
  }
143
145
 
146
+ export function getCommandModulesPublic(): ShellModule[] {
147
+ return getCommandModules();
148
+ }
149
+
144
150
  export function resolveModule(name: string): ShellModule | undefined {
145
151
  if (!cachedCommandNames) buildCache();
146
152
  return commandRegistry.get(name.toLowerCase());
@@ -152,43 +158,41 @@ function splitArgsRespectingQuotes(input: string): string[] {
152
158
  let inQuotes = false;
153
159
  let quoteChar = "";
154
160
 
155
- for (let i = 0; i < input.length; i += 1) {
161
+ for (let i = 0; i < input.length; i++) {
156
162
  const ch = input[i] || "";
157
163
  const prev = i > 0 ? input[i - 1] : "";
158
-
159
164
  if ((ch === '"' || ch === "'") && prev !== "\\") {
160
- if (!inQuotes) {
161
- inQuotes = true;
162
- quoteChar = ch;
163
- continue;
164
- }
165
- if (ch === quoteChar) {
166
- inQuotes = false;
167
- quoteChar = "";
168
- continue;
169
- }
165
+ if (!inQuotes) { inQuotes = true; quoteChar = ch; continue; }
166
+ if (ch === quoteChar) { inQuotes = false; quoteChar = ""; continue; }
170
167
  }
171
-
172
168
  if (/\s/.test(ch) && !inQuotes) {
173
- if (current.length > 0) {
174
- tokens.push(current);
175
- current = "";
176
- }
169
+ if (current.length > 0) { tokens.push(current); current = ""; }
177
170
  continue;
178
171
  }
179
-
180
172
  current += ch;
181
173
  }
182
-
183
174
  if (current.length > 0) tokens.push(current);
184
175
  return tokens;
185
176
  }
186
177
 
187
178
  function parseInput(rawInput: string): { commandName: string; args: string[] } {
188
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 {
189
184
  return {
190
- commandName: parts[0]?.toLowerCase() ?? "",
191
- args: parts.slice(1),
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,
192
196
  };
193
197
  }
194
198
 
@@ -200,42 +204,37 @@ export async function runCommand(
200
204
  cwd: string,
201
205
  shell: VirtualShell,
202
206
  stdin?: string,
207
+ env?: ShellEnv,
203
208
  ): Promise<CommandResult> {
204
209
  const trimmed = rawInput.trim();
205
-
206
210
  if (trimmed.length === 0) return { exitCode: 0 };
207
211
 
208
- if (trimmed.includes("|") || trimmed.includes(">") || trimmed.includes("<")) {
209
- const { parseShellPipeline } = await import("../VirtualShell/shellParser");
210
- const { executePipeline } = await import("../SSHMimic/executor");
211
-
212
- const pipeline = parseShellPipeline(trimmed);
213
- if (!pipeline.isValid) {
214
- return { stderr: pipeline.error || "Syntax error", exitCode: 1 };
215
- }
216
-
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 };
217
227
  try {
218
- return await executePipeline(
219
- pipeline,
220
- authUser,
221
- hostname,
222
- mode,
223
- cwd,
224
- shell,
225
- );
228
+ return await executeStatements(script.statements, authUser, hostname, mode, cwd, shell, shellEnv);
226
229
  } catch (error: unknown) {
227
- const message =
228
- error instanceof Error ? error.message : "Pipeline execution failed";
229
- return { stderr: message, exitCode: 1 };
230
+ return { stderr: error instanceof Error ? error.message : "Execution failed", exitCode: 1 };
230
231
  }
231
232
  }
232
233
 
233
234
  const { commandName, args } = parseInput(trimmed);
234
235
  const mod = resolveModule(commandName);
235
236
 
236
- if (!mod) {
237
- return { stderr: `Command '${trimmed}' not found`, exitCode: 127 };
238
- }
237
+ if (!mod) return { stderr: `${commandName}: command not found`, exitCode: 127 };
239
238
 
240
239
  try {
241
240
  return await mod.run({
@@ -248,9 +247,9 @@ export async function runCommand(
248
247
  stdin,
249
248
  cwd,
250
249
  shell,
250
+ env: shellEnv,
251
251
  });
252
252
  } catch (error: unknown) {
253
- const message = error instanceof Error ? error.message : "Command failed";
254
- return { stderr: message, exitCode: 1 };
253
+ return { stderr: error instanceof Error ? error.message : "Command failed", exitCode: 1 };
255
254
  }
256
255
  }
@@ -0,0 +1,14 @@
1
+ import type { ShellModule } from "../types/commands";
2
+
3
+ export const killCommand: ShellModule = {
4
+ name: "kill",
5
+ description: "Send signal to process",
6
+ category: "system",
7
+ params: ["[-9] <pid>"],
8
+ run: ({ args }) => {
9
+ const pid = args.find((a) => !a.startsWith("-"));
10
+ if (!pid) return { stderr: "kill: no pid specified", exitCode: 1 };
11
+ // In virtual env, we just acknowledge the kill
12
+ return { stdout: ``, exitCode: 0 };
13
+ },
14
+ };
@@ -4,6 +4,8 @@ import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
5
5
  export const lnCommand: ShellModule = {
6
6
  name: "ln",
7
+ description: "Create links",
8
+ category: "files",
7
9
  params: ["[-s] <target> <link_name>"],
8
10
  run: ({ authUser, shell, cwd, args }) => {
9
11
  const symbolic = ifFlag(args, ["-s", "--symbolic"]);
@@ -28,6 +28,8 @@ function formatDate(date: Date): string {
28
28
 
29
29
  export const lsCommand: ShellModule = {
30
30
  name: "ls",
31
+ description: "List directory contents",
32
+ category: "navigation",
31
33
  params: ["[path]"],
32
34
  run: ({ authUser, shell, cwd, args }) => {
33
35
  const longFormat = ifFlag(args, ["-l", "--long"]);
@@ -4,6 +4,8 @@ import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
5
5
  export const mkdirCommand: ShellModule = {
6
6
  name: "mkdir",
7
+ description: "Make directories",
8
+ category: "files",
7
9
  params: ["<dir>"],
8
10
  run: ({ authUser, shell, cwd, args }) => {
9
11
  if (args.length === 0) {
@@ -3,6 +3,8 @@ import { assertPathAccess, resolvePath } from "./helpers";
3
3
 
4
4
  export const mvCommand: ShellModule = {
5
5
  name: "mv",
6
+ description: "Move or rename files",
7
+ category: "files",
6
8
  params: ["<source> <dest>"],
7
9
  run: ({ authUser, shell, cwd, args }) => {
8
10
  const positionals = args.filter((a) => !a.startsWith("-"));
@@ -4,6 +4,8 @@ import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
5
5
  export const nanoCommand: ShellModule = {
6
6
  name: "nano",
7
+ description: "Text editor",
8
+ category: "shell",
7
9
  params: ["<file>"],
8
10
  run: ({ authUser, shell, cwd, args }) => {
9
11
  const fileArg = args[0];
@@ -5,6 +5,8 @@ import { getAllEnvVars } from "./set";
5
5
 
6
6
  export const neofetchCommand: ShellModule = {
7
7
  name: "neofetch",
8
+ description: "System info display",
9
+ category: "misc",
8
10
  params: ["[--off]"],
9
11
  run: ({ args, authUser, hostname, shell }) => {
10
12
  const env = getAllEnvVars(authUser);
@@ -2,6 +2,8 @@ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const passwdCommand: ShellModule = {
4
4
  name: "passwd",
5
+ description: "Change user password",
6
+ category: "users",
5
7
  params: ["<username> <password>"],
6
8
  run: async ({ authUser, args, shell }) => {
7
9
  const [username, password] = args;
@@ -0,0 +1,20 @@
1
+ import type { ShellModule } from "../types/commands";
2
+
3
+ export const pingCommand: ShellModule = {
4
+ name: "ping",
5
+ description: "Send ICMP ECHO_REQUEST (mock)",
6
+ category: "network",
7
+ params: ["[-c <count>] <host>"],
8
+ run: ({ args }) => {
9
+ const host = args.find((a) => !a.startsWith("-")) ?? "localhost";
10
+ const count = 4;
11
+ const lines = [`PING ${host}: 56 data bytes`];
12
+ for (let i = 0; i < count; i++) {
13
+ const ms = (Math.random() * 10 + 1).toFixed(3);
14
+ lines.push(`64 bytes from ${host}: icmp_seq=${i} ttl=64 time=${ms} ms`);
15
+ }
16
+ lines.push(`--- ${host} ping statistics ---`);
17
+ lines.push(`${count} packets transmitted, ${count} received, 0% packet loss`);
18
+ return { stdout: lines.join("\n"), exitCode: 0 };
19
+ },
20
+ };
@@ -0,0 +1,19 @@
1
+ import type { ShellModule } from "../types/commands";
2
+
3
+ export const psCommand: ShellModule = {
4
+ name: "ps",
5
+ description: "Report process status",
6
+ category: "system",
7
+ params: ["[-a] [-u] [-x]"],
8
+ run: ({ authUser, shell }) => {
9
+ const sessions = shell.users.listActiveSessions();
10
+ const lines = [" PID TTY TIME CMD"];
11
+ let pid = 1000;
12
+ for (const s of sessions) {
13
+ lines.push(`${String(pid).padStart(5)} ${s.tty.padEnd(12)} 00:00:00 ${s.username === authUser ? "bash" : `bash (${s.username})`}`);
14
+ pid++;
15
+ }
16
+ lines.push(`${String(pid).padStart(5)} pts/0 00:00:00 ps`);
17
+ return { stdout: lines.join("\n"), exitCode: 0 };
18
+ },
19
+ };
@@ -2,6 +2,8 @@ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const pwdCommand: ShellModule = {
4
4
  name: "pwd",
5
+ description: "Print working directory",
6
+ category: "navigation",
5
7
  params: [],
6
8
  run: ({ cwd }) => ({ stdout: cwd, exitCode: 0 }),
7
9
  };
@@ -4,6 +4,8 @@ import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
5
5
  export const rmCommand: ShellModule = {
6
6
  name: "rm",
7
+ description: "Remove files or directories",
8
+ category: "files",
7
9
  params: ["[-r|-rf] <path>"],
8
10
  run: ({ authUser, shell, cwd, args }) => {
9
11
  if (args.length === 0) {
@@ -0,0 +1,45 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ import { getFlag, ifFlag } from "./command-helpers";
3
+ import { resolvePath } from "./helpers";
4
+
5
+ export const sedCommand: ShellModule = {
6
+ name: "sed",
7
+ description: "Stream editor for filtering and transforming text",
8
+ category: "text",
9
+ params: ["-e <expr> [file]", "s/pattern/replace/[g]"],
10
+ run: ({ authUser, shell, cwd, args, stdin }) => {
11
+ const inPlace = ifFlag(args, ["-i"]);
12
+ const expr = (getFlag(args, ["-e"]) as string | undefined) ?? args.find((a) => !a.startsWith("-"));
13
+ const fileArg = args.filter((a) => !a.startsWith("-") && a !== expr).pop();
14
+
15
+ if (!expr) return { stderr: "sed: no expression", exitCode: 1 };
16
+
17
+ let content = stdin ?? "";
18
+ if (fileArg) {
19
+ const p = resolvePath(cwd, fileArg);
20
+ try { content = shell.vfs.readFile(p); } catch { return { stderr: `sed: ${fileArg}: No such file or directory`, exitCode: 1 }; }
21
+ }
22
+
23
+ // Parse s/from/to/[g]
24
+ const sMatch = expr.match(/^s([^a-zA-Z0-9])(.+?)\1(.*?)\1([gi]*)$/);
25
+ if (!sMatch) return { stderr: `sed: unrecognized command: ${expr}`, exitCode: 1 };
26
+
27
+ const [, , from, to, flags] = sMatch;
28
+ const regexFlags = (flags ?? "").includes("i") ? "gi" : (flags ?? "").includes("g") ? "g" : "";
29
+ let regex: RegExp;
30
+ try { regex = new RegExp(from!, regexFlags || ""); }
31
+ catch (_e) { return { stderr: `sed: invalid regex: ${from}`, exitCode: 1 }; }
32
+
33
+ const result = (flags ?? "").includes("g") || regexFlags.includes("g")
34
+ ? content.replace(regex, to ?? "")
35
+ : content.replace(regex, to ?? "");
36
+
37
+ if (inPlace && fileArg) {
38
+ const p = resolvePath(cwd, fileArg);
39
+ shell.writeFileAsUser(authUser, p, result);
40
+ return { exitCode: 0 };
41
+ }
42
+
43
+ return { stdout: result, exitCode: 0 };
44
+ },
45
+ };
@@ -1,10 +1,8 @@
1
1
  /** biome-ignore-all lint/style/useNamingConvention: env variables */
2
2
  import type { ShellModule } from "../types/commands";
3
- import { getArg } from "./command-helpers";
4
3
 
5
- // Simple in-memory environment variables store
6
- // In a real implementation, this would be per-session/per-user
7
- const envVars: Record<string, string> = {
4
+ // Legacy global store kept for compatibility with older callers
5
+ const _globalEnv: Record<string, string> = {
8
6
  PATH: "/usr/local/bin:/usr/bin:/bin",
9
7
  HOME: "/home/user",
10
8
  SHELL: "/bin/sh",
@@ -12,62 +10,33 @@ const envVars: Record<string, string> = {
12
10
  USER: "user",
13
11
  };
14
12
 
15
- export function getEnvVar(name: string): string | undefined {
16
- return envVars[name];
17
- }
18
-
19
- export function setEnvVar(name: string, value: string): void {
20
- envVars[name] = value;
21
- }
22
-
13
+ /** @deprecated use env.vars from CommandContext */
14
+ export function getEnvVar(name: string): string | undefined { return _globalEnv[name]; }
15
+ /** @deprecated use env.vars from CommandContext */
16
+ export function setEnvVar(name: string, value: string): void { _globalEnv[name] = value; }
17
+ /** @deprecated use env.vars from CommandContext */
23
18
  export function getAllEnvVars(authUser: string): Record<string, string> {
24
- envVars.USER = authUser;
25
- envVars.HOME = `/home/${authUser}`;
26
- return { ...envVars };
19
+ _globalEnv.USER = authUser;
20
+ _globalEnv.HOME = `/home/${authUser}`;
21
+ return { ..._globalEnv };
27
22
  }
28
23
 
29
24
  export const setCommand: ShellModule = {
30
25
  name: "set",
26
+ description: "Display or set shell variables",
27
+ category: "shell",
31
28
  params: ["[VAR=value]"],
32
- run: ({ args }) => {
33
- // No arguments: display all environment variables
29
+ run: ({ args, env }) => {
34
30
  if (args.length === 0) {
35
- const output = Object.entries(envVars)
36
- .map(([key, value]) => `${key}=${value}`)
37
- .sort()
38
- .join("\n");
39
-
40
- return { stdout: output, exitCode: 0 };
31
+ const out = Object.entries(env.vars).map(([k, v]) => `${k}=${v}`).join("\n");
32
+ return { stdout: out, exitCode: 0 };
41
33
  }
42
-
43
- // Parse VAR=value format
44
- const assignments: string[] = [];
45
- for (let index = 0; ; index += 1) {
46
- const arg = getArg(args, index);
47
- if (!arg) {
48
- break;
49
- }
50
-
34
+ for (const arg of args) {
51
35
  if (arg.includes("=")) {
52
- const [varName, varValue] = arg.split("=", 2);
53
- if (varName && varValue !== undefined) {
54
- setEnvVar(varName, varValue);
55
- assignments.push(arg);
56
- }
57
- } else {
58
- // If no '=' present, display that specific variable
59
- const value = getEnvVar(arg);
60
- if (value !== undefined) {
61
- assignments.push(`${arg}=${value}`);
62
- } else {
63
- assignments.push(`${arg}: not set`);
64
- }
36
+ const eq = arg.indexOf("=");
37
+ env.vars[arg.slice(0, eq)] = arg.slice(eq + 1);
65
38
  }
66
39
  }
67
-
68
- return {
69
- stdout: assignments.length > 0 ? assignments.join("\n") : "",
70
- exitCode: 0,
71
- };
40
+ return { exitCode: 0 };
72
41
  },
73
42
  };