seahorse-bash-client 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.
- package/DESIGN.md +463 -0
- package/bin/cli.js +2 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +190 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/pty-manager.d.ts +52 -0
- package/dist/pty-manager.d.ts.map +1 -0
- package/dist/pty-manager.js +387 -0
- package/dist/pty-manager.js.map +1 -0
- package/dist/pty-manager.test.d.ts +8 -0
- package/dist/pty-manager.test.d.ts.map +1 -0
- package/dist/pty-manager.test.js +427 -0
- package/dist/pty-manager.test.js.map +1 -0
- package/dist/tools.d.ts +6 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +212 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/websocket-client.d.ts +73 -0
- package/dist/websocket-client.d.ts.map +1 -0
- package/dist/websocket-client.js +318 -0
- package/dist/websocket-client.js.map +1 -0
- package/package.json +55 -0
- package/src/cli.ts +179 -0
- package/src/index.ts +10 -0
- package/src/pty-manager.test.ts +555 -0
- package/src/pty-manager.ts +430 -0
- package/src/tools.ts +217 -0
- package/src/types.ts +221 -0
- package/src/websocket-client.ts +374 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WebSocket Client - Connects to Seahorse Agent and handles MCP tool calls
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.WebSocketClient = void 0;
|
|
10
|
+
const ws_1 = __importDefault(require("ws"));
|
|
11
|
+
const events_1 = require("events");
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const https_1 = __importDefault(require("https"));
|
|
14
|
+
const http_1 = __importDefault(require("http"));
|
|
15
|
+
const pty_manager_1 = require("./pty-manager");
|
|
16
|
+
const tools_1 = require("./tools");
|
|
17
|
+
class WebSocketClient extends events_1.EventEmitter {
|
|
18
|
+
constructor(config) {
|
|
19
|
+
super();
|
|
20
|
+
this.ws = null;
|
|
21
|
+
this.reconnectTimer = null;
|
|
22
|
+
this.heartbeatTimer = null;
|
|
23
|
+
this.isConnected = false;
|
|
24
|
+
this.isShuttingDown = false;
|
|
25
|
+
this.config = {
|
|
26
|
+
reconnectInterval: 5000,
|
|
27
|
+
heartbeatInterval: 30000,
|
|
28
|
+
maxOutputLines: 50000,
|
|
29
|
+
...config,
|
|
30
|
+
};
|
|
31
|
+
this.ptyManager = new pty_manager_1.PTYManager({
|
|
32
|
+
maxOutputLines: this.config.maxOutputLines,
|
|
33
|
+
defaultShell: this.config.defaultShell,
|
|
34
|
+
});
|
|
35
|
+
// Forward session exit events
|
|
36
|
+
this.ptyManager.on('session:exited', (data) => {
|
|
37
|
+
this.sendNotification('session/exited', data);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Connect to the Seahorse Agent
|
|
42
|
+
*/
|
|
43
|
+
async connect() {
|
|
44
|
+
if (this.isShuttingDown)
|
|
45
|
+
return;
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(this.config.serverUrl);
|
|
49
|
+
const isSecure = url.protocol === 'wss:';
|
|
50
|
+
// Create agent that forces HTTP/1.1 (no ALPN for HTTP/2)
|
|
51
|
+
const agent = isSecure
|
|
52
|
+
? new https_1.default.Agent({
|
|
53
|
+
rejectUnauthorized: !this.config.insecure,
|
|
54
|
+
// Force HTTP/1.1 by not advertising HTTP/2
|
|
55
|
+
ALPNProtocols: ['http/1.1'],
|
|
56
|
+
})
|
|
57
|
+
: new http_1.default.Agent();
|
|
58
|
+
const wsOptions = {
|
|
59
|
+
agent,
|
|
60
|
+
perMessageDeflate: false,
|
|
61
|
+
};
|
|
62
|
+
this.ws = new ws_1.default(this.config.serverUrl, wsOptions);
|
|
63
|
+
this.ws.on('open', () => {
|
|
64
|
+
this.isConnected = true;
|
|
65
|
+
this.emit('connected');
|
|
66
|
+
this.register();
|
|
67
|
+
this.startHeartbeat();
|
|
68
|
+
resolve();
|
|
69
|
+
});
|
|
70
|
+
this.ws.on('message', (data) => {
|
|
71
|
+
this.handleMessage(data);
|
|
72
|
+
});
|
|
73
|
+
this.ws.on('close', (code, reason) => {
|
|
74
|
+
this.isConnected = false;
|
|
75
|
+
this.stopHeartbeat();
|
|
76
|
+
this.emit('disconnected', { code, reason: reason.toString() });
|
|
77
|
+
if (!this.isShuttingDown) {
|
|
78
|
+
this.scheduleReconnect();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
this.ws.on('error', (error) => {
|
|
82
|
+
this.emit('error', error);
|
|
83
|
+
if (!this.isConnected) {
|
|
84
|
+
reject(error);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
reject(error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Disconnect from the server
|
|
95
|
+
*/
|
|
96
|
+
async disconnect() {
|
|
97
|
+
this.isShuttingDown = true;
|
|
98
|
+
if (this.reconnectTimer) {
|
|
99
|
+
clearTimeout(this.reconnectTimer);
|
|
100
|
+
this.reconnectTimer = null;
|
|
101
|
+
}
|
|
102
|
+
this.stopHeartbeat();
|
|
103
|
+
// Shutdown all PTY sessions
|
|
104
|
+
await this.ptyManager.shutdown();
|
|
105
|
+
if (this.ws) {
|
|
106
|
+
this.ws.close(1000, 'Client shutdown');
|
|
107
|
+
this.ws = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Register tools with the agent
|
|
112
|
+
*/
|
|
113
|
+
register() {
|
|
114
|
+
// Note: Server expects "registration" type, not "register"
|
|
115
|
+
const message = {
|
|
116
|
+
type: 'registration',
|
|
117
|
+
tools: tools_1.TOOL_DEFINITIONS,
|
|
118
|
+
metadata: {
|
|
119
|
+
version: '1.0.0',
|
|
120
|
+
hostname: os_1.default.hostname(),
|
|
121
|
+
platform: process.platform,
|
|
122
|
+
shell: this.config.defaultShell || process.env.SHELL || '/bin/bash',
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
this.send(message);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Handle incoming WebSocket messages
|
|
129
|
+
*/
|
|
130
|
+
handleMessage(data) {
|
|
131
|
+
try {
|
|
132
|
+
const message = JSON.parse(data.toString());
|
|
133
|
+
switch (message.type) {
|
|
134
|
+
case 'tool_call':
|
|
135
|
+
this.handleToolCall(message);
|
|
136
|
+
break;
|
|
137
|
+
case 'heartbeat':
|
|
138
|
+
case 'heartbeat_ack':
|
|
139
|
+
// Respond to heartbeat, ignore ack
|
|
140
|
+
if (message.type === 'heartbeat') {
|
|
141
|
+
this.send({ type: 'heartbeat' });
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
case 'registration_ack':
|
|
145
|
+
// Server acknowledged registration
|
|
146
|
+
const ackData = message;
|
|
147
|
+
this.emit('registered', {
|
|
148
|
+
serverName: ackData.server_name,
|
|
149
|
+
tools: tools_1.TOOL_DEFINITIONS,
|
|
150
|
+
registeredCount: ackData.registered_tools,
|
|
151
|
+
});
|
|
152
|
+
break;
|
|
153
|
+
case 'error':
|
|
154
|
+
const errorData = message;
|
|
155
|
+
this.emit('error', new Error(`Server error: ${errorData.error}`));
|
|
156
|
+
break;
|
|
157
|
+
default:
|
|
158
|
+
this.emit('message', message);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
this.emit('error', new Error(`Failed to parse message: ${error}`));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Handle a tool call from the agent
|
|
167
|
+
*/
|
|
168
|
+
async handleToolCall(message) {
|
|
169
|
+
const { request_id, params } = message;
|
|
170
|
+
const { name, arguments: args } = params;
|
|
171
|
+
this.emit('tool:call', { name, args, requestId: request_id });
|
|
172
|
+
try {
|
|
173
|
+
const result = await this.executeTool(name, args);
|
|
174
|
+
this.sendToolResponse(request_id, result, false);
|
|
175
|
+
this.emit('tool:success', { name, requestId: request_id });
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
179
|
+
this.sendToolResponse(request_id, { error: errorMessage }, true, errorMessage);
|
|
180
|
+
this.emit('tool:error', { name, requestId: request_id, error: errorMessage });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Execute a tool
|
|
185
|
+
*/
|
|
186
|
+
async executeTool(name, args) {
|
|
187
|
+
// Validate allowed commands if configured
|
|
188
|
+
if (this.config.allowedCommands && name.startsWith('bash_')) {
|
|
189
|
+
const command = args.command || '';
|
|
190
|
+
const isAllowed = this.config.allowedCommands.some((allowed) => command.startsWith(allowed) || command === allowed);
|
|
191
|
+
if (!isAllowed) {
|
|
192
|
+
throw new Error(`Command not in allowed list: ${command}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Validate blocked commands if configured
|
|
196
|
+
if (this.config.blockedCommands && name.startsWith('bash_')) {
|
|
197
|
+
const command = args.command || '';
|
|
198
|
+
const isBlocked = this.config.blockedCommands.some((blocked) => command.includes(blocked) || command.startsWith(blocked));
|
|
199
|
+
if (isBlocked) {
|
|
200
|
+
throw new Error(`Command is blocked: ${command}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Validate cwd if restrictions are configured
|
|
204
|
+
if (this.config.allowedCwdPaths && args.cwd) {
|
|
205
|
+
const cwd = args.cwd;
|
|
206
|
+
const isAllowed = this.config.allowedCwdPaths.some((allowed) => cwd.startsWith(allowed));
|
|
207
|
+
if (!isAllowed) {
|
|
208
|
+
throw new Error(`Working directory not allowed: ${cwd}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
switch (name) {
|
|
212
|
+
case 'bash_spawn':
|
|
213
|
+
return this.ptyManager.spawn(args);
|
|
214
|
+
case 'bash_exec':
|
|
215
|
+
return this.ptyManager.exec(args);
|
|
216
|
+
case 'bash_write':
|
|
217
|
+
return this.ptyManager.write(args);
|
|
218
|
+
case 'bash_read':
|
|
219
|
+
return this.ptyManager.read(args);
|
|
220
|
+
case 'bash_list':
|
|
221
|
+
return this.ptyManager.list(args);
|
|
222
|
+
case 'bash_kill':
|
|
223
|
+
return this.ptyManager.kill(args);
|
|
224
|
+
default:
|
|
225
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Send a tool response
|
|
230
|
+
*/
|
|
231
|
+
sendToolResponse(requestId, result, isError, errorMessage) {
|
|
232
|
+
const response = {
|
|
233
|
+
type: 'tool_response',
|
|
234
|
+
request_id: requestId,
|
|
235
|
+
content: [
|
|
236
|
+
{
|
|
237
|
+
type: 'text',
|
|
238
|
+
text: JSON.stringify(result, null, 2),
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
isError,
|
|
242
|
+
...(errorMessage && { error: errorMessage }),
|
|
243
|
+
};
|
|
244
|
+
this.send(response);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Send a notification to the agent
|
|
248
|
+
*/
|
|
249
|
+
sendNotification(method, params) {
|
|
250
|
+
if (!this.isConnected)
|
|
251
|
+
return;
|
|
252
|
+
const notification = {
|
|
253
|
+
type: 'notification',
|
|
254
|
+
method,
|
|
255
|
+
params,
|
|
256
|
+
};
|
|
257
|
+
this.send(notification);
|
|
258
|
+
this.emit('notification:sent', { method, params });
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Send a message through the WebSocket
|
|
262
|
+
*/
|
|
263
|
+
send(message) {
|
|
264
|
+
if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
|
|
265
|
+
this.ws.send(JSON.stringify(message));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Schedule a reconnection attempt
|
|
270
|
+
*/
|
|
271
|
+
scheduleReconnect() {
|
|
272
|
+
if (this.reconnectTimer)
|
|
273
|
+
return;
|
|
274
|
+
this.emit('reconnecting', { interval: this.config.reconnectInterval });
|
|
275
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
276
|
+
this.reconnectTimer = null;
|
|
277
|
+
try {
|
|
278
|
+
await this.connect();
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
// Will retry via close handler
|
|
282
|
+
}
|
|
283
|
+
}, this.config.reconnectInterval);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Start heartbeat timer
|
|
287
|
+
*/
|
|
288
|
+
startHeartbeat() {
|
|
289
|
+
this.heartbeatTimer = setInterval(() => {
|
|
290
|
+
if (this.isConnected) {
|
|
291
|
+
this.send({ type: 'heartbeat' });
|
|
292
|
+
}
|
|
293
|
+
}, this.config.heartbeatInterval);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Stop heartbeat timer
|
|
297
|
+
*/
|
|
298
|
+
stopHeartbeat() {
|
|
299
|
+
if (this.heartbeatTimer) {
|
|
300
|
+
clearInterval(this.heartbeatTimer);
|
|
301
|
+
this.heartbeatTimer = null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get connection status
|
|
306
|
+
*/
|
|
307
|
+
get connected() {
|
|
308
|
+
return this.isConnected;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Get PTY manager for direct access
|
|
312
|
+
*/
|
|
313
|
+
get pty() {
|
|
314
|
+
return this.ptyManager;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
exports.WebSocketClient = WebSocketClient;
|
|
318
|
+
//# sourceMappingURL=websocket-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-client.js","sourceRoot":"","sources":["../src/websocket-client.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;AAEH,4CAA2B;AAC3B,mCAAsC;AACtC,4CAAoB;AACpB,kDAA0B;AAC1B,gDAAwB;AASxB,+CAA2C;AAC3C,mCAA2C;AAE3C,MAAa,eAAgB,SAAQ,qBAAY;IAS/C,YAAY,MAAoB;QAC9B,KAAK,EAAE,CAAC;QATF,OAAE,GAAqB,IAAI,CAAC;QAG5B,mBAAc,GAA0B,IAAI,CAAC;QAC7C,mBAAc,GAA0B,IAAI,CAAC;QAC7C,gBAAW,GAAG,KAAK,CAAC;QACpB,mBAAc,GAAG,KAAK,CAAC;QAI7B,IAAI,CAAC,MAAM,GAAG;YACZ,iBAAiB,EAAE,IAAI;YACvB,iBAAiB,EAAE,KAAK;YACxB,cAAc,EAAE,KAAK;YACrB,GAAG,MAAM;SACV,CAAC;QACF,IAAI,CAAC,UAAU,GAAG,IAAI,wBAAU,CAAC;YAC/B,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc;YAC1C,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;SACvC,CAAC,CAAC;QAEH,8BAA8B;QAC9B,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE;YAC5C,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACX,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO;QAEhC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC3C,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC;gBAEzC,yDAAyD;gBACzD,MAAM,KAAK,GAAG,QAAQ;oBACpB,CAAC,CAAC,IAAI,eAAK,CAAC,KAAK,CAAC;wBACd,kBAAkB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ;wBACzC,2CAA2C;wBAC3C,aAAa,EAAE,CAAC,UAAU,CAAC;qBAC5B,CAAC;oBACJ,CAAC,CAAC,IAAI,cAAI,CAAC,KAAK,EAAE,CAAC;gBAErB,MAAM,SAAS,GAA4B;oBACzC,KAAK;oBACL,iBAAiB,EAAE,KAAK;iBACzB,CAAC;gBAEF,IAAI,CAAC,EAAE,GAAG,IAAI,YAAS,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;gBAE1D,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;oBACtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;oBACxB,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;oBACvB,IAAI,CAAC,QAAQ,EAAE,CAAC;oBAChB,IAAI,CAAC,cAAc,EAAE,CAAC;oBACtB,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;gBAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAY,EAAE,EAAE;oBACrC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBAC3B,CAAC,CAAC,CAAC;gBAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAE;oBACnD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;oBACzB,IAAI,CAAC,aAAa,EAAE,CAAC;oBACrB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;oBAE/D,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;wBACzB,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBAC3B,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;oBACnC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;oBAC1B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;wBACtB,MAAM,CAAC,KAAK,CAAC,CAAC;oBAChB,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAE3B,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,aAAa,EAAE,CAAC;QAErB,4BAA4B;QAC5B,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;QAEjC,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;YACvC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC;IAED;;OAEG;IACK,QAAQ;QACd,2DAA2D;QAC3D,MAAM,OAAO,GAAG;YACd,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE,wBAAgB;YACvB,QAAQ,EAAE;gBACR,OAAO,EAAE,OAAO;gBAChB,QAAQ,EAAE,YAAE,CAAC,QAAQ,EAAE;gBACvB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,WAAW;aACpE;SACF,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,OAAc,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,IAAY;QAChC,IAAI,CAAC;YACH,MAAM,OAAO,GAAqB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAE9D,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;gBACrB,KAAK,WAAW;oBACd,IAAI,CAAC,cAAc,CAAC,OAA0B,CAAC,CAAC;oBAChD,MAAM;gBACR,KAAK,WAAW,CAAC;gBACjB,KAAK,eAAe;oBAClB,mCAAmC;oBACnC,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;wBACjC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;oBACnC,CAAC;oBACD,MAAM;gBACR,KAAK,kBAAkB;oBACrB,mCAAmC;oBACnC,MAAM,OAAO,GAAG,OAAgG,CAAC;oBACjH,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE;wBACtB,UAAU,EAAE,OAAO,CAAC,WAAW;wBAC/B,KAAK,EAAE,wBAAgB;wBACvB,eAAe,EAAE,OAAO,CAAC,gBAAgB;qBAC1C,CAAC,CAAC;oBACH,MAAM;gBACR,KAAK,OAAO;oBACV,MAAM,SAAS,GAAG,OAA0C,CAAC;oBAC7D,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,iBAAiB,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;oBAClE,MAAM;gBACR;oBACE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,4BAA4B,KAAK,EAAE,CAAC,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,cAAc,CAAC,OAAwB;QACnD,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QACvC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;QAEzC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;QAE9D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAClD,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YACjD,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;YAC/E,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,WAAW,CACvB,IAAY,EACZ,IAA6B;QAE7B,0CAA0C;QAC1C,IAAI,IAAI,CAAC,MAAM,CAAC,eAAe,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5D,MAAM,OAAO,GAAI,IAAI,CAAC,OAAkB,IAAI,EAAE,CAAC;YAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAChD,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,OAAO,KAAK,OAAO,CAChE,CAAC;YACF,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,gCAAgC,OAAO,EAAE,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;QAED,0CAA0C;QAC1C,IAAI,IAAI,CAAC,MAAM,CAAC,eAAe,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5D,MAAM,OAAO,GAAI,IAAI,CAAC,OAAkB,IAAI,EAAE,CAAC;YAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAChD,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CACtE,CAAC;YACF,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,uBAAuB,OAAO,EAAE,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;QAED,8CAA8C;QAC9C,IAAI,IAAI,CAAC,MAAM,CAAC,eAAe,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAa,CAAC;YAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;YACzF,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,EAAE,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;QAED,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,YAAY;gBACf,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAW,CAAC,CAAC;YAE5C,KAAK,WAAW;gBACd,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAW,CAAC,CAAC;YAE3C,KAAK,YAAY;gBACf,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAW,CAAC,CAAC;YAE5C,KAAK,WAAW;gBACd,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAW,CAAC,CAAC;YAE3C,KAAK,WAAW;gBACd,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAW,CAAC,CAAC;YAE3C,KAAK,WAAW;gBACd,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAW,CAAC,CAAC;YAE3C;gBACE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED;;OAEG;IACK,gBAAgB,CACtB,SAAiB,EACjB,MAAc,EACd,OAAgB,EAChB,YAAqB;QAErB,MAAM,QAAQ,GAAwB;YACpC,IAAI,EAAE,eAAe;YACrB,UAAU,EAAE,SAAS;YACrB,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;iBACtC;aACF;YACD,OAAO;YACP,GAAG,CAAC,YAAY,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;SAC7C,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtB,CAAC;IAED;;OAEG;IACK,gBAAgB,CAAC,MAAc,EAAE,MAA+B;QACtE,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO;QAE9B,MAAM,YAAY,GAAwB;YACxC,IAAI,EAAE,cAAc;YACpB,MAAM;YACN,MAAM;SACP,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACrD,CAAC;IAED;;OAEG;IACK,IAAI,CAAC,OAAyB;QACpC,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,YAAS,CAAC,IAAI,EAAE,CAAC;YACrD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,iBAAiB;QACvB,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO;QAEhC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAEvE,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YAC1C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACvB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,+BAA+B;YACjC,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACK,cAAc;QACpB,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACrB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;YACnC,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACK,aAAa;QACnB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;CACF;AAjWD,0CAiWC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "seahorse-bash-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reverse MCP client for providing bash/PTY tools to Seahorse Agent",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"seahorse-bash": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"postinstall": "cd node_modules/node-pty && npm run install",
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"watch": "tsc -w",
|
|
14
|
+
"start": "node dist/cli.js",
|
|
15
|
+
"dev": "ts-node src/cli.ts",
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"seahorse",
|
|
22
|
+
"mcp",
|
|
23
|
+
"bash",
|
|
24
|
+
"pty",
|
|
25
|
+
"shell",
|
|
26
|
+
"reverse-connection",
|
|
27
|
+
"agent"
|
|
28
|
+
],
|
|
29
|
+
"author": "Seahorse Team",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"chalk": "^4.1.2",
|
|
33
|
+
"commander": "^11.0.0",
|
|
34
|
+
"node-pty": "^1.0.0",
|
|
35
|
+
"ora": "^5.4.1",
|
|
36
|
+
"uuid": "^9.0.0",
|
|
37
|
+
"ws": "^8.14.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20.19.30",
|
|
41
|
+
"@types/uuid": "^9.0.0",
|
|
42
|
+
"@types/ws": "^8.5.0",
|
|
43
|
+
"ts-node": "^10.9.0",
|
|
44
|
+
"typescript": "^5.0.0",
|
|
45
|
+
"vitest": "^4.0.18"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/dn-inc/seahorse-mcp-agent-server.git"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/dn-inc/seahorse-mcp-agent-server#readme"
|
|
55
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Seahorse Bash Client CLI
|
|
4
|
+
*
|
|
5
|
+
* Connects to Seahorse Agent and provides PTY-based bash tools.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import ora from 'ora';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import { WebSocketClient } from './websocket-client';
|
|
13
|
+
import { ClientConfig } from './types';
|
|
14
|
+
|
|
15
|
+
const VERSION = '1.0.0';
|
|
16
|
+
|
|
17
|
+
const program = new Command();
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.name('seahorse-bash')
|
|
21
|
+
.description('Seahorse Bash Client - PTY-based bash tools for Seahorse Agent')
|
|
22
|
+
.version(VERSION);
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.command('connect')
|
|
26
|
+
.description('Connect to Seahorse Agent and provide bash tools')
|
|
27
|
+
.requiredOption('-u, --url <url>', 'Seahorse Agent WebSocket URL (e.g., wss://agent.example.com/ws/remote-mcp)')
|
|
28
|
+
.option('-n, --name <name>', 'Server name for identification', `bash-${os.hostname()}`)
|
|
29
|
+
.option('-s, --shell <shell>', 'Default shell to use', process.env.SHELL || '/bin/bash')
|
|
30
|
+
.option('--reconnect-interval <ms>', 'Reconnect interval in milliseconds', '5000')
|
|
31
|
+
.option('--heartbeat-interval <ms>', 'Heartbeat interval in milliseconds', '30000')
|
|
32
|
+
.option('--max-output-lines <lines>', 'Maximum output lines to buffer per session', '50000')
|
|
33
|
+
.option('--allowed-commands <cmds>', 'Comma-separated list of allowed command prefixes')
|
|
34
|
+
.option('--blocked-commands <cmds>', 'Comma-separated list of blocked command patterns')
|
|
35
|
+
.option('--allowed-cwd <paths>', 'Comma-separated list of allowed working directory paths')
|
|
36
|
+
.option('-k, --insecure', 'Skip SSL certificate verification (for self-signed certs)')
|
|
37
|
+
.action(async (options) => {
|
|
38
|
+
const spinner = ora('Connecting to Seahorse Agent...').start();
|
|
39
|
+
|
|
40
|
+
const config: ClientConfig = {
|
|
41
|
+
serverUrl: options.url,
|
|
42
|
+
serverName: options.name,
|
|
43
|
+
defaultShell: options.shell,
|
|
44
|
+
reconnectInterval: parseInt(options.reconnectInterval, 10),
|
|
45
|
+
heartbeatInterval: parseInt(options.heartbeatInterval, 10),
|
|
46
|
+
maxOutputLines: parseInt(options.maxOutputLines, 10),
|
|
47
|
+
allowedCommands: options.allowedCommands?.split(',').map((s: string) => s.trim()),
|
|
48
|
+
blockedCommands: options.blockedCommands?.split(',').map((s: string) => s.trim()),
|
|
49
|
+
allowedCwdPaths: options.allowedCwd?.split(',').map((s: string) => s.trim()),
|
|
50
|
+
insecure: options.insecure || false,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const client = new WebSocketClient(config);
|
|
54
|
+
|
|
55
|
+
// Event handlers
|
|
56
|
+
client.on('connected', () => {
|
|
57
|
+
spinner.succeed(chalk.green('Connected to Seahorse Agent'));
|
|
58
|
+
console.log(chalk.gray(` Server name: ${config.serverName}`));
|
|
59
|
+
console.log(chalk.gray(` Shell: ${config.defaultShell}`));
|
|
60
|
+
console.log(chalk.gray(` Platform: ${process.platform}`));
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(chalk.cyan('Listening for tool calls...'));
|
|
63
|
+
console.log(chalk.gray('Press Ctrl+C to disconnect'));
|
|
64
|
+
console.log();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
client.on('registered', ({ tools }) => {
|
|
68
|
+
console.log(chalk.green(`Registered ${tools.length} tools:`));
|
|
69
|
+
tools.forEach((tool: { name: string }) => {
|
|
70
|
+
console.log(chalk.gray(` - ${tool.name}`));
|
|
71
|
+
});
|
|
72
|
+
console.log();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
client.on('disconnected', ({ code, reason }) => {
|
|
76
|
+
console.log(chalk.yellow(`Disconnected (code: ${code}, reason: ${reason})`));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
client.on('reconnecting', ({ interval }) => {
|
|
80
|
+
console.log(chalk.yellow(`Reconnecting in ${interval}ms...`));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
client.on('error', (error) => {
|
|
84
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
client.on('tool:call', ({ name, requestId }) => {
|
|
88
|
+
console.log(chalk.blue(`[${new Date().toISOString()}] Tool call: ${name} (${requestId})`));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
client.on('tool:success', ({ name, requestId }) => {
|
|
92
|
+
console.log(chalk.green(`[${new Date().toISOString()}] Tool success: ${name} (${requestId})`));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
client.on('tool:error', ({ name, requestId, error }) => {
|
|
96
|
+
console.log(chalk.red(`[${new Date().toISOString()}] Tool error: ${name} (${requestId}): ${error}`));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
client.on('notification:sent', ({ method }) => {
|
|
100
|
+
console.log(chalk.magenta(`[${new Date().toISOString()}] Notification: ${method}`));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Graceful shutdown
|
|
104
|
+
const shutdown = async () => {
|
|
105
|
+
console.log();
|
|
106
|
+
console.log(chalk.yellow('Shutting down...'));
|
|
107
|
+
await client.disconnect();
|
|
108
|
+
console.log(chalk.green('Disconnected. Goodbye!'));
|
|
109
|
+
process.exit(0);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
process.on('SIGINT', shutdown);
|
|
113
|
+
process.on('SIGTERM', shutdown);
|
|
114
|
+
|
|
115
|
+
// Connect
|
|
116
|
+
try {
|
|
117
|
+
await client.connect();
|
|
118
|
+
} catch (error) {
|
|
119
|
+
spinner.fail(chalk.red(`Failed to connect: ${error instanceof Error ? error.message : error}`));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
program
|
|
125
|
+
.command('test')
|
|
126
|
+
.description('Test PTY functionality locally (without connecting to agent)')
|
|
127
|
+
.option('-c, --command <cmd>', 'Command to execute', 'echo "Hello from PTY!"')
|
|
128
|
+
.option('-s, --shell <shell>', 'Shell to use', process.env.SHELL || '/bin/bash')
|
|
129
|
+
.action(async (options) => {
|
|
130
|
+
const { PTYManager } = await import('./pty-manager');
|
|
131
|
+
const ptyManager = new PTYManager({ defaultShell: options.shell });
|
|
132
|
+
|
|
133
|
+
console.log(chalk.cyan('Testing PTY Manager...'));
|
|
134
|
+
console.log();
|
|
135
|
+
|
|
136
|
+
// Test bash_exec
|
|
137
|
+
console.log(chalk.yellow('1. Testing bash_exec:'));
|
|
138
|
+
const execResult = await ptyManager.exec({ command: options.command });
|
|
139
|
+
console.log(chalk.gray(` Exit code: ${execResult.exitCode}`));
|
|
140
|
+
console.log(chalk.gray(` Duration: ${execResult.duration}ms`));
|
|
141
|
+
console.log(chalk.gray(` Output: ${execResult.stdout}`));
|
|
142
|
+
console.log();
|
|
143
|
+
|
|
144
|
+
// Test bash_spawn
|
|
145
|
+
console.log(chalk.yellow('2. Testing bash_spawn:'));
|
|
146
|
+
const spawnResult = await ptyManager.spawn({
|
|
147
|
+
command: 'for i in 1 2 3; do echo "Count: $i"; sleep 0.5; done',
|
|
148
|
+
name: 'test-session',
|
|
149
|
+
});
|
|
150
|
+
console.log(chalk.gray(` Session ID: ${spawnResult.sessionId}`));
|
|
151
|
+
console.log(chalk.gray(` PID: ${spawnResult.pid}`));
|
|
152
|
+
|
|
153
|
+
// Wait for output
|
|
154
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
155
|
+
|
|
156
|
+
// Test bash_read
|
|
157
|
+
console.log();
|
|
158
|
+
console.log(chalk.yellow('3. Testing bash_read:'));
|
|
159
|
+
const readResult = ptyManager.read({ sessionId: spawnResult.sessionId, tail: true, limit: 10 });
|
|
160
|
+
console.log(chalk.gray(` Total lines: ${readResult.totalLines}`));
|
|
161
|
+
readResult.lines.forEach((line) => {
|
|
162
|
+
console.log(chalk.gray(` [${line.line}] ${line.text}`));
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Test bash_list
|
|
166
|
+
console.log();
|
|
167
|
+
console.log(chalk.yellow('4. Testing bash_list:'));
|
|
168
|
+
const listResult = ptyManager.list({});
|
|
169
|
+
console.log(chalk.gray(` Total sessions: ${listResult.summary.total}`));
|
|
170
|
+
console.log(chalk.gray(` Running: ${listResult.summary.running}`));
|
|
171
|
+
console.log(chalk.gray(` Exited: ${listResult.summary.exited}`));
|
|
172
|
+
|
|
173
|
+
// Cleanup
|
|
174
|
+
await ptyManager.shutdown();
|
|
175
|
+
console.log();
|
|
176
|
+
console.log(chalk.green('All tests passed!'));
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
program.parse();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seahorse Bash Client
|
|
3
|
+
*
|
|
4
|
+
* Reverse MCP client that provides PTY-based bash tools to Seahorse Agent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { WebSocketClient } from './websocket-client';
|
|
8
|
+
export { PTYManager, ptyManager } from './pty-manager';
|
|
9
|
+
export { TOOL_DEFINITIONS } from './tools';
|
|
10
|
+
export * from './types';
|