numux 2.16.1 → 2.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 +2 -0
- package/dist/man/numux.1 +15 -1
- package/dist/numux.js +296 -41
- package/dist/types.d.ts +8 -0
- package/dist/utils/color.d.ts +3 -3
- package/dist/utils/theme.d.ts +75 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -228,6 +228,7 @@ export default defineConfig({
|
|
|
228
228
|
| `--no-watch` | Disable file watching even if config has watch patterns |
|
|
229
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
|
+
| `--theme` `<light|dark|auto>` | TUI theme (auto detects terminal background) |
|
|
231
232
|
| `--debug` | Enable debug logging to .numux/debug.log |
|
|
232
233
|
| `-h,` `--help` | Show this help |
|
|
233
234
|
| `-v,` `--version` | Show version |
|
|
@@ -284,6 +285,7 @@ Top-level options apply to all processes (process-level settings override):
|
|
|
284
285
|
| `killOthersOnFail` | `boolean` | Kill all processes when any one exits with a non-zero exit code |
|
|
285
286
|
| `noWatch` | `boolean` | Disable file watching even if processes have watch patterns |
|
|
286
287
|
| `logDir` | `string` | Directory to write per-process log files |
|
|
288
|
+
| `theme` | `ThemePref` | TUI color theme. `'auto'` detects the terminal background via OSC 11 (falling back to `COLORFGBG` then dark). `'light'`/`'dark'` skip detection. |
|
|
287
289
|
<!-- /generated:config-global -->
|
|
288
290
|
|
|
289
291
|
```ts
|
package/dist/man/numux.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH "NUMUX" "1" "May 2026" "2.
|
|
1
|
+
.TH "NUMUX" "1" "May 2026" "2.17.0" "numux manual"
|
|
2
2
|
.SH "NAME"
|
|
3
3
|
\fBnumux\fR
|
|
4
4
|
.P
|
|
@@ -349,6 +349,12 @@ Write per\-process logs to directory
|
|
|
349
349
|
T}
|
|
350
350
|
_
|
|
351
351
|
T{
|
|
352
|
+
\fB\-\-theme\fP `<light
|
|
353
|
+
T}|T{
|
|
354
|
+
dark
|
|
355
|
+
T}
|
|
356
|
+
_
|
|
357
|
+
T{
|
|
352
358
|
\fB\-\-debug\fP
|
|
353
359
|
T}|T{
|
|
354
360
|
Enable debug logging to \.numux/debug\.log
|
|
@@ -538,6 +544,14 @@ T}|T{
|
|
|
538
544
|
T}|T{
|
|
539
545
|
Directory to write per\-process log files
|
|
540
546
|
T}
|
|
547
|
+
_
|
|
548
|
+
T{
|
|
549
|
+
\fBtheme\fP
|
|
550
|
+
T}|T{
|
|
551
|
+
\fBThemePref\fP
|
|
552
|
+
T}|T{
|
|
553
|
+
TUI color theme\. \fB'auto'\fP detects the terminal background via OSC 11 (falling back to \fBCOLORFGBG\fP then dark)\. \fB'light'\fP/\fB'dark'\fP skip detection\.
|
|
554
|
+
T}
|
|
541
555
|
.TE
|
|
542
556
|
<!\-\- /generated:config\-global \-\->
|
|
543
557
|
|
package/dist/numux.js
CHANGED
|
@@ -246,6 +246,7 @@ export default defineConfig({
|
|
|
246
246
|
| \`--no-watch\` | Disable file watching even if config has watch patterns |
|
|
247
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
|
+
| \`--theme\` \`<light|dark|auto>\` | TUI theme (auto detects terminal background) |
|
|
249
250
|
| \`--debug\` | Enable debug logging to .numux/debug.log |
|
|
250
251
|
| \`-h,\` \`--help\` | Show this help |
|
|
251
252
|
| \`-v,\` \`--version\` | Show version |
|
|
@@ -290,6 +291,7 @@ numux logs api | tail -f # Follow process log output
|
|
|
290
291
|
| \`killOthersOnFail\` | \`boolean\` | Kill all processes when any one exits with a non-zero exit code |
|
|
291
292
|
| \`noWatch\` | \`boolean\` | Disable file watching even if processes have watch patterns |
|
|
292
293
|
| \`logDir\` | \`string\` | Directory to write per-process log files |
|
|
294
|
+
| \`theme\` | \`ThemePref\` | TUI color theme. \`'auto'\` detects the terminal background via OSC 11 (falling back to \`COLORFGBG\` then dark). \`'light'\`/\`'dark'\` skip detection. |
|
|
293
295
|
<!-- /generated:config-global -->
|
|
294
296
|
|
|
295
297
|
\`\`\`ts
|
|
@@ -548,7 +550,7 @@ var init_help = __esm(() => {
|
|
|
548
550
|
var require_package = __commonJS((exports, module) => {
|
|
549
551
|
module.exports = {
|
|
550
552
|
name: "numux",
|
|
551
|
-
version: "2.
|
|
553
|
+
version: "2.17.0",
|
|
552
554
|
description: "Terminal multiplexer with dependency orchestration",
|
|
553
555
|
type: "module",
|
|
554
556
|
license: "MIT",
|
|
@@ -876,6 +878,20 @@ var FLAGS = [
|
|
|
876
878
|
valueName: "<path>",
|
|
877
879
|
completionHint: "directory"
|
|
878
880
|
},
|
|
881
|
+
{
|
|
882
|
+
type: "value",
|
|
883
|
+
long: "--theme",
|
|
884
|
+
key: "theme",
|
|
885
|
+
description: "TUI theme (auto detects terminal background)",
|
|
886
|
+
valueName: "<light|dark|auto>",
|
|
887
|
+
completionHint: "none",
|
|
888
|
+
parse(raw, flag) {
|
|
889
|
+
if (raw !== "light" && raw !== "dark" && raw !== "auto") {
|
|
890
|
+
throw new Error(`${flag} must be light, dark, or auto. Got "${raw}"`);
|
|
891
|
+
}
|
|
892
|
+
return raw;
|
|
893
|
+
}
|
|
894
|
+
},
|
|
879
895
|
{
|
|
880
896
|
type: "boolean",
|
|
881
897
|
long: "--debug",
|
|
@@ -1704,10 +1720,11 @@ function resolveColor(color) {
|
|
|
1704
1720
|
return color[0];
|
|
1705
1721
|
return;
|
|
1706
1722
|
}
|
|
1707
|
-
function buildProcessColorMap(names, config) {
|
|
1723
|
+
function buildProcessColorMap(names, config, palette = DEFAULT_PALETTE) {
|
|
1708
1724
|
const map = new Map;
|
|
1709
1725
|
if ("NO_COLOR" in process.env)
|
|
1710
1726
|
return map;
|
|
1727
|
+
const ansiPalette = palette === DEFAULT_PALETTE ? DEFAULT_ANSI_COLORS : palette.map(hexToAnsi);
|
|
1711
1728
|
let paletteIndex = 0;
|
|
1712
1729
|
for (const name of names) {
|
|
1713
1730
|
const explicit = resolveColor(config.processes[name]?.color);
|
|
@@ -1716,15 +1733,15 @@ function buildProcessColorMap(names, config) {
|
|
|
1716
1733
|
if (hex)
|
|
1717
1734
|
map.set(name, hexToAnsi(hex));
|
|
1718
1735
|
else
|
|
1719
|
-
map.set(name,
|
|
1736
|
+
map.set(name, ansiPalette[paletteIndex++ % ansiPalette.length]);
|
|
1720
1737
|
} else {
|
|
1721
|
-
map.set(name,
|
|
1738
|
+
map.set(name, ansiPalette[paletteIndex % ansiPalette.length]);
|
|
1722
1739
|
paletteIndex++;
|
|
1723
1740
|
}
|
|
1724
1741
|
}
|
|
1725
1742
|
return map;
|
|
1726
1743
|
}
|
|
1727
|
-
function buildProcessHexColorMap(names, config) {
|
|
1744
|
+
function buildProcessHexColorMap(names, config, palette = DEFAULT_PALETTE) {
|
|
1728
1745
|
const map = new Map;
|
|
1729
1746
|
if ("NO_COLOR" in process.env)
|
|
1730
1747
|
return map;
|
|
@@ -1736,9 +1753,9 @@ function buildProcessHexColorMap(names, config) {
|
|
|
1736
1753
|
if (hex)
|
|
1737
1754
|
map.set(name, hex);
|
|
1738
1755
|
else
|
|
1739
|
-
map.set(name,
|
|
1756
|
+
map.set(name, palette[paletteIndex++ % palette.length]);
|
|
1740
1757
|
} else {
|
|
1741
|
-
map.set(name,
|
|
1758
|
+
map.set(name, palette[paletteIndex % palette.length]);
|
|
1742
1759
|
paletteIndex++;
|
|
1743
1760
|
}
|
|
1744
1761
|
}
|
|
@@ -1783,6 +1800,7 @@ function validateConfig(raw, _warnings) {
|
|
|
1783
1800
|
const killOthersOnFail = config.killOthersOnFail === true ? true : undefined;
|
|
1784
1801
|
const noWatch = config.noWatch === true ? true : undefined;
|
|
1785
1802
|
const logDir = typeof config.logDir === "string" && config.logDir.trim() ? config.logDir.trim() : undefined;
|
|
1803
|
+
const theme = validateTheme(config.theme);
|
|
1786
1804
|
const validated = {};
|
|
1787
1805
|
for (const name of names) {
|
|
1788
1806
|
let proc = processes[name];
|
|
@@ -1881,9 +1899,19 @@ function validateConfig(raw, _warnings) {
|
|
|
1881
1899
|
...killOthersOnFail ? { killOthersOnFail } : {},
|
|
1882
1900
|
...noWatch ? { noWatch } : {},
|
|
1883
1901
|
...logDir ? { logDir } : {},
|
|
1902
|
+
...theme ? { theme } : {},
|
|
1884
1903
|
processes: validated
|
|
1885
1904
|
};
|
|
1886
1905
|
}
|
|
1906
|
+
var VALID_THEME_VALUES = new Set(["light", "dark", "auto"]);
|
|
1907
|
+
function validateTheme(value) {
|
|
1908
|
+
if (value === undefined)
|
|
1909
|
+
return;
|
|
1910
|
+
if (typeof value !== "string" || !VALID_THEME_VALUES.has(value)) {
|
|
1911
|
+
throw new Error(`theme must be one of: light, dark, auto. Got "${String(value)}"`);
|
|
1912
|
+
}
|
|
1913
|
+
return value;
|
|
1914
|
+
}
|
|
1887
1915
|
function validateStringOrStringArray(value) {
|
|
1888
1916
|
if (typeof value === "string")
|
|
1889
1917
|
return value;
|
|
@@ -2949,6 +2977,178 @@ function setupShutdownHandlers(app, logWriter) {
|
|
|
2949
2977
|
});
|
|
2950
2978
|
}
|
|
2951
2979
|
|
|
2980
|
+
// src/utils/theme.ts
|
|
2981
|
+
var DARK_THEME = {
|
|
2982
|
+
mode: "dark",
|
|
2983
|
+
statusBarBg: "#1a1a1a",
|
|
2984
|
+
statusBarText: "#cccccc",
|
|
2985
|
+
helpBackdropBg: "#000000",
|
|
2986
|
+
helpBoxBg: "#1a1a2e",
|
|
2987
|
+
helpBorder: "#444444",
|
|
2988
|
+
helpText: "#cccccc",
|
|
2989
|
+
sidebarBg: "#1a1a1a",
|
|
2990
|
+
sidebarBorder: "#444444",
|
|
2991
|
+
tabSelectedBg: "#334455",
|
|
2992
|
+
tabSelectedText: "#ffffff",
|
|
2993
|
+
tabText: "#888888",
|
|
2994
|
+
tabDescriptionText: "#888888",
|
|
2995
|
+
tabSelectedDescriptionText: "#cccccc",
|
|
2996
|
+
scrollTrackBg: "#252527",
|
|
2997
|
+
scrollThumbBg: "#9a9ea3",
|
|
2998
|
+
searchCurrentBg: "#b58900",
|
|
2999
|
+
searchMatchBg: "#073642",
|
|
3000
|
+
palette: ["#00cccc", "#cccc00", "#cc00cc", "#5577ff", "#00cc00", "#ff5555", "#ffa500", "#cc88ff"],
|
|
3001
|
+
status: {
|
|
3002
|
+
ready: "#00cc00",
|
|
3003
|
+
failed: "#ff5555",
|
|
3004
|
+
stopped: "#888888",
|
|
3005
|
+
finished: "#66aa66",
|
|
3006
|
+
skipped: "#888888"
|
|
3007
|
+
},
|
|
3008
|
+
inputWaiting: "#ffaa00",
|
|
3009
|
+
errorIndicator: "#ff5555",
|
|
3010
|
+
searchMatchTab: "#b58900",
|
|
3011
|
+
iconDefault: "#888888"
|
|
3012
|
+
};
|
|
3013
|
+
var LIGHT_THEME = {
|
|
3014
|
+
mode: "light",
|
|
3015
|
+
statusBarBg: "#e8e8e8",
|
|
3016
|
+
statusBarText: "#000000",
|
|
3017
|
+
helpBackdropBg: "#ffffff",
|
|
3018
|
+
helpBoxBg: "#f5f5f5",
|
|
3019
|
+
helpBorder: "#aaaaaa",
|
|
3020
|
+
helpText: "#1a1a1a",
|
|
3021
|
+
sidebarBg: "#f0f0f0",
|
|
3022
|
+
sidebarBorder: "#aaaaaa",
|
|
3023
|
+
tabSelectedBg: "#7a9bbf",
|
|
3024
|
+
tabSelectedText: "#ffffff",
|
|
3025
|
+
tabText: "#444444",
|
|
3026
|
+
tabDescriptionText: "#666666",
|
|
3027
|
+
tabSelectedDescriptionText: "#e8e8e8",
|
|
3028
|
+
scrollTrackBg: "#d0d0d0",
|
|
3029
|
+
scrollThumbBg: "#888888",
|
|
3030
|
+
searchCurrentBg: "#ffaa33",
|
|
3031
|
+
searchMatchBg: "#d0e4b8",
|
|
3032
|
+
palette: ["#008888", "#886600", "#880088", "#0033aa", "#006600", "#aa0000", "#cc5500", "#6622aa"],
|
|
3033
|
+
status: {
|
|
3034
|
+
ready: "#006600",
|
|
3035
|
+
failed: "#aa0000",
|
|
3036
|
+
stopped: "#666666",
|
|
3037
|
+
finished: "#2a7a2a",
|
|
3038
|
+
skipped: "#666666"
|
|
3039
|
+
},
|
|
3040
|
+
inputWaiting: "#cc7a00",
|
|
3041
|
+
errorIndicator: "#aa0000",
|
|
3042
|
+
searchMatchTab: "#cc7a00",
|
|
3043
|
+
iconDefault: "#666666"
|
|
3044
|
+
};
|
|
3045
|
+
function themeFor(mode) {
|
|
3046
|
+
return mode === "light" ? LIGHT_THEME : DARK_THEME;
|
|
3047
|
+
}
|
|
3048
|
+
function relativeLuminance(r, g, b) {
|
|
3049
|
+
const norm = (c) => {
|
|
3050
|
+
const s = c / 255;
|
|
3051
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
3052
|
+
};
|
|
3053
|
+
return 0.2126 * norm(r) + 0.7152 * norm(g) + 0.0722 * norm(b);
|
|
3054
|
+
}
|
|
3055
|
+
function isLightRgb(r, g, b) {
|
|
3056
|
+
return relativeLuminance(r, g, b) > 0.5;
|
|
3057
|
+
}
|
|
3058
|
+
function parseOSC11Response(data) {
|
|
3059
|
+
const match = data.match(/rgb:([0-9a-f]+)\/([0-9a-f]+)\/([0-9a-f]+)/i);
|
|
3060
|
+
if (!match)
|
|
3061
|
+
return null;
|
|
3062
|
+
const scale = (hex) => {
|
|
3063
|
+
if (hex.length === 0 || hex.length > 4)
|
|
3064
|
+
return Number.NaN;
|
|
3065
|
+
const val = Number.parseInt(hex, 16);
|
|
3066
|
+
if (Number.isNaN(val))
|
|
3067
|
+
return Number.NaN;
|
|
3068
|
+
const max = 16 ** hex.length - 1;
|
|
3069
|
+
return Math.round(val / max * 255);
|
|
3070
|
+
};
|
|
3071
|
+
const r = scale(match[1]);
|
|
3072
|
+
const g = scale(match[2]);
|
|
3073
|
+
const b = scale(match[3]);
|
|
3074
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b))
|
|
3075
|
+
return null;
|
|
3076
|
+
return { r, g, b };
|
|
3077
|
+
}
|
|
3078
|
+
function parseColorFgBg(value) {
|
|
3079
|
+
if (!value)
|
|
3080
|
+
return null;
|
|
3081
|
+
const parts = value.split(";");
|
|
3082
|
+
if (parts.length < 2)
|
|
3083
|
+
return null;
|
|
3084
|
+
const bgRaw = parts[parts.length - 1].trim();
|
|
3085
|
+
const bg = Number.parseInt(bgRaw, 10);
|
|
3086
|
+
if (Number.isNaN(bg))
|
|
3087
|
+
return null;
|
|
3088
|
+
return bg >= 7 && bg <= 15 ? "light" : "dark";
|
|
3089
|
+
}
|
|
3090
|
+
function queryOSC11(timeoutMs = 100) {
|
|
3091
|
+
const stdin = process.stdin;
|
|
3092
|
+
const stdout = process.stdout;
|
|
3093
|
+
if (!(stdin.isTTY && stdout.isTTY))
|
|
3094
|
+
return Promise.resolve(null);
|
|
3095
|
+
return new Promise((resolve8) => {
|
|
3096
|
+
let settled = false;
|
|
3097
|
+
let buf = "";
|
|
3098
|
+
let timer = null;
|
|
3099
|
+
const wasRaw = stdin.isRaw;
|
|
3100
|
+
const finish = (result) => {
|
|
3101
|
+
if (settled)
|
|
3102
|
+
return;
|
|
3103
|
+
settled = true;
|
|
3104
|
+
if (timer)
|
|
3105
|
+
clearTimeout(timer);
|
|
3106
|
+
stdin.off("data", onData);
|
|
3107
|
+
try {
|
|
3108
|
+
if (!wasRaw)
|
|
3109
|
+
stdin.setRawMode(false);
|
|
3110
|
+
} catch {}
|
|
3111
|
+
stdin.pause();
|
|
3112
|
+
resolve8(result);
|
|
3113
|
+
};
|
|
3114
|
+
const onData = (chunk) => {
|
|
3115
|
+
buf += chunk.toString("utf8");
|
|
3116
|
+
const match = buf.match(/\x1b\]1[01];rgb:[0-9a-f/]+(?:\x07|\x1b\\)/i);
|
|
3117
|
+
if (!match)
|
|
3118
|
+
return;
|
|
3119
|
+
const parsed = parseOSC11Response(match[0]);
|
|
3120
|
+
if (!parsed) {
|
|
3121
|
+
finish(null);
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
3124
|
+
finish(isLightRgb(parsed.r, parsed.g, parsed.b) ? "light" : "dark");
|
|
3125
|
+
};
|
|
3126
|
+
try {
|
|
3127
|
+
stdin.setRawMode(true);
|
|
3128
|
+
stdin.resume();
|
|
3129
|
+
stdin.on("data", onData);
|
|
3130
|
+
timer = setTimeout(() => finish(null), timeoutMs);
|
|
3131
|
+
stdout.write("\x1B]11;?\x1B\\");
|
|
3132
|
+
} catch {
|
|
3133
|
+
finish(null);
|
|
3134
|
+
}
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
async function detectThemeMode(timeoutMs = 100) {
|
|
3138
|
+
const osc = await queryOSC11(timeoutMs);
|
|
3139
|
+
if (osc)
|
|
3140
|
+
return osc;
|
|
3141
|
+
return parseColorFgBg(process.env.COLORFGBG);
|
|
3142
|
+
}
|
|
3143
|
+
async function resolveTheme(pref = "auto") {
|
|
3144
|
+
if (pref === "light")
|
|
3145
|
+
return LIGHT_THEME;
|
|
3146
|
+
if (pref === "dark")
|
|
3147
|
+
return DARK_THEME;
|
|
3148
|
+
const detected = await detectThemeMode();
|
|
3149
|
+
return themeFor(detected ?? "dark");
|
|
3150
|
+
}
|
|
3151
|
+
|
|
2952
3152
|
// src/ui/help-overlay.ts
|
|
2953
3153
|
import { BoxRenderable, TextRenderable } from "@opentui/core";
|
|
2954
3154
|
|
|
@@ -3002,7 +3202,7 @@ var STATUS_BAR_TEXT = STATUS_HINTS_COMPACT.map((h) => {
|
|
|
3002
3202
|
class HelpOverlay {
|
|
3003
3203
|
renderable;
|
|
3004
3204
|
textRenderable;
|
|
3005
|
-
constructor(renderer) {
|
|
3205
|
+
constructor(renderer, theme = DARK_THEME) {
|
|
3006
3206
|
this.renderable = new BoxRenderable(renderer, {
|
|
3007
3207
|
id: "help-overlay",
|
|
3008
3208
|
position: "absolute",
|
|
@@ -3018,7 +3218,7 @@ class HelpOverlay {
|
|
|
3018
3218
|
position: "absolute",
|
|
3019
3219
|
width: "100%",
|
|
3020
3220
|
height: "100%",
|
|
3021
|
-
backgroundColor:
|
|
3221
|
+
backgroundColor: theme.helpBackdropBg,
|
|
3022
3222
|
opacity: 0.7
|
|
3023
3223
|
});
|
|
3024
3224
|
const box = new BoxRenderable(renderer, {
|
|
@@ -3026,9 +3226,9 @@ class HelpOverlay {
|
|
|
3026
3226
|
flexDirection: "column",
|
|
3027
3227
|
padding: 1,
|
|
3028
3228
|
paddingX: 5,
|
|
3029
|
-
backgroundColor:
|
|
3229
|
+
backgroundColor: theme.helpBoxBg,
|
|
3030
3230
|
border: true,
|
|
3031
|
-
borderColor:
|
|
3231
|
+
borderColor: theme.helpBorder,
|
|
3032
3232
|
zIndex: 101
|
|
3033
3233
|
});
|
|
3034
3234
|
const lines = [
|
|
@@ -3045,7 +3245,7 @@ class HelpOverlay {
|
|
|
3045
3245
|
id: "help-text",
|
|
3046
3246
|
content: lines.join(`
|
|
3047
3247
|
`),
|
|
3048
|
-
fg:
|
|
3248
|
+
fg: theme.helpText
|
|
3049
3249
|
});
|
|
3050
3250
|
box.add(this.textRenderable);
|
|
3051
3251
|
this.renderable.add(backdrop);
|
|
@@ -3556,7 +3756,7 @@ function openLink(link) {
|
|
|
3556
3756
|
}
|
|
3557
3757
|
|
|
3558
3758
|
// src/ui/pane.ts
|
|
3559
|
-
var RENDER_LIMIT =
|
|
3759
|
+
var RENDER_LIMIT = 1500;
|
|
3560
3760
|
var MAX_SCROLLBACK_LINES = 1e6;
|
|
3561
3761
|
var MAX_BUFFER_BYTES = 500 * 1024 * 1024;
|
|
3562
3762
|
|
|
@@ -3570,11 +3770,16 @@ class Pane {
|
|
|
3570
3770
|
_timestampFormat = null;
|
|
3571
3771
|
lineTimestamps = [];
|
|
3572
3772
|
lineCounter = 0;
|
|
3773
|
+
signedLineCount = 0;
|
|
3774
|
+
timestampUpdateTimer = null;
|
|
3775
|
+
static TIMESTAMP_UPDATE_DEBOUNCE_MS = 32;
|
|
3573
3776
|
_onScroll = null;
|
|
3574
3777
|
_onCopy = null;
|
|
3575
3778
|
_onLinkClick = null;
|
|
3576
|
-
|
|
3779
|
+
theme;
|
|
3780
|
+
constructor(renderer, name, cols, rows, interactive = false, theme = DARK_THEME) {
|
|
3577
3781
|
this.renderer = renderer;
|
|
3782
|
+
this.theme = theme;
|
|
3578
3783
|
this.scrollBox = new ScrollBoxRenderable(renderer, {
|
|
3579
3784
|
id: `pane-${name}`,
|
|
3580
3785
|
flexGrow: 1,
|
|
@@ -3582,7 +3787,13 @@ class Pane {
|
|
|
3582
3787
|
stickyScroll: true,
|
|
3583
3788
|
stickyStart: "bottom",
|
|
3584
3789
|
visible: false,
|
|
3585
|
-
onMouseScroll: () => this._onScroll?.()
|
|
3790
|
+
onMouseScroll: () => this._onScroll?.(),
|
|
3791
|
+
scrollbarOptions: {
|
|
3792
|
+
trackOptions: {
|
|
3793
|
+
backgroundColor: theme.scrollTrackBg,
|
|
3794
|
+
foregroundColor: theme.scrollThumbBg
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3586
3797
|
});
|
|
3587
3798
|
this.terminal = new TailingTerminal(renderer, {
|
|
3588
3799
|
id: `term-${name}`,
|
|
@@ -3630,6 +3841,8 @@ class Pane {
|
|
|
3630
3841
|
this.bytesFed = 0;
|
|
3631
3842
|
this.lineTimestamps = [];
|
|
3632
3843
|
this.lineCounter = 0;
|
|
3844
|
+
this.signedLineCount = 0;
|
|
3845
|
+
this.timestampGutter?.clearAllLineSigns();
|
|
3633
3846
|
}
|
|
3634
3847
|
const now = Date.now();
|
|
3635
3848
|
if (this.lineCounter === 0) {
|
|
@@ -3644,10 +3857,18 @@ class Pane {
|
|
|
3644
3857
|
}
|
|
3645
3858
|
const text = this.decoder.decode(data, { stream: true });
|
|
3646
3859
|
this.terminal.feed(text);
|
|
3647
|
-
if (this._timestampFormat) {
|
|
3648
|
-
this.
|
|
3860
|
+
if (this._timestampFormat && this.lineTimestamps.length !== this.signedLineCount) {
|
|
3861
|
+
this.scheduleTimestampUpdate();
|
|
3649
3862
|
}
|
|
3650
3863
|
}
|
|
3864
|
+
scheduleTimestampUpdate() {
|
|
3865
|
+
if (this.timestampUpdateTimer)
|
|
3866
|
+
return;
|
|
3867
|
+
this.timestampUpdateTimer = setTimeout(() => {
|
|
3868
|
+
this.timestampUpdateTimer = null;
|
|
3869
|
+
this.updateTimestampSigns();
|
|
3870
|
+
}, Pane.TIMESTAMP_UPDATE_DEBOUNCE_MS);
|
|
3871
|
+
}
|
|
3651
3872
|
resize(cols, rows) {
|
|
3652
3873
|
this.terminal.cols = cols;
|
|
3653
3874
|
this.terminal.rows = rows;
|
|
@@ -3708,7 +3929,7 @@ class Pane {
|
|
|
3708
3929
|
line: m.line,
|
|
3709
3930
|
start: m.start,
|
|
3710
3931
|
end: m.end,
|
|
3711
|
-
backgroundColor: i === currentIndex ?
|
|
3932
|
+
backgroundColor: i === currentIndex ? this.theme.searchCurrentBg : this.theme.searchMatchBg
|
|
3712
3933
|
});
|
|
3713
3934
|
}
|
|
3714
3935
|
this.terminal.highlights = regions;
|
|
@@ -3725,6 +3946,11 @@ class Pane {
|
|
|
3725
3946
|
this.bytesFed = 0;
|
|
3726
3947
|
this.lineTimestamps = [];
|
|
3727
3948
|
this.lineCounter = 0;
|
|
3949
|
+
this.signedLineCount = 0;
|
|
3950
|
+
if (this.timestampUpdateTimer) {
|
|
3951
|
+
clearTimeout(this.timestampUpdateTimer);
|
|
3952
|
+
this.timestampUpdateTimer = null;
|
|
3953
|
+
}
|
|
3728
3954
|
if (this._timestampFormat) {
|
|
3729
3955
|
this.timestampGutter?.clearAllLineSigns();
|
|
3730
3956
|
}
|
|
@@ -3736,6 +3962,11 @@ class Pane {
|
|
|
3736
3962
|
if (wasEnabled === isEnabled && this._timestampFormat === newFormat)
|
|
3737
3963
|
return;
|
|
3738
3964
|
this._timestampFormat = newFormat;
|
|
3965
|
+
if (this.timestampUpdateTimer) {
|
|
3966
|
+
clearTimeout(this.timestampUpdateTimer);
|
|
3967
|
+
this.timestampUpdateTimer = null;
|
|
3968
|
+
}
|
|
3969
|
+
this.signedLineCount = 0;
|
|
3739
3970
|
if (isEnabled && !wasEnabled) {
|
|
3740
3971
|
this.scrollBox.remove(this.terminal.id);
|
|
3741
3972
|
const gutterWidth = (newFormat?.length ?? 8) + 1;
|
|
@@ -3775,8 +4006,14 @@ class Pane {
|
|
|
3775
4006
|
signs.set(i, { before: formatTimestamp(new Date(this.lineTimestamps[i]), fmt) });
|
|
3776
4007
|
}
|
|
3777
4008
|
this.timestampGutter.setLineSigns(signs);
|
|
4009
|
+
this.signedLineCount = this.lineTimestamps.length;
|
|
3778
4010
|
}
|
|
3779
4011
|
destroy() {
|
|
4012
|
+
if (this.timestampUpdateTimer) {
|
|
4013
|
+
clearTimeout(this.timestampUpdateTimer);
|
|
4014
|
+
this.timestampUpdateTimer = null;
|
|
4015
|
+
}
|
|
4016
|
+
this.timestampGutter?.clearTarget();
|
|
3780
4017
|
this.terminal.destroy();
|
|
3781
4018
|
}
|
|
3782
4019
|
}
|
|
@@ -3997,11 +4234,11 @@ class StatusBar {
|
|
|
3997
4234
|
_tempMessage = null;
|
|
3998
4235
|
_tempTimer = null;
|
|
3999
4236
|
_inputMode = false;
|
|
4000
|
-
constructor(renderer) {
|
|
4237
|
+
constructor(renderer, theme = DARK_THEME) {
|
|
4001
4238
|
this.renderable = new BoxRenderable2(renderer, {
|
|
4002
4239
|
id: "status-bar",
|
|
4003
4240
|
width: "100%",
|
|
4004
|
-
backgroundColor:
|
|
4241
|
+
backgroundColor: theme.statusBarBg,
|
|
4005
4242
|
paddingX: 1,
|
|
4006
4243
|
minHeight: 1
|
|
4007
4244
|
});
|
|
@@ -4009,6 +4246,7 @@ class StatusBar {
|
|
|
4009
4246
|
id: "status-bar-text",
|
|
4010
4247
|
width: "100%",
|
|
4011
4248
|
wrapMode: "word",
|
|
4249
|
+
fg: theme.statusBarText,
|
|
4012
4250
|
content: this.buildContent()
|
|
4013
4251
|
});
|
|
4014
4252
|
this.text.selectable = false;
|
|
@@ -4094,13 +4332,15 @@ var STATUS_ICONS = {
|
|
|
4094
4332
|
failed: "\u2716",
|
|
4095
4333
|
skipped: "\u2298"
|
|
4096
4334
|
};
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4335
|
+
function getStatusIconHex(theme) {
|
|
4336
|
+
return {
|
|
4337
|
+
ready: theme.status.ready,
|
|
4338
|
+
finished: theme.status.finished,
|
|
4339
|
+
failed: theme.status.failed,
|
|
4340
|
+
stopped: theme.status.stopped,
|
|
4341
|
+
skipped: theme.status.skipped
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4104
4344
|
var TERMINAL_STATUSES = new Set(["finished", "stopped", "failed", "skipped"]);
|
|
4105
4345
|
function getDisplayOrder(originalNames, statuses) {
|
|
4106
4346
|
const active = originalNames.filter((n) => !TERMINAL_STATUSES.has(statuses.get(n)));
|
|
@@ -4120,16 +4360,17 @@ function formatDescription(status, exitCode, restartCount) {
|
|
|
4120
4360
|
}
|
|
4121
4361
|
return desc;
|
|
4122
4362
|
}
|
|
4123
|
-
function resolveOptionColors(names, statuses, processColors, inputWaiting, erroredProcesses, searchMatchProcesses) {
|
|
4363
|
+
function resolveOptionColors(names, statuses, processColors, inputWaiting, erroredProcesses, theme, searchMatchProcesses) {
|
|
4364
|
+
const statusIconHex = getStatusIconHex(theme);
|
|
4124
4365
|
return names.map((name) => {
|
|
4125
4366
|
const status = statuses.get(name);
|
|
4126
4367
|
const waiting = inputWaiting.has(name);
|
|
4127
4368
|
const errored = erroredProcesses.has(name);
|
|
4128
4369
|
const hasSearchMatch = searchMatchProcesses?.has(name);
|
|
4129
|
-
const statusHex = hasSearchMatch ?
|
|
4370
|
+
const statusHex = hasSearchMatch ? theme.searchMatchTab : waiting ? theme.inputWaiting : errored ? theme.errorIndicator : statusIconHex[status];
|
|
4130
4371
|
const processHex = processColors.get(name);
|
|
4131
4372
|
return {
|
|
4132
|
-
iconHex: statusHex ?? processHex ??
|
|
4373
|
+
iconHex: statusHex ?? processHex ?? theme.iconDefault,
|
|
4133
4374
|
nameHex: processHex ?? null
|
|
4134
4375
|
};
|
|
4135
4376
|
});
|
|
@@ -4213,13 +4454,15 @@ class TabBar {
|
|
|
4213
4454
|
inputWaiting = new Set;
|
|
4214
4455
|
erroredProcesses = new Set;
|
|
4215
4456
|
searchMatchCounts = new Map;
|
|
4216
|
-
|
|
4457
|
+
theme;
|
|
4458
|
+
constructor(renderer, names, colors, theme = DARK_THEME, reorderByStatus = false) {
|
|
4217
4459
|
this.originalNames = names;
|
|
4218
4460
|
this.names = [...names];
|
|
4219
4461
|
this.reorderByStatus = reorderByStatus;
|
|
4220
4462
|
this.statuses = new Map(names.map((n) => [n, "pending"]));
|
|
4221
4463
|
this.baseDescriptions = new Map(names.map((n) => [n, "pending"]));
|
|
4222
4464
|
this.processColors = colors ?? new Map;
|
|
4465
|
+
this.theme = theme;
|
|
4223
4466
|
this.renderable = new ColoredSelectRenderable(renderer, {
|
|
4224
4467
|
id: "tab-bar",
|
|
4225
4468
|
width: "100%",
|
|
@@ -4228,9 +4471,13 @@ class TabBar {
|
|
|
4228
4471
|
name: formatTab(n, "pending"),
|
|
4229
4472
|
description: "pending"
|
|
4230
4473
|
})),
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4474
|
+
backgroundColor: theme.sidebarBg,
|
|
4475
|
+
focusedBackgroundColor: theme.sidebarBg,
|
|
4476
|
+
selectedBackgroundColor: theme.tabSelectedBg,
|
|
4477
|
+
selectedTextColor: theme.tabSelectedText,
|
|
4478
|
+
textColor: theme.tabText,
|
|
4479
|
+
descriptionColor: theme.tabDescriptionText,
|
|
4480
|
+
selectedDescriptionColor: theme.tabSelectedDescriptionText,
|
|
4234
4481
|
showDescription: true,
|
|
4235
4482
|
wrapSelection: true
|
|
4236
4483
|
});
|
|
@@ -4314,7 +4561,7 @@ class TabBar {
|
|
|
4314
4561
|
}
|
|
4315
4562
|
updateOptionColors() {
|
|
4316
4563
|
const searchProcesses = this.searchMatchCounts.size > 0 ? new Set(this.searchMatchCounts.keys()) : undefined;
|
|
4317
|
-
const resolved = resolveOptionColors(this.names, this.statuses, this.processColors, this.inputWaiting, this.erroredProcesses, searchProcesses);
|
|
4564
|
+
const resolved = resolveOptionColors(this.names, this.statuses, this.processColors, this.inputWaiting, this.erroredProcesses, this.theme, searchProcesses);
|
|
4318
4565
|
const colors = resolved.map((c) => ({
|
|
4319
4566
|
icon: parseColor(c.iconHex),
|
|
4320
4567
|
name: c.nameHex ? parseColor(c.nameHex) : null
|
|
@@ -4347,6 +4594,7 @@ class App {
|
|
|
4347
4594
|
sidebarWidth = 20;
|
|
4348
4595
|
config;
|
|
4349
4596
|
logWriter;
|
|
4597
|
+
theme = DARK_THEME;
|
|
4350
4598
|
resizeTimer = null;
|
|
4351
4599
|
inputWaitTimers = new Map;
|
|
4352
4600
|
awaitingInput = new Set;
|
|
@@ -4357,6 +4605,9 @@ class App {
|
|
|
4357
4605
|
this.names = manager.getProcessNames();
|
|
4358
4606
|
}
|
|
4359
4607
|
async start() {
|
|
4608
|
+
log(`theme detect: pref=${this.config.theme ?? "auto"} stdin.isTTY=${process.stdin.isTTY} stdout.isTTY=${process.stdout.isTTY} COLORFGBG=${process.env.COLORFGBG ?? "(unset)"}`);
|
|
4609
|
+
this.theme = await resolveTheme(this.config.theme);
|
|
4610
|
+
log(`theme resolved: ${this.theme.mode}`);
|
|
4360
4611
|
this.renderer = await createCliRenderer({
|
|
4361
4612
|
exitOnCtrlC: false,
|
|
4362
4613
|
useMouse: true,
|
|
@@ -4375,8 +4626,8 @@ class App {
|
|
|
4375
4626
|
height: "100%",
|
|
4376
4627
|
border: false
|
|
4377
4628
|
});
|
|
4378
|
-
const processHexColors = buildProcessHexColorMap(this.names, this.config);
|
|
4379
|
-
this.tabBar = new TabBar(this.renderer, this.names, processHexColors, this.config.sort === "status");
|
|
4629
|
+
const processHexColors = buildProcessHexColorMap(this.names, this.config, this.theme.palette);
|
|
4630
|
+
this.tabBar = new TabBar(this.renderer, this.names, processHexColors, this.theme, this.config.sort === "status");
|
|
4380
4631
|
const contentRow = new BoxRenderable3(this.renderer, {
|
|
4381
4632
|
id: "content-row",
|
|
4382
4633
|
flexDirection: "row",
|
|
@@ -4389,7 +4640,8 @@ class App {
|
|
|
4389
4640
|
width: this.sidebarWidth,
|
|
4390
4641
|
height: "100%",
|
|
4391
4642
|
border: ["right"],
|
|
4392
|
-
borderColor:
|
|
4643
|
+
borderColor: this.theme.sidebarBorder,
|
|
4644
|
+
backgroundColor: this.theme.sidebarBg
|
|
4393
4645
|
});
|
|
4394
4646
|
sidebar.add(this.tabBar.renderable);
|
|
4395
4647
|
const paneContainer = new BoxRenderable3(this.renderer, {
|
|
@@ -4397,8 +4649,8 @@ class App {
|
|
|
4397
4649
|
flexGrow: 1,
|
|
4398
4650
|
border: false
|
|
4399
4651
|
});
|
|
4400
|
-
this.statusBar = new StatusBar(this.renderer);
|
|
4401
|
-
this.helpOverlay = new HelpOverlay(this.renderer);
|
|
4652
|
+
this.statusBar = new StatusBar(this.renderer, this.theme);
|
|
4653
|
+
this.helpOverlay = new HelpOverlay(this.renderer, this.theme);
|
|
4402
4654
|
this.search = new SearchController({
|
|
4403
4655
|
logWriter: this.logWriter,
|
|
4404
4656
|
statusBar: this.statusBar,
|
|
@@ -4408,7 +4660,7 @@ class App {
|
|
|
4408
4660
|
});
|
|
4409
4661
|
for (const name of this.names) {
|
|
4410
4662
|
const interactive = this.config.processes[name].interactive === true;
|
|
4411
|
-
const pane = new Pane(this.renderer, name, termCols, termRows, interactive);
|
|
4663
|
+
const pane = new Pane(this.renderer, name, termCols, termRows, interactive, this.theme);
|
|
4412
4664
|
if (this.config.timestamps) {
|
|
4413
4665
|
pane.setTimestamps(this.config.timestamps);
|
|
4414
4666
|
}
|
|
@@ -5426,6 +5678,9 @@ async function main() {
|
|
|
5426
5678
|
if (parsed.only || parsed.exclude) {
|
|
5427
5679
|
config = filterConfig(config, parsed.only, parsed.exclude);
|
|
5428
5680
|
}
|
|
5681
|
+
if (parsed.theme) {
|
|
5682
|
+
config.theme = parsed.theme;
|
|
5683
|
+
}
|
|
5429
5684
|
if (parsed.autoColors) {
|
|
5430
5685
|
for (const [name, proc] of Object.entries(config.processes)) {
|
|
5431
5686
|
if (!proc.color) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Color } from './utils/color';
|
|
2
|
+
import type { ThemePref } from './utils/theme';
|
|
2
3
|
export interface NumuxProcessConfig<K extends string = string> {
|
|
3
4
|
/** Shell command to run. Supports `$dep.group` references from dependency capture groups */
|
|
4
5
|
command: string;
|
|
@@ -122,6 +123,12 @@ export interface NumuxConfig<K extends string = string> {
|
|
|
122
123
|
noWatch?: boolean;
|
|
123
124
|
/** Directory to write per-process log files */
|
|
124
125
|
logDir?: string;
|
|
126
|
+
/**
|
|
127
|
+
* TUI color theme. `'auto'` detects the terminal background via OSC 11 (falling back
|
|
128
|
+
* to `COLORFGBG` then dark). `'light'`/`'dark'` skip detection.
|
|
129
|
+
* @default 'auto'
|
|
130
|
+
*/
|
|
131
|
+
theme?: ThemePref;
|
|
125
132
|
processes: Record<K, NumuxProcessConfig<K> | NumuxScriptPattern<K> | string | true>;
|
|
126
133
|
}
|
|
127
134
|
export type SortOrder = 'config' | 'alphabetical' | 'topological' | 'status';
|
|
@@ -138,6 +145,7 @@ export interface ResolvedNumuxConfig {
|
|
|
138
145
|
killOthersOnFail?: boolean;
|
|
139
146
|
noWatch?: boolean;
|
|
140
147
|
logDir?: string;
|
|
148
|
+
theme?: ThemePref;
|
|
141
149
|
processes: Record<string, ResolvedProcessConfig>;
|
|
142
150
|
}
|
|
143
151
|
export type ProcessStatus = 'pending' | 'starting' | 'ready' | 'running' | 'stopping' | 'stopped' | 'finished' | 'failed' | 'skipped';
|
package/dist/utils/color.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare const ANSI_RESET = "\u001B[0m";
|
|
|
34
34
|
export declare function stripAnsi(str: string): string;
|
|
35
35
|
/** Pick a deterministic color from the default palette based on the process name */
|
|
36
36
|
export declare function colorFromName(name: string): Color;
|
|
37
|
-
/** Build a map of process names to ANSI color codes, using explicit config colors or a
|
|
38
|
-
export declare function buildProcessColorMap(names: string[], config: ResolvedNumuxConfig): Map<string, string>;
|
|
37
|
+
/** Build a map of process names to ANSI color codes, using explicit config colors or a palette. */
|
|
38
|
+
export declare function buildProcessColorMap(names: string[], config: ResolvedNumuxConfig, palette?: readonly string[]): Map<string, string>;
|
|
39
39
|
/** Build a map of process names to hex color strings (for StyledText rendering). */
|
|
40
|
-
export declare function buildProcessHexColorMap(names: string[], config: ResolvedNumuxConfig): Map<string, string>;
|
|
40
|
+
export declare function buildProcessHexColorMap(names: string[], config: ResolvedNumuxConfig, palette?: readonly string[]): Map<string, string>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Light/dark theme resolution. Detects terminal background via OSC 11
|
|
3
|
+
* query, falling back to COLORFGBG env var, then to dark. Explicit
|
|
4
|
+
* user config (`theme: 'light' | 'dark'`) skips detection entirely.
|
|
5
|
+
*/
|
|
6
|
+
export type ThemeMode = 'light' | 'dark';
|
|
7
|
+
export type ThemePref = ThemeMode | 'auto';
|
|
8
|
+
export interface StatusColors {
|
|
9
|
+
ready: string;
|
|
10
|
+
failed: string;
|
|
11
|
+
stopped: string;
|
|
12
|
+
finished: string;
|
|
13
|
+
skipped: string;
|
|
14
|
+
}
|
|
15
|
+
export interface Theme {
|
|
16
|
+
mode: ThemeMode;
|
|
17
|
+
statusBarBg: string;
|
|
18
|
+
statusBarText: string;
|
|
19
|
+
helpBackdropBg: string;
|
|
20
|
+
helpBoxBg: string;
|
|
21
|
+
helpBorder: string;
|
|
22
|
+
helpText: string;
|
|
23
|
+
sidebarBg: string;
|
|
24
|
+
sidebarBorder: string;
|
|
25
|
+
tabSelectedBg: string;
|
|
26
|
+
tabSelectedText: string;
|
|
27
|
+
tabText: string;
|
|
28
|
+
tabDescriptionText: string;
|
|
29
|
+
tabSelectedDescriptionText: string;
|
|
30
|
+
scrollTrackBg: string;
|
|
31
|
+
scrollThumbBg: string;
|
|
32
|
+
searchCurrentBg: string;
|
|
33
|
+
searchMatchBg: string;
|
|
34
|
+
palette: readonly string[];
|
|
35
|
+
status: StatusColors;
|
|
36
|
+
inputWaiting: string;
|
|
37
|
+
errorIndicator: string;
|
|
38
|
+
searchMatchTab: string;
|
|
39
|
+
iconDefault: string;
|
|
40
|
+
}
|
|
41
|
+
export declare const DARK_THEME: Theme;
|
|
42
|
+
export declare const LIGHT_THEME: Theme;
|
|
43
|
+
export declare function themeFor(mode: ThemeMode): Theme;
|
|
44
|
+
/** WCAG relative luminance (0–1). */
|
|
45
|
+
export declare function relativeLuminance(r: number, g: number, b: number): number;
|
|
46
|
+
export declare function isLightRgb(r: number, g: number, b: number): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Parse OSC 11 response body. Accepts `rgb:RRRR/GGGG/BBBB` (4 hex digits,
|
|
49
|
+
* xterm/Ghostty/kitty/alacritty/iTerm2) or 2-digit short form. Returns null
|
|
50
|
+
* on malformed input.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseOSC11Response(data: string): {
|
|
53
|
+
r: number;
|
|
54
|
+
g: number;
|
|
55
|
+
b: number;
|
|
56
|
+
} | null;
|
|
57
|
+
/**
|
|
58
|
+
* Parse COLORFGBG env var (e.g. `"15;0"` = white fg on black bg = dark).
|
|
59
|
+
* Convention: bg index ≥7 is light, otherwise dark. iTerm2 sometimes sets
|
|
60
|
+
* the middle field to `default`; we read the last segment as the bg.
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseColorFgBg(value: string | undefined): ThemeMode | null;
|
|
63
|
+
/**
|
|
64
|
+
* Query the terminal's background color via OSC 11. Resolves to `null` on
|
|
65
|
+
* non-TTY, timeout, or unparseable response. Runs synchronously-ish in under
|
|
66
|
+
* `timeoutMs` (default 100ms). Must be called before any renderer takes
|
|
67
|
+
* over stdin.
|
|
68
|
+
*/
|
|
69
|
+
export declare function queryOSC11(timeoutMs?: number): Promise<ThemeMode | null>;
|
|
70
|
+
export declare function detectThemeMode(timeoutMs?: number): Promise<ThemeMode | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Resolve final theme. Explicit `'light'`/`'dark'` skips detection;
|
|
73
|
+
* `'auto'` (or undefined) runs detection, falling back to dark.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveTheme(pref?: ThemePref | undefined): Promise<Theme>;
|