svamp-cli 0.2.44 → 0.2.46

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-vzP5AWB0.mjs');
151
+ const { connectAndGetMachine } = await import('./commands-B6zZtCuh.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-vzP5AWB0.mjs');
168
+ const { resolveSessionId } = await import('./commands-B6zZtCuh.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-DwnThKiy.mjs';
1
+ import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-wfhhtkl1.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-DwnThKiy.mjs').then(function (n) { return n.n; });
45
+ const { restartDaemon } = await import('./run-wfhhtkl1.mjs').then(function (n) { return n.o; });
46
46
  await restartDaemon();
47
47
  process.exit(0);
48
48
  }
@@ -54,6 +54,14 @@ async function main() {
54
54
  process.exit(1);
55
55
  }
56
56
  const { spawn } = await import('child_process');
57
+ const { existsSync, mkdirSync, openSync, closeSync, statSync } = await import('fs');
58
+ const { join } = await import('path');
59
+ const os = await import('os');
60
+ const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
61
+ const logsDir = join(svampHome, "logs");
62
+ mkdirSync(logsDir, { recursive: true });
63
+ const bootLogPath = join(logsDir, "daemon-boot.log");
64
+ const bootFd = openSync(bootLogPath, "a");
57
65
  const extraArgs = [];
58
66
  if (args.includes("--no-auto-continue")) extraArgs.push("--no-auto-continue");
59
67
  const child = spawn(process.execPath, [
@@ -66,17 +74,15 @@ async function main() {
66
74
  ...extraArgs
67
75
  ], {
68
76
  detached: true,
69
- stdio: "ignore",
77
+ stdio: ["ignore", bootFd, bootFd],
70
78
  env: process.env
71
79
  });
80
+ try {
81
+ closeSync(bootFd);
82
+ } catch {
83
+ }
72
84
  child.unref();
73
- const { existsSync } = await import('fs');
74
- const { join } = await import('path');
75
- const os = await import('os');
76
- const stateFile = join(
77
- process.env.SVAMP_HOME || join(os.homedir(), ".svamp"),
78
- "daemon.state.json"
79
- );
85
+ const stateFile = join(svampHome, "daemon.state.json");
80
86
  let started = false;
81
87
  for (let i = 0; i < 100; i++) {
82
88
  await new Promise((r) => setTimeout(r, 100));
@@ -88,7 +94,14 @@ async function main() {
88
94
  if (started) {
89
95
  console.log("Daemon started successfully");
90
96
  } else {
91
- console.error("Failed to start daemon (timeout waiting for state file)");
97
+ let bootHint = "";
98
+ try {
99
+ const st = statSync(bootLogPath);
100
+ if (st.size > 0) bootHint = `
101
+ Check boot log for errors: ${bootLogPath}`;
102
+ } catch {
103
+ }
104
+ console.error(`Failed to start daemon (timeout waiting for state file)${bootHint}`);
92
105
  process.exit(1);
93
106
  }
94
107
  process.exit(0);
@@ -115,7 +128,7 @@ async function main() {
115
128
  releaseSupervisorLock,
116
129
  findOrphanedSyncPids,
117
130
  killOrphanedSyncs
118
- } = await import('./supervisorLock-DwNAn0VN.mjs');
131
+ } = await import('./supervisorLock-DmfzJx7B.mjs');
119
132
  const supervisorPidFile = pathJoin(svampHome, "supervisor.pid");
120
133
  const lock = acquireSupervisorLockWithRetry(supervisorPidFile, process.pid);
121
134
  if (!lock.acquired) {
@@ -264,7 +277,7 @@ async function main() {
264
277
  console.error("svamp service: Service commands are not available in sandboxed sessions.");
265
278
  process.exit(1);
266
279
  }
267
- const { handleServiceCommand } = await import('./commands-DcHNe0bC.mjs');
280
+ const { handleServiceCommand } = await import('./commands-DsCkxa3k.mjs');
268
281
  await handleServiceCommand();
269
282
  } else if (subcommand === "serve") {
270
283
  const { isSandboxed: isSandboxedServe } = await import('./sandboxDetect-DNTcbgWD.mjs');
@@ -272,7 +285,7 @@ async function main() {
272
285
  console.error("svamp serve: Serve commands are not available in sandboxed sessions.");
273
286
  process.exit(1);
274
287
  }
275
- const { handleServeCommand } = await import('./serveCommands-By_bSGUl.mjs');
288
+ const { handleServeCommand } = await import('./serveCommands-DhtNhaur.mjs');
276
289
  await handleServeCommand();
277
290
  process.exit(0);
278
291
  } else if (subcommand === "process" || subcommand === "proc") {
@@ -281,7 +294,7 @@ async function main() {
281
294
  console.error("svamp process: Process commands are not available in sandboxed sessions.");
282
295
  process.exit(1);
283
296
  }
284
- const { processCommand } = await import('./commands-DYNzHqj8.mjs');
297
+ const { processCommand } = await import('./commands-Cs5-T_lh.mjs');
285
298
  let machineId;
286
299
  const processArgs = args.slice(1);
287
300
  const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
@@ -299,7 +312,7 @@ async function main() {
299
312
  } else if (!subcommand || subcommand === "start") {
300
313
  await handleInteractiveCommand();
301
314
  } else if (subcommand === "--version" || subcommand === "-v") {
302
- const pkg = await import('./package-bycncAEw.mjs').catch(() => ({ default: { version: "unknown" } }));
315
+ const pkg = await import('./package-CyXoMaC5.mjs').catch(() => ({ default: { version: "unknown" } }));
303
316
  console.log(`svamp version: ${pkg.default.version}`);
304
317
  } else {
305
318
  console.error(`Unknown command: ${subcommand}`);
@@ -308,7 +321,7 @@ async function main() {
308
321
  }
309
322
  }
310
323
  async function handleInteractiveCommand() {
311
- const { runInteractive } = await import('./run-DBqJkrp6.mjs');
324
+ const { runInteractive } = await import('./run-yJ1mtT8S.mjs');
312
325
  const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
313
326
  let directory = process.cwd();
314
327
  let resumeSessionId;
@@ -353,7 +366,7 @@ async function handleAgentCommand() {
353
366
  return;
354
367
  }
355
368
  if (agentArgs[0] === "list") {
356
- const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-DwnThKiy.mjs').then(function (n) { return n.i; });
369
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-wfhhtkl1.mjs').then(function (n) { return n.j; });
357
370
  console.log("Known agents:");
358
371
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
359
372
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
@@ -365,7 +378,7 @@ async function handleAgentCommand() {
365
378
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
366
379
  return;
367
380
  }
368
- const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-DwnThKiy.mjs').then(function (n) { return n.i; });
381
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-wfhhtkl1.mjs').then(function (n) { return n.j; });
369
382
  let cwd = process.cwd();
370
383
  const filteredArgs = [];
371
384
  for (let i = 0; i < agentArgs.length; i++) {
@@ -389,12 +402,12 @@ async function handleAgentCommand() {
389
402
  console.log(`Starting ${config.agentName} agent in ${cwd}...`);
390
403
  let backend;
391
404
  if (KNOWN_MCP_AGENTS[config.agentName]) {
392
- const { CodexMcpBackend } = await import('./run-DwnThKiy.mjs').then(function (n) { return n.j; });
405
+ const { CodexMcpBackend } = await import('./run-wfhhtkl1.mjs').then(function (n) { return n.k; });
393
406
  backend = new CodexMcpBackend({ cwd, log: logFn });
394
407
  } else {
395
- const { AcpBackend } = await import('./run-DwnThKiy.mjs').then(function (n) { return n.h; });
396
- const { GeminiTransport } = await import('./run-DwnThKiy.mjs').then(function (n) { return n.G; });
397
- const { DefaultTransport } = await import('./run-DwnThKiy.mjs').then(function (n) { return n.D; });
408
+ const { AcpBackend } = await import('./run-wfhhtkl1.mjs').then(function (n) { return n.i; });
409
+ const { GeminiTransport } = await import('./run-wfhhtkl1.mjs').then(function (n) { return n.G; });
410
+ const { DefaultTransport } = await import('./run-wfhhtkl1.mjs').then(function (n) { return n.D; });
398
411
  const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
399
412
  backend = new AcpBackend({
400
413
  agentName: config.agentName,
@@ -521,7 +534,7 @@ async function handleSessionCommand() {
521
534
  process.exit(1);
522
535
  }
523
536
  }
524
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-vzP5AWB0.mjs');
537
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-B6zZtCuh.mjs');
525
538
  const parseFlagStr = (flag, shortFlag) => {
526
539
  for (let i = 1; i < sessionArgs.length; i++) {
527
540
  if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
@@ -581,7 +594,7 @@ async function handleSessionCommand() {
581
594
  allowDomain.push(sessionArgs[++i]);
582
595
  }
583
596
  }
584
- const { parseShareArg } = await import('./commands-vzP5AWB0.mjs');
597
+ const { parseShareArg } = await import('./commands-B6zZtCuh.mjs');
585
598
  const shareEntries = share.map((s) => parseShareArg(s));
586
599
  await sessionSpawn(agent, dir, targetMachineId, {
587
600
  message,
@@ -667,7 +680,7 @@ async function handleSessionCommand() {
667
680
  console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
668
681
  process.exit(1);
669
682
  }
670
- const { sessionApprove } = await import('./commands-vzP5AWB0.mjs');
683
+ const { sessionApprove } = await import('./commands-B6zZtCuh.mjs');
671
684
  const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
672
685
  await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
673
686
  json: hasFlag("--json")
@@ -677,7 +690,7 @@ async function handleSessionCommand() {
677
690
  console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
678
691
  process.exit(1);
679
692
  }
680
- const { sessionDeny } = await import('./commands-vzP5AWB0.mjs');
693
+ const { sessionDeny } = await import('./commands-B6zZtCuh.mjs');
681
694
  const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
682
695
  await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
683
696
  json: hasFlag("--json")
@@ -713,7 +726,7 @@ async function handleSessionCommand() {
713
726
  console.error("Usage: svamp session set-title <title>");
714
727
  process.exit(1);
715
728
  }
716
- const { sessionSetTitle } = await import('./agentCommands-Dlqk61bM.mjs');
729
+ const { sessionSetTitle } = await import('./agentCommands-Bqjcj1EX.mjs');
717
730
  await sessionSetTitle(title);
718
731
  } else if (sessionSubcommand === "set-link") {
719
732
  const url = sessionArgs[1];
@@ -722,7 +735,7 @@ async function handleSessionCommand() {
722
735
  process.exit(1);
723
736
  }
724
737
  const label = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
725
- const { sessionSetLink } = await import('./agentCommands-Dlqk61bM.mjs');
738
+ const { sessionSetLink } = await import('./agentCommands-Bqjcj1EX.mjs');
726
739
  await sessionSetLink(url, label);
727
740
  } else if (sessionSubcommand === "notify") {
728
741
  const message = sessionArgs[1];
@@ -731,7 +744,7 @@ async function handleSessionCommand() {
731
744
  process.exit(1);
732
745
  }
733
746
  const level = parseFlagStr("--level") || "info";
734
- const { sessionNotify } = await import('./agentCommands-Dlqk61bM.mjs');
747
+ const { sessionNotify } = await import('./agentCommands-Bqjcj1EX.mjs');
735
748
  await sessionNotify(message, level);
736
749
  } else if (sessionSubcommand === "broadcast") {
737
750
  const action = sessionArgs[1];
@@ -739,7 +752,7 @@ async function handleSessionCommand() {
739
752
  console.error("Usage: svamp session broadcast <action> [args...]\nActions: open-canvas <url> [label], close-canvas, toast <message>");
740
753
  process.exit(1);
741
754
  }
742
- const { sessionBroadcast } = await import('./agentCommands-Dlqk61bM.mjs');
755
+ const { sessionBroadcast } = await import('./agentCommands-Bqjcj1EX.mjs');
743
756
  await sessionBroadcast(action, sessionArgs.slice(2).filter((a) => !a.startsWith("--")));
744
757
  } else if (sessionSubcommand === "inbox") {
745
758
  const inboxSubcmd = sessionArgs[1];
@@ -750,7 +763,7 @@ async function handleSessionCommand() {
750
763
  process.exit(1);
751
764
  }
752
765
  if (agentSessionId) {
753
- const { inboxSend } = await import('./agentCommands-Dlqk61bM.mjs');
766
+ const { inboxSend } = await import('./agentCommands-Bqjcj1EX.mjs');
754
767
  await inboxSend(sessionArgs[2], {
755
768
  body: sessionArgs[3],
756
769
  subject: parseFlagStr("--subject"),
@@ -765,7 +778,7 @@ async function handleSessionCommand() {
765
778
  }
766
779
  } else if (inboxSubcmd === "list" || inboxSubcmd === "ls") {
767
780
  if (agentSessionId && !sessionArgs[2]) {
768
- const { inboxList } = await import('./agentCommands-Dlqk61bM.mjs');
781
+ const { inboxList } = await import('./agentCommands-Bqjcj1EX.mjs');
769
782
  await inboxList({
770
783
  unread: hasFlag("--unread"),
771
784
  limit: parseFlagInt("--limit"),
@@ -787,7 +800,7 @@ async function handleSessionCommand() {
787
800
  process.exit(1);
788
801
  }
789
802
  if (agentSessionId && !sessionArgs[3]) {
790
- const { inboxList } = await import('./agentCommands-Dlqk61bM.mjs');
803
+ const { inboxList } = await import('./agentCommands-Bqjcj1EX.mjs');
791
804
  await sessionInboxRead(agentSessionId, sessionArgs[2], targetMachineId);
792
805
  } else if (sessionArgs[3]) {
793
806
  await sessionInboxRead(sessionArgs[2], sessionArgs[3], targetMachineId);
@@ -797,7 +810,7 @@ async function handleSessionCommand() {
797
810
  }
798
811
  } else if (inboxSubcmd === "reply") {
799
812
  if (agentSessionId && sessionArgs[2] && sessionArgs[3] && !sessionArgs[4]) {
800
- const { inboxReply } = await import('./agentCommands-Dlqk61bM.mjs');
813
+ const { inboxReply } = await import('./agentCommands-Bqjcj1EX.mjs');
801
814
  await inboxReply(sessionArgs[2], sessionArgs[3]);
802
815
  } else if (sessionArgs[2] && sessionArgs[3] && sessionArgs[4]) {
803
816
  await sessionInboxReply(sessionArgs[2], sessionArgs[3], sessionArgs[4], targetMachineId);
@@ -833,7 +846,7 @@ async function handleMachineCommand() {
833
846
  return;
834
847
  }
835
848
  if (machineSubcommand === "share") {
836
- const { machineShare } = await import('./commands-vzP5AWB0.mjs');
849
+ const { machineShare } = await import('./commands-B6zZtCuh.mjs');
837
850
  let machineId;
838
851
  const shareArgs = [];
839
852
  for (let i = 1; i < machineArgs.length; i++) {
@@ -863,7 +876,7 @@ async function handleMachineCommand() {
863
876
  }
864
877
  await machineShare(machineId, { add, remove, list, configPath, showConfig });
865
878
  } else if (machineSubcommand === "exec") {
866
- const { machineExec } = await import('./commands-vzP5AWB0.mjs');
879
+ const { machineExec } = await import('./commands-B6zZtCuh.mjs');
867
880
  let machineId;
868
881
  let cwd;
869
882
  const cmdParts = [];
@@ -883,7 +896,7 @@ async function handleMachineCommand() {
883
896
  }
884
897
  await machineExec(machineId, command, cwd);
885
898
  } else if (machineSubcommand === "info") {
886
- const { machineInfo } = await import('./commands-vzP5AWB0.mjs');
899
+ const { machineInfo } = await import('./commands-B6zZtCuh.mjs');
887
900
  let machineId;
888
901
  for (let i = 1; i < machineArgs.length; i++) {
889
902
  if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
@@ -903,10 +916,10 @@ async function handleMachineCommand() {
903
916
  level = machineArgs[++i];
904
917
  }
905
918
  }
906
- const { machineNotify } = await import('./agentCommands-Dlqk61bM.mjs');
919
+ const { machineNotify } = await import('./agentCommands-Bqjcj1EX.mjs');
907
920
  await machineNotify(message, level);
908
921
  } else if (machineSubcommand === "ls") {
909
- const { machineLs } = await import('./commands-vzP5AWB0.mjs');
922
+ const { machineLs } = await import('./commands-B6zZtCuh.mjs');
910
923
  let machineId;
911
924
  let showHidden = false;
912
925
  let path;
@@ -1368,7 +1381,7 @@ async function applyClaudeAuthFlags(argv) {
1368
1381
  "--use-hypha-proxy, --use-claude-login, and --anthropic-base-url/--anthropic-api-key are mutually exclusive"
1369
1382
  );
1370
1383
  }
1371
- const mod = await import('./run-DwnThKiy.mjs').then(function (n) { return n.k; });
1384
+ const mod = await import('./run-wfhhtkl1.mjs').then(function (n) { return n.n; });
1372
1385
  if (hasHypha) {
1373
1386
  mod.setClaudeAuthHyphaProxy();
1374
1387
  console.log("Claude auth configured: hypha-proxy (uses HYPHA_TOKEN live at each spawn).");
@@ -1391,7 +1404,7 @@ async function applyClaudeAuthFlags(argv) {
1391
1404
  }
1392
1405
  async function handleDaemonAuthCommand(argv) {
1393
1406
  const sub = (argv[0] || "status").toLowerCase();
1394
- const mod = await import('./run-DwnThKiy.mjs').then(function (n) { return n.k; });
1407
+ const mod = await import('./run-wfhhtkl1.mjs').then(function (n) { return n.n; });
1395
1408
  if (sub === "--help" || sub === "-h" || sub === "help") {
1396
1409
  console.log(`
1397
1410
  svamp daemon auth \u2014 Configure how Claude subprocesses authenticate
@@ -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-DwnThKiy.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-wfhhtkl1.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
@@ -1,11 +1,11 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-vzP5AWB0.mjs';
3
+ import { connectAndGetMachine } from './commands-B6zZtCuh.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-DwnThKiy.mjs';
8
+ import './run-wfhhtkl1.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;
@@ -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-vzP5AWB0.mjs');
100
+ const { connectAndGetMachine } = await import('./commands-B6zZtCuh.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-vzP5AWB0.mjs');
129
+ const { connectAndGetMachine } = await import('./commands-B6zZtCuh.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-DwnThKiy.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-wfhhtkl1.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.44";
2
+ var version = "0.2.46";
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";