bgproc 0.1.0 → 0.3.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
@@ -18,6 +18,17 @@ npx bgproc start -n myserver -- npm run dev
18
18
  # Start a process
19
19
  bgproc start -n myserver -- npm run dev
20
20
 
21
+ # Start and wait for port to be detected (great for dev servers)
22
+ bgproc start -n myserver -w -- npm run dev
23
+ # Streams logs to stderr, prints JSON with port to stdout when ready
24
+
25
+ # Force restart (kills existing process with same name)
26
+ bgproc start -n myserver -f -w -- npm run dev
27
+
28
+ # Restart with same command and cwd
29
+ bgproc restart myserver
30
+ bgproc restart myserver -w # wait for port
31
+
21
32
  # Check status (returns JSON with port detection)
22
33
  bgproc status myserver
23
34
  # {"name":"myserver","pid":12345,"running":true,"port":3000,...}
@@ -45,8 +56,11 @@ bgproc clean --all
45
56
  ## Features
46
57
 
47
58
  - **JSON output**: All commands output JSON to stdout, errors to stderr
48
- - **Port detection**: Automatically detects listening ports via `lsof`
49
- - **Duplicate prevention**: Prevent starting multiple processes with the same name
59
+ - **Port detection**: Automatically detects listening ports via `lsof` (checks child processes too)
60
+ - **Wait for port**: `--wait-for-port` blocks until port is detected, streaming logs
61
+ - **Restart**: `restart` re-runs a process with the same command and cwd
62
+ - **Force restart**: `--force` kills existing process with same name before starting
63
+ - **Duplicate prevention**: Prevents starting multiple processes with the same name
50
64
  - **Log management**: Stdout/stderr captured, capped at 1MB
51
65
  - **Timeout support**: `--timeout 60` kills after N seconds
52
66
  - **Auto-cleanup**: Starting a process with the same name as a dead one auto-cleans it
@@ -57,8 +71,18 @@ bgproc clean --all
57
71
  ### `start`
58
72
 
59
73
  ```
60
- -n, --name Process name (required)
61
- -t, --timeout Kill after N seconds
74
+ -n, --name Process name (required)
75
+ -f, --force Kill existing process with same name before starting
76
+ -t, --timeout Kill after N seconds
77
+ -w, --wait-for-port Wait for port detection (optional: timeout in seconds)
78
+ --keep Keep process running on wait timeout (default: kill)
79
+ ```
80
+
81
+ ### `restart`
82
+
83
+ ```
84
+ -w, --wait-for-port Wait for port detection (optional: timeout in seconds)
85
+ --keep Keep process running on wait timeout (default: kill)
62
86
  ```
63
87
 
64
88
  ### `status`, `stop`, `logs`, `clean`
@@ -133,8 +157,9 @@ Use `bgproc` to manage dev servers and background processes. All commands output
133
157
  Workflow:
134
158
  1. `bgproc start -n devserver -- npm run dev` - Start a process
135
159
  2. `bgproc status devserver` - Check if running, get port
136
- 3. `bgproc logs devserver` - View output if something's wrong
137
- 4. `bgproc stop devserver` - Stop when done
160
+ 3. `bgproc restart devserver` - Restart with same command
161
+ 4. `bgproc logs devserver` - View output if something's wrong
162
+ 5. `bgproc stop devserver` - Stop when done
138
163
  ```
139
164
 
140
165
  ## Platform Support
package/dist/cli.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { defineCommand, runMain } from "citty";
3
- import { execSync, spawn } from "node:child_process";
4
3
  import { existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
5
4
  import { homedir } from "node:os";
6
5
  import path, { join } from "node:path";
6
+ import { execSync, spawn } from "node:child_process";
7
7
 
8
8
  //#region src/registry.ts
9
9
  /**
@@ -46,7 +46,7 @@ function addProcess(name, entry) {
46
46
  const registry = readRegistry();
47
47
  const existing = registry[name];
48
48
  if (existing) {
49
- if (isProcessRunning(existing.pid)) throw new Error(`Process '${name}' is already running (PID ${existing.pid}). Use 'bgproc stop ${name}' first.`);
49
+ if (isProcessRunning(existing.pid)) throw new Error(`Process '${name}' is already running (PID ${existing.pid}). Use --force to restart.`);
50
50
  const logPaths = getLogPaths(name);
51
51
  try {
52
52
  if (existsSync(logPaths.stdout)) unlinkSync(logPaths.stdout);
@@ -79,12 +79,173 @@ function getLogPaths(name) {
79
79
  };
80
80
  }
81
81
 
82
+ //#endregion
83
+ //#region src/ports.ts
84
+ /**
85
+ * Get all descendant PIDs of a process (children, grandchildren, etc.)
86
+ */
87
+ function getDescendantPids(pid) {
88
+ const descendants = [];
89
+ try {
90
+ const output = execSync(`pgrep -P ${pid} 2>/dev/null`, { encoding: "utf-8" });
91
+ for (const line of output.trim().split("\n")) {
92
+ const childPid = parseInt(line, 10);
93
+ if (!isNaN(childPid)) {
94
+ descendants.push(childPid);
95
+ descendants.push(...getDescendantPids(childPid));
96
+ }
97
+ }
98
+ } catch {}
99
+ return descendants;
100
+ }
101
+ /**
102
+ * Detect listening ports for a given PID and all its descendants using lsof
103
+ */
104
+ function detectPorts(pid) {
105
+ try {
106
+ const output = execSync(`lsof -p ${[pid, ...getDescendantPids(pid)].join(",")} -P -n 2>/dev/null | grep LISTEN`, { encoding: "utf-8" });
107
+ const ports = [];
108
+ for (const line of output.split("\n")) {
109
+ const match = line.match(/:(\d+)\s+\(LISTEN\)/);
110
+ if (match) ports.push(parseInt(match[1], 10));
111
+ }
112
+ return [...new Set(ports)].sort((a, b) => a - b);
113
+ } catch {
114
+ return [];
115
+ }
116
+ }
117
+
118
+ //#endregion
119
+ //#region src/process.ts
120
+ function spawnProcess(name, command, cwd, timeout) {
121
+ ensureDataDir();
122
+ const logPaths = getLogPaths(name);
123
+ const stdoutFd = openSync(logPaths.stdout, "a");
124
+ const stderrFd = openSync(logPaths.stderr, "a");
125
+ const child = spawn(command[0], command.slice(1), {
126
+ cwd,
127
+ detached: true,
128
+ stdio: [
129
+ "ignore",
130
+ stdoutFd,
131
+ stderrFd
132
+ ]
133
+ });
134
+ child.unref();
135
+ const entry = {
136
+ pid: child.pid,
137
+ command,
138
+ cwd,
139
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
140
+ ...timeout && {
141
+ timeout,
142
+ killAt: new Date(Date.now() + timeout * 1e3).toISOString()
143
+ }
144
+ };
145
+ try {
146
+ addProcess(name, entry);
147
+ } catch (err) {
148
+ try {
149
+ process.kill(child.pid, "SIGTERM");
150
+ } catch {}
151
+ throw err;
152
+ }
153
+ if (timeout && child.pid) scheduleKill(child.pid, timeout, name);
154
+ return {
155
+ child,
156
+ entry,
157
+ logPaths
158
+ };
159
+ }
160
+ function buildStatus(name, entry, extra) {
161
+ return {
162
+ name,
163
+ pid: entry.pid,
164
+ cwd: entry.cwd,
165
+ command: entry.command.join(" "),
166
+ ...entry.timeout && { killAt: entry.killAt },
167
+ ...extra
168
+ };
169
+ }
170
+ async function outputStatus(status, pid, logPath, waitForPort, keep) {
171
+ if (waitForPort !== void 0) {
172
+ const result = await waitForPortDetection(pid, logPath, waitForPort ? parseInt(waitForPort, 10) : void 0, !keep);
173
+ if (result.error) {
174
+ console.error(result.error);
175
+ process.exit(1);
176
+ }
177
+ console.log(JSON.stringify({
178
+ ...status,
179
+ ports: result.ports,
180
+ port: result.ports[0]
181
+ }));
182
+ } else console.log(JSON.stringify(status));
183
+ }
184
+ function waitForPortDetection(pid, logPath, timeoutSeconds, killOnTimeout) {
185
+ return new Promise((resolve) => {
186
+ const startTime = Date.now();
187
+ const tail = spawn("tail", ["-f", logPath], { stdio: [
188
+ "ignore",
189
+ "pipe",
190
+ "ignore"
191
+ ] });
192
+ tail.stdout?.pipe(process.stderr);
193
+ const cleanup = (tailProcess, intervalId) => {
194
+ clearInterval(intervalId);
195
+ tailProcess.kill();
196
+ };
197
+ const pollInterval = setInterval(() => {
198
+ if (!isProcessRunning(pid)) {
199
+ cleanup(tail, pollInterval);
200
+ resolve({ error: `Process ${pid} died before a port was detected` });
201
+ return;
202
+ }
203
+ if (timeoutSeconds !== void 0) {
204
+ if ((Date.now() - startTime) / 1e3 >= timeoutSeconds) {
205
+ cleanup(tail, pollInterval);
206
+ if (killOnTimeout) {
207
+ try {
208
+ process.kill(pid, "SIGTERM");
209
+ } catch {}
210
+ resolve({ error: `Timeout: no port detected after ${timeoutSeconds}s (process killed)` });
211
+ } else resolve({ error: `Timeout: no port detected after ${timeoutSeconds}s (process still running)` });
212
+ return;
213
+ }
214
+ }
215
+ const ports = detectPorts(pid);
216
+ if (ports.length > 0) {
217
+ cleanup(tail, pollInterval);
218
+ resolve({ ports });
219
+ }
220
+ }, 500);
221
+ });
222
+ }
223
+ function scheduleKill(pid, seconds, name) {
224
+ spawn(process.execPath, ["-e", `
225
+ setTimeout(() => {
226
+ try {
227
+ process.kill(${pid}, 0);
228
+ process.kill(${pid}, 'SIGTERM');
229
+ console.error('bgproc: ' + process.env.BGPROC_NAME + ' killed after ${seconds}s timeout');
230
+ } catch {}
231
+ process.exit(0);
232
+ }, ${seconds * 1e3});
233
+ `], {
234
+ detached: true,
235
+ stdio: "ignore",
236
+ env: {
237
+ ...process.env,
238
+ BGPROC_NAME: name
239
+ }
240
+ }).unref();
241
+ }
242
+
82
243
  //#endregion
83
244
  //#region src/commands/start.ts
84
245
  const startCommand = defineCommand({
85
246
  meta: {
86
247
  name: "start",
87
- description: "Start a background process\n\nUsage: bgproc start -n <name> [-t <seconds>] -- <command...>"
248
+ description: "Start a background process\n\nUsage: bgproc start -n <name> [-f] [-t <seconds>] [-w [<seconds>]] [--keep] -- <command...>"
88
249
  },
89
250
  args: {
90
251
  name: {
@@ -97,9 +258,23 @@ const startCommand = defineCommand({
97
258
  type: "string",
98
259
  alias: "t",
99
260
  description: "Kill after N seconds"
261
+ },
262
+ waitForPort: {
263
+ type: "string",
264
+ alias: "w",
265
+ description: "Wait for port to be detected (optional: timeout in seconds)"
266
+ },
267
+ keep: {
268
+ type: "boolean",
269
+ description: "Keep process running on timeout (only with --wait-for-port)"
270
+ },
271
+ force: {
272
+ type: "boolean",
273
+ alias: "f",
274
+ description: "Kill existing process with same name before starting"
100
275
  }
101
276
  },
102
- run({ args, rawArgs }) {
277
+ async run({ args, rawArgs }) {
103
278
  const name = args.name;
104
279
  try {
105
280
  validateName(name);
@@ -107,6 +282,19 @@ const startCommand = defineCommand({
107
282
  console.error(`Error: ${err.message}`);
108
283
  process.exit(1);
109
284
  }
285
+ if (args.keep && args.waitForPort === void 0) {
286
+ console.error("Error: --keep requires --wait-for-port");
287
+ process.exit(1);
288
+ }
289
+ if (args.force) {
290
+ const existing = getProcess(name);
291
+ if (existing && isProcessRunning(existing.pid)) {
292
+ try {
293
+ process.kill(existing.pid, "SIGTERM");
294
+ } catch {}
295
+ removeProcess(name);
296
+ }
297
+ }
110
298
  const timeout = args.timeout ? parseInt(args.timeout, 10) : void 0;
111
299
  const dashDashIdx = rawArgs.indexOf("--");
112
300
  const command = dashDashIdx >= 0 ? rawArgs.slice(dashDashIdx + 1) : [];
@@ -114,116 +302,24 @@ const startCommand = defineCommand({
114
302
  console.error("Error: No command specified. Use: bgproc start -n <name> -- <command>");
115
303
  process.exit(1);
116
304
  }
117
- ensureDataDir();
118
- const logPaths = getLogPaths(name);
119
- const cwd = process.cwd();
120
- const stdoutFd = openSync(logPaths.stdout, "a");
121
- const stderrFd = openSync(logPaths.stderr, "a");
122
- const child = spawn(command[0], command.slice(1), {
123
- cwd,
124
- detached: true,
125
- stdio: [
126
- "ignore",
127
- stdoutFd,
128
- stderrFd
129
- ]
130
- });
131
- child.unref();
132
- const entry = {
133
- pid: child.pid,
134
- command,
135
- cwd,
136
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
137
- ...timeout && {
138
- timeout,
139
- killAt: new Date(Date.now() + timeout * 1e3).toISOString()
140
- }
141
- };
305
+ let result;
142
306
  try {
143
- addProcess(name, entry);
307
+ result = spawnProcess(name, command, process.cwd(), timeout);
144
308
  } catch (err) {
145
- try {
146
- process.kill(child.pid, "SIGTERM");
147
- } catch {}
148
309
  console.error(err.message);
149
310
  process.exit(1);
150
311
  }
151
- if (timeout && child.pid) scheduleKill(child.pid, timeout, name);
152
- console.log(JSON.stringify({
153
- name,
154
- pid: child.pid,
155
- cwd,
156
- command: command.join(" "),
157
- ...timeout && { killAt: entry.killAt }
158
- }));
312
+ await outputStatus(buildStatus(name, result.entry), result.child.pid, result.logPaths.stdout, args.waitForPort, args.keep);
159
313
  }
160
314
  });
161
- /**
162
- * Fork a small process to kill after timeout
163
- * This survives the parent CLI exiting
164
- */
165
- function scheduleKill(pid, seconds, name) {
166
- spawn(process.execPath, ["-e", `
167
- setTimeout(() => {
168
- try {
169
- process.kill(${pid}, 0); // check if alive
170
- process.kill(${pid}, 'SIGTERM');
171
- console.error('bgproc: ' + process.env.BGPROC_NAME + ' killed after ${seconds}s timeout');
172
- } catch {}
173
- process.exit(0);
174
- }, ${seconds * 1e3});
175
- `], {
176
- detached: true,
177
- stdio: "ignore",
178
- env: {
179
- ...process.env,
180
- BGPROC_NAME: name
181
- }
182
- }).unref();
183
- }
184
315
 
185
316
  //#endregion
186
- //#region src/ports.ts
187
- /**
188
- * Detect listening ports for a given PID using lsof
189
- */
190
- function detectPorts(pid) {
191
- try {
192
- const output = execSync(`lsof -p ${pid} -P -n 2>/dev/null | grep LISTEN`, { encoding: "utf-8" });
193
- const ports = [];
194
- for (const line of output.split("\n")) {
195
- const match = line.match(/:(\d+)\s+\(LISTEN\)/);
196
- if (match) ports.push(parseInt(match[1], 10));
197
- }
198
- return [...new Set(ports)];
199
- } catch {
200
- return [];
201
- }
202
- }
203
- /**
204
- * Try to detect port from log output (for when lsof doesn't show it yet)
205
- */
206
- function detectPortFromLogs(logPath) {
207
- if (!existsSync(logPath)) return null;
208
- try {
209
- const lines = readFileSync(logPath, "utf-8").split("\n").slice(-50);
210
- const patterns = [
211
- /localhost:(\d+)/i,
212
- /127\.0\.0\.1:(\d+)/,
213
- /0\.0\.0\.0:(\d+)/,
214
- /port\s+(\d+)/i,
215
- /listening\s+(?:on\s+)?(?:port\s+)?:?(\d+)/i,
216
- /:\/\/[^:]+:(\d+)/
217
- ];
218
- for (const line of lines.reverse()) for (const pattern of patterns) {
219
- const match = line.match(pattern);
220
- if (match) {
221
- const port = parseInt(match[1], 10);
222
- if (port > 0 && port < 65536) return port;
223
- }
224
- }
225
- } catch {}
226
- return null;
317
+ //#region src/utils.ts
318
+ function formatUptime(startedAt) {
319
+ const seconds = Math.floor((Date.now() - startedAt.getTime()) / 1e3);
320
+ if (seconds < 60) return `${seconds}s`;
321
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m${seconds % 60}s`;
322
+ return `${Math.floor(seconds / 3600)}h${Math.floor(seconds % 3600 / 60)}m`;
227
323
  }
228
324
 
229
325
  //#endregion
@@ -252,15 +348,7 @@ const statusCommand = defineCommand({
252
348
  process.exit(1);
253
349
  }
254
350
  const running = isProcessRunning(entry.pid);
255
- const logPaths = getLogPaths(name);
256
- let ports = [];
257
- if (running) {
258
- ports = detectPorts(entry.pid);
259
- if (ports.length === 0) {
260
- const logPort = detectPortFromLogs(logPaths.stdout);
261
- if (logPort) ports = [logPort];
262
- }
263
- }
351
+ const ports = running ? detectPorts(entry.pid) : [];
264
352
  const uptime = running ? formatUptime(new Date(entry.startedAt)) : null;
265
353
  console.log(JSON.stringify({
266
354
  name,
@@ -276,12 +364,6 @@ const statusCommand = defineCommand({
276
364
  }));
277
365
  }
278
366
  });
279
- function formatUptime(startedAt) {
280
- const seconds = Math.floor((Date.now() - startedAt.getTime()) / 1e3);
281
- if (seconds < 60) return `${seconds}s`;
282
- if (seconds < 3600) return `${Math.floor(seconds / 60)}m${seconds % 60}s`;
283
- return `${Math.floor(seconds / 3600)}h${Math.floor(seconds % 3600 / 60)}m`;
284
- }
285
367
 
286
368
  //#endregion
287
369
  //#region src/logs.ts
@@ -457,22 +539,18 @@ const listCommand = defineCommand({
457
539
  return true;
458
540
  }).map(([name, entry]) => {
459
541
  const running = isProcessRunning(entry.pid);
460
- let ports = [];
461
- if (running) {
462
- ports = detectPorts(entry.pid);
463
- if (ports.length === 0) {
464
- const logPort = detectPortFromLogs(getLogPaths(name).stdout);
465
- if (logPort) ports = [logPort];
466
- }
467
- }
542
+ const ports = running ? detectPorts(entry.pid) : [];
468
543
  return {
469
544
  name,
470
545
  pid: entry.pid,
471
546
  running,
547
+ ports,
472
548
  port: ports[0] ?? null,
473
549
  cwd: entry.cwd,
474
550
  command: entry.command.join(" "),
475
- startedAt: entry.startedAt
551
+ startedAt: entry.startedAt,
552
+ uptime: running ? formatUptime(new Date(entry.startedAt)) : null,
553
+ ...entry.killAt && { killAt: entry.killAt }
476
554
  };
477
555
  });
478
556
  console.log(JSON.stringify(entries, null, 2));
@@ -544,6 +622,56 @@ function cleanProcess(name) {
544
622
  } catch {}
545
623
  }
546
624
 
625
+ //#endregion
626
+ //#region src/commands/restart.ts
627
+ const restartCommand = defineCommand({
628
+ meta: {
629
+ name: "restart",
630
+ description: "Restart a background process with the same command and cwd\n\nUsage: bgproc restart <name> [-w [<seconds>]] [--keep]"
631
+ },
632
+ args: {
633
+ name: {
634
+ type: "string",
635
+ alias: "n",
636
+ description: "Process name"
637
+ },
638
+ waitForPort: {
639
+ type: "string",
640
+ alias: "w",
641
+ description: "Wait for port to be detected (optional: timeout in seconds)"
642
+ },
643
+ keep: {
644
+ type: "boolean",
645
+ description: "Keep process running on timeout (only with --wait-for-port)"
646
+ }
647
+ },
648
+ async run({ args, rawArgs }) {
649
+ const name = args.name ?? rawArgs[0];
650
+ try {
651
+ validateName(name);
652
+ } catch (err) {
653
+ console.error(`Error: ${err.message}`);
654
+ process.exit(1);
655
+ }
656
+ if (args.keep && args.waitForPort === void 0) {
657
+ console.error("Error: --keep requires --wait-for-port");
658
+ process.exit(1);
659
+ }
660
+ const existing = getProcess(name);
661
+ if (!existing) {
662
+ console.error(`Process '${name}' not found`);
663
+ process.exit(1);
664
+ }
665
+ if (isProcessRunning(existing.pid)) try {
666
+ process.kill(existing.pid, "SIGTERM");
667
+ } catch {}
668
+ removeProcess(name);
669
+ const { command, cwd, timeout } = existing;
670
+ const result = spawnProcess(name, command, cwd, timeout);
671
+ await outputStatus(buildStatus(name, result.entry, { restarted: true }), result.child.pid, result.logPaths.stdout, args.waitForPort, args.keep);
672
+ }
673
+ });
674
+
547
675
  //#endregion
548
676
  //#region src/cli.ts
549
677
  await runMain(defineCommand({
@@ -558,6 +686,7 @@ await runMain(defineCommand({
558
686
  logs: logsCommand,
559
687
  stop: stopCommand,
560
688
  list: listCommand,
689
+ restart: restartCommand,
561
690
  clean: cleanCommand
562
691
  }
563
692
  }));
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/registry.ts","../src/commands/start.ts","../src/ports.ts","../src/commands/status.ts","../src/logs.ts","../src/commands/logs.ts","../src/commands/stop.ts","../src/commands/list.ts","../src/commands/clean.ts","../src/cli.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * Validate process name to prevent path traversal and code injection\n */\nexport function validateName(name: string): void {\n if (!name) {\n throw new Error(\"Process name required\");\n }\n if (!/^[a-zA-Z0-9_-]+$/.test(name)) {\n throw new Error(\n \"Process name must contain only alphanumeric characters, hyphens, and underscores\",\n );\n }\n if (name.length > 64) {\n throw new Error(\"Process name must be 64 characters or less\");\n }\n}\n\nexport interface ProcessEntry {\n pid: number;\n command: string[];\n cwd: string;\n startedAt: string;\n timeout?: number; // seconds, if set\n killAt?: string; // ISO timestamp when to kill\n}\n\nexport interface Registry {\n [name: string]: ProcessEntry;\n}\n\nfunction getDataDir(): string {\n return (\n process.env.BGPROC_DATA_DIR || join(homedir(), \".local\", \"share\", \"bgproc\")\n );\n}\n\nfunction getRegistryPath(): string {\n return join(getDataDir(), \"registry.json\");\n}\n\nexport function getLogsDir(): string {\n return join(getDataDir(), \"logs\");\n}\n\nexport function ensureDataDir(): void {\n mkdirSync(getDataDir(), { recursive: true });\n mkdirSync(getLogsDir(), { recursive: true });\n}\n\nexport function readRegistry(): Registry {\n ensureDataDir();\n const registryPath = getRegistryPath();\n if (!existsSync(registryPath)) {\n return {};\n }\n try {\n const content = readFileSync(registryPath, \"utf-8\");\n return JSON.parse(content);\n } catch {\n return {};\n }\n}\n\nexport function writeRegistry(registry: Registry): void {\n ensureDataDir();\n writeFileSync(getRegistryPath(), JSON.stringify(registry, null, 2));\n}\n\nexport function addProcess(name: string, entry: ProcessEntry): void {\n const registry = readRegistry();\n const existing = registry[name];\n\n if (existing) {\n if (isProcessRunning(existing.pid)) {\n throw new Error(\n `Process '${name}' is already running (PID ${existing.pid}). Use 'bgproc stop ${name}' first.`,\n );\n }\n // Dead process - auto-clean old logs before starting fresh\n const logPaths = getLogPaths(name);\n try {\n if (existsSync(logPaths.stdout)) unlinkSync(logPaths.stdout);\n if (existsSync(logPaths.stderr)) unlinkSync(logPaths.stderr);\n } catch {\n // ignore\n }\n }\n\n registry[name] = entry;\n writeRegistry(registry);\n}\n\nexport function removeProcess(name: string): void {\n const registry = readRegistry();\n delete registry[name];\n writeRegistry(registry);\n}\n\nexport function getProcess(name: string): ProcessEntry | undefined {\n const registry = readRegistry();\n return registry[name];\n}\n\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getLogPaths(name: string): { stdout: string; stderr: string } {\n return {\n stdout: join(getLogsDir(), `${name}.stdout.log`),\n stderr: join(getLogsDir(), `${name}.stderr.log`),\n };\n}\n","import { defineCommand } from \"citty\";\nimport { spawn } from \"node:child_process\";\nimport { openSync } from \"node:fs\";\nimport { addProcess, getLogPaths, ensureDataDir, validateName } from \"../registry.js\";\n\nexport const startCommand = defineCommand({\n meta: {\n name: \"start\",\n description:\n \"Start a background process\\n\\nUsage: bgproc start -n <name> [-t <seconds>] -- <command...>\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Name for the process\",\n required: true,\n },\n timeout: {\n type: \"string\",\n alias: \"t\",\n description: \"Kill after N seconds\",\n },\n },\n run({ args, rawArgs }) {\n const name = args.name;\n\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n const timeout = args.timeout ? parseInt(args.timeout, 10) : undefined;\n\n // Get command from rawArgs after \"--\"\n const dashDashIdx = rawArgs.indexOf(\"--\");\n const command = dashDashIdx >= 0 ? rawArgs.slice(dashDashIdx + 1) : [];\n\n if (command.length === 0) {\n console.error(\n \"Error: No command specified. Use: bgproc start -n <name> -- <command>\",\n );\n process.exit(1);\n }\n\n ensureDataDir();\n const logPaths = getLogPaths(name);\n const cwd = process.cwd();\n\n // Open log files\n const stdoutFd = openSync(logPaths.stdout, \"a\");\n const stderrFd = openSync(logPaths.stderr, \"a\");\n\n // Spawn detached process\n const child = spawn(command[0]!, command.slice(1), {\n cwd,\n detached: true,\n stdio: [\"ignore\", stdoutFd, stderrFd],\n });\n\n child.unref();\n\n const entry = {\n pid: child.pid!,\n command,\n cwd,\n startedAt: new Date().toISOString(),\n ...(timeout && {\n timeout,\n killAt: new Date(Date.now() + timeout * 1000).toISOString(),\n }),\n };\n\n try {\n addProcess(name, entry);\n } catch (err) {\n // Kill the process we just started since we can't register it\n try {\n process.kill(child.pid!, \"SIGTERM\");\n } catch {\n // ignore\n }\n console.error((err as Error).message);\n process.exit(1);\n }\n\n // If timeout specified, schedule kill\n if (timeout && child.pid) {\n scheduleKill(child.pid, timeout, name);\n }\n\n console.log(\n JSON.stringify({\n name,\n pid: child.pid,\n cwd,\n command: command.join(\" \"),\n ...(timeout && { killAt: entry.killAt }),\n }),\n );\n },\n});\n\n/**\n * Fork a small process to kill after timeout\n * This survives the parent CLI exiting\n */\nfunction scheduleKill(pid: number, seconds: number, name: string): void {\n const killer = spawn(\n process.execPath,\n [\n \"-e\",\n `\n setTimeout(() => {\n try {\n process.kill(${pid}, 0); // check if alive\n process.kill(${pid}, 'SIGTERM');\n console.error('bgproc: ' + process.env.BGPROC_NAME + ' killed after ${seconds}s timeout');\n } catch {}\n process.exit(0);\n }, ${seconds * 1000});\n `,\n ],\n {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env, BGPROC_NAME: name },\n },\n );\n killer.unref();\n}\n","import { execSync } from \"node:child_process\";\nimport { readFileSync, existsSync } from \"node:fs\";\n\n/**\n * Detect listening ports for a given PID using lsof\n */\nexport function detectPorts(pid: number): number[] {\n try {\n // Just filter by PID and look for LISTEN in the output\n // -P = show port numbers, -n = no DNS resolution\n const output = execSync(`lsof -p ${pid} -P -n 2>/dev/null | grep LISTEN`, {\n encoding: \"utf-8\",\n });\n const ports: number[] = [];\n for (const line of output.split(\"\\n\")) {\n // Format: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\n // NAME is like *:3000 or 127.0.0.1:3000 or [::1]:3000\n const match = line.match(/:(\\d+)\\s+\\(LISTEN\\)/);\n if (match) {\n ports.push(parseInt(match[1], 10));\n }\n }\n return [...new Set(ports)]; // dedupe\n } catch {\n return [];\n }\n}\n\n/**\n * Try to detect port from log output (for when lsof doesn't show it yet)\n */\nexport function detectPortFromLogs(logPath: string): number | null {\n if (!existsSync(logPath)) return null;\n\n try {\n const content = readFileSync(logPath, \"utf-8\");\n // Check last 50 lines for port announcements\n const lines = content.split(\"\\n\").slice(-50);\n\n const patterns = [\n /localhost:(\\d+)/i,\n /127\\.0\\.0\\.1:(\\d+)/,\n /0\\.0\\.0\\.0:(\\d+)/,\n /port\\s+(\\d+)/i,\n /listening\\s+(?:on\\s+)?(?:port\\s+)?:?(\\d+)/i,\n /:\\/\\/[^:]+:(\\d+)/,\n ];\n\n for (const line of lines.reverse()) {\n for (const pattern of patterns) {\n const match = line.match(pattern);\n if (match) {\n const port = parseInt(match[1], 10);\n if (port > 0 && port < 65536) {\n return port;\n }\n }\n }\n }\n } catch {\n // ignore\n }\n\n return null;\n}\n","import { defineCommand } from \"citty\";\nimport { getProcess, isProcessRunning, getLogPaths, validateName } from \"../registry.js\";\nimport { detectPorts, detectPortFromLogs } from \"../ports.js\";\n\nexport const statusCommand = defineCommand({\n meta: {\n name: \"status\",\n description: \"Get status of a background process, including pid and open ports\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Process name\",\n },\n },\n run({ args, rawArgs }) {\n const name = args.name ?? rawArgs[0];\n\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n const entry = getProcess(name);\n\n if (!entry) {\n console.error(`Process '${name}' not found`);\n process.exit(1);\n }\n\n const running = isProcessRunning(entry.pid);\n const logPaths = getLogPaths(name);\n\n let ports: number[] = [];\n if (running) {\n ports = detectPorts(entry.pid);\n // Fallback to log parsing if lsof didn't find anything\n if (ports.length === 0) {\n const logPort = detectPortFromLogs(logPaths.stdout);\n if (logPort) ports = [logPort];\n }\n }\n\n const uptime = running ? formatUptime(new Date(entry.startedAt)) : null;\n\n console.log(\n JSON.stringify({\n name,\n pid: entry.pid,\n running,\n ports,\n port: ports[0] ?? null, // convenience: first port\n cwd: entry.cwd,\n command: entry.command.join(\" \"),\n startedAt: entry.startedAt,\n uptime,\n ...(entry.killAt && { killAt: entry.killAt }),\n }),\n );\n },\n});\n\nfunction formatUptime(startedAt: Date): string {\n const seconds = Math.floor((Date.now() - startedAt.getTime()) / 1000);\n if (seconds < 60) return `${seconds}s`;\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m${seconds % 60}s`;\n const hours = Math.floor(seconds / 3600);\n const mins = Math.floor((seconds % 3600) / 60);\n return `${hours}h${mins}m`;\n}\n","import { createWriteStream, statSync, readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport type { WriteStream } from \"node:fs\";\n\nconst MAX_LOG_SIZE = 1 * 1024 * 1024; // 1MB\nconst KEEP_SIZE = 512 * 1024; // Keep last 512KB when truncating\n\n/**\n * Create a write stream that caps file size at 1MB\n */\nexport function createCappedWriteStream(path: string): WriteStream {\n const stream = createWriteStream(path, { flags: \"a\" });\n\n // Check and truncate periodically\n let bytesWritten = 0;\n const originalWrite = stream.write.bind(stream);\n\n stream.write = function (chunk: any, ...args: any[]): boolean {\n bytesWritten += Buffer.byteLength(chunk);\n\n // Every ~100KB written, check total size\n if (bytesWritten > 100 * 1024) {\n bytesWritten = 0;\n try {\n const stats = statSync(path);\n if (stats.size > MAX_LOG_SIZE) {\n // Truncate asynchronously - next writes will be to truncated file\n truncateLogFile(path);\n }\n } catch {\n // ignore\n }\n }\n\n return originalWrite(chunk, ...args);\n } as typeof stream.write;\n\n return stream;\n}\n\n/**\n * Truncate log file to keep only the last KEEP_SIZE bytes\n */\nfunction truncateLogFile(path: string): void {\n try {\n const content = readFileSync(path);\n if (content.length > MAX_LOG_SIZE) {\n const kept = content.slice(-KEEP_SIZE);\n // Find first newline to avoid cutting mid-line\n const newlineIdx = kept.indexOf(10); // \\n\n const trimmed = newlineIdx > 0 ? kept.slice(newlineIdx + 1) : kept;\n writeFileSync(path, trimmed);\n }\n } catch {\n // ignore\n }\n}\n\n/**\n * Read the last N lines from a log file\n */\nexport function readLastLines(path: string, n: number): string[] {\n if (!existsSync(path)) return [];\n\n try {\n const content = readFileSync(path, \"utf-8\");\n const lines = content.split(\"\\n\");\n // Remove trailing empty line if present\n if (lines[lines.length - 1] === \"\") {\n lines.pop();\n }\n return lines.slice(-n);\n } catch {\n return [];\n }\n}\n\n/**\n * Read entire log file\n */\nexport function readLog(path: string): string {\n if (!existsSync(path)) return \"\";\n try {\n return readFileSync(path, \"utf-8\");\n } catch {\n return \"\";\n }\n}\n","import { defineCommand } from \"citty\";\nimport { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { getProcess, getLogPaths, validateName } from \"../registry.js\";\nimport { readLastLines, readLog } from \"../logs.js\";\n\nexport const logsCommand = defineCommand({\n meta: {\n name: \"logs\",\n description: \"View logs for a background process\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Process name\",\n },\n tail: {\n type: \"string\",\n alias: \"t\",\n description: \"Number of lines to show (default: 100)\",\n },\n follow: {\n type: \"boolean\",\n alias: \"f\",\n description: \"Follow log output (tail -f)\",\n },\n errors: {\n type: \"boolean\",\n alias: \"e\",\n description: \"Show only stderr\",\n },\n all: {\n type: \"boolean\",\n alias: \"a\",\n description: \"Show all logs (no line limit)\",\n },\n },\n run({ args, rawArgs }) {\n const name = args.name ?? rawArgs[0];\n\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n const entry = getProcess(name);\n\n if (!entry) {\n console.error(`Process '${name}' not found`);\n process.exit(1);\n }\n\n const logPaths = getLogPaths(name);\n const logPath = args.errors ? logPaths.stderr : logPaths.stdout;\n\n if (!existsSync(logPath)) {\n console.error(`No logs found for '${name}'`);\n process.exit(1);\n }\n\n if (args.follow) {\n // Use tail -f for follow mode\n const tail = spawn(\"tail\", [\"-f\", logPath], {\n stdio: \"inherit\",\n });\n\n process.on(\"SIGINT\", () => {\n tail.kill();\n process.exit(0);\n });\n\n return;\n }\n\n if (args.all) {\n const content = readLog(logPath);\n process.stdout.write(content);\n return;\n }\n\n const lines = parseInt(args.tail ?? \"100\", 10);\n const output = readLastLines(logPath, lines);\n console.log(output.join(\"\\n\"));\n },\n});\n","import { defineCommand } from \"citty\";\nimport { getProcess, removeProcess, isProcessRunning, validateName } from \"../registry.js\";\n\nexport const stopCommand = defineCommand({\n meta: {\n name: \"stop\",\n description: \"Stop a background process\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Process name\",\n },\n force: {\n type: \"boolean\",\n alias: \"f\",\n description: \"Force kill (SIGKILL instead of SIGTERM)\",\n },\n },\n run({ args, rawArgs }) {\n const name = args.name ?? rawArgs[0];\n\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n const entry = getProcess(name);\n\n if (!entry) {\n console.error(`Process '${name}' not found`);\n process.exit(1);\n }\n\n const wasRunning = isProcessRunning(entry.pid);\n\n if (!wasRunning) {\n process.stderr.write(\n `Warning: Process '${name}' (PID ${entry.pid}) was already dead\\n`,\n );\n } else {\n const signal = args.force ? \"SIGKILL\" : \"SIGTERM\";\n try {\n process.kill(entry.pid, signal);\n } catch (err) {\n console.error(`Failed to kill process: ${(err as Error).message}`);\n process.exit(1);\n }\n }\n\n removeProcess(name);\n\n console.log(\n JSON.stringify({\n name,\n pid: entry.pid,\n stopped: true,\n wasRunning,\n signal: wasRunning ? (args.force ? \"SIGKILL\" : \"SIGTERM\") : null,\n }),\n );\n },\n});\n","import path from \"node:path\";\nimport { defineCommand } from \"citty\";\nimport { readRegistry, isProcessRunning, getLogPaths } from \"../registry.js\";\nimport { detectPorts, detectPortFromLogs } from \"../ports.js\";\n\nexport const listCommand = defineCommand({\n meta: {\n name: \"list\",\n description: \"List all background processes\",\n },\n args: {\n cwd: {\n type: \"string\",\n alias: \"c\",\n description: \"Filter by cwd (no arg = current directory)\",\n },\n },\n run({ args }) {\n const registry = readRegistry();\n\n // Handle --cwd with no value: use current directory\n // citty will set it to \"\" if flag present with no value\n let cwdFilter: string | undefined;\n if (args.cwd !== undefined) {\n cwdFilter = args.cwd === \"\" ? process.cwd() : path.resolve(args.cwd);\n }\n\n const entries = Object.entries(registry)\n .filter(([_, entry]) => {\n if (cwdFilter && entry.cwd !== cwdFilter) {\n return false;\n }\n return true;\n })\n .map(([name, entry]) => {\n const running = isProcessRunning(entry.pid);\n let ports: number[] = [];\n\n if (running) {\n ports = detectPorts(entry.pid);\n if (ports.length === 0) {\n const logPaths = getLogPaths(name);\n const logPort = detectPortFromLogs(logPaths.stdout);\n if (logPort) ports = [logPort];\n }\n }\n\n return {\n name,\n pid: entry.pid,\n running,\n port: ports[0] ?? null,\n cwd: entry.cwd,\n command: entry.command.join(\" \"),\n startedAt: entry.startedAt,\n };\n });\n\n console.log(JSON.stringify(entries, null, 2));\n },\n});\n","import { defineCommand } from \"citty\";\nimport { unlinkSync, existsSync } from \"node:fs\";\nimport {\n readRegistry,\n removeProcess,\n isProcessRunning,\n getLogPaths,\n validateName,\n} from \"../registry.js\";\n\nexport const cleanCommand = defineCommand({\n meta: {\n name: \"clean\",\n description: \"Remove dead processes and their logs\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Process name\",\n },\n all: {\n type: \"boolean\",\n alias: \"a\",\n description: \"Clean all dead processes\",\n },\n },\n run({ args, rawArgs }) {\n const registry = readRegistry();\n const cleaned: string[] = [];\n const name = args.name ?? rawArgs[0];\n\n if (args.all) {\n // Clean all dead processes\n for (const [procName, entry] of Object.entries(registry)) {\n if (!isProcessRunning(entry.pid)) {\n cleanProcess(procName);\n cleaned.push(procName);\n }\n }\n } else if (name) {\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n const entry = registry[name];\n\n if (!entry) {\n console.error(`Process '${name}' not found`);\n process.exit(1);\n }\n\n if (isProcessRunning(entry.pid)) {\n console.error(\n `Process '${name}' is still running. Use 'bgproc stop ${name}' first.`,\n );\n process.exit(1);\n }\n\n cleanProcess(name);\n cleaned.push(name);\n } else {\n console.error(\"Specify a process name or use --all\");\n process.exit(1);\n }\n\n console.log(\n JSON.stringify({\n cleaned,\n count: cleaned.length,\n }),\n );\n },\n});\n\nfunction cleanProcess(name: string): void {\n removeProcess(name);\n\n const logPaths = getLogPaths(name);\n try {\n if (existsSync(logPaths.stdout)) unlinkSync(logPaths.stdout);\n if (existsSync(logPaths.stderr)) unlinkSync(logPaths.stderr);\n } catch {\n // ignore\n }\n}\n","import { defineCommand, runMain } from \"citty\";\nimport { startCommand } from \"./commands/start.js\";\nimport { statusCommand } from \"./commands/status.js\";\nimport { logsCommand } from \"./commands/logs.js\";\nimport { stopCommand } from \"./commands/stop.js\";\nimport { listCommand } from \"./commands/list.js\";\nimport { cleanCommand } from \"./commands/clean.js\";\n\nconst main = defineCommand({\n meta: {\n name: \"bgproc\",\n version: \"0.1.0\",\n description: \"Simple process manager for agents. All commands output JSON to stdout.\\nExample: bgproc start -n myserver -- npm run dev\",\n },\n subCommands: {\n start: startCommand,\n status: statusCommand,\n logs: logsCommand,\n stop: stopCommand,\n list: listCommand,\n clean: cleanCommand,\n },\n});\n\nawait runMain(main);\n"],"mappings":";;;;;;;;;;;AAOA,SAAgB,aAAa,MAAoB;AAC/C,KAAI,CAAC,KACH,OAAM,IAAI,MAAM,wBAAwB;AAE1C,KAAI,CAAC,mBAAmB,KAAK,KAAK,CAChC,OAAM,IAAI,MACR,mFACD;AAEH,KAAI,KAAK,SAAS,GAChB,OAAM,IAAI,MAAM,6CAA6C;;AAiBjE,SAAS,aAAqB;AAC5B,QACE,QAAQ,IAAI,mBAAmB,KAAK,SAAS,EAAE,UAAU,SAAS,SAAS;;AAI/E,SAAS,kBAA0B;AACjC,QAAO,KAAK,YAAY,EAAE,gBAAgB;;AAG5C,SAAgB,aAAqB;AACnC,QAAO,KAAK,YAAY,EAAE,OAAO;;AAGnC,SAAgB,gBAAsB;AACpC,WAAU,YAAY,EAAE,EAAE,WAAW,MAAM,CAAC;AAC5C,WAAU,YAAY,EAAE,EAAE,WAAW,MAAM,CAAC;;AAG9C,SAAgB,eAAyB;AACvC,gBAAe;CACf,MAAM,eAAe,iBAAiB;AACtC,KAAI,CAAC,WAAW,aAAa,CAC3B,QAAO,EAAE;AAEX,KAAI;EACF,MAAM,UAAU,aAAa,cAAc,QAAQ;AACnD,SAAO,KAAK,MAAM,QAAQ;SACpB;AACN,SAAO,EAAE;;;AAIb,SAAgB,cAAc,UAA0B;AACtD,gBAAe;AACf,eAAc,iBAAiB,EAAE,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC;;AAGrE,SAAgB,WAAW,MAAc,OAA2B;CAClE,MAAM,WAAW,cAAc;CAC/B,MAAM,WAAW,SAAS;AAE1B,KAAI,UAAU;AACZ,MAAI,iBAAiB,SAAS,IAAI,CAChC,OAAM,IAAI,MACR,YAAY,KAAK,4BAA4B,SAAS,IAAI,sBAAsB,KAAK,UACtF;EAGH,MAAM,WAAW,YAAY,KAAK;AAClC,MAAI;AACF,OAAI,WAAW,SAAS,OAAO,CAAE,YAAW,SAAS,OAAO;AAC5D,OAAI,WAAW,SAAS,OAAO,CAAE,YAAW,SAAS,OAAO;UACtD;;AAKV,UAAS,QAAQ;AACjB,eAAc,SAAS;;AAGzB,SAAgB,cAAc,MAAoB;CAChD,MAAM,WAAW,cAAc;AAC/B,QAAO,SAAS;AAChB,eAAc,SAAS;;AAGzB,SAAgB,WAAW,MAAwC;AAEjE,QADiB,cAAc,CACf;;AAGlB,SAAgB,iBAAiB,KAAsB;AACrD,KAAI;AACF,UAAQ,KAAK,KAAK,EAAE;AACpB,SAAO;SACD;AACN,SAAO;;;AAIX,SAAgB,YAAY,MAAkD;AAC5E,QAAO;EACL,QAAQ,KAAK,YAAY,EAAE,GAAG,KAAK,aAAa;EAChD,QAAQ,KAAK,YAAY,EAAE,GAAG,KAAK,aAAa;EACjD;;;;;ACnHH,MAAa,eAAe,cAAc;CACxC,MAAM;EACJ,MAAM;EACN,aACE;EACH;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACb,UAAU;GACX;EACD,SAAS;GACP,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,IAAI,EAAE,MAAM,WAAW;EACrB,MAAM,OAAO,KAAK;AAElB,MAAI;AACF,gBAAa,KAAK;WACX,KAAK;AACZ,WAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,WAAQ,KAAK,EAAE;;EAGjB,MAAM,UAAU,KAAK,UAAU,SAAS,KAAK,SAAS,GAAG,GAAG;EAG5D,MAAM,cAAc,QAAQ,QAAQ,KAAK;EACzC,MAAM,UAAU,eAAe,IAAI,QAAQ,MAAM,cAAc,EAAE,GAAG,EAAE;AAEtE,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAQ,MACN,wEACD;AACD,WAAQ,KAAK,EAAE;;AAGjB,iBAAe;EACf,MAAM,WAAW,YAAY,KAAK;EAClC,MAAM,MAAM,QAAQ,KAAK;EAGzB,MAAM,WAAW,SAAS,SAAS,QAAQ,IAAI;EAC/C,MAAM,WAAW,SAAS,SAAS,QAAQ,IAAI;EAG/C,MAAM,QAAQ,MAAM,QAAQ,IAAK,QAAQ,MAAM,EAAE,EAAE;GACjD;GACA,UAAU;GACV,OAAO;IAAC;IAAU;IAAU;IAAS;GACtC,CAAC;AAEF,QAAM,OAAO;EAEb,MAAM,QAAQ;GACZ,KAAK,MAAM;GACX;GACA;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,GAAI,WAAW;IACb;IACA,QAAQ,IAAI,KAAK,KAAK,KAAK,GAAG,UAAU,IAAK,CAAC,aAAa;IAC5D;GACF;AAED,MAAI;AACF,cAAW,MAAM,MAAM;WAChB,KAAK;AAEZ,OAAI;AACF,YAAQ,KAAK,MAAM,KAAM,UAAU;WAC7B;AAGR,WAAQ,MAAO,IAAc,QAAQ;AACrC,WAAQ,KAAK,EAAE;;AAIjB,MAAI,WAAW,MAAM,IACnB,cAAa,MAAM,KAAK,SAAS,KAAK;AAGxC,UAAQ,IACN,KAAK,UAAU;GACb;GACA,KAAK,MAAM;GACX;GACA,SAAS,QAAQ,KAAK,IAAI;GAC1B,GAAI,WAAW,EAAE,QAAQ,MAAM,QAAQ;GACxC,CAAC,CACH;;CAEJ,CAAC;;;;;AAMF,SAAS,aAAa,KAAa,SAAiB,MAAoB;AAsBtE,CArBe,MACb,QAAQ,UACR,CACE,MACA;;;yBAGmB,IAAI;yBACJ,IAAI;gFACmD,QAAQ;;;WAG7E,UAAU,IAAK;QAErB,EACD;EACE,UAAU;EACV,OAAO;EACP,KAAK;GAAE,GAAG,QAAQ;GAAK,aAAa;GAAM;EAC3C,CACF,CACM,OAAO;;;;;;;;AC7HhB,SAAgB,YAAY,KAAuB;AACjD,KAAI;EAGF,MAAM,SAAS,SAAS,WAAW,IAAI,mCAAmC,EACxE,UAAU,SACX,CAAC;EACF,MAAM,QAAkB,EAAE;AAC1B,OAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,EAAE;GAGrC,MAAM,QAAQ,KAAK,MAAM,sBAAsB;AAC/C,OAAI,MACF,OAAM,KAAK,SAAS,MAAM,IAAI,GAAG,CAAC;;AAGtC,SAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;SACpB;AACN,SAAO,EAAE;;;;;;AAOb,SAAgB,mBAAmB,SAAgC;AACjE,KAAI,CAAC,WAAW,QAAQ,CAAE,QAAO;AAEjC,KAAI;EAGF,MAAM,QAFU,aAAa,SAAS,QAAQ,CAExB,MAAM,KAAK,CAAC,MAAM,IAAI;EAE5C,MAAM,WAAW;GACf;GACA;GACA;GACA;GACA;GACA;GACD;AAED,OAAK,MAAM,QAAQ,MAAM,SAAS,CAChC,MAAK,MAAM,WAAW,UAAU;GAC9B,MAAM,QAAQ,KAAK,MAAM,QAAQ;AACjC,OAAI,OAAO;IACT,MAAM,OAAO,SAAS,MAAM,IAAI,GAAG;AACnC,QAAI,OAAO,KAAK,OAAO,MACrB,QAAO;;;SAKT;AAIR,QAAO;;;;;AC3DT,MAAa,gBAAgB,cAAc;CACzC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,EACJ,MAAM;EACJ,MAAM;EACN,OAAO;EACP,aAAa;EACd,EACF;CACD,IAAI,EAAE,MAAM,WAAW;EACrB,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,MAAI;AACF,gBAAa,KAAK;WACX,KAAK;AACZ,WAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,WAAQ,KAAK,EAAE;;EAGjB,MAAM,QAAQ,WAAW,KAAK;AAE9B,MAAI,CAAC,OAAO;AACV,WAAQ,MAAM,YAAY,KAAK,aAAa;AAC5C,WAAQ,KAAK,EAAE;;EAGjB,MAAM,UAAU,iBAAiB,MAAM,IAAI;EAC3C,MAAM,WAAW,YAAY,KAAK;EAElC,IAAI,QAAkB,EAAE;AACxB,MAAI,SAAS;AACX,WAAQ,YAAY,MAAM,IAAI;AAE9B,OAAI,MAAM,WAAW,GAAG;IACtB,MAAM,UAAU,mBAAmB,SAAS,OAAO;AACnD,QAAI,QAAS,SAAQ,CAAC,QAAQ;;;EAIlC,MAAM,SAAS,UAAU,aAAa,IAAI,KAAK,MAAM,UAAU,CAAC,GAAG;AAEnE,UAAQ,IACN,KAAK,UAAU;GACb;GACA,KAAK,MAAM;GACX;GACA;GACA,MAAM,MAAM,MAAM;GAClB,KAAK,MAAM;GACX,SAAS,MAAM,QAAQ,KAAK,IAAI;GAChC,WAAW,MAAM;GACjB;GACA,GAAI,MAAM,UAAU,EAAE,QAAQ,MAAM,QAAQ;GAC7C,CAAC,CACH;;CAEJ,CAAC;AAEF,SAAS,aAAa,WAAyB;CAC7C,MAAM,UAAU,KAAK,OAAO,KAAK,KAAK,GAAG,UAAU,SAAS,IAAI,IAAK;AACrE,KAAI,UAAU,GAAI,QAAO,GAAG,QAAQ;AACpC,KAAI,UAAU,KAAM,QAAO,GAAG,KAAK,MAAM,UAAU,GAAG,CAAC,GAAG,UAAU,GAAG;AAGvE,QAAO,GAFO,KAAK,MAAM,UAAU,KAAK,CAExB,GADH,KAAK,MAAO,UAAU,OAAQ,GAAG,CACtB;;;;;ACpE1B,MAAM,eAAe,IAAI,OAAO;AAChC,MAAM,YAAY,MAAM;;;;AAwDxB,SAAgB,cAAc,MAAc,GAAqB;AAC/D,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO,EAAE;AAEhC,KAAI;EAEF,MAAM,QADU,aAAa,MAAM,QAAQ,CACrB,MAAM,KAAK;AAEjC,MAAI,MAAM,MAAM,SAAS,OAAO,GAC9B,OAAM,KAAK;AAEb,SAAO,MAAM,MAAM,CAAC,EAAE;SAChB;AACN,SAAO,EAAE;;;;;;AAOb,SAAgB,QAAQ,MAAsB;AAC5C,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;AAC9B,KAAI;AACF,SAAO,aAAa,MAAM,QAAQ;SAC5B;AACN,SAAO;;;;;;AC9EX,MAAa,cAAc,cAAc;CACvC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,QAAQ;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,QAAQ;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,KAAK;GACH,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,IAAI,EAAE,MAAM,WAAW;EACrB,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,MAAI;AACF,gBAAa,KAAK;WACX,KAAK;AACZ,WAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,WAAQ,KAAK,EAAE;;AAKjB,MAAI,CAFU,WAAW,KAAK,EAElB;AACV,WAAQ,MAAM,YAAY,KAAK,aAAa;AAC5C,WAAQ,KAAK,EAAE;;EAGjB,MAAM,WAAW,YAAY,KAAK;EAClC,MAAM,UAAU,KAAK,SAAS,SAAS,SAAS,SAAS;AAEzD,MAAI,CAAC,WAAW,QAAQ,EAAE;AACxB,WAAQ,MAAM,sBAAsB,KAAK,GAAG;AAC5C,WAAQ,KAAK,EAAE;;AAGjB,MAAI,KAAK,QAAQ;GAEf,MAAM,OAAO,MAAM,QAAQ,CAAC,MAAM,QAAQ,EAAE,EAC1C,OAAO,WACR,CAAC;AAEF,WAAQ,GAAG,gBAAgB;AACzB,SAAK,MAAM;AACX,YAAQ,KAAK,EAAE;KACf;AAEF;;AAGF,MAAI,KAAK,KAAK;GACZ,MAAM,UAAU,QAAQ,QAAQ;AAChC,WAAQ,OAAO,MAAM,QAAQ;AAC7B;;EAIF,MAAM,SAAS,cAAc,SADf,SAAS,KAAK,QAAQ,OAAO,GAAG,CACF;AAC5C,UAAQ,IAAI,OAAO,KAAK,KAAK,CAAC;;CAEjC,CAAC;;;;ACpFF,MAAa,cAAc,cAAc;CACvC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,IAAI,EAAE,MAAM,WAAW;EACrB,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,MAAI;AACF,gBAAa,KAAK;WACX,KAAK;AACZ,WAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,WAAQ,KAAK,EAAE;;EAGjB,MAAM,QAAQ,WAAW,KAAK;AAE9B,MAAI,CAAC,OAAO;AACV,WAAQ,MAAM,YAAY,KAAK,aAAa;AAC5C,WAAQ,KAAK,EAAE;;EAGjB,MAAM,aAAa,iBAAiB,MAAM,IAAI;AAE9C,MAAI,CAAC,WACH,SAAQ,OAAO,MACb,qBAAqB,KAAK,SAAS,MAAM,IAAI,sBAC9C;OACI;GACL,MAAM,SAAS,KAAK,QAAQ,YAAY;AACxC,OAAI;AACF,YAAQ,KAAK,MAAM,KAAK,OAAO;YACxB,KAAK;AACZ,YAAQ,MAAM,2BAA4B,IAAc,UAAU;AAClE,YAAQ,KAAK,EAAE;;;AAInB,gBAAc,KAAK;AAEnB,UAAQ,IACN,KAAK,UAAU;GACb;GACA,KAAK,MAAM;GACX,SAAS;GACT;GACA,QAAQ,aAAc,KAAK,QAAQ,YAAY,YAAa;GAC7D,CAAC,CACH;;CAEJ,CAAC;;;;AC5DF,MAAa,cAAc,cAAc;CACvC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,EACJ,KAAK;EACH,MAAM;EACN,OAAO;EACP,aAAa;EACd,EACF;CACD,IAAI,EAAE,QAAQ;EACZ,MAAM,WAAW,cAAc;EAI/B,IAAI;AACJ,MAAI,KAAK,QAAQ,OACf,aAAY,KAAK,QAAQ,KAAK,QAAQ,KAAK,GAAG,KAAK,QAAQ,KAAK,IAAI;EAGtE,MAAM,UAAU,OAAO,QAAQ,SAAS,CACrC,QAAQ,CAAC,GAAG,WAAW;AACtB,OAAI,aAAa,MAAM,QAAQ,UAC7B,QAAO;AAET,UAAO;IACP,CACD,KAAK,CAAC,MAAM,WAAW;GACtB,MAAM,UAAU,iBAAiB,MAAM,IAAI;GAC3C,IAAI,QAAkB,EAAE;AAExB,OAAI,SAAS;AACX,YAAQ,YAAY,MAAM,IAAI;AAC9B,QAAI,MAAM,WAAW,GAAG;KAEtB,MAAM,UAAU,mBADC,YAAY,KAAK,CACU,OAAO;AACnD,SAAI,QAAS,SAAQ,CAAC,QAAQ;;;AAIlC,UAAO;IACL;IACA,KAAK,MAAM;IACX;IACA,MAAM,MAAM,MAAM;IAClB,KAAK,MAAM;IACX,SAAS,MAAM,QAAQ,KAAK,IAAI;IAChC,WAAW,MAAM;IAClB;IACD;AAEJ,UAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;;CAEhD,CAAC;;;;AClDF,MAAa,eAAe,cAAc;CACxC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,KAAK;GACH,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,IAAI,EAAE,MAAM,WAAW;EACrB,MAAM,WAAW,cAAc;EAC/B,MAAM,UAAoB,EAAE;EAC5B,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,MAAI,KAAK,KAEP;QAAK,MAAM,CAAC,UAAU,UAAU,OAAO,QAAQ,SAAS,CACtD,KAAI,CAAC,iBAAiB,MAAM,IAAI,EAAE;AAChC,iBAAa,SAAS;AACtB,YAAQ,KAAK,SAAS;;aAGjB,MAAM;AACf,OAAI;AACF,iBAAa,KAAK;YACX,KAAK;AACZ,YAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,YAAQ,KAAK,EAAE;;GAGjB,MAAM,QAAQ,SAAS;AAEvB,OAAI,CAAC,OAAO;AACV,YAAQ,MAAM,YAAY,KAAK,aAAa;AAC5C,YAAQ,KAAK,EAAE;;AAGjB,OAAI,iBAAiB,MAAM,IAAI,EAAE;AAC/B,YAAQ,MACN,YAAY,KAAK,uCAAuC,KAAK,UAC9D;AACD,YAAQ,KAAK,EAAE;;AAGjB,gBAAa,KAAK;AAClB,WAAQ,KAAK,KAAK;SACb;AACL,WAAQ,MAAM,sCAAsC;AACpD,WAAQ,KAAK,EAAE;;AAGjB,UAAQ,IACN,KAAK,UAAU;GACb;GACA,OAAO,QAAQ;GAChB,CAAC,CACH;;CAEJ,CAAC;AAEF,SAAS,aAAa,MAAoB;AACxC,eAAc,KAAK;CAEnB,MAAM,WAAW,YAAY,KAAK;AAClC,KAAI;AACF,MAAI,WAAW,SAAS,OAAO,CAAE,YAAW,SAAS,OAAO;AAC5D,MAAI,WAAW,SAAS,OAAO,CAAE,YAAW,SAAS,OAAO;SACtD;;;;;AC7DV,MAAM,QAhBO,cAAc;CACzB,MAAM;EACJ,MAAM;EACN,SAAS;EACT,aAAa;EACd;CACD,aAAa;EACX,OAAO;EACP,QAAQ;EACR,MAAM;EACN,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACF,CAAC,CAEiB"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/registry.ts","../src/ports.ts","../src/process.ts","../src/commands/start.ts","../src/utils.ts","../src/commands/status.ts","../src/logs.ts","../src/commands/logs.ts","../src/commands/stop.ts","../src/commands/list.ts","../src/commands/clean.ts","../src/commands/restart.ts","../src/cli.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * Validate process name to prevent path traversal and code injection\n */\nexport function validateName(name: string): void {\n if (!name) {\n throw new Error(\"Process name required\");\n }\n if (!/^[a-zA-Z0-9_-]+$/.test(name)) {\n throw new Error(\n \"Process name must contain only alphanumeric characters, hyphens, and underscores\",\n );\n }\n if (name.length > 64) {\n throw new Error(\"Process name must be 64 characters or less\");\n }\n}\n\nexport interface ProcessEntry {\n pid: number;\n command: string[];\n cwd: string;\n startedAt: string;\n timeout?: number; // seconds, if set\n killAt?: string; // ISO timestamp when to kill\n}\n\nexport interface Registry {\n [name: string]: ProcessEntry;\n}\n\nfunction getDataDir(): string {\n return (\n process.env.BGPROC_DATA_DIR || join(homedir(), \".local\", \"share\", \"bgproc\")\n );\n}\n\nfunction getRegistryPath(): string {\n return join(getDataDir(), \"registry.json\");\n}\n\nexport function getLogsDir(): string {\n return join(getDataDir(), \"logs\");\n}\n\nexport function ensureDataDir(): void {\n mkdirSync(getDataDir(), { recursive: true });\n mkdirSync(getLogsDir(), { recursive: true });\n}\n\nexport function readRegistry(): Registry {\n ensureDataDir();\n const registryPath = getRegistryPath();\n if (!existsSync(registryPath)) {\n return {};\n }\n try {\n const content = readFileSync(registryPath, \"utf-8\");\n return JSON.parse(content);\n } catch {\n return {};\n }\n}\n\nexport function writeRegistry(registry: Registry): void {\n ensureDataDir();\n writeFileSync(getRegistryPath(), JSON.stringify(registry, null, 2));\n}\n\nexport function addProcess(name: string, entry: ProcessEntry): void {\n const registry = readRegistry();\n const existing = registry[name];\n\n if (existing) {\n if (isProcessRunning(existing.pid)) {\n throw new Error(\n `Process '${name}' is already running (PID ${existing.pid}). Use --force to restart.`,\n );\n }\n // Dead process - auto-clean old logs before starting fresh\n const logPaths = getLogPaths(name);\n try {\n if (existsSync(logPaths.stdout)) unlinkSync(logPaths.stdout);\n if (existsSync(logPaths.stderr)) unlinkSync(logPaths.stderr);\n } catch {\n // ignore\n }\n }\n\n registry[name] = entry;\n writeRegistry(registry);\n}\n\nexport function removeProcess(name: string): void {\n const registry = readRegistry();\n delete registry[name];\n writeRegistry(registry);\n}\n\nexport function getProcess(name: string): ProcessEntry | undefined {\n const registry = readRegistry();\n return registry[name];\n}\n\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getLogPaths(name: string): { stdout: string; stderr: string } {\n return {\n stdout: join(getLogsDir(), `${name}.stdout.log`),\n stderr: join(getLogsDir(), `${name}.stderr.log`),\n };\n}\n","import { execSync } from \"node:child_process\";\n\n/**\n * Get all descendant PIDs of a process (children, grandchildren, etc.)\n */\nfunction getDescendantPids(pid: number): number[] {\n const descendants: number[] = [];\n try {\n // Get direct children using pgrep\n const output = execSync(`pgrep -P ${pid} 2>/dev/null`, {\n encoding: \"utf-8\",\n });\n for (const line of output.trim().split(\"\\n\")) {\n const childPid = parseInt(line, 10);\n if (!isNaN(childPid)) {\n descendants.push(childPid);\n // Recursively get grandchildren\n descendants.push(...getDescendantPids(childPid));\n }\n }\n } catch {\n // No children or pgrep failed\n }\n return descendants;\n}\n\n/**\n * Detect listening ports for a given PID and all its descendants using lsof\n */\nexport function detectPorts(pid: number): number[] {\n try {\n // Get all PIDs to check (the process itself plus all descendants)\n const allPids = [pid, ...getDescendantPids(pid)];\n const pidList = allPids.join(\",\");\n\n // Check all PIDs at once - lsof accepts comma-separated PIDs\n // -P = show port numbers, -n = no DNS resolution\n const output = execSync(\n `lsof -p ${pidList} -P -n 2>/dev/null | grep LISTEN`,\n {\n encoding: \"utf-8\",\n }\n );\n const ports: number[] = [];\n for (const line of output.split(\"\\n\")) {\n // Format: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\n // NAME is like *:3000 or 127.0.0.1:3000 or [::1]:3000\n const match = line.match(/:(\\d+)\\s+\\(LISTEN\\)/);\n if (match) {\n ports.push(parseInt(match[1], 10));\n }\n }\n return [...new Set(ports)].sort((a, b) => a - b); // dedupe and sort ascending\n } catch {\n return [];\n }\n}\n","import { spawn, ChildProcess } from \"node:child_process\";\nimport { openSync } from \"node:fs\";\nimport {\n addProcess,\n getLogPaths,\n ensureDataDir,\n isProcessRunning,\n type ProcessEntry,\n} from \"./registry.js\";\nimport { detectPorts } from \"./ports.js\";\n\nexport interface SpawnResult {\n child: ChildProcess;\n entry: ProcessEntry;\n logPaths: { stdout: string; stderr: string };\n}\n\nexport function spawnProcess(\n name: string,\n command: string[],\n cwd: string,\n timeout?: number,\n): SpawnResult {\n ensureDataDir();\n const logPaths = getLogPaths(name);\n\n const stdoutFd = openSync(logPaths.stdout, \"a\");\n const stderrFd = openSync(logPaths.stderr, \"a\");\n\n const child = spawn(command[0]!, command.slice(1), {\n cwd,\n detached: true,\n stdio: [\"ignore\", stdoutFd, stderrFd],\n });\n\n child.unref();\n\n const entry: ProcessEntry = {\n pid: child.pid!,\n command,\n cwd,\n startedAt: new Date().toISOString(),\n ...(timeout && {\n timeout,\n killAt: new Date(Date.now() + timeout * 1000).toISOString(),\n }),\n };\n\n try {\n addProcess(name, entry);\n } catch (err) {\n try {\n process.kill(child.pid!, \"SIGTERM\");\n } catch {\n // ignore\n }\n throw err;\n }\n\n if (timeout && child.pid) {\n scheduleKill(child.pid, timeout, name);\n }\n\n return { child, entry, logPaths };\n}\n\nexport function buildStatus(\n name: string,\n entry: ProcessEntry,\n extra?: Record<string, unknown>,\n): Record<string, unknown> {\n return {\n name,\n pid: entry.pid,\n cwd: entry.cwd,\n command: entry.command.join(\" \"),\n ...(entry.timeout && { killAt: entry.killAt }),\n ...extra,\n };\n}\n\nexport interface WaitResult {\n ports?: number[];\n error?: string;\n}\n\nexport async function outputStatus(\n status: Record<string, unknown>,\n pid: number,\n logPath: string,\n waitForPort: string | undefined,\n keep: boolean | undefined,\n): Promise<void> {\n if (waitForPort !== undefined) {\n const waitTimeout = waitForPort ? parseInt(waitForPort, 10) : undefined;\n const killOnTimeout = !keep;\n const result = await waitForPortDetection(pid, logPath, waitTimeout, killOnTimeout);\n\n if (result.error) {\n console.error(result.error);\n process.exit(1);\n }\n\n console.log(\n JSON.stringify({\n ...status,\n ports: result.ports,\n port: result.ports![0],\n }),\n );\n } else {\n console.log(JSON.stringify(status));\n }\n}\n\nfunction waitForPortDetection(\n pid: number,\n logPath: string,\n timeoutSeconds?: number,\n killOnTimeout?: boolean,\n): Promise<WaitResult> {\n return new Promise((resolve) => {\n const startTime = Date.now();\n\n const tail = spawn(\"tail\", [\"-f\", logPath], {\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n tail.stdout?.pipe(process.stderr);\n\n const cleanup = (tailProcess: ChildProcess, intervalId: NodeJS.Timeout) => {\n clearInterval(intervalId);\n tailProcess.kill();\n };\n\n const pollInterval = setInterval(() => {\n if (!isProcessRunning(pid)) {\n cleanup(tail, pollInterval);\n resolve({ error: `Process ${pid} died before a port was detected` });\n return;\n }\n\n if (timeoutSeconds !== undefined) {\n const elapsed = (Date.now() - startTime) / 1000;\n if (elapsed >= timeoutSeconds) {\n cleanup(tail, pollInterval);\n if (killOnTimeout) {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // ignore - process may have just died\n }\n resolve({ error: `Timeout: no port detected after ${timeoutSeconds}s (process killed)` });\n } else {\n resolve({ error: `Timeout: no port detected after ${timeoutSeconds}s (process still running)` });\n }\n return;\n }\n }\n\n const ports = detectPorts(pid);\n if (ports.length > 0) {\n cleanup(tail, pollInterval);\n resolve({ ports });\n }\n }, 500);\n });\n}\n\nfunction scheduleKill(pid: number, seconds: number, name: string): void {\n const killer = spawn(\n process.execPath,\n [\n \"-e\",\n `\n setTimeout(() => {\n try {\n process.kill(${pid}, 0);\n process.kill(${pid}, 'SIGTERM');\n console.error('bgproc: ' + process.env.BGPROC_NAME + ' killed after ${seconds}s timeout');\n } catch {}\n process.exit(0);\n }, ${seconds * 1000});\n `,\n ],\n {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env, BGPROC_NAME: name },\n },\n );\n killer.unref();\n}\n","import { defineCommand } from \"citty\";\nimport { getProcess, removeProcess, validateName, isProcessRunning } from \"../registry.js\";\nimport { spawnProcess, buildStatus, outputStatus } from \"../process.js\";\n\nexport const startCommand = defineCommand({\n meta: {\n name: \"start\",\n description:\n \"Start a background process\\n\\nUsage: bgproc start -n <name> [-f] [-t <seconds>] [-w [<seconds>]] [--keep] -- <command...>\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Name for the process\",\n required: true,\n },\n timeout: {\n type: \"string\",\n alias: \"t\",\n description: \"Kill after N seconds\",\n },\n waitForPort: {\n type: \"string\",\n alias: \"w\",\n description: \"Wait for port to be detected (optional: timeout in seconds)\",\n },\n keep: {\n type: \"boolean\",\n description: \"Keep process running on timeout (only with --wait-for-port)\",\n },\n force: {\n type: \"boolean\",\n alias: \"f\",\n description: \"Kill existing process with same name before starting\",\n },\n },\n async run({ args, rawArgs }) {\n const name = args.name;\n\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n if (args.keep && args.waitForPort === undefined) {\n console.error(\"Error: --keep requires --wait-for-port\");\n process.exit(1);\n }\n\n // If --force, kill any existing process with this name\n if (args.force) {\n const existing = getProcess(name);\n if (existing && isProcessRunning(existing.pid)) {\n try {\n process.kill(existing.pid, \"SIGTERM\");\n } catch {\n // ignore - process may have just died\n }\n removeProcess(name);\n }\n }\n\n const timeout = args.timeout ? parseInt(args.timeout, 10) : undefined;\n\n // Get command from rawArgs after \"--\"\n const dashDashIdx = rawArgs.indexOf(\"--\");\n const command = dashDashIdx >= 0 ? rawArgs.slice(dashDashIdx + 1) : [];\n\n if (command.length === 0) {\n console.error(\n \"Error: No command specified. Use: bgproc start -n <name> -- <command>\",\n );\n process.exit(1);\n }\n\n let result;\n try {\n result = spawnProcess(name, command, process.cwd(), timeout);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n\n const status = buildStatus(name, result.entry);\n await outputStatus(status, result.child.pid!, result.logPaths.stdout, args.waitForPort, args.keep);\n },\n});\n","export function formatUptime(startedAt: Date): string {\n const seconds = Math.floor((Date.now() - startedAt.getTime()) / 1000);\n if (seconds < 60) return `${seconds}s`;\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m${seconds % 60}s`;\n const hours = Math.floor(seconds / 3600);\n const mins = Math.floor((seconds % 3600) / 60);\n return `${hours}h${mins}m`;\n}\n","import { defineCommand } from \"citty\";\nimport { getProcess, isProcessRunning, validateName } from \"../registry.js\";\nimport { detectPorts } from \"../ports.js\";\nimport { formatUptime } from \"../utils.js\";\n\nexport const statusCommand = defineCommand({\n meta: {\n name: \"status\",\n description: \"Get status of a background process, including pid and open ports\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Process name\",\n },\n },\n run({ args, rawArgs }) {\n const name = args.name ?? rawArgs[0];\n\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n const entry = getProcess(name);\n\n if (!entry) {\n console.error(`Process '${name}' not found`);\n process.exit(1);\n }\n\n const running = isProcessRunning(entry.pid);\n const ports = running ? detectPorts(entry.pid) : [];\n\n const uptime = running ? formatUptime(new Date(entry.startedAt)) : null;\n\n console.log(\n JSON.stringify({\n name,\n pid: entry.pid,\n running,\n ports,\n port: ports[0] ?? null, // convenience: first port\n cwd: entry.cwd,\n command: entry.command.join(\" \"),\n startedAt: entry.startedAt,\n uptime,\n ...(entry.killAt && { killAt: entry.killAt }),\n }),\n );\n },\n});\n","import { createWriteStream, statSync, readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport type { WriteStream } from \"node:fs\";\n\nconst MAX_LOG_SIZE = 1 * 1024 * 1024; // 1MB\nconst KEEP_SIZE = 512 * 1024; // Keep last 512KB when truncating\n\n/**\n * Create a write stream that caps file size at 1MB\n */\nexport function createCappedWriteStream(path: string): WriteStream {\n const stream = createWriteStream(path, { flags: \"a\" });\n\n // Check and truncate periodically\n let bytesWritten = 0;\n const originalWrite = stream.write.bind(stream);\n\n stream.write = function (chunk: any, ...args: any[]): boolean {\n bytesWritten += Buffer.byteLength(chunk);\n\n // Every ~100KB written, check total size\n if (bytesWritten > 100 * 1024) {\n bytesWritten = 0;\n try {\n const stats = statSync(path);\n if (stats.size > MAX_LOG_SIZE) {\n // Truncate asynchronously - next writes will be to truncated file\n truncateLogFile(path);\n }\n } catch {\n // ignore\n }\n }\n\n return originalWrite(chunk, ...args);\n } as typeof stream.write;\n\n return stream;\n}\n\n/**\n * Truncate log file to keep only the last KEEP_SIZE bytes\n */\nfunction truncateLogFile(path: string): void {\n try {\n const content = readFileSync(path);\n if (content.length > MAX_LOG_SIZE) {\n const kept = content.slice(-KEEP_SIZE);\n // Find first newline to avoid cutting mid-line\n const newlineIdx = kept.indexOf(10); // \\n\n const trimmed = newlineIdx > 0 ? kept.slice(newlineIdx + 1) : kept;\n writeFileSync(path, trimmed);\n }\n } catch {\n // ignore\n }\n}\n\n/**\n * Read the last N lines from a log file\n */\nexport function readLastLines(path: string, n: number): string[] {\n if (!existsSync(path)) return [];\n\n try {\n const content = readFileSync(path, \"utf-8\");\n const lines = content.split(\"\\n\");\n // Remove trailing empty line if present\n if (lines[lines.length - 1] === \"\") {\n lines.pop();\n }\n return lines.slice(-n);\n } catch {\n return [];\n }\n}\n\n/**\n * Read entire log file\n */\nexport function readLog(path: string): string {\n if (!existsSync(path)) return \"\";\n try {\n return readFileSync(path, \"utf-8\");\n } catch {\n return \"\";\n }\n}\n","import { defineCommand } from \"citty\";\nimport { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { getProcess, getLogPaths, validateName } from \"../registry.js\";\nimport { readLastLines, readLog } from \"../logs.js\";\n\nexport const logsCommand = defineCommand({\n meta: {\n name: \"logs\",\n description: \"View logs for a background process\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Process name\",\n },\n tail: {\n type: \"string\",\n alias: \"t\",\n description: \"Number of lines to show (default: 100)\",\n },\n follow: {\n type: \"boolean\",\n alias: \"f\",\n description: \"Follow log output (tail -f)\",\n },\n errors: {\n type: \"boolean\",\n alias: \"e\",\n description: \"Show only stderr\",\n },\n all: {\n type: \"boolean\",\n alias: \"a\",\n description: \"Show all logs (no line limit)\",\n },\n },\n run({ args, rawArgs }) {\n const name = args.name ?? rawArgs[0];\n\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n const entry = getProcess(name);\n\n if (!entry) {\n console.error(`Process '${name}' not found`);\n process.exit(1);\n }\n\n const logPaths = getLogPaths(name);\n const logPath = args.errors ? logPaths.stderr : logPaths.stdout;\n\n if (!existsSync(logPath)) {\n console.error(`No logs found for '${name}'`);\n process.exit(1);\n }\n\n if (args.follow) {\n // Use tail -f for follow mode\n const tail = spawn(\"tail\", [\"-f\", logPath], {\n stdio: \"inherit\",\n });\n\n process.on(\"SIGINT\", () => {\n tail.kill();\n process.exit(0);\n });\n\n return;\n }\n\n if (args.all) {\n const content = readLog(logPath);\n process.stdout.write(content);\n return;\n }\n\n const lines = parseInt(args.tail ?? \"100\", 10);\n const output = readLastLines(logPath, lines);\n console.log(output.join(\"\\n\"));\n },\n});\n","import { defineCommand } from \"citty\";\nimport { getProcess, removeProcess, isProcessRunning, validateName } from \"../registry.js\";\n\nexport const stopCommand = defineCommand({\n meta: {\n name: \"stop\",\n description: \"Stop a background process\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Process name\",\n },\n force: {\n type: \"boolean\",\n alias: \"f\",\n description: \"Force kill (SIGKILL instead of SIGTERM)\",\n },\n },\n run({ args, rawArgs }) {\n const name = args.name ?? rawArgs[0];\n\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n const entry = getProcess(name);\n\n if (!entry) {\n console.error(`Process '${name}' not found`);\n process.exit(1);\n }\n\n const wasRunning = isProcessRunning(entry.pid);\n\n if (!wasRunning) {\n process.stderr.write(\n `Warning: Process '${name}' (PID ${entry.pid}) was already dead\\n`,\n );\n } else {\n const signal = args.force ? \"SIGKILL\" : \"SIGTERM\";\n try {\n process.kill(entry.pid, signal);\n } catch (err) {\n console.error(`Failed to kill process: ${(err as Error).message}`);\n process.exit(1);\n }\n }\n\n removeProcess(name);\n\n console.log(\n JSON.stringify({\n name,\n pid: entry.pid,\n stopped: true,\n wasRunning,\n signal: wasRunning ? (args.force ? \"SIGKILL\" : \"SIGTERM\") : null,\n }),\n );\n },\n});\n","import path from \"node:path\";\nimport { defineCommand } from \"citty\";\nimport { readRegistry, isProcessRunning } from \"../registry.js\";\nimport { detectPorts } from \"../ports.js\";\nimport { formatUptime } from \"../utils.js\";\n\nexport const listCommand = defineCommand({\n meta: {\n name: \"list\",\n description: \"List all background processes\",\n },\n args: {\n cwd: {\n type: \"string\",\n alias: \"c\",\n description: \"Filter by cwd (no arg = current directory)\",\n },\n },\n run({ args }) {\n const registry = readRegistry();\n\n // Handle --cwd with no value: use current directory\n // citty will set it to \"\" if flag present with no value\n let cwdFilter: string | undefined;\n if (args.cwd !== undefined) {\n cwdFilter = args.cwd === \"\" ? process.cwd() : path.resolve(args.cwd);\n }\n\n const entries = Object.entries(registry)\n .filter(([_, entry]) => {\n if (cwdFilter && entry.cwd !== cwdFilter) {\n return false;\n }\n return true;\n })\n .map(([name, entry]) => {\n const running = isProcessRunning(entry.pid);\n const ports = running ? detectPorts(entry.pid) : [];\n\n return {\n name,\n pid: entry.pid,\n running,\n ports,\n port: ports[0] ?? null,\n cwd: entry.cwd,\n command: entry.command.join(\" \"),\n startedAt: entry.startedAt,\n uptime: running ? formatUptime(new Date(entry.startedAt)) : null,\n ...(entry.killAt && { killAt: entry.killAt }),\n };\n });\n\n console.log(JSON.stringify(entries, null, 2));\n },\n});\n","import { defineCommand } from \"citty\";\nimport { unlinkSync, existsSync } from \"node:fs\";\nimport {\n readRegistry,\n removeProcess,\n isProcessRunning,\n getLogPaths,\n validateName,\n} from \"../registry.js\";\n\nexport const cleanCommand = defineCommand({\n meta: {\n name: \"clean\",\n description: \"Remove dead processes and their logs\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Process name\",\n },\n all: {\n type: \"boolean\",\n alias: \"a\",\n description: \"Clean all dead processes\",\n },\n },\n run({ args, rawArgs }) {\n const registry = readRegistry();\n const cleaned: string[] = [];\n const name = args.name ?? rawArgs[0];\n\n if (args.all) {\n // Clean all dead processes\n for (const [procName, entry] of Object.entries(registry)) {\n if (!isProcessRunning(entry.pid)) {\n cleanProcess(procName);\n cleaned.push(procName);\n }\n }\n } else if (name) {\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n const entry = registry[name];\n\n if (!entry) {\n console.error(`Process '${name}' not found`);\n process.exit(1);\n }\n\n if (isProcessRunning(entry.pid)) {\n console.error(\n `Process '${name}' is still running. Use 'bgproc stop ${name}' first.`,\n );\n process.exit(1);\n }\n\n cleanProcess(name);\n cleaned.push(name);\n } else {\n console.error(\"Specify a process name or use --all\");\n process.exit(1);\n }\n\n console.log(\n JSON.stringify({\n cleaned,\n count: cleaned.length,\n }),\n );\n },\n});\n\nfunction cleanProcess(name: string): void {\n removeProcess(name);\n\n const logPaths = getLogPaths(name);\n try {\n if (existsSync(logPaths.stdout)) unlinkSync(logPaths.stdout);\n if (existsSync(logPaths.stderr)) unlinkSync(logPaths.stderr);\n } catch {\n // ignore\n }\n}\n","import { defineCommand } from \"citty\";\nimport { getProcess, removeProcess, validateName, isProcessRunning } from \"../registry.js\";\nimport { spawnProcess, buildStatus, outputStatus } from \"../process.js\";\n\nexport const restartCommand = defineCommand({\n meta: {\n name: \"restart\",\n description:\n \"Restart a background process with the same command and cwd\\n\\nUsage: bgproc restart <name> [-w [<seconds>]] [--keep]\",\n },\n args: {\n name: {\n type: \"string\",\n alias: \"n\",\n description: \"Process name\",\n },\n waitForPort: {\n type: \"string\",\n alias: \"w\",\n description: \"Wait for port to be detected (optional: timeout in seconds)\",\n },\n keep: {\n type: \"boolean\",\n description: \"Keep process running on timeout (only with --wait-for-port)\",\n },\n },\n async run({ args, rawArgs }) {\n const name = args.name ?? rawArgs[0];\n\n try {\n validateName(name);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exit(1);\n }\n\n if (args.keep && args.waitForPort === undefined) {\n console.error(\"Error: --keep requires --wait-for-port\");\n process.exit(1);\n }\n\n const existing = getProcess(name);\n if (!existing) {\n console.error(`Process '${name}' not found`);\n process.exit(1);\n }\n\n // Kill if running\n if (isProcessRunning(existing.pid)) {\n try {\n process.kill(existing.pid, \"SIGTERM\");\n } catch {\n // ignore - process may have just died\n }\n }\n\n removeProcess(name);\n\n const { command, cwd, timeout } = existing;\n const result = spawnProcess(name, command, cwd, timeout);\n\n const status = buildStatus(name, result.entry, { restarted: true });\n await outputStatus(status, result.child.pid!, result.logPaths.stdout, args.waitForPort, args.keep);\n },\n});\n","import { defineCommand, runMain } from \"citty\";\nimport { startCommand } from \"./commands/start.js\";\nimport { statusCommand } from \"./commands/status.js\";\nimport { logsCommand } from \"./commands/logs.js\";\nimport { stopCommand } from \"./commands/stop.js\";\nimport { listCommand } from \"./commands/list.js\";\nimport { cleanCommand } from \"./commands/clean.js\";\nimport { restartCommand } from \"./commands/restart.js\";\n\nconst main = defineCommand({\n meta: {\n name: \"bgproc\",\n version: \"0.1.0\",\n description: \"Simple process manager for agents. All commands output JSON to stdout.\\nExample: bgproc start -n myserver -- npm run dev\",\n },\n subCommands: {\n start: startCommand,\n status: statusCommand,\n logs: logsCommand,\n stop: stopCommand,\n list: listCommand,\n restart: restartCommand,\n clean: cleanCommand,\n },\n});\n\nawait runMain(main);\n"],"mappings":";;;;;;;;;;;AAOA,SAAgB,aAAa,MAAoB;AAC/C,KAAI,CAAC,KACH,OAAM,IAAI,MAAM,wBAAwB;AAE1C,KAAI,CAAC,mBAAmB,KAAK,KAAK,CAChC,OAAM,IAAI,MACR,mFACD;AAEH,KAAI,KAAK,SAAS,GAChB,OAAM,IAAI,MAAM,6CAA6C;;AAiBjE,SAAS,aAAqB;AAC5B,QACE,QAAQ,IAAI,mBAAmB,KAAK,SAAS,EAAE,UAAU,SAAS,SAAS;;AAI/E,SAAS,kBAA0B;AACjC,QAAO,KAAK,YAAY,EAAE,gBAAgB;;AAG5C,SAAgB,aAAqB;AACnC,QAAO,KAAK,YAAY,EAAE,OAAO;;AAGnC,SAAgB,gBAAsB;AACpC,WAAU,YAAY,EAAE,EAAE,WAAW,MAAM,CAAC;AAC5C,WAAU,YAAY,EAAE,EAAE,WAAW,MAAM,CAAC;;AAG9C,SAAgB,eAAyB;AACvC,gBAAe;CACf,MAAM,eAAe,iBAAiB;AACtC,KAAI,CAAC,WAAW,aAAa,CAC3B,QAAO,EAAE;AAEX,KAAI;EACF,MAAM,UAAU,aAAa,cAAc,QAAQ;AACnD,SAAO,KAAK,MAAM,QAAQ;SACpB;AACN,SAAO,EAAE;;;AAIb,SAAgB,cAAc,UAA0B;AACtD,gBAAe;AACf,eAAc,iBAAiB,EAAE,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC;;AAGrE,SAAgB,WAAW,MAAc,OAA2B;CAClE,MAAM,WAAW,cAAc;CAC/B,MAAM,WAAW,SAAS;AAE1B,KAAI,UAAU;AACZ,MAAI,iBAAiB,SAAS,IAAI,CAChC,OAAM,IAAI,MACR,YAAY,KAAK,4BAA4B,SAAS,IAAI,4BAC3D;EAGH,MAAM,WAAW,YAAY,KAAK;AAClC,MAAI;AACF,OAAI,WAAW,SAAS,OAAO,CAAE,YAAW,SAAS,OAAO;AAC5D,OAAI,WAAW,SAAS,OAAO,CAAE,YAAW,SAAS,OAAO;UACtD;;AAKV,UAAS,QAAQ;AACjB,eAAc,SAAS;;AAGzB,SAAgB,cAAc,MAAoB;CAChD,MAAM,WAAW,cAAc;AAC/B,QAAO,SAAS;AAChB,eAAc,SAAS;;AAGzB,SAAgB,WAAW,MAAwC;AAEjE,QADiB,cAAc,CACf;;AAGlB,SAAgB,iBAAiB,KAAsB;AACrD,KAAI;AACF,UAAQ,KAAK,KAAK,EAAE;AACpB,SAAO;SACD;AACN,SAAO;;;AAIX,SAAgB,YAAY,MAAkD;AAC5E,QAAO;EACL,QAAQ,KAAK,YAAY,EAAE,GAAG,KAAK,aAAa;EAChD,QAAQ,KAAK,YAAY,EAAE,GAAG,KAAK,aAAa;EACjD;;;;;;;;ACnHH,SAAS,kBAAkB,KAAuB;CAChD,MAAM,cAAwB,EAAE;AAChC,KAAI;EAEF,MAAM,SAAS,SAAS,YAAY,IAAI,eAAe,EACrD,UAAU,SACX,CAAC;AACF,OAAK,MAAM,QAAQ,OAAO,MAAM,CAAC,MAAM,KAAK,EAAE;GAC5C,MAAM,WAAW,SAAS,MAAM,GAAG;AACnC,OAAI,CAAC,MAAM,SAAS,EAAE;AACpB,gBAAY,KAAK,SAAS;AAE1B,gBAAY,KAAK,GAAG,kBAAkB,SAAS,CAAC;;;SAG9C;AAGR,QAAO;;;;;AAMT,SAAgB,YAAY,KAAuB;AACjD,KAAI;EAOF,MAAM,SAAS,SACb,WANc,CAAC,KAAK,GAAG,kBAAkB,IAAI,CAAC,CACxB,KAAK,IAAI,CAKZ,mCACnB,EACE,UAAU,SACX,CACF;EACD,MAAM,QAAkB,EAAE;AAC1B,OAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,EAAE;GAGrC,MAAM,QAAQ,KAAK,MAAM,sBAAsB;AAC/C,OAAI,MACF,OAAM,KAAK,SAAS,MAAM,IAAI,GAAG,CAAC;;AAGtC,SAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE;SAC1C;AACN,SAAO,EAAE;;;;;;ACrCb,SAAgB,aACd,MACA,SACA,KACA,SACa;AACb,gBAAe;CACf,MAAM,WAAW,YAAY,KAAK;CAElC,MAAM,WAAW,SAAS,SAAS,QAAQ,IAAI;CAC/C,MAAM,WAAW,SAAS,SAAS,QAAQ,IAAI;CAE/C,MAAM,QAAQ,MAAM,QAAQ,IAAK,QAAQ,MAAM,EAAE,EAAE;EACjD;EACA,UAAU;EACV,OAAO;GAAC;GAAU;GAAU;GAAS;EACtC,CAAC;AAEF,OAAM,OAAO;CAEb,MAAM,QAAsB;EAC1B,KAAK,MAAM;EACX;EACA;EACA,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,GAAI,WAAW;GACb;GACA,QAAQ,IAAI,KAAK,KAAK,KAAK,GAAG,UAAU,IAAK,CAAC,aAAa;GAC5D;EACF;AAED,KAAI;AACF,aAAW,MAAM,MAAM;UAChB,KAAK;AACZ,MAAI;AACF,WAAQ,KAAK,MAAM,KAAM,UAAU;UAC7B;AAGR,QAAM;;AAGR,KAAI,WAAW,MAAM,IACnB,cAAa,MAAM,KAAK,SAAS,KAAK;AAGxC,QAAO;EAAE;EAAO;EAAO;EAAU;;AAGnC,SAAgB,YACd,MACA,OACA,OACyB;AACzB,QAAO;EACL;EACA,KAAK,MAAM;EACX,KAAK,MAAM;EACX,SAAS,MAAM,QAAQ,KAAK,IAAI;EAChC,GAAI,MAAM,WAAW,EAAE,QAAQ,MAAM,QAAQ;EAC7C,GAAG;EACJ;;AAQH,eAAsB,aACpB,QACA,KACA,SACA,aACA,MACe;AACf,KAAI,gBAAgB,QAAW;EAG7B,MAAM,SAAS,MAAM,qBAAqB,KAAK,SAF3B,cAAc,SAAS,aAAa,GAAG,GAAG,QACxC,CAAC,KAC4D;AAEnF,MAAI,OAAO,OAAO;AAChB,WAAQ,MAAM,OAAO,MAAM;AAC3B,WAAQ,KAAK,EAAE;;AAGjB,UAAQ,IACN,KAAK,UAAU;GACb,GAAG;GACH,OAAO,OAAO;GACd,MAAM,OAAO,MAAO;GACrB,CAAC,CACH;OAED,SAAQ,IAAI,KAAK,UAAU,OAAO,CAAC;;AAIvC,SAAS,qBACP,KACA,SACA,gBACA,eACqB;AACrB,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,YAAY,KAAK,KAAK;EAE5B,MAAM,OAAO,MAAM,QAAQ,CAAC,MAAM,QAAQ,EAAE,EAC1C,OAAO;GAAC;GAAU;GAAQ;GAAS,EACpC,CAAC;AACF,OAAK,QAAQ,KAAK,QAAQ,OAAO;EAEjC,MAAM,WAAW,aAA2B,eAA+B;AACzE,iBAAc,WAAW;AACzB,eAAY,MAAM;;EAGpB,MAAM,eAAe,kBAAkB;AACrC,OAAI,CAAC,iBAAiB,IAAI,EAAE;AAC1B,YAAQ,MAAM,aAAa;AAC3B,YAAQ,EAAE,OAAO,WAAW,IAAI,mCAAmC,CAAC;AACpE;;AAGF,OAAI,mBAAmB,QAErB;SADiB,KAAK,KAAK,GAAG,aAAa,OAC5B,gBAAgB;AAC7B,aAAQ,MAAM,aAAa;AAC3B,SAAI,eAAe;AACjB,UAAI;AACF,eAAQ,KAAK,KAAK,UAAU;cACtB;AAGR,cAAQ,EAAE,OAAO,mCAAmC,eAAe,qBAAqB,CAAC;WAEzF,SAAQ,EAAE,OAAO,mCAAmC,eAAe,4BAA4B,CAAC;AAElG;;;GAIJ,MAAM,QAAQ,YAAY,IAAI;AAC9B,OAAI,MAAM,SAAS,GAAG;AACpB,YAAQ,MAAM,aAAa;AAC3B,YAAQ,EAAE,OAAO,CAAC;;KAEnB,IAAI;GACP;;AAGJ,SAAS,aAAa,KAAa,SAAiB,MAAoB;AAsBtE,CArBe,MACb,QAAQ,UACR,CACE,MACA;;;yBAGmB,IAAI;yBACJ,IAAI;gFACmD,QAAQ;;;WAG7E,UAAU,IAAK;QAErB,EACD;EACE,UAAU;EACV,OAAO;EACP,KAAK;GAAE,GAAG,QAAQ;GAAK,aAAa;GAAM;EAC3C,CACF,CACM,OAAO;;;;;AC1LhB,MAAa,eAAe,cAAc;CACxC,MAAM;EACJ,MAAM;EACN,aACE;EACH;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACb,UAAU;GACX;EACD,SAAS;GACP,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,aAAa;GACX,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,MAAM;GACJ,MAAM;GACN,aAAa;GACd;EACD,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,MAAM,IAAI,EAAE,MAAM,WAAW;EAC3B,MAAM,OAAO,KAAK;AAElB,MAAI;AACF,gBAAa,KAAK;WACX,KAAK;AACZ,WAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,WAAQ,KAAK,EAAE;;AAGjB,MAAI,KAAK,QAAQ,KAAK,gBAAgB,QAAW;AAC/C,WAAQ,MAAM,yCAAyC;AACvD,WAAQ,KAAK,EAAE;;AAIjB,MAAI,KAAK,OAAO;GACd,MAAM,WAAW,WAAW,KAAK;AACjC,OAAI,YAAY,iBAAiB,SAAS,IAAI,EAAE;AAC9C,QAAI;AACF,aAAQ,KAAK,SAAS,KAAK,UAAU;YAC/B;AAGR,kBAAc,KAAK;;;EAIvB,MAAM,UAAU,KAAK,UAAU,SAAS,KAAK,SAAS,GAAG,GAAG;EAG5D,MAAM,cAAc,QAAQ,QAAQ,KAAK;EACzC,MAAM,UAAU,eAAe,IAAI,QAAQ,MAAM,cAAc,EAAE,GAAG,EAAE;AAEtE,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAQ,MACN,wEACD;AACD,WAAQ,KAAK,EAAE;;EAGjB,IAAI;AACJ,MAAI;AACF,YAAS,aAAa,MAAM,SAAS,QAAQ,KAAK,EAAE,QAAQ;WACrD,KAAK;AACZ,WAAQ,MAAO,IAAc,QAAQ;AACrC,WAAQ,KAAK,EAAE;;AAIjB,QAAM,aADS,YAAY,MAAM,OAAO,MAAM,EACnB,OAAO,MAAM,KAAM,OAAO,SAAS,QAAQ,KAAK,aAAa,KAAK,KAAK;;CAErG,CAAC;;;;ACzFF,SAAgB,aAAa,WAAyB;CACpD,MAAM,UAAU,KAAK,OAAO,KAAK,KAAK,GAAG,UAAU,SAAS,IAAI,IAAK;AACrE,KAAI,UAAU,GAAI,QAAO,GAAG,QAAQ;AACpC,KAAI,UAAU,KAAM,QAAO,GAAG,KAAK,MAAM,UAAU,GAAG,CAAC,GAAG,UAAU,GAAG;AAGvE,QAAO,GAFO,KAAK,MAAM,UAAU,KAAK,CAExB,GADH,KAAK,MAAO,UAAU,OAAQ,GAAG,CACtB;;;;;ACD1B,MAAa,gBAAgB,cAAc;CACzC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,EACJ,MAAM;EACJ,MAAM;EACN,OAAO;EACP,aAAa;EACd,EACF;CACD,IAAI,EAAE,MAAM,WAAW;EACrB,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,MAAI;AACF,gBAAa,KAAK;WACX,KAAK;AACZ,WAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,WAAQ,KAAK,EAAE;;EAGjB,MAAM,QAAQ,WAAW,KAAK;AAE9B,MAAI,CAAC,OAAO;AACV,WAAQ,MAAM,YAAY,KAAK,aAAa;AAC5C,WAAQ,KAAK,EAAE;;EAGjB,MAAM,UAAU,iBAAiB,MAAM,IAAI;EAC3C,MAAM,QAAQ,UAAU,YAAY,MAAM,IAAI,GAAG,EAAE;EAEnD,MAAM,SAAS,UAAU,aAAa,IAAI,KAAK,MAAM,UAAU,CAAC,GAAG;AAEnE,UAAQ,IACN,KAAK,UAAU;GACb;GACA,KAAK,MAAM;GACX;GACA;GACA,MAAM,MAAM,MAAM;GAClB,KAAK,MAAM;GACX,SAAS,MAAM,QAAQ,KAAK,IAAI;GAChC,WAAW,MAAM;GACjB;GACA,GAAI,MAAM,UAAU,EAAE,QAAQ,MAAM,QAAQ;GAC7C,CAAC,CACH;;CAEJ,CAAC;;;;ACnDF,MAAM,eAAe,IAAI,OAAO;AAChC,MAAM,YAAY,MAAM;;;;AAwDxB,SAAgB,cAAc,MAAc,GAAqB;AAC/D,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO,EAAE;AAEhC,KAAI;EAEF,MAAM,QADU,aAAa,MAAM,QAAQ,CACrB,MAAM,KAAK;AAEjC,MAAI,MAAM,MAAM,SAAS,OAAO,GAC9B,OAAM,KAAK;AAEb,SAAO,MAAM,MAAM,CAAC,EAAE;SAChB;AACN,SAAO,EAAE;;;;;;AAOb,SAAgB,QAAQ,MAAsB;AAC5C,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;AAC9B,KAAI;AACF,SAAO,aAAa,MAAM,QAAQ;SAC5B;AACN,SAAO;;;;;;AC9EX,MAAa,cAAc,cAAc;CACvC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,QAAQ;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,QAAQ;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,KAAK;GACH,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,IAAI,EAAE,MAAM,WAAW;EACrB,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,MAAI;AACF,gBAAa,KAAK;WACX,KAAK;AACZ,WAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,WAAQ,KAAK,EAAE;;AAKjB,MAAI,CAFU,WAAW,KAAK,EAElB;AACV,WAAQ,MAAM,YAAY,KAAK,aAAa;AAC5C,WAAQ,KAAK,EAAE;;EAGjB,MAAM,WAAW,YAAY,KAAK;EAClC,MAAM,UAAU,KAAK,SAAS,SAAS,SAAS,SAAS;AAEzD,MAAI,CAAC,WAAW,QAAQ,EAAE;AACxB,WAAQ,MAAM,sBAAsB,KAAK,GAAG;AAC5C,WAAQ,KAAK,EAAE;;AAGjB,MAAI,KAAK,QAAQ;GAEf,MAAM,OAAO,MAAM,QAAQ,CAAC,MAAM,QAAQ,EAAE,EAC1C,OAAO,WACR,CAAC;AAEF,WAAQ,GAAG,gBAAgB;AACzB,SAAK,MAAM;AACX,YAAQ,KAAK,EAAE;KACf;AAEF;;AAGF,MAAI,KAAK,KAAK;GACZ,MAAM,UAAU,QAAQ,QAAQ;AAChC,WAAQ,OAAO,MAAM,QAAQ;AAC7B;;EAIF,MAAM,SAAS,cAAc,SADf,SAAS,KAAK,QAAQ,OAAO,GAAG,CACF;AAC5C,UAAQ,IAAI,OAAO,KAAK,KAAK,CAAC;;CAEjC,CAAC;;;;ACpFF,MAAa,cAAc,cAAc;CACvC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,IAAI,EAAE,MAAM,WAAW;EACrB,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,MAAI;AACF,gBAAa,KAAK;WACX,KAAK;AACZ,WAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,WAAQ,KAAK,EAAE;;EAGjB,MAAM,QAAQ,WAAW,KAAK;AAE9B,MAAI,CAAC,OAAO;AACV,WAAQ,MAAM,YAAY,KAAK,aAAa;AAC5C,WAAQ,KAAK,EAAE;;EAGjB,MAAM,aAAa,iBAAiB,MAAM,IAAI;AAE9C,MAAI,CAAC,WACH,SAAQ,OAAO,MACb,qBAAqB,KAAK,SAAS,MAAM,IAAI,sBAC9C;OACI;GACL,MAAM,SAAS,KAAK,QAAQ,YAAY;AACxC,OAAI;AACF,YAAQ,KAAK,MAAM,KAAK,OAAO;YACxB,KAAK;AACZ,YAAQ,MAAM,2BAA4B,IAAc,UAAU;AAClE,YAAQ,KAAK,EAAE;;;AAInB,gBAAc,KAAK;AAEnB,UAAQ,IACN,KAAK,UAAU;GACb;GACA,KAAK,MAAM;GACX,SAAS;GACT;GACA,QAAQ,aAAc,KAAK,QAAQ,YAAY,YAAa;GAC7D,CAAC,CACH;;CAEJ,CAAC;;;;AC3DF,MAAa,cAAc,cAAc;CACvC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,EACJ,KAAK;EACH,MAAM;EACN,OAAO;EACP,aAAa;EACd,EACF;CACD,IAAI,EAAE,QAAQ;EACZ,MAAM,WAAW,cAAc;EAI/B,IAAI;AACJ,MAAI,KAAK,QAAQ,OACf,aAAY,KAAK,QAAQ,KAAK,QAAQ,KAAK,GAAG,KAAK,QAAQ,KAAK,IAAI;EAGtE,MAAM,UAAU,OAAO,QAAQ,SAAS,CACrC,QAAQ,CAAC,GAAG,WAAW;AACtB,OAAI,aAAa,MAAM,QAAQ,UAC7B,QAAO;AAET,UAAO;IACP,CACD,KAAK,CAAC,MAAM,WAAW;GACtB,MAAM,UAAU,iBAAiB,MAAM,IAAI;GAC3C,MAAM,QAAQ,UAAU,YAAY,MAAM,IAAI,GAAG,EAAE;AAEnD,UAAO;IACL;IACA,KAAK,MAAM;IACX;IACA;IACA,MAAM,MAAM,MAAM;IAClB,KAAK,MAAM;IACX,SAAS,MAAM,QAAQ,KAAK,IAAI;IAChC,WAAW,MAAM;IACjB,QAAQ,UAAU,aAAa,IAAI,KAAK,MAAM,UAAU,CAAC,GAAG;IAC5D,GAAI,MAAM,UAAU,EAAE,QAAQ,MAAM,QAAQ;IAC7C;IACD;AAEJ,UAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;;CAEhD,CAAC;;;;AC7CF,MAAa,eAAe,cAAc;CACxC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,KAAK;GACH,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,IAAI,EAAE,MAAM,WAAW;EACrB,MAAM,WAAW,cAAc;EAC/B,MAAM,UAAoB,EAAE;EAC5B,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,MAAI,KAAK,KAEP;QAAK,MAAM,CAAC,UAAU,UAAU,OAAO,QAAQ,SAAS,CACtD,KAAI,CAAC,iBAAiB,MAAM,IAAI,EAAE;AAChC,iBAAa,SAAS;AACtB,YAAQ,KAAK,SAAS;;aAGjB,MAAM;AACf,OAAI;AACF,iBAAa,KAAK;YACX,KAAK;AACZ,YAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,YAAQ,KAAK,EAAE;;GAGjB,MAAM,QAAQ,SAAS;AAEvB,OAAI,CAAC,OAAO;AACV,YAAQ,MAAM,YAAY,KAAK,aAAa;AAC5C,YAAQ,KAAK,EAAE;;AAGjB,OAAI,iBAAiB,MAAM,IAAI,EAAE;AAC/B,YAAQ,MACN,YAAY,KAAK,uCAAuC,KAAK,UAC9D;AACD,YAAQ,KAAK,EAAE;;AAGjB,gBAAa,KAAK;AAClB,WAAQ,KAAK,KAAK;SACb;AACL,WAAQ,MAAM,sCAAsC;AACpD,WAAQ,KAAK,EAAE;;AAGjB,UAAQ,IACN,KAAK,UAAU;GACb;GACA,OAAO,QAAQ;GAChB,CAAC,CACH;;CAEJ,CAAC;AAEF,SAAS,aAAa,MAAoB;AACxC,eAAc,KAAK;CAEnB,MAAM,WAAW,YAAY,KAAK;AAClC,KAAI;AACF,MAAI,WAAW,SAAS,OAAO,CAAE,YAAW,SAAS,OAAO;AAC5D,MAAI,WAAW,SAAS,OAAO,CAAE,YAAW,SAAS,OAAO;SACtD;;;;;ACjFV,MAAa,iBAAiB,cAAc;CAC1C,MAAM;EACJ,MAAM;EACN,aACE;EACH;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,aAAa;GACX,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,MAAM;GACJ,MAAM;GACN,aAAa;GACd;EACF;CACD,MAAM,IAAI,EAAE,MAAM,WAAW;EAC3B,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,MAAI;AACF,gBAAa,KAAK;WACX,KAAK;AACZ,WAAQ,MAAM,UAAW,IAAc,UAAU;AACjD,WAAQ,KAAK,EAAE;;AAGjB,MAAI,KAAK,QAAQ,KAAK,gBAAgB,QAAW;AAC/C,WAAQ,MAAM,yCAAyC;AACvD,WAAQ,KAAK,EAAE;;EAGjB,MAAM,WAAW,WAAW,KAAK;AACjC,MAAI,CAAC,UAAU;AACb,WAAQ,MAAM,YAAY,KAAK,aAAa;AAC5C,WAAQ,KAAK,EAAE;;AAIjB,MAAI,iBAAiB,SAAS,IAAI,CAChC,KAAI;AACF,WAAQ,KAAK,SAAS,KAAK,UAAU;UAC/B;AAKV,gBAAc,KAAK;EAEnB,MAAM,EAAE,SAAS,KAAK,YAAY;EAClC,MAAM,SAAS,aAAa,MAAM,SAAS,KAAK,QAAQ;AAGxD,QAAM,aADS,YAAY,MAAM,OAAO,OAAO,EAAE,WAAW,MAAM,CAAC,EACxC,OAAO,MAAM,KAAM,OAAO,SAAS,QAAQ,KAAK,aAAa,KAAK,KAAK;;CAErG,CAAC;;;;ACtCF,MAAM,QAjBO,cAAc;CACzB,MAAM;EACJ,MAAM;EACN,SAAS;EACT,aAAa;EACd;CACD,aAAa;EACX,OAAO;EACP,QAAQ;EACR,MAAM;EACN,MAAM;EACN,MAAM;EACN,SAAS;EACT,OAAO;EACR;CACF,CAAC,CAEiB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bgproc",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Simple process manager for agents",
5
5
  "keywords": [
6
6
  "background",