mcp-ssh-pty 1.2.0 → 1.2.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/shell-manager.d.ts +2 -1
- package/dist/shell-manager.js +42 -24
- package/package.json +2 -1
package/dist/shell-manager.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { ShellResult, ShellConfig } from "./types.js";
|
|
|
3
3
|
export declare class ShellManager {
|
|
4
4
|
private shell;
|
|
5
5
|
private localProcess;
|
|
6
|
+
private ptyProcess;
|
|
6
7
|
private outputBuffer;
|
|
7
8
|
private outputLines;
|
|
8
9
|
private lastOutputTime;
|
|
@@ -14,7 +15,7 @@ export declare class ShellManager {
|
|
|
14
15
|
*/
|
|
15
16
|
open(client: Client): Promise<void>;
|
|
16
17
|
/**
|
|
17
|
-
* 打开本地 Shell
|
|
18
|
+
* 打开本地 Shell(使用 node-pty 创建真正的 PTY)
|
|
18
19
|
*/
|
|
19
20
|
openLocal(): Promise<void>;
|
|
20
21
|
/**
|
package/dist/shell-manager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as pty from "node-pty";
|
|
2
2
|
const DEFAULT_CONFIG = {
|
|
3
3
|
quickTimeout: 2000,
|
|
4
4
|
maxTimeout: 5000,
|
|
@@ -8,6 +8,7 @@ const DEFAULT_CONFIG = {
|
|
|
8
8
|
export class ShellManager {
|
|
9
9
|
shell = null;
|
|
10
10
|
localProcess = null;
|
|
11
|
+
ptyProcess = null;
|
|
11
12
|
outputBuffer = "";
|
|
12
13
|
outputLines = [];
|
|
13
14
|
lastOutputTime = 0;
|
|
@@ -37,42 +38,39 @@ export class ShellManager {
|
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
/**
|
|
40
|
-
* 打开本地 Shell
|
|
41
|
+
* 打开本地 Shell(使用 node-pty 创建真正的 PTY)
|
|
41
42
|
*/
|
|
42
43
|
async openLocal() {
|
|
43
44
|
return new Promise((resolve, reject) => {
|
|
44
45
|
try {
|
|
45
46
|
// 检测系统默认 shell
|
|
46
|
-
const shellPath = process.env.SHELL || (process.platform === "win32" ? "
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const shellPath = process.env.SHELL || (process.platform === "win32" ? "powershell.exe" : "/bin/sh");
|
|
48
|
+
// 使用 node-pty 创建真正的 PTY
|
|
49
|
+
const ptyProc = pty.spawn(shellPath, [], {
|
|
50
|
+
name: "xterm-256color",
|
|
51
|
+
cols: 120,
|
|
52
|
+
rows: 40,
|
|
53
|
+
cwd: process.cwd(),
|
|
54
|
+
env: process.env,
|
|
50
55
|
});
|
|
51
|
-
this.
|
|
56
|
+
this.ptyProcess = ptyProc;
|
|
52
57
|
this.isLocal = true;
|
|
53
58
|
// 创建统一的 stream 接口
|
|
54
59
|
const dataListeners = [];
|
|
55
60
|
const closeListeners = [];
|
|
56
61
|
const errorListeners = [];
|
|
57
|
-
|
|
58
|
-
dataListeners.forEach((l) => l(data));
|
|
62
|
+
ptyProc.onData((data) => {
|
|
63
|
+
dataListeners.forEach((l) => l(Buffer.from(data)));
|
|
59
64
|
});
|
|
60
|
-
|
|
61
|
-
dataListeners.forEach((l) => l(data));
|
|
62
|
-
});
|
|
63
|
-
proc.on("close", () => {
|
|
65
|
+
ptyProc.onExit(() => {
|
|
64
66
|
closeListeners.forEach((l) => l());
|
|
65
67
|
});
|
|
66
|
-
proc.on("error", (err) => {
|
|
67
|
-
errorListeners.forEach((l) => l(err));
|
|
68
|
-
});
|
|
69
68
|
const stream = {
|
|
70
69
|
write: (data) => {
|
|
71
|
-
|
|
70
|
+
ptyProc.write(data);
|
|
72
71
|
},
|
|
73
72
|
end: () => {
|
|
74
|
-
|
|
75
|
-
proc.kill();
|
|
73
|
+
ptyProc.kill();
|
|
76
74
|
},
|
|
77
75
|
on: ((event, listener) => {
|
|
78
76
|
if (event === "data") {
|
|
@@ -88,10 +86,10 @@ export class ShellManager {
|
|
|
88
86
|
};
|
|
89
87
|
this.shell = stream;
|
|
90
88
|
this.setupStream(stream);
|
|
91
|
-
//
|
|
89
|
+
// 等待初始提示符
|
|
92
90
|
setTimeout(() => {
|
|
93
91
|
resolve();
|
|
94
|
-
},
|
|
92
|
+
}, 500);
|
|
95
93
|
}
|
|
96
94
|
catch (error) {
|
|
97
95
|
reject(new Error(`无法打开本地 shell: ${error instanceof Error ? error.message : String(error)}`));
|
|
@@ -124,6 +122,7 @@ export class ShellManager {
|
|
|
124
122
|
stream.on("close", () => {
|
|
125
123
|
this.shell = null;
|
|
126
124
|
this.localProcess = null;
|
|
125
|
+
this.ptyProcess = null;
|
|
127
126
|
});
|
|
128
127
|
stream.on("error", (err) => {
|
|
129
128
|
console.error("Shell error:", err.message);
|
|
@@ -223,6 +222,10 @@ export class ShellManager {
|
|
|
223
222
|
this.localProcess.kill();
|
|
224
223
|
this.localProcess = null;
|
|
225
224
|
}
|
|
225
|
+
if (this.ptyProcess) {
|
|
226
|
+
this.ptyProcess.kill();
|
|
227
|
+
this.ptyProcess = null;
|
|
228
|
+
}
|
|
226
229
|
this.outputLines = [];
|
|
227
230
|
this.outputBuffer = "";
|
|
228
231
|
this.isLocal = false;
|
|
@@ -248,8 +251,14 @@ export class ShellManager {
|
|
|
248
251
|
const newLines = this.outputLines.slice(startLineCount);
|
|
249
252
|
const currentOutput = newLines.join("\n") +
|
|
250
253
|
(this.outputBuffer ? "\n" + this.outputBuffer : "");
|
|
251
|
-
|
|
254
|
+
// 获取最后一行用于提示符检测
|
|
255
|
+
// 处理 \r(回车):取最后一个 \r 后面的内容,因为那才是当前可见的行
|
|
256
|
+
let lastLine = this.outputBuffer ||
|
|
252
257
|
(newLines.length > 0 ? newLines[newLines.length - 1] : "");
|
|
258
|
+
const lastCR = lastLine.lastIndexOf("\r");
|
|
259
|
+
if (lastCR !== -1) {
|
|
260
|
+
lastLine = lastLine.slice(lastCR + 1);
|
|
261
|
+
}
|
|
253
262
|
const hasPrompt = this.detectPrompt(lastLine);
|
|
254
263
|
// 检测输出是否稳定(连续 3 次检查没有新输出)
|
|
255
264
|
if (this.lastOutputTime <= lastCheckTime) {
|
|
@@ -296,8 +305,13 @@ export class ShellManager {
|
|
|
296
305
|
* 私有方法:检测提示符
|
|
297
306
|
*/
|
|
298
307
|
detectPrompt(line) {
|
|
299
|
-
// 移除 ANSI
|
|
300
|
-
const cleanLine = line
|
|
308
|
+
// 移除 ANSI 转义序列(更完整的正则)
|
|
309
|
+
const cleanLine = line
|
|
310
|
+
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "") // 标准 ANSI 序列
|
|
311
|
+
.replace(/\x1b\][^\x07]*\x07/g, "") // OSC 序列 (如 \e]2;...\a)
|
|
312
|
+
.replace(/\x1b\][^\x1b]*\x1b\\/g, "") // OSC 序列 (如 \e]7;...\e\)
|
|
313
|
+
.replace(/[\x00-\x1f]/g, "") // 其他控制字符
|
|
314
|
+
.trim();
|
|
301
315
|
if (!cleanLine)
|
|
302
316
|
return false;
|
|
303
317
|
// 常见提示符模式
|
|
@@ -310,6 +324,10 @@ export class ShellManager {
|
|
|
310
324
|
/\)\s*[$#>]\s*$/, // )$ 或 )# 结尾(一些自定义 PS1)
|
|
311
325
|
/~\s*[$#>]\s*$/, // ~$ 结尾
|
|
312
326
|
/@.*:\s*[$#>]\s*$/, // user@host: $ 格式
|
|
327
|
+
/^➜\s+/, // oh-my-zsh robbyrussell 主题 (➜ 开头)
|
|
328
|
+
/❯\s*$/, // pure/starship 主题
|
|
329
|
+
/λ\s*$/, // lambda 主题
|
|
330
|
+
/^\s*%\s*$/, // zsh 默认提示符
|
|
313
331
|
];
|
|
314
332
|
return patterns.some((p) => p.test(cleanLine));
|
|
315
333
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-ssh-pty",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "MCP Server for SSH remote command execution with PTY shell support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@inquirer/prompts": "^8.1.0",
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
35
35
|
"commander": "^14.0.2",
|
|
36
|
+
"node-pty": "^1.1.0",
|
|
36
37
|
"ssh2": "^1.16.0",
|
|
37
38
|
"zod": "^3.24.0"
|
|
38
39
|
},
|