@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.
@@ -15,7 +15,9 @@
15
15
  "Bash(git push:*)",
16
16
  "Bash(mkdir:*)",
17
17
  "Bash(chmod:*)",
18
- "Bash(npm:*)"
18
+ "Bash(npm:*)",
19
+ "Bash(claude --print \"Please enter plan mode (press Shift+Tab twice)\")",
20
+ "WebFetch(domain:docs.anthropic.com)"
19
21
  ],
20
22
  "deny": []
21
23
  }
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="main-layout">
16
- <div id="terminals-area">
17
- <div id="terminals-container">
18
- <!-- Terminal instances will be added here dynamically -->
19
- </div>
20
- </div>
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="chat-controls">
32
- <div id="terminal-selector">
33
- <label>Send to:</label>
34
- <div id="terminal-checkboxes">
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
- <div id="default-prompt-section" class="collapsible">
40
- <div class="collapsible-header">
41
- <button class="collapse-toggle">▶</button>
42
- <span>Default Prompt</span>
43
- </div>
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
- <div class="message-input-section">
54
- <textarea id="chat-input" placeholder="Type your message..."></textarea>
55
- <button id="send-message-btn">Send</button>
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(term => term.kill());
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
- terminals.set(terminalId, ptyProcess);
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 terminal = terminals.get(terminalId);
90
- if (terminal) {
91
- terminal.write(data);
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 terminal = terminals.get(terminalId);
100
- if (terminal) {
101
- terminal.resize(cols, rows);
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 terminal = terminals.get(terminalId);
110
- if (terminal) {
111
- terminal.kill();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adiontaegerron/claude-multi-terminal",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Multi-terminal editor for coordinating multiple Claude Code instances",
5
5
  "main": "main.js",
6
6
  "bin": {
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
- title.textContent = `Terminal ${terminalCounter}`;
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: `Terminal ${terminalCounter}`
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, `Terminal ${terminalCounter}`);
159
+ addTerminalCheckbox(terminalId, terminalName);
91
160
 
92
161
  // Auto-start Claude Code
93
162
  setTimeout(() => {
94
- ipcRenderer.invoke('terminal:write', terminalId, 'claude\n');
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
- // Send both the plan mode instruction and message
273
- const messageToSend = `Please enter plan mode (press Shift+Tab twice)\n\n${fullMessage}`;
274
- ipcRenderer.invoke('terminal:write', terminalId, messageToSend + '\r\n');
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
- sendMessage();
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
- #main-layout {
42
- flex: 1;
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
- overflow: hidden;
48
+ flex-direction: column;
49
+ gap: 12px;
45
50
  }
46
51
 
47
- /* Terminals Area - Left Side */
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
- /* Chat Sidebar - Right Side */
141
- #chat-sidebar {
142
- width: 400px;
143
- background-color: #1e1e1e;
144
- border-left: 1px solid #444;
145
+ /* Control Rows */
146
+ .controls-row {
145
147
  display: flex;
146
- flex-direction: column;
148
+ gap: 20px;
149
+ align-items: stretch;
150
+ margin-bottom: 10px;
147
151
  }
148
152
 
149
- .chat-header {
150
- padding: 15px;
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: 10px;
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
- .message-sender {
180
- font-weight: bold;
181
- color: #4da6ff;
182
- font-size: 14px;
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
- #chat-controls {
193
- background-color: #252526;
194
- border-top: 1px solid #444;
195
- padding: 15px;
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: 14px;
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: column;
213
- gap: 8px;
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
- /* Collapsible Default Prompt */
233
+ /* Default Prompt Section */
236
234
  #default-prompt-section {
237
- margin-bottom: 15px;
235
+ position: relative;
238
236
  }
239
237
 
240
238
  .collapsible-header {
241
239
  display: flex;
242
240
  align-items: center;
243
- gap: 8px;
241
+ gap: 6px;
244
242
  cursor: pointer;
245
243
  user-select: none;
246
- padding: 8px;
247
- background-color: #3c3c3c;
248
- border-radius: 4px;
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
- background-color: #444;
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: 80px;
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: 13px;
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: 13px;
308
+ font-size: 12px;
293
309
  color: #cccccc;
310
+ padding: 3px 0;
294
311
  }
295
312
 
296
- /* Message Input */
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: 8px;
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: 10px 20px;
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
- #main-layout {
351
- flex-direction: column;
396
+ #terminals-container {
397
+ grid-template-columns: 1fr;
352
398
  }
353
399
 
354
- #terminals-area {
355
- flex: 1;
400
+ #toolbar-extended {
401
+ padding: 10px;
356
402
  }
357
403
 
358
- #chat-sidebar {
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
- #terminals-container {
366
- grid-template-columns: 1fr;
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
  }