typescript-virtual-container 1.1.1-b → 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 (185) hide show
  1. package/.vscode/settings.json +1 -1
  2. package/dist/SSHClient/index.d.ts +138 -0
  3. package/dist/SSHClient/index.d.ts.map +1 -0
  4. package/dist/SSHClient/index.js +216 -0
  5. package/dist/SSHMimic/exec.d.ts +4 -0
  6. package/dist/SSHMimic/exec.d.ts.map +1 -0
  7. package/dist/SSHMimic/exec.js +21 -0
  8. package/dist/SSHMimic/executor.d.ts +9 -0
  9. package/dist/SSHMimic/executor.d.ts.map +1 -0
  10. package/dist/SSHMimic/executor.js +131 -0
  11. package/dist/SSHMimic/hostKey.d.ts +2 -0
  12. package/dist/SSHMimic/hostKey.d.ts.map +1 -0
  13. package/dist/SSHMimic/hostKey.js +17 -0
  14. package/dist/SSHMimic/index.d.ts +39 -0
  15. package/dist/SSHMimic/index.d.ts.map +1 -0
  16. package/dist/SSHMimic/index.js +113 -0
  17. package/dist/SSHMimic/loginFormat.d.ts +2 -0
  18. package/dist/SSHMimic/loginFormat.d.ts.map +1 -0
  19. package/dist/SSHMimic/loginFormat.js +10 -0
  20. package/dist/SSHMimic/prompt.d.ts +2 -0
  21. package/dist/SSHMimic/prompt.d.ts.map +1 -0
  22. package/dist/SSHMimic/prompt.js +9 -0
  23. package/dist/VirtualFileSystem/archive.d.ts +5 -0
  24. package/dist/VirtualFileSystem/archive.d.ts.map +1 -0
  25. package/dist/VirtualFileSystem/archive.js +56 -0
  26. package/dist/VirtualFileSystem/index.d.ts +131 -0
  27. package/dist/VirtualFileSystem/index.d.ts.map +1 -0
  28. package/dist/VirtualFileSystem/index.js +355 -0
  29. package/dist/VirtualFileSystem/internalTypes.d.ts +18 -0
  30. package/dist/VirtualFileSystem/internalTypes.d.ts.map +1 -0
  31. package/dist/VirtualFileSystem/internalTypes.js +0 -0
  32. package/dist/VirtualFileSystem/path.d.ts +9 -0
  33. package/dist/VirtualFileSystem/path.d.ts.map +1 -0
  34. package/dist/VirtualFileSystem/path.js +49 -0
  35. package/dist/VirtualFileSystem/snapshot.d.ts +5 -0
  36. package/dist/VirtualFileSystem/snapshot.d.ts.map +1 -0
  37. package/dist/VirtualFileSystem/snapshot.js +59 -0
  38. package/dist/VirtualFileSystem/tree.d.ts +3 -0
  39. package/dist/VirtualFileSystem/tree.d.ts.map +1 -0
  40. package/dist/VirtualFileSystem/tree.js +19 -0
  41. package/dist/VirtualShell/index.d.ts +86 -0
  42. package/dist/VirtualShell/index.d.ts.map +1 -0
  43. package/dist/VirtualShell/index.js +129 -0
  44. package/dist/VirtualShell/shell.d.ts +5 -0
  45. package/dist/VirtualShell/shell.d.ts.map +1 -0
  46. package/dist/VirtualShell/shell.js +473 -0
  47. package/dist/VirtualShell/shellParser.d.ts +4 -0
  48. package/dist/VirtualShell/shellParser.d.ts.map +1 -0
  49. package/dist/VirtualShell/shellParser.js +207 -0
  50. package/dist/VirtualUserManager/index.d.ts +168 -0
  51. package/dist/VirtualUserManager/index.d.ts.map +1 -0
  52. package/dist/VirtualUserManager/index.js +375 -0
  53. package/dist/commands/adduser.d.ts +3 -0
  54. package/dist/commands/adduser.d.ts.map +1 -0
  55. package/dist/commands/adduser.js +18 -0
  56. package/dist/commands/cat.d.ts +3 -0
  57. package/dist/commands/cat.d.ts.map +1 -0
  58. package/dist/commands/cat.js +15 -0
  59. package/dist/commands/cd.d.ts +3 -0
  60. package/dist/commands/cd.d.ts.map +1 -0
  61. package/dist/commands/cd.js +17 -0
  62. package/dist/commands/clear.d.ts +3 -0
  63. package/dist/commands/clear.d.ts.map +1 -0
  64. package/dist/commands/clear.js +5 -0
  65. package/dist/commands/command-helpers.d.ts +23 -0
  66. package/dist/commands/command-helpers.d.ts.map +1 -0
  67. package/dist/commands/command-helpers.js +139 -0
  68. package/dist/commands/curl.d.ts +3 -0
  69. package/dist/commands/curl.d.ts.map +1 -0
  70. package/dist/commands/curl.js +44 -0
  71. package/dist/commands/deluser.d.ts +3 -0
  72. package/dist/commands/deluser.d.ts.map +1 -0
  73. package/dist/commands/deluser.js +15 -0
  74. package/dist/commands/echo.d.ts +3 -0
  75. package/dist/commands/echo.d.ts.map +1 -0
  76. package/dist/commands/echo.js +22 -0
  77. package/dist/commands/env.d.ts +3 -0
  78. package/dist/commands/env.d.ts.map +1 -0
  79. package/dist/commands/env.js +18 -0
  80. package/dist/commands/exit.d.ts +3 -0
  81. package/dist/commands/exit.d.ts.map +1 -0
  82. package/dist/commands/exit.js +5 -0
  83. package/dist/commands/export.d.ts +3 -0
  84. package/dist/commands/export.d.ts.map +1 -0
  85. package/dist/commands/export.js +34 -0
  86. package/dist/commands/grep.d.ts +3 -0
  87. package/dist/commands/grep.d.ts.map +1 -0
  88. package/dist/commands/grep.js +69 -0
  89. package/dist/commands/help.d.ts +3 -0
  90. package/dist/commands/help.d.ts.map +1 -0
  91. package/dist/commands/help.js +7 -0
  92. package/dist/commands/helpers.d.ts +26 -0
  93. package/dist/commands/helpers.d.ts.map +1 -0
  94. package/dist/commands/helpers.js +160 -0
  95. package/dist/commands/hostname.d.ts +3 -0
  96. package/dist/commands/hostname.d.ts.map +1 -0
  97. package/dist/commands/hostname.js +5 -0
  98. package/dist/commands/htop.d.ts +3 -0
  99. package/dist/commands/htop.d.ts.map +1 -0
  100. package/dist/commands/htop.js +10 -0
  101. package/dist/commands/index.d.ts +8 -0
  102. package/dist/commands/index.d.ts.map +1 -0
  103. package/dist/commands/index.js +212 -0
  104. package/dist/commands/ls.d.ts +3 -0
  105. package/dist/commands/ls.d.ts.map +1 -0
  106. package/dist/commands/ls.js +47 -0
  107. package/dist/commands/mkdir.d.ts +3 -0
  108. package/dist/commands/mkdir.d.ts.map +1 -0
  109. package/dist/commands/mkdir.js +21 -0
  110. package/dist/commands/nano.d.ts +3 -0
  111. package/dist/commands/nano.d.ts.map +1 -0
  112. package/dist/commands/nano.js +27 -0
  113. package/dist/commands/neofetch.d.ts +3 -0
  114. package/dist/commands/neofetch.d.ts.map +1 -0
  115. package/dist/commands/neofetch.js +32 -0
  116. package/dist/commands/pwd.d.ts +3 -0
  117. package/dist/commands/pwd.d.ts.map +1 -0
  118. package/dist/commands/pwd.js +5 -0
  119. package/dist/commands/rm.d.ts +3 -0
  120. package/dist/commands/rm.d.ts.map +1 -0
  121. package/dist/commands/rm.js +29 -0
  122. package/dist/commands/set.d.ts +7 -0
  123. package/dist/commands/set.d.ts.map +1 -0
  124. package/dist/commands/set.js +64 -0
  125. package/dist/commands/sh.d.ts +4 -0
  126. package/dist/commands/sh.d.ts.map +1 -0
  127. package/dist/commands/sh.js +45 -0
  128. package/dist/commands/su.d.ts +3 -0
  129. package/dist/commands/su.d.ts.map +1 -0
  130. package/dist/commands/su.js +24 -0
  131. package/dist/commands/sudo.d.ts +3 -0
  132. package/dist/commands/sudo.d.ts.map +1 -0
  133. package/dist/commands/sudo.js +47 -0
  134. package/dist/commands/touch.d.ts +3 -0
  135. package/dist/commands/touch.d.ts.map +1 -0
  136. package/dist/commands/touch.js +18 -0
  137. package/dist/commands/tree.d.ts +3 -0
  138. package/dist/commands/tree.d.ts.map +1 -0
  139. package/dist/commands/tree.js +11 -0
  140. package/dist/commands/unset.d.ts +3 -0
  141. package/dist/commands/unset.d.ts.map +1 -0
  142. package/dist/commands/unset.js +15 -0
  143. package/dist/commands/wget.d.ts +3 -0
  144. package/dist/commands/wget.d.ts.map +1 -0
  145. package/dist/commands/wget.js +113 -0
  146. package/dist/commands/who.d.ts +3 -0
  147. package/dist/commands/who.d.ts.map +1 -0
  148. package/dist/commands/who.js +15 -0
  149. package/dist/commands/whoami.d.ts +3 -0
  150. package/dist/commands/whoami.d.ts.map +1 -0
  151. package/dist/commands/whoami.js +5 -0
  152. package/dist/index.d.ts +11 -0
  153. package/dist/index.d.ts.map +1 -0
  154. package/dist/index.js +7 -0
  155. package/dist/modules/neofetch.d.ts +19 -0
  156. package/dist/modules/neofetch.d.ts.map +1 -0
  157. package/dist/modules/neofetch.js +284 -0
  158. package/dist/modules/shellInteractive.d.ts +6 -0
  159. package/dist/modules/shellInteractive.d.ts.map +1 -0
  160. package/dist/modules/shellInteractive.js +26 -0
  161. package/dist/modules/shellRuntime.d.ts +11 -0
  162. package/dist/modules/shellRuntime.d.ts.map +1 -0
  163. package/dist/modules/shellRuntime.js +52 -0
  164. package/dist/standalone.d.ts +2 -0
  165. package/dist/standalone.d.ts.map +1 -0
  166. package/dist/standalone.js +25 -0
  167. package/dist/types/commands.d.ts +89 -0
  168. package/dist/types/commands.d.ts.map +1 -0
  169. package/dist/types/commands.js +0 -0
  170. package/dist/types/pipeline.d.ts +23 -0
  171. package/dist/types/pipeline.d.ts.map +1 -0
  172. package/dist/types/pipeline.js +0 -0
  173. package/dist/types/streams.d.ts +32 -0
  174. package/dist/types/streams.d.ts.map +1 -0
  175. package/dist/types/streams.js +0 -0
  176. package/dist/types/vfs.d.ts +71 -0
  177. package/dist/types/vfs.d.ts.map +1 -0
  178. package/dist/types/vfs.js +0 -0
  179. package/package.json +4 -2
  180. package/src/VirtualShell/shell.ts +3 -3
  181. package/src/commands/neofetch.ts +1 -1
  182. package/{modules → src/modules}/neofetch.ts +56 -51
  183. package/{modules → src/modules}/shellInteractive.ts +16 -4
  184. package/tsconfig.json +19 -8
  185. /package/{modules → src/modules}/shellRuntime.ts +0 -0
@@ -0,0 +1,375 @@
1
+ import { randomBytes, randomUUID, scryptSync } from "node:crypto";
2
+ import * as path from "node:path";
3
+ /**
4
+ * Persistent user, sudoers, and active-session manager for the shell runtime.
5
+ *
6
+ * Passwords are hashed with scrypt and stored in the backing virtual filesystem.
7
+ */
8
+ export class VirtualUserManager {
9
+ vfs;
10
+ defaultRootPassword;
11
+ autoSudoForNewUsers;
12
+ usersPath = "/virtual-env-js/.auth/htpasswd";
13
+ sudoersPath = "/virtual-env-js/.auth/sudoers";
14
+ quotasPath = "/virtual-env-js/.auth/quotas";
15
+ authDirPath = "/virtual-env-js/.auth";
16
+ users = new Map();
17
+ sudoers = new Set();
18
+ quotas = new Map();
19
+ activeSessions = new Map();
20
+ nextTty = 0;
21
+ /**
22
+ * Creates a user manager instance backed by a virtual filesystem.
23
+ *
24
+ * @param vfs Backing virtual filesystem used for persistence.
25
+ * @param defaultRootPassword Initial root password used when root is created.
26
+ * @param autoSudoForNewUsers Whether newly created users are added to sudoers.
27
+ */
28
+ constructor(vfs, defaultRootPassword = "root", autoSudoForNewUsers = true) {
29
+ this.vfs = vfs;
30
+ this.defaultRootPassword = defaultRootPassword;
31
+ this.autoSudoForNewUsers = autoSudoForNewUsers;
32
+ }
33
+ /**
34
+ * Loads users/sudoers from disk and ensures root account exists.
35
+ */
36
+ async initialize() {
37
+ this.loadFromVfs();
38
+ this.loadSudoersFromVfs();
39
+ this.loadQuotasFromVfs();
40
+ this.users.set("root", this.createRecord("root", this.defaultRootPassword));
41
+ this.sudoers.add("root");
42
+ await this.persist();
43
+ }
44
+ /**
45
+ * Sets max allowed bytes under /home/<username>.
46
+ *
47
+ * @param username Target username.
48
+ * @param maxBytes Quota ceiling in bytes.
49
+ */
50
+ async setQuotaBytes(username, maxBytes) {
51
+ this.validateUsername(username);
52
+ if (!this.users.has(username)) {
53
+ throw new Error(`quota: user '${username}' does not exist`);
54
+ }
55
+ if (!Number.isFinite(maxBytes) || maxBytes < 0) {
56
+ throw new Error("quota: maxBytes must be a non-negative number");
57
+ }
58
+ this.quotas.set(username, Math.floor(maxBytes));
59
+ await this.persist();
60
+ }
61
+ /**
62
+ * Removes quota for a user.
63
+ *
64
+ * @param username Target username.
65
+ */
66
+ async clearQuota(username) {
67
+ this.validateUsername(username);
68
+ this.quotas.delete(username);
69
+ await this.persist();
70
+ }
71
+ /**
72
+ * Gets configured quota in bytes for a user.
73
+ *
74
+ * @param username Target username.
75
+ * @returns Quota in bytes, or null when unlimited.
76
+ */
77
+ getQuotaBytes(username) {
78
+ return this.quotas.get(username) ?? null;
79
+ }
80
+ /**
81
+ * Computes current usage under /home/<username>.
82
+ *
83
+ * @param username Target username.
84
+ * @returns Current usage in bytes.
85
+ */
86
+ getUsageBytes(username) {
87
+ const homePath = `/home/${username}`;
88
+ if (!this.vfs.exists(homePath)) {
89
+ return 0;
90
+ }
91
+ return this.vfs.getUsageBytes(homePath);
92
+ }
93
+ /**
94
+ * Validates that writing file content would not exceed user quota.
95
+ *
96
+ * Quotas are enforced only for writes inside /home/<username>.
97
+ *
98
+ * @param username Authenticated user.
99
+ * @param targetPath Target file path.
100
+ * @param nextContent New file content.
101
+ */
102
+ assertWriteWithinQuota(username, targetPath, nextContent) {
103
+ const quota = this.quotas.get(username);
104
+ if (quota === undefined) {
105
+ return;
106
+ }
107
+ const normalizedPath = normalizeVfsPath(targetPath);
108
+ const homePath = normalizeVfsPath(`/home/${username}`);
109
+ const inUserHome = normalizedPath === homePath || normalizedPath.startsWith(`${homePath}/`);
110
+ if (!inUserHome) {
111
+ return;
112
+ }
113
+ const currentUsage = this.getUsageBytes(username);
114
+ let existingSize = 0;
115
+ if (this.vfs.exists(normalizedPath)) {
116
+ const existing = this.vfs.stat(normalizedPath);
117
+ if (existing.type === "file") {
118
+ existingSize = existing.size;
119
+ }
120
+ }
121
+ const incomingSize = Buffer.isBuffer(nextContent)
122
+ ? nextContent.length
123
+ : Buffer.byteLength(nextContent, "utf8");
124
+ const projectedUsage = currentUsage - existingSize + incomingSize;
125
+ if (projectedUsage > quota) {
126
+ throw new Error(`quota exceeded for '${username}': ${projectedUsage}/${quota} bytes`);
127
+ }
128
+ }
129
+ /**
130
+ * Verifies plaintext password against stored record.
131
+ *
132
+ * @param username User login name.
133
+ * @param password Plaintext password candidate.
134
+ * @returns True when credentials are valid.
135
+ */
136
+ verifyPassword(username, password) {
137
+ const record = this.users.get(username);
138
+ if (!record) {
139
+ return false;
140
+ }
141
+ return this.hashPassword(password, record.salt) === record.passwordHash;
142
+ }
143
+ /**
144
+ * Creates user, home directory, and sudo access entry.
145
+ *
146
+ * @param username New username.
147
+ * @param password Initial plaintext password.
148
+ */
149
+ async addUser(username, password) {
150
+ this.validateUsername(username);
151
+ this.validatePassword(password);
152
+ if (this.users.has(username)) {
153
+ throw new Error(`adduser: user '${username}' already exists`);
154
+ }
155
+ this.users.set(username, this.createRecord(username, password));
156
+ if (this.autoSudoForNewUsers) {
157
+ this.sudoers.add(username);
158
+ }
159
+ const homePath = `/home/${username}`;
160
+ if (!this.vfs.exists(homePath)) {
161
+ this.vfs.mkdir(homePath, 0o755);
162
+ this.vfs.writeFile(`${homePath}/README.txt`, `Welcome to the virtual environment, ${username}`);
163
+ }
164
+ await this.persist();
165
+ }
166
+ /**
167
+ * Deletes existing non-root user account.
168
+ *
169
+ * @param username Username to remove.
170
+ */
171
+ async deleteUser(username) {
172
+ this.validateUsername(username);
173
+ if (username === "root") {
174
+ throw new Error("deluser: cannot delete root");
175
+ }
176
+ if (!this.users.delete(username)) {
177
+ throw new Error(`deluser: user '${username}' does not exist`);
178
+ }
179
+ this.sudoers.delete(username);
180
+ await this.persist();
181
+ }
182
+ /**
183
+ * Checks whether user is member of sudoers set.
184
+ *
185
+ * @param username Username to test.
186
+ * @returns True when user can run sudo.
187
+ */
188
+ isSudoer(username) {
189
+ return this.sudoers.has(username);
190
+ }
191
+ /**
192
+ * Grants sudo access to existing user.
193
+ *
194
+ * @param username Username to promote.
195
+ */
196
+ async addSudoer(username) {
197
+ this.validateUsername(username);
198
+ if (!this.users.has(username)) {
199
+ throw new Error(`sudoers: user '${username}' does not exist`);
200
+ }
201
+ this.sudoers.add(username);
202
+ await this.persist();
203
+ }
204
+ /**
205
+ * Revokes sudo access from user.
206
+ *
207
+ * @param username Username to demote.
208
+ */
209
+ async removeSudoer(username) {
210
+ this.validateUsername(username);
211
+ if (username === "root") {
212
+ throw new Error("sudoers: cannot remove root");
213
+ }
214
+ this.sudoers.delete(username);
215
+ await this.persist();
216
+ }
217
+ /**
218
+ * Registers active session and allocates tty id.
219
+ *
220
+ * @param username Session username.
221
+ * @param remoteAddress Session source address.
222
+ * @returns Registered session descriptor.
223
+ */
224
+ registerSession(username, remoteAddress) {
225
+ const session = {
226
+ id: randomUUID(),
227
+ username,
228
+ tty: `pts/${this.nextTty++}`,
229
+ remoteAddress,
230
+ startedAt: new Date().toISOString(),
231
+ };
232
+ this.activeSessions.set(session.id, session);
233
+ return session;
234
+ }
235
+ /**
236
+ * Unregisters active session when connection closes.
237
+ *
238
+ * @param sessionId Session identifier; ignored when nullish.
239
+ */
240
+ unregisterSession(sessionId) {
241
+ if (!sessionId) {
242
+ return;
243
+ }
244
+ this.activeSessions.delete(sessionId);
245
+ }
246
+ /**
247
+ * Updates username/address metadata for existing session.
248
+ *
249
+ * @param sessionId Session identifier; ignored when nullish.
250
+ * @param username New username value.
251
+ * @param remoteAddress New remote address value.
252
+ */
253
+ updateSession(sessionId, username, remoteAddress) {
254
+ if (!sessionId) {
255
+ return;
256
+ }
257
+ const session = this.activeSessions.get(sessionId);
258
+ if (!session) {
259
+ return;
260
+ }
261
+ this.activeSessions.set(sessionId, {
262
+ ...session,
263
+ username,
264
+ remoteAddress,
265
+ });
266
+ }
267
+ /**
268
+ * Lists active sessions sorted by start time.
269
+ *
270
+ * @returns Snapshot of active session descriptors.
271
+ */
272
+ listActiveSessions() {
273
+ return Array.from(this.activeSessions.values()).sort((left, right) => left.startedAt.localeCompare(right.startedAt));
274
+ }
275
+ loadFromVfs() {
276
+ this.users.clear();
277
+ if (!this.vfs.exists(this.usersPath)) {
278
+ return;
279
+ }
280
+ const raw = this.vfs.readFile(this.usersPath);
281
+ for (const line of raw.split("\n")) {
282
+ const trimmed = line.trim();
283
+ if (trimmed.length === 0) {
284
+ continue;
285
+ }
286
+ const parts = trimmed.split(":");
287
+ if (parts.length < 3) {
288
+ continue;
289
+ }
290
+ const [username, salt, passwordHash] = parts;
291
+ if (!username || !salt || !passwordHash) {
292
+ continue;
293
+ }
294
+ this.users.set(username, { username, salt, passwordHash });
295
+ }
296
+ }
297
+ loadSudoersFromVfs() {
298
+ this.sudoers.clear();
299
+ if (!this.vfs.exists(this.sudoersPath)) {
300
+ return;
301
+ }
302
+ const raw = this.vfs.readFile(this.sudoersPath);
303
+ for (const line of raw.split("\n")) {
304
+ const username = line.trim();
305
+ if (username.length > 0) {
306
+ this.sudoers.add(username);
307
+ }
308
+ }
309
+ }
310
+ loadQuotasFromVfs() {
311
+ this.quotas.clear();
312
+ if (!this.vfs.exists(this.quotasPath)) {
313
+ return;
314
+ }
315
+ const raw = this.vfs.readFile(this.quotasPath);
316
+ for (const line of raw.split("\n")) {
317
+ const trimmed = line.trim();
318
+ if (trimmed.length === 0) {
319
+ continue;
320
+ }
321
+ const [username, value] = trimmed.split(":");
322
+ const bytes = Number.parseInt(value ?? "", 10);
323
+ if (!username || !Number.isFinite(bytes) || bytes < 0) {
324
+ continue;
325
+ }
326
+ this.quotas.set(username, bytes);
327
+ }
328
+ }
329
+ async persist() {
330
+ if (!this.vfs.exists(this.authDirPath)) {
331
+ this.vfs.mkdir(this.authDirPath, 0o700);
332
+ }
333
+ const content = Array.from(this.users.values())
334
+ .sort((left, right) => left.username.localeCompare(right.username))
335
+ .map((record) => [record.username, record.salt, record.passwordHash].join(":"))
336
+ .join("\n");
337
+ this.vfs.writeFile(this.usersPath, content.length > 0 ? `${content}\n` : "", { mode: 0o600 });
338
+ const sudoersContent = Array.from(this.sudoers.values()).sort().join("\n");
339
+ this.vfs.writeFile(this.sudoersPath, sudoersContent.length > 0 ? `${sudoersContent}\n` : "", { mode: 0o600 });
340
+ const quotasContent = Array.from(this.quotas.entries())
341
+ .sort(([left], [right]) => left.localeCompare(right))
342
+ .map(([username, maxBytes]) => `${username}:${maxBytes}`)
343
+ .join("\n");
344
+ this.vfs.writeFile(this.quotasPath, quotasContent.length > 0 ? `${quotasContent}\n` : "", { mode: 0o600 });
345
+ await this.vfs.flushMirror();
346
+ }
347
+ createRecord(username, password) {
348
+ const salt = randomBytes(16).toString("hex");
349
+ return {
350
+ username,
351
+ salt,
352
+ passwordHash: this.hashPassword(password, salt),
353
+ };
354
+ }
355
+ hashPassword(password, salt) {
356
+ return scryptSync(password, salt, 64).toString("hex");
357
+ }
358
+ validateUsername(username) {
359
+ if (!username || username.trim() === "") {
360
+ throw new Error("invalid username");
361
+ }
362
+ if (!/^[a-z_][a-z0-9_-]{0,31}$/i.test(username)) {
363
+ throw new Error("invalid username");
364
+ }
365
+ }
366
+ validatePassword(password) {
367
+ if (!password || password.trim() === "") {
368
+ throw new Error("invalid password");
369
+ }
370
+ }
371
+ }
372
+ function normalizeVfsPath(targetPath) {
373
+ const normalized = path.posix.normalize(targetPath);
374
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
375
+ }
@@ -0,0 +1,3 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ export declare const adduserCommand: ShellModule;
3
+ //# sourceMappingURL=adduser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adduser.d.ts","sourceRoot":"","sources":["../../src/commands/adduser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,cAAc,EAAE,WAmB5B,CAAC"}
@@ -0,0 +1,18 @@
1
+ export const adduserCommand = {
2
+ name: "adduser",
3
+ params: ["<username> <password>"],
4
+ run: async ({ authUser, shell, args }) => {
5
+ if (authUser !== "root") {
6
+ return { stderr: "adduser: permission denied", exitCode: 1 };
7
+ }
8
+ const [username, password] = args;
9
+ if (!username || !password) {
10
+ return {
11
+ stderr: "adduser: usage: adduser <username> <password>",
12
+ exitCode: 1,
13
+ };
14
+ }
15
+ await shell.users.addUser(username, password);
16
+ return { stdout: `adduser: user '${username}' created`, exitCode: 0 };
17
+ },
18
+ };
@@ -0,0 +1,3 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ export declare const catCommand: ShellModule;
3
+ //# sourceMappingURL=cat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cat.d.ts","sourceRoot":"","sources":["../../src/commands/cat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,UAAU,EAAE,WAaxB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { getArg } from "./command-helpers";
2
+ import { assertPathAccess, resolveReadablePath } from "./helpers";
3
+ export const catCommand = {
4
+ name: "cat",
5
+ params: ["<file>"],
6
+ run: ({ authUser, shell, cwd, args }) => {
7
+ const fileArg = getArg(args, 0);
8
+ if (!fileArg) {
9
+ return { stderr: "cat: missing file operand", exitCode: 1 };
10
+ }
11
+ const target = resolveReadablePath(shell.vfs, cwd, fileArg);
12
+ assertPathAccess(authUser, target, "cat");
13
+ return { stdout: shell.vfs.readFile(target), exitCode: 0 };
14
+ },
15
+ };
@@ -0,0 +1,3 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ export declare const cdCommand: ShellModule;
3
+ //# sourceMappingURL=cd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cd.d.ts","sourceRoot":"","sources":["../../src/commands/cd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,eAAO,MAAM,SAAS,EAAE,WAiBvB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { assertPathAccess, resolvePath } from "./helpers";
2
+ export const cdCommand = {
3
+ name: "cd",
4
+ params: ["[path]"],
5
+ run: ({ authUser, shell, cwd, args, mode }) => {
6
+ const target = resolvePath(cwd, args[0] ?? "/virtual-env-js");
7
+ assertPathAccess(authUser, target, "cd");
8
+ const stats = shell.vfs.stat(target);
9
+ if (stats.type !== "directory") {
10
+ return { stderr: `cd: not a directory: ${target}`, exitCode: 1 };
11
+ }
12
+ if (mode === "exec") {
13
+ return { exitCode: 0 };
14
+ }
15
+ return { nextCwd: target, exitCode: 0 };
16
+ },
17
+ };
@@ -0,0 +1,3 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ export declare const clearCommand: ShellModule;
3
+ //# sourceMappingURL=clear.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clear.d.ts","sourceRoot":"","sources":["../../src/commands/clear.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,YAAY,EAAE,WAI1B,CAAC"}
@@ -0,0 +1,5 @@
1
+ export const clearCommand = {
2
+ name: "clear",
3
+ params: [],
4
+ run: () => ({ clearScreen: true, exitCode: 0 }),
5
+ };
@@ -0,0 +1,23 @@
1
+ type ArgParseOptions = {
2
+ flags?: string[];
3
+ flagsWithValue?: string[];
4
+ };
5
+ export declare function ifFlag(args: string[], flags: string | string[]): boolean;
6
+ export declare function getFlag(args: string[], flags: string | string[]): string | true | undefined;
7
+ export declare function getArg(args: string[], index: number, options?: ArgParseOptions): string | undefined;
8
+ /**
9
+ * Parse arguments into flags, flags with values, and positionals.
10
+ * @param args - Array of arguments to parse.
11
+ * @param options - Parsing options for flags and flags with values.
12
+ * @returns Parsed arguments as { flags, flagsWithValues, positionals }.
13
+ */
14
+ export declare function parseArgs(args: string[], options?: {
15
+ flags?: string[];
16
+ flagsWithValue?: string[];
17
+ }): {
18
+ flags: Set<string>;
19
+ flagsWithValues: Map<string, string>;
20
+ positionals: string[];
21
+ };
22
+ export {};
23
+ //# sourceMappingURL=command-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/command-helpers.ts"],"names":[],"mappings":"AAAA,KAAK,eAAe,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC;AA+EF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAYxE;AAED,wBAAgB,OAAO,CACtB,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GACtB,MAAM,GAAG,IAAI,GAAG,SAAS,CA0B3B;AAED,wBAAgB,MAAM,CACrB,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAoB,GAC3B,MAAM,GAAG,SAAS,CAGpB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAC3D;IACF,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,WAAW,EAAE,MAAM,EAAE,CAAC;CACtB,CAiDA"}
@@ -0,0 +1,139 @@
1
+ function toFlagList(flags) {
2
+ return Array.isArray(flags) ? flags : [flags];
3
+ }
4
+ function matchFlagToken(token, flag) {
5
+ if (token === flag) {
6
+ return { matched: true, inlineValue: null };
7
+ }
8
+ const prefix = `${flag}=`;
9
+ if (token.startsWith(prefix)) {
10
+ return { matched: true, inlineValue: token.slice(prefix.length) };
11
+ }
12
+ return { matched: false, inlineValue: null };
13
+ }
14
+ function collectPositionals(args, options = {}) {
15
+ const boolFlags = new Set(options.flags ?? []);
16
+ const valueFlags = new Set(options.flagsWithValue ?? []);
17
+ const positionals = [];
18
+ let passthrough = false;
19
+ for (let index = 0; index < args.length; index += 1) {
20
+ const arg = args[index];
21
+ if (passthrough) {
22
+ positionals.push(arg);
23
+ continue;
24
+ }
25
+ if (arg === "--") {
26
+ passthrough = true;
27
+ continue;
28
+ }
29
+ let consumed = false;
30
+ for (const flag of boolFlags) {
31
+ const { matched } = matchFlagToken(arg, flag);
32
+ if (matched) {
33
+ consumed = true;
34
+ break;
35
+ }
36
+ }
37
+ if (consumed) {
38
+ continue;
39
+ }
40
+ for (const flag of valueFlags) {
41
+ const match = matchFlagToken(arg, flag);
42
+ if (!match.matched) {
43
+ continue;
44
+ }
45
+ consumed = true;
46
+ if (match.inlineValue === null && index + 1 < args.length) {
47
+ index += 1;
48
+ }
49
+ break;
50
+ }
51
+ if (!consumed) {
52
+ positionals.push(arg);
53
+ }
54
+ }
55
+ return positionals;
56
+ }
57
+ export function ifFlag(args, flags) {
58
+ const allFlags = toFlagList(flags);
59
+ for (const arg of args) {
60
+ for (const flag of allFlags) {
61
+ if (matchFlagToken(arg, flag).matched) {
62
+ return true;
63
+ }
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+ export function getFlag(args, flags) {
69
+ const allFlags = toFlagList(flags);
70
+ for (let index = 0; index < args.length; index += 1) {
71
+ const arg = args[index];
72
+ for (const flag of allFlags) {
73
+ const match = matchFlagToken(arg, flag);
74
+ if (!match.matched) {
75
+ continue;
76
+ }
77
+ if (match.inlineValue !== null) {
78
+ return match.inlineValue;
79
+ }
80
+ const next = args[index + 1];
81
+ if (next !== undefined && next !== "--") {
82
+ return next;
83
+ }
84
+ return true;
85
+ }
86
+ }
87
+ return undefined;
88
+ }
89
+ export function getArg(args, index, options = {}) {
90
+ const positionals = collectPositionals(args, options);
91
+ return positionals[index];
92
+ }
93
+ /**
94
+ * Parse arguments into flags, flags with values, and positionals.
95
+ * @param args - Array of arguments to parse.
96
+ * @param options - Parsing options for flags and flags with values.
97
+ * @returns Parsed arguments as { flags, flagsWithValues, positionals }.
98
+ */
99
+ export function parseArgs(args, options = {}) {
100
+ const flags = new Set();
101
+ const flagsWithValues = new Map();
102
+ const positionals = [];
103
+ const boolFlags = new Set(options.flags ?? []);
104
+ const valueFlags = new Set(options.flagsWithValue ?? []);
105
+ let passthrough = false;
106
+ for (let index = 0; index < args.length; index += 1) {
107
+ const arg = args[index];
108
+ if (passthrough) {
109
+ positionals.push(arg);
110
+ continue;
111
+ }
112
+ if (arg === "--") {
113
+ passthrough = true;
114
+ continue;
115
+ }
116
+ if (boolFlags.has(arg)) {
117
+ flags.add(arg);
118
+ continue;
119
+ }
120
+ if (valueFlags.has(arg)) {
121
+ const next = args[index + 1];
122
+ if (next && !next.startsWith("-")) {
123
+ flagsWithValues.set(arg, next);
124
+ index += 1;
125
+ }
126
+ else {
127
+ flagsWithValues.set(arg, "");
128
+ }
129
+ continue;
130
+ }
131
+ const inlineFlag = Array.from(valueFlags).find((flag) => arg.startsWith(`${flag}=`));
132
+ if (inlineFlag) {
133
+ flagsWithValues.set(inlineFlag, arg.slice(inlineFlag.length + 1));
134
+ continue;
135
+ }
136
+ positionals.push(arg);
137
+ }
138
+ return { flags, flagsWithValues, positionals };
139
+ }
@@ -0,0 +1,3 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ export declare const curlCommand: ShellModule;
3
+ //# sourceMappingURL=curl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curl.d.ts","sourceRoot":"","sources":["../../src/commands/curl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AASrD,eAAO,MAAM,WAAW,EAAE,WAiDzB,CAAC"}