@adiontaegerron/claude-multi-terminal 1.1.0 → 1.2.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/README.md CHANGED
@@ -9,7 +9,14 @@ A multi-terminal editor for coordinating multiple Claude Code instances. Run mul
9
9
  - **Multi-Terminal Support**: Run multiple Claude Code instances in a 2x2 grid layout
10
10
  - **Unified Chat Interface**: Send commands to selected terminals from a single chat interface
11
11
  - **Terminal Selection**: Choose which terminals receive your commands with checkboxes
12
+ - **Multi-@ Terminal Commands**: Send commands to multiple terminals with various syntaxes:
13
+ - `@all command` - Send to all terminals
14
+ - `@term1,term2,term3 command` - Comma-separated terminals
15
+ - `@term1 @term2 command` - Space-separated terminals
16
+ - **Smart Autocomplete**: Get terminal suggestions while typing @ anywhere in your message
17
+ - **Slash Commands**: Built-in commands for terminal management
12
18
  - **Default Prompt**: Set a default prompt to prepend to all messages
19
+ - **Plan Mode Support**: Automatically enters plan mode with Shift+Tab
13
20
  - **Auto-Start Claude**: Each terminal automatically starts Claude Code
14
21
  - **Rename Terminals**: Double-click terminal titles to rename them
15
22
  - **Responsive Layout**: Adapts to different screen sizes
@@ -49,12 +56,36 @@ This will open the multi-terminal editor with all terminals starting in your cur
49
56
  4. **Send Commands**: Type in the chat input and click "Send" or press Enter
50
57
  5. **Use Default Prompt**: Expand the "Default Prompt" section to set a prompt that's prepended to all messages
51
58
 
59
+ ### Slash Commands
60
+
61
+ - `/send <terminal> <command>` - Send command to specific terminal
62
+ - `/send-all <command>` or `/broadcast <command>` - Send to all terminals
63
+ - `/list` - List all terminals
64
+ - `/create [name]` - Create new terminal with optional name
65
+ - `/close <terminal>` - Close specific terminal
66
+ - `/return <terminal>` - Submit current input in terminal (press Enter)
67
+ - `/clear <terminal>` or `/interrupt <terminal>` - Send Ctrl+C to terminal
68
+ - `/help` - Show available commands
69
+
70
+ ### @ Terminal Commands
71
+
72
+ Send commands directly to terminals using @ syntax:
73
+
74
+ - `@Terminal1 ls -la` - Send to single terminal
75
+ - `@"My Terminal" pwd` - Terminal with spaces in name
76
+ - `@term1,term2,term3 date` - Multiple terminals (comma-separated)
77
+ - `@term1 @term2 @term3 whoami` - Multiple terminals (space-separated)
78
+ - `@all echo "Hello"` - Send to all terminals
79
+
80
+ **Autocomplete**: Type `@` anywhere to get terminal suggestions!
81
+
52
82
  ### Tips
53
83
 
54
84
  - Each terminal automatically starts Claude Code when created
55
- - Messages include a plan mode instruction by default
85
+ - Plan mode is activated automatically using Shift+Tab
56
86
  - Double-click terminal titles to rename them for better organization
57
87
  - The application uses your current directory as the working directory for all terminals
88
+ - Use multi-@ commands to coordinate multiple Claude instances efficiently
58
89
 
59
90
  ## Development
60
91
 
package/index.html CHANGED
@@ -36,18 +36,22 @@
36
36
  </div>
37
37
  </div>
38
38
 
39
+ <div class="plan-mode-row">
40
+ <label class="plan-mode-option">
41
+ <input type="checkbox" id="use-plan-mode" checked />
42
+ <span>Send in Plan Mode</span>
43
+ </label>
44
+ </div>
45
+
39
46
  <div class="input-row">
40
47
  <div class="input-wrapper">
41
- <textarea id="chat-input" placeholder="Type your message or / for commands..."></textarea>
48
+ <div id="input-overlay" class="input-overlay"></div>
49
+ <textarea id="chat-input" placeholder="Type your message, / for commands, or @terminal for direct send..."></textarea>
42
50
  <div id="command-dropdown" class="command-dropdown" style="display: none;">
43
51
  <div id="command-list" class="command-list">
44
52
  <!-- Command options will be populated here -->
45
53
  </div>
46
54
  </div>
47
- <label class="plan-mode-option">
48
- <input type="checkbox" id="use-plan-mode" checked />
49
- <span>Use Plan Mode</span>
50
- </label>
51
55
  </div>
52
56
  <button id="send-message-btn">Send</button>
53
57
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adiontaegerron/claude-multi-terminal",
3
- "version": "1.1.0",
3
+ "version": "1.2.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
@@ -337,6 +337,13 @@ function sendMessage() {
337
337
  return;
338
338
  }
339
339
 
340
+ // Check if this is an @terminal command
341
+ if (message.startsWith('@')) {
342
+ executeAtCommand(message);
343
+ input.value = '';
344
+ return;
345
+ }
346
+
340
347
  // Check if default prompt should be prepended
341
348
  let fullMessage = message;
342
349
  const useDefaultPrompt = document.getElementById('use-default-prompt').checked;
@@ -361,18 +368,32 @@ function sendMessage() {
361
368
 
362
369
  // Send to selected terminals
363
370
  selectedTerminals.forEach(terminalId => {
371
+ const termData = terminals.get(terminalId);
372
+ if (!termData) return;
373
+
364
374
  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');
375
+ // Send Shift+Tab twice to enter plan mode
376
+ // ESC[Z is the escape sequence for Shift+Tab
377
+ ipcRenderer.invoke('terminal:write', terminalId, '\x1b[Z\x1b[Z');
367
378
 
368
- // Small delay to ensure the first command is processed
379
+ // Small delay to ensure plan mode is activated
369
380
  setTimeout(() => {
370
381
  // Send the actual message with carriage return to execute
371
382
  ipcRenderer.invoke('terminal:write', terminalId, fullMessage + '\r');
372
- }, 100);
383
+
384
+ // Auto-return after sending the message
385
+ setTimeout(() => {
386
+ executeReturnCommand([termData.name], true);
387
+ }, 150);
388
+ }, 300);
373
389
  } else {
374
390
  // Send the message directly to the terminal
375
391
  ipcRenderer.invoke('terminal:write', terminalId, fullMessage + '\r');
392
+
393
+ // Auto-return after sending the message
394
+ setTimeout(() => {
395
+ executeReturnCommand([termData.name], true);
396
+ }, 150);
376
397
  }
377
398
  });
378
399
 
@@ -536,7 +557,9 @@ function executeHelpCommand() {
536
557
  `${cmd.syntax} - ${cmd.description}`
537
558
  ).join('\n');
538
559
 
539
- addChatMessage('System', 'Available commands:\n' + helpText);
560
+ const atHelp = '\n@terminal <command> - Send command directly to named terminal(s)\nExamples:\n @Tom ls -la - Send to single terminal\n @"My Server" status - Terminal with spaces in name\n @term1,term2,term3 pwd - Multiple terminals (comma-separated)\n @term1 @term2 date - Multiple terminals (space-separated)\n @all whoami - Send to all terminals';
561
+
562
+ addChatMessage('System', 'Available commands:\n' + helpText + atHelp);
540
563
  }
541
564
 
542
565
  function executeCreateCommand(args) {
@@ -564,9 +587,9 @@ function executeCloseCommand(args) {
564
587
  addChatMessage('System', `✅ Closed terminal ${result.terminal.name}`);
565
588
  }
566
589
 
567
- function executeReturnCommand(args) {
590
+ function executeReturnCommand(args, silent = false) {
568
591
  if (args.length === 0) {
569
- addChatMessage('System', 'Usage: /return <terminal>');
592
+ if (!silent) addChatMessage('System', 'Usage: /return <terminal>');
570
593
  return;
571
594
  }
572
595
 
@@ -574,14 +597,18 @@ function executeReturnCommand(args) {
574
597
  const result = findTerminal(terminalIdentifier);
575
598
 
576
599
  if (!result) {
577
- const available = Array.from(terminals.values()).map(t => t.name).join(', ');
578
- addChatMessage('System', `Terminal '${terminalIdentifier}' not found. Available: ${available}`);
600
+ if (!silent) {
601
+ const available = Array.from(terminals.values()).map(t => t.name).join(', ');
602
+ addChatMessage('System', `Terminal '${terminalIdentifier}' not found. Available: ${available}`);
603
+ }
579
604
  return;
580
605
  }
581
606
 
582
607
  // Send carriage return to submit current input
583
608
  ipcRenderer.invoke('terminal:write', result.id, '\r');
584
- addChatMessage('System', `✅ Submitted input in ${result.terminal.name}`);
609
+ if (!silent) {
610
+ addChatMessage('System', `✅ Submitted input in ${result.terminal.name}`);
611
+ }
585
612
  }
586
613
 
587
614
  function executeClearCommand(args) {
@@ -604,6 +631,114 @@ function executeClearCommand(args) {
604
631
  addChatMessage('System', `✅ Sent Ctrl+C to ${result.terminal.name}`);
605
632
  }
606
633
 
634
+ function executeAtCommand(message) {
635
+ // Parse various @terminal command syntaxes:
636
+ // 1. Space-separated: "@term1 @term2 command"
637
+ // 2. Comma-separated: "@term1,term2,term3 command"
638
+ // 3. Single terminal: "@terminal command" or '@"terminal name" command'
639
+ // 4. All terminals: "@all command"
640
+
641
+ let targetTerminals = [];
642
+ let command = '';
643
+
644
+ // First, try to match space-separated terminals at the beginning
645
+ const spaceSeparatedRegex = /^(@(?:"[^"]+"|[^\s,]+)\s+)+(.+)$/;
646
+ const spaceSeparatedMatch = message.match(spaceSeparatedRegex);
647
+
648
+ if (spaceSeparatedMatch) {
649
+ // Extract all @terminal references
650
+ const terminalsStr = spaceSeparatedMatch[0].substring(0, spaceSeparatedMatch[0].length - spaceSeparatedMatch[2].length);
651
+ command = spaceSeparatedMatch[2].trim();
652
+
653
+ // Parse each @terminal reference
654
+ const terminalMatches = terminalsStr.matchAll(/@(?:"([^"]+)"|([^\s,]+))/g);
655
+ for (const match of terminalMatches) {
656
+ const terminalName = match[1] || match[2];
657
+ targetTerminals.push(terminalName.trim());
658
+ }
659
+ } else {
660
+ // Try single @terminal with possible comma-separated names
661
+ let match = message.match(/^@"([^"]+)"\s+(.+)$/); // Handle quoted names
662
+
663
+ if (!match) {
664
+ match = message.match(/^@(\S+)\s+(.+)$/); // Handle unquoted names (including comma-separated)
665
+ }
666
+
667
+ if (!match) {
668
+ addChatMessage('System', 'Usage: @terminal <command>\nExamples:\n @Tom ls -la\n @term1,term2 pwd\n @term1 @term2 date\n @all whoami');
669
+ return;
670
+ }
671
+
672
+ const terminalNameStr = match[1];
673
+ command = match[2];
674
+
675
+ // Check if it's @all
676
+ if (terminalNameStr.toLowerCase() === 'all') {
677
+ // Send to all terminals
678
+ const allTerminals = Array.from(terminals.entries());
679
+ if (allTerminals.length === 0) {
680
+ addChatMessage('System', 'No terminals available');
681
+ return;
682
+ }
683
+
684
+ const terminalNames = [];
685
+ for (const [id, termData] of allTerminals) {
686
+ ipcRenderer.invoke('terminal:write', id, command + '\r');
687
+ terminalNames.push(termData.name);
688
+ }
689
+
690
+ addChatMessage('System', `✅ Sent '${command}' to all ${allTerminals.length} terminals`);
691
+
692
+ // Auto-return to all terminals after a short delay
693
+ setTimeout(() => {
694
+ for (const name of terminalNames) {
695
+ executeReturnCommand([name], true);
696
+ }
697
+ }, 150);
698
+ return;
699
+ }
700
+
701
+ // Handle comma-separated terminal names
702
+ targetTerminals = terminalNameStr.split(',').map(name => name.trim());
703
+ }
704
+
705
+ // Process all target terminals
706
+ const foundTerminals = [];
707
+ const notFoundTerminals = [];
708
+
709
+ for (const terminalName of targetTerminals) {
710
+ const result = findTerminal(terminalName);
711
+ if (result) {
712
+ foundTerminals.push({ name: terminalName, id: result.id, terminal: result.terminal });
713
+ } else {
714
+ notFoundTerminals.push(terminalName);
715
+ }
716
+ }
717
+
718
+ // Report not found terminals
719
+ if (notFoundTerminals.length > 0) {
720
+ const available = Array.from(terminals.values()).map(t => t.name).join(', ');
721
+ addChatMessage('System', `Terminal(s) not found: ${notFoundTerminals.join(', ')}. Available: ${available}`);
722
+ }
723
+
724
+ // Send command to all found terminals
725
+ if (foundTerminals.length > 0) {
726
+ const sentToNames = [];
727
+ for (const { id, terminal } of foundTerminals) {
728
+ ipcRenderer.invoke('terminal:write', id, command + '\r');
729
+ sentToNames.push(terminal.name);
730
+ }
731
+ addChatMessage('System', `✅ Sent '${command}' to ${foundTerminals.length} terminal(s): ${sentToNames.join(', ')}`);
732
+
733
+ // Auto-return to all terminals after a short delay
734
+ setTimeout(() => {
735
+ for (const name of sentToNames) {
736
+ executeReturnCommand([name], true); // true = silent mode
737
+ }
738
+ }, 150);
739
+ }
740
+ }
741
+
607
742
  // Dropdown Functions
608
743
  function showCommandDropdown() {
609
744
  const dropdown = document.getElementById('command-dropdown');
@@ -626,6 +761,69 @@ function showCommandDropdown() {
626
761
  selectedCommandIndex = -1;
627
762
  }
628
763
 
764
+ function showTerminalDropdown(cursorPos = null) {
765
+ const dropdown = document.getElementById('command-dropdown');
766
+ const input = document.getElementById('chat-input');
767
+ const inputValue = input.value;
768
+ const pos = cursorPos !== null ? cursorPos : input.selectionStart;
769
+
770
+ // Find the @ symbol and extract the partial terminal name
771
+ let atPos = -1;
772
+ let searchTerm = '';
773
+
774
+ // Search backwards from cursor to find the @ symbol
775
+ for (let i = pos - 1; i >= 0; i--) {
776
+ if (inputValue[i] === '@') {
777
+ atPos = i;
778
+ // Extract text from @ to cursor position
779
+ searchTerm = inputValue.substring(i + 1, pos).toLowerCase();
780
+ break;
781
+ } else if (inputValue[i] === ' ' || inputValue[i] === ',') {
782
+ // Stop if we hit a space or comma before finding @
783
+ break;
784
+ }
785
+ }
786
+
787
+ // If no @ found or @ is not at a valid position, hide dropdown
788
+ if (atPos === -1) {
789
+ hideCommandDropdown();
790
+ return;
791
+ }
792
+
793
+ const availableTerminals = Array.from(terminals.entries()).map(([id, termData]) => ({
794
+ name: '@' + termData.name,
795
+ syntax: '@' + termData.name + ' <command>',
796
+ description: `Send command to ${termData.name}`,
797
+ id: id,
798
+ isTerminal: true,
799
+ terminalName: termData.name,
800
+ atPosition: atPos
801
+ }));
802
+
803
+ // Add @all option
804
+ availableTerminals.unshift({
805
+ name: '@all',
806
+ syntax: '@all <command>',
807
+ description: 'Send command to all terminals',
808
+ isTerminal: true,
809
+ terminalName: 'all',
810
+ atPosition: atPos
811
+ });
812
+
813
+ currentCommands = availableTerminals.filter(term =>
814
+ term.terminalName.toLowerCase().startsWith(searchTerm)
815
+ );
816
+
817
+ if (currentCommands.length === 0) {
818
+ hideCommandDropdown();
819
+ return;
820
+ }
821
+
822
+ populateCommandDropdown();
823
+ dropdown.style.display = 'block';
824
+ selectedCommandIndex = -1;
825
+ }
826
+
629
827
  function hideCommandDropdown() {
630
828
  const dropdown = document.getElementById('command-dropdown');
631
829
  dropdown.style.display = 'none';
@@ -672,14 +870,35 @@ function selectCommand(index) {
672
870
  const command = currentCommands[index];
673
871
  const input = document.getElementById('chat-input');
674
872
 
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);
873
+ // Check if this is a terminal selection with position info
874
+ if (command.isTerminal && command.atPosition !== undefined) {
875
+ const value = input.value;
876
+ const atPos = command.atPosition;
877
+
878
+ // Find the end of the partial terminal name
879
+ let endPos = input.selectionStart;
880
+
881
+ // Replace the partial terminal name with the selected one
882
+ const beforeAt = value.substring(0, atPos);
883
+ const afterCursor = value.substring(endPos);
884
+
885
+ // Insert the selected terminal name (without the @, as it's already there)
886
+ input.value = beforeAt + '@' + command.terminalName + ' ' + afterCursor;
887
+
888
+ // Position cursor after the inserted terminal name and space
889
+ const newCursorPos = atPos + command.terminalName.length + 2; // +2 for @ and space
890
+ input.setSelectionRange(newCursorPos, newCursorPos);
891
+ } else {
892
+ // Original behavior for slash commands
893
+ input.value = command.name + ' ';
894
+ input.setSelectionRange(input.value.length, input.value.length);
895
+ }
681
896
 
897
+ input.focus();
682
898
  hideCommandDropdown();
899
+
900
+ // Trigger input event to update highlighting
901
+ input.dispatchEvent(new Event('input', { bubbles: true }));
683
902
  }
684
903
 
685
904
  function navigateDropdown(direction) {
@@ -705,16 +924,88 @@ function selectCurrentCommand() {
705
924
  }
706
925
  }
707
926
 
927
+ // Update overlay with syntax highlighting
928
+ function updateOverlayHighlighting() {
929
+ const input = document.getElementById('chat-input');
930
+ const overlay = document.getElementById('input-overlay');
931
+ const value = input.value;
932
+
933
+ // Parse and highlight text
934
+ let highlightedText = escapeHtml(value);
935
+
936
+ // Highlight @terminal commands
937
+ highlightedText = highlightedText.replace(
938
+ /@"([^"]+)"/g,
939
+ (match, terminalName) => {
940
+ const result = findTerminal(terminalName);
941
+ return result ? `@<span class="highlight-terminal">"${terminalName}"</span>` : match;
942
+ }
943
+ );
944
+
945
+ highlightedText = highlightedText.replace(
946
+ /@(\S+)/g,
947
+ (match, terminalName) => {
948
+ // Handle @all specially
949
+ if (terminalName.toLowerCase() === 'all') {
950
+ return `@<span class="highlight-terminal">${terminalName}</span>`;
951
+ }
952
+ // Handle comma-separated terminals
953
+ if (terminalName.includes(',')) {
954
+ const parts = terminalName.split(',');
955
+ const highlightedParts = parts.map((part, index) => {
956
+ const trimmedPart = part.trim();
957
+ const result = findTerminal(trimmedPart);
958
+ return (index > 0 ? ',' : '') + (result ? `<span class="highlight-terminal">${trimmedPart}</span>` : trimmedPart);
959
+ }).join('');
960
+ return '@' + highlightedParts;
961
+ }
962
+ // Single terminal
963
+ const result = findTerminal(terminalName);
964
+ return result ? `@<span class="highlight-terminal">${terminalName}</span>` : match;
965
+ }
966
+ );
967
+
968
+ // Highlight slash commands
969
+ highlightedText = highlightedText.replace(
970
+ /^(\/\w+)/gm,
971
+ (match, command) => {
972
+ const validCommand = availableCommands.find(cmd => cmd.name === command);
973
+ return validCommand ? `<span class="highlight-command">${command}</span>` : match;
974
+ }
975
+ );
976
+
977
+ overlay.innerHTML = highlightedText;
978
+
979
+ // Sync scroll position
980
+ overlay.scrollTop = input.scrollTop;
981
+ overlay.scrollLeft = input.scrollLeft;
982
+ }
983
+
984
+ function escapeHtml(text) {
985
+ const div = document.createElement('div');
986
+ div.textContent = text;
987
+ return div.innerHTML;
988
+ }
989
+
708
990
  // Initialize collapsible default prompt section
709
991
  function initCollapsible() {
710
992
  const toggle = document.querySelector('.collapse-toggle');
711
993
  const content = document.querySelector('.collapsible-content');
994
+ const header = document.querySelector('.collapsible-header');
712
995
 
713
- toggle.addEventListener('click', () => {
996
+ const toggleSection = (e) => {
997
+ // Prevent double-triggering when clicking the toggle button
998
+ if (e && e.target === toggle) {
999
+ e.stopPropagation();
1000
+ }
1001
+
714
1002
  const isExpanded = content.style.display !== 'none';
715
1003
  content.style.display = isExpanded ? 'none' : 'block';
716
1004
  toggle.textContent = isExpanded ? '▶' : '▼';
717
- });
1005
+ };
1006
+
1007
+ // Make the entire header clickable
1008
+ header.addEventListener('click', toggleSection);
718
1009
  }
719
1010
 
720
1011
 
@@ -753,6 +1044,9 @@ document.addEventListener('DOMContentLoaded', () => {
753
1044
  e.preventDefault();
754
1045
  if (selectedCommandIndex >= 0) {
755
1046
  selectCurrentCommand();
1047
+ } else if (currentCommands.length > 0) {
1048
+ // Auto-select first option if none selected
1049
+ selectCommand(0);
756
1050
  }
757
1051
  } else if (e.key === 'ArrowUp' && !isDropdownVisible && commandHistory.length > 0) {
758
1052
  e.preventDefault();
@@ -775,21 +1069,45 @@ document.addEventListener('DOMContentLoaded', () => {
775
1069
  }
776
1070
  });
777
1071
 
778
- // Chat input - Show dropdown on '/' and filter as user types
1072
+ // Chat input - Show dropdown on '/' and '@' and filter as user types
779
1073
  document.getElementById('chat-input').addEventListener('input', (e) => {
780
1074
  const input = e.target;
781
1075
  const value = input.value;
782
1076
  const cursorPos = input.selectionStart;
783
1077
 
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
1078
+ // Update overlay highlighting
1079
+ updateOverlayHighlighting();
1080
+
1081
+ // Check for slash commands (only at beginning and when cursor at end)
1082
+ if (cursorPos === value.length && value.startsWith('/') && value.length >= 1) {
789
1083
  showCommandDropdown();
790
- } else {
791
- hideCommandDropdown();
1084
+ return;
1085
+ }
1086
+
1087
+ // Check for @ at cursor position (can be anywhere in the input)
1088
+ if (cursorPos > 0) {
1089
+ // Look for @ at or before cursor position
1090
+ let foundAt = false;
1091
+ for (let i = cursorPos - 1; i >= 0; i--) {
1092
+ if (value[i] === '@') {
1093
+ foundAt = true;
1094
+ break;
1095
+ } else if (value[i] === ' ' || value[i] === ',') {
1096
+ // If we hit a space or comma, check if it's immediately followed by @
1097
+ if (i === cursorPos - 1 && value[cursorPos - 1] === '@') {
1098
+ foundAt = true;
1099
+ }
1100
+ break;
1101
+ }
1102
+ }
1103
+
1104
+ if (foundAt) {
1105
+ showTerminalDropdown(cursorPos);
1106
+ return;
1107
+ }
792
1108
  }
1109
+
1110
+ hideCommandDropdown();
793
1111
  });
794
1112
 
795
1113
  // Hide dropdown when clicking outside
@@ -802,6 +1120,14 @@ document.addEventListener('DOMContentLoaded', () => {
802
1120
  }
803
1121
  });
804
1122
 
1123
+ // Sync overlay scroll with input
1124
+ document.getElementById('chat-input').addEventListener('scroll', () => {
1125
+ const input = document.getElementById('chat-input');
1126
+ const overlay = document.getElementById('input-overlay');
1127
+ overlay.scrollTop = input.scrollTop;
1128
+ overlay.scrollLeft = input.scrollLeft;
1129
+ });
1130
+
805
1131
  // Initialize collapsible
806
1132
  initCollapsible();
807
1133
 
package/style.css CHANGED
@@ -312,15 +312,39 @@ body {
312
312
 
313
313
  /* Removed - Mode selector is now integrated into input area */
314
314
 
315
+ /* Input Overlay for Syntax Highlighting */
316
+ .input-overlay {
317
+ position: absolute;
318
+ top: 0;
319
+ left: 0;
320
+ right: 0;
321
+ min-height: 80px;
322
+ max-height: 200px;
323
+ padding: 10px 12px;
324
+ border-radius: 4px;
325
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
326
+ font-size: 14px;
327
+ line-height: 1.5;
328
+ color: transparent;
329
+ background: transparent;
330
+ pointer-events: none;
331
+ z-index: 1;
332
+ white-space: pre-wrap;
333
+ word-wrap: break-word;
334
+ overflow: hidden;
335
+ }
336
+
315
337
  /* Chat Input - Full Width */
316
338
  #chat-input {
339
+ position: relative;
340
+ z-index: 2;
317
341
  width: 100%;
318
342
  min-height: 80px;
319
343
  max-height: 200px;
320
344
  background-color: #3c3c3c;
321
345
  border: 1px solid #555;
322
346
  color: white;
323
- padding: 10px 12px 35px 12px;
347
+ padding: 10px 12px;
324
348
  border-radius: 4px;
325
349
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
326
350
  font-size: 14px;
@@ -334,26 +358,38 @@ body {
334
358
  box-shadow: 0 0 0 2px rgba(14, 99, 156, 0.2);
335
359
  }
336
360
 
361
+ /* Syntax highlighting styles */
362
+ .highlight-terminal {
363
+ color: #4CAF50;
364
+ }
365
+
366
+ .highlight-command {
367
+ color: #FF9800;
368
+ }
369
+
370
+ /* Plan Mode Row */
371
+ .plan-mode-row {
372
+ margin-bottom: 8px;
373
+ }
374
+
337
375
  /* Plan Mode Option */
338
376
  .plan-mode-option {
339
- position: absolute;
340
- bottom: 10px;
341
- left: 12px;
342
377
  display: flex;
343
378
  align-items: center;
344
- gap: 6px;
345
- font-size: 12px;
346
- color: #999;
379
+ gap: 8px;
380
+ font-size: 14px;
381
+ color: #cccccc;
347
382
  cursor: pointer;
383
+ padding: 4px 0;
348
384
  }
349
385
 
350
386
  .plan-mode-option:hover {
351
- color: #cccccc;
387
+ color: #ffffff;
352
388
  }
353
389
 
354
390
  .plan-mode-option input[type="checkbox"] {
355
- width: 14px;
356
- height: 14px;
391
+ width: 16px;
392
+ height: 16px;
357
393
  cursor: pointer;
358
394
  }
359
395