ssh-agent-workspace 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +319 -0
- package/dist/__tests__/SSHManager.test.d.ts +2 -0
- package/dist/__tests__/SSHManager.test.d.ts.map +1 -0
- package/dist/__tests__/SSHManager.test.js +134 -0
- package/dist/__tests__/SSHManager.test.js.map +1 -0
- package/dist/__tests__/SessionManager.test.d.ts +2 -0
- package/dist/__tests__/SessionManager.test.d.ts.map +1 -0
- package/dist/__tests__/SessionManager.test.js +141 -0
- package/dist/__tests__/SessionManager.test.js.map +1 -0
- package/dist/__tests__/StorageManager.test.d.ts +2 -0
- package/dist/__tests__/StorageManager.test.d.ts.map +1 -0
- package/dist/__tests__/StorageManager.test.js +171 -0
- package/dist/__tests__/StorageManager.test.js.map +1 -0
- package/dist/__tests__/ansi.test.d.ts +2 -0
- package/dist/__tests__/ansi.test.d.ts.map +1 -0
- package/dist/__tests__/ansi.test.js +41 -0
- package/dist/__tests__/ansi.test.js.map +1 -0
- package/dist/__tests__/security.test.d.ts +2 -0
- package/dist/__tests__/security.test.d.ts.map +1 -0
- package/dist/__tests__/security.test.js +87 -0
- package/dist/__tests__/security.test.js.map +1 -0
- package/dist/__tests__/validation.test.d.ts +2 -0
- package/dist/__tests__/validation.test.d.ts.map +1 -0
- package/dist/__tests__/validation.test.js +23 -0
- package/dist/__tests__/validation.test.js.map +1 -0
- package/dist/core/HostSecurityManager.d.ts +25 -0
- package/dist/core/HostSecurityManager.d.ts.map +1 -0
- package/dist/core/HostSecurityManager.js +76 -0
- package/dist/core/HostSecurityManager.js.map +1 -0
- package/dist/core/SSHManager.d.ts +48 -0
- package/dist/core/SSHManager.d.ts.map +1 -0
- package/dist/core/SSHManager.js +288 -0
- package/dist/core/SSHManager.js.map +1 -0
- package/dist/core/SessionManager.d.ts +15 -0
- package/dist/core/SessionManager.d.ts.map +1 -0
- package/dist/core/SessionManager.js +96 -0
- package/dist/core/SessionManager.js.map +1 -0
- package/dist/core/StorageManager.d.ts +27 -0
- package/dist/core/StorageManager.d.ts.map +1 -0
- package/dist/core/StorageManager.js +87 -0
- package/dist/core/StorageManager.js.map +1 -0
- package/dist/core/TmuxManager.d.ts +21 -0
- package/dist/core/TmuxManager.d.ts.map +1 -0
- package/dist/core/TmuxManager.js +110 -0
- package/dist/core/TmuxManager.js.map +1 -0
- package/dist/core/ToolConfigManager.d.ts +15 -0
- package/dist/core/ToolConfigManager.d.ts.map +1 -0
- package/dist/core/ToolConfigManager.js +57 -0
- package/dist/core/ToolConfigManager.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +169 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +44 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +152 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/backup.d.ts +74 -0
- package/dist/tools/backup.d.ts.map +1 -0
- package/dist/tools/backup.js +152 -0
- package/dist/tools/backup.js.map +1 -0
- package/dist/tools/connect.d.ts +46 -0
- package/dist/tools/connect.d.ts.map +1 -0
- package/dist/tools/connect.js +235 -0
- package/dist/tools/connect.js.map +1 -0
- package/dist/tools/connection_status.d.ts +39 -0
- package/dist/tools/connection_status.d.ts.map +1 -0
- package/dist/tools/connection_status.js +67 -0
- package/dist/tools/connection_status.js.map +1 -0
- package/dist/tools/db_query.d.ts +103 -0
- package/dist/tools/db_query.d.ts.map +1 -0
- package/dist/tools/db_query.js +194 -0
- package/dist/tools/db_query.js.map +1 -0
- package/dist/tools/deploy.d.ts +127 -0
- package/dist/tools/deploy.d.ts.map +1 -0
- package/dist/tools/deploy.js +201 -0
- package/dist/tools/deploy.js.map +1 -0
- package/dist/tools/disconnect.d.ts +46 -0
- package/dist/tools/disconnect.d.ts.map +1 -0
- package/dist/tools/disconnect.js +77 -0
- package/dist/tools/disconnect.js.map +1 -0
- package/dist/tools/exec.d.ts +69 -0
- package/dist/tools/exec.d.ts.map +1 -0
- package/dist/tools/exec.js +188 -0
- package/dist/tools/exec.js.map +1 -0
- package/dist/tools/group_exec.d.ts +80 -0
- package/dist/tools/group_exec.d.ts.map +1 -0
- package/dist/tools/group_exec.js +150 -0
- package/dist/tools/group_exec.js.map +1 -0
- package/dist/tools/health_check.d.ts +38 -0
- package/dist/tools/health_check.d.ts.map +1 -0
- package/dist/tools/health_check.js +161 -0
- package/dist/tools/health_check.js.map +1 -0
- package/dist/tools/host_security.d.ts +52 -0
- package/dist/tools/host_security.d.ts.map +1 -0
- package/dist/tools/host_security.js +127 -0
- package/dist/tools/host_security.js.map +1 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +24 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/interrupt.d.ts +47 -0
- package/dist/tools/interrupt.d.ts.map +1 -0
- package/dist/tools/interrupt.js +77 -0
- package/dist/tools/interrupt.js.map +1 -0
- package/dist/tools/list_hosts.d.ts +15 -0
- package/dist/tools/list_hosts.d.ts.map +1 -0
- package/dist/tools/list_hosts.js +18 -0
- package/dist/tools/list_hosts.js.map +1 -0
- package/dist/tools/list_sessions.d.ts +16 -0
- package/dist/tools/list_sessions.d.ts.map +1 -0
- package/dist/tools/list_sessions.js +20 -0
- package/dist/tools/list_sessions.js.map +1 -0
- package/dist/tools/read_output.d.ts +46 -0
- package/dist/tools/read_output.d.ts.map +1 -0
- package/dist/tools/read_output.js +73 -0
- package/dist/tools/read_output.js.map +1 -0
- package/dist/tools/reconnect_to_tmux.d.ts +53 -0
- package/dist/tools/reconnect_to_tmux.d.ts.map +1 -0
- package/dist/tools/reconnect_to_tmux.js +199 -0
- package/dist/tools/reconnect_to_tmux.js.map +1 -0
- package/dist/tools/send_input.d.ts +45 -0
- package/dist/tools/send_input.d.ts.map +1 -0
- package/dist/tools/send_input.js +83 -0
- package/dist/tools/send_input.js.map +1 -0
- package/dist/tools/sftp_download.d.ts +52 -0
- package/dist/tools/sftp_download.d.ts.map +1 -0
- package/dist/tools/sftp_download.js +90 -0
- package/dist/tools/sftp_download.js.map +1 -0
- package/dist/tools/sftp_list.d.ts +46 -0
- package/dist/tools/sftp_list.d.ts.map +1 -0
- package/dist/tools/sftp_list.js +93 -0
- package/dist/tools/sftp_list.js.map +1 -0
- package/dist/tools/sftp_upload.d.ts +52 -0
- package/dist/tools/sftp_upload.d.ts.map +1 -0
- package/dist/tools/sftp_upload.js +98 -0
- package/dist/tools/sftp_upload.js.map +1 -0
- package/dist/tools/ssh_tunnel.d.ts +116 -0
- package/dist/tools/ssh_tunnel.d.ts.map +1 -0
- package/dist/tools/ssh_tunnel.js +282 -0
- package/dist/tools/ssh_tunnel.js.map +1 -0
- package/dist/tools/sync.d.ts +71 -0
- package/dist/tools/sync.d.ts.map +1 -0
- package/dist/tools/sync.js +310 -0
- package/dist/tools/sync.js.map +1 -0
- package/dist/tools/tail_log.d.ts +61 -0
- package/dist/tools/tail_log.d.ts.map +1 -0
- package/dist/tools/tail_log.js +111 -0
- package/dist/tools/tail_log.js.map +1 -0
- package/dist/tools/tools_config.d.ts +34 -0
- package/dist/tools/tools_config.d.ts.map +1 -0
- package/dist/tools/tools_config.js +98 -0
- package/dist/tools/tools_config.js.map +1 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/ansi.d.ts +2 -0
- package/dist/utils/ansi.d.ts.map +1 -0
- package/dist/utils/ansi.js +7 -0
- package/dist/utils/ansi.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +8 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/security.d.ts +7 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +58 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/utils/ssh.d.ts +4 -0
- package/dist/utils/ssh.d.ts.map +1 -0
- package/dist/utils/ssh.js +29 -0
- package/dist/utils/ssh.js.map +1 -0
- package/dist/utils/sshConfig.d.ts +4 -0
- package/dist/utils/sshConfig.d.ts.map +1 -0
- package/dist/utils/sshConfig.js +85 -0
- package/dist/utils/sshConfig.js.map +1 -0
- package/dist/utils/validation.d.ts +4 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +12 -0
- package/dist/utils/validation.js.map +1 -0
- package/docs/SECURITY.md +213 -0
- package/docs/TOOLS.md +425 -0
- package/keygen.bat +325 -0
- package/package.json +48 -0
- package/test_check.bat +9 -0
- package/test_delayed.bat +12 -0
- package/vitest.config.ts +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# ssh-agent-workspace
|
|
2
|
+
|
|
3
|
+
> **Persistent SSH workspaces for AI agents.**
|
|
4
|
+
>
|
|
5
|
+
> Stateful tmux-backed sessions that survive reconnects, MCP restarts, and network drops — with runtime security policies and tool configuration.
|
|
6
|
+
|
|
7
|
+
<p align="left">
|
|
8
|
+
<img src="https://img.shields.io/badge/Node.js-≥18-339933?logo=node.js" alt="Node.js ≥18">
|
|
9
|
+
<img src="https://img.shields.io/badge/MCP-Server-orange" alt="MCP">
|
|
10
|
+
<img src="https://img.shields.io/badge/Tools-25-blue" alt="25 tools">
|
|
11
|
+
<img src="https://img.shields.io/badge/npm-v1.0.0-red?logo=npm" alt="npm">
|
|
12
|
+
<img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT">
|
|
13
|
+
<img src="https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey" alt="platform">
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## The Problem
|
|
19
|
+
|
|
20
|
+
Every SSH MCP server runs commands in a **fresh shell**. That means:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
❌ cwd resets every time
|
|
24
|
+
❌ env vars are gone
|
|
25
|
+
❌ shell history disappears
|
|
26
|
+
❌ vim / htop / docker attach break
|
|
27
|
+
❌ all state evaporates on reconnect
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Your AI agent has to `cd`, re-export, re-configure before every single command — wasting tokens, time, and context.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What ssh-agent-workspace Does
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
AI Agent
|
|
38
|
+
│
|
|
39
|
+
│ MCP stdio
|
|
40
|
+
▼
|
|
41
|
+
tmux workspace (persistent)
|
|
42
|
+
├─ cwd survives
|
|
43
|
+
├─ env survives
|
|
44
|
+
├─ history survives
|
|
45
|
+
├─ processes survive (vim, htop, docker attach...)
|
|
46
|
+
├─ auto-restore after MCP restart
|
|
47
|
+
├─ auto-restore after SSH drop
|
|
48
|
+
├─ runtime security per host
|
|
49
|
+
└─ runtime tool enable/disable
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Your agent gets a **real interactive terminal** — not one-off exec commands. It's like giving your AI its own tmux session that never dies.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Comparison
|
|
57
|
+
|
|
58
|
+
| | ssh-agent-workspace | Typical SSH MCP |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| **Session model** | Persistent tmux workspace | Throwaway exec channel |
|
|
61
|
+
| **cwd / env / history** | Survives everything | Lost after each command |
|
|
62
|
+
| **Running processes** | Stay alive (vim, htop, etc.) | Killed immediately |
|
|
63
|
+
| **Reconnection** | Auto-restore on startup | Manual reconnect, fresh shell |
|
|
64
|
+
| **Prompt detection** | Deterministic custom PS1 | Blind sleep + guess |
|
|
65
|
+
| **Per-host security** | runtime `host_security` tool | Env vars only, restart required |
|
|
66
|
+
| **Tool management** | runtime `tools_config` (persistent) | None or env vars |
|
|
67
|
+
| **Token efficiency** | ~2,800 tokens (25 tools) | ~43,500 tokens (37 tools) |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
### Install
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install -g ssh-agent-workspace
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or from source:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/ShiroNexo/dynamic-ssh-mcp.git
|
|
83
|
+
cd dynamic-ssh-mcp
|
|
84
|
+
npm install && npm run build
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Configure MCP Client
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"mcpServers": {
|
|
92
|
+
"workspace": {
|
|
93
|
+
"command": "node",
|
|
94
|
+
"args": ["/path/to/dist/index.js"],
|
|
95
|
+
"env": {
|
|
96
|
+
"MCP_SSH_RESTORE_SESSIONS": "true"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### First session
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
> connect host=prod
|
|
107
|
+
→ { session_id: "sess_abc", tmux_session: "mcp_prod_x1y2z3" }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Your agent now has a persistent workspace on `prod`. Running `cd /var/www` once means the agent stays there for every subsequent command.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Key Features
|
|
115
|
+
|
|
116
|
+
### Workspace Persistence
|
|
117
|
+
|
|
118
|
+
Every session is a **dedicated tmux session** on the remote host. Your agent's working directory, environment variables, shell history, and running processes persist across commands, disconnections, and server restarts.
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
connect → tmux workspace created with PS1='__MCP_PROMPT__> '
|
|
122
|
+
│
|
|
123
|
+
exec "cd /var/www" → prompt wait → output returned → cwd is now /var/www
|
|
124
|
+
exec "docker ps" → runs in /var/www, no need to cd again
|
|
125
|
+
exec "vim app.js" → vim opens and stays running in tmux
|
|
126
|
+
|
|
127
|
+
(SSH drops, MCP restarts...)
|
|
128
|
+
|
|
129
|
+
reconnect_to_tmux → same tmux session, same cwd, vim still open
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Deterministic Output
|
|
133
|
+
|
|
134
|
+
Instead of `sleep 3 && capture`, every `exec` call polls `tmux capture-pane` until the exact prompt string `__MCP_PROMPT__>` appears. No race conditions, no false positives from output that looks like a prompt.
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Send command → grace interval → poll every 250ms → prompt detected → capture → return output
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Auto-Recovery
|
|
141
|
+
|
|
142
|
+
On server start, `ssh-agent-workspace` scans all configured hosts for `mcp_*` tmux sessions and automatically reconnects. Disable with `MCP_SSH_RESTORE_SESSIONS=false`.
|
|
143
|
+
|
|
144
|
+
### Runtime Reconfiguration
|
|
145
|
+
|
|
146
|
+
| Tool | What it does |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `tools_config` | Enable/disable any tool at runtime. Persistent. `tools_config` itself can never be disabled. |
|
|
149
|
+
| `host_security` | Set per-host read-only, command allowlist/denylist at runtime. Overrides global env vars per host. |
|
|
150
|
+
|
|
151
|
+
No restart needed. Changes apply immediately.
|
|
152
|
+
|
|
153
|
+
### Three-Layer Security
|
|
154
|
+
|
|
155
|
+
| Layer | Scope | Mechanism |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| **Global** | All hosts | `MCP_SSH_READONLY`, `MCP_SSH_ALLOWED_HOSTS`, `MCP_SSH_DENYLIST_COMMANDS` |
|
|
158
|
+
| **Per-Host** | Individual host | `host_security` tool: `readonly`, `allow_commands`, `deny_commands` |
|
|
159
|
+
| **Per-Operation** | Single command/query | SQL keyword blocklist, path sanitization, shell escaping |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Tools (25)
|
|
164
|
+
|
|
165
|
+
### Workspace (9)
|
|
166
|
+
|
|
167
|
+
| Tool | Description |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `connect` | Create persistent tmux workspace on remote host |
|
|
170
|
+
| `reconnect_to_tmux` | Reattach to existing workspace after disconnect |
|
|
171
|
+
| `exec` | Run command, wait for prompt, return output |
|
|
172
|
+
| `send_input` | Inject raw input (non-blocking) |
|
|
173
|
+
| `read_output` | Capture pane tail |
|
|
174
|
+
| `interrupt` | Ctrl-C / Ctrl-D signal |
|
|
175
|
+
| `disconnect` | Close session. Optionally kill tmux or keep alive |
|
|
176
|
+
| `list_hosts` | List `~/.ssh/config` aliases |
|
|
177
|
+
| `list_sessions` | List active workspaces |
|
|
178
|
+
|
|
179
|
+
### File Transfer (3)
|
|
180
|
+
|
|
181
|
+
| Tool | Description |
|
|
182
|
+
|---|---|
|
|
183
|
+
| `sftp_upload` | Upload file to remote |
|
|
184
|
+
| `sftp_download` | Download file from remote |
|
|
185
|
+
| `sftp_list` | List remote directory |
|
|
186
|
+
|
|
187
|
+
### Monitoring (3)
|
|
188
|
+
|
|
189
|
+
| Tool | Description |
|
|
190
|
+
|---|---|
|
|
191
|
+
| `connection_status` | SSH liveness + tmux existence |
|
|
192
|
+
| `health_check` | CPU / RAM / Disk / Load / Uptime |
|
|
193
|
+
| `tail_log` | Log tail with optional follow |
|
|
194
|
+
|
|
195
|
+
### DevOps (4)
|
|
196
|
+
|
|
197
|
+
| Tool | Description |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `deploy` | Upload → backup → chmod → chown → restart |
|
|
200
|
+
| `backup` | tar.gz archive → download → cleanup |
|
|
201
|
+
| `sync` | Rsync-lite via SFTP (bidirectional, dry-run) |
|
|
202
|
+
| `ssh_tunnel_open` / `ssh_tunnel_list` / `ssh_tunnel_close` | Port forwarding + SOCKS5 |
|
|
203
|
+
|
|
204
|
+
### Cluster & Queries (2)
|
|
205
|
+
|
|
206
|
+
| Tool | Description |
|
|
207
|
+
|---|---|
|
|
208
|
+
| `group_exec` | Run command across multiple workspaces (parallel/sequential) |
|
|
209
|
+
| `db_query` | Read-only MySQL / PostgreSQL / MongoDB via SSH |
|
|
210
|
+
|
|
211
|
+
### Runtime Config (2)
|
|
212
|
+
|
|
213
|
+
| Tool | Description |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `tools_config` | Enable/disable tools at runtime |
|
|
216
|
+
| `host_security` | Per-host read-only, command allow/denylist |
|
|
217
|
+
|
|
218
|
+
**Full reference:** [`docs/TOOLS.md`](docs/TOOLS.md) | **Security:** [`docs/SECURITY.md`](docs/SECURITY.md)
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Usage Examples
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
> connect host=prod
|
|
226
|
+
→ session_id: "sess_abc", tmux_session: "mcp_prod_x1y2z3"
|
|
227
|
+
|
|
228
|
+
> exec session_id=sess_abc command="cd /var/www && docker ps"
|
|
229
|
+
→ { output: "3 containers running" }
|
|
230
|
+
|
|
231
|
+
> exec session_id=sess_abc command="ls"
|
|
232
|
+
→ { output: "..." } (cwd is still /var/www)
|
|
233
|
+
|
|
234
|
+
> group_exec session_ids=["sess_abc","sess_def"] command="uptime"
|
|
235
|
+
→ [{ host: "prod", output: "up 14 days" }, { host: "staging", output: "up 3 days" }]
|
|
236
|
+
|
|
237
|
+
> health_check session_id=sess_abc
|
|
238
|
+
→ { cpu: 12%, memory: 45%, disk: [{ "/": 56% }], uptime: "14 days" }
|
|
239
|
+
|
|
240
|
+
> db_query session_id=sess_abc type=mysql database=mydb query="SELECT COUNT(*) FROM users"
|
|
241
|
+
→ [{ "COUNT(*)": 15423 }]
|
|
242
|
+
|
|
243
|
+
> deploy session_id=sess_abc files=[{"local":"dist/app.js","remote":"/var/www/app.js"}] backup=true chmod="755" restart_service="nginx"
|
|
244
|
+
|
|
245
|
+
> host_security action=set host=prod readonly=true
|
|
246
|
+
→ Host 'prod' locked to read-only
|
|
247
|
+
|
|
248
|
+
> tools_config disable backup
|
|
249
|
+
→ Tool 'backup' disabled. Removed from MCP tool list.
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Configuration
|
|
255
|
+
|
|
256
|
+
### Environment Variables
|
|
257
|
+
|
|
258
|
+
| Variable | Default | Description |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| `LOG_LEVEL` | `info` | `trace`, `debug`, `info`, `warn`, `error` |
|
|
261
|
+
| `MCP_SSH_READONLY` | `false` | Block all write operations globally |
|
|
262
|
+
| `MCP_SSH_ALLOWED_HOSTS` | `(all)` | Comma-separated host whitelist |
|
|
263
|
+
| `MCP_SSH_DENYLIST_COMMANDS` | `(none)` | Global command blocklist |
|
|
264
|
+
| `MCP_SSH_RESTORE_SESSIONS` | `true` | Auto-restore workspaces on startup |
|
|
265
|
+
|
|
266
|
+
### SSH Config
|
|
267
|
+
|
|
268
|
+
Hosts must be defined in `~/.ssh/config`:
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
Host prod
|
|
272
|
+
HostName 10.0.0.5
|
|
273
|
+
User deploy
|
|
274
|
+
IdentityFile ~/.ssh/id_ed25519
|
|
275
|
+
|
|
276
|
+
Host internal
|
|
277
|
+
HostName 172.16.0.50
|
|
278
|
+
User admin
|
|
279
|
+
ProxyJump bastion
|
|
280
|
+
|
|
281
|
+
Host bastion
|
|
282
|
+
HostName jump.example.com
|
|
283
|
+
User jumpuser
|
|
284
|
+
```
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Project Structure
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
src/
|
|
291
|
+
├── core/
|
|
292
|
+
│ ├── SessionManager.ts # Session registry + persistence
|
|
293
|
+
│ ├── SSHManager.ts # SSH2 connections, SFTP, proxy-jump
|
|
294
|
+
│ ├── TmuxManager.ts # Tmux operations (create, capture, signal)
|
|
295
|
+
│ ├── StorageManager.ts # Persistent session storage (JSON)
|
|
296
|
+
│ ├── ToolConfigManager.ts # Runtime tool enable/disable
|
|
297
|
+
│ └── HostSecurityManager.ts # Per-host security policies
|
|
298
|
+
├── tools/ # 25 MCP tool handlers
|
|
299
|
+
└── utils/ # Security, SSH config parsing, logging, validation
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Troubleshooting
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
ssh-add -l # Verify SSH agent key
|
|
308
|
+
ssh <alias> # Test manual login
|
|
309
|
+
chmod 600 ~/.ssh/id_* # Fix key permissions
|
|
310
|
+
|
|
311
|
+
# Clean stale workspaces on remote
|
|
312
|
+
tmux ls | grep '^mcp_' | awk -F: '{print $1}' | xargs -I{} tmux kill-session -t {}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## License
|
|
318
|
+
|
|
319
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SSHManager.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/SSHManager.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { SSHManager } from '../core/SSHManager.js';
|
|
3
|
+
function createMockSftp() {
|
|
4
|
+
return {
|
|
5
|
+
fastPut: vi.fn(),
|
|
6
|
+
fastGet: vi.fn(),
|
|
7
|
+
readdir: vi.fn(),
|
|
8
|
+
stat: vi.fn(),
|
|
9
|
+
mkdir: vi.fn(),
|
|
10
|
+
unlink: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function createMockSsh(sftp) {
|
|
14
|
+
return {
|
|
15
|
+
sftp: vi.fn((cb) => {
|
|
16
|
+
cb(undefined, sftp);
|
|
17
|
+
}),
|
|
18
|
+
exec: vi.fn(),
|
|
19
|
+
on: vi.fn(),
|
|
20
|
+
connect: vi.fn(),
|
|
21
|
+
destroy: vi.fn(),
|
|
22
|
+
removeAllListeners: vi.fn(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
describe('SSHManager SFTP', () => {
|
|
26
|
+
let sshManager;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
sshManager = new SSHManager();
|
|
29
|
+
});
|
|
30
|
+
describe('sftpUpload', () => {
|
|
31
|
+
it('should upload a file successfully', async () => {
|
|
32
|
+
const sftp = createMockSftp();
|
|
33
|
+
sftp.fastPut.mockImplementation((local, remote, cb) => {
|
|
34
|
+
cb(undefined);
|
|
35
|
+
});
|
|
36
|
+
const ssh = createMockSsh(sftp);
|
|
37
|
+
await expect(sshManager.sftpUpload(ssh, '/local/file.txt', '/remote/file.txt')).resolves.not.toThrow();
|
|
38
|
+
expect(sftp.fastPut).toHaveBeenCalledWith('/local/file.txt', '/remote/file.txt', expect.any(Function));
|
|
39
|
+
});
|
|
40
|
+
it('should reject on SFTP error', async () => {
|
|
41
|
+
const sftp = createMockSftp();
|
|
42
|
+
sftp.fastPut.mockImplementation((_l, _r, cb) => {
|
|
43
|
+
cb(new Error('Upload failed'));
|
|
44
|
+
});
|
|
45
|
+
const ssh = createMockSsh(sftp);
|
|
46
|
+
await expect(sshManager.sftpUpload(ssh, '/local/file.txt', '/remote/file.txt')).rejects.toThrow('Upload failed');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('sftpDownload', () => {
|
|
50
|
+
it('should download a file successfully', async () => {
|
|
51
|
+
const sftp = createMockSftp();
|
|
52
|
+
sftp.fastGet.mockImplementation((remote, local, cb) => {
|
|
53
|
+
cb(undefined);
|
|
54
|
+
});
|
|
55
|
+
const ssh = createMockSsh(sftp);
|
|
56
|
+
await expect(sshManager.sftpDownload(ssh, '/remote/file.txt', '/local/file.txt')).resolves.not.toThrow();
|
|
57
|
+
expect(sftp.fastGet).toHaveBeenCalledWith('/remote/file.txt', '/local/file.txt', expect.any(Function));
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('sftpList', () => {
|
|
61
|
+
it('should list directory entries', async () => {
|
|
62
|
+
const sftp = createMockSftp();
|
|
63
|
+
sftp.readdir.mockImplementation((_path, cb) => {
|
|
64
|
+
cb(undefined, [
|
|
65
|
+
{ filename: 'file.txt', longname: '-rw-r--r-- 1 user group 100 Jan 1 2024 file.txt', attrs: { mode: 0o100644, size: 100, uid: 1000, gid: 1000, atime: 1700000000, mtime: 1700000000 } },
|
|
66
|
+
{ filename: 'dir', longname: 'drwxr-xr-x 1 user group 0 Jan 1 2024 dir', attrs: { mode: 0o040755, size: 0, uid: 1000, gid: 1000, atime: 1700000000, mtime: 1700000000 } },
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
const ssh = createMockSsh(sftp);
|
|
70
|
+
const entries = await sshManager.sftpList(ssh, '/remote');
|
|
71
|
+
expect(entries).toHaveLength(2);
|
|
72
|
+
expect(entries[0].filename).toBe('file.txt');
|
|
73
|
+
expect(entries[0].attrs.isFile).toBe(true);
|
|
74
|
+
expect(entries[0].attrs.isDirectory).toBe(false);
|
|
75
|
+
expect(entries[1].filename).toBe('dir');
|
|
76
|
+
expect(entries[1].attrs.isDirectory).toBe(true);
|
|
77
|
+
expect(entries[1].attrs.isFile).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('sftpStat', () => {
|
|
81
|
+
it('should return file stats', async () => {
|
|
82
|
+
const sftp = createMockSftp();
|
|
83
|
+
sftp.stat.mockImplementation((_path, cb) => {
|
|
84
|
+
cb(undefined, { mode: 0o100644, size: 1024, uid: 1000, gid: 1000, atime: 1700000000, mtime: 1700000000 });
|
|
85
|
+
});
|
|
86
|
+
const ssh = createMockSsh(sftp);
|
|
87
|
+
const stats = await sshManager.sftpStat(ssh, '/remote/file.txt');
|
|
88
|
+
expect(stats.size).toBe(1024);
|
|
89
|
+
expect(stats.isFile).toBe(true);
|
|
90
|
+
expect(stats.isDirectory).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('sftpExists', () => {
|
|
94
|
+
it('should return true when file exists', async () => {
|
|
95
|
+
const sftp = createMockSftp();
|
|
96
|
+
sftp.stat.mockImplementation((_path, cb) => {
|
|
97
|
+
cb(undefined, { mode: 0o100644, size: 0, uid: 0, gid: 0, atime: 0, mtime: 0 });
|
|
98
|
+
});
|
|
99
|
+
const ssh = createMockSsh(sftp);
|
|
100
|
+
const exists = await sshManager.sftpExists(ssh, '/remote/file.txt');
|
|
101
|
+
expect(exists).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
it('should return false when file does not exist', async () => {
|
|
104
|
+
const sftp = createMockSftp();
|
|
105
|
+
sftp.stat.mockImplementation((_path, cb) => {
|
|
106
|
+
cb(new Error('No such file'), undefined);
|
|
107
|
+
});
|
|
108
|
+
const ssh = createMockSsh(sftp);
|
|
109
|
+
const exists = await sshManager.sftpExists(ssh, '/remote/nonexistent.txt');
|
|
110
|
+
expect(exists).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('sftpMkdir', () => {
|
|
114
|
+
it('should create directory', async () => {
|
|
115
|
+
const sftp = createMockSftp();
|
|
116
|
+
sftp.mkdir.mockImplementation((_path, _opts, cb) => {
|
|
117
|
+
cb(undefined);
|
|
118
|
+
});
|
|
119
|
+
const ssh = createMockSsh(sftp);
|
|
120
|
+
await expect(sshManager.sftpMkdir(ssh, '/remote/newdir')).resolves.not.toThrow();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe('sftpUnlink', () => {
|
|
124
|
+
it('should delete file', async () => {
|
|
125
|
+
const sftp = createMockSftp();
|
|
126
|
+
sftp.unlink.mockImplementation((_path, cb) => {
|
|
127
|
+
cb(undefined);
|
|
128
|
+
});
|
|
129
|
+
const ssh = createMockSsh(sftp);
|
|
130
|
+
await expect(sshManager.sftpUnlink(ssh, '/remote/file.txt')).resolves.not.toThrow();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=SSHManager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SSHManager.test.js","sourceRoot":"","sources":["../../src/__tests__/SSHManager.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionManager.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/SessionManager.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { SessionManager } from '../core/SessionManager.js';
|
|
3
|
+
import { StorageManager } from '../core/StorageManager.js';
|
|
4
|
+
function createMockSsh() {
|
|
5
|
+
const listeners = {};
|
|
6
|
+
return {
|
|
7
|
+
on: (event, cb) => {
|
|
8
|
+
if (!listeners[event])
|
|
9
|
+
listeners[event] = [];
|
|
10
|
+
listeners[event].push(cb);
|
|
11
|
+
},
|
|
12
|
+
emit: (event, ...args) => {
|
|
13
|
+
if (listeners[event]) {
|
|
14
|
+
listeners[event].forEach((cb) => cb(...args));
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
destroy: () => {
|
|
18
|
+
if (listeners['close']) {
|
|
19
|
+
listeners['close'].forEach((cb) => cb());
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
removeAllListeners: (event) => {
|
|
23
|
+
delete listeners[event];
|
|
24
|
+
},
|
|
25
|
+
listeners,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
describe('SessionManager', () => {
|
|
29
|
+
let storage;
|
|
30
|
+
let sessionManager;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
storage = new StorageManager();
|
|
33
|
+
sessionManager = new SessionManager(storage);
|
|
34
|
+
});
|
|
35
|
+
it('should create a session with auto-generated ID', () => {
|
|
36
|
+
const ssh = createMockSsh();
|
|
37
|
+
const session = sessionManager.create('prod', ssh, 'mcp_prod_abc', 'bash');
|
|
38
|
+
expect(session.id).toMatch(/^sess_[a-f0-9]{32}$/);
|
|
39
|
+
expect(session.host).toBe('prod');
|
|
40
|
+
expect(session.tmuxSession).toBe('mcp_prod_abc');
|
|
41
|
+
expect(session.shell).toBe('bash');
|
|
42
|
+
expect(session.connectedAt).toBeGreaterThan(0);
|
|
43
|
+
expect(session.lastActivity).toBeGreaterThan(0);
|
|
44
|
+
});
|
|
45
|
+
it('should create a session with existing ID', () => {
|
|
46
|
+
const ssh = createMockSsh();
|
|
47
|
+
const session = sessionManager.create('prod', ssh, 'mcp_prod_abc', 'bash', 'sess_existing123');
|
|
48
|
+
expect(session.id).toBe('sess_existing123');
|
|
49
|
+
});
|
|
50
|
+
it('should get session by ID', () => {
|
|
51
|
+
const ssh = createMockSsh();
|
|
52
|
+
const session = sessionManager.create('staging', ssh, 'mcp_staging_xyz');
|
|
53
|
+
const retrieved = sessionManager.get(session.id);
|
|
54
|
+
expect(retrieved).toBeDefined();
|
|
55
|
+
expect(retrieved.host).toBe('staging');
|
|
56
|
+
expect(retrieved.tmuxSession).toBe('mcp_staging_xyz');
|
|
57
|
+
});
|
|
58
|
+
it('should return undefined for non-existent session', () => {
|
|
59
|
+
const result = sessionManager.get('sess_nonexistent');
|
|
60
|
+
expect(result).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
it('should update lastActivity on get', () => {
|
|
63
|
+
const ssh = createMockSsh();
|
|
64
|
+
const session = sessionManager.create('prod', ssh, 'mcp_prod');
|
|
65
|
+
const before = session.lastActivity;
|
|
66
|
+
// Wait 10ms to ensure timestamp changes
|
|
67
|
+
const retrieved = sessionManager.get(session.id);
|
|
68
|
+
// get() should update lastActivity
|
|
69
|
+
expect(session.lastActivity).toBeGreaterThanOrEqual(before);
|
|
70
|
+
});
|
|
71
|
+
it('should remove session and destroy SSH', () => {
|
|
72
|
+
const ssh = createMockSsh();
|
|
73
|
+
let destroyed = false;
|
|
74
|
+
ssh.destroy = () => { destroyed = true; };
|
|
75
|
+
const session = sessionManager.create('prod', ssh, 'mcp_prod');
|
|
76
|
+
const removed = sessionManager.remove(session.id);
|
|
77
|
+
expect(removed).toBe(true);
|
|
78
|
+
expect(destroyed).toBe(true);
|
|
79
|
+
expect(sessionManager.has(session.id)).toBe(false);
|
|
80
|
+
expect(sessionManager.get(session.id)).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
it('should return false when removing non-existent session', () => {
|
|
83
|
+
const result = sessionManager.remove('sess_nonexistent');
|
|
84
|
+
expect(result).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
it('should auto-remove session when SSH closes', () => {
|
|
87
|
+
const ssh = createMockSsh();
|
|
88
|
+
const session = sessionManager.create('prod', ssh, 'mcp_prod');
|
|
89
|
+
expect(sessionManager.has(session.id)).toBe(true);
|
|
90
|
+
// Simulate SSH close event
|
|
91
|
+
ssh.emit('close');
|
|
92
|
+
expect(sessionManager.has(session.id)).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
it('should list all sessions', () => {
|
|
95
|
+
const ssh1 = createMockSsh();
|
|
96
|
+
const ssh2 = createMockSsh();
|
|
97
|
+
sessionManager.create('prod', ssh1, 'mcp_prod_1');
|
|
98
|
+
sessionManager.create('staging', ssh2, 'mcp_staging_1');
|
|
99
|
+
const list = sessionManager.list();
|
|
100
|
+
expect(list).toHaveLength(2);
|
|
101
|
+
expect(list[0]).toHaveProperty('id');
|
|
102
|
+
expect(list[0]).toHaveProperty('host');
|
|
103
|
+
expect(list[0]).toHaveProperty('connectedAt');
|
|
104
|
+
expect(list[0]).toHaveProperty('lastActivity');
|
|
105
|
+
expect(list[0]).toHaveProperty('tmuxSession');
|
|
106
|
+
});
|
|
107
|
+
it('should disconnect all sessions', () => {
|
|
108
|
+
const ssh1 = createMockSsh();
|
|
109
|
+
const ssh2 = createMockSsh();
|
|
110
|
+
sessionManager.create('prod', ssh1, 'mcp_prod_1');
|
|
111
|
+
sessionManager.create('staging', ssh2, 'mcp_staging_1');
|
|
112
|
+
expect(sessionManager.count).toBe(2);
|
|
113
|
+
sessionManager.disconnectAll();
|
|
114
|
+
expect(sessionManager.count).toBe(0);
|
|
115
|
+
});
|
|
116
|
+
it('should track session count', () => {
|
|
117
|
+
expect(sessionManager.count).toBe(0);
|
|
118
|
+
const ssh1 = createMockSsh();
|
|
119
|
+
sessionManager.create('prod', ssh1, 'mcp_prod_1');
|
|
120
|
+
expect(sessionManager.count).toBe(1);
|
|
121
|
+
const ssh2 = createMockSsh();
|
|
122
|
+
sessionManager.create('staging', ssh2, 'mcp_staging_1');
|
|
123
|
+
expect(sessionManager.count).toBe(2);
|
|
124
|
+
});
|
|
125
|
+
it('should persist session to storage on create', () => {
|
|
126
|
+
const ssh = createMockSsh();
|
|
127
|
+
const session = sessionManager.create('prod', ssh, 'mcp_prod_abc', 'bash');
|
|
128
|
+
const stored = storage.get(session.id);
|
|
129
|
+
expect(stored).toBeDefined();
|
|
130
|
+
expect(stored.host).toBe('prod');
|
|
131
|
+
expect(stored.tmuxSession).toBe('mcp_prod_abc');
|
|
132
|
+
expect(stored.shell).toBe('bash');
|
|
133
|
+
});
|
|
134
|
+
it('should remove session from storage on remove', () => {
|
|
135
|
+
const ssh = createMockSsh();
|
|
136
|
+
const session = sessionManager.create('prod', ssh, 'mcp_prod_abc');
|
|
137
|
+
sessionManager.remove(session.id);
|
|
138
|
+
expect(storage.get(session.id)).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=SessionManager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionManager.test.js","sourceRoot":"","sources":["../../src/__tests__/SessionManager.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,SAAS,aAAa;IACpB,MAAM,SAAS,GAAoD,EAAE,CAAC;IACtE,OAAO;QACL,EAAE,EAAE,CAAC,KAAa,EAAE,EAA4B,EAAE,EAAE;YAClD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;gBAAE,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;YAC7C,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,EAAE,CAAC,KAAa,EAAE,GAAG,IAAW,EAAE,EAAE;YACtC,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrB,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;QACD,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvB,SAAS,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QACD,kBAAkB,EAAE,CAAC,KAAa,EAAE,EAAE;YACpC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QACD,SAAS;KACV,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,IAAI,OAAuB,CAAC;IAC5B,IAAI,cAA8B,CAAC;IAEnC,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,IAAI,cAAc,EAAE,CAAC;QAC/B,cAAc,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,GAAU,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QAElF,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;QAClD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACjD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,GAAU,EAAE,cAAc,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC;QAEtG,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,SAAS,EAAE,GAAU,EAAE,iBAAiB,CAAC,CAAC;QAEhF,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;QAChC,MAAM,CAAC,SAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxC,MAAM,CAAC,SAAU,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACtD,MAAM,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,GAAU,EAAE,UAAU,CAAC,CAAC;QAEtE,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QACpC,wCAAwC;QACxC,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjD,mCAAmC;QACnC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAE1C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,GAAU,EAAE,UAAU,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAElD,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QACzD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,GAAU,EAAE,UAAU,CAAC,CAAC;QAEtE,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAElD,2BAA2B;QAC3B,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAElB,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;QAC7B,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,IAAW,EAAE,YAAY,CAAC,CAAC;QACzD,cAAc,CAAC,MAAM,CAAC,SAAS,EAAE,IAAW,EAAE,eAAe,CAAC,CAAC;QAE/D,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC9C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;QAC7B,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,IAAW,EAAE,YAAY,CAAC,CAAC;QACzD,cAAc,CAAC,MAAM,CAAC,SAAS,EAAE,IAAW,EAAE,eAAe,CAAC,CAAC;QAE/D,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAErC,cAAc,CAAC,aAAa,EAAE,CAAC;QAE/B,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAErC,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;QAC7B,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,IAAW,EAAE,YAAY,CAAC,CAAC;QACzD,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAErC,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;QAC7B,cAAc,CAAC,MAAM,CAAC,SAAS,EAAE,IAAW,EAAE,eAAe,CAAC,CAAC;QAC/D,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,GAAU,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QAElF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,MAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,CAAC,MAAO,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACjD,MAAM,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,GAAU,EAAE,cAAc,CAAC,CAAC;QAE1E,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageManager.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/StorageManager.test.ts"],"names":[],"mappings":""}
|