startall 0.0.13 → 0.0.14
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 +23 -7
- package/index.js +372 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,9 +56,13 @@ Traditional solutions fall short:
|
|
|
56
56
|
- **Quick process toggle**: Use number keys `1-9` for instant visibility control
|
|
57
57
|
|
|
58
58
|
### 🔧 Advanced Controls
|
|
59
|
+
- **Quick Commands**: Assign keyboard shortcuts to run scripts on-demand
|
|
60
|
+
- Press assigned key to run command in a popup overlay
|
|
61
|
+
- Perfect for build scripts, tests, or any short-running command
|
|
62
|
+
- Configure in settings (`o` → Quick Commands section)
|
|
59
63
|
- **Interactive input mode**: Send commands to running processes via stdin (`i`)
|
|
60
64
|
- Perfect for dev servers that accept commands (Vite, Rust watch, etc.)
|
|
61
|
-
- **Settings panel**: Configure ignore/include patterns (`o`)
|
|
65
|
+
- **Settings panel**: Configure ignore/include patterns, shortcuts, and more (`o`)
|
|
62
66
|
- Wildcard support (`*`) for pattern matching
|
|
63
67
|
- Per-script visibility toggles
|
|
64
68
|
- **Keyboard & mouse support**: Full keyboard navigation + mouse clicking/scrolling
|
|
@@ -103,6 +107,7 @@ That's it! The TUI will:
|
|
|
103
107
|
- `s` - Stop/start selected process
|
|
104
108
|
- `r` - Restart selected process
|
|
105
109
|
- `i` - Send input to selected process (interactive mode)
|
|
110
|
+
- `a-z` - Run assigned quick command (if configured)
|
|
106
111
|
|
|
107
112
|
*Pane Management:*
|
|
108
113
|
- `\` - Open command palette
|
|
@@ -137,13 +142,17 @@ That's it! The TUI will:
|
|
|
137
142
|
- `Ctrl+C` - Force quit
|
|
138
143
|
|
|
139
144
|
**Settings Screen:**
|
|
140
|
-
- `Tab`/`←`/`→` - Switch sections (Ignore/Include/
|
|
145
|
+
- `Tab`/`←`/`→` - Switch sections (Display/Ignore/Include/Quick Commands/Script List)
|
|
141
146
|
- `↑`/`↓` - Navigate items
|
|
142
|
-
- `
|
|
143
|
-
- `
|
|
144
|
-
- `Space` or `Enter` - Toggle
|
|
147
|
+
- `i` - Add new ignore pattern
|
|
148
|
+
- `n` - Add new include pattern
|
|
149
|
+
- `Space` or `Enter` - Toggle option (Display) / Assign shortcut (Quick Commands) / Toggle ignore (Script List)
|
|
150
|
+
- `d` or `Backspace` - Delete pattern or shortcut
|
|
145
151
|
- `Esc` or `q` - Return to previous screen
|
|
146
152
|
|
|
153
|
+
**Quick Commands Overlay:**
|
|
154
|
+
- `Esc` - Close overlay and stop command (if running)
|
|
155
|
+
|
|
147
156
|
## Why Build This?
|
|
148
157
|
|
|
149
158
|
Existing tools either:
|
|
@@ -164,12 +173,19 @@ Existing tools either:
|
|
|
164
173
|
{
|
|
165
174
|
"defaultSelection": ["frontend", "backend"],
|
|
166
175
|
"include": ["dev:*"],
|
|
167
|
-
"ignore": ["*:test"]
|
|
176
|
+
"ignore": ["*:test"],
|
|
177
|
+
"shortcuts": {
|
|
178
|
+
"b": "build",
|
|
179
|
+
"t": "test",
|
|
180
|
+
"l": "lint"
|
|
181
|
+
}
|
|
168
182
|
}
|
|
169
183
|
```
|
|
184
|
+
- `defaultSelection`: scripts to auto-select on startup
|
|
170
185
|
- `include` (optional): if defined, only scripts matching these patterns are shown
|
|
171
186
|
- `ignore`: scripts matching these patterns are hidden
|
|
172
|
-
-
|
|
187
|
+
- `shortcuts`: keyboard shortcuts for running commands on-demand
|
|
188
|
+
- All patterns support wildcards (`*`)
|
|
173
189
|
|
|
174
190
|
## Roadmap
|
|
175
191
|
|
package/index.js
CHANGED
|
@@ -10,7 +10,18 @@ import stripAnsi from 'strip-ansi';
|
|
|
10
10
|
// Configuration
|
|
11
11
|
const CONFIG_FILE = process.argv[2] || 'startall.json';
|
|
12
12
|
const COUNTDOWN_SECONDS = 10;
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// Read version from package.json
|
|
15
|
+
function getAppVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const packagePath = new URL('./package.json', import.meta.url);
|
|
18
|
+
const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
19
|
+
return `v${pkg.version}`;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return 'v0.0.0'; // Fallback if package.json can't be read
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const APP_VERSION = getAppVersion();
|
|
14
25
|
|
|
15
26
|
// Detect if running inside VS Code's integrated terminal
|
|
16
27
|
const IS_VSCODE = process.env.TERM_PROGRAM === 'vscode';
|
|
@@ -307,10 +318,10 @@ function loadConfig() {
|
|
|
307
318
|
try {
|
|
308
319
|
return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
309
320
|
} catch {
|
|
310
|
-
return { defaultSelection: [], ignore: [] };
|
|
321
|
+
return { defaultSelection: [], ignore: [], shortcuts: {} };
|
|
311
322
|
}
|
|
312
323
|
}
|
|
313
|
-
return { defaultSelection: [], ignore: [] };
|
|
324
|
+
return { defaultSelection: [], ignore: [], shortcuts: {} };
|
|
314
325
|
}
|
|
315
326
|
|
|
316
327
|
// Save config
|
|
@@ -353,12 +364,21 @@ class ProcessManager {
|
|
|
353
364
|
this.inputModeText = ''; // Text being typed for stdin
|
|
354
365
|
|
|
355
366
|
// Settings menu state
|
|
356
|
-
this.settingsSection = 'display'; // 'display' | 'ignore' | 'include' | 'scripts'
|
|
367
|
+
this.settingsSection = 'display'; // 'display' | 'ignore' | 'include' | 'scripts' | 'shortcuts'
|
|
357
368
|
this.settingsIndex = 0; // Current selection index within section
|
|
358
369
|
this.isAddingPattern = false; // Whether typing a new pattern
|
|
359
370
|
this.newPatternText = ''; // Text being typed for new pattern
|
|
371
|
+
this.isAssigningShortcut = false; // Whether waiting for a key to assign as shortcut
|
|
372
|
+
this.shortcutScriptName = ''; // Script name being assigned a shortcut
|
|
360
373
|
this.settingsContainer = null; // UI reference
|
|
361
374
|
this.previousPhase = 'selection'; // Track where we came from
|
|
375
|
+
|
|
376
|
+
// Command execution overlay state
|
|
377
|
+
this.showCommandOverlay = false; // Whether command output overlay is visible
|
|
378
|
+
this.commandOverlayOutput = []; // Output lines from command
|
|
379
|
+
this.commandOverlayScript = ''; // Script name being executed
|
|
380
|
+
this.commandOverlayStatus = 'running'; // 'running' | 'exited' | 'crashed'
|
|
381
|
+
this.commandOverlayProcess = null; // Process reference
|
|
362
382
|
this.outputBox = null; // Reference to the output container
|
|
363
383
|
this.destroyed = false; // Flag to prevent operations after cleanup
|
|
364
384
|
this.lastRenderedLineCount = 0; // Track how many lines we've rendered
|
|
@@ -465,6 +485,15 @@ class ProcessManager {
|
|
|
465
485
|
this.handleSettingsInput(keyName, keyEvent);
|
|
466
486
|
return;
|
|
467
487
|
} else if (this.phase === 'running') {
|
|
488
|
+
// Handle command overlay
|
|
489
|
+
if (this.showCommandOverlay) {
|
|
490
|
+
if (keyName === 'escape') {
|
|
491
|
+
this.closeCommandOverlay();
|
|
492
|
+
this.buildRunningUI();
|
|
493
|
+
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
468
497
|
// Handle split menu
|
|
469
498
|
if (this.showSplitMenu) {
|
|
470
499
|
this.handleSplitMenuInput(keyName, keyEvent);
|
|
@@ -679,6 +708,17 @@ class ProcessManager {
|
|
|
679
708
|
this.inputModeText = '';
|
|
680
709
|
this.buildRunningUI();
|
|
681
710
|
}
|
|
711
|
+
} else if (keyName && keyName.length === 1 && !keyEvent.ctrl && !keyEvent.meta && !keyEvent.shift) {
|
|
712
|
+
// Check if this key is a custom shortcut
|
|
713
|
+
const shortcuts = this.config.shortcuts || {};
|
|
714
|
+
const scriptName = shortcuts[keyName];
|
|
715
|
+
if (scriptName) {
|
|
716
|
+
// Find the script in allScripts (since it might be ignored/filtered out)
|
|
717
|
+
const script = this.allScripts.find(s => s.name === scriptName);
|
|
718
|
+
if (script) {
|
|
719
|
+
this.executeCommand(scriptName);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
682
722
|
}
|
|
683
723
|
}
|
|
684
724
|
}
|
|
@@ -890,6 +930,40 @@ class ProcessManager {
|
|
|
890
930
|
}
|
|
891
931
|
|
|
892
932
|
handleSettingsInput(keyName, keyEvent) {
|
|
933
|
+
// Handle shortcut assignment mode
|
|
934
|
+
if (this.isAssigningShortcut) {
|
|
935
|
+
if (keyName === 'escape') {
|
|
936
|
+
this.isAssigningShortcut = false;
|
|
937
|
+
this.shortcutScriptName = '';
|
|
938
|
+
this.buildSettingsUI();
|
|
939
|
+
return;
|
|
940
|
+
} else if (keyName && keyName.length === 1 && !keyEvent.ctrl && !keyEvent.meta && !keyEvent.shift) {
|
|
941
|
+
// Assign this key to the script
|
|
942
|
+
if (!this.config.shortcuts) this.config.shortcuts = {};
|
|
943
|
+
|
|
944
|
+
// Remove any existing shortcut for this script (one key per script)
|
|
945
|
+
for (const [key, scriptName] of Object.entries(this.config.shortcuts)) {
|
|
946
|
+
if (scriptName === this.shortcutScriptName) {
|
|
947
|
+
delete this.config.shortcuts[key];
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Also remove this key if it's assigned to another script (one script per key)
|
|
952
|
+
if (this.config.shortcuts[keyName]) {
|
|
953
|
+
delete this.config.shortcuts[keyName];
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Assign the new shortcut
|
|
957
|
+
this.config.shortcuts[keyName] = this.shortcutScriptName;
|
|
958
|
+
saveConfig(this.config);
|
|
959
|
+
this.isAssigningShortcut = false;
|
|
960
|
+
this.shortcutScriptName = '';
|
|
961
|
+
this.buildSettingsUI();
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
893
967
|
// Handle text input mode for adding patterns
|
|
894
968
|
if (this.isAddingPattern) {
|
|
895
969
|
if (keyName === 'escape') {
|
|
@@ -944,21 +1018,21 @@ class ProcessManager {
|
|
|
944
1018
|
}
|
|
945
1019
|
} else if (keyName === 'tab' || keyName === 'right') {
|
|
946
1020
|
// Switch section
|
|
947
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1021
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
948
1022
|
const idx = sections.indexOf(this.settingsSection);
|
|
949
1023
|
this.settingsSection = sections[(idx + 1) % sections.length];
|
|
950
1024
|
this.settingsIndex = 0;
|
|
951
1025
|
this.buildSettingsUI();
|
|
952
1026
|
} else if (keyEvent.shift && keyName === 'tab') {
|
|
953
1027
|
// Switch section backwards
|
|
954
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1028
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
955
1029
|
const idx = sections.indexOf(this.settingsSection);
|
|
956
1030
|
this.settingsSection = sections[(idx - 1 + sections.length) % sections.length];
|
|
957
1031
|
this.settingsIndex = 0;
|
|
958
1032
|
this.buildSettingsUI();
|
|
959
1033
|
} else if (keyName === 'left') {
|
|
960
1034
|
// Switch section backwards
|
|
961
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1035
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
962
1036
|
const idx = sections.indexOf(this.settingsSection);
|
|
963
1037
|
this.settingsSection = sections[(idx - 1 + sections.length) % sections.length];
|
|
964
1038
|
this.settingsIndex = 0;
|
|
@@ -969,7 +1043,7 @@ class ProcessManager {
|
|
|
969
1043
|
this.buildSettingsUI();
|
|
970
1044
|
} else {
|
|
971
1045
|
// Move to previous section
|
|
972
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1046
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
973
1047
|
const idx = sections.indexOf(this.settingsSection);
|
|
974
1048
|
if (idx > 0) {
|
|
975
1049
|
this.settingsSection = sections[idx - 1];
|
|
@@ -984,7 +1058,7 @@ class ProcessManager {
|
|
|
984
1058
|
this.buildSettingsUI();
|
|
985
1059
|
} else {
|
|
986
1060
|
// Move to next section
|
|
987
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1061
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
988
1062
|
const idx = sections.indexOf(this.settingsSection);
|
|
989
1063
|
if (idx < sections.length - 1) {
|
|
990
1064
|
this.settingsSection = sections[idx + 1];
|
|
@@ -1005,14 +1079,16 @@ class ProcessManager {
|
|
|
1005
1079
|
this.newPatternText = '';
|
|
1006
1080
|
this.buildSettingsUI();
|
|
1007
1081
|
} else if (keyName === 'd' || keyName === 'backspace') {
|
|
1008
|
-
// Delete selected pattern
|
|
1082
|
+
// Delete selected pattern or shortcut
|
|
1009
1083
|
this.deleteSelectedItem();
|
|
1010
1084
|
this.buildSettingsUI();
|
|
1011
1085
|
} else if (keyName === 'space' || keyName === 'enter' || keyName === 'return') {
|
|
1012
|
-
// Toggle display options, script visibility
|
|
1086
|
+
// Toggle display options, script visibility, or assign shortcut
|
|
1013
1087
|
if (this.settingsSection === 'display') {
|
|
1014
1088
|
this.toggleDisplayOption();
|
|
1015
1089
|
this.buildSettingsUI();
|
|
1090
|
+
} else if (this.settingsSection === 'shortcuts') {
|
|
1091
|
+
this.assignShortcut();
|
|
1016
1092
|
} else if (this.settingsSection === 'scripts') {
|
|
1017
1093
|
this.toggleScriptIgnore();
|
|
1018
1094
|
this.buildSettingsUI();
|
|
@@ -1029,6 +1105,8 @@ class ProcessManager {
|
|
|
1029
1105
|
} else if (this.settingsSection === 'include') {
|
|
1030
1106
|
const count = this.config.include?.length || 0;
|
|
1031
1107
|
return count > 0 ? count - 1 : 0;
|
|
1108
|
+
} else if (this.settingsSection === 'shortcuts') {
|
|
1109
|
+
return Math.max(0, this.allScripts.length - 1);
|
|
1032
1110
|
} else if (this.settingsSection === 'scripts') {
|
|
1033
1111
|
return Math.max(0, this.allScripts.length - 1);
|
|
1034
1112
|
}
|
|
@@ -1059,6 +1137,27 @@ class ProcessManager {
|
|
|
1059
1137
|
saveConfig(this.config);
|
|
1060
1138
|
this.applyFilters();
|
|
1061
1139
|
this.settingsIndex = Math.max(0, Math.min(this.settingsIndex, (this.config.include?.length || 1) - 1));
|
|
1140
|
+
} else if (this.settingsSection === 'shortcuts') {
|
|
1141
|
+
const script = this.allScripts[this.settingsIndex];
|
|
1142
|
+
if (script && this.config.shortcuts) {
|
|
1143
|
+
// Find and delete any shortcut assigned to this script
|
|
1144
|
+
for (const [key, scriptName] of Object.entries(this.config.shortcuts)) {
|
|
1145
|
+
if (scriptName === script.name) {
|
|
1146
|
+
delete this.config.shortcuts[key];
|
|
1147
|
+
saveConfig(this.config);
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
assignShortcut() {
|
|
1156
|
+
const script = this.allScripts[this.settingsIndex];
|
|
1157
|
+
if (script) {
|
|
1158
|
+
this.isAssigningShortcut = true;
|
|
1159
|
+
this.shortcutScriptName = script.name;
|
|
1160
|
+
this.buildSettingsUI();
|
|
1062
1161
|
}
|
|
1063
1162
|
}
|
|
1064
1163
|
|
|
@@ -1494,6 +1593,24 @@ class ProcessManager {
|
|
|
1494
1593
|
this.settingsContainer.add(inputBar);
|
|
1495
1594
|
}
|
|
1496
1595
|
|
|
1596
|
+
// Input prompt if assigning shortcut
|
|
1597
|
+
if (this.isAssigningShortcut) {
|
|
1598
|
+
const inputBar = new BoxRenderable(this.renderer, {
|
|
1599
|
+
id: 'input-bar',
|
|
1600
|
+
border: ['left'],
|
|
1601
|
+
borderStyle: 'single',
|
|
1602
|
+
borderColor: COLORS.accent,
|
|
1603
|
+
paddingLeft: 1,
|
|
1604
|
+
marginBottom: 1,
|
|
1605
|
+
});
|
|
1606
|
+
const inputText = new TextRenderable(this.renderer, {
|
|
1607
|
+
id: 'input-text',
|
|
1608
|
+
content: t`${fg(COLORS.textDim)('Press a key to assign as shortcut for')} ${fg(COLORS.accent)(this.shortcutScriptName)} ${fg(COLORS.textDim)('(esc to cancel)')}`,
|
|
1609
|
+
});
|
|
1610
|
+
inputBar.add(inputText);
|
|
1611
|
+
this.settingsContainer.add(inputBar);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1497
1614
|
// Combined content panel with all sections
|
|
1498
1615
|
const contentPanel = new BoxRenderable(this.renderer, {
|
|
1499
1616
|
id: 'content-panel',
|
|
@@ -1556,16 +1673,31 @@ class ProcessManager {
|
|
|
1556
1673
|
|
|
1557
1674
|
contentPanel.add(leftColumn);
|
|
1558
1675
|
|
|
1559
|
-
//
|
|
1676
|
+
// Middle column - Quick Commands
|
|
1677
|
+
const shortcutsBox = new BoxRenderable(this.renderer, {
|
|
1678
|
+
id: 'shortcuts-box',
|
|
1679
|
+
flexDirection: 'column',
|
|
1680
|
+
border: true,
|
|
1681
|
+
borderStyle: 'rounded',
|
|
1682
|
+
borderColor: this.settingsSection === 'shortcuts' ? COLORS.borderFocused : COLORS.border,
|
|
1683
|
+
title: ' Quick Commands ',
|
|
1684
|
+
titleAlignment: 'left',
|
|
1685
|
+
flexGrow: 1,
|
|
1686
|
+
padding: 1,
|
|
1687
|
+
});
|
|
1688
|
+
this.buildShortcutsSectionContent(shortcutsBox);
|
|
1689
|
+
contentPanel.add(shortcutsBox);
|
|
1690
|
+
|
|
1691
|
+
// Right column - Script List
|
|
1560
1692
|
const scriptsBox = new BoxRenderable(this.renderer, {
|
|
1561
1693
|
id: 'scripts-box',
|
|
1562
1694
|
flexDirection: 'column',
|
|
1563
1695
|
border: true,
|
|
1564
1696
|
borderStyle: 'rounded',
|
|
1565
1697
|
borderColor: this.settingsSection === 'scripts' ? COLORS.borderFocused : COLORS.border,
|
|
1566
|
-
title: '
|
|
1698
|
+
title: ' Script List ',
|
|
1567
1699
|
titleAlignment: 'left',
|
|
1568
|
-
flexGrow:
|
|
1700
|
+
flexGrow: 1,
|
|
1569
1701
|
padding: 1,
|
|
1570
1702
|
});
|
|
1571
1703
|
this.buildScriptsSectionContent(scriptsBox);
|
|
@@ -1586,19 +1718,27 @@ class ProcessManager {
|
|
|
1586
1718
|
gap: 2,
|
|
1587
1719
|
});
|
|
1588
1720
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1721
|
+
let shortcuts;
|
|
1722
|
+
if (this.isAddingPattern) {
|
|
1723
|
+
shortcuts = [
|
|
1724
|
+
{ key: 'enter', desc: 'save' },
|
|
1725
|
+
{ key: 'esc', desc: 'cancel' },
|
|
1726
|
+
];
|
|
1727
|
+
} else if (this.isAssigningShortcut) {
|
|
1728
|
+
shortcuts = [
|
|
1729
|
+
{ key: 'any key', desc: 'assign' },
|
|
1730
|
+
{ key: 'esc', desc: 'cancel' },
|
|
1731
|
+
];
|
|
1732
|
+
} else {
|
|
1733
|
+
shortcuts = [
|
|
1734
|
+
{ key: 'tab', desc: 'section' },
|
|
1735
|
+
{ key: 'space', desc: this.settingsSection === 'shortcuts' ? 'assign' : 'toggle' },
|
|
1736
|
+
{ key: 'i', desc: 'add ignore' },
|
|
1737
|
+
{ key: 'n', desc: 'add include' },
|
|
1738
|
+
{ key: 'd', desc: 'delete' },
|
|
1739
|
+
{ key: 'esc', desc: 'back' },
|
|
1740
|
+
];
|
|
1741
|
+
}
|
|
1602
1742
|
|
|
1603
1743
|
shortcuts.forEach(({ key, desc }) => {
|
|
1604
1744
|
const shortcut = new TextRenderable(this.renderer, {
|
|
@@ -1679,6 +1819,38 @@ class ProcessManager {
|
|
|
1679
1819
|
}
|
|
1680
1820
|
}
|
|
1681
1821
|
|
|
1822
|
+
buildShortcutsSectionContent(container) {
|
|
1823
|
+
const shortcuts = this.config.shortcuts || {};
|
|
1824
|
+
|
|
1825
|
+
// Helper to find shortcut key for a script
|
|
1826
|
+
const getShortcutKey = (scriptName) => {
|
|
1827
|
+
for (const [key, name] of Object.entries(shortcuts)) {
|
|
1828
|
+
if (name === scriptName) return key;
|
|
1829
|
+
}
|
|
1830
|
+
return null;
|
|
1831
|
+
};
|
|
1832
|
+
|
|
1833
|
+
this.allScripts.forEach((script, idx) => {
|
|
1834
|
+
const isFocused = this.settingsSection === 'shortcuts' && idx === this.settingsIndex;
|
|
1835
|
+
const indicator = isFocused ? '>' : ' ';
|
|
1836
|
+
const shortcutKey = getShortcutKey(script.name);
|
|
1837
|
+
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
1838
|
+
|
|
1839
|
+
let content;
|
|
1840
|
+
if (shortcutKey) {
|
|
1841
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(COLORS.warning)(`[${shortcutKey}]`)} ${fg(processColor)(script.displayName)}`;
|
|
1842
|
+
} else {
|
|
1843
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(COLORS.textDim)('[ ]')} ${fg(processColor)(script.displayName)}`;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
const line = new TextRenderable(this.renderer, {
|
|
1847
|
+
id: `shortcut-item-${idx}`,
|
|
1848
|
+
content: content,
|
|
1849
|
+
});
|
|
1850
|
+
container.add(line);
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1682
1854
|
buildScriptsSectionContent(container) {
|
|
1683
1855
|
const ignorePatterns = this.config.ignore || [];
|
|
1684
1856
|
|
|
@@ -1691,9 +1863,16 @@ class ProcessManager {
|
|
|
1691
1863
|
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
1692
1864
|
const nameColor = isIgnored ? COLORS.textDim : processColor;
|
|
1693
1865
|
|
|
1866
|
+
let content;
|
|
1867
|
+
if (isIgnored) {
|
|
1868
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(checkColor)(checkbox)} ${fg(nameColor)(script.displayName)} ${fg(COLORS.textDim)('(ignored)')}`;
|
|
1869
|
+
} else {
|
|
1870
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(checkColor)(checkbox)} ${fg(nameColor)(script.displayName)}`;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1694
1873
|
const line = new TextRenderable(this.renderer, {
|
|
1695
1874
|
id: `script-toggle-${idx}`,
|
|
1696
|
-
content:
|
|
1875
|
+
content: content,
|
|
1697
1876
|
});
|
|
1698
1877
|
container.add(line);
|
|
1699
1878
|
});
|
|
@@ -1723,6 +1902,15 @@ class ProcessManager {
|
|
|
1723
1902
|
this.countdownInterval = null;
|
|
1724
1903
|
}
|
|
1725
1904
|
|
|
1905
|
+
// Clean up command overlay process if running
|
|
1906
|
+
if (this.commandOverlayProcess && this.commandOverlayProcess.pid) {
|
|
1907
|
+
try {
|
|
1908
|
+
kill(this.commandOverlayProcess.pid, 'SIGKILL');
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
// Ignore
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1726
1914
|
for (const [scriptName, proc] of this.processRefs.entries()) {
|
|
1727
1915
|
try {
|
|
1728
1916
|
if (proc.pid) {
|
|
@@ -1733,6 +1921,75 @@ class ProcessManager {
|
|
|
1733
1921
|
}
|
|
1734
1922
|
}
|
|
1735
1923
|
}
|
|
1924
|
+
|
|
1925
|
+
executeCommand(scriptName) {
|
|
1926
|
+
// Initialize overlay state
|
|
1927
|
+
this.showCommandOverlay = true;
|
|
1928
|
+
this.commandOverlayOutput = [];
|
|
1929
|
+
this.commandOverlayScript = scriptName;
|
|
1930
|
+
this.commandOverlayStatus = 'running';
|
|
1931
|
+
|
|
1932
|
+
// Spawn the process
|
|
1933
|
+
const proc = spawn('npm', ['run', scriptName], {
|
|
1934
|
+
env: {
|
|
1935
|
+
...process.env,
|
|
1936
|
+
FORCE_COLOR: '1',
|
|
1937
|
+
COLORTERM: 'truecolor',
|
|
1938
|
+
},
|
|
1939
|
+
shell: true,
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
this.commandOverlayProcess = proc;
|
|
1943
|
+
|
|
1944
|
+
proc.stdout.on('data', (data) => {
|
|
1945
|
+
const text = data.toString();
|
|
1946
|
+
const lines = text.split('\n');
|
|
1947
|
+
lines.forEach(line => {
|
|
1948
|
+
if (line.trim()) {
|
|
1949
|
+
this.commandOverlayOutput.push(line);
|
|
1950
|
+
this.buildRunningUI();
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
proc.stderr.on('data', (data) => {
|
|
1956
|
+
const text = data.toString();
|
|
1957
|
+
const lines = text.split('\n');
|
|
1958
|
+
lines.forEach(line => {
|
|
1959
|
+
if (line.trim()) {
|
|
1960
|
+
this.commandOverlayOutput.push(line);
|
|
1961
|
+
this.buildRunningUI();
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
proc.on('exit', (code) => {
|
|
1967
|
+
this.commandOverlayStatus = code === 0 ? 'exited' : 'crashed';
|
|
1968
|
+
this.commandOverlayOutput.push('');
|
|
1969
|
+
this.commandOverlayOutput.push(`Process exited with code ${code}`);
|
|
1970
|
+
this.commandOverlayProcess = null;
|
|
1971
|
+
this.buildRunningUI();
|
|
1972
|
+
});
|
|
1973
|
+
|
|
1974
|
+
this.buildRunningUI();
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
closeCommandOverlay() {
|
|
1978
|
+
// Kill the process if still running
|
|
1979
|
+
if (this.commandOverlayProcess && this.commandOverlayProcess.pid) {
|
|
1980
|
+
try {
|
|
1981
|
+
kill(this.commandOverlayProcess.pid, 'SIGKILL');
|
|
1982
|
+
} catch (err) {
|
|
1983
|
+
// Ignore
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
this.showCommandOverlay = false;
|
|
1988
|
+
this.commandOverlayOutput = [];
|
|
1989
|
+
this.commandOverlayScript = '';
|
|
1990
|
+
this.commandOverlayStatus = 'running';
|
|
1991
|
+
this.commandOverlayProcess = null;
|
|
1992
|
+
}
|
|
1736
1993
|
|
|
1737
1994
|
buildSelectionUI() {
|
|
1738
1995
|
// Remove old containers if they exist - use destroyRecursively to clean up all children
|
|
@@ -2335,6 +2592,88 @@ class ProcessManager {
|
|
|
2335
2592
|
parent.add(overlay);
|
|
2336
2593
|
}
|
|
2337
2594
|
|
|
2595
|
+
// Build command output overlay
|
|
2596
|
+
buildCommandOverlay(parent) {
|
|
2597
|
+
const statusIcon = this.commandOverlayStatus === 'running' ? '●' :
|
|
2598
|
+
this.commandOverlayStatus === 'exited' ? '✓' : '✖';
|
|
2599
|
+
const statusColor = this.commandOverlayStatus === 'running' ? COLORS.warning :
|
|
2600
|
+
this.commandOverlayStatus === 'exited' ? COLORS.success : COLORS.error;
|
|
2601
|
+
const title = ` ${statusIcon} ${this.commandOverlayScript} `;
|
|
2602
|
+
|
|
2603
|
+
// Create centered overlay with scrollable content
|
|
2604
|
+
const overlay = new BoxRenderable(this.renderer, {
|
|
2605
|
+
id: 'command-overlay',
|
|
2606
|
+
position: 'absolute',
|
|
2607
|
+
top: '10%',
|
|
2608
|
+
left: '10%',
|
|
2609
|
+
width: '80%',
|
|
2610
|
+
height: '80%',
|
|
2611
|
+
backgroundColor: COLORS.bg,
|
|
2612
|
+
border: true,
|
|
2613
|
+
borderStyle: 'rounded',
|
|
2614
|
+
borderColor: statusColor,
|
|
2615
|
+
title: title,
|
|
2616
|
+
padding: 0,
|
|
2617
|
+
flexDirection: 'column',
|
|
2618
|
+
});
|
|
2619
|
+
|
|
2620
|
+
// Scrollable output content
|
|
2621
|
+
const outputBox = new ScrollBoxRenderable(this.renderer, {
|
|
2622
|
+
id: 'command-output',
|
|
2623
|
+
height: Math.floor(this.renderer.height * 0.8) - 4,
|
|
2624
|
+
scrollX: false,
|
|
2625
|
+
scrollY: true,
|
|
2626
|
+
focusable: true,
|
|
2627
|
+
style: {
|
|
2628
|
+
rootOptions: {
|
|
2629
|
+
flexGrow: 1,
|
|
2630
|
+
paddingLeft: 1,
|
|
2631
|
+
paddingRight: 1,
|
|
2632
|
+
backgroundColor: COLORS.bg,
|
|
2633
|
+
},
|
|
2634
|
+
contentOptions: {
|
|
2635
|
+
backgroundColor: COLORS.bg,
|
|
2636
|
+
width: '100%',
|
|
2637
|
+
},
|
|
2638
|
+
},
|
|
2639
|
+
});
|
|
2640
|
+
|
|
2641
|
+
// Add output lines
|
|
2642
|
+
this.commandOverlayOutput.forEach((line, idx) => {
|
|
2643
|
+
const outputLine = new TextRenderable(this.renderer, {
|
|
2644
|
+
id: `cmd-output-${idx}`,
|
|
2645
|
+
content: line,
|
|
2646
|
+
});
|
|
2647
|
+
outputBox.content.add(outputLine);
|
|
2648
|
+
});
|
|
2649
|
+
|
|
2650
|
+
// Auto-scroll to bottom
|
|
2651
|
+
if (outputBox.scrollTo) {
|
|
2652
|
+
outputBox.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER });
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
overlay.add(outputBox);
|
|
2656
|
+
|
|
2657
|
+
// Footer hint
|
|
2658
|
+
const hintBar = new BoxRenderable(this.renderer, {
|
|
2659
|
+
id: 'command-hint-bar',
|
|
2660
|
+
border: ['top'],
|
|
2661
|
+
borderStyle: 'single',
|
|
2662
|
+
borderColor: COLORS.border,
|
|
2663
|
+
paddingTop: 1,
|
|
2664
|
+
paddingLeft: 1,
|
|
2665
|
+
});
|
|
2666
|
+
|
|
2667
|
+
const hint = new TextRenderable(this.renderer, {
|
|
2668
|
+
id: 'command-hint',
|
|
2669
|
+
content: t`${fg(COLORS.textDim)('Press')} ${fg(COLORS.accent)('Esc')} ${fg(COLORS.textDim)('to close')}`,
|
|
2670
|
+
});
|
|
2671
|
+
hintBar.add(hint);
|
|
2672
|
+
overlay.add(hintBar);
|
|
2673
|
+
|
|
2674
|
+
parent.add(overlay);
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2338
2677
|
buildRunningUI() {
|
|
2339
2678
|
// Save scroll positions before destroying
|
|
2340
2679
|
for (const [paneId, scrollBox] of this.paneScrollBoxes.entries()) {
|
|
@@ -2562,6 +2901,11 @@ class ProcessManager {
|
|
|
2562
2901
|
this.buildSplitMenuOverlay(mainContainer);
|
|
2563
2902
|
}
|
|
2564
2903
|
|
|
2904
|
+
// Add command output overlay if active
|
|
2905
|
+
if (this.showCommandOverlay) {
|
|
2906
|
+
this.buildCommandOverlay(mainContainer);
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2565
2909
|
this.renderer.root.add(mainContainer);
|
|
2566
2910
|
this.runningContainer = mainContainer;
|
|
2567
2911
|
}
|