persistent-terminal-mcp 1.0.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/CHANGELOG.md +278 -0
- package/LICENSE +21 -0
- package/README.en.md +259 -0
- package/README.md +608 -0
- package/dist/examples/basic-usage.d.ts +3 -0
- package/dist/examples/basic-usage.d.ts.map +1 -0
- package/dist/examples/basic-usage.js +116 -0
- package/dist/examples/basic-usage.js.map +1 -0
- package/dist/examples/codex-bug-fix-demo.d.ts +3 -0
- package/dist/examples/codex-bug-fix-demo.d.ts.map +1 -0
- package/dist/examples/codex-bug-fix-demo.js +134 -0
- package/dist/examples/codex-bug-fix-demo.js.map +1 -0
- package/dist/examples/interactive-demo.d.ts +3 -0
- package/dist/examples/interactive-demo.d.ts.map +1 -0
- package/dist/examples/interactive-demo.js +235 -0
- package/dist/examples/interactive-demo.js.map +1 -0
- package/dist/examples/rest-api-demo.d.ts +7 -0
- package/dist/examples/rest-api-demo.d.ts.map +1 -0
- package/dist/examples/rest-api-demo.js +160 -0
- package/dist/examples/rest-api-demo.js.map +1 -0
- package/dist/examples/smart-reading-demo.d.ts +3 -0
- package/dist/examples/smart-reading-demo.d.ts.map +1 -0
- package/dist/examples/smart-reading-demo.js +153 -0
- package/dist/examples/smart-reading-demo.js.map +1 -0
- package/dist/examples/test-all-9-tools.d.ts +8 -0
- package/dist/examples/test-all-9-tools.d.ts.map +1 -0
- package/dist/examples/test-all-9-tools.js +162 -0
- package/dist/examples/test-all-9-tools.js.map +1 -0
- package/dist/examples/test-all-tools.d.ts +3 -0
- package/dist/examples/test-all-tools.d.ts.map +1 -0
- package/dist/examples/test-all-tools.js +150 -0
- package/dist/examples/test-all-tools.js.map +1 -0
- package/dist/examples/test-fixes.d.ts +9 -0
- package/dist/examples/test-fixes.d.ts.map +1 -0
- package/dist/examples/test-fixes.js +168 -0
- package/dist/examples/test-fixes.js.map +1 -0
- package/dist/examples/test-spinner-compaction.d.ts +8 -0
- package/dist/examples/test-spinner-compaction.d.ts.map +1 -0
- package/dist/examples/test-spinner-compaction.js +155 -0
- package/dist/examples/test-spinner-compaction.js.map +1 -0
- package/dist/examples/test-web-ui.d.ts +12 -0
- package/dist/examples/test-web-ui.d.ts.map +1 -0
- package/dist/examples/test-web-ui.js +90 -0
- package/dist/examples/test-web-ui.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +49 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +1056 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/output-buffer.d.ts +134 -0
- package/dist/output-buffer.d.ts.map +1 -0
- package/dist/output-buffer.js +553 -0
- package/dist/output-buffer.js.map +1 -0
- package/dist/rest-api.d.ts +33 -0
- package/dist/rest-api.d.ts.map +1 -0
- package/dist/rest-api.js +325 -0
- package/dist/rest-api.js.map +1 -0
- package/dist/rest-server.d.ts +3 -0
- package/dist/rest-server.d.ts.map +1 -0
- package/dist/rest-server.js +72 -0
- package/dist/rest-server.js.map +1 -0
- package/dist/terminal-manager.d.ts +93 -0
- package/dist/terminal-manager.d.ts.map +1 -0
- package/dist/terminal-manager.js +702 -0
- package/dist/terminal-manager.js.map +1 -0
- package/dist/types.d.ts +201 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/web-ui-manager.d.ts +38 -0
- package/dist/web-ui-manager.d.ts.map +1 -0
- package/dist/web-ui-manager.js +130 -0
- package/dist/web-ui-manager.js.map +1 -0
- package/dist/web-ui-server.d.ts +42 -0
- package/dist/web-ui-server.d.ts.map +1 -0
- package/dist/web-ui-server.js +324 -0
- package/dist/web-ui-server.js.map +1 -0
- package/package.json +129 -0
- package/public/app.js +265 -0
- package/public/index.html +63 -0
- package/public/styles.css +383 -0
- package/public/terminal.html +56 -0
- package/public/terminal.js +244 -0
- package/public/test.html +97 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Terminal</title>
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
|
|
8
|
+
<link rel="stylesheet" href="/styles.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div class="terminal-page">
|
|
12
|
+
<header class="terminal-header">
|
|
13
|
+
<div class="terminal-info">
|
|
14
|
+
<a href="/" class="back-link">← Back to List</a>
|
|
15
|
+
<h2 id="terminal-title">Terminal</h2>
|
|
16
|
+
<span id="terminal-status" class="status-badge">Loading...</span>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="terminal-actions">
|
|
19
|
+
<button id="clear-btn" class="btn btn-secondary">Clear</button>
|
|
20
|
+
<button id="kill-btn" class="btn btn-danger">Kill Terminal</button>
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
|
|
24
|
+
<div class="terminal-details">
|
|
25
|
+
<div class="detail-item">
|
|
26
|
+
<span class="detail-label">PID:</span>
|
|
27
|
+
<span id="detail-pid">-</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="detail-item">
|
|
30
|
+
<span class="detail-label">Shell:</span>
|
|
31
|
+
<span id="detail-shell">-</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="detail-item">
|
|
34
|
+
<span class="detail-label">CWD:</span>
|
|
35
|
+
<span id="detail-cwd">-</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="detail-item">
|
|
38
|
+
<span class="detail-label">Created:</span>
|
|
39
|
+
<span id="detail-created">-</span>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div id="terminal-container"></div>
|
|
44
|
+
|
|
45
|
+
<div class="input-container">
|
|
46
|
+
<input type="text" id="command-input" placeholder="Type command and press Enter...">
|
|
47
|
+
<button id="send-btn" class="btn btn-primary">Send</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
|
|
52
|
+
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
|
|
53
|
+
<script src="/terminal.js?v=3"></script>
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
|
56
|
+
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// Terminal Detail Page Logic
|
|
2
|
+
|
|
3
|
+
let term = null;
|
|
4
|
+
let ws = null;
|
|
5
|
+
let terminalId = null;
|
|
6
|
+
let currentCursor = 0;
|
|
7
|
+
|
|
8
|
+
// Initialize
|
|
9
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
10
|
+
// Get terminal ID from URL
|
|
11
|
+
const pathParts = window.location.pathname.split('/');
|
|
12
|
+
terminalId = pathParts[pathParts.length - 1];
|
|
13
|
+
|
|
14
|
+
if (!terminalId) {
|
|
15
|
+
alert('Invalid terminal ID');
|
|
16
|
+
window.location.href = '/';
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setupTerminal();
|
|
21
|
+
setupEventListeners();
|
|
22
|
+
connectWebSocket();
|
|
23
|
+
loadTerminalInfo();
|
|
24
|
+
loadTerminalOutput();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Setup xterm.js
|
|
28
|
+
function setupTerminal() {
|
|
29
|
+
try {
|
|
30
|
+
term = new Terminal({
|
|
31
|
+
cursorBlink: true,
|
|
32
|
+
fontSize: 14,
|
|
33
|
+
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
34
|
+
theme: {
|
|
35
|
+
background: '#000000',
|
|
36
|
+
foreground: '#ffffff',
|
|
37
|
+
cursor: '#ffffff',
|
|
38
|
+
selection: '#ffffff40'
|
|
39
|
+
},
|
|
40
|
+
convertEol: true,
|
|
41
|
+
rows: 24,
|
|
42
|
+
cols: 80
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const container = document.getElementById('terminal-container');
|
|
46
|
+
term.open(container);
|
|
47
|
+
|
|
48
|
+
// Fit terminal to container (optional, fallback if FitAddon not available)
|
|
49
|
+
if (typeof FitAddon !== 'undefined') {
|
|
50
|
+
try {
|
|
51
|
+
const fitAddon = new FitAddon.FitAddon();
|
|
52
|
+
term.loadAddon(fitAddon);
|
|
53
|
+
fitAddon.fit();
|
|
54
|
+
|
|
55
|
+
// Resize on window resize
|
|
56
|
+
window.addEventListener('resize', () => {
|
|
57
|
+
fitAddon.fit();
|
|
58
|
+
});
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.warn('FitAddon not available:', e);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('Terminal initialized successfully');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Failed to setup terminal:', error);
|
|
67
|
+
alert('Failed to initialize terminal: ' + error.message);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Setup event listeners
|
|
72
|
+
function setupEventListeners() {
|
|
73
|
+
document.getElementById('send-btn').addEventListener('click', sendCommand);
|
|
74
|
+
document.getElementById('command-input').addEventListener('keypress', (e) => {
|
|
75
|
+
if (e.key === 'Enter') {
|
|
76
|
+
sendCommand();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
document.getElementById('clear-btn').addEventListener('click', () => {
|
|
81
|
+
term.clear();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
document.getElementById('kill-btn').addEventListener('click', killTerminal);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// WebSocket connection
|
|
88
|
+
function connectWebSocket() {
|
|
89
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
90
|
+
const wsUrl = `${protocol}//${window.location.host}`;
|
|
91
|
+
|
|
92
|
+
ws = new WebSocket(wsUrl);
|
|
93
|
+
|
|
94
|
+
ws.onopen = () => {
|
|
95
|
+
console.log('WebSocket connected');
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
ws.onmessage = (event) => {
|
|
99
|
+
const message = JSON.parse(event.data);
|
|
100
|
+
handleWebSocketMessage(message);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
ws.onerror = (error) => {
|
|
104
|
+
console.error('WebSocket error:', error);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
ws.onclose = () => {
|
|
108
|
+
console.log('WebSocket disconnected, reconnecting...');
|
|
109
|
+
setTimeout(connectWebSocket, 2000);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Handle WebSocket messages
|
|
114
|
+
function handleWebSocketMessage(message) {
|
|
115
|
+
if (message.terminalId !== terminalId) return;
|
|
116
|
+
|
|
117
|
+
switch (message.type) {
|
|
118
|
+
case 'output':
|
|
119
|
+
term.write(message.data);
|
|
120
|
+
break;
|
|
121
|
+
case 'exit':
|
|
122
|
+
updateStatus('terminated');
|
|
123
|
+
term.write('\r\n\x1b[31m[Terminal Exited]\x1b[0m\r\n');
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Load terminal info
|
|
129
|
+
async function loadTerminalInfo() {
|
|
130
|
+
try {
|
|
131
|
+
console.log('Loading terminal info for:', terminalId);
|
|
132
|
+
const response = await fetch(`/api/terminals/${terminalId}`);
|
|
133
|
+
|
|
134
|
+
console.log('Response status:', response.status);
|
|
135
|
+
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
const errorText = await response.text();
|
|
138
|
+
console.error('Error response:', errorText);
|
|
139
|
+
throw new Error(`Terminal not found (${response.status})`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const data = await response.json();
|
|
143
|
+
console.log('Terminal data:', data);
|
|
144
|
+
|
|
145
|
+
document.getElementById('terminal-title').textContent = `Terminal ${data.id.substring(0, 8)}`;
|
|
146
|
+
document.getElementById('detail-pid').textContent = data.pid;
|
|
147
|
+
document.getElementById('detail-shell').textContent = data.shell;
|
|
148
|
+
document.getElementById('detail-cwd').textContent = data.cwd;
|
|
149
|
+
document.getElementById('detail-created').textContent = new Date(data.created).toLocaleString();
|
|
150
|
+
|
|
151
|
+
updateStatus(data.status);
|
|
152
|
+
console.log('Terminal info loaded successfully');
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('Failed to load terminal info:', error);
|
|
155
|
+
alert('Failed to load terminal: ' + error.message);
|
|
156
|
+
// Don't redirect immediately, let user see the error
|
|
157
|
+
// window.location.href = '/';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Load terminal output
|
|
162
|
+
async function loadTerminalOutput() {
|
|
163
|
+
try {
|
|
164
|
+
console.log('Loading terminal output for:', terminalId);
|
|
165
|
+
const response = await fetch(`/api/terminals/${terminalId}/output?since=${currentCursor}`);
|
|
166
|
+
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
const errorText = await response.text();
|
|
169
|
+
console.error('Failed to load output:', errorText);
|
|
170
|
+
throw new Error('Failed to load output');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const data = await response.json();
|
|
174
|
+
console.log('Output data:', data);
|
|
175
|
+
|
|
176
|
+
if (data.output) {
|
|
177
|
+
term.write(data.output);
|
|
178
|
+
console.log('Wrote output to terminal');
|
|
179
|
+
} else {
|
|
180
|
+
console.log('No output to display');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
currentCursor = data.cursor || data.since || 0;
|
|
184
|
+
console.log('Current cursor:', currentCursor);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('Failed to load terminal output:', error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Send command
|
|
191
|
+
async function sendCommand() {
|
|
192
|
+
const input = document.getElementById('command-input');
|
|
193
|
+
const command = input.value;
|
|
194
|
+
|
|
195
|
+
if (!command.trim()) return;
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const response = await fetch(`/api/terminals/${terminalId}/input`, {
|
|
199
|
+
method: 'POST',
|
|
200
|
+
headers: { 'Content-Type': 'application/json' },
|
|
201
|
+
body: JSON.stringify({ input: command })
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new Error('Failed to send command');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
input.value = '';
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error('Failed to send command:', error);
|
|
211
|
+
alert('Failed to send command: ' + error.message);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Kill terminal
|
|
216
|
+
async function killTerminal() {
|
|
217
|
+
if (!confirm('Are you sure you want to kill this terminal?')) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch(`/api/terminals/${terminalId}`, {
|
|
223
|
+
method: 'DELETE'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
throw new Error('Failed to kill terminal');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
alert('Terminal killed');
|
|
231
|
+
window.location.href = '/';
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('Failed to kill terminal:', error);
|
|
234
|
+
alert('Failed to kill terminal: ' + error.message);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Update status badge
|
|
239
|
+
function updateStatus(status) {
|
|
240
|
+
const badge = document.getElementById('terminal-status');
|
|
241
|
+
badge.textContent = status;
|
|
242
|
+
badge.className = 'status-badge status-' + status;
|
|
243
|
+
}
|
|
244
|
+
|
package/public/test.html
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>API Test</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: monospace;
|
|
10
|
+
padding: 20px;
|
|
11
|
+
background: #1e1e1e;
|
|
12
|
+
color: #d4d4d4;
|
|
13
|
+
}
|
|
14
|
+
pre {
|
|
15
|
+
background: #000;
|
|
16
|
+
padding: 10px;
|
|
17
|
+
border-radius: 4px;
|
|
18
|
+
overflow-x: auto;
|
|
19
|
+
}
|
|
20
|
+
button {
|
|
21
|
+
padding: 10px 20px;
|
|
22
|
+
margin: 5px;
|
|
23
|
+
background: #0e639c;
|
|
24
|
+
color: white;
|
|
25
|
+
border: none;
|
|
26
|
+
border-radius: 4px;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
}
|
|
29
|
+
button:hover {
|
|
30
|
+
background: #1177bb;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<h1>API Test Page</h1>
|
|
36
|
+
|
|
37
|
+
<div>
|
|
38
|
+
<button onclick="testListTerminals()">Test: List Terminals</button>
|
|
39
|
+
<button onclick="testGetTerminal()">Test: Get Terminal</button>
|
|
40
|
+
<button onclick="testGetOutput()">Test: Get Output</button>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<h2>Results:</h2>
|
|
44
|
+
<pre id="results">Click a button to test...</pre>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
async function testListTerminals() {
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch('/api/terminals');
|
|
50
|
+
const data = await response.json();
|
|
51
|
+
document.getElementById('results').textContent = JSON.stringify(data, null, 2);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
document.getElementById('results').textContent = 'Error: ' + error.message;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function testGetTerminal() {
|
|
58
|
+
try {
|
|
59
|
+
// First get the list to find a terminal ID
|
|
60
|
+
const listResponse = await fetch('/api/terminals');
|
|
61
|
+
const listData = await listResponse.json();
|
|
62
|
+
|
|
63
|
+
if (listData.terminals && listData.terminals.length > 0) {
|
|
64
|
+
const terminalId = listData.terminals[0].id;
|
|
65
|
+
const response = await fetch(`/api/terminals/${terminalId}`);
|
|
66
|
+
const data = await response.json();
|
|
67
|
+
document.getElementById('results').textContent = JSON.stringify(data, null, 2);
|
|
68
|
+
} else {
|
|
69
|
+
document.getElementById('results').textContent = 'No terminals found';
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
document.getElementById('results').textContent = 'Error: ' + error.message;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function testGetOutput() {
|
|
77
|
+
try {
|
|
78
|
+
// First get the list to find a terminal ID
|
|
79
|
+
const listResponse = await fetch('/api/terminals');
|
|
80
|
+
const listData = await listResponse.json();
|
|
81
|
+
|
|
82
|
+
if (listData.terminals && listData.terminals.length > 0) {
|
|
83
|
+
const terminalId = listData.terminals[0].id;
|
|
84
|
+
const response = await fetch(`/api/terminals/${terminalId}/output`);
|
|
85
|
+
const data = await response.json();
|
|
86
|
+
document.getElementById('results').textContent = JSON.stringify(data, null, 2);
|
|
87
|
+
} else {
|
|
88
|
+
document.getElementById('results').textContent = 'No terminals found';
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
document.getElementById('results').textContent = 'Error: ' + error.message;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</script>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
|