typescript-virtual-container 1.5.10 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +236 -456
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/Honeypot/index.d.ts +9 -0
  4. package/dist/Honeypot/index.js +57 -0
  5. package/dist/SSHMimic/exec.d.ts +4 -0
  6. package/dist/SSHMimic/exec.js +4 -0
  7. package/dist/SSHMimic/executor.d.ts +10 -1
  8. package/dist/SSHMimic/executor.js +18 -8
  9. package/dist/SSHMimic/hostKey.d.ts +5 -0
  10. package/dist/SSHMimic/hostKey.js +5 -0
  11. package/dist/SSHMimic/loginBanner.d.ts +7 -0
  12. package/dist/SSHMimic/loginBanner.js +4 -0
  13. package/dist/SSHMimic/loginFormat.d.ts +4 -0
  14. package/dist/SSHMimic/loginFormat.js +4 -0
  15. package/dist/SSHMimic/prompt.d.ts +9 -0
  16. package/dist/SSHMimic/prompt.js +9 -0
  17. package/dist/SSHMimic/scp.d.ts +18 -0
  18. package/dist/SSHMimic/scp.js +14 -0
  19. package/dist/VirtualFileSystem/binaryPack.d.ts +7 -3
  20. package/dist/VirtualFileSystem/binaryPack.js +32 -10
  21. package/dist/VirtualFileSystem/index.d.ts +29 -0
  22. package/dist/VirtualFileSystem/index.js +126 -5
  23. package/dist/VirtualFileSystem/internalTypes.d.ts +4 -0
  24. package/dist/VirtualFileSystem/journal.d.ts +10 -4
  25. package/dist/VirtualFileSystem/journal.js +12 -2
  26. package/dist/VirtualFileSystem/path.d.ts +23 -1
  27. package/dist/VirtualFileSystem/path.js +23 -3
  28. package/dist/VirtualPackageManager/index.js +1 -1
  29. package/dist/VirtualShell/index.d.ts +3 -0
  30. package/dist/VirtualShell/index.js +12 -3
  31. package/dist/VirtualUserManager/index.d.ts +20 -1
  32. package/dist/VirtualUserManager/index.js +52 -15
  33. package/dist/commands/bc.d.ts +5 -0
  34. package/dist/commands/bc.js +5 -0
  35. package/dist/commands/cat.js +2 -2
  36. package/dist/commands/chgrp.d.ts +7 -0
  37. package/dist/commands/chgrp.js +42 -0
  38. package/dist/commands/chown.d.ts +7 -0
  39. package/dist/commands/chown.js +79 -0
  40. package/dist/commands/cp.js +4 -3
  41. package/dist/commands/dd.d.ts +7 -0
  42. package/dist/commands/dd.js +60 -0
  43. package/dist/commands/declare.js +0 -2
  44. package/dist/commands/expr.d.ts +7 -0
  45. package/dist/commands/expr.js +63 -0
  46. package/dist/commands/fun.d.ts +5 -0
  47. package/dist/commands/fun.js +5 -1
  48. package/dist/commands/help.d.ts +5 -0
  49. package/dist/commands/help.js +5 -0
  50. package/dist/commands/helpers.d.ts +43 -0
  51. package/dist/commands/helpers.js +61 -0
  52. package/dist/commands/id.d.ts +5 -0
  53. package/dist/commands/id.js +5 -0
  54. package/dist/commands/ip.d.ts +1 -0
  55. package/dist/commands/ip.js +50 -23
  56. package/dist/commands/jobs.js +43 -9
  57. package/dist/commands/kill.d.ts +1 -0
  58. package/dist/commands/kill.js +13 -5
  59. package/dist/commands/last.js +1 -1
  60. package/dist/commands/ln.d.ts +5 -0
  61. package/dist/commands/ln.js +5 -0
  62. package/dist/commands/ls.d.ts +5 -0
  63. package/dist/commands/ls.js +19 -4
  64. package/dist/commands/lsb-release.js +1 -1
  65. package/dist/commands/man.d.ts +5 -0
  66. package/dist/commands/man.js +5 -0
  67. package/dist/commands/manuals-bundle.js +242 -0
  68. package/dist/commands/miscutils.d.ts +43 -0
  69. package/dist/commands/miscutils.js +233 -0
  70. package/dist/commands/mkdir.js +3 -2
  71. package/dist/commands/mv.js +4 -3
  72. package/dist/commands/netcat.d.ts +7 -0
  73. package/dist/commands/netcat.js +64 -0
  74. package/dist/commands/nice.d.ts +7 -0
  75. package/dist/commands/nice.js +22 -0
  76. package/dist/commands/nohup.d.ts +7 -0
  77. package/dist/commands/nohup.js +18 -0
  78. package/dist/commands/ping.d.ts +2 -1
  79. package/dist/commands/ping.js +46 -8
  80. package/dist/commands/procUtils.d.ts +13 -0
  81. package/dist/commands/procUtils.js +72 -0
  82. package/dist/commands/pwd.d.ts +5 -0
  83. package/dist/commands/pwd.js +5 -0
  84. package/dist/commands/python.js +0 -4
  85. package/dist/commands/read.js +0 -1
  86. package/dist/commands/registry.d.ts +37 -0
  87. package/dist/commands/registry.js +73 -0
  88. package/dist/commands/rm.js +3 -2
  89. package/dist/commands/runtime.d.ts +47 -1
  90. package/dist/commands/runtime.js +60 -5
  91. package/dist/commands/sh.d.ts +5 -0
  92. package/dist/commands/sh.js +5 -0
  93. package/dist/commands/stat.js +3 -2
  94. package/dist/commands/strace.js +0 -1
  95. package/dist/commands/sysinfo.d.ts +19 -0
  96. package/dist/commands/sysinfo.js +73 -0
  97. package/dist/commands/test.d.ts +5 -0
  98. package/dist/commands/test.js +5 -0
  99. package/dist/commands/textutils.d.ts +25 -0
  100. package/dist/commands/textutils.js +171 -0
  101. package/dist/commands/top.d.ts +7 -0
  102. package/dist/commands/top.js +54 -0
  103. package/dist/commands/touch.js +6 -2
  104. package/dist/commands/tr.d.ts +5 -0
  105. package/dist/commands/tr.js +5 -0
  106. package/dist/commands/w.js +1 -1
  107. package/dist/commands/which.d.ts +5 -0
  108. package/dist/commands/which.js +5 -0
  109. package/dist/index.d.ts +10 -2
  110. package/dist/index.js +4 -0
  111. package/dist/modules/VirtualNetworkManager.d.ts +54 -0
  112. package/dist/modules/VirtualNetworkManager.js +144 -0
  113. package/dist/modules/linuxRootfs.d.ts +4 -3
  114. package/dist/modules/linuxRootfs.js +115 -74
  115. package/dist/modules/neofetch.d.ts +2 -0
  116. package/dist/modules/neofetch.js +3 -2
  117. package/dist/modules/pacmanGame.d.ts +2 -0
  118. package/dist/modules/pacmanGame.js +1 -0
  119. package/dist/modules/shellInteractive.d.ts +2 -0
  120. package/dist/modules/shellInteractive.js +2 -0
  121. package/dist/modules/shellRuntime.d.ts +7 -0
  122. package/dist/modules/shellRuntime.js +6 -0
  123. package/dist/modules/webTermRenderer.js +0 -7
  124. package/dist/types/commands.d.ts +1 -1
  125. package/dist/types/vfs.d.ts +8 -0
  126. package/dist/utils/argv.d.ts +22 -3
  127. package/dist/utils/argv.js +22 -3
  128. package/dist/utils/perfLogger.d.ts +10 -2
  129. package/dist/utils/perfLogger.js +7 -14
  130. package/dist/utils/shellSession.d.ts +35 -0
  131. package/dist/utils/shellSession.js +35 -0
  132. package/package.json +3 -2
  133. package/scripts/postinstall.js +42 -0
@@ -40,6 +40,15 @@ export async function applyUserSwitch(newUser, hostname, cwd, shellEnv, shell) {
40
40
  catch { /* ignore */ }
41
41
  }
42
42
  }
43
+ /**
44
+ * Creates the default shell environment variables for a given user and host.
45
+ * Sets PATH, HOME, USER, LOGNAME, SHELL, TERM, HOSTNAME, and a coloured PS1
46
+ * prompt that distinguishes root (red) from ordinary users (magenta).
47
+ *
48
+ * @param authUser - The authenticated username
49
+ * @param hostname - The machine hostname
50
+ * @returns A ShellEnv populated with default variable values
51
+ */
43
52
  export function makeDefaultEnv(authUser, hostname) {
44
53
  return {
45
54
  vars: {
@@ -98,7 +107,7 @@ function resolveVfsBinary(name, env, shell, authUser) {
98
107
  continue;
99
108
  return full;
100
109
  }
101
- catch { }
110
+ catch { /* not a regular file */ }
102
111
  }
103
112
  return null;
104
113
  }
@@ -133,7 +142,27 @@ async function runVfsStub(vfsBinary, cmdName, args, rawInput, authUser, hostname
133
142
  return { stderr: `${cmdName}: command not found`, exitCode: 127 };
134
143
  }
135
144
  let _callDepth = 0;
136
- export async function runCommandDirect(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
145
+ /**
146
+ * Runs a command directly by name, bypassing shell parsing and expansion.
147
+ * Handles variable assignments prefixing the command, shell functions
148
+ * (stored as `__func_<name>`), aliases, registered modules, and VFS
149
+ * stub binaries. Includes an anti-loop guard via a module-level call depth
150
+ * counter and registers the process in the user's session table.
151
+ *
152
+ * @param name - The command name to execute
153
+ * @param args - Argument array (does not include the command name)
154
+ * @param authUser - The authenticated user
155
+ * @param hostname - The machine hostname
156
+ * @param mode - The current command mode (shell, pipe, etc.)
157
+ * @param cwd - The current working directory
158
+ * @param shell - The VirtualShell instance
159
+ * @param stdin - Optional stdin string
160
+ * @param env - The current shell environment
161
+ * @param background - Whether the command is run in the background
162
+ * @param abortController - Optional controller to abort a background process
163
+ * @returns The command result
164
+ */
165
+ export async function runCommandDirect(name, args, authUser, hostname, mode, cwd, shell, stdin, env, background = false, abortController) {
137
166
  // Anti-loop guard: track call depth via env to avoid infinite recursion
138
167
  _callDepth++;
139
168
  if (_callDepth > MAX_CALL_DEPTH) {
@@ -143,15 +172,24 @@ export async function runCommandDirect(name, args, authUser, hostname, mode, cwd
143
172
  // Register as visible process only at the outermost call level
144
173
  const isTopLevel = _callDepth === 1;
145
174
  const pid = isTopLevel
146
- ? shell.users.registerProcess(authUser, name, [name, ...args], env.vars.__TTY ?? "?")
175
+ ? shell.users.registerProcess(authUser, name, [name, ...args], env.vars.__TTY ?? "?", abortController)
147
176
  : -1;
148
177
  try {
178
+ if (background && abortController?.signal.aborted) {
179
+ return { stderr: "", exitCode: 130 };
180
+ }
149
181
  return await _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env);
150
182
  }
151
183
  finally {
152
184
  _callDepth--;
153
- if (isTopLevel && pid !== -1)
154
- shell.users.unregisterProcess(pid);
185
+ if (isTopLevel && pid !== -1) {
186
+ if (background) {
187
+ shell.users.markProcessDone(pid);
188
+ }
189
+ else {
190
+ shell.users.unregisterProcess(pid);
191
+ }
192
+ }
155
193
  }
156
194
  }
157
195
  async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
@@ -253,6 +291,23 @@ async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd,
253
291
  };
254
292
  }
255
293
  }
294
+ /**
295
+ * Parses and runs a command line string through the full execution pipeline:
296
+ * history expansion (`!!` / `!n`), alias expansion, sh-syntax detection
297
+ * (for/while/if/function/arithmetic), pipe/redirect routing via the shell
298
+ * parser, variable expansion, brace expansion, and glob expansion. Falls
299
+ * back to the `sh` interpreter for compound constructs.
300
+ *
301
+ * @param rawInput - The raw command line string to parse and run
302
+ * @param authUser - The authenticated user
303
+ * @param hostname - The machine hostname
304
+ * @param mode - The current command mode (shell, pipe, etc.)
305
+ * @param cwd - The current working directory
306
+ * @param shell - The VirtualShell instance
307
+ * @param stdin - Optional stdin string
308
+ * @param env - Optional shell environment (created from defaults if omitted)
309
+ * @returns The command result
310
+ */
256
311
  export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell, stdin, env) {
257
312
  const trimmed = rawInput.trim();
258
313
  if (trimmed.length === 0)
@@ -1,2 +1,7 @@
1
1
  import type { ShellModule } from "../types/commands";
2
+ /**
3
+ * Execute shell script or command.
4
+ * @category shell
5
+ * @params ["-c <script>", "[<file>]"]
6
+ */
2
7
  export declare const shCommand: ShellModule;
@@ -514,6 +514,11 @@ function splitShScript(script) {
514
514
  lines.push(t);
515
515
  return lines;
516
516
  }
517
+ /**
518
+ * Execute shell script or command.
519
+ * @category shell
520
+ * @params ["-c <script>", "[<file>]"]
521
+ */
517
522
  export const shCommand = {
518
523
  name: "sh",
519
524
  aliases: ["bash"],
@@ -20,7 +20,6 @@ export const statCommand = {
20
20
  }
21
21
  const st = shell.vfs.stat(p);
22
22
  const isDir = st.type === "directory";
23
- const _isLink = shell.vfs.isSymlink(p);
24
23
  const isSymlink = shell.vfs.isSymlink(p);
25
24
  const modePerm = (mode) => {
26
25
  const bits = [0o400, 0o200, 0o100, 0o040, 0o020, 0o010, 0o004, 0o002, 0o001];
@@ -44,10 +43,12 @@ export const statCommand = {
44
43
  .replace("%z", ts(st.updatedAt));
45
44
  return { stdout: `${out}\n`, exitCode: 0 };
46
45
  }
46
+ const statUid = "uid" in st ? st.uid : 0;
47
+ const statGid = "gid" in st ? st.gid : 0;
47
48
  const out = [
48
49
  ` File: ${file}${isSymlink ? ` -> ${shell.vfs.resolveSymlink(p)}` : ""}`,
49
50
  ` Size: ${size}${"\t".repeat(3)}${isSymlink ? "symbolic link" : isDir ? "directory" : "regular file"}`,
50
- `Access: (${octal}/${modeStr}) Uid: ( 0/ root) Gid: ( 0/ root)`,
51
+ `Access: (${octal}/${modeStr}) Uid: (${String(statUid).padStart(5)}/ root) Gid: (${String(statGid).padStart(5)}/ root)`,
51
52
  `Modify: ${ts(st.updatedAt)}`,
52
53
  `Change: ${ts(st.updatedAt)}`,
53
54
  ].join("\n");
@@ -11,7 +11,6 @@ export const straceCommand = {
11
11
  const cmd = args.find((a) => !a.startsWith("-"));
12
12
  if (!cmd)
13
13
  return { stderr: "strace: must have PROG [ARGS] or -p PID", exitCode: 1 };
14
- const _pid = Math.floor(Math.random() * 30000) + 1000;
15
14
  const lines = [
16
15
  `execve("/usr/bin/${cmd}", ["${cmd}"${args.slice(1).map((a) => `, "${a}"`).join("")}], 0x... /* ... vars */) = 0`,
17
16
  `brk(NULL) = 0x${(Math.random() * 0xfffff | 0).toString(16)}000`,
@@ -0,0 +1,19 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ /**
3
+ * Display CPU architecture information
4
+ * @category system
5
+ * @params []
6
+ */
7
+ export declare const lscpuCommand: ShellModule;
8
+ /**
9
+ * List USB devices
10
+ * @category system
11
+ * @params []
12
+ */
13
+ export declare const lsusbCommand: ShellModule;
14
+ /**
15
+ * List PCI devices
16
+ * @category system
17
+ * @params []
18
+ */
19
+ export declare const lspciCommand: ShellModule;
@@ -0,0 +1,73 @@
1
+ import * as os from "node:os";
2
+ /**
3
+ * Display CPU architecture information
4
+ * @category system
5
+ * @params []
6
+ */
7
+ export const lscpuCommand = {
8
+ name: "lscpu",
9
+ description: "Display CPU architecture information",
10
+ category: "system",
11
+ params: [],
12
+ run: () => {
13
+ const cpus = os.cpus();
14
+ const arch = os.arch();
15
+ const endian = os.endianness();
16
+ const cores = cpus.length;
17
+ const model = cpus.length > 0 ? cpus[0].model : "Unknown";
18
+ const lines = [
19
+ `Architecture: ${arch}`,
20
+ `CPU op-mode(s): 32-bit, 64-bit`,
21
+ `Byte Order: ${endian}`,
22
+ `CPU(s): ${cores}`,
23
+ `On-line CPU(s) list: 0-${cores - 1}`,
24
+ `Model name: ${model}`,
25
+ `Thread(s) per core: 1`,
26
+ `Core(s) per socket: ${cores}`,
27
+ `Socket(s): 1`,
28
+ `Vendor ID: GenuineIntel`,
29
+ ];
30
+ return { stdout: lines.join("\n") + "\n", exitCode: 0 };
31
+ },
32
+ };
33
+ /**
34
+ * List USB devices
35
+ * @category system
36
+ * @params []
37
+ */
38
+ export const lsusbCommand = {
39
+ name: "lsusb",
40
+ description: "List USB devices",
41
+ category: "system",
42
+ params: [],
43
+ run: () => {
44
+ const devices = [
45
+ "Bus 001 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub",
46
+ "Bus 001 Device 002: ID 80ee:0021 VirtualBox USB Tablet",
47
+ "Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub",
48
+ ];
49
+ return { stdout: devices.join("\n") + "\n", exitCode: 0 };
50
+ },
51
+ };
52
+ /**
53
+ * List PCI devices
54
+ * @category system
55
+ * @params []
56
+ */
57
+ export const lspciCommand = {
58
+ name: "lspci",
59
+ description: "List PCI devices",
60
+ category: "system",
61
+ params: [],
62
+ run: () => {
63
+ const devices = [
64
+ "00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma]",
65
+ "00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]",
66
+ "00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]",
67
+ "00:02.0 VGA compatible controller: VMware SVGA II Adapter",
68
+ "00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller",
69
+ "00:04.0 SATA controller: Intel Corporation 82801IR/IO (ICH9R) SATA Controller",
70
+ ];
71
+ return { stdout: devices.join("\n") + "\n", exitCode: 0 };
72
+ },
73
+ };
@@ -1,2 +1,7 @@
1
1
  import type { ShellModule } from "../types/commands";
2
+ /**
3
+ * Evaluate conditional expression.
4
+ * @category shell
5
+ * @params ["<expression>"]
6
+ */
2
7
  export declare const testCommand: ShellModule;
@@ -96,6 +96,11 @@ function evalTest(tokens, shell, cwd) {
96
96
  return (tokens[0] ?? "").length > 0;
97
97
  return false;
98
98
  }
99
+ /**
100
+ * Evaluate conditional expression.
101
+ * @category shell
102
+ * @params ["<expression>"]
103
+ */
99
104
  export const testCommand = {
100
105
  name: "test",
101
106
  aliases: ["["],
@@ -0,0 +1,25 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ /**
3
+ * Join lines of two files on a common field
4
+ * @category text
5
+ * @params ["[-t sep] <file1> <file2>"]
6
+ */
7
+ export declare const joinCommand: ShellModule;
8
+ /**
9
+ * Compare two sorted files line by line
10
+ * @category text
11
+ * @params ["<file1> <file2>"]
12
+ */
13
+ export declare const commCommand: ShellModule;
14
+ /**
15
+ * Split a file into pieces
16
+ * @category text
17
+ * @params ["[-l lines] [-b bytes] <file> [prefix]"]
18
+ */
19
+ export declare const splitCommand: ShellModule;
20
+ /**
21
+ * Split a file based on context patterns
22
+ * @category text
23
+ * @params ["<file> <pattern>..."]
24
+ */
25
+ export declare const csplitCommand: ShellModule;
@@ -0,0 +1,171 @@
1
+ import { parseArgs } from "./command-helpers";
2
+ import { resolvePath } from "./helpers";
3
+ function alphaSuffix(n) {
4
+ let result = "";
5
+ let x = n;
6
+ do {
7
+ result = String.fromCharCode(97 + (x % 26)) + result;
8
+ x = Math.floor(x / 26) - 1;
9
+ } while (x >= 0);
10
+ return result;
11
+ }
12
+ /**
13
+ * Join lines of two files on a common field
14
+ * @category text
15
+ * @params ["[-t sep] <file1> <file2>"]
16
+ */
17
+ export const joinCommand = {
18
+ name: "join",
19
+ description: "Join lines of two files on a common field",
20
+ category: "text",
21
+ params: ["[-t sep] <file1> <file2>"],
22
+ run: ({ shell, cwd, args }) => {
23
+ const { flagsWithValues, positionals } = parseArgs(args, {
24
+ flagsWithValue: ["-t"],
25
+ });
26
+ const separator = flagsWithValues.get("-t") || " \t";
27
+ const [file1, file2] = positionals;
28
+ if (!file1 || !file2) {
29
+ return { stderr: "join: missing operand\n", exitCode: 1 };
30
+ }
31
+ const p1 = resolvePath(cwd, file1);
32
+ const p2 = resolvePath(cwd, file2);
33
+ if (!shell.vfs.exists(p1) || !shell.vfs.exists(p2)) {
34
+ return { stderr: "join: No such file\n", exitCode: 1 };
35
+ }
36
+ const lines1 = shell.vfs.readFile(p1).split("\n").filter(Boolean);
37
+ const lines2 = shell.vfs.readFile(p2).split("\n").filter(Boolean);
38
+ const sep = separator === " \t" ? /\s+/ : new RegExp(separator);
39
+ const map1 = new Map();
40
+ for (const line of lines1) {
41
+ const key = line.split(sep)[0] || line;
42
+ map1.set(key, line);
43
+ }
44
+ const results = [];
45
+ for (const line of lines2) {
46
+ const key = line.split(sep)[0] || line;
47
+ const match = map1.get(key);
48
+ if (match) {
49
+ results.push(`${match} ${line}`);
50
+ }
51
+ }
52
+ return { stdout: results.join("\n") + "\n", exitCode: 0 };
53
+ },
54
+ };
55
+ /**
56
+ * Compare two sorted files line by line
57
+ * @category text
58
+ * @params ["<file1> <file2>"]
59
+ */
60
+ export const commCommand = {
61
+ name: "comm",
62
+ description: "Compare two sorted files line by line",
63
+ category: "text",
64
+ params: ["<file1> <file2>"],
65
+ run: ({ shell, cwd, args }) => {
66
+ const positionals = args.filter((a) => !a.startsWith("-"));
67
+ const [file1, file2] = positionals;
68
+ if (!file1 || !file2) {
69
+ return { stderr: "comm: missing operand\n", exitCode: 1 };
70
+ }
71
+ const p1 = resolvePath(cwd, file1);
72
+ const p2 = resolvePath(cwd, file2);
73
+ if (!shell.vfs.exists(p1) || !shell.vfs.exists(p2)) {
74
+ return { stderr: "comm: No such file\n", exitCode: 1 };
75
+ }
76
+ const lines1 = shell.vfs.readFile(p1).split("\n");
77
+ const lines2 = shell.vfs.readFile(p2).split("\n");
78
+ if (lines1[lines1.length - 1] === "")
79
+ lines1.pop();
80
+ if (lines2[lines2.length - 1] === "")
81
+ lines2.pop();
82
+ const set1 = new Set(lines1);
83
+ const set2 = new Set(lines2);
84
+ const only1 = [];
85
+ const only2 = [];
86
+ const both = [];
87
+ for (const line of lines1) {
88
+ if (set2.has(line)) {
89
+ both.push(line);
90
+ }
91
+ else {
92
+ only1.push(line);
93
+ }
94
+ }
95
+ for (const line of lines2) {
96
+ if (!set1.has(line)) {
97
+ only2.push(line);
98
+ }
99
+ }
100
+ const maxLen = Math.max(only1.length, only2.length, both.length);
101
+ const results = [];
102
+ for (let i = 0; i < maxLen; i++) {
103
+ const col1 = i < only1.length ? only1[i] : "";
104
+ const col2 = i < only2.length ? only2[i] : "";
105
+ const col3 = i < both.length ? both[i] : "";
106
+ results.push(`${col1}\t${col2}\t${col3}`);
107
+ }
108
+ return { stdout: results.join("\n") + "\n", exitCode: 0 };
109
+ },
110
+ };
111
+ /**
112
+ * Split a file into pieces
113
+ * @category text
114
+ * @params ["[-l lines] [-b bytes] <file> [prefix]"]
115
+ */
116
+ export const splitCommand = {
117
+ name: "split",
118
+ description: "Split a file into pieces",
119
+ category: "text",
120
+ params: ["[-l lines] [-b bytes] <file> [prefix]"],
121
+ run: ({ authUser, shell, cwd, args }) => {
122
+ const { flagsWithValues, positionals } = parseArgs(args, {
123
+ flagsWithValue: ["-l", "-b"],
124
+ });
125
+ const linesPerFile = parseInt(flagsWithValues.get("-l") || "1000", 10);
126
+ const bytesPerFile = flagsWithValues.has("-b") ? parseInt(flagsWithValues.get("-b"), 10) : undefined;
127
+ const fileArg = positionals[0];
128
+ const prefix = positionals[1] || "x";
129
+ if (!fileArg) {
130
+ return { stderr: "split: missing file operand\n", exitCode: 1 };
131
+ }
132
+ const p = resolvePath(cwd, fileArg);
133
+ if (!shell.vfs.exists(p)) {
134
+ return { stderr: `split: ${fileArg}: No such file or directory\n`, exitCode: 1 };
135
+ }
136
+ const content = shell.vfs.readFile(p);
137
+ if (bytesPerFile !== undefined) {
138
+ let chunkIndex = 0;
139
+ for (let i = 0; i < content.length; i += bytesPerFile) {
140
+ const chunk = content.slice(i, i + bytesPerFile);
141
+ const outPath = resolvePath(cwd, `${prefix}${alphaSuffix(chunkIndex)}`);
142
+ shell.writeFileAsUser(authUser, outPath, chunk);
143
+ chunkIndex++;
144
+ }
145
+ return { exitCode: 0 };
146
+ }
147
+ const allLines = content.split("\n");
148
+ let chunkIndex = 0;
149
+ for (let i = 0; i < allLines.length; i += linesPerFile) {
150
+ const chunk = allLines.slice(i, i + linesPerFile).join("\n");
151
+ const outPath = resolvePath(cwd, `${prefix}${alphaSuffix(chunkIndex)}`);
152
+ shell.writeFileAsUser(authUser, outPath, chunk);
153
+ chunkIndex++;
154
+ }
155
+ return { exitCode: 0 };
156
+ },
157
+ };
158
+ /**
159
+ * Split a file based on context patterns
160
+ * @category text
161
+ * @params ["<file> <pattern>..."]
162
+ */
163
+ export const csplitCommand = {
164
+ name: "csplit",
165
+ description: "Split a file based on context patterns",
166
+ category: "text",
167
+ params: ["<file> <pattern>..."],
168
+ run: () => {
169
+ return { stderr: "csplit: not implemented\n", exitCode: 1 };
170
+ },
171
+ };
@@ -0,0 +1,7 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ /**
3
+ * Display processes
4
+ * @category system
5
+ * @params []
6
+ */
7
+ export declare const topCommand: ShellModule;
@@ -0,0 +1,54 @@
1
+ import * as os from "node:os";
2
+ /**
3
+ * Display processes
4
+ * @category system
5
+ * @params []
6
+ */
7
+ export const topCommand = {
8
+ name: "top",
9
+ description: "Display processes",
10
+ category: "system",
11
+ params: [],
12
+ run: ({ shell }) => {
13
+ const uptimeSec = Math.floor((Date.now() - shell.startTime) / 1000);
14
+ const sessions = shell.users.listActiveSessions();
15
+ const procs = shell.users.listProcesses();
16
+ const totalMem = os.totalmem();
17
+ const freeMem = os.freemem();
18
+ const usedMem = totalMem - freeMem;
19
+ const loadAverages = os.loadavg();
20
+ const lines = [];
21
+ const uptimeStr = `${Math.floor(uptimeSec / 3600)}:${String(Math.floor((uptimeSec % 3600) / 60)).padStart(2, "0")}`;
22
+ lines.push(`top - ${new Date().toLocaleTimeString()} up ${uptimeStr}, ${sessions.length} user(s), load average: ${loadAverages.map((l) => l.toFixed(2)).join(", ")}`);
23
+ lines.push(`Tasks: ${sessions.length + procs.length} total, ${procs.filter((p) => p.status === "running").length || 1} running`);
24
+ const memTotalMb = (totalMem / 1024 / 1024).toFixed(0);
25
+ const memUsedMb = (usedMem / 1024 / 1024).toFixed(0);
26
+ const memFreeMb = (freeMem / 1024 / 1024).toFixed(0);
27
+ lines.push(`MiB Mem : ${memTotalMb.padStart(8)} total, ${memFreeMb.padStart(8)} free, ${memUsedMb.padStart(8)} used`);
28
+ const swapTotal = Math.floor(totalMem * 0.5);
29
+ const swapUsed = Math.floor(swapTotal * 0.05);
30
+ const swapFree = swapTotal - swapUsed;
31
+ lines.push(`MiB Swap: ${String(swapTotal).padStart(8)} total, ${String(swapFree).padStart(8)} free, ${String(swapUsed).padStart(8)} used`);
32
+ lines.push("");
33
+ lines.push(" PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND");
34
+ sessions.forEach((s, i) => {
35
+ const pid = 1000 + i;
36
+ const virt = Math.floor(Math.random() * 200000 + 50000);
37
+ const res = Math.floor(Math.random() * 10000 + 2000);
38
+ const shr = Math.floor(res * 0.6);
39
+ const cpu = (Math.random() * 5).toFixed(1);
40
+ const mem = ((res / (totalMem / 1024)) * 100).toFixed(1);
41
+ lines.push(`${String(pid).padStart(5)} ${s.username.padEnd(8).slice(0, 8)} 20 0 ${String(virt).padStart(7)} ${String(res).padStart(6)} ${String(shr).padStart(6)} S ${cpu.padStart(4)} ${mem.padStart(5)} 0:00.00 bash`);
42
+ });
43
+ procs.forEach((p) => {
44
+ const virt = Math.floor(Math.random() * 50000 + 10000);
45
+ const res = Math.floor(Math.random() * 5000 + 500);
46
+ const shr = Math.floor(res * 0.5);
47
+ const cpu = (Math.random() * 10).toFixed(1);
48
+ const mem = ((res / (totalMem / 1024)) * 100).toFixed(1);
49
+ const status = p.status === "running" ? "R" : "S";
50
+ lines.push(`${String(p.pid).padStart(5)} ${p.username.padEnd(8).slice(0, 8)} 20 0 ${String(virt).padStart(7)} ${String(res).padStart(6)} ${String(shr).padStart(6)} ${status} ${cpu.padStart(4)} ${mem.padStart(5)} 0:00.00 ${p.command}`);
51
+ });
52
+ return { stdout: lines.join("\n") + "\n", exitCode: 0 };
53
+ },
54
+ };
@@ -1,4 +1,5 @@
1
- import { assertPathAccess, resolvePath } from "./helpers";
1
+ import * as path from "node:path";
2
+ import { checkFilePermission, resolvePath } from "./helpers";
2
3
  /**
3
4
  * Create empty files or update file timestamps.
4
5
  * @category files
@@ -15,10 +16,13 @@ export const touchCommand = {
15
16
  }
16
17
  for (const file of args) {
17
18
  const target = resolvePath(cwd, file);
18
- assertPathAccess(authUser, target, "touch");
19
19
  if (!shell.vfs.exists(target)) {
20
+ checkFilePermission(shell.vfs, shell.users, authUser, path.posix.dirname(target), 2);
20
21
  shell.writeFileAsUser(authUser, target, "");
21
22
  }
23
+ else {
24
+ checkFilePermission(shell.vfs, shell.users, authUser, target, 2);
25
+ }
22
26
  }
23
27
  return { exitCode: 0 };
24
28
  },
@@ -1,2 +1,7 @@
1
1
  import type { ShellModule } from "../types/commands";
2
+ /**
3
+ * Translate or delete characters.
4
+ * @category text
5
+ * @params ["[-d] [-s] <set1> [set2]"]
6
+ */
2
7
  export declare const trCommand: ShellModule;
@@ -27,6 +27,11 @@ function expandTrSet(s) {
27
27
  }
28
28
  return chars;
29
29
  }
30
+ /**
31
+ * Translate or delete characters.
32
+ * @category text
33
+ * @params ["[-d] [-s] <set1> [set2]"]
34
+ */
30
35
  export const trCommand = {
31
36
  name: "tr",
32
37
  description: "Translate or delete characters",
@@ -27,7 +27,7 @@ export const wCommand = {
27
27
  const log = JSON.parse(shell.vfs.readFile(logPath));
28
28
  loginTime = new Date(log.at).toTimeString().slice(0, 5);
29
29
  }
30
- catch { }
30
+ catch { /* login log may be absent */ }
31
31
  }
32
32
  const header = ` ${timeStr} up ${uptimeStr}, 1 user, load average: 0.${Math.floor(Math.random() * 30).toString().padStart(2, "0")}, 0.${Math.floor(Math.random() * 15).toString().padStart(2, "0")}, 0.${Math.floor(Math.random() * 10).toString().padStart(2, "0")}`;
33
33
  const colHeader = "USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT";
@@ -1,2 +1,7 @@
1
1
  import type { ShellModule } from "../types/commands";
2
+ /**
3
+ * Locate a command in PATH.
4
+ * @category shell
5
+ * @params ["<command...>"]
6
+ */
2
7
  export declare const whichCommand: ShellModule;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Locate a command in PATH.
3
+ * @category shell
4
+ * @params ["<command...>"]
5
+ */
1
6
  export const whichCommand = {
2
7
  name: "which",
3
8
  description: "Locate a command in PATH",
package/dist/index.d.ts CHANGED
@@ -4,17 +4,25 @@ export { SftpMimic as VirtualSftpServer, SshMimic as VirtualSshServer } from "./
4
4
  export type { SftpMimicOptions as VirtualSftpServerOptions } from "./SSHMimic/sftp";
5
5
  export { default as VirtualFileSystem } from "./VirtualFileSystem/index";
6
6
  export { VirtualPackageManager } from "./VirtualPackageManager/index";
7
+ export { VirtualNetworkManager } from "./modules/VirtualNetworkManager";
8
+ export type { VirtualInterface, VirtualRoute, VirtualArpEntry } from "./modules/VirtualNetworkManager";
7
9
  export { VirtualShell } from "./VirtualShell/index";
8
10
  export { VirtualUserManager } from "./VirtualUserManager/index";
9
- export type { VirtualActiveSession, VirtualProcess } from "./VirtualUserManager/index";
11
+ export type { VirtualActiveSession, VirtualProcess, VirtualUserRecord, ProcessStatus } from "./VirtualUserManager/index";
10
12
  export { IdleManager } from "./VirtualShell/idleManager";
11
13
  export type { IdleManagerOptions, IdleState } from "./VirtualShell/idleManager";
12
14
  export type { AuditLogEntry, HoneyPotStats } from "./Honeypot/index";
13
15
  export type { CommandContext, CommandMode, CommandOutcome, CommandResult, NanoEditorSession, PasswordChallenge, ShellEnv, ShellModule, SudoChallenge } from "./types/commands";
14
16
  export type { ExecStream, ShellStream } from "./types/streams";
15
- export type { RemoveOptions, VfsBaseNode, VfsDirectoryNode, VfsFileNode, VfsNodeStats, VfsNodeType, VfsSnapshot, VfsSnapshotBaseNode, VfsSnapshotDirectoryNode, VfsSnapshotFileNode, VfsSnapshotNode, WriteFileOptions } from "./types/vfs";
17
+ export type { MountOptions, MountPoint, RemoveOptions, VfsBaseNode, VfsDirectoryNode, VfsFileNode, VfsNodeStats, VfsNodeType, VfsSnapshot, VfsSnapshotBaseNode, VfsSnapshotDirectoryNode, VfsSnapshotFileNode, VfsSnapshotNode, WriteFileOptions } from "./types/vfs";
16
18
  export type { VfsOptions, VfsPersistenceMode } from "./VirtualFileSystem/index";
17
19
  export type { ShellProperties, VirtualShellVfsLike, VirtualShellVfsOptions } from "./VirtualShell/index";
20
+ export type { PipelineCommand, Pipeline, Statement, Script, LogicalOp } from "./types/pipeline";
21
+ export { NanoEditor } from "./modules/nanoEditor";
22
+ export type { NanoEditorOptions, NanoExitReason } from "./modules/nanoEditor";
23
+ export { parseArgs } from "./commands/command-helpers";
24
+ export { createPerfLogger } from "./utils/perfLogger";
25
+ export type { PerfLogger } from "./utils/perfLogger";
18
26
  export type { InstalledPackage, PackageDefinition, PackageFile } from "./VirtualPackageManager/index";
19
27
  export { assertDiff, diffSnapshots, formatDiff } from "./utils/vfsDiff";
20
28
  export type { VfsDiff, VfsDiffEntry, VfsDiffModified } from "./utils/vfsDiff";
package/dist/index.js CHANGED
@@ -3,8 +3,12 @@ export { SshClient } from "./SSHClient/index";
3
3
  export { SftpMimic as VirtualSftpServer, SshMimic as VirtualSshServer } from "./SSHMimic/index";
4
4
  export { default as VirtualFileSystem } from "./VirtualFileSystem/index";
5
5
  export { VirtualPackageManager } from "./VirtualPackageManager/index";
6
+ export { VirtualNetworkManager } from "./modules/VirtualNetworkManager";
6
7
  export { VirtualShell } from "./VirtualShell/index";
7
8
  export { VirtualUserManager } from "./VirtualUserManager/index";
8
9
  export { IdleManager } from "./VirtualShell/idleManager";
10
+ export { NanoEditor } from "./modules/nanoEditor";
11
+ export { parseArgs } from "./commands/command-helpers";
12
+ export { createPerfLogger } from "./utils/perfLogger";
9
13
  export { assertDiff, diffSnapshots, formatDiff } from "./utils/vfsDiff";
10
14
  export { getArg, getFlag, ifFlag } from "./commands/command-helpers";