svamp-cli 0.2.45 → 0.2.47

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-Cd_I1MXo.mjs');
151
+ const { connectAndGetMachine } = await import('./commands-JWrmpGcs.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-Cd_I1MXo.mjs');
168
+ const { resolveSessionId } = await import('./commands-JWrmpGcs.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-D59qJKn_.mjs';
1
+ import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-6umeTX-K.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -42,7 +42,7 @@ async function main() {
42
42
  console.error(`svamp daemon restart: ${err.message || err}`);
43
43
  process.exit(1);
44
44
  }
45
- const { restartDaemon } = await import('./run-D59qJKn_.mjs').then(function (n) { return n.n; });
45
+ const { restartDaemon } = await import('./run-6umeTX-K.mjs').then(function (n) { return n.o; });
46
46
  await restartDaemon();
47
47
  process.exit(0);
48
48
  }
@@ -277,7 +277,7 @@ async function main() {
277
277
  console.error("svamp service: Service commands are not available in sandboxed sessions.");
278
278
  process.exit(1);
279
279
  }
280
- const { handleServiceCommand } = await import('./commands-C6D6TMSl.mjs');
280
+ const { handleServiceCommand } = await import('./commands-TyAIFJx-.mjs');
281
281
  await handleServiceCommand();
282
282
  } else if (subcommand === "serve") {
283
283
  const { isSandboxed: isSandboxedServe } = await import('./sandboxDetect-DNTcbgWD.mjs');
@@ -285,7 +285,7 @@ async function main() {
285
285
  console.error("svamp serve: Serve commands are not available in sandboxed sessions.");
286
286
  process.exit(1);
287
287
  }
288
- const { handleServeCommand } = await import('./serveCommands-DtKlt1DY.mjs');
288
+ const { handleServeCommand } = await import('./serveCommands-FUE8m232.mjs');
289
289
  await handleServeCommand();
290
290
  process.exit(0);
291
291
  } else if (subcommand === "process" || subcommand === "proc") {
@@ -294,7 +294,7 @@ async function main() {
294
294
  console.error("svamp process: Process commands are not available in sandboxed sessions.");
295
295
  process.exit(1);
296
296
  }
297
- const { processCommand } = await import('./commands-0xDVhPKr.mjs');
297
+ const { processCommand } = await import('./commands-BJR_98XX.mjs');
298
298
  let machineId;
299
299
  const processArgs = args.slice(1);
300
300
  const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
@@ -312,7 +312,7 @@ async function main() {
312
312
  } else if (!subcommand || subcommand === "start") {
313
313
  await handleInteractiveCommand();
314
314
  } else if (subcommand === "--version" || subcommand === "-v") {
315
- const pkg = await import('./package-Cx2tEoke.mjs').catch(() => ({ default: { version: "unknown" } }));
315
+ const pkg = await import('./package-CNFS7wvh.mjs').catch(() => ({ default: { version: "unknown" } }));
316
316
  console.log(`svamp version: ${pkg.default.version}`);
317
317
  } else {
318
318
  console.error(`Unknown command: ${subcommand}`);
@@ -321,7 +321,7 @@ async function main() {
321
321
  }
322
322
  }
323
323
  async function handleInteractiveCommand() {
324
- const { runInteractive } = await import('./run-DZhogQUH.mjs');
324
+ const { runInteractive } = await import('./run-DR7E3IZL.mjs');
325
325
  const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
326
326
  let directory = process.cwd();
327
327
  let resumeSessionId;
@@ -366,7 +366,7 @@ async function handleAgentCommand() {
366
366
  return;
367
367
  }
368
368
  if (agentArgs[0] === "list") {
369
- const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-D59qJKn_.mjs').then(function (n) { return n.i; });
369
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-6umeTX-K.mjs').then(function (n) { return n.j; });
370
370
  console.log("Known agents:");
371
371
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
372
372
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
@@ -378,7 +378,7 @@ async function handleAgentCommand() {
378
378
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
379
379
  return;
380
380
  }
381
- const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-D59qJKn_.mjs').then(function (n) { return n.i; });
381
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-6umeTX-K.mjs').then(function (n) { return n.j; });
382
382
  let cwd = process.cwd();
383
383
  const filteredArgs = [];
384
384
  for (let i = 0; i < agentArgs.length; i++) {
@@ -402,12 +402,12 @@ async function handleAgentCommand() {
402
402
  console.log(`Starting ${config.agentName} agent in ${cwd}...`);
403
403
  let backend;
404
404
  if (KNOWN_MCP_AGENTS[config.agentName]) {
405
- const { CodexMcpBackend } = await import('./run-D59qJKn_.mjs').then(function (n) { return n.j; });
405
+ const { CodexMcpBackend } = await import('./run-6umeTX-K.mjs').then(function (n) { return n.k; });
406
406
  backend = new CodexMcpBackend({ cwd, log: logFn });
407
407
  } else {
408
- const { AcpBackend } = await import('./run-D59qJKn_.mjs').then(function (n) { return n.h; });
409
- const { GeminiTransport } = await import('./run-D59qJKn_.mjs').then(function (n) { return n.G; });
410
- const { DefaultTransport } = await import('./run-D59qJKn_.mjs').then(function (n) { return n.D; });
408
+ const { AcpBackend } = await import('./run-6umeTX-K.mjs').then(function (n) { return n.i; });
409
+ const { GeminiTransport } = await import('./run-6umeTX-K.mjs').then(function (n) { return n.G; });
410
+ const { DefaultTransport } = await import('./run-6umeTX-K.mjs').then(function (n) { return n.D; });
411
411
  const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
412
412
  backend = new AcpBackend({
413
413
  agentName: config.agentName,
@@ -534,7 +534,7 @@ async function handleSessionCommand() {
534
534
  process.exit(1);
535
535
  }
536
536
  }
537
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-Cd_I1MXo.mjs');
537
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-JWrmpGcs.mjs');
538
538
  const parseFlagStr = (flag, shortFlag) => {
539
539
  for (let i = 1; i < sessionArgs.length; i++) {
540
540
  if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
@@ -594,7 +594,7 @@ async function handleSessionCommand() {
594
594
  allowDomain.push(sessionArgs[++i]);
595
595
  }
596
596
  }
597
- const { parseShareArg } = await import('./commands-Cd_I1MXo.mjs');
597
+ const { parseShareArg } = await import('./commands-JWrmpGcs.mjs');
598
598
  const shareEntries = share.map((s) => parseShareArg(s));
599
599
  await sessionSpawn(agent, dir, targetMachineId, {
600
600
  message,
@@ -680,7 +680,7 @@ async function handleSessionCommand() {
680
680
  console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
681
681
  process.exit(1);
682
682
  }
683
- const { sessionApprove } = await import('./commands-Cd_I1MXo.mjs');
683
+ const { sessionApprove } = await import('./commands-JWrmpGcs.mjs');
684
684
  const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
685
685
  await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
686
686
  json: hasFlag("--json")
@@ -690,7 +690,7 @@ async function handleSessionCommand() {
690
690
  console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
691
691
  process.exit(1);
692
692
  }
693
- const { sessionDeny } = await import('./commands-Cd_I1MXo.mjs');
693
+ const { sessionDeny } = await import('./commands-JWrmpGcs.mjs');
694
694
  const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
695
695
  await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
696
696
  json: hasFlag("--json")
@@ -726,7 +726,7 @@ async function handleSessionCommand() {
726
726
  console.error("Usage: svamp session set-title <title>");
727
727
  process.exit(1);
728
728
  }
729
- const { sessionSetTitle } = await import('./agentCommands-dlpOoDcq.mjs');
729
+ const { sessionSetTitle } = await import('./agentCommands-BuGwfYhd.mjs');
730
730
  await sessionSetTitle(title);
731
731
  } else if (sessionSubcommand === "set-link") {
732
732
  const url = sessionArgs[1];
@@ -735,7 +735,7 @@ async function handleSessionCommand() {
735
735
  process.exit(1);
736
736
  }
737
737
  const label = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
738
- const { sessionSetLink } = await import('./agentCommands-dlpOoDcq.mjs');
738
+ const { sessionSetLink } = await import('./agentCommands-BuGwfYhd.mjs');
739
739
  await sessionSetLink(url, label);
740
740
  } else if (sessionSubcommand === "notify") {
741
741
  const message = sessionArgs[1];
@@ -744,7 +744,7 @@ async function handleSessionCommand() {
744
744
  process.exit(1);
745
745
  }
746
746
  const level = parseFlagStr("--level") || "info";
747
- const { sessionNotify } = await import('./agentCommands-dlpOoDcq.mjs');
747
+ const { sessionNotify } = await import('./agentCommands-BuGwfYhd.mjs');
748
748
  await sessionNotify(message, level);
749
749
  } else if (sessionSubcommand === "broadcast") {
750
750
  const action = sessionArgs[1];
@@ -752,7 +752,7 @@ async function handleSessionCommand() {
752
752
  console.error("Usage: svamp session broadcast <action> [args...]\nActions: open-canvas <url> [label], close-canvas, toast <message>");
753
753
  process.exit(1);
754
754
  }
755
- const { sessionBroadcast } = await import('./agentCommands-dlpOoDcq.mjs');
755
+ const { sessionBroadcast } = await import('./agentCommands-BuGwfYhd.mjs');
756
756
  await sessionBroadcast(action, sessionArgs.slice(2).filter((a) => !a.startsWith("--")));
757
757
  } else if (sessionSubcommand === "inbox") {
758
758
  const inboxSubcmd = sessionArgs[1];
@@ -763,7 +763,7 @@ async function handleSessionCommand() {
763
763
  process.exit(1);
764
764
  }
765
765
  if (agentSessionId) {
766
- const { inboxSend } = await import('./agentCommands-dlpOoDcq.mjs');
766
+ const { inboxSend } = await import('./agentCommands-BuGwfYhd.mjs');
767
767
  await inboxSend(sessionArgs[2], {
768
768
  body: sessionArgs[3],
769
769
  subject: parseFlagStr("--subject"),
@@ -778,7 +778,7 @@ async function handleSessionCommand() {
778
778
  }
779
779
  } else if (inboxSubcmd === "list" || inboxSubcmd === "ls") {
780
780
  if (agentSessionId && !sessionArgs[2]) {
781
- const { inboxList } = await import('./agentCommands-dlpOoDcq.mjs');
781
+ const { inboxList } = await import('./agentCommands-BuGwfYhd.mjs');
782
782
  await inboxList({
783
783
  unread: hasFlag("--unread"),
784
784
  limit: parseFlagInt("--limit"),
@@ -800,7 +800,7 @@ async function handleSessionCommand() {
800
800
  process.exit(1);
801
801
  }
802
802
  if (agentSessionId && !sessionArgs[3]) {
803
- const { inboxList } = await import('./agentCommands-dlpOoDcq.mjs');
803
+ const { inboxList } = await import('./agentCommands-BuGwfYhd.mjs');
804
804
  await sessionInboxRead(agentSessionId, sessionArgs[2], targetMachineId);
805
805
  } else if (sessionArgs[3]) {
806
806
  await sessionInboxRead(sessionArgs[2], sessionArgs[3], targetMachineId);
@@ -810,7 +810,7 @@ async function handleSessionCommand() {
810
810
  }
811
811
  } else if (inboxSubcmd === "reply") {
812
812
  if (agentSessionId && sessionArgs[2] && sessionArgs[3] && !sessionArgs[4]) {
813
- const { inboxReply } = await import('./agentCommands-dlpOoDcq.mjs');
813
+ const { inboxReply } = await import('./agentCommands-BuGwfYhd.mjs');
814
814
  await inboxReply(sessionArgs[2], sessionArgs[3]);
815
815
  } else if (sessionArgs[2] && sessionArgs[3] && sessionArgs[4]) {
816
816
  await sessionInboxReply(sessionArgs[2], sessionArgs[3], sessionArgs[4], targetMachineId);
@@ -846,7 +846,7 @@ async function handleMachineCommand() {
846
846
  return;
847
847
  }
848
848
  if (machineSubcommand === "share") {
849
- const { machineShare } = await import('./commands-Cd_I1MXo.mjs');
849
+ const { machineShare } = await import('./commands-JWrmpGcs.mjs');
850
850
  let machineId;
851
851
  const shareArgs = [];
852
852
  for (let i = 1; i < machineArgs.length; i++) {
@@ -876,7 +876,7 @@ async function handleMachineCommand() {
876
876
  }
877
877
  await machineShare(machineId, { add, remove, list, configPath, showConfig });
878
878
  } else if (machineSubcommand === "exec") {
879
- const { machineExec } = await import('./commands-Cd_I1MXo.mjs');
879
+ const { machineExec } = await import('./commands-JWrmpGcs.mjs');
880
880
  let machineId;
881
881
  let cwd;
882
882
  const cmdParts = [];
@@ -896,7 +896,7 @@ async function handleMachineCommand() {
896
896
  }
897
897
  await machineExec(machineId, command, cwd);
898
898
  } else if (machineSubcommand === "info") {
899
- const { machineInfo } = await import('./commands-Cd_I1MXo.mjs');
899
+ const { machineInfo } = await import('./commands-JWrmpGcs.mjs');
900
900
  let machineId;
901
901
  for (let i = 1; i < machineArgs.length; i++) {
902
902
  if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
@@ -916,10 +916,10 @@ async function handleMachineCommand() {
916
916
  level = machineArgs[++i];
917
917
  }
918
918
  }
919
- const { machineNotify } = await import('./agentCommands-dlpOoDcq.mjs');
919
+ const { machineNotify } = await import('./agentCommands-BuGwfYhd.mjs');
920
920
  await machineNotify(message, level);
921
921
  } else if (machineSubcommand === "ls") {
922
- const { machineLs } = await import('./commands-Cd_I1MXo.mjs');
922
+ const { machineLs } = await import('./commands-JWrmpGcs.mjs');
923
923
  let machineId;
924
924
  let showHidden = false;
925
925
  let path;
@@ -1381,7 +1381,7 @@ async function applyClaudeAuthFlags(argv) {
1381
1381
  "--use-hypha-proxy, --use-claude-login, and --anthropic-base-url/--anthropic-api-key are mutually exclusive"
1382
1382
  );
1383
1383
  }
1384
- const mod = await import('./run-D59qJKn_.mjs').then(function (n) { return n.k; });
1384
+ const mod = await import('./run-6umeTX-K.mjs').then(function (n) { return n.n; });
1385
1385
  if (hasHypha) {
1386
1386
  mod.setClaudeAuthHyphaProxy();
1387
1387
  console.log("Claude auth configured: hypha-proxy (uses HYPHA_TOKEN live at each spawn).");
@@ -1404,7 +1404,7 @@ async function applyClaudeAuthFlags(argv) {
1404
1404
  }
1405
1405
  async function handleDaemonAuthCommand(argv) {
1406
1406
  const sub = (argv[0] || "status").toLowerCase();
1407
- const mod = await import('./run-D59qJKn_.mjs').then(function (n) { return n.k; });
1407
+ const mod = await import('./run-6umeTX-K.mjs').then(function (n) { return n.n; });
1408
1408
  if (sub === "--help" || sub === "-h" || sub === "help") {
1409
1409
  console.log(`
1410
1410
  svamp daemon auth \u2014 Configure how Claude subprocesses authenticate
@@ -1,11 +1,11 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-Cd_I1MXo.mjs';
3
+ import { connectAndGetMachine } from './commands-JWrmpGcs.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-D59qJKn_.mjs';
8
+ import './run-6umeTX-K.mjs';
9
9
  import 'os';
10
10
  import 'fs/promises';
11
11
  import 'url';
@@ -186,6 +186,8 @@ function statusIcon(status) {
186
186
  return "\u231B";
187
187
  case "stopping":
188
188
  return "\u2193";
189
+ case "crash-loop-backoff":
190
+ return "\u26A0";
189
191
  default:
190
192
  return "?";
191
193
  }
@@ -211,7 +213,7 @@ function printProcessTable(processes) {
211
213
  return;
212
214
  }
213
215
  const cols = ["NAME", "STATUS", "PID", "RESTARTS", "UPTIME", "PROBE", "TTL"];
214
- const widths = [24, 12, 8, 10, 10, 8, 8];
216
+ const widths = [24, 21, 8, 10, 10, 8, 8];
215
217
  const header = cols.map((c, i) => c.padEnd(widths[i])).join(" ");
216
218
  console.log(header);
217
219
  console.log("\u2500".repeat(header.length));
@@ -239,6 +241,13 @@ Process: ${spec.name}`);
239
241
  console.log(` Keep-alive: ${spec.keepAlive}${spec.maxRestarts > 0 ? ` (max ${spec.maxRestarts})` : ""}`);
240
242
  if (state.restartCount > 0) console.log(` Restarts: ${state.restartCount}`);
241
243
  if (state.startedAt) console.log(` Uptime: ${formatUptime(state.startedAt)}`);
244
+ if (state.crashLoopBackOff) {
245
+ const c = state.crashLoopBackOff;
246
+ const ago = Math.round((Date.now() - c.enteredAt) / 1e3);
247
+ const windowS = Math.round((c.enteredAt - c.windowStart) / 1e3);
248
+ console.log(` CrashLoop: \u26A0 ${c.failureCount} failures in ${windowS}s (entered ${ago}s ago)`);
249
+ console.log(` Run 'svamp process restart ${spec.name}' to retry.`);
250
+ }
242
251
  if (spec.probe) {
243
252
  const p = spec.probe;
244
253
  const lp = state.lastProbe;
@@ -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-D59qJKn_.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-6umeTX-K.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
@@ -55,7 +55,7 @@ async function serviceExpose(args) {
55
55
  console.error("Usage: svamp service expose <name> --port <port> [--port <port2>] [options]");
56
56
  process.exit(1);
57
57
  }
58
- const { runFrpcTunnel } = await import('./frpc-DzRFx60H.mjs');
58
+ const { runFrpcTunnel } = await import('./frpc-j60b46eU.mjs');
59
59
  await runFrpcTunnel(name, ports, void 0, {
60
60
  group,
61
61
  groupKey,
@@ -88,7 +88,7 @@ async function serviceServe(args) {
88
88
  };
89
89
  process.on("SIGINT", cleanup);
90
90
  process.on("SIGTERM", cleanup);
91
- const { runFrpcTunnel } = await import('./frpc-DzRFx60H.mjs');
91
+ const { runFrpcTunnel } = await import('./frpc-j60b46eU.mjs');
92
92
  await runFrpcTunnel(name, [caddyPort]);
93
93
  } catch (err) {
94
94
  console.error(`Error serving directory: ${err.message}`);
@@ -97,7 +97,7 @@ async function serviceServe(args) {
97
97
  }
98
98
  async function serviceList(_args) {
99
99
  try {
100
- const { connectAndGetMachine } = await import('./commands-Cd_I1MXo.mjs');
100
+ const { connectAndGetMachine } = await import('./commands-JWrmpGcs.mjs');
101
101
  const { server, machine } = await connectAndGetMachine();
102
102
  try {
103
103
  const tunnels = await machine.tunnelList({});
@@ -126,7 +126,7 @@ async function serviceDelete(args) {
126
126
  process.exit(1);
127
127
  }
128
128
  try {
129
- const { connectAndGetMachine } = await import('./commands-Cd_I1MXo.mjs');
129
+ const { connectAndGetMachine } = await import('./commands-JWrmpGcs.mjs');
130
130
  const { server, machine } = await connectAndGetMachine();
131
131
  try {
132
132
  await machine.tunnelStop({ name });
@@ -135,8 +135,12 @@ function generateFrpcConfig(config, proxies) {
135
135
  "",
136
136
  "# Transport",
137
137
  ...useWSS ? ['transport.protocol = "wss"'] : [],
138
- "transport.heartbeatInterval = 30",
139
- "transport.heartbeatTimeout = 90",
138
+ // 15s/45s heartbeat — 2x faster failure detection than the frpc default
139
+ // (30s/90s). With matching server-side frps 0.68.x, dead-peer detection
140
+ // settles in well under a minute, so the daemon-side recreate threshold
141
+ // can be tightened too.
142
+ "transport.heartbeatInterval = 15",
143
+ "transport.heartbeatTimeout = 45",
140
144
  "transport.poolCount = 5",
141
145
  "",
142
146
  "# Don't exit on login failure \u2014 let frpc keep retrying",
@@ -198,6 +202,11 @@ class FrpcTunnel {
198
202
  _lastErrorAt = 0;
199
203
  _firstErrorAt = 0;
200
204
  _restartAttempts = 0;
205
+ // End-to-end probe state (set when options.probeUrl is provided).
206
+ _probeTimer = null;
207
+ _lastProbeOkAt = 0;
208
+ _lastProbeFailAt = 0;
209
+ _probeOk = false;
201
210
  constructor(options) {
202
211
  this.options = options;
203
212
  this.log = options.log || ((msg) => console.log(`[FRPC] ${msg}`));
@@ -269,7 +278,8 @@ class FrpcTunnel {
269
278
  this._consecutiveErrors = 0;
270
279
  this._firstErrorAt = 0;
271
280
  this.options.onConnect?.();
272
- if (!resolved) {
281
+ const probeConfigured = !!(this.options.probeUrl || this.options.probePath);
282
+ if (!resolved && !probeConfigured) {
273
283
  resolved = true;
274
284
  resolve();
275
285
  }
@@ -312,6 +322,16 @@ class FrpcTunnel {
312
322
  }, delay);
313
323
  }
314
324
  });
325
+ if (this.options.probeUrl || this.options.probePath) {
326
+ this.startProbeLoop({
327
+ onFirstOk: () => {
328
+ if (!resolved) {
329
+ resolved = true;
330
+ resolve();
331
+ }
332
+ }
333
+ });
334
+ }
315
335
  setTimeout(() => {
316
336
  if (!resolved) {
317
337
  resolved = true;
@@ -321,10 +341,99 @@ class FrpcTunnel {
321
341
  }, 3e4);
322
342
  });
323
343
  }
344
+ /** Resolve the effective probe URL, honoring probeUrl > probePath. */
345
+ resolveProbeUrl() {
346
+ if (this.options.probeUrl) return this.options.probeUrl;
347
+ if (this.options.probePath) {
348
+ const base = this.getUrls().get(this.options.ports[0]);
349
+ if (!base) return null;
350
+ const sep = this.options.probePath.startsWith("/") ? "" : "/";
351
+ return `${base}${sep}${this.options.probePath}`;
352
+ }
353
+ return null;
354
+ }
355
+ /**
356
+ * Start an end-to-end probe loop against the resolved probe URL.
357
+ * Updates _probeOk + timestamps and fires onProbeFail on transitions.
358
+ * Called from connect(); cleaned up in destroy().
359
+ */
360
+ startProbeLoop(opts = {}) {
361
+ const probeUrl = this.resolveProbeUrl();
362
+ if (!probeUrl) return;
363
+ this.stopProbeLoop();
364
+ const intervalMs = this.options.probeIntervalMs ?? 3e4;
365
+ let firstOkFired = false;
366
+ let inFlight = false;
367
+ const runProbe = async () => {
368
+ if (this._destroyed || inFlight) return;
369
+ inFlight = true;
370
+ try {
371
+ const ctrl = new AbortController();
372
+ const timer = setTimeout(() => ctrl.abort(), 1e4);
373
+ let resp;
374
+ try {
375
+ resp = await fetch(probeUrl, {
376
+ method: "GET",
377
+ signal: ctrl.signal,
378
+ // Don't follow redirects — we just want any 2xx/3xx.
379
+ redirect: "manual",
380
+ headers: { "User-Agent": "svamp-frpc-probe/1" }
381
+ });
382
+ } finally {
383
+ clearTimeout(timer);
384
+ }
385
+ if (resp.status < 400) {
386
+ const wasFailing = !this._probeOk;
387
+ this._probeOk = true;
388
+ this._lastProbeOkAt = Date.now();
389
+ if (wasFailing) {
390
+ this.log(`probe ok: ${probeUrl} (${resp.status})`);
391
+ }
392
+ if (!firstOkFired && opts.onFirstOk) {
393
+ firstOkFired = true;
394
+ opts.onFirstOk();
395
+ }
396
+ } else {
397
+ throw new Error(`probe got ${resp.status}`);
398
+ }
399
+ } catch (err) {
400
+ const wasOk = this._probeOk;
401
+ this._probeOk = false;
402
+ this._lastProbeFailAt = Date.now();
403
+ if (wasOk) {
404
+ this.log(`probe failed: ${err?.message || err}`);
405
+ this.options.onProbeFail?.(err instanceof Error ? err : new Error(String(err)));
406
+ }
407
+ } finally {
408
+ inFlight = false;
409
+ }
410
+ };
411
+ const initialDelays = [500, 2e3, 5e3, 1e4];
412
+ let burstIdx = 0;
413
+ const burstTimer = setInterval(() => {
414
+ if (this._destroyed || firstOkFired || burstIdx >= initialDelays.length) {
415
+ clearInterval(burstTimer);
416
+ return;
417
+ }
418
+ burstIdx++;
419
+ void runProbe();
420
+ }, 1500);
421
+ if (intervalMs > 0) {
422
+ this._probeTimer = setInterval(runProbe, intervalMs);
423
+ }
424
+ void runProbe();
425
+ }
426
+ stopProbeLoop() {
427
+ if (this._probeTimer) {
428
+ clearInterval(this._probeTimer);
429
+ this._probeTimer = null;
430
+ }
431
+ }
324
432
  /** Disconnect and stop the frpc process. */
325
433
  destroy() {
326
434
  this._destroyed = true;
327
435
  this._connected = false;
436
+ this.stopProbeLoop();
328
437
  if (this.process) {
329
438
  this.process.kill("SIGTERM");
330
439
  const p = this.process;
@@ -359,7 +468,14 @@ class FrpcTunnel {
359
468
  lastErrorAt: this._lastErrorAt,
360
469
  firstErrorAt: this._firstErrorAt,
361
470
  restartAttempts: this._restartAttempts,
362
- failingDurationMs: this._firstErrorAt > 0 ? Date.now() - this._firstErrorAt : 0
471
+ failingDurationMs: this._firstErrorAt > 0 ? Date.now() - this._firstErrorAt : 0,
472
+ probe: this.resolveProbeUrl() ? {
473
+ url: this.resolveProbeUrl(),
474
+ ok: this._probeOk,
475
+ lastOkAt: this._lastProbeOkAt,
476
+ lastFailAt: this._lastProbeFailAt,
477
+ stalenessMs: this._lastProbeOkAt > 0 ? Date.now() - this._lastProbeOkAt : 0
478
+ } : void 0
363
479
  };
364
480
  }
365
481
  /** Update the Hypha token. Rewrites config; takes effect on next frpc restart. */
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-D59qJKn_.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-6umeTX-K.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.2.45";
2
+ var version = "0.2.47";
3
3
  var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
4
  var author = "Amun AI AB";
5
5
  var license = "SEE LICENSE IN LICENSE";