@pyrokine/mcp-ssh 1.1.0 → 1.1.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/README.md +81 -72
- package/README_zh.md +81 -73
- package/dist/file-ops.js +36 -20
- package/dist/index.js +24 -9
- package/dist/session-manager.d.ts +62 -51
- package/dist/session-manager.js +201 -168
- package/dist/ssh-config.js +2 -2
- package/package.json +1 -1
- package/src/file-ops.ts +602 -577
- package/src/index.ts +971 -948
- package/src/session-manager.ts +986 -945
- package/src/ssh-config.ts +185 -185
- package/src/types.ts +89 -89
- package/tsconfig.json +7 -2
package/src/ssh-config.ts
CHANGED
|
@@ -8,30 +8,30 @@
|
|
|
8
8
|
* - ProxyJump 解析(支持 user@host:port 格式)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import * as fs from 'fs'
|
|
12
|
-
import * as
|
|
13
|
-
import * as
|
|
11
|
+
import * as fs from 'fs'
|
|
12
|
+
import * as os from 'os'
|
|
13
|
+
import * as path from 'path'
|
|
14
14
|
|
|
15
15
|
export interface SSHConfigHost {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
host: string; // Host 别名
|
|
17
|
+
hostName?: string; // 实际地址
|
|
18
|
+
user?: string; // 用户名
|
|
19
|
+
port?: number; // 端口
|
|
20
|
+
identityFile?: string; // 私钥路径
|
|
21
|
+
proxyJump?: string; // 跳板机(原始字符串,可能是 user@host:port 格式)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** ProxyJump 解析结果 */
|
|
25
25
|
export interface ParsedProxyJump {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
user?: string;
|
|
27
|
+
host: string;
|
|
28
|
+
port?: number;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** 内部使用的配置块 */
|
|
32
32
|
interface ConfigBlock {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
patterns: string[]; // Host 行的所有模式/别名
|
|
34
|
+
config: Omit<SSHConfigHost, 'host'>;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -39,39 +39,39 @@ interface ConfigBlock {
|
|
|
39
39
|
* 返回 { host, port },host 不含方括号
|
|
40
40
|
*/
|
|
41
41
|
function parseHostPort(s: string): { host: string; port?: number } {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
// IPv6 方括号格式: [addr]:port 或 [addr]
|
|
43
|
+
if (s.startsWith('[')) {
|
|
44
|
+
const closeBracket = s.indexOf(']')
|
|
45
|
+
if (closeBracket !== -1) {
|
|
46
|
+
const host = s.slice(1, closeBracket) // 去掉方括号
|
|
47
|
+
const rest = s.slice(closeBracket + 1)
|
|
48
|
+
if (rest.startsWith(':')) {
|
|
49
|
+
const parsedPort = parseInt(rest.slice(1), 10)
|
|
50
|
+
if (!isNaN(parsedPort)) {
|
|
51
|
+
return {host, port: parsedPort}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {host}
|
|
52
55
|
}
|
|
53
|
-
}
|
|
54
|
-
return { host };
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return { host: s };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 普通格式: host:port 或 host
|
|
65
|
-
const colonIndex = s.lastIndexOf(':');
|
|
66
|
-
if (colonIndex !== -1) {
|
|
67
|
-
const host = s.slice(0, colonIndex);
|
|
68
|
-
const portStr = s.slice(colonIndex + 1);
|
|
69
|
-
const parsedPort = parseInt(portStr, 10);
|
|
70
|
-
if (!isNaN(parsedPort)) {
|
|
71
|
-
return { host, port: parsedPort };
|
|
57
|
+
|
|
58
|
+
// 检测裸 IPv6(多个冒号但无方括号):安全失败,当作 host-only
|
|
59
|
+
const colonCount = (s.match(/:/g) || []).length
|
|
60
|
+
if (colonCount >= 2) {
|
|
61
|
+
return {host: s}
|
|
72
62
|
}
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
|
|
64
|
+
// 普通格式: host:port 或 host
|
|
65
|
+
const colonIndex = s.lastIndexOf(':')
|
|
66
|
+
if (colonIndex !== -1) {
|
|
67
|
+
const host = s.slice(0, colonIndex)
|
|
68
|
+
const portStr = s.slice(colonIndex + 1)
|
|
69
|
+
const parsedPort = parseInt(portStr, 10)
|
|
70
|
+
if (!isNaN(parsedPort)) {
|
|
71
|
+
return {host, port: parsedPort}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {host: s}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
@@ -80,39 +80,39 @@ function parseHostPort(s: string): { host: string; port?: number } {
|
|
|
80
80
|
* 注意:只解析第一跳,不支持逗号分隔的多跳链路
|
|
81
81
|
*/
|
|
82
82
|
export function parseProxyJump(proxyJump: string): ParsedProxyJump | null {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
83
|
+
if (!proxyJump) {
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 取第一跳(如果有逗号分隔)
|
|
88
|
+
const firstJump = proxyJump.split(',')[0].trim()
|
|
89
|
+
if (!firstJump) {
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let user: string | undefined
|
|
94
|
+
|
|
95
|
+
// 解析 user@... 格式
|
|
96
|
+
const atIndex = firstJump.indexOf('@')
|
|
97
|
+
if (atIndex !== -1) {
|
|
98
|
+
user = firstJump.slice(0, atIndex)
|
|
99
|
+
const rest = firstJump.slice(atIndex + 1)
|
|
100
|
+
const {host, port} = parseHostPort(rest)
|
|
101
|
+
return {user, host, port}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const {host, port} = parseHostPort(firstJump)
|
|
105
|
+
return {user, host, port}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* 展开 ~ 路径
|
|
110
110
|
*/
|
|
111
111
|
function expandTilde(filePath: string): string {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
if (filePath.startsWith('~')) {
|
|
113
|
+
return path.join(os.homedir(), filePath.slice(1))
|
|
114
|
+
}
|
|
115
|
+
return filePath
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/**
|
|
@@ -120,21 +120,21 @@ function expandTilde(filePath: string): string {
|
|
|
120
120
|
* "value # comment" -> "value"
|
|
121
121
|
*/
|
|
122
122
|
function stripInlineComment(value: string): string {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
123
|
+
// 查找不在引号内的 #
|
|
124
|
+
let inQuote = false
|
|
125
|
+
let quoteChar = ''
|
|
126
|
+
for (let i = 0; i < value.length; i++) {
|
|
127
|
+
const ch = value[i]
|
|
128
|
+
if (!inQuote && (ch === '"' || ch === '\'')) {
|
|
129
|
+
inQuote = true
|
|
130
|
+
quoteChar = ch
|
|
131
|
+
} else if (inQuote && ch === quoteChar) {
|
|
132
|
+
inQuote = false
|
|
133
|
+
} else if (!inQuote && ch === '#') {
|
|
134
|
+
return value.slice(0, i).trim()
|
|
135
|
+
}
|
|
135
136
|
}
|
|
136
|
-
|
|
137
|
-
return value.trim();
|
|
137
|
+
return value.trim()
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
@@ -143,122 +143,122 @@ function stripInlineComment(value: string): string {
|
|
|
143
143
|
* 跳过 Match 块(避免条件配置被误应用)
|
|
144
144
|
*/
|
|
145
145
|
export function parseSSHConfig(configPath?: string): SSHConfigHost[] {
|
|
146
|
-
|
|
146
|
+
const filePath = configPath || path.join(os.homedir(), '.ssh', 'config')
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
if (!fs.existsSync(filePath)) {
|
|
149
|
+
return []
|
|
150
|
+
}
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
153
|
+
const lines = content.split('\n')
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
// 第一遍:收集所有配置块
|
|
156
|
+
const blocks: ConfigBlock[] = []
|
|
157
|
+
let currentBlock: ConfigBlock | null = null
|
|
158
|
+
let globalDefaults: Omit<SSHConfigHost, 'host'> = {}
|
|
159
|
+
let inMatchBlock = false // 跳过 Match 块
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
for (const line of lines) {
|
|
162
|
+
const trimmed = line.trim()
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
// 跳过空行和注释
|
|
165
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
// 解析 key value(支持 = 和空格分隔)
|
|
170
|
+
const match = trimmed.match(/^(\S+)\s*[=\s]\s*(.+)$/)
|
|
171
|
+
if (!match) {
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const [, key, rawValue] = match
|
|
176
|
+
const keyLower = key.toLowerCase()
|
|
177
|
+
const value = stripInlineComment(rawValue)
|
|
178
|
+
|
|
179
|
+
if (keyLower === 'host') {
|
|
180
|
+
// Host 块开始,结束 Match 块
|
|
181
|
+
inMatchBlock = false
|
|
182
|
+
|
|
183
|
+
// 保存上一个 block
|
|
184
|
+
if (currentBlock) {
|
|
185
|
+
blocks.push(currentBlock)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Host 行可能有多个别名/模式,用空格分隔
|
|
189
|
+
const patterns = value.split(/\s+/).filter(p => p.length > 0)
|
|
190
|
+
currentBlock = {patterns, config: {}}
|
|
191
|
+
} else if (keyLower === 'match') {
|
|
192
|
+
// Match 块开始,跳过直到下一个 Host
|
|
193
|
+
inMatchBlock = true
|
|
194
|
+
// 保存当前 block(如果有)
|
|
195
|
+
if (currentBlock) {
|
|
196
|
+
blocks.push(currentBlock)
|
|
197
|
+
currentBlock = null
|
|
198
|
+
}
|
|
199
|
+
} else if (!inMatchBlock && currentBlock) {
|
|
200
|
+
// 解析配置项(不在 Match 块内)
|
|
201
|
+
switch (keyLower) {
|
|
202
|
+
case 'hostname':
|
|
203
|
+
currentBlock.config.hostName = value
|
|
204
|
+
break
|
|
205
|
+
case 'user':
|
|
206
|
+
currentBlock.config.user = value
|
|
207
|
+
break
|
|
208
|
+
case 'port':
|
|
209
|
+
currentBlock.config.port = parseInt(value, 10)
|
|
210
|
+
break
|
|
211
|
+
case 'identityfile':
|
|
212
|
+
currentBlock.config.identityFile = expandTilde(value)
|
|
213
|
+
break
|
|
214
|
+
case 'proxyjump':
|
|
215
|
+
currentBlock.config.proxyJump = value
|
|
216
|
+
break
|
|
217
|
+
}
|
|
218
|
+
}
|
|
173
219
|
}
|
|
174
220
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (keyLower === 'host') {
|
|
180
|
-
// Host 块开始,结束 Match 块
|
|
181
|
-
inMatchBlock = false;
|
|
182
|
-
|
|
183
|
-
// 保存上一个 block
|
|
184
|
-
if (currentBlock) {
|
|
185
|
-
blocks.push(currentBlock);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Host 行可能有多个别名/模式,用空格分隔
|
|
189
|
-
const patterns = value.split(/\s+/).filter(p => p.length > 0);
|
|
190
|
-
currentBlock = { patterns, config: {} };
|
|
191
|
-
} else if (keyLower === 'match') {
|
|
192
|
-
// Match 块开始,跳过直到下一个 Host
|
|
193
|
-
inMatchBlock = true;
|
|
194
|
-
// 保存当前 block(如果有)
|
|
195
|
-
if (currentBlock) {
|
|
196
|
-
blocks.push(currentBlock);
|
|
197
|
-
currentBlock = null;
|
|
198
|
-
}
|
|
199
|
-
} else if (!inMatchBlock && currentBlock) {
|
|
200
|
-
// 解析配置项(不在 Match 块内)
|
|
201
|
-
switch (keyLower) {
|
|
202
|
-
case 'hostname':
|
|
203
|
-
currentBlock.config.hostName = value;
|
|
204
|
-
break;
|
|
205
|
-
case 'user':
|
|
206
|
-
currentBlock.config.user = value;
|
|
207
|
-
break;
|
|
208
|
-
case 'port':
|
|
209
|
-
currentBlock.config.port = parseInt(value, 10);
|
|
210
|
-
break;
|
|
211
|
-
case 'identityfile':
|
|
212
|
-
currentBlock.config.identityFile = expandTilde(value);
|
|
213
|
-
break;
|
|
214
|
-
case 'proxyjump':
|
|
215
|
-
currentBlock.config.proxyJump = value;
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
221
|
+
// 保存最后一个 block
|
|
222
|
+
if (currentBlock && !inMatchBlock) {
|
|
223
|
+
blocks.push(currentBlock)
|
|
218
224
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// 第二遍:提取 Host * 的全局默认配置
|
|
227
|
-
for (const block of blocks) {
|
|
228
|
-
if (block.patterns.length === 1 && block.patterns[0] === '*') {
|
|
229
|
-
globalDefaults = { ...block.config };
|
|
230
|
-
break;
|
|
225
|
+
|
|
226
|
+
// 第二遍:提取 Host * 的全局默认配置
|
|
227
|
+
for (const block of blocks) {
|
|
228
|
+
if (block.patterns.length === 1 && block.patterns[0] === '*') {
|
|
229
|
+
globalDefaults = {...block.config}
|
|
230
|
+
break
|
|
231
|
+
}
|
|
231
232
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
233
|
+
|
|
234
|
+
// 第三遍:展开所有 Host,应用继承
|
|
235
|
+
const hosts: SSHConfigHost[] = []
|
|
236
|
+
|
|
237
|
+
for (const block of blocks) {
|
|
238
|
+
for (const pattern of block.patterns) {
|
|
239
|
+
// 跳过通配符模式(*, *.example.com 等)
|
|
240
|
+
if (pattern.includes('*') || pattern.includes('?')) {
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 合并配置:全局默认 + 当前块配置
|
|
245
|
+
const merged: SSHConfigHost = {
|
|
246
|
+
host: pattern,
|
|
247
|
+
...globalDefaults,
|
|
248
|
+
...block.config,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
hosts.push(merged)
|
|
252
|
+
}
|
|
252
253
|
}
|
|
253
|
-
}
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
return hosts
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/**
|
|
259
259
|
* 根据 Host 名称获取配置
|
|
260
260
|
*/
|
|
261
261
|
export function getHostConfig(hostName: string, configPath?: string): SSHConfigHost | null {
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
const hosts = parseSSHConfig(configPath)
|
|
263
|
+
return hosts.find(h => h.host === hostName) || null
|
|
264
264
|
}
|
package/src/types.ts
CHANGED
|
@@ -3,108 +3,108 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export interface SSHConnectionConfig {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
username: string;
|
|
9
|
+
password?: string;
|
|
10
|
+
privateKeyPath?: string;
|
|
11
|
+
privateKey?: string;
|
|
12
|
+
passphrase?: string;
|
|
13
|
+
alias?: string;
|
|
14
|
+
// 高级配置
|
|
15
|
+
keepaliveInterval?: number; // 心跳间隔(毫秒)
|
|
16
|
+
keepaliveCountMax?: number; // 最大心跳失败次数
|
|
17
|
+
readyTimeout?: number; // 连接超时(毫秒)
|
|
18
|
+
// 环境配置
|
|
19
|
+
env?: Record<string, string>; // 环境变量
|
|
20
|
+
lang?: string; // LANG 设置
|
|
21
|
+
shell?: string; // Shell 类型
|
|
22
|
+
// 跳板机
|
|
23
|
+
jumpHost?: SSHConnectionConfig;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export interface SSHSessionInfo {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
alias: string;
|
|
28
|
+
host: string;
|
|
29
|
+
port: number;
|
|
30
|
+
username: string;
|
|
31
|
+
connected: boolean;
|
|
32
|
+
connectedAt: number;
|
|
33
|
+
lastUsedAt: number;
|
|
34
|
+
env?: Record<string, string>;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export interface ExecOptions {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
timeout?: number; // 命令超时(毫秒)
|
|
39
|
+
env?: Record<string, string>; // 额外环境变量
|
|
40
|
+
cwd?: string; // 工作目录
|
|
41
|
+
pty?: boolean; // 是否使用 PTY
|
|
42
|
+
maxOutputSize?: number; // 最大输出大小(字节),默认 10MB
|
|
43
|
+
// PTY 配置
|
|
44
|
+
rows?: number;
|
|
45
|
+
cols?: number;
|
|
46
|
+
term?: string;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export interface ExecResult {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
success: boolean;
|
|
51
|
+
stdout: string;
|
|
52
|
+
stderr: string;
|
|
53
|
+
exitCode: number;
|
|
54
|
+
duration: number; // 执行时间(毫秒)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export interface FileInfo {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
name: string;
|
|
59
|
+
path: string;
|
|
60
|
+
size: number;
|
|
61
|
+
isDirectory: boolean;
|
|
62
|
+
isFile: boolean;
|
|
63
|
+
isSymlink: boolean;
|
|
64
|
+
permissions: string;
|
|
65
|
+
owner: number;
|
|
66
|
+
group: number;
|
|
67
|
+
mtime: Date;
|
|
68
|
+
atime: Date;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export interface TransferProgress {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
transferred: number;
|
|
73
|
+
total: number;
|
|
74
|
+
percent: number;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// 持久化存储的会话配置(不含敏感信息)
|
|
78
78
|
export interface PersistedSession {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
alias: string;
|
|
80
|
+
host: string;
|
|
81
|
+
port: number;
|
|
82
|
+
username: string;
|
|
83
|
+
connectedAt: number;
|
|
84
|
+
env?: Record<string, string>;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// PTY 会话配置
|
|
88
88
|
export interface PtyOptions {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
rows?: number;
|
|
90
|
+
cols?: number;
|
|
91
|
+
term?: string;
|
|
92
|
+
env?: Record<string, string>;
|
|
93
|
+
cwd?: string;
|
|
94
|
+
bufferSize?: number; // 输出缓冲区大小,默认 1MB
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// PTY 会话信息
|
|
98
98
|
export interface PtySessionInfo {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
id: string;
|
|
100
|
+
alias: string; // SSH 连接别名
|
|
101
|
+
command: string; // 启动命令
|
|
102
|
+
rows: number;
|
|
103
|
+
cols: number;
|
|
104
|
+
createdAt: number;
|
|
105
|
+
lastReadAt: number;
|
|
106
|
+
bufferSize: number; // 当前缓冲区大小
|
|
107
|
+
active: boolean;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// 端口转发类型
|
|
@@ -112,22 +112,22 @@ export type ForwardType = 'local' | 'remote';
|
|
|
112
112
|
|
|
113
113
|
// 端口转发配置
|
|
114
114
|
export interface PortForwardConfig {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
type: ForwardType;
|
|
116
|
+
localHost: string;
|
|
117
|
+
localPort: number;
|
|
118
|
+
remoteHost: string;
|
|
119
|
+
remotePort: number;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// 端口转发信息
|
|
123
123
|
export interface PortForwardInfo {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
id: string;
|
|
125
|
+
alias: string; // SSH 连接别名
|
|
126
|
+
type: ForwardType;
|
|
127
|
+
localHost: string;
|
|
128
|
+
localPort: number;
|
|
129
|
+
remoteHost: string;
|
|
130
|
+
remotePort: number;
|
|
131
|
+
createdAt: number;
|
|
132
|
+
active: boolean;
|
|
133
133
|
}
|
package/tsconfig.json
CHANGED