svamp-cli 0.1.67 → 0.1.69

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.
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-BDmLH9T8.mjs';
1
+ import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-DxmtlxnM.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -109,7 +109,7 @@ async function main() {
109
109
  const { handleServiceCommand } = await import('./commands-CF32XIau.mjs');
110
110
  await handleServiceCommand();
111
111
  } else if (subcommand === "process" || subcommand === "proc") {
112
- const { processCommand } = await import('./commands-CdQ8qr6l.mjs');
112
+ const { processCommand } = await import('./commands-CNWZF0B9.mjs');
113
113
  let machineId;
114
114
  const processArgs = args.slice(1);
115
115
  const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
@@ -127,7 +127,7 @@ async function main() {
127
127
  } else if (!subcommand || subcommand === "start") {
128
128
  await handleInteractiveCommand();
129
129
  } else if (subcommand === "--version" || subcommand === "-v") {
130
- const pkg = await import('./package-BeHXZuag.mjs').catch(() => ({ default: { version: "unknown" } }));
130
+ const pkg = await import('./package-D2QVDgm_.mjs').catch(() => ({ default: { version: "unknown" } }));
131
131
  console.log(`svamp version: ${pkg.default.version}`);
132
132
  } else {
133
133
  console.error(`Unknown command: ${subcommand}`);
@@ -136,7 +136,7 @@ async function main() {
136
136
  }
137
137
  }
138
138
  async function handleInteractiveCommand() {
139
- const { runInteractive } = await import('./run-ez-QlRKy.mjs');
139
+ const { runInteractive } = await import('./run-DqPEyYsf.mjs');
140
140
  const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
141
141
  let directory = process.cwd();
142
142
  let resumeSessionId;
@@ -181,7 +181,7 @@ async function handleAgentCommand() {
181
181
  return;
182
182
  }
183
183
  if (agentArgs[0] === "list") {
184
- const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-BDmLH9T8.mjs').then(function (n) { return n.i; });
184
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-DxmtlxnM.mjs').then(function (n) { return n.i; });
185
185
  console.log("Known agents:");
186
186
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
187
187
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
@@ -193,7 +193,7 @@ async function handleAgentCommand() {
193
193
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
194
194
  return;
195
195
  }
196
- const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-BDmLH9T8.mjs').then(function (n) { return n.i; });
196
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-DxmtlxnM.mjs').then(function (n) { return n.i; });
197
197
  let cwd = process.cwd();
198
198
  const filteredArgs = [];
199
199
  for (let i = 0; i < agentArgs.length; i++) {
@@ -217,12 +217,12 @@ async function handleAgentCommand() {
217
217
  console.log(`Starting ${config.agentName} agent in ${cwd}...`);
218
218
  let backend;
219
219
  if (KNOWN_MCP_AGENTS[config.agentName]) {
220
- const { CodexMcpBackend } = await import('./run-BDmLH9T8.mjs').then(function (n) { return n.j; });
220
+ const { CodexMcpBackend } = await import('./run-DxmtlxnM.mjs').then(function (n) { return n.j; });
221
221
  backend = new CodexMcpBackend({ cwd, log: logFn });
222
222
  } else {
223
- const { AcpBackend } = await import('./run-BDmLH9T8.mjs').then(function (n) { return n.h; });
224
- const { GeminiTransport } = await import('./run-BDmLH9T8.mjs').then(function (n) { return n.G; });
225
- const { DefaultTransport } = await import('./run-BDmLH9T8.mjs').then(function (n) { return n.D; });
223
+ const { AcpBackend } = await import('./run-DxmtlxnM.mjs').then(function (n) { return n.h; });
224
+ const { GeminiTransport } = await import('./run-DxmtlxnM.mjs').then(function (n) { return n.G; });
225
+ const { DefaultTransport } = await import('./run-DxmtlxnM.mjs').then(function (n) { return n.D; });
226
226
  const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
227
227
  backend = new AcpBackend({
228
228
  agentName: config.agentName,
@@ -340,7 +340,7 @@ async function handleSessionCommand() {
340
340
  printSessionHelp();
341
341
  return;
342
342
  }
343
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-Dpc6QK0m.mjs');
343
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-CgWkG45c.mjs');
344
344
  const parseFlagStr = (flag, shortFlag) => {
345
345
  for (let i = 1; i < sessionArgs.length; i++) {
346
346
  if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
@@ -400,7 +400,7 @@ async function handleSessionCommand() {
400
400
  allowDomain.push(sessionArgs[++i]);
401
401
  }
402
402
  }
403
- const { parseShareArg } = await import('./commands-Dpc6QK0m.mjs');
403
+ const { parseShareArg } = await import('./commands-CgWkG45c.mjs');
404
404
  const shareEntries = share.map((s) => parseShareArg(s));
405
405
  await sessionSpawn(agent, dir, targetMachineId, {
406
406
  message,
@@ -484,7 +484,7 @@ async function handleSessionCommand() {
484
484
  console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
485
485
  process.exit(1);
486
486
  }
487
- const { sessionApprove } = await import('./commands-Dpc6QK0m.mjs');
487
+ const { sessionApprove } = await import('./commands-CgWkG45c.mjs');
488
488
  const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
489
489
  await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
490
490
  json: hasFlag("--json")
@@ -494,7 +494,7 @@ async function handleSessionCommand() {
494
494
  console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
495
495
  process.exit(1);
496
496
  }
497
- const { sessionDeny } = await import('./commands-Dpc6QK0m.mjs');
497
+ const { sessionDeny } = await import('./commands-CgWkG45c.mjs');
498
498
  const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
499
499
  await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
500
500
  json: hasFlag("--json")
@@ -597,7 +597,7 @@ async function handleMachineCommand() {
597
597
  return;
598
598
  }
599
599
  if (machineSubcommand === "share") {
600
- const { machineShare } = await import('./commands-Dpc6QK0m.mjs');
600
+ const { machineShare } = await import('./commands-CgWkG45c.mjs');
601
601
  let machineId;
602
602
  const shareArgs = [];
603
603
  for (let i = 1; i < machineArgs.length; i++) {
@@ -627,7 +627,7 @@ async function handleMachineCommand() {
627
627
  }
628
628
  await machineShare(machineId, { add, remove, list, configPath, showConfig });
629
629
  } else if (machineSubcommand === "exec") {
630
- const { machineExec } = await import('./commands-Dpc6QK0m.mjs');
630
+ const { machineExec } = await import('./commands-CgWkG45c.mjs');
631
631
  let machineId;
632
632
  let cwd;
633
633
  const cmdParts = [];
@@ -647,7 +647,7 @@ async function handleMachineCommand() {
647
647
  }
648
648
  await machineExec(machineId, command, cwd);
649
649
  } else if (machineSubcommand === "info") {
650
- const { machineInfo } = await import('./commands-Dpc6QK0m.mjs');
650
+ const { machineInfo } = await import('./commands-CgWkG45c.mjs');
651
651
  let machineId;
652
652
  for (let i = 1; i < machineArgs.length; i++) {
653
653
  if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
@@ -670,7 +670,7 @@ async function handleMachineCommand() {
670
670
  const { machineNotify } = await import('./agentCommands-C6iGblcL.mjs');
671
671
  await machineNotify(message, level);
672
672
  } else if (machineSubcommand === "ls") {
673
- const { machineLs } = await import('./commands-Dpc6QK0m.mjs');
673
+ const { machineLs } = await import('./commands-CgWkG45c.mjs');
674
674
  let machineId;
675
675
  let showHidden = false;
676
676
  let path;
@@ -684,43 +684,6 @@ async function handleMachineCommand() {
684
684
  }
685
685
  }
686
686
  await machineLs(machineId, path, showHidden);
687
- } else if (machineSubcommand === "storage") {
688
- const { storageList, storageOrphans, storageMarkOrphan, storageUnmarkOrphan, storageDelete, printStorageHelp } = await import('./storageCommands-CKhntx1P.mjs');
689
- const storageArgs = machineArgs.slice(1);
690
- const storageSubcmd = storageArgs[0];
691
- if (!storageSubcmd || storageSubcmd === "--help" || storageSubcmd === "-h") {
692
- printStorageHelp();
693
- } else if (storageSubcmd === "list") {
694
- await storageList();
695
- } else if (storageSubcmd === "orphans") {
696
- const mark = storageArgs.includes("--mark");
697
- await storageOrphans(mark);
698
- } else if (storageSubcmd === "mark-orphan") {
699
- const ns = storageArgs[1];
700
- if (!ns) {
701
- console.error("Usage: svamp machine storage mark-orphan <namespace>");
702
- process.exit(1);
703
- }
704
- await storageMarkOrphan(ns);
705
- } else if (storageSubcmd === "unmark-orphan") {
706
- const ns = storageArgs[1];
707
- if (!ns) {
708
- console.error("Usage: svamp machine storage unmark-orphan <namespace>");
709
- process.exit(1);
710
- }
711
- await storageUnmarkOrphan(ns);
712
- } else if (storageSubcmd === "delete") {
713
- const ns = storageArgs[1];
714
- if (!ns) {
715
- console.error("Usage: svamp machine storage delete <namespace>");
716
- process.exit(1);
717
- }
718
- await storageDelete(ns);
719
- } else {
720
- console.error(`Unknown storage command: ${storageSubcmd}`);
721
- printStorageHelp();
722
- process.exit(1);
723
- }
724
687
  } else {
725
688
  console.error(`Unknown machine command: ${machineSubcommand}`);
726
689
  printMachineHelp();
@@ -1322,13 +1285,6 @@ Usage:
1322
1285
  svamp machine share --config <path> Apply security context config
1323
1286
  svamp machine share --show-config Show current security context config
1324
1287
 
1325
- Storage (admin):
1326
- svamp machine storage list List all user PVCs with status
1327
- svamp machine storage orphans [--mark] Show PVCs with no active namespace
1328
- svamp machine storage mark-orphan <namespace> Flag a PVC as orphaned
1329
- svamp machine storage unmark-orphan <namespace> Remove orphan flag
1330
- svamp machine storage delete <namespace> Permanently delete a PVC (double confirm)
1331
-
1332
1288
  Options:
1333
1289
  --machine <id>, -m <id> Target a specific machine (prefix match supported)
1334
1290
  --cwd <path> Working directory for exec (default: home dir)
@@ -1,11 +1,11 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-Dpc6QK0m.mjs';
3
+ import { connectAndGetMachine } from './commands-CgWkG45c.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-BDmLH9T8.mjs';
8
+ import './run-DxmtlxnM.mjs';
9
9
  import 'os';
10
10
  import 'fs/promises';
11
11
  import 'url';
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { resolve, join } from 'node:path';
4
4
  import os from 'node:os';
5
- import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-BDmLH9T8.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-DxmtlxnM.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
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-BDmLH9T8.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-DxmtlxnM.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.1.67";
2
+ var version = "0.1.69";
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";
@@ -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-BDmLH9T8.mjs';
5
+ import { c as connectToHypha, a as registerSessionService } from './run-DxmtlxnM.mjs';
6
6
  import { createServer } from 'node:http';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { createInterface } from 'node:readline';
@@ -1158,6 +1158,7 @@ async function registerSessionService(server, sessionId, initialMetadata, initia
1158
1158
  sessionId,
1159
1159
  metadata: { value: metadata, version: metadataVersion }
1160
1160
  });
1161
+ callbacks.onSharingUpdate?.(newSharing);
1161
1162
  return { success: true, sharing: newSharing };
1162
1163
  },
1163
1164
  /** Update security context and restart the agent process with new rules */
@@ -4466,6 +4467,53 @@ async function installSkillFromMarketplace(name) {
4466
4467
  writeFileSync(localPath, content, "utf-8");
4467
4468
  }
4468
4469
  }
4470
+ function preventMachineSleep(logger) {
4471
+ if (process.platform === "darwin") {
4472
+ const caff = spawn$1("caffeinate", ["-s", "-i", "-m"], { stdio: "ignore", detached: false });
4473
+ caff.on("error", (err) => {
4474
+ logger.log(`Warning: could not start caffeinate to prevent sleep: ${err.message}`);
4475
+ });
4476
+ caff.on("exit", (code) => {
4477
+ if (code !== null && code !== 0) {
4478
+ logger.log(`Warning: caffeinate exited unexpectedly (code ${code})`);
4479
+ }
4480
+ });
4481
+ process.on("exit", () => {
4482
+ try {
4483
+ caff.kill();
4484
+ } catch {
4485
+ }
4486
+ });
4487
+ logger.log("caffeinate started \u2014 machine will not sleep while daemon is running");
4488
+ return;
4489
+ }
4490
+ if (process.platform === "linux") {
4491
+ const inh = spawn$1(
4492
+ "systemd-inhibit",
4493
+ ["--what=idle:sleep:handle-lid-switch", "--who=svamp", "--why=Svamp daemon running", "--mode=block", "sleep", "infinity"],
4494
+ { stdio: "ignore", detached: false }
4495
+ );
4496
+ inh.on("error", () => {
4497
+ });
4498
+ inh.on("exit", (code) => {
4499
+ if (code !== null && code !== 0 && code !== 130) {
4500
+ logger.log(`Warning: systemd-inhibit exited with code ${code} \u2014 sleep prevention may be inactive`);
4501
+ }
4502
+ });
4503
+ process.on("exit", () => {
4504
+ try {
4505
+ inh.kill();
4506
+ } catch {
4507
+ }
4508
+ });
4509
+ setImmediate(() => {
4510
+ if (!inh.killed && inh.exitCode === null) {
4511
+ logger.log("systemd-inhibit started \u2014 machine will not idle-sleep while daemon is running");
4512
+ }
4513
+ });
4514
+ return;
4515
+ }
4516
+ }
4469
4517
  async function ensureAutoInstalledSkills(logger) {
4470
4518
  const tasks = [
4471
4519
  {
@@ -5158,8 +5206,12 @@ async function startDaemon(options) {
5158
5206
  // hypha-rpc connection closed message
5159
5207
  "Client disconnected",
5160
5208
  // Hypha client disconnect events
5161
- "fetch failed"
5209
+ "fetch failed",
5162
5210
  // Fetch API errors during reconnect
5211
+ "already exists in the cache store",
5212
+ // hypha-rpc: stale in-flight RPC calls after reconnect try to resolve with duplicate message keys
5213
+ "Failed to send the request when calling method"
5214
+ // hypha-rpc: RPC call failed after reconnect (often wraps the cache store error)
5163
5215
  ];
5164
5216
  let unhandledRejectionCount = 0;
5165
5217
  let unhandledRejectionResetTimer = null;
@@ -5254,6 +5306,7 @@ async function startDaemon(options) {
5254
5306
  await supervisor.init();
5255
5307
  ensureAutoInstalledSkills(logger).catch(() => {
5256
5308
  });
5309
+ preventMachineSleep(logger);
5257
5310
  try {
5258
5311
  logger.log("Connecting to Hypha server...");
5259
5312
  server = await connectToHypha({
@@ -6218,6 +6271,23 @@ The automated loop has finished. Review the progress above and let me know if yo
6218
6271
  sessionMetadata = { ...sessionMetadata, securityContext: newSecurityContext };
6219
6272
  return await restartClaudeHandler();
6220
6273
  },
6274
+ onSharingUpdate: (newSharing) => {
6275
+ logger.log(`[Session ${sessionId}] Sharing config updated \u2014 persisting to disk`);
6276
+ sessionMetadata = { ...sessionMetadata, sharing: newSharing };
6277
+ if (!trackedSession.stopped) {
6278
+ saveSession({
6279
+ sessionId,
6280
+ directory,
6281
+ claudeResumeId,
6282
+ permissionMode: currentPermissionMode,
6283
+ spawnMeta: lastSpawnMeta,
6284
+ metadata: sessionMetadata,
6285
+ createdAt: Date.now(),
6286
+ machineId,
6287
+ wasProcessing: sessionWasProcessing
6288
+ });
6289
+ }
6290
+ },
6221
6291
  onApplySystemPrompt: async (prompt) => {
6222
6292
  logger.log(`[Session ${sessionId}] System prompt update requested \u2014 restarting agent`);
6223
6293
  lastSpawnMeta = { ...lastSpawnMeta, appendSystemPrompt: prompt };
@@ -6609,6 +6679,10 @@ The automated loop has finished. Review the progress above and let me know if yo
6609
6679
  sessionMetadata = { ...sessionMetadata, securityContext: newSecurityContext };
6610
6680
  return { success: false, message: "Security context updates with restart are not yet supported for this agent type." };
6611
6681
  },
6682
+ onSharingUpdate: (newSharing) => {
6683
+ logger.log(`[${agentName} Session ${sessionId}] Sharing config updated \u2014 persisting in-memory`);
6684
+ sessionMetadata = { ...sessionMetadata, sharing: newSharing };
6685
+ },
6612
6686
  onApplySystemPrompt: async () => {
6613
6687
  return { success: false, message: "System prompt updates with restart are not yet supported for this agent type." };
6614
6688
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.1.67",
4
- "description": "Svamp CLI \u2014 AI workspace daemon on Hypha Cloud",
3
+ "version": "0.1.69",
4
+ "description": "Svamp CLI AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
7
7
  "type": "module",
@@ -1,154 +0,0 @@
1
- import { getSandboxEnv } from './api-BRbsyqJ4.mjs';
2
-
3
- async function adminFetch(path, method = "GET", body) {
4
- const env = getSandboxEnv();
5
- if (!env.apiKey) {
6
- throw new Error('No API credentials. Run "svamp login" or set SANDBOX_API_KEY.');
7
- }
8
- const url = `${env.apiUrl.replace(/\/+$/, "")}${path}`;
9
- const res = await fetch(url, {
10
- method,
11
- headers: {
12
- "Authorization": `Bearer ${env.apiKey}`,
13
- "Content-Type": "application/json"
14
- },
15
- ...{}
16
- });
17
- if (!res.ok) {
18
- const text = await res.text().catch(() => "");
19
- throw new Error(`HTTP ${res.status} ${method} ${path}: ${text}`);
20
- }
21
- return res.json();
22
- }
23
- function fmtAge(ts) {
24
- if (!ts) return "?";
25
- try {
26
- const dt = /^\d+$/.test(ts) ? new Date(parseInt(ts, 10) * 1e3) : new Date(ts);
27
- const secs = (Date.now() - dt.getTime()) / 1e3;
28
- const d = Math.floor(secs / 86400);
29
- const h = Math.floor(secs % 86400 / 3600);
30
- return d > 0 ? `${d}d ${h}h ago` : `${h}h ago`;
31
- } catch {
32
- return ts;
33
- }
34
- }
35
- function pad(s, n) {
36
- return s.length >= n ? s : s + " ".repeat(n - s.length);
37
- }
38
- async function storageList() {
39
- const data = await adminFetch("/admin/storage/pvcs");
40
- const pvcs = data.pvcs ?? [];
41
- if (!pvcs.length) {
42
- console.log("No managed PVCs found.");
43
- return;
44
- }
45
- console.log("");
46
- console.log(`${pad("NAMESPACE", 36)}${pad("SIZE", 9)}${pad("STATUS", 9)}${pad("CREATED", 20)}ORPHANED`);
47
- console.log("\u2500".repeat(90));
48
- for (const p of pvcs.sort((a, b) => a.namespace.localeCompare(b.namespace))) {
49
- const orphan = p.orphaned_at ? `\u26A0 ${fmtAge(p.orphaned_at)}` : "\u2014";
50
- console.log(`${pad(p.namespace, 36)}${pad(p.capacity ?? "?", 9)}${pad(p.status ?? "?", 9)}${pad(fmtAge(p.created_at), 20)}${orphan}`);
51
- }
52
- console.log(`
53
- Total: ${data.count} PVC(s)`);
54
- }
55
- async function storageOrphans(mark = false) {
56
- const data = await adminFetch(`/admin/orphaned-pvcs?dry_run=${mark ? "false" : "true"}`);
57
- const orphans = data.orphaned_pvcs ?? [];
58
- if (!orphans.length) {
59
- console.log("\u2705 No orphaned PVCs found.");
60
- return;
61
- }
62
- console.log(`
63
- \u26A0 ${orphans.length} orphaned PVC(s) (namespace has no running pods):
64
- `);
65
- console.log(`${pad("NAMESPACE", 36)}${pad("SIZE", 9)}${pad("MARKED", 9)}ORPHANED FOR`);
66
- console.log("\u2500".repeat(72));
67
- for (const o of orphans) {
68
- const age = o.orphaned_for || (o.marked ? "?" : "not marked");
69
- console.log(`${pad(o.namespace, 36)}${pad(o.storage ?? "?", 9)}${pad(o.marked ? "yes" : "no", 9)}${age}`);
70
- }
71
- if (!mark) {
72
- console.log("\nTip: pass --mark to start the retention clock on unmarked orphans.");
73
- }
74
- console.log("\nTo delete: svamp machine storage delete <namespace>");
75
- }
76
- async function storageMarkOrphan(namespace) {
77
- const data = await adminFetch(`/admin/storage/mark-orphan/${namespace}`, "POST");
78
- if (data.marked) {
79
- console.log(`\u2705 PVC for '${namespace}' marked as orphaned.`);
80
- console.log(` To delete: svamp machine storage delete ${namespace}`);
81
- } else {
82
- console.log(`\u2139 PVC for '${namespace}' was already marked or not found.`);
83
- }
84
- }
85
- async function storageUnmarkOrphan(namespace) {
86
- const data = await adminFetch(`/admin/storage/unmark-orphan/${namespace}`, "POST");
87
- if (data.unmarked) {
88
- console.log(`\u2705 Orphan mark removed from '${namespace}'.`);
89
- } else {
90
- console.log(`\u2139 PVC for '${namespace}' was not marked or not found.`);
91
- }
92
- }
93
- async function storageDelete(namespace) {
94
- const readline = await import('readline');
95
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
96
- const ask = (q) => new Promise((res) => rl.question(q, res));
97
- try {
98
- const data2 = await adminFetch("/admin/storage/pvcs");
99
- const pvc = (data2.pvcs ?? []).find((p) => p.namespace === namespace);
100
- if (!pvc) {
101
- console.error(`No PVC found for namespace '${namespace}'.`);
102
- rl.close();
103
- process.exit(1);
104
- }
105
- console.log(`
106
- \u26A0 Namespace: ${pvc.namespace}`);
107
- console.log(` Size: ${pvc.capacity ?? "?"}`);
108
- console.log(` Created: ${fmtAge(pvc.created_at)}`);
109
- if (pvc.orphaned_at) console.log(` Orphaned: ${fmtAge(pvc.orphaned_at)}`);
110
- } catch {
111
- }
112
- console.log("\n\u26A0 PERMANENTLY deleting this PVC will IRREVERSIBLY destroy all user data.\n");
113
- const a1 = (await ask(`Type the namespace name to confirm: `)).trim();
114
- if (a1 !== namespace) {
115
- console.log("Confirmation mismatch \u2014 aborted.");
116
- rl.close();
117
- return;
118
- }
119
- const a2 = (await ask(`Type it again to double-confirm: `)).trim();
120
- rl.close();
121
- if (a2 !== namespace) {
122
- console.log("Confirmation mismatch \u2014 aborted.");
123
- return;
124
- }
125
- const data = await adminFetch(`/admin/storage/pvc/${namespace}`, "DELETE");
126
- if (data.deleted) {
127
- console.log(`\u2705 PVC for '${namespace}' permanently deleted.`);
128
- } else {
129
- console.log(`\u2139 PVC for '${namespace}' was not found or already deleted.`);
130
- }
131
- }
132
- function printStorageHelp() {
133
- console.log(`
134
- svamp machine storage \u2014 sandbox PVC lifecycle management (admin only)
135
-
136
- Usage:
137
- svamp machine storage list List all PVCs with status
138
- svamp machine storage orphans [--mark] Show PVCs with no active namespace
139
- svamp machine storage mark-orphan <ns> Flag a PVC as orphaned (no deletion)
140
- svamp machine storage unmark-orphan <ns> Remove orphan flag
141
- svamp machine storage delete <ns> Permanently delete a PVC (double confirmation)
142
-
143
- Environment:
144
- SANDBOX_API_URL Sandbox API base URL (default: https://agent-sandbox.aicell.io)
145
- SANDBOX_API_KEY Admin API key (or use HYPHA_TOKEN from svamp login)
146
-
147
- Safety rules:
148
- - Automated systems NEVER delete PVCs
149
- - delete requires typing the namespace name twice
150
- - delete checks for running pods and refuses if any are active
151
- `);
152
- }
153
-
154
- export { printStorageHelp, storageDelete, storageList, storageMarkOrphan, storageOrphans, storageUnmarkOrphan };