numux 0.0.1 → 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 hyldmo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1 +1,293 @@
1
1
  # numux
2
+
3
+ Terminal multiplexer with dependency orchestration. Run multiple processes in a tabbed TUI with a dependency graph controlling startup order.
4
+
5
+ ## Install
6
+
7
+ Requires [Bun](https://bun.sh) >= 1.0.
8
+
9
+ ```sh
10
+ bun install -g numux
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Quick start
16
+
17
+ ```sh
18
+ numux init
19
+ ```
20
+
21
+ This creates a starter `numux.config.ts` with commented-out examples. Edit it, then run `numux`.
22
+
23
+ ### Config file
24
+
25
+ Create `numux.config.ts` (or `.js`, `.yaml`, `.yml`, `.json`, or a `"numux"` key in `package.json`):
26
+
27
+ ```ts
28
+ import { defineConfig } from 'numux'
29
+
30
+ export default defineConfig({
31
+ processes: {
32
+ db: {
33
+ command: 'docker compose up postgres',
34
+ readyPattern: 'ready to accept connections',
35
+ },
36
+ migrate: {
37
+ command: 'bun run migrate',
38
+ dependsOn: ['db'],
39
+ persistent: false,
40
+ },
41
+ api: {
42
+ command: 'bun run dev:api',
43
+ dependsOn: ['migrate'],
44
+ readyPattern: 'listening on port 3000',
45
+ },
46
+ // String shorthand for simple processes
47
+ web: 'bun run dev:web',
48
+ // Interactive process — keyboard input is forwarded
49
+ confirm: {
50
+ command: 'sh -c "printf \'Deploy to staging? [y/n] \' && read answer && echo $answer"',
51
+ interactive: true,
52
+ persistent: false,
53
+ },
54
+ },
55
+ })
56
+ ```
57
+
58
+ The `defineConfig()` helper is optional — it provides type checking for your config.
59
+
60
+ Processes can be a string (shorthand for `{ command: "..." }`) or a full config object.
61
+
62
+ Then run:
63
+
64
+ ```sh
65
+ numux
66
+ ```
67
+
68
+ ### Subcommands
69
+
70
+ ```sh
71
+ numux init # Create a starter numux.config.ts
72
+ numux validate # Validate config and show process dependency graph
73
+ numux exec <name> [--] <command> # Run a command in a process's environment
74
+ numux completions <shell> # Generate shell completions (bash, zsh, fish)
75
+ ```
76
+
77
+ `validate` respects `--only`/`--exclude` filters and shows processes grouped by dependency tiers.
78
+
79
+ `exec` runs a one-off command using a process's configured `cwd`, `env`, and `envFile` — useful for migrations, scripts, or any command that needs the same environment:
80
+
81
+ ```sh
82
+ numux exec api -- npx prisma migrate
83
+ numux exec web npm run build
84
+ ```
85
+
86
+ Set up completions for your shell:
87
+
88
+ ```sh
89
+ # Bash (add to ~/.bashrc)
90
+ eval "$(numux completions bash)"
91
+
92
+ # Zsh (add to ~/.zshrc)
93
+ eval "$(numux completions zsh)"
94
+
95
+ # Fish
96
+ numux completions fish | source
97
+ # Or save permanently:
98
+ numux completions fish > ~/.config/fish/completions/numux.fish
99
+ ```
100
+
101
+ ### Ad-hoc commands
102
+
103
+ ```sh
104
+ # Unnamed (name derived from command)
105
+ numux "bun dev:api" "bun dev:web"
106
+
107
+ # Named
108
+ numux -n api="bun dev:api" -n web="bun dev:web"
109
+ ```
110
+
111
+ ### Options
112
+
113
+ | Flag | Description |
114
+ |------|-------------|
115
+ | `-c, --config <path>` | Explicit config file path |
116
+ | `-n, --name <name=cmd>` | Add a named process (repeatable) |
117
+ | `-p, --prefix` | Prefixed output mode (no TUI, for CI/scripts) |
118
+ | `--only <a,b,...>` | Only run these processes (+ their dependencies) |
119
+ | `--exclude <a,b,...>` | Exclude these processes |
120
+ | `--kill-others` | Kill all processes when any exits |
121
+ | `--no-restart` | Disable auto-restart for crashed processes |
122
+ | `--no-watch` | Disable file watching even if config has `watch` patterns |
123
+ | `-t, --timestamps` | Add `[HH:MM:SS]` timestamps to prefixed output |
124
+ | `--log-dir <path>` | Write per-process output to `<path>/<name>.log` |
125
+ | `--debug` | Log to `.numux/debug.log` |
126
+ | `-h, --help` | Show help |
127
+ | `-v, --version` | Show version |
128
+
129
+ ### Prefix mode
130
+
131
+ Use `--prefix` (`-p`) for CI or headless environments. Output is printed with colored `[name]` prefixes instead of the TUI:
132
+
133
+ ```sh
134
+ numux --prefix
135
+ ```
136
+
137
+ Auto-exits when all processes finish. Exit code 1 if any process failed.
138
+
139
+ ## Config reference
140
+
141
+ ### Global options
142
+
143
+ Top-level options apply to all processes (process-level settings override):
144
+
145
+ | Field | Type | Description |
146
+ |-------|------|-------------|
147
+ | `cwd` | `string` | Working directory for all processes (process `cwd` overrides) |
148
+ | `env` | `Record<string, string>` | Environment variables merged into all processes (process `env` overrides per key) |
149
+ | `envFile` | `string \| string[]` | `.env` file(s) for all processes (process `envFile` replaces if set) |
150
+
151
+ ```ts
152
+ export default defineConfig({
153
+ cwd: './packages/backend',
154
+ env: { NODE_ENV: 'development' },
155
+ envFile: '.env',
156
+ processes: {
157
+ api: { command: 'node server.js' }, // inherits cwd, env, envFile
158
+ web: { command: 'vite', cwd: './packages/web' }, // overrides cwd
159
+ },
160
+ })
161
+ ```
162
+
163
+ ### Process options
164
+
165
+ Each process accepts:
166
+
167
+ | Field | Type | Default | Description |
168
+ |-------|------|---------|-------------|
169
+ | `command` | `string` | *required* | Shell command to run |
170
+ | `cwd` | `string` | `process.cwd()` | Working directory |
171
+ | `env` | `Record<string, string>` | — | Extra environment variables |
172
+ | `envFile` | `string \| string[]` | — | `.env` file path(s) to load (relative to `cwd`) |
173
+ | `dependsOn` | `string[]` | — | Processes that must be ready first |
174
+ | `readyPattern` | `string` | — | Regex matched against stdout to signal readiness |
175
+ | `readyTimeout` | `number` | — | Milliseconds to wait for `readyPattern` before failing |
176
+ | `persistent` | `boolean` | `true` | `false` for one-shot commands (exit 0 = ready) |
177
+ | `maxRestarts` | `number` | `Infinity` | Max auto-restart attempts before giving up |
178
+ | `delay` | `number` | — | Milliseconds to wait before starting the process |
179
+ | `condition` | `string` | — | Env var name; process skipped if falsy. Prefix with `!` to negate |
180
+ | `stopSignal` | `string` | `SIGTERM` | Signal for graceful stop (`SIGTERM`, `SIGINT`, or `SIGHUP`) |
181
+ | `color` | `string` | auto | Hex color for tab icon and status bar (e.g. `"#ff6600"`) |
182
+ | `watch` | `string \| string[]` | — | Glob patterns — restart process when matching files change |
183
+ | `interactive` | `boolean` | `false` | When `true`, keyboard input is forwarded to the process |
184
+
185
+ ### File watching
186
+
187
+ Use `watch` to automatically restart a process when source files change:
188
+
189
+ ```ts
190
+ export default defineConfig({
191
+ processes: {
192
+ api: {
193
+ command: 'node server.js',
194
+ watch: 'src/**/*.ts',
195
+ },
196
+ styles: {
197
+ command: 'sass --watch src:dist',
198
+ watch: ['src/**/*.scss', 'src/**/*.css'],
199
+ },
200
+ },
201
+ })
202
+ ```
203
+
204
+ Patterns are matched relative to the process's `cwd` (or the project root). Changes in `node_modules` and `.git` are always ignored. Rapid file changes are debounced (300ms) to avoid restart storms.
205
+
206
+ A watched process is only restarted if it's currently running, ready, or failed — manually stopped processes are not affected.
207
+
208
+ ### Environment variable interpolation
209
+
210
+ Config values support `${VAR}` syntax for environment variable substitution:
211
+
212
+ ```yaml
213
+ processes:
214
+ api:
215
+ command: node server.js --port ${PORT:-3000}
216
+ env:
217
+ DATABASE_URL: ${DATABASE_URL:?DATABASE_URL must be set}
218
+ ```
219
+
220
+ | Syntax | Behavior |
221
+ |--------|----------|
222
+ | `${VAR}` | Value of `VAR`, or empty string if unset |
223
+ | `${VAR:-default}` | Value of `VAR`, or `default` if unset |
224
+ | `${VAR:?error}` | Value of `VAR`, or error with message if unset |
225
+
226
+ Interpolation applies to all string values in the config (command, cwd, env, envFile, readyPattern, etc.).
227
+
228
+ ### Conditional processes
229
+
230
+ Use `condition` to run a process only when an environment variable is set:
231
+
232
+ ```yaml
233
+ processes:
234
+ seed:
235
+ command: bun run seed
236
+ persistent: false
237
+ condition: SEED_DB # only runs when SEED_DB is set and truthy
238
+ storybook:
239
+ command: bun run storybook
240
+ condition: "!CI" # skipped in CI environments
241
+ ```
242
+
243
+ Falsy values: unset, empty string, `"0"`, `"false"`, `"no"`, `"off"` (case-insensitive). If a conditional process is skipped, its dependents are also skipped.
244
+
245
+ ### Dependency orchestration
246
+
247
+ Processes are grouped into tiers by topological sort. Each tier starts after the previous tier is ready. If a process fails, its dependents are skipped.
248
+
249
+ A process becomes **ready** when:
250
+ - **persistent + readyPattern** — the pattern matches in stdout
251
+ - **persistent + no readyPattern** — immediately after spawn
252
+ - **non-persistent** — exits with code 0
253
+
254
+ Persistent processes that crash are auto-restarted with exponential backoff (1s–30s). Backoff resets after 10s of uptime.
255
+
256
+ ## Keybindings
257
+
258
+ | Key | Action |
259
+ |-----|--------|
260
+ | `Ctrl+C` | Quit (graceful shutdown) |
261
+ | `Alt+R` | Restart active process |
262
+ | `Alt+Shift+R` | Restart all processes |
263
+ | `Alt+S` | Stop/start active process |
264
+ | `Alt+L` | Clear active pane output |
265
+ | `Alt+1`–`Alt+9` | Jump to tab |
266
+ | `Alt+Left/Right` | Cycle tabs |
267
+ | `Up/Down` | Scroll output 1 line (non-interactive panes) |
268
+ | `PageUp/PageDown` | Scroll output by page (non-interactive panes) |
269
+ | `Home/End` | Scroll to top/bottom (non-interactive panes) |
270
+ | `Alt+PageUp/PageDown` | Scroll output up/down |
271
+ | `Alt+Home/End` | Scroll to top/bottom |
272
+ | `Alt+F` | Search in active pane output |
273
+
274
+ While searching: type to filter, `Enter`/`Shift+Enter` to navigate matches, `Escape` to close.
275
+
276
+ Panes are readonly by default — keyboard input is not forwarded to processes. Set `interactive: true` on processes that need stdin (REPLs, shells, etc.).
277
+
278
+ ## Tab icons
279
+
280
+ | Icon | Status |
281
+ |------|--------|
282
+ | ○ | Pending |
283
+ | ◐ | Starting |
284
+ | ◉ | Running |
285
+ | ● | Ready |
286
+ | ◑ | Stopping |
287
+ | ■ | Stopped |
288
+ | ✖ | Failed |
289
+ | ⊘ | Skipped |
290
+
291
+ ## License
292
+
293
+ MIT
package/package.json CHANGED
@@ -1,11 +1,49 @@
1
1
  {
2
2
  "name": "numux",
3
- "version": "0.0.1",
4
- "packageManager": "yarn@4.12.0",
3
+ "version": "1.1.0",
4
+ "description": "Terminal multiplexer with dependency orchestration",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "hyldmo",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/hyldmo/numux.git"
11
+ },
12
+ "keywords": [
13
+ "terminal",
14
+ "multiplexer",
15
+ "process-manager",
16
+ "tui",
17
+ "dev-tools",
18
+ "orchestration"
19
+ ],
20
+ "engines": {
21
+ "bun": ">=1.0"
22
+ },
23
+ "bin": {
24
+ "numux": "src/index.ts"
25
+ },
26
+ "exports": {
27
+ ".": "./src/config.ts"
28
+ },
5
29
  "scripts": {
30
+ "dev": "bun run src/index.ts",
31
+ "test": "bun test",
32
+ "typecheck": "bunx tsc --noEmit",
33
+ "lint": "biome check .",
6
34
  "fix": "biome check . --fix --unsafe"
7
35
  },
8
36
  "files": [
9
- "README.md"
10
- ]
37
+ "src/**/*.ts",
38
+ "!src/**/*.test.ts"
39
+ ],
40
+ "dependencies": {
41
+ "@opentui/core": "^0.1.81",
42
+ "ghostty-opentui": "^1.4.3",
43
+ "yaml": "^2.8.2"
44
+ },
45
+ "devDependencies": {
46
+ "@biomejs/biome": "^2.4.4",
47
+ "@types/bun": "^1.3.9"
48
+ }
11
49
  }
package/src/cli.ts ADDED
@@ -0,0 +1,207 @@
1
+ import type { ResolvedNumuxConfig } from './types'
2
+
3
+ export interface ParsedArgs {
4
+ help: boolean
5
+ version: boolean
6
+ debug: boolean
7
+ init: boolean
8
+ validate: boolean
9
+ exec: boolean
10
+ execName?: string
11
+ execCommand?: string
12
+ completions?: string
13
+ prefix: boolean
14
+ killOthers: boolean
15
+ timestamps: boolean
16
+ noRestart: boolean
17
+ noWatch: boolean
18
+ configPath?: string
19
+ logDir?: string
20
+ only?: string[]
21
+ exclude?: string[]
22
+ commands: string[]
23
+ named: Array<{ name: string; command: string }>
24
+ }
25
+
26
+ export function parseArgs(argv: string[]): ParsedArgs {
27
+ const result: ParsedArgs = {
28
+ help: false,
29
+ version: false,
30
+ debug: false,
31
+ init: false,
32
+ validate: false,
33
+ exec: false,
34
+ prefix: false,
35
+ killOthers: false,
36
+ timestamps: false,
37
+ noRestart: false,
38
+ noWatch: false,
39
+ configPath: undefined,
40
+ commands: [],
41
+ named: []
42
+ }
43
+
44
+ const args = argv.slice(2) // skip bun + script
45
+ let i = 0
46
+
47
+ /** Consume the next argument as a value for the given flag, erroring if missing */
48
+ const consumeValue = (flag: string): string => {
49
+ const next = args[++i]
50
+ if (next === undefined) {
51
+ throw new Error(`Missing value for ${flag}`)
52
+ }
53
+ return next
54
+ }
55
+
56
+ while (i < args.length) {
57
+ const arg = args[i]
58
+
59
+ if (arg === '-h' || arg === '--help') {
60
+ result.help = true
61
+ } else if (arg === '-v' || arg === '--version') {
62
+ result.version = true
63
+ } else if (arg === '--debug') {
64
+ result.debug = true
65
+ } else if (arg === '-p' || arg === '--prefix') {
66
+ result.prefix = true
67
+ } else if (arg === '--kill-others') {
68
+ result.killOthers = true
69
+ } else if (arg === '-t' || arg === '--timestamps') {
70
+ result.timestamps = true
71
+ } else if (arg === '--no-restart') {
72
+ result.noRestart = true
73
+ } else if (arg === '--no-watch') {
74
+ result.noWatch = true
75
+ } else if (arg === '-c' || arg === '--config') {
76
+ result.configPath = consumeValue(arg)
77
+ } else if (arg === '--log-dir') {
78
+ result.logDir = consumeValue(arg)
79
+ } else if (arg === '--only') {
80
+ result.only = consumeValue(arg)
81
+ .split(',')
82
+ .map(s => s.trim())
83
+ .filter(Boolean)
84
+ } else if (arg === '--exclude') {
85
+ result.exclude = consumeValue(arg)
86
+ .split(',')
87
+ .map(s => s.trim())
88
+ .filter(Boolean)
89
+ } else if (arg === '-n' || arg === '--name') {
90
+ const value = consumeValue(arg)
91
+ const eq = value.indexOf('=')
92
+ if (eq < 1) {
93
+ throw new Error(`Invalid --name value: expected "name=command", got "${value}"`)
94
+ }
95
+ result.named.push({
96
+ name: value.slice(0, eq),
97
+ command: value.slice(eq + 1)
98
+ })
99
+ } else if (arg === 'init' && result.commands.length === 0) {
100
+ result.init = true
101
+ } else if (arg === 'validate' && result.commands.length === 0) {
102
+ result.validate = true
103
+ } else if (arg === 'exec' && result.commands.length === 0) {
104
+ result.exec = true
105
+ const name = args[++i]
106
+ if (!name) throw new Error('exec requires a process name')
107
+ result.execName = name
108
+ // Skip optional --
109
+ if (args[i + 1] === '--') i++
110
+ const rest = args.slice(i + 1)
111
+ if (rest.length === 0) throw new Error('exec requires a command to run')
112
+ result.execCommand = rest.join(' ')
113
+ break
114
+ } else if (arg === 'completions' && result.commands.length === 0) {
115
+ result.completions = consumeValue(arg)
116
+ } else if (!arg.startsWith('-')) {
117
+ result.commands.push(arg)
118
+ } else {
119
+ throw new Error(`Unknown option: ${arg}`)
120
+ }
121
+
122
+ i++
123
+ }
124
+
125
+ return result
126
+ }
127
+
128
+ export function buildConfigFromArgs(
129
+ commands: string[],
130
+ named: Array<{ name: string; command: string }>,
131
+ options?: { noRestart?: boolean }
132
+ ): ResolvedNumuxConfig {
133
+ const processes: ResolvedNumuxConfig['processes'] = {}
134
+ const maxRestarts = options?.noRestart ? 0 : undefined
135
+
136
+ for (const { name, command } of named) {
137
+ processes[name] = { command, persistent: true, maxRestarts }
138
+ }
139
+
140
+ for (let i = 0; i < commands.length; i++) {
141
+ const cmd = commands[i]
142
+ // Derive name from command: first word, deduplicated
143
+ let name = cmd.split(/\s+/)[0].split('/').pop()!
144
+ if (processes[name]) {
145
+ name = `${name}-${i}`
146
+ }
147
+ processes[name] = { command: cmd, persistent: true, maxRestarts }
148
+ }
149
+
150
+ return { processes }
151
+ }
152
+
153
+ /** Filter a config to include/exclude specific processes. --only also pulls in transitive dependencies. */
154
+ export function filterConfig(config: ResolvedNumuxConfig, only?: string[], exclude?: string[]): ResolvedNumuxConfig {
155
+ const allNames = Object.keys(config.processes)
156
+
157
+ let selected: Set<string>
158
+
159
+ if (only && only.length > 0) {
160
+ // Validate names exist
161
+ for (const name of only) {
162
+ if (!allNames.includes(name)) {
163
+ throw new Error(`--only: unknown process "${name}"`)
164
+ }
165
+ }
166
+ // Collect transitive dependencies
167
+ selected = new Set<string>()
168
+ const queue = [...only]
169
+ while (queue.length > 0) {
170
+ const name = queue.pop()!
171
+ if (selected.has(name)) continue
172
+ selected.add(name)
173
+ const deps = config.processes[name].dependsOn ?? []
174
+ for (const dep of deps) {
175
+ if (!selected.has(dep)) queue.push(dep)
176
+ }
177
+ }
178
+ } else {
179
+ selected = new Set(allNames)
180
+ }
181
+
182
+ if (exclude && exclude.length > 0) {
183
+ for (const name of exclude) {
184
+ if (!allNames.includes(name)) {
185
+ throw new Error(`--exclude: unknown process "${name}"`)
186
+ }
187
+ selected.delete(name)
188
+ }
189
+ }
190
+
191
+ if (selected.size === 0) {
192
+ throw new Error('No processes left after filtering')
193
+ }
194
+
195
+ const processes: ResolvedNumuxConfig['processes'] = {}
196
+ for (const name of selected) {
197
+ const proc = { ...config.processes[name] }
198
+ // Remove deps that were filtered out
199
+ if (proc.dependsOn) {
200
+ proc.dependsOn = proc.dependsOn.filter(d => selected.has(d))
201
+ if (proc.dependsOn.length === 0) proc.dependsOn = undefined
202
+ }
203
+ processes[name] = proc
204
+ }
205
+
206
+ return { processes }
207
+ }
@@ -0,0 +1,119 @@
1
+ const SUPPORTED_SHELLS = ['bash', 'zsh', 'fish'] as const
2
+
3
+ export function generateCompletions(shell: string): string {
4
+ switch (shell) {
5
+ case 'bash':
6
+ return bashCompletions()
7
+ case 'zsh':
8
+ return zshCompletions()
9
+ case 'fish':
10
+ return fishCompletions()
11
+ default:
12
+ throw new Error(`Unknown shell: "${shell}". Supported: ${SUPPORTED_SHELLS.join(', ')}`)
13
+ }
14
+ }
15
+
16
+ function bashCompletions(): string {
17
+ return `# numux bash completions
18
+ # Add to ~/.bashrc: eval "$(numux completions bash)"
19
+ _numux() {
20
+ local cur prev
21
+ cur="\${COMP_WORDS[COMP_CWORD]}"
22
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
23
+
24
+ case "$prev" in
25
+ -c|--config)
26
+ COMPREPLY=( $(compgen -f -- "$cur") )
27
+ return ;;
28
+ --log-dir)
29
+ COMPREPLY=( $(compgen -d -- "$cur") )
30
+ return ;;
31
+ --only|--exclude)
32
+ return ;;
33
+ -n|--name)
34
+ return ;;
35
+ completions)
36
+ COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
37
+ return ;;
38
+ esac
39
+
40
+ if [[ "$cur" == -* ]]; then
41
+ COMPREPLY=( $(compgen -W "-h --help -v --version -c --config -n --name -p --prefix --only --exclude --kill-others --no-restart --no-watch -t --timestamps --log-dir --debug" -- "$cur") )
42
+ else
43
+ local subcmds="init validate exec completions"
44
+ COMPREPLY=( $(compgen -W "$subcmds" -- "$cur") )
45
+ fi
46
+ }
47
+ complete -F _numux numux`
48
+ }
49
+
50
+ function zshCompletions(): string {
51
+ return `#compdef numux
52
+ # numux zsh completions
53
+ # Add to ~/.zshrc: eval "$(numux completions zsh)"
54
+ _numux() {
55
+ local -a subcmds
56
+ subcmds=(
57
+ 'init:Create a starter config file'
58
+ 'validate:Validate config and show process graph'
59
+ 'exec:Run a command in a process environment'
60
+ 'completions:Generate shell completions'
61
+ )
62
+
63
+ _arguments -s \\
64
+ '(-h --help)'{-h,--help}'[Show help]' \\
65
+ '(-v --version)'{-v,--version}'[Show version]' \\
66
+ '(-c --config)'{-c,--config}'[Config file path]:file:_files' \\
67
+ '(-n --name)'{-n,--name}'[Named process (name=command)]:named process' \\
68
+ '(-p --prefix)'{-p,--prefix}'[Prefixed output mode]' \\
69
+ '--only[Only run these processes]:processes' \\
70
+ '--exclude[Exclude these processes]:processes' \\
71
+ '--kill-others[Kill all when any exits]' \\
72
+ '--no-restart[Disable auto-restart]' \\
73
+ '--no-watch[Disable file watching]' \\
74
+ '(-t --timestamps)'{-t,--timestamps}'[Add timestamps to output]' \\
75
+ '--log-dir[Log directory]:directory:_directories' \\
76
+ '--debug[Enable debug logging]' \\
77
+ '1:subcommand:->subcmd' \\
78
+ '*:command' \\
79
+ && return
80
+
81
+ case "$state" in
82
+ subcmd)
83
+ _describe 'subcommand' subcmds
84
+ ;;
85
+ esac
86
+ }
87
+ _numux`
88
+ }
89
+
90
+ function fishCompletions(): string {
91
+ return `# numux fish completions
92
+ # Add to fish: numux completions fish | source
93
+ # Or save to: ~/.config/fish/completions/numux.fish
94
+ complete -c numux -f
95
+
96
+ # Subcommands
97
+ complete -c numux -n __fish_use_subcommand -a init -d 'Create a starter config file'
98
+ complete -c numux -n __fish_use_subcommand -a validate -d 'Validate config and show process graph'
99
+ complete -c numux -n __fish_use_subcommand -a exec -d 'Run a command in a process environment'
100
+ complete -c numux -n __fish_use_subcommand -a completions -d 'Generate shell completions'
101
+
102
+ # Completions subcommand
103
+ complete -c numux -n '__fish_seen_subcommand_from completions' -a 'bash zsh fish'
104
+
105
+ # Options
106
+ complete -c numux -s h -l help -d 'Show help'
107
+ complete -c numux -s v -l version -d 'Show version'
108
+ complete -c numux -s c -l config -rF -d 'Config file path'
109
+ complete -c numux -s n -l name -r -d 'Named process (name=command)'
110
+ complete -c numux -s p -l prefix -d 'Prefixed output mode'
111
+ complete -c numux -l only -r -d 'Only run these processes'
112
+ complete -c numux -l exclude -r -d 'Exclude these processes'
113
+ complete -c numux -l kill-others -d 'Kill all when any exits'
114
+ complete -c numux -l no-restart -d 'Disable auto-restart'
115
+ complete -c numux -l no-watch -d 'Disable file watching'
116
+ complete -c numux -s t -l timestamps -d 'Add timestamps to output'
117
+ complete -c numux -l log-dir -ra '(__fish_complete_directories)' -d 'Log directory'
118
+ complete -c numux -l debug -d 'Enable debug logging'`
119
+ }