@pyrokine/mcp-ssh 1.0.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/dist/file-ops.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * SSH File Operations - 文件操作
3
3
  */
4
+ import { execSync } from 'child_process';
4
5
  import * as fs from 'fs';
5
6
  import * as path from 'path';
6
- import { execSync } from 'child_process';
7
7
  import { sessionManager } from './session-manager.js';
8
8
  /**
9
9
  * 上传文件
@@ -20,12 +20,14 @@ export async function uploadFile(alias, localPath, remotePath, onProgress) {
20
20
  const writeStream = sftp.createWriteStream(remotePath);
21
21
  let settled = false;
22
22
  const cleanup = (err) => {
23
- if (settled)
23
+ if (settled) {
24
24
  return;
25
+ }
25
26
  settled = true;
26
27
  sftp.end();
27
- if (err)
28
+ if (err) {
28
29
  reject(err);
30
+ }
29
31
  };
30
32
  let transferred = 0;
31
33
  readStream.on('data', (chunk) => {
@@ -58,10 +60,12 @@ export async function downloadFile(alias, remotePath, localPath, onProgress) {
58
60
  // 获取远程文件大小
59
61
  const stats = await new Promise((resolve, reject) => {
60
62
  sftp.stat(remotePath, (err, stats) => {
61
- if (err)
63
+ if (err) {
62
64
  reject(err);
63
- else
65
+ }
66
+ else {
64
67
  resolve(stats);
68
+ }
65
69
  });
66
70
  });
67
71
  const totalSize = stats.size;
@@ -75,12 +79,14 @@ export async function downloadFile(alias, remotePath, localPath, onProgress) {
75
79
  const writeStream = fs.createWriteStream(localPath);
76
80
  let settled = false;
77
81
  const cleanup = (err) => {
78
- if (settled)
82
+ if (settled) {
79
83
  return;
84
+ }
80
85
  settled = true;
81
86
  sftp.end();
82
- if (err)
87
+ if (err) {
83
88
  reject(err);
89
+ }
84
90
  };
85
91
  let transferred = 0;
86
92
  readStream.on('data', (chunk) => {
@@ -108,16 +114,17 @@ export async function downloadFile(alias, remotePath, localPath, onProgress) {
108
114
  /**
109
115
  * 读取远程文件内容
110
116
  */
111
- export async function readFile(alias, remotePath, maxBytes = 1024 * 1024 // 默认最大 1MB
112
- ) {
117
+ export async function readFile(alias, remotePath, maxBytes = 1024 * 1024) {
113
118
  const sftp = await sessionManager.getSftp(alias);
114
119
  // 获取文件大小
115
120
  const stats = await new Promise((resolve, reject) => {
116
121
  sftp.stat(remotePath, (err, stats) => {
117
- if (err)
122
+ if (err) {
118
123
  reject(err);
119
- else
124
+ }
125
+ else {
120
126
  resolve(stats);
127
+ }
121
128
  });
122
129
  });
123
130
  const actualSize = stats.size;
@@ -264,10 +271,12 @@ export async function mkdir(alias, remotePath, recursive = false) {
264
271
  return new Promise((resolve, reject) => {
265
272
  sftp.mkdir(remotePath, (err) => {
266
273
  sftp.end();
267
- if (err)
274
+ if (err) {
268
275
  reject(err);
269
- else
276
+ }
277
+ else {
270
278
  resolve(true);
279
+ }
271
280
  });
272
281
  });
273
282
  }
@@ -279,10 +288,12 @@ export async function removeFile(alias, remotePath) {
279
288
  return new Promise((resolve, reject) => {
280
289
  sftp.unlink(remotePath, (err) => {
281
290
  sftp.end();
282
- if (err)
291
+ if (err) {
283
292
  reject(err);
284
- else
293
+ }
294
+ else {
285
295
  resolve(true);
296
+ }
286
297
  });
287
298
  });
288
299
  }
@@ -323,7 +334,7 @@ export async function syncFiles(alias, localPath, remotePath, direction, options
323
334
  * 转义 shell 路径参数
324
335
  */
325
336
  function escapeShellPath(p) {
326
- return `'${p.replace(/'/g, "'\\''")}'`;
337
+ return `'${p.replace(/'/g, '\'\\\'\'')}'`;
327
338
  }
328
339
  /**
329
340
  * 使用 rsync 同步文件
@@ -336,7 +347,8 @@ async function syncWithRsync(alias, localPath, remotePath, direction, options) {
336
347
  execSync('which rsync', { stdio: 'pipe' });
337
348
  hasLocalRsync = true;
338
349
  }
339
- catch { }
350
+ catch {
351
+ }
340
352
  if (!hasLocalRsync) {
341
353
  // 本地没有 rsync,回退到 SFTP
342
354
  return syncWithSftp(alias, localPath, remotePath, direction, options);
@@ -374,13 +386,16 @@ async function syncWithRsync(alias, localPath, remotePath, direction, options) {
374
386
  const result = execSync(rsyncCmd, {
375
387
  encoding: 'utf-8',
376
388
  timeout: 600000, // 10 分钟超时
377
- stdio: ['pipe', 'pipe', 'pipe']
389
+ stdio: ['pipe', 'pipe', 'pipe'],
378
390
  });
379
391
  // 解析 rsync 输出统计文件数
380
392
  const lines = result.split('\n');
381
393
  let filesTransferred = 0;
382
394
  for (const line of lines) {
383
- if (line.trim() && !line.startsWith('sending') && !line.startsWith('receiving') && !line.startsWith('total')) {
395
+ if (line.trim() &&
396
+ !line.startsWith('sending') &&
397
+ !line.startsWith('receiving') &&
398
+ !line.startsWith('total')) {
384
399
  filesTransferred++;
385
400
  }
386
401
  }
@@ -409,7 +424,8 @@ async function syncWithSftp(alias, localPath, remotePath, direction, options) {
409
424
  return {
410
425
  success: true,
411
426
  method: 'sftp',
412
- output: 'Dry run mode: would transfer files via SFTP' + (warnings.length ? `. Warning: ${warnings.join('; ')}` : ''),
427
+ output: 'Dry run mode: would transfer files via SFTP' +
428
+ (warnings.length ? `. Warning: ${warnings.join('; ')}` : ''),
413
429
  };
414
430
  }
415
431
  try {
package/dist/index.js CHANGED
@@ -15,9 +15,10 @@
15
15
  */
16
16
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
17
17
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
18
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
19
- import { sessionManager } from './session-manager.js';
18
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
20
19
  import * as fileOps from './file-ops.js';
20
+ import { sessionManager } from './session-manager.js';
21
+ import { parseProxyJump, parseSSHConfig } from './ssh-config.js';
21
22
  // 创建 MCP Server
22
23
  const server = new Server({
23
24
  name: 'ssh-mcp-pro',
@@ -34,28 +35,43 @@ const tools = [
34
35
  name: 'ssh_connect',
35
36
  description: `建立 SSH 连接并保持会话。支持密码、密钥认证,支持跳板机。
36
37
 
38
+ 可通过 configHost 参数使用 ~/.ssh/config 中的配置,无需重复填写连接信息。
39
+ 支持 Host 多别名、Host * 全局默认继承、ProxyJump(user@host:port 格式)。
40
+
37
41
  示例:
38
- - 密码认证: ssh_connect(host="192.168.1.1", user="root", password="xxx")
42
+ - 使用 ssh config: ssh_connect(configHost="myserver")
39
43
  - 密钥认证: ssh_connect(host="192.168.1.1", user="root", keyPath="/home/.ssh/id_rsa")
40
- - 自定义别名: ssh_connect(..., alias="myserver")
41
- - 设置环境变量: ssh_connect(..., env={"LANG": "en_US.UTF-8"})`,
44
+ - 跳板机: ssh_connect(host="内网IP", user="root", keyPath="...", jumpHost={host:"跳板机IP", user:"root", keyPath:"..."})`,
42
45
  inputSchema: {
43
46
  type: 'object',
44
47
  properties: {
45
- host: { type: 'string', description: '服务器地址' },
46
- user: { type: 'string', description: '用户名' },
47
- password: { type: 'string', description: '密码(与 keyPath 二选一)' },
48
+ configHost: { type: 'string', description: '使用 ~/.ssh/config 中的 Host 配置(推荐)' },
49
+ configPath: { type: 'string', description: 'SSH 配置文件路径(默认 ~/.ssh/config)' },
50
+ host: { type: 'string', description: '服务器地址(使用 configHost 时可省略)' },
51
+ user: { type: 'string', description: '用户名(使用 configHost 时可省略)' },
52
+ password: { type: 'string', description: '密码' },
48
53
  keyPath: { type: 'string', description: 'SSH 私钥路径' },
49
- port: { type: 'number', description: 'SSH 端口,默认 22', default: 22 },
50
- alias: { type: 'string', description: '连接别名(可选,用于后续引用)' },
54
+ port: { type: 'number', description: 'SSH 端口,默认 22' },
55
+ alias: { type: 'string', description: '连接别名(可选,默认使用 configHost 或 host)' },
51
56
  env: {
52
57
  type: 'object',
53
- description: '环境变量,如 {"LANG": "en_US.UTF-8"}',
58
+ description: '环境变量',
54
59
  additionalProperties: { type: 'string' },
55
60
  },
56
61
  keepaliveInterval: { type: 'number', description: '心跳间隔(毫秒),默认 30000' },
62
+ jumpHost: {
63
+ type: 'object',
64
+ description: '跳板机配置',
65
+ properties: {
66
+ host: { type: 'string', description: '跳板机地址' },
67
+ user: { type: 'string', description: '跳板机用户名' },
68
+ password: { type: 'string', description: '跳板机密码' },
69
+ keyPath: { type: 'string', description: '跳板机私钥路径' },
70
+ port: { type: 'number', description: '跳板机端口,默认 22' },
71
+ },
72
+ required: ['host', 'user'],
73
+ },
57
74
  },
58
- required: ['host', 'user'],
59
75
  },
60
76
  },
61
77
  {
@@ -117,6 +133,9 @@ const tools = [
117
133
 
118
134
  适用场景: SSH 以 root 登录,但需要以其他用户(如 caros)执行命令。
119
135
 
136
+ 默认加载目标用户的 shell 配置以获取环境变量(su -c 创建非交互式 shell,不会自动执行 rc 文件)。
137
+ 支持 bash(.bashrc)、zsh(.zshrc) 及其他 shell(.profile)。
138
+
120
139
  示例: ssh_exec_as_user(alias="server", command="whoami", targetUser="caros")`,
121
140
  inputSchema: {
122
141
  type: 'object',
@@ -125,6 +144,7 @@ const tools = [
125
144
  command: { type: 'string', description: '要执行的命令' },
126
145
  targetUser: { type: 'string', description: '目标用户名' },
127
146
  timeout: { type: 'number', description: '超时(毫秒)' },
147
+ loadProfile: { type: 'boolean', description: '是否加载 .bashrc(默认 true)' },
128
148
  },
129
149
  required: ['alias', 'command', 'targetUser'],
130
150
  },
@@ -380,7 +400,11 @@ rsync 可实现增量传输,对大目录同步效率更高。
380
400
  type: 'object',
381
401
  properties: {
382
402
  ptyId: { type: 'string', description: 'PTY 会话 ID' },
383
- mode: { type: 'string', enum: ['screen', 'raw'], description: '输出模式:screen(当前屏幕)或 raw(原始流),默认 screen' },
403
+ mode: {
404
+ type: 'string',
405
+ enum: ['screen', 'raw'],
406
+ description: '输出模式:screen(当前屏幕)或 raw(原始流),默认 screen',
407
+ },
384
408
  clear: { type: 'boolean', description: '(仅 raw 模式) 读取后是否清空缓冲区,默认 true' },
385
409
  },
386
410
  required: ['ptyId'],
@@ -480,6 +504,42 @@ rsync 可实现增量传输,对大目录同步效率更高。
480
504
  properties: {},
481
505
  },
482
506
  },
507
+ // ========== SSH Config ==========
508
+ {
509
+ name: 'ssh_config_list',
510
+ description: `列出 ~/.ssh/config 中配置的所有 Host。
511
+
512
+ 返回每个 Host 的配置信息(别名、地址、用户、端口、密钥路径等)。`,
513
+ inputSchema: {
514
+ type: 'object',
515
+ properties: {
516
+ configPath: { type: 'string', description: 'SSH 配置文件路径(默认 ~/.ssh/config)' },
517
+ },
518
+ },
519
+ },
520
+ // ========== 批量执行 ==========
521
+ {
522
+ name: 'ssh_exec_parallel',
523
+ description: `在多个已连接的会话上并行执行同一命令。
524
+
525
+ 示例:
526
+ - ssh_exec_parallel(aliases=["server1", "server2"], command="uptime")
527
+
528
+ 返回每个主机的执行结果。`,
529
+ inputSchema: {
530
+ type: 'object',
531
+ properties: {
532
+ aliases: {
533
+ type: 'array',
534
+ items: { type: 'string' },
535
+ description: '连接别名列表',
536
+ },
537
+ command: { type: 'string', description: '要执行的命令' },
538
+ timeout: { type: 'number', description: '每个命令的超时(毫秒),默认 30000' },
539
+ },
540
+ required: ['aliases', 'command'],
541
+ },
542
+ },
483
543
  ];
484
544
  // 注册工具列表
485
545
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -493,20 +553,78 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
493
553
  switch (name) {
494
554
  // ========== 连接管理 ==========
495
555
  case 'ssh_connect': {
556
+ // 解析 configHost
557
+ let host = args.host;
558
+ let user = args.user;
559
+ let port = args.port;
560
+ let keyPath = args.keyPath;
561
+ const configPath = args.configPath;
562
+ let jumpHostResolved;
563
+ if (args.configHost) {
564
+ const allHosts = parseSSHConfig(configPath);
565
+ const hostConfig = allHosts.find(h => h.host === args.configHost);
566
+ if (!hostConfig) {
567
+ throw new Error(`Host '${args.configHost}' not found in SSH config`);
568
+ }
569
+ // 显式参数优先于 config 值
570
+ host = host || hostConfig.hostName || hostConfig.host;
571
+ user = user || hostConfig.user;
572
+ port = port || hostConfig.port;
573
+ keyPath = keyPath || hostConfig.identityFile;
574
+ // 解析 ProxyJump(支持 user@host:port 格式)
575
+ if (hostConfig.proxyJump) {
576
+ const parsed = parseProxyJump(hostConfig.proxyJump);
577
+ if (parsed) {
578
+ // 先尝试在 config 中查找对应的 Host
579
+ const jumpHostConfig = allHosts.find(h => h.host === parsed.host);
580
+ if (jumpHostConfig) {
581
+ // 使用 config 中的配置,但 parsed 的 user/port 优先
582
+ jumpHostResolved = {
583
+ host: jumpHostConfig.hostName || jumpHostConfig.host,
584
+ port: parsed.port || jumpHostConfig.port || 22,
585
+ username: parsed.user || jumpHostConfig.user || 'root',
586
+ privateKeyPath: jumpHostConfig.identityFile,
587
+ };
588
+ }
589
+ else {
590
+ // 直接使用 parsed 的值
591
+ jumpHostResolved = {
592
+ host: parsed.host,
593
+ port: parsed.port || 22,
594
+ username: parsed.user || 'root',
595
+ };
596
+ }
597
+ }
598
+ }
599
+ }
600
+ if (!host || !user) {
601
+ throw new Error('host and user are required (either directly or via configHost)');
602
+ }
603
+ // 手动指定的 jumpHost 优先级高于 ProxyJump
604
+ const jumpHostArg = args.jumpHost;
605
+ const jumpHost = jumpHostArg ? {
606
+ host: jumpHostArg.host,
607
+ port: jumpHostArg.port || 22,
608
+ username: jumpHostArg.user,
609
+ password: jumpHostArg.password,
610
+ privateKeyPath: jumpHostArg.keyPath,
611
+ } : jumpHostResolved;
496
612
  const alias = await sessionManager.connect({
497
- host: args.host,
498
- port: args.port || 22,
499
- username: args.user,
613
+ host,
614
+ port: port || 22,
615
+ username: user,
500
616
  password: args.password,
501
- privateKeyPath: args.keyPath,
502
- alias: args.alias,
617
+ privateKeyPath: keyPath,
618
+ alias: args.alias ||
619
+ args.configHost,
503
620
  env: args.env,
504
621
  keepaliveInterval: args.keepaliveInterval,
622
+ jumpHost,
505
623
  });
506
624
  result = {
507
625
  success: true,
508
626
  alias,
509
- message: `Connected to ${args.user}@${args.host}:${args.port || 22}`,
627
+ message: `Connected to ${user}@${host}:${port || 22}${jumpHost ? ' via jump host' : ''}`,
510
628
  };
511
629
  break;
512
630
  }
@@ -546,7 +664,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
546
664
  break;
547
665
  }
548
666
  case 'ssh_exec_as_user': {
549
- const execResult = await sessionManager.execAsUser(args.alias, args.command, args.targetUser, { timeout: args.timeout });
667
+ const execResult = await sessionManager.execAsUser(args.alias, args.command, args.targetUser, {
668
+ timeout: args.timeout,
669
+ loadProfile: args.loadProfile,
670
+ });
550
671
  result = execResult;
551
672
  break;
552
673
  }
@@ -579,8 +700,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
579
700
  success: false,
580
701
  error: err.message,
581
702
  });
582
- if (stopOnError)
703
+ if (stopOnError) {
583
704
  break;
705
+ }
584
706
  }
585
707
  }
586
708
  result = {
@@ -732,7 +854,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
732
854
  success: true,
733
855
  forwardId,
734
856
  type: 'local',
735
- message: `Local forward: ${args.localHost || '127.0.0.1'}:${args.localPort} -> ${args.remoteHost}:${args.remotePort}`,
857
+ message: `Local forward: ${args.localHost ||
858
+ '127.0.0.1'}:${args.localPort} -> ${args.remoteHost}:${args.remotePort}`,
736
859
  };
737
860
  break;
738
861
  }
@@ -742,7 +865,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
742
865
  success: true,
743
866
  forwardId,
744
867
  type: 'remote',
745
- message: `Remote forward: ${args.remoteHost || '127.0.0.1'}:${args.remotePort} -> ${args.localHost}:${args.localPort}`,
868
+ message: `Remote forward: ${args.remoteHost ||
869
+ '127.0.0.1'}:${args.remotePort} -> ${args.localHost}:${args.localPort}`,
746
870
  };
747
871
  break;
748
872
  }
@@ -765,6 +889,56 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
765
889
  };
766
890
  break;
767
891
  }
892
+ // ========== SSH Config ==========
893
+ case 'ssh_config_list': {
894
+ const hosts = parseSSHConfig(args.configPath);
895
+ result = {
896
+ success: true,
897
+ count: hosts.length,
898
+ hosts: hosts.map(h => ({
899
+ host: h.host,
900
+ hostName: h.hostName,
901
+ user: h.user,
902
+ port: h.port,
903
+ identityFile: h.identityFile,
904
+ proxyJump: h.proxyJump,
905
+ })),
906
+ };
907
+ break;
908
+ }
909
+ // ========== 批量执行 ==========
910
+ case 'ssh_exec_parallel': {
911
+ const aliases = args.aliases;
912
+ const command = args.command;
913
+ const timeout = args.timeout;
914
+ const execPromises = aliases.map(async (alias) => {
915
+ try {
916
+ const execResult = await sessionManager.exec(alias, command, { timeout });
917
+ return {
918
+ alias,
919
+ success: execResult.success,
920
+ exitCode: execResult.exitCode,
921
+ stdout: execResult.stdout,
922
+ stderr: execResult.stderr,
923
+ duration: execResult.duration,
924
+ };
925
+ }
926
+ catch (err) {
927
+ return {
928
+ alias,
929
+ success: false,
930
+ error: err.message,
931
+ };
932
+ }
933
+ });
934
+ const results = await Promise.all(execPromises);
935
+ result = {
936
+ success: results.every(r => r.success),
937
+ total: aliases.length,
938
+ results,
939
+ };
940
+ break;
941
+ }
768
942
  default:
769
943
  throw new Error(`Unknown tool: ${name}`);
770
944
  }
@@ -8,7 +8,7 @@
8
8
  * - 会话持久化
9
9
  */
10
10
  import { Client, ClientChannel, SFTPWrapper } from 'ssh2';
11
- import { SSHConnectionConfig, SSHSessionInfo, ExecOptions, ExecResult, PersistedSession, PtyOptions, PtySessionInfo, PortForwardInfo } from './types.js';
11
+ import { ExecOptions, ExecResult, PersistedSession, PortForwardInfo, PtyOptions, PtySessionInfo, SSHConnectionConfig, SSHSessionInfo } from './types.js';
12
12
  interface SSHSession {
13
13
  client: Client;
14
14
  config: SSHConnectionConfig;
@@ -37,23 +37,10 @@ export declare class SessionManager {
37
37
  private maxReconnectAttempts;
38
38
  private defaultPtyBufferSize;
39
39
  constructor(persistPath?: string);
40
- private ensurePersistDir;
41
- /**
42
- * 生成连接别名
43
- */
44
- private generateAlias;
45
40
  /**
46
41
  * 建立 SSH 连接
47
42
  */
48
43
  connect(config: SSHConnectionConfig): Promise<string>;
49
- /**
50
- * 通过跳板机转发连接
51
- */
52
- private forwardConnection;
53
- /**
54
- * 检查连接是否存活
55
- */
56
- private isAlive;
57
44
  /**
58
45
  * 重新连接
59
46
  */
@@ -74,26 +61,20 @@ export declare class SessionManager {
74
61
  * 列出所有会话
75
62
  */
76
63
  listSessions(): SSHSessionInfo[];
77
- /**
78
- * 转义 shell 参数(使用单引号方式)
79
- */
80
- private escapeShellArg;
81
64
  /**
82
65
  * 执行命令
83
66
  */
84
67
  exec(alias: string, command: string, options?: ExecOptions): Promise<ExecResult>;
85
- /**
86
- * 校验用户名(只允许字母、数字、下划线、连字符)
87
- */
88
- private isValidUsername;
89
- /**
90
- * 校验环境变量名(只允许字母、数字、下划线,不能以数字开头)
91
- */
92
- private isValidEnvKey;
93
68
  /**
94
69
  * 以其他用户身份执行命令
70
+ * @param loadProfile 是否加载用户的 shell 配置(默认 true)。
71
+ * su -c 创建非交互式 shell,不会自动执行 rc 文件,
72
+ * 但大多数用户的环境变量设置在 rc 文件中,因此默认加载。
73
+ * 支持 bash(.bashrc)、zsh(.zshrc) 及其他 shell(.profile)。
95
74
  */
96
- execAsUser(alias: string, command: string, targetUser: string, options?: ExecOptions): Promise<ExecResult>;
75
+ execAsUser(alias: string, command: string, targetUser: string, options?: ExecOptions & {
76
+ loadProfile?: boolean;
77
+ }): Promise<ExecResult>;
97
78
  /**
98
79
  * 使用 sudo 执行命令
99
80
  */
@@ -102,18 +83,10 @@ export declare class SessionManager {
102
83
  * 获取 SFTP 客户端
103
84
  */
104
85
  getSftp(alias: string): Promise<SFTPWrapper>;
105
- /**
106
- * 持久化会话信息
107
- */
108
- private persistSessions;
109
86
  /**
110
87
  * 加载持久化的会话信息(仅用于显示,不自动重连)
111
88
  */
112
89
  loadPersistedSessions(): PersistedSession[];
113
- /**
114
- * 生成 PTY 会话 ID
115
- */
116
- private generatePtyId;
117
90
  /**
118
91
  * 启动持久化 PTY 会话
119
92
  */
@@ -122,10 +95,6 @@ export declare class SessionManager {
122
95
  * 向 PTY 写入数据
123
96
  */
124
97
  ptyWrite(ptyId: string, data: string): boolean;
125
- /**
126
- * 从终端仿真器获取当前屏幕内容
127
- */
128
- private getScreenContent;
129
98
  /**
130
99
  * 读取 PTY 输出
131
100
  * @param mode 'screen' 返回当前屏幕内容,'raw' 返回原始 ANSI 流
@@ -155,10 +124,6 @@ export declare class SessionManager {
155
124
  * 关闭所有 PTY 会话
156
125
  */
157
126
  ptyCloseAll(): number;
158
- /**
159
- * 生成端口转发 ID
160
- */
161
- private generateForwardId;
162
127
  /**
163
128
  * 创建本地端口转发
164
129
  * 本地监听 localHost:localPort,转发到远程 remoteHost:remotePort
@@ -169,6 +134,60 @@ export declare class SessionManager {
169
134
  * 远程监听 remoteHost:remotePort,转发到本地 localHost:localPort
170
135
  */
171
136
  forwardRemote(alias: string, remotePort: number, localHost: string, localPort: number, remoteHost?: string): Promise<string>;
137
+ /**
138
+ * 关闭端口转发
139
+ */
140
+ forwardClose(forwardId: string): boolean;
141
+ /**
142
+ * 列出所有端口转发
143
+ */
144
+ forwardList(): PortForwardInfo[];
145
+ private ensurePersistDir;
146
+ /**
147
+ * 生成连接别名
148
+ */
149
+ private generateAlias;
150
+ /**
151
+ * 通过跳板机转发连接
152
+ */
153
+ private forwardConnection;
154
+ /**
155
+ * 检查连接是否存活
156
+ */
157
+ private isAlive;
158
+ /**
159
+ * 转义 shell 参数(使用单引号方式)
160
+ */
161
+ private escapeShellArg;
162
+ /**
163
+ * 校验用户名(只允许字母、数字、下划线、连字符)
164
+ */
165
+ private isValidUsername;
166
+ /**
167
+ * 校验环境变量名(只允许字母、数字、下划线,不能以数字开头)
168
+ */
169
+ private isValidEnvKey;
170
+ /**
171
+ * 根据用户 shell 类型生成加载配置文件的命令
172
+ * bash → .bashrc, zsh → .zshrc, 其他 → .profile
173
+ */
174
+ private getLoadProfileCommand;
175
+ /**
176
+ * 持久化会话信息
177
+ */
178
+ private persistSessions;
179
+ /**
180
+ * 生成 PTY 会话 ID
181
+ */
182
+ private generatePtyId;
183
+ /**
184
+ * 从终端仿真器获取当前屏幕内容
185
+ */
186
+ private getScreenContent;
187
+ /**
188
+ * 生成端口转发 ID
189
+ */
190
+ private generateForwardId;
172
191
  /**
173
192
  * 确保 SSH session 有共享的 tcp connection dispatcher
174
193
  * 所有 remote forward 共用一个 dispatcher,根据 destIP/destPort 路由
@@ -180,14 +199,6 @@ export declare class SessionManager {
180
199
  * @param alias - session 的 map key
181
200
  */
182
201
  private removeTcpDispatcherIfEmpty;
183
- /**
184
- * 关闭端口转发
185
- */
186
- forwardClose(forwardId: string): boolean;
187
- /**
188
- * 列出所有端口转发
189
- */
190
- forwardList(): PortForwardInfo[];
191
202
  }
192
203
  export declare const sessionManager: SessionManager;
193
204
  export {};