typescript-virtual-container 1.1.0 → 1.1.1-c

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 (229) hide show
  1. package/.vscode/settings.json +18 -0
  2. package/README.md +45 -5
  3. package/dist/SSHClient/index.d.ts +138 -0
  4. package/dist/SSHClient/index.d.ts.map +1 -0
  5. package/dist/SSHClient/index.js +216 -0
  6. package/dist/SSHMimic/exec.d.ts +4 -0
  7. package/dist/SSHMimic/exec.d.ts.map +1 -0
  8. package/dist/SSHMimic/exec.js +21 -0
  9. package/dist/SSHMimic/executor.d.ts +9 -0
  10. package/dist/SSHMimic/executor.d.ts.map +1 -0
  11. package/dist/SSHMimic/executor.js +131 -0
  12. package/dist/SSHMimic/hostKey.d.ts +2 -0
  13. package/dist/SSHMimic/hostKey.d.ts.map +1 -0
  14. package/dist/SSHMimic/hostKey.js +17 -0
  15. package/dist/SSHMimic/index.d.ts +39 -0
  16. package/dist/SSHMimic/index.d.ts.map +1 -0
  17. package/dist/SSHMimic/index.js +113 -0
  18. package/dist/SSHMimic/loginFormat.d.ts +2 -0
  19. package/dist/SSHMimic/loginFormat.d.ts.map +1 -0
  20. package/dist/SSHMimic/loginFormat.js +10 -0
  21. package/dist/SSHMimic/prompt.d.ts +2 -0
  22. package/dist/SSHMimic/prompt.d.ts.map +1 -0
  23. package/dist/SSHMimic/prompt.js +9 -0
  24. package/dist/VirtualFileSystem/archive.d.ts +5 -0
  25. package/dist/VirtualFileSystem/archive.d.ts.map +1 -0
  26. package/dist/VirtualFileSystem/archive.js +56 -0
  27. package/dist/VirtualFileSystem/index.d.ts +131 -0
  28. package/dist/VirtualFileSystem/index.d.ts.map +1 -0
  29. package/dist/VirtualFileSystem/index.js +355 -0
  30. package/dist/VirtualFileSystem/internalTypes.d.ts +18 -0
  31. package/dist/VirtualFileSystem/internalTypes.d.ts.map +1 -0
  32. package/dist/VirtualFileSystem/internalTypes.js +0 -0
  33. package/dist/VirtualFileSystem/path.d.ts +9 -0
  34. package/dist/VirtualFileSystem/path.d.ts.map +1 -0
  35. package/dist/VirtualFileSystem/path.js +49 -0
  36. package/dist/VirtualFileSystem/snapshot.d.ts +5 -0
  37. package/dist/VirtualFileSystem/snapshot.d.ts.map +1 -0
  38. package/dist/VirtualFileSystem/snapshot.js +59 -0
  39. package/dist/VirtualFileSystem/tree.d.ts +3 -0
  40. package/dist/VirtualFileSystem/tree.d.ts.map +1 -0
  41. package/dist/VirtualFileSystem/tree.js +19 -0
  42. package/dist/VirtualShell/index.d.ts +86 -0
  43. package/dist/VirtualShell/index.d.ts.map +1 -0
  44. package/dist/VirtualShell/index.js +129 -0
  45. package/dist/VirtualShell/shell.d.ts +5 -0
  46. package/dist/VirtualShell/shell.d.ts.map +1 -0
  47. package/dist/VirtualShell/shell.js +473 -0
  48. package/dist/VirtualShell/shellParser.d.ts +4 -0
  49. package/dist/VirtualShell/shellParser.d.ts.map +1 -0
  50. package/dist/VirtualShell/shellParser.js +207 -0
  51. package/dist/VirtualUserManager/index.d.ts +168 -0
  52. package/dist/VirtualUserManager/index.d.ts.map +1 -0
  53. package/dist/VirtualUserManager/index.js +375 -0
  54. package/dist/commands/adduser.d.ts +3 -0
  55. package/dist/commands/adduser.d.ts.map +1 -0
  56. package/dist/commands/adduser.js +18 -0
  57. package/dist/commands/cat.d.ts +3 -0
  58. package/dist/commands/cat.d.ts.map +1 -0
  59. package/dist/commands/cat.js +15 -0
  60. package/dist/commands/cd.d.ts +3 -0
  61. package/dist/commands/cd.d.ts.map +1 -0
  62. package/dist/commands/cd.js +17 -0
  63. package/dist/commands/clear.d.ts +3 -0
  64. package/dist/commands/clear.d.ts.map +1 -0
  65. package/dist/commands/clear.js +5 -0
  66. package/dist/commands/command-helpers.d.ts +23 -0
  67. package/dist/commands/command-helpers.d.ts.map +1 -0
  68. package/dist/commands/command-helpers.js +139 -0
  69. package/dist/commands/curl.d.ts +3 -0
  70. package/dist/commands/curl.d.ts.map +1 -0
  71. package/dist/commands/curl.js +44 -0
  72. package/dist/commands/deluser.d.ts +3 -0
  73. package/dist/commands/deluser.d.ts.map +1 -0
  74. package/dist/commands/deluser.js +15 -0
  75. package/dist/commands/echo.d.ts +3 -0
  76. package/dist/commands/echo.d.ts.map +1 -0
  77. package/dist/commands/echo.js +22 -0
  78. package/dist/commands/env.d.ts +3 -0
  79. package/dist/commands/env.d.ts.map +1 -0
  80. package/dist/commands/env.js +18 -0
  81. package/dist/commands/exit.d.ts +3 -0
  82. package/dist/commands/exit.d.ts.map +1 -0
  83. package/dist/commands/exit.js +5 -0
  84. package/dist/commands/export.d.ts +3 -0
  85. package/dist/commands/export.d.ts.map +1 -0
  86. package/dist/commands/export.js +34 -0
  87. package/dist/commands/grep.d.ts +3 -0
  88. package/dist/commands/grep.d.ts.map +1 -0
  89. package/dist/commands/grep.js +69 -0
  90. package/dist/commands/help.d.ts +3 -0
  91. package/dist/commands/help.d.ts.map +1 -0
  92. package/dist/commands/help.js +7 -0
  93. package/dist/commands/helpers.d.ts +26 -0
  94. package/dist/commands/helpers.d.ts.map +1 -0
  95. package/dist/commands/helpers.js +160 -0
  96. package/dist/commands/hostname.d.ts +3 -0
  97. package/dist/commands/hostname.d.ts.map +1 -0
  98. package/dist/commands/hostname.js +5 -0
  99. package/dist/commands/htop.d.ts +3 -0
  100. package/dist/commands/htop.d.ts.map +1 -0
  101. package/dist/commands/htop.js +10 -0
  102. package/dist/commands/index.d.ts +8 -0
  103. package/dist/commands/index.d.ts.map +1 -0
  104. package/dist/commands/index.js +212 -0
  105. package/dist/commands/ls.d.ts +3 -0
  106. package/dist/commands/ls.d.ts.map +1 -0
  107. package/dist/commands/ls.js +47 -0
  108. package/dist/commands/mkdir.d.ts +3 -0
  109. package/dist/commands/mkdir.d.ts.map +1 -0
  110. package/dist/commands/mkdir.js +21 -0
  111. package/dist/commands/nano.d.ts +3 -0
  112. package/dist/commands/nano.d.ts.map +1 -0
  113. package/dist/commands/nano.js +27 -0
  114. package/dist/commands/neofetch.d.ts +3 -0
  115. package/dist/commands/neofetch.d.ts.map +1 -0
  116. package/dist/commands/neofetch.js +32 -0
  117. package/dist/commands/pwd.d.ts +3 -0
  118. package/dist/commands/pwd.d.ts.map +1 -0
  119. package/dist/commands/pwd.js +5 -0
  120. package/dist/commands/rm.d.ts +3 -0
  121. package/dist/commands/rm.d.ts.map +1 -0
  122. package/dist/commands/rm.js +29 -0
  123. package/dist/commands/set.d.ts +7 -0
  124. package/dist/commands/set.d.ts.map +1 -0
  125. package/dist/commands/set.js +64 -0
  126. package/dist/commands/sh.d.ts +4 -0
  127. package/dist/commands/sh.d.ts.map +1 -0
  128. package/dist/commands/sh.js +45 -0
  129. package/dist/commands/su.d.ts +3 -0
  130. package/dist/commands/su.d.ts.map +1 -0
  131. package/dist/commands/su.js +24 -0
  132. package/dist/commands/sudo.d.ts +3 -0
  133. package/dist/commands/sudo.d.ts.map +1 -0
  134. package/dist/commands/sudo.js +47 -0
  135. package/dist/commands/touch.d.ts +3 -0
  136. package/dist/commands/touch.d.ts.map +1 -0
  137. package/dist/commands/touch.js +18 -0
  138. package/dist/commands/tree.d.ts +3 -0
  139. package/dist/commands/tree.d.ts.map +1 -0
  140. package/dist/commands/tree.js +11 -0
  141. package/dist/commands/unset.d.ts +3 -0
  142. package/dist/commands/unset.d.ts.map +1 -0
  143. package/dist/commands/unset.js +15 -0
  144. package/dist/commands/wget.d.ts +3 -0
  145. package/dist/commands/wget.d.ts.map +1 -0
  146. package/dist/commands/wget.js +113 -0
  147. package/dist/commands/who.d.ts +3 -0
  148. package/dist/commands/who.d.ts.map +1 -0
  149. package/dist/commands/who.js +15 -0
  150. package/dist/commands/whoami.d.ts +3 -0
  151. package/dist/commands/whoami.d.ts.map +1 -0
  152. package/dist/commands/whoami.js +5 -0
  153. package/dist/index.d.ts +11 -0
  154. package/dist/index.d.ts.map +1 -0
  155. package/dist/index.js +7 -0
  156. package/dist/modules/neofetch.d.ts +19 -0
  157. package/dist/modules/neofetch.d.ts.map +1 -0
  158. package/dist/modules/neofetch.js +284 -0
  159. package/dist/modules/shellInteractive.d.ts +6 -0
  160. package/dist/modules/shellInteractive.d.ts.map +1 -0
  161. package/dist/modules/shellInteractive.js +26 -0
  162. package/dist/modules/shellRuntime.d.ts +11 -0
  163. package/dist/modules/shellRuntime.d.ts.map +1 -0
  164. package/dist/modules/shellRuntime.js +52 -0
  165. package/dist/standalone.d.ts +2 -0
  166. package/dist/standalone.d.ts.map +1 -0
  167. package/dist/standalone.js +25 -0
  168. package/dist/types/commands.d.ts +89 -0
  169. package/dist/types/commands.d.ts.map +1 -0
  170. package/dist/types/commands.js +0 -0
  171. package/dist/types/pipeline.d.ts +23 -0
  172. package/dist/types/pipeline.d.ts.map +1 -0
  173. package/dist/types/pipeline.js +0 -0
  174. package/dist/types/streams.d.ts +32 -0
  175. package/dist/types/streams.d.ts.map +1 -0
  176. package/dist/types/streams.js +0 -0
  177. package/dist/types/vfs.d.ts +71 -0
  178. package/dist/types/vfs.d.ts.map +1 -0
  179. package/dist/types/vfs.js +0 -0
  180. package/package.json +4 -2
  181. package/src/{SSHMimic/client.ts → SSHClient/index.ts} +2 -2
  182. package/src/SSHMimic/exec.ts +1 -1
  183. package/src/SSHMimic/executor.ts +8 -8
  184. package/src/VirtualFileSystem/index.ts +26 -0
  185. package/src/VirtualShell/index.ts +17 -1
  186. package/src/VirtualShell/shell.ts +19 -107
  187. package/src/VirtualShell/shellParser.ts +32 -7
  188. package/src/VirtualUserManager/index.ts +149 -0
  189. package/src/{VirtualShell/commands → commands}/adduser.ts +1 -1
  190. package/src/{VirtualShell/commands → commands}/cat.ts +1 -1
  191. package/src/{VirtualShell/commands → commands}/cd.ts +1 -1
  192. package/src/{VirtualShell/commands → commands}/clear.ts +1 -1
  193. package/src/{VirtualShell/commands → commands}/curl.ts +2 -2
  194. package/src/{VirtualShell/commands → commands}/deluser.ts +1 -1
  195. package/src/{VirtualShell/commands → commands}/echo.ts +1 -1
  196. package/src/{VirtualShell/commands → commands}/env.ts +1 -1
  197. package/src/{VirtualShell/commands → commands}/exit.ts +1 -1
  198. package/src/{VirtualShell/commands → commands}/export.ts +1 -1
  199. package/src/{VirtualShell/commands → commands}/grep.ts +1 -1
  200. package/src/{VirtualShell/commands → commands}/help.ts +1 -1
  201. package/src/{VirtualShell/commands → commands}/helpers.ts +1 -1
  202. package/src/{VirtualShell/commands → commands}/hostname.ts +1 -1
  203. package/src/{VirtualShell/commands → commands}/htop.ts +1 -1
  204. package/src/{VirtualShell/commands → commands}/index.ts +7 -4
  205. package/src/{VirtualShell/commands → commands}/ls.ts +1 -1
  206. package/src/{VirtualShell/commands → commands}/mkdir.ts +1 -1
  207. package/src/{VirtualShell/commands → commands}/nano.ts +1 -1
  208. package/src/{VirtualShell/commands → commands}/neofetch.ts +2 -2
  209. package/src/{VirtualShell/commands → commands}/pwd.ts +1 -1
  210. package/src/{VirtualShell/commands → commands}/rm.ts +1 -1
  211. package/src/{VirtualShell/commands → commands}/set.ts +1 -1
  212. package/src/{VirtualShell/commands → commands}/sh.ts +1 -1
  213. package/src/{VirtualShell/commands → commands}/su.ts +1 -1
  214. package/src/{VirtualShell/commands → commands}/sudo.ts +1 -1
  215. package/src/{VirtualShell/commands → commands}/touch.ts +2 -2
  216. package/src/{VirtualShell/commands → commands}/tree.ts +1 -1
  217. package/src/{VirtualShell/commands → commands}/unset.ts +1 -1
  218. package/src/{VirtualShell/commands → commands}/wget.ts +2 -2
  219. package/src/{VirtualShell/commands → commands}/who.ts +2 -2
  220. package/src/{VirtualShell/commands → commands}/whoami.ts +1 -1
  221. package/src/index.ts +2 -2
  222. package/{modules → src/modules}/neofetch.ts +56 -51
  223. package/src/modules/shellInteractive.ts +57 -0
  224. package/src/modules/shellRuntime.ts +76 -0
  225. package/tests/command-helpers.test.ts +1 -1
  226. package/tests/helpers.test.ts +1 -1
  227. package/tests/users.test.ts +60 -0
  228. package/tsconfig.json +19 -8
  229. /package/src/{VirtualShell/commands → commands}/command-helpers.ts +0 -0
@@ -9,7 +9,16 @@ export function parseShellPipeline(rawInput: string): Pipeline {
9
9
  }
10
10
 
11
11
  const commands: PipelineCommand[] = [];
12
- const pipeTokens = tokenizePipeline(trimmed);
12
+ const tokenized = tokenizePipeline(trimmed);
13
+ if (tokenized.error) {
14
+ return {
15
+ commands: [],
16
+ isValid: false,
17
+ error: tokenized.error,
18
+ };
19
+ }
20
+
21
+ const pipeTokens = tokenized.tokens;
13
22
 
14
23
  for (const token of pipeTokens) {
15
24
  const cmd = parseCommandWithRedirections(token);
@@ -29,7 +38,7 @@ export function parseShellPipeline(rawInput: string): Pipeline {
29
38
  }
30
39
 
31
40
  /** Tokenize input by pipes, respecting quoted strings */
32
- function tokenizePipeline(input: string): string[] {
41
+ function tokenizePipeline(input: string): { tokens: string[]; error?: string } {
33
42
  const tokens: string[] = [];
34
43
  let current = "";
35
44
  let inQuotes = false;
@@ -49,9 +58,13 @@ function tokenizePipeline(input: string): string[] {
49
58
  current += ch;
50
59
  i++;
51
60
  } else if (ch === "|" && !inQuotes) {
52
- if (current.trim()) {
53
- tokens.push(current.trim());
61
+ if (!current.trim()) {
62
+ return {
63
+ tokens: [],
64
+ error: "Syntax error near unexpected token '|'",
65
+ };
54
66
  }
67
+ tokens.push(current.trim());
55
68
  current = "";
56
69
  i++;
57
70
  } else {
@@ -60,11 +73,23 @@ function tokenizePipeline(input: string): string[] {
60
73
  }
61
74
  }
62
75
 
63
- if (current.trim()) {
64
- tokens.push(current.trim());
76
+ if (inQuotes) {
77
+ return {
78
+ tokens: [],
79
+ error: "Syntax error: unterminated quote",
80
+ };
65
81
  }
66
82
 
67
- return tokens;
83
+ if (!current.trim()) {
84
+ return {
85
+ tokens: [],
86
+ error: "Syntax error near unexpected token '|'",
87
+ };
88
+ }
89
+
90
+ tokens.push(current.trim());
91
+
92
+ return { tokens };
68
93
  }
69
94
 
70
95
  interface ParseResult {
@@ -1,4 +1,5 @@
1
1
  import { randomBytes, randomUUID, scryptSync } from "node:crypto";
2
+ import * as path from "node:path";
2
3
  import type VirtualFileSystem from "../VirtualFileSystem";
3
4
 
4
5
  /** Persisted virtual user credential record. */
@@ -33,9 +34,11 @@ export interface VirtualActiveSession {
33
34
  export class VirtualUserManager {
34
35
  private readonly usersPath = "/virtual-env-js/.auth/htpasswd";
35
36
  private readonly sudoersPath = "/virtual-env-js/.auth/sudoers";
37
+ private readonly quotasPath = "/virtual-env-js/.auth/quotas";
36
38
  private readonly authDirPath = "/virtual-env-js/.auth";
37
39
  private readonly users = new Map<string, VirtualUserRecord>();
38
40
  private readonly sudoers = new Set<string>();
41
+ private readonly quotas = new Map<string, number>();
39
42
  private readonly activeSessions = new Map<string, VirtualActiveSession>();
40
43
  private nextTty = 0;
41
44
 
@@ -58,6 +61,7 @@ export class VirtualUserManager {
58
61
  public async initialize(): Promise<void> {
59
62
  this.loadFromVfs();
60
63
  this.loadSudoersFromVfs();
64
+ this.loadQuotasFromVfs();
61
65
 
62
66
  this.users.set("root", this.createRecord("root", this.defaultRootPassword));
63
67
 
@@ -66,6 +70,113 @@ export class VirtualUserManager {
66
70
  await this.persist();
67
71
  }
68
72
 
73
+ /**
74
+ * Sets max allowed bytes under /home/<username>.
75
+ *
76
+ * @param username Target username.
77
+ * @param maxBytes Quota ceiling in bytes.
78
+ */
79
+ public async setQuotaBytes(
80
+ username: string,
81
+ maxBytes: number,
82
+ ): Promise<void> {
83
+ this.validateUsername(username);
84
+ if (!this.users.has(username)) {
85
+ throw new Error(`quota: user '${username}' does not exist`);
86
+ }
87
+
88
+ if (!Number.isFinite(maxBytes) || maxBytes < 0) {
89
+ throw new Error("quota: maxBytes must be a non-negative number");
90
+ }
91
+
92
+ this.quotas.set(username, Math.floor(maxBytes));
93
+ await this.persist();
94
+ }
95
+
96
+ /**
97
+ * Removes quota for a user.
98
+ *
99
+ * @param username Target username.
100
+ */
101
+ public async clearQuota(username: string): Promise<void> {
102
+ this.validateUsername(username);
103
+ this.quotas.delete(username);
104
+ await this.persist();
105
+ }
106
+
107
+ /**
108
+ * Gets configured quota in bytes for a user.
109
+ *
110
+ * @param username Target username.
111
+ * @returns Quota in bytes, or null when unlimited.
112
+ */
113
+ public getQuotaBytes(username: string): number | null {
114
+ return this.quotas.get(username) ?? null;
115
+ }
116
+
117
+ /**
118
+ * Computes current usage under /home/<username>.
119
+ *
120
+ * @param username Target username.
121
+ * @returns Current usage in bytes.
122
+ */
123
+ public getUsageBytes(username: string): number {
124
+ const homePath = `/home/${username}`;
125
+ if (!this.vfs.exists(homePath)) {
126
+ return 0;
127
+ }
128
+
129
+ return this.vfs.getUsageBytes(homePath);
130
+ }
131
+
132
+ /**
133
+ * Validates that writing file content would not exceed user quota.
134
+ *
135
+ * Quotas are enforced only for writes inside /home/<username>.
136
+ *
137
+ * @param username Authenticated user.
138
+ * @param targetPath Target file path.
139
+ * @param nextContent New file content.
140
+ */
141
+ public assertWriteWithinQuota(
142
+ username: string,
143
+ targetPath: string,
144
+ nextContent: string | Buffer,
145
+ ): void {
146
+ const quota = this.quotas.get(username);
147
+ if (quota === undefined) {
148
+ return;
149
+ }
150
+
151
+ const normalizedPath = normalizeVfsPath(targetPath);
152
+ const homePath = normalizeVfsPath(`/home/${username}`);
153
+ const inUserHome =
154
+ normalizedPath === homePath || normalizedPath.startsWith(`${homePath}/`);
155
+ if (!inUserHome) {
156
+ return;
157
+ }
158
+
159
+ const currentUsage = this.getUsageBytes(username);
160
+ let existingSize = 0;
161
+ if (this.vfs.exists(normalizedPath)) {
162
+ const existing = this.vfs.stat(normalizedPath);
163
+ if (existing.type === "file") {
164
+ existingSize = existing.size;
165
+ }
166
+ }
167
+
168
+ const incomingSize = Buffer.isBuffer(nextContent)
169
+ ? nextContent.length
170
+ : Buffer.byteLength(nextContent, "utf8");
171
+ const projectedUsage = currentUsage - existingSize + incomingSize;
172
+
173
+ if (projectedUsage > quota) {
174
+ throw new Error(
175
+ `quota exceeded for '${username}': ${projectedUsage}/${quota} bytes`,
176
+ );
177
+ }
178
+ }
179
+
69
180
  /**
70
181
  * Verifies plaintext password against stored record.
71
182
  *
@@ -291,6 +402,30 @@ export class VirtualUserManager {
291
402
  }
292
403
  }
293
404
 
405
+ private loadQuotasFromVfs(): void {
406
+ this.quotas.clear();
407
+
408
+ if (!this.vfs.exists(this.quotasPath)) {
409
+ return;
410
+ }
411
+
412
+ const raw = this.vfs.readFile(this.quotasPath);
413
+ for (const line of raw.split("\n")) {
414
+ const trimmed = line.trim();
415
+ if (trimmed.length === 0) {
416
+ continue;
417
+ }
418
+
419
+ const [username, value] = trimmed.split(":");
420
+ const bytes = Number.parseInt(value ?? "", 10);
421
+ if (!username || !Number.isFinite(bytes) || bytes < 0) {
422
+ continue;
423
+ }
424
+
425
+ this.quotas.set(username, bytes);
426
+ }
427
+ }
428
+
294
429
  private async persist(): Promise<void> {
295
430
  if (!this.vfs.exists(this.authDirPath)) {
296
431
  this.vfs.mkdir(this.authDirPath, 0o700);
@@ -314,6 +449,15 @@ export class VirtualUserManager {
314
449
  sudoersContent.length > 0 ? `${sudoersContent}\n` : "",
315
450
  { mode: 0o600 },
316
451
  );
452
+ const quotasContent = Array.from(this.quotas.entries())
453
+ .sort(([left], [right]) => left.localeCompare(right))
454
+ .map(([username, maxBytes]) => `${username}:${maxBytes}`)
455
+ .join("\n");
456
+ this.vfs.writeFile(
457
+ this.quotasPath,
458
+ quotasContent.length > 0 ? `${quotasContent}\n` : "",
459
+ { mode: 0o600 },
460
+ );
317
461
  await this.vfs.flushMirror();
318
462
  }
319
463
 
@@ -346,3 +490,8 @@ export class VirtualUserManager {
346
490
  }
347
491
  }
348
492
  }
493
+
494
+ function normalizeVfsPath(targetPath: string): string {
495
+ const normalized = path.posix.normalize(targetPath);
496
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
497
+ }
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const adduserCommand: ShellModule = {
4
4
  name: "adduser",
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { getArg } from "./command-helpers";
3
3
  import { assertPathAccess, resolveReadablePath } from "./helpers";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { assertPathAccess, resolvePath } from "./helpers";
3
3
 
4
4
  export const cdCommand: ShellModule = {
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const clearCommand: ShellModule = {
4
4
  name: "clear",
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { parseArgs } from "./command-helpers";
3
3
  import {
4
4
  assertPathAccess,
@@ -39,7 +39,7 @@ export const curlCommand: ShellModule = {
39
39
  if (outputPath) {
40
40
  const target = resolvePath(cwd, outputPath);
41
41
  assertPathAccess(authUser, target, "curl");
42
- shell.vfs.writeFile(target, result.stdout);
42
+ shell.writeFileAsUser(authUser, target, result.stdout);
43
43
  return {
44
44
  stderr: result.stderr
45
45
  ? normalizeTerminalOutput(result.stderr)
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const deluserCommand: ShellModule = {
4
4
  name: "deluser",
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { parseArgs } from "./command-helpers";
3
3
  import { getAllEnvVars } from "./set";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { getAllEnvVars } from "./set";
3
3
 
4
4
  export const envCommand: ShellModule = {
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const exitCommand: ShellModule = {
4
4
  name: "exit",
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { getArg } from "./command-helpers";
3
3
  import { getEnvVar, setEnvVar } from "./set";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { parseArgs } from "./command-helpers";
3
3
  import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export function createHelpCommand(getNames: () => string[]): ShellModule {
4
4
  return {
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import * as path from "node:path";
3
- import type VirtualFileSystem from "../../VirtualFileSystem";
3
+ import type VirtualFileSystem from "../VirtualFileSystem";
4
4
 
5
5
  const PROTECTED_PREFIXES = ["/virtual-env-js/.auth"] as const;
6
6
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const hostnameCommand: ShellModule = {
4
4
  name: "hostname",
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const htopCommand: ShellModule = {
4
4
  name: "htop",
@@ -1,10 +1,10 @@
1
- import type { VirtualShell } from "..";
1
+ import type { VirtualShell } from "../VirtualShell";
2
2
  import type {
3
3
  CommandContext,
4
4
  CommandMode,
5
5
  CommandResult,
6
6
  ShellModule,
7
- } from "../../types/commands";
7
+ } from "../types/commands";
8
8
  import { adduserCommand } from "./adduser";
9
9
  import { catCommand } from "./cat";
10
10
  import { cdCommand } from "./cd";
@@ -142,6 +142,9 @@ export function getCommandNames(): string[] {
142
142
  }
143
143
 
144
144
  export function resolveModule(name: string): ShellModule | undefined {
145
+ if (!cachedCommandNames) {
146
+ buildCache();
147
+ }
145
148
  return commandRegistry.get(name.toLowerCase());
146
149
  }
147
150
 
@@ -213,8 +216,8 @@ export async function runCommand(
213
216
  }
214
217
 
215
218
  if (trimmed.includes("|") || trimmed.includes(">") || trimmed.includes("<")) {
216
- const { parseShellPipeline } = await import("../shellParser");
217
- const { executePipeline } = await import("../../SSHMimic/executor");
219
+ const { parseShellPipeline } = await import("../VirtualShell/shellParser");
220
+ const { executePipeline } = await import("../SSHMimic/executor");
218
221
 
219
222
  const pipeline = parseShellPipeline(trimmed);
220
223
  if (!pipeline.isValid) {
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { getArg, ifFlag } from "./command-helpers";
3
3
  import { assertPathAccess, joinListWithType, resolvePath } from "./helpers";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { getArg } from "./command-helpers";
3
3
  import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
@@ -1,5 +1,5 @@
1
1
  import * as path from "node:path";
2
- import type { ShellModule } from "../../types/commands";
2
+ import type { ShellModule } from "../types/commands";
3
3
  import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
5
5
  export const nanoCommand: ShellModule = {
@@ -1,5 +1,5 @@
1
- import { buildNeofetchOutput } from "../../../modules/neofetch";
2
- import type { ShellModule } from "../../types/commands";
1
+ import { buildNeofetchOutput } from "../modules/neofetch";
2
+ import type { ShellModule } from "../types/commands";
3
3
  import { ifFlag } from "./command-helpers";
4
4
  import { getAllEnvVars } from "./set";
5
5
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const pwdCommand: ShellModule = {
4
4
  name: "pwd",
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { getArg, ifFlag } from "./command-helpers";
3
3
  import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
@@ -1,5 +1,5 @@
1
1
  /** biome-ignore-all lint/style/useNamingConvention: env variables */
2
- import type { ShellModule } from "../../types/commands";
2
+ import type { ShellModule } from "../types/commands";
3
3
  import { getArg } from "./command-helpers";
4
4
 
5
5
  // Simple in-memory environment variables store
@@ -1,4 +1,4 @@
1
- import type { CommandContext, ShellModule } from "../../types/commands";
1
+ import type { CommandContext, ShellModule } from "../types/commands";
2
2
  import { getArg, getFlag } from "./command-helpers";
3
3
  import { runCommand } from "./index";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { getArg } from "./command-helpers";
3
3
 
4
4
  export const suCommand: ShellModule = {
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { parseArgs } from "./command-helpers";
3
3
  import { runCommand } from "./index";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { assertPathAccess, resolvePath } from "./helpers";
3
3
 
4
4
  export const touchCommand: ShellModule = {
@@ -13,7 +13,7 @@ export const touchCommand: ShellModule = {
13
13
  const target = resolvePath(cwd, file);
14
14
  assertPathAccess(authUser, target, "touch");
15
15
  if (!shell.vfs.exists(target)) {
16
- shell.vfs.writeFile(target, "");
16
+ shell.writeFileAsUser(authUser, target, "");
17
17
  }
18
18
  }
19
19
  return { exitCode: 0 };
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { getArg } from "./command-helpers";
3
3
  import { assertPathAccess, resolvePath } from "./helpers";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
  import { setEnvVar } from "./set";
3
3
 
4
4
  export const unsetCommand: ShellModule = {
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { mkdtemp, readFile, rm } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import type { ShellModule } from "../../types/commands";
5
+ import type { ShellModule } from "../types/commands";
6
6
  import { ifFlag, parseArgs } from "./command-helpers";
7
7
  import {
8
8
  assertPathAccess,
@@ -131,7 +131,7 @@ export const wgetCommand: ShellModule = {
131
131
  const content = await readFile(tempFile, "utf8");
132
132
  const target = resolvePath(cwd, outputPath || stripUrlFilename(url));
133
133
  assertPathAccess(authUser, target, "wget");
134
- shell.vfs.writeFile(target, content);
134
+ shell.writeFileAsUser(authUser, target, content);
135
135
 
136
136
  return {
137
137
  stdout: `saved ${target}`,
@@ -1,5 +1,5 @@
1
- import { formatLoginDate } from "../../SSHMimic/loginFormat";
2
- import type { ShellModule } from "../../types/commands";
1
+ import { formatLoginDate } from "../SSHMimic/loginFormat";
2
+ import type { ShellModule } from "../types/commands";
3
3
 
4
4
  export const whoCommand: ShellModule = {
5
5
  name: "who",
@@ -1,4 +1,4 @@
1
- import type { ShellModule } from "../../types/commands";
1
+ import type { ShellModule } from "../types/commands";
2
2
 
3
3
  export const whoamiCommand: ShellModule = {
4
4
  name: "whoami",
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SshClient } from "./SSHMimic/client";
1
+ import { SshClient } from "./SSHClient";
2
2
  import { SshMimic } from "./SSHMimic/index";
3
3
  import VirtualFileSystem from "./VirtualFileSystem";
4
4
  import { VirtualShell } from "./VirtualShell";
@@ -41,4 +41,4 @@ export {
41
41
  getArg,
42
42
  getFlag,
43
43
  ifFlag,
44
- } from "./VirtualShell/commands/command-helpers";
44
+ } from "./commands/command-helpers";