numux 1.25.0 → 2.0.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
@@ -1,6 +1,8 @@
1
1
  # numux
2
2
 
3
- Terminal multiplexer with dependency orchestration. Run multiple processes in a tabbed TUI with a dependency graph controlling startup order.
3
+ Terminal multiplexer with dependency orchestration. Run multiple processes in a tabbed TUI with a dependency graph controlling startup order, readiness detection, and output capture between processes.
4
+
5
+ Works with zero configuration — pass commands as arguments, run a script across monorepo workspaces with `-w`, or match multiple scripts with glob patterns like `'dev:*'`. For advanced setups, define a typed config with conditional processes, file watching with auto-restart, error detection, log persistence, and output capture — e.g. extract a port from one process's stdout and pass it to another process's command or env.
4
6
 
5
7
  Inspired by `sst dev` and `concurrently`
6
8
 
@@ -38,7 +40,6 @@ export default defineConfig({
38
40
  migrate: {
39
41
  command: 'bun run migrate',
40
42
  dependsOn: ['db'],
41
- persistent: false,
42
43
  },
43
44
  api: {
44
45
  command: 'bun run dev:api',
@@ -51,7 +52,6 @@ export default defineConfig({
51
52
  confirm: {
52
53
  command: 'sh -c "printf \'Deploy to staging? [y/n] \' && read answer && echo $answer"',
53
54
  interactive: true,
54
- persistent: false,
55
55
  },
56
56
  },
57
57
  })
@@ -164,8 +164,9 @@ Template properties (color, env, dependsOn, etc.) are inherited by all matched p
164
164
  | `-p, --prefix` | Prefixed output mode (no TUI, for CI/scripts) |
165
165
  | `--only <a,b,...>` | Only run these processes (+ their dependencies) |
166
166
  | `--exclude <a,b,...>` | Exclude these processes |
167
- | `--kill-others` | Kill all processes when any exits |
168
- | `--no-restart` | Disable auto-restart for crashed processes |
167
+ | `--kill-others` | Kill all processes when any exits (regardless of exit code) |
168
+ | `--kill-others-on-fail` | Kill all processes when any exits with a non-zero exit code |
169
+ | `--max-restarts <n>` | Max auto-restarts for crashed processes |
169
170
  | `-s, --sort <mode>` | Tab display order: `config` (default), `alphabetical`, `topological` |
170
171
  | `--no-watch` | Disable file watching even if config has `watch` patterns |
171
172
  | `-t, --timestamps` | Add `[HH:MM:SS]` timestamps to prefixed output |
@@ -196,8 +197,7 @@ Top-level options apply to all processes (process-level settings override):
196
197
  | `env` | `Record<string, string>` | Environment variables merged into all processes (process `env` overrides per key) |
197
198
  | `envFile` | `string \| string[] \| false` | `.env` file(s) for all processes (process `envFile` replaces if set; `false` disables) |
198
199
  | `showCommand` | `boolean` | Print the command being run as the first line of output (default: `true`) |
199
- | `persistent` | `boolean` | Set to `false` to make all processes one-shot by default (default: `true`) |
200
- | `maxRestarts` | `number` | Restart limit for all processes (default: `Infinity`) |
200
+ | `maxRestarts` | `number` | Restart limit for all processes (default: `0`) |
201
201
  | `readyTimeout` | `number` | Ready timeout in ms for all processes |
202
202
  | `stopSignal` | `'SIGTERM' \| 'SIGINT' \| 'SIGHUP'` | Stop signal for all processes (default: `'SIGTERM'`) |
203
203
  | `errorMatcher` | `boolean \| string` | Error detection for all processes (`true` = ANSI red, string = regex) |
@@ -229,8 +229,7 @@ Each process accepts:
229
229
  | `dependsOn` | `string[]` | — | Processes that must be ready first |
230
230
  | `readyPattern` | `string \| RegExp` | — | Regex matched against stdout to signal readiness. Use `RegExp` to capture groups (see below) |
231
231
  | `readyTimeout` | `number` | — | Milliseconds to wait for `readyPattern` before failing |
232
- | `persistent` | `boolean` | `true` | `false` for one-shot commands (exit 0 = ready) |
233
- | `maxRestarts` | `number` | `Infinity` | Max auto-restart attempts before giving up |
232
+ | `maxRestarts` | `number` | `0` | Max auto-restart attempts on non-zero exit (0 = no restarts) |
234
233
  | `delay` | `number` | — | Milliseconds to wait before starting the process |
235
234
  | `condition` | `string` | — | Env var name; process skipped if falsy. Prefix with `!` to negate |
236
235
  | `platform` | `string \| string[]` | — | OS(es) this process runs on (e.g. `'darwin'`, `'linux'`). Non-matching processes are removed; dependents still start |
@@ -298,7 +297,6 @@ export default defineConfig({
298
297
  processes: {
299
298
  seed: {
300
299
  command: 'bun run seed',
301
- persistent: false,
302
300
  condition: 'SEED_DB', // only runs when SEED_DB is set and truthy
303
301
  },
304
302
  storybook: {
@@ -316,11 +314,10 @@ Falsy values: unset, empty string, `"0"`, `"false"`, `"no"`, `"off"` (case-insen
316
314
  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.
317
315
 
318
316
  A process becomes **ready** when:
319
- - **persistent + readyPattern** — the pattern matches in stdout
320
- - **persistent + no readyPattern**immediately after spawn
321
- - **non-persistent** — exits with code 0
317
+ - **Has `readyPattern`** — the pattern matches in stdout (long-running server)
318
+ - **No `readyPattern`**exits with code 0 (one-shot task)
322
319
 
323
- Persistent processes that crash are auto-restarted with exponential backoff (1s–30s). Backoff resets after 10s of uptime.
320
+ Processes that crash (non-zero exit) can be auto-restarted by setting `maxRestarts` (default: `0`). Restarts use exponential backoff (1s–30s), which resets after 10s of uptime.
324
321
 
325
322
  ### Dependency output capture
326
323
 
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.25.0",
39
+ version: "2.0.0",
40
40
  description: "Terminal multiplexer with dependency orchestration",
41
41
  type: "module",
42
42
  license: "MIT",
@@ -192,13 +192,27 @@ var FLAGS = [
192
192
  type: "boolean",
193
193
  long: "--kill-others",
194
194
  key: "killOthers",
195
- description: "Kill all processes when any exits"
195
+ description: "Kill all processes when any exits (regardless of exit code)"
196
196
  },
197
197
  {
198
198
  type: "boolean",
199
- long: "--no-restart",
200
- key: "noRestart",
201
- description: "Disable auto-restart for crashed processes"
199
+ long: "--kill-others-on-fail",
200
+ key: "killOthersOnFail",
201
+ description: "Kill all processes when any exits with non-zero code"
202
+ },
203
+ {
204
+ type: "value",
205
+ long: "--max-restarts",
206
+ key: "maxRestarts",
207
+ description: "Max auto-restarts for crashed processes",
208
+ valueName: "<n>",
209
+ completionHint: "none",
210
+ parse(raw, flag) {
211
+ const n = Number(raw);
212
+ if (!Number.isInteger(n) || n < 0)
213
+ throw new Error(`${flag} must be a non-negative integer, got "${raw}"`);
214
+ return n;
215
+ }
202
216
  },
203
217
  {
204
218
  type: "boolean",
@@ -342,8 +356,8 @@ function parseArgs(argv) {
342
356
  exec: false,
343
357
  prefix: false,
344
358
  killOthers: false,
359
+ killOthersOnFail: false,
345
360
  timestamps: false,
346
- noRestart: false,
347
361
  noWatch: false,
348
362
  autoColors: false,
349
363
  configPath: undefined,
@@ -390,12 +404,11 @@ function parseArgs(argv) {
390
404
  }
391
405
  function buildConfigFromArgs(commands, named, options) {
392
406
  const processes = {};
393
- const maxRestarts = options?.noRestart ? 0 : undefined;
394
407
  const colors = options?.colors;
395
408
  let colorIndex = 0;
396
409
  for (const { name, command } of named) {
397
410
  const color = colors?.[colorIndex++ % colors.length];
398
- processes[name] = { command, persistent: true, maxRestarts, ...color ? { color } : {} };
411
+ processes[name] = { command, ...color ? { color } : {} };
399
412
  }
400
413
  for (let i = 0;i < commands.length; i++) {
401
414
  const cmd = commands[i];
@@ -404,7 +417,7 @@ function buildConfigFromArgs(commands, named, options) {
404
417
  name = `${name}-${i}`;
405
418
  }
406
419
  const color = colors?.[colorIndex++ % colors.length];
407
- processes[name] = { command: cmd, persistent: true, maxRestarts, ...color ? { color } : {} };
420
+ processes[name] = { command: cmd, ...color ? { color } : {} };
408
421
  }
409
422
  return { processes };
410
423
  }
@@ -1028,7 +1041,7 @@ function buildProcessHexColorMap(names, config) {
1028
1041
  }
1029
1042
 
1030
1043
  // src/config/validator.ts
1031
- function validateConfig(raw, warnings) {
1044
+ function validateConfig(raw, _warnings) {
1032
1045
  if (!raw || typeof raw !== "object") {
1033
1046
  throw new Error("Config must be an object");
1034
1047
  }
@@ -1046,7 +1059,6 @@ function validateConfig(raw, warnings) {
1046
1059
  const globalEnvFile = validateEnvFile(config.envFile);
1047
1060
  const globalMaxRestarts = typeof config.maxRestarts === "number" && config.maxRestarts >= 0 ? config.maxRestarts : undefined;
1048
1061
  const globalReadyTimeout = typeof config.readyTimeout === "number" && config.readyTimeout > 0 ? config.readyTimeout : undefined;
1049
- const globalPersistent = typeof config.persistent === "boolean" ? config.persistent : undefined;
1050
1062
  const globalStopSignal = validateStopSignal(config.stopSignal);
1051
1063
  const globalErrorMatcher = validateErrorMatcher("(global)", config.errorMatcher);
1052
1064
  const globalWatch = validateStringOrStringArray(config.watch);
@@ -1063,6 +1075,7 @@ function validateConfig(raw, warnings) {
1063
1075
  const prefix = config.prefix === true ? true : undefined;
1064
1076
  const timestamps = config.timestamps === true ? true : undefined;
1065
1077
  const killOthers = config.killOthers === true ? true : undefined;
1078
+ const killOthersOnFail = config.killOthersOnFail === true ? true : undefined;
1066
1079
  const noWatch = config.noWatch === true ? true : undefined;
1067
1080
  const logDir = typeof config.logDir === "string" && config.logDir.trim() ? config.logDir.trim() : undefined;
1068
1081
  const validated = {};
@@ -1107,7 +1120,6 @@ function validateConfig(raw, warnings) {
1107
1120
  }
1108
1121
  }
1109
1122
  }
1110
- const persistent = typeof p.persistent === "boolean" ? p.persistent : globalPersistent ?? true;
1111
1123
  const readyPattern = p.readyPattern instanceof RegExp ? p.readyPattern : typeof p.readyPattern === "string" ? p.readyPattern : undefined;
1112
1124
  if (typeof readyPattern === "string") {
1113
1125
  try {
@@ -1118,12 +1130,6 @@ function validateConfig(raw, warnings) {
1118
1130
  });
1119
1131
  }
1120
1132
  }
1121
- if (readyPattern && !persistent) {
1122
- warnings?.push({
1123
- process: name,
1124
- message: "readyPattern is ignored on non-persistent processes (readiness is determined by exit code)"
1125
- });
1126
- }
1127
1133
  if (p.env && typeof p.env === "object") {
1128
1134
  for (const [k, v] of Object.entries(p.env)) {
1129
1135
  if (typeof v !== "string") {
@@ -1148,8 +1154,7 @@ function validateConfig(raw, warnings) {
1148
1154
  envFile: processEnvFile ?? globalEnvFile,
1149
1155
  dependsOn: Array.isArray(p.dependsOn) ? p.dependsOn : undefined,
1150
1156
  readyPattern,
1151
- persistent,
1152
- maxRestarts: processMaxRestarts ?? globalMaxRestarts,
1157
+ maxRestarts: processMaxRestarts ?? globalMaxRestarts ?? 0,
1153
1158
  readyTimeout: processReadyTimeout ?? globalReadyTimeout,
1154
1159
  delay: typeof p.delay === "number" && p.delay > 0 ? p.delay : undefined,
1155
1160
  condition: typeof p.condition === "string" && p.condition.trim() ? p.condition.trim() : undefined,
@@ -1167,6 +1172,7 @@ function validateConfig(raw, warnings) {
1167
1172
  ...prefix ? { prefix } : {},
1168
1173
  ...timestamps ? { timestamps } : {},
1169
1174
  ...killOthers ? { killOthers } : {},
1175
+ ...killOthersOnFail ? { killOthersOnFail } : {},
1170
1176
  ...noWatch ? { noWatch } : {},
1171
1177
  ...logDir ? { logDir } : {},
1172
1178
  processes: validated
@@ -1284,8 +1290,7 @@ function resolveWorkspaceProcesses(script, cwd) {
1284
1290
  usedNames.add(name);
1285
1291
  processes[name] = {
1286
1292
  command: `${pm} run ${script}`,
1287
- cwd: dir,
1288
- persistent: true
1293
+ cwd: dir
1289
1294
  };
1290
1295
  }
1291
1296
  if (Object.keys(processes).length === 0) {
@@ -1458,12 +1463,11 @@ function extractCaptures(match) {
1458
1463
  function createReadinessChecker(config) {
1459
1464
  const shouldCapture = config.readyPattern instanceof RegExp;
1460
1465
  const pattern = config.readyPattern ? config.readyPattern instanceof RegExp ? config.readyPattern : new RegExp(config.readyPattern) : null;
1461
- const persistent = config.persistent !== false;
1462
1466
  let outputBuffer = "";
1463
1467
  let _captures = null;
1464
1468
  return {
1465
1469
  feedOutput(data) {
1466
- if (!(persistent && pattern))
1470
+ if (!pattern)
1467
1471
  return false;
1468
1472
  outputBuffer += data;
1469
1473
  if (outputBuffer.length > BUFFER_CAP2) {
@@ -1482,11 +1486,8 @@ function createReadinessChecker(config) {
1482
1486
  get captures() {
1483
1487
  return _captures;
1484
1488
  },
1485
- get isImmediatelyReady() {
1486
- return persistent && !pattern;
1487
- },
1488
1489
  get dependsOnExit() {
1489
- return !persistent;
1490
+ return !pattern;
1490
1491
  }
1491
1492
  };
1492
1493
  }
@@ -1576,10 +1577,7 @@ class ProcessRunner {
1576
1577
  `;
1577
1578
  this.handler.onOutput(encoder.encode(msg));
1578
1579
  }
1579
- this.handler.onStatus(this.config.persistent !== false ? "running" : "starting");
1580
- if (this.readiness.isImmediatelyReady) {
1581
- this.markReady();
1582
- }
1580
+ this.handler.onStatus(this.config.readyPattern ? "running" : "starting");
1583
1581
  this.startReadyTimeout(gen);
1584
1582
  this.proc.exited.then((code) => {
1585
1583
  if (this.generation !== gen)
@@ -1627,7 +1625,7 @@ class ProcessRunner {
1627
1625
  }
1628
1626
  startReadyTimeout(gen) {
1629
1627
  const timeout = this.config.readyTimeout;
1630
- if (!(timeout && this.config.readyPattern) || this.config.persistent === false)
1628
+ if (!(timeout && this.config.readyPattern))
1631
1629
  return;
1632
1630
  this.readyTimer = setTimeout(() => {
1633
1631
  this.readyTimer = null;
@@ -1885,8 +1883,6 @@ class ProcessManager {
1885
1883
  if (this.stopping)
1886
1884
  return;
1887
1885
  const proc = this.config.processes[name];
1888
- if (proc.persistent === false)
1889
- return;
1890
1886
  if (exitCode === 0)
1891
1887
  return;
1892
1888
  if (exitCode === null)
@@ -1897,8 +1893,8 @@ class ProcessManager {
1897
1893
  this.restartAttempts.set(name, 0);
1898
1894
  }
1899
1895
  const attempt = this.restartAttempts.get(name) ?? 0;
1900
- const maxRestarts = proc.maxRestarts;
1901
- if (maxRestarts !== undefined && attempt >= maxRestarts) {
1896
+ const maxRestarts = proc.maxRestarts ?? 0;
1897
+ if (attempt >= maxRestarts) {
1902
1898
  log(`[${name}] Reached maxRestarts limit (${maxRestarts}), not restarting`);
1903
1899
  if (maxRestarts > 0) {
1904
1900
  const encoder2 = new TextEncoder;
@@ -1913,7 +1909,7 @@ class ProcessManager {
1913
1909
  this.restartAttempts.set(name, attempt + 1);
1914
1910
  const encoder = new TextEncoder;
1915
1911
  const msg = `\r
1916
- \x1B[33m[numux] restarting in ${(delay / 1000).toFixed(0)}s (attempt ${attempt + 1}${maxRestarts !== undefined ? `/${maxRestarts}` : ""})...\x1B[0m\r
1912
+ \x1B[33m[numux] restarting in ${(delay / 1000).toFixed(0)}s (attempt ${attempt + 1}${Number.isFinite(maxRestarts) ? `/${maxRestarts}` : ""})...\x1B[0m\r
1917
1913
  `;
1918
1914
  this.emit({ type: "output", name, data: encoder.encode(msg) });
1919
1915
  const timer = setTimeout(() => {
@@ -1944,7 +1940,7 @@ class ProcessManager {
1944
1940
  const state = this.states.get(name);
1945
1941
  if (!state)
1946
1942
  return;
1947
- if (state.status === "pending" || state.status === "stopped" || state.status === "finished" || state.status === "stopping" || state.status === "skipped")
1943
+ if (state.status === "pending" || state.status === "stopped" || state.status === "stopping" || state.status === "skipped")
1948
1944
  return;
1949
1945
  log(`[${name}] File changed: ${changedFile}, restarting`);
1950
1946
  const msg = `\r
@@ -2187,10 +2183,14 @@ function openLink(link) {
2187
2183
  }
2188
2184
 
2189
2185
  // src/ui/pane.ts
2186
+ var MAX_SCROLLBACK_LINES = 50000;
2187
+ var MAX_BUFFER_BYTES = 10 * 1024 * 1024;
2188
+
2190
2189
  class Pane {
2191
2190
  scrollBox;
2192
2191
  terminal;
2193
2192
  decoder = new TextDecoder;
2193
+ bytesFed = 0;
2194
2194
  _onScroll = null;
2195
2195
  _onCopy = null;
2196
2196
  _onLinkClick = null;
@@ -2238,6 +2238,11 @@ class Pane {
2238
2238
  this.scrollBox.add(this.terminal);
2239
2239
  }
2240
2240
  feed(data) {
2241
+ this.bytesFed += data.length;
2242
+ if (this.terminal.lineCount > MAX_SCROLLBACK_LINES || this.bytesFed > MAX_BUFFER_BYTES) {
2243
+ this.terminal.reset();
2244
+ this.bytesFed = 0;
2245
+ }
2241
2246
  const text = this.decoder.decode(data, { stream: true });
2242
2247
  this.terminal.feed(text);
2243
2248
  }
@@ -2315,6 +2320,7 @@ class Pane {
2315
2320
  }
2316
2321
  clear() {
2317
2322
  this.terminal.reset();
2323
+ this.bytesFed = 0;
2318
2324
  }
2319
2325
  destroy() {
2320
2326
  this.terminal.destroy();
@@ -3185,6 +3191,7 @@ class PrefixDisplay {
3185
3191
  buffers = new Map;
3186
3192
  logWriter;
3187
3193
  killOthers;
3194
+ killOthersOnFail;
3188
3195
  timestamps;
3189
3196
  stopping = false;
3190
3197
  startTime = 0;
@@ -3192,6 +3199,7 @@ class PrefixDisplay {
3192
3199
  this.manager = manager;
3193
3200
  this.logWriter = options.logWriter;
3194
3201
  this.killOthers = options.killOthers ?? false;
3202
+ this.killOthersOnFail = options.killOthersOnFail ?? false;
3195
3203
  this.timestamps = options.timestamps ?? false;
3196
3204
  this.noColor = "NO_COLOR" in process.env;
3197
3205
  const names = manager.getProcessNames();
@@ -3233,7 +3241,8 @@ class PrefixDisplay {
3233
3241
  this.handleStatus(event.name, event.status);
3234
3242
  } else if (event.type === "exit") {
3235
3243
  this.flushBuffer(event.name);
3236
- if (this.killOthers) {
3244
+ const exitCode = this.manager.getState(event.name)?.exitCode ?? null;
3245
+ if (this.killOthers || this.killOthersOnFail && exitCode !== 0) {
3237
3246
  this.killAllAndExit(event.name);
3238
3247
  } else {
3239
3248
  this.checkAllDone();
@@ -3662,7 +3671,7 @@ async function main() {
3662
3671
  flags.push(`depends on: ${proc.dependsOn.join(", ")}`);
3663
3672
  if (proc.readyPattern)
3664
3673
  flags.push(`ready: /${proc.readyPattern}/`);
3665
- if (proc.persistent === false)
3674
+ if (!proc.readyPattern)
3666
3675
  flags.push("one-shot");
3667
3676
  if (proc.delay)
3668
3677
  flags.push(`delay: ${proc.delay}ms`);
@@ -3739,7 +3748,6 @@ async function main() {
3739
3748
  config = validateConfig(expanded, warnings);
3740
3749
  } else {
3741
3750
  config = buildConfigFromArgs(parsed.commands, parsed.named, {
3742
- noRestart: parsed.noRestart,
3743
3751
  colors: parsed.colors
3744
3752
  });
3745
3753
  }
@@ -3753,8 +3761,6 @@ async function main() {
3753
3761
  suffix++;
3754
3762
  finalName = `${finalName}-${suffix}`;
3755
3763
  }
3756
- if (parsed.noRestart)
3757
- proc.maxRestarts = 0;
3758
3764
  config.processes[finalName] = proc;
3759
3765
  }
3760
3766
  }
@@ -3762,11 +3768,6 @@ async function main() {
3762
3768
  const raw = expandScriptPatterns(await loadConfig(parsed.configPath));
3763
3769
  config = validateConfig(raw, warnings);
3764
3770
  config = filterByPlatform(config);
3765
- if (parsed.noRestart) {
3766
- for (const proc of Object.values(config.processes)) {
3767
- proc.maxRestarts = 0;
3768
- }
3769
- }
3770
3771
  }
3771
3772
  if (parsed.sort) {
3772
3773
  config.sort = parsed.sort;
@@ -3776,6 +3777,11 @@ async function main() {
3776
3777
  proc.envFile = parsed.envFile;
3777
3778
  }
3778
3779
  }
3780
+ if (parsed.maxRestarts !== undefined) {
3781
+ for (const proc of Object.values(config.processes)) {
3782
+ proc.maxRestarts = parsed.maxRestarts;
3783
+ }
3784
+ }
3779
3785
  if (parsed.noWatch || config.noWatch) {
3780
3786
  for (const proc of Object.values(config.processes)) {
3781
3787
  delete proc.watch;
@@ -3797,14 +3803,10 @@ async function main() {
3797
3803
  printWarnings(warnings);
3798
3804
  const usePrefix = parsed.prefix || config.prefix;
3799
3805
  if (usePrefix) {
3800
- if (!parsed.noRestart) {
3801
- for (const proc of Object.values(config.processes)) {
3802
- proc.maxRestarts ??= 0;
3803
- }
3804
- }
3805
3806
  const display = new PrefixDisplay(manager, config, {
3806
3807
  logWriter,
3807
3808
  killOthers: parsed.killOthers || config.killOthers,
3809
+ killOthersOnFail: parsed.killOthersOnFail || config.killOthersOnFail,
3808
3810
  timestamps: parsed.timestamps || config.timestamps
3809
3811
  });
3810
3812
  await display.start();
package/dist/types.d.ts CHANGED
@@ -17,13 +17,8 @@ export interface NumuxProcessConfig<K extends string = string> {
17
17
  /** Regex matched against stdout to signal readiness. Use `RegExp` to capture groups for `$dep.group` expansion */
18
18
  readyPattern?: string | RegExp;
19
19
  /**
20
- * Set to `false` for one-shot processes
21
- * @default true
22
- */
23
- persistent?: boolean;
24
- /**
25
- * Limit auto-restart attempts
26
- * @default Infinity
20
+ * Limit auto-restart attempts (only restarts on non-zero exit)
21
+ * @default 0
27
22
  */
28
23
  maxRestarts?: number;
29
24
  /** Milliseconds to wait for readyPattern before failing */
@@ -74,18 +69,12 @@ export interface NumuxConfig<K extends string = string> {
74
69
  */
75
70
  showCommand?: boolean;
76
71
  /**
77
- * Global restart limit, inherited by all processes
78
- * @default Infinity
72
+ * Global restart limit, inherited by all processes (only restarts on non-zero exit)
73
+ * @default 0
79
74
  */
80
75
  maxRestarts?: number;
81
76
  /** Global ready timeout (ms), inherited by all processes */
82
77
  readyTimeout?: number;
83
- /**
84
- * Set to `false` to make all processes non-persistent (one-shot) by default.
85
- * Individual processes can still override with their own `persistent` value.
86
- * @default true
87
- */
88
- persistent?: boolean;
89
78
  /**
90
79
  * Global stop signal, inherited by all processes
91
80
  * @default 'SIGTERM'
@@ -109,10 +98,15 @@ export interface NumuxConfig<K extends string = string> {
109
98
  /** Add timestamps to prefixed output lines (only applies when `prefix` is true) */
110
99
  timestamps?: boolean;
111
100
  /**
112
- * Kill all processes when any one exits
101
+ * Kill all processes when any one exits (regardless of exit code)
113
102
  * @default false
114
103
  */
115
104
  killOthers?: boolean;
105
+ /**
106
+ * Kill all processes when any one exits with a non-zero exit code
107
+ * @default false
108
+ */
109
+ killOthersOnFail?: boolean;
116
110
  /**
117
111
  * Disable file watching even if processes have watch patterns
118
112
  * @default false
@@ -133,6 +127,7 @@ export interface ResolvedNumuxConfig {
133
127
  prefix?: boolean;
134
128
  timestamps?: boolean;
135
129
  killOthers?: boolean;
130
+ killOthersOnFail?: boolean;
136
131
  noWatch?: boolean;
137
132
  logDir?: string;
138
133
  processes: Record<string, ResolvedProcessConfig>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "numux",
3
- "version": "1.25.0",
3
+ "version": "2.0.0",
4
4
  "description": "Terminal multiplexer with dependency orchestration",
5
5
  "type": "module",
6
6
  "license": "MIT",