svamp-cli 0.2.225 → 0.2.226

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.
@@ -0,0 +1,265 @@
1
+ import * as path from 'path';
2
+
3
+ function getFlag(args, flag) {
4
+ const idx = args.indexOf(flag);
5
+ return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : void 0;
6
+ }
7
+ function getAllFlags(args, flag) {
8
+ const values = [];
9
+ for (let i = 0; i < args.length; i++) {
10
+ if (args[i] === flag && i + 1 < args.length) {
11
+ values.push(args[i + 1]);
12
+ i++;
13
+ }
14
+ }
15
+ return values;
16
+ }
17
+ function hasFlag(args, ...flags) {
18
+ return flags.some((f) => args.includes(f));
19
+ }
20
+ function positionalArgs(args) {
21
+ const result = [];
22
+ for (let i = 0; i < args.length; i++) {
23
+ if (args[i].startsWith("--")) {
24
+ if (i + 1 < args.length && !args[i + 1].startsWith("--")) i++;
25
+ continue;
26
+ }
27
+ result.push(args[i]);
28
+ }
29
+ return result;
30
+ }
31
+ function parsePorts(args) {
32
+ const portStrs = getAllFlags(args, "--port");
33
+ if (portStrs.length === 0) return [];
34
+ const ports = [];
35
+ for (const s of portStrs) {
36
+ const p = parseInt(s, 10);
37
+ if (isNaN(p) || p < 1 || p > 65535) {
38
+ console.error(`Error: invalid port '${s}' \u2014 must be 1-65535`);
39
+ process.exit(1);
40
+ }
41
+ ports.push(p);
42
+ }
43
+ return ports;
44
+ }
45
+ async function serviceExpose(args) {
46
+ const positional = positionalArgs(args);
47
+ const name = positional[0];
48
+ const ports = parsePorts(args);
49
+ const group = getFlag(args, "--group");
50
+ const groupKey = getFlag(args, "--group-key");
51
+ const healthCheck = getFlag(args, "--health-check");
52
+ const healthPath = getFlag(args, "--health-path");
53
+ const healthInterval = getFlag(args, "--health-interval");
54
+ const foreground = hasFlag(args, "--foreground");
55
+ if (!name || ports.length === 0) {
56
+ console.error("Usage: svamp service expose <name> --port <port> [--port <port2>] [options]");
57
+ console.error(" --foreground run frpc in this shell (legacy, dies with shell)");
58
+ process.exit(1);
59
+ }
60
+ if (foreground) {
61
+ const { runFrpcTunnel } = await import('./frpc-CjX2a7K3.mjs');
62
+ await runFrpcTunnel(name, ports, void 0, {
63
+ group,
64
+ groupKey,
65
+ healthCheckType: healthCheck,
66
+ healthCheckPath: healthPath,
67
+ healthCheckInterval: healthInterval ? parseInt(healthInterval, 10) : void 0
68
+ });
69
+ return;
70
+ }
71
+ const { connectAndGetMachine } = await import('./commands-Ctop57By.mjs');
72
+ const { server, machine } = await connectAndGetMachine();
73
+ try {
74
+ const status = await machine.tunnelStart({
75
+ name,
76
+ ports,
77
+ group: group || void 0,
78
+ groupKey: groupKey || void 0,
79
+ healthCheckType: healthCheck,
80
+ healthCheckPath: healthPath || void 0,
81
+ healthCheckInterval: healthInterval ? parseInt(healthInterval, 10) : void 0
82
+ });
83
+ console.log(`Tunnel '${name}' registered with daemon \u2014 supervised + restart-persistent.`);
84
+ if (status && status.urls) {
85
+ const urlEntries = Object.entries(status.urls);
86
+ for (const [port, url] of urlEntries) {
87
+ console.log(` port ${port}: ${url}`);
88
+ }
89
+ if (process.env.SVAMP_SESSION_ID) {
90
+ const { autoAddSessionLink } = await import('./agentCommands-CGLZ9Nn0.mjs');
91
+ let added = 0;
92
+ for (const [port, url] of urlEntries) {
93
+ const label = urlEntries.length > 1 ? `${name}:${port}` : name;
94
+ if (autoAddSessionLink(String(url), label)) added++;
95
+ }
96
+ if (added) console.log(` \u21B3 added to this session's Launch Pad`);
97
+ }
98
+ }
99
+ console.log(`To stop: svamp service delete ${name} (or: svamp tunnel stop ${name})`);
100
+ } catch (err) {
101
+ if (/already running/i.test(err?.message || "")) {
102
+ console.error(`Tunnel '${name}' is already running. Use \`svamp service delete ${name}\` first.`);
103
+ } else {
104
+ console.error(`Failed to register tunnel: ${err?.message || err}`);
105
+ console.error(`Falling back to foreground mode? Add --foreground to run in this shell.`);
106
+ }
107
+ process.exit(1);
108
+ } finally {
109
+ await server.disconnect();
110
+ }
111
+ }
112
+ async function serviceServe(args) {
113
+ const positional = positionalArgs(args);
114
+ const name = positional[0];
115
+ const directory = positional[1] || ".";
116
+ const noListing = hasFlag(args, "--no-listing");
117
+ if (!name) {
118
+ console.error("Usage: svamp service serve <name> [directory] [--no-listing]");
119
+ console.error(" directory defaults to current directory");
120
+ process.exit(1);
121
+ }
122
+ try {
123
+ const resolvedDir = path.resolve(directory);
124
+ console.log(`Serving ${resolvedDir}`);
125
+ const caddyPort = 18080;
126
+ const { CaddyManager } = await import('./caddy-CuTbE3NY.mjs');
127
+ const caddy = new CaddyManager({ listenPort: caddyPort });
128
+ await caddy.addMount(name, resolvedDir, !noListing);
129
+ await caddy.start();
130
+ const cleanup = () => {
131
+ caddy.stop();
132
+ process.exit(0);
133
+ };
134
+ process.on("SIGINT", cleanup);
135
+ process.on("SIGTERM", cleanup);
136
+ const { runFrpcTunnel } = await import('./frpc-CjX2a7K3.mjs');
137
+ await runFrpcTunnel(name, [caddyPort]);
138
+ } catch (err) {
139
+ console.error(`Error serving directory: ${err.message}`);
140
+ process.exit(1);
141
+ }
142
+ }
143
+ async function serviceList(_args) {
144
+ try {
145
+ const { connectAndGetMachine } = await import('./commands-Ctop57By.mjs');
146
+ const { server, machine } = await connectAndGetMachine();
147
+ try {
148
+ const tunnels = await machine.tunnelList({});
149
+ if (!tunnels || tunnels.length === 0) {
150
+ console.log("No daemon-managed tunnels configured.");
151
+ console.log("Standalone foreground tunnels (from `svamp service expose`) are not listed here \u2014 check `pgrep -af frpc`.");
152
+ return;
153
+ }
154
+ const label = (t) => typeof t.state === "string" ? t.state : t.connected ? "connected" : "disconnected";
155
+ const icon = { connected: "\u2713", reconnecting: "\u21BB", failed: "\u2717", disconnected: "\u2717" };
156
+ console.log("Daemon-managed tunnels:");
157
+ for (const t of tunnels) {
158
+ const st = label(t);
159
+ const ports = Array.isArray(t.ports) && t.ports.length ? ` :${t.ports.join(",")}` : "";
160
+ let extra = "";
161
+ if (st !== "connected") {
162
+ const bits = [];
163
+ if (t.restartAttempts) bits.push(`${t.restartAttempts} restarts`);
164
+ if (t.probe && !t.probe.ok && t.probe.stalenessMs) bits.push(`probe stale ${Math.round(t.probe.stalenessMs / 1e3)}s`);
165
+ if (bits.length) extra = ` (${bits.join(", ")})`;
166
+ }
167
+ console.log(` ${icon[st] ?? "\u2022"} ${t.name}${ports} \u2014 ${st}${extra}`);
168
+ }
169
+ } finally {
170
+ await server.disconnect();
171
+ }
172
+ } catch (err) {
173
+ console.error(`Error listing tunnels: ${err.message}`);
174
+ process.exit(1);
175
+ }
176
+ }
177
+ async function serviceDelete(args) {
178
+ const positional = positionalArgs(args);
179
+ const name = positional[0];
180
+ if (!name) {
181
+ console.error("Usage: svamp service delete <name>");
182
+ process.exit(1);
183
+ }
184
+ try {
185
+ const { connectAndGetMachine } = await import('./commands-Ctop57By.mjs');
186
+ const { server, machine } = await connectAndGetMachine();
187
+ try {
188
+ await machine.tunnelStop({ name });
189
+ console.log(`Stopped tunnel '${name}'.`);
190
+ } catch (err) {
191
+ if (/not found/i.test(err.message)) {
192
+ console.error(`No daemon-managed tunnel named '${name}'.`);
193
+ console.error("If this was a standalone foreground tunnel (from `svamp service expose`), the daemon can't stop it.");
194
+ console.error(`Kill the frpc process directly: pkill -f 'frpc.*${name}'`);
195
+ } else {
196
+ console.error(`Error stopping tunnel: ${err.message}`);
197
+ }
198
+ process.exit(1);
199
+ } finally {
200
+ await server.disconnect();
201
+ }
202
+ } catch (err) {
203
+ console.error(`Error: ${err.message}`);
204
+ process.exit(1);
205
+ }
206
+ }
207
+ async function handleServiceCommand() {
208
+ const args = process.argv.slice(2);
209
+ const serviceArgs = args.slice(1);
210
+ const sub = serviceArgs[0];
211
+ if (!sub || sub === "--help" || sub === "-h") {
212
+ printServiceHelp();
213
+ return;
214
+ }
215
+ const commandArgs = serviceArgs.slice(1);
216
+ if (sub === "expose" || sub === "tunnel") {
217
+ await serviceExpose(commandArgs);
218
+ } else if (sub === "serve") {
219
+ await serviceServe(commandArgs);
220
+ } else if (sub === "list" || sub === "ls") {
221
+ await serviceList();
222
+ } else if (sub === "delete" || sub === "rm") {
223
+ await serviceDelete(commandArgs);
224
+ } else {
225
+ console.error(`Unknown service command: ${sub}`);
226
+ printServiceHelp();
227
+ process.exit(1);
228
+ }
229
+ }
230
+ function printServiceHelp() {
231
+ console.log(`
232
+ svamp service \u2014 Expose local services via frpc tunnels
233
+
234
+ Usage:
235
+ svamp service expose <name> --port <port> [options] Expose local ports via tunnel
236
+ svamp service serve <name> [directory] [--no-listing] Serve static files via tunnel
237
+ svamp service list List active tunnels (daemon)
238
+ svamp service delete <name> Stop a tunnel (daemon)
239
+
240
+ Options:
241
+ --port <port> Port to expose (repeatable for multi-port)
242
+ --group <name> Service group \u2014 multiple machines share the same URL
243
+ --group-key <secret> Shared key for the service group
244
+ --health-check <tcp|http> Enable health checks on local backend
245
+ --health-path <path> HTTP health check path (e.g., /health)
246
+ --health-interval <sec> Health check interval (default: 10s)
247
+
248
+ Service Groups:
249
+ Run the same command on different machines with --group to create a
250
+ load-balanced service. frps round-robins traffic across all group members.
251
+
252
+ Machine A: svamp service expose my-api --port 8000 --group my-api --group-key s3cret
253
+ Machine B: svamp service expose my-api --port 8000 --group my-api --group-key s3cret
254
+ \u2192 Both serve at the same URL, frps load-balances between them
255
+
256
+ Examples:
257
+ svamp service expose my-api --port 8000
258
+ svamp service expose my-api --port 8000 --health-check http --health-path /health
259
+ svamp service expose my-api --port 8000 --port 3000
260
+ svamp service expose my-api --port 8000 --group my-api --group-key s3cret
261
+ svamp service serve my-site ./dist
262
+ `.trim());
263
+ }
264
+
265
+ export { handleServiceCommand, serviceDelete, serviceExpose, serviceList, serviceServe };