numux 1.16.0 → 1.17.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 -1
- package/dist/numux.js +116 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -300,7 +300,7 @@ Falsy values: unset, empty string, `"0"`, `"false"`, `"no"`, `"off"` (case-insen
|
|
|
300
300
|
|
|
301
301
|
### Dependency orchestration
|
|
302
302
|
|
|
303
|
-
|
|
303
|
+
Each process starts as soon as its declared `dependsOn` dependencies are ready — it does not wait for unrelated processes. If a process fails, its dependents are skipped.
|
|
304
304
|
|
|
305
305
|
A process becomes **ready** when:
|
|
306
306
|
- **persistent + readyPattern** — the pattern matches in stdout
|
package/dist/numux.js
CHANGED
|
@@ -36,7 +36,7 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
|
|
|
36
36
|
var require_package = __commonJS((exports, module) => {
|
|
37
37
|
module.exports = {
|
|
38
38
|
name: "numux",
|
|
39
|
-
version: "1.
|
|
39
|
+
version: "1.17.0",
|
|
40
40
|
description: "Terminal multiplexer with dependency orchestration",
|
|
41
41
|
type: "module",
|
|
42
42
|
license: "MIT",
|
|
@@ -1691,38 +1691,49 @@ class ProcessManager {
|
|
|
1691
1691
|
log("Starting all processes");
|
|
1692
1692
|
this.lastCols = cols;
|
|
1693
1693
|
this.lastRows = rows;
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
});
|
|
1708
|
-
if (failedDep) {
|
|
1709
|
-
log(`Skipping ${name}: dependency ${failedDep} failed`);
|
|
1710
|
-
this.updateStatus(name, "skipped");
|
|
1711
|
-
continue;
|
|
1712
|
-
}
|
|
1713
|
-
const { promise, resolve: resolve8 } = Promise.withResolvers();
|
|
1714
|
-
readyPromises.push(promise);
|
|
1715
|
-
this.pendingReadyResolvers.set(name, resolve8);
|
|
1716
|
-
this.createRunner(name, () => {
|
|
1717
|
-
this.pendingReadyResolvers.delete(name);
|
|
1718
|
-
resolve8();
|
|
1719
|
-
});
|
|
1720
|
-
this.startProcess(name, cols, rows);
|
|
1694
|
+
const readyPromises = new Map;
|
|
1695
|
+
const readyResolvers = new Map;
|
|
1696
|
+
for (const name of this.tiers.flat()) {
|
|
1697
|
+
const { promise, resolve: resolve8 } = Promise.withResolvers();
|
|
1698
|
+
readyPromises.set(name, promise);
|
|
1699
|
+
readyResolvers.set(name, resolve8);
|
|
1700
|
+
}
|
|
1701
|
+
const launches = this.tiers.flat().map(async (name) => {
|
|
1702
|
+
const proc = this.config.processes[name];
|
|
1703
|
+
const resolve8 = readyResolvers.get(name);
|
|
1704
|
+
const deps = proc.dependsOn ?? [];
|
|
1705
|
+
if (deps.length > 0) {
|
|
1706
|
+
await Promise.all(deps.map((d) => readyPromises.get(d)));
|
|
1721
1707
|
}
|
|
1722
|
-
if (
|
|
1723
|
-
|
|
1708
|
+
if (this.stopping) {
|
|
1709
|
+
resolve8();
|
|
1710
|
+
return;
|
|
1724
1711
|
}
|
|
1725
|
-
|
|
1712
|
+
if (proc.condition && !evaluateCondition(proc.condition)) {
|
|
1713
|
+
log(`Skipping ${name}: condition "${proc.condition}" not met`);
|
|
1714
|
+
this.updateStatus(name, "skipped");
|
|
1715
|
+
resolve8();
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
const failedDep = deps.find((d) => {
|
|
1719
|
+
const s = this.states.get(d).status;
|
|
1720
|
+
return s === "failed" || s === "skipped";
|
|
1721
|
+
});
|
|
1722
|
+
if (failedDep) {
|
|
1723
|
+
log(`Skipping ${name}: dependency ${failedDep} failed`);
|
|
1724
|
+
this.updateStatus(name, "skipped");
|
|
1725
|
+
resolve8();
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
this.pendingReadyResolvers.set(name, resolve8);
|
|
1729
|
+
this.createRunner(name, () => {
|
|
1730
|
+
this.pendingReadyResolvers.delete(name);
|
|
1731
|
+
resolve8();
|
|
1732
|
+
});
|
|
1733
|
+
this.startProcess(name, cols, rows);
|
|
1734
|
+
await readyPromises.get(name);
|
|
1735
|
+
});
|
|
1736
|
+
await Promise.all(launches);
|
|
1726
1737
|
this.setupWatchers();
|
|
1727
1738
|
}
|
|
1728
1739
|
startProcess(name, cols, rows) {
|
|
@@ -2008,6 +2019,7 @@ var STATUS_HINTS = [
|
|
|
2008
2019
|
[SHORTCUTS.search.label, SHORTCUTS.search.description],
|
|
2009
2020
|
[SHORTCUTS.copy.label, SHORTCUTS.copy.description],
|
|
2010
2021
|
[SHORTCUTS.clear.label, SHORTCUTS.clear.description],
|
|
2022
|
+
["Ctrl+Click", "open link"],
|
|
2011
2023
|
["Ctrl+C", "quit"]
|
|
2012
2024
|
];
|
|
2013
2025
|
var STATUS_BAR_TEXT = STATUS_HINTS.map(([l, d]) => `${l}: ${d}`).join(" ");
|
|
@@ -2016,12 +2028,57 @@ var STATUS_BAR_TEXT = STATUS_HINTS.map(([l, d]) => `${l}: ${d}`).join(" ");
|
|
|
2016
2028
|
import { ScrollBoxRenderable } from "@opentui/core";
|
|
2017
2029
|
import { GhosttyTerminalRenderable } from "ghostty-opentui/terminal-buffer";
|
|
2018
2030
|
|
|
2031
|
+
// src/ui/url-handler.ts
|
|
2032
|
+
var URL_RE = /https?:\/\/[^\s<>"'`)\]},;]+/g;
|
|
2033
|
+
var FILE_PATH_RE = /(?:\.\.?\/|\/)[^\s:]+(?::(\d+)(?::(\d+))?)?/g;
|
|
2034
|
+
var TRAILING_PUNCT = /[.,;:!?)>\]'"]+$/;
|
|
2035
|
+
function findLinksInLine(line) {
|
|
2036
|
+
const links = [];
|
|
2037
|
+
for (const m of line.matchAll(URL_RE)) {
|
|
2038
|
+
const url = m[0].replace(TRAILING_PUNCT, "");
|
|
2039
|
+
links.push({
|
|
2040
|
+
url,
|
|
2041
|
+
start: m.index,
|
|
2042
|
+
end: m.index + url.length,
|
|
2043
|
+
type: "url"
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
for (const m of line.matchAll(FILE_PATH_RE)) {
|
|
2047
|
+
const start = m.index;
|
|
2048
|
+
const end = m.index + m[0].length;
|
|
2049
|
+
if (links.some((l) => start < l.end && end > l.start))
|
|
2050
|
+
continue;
|
|
2051
|
+
links.push({
|
|
2052
|
+
url: m[0],
|
|
2053
|
+
start,
|
|
2054
|
+
end,
|
|
2055
|
+
type: "file"
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
return links.sort((a, b) => a.start - b.start);
|
|
2059
|
+
}
|
|
2060
|
+
function findLinkAtPosition(line, col) {
|
|
2061
|
+
const links = findLinksInLine(line);
|
|
2062
|
+
return links.find((l) => col >= l.start && col < l.end) ?? null;
|
|
2063
|
+
}
|
|
2064
|
+
function openLink(link) {
|
|
2065
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : null;
|
|
2066
|
+
if (!opener)
|
|
2067
|
+
return;
|
|
2068
|
+
try {
|
|
2069
|
+
const proc = Bun.spawn([opener, link.url]);
|
|
2070
|
+
proc.exited.catch(() => {});
|
|
2071
|
+
} catch {}
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
// src/ui/pane.ts
|
|
2019
2075
|
class Pane {
|
|
2020
2076
|
scrollBox;
|
|
2021
2077
|
terminal;
|
|
2022
2078
|
decoder = new TextDecoder;
|
|
2023
2079
|
_onScroll = null;
|
|
2024
2080
|
_onCopy = null;
|
|
2081
|
+
_onLinkClick = null;
|
|
2025
2082
|
_textLines = null;
|
|
2026
2083
|
_textLinesLower = null;
|
|
2027
2084
|
constructor(renderer, name, cols, rows, interactive = false) {
|
|
@@ -2056,6 +2113,15 @@ class Pane {
|
|
|
2056
2113
|
}
|
|
2057
2114
|
return result;
|
|
2058
2115
|
};
|
|
2116
|
+
this.terminal.onMouseDown = (event) => {
|
|
2117
|
+
if (event.modifiers.ctrl && event.button === 0) {
|
|
2118
|
+
const link = this.getLinkAtMouse(event.x, event.y);
|
|
2119
|
+
if (link) {
|
|
2120
|
+
event.stopPropagation();
|
|
2121
|
+
this._onLinkClick?.(link);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2059
2125
|
this.scrollBox.add(this.terminal);
|
|
2060
2126
|
}
|
|
2061
2127
|
feed(data) {
|
|
@@ -2094,6 +2160,21 @@ class Pane {
|
|
|
2094
2160
|
onCopy(handler) {
|
|
2095
2161
|
this._onCopy = handler;
|
|
2096
2162
|
}
|
|
2163
|
+
onLinkClick(handler) {
|
|
2164
|
+
this._onLinkClick = handler;
|
|
2165
|
+
}
|
|
2166
|
+
getLinkAtMouse(localX, localY) {
|
|
2167
|
+
if (!this._textLines) {
|
|
2168
|
+
const text = this.terminal.getText();
|
|
2169
|
+
this._textLines = text.split(`
|
|
2170
|
+
`);
|
|
2171
|
+
this._textLinesLower = this._textLines.map((l) => l.toLowerCase());
|
|
2172
|
+
}
|
|
2173
|
+
const lineIndex = Math.floor(this.scrollBox.scrollTop) + localY;
|
|
2174
|
+
if (lineIndex < 0 || lineIndex >= this._textLines.length)
|
|
2175
|
+
return null;
|
|
2176
|
+
return findLinkAtPosition(this._textLines[lineIndex], localX);
|
|
2177
|
+
}
|
|
2097
2178
|
show() {
|
|
2098
2179
|
this.scrollBox.visible = true;
|
|
2099
2180
|
}
|
|
@@ -2543,6 +2624,10 @@ class App {
|
|
|
2543
2624
|
this.copyToClipboard(text);
|
|
2544
2625
|
this.statusBar.showTemporaryMessage("Copied!");
|
|
2545
2626
|
});
|
|
2627
|
+
pane.onLinkClick((link) => {
|
|
2628
|
+
openLink(link);
|
|
2629
|
+
this.statusBar.showTemporaryMessage(`Opening ${link.url}`);
|
|
2630
|
+
});
|
|
2546
2631
|
pane.onScroll(() => {
|
|
2547
2632
|
if (this.searchMode && this.searchMatches.length > 0 && this.activePane === name) {
|
|
2548
2633
|
this.updateSearchHighlights();
|