typescript-virtual-container 1.2.5 → 1.2.6
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 +387 -193
- package/benchmark-results.txt +21 -21
- package/dist/SSHMimic/exec.js +2 -2
- package/dist/SSHMimic/executor.d.ts +6 -7
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +77 -60
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +6 -20
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +14 -0
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +13 -36
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +19 -2
- package/dist/VirtualShell/shellParser.d.ts +20 -2
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualShell/shellParser.js +229 -120
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +2 -0
- package/dist/commands/awk.d.ts +3 -0
- package/dist/commands/awk.d.ts.map +1 -0
- package/dist/commands/awk.js +29 -0
- package/dist/commands/base64.d.ts +3 -0
- package/dist/commands/base64.d.ts.map +1 -0
- package/dist/commands/base64.js +20 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +2 -0
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +2 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +2 -0
- package/dist/commands/clear.d.ts.map +1 -1
- package/dist/commands/clear.js +4 -1
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +2 -0
- package/dist/commands/curl.d.ts.map +1 -1
- package/dist/commands/curl.js +2 -0
- package/dist/commands/cut.d.ts +3 -0
- package/dist/commands/cut.d.ts.map +1 -0
- package/dist/commands/cut.js +27 -0
- package/dist/commands/date.d.ts +3 -0
- package/dist/commands/date.d.ts.map +1 -0
- package/dist/commands/date.js +22 -0
- package/dist/commands/deluser.d.ts.map +1 -1
- package/dist/commands/deluser.js +2 -0
- package/dist/commands/df.d.ts +3 -0
- package/dist/commands/df.d.ts.map +1 -0
- package/dist/commands/df.js +16 -0
- package/dist/commands/diff.d.ts +3 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +40 -0
- package/dist/commands/du.d.ts +3 -0
- package/dist/commands/du.d.ts.map +1 -0
- package/dist/commands/du.js +39 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +2 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +6 -14
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +11 -21
- package/dist/commands/find.d.ts.map +1 -1
- package/dist/commands/find.js +2 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +4 -7
- package/dist/commands/groups.d.ts +3 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +12 -0
- package/dist/commands/gzip.d.ts +4 -0
- package/dist/commands/gzip.d.ts.map +1 -0
- package/dist/commands/gzip.js +40 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +2 -0
- package/dist/commands/help.d.ts +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +75 -3
- package/dist/commands/hostname.d.ts.map +1 -1
- package/dist/commands/hostname.js +2 -0
- package/dist/commands/htop.d.ts.map +1 -1
- package/dist/commands/htop.js +2 -0
- package/dist/commands/id.d.ts +3 -0
- package/dist/commands/id.d.ts.map +1 -0
- package/dist/commands/id.js +14 -0
- package/dist/commands/index.d.ts +5 -2
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +89 -62
- package/dist/commands/kill.d.ts +3 -0
- package/dist/commands/kill.d.ts.map +1 -0
- package/dist/commands/kill.js +13 -0
- package/dist/commands/ln.d.ts.map +1 -1
- package/dist/commands/ln.js +2 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +2 -0
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +2 -0
- package/dist/commands/mv.d.ts.map +1 -1
- package/dist/commands/mv.js +2 -0
- package/dist/commands/nano.d.ts.map +1 -1
- package/dist/commands/nano.js +2 -0
- package/dist/commands/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +2 -0
- package/dist/commands/passwd.d.ts.map +1 -1
- package/dist/commands/passwd.js +2 -0
- package/dist/commands/ping.d.ts +3 -0
- package/dist/commands/ping.d.ts.map +1 -0
- package/dist/commands/ping.js +18 -0
- package/dist/commands/ps.d.ts +3 -0
- package/dist/commands/ps.d.ts.map +1 -0
- package/dist/commands/ps.js +17 -0
- package/dist/commands/pwd.d.ts.map +1 -1
- package/dist/commands/pwd.js +2 -0
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +2 -0
- package/dist/commands/sed.d.ts +3 -0
- package/dist/commands/sed.d.ts.map +1 -0
- package/dist/commands/sed.js +47 -0
- package/dist/commands/set.d.ts +3 -0
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +19 -46
- package/dist/commands/sh.d.ts +0 -1
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +228 -35
- package/dist/commands/sleep.d.ts +3 -0
- package/dist/commands/sleep.d.ts.map +1 -0
- package/dist/commands/sleep.js +13 -0
- package/dist/commands/sort.d.ts +3 -0
- package/dist/commands/sort.d.ts.map +1 -0
- package/dist/commands/sort.js +37 -0
- package/dist/commands/su.d.ts.map +1 -1
- package/dist/commands/su.js +2 -0
- package/dist/commands/sudo.d.ts.map +1 -1
- package/dist/commands/sudo.js +2 -0
- package/dist/commands/tail.d.ts.map +1 -1
- package/dist/commands/tail.js +2 -0
- package/dist/commands/tar.d.ts +3 -0
- package/dist/commands/tar.d.ts.map +1 -0
- package/dist/commands/tar.js +64 -0
- package/dist/commands/tee.d.ts +3 -0
- package/dist/commands/tee.d.ts.map +1 -0
- package/dist/commands/tee.js +29 -0
- package/dist/commands/touch.d.ts.map +1 -1
- package/dist/commands/touch.js +2 -0
- package/dist/commands/tr.d.ts +3 -0
- package/dist/commands/tr.d.ts.map +1 -0
- package/dist/commands/tr.js +24 -0
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +2 -0
- package/dist/commands/uname.d.ts +3 -0
- package/dist/commands/uname.d.ts.map +1 -0
- package/dist/commands/uname.js +21 -0
- package/dist/commands/uniq.d.ts +3 -0
- package/dist/commands/uniq.d.ts.map +1 -0
- package/dist/commands/uniq.js +33 -0
- package/dist/commands/unset.d.ts.map +1 -1
- package/dist/commands/unset.js +6 -10
- package/dist/commands/wc.d.ts.map +1 -1
- package/dist/commands/wc.js +2 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +2 -0
- package/dist/commands/who.d.ts.map +1 -1
- package/dist/commands/who.js +2 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +2 -0
- package/dist/commands/xargs.d.ts +3 -0
- package/dist/commands/xargs.d.ts.map +1 -0
- package/dist/commands/xargs.js +16 -0
- package/dist/types/commands.d.ts +13 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/pipeline.d.ts +20 -0
- package/dist/types/pipeline.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/SSHMimic/exec.ts +2 -2
- package/src/SSHMimic/executor.ts +95 -98
- package/src/SSHMimic/index.ts +15 -49
- package/src/SSHMimic/sftp.ts +15 -0
- package/src/VirtualFileSystem/index.ts +27 -75
- package/src/VirtualShell/shell.ts +19 -2
- package/src/VirtualShell/shellParser.ts +202 -168
- package/src/VirtualUserManager/index.ts +2 -7
- package/src/commands/adduser.ts +2 -0
- package/src/commands/awk.ts +30 -0
- package/src/commands/base64.ts +18 -0
- package/src/commands/cat.ts +2 -0
- package/src/commands/cd.ts +2 -0
- package/src/commands/chmod.ts +2 -0
- package/src/commands/clear.ts +4 -1
- package/src/commands/cp.ts +2 -0
- package/src/commands/curl.ts +2 -0
- package/src/commands/cut.ts +29 -0
- package/src/commands/date.ts +24 -0
- package/src/commands/deluser.ts +2 -0
- package/src/commands/df.ts +18 -0
- package/src/commands/diff.ts +29 -0
- package/src/commands/du.ts +39 -0
- package/src/commands/echo.ts +2 -0
- package/src/commands/env.ts +6 -16
- package/src/commands/export.ts +11 -24
- package/src/commands/find.ts +2 -0
- package/src/commands/grep.ts +4 -7
- package/src/commands/groups.ts +14 -0
- package/src/commands/gzip.ts +31 -0
- package/src/commands/head.ts +2 -0
- package/src/commands/help.ts +81 -3
- package/src/commands/hostname.ts +2 -0
- package/src/commands/htop.ts +2 -0
- package/src/commands/id.ts +16 -0
- package/src/commands/index.ts +98 -99
- package/src/commands/kill.ts +14 -0
- package/src/commands/ln.ts +2 -0
- package/src/commands/ls.ts +2 -0
- package/src/commands/mkdir.ts +2 -0
- package/src/commands/mv.ts +2 -0
- package/src/commands/nano.ts +2 -0
- package/src/commands/neofetch.ts +2 -0
- package/src/commands/passwd.ts +2 -0
- package/src/commands/ping.ts +20 -0
- package/src/commands/ps.ts +19 -0
- package/src/commands/pwd.ts +2 -0
- package/src/commands/rm.ts +2 -0
- package/src/commands/sed.ts +45 -0
- package/src/commands/set.ts +19 -50
- package/src/commands/sh.ts +192 -43
- package/src/commands/sleep.ts +14 -0
- package/src/commands/sort.ts +37 -0
- package/src/commands/su.ts +2 -0
- package/src/commands/sudo.ts +2 -0
- package/src/commands/tail.ts +2 -0
- package/src/commands/tar.ts +58 -0
- package/src/commands/tee.ts +25 -0
- package/src/commands/touch.ts +2 -0
- package/src/commands/tr.ts +24 -0
- package/src/commands/tree.ts +2 -0
- package/src/commands/uname.ts +20 -0
- package/src/commands/uniq.ts +28 -0
- package/src/commands/unset.ts +5 -12
- package/src/commands/wc.ts +2 -0
- package/src/commands/wget.ts +2 -0
- package/src/commands/who.ts +2 -0
- package/src/commands/whoami.ts +2 -0
- package/src/commands/xargs.ts +17 -0
- package/src/types/commands.ts +14 -0
- package/src/types/pipeline.ts +23 -0
- package/standalone.js +92 -64
- package/standalone.js.map +4 -4
- package/tests/users.test.ts +5 -34
package/src/commands/index.ts
CHANGED
|
@@ -1,28 +1,41 @@
|
|
|
1
|
+
/** biome-ignore-all lint/style/useNamingConvention: ENV VARIABLES */
|
|
1
2
|
import type { VirtualShell } from "../VirtualShell";
|
|
2
3
|
import type {
|
|
3
4
|
CommandContext,
|
|
4
5
|
CommandMode,
|
|
5
6
|
CommandResult,
|
|
7
|
+
ShellEnv,
|
|
6
8
|
ShellModule,
|
|
7
9
|
} from "../types/commands";
|
|
8
10
|
import { adduserCommand } from "./adduser";
|
|
11
|
+
import { awkCommand } from "./awk";
|
|
12
|
+
import { base64Command } from "./base64";
|
|
9
13
|
import { catCommand } from "./cat";
|
|
10
14
|
import { cdCommand } from "./cd";
|
|
11
15
|
import { chmodCommand } from "./chmod";
|
|
12
16
|
import { clearCommand } from "./clear";
|
|
13
17
|
import { cpCommand } from "./cp";
|
|
14
18
|
import { curlCommand } from "./curl";
|
|
19
|
+
import { cutCommand } from "./cut";
|
|
20
|
+
import { dateCommand } from "./date";
|
|
15
21
|
import { deluserCommand } from "./deluser";
|
|
22
|
+
import { dfCommand } from "./df";
|
|
23
|
+
import { diffCommand } from "./diff";
|
|
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";
|
|
21
30
|
import { grepCommand } from "./grep";
|
|
31
|
+
import { groupsCommand } from "./groups";
|
|
32
|
+
import { gunzipCommand, gzipCommand } from "./gzip";
|
|
22
33
|
import { headCommand } from "./head";
|
|
23
34
|
import { createHelpCommand } from "./help";
|
|
24
35
|
import { hostnameCommand } from "./hostname";
|
|
25
36
|
import { htopCommand } from "./htop";
|
|
37
|
+
import { idCommand } from "./id";
|
|
38
|
+
import { killCommand } from "./kill";
|
|
26
39
|
import { lnCommand } from "./ln";
|
|
27
40
|
import { lsCommand } from "./ls";
|
|
28
41
|
import { mkdirCommand } from "./mkdir";
|
|
@@ -30,78 +43,71 @@ import { mvCommand } from "./mv";
|
|
|
30
43
|
import { nanoCommand } from "./nano";
|
|
31
44
|
import { neofetchCommand } from "./neofetch";
|
|
32
45
|
import { passwdCommand } from "./passwd";
|
|
46
|
+
import { pingCommand } from "./ping";
|
|
47
|
+
import { psCommand } from "./ps";
|
|
33
48
|
import { pwdCommand } from "./pwd";
|
|
34
49
|
import { rmCommand } from "./rm";
|
|
50
|
+
import { sedCommand } from "./sed";
|
|
35
51
|
import { setCommand } from "./set";
|
|
36
52
|
import { shCommand } from "./sh";
|
|
53
|
+
import { sleepCommand } from "./sleep";
|
|
54
|
+
import { sortCommand } from "./sort";
|
|
37
55
|
import { suCommand } from "./su";
|
|
38
56
|
import { sudoCommand } from "./sudo";
|
|
39
57
|
import { tailCommand } from "./tail";
|
|
58
|
+
import { tarCommand } from "./tar";
|
|
59
|
+
import { teeCommand } from "./tee";
|
|
40
60
|
import { touchCommand } from "./touch";
|
|
61
|
+
import { trCommand } from "./tr";
|
|
41
62
|
import { treeCommand } from "./tree";
|
|
63
|
+
import { unameCommand } from "./uname";
|
|
64
|
+
import { uniqCommand } from "./uniq";
|
|
42
65
|
import { unsetCommand } from "./unset";
|
|
43
66
|
import { wcCommand } from "./wc";
|
|
44
67
|
import { wgetCommand } from "./wget";
|
|
45
68
|
import { whoCommand } from "./who";
|
|
46
69
|
import { whoamiCommand } from "./whoami";
|
|
70
|
+
import { xargsCommand } from "./xargs";
|
|
47
71
|
|
|
48
72
|
const BASE_COMMANDS: ShellModule[] = [
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
// Navigation
|
|
74
|
+
pwdCommand, cdCommand, lsCommand, treeCommand,
|
|
75
|
+
// Files
|
|
76
|
+
catCommand, touchCommand, rmCommand, mkdirCommand, cpCommand, mvCommand, lnCommand,
|
|
77
|
+
chmodCommand, findCommand,
|
|
78
|
+
// Text processing
|
|
79
|
+
grepCommand, sedCommand, awkCommand, sortCommand, uniqCommand, wcCommand,
|
|
80
|
+
headCommand, tailCommand, cutCommand, trCommand, teeCommand, xargsCommand,
|
|
81
|
+
diffCommand,
|
|
82
|
+
// Archives
|
|
83
|
+
tarCommand, gzipCommand, gunzipCommand, base64Command,
|
|
84
|
+
// System info
|
|
85
|
+
whoamiCommand, whoCommand, hostnameCommand, idCommand, groupsCommand, unameCommand,
|
|
86
|
+
psCommand, killCommand, dfCommand, duCommand, dateCommand, sleepCommand, pingCommand,
|
|
87
|
+
// Shell
|
|
88
|
+
echoCommand, envCommand, exportCommand, setCommand, unsetCommand, shCommand,
|
|
89
|
+
clearCommand, exitCommand,
|
|
90
|
+
// Editors
|
|
91
|
+
nanoCommand, htopCommand,
|
|
92
|
+
// Network
|
|
93
|
+
curlCommand, wgetCommand,
|
|
94
|
+
// Users
|
|
95
|
+
adduserCommand, passwdCommand, deluserCommand, sudoCommand, suCommand,
|
|
96
|
+
// Misc
|
|
62
97
|
neofetchCommand,
|
|
63
|
-
htopCommand,
|
|
64
|
-
adduserCommand,
|
|
65
|
-
passwdCommand,
|
|
66
|
-
deluserCommand,
|
|
67
|
-
sudoCommand,
|
|
68
|
-
suCommand,
|
|
69
|
-
curlCommand,
|
|
70
|
-
envCommand,
|
|
71
|
-
wgetCommand,
|
|
72
|
-
grepCommand,
|
|
73
|
-
exportCommand,
|
|
74
|
-
setCommand,
|
|
75
|
-
unsetCommand,
|
|
76
|
-
shCommand,
|
|
77
|
-
clearCommand,
|
|
78
|
-
exitCommand,
|
|
79
|
-
cpCommand,
|
|
80
|
-
mvCommand,
|
|
81
|
-
lnCommand,
|
|
82
|
-
findCommand,
|
|
83
|
-
wcCommand,
|
|
84
|
-
headCommand,
|
|
85
|
-
tailCommand,
|
|
86
|
-
chmodCommand,
|
|
87
98
|
];
|
|
88
99
|
|
|
89
100
|
const customCommands: ShellModule[] = [];
|
|
90
|
-
|
|
91
|
-
const helpCommand = createHelpCommand(() =>
|
|
92
|
-
getCommandModules().map((cmd) => cmd.name),
|
|
93
|
-
);
|
|
94
|
-
|
|
95
101
|
const commandRegistry = new Map<string, ShellModule>();
|
|
96
102
|
let cachedCommandNames: string[] | null = null;
|
|
97
103
|
|
|
104
|
+
const helpCommand = createHelpCommand(() => getCommandModules().map((cmd) => cmd.name));
|
|
105
|
+
|
|
98
106
|
function buildCache(): void {
|
|
99
107
|
commandRegistry.clear();
|
|
100
108
|
for (const mod of getCommandModules()) {
|
|
101
109
|
commandRegistry.set(mod.name, mod);
|
|
102
|
-
for (const alias of mod.aliases ?? [])
|
|
103
|
-
commandRegistry.set(alias, mod);
|
|
104
|
-
}
|
|
110
|
+
for (const alias of mod.aliases ?? []) commandRegistry.set(alias, mod);
|
|
105
111
|
}
|
|
106
112
|
cachedCommandNames = Array.from(commandRegistry.keys()).sort();
|
|
107
113
|
}
|
|
@@ -114,16 +120,12 @@ export function registerCommand(module: ShellModule): void {
|
|
|
114
120
|
const normalized: ShellModule = {
|
|
115
121
|
...module,
|
|
116
122
|
name: module.name.trim().toLowerCase(),
|
|
117
|
-
aliases: module.aliases?.map((
|
|
123
|
+
aliases: module.aliases?.map((a) => a.trim().toLowerCase()),
|
|
118
124
|
};
|
|
119
|
-
|
|
120
125
|
const names = [normalized.name, ...(normalized.aliases ?? [])];
|
|
121
|
-
if (names.some((
|
|
122
|
-
throw new Error(
|
|
123
|
-
"Command names and aliases must be non-empty and contain no spaces",
|
|
124
|
-
);
|
|
126
|
+
if (names.some((n) => n.length === 0 || /\s/.test(n))) {
|
|
127
|
+
throw new Error("Command names must be non-empty and contain no spaces");
|
|
125
128
|
}
|
|
126
|
-
|
|
127
129
|
customCommands.push(normalized);
|
|
128
130
|
buildCache();
|
|
129
131
|
}
|
|
@@ -141,6 +143,10 @@ export function getCommandNames(): string[] {
|
|
|
141
143
|
return cachedCommandNames!;
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
export function getCommandModulesPublic(): ShellModule[] {
|
|
147
|
+
return getCommandModules();
|
|
148
|
+
}
|
|
149
|
+
|
|
144
150
|
export function resolveModule(name: string): ShellModule | undefined {
|
|
145
151
|
if (!cachedCommandNames) buildCache();
|
|
146
152
|
return commandRegistry.get(name.toLowerCase());
|
|
@@ -152,43 +158,41 @@ function splitArgsRespectingQuotes(input: string): string[] {
|
|
|
152
158
|
let inQuotes = false;
|
|
153
159
|
let quoteChar = "";
|
|
154
160
|
|
|
155
|
-
for (let i = 0; i < input.length; i
|
|
161
|
+
for (let i = 0; i < input.length; i++) {
|
|
156
162
|
const ch = input[i] || "";
|
|
157
163
|
const prev = i > 0 ? input[i - 1] : "";
|
|
158
|
-
|
|
159
164
|
if ((ch === '"' || ch === "'") && prev !== "\\") {
|
|
160
|
-
if (!inQuotes) {
|
|
161
|
-
|
|
162
|
-
quoteChar = ch;
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (ch === quoteChar) {
|
|
166
|
-
inQuotes = false;
|
|
167
|
-
quoteChar = "";
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
165
|
+
if (!inQuotes) { inQuotes = true; quoteChar = ch; continue; }
|
|
166
|
+
if (ch === quoteChar) { inQuotes = false; quoteChar = ""; continue; }
|
|
170
167
|
}
|
|
171
|
-
|
|
172
168
|
if (/\s/.test(ch) && !inQuotes) {
|
|
173
|
-
if (current.length > 0) {
|
|
174
|
-
tokens.push(current);
|
|
175
|
-
current = "";
|
|
176
|
-
}
|
|
169
|
+
if (current.length > 0) { tokens.push(current); current = ""; }
|
|
177
170
|
continue;
|
|
178
171
|
}
|
|
179
|
-
|
|
180
172
|
current += ch;
|
|
181
173
|
}
|
|
182
|
-
|
|
183
174
|
if (current.length > 0) tokens.push(current);
|
|
184
175
|
return tokens;
|
|
185
176
|
}
|
|
186
177
|
|
|
187
178
|
function parseInput(rawInput: string): { commandName: string; args: string[] } {
|
|
188
179
|
const parts = splitArgsRespectingQuotes(rawInput.trim());
|
|
180
|
+
return { commandName: parts[0]?.toLowerCase() ?? "", args: parts.slice(1) };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function makeDefaultEnv(authUser: string, hostname: string): ShellEnv {
|
|
189
184
|
return {
|
|
190
|
-
|
|
191
|
-
|
|
185
|
+
vars: {
|
|
186
|
+
PATH: "/usr/local/bin:/usr/bin:/bin",
|
|
187
|
+
HOME: `/home/${authUser}`,
|
|
188
|
+
USER: authUser,
|
|
189
|
+
LOGNAME: authUser,
|
|
190
|
+
SHELL: "/bin/sh",
|
|
191
|
+
TERM: "xterm-256color",
|
|
192
|
+
HOSTNAME: hostname,
|
|
193
|
+
PS1: "\\u@\\h:\\w\\$ ",
|
|
194
|
+
},
|
|
195
|
+
lastExitCode: 0,
|
|
192
196
|
};
|
|
193
197
|
}
|
|
194
198
|
|
|
@@ -200,42 +204,37 @@ export async function runCommand(
|
|
|
200
204
|
cwd: string,
|
|
201
205
|
shell: VirtualShell,
|
|
202
206
|
stdin?: string,
|
|
207
|
+
env?: ShellEnv,
|
|
203
208
|
): Promise<CommandResult> {
|
|
204
209
|
const trimmed = rawInput.trim();
|
|
205
|
-
|
|
206
210
|
if (trimmed.length === 0) return { exitCode: 0 };
|
|
207
211
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
const shellEnv: ShellEnv = env ?? makeDefaultEnv(authUser, hostname);
|
|
213
|
+
|
|
214
|
+
// Detect shell operators
|
|
215
|
+
if (
|
|
216
|
+
/(?<![|&])[|](?![|])/.test(trimmed) ||
|
|
217
|
+
trimmed.includes(">") ||
|
|
218
|
+
trimmed.includes("<") ||
|
|
219
|
+
trimmed.includes("&&") ||
|
|
220
|
+
trimmed.includes("||") ||
|
|
221
|
+
trimmed.includes(";")
|
|
222
|
+
) {
|
|
223
|
+
const { parseScript } = await import("../VirtualShell/shellParser");
|
|
224
|
+
const { executeStatements } = await import("../SSHMimic/executor");
|
|
225
|
+
const script = parseScript(trimmed);
|
|
226
|
+
if (!script.isValid) return { stderr: script.error || "Syntax error", exitCode: 1 };
|
|
217
227
|
try {
|
|
218
|
-
return await
|
|
219
|
-
pipeline,
|
|
220
|
-
authUser,
|
|
221
|
-
hostname,
|
|
222
|
-
mode,
|
|
223
|
-
cwd,
|
|
224
|
-
shell,
|
|
225
|
-
);
|
|
228
|
+
return await executeStatements(script.statements, authUser, hostname, mode, cwd, shell, shellEnv);
|
|
226
229
|
} catch (error: unknown) {
|
|
227
|
-
|
|
228
|
-
error instanceof Error ? error.message : "Pipeline execution failed";
|
|
229
|
-
return { stderr: message, exitCode: 1 };
|
|
230
|
+
return { stderr: error instanceof Error ? error.message : "Execution failed", exitCode: 1 };
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
const { commandName, args } = parseInput(trimmed);
|
|
234
235
|
const mod = resolveModule(commandName);
|
|
235
236
|
|
|
236
|
-
if (!mod) {
|
|
237
|
-
return { stderr: `Command '${trimmed}' not found`, exitCode: 127 };
|
|
238
|
-
}
|
|
237
|
+
if (!mod) return { stderr: `${commandName}: command not found`, exitCode: 127 };
|
|
239
238
|
|
|
240
239
|
try {
|
|
241
240
|
return await mod.run({
|
|
@@ -248,9 +247,9 @@ export async function runCommand(
|
|
|
248
247
|
stdin,
|
|
249
248
|
cwd,
|
|
250
249
|
shell,
|
|
250
|
+
env: shellEnv,
|
|
251
251
|
});
|
|
252
252
|
} catch (error: unknown) {
|
|
253
|
-
|
|
254
|
-
return { stderr: message, exitCode: 1 };
|
|
253
|
+
return { stderr: error instanceof Error ? error.message : "Command failed", exitCode: 1 };
|
|
255
254
|
}
|
|
256
255
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ShellModule } from "../types/commands";
|
|
2
|
+
|
|
3
|
+
export const killCommand: ShellModule = {
|
|
4
|
+
name: "kill",
|
|
5
|
+
description: "Send signal to process",
|
|
6
|
+
category: "system",
|
|
7
|
+
params: ["[-9] <pid>"],
|
|
8
|
+
run: ({ args }) => {
|
|
9
|
+
const pid = args.find((a) => !a.startsWith("-"));
|
|
10
|
+
if (!pid) return { stderr: "kill: no pid specified", exitCode: 1 };
|
|
11
|
+
// In virtual env, we just acknowledge the kill
|
|
12
|
+
return { stdout: ``, exitCode: 0 };
|
|
13
|
+
},
|
|
14
|
+
};
|
package/src/commands/ln.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { assertPathAccess, resolvePath } from "./helpers";
|
|
|
4
4
|
|
|
5
5
|
export const lnCommand: ShellModule = {
|
|
6
6
|
name: "ln",
|
|
7
|
+
description: "Create links",
|
|
8
|
+
category: "files",
|
|
7
9
|
params: ["[-s] <target> <link_name>"],
|
|
8
10
|
run: ({ authUser, shell, cwd, args }) => {
|
|
9
11
|
const symbolic = ifFlag(args, ["-s", "--symbolic"]);
|
package/src/commands/ls.ts
CHANGED
|
@@ -28,6 +28,8 @@ function formatDate(date: Date): string {
|
|
|
28
28
|
|
|
29
29
|
export const lsCommand: ShellModule = {
|
|
30
30
|
name: "ls",
|
|
31
|
+
description: "List directory contents",
|
|
32
|
+
category: "navigation",
|
|
31
33
|
params: ["[path]"],
|
|
32
34
|
run: ({ authUser, shell, cwd, args }) => {
|
|
33
35
|
const longFormat = ifFlag(args, ["-l", "--long"]);
|
package/src/commands/mkdir.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { assertPathAccess, resolvePath } from "./helpers";
|
|
|
4
4
|
|
|
5
5
|
export const mkdirCommand: ShellModule = {
|
|
6
6
|
name: "mkdir",
|
|
7
|
+
description: "Make directories",
|
|
8
|
+
category: "files",
|
|
7
9
|
params: ["<dir>"],
|
|
8
10
|
run: ({ authUser, shell, cwd, args }) => {
|
|
9
11
|
if (args.length === 0) {
|
package/src/commands/mv.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { assertPathAccess, resolvePath } from "./helpers";
|
|
|
3
3
|
|
|
4
4
|
export const mvCommand: ShellModule = {
|
|
5
5
|
name: "mv",
|
|
6
|
+
description: "Move or rename files",
|
|
7
|
+
category: "files",
|
|
6
8
|
params: ["<source> <dest>"],
|
|
7
9
|
run: ({ authUser, shell, cwd, args }) => {
|
|
8
10
|
const positionals = args.filter((a) => !a.startsWith("-"));
|
package/src/commands/nano.ts
CHANGED
package/src/commands/neofetch.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { getAllEnvVars } from "./set";
|
|
|
5
5
|
|
|
6
6
|
export const neofetchCommand: ShellModule = {
|
|
7
7
|
name: "neofetch",
|
|
8
|
+
description: "System info display",
|
|
9
|
+
category: "misc",
|
|
8
10
|
params: ["[--off]"],
|
|
9
11
|
run: ({ args, authUser, hostname, shell }) => {
|
|
10
12
|
const env = getAllEnvVars(authUser);
|
package/src/commands/passwd.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
|
|
3
3
|
export const passwdCommand: ShellModule = {
|
|
4
4
|
name: "passwd",
|
|
5
|
+
description: "Change user password",
|
|
6
|
+
category: "users",
|
|
5
7
|
params: ["<username> <password>"],
|
|
6
8
|
run: async ({ authUser, args, shell }) => {
|
|
7
9
|
const [username, password] = args;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ShellModule } from "../types/commands";
|
|
2
|
+
|
|
3
|
+
export const pingCommand: ShellModule = {
|
|
4
|
+
name: "ping",
|
|
5
|
+
description: "Send ICMP ECHO_REQUEST (mock)",
|
|
6
|
+
category: "network",
|
|
7
|
+
params: ["[-c <count>] <host>"],
|
|
8
|
+
run: ({ args }) => {
|
|
9
|
+
const host = args.find((a) => !a.startsWith("-")) ?? "localhost";
|
|
10
|
+
const count = 4;
|
|
11
|
+
const lines = [`PING ${host}: 56 data bytes`];
|
|
12
|
+
for (let i = 0; i < count; i++) {
|
|
13
|
+
const ms = (Math.random() * 10 + 1).toFixed(3);
|
|
14
|
+
lines.push(`64 bytes from ${host}: icmp_seq=${i} ttl=64 time=${ms} ms`);
|
|
15
|
+
}
|
|
16
|
+
lines.push(`--- ${host} ping statistics ---`);
|
|
17
|
+
lines.push(`${count} packets transmitted, ${count} received, 0% packet loss`);
|
|
18
|
+
return { stdout: lines.join("\n"), exitCode: 0 };
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ShellModule } from "../types/commands";
|
|
2
|
+
|
|
3
|
+
export const psCommand: ShellModule = {
|
|
4
|
+
name: "ps",
|
|
5
|
+
description: "Report process status",
|
|
6
|
+
category: "system",
|
|
7
|
+
params: ["[-a] [-u] [-x]"],
|
|
8
|
+
run: ({ authUser, shell }) => {
|
|
9
|
+
const sessions = shell.users.listActiveSessions();
|
|
10
|
+
const lines = [" PID TTY TIME CMD"];
|
|
11
|
+
let pid = 1000;
|
|
12
|
+
for (const s of sessions) {
|
|
13
|
+
lines.push(`${String(pid).padStart(5)} ${s.tty.padEnd(12)} 00:00:00 ${s.username === authUser ? "bash" : `bash (${s.username})`}`);
|
|
14
|
+
pid++;
|
|
15
|
+
}
|
|
16
|
+
lines.push(`${String(pid).padStart(5)} pts/0 00:00:00 ps`);
|
|
17
|
+
return { stdout: lines.join("\n"), exitCode: 0 };
|
|
18
|
+
},
|
|
19
|
+
};
|
package/src/commands/pwd.ts
CHANGED
package/src/commands/rm.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { assertPathAccess, resolvePath } from "./helpers";
|
|
|
4
4
|
|
|
5
5
|
export const rmCommand: ShellModule = {
|
|
6
6
|
name: "rm",
|
|
7
|
+
description: "Remove files or directories",
|
|
8
|
+
category: "files",
|
|
7
9
|
params: ["[-r|-rf] <path>"],
|
|
8
10
|
run: ({ authUser, shell, cwd, args }) => {
|
|
9
11
|
if (args.length === 0) {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ShellModule } from "../types/commands";
|
|
2
|
+
import { getFlag, ifFlag } from "./command-helpers";
|
|
3
|
+
import { resolvePath } from "./helpers";
|
|
4
|
+
|
|
5
|
+
export const sedCommand: ShellModule = {
|
|
6
|
+
name: "sed",
|
|
7
|
+
description: "Stream editor for filtering and transforming text",
|
|
8
|
+
category: "text",
|
|
9
|
+
params: ["-e <expr> [file]", "s/pattern/replace/[g]"],
|
|
10
|
+
run: ({ authUser, shell, cwd, args, stdin }) => {
|
|
11
|
+
const inPlace = ifFlag(args, ["-i"]);
|
|
12
|
+
const expr = (getFlag(args, ["-e"]) as string | undefined) ?? args.find((a) => !a.startsWith("-"));
|
|
13
|
+
const fileArg = args.filter((a) => !a.startsWith("-") && a !== expr).pop();
|
|
14
|
+
|
|
15
|
+
if (!expr) return { stderr: "sed: no expression", exitCode: 1 };
|
|
16
|
+
|
|
17
|
+
let content = stdin ?? "";
|
|
18
|
+
if (fileArg) {
|
|
19
|
+
const p = resolvePath(cwd, fileArg);
|
|
20
|
+
try { content = shell.vfs.readFile(p); } catch { return { stderr: `sed: ${fileArg}: No such file or directory`, exitCode: 1 }; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Parse s/from/to/[g]
|
|
24
|
+
const sMatch = expr.match(/^s([^a-zA-Z0-9])(.+?)\1(.*?)\1([gi]*)$/);
|
|
25
|
+
if (!sMatch) return { stderr: `sed: unrecognized command: ${expr}`, exitCode: 1 };
|
|
26
|
+
|
|
27
|
+
const [, , from, to, flags] = sMatch;
|
|
28
|
+
const regexFlags = (flags ?? "").includes("i") ? "gi" : (flags ?? "").includes("g") ? "g" : "";
|
|
29
|
+
let regex: RegExp;
|
|
30
|
+
try { regex = new RegExp(from!, regexFlags || ""); }
|
|
31
|
+
catch (_e) { return { stderr: `sed: invalid regex: ${from}`, exitCode: 1 }; }
|
|
32
|
+
|
|
33
|
+
const result = (flags ?? "").includes("g") || regexFlags.includes("g")
|
|
34
|
+
? content.replace(regex, to ?? "")
|
|
35
|
+
: content.replace(regex, to ?? "");
|
|
36
|
+
|
|
37
|
+
if (inPlace && fileArg) {
|
|
38
|
+
const p = resolvePath(cwd, fileArg);
|
|
39
|
+
shell.writeFileAsUser(authUser, p, result);
|
|
40
|
+
return { exitCode: 0 };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { stdout: result, exitCode: 0 };
|
|
44
|
+
},
|
|
45
|
+
};
|
package/src/commands/set.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/** biome-ignore-all lint/style/useNamingConvention: env variables */
|
|
2
2
|
import type { ShellModule } from "../types/commands";
|
|
3
|
-
import { getArg } from "./command-helpers";
|
|
4
3
|
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
const envVars: Record<string, string> = {
|
|
4
|
+
// Legacy global store kept for compatibility with older callers
|
|
5
|
+
const _globalEnv: Record<string, string> = {
|
|
8
6
|
PATH: "/usr/local/bin:/usr/bin:/bin",
|
|
9
7
|
HOME: "/home/user",
|
|
10
8
|
SHELL: "/bin/sh",
|
|
@@ -12,62 +10,33 @@ const envVars: Record<string, string> = {
|
|
|
12
10
|
USER: "user",
|
|
13
11
|
};
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
envVars[name] = value;
|
|
21
|
-
}
|
|
22
|
-
|
|
13
|
+
/** @deprecated use env.vars from CommandContext */
|
|
14
|
+
export function getEnvVar(name: string): string | undefined { return _globalEnv[name]; }
|
|
15
|
+
/** @deprecated use env.vars from CommandContext */
|
|
16
|
+
export function setEnvVar(name: string, value: string): void { _globalEnv[name] = value; }
|
|
17
|
+
/** @deprecated use env.vars from CommandContext */
|
|
23
18
|
export function getAllEnvVars(authUser: string): Record<string, string> {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return { ...
|
|
19
|
+
_globalEnv.USER = authUser;
|
|
20
|
+
_globalEnv.HOME = `/home/${authUser}`;
|
|
21
|
+
return { ..._globalEnv };
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
export const setCommand: ShellModule = {
|
|
30
25
|
name: "set",
|
|
26
|
+
description: "Display or set shell variables",
|
|
27
|
+
category: "shell",
|
|
31
28
|
params: ["[VAR=value]"],
|
|
32
|
-
run: ({ args }) => {
|
|
33
|
-
// No arguments: display all environment variables
|
|
29
|
+
run: ({ args, env }) => {
|
|
34
30
|
if (args.length === 0) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
.sort()
|
|
38
|
-
.join("\n");
|
|
39
|
-
|
|
40
|
-
return { stdout: output, exitCode: 0 };
|
|
31
|
+
const out = Object.entries(env.vars).map(([k, v]) => `${k}=${v}`).join("\n");
|
|
32
|
+
return { stdout: out, exitCode: 0 };
|
|
41
33
|
}
|
|
42
|
-
|
|
43
|
-
// Parse VAR=value format
|
|
44
|
-
const assignments: string[] = [];
|
|
45
|
-
for (let index = 0; ; index += 1) {
|
|
46
|
-
const arg = getArg(args, index);
|
|
47
|
-
if (!arg) {
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
|
|
34
|
+
for (const arg of args) {
|
|
51
35
|
if (arg.includes("=")) {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
setEnvVar(varName, varValue);
|
|
55
|
-
assignments.push(arg);
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
// If no '=' present, display that specific variable
|
|
59
|
-
const value = getEnvVar(arg);
|
|
60
|
-
if (value !== undefined) {
|
|
61
|
-
assignments.push(`${arg}=${value}`);
|
|
62
|
-
} else {
|
|
63
|
-
assignments.push(`${arg}: not set`);
|
|
64
|
-
}
|
|
36
|
+
const eq = arg.indexOf("=");
|
|
37
|
+
env.vars[arg.slice(0, eq)] = arg.slice(eq + 1);
|
|
65
38
|
}
|
|
66
39
|
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
stdout: assignments.length > 0 ? assignments.join("\n") : "",
|
|
70
|
-
exitCode: 0,
|
|
71
|
-
};
|
|
40
|
+
return { exitCode: 0 };
|
|
72
41
|
},
|
|
73
42
|
};
|