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.
Files changed (188) hide show
  1. package/README.md +319 -0
  2. package/dist/__tests__/SSHManager.test.d.ts +2 -0
  3. package/dist/__tests__/SSHManager.test.d.ts.map +1 -0
  4. package/dist/__tests__/SSHManager.test.js +134 -0
  5. package/dist/__tests__/SSHManager.test.js.map +1 -0
  6. package/dist/__tests__/SessionManager.test.d.ts +2 -0
  7. package/dist/__tests__/SessionManager.test.d.ts.map +1 -0
  8. package/dist/__tests__/SessionManager.test.js +141 -0
  9. package/dist/__tests__/SessionManager.test.js.map +1 -0
  10. package/dist/__tests__/StorageManager.test.d.ts +2 -0
  11. package/dist/__tests__/StorageManager.test.d.ts.map +1 -0
  12. package/dist/__tests__/StorageManager.test.js +171 -0
  13. package/dist/__tests__/StorageManager.test.js.map +1 -0
  14. package/dist/__tests__/ansi.test.d.ts +2 -0
  15. package/dist/__tests__/ansi.test.d.ts.map +1 -0
  16. package/dist/__tests__/ansi.test.js +41 -0
  17. package/dist/__tests__/ansi.test.js.map +1 -0
  18. package/dist/__tests__/security.test.d.ts +2 -0
  19. package/dist/__tests__/security.test.d.ts.map +1 -0
  20. package/dist/__tests__/security.test.js +87 -0
  21. package/dist/__tests__/security.test.js.map +1 -0
  22. package/dist/__tests__/validation.test.d.ts +2 -0
  23. package/dist/__tests__/validation.test.d.ts.map +1 -0
  24. package/dist/__tests__/validation.test.js +23 -0
  25. package/dist/__tests__/validation.test.js.map +1 -0
  26. package/dist/core/HostSecurityManager.d.ts +25 -0
  27. package/dist/core/HostSecurityManager.d.ts.map +1 -0
  28. package/dist/core/HostSecurityManager.js +76 -0
  29. package/dist/core/HostSecurityManager.js.map +1 -0
  30. package/dist/core/SSHManager.d.ts +48 -0
  31. package/dist/core/SSHManager.d.ts.map +1 -0
  32. package/dist/core/SSHManager.js +288 -0
  33. package/dist/core/SSHManager.js.map +1 -0
  34. package/dist/core/SessionManager.d.ts +15 -0
  35. package/dist/core/SessionManager.d.ts.map +1 -0
  36. package/dist/core/SessionManager.js +96 -0
  37. package/dist/core/SessionManager.js.map +1 -0
  38. package/dist/core/StorageManager.d.ts +27 -0
  39. package/dist/core/StorageManager.d.ts.map +1 -0
  40. package/dist/core/StorageManager.js +87 -0
  41. package/dist/core/StorageManager.js.map +1 -0
  42. package/dist/core/TmuxManager.d.ts +21 -0
  43. package/dist/core/TmuxManager.d.ts.map +1 -0
  44. package/dist/core/TmuxManager.js +110 -0
  45. package/dist/core/TmuxManager.js.map +1 -0
  46. package/dist/core/ToolConfigManager.d.ts +15 -0
  47. package/dist/core/ToolConfigManager.d.ts.map +1 -0
  48. package/dist/core/ToolConfigManager.js +57 -0
  49. package/dist/core/ToolConfigManager.js.map +1 -0
  50. package/dist/index.d.ts +3 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +169 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/server.d.ts +44 -0
  55. package/dist/server.d.ts.map +1 -0
  56. package/dist/server.js +152 -0
  57. package/dist/server.js.map +1 -0
  58. package/dist/tools/backup.d.ts +74 -0
  59. package/dist/tools/backup.d.ts.map +1 -0
  60. package/dist/tools/backup.js +152 -0
  61. package/dist/tools/backup.js.map +1 -0
  62. package/dist/tools/connect.d.ts +46 -0
  63. package/dist/tools/connect.d.ts.map +1 -0
  64. package/dist/tools/connect.js +235 -0
  65. package/dist/tools/connect.js.map +1 -0
  66. package/dist/tools/connection_status.d.ts +39 -0
  67. package/dist/tools/connection_status.d.ts.map +1 -0
  68. package/dist/tools/connection_status.js +67 -0
  69. package/dist/tools/connection_status.js.map +1 -0
  70. package/dist/tools/db_query.d.ts +103 -0
  71. package/dist/tools/db_query.d.ts.map +1 -0
  72. package/dist/tools/db_query.js +194 -0
  73. package/dist/tools/db_query.js.map +1 -0
  74. package/dist/tools/deploy.d.ts +127 -0
  75. package/dist/tools/deploy.d.ts.map +1 -0
  76. package/dist/tools/deploy.js +201 -0
  77. package/dist/tools/deploy.js.map +1 -0
  78. package/dist/tools/disconnect.d.ts +46 -0
  79. package/dist/tools/disconnect.d.ts.map +1 -0
  80. package/dist/tools/disconnect.js +77 -0
  81. package/dist/tools/disconnect.js.map +1 -0
  82. package/dist/tools/exec.d.ts +69 -0
  83. package/dist/tools/exec.d.ts.map +1 -0
  84. package/dist/tools/exec.js +188 -0
  85. package/dist/tools/exec.js.map +1 -0
  86. package/dist/tools/group_exec.d.ts +80 -0
  87. package/dist/tools/group_exec.d.ts.map +1 -0
  88. package/dist/tools/group_exec.js +150 -0
  89. package/dist/tools/group_exec.js.map +1 -0
  90. package/dist/tools/health_check.d.ts +38 -0
  91. package/dist/tools/health_check.d.ts.map +1 -0
  92. package/dist/tools/health_check.js +161 -0
  93. package/dist/tools/health_check.js.map +1 -0
  94. package/dist/tools/host_security.d.ts +52 -0
  95. package/dist/tools/host_security.d.ts.map +1 -0
  96. package/dist/tools/host_security.js +127 -0
  97. package/dist/tools/host_security.js.map +1 -0
  98. package/dist/tools/index.d.ts +24 -0
  99. package/dist/tools/index.d.ts.map +1 -0
  100. package/dist/tools/index.js +24 -0
  101. package/dist/tools/index.js.map +1 -0
  102. package/dist/tools/interrupt.d.ts +47 -0
  103. package/dist/tools/interrupt.d.ts.map +1 -0
  104. package/dist/tools/interrupt.js +77 -0
  105. package/dist/tools/interrupt.js.map +1 -0
  106. package/dist/tools/list_hosts.d.ts +15 -0
  107. package/dist/tools/list_hosts.d.ts.map +1 -0
  108. package/dist/tools/list_hosts.js +18 -0
  109. package/dist/tools/list_hosts.js.map +1 -0
  110. package/dist/tools/list_sessions.d.ts +16 -0
  111. package/dist/tools/list_sessions.d.ts.map +1 -0
  112. package/dist/tools/list_sessions.js +20 -0
  113. package/dist/tools/list_sessions.js.map +1 -0
  114. package/dist/tools/read_output.d.ts +46 -0
  115. package/dist/tools/read_output.d.ts.map +1 -0
  116. package/dist/tools/read_output.js +73 -0
  117. package/dist/tools/read_output.js.map +1 -0
  118. package/dist/tools/reconnect_to_tmux.d.ts +53 -0
  119. package/dist/tools/reconnect_to_tmux.d.ts.map +1 -0
  120. package/dist/tools/reconnect_to_tmux.js +199 -0
  121. package/dist/tools/reconnect_to_tmux.js.map +1 -0
  122. package/dist/tools/send_input.d.ts +45 -0
  123. package/dist/tools/send_input.d.ts.map +1 -0
  124. package/dist/tools/send_input.js +83 -0
  125. package/dist/tools/send_input.js.map +1 -0
  126. package/dist/tools/sftp_download.d.ts +52 -0
  127. package/dist/tools/sftp_download.d.ts.map +1 -0
  128. package/dist/tools/sftp_download.js +90 -0
  129. package/dist/tools/sftp_download.js.map +1 -0
  130. package/dist/tools/sftp_list.d.ts +46 -0
  131. package/dist/tools/sftp_list.d.ts.map +1 -0
  132. package/dist/tools/sftp_list.js +93 -0
  133. package/dist/tools/sftp_list.js.map +1 -0
  134. package/dist/tools/sftp_upload.d.ts +52 -0
  135. package/dist/tools/sftp_upload.d.ts.map +1 -0
  136. package/dist/tools/sftp_upload.js +98 -0
  137. package/dist/tools/sftp_upload.js.map +1 -0
  138. package/dist/tools/ssh_tunnel.d.ts +116 -0
  139. package/dist/tools/ssh_tunnel.d.ts.map +1 -0
  140. package/dist/tools/ssh_tunnel.js +282 -0
  141. package/dist/tools/ssh_tunnel.js.map +1 -0
  142. package/dist/tools/sync.d.ts +71 -0
  143. package/dist/tools/sync.d.ts.map +1 -0
  144. package/dist/tools/sync.js +310 -0
  145. package/dist/tools/sync.js.map +1 -0
  146. package/dist/tools/tail_log.d.ts +61 -0
  147. package/dist/tools/tail_log.d.ts.map +1 -0
  148. package/dist/tools/tail_log.js +111 -0
  149. package/dist/tools/tail_log.js.map +1 -0
  150. package/dist/tools/tools_config.d.ts +34 -0
  151. package/dist/tools/tools_config.d.ts.map +1 -0
  152. package/dist/tools/tools_config.js +98 -0
  153. package/dist/tools/tools_config.js.map +1 -0
  154. package/dist/types/index.d.ts +21 -0
  155. package/dist/types/index.d.ts.map +1 -0
  156. package/dist/types/index.js +2 -0
  157. package/dist/types/index.js.map +1 -0
  158. package/dist/utils/ansi.d.ts +2 -0
  159. package/dist/utils/ansi.d.ts.map +1 -0
  160. package/dist/utils/ansi.js +7 -0
  161. package/dist/utils/ansi.js.map +1 -0
  162. package/dist/utils/logger.d.ts +3 -0
  163. package/dist/utils/logger.d.ts.map +1 -0
  164. package/dist/utils/logger.js +8 -0
  165. package/dist/utils/logger.js.map +1 -0
  166. package/dist/utils/security.d.ts +7 -0
  167. package/dist/utils/security.d.ts.map +1 -0
  168. package/dist/utils/security.js +58 -0
  169. package/dist/utils/security.js.map +1 -0
  170. package/dist/utils/ssh.d.ts +4 -0
  171. package/dist/utils/ssh.d.ts.map +1 -0
  172. package/dist/utils/ssh.js +29 -0
  173. package/dist/utils/ssh.js.map +1 -0
  174. package/dist/utils/sshConfig.d.ts +4 -0
  175. package/dist/utils/sshConfig.d.ts.map +1 -0
  176. package/dist/utils/sshConfig.js +85 -0
  177. package/dist/utils/sshConfig.js.map +1 -0
  178. package/dist/utils/validation.d.ts +4 -0
  179. package/dist/utils/validation.d.ts.map +1 -0
  180. package/dist/utils/validation.js +12 -0
  181. package/dist/utils/validation.js.map +1 -0
  182. package/docs/SECURITY.md +213 -0
  183. package/docs/TOOLS.md +425 -0
  184. package/keygen.bat +325 -0
  185. package/package.json +48 -0
  186. package/test_check.bat +9 -0
  187. package/test_delayed.bat +12 -0
  188. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=SSHManager.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=SessionManager.test.d.ts.map
@@ -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":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=StorageManager.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageManager.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/StorageManager.test.ts"],"names":[],"mappings":""}