typescript-virtual-container 1.2.0 → 1.2.2
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/.github/workflows/publish.yml +25 -0
- package/README.md +25 -8
- package/benchmark-results.txt +40 -0
- package/benchmark-virtualshell.ts +96 -0
- package/dist/Honeypot/index.d.ts.map +1 -1
- package/dist/Honeypot/index.js +9 -0
- package/dist/SSHClient/index.d.ts +0 -14
- package/dist/SSHClient/index.d.ts.map +1 -1
- package/dist/SSHClient/index.js +19 -0
- package/dist/SSHMimic/index.d.ts +0 -7
- package/dist/SSHMimic/index.d.ts.map +1 -1
- package/dist/SSHMimic/index.js +5 -0
- package/dist/SSHMimic/sftp.d.ts.map +1 -1
- package/dist/SSHMimic/sftp.js +5 -0
- package/dist/VirtualFileSystem/index.d.ts +0 -7
- package/dist/VirtualFileSystem/index.d.ts.map +1 -1
- package/dist/VirtualFileSystem/index.js +18 -0
- package/dist/VirtualShell/index.d.ts.map +1 -1
- package/dist/VirtualShell/index.js +14 -1
- package/dist/VirtualUserManager/index.d.ts +4 -1
- package/dist/VirtualUserManager/index.d.ts.map +1 -1
- package/dist/VirtualUserManager/index.js +72 -13
- package/dist/utils/perfLogger.d.ts +9 -0
- package/dist/utils/perfLogger.d.ts.map +1 -0
- package/dist/utils/perfLogger.js +49 -0
- package/package.json +6 -3
- package/src/Honeypot/index.ts +11 -0
- package/src/SSHClient/index.ts +23 -1
- package/src/SSHMimic/index.ts +6 -0
- package/src/SSHMimic/sftp.ts +8 -1
- package/src/VirtualFileSystem/index.ts +20 -0
- package/src/VirtualShell/index.ts +18 -1
- package/src/VirtualUserManager/index.ts +103 -26
- package/src/utils/perfLogger.ts +72 -0
- package/dist/VirtualFileSystem/archive.d.ts +0 -5
- package/dist/VirtualFileSystem/archive.d.ts.map +0 -1
- package/dist/VirtualFileSystem/archive.js +0 -56
- package/dist/VirtualFileSystem/snapshot.d.ts +0 -5
- package/dist/VirtualFileSystem/snapshot.d.ts.map +0 -1
- package/dist/VirtualFileSystem/snapshot.js +0 -59
- package/dist/VirtualFileSystem/tree.d.ts +0 -3
- package/dist/VirtualFileSystem/tree.d.ts.map +0 -1
- package/dist/VirtualFileSystem/tree.js +0 -19
- package/dist/honeypot.d.ts +0 -132
- package/dist/honeypot.d.ts.map +0 -1
- package/dist/honeypot.js +0 -289
- package/src/VirtualFileSystem/archive.ts +0 -74
- package/src/VirtualFileSystem/snapshot.ts +0 -84
- package/src/VirtualFileSystem/tree.ts +0 -34
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Publish Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
id-token: write # Required for OIDC
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-node@v6
|
|
20
|
+
with:
|
|
21
|
+
node-version: '24'
|
|
22
|
+
registry-url: 'https://registry.npmjs.org'
|
|
23
|
+
- run: npm install
|
|
24
|
+
- run: npm run build --if-present
|
|
25
|
+
- run: npm publish
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `typescript-virtual-container`
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Scalable SSH/SFTP server with a virtual filesystem and typed programmatic API for testing, automation, and interactive shell scripting in TypeScript/JavaScript.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/typescript-virtual-container)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
`typescript-virtual-container` is a lightweight, fully-typed SSH/SFTP runtime written in TypeScript that provides:
|
|
38
38
|
|
|
39
39
|
- **SSH + SFTP Protocol Support**: Serve SSH shell/exec sessions and SFTP file operations on configurable ports.
|
|
40
|
-
- **Virtual Filesystem**:
|
|
40
|
+
- **Virtual Filesystem**: Fast developer workflow backed by a mirror directory under `.vfs/mirror`, with optional gzip compression and programmatic access.
|
|
41
41
|
- **User Management**: Create, authenticate, and manage virtual users with strict password hashing (scrypt) and sudo-like privilege elevation.
|
|
42
42
|
- **Programmatic Shell API**: Execute shell commands and query filesystem state directly from TypeScript without SSH overhead.
|
|
43
43
|
- **Event-Driven Architecture**: All core classes extend `EventEmitter` for lifecycle and operation tracking. Listen to auth events, filesystem operations, session lifecycle, and command execution for auditing and integration.
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
### What This Is
|
|
51
51
|
|
|
52
52
|
- A virtual shell runtime written in TypeScript.
|
|
53
|
-
-
|
|
53
|
+
- A virtual environment with its own virtual filesystem, user management, and command runtime.
|
|
54
54
|
- A practical tool for deterministic testing, automation pipelines, and SSH-like workflows without running real containers.
|
|
55
55
|
|
|
56
56
|
### What This Is Not
|
|
@@ -1684,17 +1684,34 @@ const ssh = new VirtualSshServer({
|
|
|
1684
1684
|
|
|
1685
1685
|
## Performance & Scalability
|
|
1686
1686
|
|
|
1687
|
-
###
|
|
1687
|
+
### Benchmarking
|
|
1688
1688
|
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1689
|
+
Use the built-in benchmark script to measure initialization and command throughput under concurrent shell loads:
|
|
1690
|
+
|
|
1691
|
+
```bash
|
|
1692
|
+
bun ./benchmark-virtualshell.ts
|
|
1693
|
+
```
|
|
1694
|
+
|
|
1695
|
+
The benchmark reports:
|
|
1696
|
+
|
|
1697
|
+
- shell initialization time by concurrency level
|
|
1698
|
+
- command execution time across all active shells
|
|
1699
|
+
- RSS memory growth during the run
|
|
1700
|
+
|
|
1701
|
+
Recent baseline runs show strong startup behavior up to 100 concurrent shells, and the runtime is designed to scale up to **1000 environments very easily** for testing and automation workloads.
|
|
1692
1702
|
|
|
1693
1703
|
### Concurrency
|
|
1694
1704
|
|
|
1695
1705
|
- SSH server handles multiple concurrent connections (event-driven)
|
|
1696
1706
|
- Programmatic `SshClient` is synchronous (executes sequentially per instance)
|
|
1697
1707
|
- Create multiple client instances for parallel operations
|
|
1708
|
+
- Horizontal shell instantiation (`new VirtualShell(...)`) is intended for high-volume scenarios, including large test matrices and multi-tenant simulation batches
|
|
1709
|
+
|
|
1710
|
+
### Scalability Notes
|
|
1711
|
+
|
|
1712
|
+
- Use a dedicated `basePath` per isolated environment to parallelize safely
|
|
1713
|
+
- Reuse long-lived shell instances when you need low-latency command bursts
|
|
1714
|
+
- Keep performance logging enabled in development (`DEV_MODE=1` or `RENDER_PERF=1`) to locate hotspots quickly
|
|
1698
1715
|
|
|
1699
1716
|
**Example:**
|
|
1700
1717
|
|
|
@@ -1740,7 +1757,7 @@ async function processResult(r: CommandResult) {
|
|
|
1740
1757
|
|
|
1741
1758
|
### Is this a real container runtime?
|
|
1742
1759
|
|
|
1743
|
-
No. It emulates SSH sessions, users, and filesystem behavior in
|
|
1760
|
+
No. It emulates SSH sessions, users, and filesystem behavior in a virtual runtime. It is ideal for testing, simulations, and automation workflows where full OS isolation is not required.
|
|
1744
1761
|
|
|
1745
1762
|
### Can I use this in production?
|
|
1746
1763
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Benchmarking VirtualShell concurrency:
|
|
2
|
+
|
|
3
|
+
Running 1 shells...
|
|
4
|
+
Initialized 1 shells in 86ms, RSS 82 MB
|
|
5
|
+
Executed shell commands in 2ms, RSS now 82 MB (+0 MB)
|
|
6
|
+
|
|
7
|
+
Running 2 shells...
|
|
8
|
+
Initialized 2 shells in 2ms, RSS 82 MB
|
|
9
|
+
Executed shell commands in 1ms, RSS now 82 MB (+0 MB)
|
|
10
|
+
|
|
11
|
+
Running 5 shells...
|
|
12
|
+
Initialized 5 shells in 5ms, RSS 82 MB
|
|
13
|
+
Executed shell commands in 2ms, RSS now 82 MB (+0 MB)
|
|
14
|
+
|
|
15
|
+
Running 10 shells...
|
|
16
|
+
Initialized 10 shells in 5ms, RSS 84 MB
|
|
17
|
+
Executed shell commands in 3ms, RSS now 84 MB (+1 MB)
|
|
18
|
+
|
|
19
|
+
Running 20 shells...
|
|
20
|
+
Initialized 20 shells in 12ms, RSS 84 MB
|
|
21
|
+
Executed shell commands in 6ms, RSS now 85 MB (+1 MB)
|
|
22
|
+
|
|
23
|
+
Running 50 shells...
|
|
24
|
+
Initialized 50 shells in 30ms, RSS 87 MB
|
|
25
|
+
Executed shell commands in 12ms, RSS now 88 MB (+2 MB)
|
|
26
|
+
|
|
27
|
+
Running 100 shells...
|
|
28
|
+
Initialized 100 shells in 52ms, RSS 91 MB
|
|
29
|
+
Executed shell commands in 28ms, RSS now 92 MB (+2 MB)
|
|
30
|
+
|
|
31
|
+
Summary:
|
|
32
|
+
|
|
33
|
+
count init_ms cmd_ms init_rss final_rss
|
|
34
|
+
1 86 2 82 MB 82 MB 0 MB
|
|
35
|
+
2 2 1 82 MB 82 MB 0 MB
|
|
36
|
+
5 5 2 82 MB 82 MB 0 MB
|
|
37
|
+
10 5 3 84 MB 84 MB 1 MB
|
|
38
|
+
20 12 6 84 MB 85 MB 1 MB
|
|
39
|
+
50 30 12 87 MB 88 MB 2 MB
|
|
40
|
+
100 52 28 91 MB 92 MB 2 MB
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { mkdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { VirtualShell } from "./src/index.ts";
|
|
6
|
+
|
|
7
|
+
const counts = [1, 2, 5, 10, 20, 50, 100];
|
|
8
|
+
const rootBenchmarkPath = join(process.cwd(), ".benchmark-shells");
|
|
9
|
+
|
|
10
|
+
function bytesToMb(bytes: number): string {
|
|
11
|
+
return `${Math.round(bytes / 1024 / 1024)} MB`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function createShell(baseName: string, index: number): Promise<VirtualShell> {
|
|
15
|
+
const basePath = join(rootBenchmarkPath, `${baseName}-${index}`);
|
|
16
|
+
mkdirSync(basePath, { recursive: true });
|
|
17
|
+
const shell = new VirtualShell(`${baseName}-${index}`, undefined, basePath);
|
|
18
|
+
await shell.ensureInitialized();
|
|
19
|
+
return shell;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function runSingleBenchmark(count: number) {
|
|
23
|
+
const label = `shells-${count}`;
|
|
24
|
+
const start = Date.now();
|
|
25
|
+
const shells: VirtualShell[] = await Promise.all(
|
|
26
|
+
Array.from({ length: count }, (_, index) => createShell(label, index)),
|
|
27
|
+
);
|
|
28
|
+
const initMs = Date.now() - start;
|
|
29
|
+
const initRss = process.memoryUsage().rss;
|
|
30
|
+
|
|
31
|
+
const commandStart = Date.now();
|
|
32
|
+
await Promise.all(
|
|
33
|
+
shells.map(async (shell, index) => {
|
|
34
|
+
const cwd = "/home/root";
|
|
35
|
+
shell.executeCommand(`mkdir -p /tmp/benchmark-${index}`, "root", cwd);
|
|
36
|
+
shell.executeCommand(
|
|
37
|
+
`echo "hello ${index}" > /tmp/benchmark-${index}/result.txt`,
|
|
38
|
+
"root",
|
|
39
|
+
cwd,
|
|
40
|
+
);
|
|
41
|
+
shell.executeCommand(
|
|
42
|
+
`cat /tmp/benchmark-${index}/result.txt`,
|
|
43
|
+
"root",
|
|
44
|
+
cwd,
|
|
45
|
+
);
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
const commandMs = Date.now() - commandStart;
|
|
49
|
+
const finalRss = process.memoryUsage().rss;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
count,
|
|
53
|
+
initMs,
|
|
54
|
+
commandMs,
|
|
55
|
+
initRss,
|
|
56
|
+
finalRss,
|
|
57
|
+
deltaRss: finalRss - initRss,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function main() {
|
|
62
|
+
rmSync(rootBenchmarkPath, { recursive: true, force: true });
|
|
63
|
+
mkdirSync(rootBenchmarkPath, { recursive: true });
|
|
64
|
+
|
|
65
|
+
console.log("Benchmarking VirtualShell concurrency:\n");
|
|
66
|
+
const results = [];
|
|
67
|
+
for (const count of counts) {
|
|
68
|
+
console.log(`Running ${count} shells...`);
|
|
69
|
+
const result = await runSingleBenchmark(count);
|
|
70
|
+
results.push(result);
|
|
71
|
+
console.log(
|
|
72
|
+
` Initialized ${count} shells in ${result.initMs}ms, RSS ${bytesToMb(result.initRss)}`,
|
|
73
|
+
);
|
|
74
|
+
console.log(
|
|
75
|
+
` Executed shell commands in ${result.commandMs}ms, RSS now ${bytesToMb(result.finalRss)} (+${bytesToMb(result.deltaRss)})`,
|
|
76
|
+
);
|
|
77
|
+
console.log("");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log("Summary:\n");
|
|
81
|
+
console.log(
|
|
82
|
+
"count\tinit_ms\tcmd_ms\tinit_rss\tfinal_rss\tdelta_rss",
|
|
83
|
+
);
|
|
84
|
+
for (const row of results) {
|
|
85
|
+
console.log(
|
|
86
|
+
`${row.count}\t${row.initMs}\t${row.commandMs}\t${bytesToMb(row.initRss)}\t${bytesToMb(row.finalRss)}\t${bytesToMb(row.deltaRss)}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(`\nBenchmark data stored under ${rootBenchmarkPath}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main().catch((error) => {
|
|
94
|
+
console.error(error);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Honeypot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Honeypot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAID;;;;;GAKG;AACH,qBAAa,QAAQ;IACpB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,KAAK,CAaX;IAEF,OAAO,CAAC,UAAU,CAAS;IAE3B;;;;OAIG;gBACS,UAAU,GAAE,MAAc;IAKtC;;;;;;;;OAQG;IACI,MAAM,CACZ,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,kBAAkB,EACzB,GAAG,CAAC,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE,SAAS,GACd,IAAI;IAaP;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAkChC;;OAEG;IACH,OAAO,CAAC,cAAc;IAyCtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAyCvB;;;;;;OAMG;IACH,OAAO,CAAC,GAAG;IAuBX;;;;;;OAMG;IACI,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,EAAE;IAQnE;;;;OAIG;IACI,QAAQ,IAAI,QAAQ,CAAC,aAAa,CAAC;IAK1C;;OAEG;IACI,KAAK,IAAI,IAAI;IAmBpB;;;;;OAKG;IACI,SAAS,CAAC,KAAK,GAAE,MAAY,GAAG,aAAa,EAAE;IAKtD;;;;OAIG;IACI,eAAe,IAAI,KAAK,CAAC;QAC/B,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;QACpC,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC;CAmDF;AAED,eAAe,QAAQ,CAAC"}
|
package/dist/Honeypot/index.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module honeypot
|
|
9
9
|
*/
|
|
10
|
+
import { createPerfLogger } from "../utils/perfLogger";
|
|
11
|
+
const perf = createPerfLogger("HoneyPot");
|
|
10
12
|
/**
|
|
11
13
|
* HoneyPot audit and event tracking utility.
|
|
12
14
|
*
|
|
@@ -36,6 +38,7 @@ export class HoneyPot {
|
|
|
36
38
|
* @param maxLogSize Maximum audit log entries to retain (default: 10000).
|
|
37
39
|
*/
|
|
38
40
|
constructor(maxLogSize = 10000) {
|
|
41
|
+
perf.mark("constructor");
|
|
39
42
|
this.maxLogSize = maxLogSize;
|
|
40
43
|
}
|
|
41
44
|
/**
|
|
@@ -48,6 +51,7 @@ export class HoneyPot {
|
|
|
48
51
|
* @param sftp SftpMimic instance (optional).
|
|
49
52
|
*/
|
|
50
53
|
attach(shell, vfs, users, ssh, sftp) {
|
|
54
|
+
perf.mark("attach");
|
|
51
55
|
this.attachVirtualShell(shell);
|
|
52
56
|
this.attachVirtualFileSystem(vfs);
|
|
53
57
|
this.attachVirtualUserManager(users);
|
|
@@ -204,6 +208,7 @@ export class HoneyPot {
|
|
|
204
208
|
* @returns Filtered audit log entries.
|
|
205
209
|
*/
|
|
206
210
|
getAuditLog(type, source) {
|
|
211
|
+
perf.mark("getAuditLog");
|
|
207
212
|
return this.auditLog.filter((entry) => (!type || entry.type === type) && (!source || entry.source === source));
|
|
208
213
|
}
|
|
209
214
|
/**
|
|
@@ -212,12 +217,14 @@ export class HoneyPot {
|
|
|
212
217
|
* @returns Snapshot of honeypot stats.
|
|
213
218
|
*/
|
|
214
219
|
getStats() {
|
|
220
|
+
perf.mark("getStats");
|
|
215
221
|
return Object.freeze({ ...this.stats });
|
|
216
222
|
}
|
|
217
223
|
/**
|
|
218
224
|
* Clears audit log and resets statistics.
|
|
219
225
|
*/
|
|
220
226
|
reset() {
|
|
227
|
+
perf.mark("reset");
|
|
221
228
|
this.auditLog = [];
|
|
222
229
|
this.stats = {
|
|
223
230
|
authAttempts: 0,
|
|
@@ -241,6 +248,7 @@ export class HoneyPot {
|
|
|
241
248
|
* @returns Recent audit log entries.
|
|
242
249
|
*/
|
|
243
250
|
getRecent(limit = 100) {
|
|
251
|
+
perf.mark("getRecent");
|
|
244
252
|
return this.auditLog.slice(Math.max(0, this.auditLog.length - limit));
|
|
245
253
|
}
|
|
246
254
|
/**
|
|
@@ -249,6 +257,7 @@ export class HoneyPot {
|
|
|
249
257
|
* @returns Array of anomalies detected.
|
|
250
258
|
*/
|
|
251
259
|
detectAnomalies() {
|
|
260
|
+
perf.mark("detectAnomalies");
|
|
252
261
|
const anomalies = [];
|
|
253
262
|
// High auth failure rate
|
|
254
263
|
if (this.stats.authAttempts > 0 &&
|
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
import type { CommandResult } from "../types/commands";
|
|
2
2
|
import type { VirtualShell } from "../VirtualShell";
|
|
3
|
-
/**
|
|
4
|
-
* Programmatic client for executing shell commands against a virtual shell.
|
|
5
|
-
*
|
|
6
|
-
* Maintains working-directory state across invocations and runs commands as a
|
|
7
|
-
* single authenticated user without SSH transport overhead.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* const shell = new VirtualShell("typescript-vm");
|
|
12
|
-
* const client = new SshClient(shell, "alice");
|
|
13
|
-
* const result = await client.cd("/tmp");
|
|
14
|
-
* const list = await client.ls();
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
3
|
export declare class SshClient {
|
|
18
4
|
private shell;
|
|
19
5
|
private username;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHClient/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHClient/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAkBpD,qBAAa,SAAS;IAUpB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAVjB,OAAO,CAAC,UAAU,CAAO;IAEzB;;;;;OAKG;gBAEM,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM;IAKzB;;;;;OAKG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA2BnD;;;;;OAKG;IACG,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAM/C;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;IAKnC;;;;;OAKG;IACG,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAS9C;;;;;OAKG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAK/C;;;;;;OAMG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAMpE;;;;;OAKG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAKjD;;;;;;OAMG;IACG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAMjE;;;;;;OAMG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBtE;;;;;OAKG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBpD;;;;OAIG;IACH,MAAM,IAAI,MAAM;IAKhB;;;;OAIG;IACH,WAAW,IAAI,MAAM;IAKrB;;;;;OAKG;IACG,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAMjD;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;IAKtC;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC;IAKxC;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;CAInC"}
|
package/dist/SSHClient/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runCommand } from "../commands";
|
|
2
|
+
import { createPerfLogger } from "../utils/perfLogger";
|
|
2
3
|
/**
|
|
3
4
|
* Programmatic client for executing shell commands against a virtual shell.
|
|
4
5
|
*
|
|
@@ -13,6 +14,7 @@ import { runCommand } from "../commands";
|
|
|
13
14
|
* const list = await client.ls();
|
|
14
15
|
* ```
|
|
15
16
|
*/
|
|
17
|
+
const perf = createPerfLogger("SshClient");
|
|
16
18
|
export class SshClient {
|
|
17
19
|
shell;
|
|
18
20
|
username;
|
|
@@ -26,6 +28,7 @@ export class SshClient {
|
|
|
26
28
|
constructor(shell, username) {
|
|
27
29
|
this.shell = shell;
|
|
28
30
|
this.username = username;
|
|
31
|
+
perf.mark("constructor");
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* Executes raw shell command.
|
|
@@ -34,6 +37,7 @@ export class SshClient {
|
|
|
34
37
|
* @returns Command result with stdout/stderr/exitCode.
|
|
35
38
|
*/
|
|
36
39
|
async exec(command) {
|
|
40
|
+
perf.mark("exec");
|
|
37
41
|
const vfs = this.shell.getVfs();
|
|
38
42
|
const users = this.shell.getUsers();
|
|
39
43
|
const hostname = this.shell.getHostname();
|
|
@@ -54,6 +58,7 @@ export class SshClient {
|
|
|
54
58
|
* @returns Result with directory listing in stdout.
|
|
55
59
|
*/
|
|
56
60
|
async ls(path) {
|
|
61
|
+
perf.mark("ls");
|
|
57
62
|
const target = path ?? ".";
|
|
58
63
|
return this.exec(`ls ${target}`);
|
|
59
64
|
}
|
|
@@ -63,6 +68,7 @@ export class SshClient {
|
|
|
63
68
|
* @returns Result with cwd path in stdout.
|
|
64
69
|
*/
|
|
65
70
|
async pwd() {
|
|
71
|
+
perf.mark("pwd");
|
|
66
72
|
return this.exec("pwd");
|
|
67
73
|
}
|
|
68
74
|
/**
|
|
@@ -72,6 +78,7 @@ export class SshClient {
|
|
|
72
78
|
* @returns Result; updates internal cwd on success.
|
|
73
79
|
*/
|
|
74
80
|
async cd(path) {
|
|
81
|
+
perf.mark("cd");
|
|
75
82
|
const result = await this.exec(`cd ${path}`);
|
|
76
83
|
if (result.nextCwd && result.exitCode !== 1) {
|
|
77
84
|
this.currentCwd = result.nextCwd;
|
|
@@ -85,6 +92,7 @@ export class SshClient {
|
|
|
85
92
|
* @returns Result with file content in stdout.
|
|
86
93
|
*/
|
|
87
94
|
async cat(path) {
|
|
95
|
+
perf.mark("cat");
|
|
88
96
|
return this.exec(`cat ${path}`);
|
|
89
97
|
}
|
|
90
98
|
/**
|
|
@@ -95,6 +103,7 @@ export class SshClient {
|
|
|
95
103
|
* @returns Result from mkdir command.
|
|
96
104
|
*/
|
|
97
105
|
async mkdir(path, recursive = false) {
|
|
106
|
+
perf.mark("mkdir");
|
|
98
107
|
const flag = recursive ? "-p " : "";
|
|
99
108
|
return this.exec(`mkdir ${flag}${path}`);
|
|
100
109
|
}
|
|
@@ -105,6 +114,7 @@ export class SshClient {
|
|
|
105
114
|
* @returns Result from touch command.
|
|
106
115
|
*/
|
|
107
116
|
async touch(path) {
|
|
117
|
+
perf.mark("touch");
|
|
108
118
|
return this.exec(`touch ${path}`);
|
|
109
119
|
}
|
|
110
120
|
/**
|
|
@@ -115,6 +125,7 @@ export class SshClient {
|
|
|
115
125
|
* @returns Result from rm command.
|
|
116
126
|
*/
|
|
117
127
|
async rm(path, recursive = false) {
|
|
128
|
+
perf.mark("rm");
|
|
118
129
|
const flag = recursive ? "-r " : "";
|
|
119
130
|
return this.exec(`rm ${flag}${path}`);
|
|
120
131
|
}
|
|
@@ -126,6 +137,7 @@ export class SshClient {
|
|
|
126
137
|
* @returns Result from touch/write simulation.
|
|
127
138
|
*/
|
|
128
139
|
async writeFile(path, content) {
|
|
140
|
+
perf.mark("writeFile");
|
|
129
141
|
const vfs = this.shell.getVfs();
|
|
130
142
|
if (!vfs) {
|
|
131
143
|
throw new Error("SSH client not started");
|
|
@@ -148,6 +160,7 @@ export class SshClient {
|
|
|
148
160
|
* @returns File content as string or error in result.
|
|
149
161
|
*/
|
|
150
162
|
async readFile(path) {
|
|
163
|
+
perf.mark("readFile");
|
|
151
164
|
const vfs = this.shell.getVfs();
|
|
152
165
|
if (!vfs) {
|
|
153
166
|
throw new Error("SSH client not started");
|
|
@@ -169,6 +182,7 @@ export class SshClient {
|
|
|
169
182
|
* @returns Normalized cwd path.
|
|
170
183
|
*/
|
|
171
184
|
getCwd() {
|
|
185
|
+
perf.mark("getCwd");
|
|
172
186
|
return this.currentCwd;
|
|
173
187
|
}
|
|
174
188
|
/**
|
|
@@ -177,6 +191,7 @@ export class SshClient {
|
|
|
177
191
|
* @returns Associated username.
|
|
178
192
|
*/
|
|
179
193
|
getUsername() {
|
|
194
|
+
perf.mark("getUsername");
|
|
180
195
|
return this.username;
|
|
181
196
|
}
|
|
182
197
|
/**
|
|
@@ -186,6 +201,7 @@ export class SshClient {
|
|
|
186
201
|
* @returns Result with ASCII tree in stdout.
|
|
187
202
|
*/
|
|
188
203
|
async tree(path) {
|
|
204
|
+
perf.mark("tree");
|
|
189
205
|
const target = path ?? ".";
|
|
190
206
|
return this.exec(`tree ${target}`);
|
|
191
207
|
}
|
|
@@ -195,6 +211,7 @@ export class SshClient {
|
|
|
195
211
|
* @returns Result from whoami command.
|
|
196
212
|
*/
|
|
197
213
|
async whoami() {
|
|
214
|
+
perf.mark("whoami");
|
|
198
215
|
return this.exec("whoami");
|
|
199
216
|
}
|
|
200
217
|
/**
|
|
@@ -203,6 +220,7 @@ export class SshClient {
|
|
|
203
220
|
* @returns Result from hostname command.
|
|
204
221
|
*/
|
|
205
222
|
async hostname() {
|
|
223
|
+
perf.mark("hostname");
|
|
206
224
|
return this.exec("hostname");
|
|
207
225
|
}
|
|
208
226
|
/**
|
|
@@ -211,6 +229,7 @@ export class SshClient {
|
|
|
211
229
|
* @returns Result from who command.
|
|
212
230
|
*/
|
|
213
231
|
async who() {
|
|
232
|
+
perf.mark("who");
|
|
214
233
|
return this.exec("who");
|
|
215
234
|
}
|
|
216
235
|
}
|
package/dist/SSHMimic/index.d.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { Server as SshServer } from "ssh2";
|
|
3
3
|
import { VirtualShell } from "../VirtualShell";
|
|
4
|
-
/**
|
|
5
|
-
* SSH server facade that wires the virtual shell runtime into ssh2 sessions.
|
|
6
|
-
*
|
|
7
|
-
* This class is exported as `VirtualSshServer` for public API compatibility.
|
|
8
|
-
* Create an instance, call {@link SshMimic.start}, and stop it with
|
|
9
|
-
* {@link SshMimic.stop} when your process exits.
|
|
10
|
-
*/
|
|
11
4
|
declare class SshMimic extends EventEmitter {
|
|
12
5
|
port: number;
|
|
13
6
|
server: SshServer | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAc/C,cAAM,QAAS,SAAQ,YAAY;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;;;OAMG;gBACS,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAkC,GAClC,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,YAAY,CAAC;KACrB;IASD;;;;OAIG;IACU,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAyHrC;;OAEG;IACI,IAAI,IAAI,IAAI;CASnB;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/SSHMimic/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { Server as SshServer } from "ssh2";
|
|
3
3
|
import { VirtualShell } from "../VirtualShell";
|
|
4
|
+
import { createPerfLogger } from "../utils/perfLogger";
|
|
4
5
|
import { runExec } from "./exec";
|
|
5
6
|
import { loadOrCreateHostKey } from "./hostKey";
|
|
6
7
|
/**
|
|
@@ -10,6 +11,7 @@ import { loadOrCreateHostKey } from "./hostKey";
|
|
|
10
11
|
* Create an instance, call {@link SshMimic.start}, and stop it with
|
|
11
12
|
* {@link SshMimic.stop} when your process exits.
|
|
12
13
|
*/
|
|
14
|
+
const perf = createPerfLogger("SshMimic");
|
|
13
15
|
class SshMimic extends EventEmitter {
|
|
14
16
|
port;
|
|
15
17
|
server;
|
|
@@ -24,6 +26,7 @@ class SshMimic extends EventEmitter {
|
|
|
24
26
|
*/
|
|
25
27
|
constructor({ port, hostname = "typescript-vm", shell = new VirtualShell(hostname), }) {
|
|
26
28
|
super();
|
|
29
|
+
perf.mark("constructor");
|
|
27
30
|
this.port = port;
|
|
28
31
|
this.shellHostname = hostname;
|
|
29
32
|
this.server = null;
|
|
@@ -35,6 +38,7 @@ class SshMimic extends EventEmitter {
|
|
|
35
38
|
* @returns Promise resolved with bound listening port.
|
|
36
39
|
*/
|
|
37
40
|
async start() {
|
|
41
|
+
perf.mark("start");
|
|
38
42
|
const shell = this.shell;
|
|
39
43
|
const privateKey = loadOrCreateHostKey();
|
|
40
44
|
// Ensure VirtualShell is fully initialized before accepting connections
|
|
@@ -118,6 +122,7 @@ class SshMimic extends EventEmitter {
|
|
|
118
122
|
* Stops server if running.
|
|
119
123
|
*/
|
|
120
124
|
stop() {
|
|
125
|
+
perf.mark("stop");
|
|
121
126
|
if (this.server) {
|
|
122
127
|
this.server.close(() => {
|
|
123
128
|
console.log("SSH Mimic stopped");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/sftp.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/sftp.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAI3C,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AA4HhE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,iBAAiB,CAAC;IACxB,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAED,qBAAa,SAAU,SAAQ,YAAY;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,OAAO,CAAiC;gBAEpC,EACX,IAAI,EACJ,QAA0B,EAC1B,KAAK,EACL,GAAG,EACH,KAAK,GACL,EAAE,gBAAgB;IAwBnB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAyJ9B,IAAI,IAAI,IAAI;IAUnB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,kBAAkB;CA6a1B"}
|
package/dist/SSHMimic/sftp.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { Server as SshServer } from "ssh2";
|
|
5
|
+
import { createPerfLogger } from "../utils/perfLogger";
|
|
5
6
|
import { VirtualShell } from "../VirtualShell";
|
|
6
7
|
import { loadOrCreateHostKey } from "./hostKey";
|
|
7
8
|
const SFTP_STATUS_CODE = {
|
|
@@ -23,6 +24,7 @@ const OPEN_MODE = {
|
|
|
23
24
|
TRUNC: 0x00000010,
|
|
24
25
|
EXCL: 0x00000020,
|
|
25
26
|
};
|
|
27
|
+
const perf = createPerfLogger("SftpMimic");
|
|
26
28
|
export class SftpMimic extends EventEmitter {
|
|
27
29
|
port;
|
|
28
30
|
server;
|
|
@@ -34,6 +36,7 @@ export class SftpMimic extends EventEmitter {
|
|
|
34
36
|
handles = new Map();
|
|
35
37
|
constructor({ port, hostname = "typescript-vm", shell, vfs, users, }) {
|
|
36
38
|
super();
|
|
39
|
+
perf.mark("constructor");
|
|
37
40
|
this.port = port;
|
|
38
41
|
this.server = null;
|
|
39
42
|
this.hostname = hostname;
|
|
@@ -62,6 +65,7 @@ export class SftpMimic extends EventEmitter {
|
|
|
62
65
|
return this.shell?.users ?? this.users;
|
|
63
66
|
}
|
|
64
67
|
async start() {
|
|
68
|
+
perf.mark("start");
|
|
65
69
|
const privateKey = loadOrCreateHostKey();
|
|
66
70
|
// Ensure VirtualShell is fully initialized before accepting connections
|
|
67
71
|
if (this.shell) {
|
|
@@ -175,6 +179,7 @@ export class SftpMimic extends EventEmitter {
|
|
|
175
179
|
});
|
|
176
180
|
}
|
|
177
181
|
stop() {
|
|
182
|
+
perf.mark("stop");
|
|
178
183
|
if (this.server) {
|
|
179
184
|
this.server.close(() => {
|
|
180
185
|
console.log("SFTP Mimic stopped");
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import type { RemoveOptions, VfsNodeStats, WriteFileOptions } from "../types/vfs";
|
|
3
|
-
/**
|
|
4
|
-
* In-memory virtual filesystem with tar.gz mirror persistence.
|
|
5
|
-
*
|
|
6
|
-
* Paths are normalized to POSIX-like absolute paths. Use
|
|
7
|
-
* {@link VirtualFileSystem.restoreMirror} on startup and
|
|
8
|
-
* {@link VirtualFileSystem.flushMirror} to persist pending changes.
|
|
9
|
-
*/
|
|
10
3
|
declare class VirtualFileSystem extends EventEmitter {
|
|
11
4
|
private readonly mirrorRoot;
|
|
12
5
|
private ensureMirrorRoot;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualFileSystem/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,MAAM,cAAc,CAAC;AActB,cAAM,iBAAkB,SAAQ,YAAY;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,eAAe;IA4BvB;;;;OAIG;gBACS,OAAO,GAAE,MAAsB;IAM3C;;;;OAIG;IACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3C;;;;OAIG;IACU,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAMzC;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,MAAc,GAAG,IAAI;IAa5D;;;;;;;;OAQG;IACI,SAAS,CACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC5B,IAAI;IAyBP;;;;;;;OAOG;IACI,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAe3C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAU1C;;;;;OAKG;IACI,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IASpD;;;;;OAKG;IACI,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY;IAsC7C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM,EAAE;IAU5C;;;;;OAKG;IACI,IAAI,CAAC,OAAO,GAAE,MAAY,GAAG,MAAM;IAY1C;;;;;;;;OAQG;IACI,aAAa,CAAC,UAAU,GAAE,MAAY,GAAG,MAAM;IAStD;;;;OAIG;IACI,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAa7C;;;;OAIG;IACI,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAa/C;;;;;OAKG;IACI,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI;IA6BpE;;;;;OAKG;IACI,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAuBnD;AAED,eAAe,iBAAiB,CAAC"}
|