@mario.andreschak/mcp-bash 3.42.1 → 3.43.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/dist/resources.js +242 -242
- package/dist/tools.d.ts +49 -0
- package/dist/tools.js +667 -44
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/resources.js
CHANGED
|
@@ -52,247 +52,247 @@ export function bashReadResource(uri) {
|
|
|
52
52
|
},
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
-
const BASH_TERMINAL_APP_HTML = `<!doctype html>
|
|
56
|
-
<html lang="en">
|
|
57
|
-
<head>
|
|
58
|
-
<meta charset="utf-8" />
|
|
59
|
-
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
60
|
-
<style>${XTERM_CSS}</style>
|
|
61
|
-
<style>
|
|
62
|
-
:root { color-scheme:dark; --bar:#111827; --border:#334155; --fg:#e5e7eb; --muted:#94a3b8; --accent:#60a5fa; --danger:#f87171; }
|
|
63
|
-
* { box-sizing:border-box; }
|
|
64
|
-
html,body { width:100%; height:100%; margin:0; overflow:hidden; background:#050a07; color:var(--fg); font:12px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
|
|
65
|
-
body { display:flex; flex-direction:column; }
|
|
66
|
-
#toolbar { display:flex; align-items:center; gap:7px; min-height:40px; padding:5px 8px; background:var(--bar); border-bottom:1px solid var(--border); }
|
|
67
|
-
#terminal { flex:1 1 auto; min-height:180px; padding:5px; }
|
|
68
|
-
#status { flex:1 1 auto; min-width:0; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
69
|
-
.live { color:#86efac !important; }
|
|
70
|
-
.error { color:var(--danger) !important; }
|
|
71
|
-
button,select,input { min-height:28px; border:1px solid var(--border); border-radius:5px; background:#0f172a; color:var(--fg); font:inherit; padding:3px 7px; }
|
|
72
|
-
button { cursor:pointer; }
|
|
73
|
-
button:disabled { opacity:.5; cursor:not-allowed; }
|
|
74
|
-
#cwd { width:min(28vw,260px); }
|
|
75
|
-
#shell { max-width:125px; }
|
|
76
|
-
.xterm { height:100%; }
|
|
77
|
-
.xterm-viewport { scrollbar-color:#475569 #07120d; }
|
|
78
|
-
</style>
|
|
79
|
-
<script>${XTERM_JS}</script>
|
|
80
|
-
<script>${XTERM_FIT_JS}</script>
|
|
81
|
-
</head>
|
|
82
|
-
<body>
|
|
83
|
-
<div id="toolbar">
|
|
84
|
-
<select id="shell" aria-label="Shell"><option value="default">Automatic</option><option value="pwsh">PowerShell</option><option value="bash">Bash</option><option value="cmd">cmd</option></select>
|
|
85
|
-
<input id="cwd" aria-label="Working directory" value="." title="Working directory" />
|
|
86
|
-
<button id="new" type="button">New</button>
|
|
87
|
-
<button id="copy" type="button" title="Copy selection">Copy</button>
|
|
88
|
-
<button id="clear" type="button">Clear</button>
|
|
89
|
-
<button id="close" type="button">Close</button>
|
|
90
|
-
<span id="status">Initializing terminal…</span>
|
|
91
|
-
</div>
|
|
92
|
-
<div id="terminal" aria-label="Interactive terminal"></div>
|
|
93
|
-
<script>
|
|
94
|
-
(function () {
|
|
95
|
-
"use strict";
|
|
96
|
-
var nextId = 1;
|
|
97
|
-
var pending = {};
|
|
98
|
-
var sessionId = "";
|
|
99
|
-
var cursor = 0;
|
|
100
|
-
var running = false;
|
|
101
|
-
var pollTimer = null;
|
|
102
|
-
var pollBusy = false;
|
|
103
|
-
var opening = false;
|
|
104
|
-
var inputQueue = "";
|
|
105
|
-
var inputBusy = false;
|
|
106
|
-
var resizeTimer = null;
|
|
107
|
-
var lastToolInput = {};
|
|
108
|
-
|
|
109
|
-
var statusEl = document.getElementById("status");
|
|
110
|
-
var shellEl = document.getElementById("shell");
|
|
111
|
-
var cwdEl = document.getElementById("cwd");
|
|
112
|
-
var terminal = new Terminal({
|
|
113
|
-
cursorBlink:true,
|
|
114
|
-
convertEol:false,
|
|
115
|
-
scrollback:10000,
|
|
116
|
-
fontFamily:'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
|
|
117
|
-
fontSize:13,
|
|
118
|
-
theme:{ background:'#050a07', foreground:'#d1fae5', cursor:'#86efac', selectionBackground:'#2563eb88' }
|
|
119
|
-
});
|
|
120
|
-
var fitAddon = new FitAddon.FitAddon();
|
|
121
|
-
terminal.loadAddon(fitAddon);
|
|
122
|
-
terminal.open(document.getElementById("terminal"));
|
|
123
|
-
fitAddon.fit();
|
|
124
|
-
|
|
125
|
-
function post(message) { window.parent.postMessage(message, "*"); }
|
|
126
|
-
function rpc(method, params) {
|
|
127
|
-
return new Promise(function (resolve, reject) {
|
|
128
|
-
var id = nextId++;
|
|
129
|
-
pending[id] = { resolve:resolve, reject:reject };
|
|
130
|
-
post({ jsonrpc:"2.0", id:id, method:method, params:params || {} });
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
function notify(method, params) { post({ jsonrpc:"2.0", method:method, params:params || {} }); }
|
|
134
|
-
function payloadOf(result) {
|
|
135
|
-
if (!result) return null;
|
|
136
|
-
if (result.structuredContent) return result.structuredContent;
|
|
137
|
-
try {
|
|
138
|
-
var text = result.content && result.content[0] && result.content[0].text;
|
|
139
|
-
return text ? JSON.parse(text) : null;
|
|
140
|
-
} catch (e) { return null; }
|
|
141
|
-
}
|
|
142
|
-
async function callTool(name, args) {
|
|
143
|
-
var result = await rpc("tools/call", { name:name, arguments:args || {} });
|
|
144
|
-
var data = payloadOf(result) || {};
|
|
145
|
-
if (result && result.isError) throw new Error(data.error || name + " failed");
|
|
146
|
-
return data;
|
|
147
|
-
}
|
|
148
|
-
function setStatus(message, kind) {
|
|
149
|
-
statusEl.textContent = message;
|
|
150
|
-
statusEl.className = kind || "";
|
|
151
|
-
}
|
|
152
|
-
function schedulePoll(delay) {
|
|
153
|
-
if (pollTimer) clearTimeout(pollTimer);
|
|
154
|
-
pollTimer = sessionId ? setTimeout(poll, delay == null ? (running ? 60 : 500) : delay) : null;
|
|
155
|
-
}
|
|
156
|
-
function acceptSession(data) {
|
|
157
|
-
if (!data || !data.sessionId) return false;
|
|
158
|
-
sessionId = String(data.sessionId);
|
|
159
|
-
cursor = Number(data.cursor || data.nextCursor || 0);
|
|
160
|
-
running = data.running !== false;
|
|
161
|
-
setStatus((running ? "● " : "○ ") + (data.shell || "terminal") + " — " + (data.cwd || sessionId), running ? "live" : "");
|
|
162
|
-
schedulePoll(0);
|
|
163
|
-
terminal.focus();
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
async function openTerminal() {
|
|
167
|
-
if (opening) return;
|
|
168
|
-
opening = true;
|
|
169
|
-
document.getElementById("new").disabled = true;
|
|
170
|
-
try {
|
|
171
|
-
if (sessionId && running) await callTool("terminal_close", { sessionId:sessionId }).catch(function () {});
|
|
172
|
-
terminal.reset();
|
|
173
|
-
terminal.clear();
|
|
174
|
-
var data = await callTool("open_terminal", {
|
|
175
|
-
shell:shellEl.value,
|
|
176
|
-
cwd:cwdEl.value.trim() || ".",
|
|
177
|
-
cols:terminal.cols,
|
|
178
|
-
rows:terminal.rows
|
|
179
|
-
});
|
|
180
|
-
acceptSession(data);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
setStatus(error && error.message ? error.message : String(error), "error");
|
|
183
|
-
terminal.writeln("\\r\\n\\x1b[31m" + (error && error.message ? error.message : String(error)) + "\\x1b[0m");
|
|
184
|
-
} finally {
|
|
185
|
-
opening = false;
|
|
186
|
-
document.getElementById("new").disabled = false;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
async function poll() {
|
|
190
|
-
if (!sessionId || pollBusy) return;
|
|
191
|
-
pollBusy = true;
|
|
192
|
-
try {
|
|
193
|
-
var data = await callTool("terminal_read", { sessionId:sessionId, cursor:cursor });
|
|
194
|
-
if (data.reset) { terminal.reset(); terminal.clear(); }
|
|
195
|
-
if (typeof data.chunk === "string" && data.chunk) terminal.write(data.chunk);
|
|
196
|
-
cursor = Number(data.nextCursor == null ? cursor : data.nextCursor);
|
|
197
|
-
running = data.running !== false;
|
|
198
|
-
setStatus((running ? "● " : "○ ") + (data.shell || "terminal") + " — " + (data.cwd || sessionId) + (running ? "" : " — exited " + String(data.exitCode)), running ? "live" : "");
|
|
199
|
-
} catch (error) {
|
|
200
|
-
running = false;
|
|
201
|
-
setStatus(error && error.message ? error.message : String(error), "error");
|
|
202
|
-
} finally {
|
|
203
|
-
pollBusy = false;
|
|
204
|
-
schedulePoll();
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
async function flushInput() {
|
|
208
|
-
if (inputBusy || !inputQueue || !sessionId || !running) return;
|
|
209
|
-
inputBusy = true;
|
|
210
|
-
var data = inputQueue;
|
|
211
|
-
inputQueue = "";
|
|
212
|
-
try { await callTool("terminal_write", { sessionId:sessionId, data:data }); }
|
|
213
|
-
catch (error) { setStatus(error && error.message ? error.message : String(error), "error"); }
|
|
214
|
-
finally { inputBusy = false; if (inputQueue) setTimeout(flushInput, 0); }
|
|
215
|
-
}
|
|
216
|
-
function sendResize() {
|
|
217
|
-
if (!sessionId || !running) return;
|
|
218
|
-
callTool("terminal_resize", { sessionId:sessionId, cols:terminal.cols, rows:terminal.rows }).catch(function () {});
|
|
219
|
-
}
|
|
220
|
-
function acceptToolResult(result) {
|
|
221
|
-
var data = payloadOf(result) || {};
|
|
222
|
-
acceptSession(data);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
terminal.onData(function (data) {
|
|
226
|
-
inputQueue += data;
|
|
227
|
-
setTimeout(flushInput, 0);
|
|
228
|
-
});
|
|
229
|
-
terminal.onResize(function () {
|
|
230
|
-
if (resizeTimer) clearTimeout(resizeTimer);
|
|
231
|
-
resizeTimer = setTimeout(sendResize, 100);
|
|
232
|
-
});
|
|
233
|
-
window.addEventListener("resize", function () { try { fitAddon.fit(); } catch (e) {} });
|
|
234
|
-
document.getElementById("new").onclick = openTerminal;
|
|
235
|
-
document.getElementById("clear").onclick = function () { terminal.clear(); };
|
|
236
|
-
document.getElementById("copy").onclick = function () {
|
|
237
|
-
var text = terminal.getSelection();
|
|
238
|
-
if (!text) { setStatus("Select terminal text first.", ""); return; }
|
|
239
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
240
|
-
navigator.clipboard.writeText(text).then(function () { setStatus("Selection copied.", ""); }).catch(function () { setStatus("Copy was denied by the host.", "error"); });
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
document.getElementById("close").onclick = async function () {
|
|
244
|
-
if (!sessionId) return;
|
|
245
|
-
await callTool("terminal_close", { sessionId:sessionId }).catch(function () {});
|
|
246
|
-
running = false;
|
|
247
|
-
setStatus("Terminal closed.", "");
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
window.addEventListener("message", function (event) {
|
|
251
|
-
var message = event.data;
|
|
252
|
-
if (!message || message.jsonrpc !== "2.0") return;
|
|
253
|
-
if (message.id !== undefined && (message.result !== undefined || message.error !== undefined)) {
|
|
254
|
-
var waiting = pending[message.id];
|
|
255
|
-
if (!waiting) return;
|
|
256
|
-
delete pending[message.id];
|
|
257
|
-
if (message.error) waiting.reject(new Error(message.error.message || "RPC error"));
|
|
258
|
-
else waiting.resolve(message.result);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
if (message.method === "ui/notifications/tool-input") {
|
|
262
|
-
lastToolInput = message.params && message.params.arguments || {};
|
|
263
|
-
if (typeof lastToolInput.cwd === "string") cwdEl.value = lastToolInput.cwd;
|
|
264
|
-
if (typeof lastToolInput.shell === "string") shellEl.value = lastToolInput.shell;
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
if (message.method === "ui/notifications/tool-result") { acceptToolResult(message.params && message.params.result ? message.params.result : message.params); return; }
|
|
268
|
-
if (message.method === "ping" && message.id !== undefined) { post({ jsonrpc:"2.0", id:message.id, result:{} }); return; }
|
|
269
|
-
if (message.method === "ui/resource-teardown" && message.id !== undefined) {
|
|
270
|
-
// Dock/fullscreen transitions tear down Views; the owner-scoped PTY stays
|
|
271
|
-
// alive so the replacement View can reattach through terminal_list.
|
|
272
|
-
post({ jsonrpc:"2.0", id:message.id, result:{} });
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
rpc("ui/initialize", {
|
|
277
|
-
appInfo:{ name:"bash-terminal", version:"2.0.0" },
|
|
278
|
-
appCapabilities:{ availableDisplayModes:["inline","fullscreen","pip"] },
|
|
279
|
-
protocolVersion:"${MCP_APPS_PROTOCOL_VERSION}"
|
|
280
|
-
}).then(async function () {
|
|
281
|
-
notify("ui/notifications/initialized", {});
|
|
282
|
-
try { fitAddon.fit(); } catch (e) {}
|
|
283
|
-
setTimeout(async function () {
|
|
284
|
-
if (sessionId) return;
|
|
285
|
-
try {
|
|
286
|
-
var listed = await callTool("terminal_list", {});
|
|
287
|
-
var sessions = Array.isArray(listed.sessions) ? listed.sessions : [];
|
|
288
|
-
var active = sessions.filter(function (item) { return item.running; }).pop();
|
|
289
|
-
if (!acceptSession(active)) await openTerminal();
|
|
290
|
-
} catch (e) { await openTerminal(); }
|
|
291
|
-
}, 350);
|
|
292
|
-
rpc("ui/request-display-mode", { mode:"pip" }).catch(function () {});
|
|
293
|
-
}).catch(function (error) { setStatus("MCP App initialization failed: " + (error && error.message ? error.message : error), "error"); });
|
|
294
|
-
})();
|
|
295
|
-
</script>
|
|
296
|
-
</body>
|
|
55
|
+
const BASH_TERMINAL_APP_HTML = `<!doctype html>
|
|
56
|
+
<html lang="en">
|
|
57
|
+
<head>
|
|
58
|
+
<meta charset="utf-8" />
|
|
59
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
60
|
+
<style>${XTERM_CSS}</style>
|
|
61
|
+
<style>
|
|
62
|
+
:root { color-scheme:dark; --bar:#111827; --border:#334155; --fg:#e5e7eb; --muted:#94a3b8; --accent:#60a5fa; --danger:#f87171; }
|
|
63
|
+
* { box-sizing:border-box; }
|
|
64
|
+
html,body { width:100%; height:100%; margin:0; overflow:hidden; background:#050a07; color:var(--fg); font:12px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
|
|
65
|
+
body { display:flex; flex-direction:column; }
|
|
66
|
+
#toolbar { display:flex; align-items:center; gap:7px; min-height:40px; padding:5px 8px; background:var(--bar); border-bottom:1px solid var(--border); }
|
|
67
|
+
#terminal { flex:1 1 auto; min-height:180px; padding:5px; }
|
|
68
|
+
#status { flex:1 1 auto; min-width:0; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
69
|
+
.live { color:#86efac !important; }
|
|
70
|
+
.error { color:var(--danger) !important; }
|
|
71
|
+
button,select,input { min-height:28px; border:1px solid var(--border); border-radius:5px; background:#0f172a; color:var(--fg); font:inherit; padding:3px 7px; }
|
|
72
|
+
button { cursor:pointer; }
|
|
73
|
+
button:disabled { opacity:.5; cursor:not-allowed; }
|
|
74
|
+
#cwd { width:min(28vw,260px); }
|
|
75
|
+
#shell { max-width:125px; }
|
|
76
|
+
.xterm { height:100%; }
|
|
77
|
+
.xterm-viewport { scrollbar-color:#475569 #07120d; }
|
|
78
|
+
</style>
|
|
79
|
+
<script>${XTERM_JS}</script>
|
|
80
|
+
<script>${XTERM_FIT_JS}</script>
|
|
81
|
+
</head>
|
|
82
|
+
<body>
|
|
83
|
+
<div id="toolbar">
|
|
84
|
+
<select id="shell" aria-label="Shell"><option value="default">Automatic</option><option value="pwsh">PowerShell</option><option value="bash">Bash</option><option value="cmd">cmd</option></select>
|
|
85
|
+
<input id="cwd" aria-label="Working directory" value="." title="Working directory" />
|
|
86
|
+
<button id="new" type="button">New</button>
|
|
87
|
+
<button id="copy" type="button" title="Copy selection">Copy</button>
|
|
88
|
+
<button id="clear" type="button">Clear</button>
|
|
89
|
+
<button id="close" type="button">Close</button>
|
|
90
|
+
<span id="status">Initializing terminal…</span>
|
|
91
|
+
</div>
|
|
92
|
+
<div id="terminal" aria-label="Interactive terminal"></div>
|
|
93
|
+
<script>
|
|
94
|
+
(function () {
|
|
95
|
+
"use strict";
|
|
96
|
+
var nextId = 1;
|
|
97
|
+
var pending = {};
|
|
98
|
+
var sessionId = "";
|
|
99
|
+
var cursor = 0;
|
|
100
|
+
var running = false;
|
|
101
|
+
var pollTimer = null;
|
|
102
|
+
var pollBusy = false;
|
|
103
|
+
var opening = false;
|
|
104
|
+
var inputQueue = "";
|
|
105
|
+
var inputBusy = false;
|
|
106
|
+
var resizeTimer = null;
|
|
107
|
+
var lastToolInput = {};
|
|
108
|
+
|
|
109
|
+
var statusEl = document.getElementById("status");
|
|
110
|
+
var shellEl = document.getElementById("shell");
|
|
111
|
+
var cwdEl = document.getElementById("cwd");
|
|
112
|
+
var terminal = new Terminal({
|
|
113
|
+
cursorBlink:true,
|
|
114
|
+
convertEol:false,
|
|
115
|
+
scrollback:10000,
|
|
116
|
+
fontFamily:'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
|
|
117
|
+
fontSize:13,
|
|
118
|
+
theme:{ background:'#050a07', foreground:'#d1fae5', cursor:'#86efac', selectionBackground:'#2563eb88' }
|
|
119
|
+
});
|
|
120
|
+
var fitAddon = new FitAddon.FitAddon();
|
|
121
|
+
terminal.loadAddon(fitAddon);
|
|
122
|
+
terminal.open(document.getElementById("terminal"));
|
|
123
|
+
fitAddon.fit();
|
|
124
|
+
|
|
125
|
+
function post(message) { window.parent.postMessage(message, "*"); }
|
|
126
|
+
function rpc(method, params) {
|
|
127
|
+
return new Promise(function (resolve, reject) {
|
|
128
|
+
var id = nextId++;
|
|
129
|
+
pending[id] = { resolve:resolve, reject:reject };
|
|
130
|
+
post({ jsonrpc:"2.0", id:id, method:method, params:params || {} });
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function notify(method, params) { post({ jsonrpc:"2.0", method:method, params:params || {} }); }
|
|
134
|
+
function payloadOf(result) {
|
|
135
|
+
if (!result) return null;
|
|
136
|
+
if (result.structuredContent) return result.structuredContent;
|
|
137
|
+
try {
|
|
138
|
+
var text = result.content && result.content[0] && result.content[0].text;
|
|
139
|
+
return text ? JSON.parse(text) : null;
|
|
140
|
+
} catch (e) { return null; }
|
|
141
|
+
}
|
|
142
|
+
async function callTool(name, args) {
|
|
143
|
+
var result = await rpc("tools/call", { name:name, arguments:args || {} });
|
|
144
|
+
var data = payloadOf(result) || {};
|
|
145
|
+
if (result && result.isError) throw new Error(data.error || name + " failed");
|
|
146
|
+
return data;
|
|
147
|
+
}
|
|
148
|
+
function setStatus(message, kind) {
|
|
149
|
+
statusEl.textContent = message;
|
|
150
|
+
statusEl.className = kind || "";
|
|
151
|
+
}
|
|
152
|
+
function schedulePoll(delay) {
|
|
153
|
+
if (pollTimer) clearTimeout(pollTimer);
|
|
154
|
+
pollTimer = sessionId ? setTimeout(poll, delay == null ? (running ? 60 : 500) : delay) : null;
|
|
155
|
+
}
|
|
156
|
+
function acceptSession(data) {
|
|
157
|
+
if (!data || !data.sessionId) return false;
|
|
158
|
+
sessionId = String(data.sessionId);
|
|
159
|
+
cursor = Number(data.cursor || data.nextCursor || 0);
|
|
160
|
+
running = data.running !== false;
|
|
161
|
+
setStatus((running ? "● " : "○ ") + (data.shell || "terminal") + " — " + (data.cwd || sessionId), running ? "live" : "");
|
|
162
|
+
schedulePoll(0);
|
|
163
|
+
terminal.focus();
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
async function openTerminal() {
|
|
167
|
+
if (opening) return;
|
|
168
|
+
opening = true;
|
|
169
|
+
document.getElementById("new").disabled = true;
|
|
170
|
+
try {
|
|
171
|
+
if (sessionId && running) await callTool("terminal_close", { sessionId:sessionId }).catch(function () {});
|
|
172
|
+
terminal.reset();
|
|
173
|
+
terminal.clear();
|
|
174
|
+
var data = await callTool("open_terminal", {
|
|
175
|
+
shell:shellEl.value,
|
|
176
|
+
cwd:cwdEl.value.trim() || ".",
|
|
177
|
+
cols:terminal.cols,
|
|
178
|
+
rows:terminal.rows
|
|
179
|
+
});
|
|
180
|
+
acceptSession(data);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
setStatus(error && error.message ? error.message : String(error), "error");
|
|
183
|
+
terminal.writeln("\\r\\n\\x1b[31m" + (error && error.message ? error.message : String(error)) + "\\x1b[0m");
|
|
184
|
+
} finally {
|
|
185
|
+
opening = false;
|
|
186
|
+
document.getElementById("new").disabled = false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function poll() {
|
|
190
|
+
if (!sessionId || pollBusy) return;
|
|
191
|
+
pollBusy = true;
|
|
192
|
+
try {
|
|
193
|
+
var data = await callTool("terminal_read", { sessionId:sessionId, cursor:cursor });
|
|
194
|
+
if (data.reset) { terminal.reset(); terminal.clear(); }
|
|
195
|
+
if (typeof data.chunk === "string" && data.chunk) terminal.write(data.chunk);
|
|
196
|
+
cursor = Number(data.nextCursor == null ? cursor : data.nextCursor);
|
|
197
|
+
running = data.running !== false;
|
|
198
|
+
setStatus((running ? "● " : "○ ") + (data.shell || "terminal") + " — " + (data.cwd || sessionId) + (running ? "" : " — exited " + String(data.exitCode)), running ? "live" : "");
|
|
199
|
+
} catch (error) {
|
|
200
|
+
running = false;
|
|
201
|
+
setStatus(error && error.message ? error.message : String(error), "error");
|
|
202
|
+
} finally {
|
|
203
|
+
pollBusy = false;
|
|
204
|
+
schedulePoll();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function flushInput() {
|
|
208
|
+
if (inputBusy || !inputQueue || !sessionId || !running) return;
|
|
209
|
+
inputBusy = true;
|
|
210
|
+
var data = inputQueue;
|
|
211
|
+
inputQueue = "";
|
|
212
|
+
try { await callTool("terminal_write", { sessionId:sessionId, data:data }); }
|
|
213
|
+
catch (error) { setStatus(error && error.message ? error.message : String(error), "error"); }
|
|
214
|
+
finally { inputBusy = false; if (inputQueue) setTimeout(flushInput, 0); }
|
|
215
|
+
}
|
|
216
|
+
function sendResize() {
|
|
217
|
+
if (!sessionId || !running) return;
|
|
218
|
+
callTool("terminal_resize", { sessionId:sessionId, cols:terminal.cols, rows:terminal.rows }).catch(function () {});
|
|
219
|
+
}
|
|
220
|
+
function acceptToolResult(result) {
|
|
221
|
+
var data = payloadOf(result) || {};
|
|
222
|
+
acceptSession(data);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
terminal.onData(function (data) {
|
|
226
|
+
inputQueue += data;
|
|
227
|
+
setTimeout(flushInput, 0);
|
|
228
|
+
});
|
|
229
|
+
terminal.onResize(function () {
|
|
230
|
+
if (resizeTimer) clearTimeout(resizeTimer);
|
|
231
|
+
resizeTimer = setTimeout(sendResize, 100);
|
|
232
|
+
});
|
|
233
|
+
window.addEventListener("resize", function () { try { fitAddon.fit(); } catch (e) {} });
|
|
234
|
+
document.getElementById("new").onclick = openTerminal;
|
|
235
|
+
document.getElementById("clear").onclick = function () { terminal.clear(); };
|
|
236
|
+
document.getElementById("copy").onclick = function () {
|
|
237
|
+
var text = terminal.getSelection();
|
|
238
|
+
if (!text) { setStatus("Select terminal text first.", ""); return; }
|
|
239
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
240
|
+
navigator.clipboard.writeText(text).then(function () { setStatus("Selection copied.", ""); }).catch(function () { setStatus("Copy was denied by the host.", "error"); });
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
document.getElementById("close").onclick = async function () {
|
|
244
|
+
if (!sessionId) return;
|
|
245
|
+
await callTool("terminal_close", { sessionId:sessionId }).catch(function () {});
|
|
246
|
+
running = false;
|
|
247
|
+
setStatus("Terminal closed.", "");
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
window.addEventListener("message", function (event) {
|
|
251
|
+
var message = event.data;
|
|
252
|
+
if (!message || message.jsonrpc !== "2.0") return;
|
|
253
|
+
if (message.id !== undefined && (message.result !== undefined || message.error !== undefined)) {
|
|
254
|
+
var waiting = pending[message.id];
|
|
255
|
+
if (!waiting) return;
|
|
256
|
+
delete pending[message.id];
|
|
257
|
+
if (message.error) waiting.reject(new Error(message.error.message || "RPC error"));
|
|
258
|
+
else waiting.resolve(message.result);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (message.method === "ui/notifications/tool-input") {
|
|
262
|
+
lastToolInput = message.params && message.params.arguments || {};
|
|
263
|
+
if (typeof lastToolInput.cwd === "string") cwdEl.value = lastToolInput.cwd;
|
|
264
|
+
if (typeof lastToolInput.shell === "string") shellEl.value = lastToolInput.shell;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (message.method === "ui/notifications/tool-result") { acceptToolResult(message.params && message.params.result ? message.params.result : message.params); return; }
|
|
268
|
+
if (message.method === "ping" && message.id !== undefined) { post({ jsonrpc:"2.0", id:message.id, result:{} }); return; }
|
|
269
|
+
if (message.method === "ui/resource-teardown" && message.id !== undefined) {
|
|
270
|
+
// Dock/fullscreen transitions tear down Views; the owner-scoped PTY stays
|
|
271
|
+
// alive so the replacement View can reattach through terminal_list.
|
|
272
|
+
post({ jsonrpc:"2.0", id:message.id, result:{} });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
rpc("ui/initialize", {
|
|
277
|
+
appInfo:{ name:"bash-terminal", version:"2.0.0" },
|
|
278
|
+
appCapabilities:{ availableDisplayModes:["inline","fullscreen","pip"] },
|
|
279
|
+
protocolVersion:"${MCP_APPS_PROTOCOL_VERSION}"
|
|
280
|
+
}).then(async function () {
|
|
281
|
+
notify("ui/notifications/initialized", {});
|
|
282
|
+
try { fitAddon.fit(); } catch (e) {}
|
|
283
|
+
setTimeout(async function () {
|
|
284
|
+
if (sessionId) return;
|
|
285
|
+
try {
|
|
286
|
+
var listed = await callTool("terminal_list", {});
|
|
287
|
+
var sessions = Array.isArray(listed.sessions) ? listed.sessions : [];
|
|
288
|
+
var active = sessions.filter(function (item) { return item.running; }).pop();
|
|
289
|
+
if (!acceptSession(active)) await openTerminal();
|
|
290
|
+
} catch (e) { await openTerminal(); }
|
|
291
|
+
}, 350);
|
|
292
|
+
rpc("ui/request-display-mode", { mode:"pip" }).catch(function () {});
|
|
293
|
+
}).catch(function (error) { setStatus("MCP App initialization failed: " + (error && error.message ? error.message : error), "error"); });
|
|
294
|
+
})();
|
|
295
|
+
</script>
|
|
296
|
+
</body>
|
|
297
297
|
</html>`;
|
|
298
298
|
//# sourceMappingURL=resources.js.map
|
package/dist/tools.d.ts
CHANGED
|
@@ -11,6 +11,28 @@ export interface BashExecutionContext {
|
|
|
11
11
|
signal?: AbortSignal;
|
|
12
12
|
onProgress?: (progress: BashToolProgress) => void | Promise<void>;
|
|
13
13
|
}
|
|
14
|
+
export interface ShellInfoShellEntry {
|
|
15
|
+
shell: string;
|
|
16
|
+
available: boolean;
|
|
17
|
+
path: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface ShellInfoBinaryEntry {
|
|
20
|
+
name: string;
|
|
21
|
+
found: boolean;
|
|
22
|
+
path: string | null;
|
|
23
|
+
/** Windows Store app-execution alias stub: present on PATH but not runnable. */
|
|
24
|
+
alias?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface ShellInfoPayload {
|
|
27
|
+
platform: NodeJS.Platform;
|
|
28
|
+
defaultShell: EffectiveShell;
|
|
29
|
+
defaultShellPath: string | null;
|
|
30
|
+
shells: ShellInfoShellEntry[];
|
|
31
|
+
binaries: ShellInfoBinaryEntry[];
|
|
32
|
+
notes: string[];
|
|
33
|
+
}
|
|
34
|
+
/** Describe the shells and interpreters actually available on this machine. */
|
|
35
|
+
export declare function collectShellInfo(): ShellInfoPayload;
|
|
14
36
|
/** Test-only: forget cached shell-executable lookups. */
|
|
15
37
|
export declare function _resetBashShellCacheForTests(): void;
|
|
16
38
|
interface BashSession {
|
|
@@ -22,12 +44,16 @@ interface BashSession {
|
|
|
22
44
|
child: ChildProcess;
|
|
23
45
|
output: string;
|
|
24
46
|
truncated: boolean;
|
|
47
|
+
/** Characters that arrived on stderr (logging volume, not failure). */
|
|
48
|
+
stderrChars?: number;
|
|
25
49
|
running: boolean;
|
|
26
50
|
exitCode: number | null;
|
|
27
51
|
startedAt: number;
|
|
28
52
|
endedAt?: number;
|
|
29
53
|
cancelEscalation?: () => void;
|
|
30
54
|
reapTimer?: NodeJS.Timeout;
|
|
55
|
+
/** Preflight found a command head that cannot be resolved on PATH. */
|
|
56
|
+
missingExecutableWarning?: boolean;
|
|
31
57
|
}
|
|
32
58
|
interface TerminalSession {
|
|
33
59
|
id: string;
|
|
@@ -52,6 +78,29 @@ declare global {
|
|
|
52
78
|
}
|
|
53
79
|
/** Kill every live session's process tree — used on FLUJO process exit. */
|
|
54
80
|
export declare function shutdownBashSessions(): void;
|
|
81
|
+
export declare function wrapPowerShellCommand(command: string): string;
|
|
82
|
+
/** `chcp 65001` forces UTF-8 output for cmd.exe children (issue #364). */
|
|
83
|
+
export declare function wrapCmdCommand(command: string): string;
|
|
84
|
+
export interface OutputAppender {
|
|
85
|
+
(chunk: string): void;
|
|
86
|
+
/** Total characters ever appended, including any omitted middle. */
|
|
87
|
+
totalChars: () => number;
|
|
88
|
+
/** Characters dropped from the middle because of the cap. */
|
|
89
|
+
droppedChars: () => number;
|
|
90
|
+
}
|
|
91
|
+
/** True when the command relies on POSIX `&&` / `||` statement chaining. */
|
|
92
|
+
export declare function commandUsesPosixChaining(command: string): boolean;
|
|
93
|
+
export declare function detectDialectMismatch(command: string, shell: EffectiveShell, isAvailable?: (name: string) => boolean): string[];
|
|
94
|
+
/** Commands that typically hang forever waiting on a pager or interactive prompt. */
|
|
95
|
+
export declare function detectInteractiveHangRisk(command: string): string[];
|
|
96
|
+
export type OutputEncodingMode = 'utf8' | 'utf16le' | 'auto';
|
|
97
|
+
/**
|
|
98
|
+
* Decode child output deterministically (issue #364). Windows tooling happily
|
|
99
|
+
* writes UTF-16LE (e.g. PowerShell redirection into a log) which the default
|
|
100
|
+
* UTF-8 decode turns into NUL-riddled mojibake. `auto` sniffs a BOM or a high
|
|
101
|
+
* NUL ratio in the first chunk and then stays with that choice.
|
|
102
|
+
*/
|
|
103
|
+
export declare function createStreamDecoder(mode?: OutputEncodingMode): (chunk: Buffer | string) => string;
|
|
55
104
|
export declare function bashToolDefinitions(): Tool[];
|
|
56
105
|
export declare function bashCallTool(toolName: string, args: Record<string, unknown>, callerNodeId?: string, ownerScope?: string, context?: BashExecutionContext): Promise<CallToolResult>;
|
|
57
106
|
/** Test-only: force-kill and clear all sessions. */
|