typescript-virtual-container 1.5.11 → 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.
- package/README.md +236 -456
- package/dist/.tsbuildinfo +1 -1
- package/dist/Honeypot/index.d.ts +9 -0
- package/dist/Honeypot/index.js +57 -0
- package/dist/SSHMimic/exec.d.ts +4 -0
- package/dist/SSHMimic/exec.js +4 -0
- package/dist/SSHMimic/executor.d.ts +10 -1
- package/dist/SSHMimic/executor.js +18 -8
- package/dist/SSHMimic/hostKey.d.ts +5 -0
- package/dist/SSHMimic/hostKey.js +5 -0
- package/dist/SSHMimic/loginBanner.d.ts +7 -0
- package/dist/SSHMimic/loginBanner.js +4 -0
- package/dist/SSHMimic/loginFormat.d.ts +4 -0
- package/dist/SSHMimic/loginFormat.js +4 -0
- package/dist/SSHMimic/prompt.d.ts +9 -0
- package/dist/SSHMimic/prompt.js +9 -0
- package/dist/SSHMimic/scp.d.ts +18 -0
- package/dist/SSHMimic/scp.js +14 -0
- package/dist/VirtualFileSystem/binaryPack.d.ts +7 -3
- package/dist/VirtualFileSystem/binaryPack.js +32 -10
- package/dist/VirtualFileSystem/index.d.ts +29 -0
- package/dist/VirtualFileSystem/index.js +126 -5
- package/dist/VirtualFileSystem/internalTypes.d.ts +4 -0
- package/dist/VirtualFileSystem/journal.d.ts +10 -4
- package/dist/VirtualFileSystem/journal.js +12 -2
- package/dist/VirtualFileSystem/path.d.ts +23 -1
- package/dist/VirtualFileSystem/path.js +23 -3
- package/dist/VirtualPackageManager/index.js +1 -1
- package/dist/VirtualShell/index.d.ts +3 -0
- package/dist/VirtualShell/index.js +12 -3
- package/dist/VirtualUserManager/index.d.ts +20 -1
- package/dist/VirtualUserManager/index.js +52 -15
- package/dist/commands/bc.d.ts +5 -0
- package/dist/commands/bc.js +5 -0
- package/dist/commands/cat.js +2 -2
- package/dist/commands/chgrp.d.ts +7 -0
- package/dist/commands/chgrp.js +42 -0
- package/dist/commands/chown.d.ts +7 -0
- package/dist/commands/chown.js +79 -0
- package/dist/commands/cp.js +4 -3
- package/dist/commands/dd.d.ts +7 -0
- package/dist/commands/dd.js +60 -0
- package/dist/commands/declare.js +0 -2
- package/dist/commands/expr.d.ts +7 -0
- package/dist/commands/expr.js +63 -0
- package/dist/commands/fun.d.ts +5 -0
- package/dist/commands/fun.js +5 -1
- package/dist/commands/help.d.ts +5 -0
- package/dist/commands/help.js +5 -0
- package/dist/commands/helpers.d.ts +43 -0
- package/dist/commands/helpers.js +61 -0
- package/dist/commands/id.d.ts +5 -0
- package/dist/commands/id.js +5 -0
- package/dist/commands/ip.d.ts +1 -0
- package/dist/commands/ip.js +50 -23
- package/dist/commands/jobs.js +43 -9
- package/dist/commands/kill.d.ts +1 -0
- package/dist/commands/kill.js +13 -5
- package/dist/commands/last.js +1 -1
- package/dist/commands/ln.d.ts +5 -0
- package/dist/commands/ln.js +5 -0
- package/dist/commands/ls.d.ts +5 -0
- package/dist/commands/ls.js +19 -4
- package/dist/commands/lsb-release.js +1 -1
- package/dist/commands/man.d.ts +5 -0
- package/dist/commands/man.js +5 -0
- package/dist/commands/manuals-bundle.js +242 -0
- package/dist/commands/miscutils.d.ts +43 -0
- package/dist/commands/miscutils.js +233 -0
- package/dist/commands/mkdir.js +3 -2
- package/dist/commands/mv.js +4 -3
- package/dist/commands/netcat.d.ts +7 -0
- package/dist/commands/netcat.js +64 -0
- package/dist/commands/nice.d.ts +7 -0
- package/dist/commands/nice.js +22 -0
- package/dist/commands/nohup.d.ts +7 -0
- package/dist/commands/nohup.js +18 -0
- package/dist/commands/ping.d.ts +2 -1
- package/dist/commands/ping.js +46 -8
- package/dist/commands/procUtils.d.ts +13 -0
- package/dist/commands/procUtils.js +72 -0
- package/dist/commands/pwd.d.ts +5 -0
- package/dist/commands/pwd.js +5 -0
- package/dist/commands/python.js +0 -4
- package/dist/commands/read.js +0 -1
- package/dist/commands/registry.d.ts +37 -0
- package/dist/commands/registry.js +73 -0
- package/dist/commands/rm.js +3 -2
- package/dist/commands/runtime.d.ts +47 -1
- package/dist/commands/runtime.js +60 -5
- package/dist/commands/sh.d.ts +5 -0
- package/dist/commands/sh.js +5 -0
- package/dist/commands/stat.js +3 -2
- package/dist/commands/strace.js +0 -1
- package/dist/commands/sysinfo.d.ts +19 -0
- package/dist/commands/sysinfo.js +73 -0
- package/dist/commands/test.d.ts +5 -0
- package/dist/commands/test.js +5 -0
- package/dist/commands/textutils.d.ts +25 -0
- package/dist/commands/textutils.js +171 -0
- package/dist/commands/top.d.ts +7 -0
- package/dist/commands/top.js +54 -0
- package/dist/commands/touch.js +6 -2
- package/dist/commands/tr.d.ts +5 -0
- package/dist/commands/tr.js +5 -0
- package/dist/commands/w.js +1 -1
- package/dist/commands/which.d.ts +5 -0
- package/dist/commands/which.js +5 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +4 -0
- package/dist/modules/VirtualNetworkManager.d.ts +54 -0
- package/dist/modules/VirtualNetworkManager.js +144 -0
- package/dist/modules/linuxRootfs.d.ts +4 -3
- package/dist/modules/linuxRootfs.js +115 -74
- package/dist/modules/neofetch.d.ts +2 -0
- package/dist/modules/neofetch.js +3 -2
- package/dist/modules/pacmanGame.d.ts +2 -0
- package/dist/modules/pacmanGame.js +1 -0
- package/dist/modules/shellInteractive.d.ts +2 -0
- package/dist/modules/shellInteractive.js +2 -0
- package/dist/modules/shellRuntime.d.ts +7 -0
- package/dist/modules/shellRuntime.js +6 -0
- package/dist/modules/webTermRenderer.js +0 -7
- package/dist/types/commands.d.ts +1 -1
- package/dist/types/vfs.d.ts +8 -0
- package/dist/utils/argv.d.ts +22 -3
- package/dist/utils/argv.js +22 -3
- package/dist/utils/perfLogger.d.ts +10 -2
- package/dist/utils/perfLogger.js +7 -14
- package/dist/utils/shellSession.d.ts +35 -0
- package/dist/utils/shellSession.js +35 -0
- package/package.json +1 -1
package/dist/commands/runtime.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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)
|
package/dist/commands/sh.d.ts
CHANGED
package/dist/commands/sh.js
CHANGED
|
@@ -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"],
|
package/dist/commands/stat.js
CHANGED
|
@@ -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: (
|
|
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");
|
package/dist/commands/strace.js
CHANGED
|
@@ -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
|
+
};
|
package/dist/commands/test.d.ts
CHANGED
package/dist/commands/test.js
CHANGED
|
@@ -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,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
|
+
};
|
package/dist/commands/touch.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
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
|
},
|
package/dist/commands/tr.d.ts
CHANGED
package/dist/commands/tr.js
CHANGED
|
@@ -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",
|
package/dist/commands/w.js
CHANGED
|
@@ -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";
|
package/dist/commands/which.d.ts
CHANGED
package/dist/commands/which.js
CHANGED
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";
|