@adiontaegerron/claude-multi-terminal 1.1.0 → 2.0.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 +51 -5
- package/index.html +9 -12
- package/package.json +1 -1
- package/renderer.js +567 -106
- package/style.css +46 -63
package/README.md
CHANGED
@@ -7,13 +7,33 @@ A multi-terminal editor for coordinating multiple Claude Code instances. Run mul
|
|
7
7
|
## Features
|
8
8
|
|
9
9
|
- **Multi-Terminal Support**: Run multiple Claude Code instances in a 2x2 grid layout
|
10
|
-
- **Unified Chat Interface**: Send commands to
|
11
|
-
- **Terminal
|
10
|
+
- **Unified Chat Interface**: Send commands to terminals using @ syntax
|
11
|
+
- **Multi-@ Terminal Commands**: Target terminals with flexible syntax:
|
12
|
+
- `@all command` - Send to all terminals
|
13
|
+
- `@term1,term2,term3 command` - Comma-separated terminals
|
14
|
+
- `@term1 @term2 command` - Space-separated terminals
|
15
|
+
- **Mixed @/ Commands**: Combine terminal targeting with slash commands:
|
16
|
+
- `@term1 /clear` - Run slash command on specific terminal
|
17
|
+
- `@term1 @term2 /list` - Multiple terminals with slash commands
|
18
|
+
- `@all /interrupt` - Slash commands with @all
|
19
|
+
- **Smart Autocomplete**: Get suggestions while typing @ or / anywhere in your message
|
20
|
+
- **Slash Commands**: Built-in commands for terminal management (works anywhere)
|
12
21
|
- **Default Prompt**: Set a default prompt to prepend to all messages
|
22
|
+
- **Plan Mode Support**: Automatically enters plan mode with Shift+Tab
|
13
23
|
- **Auto-Start Claude**: Each terminal automatically starts Claude Code
|
14
24
|
- **Rename Terminals**: Double-click terminal titles to rename them
|
15
25
|
- **Responsive Layout**: Adapts to different screen sizes
|
16
26
|
|
27
|
+
## Breaking Changes in v2.0
|
28
|
+
|
29
|
+
- **Removed checkbox selection system** - All commands must now use @ syntax
|
30
|
+
- **No default terminal selection** - You must explicitly specify terminals with @
|
31
|
+
- **Plain text without @ or / will show an error** - Every command needs a target
|
32
|
+
|
33
|
+
To migrate from v1.x:
|
34
|
+
- Instead of selecting checkboxes and typing a command, use: `@term1 @term2 command`
|
35
|
+
- To send to all terminals, use: `@all command`
|
36
|
+
|
17
37
|
## Installation
|
18
38
|
|
19
39
|
### From npm
|
@@ -45,16 +65,42 @@ This will open the multi-terminal editor with all terminals starting in your cur
|
|
45
65
|
|
46
66
|
1. **Start the Application**: Run `claude-multi` in your project directory
|
47
67
|
2. **Create Terminals**: Click "New Terminal" to add more Claude instances (starts with one)
|
48
|
-
3. **
|
49
|
-
4. **Send Commands**: Type
|
68
|
+
3. **Target Terminals**: Use @ syntax to specify which terminals receive commands
|
69
|
+
4. **Send Commands**: Type your command with @ targeting and press Enter
|
50
70
|
5. **Use Default Prompt**: Expand the "Default Prompt" section to set a prompt that's prepended to all messages
|
51
71
|
|
72
|
+
**Important**: Version 2.0 removes checkbox selection. All commands must now use @ syntax to target terminals.
|
73
|
+
|
74
|
+
### Slash Commands
|
75
|
+
|
76
|
+
- `/send <terminal> <command>` - Send command to specific terminal
|
77
|
+
- `/send-all <command>` or `/broadcast <command>` - Send to all terminals
|
78
|
+
- `/list` - List all terminals
|
79
|
+
- `/create [name]` - Create new terminal with optional name
|
80
|
+
- `/close <terminal>` - Close specific terminal
|
81
|
+
- `/return <terminal>` - Submit current input in terminal (press Enter)
|
82
|
+
- `/clear <terminal>` or `/interrupt <terminal>` - Send Ctrl+C to terminal
|
83
|
+
- `/help` - Show available commands
|
84
|
+
|
85
|
+
### @ Terminal Commands
|
86
|
+
|
87
|
+
Send commands directly to terminals using @ syntax:
|
88
|
+
|
89
|
+
- `@Terminal1 ls -la` - Send to single terminal
|
90
|
+
- `@"My Terminal" pwd` - Terminal with spaces in name
|
91
|
+
- `@term1,term2,term3 date` - Multiple terminals (comma-separated)
|
92
|
+
- `@term1 @term2 @term3 whoami` - Multiple terminals (space-separated)
|
93
|
+
- `@all echo "Hello"` - Send to all terminals
|
94
|
+
|
95
|
+
**Autocomplete**: Type `@` anywhere to get terminal suggestions!
|
96
|
+
|
52
97
|
### Tips
|
53
98
|
|
54
99
|
- Each terminal automatically starts Claude Code when created
|
55
|
-
-
|
100
|
+
- Plan mode is activated automatically using Shift+Tab
|
56
101
|
- Double-click terminal titles to rename them for better organization
|
57
102
|
- The application uses your current directory as the working directory for all terminals
|
103
|
+
- Use multi-@ commands to coordinate multiple Claude instances efficiently
|
58
104
|
|
59
105
|
## Development
|
60
106
|
|
package/index.html
CHANGED
@@ -14,13 +14,6 @@
|
|
14
14
|
|
15
15
|
<div id="toolbar-extended">
|
16
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>
|
22
|
-
</div>
|
23
|
-
|
24
17
|
<div id="default-prompt-section" class="control-section collapsible">
|
25
18
|
<div class="collapsible-header">
|
26
19
|
<button class="collapse-toggle">▶</button>
|
@@ -36,18 +29,22 @@
|
|
36
29
|
</div>
|
37
30
|
</div>
|
38
31
|
|
32
|
+
<div class="plan-mode-row">
|
33
|
+
<label class="plan-mode-option">
|
34
|
+
<input type="checkbox" id="use-plan-mode" checked />
|
35
|
+
<span>Send in Plan Mode</span>
|
36
|
+
</label>
|
37
|
+
</div>
|
38
|
+
|
39
39
|
<div class="input-row">
|
40
40
|
<div class="input-wrapper">
|
41
|
-
<
|
41
|
+
<div id="input-overlay" class="input-overlay"></div>
|
42
|
+
<textarea id="chat-input" placeholder="Type your message, / for commands, or @terminal for direct send..."></textarea>
|
42
43
|
<div id="command-dropdown" class="command-dropdown" style="display: none;">
|
43
44
|
<div id="command-list" class="command-list">
|
44
45
|
<!-- Command options will be populated here -->
|
45
46
|
</div>
|
46
47
|
</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
48
|
</div>
|
52
49
|
<button id="send-message-btn">Send</button>
|
53
50
|
</div>
|
package/package.json
CHANGED
package/renderer.js
CHANGED
@@ -155,9 +155,6 @@ async function createTerminal(customName = null) {
|
|
155
155
|
// Create terminal in main process
|
156
156
|
await ipcRenderer.invoke('terminal:create', terminalId);
|
157
157
|
|
158
|
-
// Add checkbox to chat sidebar
|
159
|
-
addTerminalCheckbox(terminalId, terminalName);
|
160
|
-
|
161
158
|
// Auto-start Claude Code
|
162
159
|
setTimeout(() => {
|
163
160
|
ipcRenderer.invoke('terminal:write', terminalId, 'claude\r');
|
@@ -191,7 +188,6 @@ async function createTerminal(customName = null) {
|
|
191
188
|
if (newName && newName !== title.textContent) {
|
192
189
|
title.textContent = newName;
|
193
190
|
terminals.get(terminalId).name = newName;
|
194
|
-
updateTerminalCheckbox(terminalId, newName);
|
195
191
|
}
|
196
192
|
title.style.display = 'inline';
|
197
193
|
renameInput.style.display = 'none';
|
@@ -249,9 +245,6 @@ async function closeTerminal(terminalId) {
|
|
249
245
|
terminal.xterm.dispose();
|
250
246
|
terminal.element.remove();
|
251
247
|
terminals.delete(terminalId);
|
252
|
-
|
253
|
-
// Remove checkbox from chat
|
254
|
-
removeTerminalCheckbox(terminalId);
|
255
248
|
}
|
256
249
|
}
|
257
250
|
|
@@ -270,47 +263,83 @@ ipcRenderer.on('terminal:exit', (event, terminalId, exitCode) => {
|
|
270
263
|
}
|
271
264
|
});
|
272
265
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
266
|
+
// Parse complex commands that mix @ terminals and / commands
|
267
|
+
function parseComplexCommand(message) {
|
268
|
+
const commands = [];
|
269
|
+
let currentPos = 0;
|
270
|
+
|
271
|
+
while (currentPos < message.length) {
|
272
|
+
// Skip whitespace
|
273
|
+
while (currentPos < message.length && message[currentPos] === ' ') {
|
274
|
+
currentPos++;
|
275
|
+
}
|
276
|
+
|
277
|
+
if (currentPos >= message.length) break;
|
278
|
+
|
279
|
+
// Check if we're starting with @ terminals
|
280
|
+
const terminals = [];
|
281
|
+
while (currentPos < message.length && message[currentPos] === '@') {
|
282
|
+
// Parse @terminal or @"terminal name"
|
283
|
+
let terminalName = '';
|
284
|
+
currentPos++; // Skip @
|
285
|
+
|
286
|
+
if (currentPos < message.length && message[currentPos] === '"') {
|
287
|
+
// Quoted terminal name
|
288
|
+
currentPos++; // Skip opening quote
|
289
|
+
while (currentPos < message.length && message[currentPos] !== '"') {
|
290
|
+
terminalName += message[currentPos];
|
291
|
+
currentPos++;
|
292
|
+
}
|
293
|
+
if (currentPos < message.length) currentPos++; // Skip closing quote
|
294
|
+
} else {
|
295
|
+
// Unquoted terminal name (can include commas for multiple terminals)
|
296
|
+
while (currentPos < message.length &&
|
297
|
+
message[currentPos] !== ' ' &&
|
298
|
+
message[currentPos] !== '@' &&
|
299
|
+
message[currentPos] !== '/') {
|
300
|
+
terminalName += message[currentPos];
|
301
|
+
currentPos++;
|
302
|
+
}
|
303
|
+
}
|
304
|
+
|
305
|
+
// Handle comma-separated terminals
|
306
|
+
if (terminalName.includes(',')) {
|
307
|
+
terminals.push(...terminalName.split(',').map(t => t.trim()).filter(t => t));
|
308
|
+
} else if (terminalName) {
|
309
|
+
terminals.push(terminalName);
|
310
|
+
}
|
311
|
+
|
312
|
+
// Skip whitespace after terminal
|
313
|
+
while (currentPos < message.length && message[currentPos] === ' ') {
|
314
|
+
currentPos++;
|
315
|
+
}
|
316
|
+
}
|
317
|
+
|
318
|
+
// Now parse the command (either /command or regular command)
|
319
|
+
let command = '';
|
320
|
+
let commandStart = currentPos;
|
321
|
+
|
322
|
+
// Find the end of this command (next @ or end of string)
|
323
|
+
while (currentPos < message.length) {
|
324
|
+
if (message[currentPos] === '@' &&
|
325
|
+
(currentPos === 0 || message[currentPos - 1] === ' ')) {
|
326
|
+
// Found start of next terminal specification
|
327
|
+
break;
|
328
|
+
}
|
329
|
+
currentPos++;
|
330
|
+
}
|
331
|
+
|
332
|
+
command = message.substring(commandStart, currentPos).trim();
|
333
|
+
|
334
|
+
if (terminals.length > 0 && command) {
|
335
|
+
commands.push({ terminals, command });
|
336
|
+
} else if (command && !terminals.length) {
|
337
|
+
// Command without specific terminals (use all or selected)
|
338
|
+
commands.push({ terminals: [], command });
|
339
|
+
}
|
308
340
|
}
|
309
|
-
|
310
|
-
|
311
|
-
function getSelectedTerminals() {
|
312
|
-
const checkboxes = document.querySelectorAll('.terminal-selector-checkbox:checked');
|
313
|
-
return Array.from(checkboxes).map(cb => cb.value);
|
341
|
+
|
342
|
+
return commands;
|
314
343
|
}
|
315
344
|
|
316
345
|
function sendMessage() {
|
@@ -330,26 +359,11 @@ function sendMessage() {
|
|
330
359
|
// Hide command dropdown
|
331
360
|
hideCommandDropdown();
|
332
361
|
|
333
|
-
//
|
334
|
-
|
335
|
-
executeCommand(message);
|
336
|
-
input.value = '';
|
337
|
-
return;
|
338
|
-
}
|
339
|
-
|
340
|
-
// Check if default prompt should be prepended
|
341
|
-
let fullMessage = message;
|
342
|
-
const useDefaultPrompt = document.getElementById('use-default-prompt').checked;
|
343
|
-
const defaultPrompt = document.getElementById('default-prompt-text').value.trim();
|
344
|
-
|
345
|
-
if (useDefaultPrompt && defaultPrompt) {
|
346
|
-
fullMessage = defaultPrompt + ' ' + message;
|
347
|
-
}
|
362
|
+
// Parse the complex command
|
363
|
+
const parsedCommands = parseComplexCommand(message);
|
348
364
|
|
349
|
-
|
350
|
-
|
351
|
-
if (selectedTerminals.length === 0) {
|
352
|
-
alert('Please select at least one terminal');
|
365
|
+
if (parsedCommands.length === 0) {
|
366
|
+
addChatMessage('System', 'Invalid command format. Use @terminal command or /command syntax.');
|
353
367
|
return;
|
354
368
|
}
|
355
369
|
|
@@ -358,23 +372,107 @@ function sendMessage() {
|
|
358
372
|
|
359
373
|
// Check if plan mode is enabled
|
360
374
|
const usePlanMode = document.getElementById('use-plan-mode').checked;
|
375
|
+
const useDefaultPrompt = document.getElementById('use-default-prompt').checked;
|
376
|
+
const defaultPrompt = document.getElementById('default-prompt-text').value.trim();
|
361
377
|
|
362
|
-
//
|
363
|
-
|
364
|
-
if (
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
378
|
+
// Execute each parsed command
|
379
|
+
for (const { terminals: targetTerminals, command } of parsedCommands) {
|
380
|
+
// Apply default prompt if needed (only for non-slash commands)
|
381
|
+
let finalCommand = command;
|
382
|
+
if (useDefaultPrompt && defaultPrompt && !command.startsWith('/')) {
|
383
|
+
finalCommand = defaultPrompt + ' ' + command;
|
384
|
+
}
|
385
|
+
|
386
|
+
// Determine which terminals to use
|
387
|
+
let terminalsToUse = [];
|
388
|
+
if (targetTerminals.length === 0) {
|
389
|
+
// No specific terminals specified
|
390
|
+
if (command.startsWith('/')) {
|
391
|
+
// Slash command without terminals - execute normally
|
392
|
+
executeCommand(command);
|
393
|
+
continue;
|
394
|
+
} else {
|
395
|
+
// Regular command without terminals - error
|
396
|
+
addChatMessage('System', 'Please specify terminals using @terminal syntax.');
|
397
|
+
continue;
|
398
|
+
}
|
399
|
+
}
|
400
|
+
|
401
|
+
// Process terminal specifications
|
402
|
+
for (const terminalSpec of targetTerminals) {
|
403
|
+
if (terminalSpec.toLowerCase() === 'all') {
|
404
|
+
// @all - use all terminals
|
405
|
+
terminalsToUse = Array.from(terminals.keys());
|
406
|
+
break;
|
407
|
+
} else {
|
408
|
+
// Find specific terminal
|
409
|
+
const result = findTerminal(terminalSpec);
|
410
|
+
if (result) {
|
411
|
+
terminalsToUse.push(result.id);
|
412
|
+
} else {
|
413
|
+
addChatMessage('System', `Terminal '${terminalSpec}' not found.`);
|
414
|
+
}
|
415
|
+
}
|
416
|
+
}
|
417
|
+
|
418
|
+
// Execute command on all target terminals
|
419
|
+
if (command.startsWith('/')) {
|
420
|
+
// Slash command - execute for each terminal
|
421
|
+
for (const terminalId of terminalsToUse) {
|
422
|
+
const termData = terminals.get(terminalId);
|
423
|
+
if (!termData) continue;
|
424
|
+
|
425
|
+
// Parse and execute the slash command with terminal context
|
426
|
+
const { command: slashCmd, args } = parseCommand(command);
|
427
|
+
|
428
|
+
// Modify args to include terminal reference if needed
|
429
|
+
switch (slashCmd) {
|
430
|
+
case '/send':
|
431
|
+
case '/clear':
|
432
|
+
case '/interrupt':
|
433
|
+
case '/close':
|
434
|
+
case '/return':
|
435
|
+
// These commands take a terminal as first arg
|
436
|
+
// Replace with current terminal
|
437
|
+
executeCommand(`${slashCmd} ${termData.name} ${args.slice(1).join(' ')}`.trim());
|
438
|
+
break;
|
439
|
+
default:
|
440
|
+
// Other commands execute as-is
|
441
|
+
executeCommand(command);
|
442
|
+
break;
|
443
|
+
}
|
444
|
+
}
|
373
445
|
} else {
|
374
|
-
//
|
375
|
-
|
446
|
+
// Regular command - send to terminals
|
447
|
+
for (const terminalId of terminalsToUse) {
|
448
|
+
const termData = terminals.get(terminalId);
|
449
|
+
if (!termData) continue;
|
450
|
+
|
451
|
+
if (usePlanMode) {
|
452
|
+
// Send Shift+Tab twice to enter plan mode
|
453
|
+
ipcRenderer.invoke('terminal:write', terminalId, '\x1b[Z\x1b[Z');
|
454
|
+
|
455
|
+
// Small delay to ensure plan mode is activated
|
456
|
+
setTimeout(() => {
|
457
|
+
ipcRenderer.invoke('terminal:write', terminalId, finalCommand + '\r');
|
458
|
+
|
459
|
+
// Auto-return after sending the message
|
460
|
+
setTimeout(() => {
|
461
|
+
executeReturnCommand([termData.name], true);
|
462
|
+
}, 150);
|
463
|
+
}, 300);
|
464
|
+
} else {
|
465
|
+
// Send the message directly to the terminal
|
466
|
+
ipcRenderer.invoke('terminal:write', terminalId, finalCommand + '\r');
|
467
|
+
|
468
|
+
// Auto-return after sending the message
|
469
|
+
setTimeout(() => {
|
470
|
+
executeReturnCommand([termData.name], true);
|
471
|
+
}, 150);
|
472
|
+
}
|
473
|
+
}
|
376
474
|
}
|
377
|
-
}
|
475
|
+
}
|
378
476
|
|
379
477
|
// Clear input
|
380
478
|
input.value = '';
|
@@ -536,7 +634,9 @@ function executeHelpCommand() {
|
|
536
634
|
`${cmd.syntax} - ${cmd.description}`
|
537
635
|
).join('\n');
|
538
636
|
|
539
|
-
|
637
|
+
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';
|
638
|
+
|
639
|
+
addChatMessage('System', 'Available commands:\n' + helpText + atHelp);
|
540
640
|
}
|
541
641
|
|
542
642
|
function executeCreateCommand(args) {
|
@@ -564,9 +664,9 @@ function executeCloseCommand(args) {
|
|
564
664
|
addChatMessage('System', `✅ Closed terminal ${result.terminal.name}`);
|
565
665
|
}
|
566
666
|
|
567
|
-
function executeReturnCommand(args) {
|
667
|
+
function executeReturnCommand(args, silent = false) {
|
568
668
|
if (args.length === 0) {
|
569
|
-
addChatMessage('System', 'Usage: /return <terminal>');
|
669
|
+
if (!silent) addChatMessage('System', 'Usage: /return <terminal>');
|
570
670
|
return;
|
571
671
|
}
|
572
672
|
|
@@ -574,14 +674,18 @@ function executeReturnCommand(args) {
|
|
574
674
|
const result = findTerminal(terminalIdentifier);
|
575
675
|
|
576
676
|
if (!result) {
|
577
|
-
|
578
|
-
|
677
|
+
if (!silent) {
|
678
|
+
const available = Array.from(terminals.values()).map(t => t.name).join(', ');
|
679
|
+
addChatMessage('System', `Terminal '${terminalIdentifier}' not found. Available: ${available}`);
|
680
|
+
}
|
579
681
|
return;
|
580
682
|
}
|
581
683
|
|
582
684
|
// Send carriage return to submit current input
|
583
685
|
ipcRenderer.invoke('terminal:write', result.id, '\r');
|
584
|
-
|
686
|
+
if (!silent) {
|
687
|
+
addChatMessage('System', `✅ Submitted input in ${result.terminal.name}`);
|
688
|
+
}
|
585
689
|
}
|
586
690
|
|
587
691
|
function executeClearCommand(args) {
|
@@ -604,16 +708,214 @@ function executeClearCommand(args) {
|
|
604
708
|
addChatMessage('System', `✅ Sent Ctrl+C to ${result.terminal.name}`);
|
605
709
|
}
|
606
710
|
|
711
|
+
function executeAtCommand(message) {
|
712
|
+
// Parse various @terminal command syntaxes:
|
713
|
+
// 1. Space-separated: "@term1 @term2 command"
|
714
|
+
// 2. Comma-separated: "@term1,term2,term3 command"
|
715
|
+
// 3. Single terminal: "@terminal command" or '@"terminal name" command'
|
716
|
+
// 4. All terminals: "@all command"
|
717
|
+
|
718
|
+
let targetTerminals = [];
|
719
|
+
let command = '';
|
720
|
+
|
721
|
+
// First, try to match space-separated terminals at the beginning
|
722
|
+
const spaceSeparatedRegex = /^(@(?:"[^"]+"|[^\s,]+)\s+)+(.+)$/;
|
723
|
+
const spaceSeparatedMatch = message.match(spaceSeparatedRegex);
|
724
|
+
|
725
|
+
if (spaceSeparatedMatch) {
|
726
|
+
// Extract all @terminal references
|
727
|
+
const terminalsStr = spaceSeparatedMatch[0].substring(0, spaceSeparatedMatch[0].length - spaceSeparatedMatch[2].length);
|
728
|
+
command = spaceSeparatedMatch[2].trim();
|
729
|
+
|
730
|
+
// Parse each @terminal reference
|
731
|
+
const terminalMatches = terminalsStr.matchAll(/@(?:"([^"]+)"|([^\s,]+))/g);
|
732
|
+
for (const match of terminalMatches) {
|
733
|
+
const terminalName = match[1] || match[2];
|
734
|
+
targetTerminals.push(terminalName.trim());
|
735
|
+
}
|
736
|
+
} else {
|
737
|
+
// Try single @terminal with possible comma-separated names
|
738
|
+
let match = message.match(/^@"([^"]+)"\s+(.+)$/); // Handle quoted names
|
739
|
+
|
740
|
+
if (!match) {
|
741
|
+
match = message.match(/^@(\S+)\s+(.+)$/); // Handle unquoted names (including comma-separated)
|
742
|
+
}
|
743
|
+
|
744
|
+
if (!match) {
|
745
|
+
addChatMessage('System', 'Usage: @terminal <command>\nExamples:\n @Tom ls -la\n @term1,term2 pwd\n @term1 @term2 date\n @all whoami');
|
746
|
+
return;
|
747
|
+
}
|
748
|
+
|
749
|
+
const terminalNameStr = match[1];
|
750
|
+
command = match[2];
|
751
|
+
|
752
|
+
// Check if it's @all
|
753
|
+
if (terminalNameStr.toLowerCase() === 'all') {
|
754
|
+
// Send to all terminals
|
755
|
+
const allTerminals = Array.from(terminals.entries());
|
756
|
+
if (allTerminals.length === 0) {
|
757
|
+
addChatMessage('System', 'No terminals available');
|
758
|
+
return;
|
759
|
+
}
|
760
|
+
|
761
|
+
const terminalNames = [];
|
762
|
+
for (const [id, termData] of allTerminals) {
|
763
|
+
ipcRenderer.invoke('terminal:write', id, command + '\r');
|
764
|
+
terminalNames.push(termData.name);
|
765
|
+
}
|
766
|
+
|
767
|
+
addChatMessage('System', `✅ Sent '${command}' to all ${allTerminals.length} terminals`);
|
768
|
+
|
769
|
+
// Auto-return to all terminals after a short delay
|
770
|
+
setTimeout(() => {
|
771
|
+
for (const name of terminalNames) {
|
772
|
+
executeReturnCommand([name], true);
|
773
|
+
}
|
774
|
+
}, 150);
|
775
|
+
return;
|
776
|
+
}
|
777
|
+
|
778
|
+
// Handle comma-separated terminal names
|
779
|
+
targetTerminals = terminalNameStr.split(',').map(name => name.trim());
|
780
|
+
}
|
781
|
+
|
782
|
+
// Process all target terminals
|
783
|
+
const foundTerminals = [];
|
784
|
+
const notFoundTerminals = [];
|
785
|
+
|
786
|
+
for (const terminalName of targetTerminals) {
|
787
|
+
const result = findTerminal(terminalName);
|
788
|
+
if (result) {
|
789
|
+
foundTerminals.push({ name: terminalName, id: result.id, terminal: result.terminal });
|
790
|
+
} else {
|
791
|
+
notFoundTerminals.push(terminalName);
|
792
|
+
}
|
793
|
+
}
|
794
|
+
|
795
|
+
// Report not found terminals
|
796
|
+
if (notFoundTerminals.length > 0) {
|
797
|
+
const available = Array.from(terminals.values()).map(t => t.name).join(', ');
|
798
|
+
addChatMessage('System', `Terminal(s) not found: ${notFoundTerminals.join(', ')}. Available: ${available}`);
|
799
|
+
}
|
800
|
+
|
801
|
+
// Send command to all found terminals
|
802
|
+
if (foundTerminals.length > 0) {
|
803
|
+
const sentToNames = [];
|
804
|
+
for (const { id, terminal } of foundTerminals) {
|
805
|
+
ipcRenderer.invoke('terminal:write', id, command + '\r');
|
806
|
+
sentToNames.push(terminal.name);
|
807
|
+
}
|
808
|
+
addChatMessage('System', `✅ Sent '${command}' to ${foundTerminals.length} terminal(s): ${sentToNames.join(', ')}`);
|
809
|
+
|
810
|
+
// Auto-return to all terminals after a short delay
|
811
|
+
setTimeout(() => {
|
812
|
+
for (const name of sentToNames) {
|
813
|
+
executeReturnCommand([name], true); // true = silent mode
|
814
|
+
}
|
815
|
+
}, 150);
|
816
|
+
}
|
817
|
+
}
|
818
|
+
|
607
819
|
// Dropdown Functions
|
608
|
-
function showCommandDropdown() {
|
820
|
+
function showCommandDropdown(cursorPos = null) {
|
609
821
|
const dropdown = document.getElementById('command-dropdown');
|
610
822
|
const input = document.getElementById('chat-input');
|
611
823
|
const inputValue = input.value;
|
824
|
+
const pos = cursorPos !== null ? cursorPos : input.selectionStart;
|
825
|
+
|
826
|
+
// Find the / symbol and extract the partial command name
|
827
|
+
let slashPos = -1;
|
828
|
+
let searchTerm = '';
|
829
|
+
|
830
|
+
// Search backwards from cursor to find the / symbol
|
831
|
+
for (let i = pos - 1; i >= 0; i--) {
|
832
|
+
if (inputValue[i] === '/') {
|
833
|
+
slashPos = i;
|
834
|
+
// Extract text from / to cursor position
|
835
|
+
searchTerm = inputValue.substring(i + 1, pos).toLowerCase();
|
836
|
+
break;
|
837
|
+
} else if (inputValue[i] === ' ' || inputValue[i] === '@') {
|
838
|
+
// Stop if we hit a space or @ before finding /
|
839
|
+
break;
|
840
|
+
}
|
841
|
+
}
|
612
842
|
|
613
|
-
//
|
614
|
-
|
843
|
+
// If no / found or / is not at a valid position, hide dropdown
|
844
|
+
if (slashPos === -1) {
|
845
|
+
hideCommandDropdown();
|
846
|
+
return;
|
847
|
+
}
|
848
|
+
|
849
|
+
// Filter commands based on partial input
|
615
850
|
currentCommands = availableCommands.filter(cmd =>
|
616
851
|
cmd.name.substring(1).toLowerCase().startsWith(searchTerm)
|
852
|
+
).map(cmd => ({
|
853
|
+
...cmd,
|
854
|
+
isSlashCommand: true,
|
855
|
+
slashPosition: slashPos
|
856
|
+
}));
|
857
|
+
|
858
|
+
if (currentCommands.length === 0) {
|
859
|
+
hideCommandDropdown();
|
860
|
+
return;
|
861
|
+
}
|
862
|
+
|
863
|
+
populateCommandDropdown();
|
864
|
+
dropdown.style.display = 'block';
|
865
|
+
selectedCommandIndex = -1;
|
866
|
+
}
|
867
|
+
|
868
|
+
function showTerminalDropdown(cursorPos = null) {
|
869
|
+
const dropdown = document.getElementById('command-dropdown');
|
870
|
+
const input = document.getElementById('chat-input');
|
871
|
+
const inputValue = input.value;
|
872
|
+
const pos = cursorPos !== null ? cursorPos : input.selectionStart;
|
873
|
+
|
874
|
+
// Find the @ symbol and extract the partial terminal name
|
875
|
+
let atPos = -1;
|
876
|
+
let searchTerm = '';
|
877
|
+
|
878
|
+
// Search backwards from cursor to find the @ symbol
|
879
|
+
for (let i = pos - 1; i >= 0; i--) {
|
880
|
+
if (inputValue[i] === '@') {
|
881
|
+
atPos = i;
|
882
|
+
// Extract text from @ to cursor position
|
883
|
+
searchTerm = inputValue.substring(i + 1, pos).toLowerCase();
|
884
|
+
break;
|
885
|
+
} else if (inputValue[i] === ' ' || inputValue[i] === ',') {
|
886
|
+
// Stop if we hit a space or comma before finding @
|
887
|
+
break;
|
888
|
+
}
|
889
|
+
}
|
890
|
+
|
891
|
+
// If no @ found or @ is not at a valid position, hide dropdown
|
892
|
+
if (atPos === -1) {
|
893
|
+
hideCommandDropdown();
|
894
|
+
return;
|
895
|
+
}
|
896
|
+
|
897
|
+
const availableTerminals = Array.from(terminals.entries()).map(([id, termData]) => ({
|
898
|
+
name: '@' + termData.name,
|
899
|
+
syntax: '@' + termData.name + ' <command>',
|
900
|
+
description: `Send command to ${termData.name}`,
|
901
|
+
id: id,
|
902
|
+
isTerminal: true,
|
903
|
+
terminalName: termData.name,
|
904
|
+
atPosition: atPos
|
905
|
+
}));
|
906
|
+
|
907
|
+
// Add @all option
|
908
|
+
availableTerminals.unshift({
|
909
|
+
name: '@all',
|
910
|
+
syntax: '@all <command>',
|
911
|
+
description: 'Send command to all terminals',
|
912
|
+
isTerminal: true,
|
913
|
+
terminalName: 'all',
|
914
|
+
atPosition: atPos
|
915
|
+
});
|
916
|
+
|
917
|
+
currentCommands = availableTerminals.filter(term =>
|
918
|
+
term.terminalName.toLowerCase().startsWith(searchTerm)
|
617
919
|
);
|
618
920
|
|
619
921
|
if (currentCommands.length === 0) {
|
@@ -671,15 +973,53 @@ function selectCommand(index) {
|
|
671
973
|
|
672
974
|
const command = currentCommands[index];
|
673
975
|
const input = document.getElementById('chat-input');
|
976
|
+
const value = input.value;
|
674
977
|
|
675
|
-
//
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
978
|
+
// Check if this is a terminal selection with position info
|
979
|
+
if (command.isTerminal && command.atPosition !== undefined) {
|
980
|
+
const atPos = command.atPosition;
|
981
|
+
|
982
|
+
// Find the end of the partial terminal name
|
983
|
+
let endPos = input.selectionStart;
|
984
|
+
|
985
|
+
// Replace the partial terminal name with the selected one
|
986
|
+
const beforeAt = value.substring(0, atPos);
|
987
|
+
const afterCursor = value.substring(endPos);
|
988
|
+
|
989
|
+
// Insert the selected terminal name (without the @, as it's already there)
|
990
|
+
input.value = beforeAt + '@' + command.terminalName + ' ' + afterCursor;
|
991
|
+
|
992
|
+
// Position cursor after the inserted terminal name and space
|
993
|
+
const newCursorPos = atPos + command.terminalName.length + 2; // +2 for @ and space
|
994
|
+
input.setSelectionRange(newCursorPos, newCursorPos);
|
995
|
+
} else if (command.isSlashCommand && command.slashPosition !== undefined) {
|
996
|
+
// Slash command at specific position
|
997
|
+
const slashPos = command.slashPosition;
|
998
|
+
|
999
|
+
// Find the end of the partial command name
|
1000
|
+
let endPos = input.selectionStart;
|
1001
|
+
|
1002
|
+
// Replace the partial command with the selected one
|
1003
|
+
const beforeSlash = value.substring(0, slashPos);
|
1004
|
+
const afterCursor = value.substring(endPos);
|
1005
|
+
|
1006
|
+
// Insert the selected command name
|
1007
|
+
input.value = beforeSlash + command.name + ' ' + afterCursor;
|
1008
|
+
|
1009
|
+
// Position cursor after the inserted command name and space
|
1010
|
+
const newCursorPos = slashPos + command.name.length + 1; // +1 for space
|
1011
|
+
input.setSelectionRange(newCursorPos, newCursorPos);
|
1012
|
+
} else {
|
1013
|
+
// Original behavior (shouldn't reach here with new system)
|
1014
|
+
input.value = command.name + ' ';
|
1015
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
1016
|
+
}
|
681
1017
|
|
1018
|
+
input.focus();
|
682
1019
|
hideCommandDropdown();
|
1020
|
+
|
1021
|
+
// Trigger input event to update highlighting
|
1022
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
683
1023
|
}
|
684
1024
|
|
685
1025
|
function navigateDropdown(direction) {
|
@@ -705,16 +1045,96 @@ function selectCurrentCommand() {
|
|
705
1045
|
}
|
706
1046
|
}
|
707
1047
|
|
1048
|
+
// Update overlay with syntax highlighting
|
1049
|
+
function updateOverlayHighlighting() {
|
1050
|
+
const input = document.getElementById('chat-input');
|
1051
|
+
const overlay = document.getElementById('input-overlay');
|
1052
|
+
const value = input.value;
|
1053
|
+
|
1054
|
+
// Parse and highlight text
|
1055
|
+
let highlightedText = escapeHtml(value);
|
1056
|
+
|
1057
|
+
// Highlight @terminal commands
|
1058
|
+
highlightedText = highlightedText.replace(
|
1059
|
+
/@"([^"]+)"/g,
|
1060
|
+
(match, terminalName) => {
|
1061
|
+
const result = findTerminal(terminalName);
|
1062
|
+
return result ? `@<span class="highlight-terminal">"${terminalName}"</span>` : match;
|
1063
|
+
}
|
1064
|
+
);
|
1065
|
+
|
1066
|
+
highlightedText = highlightedText.replace(
|
1067
|
+
/@(\S+)/g,
|
1068
|
+
(match, terminalName) => {
|
1069
|
+
// Handle @all specially
|
1070
|
+
if (terminalName.toLowerCase() === 'all') {
|
1071
|
+
return `@<span class="highlight-terminal">${terminalName}</span>`;
|
1072
|
+
}
|
1073
|
+
// Handle comma-separated terminals
|
1074
|
+
if (terminalName.includes(',')) {
|
1075
|
+
const parts = terminalName.split(',');
|
1076
|
+
const highlightedParts = parts.map((part, index) => {
|
1077
|
+
const trimmedPart = part.trim();
|
1078
|
+
const result = findTerminal(trimmedPart);
|
1079
|
+
return (index > 0 ? ',' : '') + (result ? `<span class="highlight-terminal">${trimmedPart}</span>` : trimmedPart);
|
1080
|
+
}).join('');
|
1081
|
+
return '@' + highlightedParts;
|
1082
|
+
}
|
1083
|
+
// Single terminal
|
1084
|
+
const result = findTerminal(terminalName);
|
1085
|
+
return result ? `@<span class="highlight-terminal">${terminalName}</span>` : match;
|
1086
|
+
}
|
1087
|
+
);
|
1088
|
+
|
1089
|
+
// Highlight slash commands (anywhere in the text)
|
1090
|
+
highlightedText = highlightedText.replace(
|
1091
|
+
/(\/\w+)/g,
|
1092
|
+
(match, command, offset) => {
|
1093
|
+
// Check if this is a valid position for a command (start of string or after space/@ )
|
1094
|
+
const isValidPosition = offset === 0 ||
|
1095
|
+
highlightedText[offset - 1] === ' ' ||
|
1096
|
+
highlightedText[offset - 1] === '@';
|
1097
|
+
|
1098
|
+
if (isValidPosition) {
|
1099
|
+
const validCommand = availableCommands.find(cmd => cmd.name === command);
|
1100
|
+
return validCommand ? `<span class="highlight-command">${command}</span>` : match;
|
1101
|
+
}
|
1102
|
+
return match;
|
1103
|
+
}
|
1104
|
+
);
|
1105
|
+
|
1106
|
+
overlay.innerHTML = highlightedText;
|
1107
|
+
|
1108
|
+
// Sync scroll position
|
1109
|
+
overlay.scrollTop = input.scrollTop;
|
1110
|
+
overlay.scrollLeft = input.scrollLeft;
|
1111
|
+
}
|
1112
|
+
|
1113
|
+
function escapeHtml(text) {
|
1114
|
+
const div = document.createElement('div');
|
1115
|
+
div.textContent = text;
|
1116
|
+
return div.innerHTML;
|
1117
|
+
}
|
1118
|
+
|
708
1119
|
// Initialize collapsible default prompt section
|
709
1120
|
function initCollapsible() {
|
710
1121
|
const toggle = document.querySelector('.collapse-toggle');
|
711
1122
|
const content = document.querySelector('.collapsible-content');
|
1123
|
+
const header = document.querySelector('.collapsible-header');
|
712
1124
|
|
713
|
-
|
1125
|
+
const toggleSection = (e) => {
|
1126
|
+
// Prevent double-triggering when clicking the toggle button
|
1127
|
+
if (e && e.target === toggle) {
|
1128
|
+
e.stopPropagation();
|
1129
|
+
}
|
1130
|
+
|
714
1131
|
const isExpanded = content.style.display !== 'none';
|
715
1132
|
content.style.display = isExpanded ? 'none' : 'block';
|
716
1133
|
toggle.textContent = isExpanded ? '▶' : '▼';
|
717
|
-
}
|
1134
|
+
};
|
1135
|
+
|
1136
|
+
// Make the entire header clickable
|
1137
|
+
header.addEventListener('click', toggleSection);
|
718
1138
|
}
|
719
1139
|
|
720
1140
|
|
@@ -753,6 +1173,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
753
1173
|
e.preventDefault();
|
754
1174
|
if (selectedCommandIndex >= 0) {
|
755
1175
|
selectCurrentCommand();
|
1176
|
+
} else if (currentCommands.length > 0) {
|
1177
|
+
// Auto-select first option if none selected
|
1178
|
+
selectCommand(0);
|
756
1179
|
}
|
757
1180
|
} else if (e.key === 'ArrowUp' && !isDropdownVisible && commandHistory.length > 0) {
|
758
1181
|
e.preventDefault();
|
@@ -775,21 +1198,51 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
775
1198
|
}
|
776
1199
|
});
|
777
1200
|
|
778
|
-
// Chat input - Show dropdown on '/' and filter as user types
|
1201
|
+
// Chat input - Show dropdown on '/' and '@' and filter as user types
|
779
1202
|
document.getElementById('chat-input').addEventListener('input', (e) => {
|
780
1203
|
const input = e.target;
|
781
1204
|
const value = input.value;
|
782
1205
|
const cursorPos = input.selectionStart;
|
783
1206
|
|
784
|
-
//
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
1207
|
+
// Update overlay highlighting
|
1208
|
+
updateOverlayHighlighting();
|
1209
|
+
|
1210
|
+
// Check for both @ and / at cursor position
|
1211
|
+
if (cursorPos > 0) {
|
1212
|
+
// Look for @ at or before cursor position
|
1213
|
+
let foundAt = false;
|
1214
|
+
let foundSlash = false;
|
1215
|
+
|
1216
|
+
for (let i = cursorPos - 1; i >= 0; i--) {
|
1217
|
+
if (value[i] === '@') {
|
1218
|
+
foundAt = true;
|
1219
|
+
break;
|
1220
|
+
} else if (value[i] === '/') {
|
1221
|
+
foundSlash = true;
|
1222
|
+
break;
|
1223
|
+
} else if (value[i] === ' ' || value[i] === ',') {
|
1224
|
+
// If we hit a space or comma, check if it's immediately followed by @ or /
|
1225
|
+
if (i === cursorPos - 1) {
|
1226
|
+
if (value[cursorPos - 1] === '@') {
|
1227
|
+
foundAt = true;
|
1228
|
+
} else if (value[cursorPos - 1] === '/') {
|
1229
|
+
foundSlash = true;
|
1230
|
+
}
|
1231
|
+
}
|
1232
|
+
break;
|
1233
|
+
}
|
1234
|
+
}
|
1235
|
+
|
1236
|
+
if (foundAt) {
|
1237
|
+
showTerminalDropdown(cursorPos);
|
1238
|
+
return;
|
1239
|
+
} else if (foundSlash) {
|
1240
|
+
showCommandDropdown(cursorPos);
|
1241
|
+
return;
|
1242
|
+
}
|
792
1243
|
}
|
1244
|
+
|
1245
|
+
hideCommandDropdown();
|
793
1246
|
});
|
794
1247
|
|
795
1248
|
// Hide dropdown when clicking outside
|
@@ -802,6 +1255,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
802
1255
|
}
|
803
1256
|
});
|
804
1257
|
|
1258
|
+
// Sync overlay scroll with input
|
1259
|
+
document.getElementById('chat-input').addEventListener('scroll', () => {
|
1260
|
+
const input = document.getElementById('chat-input');
|
1261
|
+
const overlay = document.getElementById('input-overlay');
|
1262
|
+
overlay.scrollTop = input.scrollTop;
|
1263
|
+
overlay.scrollLeft = input.scrollLeft;
|
1264
|
+
});
|
1265
|
+
|
805
1266
|
// Initialize collapsible
|
806
1267
|
initCollapsible();
|
807
1268
|
|
package/style.css
CHANGED
@@ -176,59 +176,6 @@ body {
|
|
176
176
|
position: relative;
|
177
177
|
}
|
178
178
|
|
179
|
-
/* Terminal Selection */
|
180
|
-
#terminal-selector > label {
|
181
|
-
display: block;
|
182
|
-
font-size: 13px;
|
183
|
-
color: #cccccc;
|
184
|
-
margin-bottom: 8px;
|
185
|
-
font-weight: 500;
|
186
|
-
}
|
187
|
-
|
188
|
-
#terminal-checkboxes {
|
189
|
-
display: flex;
|
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);
|
212
|
-
}
|
213
|
-
|
214
|
-
.terminal-checkbox-label {
|
215
|
-
display: flex;
|
216
|
-
align-items: center;
|
217
|
-
gap: 8px;
|
218
|
-
font-size: 14px;
|
219
|
-
color: #cccccc;
|
220
|
-
cursor: pointer;
|
221
|
-
}
|
222
|
-
|
223
|
-
.terminal-checkbox-label:hover {
|
224
|
-
color: #ffffff;
|
225
|
-
}
|
226
|
-
|
227
|
-
.terminal-selector-checkbox {
|
228
|
-
width: 16px;
|
229
|
-
height: 16px;
|
230
|
-
cursor: pointer;
|
231
|
-
}
|
232
179
|
|
233
180
|
/* Default Prompt Section */
|
234
181
|
#default-prompt-section {
|
@@ -312,15 +259,39 @@ body {
|
|
312
259
|
|
313
260
|
/* Removed - Mode selector is now integrated into input area */
|
314
261
|
|
262
|
+
/* Input Overlay for Syntax Highlighting */
|
263
|
+
.input-overlay {
|
264
|
+
position: absolute;
|
265
|
+
top: 0;
|
266
|
+
left: 0;
|
267
|
+
right: 0;
|
268
|
+
min-height: 80px;
|
269
|
+
max-height: 200px;
|
270
|
+
padding: 10px 12px;
|
271
|
+
border-radius: 4px;
|
272
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
273
|
+
font-size: 14px;
|
274
|
+
line-height: 1.5;
|
275
|
+
color: transparent;
|
276
|
+
background: transparent;
|
277
|
+
pointer-events: none;
|
278
|
+
z-index: 1;
|
279
|
+
white-space: pre-wrap;
|
280
|
+
word-wrap: break-word;
|
281
|
+
overflow: hidden;
|
282
|
+
}
|
283
|
+
|
315
284
|
/* Chat Input - Full Width */
|
316
285
|
#chat-input {
|
286
|
+
position: relative;
|
287
|
+
z-index: 2;
|
317
288
|
width: 100%;
|
318
289
|
min-height: 80px;
|
319
290
|
max-height: 200px;
|
320
291
|
background-color: #3c3c3c;
|
321
292
|
border: 1px solid #555;
|
322
293
|
color: white;
|
323
|
-
padding: 10px 12px
|
294
|
+
padding: 10px 12px;
|
324
295
|
border-radius: 4px;
|
325
296
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
326
297
|
font-size: 14px;
|
@@ -334,26 +305,38 @@ body {
|
|
334
305
|
box-shadow: 0 0 0 2px rgba(14, 99, 156, 0.2);
|
335
306
|
}
|
336
307
|
|
308
|
+
/* Syntax highlighting styles */
|
309
|
+
.highlight-terminal {
|
310
|
+
color: #4CAF50;
|
311
|
+
}
|
312
|
+
|
313
|
+
.highlight-command {
|
314
|
+
color: #FF9800;
|
315
|
+
}
|
316
|
+
|
317
|
+
/* Plan Mode Row */
|
318
|
+
.plan-mode-row {
|
319
|
+
margin-bottom: 8px;
|
320
|
+
}
|
321
|
+
|
337
322
|
/* Plan Mode Option */
|
338
323
|
.plan-mode-option {
|
339
|
-
position: absolute;
|
340
|
-
bottom: 10px;
|
341
|
-
left: 12px;
|
342
324
|
display: flex;
|
343
325
|
align-items: center;
|
344
|
-
gap:
|
345
|
-
font-size:
|
346
|
-
color: #
|
326
|
+
gap: 8px;
|
327
|
+
font-size: 14px;
|
328
|
+
color: #cccccc;
|
347
329
|
cursor: pointer;
|
330
|
+
padding: 4px 0;
|
348
331
|
}
|
349
332
|
|
350
333
|
.plan-mode-option:hover {
|
351
|
-
color: #
|
334
|
+
color: #ffffff;
|
352
335
|
}
|
353
336
|
|
354
337
|
.plan-mode-option input[type="checkbox"] {
|
355
|
-
width:
|
356
|
-
height:
|
338
|
+
width: 16px;
|
339
|
+
height: 16px;
|
357
340
|
cursor: pointer;
|
358
341
|
}
|
359
342
|
|