svamp-cli 0.1.65 → 0.1.67

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-DPhSmbr1.mjs';
1
+ import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-BDmLH9T8.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-Dc_6JdE_.mjs');
112
+ const { processCommand } = await import('./commands-CdQ8qr6l.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-CvCDrFPH.mjs').catch(() => ({ default: { version: "unknown" } }));
130
+ const pkg = await import('./package-BeHXZuag.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-DTwMJ7dx.mjs');
139
+ const { runInteractive } = await import('./run-ez-QlRKy.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-DPhSmbr1.mjs').then(function (n) { return n.i; });
184
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-BDmLH9T8.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-DPhSmbr1.mjs').then(function (n) { return n.i; });
196
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-BDmLH9T8.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-DPhSmbr1.mjs').then(function (n) { return n.j; });
220
+ const { CodexMcpBackend } = await import('./run-BDmLH9T8.mjs').then(function (n) { return n.j; });
221
221
  backend = new CodexMcpBackend({ cwd, log: logFn });
222
222
  } else {
223
- const { AcpBackend } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.h; });
224
- const { GeminiTransport } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.G; });
225
- const { DefaultTransport } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.D; });
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; });
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-FAWhkKtz.mjs');
343
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-Dpc6QK0m.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-FAWhkKtz.mjs');
403
+ const { parseShareArg } = await import('./commands-Dpc6QK0m.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-FAWhkKtz.mjs');
487
+ const { sessionApprove } = await import('./commands-Dpc6QK0m.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-FAWhkKtz.mjs');
497
+ const { sessionDeny } = await import('./commands-Dpc6QK0m.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-FAWhkKtz.mjs');
600
+ const { machineShare } = await import('./commands-Dpc6QK0m.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-FAWhkKtz.mjs');
630
+ const { machineExec } = await import('./commands-Dpc6QK0m.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-FAWhkKtz.mjs');
650
+ const { machineInfo } = await import('./commands-Dpc6QK0m.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-FAWhkKtz.mjs');
673
+ const { machineLs } = await import('./commands-Dpc6QK0m.mjs');
674
674
  let machineId;
675
675
  let showHidden = false;
676
676
  let path;
@@ -684,6 +684,43 @@ 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
+ }
687
724
  } else {
688
725
  console.error(`Unknown machine command: ${machineSubcommand}`);
689
726
  printMachineHelp();
@@ -1044,50 +1081,42 @@ function printHelp() {
1044
1081
  console.log(`
1045
1082
  svamp \u2014 AI workspace on Hypha Cloud
1046
1083
 
1047
- Usage:
1048
- svamp Start interactive Claude session (synced to cloud)
1049
- svamp start [-d <path>] Same as above, with explicit directory
1050
- svamp login [url] Login to Hypha (opens browser, stores token)
1051
- svamp daemon start Start the daemon (detached)
1052
- svamp daemon stop Stop the daemon (sessions preserved for restart)
1053
- svamp daemon restart Restart the daemon (sessions resume seamlessly)
1054
- svamp daemon status Show daemon status
1055
- svamp daemon install Install as system service (launchd/systemd/wrapper)
1056
- svamp session list List active sessions
1057
- svamp session spawn Spawn a new session on the daemon
1058
- svamp session send <id> <m> Send message to a session
1059
- svamp session wait <id> Wait for agent to become idle
1060
- svamp session info <id> Show session status & pending permissions
1061
- svamp session messages <id> Show message history
1062
- svamp session approve <id> Approve pending permission request
1063
- svamp session deny <id> Deny pending permission request
1064
- svamp session attach <id> Attach to a session (interactive terminal)
1065
- svamp session share <id> Manage session sharing
1066
- svamp session --help Show all session commands
1067
- svamp machine share Manage machine sharing & security contexts
1068
- svamp machine --help Show all machine commands
1069
- svamp skills find <query> Search the skills marketplace
1070
- svamp skills install <n> Install a skill from the marketplace
1071
- svamp skills --help Show all skill commands
1072
- svamp process apply <f.yaml> Create/update a supervised process (idempotent)
1073
- svamp process list List supervised processes
1074
- svamp process start <name> Start or create a process
1075
- svamp process logs <name> Show process log output
1076
- svamp process --help Show all process commands
1077
- svamp service expose <name> Expose a service from this sandbox
1078
- svamp service list List service groups
1079
- svamp service --help Show all service commands
1080
- svamp agent list List known agents
1081
- svamp agent <name> Start local agent session (gemini, codex)
1082
- svamp --version Show version
1083
- svamp --help Show this help
1084
+ Quick start \u2014 spawn an agent to do a task (use -p bypassPermissions to run autonomously):
1085
+ svamp session spawn claude -d ./project -p bypassPermissions --message "fix tests" --wait
1086
+
1087
+ Note: without -p bypassPermissions the agent PAUSES waiting for human approval of each
1088
+ tool use. This will cause it to hang in automated/agent contexts. Use bypassPermissions
1089
+ for autonomous work, or "default" only when a human is actively watching.
1090
+
1091
+ Commands:
1092
+ svamp Start interactive Claude session (synced to cloud)
1093
+ svamp login [url] Login to Hypha (opens browser, stores token)
1094
+ svamp daemon start Start the background daemon (required for sessions)
1095
+ svamp daemon status Show daemon status
1096
+ svamp daemon --help Show all daemon commands
1097
+
1098
+ Session management (requires daemon running):
1099
+ svamp session spawn <agent> [opts] Spawn a new agent session (agents: claude, gemini, codex)
1100
+ svamp session send <id> <message> Send message to a session (--wait to block until done)
1101
+ svamp session wait <id> Wait for agent to become idle
1102
+ svamp session messages <id> Show message history (--last N, --json, --raw)
1103
+ svamp session info <id> Show session status & pending permissions
1104
+ svamp session list List sessions
1105
+ svamp session stop <id> Stop a session
1106
+ svamp session attach <id> Attach interactive terminal (stdin/stdout)
1107
+ svamp session approve/deny <id> Approve or deny pending permission requests
1108
+ svamp session --help Show ALL session commands and detailed options
1109
+
1110
+ Other:
1111
+ svamp machine --help Machine sharing & security contexts
1112
+ svamp skills --help Skills marketplace (find, install, publish)
1113
+ svamp service --help Service exposure (HTTP services from sandboxes)
1114
+ svamp agent <name> Start local agent session (no daemon needed)
1115
+ svamp --version Show version
1084
1116
 
1085
1117
  Interactive mode:
1086
- When you run 'svamp' with no arguments, Claude starts in your terminal
1087
- with full interactive access. Your session is synced to Hypha Cloud so
1088
- it's visible in the web app. When a message arrives from the web app,
1089
- svamp switches to remote mode to process it, then you can press
1090
- Space-Space to return to local mode.
1118
+ Run 'svamp' with no arguments to start Claude in your terminal with cloud sync.
1119
+ Messages from the web app auto-switch to remote mode. Space-Space returns to local.
1091
1120
 
1092
1121
  Environment variables:
1093
1122
  HYPHA_SERVER_URL Hypha server URL (required for cloud sync)
@@ -1113,149 +1142,170 @@ Usage:
1113
1142
  }
1114
1143
  function printSessionHelp() {
1115
1144
  console.log(`
1116
- svamp session \u2014 Manage daemon sessions (Claude, Gemini, Codex)
1145
+ svamp session \u2014 Spawn and manage AI agent sessions
1117
1146
 
1118
- Commands:
1119
- svamp session list [--active] [--json] List sessions (alias: ls)
1120
- svamp session machines List discoverable machines
1121
- svamp session spawn <agent> [-d <path>] [options] Spawn a new session
1122
- svamp session stop <id> Stop a session
1123
- svamp session info <id> [--json] Show status, activity & pending permissions
1124
- svamp session send <id> <message> [--wait] [--timeout N] [--json]
1125
- Send a message to a session
1126
- svamp session wait <id> [--timeout N] [--json] Wait for agent to become idle
1127
- svamp session messages <id> [--last N] [--json] [--raw] [--after N] [--limit N]
1128
- Show messages (alias: msgs)
1129
- svamp session attach <id> Attach to session (interactive terminal)
1130
- svamp session approve <id> [request-id] [--json] Approve pending permission(s)
1131
- svamp session deny <id> [request-id] [--json] Deny pending permission(s)
1147
+ QUICK START \u2014 Spawn a session, give it a task, get results:
1132
1148
 
1133
- Spawn options:
1134
- -d, --directory <path> Working directory (default: cwd)
1135
- -p, --permission-mode <mode> Agent permission mode (see "Permission modes" below)
1136
- --message <msg> Send initial message after spawn
1137
- --wait Wait for agent to become idle before returning
1138
- --worktree Create a git worktree branch (at .dev/worktree/<name>)
1139
- --isolate Force OS-level sandbox isolation
1140
- --security-context <path> Apply security context config (JSON file)
1141
- --share <email>[:<role>] Share with user (repeatable). Role: view, interact, admin
1142
- --deny-network Block all network access
1143
- --deny-read <path> Deny reading path (repeatable)
1144
- --allow-write <path> Allow writing path (repeatable)
1145
- --allow-domain <domain> Allow network domain (repeatable)
1149
+ # One-liner: spawn agent, send task, wait for completion
1150
+ svamp session spawn claude -d ./my-project -p bypassPermissions \\
1151
+ --message "fix the failing tests" --wait
1146
1152
 
1147
- Permission modes (-p, --permission-mode):
1148
- default Prompt for each tool use (default)
1149
- acceptEdits Auto-approve file edits, prompt for bash/dangerous tools
1150
- bypassPermissions Auto-approve all tools (no prompts)
1153
+ # The command prints the session ID (e.g., "abc12345-..."). Use it to:
1154
+ svamp session messages abc1 --last 10 # read output (prefix match on ID)
1155
+ svamp session send abc1 "now run lint" --wait # send follow-up, wait for done
1156
+ svamp session stop abc1 # stop when finished
1151
1157
 
1152
- Permission workflow:
1153
- When an agent runs with 'default' or 'acceptEdits' permission mode, it pauses
1154
- when it needs to use a tool that requires approval. Use these commands to manage:
1158
+ # Spawn on a remote/cloud machine:
1159
+ svamp session spawn claude -d /workspace -m cloud-box --message "deploy" --wait
1155
1160
 
1156
- 1. Check status: svamp session info <id> --json
1157
- \u2192 shows pendingPermissions array when agent is blocked
1158
- 2. Wait & detect: svamp session wait <id> --json
1159
- \u2192 exits with code 2 if permission pending (see exit codes)
1160
- 3. Approve: svamp session approve <id> (approve all pending)
1161
- svamp session approve <id> <rid> (approve specific request)
1162
- 4. Deny: svamp session deny <id> (deny all pending)
1163
- svamp session deny <id> <rid> (deny specific request)
1161
+ AGENTS:
1162
+ claude Claude Code \u2014 full coding agent (default)
1163
+ gemini Google Gemini via ACP protocol
1164
+ codex OpenAI Codex via MCP protocol
1164
1165
 
1165
- Request IDs support prefix matching (e.g., "abc1" matches "abc12345-...").
1166
+ Usage: svamp session spawn <agent> [options]
1167
+ The <agent> argument is required. Use "claude" if unsure.
1166
1168
 
1167
- Messages options:
1168
- --last N Show only the last N messages
1169
- --after N Start after sequence number N
1170
- --limit N Max messages to fetch from server (default: 1000, cap: 500 per page)
1171
- --json Output as JSON (FormattedMessage: id, seq, role, text, createdAt)
1172
- --raw With --json: output full raw message objects (includes tool_use args,
1173
- tool_result content, thinking blocks \u2014 use for programmatic parsing)
1169
+ COMMANDS:
1174
1170
 
1175
- Exit codes (wait, send --wait, spawn --wait):
1176
- 0 Agent is idle (task completed)
1177
- 1 Error (timeout, connection failure, session not found)
1178
- 2 Agent is waiting for permission approval \u2014 use approve/deny to continue
1171
+ Lifecycle:
1172
+ spawn <agent> [-d <path>] [options] Spawn a new agent session
1173
+ stop <id> Stop a running session
1174
+ list [--active] [--json] List sessions (alias: ls)
1175
+ machines List discoverable machines
1176
+ info <id> [--json] Show status & pending permissions
1179
1177
 
1180
- Global options:
1181
- --machine <id>, -m <id> Target a specific machine (prefix match supported)
1178
+ Communicate:
1179
+ send <id> <message> [--wait] [--timeout N] [--json]
1180
+ Send a message to a running session
1181
+ messages <id> [--last N] [--json] Read message history (alias: msgs)
1182
+ wait <id> [--timeout N] [--json] Wait for agent to become idle
1183
+ attach <id> Attach interactive terminal (stdin/stdout)
1182
1184
 
1183
- Agents: claude (default), gemini, codex
1184
- Session and machine IDs support prefix matching (e.g., "abc1" matches "abc12345-...").
1185
+ Permissions (when agent pauses for approval):
1186
+ approve <id> [request-id] [--json] Approve pending tool permission(s)
1187
+ deny <id> [request-id] [--json] Deny pending tool permission(s)
1185
1188
 
1186
- Sharing:
1187
- svamp session share <id> --list List shared users
1188
- svamp session share <id> --add <email>[:<role>] Share with user
1189
- svamp session share <id> --remove <email> Remove shared user
1190
- svamp session share <id> --public <view|interact|off> Set public link access
1189
+ Sharing:
1190
+ share <id> --list List shared users
1191
+ share <id> --add <email>[:<role>] Share with user (role: view|interact|admin)
1192
+ share <id> --remove <email> Remove shared user
1193
+ share <id> --public <view|interact|off> Set public link access
1191
1194
 
1192
- Options:
1193
- --machine <id>, -m <id> Target a specific machine (prefix match supported)
1195
+ Ralph Loop (iterative automation):
1196
+ ralph-start <id> "<task>" [options] Start iterative loop
1197
+ ralph-cancel <id> Cancel loop
1198
+ ralph-status <id> Show loop status
1194
1199
 
1195
- Spawn options:
1196
- -p, --permission-mode <mode> Agent permission mode: default, acceptEdits, bypassPermissions
1197
- --worktree Create a git worktree branch as working directory
1200
+ Message Queue:
1201
+ queue add <id> "<message>" Queue a message for later
1202
+ queue list <id> List queued messages
1203
+ queue clear <id> Clear queue
1198
1204
 
1199
- Spawn isolation options:
1200
- --share <email>[:<role>] Share session (repeatable). Role: view, interact, admin
1201
- --security-context <path> Path to security context JSON config file
1202
- --isolate Force OS-level isolation even without sharing
1203
- --deny-read <path> Deny read access to path (repeatable)
1204
- --allow-write <path> Allow write access to path (repeatable)
1205
- --deny-network Deny all network access
1206
- --allow-domain <domain> Allow network access to domain (repeatable)
1205
+ SPAWN OPTIONS:
1206
+ -d, --directory <path> Working directory (REQUIRED for meaningful work)
1207
+ -p, --permission-mode <mode> Permission mode (see below). Default: "default"
1208
+ --message <msg> Send this message to the agent immediately after spawn
1209
+ --wait Block until agent finishes (idle). Combine with --message
1210
+ for fire-and-forget: spawn \u2192 task \u2192 wait \u2192 done.
1211
+ --timeout <sec> Timeout for --wait (default: no timeout)
1212
+ --worktree Create isolated git worktree branch (.dev/worktree/<name>)
1213
+ --tags <tag1,tag2> Comma-separated tags for session grouping
1214
+ --parent <sessionId> Set parent session (auto-set from SVAMP_SESSION_ID env)
1207
1215
 
1208
- Spawn grouping options:
1209
- --tags <tag1,tag2> Comma-separated tags
1210
- --parent <sessionId> Set parent session (auto-detected from SVAMP_SESSION_ID)
1216
+ PERMISSION MODES:
1217
+ default \u26A0 Agent PAUSES and waits for human approval before each tool use.
1218
+ The agent will be stuck (exit code 2) until you run approve/deny.
1219
+ DO NOT use default mode in automated pipelines \u2014 the agent will hang.
1220
+ acceptEdits Auto-approve file edits. Still pauses for bash & dangerous tools.
1221
+ bypassPermissions Auto-approve everything. No prompts, no pauses. Fully autonomous.
1222
+ Use this for automation, CI, or when spawning from another agent.
1211
1223
 
1212
- Agents: claude (default), gemini, codex
1224
+ \u26A1 For agents/scripts: ALWAYS pass -p bypassPermissions unless you have a human
1225
+ monitoring the session who can approve/deny tool requests in real time.
1213
1226
 
1214
- Session and machine IDs can be abbreviated (prefix match, like Docker).
1227
+ PERMISSION WORKFLOW (only relevant for "default" and "acceptEdits" modes):
1228
+ 1. svamp session wait <id> --json \u2192 exit code 2 means permission pending
1229
+ 2. svamp session info <id> --json \u2192 shows pendingPermissions array
1230
+ 3. svamp session approve <id> \u2192 approve all pending (or approve <id> <rid>)
1231
+ 4. svamp session deny <id> \u2192 deny all pending (or deny <id> <rid>)
1215
1232
 
1216
- NOTE: By default, spawned agents run with 'default' permission mode, which means
1217
- the agent may pause to request permission for tool use (file edits, bash, etc.).
1218
- Use -p bypassPermissions to run without approval prompts.
1233
+ ISOLATION & SHARING OPTIONS (for spawn):
1234
+ --isolate Force OS-level sandbox (even without sharing)
1235
+ --security-context <path> Apply security context config (JSON file)
1236
+ --share <email>[:<role>] Share session (repeatable). Role: view|interact|admin
1237
+ --deny-network Block all network access
1238
+ --deny-read <path> Deny reading path (repeatable)
1239
+ --allow-write <path> Allow writing path (repeatable)
1240
+ --allow-domain <domain> Allow network domain (repeatable)
1241
+
1242
+ MESSAGES OPTIONS:
1243
+ --last N Show last N messages only
1244
+ --after N Start after sequence number N (for pagination)
1245
+ --limit N Max messages to fetch (default: 1000)
1246
+ --json Output as JSON array of {id, seq, role, text, createdAt}
1247
+ --raw With --json: include full raw objects (tool_use, tool_result, thinking)
1248
+
1249
+ RALPH LOOP OPTIONS (for ralph-start):
1250
+ --context-mode <mode> fresh (new process each iteration) or continue (same process)
1251
+ --completion-promise <text> Completion signal text (default: DONE)
1252
+ --max-iterations <n> Max iterations (default: 10)
1253
+ --cooldown <sec> Delay between iterations (default: 1)
1254
+
1255
+ EXIT CODES (for wait, send --wait, spawn --wait):
1256
+ 0 Agent is idle (task completed successfully)
1257
+ 1 Error (timeout, connection failure, session not found)
1258
+ 2 Agent is waiting for permission approval \u2014 use approve/deny to unblock
1219
1259
 
1220
- Attach commands:
1260
+ ATTACH COMMANDS (inside an attached session):
1221
1261
  /quit, /detach Detach (session keeps running)
1222
1262
  /abort, /cancel Cancel current agent turn
1223
- /kill Stop the session
1263
+ /kill Stop the session entirely
1224
1264
  /info Show session status
1225
1265
 
1226
- Ralph Loop (iterative task automation):
1227
- svamp session ralph-start <id> "<task>" [--context-mode fresh|continue] [--completion-promise TEXT] [--max-iterations N] [--cooldown N]
1228
- svamp session ralph-cancel <id>
1229
- svamp session ralph-status <id>
1230
- (alias: svamp session ralph <id> "<task>" ...)
1266
+ GLOBAL OPTIONS:
1267
+ --machine <id>, -m <id> Target a specific machine (prefix match supported)
1231
1268
 
1232
- Message Queue:
1233
- svamp session queue add <id> "<message>"
1234
- svamp session queue list <id>
1235
- svamp session queue clear <id>
1269
+ ID MATCHING:
1270
+ Session and machine IDs support prefix matching.
1271
+ "abc1" matches "abc12345-6789-..." \u2014 you only need enough characters to be unique.
1236
1272
 
1237
- Examples:
1238
- # Spawn with bypassed permissions (no prompts)
1239
- svamp session spawn claude -d ./proj -p bypassPermissions --message "fix tests" --wait
1273
+ EXAMPLES:
1274
+
1275
+ # === Common: Spawn agent to do a task and wait ===
1276
+ svamp session spawn claude -d ./my-project -p bypassPermissions \\
1277
+ --message "fix all TypeScript errors, run tsc to verify" --wait
1240
1278
 
1241
- # Spawn with default permissions, handle approval via CLI
1242
- svamp session spawn claude -d ./proj --message "refactor auth"
1243
- svamp session wait abc1 --json # exits with code 2 if permission pending
1244
- svamp session approve abc1 # approve all pending permissions
1245
- svamp session wait abc1 # wait for completion
1279
+ # === Multi-step: Spawn, then send messages interactively ===
1280
+ ID=$(svamp session spawn claude -d ./proj -p bypassPermissions)
1281
+ svamp session send $ID "first, read the README" --wait
1282
+ svamp session send $ID "now implement the feature described there" --wait
1283
+ svamp session messages $ID --last 20
1246
1284
 
1247
- # Monitor session messages (raw for full tool details)
1248
- svamp session messages abc1 --last 10 --json --raw
1285
+ # === Spawn child session from another agent (e.g., in a bash tool) ===
1286
+ # Use -p bypassPermissions so the child doesn't get stuck on approvals.
1287
+ # Use --wait so your script blocks until the child finishes.
1288
+ svamp session spawn claude -d /tmp/test-project -p bypassPermissions \\
1289
+ --message "run the test suite and report results" --wait
1290
+ # Then read the child's output:
1291
+ svamp session messages <child-id> --last 5
1249
1292
 
1250
- # Send message and wait for completion
1251
- svamp session send abc1 "run the tests" --wait --json
1293
+ # === Monitor with permissions (default mode) ===
1294
+ svamp session spawn claude -d ./proj --message "refactor auth module"
1295
+ svamp session wait abc1 --json # exit code 2 = permission pending
1296
+ svamp session approve abc1 # approve all pending
1297
+ svamp session wait abc1 # wait for completion
1252
1298
 
1253
- # Isolation & sharing
1299
+ # === Spawn on a remote machine ===
1300
+ svamp session spawn claude -d /workspace -m my-cloud-box \\
1301
+ -p bypassPermissions --message "deploy to staging" --wait
1302
+
1303
+ # === Isolated session with network restrictions ===
1254
1304
  svamp session spawn claude -d /tmp/sandbox --isolate --deny-network
1255
- svamp session spawn claude -d /tmp/proj --share alice@example.com:admin
1256
1305
 
1257
- # Ralph Loop
1258
- svamp session ralph-start abc1 "Fix all linting errors" --context-mode fresh --max-iterations 10
1306
+ # === Ralph Loop: iterative task automation ===
1307
+ svamp session ralph-start abc1 "Fix all linting errors" \\
1308
+ --context-mode fresh --max-iterations 10
1259
1309
  `);
1260
1310
  }
1261
1311
  function printMachineHelp() {
@@ -1272,6 +1322,13 @@ Usage:
1272
1322
  svamp machine share --config <path> Apply security context config
1273
1323
  svamp machine share --show-config Show current security context config
1274
1324
 
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
+
1275
1332
  Options:
1276
1333
  --machine <id>, -m <id> Target a specific machine (prefix match supported)
1277
1334
  --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-FAWhkKtz.mjs';
3
+ import { connectAndGetMachine } from './commands-Dpc6QK0m.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-DPhSmbr1.mjs';
8
+ import './run-BDmLH9T8.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-DPhSmbr1.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-BDmLH9T8.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-DPhSmbr1.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-BDmLH9T8.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.65";
2
+ var version = "0.1.67";
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";
@@ -19,7 +19,7 @@ var exports$1 = {
19
19
  var scripts = {
20
20
  build: "rm -rf dist && tsc --noEmit && pkgroll",
21
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",
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",
23
23
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
24
  dev: "tsx src/cli.ts",
25
25
  "dev:daemon": "tsx src/cli.ts daemon start-sync",
@@ -5411,6 +5411,10 @@ async function startDaemon(options) {
5411
5411
  securityContext: options2.securityContext,
5412
5412
  tags: options2.tags,
5413
5413
  parentSessionId: options2.parentSessionId,
5414
+ ...options2.parentSessionId && (() => {
5415
+ const parentTracked = Array.from(pidToTrackedSession.values()).find((t) => t.svampSessionId === options2.parentSessionId);
5416
+ return parentTracked?.directory ? { parentSessionPath: parentTracked.directory } : {};
5417
+ })(),
5414
5418
  ...options2.injectPlatformGuidance !== void 0 && { injectPlatformGuidance: options2.injectPlatformGuidance }
5415
5419
  };
5416
5420
  let claudeProcess = null;
@@ -5421,6 +5425,7 @@ async function startDaemon(options) {
5421
5425
  let lastSpawnMeta = persisted?.spawnMeta || {};
5422
5426
  let sessionWasProcessing = !!options2.wasProcessing;
5423
5427
  let lastAssistantText = "";
5428
+ let spawnHasReceivedInit = false;
5424
5429
  const signalProcessing = (processing) => {
5425
5430
  sessionService.sendKeepAlive(processing);
5426
5431
  const newState = processing ? "running" : "idle";
@@ -5535,6 +5540,7 @@ async function startDaemon(options) {
5535
5540
  shell: process.platform === "win32"
5536
5541
  });
5537
5542
  claudeProcess = child;
5543
+ spawnHasReceivedInit = false;
5538
5544
  logger.log(`[Session ${sessionId}] Claude PID: ${child.pid}, stdin: ${!!child.stdin}, stdout: ${!!child.stdout}, stderr: ${!!child.stderr}`);
5539
5545
  child.stdin?.on("error", (err) => {
5540
5546
  logger.log(`[Session ${sessionId}] Claude stdin error: ${err.message}`);
@@ -5893,7 +5899,9 @@ The automated loop has finished. Review the progress above and let me know if yo
5893
5899
  }
5894
5900
  userMessagePending = false;
5895
5901
  if (msg.session_id) {
5896
- const isConversationClear = claudeResumeId && msg.session_id !== claudeResumeId;
5902
+ const isResumeFailure = !spawnHasReceivedInit && claudeResumeId && msg.session_id !== claudeResumeId;
5903
+ const isConversationClear = spawnHasReceivedInit && claudeResumeId && msg.session_id !== claudeResumeId;
5904
+ spawnHasReceivedInit = true;
5897
5905
  claudeResumeId = msg.session_id;
5898
5906
  sessionMetadata = { ...sessionMetadata, claudeSessionId: msg.session_id };
5899
5907
  sessionService.updateMetadata(sessionMetadata);
@@ -5911,7 +5919,9 @@ The automated loop has finished. Review the progress above and let me know if yo
5911
5919
  });
5912
5920
  artifactSync.scheduleDebouncedSync(sessionId, getSessionDir(directory, sessionId), sessionMetadata, machineId);
5913
5921
  }
5914
- if (isConversationClear) {
5922
+ if (isResumeFailure) {
5923
+ logger.log(`[Session ${sessionId}] Resume failed \u2014 Claude started fresh session (tried: ${persisted?.claudeResumeId ?? "unknown"}, got: ${msg.session_id})`);
5924
+ } else if (isConversationClear) {
5915
5925
  logger.log(`[Session ${sessionId}] Conversation cleared (/clear) \u2014 new Claude session: ${msg.session_id}`);
5916
5926
  sessionService.clearMessages();
5917
5927
  sessionService.pushMessage(
@@ -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-DPhSmbr1.mjs';
5
+ import { c as connectToHypha, a as registerSessionService } from './run-BDmLH9T8.mjs';
6
6
  import { createServer } from 'node:http';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { createInterface } from 'node:readline';
@@ -0,0 +1,154 @@
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 };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.1.65",
4
- "description": "Svamp CLI AI workspace daemon on Hypha Cloud",
3
+ "version": "0.1.67",
4
+ "description": "Svamp CLI \u2014 AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
7
7
  "type": "module",
@@ -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",
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",
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",