bgrun 3.3.1 → 3.3.3

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,32 +1,32 @@
1
- <![CDATA[<div align="center">
1
+ <div align="center">
2
2
 
3
- # 🦎 BGR
3
+ # bgrun
4
4
 
5
- **Background Runner — a modern process manager built on Bun**
5
+ **Bun Background Runner — a modern process manager built on Bun**
6
6
 
7
- [![npm](https://img.shields.io/npm/v/bgr?color=F7A41D&label=npm&logo=npm)](https://www.npmjs.com/package/bgr)
7
+ [![npm](https://img.shields.io/npm/v/bgrun?color=F7A41D&label=npm&logo=npm)](https://www.npmjs.com/package/bgrun)
8
8
  [![bun](https://img.shields.io/badge/runtime-bun-F7A41D?logo=bun)](https://bun.sh/)
9
- [![license](https://img.shields.io/npm/l/bgr)](./LICENSE)
9
+ [![license](https://img.shields.io/npm/l/bgrun)](./LICENSE)
10
10
 
11
11
  Start, stop, restart, and monitor any process — from dev servers to Docker containers.
12
12
  Zero config. One command. Beautiful dashboard included.
13
13
 
14
14
  ```
15
- bun install -g bgr
15
+ bun install -g bgrun
16
16
  ```
17
17
 
18
18
  </div>
19
19
 
20
20
  ---
21
21
 
22
- ## Why BGR?
22
+ ## Why bgrun?
23
23
 
24
- | Feature | PM2 | bgr |
25
- |---------|-----|-----|
24
+ | Feature | PM2 | bgrun |
25
+ |---------|-----|-------|
26
26
  | Runtime | Node.js | Bun (5× faster startup) |
27
- | Install | `npm i -g pm2` (50+ deps) | `bun i -g bgr` (minimal deps) |
27
+ | Install | `npm i -g pm2` (50+ deps) | `bun i -g bgrun` (minimal deps) |
28
28
  | Config format | JSON / JS / YAML | TOML (or none at all) |
29
- | Dashboard | `pm2 monit` (TUI) | `bgr --dashboard` (full web UI) |
29
+ | Dashboard | `pm2 monit` (TUI) | `bgrun --dashboard` (full web UI) |
30
30
  | Language support | Any | Any |
31
31
  | Docker-aware | ❌ | ✅ detects container status |
32
32
  | Port management | Manual | Auto-detect & cleanup |
@@ -34,25 +34,27 @@ bun install -g bgr
34
34
  | Programmatic API | ✅ | ✅ (first-class TypeScript) |
35
35
  | Process persistence | ✅ | ✅ (SQLite) |
36
36
 
37
+ > **Note:** The CLI is available as both `bgrun` and `bgr` (alias). All examples below use `bgrun`.
38
+
37
39
  ---
38
40
 
39
41
  ## Quick Start
40
42
 
41
43
  ```bash
42
44
  # Install globally
43
- bun install -g bgr
45
+ bun install -g bgrun
44
46
 
45
47
  # Start a process
46
- bgr --name my-api --directory ./my-project --command "bun run server.ts"
48
+ bgrun --name my-api --directory ./my-project --command "bun run server.ts"
47
49
 
48
50
  # List all processes
49
- bgr
51
+ bgrun
50
52
 
51
53
  # Open the web dashboard
52
- bgr --dashboard
54
+ bgrun --dashboard
53
55
  ```
54
56
 
55
- That's it. BGR tracks the PID, captures stdout/stderr, detects the port, and survives terminal close.
57
+ That's it. bgrun tracks the PID, captures stdout/stderr, detects the port, and survives terminal close.
56
58
 
57
59
  ---
58
60
 
@@ -77,7 +79,7 @@ That's it. BGR tracks the PID, captures stdout/stderr, detects the port, and sur
77
79
  ### Starting a process
78
80
 
79
81
  ```bash
80
- bgr --name my-api \
82
+ bgrun --name my-api \
81
83
  --directory ~/projects/my-api \
82
84
  --command "bun run server.ts"
83
85
  ```
@@ -85,34 +87,34 @@ bgr --name my-api \
85
87
  Short form — if you're already *in* the project directory:
86
88
 
87
89
  ```bash
88
- bgr --name my-api --command "bun run server.ts"
89
- # bgr uses current directory by default
90
+ bgrun --name my-api --command "bun run server.ts"
91
+ # bgrun uses current directory by default
90
92
  ```
91
93
 
92
94
  ### Listing processes
93
95
 
94
96
  ```bash
95
- bgr # Pretty table
96
- bgr --json # Machine-readable JSON
97
- bgr --filter api # Filter by group (BGR_GROUP env)
97
+ bgrun # Pretty table
98
+ bgrun --json # Machine-readable JSON
99
+ bgrun --filter api # Filter by group (BGR_GROUP env)
98
100
  ```
99
101
 
100
102
  ### Viewing a process
101
103
 
102
104
  ```bash
103
- bgr my-api # Show status, PID, port, runtime, command
104
- bgr my-api --logs # Show stdout + stderr interleaved
105
- bgr my-api --logs --log-stdout --lines 50 # Last 50 stdout lines only
105
+ bgrun my-api # Show status, PID, port, runtime, command
106
+ bgrun my-api --logs # Show stdout + stderr interleaved
107
+ bgrun my-api --logs --log-stdout --lines 50 # Last 50 stdout lines only
106
108
  ```
107
109
 
108
110
  ### Stopping, restarting, deleting
109
111
 
110
112
  ```bash
111
- bgr --stop my-api # Graceful stop (SIGTERM → SIGKILL)
112
- bgr --restart my-api # Stop then start again with same command
113
- bgr --delete my-api # Stop and remove from database
114
- bgr --clean # Remove all stopped processes
115
- bgr --nuke # ☠️ Delete everything
113
+ bgrun --stop my-api # Graceful stop (SIGTERM → SIGKILL)
114
+ bgrun --restart my-api # Stop then start again with same command
115
+ bgrun --delete my-api # Stop and remove from database
116
+ bgrun --clean # Remove all stopped processes
117
+ bgrun --nuke # ☠️ Delete everything
116
118
  ```
117
119
 
118
120
  ### Force restart
@@ -120,7 +122,7 @@ bgr --nuke # ☠️ Delete everything
120
122
  When a process is stuck or its port is orphaned:
121
123
 
122
124
  ```bash
123
- bgr --name my-api --command "bun run server.ts" --force
125
+ bgrun --name my-api --command "bun run server.ts" --force
124
126
  ```
125
127
 
126
128
  `--force` will:
@@ -134,10 +136,10 @@ bgr --name my-api --command "bun run server.ts" --force
134
136
 
135
137
  ## Dashboard
136
138
 
137
- BGR ships with a built-in web dashboard for managing all your processes visually.
139
+ bgrun ships with a built-in web dashboard for managing all your processes visually.
138
140
 
139
141
  ```bash
140
- bgr --dashboard
142
+ bgrun --dashboard
141
143
  ```
142
144
 
143
145
  The dashboard provides:
@@ -145,7 +147,7 @@ The dashboard provides:
145
147
  - **Start/stop/restart/delete** actions with one click
146
148
  - **Log viewer** with monospace display and auto-scroll
147
149
  - **Process detail drawer** with stdout/stderr tabs
148
- - **Auto-refresh** every 3 seconds
150
+ - **Auto-refresh** every 5 seconds
149
151
 
150
152
  ### Dashboard port selection
151
153
 
@@ -153,21 +155,21 @@ The dashboard uses [Melina.js](https://github.com/7flash/melina.js) for serving
153
155
 
154
156
  | Scenario | Behavior |
155
157
  |----------|----------|
156
- | `bgr --dashboard` | Starts on port 3000. If busy, auto-falls back to 3001, 3002, etc. |
157
- | `BUN_PORT=4000 bgr --dashboard` | Starts on port 4000. Fails with error if port is busy. |
158
- | `bgr --dashboard --port 5000` | Same as `BUN_PORT=5000` — explicit, no fallback. |
158
+ | `bgrun --dashboard` | Starts on port 3000. If busy, auto-falls back to 3001, 3002, etc. |
159
+ | `BUN_PORT=4000 bgrun --dashboard` | Starts on port 4000. Fails with error if port is busy. |
160
+ | `bgrun --dashboard --port 5000` | Same as `BUN_PORT=5000` — explicit, no fallback. |
159
161
  | Dashboard already running | Prints current URL and PID instead of starting a second instance. |
160
162
 
161
- The actual port is always detected from the running process and displayed correctly in `bgr` output.
163
+ The actual port is always detected from the running process and displayed correctly in `bgrun` output.
162
164
 
163
165
  ---
164
166
 
165
167
  ## File Watching
166
168
 
167
- For development, BGR can watch for file changes and auto-restart:
169
+ For development, bgrun can watch for file changes and auto-restart:
168
170
 
169
171
  ```bash
170
- bgr --name frontend \
172
+ bgrun --name frontend \
171
173
  --directory ~/projects/frontend \
172
174
  --command "bun run dev" \
173
175
  --watch
@@ -176,7 +178,7 @@ bgr --name frontend \
176
178
  This monitors the working directory for changes and restarts the process when files are modified. Combine with `--force` to ensure clean restarts:
177
179
 
178
180
  ```bash
179
- bgr --name api \
181
+ bgrun --name api \
180
182
  --command "bun run server.ts" \
181
183
  --watch \
182
184
  --force \
@@ -187,20 +189,20 @@ bgr --name api \
187
189
 
188
190
  ## Port Handling
189
191
 
190
- BGR automatically detects which TCP ports a process is listening on by querying the OS. This means:
192
+ bgrun automatically detects which TCP ports a process is listening on by querying the OS. This means:
191
193
 
192
- - **No port configuration needed** — BGR discovers ports from `netstat`
193
- - **No environment variable assumptions** — BGR doesn't guess `PORT` or `BUN_PORT`
194
+ - **No port configuration needed** — bgrun discovers ports from `netstat`
195
+ - **No environment variable assumptions** — bgrun doesn't guess `PORT` or `BUN_PORT`
194
196
  - **Clean restarts** — `--force` kills all orphaned port bindings before restarting
195
- - **Accurate display** — the port shown in `bgr` output is the *actual* bound port
197
+ - **Accurate display** — the port shown in `bgrun` output is the *actual* bound port
196
198
 
197
199
  ### How it works
198
200
 
199
201
  ```
200
- 1. bgr spawns your process
202
+ 1. bgrun spawns your process
201
203
  2. Process starts and binds to a port (however it wants)
202
- 3. bgr queries `netstat -ano` (Windows) or `ss -tlnp` (Linux)
203
- 4. bgr finds all TCP LISTEN ports for the process PID
204
+ 3. bgrun queries `netstat -ano` (Windows) or `ss -tlnp` (Linux)
205
+ 4. bgrun finds all TCP LISTEN ports for the process PID
204
206
  5. These ports are displayed in the table and used for cleanup
205
207
  ```
206
208
 
@@ -209,7 +211,7 @@ BGR automatically detects which TCP ports a process is listening on by querying
209
211
  If you `--force` restart a process and its old port is still held by a zombie:
210
212
 
211
213
  ```
212
- 1. bgr detects ports held by the old PID
214
+ 1. bgrun detects ports held by the old PID
213
215
  2. Sends SIGTERM to the old process
214
216
  3. Kills any remaining processes on those ports
215
217
  4. Waits for ports to become free (up to 5 seconds)
@@ -220,36 +222,36 @@ If you `--force` restart a process and its old port is still held by a zombie:
220
222
 
221
223
  ## Docker Integration
222
224
 
223
- BGR can manage Docker containers alongside regular processes:
225
+ bgrun can manage Docker containers alongside regular processes:
224
226
 
225
227
  ```bash
226
228
  # Start a Postgres container
227
- bgr --name postgres \
229
+ bgrun --name postgres \
228
230
  --command "docker run --name bgr-postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret postgres:16"
229
231
 
230
232
  # Start a Redis container
231
- bgr --name redis \
233
+ bgrun --name redis \
232
234
  --command "docker run --name bgr-redis -p 6379:6379 redis:7-alpine"
233
235
  ```
234
236
 
235
- ### How BGR handles Docker
237
+ ### How bgrun handles Docker
236
238
 
237
- BGR is **Docker-aware** — when it detects a `docker run` command, it:
239
+ bgrun is **Docker-aware** — when it detects a `docker run` command, it:
238
240
 
239
241
  1. **Checks container status** via `docker inspect` instead of checking the PID
240
- 2. **Handles container lifecycle** — stops containers with `docker stop` on `bgr --stop`
242
+ 2. **Handles container lifecycle** — stops containers with `docker stop` on `bgrun --stop`
241
243
  3. **Reports correct status** — shows Running/Stopped based on container state, not process state
242
244
 
243
245
  ### Docker Compose alternative
244
246
 
245
- Instead of `docker-compose.yml`, use BGR to orchestrate containers alongside your app:
247
+ Instead of `docker-compose.yml`, use bgrun to orchestrate containers alongside your app:
246
248
 
247
249
  ```bash
248
250
  #!/bin/bash
249
251
  # start-stack.sh
250
252
 
251
253
  # Database
252
- bgr --name db \
254
+ bgrun --name db \
253
255
  --command "docker run --name bgr-db -p 5432:5432 \
254
256
  -v pgdata:/var/lib/postgresql/data \
255
257
  -e POSTGRES_DB=myapp \
@@ -258,19 +260,19 @@ bgr --name db \
258
260
  --force
259
261
 
260
262
  # Cache
261
- bgr --name cache \
263
+ bgrun --name cache \
262
264
  --command "docker run --name bgr-cache -p 6379:6379 redis:7-alpine" \
263
265
  --force
264
266
 
265
267
  # Your app (not Docker, just a regular process)
266
- bgr --name api \
268
+ bgrun --name api \
267
269
  --directory ~/projects/my-api \
268
270
  --command "bun run server.ts" \
269
271
  --config production.toml \
270
272
  --force
271
273
 
272
274
  # See everything
273
- bgr
275
+ bgrun
274
276
  ```
275
277
 
276
278
  The advantage over Docker Compose: your app processes and Docker containers are managed in the **same place** with the **same commands**.
@@ -279,16 +281,16 @@ The advantage over Docker Compose: your app processes and Docker containers are
279
281
 
280
282
  ## Caddy Reverse Proxy
281
283
 
282
- BGR pairs naturally with [Caddy](https://caddyserver.com/) for production deployments with automatic HTTPS.
284
+ bgrun pairs naturally with [Caddy](https://caddyserver.com/) for production deployments with automatic HTTPS.
283
285
 
284
286
  ### Basic setup
285
287
 
286
288
  ```bash
287
- # Start your app on any port (BGR detects it)
288
- bgr --name my-api --command "bun run server.ts" --force
289
+ # Start your app on any port (bgrun detects it)
290
+ bgrun --name my-api --command "bun run server.ts" --force
289
291
 
290
292
  # Check which port it got
291
- bgr
293
+ bgrun
292
294
  # → my-api ● Running :3000 bun run server.ts
293
295
  ```
294
296
 
@@ -308,55 +310,35 @@ dashboard.example.com {
308
310
 
309
311
  ```bash
310
312
  # Start services
311
- bgr --name api --command "bun run api/server.ts" --force
312
- bgr --name frontend --command "bun run frontend/server.ts" --force
313
- bgr --name admin --command "bun run admin/server.ts" --force
313
+ bgrun --name api --command "bun run api/server.ts" --force
314
+ bgrun --name frontend --command "bun run frontend/server.ts" --force
315
+ bgrun --name admin --command "bun run admin/server.ts" --force
314
316
 
315
317
  # Start dashboard
316
- bgr --dashboard
318
+ bgrun --dashboard
317
319
  ```
318
320
 
319
- **Caddyfile:**
320
-
321
- ```caddy
322
- api.example.com {
323
- reverse_proxy localhost:3000
324
- }
321
+ ### Managing Caddy with bgrun
325
322
 
326
- app.example.com {
327
- reverse_proxy localhost:3001
328
- }
329
-
330
- admin.example.com {
331
- reverse_proxy localhost:3002
332
- }
333
-
334
- status.example.com {
335
- reverse_proxy localhost:3003 # BGR dashboard
336
- }
337
- ```
338
-
339
- ### Managing Caddy with BGR
340
-
341
- You can even manage Caddy itself as a BGR process:
323
+ You can even manage Caddy itself as a bgrun process:
342
324
 
343
325
  ```bash
344
- bgr --name caddy \
326
+ bgrun --name caddy \
345
327
  --directory /etc/caddy \
346
328
  --command "caddy run --config Caddyfile" \
347
329
  --force
348
330
  ```
349
331
 
350
- Now `bgr` shows your entire stack — app servers, databases, and reverse proxy — in one place.
332
+ Now `bgrun` shows your entire stack — app servers, databases, and reverse proxy — in one place.
351
333
 
352
334
  ---
353
335
 
354
336
  ## TOML Configuration
355
337
 
356
- BGR loads TOML config files and flattens them into environment variables:
338
+ bgrun loads TOML config files and flattens them into environment variables:
357
339
 
358
340
  ```bash
359
- bgr --name api --command "bun run server.ts" --config production.toml
341
+ bgrun --name api --command "bun run server.ts" --config production.toml
360
342
  ```
361
343
 
362
344
  ```toml
@@ -386,16 +368,16 @@ AUTH_SESSION_TTL=3600
386
368
 
387
369
  The convention: `[section]` becomes the prefix, `key` becomes the suffix, joined with `_`, uppercased.
388
370
 
389
- If no `--config` is specified, BGR looks for `.config.toml` in the working directory automatically.
371
+ If no `--config` is specified, bgrun looks for `.config.toml` in the working directory automatically.
390
372
 
391
373
  ---
392
374
 
393
375
  ## Programmatic API
394
376
 
395
- BGR exposes its internals as importable TypeScript functions:
377
+ bgrun exposes its internals as importable TypeScript functions:
396
378
 
397
379
  ```bash
398
- bun add bgr
380
+ bun add bgrun
399
381
  ```
400
382
 
401
383
  ### Process management
@@ -410,7 +392,7 @@ import {
410
392
  getProcessPorts,
411
393
  readFileTail,
412
394
  calculateRuntime,
413
- } from 'bgr'
395
+ } from 'bgrun'
414
396
 
415
397
  // List all processes
416
398
  const procs = getAllProcesses()
@@ -435,10 +417,10 @@ if (proc) {
435
417
  }
436
418
 
437
419
  // Read logs
438
- const proc = getProcess('my-api')
439
- if (proc) {
440
- const stdout = await readFileTail(proc.stdout_path, 100) // last 100 lines
441
- const stderr = await readFileTail(proc.stderr_path, 100)
420
+ const myProc = getProcess('my-api')
421
+ if (myProc) {
422
+ const stdout = await readFileTail(myProc.stdout_path, 100) // last 100 lines
423
+ const stderr = await readFileTail(myProc.stderr_path, 100)
442
424
  }
443
425
 
444
426
  // Stop a process
@@ -448,7 +430,7 @@ await terminateProcess(proc.pid)
448
430
  ### Build a custom dashboard
449
431
 
450
432
  ```typescript
451
- import { getAllProcesses, isProcessRunning, calculateRuntime } from 'bgr'
433
+ import { getAllProcesses, isProcessRunning, calculateRuntime } from 'bgrun'
452
434
 
453
435
  // Express/Hono/Elysia endpoint
454
436
  export async function GET() {
@@ -473,20 +455,20 @@ If you're coming from PM2, here's a direct mapping of commands:
473
455
 
474
456
  ### Command mapping
475
457
 
476
- | PM2 | BGR |
477
- |-----|-----|
478
- | `pm2 start app.js --name api` | `bgr --name api --command "node app.js"` |
458
+ | PM2 | bgrun |
459
+ |-----|-------|
460
+ | `pm2 start app.js --name api` | `bgrun --name api --command "node app.js"` |
479
461
  | `pm2 start app.js -i max` | *(cluster mode not supported — use multiple named processes)* |
480
- | `pm2 list` | `bgr` |
481
- | `pm2 show api` | `bgr api` |
482
- | `pm2 logs api` | `bgr api --logs` |
483
- | `pm2 logs api --lines 50` | `bgr api --logs --lines 50` |
484
- | `pm2 stop api` | `bgr --stop api` |
485
- | `pm2 restart api` | `bgr --restart api` |
486
- | `pm2 delete api` | `bgr --delete api` |
487
- | `pm2 flush` | `bgr --clean` |
488
- | `pm2 kill` | `bgr --nuke` |
489
- | `pm2 monit` | `bgr --dashboard` |
462
+ | `pm2 list` | `bgrun` |
463
+ | `pm2 show api` | `bgrun api` |
464
+ | `pm2 logs api` | `bgrun api --logs` |
465
+ | `pm2 logs api --lines 50` | `bgrun api --logs --lines 50` |
466
+ | `pm2 stop api` | `bgrun --stop api` |
467
+ | `pm2 restart api` | `bgrun --restart api` |
468
+ | `pm2 delete api` | `bgrun --delete api` |
469
+ | `pm2 flush` | `bgrun --clean` |
470
+ | `pm2 kill` | `bgrun --nuke` |
471
+ | `pm2 monit` | `bgrun --dashboard` |
490
472
  | `pm2 save` / `pm2 resurrect` | *(automatic — processes persist in SQLite)* |
491
473
 
492
474
  ### ecosystem.config.js → TOML + shell script
@@ -513,7 +495,7 @@ module.exports = {
513
495
  }
514
496
  ```
515
497
 
516
- **BGR equivalent:**
498
+ **bgrun equivalent:**
517
499
 
518
500
  ```toml
519
501
  # api.toml
@@ -527,32 +509,32 @@ env = "production"
527
509
  ```bash
528
510
  #!/bin/bash
529
511
  # start.sh
530
- bgr --name api --directory ./api --command "node server.js" --config api.toml --force
531
- bgr --name worker --directory ./workers --command "node worker.js" --force
512
+ bgrun --name api --directory ./api --command "node server.js" --config api.toml --force
513
+ bgrun --name worker --directory ./workers --command "node worker.js" --force
532
514
  ```
533
515
 
534
516
  ### Key differences
535
517
 
536
- 1. **No cluster mode** — BGR manages independent processes. For multi-core, run multiple named instances (`api-1`, `api-2`) behind a load balancer.
518
+ 1. **No cluster mode** — bgrun manages independent processes. For multi-core, run multiple named instances (`api-1`, `api-2`) behind a load balancer.
537
519
 
538
- 2. **No `pm2 startup`** — BGR doesn't install itself as a system service. Use your OS init system (systemd, launchd, Windows Task Scheduler) to run BGR at boot:
520
+ 2. **No `pm2 startup`** — bgrun doesn't install itself as a system service. Use your OS init system (systemd, launchd, Windows Task Scheduler) to run bgrun at boot:
539
521
 
540
522
  ```ini
541
- # /etc/systemd/system/bgr-api.service
523
+ # /etc/systemd/system/bgrun-api.service
542
524
  [Unit]
543
- Description=My API via BGR
525
+ Description=My API via bgrun
544
526
 
545
527
  [Service]
546
- ExecStart=/usr/local/bin/bgr --name api --directory /var/www/api --command "bun run server.ts" --force
528
+ ExecStart=/usr/local/bin/bgrun --name api --directory /var/www/api --command "bun run server.ts" --force
547
529
  Restart=always
548
530
 
549
531
  [Install]
550
532
  WantedBy=multi-user.target
551
533
  ```
552
534
 
553
- 3. **No log rotation** — BGR writes to plain text files in `~/.bgr/`. Use `logrotate` or similar tools, or specify custom log paths with `--stdout` and `--stderr`.
535
+ 3. **No log rotation** — bgrun writes to plain text files in `~/.bgr/`. Use `logrotate` or similar tools, or specify custom log paths with `--stdout` and `--stderr`.
554
536
 
555
- 4. **Bun required** — BGR runs on Bun, but the *processes it manages* can be anything: Node.js, Python, Ruby, Go, Docker, shell scripts.
537
+ 4. **Bun required** — bgrun runs on Bun, but the *processes it manages* can be anything: Node.js, Python, Ruby, Go, Docker, shell scripts.
556
538
 
557
539
  ---
558
540
 
@@ -560,10 +542,10 @@ bgr --name worker --directory ./workers --command "node worker.js" --force
560
542
 
561
543
  ### What happens when a process crashes?
562
544
 
563
- BGR records the process as **Stopped**. The PID and log files are preserved so you can inspect what happened:
545
+ bgrun records the process as **Stopped**. The PID and log files are preserved so you can inspect what happened:
564
546
 
565
547
  ```bash
566
- bgr my-api --logs --log-stderr
548
+ bgrun my-api --logs --log-stderr
567
549
  ```
568
550
 
569
551
  For auto-restart on crash, use the guard script:
@@ -572,25 +554,25 @@ For auto-restart on crash, use the guard script:
572
554
  bun run guard.ts my-api 30 # Check every 30 seconds, restart if dead
573
555
  ```
574
556
 
575
- ### What happens on `bgr --force` if the port is stuck?
557
+ ### What happens on `bgrun --force` if the port is stuck?
576
558
 
577
- BGR queries the OS for all TCP ports held by the old PID, kills them, and waits up to 5 seconds for cleanup. If ports are still held after that, the new process starts anyway (and will likely pick a different port).
559
+ bgrun queries the OS for all TCP ports held by the old PID, kills them, and waits up to 5 seconds for cleanup. If ports are still held after that, the new process starts anyway (and will likely pick a different port).
578
560
 
579
561
  ### What happens if I start two processes with the same name?
580
562
 
581
- The new process replaces the old one. If the old one is still running, use `--force` to kill it first. Without `--force`, BGR will refuse to start if a process with that name is already running.
563
+ The new process replaces the old one. If the old one is still running, use `--force` to kill it first. Without `--force`, bgrun will refuse to start if a process with that name is already running.
582
564
 
583
- ### What happens if BGR itself is killed?
565
+ ### What happens if bgrun itself is killed?
584
566
 
585
- The managed processes keep running — they're independent OS processes. When you run `bgr` again, it reconnects to the SQLite database and checks which PIDs are still alive. Dead processes are marked as **Stopped**.
567
+ The managed processes keep running — they're independent OS processes. When you run `bgrun` again, it reconnects to the SQLite database and checks which PIDs are still alive. Dead processes are marked as **Stopped**.
586
568
 
587
569
  ### What about Windows?
588
570
 
589
- BGR works on Windows. Process management uses `taskkill` and `wmic` instead of Unix signals. Port detection uses `netstat -ano`. The dashboard runs in your browser, so it works everywhere.
571
+ bgrun works on Windows. Process management uses `taskkill` and `wmic` instead of Unix signals. Port detection uses `netstat -ano`. The dashboard runs in your browser, so it works everywhere.
590
572
 
591
573
  ### Can I manage processes on a remote server?
592
574
 
593
- Not directly — BGR manages processes on the local machine. For remote management, run BGR on the remote server and expose the dashboard behind a reverse proxy (see [Caddy section](#caddy-reverse-proxy)).
575
+ Not directly — bgrun manages processes on the local machine. For remote management, run bgrun on the remote server and expose the dashboard behind a reverse proxy (see [Caddy section](#caddy-reverse-proxy)).
594
576
 
595
577
  ---
596
578
 
@@ -599,7 +581,7 @@ Not directly — BGR manages processes on the local machine. For remote manageme
599
581
  By default, logs go to `~/.bgr/<name>-out.txt` and `~/.bgr/<name>-err.txt`. Override with:
600
582
 
601
583
  ```bash
602
- bgr --name api \
584
+ bgrun --name api \
603
585
  --command "bun run server.ts" \
604
586
  --stdout /var/log/api/stdout.log \
605
587
  --stderr /var/log/api/stderr.log
@@ -612,15 +594,15 @@ bgr --name api \
612
594
  Tag processes with `BGR_GROUP` to organize and filter them:
613
595
 
614
596
  ```bash
615
- BGR_GROUP=prod bgr --name api --command "bun run server.ts" --force
616
- BGR_GROUP=prod bgr --name worker --command "bun run worker.ts" --force
617
- BGR_GROUP=dev bgr --name dev-server --command "bun run dev" --force
597
+ BGR_GROUP=prod bgrun --name api --command "bun run server.ts" --force
598
+ BGR_GROUP=prod bgrun --name worker --command "bun run worker.ts" --force
599
+ BGR_GROUP=dev bgrun --name dev-server --command "bun run dev" --force
618
600
 
619
601
  # Show only production processes
620
- bgr --filter prod
602
+ bgrun --filter prod
621
603
 
622
604
  # Show only dev processes
623
- bgr --filter dev
605
+ bgrun --filter dev
624
606
  ```
625
607
 
626
608
  ---
@@ -630,7 +612,7 @@ bgr --filter dev
630
612
  Pull the latest changes before starting:
631
613
 
632
614
  ```bash
633
- bgr --name api \
615
+ bgrun --name api \
634
616
  --directory ~/projects/api \
635
617
  --command "bun run server.ts" \
636
618
  --fetch \
@@ -641,7 +623,7 @@ bgr --name api \
641
623
 
642
624
  ```bash
643
625
  # Deploy script
644
- bgr --name api --directory /var/www/api --command "bun run server.ts" --fetch --force
626
+ bgrun --name api --directory /var/www/api --command "bun run server.ts" --fetch --force
645
627
  ```
646
628
 
647
629
  ---
@@ -687,6 +669,7 @@ All state lives in `~/.bgr/`. To reset everything, delete this directory.
687
669
  | `--clean` | Remove stopped processes | - |
688
670
  | `--nuke` | Delete ALL processes | - |
689
671
  | `--dashboard` | Launch web dashboard | - |
672
+ | `--port <number>` | Port for dashboard | 3000 |
690
673
  | `--version` | Show version | - |
691
674
  | `--help` | Show help | - |
692
675
 
@@ -717,4 +700,3 @@ MIT
717
700
  Built by [Mements](https://github.com/Mements) with ⚡ Bun
718
701
 
719
702
  </div>
720
- ]]>
package/dist/index.js CHANGED
@@ -1,37 +1,11 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- var __defProp = Object.defineProperty;
4
- var __export = (target, all) => {
5
- for (var name in all)
6
- __defProp(target, name, {
7
- get: all[name],
8
- enumerable: true,
9
- configurable: true,
10
- set: (newValue) => all[name] = () => newValue
11
- });
12
- };
13
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
3
  var __require = import.meta.require;
15
4
 
5
+ // src/index.ts
6
+ import { parseArgs } from "util";
7
+
16
8
  // src/platform.ts
17
- var exports_platform = {};
18
- __export(exports_platform, {
19
- waitForPortFree: () => waitForPortFree,
20
- terminateProcess: () => terminateProcess,
21
- readFileTail: () => readFileTail,
22
- killProcessOnPort: () => killProcessOnPort,
23
- isWindows: () => isWindows,
24
- isProcessRunning: () => isProcessRunning,
25
- isPortFree: () => isPortFree,
26
- getShellCommand: () => getShellCommand,
27
- getProcessPorts: () => getProcessPorts,
28
- getProcessMemory: () => getProcessMemory,
29
- getHomeDir: () => getHomeDir,
30
- findPidByPort: () => findPidByPort,
31
- findChildPid: () => findChildPid,
32
- ensureDir: () => ensureDir,
33
- copyFile: () => copyFile
34
- });
35
9
  import * as fs from "fs";
36
10
  import * as os from "os";
37
11
  var {$ } = globalThis.Bun;
@@ -225,44 +199,6 @@ async function findChildPid(parentPid) {
225
199
  }
226
200
  return currentPid;
227
201
  }
228
- async function findPidByPort(port, maxWaitMs = 8000) {
229
- const start = Date.now();
230
- const pollMs = 500;
231
- while (Date.now() - start < maxWaitMs) {
232
- try {
233
- if (isWindows()) {
234
- const result = await $`netstat -ano`.nothrow().quiet().text();
235
- for (const line of result.split(`
236
- `)) {
237
- if (line.includes(`:${port}`) && line.includes("LISTENING")) {
238
- const parts = line.trim().split(/\s+/);
239
- const pid = parseInt(parts[parts.length - 1]);
240
- if (!isNaN(pid) && pid > 0)
241
- return pid;
242
- }
243
- }
244
- } else {
245
- try {
246
- const result2 = await $`ss -tlnp`.nothrow().quiet().text();
247
- for (const line of result2.split(`
248
- `)) {
249
- if (line.includes(`:${port}`)) {
250
- const pidMatch = line.match(/pid=(\d+)/);
251
- if (pidMatch)
252
- return parseInt(pidMatch[1]);
253
- }
254
- }
255
- } catch {}
256
- const result = await $`lsof -iTCP:${port} -sTCP:LISTEN -t`.nothrow().quiet().text();
257
- const pid = parseInt(result.trim());
258
- if (!isNaN(pid) && pid > 0)
259
- return pid;
260
- }
261
- } catch {}
262
- await new Promise((resolve) => setTimeout(resolve, pollMs));
263
- }
264
- return null;
265
- }
266
202
  async function readFileTail(filePath, lines) {
267
203
  try {
268
204
  const content = await Bun.file(filePath).text();
@@ -277,28 +213,6 @@ async function readFileTail(filePath, lines) {
277
213
  throw new Error(`Error reading file: ${error}`);
278
214
  }
279
215
  }
280
- function copyFile(src, dest) {
281
- fs.copyFileSync(src, dest);
282
- }
283
- async function getProcessMemory(pid) {
284
- try {
285
- if (isWindows()) {
286
- const result = await $`wmic process where ProcessId=${pid} get WorkingSetSize`.nothrow().text();
287
- const lines = result.split(`
288
- `).filter((line) => line.trim() && !line.includes("WorkingSetSize"));
289
- if (lines.length > 0) {
290
- return parseInt(lines[0].trim()) || 0;
291
- }
292
- return 0;
293
- } else {
294
- const result = await $`ps -o rss= -p ${pid}`.text();
295
- const memoryKB = parseInt(result.trim());
296
- return memoryKB * 1024;
297
- }
298
- } catch {
299
- return 0;
300
- }
301
- }
302
216
  async function getProcessPorts(pid) {
303
217
  try {
304
218
  if (isWindows()) {
@@ -345,15 +259,9 @@ async function getProcessPorts(pid) {
345
259
  return [];
346
260
  }
347
261
  }
348
- var init_platform = () => {};
349
-
350
- // src/index.ts
351
- import { parseArgs } from "util";
352
262
 
353
263
  // src/utils.ts
354
- init_platform();
355
264
  import * as fs2 from "fs";
356
- import { join } from "path";
357
265
  import chalk from "chalk";
358
266
  function parseEnvString(envString) {
359
267
  const env = {};
@@ -372,6 +280,7 @@ function calculateRuntime(startTime) {
372
280
  }
373
281
  async function getVersion() {
374
282
  try {
283
+ const { join } = await import("path");
375
284
  const pkgPath = join(import.meta.dir, "../package.json");
376
285
  const pkg = await Bun.file(pkgPath).json();
377
286
  return pkg.version || "0.0.0";
@@ -430,9 +339,8 @@ function tailFile(path, prefix, colorFn, lines) {
430
339
  }
431
340
 
432
341
  // src/db.ts
433
- init_platform();
434
342
  import { Database, z } from "sqlite-zod-orm";
435
- import { join as join2 } from "path";
343
+ import { join } from "path";
436
344
  var {sleep } = globalThis.Bun;
437
345
  var ProcessSchema = z.object({
438
346
  pid: z.number(),
@@ -447,8 +355,8 @@ var ProcessSchema = z.object({
447
355
  });
448
356
  var homePath = getHomeDir();
449
357
  var dbName = process.env.DB_NAME ?? "bgr";
450
- var dbPath = join2(homePath, ".bgr", `${dbName}_v2.sqlite`);
451
- ensureDir(join2(homePath, ".bgr"));
358
+ var dbPath = join(homePath, ".bgr", `${dbName}_v2.sqlite`);
359
+ ensureDir(join(homePath, ".bgr"));
452
360
  var db = new Database(dbPath, {
453
361
  process: ProcessSchema
454
362
  }, {
@@ -501,9 +409,6 @@ async function retryDatabaseOperation(operation, maxRetries = 5, delay = 100) {
501
409
  throw new Error("Max retries reached for database operation");
502
410
  }
503
411
 
504
- // src/commands/run.ts
505
- init_platform();
506
-
507
412
  // src/logger.ts
508
413
  import boxen from "boxen";
509
414
  import chalk2 from "chalk";
@@ -563,7 +468,7 @@ async function parseConfigFile(configPath) {
563
468
  // src/commands/run.ts
564
469
  var {$: $2 } = globalThis.Bun;
565
470
  var {sleep: sleep2 } = globalThis.Bun;
566
- import { join as join3 } from "path";
471
+ import { join as join2 } from "path";
567
472
  var homePath2 = getHomeDir();
568
473
  async function handleRun(options) {
569
474
  const { command, directory, env, name, configPath, force, fetch, stdout, stderr } = options;
@@ -630,7 +535,7 @@ async function handleRun(options) {
630
535
  finalConfigPath = ".config.toml";
631
536
  }
632
537
  if (finalConfigPath) {
633
- const fullConfigPath = join3(finalDirectory, finalConfigPath);
538
+ const fullConfigPath = join2(finalDirectory, finalConfigPath);
634
539
  if (await Bun.file(fullConfigPath).exists()) {
635
540
  try {
636
541
  const newConfigEnv = await parseConfigFile(fullConfigPath);
@@ -643,9 +548,9 @@ async function handleRun(options) {
643
548
  console.log(`Config file '${finalConfigPath}' not found, continuing without it.`);
644
549
  }
645
550
  }
646
- const stdoutPath = stdout || existingProcess?.stdout_path || join3(homePath2, ".bgr", `${name}-out.txt`);
551
+ const stdoutPath = stdout || existingProcess?.stdout_path || join2(homePath2, ".bgr", `${name}-out.txt`);
647
552
  Bun.write(stdoutPath, "");
648
- const stderrPath = stderr || existingProcess?.stderr_path || join3(homePath2, ".bgr", `${name}-err.txt`);
553
+ const stderrPath = stderr || existingProcess?.stderr_path || join2(homePath2, ".bgr", `${name}-err.txt`);
649
554
  Bun.write(stderrPath, "");
650
555
  const newProcess = Bun.spawn(getShellCommand(finalCommand), {
651
556
  env: { ...Bun.env, ...finalEnv },
@@ -759,9 +664,11 @@ function renderHorizontalTable(rows, columns, options = {}) {
759
664
  return { table: chalk3.gray("No data to display"), truncatedIndices: [] };
760
665
  const borderChars = {
761
666
  rounded: ["\u256D", "\u252C", "\u256E", "\u2500", "\u2502", "\u251C", "\u253C", "\u2524", "\u2570", "\u2534", "\u256F"],
667
+ single: ["\u250C", "\u252C", "\u2510", "\u2500", "\u2502", "\u251C", "\u253C", "\u2524", "\u2514", "\u2534", "\u2518"],
668
+ double: ["\u2554", "\u2566", "\u2557", "\u2550", "\u2551", "\u2560", "\u256C", "\u2563", "\u255A", "\u2569", "\u255D"],
762
669
  none: [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
763
- }[borderStyle];
764
- const [tl, tc, tr, h, v, ml, mc, mr, bl, bc, br] = borderChars;
670
+ };
671
+ const [tl, tc, tr, h, v, ml, mc, mr, bl, bc, br] = borderChars[borderStyle] ?? borderChars.rounded;
765
672
  const columnWidths = calculateColumnWidths(rows, columns, maxWidth, padding);
766
673
  const widthArray = columns.map((col) => columnWidths.get(col.key));
767
674
  const truncatedIndices = new Set;
@@ -834,7 +741,6 @@ function renderProcessTable(processes, options) {
834
741
  }
835
742
 
836
743
  // src/commands/list.ts
837
- init_platform();
838
744
  async function showAll(opts) {
839
745
  const processes = getAllProcesses();
840
746
  const filtered = processes.filter((proc) => {
@@ -896,7 +802,6 @@ async function showAll(opts) {
896
802
  }
897
803
 
898
804
  // src/commands/cleanup.ts
899
- init_platform();
900
805
  import * as fs3 from "fs";
901
806
  async function handleDelete(name) {
902
807
  const process2 = getProcess(name);
@@ -961,11 +866,10 @@ async function handleStop(name) {
961
866
  announce(`Process '${name}' is already stopped.`, "Process Stop");
962
867
  return;
963
868
  }
964
- const { getProcessPorts: getProcessPorts2, killProcessOnPort: killProcessOnPort2 } = await Promise.resolve().then(() => (init_platform(), exports_platform));
965
- const ports = await getProcessPorts2(proc.pid);
869
+ const ports = await getProcessPorts(proc.pid);
966
870
  await terminateProcess(proc.pid);
967
871
  for (const port of ports) {
968
- await killProcessOnPort2(port);
872
+ await killProcessOnPort(port);
969
873
  }
970
874
  announce(`Process '${name}' has been stopped (kept in registry).`, "Process Stopped");
971
875
  }
@@ -975,21 +879,20 @@ async function handleDeleteAll() {
975
879
  announce("There are no processes to delete.", "Delete All");
976
880
  return;
977
881
  }
978
- const { getProcessPorts: getProcessPorts2, killProcessOnPort: killProcessOnPort2, waitForPortFree: waitForPortFree2 } = await Promise.resolve().then(() => (init_platform(), exports_platform));
979
882
  let killedCount = 0;
980
883
  let portsFreed = 0;
981
884
  for (const proc of processes) {
982
885
  const running = await isProcessRunning(proc.pid);
983
886
  if (running) {
984
- const ports = await getProcessPorts2(proc.pid);
887
+ const ports = await getProcessPorts(proc.pid);
985
888
  await terminateProcess(proc.pid, true);
986
889
  killedCount++;
987
890
  for (const port of ports) {
988
- await killProcessOnPort2(port);
989
- const freed = await waitForPortFree2(port, 3000);
891
+ await killProcessOnPort(port);
892
+ const freed = await waitForPortFree(port, 3000);
990
893
  if (!freed) {
991
- await killProcessOnPort2(port);
992
- await waitForPortFree2(port, 2000);
894
+ await killProcessOnPort(port);
895
+ await waitForPortFree(port, 2000);
993
896
  }
994
897
  portsFreed++;
995
898
  }
@@ -1015,9 +918,7 @@ async function handleDeleteAll() {
1015
918
  }
1016
919
 
1017
920
  // src/commands/watch.ts
1018
- init_platform();
1019
921
  import * as fs4 from "fs";
1020
- import { join as join4 } from "path";
1021
922
  import path from "path";
1022
923
  import chalk5 from "chalk";
1023
924
  async function handleWatch(options, logOptions) {
@@ -1177,7 +1078,7 @@ ${color(content)}
1177
1078
  const watcher = fs4.watch(workdir, { recursive: true }, (eventType, filename) => {
1178
1079
  if (filename == null)
1179
1080
  return;
1180
- const fullPath = join4(workdir, filename);
1081
+ const fullPath = path.join(workdir, filename);
1181
1082
  if (fullPath.includes(".git") || fullPath.includes("node_modules"))
1182
1083
  return;
1183
1084
  if (debounceTimeout)
@@ -1205,7 +1106,6 @@ SIGINT received...`));
1205
1106
  }
1206
1107
 
1207
1108
  // src/commands/logs.ts
1208
- init_platform();
1209
1109
  import chalk6 from "chalk";
1210
1110
  import * as fs5 from "fs";
1211
1111
  async function showLogs(name, logType = "both", lines) {
@@ -1249,7 +1149,6 @@ async function showLogs(name, logType = "both", lines) {
1249
1149
  }
1250
1150
 
1251
1151
  // src/commands/details.ts
1252
- init_platform();
1253
1152
  import chalk7 from "chalk";
1254
1153
  async function showDetails(name) {
1255
1154
  const proc = getProcess(name);
@@ -1289,20 +1188,19 @@ import { start } from "melina";
1289
1188
  import path2 from "path";
1290
1189
  async function startServer() {
1291
1190
  const appDir = path2.join(import.meta.dir, "../dashboard/app");
1292
- const port = process.env.BUN_PORT ? parseInt(process.env.BUN_PORT, 10) : 3000;
1191
+ const explicitPort = process.env.BUN_PORT ? parseInt(process.env.BUN_PORT, 10) : undefined;
1293
1192
  await start({
1294
1193
  appDir,
1295
1194
  defaultTitle: "bgrun Dashboard - Process Manager",
1296
1195
  globalCss: path2.join(appDir, "globals.css"),
1297
- port
1196
+ ...explicitPort !== undefined && { port: explicitPort }
1298
1197
  });
1299
1198
  }
1300
1199
 
1301
1200
  // src/index.ts
1302
- init_platform();
1303
1201
  import dedent from "dedent";
1304
1202
  import chalk8 from "chalk";
1305
- import { join as join5 } from "path";
1203
+ import { join as join3 } from "path";
1306
1204
  var {sleep: sleep3 } = globalThis.Bun;
1307
1205
  async function showHelp() {
1308
1206
  const usage = dedent`
@@ -1389,7 +1287,7 @@ async function run() {
1389
1287
  if (values.dashboard) {
1390
1288
  const dashboardName = "bgr-dashboard";
1391
1289
  const homePath3 = getHomeDir();
1392
- const bgrDir = join5(homePath3, ".bgr");
1290
+ const bgrDir = join3(homePath3, ".bgr");
1393
1291
  const requestedPort = values.port;
1394
1292
  const existing = getProcess(dashboardName);
1395
1293
  if (existing && await isProcessRunning(existing.pid)) {
@@ -1418,8 +1316,8 @@ async function run() {
1418
1316
  const scriptPath = resolve(process.argv[1]);
1419
1317
  const spawnCommand = `bun run ${scriptPath} --_serve`;
1420
1318
  const command = `bgrun --_serve`;
1421
- const stdoutPath = join5(bgrDir, `${dashboardName}-out.txt`);
1422
- const stderrPath = join5(bgrDir, `${dashboardName}-err.txt`);
1319
+ const stdoutPath = join3(bgrDir, `${dashboardName}-out.txt`);
1320
+ const stderrPath = join3(bgrDir, `${dashboardName}-err.txt`);
1423
1321
  await Bun.write(stdoutPath, "");
1424
1322
  await Bun.write(stderrPath, "");
1425
1323
  const spawnEnv = { ...Bun.env };
package/package.json CHANGED
@@ -1,60 +1,56 @@
1
- {
2
- "name": "bgrun",
3
- "version": "3.3.1",
4
- "description": "bgrun — A lightweight process manager for Bun",
5
- "type": "module",
6
- "main": "./src/api.ts",
7
- "exports": {
8
- ".": "./src/api.ts"
9
- },
10
- "bin": {
11
- "bgrun": "./dist/index.js",
12
- "bgr": "./dist/index.js"
13
- },
14
- "scripts": {
15
- "build": "bun run ./src/build.ts",
16
- "test": "bun test",
17
- "prepublishOnly": "bun run build"
18
- },
19
- "files": [
20
- "dist",
21
- "src",
22
- "dashboard/app",
23
- "README.md",
24
- "examples/bgr-startup.sh"
25
- ],
26
- "keywords": [
27
- "process-manager",
28
- "bun",
29
- "monitoring",
30
- "devops",
31
- "deployment",
32
- "background",
33
- "daemon"
34
- ],
35
- "author": "7flash",
36
- "license": "MIT",
37
- "repository": {
38
- "type": "git",
39
- "url": "https://github.com/Mements/bgr.git"
40
- },
41
- "devDependencies": {
42
- "bun-types": "latest"
43
- },
44
- "peerDependencies": {
45
- "typescript": "^5.0.0"
46
- },
47
- "dependencies": {
48
- "sqlite-zod-orm": "latest",
49
- "@ments/web": "^1.12.1",
50
- "boxen": "^8.0.1",
51
- "chalk": "^5.4.1",
52
- "dedent": "^1.5.3",
53
- "melina": "^1.3.2",
54
- "react": "^19.2.4",
55
- "react-dom": "^19.2.4"
56
- },
57
- "engines": {
58
- "bun": ">=1.0.0"
59
- }
60
- }
1
+ {
2
+ "name": "bgrun",
3
+ "version": "3.3.3",
4
+ "description": "bgrun — A lightweight process manager for Bun",
5
+ "type": "module",
6
+ "main": "./src/api.ts",
7
+ "exports": {
8
+ ".": "./src/api.ts"
9
+ },
10
+ "bin": {
11
+ "bgrun": "./dist/index.js"
12
+ },
13
+ "scripts": {
14
+ "build": "bun run ./src/build.ts",
15
+ "test": "bun test",
16
+ "prepublishOnly": "bun run build"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "dashboard/app",
22
+ "README.md",
23
+ "examples/bgr-startup.sh"
24
+ ],
25
+ "keywords": [
26
+ "process-manager",
27
+ "bun",
28
+ "monitoring",
29
+ "devops",
30
+ "deployment",
31
+ "background",
32
+ "daemon"
33
+ ],
34
+ "author": "7flash",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/Mements/bgr.git"
39
+ },
40
+ "devDependencies": {
41
+ "bun-types": "latest"
42
+ },
43
+ "peerDependencies": {
44
+ "typescript": "^5.0.0"
45
+ },
46
+ "dependencies": {
47
+ "boxen": "^8.0.1",
48
+ "chalk": "^5.4.1",
49
+ "dedent": "^1.5.3",
50
+ "melina": "^1.3.2",
51
+ "sqlite-zod-orm": "^3.8.0"
52
+ },
53
+ "engines": {
54
+ "bun": ">=1.0.0"
55
+ }
56
+ }
package/src/api.ts CHANGED
@@ -18,6 +18,10 @@
18
18
  * ```
19
19
  */
20
20
 
21
+ // --- Types ---
22
+ export type { Process } from './db'
23
+ export type { CommandOptions } from './types'
24
+
21
25
  // --- Database Operations ---
22
26
  export { db, getAllProcesses, getProcess, insertProcess, removeProcess, removeProcessByName, removeAllProcesses, retryDatabaseOperation } from './db'
23
27
 
@@ -1,17 +1,14 @@
1
1
 
2
2
  import { getProcess, removeProcessByName, removeProcess, getAllProcesses, removeAllProcesses } from "../db";
3
- import { isProcessRunning, terminateProcess } from "../platform";
3
+ import { isProcessRunning, terminateProcess, getProcessPorts, killProcessOnPort, waitForPortFree } from "../platform";
4
4
  import { announce, error } from "../logger";
5
- import type { ProcessRecord } from "../types";
6
5
  import * as fs from "fs";
7
6
 
8
7
  export async function handleDelete(name: string) {
9
- const process = getProcess(name); // Wrapper or raw query needed if getProcess doesn't return full type
10
- // getProcess returns ProcessRecord | null
8
+ const process = getProcess(name);
11
9
 
12
10
  if (!process) {
13
11
  error(`No process found named '${name}'`);
14
- // error calls process.exit(1), so we return but TS doesn't know.
15
12
  return;
16
13
  }
17
14
 
@@ -76,7 +73,6 @@ export async function handleStop(name: string) {
76
73
  }
77
74
 
78
75
  // Detect ports the process is using BEFORE killing it
79
- const { getProcessPorts, killProcessOnPort } = await import("../platform");
80
76
  const ports = await getProcessPorts(proc.pid);
81
77
 
82
78
  await terminateProcess(proc.pid);
@@ -96,7 +92,6 @@ export async function handleDeleteAll() {
96
92
  return;
97
93
  }
98
94
 
99
- const { getProcessPorts, killProcessOnPort, waitForPortFree } = await import("../platform");
100
95
  let killedCount = 0;
101
96
  let portsFreed = 0;
102
97
 
@@ -1,6 +1,6 @@
1
1
  import chalk from "chalk";
2
- import { ProcessTableRow, renderProcessTable } from "../table";
3
- import { ProcessRecord } from "../types";
2
+ import { renderProcessTable } from "../table";
3
+ import type { ProcessTableRow } from "../table";
4
4
  import { getAllProcesses } from "../db";
5
5
  import { announce } from "../logger";
6
6
  import { isProcessRunning, calculateRuntime, parseEnvString } from "../utils";
@@ -1,22 +1,22 @@
1
- import type { CommandOptions, ProcessRecord } from "../types";
1
+ import type { CommandOptions } from "../types";
2
+ import type { Process } from "../db";
2
3
  import { getProcess } from "../db";
3
4
  import { isProcessRunning } from "../platform";
4
5
  import { error, announce } from "../logger";
5
6
  import { tailFile } from "../utils";
6
7
  import { handleRun } from "./run";
7
8
  import * as fs from "fs";
8
- import { join } from "path";
9
9
  import path from "path";
10
10
  import chalk, { type ChalkInstance } from "chalk";
11
11
 
12
12
  export async function handleWatch(options: CommandOptions, logOptions: { showLogs: boolean; logType: 'stdout' | 'stderr' | 'both', lines?: number }) {
13
- let currentProcess: ProcessRecord | null = null;
13
+ let currentProcess: Process | null = null;
14
14
  let isRestarting = false;
15
15
  let debounceTimeout: Timer | null = null;
16
16
  let tailStops: (() => void)[] = [];
17
17
  let lastRestartPath: string | null = null; // Track if restart was due to file change
18
18
 
19
- const dumpLogsIfDead = async (proc: ProcessRecord, reason: string) => {
19
+ const dumpLogsIfDead = async (proc: Process, reason: string) => {
20
20
  const isDead = !(await isProcessRunning(proc.pid));
21
21
  if (!isDead) return false;
22
22
 
@@ -193,7 +193,7 @@ export async function handleWatch(options: CommandOptions, logOptions: { showLog
193
193
 
194
194
  const watcher = fs.watch(workdir, { recursive: true }, (eventType, filename) => {
195
195
  if (filename == null) return;
196
- const fullPath = join(workdir, filename as string);
196
+ const fullPath = path.join(workdir, filename as string);
197
197
  if (fullPath.includes(".git") || fullPath.includes("node_modules")) return;
198
198
  if (debounceTimeout) clearTimeout(debounceTimeout);
199
199
  debounceTimeout = setTimeout(() => restartProcess(fullPath), 500);
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { join } from "path";
1
+
2
2
 
3
3
 
4
4
  function formatEnvKey(key: string): string {
package/src/server.ts CHANGED
@@ -14,11 +14,13 @@ import path from 'path';
14
14
  export async function startServer() {
15
15
  const appDir = path.join(import.meta.dir, '../dashboard/app');
16
16
 
17
- const port = process.env.BUN_PORT ? parseInt(process.env.BUN_PORT, 10) : 3000;
17
+ // Only pass port when BUN_PORT is explicitly set.
18
+ // When omitted, Melina defaults to 3000 with auto-fallback to next available port.
19
+ const explicitPort = process.env.BUN_PORT ? parseInt(process.env.BUN_PORT, 10) : undefined;
18
20
  await start({
19
21
  appDir,
20
22
  defaultTitle: 'bgrun Dashboard - Process Manager',
21
23
  globalCss: path.join(appDir, 'globals.css'),
22
- port,
24
+ ...(explicitPort !== undefined && { port: explicitPort }),
23
25
  });
24
26
  }
package/src/table.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- // ./Documents/bgr/src/table.ts
4
-
5
3
  import chalk from "chalk";
6
4
 
7
5
  export interface TableColumn {
@@ -14,7 +12,7 @@ export interface TableColumn {
14
12
  export interface TableOptions {
15
13
  maxWidth?: number;
16
14
  padding?: number;
17
- borderStyle?: "single" | "double" | "rounded" | "none";
15
+ borderStyle?: "rounded" | "single" | "double" | "none";
18
16
  showHeaders?: boolean;
19
17
  }
20
18
 
@@ -145,11 +143,13 @@ export function renderHorizontalTable(
145
143
  const { maxWidth = getTerminalWidth(), padding = 2, borderStyle = "rounded", showHeaders = true } = options;
146
144
  if (rows.length === 0) return { table: chalk.gray("No data to display"), truncatedIndices: [] };
147
145
 
148
- const borderChars = {
146
+ const borderChars: Record<string, string[]> = {
149
147
  rounded: ["╭", "┬", "╮", "─", "│", "├", "┼", "┤", "╰", "┴", "╯"],
148
+ single: ["┌", "┬", "┐", "─", "│", "├", "┼", "┤", "└", "┴", "┘"],
149
+ double: ["╔", "╦", "╗", "═", "║", "╠", "╬", "╣", "╚", "╩", "╝"],
150
150
  none: [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "],
151
- }[borderStyle]!;
152
- const [tl, tc, tr, h, v, ml, mc, mr, bl, bc, br] = borderChars;
151
+ };
152
+ const [tl, tc, tr, h, v, ml, mc, mr, bl, bc, br] = borderChars[borderStyle] ?? borderChars.rounded;
153
153
  const columnWidths = calculateColumnWidths(rows, columns, maxWidth, padding);
154
154
  const widthArray = columns.map((col) => columnWidths.get(col.key)!);
155
155
  const truncatedIndices = new Set<number>();
package/src/types.ts CHANGED
@@ -12,16 +12,3 @@ export interface CommandOptions {
12
12
  stderr?: string;
13
13
  dbPath?: string;
14
14
  }
15
-
16
- export interface ProcessRecord {
17
- id: number;
18
- pid: number;
19
- workdir: string;
20
- command: string;
21
- name: string;
22
- env: string;
23
- timestamp: string;
24
- configPath?: string;
25
- stdout_path: string;
26
- stderr_path: string;
27
- }
package/src/utils.ts CHANGED
@@ -16,16 +16,16 @@ export function calculateRuntime(startTime: string): string {
16
16
  return `${diffInMinutes} minutes`;
17
17
  }
18
18
 
19
- // Re-export specific utils from platform if they are used as generic utils
19
+ // Re-export platform utils for backward compatibility and convenience
20
20
  export { isProcessRunning } from "./platform";
21
21
 
22
22
  import * as fs from "fs";
23
- import { join } from "path";
24
23
  import chalk from "chalk";
25
24
 
26
25
  // Read version at runtime instead of using macros (macros crash on Windows)
27
26
  export async function getVersion(): Promise<string> {
28
27
  try {
28
+ const { join } = await import("path");
29
29
  const pkgPath = join(import.meta.dir, '../package.json');
30
30
  const pkg = await Bun.file(pkgPath).json();
31
31
  return pkg.version || '0.0.0';
@@ -45,8 +45,6 @@ export function validateDirectory(directory: string) {
45
45
  export function tailFile(path: string, prefix: string, colorFn: (s: string) => string, lines?: number): () => void {
46
46
  let position = 0;
47
47
  let lastPartial = '';
48
- // Check if file exists first? The original code did fs.openSync which throws if not exist.
49
- // Assuming caller checks persistence or we catch.
50
48
 
51
49
  if (!fs.existsSync(path)) {
52
50
  return () => { };
@@ -96,4 +94,3 @@ export function tailFile(path: string, prefix: string, colorFn: (s: string) => s
96
94
  try { fs.closeSync(fd); } catch { }
97
95
  };
98
96
  }
99
-
package/src/schema.ts DELETED
@@ -1,2 +0,0 @@
1
- // Schema is now defined inline in db.ts — re-export for backwards compatibility
2
- export { ProcessSchema, type Process } from "./db";
@@ -1,17 +0,0 @@
1
- import { file } from 'bun';
2
- import { join } from 'path';
3
-
4
- /**
5
- * Reads the project's version from package.json at build time.
6
- * The returned value is directly inlined into the bundle.
7
- */
8
- export async function getVersion(): Promise<string> {
9
- try {
10
- const pkgPath = join(import.meta.dir, '../package.json');
11
- const pkg = await file(pkgPath).json();
12
- return pkg.version || '0.0.0';
13
- } catch (err) {
14
- console.error("Failed to read version from package.json during build:", err);
15
- return '0.0.0-error';
16
- }
17
- }