panex 0.9.2 → 0.9.3
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 +27 -29
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,20 +21,18 @@ A terminal UI for running multiple processes in parallel. Like Turborepo's TUI,
|
|
|
21
21
|
- **Zero config** - Just pass commands as arguments
|
|
22
22
|
- **Cross-platform** - macOS, Linux, Windows
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Requirements
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
# npm
|
|
28
|
-
npm install -g panex
|
|
26
|
+
**Bun runtime is required.** Panex uses Bun's built-in PTY support for interactive processes.
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
npx panex "npm run api" "npm run web"
|
|
28
|
+
## Installation
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
```bash
|
|
31
|
+
# Run directly with bunx
|
|
34
32
|
bunx panex "bun run api" "bun run web"
|
|
35
33
|
|
|
36
|
-
#
|
|
37
|
-
|
|
34
|
+
# Or install globally
|
|
35
|
+
bun add -g panex
|
|
38
36
|
```
|
|
39
37
|
|
|
40
38
|
## Usage
|
|
@@ -51,28 +49,28 @@ panex -n api,web,mobile "npm run api" "npm run web" "npm run mobile"
|
|
|
51
49
|
|
|
52
50
|
### Keyboard Shortcuts
|
|
53
51
|
|
|
54
|
-
| Key
|
|
55
|
-
|
|
56
|
-
| `↑/↓` or `j/k` | Navigate process list
|
|
57
|
-
| `Enter`
|
|
58
|
-
| `Esc`
|
|
59
|
-
| `r`
|
|
60
|
-
| `x`
|
|
61
|
-
| `
|
|
62
|
-
| `q`
|
|
63
|
-
| `?`
|
|
64
|
-
| `g/G`
|
|
65
|
-
| `PgUp/PgDn`
|
|
52
|
+
| Key | Action |
|
|
53
|
+
| -------------- | -------------------------------- |
|
|
54
|
+
| `↑/↓` or `j/k` | Navigate process list |
|
|
55
|
+
| `Enter` | Focus process (interactive mode) |
|
|
56
|
+
| `Esc` | Exit focus mode |
|
|
57
|
+
| `r` | Restart selected process |
|
|
58
|
+
| `x` | Kill selected process |
|
|
59
|
+
| `A` | Restart all processes |
|
|
60
|
+
| `q` | Quit panex |
|
|
61
|
+
| `?` | Show help |
|
|
62
|
+
| `g/G` | Scroll to top/bottom |
|
|
63
|
+
| `PgUp/PgDn` | Scroll output |
|
|
66
64
|
|
|
67
65
|
## Why panex?
|
|
68
66
|
|
|
69
|
-
| Feature
|
|
70
|
-
|
|
71
|
-
| Split-pane TUI
|
|
72
|
-
| PTY support (QR codes) | ✅
|
|
73
|
-
| Zero config
|
|
74
|
-
| npm install
|
|
75
|
-
| No monorepo required
|
|
67
|
+
| Feature | panex | concurrently | mprocs | turbo |
|
|
68
|
+
| ---------------------- | ----- | ------------ | ------ | ----- |
|
|
69
|
+
| Split-pane TUI | ✅ | ❌ | ✅ | ✅ |
|
|
70
|
+
| PTY support (QR codes) | ✅ | ❌ | ✅ | ✅ |
|
|
71
|
+
| Zero config | ✅ | ✅ | ❌ | ❌ |
|
|
72
|
+
| npm install | ✅ | ✅ | ❌ | ✅ |
|
|
73
|
+
| No monorepo required | ✅ | ✅ | ✅ | ❌ |
|
|
76
74
|
|
|
77
75
|
## Development
|
|
78
76
|
|
|
@@ -104,7 +102,7 @@ node dist/cli.js "echo test"
|
|
|
104
102
|
|
|
105
103
|
- TypeScript + Bun
|
|
106
104
|
- blessed (TUI framework)
|
|
107
|
-
-
|
|
105
|
+
- Bun.spawn with terminal (PTY support)
|
|
108
106
|
- commander (CLI parsing)
|
|
109
107
|
- tsup (build tool)
|
|
110
108
|
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
@@ -423,6 +423,12 @@ async function createTUI(config) {
|
|
|
423
423
|
}
|
|
424
424
|
|
|
425
425
|
// src/cli.ts
|
|
426
|
+
if (typeof Bun === "undefined") {
|
|
427
|
+
console.error("Error: panex requires Bun runtime.");
|
|
428
|
+
console.error("Please run with bunx instead of npx:");
|
|
429
|
+
console.error(' bunx panex "cmd1" "cmd2"');
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
426
432
|
var program = new Command();
|
|
427
433
|
program.name("panex").description("Terminal UI for running multiple processes in parallel").version("0.1.0").argument("<commands...>", "Commands to run in parallel").option("-n, --names <names>", "Comma-separated names for each process").action(async (commands, options) => {
|
|
428
434
|
const rawNames = options.names?.split(",") ?? commands.map((_, i) => `proc${i + 1}`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/tui.ts","../src/process-manager.ts"],"sourcesContent":["import { Command } from 'commander';\nimport type { PanexConfig } from './types';\nimport { createTUI } from './tui';\n\nconst program = new Command();\n\nprogram\n .name('panex')\n .description('Terminal UI for running multiple processes in parallel')\n .version('0.1.0')\n .argument('<commands...>', 'Commands to run in parallel')\n .option('-n, --names <names>', 'Comma-separated names for each process')\n .action(async (commands: string[], options: { names?: string }) => {\n const rawNames = options.names?.split(',') ?? commands.map((_, i) => `proc${i + 1}`);\n\n // Ensure unique names by adding suffix for duplicates\n const usedNames = new Map<string, number>();\n const names = rawNames.map((name, i) => {\n const baseName = name || `proc${i + 1}`;\n const count = usedNames.get(baseName) ?? 0;\n usedNames.set(baseName, count + 1);\n return count === 0 ? baseName : `${baseName}-${count + 1}`;\n });\n\n const config: PanexConfig = {\n procs: Object.fromEntries(\n commands.map((cmd, i) => [names[i], { shell: cmd }])\n ),\n };\n\n await createTUI(config);\n });\n\nprogram.parse();","import blessed from 'blessed';\nimport type { PanexConfig } from './types';\nimport { ProcessManager } from './process-manager';\n\nexport async function createTUI(config: PanexConfig): Promise<void> {\n const processManager = new ProcessManager(config.procs);\n\n // Create screen\n const screen = blessed.screen({\n smartCSR: true,\n title: 'panex',\n fullUnicode: true,\n });\n\n // Process list (left panel)\n const processList = blessed.list({\n parent: screen,\n label: ' PROCESSES ',\n top: 0,\n left: 0,\n width: '20%',\n height: '100%-1',\n border: { type: 'line' },\n style: {\n border: { fg: 'blue' },\n selected: { bg: 'blue', fg: 'white' },\n item: { fg: 'white' },\n },\n mouse: config.settings?.mouse ?? true,\n scrollbar: {\n ch: '│',\n style: { bg: 'blue' },\n },\n });\n\n // Output panel (right panel)\n const outputBox = blessed.box({\n parent: screen,\n label: ' OUTPUT ',\n top: 0,\n left: '20%',\n width: '80%',\n height: '100%-1',\n border: { type: 'line' },\n style: {\n border: { fg: 'green' },\n },\n scrollable: true,\n alwaysScroll: true,\n scrollbar: {\n ch: '│',\n style: { bg: 'green' },\n },\n mouse: config.settings?.mouse ?? true,\n keys: true,\n vi: true,\n });\n\n // Status bar\n const statusBar = blessed.box({\n parent: screen,\n bottom: 0,\n left: 0,\n width: '100%',\n height: 1,\n style: {\n bg: 'blue',\n fg: 'white',\n },\n content: ' [↑↓/jk] select [Enter] focus [r] restart [A] restart All [x] kill [q] quit [?] help ',\n });\n\n // Help popup\n const helpBox = blessed.box({\n parent: screen,\n top: 'center',\n left: 'center',\n width: '60%',\n height: '60%',\n label: ' Help ',\n border: { type: 'line' },\n style: {\n border: { fg: 'yellow' },\n bg: 'black',\n },\n hidden: true,\n content: `\n Keyboard Shortcuts\n ──────────────────\n\n Navigation\n ↑/↓ or j/k Navigate process list\n g/G Scroll to top/bottom of output\n PgUp/PgDn Scroll output\n\n Process Control\n Enter Focus process (interactive mode)\n Esc Exit focus mode\n r Restart selected process\n A Restart all processes\n x Kill selected process\n\n General\n ? Toggle this help\n q Quit panex\n\n Press any key to close this help...\n `,\n });\n\n // State\n let selectedIndex = 0;\n let focusMode = false;\n const processNames = Object.keys(config.procs);\n const scrollPositions = new Map<string, number>(); // Track scroll % per process\n\n // Update process list UI\n function updateProcessList() {\n const items = processNames.map((name, i) => {\n const proc = processManager.getProcess(name);\n const status = proc?.status === 'running' ? '●' : proc?.status === 'error' ? '✗' : '○';\n const prefix = i === selectedIndex ? '▶' : ' ';\n return `${prefix} ${name} ${status}`;\n });\n processList.setItems(items);\n processList.select(selectedIndex);\n screen.render();\n }\n\n // Update output panel\n function updateOutput(autoScroll = false) {\n const name = processNames[selectedIndex];\n if (name) {\n outputBox.setLabel(` OUTPUT: ${name} `);\n const output = processManager.getOutput(name);\n outputBox.setContent(output);\n if (autoScroll) {\n outputBox.setScrollPerc(100); // Scroll to bottom for new output\n } else {\n // Restore saved scroll position or default to bottom\n const savedPos = scrollPositions.get(name) ?? 100;\n outputBox.setScrollPerc(savedPos);\n }\n }\n screen.render();\n }\n\n // Save current scroll position before switching\n function saveScrollPosition() {\n const name = processNames[selectedIndex];\n if (name) {\n scrollPositions.set(name, outputBox.getScrollPerc());\n }\n }\n\n // Event handlers\n processManager.on('output', (name: string) => {\n if (name === processNames[selectedIndex]) {\n updateOutput(true); // Auto-scroll for new output\n }\n });\n\n processManager.on('started', () => {\n updateProcessList();\n });\n\n processManager.on('exit', () => {\n updateProcessList();\n });\n\n processManager.on('error', (name: string) => {\n updateProcessList();\n if (name === processNames[selectedIndex]) {\n updateOutput();\n }\n });\n\n // Keyboard handling\n screen.key(['q', 'C-c'], () => {\n processManager.killAll();\n process.exit(0);\n });\n\n screen.key(['?'], () => {\n helpBox.toggle();\n screen.render();\n });\n\n screen.key(['escape'], () => {\n if (!helpBox.hidden) {\n helpBox.hide();\n screen.render();\n return;\n }\n if (focusMode) {\n focusMode = false;\n statusBar.setContent(' [↑↓/jk] select [Enter] focus [r] restart [A] restart All [x] kill [q] quit [?] help ');\n screen.render();\n }\n });\n\n helpBox.key(['escape', 'q', '?', 'enter', 'space'], () => {\n helpBox.hide();\n screen.render();\n });\n\n screen.key(['up', 'k'], () => {\n if (focusMode || !helpBox.hidden) return;\n if (selectedIndex > 0) {\n saveScrollPosition();\n selectedIndex--;\n updateProcessList();\n updateOutput();\n }\n });\n\n screen.key(['down', 'j'], () => {\n if (focusMode || !helpBox.hidden) return;\n if (selectedIndex < processNames.length - 1) {\n saveScrollPosition();\n selectedIndex++;\n updateProcessList();\n updateOutput();\n }\n });\n\n screen.key(['enter'], () => {\n if (!helpBox.hidden) {\n helpBox.hide();\n screen.render();\n return;\n }\n if (!focusMode) {\n // Enter focus mode (don't forward this Enter)\n focusMode = true;\n const name = processNames[selectedIndex];\n statusBar.setContent(` FOCUS: ${name} - Type to interact, [Esc] to exit focus mode `);\n screen.render();\n } else {\n // Already in focus mode - forward Enter to process\n const name = processNames[selectedIndex];\n if (name) {\n processManager.write(name, '\\r');\n }\n }\n });\n\n screen.key(['r'], () => {\n if (focusMode || !helpBox.hidden) return;\n const name = processNames[selectedIndex];\n if (name) {\n processManager.restart(name);\n }\n });\n\n screen.key(['S-a'], () => {\n if (focusMode || !helpBox.hidden) return;\n processManager.restartAll();\n });\n\n screen.key(['x'], () => {\n if (focusMode || !helpBox.hidden) return;\n const name = processNames[selectedIndex];\n if (name) {\n processManager.kill(name);\n }\n });\n\n screen.key(['g'], () => {\n if (focusMode || !helpBox.hidden) return;\n outputBox.setScrollPerc(0);\n screen.render();\n });\n\n screen.key(['S-g'], () => {\n if (focusMode || !helpBox.hidden) return;\n outputBox.setScrollPerc(100);\n screen.render();\n });\n\n // Mouse click on output box enables focus mode\n outputBox.on('click', () => {\n if (!helpBox.hidden) return;\n if (!focusMode) {\n focusMode = true;\n const name = processNames[selectedIndex];\n statusBar.setContent(` FOCUS: ${name} - Type to interact, [Esc] to exit focus mode `);\n screen.render();\n }\n });\n\n // Mouse click on process list (single click)\n processList.on('element click', (_el: blessed.Widgets.BlessedElement, data: { y: number }) => {\n if (!helpBox.hidden) return;\n\n // Calculate index: y is absolute, subtract list's absolute top and border\n const absTop = (processList.atop as number) ?? 0;\n const clickedIndex = data.y - absTop - 1; // -1 for border\n\n if (clickedIndex < 0 || clickedIndex >= processNames.length) return;\n\n // Exit focus mode on click\n if (focusMode) {\n focusMode = false;\n statusBar.setContent(' [↑↓/jk] select [Enter] focus [r] restart [A] restart All [x] kill [q] quit [?] help ');\n }\n if (clickedIndex !== selectedIndex) {\n saveScrollPosition();\n selectedIndex = clickedIndex;\n updateProcessList();\n updateOutput();\n }\n screen.render();\n });\n\n // Forward input in focus mode\n screen.on('keypress', (ch: string, key: { full: string; name?: string }) => {\n if (focusMode && ch) {\n // Don't forward Escape (used to exit focus) or Enter (handled separately)\n if (key.name === 'escape' || key.name === 'return' || key.name === 'enter') {\n return;\n }\n const name = processNames[selectedIndex];\n if (name) {\n processManager.write(name, ch);\n }\n }\n });\n\n // Handle resize\n screen.on('resize', () => {\n const name = processNames[selectedIndex];\n if (name) {\n const cols = Math.floor((screen.width as number) * 0.8) - 2;\n const rows = (screen.height as number) - 3;\n processManager.resize(name, cols, rows);\n }\n });\n\n // Initial render\n updateProcessList();\n updateOutput();\n processList.focus();\n\n // Start all processes\n await processManager.startAll();\n updateProcessList();\n updateOutput();\n\n screen.render();\n}","import { EventEmitter } from 'events';\nimport type { ProcessConfig } from './types';\n\ninterface PtyHandle {\n write(data: string): void;\n resize(cols: number, rows: number): void;\n kill(): void;\n}\n\nexport interface ManagedProcess {\n name: string;\n config: ProcessConfig;\n pty: PtyHandle | null;\n status: 'running' | 'stopped' | 'error';\n output: string[];\n exitCode: number | null;\n}\n\nexport class ProcessManager extends EventEmitter {\n private processes: Map<string, ManagedProcess> = new Map();\n private maxOutputLines = 10000;\n\n constructor(private procs: Record<string, ProcessConfig>) {\n super();\n }\n\n async startAll(): Promise<void> {\n for (const [name, config] of Object.entries(this.procs)) {\n await this.start(name, config);\n }\n }\n\n async start(name: string, config: ProcessConfig): Promise<void> {\n const existing = this.processes.get(name);\n if (existing?.pty) {\n existing.pty.kill();\n }\n\n const shell = process.platform === 'win32' ? 'powershell.exe' : 'bash';\n const args = config.shell\n ? ['-c', config.shell]\n : config.cmd\n ? ['-c', config.cmd.join(' ')]\n : [];\n\n const cwd = config.cwd ?? process.cwd();\n const env = { ...process.env, ...config.env };\n\n const managed: ManagedProcess = {\n name,\n config,\n pty: null,\n status: 'running',\n output: [],\n exitCode: null,\n };\n\n this.processes.set(name, managed);\n\n try {\n const proc = Bun.spawn([shell, ...args], {\n cwd,\n env: env as Record<string, string>,\n terminal: {\n cols: 120,\n rows: 30,\n data: (_terminal: unknown, data: Uint8Array) => {\n const str = new TextDecoder().decode(data);\n managed.output.push(str);\n if (managed.output.length > this.maxOutputLines) {\n managed.output = managed.output.slice(-this.maxOutputLines);\n }\n this.emit('output', name, str);\n },\n },\n });\n\n managed.pty = {\n write: (data: string) => proc.terminal?.write(data),\n resize: (cols: number, rows: number) => proc.terminal?.resize(cols, rows),\n kill: () => proc.kill(),\n };\n\n // Handle exit\n proc.exited.then((exitCode) => {\n managed.status = exitCode === 0 ? 'stopped' : 'error';\n managed.exitCode = exitCode;\n managed.pty = null;\n this.emit('exit', name, exitCode);\n\n if (managed.config.autoRestart && exitCode !== 0) {\n setTimeout(() => this.start(name, managed.config), 1000);\n }\n });\n\n this.emit('started', name);\n } catch (error) {\n managed.status = 'error';\n managed.output = [`Error starting process: ${error}`];\n managed.exitCode = -1;\n this.emit('error', name, error);\n }\n }\n\n restart(name: string): void {\n const proc = this.processes.get(name);\n if (proc) {\n if (proc.pty) {\n proc.pty.kill();\n }\n proc.output = [];\n this.start(name, proc.config);\n }\n }\n\n restartAll(): void {\n for (const name of this.processes.keys()) {\n this.restart(name);\n }\n }\n\n kill(name: string): void {\n const proc = this.processes.get(name);\n if (proc?.pty) {\n proc.pty.kill();\n }\n }\n\n killAll(): void {\n for (const proc of this.processes.values()) {\n if (proc.pty) {\n proc.pty.kill();\n }\n }\n }\n\n write(name: string, data: string): void {\n const proc = this.processes.get(name);\n if (proc?.pty) {\n proc.pty.write(data);\n }\n }\n\n resize(name: string, cols: number, rows: number): void {\n const proc = this.processes.get(name);\n if (proc?.pty) {\n proc.pty.resize(cols, rows);\n }\n }\n\n getProcess(name: string): ManagedProcess | undefined {\n return this.processes.get(name);\n }\n\n getProcesses(): ManagedProcess[] {\n return Array.from(this.processes.values());\n }\n\n getNames(): string[] {\n return Array.from(this.processes.keys());\n }\n\n getOutput(name: string): string {\n return this.processes.get(name)?.output.join('') ?? '';\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/tui.ts","../src/process-manager.ts"],"sourcesContent":["// Check for Bun runtime - must be at top before any Bun APIs are used\nif (typeof Bun === 'undefined') {\n console.error('Error: panex requires Bun runtime.');\n console.error('Please run with bunx instead of npx:');\n console.error('\\tbunx panex \"cmd1\" \"cmd2\"');\n process.exit(1);\n}\n\nimport { Command } from 'commander';\nimport type { PanexConfig } from './types';\nimport { createTUI } from './tui';\n\nconst program = new Command();\n\nprogram\n .name('panex')\n .description('Terminal UI for running multiple processes in parallel')\n .version('0.1.0')\n .argument('<commands...>', 'Commands to run in parallel')\n .option('-n, --names <names>', 'Comma-separated names for each process')\n .action(async (commands: string[], options: { names?: string; }) => {\n const rawNames = options.names?.split(',') ?? commands.map((_, i) => `proc${i + 1}`);\n\n // Ensure unique names by adding suffix for duplicates\n const usedNames = new Map<string, number>();\n const names = rawNames.map((name, i) => {\n const baseName = name || `proc${i + 1}`;\n const count = usedNames.get(baseName) ?? 0;\n usedNames.set(baseName, count + 1);\n return count === 0 ? baseName : `${baseName}-${count + 1}`;\n });\n\n const config: PanexConfig = {\n procs: Object.fromEntries(\n commands.map((cmd, i) => [names[i], { shell: cmd }])\n ),\n };\n\n await createTUI(config);\n });\n\nprogram.parse();","import blessed from 'blessed';\nimport type { PanexConfig } from './types';\nimport { ProcessManager } from './process-manager';\n\nexport async function createTUI(config: PanexConfig): Promise<void> {\n const processManager = new ProcessManager(config.procs);\n\n // Create screen\n const screen = blessed.screen({\n smartCSR: true,\n title: 'panex',\n fullUnicode: true,\n });\n\n // Process list (left panel)\n const processList = blessed.list({\n parent: screen,\n label: ' PROCESSES ',\n top: 0,\n left: 0,\n width: '20%',\n height: '100%-1',\n border: { type: 'line' },\n style: {\n border: { fg: 'blue' },\n selected: { bg: 'blue', fg: 'white' },\n item: { fg: 'white' },\n },\n mouse: config.settings?.mouse ?? true,\n scrollbar: {\n ch: '│',\n style: { bg: 'blue' },\n },\n });\n\n // Output panel (right panel)\n const outputBox = blessed.box({\n parent: screen,\n label: ' OUTPUT ',\n top: 0,\n left: '20%',\n width: '80%',\n height: '100%-1',\n border: { type: 'line' },\n style: {\n border: { fg: 'green' },\n },\n scrollable: true,\n alwaysScroll: true,\n scrollbar: {\n ch: '│',\n style: { bg: 'green' },\n },\n mouse: config.settings?.mouse ?? true,\n keys: true,\n vi: true,\n });\n\n // Status bar\n const statusBar = blessed.box({\n parent: screen,\n bottom: 0,\n left: 0,\n width: '100%',\n height: 1,\n style: {\n bg: 'blue',\n fg: 'white',\n },\n content: ' [↑↓/jk] select [Enter] focus [r] restart [A] restart All [x] kill [q] quit [?] help ',\n });\n\n // Help popup\n const helpBox = blessed.box({\n parent: screen,\n top: 'center',\n left: 'center',\n width: '60%',\n height: '60%',\n label: ' Help ',\n border: { type: 'line' },\n style: {\n border: { fg: 'yellow' },\n bg: 'black',\n },\n hidden: true,\n content: `\n Keyboard Shortcuts\n ──────────────────\n\n Navigation\n ↑/↓ or j/k Navigate process list\n g/G Scroll to top/bottom of output\n PgUp/PgDn Scroll output\n\n Process Control\n Enter Focus process (interactive mode)\n Esc Exit focus mode\n r Restart selected process\n A Restart all processes\n x Kill selected process\n\n General\n ? Toggle this help\n q Quit panex\n\n Press any key to close this help...\n `,\n });\n\n // State\n let selectedIndex = 0;\n let focusMode = false;\n const processNames = Object.keys(config.procs);\n const scrollPositions = new Map<string, number>(); // Track scroll % per process\n\n // Update process list UI\n function updateProcessList() {\n const items = processNames.map((name, i) => {\n const proc = processManager.getProcess(name);\n const status = proc?.status === 'running' ? '●' : proc?.status === 'error' ? '✗' : '○';\n const prefix = i === selectedIndex ? '▶' : ' ';\n return `${prefix} ${name} ${status}`;\n });\n processList.setItems(items);\n processList.select(selectedIndex);\n screen.render();\n }\n\n // Update output panel\n function updateOutput(autoScroll = false) {\n const name = processNames[selectedIndex];\n if (name) {\n outputBox.setLabel(` OUTPUT: ${name} `);\n const output = processManager.getOutput(name);\n outputBox.setContent(output);\n if (autoScroll) {\n outputBox.setScrollPerc(100); // Scroll to bottom for new output\n } else {\n // Restore saved scroll position or default to bottom\n const savedPos = scrollPositions.get(name) ?? 100;\n outputBox.setScrollPerc(savedPos);\n }\n }\n screen.render();\n }\n\n // Save current scroll position before switching\n function saveScrollPosition() {\n const name = processNames[selectedIndex];\n if (name) {\n scrollPositions.set(name, outputBox.getScrollPerc());\n }\n }\n\n // Event handlers\n processManager.on('output', (name: string) => {\n if (name === processNames[selectedIndex]) {\n updateOutput(true); // Auto-scroll for new output\n }\n });\n\n processManager.on('started', () => {\n updateProcessList();\n });\n\n processManager.on('exit', () => {\n updateProcessList();\n });\n\n processManager.on('error', (name: string) => {\n updateProcessList();\n if (name === processNames[selectedIndex]) {\n updateOutput();\n }\n });\n\n // Keyboard handling\n screen.key(['q', 'C-c'], () => {\n processManager.killAll();\n process.exit(0);\n });\n\n screen.key(['?'], () => {\n helpBox.toggle();\n screen.render();\n });\n\n screen.key(['escape'], () => {\n if (!helpBox.hidden) {\n helpBox.hide();\n screen.render();\n return;\n }\n if (focusMode) {\n focusMode = false;\n statusBar.setContent(' [↑↓/jk] select [Enter] focus [r] restart [A] restart All [x] kill [q] quit [?] help ');\n screen.render();\n }\n });\n\n helpBox.key(['escape', 'q', '?', 'enter', 'space'], () => {\n helpBox.hide();\n screen.render();\n });\n\n screen.key(['up', 'k'], () => {\n if (focusMode || !helpBox.hidden) return;\n if (selectedIndex > 0) {\n saveScrollPosition();\n selectedIndex--;\n updateProcessList();\n updateOutput();\n }\n });\n\n screen.key(['down', 'j'], () => {\n if (focusMode || !helpBox.hidden) return;\n if (selectedIndex < processNames.length - 1) {\n saveScrollPosition();\n selectedIndex++;\n updateProcessList();\n updateOutput();\n }\n });\n\n screen.key(['enter'], () => {\n if (!helpBox.hidden) {\n helpBox.hide();\n screen.render();\n return;\n }\n if (!focusMode) {\n // Enter focus mode (don't forward this Enter)\n focusMode = true;\n const name = processNames[selectedIndex];\n statusBar.setContent(` FOCUS: ${name} - Type to interact, [Esc] to exit focus mode `);\n screen.render();\n } else {\n // Already in focus mode - forward Enter to process\n const name = processNames[selectedIndex];\n if (name) {\n processManager.write(name, '\\r');\n }\n }\n });\n\n screen.key(['r'], () => {\n if (focusMode || !helpBox.hidden) return;\n const name = processNames[selectedIndex];\n if (name) {\n processManager.restart(name);\n }\n });\n\n screen.key(['S-a'], () => {\n if (focusMode || !helpBox.hidden) return;\n processManager.restartAll();\n });\n\n screen.key(['x'], () => {\n if (focusMode || !helpBox.hidden) return;\n const name = processNames[selectedIndex];\n if (name) {\n processManager.kill(name);\n }\n });\n\n screen.key(['g'], () => {\n if (focusMode || !helpBox.hidden) return;\n outputBox.setScrollPerc(0);\n screen.render();\n });\n\n screen.key(['S-g'], () => {\n if (focusMode || !helpBox.hidden) return;\n outputBox.setScrollPerc(100);\n screen.render();\n });\n\n // Mouse click on output box enables focus mode\n outputBox.on('click', () => {\n if (!helpBox.hidden) return;\n if (!focusMode) {\n focusMode = true;\n const name = processNames[selectedIndex];\n statusBar.setContent(` FOCUS: ${name} - Type to interact, [Esc] to exit focus mode `);\n screen.render();\n }\n });\n\n // Mouse click on process list (single click)\n processList.on('element click', (_el: blessed.Widgets.BlessedElement, data: { y: number }) => {\n if (!helpBox.hidden) return;\n\n // Calculate index: y is absolute, subtract list's absolute top and border\n const absTop = (processList.atop as number) ?? 0;\n const clickedIndex = data.y - absTop - 1; // -1 for border\n\n if (clickedIndex < 0 || clickedIndex >= processNames.length) return;\n\n // Exit focus mode on click\n if (focusMode) {\n focusMode = false;\n statusBar.setContent(' [↑↓/jk] select [Enter] focus [r] restart [A] restart All [x] kill [q] quit [?] help ');\n }\n if (clickedIndex !== selectedIndex) {\n saveScrollPosition();\n selectedIndex = clickedIndex;\n updateProcessList();\n updateOutput();\n }\n screen.render();\n });\n\n // Forward input in focus mode\n screen.on('keypress', (ch: string, key: { full: string; name?: string }) => {\n if (focusMode && ch) {\n // Don't forward Escape (used to exit focus) or Enter (handled separately)\n if (key.name === 'escape' || key.name === 'return' || key.name === 'enter') {\n return;\n }\n const name = processNames[selectedIndex];\n if (name) {\n processManager.write(name, ch);\n }\n }\n });\n\n // Handle resize\n screen.on('resize', () => {\n const name = processNames[selectedIndex];\n if (name) {\n const cols = Math.floor((screen.width as number) * 0.8) - 2;\n const rows = (screen.height as number) - 3;\n processManager.resize(name, cols, rows);\n }\n });\n\n // Initial render\n updateProcessList();\n updateOutput();\n processList.focus();\n\n // Start all processes\n await processManager.startAll();\n updateProcessList();\n updateOutput();\n\n screen.render();\n}","import { EventEmitter } from 'events';\nimport type { ProcessConfig } from './types';\n\ninterface PtyHandle {\n write(data: string): void;\n resize(cols: number, rows: number): void;\n kill(): void;\n}\n\nexport interface ManagedProcess {\n name: string;\n config: ProcessConfig;\n pty: PtyHandle | null;\n status: 'running' | 'stopped' | 'error';\n output: string[];\n exitCode: number | null;\n}\n\nexport class ProcessManager extends EventEmitter {\n private processes: Map<string, ManagedProcess> = new Map();\n private maxOutputLines = 10000;\n\n constructor(private procs: Record<string, ProcessConfig>) {\n super();\n }\n\n async startAll(): Promise<void> {\n for (const [name, config] of Object.entries(this.procs)) {\n await this.start(name, config);\n }\n }\n\n async start(name: string, config: ProcessConfig): Promise<void> {\n const existing = this.processes.get(name);\n if (existing?.pty) {\n existing.pty.kill();\n }\n\n const shell = process.platform === 'win32' ? 'powershell.exe' : 'bash';\n const args = config.shell\n ? ['-c', config.shell]\n : config.cmd\n ? ['-c', config.cmd.join(' ')]\n : [];\n\n const cwd = config.cwd ?? process.cwd();\n const env = { ...process.env, ...config.env };\n\n const managed: ManagedProcess = {\n name,\n config,\n pty: null,\n status: 'running',\n output: [],\n exitCode: null,\n };\n\n this.processes.set(name, managed);\n\n try {\n const proc = Bun.spawn([shell, ...args], {\n cwd,\n env: env as Record<string, string>,\n terminal: {\n cols: 120,\n rows: 30,\n data: (_terminal: unknown, data: Uint8Array) => {\n const str = new TextDecoder().decode(data);\n managed.output.push(str);\n if (managed.output.length > this.maxOutputLines) {\n managed.output = managed.output.slice(-this.maxOutputLines);\n }\n this.emit('output', name, str);\n },\n },\n });\n\n managed.pty = {\n write: (data: string) => proc.terminal?.write(data),\n resize: (cols: number, rows: number) => proc.terminal?.resize(cols, rows),\n kill: () => proc.kill(),\n };\n\n // Handle exit\n proc.exited.then((exitCode) => {\n managed.status = exitCode === 0 ? 'stopped' : 'error';\n managed.exitCode = exitCode;\n managed.pty = null;\n this.emit('exit', name, exitCode);\n\n if (managed.config.autoRestart && exitCode !== 0) {\n setTimeout(() => this.start(name, managed.config), 1000);\n }\n });\n\n this.emit('started', name);\n } catch (error) {\n managed.status = 'error';\n managed.output = [`Error starting process: ${error}`];\n managed.exitCode = -1;\n this.emit('error', name, error);\n }\n }\n\n restart(name: string): void {\n const proc = this.processes.get(name);\n if (proc) {\n if (proc.pty) {\n proc.pty.kill();\n }\n proc.output = [];\n this.start(name, proc.config);\n }\n }\n\n restartAll(): void {\n for (const name of this.processes.keys()) {\n this.restart(name);\n }\n }\n\n kill(name: string): void {\n const proc = this.processes.get(name);\n if (proc?.pty) {\n proc.pty.kill();\n }\n }\n\n killAll(): void {\n for (const proc of this.processes.values()) {\n if (proc.pty) {\n proc.pty.kill();\n }\n }\n }\n\n write(name: string, data: string): void {\n const proc = this.processes.get(name);\n if (proc?.pty) {\n proc.pty.write(data);\n }\n }\n\n resize(name: string, cols: number, rows: number): void {\n const proc = this.processes.get(name);\n if (proc?.pty) {\n proc.pty.resize(cols, rows);\n }\n }\n\n getProcess(name: string): ManagedProcess | undefined {\n return this.processes.get(name);\n }\n\n getProcesses(): ManagedProcess[] {\n return Array.from(this.processes.values());\n }\n\n getNames(): string[] {\n return Array.from(this.processes.keys());\n }\n\n getOutput(name: string): string {\n return this.processes.get(name)?.output.join('') ?? '';\n }\n}\n"],"mappings":"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","names":[]}
|