numux 1.5.2 → 1.7.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.
Files changed (3) hide show
  1. package/README.md +16 -13
  2. package/dist/numux.js +52 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -178,7 +178,7 @@ Each process accepts:
178
178
  | `delay` | `number` | — | Milliseconds to wait before starting the process |
179
179
  | `condition` | `string` | — | Env var name; process skipped if falsy. Prefix with `!` to negate |
180
180
  | `stopSignal` | `string` | `SIGTERM` | Signal for graceful stop (`SIGTERM`, `SIGINT`, or `SIGHUP`) |
181
- | `color` | `string` | auto | Hex color for tab icon and status bar (e.g. `"#ff6600"`) |
181
+ | `color` | `string \| string[]` | auto | Hex (e.g. `"#ff6600"`) or basic name: black, red, green, yellow, blue, magenta, cyan, white, gray, orange, purple |
182
182
  | `watch` | `string \| string[]` | — | Glob patterns — restart process when matching files change |
183
183
  | `interactive` | `boolean` | `false` | When `true`, keyboard input is forwarded to the process |
184
184
 
@@ -268,18 +268,15 @@ Persistent processes that crash are auto-restarted with exponential backoff (1s
268
268
  | Key | Action |
269
269
  |-----|--------|
270
270
  | `Ctrl+C` | Quit (graceful shutdown) |
271
- | `Alt+R` | Restart active process |
272
- | `Alt+Shift+R` | Restart all processes |
273
- | `Alt+S` | Stop/start active process |
274
- | `Alt+L` | Clear active pane output |
275
- | `Alt+1`–`Alt+9` | Jump to tab |
276
- | `Alt+Left/Right` | Cycle tabs |
277
- | `Up/Down` | Navigate between tabs |
278
- | `PageUp/PageDown` | Scroll output by page (non-interactive panes) |
279
- | `Home/End` | Scroll to top/bottom (non-interactive panes) |
280
- | `Alt+PageUp/PageDown` | Scroll output up/down |
281
- | `Alt+Home/End` | Scroll to top/bottom |
282
- | `Alt+F` | Search in active pane output |
271
+ | `R` | Restart active process |
272
+ | `Shift+R` | Restart all processes |
273
+ | `S` | Stop/start active process |
274
+ | `L` | Clear active pane output |
275
+ | `F` | Search in active pane output |
276
+ | `1`–`9` | Jump to tab |
277
+ | `Left/Right` | Cycle tabs |
278
+ | `PageUp/PageDown` | Scroll output by page |
279
+ | `Home/End` | Scroll to top/bottom |
283
280
 
284
281
  While searching: type to filter, `Enter`/`Shift+Enter` to navigate matches, `Escape` to close.
285
282
 
@@ -298,6 +295,12 @@ Panes are readonly by default — keyboard input is not forwarded to processes.
298
295
  | ✖ | Failed |
299
296
  | ⊘ | Skipped |
300
297
 
298
+ ## Dependencies
299
+
300
+ ### ghostty-opentui
301
+
302
+ Despite the name, [`ghostty-opentui`](https://github.com/user/ghostty-opentui) is **not** a compatibility layer for the [Ghostty](https://ghostty.org) terminal. It uses Ghostty's Zig-based VT parser as the ANSI terminal emulation engine for OpenTUI's terminal renderable. It works in any terminal emulator (iTerm, Kitty, Alacritty, WezTerm, etc.) and adds ~8MB to install size due to native binaries.
303
+
301
304
  ## License
302
305
 
303
306
  MIT
package/dist/numux.js CHANGED
@@ -22,7 +22,7 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
22
22
  var require_package = __commonJS((exports, module) => {
23
23
  module.exports = {
24
24
  name: "numux",
25
- version: "1.5.2",
25
+ version: "1.7.0",
26
26
  description: "Terminal multiplexer with dependency orchestration",
27
27
  type: "module",
28
28
  license: "MIT",
@@ -382,15 +382,18 @@ function detectPackageManager(pkgJson, cwd) {
382
382
  }
383
383
  return "npm";
384
384
  }
385
+ function isGlobPattern(name) {
386
+ return /[*?[]/.test(name);
387
+ }
385
388
  function expandScriptPatterns(config, cwd) {
386
389
  const entries = Object.entries(config.processes);
387
- const hasWildcard = entries.some(([name]) => name.startsWith("npm:"));
390
+ const hasWildcard = entries.some(([name]) => name.startsWith("npm:") || isGlobPattern(name));
388
391
  if (!hasWildcard)
389
392
  return config;
390
393
  const dir = config.cwd ?? cwd ?? process.cwd();
391
394
  const pkgPath = resolve(dir, "package.json");
392
395
  if (!existsSync(pkgPath)) {
393
- throw new Error(`npm: patterns require a package.json (looked in ${dir})`);
396
+ throw new Error(`Wildcard patterns require a package.json (looked in ${dir})`);
394
397
  }
395
398
  const pkgJson = JSON.parse(readFileSync(pkgPath, "utf-8"));
396
399
  const scripts = pkgJson.scripts;
@@ -401,11 +404,11 @@ function expandScriptPatterns(config, cwd) {
401
404
  const pm = detectPackageManager(pkgJson, dir);
402
405
  const expanded = {};
403
406
  for (const [name, value] of entries) {
404
- if (!name.startsWith("npm:")) {
407
+ if (!(name.startsWith("npm:") || isGlobPattern(name))) {
405
408
  expanded[name] = value;
406
409
  continue;
407
410
  }
408
- const pattern = name.slice(4);
411
+ const pattern = name.startsWith("npm:") ? name.slice(4) : name;
409
412
  const template = value ?? {};
410
413
  if (template.command) {
411
414
  throw new Error(`"${name}": wildcard processes cannot have a "command" field (commands come from package.json scripts)`);
@@ -589,6 +592,31 @@ function findCycle(remaining, config) {
589
592
  }
590
593
 
591
594
  // src/utils/color.ts
595
+ var BASIC_COLORS = {
596
+ black: "#000000",
597
+ red: "#ff0000",
598
+ green: "#00ff00",
599
+ yellow: "#ffff00",
600
+ blue: "#0000ff",
601
+ magenta: "#ff00ff",
602
+ cyan: "#00ffff",
603
+ white: "#ffffff",
604
+ gray: "#808080",
605
+ grey: "#808080",
606
+ orange: "#ffa500",
607
+ purple: "#800080"
608
+ };
609
+ function isValidColor(color) {
610
+ if (HEX_COLOR_RE.test(color))
611
+ return true;
612
+ return color.toLowerCase() in BASIC_COLORS;
613
+ }
614
+ function resolveToHex(color) {
615
+ if (HEX_COLOR_RE.test(color))
616
+ return color.startsWith("#") ? color : `#${color}`;
617
+ const hex = BASIC_COLORS[color.toLowerCase()];
618
+ return hex ?? "";
619
+ }
592
620
  function hexToAnsi(hex) {
593
621
  const h = hex.replace("#", "");
594
622
  const r = Number.parseInt(h.slice(0, 2), 16);
@@ -638,7 +666,11 @@ function buildProcessColorMap(names, config) {
638
666
  for (const name of names) {
639
667
  const explicit = resolveColor(config.processes[name]?.color);
640
668
  if (explicit) {
641
- map.set(name, hexToAnsi(explicit));
669
+ const hex = resolveToHex(explicit);
670
+ if (hex)
671
+ map.set(name, hexToAnsi(hex));
672
+ else
673
+ map.set(name, DEFAULT_ANSI_COLORS[paletteIndex++ % DEFAULT_ANSI_COLORS.length]);
642
674
  } else {
643
675
  map.set(name, DEFAULT_ANSI_COLORS[paletteIndex % DEFAULT_ANSI_COLORS.length]);
644
676
  paletteIndex++;
@@ -654,7 +686,11 @@ function buildProcessHexColorMap(names, config) {
654
686
  for (const name of names) {
655
687
  const explicit = resolveColor(config.processes[name]?.color);
656
688
  if (explicit) {
657
- map.set(name, explicit.startsWith("#") ? explicit : `#${explicit}`);
689
+ const hex = resolveToHex(explicit);
690
+ if (hex)
691
+ map.set(name, hex);
692
+ else
693
+ map.set(name, DEFAULT_HEX_COLORS[paletteIndex++ % DEFAULT_HEX_COLORS.length]);
658
694
  } else {
659
695
  map.set(name, DEFAULT_HEX_COLORS[paletteIndex % DEFAULT_HEX_COLORS.length]);
660
696
  paletteIndex++;
@@ -718,13 +754,13 @@ function validateConfig(raw, warnings) {
718
754
  }
719
755
  }
720
756
  if (typeof p.color === "string") {
721
- if (!HEX_COLOR_RE.test(p.color)) {
722
- throw new Error(`Process "${name}".color must be a valid hex color (e.g. "#ff8800"), got "${p.color}"`);
757
+ if (!isValidColor(p.color)) {
758
+ throw new Error(`Process "${name}".color must be a hex color (e.g. "#ff8800") or basic name (black, red, green, yellow, blue, magenta, cyan, white, gray, orange, purple), got "${p.color}"`);
723
759
  }
724
760
  } else if (Array.isArray(p.color)) {
725
761
  for (const c of p.color) {
726
- if (typeof c !== "string" || !HEX_COLOR_RE.test(c)) {
727
- throw new Error(`Process "${name}".color entries must be valid hex colors (e.g. "#ff8800"), got "${c}"`);
762
+ if (typeof c !== "string" || !isValidColor(c)) {
763
+ throw new Error(`Process "${name}".color entries must be hex or basic names (black, red, green, yellow, blue, magenta, cyan, white, gray, orange), got "${c}"`);
728
764
  }
729
765
  }
730
766
  }
@@ -2400,7 +2436,7 @@ Usage:
2400
2436
 
2401
2437
  Options:
2402
2438
  -n, --name <name=command> Add a named process
2403
- -c, --color <colors> Comma-separated colors for processes (hex, e.g. #ff0,#0f0)
2439
+ -c, --color <colors> Comma-separated colors (hex or names: black, red, green, yellow, blue, magenta, cyan, white, gray, orange, purple)
2404
2440
  --config <path> Config file path (default: auto-detect)
2405
2441
  -p, --prefix Prefixed output mode (no TUI, for CI/scripts)
2406
2442
  --only <a,b,...> Only run these processes (+ their dependencies)
@@ -2531,10 +2567,11 @@ async function main() {
2531
2567
  let config;
2532
2568
  const warnings = [];
2533
2569
  if (parsed.commands.length > 0 || parsed.named.length > 0) {
2534
- const hasNpmPatterns = parsed.commands.some((c) => c.startsWith("npm:"));
2570
+ const isScriptPattern = (c) => c.startsWith("npm:") || /[*?[]/.test(c);
2571
+ const hasNpmPatterns = parsed.commands.some(isScriptPattern);
2535
2572
  if (hasNpmPatterns) {
2536
- const npmPatterns = parsed.commands.filter((c) => c.startsWith("npm:"));
2537
- const otherCommands = parsed.commands.filter((c) => !c.startsWith("npm:"));
2573
+ const npmPatterns = parsed.commands.filter(isScriptPattern);
2574
+ const otherCommands = parsed.commands.filter((c) => !isScriptPattern(c));
2538
2575
  const processes = {};
2539
2576
  for (const pattern of npmPatterns) {
2540
2577
  const entry = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "numux",
3
- "version": "1.5.2",
3
+ "version": "1.7.0",
4
4
  "description": "Terminal multiplexer with dependency orchestration",
5
5
  "type": "module",
6
6
  "license": "MIT",