startall 0.0.12 → 0.0.13
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 +88 -43
- package/index.js +388 -117
- package/package.json +3 -2
- package/screenshot.png +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# 🚀
|
|
1
|
+
# 🚀 startall
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> A powerful, interactive terminal UI for managing multiple npm scripts with tmux-style panes, filtering, and real-time control
|
|
4
|
+
|
|
5
|
+

|
|
4
6
|
|
|
5
7
|
## The Problem
|
|
6
8
|
|
|
@@ -20,47 +22,47 @@ Traditional solutions fall short:
|
|
|
20
22
|
|
|
21
23
|
## The Solution
|
|
22
24
|
|
|
23
|
-
**
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
┌─ Starting in 7s... [Enter to start now] ─────────────┐
|
|
27
|
-
│ [x] frontend (npm run start:frontend) │
|
|
28
|
-
│ [x] backend (npm run start:backend) │
|
|
29
|
-
│ [ ] worker (npm run start:worker) │
|
|
30
|
-
│ [x] db (npm run start:db) │
|
|
31
|
-
│ │
|
|
32
|
-
│ ↑/↓ Navigate | Space: Toggle | Enter: Start │
|
|
33
|
-
└───────────────────────────────────────────────────────┘
|
|
34
|
-
|
|
35
|
-
After starting:
|
|
36
|
-
┌─ Processes ──────────────┬─ Output (filter: error) ───┐
|
|
37
|
-
│ [f] frontend ● Running │ [backend] Error: ECONNREF │
|
|
38
|
-
│ [b] backend ✖ Crashed │ [backend] Retrying... │
|
|
39
|
-
│ [w] worker ⏸ Stopped │ [frontend] Started on 3000 │
|
|
40
|
-
│ [d] db ● Running │ │
|
|
41
|
-
│ │ │
|
|
42
|
-
│ Space: Start/Stop │ │
|
|
43
|
-
│ r: Restart │ │
|
|
44
|
-
│ /: Filter output │ │
|
|
45
|
-
└──────────────────────────┴────────────────────────────┘
|
|
46
|
-
```
|
|
25
|
+
**startall** is a sophisticated TUI that combines the power of tmux with the simplicity of npm scripts, giving you complete control over your development processes with split panes, filtering, and interactive controls.
|
|
47
26
|
|
|
48
27
|
## Features
|
|
49
28
|
|
|
50
|
-
###
|
|
51
|
-
- **Auto-discovery**:
|
|
52
|
-
- **Smart defaults**: Remembers your last selection
|
|
53
|
-
- **10-second countdown**:
|
|
29
|
+
### 🎯 Core Features
|
|
30
|
+
- **Auto-discovery**: Automatically reads all scripts from `package.json`
|
|
31
|
+
- **Smart defaults**: Remembers your last selection in `startall.json`
|
|
32
|
+
- **10-second countdown**: Review selections before starting
|
|
54
33
|
- **Parallel execution**: Run multiple npm scripts simultaneously
|
|
55
|
-
- **
|
|
56
|
-
|
|
57
|
-
### 🚧 Planned
|
|
58
|
-
- **Live status monitoring**: See which processes are running/crashed/stopped at a glance
|
|
59
|
-
- **Interactive controls**: Start, stop, and restart individual processes with keyboard shortcuts
|
|
60
|
-
- **Output filtering**: Search/filter logs across all processes in real-time
|
|
34
|
+
- **Live status monitoring**: Real-time status indicators (● running, ✖ crashed, ○ stopped)
|
|
35
|
+
- **Interactive controls**: Start, stop, and restart individual processes on the fly
|
|
61
36
|
- **Cross-platform**: Works identically on Windows, Linux, and macOS
|
|
62
|
-
|
|
63
|
-
|
|
37
|
+
|
|
38
|
+
### 🎨 Advanced UI
|
|
39
|
+
- **Multi-pane layout**: tmux-inspired split panes (vertical & horizontal)
|
|
40
|
+
- **Flexible filtering**:
|
|
41
|
+
- Text search across all output (`/`)
|
|
42
|
+
- Filter by ANSI color (red/yellow/green/blue/cyan/magenta) (`c`)
|
|
43
|
+
- Per-process visibility toggles (`Space` or `1-9`)
|
|
44
|
+
- Per-pane filters (different views in each pane)
|
|
45
|
+
- **Custom pane naming**: Label panes for easier identification (`n`)
|
|
46
|
+
- **Persistent layouts**: Your pane configuration is saved between sessions
|
|
47
|
+
- **Process-specific views**: Show/hide specific processes in each pane
|
|
48
|
+
- **Colored output**: Each process gets unique color-coded output
|
|
49
|
+
- **Pause/resume**: Freeze output to review logs (`p`)
|
|
50
|
+
- **Scrollable history**: 1000-line buffer with mouse wheel support
|
|
51
|
+
- **Enhanced navigation**: Home/End/PageUp/PageDown keys
|
|
52
|
+
|
|
53
|
+
### ⚙️ Display Options
|
|
54
|
+
- **Toggleable line numbers**: Show/hide line numbers (`#`)
|
|
55
|
+
- **Timestamps**: Show/hide timestamps for each log line (`t`)
|
|
56
|
+
- **Quick process toggle**: Use number keys `1-9` for instant visibility control
|
|
57
|
+
|
|
58
|
+
### 🔧 Advanced Controls
|
|
59
|
+
- **Interactive input mode**: Send commands to running processes via stdin (`i`)
|
|
60
|
+
- Perfect for dev servers that accept commands (Vite, Rust watch, etc.)
|
|
61
|
+
- **Settings panel**: Configure ignore/include patterns (`o`)
|
|
62
|
+
- Wildcard support (`*`) for pattern matching
|
|
63
|
+
- Per-script visibility toggles
|
|
64
|
+
- **Keyboard & mouse support**: Full keyboard navigation + mouse clicking/scrolling
|
|
65
|
+
- **VSCode integration**: Optimized for VSCode integrated terminal
|
|
64
66
|
|
|
65
67
|
## Installation
|
|
66
68
|
|
|
@@ -90,14 +92,57 @@ That's it! The TUI will:
|
|
|
90
92
|
- `↑`/`↓` - Navigate scripts
|
|
91
93
|
- `Space` - Toggle selection
|
|
92
94
|
- `Enter` - Start immediately (skip countdown)
|
|
95
|
+
- `o` - Open settings
|
|
93
96
|
- `Ctrl+C` - Exit
|
|
94
97
|
|
|
95
|
-
**Running Screen
|
|
96
|
-
|
|
98
|
+
**Running Screen:**
|
|
99
|
+
|
|
100
|
+
*Process Control:*
|
|
101
|
+
- `1-9` - Quick toggle process visibility in focused pane
|
|
102
|
+
- `Space` - Toggle visibility of selected process
|
|
103
|
+
- `s` - Stop/start selected process
|
|
97
104
|
- `r` - Restart selected process
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
- `i` - Send input to selected process (interactive mode)
|
|
106
|
+
|
|
107
|
+
*Pane Management:*
|
|
108
|
+
- `\` - Open command palette
|
|
109
|
+
- `|` - Split pane vertically (left/right)
|
|
110
|
+
- `_` - Split pane horizontally (top/bottom)
|
|
111
|
+
- `x` - Close current pane (if >1 pane exists)
|
|
112
|
+
- `Tab` - Next pane
|
|
113
|
+
- `Shift+Tab` - Previous pane
|
|
114
|
+
- `n` - Name current pane
|
|
115
|
+
|
|
116
|
+
*Filtering & View:*
|
|
117
|
+
- `/` - Enter text filter mode
|
|
118
|
+
- `c` - Cycle color filter (red/yellow/green/blue/cyan/magenta/none)
|
|
119
|
+
- `f` - Filter to selected process only
|
|
120
|
+
- `Esc` - Clear filters
|
|
121
|
+
- `p` - Pause/resume output scrolling
|
|
122
|
+
- `#` - Toggle line numbers
|
|
123
|
+
- `t` - Toggle timestamps
|
|
124
|
+
|
|
125
|
+
*Navigation:*
|
|
126
|
+
- `↑`/`↓` or `k`/`j` - Select process (vim-style)
|
|
127
|
+
- `←`/`→` or `h`/`l` - Select process (vim-style)
|
|
128
|
+
- `Home` - Scroll to top of pane
|
|
129
|
+
- `End` - Scroll to bottom of pane
|
|
130
|
+
- `Page Up` - Scroll up one page
|
|
131
|
+
- `Page Down` - Scroll down one page
|
|
132
|
+
- `Mouse wheel` - Scroll output
|
|
133
|
+
|
|
134
|
+
*Other:*
|
|
135
|
+
- `o` - Open settings
|
|
136
|
+
- `q` - Quit (stops all processes)
|
|
137
|
+
- `Ctrl+C` - Force quit
|
|
138
|
+
|
|
139
|
+
**Settings Screen:**
|
|
140
|
+
- `Tab`/`←`/`→` - Switch sections (Ignore/Include/Scripts)
|
|
141
|
+
- `↑`/`↓` - Navigate items
|
|
142
|
+
- `a` - Add new pattern (Ignore/Include sections)
|
|
143
|
+
- `d` or `Backspace` - Delete pattern
|
|
144
|
+
- `Space` or `Enter` - Toggle script (Scripts section)
|
|
145
|
+
- `Esc` or `q` - Return to previous screen
|
|
101
146
|
|
|
102
147
|
## Why Build This?
|
|
103
148
|
|
package/index.js
CHANGED
|
@@ -15,6 +15,16 @@ const APP_VERSION = 'v0.0.4';
|
|
|
15
15
|
// Detect if running inside VS Code's integrated terminal
|
|
16
16
|
const IS_VSCODE = process.env.TERM_PROGRAM === 'vscode';
|
|
17
17
|
|
|
18
|
+
// VSCode-specific optimizations
|
|
19
|
+
const VSCODE_CONFIG = {
|
|
20
|
+
// VSCode terminal has better mouse support
|
|
21
|
+
enhancedMouse: IS_VSCODE,
|
|
22
|
+
// VSCode can detect and linkify file paths (file:///path/to/file.js:line:col)
|
|
23
|
+
fileLinking: IS_VSCODE,
|
|
24
|
+
// Some key combinations are captured by VSCode
|
|
25
|
+
remapKeys: IS_VSCODE,
|
|
26
|
+
};
|
|
27
|
+
|
|
18
28
|
// Pane ID generator
|
|
19
29
|
let paneIdCounter = 0;
|
|
20
30
|
function generatePaneId() {
|
|
@@ -337,9 +347,13 @@ class ProcessManager {
|
|
|
337
347
|
this.isFilterMode = false; // Whether in filter input mode
|
|
338
348
|
this.isNamingMode = false; // Whether in pane naming input mode
|
|
339
349
|
this.namingModeText = ''; // Text being typed for pane name
|
|
350
|
+
this.showLineNumbers = this.config.showLineNumbers !== undefined ? this.config.showLineNumbers : true; // Whether to show line numbers
|
|
351
|
+
this.showTimestamps = this.config.showTimestamps !== undefined ? this.config.showTimestamps : false; // Whether to show timestamps
|
|
352
|
+
this.isInputMode = false; // Whether in stdin input mode
|
|
353
|
+
this.inputModeText = ''; // Text being typed for stdin
|
|
340
354
|
|
|
341
355
|
// Settings menu state
|
|
342
|
-
this.settingsSection = '
|
|
356
|
+
this.settingsSection = 'display'; // 'display' | 'ignore' | 'include' | 'scripts'
|
|
343
357
|
this.settingsIndex = 0; // Current selection index within section
|
|
344
358
|
this.isAddingPattern = false; // Whether typing a new pattern
|
|
345
359
|
this.newPatternText = ''; // Text being typed for new pattern
|
|
@@ -427,10 +441,25 @@ class ProcessManager {
|
|
|
427
441
|
clearInterval(this.countdownInterval);
|
|
428
442
|
this.previousPhase = 'selection';
|
|
429
443
|
this.phase = 'settings';
|
|
430
|
-
this.settingsSection = '
|
|
444
|
+
this.settingsSection = 'display';
|
|
431
445
|
this.settingsIndex = 0;
|
|
432
446
|
this.buildSettingsUI();
|
|
433
447
|
return;
|
|
448
|
+
} else if (keyName >= '1' && keyName <= '9') {
|
|
449
|
+
// Toggle script by number (1-9)
|
|
450
|
+
const index = parseInt(keyName) - 1;
|
|
451
|
+
if (index >= 0 && index < this.scripts.length) {
|
|
452
|
+
const scriptName = this.scripts[index]?.name;
|
|
453
|
+
if (scriptName) {
|
|
454
|
+
if (this.selectedScripts.has(scriptName)) {
|
|
455
|
+
this.selectedScripts.delete(scriptName);
|
|
456
|
+
} else {
|
|
457
|
+
this.selectedScripts.add(scriptName);
|
|
458
|
+
}
|
|
459
|
+
// Reset countdown when selection changes
|
|
460
|
+
this.countdown = COUNTDOWN_SECONDS;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
434
463
|
}
|
|
435
464
|
} else if (this.phase === 'settings') {
|
|
436
465
|
this.handleSettingsInput(keyName, keyEvent);
|
|
@@ -442,8 +471,34 @@ class ProcessManager {
|
|
|
442
471
|
return;
|
|
443
472
|
}
|
|
444
473
|
|
|
474
|
+
// If in input mode (stdin), handle stdin input
|
|
475
|
+
if (this.isInputMode) {
|
|
476
|
+
const scriptName = this.scripts[this.selectedIndex]?.name;
|
|
477
|
+
if (keyName === 'escape') {
|
|
478
|
+
this.isInputMode = false;
|
|
479
|
+
this.inputModeText = '';
|
|
480
|
+
this.buildRunningUI();
|
|
481
|
+
} else if (keyName === 'enter' || keyName === 'return') {
|
|
482
|
+
// Send the input to the selected process
|
|
483
|
+
if (scriptName && this.inputModeText.trim()) {
|
|
484
|
+
this.sendInputToProcess(scriptName, this.inputModeText + '\n');
|
|
485
|
+
}
|
|
486
|
+
this.isInputMode = false;
|
|
487
|
+
this.inputModeText = '';
|
|
488
|
+
this.buildRunningUI();
|
|
489
|
+
} else if (keyName === 'backspace') {
|
|
490
|
+
this.inputModeText = this.inputModeText.slice(0, -1);
|
|
491
|
+
this.buildRunningUI();
|
|
492
|
+
} else if (keyName === 'space') {
|
|
493
|
+
this.inputModeText += ' ';
|
|
494
|
+
this.buildRunningUI();
|
|
495
|
+
} else if (keyName && keyName.length === 1 && !keyEvent.ctrl && !keyEvent.meta) {
|
|
496
|
+
this.inputModeText += keyName;
|
|
497
|
+
this.buildRunningUI();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
445
500
|
// If in naming mode, handle name input
|
|
446
|
-
if (this.isNamingMode) {
|
|
501
|
+
else if (this.isNamingMode) {
|
|
447
502
|
const pane = findPaneById(this.paneRoot, this.focusedPaneId);
|
|
448
503
|
if (keyName === 'escape') {
|
|
449
504
|
this.isNamingMode = false;
|
|
@@ -549,12 +604,15 @@ class ProcessManager {
|
|
|
549
604
|
this.selectedIndex = Math.min(this.scripts.length - 1, this.selectedIndex + 1);
|
|
550
605
|
this.buildRunningUI(); // Rebuild to show selection change
|
|
551
606
|
} else if (keyName === 'left' || keyName === 'h') {
|
|
552
|
-
// Navigate processes left
|
|
553
|
-
this.selectedIndex =
|
|
607
|
+
// Navigate processes left with wrapping
|
|
608
|
+
this.selectedIndex = this.selectedIndex - 1;
|
|
609
|
+
if (this.selectedIndex < 0) {
|
|
610
|
+
this.selectedIndex = this.scripts.length - 1;
|
|
611
|
+
}
|
|
554
612
|
this.buildRunningUI(); // Rebuild to show selection change
|
|
555
613
|
} else if (keyName === 'right' || keyName === 'l') {
|
|
556
|
-
// Navigate processes right
|
|
557
|
-
this.selectedIndex =
|
|
614
|
+
// Navigate processes right with wrapping
|
|
615
|
+
this.selectedIndex = (this.selectedIndex + 1) % this.scripts.length;
|
|
558
616
|
this.buildRunningUI(); // Rebuild to show selection change
|
|
559
617
|
} else if (keyName === 'r') {
|
|
560
618
|
const scriptName = this.scripts[this.selectedIndex]?.name;
|
|
@@ -568,13 +626,13 @@ class ProcessManager {
|
|
|
568
626
|
this.toggleProcess(scriptName);
|
|
569
627
|
}
|
|
570
628
|
} else if (keyName === 'o') {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
629
|
+
// Open settings (options)
|
|
630
|
+
this.previousPhase = 'running';
|
|
631
|
+
this.phase = 'settings';
|
|
632
|
+
this.settingsSection = 'display';
|
|
633
|
+
this.settingsIndex = 0;
|
|
634
|
+
this.buildSettingsUI();
|
|
635
|
+
return;
|
|
578
636
|
} else if (keyName === 'c') {
|
|
579
637
|
// Cycle color filter on focused pane
|
|
580
638
|
const pane = findPaneById(this.paneRoot, this.focusedPaneId);
|
|
@@ -592,6 +650,35 @@ class ProcessManager {
|
|
|
592
650
|
// Navigate to previous pane
|
|
593
651
|
this.navigateToNextPane(-1);
|
|
594
652
|
this.buildRunningUI();
|
|
653
|
+
} else if (keyName === 'home') {
|
|
654
|
+
// Scroll to top of focused pane
|
|
655
|
+
this.scrollFocusedPane('home');
|
|
656
|
+
} else if (keyName === 'end') {
|
|
657
|
+
// Scroll to bottom of focused pane
|
|
658
|
+
this.scrollFocusedPane('end');
|
|
659
|
+
} else if (keyName === 'pageup') {
|
|
660
|
+
// Scroll up one page in focused pane
|
|
661
|
+
this.scrollFocusedPane('pageup');
|
|
662
|
+
} else if (keyName === 'pagedown') {
|
|
663
|
+
// Scroll down one page in focused pane
|
|
664
|
+
this.scrollFocusedPane('pagedown');
|
|
665
|
+
} else if (keyName >= '1' && keyName <= '9') {
|
|
666
|
+
// Toggle process by number (1-9)
|
|
667
|
+
const index = parseInt(keyName) - 1;
|
|
668
|
+
if (index >= 0 && index < this.scripts.length) {
|
|
669
|
+
this.selectedIndex = index;
|
|
670
|
+
this.toggleProcessVisibility();
|
|
671
|
+
this.buildRunningUI();
|
|
672
|
+
}
|
|
673
|
+
} else if (keyName === 'i') {
|
|
674
|
+
// Enter input mode to send stdin to selected process
|
|
675
|
+
const scriptName = this.scripts[this.selectedIndex]?.name;
|
|
676
|
+
const proc = this.processes.get(scriptName);
|
|
677
|
+
if (scriptName && proc?.status === 'running') {
|
|
678
|
+
this.isInputMode = true;
|
|
679
|
+
this.inputModeText = '';
|
|
680
|
+
this.buildRunningUI();
|
|
681
|
+
}
|
|
595
682
|
}
|
|
596
683
|
}
|
|
597
684
|
}
|
|
@@ -788,6 +875,19 @@ class ProcessManager {
|
|
|
788
875
|
this.startProcess(scriptName);
|
|
789
876
|
}
|
|
790
877
|
}
|
|
878
|
+
|
|
879
|
+
sendInputToProcess(scriptName, input) {
|
|
880
|
+
const proc = this.processRefs.get(scriptName);
|
|
881
|
+
if (proc && proc.stdin && proc.stdin.writable) {
|
|
882
|
+
try {
|
|
883
|
+
proc.stdin.write(input);
|
|
884
|
+
// Echo the input in the output for visibility
|
|
885
|
+
this.addOutputLine(scriptName, `> ${input.trim()}`);
|
|
886
|
+
} catch (err) {
|
|
887
|
+
this.addOutputLine(scriptName, `Error sending input: ${err.message}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
791
891
|
|
|
792
892
|
handleSettingsInput(keyName, keyEvent) {
|
|
793
893
|
// Handle text input mode for adding patterns
|
|
@@ -824,6 +924,14 @@ class ProcessManager {
|
|
|
824
924
|
|
|
825
925
|
// Normal settings navigation
|
|
826
926
|
if (keyName === 'escape' || keyName === 'q') {
|
|
927
|
+
// Apply filters before returning (updates this.scripts)
|
|
928
|
+
this.applyFilters();
|
|
929
|
+
|
|
930
|
+
// Ensure selectedIndex is within bounds after filter changes
|
|
931
|
+
if (this.selectedIndex >= this.scripts.length) {
|
|
932
|
+
this.selectedIndex = Math.max(0, this.scripts.length - 1);
|
|
933
|
+
}
|
|
934
|
+
|
|
827
935
|
// Return to previous phase
|
|
828
936
|
if (this.previousPhase === 'running') {
|
|
829
937
|
this.phase = 'running';
|
|
@@ -836,46 +944,76 @@ class ProcessManager {
|
|
|
836
944
|
}
|
|
837
945
|
} else if (keyName === 'tab' || keyName === 'right') {
|
|
838
946
|
// Switch section
|
|
839
|
-
const sections = ['ignore', 'include', 'scripts'];
|
|
947
|
+
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
840
948
|
const idx = sections.indexOf(this.settingsSection);
|
|
841
949
|
this.settingsSection = sections[(idx + 1) % sections.length];
|
|
842
950
|
this.settingsIndex = 0;
|
|
843
951
|
this.buildSettingsUI();
|
|
844
952
|
} else if (keyEvent.shift && keyName === 'tab') {
|
|
845
953
|
// Switch section backwards
|
|
846
|
-
const sections = ['ignore', 'include', 'scripts'];
|
|
954
|
+
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
847
955
|
const idx = sections.indexOf(this.settingsSection);
|
|
848
956
|
this.settingsSection = sections[(idx - 1 + sections.length) % sections.length];
|
|
849
957
|
this.settingsIndex = 0;
|
|
850
958
|
this.buildSettingsUI();
|
|
851
959
|
} else if (keyName === 'left') {
|
|
852
960
|
// Switch section backwards
|
|
853
|
-
const sections = ['ignore', 'include', 'scripts'];
|
|
961
|
+
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
854
962
|
const idx = sections.indexOf(this.settingsSection);
|
|
855
963
|
this.settingsSection = sections[(idx - 1 + sections.length) % sections.length];
|
|
856
964
|
this.settingsIndex = 0;
|
|
857
965
|
this.buildSettingsUI();
|
|
858
966
|
} else if (keyName === 'up') {
|
|
859
|
-
|
|
860
|
-
|
|
967
|
+
if (this.settingsIndex > 0) {
|
|
968
|
+
this.settingsIndex--;
|
|
969
|
+
this.buildSettingsUI();
|
|
970
|
+
} else {
|
|
971
|
+
// Move to previous section
|
|
972
|
+
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
973
|
+
const idx = sections.indexOf(this.settingsSection);
|
|
974
|
+
if (idx > 0) {
|
|
975
|
+
this.settingsSection = sections[idx - 1];
|
|
976
|
+
this.settingsIndex = this.getSettingsMaxIndex();
|
|
977
|
+
this.buildSettingsUI();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
861
980
|
} else if (keyName === 'down') {
|
|
862
981
|
const maxIndex = this.getSettingsMaxIndex();
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
} else if (keyName === 'a') {
|
|
866
|
-
// Add new pattern (only for ignore/include sections)
|
|
867
|
-
if (this.settingsSection === 'ignore' || this.settingsSection === 'include') {
|
|
868
|
-
this.isAddingPattern = true;
|
|
869
|
-
this.newPatternText = '';
|
|
982
|
+
if (this.settingsIndex < maxIndex) {
|
|
983
|
+
this.settingsIndex++;
|
|
870
984
|
this.buildSettingsUI();
|
|
985
|
+
} else {
|
|
986
|
+
// Move to next section
|
|
987
|
+
const sections = ['display', 'ignore', 'include', 'scripts'];
|
|
988
|
+
const idx = sections.indexOf(this.settingsSection);
|
|
989
|
+
if (idx < sections.length - 1) {
|
|
990
|
+
this.settingsSection = sections[idx + 1];
|
|
991
|
+
this.settingsIndex = 0;
|
|
992
|
+
this.buildSettingsUI();
|
|
993
|
+
}
|
|
871
994
|
}
|
|
995
|
+
} else if (keyName === 'i') {
|
|
996
|
+
// Add new ignore pattern
|
|
997
|
+
this.settingsSection = 'ignore';
|
|
998
|
+
this.isAddingPattern = true;
|
|
999
|
+
this.newPatternText = '';
|
|
1000
|
+
this.buildSettingsUI();
|
|
1001
|
+
} else if (keyName === 'n') {
|
|
1002
|
+
// Add new include pattern
|
|
1003
|
+
this.settingsSection = 'include';
|
|
1004
|
+
this.isAddingPattern = true;
|
|
1005
|
+
this.newPatternText = '';
|
|
1006
|
+
this.buildSettingsUI();
|
|
872
1007
|
} else if (keyName === 'd' || keyName === 'backspace') {
|
|
873
|
-
// Delete selected pattern
|
|
1008
|
+
// Delete selected pattern
|
|
874
1009
|
this.deleteSelectedItem();
|
|
875
1010
|
this.buildSettingsUI();
|
|
876
1011
|
} else if (keyName === 'space' || keyName === 'enter' || keyName === 'return') {
|
|
877
|
-
// Toggle
|
|
878
|
-
if (this.settingsSection === '
|
|
1012
|
+
// Toggle display options, script visibility
|
|
1013
|
+
if (this.settingsSection === 'display') {
|
|
1014
|
+
this.toggleDisplayOption();
|
|
1015
|
+
this.buildSettingsUI();
|
|
1016
|
+
} else if (this.settingsSection === 'scripts') {
|
|
879
1017
|
this.toggleScriptIgnore();
|
|
880
1018
|
this.buildSettingsUI();
|
|
881
1019
|
}
|
|
@@ -883,16 +1021,31 @@ class ProcessManager {
|
|
|
883
1021
|
}
|
|
884
1022
|
|
|
885
1023
|
getSettingsMaxIndex() {
|
|
886
|
-
if (this.settingsSection === '
|
|
887
|
-
return
|
|
1024
|
+
if (this.settingsSection === 'display') {
|
|
1025
|
+
return 1; // 2 display options (line numbers, timestamps)
|
|
1026
|
+
} else if (this.settingsSection === 'ignore') {
|
|
1027
|
+
const count = this.config.ignore?.length || 0;
|
|
1028
|
+
return count > 0 ? count - 1 : 0;
|
|
888
1029
|
} else if (this.settingsSection === 'include') {
|
|
889
|
-
|
|
1030
|
+
const count = this.config.include?.length || 0;
|
|
1031
|
+
return count > 0 ? count - 1 : 0;
|
|
890
1032
|
} else if (this.settingsSection === 'scripts') {
|
|
891
1033
|
return Math.max(0, this.allScripts.length - 1);
|
|
892
1034
|
}
|
|
893
1035
|
return 0;
|
|
894
1036
|
}
|
|
895
1037
|
|
|
1038
|
+
toggleDisplayOption() {
|
|
1039
|
+
if (this.settingsIndex === 0) {
|
|
1040
|
+
this.showLineNumbers = !this.showLineNumbers;
|
|
1041
|
+
this.config.showLineNumbers = this.showLineNumbers;
|
|
1042
|
+
} else if (this.settingsIndex === 1) {
|
|
1043
|
+
this.showTimestamps = !this.showTimestamps;
|
|
1044
|
+
this.config.showTimestamps = this.showTimestamps;
|
|
1045
|
+
}
|
|
1046
|
+
saveConfig(this.config);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
896
1049
|
deleteSelectedItem() {
|
|
897
1050
|
if (this.settingsSection === 'ignore' && this.config.ignore?.length > 0) {
|
|
898
1051
|
this.config.ignore.splice(this.settingsIndex, 1);
|
|
@@ -1027,6 +1180,9 @@ class ProcessManager {
|
|
|
1027
1180
|
items.push({ label: 'Previous Pane', shortcut: 'Shift+Tab', action: () => this.navigateToNextPane(-1) });
|
|
1028
1181
|
}
|
|
1029
1182
|
|
|
1183
|
+
items.push({ label: 'Toggle Line Numbers', shortcut: '#', action: () => { this.showLineNumbers = !this.showLineNumbers; } });
|
|
1184
|
+
items.push({ label: 'Toggle Timestamps', shortcut: 't', action: () => { this.showTimestamps = !this.showTimestamps; } });
|
|
1185
|
+
|
|
1030
1186
|
return items;
|
|
1031
1187
|
}
|
|
1032
1188
|
|
|
@@ -1178,6 +1334,42 @@ class ProcessManager {
|
|
|
1178
1334
|
saveConfig(this.config);
|
|
1179
1335
|
}
|
|
1180
1336
|
|
|
1337
|
+
// Scroll the focused pane
|
|
1338
|
+
scrollFocusedPane(direction) {
|
|
1339
|
+
if (!this.focusedPaneId) return;
|
|
1340
|
+
|
|
1341
|
+
const scrollBox = this.paneScrollBoxes.get(this.focusedPaneId);
|
|
1342
|
+
if (!scrollBox || !scrollBox.scrollTo) return;
|
|
1343
|
+
|
|
1344
|
+
const currentY = scrollBox.scrollTop || 0;
|
|
1345
|
+
const viewportHeight = scrollBox.height || 20;
|
|
1346
|
+
const contentHeight = scrollBox.contentHeight || 0;
|
|
1347
|
+
|
|
1348
|
+
let newY = currentY;
|
|
1349
|
+
|
|
1350
|
+
if (direction === 'home') {
|
|
1351
|
+
newY = 0;
|
|
1352
|
+
} else if (direction === 'end') {
|
|
1353
|
+
newY = Number.MAX_SAFE_INTEGER;
|
|
1354
|
+
} else if (direction === 'pageup') {
|
|
1355
|
+
newY = Math.max(0, currentY - viewportHeight);
|
|
1356
|
+
} else if (direction === 'pagedown') {
|
|
1357
|
+
newY = Math.min(contentHeight - viewportHeight, currentY + viewportHeight);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
scrollBox.scrollTo({ x: 0, y: newY });
|
|
1361
|
+
|
|
1362
|
+
// Save the new scroll position
|
|
1363
|
+
this.paneScrollPositions.set(this.focusedPaneId, { x: 0, y: newY });
|
|
1364
|
+
|
|
1365
|
+
// Auto-pause when manually scrolling (unless going to end)
|
|
1366
|
+
if (direction !== 'end' && !this.isPaused) {
|
|
1367
|
+
this.isPaused = true;
|
|
1368
|
+
this.updateStreamPauseState();
|
|
1369
|
+
this.buildRunningUI();
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1181
1373
|
// Check if a process is visible in the focused pane
|
|
1182
1374
|
isProcessVisibleInPane(scriptName, pane) {
|
|
1183
1375
|
if (!pane) return true;
|
|
@@ -1302,54 +1494,82 @@ class ProcessManager {
|
|
|
1302
1494
|
this.settingsContainer.add(inputBar);
|
|
1303
1495
|
}
|
|
1304
1496
|
|
|
1305
|
-
//
|
|
1306
|
-
const
|
|
1307
|
-
id: '
|
|
1497
|
+
// Combined content panel with all sections
|
|
1498
|
+
const contentPanel = new BoxRenderable(this.renderer, {
|
|
1499
|
+
id: 'content-panel',
|
|
1308
1500
|
flexDirection: 'row',
|
|
1309
|
-
|
|
1310
|
-
|
|
1501
|
+
flexGrow: 1,
|
|
1502
|
+
gap: 1,
|
|
1311
1503
|
});
|
|
1312
1504
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
sections.forEach(({ id, label }) => {
|
|
1320
|
-
const isActive = this.settingsSection === id;
|
|
1321
|
-
const tab = new TextRenderable(this.renderer, {
|
|
1322
|
-
id: `tab-${id}`,
|
|
1323
|
-
content: isActive
|
|
1324
|
-
? t`${fg(COLORS.accent)('[' + label + ']')}`
|
|
1325
|
-
: t`${fg(COLORS.textDim)(' ' + label + ' ')}`,
|
|
1326
|
-
});
|
|
1327
|
-
tabsContainer.add(tab);
|
|
1505
|
+
// Left column - Display options, Ignore, Include
|
|
1506
|
+
const leftColumn = new BoxRenderable(this.renderer, {
|
|
1507
|
+
id: 'left-column',
|
|
1508
|
+
flexDirection: 'column',
|
|
1509
|
+
flexGrow: 1,
|
|
1510
|
+
gap: 1,
|
|
1328
1511
|
});
|
|
1329
1512
|
|
|
1330
|
-
|
|
1513
|
+
// Display options section
|
|
1514
|
+
const displayBox = new BoxRenderable(this.renderer, {
|
|
1515
|
+
id: 'display-box',
|
|
1516
|
+
flexDirection: 'column',
|
|
1517
|
+
border: true,
|
|
1518
|
+
borderStyle: 'rounded',
|
|
1519
|
+
borderColor: this.settingsSection === 'display' ? COLORS.borderFocused : COLORS.border,
|
|
1520
|
+
title: ' Display Options ',
|
|
1521
|
+
titleAlignment: 'left',
|
|
1522
|
+
padding: 1,
|
|
1523
|
+
});
|
|
1524
|
+
this.buildDisplaySectionContent(displayBox);
|
|
1525
|
+
leftColumn.add(displayBox);
|
|
1331
1526
|
|
|
1332
|
-
//
|
|
1333
|
-
const
|
|
1334
|
-
id: '
|
|
1527
|
+
// Ignore patterns section
|
|
1528
|
+
const ignoreBox = new BoxRenderable(this.renderer, {
|
|
1529
|
+
id: 'ignore-box',
|
|
1335
1530
|
flexDirection: 'column',
|
|
1336
1531
|
border: true,
|
|
1337
1532
|
borderStyle: 'rounded',
|
|
1338
|
-
borderColor: COLORS.border,
|
|
1339
|
-
title:
|
|
1533
|
+
borderColor: this.settingsSection === 'ignore' ? COLORS.borderFocused : COLORS.border,
|
|
1534
|
+
title: ' Ignore Patterns (i) ',
|
|
1340
1535
|
titleAlignment: 'left',
|
|
1536
|
+
padding: 1,
|
|
1341
1537
|
flexGrow: 1,
|
|
1538
|
+
});
|
|
1539
|
+
this.buildIgnoreSectionContent(ignoreBox);
|
|
1540
|
+
leftColumn.add(ignoreBox);
|
|
1541
|
+
|
|
1542
|
+
// Include patterns section
|
|
1543
|
+
const includeBox = new BoxRenderable(this.renderer, {
|
|
1544
|
+
id: 'include-box',
|
|
1545
|
+
flexDirection: 'column',
|
|
1546
|
+
border: true,
|
|
1547
|
+
borderStyle: 'rounded',
|
|
1548
|
+
borderColor: this.settingsSection === 'include' ? COLORS.borderFocused : COLORS.border,
|
|
1549
|
+
title: ' Include Patterns (n) ',
|
|
1550
|
+
titleAlignment: 'left',
|
|
1342
1551
|
padding: 1,
|
|
1552
|
+
flexGrow: 1,
|
|
1343
1553
|
});
|
|
1554
|
+
this.buildIncludeSectionContent(includeBox);
|
|
1555
|
+
leftColumn.add(includeBox);
|
|
1344
1556
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1557
|
+
contentPanel.add(leftColumn);
|
|
1558
|
+
|
|
1559
|
+
// Right column - Scripts list
|
|
1560
|
+
const scriptsBox = new BoxRenderable(this.renderer, {
|
|
1561
|
+
id: 'scripts-box',
|
|
1562
|
+
flexDirection: 'column',
|
|
1563
|
+
border: true,
|
|
1564
|
+
borderStyle: 'rounded',
|
|
1565
|
+
borderColor: this.settingsSection === 'scripts' ? COLORS.borderFocused : COLORS.border,
|
|
1566
|
+
title: ' Scripts ',
|
|
1567
|
+
titleAlignment: 'left',
|
|
1568
|
+
flexGrow: 2,
|
|
1569
|
+
padding: 1,
|
|
1570
|
+
});
|
|
1571
|
+
this.buildScriptsSectionContent(scriptsBox);
|
|
1572
|
+
contentPanel.add(scriptsBox);
|
|
1353
1573
|
|
|
1354
1574
|
this.settingsContainer.add(contentPanel);
|
|
1355
1575
|
|
|
@@ -1373,9 +1593,10 @@ class ProcessManager {
|
|
|
1373
1593
|
]
|
|
1374
1594
|
: [
|
|
1375
1595
|
{ key: 'tab', desc: 'section' },
|
|
1376
|
-
{ key: 'a', desc: 'add' },
|
|
1377
|
-
{ key: 'd', desc: 'delete' },
|
|
1378
1596
|
{ key: 'space', desc: 'toggle' },
|
|
1597
|
+
{ key: 'i', desc: 'add ignore' },
|
|
1598
|
+
{ key: 'n', desc: 'add include' },
|
|
1599
|
+
{ key: 'd', desc: 'delete' },
|
|
1379
1600
|
{ key: 'esc', desc: 'back' },
|
|
1380
1601
|
];
|
|
1381
1602
|
|
|
@@ -1392,26 +1613,38 @@ class ProcessManager {
|
|
|
1392
1613
|
this.renderer.root.add(this.settingsContainer);
|
|
1393
1614
|
}
|
|
1394
1615
|
|
|
1395
|
-
|
|
1396
|
-
const
|
|
1397
|
-
id: '
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
container.add(desc);
|
|
1401
|
-
|
|
1402
|
-
container.add(new TextRenderable(this.renderer, { id: 'spacer', content: '' }));
|
|
1616
|
+
buildDisplaySectionContent(container) {
|
|
1617
|
+
const options = [
|
|
1618
|
+
{ id: 'lineNumbers', label: 'Show Line Numbers', value: this.showLineNumbers },
|
|
1619
|
+
{ id: 'timestamps', label: 'Show Timestamps', value: this.showTimestamps },
|
|
1620
|
+
];
|
|
1403
1621
|
|
|
1622
|
+
options.forEach((option, idx) => {
|
|
1623
|
+
const isFocused = this.settingsSection === 'display' && idx === this.settingsIndex;
|
|
1624
|
+
const indicator = isFocused ? '>' : ' ';
|
|
1625
|
+
const checkbox = option.value ? '[x]' : '[ ]';
|
|
1626
|
+
const checkColor = option.value ? COLORS.success : COLORS.textDim;
|
|
1627
|
+
|
|
1628
|
+
const line = new TextRenderable(this.renderer, {
|
|
1629
|
+
id: `display-option-${idx}`,
|
|
1630
|
+
content: t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(checkColor)(checkbox)} ${fg(COLORS.text)(option.label)}`,
|
|
1631
|
+
});
|
|
1632
|
+
container.add(line);
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
buildIgnoreSectionContent(container) {
|
|
1404
1637
|
const patterns = this.config.ignore || [];
|
|
1405
1638
|
|
|
1406
1639
|
if (patterns.length === 0) {
|
|
1407
1640
|
const empty = new TextRenderable(this.renderer, {
|
|
1408
1641
|
id: 'ignore-empty',
|
|
1409
|
-
content: t`${fg(COLORS.textDim)('
|
|
1642
|
+
content: t`${fg(COLORS.textDim)('Press i to add')}`,
|
|
1410
1643
|
});
|
|
1411
1644
|
container.add(empty);
|
|
1412
1645
|
} else {
|
|
1413
1646
|
patterns.forEach((pattern, idx) => {
|
|
1414
|
-
const isFocused = idx === this.settingsIndex;
|
|
1647
|
+
const isFocused = this.settingsSection === 'ignore' && idx === this.settingsIndex;
|
|
1415
1648
|
const indicator = isFocused ? '>' : ' ';
|
|
1416
1649
|
|
|
1417
1650
|
const line = new TextRenderable(this.renderer, {
|
|
@@ -1424,25 +1657,17 @@ class ProcessManager {
|
|
|
1424
1657
|
}
|
|
1425
1658
|
|
|
1426
1659
|
buildIncludeSectionContent(container) {
|
|
1427
|
-
const desc = new TextRenderable(this.renderer, {
|
|
1428
|
-
id: 'include-desc',
|
|
1429
|
-
content: t`${fg(COLORS.textDim)('Only show scripts matching these patterns. Use * as wildcard.')}`,
|
|
1430
|
-
});
|
|
1431
|
-
container.add(desc);
|
|
1432
|
-
|
|
1433
|
-
container.add(new TextRenderable(this.renderer, { id: 'spacer', content: '' }));
|
|
1434
|
-
|
|
1435
1660
|
const patterns = this.config.include || [];
|
|
1436
1661
|
|
|
1437
1662
|
if (patterns.length === 0) {
|
|
1438
1663
|
const empty = new TextRenderable(this.renderer, {
|
|
1439
1664
|
id: 'include-empty',
|
|
1440
|
-
content: t`${fg(COLORS.textDim)('
|
|
1665
|
+
content: t`${fg(COLORS.textDim)('Press n to add')}`,
|
|
1441
1666
|
});
|
|
1442
1667
|
container.add(empty);
|
|
1443
1668
|
} else {
|
|
1444
1669
|
patterns.forEach((pattern, idx) => {
|
|
1445
|
-
const isFocused = idx === this.settingsIndex;
|
|
1670
|
+
const isFocused = this.settingsSection === 'include' && idx === this.settingsIndex;
|
|
1446
1671
|
const indicator = isFocused ? '>' : ' ';
|
|
1447
1672
|
|
|
1448
1673
|
const line = new TextRenderable(this.renderer, {
|
|
@@ -1455,19 +1680,11 @@ class ProcessManager {
|
|
|
1455
1680
|
}
|
|
1456
1681
|
|
|
1457
1682
|
buildScriptsSectionContent(container) {
|
|
1458
|
-
const desc = new TextRenderable(this.renderer, {
|
|
1459
|
-
id: 'scripts-desc',
|
|
1460
|
-
content: t`${fg(COLORS.textDim)('Toggle individual scripts. Ignored scripts are hidden from selection.')}`,
|
|
1461
|
-
});
|
|
1462
|
-
container.add(desc);
|
|
1463
|
-
|
|
1464
|
-
container.add(new TextRenderable(this.renderer, { id: 'spacer', content: '' }));
|
|
1465
|
-
|
|
1466
1683
|
const ignorePatterns = this.config.ignore || [];
|
|
1467
1684
|
|
|
1468
1685
|
this.allScripts.forEach((script, idx) => {
|
|
1469
1686
|
const isIgnored = ignorePatterns.includes(script.name);
|
|
1470
|
-
const isFocused = idx === this.settingsIndex;
|
|
1687
|
+
const isFocused = this.settingsSection === 'scripts' && idx === this.settingsIndex;
|
|
1471
1688
|
const indicator = isFocused ? '>' : ' ';
|
|
1472
1689
|
const checkbox = isIgnored ? '[x]' : '[ ]';
|
|
1473
1690
|
const checkColor = isIgnored ? COLORS.error : COLORS.success;
|
|
@@ -1563,14 +1780,22 @@ class ProcessManager {
|
|
|
1563
1780
|
this.scriptLines = this.scripts.map((script, index) => {
|
|
1564
1781
|
const isSelected = this.selectedScripts.has(script.name);
|
|
1565
1782
|
const isFocused = index === this.selectedIndex;
|
|
1566
|
-
const checkIcon = isSelected ? '●' : '○';
|
|
1567
|
-
const checkColor = isSelected ? COLORS.success : COLORS.textDim;
|
|
1568
1783
|
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
1569
1784
|
const nameColor = isFocused ? COLORS.text : processColor;
|
|
1785
|
+
const numberColor = processColor;
|
|
1786
|
+
const bracketColor = processColor;
|
|
1570
1787
|
const bgColor = isFocused ? COLORS.bgHighlight : null;
|
|
1571
1788
|
|
|
1572
|
-
//
|
|
1573
|
-
const
|
|
1789
|
+
// Show number for first 9 scripts
|
|
1790
|
+
const numberLabel = index < 9 ? ` ${index + 1}` : ' ';
|
|
1791
|
+
|
|
1792
|
+
// Build checkbox with colored brackets and white x (like running screen)
|
|
1793
|
+
let content;
|
|
1794
|
+
if (isSelected) {
|
|
1795
|
+
content = t`${fg(numberColor)(numberLabel)} ${fg(bracketColor)('[')}${fg(COLORS.text)('x')}${fg(bracketColor)(']')} ${fg(nameColor)(script.displayName)}`;
|
|
1796
|
+
} else {
|
|
1797
|
+
content = t`${fg(numberColor)(numberLabel)} ${fg(bracketColor)('[ ]')} ${fg(nameColor)(script.displayName)}`;
|
|
1798
|
+
}
|
|
1574
1799
|
|
|
1575
1800
|
const lineContainer = new BoxRenderable(this.renderer, {
|
|
1576
1801
|
id: `script-box-${index}`,
|
|
@@ -1835,11 +2060,25 @@ class ProcessManager {
|
|
|
1835
2060
|
const line = newLines[i];
|
|
1836
2061
|
const processColor = this.processColors.get(line.process) || COLORS.text;
|
|
1837
2062
|
const trimmedText = line.text.trim();
|
|
1838
|
-
|
|
2063
|
+
|
|
2064
|
+
// Build content with proper template literal
|
|
2065
|
+
const lineNumber = this.showLineNumbers ? String(line.lineNumber).padStart(4, ' ') : '';
|
|
2066
|
+
const timestamp = this.showTimestamps ? new Date(line.timestamp).toLocaleTimeString('en-US', { hour12: false }) : '';
|
|
2067
|
+
|
|
2068
|
+
let content;
|
|
2069
|
+
if (this.showLineNumbers && this.showTimestamps) {
|
|
2070
|
+
content = t`${fg(COLORS.textDim)(lineNumber)} ${fg(COLORS.textDim)(`[${timestamp}]`)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2071
|
+
} else if (this.showLineNumbers) {
|
|
2072
|
+
content = t`${fg(COLORS.textDim)(lineNumber)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2073
|
+
} else if (this.showTimestamps) {
|
|
2074
|
+
content = t`${fg(COLORS.textDim)(`[${timestamp}]`)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2075
|
+
} else {
|
|
2076
|
+
content = t`${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2077
|
+
}
|
|
1839
2078
|
|
|
1840
2079
|
const outputLine = new TextRenderable(this.renderer, {
|
|
1841
2080
|
id: `output-${pane.id}-${line.lineNumber}`,
|
|
1842
|
-
content:
|
|
2081
|
+
content: content,
|
|
1843
2082
|
bg: '#000000',
|
|
1844
2083
|
});
|
|
1845
2084
|
|
|
@@ -1883,10 +2122,25 @@ class ProcessManager {
|
|
|
1883
2122
|
|
|
1884
2123
|
// Trim whitespace and let text wrap naturally - ScrollBox will handle overflow
|
|
1885
2124
|
const trimmedText = line.text.trim();
|
|
1886
|
-
|
|
2125
|
+
|
|
2126
|
+
// Build content with proper template literal
|
|
2127
|
+
const lineNumber = this.showLineNumbers ? String(line.lineNumber).padStart(4, ' ') : '';
|
|
2128
|
+
const timestamp = this.showTimestamps ? new Date(line.timestamp).toLocaleTimeString('en-US', { hour12: false }) : '';
|
|
2129
|
+
|
|
2130
|
+
let content;
|
|
2131
|
+
if (this.showLineNumbers && this.showTimestamps) {
|
|
2132
|
+
content = t`${fg(COLORS.textDim)(lineNumber)} ${fg(COLORS.textDim)(`[${timestamp}]`)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2133
|
+
} else if (this.showLineNumbers) {
|
|
2134
|
+
content = t`${fg(COLORS.textDim)(lineNumber)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2135
|
+
} else if (this.showTimestamps) {
|
|
2136
|
+
content = t`${fg(COLORS.textDim)(`[${timestamp}]`)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2137
|
+
} else {
|
|
2138
|
+
content = t`${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
1887
2141
|
const outputLine = new TextRenderable(this.renderer, {
|
|
1888
2142
|
id: `output-${pane.id}-${i}`,
|
|
1889
|
-
content:
|
|
2143
|
+
content: content,
|
|
1890
2144
|
bg: '#000000', // Black background for pane content
|
|
1891
2145
|
});
|
|
1892
2146
|
|
|
@@ -2132,15 +2386,7 @@ class ProcessManager {
|
|
|
2132
2386
|
paddingLeft: 1,
|
|
2133
2387
|
});
|
|
2134
2388
|
|
|
2135
|
-
|
|
2136
|
-
const allPanes = getAllPaneIds(this.paneRoot);
|
|
2137
|
-
if (allPanes.length > 1) {
|
|
2138
|
-
const paneIndicator = new TextRenderable(this.renderer, {
|
|
2139
|
-
id: 'pane-indicator',
|
|
2140
|
-
content: t`${fg(COLORS.cyan)(`[${allPanes.length} panes]`)} `,
|
|
2141
|
-
});
|
|
2142
|
-
processBar.add(paneIndicator);
|
|
2143
|
-
}
|
|
2389
|
+
|
|
2144
2390
|
|
|
2145
2391
|
// Add each process with checkbox showing visibility in focused pane
|
|
2146
2392
|
const focusedPane = findPaneById(this.paneRoot, this.focusedPaneId);
|
|
@@ -2153,13 +2399,25 @@ class ProcessManager {
|
|
|
2153
2399
|
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
2154
2400
|
const isSelected = this.selectedIndex === index;
|
|
2155
2401
|
const isVisible = this.isProcessVisibleInPane(script.name, focusedPane);
|
|
2156
|
-
const checkbox = isVisible ? '[x]' : '[ ]';
|
|
2157
2402
|
const nameColor = isSelected ? COLORS.accent : (isVisible ? processColor : COLORS.textDim);
|
|
2403
|
+
const numberColor = isVisible ? processColor : COLORS.textDim;
|
|
2158
2404
|
const indicator = isSelected ? '>' : ' ';
|
|
2405
|
+
const bracketColor = isVisible ? processColor : COLORS.textDim;
|
|
2406
|
+
|
|
2407
|
+
// Show number for first 9 processes
|
|
2408
|
+
const numberLabel = index < 9 ? `${index + 1}` : ' ';
|
|
2409
|
+
|
|
2410
|
+
// Build content - can't nest template literals, so build entire thing at once
|
|
2411
|
+
let content;
|
|
2412
|
+
if (isVisible) {
|
|
2413
|
+
content = t`${fg(numberColor)(numberLabel)} ${fg(isSelected ? COLORS.accent : COLORS.textDim)(indicator)}${fg(bracketColor)('[')}${fg(COLORS.text)('x')}${fg(bracketColor)(']')} ${fg(statusColor)(statusIcon)} ${fg(nameColor)(script.displayName)}`;
|
|
2414
|
+
} else {
|
|
2415
|
+
content = t`${fg(numberColor)(numberLabel)} ${fg(isSelected ? COLORS.accent : COLORS.textDim)(indicator)}${fg(bracketColor)('[ ]')} ${fg(statusColor)(statusIcon)} ${fg(nameColor)(script.displayName)}`;
|
|
2416
|
+
}
|
|
2159
2417
|
|
|
2160
2418
|
const processItem = new TextRenderable(this.renderer, {
|
|
2161
2419
|
id: `process-item-${index}`,
|
|
2162
|
-
content:
|
|
2420
|
+
content: content,
|
|
2163
2421
|
});
|
|
2164
2422
|
processBar.add(processItem);
|
|
2165
2423
|
});
|
|
@@ -2230,6 +2488,17 @@ class ProcessManager {
|
|
|
2230
2488
|
leftSide.add(filterIndicator);
|
|
2231
2489
|
}
|
|
2232
2490
|
|
|
2491
|
+
// Input mode indicator if active
|
|
2492
|
+
if (this.isInputMode) {
|
|
2493
|
+
const scriptName = this.scripts[this.selectedIndex]?.displayName || '';
|
|
2494
|
+
const inputText = `[${scriptName}]> ${this.inputModeText}_`;
|
|
2495
|
+
const inputIndicator = new TextRenderable(this.renderer, {
|
|
2496
|
+
id: 'input-indicator',
|
|
2497
|
+
content: t`${fg(COLORS.success)(inputText)}`,
|
|
2498
|
+
});
|
|
2499
|
+
leftSide.add(inputIndicator);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2233
2502
|
// Color filter indicator if active on focused pane
|
|
2234
2503
|
if (focusedPane?.colorFilter) {
|
|
2235
2504
|
const colorMap = {
|
|
@@ -2258,13 +2527,15 @@ class ProcessManager {
|
|
|
2258
2527
|
|
|
2259
2528
|
const shortcuts = [
|
|
2260
2529
|
{ key: '\\', desc: 'panes', color: COLORS.cyan },
|
|
2261
|
-
{ key: '
|
|
2530
|
+
{ key: '1-9', desc: 'toggle', color: COLORS.success },
|
|
2531
|
+
{ key: 'i', desc: 'input', color: COLORS.success },
|
|
2262
2532
|
{ key: 'n', desc: 'name', color: COLORS.accent },
|
|
2263
2533
|
{ key: 'p', desc: 'pause', color: COLORS.warning },
|
|
2264
2534
|
{ key: '/', desc: 'filter', color: COLORS.cyan },
|
|
2265
2535
|
{ key: 'c', desc: 'color', color: COLORS.magenta },
|
|
2266
2536
|
{ key: 's', desc: 'stop', color: COLORS.error },
|
|
2267
2537
|
{ key: 'r', desc: 'restart', color: COLORS.success },
|
|
2538
|
+
{ key: 'o', desc: 'settings', color: COLORS.magenta },
|
|
2268
2539
|
{ key: 'q', desc: 'quit', color: COLORS.error },
|
|
2269
2540
|
];
|
|
2270
2541
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "startall",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"demo:worker2": "node -e \"setInterval(() => console.log('Processing jobs...'), 100)\"",
|
|
16
16
|
"demo:errors": "node -e \"setInterval(() => { console.log('\\x1b[32mOK: All good\\x1b[0m'); console.log('\\x1b[33mWARN: Deprecated API used\\x1b[0m'); console.log('\\x1b[31mERROR: Connection failed\\x1b[0m'); console.log('Normal log message'); }, 2000)\"",
|
|
17
17
|
"demo:build": "node -e \"let i=0; setInterval(() => { i++; if(i%5===0) console.log('\\x1b[31mError: Type mismatch in file.ts:42\\x1b[0m'); else if(i%3===0) console.log('\\x1b[33mWarning: Unused variable\\x1b[0m'); else console.log('\\x1b[36mCompiling module ' + i + '...\\x1b[0m'); }, 800)\"",
|
|
18
|
-
"demo:longtext": "node -e \"setInterval(() => { console.log('\\x1b[36m[2024-01-20T12:34:56.789Z] Executing SQL query: SELECT users.id, users.name, users.email, orders.order_id, orders.total, products.name FROM users INNER JOIN orders ON users.id = orders.user_id LEFT JOIN products ON orders.product_id = products.id WHERE users.created_at > NOW() - INTERVAL 30 DAY AND orders.status = \\'completed\\' ORDER BY orders.total DESC LIMIT 100\\x1b[0m'); console.log('Fetched 42 records in 127ms'); }, 1500)\""
|
|
18
|
+
"demo:longtext": "node -e \"setInterval(() => { console.log('\\x1b[36m[2024-01-20T12:34:56.789Z] Executing SQL query: SELECT users.id, users.name, users.email, orders.order_id, orders.total, products.name FROM users INNER JOIN orders ON users.id = orders.user_id LEFT JOIN products ON orders.product_id = products.id WHERE users.created_at > NOW() - INTERVAL 30 DAY AND orders.status = \\'completed\\' ORDER BY orders.total DESC LIMIT 100\\x1b[0m'); console.log('Fetched 42 records in 127ms'); }, 1500)\"",
|
|
19
|
+
"demo:cmd": "cmd"
|
|
19
20
|
},
|
|
20
21
|
"keywords": [],
|
|
21
22
|
"author": "",
|
package/screenshot.png
ADDED
|
Binary file
|