svamp-cli 0.2.37 → 0.2.39

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-CrTDsMDZ.mjs');
151
+ const { connectAndGetMachine } = await import('./commands-CWKFZm9s.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-CrTDsMDZ.mjs');
168
+ const { resolveSessionId } = await import('./commands-CWKFZm9s.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-Dz0TkCj6.mjs';
1
+ import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-CRwkPQDW.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -36,11 +36,23 @@ async function main() {
36
36
  await logoutFromHypha();
37
37
  } else if (subcommand === "daemon") {
38
38
  if (daemonSubcommand === "restart") {
39
- const { restartDaemon } = await import('./run-Dz0TkCj6.mjs').then(function (n) { return n.k; });
39
+ try {
40
+ await applyClaudeAuthFlags(args);
41
+ } catch (err) {
42
+ console.error(`svamp daemon restart: ${err.message || err}`);
43
+ process.exit(1);
44
+ }
45
+ const { restartDaemon } = await import('./run-CRwkPQDW.mjs').then(function (n) { return n.n; });
40
46
  await restartDaemon();
41
47
  process.exit(0);
42
48
  }
43
49
  if (daemonSubcommand === "start") {
50
+ try {
51
+ await applyClaudeAuthFlags(args);
52
+ } catch (err) {
53
+ console.error(`svamp daemon start: ${err.message || err}`);
54
+ process.exit(1);
55
+ }
44
56
  const { spawn } = await import('child_process');
45
57
  const extraArgs = [];
46
58
  if (args.includes("--no-auto-continue")) extraArgs.push("--no-auto-continue");
@@ -82,10 +94,11 @@ async function main() {
82
94
  process.exit(0);
83
95
  } else if (daemonSubcommand === "start-supervised") {
84
96
  const { spawn: spawnChild } = await import('child_process');
85
- const { appendFileSync, mkdirSync, existsSync: fsExists } = await import('fs');
97
+ const { appendFileSync, mkdirSync } = await import('fs');
86
98
  const { join: pathJoin } = await import('path');
87
99
  const osModule = await import('os');
88
100
  const svampHome = process.env.SVAMP_HOME || pathJoin(osModule.homedir(), ".svamp");
101
+ mkdirSync(svampHome, { recursive: true });
89
102
  const logsDir = pathJoin(svampHome, "logs");
90
103
  mkdirSync(logsDir, { recursive: true });
91
104
  const logFile = pathJoin(logsDir, "daemon-supervised.log");
@@ -97,13 +110,27 @@ async function main() {
97
110
  } catch {
98
111
  }
99
112
  };
113
+ const {
114
+ acquireSupervisorLockWithRetry,
115
+ releaseSupervisorLock,
116
+ findOrphanedSyncPids,
117
+ killOrphanedSyncs
118
+ } = await import('./supervisorLock-DwNAn0VN.mjs');
100
119
  const supervisorPidFile = pathJoin(svampHome, "supervisor.pid");
120
+ const lock = acquireSupervisorLockWithRetry(supervisorPidFile, process.pid);
121
+ if (!lock.acquired) {
122
+ log(`Another supervisor is already running (PID ${lock.heldBy}) \u2014 exiting`);
123
+ console.error(`svamp daemon: another supervisor is already running (PID ${lock.heldBy}).`);
124
+ process.exit(0);
125
+ }
101
126
  try {
102
- appendFileSync(supervisorPidFile, "");
103
- } catch {
127
+ const orphans = findOrphanedSyncPids(process.pid);
128
+ if (orphans.length > 0) {
129
+ await killOrphanedSyncs(orphans, { log, gracePeriodMs: 3e3 });
130
+ }
131
+ } catch (err) {
132
+ log(`Orphan scan failed (non-fatal): ${err?.message || err}`);
104
133
  }
105
- const { writeFileSync: wfs } = await import('fs');
106
- wfs(supervisorPidFile, String(process.pid), "utf-8");
107
134
  const extraSyncArgs = [];
108
135
  if (args.includes("--no-auto-continue")) extraSyncArgs.push("--no-auto-continue");
109
136
  const BASE_DELAY_MS = 2e3;
@@ -188,11 +215,7 @@ async function main() {
188
215
  setTimeout(() => clearInterval(checkStop), delay + 100);
189
216
  });
190
217
  }
191
- try {
192
- const { unlinkSync: us } = await import('fs');
193
- us(supervisorPidFile);
194
- } catch {
195
- }
218
+ releaseSupervisorLock(supervisorPidFile, process.pid);
196
219
  process.exit(0);
197
220
  } else if (daemonSubcommand === "start-sync") {
198
221
  const noAutoContinue = args.includes("--no-auto-continue");
@@ -211,6 +234,9 @@ async function main() {
211
234
  } else if (daemonSubcommand === "uninstall") {
212
235
  await uninstallDaemonService();
213
236
  process.exit(0);
237
+ } else if (daemonSubcommand === "auth") {
238
+ await handleDaemonAuthCommand(args.slice(2));
239
+ process.exit(0);
214
240
  } else {
215
241
  printDaemonHelp();
216
242
  }
@@ -246,7 +272,7 @@ async function main() {
246
272
  console.error("svamp serve: Serve commands are not available in sandboxed sessions.");
247
273
  process.exit(1);
248
274
  }
249
- const { handleServeCommand } = await import('./serveCommands-FnBtBifV.mjs');
275
+ const { handleServeCommand } = await import('./serveCommands-B9Dq2loo.mjs');
250
276
  await handleServeCommand();
251
277
  process.exit(0);
252
278
  } else if (subcommand === "process" || subcommand === "proc") {
@@ -255,7 +281,7 @@ async function main() {
255
281
  console.error("svamp process: Process commands are not available in sandboxed sessions.");
256
282
  process.exit(1);
257
283
  }
258
- const { processCommand } = await import('./commands-Dz_JXrcs.mjs');
284
+ const { processCommand } = await import('./commands-DVXppwx2.mjs');
259
285
  let machineId;
260
286
  const processArgs = args.slice(1);
261
287
  const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
@@ -273,7 +299,7 @@ async function main() {
273
299
  } else if (!subcommand || subcommand === "start") {
274
300
  await handleInteractiveCommand();
275
301
  } else if (subcommand === "--version" || subcommand === "-v") {
276
- const pkg = await import('./package-DRX_LQ92.mjs').catch(() => ({ default: { version: "unknown" } }));
302
+ const pkg = await import('./package-CPiu8-uW.mjs').catch(() => ({ default: { version: "unknown" } }));
277
303
  console.log(`svamp version: ${pkg.default.version}`);
278
304
  } else {
279
305
  console.error(`Unknown command: ${subcommand}`);
@@ -282,7 +308,7 @@ async function main() {
282
308
  }
283
309
  }
284
310
  async function handleInteractiveCommand() {
285
- const { runInteractive } = await import('./run-BckEhiLq.mjs');
311
+ const { runInteractive } = await import('./run--6SzG2CH.mjs');
286
312
  const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
287
313
  let directory = process.cwd();
288
314
  let resumeSessionId;
@@ -327,7 +353,7 @@ async function handleAgentCommand() {
327
353
  return;
328
354
  }
329
355
  if (agentArgs[0] === "list") {
330
- const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-Dz0TkCj6.mjs').then(function (n) { return n.i; });
356
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-CRwkPQDW.mjs').then(function (n) { return n.i; });
331
357
  console.log("Known agents:");
332
358
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
333
359
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
@@ -339,7 +365,7 @@ async function handleAgentCommand() {
339
365
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
340
366
  return;
341
367
  }
342
- const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-Dz0TkCj6.mjs').then(function (n) { return n.i; });
368
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-CRwkPQDW.mjs').then(function (n) { return n.i; });
343
369
  let cwd = process.cwd();
344
370
  const filteredArgs = [];
345
371
  for (let i = 0; i < agentArgs.length; i++) {
@@ -363,12 +389,12 @@ async function handleAgentCommand() {
363
389
  console.log(`Starting ${config.agentName} agent in ${cwd}...`);
364
390
  let backend;
365
391
  if (KNOWN_MCP_AGENTS[config.agentName]) {
366
- const { CodexMcpBackend } = await import('./run-Dz0TkCj6.mjs').then(function (n) { return n.j; });
392
+ const { CodexMcpBackend } = await import('./run-CRwkPQDW.mjs').then(function (n) { return n.j; });
367
393
  backend = new CodexMcpBackend({ cwd, log: logFn });
368
394
  } else {
369
- const { AcpBackend } = await import('./run-Dz0TkCj6.mjs').then(function (n) { return n.h; });
370
- const { GeminiTransport } = await import('./run-Dz0TkCj6.mjs').then(function (n) { return n.G; });
371
- const { DefaultTransport } = await import('./run-Dz0TkCj6.mjs').then(function (n) { return n.D; });
395
+ const { AcpBackend } = await import('./run-CRwkPQDW.mjs').then(function (n) { return n.h; });
396
+ const { GeminiTransport } = await import('./run-CRwkPQDW.mjs').then(function (n) { return n.G; });
397
+ const { DefaultTransport } = await import('./run-CRwkPQDW.mjs').then(function (n) { return n.D; });
372
398
  const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
373
399
  backend = new AcpBackend({
374
400
  agentName: config.agentName,
@@ -495,7 +521,7 @@ async function handleSessionCommand() {
495
521
  process.exit(1);
496
522
  }
497
523
  }
498
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-CrTDsMDZ.mjs');
524
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-CWKFZm9s.mjs');
499
525
  const parseFlagStr = (flag, shortFlag) => {
500
526
  for (let i = 1; i < sessionArgs.length; i++) {
501
527
  if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
@@ -555,7 +581,7 @@ async function handleSessionCommand() {
555
581
  allowDomain.push(sessionArgs[++i]);
556
582
  }
557
583
  }
558
- const { parseShareArg } = await import('./commands-CrTDsMDZ.mjs');
584
+ const { parseShareArg } = await import('./commands-CWKFZm9s.mjs');
559
585
  const shareEntries = share.map((s) => parseShareArg(s));
560
586
  await sessionSpawn(agent, dir, targetMachineId, {
561
587
  message,
@@ -641,7 +667,7 @@ async function handleSessionCommand() {
641
667
  console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
642
668
  process.exit(1);
643
669
  }
644
- const { sessionApprove } = await import('./commands-CrTDsMDZ.mjs');
670
+ const { sessionApprove } = await import('./commands-CWKFZm9s.mjs');
645
671
  const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
646
672
  await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
647
673
  json: hasFlag("--json")
@@ -651,7 +677,7 @@ async function handleSessionCommand() {
651
677
  console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
652
678
  process.exit(1);
653
679
  }
654
- const { sessionDeny } = await import('./commands-CrTDsMDZ.mjs');
680
+ const { sessionDeny } = await import('./commands-CWKFZm9s.mjs');
655
681
  const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
656
682
  await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
657
683
  json: hasFlag("--json")
@@ -687,7 +713,7 @@ async function handleSessionCommand() {
687
713
  console.error("Usage: svamp session set-title <title>");
688
714
  process.exit(1);
689
715
  }
690
- const { sessionSetTitle } = await import('./agentCommands-C88l82pM.mjs');
716
+ const { sessionSetTitle } = await import('./agentCommands-BvmStLoh.mjs');
691
717
  await sessionSetTitle(title);
692
718
  } else if (sessionSubcommand === "set-link") {
693
719
  const url = sessionArgs[1];
@@ -696,7 +722,7 @@ async function handleSessionCommand() {
696
722
  process.exit(1);
697
723
  }
698
724
  const label = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
699
- const { sessionSetLink } = await import('./agentCommands-C88l82pM.mjs');
725
+ const { sessionSetLink } = await import('./agentCommands-BvmStLoh.mjs');
700
726
  await sessionSetLink(url, label);
701
727
  } else if (sessionSubcommand === "notify") {
702
728
  const message = sessionArgs[1];
@@ -705,7 +731,7 @@ async function handleSessionCommand() {
705
731
  process.exit(1);
706
732
  }
707
733
  const level = parseFlagStr("--level") || "info";
708
- const { sessionNotify } = await import('./agentCommands-C88l82pM.mjs');
734
+ const { sessionNotify } = await import('./agentCommands-BvmStLoh.mjs');
709
735
  await sessionNotify(message, level);
710
736
  } else if (sessionSubcommand === "broadcast") {
711
737
  const action = sessionArgs[1];
@@ -713,7 +739,7 @@ async function handleSessionCommand() {
713
739
  console.error("Usage: svamp session broadcast <action> [args...]\nActions: open-canvas <url> [label], close-canvas, toast <message>");
714
740
  process.exit(1);
715
741
  }
716
- const { sessionBroadcast } = await import('./agentCommands-C88l82pM.mjs');
742
+ const { sessionBroadcast } = await import('./agentCommands-BvmStLoh.mjs');
717
743
  await sessionBroadcast(action, sessionArgs.slice(2).filter((a) => !a.startsWith("--")));
718
744
  } else if (sessionSubcommand === "inbox") {
719
745
  const inboxSubcmd = sessionArgs[1];
@@ -724,7 +750,7 @@ async function handleSessionCommand() {
724
750
  process.exit(1);
725
751
  }
726
752
  if (agentSessionId) {
727
- const { inboxSend } = await import('./agentCommands-C88l82pM.mjs');
753
+ const { inboxSend } = await import('./agentCommands-BvmStLoh.mjs');
728
754
  await inboxSend(sessionArgs[2], {
729
755
  body: sessionArgs[3],
730
756
  subject: parseFlagStr("--subject"),
@@ -739,7 +765,7 @@ async function handleSessionCommand() {
739
765
  }
740
766
  } else if (inboxSubcmd === "list" || inboxSubcmd === "ls") {
741
767
  if (agentSessionId && !sessionArgs[2]) {
742
- const { inboxList } = await import('./agentCommands-C88l82pM.mjs');
768
+ const { inboxList } = await import('./agentCommands-BvmStLoh.mjs');
743
769
  await inboxList({
744
770
  unread: hasFlag("--unread"),
745
771
  limit: parseFlagInt("--limit"),
@@ -761,7 +787,7 @@ async function handleSessionCommand() {
761
787
  process.exit(1);
762
788
  }
763
789
  if (agentSessionId && !sessionArgs[3]) {
764
- const { inboxList } = await import('./agentCommands-C88l82pM.mjs');
790
+ const { inboxList } = await import('./agentCommands-BvmStLoh.mjs');
765
791
  await sessionInboxRead(agentSessionId, sessionArgs[2], targetMachineId);
766
792
  } else if (sessionArgs[3]) {
767
793
  await sessionInboxRead(sessionArgs[2], sessionArgs[3], targetMachineId);
@@ -771,7 +797,7 @@ async function handleSessionCommand() {
771
797
  }
772
798
  } else if (inboxSubcmd === "reply") {
773
799
  if (agentSessionId && sessionArgs[2] && sessionArgs[3] && !sessionArgs[4]) {
774
- const { inboxReply } = await import('./agentCommands-C88l82pM.mjs');
800
+ const { inboxReply } = await import('./agentCommands-BvmStLoh.mjs');
775
801
  await inboxReply(sessionArgs[2], sessionArgs[3]);
776
802
  } else if (sessionArgs[2] && sessionArgs[3] && sessionArgs[4]) {
777
803
  await sessionInboxReply(sessionArgs[2], sessionArgs[3], sessionArgs[4], targetMachineId);
@@ -807,7 +833,7 @@ async function handleMachineCommand() {
807
833
  return;
808
834
  }
809
835
  if (machineSubcommand === "share") {
810
- const { machineShare } = await import('./commands-CrTDsMDZ.mjs');
836
+ const { machineShare } = await import('./commands-CWKFZm9s.mjs');
811
837
  let machineId;
812
838
  const shareArgs = [];
813
839
  for (let i = 1; i < machineArgs.length; i++) {
@@ -837,7 +863,7 @@ async function handleMachineCommand() {
837
863
  }
838
864
  await machineShare(machineId, { add, remove, list, configPath, showConfig });
839
865
  } else if (machineSubcommand === "exec") {
840
- const { machineExec } = await import('./commands-CrTDsMDZ.mjs');
866
+ const { machineExec } = await import('./commands-CWKFZm9s.mjs');
841
867
  let machineId;
842
868
  let cwd;
843
869
  const cmdParts = [];
@@ -857,7 +883,7 @@ async function handleMachineCommand() {
857
883
  }
858
884
  await machineExec(machineId, command, cwd);
859
885
  } else if (machineSubcommand === "info") {
860
- const { machineInfo } = await import('./commands-CrTDsMDZ.mjs');
886
+ const { machineInfo } = await import('./commands-CWKFZm9s.mjs');
861
887
  let machineId;
862
888
  for (let i = 1; i < machineArgs.length; i++) {
863
889
  if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
@@ -877,10 +903,10 @@ async function handleMachineCommand() {
877
903
  level = machineArgs[++i];
878
904
  }
879
905
  }
880
- const { machineNotify } = await import('./agentCommands-C88l82pM.mjs');
906
+ const { machineNotify } = await import('./agentCommands-BvmStLoh.mjs');
881
907
  await machineNotify(message, level);
882
908
  } else if (machineSubcommand === "ls") {
883
- const { machineLs } = await import('./commands-CrTDsMDZ.mjs');
909
+ const { machineLs } = await import('./commands-CWKFZm9s.mjs');
884
910
  let machineId;
885
911
  let showHidden = false;
886
912
  let path;
@@ -1318,8 +1344,92 @@ Usage:
1318
1344
  svamp daemon status Show daemon status
1319
1345
  svamp daemon install Install as login service (macOS/Linux) \u2014 auto-start at login
1320
1346
  svamp daemon uninstall Remove login service
1347
+ svamp daemon auth Show Claude API auth mode used for spawned sessions
1348
+ svamp daemon auth use-hypha-proxy Route Claude through https://proxy.hypha.aicell.io with HYPHA_TOKEN
1349
+ svamp daemon auth use-login Use ~/.claude credentials (run "claude login" to set them)
1350
+ svamp daemon auth set <URL> <KEY> Use a custom Anthropic gateway (base URL must not end in /v1)
1351
+
1352
+ Claude auth flags (take effect on next start/restart):
1353
+ svamp daemon start --use-hypha-proxy
1354
+ svamp daemon start --use-claude-login
1355
+ svamp daemon start --anthropic-base-url URL --anthropic-api-key KEY
1321
1356
  `);
1322
1357
  }
1358
+ async function applyClaudeAuthFlags(argv) {
1359
+ const hasHypha = argv.includes("--use-hypha-proxy");
1360
+ const hasLogin = argv.includes("--use-claude-login") || argv.includes("--use-login");
1361
+ const baseUrlIdx = argv.indexOf("--anthropic-base-url");
1362
+ const apiKeyIdx = argv.indexOf("--anthropic-api-key");
1363
+ const hasCustom = baseUrlIdx !== -1 || apiKeyIdx !== -1;
1364
+ const chosen = [hasHypha, hasLogin, hasCustom].filter(Boolean).length;
1365
+ if (chosen === 0) return;
1366
+ if (chosen > 1) {
1367
+ throw new Error(
1368
+ "--use-hypha-proxy, --use-claude-login, and --anthropic-base-url/--anthropic-api-key are mutually exclusive"
1369
+ );
1370
+ }
1371
+ const mod = await import('./run-CRwkPQDW.mjs').then(function (n) { return n.k; });
1372
+ if (hasHypha) {
1373
+ mod.setClaudeAuthHyphaProxy();
1374
+ console.log("Claude auth configured: hypha-proxy (uses HYPHA_TOKEN live at each spawn).");
1375
+ return;
1376
+ }
1377
+ if (hasLogin) {
1378
+ mod.setClaudeAuthLogin();
1379
+ console.log('Claude auth configured: login (uses ~/.claude credentials \u2014 run "claude login" if needed).');
1380
+ return;
1381
+ }
1382
+ if (baseUrlIdx === -1 || apiKeyIdx === -1) {
1383
+ throw new Error("--anthropic-base-url and --anthropic-api-key must be passed together");
1384
+ }
1385
+ const baseUrl = argv[baseUrlIdx + 1];
1386
+ const apiKey = argv[apiKeyIdx + 1];
1387
+ if (!baseUrl || baseUrl.startsWith("--")) throw new Error("--anthropic-base-url requires a value");
1388
+ if (!apiKey || apiKey.startsWith("--")) throw new Error("--anthropic-api-key requires a value");
1389
+ mod.setClaudeAuthCustom(baseUrl, apiKey);
1390
+ console.log(`Claude auth configured: custom (base URL ${baseUrl}).`);
1391
+ }
1392
+ async function handleDaemonAuthCommand(argv) {
1393
+ const sub = (argv[0] || "status").toLowerCase();
1394
+ const mod = await import('./run-CRwkPQDW.mjs').then(function (n) { return n.k; });
1395
+ if (sub === "status" || sub === "show") {
1396
+ const s = mod.getClaudeAuthStatus();
1397
+ console.log(`Claude auth mode: ${s.mode}`);
1398
+ if (s.baseUrl) console.log(` ANTHROPIC_BASE_URL: ${s.baseUrl}`);
1399
+ if (s.apiKeyPreview) console.log(` ANTHROPIC_API_KEY : ${s.apiKeyPreview}`);
1400
+ if (s.mode === "login") {
1401
+ console.log(' (Claude subprocesses will use credentials from ~/.claude \u2014 run "claude login" to set them.)');
1402
+ }
1403
+ if (s.hyphaTokenMissing) {
1404
+ console.log(' WARNING: mode=hypha but HYPHA_TOKEN is not set \u2014 run "svamp login" first.');
1405
+ }
1406
+ return;
1407
+ }
1408
+ if (sub === "use-hypha-proxy") {
1409
+ mod.setClaudeAuthHyphaProxy();
1410
+ console.log('Claude auth set to hypha-proxy. Run "svamp daemon restart" to apply.');
1411
+ return;
1412
+ }
1413
+ if (sub === "use-login" || sub === "use-claude-login") {
1414
+ mod.setClaudeAuthLogin();
1415
+ console.log('Claude auth set to login (~/.claude credentials). Run "svamp daemon restart" to apply.');
1416
+ return;
1417
+ }
1418
+ if (sub === "set") {
1419
+ const baseUrl = argv[1];
1420
+ const apiKey = argv[2];
1421
+ if (!baseUrl || !apiKey) {
1422
+ console.error("Usage: svamp daemon auth set <base-url> <api-key>");
1423
+ process.exit(1);
1424
+ }
1425
+ mod.setClaudeAuthCustom(baseUrl, apiKey);
1426
+ console.log(`Claude auth set to custom (${baseUrl}). Run "svamp daemon restart" to apply.`);
1427
+ return;
1428
+ }
1429
+ console.error(`Unknown subcommand: svamp daemon auth ${sub}`);
1430
+ console.error("Available: status, use-hypha-proxy, use-login, set <URL> <KEY>");
1431
+ process.exit(1);
1432
+ }
1323
1433
  function printSessionHelp() {
1324
1434
  console.log(`
1325
1435
  svamp session \u2014 Spawn and manage AI agent sessions
@@ -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-Dz0TkCj6.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-CRwkPQDW.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-CrTDsMDZ.mjs';
3
+ import { connectAndGetMachine } from './commands-CWKFZm9s.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-Dz0TkCj6.mjs';
8
+ import './run-CRwkPQDW.mjs';
9
9
  import 'os';
10
10
  import 'fs/promises';
11
11
  import 'url';
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-Dz0TkCj6.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-CRwkPQDW.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -0,0 +1,63 @@
1
+ var name = "svamp-cli";
2
+ var version = "0.2.39";
3
+ var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
+ var author = "Amun AI AB";
5
+ var license = "SEE LICENSE IN LICENSE";
6
+ var type = "module";
7
+ var bin = {
8
+ svamp: "./bin/svamp.mjs"
9
+ };
10
+ var files = [
11
+ "dist",
12
+ "bin"
13
+ ];
14
+ var main = "./dist/index.mjs";
15
+ var exports$1 = {
16
+ ".": "./dist/index.mjs",
17
+ "./cli": "./dist/cli.mjs"
18
+ };
19
+ var scripts = {
20
+ build: "rm -rf dist && tsc --noEmit && pkgroll",
21
+ typecheck: "tsc --noEmit",
22
+ test: "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only",
23
+ "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
+ dev: "tsx src/cli.ts",
25
+ "dev:daemon": "tsx src/cli.ts daemon start-sync",
26
+ "test:e2e": "node --no-warnings test/e2e-session-tests.mjs",
27
+ "test:frpc": "npx tsx test/test-frpc-e2e.mjs"
28
+ };
29
+ var dependencies = {
30
+ "@agentclientprotocol/sdk": "^0.14.1",
31
+ "@modelcontextprotocol/sdk": "^1.25.3",
32
+ "hypha-rpc": "0.21.36",
33
+ "node-pty": "1.2.0-beta.11",
34
+ ws: "^8.18.0",
35
+ yaml: "^2.8.2",
36
+ zod: "^3.24.4"
37
+ };
38
+ var devDependencies = {
39
+ "@types/node": ">=20",
40
+ "@types/ws": "^8.5.14",
41
+ pkgroll: "^2.14.2",
42
+ tsx: "^4.20.6",
43
+ typescript: "5.9.3"
44
+ };
45
+ var packageManager = "yarn@1.22.22";
46
+ var _package = {
47
+ name: name,
48
+ version: version,
49
+ description: description,
50
+ author: author,
51
+ license: license,
52
+ type: type,
53
+ bin: bin,
54
+ files: files,
55
+ main: main,
56
+ exports: exports$1,
57
+ scripts: scripts,
58
+ dependencies: dependencies,
59
+ devDependencies: devDependencies,
60
+ packageManager: packageManager
61
+ };
62
+
63
+ export { author, bin, _package as default, dependencies, description, devDependencies, exports$1 as exports, files, license, main, name, packageManager, scripts, type, version };
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
2
2
  import os from 'node:os';
3
3
  import { join, resolve } from 'node:path';
4
4
  import { mkdirSync, writeFileSync, existsSync, unlinkSync, readFileSync, watch } from 'node:fs';
5
- import { c as connectToHypha, a as registerSessionService } from './run-Dz0TkCj6.mjs';
5
+ import { c as connectToHypha, a as registerSessionService } from './run-CRwkPQDW.mjs';
6
6
  import { createServer } from 'node:http';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { createInterface } from 'node:readline';
@@ -4922,6 +4922,170 @@ var credentialStaging = /*#__PURE__*/Object.freeze({
4922
4922
  stageCredentialsForSharing: stageCredentialsForSharing
4923
4923
  });
4924
4924
 
4925
+ function shouldIsolate(input) {
4926
+ if (input.forceIsolation) return true;
4927
+ const sessionCtx = input.sessionSecurityContext;
4928
+ if (sessionCtx === null) return false;
4929
+ if (sessionCtx !== void 0) return true;
4930
+ return input.optionsSecurityContext !== null && input.optionsSecurityContext !== void 0;
4931
+ }
4932
+
4933
+ const HYPHA_PROXY_BASE_URL = "https://proxy.hypha.aicell.io";
4934
+ const MODE_KEY = "SVAMP_CLAUDE_PROXY";
4935
+ const MANAGED_KEYS = /* @__PURE__ */ new Set([
4936
+ MODE_KEY,
4937
+ "ANTHROPIC_BASE_URL",
4938
+ "ANTHROPIC_API_KEY"
4939
+ ]);
4940
+ function envFilePath() {
4941
+ const svampHome = process.env.SVAMP_HOME || join$1(homedir(), ".svamp");
4942
+ return join$1(svampHome, ".env");
4943
+ }
4944
+ function readEnvLines() {
4945
+ const file = envFilePath();
4946
+ if (!existsSync(file)) return [];
4947
+ return readFileSync(file, "utf-8").split("\n");
4948
+ }
4949
+ function writeEnvLines(lines) {
4950
+ const file = envFilePath();
4951
+ const dir = join$1(file, "..");
4952
+ if (!existsSync(dir)) mkdirSync$1(dir, { recursive: true });
4953
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
4954
+ lines.pop();
4955
+ }
4956
+ writeFileSync$1(file, lines.join("\n") + "\n", "utf-8");
4957
+ }
4958
+ function updateEnvFile(updates) {
4959
+ const lines = readEnvLines();
4960
+ const kept = [];
4961
+ const remainingUpdates = new Map(Object.entries(updates));
4962
+ for (const line of lines) {
4963
+ const trimmed = line.trim();
4964
+ if (!trimmed || trimmed.startsWith("#")) {
4965
+ kept.push(line);
4966
+ continue;
4967
+ }
4968
+ const eq = trimmed.indexOf("=");
4969
+ const key = eq === -1 ? trimmed : trimmed.slice(0, eq);
4970
+ if (remainingUpdates.has(key)) {
4971
+ const v = remainingUpdates.get(key);
4972
+ remainingUpdates.delete(key);
4973
+ if (v === void 0) continue;
4974
+ kept.push(`${key}=${v}`);
4975
+ continue;
4976
+ }
4977
+ kept.push(line);
4978
+ }
4979
+ for (const [key, v] of remainingUpdates) {
4980
+ if (v === void 0) continue;
4981
+ kept.push(`${key}=${v}`);
4982
+ }
4983
+ writeEnvLines(kept);
4984
+ }
4985
+ function currentMode() {
4986
+ const raw = (process.env[MODE_KEY] || "").trim().toLowerCase();
4987
+ if (raw === "hypha" || raw === "hypha-proxy") return "hypha";
4988
+ if (raw === "custom") return "custom";
4989
+ return "login";
4990
+ }
4991
+ function redactKey(key) {
4992
+ if (!key) return void 0;
4993
+ if (key.length <= 12) return "***";
4994
+ return `${key.slice(0, 8)}\u2026${key.slice(-4)}`;
4995
+ }
4996
+ function getClaudeAuthStatus() {
4997
+ const mode = currentMode();
4998
+ if (mode === "hypha") {
4999
+ const token = process.env.HYPHA_TOKEN;
5000
+ return {
5001
+ mode,
5002
+ baseUrl: HYPHA_PROXY_BASE_URL,
5003
+ apiKeyPreview: redactKey(token),
5004
+ hyphaTokenMissing: !token
5005
+ };
5006
+ }
5007
+ if (mode === "custom") {
5008
+ return {
5009
+ mode,
5010
+ baseUrl: process.env.ANTHROPIC_BASE_URL,
5011
+ apiKeyPreview: redactKey(process.env.ANTHROPIC_API_KEY)
5012
+ };
5013
+ }
5014
+ return { mode: "login" };
5015
+ }
5016
+ function setClaudeAuthHyphaProxy() {
5017
+ updateEnvFile({
5018
+ [MODE_KEY]: "hypha",
5019
+ // Hypha mode resolves token live at spawn — no stored copy.
5020
+ ANTHROPIC_BASE_URL: void 0,
5021
+ ANTHROPIC_API_KEY: void 0
5022
+ });
5023
+ }
5024
+ function setClaudeAuthLogin() {
5025
+ updateEnvFile({
5026
+ [MODE_KEY]: void 0,
5027
+ ANTHROPIC_BASE_URL: void 0,
5028
+ ANTHROPIC_API_KEY: void 0
5029
+ });
5030
+ }
5031
+ function setClaudeAuthCustom(baseUrl, apiKey) {
5032
+ if (!baseUrl) throw new Error("ANTHROPIC_BASE_URL is required for custom mode");
5033
+ if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required for custom mode");
5034
+ const trimmed = baseUrl.replace(/\/+$/, "");
5035
+ if (trimmed.endsWith("/v1")) {
5036
+ throw new Error(
5037
+ "ANTHROPIC_BASE_URL must not end in /v1 \u2014 the SDK appends it, producing double /v1/v1/messages"
5038
+ );
5039
+ }
5040
+ updateEnvFile({
5041
+ [MODE_KEY]: "custom",
5042
+ ANTHROPIC_BASE_URL: trimmed,
5043
+ ANTHROPIC_API_KEY: apiKey
5044
+ });
5045
+ }
5046
+ function applyClaudeProxyEnv(spawnEnv) {
5047
+ const mode = currentMode();
5048
+ if (mode === "hypha") {
5049
+ const token = process.env.HYPHA_TOKEN;
5050
+ if (!token) {
5051
+ delete spawnEnv.ANTHROPIC_BASE_URL;
5052
+ delete spawnEnv.ANTHROPIC_API_KEY;
5053
+ return "hypha mode but HYPHA_TOKEN missing \u2014 falling back to login";
5054
+ }
5055
+ spawnEnv.ANTHROPIC_BASE_URL = HYPHA_PROXY_BASE_URL;
5056
+ spawnEnv.ANTHROPIC_API_KEY = token;
5057
+ return `hypha proxy (${HYPHA_PROXY_BASE_URL})`;
5058
+ }
5059
+ if (mode === "custom") {
5060
+ const url = process.env.ANTHROPIC_BASE_URL;
5061
+ const key = process.env.ANTHROPIC_API_KEY;
5062
+ if (!url || !key) {
5063
+ delete spawnEnv.ANTHROPIC_BASE_URL;
5064
+ delete spawnEnv.ANTHROPIC_API_KEY;
5065
+ return "custom mode but ANTHROPIC_BASE_URL/API_KEY missing \u2014 falling back to login";
5066
+ }
5067
+ spawnEnv.ANTHROPIC_BASE_URL = url;
5068
+ spawnEnv.ANTHROPIC_API_KEY = key;
5069
+ return `custom proxy (${url})`;
5070
+ }
5071
+ delete spawnEnv.ANTHROPIC_BASE_URL;
5072
+ delete spawnEnv.ANTHROPIC_API_KEY;
5073
+ return null;
5074
+ }
5075
+ const MANAGED_ENV_KEYS = Array.from(MANAGED_KEYS);
5076
+
5077
+ var claudeAuth = /*#__PURE__*/Object.freeze({
5078
+ __proto__: null,
5079
+ HYPHA_PROXY_BASE_URL: HYPHA_PROXY_BASE_URL,
5080
+ MANAGED_ENV_KEYS: MANAGED_ENV_KEYS,
5081
+ applyClaudeProxyEnv: applyClaudeProxyEnv,
5082
+ getClaudeAuthStatus: getClaudeAuthStatus,
5083
+ setClaudeAuthCustom: setClaudeAuthCustom,
5084
+ setClaudeAuthHyphaProxy: setClaudeAuthHyphaProxy,
5085
+ setClaudeAuthLogin: setClaudeAuthLogin,
5086
+ updateEnvFile: updateEnvFile
5087
+ });
5088
+
4925
5089
  const DEFAULT_PROBE_INTERVAL_S = 10;
4926
5090
  const DEFAULT_PROBE_TIMEOUT_S = 5;
4927
5091
  const DEFAULT_PROBE_FAILURE_THRESHOLD = 3;
@@ -6300,6 +6464,20 @@ async function startDaemon(options) {
6300
6464
  process.on("SIGINT", () => requestShutdown("os-signal"));
6301
6465
  process.on("SIGTERM", () => requestShutdown("os-signal"));
6302
6466
  process.on("SIGUSR1", () => requestShutdown("os-signal-cleanup"));
6467
+ const { watchParentLiveness } = await import('./supervisorLock-DwNAn0VN.mjs');
6468
+ const cancelParentWatchdog = watchParentLiveness({
6469
+ intervalMs: 5e3,
6470
+ onParentDeath: () => {
6471
+ logger.log("Supervisor process died \u2014 sync daemon shutting down to avoid orphan state");
6472
+ requestShutdown("supervisor-died");
6473
+ }
6474
+ });
6475
+ process.on("exit", () => {
6476
+ try {
6477
+ cancelParentWatchdog();
6478
+ } catch {
6479
+ }
6480
+ });
6303
6481
  process.on("uncaughtException", (error) => {
6304
6482
  if (shutdownRequested) return;
6305
6483
  logger.error("Uncaught exception (non-fatal, daemon continues):", error);
@@ -6585,8 +6763,7 @@ async function startDaemon(options) {
6585
6763
  }
6586
6764
  });
6587
6765
  }, buildIsolationConfig2 = function(dir) {
6588
- if (!options2.forceIsolation && !sessionMetadata.sharing?.enabled) return null;
6589
- if (sessionMetadata.securityContext === null) return null;
6766
+ if (!shouldIsolateSession()) return null;
6590
6767
  const method = isolationCapabilities.preferred;
6591
6768
  if (!method) return null;
6592
6769
  const detail = isolationCapabilities.details[method];
@@ -6603,10 +6780,10 @@ async function startDaemon(options) {
6603
6780
  trustOverride: true
6604
6781
  };
6605
6782
  }
6606
- if (sessionMetadata.sharing?.enabled && stagedCredentials) {
6783
+ if (stagedCredentials) {
6607
6784
  config.credentialStagingPath = stagedCredentials.homePath;
6608
6785
  }
6609
- const activeSecurityContext = sessionMetadata.securityContext ?? options2.securityContext;
6786
+ const activeSecurityContext = getActiveSecurityContext();
6610
6787
  if (activeSecurityContext) {
6611
6788
  config = applySecurityContext(config, activeSecurityContext);
6612
6789
  }
@@ -6743,6 +6920,12 @@ async function startDaemon(options) {
6743
6920
  "auto-approve-all": "bypassPermissions"
6744
6921
  };
6745
6922
  const toClaudePermissionMode = (mode) => CLAUDE_PERMISSION_MODE_MAP[mode] || mode;
6923
+ const getActiveSecurityContext = () => sessionMetadata.securityContext ?? options2.securityContext;
6924
+ const shouldIsolateSession = () => shouldIsolate({
6925
+ forceIsolation: options2.forceIsolation,
6926
+ sessionSecurityContext: sessionMetadata.securityContext,
6927
+ optionsSecurityContext: options2.securityContext
6928
+ });
6746
6929
  let isolationCleanupFiles = [];
6747
6930
  const spawnClaude = (initialMessage, meta) => {
6748
6931
  const effectiveMeta = { ...lastSpawnMeta, ...meta };
@@ -6786,7 +6969,7 @@ async function startDaemon(options) {
6786
6969
  if (wrapped.cleanupFiles) isolationCleanupFiles = wrapped.cleanupFiles;
6787
6970
  sessionMetadata = { ...sessionMetadata, isolationMethod: isoConfig.method };
6788
6971
  logger.log(`[Session ${sessionId}] Isolation: ${isoConfig.method} (binary: ${isoConfig.binaryPath})`);
6789
- } else if (options2.forceIsolation || sessionMetadata.sharing?.enabled) {
6972
+ } else if (shouldIsolateSession()) {
6790
6973
  logger.log(`[Session ${sessionId}] WARNING: No isolation runtime (nono/docker/podman) available. Session is NOT sandboxed.`);
6791
6974
  sessionMetadata = { ...sessionMetadata, isolationMethod: void 0 };
6792
6975
  } else {
@@ -6797,18 +6980,19 @@ async function startDaemon(options) {
6797
6980
  delete spawnEnv.CLAUDECODE;
6798
6981
  spawnEnv.SVAMP_SESSION_ID = sessionId;
6799
6982
  delete spawnEnv.SVAMP_SANDBOXED;
6800
- if (sessionMetadata.sharing?.enabled && stagedCredentials) {
6983
+ const proxyDesc = applyClaudeProxyEnv(spawnEnv);
6984
+ if (proxyDesc) {
6985
+ logger.log(`[Session ${sessionId}] Claude auth: ${proxyDesc}`);
6986
+ }
6987
+ if (isoConfig && stagedCredentials) {
6801
6988
  Object.assign(spawnEnv, stagedCredentials.env);
6802
6989
  const filtered = {};
6803
6990
  for (const [k, v] of Object.entries(spawnEnv)) {
6804
6991
  if (v !== void 0) filtered[k] = v;
6805
6992
  }
6806
6993
  spawnEnv = sanitizeEnvForSharing(filtered);
6807
- logger.log(`[Session ${sessionId}] Credential staging: HOME=${stagedCredentials.homePath}`);
6808
- if (sessionMetadata.securityContext) {
6809
- spawnEnv.SVAMP_SANDBOXED = "1";
6810
- logger.log(`[Session ${sessionId}] Sandbox mode: ON (securityContext present)`);
6811
- }
6994
+ spawnEnv.SVAMP_SANDBOXED = "1";
6995
+ logger.log(`[Session ${sessionId}] Credential staging: HOME=${stagedCredentials.homePath}, SVAMP_SANDBOXED=1`);
6812
6996
  }
6813
6997
  const child = spawn$1(spawnCommand, spawnArgs, {
6814
6998
  cwd: directory,
@@ -7419,6 +7603,14 @@ The automated loop has finished. Review the progress above and let me know if yo
7419
7603
  return { success: false, message: "Session was stopped during restart." };
7420
7604
  }
7421
7605
  if (claudeResumeId) {
7606
+ if (!stagedCredentials && shouldIsolateSession()) {
7607
+ try {
7608
+ stagedCredentials = await stageCredentialsForSharing(sessionId);
7609
+ logger.log(`[Session ${sessionId}] Credentials staged at ${stagedCredentials.homePath} (runtime enable)`);
7610
+ } catch (err) {
7611
+ logger.log(`[Session ${sessionId}] WARNING: Runtime credential staging failed: ${err.message}.`);
7612
+ }
7613
+ }
7422
7614
  spawnClaude(void 0, { permissionMode: currentPermissionMode });
7423
7615
  sessionService.updateMetadata(sessionMetadata);
7424
7616
  logger.log(`[Session ${sessionId}] Claude respawned with --resume ${claudeResumeId}`);
@@ -7435,12 +7627,12 @@ The automated loop has finished. Review the progress above and let me know if yo
7435
7627
  isRestartingClaude = false;
7436
7628
  }
7437
7629
  };
7438
- if (sessionMetadata.sharing?.enabled) {
7630
+ if (shouldIsolateSession()) {
7439
7631
  try {
7440
7632
  stagedCredentials = await stageCredentialsForSharing(sessionId);
7441
7633
  logger.log(`[Session ${sessionId}] Credentials staged at ${stagedCredentials.homePath}`);
7442
7634
  } catch (err) {
7443
- logger.log(`[Session ${sessionId}] WARNING: Credential staging failed: ${err.message}. Shared users may have access to ~/.claude history.`);
7635
+ logger.log(`[Session ${sessionId}] WARNING: Credential staging failed: ${err.message}.`);
7444
7636
  }
7445
7637
  }
7446
7638
  let processMessageQueueRef;
@@ -8357,7 +8549,8 @@ The automated loop has finished. Review the progress above and let me know if yo
8357
8549
  const writeSvampConfigPatchAcp = svampConfigChecker.writeConfig;
8358
8550
  const permissionHandler = new HyphaPermissionHandler(shouldAutoAllow2, logger.log);
8359
8551
  let agentIsoConfig;
8360
- if ((options2.forceIsolation || sessionMetadata.sharing?.enabled) && isolationCapabilities.preferred) {
8552
+ const agentShouldIsolate = Boolean(options2.forceIsolation) || Boolean(options2.securityContext);
8553
+ if (agentShouldIsolate && isolationCapabilities.preferred) {
8361
8554
  const method = isolationCapabilities.preferred;
8362
8555
  const detail = isolationCapabilities.details[method];
8363
8556
  if (detail.found && detail.verified !== false) {
@@ -9014,18 +9207,31 @@ The automated loop has finished. Review the progress above and let me know if yo
9014
9207
  }, HEARTBEAT_INTERVAL_MS);
9015
9208
  const PROXY_TOKEN_REFRESH_INTERVAL_MS = 7 * 24 * 60 * 60 * 1e3;
9016
9209
  let proxyTokenRefreshInterval;
9017
- if (process.env.ANTHROPIC_BASE_URL) {
9210
+ const proxyMode = (process.env.SVAMP_CLAUDE_PROXY || "").toLowerCase();
9211
+ if (proxyMode === "hypha" || proxyMode === "hypha-proxy") {
9212
+ const refreshProxyToken = async () => {
9213
+ try {
9214
+ const freshToken = await server.generateToken({ expires_in: 30 * 24 * 3600 });
9215
+ process.env.HYPHA_TOKEN = freshToken;
9216
+ logger.log("Hypha proxy token refreshed (30-day expiry)");
9217
+ } catch (err) {
9218
+ logger.log(`Hypha proxy token refresh failed (will retry in 7 days): ${err}`);
9219
+ }
9220
+ };
9221
+ proxyTokenRefreshInterval = setInterval(refreshProxyToken, PROXY_TOKEN_REFRESH_INTERVAL_MS);
9222
+ logger.log("Hypha proxy token auto-refresh enabled (every 7 days)");
9223
+ } else if (process.env.ANTHROPIC_BASE_URL && !process.env.SVAMP_CLAUDE_PROXY) {
9018
9224
  const refreshProxyToken = async () => {
9019
9225
  try {
9020
9226
  const freshToken = await server.generateToken({ expires_in: 30 * 24 * 3600 });
9021
9227
  process.env.ANTHROPIC_API_KEY = freshToken;
9022
- logger.log("Proxy token refreshed (30-day expiry)");
9228
+ logger.log("Proxy token refreshed (legacy, 30-day expiry)");
9023
9229
  } catch (err) {
9024
9230
  logger.log(`Proxy token refresh failed (will retry in 7 days): ${err}`);
9025
9231
  }
9026
9232
  };
9027
9233
  proxyTokenRefreshInterval = setInterval(refreshProxyToken, PROXY_TOKEN_REFRESH_INTERVAL_MS);
9028
- logger.log("Proxy token auto-refresh enabled (every 7 days)");
9234
+ logger.log("Proxy token auto-refresh enabled (legacy mode, every 7 days)");
9029
9235
  }
9030
9236
  let oauthRefreshInterval;
9031
9237
  const refreshOAuthAndRestageCredentials = async () => {
@@ -9363,4 +9569,4 @@ var run = /*#__PURE__*/Object.freeze({
9363
9569
  stopDaemon: stopDaemon
9364
9570
  });
9365
9571
 
9366
- export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, resolveSecurityContext as e, buildSecurityContextFromFlags as f, getHyphaServerUrl as g, acpBackend as h, acpAgentConfig as i, codexMcpBackend as j, run as k, loadSecurityContextConfig as l, mergeSecurityContexts as m, registerMachineService as r, startDaemon as s };
9572
+ export { DefaultTransport$1 as D, GeminiTransport$1 as G, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, resolveSecurityContext as e, buildSecurityContextFromFlags as f, getHyphaServerUrl as g, acpBackend as h, acpAgentConfig as i, codexMcpBackend as j, claudeAuth as k, loadSecurityContextConfig as l, mergeSecurityContexts as m, run as n, registerMachineService as r, startDaemon as s };
@@ -52,7 +52,7 @@ async function handleServeCommand() {
52
52
  }
53
53
  }
54
54
  async function serveAdd(args, machineId) {
55
- const { connectAndGetMachine } = await import('./commands-CrTDsMDZ.mjs');
55
+ const { connectAndGetMachine } = await import('./commands-CWKFZm9s.mjs');
56
56
  const pos = positionalArgs(args);
57
57
  const name = pos[0];
58
58
  if (!name) {
@@ -84,7 +84,7 @@ async function serveAdd(args, machineId) {
84
84
  }
85
85
  }
86
86
  async function serveRemove(args, machineId) {
87
- const { connectAndGetMachine } = await import('./commands-CrTDsMDZ.mjs');
87
+ const { connectAndGetMachine } = await import('./commands-CWKFZm9s.mjs');
88
88
  const pos = positionalArgs(args);
89
89
  const name = pos[0];
90
90
  if (!name) {
@@ -104,7 +104,7 @@ async function serveRemove(args, machineId) {
104
104
  }
105
105
  }
106
106
  async function serveList(args, machineId) {
107
- const { connectAndGetMachine } = await import('./commands-CrTDsMDZ.mjs');
107
+ const { connectAndGetMachine } = await import('./commands-CWKFZm9s.mjs');
108
108
  const all = hasFlag(args, "--all", "-a");
109
109
  const json = hasFlag(args, "--json");
110
110
  const sessionId = getFlag(args, "--session");
@@ -137,7 +137,7 @@ async function serveList(args, machineId) {
137
137
  }
138
138
  }
139
139
  async function serveInfo(machineId) {
140
- const { connectAndGetMachine } = await import('./commands-CrTDsMDZ.mjs');
140
+ const { connectAndGetMachine } = await import('./commands-CWKFZm9s.mjs');
141
141
  const { machine, server } = await connectAndGetMachine(machineId);
142
142
  try {
143
143
  const info = await machine.serveInfo();
@@ -0,0 +1,157 @@
1
+ import { existsSync, readFileSync, unlinkSync, openSync, writeSync, closeSync } from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
3
+
4
+ function acquireSupervisorLock(pidFile, pid = process.pid) {
5
+ try {
6
+ const fd = openSync(pidFile, "wx");
7
+ try {
8
+ writeSync(fd, String(pid));
9
+ } finally {
10
+ closeSync(fd);
11
+ }
12
+ return { acquired: true };
13
+ } catch (err) {
14
+ if (err?.code !== "EEXIST") throw err;
15
+ }
16
+ let existingPid = 0;
17
+ try {
18
+ existingPid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
19
+ } catch {
20
+ }
21
+ if (!existingPid || Number.isNaN(existingPid) || existingPid <= 0) {
22
+ try {
23
+ unlinkSync(pidFile);
24
+ } catch {
25
+ }
26
+ return { acquired: false, staleCleaned: true };
27
+ }
28
+ if (isPidAlive(existingPid)) {
29
+ return { acquired: false, heldBy: existingPid };
30
+ }
31
+ try {
32
+ unlinkSync(pidFile);
33
+ } catch {
34
+ }
35
+ return { acquired: false, staleCleaned: true };
36
+ }
37
+ function acquireSupervisorLockWithRetry(pidFile, pid = process.pid) {
38
+ let result = acquireSupervisorLock(pidFile, pid);
39
+ if (!result.acquired && result.staleCleaned) {
40
+ result = acquireSupervisorLock(pidFile, pid);
41
+ }
42
+ return { acquired: result.acquired, heldBy: result.heldBy };
43
+ }
44
+ function releaseSupervisorLock(pidFile, pid = process.pid) {
45
+ try {
46
+ if (!existsSync(pidFile)) return;
47
+ const content = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
48
+ if (content === pid) unlinkSync(pidFile);
49
+ } catch {
50
+ }
51
+ }
52
+ function isPidAlive(pid) {
53
+ if (!pid || Number.isNaN(pid) || pid <= 0) return false;
54
+ try {
55
+ process.kill(pid, 0);
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ function findOrphanedSyncPids(currentSupervisorPid = process.pid) {
62
+ if (process.platform === "win32") return [];
63
+ const pids = [];
64
+ try {
65
+ const output = execFileSync("pgrep", ["-af", "svamp daemon start-sync"], {
66
+ encoding: "utf-8",
67
+ timeout: 5e3
68
+ });
69
+ for (const line of output.split("\n")) {
70
+ const trimmed = line.trim();
71
+ if (!trimmed) continue;
72
+ const match = trimmed.match(/^(\d+)\s/);
73
+ if (!match) continue;
74
+ const pid = parseInt(match[1], 10);
75
+ if (Number.isNaN(pid) || pid <= 0) continue;
76
+ if (pid === process.pid) continue;
77
+ const ppid = getPpid(pid);
78
+ if (ppid === currentSupervisorPid) continue;
79
+ pids.push(pid);
80
+ }
81
+ } catch (err) {
82
+ if (err?.status !== 1) ;
83
+ }
84
+ return pids;
85
+ }
86
+ function getPpid(pid) {
87
+ if (process.platform === "win32") return 0;
88
+ try {
89
+ const out = execFileSync("ps", ["-o", "ppid=", "-p", String(pid)], {
90
+ encoding: "utf-8",
91
+ timeout: 2e3
92
+ }).trim();
93
+ const ppid = parseInt(out, 10);
94
+ return Number.isNaN(ppid) ? 0 : ppid;
95
+ } catch {
96
+ return 0;
97
+ }
98
+ }
99
+ async function killOrphanedSyncs(pids, opts = {}) {
100
+ if (pids.length === 0) return;
101
+ const log = opts.log || (() => {
102
+ });
103
+ const gracePeriodMs = opts.gracePeriodMs ?? 3e3;
104
+ log(`Killing ${pids.length} orphan sync daemon(s): ${pids.join(", ")}`);
105
+ for (const pid of pids) {
106
+ try {
107
+ process.kill(pid, "SIGTERM");
108
+ } catch {
109
+ }
110
+ }
111
+ const pollStep = 100;
112
+ const steps = Math.max(1, Math.ceil(gracePeriodMs / pollStep));
113
+ for (let i = 0; i < steps; i++) {
114
+ await new Promise((r) => setTimeout(r, pollStep));
115
+ if (pids.every((p) => !isPidAlive(p))) {
116
+ log("All orphan daemons exited cleanly");
117
+ return;
118
+ }
119
+ }
120
+ const survivors = pids.filter(isPidAlive);
121
+ if (survivors.length > 0) {
122
+ log(`Force-killing survivors: ${survivors.join(", ")}`);
123
+ for (const pid of survivors) {
124
+ try {
125
+ process.kill(pid, "SIGKILL");
126
+ } catch {
127
+ }
128
+ }
129
+ }
130
+ }
131
+ function watchParentLiveness(opts) {
132
+ if (process.env.SVAMP_SUPERVISED !== "1") {
133
+ return () => {
134
+ };
135
+ }
136
+ const supervisorPid = process.ppid;
137
+ if (!supervisorPid || supervisorPid === 1) {
138
+ return () => {
139
+ };
140
+ }
141
+ const intervalMs = opts.intervalMs ?? 5e3;
142
+ let fired = false;
143
+ const timer = setInterval(() => {
144
+ if (fired) return;
145
+ if (!isPidAlive(supervisorPid)) {
146
+ fired = true;
147
+ clearInterval(timer);
148
+ opts.onParentDeath();
149
+ }
150
+ }, intervalMs);
151
+ timer.unref?.();
152
+ return () => {
153
+ clearInterval(timer);
154
+ };
155
+ }
156
+
157
+ export { acquireSupervisorLock, acquireSupervisorLockWithRetry, findOrphanedSyncPids, getPpid, isPidAlive, killOrphanedSyncs, releaseSupervisorLock, watchParentLiveness };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.37",
3
+ "version": "0.2.39",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "rm -rf dist && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "test": "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only",
23
+ "test": "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only",
24
24
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
25
25
  "dev": "tsx src/cli.ts",
26
26
  "dev:daemon": "tsx src/cli.ts daemon start-sync",
@@ -1,63 +0,0 @@
1
- var name = "svamp-cli";
2
- var version = "0.2.37";
3
- var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
- var author = "Amun AI AB";
5
- var license = "SEE LICENSE IN LICENSE";
6
- var type = "module";
7
- var bin = {
8
- svamp: "./bin/svamp.mjs"
9
- };
10
- var files = [
11
- "dist",
12
- "bin"
13
- ];
14
- var main = "./dist/index.mjs";
15
- var exports$1 = {
16
- ".": "./dist/index.mjs",
17
- "./cli": "./dist/cli.mjs"
18
- };
19
- var scripts = {
20
- build: "rm -rf dist && tsc --noEmit && pkgroll",
21
- typecheck: "tsc --noEmit",
22
- test: "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only",
23
- "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
- dev: "tsx src/cli.ts",
25
- "dev:daemon": "tsx src/cli.ts daemon start-sync",
26
- "test:e2e": "node --no-warnings test/e2e-session-tests.mjs",
27
- "test:frpc": "npx tsx test/test-frpc-e2e.mjs"
28
- };
29
- var dependencies = {
30
- "@agentclientprotocol/sdk": "^0.14.1",
31
- "@modelcontextprotocol/sdk": "^1.25.3",
32
- "hypha-rpc": "0.21.36",
33
- "node-pty": "1.2.0-beta.11",
34
- ws: "^8.18.0",
35
- yaml: "^2.8.2",
36
- zod: "^3.24.4"
37
- };
38
- var devDependencies = {
39
- "@types/node": ">=20",
40
- "@types/ws": "^8.5.14",
41
- pkgroll: "^2.14.2",
42
- tsx: "^4.20.6",
43
- typescript: "5.9.3"
44
- };
45
- var packageManager = "yarn@1.22.22";
46
- var _package = {
47
- name: name,
48
- version: version,
49
- description: description,
50
- author: author,
51
- license: license,
52
- type: type,
53
- bin: bin,
54
- files: files,
55
- main: main,
56
- exports: exports$1,
57
- scripts: scripts,
58
- dependencies: dependencies,
59
- devDependencies: devDependencies,
60
- packageManager: packageManager
61
- };
62
-
63
- export { author, bin, _package as default, dependencies, description, devDependencies, exports$1 as exports, files, license, main, name, packageManager, scripts, type, version };