@mario.andreschak/mcp-bash 3.42.1 → 3.44.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/index.js CHANGED
@@ -66,17 +66,38 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
66
66
  });
67
67
  const transport = new StdioServerTransport();
68
68
  let closing = false;
69
- async function shutdown() {
69
+ /**
70
+ * Idempotent shutdown (issue #413).
71
+ *
72
+ * Every way this process can be told to stop must land here, because the ONLY
73
+ * thing that kills a live session's descendant tree is `shutdownBashSessions()`.
74
+ * The critical addition is stdin EOF: the MCP stdio convention is that the host
75
+ * closes stdin to request shutdown, and a host that then exits (or is killed)
76
+ * never sends a signal at all. Without an EOF path this server — and every
77
+ * background command and PTY it had spawned — survived its own host.
78
+ */
79
+ async function shutdown(reason) {
70
80
  if (closing)
71
81
  return;
72
82
  closing = true;
73
- shutdownBashSessions();
83
+ process.stderr.write(`@mario.andreschak/mcp-bash shutting down (${reason})\n`);
84
+ // Stop accepting new work BEFORE tearing anything down, so a request that
85
+ // arrives mid-shutdown cannot start a process nothing will ever kill.
74
86
  configureRootsProvider(undefined);
87
+ shutdownBashSessions();
75
88
  await server.close().catch(() => undefined);
76
89
  }
77
- process.once('SIGINT', () => void shutdown());
78
- process.once('SIGTERM', () => void shutdown());
79
- process.once('SIGHUP', () => void shutdown());
90
+ process.once('SIGINT', () => void shutdown('SIGINT'));
91
+ process.once('SIGTERM', () => void shutdown('SIGTERM'));
92
+ process.once('SIGHUP', () => void shutdown('SIGHUP'));
93
+ // stdio EOF: the host closed our input, so no further request can arrive.
94
+ process.stdin.once('end', () => void shutdown('stdin end'));
95
+ process.stdin.once('close', () => void shutdown('stdin close'));
96
+ // Transport close covers the SDK-side teardown (host closed the session). This
97
+ // must hook the SERVER's callback, not the transport's: Protocol.connect()
98
+ // installs its own transport.onclose, so assigning that directly would be
99
+ // silently overwritten by server.connect() below.
100
+ server.onclose = () => void shutdown('transport close');
80
101
  server.connect(transport).catch((error) => {
81
102
  shutdownBashSessions();
82
103
  process.stderr.write(`@mario.andreschak/mcp-bash failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
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