numux 2.9.0 → 2.10.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 +20 -1
- package/dist/config.js +1 -0
- package/dist/numux.js +419 -12
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ numux -w dev -n redis="redis-server" --colors
|
|
|
130
130
|
# Unnamed (name derived from command)
|
|
131
131
|
numux "bun dev:api" "bun dev:web"
|
|
132
132
|
|
|
133
|
-
# Named
|
|
133
|
+
# Named process
|
|
134
134
|
numux -n api="bun dev:api" -n web="bun dev:web"
|
|
135
135
|
```
|
|
136
136
|
|
|
@@ -256,6 +256,7 @@ Each process accepts:
|
|
|
256
256
|
| `readyTimeout` | `number` | — | Milliseconds to wait for `readyPattern` before failing |
|
|
257
257
|
| `maxRestarts` | `number` | `0` | Max auto-restart attempts on non-zero exit (0 = no restarts) |
|
|
258
258
|
| `delay` | `number` | — | Milliseconds to wait before starting the process |
|
|
259
|
+
| `optional` | `boolean` | `false` | Process is visible as a tab but not started automatically. Use Alt+S to start manually |
|
|
259
260
|
| `condition` | `string` | — | Env var name; process skipped if falsy. Prefix with `!` to negate |
|
|
260
261
|
| `platform` | `string \| string[]` | — | OS(es) this process runs on (e.g. `'darwin'`, `'linux'`). Non-matching processes are removed; dependents still start |
|
|
261
262
|
| `stopSignal` | `string` | `SIGTERM` | Signal for graceful stop (`SIGTERM`, `SIGINT`, or `SIGHUP`) |
|
|
@@ -360,6 +361,24 @@ export default defineConfig({
|
|
|
360
361
|
|
|
361
362
|
Falsy values: unset, empty string, `"0"`, `"false"`, `"no"`, `"off"` (case-insensitive). If a conditional process is skipped, its dependents are also skipped.
|
|
362
363
|
|
|
364
|
+
### Optional processes
|
|
365
|
+
|
|
366
|
+
Use `optional` for tools you want visible in tabs but not auto-started (e.g. Prisma Studio, debug servers):
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
export default defineConfig({
|
|
370
|
+
processes: {
|
|
371
|
+
app: { command: 'bun run dev' },
|
|
372
|
+
studio: {
|
|
373
|
+
command: 'bunx prisma studio',
|
|
374
|
+
optional: true, // shows as stopped tab, start with Alt+S
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
})
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Unlike `condition`, optional processes don't cascade — their dependents still start normally.
|
|
381
|
+
|
|
363
382
|
### Dependency orchestration
|
|
364
383
|
|
|
365
384
|
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.
|
package/dist/config.js
CHANGED
package/dist/numux.js
CHANGED
|
@@ -37,7 +37,7 @@ var __require = import.meta.require;
|
|
|
37
37
|
var require_package = __commonJS((exports, module) => {
|
|
38
38
|
module.exports = {
|
|
39
39
|
name: "numux",
|
|
40
|
-
version: "2.
|
|
40
|
+
version: "2.10.1",
|
|
41
41
|
description: "Terminal multiplexer with dependency orchestration",
|
|
42
42
|
type: "module",
|
|
43
43
|
license: "MIT",
|
|
@@ -67,7 +67,7 @@ var require_package = __commonJS((exports, module) => {
|
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
69
|
scripts: {
|
|
70
|
-
build: "bun
|
|
70
|
+
build: "bun run build.ts",
|
|
71
71
|
prepublishOnly: "bun run build",
|
|
72
72
|
dev: "cd example && bun run dev --debug",
|
|
73
73
|
test: "bun test",
|
|
@@ -1237,6 +1237,7 @@ function validateConfig(raw, _warnings) {
|
|
|
1237
1237
|
const processWatch = validateStringOrStringArray(p.watch);
|
|
1238
1238
|
validated[name] = {
|
|
1239
1239
|
command: p.command,
|
|
1240
|
+
...p.optional === true ? { optional: true } : {},
|
|
1240
1241
|
cwd: processCwd ?? globalCwd,
|
|
1241
1242
|
env: globalEnv || processEnv ? { ...globalEnv, ...processEnv } : undefined,
|
|
1242
1243
|
envFile: processEnvFile ?? globalEnvFile,
|
|
@@ -1967,6 +1968,12 @@ class ProcessManager {
|
|
|
1967
1968
|
const launches = this.tiers.flat().map(async (name) => {
|
|
1968
1969
|
const proc = this.config.processes[name];
|
|
1969
1970
|
const resolve8 = readyResolvers.get(name);
|
|
1971
|
+
if (proc.optional) {
|
|
1972
|
+
this.updateStatus(name, "stopped");
|
|
1973
|
+
this.createRunner(name);
|
|
1974
|
+
resolve8();
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1970
1977
|
const deps = proc.dependsOn ?? [];
|
|
1971
1978
|
if (deps.length > 0) {
|
|
1972
1979
|
await Promise.all(deps.map((d) => readyPromises.get(d)));
|
|
@@ -2313,7 +2320,407 @@ import {
|
|
|
2313
2320
|
LineNumberRenderable,
|
|
2314
2321
|
ScrollBoxRenderable
|
|
2315
2322
|
} from "@opentui/core";
|
|
2316
|
-
|
|
2323
|
+
|
|
2324
|
+
// node_modules/ghostty-opentui/src/terminal-buffer.ts
|
|
2325
|
+
import {
|
|
2326
|
+
TextBufferRenderable,
|
|
2327
|
+
StyledText,
|
|
2328
|
+
RGBA
|
|
2329
|
+
} from "@opentui/core";
|
|
2330
|
+
import { ptyToJson, PersistentTerminal, hasPersistentTerminalSupport, StyleFlags } from "ghostty-opentui";
|
|
2331
|
+
var DEFAULT_FG = RGBA.fromHex("#d4d4d4");
|
|
2332
|
+
var DEFAULT_BG = RGBA.fromHex("#1e1e1e");
|
|
2333
|
+
var TextAttributes = {
|
|
2334
|
+
BOLD: 1 << 0,
|
|
2335
|
+
DIM: 1 << 1,
|
|
2336
|
+
ITALIC: 1 << 2,
|
|
2337
|
+
UNDERLINE: 1 << 3,
|
|
2338
|
+
BLINK: 1 << 4,
|
|
2339
|
+
REVERSE: 1 << 5,
|
|
2340
|
+
HIDDEN: 1 << 6,
|
|
2341
|
+
STRIKETHROUGH: 1 << 7
|
|
2342
|
+
};
|
|
2343
|
+
function convertSpanToChunk(span) {
|
|
2344
|
+
const { text, fg, bg, flags } = span;
|
|
2345
|
+
let fgColor = fg ? RGBA.fromHex(fg) : DEFAULT_FG;
|
|
2346
|
+
let bgColor = bg ? RGBA.fromHex(bg) : undefined;
|
|
2347
|
+
if (flags & StyleFlags.INVERSE) {
|
|
2348
|
+
const temp = fgColor;
|
|
2349
|
+
fgColor = bgColor || DEFAULT_BG;
|
|
2350
|
+
bgColor = temp;
|
|
2351
|
+
}
|
|
2352
|
+
let attributes = 0;
|
|
2353
|
+
if (flags & StyleFlags.BOLD)
|
|
2354
|
+
attributes |= TextAttributes.BOLD;
|
|
2355
|
+
if (flags & StyleFlags.ITALIC)
|
|
2356
|
+
attributes |= TextAttributes.ITALIC;
|
|
2357
|
+
if (flags & StyleFlags.UNDERLINE)
|
|
2358
|
+
attributes |= TextAttributes.UNDERLINE;
|
|
2359
|
+
if (flags & StyleFlags.STRIKETHROUGH)
|
|
2360
|
+
attributes |= TextAttributes.STRIKETHROUGH;
|
|
2361
|
+
if (flags & StyleFlags.FAINT)
|
|
2362
|
+
attributes |= TextAttributes.DIM;
|
|
2363
|
+
return { __isChunk: true, text, fg: fgColor, bg: bgColor, attributes };
|
|
2364
|
+
}
|
|
2365
|
+
function applyHighlightsToLine(chunks, highlights) {
|
|
2366
|
+
if (highlights.length === 0)
|
|
2367
|
+
return chunks;
|
|
2368
|
+
const result = [];
|
|
2369
|
+
let col = 0;
|
|
2370
|
+
for (const chunk of chunks) {
|
|
2371
|
+
const chunkStart = col;
|
|
2372
|
+
const chunkEnd = col + chunk.text.length;
|
|
2373
|
+
const overlappingHighlights = highlights.filter((hl) => hl.start < chunkEnd && hl.end > chunkStart);
|
|
2374
|
+
if (overlappingHighlights.length === 0) {
|
|
2375
|
+
result.push(chunk);
|
|
2376
|
+
col = chunkEnd;
|
|
2377
|
+
continue;
|
|
2378
|
+
}
|
|
2379
|
+
let pos = 0;
|
|
2380
|
+
const text = chunk.text;
|
|
2381
|
+
const sortedHighlights = [...overlappingHighlights].sort((a, b) => a.start - b.start);
|
|
2382
|
+
for (const hl of sortedHighlights) {
|
|
2383
|
+
const hlStartInChunk = Math.max(0, hl.start - chunkStart);
|
|
2384
|
+
const hlEndInChunk = Math.min(text.length, hl.end - chunkStart);
|
|
2385
|
+
if (pos < hlStartInChunk) {
|
|
2386
|
+
result.push({
|
|
2387
|
+
__isChunk: true,
|
|
2388
|
+
text: text.slice(pos, hlStartInChunk),
|
|
2389
|
+
fg: chunk.fg,
|
|
2390
|
+
bg: chunk.bg,
|
|
2391
|
+
attributes: chunk.attributes
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
if (hlStartInChunk < hlEndInChunk) {
|
|
2395
|
+
const highlightedText = text.slice(hlStartInChunk, hlEndInChunk);
|
|
2396
|
+
const displayText = hl.replaceWithX ? "x".repeat(highlightedText.length) : highlightedText;
|
|
2397
|
+
result.push({
|
|
2398
|
+
__isChunk: true,
|
|
2399
|
+
text: displayText,
|
|
2400
|
+
fg: chunk.fg,
|
|
2401
|
+
bg: RGBA.fromHex(hl.backgroundColor),
|
|
2402
|
+
attributes: chunk.attributes
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
2405
|
+
pos = hlEndInChunk;
|
|
2406
|
+
}
|
|
2407
|
+
if (pos < text.length) {
|
|
2408
|
+
result.push({
|
|
2409
|
+
__isChunk: true,
|
|
2410
|
+
text: text.slice(pos),
|
|
2411
|
+
fg: chunk.fg,
|
|
2412
|
+
bg: chunk.bg,
|
|
2413
|
+
attributes: chunk.attributes
|
|
2414
|
+
});
|
|
2415
|
+
}
|
|
2416
|
+
col = chunkEnd;
|
|
2417
|
+
}
|
|
2418
|
+
return result;
|
|
2419
|
+
}
|
|
2420
|
+
function makeCursorChunk(char, style, original) {
|
|
2421
|
+
if (style === "block") {
|
|
2422
|
+
return {
|
|
2423
|
+
__isChunk: true,
|
|
2424
|
+
text: char,
|
|
2425
|
+
fg: original?.bg || RGBA.fromHex("#1e1e1e"),
|
|
2426
|
+
bg: original?.fg || DEFAULT_FG,
|
|
2427
|
+
attributes: original?.attributes ?? 0
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
return {
|
|
2431
|
+
__isChunk: true,
|
|
2432
|
+
text: char,
|
|
2433
|
+
fg: original?.fg || DEFAULT_FG,
|
|
2434
|
+
bg: original?.bg,
|
|
2435
|
+
attributes: (original?.attributes ?? 0) | TextAttributes.UNDERLINE
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
function applyCursorToLine(chunks, cursorX, cursorStyle) {
|
|
2439
|
+
const totalLen = chunks.reduce((sum, c) => sum + c.text.length, 0);
|
|
2440
|
+
if (cursorX >= totalLen) {
|
|
2441
|
+
const gap = cursorX - totalLen;
|
|
2442
|
+
if (gap > 0) {
|
|
2443
|
+
return [...chunks, { __isChunk: true, text: " ".repeat(gap), attributes: 0 }, makeCursorChunk(" ", cursorStyle)];
|
|
2444
|
+
}
|
|
2445
|
+
return [...chunks, makeCursorChunk(" ", cursorStyle)];
|
|
2446
|
+
}
|
|
2447
|
+
const result = [];
|
|
2448
|
+
let col = 0;
|
|
2449
|
+
for (const chunk of chunks) {
|
|
2450
|
+
const chunkEnd = col + chunk.text.length;
|
|
2451
|
+
if (cursorX >= col && cursorX < chunkEnd) {
|
|
2452
|
+
const pos = cursorX - col;
|
|
2453
|
+
if (pos > 0) {
|
|
2454
|
+
result.push({ ...chunk, text: chunk.text.slice(0, pos) });
|
|
2455
|
+
}
|
|
2456
|
+
result.push(makeCursorChunk(chunk.text[pos], cursorStyle, chunk));
|
|
2457
|
+
if (pos + 1 < chunk.text.length) {
|
|
2458
|
+
result.push({ ...chunk, text: chunk.text.slice(pos + 1) });
|
|
2459
|
+
}
|
|
2460
|
+
} else {
|
|
2461
|
+
result.push(chunk);
|
|
2462
|
+
}
|
|
2463
|
+
col = chunkEnd;
|
|
2464
|
+
}
|
|
2465
|
+
return result;
|
|
2466
|
+
}
|
|
2467
|
+
function terminalDataToStyledText(data, highlights, cursor) {
|
|
2468
|
+
const chunks = [];
|
|
2469
|
+
const highlightsByLine = new Map;
|
|
2470
|
+
if (highlights) {
|
|
2471
|
+
for (const hl of highlights) {
|
|
2472
|
+
const lineHighlights = highlightsByLine.get(hl.line) ?? [];
|
|
2473
|
+
lineHighlights.push(hl);
|
|
2474
|
+
highlightsByLine.set(hl.line, lineHighlights);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
for (let i = 0;i < data.lines.length; i++) {
|
|
2478
|
+
const line = data.lines[i];
|
|
2479
|
+
let lineChunks = [];
|
|
2480
|
+
if (line.spans.length === 0) {
|
|
2481
|
+
lineChunks.push({ __isChunk: true, text: " ", attributes: 0 });
|
|
2482
|
+
} else {
|
|
2483
|
+
for (const span of line.spans) {
|
|
2484
|
+
lineChunks.push(convertSpanToChunk(span));
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
const lineHighlights = highlightsByLine.get(i);
|
|
2488
|
+
if (lineHighlights) {
|
|
2489
|
+
lineChunks = applyHighlightsToLine(lineChunks, lineHighlights);
|
|
2490
|
+
}
|
|
2491
|
+
if (cursor && i === cursor.y) {
|
|
2492
|
+
lineChunks = applyCursorToLine(lineChunks, cursor.x, cursor.style);
|
|
2493
|
+
}
|
|
2494
|
+
chunks.push(...lineChunks);
|
|
2495
|
+
if (i < data.lines.length - 1) {
|
|
2496
|
+
chunks.push({ __isChunk: true, text: `
|
|
2497
|
+
`, attributes: 0 });
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
return new StyledText(chunks);
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
class GhosttyTerminalRenderable extends TextBufferRenderable {
|
|
2504
|
+
_ansi;
|
|
2505
|
+
_cols;
|
|
2506
|
+
_rows;
|
|
2507
|
+
_limit;
|
|
2508
|
+
_trimEnd;
|
|
2509
|
+
_highlights;
|
|
2510
|
+
_ansiDirty = false;
|
|
2511
|
+
_lineCount = 0;
|
|
2512
|
+
_showCursor = false;
|
|
2513
|
+
_cursorStyle = "block";
|
|
2514
|
+
_persistent = false;
|
|
2515
|
+
_persistentTerminal = null;
|
|
2516
|
+
constructor(ctx, options) {
|
|
2517
|
+
super(ctx, {
|
|
2518
|
+
...options,
|
|
2519
|
+
fg: DEFAULT_FG,
|
|
2520
|
+
wrapMode: "none"
|
|
2521
|
+
});
|
|
2522
|
+
this._ansi = options.ansi ?? "";
|
|
2523
|
+
this._cols = options.cols ?? 120;
|
|
2524
|
+
this._rows = options.rows ?? 40;
|
|
2525
|
+
this._limit = options.limit;
|
|
2526
|
+
this._trimEnd = options.trimEnd;
|
|
2527
|
+
this._highlights = options.highlights;
|
|
2528
|
+
this._persistent = options.persistent ?? false;
|
|
2529
|
+
this._showCursor = options.showCursor ?? false;
|
|
2530
|
+
this._cursorStyle = options.cursorStyle ?? "block";
|
|
2531
|
+
if (this._persistent && hasPersistentTerminalSupport()) {
|
|
2532
|
+
this._persistentTerminal = new PersistentTerminal({
|
|
2533
|
+
cols: this._cols,
|
|
2534
|
+
rows: this._rows
|
|
2535
|
+
});
|
|
2536
|
+
if (this._ansi && (typeof this._ansi === "string" ? this._ansi.length > 0 : this._ansi.length > 0)) {
|
|
2537
|
+
this._persistentTerminal.feed(this._ansi);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
this._ansiDirty = true;
|
|
2541
|
+
}
|
|
2542
|
+
get lineCount() {
|
|
2543
|
+
return this._lineCount;
|
|
2544
|
+
}
|
|
2545
|
+
get limit() {
|
|
2546
|
+
return this._limit;
|
|
2547
|
+
}
|
|
2548
|
+
set limit(value) {
|
|
2549
|
+
if (this._limit !== value) {
|
|
2550
|
+
this._limit = value;
|
|
2551
|
+
this._ansiDirty = true;
|
|
2552
|
+
this.requestRender();
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
get trimEnd() {
|
|
2556
|
+
return this._trimEnd;
|
|
2557
|
+
}
|
|
2558
|
+
set trimEnd(value) {
|
|
2559
|
+
if (this._trimEnd !== value) {
|
|
2560
|
+
this._trimEnd = value;
|
|
2561
|
+
this._ansiDirty = true;
|
|
2562
|
+
this.requestRender();
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
get highlights() {
|
|
2566
|
+
return this._highlights;
|
|
2567
|
+
}
|
|
2568
|
+
set highlights(value) {
|
|
2569
|
+
this._highlights = value;
|
|
2570
|
+
this._ansiDirty = true;
|
|
2571
|
+
this.requestRender();
|
|
2572
|
+
}
|
|
2573
|
+
get showCursor() {
|
|
2574
|
+
return this._showCursor;
|
|
2575
|
+
}
|
|
2576
|
+
set showCursor(value) {
|
|
2577
|
+
if (this._showCursor !== value) {
|
|
2578
|
+
this._showCursor = value;
|
|
2579
|
+
this._ansiDirty = true;
|
|
2580
|
+
this.requestRender();
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
get cursorStyle() {
|
|
2584
|
+
return this._cursorStyle;
|
|
2585
|
+
}
|
|
2586
|
+
set cursorStyle(value) {
|
|
2587
|
+
if (this._cursorStyle !== value) {
|
|
2588
|
+
this._cursorStyle = value;
|
|
2589
|
+
this._ansiDirty = true;
|
|
2590
|
+
this.requestRender();
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
get ansi() {
|
|
2594
|
+
return this._ansi;
|
|
2595
|
+
}
|
|
2596
|
+
set ansi(value) {
|
|
2597
|
+
if (this._ansi !== value) {
|
|
2598
|
+
this._ansi = value;
|
|
2599
|
+
if (this._persistentTerminal) {
|
|
2600
|
+
this._persistentTerminal.reset();
|
|
2601
|
+
if (value && (typeof value === "string" ? value.length > 0 : value.length > 0)) {
|
|
2602
|
+
this._persistentTerminal.feed(value);
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
this._ansiDirty = true;
|
|
2606
|
+
this.requestRender();
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
get cols() {
|
|
2610
|
+
return this._cols;
|
|
2611
|
+
}
|
|
2612
|
+
set cols(value) {
|
|
2613
|
+
if (this._cols !== value) {
|
|
2614
|
+
this._cols = value;
|
|
2615
|
+
if (this._persistentTerminal) {
|
|
2616
|
+
this._persistentTerminal.resize(value, this._rows);
|
|
2617
|
+
}
|
|
2618
|
+
this._ansiDirty = true;
|
|
2619
|
+
this.requestRender();
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
get rows() {
|
|
2623
|
+
return this._rows;
|
|
2624
|
+
}
|
|
2625
|
+
set rows(value) {
|
|
2626
|
+
if (this._rows !== value) {
|
|
2627
|
+
this._rows = value;
|
|
2628
|
+
if (this._persistentTerminal) {
|
|
2629
|
+
this._persistentTerminal.resize(this._cols, value);
|
|
2630
|
+
}
|
|
2631
|
+
this._ansiDirty = true;
|
|
2632
|
+
this.requestRender();
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
get persistent() {
|
|
2636
|
+
return this._persistent;
|
|
2637
|
+
}
|
|
2638
|
+
set persistent(_value) {}
|
|
2639
|
+
feed(data) {
|
|
2640
|
+
if (!this._persistentTerminal) {
|
|
2641
|
+
throw new Error("feed() is only available in persistent mode. Set persistent=true in options.");
|
|
2642
|
+
}
|
|
2643
|
+
this._persistentTerminal.feed(data);
|
|
2644
|
+
this._ansiDirty = true;
|
|
2645
|
+
this.requestRender();
|
|
2646
|
+
}
|
|
2647
|
+
reset() {
|
|
2648
|
+
if (!this._persistentTerminal) {
|
|
2649
|
+
throw new Error("reset() is only available in persistent mode. Set persistent=true in options.");
|
|
2650
|
+
}
|
|
2651
|
+
this._persistentTerminal.reset();
|
|
2652
|
+
this._ansiDirty = true;
|
|
2653
|
+
this.requestRender();
|
|
2654
|
+
}
|
|
2655
|
+
getCursor() {
|
|
2656
|
+
if (!this._persistentTerminal) {
|
|
2657
|
+
throw new Error("getCursor() is only available in persistent mode. Set persistent=true in options.");
|
|
2658
|
+
}
|
|
2659
|
+
return this._persistentTerminal.getCursor();
|
|
2660
|
+
}
|
|
2661
|
+
getText() {
|
|
2662
|
+
if (this._persistentTerminal) {
|
|
2663
|
+
return this._persistentTerminal.getText();
|
|
2664
|
+
}
|
|
2665
|
+
throw new Error("getText() in stateless mode is not implemented. Use persistent=true.");
|
|
2666
|
+
}
|
|
2667
|
+
destroy() {
|
|
2668
|
+
if (this._persistentTerminal) {
|
|
2669
|
+
this._persistentTerminal.destroy();
|
|
2670
|
+
this._persistentTerminal = null;
|
|
2671
|
+
}
|
|
2672
|
+
super.destroy();
|
|
2673
|
+
}
|
|
2674
|
+
renderSelf(buffer) {
|
|
2675
|
+
if (this._ansiDirty) {
|
|
2676
|
+
let data;
|
|
2677
|
+
if (this._persistentTerminal) {
|
|
2678
|
+
data = this._persistentTerminal.getJson({
|
|
2679
|
+
limit: this._limit
|
|
2680
|
+
});
|
|
2681
|
+
} else {
|
|
2682
|
+
data = ptyToJson(this._ansi, {
|
|
2683
|
+
cols: this._cols,
|
|
2684
|
+
rows: this._rows,
|
|
2685
|
+
limit: this._limit
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
if (this._trimEnd) {
|
|
2689
|
+
while (data.lines.length > 0) {
|
|
2690
|
+
const lastLine = data.lines[data.lines.length - 1];
|
|
2691
|
+
const hasText = lastLine.spans.some((span) => span.text.trim().length > 0);
|
|
2692
|
+
if (hasText)
|
|
2693
|
+
break;
|
|
2694
|
+
data.lines.pop();
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
const cursor = this._showCursor ? {
|
|
2698
|
+
x: data.cursor[0],
|
|
2699
|
+
y: Math.max(0, data.totalLines - data.rows + data.cursor[1] - data.offset),
|
|
2700
|
+
style: this._cursorStyle
|
|
2701
|
+
} : undefined;
|
|
2702
|
+
const styledText = terminalDataToStyledText(data, this._highlights, cursor);
|
|
2703
|
+
this.textBuffer.setStyledText(styledText);
|
|
2704
|
+
this.updateTextInfo();
|
|
2705
|
+
const lineInfo = this.textBufferView.logicalLineInfo;
|
|
2706
|
+
if (lineInfo) {
|
|
2707
|
+
this._lineCount = lineInfo.lineStarts.length;
|
|
2708
|
+
}
|
|
2709
|
+
this._ansiDirty = false;
|
|
2710
|
+
}
|
|
2711
|
+
super.renderSelf(buffer);
|
|
2712
|
+
}
|
|
2713
|
+
getScrollPositionForLine(lineNumber) {
|
|
2714
|
+
const clampedLine = Math.max(0, Math.min(lineNumber, this._lineCount - 1));
|
|
2715
|
+
const lineInfo = this.textBufferView.logicalLineInfo;
|
|
2716
|
+
const lineStarts = lineInfo?.lineStarts;
|
|
2717
|
+
let lineYOffset = clampedLine;
|
|
2718
|
+
if (lineStarts && lineStarts.length > clampedLine) {
|
|
2719
|
+
lineYOffset = lineStarts[clampedLine];
|
|
2720
|
+
}
|
|
2721
|
+
return this.y + lineYOffset;
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2317
2724
|
|
|
2318
2725
|
// src/utils/timestamp.ts
|
|
2319
2726
|
var DEFAULT_TIMESTAMP_FORMAT = "HH:mm:ss";
|
|
@@ -2789,7 +3196,7 @@ class SearchController {
|
|
|
2789
3196
|
}
|
|
2790
3197
|
|
|
2791
3198
|
// src/ui/status-bar.ts
|
|
2792
|
-
import { cyan, red, reverse, StyledText, TextRenderable, yellow } from "@opentui/core";
|
|
3199
|
+
import { cyan, red, reverse, StyledText as StyledText2, TextRenderable, yellow } from "@opentui/core";
|
|
2793
3200
|
function plain(text) {
|
|
2794
3201
|
return { __isChunk: true, text };
|
|
2795
3202
|
}
|
|
@@ -2835,12 +3242,12 @@ class StatusBar {
|
|
|
2835
3242
|
}
|
|
2836
3243
|
buildContent() {
|
|
2837
3244
|
if (this._tempMessage) {
|
|
2838
|
-
return new
|
|
3245
|
+
return new StyledText2([cyan(this._tempMessage)]);
|
|
2839
3246
|
}
|
|
2840
3247
|
if (this._searchMode) {
|
|
2841
3248
|
return this.buildSearchContent();
|
|
2842
3249
|
}
|
|
2843
|
-
return new
|
|
3250
|
+
return new StyledText2([plain(STATUS_BAR_TEXT)]);
|
|
2844
3251
|
}
|
|
2845
3252
|
buildSearchContent() {
|
|
2846
3253
|
const chunks = [];
|
|
@@ -2866,7 +3273,7 @@ class StatusBar {
|
|
|
2866
3273
|
chunks.push(plain(" Enter: next"));
|
|
2867
3274
|
}
|
|
2868
3275
|
chunks.push(plain(` Tab: ${isAllMode ? "single" : "all"} Esc: close`));
|
|
2869
|
-
return new
|
|
3276
|
+
return new StyledText2(chunks);
|
|
2870
3277
|
}
|
|
2871
3278
|
}
|
|
2872
3279
|
|
|
@@ -3529,8 +3936,8 @@ class PrefixDisplay {
|
|
|
3529
3936
|
this.shutdown();
|
|
3530
3937
|
});
|
|
3531
3938
|
process.on("unhandledRejection", (reason) => {
|
|
3532
|
-
const
|
|
3533
|
-
process.stderr.write(`numux: unhandled rejection: ${
|
|
3939
|
+
const stack = reason instanceof Error ? reason.stack : String(reason);
|
|
3940
|
+
process.stderr.write(`numux: unhandled rejection: ${stack}
|
|
3534
3941
|
`);
|
|
3535
3942
|
this.shutdown();
|
|
3536
3943
|
});
|
|
@@ -3941,10 +4348,10 @@ function setupShutdownHandlers(app, logWriter) {
|
|
|
3941
4348
|
});
|
|
3942
4349
|
});
|
|
3943
4350
|
process.on("unhandledRejection", (reason) => {
|
|
3944
|
-
const
|
|
3945
|
-
log("Unhandled rejection:",
|
|
4351
|
+
const stack = reason instanceof Error ? reason.stack : String(reason);
|
|
4352
|
+
log("Unhandled rejection:", stack);
|
|
3946
4353
|
app.shutdown().finally(() => {
|
|
3947
|
-
process.stderr.write(`numux: unhandled rejection: ${
|
|
4354
|
+
process.stderr.write(`numux: unhandled rejection: ${stack}
|
|
3948
4355
|
`);
|
|
3949
4356
|
logWriter?.cleanup();
|
|
3950
4357
|
process.exit(1);
|
package/dist/types.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface NumuxProcessConfig<K extends string = string> {
|
|
|
43
43
|
* @default false
|
|
44
44
|
*/
|
|
45
45
|
interactive?: boolean;
|
|
46
|
+
/** Process is visible but not started automatically. Use Alt+S to start manually */
|
|
47
|
+
optional?: boolean;
|
|
46
48
|
/** `true` = detect ANSI red output, string = regex pattern */
|
|
47
49
|
errorMatcher?: boolean | string;
|
|
48
50
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "numux",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.1",
|
|
4
4
|
"description": "Terminal multiplexer with dependency orchestration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "bun
|
|
33
|
+
"build": "bun run build.ts",
|
|
34
34
|
"prepublishOnly": "bun run build",
|
|
35
35
|
"dev": "cd example && bun run dev --debug",
|
|
36
36
|
"test": "bun test",
|