svamp-cli 0.2.48 → 0.2.50

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.
@@ -15,7 +15,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
15
15
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
16
16
  import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js';
17
17
  import { z } from 'zod';
18
- import { mkdir, rm, chmod, access, mkdtemp, copyFile, writeFile, stat, readdir, readFile as readFile$1 } from 'node:fs/promises';
18
+ import { mkdir, rm, chmod, access, mkdtemp, copyFile, writeFile, readdir, stat, readFile as readFile$1 } from 'node:fs/promises';
19
19
  import { promisify } from 'node:util';
20
20
 
21
21
  let connectToServerFn = null;
@@ -71,6 +71,14 @@ const ROLE_HIERARCHY = {
71
71
  interact: 1,
72
72
  admin: 2
73
73
  };
74
+ function normalizeAllowedUser(input, addedBy) {
75
+ return {
76
+ email: input.email.trim(),
77
+ role: "admin",
78
+ addedAt: typeof input.addedAt === "number" ? input.addedAt : Date.now(),
79
+ addedBy: input.addedBy ?? addedBy
80
+ };
81
+ }
74
82
  const ISOLATION_PREFERENCE = ["nono", "docker", "podman"];
75
83
 
76
84
  function resolveRoleLevel(sharing, userEmail) {
@@ -80,7 +88,7 @@ function resolveRoleLevel(sharing, userEmail) {
80
88
  (u) => u.email.toLowerCase() === userEmail.toLowerCase()
81
89
  );
82
90
  if (sharedUser) {
83
- level = ROLE_HIERARCHY[sharedUser.role];
91
+ level = ROLE_HIERARCHY.admin;
84
92
  }
85
93
  }
86
94
  if (sharing.publicAccess) {
@@ -737,6 +745,14 @@ async function registerMachineService(server, machineId, metadata, daemonState,
737
745
  if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
738
746
  newSharing = { ...newSharing, owner: context.user.email };
739
747
  }
748
+ const ownerEmail = newSharing.owner || context?.user?.email || "";
749
+ newSharing = {
750
+ ...newSharing,
751
+ allowedUsers: (newSharing.allowedUsers || []).map(
752
+ (u) => normalizeAllowedUser(u, ownerEmail)
753
+ ),
754
+ publicAccess: null
755
+ };
740
756
  const oldSharing = currentMetadata.sharing;
741
757
  currentMetadata = { ...currentMetadata, sharing: newSharing };
742
758
  metadataVersion++;
@@ -1748,6 +1764,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
1748
1764
  if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
1749
1765
  newSharing = { ...newSharing, owner: context.user.email };
1750
1766
  }
1767
+ const ownerEmail = newSharing.owner || context?.user?.email || "";
1768
+ newSharing = {
1769
+ ...newSharing,
1770
+ allowedUsers: (newSharing.allowedUsers || []).map(
1771
+ (u) => normalizeAllowedUser(u, ownerEmail)
1772
+ )
1773
+ };
1751
1774
  metadata = { ...metadata, sharing: newSharing };
1752
1775
  metadataVersion++;
1753
1776
  notifyListeners({
@@ -2576,7 +2599,8 @@ class SharingNotificationSync {
2576
2599
  userEmail: params.ownerEmail
2577
2600
  },
2578
2601
  title: `${typeLabel} shared with you`,
2579
- body: `${params.ownerEmail} shared "${params.label || "Untitled"}" with you as ${params.role}`,
2602
+ // Per-user tiers were retired; explicit users always have full access.
2603
+ body: `${params.ownerEmail} shared "${params.label || "Untitled"}" with you (full access)`,
2580
2604
  level: "info",
2581
2605
  action: shareType === "session" ? {
2582
2606
  type: "add-session-bookmark",
@@ -4939,6 +4963,28 @@ function sanitizeEnvForSharing(env) {
4939
4963
  }
4940
4964
  return sanitized;
4941
4965
  }
4966
+ async function sweepOrphanedStagedHomes(activeSessionIds) {
4967
+ const removed = [];
4968
+ if (!existsSync(STAGED_HOMES_DIR)) return { removed };
4969
+ const active = new Set(activeSessionIds);
4970
+ let entries;
4971
+ try {
4972
+ entries = await readdir(STAGED_HOMES_DIR, { withFileTypes: true });
4973
+ } catch {
4974
+ return { removed };
4975
+ }
4976
+ for (const entry of entries) {
4977
+ if (!entry.isDirectory()) continue;
4978
+ if (active.has(entry.name)) continue;
4979
+ const path = join$1(STAGED_HOMES_DIR, entry.name);
4980
+ try {
4981
+ await rm(path, { recursive: true, force: true });
4982
+ removed.push(entry.name);
4983
+ } catch {
4984
+ }
4985
+ }
4986
+ return { removed };
4987
+ }
4942
4988
  async function copyDirRecursive(src, dest) {
4943
4989
  const srcStat = await stat(src);
4944
4990
  if (!srcStat.isDirectory()) return;
@@ -4958,7 +5004,8 @@ async function copyDirRecursive(src, dest) {
4958
5004
  var credentialStaging = /*#__PURE__*/Object.freeze({
4959
5005
  __proto__: null,
4960
5006
  sanitizeEnvForSharing: sanitizeEnvForSharing,
4961
- stageCredentialsForSharing: stageCredentialsForSharing
5007
+ stageCredentialsForSharing: stageCredentialsForSharing,
5008
+ sweepOrphanedStagedHomes: sweepOrphanedStagedHomes
4962
5009
  });
4963
5010
 
4964
5011
  function shouldIsolate(input) {
@@ -5125,6 +5172,287 @@ var claudeAuth = /*#__PURE__*/Object.freeze({
5125
5172
  updateEnvFile: updateEnvFile
5126
5173
  });
5127
5174
 
5175
+ const DEFAULT_WEB_BASE = "https://svamp.hypha.aicell.io";
5176
+ function getShareBaseUrl() {
5177
+ const fromEnv = (process.env.SVAMP_WEB_URL || "").trim();
5178
+ const base = fromEnv || DEFAULT_WEB_BASE;
5179
+ return base.replace(/\/+$/, "");
5180
+ }
5181
+ function buildSessionShareUrl(input) {
5182
+ const params = new URLSearchParams();
5183
+ if (input.workspace) params.set("ws", input.workspace);
5184
+ if (input.machineServiceId) params.set("svc", input.machineServiceId);
5185
+ const query = params.toString();
5186
+ return `${getShareBaseUrl()}/share/${input.sessionId}${query ? `?${query}` : ""}`;
5187
+ }
5188
+ function buildMachineShareUrl(input) {
5189
+ const params = new URLSearchParams();
5190
+ if (input.workspace) params.set("ws", input.workspace);
5191
+ const query = params.toString();
5192
+ return `${getShareBaseUrl()}/share/machine/${input.machineId}${query ? `?${query}` : ""}`;
5193
+ }
5194
+ const DAEMON_SHARE_EMAILS_ENV = "SVAMP_SHARE_EMAILS";
5195
+ function parseDaemonShareEmails(raw) {
5196
+ if (!raw) return [];
5197
+ const seen = /* @__PURE__ */ new Set();
5198
+ const out = [];
5199
+ for (const part of raw.split(",")) {
5200
+ const email = part.trim();
5201
+ if (!email || !email.includes("@")) continue;
5202
+ const k = email.toLowerCase();
5203
+ if (seen.has(k)) continue;
5204
+ seen.add(k);
5205
+ out.push(email);
5206
+ }
5207
+ return out;
5208
+ }
5209
+ function applyDaemonShareSeed(sharing, seedEmails, ownerEmail) {
5210
+ const base = sharing ? { ...sharing, allowedUsers: [...sharing.allowedUsers || []] } : { enabled: true, owner: ownerEmail || "", allowedUsers: [] };
5211
+ base.enabled = true;
5212
+ if (!base.owner && ownerEmail) base.owner = ownerEmail;
5213
+ const existing = new Set(base.allowedUsers.map((u) => u.email.toLowerCase()));
5214
+ const addedEmails = [];
5215
+ for (const email of seedEmails) {
5216
+ if (existing.has(email.toLowerCase())) continue;
5217
+ base.allowedUsers.push(
5218
+ normalizeAllowedUser({ email, addedBy: "daemon-share-flag" }, base.owner || "daemon-share-flag")
5219
+ );
5220
+ existing.add(email.toLowerCase());
5221
+ addedEmails.push(email);
5222
+ }
5223
+ return { sharing: base, addedEmails };
5224
+ }
5225
+
5226
+ const AUTH0_NAMESPACES = ["https://api.imjoy.io/", "https://amun.ai/"];
5227
+ const COOKIE_NAME = "svamp_serve_token";
5228
+ const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
5229
+ const DEFAULT_CACHE_MAX_SIZE = 1e3;
5230
+ class TokenCache {
5231
+ cache = /* @__PURE__ */ new Map();
5232
+ ttlMs;
5233
+ maxSize;
5234
+ constructor(ttlMs = DEFAULT_CACHE_TTL_MS, maxSize = DEFAULT_CACHE_MAX_SIZE) {
5235
+ this.ttlMs = ttlMs;
5236
+ this.maxSize = maxSize;
5237
+ }
5238
+ get(token) {
5239
+ const info = this.cache.get(token);
5240
+ if (!info) return null;
5241
+ if (Date.now() > info.expiresAt) {
5242
+ this.cache.delete(token);
5243
+ return null;
5244
+ }
5245
+ return info;
5246
+ }
5247
+ set(token, email) {
5248
+ if (this.cache.size >= this.maxSize) {
5249
+ const oldest = this.cache.keys().next().value;
5250
+ if (oldest) this.cache.delete(oldest);
5251
+ }
5252
+ this.cache.set(token, {
5253
+ email,
5254
+ expiresAt: Date.now() + this.ttlMs
5255
+ });
5256
+ }
5257
+ clear() {
5258
+ this.cache.clear();
5259
+ }
5260
+ }
5261
+ function hasCookieToken(req) {
5262
+ return !!parseCookies(req.headers.cookie || "")[COOKIE_NAME];
5263
+ }
5264
+ function extractToken(req) {
5265
+ const cookies = parseCookies(req.headers.cookie || "");
5266
+ if (cookies[COOKIE_NAME]) return cookies[COOKIE_NAME];
5267
+ const auth = req.headers.authorization;
5268
+ if (auth?.startsWith("Bearer ") || auth?.startsWith("bearer ")) {
5269
+ return auth.slice(7).trim();
5270
+ }
5271
+ const url = new URL(req.url || "/", "http://localhost");
5272
+ const qToken = url.searchParams.get("token");
5273
+ if (qToken) return qToken;
5274
+ return null;
5275
+ }
5276
+ function parseCookies(header) {
5277
+ const cookies = {};
5278
+ for (const pair of header.split(";")) {
5279
+ const eq = pair.indexOf("=");
5280
+ if (eq > 0) {
5281
+ const key = pair.slice(0, eq).trim();
5282
+ const value = pair.slice(eq + 1).trim();
5283
+ cookies[key] = value;
5284
+ }
5285
+ }
5286
+ return cookies;
5287
+ }
5288
+ function parseJwtEmail(token) {
5289
+ try {
5290
+ const parts = token.split(".");
5291
+ if (parts.length !== 3) return null;
5292
+ const payload = JSON.parse(
5293
+ Buffer.from(parts[1], "base64url").toString("utf-8")
5294
+ );
5295
+ if (payload.exp && payload.exp * 1e3 < Date.now()) return null;
5296
+ for (const ns of AUTH0_NAMESPACES) {
5297
+ const email = payload[ns + "email"];
5298
+ if (typeof email === "string" && email) return email.toLowerCase();
5299
+ }
5300
+ if (typeof payload.email === "string" && payload.email) return payload.email.toLowerCase();
5301
+ return null;
5302
+ } catch {
5303
+ return null;
5304
+ }
5305
+ }
5306
+ async function verifyTokenViaHypha(token, hyphaServerUrl) {
5307
+ try {
5308
+ const baseUrl = hyphaServerUrl.replace(/\/$/, "");
5309
+ const url = `${baseUrl}/public/services/ws/get_user_info`;
5310
+ const resp = await fetch(url, {
5311
+ method: "GET",
5312
+ headers: {
5313
+ Authorization: `Bearer ${token}`
5314
+ },
5315
+ signal: AbortSignal.timeout(1e4)
5316
+ });
5317
+ if (!resp.ok) return null;
5318
+ const data = await resp.json();
5319
+ const email = data?.email;
5320
+ if (typeof email === "string" && email) return email.toLowerCase();
5321
+ return null;
5322
+ } catch {
5323
+ return null;
5324
+ }
5325
+ }
5326
+ class ServeAuth {
5327
+ cache;
5328
+ hyphaServerUrl;
5329
+ constructor(options) {
5330
+ this.hyphaServerUrl = options.hyphaServerUrl.replace(/\/$/, "");
5331
+ this.cache = new TokenCache(
5332
+ options.cacheTtlMs || DEFAULT_CACHE_TTL_MS,
5333
+ options.cacheMaxSize || DEFAULT_CACHE_MAX_SIZE
5334
+ );
5335
+ }
5336
+ /**
5337
+ * Authenticate a request and return the user's email, or null if not authenticated.
5338
+ */
5339
+ async authenticate(req) {
5340
+ const token = extractToken(req);
5341
+ if (!token) return null;
5342
+ const cached = this.cache.get(token);
5343
+ if (cached) return cached.email;
5344
+ const localEmail = parseJwtEmail(token);
5345
+ if (localEmail) {
5346
+ this.cache.set(token, localEmail);
5347
+ return localEmail;
5348
+ }
5349
+ const serverEmail = await verifyTokenViaHypha(token, this.hyphaServerUrl);
5350
+ if (serverEmail) {
5351
+ this.cache.set(token, serverEmail);
5352
+ return serverEmail;
5353
+ }
5354
+ return null;
5355
+ }
5356
+ /**
5357
+ * Check if a user email is authorized for a mount's access level.
5358
+ */
5359
+ isAuthorized(email, access, ownerEmail) {
5360
+ if (access === "public") return true;
5361
+ if (!email) return false;
5362
+ if (access === "owner") {
5363
+ return !!ownerEmail && email.toLowerCase() === ownerEmail.toLowerCase();
5364
+ }
5365
+ return access.some((e) => e.toLowerCase() === email.toLowerCase());
5366
+ }
5367
+ /**
5368
+ * Generate the login page HTML. Loads the `hypha-rpc` JS SDK from CDN and
5369
+ * calls `hyphaWebsocketClient.login({ server_url, login_callback })`, which
5370
+ * handles opening the login URL, polling, and token retrieval internally.
5371
+ * Matches the pattern used by bioimage.io — proven to work.
5372
+ */
5373
+ getLoginPageHtml(redirectUrl) {
5374
+ return `<!DOCTYPE html>
5375
+ <html><head>
5376
+ <meta charset="utf-8">
5377
+ <meta name="viewport" content="width=device-width,initial-scale=1">
5378
+ <title>Sign in \u2014 Svamp File Server</title>
5379
+ <style>
5380
+ *{box-sizing:border-box;margin:0;padding:0}
5381
+ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;background:#f6f8fa;color:#24292f;padding:16px}
5382
+ .card{background:#fff;border:1px solid #d0d7de;border-radius:12px;padding:32px;max-width:420px;width:100%;text-align:center;box-shadow:0 4px 12px rgba(31,35,40,0.06)}
5383
+ h1{font-size:1.25rem;margin-bottom:8px}
5384
+ .subtitle{color:#656d76;font-size:0.9rem;margin-bottom:24px}
5385
+ button{background:#0969da;color:#fff;border:none;border-radius:8px;padding:12px 24px;font-size:1rem;cursor:pointer;font-weight:500;width:100%}
5386
+ button:hover{background:#0860c4}
5387
+ button:disabled{background:#94d3a2;cursor:wait}
5388
+ .status{margin-top:16px;color:#656d76;font-size:0.85rem;min-height:20px;word-break:break-word}
5389
+ .error{color:#cf222e}
5390
+ .ok{color:#1a7f37}
5391
+ a{color:#0969da;text-decoration:none}
5392
+ a:hover{text-decoration:underline}
5393
+ </style>
5394
+ <script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.21.28/dist/hypha-rpc-websocket.min.js"><\/script>
5395
+ </head><body>
5396
+ <div class="card">
5397
+ <h1>Sign in required</h1>
5398
+ <p class="subtitle">This resource is private. Sign in with your Hypha account to continue.</p>
5399
+ <button id="login-btn">Sign in with Hypha</button>
5400
+ <div class="status" id="status"></div>
5401
+ </div>
5402
+ <script>
5403
+ const hyphaServer = ${JSON.stringify(this.hyphaServerUrl)};
5404
+ const redirectUrl = ${JSON.stringify(redirectUrl)};
5405
+ const cookieName = ${JSON.stringify(COOKIE_NAME)};
5406
+
5407
+ const btn = document.getElementById('login-btn');
5408
+ const statusEl = document.getElementById('status');
5409
+
5410
+ function setError(msg) {
5411
+ statusEl.innerHTML = '<span class="error">' + msg + '</span>';
5412
+ btn.disabled = false;
5413
+ }
5414
+
5415
+ btn.addEventListener('click', async () => {
5416
+ if (typeof hyphaWebsocketClient === 'undefined' || !hyphaWebsocketClient.login) {
5417
+ setError('Hypha SDK failed to load. Check your network connection and retry.');
5418
+ return;
5419
+ }
5420
+
5421
+ btn.disabled = true;
5422
+ statusEl.textContent = 'Opening Hypha sign-in\u2026';
5423
+
5424
+ try {
5425
+ const token = await hyphaWebsocketClient.login({
5426
+ server_url: hyphaServer,
5427
+ login_callback: (context) => {
5428
+ statusEl.textContent = 'Waiting for you to sign in\u2026';
5429
+ // Open the login URL in a new tab. Called synchronously from
5430
+ // inside the SDK's login() after the user's button click, so
5431
+ // popup blockers generally allow it.
5432
+ window.open(context.login_url, '_blank');
5433
+ },
5434
+ });
5435
+
5436
+ if (!token) throw new Error('No token returned from login');
5437
+
5438
+ // Set the cookie so subsequent requests to this origin are authenticated.
5439
+ const secure = location.protocol === 'https:' ? '; Secure' : '';
5440
+ document.cookie = cookieName + '=' + token + '; path=/; SameSite=Lax' + secure;
5441
+
5442
+ statusEl.innerHTML = '<span class="ok">Signed in. Redirecting\u2026</span>';
5443
+ setTimeout(() => { window.location.replace(redirectUrl); }, 300);
5444
+ } catch (err) {
5445
+ setError('Login failed: ' + (err && err.message ? err.message : err));
5446
+ }
5447
+ });
5448
+ <\/script>
5449
+ </body></html>`;
5450
+ }
5451
+ destroy() {
5452
+ this.cache.clear();
5453
+ }
5454
+ }
5455
+
5128
5456
  const DEFAULT_PROBE_INTERVAL_S = 10;
5129
5457
  const DEFAULT_PROBE_TIMEOUT_S = 5;
5130
5458
  const DEFAULT_PROBE_FAILURE_THRESHOLD = 3;
@@ -5975,6 +6303,28 @@ function checkTruncation(format, tail, fileSize, head) {
5975
6303
  const __filename$1 = fileURLToPath(import.meta.url);
5976
6304
  const __dirname$1 = dirname(__filename$1);
5977
6305
  const CLAUDE_SKILLS_DIR = join(os$1.homedir(), ".claude", "skills");
6306
+ function readSkillVersion(skillDir) {
6307
+ try {
6308
+ const md = readFileSync$1(join(skillDir, "SKILL.md"), "utf-8");
6309
+ const m = md.match(/^---\s*\n([\s\S]*?)\n---/);
6310
+ if (!m) return null;
6311
+ const versionLine = m[1].split("\n").find((l) => /^\s*version\s*:/.test(l));
6312
+ if (!versionLine) return null;
6313
+ return versionLine.split(":").slice(1).join(":").trim().replace(/^["']|["']$/g, "");
6314
+ } catch {
6315
+ return null;
6316
+ }
6317
+ }
6318
+ function compareVersions(a, b) {
6319
+ const pa = a.split(".").map((p) => parseInt(p, 10) || 0);
6320
+ const pb = b.split(".").map((p) => parseInt(p, 10) || 0);
6321
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
6322
+ const ai = pa[i] ?? 0;
6323
+ const bi = pb[i] ?? 0;
6324
+ if (ai !== bi) return ai < bi ? -1 : 1;
6325
+ }
6326
+ return 0;
6327
+ }
5978
6328
  async function installSkillFromEndpoint(name, baseUrl) {
5979
6329
  const resp = await fetch(baseUrl, { signal: AbortSignal.timeout(15e3) });
5980
6330
  if (!resp.ok) throw new Error(`HTTP ${resp.status} from ${baseUrl}`);
@@ -6076,25 +6426,56 @@ function preventMachineSleep(logger) {
6076
6426
  return;
6077
6427
  }
6078
6428
  }
6429
+ async function fetchMarketplaceSkillVersion(name) {
6430
+ try {
6431
+ const url = `https://hypha.aicell.io/hypha-cloud/artifacts/${name}/files/SKILL.md`;
6432
+ const resp = await fetch(url, { signal: AbortSignal.timeout(1e4) });
6433
+ if (!resp.ok) return null;
6434
+ const md = await resp.text();
6435
+ const m = md.match(/^---\s*\n([\s\S]*?)\n---/);
6436
+ if (!m) return null;
6437
+ const versionLine = m[1].split("\n").find((l) => /^\s*version\s*:/.test(l));
6438
+ if (!versionLine) return null;
6439
+ return versionLine.split(":").slice(1).join(":").trim().replace(/^["']|["']$/g, "");
6440
+ } catch {
6441
+ return null;
6442
+ }
6443
+ }
6079
6444
  async function ensureAutoInstalledSkills(logger) {
6080
6445
  const tasks = [
6081
6446
  {
6082
6447
  name: "svamp",
6083
- install: () => installSkillFromMarketplace("svamp")
6448
+ install: () => installSkillFromMarketplace("svamp"),
6449
+ marketplaceVersion: () => fetchMarketplaceSkillVersion("svamp")
6084
6450
  },
6085
6451
  {
6086
6452
  name: "hypha",
6087
6453
  install: () => installSkillFromEndpoint("hypha", "https://hypha.aicell.io/ws/agent-skills/")
6454
+ // hypha skill ships from a different endpoint without a version probe yet.
6088
6455
  }
6089
6456
  ];
6090
6457
  for (const task of tasks) {
6091
6458
  const targetDir = join(CLAUDE_SKILLS_DIR, task.name);
6092
- if (existsSync$1(targetDir)) continue;
6459
+ const installed = existsSync$1(targetDir);
6460
+ if (!installed) {
6461
+ try {
6462
+ await task.install();
6463
+ logger.log(`[skills] Auto-installed: ${task.name}`);
6464
+ } catch (err) {
6465
+ logger.log(`[skills] Auto-install of "${task.name}" failed (non-fatal): ${err.message}`);
6466
+ }
6467
+ continue;
6468
+ }
6469
+ if (!task.marketplaceVersion) continue;
6093
6470
  try {
6094
- await task.install();
6095
- logger.log(`[skills] Auto-installed: ${task.name}`);
6471
+ const remote = await task.marketplaceVersion();
6472
+ const local = readSkillVersion(targetDir);
6473
+ if (remote && local && compareVersions(remote, local) > 0) {
6474
+ logger.log(`[skills] Refreshing "${task.name}" ${local} \u2192 ${remote}...`);
6475
+ await task.install();
6476
+ logger.log(`[skills] Refreshed: ${task.name} \u2192 ${remote}`);
6477
+ }
6096
6478
  } catch (err) {
6097
- logger.log(`[skills] Auto-install of "${task.name}" failed (non-fatal): ${err.message}`);
6098
6479
  }
6099
6480
  }
6100
6481
  }
@@ -6913,7 +7294,7 @@ async function startDaemon(options) {
6913
7294
  const supervisor = new ProcessSupervisor(join(SVAMP_HOME, "processes"));
6914
7295
  await supervisor.init();
6915
7296
  const tunnels = /* @__PURE__ */ new Map();
6916
- const { ServeManager } = await import('./serveManager-RvRL-weX.mjs');
7297
+ const { ServeManager } = await import('./serveManager-rHhOPkW4.mjs');
6917
7298
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
6918
7299
  ensureAutoInstalledSkills(logger).catch(() => {
6919
7300
  });
@@ -9164,6 +9545,29 @@ The automated loop has finished. Review the progress above and let me know if yo
9164
9545
  if (persistedMachineMeta) {
9165
9546
  logger.log(`Restored machine metadata (sharing=${!!persistedMachineMeta.sharing}, securityContextConfig=${!!persistedMachineMeta.securityContextConfig}, injectPlatformGuidance=${persistedMachineMeta.injectPlatformGuidance})`);
9166
9547
  }
9548
+ let seededSharing = persistedMachineMeta?.sharing;
9549
+ const seedEmails = parseDaemonShareEmails(process.env[DAEMON_SHARE_EMAILS_ENV]);
9550
+ if (seedEmails.length > 0) {
9551
+ const ownerEmail = parseJwtEmail(process.env.HYPHA_TOKEN || "") || void 0;
9552
+ const result = applyDaemonShareSeed(seededSharing, seedEmails, ownerEmail);
9553
+ seededSharing = result.sharing;
9554
+ if (result.addedEmails.length > 0) {
9555
+ logger.log(`Seeded machine sharing with ${result.addedEmails.length} user(s): ${result.addedEmails.join(", ")}`);
9556
+ } else {
9557
+ logger.log(`Daemon --share seed: no new users (all already in allowedUsers).`);
9558
+ }
9559
+ savePersistedMachineMetadata(SVAMP_HOME, {
9560
+ sharing: seededSharing,
9561
+ securityContextConfig: persistedMachineMeta?.securityContextConfig,
9562
+ injectPlatformGuidance: persistedMachineMeta?.injectPlatformGuidance
9563
+ });
9564
+ try {
9565
+ updateEnvFile({ [DAEMON_SHARE_EMAILS_ENV]: void 0 });
9566
+ process.env[DAEMON_SHARE_EMAILS_ENV] = "";
9567
+ } catch (err) {
9568
+ logger.log(`[share-seed] Failed to clear ${DAEMON_SHARE_EMAILS_ENV} from .env: ${err.message}`);
9569
+ }
9570
+ }
9167
9571
  const machineMetadata = {
9168
9572
  host: os$1.hostname(),
9169
9573
  platform: os$1.platform(),
@@ -9173,8 +9577,9 @@ The automated loop has finished. Review the progress above and let me know if yo
9173
9577
  svampLibDir: join(__dirname$1, ".."),
9174
9578
  displayName: process.env.SVAMP_DISPLAY_NAME || void 0,
9175
9579
  isolationCapabilities,
9176
- // Restore persisted sharing, security context config, and platform guidance flag
9177
- ...persistedMachineMeta?.sharing && { sharing: persistedMachineMeta.sharing },
9580
+ // Restore persisted sharing (possibly augmented with --share seed above),
9581
+ // security context config, and platform guidance flag.
9582
+ ...seededSharing && { sharing: seededSharing },
9178
9583
  ...persistedMachineMeta?.securityContextConfig && { securityContextConfig: persistedMachineMeta.securityContextConfig },
9179
9584
  ...persistedMachineMeta?.injectPlatformGuidance !== void 0 && { injectPlatformGuidance: persistedMachineMeta.injectPlatformGuidance }
9180
9585
  };
@@ -9223,6 +9628,15 @@ The automated loop has finished. Review the progress above and let me know if yo
9223
9628
  }
9224
9629
  );
9225
9630
  logger.log(`Machine service registered: svamp-machine-${machineId}`);
9631
+ if (seededSharing?.enabled) {
9632
+ const shareUrl = buildMachineShareUrl({
9633
+ machineId,
9634
+ workspace: server.config?.workspace
9635
+ });
9636
+ logger.log(`Machine share URL: ${shareUrl}`);
9637
+ const userList = (seededSharing.allowedUsers || []).map((u) => u.email).join(", ");
9638
+ if (userList) logger.log(` Shared with: ${userList}`);
9639
+ }
9226
9640
  machineServiceRef = machineService;
9227
9641
  const artifactSync = new SessionArtifactSync(server, logger.log);
9228
9642
  const debugService = await registerDebugService(server, machineId, {
@@ -9243,6 +9657,15 @@ The automated loop has finished. Review the progress above and let me know if yo
9243
9657
  logger.log(`Debug service registered: svamp-debug-${machineId}`);
9244
9658
  await serveManager.restore();
9245
9659
  const persistedSessions = loadPersistedSessions();
9660
+ try {
9661
+ const knownIds = new Set(persistedSessions.map((s) => s.sessionId).concat(Object.keys(loadSessionIndex())));
9662
+ const { removed } = await sweepOrphanedStagedHomes(knownIds);
9663
+ if (removed.length > 0) {
9664
+ logger.log(`Cleaned up ${removed.length} orphaned staged home(s) from prior runs.`);
9665
+ }
9666
+ } catch (err) {
9667
+ logger.log(`[staged-homes] sweep failed: ${err.message}`);
9668
+ }
9246
9669
  const sessionsToAutoContinue = [];
9247
9670
  const sessionsToRalphResume = [];
9248
9671
  if (persistedSessions.length > 0) {
@@ -9922,4 +10345,4 @@ var run = /*#__PURE__*/Object.freeze({
9922
10345
  stopDaemon: stopDaemon
9923
10346
  });
9924
10347
 
9925
- 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, generateHookSettings as h, acpBackend as i, acpAgentConfig as j, codexMcpBackend as k, loadSecurityContextConfig as l, mergeSecurityContexts as m, claudeAuth as n, run as o, registerMachineService as r, startDaemon as s };
10348
+ export { DefaultTransport$1 as D, GeminiTransport$1 as G, ServeAuth as S, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, resolveSecurityContext as e, buildSecurityContextFromFlags as f, getHyphaServerUrl as g, hasCookieToken as h, buildSessionShareUrl as i, buildMachineShareUrl as j, generateHookSettings as k, loadSecurityContextConfig as l, mergeSecurityContexts as m, normalizeAllowedUser as n, acpBackend as o, acpAgentConfig as p, codexMcpBackend as q, registerMachineService as r, startDaemon as s, claudeAuth as t, run as u };
@@ -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 { resolve, join } from 'node:path';
4
4
  import { existsSync, readFileSync, watch } from 'node:fs';
5
- import { c as connectToHypha, a as registerSessionService, h as generateHookSettings } from './run-6umeTX-K.mjs';
5
+ import { c as connectToHypha, a as registerSessionService, k as generateHookSettings } from './run-CIPCavEp.mjs';
6
6
  import { createServer } from 'node:http';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { createInterface } from 'node:readline';
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-JWrmpGcs.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-CvWyrVnY.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -86,7 +86,7 @@ async function serveAdd(args, machineId) {
86
86
  }
87
87
  }
88
88
  async function serveApply(args, machineId) {
89
- const { connectAndGetMachine } = await import('./commands-JWrmpGcs.mjs');
89
+ const { connectAndGetMachine } = await import('./commands-CvWyrVnY.mjs');
90
90
  const fs = await import('fs');
91
91
  const yaml = await import('yaml');
92
92
  const file = positionalArgs(args)[0];
@@ -171,7 +171,7 @@ async function serveApply(args, machineId) {
171
171
  }
172
172
  }
173
173
  async function serveRemove(args, machineId) {
174
- const { connectAndGetMachine } = await import('./commands-JWrmpGcs.mjs');
174
+ const { connectAndGetMachine } = await import('./commands-CvWyrVnY.mjs');
175
175
  const pos = positionalArgs(args);
176
176
  const name = pos[0];
177
177
  if (!name) {
@@ -191,7 +191,7 @@ async function serveRemove(args, machineId) {
191
191
  }
192
192
  }
193
193
  async function serveList(args, machineId) {
194
- const { connectAndGetMachine } = await import('./commands-JWrmpGcs.mjs');
194
+ const { connectAndGetMachine } = await import('./commands-CvWyrVnY.mjs');
195
195
  const all = hasFlag(args, "--all", "-a");
196
196
  const json = hasFlag(args, "--json");
197
197
  const sessionId = getFlag(args, "--session");
@@ -224,7 +224,7 @@ async function serveList(args, machineId) {
224
224
  }
225
225
  }
226
226
  async function serveInfo(machineId) {
227
- const { connectAndGetMachine } = await import('./commands-JWrmpGcs.mjs');
227
+ const { connectAndGetMachine } = await import('./commands-CvWyrVnY.mjs');
228
228
  const { machine, server } = await connectAndGetMachine(machineId);
229
229
  try {
230
230
  const info = await machine.serveInfo();