startall 0.0.13 → 0.0.15
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 +31 -7
- package/index.js +522 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,9 +56,15 @@ 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**: Run any script on-demand without adding it to the persistent processes
|
|
60
|
+
- Press `e` to open command picker and select any script
|
|
61
|
+
- Or assign keyboard shortcuts for instant access (press assigned key to run)
|
|
62
|
+
- Perfect for build scripts, tests, or any short-running command
|
|
63
|
+
- Output shown in popup overlay with Esc to close
|
|
64
|
+
- Configure shortcuts in settings (`o` → Quick Commands section)
|
|
59
65
|
- **Interactive input mode**: Send commands to running processes via stdin (`i`)
|
|
60
66
|
- Perfect for dev servers that accept commands (Vite, Rust watch, etc.)
|
|
61
|
-
- **Settings panel**: Configure ignore/include patterns (`o`)
|
|
67
|
+
- **Settings panel**: Configure ignore/include patterns, shortcuts, and more (`o`)
|
|
62
68
|
- Wildcard support (`*`) for pattern matching
|
|
63
69
|
- Per-script visibility toggles
|
|
64
70
|
- **Keyboard & mouse support**: Full keyboard navigation + mouse clicking/scrolling
|
|
@@ -103,6 +109,8 @@ That's it! The TUI will:
|
|
|
103
109
|
- `s` - Stop/start selected process
|
|
104
110
|
- `r` - Restart selected process
|
|
105
111
|
- `i` - Send input to selected process (interactive mode)
|
|
112
|
+
- `e` - Execute any script (opens command picker)
|
|
113
|
+
- `a-z` - Run assigned quick command (if configured)
|
|
106
114
|
|
|
107
115
|
*Pane Management:*
|
|
108
116
|
- `\` - Open command palette
|
|
@@ -137,13 +145,22 @@ That's it! The TUI will:
|
|
|
137
145
|
- `Ctrl+C` - Force quit
|
|
138
146
|
|
|
139
147
|
**Settings Screen:**
|
|
140
|
-
- `Tab`/`←`/`→` - Switch sections (Ignore/Include/
|
|
148
|
+
- `Tab`/`←`/`→` - Switch sections (Display/Ignore/Include/Quick Commands/Script List)
|
|
141
149
|
- `↑`/`↓` - Navigate items
|
|
142
|
-
- `
|
|
143
|
-
- `
|
|
144
|
-
- `Space` or `Enter` - Toggle
|
|
150
|
+
- `i` - Add new ignore pattern
|
|
151
|
+
- `n` - Add new include pattern
|
|
152
|
+
- `Space` or `Enter` - Toggle option (Display) / Assign shortcut (Quick Commands) / Toggle ignore (Script List)
|
|
153
|
+
- `d` or `Backspace` - Delete pattern or shortcut
|
|
145
154
|
- `Esc` or `q` - Return to previous screen
|
|
146
155
|
|
|
156
|
+
**Run Command Picker:**
|
|
157
|
+
- `↑`/`↓` or `k`/`j` - Navigate scripts
|
|
158
|
+
- `Enter` - Run selected script
|
|
159
|
+
- `Esc` or `q` - Close picker
|
|
160
|
+
|
|
161
|
+
**Quick Commands Overlay:**
|
|
162
|
+
- `Esc` - Close overlay and stop command (if running)
|
|
163
|
+
|
|
147
164
|
## Why Build This?
|
|
148
165
|
|
|
149
166
|
Existing tools either:
|
|
@@ -164,12 +181,19 @@ Existing tools either:
|
|
|
164
181
|
{
|
|
165
182
|
"defaultSelection": ["frontend", "backend"],
|
|
166
183
|
"include": ["dev:*"],
|
|
167
|
-
"ignore": ["*:test"]
|
|
184
|
+
"ignore": ["*:test"],
|
|
185
|
+
"shortcuts": {
|
|
186
|
+
"b": "build",
|
|
187
|
+
"t": "test",
|
|
188
|
+
"l": "lint"
|
|
189
|
+
}
|
|
168
190
|
}
|
|
169
191
|
```
|
|
192
|
+
- `defaultSelection`: scripts to auto-select on startup
|
|
170
193
|
- `include` (optional): if defined, only scripts matching these patterns are shown
|
|
171
194
|
- `ignore`: scripts matching these patterns are hidden
|
|
172
|
-
-
|
|
195
|
+
- `shortcuts`: keyboard shortcuts for running commands on-demand
|
|
196
|
+
- All patterns support wildcards (`*`)
|
|
173
197
|
|
|
174
198
|
## Roadmap
|
|
175
199
|
|
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,25 @@ 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
|
|
382
|
+
|
|
383
|
+
// Run command modal state
|
|
384
|
+
this.showRunCommandModal = false; // Whether the run command picker is visible
|
|
385
|
+
this.runCommandModalIndex = 0; // Selected index in the modal
|
|
362
386
|
this.outputBox = null; // Reference to the output container
|
|
363
387
|
this.destroyed = false; // Flag to prevent operations after cleanup
|
|
364
388
|
this.lastRenderedLineCount = 0; // Track how many lines we've rendered
|
|
@@ -465,6 +489,21 @@ class ProcessManager {
|
|
|
465
489
|
this.handleSettingsInput(keyName, keyEvent);
|
|
466
490
|
return;
|
|
467
491
|
} else if (this.phase === 'running') {
|
|
492
|
+
// Handle command overlay
|
|
493
|
+
if (this.showCommandOverlay) {
|
|
494
|
+
if (keyName === 'escape') {
|
|
495
|
+
this.closeCommandOverlay();
|
|
496
|
+
this.buildRunningUI();
|
|
497
|
+
}
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Handle run command modal
|
|
502
|
+
if (this.showRunCommandModal) {
|
|
503
|
+
this.handleRunCommandModalInput(keyName, keyEvent);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
468
507
|
// Handle split menu
|
|
469
508
|
if (this.showSplitMenu) {
|
|
470
509
|
this.handleSplitMenuInput(keyName, keyEvent);
|
|
@@ -679,6 +718,22 @@ class ProcessManager {
|
|
|
679
718
|
this.inputModeText = '';
|
|
680
719
|
this.buildRunningUI();
|
|
681
720
|
}
|
|
721
|
+
} else if (keyName === 'e') {
|
|
722
|
+
// Open run command modal
|
|
723
|
+
this.showRunCommandModal = true;
|
|
724
|
+
this.runCommandModalIndex = 0;
|
|
725
|
+
this.buildRunningUI();
|
|
726
|
+
} else if (keyName && keyName.length === 1 && !keyEvent.ctrl && !keyEvent.meta && !keyEvent.shift) {
|
|
727
|
+
// Check if this key is a custom shortcut
|
|
728
|
+
const shortcuts = this.config.shortcuts || {};
|
|
729
|
+
const scriptName = shortcuts[keyName];
|
|
730
|
+
if (scriptName) {
|
|
731
|
+
// Find the script in allScripts (since it might be ignored/filtered out)
|
|
732
|
+
const script = this.allScripts.find(s => s.name === scriptName);
|
|
733
|
+
if (script) {
|
|
734
|
+
this.executeCommand(scriptName);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
682
737
|
}
|
|
683
738
|
}
|
|
684
739
|
}
|
|
@@ -890,6 +945,40 @@ class ProcessManager {
|
|
|
890
945
|
}
|
|
891
946
|
|
|
892
947
|
handleSettingsInput(keyName, keyEvent) {
|
|
948
|
+
// Handle shortcut assignment mode
|
|
949
|
+
if (this.isAssigningShortcut) {
|
|
950
|
+
if (keyName === 'escape') {
|
|
951
|
+
this.isAssigningShortcut = false;
|
|
952
|
+
this.shortcutScriptName = '';
|
|
953
|
+
this.buildSettingsUI();
|
|
954
|
+
return;
|
|
955
|
+
} else if (keyName && keyName.length === 1 && !keyEvent.ctrl && !keyEvent.meta && !keyEvent.shift) {
|
|
956
|
+
// Assign this key to the script
|
|
957
|
+
if (!this.config.shortcuts) this.config.shortcuts = {};
|
|
958
|
+
|
|
959
|
+
// Remove any existing shortcut for this script (one key per script)
|
|
960
|
+
for (const [key, scriptName] of Object.entries(this.config.shortcuts)) {
|
|
961
|
+
if (scriptName === this.shortcutScriptName) {
|
|
962
|
+
delete this.config.shortcuts[key];
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Also remove this key if it's assigned to another script (one script per key)
|
|
967
|
+
if (this.config.shortcuts[keyName]) {
|
|
968
|
+
delete this.config.shortcuts[keyName];
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Assign the new shortcut
|
|
972
|
+
this.config.shortcuts[keyName] = this.shortcutScriptName;
|
|
973
|
+
saveConfig(this.config);
|
|
974
|
+
this.isAssigningShortcut = false;
|
|
975
|
+
this.shortcutScriptName = '';
|
|
976
|
+
this.buildSettingsUI();
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
|
|
893
982
|
// Handle text input mode for adding patterns
|
|
894
983
|
if (this.isAddingPattern) {
|
|
895
984
|
if (keyName === 'escape') {
|
|
@@ -944,21 +1033,21 @@ class ProcessManager {
|
|
|
944
1033
|
}
|
|
945
1034
|
} else if (keyName === 'tab' || keyName === 'right') {
|
|
946
1035
|
// Switch section
|
|
947
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1036
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
948
1037
|
const idx = sections.indexOf(this.settingsSection);
|
|
949
1038
|
this.settingsSection = sections[(idx + 1) % sections.length];
|
|
950
1039
|
this.settingsIndex = 0;
|
|
951
1040
|
this.buildSettingsUI();
|
|
952
1041
|
} else if (keyEvent.shift && keyName === 'tab') {
|
|
953
1042
|
// Switch section backwards
|
|
954
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1043
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
955
1044
|
const idx = sections.indexOf(this.settingsSection);
|
|
956
1045
|
this.settingsSection = sections[(idx - 1 + sections.length) % sections.length];
|
|
957
1046
|
this.settingsIndex = 0;
|
|
958
1047
|
this.buildSettingsUI();
|
|
959
1048
|
} else if (keyName === 'left') {
|
|
960
1049
|
// Switch section backwards
|
|
961
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1050
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
962
1051
|
const idx = sections.indexOf(this.settingsSection);
|
|
963
1052
|
this.settingsSection = sections[(idx - 1 + sections.length) % sections.length];
|
|
964
1053
|
this.settingsIndex = 0;
|
|
@@ -969,7 +1058,7 @@ class ProcessManager {
|
|
|
969
1058
|
this.buildSettingsUI();
|
|
970
1059
|
} else {
|
|
971
1060
|
// Move to previous section
|
|
972
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1061
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
973
1062
|
const idx = sections.indexOf(this.settingsSection);
|
|
974
1063
|
if (idx > 0) {
|
|
975
1064
|
this.settingsSection = sections[idx - 1];
|
|
@@ -984,7 +1073,7 @@ class ProcessManager {
|
|
|
984
1073
|
this.buildSettingsUI();
|
|
985
1074
|
} else {
|
|
986
1075
|
// Move to next section
|
|
987
|
-
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
1076
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
988
1077
|
const idx = sections.indexOf(this.settingsSection);
|
|
989
1078
|
if (idx < sections.length - 1) {
|
|
990
1079
|
this.settingsSection = sections[idx + 1];
|
|
@@ -1005,14 +1094,16 @@ class ProcessManager {
|
|
|
1005
1094
|
this.newPatternText = '';
|
|
1006
1095
|
this.buildSettingsUI();
|
|
1007
1096
|
} else if (keyName === 'd' || keyName === 'backspace') {
|
|
1008
|
-
// Delete selected pattern
|
|
1097
|
+
// Delete selected pattern or shortcut
|
|
1009
1098
|
this.deleteSelectedItem();
|
|
1010
1099
|
this.buildSettingsUI();
|
|
1011
1100
|
} else if (keyName === 'space' || keyName === 'enter' || keyName === 'return') {
|
|
1012
|
-
// Toggle display options, script visibility
|
|
1101
|
+
// Toggle display options, script visibility, or assign shortcut
|
|
1013
1102
|
if (this.settingsSection === 'display') {
|
|
1014
1103
|
this.toggleDisplayOption();
|
|
1015
1104
|
this.buildSettingsUI();
|
|
1105
|
+
} else if (this.settingsSection === 'shortcuts') {
|
|
1106
|
+
this.assignShortcut();
|
|
1016
1107
|
} else if (this.settingsSection === 'scripts') {
|
|
1017
1108
|
this.toggleScriptIgnore();
|
|
1018
1109
|
this.buildSettingsUI();
|
|
@@ -1029,6 +1120,8 @@ class ProcessManager {
|
|
|
1029
1120
|
} else if (this.settingsSection === 'include') {
|
|
1030
1121
|
const count = this.config.include?.length || 0;
|
|
1031
1122
|
return count > 0 ? count - 1 : 0;
|
|
1123
|
+
} else if (this.settingsSection === 'shortcuts') {
|
|
1124
|
+
return Math.max(0, this.allScripts.length - 1);
|
|
1032
1125
|
} else if (this.settingsSection === 'scripts') {
|
|
1033
1126
|
return Math.max(0, this.allScripts.length - 1);
|
|
1034
1127
|
}
|
|
@@ -1059,6 +1152,27 @@ class ProcessManager {
|
|
|
1059
1152
|
saveConfig(this.config);
|
|
1060
1153
|
this.applyFilters();
|
|
1061
1154
|
this.settingsIndex = Math.max(0, Math.min(this.settingsIndex, (this.config.include?.length || 1) - 1));
|
|
1155
|
+
} else if (this.settingsSection === 'shortcuts') {
|
|
1156
|
+
const script = this.allScripts[this.settingsIndex];
|
|
1157
|
+
if (script && this.config.shortcuts) {
|
|
1158
|
+
// Find and delete any shortcut assigned to this script
|
|
1159
|
+
for (const [key, scriptName] of Object.entries(this.config.shortcuts)) {
|
|
1160
|
+
if (scriptName === script.name) {
|
|
1161
|
+
delete this.config.shortcuts[key];
|
|
1162
|
+
saveConfig(this.config);
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
assignShortcut() {
|
|
1171
|
+
const script = this.allScripts[this.settingsIndex];
|
|
1172
|
+
if (script) {
|
|
1173
|
+
this.isAssigningShortcut = true;
|
|
1174
|
+
this.shortcutScriptName = script.name;
|
|
1175
|
+
this.buildSettingsUI();
|
|
1062
1176
|
}
|
|
1063
1177
|
}
|
|
1064
1178
|
|
|
@@ -1167,6 +1281,28 @@ class ProcessManager {
|
|
|
1167
1281
|
}
|
|
1168
1282
|
}
|
|
1169
1283
|
|
|
1284
|
+
handleRunCommandModalInput(keyName, keyEvent) {
|
|
1285
|
+
if (keyName === 'escape' || keyName === 'q') {
|
|
1286
|
+
this.showRunCommandModal = false;
|
|
1287
|
+
this.buildRunningUI();
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
if (keyName === 'up' || keyName === 'k') {
|
|
1292
|
+
this.runCommandModalIndex = Math.max(0, this.runCommandModalIndex - 1);
|
|
1293
|
+
this.buildRunningUI();
|
|
1294
|
+
} else if (keyName === 'down' || keyName === 'j') {
|
|
1295
|
+
this.runCommandModalIndex = Math.min(this.allScripts.length - 1, this.runCommandModalIndex + 1);
|
|
1296
|
+
this.buildRunningUI();
|
|
1297
|
+
} else if (keyName === 'enter' || keyName === 'return') {
|
|
1298
|
+
const selectedScript = this.allScripts[this.runCommandModalIndex];
|
|
1299
|
+
if (selectedScript) {
|
|
1300
|
+
this.showRunCommandModal = false;
|
|
1301
|
+
this.executeCommand(selectedScript.name);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1170
1306
|
getSplitMenuItems() {
|
|
1171
1307
|
const allPanes = getAllPaneIds(this.paneRoot);
|
|
1172
1308
|
const items = [
|
|
@@ -1494,6 +1630,24 @@ class ProcessManager {
|
|
|
1494
1630
|
this.settingsContainer.add(inputBar);
|
|
1495
1631
|
}
|
|
1496
1632
|
|
|
1633
|
+
// Input prompt if assigning shortcut
|
|
1634
|
+
if (this.isAssigningShortcut) {
|
|
1635
|
+
const inputBar = new BoxRenderable(this.renderer, {
|
|
1636
|
+
id: 'input-bar',
|
|
1637
|
+
border: ['left'],
|
|
1638
|
+
borderStyle: 'single',
|
|
1639
|
+
borderColor: COLORS.accent,
|
|
1640
|
+
paddingLeft: 1,
|
|
1641
|
+
marginBottom: 1,
|
|
1642
|
+
});
|
|
1643
|
+
const inputText = new TextRenderable(this.renderer, {
|
|
1644
|
+
id: 'input-text',
|
|
1645
|
+
content: t`${fg(COLORS.textDim)('Press a key to assign as shortcut for')} ${fg(COLORS.accent)(this.shortcutScriptName)} ${fg(COLORS.textDim)('(esc to cancel)')}`,
|
|
1646
|
+
});
|
|
1647
|
+
inputBar.add(inputText);
|
|
1648
|
+
this.settingsContainer.add(inputBar);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1497
1651
|
// Combined content panel with all sections
|
|
1498
1652
|
const contentPanel = new BoxRenderable(this.renderer, {
|
|
1499
1653
|
id: 'content-panel',
|
|
@@ -1556,16 +1710,31 @@ class ProcessManager {
|
|
|
1556
1710
|
|
|
1557
1711
|
contentPanel.add(leftColumn);
|
|
1558
1712
|
|
|
1559
|
-
//
|
|
1713
|
+
// Middle column - Quick Commands
|
|
1714
|
+
const shortcutsBox = new BoxRenderable(this.renderer, {
|
|
1715
|
+
id: 'shortcuts-box',
|
|
1716
|
+
flexDirection: 'column',
|
|
1717
|
+
border: true,
|
|
1718
|
+
borderStyle: 'rounded',
|
|
1719
|
+
borderColor: this.settingsSection === 'shortcuts' ? COLORS.borderFocused : COLORS.border,
|
|
1720
|
+
title: ' Quick Commands ',
|
|
1721
|
+
titleAlignment: 'left',
|
|
1722
|
+
flexGrow: 1,
|
|
1723
|
+
padding: 1,
|
|
1724
|
+
});
|
|
1725
|
+
this.buildShortcutsSectionContent(shortcutsBox);
|
|
1726
|
+
contentPanel.add(shortcutsBox);
|
|
1727
|
+
|
|
1728
|
+
// Right column - Script List
|
|
1560
1729
|
const scriptsBox = new BoxRenderable(this.renderer, {
|
|
1561
1730
|
id: 'scripts-box',
|
|
1562
1731
|
flexDirection: 'column',
|
|
1563
1732
|
border: true,
|
|
1564
1733
|
borderStyle: 'rounded',
|
|
1565
1734
|
borderColor: this.settingsSection === 'scripts' ? COLORS.borderFocused : COLORS.border,
|
|
1566
|
-
title: '
|
|
1735
|
+
title: ' Script List ',
|
|
1567
1736
|
titleAlignment: 'left',
|
|
1568
|
-
flexGrow:
|
|
1737
|
+
flexGrow: 1,
|
|
1569
1738
|
padding: 1,
|
|
1570
1739
|
});
|
|
1571
1740
|
this.buildScriptsSectionContent(scriptsBox);
|
|
@@ -1586,19 +1755,27 @@ class ProcessManager {
|
|
|
1586
1755
|
gap: 2,
|
|
1587
1756
|
});
|
|
1588
1757
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1758
|
+
let shortcuts;
|
|
1759
|
+
if (this.isAddingPattern) {
|
|
1760
|
+
shortcuts = [
|
|
1761
|
+
{ key: 'enter', desc: 'save' },
|
|
1762
|
+
{ key: 'esc', desc: 'cancel' },
|
|
1763
|
+
];
|
|
1764
|
+
} else if (this.isAssigningShortcut) {
|
|
1765
|
+
shortcuts = [
|
|
1766
|
+
{ key: 'any key', desc: 'assign' },
|
|
1767
|
+
{ key: 'esc', desc: 'cancel' },
|
|
1768
|
+
];
|
|
1769
|
+
} else {
|
|
1770
|
+
shortcuts = [
|
|
1771
|
+
{ key: 'tab', desc: 'section' },
|
|
1772
|
+
{ key: 'space', desc: this.settingsSection === 'shortcuts' ? 'assign' : 'toggle' },
|
|
1773
|
+
{ key: 'i', desc: 'add ignore' },
|
|
1774
|
+
{ key: 'n', desc: 'add include' },
|
|
1775
|
+
{ key: 'd', desc: 'delete' },
|
|
1776
|
+
{ key: 'esc', desc: 'back' },
|
|
1777
|
+
];
|
|
1778
|
+
}
|
|
1602
1779
|
|
|
1603
1780
|
shortcuts.forEach(({ key, desc }) => {
|
|
1604
1781
|
const shortcut = new TextRenderable(this.renderer, {
|
|
@@ -1679,6 +1856,38 @@ class ProcessManager {
|
|
|
1679
1856
|
}
|
|
1680
1857
|
}
|
|
1681
1858
|
|
|
1859
|
+
buildShortcutsSectionContent(container) {
|
|
1860
|
+
const shortcuts = this.config.shortcuts || {};
|
|
1861
|
+
|
|
1862
|
+
// Helper to find shortcut key for a script
|
|
1863
|
+
const getShortcutKey = (scriptName) => {
|
|
1864
|
+
for (const [key, name] of Object.entries(shortcuts)) {
|
|
1865
|
+
if (name === scriptName) return key;
|
|
1866
|
+
}
|
|
1867
|
+
return null;
|
|
1868
|
+
};
|
|
1869
|
+
|
|
1870
|
+
this.allScripts.forEach((script, idx) => {
|
|
1871
|
+
const isFocused = this.settingsSection === 'shortcuts' && idx === this.settingsIndex;
|
|
1872
|
+
const indicator = isFocused ? '>' : ' ';
|
|
1873
|
+
const shortcutKey = getShortcutKey(script.name);
|
|
1874
|
+
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
1875
|
+
|
|
1876
|
+
let content;
|
|
1877
|
+
if (shortcutKey) {
|
|
1878
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(COLORS.warning)(`[${shortcutKey}]`)} ${fg(processColor)(script.displayName)}`;
|
|
1879
|
+
} else {
|
|
1880
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(COLORS.textDim)('[ ]')} ${fg(processColor)(script.displayName)}`;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
const line = new TextRenderable(this.renderer, {
|
|
1884
|
+
id: `shortcut-item-${idx}`,
|
|
1885
|
+
content: content,
|
|
1886
|
+
});
|
|
1887
|
+
container.add(line);
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1682
1891
|
buildScriptsSectionContent(container) {
|
|
1683
1892
|
const ignorePatterns = this.config.ignore || [];
|
|
1684
1893
|
|
|
@@ -1691,9 +1900,16 @@ class ProcessManager {
|
|
|
1691
1900
|
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
1692
1901
|
const nameColor = isIgnored ? COLORS.textDim : processColor;
|
|
1693
1902
|
|
|
1903
|
+
let content;
|
|
1904
|
+
if (isIgnored) {
|
|
1905
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(checkColor)(checkbox)} ${fg(nameColor)(script.displayName)} ${fg(COLORS.textDim)('(ignored)')}`;
|
|
1906
|
+
} else {
|
|
1907
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(checkColor)(checkbox)} ${fg(nameColor)(script.displayName)}`;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1694
1910
|
const line = new TextRenderable(this.renderer, {
|
|
1695
1911
|
id: `script-toggle-${idx}`,
|
|
1696
|
-
content:
|
|
1912
|
+
content: content,
|
|
1697
1913
|
});
|
|
1698
1914
|
container.add(line);
|
|
1699
1915
|
});
|
|
@@ -1723,6 +1939,15 @@ class ProcessManager {
|
|
|
1723
1939
|
this.countdownInterval = null;
|
|
1724
1940
|
}
|
|
1725
1941
|
|
|
1942
|
+
// Clean up command overlay process if running
|
|
1943
|
+
if (this.commandOverlayProcess && this.commandOverlayProcess.pid) {
|
|
1944
|
+
try {
|
|
1945
|
+
kill(this.commandOverlayProcess.pid, 'SIGKILL');
|
|
1946
|
+
} catch (err) {
|
|
1947
|
+
// Ignore
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1726
1951
|
for (const [scriptName, proc] of this.processRefs.entries()) {
|
|
1727
1952
|
try {
|
|
1728
1953
|
if (proc.pid) {
|
|
@@ -1733,6 +1958,75 @@ class ProcessManager {
|
|
|
1733
1958
|
}
|
|
1734
1959
|
}
|
|
1735
1960
|
}
|
|
1961
|
+
|
|
1962
|
+
executeCommand(scriptName) {
|
|
1963
|
+
// Initialize overlay state
|
|
1964
|
+
this.showCommandOverlay = true;
|
|
1965
|
+
this.commandOverlayOutput = [];
|
|
1966
|
+
this.commandOverlayScript = scriptName;
|
|
1967
|
+
this.commandOverlayStatus = 'running';
|
|
1968
|
+
|
|
1969
|
+
// Spawn the process
|
|
1970
|
+
const proc = spawn('npm', ['run', scriptName], {
|
|
1971
|
+
env: {
|
|
1972
|
+
...process.env,
|
|
1973
|
+
FORCE_COLOR: '1',
|
|
1974
|
+
COLORTERM: 'truecolor',
|
|
1975
|
+
},
|
|
1976
|
+
shell: true,
|
|
1977
|
+
});
|
|
1978
|
+
|
|
1979
|
+
this.commandOverlayProcess = proc;
|
|
1980
|
+
|
|
1981
|
+
proc.stdout.on('data', (data) => {
|
|
1982
|
+
const text = data.toString();
|
|
1983
|
+
const lines = text.split('\n');
|
|
1984
|
+
lines.forEach(line => {
|
|
1985
|
+
if (line.trim()) {
|
|
1986
|
+
this.commandOverlayOutput.push(line);
|
|
1987
|
+
this.buildRunningUI();
|
|
1988
|
+
}
|
|
1989
|
+
});
|
|
1990
|
+
});
|
|
1991
|
+
|
|
1992
|
+
proc.stderr.on('data', (data) => {
|
|
1993
|
+
const text = data.toString();
|
|
1994
|
+
const lines = text.split('\n');
|
|
1995
|
+
lines.forEach(line => {
|
|
1996
|
+
if (line.trim()) {
|
|
1997
|
+
this.commandOverlayOutput.push(line);
|
|
1998
|
+
this.buildRunningUI();
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
});
|
|
2002
|
+
|
|
2003
|
+
proc.on('exit', (code) => {
|
|
2004
|
+
this.commandOverlayStatus = code === 0 ? 'exited' : 'crashed';
|
|
2005
|
+
this.commandOverlayOutput.push('');
|
|
2006
|
+
this.commandOverlayOutput.push(`Process exited with code ${code}`);
|
|
2007
|
+
this.commandOverlayProcess = null;
|
|
2008
|
+
this.buildRunningUI();
|
|
2009
|
+
});
|
|
2010
|
+
|
|
2011
|
+
this.buildRunningUI();
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
closeCommandOverlay() {
|
|
2015
|
+
// Kill the process if still running
|
|
2016
|
+
if (this.commandOverlayProcess && this.commandOverlayProcess.pid) {
|
|
2017
|
+
try {
|
|
2018
|
+
kill(this.commandOverlayProcess.pid, 'SIGKILL');
|
|
2019
|
+
} catch (err) {
|
|
2020
|
+
// Ignore
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
this.showCommandOverlay = false;
|
|
2025
|
+
this.commandOverlayOutput = [];
|
|
2026
|
+
this.commandOverlayScript = '';
|
|
2027
|
+
this.commandOverlayStatus = 'running';
|
|
2028
|
+
this.commandOverlayProcess = null;
|
|
2029
|
+
}
|
|
1736
2030
|
|
|
1737
2031
|
buildSelectionUI() {
|
|
1738
2032
|
// Remove old containers if they exist - use destroyRecursively to clean up all children
|
|
@@ -2335,6 +2629,196 @@ class ProcessManager {
|
|
|
2335
2629
|
parent.add(overlay);
|
|
2336
2630
|
}
|
|
2337
2631
|
|
|
2632
|
+
// Build command output overlay
|
|
2633
|
+
buildCommandOverlay(parent) {
|
|
2634
|
+
const statusIcon = this.commandOverlayStatus === 'running' ? '●' :
|
|
2635
|
+
this.commandOverlayStatus === 'exited' ? '✓' : '✖';
|
|
2636
|
+
const statusColor = this.commandOverlayStatus === 'running' ? COLORS.warning :
|
|
2637
|
+
this.commandOverlayStatus === 'exited' ? COLORS.success : COLORS.error;
|
|
2638
|
+
const title = ` ${statusIcon} ${this.commandOverlayScript} `;
|
|
2639
|
+
|
|
2640
|
+
// Create centered overlay with scrollable content
|
|
2641
|
+
const overlay = new BoxRenderable(this.renderer, {
|
|
2642
|
+
id: 'command-overlay',
|
|
2643
|
+
position: 'absolute',
|
|
2644
|
+
top: '10%',
|
|
2645
|
+
left: '10%',
|
|
2646
|
+
width: '80%',
|
|
2647
|
+
height: '80%',
|
|
2648
|
+
backgroundColor: COLORS.bg,
|
|
2649
|
+
border: true,
|
|
2650
|
+
borderStyle: 'rounded',
|
|
2651
|
+
borderColor: statusColor,
|
|
2652
|
+
title: title,
|
|
2653
|
+
padding: 0,
|
|
2654
|
+
flexDirection: 'column',
|
|
2655
|
+
});
|
|
2656
|
+
|
|
2657
|
+
// Scrollable output content
|
|
2658
|
+
const outputBox = new ScrollBoxRenderable(this.renderer, {
|
|
2659
|
+
id: 'command-output',
|
|
2660
|
+
height: Math.floor(this.renderer.height * 0.8) - 4,
|
|
2661
|
+
scrollX: false,
|
|
2662
|
+
scrollY: true,
|
|
2663
|
+
focusable: true,
|
|
2664
|
+
style: {
|
|
2665
|
+
rootOptions: {
|
|
2666
|
+
flexGrow: 1,
|
|
2667
|
+
paddingLeft: 1,
|
|
2668
|
+
paddingRight: 1,
|
|
2669
|
+
backgroundColor: COLORS.bg,
|
|
2670
|
+
},
|
|
2671
|
+
contentOptions: {
|
|
2672
|
+
backgroundColor: COLORS.bg,
|
|
2673
|
+
width: '100%',
|
|
2674
|
+
},
|
|
2675
|
+
},
|
|
2676
|
+
});
|
|
2677
|
+
|
|
2678
|
+
// Add output lines
|
|
2679
|
+
this.commandOverlayOutput.forEach((line, idx) => {
|
|
2680
|
+
const outputLine = new TextRenderable(this.renderer, {
|
|
2681
|
+
id: `cmd-output-${idx}`,
|
|
2682
|
+
content: line,
|
|
2683
|
+
});
|
|
2684
|
+
outputBox.content.add(outputLine);
|
|
2685
|
+
});
|
|
2686
|
+
|
|
2687
|
+
// Auto-scroll to bottom
|
|
2688
|
+
if (outputBox.scrollTo) {
|
|
2689
|
+
outputBox.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER });
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
overlay.add(outputBox);
|
|
2693
|
+
|
|
2694
|
+
// Footer hint
|
|
2695
|
+
const hintBar = new BoxRenderable(this.renderer, {
|
|
2696
|
+
id: 'command-hint-bar',
|
|
2697
|
+
border: ['top'],
|
|
2698
|
+
borderStyle: 'single',
|
|
2699
|
+
borderColor: COLORS.border,
|
|
2700
|
+
paddingTop: 1,
|
|
2701
|
+
paddingLeft: 1,
|
|
2702
|
+
});
|
|
2703
|
+
|
|
2704
|
+
const hint = new TextRenderable(this.renderer, {
|
|
2705
|
+
id: 'command-hint',
|
|
2706
|
+
content: t`${fg(COLORS.textDim)('Press')} ${fg(COLORS.accent)('Esc')} ${fg(COLORS.textDim)('to close')}`,
|
|
2707
|
+
});
|
|
2708
|
+
hintBar.add(hint);
|
|
2709
|
+
overlay.add(hintBar);
|
|
2710
|
+
|
|
2711
|
+
parent.add(overlay);
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
// Build run command picker modal
|
|
2715
|
+
buildRunCommandModal(parent) {
|
|
2716
|
+
// Create centered overlay
|
|
2717
|
+
const overlay = new BoxRenderable(this.renderer, {
|
|
2718
|
+
id: 'run-command-modal',
|
|
2719
|
+
position: 'absolute',
|
|
2720
|
+
top: '20%',
|
|
2721
|
+
left: '25%',
|
|
2722
|
+
width: '50%',
|
|
2723
|
+
height: '60%',
|
|
2724
|
+
backgroundColor: COLORS.bgLight,
|
|
2725
|
+
border: true,
|
|
2726
|
+
borderStyle: 'rounded',
|
|
2727
|
+
borderColor: COLORS.accent,
|
|
2728
|
+
title: ' Run Command ',
|
|
2729
|
+
padding: 1,
|
|
2730
|
+
flexDirection: 'column',
|
|
2731
|
+
});
|
|
2732
|
+
|
|
2733
|
+
// Scrollable list of scripts
|
|
2734
|
+
const listBox = new ScrollBoxRenderable(this.renderer, {
|
|
2735
|
+
id: 'run-command-list',
|
|
2736
|
+
height: Math.floor(this.renderer.height * 0.6) - 4,
|
|
2737
|
+
scrollX: false,
|
|
2738
|
+
scrollY: true,
|
|
2739
|
+
focusable: true,
|
|
2740
|
+
style: {
|
|
2741
|
+
rootOptions: {
|
|
2742
|
+
flexGrow: 1,
|
|
2743
|
+
backgroundColor: COLORS.bgLight,
|
|
2744
|
+
},
|
|
2745
|
+
contentOptions: {
|
|
2746
|
+
backgroundColor: COLORS.bgLight,
|
|
2747
|
+
width: '100%',
|
|
2748
|
+
},
|
|
2749
|
+
},
|
|
2750
|
+
});
|
|
2751
|
+
|
|
2752
|
+
this.allScripts.forEach((script, idx) => {
|
|
2753
|
+
const isFocused = idx === this.runCommandModalIndex;
|
|
2754
|
+
const indicator = isFocused ? '>' : ' ';
|
|
2755
|
+
const bgColor = isFocused ? COLORS.bgHighlight : null;
|
|
2756
|
+
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
2757
|
+
|
|
2758
|
+
// Check if this script has a shortcut
|
|
2759
|
+
const shortcuts = this.config.shortcuts || {};
|
|
2760
|
+
let shortcutKey = null;
|
|
2761
|
+
for (const [key, scriptName] of Object.entries(shortcuts)) {
|
|
2762
|
+
if (scriptName === script.name) {
|
|
2763
|
+
shortcutKey = key;
|
|
2764
|
+
break;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
const itemContainer = new BoxRenderable(this.renderer, {
|
|
2769
|
+
id: `run-cmd-item-${idx}`,
|
|
2770
|
+
backgroundColor: bgColor,
|
|
2771
|
+
paddingLeft: 1,
|
|
2772
|
+
});
|
|
2773
|
+
|
|
2774
|
+
let content;
|
|
2775
|
+
if (shortcutKey) {
|
|
2776
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(processColor)(script.displayName)} ${fg(COLORS.textDim)(`(${shortcutKey})`)}`;
|
|
2777
|
+
} else {
|
|
2778
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(processColor)(script.displayName)}`;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
const itemText = new TextRenderable(this.renderer, {
|
|
2782
|
+
id: `run-cmd-text-${idx}`,
|
|
2783
|
+
content: content,
|
|
2784
|
+
});
|
|
2785
|
+
|
|
2786
|
+
itemContainer.add(itemText);
|
|
2787
|
+
listBox.content.add(itemContainer);
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2790
|
+
// Auto-scroll to focused item
|
|
2791
|
+
if (listBox.scrollTo) {
|
|
2792
|
+
const lineHeight = 1;
|
|
2793
|
+
const viewportHeight = Math.floor(this.renderer.height * 0.6) - 4;
|
|
2794
|
+
const focusedY = this.runCommandModalIndex * lineHeight;
|
|
2795
|
+
if (focusedY < listBox.scrollTop || focusedY >= listBox.scrollTop + viewportHeight) {
|
|
2796
|
+
listBox.scrollTo({ x: 0, y: Math.max(0, focusedY - Math.floor(viewportHeight / 2)) });
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
overlay.add(listBox);
|
|
2801
|
+
|
|
2802
|
+
// Footer hint
|
|
2803
|
+
const hintBar = new BoxRenderable(this.renderer, {
|
|
2804
|
+
id: 'run-cmd-hint-bar',
|
|
2805
|
+
border: ['top'],
|
|
2806
|
+
borderStyle: 'single',
|
|
2807
|
+
borderColor: COLORS.border,
|
|
2808
|
+
paddingTop: 1,
|
|
2809
|
+
paddingLeft: 1,
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2812
|
+
const hint = new TextRenderable(this.renderer, {
|
|
2813
|
+
id: 'run-cmd-hint',
|
|
2814
|
+
content: t`${fg(COLORS.textDim)('↑/↓ navigate')} ${fg(COLORS.accent)('Enter')} ${fg(COLORS.textDim)('run')} ${fg(COLORS.accent)('Esc')} ${fg(COLORS.textDim)('close')}`,
|
|
2815
|
+
});
|
|
2816
|
+
hintBar.add(hint);
|
|
2817
|
+
overlay.add(hintBar);
|
|
2818
|
+
|
|
2819
|
+
parent.add(overlay);
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2338
2822
|
buildRunningUI() {
|
|
2339
2823
|
// Save scroll positions before destroying
|
|
2340
2824
|
for (const [paneId, scrollBox] of this.paneScrollBoxes.entries()) {
|
|
@@ -2562,6 +3046,16 @@ class ProcessManager {
|
|
|
2562
3046
|
this.buildSplitMenuOverlay(mainContainer);
|
|
2563
3047
|
}
|
|
2564
3048
|
|
|
3049
|
+
// Add run command modal if active
|
|
3050
|
+
if (this.showRunCommandModal) {
|
|
3051
|
+
this.buildRunCommandModal(mainContainer);
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
// Add command output overlay if active
|
|
3055
|
+
if (this.showCommandOverlay) {
|
|
3056
|
+
this.buildCommandOverlay(mainContainer);
|
|
3057
|
+
}
|
|
3058
|
+
|
|
2565
3059
|
this.renderer.root.add(mainContainer);
|
|
2566
3060
|
this.runningContainer = mainContainer;
|
|
2567
3061
|
}
|