numux 2.13.0 → 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
@@ -226,7 +226,7 @@ export default defineConfig({
226
226
  | `--kill-others-on-fail` | Kill all processes when any exits with non-zero code |
227
227
  | `--max-restarts` `<n>` | Max auto-restarts for crashed processes |
228
228
  | `--no-watch` | Disable file watching even if config has watch patterns |
229
- | `-t,` `--timestamps` `[<format>]` | Add timestamps to output (default HH:mm:ss, or pass a format string) |
229
+ | `-t,` `--timestamps` `[<format>]` | Add timestamps to output (default HH:mm:ss.SSS, or pass a format string) |
230
230
  | `--log-dir` `<path>` | Write per-process logs to directory |
231
231
  | `--debug` | Enable debug logging to .numux/debug.log |
232
232
  | `-h,` `--help` | Show this help |
@@ -277,7 +277,7 @@ Top-level options apply to all processes (process-level settings override):
277
277
  | `watch` | `string \| string[]` | Global watch patterns, inherited by processes without their own watch |
278
278
  | `sort` | `'config' \| 'alphabetical' \| 'topological'` | Tab display order. `'config'` preserves definition order (package.json script order for wildcards), `'alphabetical'` sorts by process name, `'topological'` sorts by dependency tiers. |
279
279
  | `prefix` | `boolean` | Use prefixed output mode instead of TUI (for CI/scripts) |
280
- | `timestamps` | `boolean \| string` | Add timestamps to output lines. `true` uses default `HH:mm:ss` format, or pass a format string (e.g. `"HH:mm:ss.SSS"`) |
280
+ | `timestamps` | `boolean \| string` | Add timestamps to output lines. `true` uses default `HH:mm:ss.SSS` format, or pass a format string (e.g. `"HH:mm:ss"`) |
281
281
  | `killOthers` | `boolean` | Kill all processes when any one exits (regardless of exit code) |
282
282
  | `killOthersOnFail` | `boolean` | Kill all processes when any one exits with a non-zero exit code |
283
283
  | `noWatch` | `boolean` | Disable file watching even if processes have watch patterns |
@@ -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.0" "numux manual"
1
+ .TH "NUMUX" "1" "April 2026" "2.14.0" "numux manual"
2
2
  .SH "NAME"
3
3
  \fBnumux\fR
4
4
  .P
@@ -339,7 +339,7 @@ _
339
339
  T{
340
340
  \fB\-t,\fP \fB\-\-timestamps\fP \fB[<format>]\fP
341
341
  T}|T{
342
- Add timestamps to output (default HH:mm:ss, or pass a format string)
342
+ Add timestamps to output (default HH:mm:ss\.SSS, or pass a format string)
343
343
  T}
344
344
  _
345
345
  T{
@@ -502,7 +502,7 @@ T{
502
502
  T}|T{
503
503
  \fBboolean | string\fP
504
504
  T}|T{
505
- Add timestamps to output lines\. \fBtrue\fP uses default \fBHH:mm:ss\fP format, or pass a format string (e\.g\. \fB&quot;HH:mm:ss\.SSS&quot;\fP)
505
+ Add timestamps to output lines\. \fBtrue\fP uses default \fBHH:mm:ss\.SSS\fP format, or pass a format string (e\.g\. \fB&quot;HH:mm:ss&quot;\fP)
506
506
  T}
507
507
  _
508
508
  T{
@@ -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
@@ -244,7 +244,7 @@ export default defineConfig({
244
244
  | \`--kill-others-on-fail\` | Kill all processes when any exits with non-zero code |
245
245
  | \`--max-restarts\` \`<n>\` | Max auto-restarts for crashed processes |
246
246
  | \`--no-watch\` | Disable file watching even if config has watch patterns |
247
- | \`-t,\` \`--timestamps\` \`[<format>]\` | Add timestamps to output (default HH:mm:ss, or pass a format string) |
247
+ | \`-t,\` \`--timestamps\` \`[<format>]\` | Add timestamps to output (default HH:mm:ss.SSS, or pass a format string) |
248
248
  | \`--log-dir\` \`<path>\` | Write per-process logs to directory |
249
249
  | \`--debug\` | Enable debug logging to .numux/debug.log |
250
250
  | \`-h,\` \`--help\` | Show this help |
@@ -283,7 +283,7 @@ numux logs api | tail -f # Follow process log output
283
283
  | \`watch\` | \`string \\| string[]\` | Global watch patterns, inherited by processes without their own watch |
284
284
  | \`sort\` | \`'config' \\| 'alphabetical' \\| 'topological'\` | Tab display order. \`'config'\` preserves definition order (package.json script order for wildcards), \`'alphabetical'\` sorts by process name, \`'topological'\` sorts by dependency tiers. |
285
285
  | \`prefix\` | \`boolean\` | Use prefixed output mode instead of TUI (for CI/scripts) |
286
- | \`timestamps\` | \`boolean \\| string\` | Add timestamps to output lines. \`true\` uses default \`HH:mm:ss\` format, or pass a format string (e.g. \`"HH:mm:ss.SSS"\`) |
286
+ | \`timestamps\` | \`boolean \\| string\` | Add timestamps to output lines. \`true\` uses default \`HH:mm:ss.SSS\` format, or pass a format string (e.g. \`"HH:mm:ss"\`) |
287
287
  | \`killOthers\` | \`boolean\` | Kill all processes when any one exits (regardless of exit code) |
288
288
  | \`killOthersOnFail\` | \`boolean\` | Kill all processes when any one exits with a non-zero exit code |
289
289
  | \`noWatch\` | \`boolean\` | Disable file watching even if processes have watch patterns |
@@ -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.0",
549
+ version: "2.14.0",
545
550
  description: "Terminal multiplexer with dependency orchestration",
546
551
  type: "module",
547
552
  license: "MIT",
@@ -857,7 +862,7 @@ var FLAGS = [
857
862
  long: "--timestamps",
858
863
  short: "-t",
859
864
  key: "timestamps",
860
- description: "Add timestamps to output (default HH:mm:ss, or pass a format string)",
865
+ description: "Add timestamps to output (default HH:mm:ss.SSS, or pass a format string)",
861
866
  valueName: "<format>",
862
867
  completionHint: "none"
863
868
  },
@@ -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 {
@@ -3340,7 +3431,7 @@ Object.defineProperty(GhosttyTerminalRenderable.prototype, "lineInfo", {
3340
3431
  });
3341
3432
 
3342
3433
  // src/utils/timestamp.ts
3343
- var DEFAULT_TIMESTAMP_FORMAT = "HH:mm:ss";
3434
+ var DEFAULT_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
3344
3435
  function formatTimestamp(date, format) {
3345
3436
  const hours24 = date.getHours();
3346
3437
  const hours12 = hours24 % 12 || 12;
@@ -3601,18 +3692,16 @@ class Pane {
3601
3692
  get timestampsEnabled() {
3602
3693
  return this._timestampFormat !== null;
3603
3694
  }
3695
+ getTimestampSigns() {
3696
+ return this.timestampGutter?.getLineSigns() ?? null;
3697
+ }
3604
3698
  updateTimestampSigns() {
3605
3699
  if (!(this.timestampGutter && this._timestampFormat))
3606
3700
  return;
3607
3701
  const fmt = this._timestampFormat;
3608
3702
  const signs = new Map;
3609
- let prevFormatted = "";
3610
3703
  for (let i = 0;i < this.lineTimestamps.length; i++) {
3611
- const formatted = formatTimestamp(new Date(this.lineTimestamps[i]), fmt);
3612
- if (formatted !== prevFormatted) {
3613
- signs.set(i, { before: formatted });
3614
- prevFormatted = formatted;
3615
- }
3704
+ signs.set(i, { before: formatTimestamp(new Date(this.lineTimestamps[i]), fmt) });
3616
3705
  }
3617
3706
  this.timestampGutter.setLineSigns(signs);
3618
3707
  }
@@ -3813,13 +3902,22 @@ class SearchController {
3813
3902
  }
3814
3903
 
3815
3904
  // src/ui/status-bar.ts
3816
- 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";
3817
3914
  function plain(text) {
3818
3915
  return { __isChunk: true, text };
3819
3916
  }
3820
3917
 
3821
3918
  class StatusBar {
3822
3919
  renderable;
3920
+ text;
3823
3921
  _searchMode = false;
3824
3922
  _searchQuery = "";
3825
3923
  _searchMatchCount = 0;
@@ -3827,16 +3925,23 @@ class StatusBar {
3827
3925
  _crossProcessInfo;
3828
3926
  _tempMessage = null;
3829
3927
  _tempTimer = null;
3928
+ _inputMode = false;
3830
3929
  constructor(renderer) {
3831
- this.renderable = new TextRenderable(renderer, {
3930
+ this.renderable = new BoxRenderable2(renderer, {
3832
3931
  id: "status-bar",
3833
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%",
3834
3940
  wrapMode: "word",
3835
- content: this.buildContent(),
3836
- bg: "#1a1a1a",
3837
- paddingX: 1
3941
+ content: this.buildContent()
3838
3942
  });
3839
- this.renderable.selectable = false;
3943
+ this.text.selectable = false;
3944
+ this.renderable.add(this.text);
3840
3945
  }
3841
3946
  setSearchMode(active, query = "", matchCount = 0, currentIndex = -1, crossProcessInfo) {
3842
3947
  this._searchMode = active;
@@ -3844,19 +3949,23 @@ class StatusBar {
3844
3949
  this._searchMatchCount = matchCount;
3845
3950
  this._searchCurrentIndex = currentIndex;
3846
3951
  this._crossProcessInfo = crossProcessInfo;
3847
- this.renderable.content = this.buildContent();
3952
+ this.text.content = this.buildContent();
3848
3953
  }
3849
3954
  showTemporaryMessage(message, duration = 2000) {
3850
3955
  if (this._tempTimer)
3851
3956
  clearTimeout(this._tempTimer);
3852
3957
  this._tempMessage = message;
3853
- this.renderable.content = this.buildContent();
3958
+ this.text.content = this.buildContent();
3854
3959
  this._tempTimer = setTimeout(() => {
3855
3960
  this._tempMessage = null;
3856
3961
  this._tempTimer = null;
3857
- this.renderable.content = this.buildContent();
3962
+ this.text.content = this.buildContent();
3858
3963
  }, duration);
3859
3964
  }
3965
+ setInputMode(active) {
3966
+ this._inputMode = active;
3967
+ this.text.content = this.buildContent();
3968
+ }
3860
3969
  buildContent() {
3861
3970
  if (this._tempMessage) {
3862
3971
  return new StyledText2([cyan(this._tempMessage)]);
@@ -3864,6 +3973,9 @@ class StatusBar {
3864
3973
  if (this._searchMode) {
3865
3974
  return this.buildSearchContent();
3866
3975
  }
3976
+ if (this._inputMode) {
3977
+ return new StyledText2([yellow("INPUT"), plain(" Type to send input to process. "), plain("Esc: exit")]);
3978
+ }
3867
3979
  return new StyledText2([plain(STATUS_BAR_TEXT)]);
3868
3980
  }
3869
3981
  buildSearchContent() {
@@ -4152,8 +4264,10 @@ class App {
4152
4264
  panes = new Map;
4153
4265
  tabBar;
4154
4266
  statusBar;
4267
+ helpOverlay;
4155
4268
  search;
4156
4269
  activePane = null;
4270
+ inputMode = false;
4157
4271
  destroyed = false;
4158
4272
  names;
4159
4273
  termCols = 80;
@@ -4182,7 +4296,7 @@ class App {
4182
4296
  this.termCols = Math.max(40, width - this.sidebarWidth - 2);
4183
4297
  this.termRows = Math.max(5, height - 2);
4184
4298
  const { termCols, termRows } = this;
4185
- const layout = new BoxRenderable(this.renderer, {
4299
+ const layout = new BoxRenderable3(this.renderer, {
4186
4300
  id: "root",
4187
4301
  flexDirection: "column",
4188
4302
  width: "100%",
@@ -4191,14 +4305,14 @@ class App {
4191
4305
  });
4192
4306
  const processHexColors = buildProcessHexColorMap(this.names, this.config);
4193
4307
  this.tabBar = new TabBar(this.renderer, this.names, processHexColors);
4194
- const contentRow = new BoxRenderable(this.renderer, {
4308
+ const contentRow = new BoxRenderable3(this.renderer, {
4195
4309
  id: "content-row",
4196
4310
  flexDirection: "row",
4197
4311
  flexGrow: 1,
4198
4312
  width: "100%",
4199
4313
  border: false
4200
4314
  });
4201
- const sidebar = new BoxRenderable(this.renderer, {
4315
+ const sidebar = new BoxRenderable3(this.renderer, {
4202
4316
  id: "sidebar",
4203
4317
  width: this.sidebarWidth,
4204
4318
  height: "100%",
@@ -4206,12 +4320,13 @@ class App {
4206
4320
  borderColor: "#444"
4207
4321
  });
4208
4322
  sidebar.add(this.tabBar.renderable);
4209
- const paneContainer = new BoxRenderable(this.renderer, {
4323
+ const paneContainer = new BoxRenderable3(this.renderer, {
4210
4324
  id: "pane-container",
4211
4325
  flexGrow: 1,
4212
4326
  border: false
4213
4327
  });
4214
4328
  this.statusBar = new StatusBar(this.renderer);
4329
+ this.helpOverlay = new HelpOverlay(this.renderer);
4215
4330
  this.search = new SearchController({
4216
4331
  logWriter: this.logWriter,
4217
4332
  statusBar: this.statusBar,
@@ -4246,6 +4361,7 @@ class App {
4246
4361
  layout.add(contentRow);
4247
4362
  layout.add(this.statusBar.renderable);
4248
4363
  this.renderer.root.add(layout);
4364
+ this.renderer.root.add(this.helpOverlay.renderable);
4249
4365
  this.tabBar.onSelect((_index, name) => this.switchPane(name));
4250
4366
  this.tabBar.onSelectionChanged((_index, name) => this.switchPane(name));
4251
4367
  this.manager.on((event) => {
@@ -4282,6 +4398,14 @@ class App {
4282
4398
  this.renderer.keyInput.on("keypress", (key) => {
4283
4399
  log(key);
4284
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
+ }
4285
4409
  if (this.search.isActive) {
4286
4410
  this.search.exit();
4287
4411
  return;
@@ -4291,15 +4415,39 @@ class App {
4291
4415
  });
4292
4416
  return;
4293
4417
  }
4418
+ if (this.helpOverlay.isVisible) {
4419
+ if (key.name === "escape" || key.sequence === "?" || key.name === "h") {
4420
+ this.helpOverlay.hide();
4421
+ }
4422
+ return;
4423
+ }
4294
4424
  if (this.search.isActive) {
4295
4425
  this.search.handleInput(key);
4296
4426
  return;
4297
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
+ }
4298
4438
  if (!this.activePane)
4299
4439
  return;
4300
4440
  const isInteractive = this.config.processes[this.activePane]?.interactive === true;
4301
4441
  if (!isInteractive) {
4302
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
+ }
4303
4451
  if (key.shift && name === SHORTCUTS.scrollToBottom.key) {
4304
4452
  this.panes.get(this.activePane)?.scrollToBottom();
4305
4453
  return;
@@ -4368,6 +4516,15 @@ class App {
4368
4516
  this.switchPane(this.tabBar.getNameAtIndex(next));
4369
4517
  return;
4370
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
+ }
4371
4528
  if (name === "pageup" || name === "pagedown") {
4372
4529
  const pane = this.panes.get(this.activePane);
4373
4530
  const delta = this.termRows - 2;
@@ -4394,9 +4551,33 @@ class App {
4394
4551
  }
4395
4552
  await this.manager.startAll(termCols, termRows);
4396
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
+ }
4397
4575
  switchPane(name) {
4398
4576
  if (this.activePane === name)
4399
4577
  return;
4578
+ if (this.inputMode) {
4579
+ this.exitInputMode();
4580
+ }
4400
4581
  if (this.search.isActive && !this.search.isAllMode) {
4401
4582
  this.search.exit();
4402
4583
  }
package/dist/types.d.ts CHANGED
@@ -102,7 +102,7 @@ export interface NumuxConfig<K extends string = string> {
102
102
  * @default false
103
103
  */
104
104
  prefix?: boolean;
105
- /** Add timestamps to output lines. `true` uses default `HH:mm:ss` format, or pass a format string (e.g. `"HH:mm:ss.SSS"`) */
105
+ /** Add timestamps to output lines. `true` uses default `HH:mm:ss.SSS` format, or pass a format string (e.g. `"HH:mm:ss"`) */
106
106
  timestamps?: boolean | string;
107
107
  /**
108
108
  * Kill all processes when any one exits (regardless of exit code)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "numux",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "Terminal multiplexer with dependency orchestration",
5
5
  "type": "module",
6
6
  "license": "MIT",