svamp-cli 0.2.20 → 0.2.22

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,477 +0,0 @@
1
- import * as path from 'path';
2
- import { createServiceGroup, listServiceGroups, getServiceGroup, deleteServiceGroup, addBackend, removeBackend, addPort, removePort, renameSubdomain, getSandboxEnv } from './api-BRbsyqJ4.mjs';
3
-
4
- function getFlag(args, flag) {
5
- const idx = args.indexOf(flag);
6
- return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : void 0;
7
- }
8
- function getAllFlags(args, flag) {
9
- const values = [];
10
- for (let i = 0; i < args.length; i++) {
11
- if (args[i] === flag && i + 1 < args.length) {
12
- values.push(args[i + 1]);
13
- i++;
14
- }
15
- }
16
- return values;
17
- }
18
- function hasFlag(args, ...flags) {
19
- return flags.some((f) => args.includes(f));
20
- }
21
- function positionalArgs(args) {
22
- const result = [];
23
- for (let i = 0; i < args.length; i++) {
24
- if (args[i].startsWith("--")) {
25
- if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
26
- i++;
27
- }
28
- continue;
29
- }
30
- result.push(args[i]);
31
- }
32
- return result;
33
- }
34
- function parsePorts(args) {
35
- const portStrs = getAllFlags(args, "--port");
36
- if (portStrs.length === 0) return [];
37
- const ports = [];
38
- for (const s of portStrs) {
39
- const p = parseInt(s, 10);
40
- if (isNaN(p) || p < 1 || p > 65535) {
41
- console.error(`Error: invalid port '${s}' \u2014 must be 1-65535`);
42
- process.exit(1);
43
- }
44
- ports.push(p);
45
- }
46
- return ports;
47
- }
48
- function printServiceGroupInfo(info) {
49
- console.log(`Service group: ${info.name}`);
50
- console.log(` Namespace: ${info.namespace}`);
51
- console.log(` K8s svc: ${info.k8s_service}`);
52
- console.log(` Created: ${info.created_at}`);
53
- if (info.health_path) {
54
- console.log(` Health: ${info.health_path} (every ${info.health_interval}s)`);
55
- }
56
- console.log(` Ports (${info.ports.length}):`);
57
- for (const p of info.ports) {
58
- console.log(` ${p.port} \u2192 ${p.url}`);
59
- }
60
- console.log(` Backends (${info.backends.length}):`);
61
- for (const b of info.backends) {
62
- const healthTag = b.healthy === false ? " [unhealthy]" : b.healthy === true ? "" : "";
63
- const failInfo = b.consecutive_failures ? ` (${b.consecutive_failures} failures)` : "";
64
- console.log(` - ${b.sandbox_id} (${b.pod_ip})${healthTag}${failInfo}`);
65
- }
66
- }
67
- function printServiceGroupListItem(g) {
68
- console.log(` ${g.name}`);
69
- if (g.ports.length > 1) {
70
- for (const p of g.ports) {
71
- console.log(` ${p.port} \u2192 ${p.url}`);
72
- }
73
- } else {
74
- console.log(` URL: ${g.url}`);
75
- console.log(` Port: ${g.port}`);
76
- }
77
- console.log(` Backends: ${g.backend_count}`);
78
- }
79
- function validateSubdomain(sub) {
80
- if (sub.split("-").length - 1 < 2) {
81
- console.error(`Error: custom subdomain must contain at least 2 hyphens (e.g., my-cool-service), got '${sub}'`);
82
- process.exit(1);
83
- }
84
- }
85
- async function serviceCreate(args) {
86
- const positional = positionalArgs(args);
87
- const name = positional[0];
88
- const ports = parsePorts(args);
89
- const subdomain = getFlag(args, "--subdomain");
90
- const healthPath = getFlag(args, "--health-path");
91
- const healthIntervalStr = getFlag(args, "--health-interval");
92
- if (!name || ports.length === 0) {
93
- console.error("Usage: svamp service create <name> --port <port> [--port <port2>] [--health-path <path>]");
94
- process.exit(1);
95
- }
96
- if (subdomain) validateSubdomain(subdomain);
97
- const healthInterval = healthIntervalStr ? parseInt(healthIntervalStr, 10) : void 0;
98
- try {
99
- const result = await createServiceGroup(name, ports, {
100
- subdomain,
101
- healthPath,
102
- healthInterval
103
- });
104
- console.log(`Service group created: ${result.name}`);
105
- for (const p of result.ports) {
106
- console.log(` ${p.port} \u2192 ${p.url}`);
107
- }
108
- console.log(` K8s svc: ${result.k8s_service}`);
109
- console.log(` Backends: ${result.backends.length}`);
110
- } catch (err) {
111
- console.error(`Error creating service group: ${err.message}`);
112
- process.exit(1);
113
- }
114
- }
115
- async function serviceList(args) {
116
- const jsonOutput = hasFlag(args, "--json");
117
- try {
118
- const groups = await listServiceGroups();
119
- if (jsonOutput) {
120
- console.log(JSON.stringify(groups, null, 2));
121
- return;
122
- }
123
- if (groups.length === 0) {
124
- console.log("No service groups found.");
125
- return;
126
- }
127
- console.log(`Service groups (${groups.length}):
128
- `);
129
- for (const g of groups) {
130
- printServiceGroupListItem(g);
131
- console.log();
132
- }
133
- } catch (err) {
134
- console.error(`Error listing service groups: ${err.message}`);
135
- process.exit(1);
136
- }
137
- }
138
- async function serviceInfo(args) {
139
- const positional = positionalArgs(args);
140
- const name = positional[0];
141
- const jsonOutput = hasFlag(args, "--json");
142
- if (!name) {
143
- console.error("Usage: svamp service info <name> [--json]");
144
- process.exit(1);
145
- }
146
- try {
147
- const info = await getServiceGroup(name);
148
- if (jsonOutput) {
149
- console.log(JSON.stringify(info, null, 2));
150
- return;
151
- }
152
- printServiceGroupInfo(info);
153
- } catch (err) {
154
- console.error(`Error getting service group: ${err.message}`);
155
- process.exit(1);
156
- }
157
- }
158
- async function serviceDelete(args) {
159
- const positional = positionalArgs(args);
160
- const name = positional[0];
161
- if (!name) {
162
- console.error("Usage: svamp service delete <name>");
163
- process.exit(1);
164
- }
165
- try {
166
- await deleteServiceGroup(name);
167
- console.log(`Service group '${name}' deleted.`);
168
- } catch (err) {
169
- console.error(`Error deleting service group: ${err.message}`);
170
- process.exit(1);
171
- }
172
- }
173
- async function serviceAddBackend(args) {
174
- const positional = positionalArgs(args);
175
- const name = positional[0];
176
- const sandboxId = getFlag(args, "--sandbox-id");
177
- if (!name) {
178
- console.error("Usage: svamp service add-backend <name> [--sandbox-id <id>]");
179
- process.exit(1);
180
- }
181
- try {
182
- const result = await addBackend(name, sandboxId);
183
- console.log(`Backend added to '${name}': ${result.sandbox_id} (${result.pod_ip})`);
184
- console.log(` Total backends: ${result.backend_count}`);
185
- } catch (err) {
186
- console.error(`Error adding backend: ${err.message}`);
187
- process.exit(1);
188
- }
189
- }
190
- async function serviceRemoveBackend(args) {
191
- const positional = positionalArgs(args);
192
- const name = positional[0];
193
- const sandboxId = getFlag(args, "--sandbox-id");
194
- if (!name) {
195
- console.error("Usage: svamp service remove-backend <name> [--sandbox-id <id>]");
196
- process.exit(1);
197
- }
198
- try {
199
- const result = await removeBackend(name, sandboxId);
200
- console.log(`Backend removed from '${name}': ${result.sandbox_id}`);
201
- console.log(` Remaining backends: ${result.backend_count}`);
202
- } catch (err) {
203
- console.error(`Error removing backend: ${err.message}`);
204
- process.exit(1);
205
- }
206
- }
207
- async function serviceAddPort(args) {
208
- const positional = positionalArgs(args);
209
- const name = positional[0];
210
- const ports = parsePorts(args);
211
- const subdomain = getFlag(args, "--subdomain");
212
- if (!name || ports.length !== 1) {
213
- console.error("Usage: svamp service add-port <name> --port <port> [--subdomain <sub>]");
214
- process.exit(1);
215
- }
216
- if (subdomain) validateSubdomain(subdomain);
217
- try {
218
- const result = await addPort(name, ports[0], subdomain);
219
- console.log(`Port added to '${name}': ${result.port} \u2192 ${result.url}`);
220
- console.log(` Total ports: ${result.total_ports}`);
221
- } catch (err) {
222
- console.error(`Error adding port: ${err.message}`);
223
- process.exit(1);
224
- }
225
- }
226
- async function serviceRemovePort(args) {
227
- const positional = positionalArgs(args);
228
- const name = positional[0];
229
- const ports = parsePorts(args);
230
- if (!name || ports.length !== 1) {
231
- console.error("Usage: svamp service remove-port <name> --port <port>");
232
- process.exit(1);
233
- }
234
- try {
235
- const result = await removePort(name, ports[0]);
236
- console.log(`Port removed from '${name}': ${result.port}`);
237
- console.log(` Remaining ports: ${result.total_ports}`);
238
- } catch (err) {
239
- console.error(`Error removing port: ${err.message}`);
240
- process.exit(1);
241
- }
242
- }
243
- async function serviceRename(args) {
244
- const positional = positionalArgs(args);
245
- const name = positional[0];
246
- const ports = parsePorts(args);
247
- const subdomain = getFlag(args, "--subdomain");
248
- if (!name || ports.length !== 1 || !subdomain) {
249
- console.error("Usage: svamp service rename <name> --port <port> --subdomain <new-subdomain>");
250
- console.error(" The subdomain must contain at least 2 hyphens (e.g., my-cool-service)");
251
- process.exit(1);
252
- }
253
- if (subdomain.split("-").length - 1 < 2) {
254
- console.error(`Error: subdomain must contain at least 2 hyphens (e.g., my-cool-service), got '${subdomain}'`);
255
- process.exit(1);
256
- }
257
- try {
258
- const result = await renameSubdomain(name, ports[0], subdomain);
259
- if (result.status === "unchanged") {
260
- console.log(`Subdomain already set to '${result.subdomain}' \u2014 no change.`);
261
- } else {
262
- console.log(`Subdomain renamed: ${result.old_subdomain} \u2192 ${result.subdomain}`);
263
- console.log(` New URL: ${result.url}`);
264
- }
265
- } catch (err) {
266
- console.error(`Error renaming subdomain: ${err.message}`);
267
- process.exit(1);
268
- }
269
- }
270
- async function serviceExpose(args) {
271
- const positional = positionalArgs(args);
272
- const name = positional[0];
273
- const ports = parsePorts(args);
274
- const subdomain = getFlag(args, "--subdomain");
275
- const healthPath = getFlag(args, "--health-path");
276
- const healthIntervalStr = getFlag(args, "--health-interval");
277
- if (!name || ports.length === 0) {
278
- console.error("Usage: svamp service expose <name> --port <port> [--port <port2>] [--health-path <path>]");
279
- process.exit(1);
280
- }
281
- if (subdomain) validateSubdomain(subdomain);
282
- const healthInterval = healthIntervalStr ? parseInt(healthIntervalStr, 10) : void 0;
283
- const env = getSandboxEnv();
284
- try {
285
- const group = await createServiceGroup(name, ports, {
286
- subdomain,
287
- healthPath,
288
- healthInterval
289
- });
290
- if (env.sandboxId) {
291
- const result = await addBackend(name);
292
- console.log(`Backend added: ${result.sandbox_id} (${result.pod_ip})`);
293
- console.log(`
294
- Service is live:`);
295
- for (const p of group.ports) {
296
- console.log(` ${p.port} \u2192 ${p.url}`);
297
- }
298
- } else {
299
- const { runFrpcTunnel } = await import('./frpc-B26sB9eu.mjs');
300
- await runFrpcTunnel(name, ports);
301
- }
302
- } catch (err) {
303
- console.error(`Error exposing service: ${err.message}`);
304
- process.exit(1);
305
- }
306
- }
307
- async function serviceServe(args) {
308
- const positional = positionalArgs(args);
309
- const name = positional[0];
310
- const directory = positional[1] || ".";
311
- const subdomain = getFlag(args, "--subdomain");
312
- const healthPath = getFlag(args, "--health-path");
313
- const healthIntervalStr = getFlag(args, "--health-interval");
314
- hasFlag(args, "--no-listing");
315
- if (!name) {
316
- console.error("Usage: svamp service serve <name> [directory] [--subdomain <sub>] [--no-listing]");
317
- console.error(" directory defaults to current directory");
318
- process.exit(1);
319
- }
320
- if (subdomain) validateSubdomain(subdomain);
321
- const healthInterval = healthIntervalStr ? parseInt(healthIntervalStr, 10) : void 0;
322
- try {
323
- const resolvedDir = path.resolve(directory);
324
- console.log(`Serving ${resolvedDir}`);
325
- const caddyPort = 18080;
326
- const ports = [caddyPort];
327
- const env = getSandboxEnv();
328
- const group = await createServiceGroup(name, ports, {
329
- subdomain,
330
- healthPath,
331
- healthInterval
332
- });
333
- if (env.sandboxId) {
334
- const { CaddyManager } = await import('./caddy-fJWXn1kE.mjs');
335
- const caddy = new CaddyManager({ listenPort: caddyPort });
336
- await caddy.addMount(name, resolvedDir);
337
- await caddy.start();
338
- const result = await addBackend(name);
339
- console.log(`Backend added: ${result.sandbox_id} (${result.pod_ip})`);
340
- console.log(`
341
- Serving ${resolvedDir}:`);
342
- for (const p of group.ports) {
343
- console.log(` ${p.url}`);
344
- }
345
- const cleanup = () => {
346
- caddy.stop();
347
- process.exit(0);
348
- };
349
- process.on("SIGINT", cleanup);
350
- process.on("SIGTERM", cleanup);
351
- } else {
352
- const { CaddyManager } = await import('./caddy-fJWXn1kE.mjs');
353
- const caddy = new CaddyManager({ listenPort: caddyPort });
354
- await caddy.addMount(name, resolvedDir);
355
- await caddy.start();
356
- const { runFrpcTunnel } = await import('./frpc-B26sB9eu.mjs');
357
- const cleanup = () => {
358
- caddy.stop();
359
- process.exit(0);
360
- };
361
- process.on("SIGINT", cleanup);
362
- process.on("SIGTERM", cleanup);
363
- await runFrpcTunnel(name, ports);
364
- }
365
- } catch (err) {
366
- console.error(`Error serving directory: ${err.message}`);
367
- process.exit(1);
368
- }
369
- }
370
- async function serviceTunnel(args) {
371
- const positional = positionalArgs(args);
372
- const name = positional[0];
373
- const ports = parsePorts(args);
374
- if (!name || ports.length === 0) {
375
- console.error("Usage: svamp service tunnel <name> --port <port> [--port <port2>]");
376
- process.exit(1);
377
- }
378
- const { runFrpcTunnel } = await import('./frpc-B26sB9eu.mjs');
379
- await runFrpcTunnel(name, ports);
380
- }
381
- async function handleServiceCommand() {
382
- const args = process.argv.slice(2);
383
- const serviceArgs = args.slice(1);
384
- const sub = serviceArgs[0];
385
- if (!sub || sub === "--help" || sub === "-h") {
386
- printServiceHelp();
387
- return;
388
- }
389
- const commandArgs = serviceArgs.slice(1);
390
- if (sub === "create") {
391
- await serviceCreate(commandArgs);
392
- } else if (sub === "list" || sub === "ls") {
393
- await serviceList(commandArgs);
394
- } else if (sub === "info" || sub === "show") {
395
- await serviceInfo(commandArgs);
396
- } else if (sub === "delete" || sub === "rm") {
397
- await serviceDelete(commandArgs);
398
- } else if (sub === "add-backend" || sub === "add") {
399
- await serviceAddBackend(commandArgs);
400
- } else if (sub === "remove-backend" || sub === "remove") {
401
- await serviceRemoveBackend(commandArgs);
402
- } else if (sub === "add-port") {
403
- await serviceAddPort(commandArgs);
404
- } else if (sub === "remove-port") {
405
- await serviceRemovePort(commandArgs);
406
- } else if (sub === "rename") {
407
- await serviceRename(commandArgs);
408
- } else if (sub === "expose") {
409
- await serviceExpose(commandArgs);
410
- } else if (sub === "serve") {
411
- await serviceServe(commandArgs);
412
- } else if (sub === "tunnel") {
413
- await serviceTunnel(commandArgs);
414
- } else {
415
- console.error(`Unknown service command: ${sub}`);
416
- printServiceHelp();
417
- process.exit(1);
418
- }
419
- }
420
- function printServiceHelp() {
421
- console.log(`
422
- svamp service \u2014 Manage load-balanced service groups
423
-
424
- Usage:
425
- svamp service create <name> --port <port> [--port <port2>] [options] Create a service group
426
- svamp service list [--json] List service groups
427
- svamp service info <name> [--json] Show service group details
428
- svamp service delete <name> Delete a service group
429
- svamp service add-backend <name> [--sandbox-id <id>] Add a pod as backend
430
- svamp service remove-backend <name> [--sandbox-id <id>] Remove a backend
431
- svamp service add-port <name> --port <port> [--subdomain <sub>] Add port to existing group
432
- svamp service remove-port <name> --port <port> Remove port from group
433
- svamp service rename <name> --port <port> --subdomain <sub> Rename subdomain of a port
434
- svamp service expose <name> --port <port> [--port <port2>] [options] Create + join (auto-detects tunnel)
435
- svamp service serve <name> [directory] [options] Serve a directory as static files
436
- svamp service tunnel <name> --port <port> [--port <port2>] Tunnel local ports to service group
437
-
438
- Create/Expose options:
439
- --health-path <path> Health check endpoint (e.g., /health)
440
- --health-interval <sec> Health check interval in seconds (default: 30)
441
- --subdomain <sub> Custom subdomain (must contain at least 2 hyphens)
442
-
443
- Service groups expose HTTP services via stable URLs with K8s-native load
444
- balancing. Multiple pods can serve the same service \u2014 K8s handles traffic
445
- distribution automatically.
446
-
447
- Multi-port: Use --port multiple times to expose several ports under one
448
- service group. Each port gets its own unique subdomain and URL.
449
-
450
- Auto-detect: 'expose' automatically detects the environment:
451
- - Cloud sandbox (SANDBOX_ID set): adds pod as K8s backend
452
- - Local machine (no SANDBOX_ID): starts reverse tunnel
453
-
454
- Environment variables (set by provisioner):
455
- SANDBOX_API_URL Agent-sandbox API base URL
456
- SANDBOX_API_KEY API authentication token
457
- SANDBOX_NAMESPACE User's sandbox namespace
458
- SANDBOX_ID This pod's sandbox ID
459
-
460
- Serve options:
461
- --no-listing Disable directory listing
462
- --subdomain <sub> Custom subdomain (must contain at least 2 hyphens)
463
-
464
- Examples:
465
- svamp service expose my-api --port 8000
466
- svamp service expose my-api --port 8000 --port 3000 --health-path /health
467
- svamp service serve my-site ./dist
468
- svamp service serve my-site (serves current directory)
469
- svamp service add-port my-api --port 5000
470
- svamp service remove-port my-api --port 5000
471
- svamp service list
472
- svamp service info my-api
473
- svamp service delete my-api
474
- `.trim());
475
- }
476
-
477
- export { handleServiceCommand, serviceAddBackend, serviceAddPort, serviceCreate, serviceDelete, serviceExpose, serviceInfo, serviceList, serviceRemoveBackend, serviceRemovePort, serviceRename, serviceServe, serviceTunnel };