@adiontaegerron/claude-multi-terminal 1.2.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 +22 -7
- package/index.html +0 -7
- package/package.json +1 -1
- package/renderer.js +255 -120
- package/style.css +0 -53
package/README.md
CHANGED
@@ -7,20 +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
|
12
|
-
- **Multi-@ Terminal Commands**: Send commands to multiple terminals with various syntaxes:
|
10
|
+
- **Unified Chat Interface**: Send commands to terminals using @ syntax
|
11
|
+
- **Multi-@ Terminal Commands**: Target terminals with flexible syntax:
|
13
12
|
- `@all command` - Send to all terminals
|
14
13
|
- `@term1,term2,term3 command` - Comma-separated terminals
|
15
14
|
- `@term1 @term2 command` - Space-separated terminals
|
16
|
-
- **
|
17
|
-
-
|
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)
|
18
21
|
- **Default Prompt**: Set a default prompt to prepend to all messages
|
19
22
|
- **Plan Mode Support**: Automatically enters plan mode with Shift+Tab
|
20
23
|
- **Auto-Start Claude**: Each terminal automatically starts Claude Code
|
21
24
|
- **Rename Terminals**: Double-click terminal titles to rename them
|
22
25
|
- **Responsive Layout**: Adapts to different screen sizes
|
23
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
|
+
|
24
37
|
## Installation
|
25
38
|
|
26
39
|
### From npm
|
@@ -52,10 +65,12 @@ This will open the multi-terminal editor with all terminals starting in your cur
|
|
52
65
|
|
53
66
|
1. **Start the Application**: Run `claude-multi` in your project directory
|
54
67
|
2. **Create Terminals**: Click "New Terminal" to add more Claude instances (starts with one)
|
55
|
-
3. **
|
56
|
-
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
|
57
70
|
5. **Use Default Prompt**: Expand the "Default Prompt" section to set a prompt that's prepended to all messages
|
58
71
|
|
72
|
+
**Important**: Version 2.0 removes checkbox selection. All commands must now use @ syntax to target terminals.
|
73
|
+
|
59
74
|
### Slash Commands
|
60
75
|
|
61
76
|
- `/send <terminal> <command>` - Send command to specific terminal
|
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>
|
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,33 +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
|
-
}
|
362
|
+
// Parse the complex command
|
363
|
+
const parsedCommands = parseComplexCommand(message);
|
339
364
|
|
340
|
-
|
341
|
-
|
342
|
-
executeAtCommand(message);
|
343
|
-
input.value = '';
|
344
|
-
return;
|
345
|
-
}
|
346
|
-
|
347
|
-
// Check if default prompt should be prepended
|
348
|
-
let fullMessage = message;
|
349
|
-
const useDefaultPrompt = document.getElementById('use-default-prompt').checked;
|
350
|
-
const defaultPrompt = document.getElementById('default-prompt-text').value.trim();
|
351
|
-
|
352
|
-
if (useDefaultPrompt && defaultPrompt) {
|
353
|
-
fullMessage = defaultPrompt + ' ' + message;
|
354
|
-
}
|
355
|
-
|
356
|
-
// Get selected terminals
|
357
|
-
const selectedTerminals = getSelectedTerminals();
|
358
|
-
if (selectedTerminals.length === 0) {
|
359
|
-
alert('Please select at least one terminal');
|
365
|
+
if (parsedCommands.length === 0) {
|
366
|
+
addChatMessage('System', 'Invalid command format. Use @terminal command or /command syntax.');
|
360
367
|
return;
|
361
368
|
}
|
362
369
|
|
@@ -365,37 +372,107 @@ function sendMessage() {
|
|
365
372
|
|
366
373
|
// Check if plan mode is enabled
|
367
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();
|
368
377
|
|
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
|
+
}
|
373
385
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
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);
|
383
427
|
|
384
|
-
//
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|
+
}
|
389
445
|
} else {
|
390
|
-
//
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
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
|
+
}
|
397
474
|
}
|
398
|
-
}
|
475
|
+
}
|
399
476
|
|
400
477
|
// Clear input
|
401
478
|
input.value = '';
|
@@ -740,16 +817,43 @@ function executeAtCommand(message) {
|
|
740
817
|
}
|
741
818
|
|
742
819
|
// Dropdown Functions
|
743
|
-
function showCommandDropdown() {
|
820
|
+
function showCommandDropdown(cursorPos = null) {
|
744
821
|
const dropdown = document.getElementById('command-dropdown');
|
745
822
|
const input = document.getElementById('chat-input');
|
746
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
|
+
}
|
747
842
|
|
748
|
-
//
|
749
|
-
|
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
|
750
850
|
currentCommands = availableCommands.filter(cmd =>
|
751
851
|
cmd.name.substring(1).toLowerCase().startsWith(searchTerm)
|
752
|
-
)
|
852
|
+
).map(cmd => ({
|
853
|
+
...cmd,
|
854
|
+
isSlashCommand: true,
|
855
|
+
slashPosition: slashPos
|
856
|
+
}));
|
753
857
|
|
754
858
|
if (currentCommands.length === 0) {
|
755
859
|
hideCommandDropdown();
|
@@ -869,10 +973,10 @@ function selectCommand(index) {
|
|
869
973
|
|
870
974
|
const command = currentCommands[index];
|
871
975
|
const input = document.getElementById('chat-input');
|
976
|
+
const value = input.value;
|
872
977
|
|
873
978
|
// Check if this is a terminal selection with position info
|
874
979
|
if (command.isTerminal && command.atPosition !== undefined) {
|
875
|
-
const value = input.value;
|
876
980
|
const atPos = command.atPosition;
|
877
981
|
|
878
982
|
// Find the end of the partial terminal name
|
@@ -888,8 +992,25 @@ function selectCommand(index) {
|
|
888
992
|
// Position cursor after the inserted terminal name and space
|
889
993
|
const newCursorPos = atPos + command.terminalName.length + 2; // +2 for @ and space
|
890
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);
|
891
1012
|
} else {
|
892
|
-
// Original behavior
|
1013
|
+
// Original behavior (shouldn't reach here with new system)
|
893
1014
|
input.value = command.name + ' ';
|
894
1015
|
input.setSelectionRange(input.value.length, input.value.length);
|
895
1016
|
}
|
@@ -965,12 +1086,20 @@ function updateOverlayHighlighting() {
|
|
965
1086
|
}
|
966
1087
|
);
|
967
1088
|
|
968
|
-
// Highlight slash commands
|
1089
|
+
// Highlight slash commands (anywhere in the text)
|
969
1090
|
highlightedText = highlightedText.replace(
|
970
|
-
|
971
|
-
(match, command) => {
|
972
|
-
|
973
|
-
|
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;
|
974
1103
|
}
|
975
1104
|
);
|
976
1105
|
|
@@ -1078,24 +1207,27 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
1078
1207
|
// Update overlay highlighting
|
1079
1208
|
updateOverlayHighlighting();
|
1080
1209
|
|
1081
|
-
// Check for
|
1082
|
-
if (cursorPos === value.length && value.startsWith('/') && value.length >= 1) {
|
1083
|
-
showCommandDropdown();
|
1084
|
-
return;
|
1085
|
-
}
|
1086
|
-
|
1087
|
-
// Check for @ at cursor position (can be anywhere in the input)
|
1210
|
+
// Check for both @ and / at cursor position
|
1088
1211
|
if (cursorPos > 0) {
|
1089
1212
|
// Look for @ at or before cursor position
|
1090
1213
|
let foundAt = false;
|
1214
|
+
let foundSlash = false;
|
1215
|
+
|
1091
1216
|
for (let i = cursorPos - 1; i >= 0; i--) {
|
1092
1217
|
if (value[i] === '@') {
|
1093
1218
|
foundAt = true;
|
1094
1219
|
break;
|
1220
|
+
} else if (value[i] === '/') {
|
1221
|
+
foundSlash = true;
|
1222
|
+
break;
|
1095
1223
|
} 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
|
1098
|
-
|
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
|
+
}
|
1099
1231
|
}
|
1100
1232
|
break;
|
1101
1233
|
}
|
@@ -1104,6 +1236,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
1104
1236
|
if (foundAt) {
|
1105
1237
|
showTerminalDropdown(cursorPos);
|
1106
1238
|
return;
|
1239
|
+
} else if (foundSlash) {
|
1240
|
+
showCommandDropdown(cursorPos);
|
1241
|
+
return;
|
1107
1242
|
}
|
1108
1243
|
}
|
1109
1244
|
|
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 {
|