numux 1.8.0 → 1.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/README.md +1 -16
- package/dist/numux.js +76 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -265,22 +265,7 @@ Persistent processes that crash are auto-restarted with exponential backoff (1s
|
|
|
265
265
|
|
|
266
266
|
## Keybindings
|
|
267
267
|
|
|
268
|
-
|
|
269
|
-
|-----|--------|
|
|
270
|
-
| `Ctrl+C` | Quit (graceful shutdown) |
|
|
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 |
|
|
280
|
-
|
|
281
|
-
While searching: type to filter, `Enter`/`Shift+Enter` to navigate matches, `Escape` to close.
|
|
282
|
-
|
|
283
|
-
Panes are readonly by default — keyboard input is not forwarded to processes. Set `interactive: true` on processes that need stdin (REPLs, shells, etc.).
|
|
268
|
+
Keybindings are shown in the status bar at the bottom of the app. Panes are readonly by default — keyboard input is not forwarded to processes. Set `interactive: true` on processes that need stdin (REPLs, shells, etc.).
|
|
284
269
|
|
|
285
270
|
## Tab icons
|
|
286
271
|
|
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.
|
|
25
|
+
version: "1.9.0",
|
|
26
26
|
description: "Terminal multiplexer with dependency orchestration",
|
|
27
27
|
type: "module",
|
|
28
28
|
license: "MIT",
|
|
@@ -1549,6 +1549,26 @@ function evaluateCondition(condition) {
|
|
|
1549
1549
|
// src/ui/app.ts
|
|
1550
1550
|
import { BoxRenderable, createCliRenderer } from "@opentui/core";
|
|
1551
1551
|
|
|
1552
|
+
// src/ui/keybindings.ts
|
|
1553
|
+
var SHORTCUTS = {
|
|
1554
|
+
restartAll: { key: "r", label: "Shift+R", description: "restart all", shift: true },
|
|
1555
|
+
copy: { key: "y", label: "Y", description: "copy" },
|
|
1556
|
+
search: { key: "f", label: "F", description: "search" },
|
|
1557
|
+
restart: { key: "r", label: "R", description: "restart" },
|
|
1558
|
+
stopStart: { key: "s", label: "S", description: "stop/start" },
|
|
1559
|
+
clear: { key: "l", label: "L", description: "clear" }
|
|
1560
|
+
};
|
|
1561
|
+
var STATUS_HINTS = [
|
|
1562
|
+
["\u2190\u2192/1-9", "tabs"],
|
|
1563
|
+
[SHORTCUTS.restart.label, SHORTCUTS.restart.description],
|
|
1564
|
+
[SHORTCUTS.stopStart.label, SHORTCUTS.stopStart.description],
|
|
1565
|
+
[SHORTCUTS.search.label, SHORTCUTS.search.description],
|
|
1566
|
+
[SHORTCUTS.copy.label, SHORTCUTS.copy.description],
|
|
1567
|
+
[SHORTCUTS.clear.label, SHORTCUTS.clear.description],
|
|
1568
|
+
["Ctrl+C", "quit"]
|
|
1569
|
+
];
|
|
1570
|
+
var STATUS_BAR_TEXT = STATUS_HINTS.map(([l, d]) => `${l}: ${d}`).join(" ");
|
|
1571
|
+
|
|
1552
1572
|
// src/ui/pane.ts
|
|
1553
1573
|
import { ScrollBoxRenderable } from "@opentui/core";
|
|
1554
1574
|
import { GhosttyTerminalRenderable } from "ghostty-opentui/terminal-buffer";
|
|
@@ -1558,6 +1578,7 @@ class Pane {
|
|
|
1558
1578
|
terminal;
|
|
1559
1579
|
decoder = new TextDecoder;
|
|
1560
1580
|
_onScroll = null;
|
|
1581
|
+
_onCopy = null;
|
|
1561
1582
|
constructor(renderer, name, cols, rows, interactive = false) {
|
|
1562
1583
|
this.scrollBox = new ScrollBoxRenderable(renderer, {
|
|
1563
1584
|
id: `pane-${name}`,
|
|
@@ -1576,6 +1597,18 @@ class Pane {
|
|
|
1576
1597
|
showCursor: interactive,
|
|
1577
1598
|
trimEnd: true
|
|
1578
1599
|
});
|
|
1600
|
+
const origOnSelectionChanged = this.terminal.onSelectionChanged.bind(this.terminal);
|
|
1601
|
+
this.terminal.onSelectionChanged = (selection) => {
|
|
1602
|
+
const result = origOnSelectionChanged(selection);
|
|
1603
|
+
if (selection?.isActive && !selection.isDragging) {
|
|
1604
|
+
const text = selection.getSelectedText();
|
|
1605
|
+
if (text) {
|
|
1606
|
+
renderer.copyToClipboardOSC52(text);
|
|
1607
|
+
this._onCopy?.(text);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return result;
|
|
1611
|
+
};
|
|
1579
1612
|
this.scrollBox.add(this.terminal);
|
|
1580
1613
|
}
|
|
1581
1614
|
feed(data) {
|
|
@@ -1604,6 +1637,9 @@ class Pane {
|
|
|
1604
1637
|
onScroll(handler) {
|
|
1605
1638
|
this._onScroll = handler;
|
|
1606
1639
|
}
|
|
1640
|
+
onCopy(handler) {
|
|
1641
|
+
this._onCopy = handler;
|
|
1642
|
+
}
|
|
1607
1643
|
show() {
|
|
1608
1644
|
this.scrollBox.visible = true;
|
|
1609
1645
|
}
|
|
@@ -1667,6 +1703,8 @@ class StatusBar {
|
|
|
1667
1703
|
_searchQuery = "";
|
|
1668
1704
|
_searchMatchCount = 0;
|
|
1669
1705
|
_searchCurrentIndex = -1;
|
|
1706
|
+
_tempMessage = null;
|
|
1707
|
+
_tempTimer = null;
|
|
1670
1708
|
constructor(renderer) {
|
|
1671
1709
|
this.renderable = new TextRenderable(renderer, {
|
|
1672
1710
|
id: "status-bar",
|
|
@@ -1684,13 +1722,25 @@ class StatusBar {
|
|
|
1684
1722
|
this._searchCurrentIndex = currentIndex;
|
|
1685
1723
|
this.renderable.content = this.buildContent();
|
|
1686
1724
|
}
|
|
1725
|
+
showTemporaryMessage(message, duration = 2000) {
|
|
1726
|
+
if (this._tempTimer)
|
|
1727
|
+
clearTimeout(this._tempTimer);
|
|
1728
|
+
this._tempMessage = message;
|
|
1729
|
+
this.renderable.content = this.buildContent();
|
|
1730
|
+
this._tempTimer = setTimeout(() => {
|
|
1731
|
+
this._tempMessage = null;
|
|
1732
|
+
this._tempTimer = null;
|
|
1733
|
+
this.renderable.content = this.buildContent();
|
|
1734
|
+
}, duration);
|
|
1735
|
+
}
|
|
1687
1736
|
buildContent() {
|
|
1737
|
+
if (this._tempMessage) {
|
|
1738
|
+
return new StyledText([cyan(this._tempMessage)]);
|
|
1739
|
+
}
|
|
1688
1740
|
if (this._searchMode) {
|
|
1689
1741
|
return this.buildSearchContent();
|
|
1690
1742
|
}
|
|
1691
|
-
return new StyledText([
|
|
1692
|
-
plain("\u2190\u2192/1-9: tabs R: restart S: stop/start F: search L: clear Ctrl+C: quit")
|
|
1693
|
-
]);
|
|
1743
|
+
return new StyledText([plain(STATUS_BAR_TEXT)]);
|
|
1694
1744
|
}
|
|
1695
1745
|
buildSearchContent() {
|
|
1696
1746
|
const chunks = [];
|
|
@@ -1997,6 +2047,7 @@ class App {
|
|
|
1997
2047
|
for (const name of this.names) {
|
|
1998
2048
|
const interactive = this.config.processes[name].interactive === true;
|
|
1999
2049
|
const pane = new Pane(this.renderer, name, termCols, termRows, interactive);
|
|
2050
|
+
pane.onCopy(() => this.statusBar.showTemporaryMessage("Copied!"));
|
|
2000
2051
|
this.panes.set(name, pane);
|
|
2001
2052
|
paneContainer.add(pane.scrollBox);
|
|
2002
2053
|
}
|
|
@@ -2060,19 +2111,23 @@ class App {
|
|
|
2060
2111
|
const isInteractive = this.config.processes[this.activePane]?.interactive === true;
|
|
2061
2112
|
if (!isInteractive) {
|
|
2062
2113
|
const name = key.name.toLowerCase();
|
|
2063
|
-
if (key.shift && name ===
|
|
2114
|
+
if (key.shift && name === SHORTCUTS.restartAll.key) {
|
|
2064
2115
|
this.manager.restartAll(this.termCols, this.termRows);
|
|
2065
2116
|
return;
|
|
2066
2117
|
}
|
|
2067
|
-
if (name ===
|
|
2118
|
+
if (name === SHORTCUTS.copy.key) {
|
|
2119
|
+
this.copySelection();
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
if (name === SHORTCUTS.search.key) {
|
|
2068
2123
|
this.enterSearch();
|
|
2069
2124
|
return;
|
|
2070
2125
|
}
|
|
2071
|
-
if (name ===
|
|
2126
|
+
if (name === SHORTCUTS.restart.key) {
|
|
2072
2127
|
this.manager.restart(this.activePane, this.termCols, this.termRows);
|
|
2073
2128
|
return;
|
|
2074
2129
|
}
|
|
2075
|
-
if (name ===
|
|
2130
|
+
if (name === SHORTCUTS.stopStart.key) {
|
|
2076
2131
|
const state = this.manager.getState(this.activePane);
|
|
2077
2132
|
if (state?.status === "stopped" || state?.status === "finished" || state?.status === "failed") {
|
|
2078
2133
|
this.manager.start(this.activePane, this.termCols, this.termRows);
|
|
@@ -2081,7 +2136,7 @@ class App {
|
|
|
2081
2136
|
}
|
|
2082
2137
|
return;
|
|
2083
2138
|
}
|
|
2084
|
-
if (name ===
|
|
2139
|
+
if (name === SHORTCUTS.clear.key) {
|
|
2085
2140
|
this.panes.get(this.activePane)?.clear();
|
|
2086
2141
|
return;
|
|
2087
2142
|
}
|
|
@@ -2169,6 +2224,18 @@ class App {
|
|
|
2169
2224
|
this.tabBar.setInputWaiting(name, false);
|
|
2170
2225
|
}
|
|
2171
2226
|
}
|
|
2227
|
+
copySelection() {
|
|
2228
|
+
const selection = this.renderer.getSelection();
|
|
2229
|
+
if (!selection?.isActive)
|
|
2230
|
+
return false;
|
|
2231
|
+
const text = selection.getSelectedText();
|
|
2232
|
+
if (!text)
|
|
2233
|
+
return false;
|
|
2234
|
+
this.renderer.copyToClipboardOSC52(text);
|
|
2235
|
+
this.renderer.clearSelection();
|
|
2236
|
+
this.statusBar.showTemporaryMessage("Copied!");
|
|
2237
|
+
return true;
|
|
2238
|
+
}
|
|
2172
2239
|
enterSearch() {
|
|
2173
2240
|
this.searchMode = true;
|
|
2174
2241
|
this.searchQuery = "";
|