@adiontaegerron/claude-multi-terminal 1.0.2 → 1.1.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/.claude/settings.local.json +3 -1
- package/index.html +38 -37
- package/main.js +16 -12
- package/package.json +1 -1
- package/renderer.js +486 -11
- package/style.css +201 -91
package/index.html
CHANGED
@@ -12,49 +12,50 @@
|
|
12
12
|
<button id="new-terminal-btn">New Terminal</button>
|
13
13
|
</div>
|
14
14
|
|
15
|
-
<div id="
|
16
|
-
<div
|
17
|
-
<div id="
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
<div id="chat-sidebar">
|
23
|
-
<div class="chat-header">
|
24
|
-
<h2>Chat</h2>
|
25
|
-
</div>
|
26
|
-
|
27
|
-
<div id="chat-messages">
|
28
|
-
<!-- Chat messages will appear here -->
|
15
|
+
<div id="toolbar-extended">
|
16
|
+
<div class="controls-row">
|
17
|
+
<div id="terminal-selector" class="control-section">
|
18
|
+
<label>Send to:</label>
|
19
|
+
<div id="terminal-checkboxes">
|
20
|
+
<!-- Terminal checkboxes will be added here dynamically -->
|
21
|
+
</div>
|
29
22
|
</div>
|
30
23
|
|
31
|
-
<div id="
|
32
|
-
<div
|
33
|
-
<
|
34
|
-
<
|
35
|
-
<!-- Terminal checkboxes will be added here dynamically -->
|
36
|
-
</div>
|
24
|
+
<div id="default-prompt-section" class="control-section collapsible">
|
25
|
+
<div class="collapsible-header">
|
26
|
+
<button class="collapse-toggle">▶</button>
|
27
|
+
<span>Default Prompt</span>
|
37
28
|
</div>
|
38
|
-
|
39
|
-
|
40
|
-
<
|
41
|
-
<
|
42
|
-
<span>
|
43
|
-
</
|
44
|
-
<div class="collapsible-content" style="display: none;">
|
45
|
-
<textarea id="default-prompt-text" placeholder="Enter default prompt to prepend to all messages..."></textarea>
|
46
|
-
<label>
|
47
|
-
<input type="checkbox" id="use-default-prompt" />
|
48
|
-
<span>Always prepend to messages</span>
|
49
|
-
</label>
|
50
|
-
</div>
|
29
|
+
<div class="collapsible-content" style="display: none;">
|
30
|
+
<textarea id="default-prompt-text" placeholder="Enter default prompt to prepend to all messages..."></textarea>
|
31
|
+
<label>
|
32
|
+
<input type="checkbox" id="use-default-prompt" />
|
33
|
+
<span>Always prepend to messages</span>
|
34
|
+
</label>
|
51
35
|
</div>
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
|
39
|
+
<div class="input-row">
|
40
|
+
<div class="input-wrapper">
|
41
|
+
<textarea id="chat-input" placeholder="Type your message or / for commands..."></textarea>
|
42
|
+
<div id="command-dropdown" class="command-dropdown" style="display: none;">
|
43
|
+
<div id="command-list" class="command-list">
|
44
|
+
<!-- Command options will be populated here -->
|
45
|
+
</div>
|
56
46
|
</div>
|
47
|
+
<label class="plan-mode-option">
|
48
|
+
<input type="checkbox" id="use-plan-mode" checked />
|
49
|
+
<span>Use Plan Mode</span>
|
50
|
+
</label>
|
57
51
|
</div>
|
52
|
+
<button id="send-message-btn">Send</button>
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
|
56
|
+
<div id="terminals-area">
|
57
|
+
<div id="terminals-container">
|
58
|
+
<!-- Terminal instances will be added here dynamically -->
|
58
59
|
</div>
|
59
60
|
</div>
|
60
61
|
</div>
|
package/main.js
CHANGED
@@ -33,7 +33,7 @@ function createWindow() {
|
|
33
33
|
|
34
34
|
mainWindow.on('closed', () => {
|
35
35
|
// Clean up all terminals on window close
|
36
|
-
terminals.forEach(
|
36
|
+
terminals.forEach(termData => termData.pty.kill());
|
37
37
|
terminals.clear();
|
38
38
|
mainWindow = null;
|
39
39
|
});
|
@@ -68,7 +68,10 @@ ipcMain.handle('terminal:create', (event, terminalId) => {
|
|
68
68
|
env: process.env
|
69
69
|
});
|
70
70
|
|
71
|
-
|
71
|
+
// Store terminal
|
72
|
+
terminals.set(terminalId, {
|
73
|
+
pty: ptyProcess
|
74
|
+
});
|
72
75
|
|
73
76
|
// Forward terminal output to renderer
|
74
77
|
ptyProcess.onData((data) => {
|
@@ -86,9 +89,9 @@ ipcMain.handle('terminal:create', (event, terminalId) => {
|
|
86
89
|
|
87
90
|
// Write data to terminal
|
88
91
|
ipcMain.handle('terminal:write', (event, terminalId, data) => {
|
89
|
-
const
|
90
|
-
if (
|
91
|
-
|
92
|
+
const terminalData = terminals.get(terminalId);
|
93
|
+
if (terminalData) {
|
94
|
+
terminalData.pty.write(data);
|
92
95
|
return { success: true };
|
93
96
|
}
|
94
97
|
return { success: false, error: 'Terminal not found' };
|
@@ -96,9 +99,9 @@ ipcMain.handle('terminal:write', (event, terminalId, data) => {
|
|
96
99
|
|
97
100
|
// Resize terminal
|
98
101
|
ipcMain.handle('terminal:resize', (event, terminalId, cols, rows) => {
|
99
|
-
const
|
100
|
-
if (
|
101
|
-
|
102
|
+
const terminalData = terminals.get(terminalId);
|
103
|
+
if (terminalData) {
|
104
|
+
terminalData.pty.resize(cols, rows);
|
102
105
|
return { success: true };
|
103
106
|
}
|
104
107
|
return { success: false, error: 'Terminal not found' };
|
@@ -106,11 +109,12 @@ ipcMain.handle('terminal:resize', (event, terminalId, cols, rows) => {
|
|
106
109
|
|
107
110
|
// Close terminal
|
108
111
|
ipcMain.handle('terminal:close', (event, terminalId) => {
|
109
|
-
const
|
110
|
-
if (
|
111
|
-
|
112
|
+
const terminalData = terminals.get(terminalId);
|
113
|
+
if (terminalData) {
|
114
|
+
terminalData.pty.kill();
|
112
115
|
terminals.delete(terminalId);
|
113
116
|
return { success: true };
|
114
117
|
}
|
115
118
|
return { success: false, error: 'Terminal not found' };
|
116
|
-
});
|
119
|
+
});
|
120
|
+
|
package/package.json
CHANGED
package/renderer.js
CHANGED
@@ -6,8 +6,76 @@ const { FitAddon } = require('xterm-addon-fit');
|
|
6
6
|
const terminals = new Map();
|
7
7
|
let terminalCounter = 0;
|
8
8
|
|
9
|
+
// Command system
|
10
|
+
let selectedCommandIndex = -1;
|
11
|
+
let currentCommands = [];
|
12
|
+
|
13
|
+
// Command history
|
14
|
+
const commandHistory = [];
|
15
|
+
let historyIndex = -1;
|
16
|
+
let tempCurrentInput = '';
|
17
|
+
const MAX_HISTORY_SIZE = 50;
|
18
|
+
|
19
|
+
// Available commands
|
20
|
+
const availableCommands = [
|
21
|
+
{
|
22
|
+
name: '/send',
|
23
|
+
syntax: '/send <terminal> <command>',
|
24
|
+
description: 'Send command to specific terminal',
|
25
|
+
args: ['terminal', 'command']
|
26
|
+
},
|
27
|
+
{
|
28
|
+
name: '/send-all',
|
29
|
+
syntax: '/send-all <command>',
|
30
|
+
description: 'Send command to all terminals',
|
31
|
+
args: ['command']
|
32
|
+
},
|
33
|
+
{
|
34
|
+
name: '/list',
|
35
|
+
syntax: '/list',
|
36
|
+
description: 'List all terminals',
|
37
|
+
args: []
|
38
|
+
},
|
39
|
+
{
|
40
|
+
name: '/help',
|
41
|
+
syntax: '/help',
|
42
|
+
description: 'Show available commands',
|
43
|
+
args: []
|
44
|
+
},
|
45
|
+
{
|
46
|
+
name: '/create',
|
47
|
+
syntax: '/create [name]',
|
48
|
+
description: 'Create new terminal with optional name',
|
49
|
+
args: ['name?']
|
50
|
+
},
|
51
|
+
{
|
52
|
+
name: '/close',
|
53
|
+
syntax: '/close <terminal>',
|
54
|
+
description: 'Close specific terminal',
|
55
|
+
args: ['terminal']
|
56
|
+
},
|
57
|
+
{
|
58
|
+
name: '/return',
|
59
|
+
syntax: '/return <terminal>',
|
60
|
+
description: 'Submit current input in specified terminal (press Enter)',
|
61
|
+
args: ['terminal']
|
62
|
+
},
|
63
|
+
{
|
64
|
+
name: '/clear',
|
65
|
+
syntax: '/clear <terminal>',
|
66
|
+
description: 'Clear current input line in specified terminal (Ctrl+C)',
|
67
|
+
args: ['terminal']
|
68
|
+
},
|
69
|
+
{
|
70
|
+
name: '/interrupt',
|
71
|
+
syntax: '/interrupt <terminal>',
|
72
|
+
description: 'Interrupt current command in specified terminal (Ctrl+C)',
|
73
|
+
args: ['terminal']
|
74
|
+
}
|
75
|
+
];
|
76
|
+
|
9
77
|
// Create a new terminal instance
|
10
|
-
async function createTerminal() {
|
78
|
+
async function createTerminal(customName = null) {
|
11
79
|
terminalCounter++;
|
12
80
|
const terminalId = `terminal-${terminalCounter}`;
|
13
81
|
|
@@ -21,7 +89,8 @@ async function createTerminal() {
|
|
21
89
|
header.className = 'terminal-header';
|
22
90
|
|
23
91
|
const title = document.createElement('span');
|
24
|
-
|
92
|
+
const terminalName = customName || `Terminal ${terminalCounter}`;
|
93
|
+
title.textContent = terminalName;
|
25
94
|
title.className = 'terminal-title';
|
26
95
|
title.style.cursor = 'pointer';
|
27
96
|
|
@@ -80,18 +149,18 @@ async function createTerminal() {
|
|
80
149
|
fitAddon,
|
81
150
|
element: terminalContainer,
|
82
151
|
titleElement: title,
|
83
|
-
name:
|
152
|
+
name: terminalName
|
84
153
|
});
|
85
154
|
|
86
155
|
// Create terminal in main process
|
87
156
|
await ipcRenderer.invoke('terminal:create', terminalId);
|
88
157
|
|
89
158
|
// Add checkbox to chat sidebar
|
90
|
-
addTerminalCheckbox(terminalId,
|
159
|
+
addTerminalCheckbox(terminalId, terminalName);
|
91
160
|
|
92
161
|
// Auto-start Claude Code
|
93
162
|
setTimeout(() => {
|
94
|
-
ipcRenderer.invoke('terminal:write', terminalId, 'claude\
|
163
|
+
ipcRenderer.invoke('terminal:write', terminalId, 'claude\r');
|
95
164
|
}, 1000);
|
96
165
|
|
97
166
|
// Ensure terminal is ready and focused
|
@@ -201,6 +270,7 @@ ipcRenderer.on('terminal:exit', (event, terminalId, exitCode) => {
|
|
201
270
|
}
|
202
271
|
});
|
203
272
|
|
273
|
+
|
204
274
|
// Chat functionality
|
205
275
|
function addTerminalCheckbox(terminalId, name) {
|
206
276
|
const container = document.getElementById('terminal-checkboxes');
|
@@ -248,6 +318,25 @@ function sendMessage() {
|
|
248
318
|
const message = input.value.trim();
|
249
319
|
if (!message) return;
|
250
320
|
|
321
|
+
// Save to command history
|
322
|
+
if (commandHistory.length === 0 || commandHistory[commandHistory.length - 1] !== message) {
|
323
|
+
commandHistory.push(message);
|
324
|
+
if (commandHistory.length > MAX_HISTORY_SIZE) {
|
325
|
+
commandHistory.shift();
|
326
|
+
}
|
327
|
+
}
|
328
|
+
historyIndex = commandHistory.length;
|
329
|
+
|
330
|
+
// Hide command dropdown
|
331
|
+
hideCommandDropdown();
|
332
|
+
|
333
|
+
// Check if this is a slash command
|
334
|
+
if (message.startsWith('/')) {
|
335
|
+
executeCommand(message);
|
336
|
+
input.value = '';
|
337
|
+
return;
|
338
|
+
}
|
339
|
+
|
251
340
|
// Check if default prompt should be prepended
|
252
341
|
let fullMessage = message;
|
253
342
|
const useDefaultPrompt = document.getElementById('use-default-prompt').checked;
|
@@ -267,11 +356,24 @@ function sendMessage() {
|
|
267
356
|
// Add message to chat display
|
268
357
|
addChatMessage('You', message);
|
269
358
|
|
359
|
+
// Check if plan mode is enabled
|
360
|
+
const usePlanMode = document.getElementById('use-plan-mode').checked;
|
361
|
+
|
270
362
|
// Send to selected terminals
|
271
363
|
selectedTerminals.forEach(terminalId => {
|
272
|
-
|
273
|
-
|
274
|
-
|
364
|
+
if (usePlanMode) {
|
365
|
+
// Send the plan mode instruction first with proper line endings
|
366
|
+
ipcRenderer.invoke('terminal:write', terminalId, 'Please enter plan mode (press Shift+Tab twice)\r');
|
367
|
+
|
368
|
+
// Small delay to ensure the first command is processed
|
369
|
+
setTimeout(() => {
|
370
|
+
// Send the actual message with carriage return to execute
|
371
|
+
ipcRenderer.invoke('terminal:write', terminalId, fullMessage + '\r');
|
372
|
+
}, 100);
|
373
|
+
} else {
|
374
|
+
// Send the message directly to the terminal
|
375
|
+
ipcRenderer.invoke('terminal:write', terminalId, fullMessage + '\r');
|
376
|
+
}
|
275
377
|
});
|
276
378
|
|
277
379
|
// Clear input
|
@@ -280,6 +382,8 @@ function sendMessage() {
|
|
280
382
|
|
281
383
|
function addChatMessage(sender, message) {
|
282
384
|
const messagesContainer = document.getElementById('chat-messages');
|
385
|
+
if (!messagesContainer) return; // Skip if no chat messages container
|
386
|
+
|
283
387
|
const messageDiv = document.createElement('div');
|
284
388
|
messageDiv.className = 'chat-message';
|
285
389
|
|
@@ -299,6 +403,308 @@ function addChatMessage(sender, message) {
|
|
299
403
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
300
404
|
}
|
301
405
|
|
406
|
+
// Command System Functions
|
407
|
+
function parseCommand(message) {
|
408
|
+
const parts = message.split(' ');
|
409
|
+
const command = parts[0];
|
410
|
+
const args = parts.slice(1);
|
411
|
+
|
412
|
+
return { command, args, raw: message };
|
413
|
+
}
|
414
|
+
|
415
|
+
function findTerminal(identifier) {
|
416
|
+
// Try to find terminal by various methods
|
417
|
+
|
418
|
+
// 1. Try exact terminal ID match
|
419
|
+
if (terminals.has(identifier)) {
|
420
|
+
return { id: identifier, terminal: terminals.get(identifier) };
|
421
|
+
}
|
422
|
+
|
423
|
+
// 2. Try exact name match (case-insensitive)
|
424
|
+
for (const [id, termData] of terminals.entries()) {
|
425
|
+
if (termData.name && termData.name.toLowerCase() === identifier.toLowerCase()) {
|
426
|
+
return { id, terminal: termData };
|
427
|
+
}
|
428
|
+
}
|
429
|
+
|
430
|
+
// 3. Try partial name match
|
431
|
+
for (const [id, termData] of terminals.entries()) {
|
432
|
+
if (termData.name && termData.name.toLowerCase().includes(identifier.toLowerCase())) {
|
433
|
+
return { id, terminal: termData };
|
434
|
+
}
|
435
|
+
}
|
436
|
+
|
437
|
+
// 4. Try position/number match
|
438
|
+
const position = parseInt(identifier);
|
439
|
+
if (!isNaN(position) && position > 0) {
|
440
|
+
const terminalArray = Array.from(terminals.entries());
|
441
|
+
if (position <= terminalArray.length) {
|
442
|
+
const [id, termData] = terminalArray[position - 1];
|
443
|
+
return { id, terminal: termData };
|
444
|
+
}
|
445
|
+
}
|
446
|
+
|
447
|
+
return null;
|
448
|
+
}
|
449
|
+
|
450
|
+
function executeCommand(message) {
|
451
|
+
const { command, args } = parseCommand(message);
|
452
|
+
|
453
|
+
switch (command) {
|
454
|
+
case '/send':
|
455
|
+
executeSendCommand(args);
|
456
|
+
break;
|
457
|
+
case '/send-all':
|
458
|
+
executeSendAllCommand(args);
|
459
|
+
break;
|
460
|
+
case '/list':
|
461
|
+
executeListCommand();
|
462
|
+
break;
|
463
|
+
case '/help':
|
464
|
+
executeHelpCommand();
|
465
|
+
break;
|
466
|
+
case '/create':
|
467
|
+
executeCreateCommand(args);
|
468
|
+
break;
|
469
|
+
case '/close':
|
470
|
+
executeCloseCommand(args);
|
471
|
+
break;
|
472
|
+
case '/return':
|
473
|
+
executeReturnCommand(args);
|
474
|
+
break;
|
475
|
+
case '/clear':
|
476
|
+
case '/interrupt':
|
477
|
+
executeClearCommand(args);
|
478
|
+
break;
|
479
|
+
default:
|
480
|
+
addChatMessage('System', `Unknown command: ${command}. Type /help for available commands.`);
|
481
|
+
}
|
482
|
+
}
|
483
|
+
|
484
|
+
function executeSendCommand(args) {
|
485
|
+
if (args.length < 2) {
|
486
|
+
addChatMessage('System', 'Usage: /send <terminal> <command>');
|
487
|
+
return;
|
488
|
+
}
|
489
|
+
|
490
|
+
const terminalIdentifier = args[0];
|
491
|
+
const command = args.slice(1).join(' ');
|
492
|
+
|
493
|
+
const result = findTerminal(terminalIdentifier);
|
494
|
+
if (!result) {
|
495
|
+
const available = Array.from(terminals.values()).map(t => t.name).join(', ');
|
496
|
+
addChatMessage('System', `Terminal '${terminalIdentifier}' not found. Available: ${available}`);
|
497
|
+
return;
|
498
|
+
}
|
499
|
+
|
500
|
+
// Send command to terminal
|
501
|
+
ipcRenderer.invoke('terminal:write', result.id, command + '\r');
|
502
|
+
addChatMessage('System', `✅ Sent '${command}' to ${result.terminal.name}`);
|
503
|
+
}
|
504
|
+
|
505
|
+
function executeSendAllCommand(args) {
|
506
|
+
if (args.length === 0) {
|
507
|
+
addChatMessage('System', 'Usage: /send-all <command>');
|
508
|
+
return;
|
509
|
+
}
|
510
|
+
|
511
|
+
const command = args.join(' ');
|
512
|
+
let count = 0;
|
513
|
+
|
514
|
+
for (const [id, termData] of terminals.entries()) {
|
515
|
+
ipcRenderer.invoke('terminal:write', id, command + '\r');
|
516
|
+
count++;
|
517
|
+
}
|
518
|
+
|
519
|
+
addChatMessage('System', `✅ Sent '${command}' to ${count} terminals`);
|
520
|
+
}
|
521
|
+
|
522
|
+
function executeListCommand() {
|
523
|
+
const terminalList = Array.from(terminals.entries()).map(([id, termData], index) => {
|
524
|
+
return `${index + 1}. ${termData.name} (${id})`;
|
525
|
+
});
|
526
|
+
|
527
|
+
if (terminalList.length === 0) {
|
528
|
+
addChatMessage('System', 'No terminals available');
|
529
|
+
} else {
|
530
|
+
addChatMessage('System', 'Available terminals:\n' + terminalList.join('\n'));
|
531
|
+
}
|
532
|
+
}
|
533
|
+
|
534
|
+
function executeHelpCommand() {
|
535
|
+
const helpText = availableCommands.map(cmd =>
|
536
|
+
`${cmd.syntax} - ${cmd.description}`
|
537
|
+
).join('\n');
|
538
|
+
|
539
|
+
addChatMessage('System', 'Available commands:\n' + helpText);
|
540
|
+
}
|
541
|
+
|
542
|
+
function executeCreateCommand(args) {
|
543
|
+
const name = args.length > 0 ? args.join(' ') : undefined;
|
544
|
+
createTerminal(name);
|
545
|
+
addChatMessage('System', `✅ Creating new terminal${name ? ` named '${name}'` : ''}`);
|
546
|
+
}
|
547
|
+
|
548
|
+
function executeCloseCommand(args) {
|
549
|
+
if (args.length === 0) {
|
550
|
+
addChatMessage('System', 'Usage: /close <terminal>');
|
551
|
+
return;
|
552
|
+
}
|
553
|
+
|
554
|
+
const terminalIdentifier = args[0];
|
555
|
+
const result = findTerminal(terminalIdentifier);
|
556
|
+
|
557
|
+
if (!result) {
|
558
|
+
const available = Array.from(terminals.values()).map(t => t.name).join(', ');
|
559
|
+
addChatMessage('System', `Terminal '${terminalIdentifier}' not found. Available: ${available}`);
|
560
|
+
return;
|
561
|
+
}
|
562
|
+
|
563
|
+
closeTerminal(result.id);
|
564
|
+
addChatMessage('System', `✅ Closed terminal ${result.terminal.name}`);
|
565
|
+
}
|
566
|
+
|
567
|
+
function executeReturnCommand(args) {
|
568
|
+
if (args.length === 0) {
|
569
|
+
addChatMessage('System', 'Usage: /return <terminal>');
|
570
|
+
return;
|
571
|
+
}
|
572
|
+
|
573
|
+
const terminalIdentifier = args[0];
|
574
|
+
const result = findTerminal(terminalIdentifier);
|
575
|
+
|
576
|
+
if (!result) {
|
577
|
+
const available = Array.from(terminals.values()).map(t => t.name).join(', ');
|
578
|
+
addChatMessage('System', `Terminal '${terminalIdentifier}' not found. Available: ${available}`);
|
579
|
+
return;
|
580
|
+
}
|
581
|
+
|
582
|
+
// Send carriage return to submit current input
|
583
|
+
ipcRenderer.invoke('terminal:write', result.id, '\r');
|
584
|
+
addChatMessage('System', `✅ Submitted input in ${result.terminal.name}`);
|
585
|
+
}
|
586
|
+
|
587
|
+
function executeClearCommand(args) {
|
588
|
+
if (args.length === 0) {
|
589
|
+
addChatMessage('System', 'Usage: /clear <terminal> or /interrupt <terminal>');
|
590
|
+
return;
|
591
|
+
}
|
592
|
+
|
593
|
+
const terminalIdentifier = args[0];
|
594
|
+
const result = findTerminal(terminalIdentifier);
|
595
|
+
|
596
|
+
if (!result) {
|
597
|
+
const available = Array.from(terminals.values()).map(t => t.name).join(', ');
|
598
|
+
addChatMessage('System', `Terminal '${terminalIdentifier}' not found. Available: ${available}`);
|
599
|
+
return;
|
600
|
+
}
|
601
|
+
|
602
|
+
// Send Ctrl+C to clear/interrupt
|
603
|
+
ipcRenderer.invoke('terminal:write', result.id, '\x03');
|
604
|
+
addChatMessage('System', `✅ Sent Ctrl+C to ${result.terminal.name}`);
|
605
|
+
}
|
606
|
+
|
607
|
+
// Dropdown Functions
|
608
|
+
function showCommandDropdown() {
|
609
|
+
const dropdown = document.getElementById('command-dropdown');
|
610
|
+
const input = document.getElementById('chat-input');
|
611
|
+
const inputValue = input.value;
|
612
|
+
|
613
|
+
// Filter commands based on current input
|
614
|
+
const searchTerm = inputValue.substring(1).toLowerCase(); // Remove the '/'
|
615
|
+
currentCommands = availableCommands.filter(cmd =>
|
616
|
+
cmd.name.substring(1).toLowerCase().startsWith(searchTerm)
|
617
|
+
);
|
618
|
+
|
619
|
+
if (currentCommands.length === 0) {
|
620
|
+
hideCommandDropdown();
|
621
|
+
return;
|
622
|
+
}
|
623
|
+
|
624
|
+
populateCommandDropdown();
|
625
|
+
dropdown.style.display = 'block';
|
626
|
+
selectedCommandIndex = -1;
|
627
|
+
}
|
628
|
+
|
629
|
+
function hideCommandDropdown() {
|
630
|
+
const dropdown = document.getElementById('command-dropdown');
|
631
|
+
dropdown.style.display = 'none';
|
632
|
+
selectedCommandIndex = -1;
|
633
|
+
currentCommands = [];
|
634
|
+
}
|
635
|
+
|
636
|
+
function populateCommandDropdown() {
|
637
|
+
const commandList = document.getElementById('command-list');
|
638
|
+
commandList.innerHTML = '';
|
639
|
+
|
640
|
+
currentCommands.forEach((command, index) => {
|
641
|
+
const option = document.createElement('div');
|
642
|
+
option.className = 'command-option';
|
643
|
+
option.setAttribute('data-index', index);
|
644
|
+
|
645
|
+
const name = document.createElement('div');
|
646
|
+
name.className = 'command-name';
|
647
|
+
name.textContent = command.name;
|
648
|
+
|
649
|
+
const description = document.createElement('div');
|
650
|
+
description.className = 'command-description';
|
651
|
+
description.textContent = command.description;
|
652
|
+
|
653
|
+
const syntax = document.createElement('div');
|
654
|
+
syntax.className = 'command-syntax';
|
655
|
+
syntax.textContent = command.syntax;
|
656
|
+
|
657
|
+
option.appendChild(name);
|
658
|
+
option.appendChild(description);
|
659
|
+
option.appendChild(syntax);
|
660
|
+
|
661
|
+
option.addEventListener('click', () => {
|
662
|
+
selectCommand(index);
|
663
|
+
});
|
664
|
+
|
665
|
+
commandList.appendChild(option);
|
666
|
+
});
|
667
|
+
}
|
668
|
+
|
669
|
+
function selectCommand(index) {
|
670
|
+
if (index < 0 || index >= currentCommands.length) return;
|
671
|
+
|
672
|
+
const command = currentCommands[index];
|
673
|
+
const input = document.getElementById('chat-input');
|
674
|
+
|
675
|
+
// Set the command name and position cursor for arguments
|
676
|
+
input.value = command.name + ' ';
|
677
|
+
input.focus();
|
678
|
+
|
679
|
+
// Position cursor at the end
|
680
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
681
|
+
|
682
|
+
hideCommandDropdown();
|
683
|
+
}
|
684
|
+
|
685
|
+
function navigateDropdown(direction) {
|
686
|
+
if (currentCommands.length === 0) return;
|
687
|
+
|
688
|
+
// Update selected index
|
689
|
+
if (direction === 'down') {
|
690
|
+
selectedCommandIndex = Math.min(selectedCommandIndex + 1, currentCommands.length - 1);
|
691
|
+
} else if (direction === 'up') {
|
692
|
+
selectedCommandIndex = Math.max(selectedCommandIndex - 1, -1);
|
693
|
+
}
|
694
|
+
|
695
|
+
// Update visual selection
|
696
|
+
const options = document.querySelectorAll('.command-option');
|
697
|
+
options.forEach((option, index) => {
|
698
|
+
option.classList.toggle('selected', index === selectedCommandIndex);
|
699
|
+
});
|
700
|
+
}
|
701
|
+
|
702
|
+
function selectCurrentCommand() {
|
703
|
+
if (selectedCommandIndex >= 0 && selectedCommandIndex < currentCommands.length) {
|
704
|
+
selectCommand(selectedCommandIndex);
|
705
|
+
}
|
706
|
+
}
|
707
|
+
|
302
708
|
// Initialize collapsible default prompt section
|
303
709
|
function initCollapsible() {
|
304
710
|
const toggle = document.querySelector('.collapse-toggle');
|
@@ -311,19 +717,88 @@ function initCollapsible() {
|
|
311
717
|
});
|
312
718
|
}
|
313
719
|
|
720
|
+
|
314
721
|
// Initialize app
|
315
722
|
document.addEventListener('DOMContentLoaded', () => {
|
316
723
|
// New terminal button
|
317
|
-
document.getElementById('new-terminal-btn').addEventListener('click', createTerminal);
|
724
|
+
document.getElementById('new-terminal-btn').addEventListener('click', () => createTerminal());
|
725
|
+
|
318
726
|
|
319
727
|
// Send message button
|
320
728
|
document.getElementById('send-message-btn').addEventListener('click', sendMessage);
|
321
729
|
|
322
|
-
// Chat input - Enter key (Shift+Enter for new line)
|
730
|
+
// Chat input - Enter key (Shift+Enter for new line) and dropdown navigation
|
323
731
|
document.getElementById('chat-input').addEventListener('keydown', (e) => {
|
732
|
+
const dropdown = document.getElementById('command-dropdown');
|
733
|
+
const isDropdownVisible = dropdown.style.display === 'block';
|
734
|
+
const input = e.target;
|
735
|
+
|
324
736
|
if (e.key === 'Enter' && !e.shiftKey) {
|
325
737
|
e.preventDefault();
|
326
|
-
|
738
|
+
if (isDropdownVisible && selectedCommandIndex >= 0) {
|
739
|
+
selectCurrentCommand();
|
740
|
+
} else {
|
741
|
+
sendMessage();
|
742
|
+
}
|
743
|
+
} else if (e.key === 'Escape' && isDropdownVisible) {
|
744
|
+
e.preventDefault();
|
745
|
+
hideCommandDropdown();
|
746
|
+
} else if (e.key === 'ArrowDown' && isDropdownVisible) {
|
747
|
+
e.preventDefault();
|
748
|
+
navigateDropdown('down');
|
749
|
+
} else if (e.key === 'ArrowUp' && isDropdownVisible) {
|
750
|
+
e.preventDefault();
|
751
|
+
navigateDropdown('up');
|
752
|
+
} else if (e.key === 'Tab' && isDropdownVisible) {
|
753
|
+
e.preventDefault();
|
754
|
+
if (selectedCommandIndex >= 0) {
|
755
|
+
selectCurrentCommand();
|
756
|
+
}
|
757
|
+
} else if (e.key === 'ArrowUp' && !isDropdownVisible && commandHistory.length > 0) {
|
758
|
+
e.preventDefault();
|
759
|
+
if (historyIndex === commandHistory.length) {
|
760
|
+
tempCurrentInput = input.value;
|
761
|
+
}
|
762
|
+
if (historyIndex > 0) {
|
763
|
+
historyIndex--;
|
764
|
+
input.value = commandHistory[historyIndex];
|
765
|
+
}
|
766
|
+
} else if (e.key === 'ArrowDown' && !isDropdownVisible && commandHistory.length > 0) {
|
767
|
+
e.preventDefault();
|
768
|
+
if (historyIndex < commandHistory.length - 1) {
|
769
|
+
historyIndex++;
|
770
|
+
input.value = commandHistory[historyIndex];
|
771
|
+
} else if (historyIndex === commandHistory.length - 1) {
|
772
|
+
historyIndex = commandHistory.length;
|
773
|
+
input.value = tempCurrentInput;
|
774
|
+
}
|
775
|
+
}
|
776
|
+
});
|
777
|
+
|
778
|
+
// Chat input - Show dropdown on '/' and filter as user types
|
779
|
+
document.getElementById('chat-input').addEventListener('input', (e) => {
|
780
|
+
const input = e.target;
|
781
|
+
const value = input.value;
|
782
|
+
const cursorPos = input.selectionStart;
|
783
|
+
|
784
|
+
// Check if cursor is at end and input starts with '/'
|
785
|
+
if (cursorPos === value.length && value.startsWith('/') && value.length > 1) {
|
786
|
+
showCommandDropdown();
|
787
|
+
} else if (value.startsWith('/') && value.length === 1) {
|
788
|
+
// Show all commands when just '/' is typed
|
789
|
+
showCommandDropdown();
|
790
|
+
} else {
|
791
|
+
hideCommandDropdown();
|
792
|
+
}
|
793
|
+
});
|
794
|
+
|
795
|
+
// Hide dropdown when clicking outside
|
796
|
+
document.addEventListener('click', (e) => {
|
797
|
+
const dropdown = document.getElementById('command-dropdown');
|
798
|
+
const input = document.getElementById('chat-input');
|
799
|
+
|
800
|
+
if (!dropdown.contains(e.target) && e.target !== input) {
|
801
|
+
hideCommandDropdown();
|
327
802
|
}
|
328
803
|
});
|
329
804
|
|
package/style.css
CHANGED
@@ -32,19 +32,24 @@ body {
|
|
32
32
|
border-radius: 4px;
|
33
33
|
cursor: pointer;
|
34
34
|
font-size: 14px;
|
35
|
+
margin-right: 10px;
|
35
36
|
}
|
36
37
|
|
37
38
|
#new-terminal-btn:hover {
|
38
39
|
background-color: #1177bb;
|
39
40
|
}
|
40
41
|
|
41
|
-
|
42
|
-
|
42
|
+
/* Extended Toolbar */
|
43
|
+
#toolbar-extended {
|
44
|
+
background-color: #1e1e1e;
|
45
|
+
padding: 12px 15px;
|
46
|
+
border-bottom: 1px solid #444;
|
43
47
|
display: flex;
|
44
|
-
|
48
|
+
flex-direction: column;
|
49
|
+
gap: 12px;
|
45
50
|
}
|
46
51
|
|
47
|
-
/* Terminals Area -
|
52
|
+
/* Terminals Area - Full Width */
|
48
53
|
#terminals-area {
|
49
54
|
flex: 1;
|
50
55
|
background-color: #252526;
|
@@ -137,80 +142,73 @@ body {
|
|
137
142
|
cursor: text;
|
138
143
|
}
|
139
144
|
|
140
|
-
/*
|
141
|
-
|
142
|
-
width: 400px;
|
143
|
-
background-color: #1e1e1e;
|
144
|
-
border-left: 1px solid #444;
|
145
|
+
/* Control Rows */
|
146
|
+
.controls-row {
|
145
147
|
display: flex;
|
146
|
-
|
148
|
+
gap: 20px;
|
149
|
+
align-items: stretch;
|
150
|
+
margin-bottom: 10px;
|
147
151
|
}
|
148
152
|
|
149
|
-
|
150
|
-
|
151
|
-
background-color: #252526;
|
152
|
-
border-bottom: 1px solid #444;
|
153
|
-
}
|
154
|
-
|
155
|
-
.chat-header h2 {
|
156
|
-
margin: 0;
|
157
|
-
font-size: 18px;
|
158
|
-
color: #cccccc;
|
159
|
-
}
|
160
|
-
|
161
|
-
#chat-messages {
|
153
|
+
/* Control Sections - 50/50 Split */
|
154
|
+
.control-section {
|
162
155
|
flex: 1;
|
163
|
-
padding: 15px;
|
164
|
-
overflow-y: auto;
|
165
156
|
display: flex;
|
166
157
|
flex-direction: column;
|
167
|
-
gap:
|
168
|
-
|
169
|
-
|
170
|
-
.chat-message {
|
171
|
-
display: flex;
|
172
|
-
flex-direction: column;
|
173
|
-
gap: 5px;
|
174
|
-
background-color: #252526;
|
175
|
-
padding: 10px;
|
158
|
+
gap: 8px;
|
159
|
+
background-color: transparent;
|
160
|
+
padding: 12px;
|
176
161
|
border-radius: 6px;
|
162
|
+
border: 1px solid #3e3e42;
|
177
163
|
}
|
178
164
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
.message-text {
|
186
|
-
color: #cccccc;
|
187
|
-
font-size: 14px;
|
188
|
-
line-height: 1.5;
|
189
|
-
white-space: pre-wrap;
|
165
|
+
/* Input Row */
|
166
|
+
.input-row {
|
167
|
+
display: flex;
|
168
|
+
gap: 12px;
|
169
|
+
align-items: flex-end;
|
170
|
+
width: 100%;
|
190
171
|
}
|
191
172
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
173
|
+
/* Input Wrapper */
|
174
|
+
.input-wrapper {
|
175
|
+
flex: 1;
|
176
|
+
position: relative;
|
196
177
|
}
|
197
178
|
|
198
179
|
/* Terminal Selection */
|
199
|
-
#terminal-selector {
|
200
|
-
margin-bottom: 15px;
|
201
|
-
}
|
202
|
-
|
203
|
-
#terminal-selector label {
|
180
|
+
#terminal-selector > label {
|
204
181
|
display: block;
|
205
|
-
font-size:
|
182
|
+
font-size: 13px;
|
206
183
|
color: #cccccc;
|
207
184
|
margin-bottom: 8px;
|
185
|
+
font-weight: 500;
|
208
186
|
}
|
209
187
|
|
210
188
|
#terminal-checkboxes {
|
211
189
|
display: flex;
|
212
|
-
flex-direction:
|
213
|
-
|
190
|
+
flex-direction: row;
|
191
|
+
flex-wrap: wrap;
|
192
|
+
gap: 8px 15px;
|
193
|
+
max-height: 80px;
|
194
|
+
overflow-y: auto;
|
195
|
+
padding-right: 5px;
|
196
|
+
}
|
197
|
+
|
198
|
+
.terminal-checkbox-label {
|
199
|
+
font-size: 13px !important;
|
200
|
+
padding: 3px 8px;
|
201
|
+
display: flex;
|
202
|
+
align-items: center;
|
203
|
+
gap: 6px;
|
204
|
+
white-space: nowrap;
|
205
|
+
background-color: rgba(255, 255, 255, 0.05);
|
206
|
+
border-radius: 4px;
|
207
|
+
transition: background-color 0.2s;
|
208
|
+
}
|
209
|
+
|
210
|
+
.terminal-checkbox-label:hover {
|
211
|
+
background-color: rgba(255, 255, 255, 0.1);
|
214
212
|
}
|
215
213
|
|
216
214
|
.terminal-checkbox-label {
|
@@ -232,25 +230,42 @@ body {
|
|
232
230
|
cursor: pointer;
|
233
231
|
}
|
234
232
|
|
235
|
-
/*
|
233
|
+
/* Default Prompt Section */
|
236
234
|
#default-prompt-section {
|
237
|
-
|
235
|
+
position: relative;
|
238
236
|
}
|
239
237
|
|
240
238
|
.collapsible-header {
|
241
239
|
display: flex;
|
242
240
|
align-items: center;
|
243
|
-
gap:
|
241
|
+
gap: 6px;
|
244
242
|
cursor: pointer;
|
245
243
|
user-select: none;
|
246
|
-
padding:
|
247
|
-
background-color:
|
248
|
-
border-radius:
|
244
|
+
padding: 0;
|
245
|
+
background-color: transparent;
|
246
|
+
border-radius: 3px;
|
249
247
|
margin-bottom: 8px;
|
248
|
+
font-size: 13px;
|
249
|
+
color: #cccccc;
|
250
|
+
font-weight: 500;
|
250
251
|
}
|
251
252
|
|
252
253
|
.collapsible-header:hover {
|
253
|
-
|
254
|
+
color: #ffffff;
|
255
|
+
}
|
256
|
+
|
257
|
+
.collapse-toggle {
|
258
|
+
background: none;
|
259
|
+
border: none;
|
260
|
+
color: #cccccc;
|
261
|
+
font-size: 10px;
|
262
|
+
cursor: pointer;
|
263
|
+
padding: 0;
|
264
|
+
transition: transform 0.2s;
|
265
|
+
}
|
266
|
+
|
267
|
+
.collapsible-header:hover .collapse-toggle {
|
268
|
+
color: #ffffff;
|
254
269
|
}
|
255
270
|
|
256
271
|
.collapse-toggle {
|
@@ -268,14 +283,15 @@ body {
|
|
268
283
|
|
269
284
|
#default-prompt-text {
|
270
285
|
width: 100%;
|
271
|
-
min-height:
|
286
|
+
min-height: 60px;
|
287
|
+
max-height: 120px;
|
272
288
|
background-color: #3c3c3c;
|
273
289
|
border: 1px solid #555;
|
274
290
|
color: white;
|
275
291
|
padding: 8px;
|
276
292
|
border-radius: 4px;
|
277
293
|
font-family: 'Consolas', 'Courier New', monospace;
|
278
|
-
font-size:
|
294
|
+
font-size: 12px;
|
279
295
|
resize: vertical;
|
280
296
|
margin-bottom: 8px;
|
281
297
|
}
|
@@ -289,43 +305,70 @@ body {
|
|
289
305
|
display: flex;
|
290
306
|
align-items: center;
|
291
307
|
gap: 8px;
|
292
|
-
font-size:
|
308
|
+
font-size: 12px;
|
293
309
|
color: #cccccc;
|
310
|
+
padding: 3px 0;
|
294
311
|
}
|
295
312
|
|
296
|
-
/*
|
297
|
-
.message-input-section {
|
298
|
-
display: flex;
|
299
|
-
flex-direction: column;
|
300
|
-
gap: 10px;
|
301
|
-
}
|
313
|
+
/* Removed - Mode selector is now integrated into input area */
|
302
314
|
|
315
|
+
/* Chat Input - Full Width */
|
303
316
|
#chat-input {
|
304
317
|
width: 100%;
|
305
318
|
min-height: 80px;
|
319
|
+
max-height: 200px;
|
306
320
|
background-color: #3c3c3c;
|
307
321
|
border: 1px solid #555;
|
308
322
|
color: white;
|
309
|
-
padding:
|
323
|
+
padding: 10px 12px 35px 12px;
|
310
324
|
border-radius: 4px;
|
311
325
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
312
326
|
font-size: 14px;
|
313
327
|
resize: vertical;
|
328
|
+
line-height: 1.5;
|
314
329
|
}
|
315
330
|
|
316
331
|
#chat-input:focus {
|
317
332
|
outline: none;
|
318
333
|
border-color: #0e639c;
|
334
|
+
box-shadow: 0 0 0 2px rgba(14, 99, 156, 0.2);
|
335
|
+
}
|
336
|
+
|
337
|
+
/* Plan Mode Option */
|
338
|
+
.plan-mode-option {
|
339
|
+
position: absolute;
|
340
|
+
bottom: 10px;
|
341
|
+
left: 12px;
|
342
|
+
display: flex;
|
343
|
+
align-items: center;
|
344
|
+
gap: 6px;
|
345
|
+
font-size: 12px;
|
346
|
+
color: #999;
|
347
|
+
cursor: pointer;
|
348
|
+
}
|
349
|
+
|
350
|
+
.plan-mode-option:hover {
|
351
|
+
color: #cccccc;
|
352
|
+
}
|
353
|
+
|
354
|
+
.plan-mode-option input[type="checkbox"] {
|
355
|
+
width: 14px;
|
356
|
+
height: 14px;
|
357
|
+
cursor: pointer;
|
319
358
|
}
|
320
359
|
|
321
360
|
#send-message-btn {
|
322
361
|
background-color: #0e639c;
|
323
362
|
color: white;
|
324
363
|
border: none;
|
325
|
-
padding:
|
364
|
+
padding: 12px 24px;
|
326
365
|
border-radius: 4px;
|
327
366
|
cursor: pointer;
|
328
367
|
font-size: 14px;
|
368
|
+
font-weight: 500;
|
369
|
+
height: fit-content;
|
370
|
+
white-space: nowrap;
|
371
|
+
transition: background-color 0.2s ease;
|
329
372
|
align-self: flex-end;
|
330
373
|
}
|
331
374
|
|
@@ -334,36 +377,46 @@ body {
|
|
334
377
|
}
|
335
378
|
|
336
379
|
/* Responsive adjustments */
|
337
|
-
@media (max-width: 1200px) {
|
338
|
-
#chat-sidebar {
|
339
|
-
width: 350px;
|
340
|
-
}
|
341
|
-
}
|
342
|
-
|
343
380
|
@media (max-width: 1000px) {
|
344
381
|
#terminals-container {
|
345
382
|
grid-template-columns: 1fr;
|
346
383
|
}
|
384
|
+
|
385
|
+
.controls-row {
|
386
|
+
flex-direction: column;
|
387
|
+
gap: 10px;
|
388
|
+
}
|
389
|
+
|
390
|
+
.controls-row > div {
|
391
|
+
width: 100%;
|
392
|
+
}
|
347
393
|
}
|
348
394
|
|
349
395
|
@media (max-width: 768px) {
|
350
|
-
#
|
351
|
-
|
396
|
+
#terminals-container {
|
397
|
+
grid-template-columns: 1fr;
|
352
398
|
}
|
353
399
|
|
354
|
-
#
|
355
|
-
|
400
|
+
#toolbar-extended {
|
401
|
+
padding: 10px;
|
356
402
|
}
|
357
403
|
|
358
|
-
#
|
404
|
+
#default-prompt-text {
|
359
405
|
width: 100%;
|
360
|
-
height: 300px;
|
361
|
-
border-left: none;
|
362
|
-
border-top: 1px solid #444;
|
363
406
|
}
|
364
407
|
|
365
|
-
#
|
366
|
-
|
408
|
+
#chat-input {
|
409
|
+
min-height: 60px;
|
410
|
+
}
|
411
|
+
|
412
|
+
.input-row {
|
413
|
+
flex-direction: column;
|
414
|
+
align-items: stretch;
|
415
|
+
}
|
416
|
+
|
417
|
+
#send-message-btn {
|
418
|
+
align-self: stretch;
|
419
|
+
padding: 10px;
|
367
420
|
}
|
368
421
|
}
|
369
422
|
|
@@ -384,4 +437,61 @@ body {
|
|
384
437
|
|
385
438
|
::-webkit-scrollbar-thumb:hover {
|
386
439
|
background: #555;
|
440
|
+
}
|
441
|
+
|
442
|
+
/* Command Dropdown */
|
443
|
+
.command-dropdown {
|
444
|
+
position: absolute;
|
445
|
+
top: 100%;
|
446
|
+
left: 0;
|
447
|
+
right: 0;
|
448
|
+
background-color: #2d2d30;
|
449
|
+
border: 1px solid #555;
|
450
|
+
border-radius: 4px;
|
451
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
452
|
+
z-index: 1000;
|
453
|
+
max-height: 200px;
|
454
|
+
overflow-y: auto;
|
455
|
+
margin-top: 4px;
|
456
|
+
}
|
457
|
+
|
458
|
+
.command-list {
|
459
|
+
padding: 4px 0;
|
460
|
+
}
|
461
|
+
|
462
|
+
.command-option {
|
463
|
+
display: flex;
|
464
|
+
flex-direction: column;
|
465
|
+
padding: 8px 12px;
|
466
|
+
cursor: pointer;
|
467
|
+
border-bottom: 1px solid #3e3e42;
|
468
|
+
transition: background-color 0.15s ease;
|
469
|
+
}
|
470
|
+
|
471
|
+
.command-option:last-child {
|
472
|
+
border-bottom: none;
|
473
|
+
}
|
474
|
+
|
475
|
+
.command-option:hover,
|
476
|
+
.command-option.selected {
|
477
|
+
background-color: #094771;
|
478
|
+
}
|
479
|
+
|
480
|
+
.command-name {
|
481
|
+
font-size: 14px;
|
482
|
+
font-weight: 500;
|
483
|
+
color: #ffffff;
|
484
|
+
margin-bottom: 2px;
|
485
|
+
}
|
486
|
+
|
487
|
+
.command-description {
|
488
|
+
font-size: 12px;
|
489
|
+
color: #cccccc;
|
490
|
+
font-style: italic;
|
491
|
+
}
|
492
|
+
|
493
|
+
.command-syntax {
|
494
|
+
font-size: 11px;
|
495
|
+
color: #999;
|
496
|
+
font-family: 'Consolas', 'Courier New', monospace;
|
387
497
|
}
|