svamp-cli 0.1.85 → 0.1.87

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.
@@ -1,593 +0,0 @@
1
- import { writeFileSync, readFileSync } from 'fs';
2
- import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-DJo7kCIf.mjs';
4
- import 'node:fs';
5
- import 'node:child_process';
6
- import 'node:path';
7
- import 'node:os';
8
- import './run-D0PoM5_v.mjs';
9
- import 'os';
10
- import 'fs/promises';
11
- import 'url';
12
- import 'child_process';
13
- import 'crypto';
14
- import 'node:crypto';
15
- import '@agentclientprotocol/sdk';
16
- import '@modelcontextprotocol/sdk/client/index.js';
17
- import '@modelcontextprotocol/sdk/client/stdio.js';
18
- import '@modelcontextprotocol/sdk/types.js';
19
- import 'zod';
20
- import 'node:fs/promises';
21
- import 'node:util';
22
-
23
- function getFlag(args, flag) {
24
- const idx = args.indexOf(flag);
25
- return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : void 0;
26
- }
27
- function getAllFlags(args, flag) {
28
- const values = [];
29
- for (let i = 0; i < args.length; i++) {
30
- if (args[i] === flag && i + 1 < args.length) {
31
- values.push(args[i + 1]);
32
- i++;
33
- }
34
- }
35
- return values;
36
- }
37
- function hasFlag(args, ...flags) {
38
- return flags.some((f) => args.includes(f));
39
- }
40
- function splitOnDoubleDash(args) {
41
- const sep = args.indexOf("--");
42
- if (sep === -1) return { flagArgs: args, cmdArgs: [] };
43
- return { flagArgs: args.slice(0, sep), cmdArgs: args.slice(sep + 1) };
44
- }
45
- function parseIntFlag(args, flag, defaultVal) {
46
- const raw = getFlag(args, flag);
47
- if (raw === void 0) return defaultVal;
48
- const n = parseInt(raw, 10);
49
- if (isNaN(n) || n < 0) {
50
- console.error(`Error: ${flag} must be a non-negative integer`);
51
- process.exit(1);
52
- }
53
- return n;
54
- }
55
- function parseIntFlagOpt(args, flag) {
56
- const raw = getFlag(args, flag);
57
- if (raw === void 0) return void 0;
58
- const n = parseInt(raw, 10);
59
- if (isNaN(n) || n < 0) {
60
- console.error(`Error: ${flag} must be a non-negative integer`);
61
- process.exit(1);
62
- }
63
- return n;
64
- }
65
- async function loadYaml() {
66
- try {
67
- return await import('yaml');
68
- } catch {
69
- console.error("Error: YAML support requires the `yaml` package. Run: yarn workspace svamp-cli add yaml");
70
- process.exit(1);
71
- }
72
- }
73
- async function readSpecFile(filePath) {
74
- const absPath = resolve(filePath);
75
- let raw;
76
- try {
77
- raw = readFileSync(absPath, "utf-8");
78
- } catch (err) {
79
- console.error(`Error reading spec file '${filePath}': ${err.message}`);
80
- process.exit(1);
81
- }
82
- let parsed;
83
- if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
84
- const yaml = await loadYaml();
85
- try {
86
- parsed = yaml.parse(raw);
87
- } catch (err) {
88
- console.error(`Error parsing YAML: ${err.message}`);
89
- process.exit(1);
90
- }
91
- } else {
92
- try {
93
- parsed = JSON.parse(raw);
94
- } catch (err) {
95
- console.error(`Error parsing JSON: ${err.message}`);
96
- process.exit(1);
97
- }
98
- }
99
- return normalizeSpec(parsed);
100
- }
101
- async function writeSpecFile(filePath, spec) {
102
- const isYaml = filePath.endsWith(".yaml") || filePath.endsWith(".yml");
103
- const yaml = await loadYaml();
104
- const exportSpec = {
105
- name: spec.name,
106
- command: spec.command,
107
- args: spec.args,
108
- workdir: spec.workdir,
109
- keepAlive: spec.keepAlive,
110
- maxRestarts: spec.maxRestarts,
111
- restartDelay: spec.restartDelay,
112
- ...spec.env && Object.keys(spec.env).length > 0 ? { env: spec.env } : {},
113
- ...spec.probe ? { probe: spec.probe } : {},
114
- ...spec.ttl !== void 0 ? { ttl: spec.ttl } : {},
115
- ...spec.serviceGroup ? { serviceGroup: spec.serviceGroup } : {},
116
- ...spec.ports ? { ports: spec.ports } : {}
117
- };
118
- const content = isYaml ? `# svamp process spec \u2014 apply with: svamp process apply ${filePath}
119
- ${yaml.stringify(exportSpec)}` : JSON.stringify(exportSpec, null, 2);
120
- writeFileSync(filePath, content, "utf-8");
121
- }
122
- function specToYaml(spec, yaml) {
123
- const exportSpec = {
124
- name: spec.name,
125
- command: spec.command,
126
- args: spec.args,
127
- workdir: spec.workdir,
128
- keepAlive: spec.keepAlive
129
- };
130
- if (spec.maxRestarts) exportSpec.maxRestarts = spec.maxRestarts;
131
- if (spec.restartDelay !== 2) exportSpec.restartDelay = spec.restartDelay;
132
- if (spec.env && Object.keys(spec.env).length > 0) exportSpec.env = spec.env;
133
- if (spec.probe) exportSpec.probe = spec.probe;
134
- if (spec.ttl !== void 0) exportSpec.ttl = spec.ttl;
135
- if (spec.serviceGroup) exportSpec.serviceGroup = spec.serviceGroup;
136
- if (spec.ports) exportSpec.ports = spec.ports;
137
- return yaml.stringify(exportSpec);
138
- }
139
- function normalizeSpec(raw) {
140
- if (!raw || typeof raw !== "object") {
141
- console.error("Error: spec must be an object");
142
- process.exit(1);
143
- }
144
- if (!raw.name || typeof raw.name !== "string") {
145
- console.error("Error: spec.name is required");
146
- process.exit(1);
147
- }
148
- if (!raw.command || typeof raw.command !== "string") {
149
- console.error("Error: spec.command is required");
150
- process.exit(1);
151
- }
152
- return {
153
- name: raw.name,
154
- command: raw.command,
155
- args: Array.isArray(raw.args) ? raw.args.map(String) : [],
156
- workdir: typeof raw.workdir === "string" ? raw.workdir : process.cwd(),
157
- env: raw.env && typeof raw.env === "object" ? raw.env : void 0,
158
- probe: raw.probe ? {
159
- type: "http",
160
- port: Number(raw.probe.port),
161
- path: raw.probe.path ?? "/",
162
- interval: raw.probe.interval !== void 0 ? Number(raw.probe.interval) : void 0,
163
- timeout: raw.probe.timeout !== void 0 ? Number(raw.probe.timeout) : void 0,
164
- failureThreshold: raw.probe.failureThreshold !== void 0 ? Number(raw.probe.failureThreshold) : void 0
165
- } : void 0,
166
- keepAlive: Boolean(raw.keepAlive ?? false),
167
- maxRestarts: Number(raw.maxRestarts ?? 0),
168
- restartDelay: Number(raw.restartDelay ?? 2),
169
- ttl: raw.ttl !== void 0 ? Number(raw.ttl) : void 0,
170
- serviceGroup: raw.serviceGroup,
171
- ports: Array.isArray(raw.ports) ? raw.ports.map(Number) : void 0
172
- };
173
- }
174
- function statusIcon(status) {
175
- switch (status) {
176
- case "running":
177
- return "\u25CF";
178
- case "starting":
179
- return "\u25CB";
180
- case "stopped":
181
- return "\u25A1";
182
- case "failed":
183
- return "\u2716";
184
- case "expired":
185
- return "\u231B";
186
- case "stopping":
187
- return "\u2193";
188
- default:
189
- return "?";
190
- }
191
- }
192
- function formatUptime(startedAt) {
193
- if (!startedAt) return "";
194
- const s = Math.floor((Date.now() - startedAt) / 1e3);
195
- if (s < 60) return `${s}s`;
196
- if (s < 3600) return `${Math.floor(s / 60)}m${s % 60}s`;
197
- return `${Math.floor(s / 3600)}h${Math.floor(s % 3600 / 60)}m`;
198
- }
199
- function formatTTL(spec) {
200
- if (spec.ttl === void 0) return "";
201
- const remaining = Math.floor((spec.createdAt + spec.ttl * 1e3 - Date.now()) / 1e3);
202
- if (remaining <= 0) return "expired";
203
- if (remaining < 60) return `ttl:${remaining}s`;
204
- if (remaining < 3600) return `ttl:${Math.floor(remaining / 60)}m`;
205
- return `ttl:${Math.floor(remaining / 3600)}h`;
206
- }
207
- function printProcessTable(processes) {
208
- if (processes.length === 0) {
209
- console.log("No supervised processes.");
210
- return;
211
- }
212
- const cols = ["NAME", "STATUS", "PID", "RESTARTS", "UPTIME", "PROBE", "TTL"];
213
- const widths = [24, 12, 8, 10, 10, 8, 8];
214
- const header = cols.map((c, i) => c.padEnd(widths[i])).join(" ");
215
- console.log(header);
216
- console.log("\u2500".repeat(header.length));
217
- for (const { spec, state } of processes) {
218
- const row = [
219
- spec.name.padEnd(widths[0]),
220
- `${statusIcon(state.status)} ${state.status}`.padEnd(widths[1]),
221
- (state.pid?.toString() ?? "\u2014").padEnd(widths[2]),
222
- state.restartCount.toString().padEnd(widths[3]),
223
- formatUptime(state.startedAt).padEnd(widths[4]),
224
- (spec.probe ? state.lastProbe ? state.lastProbe.ok ? "ok" : "fail" : "wait" : "\u2014").padEnd(widths[5]),
225
- formatTTL(spec).padEnd(widths[6])
226
- ];
227
- console.log(row.join(" "));
228
- }
229
- }
230
- function printProcessDetail(info) {
231
- const { spec, state } = info;
232
- console.log(`
233
- Process: ${spec.name}`);
234
- console.log(` ID: ${spec.id}`);
235
- console.log(` Status: ${statusIcon(state.status)} ${state.status}${state.pid ? ` (pid ${state.pid})` : ""}`);
236
- console.log(` Command: ${spec.command} ${spec.args.join(" ")}`);
237
- console.log(` Workdir: ${spec.workdir}`);
238
- console.log(` Keep-alive: ${spec.keepAlive}${spec.maxRestarts > 0 ? ` (max ${spec.maxRestarts})` : ""}`);
239
- if (state.restartCount > 0) console.log(` Restarts: ${state.restartCount}`);
240
- if (state.startedAt) console.log(` Uptime: ${formatUptime(state.startedAt)}`);
241
- if (spec.probe) {
242
- const p = spec.probe;
243
- const lp = state.lastProbe;
244
- const result = lp ? lp.ok ? `OK (HTTP ${lp.statusCode})` : `FAIL (${lp.error ?? `HTTP ${lp.statusCode}`})` : "waiting...";
245
- console.log(` Probe: http://localhost:${p.port}${p.path ?? "/"} [${result}]`);
246
- if (state.consecutiveProbeFailures > 0) {
247
- console.log(` \u26A0 ${state.consecutiveProbeFailures} consecutive failure(s)`);
248
- }
249
- }
250
- if (spec.ttl !== void 0) console.log(` TTL: ${spec.ttl}s (${formatTTL(spec) || "no expiry"})`);
251
- if (spec.serviceGroup) console.log(` Service: ${spec.serviceGroup} port(s): ${spec.ports?.join(", ")}`);
252
- if (spec.env && Object.keys(spec.env).length > 0) {
253
- console.log(` Env: ${Object.entries(spec.env).map(([k, v]) => `${k}=${v}`).join(", ")}`);
254
- }
255
- console.log(` Created: ${new Date(spec.createdAt).toLocaleString()}`);
256
- }
257
- async function processCommand(args, machineId) {
258
- const sub = args[0];
259
- if (!sub || sub === "help" || sub === "--help" || sub === "-h") {
260
- printHelp();
261
- return;
262
- }
263
- const subArgs = args.slice(1);
264
- switch (sub) {
265
- case "apply":
266
- return applyCommand(subArgs, machineId);
267
- case "list":
268
- case "ls":
269
- return listCommand(subArgs, machineId);
270
- case "get":
271
- return getCommand(subArgs, machineId);
272
- case "start":
273
- return startCommand(subArgs, machineId);
274
- case "stop":
275
- return stopCommand(subArgs, machineId);
276
- case "restart":
277
- return restartCommand(subArgs, machineId);
278
- case "delete":
279
- case "remove":
280
- case "rm":
281
- return deleteCommand(subArgs, machineId);
282
- case "logs":
283
- case "log":
284
- return logsCommand(subArgs, machineId);
285
- default:
286
- if (sub.endsWith(".yaml") || sub.endsWith(".yml") || sub.endsWith(".json")) {
287
- return applyCommand(args, machineId);
288
- }
289
- console.error(`Unknown process subcommand: '${sub}'`);
290
- printHelp();
291
- process.exit(1);
292
- }
293
- }
294
- async function applyCommand(args, machineId) {
295
- const filePath = args.find((a) => !a.startsWith("-"));
296
- if (!filePath) {
297
- console.error("Error: spec file required");
298
- console.error("Usage: svamp process apply <file.yaml>");
299
- process.exit(1);
300
- }
301
- const save = hasFlag(args, "--save");
302
- const spec = await readSpecFile(filePath);
303
- const { server, machine } = await connectAndGetMachine(machineId);
304
- try {
305
- const result = await machine.processApply({ spec, _rkwargs: true });
306
- const { action, info } = result;
307
- if (action === "no-change") {
308
- console.log(`process/${info.spec.name} unchanged`);
309
- } else if (action === "created") {
310
- console.log(`process/${info.spec.name} created`);
311
- } else {
312
- console.log(`process/${info.spec.name} configured`);
313
- }
314
- if (info.state.pid) {
315
- console.log(` pid: ${info.state.pid}, status: ${info.state.status}`);
316
- }
317
- if (save) {
318
- await writeSpecFile(filePath, info.spec);
319
- console.log(` Spec saved to ${filePath}`);
320
- }
321
- } finally {
322
- await server.disconnect();
323
- }
324
- }
325
- async function listCommand(args, machineId) {
326
- const json = hasFlag(args, "--json");
327
- const { server, machine } = await connectAndGetMachine(machineId);
328
- try {
329
- const processes = await machine.processList({ _rkwargs: true });
330
- if (json) {
331
- console.log(JSON.stringify(processes, null, 2));
332
- } else {
333
- printProcessTable(processes);
334
- }
335
- } finally {
336
- await server.disconnect();
337
- }
338
- }
339
- async function getCommand(args, machineId) {
340
- const idOrName = args.find((a) => !a.startsWith("-"));
341
- if (!idOrName) {
342
- console.error("Error: process name or id required");
343
- process.exit(1);
344
- }
345
- const outputFormat = getFlag(args, "-o") ?? getFlag(args, "--output") ?? "human";
346
- const outFile = getFlag(args, "--save");
347
- const { server, machine } = await connectAndGetMachine(machineId);
348
- try {
349
- const info = await machine.processGet({ idOrName, _rkwargs: true });
350
- if (!info) {
351
- console.error(`Error: process '${idOrName}' not found`);
352
- process.exit(1);
353
- }
354
- if (outputFormat === "yaml" || outputFormat === "yml") {
355
- const yaml = await loadYaml();
356
- const content = specToYaml(info.spec, yaml);
357
- if (outFile) {
358
- writeFileSync(outFile, `# svamp process spec \u2014 apply with: svamp process apply ${outFile}
359
- ${content}`, "utf-8");
360
- console.log(`Spec written to ${outFile}`);
361
- } else {
362
- process.stdout.write(content);
363
- }
364
- } else if (outputFormat === "json") {
365
- const out = JSON.stringify(info, null, 2);
366
- if (outFile) {
367
- writeFileSync(outFile, out, "utf-8");
368
- console.log(`Written to ${outFile}`);
369
- } else {
370
- console.log(out);
371
- }
372
- } else {
373
- printProcessDetail(info);
374
- }
375
- } finally {
376
- await server.disconnect();
377
- }
378
- }
379
- async function startCommand(args, machineId) {
380
- const { flagArgs, cmdArgs } = splitOnDoubleDash(args);
381
- const idOrName = flagArgs.find((a) => !a.startsWith("-"));
382
- if (!idOrName) {
383
- console.error("Error: process name or id required");
384
- process.exit(1);
385
- }
386
- if (cmdArgs.length === 0) {
387
- const { server: server2, machine: machine2 } = await connectAndGetMachine(machineId);
388
- try {
389
- await machine2.processStart({ idOrName, _rkwargs: true });
390
- console.log(`Started '${idOrName}'`);
391
- } finally {
392
- await server2.disconnect();
393
- }
394
- return;
395
- }
396
- const command = cmdArgs[0];
397
- const cmdArgList = cmdArgs.slice(1);
398
- const workdir = getFlag(flagArgs, "--workdir") ?? process.cwd();
399
- const keepAlive = hasFlag(flagArgs, "--keep-alive", "--keepalive");
400
- const maxRestarts = parseIntFlag(flagArgs, "--max-restarts", 0);
401
- const restartDelay = parseIntFlag(flagArgs, "--restart-delay", 2);
402
- const ttlRaw = getFlag(flagArgs, "--ttl");
403
- const ttl = ttlRaw !== void 0 ? parseInt(ttlRaw, 10) : void 0;
404
- const probePortRaw = getFlag(flagArgs, "--probe-port");
405
- let probe;
406
- if (probePortRaw !== void 0) {
407
- const probePort = parseInt(probePortRaw, 10);
408
- if (isNaN(probePort) || probePort < 1) {
409
- console.error("Error: --probe-port must be a valid port number");
410
- process.exit(1);
411
- }
412
- probe = {
413
- type: "http",
414
- port: probePort,
415
- ...getFlag(flagArgs, "--probe-path") ? { path: getFlag(flagArgs, "--probe-path") } : {},
416
- ...parseIntFlagOpt(flagArgs, "--probe-interval") !== void 0 ? { interval: parseIntFlagOpt(flagArgs, "--probe-interval") } : {},
417
- ...parseIntFlagOpt(flagArgs, "--probe-timeout") !== void 0 ? { timeout: parseIntFlagOpt(flagArgs, "--probe-timeout") } : {},
418
- ...parseIntFlagOpt(flagArgs, "--probe-failures") !== void 0 ? { failureThreshold: parseIntFlagOpt(flagArgs, "--probe-failures") } : {}
419
- };
420
- }
421
- const envPairs = getAllFlags(flagArgs, "--env");
422
- const env = {};
423
- for (const pair of envPairs) {
424
- const eq = pair.indexOf("=");
425
- if (eq === -1) {
426
- console.error(`Error: --env value must be KEY=VALUE`);
427
- process.exit(1);
428
- }
429
- env[pair.slice(0, eq)] = pair.slice(eq + 1);
430
- }
431
- const spec = {
432
- name: idOrName,
433
- command,
434
- args: cmdArgList,
435
- workdir,
436
- ...Object.keys(env).length > 0 ? { env } : {},
437
- keepAlive,
438
- maxRestarts,
439
- restartDelay,
440
- ...probe ? { probe } : {},
441
- ...ttl !== void 0 ? { ttl } : {}
442
- };
443
- const saveFile = getFlag(flagArgs, "--save");
444
- const { server, machine } = await connectAndGetMachine(machineId);
445
- try {
446
- const result = await machine.processApply({ spec, _rkwargs: true });
447
- const { info } = result;
448
- console.log(`Started '${info.spec.name}' (id: ${info.spec.id}, pid: ${info.state.pid ?? "\u2014"})`);
449
- if (keepAlive) console.log(" Keep-alive: enabled");
450
- if (probe) console.log(` Probe: http://localhost:${probe.port}${probe.path ?? "/"}`);
451
- if (ttl !== void 0) console.log(` TTL: ${ttl}s`);
452
- if (saveFile) {
453
- await writeSpecFile(saveFile, info.spec);
454
- console.log(` Spec saved to ${saveFile}`);
455
- }
456
- } finally {
457
- await server.disconnect();
458
- }
459
- }
460
- async function stopCommand(args, machineId) {
461
- const idOrName = args.find((a) => !a.startsWith("-"));
462
- if (!idOrName) {
463
- console.error("Error: process name or id required");
464
- process.exit(1);
465
- }
466
- const { server, machine } = await connectAndGetMachine(machineId);
467
- try {
468
- await machine.processStop({ idOrName, _rkwargs: true });
469
- console.log(`Stopped '${idOrName}'`);
470
- } finally {
471
- await server.disconnect();
472
- }
473
- }
474
- async function restartCommand(args, machineId) {
475
- const idOrName = args.find((a) => !a.startsWith("-"));
476
- if (!idOrName) {
477
- console.error("Error: process name or id required");
478
- process.exit(1);
479
- }
480
- const { server, machine } = await connectAndGetMachine(machineId);
481
- try {
482
- await machine.processRestart({ idOrName, _rkwargs: true });
483
- console.log(`Restarted '${idOrName}'`);
484
- } finally {
485
- await server.disconnect();
486
- }
487
- }
488
- async function deleteCommand(args, machineId) {
489
- const idOrName = args.find((a) => !a.startsWith("-"));
490
- if (!idOrName) {
491
- console.error("Error: process name or id required");
492
- process.exit(1);
493
- }
494
- const { server, machine } = await connectAndGetMachine(machineId);
495
- try {
496
- await machine.processRemove({ idOrName, _rkwargs: true });
497
- console.log(`Deleted '${idOrName}'`);
498
- } finally {
499
- await server.disconnect();
500
- }
501
- }
502
- async function logsCommand(args, machineId) {
503
- const idOrName = args.find((a) => !a.startsWith("-"));
504
- if (!idOrName) {
505
- console.error("Error: process name or id required");
506
- process.exit(1);
507
- }
508
- const last = parseIntFlag(args, "--last", 50);
509
- const { server, machine } = await connectAndGetMachine(machineId);
510
- try {
511
- const lines = await machine.processLogs({ idOrName, last, _rkwargs: true });
512
- if (lines.length === 0) console.log("(no output yet)");
513
- else console.log(lines.join("\n"));
514
- } finally {
515
- await server.disconnect();
516
- }
517
- }
518
- function printHelp() {
519
- console.log(`
520
- svamp process \u2014 Supervised process management (Kubernetes-style)
521
-
522
- USAGE
523
- svamp process <command> [options] [-m <machine>]
524
-
525
- COMMANDS
526
- apply <file.yaml> Apply a process spec (create or update)
527
- list [--json] List all supervised processes
528
- get <name|id> [-o yaml|json] Get process spec and status
529
- start <name|id> Start a stopped process
530
- start <name> [flags] -- <cmd> Create and start (inline shorthand)
531
- stop <name|id> Stop (keeps config for restart)
532
- restart <name|id> Restart process
533
- delete <name|id> Stop and remove from supervision
534
- logs <name|id> [--last N] Show recent log output
535
-
536
- APPLY (recommended workflow)
537
- # Create a spec file
538
- cat > myapp.yaml << EOF
539
- name: myapp
540
- command: python
541
- args: [app.py]
542
- workdir: /data/myapp
543
- keepAlive: true
544
- probe:
545
- type: http
546
- port: 8080
547
- path: /health
548
- ttl: 86400
549
- EOF
550
-
551
- # Apply (idempotent \u2014 re-run to update config)
552
- svamp process apply myapp.yaml
553
-
554
- # Export current spec
555
- svamp process get myapp -o yaml > myapp.yaml
556
-
557
- START FLAGS (inline mode: svamp process start <name> [flags] -- <cmd>)
558
- --workdir <dir> Working directory (default: cwd)
559
- --keep-alive Restart on exit/crash
560
- --max-restarts N Max restart attempts (0 = unlimited)
561
- --restart-delay N Seconds between restarts (default: 2)
562
- --probe-port N Enable HTTP health probe on port N
563
- --probe-path <path> Health check path (default: /)
564
- --probe-interval N Check interval in seconds (default: 10)
565
- --probe-timeout N Check timeout in seconds (default: 5)
566
- --probe-failures N Consecutive failures before restart (default: 3)
567
- --ttl <seconds> Auto-kill after N seconds
568
- --env KEY=VALUE Extra environment variable (repeatable)
569
- --save <file.yaml> Save spec to file after creation
570
-
571
- SPEC FILE FORMAT (YAML or JSON)
572
- name: string Required. Unique process name.
573
- command: string Required. Executable.
574
- args: string[] Command arguments.
575
- workdir: string Working directory.
576
- keepAlive: boolean Restart on crash (default: false).
577
- maxRestarts: number Max restarts (0 = unlimited).
578
- restartDelay: number Seconds between restarts.
579
- probe:
580
- type: http Only 'http' supported.
581
- port: number Port to probe.
582
- path: string HTTP path (default: /).
583
- interval: number Seconds between probes.
584
- timeout: number Probe timeout in seconds.
585
- failureThreshold: number Failures before restart.
586
- ttl: number Seconds until auto-kill.
587
- env: {KEY: value} Extra environment variables.
588
- serviceGroup: string Linked service group name.
589
- ports: number[] Ports exposed by this process.
590
- `);
591
- }
592
-
593
- export { processCommand };