numux 2.13.1 → 2.14.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/README.md CHANGED
@@ -477,14 +477,19 @@ Keybindings are shown in the status bar at the bottom of the app. Panes are read
477
477
  <!-- generated:keybindings -->
478
478
  | Key | Action |
479
479
  |-----|--------|
480
- | `←`/`→` or `1`-`9` | Tabs |
481
- | `G/Shift+G` | Top/bottom |
480
+ | `←`/`→` or `1`-`9` | Switch tabs |
481
+ | `Enter` | Input mode |
482
+ | `F` | Search |
482
483
  | `R` | Restart |
484
+ | `Shift+R` | Restart all |
483
485
  | `S` | Stop/start |
484
- | `F` | Search |
485
486
  | `Y` | Copy all |
486
487
  | `L` | Clear |
487
488
  | `T` | Timestamps |
489
+ | `↑↓` | Scroll line |
490
+ | `Shift+↑↓` | Top/bottom |
491
+ | `G/Shift+G` | Top/bottom |
492
+ | `PgUp/PgDn` | Scroll page |
488
493
  | `O` | Open logs |
489
494
  | `Ctrl+Click` | Open link |
490
495
  | `Ctrl+C` | Quit |
package/dist/man/numux.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH "NUMUX" "1" "April 2026" "2.13.1" "numux manual"
1
+ .TH "NUMUX" "1" "April 2026" "2.14.0" "numux manual"
2
2
  .SH "NAME"
3
3
  \fBnumux\fR
4
4
  .P
@@ -949,13 +949,19 @@ T}
949
949
  T{
950
950
  \fB←\fP/\fB→\fP or \fB1\fP\-\fB9\fP
951
951
  T}|T{
952
- Tabs
952
+ Switch tabs
953
953
  T}
954
954
  _
955
955
  T{
956
- \fBG/Shift+G\fP
956
+ \fBEnter\fP
957
957
  T}|T{
958
- Top/bottom
958
+ Input mode
959
+ T}
960
+ _
961
+ T{
962
+ \fBF\fP
963
+ T}|T{
964
+ Search
959
965
  T}
960
966
  _
961
967
  T{
@@ -965,15 +971,15 @@ Restart
965
971
  T}
966
972
  _
967
973
  T{
968
- \fBS\fP
974
+ \fBShift+R\fP
969
975
  T}|T{
970
- Stop/start
976
+ Restart all
971
977
  T}
972
978
  _
973
979
  T{
974
- \fBF\fP
980
+ \fBS\fP
975
981
  T}|T{
976
- Search
982
+ Stop/start
977
983
  T}
978
984
  _
979
985
  T{
@@ -995,6 +1001,30 @@ Timestamps
995
1001
  T}
996
1002
  _
997
1003
  T{
1004
+ \fB↑↓\fP
1005
+ T}|T{
1006
+ Scroll line
1007
+ T}
1008
+ _
1009
+ T{
1010
+ \fBShift+↑↓\fP
1011
+ T}|T{
1012
+ Top/bottom
1013
+ T}
1014
+ _
1015
+ T{
1016
+ \fBG/Shift+G\fP
1017
+ T}|T{
1018
+ Top/bottom
1019
+ T}
1020
+ _
1021
+ T{
1022
+ \fBPgUp/PgDn\fP
1023
+ T}|T{
1024
+ Scroll page
1025
+ T}
1026
+ _
1027
+ T{
998
1028
  \fBO\fP
999
1029
  T}|T{
1000
1030
  Open logs
package/dist/numux.js CHANGED
@@ -456,14 +456,19 @@ Unmatched references are left as-is (the shell will expand \`$db\` as empty + \`
456
456
  <!-- generated:keybindings -->
457
457
  | Key | Action |
458
458
  |-----|--------|
459
- | \`\u2190\`/\`\u2192\` or \`1\`-\`9\` | Tabs |
460
- | \`G/Shift+G\` | Top/bottom |
459
+ | \`\u2190\`/\`\u2192\` or \`1\`-\`9\` | Switch tabs |
460
+ | \`Enter\` | Input mode |
461
+ | \`F\` | Search |
461
462
  | \`R\` | Restart |
463
+ | \`Shift+R\` | Restart all |
462
464
  | \`S\` | Stop/start |
463
- | \`F\` | Search |
464
465
  | \`Y\` | Copy all |
465
466
  | \`L\` | Clear |
466
467
  | \`T\` | Timestamps |
468
+ | \`\u2191\u2193\` | Scroll line |
469
+ | \`Shift+\u2191\u2193\` | Top/bottom |
470
+ | \`G/Shift+G\` | Top/bottom |
471
+ | \`PgUp/PgDn\` | Scroll page |
467
472
  | \`O\` | Open logs |
468
473
  | \`Ctrl+Click\` | Open link |
469
474
  | \`Ctrl+C\` | Quit |
@@ -541,7 +546,7 @@ var init_help = __esm(() => {
541
546
  var require_package = __commonJS((exports, module) => {
542
547
  module.exports = {
543
548
  name: "numux",
544
- version: "2.13.1",
549
+ version: "2.14.0",
545
550
  description: "Terminal multiplexer with dependency orchestration",
546
551
  type: "module",
547
552
  license: "MIT",
@@ -2500,9 +2505,7 @@ class ProcessRunner {
2500
2505
  }
2501
2506
  }
2502
2507
  write(data) {
2503
- if (this.config.interactive && this.proc?.terminal) {
2504
- this.proc.terminal.write(data);
2505
- }
2508
+ this.proc?.terminal?.write(data);
2506
2509
  }
2507
2510
  }
2508
2511
 
@@ -2895,7 +2898,10 @@ function evaluateCondition(condition) {
2895
2898
  }
2896
2899
 
2897
2900
  // src/ui/app.ts
2898
- import { BoxRenderable, createCliRenderer } from "@opentui/core";
2901
+ import { BoxRenderable as BoxRenderable3, createCliRenderer } from "@opentui/core";
2902
+
2903
+ // src/ui/help-overlay.ts
2904
+ import { BoxRenderable, TextRenderable } from "@opentui/core";
2899
2905
 
2900
2906
  // src/ui/keybindings.ts
2901
2907
  var SHORTCUTS = {
@@ -2910,20 +2916,105 @@ var SHORTCUTS = {
2910
2916
  scrollToBottom: { key: "g", label: "Shift+G", description: "bottom", shift: true },
2911
2917
  openLogs: { key: "o", label: "O", description: "open logs" }
2912
2918
  };
2913
- var STATUS_HINTS = [
2914
- ["\u2190\u2192/1-9", "tabs"],
2919
+ function toHintPair(hint) {
2920
+ return Array.isArray(hint) ? hint : [hint.label, hint.description];
2921
+ }
2922
+ var STATUS_HINTS_COMPACT = [
2923
+ ["\u2190\u2192", "tabs"],
2924
+ SHORTCUTS.search,
2925
+ SHORTCUTS.copy,
2926
+ ["Enter", "input"],
2927
+ ["H", "help"]
2928
+ ];
2929
+ var STATUS_HINTS_FULL = [
2930
+ ["\u2190\u2192/1-9", "switch tabs"],
2931
+ ["Enter", "input mode"],
2932
+ SHORTCUTS.search,
2933
+ SHORTCUTS.restart,
2934
+ SHORTCUTS.restartAll,
2935
+ SHORTCUTS.stopStart,
2936
+ SHORTCUTS.copy,
2937
+ SHORTCUTS.clear,
2938
+ SHORTCUTS.timestamps,
2939
+ ["\u2191\u2193", "scroll line"],
2940
+ ["Shift+\u2191\u2193", "top/bottom"],
2915
2941
  ["G/Shift+G", "top/bottom"],
2916
- [SHORTCUTS.restart.label, SHORTCUTS.restart.description],
2917
- [SHORTCUTS.stopStart.label, SHORTCUTS.stopStart.description],
2918
- [SHORTCUTS.search.label, SHORTCUTS.search.description],
2919
- [SHORTCUTS.copy.label, SHORTCUTS.copy.description],
2920
- [SHORTCUTS.clear.label, SHORTCUTS.clear.description],
2921
- [SHORTCUTS.timestamps.label, SHORTCUTS.timestamps.description],
2922
- [SHORTCUTS.openLogs.label, SHORTCUTS.openLogs.description],
2942
+ ["PgUp/PgDn", "scroll page"],
2943
+ SHORTCUTS.openLogs,
2923
2944
  ["Ctrl+Click", "open link"],
2924
2945
  ["Ctrl+C", "quit"]
2925
2946
  ];
2926
- var STATUS_BAR_TEXT = STATUS_HINTS.map(([l, d]) => `${l}: ${d}`).join(" ");
2947
+ var STATUS_BAR_TEXT = STATUS_HINTS_COMPACT.map((h) => {
2948
+ const [l, d] = toHintPair(h);
2949
+ return `${l}: ${d}`;
2950
+ }).join(" ");
2951
+
2952
+ // src/ui/help-overlay.ts
2953
+ class HelpOverlay {
2954
+ renderable;
2955
+ textRenderable;
2956
+ constructor(renderer) {
2957
+ this.renderable = new BoxRenderable(renderer, {
2958
+ id: "help-overlay",
2959
+ position: "absolute",
2960
+ width: "100%",
2961
+ height: "100%",
2962
+ zIndex: 100,
2963
+ visible: false,
2964
+ justifyContent: "center",
2965
+ alignItems: "center"
2966
+ });
2967
+ const backdrop = new BoxRenderable(renderer, {
2968
+ id: "help-backdrop",
2969
+ position: "absolute",
2970
+ width: "100%",
2971
+ height: "100%",
2972
+ backgroundColor: "#000000",
2973
+ opacity: 0.7
2974
+ });
2975
+ const box = new BoxRenderable(renderer, {
2976
+ id: "help-box",
2977
+ flexDirection: "column",
2978
+ padding: 1,
2979
+ paddingX: 5,
2980
+ backgroundColor: "#1a1a2e",
2981
+ border: true,
2982
+ borderColor: "#444",
2983
+ zIndex: 101
2984
+ });
2985
+ const lines = [
2986
+ "Keyboard Shortcuts",
2987
+ "",
2988
+ ...STATUS_HINTS_FULL.map((h) => {
2989
+ const [label, desc] = toHintPair(h);
2990
+ return ` ${label.padEnd(14)} ${desc}`;
2991
+ }),
2992
+ "",
2993
+ "Press H or Esc to close"
2994
+ ];
2995
+ this.textRenderable = new TextRenderable(renderer, {
2996
+ id: "help-text",
2997
+ content: lines.join(`
2998
+ `),
2999
+ fg: "#cccccc"
3000
+ });
3001
+ box.add(this.textRenderable);
3002
+ this.renderable.add(backdrop);
3003
+ this.renderable.add(box);
3004
+ }
3005
+ get isVisible() {
3006
+ return this.renderable.visible;
3007
+ }
3008
+ toggle() {
3009
+ this.renderable.visible = !this.renderable.visible;
3010
+ }
3011
+ hide() {
3012
+ this.renderable.visible = false;
3013
+ }
3014
+ show() {
3015
+ this.renderable.visible = true;
3016
+ }
3017
+ }
2927
3018
 
2928
3019
  // src/ui/pane.ts
2929
3020
  import {
@@ -3811,13 +3902,22 @@ class SearchController {
3811
3902
  }
3812
3903
 
3813
3904
  // src/ui/status-bar.ts
3814
- import { cyan, red, reverse, StyledText as StyledText2, TextRenderable, yellow } from "@opentui/core";
3905
+ import {
3906
+ BoxRenderable as BoxRenderable2,
3907
+ cyan,
3908
+ red,
3909
+ reverse,
3910
+ StyledText as StyledText2,
3911
+ TextRenderable as TextRenderable2,
3912
+ yellow
3913
+ } from "@opentui/core";
3815
3914
  function plain(text) {
3816
3915
  return { __isChunk: true, text };
3817
3916
  }
3818
3917
 
3819
3918
  class StatusBar {
3820
3919
  renderable;
3920
+ text;
3821
3921
  _searchMode = false;
3822
3922
  _searchQuery = "";
3823
3923
  _searchMatchCount = 0;
@@ -3825,16 +3925,23 @@ class StatusBar {
3825
3925
  _crossProcessInfo;
3826
3926
  _tempMessage = null;
3827
3927
  _tempTimer = null;
3928
+ _inputMode = false;
3828
3929
  constructor(renderer) {
3829
- this.renderable = new TextRenderable(renderer, {
3930
+ this.renderable = new BoxRenderable2(renderer, {
3830
3931
  id: "status-bar",
3831
3932
  width: "100%",
3933
+ backgroundColor: "#1a1a1a",
3934
+ paddingX: 1,
3935
+ minHeight: 1
3936
+ });
3937
+ this.text = new TextRenderable2(renderer, {
3938
+ id: "status-bar-text",
3939
+ width: "100%",
3832
3940
  wrapMode: "word",
3833
- content: this.buildContent(),
3834
- bg: "#1a1a1a",
3835
- paddingX: 1
3941
+ content: this.buildContent()
3836
3942
  });
3837
- this.renderable.selectable = false;
3943
+ this.text.selectable = false;
3944
+ this.renderable.add(this.text);
3838
3945
  }
3839
3946
  setSearchMode(active, query = "", matchCount = 0, currentIndex = -1, crossProcessInfo) {
3840
3947
  this._searchMode = active;
@@ -3842,19 +3949,23 @@ class StatusBar {
3842
3949
  this._searchMatchCount = matchCount;
3843
3950
  this._searchCurrentIndex = currentIndex;
3844
3951
  this._crossProcessInfo = crossProcessInfo;
3845
- this.renderable.content = this.buildContent();
3952
+ this.text.content = this.buildContent();
3846
3953
  }
3847
3954
  showTemporaryMessage(message, duration = 2000) {
3848
3955
  if (this._tempTimer)
3849
3956
  clearTimeout(this._tempTimer);
3850
3957
  this._tempMessage = message;
3851
- this.renderable.content = this.buildContent();
3958
+ this.text.content = this.buildContent();
3852
3959
  this._tempTimer = setTimeout(() => {
3853
3960
  this._tempMessage = null;
3854
3961
  this._tempTimer = null;
3855
- this.renderable.content = this.buildContent();
3962
+ this.text.content = this.buildContent();
3856
3963
  }, duration);
3857
3964
  }
3965
+ setInputMode(active) {
3966
+ this._inputMode = active;
3967
+ this.text.content = this.buildContent();
3968
+ }
3858
3969
  buildContent() {
3859
3970
  if (this._tempMessage) {
3860
3971
  return new StyledText2([cyan(this._tempMessage)]);
@@ -3862,6 +3973,9 @@ class StatusBar {
3862
3973
  if (this._searchMode) {
3863
3974
  return this.buildSearchContent();
3864
3975
  }
3976
+ if (this._inputMode) {
3977
+ return new StyledText2([yellow("INPUT"), plain(" Type to send input to process. "), plain("Esc: exit")]);
3978
+ }
3865
3979
  return new StyledText2([plain(STATUS_BAR_TEXT)]);
3866
3980
  }
3867
3981
  buildSearchContent() {
@@ -4150,8 +4264,10 @@ class App {
4150
4264
  panes = new Map;
4151
4265
  tabBar;
4152
4266
  statusBar;
4267
+ helpOverlay;
4153
4268
  search;
4154
4269
  activePane = null;
4270
+ inputMode = false;
4155
4271
  destroyed = false;
4156
4272
  names;
4157
4273
  termCols = 80;
@@ -4180,7 +4296,7 @@ class App {
4180
4296
  this.termCols = Math.max(40, width - this.sidebarWidth - 2);
4181
4297
  this.termRows = Math.max(5, height - 2);
4182
4298
  const { termCols, termRows } = this;
4183
- const layout = new BoxRenderable(this.renderer, {
4299
+ const layout = new BoxRenderable3(this.renderer, {
4184
4300
  id: "root",
4185
4301
  flexDirection: "column",
4186
4302
  width: "100%",
@@ -4189,14 +4305,14 @@ class App {
4189
4305
  });
4190
4306
  const processHexColors = buildProcessHexColorMap(this.names, this.config);
4191
4307
  this.tabBar = new TabBar(this.renderer, this.names, processHexColors);
4192
- const contentRow = new BoxRenderable(this.renderer, {
4308
+ const contentRow = new BoxRenderable3(this.renderer, {
4193
4309
  id: "content-row",
4194
4310
  flexDirection: "row",
4195
4311
  flexGrow: 1,
4196
4312
  width: "100%",
4197
4313
  border: false
4198
4314
  });
4199
- const sidebar = new BoxRenderable(this.renderer, {
4315
+ const sidebar = new BoxRenderable3(this.renderer, {
4200
4316
  id: "sidebar",
4201
4317
  width: this.sidebarWidth,
4202
4318
  height: "100%",
@@ -4204,12 +4320,13 @@ class App {
4204
4320
  borderColor: "#444"
4205
4321
  });
4206
4322
  sidebar.add(this.tabBar.renderable);
4207
- const paneContainer = new BoxRenderable(this.renderer, {
4323
+ const paneContainer = new BoxRenderable3(this.renderer, {
4208
4324
  id: "pane-container",
4209
4325
  flexGrow: 1,
4210
4326
  border: false
4211
4327
  });
4212
4328
  this.statusBar = new StatusBar(this.renderer);
4329
+ this.helpOverlay = new HelpOverlay(this.renderer);
4213
4330
  this.search = new SearchController({
4214
4331
  logWriter: this.logWriter,
4215
4332
  statusBar: this.statusBar,
@@ -4244,6 +4361,7 @@ class App {
4244
4361
  layout.add(contentRow);
4245
4362
  layout.add(this.statusBar.renderable);
4246
4363
  this.renderer.root.add(layout);
4364
+ this.renderer.root.add(this.helpOverlay.renderable);
4247
4365
  this.tabBar.onSelect((_index, name) => this.switchPane(name));
4248
4366
  this.tabBar.onSelectionChanged((_index, name) => this.switchPane(name));
4249
4367
  this.manager.on((event) => {
@@ -4280,6 +4398,14 @@ class App {
4280
4398
  this.renderer.keyInput.on("keypress", (key) => {
4281
4399
  log(key);
4282
4400
  if (key.ctrl && key.name === "c") {
4401
+ if (this.helpOverlay.isVisible) {
4402
+ this.helpOverlay.hide();
4403
+ return;
4404
+ }
4405
+ if (this.inputMode) {
4406
+ this.exitInputMode();
4407
+ return;
4408
+ }
4283
4409
  if (this.search.isActive) {
4284
4410
  this.search.exit();
4285
4411
  return;
@@ -4289,15 +4415,39 @@ class App {
4289
4415
  });
4290
4416
  return;
4291
4417
  }
4418
+ if (this.helpOverlay.isVisible) {
4419
+ if (key.name === "escape" || key.sequence === "?" || key.name === "h") {
4420
+ this.helpOverlay.hide();
4421
+ }
4422
+ return;
4423
+ }
4292
4424
  if (this.search.isActive) {
4293
4425
  this.search.handleInput(key);
4294
4426
  return;
4295
4427
  }
4428
+ if (this.inputMode && this.activePane) {
4429
+ if (key.name === "escape") {
4430
+ this.exitInputMode();
4431
+ return;
4432
+ }
4433
+ if (key.sequence) {
4434
+ this.manager.write(this.activePane, key.sequence);
4435
+ }
4436
+ return;
4437
+ }
4296
4438
  if (!this.activePane)
4297
4439
  return;
4298
4440
  const isInteractive = this.config.processes[this.activePane]?.interactive === true;
4299
4441
  if (!isInteractive) {
4300
4442
  const name = key.name.toLowerCase();
4443
+ if (key.sequence === "?" || name === "h") {
4444
+ this.helpOverlay.toggle();
4445
+ return;
4446
+ }
4447
+ if (name === "return") {
4448
+ this.enterInputMode();
4449
+ return;
4450
+ }
4301
4451
  if (key.shift && name === SHORTCUTS.scrollToBottom.key) {
4302
4452
  this.panes.get(this.activePane)?.scrollToBottom();
4303
4453
  return;
@@ -4366,6 +4516,15 @@ class App {
4366
4516
  this.switchPane(this.tabBar.getNameAtIndex(next));
4367
4517
  return;
4368
4518
  }
4519
+ if (name === "up" || name === "down") {
4520
+ const pane = this.panes.get(this.activePane);
4521
+ if (key.shift) {
4522
+ name === "up" ? pane?.scrollToTop() : pane?.scrollToBottom();
4523
+ } else {
4524
+ pane?.scrollBy(name === "up" ? -1 : 1);
4525
+ }
4526
+ return;
4527
+ }
4369
4528
  if (name === "pageup" || name === "pagedown") {
4370
4529
  const pane = this.panes.get(this.activePane);
4371
4530
  const delta = this.termRows - 2;
@@ -4392,9 +4551,33 @@ class App {
4392
4551
  }
4393
4552
  await this.manager.startAll(termCols, termRows);
4394
4553
  }
4554
+ enterInputMode() {
4555
+ this.inputMode = true;
4556
+ this.statusBar.setInputMode(true);
4557
+ if (this.activePane) {
4558
+ const pane = this.panes.get(this.activePane);
4559
+ if (pane)
4560
+ pane.terminal.showCursor = true;
4561
+ }
4562
+ }
4563
+ exitInputMode() {
4564
+ this.inputMode = false;
4565
+ this.statusBar.setInputMode(false);
4566
+ if (this.activePane) {
4567
+ const isInteractive = this.config.processes[this.activePane]?.interactive === true;
4568
+ if (!isInteractive) {
4569
+ const pane = this.panes.get(this.activePane);
4570
+ if (pane)
4571
+ pane.terminal.showCursor = false;
4572
+ }
4573
+ }
4574
+ }
4395
4575
  switchPane(name) {
4396
4576
  if (this.activePane === name)
4397
4577
  return;
4578
+ if (this.inputMode) {
4579
+ this.exitInputMode();
4580
+ }
4398
4581
  if (this.search.isActive && !this.search.isAllMode) {
4399
4582
  this.search.exit();
4400
4583
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "numux",
3
- "version": "2.13.1",
3
+ "version": "2.14.0",
4
4
  "description": "Terminal multiplexer with dependency orchestration",
5
5
  "type": "module",
6
6
  "license": "MIT",