numux 2.11.0 → 2.12.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 +14 -7
- package/dist/man/numux.1 +16 -9
- package/dist/numux.js +126 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,13 +89,6 @@ numux exec api -- npx prisma migrate
|
|
|
89
89
|
numux exec web npm run build
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
`logs` prints the log directory path, or a specific process's log contents:
|
|
93
|
-
|
|
94
|
-
```sh
|
|
95
|
-
numux logs # Print log directory path
|
|
96
|
-
numux logs api # Print the api process log
|
|
97
|
-
```
|
|
98
|
-
|
|
99
92
|
Set up completions for your shell:
|
|
100
93
|
|
|
101
94
|
```sh
|
|
@@ -250,6 +243,20 @@ numux --prefix
|
|
|
250
243
|
|
|
251
244
|
Auto-exits when all processes finish. Exit code 1 if any process failed.
|
|
252
245
|
|
|
246
|
+
## Logging
|
|
247
|
+
|
|
248
|
+
numux writes per-process log files (ANSI-stripped) when `--log-dir` is set or `logDir` is configured. Each session creates a timestamped subdirectory with a `latest` symlink pointing to the most recent run.
|
|
249
|
+
|
|
250
|
+
<!-- generated:logging-usage -->
|
|
251
|
+
```sh
|
|
252
|
+
numux logs # Print log directory path
|
|
253
|
+
numux logs api # Pipe the api process log to stdout
|
|
254
|
+
numux logs api | grep "ERROR" # Search process logs
|
|
255
|
+
numux logs api | tail -f # Follow process log output
|
|
256
|
+
```
|
|
257
|
+
<!-- /generated:logging-usage -->
|
|
258
|
+
|
|
259
|
+
|
|
253
260
|
## Config reference
|
|
254
261
|
|
|
255
262
|
### Global options
|
package/dist/man/numux.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH "NUMUX" "1" "April 2026" "2.
|
|
1
|
+
.TH "NUMUX" "1" "April 2026" "2.12.0" "numux manual"
|
|
2
2
|
.SH "NAME"
|
|
3
3
|
\fBnumux\fR
|
|
4
4
|
.P
|
|
@@ -97,14 +97,6 @@ numux exec web npm run build
|
|
|
97
97
|
.fi
|
|
98
98
|
.RE
|
|
99
99
|
.P
|
|
100
|
-
\fBlogs\fP prints the log directory path, or a specific process's log contents:
|
|
101
|
-
.RS 2
|
|
102
|
-
.nf
|
|
103
|
-
numux logs # Print log directory path
|
|
104
|
-
numux logs api # Print the api process log
|
|
105
|
-
.fi
|
|
106
|
-
.RE
|
|
107
|
-
.P
|
|
108
100
|
Set up completions for your shell:
|
|
109
101
|
.RS 2
|
|
110
102
|
.nf
|
|
@@ -386,6 +378,21 @@ numux \-\-prefix
|
|
|
386
378
|
.RE
|
|
387
379
|
.P
|
|
388
380
|
Auto\-exits when all processes finish\. Exit code 1 if any process failed\.
|
|
381
|
+
.SH Logging
|
|
382
|
+
.P
|
|
383
|
+
numux writes per\-process log files (ANSI\-stripped) when \fB\-\-log\-dir\fP is set or \fBlogDir\fP is configured\. Each session creates a timestamped subdirectory with a \fBlatest\fP symlink pointing to the most recent run\.
|
|
384
|
+
<!\-\- generated:logging\-usage \-\->
|
|
385
|
+
.RS 2
|
|
386
|
+
.nf
|
|
387
|
+
numux logs # Print log directory path
|
|
388
|
+
numux logs api # Pipe the api process log to stdout
|
|
389
|
+
numux logs api | grep "ERROR" # Search process logs
|
|
390
|
+
numux logs api | tail \-f # Follow process log output
|
|
391
|
+
.fi
|
|
392
|
+
.RE
|
|
393
|
+
<!\-\- /generated:logging\-usage \-\->
|
|
394
|
+
|
|
395
|
+
|
|
389
396
|
.SH Config reference
|
|
390
397
|
.SS Global options
|
|
391
398
|
.P
|
package/dist/numux.js
CHANGED
|
@@ -127,13 +127,6 @@ numux exec api -- npx prisma migrate
|
|
|
127
127
|
numux exec web npm run build
|
|
128
128
|
\`\`\`
|
|
129
129
|
|
|
130
|
-
\`logs\` prints the log directory path, or a specific process's log contents:
|
|
131
|
-
|
|
132
|
-
\`\`\`sh
|
|
133
|
-
numux logs # Print log directory path
|
|
134
|
-
numux logs api # Print the api process log
|
|
135
|
-
\`\`\`
|
|
136
|
-
|
|
137
130
|
Set up completions for your shell:
|
|
138
131
|
|
|
139
132
|
\`\`\`sh
|
|
@@ -264,6 +257,16 @@ numux --prefix
|
|
|
264
257
|
\`\`\`
|
|
265
258
|
|
|
266
259
|
Auto-exits when all processes finish. Exit code 1 if any process failed.` },
|
|
260
|
+
logging: { title: "Logging", body: `numux writes per-process log files (ANSI-stripped) when \`--log-dir\` is set or \`logDir\` is configured. Each session creates a timestamped subdirectory with a \`latest\` symlink pointing to the most recent run.
|
|
261
|
+
|
|
262
|
+
<!-- generated:logging-usage -->
|
|
263
|
+
\`\`\`sh
|
|
264
|
+
numux logs # Print log directory path
|
|
265
|
+
numux logs api # Pipe the api process log to stdout
|
|
266
|
+
numux logs api | grep "ERROR" # Search process logs
|
|
267
|
+
numux logs api | tail -f # Follow process log output
|
|
268
|
+
\`\`\`
|
|
269
|
+
<!-- /generated:logging-usage -->` },
|
|
267
270
|
"global-options": { title: "Global options", body: `Top-level options apply to all processes (process-level settings override):
|
|
268
271
|
|
|
269
272
|
<!-- generated:config-global -->
|
|
@@ -538,7 +541,7 @@ var init_help = __esm(() => {
|
|
|
538
541
|
var require_package = __commonJS((exports, module) => {
|
|
539
542
|
module.exports = {
|
|
540
543
|
name: "numux",
|
|
541
|
-
version: "2.
|
|
544
|
+
version: "2.12.0",
|
|
542
545
|
description: "Terminal multiplexer with dependency orchestration",
|
|
543
546
|
type: "module",
|
|
544
547
|
license: "MIT",
|
|
@@ -925,6 +928,14 @@ var SUBCOMMANDS = [
|
|
|
925
928
|
name: "logs",
|
|
926
929
|
description: "Open the log directory or a specific process log",
|
|
927
930
|
usage: "logs [name]",
|
|
931
|
+
examples: [
|
|
932
|
+
["numux logs", "Print log directory path"],
|
|
933
|
+
["numux logs api", "Pipe the api process log to stdout"],
|
|
934
|
+
['numux logs api | grep "ERROR"', "Search process logs"],
|
|
935
|
+
["numux logs api | tail -f", "Follow process log output"]
|
|
936
|
+
],
|
|
937
|
+
completionArgs: "dynamic",
|
|
938
|
+
completionScript: "numux logs 2>/dev/null",
|
|
928
939
|
parse: (args, i, result) => {
|
|
929
940
|
result.logs = true;
|
|
930
941
|
const next = args[i + 1];
|
|
@@ -939,6 +950,7 @@ var SUBCOMMANDS = [
|
|
|
939
950
|
name: "completions",
|
|
940
951
|
description: "Generate shell completions (bash, zsh, fish)",
|
|
941
952
|
usage: "completions <shell>",
|
|
953
|
+
completionArgs: ["bash", "zsh", "fish"],
|
|
942
954
|
parse: (args, i, result) => {
|
|
943
955
|
const next = args[++i];
|
|
944
956
|
if (next === undefined)
|
|
@@ -951,6 +963,7 @@ var SUBCOMMANDS = [
|
|
|
951
963
|
name: "help",
|
|
952
964
|
description: "Show help for a topic",
|
|
953
965
|
usage: "help [topic]",
|
|
966
|
+
completionArgs: "dynamic",
|
|
954
967
|
parse: (args, i, result) => {
|
|
955
968
|
result.help = true;
|
|
956
969
|
const next = args[i + 1];
|
|
@@ -1151,6 +1164,13 @@ function filterConfig(config, only, exclude) {
|
|
|
1151
1164
|
init_help_topics();
|
|
1152
1165
|
var SUPPORTED_SHELLS = ["bash", "zsh", "fish"];
|
|
1153
1166
|
var HELP_TOPIC_NAMES = [...Object.keys(HELP_TOPICS), ...Object.keys(TOPIC_ALIASES)];
|
|
1167
|
+
function resolveArgs(sub) {
|
|
1168
|
+
if (!sub.completionArgs)
|
|
1169
|
+
return;
|
|
1170
|
+
if (sub.completionArgs === "dynamic" && sub.name === "help")
|
|
1171
|
+
return HELP_TOPIC_NAMES;
|
|
1172
|
+
return sub.completionArgs;
|
|
1173
|
+
}
|
|
1154
1174
|
function generateCompletions(shell) {
|
|
1155
1175
|
switch (shell) {
|
|
1156
1176
|
case "bash":
|
|
@@ -1188,12 +1208,30 @@ function bashCompletions() {
|
|
|
1188
1208
|
return ;;`);
|
|
1189
1209
|
}
|
|
1190
1210
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1211
|
+
for (const sub of SUBCOMMANDS) {
|
|
1212
|
+
const args = resolveArgs(sub);
|
|
1213
|
+
if (!args)
|
|
1214
|
+
continue;
|
|
1215
|
+
if (args === "dynamic") {
|
|
1216
|
+
const script = sub.completionScript;
|
|
1217
|
+
if (!script)
|
|
1218
|
+
continue;
|
|
1219
|
+
const dir = `$(${script})`;
|
|
1220
|
+
caseEntries.push(` ${sub.name})
|
|
1221
|
+
local logdir
|
|
1222
|
+
logdir="${dir}"
|
|
1223
|
+
if [ -n "$logdir" ] && [ -d "$logdir" ]; then
|
|
1224
|
+
local names
|
|
1225
|
+
names="$(ls "$logdir"/*.log 2>/dev/null | xargs -I{} basename {} .log)"
|
|
1226
|
+
COMPREPLY=( $(compgen -W "$names" -- "$cur") )
|
|
1227
|
+
fi
|
|
1193
1228
|
return ;;`);
|
|
1194
|
-
|
|
1195
|
-
|
|
1229
|
+
} else {
|
|
1230
|
+
caseEntries.push(` ${sub.name})
|
|
1231
|
+
COMPREPLY=( $(compgen -W "${args.join(" ")}" -- "$cur") )
|
|
1196
1232
|
return ;;`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1197
1235
|
const allFlags = FLAGS.flatMap((f) => f.short ? [f.short, f.long] : [f.long]);
|
|
1198
1236
|
const subcmds = SUBCOMMANDS.map((s) => s.name);
|
|
1199
1237
|
return `# numux bash completions
|
|
@@ -1253,6 +1291,31 @@ function zshCompletions() {
|
|
|
1253
1291
|
}
|
|
1254
1292
|
const argsBlock = argLines.map((l) => `${l} \\`).join(`
|
|
1255
1293
|
`);
|
|
1294
|
+
const subcmdCases = [];
|
|
1295
|
+
for (const sub of SUBCOMMANDS) {
|
|
1296
|
+
const args = resolveArgs(sub);
|
|
1297
|
+
if (!args)
|
|
1298
|
+
continue;
|
|
1299
|
+
if (args === "dynamic") {
|
|
1300
|
+
const script = sub.completionScript;
|
|
1301
|
+
if (!script)
|
|
1302
|
+
continue;
|
|
1303
|
+
subcmdCases.push(` ${sub.name})
|
|
1304
|
+
local logdir names
|
|
1305
|
+
logdir="\\$(${script})"
|
|
1306
|
+
if [[ -n "\\$logdir" ]] && [[ -d "\\$logdir" ]]; then
|
|
1307
|
+
names=( \\$(ls "\\$logdir"/*.log 2>/dev/null | xargs -I{} basename {} .log) )
|
|
1308
|
+
_describe 'process' names
|
|
1309
|
+
fi
|
|
1310
|
+
;;`);
|
|
1311
|
+
} else {
|
|
1312
|
+
subcmdCases.push(` ${sub.name})
|
|
1313
|
+
local -a args
|
|
1314
|
+
args=(${args.map((a) => `'${a}'`).join(" ")})
|
|
1315
|
+
_describe '${sub.name}' args
|
|
1316
|
+
;;`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1256
1319
|
return `#compdef numux
|
|
1257
1320
|
# numux zsh completions
|
|
1258
1321
|
# Add to ~/.zshrc: eval "$(numux completions zsh)"
|
|
@@ -1275,14 +1338,8 @@ ${argsBlock}
|
|
|
1275
1338
|
esac
|
|
1276
1339
|
|
|
1277
1340
|
case "\${words[2]}" in
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
topics=(${HELP_TOPIC_NAMES.map((t) => `'${t}'`).join(" ")})
|
|
1281
|
-
_describe 'topic' topics
|
|
1282
|
-
;;
|
|
1283
|
-
completions)
|
|
1284
|
-
_values 'shell' bash zsh fish
|
|
1285
|
-
;;
|
|
1341
|
+
${subcmdCases.join(`
|
|
1342
|
+
`)}
|
|
1286
1343
|
esac
|
|
1287
1344
|
}
|
|
1288
1345
|
_numux`;
|
|
@@ -1299,7 +1356,22 @@ function fishCompletions() {
|
|
|
1299
1356
|
for (const s of SUBCOMMANDS) {
|
|
1300
1357
|
lines.push(`complete -c numux -n __fish_use_subcommand -a ${s.name} -d '${sq(s.description)}'`);
|
|
1301
1358
|
}
|
|
1302
|
-
|
|
1359
|
+
for (const sub of SUBCOMMANDS) {
|
|
1360
|
+
const args = resolveArgs(sub);
|
|
1361
|
+
if (!args)
|
|
1362
|
+
continue;
|
|
1363
|
+
lines.push("");
|
|
1364
|
+
lines.push(`# ${sub.name} subcommand`);
|
|
1365
|
+
if (args === "dynamic") {
|
|
1366
|
+
const script = sub.completionScript;
|
|
1367
|
+
if (!script)
|
|
1368
|
+
continue;
|
|
1369
|
+
lines.push(`complete -c numux -n '__fish_seen_subcommand_from ${sub.name}' -a '(set -l d (${script}); and ls $d/*.log 2>/dev/null | xargs -I{} basename {} .log)'`);
|
|
1370
|
+
} else {
|
|
1371
|
+
lines.push(`complete -c numux -n '__fish_seen_subcommand_from ${sub.name}' -a '${args.join(" ")}'`);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
lines.push("", "# Options");
|
|
1303
1375
|
for (const f of FLAGS) {
|
|
1304
1376
|
const parts = ["complete -c numux"];
|
|
1305
1377
|
if (f.short)
|
|
@@ -4258,7 +4330,7 @@ class App {
|
|
|
4258
4330
|
}
|
|
4259
4331
|
if (name === SHORTCUTS.clear.key) {
|
|
4260
4332
|
this.panes.get(this.activePane)?.clear();
|
|
4261
|
-
this.logWriter.
|
|
4333
|
+
this.logWriter.markCopyStart(this.activePane);
|
|
4262
4334
|
return;
|
|
4263
4335
|
}
|
|
4264
4336
|
if (name === SHORTCUTS.timestamps.key) {
|
|
@@ -4389,10 +4461,7 @@ class App {
|
|
|
4389
4461
|
copyAllText() {
|
|
4390
4462
|
if (!this.activePane)
|
|
4391
4463
|
return;
|
|
4392
|
-
const
|
|
4393
|
-
if (!pane)
|
|
4394
|
-
return;
|
|
4395
|
-
const text = pane.getText();
|
|
4464
|
+
const text = this.logWriter.readLog(this.activePane);
|
|
4396
4465
|
if (!text) {
|
|
4397
4466
|
this.statusBar.showTemporaryMessage("No output to copy");
|
|
4398
4467
|
return;
|
|
@@ -4647,13 +4716,14 @@ ${DIM}Done in ${elapsed}${RESET}
|
|
|
4647
4716
|
}
|
|
4648
4717
|
|
|
4649
4718
|
// src/utils/log-writer.ts
|
|
4650
|
-
import { closeSync, mkdirSync as mkdirSync2, openSync, rmSync, symlinkSync, unlinkSync, writeSync } from "fs";
|
|
4719
|
+
import { closeSync, mkdirSync as mkdirSync2, openSync, readSync, rmSync, symlinkSync, unlinkSync, writeSync } from "fs";
|
|
4651
4720
|
import { tmpdir } from "os";
|
|
4652
4721
|
import { basename as basename2, join } from "path";
|
|
4653
4722
|
class LogWriter {
|
|
4654
4723
|
dir;
|
|
4655
4724
|
isTemp;
|
|
4656
4725
|
files = new Map;
|
|
4726
|
+
copyOffsets = new Map;
|
|
4657
4727
|
decoder = new TextDecoder;
|
|
4658
4728
|
encoder = new TextEncoder;
|
|
4659
4729
|
constructor(dir, isTemp = false) {
|
|
@@ -4709,6 +4779,35 @@ class LogWriter {
|
|
|
4709
4779
|
`);
|
|
4710
4780
|
}
|
|
4711
4781
|
};
|
|
4782
|
+
markCopyStart(name) {
|
|
4783
|
+
const path = this.getLogPath(name);
|
|
4784
|
+
if (!path)
|
|
4785
|
+
return;
|
|
4786
|
+
try {
|
|
4787
|
+
this.copyOffsets.set(name, Bun.file(path).size);
|
|
4788
|
+
} catch {}
|
|
4789
|
+
}
|
|
4790
|
+
readLog(name) {
|
|
4791
|
+
const path = this.getLogPath(name);
|
|
4792
|
+
if (!path)
|
|
4793
|
+
return;
|
|
4794
|
+
try {
|
|
4795
|
+
const size = Bun.file(path).size;
|
|
4796
|
+
const offset = this.copyOffsets.get(name) ?? 0;
|
|
4797
|
+
if (size <= offset)
|
|
4798
|
+
return;
|
|
4799
|
+
const fd = openSync(path, "r");
|
|
4800
|
+
try {
|
|
4801
|
+
const buf = Buffer.alloc(size - offset);
|
|
4802
|
+
readSync(fd, buf, 0, buf.length, offset);
|
|
4803
|
+
return buf.toString("utf-8");
|
|
4804
|
+
} finally {
|
|
4805
|
+
closeSync(fd);
|
|
4806
|
+
}
|
|
4807
|
+
} catch {
|
|
4808
|
+
return;
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4712
4811
|
getLogPath(name) {
|
|
4713
4812
|
if (this.files.has(name)) {
|
|
4714
4813
|
return join(this.dir, `${name}.log`);
|
|
@@ -4812,17 +4911,6 @@ class LogWriter {
|
|
|
4812
4911
|
return [];
|
|
4813
4912
|
}
|
|
4814
4913
|
}
|
|
4815
|
-
truncate(name) {
|
|
4816
|
-
const fd = this.files.get(name);
|
|
4817
|
-
if (fd === undefined)
|
|
4818
|
-
return;
|
|
4819
|
-
try {
|
|
4820
|
-
closeSync(fd);
|
|
4821
|
-
const path = join(this.dir, `${name}.log`);
|
|
4822
|
-
const newFd = openSync(path, "w");
|
|
4823
|
-
this.files.set(name, newFd);
|
|
4824
|
-
} catch {}
|
|
4825
|
-
}
|
|
4826
4914
|
close() {
|
|
4827
4915
|
for (const fd of this.files.values()) {
|
|
4828
4916
|
closeSync(fd);
|