numux 2.13.1 → 2.14.1

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.1" "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.1",
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 {
@@ -2931,6 +3022,19 @@ import {
2931
3022
  ScrollBoxRenderable
2932
3023
  } from "@opentui/core";
2933
3024
 
3025
+ // src/utils/timestamp.ts
3026
+ var DEFAULT_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
3027
+ function formatTimestamp(date, format) {
3028
+ const hours24 = date.getHours();
3029
+ const hours12 = hours24 % 12 || 12;
3030
+ return format.replace("YYYY", date.getFullYear().toString()).replace("MM", (date.getMonth() + 1).toString().padStart(2, "0")).replace("DD", date.getDate().toString().padStart(2, "0")).replace("HH", hours24.toString().padStart(2, "0")).replace("hh", hours12.toString().padStart(2, "0")).replace("mm", date.getMinutes().toString().padStart(2, "0")).replace("SSS", date.getMilliseconds().toString().padStart(3, "0")).replace("ss", date.getSeconds().toString().padStart(2, "0")).replace("A", hours24 < 12 ? "AM" : "PM");
3031
+ }
3032
+ function resolveTimestampFormat(timestamps) {
3033
+ if (!timestamps)
3034
+ return null;
3035
+ return typeof timestamps === "string" ? timestamps : DEFAULT_TIMESTAMP_FORMAT;
3036
+ }
3037
+
2934
3038
  // node_modules/ghostty-opentui/src/terminal-buffer.ts
2935
3039
  import {
2936
3040
  TextBufferRenderable,
@@ -3339,17 +3443,24 @@ Object.defineProperty(GhosttyTerminalRenderable.prototype, "lineInfo", {
3339
3443
  configurable: true
3340
3444
  });
3341
3445
 
3342
- // src/utils/timestamp.ts
3343
- var DEFAULT_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
3344
- function formatTimestamp(date, format) {
3345
- const hours24 = date.getHours();
3346
- const hours12 = hours24 % 12 || 12;
3347
- return format.replace("YYYY", date.getFullYear().toString()).replace("MM", (date.getMonth() + 1).toString().padStart(2, "0")).replace("DD", date.getDate().toString().padStart(2, "0")).replace("HH", hours24.toString().padStart(2, "0")).replace("hh", hours12.toString().padStart(2, "0")).replace("mm", date.getMinutes().toString().padStart(2, "0")).replace("SSS", date.getMilliseconds().toString().padStart(3, "0")).replace("ss", date.getSeconds().toString().padStart(2, "0")).replace("A", hours24 < 12 ? "AM" : "PM");
3348
- }
3349
- function resolveTimestampFormat(timestamps) {
3350
- if (!timestamps)
3351
- return null;
3352
- return typeof timestamps === "string" ? timestamps : DEFAULT_TIMESTAMP_FORMAT;
3446
+ // src/ui/tailing-terminal.ts
3447
+ class TailingTerminal extends GhosttyTerminalRenderable {
3448
+ constructor(ctx, options) {
3449
+ super(ctx, options);
3450
+ const self = this;
3451
+ const pt = self._persistentTerminal;
3452
+ if (!pt)
3453
+ return;
3454
+ const origGetJson = pt.getJson.bind(pt);
3455
+ pt.getJson = (opts = {}) => {
3456
+ if (opts.limit && opts.offset === undefined) {
3457
+ const peek = origGetJson({ limit: 1 });
3458
+ const offset = Math.max(0, peek.totalLines - opts.limit);
3459
+ return origGetJson({ offset, limit: opts.limit });
3460
+ }
3461
+ return origGetJson(opts);
3462
+ };
3463
+ }
3353
3464
  }
3354
3465
 
3355
3466
  // src/ui/url-handler.ts
@@ -3396,8 +3507,9 @@ function openLink(link) {
3396
3507
  }
3397
3508
 
3398
3509
  // src/ui/pane.ts
3399
- var MAX_SCROLLBACK_LINES = 50000;
3400
- var MAX_BUFFER_BYTES = 10 * 1024 * 1024;
3510
+ var RENDER_LIMIT = 5000;
3511
+ var MAX_SCROLLBACK_LINES = 1e6;
3512
+ var MAX_BUFFER_BYTES = 500 * 1024 * 1024;
3401
3513
 
3402
3514
  class Pane {
3403
3515
  scrollBox;
@@ -3423,13 +3535,14 @@ class Pane {
3423
3535
  visible: false,
3424
3536
  onMouseScroll: () => this._onScroll?.()
3425
3537
  });
3426
- this.terminal = new GhosttyTerminalRenderable(renderer, {
3538
+ this.terminal = new TailingTerminal(renderer, {
3427
3539
  id: `term-${name}`,
3428
3540
  cols,
3429
3541
  rows,
3430
3542
  persistent: true,
3431
3543
  showCursor: interactive,
3432
3544
  trimEnd: true,
3545
+ limit: RENDER_LIMIT,
3433
3546
  flexGrow: 1
3434
3547
  });
3435
3548
  const origOnSelectionChanged = this.terminal.onSelectionChanged.bind(this.terminal);
@@ -3463,7 +3576,7 @@ class Pane {
3463
3576
  }
3464
3577
  feed(data) {
3465
3578
  this.bytesFed += data.length;
3466
- if (this.terminal.lineCount > MAX_SCROLLBACK_LINES || this.bytesFed > MAX_BUFFER_BYTES) {
3579
+ if (this.lineCounter > MAX_SCROLLBACK_LINES || this.bytesFed > MAX_BUFFER_BYTES) {
3467
3580
  this.terminal.reset();
3468
3581
  this.bytesFed = 0;
3469
3582
  this.lineTimestamps = [];
@@ -3811,13 +3924,22 @@ class SearchController {
3811
3924
  }
3812
3925
 
3813
3926
  // src/ui/status-bar.ts
3814
- import { cyan, red, reverse, StyledText as StyledText2, TextRenderable, yellow } from "@opentui/core";
3927
+ import {
3928
+ BoxRenderable as BoxRenderable2,
3929
+ cyan,
3930
+ red,
3931
+ reverse,
3932
+ StyledText as StyledText2,
3933
+ TextRenderable as TextRenderable2,
3934
+ yellow
3935
+ } from "@opentui/core";
3815
3936
  function plain(text) {
3816
3937
  return { __isChunk: true, text };
3817
3938
  }
3818
3939
 
3819
3940
  class StatusBar {
3820
3941
  renderable;
3942
+ text;
3821
3943
  _searchMode = false;
3822
3944
  _searchQuery = "";
3823
3945
  _searchMatchCount = 0;
@@ -3825,16 +3947,23 @@ class StatusBar {
3825
3947
  _crossProcessInfo;
3826
3948
  _tempMessage = null;
3827
3949
  _tempTimer = null;
3950
+ _inputMode = false;
3828
3951
  constructor(renderer) {
3829
- this.renderable = new TextRenderable(renderer, {
3952
+ this.renderable = new BoxRenderable2(renderer, {
3830
3953
  id: "status-bar",
3831
3954
  width: "100%",
3955
+ backgroundColor: "#1a1a1a",
3956
+ paddingX: 1,
3957
+ minHeight: 1
3958
+ });
3959
+ this.text = new TextRenderable2(renderer, {
3960
+ id: "status-bar-text",
3961
+ width: "100%",
3832
3962
  wrapMode: "word",
3833
- content: this.buildContent(),
3834
- bg: "#1a1a1a",
3835
- paddingX: 1
3963
+ content: this.buildContent()
3836
3964
  });
3837
- this.renderable.selectable = false;
3965
+ this.text.selectable = false;
3966
+ this.renderable.add(this.text);
3838
3967
  }
3839
3968
  setSearchMode(active, query = "", matchCount = 0, currentIndex = -1, crossProcessInfo) {
3840
3969
  this._searchMode = active;
@@ -3842,19 +3971,23 @@ class StatusBar {
3842
3971
  this._searchMatchCount = matchCount;
3843
3972
  this._searchCurrentIndex = currentIndex;
3844
3973
  this._crossProcessInfo = crossProcessInfo;
3845
- this.renderable.content = this.buildContent();
3974
+ this.text.content = this.buildContent();
3846
3975
  }
3847
3976
  showTemporaryMessage(message, duration = 2000) {
3848
3977
  if (this._tempTimer)
3849
3978
  clearTimeout(this._tempTimer);
3850
3979
  this._tempMessage = message;
3851
- this.renderable.content = this.buildContent();
3980
+ this.text.content = this.buildContent();
3852
3981
  this._tempTimer = setTimeout(() => {
3853
3982
  this._tempMessage = null;
3854
3983
  this._tempTimer = null;
3855
- this.renderable.content = this.buildContent();
3984
+ this.text.content = this.buildContent();
3856
3985
  }, duration);
3857
3986
  }
3987
+ setInputMode(active) {
3988
+ this._inputMode = active;
3989
+ this.text.content = this.buildContent();
3990
+ }
3858
3991
  buildContent() {
3859
3992
  if (this._tempMessage) {
3860
3993
  return new StyledText2([cyan(this._tempMessage)]);
@@ -3862,6 +3995,9 @@ class StatusBar {
3862
3995
  if (this._searchMode) {
3863
3996
  return this.buildSearchContent();
3864
3997
  }
3998
+ if (this._inputMode) {
3999
+ return new StyledText2([yellow("INPUT"), plain(" Type to send input to process. "), plain("Esc: exit")]);
4000
+ }
3865
4001
  return new StyledText2([plain(STATUS_BAR_TEXT)]);
3866
4002
  }
3867
4003
  buildSearchContent() {
@@ -4150,8 +4286,10 @@ class App {
4150
4286
  panes = new Map;
4151
4287
  tabBar;
4152
4288
  statusBar;
4289
+ helpOverlay;
4153
4290
  search;
4154
4291
  activePane = null;
4292
+ inputMode = false;
4155
4293
  destroyed = false;
4156
4294
  names;
4157
4295
  termCols = 80;
@@ -4180,7 +4318,7 @@ class App {
4180
4318
  this.termCols = Math.max(40, width - this.sidebarWidth - 2);
4181
4319
  this.termRows = Math.max(5, height - 2);
4182
4320
  const { termCols, termRows } = this;
4183
- const layout = new BoxRenderable(this.renderer, {
4321
+ const layout = new BoxRenderable3(this.renderer, {
4184
4322
  id: "root",
4185
4323
  flexDirection: "column",
4186
4324
  width: "100%",
@@ -4189,14 +4327,14 @@ class App {
4189
4327
  });
4190
4328
  const processHexColors = buildProcessHexColorMap(this.names, this.config);
4191
4329
  this.tabBar = new TabBar(this.renderer, this.names, processHexColors);
4192
- const contentRow = new BoxRenderable(this.renderer, {
4330
+ const contentRow = new BoxRenderable3(this.renderer, {
4193
4331
  id: "content-row",
4194
4332
  flexDirection: "row",
4195
4333
  flexGrow: 1,
4196
4334
  width: "100%",
4197
4335
  border: false
4198
4336
  });
4199
- const sidebar = new BoxRenderable(this.renderer, {
4337
+ const sidebar = new BoxRenderable3(this.renderer, {
4200
4338
  id: "sidebar",
4201
4339
  width: this.sidebarWidth,
4202
4340
  height: "100%",
@@ -4204,12 +4342,13 @@ class App {
4204
4342
  borderColor: "#444"
4205
4343
  });
4206
4344
  sidebar.add(this.tabBar.renderable);
4207
- const paneContainer = new BoxRenderable(this.renderer, {
4345
+ const paneContainer = new BoxRenderable3(this.renderer, {
4208
4346
  id: "pane-container",
4209
4347
  flexGrow: 1,
4210
4348
  border: false
4211
4349
  });
4212
4350
  this.statusBar = new StatusBar(this.renderer);
4351
+ this.helpOverlay = new HelpOverlay(this.renderer);
4213
4352
  this.search = new SearchController({
4214
4353
  logWriter: this.logWriter,
4215
4354
  statusBar: this.statusBar,
@@ -4244,6 +4383,7 @@ class App {
4244
4383
  layout.add(contentRow);
4245
4384
  layout.add(this.statusBar.renderable);
4246
4385
  this.renderer.root.add(layout);
4386
+ this.renderer.root.add(this.helpOverlay.renderable);
4247
4387
  this.tabBar.onSelect((_index, name) => this.switchPane(name));
4248
4388
  this.tabBar.onSelectionChanged((_index, name) => this.switchPane(name));
4249
4389
  this.manager.on((event) => {
@@ -4280,6 +4420,14 @@ class App {
4280
4420
  this.renderer.keyInput.on("keypress", (key) => {
4281
4421
  log(key);
4282
4422
  if (key.ctrl && key.name === "c") {
4423
+ if (this.helpOverlay.isVisible) {
4424
+ this.helpOverlay.hide();
4425
+ return;
4426
+ }
4427
+ if (this.inputMode) {
4428
+ this.exitInputMode();
4429
+ return;
4430
+ }
4283
4431
  if (this.search.isActive) {
4284
4432
  this.search.exit();
4285
4433
  return;
@@ -4289,15 +4437,39 @@ class App {
4289
4437
  });
4290
4438
  return;
4291
4439
  }
4440
+ if (this.helpOverlay.isVisible) {
4441
+ if (key.name === "escape" || key.sequence === "?" || key.name === "h") {
4442
+ this.helpOverlay.hide();
4443
+ }
4444
+ return;
4445
+ }
4292
4446
  if (this.search.isActive) {
4293
4447
  this.search.handleInput(key);
4294
4448
  return;
4295
4449
  }
4450
+ if (this.inputMode && this.activePane) {
4451
+ if (key.name === "escape") {
4452
+ this.exitInputMode();
4453
+ return;
4454
+ }
4455
+ if (key.sequence) {
4456
+ this.manager.write(this.activePane, key.sequence);
4457
+ }
4458
+ return;
4459
+ }
4296
4460
  if (!this.activePane)
4297
4461
  return;
4298
4462
  const isInteractive = this.config.processes[this.activePane]?.interactive === true;
4299
4463
  if (!isInteractive) {
4300
4464
  const name = key.name.toLowerCase();
4465
+ if (key.sequence === "?" || name === "h") {
4466
+ this.helpOverlay.toggle();
4467
+ return;
4468
+ }
4469
+ if (name === "return") {
4470
+ this.enterInputMode();
4471
+ return;
4472
+ }
4301
4473
  if (key.shift && name === SHORTCUTS.scrollToBottom.key) {
4302
4474
  this.panes.get(this.activePane)?.scrollToBottom();
4303
4475
  return;
@@ -4366,6 +4538,15 @@ class App {
4366
4538
  this.switchPane(this.tabBar.getNameAtIndex(next));
4367
4539
  return;
4368
4540
  }
4541
+ if (name === "up" || name === "down") {
4542
+ const pane = this.panes.get(this.activePane);
4543
+ if (key.shift) {
4544
+ name === "up" ? pane?.scrollToTop() : pane?.scrollToBottom();
4545
+ } else {
4546
+ pane?.scrollBy(name === "up" ? -1 : 1);
4547
+ }
4548
+ return;
4549
+ }
4369
4550
  if (name === "pageup" || name === "pagedown") {
4370
4551
  const pane = this.panes.get(this.activePane);
4371
4552
  const delta = this.termRows - 2;
@@ -4392,9 +4573,33 @@ class App {
4392
4573
  }
4393
4574
  await this.manager.startAll(termCols, termRows);
4394
4575
  }
4576
+ enterInputMode() {
4577
+ this.inputMode = true;
4578
+ this.statusBar.setInputMode(true);
4579
+ if (this.activePane) {
4580
+ const pane = this.panes.get(this.activePane);
4581
+ if (pane)
4582
+ pane.terminal.showCursor = true;
4583
+ }
4584
+ }
4585
+ exitInputMode() {
4586
+ this.inputMode = false;
4587
+ this.statusBar.setInputMode(false);
4588
+ if (this.activePane) {
4589
+ const isInteractive = this.config.processes[this.activePane]?.interactive === true;
4590
+ if (!isInteractive) {
4591
+ const pane = this.panes.get(this.activePane);
4592
+ if (pane)
4593
+ pane.terminal.showCursor = false;
4594
+ }
4595
+ }
4596
+ }
4395
4597
  switchPane(name) {
4396
4598
  if (this.activePane === name)
4397
4599
  return;
4600
+ if (this.inputMode) {
4601
+ this.exitInputMode();
4602
+ }
4398
4603
  if (this.search.isActive && !this.search.isAllMode) {
4399
4604
  this.search.exit();
4400
4605
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "numux",
3
- "version": "2.13.1",
3
+ "version": "2.14.1",
4
4
  "description": "Terminal multiplexer with dependency orchestration",
5
5
  "type": "module",
6
6
  "license": "MIT",