typescript-virtual-container 1.2.7 → 1.2.9

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 (130) hide show
  1. package/README.md +457 -42
  2. package/dist/SSHMimic/executor.js +3 -5
  3. package/dist/VirtualFileSystem/binaryPack.d.ts +49 -0
  4. package/dist/VirtualFileSystem/binaryPack.d.ts.map +1 -0
  5. package/dist/VirtualFileSystem/binaryPack.js +193 -0
  6. package/dist/VirtualFileSystem/index.d.ts +7 -5
  7. package/dist/VirtualFileSystem/index.d.ts.map +1 -1
  8. package/dist/VirtualFileSystem/index.js +20 -9
  9. package/dist/VirtualPackageManager/index.d.ts +202 -0
  10. package/dist/VirtualPackageManager/index.d.ts.map +1 -0
  11. package/dist/VirtualPackageManager/index.js +676 -0
  12. package/dist/VirtualShell/index.d.ts +87 -12
  13. package/dist/VirtualShell/index.d.ts.map +1 -1
  14. package/dist/VirtualShell/index.js +83 -12
  15. package/dist/VirtualUserManager/index.d.ts +52 -20
  16. package/dist/VirtualUserManager/index.d.ts.map +1 -1
  17. package/dist/VirtualUserManager/index.js +54 -20
  18. package/dist/commands/alias.d.ts +4 -0
  19. package/dist/commands/alias.d.ts.map +1 -0
  20. package/dist/commands/alias.js +58 -0
  21. package/dist/commands/apt.d.ts +4 -0
  22. package/dist/commands/apt.d.ts.map +1 -0
  23. package/dist/commands/apt.js +182 -0
  24. package/dist/commands/cat.d.ts.map +1 -1
  25. package/dist/commands/cat.js +27 -8
  26. package/dist/commands/chmod.d.ts.map +1 -1
  27. package/dist/commands/chmod.js +52 -3
  28. package/dist/commands/command-helpers.d.ts +78 -4
  29. package/dist/commands/command-helpers.d.ts.map +1 -1
  30. package/dist/commands/command-helpers.js +78 -4
  31. package/dist/commands/curl.d.ts.map +1 -1
  32. package/dist/commands/curl.js +81 -29
  33. package/dist/commands/dpkg.d.ts +4 -0
  34. package/dist/commands/dpkg.d.ts.map +1 -0
  35. package/dist/commands/dpkg.js +144 -0
  36. package/dist/commands/echo.d.ts.map +1 -1
  37. package/dist/commands/echo.js +24 -12
  38. package/dist/commands/free.d.ts +3 -0
  39. package/dist/commands/free.d.ts.map +1 -0
  40. package/dist/commands/free.js +38 -0
  41. package/dist/commands/helpers.d.ts +3 -0
  42. package/dist/commands/helpers.d.ts.map +1 -1
  43. package/dist/commands/helpers.js +3 -0
  44. package/dist/commands/history.d.ts +3 -0
  45. package/dist/commands/history.d.ts.map +1 -0
  46. package/dist/commands/history.js +21 -0
  47. package/dist/commands/index.d.ts +8 -1
  48. package/dist/commands/index.d.ts.map +1 -1
  49. package/dist/commands/index.js +120 -11
  50. package/dist/commands/ls.d.ts.map +1 -1
  51. package/dist/commands/ls.js +4 -3
  52. package/dist/commands/lsb-release.d.ts +3 -0
  53. package/dist/commands/lsb-release.d.ts.map +1 -0
  54. package/dist/commands/lsb-release.js +50 -0
  55. package/dist/commands/man.d.ts +3 -0
  56. package/dist/commands/man.d.ts.map +1 -0
  57. package/dist/commands/man.js +155 -0
  58. package/dist/commands/neofetch.d.ts.map +1 -1
  59. package/dist/commands/neofetch.js +5 -0
  60. package/dist/commands/ping.d.ts.map +1 -1
  61. package/dist/commands/ping.js +5 -2
  62. package/dist/commands/ps.d.ts.map +1 -1
  63. package/dist/commands/ps.js +27 -6
  64. package/dist/commands/sh.d.ts.map +1 -1
  65. package/dist/commands/sh.js +29 -11
  66. package/dist/commands/source.d.ts +3 -0
  67. package/dist/commands/source.d.ts.map +1 -0
  68. package/dist/commands/source.js +31 -0
  69. package/dist/commands/test.d.ts +3 -0
  70. package/dist/commands/test.d.ts.map +1 -0
  71. package/dist/commands/test.js +92 -0
  72. package/dist/commands/type.d.ts +3 -0
  73. package/dist/commands/type.d.ts.map +1 -0
  74. package/dist/commands/type.js +34 -0
  75. package/dist/commands/uptime.d.ts +3 -0
  76. package/dist/commands/uptime.d.ts.map +1 -0
  77. package/dist/commands/uptime.js +40 -0
  78. package/dist/commands/wget.d.ts.map +1 -1
  79. package/dist/commands/wget.js +71 -100
  80. package/dist/commands/which.d.ts +3 -0
  81. package/dist/commands/which.d.ts.map +1 -0
  82. package/dist/commands/which.js +32 -0
  83. package/dist/index.d.ts +5 -2
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +2 -1
  86. package/dist/modules/linuxRootfs.d.ts +24 -0
  87. package/dist/modules/linuxRootfs.d.ts.map +1 -0
  88. package/dist/modules/linuxRootfs.js +297 -0
  89. package/dist/modules/neofetch.d.ts.map +1 -1
  90. package/dist/modules/neofetch.js +1 -0
  91. package/dist/standalone.js +4 -1
  92. package/package.json +2 -1
  93. package/src/SSHMimic/executor.ts +3 -5
  94. package/src/VirtualFileSystem/binaryPack.ts +219 -0
  95. package/src/VirtualFileSystem/index.ts +21 -11
  96. package/src/VirtualPackageManager/index.ts +820 -0
  97. package/src/VirtualShell/index.ts +104 -13
  98. package/src/VirtualUserManager/index.ts +55 -20
  99. package/src/commands/alias.ts +60 -0
  100. package/src/commands/apt.ts +198 -0
  101. package/src/commands/cat.ts +32 -8
  102. package/src/commands/chmod.ts +48 -3
  103. package/src/commands/command-helpers.ts +78 -4
  104. package/src/commands/curl.ts +78 -37
  105. package/src/commands/dpkg.ts +158 -0
  106. package/src/commands/echo.ts +30 -14
  107. package/src/commands/free.ts +40 -0
  108. package/src/commands/helpers.ts +8 -0
  109. package/src/commands/history.ts +29 -0
  110. package/src/commands/index.ts +116 -11
  111. package/src/commands/ls.ts +5 -4
  112. package/src/commands/lsb-release.ts +52 -0
  113. package/src/commands/man.ts +166 -0
  114. package/src/commands/neofetch.ts +5 -0
  115. package/src/commands/ping.ts +5 -2
  116. package/src/commands/ps.ts +28 -6
  117. package/src/commands/sh.ts +33 -11
  118. package/src/commands/source.ts +35 -0
  119. package/src/commands/test.ts +100 -0
  120. package/src/commands/type.ts +40 -0
  121. package/src/commands/uptime.ts +46 -0
  122. package/src/commands/wget.ts +70 -123
  123. package/src/commands/which.ts +34 -0
  124. package/src/index.ts +10 -0
  125. package/src/modules/linuxRootfs.ts +439 -0
  126. package/src/modules/neofetch.ts +1 -0
  127. package/src/standalone.ts +4 -1
  128. package/standalone.js +418 -103
  129. package/standalone.js.map +4 -4
  130. package/tests/new-features.test.ts +626 -0
@@ -1,4 +1,12 @@
1
+ /** biome-ignore-all lint/style/useNamingConvention: ENV VARIABLES */
2
+ import { executeStatements } from "../SSHMimic/executor";
3
+ import { parseScript } from "../VirtualShell/shellParser";
1
4
  import { adduserCommand } from "./adduser";
5
+ import { aliasCommand, unaliasCommand } from "./alias";
6
+ import { testCommand } from "./test";
7
+ import { sourceCommand } from "./source";
8
+ import { historyCommand } from "./history";
9
+ import { aptCacheCommand, aptCommand } from "./apt";
2
10
  import { awkCommand } from "./awk";
3
11
  import { base64Command } from "./base64";
4
12
  import { catCommand } from "./cat";
@@ -12,12 +20,14 @@ import { dateCommand } from "./date";
12
20
  import { deluserCommand } from "./deluser";
13
21
  import { dfCommand } from "./df";
14
22
  import { diffCommand } from "./diff";
23
+ import { dpkgCommand, dpkgQueryCommand } from "./dpkg";
15
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";
30
+ import { freeCommand } from "./free";
21
31
  import { grepCommand } from "./grep";
22
32
  import { groupsCommand } from "./groups";
23
33
  import { gunzipCommand, gzipCommand } from "./gzip";
@@ -29,6 +39,8 @@ import { idCommand } from "./id";
29
39
  import { killCommand } from "./kill";
30
40
  import { lnCommand } from "./ln";
31
41
  import { lsCommand } from "./ls";
42
+ import { lsbReleaseCommand } from "./lsb-release";
43
+ import { manCommand } from "./man";
32
44
  import { mkdirCommand } from "./mkdir";
33
45
  import { mvCommand } from "./mv";
34
46
  import { nanoCommand } from "./nano";
@@ -51,11 +63,14 @@ import { teeCommand } from "./tee";
51
63
  import { touchCommand } from "./touch";
52
64
  import { trCommand } from "./tr";
53
65
  import { treeCommand } from "./tree";
66
+ import { typeCommand } from "./type";
54
67
  import { unameCommand } from "./uname";
55
68
  import { uniqCommand } from "./uniq";
56
69
  import { unsetCommand } from "./unset";
70
+ import { uptimeCommand } from "./uptime";
57
71
  import { wcCommand } from "./wc";
58
72
  import { wgetCommand } from "./wget";
73
+ import { whichCommand } from "./which";
59
74
  import { whoCommand } from "./who";
60
75
  import { whoamiCommand } from "./whoami";
61
76
  import { xargsCommand } from "./xargs";
@@ -85,6 +100,13 @@ const BASE_COMMANDS = [
85
100
  adduserCommand, passwdCommand, deluserCommand, sudoCommand, suCommand,
86
101
  // Misc
87
102
  neofetchCommand,
103
+ // Package management
104
+ aptCommand, aptCacheCommand, dpkgCommand, dpkgQueryCommand,
105
+ // Shell (extended)
106
+ whichCommand, typeCommand, manCommand, aliasCommand, unaliasCommand,
107
+ testCommand, sourceCommand, historyCommand,
108
+ // System (extended)
109
+ uptimeCommand, freeCommand, lsbReleaseCommand,
88
110
  ];
89
111
  const customCommands = [];
90
112
  const commandRegistry = new Map();
@@ -183,21 +205,108 @@ export function makeDefaultEnv(authUser, hostname) {
183
205
  lastExitCode: 0,
184
206
  };
185
207
  }
208
+ /**
209
+ * Execute a pre-parsed command directly by name and argument list.
210
+ *
211
+ * Unlike `runCommand`, this function does NOT re-join name+args into a string
212
+ * and re-parse — so arguments that contain special characters (`;`, `|`, `>`,
213
+ * quotes) are passed through verbatim. Use this from the pipeline executor.
214
+ */
215
+ export async function runCommandDirect(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
216
+ // Alias expansion on the command name
217
+ const aliasVal = env.vars[`__alias_${name}`];
218
+ if (aliasVal) {
219
+ // Alias may expand to a multi-word command — re-route through runCommand
220
+ return runCommand(`${aliasVal} ${args.join(" ")}`, authUser, hostname, mode, cwd, shell, stdin, env);
221
+ }
222
+ const mod = resolveModule(name);
223
+ if (!mod)
224
+ return { stderr: `${name}: command not found`, exitCode: 127 };
225
+ try {
226
+ return await mod.run({
227
+ authUser,
228
+ hostname,
229
+ activeSessions: shell.users.listActiveSessions(),
230
+ rawInput: [name, ...args].join(" "),
231
+ mode,
232
+ args,
233
+ stdin,
234
+ cwd,
235
+ shell,
236
+ env,
237
+ });
238
+ }
239
+ catch (error) {
240
+ return { stderr: error instanceof Error ? error.message : "Command failed", exitCode: 1 };
241
+ }
242
+ }
186
243
  export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell, stdin, env) {
187
244
  const trimmed = rawInput.trim();
188
245
  if (trimmed.length === 0)
189
246
  return { exitCode: 0 };
190
247
  const shellEnv = env ?? makeDefaultEnv(authUser, hostname);
248
+ // ── $(cmd) command substitution ──────────────────────────────────────────
249
+ let expanded = trimmed;
250
+ if (expanded.includes("$(")) {
251
+ // Only substitute $(…) that are NOT inside single quotes
252
+ // Strategy: walk char by char, track single-quote state
253
+ let result = "";
254
+ let inSingle = false;
255
+ let i = 0;
256
+ while (i < expanded.length) {
257
+ const ch = expanded[i];
258
+ if (ch === "'" && !inSingle) {
259
+ inSingle = true;
260
+ result += ch;
261
+ i++;
262
+ continue;
263
+ }
264
+ if (ch === "'" && inSingle) {
265
+ inSingle = false;
266
+ result += ch;
267
+ i++;
268
+ continue;
269
+ }
270
+ if (!inSingle && ch === "$" && expanded[i + 1] === "(") {
271
+ // Find matching closing )
272
+ let depth = 0;
273
+ let j = i + 1;
274
+ while (j < expanded.length) {
275
+ if (expanded[j] === "(")
276
+ depth++;
277
+ else if (expanded[j] === ")") {
278
+ depth--;
279
+ if (depth === 0)
280
+ break;
281
+ }
282
+ j++;
283
+ }
284
+ const sub = expanded.slice(i + 2, j).trim();
285
+ const subResult = await runCommand(sub, authUser, hostname, mode, cwd, shell, undefined, shellEnv);
286
+ const subOut = (subResult.stdout ?? "").replace(/\n$/, "");
287
+ result += subOut;
288
+ i = j + 1;
289
+ continue;
290
+ }
291
+ result += ch;
292
+ i++;
293
+ }
294
+ expanded = result;
295
+ }
296
+ // ── alias expansion ───────────────────────────────────────────────────────
297
+ const firstWord = expanded.split(/\s+/)[0] ?? "";
298
+ const aliasVal = shellEnv.vars[`__alias_${firstWord}`];
299
+ if (aliasVal) {
300
+ expanded = expanded.replace(firstWord, aliasVal);
301
+ }
191
302
  // Detect shell operators
192
- if (/(?<![|&])[|](?![|])/.test(trimmed) ||
193
- trimmed.includes(">") ||
194
- trimmed.includes("<") ||
195
- trimmed.includes("&&") ||
196
- trimmed.includes("||") ||
197
- trimmed.includes(";")) {
198
- const { parseScript } = await import("../VirtualShell/shellParser");
199
- const { executeStatements } = await import("../SSHMimic/executor");
200
- const script = parseScript(trimmed);
303
+ if (/(?<![|&])[|](?![|])/.test(expanded) ||
304
+ expanded.includes(">") ||
305
+ expanded.includes("<") ||
306
+ expanded.includes("&&") ||
307
+ expanded.includes("||") ||
308
+ expanded.includes(";")) {
309
+ const script = parseScript(expanded);
201
310
  if (!script.isValid)
202
311
  return { stderr: script.error || "Syntax error", exitCode: 1 };
203
312
  try {
@@ -207,7 +316,7 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
207
316
  return { stderr: error instanceof Error ? error.message : "Execution failed", exitCode: 1 };
208
317
  }
209
318
  }
210
- const { commandName, args } = parseInput(trimmed);
319
+ const { commandName, args } = parseInput(expanded);
211
320
  const mod = resolveModule(commandName);
212
321
  if (!mod)
213
322
  return { stderr: `${commandName}: command not found`, exitCode: 127 };
@@ -216,7 +325,7 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
216
325
  authUser,
217
326
  hostname,
218
327
  activeSessions: shell.users.listActiveSessions(),
219
- rawInput: trimmed,
328
+ rawInput: expanded,
220
329
  mode,
221
330
  args,
222
331
  stdin,
@@ -1 +1 @@
1
- {"version":3,"file":"ls.d.ts","sourceRoot":"","sources":["../../src/commands/ls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA4BrD,eAAO,MAAM,SAAS,EAAE,WAyBvB,CAAC"}
1
+ {"version":3,"file":"ls.d.ts","sourceRoot":"","sources":["../../src/commands/ls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA4BrD,eAAO,MAAM,SAAS,EAAE,WA0BvB,CAAC"}
@@ -25,15 +25,16 @@ export const lsCommand = {
25
25
  name: "ls",
26
26
  description: "List directory contents",
27
27
  category: "navigation",
28
- params: ["[path]"],
28
+ params: ["[-la] [path]"],
29
29
  run: ({ authUser, shell, cwd, args }) => {
30
30
  const longFormat = ifFlag(args, ["-l", "--long"]);
31
- const targetArg = getArg(args, 0, { flags: ["-l", "--long"] });
31
+ const showHidden = ifFlag(args, ["-a", "--all"]);
32
+ const targetArg = getArg(args, 0, { flags: ["-l", "--long", "-a", "--all", "-la", "-al"] });
32
33
  const target = resolvePath(cwd, targetArg ?? cwd);
33
34
  assertPathAccess(authUser, target, "ls");
34
35
  const items = shell.vfs
35
36
  .list(target)
36
- .filter((name) => !name.startsWith("."));
37
+ .filter((name) => showHidden || !name.startsWith("."));
37
38
  const rendered = longFormat
38
39
  ? items
39
40
  .map((name) => {
@@ -0,0 +1,3 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ export declare const lsbReleaseCommand: ShellModule;
3
+ //# sourceMappingURL=lsb-release.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lsb-release.d.ts","sourceRoot":"","sources":["../../src/commands/lsb-release.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,eAAO,MAAM,iBAAiB,EAAE,WAgD/B,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { ifFlag } from "./command-helpers";
2
+ export const lsbReleaseCommand = {
3
+ name: "lsb_release",
4
+ description: "Print distribution-specific information",
5
+ category: "system",
6
+ params: ["[-a] [-i] [-d] [-r] [-c]"],
7
+ run: ({ args, shell }) => {
8
+ let osName = shell.properties?.os ?? "Fortune GNU/Linux x64";
9
+ let codename = "aurora";
10
+ let version = "1.0";
11
+ try {
12
+ const content = shell.vfs.readFile("/etc/os-release");
13
+ for (const line of content.split("\n")) {
14
+ if (line.startsWith("PRETTY_NAME="))
15
+ osName = line.slice("PRETTY_NAME=".length).replace(/^"|"$/g, "").trim();
16
+ if (line.startsWith("VERSION_CODENAME="))
17
+ codename = line.slice("VERSION_CODENAME=".length).trim();
18
+ if (line.startsWith("VERSION_ID="))
19
+ version = line.slice("VERSION_ID=".length).replace(/^"|"$/g, "").trim();
20
+ }
21
+ }
22
+ catch { }
23
+ const all = ifFlag(args, ["-a", "--all"]);
24
+ const showId = ifFlag(args, ["-i", "--id"]);
25
+ const showDesc = ifFlag(args, ["-d", "--description"]);
26
+ const showRelease = ifFlag(args, ["-r", "--release"]);
27
+ const showCodename = ifFlag(args, ["-c", "--codename"]);
28
+ if (all || args.length === 0) {
29
+ return {
30
+ stdout: [
31
+ `Distributor ID:\tFortune`,
32
+ `Description:\t${osName}`,
33
+ `Release:\t${version}`,
34
+ `Codename:\t${codename}`,
35
+ ].join("\n"),
36
+ exitCode: 0,
37
+ };
38
+ }
39
+ const lines = [];
40
+ if (showId)
41
+ lines.push(`Distributor ID:\tFortune`);
42
+ if (showDesc)
43
+ lines.push(`Description:\t${osName}`);
44
+ if (showRelease)
45
+ lines.push(`Release:\t${version}`);
46
+ if (showCodename)
47
+ lines.push(`Codename:\t${codename}`);
48
+ return { stdout: lines.join("\n"), exitCode: 0 };
49
+ },
50
+ };
@@ -0,0 +1,3 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ export declare const manCommand: ShellModule;
3
+ //# sourceMappingURL=man.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"man.d.ts","sourceRoot":"","sources":["../../src/commands/man.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAiJrD,eAAO,MAAM,UAAU,EAAE,WAoBxB,CAAC"}
@@ -0,0 +1,155 @@
1
+ const MAN_PAGES = {
2
+ ls: `LS(1) User Commands LS(1)
3
+
4
+ NAME
5
+ ls - list directory contents
6
+
7
+ SYNOPSIS
8
+ ls [OPTION]... [FILE]...
9
+
10
+ DESCRIPTION
11
+ List information about the FILEs (the current directory by default).
12
+
13
+ OPTIONS
14
+ -l use a long listing format
15
+ -a do not ignore entries starting with .
16
+ -h with -l, print human readable sizes
17
+ -r reverse order while sorting
18
+ -t sort by modification time
19
+
20
+ AUTHOR
21
+ Written by Richard M. Stallman and David MacKenzie.`,
22
+ cat: `CAT(1) User Commands CAT(1)
23
+
24
+ NAME
25
+ cat - concatenate files and print on the standard output
26
+
27
+ SYNOPSIS
28
+ cat [OPTION]... [FILE]...
29
+
30
+ DESCRIPTION
31
+ Concatenate FILE(s) to standard output.
32
+
33
+ OPTIONS
34
+ -n, --number number all output lines
35
+ -b, --number-nonblank number nonempty output lines`,
36
+ grep: `GREP(1) User Commands GREP(1)
37
+
38
+ NAME
39
+ grep, egrep, fgrep - print lines that match patterns
40
+
41
+ SYNOPSIS
42
+ grep [OPTION]... PATTERNS [FILE]...
43
+
44
+ OPTIONS
45
+ -i, --ignore-case ignore case distinctions in patterns and data
46
+ -v, --invert-match select non-matching lines
47
+ -n, --line-number print line number with output lines
48
+ -r, --recursive read all files under each directory, recursively`,
49
+ apt: `APT(8) APT APT(8)
50
+
51
+ NAME
52
+ apt - command-line interface
53
+
54
+ SYNOPSIS
55
+ apt [options] command
56
+
57
+ DESCRIPTION
58
+ apt provides a high-level commandline interface for the package
59
+ management system.
60
+
61
+ COMMANDS
62
+ install pkg... Install packages
63
+ remove pkg... Remove packages
64
+ update Download package information
65
+ upgrade Upgrade installed packages
66
+ search term Search in package descriptions
67
+ show pkg Show package information
68
+ list List packages`,
69
+ ssh: `SSH(1) OpenSSH SSH(1)
70
+
71
+ NAME
72
+ ssh - OpenSSH remote login client
73
+
74
+ SYNOPSIS
75
+ ssh [-p port] [user@]hostname [command]
76
+
77
+ DESCRIPTION
78
+ ssh (SSH client) is a program for logging into a remote machine and
79
+ for executing commands on a remote machine.`,
80
+ curl: `CURL(1) User Commands CURL(1)
81
+
82
+ NAME
83
+ curl - transfer a URL
84
+
85
+ SYNOPSIS
86
+ curl [options / URLs]
87
+
88
+ DESCRIPTION
89
+ curl is a tool for transferring data with URL syntax.
90
+
91
+ OPTIONS
92
+ -o, --output <file> Write output to <file>
93
+ -X, --request <method> Specify request method
94
+ -d, --data <data> HTTP POST data
95
+ -H, --header <header> Pass custom header
96
+ -s, --silent Silent mode
97
+ -I, --head Show document info only
98
+ -L, --location Follow redirects
99
+ -v, --verbose Make the operation more talkative`,
100
+ chmod: `CHMOD(1) User Commands CHMOD(1)
101
+
102
+ NAME
103
+ chmod - change file mode bits
104
+
105
+ SYNOPSIS
106
+ chmod [OPTION]... MODE[,MODE]... FILE...
107
+ chmod [OPTION]... OCTAL-MODE FILE...
108
+
109
+ DESCRIPTION
110
+ Change the file mode bits of each given file according to MODE.
111
+
112
+ EXAMPLES
113
+ chmod 755 script.sh rwxr-xr-x
114
+ chmod 644 file.txt rw-r--r--
115
+ chmod +x script.sh add execute permission`,
116
+ tar: `TAR(1) GNU tar Manual TAR(1)
117
+
118
+ NAME
119
+ tar - an archiving utility
120
+
121
+ SYNOPSIS
122
+ tar [OPTION...] [FILE]...
123
+
124
+ DESCRIPTION
125
+ tar saves many files together into a single tape or disk archive,
126
+ and can restore individual files from the archive.
127
+
128
+ OPTIONS
129
+ -c, --create create a new archive
130
+ -x, --extract extract files from an archive
131
+ -z, --gzip filter the archive through gzip
132
+ -f, --file=ARCHIVE use archive file or device ARCHIVE
133
+ -v, --verbose verbosely list files processed
134
+ -t, --list list the contents of an archive`,
135
+ };
136
+ export const manCommand = {
137
+ name: "man",
138
+ description: "Interface to the system reference manuals",
139
+ category: "shell",
140
+ params: ["<command>"],
141
+ run: ({ args, shell }) => {
142
+ const name = args[0];
143
+ if (!name)
144
+ return { stderr: "What manual page do you want?", exitCode: 1 };
145
+ // VFS-installed man pages take priority
146
+ const manPath = `/usr/share/man/man1/${name}.1`;
147
+ if (shell.vfs.exists(manPath)) {
148
+ return { stdout: shell.vfs.readFile(manPath), exitCode: 0 };
149
+ }
150
+ const page = MAN_PAGES[name.toLowerCase()];
151
+ if (page)
152
+ return { stdout: page, exitCode: 0 };
153
+ return { stderr: `No manual entry for ${name}`, exitCode: 16 };
154
+ },
155
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"neofetch.d.ts","sourceRoot":"","sources":["../../src/commands/neofetch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,eAAe,EAAE,WAiC7B,CAAC"}
1
+ {"version":3,"file":"neofetch.d.ts","sourceRoot":"","sources":["../../src/commands/neofetch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,eAAe,EAAE,WAsC7B,CAAC"}
@@ -27,6 +27,11 @@ export const neofetchCommand = {
27
27
  shell: env.SHELL,
28
28
  shellProps: shell.properties,
29
29
  terminal: env.TERM,
30
+ uptimeSeconds: Math.floor((Date.now() - shell.startTime) / 1000),
31
+ packages: (() => {
32
+ const count = shell.packageManager?.installedCount() ?? 0;
33
+ return `${count} (dpkg)`;
34
+ })(),
30
35
  }),
31
36
  exitCode: 0,
32
37
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ping.d.ts","sourceRoot":"","sources":["../../src/commands/ping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,WAAW,EAAE,WAiBzB,CAAC"}
1
+ {"version":3,"file":"ping.d.ts","sourceRoot":"","sources":["../../src/commands/ping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,eAAO,MAAM,WAAW,EAAE,WAmBzB,CAAC"}
@@ -1,11 +1,14 @@
1
+ import { parseArgs } from "./command-helpers";
1
2
  export const pingCommand = {
2
3
  name: "ping",
3
4
  description: "Send ICMP ECHO_REQUEST (mock)",
4
5
  category: "network",
5
6
  params: ["[-c <count>] <host>"],
6
7
  run: ({ args }) => {
7
- const host = args.find((a) => !a.startsWith("-")) ?? "localhost";
8
- const count = 4;
8
+ const { flagsWithValues, positionals } = parseArgs(args, { flagsWithValue: ["-c", "-i", "-W"] });
9
+ const host = positionals[0] ?? "localhost";
10
+ const countArg = flagsWithValues.get("-c");
11
+ const count = countArg ? Math.max(1, parseInt(countArg, 10) || 4) : 4;
9
12
  const lines = [`PING ${host}: 56 data bytes`];
10
13
  for (let i = 0; i < count; i++) {
11
14
  const ms = (Math.random() * 10 + 1).toFixed(3);
@@ -1 +1 @@
1
- {"version":3,"file":"ps.d.ts","sourceRoot":"","sources":["../../src/commands/ps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,SAAS,EAAE,WAgBvB,CAAC"}
1
+ {"version":3,"file":"ps.d.ts","sourceRoot":"","sources":["../../src/commands/ps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,eAAO,MAAM,SAAS,EAAE,WAqCvB,CAAC"}
@@ -1,17 +1,38 @@
1
+ import { ifFlag } from "./command-helpers";
1
2
  export const psCommand = {
2
3
  name: "ps",
3
4
  description: "Report process status",
4
5
  category: "system",
5
- params: ["[-a] [-u] [-x]"],
6
- run: ({ authUser, shell }) => {
6
+ params: ["[-a] [-u] [-x] [aux]"],
7
+ run: ({ authUser, shell, args }) => {
7
8
  const sessions = shell.users.listActiveSessions();
8
- const lines = [" PID TTY TIME CMD"];
9
+ const showUser = ifFlag(args, ["-u"]) || args.includes("u") || args.includes("aux") || args.includes("au");
10
+ const showAll = ifFlag(args, ["-a", "-x"]) || args.includes("a") || args.includes("aux");
11
+ if (showUser) {
12
+ const header = "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND";
13
+ const rows = [header];
14
+ let pid = 1000;
15
+ for (const s of sessions) {
16
+ const user = s.username.padEnd(10).slice(0, 10);
17
+ const mem = (Math.random() * 0.5).toFixed(1);
18
+ const vsz = Math.floor(Math.random() * 20000 + 5000);
19
+ const rss = Math.floor(Math.random() * 5000 + 1000);
20
+ rows.push(`${user} ${String(pid).padStart(6)} 0.0 ${mem.padStart(4)} ${String(vsz).padStart(6)} ${String(rss).padStart(5)} ${s.tty.padEnd(8)} Ss 00:00 0:00 bash`);
21
+ pid++;
22
+ }
23
+ rows.push(`root ${String(pid).padStart(6)} 0.0 0.0 0 0 ? S 00:00 0:00 ps`);
24
+ return { stdout: rows.join("\n"), exitCode: 0 };
25
+ }
26
+ const header = " PID TTY TIME CMD";
27
+ const rows = [header];
9
28
  let pid = 1000;
10
29
  for (const s of sessions) {
11
- lines.push(`${String(pid).padStart(5)} ${s.tty.padEnd(12)} 00:00:00 ${s.username === authUser ? "bash" : `bash (${s.username})`}`);
30
+ if (!showAll && s.username !== authUser)
31
+ continue;
32
+ rows.push(`${String(pid).padStart(5)} ${s.tty.padEnd(12)} 00:00:00 ${s.username === authUser ? "bash" : `bash (${s.username})`}`);
12
33
  pid++;
13
34
  }
14
- lines.push(`${String(pid).padStart(5)} pts/0 00:00:00 ps`);
15
- return { stdout: lines.join("\n"), exitCode: 0 };
35
+ rows.push(`${String(pid).padStart(5)} pts/0 00:00:00 ps`);
36
+ return { stdout: rows.join("\n"), exitCode: 0 };
16
37
  },
17
38
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sh.d.ts","sourceRoot":"","sources":["../../src/commands/sh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiC,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA+KpF,eAAO,MAAM,SAAS,EAAE,WA+BvB,CAAC"}
1
+ {"version":3,"file":"sh.d.ts","sourceRoot":"","sources":["../../src/commands/sh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiC,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAqMpF,eAAO,MAAM,SAAS,EAAE,WA+BvB,CAAC"}
@@ -1,8 +1,8 @@
1
- import { getArg, ifFlag } from "./command-helpers";
1
+ import { ifFlag } from "./command-helpers";
2
2
  import { resolvePath } from "./helpers";
3
3
  import { runCommand } from "./index";
4
- /** Expand $VAR and ${VAR:-default} in a line using the current env */
5
- function expandVars(line, env, lastExit) {
4
+ /** Expand $VAR and ${VAR:-default} in a line using the current env (sync, no $(cmd)) */
5
+ function expandVarsSync(line, env, lastExit) {
6
6
  return line
7
7
  .replace(/\$\?/g, String(lastExit))
8
8
  .replace(/\$\{([^}:]+):-([^}]*)\}/g, (_, n, d) => env[n] ?? d)
@@ -10,6 +10,24 @@ function expandVars(line, env, lastExit) {
10
10
  .replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, n) => env[n] ?? "")
11
11
  .replace(/^~(\/|$)/, `${env.HOME ?? "/home/user"}$1`);
12
12
  }
13
+ /**
14
+ * Expand $VAR, ${VAR:-default}, and $(cmd) substitution asynchronously.
15
+ * Used before executing each line in sh script context.
16
+ */
17
+ async function expandVars(line, env, lastExit, ctx) {
18
+ // $(cmd) substitution first
19
+ if (line.includes("$(")) {
20
+ const subRe = /\$\(([^)]+)\)/g;
21
+ const matches = [...line.matchAll(subRe)];
22
+ for (const m of matches) {
23
+ const sub = m[1]?.trim() ?? "";
24
+ const subResult = await runCommand(sub, ctx.authUser, ctx.hostname, ctx.mode, ctx.cwd, ctx.shell, undefined, ctx.env);
25
+ const subOut = (subResult.stdout ?? "").replace(/\n$/, "");
26
+ line = line.replace(m[0], subOut);
27
+ }
28
+ }
29
+ return expandVarsSync(line, env, lastExit);
30
+ }
13
31
  /** Very small shell interpreter: supports if/elif/else/fi, for/do/done, while/do/done */
14
32
  function parseBlocks(lines) {
15
33
  const blocks = [];
@@ -57,8 +75,8 @@ function parseBlocks(lines) {
57
75
  const body = [];
58
76
  i++;
59
77
  while (i < lines.length && lines[i]?.trim() !== "done") {
60
- const l = lines[i].trim();
61
- if (l !== "do")
78
+ const l = lines[i].trim().replace(/^do\s+/, "");
79
+ if (l && l !== "do")
62
80
  body.push(l);
63
81
  i++;
64
82
  }
@@ -73,8 +91,8 @@ function parseBlocks(lines) {
73
91
  const body = [];
74
92
  i++;
75
93
  while (i < lines.length && lines[i]?.trim() !== "done") {
76
- const l = lines[i].trim();
77
- if (l !== "do")
94
+ const l = lines[i].trim().replace(/^do\s+/, "");
95
+ if (l && l !== "do")
78
96
  body.push(l);
79
97
  i++;
80
98
  }
@@ -88,7 +106,7 @@ function parseBlocks(lines) {
88
106
  return blocks;
89
107
  }
90
108
  async function evalCondition(cond, ctx) {
91
- const expanded = expandVars(cond, ctx.env.vars, ctx.env.lastExitCode);
109
+ const expanded = await expandVars(cond, ctx.env.vars, ctx.env.lastExitCode, ctx);
92
110
  // test -f / test -d / [ ... ]
93
111
  const testMatch = expanded.match(/^\[?\s*(.+?)\s*\]?$/);
94
112
  if (testMatch) {
@@ -146,7 +164,7 @@ async function runBlocks(blocks, ctx) {
146
164
  let output = "";
147
165
  for (const block of blocks) {
148
166
  if (block.type === "cmd") {
149
- const expanded = expandVars(block.line, ctx.env.vars, ctx.env.lastExitCode);
167
+ const expanded = await expandVars(block.line, ctx.env.vars, ctx.env.lastExitCode, ctx);
150
168
  const r = await runCommand(expanded, ctx.authUser, ctx.hostname, ctx.mode, ctx.cwd, ctx.shell, undefined, ctx.env);
151
169
  ctx.env.lastExitCode = r.exitCode ?? 0;
152
170
  if (r.stdout)
@@ -181,7 +199,7 @@ async function runBlocks(blocks, ctx) {
181
199
  }
182
200
  }
183
201
  else if (block.type === "for") {
184
- const listExpanded = expandVars(block.list, ctx.env.vars, ctx.env.lastExitCode);
202
+ const listExpanded = await expandVars(block.list, ctx.env.vars, ctx.env.lastExitCode, ctx);
185
203
  const items = listExpanded.trim().split(/\s+/);
186
204
  for (const item of items) {
187
205
  ctx.env.vars[block.var] = item;
@@ -216,7 +234,7 @@ export const shCommand = {
216
234
  const { args, shell, cwd } = ctx;
217
235
  // sh -c "inline script"
218
236
  if (ifFlag(args, "-c")) {
219
- const script = getArg(args, 1) ?? "";
237
+ const script = args[args.indexOf("-c") + 1] ?? "";
220
238
  if (!script)
221
239
  return { stderr: "sh: -c requires a script", exitCode: 1 };
222
240
  const lines = script.split(/[;\n]/).map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
@@ -0,0 +1,3 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ export declare const sourceCommand: ShellModule;
3
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/commands/source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,eAAO,MAAM,aAAa,EAAE,WA8B3B,CAAC"}