svamp-cli 0.2.21 → 0.2.23

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.
@@ -148,7 +148,7 @@ async function sessionBroadcast(action, args) {
148
148
  console.log(`Broadcast sent: ${action}`);
149
149
  }
150
150
  async function connectToMachineService() {
151
- const { connectAndGetMachine } = await import('./commands-DVCdUK0p.mjs');
151
+ const { connectAndGetMachine } = await import('./commands-DrNOSpki.mjs');
152
152
  return connectAndGetMachine();
153
153
  }
154
154
  async function inboxSend(targetSessionId, opts) {
@@ -165,7 +165,7 @@ async function inboxSend(targetSessionId, opts) {
165
165
  }
166
166
  const { server, machine } = await connectToMachineService();
167
167
  try {
168
- const { resolveSessionId } = await import('./commands-DVCdUK0p.mjs');
168
+ const { resolveSessionId } = await import('./commands-DrNOSpki.mjs');
169
169
  const sessions = await machine.listSessions();
170
170
  const match = resolveSessionId(sessions, targetSessionId);
171
171
  const fullTargetId = match.sessionId;
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-CdjKXuaY.mjs';
1
+ import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-YVIJdP0j.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -36,7 +36,7 @@ async function main() {
36
36
  await logoutFromHypha();
37
37
  } else if (subcommand === "daemon") {
38
38
  if (daemonSubcommand === "restart") {
39
- const { restartDaemon } = await import('./run-CdjKXuaY.mjs').then(function (n) { return n.k; });
39
+ const { restartDaemon } = await import('./run-YVIJdP0j.mjs').then(function (n) { return n.k; });
40
40
  await restartDaemon();
41
41
  process.exit(0);
42
42
  }
@@ -238,7 +238,7 @@ async function main() {
238
238
  console.error("svamp service: Service commands are not available in sandboxed sessions.");
239
239
  process.exit(1);
240
240
  }
241
- const { handleServiceCommand } = await import('./commands-DdwOBsOi.mjs');
241
+ const { handleServiceCommand } = await import('./commands-CqULIXFK.mjs');
242
242
  await handleServiceCommand();
243
243
  } else if (subcommand === "serve") {
244
244
  const { isSandboxed: isSandboxedServe } = await import('./sandboxDetect-DNTcbgWD.mjs');
@@ -246,7 +246,7 @@ async function main() {
246
246
  console.error("svamp serve: Serve commands are not available in sandboxed sessions.");
247
247
  process.exit(1);
248
248
  }
249
- const { handleServeCommand } = await import('./serveCommands-PxeZcd-5.mjs');
249
+ const { handleServeCommand } = await import('./serveCommands-CCbnvtwL.mjs');
250
250
  await handleServeCommand();
251
251
  process.exit(0);
252
252
  } else if (subcommand === "process" || subcommand === "proc") {
@@ -255,7 +255,7 @@ async function main() {
255
255
  console.error("svamp process: Process commands are not available in sandboxed sessions.");
256
256
  process.exit(1);
257
257
  }
258
- const { processCommand } = await import('./commands-DkmPKHhv.mjs');
258
+ const { processCommand } = await import('./commands-DCI1dZuS.mjs');
259
259
  let machineId;
260
260
  const processArgs = args.slice(1);
261
261
  const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
@@ -273,7 +273,7 @@ async function main() {
273
273
  } else if (!subcommand || subcommand === "start") {
274
274
  await handleInteractiveCommand();
275
275
  } else if (subcommand === "--version" || subcommand === "-v") {
276
- const pkg = await import('./package-DkmeUIJ-.mjs').catch(() => ({ default: { version: "unknown" } }));
276
+ const pkg = await import('./package-D-GTa0i_.mjs').catch(() => ({ default: { version: "unknown" } }));
277
277
  console.log(`svamp version: ${pkg.default.version}`);
278
278
  } else {
279
279
  console.error(`Unknown command: ${subcommand}`);
@@ -282,7 +282,7 @@ async function main() {
282
282
  }
283
283
  }
284
284
  async function handleInteractiveCommand() {
285
- const { runInteractive } = await import('./run-B_yFI97i.mjs');
285
+ const { runInteractive } = await import('./run-Ckm62Pws.mjs');
286
286
  const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
287
287
  let directory = process.cwd();
288
288
  let resumeSessionId;
@@ -327,7 +327,7 @@ async function handleAgentCommand() {
327
327
  return;
328
328
  }
329
329
  if (agentArgs[0] === "list") {
330
- const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-CdjKXuaY.mjs').then(function (n) { return n.i; });
330
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-YVIJdP0j.mjs').then(function (n) { return n.i; });
331
331
  console.log("Known agents:");
332
332
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
333
333
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
@@ -339,7 +339,7 @@ async function handleAgentCommand() {
339
339
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
340
340
  return;
341
341
  }
342
- const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-CdjKXuaY.mjs').then(function (n) { return n.i; });
342
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-YVIJdP0j.mjs').then(function (n) { return n.i; });
343
343
  let cwd = process.cwd();
344
344
  const filteredArgs = [];
345
345
  for (let i = 0; i < agentArgs.length; i++) {
@@ -363,12 +363,12 @@ async function handleAgentCommand() {
363
363
  console.log(`Starting ${config.agentName} agent in ${cwd}...`);
364
364
  let backend;
365
365
  if (KNOWN_MCP_AGENTS[config.agentName]) {
366
- const { CodexMcpBackend } = await import('./run-CdjKXuaY.mjs').then(function (n) { return n.j; });
366
+ const { CodexMcpBackend } = await import('./run-YVIJdP0j.mjs').then(function (n) { return n.j; });
367
367
  backend = new CodexMcpBackend({ cwd, log: logFn });
368
368
  } else {
369
- const { AcpBackend } = await import('./run-CdjKXuaY.mjs').then(function (n) { return n.h; });
370
- const { GeminiTransport } = await import('./run-CdjKXuaY.mjs').then(function (n) { return n.G; });
371
- const { DefaultTransport } = await import('./run-CdjKXuaY.mjs').then(function (n) { return n.D; });
369
+ const { AcpBackend } = await import('./run-YVIJdP0j.mjs').then(function (n) { return n.h; });
370
+ const { GeminiTransport } = await import('./run-YVIJdP0j.mjs').then(function (n) { return n.G; });
371
+ const { DefaultTransport } = await import('./run-YVIJdP0j.mjs').then(function (n) { return n.D; });
372
372
  const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
373
373
  backend = new AcpBackend({
374
374
  agentName: config.agentName,
@@ -495,7 +495,7 @@ async function handleSessionCommand() {
495
495
  process.exit(1);
496
496
  }
497
497
  }
498
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-DVCdUK0p.mjs');
498
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-DrNOSpki.mjs');
499
499
  const parseFlagStr = (flag, shortFlag) => {
500
500
  for (let i = 1; i < sessionArgs.length; i++) {
501
501
  if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
@@ -555,7 +555,7 @@ async function handleSessionCommand() {
555
555
  allowDomain.push(sessionArgs[++i]);
556
556
  }
557
557
  }
558
- const { parseShareArg } = await import('./commands-DVCdUK0p.mjs');
558
+ const { parseShareArg } = await import('./commands-DrNOSpki.mjs');
559
559
  const shareEntries = share.map((s) => parseShareArg(s));
560
560
  await sessionSpawn(agent, dir, targetMachineId, {
561
561
  message,
@@ -641,7 +641,7 @@ async function handleSessionCommand() {
641
641
  console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
642
642
  process.exit(1);
643
643
  }
644
- const { sessionApprove } = await import('./commands-DVCdUK0p.mjs');
644
+ const { sessionApprove } = await import('./commands-DrNOSpki.mjs');
645
645
  const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
646
646
  await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
647
647
  json: hasFlag("--json")
@@ -651,7 +651,7 @@ async function handleSessionCommand() {
651
651
  console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
652
652
  process.exit(1);
653
653
  }
654
- const { sessionDeny } = await import('./commands-DVCdUK0p.mjs');
654
+ const { sessionDeny } = await import('./commands-DrNOSpki.mjs');
655
655
  const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
656
656
  await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
657
657
  json: hasFlag("--json")
@@ -687,7 +687,7 @@ async function handleSessionCommand() {
687
687
  console.error("Usage: svamp session set-title <title>");
688
688
  process.exit(1);
689
689
  }
690
- const { sessionSetTitle } = await import('./agentCommands-DRxsYzlw.mjs');
690
+ const { sessionSetTitle } = await import('./agentCommands-rofwcLx3.mjs');
691
691
  await sessionSetTitle(title);
692
692
  } else if (sessionSubcommand === "set-link") {
693
693
  const url = sessionArgs[1];
@@ -696,7 +696,7 @@ async function handleSessionCommand() {
696
696
  process.exit(1);
697
697
  }
698
698
  const label = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
699
- const { sessionSetLink } = await import('./agentCommands-DRxsYzlw.mjs');
699
+ const { sessionSetLink } = await import('./agentCommands-rofwcLx3.mjs');
700
700
  await sessionSetLink(url, label);
701
701
  } else if (sessionSubcommand === "notify") {
702
702
  const message = sessionArgs[1];
@@ -705,7 +705,7 @@ async function handleSessionCommand() {
705
705
  process.exit(1);
706
706
  }
707
707
  const level = parseFlagStr("--level") || "info";
708
- const { sessionNotify } = await import('./agentCommands-DRxsYzlw.mjs');
708
+ const { sessionNotify } = await import('./agentCommands-rofwcLx3.mjs');
709
709
  await sessionNotify(message, level);
710
710
  } else if (sessionSubcommand === "broadcast") {
711
711
  const action = sessionArgs[1];
@@ -713,7 +713,7 @@ async function handleSessionCommand() {
713
713
  console.error("Usage: svamp session broadcast <action> [args...]\nActions: open-canvas <url> [label], close-canvas, toast <message>");
714
714
  process.exit(1);
715
715
  }
716
- const { sessionBroadcast } = await import('./agentCommands-DRxsYzlw.mjs');
716
+ const { sessionBroadcast } = await import('./agentCommands-rofwcLx3.mjs');
717
717
  await sessionBroadcast(action, sessionArgs.slice(2).filter((a) => !a.startsWith("--")));
718
718
  } else if (sessionSubcommand === "inbox") {
719
719
  const inboxSubcmd = sessionArgs[1];
@@ -724,7 +724,7 @@ async function handleSessionCommand() {
724
724
  process.exit(1);
725
725
  }
726
726
  if (agentSessionId) {
727
- const { inboxSend } = await import('./agentCommands-DRxsYzlw.mjs');
727
+ const { inboxSend } = await import('./agentCommands-rofwcLx3.mjs');
728
728
  await inboxSend(sessionArgs[2], {
729
729
  body: sessionArgs[3],
730
730
  subject: parseFlagStr("--subject"),
@@ -739,7 +739,7 @@ async function handleSessionCommand() {
739
739
  }
740
740
  } else if (inboxSubcmd === "list" || inboxSubcmd === "ls") {
741
741
  if (agentSessionId && !sessionArgs[2]) {
742
- const { inboxList } = await import('./agentCommands-DRxsYzlw.mjs');
742
+ const { inboxList } = await import('./agentCommands-rofwcLx3.mjs');
743
743
  await inboxList({
744
744
  unread: hasFlag("--unread"),
745
745
  limit: parseFlagInt("--limit"),
@@ -761,7 +761,7 @@ async function handleSessionCommand() {
761
761
  process.exit(1);
762
762
  }
763
763
  if (agentSessionId && !sessionArgs[3]) {
764
- const { inboxList } = await import('./agentCommands-DRxsYzlw.mjs');
764
+ const { inboxList } = await import('./agentCommands-rofwcLx3.mjs');
765
765
  await sessionInboxRead(agentSessionId, sessionArgs[2], targetMachineId);
766
766
  } else if (sessionArgs[3]) {
767
767
  await sessionInboxRead(sessionArgs[2], sessionArgs[3], targetMachineId);
@@ -771,7 +771,7 @@ async function handleSessionCommand() {
771
771
  }
772
772
  } else if (inboxSubcmd === "reply") {
773
773
  if (agentSessionId && sessionArgs[2] && sessionArgs[3] && !sessionArgs[4]) {
774
- const { inboxReply } = await import('./agentCommands-DRxsYzlw.mjs');
774
+ const { inboxReply } = await import('./agentCommands-rofwcLx3.mjs');
775
775
  await inboxReply(sessionArgs[2], sessionArgs[3]);
776
776
  } else if (sessionArgs[2] && sessionArgs[3] && sessionArgs[4]) {
777
777
  await sessionInboxReply(sessionArgs[2], sessionArgs[3], sessionArgs[4], targetMachineId);
@@ -807,7 +807,7 @@ async function handleMachineCommand() {
807
807
  return;
808
808
  }
809
809
  if (machineSubcommand === "share") {
810
- const { machineShare } = await import('./commands-DVCdUK0p.mjs');
810
+ const { machineShare } = await import('./commands-DrNOSpki.mjs');
811
811
  let machineId;
812
812
  const shareArgs = [];
813
813
  for (let i = 1; i < machineArgs.length; i++) {
@@ -837,7 +837,7 @@ async function handleMachineCommand() {
837
837
  }
838
838
  await machineShare(machineId, { add, remove, list, configPath, showConfig });
839
839
  } else if (machineSubcommand === "exec") {
840
- const { machineExec } = await import('./commands-DVCdUK0p.mjs');
840
+ const { machineExec } = await import('./commands-DrNOSpki.mjs');
841
841
  let machineId;
842
842
  let cwd;
843
843
  const cmdParts = [];
@@ -857,7 +857,7 @@ async function handleMachineCommand() {
857
857
  }
858
858
  await machineExec(machineId, command, cwd);
859
859
  } else if (machineSubcommand === "info") {
860
- const { machineInfo } = await import('./commands-DVCdUK0p.mjs');
860
+ const { machineInfo } = await import('./commands-DrNOSpki.mjs');
861
861
  let machineId;
862
862
  for (let i = 1; i < machineArgs.length; i++) {
863
863
  if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
@@ -877,10 +877,10 @@ async function handleMachineCommand() {
877
877
  level = machineArgs[++i];
878
878
  }
879
879
  }
880
- const { machineNotify } = await import('./agentCommands-DRxsYzlw.mjs');
880
+ const { machineNotify } = await import('./agentCommands-rofwcLx3.mjs');
881
881
  await machineNotify(message, level);
882
882
  } else if (machineSubcommand === "ls") {
883
- const { machineLs } = await import('./commands-DVCdUK0p.mjs');
883
+ const { machineLs } = await import('./commands-DrNOSpki.mjs');
884
884
  let machineId;
885
885
  let showHidden = false;
886
886
  let path;
@@ -0,0 +1,179 @@
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("--")) {
25
+ i++;
26
+ }
27
+ continue;
28
+ }
29
+ result.push(args[i]);
30
+ }
31
+ return result;
32
+ }
33
+ function parsePorts(args) {
34
+ const portStrs = getAllFlags(args, "--port");
35
+ if (portStrs.length === 0) return [];
36
+ const ports = [];
37
+ for (const s of portStrs) {
38
+ const p = parseInt(s, 10);
39
+ if (isNaN(p) || p < 1 || p > 65535) {
40
+ console.error(`Error: invalid port '${s}' \u2014 must be 1-65535`);
41
+ process.exit(1);
42
+ }
43
+ ports.push(p);
44
+ }
45
+ return ports;
46
+ }
47
+ async function serviceExpose(args) {
48
+ const positional = positionalArgs(args);
49
+ const name = positional[0];
50
+ const ports = parsePorts(args);
51
+ const group = getFlag(args, "--group");
52
+ const groupKey = getFlag(args, "--group-key");
53
+ const healthCheck = getFlag(args, "--health-check");
54
+ const healthPath = getFlag(args, "--health-path");
55
+ const healthInterval = getFlag(args, "--health-interval");
56
+ if (!name || ports.length === 0) {
57
+ console.error("Usage: svamp service expose <name> --port <port> [--port <port2>] [options]");
58
+ process.exit(1);
59
+ }
60
+ const { runFrpcTunnel } = await import('./frpc-qqYQ6F9T.mjs');
61
+ await runFrpcTunnel(name, ports, void 0, {
62
+ group,
63
+ groupKey,
64
+ healthCheckType: healthCheck,
65
+ healthCheckPath: healthPath,
66
+ healthCheckInterval: healthInterval ? parseInt(healthInterval, 10) : void 0
67
+ });
68
+ }
69
+ async function serviceServe(args) {
70
+ const positional = positionalArgs(args);
71
+ const name = positional[0];
72
+ const directory = positional[1] || ".";
73
+ const noListing = hasFlag(args, "--no-listing");
74
+ if (!name) {
75
+ console.error("Usage: svamp service serve <name> [directory] [--no-listing]");
76
+ console.error(" directory defaults to current directory");
77
+ process.exit(1);
78
+ }
79
+ try {
80
+ const resolvedDir = path.resolve(directory);
81
+ console.log(`Serving ${resolvedDir}`);
82
+ const caddyPort = 18080;
83
+ const { CaddyManager } = await import('./caddy-fJWXn1kE.mjs');
84
+ const caddy = new CaddyManager({ listenPort: caddyPort });
85
+ await caddy.addMount(name, resolvedDir, !noListing);
86
+ await caddy.start();
87
+ const cleanup = () => {
88
+ caddy.stop();
89
+ process.exit(0);
90
+ };
91
+ process.on("SIGINT", cleanup);
92
+ process.on("SIGTERM", cleanup);
93
+ const { runFrpcTunnel } = await import('./frpc-qqYQ6F9T.mjs');
94
+ await runFrpcTunnel(name, [caddyPort]);
95
+ } catch (err) {
96
+ console.error(`Error serving directory: ${err.message}`);
97
+ process.exit(1);
98
+ }
99
+ }
100
+ async function serviceTunnel(args) {
101
+ await serviceExpose(args);
102
+ }
103
+ async function serviceList(args) {
104
+ console.log("Active tunnels are managed by the svamp daemon.");
105
+ console.log('Use "svamp service expose" to start a tunnel in the foreground.');
106
+ }
107
+ async function serviceDelete(args) {
108
+ const positional = positionalArgs(args);
109
+ const name = positional[0];
110
+ if (!name) {
111
+ console.error("Usage: svamp service delete <name>");
112
+ process.exit(1);
113
+ }
114
+ console.log(`Tunnel '${name}' can only be stopped via the daemon (tunnelStop RPC).`);
115
+ }
116
+ async function handleServiceCommand() {
117
+ const args = process.argv.slice(2);
118
+ const serviceArgs = args.slice(1);
119
+ const sub = serviceArgs[0];
120
+ if (!sub || sub === "--help" || sub === "-h") {
121
+ printServiceHelp();
122
+ return;
123
+ }
124
+ const commandArgs = serviceArgs.slice(1);
125
+ if (sub === "expose") {
126
+ await serviceExpose(commandArgs);
127
+ } else if (sub === "serve") {
128
+ await serviceServe(commandArgs);
129
+ } else if (sub === "tunnel") {
130
+ await serviceTunnel(commandArgs);
131
+ } else if (sub === "list" || sub === "ls") {
132
+ await serviceList();
133
+ } else if (sub === "delete" || sub === "rm") {
134
+ await serviceDelete(commandArgs);
135
+ } else {
136
+ console.error(`Unknown service command: ${sub}`);
137
+ printServiceHelp();
138
+ process.exit(1);
139
+ }
140
+ }
141
+ function printServiceHelp() {
142
+ console.log(`
143
+ svamp service \u2014 Expose local services via frpc tunnels
144
+
145
+ Usage:
146
+ svamp service expose <name> --port <port> [options] Expose local ports via tunnel
147
+ svamp service serve <name> [directory] [--no-listing] Serve static files via tunnel
148
+ svamp service tunnel <name> --port <port> [options] Alias for expose
149
+ svamp service list List active tunnels (daemon)
150
+ svamp service delete <name> Stop a tunnel (daemon)
151
+
152
+ Options:
153
+ --port <port> Port to expose (repeatable for multi-port)
154
+ --group <name> Service group \u2014 multiple machines share the same URL
155
+ --group-key <secret> Shared key for the service group
156
+ --health-check <tcp|http> Enable health checks on local backend
157
+ --health-path <path> HTTP health check path (e.g., /health)
158
+ --health-interval <sec> Health check interval (default: 10s)
159
+
160
+ Service Groups:
161
+ Run the same command on different machines with --group to create a
162
+ load-balanced service. frps round-robins traffic across all group members.
163
+ Unhealthy backends are auto-removed when --health-check is enabled.
164
+
165
+ Machine A: svamp service expose my-api --port 8000 --group my-api --group-key secret123
166
+ Machine B: svamp service expose my-api --port 8000 --group my-api --group-key secret123
167
+ \u2192 Both serve at the same URL, frps load-balances between them
168
+
169
+ Examples:
170
+ svamp service expose my-api --port 8000
171
+ svamp service expose my-api --port 8000 --health-check http --health-path /health
172
+ svamp service expose my-api --port 8000 --port 3000
173
+ svamp service expose my-api --port 8000 --group my-api --group-key s3cret
174
+ svamp service serve my-site ./dist
175
+ svamp service serve my-site
176
+ `.trim());
177
+ }
178
+
179
+ export { handleServiceCommand, serviceDelete, serviceExpose, serviceList, serviceServe, serviceTunnel };
@@ -1,11 +1,11 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-DVCdUK0p.mjs';
3
+ import { connectAndGetMachine } from './commands-DrNOSpki.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:child_process';
6
6
  import 'node:path';
7
7
  import 'node:os';
8
- import './run-CdjKXuaY.mjs';
8
+ import './run-YVIJdP0j.mjs';
9
9
  import 'os';
10
10
  import 'fs/promises';
11
11
  import 'url';
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { resolve, join } from 'node:path';
4
4
  import os from 'node:os';
5
- import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-CdjKXuaY.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-YVIJdP0j.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
@@ -1,14 +1,18 @@
1
1
  import { spawn, execSync } from 'child_process';
2
- import { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync } from 'fs';
2
+ import { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync, readFileSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { homedir, platform, arch } from 'os';
5
- import { randomUUID } from 'crypto';
5
+ import { createHash } from 'crypto';
6
6
 
7
7
  const FRP_VERSION = "0.61.1";
8
8
  const BIN_DIR = join(homedir(), ".svamp", "bin");
9
9
  const FRPC_BIN = join(BIN_DIR, platform() === "win32" ? "frpc.exe" : "frpc");
10
- const DEFAULT_FRPS_ADDR = "34.51.246.12";
11
- const DEFAULT_FRPS_PORT = 7e3;
10
+ function isInCluster() {
11
+ return !!(process.env.KUBERNETES_SERVICE_HOST || process.env.SANDBOX_ID);
12
+ }
13
+ const DEFAULT_FRPS_ADDR = isInCluster() ? "frps.hypha.svc.cluster.local" : "frps-ctrl.svc.hypha.aicell.io";
14
+ const DEFAULT_FRPS_PORT = isInCluster() ? 7e3 : 443;
15
+ const FRPS_PUBLIC_TOKEN = "hypha-frps-public";
12
16
  function getFrpcDownloadUrl() {
13
17
  const os = platform();
14
18
  const a = arch();
@@ -80,6 +84,15 @@ async function ensureFrpc(log) {
80
84
  { stdio: "pipe" }
81
85
  );
82
86
  chmodSync(FRPC_BIN, 493);
87
+ const bin = readFileSync(FRPC_BIN);
88
+ const old = Buffer.from("/~!frp");
89
+ const patched = Buffer.from("/frpws");
90
+ const idx = bin.indexOf(old);
91
+ if (idx !== -1) {
92
+ patched.copy(bin, idx);
93
+ writeFileSync(FRPC_BIN, bin);
94
+ logger("Patched frpc WebSocket path: /~!frp \u2192 /frpws");
95
+ }
83
96
  logger(`frpc installed at ${FRPC_BIN}`);
84
97
  } finally {
85
98
  try {
@@ -90,6 +103,7 @@ async function ensureFrpc(log) {
90
103
  return FRPC_BIN;
91
104
  }
92
105
  function generateFrpcConfig(config, proxies) {
106
+ const useWSS = config.serverPort === 443;
93
107
  const lines = [
94
108
  "# Auto-generated by svamp \u2014 do not edit",
95
109
  `serverAddr = "${config.serverAddr}"`,
@@ -98,11 +112,18 @@ function generateFrpcConfig(config, proxies) {
98
112
  'auth.method = "token"',
99
113
  `auth.token = "${config.authToken}"`,
100
114
  "",
115
+ "# Hypha JWT token for server-side authentication",
116
+ ...config.hyphaToken ? [`metadatas.token = "${config.hyphaToken}"`] : [],
117
+ "",
101
118
  "# Transport",
119
+ ...useWSS ? ['transport.protocol = "wss"'] : [],
102
120
  "transport.heartbeatInterval = 30",
103
121
  "transport.heartbeatTimeout = 90",
104
122
  "transport.poolCount = 5",
105
123
  "",
124
+ "# Don't exit on login failure \u2014 let frpc keep retrying",
125
+ "loginFailExit = false",
126
+ "",
106
127
  'log.to = "console"',
107
128
  'log.level = "info"',
108
129
  ""
@@ -119,6 +140,21 @@ function generateFrpcConfig(config, proxies) {
119
140
  if (proxy.customDomains && proxy.customDomains.length > 0) {
120
141
  lines.push(`customDomains = [${proxy.customDomains.map((d) => `"${d}"`).join(", ")}]`);
121
142
  }
143
+ if (proxy.group) {
144
+ lines.push(`loadBalancer.group = "${proxy.group}"`);
145
+ if (proxy.groupKey) {
146
+ lines.push(`loadBalancer.groupKey = "${proxy.groupKey}"`);
147
+ }
148
+ }
149
+ if (proxy.healthCheckType) {
150
+ lines.push(`healthCheck.type = "${proxy.healthCheckType}"`);
151
+ if (proxy.healthCheckType === "http" && proxy.healthCheckPath) {
152
+ lines.push(`healthCheck.path = "${proxy.healthCheckPath}"`);
153
+ }
154
+ lines.push(`healthCheck.intervalSeconds = ${proxy.healthCheckInterval || 10}`);
155
+ lines.push(`healthCheck.timeoutSeconds = ${proxy.healthCheckTimeout || 3}`);
156
+ lines.push(`healthCheck.maxFailed = ${proxy.healthCheckMaxFailed || 3}`);
157
+ }
122
158
  if (proxy.type === "http") {
123
159
  lines.push("transport.useEncryption = false");
124
160
  lines.push("transport.useCompression = false");
@@ -137,20 +173,41 @@ class FrpcTunnel {
137
173
  log;
138
174
  logBuffer = [];
139
175
  maxLogLines = 200;
176
+ proxies = [];
140
177
  constructor(options) {
141
178
  this.options = options;
142
179
  this.log = options.log || ((msg) => console.log(`[FRPC] ${msg}`));
180
+ const hyphaToken = options.serverConfig?.hyphaToken || process.env.HYPHA_TOKEN || "";
143
181
  this.serverConfig = {
144
182
  serverAddr: options.serverConfig?.serverAddr || process.env.FRPS_SERVER_ADDR || DEFAULT_FRPS_ADDR,
145
183
  serverPort: options.serverConfig?.serverPort || parseInt(process.env.FRPS_SERVER_PORT || "", 10) || DEFAULT_FRPS_PORT,
146
- authToken: options.serverConfig?.authToken || process.env.FRPS_AUTH_TOKEN || "",
147
- subDomainHost: options.serverConfig?.subDomainHost || process.env.FRPS_SUBDOMAIN_HOST || "svc.hypha.amun.ai"
184
+ authToken: options.serverConfig?.authToken || process.env.FRPS_AUTH_TOKEN || FRPS_PUBLIC_TOKEN,
185
+ hyphaToken,
186
+ subDomainHost: options.serverConfig?.subDomainHost || process.env.FRPS_SUBDOMAIN_HOST || "svc.hypha.aicell.io"
148
187
  };
149
- if (!this.serverConfig.authToken) {
188
+ if (!this.serverConfig.hyphaToken) {
150
189
  throw new Error(
151
- "frpc auth token required. Set FRPS_AUTH_TOKEN or pass serverConfig.authToken."
190
+ 'Hypha token required for frpc authentication. Run "svamp login" or set HYPHA_TOKEN.'
152
191
  );
153
192
  }
193
+ const machineId = homedir();
194
+ const machineHash = createHash("sha256").update(machineId).digest("hex").slice(0, 6);
195
+ this.proxies = options.ports.map((port) => {
196
+ const subdomain = options.subdomains?.get(port) || (options.group ? `${options.group}-${createHash("sha256").update(options.group).digest("hex").slice(0, 8)}` : `${options.name}-${port}-${createHash("sha256").update(`${options.name}-${port}-${machineId}`).digest("hex").slice(0, 8)}`);
197
+ const proxyName = options.group ? `${options.name}-${port}-${machineHash}` : `${options.name}-${port}`;
198
+ return {
199
+ name: proxyName,
200
+ type: "http",
201
+ localIP: options.localHost || "127.0.0.1",
202
+ localPort: port,
203
+ subdomain,
204
+ group: options.group,
205
+ groupKey: options.groupKey,
206
+ healthCheckType: options.healthCheckType,
207
+ healthCheckPath: options.healthCheckPath,
208
+ healthCheckInterval: options.healthCheckInterval
209
+ };
210
+ });
154
211
  const configDir = join(homedir(), ".svamp", "frpc");
155
212
  mkdirSync(configDir, { recursive: true });
156
213
  this.configPath = join(configDir, `${options.name}.toml`);
@@ -160,17 +217,7 @@ class FrpcTunnel {
160
217
  if (this._destroyed) return;
161
218
  if (this.process) return;
162
219
  const frpcPath = await ensureFrpc(this.log);
163
- const proxies = this.options.ports.map((port, i) => {
164
- const subdomain = this.options.subdomains?.get(port) || `${this.options.name}-${port}-${randomUUID().slice(0, 8)}`;
165
- return {
166
- name: `${this.options.name}-${port}`,
167
- type: "http",
168
- localIP: this.options.localHost || "127.0.0.1",
169
- localPort: port,
170
- subdomain
171
- };
172
- });
173
- const configContent = generateFrpcConfig(this.serverConfig, proxies);
220
+ const configContent = generateFrpcConfig(this.serverConfig, this.proxies);
174
221
  writeFileSync(this.configPath, configContent);
175
222
  this.log(`Config written to ${this.configPath}`);
176
223
  return new Promise((resolve, reject) => {
@@ -279,19 +326,20 @@ class FrpcTunnel {
279
326
  getUrls() {
280
327
  const urls = /* @__PURE__ */ new Map();
281
328
  const base = this.serverConfig.subDomainHost || "svc.hypha.amun.ai";
282
- for (const port of this.options.ports) {
283
- const subdomain = this.options.subdomains?.get(port) || `${this.options.name}-${port}`;
284
- urls.set(port, `https://${subdomain}.${base}`);
329
+ for (const proxy of this.proxies) {
330
+ const subdomain = proxy.subdomain || proxy.name;
331
+ urls.set(proxy.localPort, `https://${subdomain}.${base}`);
285
332
  }
286
333
  return urls;
287
334
  }
288
335
  }
289
- async function runFrpcTunnel(name, ports, serverConfig) {
336
+ async function runFrpcTunnel(name, ports, serverConfig, tunnelOptions) {
290
337
  const portList = ports.join(", ");
291
338
  const tunnel = new FrpcTunnel({
292
339
  name,
293
340
  ports,
294
341
  serverConfig,
342
+ ...tunnelOptions,
295
343
  onConnect: () => {
296
344
  console.log(`Tunnel connected: ${name} \u2192 localhost:[${portList}]`);
297
345
  const urls = tunnel.getUrls();
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-CdjKXuaY.mjs';
1
+ export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-YVIJdP0j.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';