panex 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Anton Veretennikov (king8fisher)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # panex
2
+
3
+ A terminal UI for running multiple processes in parallel. Like Turborepo's TUI, without the monorepo.
4
+
5
+ ```
6
+ ┌──────────────┬──────────────────────────────────────────────┐
7
+ │ PROCESSES │ OUTPUT: api │
8
+ │ │ │
9
+ │ ▶ api ● │ Server listening on http://localhost:3001 │
10
+ │ web ● │ {"level":30,"msg":"request completed"} │
11
+ │ mobile ● │ │
12
+ ├──────────────┴──────────────────────────────────────────────┤
13
+ │ [↑↓] select [enter] focus [r] restart [q] quit │
14
+ └─────────────────────────────────────────────────────────────┘
15
+ ```
16
+
17
+ ## Features
18
+
19
+ - **Split-pane TUI** - See all your processes at once
20
+ - **Full PTY support** - QR codes, colors, interactive prompts work
21
+ - **Zero config** - Just pass commands as arguments
22
+ - **Cross-platform** - macOS, Linux, Windows
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ # npm
28
+ npm install -g panex
29
+
30
+ # or run directly
31
+ npx panex "npm run api" "npm run web"
32
+
33
+ # bun
34
+ bunx panex "bun run api" "bun run web"
35
+
36
+ # pnpm
37
+ pnpm add -g panex
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ### Quick Start
43
+
44
+ ```bash
45
+ # Run multiple commands
46
+ panex "npm run api" "npm run web" "npm run mobile"
47
+
48
+ # With custom names
49
+ panex -n api,web,mobile "npm run api" "npm run web" "npm run mobile"
50
+ ```
51
+
52
+ ### Keyboard Shortcuts
53
+
54
+ | Key | Action |
55
+ |-----|--------|
56
+ | `↑/↓` or `j/k` | Navigate process list |
57
+ | `Enter` | Focus process (interactive mode) |
58
+ | `Esc` | Exit focus mode |
59
+ | `r` | Restart selected process |
60
+ | `x` | Kill selected process |
61
+ | `a` | Restart all processes |
62
+ | `q` | Quit panex |
63
+ | `?` | Show help |
64
+ | `g/G` | Scroll to top/bottom |
65
+ | `PgUp/PgDn` | Scroll output |
66
+
67
+ ## Why panex?
68
+
69
+ | Feature | panex | concurrently | mprocs | turbo |
70
+ |---------|-------|--------------|--------|-------|
71
+ | Split-pane TUI | ✅ | ❌ | ✅ | ✅ |
72
+ | PTY support (QR codes) | ✅ | ❌ | ✅ | ✅ |
73
+ | Zero config | ✅ | ✅ | ❌ | ❌ |
74
+ | npm install | ✅ | ✅ | ❌ | ✅ |
75
+ | No monorepo required | ✅ | ✅ | ✅ | ❌ |
76
+
77
+ ## Development
78
+
79
+ ```bash
80
+ # Clone
81
+ git clone https://github.com/king8fisher/panex
82
+ cd panex
83
+
84
+ # Install dependencies
85
+ bun install
86
+
87
+ # Run in dev mode
88
+ bun run dev "echo hello" "sleep 2 && echo world"
89
+
90
+ # Type check
91
+ bun run typecheck
92
+
93
+ # Build for npm
94
+ bun run build
95
+
96
+ # Run tests
97
+ bun test
98
+
99
+ # Test built CLI
100
+ node dist/cli.js "echo test"
101
+ ```
102
+
103
+ ## Tech Stack
104
+
105
+ - TypeScript + Bun
106
+ - blessed (TUI framework)
107
+ - node-pty (PTY support for interactive processes)
108
+ - commander (CLI parsing)
109
+ - tsup (build tool)
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/process-manager.ts
4
+ import * as pty from "node-pty";
5
+ import { EventEmitter } from "events";
6
+ var ProcessManager = class extends EventEmitter {
7
+ constructor(procs) {
8
+ super();
9
+ this.procs = procs;
10
+ }
11
+ processes = /* @__PURE__ */ new Map();
12
+ maxOutputLines = 1e4;
13
+ async startAll() {
14
+ for (const [name, config] of Object.entries(this.procs)) {
15
+ await this.start(name, config);
16
+ }
17
+ }
18
+ async start(name, config) {
19
+ const existing = this.processes.get(name);
20
+ if (existing?.pty) {
21
+ existing.pty.kill();
22
+ }
23
+ const shell = process.platform === "win32" ? "powershell.exe" : "bash";
24
+ const args = config.shell ? ["-c", config.shell] : config.cmd ? ["-c", config.cmd.join(" ")] : [];
25
+ const cwd = config.cwd ?? process.cwd();
26
+ const env = { ...process.env, ...config.env };
27
+ try {
28
+ const ptyProcess = pty.spawn(shell, args, {
29
+ name: "xterm-256color",
30
+ cols: 120,
31
+ rows: 30,
32
+ cwd,
33
+ env
34
+ });
35
+ const managed = {
36
+ name,
37
+ config,
38
+ pty: ptyProcess,
39
+ status: "running",
40
+ output: [],
41
+ exitCode: null
42
+ };
43
+ this.processes.set(name, managed);
44
+ ptyProcess.onData((data) => {
45
+ managed.output.push(data);
46
+ if (managed.output.length > this.maxOutputLines) {
47
+ managed.output = managed.output.slice(-this.maxOutputLines);
48
+ }
49
+ this.emit("output", name, data);
50
+ });
51
+ ptyProcess.onExit(({ exitCode }) => {
52
+ managed.status = exitCode === 0 ? "stopped" : "error";
53
+ managed.exitCode = exitCode;
54
+ managed.pty = null;
55
+ this.emit("exit", name, exitCode);
56
+ if (config.autoRestart && exitCode !== 0) {
57
+ setTimeout(() => {
58
+ this.start(name, config);
59
+ }, 1e3);
60
+ }
61
+ });
62
+ this.emit("started", name);
63
+ } catch (error) {
64
+ const managed = {
65
+ name,
66
+ config,
67
+ pty: null,
68
+ status: "error",
69
+ output: [`Error starting process: ${error}`],
70
+ exitCode: -1
71
+ };
72
+ this.processes.set(name, managed);
73
+ this.emit("error", name, error);
74
+ }
75
+ }
76
+ restart(name) {
77
+ const proc = this.processes.get(name);
78
+ if (proc) {
79
+ if (proc.pty) {
80
+ proc.pty.kill();
81
+ }
82
+ proc.output = [];
83
+ this.start(name, proc.config);
84
+ }
85
+ }
86
+ restartAll() {
87
+ for (const name of this.processes.keys()) {
88
+ this.restart(name);
89
+ }
90
+ }
91
+ kill(name) {
92
+ const proc = this.processes.get(name);
93
+ if (proc?.pty) {
94
+ proc.pty.kill();
95
+ }
96
+ }
97
+ killAll() {
98
+ for (const proc of this.processes.values()) {
99
+ if (proc.pty) {
100
+ proc.pty.kill();
101
+ }
102
+ }
103
+ }
104
+ write(name, data) {
105
+ const proc = this.processes.get(name);
106
+ if (proc?.pty) {
107
+ proc.pty.write(data);
108
+ }
109
+ }
110
+ resize(name, cols, rows) {
111
+ const proc = this.processes.get(name);
112
+ if (proc?.pty) {
113
+ proc.pty.resize(cols, rows);
114
+ }
115
+ }
116
+ getProcess(name) {
117
+ return this.processes.get(name);
118
+ }
119
+ getProcesses() {
120
+ return Array.from(this.processes.values());
121
+ }
122
+ getNames() {
123
+ return Array.from(this.processes.keys());
124
+ }
125
+ getOutput(name) {
126
+ return this.processes.get(name)?.output.join("") ?? "";
127
+ }
128
+ };
129
+
130
+ // src/tui.ts
131
+ import blessed from "blessed";
132
+ async function createTUI(config) {
133
+ const processManager = new ProcessManager(config.procs);
134
+ const screen = blessed.screen({
135
+ smartCSR: true,
136
+ title: "panex",
137
+ fullUnicode: true
138
+ });
139
+ const processList = blessed.list({
140
+ parent: screen,
141
+ label: " PROCESSES ",
142
+ top: 0,
143
+ left: 0,
144
+ width: "20%",
145
+ height: "100%-1",
146
+ border: { type: "line" },
147
+ style: {
148
+ border: { fg: "blue" },
149
+ selected: { bg: "blue", fg: "white" },
150
+ item: { fg: "white" }
151
+ },
152
+ keys: true,
153
+ vi: true,
154
+ mouse: config.settings?.mouse ?? true,
155
+ scrollbar: {
156
+ ch: "\u2502",
157
+ style: { bg: "blue" }
158
+ }
159
+ });
160
+ const outputBox = blessed.box({
161
+ parent: screen,
162
+ label: " OUTPUT ",
163
+ top: 0,
164
+ left: "20%",
165
+ width: "80%",
166
+ height: "100%-1",
167
+ border: { type: "line" },
168
+ style: {
169
+ border: { fg: "green" }
170
+ },
171
+ scrollable: true,
172
+ alwaysScroll: true,
173
+ scrollbar: {
174
+ ch: "\u2502",
175
+ style: { bg: "green" }
176
+ },
177
+ mouse: config.settings?.mouse ?? true,
178
+ keys: true,
179
+ vi: true
180
+ });
181
+ const statusBar = blessed.box({
182
+ parent: screen,
183
+ bottom: 0,
184
+ left: 0,
185
+ width: "100%",
186
+ height: 1,
187
+ style: {
188
+ bg: "blue",
189
+ fg: "white"
190
+ },
191
+ content: " [\u2191\u2193/jk] select [Enter] focus [r] restart [a] restart all [x] kill [q] quit [?] help "
192
+ });
193
+ const helpBox = blessed.box({
194
+ parent: screen,
195
+ top: "center",
196
+ left: "center",
197
+ width: "60%",
198
+ height: "60%",
199
+ label: " Help ",
200
+ border: { type: "line" },
201
+ style: {
202
+ border: { fg: "yellow" },
203
+ bg: "black"
204
+ },
205
+ hidden: true,
206
+ content: `
207
+ Keyboard Shortcuts
208
+ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
209
+
210
+ Navigation
211
+ \u2191/\u2193 or j/k Navigate process list
212
+ g/G Scroll to top/bottom of output
213
+ PgUp/PgDn Scroll output
214
+
215
+ Process Control
216
+ Enter Focus process (interactive mode)
217
+ Esc Exit focus mode
218
+ r Restart selected process
219
+ a Restart all processes
220
+ x Kill selected process
221
+
222
+ General
223
+ ? Toggle this help
224
+ q Quit panex
225
+
226
+ Press any key to close this help...
227
+ `
228
+ });
229
+ let selectedIndex = 0;
230
+ let focusMode = false;
231
+ const processNames = Object.keys(config.procs);
232
+ function updateProcessList() {
233
+ const items = processNames.map((name, i) => {
234
+ const proc = processManager.getProcess(name);
235
+ const status = proc?.status === "running" ? "\u25CF" : proc?.status === "error" ? "\u2717" : "\u25CB";
236
+ const prefix = i === selectedIndex ? "\u25B6" : " ";
237
+ return `${prefix} ${name} ${status}`;
238
+ });
239
+ processList.setItems(items);
240
+ processList.select(selectedIndex);
241
+ screen.render();
242
+ }
243
+ function updateOutput() {
244
+ const name = processNames[selectedIndex];
245
+ if (name) {
246
+ outputBox.setLabel(` OUTPUT: ${name} `);
247
+ const output = processManager.getOutput(name);
248
+ outputBox.setContent(output);
249
+ outputBox.setScrollPerc(100);
250
+ }
251
+ screen.render();
252
+ }
253
+ processManager.on("output", (name) => {
254
+ if (name === processNames[selectedIndex]) {
255
+ updateOutput();
256
+ }
257
+ });
258
+ processManager.on("started", () => {
259
+ updateProcessList();
260
+ });
261
+ processManager.on("exit", () => {
262
+ updateProcessList();
263
+ });
264
+ screen.key(["q", "C-c"], () => {
265
+ processManager.killAll();
266
+ process.exit(0);
267
+ });
268
+ screen.key(["?"], () => {
269
+ helpBox.toggle();
270
+ screen.render();
271
+ });
272
+ screen.key(["escape"], () => {
273
+ if (!helpBox.hidden) {
274
+ helpBox.hide();
275
+ screen.render();
276
+ return;
277
+ }
278
+ if (focusMode) {
279
+ focusMode = false;
280
+ statusBar.setContent(" [\u2191\u2193/jk] select [Enter] focus [r] restart [a] restart all [x] kill [q] quit [?] help ");
281
+ screen.render();
282
+ }
283
+ });
284
+ helpBox.key(["escape", "q", "?", "enter", "space"], () => {
285
+ helpBox.hide();
286
+ screen.render();
287
+ });
288
+ screen.key(["up", "k"], () => {
289
+ if (focusMode || !helpBox.hidden) return;
290
+ selectedIndex = Math.max(0, selectedIndex - 1);
291
+ updateProcessList();
292
+ updateOutput();
293
+ });
294
+ screen.key(["down", "j"], () => {
295
+ if (focusMode || !helpBox.hidden) return;
296
+ selectedIndex = Math.min(processNames.length - 1, selectedIndex + 1);
297
+ updateProcessList();
298
+ updateOutput();
299
+ });
300
+ screen.key(["enter"], () => {
301
+ if (!helpBox.hidden) {
302
+ helpBox.hide();
303
+ screen.render();
304
+ return;
305
+ }
306
+ focusMode = !focusMode;
307
+ const name = processNames[selectedIndex];
308
+ if (focusMode && name) {
309
+ statusBar.setContent(` FOCUS: ${name} - Type to interact, [Esc] to exit focus mode `);
310
+ } else {
311
+ statusBar.setContent(" [\u2191\u2193/jk] select [Enter] focus [r] restart [a] restart all [x] kill [q] quit [?] help ");
312
+ }
313
+ screen.render();
314
+ });
315
+ screen.key(["r"], () => {
316
+ if (focusMode || !helpBox.hidden) return;
317
+ const name = processNames[selectedIndex];
318
+ if (name) {
319
+ processManager.restart(name);
320
+ }
321
+ });
322
+ screen.key(["a"], () => {
323
+ if (focusMode || !helpBox.hidden) return;
324
+ processManager.restartAll();
325
+ });
326
+ screen.key(["x"], () => {
327
+ if (focusMode || !helpBox.hidden) return;
328
+ const name = processNames[selectedIndex];
329
+ if (name) {
330
+ processManager.kill(name);
331
+ }
332
+ });
333
+ screen.key(["g"], () => {
334
+ if (focusMode || !helpBox.hidden) return;
335
+ outputBox.setScrollPerc(0);
336
+ screen.render();
337
+ });
338
+ screen.key(["S-g"], () => {
339
+ if (focusMode || !helpBox.hidden) return;
340
+ outputBox.setScrollPerc(100);
341
+ screen.render();
342
+ });
343
+ screen.on("keypress", (ch, key) => {
344
+ if (focusMode && ch) {
345
+ const name = processNames[selectedIndex];
346
+ if (name) {
347
+ processManager.write(name, ch);
348
+ }
349
+ }
350
+ });
351
+ screen.on("resize", () => {
352
+ const name = processNames[selectedIndex];
353
+ if (name) {
354
+ const cols = Math.floor(screen.width * 0.8) - 2;
355
+ const rows = screen.height - 3;
356
+ processManager.resize(name, cols, rows);
357
+ }
358
+ });
359
+ updateProcessList();
360
+ updateOutput();
361
+ processList.focus();
362
+ await processManager.startAll();
363
+ updateProcessList();
364
+ updateOutput();
365
+ screen.render();
366
+ }
367
+
368
+ export {
369
+ ProcessManager,
370
+ createTUI
371
+ };
372
+ //# sourceMappingURL=chunk-Z7LPKPEA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/process-manager.ts","../src/tui.ts"],"sourcesContent":["import * as pty from 'node-pty';\nimport { EventEmitter } from 'events';\nimport type { ProcessConfig } from './types';\n\nexport interface ManagedProcess {\n name: string;\n config: ProcessConfig;\n pty: pty.IPty | 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 try {\n const ptyProcess = pty.spawn(shell, args, {\n name: 'xterm-256color',\n cols: 120,\n rows: 30,\n cwd,\n env: env as Record<string, string>,\n });\n\n const managed: ManagedProcess = {\n name,\n config,\n pty: ptyProcess,\n status: 'running',\n output: [],\n exitCode: null,\n };\n\n this.processes.set(name, managed);\n\n ptyProcess.onData((data) => {\n managed.output.push(data);\n // Trim output if too long\n if (managed.output.length > this.maxOutputLines) {\n managed.output = managed.output.slice(-this.maxOutputLines);\n }\n this.emit('output', name, data);\n });\n\n ptyProcess.onExit(({ exitCode }) => {\n managed.status = exitCode === 0 ? 'stopped' : 'error';\n managed.exitCode = exitCode;\n managed.pty = null;\n this.emit('exit', name, exitCode);\n\n // Auto-restart if configured\n if (config.autoRestart && exitCode !== 0) {\n setTimeout(() => {\n this.start(name, config);\n }, 1000);\n }\n });\n\n this.emit('started', name);\n } catch (error) {\n const managed: ManagedProcess = {\n name,\n config,\n pty: null,\n status: 'error',\n output: [`Error starting process: ${error}`],\n exitCode: -1,\n };\n this.processes.set(name, managed);\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","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 keys: true,\n vi: true,\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\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() {\n const name = processNames[selectedIndex];\n if (name) {\n outputBox.setLabel(` OUTPUT: ${name} `);\n const output = processManager.getOutput(name);\n outputBox.setContent(output);\n outputBox.setScrollPerc(100); // Scroll to bottom\n }\n screen.render();\n }\n\n // Event handlers\n processManager.on('output', (name: string) => {\n if (name === processNames[selectedIndex]) {\n updateOutput();\n }\n });\n\n processManager.on('started', () => {\n updateProcessList();\n });\n\n processManager.on('exit', () => {\n updateProcessList();\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 selectedIndex = Math.max(0, selectedIndex - 1);\n updateProcessList();\n updateOutput();\n });\n\n screen.key(['down', 'j'], () => {\n if (focusMode || !helpBox.hidden) return;\n selectedIndex = Math.min(processNames.length - 1, selectedIndex + 1);\n updateProcessList();\n updateOutput();\n });\n\n screen.key(['enter'], () => {\n if (!helpBox.hidden) {\n helpBox.hide();\n screen.render();\n return;\n }\n focusMode = !focusMode;\n const name = processNames[selectedIndex];\n if (focusMode && name) {\n statusBar.setContent(` FOCUS: ${name} - Type to interact, [Esc] to exit focus mode `);\n } else {\n statusBar.setContent(' [↑↓/jk] select [Enter] focus [r] restart [a] restart all [x] kill [q] quit [?] help ');\n }\n screen.render();\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(['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 // Forward input in focus mode\n screen.on('keypress', (ch: string, key: { full: string }) => {\n if (focusMode && ch) {\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}"],"mappings":"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","names":[]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ import {
4
+ createTUI
5
+ } from "./chunk-Z7LPKPEA.js";
6
+
7
+ // src/cli.ts
8
+ import { Command } from "commander";
9
+ var program = new Command();
10
+ 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) => {
11
+ const names = options.names?.split(",") ?? commands.map((_, i) => `proc${i + 1}`);
12
+ const config = {
13
+ procs: Object.fromEntries(
14
+ commands.map((cmd, i) => [
15
+ names[i] ?? `proc${i + 1}`,
16
+ { shell: cmd }
17
+ ])
18
+ )
19
+ };
20
+ await createTUI(config);
21
+ });
22
+ program.parse();
23
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\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 names = options.names?.split(',') ?? commands.map((_, i) => `proc${i + 1}`);\n const config: PanexConfig = {\n procs: Object.fromEntries(\n commands.map((cmd, i) => [\n names[i] ?? `proc${i + 1}`,\n { shell: cmd },\n ])\n ),\n };\n\n await createTUI(config);\n });\n\nprogram.parse();"],"mappings":";;;;;;;AAEA,SAAS,eAAe;AAIxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,OAAO,EACZ,YAAY,wDAAwD,EACpE,QAAQ,OAAO,EACf,SAAS,iBAAiB,6BAA6B,EACvD,OAAO,uBAAuB,wCAAwC,EACtE,OAAO,OAAO,UAAoB,YAAgC;AACjE,QAAM,QAAQ,QAAQ,OAAO,MAAM,GAAG,KAAK,SAAS,IAAI,CAAC,GAAG,MAAM,OAAO,IAAI,CAAC,EAAE;AAChF,QAAM,SAAsB;AAAA,IAC1B,OAAO,OAAO;AAAA,MACZ,SAAS,IAAI,CAAC,KAAK,MAAM;AAAA,QACvB,MAAM,CAAC,KAAK,OAAO,IAAI,CAAC;AAAA,QACxB,EAAE,OAAO,IAAI;AAAA,MACf,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,UAAU,MAAM;AACxB,CAAC;AAEH,QAAQ,MAAM;","names":[]}
@@ -0,0 +1,47 @@
1
+ import * as pty from 'node-pty';
2
+ import { EventEmitter } from 'events';
3
+
4
+ interface ProcessConfig {
5
+ cmd?: string[];
6
+ shell?: string;
7
+ cwd?: string;
8
+ env?: Record<string, string>;
9
+ autoRestart?: boolean;
10
+ }
11
+ interface PanexConfig {
12
+ procs: Record<string, ProcessConfig>;
13
+ settings?: {
14
+ mouse?: boolean;
15
+ };
16
+ }
17
+
18
+ declare function createTUI(config: PanexConfig): Promise<void>;
19
+
20
+ interface ManagedProcess {
21
+ name: string;
22
+ config: ProcessConfig;
23
+ pty: pty.IPty | null;
24
+ status: 'running' | 'stopped' | 'error';
25
+ output: string[];
26
+ exitCode: number | null;
27
+ }
28
+ declare class ProcessManager extends EventEmitter {
29
+ private procs;
30
+ private processes;
31
+ private maxOutputLines;
32
+ constructor(procs: Record<string, ProcessConfig>);
33
+ startAll(): Promise<void>;
34
+ start(name: string, config: ProcessConfig): Promise<void>;
35
+ restart(name: string): void;
36
+ restartAll(): void;
37
+ kill(name: string): void;
38
+ killAll(): void;
39
+ write(name: string, data: string): void;
40
+ resize(name: string, cols: number, rows: number): void;
41
+ getProcess(name: string): ManagedProcess | undefined;
42
+ getProcesses(): ManagedProcess[];
43
+ getNames(): string[];
44
+ getOutput(name: string): string;
45
+ }
46
+
47
+ export { type ManagedProcess, type PanexConfig, type ProcessConfig, ProcessManager, createTUI };
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ProcessManager,
4
+ createTUI
5
+ } from "./chunk-Z7LPKPEA.js";
6
+ export {
7
+ ProcessManager,
8
+ createTUI
9
+ };
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "panex",
3
+ "version": "0.9.0",
4
+ "description": "Terminal UI for running multiple processes in parallel",
5
+ "type": "module",
6
+ "bin": {
7
+ "panex": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "dev": "bun run src/cli.ts",
17
+ "build": "tsup",
18
+ "test": "bun test",
19
+ "prepublishOnly": "bun run build",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/king8fisher/panex.git"
25
+ },
26
+ "keywords": [
27
+ "tui",
28
+ "terminal",
29
+ "process",
30
+ "parallel",
31
+ "devtools",
32
+ "turbo",
33
+ "cli",
34
+ "concurrently"
35
+ ],
36
+ "author": "Anton Veretennikov (king8fisher)",
37
+ "license": "MIT",
38
+ "engines": {
39
+ "node": ">=18"
40
+ },
41
+ "dependencies": {
42
+ "blessed": "^0.1.81",
43
+ "chalk": "^5.3.0",
44
+ "commander": "^12.1.0",
45
+ "node-pty": "^1.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/blessed": "^0.1.25",
49
+ "@types/bun": "^1.3.5",
50
+ "@types/node": "^22.10.0",
51
+ "tsup": "^8.3.5",
52
+ "typescript": "^5.7.2"
53
+ }
54
+ }