enterprise-ssh-mcp-server 0.3.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 ADDED
@@ -0,0 +1,266 @@
1
+ # Enterprise SSH MCP Server
2
+
3
+ 一个基于 `@modelcontextprotocol/sdk` 和 `ssh2` 的 SSH/SFTP MCP stdio server。它把 SSH 连接作为 MCP server 进程内的持久会话保存,支持复用同一个连接执行命令、交互 shell、SFTP 文件管理、大文件后台传输和端口转发。
4
+
5
+ ## 安装与使用
6
+
7
+ 推荐直接通过 npm 启动,无需全局安装。
8
+
9
+ Windows MCP client 配置:
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "enterprise-ssh": {
15
+ "command": "cmd",
16
+ "args": ["/c", "npx", "-y", "enterprise-ssh-mcp-server"]
17
+ }
18
+ }
19
+ }
20
+ ```
21
+
22
+ macOS / Linux MCP client 配置:
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "enterprise-ssh": {
28
+ "command": "npx",
29
+ "args": ["-y", "enterprise-ssh-mcp-server"]
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ 如果已经全局安装:
36
+
37
+ ```powershell
38
+ npm install -g enterprise-ssh-mcp-server
39
+ ```
40
+
41
+ Windows 推荐通过 `cmd /c` 启动 npm 生成的 `.cmd` 命令入口,避免 PowerShell 执行策略影响:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "enterprise-ssh": {
47
+ "command": "cmd",
48
+ "args": ["/c", "enterprise-ssh-mcp"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ macOS / Linux 全局安装后可以直接使用命令入口:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "enterprise-ssh": {
60
+ "command": "enterprise-ssh-mcp",
61
+ "args": []
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ 如需指定 SSH profile 配置文件:
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "enterprise-ssh": {
73
+ "command": "cmd",
74
+ "args": ["/c", "enterprise-ssh-mcp"],
75
+ "env": {
76
+ "SSH_MCP_ENV_FILE": "C:\\Users\\yourname\\.enterprise-ssh-mcp\\.env"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## 本地开发
84
+
85
+ ```powershell
86
+ npm install
87
+ npm run build
88
+ ```
89
+
90
+ 开发期可以用:
91
+
92
+ ```json
93
+ {
94
+ "mcpServers": {
95
+ "enterprise-ssh": {
96
+ "command": "npm",
97
+ "args": ["--prefix", "D:/studyProject/skills/ssh", "run", "dev"]
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## 能力范围
104
+
105
+ - Profile:`ssh_list_profiles`、`ssh_reload_profiles`、`ssh_connect_profile`、`ssh_check_profile`、`ssh_run_profile`
106
+ - 会话:`ssh_connect`、`ssh_list_sessions`、`ssh_disconnect`、`ssh_disconnect_all`、`ssh_rekey`
107
+ - 命令:`ssh_exec`,支持 env、stdin、PTY、超时、stdout/stderr 最大字节限制、UTF-8/base64 输出
108
+ - 远程项目编辑:`ssh_project_open`、`ssh_project_tree`、`ssh_project_search`、`ssh_project_stat`、`ssh_file_read`、`ssh_file_patch`、`ssh_project_validate_patch`、`ssh_project_apply_patch`、`ssh_project_diff`、`ssh_project_run_checks`、`ssh_edit_history`、`ssh_edit_rollback`
109
+ - 交互 shell:`ssh_shell_open`、`ssh_shell_write`、`ssh_shell_read`、`ssh_shell_resize`、`ssh_shell_close`、`ssh_shell_list`
110
+ - SFTP 管理:`sftp_list`、`sftp_stat`、`sftp_read_file`、`sftp_write_file`、`sftp_mkdir`、`sftp_rm`、`sftp_rename`、`sftp_chmod`、`sftp_chown`、`sftp_touch`、`sftp_symlink`、`sftp_readlink`、`sftp_realpath`
111
+ - 大文件传输:`sftp_upload_start`、`sftp_upload_profile`、`sftp_download_start`、`sftp_download_profile`、`sftp_transfer_status`、`sftp_transfer_list`、`sftp_transfer_cancel`
112
+ - 端口转发:`ssh_tunnel_local_start`、`ssh_tunnel_remote_start`、`ssh_tunnel_list`、`ssh_tunnel_stop`
113
+
114
+ ## Agent 使用建议
115
+
116
+ - 远端健康检查、环境确认、服务器巡检:优先使用 `ssh_check_profile`。
117
+ - 远程查看/修改项目代码:优先使用 `ssh_project_tree`、`ssh_project_search`、`ssh_file_read`、`ssh_file_patch`、`ssh_project_apply_patch`、`ssh_project_diff`,不要用 shell 拼 `sed`、`cat > file` 来改代码。
118
+ - 单条远端命令:优先使用 `ssh_run_profile`,避免先 connect 再 exec。
119
+ - 需要连续多步操作、交互 shell、SFTP 批量操作、后台传输或 tunnel 时,再使用 `ssh_connect_profile` 创建持久会话。
120
+ - 不要用本地 shell 模拟远端检查;已经配置 SSH profile 时,应调用 MCP SSH 工具。
121
+ - 默认 `agent` 模式会隐藏 tunnel、裸 SFTP 写入/上传/删除/移动、chmod、chown、disconnect_all 等高风险工具;如确实需要,显式设置 `SSH_MCP_TOOLSET=full` 和 `SSH_MCP_ENABLE_DANGEROUS_TOOLS=true` 后重启 MCP client。
122
+
123
+ ## 远程项目编辑
124
+
125
+ 编辑工具以 profile 的 `EDIT_ROOT` 或 `DEFAULT_DIR` 作为硬安全边界,`projectRoot` 只能是这个边界内的子目录。写入和 patch 默认要求 `expectedSha256`,也就是先 `ssh_file_read` 读到文件指纹,再带着这个指纹修改,防止覆盖别人刚改过的文件。
126
+
127
+ 推荐工作流:
128
+
129
+ 1. `ssh_project_open`:确认远程项目根目录、git 状态和项目类型。
130
+ 2. `ssh_project_tree` / `ssh_project_search`:定位要修改的代码。
131
+ 3. `ssh_file_read`:读取目标文件,拿到 `sha256`。
132
+ 4. `ssh_file_patch` 或 `ssh_project_apply_patch`:应用单文件或多文件 unified diff。
133
+ 5. `ssh_project_diff`:查看远程 git diff。
134
+ 6. `ssh_project_run_checks`:运行测试、构建或 lint。
135
+ 7. 如需撤回,用 `ssh_edit_history` 找到 `editId`,再调用 `ssh_edit_rollback`。
136
+
137
+ 写入类工具会先保存备份,再用临时文件 + rename 方式原子替换。`ssh_file_write`、`ssh_file_delete`、`ssh_file_move` 属于高风险工具,默认隐藏;常规代码修改应优先使用 patch 工具。
138
+
139
+ ## 大文件上传策略
140
+
141
+ 大文件不要用 `sftp_write_file`。请使用 `sftp_upload_start`:
142
+
143
+ 1. 工具会立即返回 `transfer.id`,实际上传在后台继续跑,避免 MCP 单次调用因为传输时间太长而超时。
144
+ 2. 用 `sftp_transfer_status` 轮询进度,返回 `transferredBytes`、`totalBytes`、`percent`、`bytesPerSecond`、`etaSeconds`、`state`。
145
+ 3. 默认 `resume=true`,如果远端已有部分文件,会从远端大小对应的 offset 继续写。
146
+ 4. `sftp_transfer_cancel` 会销毁本地/远端 stream,状态变成 `cancelled`。
147
+ 5. `atomic=false` 适合可续传的大文件;`atomic=true` 会先写临时路径,成功后 rename 到目标路径,适合发布工件,但续传时要保留同一个 `remoteTempPath`。
148
+
149
+ 下载同理,使用 `sftp_download_start`。默认会创建本地父目录,并支持从本地已有文件大小处继续下载。
150
+
151
+ ## .env SSH Profile
152
+
153
+ MCP server 启动时会读取项目根目录的 `.env`。如果你的 MCP client 从其他目录启动,也可以用 `SSH_MCP_ENV_FILE` 指定绝对路径。
154
+
155
+ 运行时默认策略:
156
+
157
+ ```dotenv
158
+ SSH_MCP_DEFAULT_SSH_PORT=22
159
+ SSH_MCP_TOOLSET=agent
160
+ SSH_MCP_COMPACT_JSON=true
161
+ SSH_MCP_ENABLE_DANGEROUS_TOOLS=false
162
+ SSH_MCP_DEFAULT_AGENT_FORWARD=false
163
+ SSH_MCP_DEFAULT_TRY_KEYBOARD=false
164
+ SSH_MCP_DEFAULT_KEEPALIVE_INTERVAL_MS=10000
165
+ SSH_MCP_DEFAULT_KEEPALIVE_COUNT_MAX=3
166
+ SSH_MCP_DEFAULT_READY_TIMEOUT_MS=20000
167
+ SSH_MCP_DEFAULT_CONNECTION_TIMEOUT_MS=30000
168
+ SSH_MCP_DEFAULT_EXEC_TIMEOUT_MS=60000
169
+ SSH_MCP_DEFAULT_MAX_OUTPUT_BYTES=1048576
170
+ SSH_MCP_DEFAULT_SHELL_ALLOCATE_PTY=true
171
+ SSH_MCP_DEFAULT_SHELL_RING_BUFFER_BYTES=1048576
172
+ SSH_MCP_DEFAULT_SHELL_READ_MAX_BYTES=262144
173
+ SSH_MCP_DEFAULT_SFTP_READ_MAX_BYTES=1048576
174
+ SSH_MCP_DEFAULT_TRANSFER_CHUNK_SIZE_BYTES=1048576
175
+ SSH_MCP_DEFAULT_TRANSFER_RESUME=true
176
+ SSH_MCP_DEFAULT_TRANSFER_OVERWRITE=false
177
+ SSH_MCP_DEFAULT_TRANSFER_ATOMIC=false
178
+ SSH_MCP_DEFAULT_MAX_EDIT_FILE_BYTES=1048576
179
+ SSH_MCP_DEFAULT_MAX_PATCH_BYTES=2097152
180
+ SSH_MCP_DEFAULT_LOCAL_TUNNEL_HOST=127.0.0.1
181
+ SSH_MCP_DEFAULT_REMOTE_TUNNEL_HOST=127.0.0.1
182
+ ```
183
+
184
+ 这些值是工具参数省略时的默认值;单次调用仍然可以传参覆盖。用 `ssh_get_config` 查看当前生效值,用 `ssh_reload_config` 重新读取 `.env`。
185
+
186
+ Agent 友好开关:
187
+
188
+ - `SSH_MCP_TOOLSET=agent`:只暴露常用安全工具,减少 agent 选错工具;设为 `full` 会暴露全部工具。
189
+ - `SSH_MCP_COMPACT_JSON=true`:返回紧凑 JSON,减少 token 噪音。
190
+ - `SSH_MCP_ENABLE_DANGEROUS_TOOLS=false`:隐藏 `rm/chmod/chown/tunnel/disconnect_all` 这类高风险工具。
191
+ - 工具可见性在 MCP server 启动时确定;修改 `TOOLSET` 或危险工具开关后需要重启 MCP 客户端或新开会话。
192
+
193
+ 推荐使用统一 indexed profile 格式。每台服务器都使用同一组字段,只用序号区分:
194
+
195
+ ```dotenv
196
+ SSH_MCP_SERVER_1_NAME=prod
197
+ SSH_MCP_SERVER_1_HOST=prod.example.com
198
+ SSH_MCP_SERVER_1_PORT=22
199
+ SSH_MCP_SERVER_1_USER=root
200
+ SSH_MCP_SERVER_1_PASSWORD="change-me"
201
+ SSH_MCP_SERVER_1_ALIASES=production,example-prod
202
+ SSH_MCP_SERVER_1_DEFAULT_DIR=/opt/app
203
+ SSH_MCP_SERVER_1_DESCRIPTION="production server"
204
+ SSH_MCP_SERVER_1_PLATFORM=linux
205
+ SSH_MCP_SERVER_1_ALLOW_EDIT=true
206
+ SSH_MCP_SERVER_1_EDIT_ROOT=/opt/app
207
+ SSH_MCP_SERVER_1_MAX_EDIT_FILE_BYTES=1048576
208
+ SSH_MCP_SERVER_1_BACKUP_BEFORE_EDIT=true
209
+ SSH_MCP_SERVER_1_BACKUP_DIR=.mcp-edit-backups
210
+ SSH_MCP_SERVER_1_ALLOW_DELETE=false
211
+ SSH_MCP_SERVER_1_ALLOW_BINARY_EDIT=false
212
+
213
+ SSH_MCP_SERVER_2_NAME=staging
214
+ SSH_MCP_SERVER_2_HOST=staging.example.com
215
+ SSH_MCP_SERVER_2_PORT=22
216
+ SSH_MCP_SERVER_2_USER=deploy
217
+ SSH_MCP_SERVER_2_PRIVATE_KEY_PATH=C:/Users/yourname/.ssh/id_ed25519
218
+ SSH_MCP_SERVER_2_ALIASES=stage
219
+ ```
220
+
221
+ 字段规范:
222
+
223
+ - `SSH_MCP_SERVER_<N>_NAME`:连接名,也是 `ssh_connect_profile`、`ssh_run_profile`、`ssh_check_profile` 使用的 profileName。
224
+ - `SSH_MCP_SERVER_<N>_IP` / `SSH_MCP_SERVER_<N>_HOST`:服务器 IP 或域名,二选一即可。
225
+ - `SSH_MCP_SERVER_<N>_PORT`:SSH 端口,省略时默认 22。
226
+ - `SSH_MCP_SERVER_<N>_USER` / `SSH_MCP_SERVER_<N>_USERNAME`:SSH 用户名。
227
+ - `SSH_MCP_SERVER_<N>_PASSWORD`:密码认证。
228
+ - `SSH_MCP_SERVER_<N>_PRIVATE_KEY`:私钥内容。
229
+ - `SSH_MCP_SERVER_<N>_PRIVATE_KEY_PATH`:私钥文件路径。
230
+ - `SSH_MCP_SERVER_<N>_PASSPHRASE`:私钥 passphrase。
231
+ - `SSH_MCP_SERVER_<N>_AGENT`:SSH agent socket 或 Pageant。
232
+ - `SSH_MCP_SERVER_<N>_ALIASES`:逗号分隔别名。
233
+ - `SSH_MCP_SERVER_<N>_DEFAULT_DIR`:默认工作目录。
234
+ - `SSH_MCP_SERVER_<N>_DESCRIPTION`:说明。
235
+ - `SSH_MCP_SERVER_<N>_PLATFORM`:平台标记,例如 `linux`、`windows`、`macos`。
236
+ - `SSH_MCP_SERVER_<N>_ALLOW_EDIT`:是否允许项目编辑工具写入,默认 `false`。
237
+ - `SSH_MCP_SERVER_<N>_EDIT_ROOT`:项目编辑硬边界;省略时使用 `DEFAULT_DIR`。
238
+ - `SSH_MCP_SERVER_<N>_MAX_EDIT_FILE_BYTES`:单个可编辑文件大小上限;省略时使用 `SSH_MCP_DEFAULT_MAX_EDIT_FILE_BYTES`。
239
+ - `SSH_MCP_SERVER_<N>_BACKUP_BEFORE_EDIT`:修改前是否备份,默认 `true`。
240
+ - `SSH_MCP_SERVER_<N>_BACKUP_DIR`:备份目录,必须位于项目根内;省略时备份到文件同目录。
241
+ - `SSH_MCP_SERVER_<N>_ALLOW_DELETE`:是否允许 `ssh_file_delete`,默认 `false`。
242
+ - `SSH_MCP_SERVER_<N>_ALLOW_BINARY_EDIT`:是否允许编辑包含 NUL 字节的二进制文件,默认 `false`。
243
+
244
+ 只支持 `SSH_MCP_SERVER_<N>_*` 这一种格式。旧版 `SSH_SERVER_<PROFILE>_<FIELD>` 不再解析,混入旧字段会直接报错,避免配置契约不一致。
245
+
246
+ 使用流程:
247
+
248
+ 1. `ssh_list_profiles` 查看可用 profile。返回结果只包含 `hasPassword` 等布尔值,不返回密码。
249
+ 2. `ssh_check_profile` 传 `profileName` 做一键远端检查,例如 `prod`。
250
+ 3. 单条命令使用 `ssh_run_profile`;需要连续多步操作时,用 `ssh_connect_profile` 创建持久 session,再调用 `ssh_exec`、`ssh_shell_open`、SFTP 或 tunnel 工具。
251
+
252
+ ## 安全说明
253
+
254
+ - 不会在返回值里回显 password/privateKey/passphrase。
255
+ - `.env` 已被 `.gitignore` 忽略;`.env.example` 只放占位符。
256
+ - 生产环境建议在 `ssh_connect` 传入 `hostHash` 和 `expectedHostHash` 做主机指纹校验。
257
+ - `sftp_rm` 删除目录必须显式传 `recursive=true`。
258
+ - `ssh_exec` 和 `sftp_read_file` 都有默认输出大小上限,大文件请走后台 transfer job。
259
+
260
+ ## 验证
261
+
262
+ ```powershell
263
+ npm run typecheck
264
+ npm test
265
+ npm run build
266
+ ```
@@ -0,0 +1,42 @@
1
+ export type WireEncoding = "utf8" | "base64";
2
+ export type OutputStreamName = "stdout" | "stderr";
3
+ export interface CapturedOutput {
4
+ text: string;
5
+ bytesReceived: number;
6
+ bytesCaptured: number;
7
+ truncated: boolean;
8
+ }
9
+ export declare class ByteCollector {
10
+ private readonly limitBytes;
11
+ private readonly chunks;
12
+ private capturedBytes;
13
+ private receivedBytes;
14
+ private outputWasTruncated;
15
+ constructor(limitBytes: number);
16
+ append(chunk: Buffer | string): void;
17
+ snapshot(encoding: WireEncoding): CapturedOutput;
18
+ }
19
+ export interface ShellReadChunk {
20
+ stream: OutputStreamName;
21
+ data: string;
22
+ bytes: number;
23
+ receivedAt: string;
24
+ }
25
+ export interface ShellReadResult {
26
+ chunks: ShellReadChunk[];
27
+ returnedBytes: number;
28
+ remainingBytes: number;
29
+ droppedBytes: number;
30
+ }
31
+ export declare class ShellRingBuffer {
32
+ private readonly limitBytes;
33
+ private chunks;
34
+ private storedBytes;
35
+ private droppedBytes;
36
+ constructor(limitBytes: number);
37
+ append(stream: OutputStreamName, data: Buffer | string): void;
38
+ read(maxBytes: number, encoding: WireEncoding, drain: boolean): ShellReadResult;
39
+ size(): number;
40
+ private trimToLimit;
41
+ private consume;
42
+ }
@@ -0,0 +1,123 @@
1
+ export class ByteCollector {
2
+ limitBytes;
3
+ chunks = [];
4
+ capturedBytes = 0;
5
+ receivedBytes = 0;
6
+ outputWasTruncated = false;
7
+ constructor(limitBytes) {
8
+ this.limitBytes = limitBytes;
9
+ }
10
+ append(chunk) {
11
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
12
+ this.receivedBytes += buffer.length;
13
+ if (buffer.length === 0 || this.capturedBytes >= this.limitBytes) {
14
+ this.outputWasTruncated = this.outputWasTruncated || buffer.length > 0;
15
+ return;
16
+ }
17
+ const remainingBytes = this.limitBytes - this.capturedBytes;
18
+ const captured = buffer.length > remainingBytes ? buffer.subarray(0, remainingBytes) : buffer;
19
+ this.chunks.push(captured);
20
+ this.capturedBytes += captured.length;
21
+ this.outputWasTruncated = this.outputWasTruncated || captured.length < buffer.length;
22
+ }
23
+ snapshot(encoding) {
24
+ const buffer = Buffer.concat(this.chunks, this.capturedBytes);
25
+ return {
26
+ text: encoding === "base64" ? buffer.toString("base64") : buffer.toString("utf8"),
27
+ bytesReceived: this.receivedBytes,
28
+ bytesCaptured: this.capturedBytes,
29
+ truncated: this.outputWasTruncated,
30
+ };
31
+ }
32
+ }
33
+ export class ShellRingBuffer {
34
+ limitBytes;
35
+ chunks = [];
36
+ storedBytes = 0;
37
+ droppedBytes = 0;
38
+ constructor(limitBytes) {
39
+ this.limitBytes = limitBytes;
40
+ }
41
+ append(stream, data) {
42
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
43
+ if (buffer.length === 0) {
44
+ return;
45
+ }
46
+ this.chunks.push({
47
+ stream,
48
+ data: buffer,
49
+ receivedAt: new Date().toISOString(),
50
+ });
51
+ this.storedBytes += buffer.length;
52
+ this.trimToLimit();
53
+ }
54
+ read(maxBytes, encoding, drain) {
55
+ const chunks = [];
56
+ let returnedBytes = 0;
57
+ let remainingToRead = maxBytes;
58
+ for (const chunk of this.chunks) {
59
+ if (remainingToRead <= 0) {
60
+ break;
61
+ }
62
+ const bytesToTake = Math.min(chunk.data.length, remainingToRead);
63
+ const data = chunk.data.subarray(0, bytesToTake);
64
+ chunks.push({
65
+ stream: chunk.stream,
66
+ data: encoding === "base64" ? data.toString("base64") : data.toString("utf8"),
67
+ bytes: bytesToTake,
68
+ receivedAt: chunk.receivedAt,
69
+ });
70
+ returnedBytes += bytesToTake;
71
+ remainingToRead -= bytesToTake;
72
+ }
73
+ if (drain && returnedBytes > 0) {
74
+ this.consume(returnedBytes);
75
+ }
76
+ return {
77
+ chunks,
78
+ returnedBytes,
79
+ remainingBytes: this.storedBytes,
80
+ droppedBytes: this.droppedBytes,
81
+ };
82
+ }
83
+ size() {
84
+ return this.storedBytes;
85
+ }
86
+ trimToLimit() {
87
+ while (this.storedBytes > this.limitBytes && this.chunks.length > 0) {
88
+ const excessBytes = this.storedBytes - this.limitBytes;
89
+ const first = this.chunks[0];
90
+ if (first === undefined) {
91
+ return;
92
+ }
93
+ if (first.data.length <= excessBytes) {
94
+ this.chunks.shift();
95
+ this.storedBytes -= first.data.length;
96
+ this.droppedBytes += first.data.length;
97
+ continue;
98
+ }
99
+ first.data = first.data.subarray(excessBytes);
100
+ this.storedBytes -= excessBytes;
101
+ this.droppedBytes += excessBytes;
102
+ }
103
+ }
104
+ consume(bytesToConsume) {
105
+ let remaining = bytesToConsume;
106
+ while (remaining > 0 && this.chunks.length > 0) {
107
+ const first = this.chunks[0];
108
+ if (first === undefined) {
109
+ return;
110
+ }
111
+ if (first.data.length <= remaining) {
112
+ this.chunks.shift();
113
+ this.storedBytes -= first.data.length;
114
+ remaining -= first.data.length;
115
+ continue;
116
+ }
117
+ first.data = first.data.subarray(remaining);
118
+ this.storedBytes -= remaining;
119
+ remaining = 0;
120
+ }
121
+ }
122
+ }
123
+ //# sourceMappingURL=buffers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffers.js","sourceRoot":"","sources":["../src/buffers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ export interface SshServerProfile {
2
+ name: string;
3
+ host: string;
4
+ port: number;
5
+ username: string;
6
+ sourcePath: string;
7
+ aliases: string[];
8
+ password?: string;
9
+ privateKey?: string;
10
+ privateKeyPath?: string;
11
+ passphrase?: string;
12
+ agent?: string;
13
+ displayName?: string;
14
+ defaultDir?: string;
15
+ description?: string;
16
+ platform?: string;
17
+ allowEdit?: boolean;
18
+ editRoot?: string;
19
+ maxEditFileBytes?: number;
20
+ backupBeforeEdit?: boolean;
21
+ backupDir?: string;
22
+ allowDelete?: boolean;
23
+ allowBinaryEdit?: boolean;
24
+ }
25
+ export interface PublicSshServerProfile {
26
+ name: string;
27
+ host: string;
28
+ port: number;
29
+ username: string;
30
+ sourcePath: string;
31
+ aliases: string[];
32
+ hasPassword: boolean;
33
+ hasPrivateKey: boolean;
34
+ hasPrivateKeyPath: boolean;
35
+ hasPassphrase: boolean;
36
+ hasAgent: boolean;
37
+ displayName?: string;
38
+ defaultDir?: string;
39
+ description?: string;
40
+ platform?: string;
41
+ allowEdit: boolean;
42
+ editRoot?: string;
43
+ maxEditFileBytes?: number;
44
+ backupBeforeEdit: boolean;
45
+ backupDir?: string;
46
+ allowDelete: boolean;
47
+ allowBinaryEdit: boolean;
48
+ }
49
+ export declare class ProfileRegistry {
50
+ private readonly envPath;
51
+ private profiles;
52
+ constructor(envPath: string);
53
+ reload(): PublicSshServerProfile[];
54
+ list(): PublicSshServerProfile[];
55
+ get(profileName: string): SshServerProfile;
56
+ sourcePath(): string;
57
+ }
58
+ export declare function defaultEnvPath(): string;
59
+ export declare function loadProfilesFromEnvFile(envPath: string): Map<string, SshServerProfile>;
60
+ export declare function parseEnvFile(content: string): Map<string, string>;
61
+ export declare function parseProfiles(variables: Map<string, string>, sourcePath: string): Map<string, SshServerProfile>;
62
+ export declare function publicProfile(profile: SshServerProfile): PublicSshServerProfile;