numux 2.8.0 → 2.10.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 CHANGED
@@ -73,6 +73,7 @@ numux
73
73
  numux init # Create a starter numux.config.ts
74
74
  numux validate # Validate config and show process dependency graph
75
75
  numux exec <name> [--] <command> # Run a command in a process's environment
76
+ numux logs [name] # Open log directory or view a process log
76
77
  numux completions <shell> # Generate shell completions (bash, zsh, fish)
77
78
  ```
78
79
 
@@ -85,6 +86,13 @@ numux exec api -- npx prisma migrate
85
86
  numux exec web npm run build
86
87
  ```
87
88
 
89
+ `logs` prints the log directory path, or a specific process's log contents:
90
+
91
+ ```sh
92
+ numux logs # Print log directory path
93
+ numux logs api # Print the api process log
94
+ ```
95
+
88
96
  Set up completions for your shell:
89
97
 
90
98
  ```sh
@@ -248,6 +256,7 @@ Each process accepts:
248
256
  | `readyTimeout` | `number` | — | Milliseconds to wait for `readyPattern` before failing |
249
257
  | `maxRestarts` | `number` | `0` | Max auto-restart attempts on non-zero exit (0 = no restarts) |
250
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 |
251
260
  | `condition` | `string` | — | Env var name; process skipped if falsy. Prefix with `!` to negate |
252
261
  | `platform` | `string \| string[]` | — | OS(es) this process runs on (e.g. `'darwin'`, `'linux'`). Non-matching processes are removed; dependents still start |
253
262
  | `stopSignal` | `string` | `SIGTERM` | Signal for graceful stop (`SIGTERM`, `SIGINT`, or `SIGHUP`) |
@@ -352,6 +361,24 @@ export default defineConfig({
352
361
 
353
362
  Falsy values: unset, empty string, `"0"`, `"false"`, `"no"`, `"off"` (case-insensitive). If a conditional process is skipped, its dependents are also skipped.
354
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
+
355
382
  ### Dependency orchestration
356
383
 
357
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/numux.js CHANGED
@@ -31,12 +31,13 @@ var __toESM = (mod, isNodeMode, target) => {
31
31
  return to;
32
32
  };
33
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __require = import.meta.require;
34
35
 
35
36
  // package.json
36
37
  var require_package = __commonJS((exports, module) => {
37
38
  module.exports = {
38
39
  name: "numux",
39
- version: "2.8.0",
40
+ version: "2.10.0",
40
41
  description: "Terminal multiplexer with dependency orchestration",
41
42
  type: "module",
42
43
  license: "MIT",
@@ -300,6 +301,20 @@ var SUBCOMMANDS = [
300
301
  return "break";
301
302
  }
302
303
  },
304
+ {
305
+ name: "logs",
306
+ description: "Open the log directory or a specific process log",
307
+ usage: "logs [name]",
308
+ parse: (args, i, result) => {
309
+ result.logs = true;
310
+ const next = args[i + 1];
311
+ if (next !== undefined && !next.startsWith("-")) {
312
+ result.logsProcess = next;
313
+ i++;
314
+ }
315
+ return i;
316
+ }
317
+ },
303
318
  {
304
319
  name: "completions",
305
320
  description: "Generate shell completions (bash, zsh, fish)",
@@ -364,6 +379,7 @@ function parseArgs(argv) {
364
379
  init: false,
365
380
  validate: false,
366
381
  exec: false,
382
+ logs: false,
367
383
  prefix: false,
368
384
  killOthers: false,
369
385
  killOthersOnFail: false,
@@ -1221,6 +1237,7 @@ function validateConfig(raw, _warnings) {
1221
1237
  const processWatch = validateStringOrStringArray(p.watch);
1222
1238
  validated[name] = {
1223
1239
  command: p.command,
1240
+ ...p.optional === true ? { optional: true } : {},
1224
1241
  cwd: processCwd ?? globalCwd,
1225
1242
  env: globalEnv || processEnv ? { ...globalEnv, ...processEnv } : undefined,
1226
1243
  envFile: processEnvFile ?? globalEnvFile,
@@ -1951,6 +1968,12 @@ class ProcessManager {
1951
1968
  const launches = this.tiers.flat().map(async (name) => {
1952
1969
  const proc = this.config.processes[name];
1953
1970
  const resolve8 = readyResolvers.get(name);
1971
+ if (proc.optional) {
1972
+ this.updateStatus(name, "stopped");
1973
+ this.createRunner(name);
1974
+ resolve8();
1975
+ return;
1976
+ }
1954
1977
  const deps = proc.dependsOn ?? [];
1955
1978
  if (deps.length > 0) {
1956
1979
  await Promise.all(deps.map((d) => readyPromises.get(d)));
@@ -3513,8 +3536,8 @@ class PrefixDisplay {
3513
3536
  this.shutdown();
3514
3537
  });
3515
3538
  process.on("unhandledRejection", (reason) => {
3516
- const message = reason instanceof Error ? reason.message : String(reason);
3517
- process.stderr.write(`numux: unhandled rejection: ${message}
3539
+ const stack = reason instanceof Error ? reason.stack : String(reason);
3540
+ process.stderr.write(`numux: unhandled rejection: ${stack}
3518
3541
  `);
3519
3542
  this.shutdown();
3520
3543
  });
@@ -3925,10 +3948,10 @@ function setupShutdownHandlers(app, logWriter) {
3925
3948
  });
3926
3949
  });
3927
3950
  process.on("unhandledRejection", (reason) => {
3928
- const message = reason instanceof Error ? reason.message : String(reason);
3929
- log("Unhandled rejection:", message);
3951
+ const stack = reason instanceof Error ? reason.stack : String(reason);
3952
+ log("Unhandled rejection:", stack);
3930
3953
  app.shutdown().finally(() => {
3931
- process.stderr.write(`numux: unhandled rejection: ${message}
3954
+ process.stderr.write(`numux: unhandled rejection: ${stack}
3932
3955
  `);
3933
3956
  logWriter?.cleanup();
3934
3957
  process.exit(1);
@@ -3985,6 +4008,28 @@ async function main() {
3985
4008
  console.info(generateCompletions(parsed.completions));
3986
4009
  process.exit(0);
3987
4010
  }
4011
+ if (parsed.logs) {
4012
+ const logDir2 = parsed.logDir ?? await resolveLogDir(parsed.configPath);
4013
+ const latestDir = resolve8(logDir2, "latest");
4014
+ const target = existsSync6(latestDir) ? latestDir : logDir2;
4015
+ if (parsed.logsProcess) {
4016
+ const logFile2 = resolve8(target, `${parsed.logsProcess}.log`);
4017
+ if (!existsSync6(logFile2)) {
4018
+ const { readdirSync } = await import("fs");
4019
+ const files = readdirSync(target).filter((f) => f.endsWith(".log")).map((f) => f.replace(/\.log$/, ""));
4020
+ const available = files.length > 0 ? `Available: ${files.join(", ")}` : "No log files found";
4021
+ console.error(`No log file for "${parsed.logsProcess}". ${available}`);
4022
+ process.exit(1);
4023
+ }
4024
+ const child = Bun.spawn(["cat", logFile2], {
4025
+ stdout: "inherit",
4026
+ stderr: "inherit"
4027
+ });
4028
+ process.exit(await child.exited);
4029
+ }
4030
+ console.info(target);
4031
+ process.exit(0);
4032
+ }
3988
4033
  if (parsed.validate) {
3989
4034
  const raw = expandWorkspaces(expandScriptPatterns(await loadConfig(parsed.configPath)));
3990
4035
  const warnings2 = [];
@@ -4161,6 +4206,15 @@ function printWarnings(warnings) {
4161
4206
  console.warn(`Warning: process "${w.process}": ${w.message}`);
4162
4207
  }
4163
4208
  }
4209
+ async function resolveLogDir(configPath) {
4210
+ try {
4211
+ const raw = await loadConfig(configPath);
4212
+ if (typeof raw.logDir === "string" && raw.logDir.trim()) {
4213
+ return resolve8(raw.logDir.trim());
4214
+ }
4215
+ } catch {}
4216
+ return defaultLogDir(process.cwd());
4217
+ }
4164
4218
  main().catch((err) => {
4165
4219
  console.error(err instanceof Error ? err.message : err);
4166
4220
  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.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "Terminal multiplexer with dependency orchestration",
5
5
  "type": "module",
6
6
  "license": "MIT",