typescript-virtual-container 1.3.3 → 1.4.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/.vscode/settings.json +0 -1
- package/README.md +674 -1504
- package/benchmark-results.txt +21 -21
- package/builds/self-standalone.js +274 -208
- package/builds/self-standalone.js.map +4 -4
- package/builds/standalone-wo-sftp.js +201 -149
- package/builds/standalone-wo-sftp.js.map +4 -4
- package/builds/standalone.js +263 -211
- package/builds/standalone.js.map +4 -4
- package/builds/web-full-api.min.js +3 -3
- package/builds/web-full-api.min.js.map +4 -4
- package/builds/web.min.js +2 -2
- package/builds/web.min.js.map +4 -4
- package/bun.lock +14 -12
- package/dist/SSHClient/index.d.ts.map +1 -1
- package/dist/SSHClient/index.js +5 -3
- package/dist/SSHMimic/executor.d.ts +1 -3
- package/dist/SSHMimic/executor.d.ts.map +1 -1
- package/dist/SSHMimic/executor.js +20 -22
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +5 -3
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +26 -21
- package/dist/VirtualShell/shell.d.ts.map +1 -1
- package/dist/VirtualShell/shell.js +25 -3
- package/dist/VirtualShell/shellParser.d.ts +1 -8
- package/dist/VirtualShell/shellParser.d.ts.map +1 -1
- package/dist/VirtualShell/shellParser.js +2 -81
- package/dist/VirtualUserManager/index.d.ts +7 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +47 -19
- package/dist/commands/adduser.d.ts +10 -4
- package/dist/commands/adduser.d.ts.map +1 -1
- package/dist/commands/adduser.js +75 -12
- package/dist/commands/alias.d.ts +5 -0
- package/dist/commands/alias.d.ts.map +1 -1
- package/dist/commands/alias.js +5 -0
- package/dist/commands/apt.d.ts +5 -0
- package/dist/commands/apt.d.ts.map +1 -1
- package/dist/commands/apt.js +5 -0
- package/dist/commands/awk.d.ts +10 -8
- package/dist/commands/awk.d.ts.map +1 -1
- package/dist/commands/awk.js +156 -28
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +0 -3
- package/dist/commands/clear.d.ts +5 -0
- package/dist/commands/clear.d.ts.map +1 -1
- package/dist/commands/clear.js +5 -0
- package/dist/commands/command-helpers.d.ts.map +1 -1
- package/dist/commands/command-helpers.js +8 -0
- package/dist/commands/declare.d.ts +5 -0
- package/dist/commands/declare.d.ts.map +1 -1
- package/dist/commands/declare.js +5 -0
- package/dist/commands/deluser.d.ts +12 -0
- package/dist/commands/deluser.d.ts.map +1 -1
- package/dist/commands/deluser.js +72 -6
- package/dist/commands/df.d.ts +5 -0
- package/dist/commands/df.d.ts.map +1 -1
- package/dist/commands/df.js +5 -0
- package/dist/commands/du.d.ts +5 -0
- package/dist/commands/du.d.ts.map +1 -1
- package/dist/commands/du.js +5 -0
- package/dist/commands/export.d.ts +5 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +5 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +22 -4
- package/dist/commands/groups.d.ts +5 -0
- package/dist/commands/groups.d.ts.map +1 -1
- package/dist/commands/groups.js +5 -0
- package/dist/commands/gzip.d.ts +5 -2
- package/dist/commands/gzip.d.ts.map +1 -1
- package/dist/commands/gzip.js +48 -28
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +12 -3
- package/dist/commands/htop.d.ts +5 -0
- package/dist/commands/htop.d.ts.map +1 -1
- package/dist/commands/htop.js +5 -0
- package/dist/commands/kill.d.ts +5 -0
- package/dist/commands/kill.d.ts.map +1 -1
- package/dist/commands/kill.js +5 -0
- package/dist/commands/ln.d.ts +2 -0
- package/dist/commands/ln.d.ts.map +1 -1
- package/dist/commands/ln.js +22 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +15 -0
- package/dist/commands/lsb-release.d.ts +5 -0
- package/dist/commands/lsb-release.d.ts.map +1 -1
- package/dist/commands/lsb-release.js +5 -0
- package/dist/commands/mkdir.d.ts +5 -0
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +5 -0
- package/dist/commands/mv.d.ts +5 -0
- package/dist/commands/mv.d.ts.map +1 -1
- package/dist/commands/mv.js +5 -0
- package/dist/commands/nano.d.ts +5 -0
- package/dist/commands/nano.d.ts.map +1 -1
- package/dist/commands/nano.js +5 -0
- package/dist/commands/neofetch.d.ts +5 -0
- package/dist/commands/neofetch.d.ts.map +1 -1
- package/dist/commands/neofetch.js +8 -5
- package/dist/commands/passwd.d.ts +8 -0
- package/dist/commands/passwd.d.ts.map +1 -1
- package/dist/commands/passwd.js +32 -11
- package/dist/commands/ping.d.ts +5 -0
- package/dist/commands/ping.d.ts.map +1 -1
- package/dist/commands/ping.js +5 -0
- package/dist/commands/printf.d.ts +5 -0
- package/dist/commands/printf.d.ts.map +1 -1
- package/dist/commands/printf.js +43 -12
- package/dist/commands/ps.d.ts +5 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +5 -0
- package/dist/commands/read.d.ts +5 -0
- package/dist/commands/read.d.ts.map +1 -1
- package/dist/commands/read.js +5 -0
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +4 -1
- package/dist/commands/rm.d.ts +5 -0
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +5 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +1 -57
- package/dist/commands/sed.d.ts +5 -0
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +5 -0
- package/dist/commands/set.d.ts +5 -6
- package/dist/commands/set.d.ts.map +1 -1
- package/dist/commands/set.js +5 -22
- package/dist/commands/sh.d.ts +6 -0
- package/dist/commands/sh.d.ts.map +1 -1
- package/dist/commands/sh.js +6 -0
- package/dist/commands/shift.d.ts +10 -0
- package/dist/commands/shift.d.ts.map +1 -1
- package/dist/commands/shift.js +10 -0
- package/dist/commands/sleep.d.ts +5 -0
- package/dist/commands/sleep.d.ts.map +1 -1
- package/dist/commands/sleep.js +5 -0
- package/dist/commands/sort.d.ts +5 -0
- package/dist/commands/sort.d.ts.map +1 -1
- package/dist/commands/sort.js +5 -0
- package/dist/commands/source.d.ts +5 -0
- package/dist/commands/source.d.ts.map +1 -1
- package/dist/commands/source.js +5 -0
- package/dist/commands/stat.d.ts +7 -0
- package/dist/commands/stat.d.ts.map +1 -0
- package/dist/commands/stat.js +56 -0
- package/dist/commands/su.d.ts +13 -0
- package/dist/commands/su.d.ts.map +1 -1
- package/dist/commands/su.js +45 -14
- package/dist/commands/sudo.d.ts.map +1 -1
- package/dist/commands/sudo.js +5 -0
- package/dist/commands/tail.d.ts +5 -0
- package/dist/commands/tail.d.ts.map +1 -1
- package/dist/commands/tail.js +15 -3
- package/dist/commands/tar.d.ts +5 -0
- package/dist/commands/tar.d.ts.map +1 -1
- package/dist/commands/tar.js +40 -10
- package/dist/commands/tee.d.ts +5 -0
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +5 -0
- package/dist/commands/touch.d.ts +5 -0
- package/dist/commands/touch.d.ts.map +1 -1
- package/dist/commands/touch.js +5 -0
- package/dist/commands/tr.d.ts.map +1 -1
- package/dist/commands/tr.js +45 -10
- package/dist/commands/tree.d.ts +5 -0
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +5 -0
- package/dist/commands/true.d.ts +10 -0
- package/dist/commands/true.d.ts.map +1 -1
- package/dist/commands/true.js +10 -0
- package/dist/commands/type.d.ts +5 -0
- package/dist/commands/type.d.ts.map +1 -1
- package/dist/commands/type.js +5 -0
- package/dist/commands/uname.d.ts +5 -0
- package/dist/commands/uname.d.ts.map +1 -1
- package/dist/commands/uname.js +5 -0
- package/dist/commands/uniq.d.ts +5 -0
- package/dist/commands/uniq.d.ts.map +1 -1
- package/dist/commands/uniq.js +5 -0
- package/dist/commands/unset.d.ts +5 -0
- package/dist/commands/unset.d.ts.map +1 -1
- package/dist/commands/unset.js +5 -0
- package/dist/commands/uptime.d.ts +5 -0
- package/dist/commands/uptime.d.ts.map +1 -1
- package/dist/commands/uptime.js +5 -0
- package/dist/commands/wc.d.ts +5 -0
- package/dist/commands/wc.d.ts.map +1 -1
- package/dist/commands/wc.js +5 -0
- package/dist/commands/wget.d.ts +5 -0
- package/dist/commands/wget.d.ts.map +1 -1
- package/dist/commands/wget.js +5 -0
- package/dist/commands/who.d.ts +5 -0
- package/dist/commands/who.d.ts.map +1 -1
- package/dist/commands/who.js +5 -0
- package/dist/commands/whoami.d.ts +5 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +5 -0
- package/dist/commands/xargs.d.ts +5 -0
- package/dist/commands/xargs.d.ts.map +1 -1
- package/dist/commands/xargs.js +5 -0
- package/dist/self-standalone.js +254 -30
- package/dist/types/commands.d.ts +36 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/utils/tokenize.d.ts +20 -0
- package/dist/utils/tokenize.d.ts.map +1 -0
- package/dist/utils/tokenize.js +74 -0
- package/examples/web.min.js +2 -2
- package/package.json +1 -1
- package/src/SSHClient/index.ts +6 -3
- package/src/SSHMimic/executor.ts +21 -44
- package/src/SSHMimic/index.ts +7 -5
- package/src/SSHMimic/sftp.ts +28 -21
- package/src/VirtualShell/shell.ts +34 -4
- package/src/VirtualShell/shellParser.ts +2 -103
- package/src/VirtualUserManager/index.ts +44 -20
- package/src/commands/adduser.ts +86 -13
- package/src/commands/alias.ts +5 -0
- package/src/commands/apt.ts +5 -0
- package/src/commands/awk.ts +154 -29
- package/src/commands/cd.ts +0 -4
- package/src/commands/clear.ts +5 -0
- package/src/commands/command-helpers.ts +9 -0
- package/src/commands/declare.ts +5 -0
- package/src/commands/deluser.ts +84 -7
- package/src/commands/df.ts +5 -0
- package/src/commands/du.ts +5 -0
- package/src/commands/export.ts +5 -0
- package/src/commands/grep.ts +21 -8
- package/src/commands/groups.ts +5 -0
- package/src/commands/gzip.ts +54 -28
- package/src/commands/head.ts +14 -4
- package/src/commands/htop.ts +5 -0
- package/src/commands/kill.ts +5 -0
- package/src/commands/ln.ts +22 -0
- package/src/commands/ls.ts +17 -0
- package/src/commands/lsb-release.ts +5 -0
- package/src/commands/mkdir.ts +5 -0
- package/src/commands/mv.ts +5 -0
- package/src/commands/nano.ts +5 -0
- package/src/commands/neofetch.ts +8 -6
- package/src/commands/passwd.ts +35 -12
- package/src/commands/ping.ts +5 -0
- package/src/commands/printf.ts +30 -13
- package/src/commands/ps.ts +5 -0
- package/src/commands/read.ts +5 -0
- package/src/commands/registry.ts +4 -1
- package/src/commands/rm.ts +5 -0
- package/src/commands/runtime.ts +1 -61
- package/src/commands/sed.ts +5 -0
- package/src/commands/set.ts +5 -24
- package/src/commands/sh.ts +9 -3
- package/src/commands/shift.ts +10 -0
- package/src/commands/sleep.ts +5 -0
- package/src/commands/sort.ts +5 -0
- package/src/commands/source.ts +5 -0
- package/src/commands/stat.ts +61 -0
- package/src/commands/su.ts +54 -16
- package/src/commands/sudo.ts +5 -0
- package/src/commands/tail.ts +17 -3
- package/src/commands/tar.ts +38 -15
- package/src/commands/tee.ts +5 -0
- package/src/commands/touch.ts +5 -0
- package/src/commands/tr.ts +54 -10
- package/src/commands/tree.ts +5 -0
- package/src/commands/true.ts +10 -0
- package/src/commands/type.ts +5 -0
- package/src/commands/uname.ts +5 -0
- package/src/commands/uniq.ts +5 -0
- package/src/commands/unset.ts +5 -0
- package/src/commands/uptime.ts +5 -0
- package/src/commands/wc.ts +5 -0
- package/src/commands/wget.ts +5 -0
- package/src/commands/who.ts +5 -0
- package/src/commands/whoami.ts +5 -0
- package/src/commands/xargs.ts +5 -0
- package/src/self-standalone.ts +316 -33
- package/src/types/commands.ts +37 -0
- package/src/utils/tokenize.ts +78 -0
- package/builds/web-iife.min.js +0 -13
- package/builds/web-iife.min.js.map +0 -7
package/src/commands/gzip.ts
CHANGED
|
@@ -2,54 +2,80 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { resolvePath } from "./helpers";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Compress files using gzip
|
|
5
|
+
* Compress files using gzip — renames file to `<file>.gz`, removes original.
|
|
6
6
|
* @category archive
|
|
7
|
-
* @params ["<file>"]
|
|
8
7
|
*/
|
|
9
8
|
export const gzipCommand: ShellModule = {
|
|
10
9
|
name: "gzip",
|
|
11
10
|
description: "Compress files",
|
|
12
11
|
category: "archive",
|
|
13
|
-
params: ["<file>"],
|
|
12
|
+
params: ["[-k] [-d] <file>"],
|
|
14
13
|
run: ({ shell, cwd, args }) => {
|
|
15
|
-
const
|
|
16
|
-
|
|
14
|
+
const keepOrig = args.includes("-k") || args.includes("--keep");
|
|
15
|
+
const decompress = args.includes("-d");
|
|
16
|
+
const file = args.find((a) => !a.startsWith("-"));
|
|
17
|
+
if (!file) return { stderr: "gzip: no file specified\n", exitCode: 1 };
|
|
18
|
+
|
|
17
19
|
const p = resolvePath(cwd, file);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
|
|
21
|
+
if (decompress) {
|
|
22
|
+
// gzip -d = gunzip
|
|
23
|
+
if (!file.endsWith(".gz")) {
|
|
24
|
+
return { stderr: `gzip: ${file}: unknown suffix -- ignored\n`, exitCode: 1 };
|
|
25
|
+
}
|
|
26
|
+
if (!shell.vfs.exists(p)) {
|
|
27
|
+
return { stderr: `gzip: ${file}: No such file or directory\n`, exitCode: 1 };
|
|
28
|
+
}
|
|
29
|
+
const content = shell.vfs.readFile(p);
|
|
30
|
+
const dest = p.slice(0, -3);
|
|
31
|
+
shell.vfs.writeFile(dest, content);
|
|
32
|
+
if (!keepOrig) shell.vfs.remove(p);
|
|
20
33
|
return { exitCode: 0 };
|
|
21
|
-
} catch {
|
|
22
|
-
return {
|
|
23
|
-
stderr: `gzip: ${file}: No such file or directory`,
|
|
24
|
-
exitCode: 1,
|
|
25
|
-
};
|
|
26
34
|
}
|
|
35
|
+
|
|
36
|
+
if (!shell.vfs.exists(p)) {
|
|
37
|
+
return { stderr: `gzip: ${file}: No such file or directory\n`, exitCode: 1 };
|
|
38
|
+
}
|
|
39
|
+
if (file.endsWith(".gz")) {
|
|
40
|
+
return { stderr: `gzip: ${file}: already has .gz suffix -- unchanged\n`, exitCode: 1 };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const rawContent = shell.vfs.readFileRaw(p);
|
|
44
|
+
const gzPath = `${p}.gz`;
|
|
45
|
+
shell.vfs.writeFile(gzPath, rawContent, { compress: true });
|
|
46
|
+
if (!keepOrig) shell.vfs.remove(p);
|
|
47
|
+
return { exitCode: 0 };
|
|
27
48
|
},
|
|
28
49
|
};
|
|
29
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Decompress gzip files — renames `<file>.gz` to `<file>`, removes original.
|
|
53
|
+
* @category archive
|
|
54
|
+
*/
|
|
30
55
|
export const gunzipCommand: ShellModule = {
|
|
31
|
-
/**
|
|
32
|
-
* Decompress gzip files (or zcat alias).
|
|
33
|
-
* @category archive
|
|
34
|
-
* @params ["<file>"]
|
|
35
|
-
*/
|
|
36
56
|
name: "gunzip",
|
|
37
57
|
description: "Decompress files",
|
|
38
58
|
category: "archive",
|
|
39
|
-
params: ["<file>"],
|
|
40
59
|
aliases: ["zcat"],
|
|
60
|
+
params: ["[-k] <file>"],
|
|
41
61
|
run: ({ shell, cwd, args }) => {
|
|
42
|
-
const
|
|
43
|
-
|
|
62
|
+
const keepOrig = args.includes("-k") || args.includes("--keep");
|
|
63
|
+
const file = args.find((a) => !a.startsWith("-"));
|
|
64
|
+
if (!file) return { stderr: "gunzip: no file specified\n", exitCode: 1 };
|
|
65
|
+
|
|
44
66
|
const p = resolvePath(cwd, file);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return { exitCode:
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
exitCode: 1,
|
|
52
|
-
};
|
|
67
|
+
|
|
68
|
+
if (!shell.vfs.exists(p)) {
|
|
69
|
+
return { stderr: `gunzip: ${file}: No such file or directory\n`, exitCode: 1 };
|
|
70
|
+
}
|
|
71
|
+
if (!file.endsWith(".gz")) {
|
|
72
|
+
return { stderr: `gunzip: ${file}: unknown suffix -- ignored\n`, exitCode: 1 };
|
|
53
73
|
}
|
|
74
|
+
|
|
75
|
+
const content = shell.vfs.readFile(p);
|
|
76
|
+
const dest = p.slice(0, -3);
|
|
77
|
+
shell.vfs.writeFile(dest, content);
|
|
78
|
+
if (!keepOrig) shell.vfs.remove(p);
|
|
79
|
+
return { exitCode: 0 };
|
|
54
80
|
},
|
|
55
81
|
};
|
package/src/commands/head.ts
CHANGED
|
@@ -14,11 +14,21 @@ export const headCommand: ShellModule = {
|
|
|
14
14
|
params: ["[-n <lines>] [file...]"],
|
|
15
15
|
run: ({ authUser, shell, cwd, args, stdin }) => {
|
|
16
16
|
const nArg = getFlag(args, ["-n"]);
|
|
17
|
-
|
|
18
|
-
const
|
|
17
|
+
// Support both -n N and -N shorthand (head -2, head -10)
|
|
18
|
+
const shortN = args.find((a) => /^-\d+$/.test(a));
|
|
19
|
+
const n = typeof nArg === "string"
|
|
20
|
+
? parseInt(nArg, 10)
|
|
21
|
+
: shortN ? parseInt(shortN.slice(1), 10) : 10;
|
|
22
|
+
const positionals = args.filter(
|
|
23
|
+
(a) => !a.startsWith("-") && a !== nArg && a !== String(n),
|
|
24
|
+
);
|
|
19
25
|
|
|
20
|
-
const take = (content: string) =>
|
|
21
|
-
content.split("\n")
|
|
26
|
+
const take = (content: string) => {
|
|
27
|
+
const lines = content.split("\n");
|
|
28
|
+
// Preserve trailing newline
|
|
29
|
+
const sliced = lines.slice(0, n);
|
|
30
|
+
return sliced.join("\n") + (content.endsWith("\n") && sliced.length === lines.slice(0, n).length ? "\n" : "");
|
|
31
|
+
};
|
|
22
32
|
|
|
23
33
|
if (positionals.length === 0) {
|
|
24
34
|
return { stdout: take(stdin ?? ""), exitCode: 0 };
|
package/src/commands/htop.ts
CHANGED
package/src/commands/kill.ts
CHANGED
package/src/commands/ln.ts
CHANGED
|
@@ -47,3 +47,25 @@ export const lnCommand: ShellModule = {
|
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
|
+
|
|
51
|
+
/** Shell command: print the value of a symbolic link. */
|
|
52
|
+
export const readlinkCommand: ShellModule = {
|
|
53
|
+
name: "readlink",
|
|
54
|
+
description: "Print resolved path of symbolic link",
|
|
55
|
+
category: "files",
|
|
56
|
+
params: ["[-f] <path>"],
|
|
57
|
+
run: ({ shell, cwd, args }) => {
|
|
58
|
+
const follow = args.includes("-f") || args.includes("-e");
|
|
59
|
+
const target = args.find((a) => !a.startsWith("-"));
|
|
60
|
+
if (!target) return { stderr: "readlink: missing operand\n", exitCode: 1 };
|
|
61
|
+
const p = resolvePath(cwd, target);
|
|
62
|
+
if (!shell.vfs.exists(p)) {
|
|
63
|
+
return { stderr: `readlink: ${target}: No such file or directory\n`, exitCode: 1 };
|
|
64
|
+
}
|
|
65
|
+
if (!shell.vfs.isSymlink(p)) {
|
|
66
|
+
return { stderr: `readlink: ${target}: not a symbolic link\n`, exitCode: 1 };
|
|
67
|
+
}
|
|
68
|
+
const resolved = shell.vfs.resolveSymlink(follow ? p : p);
|
|
69
|
+
return { stdout: `${resolved}\n`, exitCode: 0 };
|
|
70
|
+
},
|
|
71
|
+
};
|
package/src/commands/ls.ts
CHANGED
|
@@ -39,6 +39,23 @@ export const lsCommand: ShellModule = {
|
|
|
39
39
|
});
|
|
40
40
|
const target = resolvePath(cwd, targetArg ?? cwd);
|
|
41
41
|
assertPathAccess(authUser, target, "ls");
|
|
42
|
+
|
|
43
|
+
// If target is a file, show its info directly (ls -l /etc/passwd)
|
|
44
|
+
if (shell.vfs.exists(target)) {
|
|
45
|
+
const st = shell.vfs.stat(target);
|
|
46
|
+
if (st.type === "file" || shell.vfs.isSymlink(target)) {
|
|
47
|
+
if (longFormat) {
|
|
48
|
+
const name = target.split("/").pop() ?? target;
|
|
49
|
+
const size = st.type === "file" ? st.size : 0;
|
|
50
|
+
return {
|
|
51
|
+
stdout: `${formatPermissions(st.mode, false)} 1 root root ${size} ${formatDate(st.updatedAt)} ${name}\n`,
|
|
52
|
+
exitCode: 0,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { stdout: target.split("/").pop() ?? target, exitCode: 0 };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
42
59
|
const items = shell.vfs
|
|
43
60
|
.list(target)
|
|
44
61
|
.filter((name) => showHidden || !name.startsWith("."));
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { ifFlag } from "./command-helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Print Linux distribution information.
|
|
6
|
+
* @category system
|
|
7
|
+
* @params ["[-a] [-i] [-d] [-r] [-c]"]
|
|
8
|
+
*/
|
|
4
9
|
export const lsbReleaseCommand: ShellModule = {
|
|
5
10
|
name: "lsb_release",
|
|
6
11
|
description: "Print distribution-specific information",
|
package/src/commands/mkdir.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { getArg } from "./command-helpers";
|
|
3
3
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Create one or more directories.
|
|
7
|
+
* @category files
|
|
8
|
+
* @params ["<dir>"]
|
|
9
|
+
*/
|
|
5
10
|
export const mkdirCommand: ShellModule = {
|
|
6
11
|
name: "mkdir",
|
|
7
12
|
description: "Make directories",
|
package/src/commands/mv.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Move or rename files and directories.
|
|
6
|
+
* @category files
|
|
7
|
+
* @params ["<source> <dest>"]
|
|
8
|
+
*/
|
|
4
9
|
export const mvCommand: ShellModule = {
|
|
5
10
|
name: "mv",
|
|
6
11
|
description: "Move or rename files",
|
package/src/commands/nano.ts
CHANGED
|
@@ -2,6 +2,11 @@ import * as path from "node:path";
|
|
|
2
2
|
import type { ShellModule } from "../types/commands";
|
|
3
3
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Simple text editor for editing files.
|
|
7
|
+
* @category files
|
|
8
|
+
* @params ["<file>"]
|
|
9
|
+
*/
|
|
5
10
|
export const nanoCommand: ShellModule = {
|
|
6
11
|
name: "nano",
|
|
7
12
|
description: "Text editor",
|
package/src/commands/neofetch.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { buildNeofetchOutput } from "../modules/neofetch";
|
|
2
2
|
import type { ShellModule } from "../types/commands";
|
|
3
3
|
import { ifFlag } from "./command-helpers";
|
|
4
|
-
import { getAllEnvVars } from "./set";
|
|
5
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Display system information in a decorative format.
|
|
7
|
+
* @category system
|
|
8
|
+
* @params ["[--off]"]
|
|
9
|
+
*/
|
|
6
10
|
export const neofetchCommand: ShellModule = {
|
|
7
11
|
name: "neofetch",
|
|
8
12
|
description: "System info display",
|
|
9
13
|
category: "system",
|
|
10
14
|
params: ["[--off]"],
|
|
11
|
-
run: ({ args, authUser, hostname, shell }) => {
|
|
12
|
-
const env = getAllEnvVars(authUser);
|
|
13
|
-
|
|
15
|
+
run: ({ args, authUser, hostname, shell, env }) => {
|
|
14
16
|
if (ifFlag(args, "--help")) {
|
|
15
17
|
return {
|
|
16
18
|
stdout: "Usage: neofetch [--off]",
|
|
@@ -29,9 +31,9 @@ export const neofetchCommand: ShellModule = {
|
|
|
29
31
|
stdout: buildNeofetchOutput({
|
|
30
32
|
user: authUser,
|
|
31
33
|
host: hostname,
|
|
32
|
-
shell: env.SHELL,
|
|
34
|
+
shell: env.vars.SHELL,
|
|
33
35
|
shellProps: shell.properties,
|
|
34
|
-
terminal: env.TERM,
|
|
36
|
+
terminal: env.vars.TERM,
|
|
35
37
|
uptimeSeconds: Math.floor((Date.now() - shell.startTime) / 1000),
|
|
36
38
|
packages: (() => {
|
|
37
39
|
const count = shell.packageManager?.installedCount() ?? 0;
|
package/src/commands/passwd.ts
CHANGED
|
@@ -1,26 +1,49 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* `passwd [username]` — change a virtual user's password.
|
|
5
|
+
*
|
|
6
|
+
* - Root can change any user's password.
|
|
7
|
+
* - Non-root can only change their own password.
|
|
8
|
+
* - Interactive: emits `passwordChallenge` for hidden-input prompting.
|
|
9
|
+
* - Non-interactive: reads new password from stdin first line.
|
|
10
|
+
*/
|
|
3
11
|
export const passwdCommand: ShellModule = {
|
|
4
12
|
name: "passwd",
|
|
5
13
|
description: "Change user password",
|
|
6
14
|
category: "users",
|
|
7
|
-
params: ["
|
|
8
|
-
run: async ({ authUser, args, shell }) => {
|
|
9
|
-
const [
|
|
10
|
-
if (!username || !password) {
|
|
11
|
-
return {
|
|
12
|
-
stderr: "passwd: usage: passwd <username> <password>",
|
|
13
|
-
exitCode: 1,
|
|
14
|
-
};
|
|
15
|
-
}
|
|
15
|
+
params: ["[username]"],
|
|
16
|
+
run: async ({ authUser, args, shell, stdin }) => {
|
|
17
|
+
const targetUser = args[0] ?? authUser;
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
// Permission check
|
|
20
|
+
if (authUser !== "root" && authUser !== targetUser) {
|
|
18
21
|
return { stderr: "passwd: permission denied", exitCode: 1 };
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
// Target must exist
|
|
25
|
+
if (!shell.users.listUsers().includes(targetUser)) {
|
|
26
|
+
return { stderr: `passwd: user '${targetUser}' does not exist`, exitCode: 1 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Non-interactive: read new password from stdin
|
|
30
|
+
if (stdin !== undefined && stdin.trim().length > 0) {
|
|
31
|
+
const password = stdin.trim().split("\n")[0]!;
|
|
32
|
+
await shell.users.setPassword(targetUser, password);
|
|
33
|
+
return {
|
|
34
|
+
stdout: `passwd: password updated successfully\n`,
|
|
35
|
+
exitCode: 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Interactive: emit password challenge
|
|
22
40
|
return {
|
|
23
|
-
|
|
41
|
+
passwordChallenge: {
|
|
42
|
+
prompt: "New password: ",
|
|
43
|
+
confirmPrompt: "Retype new password: ",
|
|
44
|
+
action: "passwd" as const,
|
|
45
|
+
targetUsername: targetUser,
|
|
46
|
+
},
|
|
24
47
|
exitCode: 0,
|
|
25
48
|
};
|
|
26
49
|
},
|
package/src/commands/ping.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { parseArgs } from "./command-helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Send ICMP ECHO_REQUEST packets (mock implementation).
|
|
6
|
+
* @category network
|
|
7
|
+
* @params ["[-c <count>] <host>"]
|
|
8
|
+
*/
|
|
4
9
|
export const pingCommand: ShellModule = {
|
|
5
10
|
name: "ping",
|
|
6
11
|
description: "Send ICMP ECHO_REQUEST (mock)",
|
package/src/commands/printf.ts
CHANGED
|
@@ -50,35 +50,47 @@ function renderPrintf(fmt: string, args: string[]): string {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
if (fmt[i] === "%" && i + 1 < fmt.length) {
|
|
53
|
-
// Optional width/precision: %[-][width][.prec]spec
|
|
54
53
|
let j = i + 1;
|
|
55
|
-
if (fmt[j] === "-") j++;
|
|
56
|
-
|
|
54
|
+
let leftAlign = false; if (fmt[j] === "-") { leftAlign = true; j++; }
|
|
55
|
+
let zeroPad = false; if (fmt[j] === "0") { zeroPad = true; j++; }
|
|
56
|
+
let width = 0;
|
|
57
|
+
while (j < fmt.length && /\d/.test(fmt[j]!)) { width = width * 10 + parseInt(fmt[j]!, 10); j++; }
|
|
58
|
+
let precision = -1;
|
|
57
59
|
if (fmt[j] === ".") {
|
|
58
|
-
j++;
|
|
59
|
-
while (j < fmt.length && /\d/.test(fmt[j]!)) j++;
|
|
60
|
+
j++; precision = 0;
|
|
61
|
+
while (j < fmt.length && /\d/.test(fmt[j]!)) { precision = precision * 10 + parseInt(fmt[j]!, 10); j++; }
|
|
60
62
|
}
|
|
61
63
|
const spec = fmt[j];
|
|
62
64
|
const arg = args[argIdx++] ?? "";
|
|
65
|
+
const pad = (s: string, ch = " "): string => {
|
|
66
|
+
if (width <= 0 || s.length >= width) return s;
|
|
67
|
+
const fill = ch.repeat(width - s.length);
|
|
68
|
+
return leftAlign ? s + fill : fill + s;
|
|
69
|
+
};
|
|
63
70
|
switch (spec) {
|
|
64
|
-
case "s":
|
|
65
|
-
|
|
71
|
+
case "s": {
|
|
72
|
+
let val = String(arg);
|
|
73
|
+
if (precision >= 0) val = val.slice(0, precision);
|
|
74
|
+
out += pad(val);
|
|
66
75
|
break;
|
|
76
|
+
}
|
|
67
77
|
case "d":
|
|
68
78
|
case "i":
|
|
69
|
-
out += String(parseInt(arg, 10) || 0);
|
|
79
|
+
out += pad(String(parseInt(arg, 10) || 0), zeroPad ? "0" : " ");
|
|
70
80
|
break;
|
|
71
|
-
case "f":
|
|
72
|
-
|
|
81
|
+
case "f": {
|
|
82
|
+
const prec = precision >= 0 ? precision : 6;
|
|
83
|
+
out += pad((parseFloat(arg) || 0).toFixed(prec));
|
|
73
84
|
break;
|
|
85
|
+
}
|
|
74
86
|
case "o":
|
|
75
|
-
out += (parseInt(arg, 10) || 0).toString(8);
|
|
87
|
+
out += pad((parseInt(arg, 10) || 0).toString(8), zeroPad ? "0" : " ");
|
|
76
88
|
break;
|
|
77
89
|
case "x":
|
|
78
|
-
out += (parseInt(arg, 10) || 0).toString(16);
|
|
90
|
+
out += pad((parseInt(arg, 10) || 0).toString(16), zeroPad ? "0" : " ");
|
|
79
91
|
break;
|
|
80
92
|
case "X":
|
|
81
|
-
out += (parseInt(arg, 10) || 0).toString(16).toUpperCase();
|
|
93
|
+
out += pad((parseInt(arg, 10) || 0).toString(16).toUpperCase(), zeroPad ? "0" : " ");
|
|
82
94
|
break;
|
|
83
95
|
case "%":
|
|
84
96
|
out += "%";
|
|
@@ -98,6 +110,11 @@ function renderPrintf(fmt: string, args: string[]): string {
|
|
|
98
110
|
return out;
|
|
99
111
|
}
|
|
100
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Format and print data to stdout.
|
|
115
|
+
* @category shell
|
|
116
|
+
* @params ["<format> [args...]"]
|
|
117
|
+
*/
|
|
101
118
|
export const printfCommand: ShellModule = {
|
|
102
119
|
name: "printf",
|
|
103
120
|
description: "Format and print data",
|
package/src/commands/ps.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { ifFlag } from "./command-helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Report process status with various formatting options.
|
|
6
|
+
* @category system
|
|
7
|
+
* @params ["[-a] [-u] [-x] [aux]"]
|
|
8
|
+
*/
|
|
4
9
|
export const psCommand: ShellModule = {
|
|
5
10
|
name: "ps",
|
|
6
11
|
description: "Report process status",
|
package/src/commands/read.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ShellModule } from "../types/commands";
|
|
2
2
|
import { ifFlag } from "./command-helpers";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Read a line from stdin into one or more variables.
|
|
6
|
+
* @category shell
|
|
7
|
+
* @params ["[-r] [-p prompt] <var...>"]
|
|
8
|
+
*/
|
|
4
9
|
export const readCommand: ShellModule = {
|
|
5
10
|
name: "read",
|
|
6
11
|
description: "Read a line from stdin into variables",
|
package/src/commands/registry.ts
CHANGED
|
@@ -35,7 +35,8 @@ import { hostnameCommand } from "./hostname";
|
|
|
35
35
|
import { htopCommand } from "./htop";
|
|
36
36
|
import { idCommand } from "./id";
|
|
37
37
|
import { killCommand } from "./kill";
|
|
38
|
-
import { lnCommand } from "./ln";
|
|
38
|
+
import { lnCommand, readlinkCommand } from "./ln";
|
|
39
|
+
import { statCommand } from "./stat";
|
|
39
40
|
import { lsCommand } from "./ls";
|
|
40
41
|
import { lsbReleaseCommand } from "./lsb-release";
|
|
41
42
|
import { manCommand } from "./man";
|
|
@@ -96,7 +97,9 @@ const BASE_COMMANDS: ShellModule[] = [
|
|
|
96
97
|
cpCommand,
|
|
97
98
|
mvCommand,
|
|
98
99
|
lnCommand,
|
|
100
|
+
readlinkCommand,
|
|
99
101
|
chmodCommand,
|
|
102
|
+
statCommand,
|
|
100
103
|
findCommand,
|
|
101
104
|
// Text processing
|
|
102
105
|
grepCommand,
|
package/src/commands/rm.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { getArg, ifFlag } from "./command-helpers";
|
|
3
3
|
import { assertPathAccess, resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Remove files or directories from the filesystem.
|
|
7
|
+
* @category files
|
|
8
|
+
* @params ["[-r|-rf] <path>"]
|
|
9
|
+
*/
|
|
5
10
|
export const rmCommand: ShellModule = {
|
|
6
11
|
name: "rm",
|
|
7
12
|
description: "Remove files or directories",
|
package/src/commands/runtime.ts
CHANGED
|
@@ -8,69 +8,9 @@ import type {
|
|
|
8
8
|
ShellEnv,
|
|
9
9
|
} from "../types/commands";
|
|
10
10
|
import { expandAsync } from "../utils/expand";
|
|
11
|
+
import { tokenizeCommand } from "../utils/tokenize";
|
|
11
12
|
import { resolveModule } from "./registry";
|
|
12
13
|
|
|
13
|
-
// ── Tokenize command input respecting quotes ──────────────────────────────────
|
|
14
|
-
function tokenizeCommand(input: string): string[] {
|
|
15
|
-
const tokens: string[] = [];
|
|
16
|
-
let current = "";
|
|
17
|
-
let inQ = false;
|
|
18
|
-
let qChar = "";
|
|
19
|
-
let i = 0;
|
|
20
|
-
|
|
21
|
-
while (i < input.length) {
|
|
22
|
-
const ch = input[i]!;
|
|
23
|
-
const next = input[i + 1];
|
|
24
|
-
|
|
25
|
-
if ((ch === '"' || ch === "'") && !inQ) {
|
|
26
|
-
inQ = true;
|
|
27
|
-
qChar = ch;
|
|
28
|
-
i++;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (inQ && ch === qChar) {
|
|
32
|
-
inQ = false;
|
|
33
|
-
qChar = "";
|
|
34
|
-
i++;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (inQ) {
|
|
38
|
-
current += ch;
|
|
39
|
-
i++;
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (ch === " ") {
|
|
44
|
-
if (current) {
|
|
45
|
-
tokens.push(current);
|
|
46
|
-
current = "";
|
|
47
|
-
}
|
|
48
|
-
i++;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if ((ch === ">" || ch === "<") && !inQ) {
|
|
53
|
-
if (current) {
|
|
54
|
-
tokens.push(current);
|
|
55
|
-
current = "";
|
|
56
|
-
}
|
|
57
|
-
if (ch === ">" && next === ">") {
|
|
58
|
-
tokens.push(">>");
|
|
59
|
-
i += 2;
|
|
60
|
-
} else {
|
|
61
|
-
tokens.push(ch);
|
|
62
|
-
i++;
|
|
63
|
-
}
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
current += ch;
|
|
68
|
-
i++;
|
|
69
|
-
}
|
|
70
|
-
if (current) tokens.push(current);
|
|
71
|
-
return tokens;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
14
|
export function makeDefaultEnv(authUser: string, hostname: string): ShellEnv {
|
|
75
15
|
return {
|
|
76
16
|
vars: {
|
package/src/commands/sed.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { ShellModule } from "../types/commands";
|
|
|
2
2
|
import { getFlag, ifFlag } from "./command-helpers";
|
|
3
3
|
import { resolvePath } from "./helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Stream editor for filtering and transforming text lines.
|
|
7
|
+
* @category text
|
|
8
|
+
* @params ["-e <expr> [file]", "s/pattern/replace/[g]"]
|
|
9
|
+
*/
|
|
5
10
|
export const sedCommand: ShellModule = {
|
|
6
11
|
name: "sed",
|
|
7
12
|
description: "Stream editor for filtering and transforming text",
|
package/src/commands/set.ts
CHANGED
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
/** biome-ignore-all lint/style/useNamingConvention: env variables */
|
|
2
2
|
import type { ShellModule } from "../types/commands";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
TERM: "xterm-256color",
|
|
10
|
-
USER: "user",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
/** @deprecated use env.vars from CommandContext */
|
|
14
|
-
export function getEnvVar(name: string): string | undefined {
|
|
15
|
-
return _globalEnv[name];
|
|
16
|
-
}
|
|
17
|
-
/** @deprecated use env.vars from CommandContext */
|
|
18
|
-
export function setEnvVar(name: string, value: string): void {
|
|
19
|
-
_globalEnv[name] = value;
|
|
20
|
-
}
|
|
21
|
-
/** @deprecated use env.vars from CommandContext */
|
|
22
|
-
export function getAllEnvVars(authUser: string): Record<string, string> {
|
|
23
|
-
_globalEnv.USER = authUser;
|
|
24
|
-
_globalEnv.HOME = `/home/${authUser}`;
|
|
25
|
-
return { ..._globalEnv };
|
|
26
|
-
}
|
|
27
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Display or set shell variables and options.
|
|
6
|
+
* @category shell
|
|
7
|
+
* @params ["[VAR=value]"]
|
|
8
|
+
*/
|
|
28
9
|
export const setCommand: ShellModule = {
|
|
29
10
|
name: "set",
|
|
30
11
|
description: "Display or set shell variables",
|
package/src/commands/sh.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
CommandContext,
|
|
3
|
+
CommandResult,
|
|
4
|
+
ShellModule,
|
|
5
5
|
} from "../types/commands";
|
|
6
6
|
import { expandAsync } from "../utils/expand";
|
|
7
7
|
import { ifFlag } from "./command-helpers";
|
|
@@ -286,6 +286,12 @@ async function runBlocks(
|
|
|
286
286
|
return { ...lastResult, stdout: output.trim() || lastResult.stdout };
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Execute shell scripts or commands with a minimal shell interpreter.
|
|
291
|
+
* Supports if/elif/else, for loops, while loops, and variable expansion.
|
|
292
|
+
* @category shell
|
|
293
|
+
* @params ["-c <script>", "[<file>]"]
|
|
294
|
+
*/
|
|
289
295
|
export const shCommand: ShellModule = {
|
|
290
296
|
name: "sh",
|
|
291
297
|
aliases: ["bash"],
|