svamp-cli 0.2.263 → 0.2.265

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.
Files changed (26) hide show
  1. package/dist/{agentCommands-DWshkZ3Z.mjs → agentCommands-CKd1jXfX.mjs} +5 -5
  2. package/dist/{auth-rJ0N4qyZ.mjs → auth-Ctsyrv9N.mjs} +2 -2
  3. package/dist/cli-DtMJLEig.mjs +2460 -0
  4. package/dist/cli.mjs +4 -2439
  5. package/dist/{commands-DfQA4ZX7.mjs → commands-BI7IDVIE.mjs} +5 -3
  6. package/dist/{commands-aGQUafx6.mjs → commands-BSBxY9iQ.mjs} +2 -2
  7. package/dist/{commands-f9G9DwC4.mjs → commands-BUhsW3bf.mjs} +2 -2
  8. package/dist/{commands-B_N0vONs.mjs → commands-Bx4WYv3I.mjs} +6 -6
  9. package/dist/{commands-BrjfwC7G.mjs → commands-CMZS3jBt.mjs} +2 -2
  10. package/dist/{commands-Dni5WrqV.mjs → commands-C_74tXoO.mjs} +1 -1
  11. package/dist/{commands-BsQqZG9I.mjs → commands-De9crCdY.mjs} +2 -2
  12. package/dist/{fleet-Bjg6QRMP.mjs → fleet-D3ycNACq.mjs} +1 -1
  13. package/dist/{frpc-YKxUZoMA.mjs → frpc-Cwn_s0Fs.mjs} +2 -2
  14. package/dist/{headlessCli-DfKlYX0C.mjs → headlessCli-nWnqozcZ.mjs} +3 -3
  15. package/dist/index.mjs +2 -2
  16. package/dist/package-BHHMByEa.mjs +64 -0
  17. package/dist/{rpc-DrA4F31T.mjs → rpc-BnHMvDxQ.mjs} +2 -2
  18. package/dist/{rpc-B4zSdme1.mjs → rpc-DwlZTOtK.mjs} +2 -2
  19. package/dist/{run-btCn5o6B.mjs → run-DW-5GWjY.mjs} +1 -1
  20. package/dist/{run-DAOLRpvC.mjs → run-b-b_szy6.mjs} +797 -342
  21. package/dist/{scheduler-DfbBMHWm.mjs → scheduler-BxijQDI8.mjs} +2 -2
  22. package/dist/{serveCommands-YmniTKX9.mjs → serveCommands-CL6B6kEf.mjs} +5 -5
  23. package/dist/{serveManager-DVDWrx2q.mjs → serveManager-Dy1ZZNaY.mjs} +3 -3
  24. package/dist/{sideband-DPLjT788.mjs → sideband-DywLL2xs.mjs} +2 -2
  25. package/package.json +2 -2
  26. package/dist/package-Cl0NCpCL.mjs +0 -64
@@ -0,0 +1,2460 @@
1
+ import { a0 as clearStopMarker, a1 as stopMarkerExists, s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-b-b_szy6.mjs';
2
+ import { ensureSupervisorViaServiceManager, LAUNCHD_LABEL } from './serviceManager-hlOVxkhW.mjs';
3
+
4
+ function flushAndExit(code) {
5
+ return new Promise((resolve) => {
6
+ const streams = [process.stdout, process.stderr];
7
+ let remaining = streams.length;
8
+ const tryExit = () => {
9
+ if (--remaining <= 0) {
10
+ process.exit(code);
11
+ resolve(void 0);
12
+ }
13
+ };
14
+ for (const s of streams) {
15
+ if (s.writableLength) {
16
+ s.write("", () => tryExit());
17
+ } else {
18
+ tryExit();
19
+ }
20
+ }
21
+ });
22
+ }
23
+
24
+ const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
25
+ if (nodeMajor < 22) {
26
+ console.error(`Error: svamp requires Node.js >= 22 (you have ${process.version}).`);
27
+ console.error(" Node 22+ includes native WebSocket support needed by hypha-rpc.");
28
+ console.error(" Upgrade with: nvm install 22 && nvm alias default 22");
29
+ process.exit(1);
30
+ }
31
+ const args = process.argv.slice(2);
32
+ const subcommand = args[0];
33
+ let daemonSubcommand = args[1];
34
+ async function main() {
35
+ try {
36
+ const { getLoadedConfig } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.as; });
37
+ getLoadedConfig();
38
+ } catch {
39
+ }
40
+ if (subcommand === "login") {
41
+ await loginToHypha();
42
+ } else if (subcommand === "logout") {
43
+ await logoutFromHypha();
44
+ } else if (subcommand === "daemon") {
45
+ if (daemonSubcommand === "restart") {
46
+ try {
47
+ await applyClaudeAuthFlags(args);
48
+ await applyDaemonShareFlag(args);
49
+ } catch (err) {
50
+ console.error(`svamp daemon restart: ${err.message || err}`);
51
+ process.exit(1);
52
+ }
53
+ const whenIdle = args.includes("--when-idle");
54
+ const force = args.includes("--force");
55
+ const mod = await import('./run-b-b_szy6.mjs').then(function (n) { return n.au; });
56
+ if (whenIdle && !force) {
57
+ if (mod.isDaemonAlive()) {
58
+ mod.writePendingRestart({ reason: "svamp daemon restart --when-idle" });
59
+ console.log("\u2713 Scheduled a graceful restart \u2014 the daemon will restart when no session is actively thinking (max wait 1h).");
60
+ process.exit(0);
61
+ }
62
+ console.log("Daemon not running \u2014 restarting immediately.");
63
+ }
64
+ await mod.restartDaemon();
65
+ process.exit(0);
66
+ }
67
+ if (daemonSubcommand === "start") {
68
+ try {
69
+ await applyClaudeAuthFlags(args);
70
+ await applyDaemonShareFlag(args);
71
+ } catch (err) {
72
+ console.error(`svamp daemon start: ${err.message || err}`);
73
+ process.exit(1);
74
+ }
75
+ clearStopMarker();
76
+ const { spawn } = await import('child_process');
77
+ const { existsSync, mkdirSync, openSync, closeSync, statSync, readFileSync } = await import('fs');
78
+ const { join } = await import('path');
79
+ const os = await import('os');
80
+ const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
81
+ const logsDir = join(svampHome, "logs");
82
+ mkdirSync(logsDir, { recursive: true });
83
+ const supervisorPidFile = join(svampHome, "supervisor.pid");
84
+ let supervisorAlive = false;
85
+ try {
86
+ if (existsSync(supervisorPidFile)) {
87
+ const pid = parseInt(readFileSync(supervisorPidFile, "utf-8").trim(), 10);
88
+ if (pid && !isNaN(pid)) {
89
+ try {
90
+ process.kill(pid, 0);
91
+ supervisorAlive = true;
92
+ } catch {
93
+ }
94
+ }
95
+ }
96
+ } catch {
97
+ }
98
+ if (supervisorAlive) {
99
+ console.log("Daemon already running");
100
+ process.exit(0);
101
+ }
102
+ const startedViaService = await ensureSupervisorViaServiceManager();
103
+ const bootLogPath = join(logsDir, "daemon-boot.log");
104
+ if (!startedViaService) {
105
+ const bootFd = openSync(bootLogPath, "a");
106
+ const extraArgs = [];
107
+ if (args.includes("--no-auto-continue")) extraArgs.push("--no-auto-continue");
108
+ const child = spawn(process.execPath, [
109
+ "--no-warnings",
110
+ "--no-deprecation",
111
+ ...process.argv.slice(1, 2),
112
+ // the script path
113
+ "daemon",
114
+ "start-supervised",
115
+ ...extraArgs
116
+ ], {
117
+ detached: true,
118
+ stdio: ["ignore", bootFd, bootFd],
119
+ env: process.env
120
+ });
121
+ try {
122
+ closeSync(bootFd);
123
+ } catch {
124
+ }
125
+ child.unref();
126
+ }
127
+ const stateFile = join(svampHome, "daemon.state.json");
128
+ let beforeMtimeMs = -1;
129
+ try {
130
+ beforeMtimeMs = statSync(stateFile).mtimeMs;
131
+ } catch {
132
+ }
133
+ let started = false;
134
+ for (let i = 0; i < 600; i++) {
135
+ await new Promise((r) => setTimeout(r, 100));
136
+ try {
137
+ const st = statSync(stateFile);
138
+ if (st.mtimeMs > beforeMtimeMs) {
139
+ started = true;
140
+ break;
141
+ }
142
+ } catch {
143
+ }
144
+ }
145
+ if (started) {
146
+ console.log("Daemon started successfully");
147
+ } else {
148
+ let bootHint = "";
149
+ try {
150
+ const st = statSync(bootLogPath);
151
+ if (st.size > 0) bootHint = `
152
+ Check boot log for errors: ${bootLogPath}`;
153
+ } catch {
154
+ }
155
+ console.error(`Failed to start daemon (timeout waiting for state file)${bootHint}`);
156
+ process.exit(1);
157
+ }
158
+ process.exit(0);
159
+ } else if (daemonSubcommand === "start-supervised") {
160
+ const { spawn: spawnChild } = await import('child_process');
161
+ const { appendFileSync, mkdirSync } = await import('fs');
162
+ const { join: pathJoin } = await import('path');
163
+ const osModule = await import('os');
164
+ const svampHome = process.env.SVAMP_HOME || pathJoin(osModule.homedir(), ".svamp");
165
+ mkdirSync(svampHome, { recursive: true });
166
+ const logsDir = pathJoin(svampHome, "logs");
167
+ mkdirSync(logsDir, { recursive: true });
168
+ const logFile = pathJoin(logsDir, "daemon-supervised.log");
169
+ const log = (msg) => {
170
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [supervisor] ${msg}
171
+ `;
172
+ try {
173
+ appendFileSync(logFile, line);
174
+ } catch {
175
+ }
176
+ };
177
+ const {
178
+ acquireSupervisorLockWithRetry,
179
+ releaseSupervisorLock,
180
+ findOrphanedSyncPids,
181
+ killOrphanedSyncs
182
+ } = await import('./supervisorLock-DmfzJx7B.mjs');
183
+ const supervisorPidFile = pathJoin(svampHome, "supervisor.pid");
184
+ const lock = acquireSupervisorLockWithRetry(supervisorPidFile, process.pid);
185
+ if (!lock.acquired) {
186
+ log(`Another supervisor is already running (PID ${lock.heldBy}) \u2014 exiting`);
187
+ console.error(`svamp daemon: another supervisor is already running (PID ${lock.heldBy}).`);
188
+ process.exit(0);
189
+ }
190
+ if (process.env.SVAMP_SKIP_ORPHAN_SCAN !== "1") {
191
+ try {
192
+ const orphans = findOrphanedSyncPids(process.pid);
193
+ if (orphans.length > 0) {
194
+ await killOrphanedSyncs(orphans, { log, gracePeriodMs: 3e3 });
195
+ }
196
+ } catch (err) {
197
+ log(`Orphan scan failed (non-fatal): ${err?.message || err}`);
198
+ }
199
+ }
200
+ clearStopMarker();
201
+ const extraSyncArgs = [];
202
+ if (args.includes("--no-auto-continue")) extraSyncArgs.push("--no-auto-continue");
203
+ const BASE_DELAY_MS = 2e3;
204
+ const MAX_DELAY_MS = 3e5;
205
+ const BACKOFF_RESET_UPTIME_MS = 6e4;
206
+ let consecutiveRapidCrashes = 0;
207
+ let currentChild = null;
208
+ let stopping = false;
209
+ let restarting = false;
210
+ const onSignal = (sig) => {
211
+ stopping = true;
212
+ if (currentChild && !currentChild.killed) {
213
+ currentChild.kill(sig);
214
+ }
215
+ };
216
+ process.on("SIGTERM", () => onSignal("SIGTERM"));
217
+ process.on("SIGINT", () => onSignal("SIGINT"));
218
+ process.on("SIGUSR1", () => onSignal("SIGUSR1"));
219
+ process.on("SIGUSR2", () => {
220
+ restarting = true;
221
+ log("Received SIGUSR2 \u2014 graceful restart requested");
222
+ if (currentChild && !currentChild.killed) {
223
+ currentChild.kill("SIGTERM");
224
+ }
225
+ });
226
+ log("Supervisor started");
227
+ while (!stopping) {
228
+ const startTime = Date.now();
229
+ const exitCode = await new Promise((resolve) => {
230
+ const child = spawnChild(process.execPath, [
231
+ "--no-warnings",
232
+ "--no-deprecation",
233
+ ...process.argv.slice(1, 2),
234
+ "daemon",
235
+ "start-sync",
236
+ ...extraSyncArgs
237
+ ], {
238
+ stdio: ["ignore", "inherit", "inherit"],
239
+ env: { ...process.env, SVAMP_SUPERVISED: "1" },
240
+ // CRITICAL for launchd KeepAlive: give the child its OWN
241
+ // process group/session (detached). Otherwise, if the
242
+ // supervisor dies abnormally (SIGKILL/OOM) while the child
243
+ // is alive, the child — still in the supervisor's process
244
+ // group — keeps launchd's job "active", so KeepAlive never
245
+ // relaunches the supervisor and the daemon stays dead. The
246
+ // orphaned child is then reaped by the new supervisor's
247
+ // orphan scan and its own parent-death watchdog.
248
+ detached: true
249
+ });
250
+ currentChild = child;
251
+ child.on("exit", (code) => resolve(code));
252
+ child.on("error", (err) => {
253
+ log(`Failed to spawn daemon: ${err.message}`);
254
+ resolve(1);
255
+ });
256
+ });
257
+ currentChild = null;
258
+ const uptime = Date.now() - startTime;
259
+ if (stopping) {
260
+ log("Supervisor received stop signal, exiting");
261
+ break;
262
+ }
263
+ if (restarting) {
264
+ restarting = false;
265
+ consecutiveRapidCrashes = 0;
266
+ log("Graceful restart: respawning daemon with new binary from disk");
267
+ continue;
268
+ }
269
+ if (stopMarkerExists()) {
270
+ log(`Daemon exited (code ${exitCode}) and stop marker present \u2014 intentional stop, not restarting`);
271
+ break;
272
+ }
273
+ if (uptime > BACKOFF_RESET_UPTIME_MS) {
274
+ consecutiveRapidCrashes = 0;
275
+ } else {
276
+ consecutiveRapidCrashes++;
277
+ }
278
+ const backoffExp = Math.min(consecutiveRapidCrashes, 10);
279
+ let delay = Math.min(BASE_DELAY_MS * Math.pow(2, backoffExp), MAX_DELAY_MS);
280
+ delay = Math.max(BASE_DELAY_MS, Math.round(delay + (Math.random() * 0.2 - 0.1) * delay));
281
+ log(`Daemon exited with code ${exitCode} (uptime=${Math.round(uptime / 1e3)}s). Restarting in ${Math.round(delay / 1e3)}s...`);
282
+ await new Promise((resolve) => {
283
+ const timer = setTimeout(resolve, delay);
284
+ const checkStop = setInterval(() => {
285
+ if (stopping) {
286
+ clearTimeout(timer);
287
+ clearInterval(checkStop);
288
+ resolve();
289
+ }
290
+ }, 500);
291
+ setTimeout(() => clearInterval(checkStop), delay + 100);
292
+ });
293
+ }
294
+ releaseSupervisorLock(supervisorPidFile, process.pid);
295
+ process.exit(0);
296
+ } else if (daemonSubcommand === "start-sync") {
297
+ if (process.env.SVAMP_TEST_SYNC_EXIT !== void 0) {
298
+ const code = Number(process.env.SVAMP_TEST_SYNC_EXIT) || 0;
299
+ const lifeMs = Number(process.env.SVAMP_TEST_SYNC_LIFE_MS) || 0;
300
+ await new Promise((r) => setTimeout(r, lifeMs));
301
+ process.exit(code);
302
+ }
303
+ const noAutoContinue = args.includes("--no-auto-continue");
304
+ await startDaemon({ noAutoContinue });
305
+ process.exit(0);
306
+ } else if (daemonSubcommand === "stop") {
307
+ const cleanup = args.includes("--cleanup");
308
+ await stopDaemon({ cleanup });
309
+ process.exit(0);
310
+ } else if (daemonSubcommand === "status") {
311
+ daemonStatus();
312
+ process.exit(0);
313
+ } else if (daemonSubcommand === "install") {
314
+ await installDaemonService();
315
+ process.exit(0);
316
+ } else if (daemonSubcommand === "uninstall") {
317
+ await uninstallDaemonService();
318
+ process.exit(0);
319
+ } else if (daemonSubcommand === "auth") {
320
+ await handleDaemonAuthCommand(args.slice(2));
321
+ process.exit(0);
322
+ } else if (daemonSubcommand === "codex-auth") {
323
+ await handleDaemonCodexAuthCommand(args.slice(2));
324
+ process.exit(0);
325
+ } else {
326
+ printDaemonHelp();
327
+ }
328
+ } else if (subcommand === "agent") {
329
+ await handleAgentCommand();
330
+ } else if (subcommand === "session") {
331
+ await handleSessionCommand();
332
+ } else if (subcommand === "machine") {
333
+ const { isSandboxed } = await import('./sandboxDetect-DNTcbgWD.mjs');
334
+ if (isSandboxed()) {
335
+ console.error("svamp machine: Machine commands are not available in sandboxed sessions.");
336
+ process.exit(1);
337
+ }
338
+ await handleMachineCommand();
339
+ } else if (subcommand === "fleet") {
340
+ const { isSandboxed } = await import('./sandboxDetect-DNTcbgWD.mjs');
341
+ if (isSandboxed()) {
342
+ console.error("svamp fleet: Fleet commands are not available in sandboxed sessions.");
343
+ process.exit(1);
344
+ }
345
+ await handleFleetCommand();
346
+ } else if (subcommand === "skills") {
347
+ const { isSandboxed } = await import('./sandboxDetect-DNTcbgWD.mjs');
348
+ if (isSandboxed()) {
349
+ console.error("svamp skills: Skills commands are not available in sandboxed sessions.");
350
+ process.exit(1);
351
+ }
352
+ await handleSkillsCommand();
353
+ } else if (subcommand === "service" || subcommand === "svc") {
354
+ const { isSandboxed } = await import('./sandboxDetect-DNTcbgWD.mjs');
355
+ if (isSandboxed()) {
356
+ console.error("svamp service: Service commands are not available in sandboxed sessions.");
357
+ process.exit(1);
358
+ }
359
+ const { handleServiceCommand } = await import('./commands-Bx4WYv3I.mjs');
360
+ await handleServiceCommand();
361
+ } else if (subcommand === "serve") {
362
+ const { isSandboxed: isSandboxedServe } = await import('./sandboxDetect-DNTcbgWD.mjs');
363
+ if (isSandboxedServe()) {
364
+ console.error("svamp serve: Serve commands are not available in sandboxed sessions.");
365
+ process.exit(1);
366
+ }
367
+ const { handleServeCommand } = await import('./serveCommands-CL6B6kEf.mjs');
368
+ await handleServeCommand();
369
+ process.exit(0);
370
+ } else if (subcommand === "process" || subcommand === "proc") {
371
+ const { isSandboxed: isSandboxedProc } = await import('./sandboxDetect-DNTcbgWD.mjs');
372
+ if (isSandboxedProc()) {
373
+ console.error("svamp process: Process commands are not available in sandboxed sessions.");
374
+ process.exit(1);
375
+ }
376
+ const { processCommand } = await import('./commands-CMZS3jBt.mjs');
377
+ let machineId;
378
+ const processArgs = args.slice(1);
379
+ const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
380
+ if (mIdx !== -1 && mIdx + 1 < processArgs.length) {
381
+ machineId = processArgs[mIdx + 1];
382
+ }
383
+ await processCommand(processArgs.filter((_a, i) => {
384
+ if (processArgs[i] === "--machine" || processArgs[i] === "-m") return false;
385
+ if (i > 0 && (processArgs[i - 1] === "--machine" || processArgs[i - 1] === "-m")) return false;
386
+ return true;
387
+ }), machineId);
388
+ process.exit(0);
389
+ } else if (subcommand === "issue" || subcommand === "issues") {
390
+ const { issueCommand } = await import('./commands-BI7IDVIE.mjs');
391
+ await issueCommand(args.slice(1));
392
+ await flushAndExit(0);
393
+ } else if (subcommand === "workflow" || subcommand === "workflows") {
394
+ const { workflowCommand } = await import('./commands-De9crCdY.mjs');
395
+ await workflowCommand(args.slice(1));
396
+ await flushAndExit(0);
397
+ } else if (subcommand === "wise-agent" || subcommand === "wise") {
398
+ await handleWiseAgentCommand(args.slice(1));
399
+ process.exit(0);
400
+ } else if (subcommand === "feature" || subcommand === "crew") {
401
+ const { crewCommand } = await import('./commands-BSBxY9iQ.mjs');
402
+ await crewCommand(args.slice(1));
403
+ process.exit(0);
404
+ } else if (subcommand === "--help" || subcommand === "-h") {
405
+ printHelp();
406
+ } else if (!subcommand || subcommand === "start") {
407
+ await handleInteractiveCommand();
408
+ } else if (subcommand === "--version" || subcommand === "-v") {
409
+ const pkg = await import('./package-BHHMByEa.mjs').catch(() => ({ default: { version: "unknown" } }));
410
+ console.log(`svamp version: ${pkg.default.version}`);
411
+ } else {
412
+ console.error(`Unknown command: ${subcommand}`);
413
+ printHelp();
414
+ process.exit(1);
415
+ }
416
+ }
417
+ async function handleInteractiveCommand() {
418
+ const { runInteractive } = await import('./run-DW-5GWjY.mjs');
419
+ const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
420
+ let directory = process.cwd();
421
+ let resumeSessionId;
422
+ let continueSession = false;
423
+ let permissionMode;
424
+ const claudeArgs = [];
425
+ let pastSeparator = false;
426
+ for (let i = 0; i < interactiveArgs.length; i++) {
427
+ if (pastSeparator) {
428
+ claudeArgs.push(interactiveArgs[i]);
429
+ continue;
430
+ }
431
+ if (interactiveArgs[i] === "--") {
432
+ pastSeparator = true;
433
+ continue;
434
+ }
435
+ if ((interactiveArgs[i] === "-d" || interactiveArgs[i] === "--directory") && i + 1 < interactiveArgs.length) {
436
+ directory = interactiveArgs[++i];
437
+ } else if ((interactiveArgs[i] === "--resume" || interactiveArgs[i] === "-r") && i + 1 < interactiveArgs.length) {
438
+ resumeSessionId = interactiveArgs[++i];
439
+ } else if (interactiveArgs[i] === "--continue" || interactiveArgs[i] === "-c") {
440
+ continueSession = true;
441
+ } else if ((interactiveArgs[i] === "--permission-mode" || interactiveArgs[i] === "-p") && i + 1 < interactiveArgs.length) {
442
+ permissionMode = interactiveArgs[++i];
443
+ } else if (interactiveArgs[i] === "--help" || interactiveArgs[i] === "-h") {
444
+ printInteractiveHelp();
445
+ return;
446
+ }
447
+ }
448
+ await runInteractive({
449
+ directory,
450
+ resumeSessionId,
451
+ continueSession,
452
+ permissionMode,
453
+ claudeArgs: claudeArgs.length > 0 ? claudeArgs : void 0
454
+ });
455
+ }
456
+ async function handleAgentCommand() {
457
+ const agentArgs = args.slice(1);
458
+ if (agentArgs.length === 0 || agentArgs[0] === "--help" || agentArgs[0] === "-h") {
459
+ printAgentHelp();
460
+ return;
461
+ }
462
+ if (agentArgs[0] === "list") {
463
+ const { KNOWN_ACP_AGENTS, KNOWN_CODEX_AGENTS: KNOWN_CODEX_AGENTS2 } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.ap; });
464
+ console.log("Known agents:");
465
+ for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
466
+ console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
467
+ }
468
+ for (const [name, config2] of Object.entries(KNOWN_CODEX_AGENTS2)) {
469
+ console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (MCP)`);
470
+ }
471
+ console.log('\nUse "svamp agent <name>" to start an interactive session.');
472
+ console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
473
+ return;
474
+ }
475
+ const { resolveAcpAgentConfig, KNOWN_CODEX_AGENTS } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.ap; });
476
+ let cwd = process.cwd();
477
+ const filteredArgs = [];
478
+ for (let i = 0; i < agentArgs.length; i++) {
479
+ if ((agentArgs[i] === "-d" || agentArgs[i] === "--directory") && i + 1 < agentArgs.length) {
480
+ cwd = agentArgs[i + 1];
481
+ i++;
482
+ } else {
483
+ filteredArgs.push(agentArgs[i]);
484
+ }
485
+ }
486
+ let config;
487
+ try {
488
+ config = resolveAcpAgentConfig(filteredArgs);
489
+ } catch (err) {
490
+ console.error(err.message);
491
+ process.exit(1);
492
+ }
493
+ const logFn = (...logArgs) => {
494
+ if (process.env.DEBUG) console.error("[debug]", ...logArgs);
495
+ };
496
+ console.log(`Starting ${config.agentName} agent in ${cwd}...`);
497
+ let backend;
498
+ if (KNOWN_CODEX_AGENTS[config.agentName]) {
499
+ const { CodexAppServerBackend } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.aq; });
500
+ backend = new CodexAppServerBackend({ cwd, log: logFn });
501
+ } else {
502
+ const { AcpBackend } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.ao; });
503
+ const { GeminiTransport } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.ar; });
504
+ const { DefaultTransport } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.an; });
505
+ const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
506
+ backend = new AcpBackend({
507
+ agentName: config.agentName,
508
+ cwd,
509
+ command: config.command,
510
+ args: config.args,
511
+ transportHandler,
512
+ log: logFn
513
+ });
514
+ }
515
+ let currentText = "";
516
+ backend.onMessage((msg) => {
517
+ switch (msg.type) {
518
+ case "model-output":
519
+ if (msg.textDelta) {
520
+ process.stdout.write(msg.textDelta);
521
+ currentText += msg.textDelta;
522
+ } else if (msg.fullText) {
523
+ process.stdout.write(msg.fullText);
524
+ }
525
+ break;
526
+ case "status":
527
+ if (msg.status === "idle") {
528
+ if (currentText && !currentText.endsWith("\n")) {
529
+ process.stdout.write("\n");
530
+ }
531
+ currentText = "";
532
+ process.stdout.write("\n> ");
533
+ } else if (msg.status === "error") {
534
+ console.error(`
535
+ Error: ${msg.detail}`);
536
+ } else if (msg.status === "stopped") {
537
+ console.log(`
538
+ Agent stopped: ${msg.detail || ""}`);
539
+ }
540
+ break;
541
+ case "tool-call":
542
+ console.log(`
543
+ [tool] ${msg.toolName}(${JSON.stringify(msg.args).substring(0, 100)})`);
544
+ break;
545
+ case "tool-result": {
546
+ const resultStr = typeof msg.result === "string" ? msg.result : JSON.stringify(msg.result);
547
+ if (resultStr.length > 200) {
548
+ console.log(`[tool result] ${resultStr.substring(0, 200)}...`);
549
+ }
550
+ break;
551
+ }
552
+ case "event":
553
+ if (msg.name === "thinking") {
554
+ const payload = msg.payload;
555
+ if (payload?.text) {
556
+ process.stdout.write(`\x1B[90m${payload.text}\x1B[0m`);
557
+ }
558
+ }
559
+ break;
560
+ }
561
+ });
562
+ try {
563
+ const result = await backend.startSession();
564
+ console.log(`Session started: ${result.sessionId}`);
565
+ process.stdout.write("> ");
566
+ } catch (err) {
567
+ const errMsg = err?.message || (typeof err === "object" ? JSON.stringify(err) : String(err));
568
+ console.error(`Failed to start ${config.agentName}: ${errMsg}`);
569
+ process.exit(1);
570
+ }
571
+ const readline = await import('readline');
572
+ const rl = readline.createInterface({
573
+ input: process.stdin,
574
+ output: process.stdout,
575
+ terminal: false
576
+ });
577
+ rl.on("line", async (line) => {
578
+ const trimmed = line.trim();
579
+ if (!trimmed) return;
580
+ if (trimmed === "/quit" || trimmed === "/exit") {
581
+ console.log("Shutting down...");
582
+ await backend.dispose();
583
+ process.exit(0);
584
+ }
585
+ if (trimmed === "/cancel") {
586
+ await backend.cancel("");
587
+ return;
588
+ }
589
+ try {
590
+ await backend.sendPrompt("", trimmed);
591
+ } catch (err) {
592
+ console.error(`Error: ${err.message}`);
593
+ }
594
+ });
595
+ rl.on("close", async () => {
596
+ await backend.dispose();
597
+ process.exit(0);
598
+ });
599
+ process.on("SIGINT", async () => {
600
+ console.log("\nShutting down...");
601
+ await backend.dispose();
602
+ process.exit(0);
603
+ });
604
+ }
605
+ async function handleSessionCommand() {
606
+ const rawSessionArgs = args.slice(1);
607
+ let targetMachineId;
608
+ const sessionArgs = [];
609
+ for (let i = 0; i < rawSessionArgs.length; i++) {
610
+ if ((rawSessionArgs[i] === "--machine" || rawSessionArgs[i] === "-m") && i + 1 < rawSessionArgs.length) {
611
+ targetMachineId = rawSessionArgs[i + 1];
612
+ i++;
613
+ } else {
614
+ sessionArgs.push(rawSessionArgs[i]);
615
+ }
616
+ }
617
+ const sessionSubcommand = sessionArgs[0];
618
+ if (!sessionSubcommand || sessionSubcommand === "--help" || sessionSubcommand === "-h") {
619
+ printSessionHelp();
620
+ return;
621
+ }
622
+ const SANDBOX_SAFE_SESSION_CMDS = /* @__PURE__ */ new Set(["set-title", "set-link", "link", "set-project-description", "set-project"]);
623
+ if (!SANDBOX_SAFE_SESSION_CMDS.has(sessionSubcommand)) {
624
+ const { isSandboxed } = await import('./sandboxDetect-DNTcbgWD.mjs');
625
+ if (isSandboxed()) {
626
+ console.error(`svamp session ${sessionSubcommand}: This command is not available in sandboxed sessions.`);
627
+ console.error("Available commands: set-title, set-link");
628
+ process.exit(1);
629
+ }
630
+ }
631
+ const { sessionList, sessionWhoami, sessionSpawn, sessionArchive, sessionResume, sessionDelete, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionLoopStart, sessionLoopCancel, sessionLoopStatus, sessionLoopExtend, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-C_74tXoO.mjs');
632
+ const parseFlagStr = (flag, shortFlag) => {
633
+ for (let i = 1; i < sessionArgs.length; i++) {
634
+ if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
635
+ return sessionArgs[i + 1];
636
+ }
637
+ }
638
+ return void 0;
639
+ };
640
+ const parseFlagInt = (flag, shortFlag) => {
641
+ const v = parseFlagStr(flag, shortFlag);
642
+ if (v === void 0) return void 0;
643
+ const n = parseInt(v, 10);
644
+ return isNaN(n) ? void 0 : n;
645
+ };
646
+ const hasFlag = (flag) => sessionArgs.includes(flag);
647
+ if (sessionSubcommand === "machines" || sessionSubcommand === "machine") {
648
+ await sessionMachines();
649
+ } else if (sessionSubcommand === "list" || sessionSubcommand === "ls") {
650
+ await sessionList(targetMachineId, {
651
+ active: hasFlag("--active"),
652
+ json: hasFlag("--json")
653
+ });
654
+ } else if (sessionSubcommand === "whoami" || sessionSubcommand === "env") {
655
+ await sessionWhoami({ json: hasFlag("--json"), all: hasFlag("--all") });
656
+ } else if (sessionSubcommand === "spawn") {
657
+ const agent = sessionArgs[1] || "claude";
658
+ let dir = process.cwd();
659
+ for (let i = 1; i < sessionArgs.length; i++) {
660
+ if ((sessionArgs[i] === "-d" || sessionArgs[i] === "--directory") && i + 1 < sessionArgs.length) {
661
+ dir = sessionArgs[i + 1];
662
+ i++;
663
+ }
664
+ }
665
+ const message = parseFlagStr("--message");
666
+ const wait = hasFlag("--wait");
667
+ const worktree = hasFlag("--worktree");
668
+ const isolate = hasFlag("--isolate");
669
+ let permissionMode = parseFlagStr("--permission-mode") || parseFlagStr("-p");
670
+ if (!permissionMode && hasFlag("--require-approval")) {
671
+ permissionMode = "default";
672
+ }
673
+ if (!permissionMode) {
674
+ permissionMode = "bypassPermissions";
675
+ }
676
+ const securityContextPath = parseFlagStr("--security-context");
677
+ const denyNetwork = hasFlag("--deny-network");
678
+ const parentSessionId = parseFlagStr("--parent");
679
+ const tagsFlag = parseFlagStr("--tags");
680
+ const share = [];
681
+ const denyRead = [];
682
+ const allowWrite = [];
683
+ const allowDomain = [];
684
+ const tags = [];
685
+ if (tagsFlag) {
686
+ tags.push(...tagsFlag.split(",").map((t) => t.trim()).filter(Boolean));
687
+ }
688
+ for (let i = 1; i < sessionArgs.length; i++) {
689
+ if (sessionArgs[i] === "--share" && i + 1 < sessionArgs.length) {
690
+ share.push(sessionArgs[++i]);
691
+ } else if (sessionArgs[i] === "--deny-read" && i + 1 < sessionArgs.length) {
692
+ denyRead.push(sessionArgs[++i]);
693
+ } else if (sessionArgs[i] === "--allow-write" && i + 1 < sessionArgs.length) {
694
+ allowWrite.push(sessionArgs[++i]);
695
+ } else if (sessionArgs[i] === "--allow-domain" && i + 1 < sessionArgs.length) {
696
+ allowDomain.push(sessionArgs[++i]);
697
+ }
698
+ }
699
+ const { parseShareArg } = await import('./commands-C_74tXoO.mjs');
700
+ const shareEntries = share.map((s) => parseShareArg(s));
701
+ await sessionSpawn(agent, dir, targetMachineId, {
702
+ message,
703
+ wait,
704
+ worktree,
705
+ isolate,
706
+ permissionMode,
707
+ model: parseFlagStr("--model"),
708
+ securityContextPath,
709
+ share: shareEntries.length > 0 ? shareEntries : void 0,
710
+ denyRead: denyRead.length > 0 ? denyRead : void 0,
711
+ allowWrite: allowWrite.length > 0 ? allowWrite : void 0,
712
+ denyNetwork,
713
+ allowDomain: allowDomain.length > 0 ? allowDomain : void 0,
714
+ tags: tags.length > 0 ? tags : void 0,
715
+ parentSessionId
716
+ });
717
+ } else if (sessionSubcommand === "archive") {
718
+ if (!sessionArgs[1]) {
719
+ console.error("Usage: svamp session archive <session-id>");
720
+ process.exit(1);
721
+ }
722
+ await sessionArchive(sessionArgs[1], targetMachineId);
723
+ } else if (sessionSubcommand === "resume") {
724
+ if (!sessionArgs[1]) {
725
+ console.error("Usage: svamp session resume <session-id>");
726
+ process.exit(1);
727
+ }
728
+ await sessionResume(sessionArgs[1], targetMachineId);
729
+ } else if (sessionSubcommand === "delete" || sessionSubcommand === "rm") {
730
+ if (!sessionArgs[1]) {
731
+ console.error("Usage: svamp session delete <session-id>");
732
+ process.exit(1);
733
+ }
734
+ await sessionDelete(sessionArgs[1], targetMachineId);
735
+ } else if (sessionSubcommand === "info") {
736
+ if (!sessionArgs[1]) {
737
+ console.error("Usage: svamp session info <session-id>");
738
+ process.exit(1);
739
+ }
740
+ await sessionInfo(sessionArgs[1], targetMachineId, {
741
+ json: hasFlag("--json")
742
+ });
743
+ } else if (sessionSubcommand === "messages" || sessionSubcommand === "msgs") {
744
+ if (!sessionArgs[1]) {
745
+ console.error("Usage: svamp session messages <session-id> [--last N] [--json] [--raw] [--after N] [--limit N]");
746
+ process.exit(1);
747
+ }
748
+ await sessionMessages(sessionArgs[1], targetMachineId, {
749
+ last: parseFlagInt("--last"),
750
+ json: hasFlag("--json"),
751
+ raw: hasFlag("--raw"),
752
+ after: parseFlagInt("--after"),
753
+ limit: parseFlagInt("--limit")
754
+ });
755
+ } else if (sessionSubcommand === "attach") {
756
+ if (!sessionArgs[1]) {
757
+ console.error("Usage: svamp session attach <session-id>");
758
+ process.exit(1);
759
+ }
760
+ await sessionAttach(sessionArgs[1], targetMachineId);
761
+ } else if (sessionSubcommand === "send") {
762
+ if (!sessionArgs[1] || !sessionArgs[2]) {
763
+ console.error('Usage: svamp session send <session-id> <message> [--model <id>] [--plain] [--subject "..."] [--when-idle] [--urgency urgent|normal] [--wait] [--response] [--btw] [--require-approval] [--timeout N] [--json]');
764
+ console.error(" --when-idle Deferred delivery: park the message and let the recipient handle it on its next idle turn (default is urgent \u2014 wakes immediately).");
765
+ console.error(" --model <id> Switch the session model (e.g. claude-opus-4-7, claude-sonnet-4-6) before delivering the message \u2014 forces a respawn.");
766
+ process.exit(1);
767
+ }
768
+ await sessionSend(sessionArgs[1], sessionArgs[2], targetMachineId, {
769
+ wait: hasFlag("--wait"),
770
+ response: hasFlag("--response"),
771
+ btw: hasFlag("--btw"),
772
+ requireApproval: hasFlag("--require-approval"),
773
+ timeout: parseFlagInt("--timeout"),
774
+ json: hasFlag("--json"),
775
+ subject: parseFlagStr("--subject"),
776
+ whenIdle: hasFlag("--when-idle"),
777
+ urgency: parseFlagStr("--urgency"),
778
+ model: parseFlagStr("--model"),
779
+ plain: hasFlag("--plain"),
780
+ asIssue: hasFlag("--as-issue"),
781
+ issueTitle: parseFlagStr("--issue-title")
782
+ });
783
+ } else if (sessionSubcommand === "edit-message" || sessionSubcommand === "edit") {
784
+ if (!sessionArgs[1] || !sessionArgs[2] || sessionArgs[3] === void 0) {
785
+ console.error("Usage: svamp session edit-message <session-id> <message-id> <new-text>");
786
+ console.error(" Rewinds history: rewrites the message + drops everything after it, then restarts Claude.");
787
+ process.exit(1);
788
+ }
789
+ const { sessionEditMessage } = await import('./commands-C_74tXoO.mjs');
790
+ await sessionEditMessage(sessionArgs[1], sessionArgs[2], sessionArgs[3], targetMachineId);
791
+ } else if (sessionSubcommand === "refine") {
792
+ if (!sessionArgs[1] || !sessionArgs[2]) {
793
+ console.error("Usage: svamp session refine <session-id> <instruction>");
794
+ console.error(" Asks the agent to revise its latest reply in place (no extra round).");
795
+ process.exit(1);
796
+ }
797
+ const { sessionRefineLastReply } = await import('./commands-C_74tXoO.mjs');
798
+ await sessionRefineLastReply(sessionArgs[1], sessionArgs[2], targetMachineId);
799
+ } else if (sessionSubcommand === "undo-edit" || sessionSubcommand === "undo") {
800
+ if (!sessionArgs[1]) {
801
+ console.error("Usage: svamp session undo-edit <session-id>");
802
+ console.error(" Reverts the most recent edit/refine, restoring the pre-edit history.");
803
+ process.exit(1);
804
+ }
805
+ const { sessionUndoEdit } = await import('./commands-C_74tXoO.mjs');
806
+ await sessionUndoEdit(sessionArgs[1], targetMachineId);
807
+ } else if (sessionSubcommand === "query") {
808
+ const dir = sessionArgs[1];
809
+ const prompt = sessionArgs[2];
810
+ if (!dir || !prompt) {
811
+ console.error("Usage: svamp session query <directory> <prompt> [--timeout N] [--keep] [--json] [--permission-mode MODE] [--tag NAME]");
812
+ console.error(" Spawns a stateless Claude session in <directory>, sends <prompt>, prints the answer, then deletes the session.");
813
+ process.exit(1);
814
+ }
815
+ const { sessionQuery } = await import('./commands-C_74tXoO.mjs');
816
+ await sessionQuery(dir, prompt, targetMachineId, {
817
+ timeout: parseFlagInt("--timeout"),
818
+ json: hasFlag("--json"),
819
+ keep: hasFlag("--keep"),
820
+ permissionMode: parseFlagStr("--permission-mode"),
821
+ tag: parseFlagStr("--tag")
822
+ });
823
+ } else if (sessionSubcommand === "wait") {
824
+ if (!sessionArgs[1]) {
825
+ console.error("Usage: svamp session wait <session-id> [--timeout N] [--json]");
826
+ process.exit(1);
827
+ }
828
+ await sessionWait(sessionArgs[1], targetMachineId, {
829
+ timeout: parseFlagInt("--timeout"),
830
+ json: hasFlag("--json")
831
+ });
832
+ } else if (sessionSubcommand === "share") {
833
+ if (!sessionArgs[1]) {
834
+ console.error("Usage: svamp session share <session-id> --add <email>[:<role>] | --remove <email> | --list | --public <view|interact|off>");
835
+ process.exit(1);
836
+ }
837
+ await sessionShare(sessionArgs[1], targetMachineId, {
838
+ add: parseFlagStr("--add"),
839
+ remove: parseFlagStr("--remove"),
840
+ list: hasFlag("--list"),
841
+ public: parseFlagStr("--public")
842
+ });
843
+ } else if (sessionSubcommand === "approve") {
844
+ if (!sessionArgs[1]) {
845
+ console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
846
+ process.exit(1);
847
+ }
848
+ const { sessionApprove } = await import('./commands-C_74tXoO.mjs');
849
+ const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
850
+ await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
851
+ json: hasFlag("--json")
852
+ });
853
+ } else if (sessionSubcommand === "deny") {
854
+ if (!sessionArgs[1]) {
855
+ console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
856
+ process.exit(1);
857
+ }
858
+ const { sessionDeny } = await import('./commands-C_74tXoO.mjs');
859
+ const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
860
+ await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
861
+ json: hasFlag("--json")
862
+ });
863
+ } else if (sessionSubcommand === "loop" || sessionSubcommand === "loop-start") {
864
+ const id = sessionArgs[1];
865
+ const maybeTask = sessionArgs[2] && !sessionArgs[2].startsWith("-") ? sessionArgs[2] : void 0;
866
+ const until = parseFlagStr("--until") ?? parseFlagStr("--criteria");
867
+ if (!id || !maybeTask && !until) {
868
+ console.error('Usage: svamp session loop <session-id> ["<task>"] --until "<criteria>" [--oracle "cmd"] [--agent] [--parent <id>] [--max N] [--max-tokens-per-hour N] [--evaluator on|off]');
869
+ console.error(" With a task: kicks the agent off and gates it until done.");
870
+ console.error(" With only --until: hot-plugs the gate onto the running session (no kickoff).");
871
+ process.exit(1);
872
+ }
873
+ const evalStr = parseFlagStr("--evaluator");
874
+ await sessionLoopStart(id, maybeTask, targetMachineId, {
875
+ until,
876
+ oracle: parseFlagStr("--oracle"),
877
+ agent: hasFlag("--agent"),
878
+ parent: parseFlagStr("--parent"),
879
+ maxIterations: parseFlagInt("--max") ?? parseFlagInt("--max-iterations") ?? parseFlagInt("--max-rounds"),
880
+ maxTokensPerHour: parseFlagInt("--max-tokens-per-hour"),
881
+ maxRuntimeSec: parseFlagInt("--max-runtime") ?? parseFlagInt("--max-runtime-sec"),
882
+ evaluator: evalStr ? evalStr !== "off" : void 0,
883
+ engine: parseFlagStr("--engine")
884
+ });
885
+ } else if (sessionSubcommand === "loop-cancel") {
886
+ if (!sessionArgs[1]) {
887
+ console.error("Usage: svamp session loop-cancel <session-id>");
888
+ process.exit(1);
889
+ }
890
+ await sessionLoopCancel(sessionArgs[1], targetMachineId);
891
+ } else if (sessionSubcommand === "loop-status") {
892
+ if (!sessionArgs[1]) {
893
+ console.error("Usage: svamp session loop-status <session-id>");
894
+ process.exit(1);
895
+ }
896
+ await sessionLoopStatus(sessionArgs[1], targetMachineId);
897
+ } else if (sessionSubcommand === "loop-extend") {
898
+ const reason = parseFlagStr("--reason");
899
+ if (!sessionArgs[1] || !reason) {
900
+ console.error('Usage: svamp session loop-extend <session-id> --reason "<why more turns are needed>" [--turns N]');
901
+ process.exit(1);
902
+ }
903
+ await sessionLoopExtend(sessionArgs[1], reason, targetMachineId, parseFlagInt("--turns") ?? void 0);
904
+ } else if (sessionSubcommand === "set-title") {
905
+ const title = sessionArgs[1];
906
+ if (!title) {
907
+ console.error("Usage: svamp session set-title <title>");
908
+ process.exit(1);
909
+ }
910
+ const { sessionSetTitle } = await import('./agentCommands-CKd1jXfX.mjs');
911
+ await sessionSetTitle(title);
912
+ } else if (sessionSubcommand === "set-project-description" || sessionSubcommand === "set-project") {
913
+ const desc = sessionArgs.slice(1).filter((a) => !a.startsWith("--")).join(" ");
914
+ if (!desc) {
915
+ console.error("Usage: svamp session set-project-description <text>");
916
+ process.exit(1);
917
+ }
918
+ const { sessionSetProjectDescription } = await import('./agentCommands-CKd1jXfX.mjs');
919
+ await sessionSetProjectDescription(desc);
920
+ } else if (sessionSubcommand === "set-link") {
921
+ const url = sessionArgs[1];
922
+ if (!url) {
923
+ console.error("Usage: svamp session set-link <url> [label]");
924
+ process.exit(1);
925
+ }
926
+ const label = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
927
+ const { sessionSetLink } = await import('./agentCommands-CKd1jXfX.mjs');
928
+ await sessionSetLink(url, label);
929
+ } else if (sessionSubcommand === "link") {
930
+ const op = sessionArgs[1];
931
+ const lm = await import('./agentCommands-CKd1jXfX.mjs');
932
+ if (op === "add") {
933
+ const url = sessionArgs[2];
934
+ if (!url || url.startsWith("--")) {
935
+ console.error('Usage: svamp session link add <url> [--label "Name"] [--icon "\u{1F517}|emoji"]');
936
+ process.exit(1);
937
+ }
938
+ await lm.sessionLinkAdd(url, parseFlagStr("--label"), parseFlagStr("--icon"));
939
+ } else if (op === "list" || op === "ls" || op === void 0) {
940
+ await lm.sessionLinkList();
941
+ } else if (op === "update") {
942
+ const id = sessionArgs[2];
943
+ if (!id || id.startsWith("--")) {
944
+ console.error("Usage: svamp session link update <id> [--url U] [--label L] [--icon I]");
945
+ process.exit(1);
946
+ }
947
+ await lm.sessionLinkUpdate(id, { url: parseFlagStr("--url"), label: parseFlagStr("--label"), icon: parseFlagStr("--icon") });
948
+ } else if (op === "remove" || op === "rm") {
949
+ const id = sessionArgs[2];
950
+ if (!id) {
951
+ console.error("Usage: svamp session link remove <id>");
952
+ process.exit(1);
953
+ }
954
+ await lm.sessionLinkRemove(id);
955
+ } else {
956
+ console.error("Usage: svamp session link <add <url> [--label L] [--icon I] | list | update <id> [...] | remove <id>>");
957
+ process.exit(1);
958
+ }
959
+ } else if (sessionSubcommand === "notify") {
960
+ const message = sessionArgs[1];
961
+ if (!message) {
962
+ console.error("Usage: svamp session notify <message> [--level info|warning|error]");
963
+ process.exit(1);
964
+ }
965
+ const level = parseFlagStr("--level") || "info";
966
+ const { sessionNotify } = await import('./agentCommands-CKd1jXfX.mjs');
967
+ await sessionNotify(message, level);
968
+ } else if (sessionSubcommand === "broadcast") {
969
+ const action = sessionArgs[1];
970
+ if (!action) {
971
+ console.error("Usage: svamp session broadcast <action> [args...]\nActions: open-canvas <url> [label], close-canvas, toast <message>");
972
+ process.exit(1);
973
+ }
974
+ const { sessionBroadcast } = await import('./agentCommands-CKd1jXfX.mjs');
975
+ await sessionBroadcast(action, sessionArgs.slice(2).filter((a) => !a.startsWith("--")));
976
+ } else if (sessionSubcommand === "inbox") {
977
+ const inboxSubcmd = sessionArgs[1];
978
+ const agentSessionId = process.env.SVAMP_SESSION_ID;
979
+ if (inboxSubcmd === "send") {
980
+ if (!sessionArgs[2] || !sessionArgs[3]) {
981
+ console.error('Usage: svamp session inbox send <target-session-id> "<body>" [--subject "..."] [--urgency urgent|normal] [--json]');
982
+ process.exit(1);
983
+ }
984
+ if (agentSessionId) {
985
+ const { inboxSend } = await import('./agentCommands-CKd1jXfX.mjs');
986
+ await inboxSend(sessionArgs[2], {
987
+ body: sessionArgs[3],
988
+ subject: parseFlagStr("--subject"),
989
+ urgency: parseFlagStr("--urgency")
990
+ });
991
+ } else {
992
+ await sessionInboxSend(sessionArgs[2], sessionArgs[3], targetMachineId, {
993
+ subject: parseFlagStr("--subject"),
994
+ urgency: parseFlagStr("--urgency"),
995
+ json: hasFlag("--json")
996
+ });
997
+ }
998
+ } else if (inboxSubcmd === "list" || inboxSubcmd === "ls") {
999
+ if (agentSessionId && !sessionArgs[2]) {
1000
+ const { inboxList } = await import('./agentCommands-CKd1jXfX.mjs');
1001
+ await inboxList({
1002
+ unread: hasFlag("--unread"),
1003
+ limit: parseFlagInt("--limit"),
1004
+ json: hasFlag("--json")
1005
+ });
1006
+ } else if (sessionArgs[2]) {
1007
+ await sessionInboxList(sessionArgs[2], targetMachineId, {
1008
+ unread: hasFlag("--unread"),
1009
+ limit: parseFlagInt("--limit"),
1010
+ json: hasFlag("--json")
1011
+ });
1012
+ } else {
1013
+ console.error("Usage: svamp session inbox list [session-id] [--unread] [--limit N] [--json]");
1014
+ process.exit(1);
1015
+ }
1016
+ } else if (inboxSubcmd === "read") {
1017
+ if (!sessionArgs[2]) {
1018
+ console.error("Usage: svamp session inbox read <session-id|message-id> [message-id]");
1019
+ process.exit(1);
1020
+ }
1021
+ if (agentSessionId && !sessionArgs[3]) {
1022
+ const { inboxList } = await import('./agentCommands-CKd1jXfX.mjs');
1023
+ await sessionInboxRead(agentSessionId, sessionArgs[2], targetMachineId);
1024
+ } else if (sessionArgs[3]) {
1025
+ await sessionInboxRead(sessionArgs[2], sessionArgs[3], targetMachineId);
1026
+ } else {
1027
+ console.error("Usage: svamp session inbox read <session-id> <message-id>");
1028
+ process.exit(1);
1029
+ }
1030
+ } else if (inboxSubcmd === "reply") {
1031
+ if (agentSessionId && sessionArgs[2] && sessionArgs[3] && !sessionArgs[4]) {
1032
+ const { inboxReply } = await import('./agentCommands-CKd1jXfX.mjs');
1033
+ await inboxReply(sessionArgs[2], sessionArgs[3]);
1034
+ } else if (sessionArgs[2] && sessionArgs[3] && sessionArgs[4]) {
1035
+ await sessionInboxReply(sessionArgs[2], sessionArgs[3], sessionArgs[4], targetMachineId);
1036
+ } else {
1037
+ console.error('Usage: svamp session inbox reply <session-id> <message-id> "<body>"');
1038
+ process.exit(1);
1039
+ }
1040
+ } else if (inboxSubcmd === "clear") {
1041
+ const clearOpts = { all: hasFlag("--all"), messageId: parseFlagStr("--message") };
1042
+ if (agentSessionId && !sessionArgs[2]) {
1043
+ await sessionInboxClear(agentSessionId, targetMachineId, clearOpts);
1044
+ } else if (sessionArgs[2]) {
1045
+ await sessionInboxClear(sessionArgs[2], targetMachineId, clearOpts);
1046
+ } else {
1047
+ console.error("Usage: svamp session inbox clear [session-id] [--message <message-id> | --all]");
1048
+ console.error(" (the positional is the SESSION id; use --message <id> to purge a specific stuck message)");
1049
+ process.exit(1);
1050
+ }
1051
+ } else {
1052
+ console.error("Usage: svamp session inbox <send|list|read|reply|clear> [session-id] [args]");
1053
+ process.exit(1);
1054
+ }
1055
+ } else {
1056
+ console.error(`Unknown session command: ${sessionSubcommand}`);
1057
+ printSessionHelp();
1058
+ process.exit(1);
1059
+ }
1060
+ process.exit(0);
1061
+ }
1062
+ async function handleMachineCommand() {
1063
+ const machineArgs = args.slice(1);
1064
+ const machineSubcommand = machineArgs[0];
1065
+ if (!machineSubcommand || machineSubcommand === "--help" || machineSubcommand === "-h") {
1066
+ printMachineHelp();
1067
+ return;
1068
+ }
1069
+ if (machineSubcommand === "share") {
1070
+ const { machineShare } = await import('./commands-C_74tXoO.mjs');
1071
+ let machineId;
1072
+ const shareArgs = [];
1073
+ for (let i = 1; i < machineArgs.length; i++) {
1074
+ if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
1075
+ machineId = machineArgs[++i];
1076
+ } else {
1077
+ shareArgs.push(machineArgs[i]);
1078
+ }
1079
+ }
1080
+ let add;
1081
+ let remove;
1082
+ let configPath;
1083
+ let list = false;
1084
+ let showConfig = false;
1085
+ for (let i = 0; i < shareArgs.length; i++) {
1086
+ if (shareArgs[i] === "--add" && i + 1 < shareArgs.length) {
1087
+ add = shareArgs[++i];
1088
+ } else if (shareArgs[i] === "--remove" && i + 1 < shareArgs.length) {
1089
+ remove = shareArgs[++i];
1090
+ } else if (shareArgs[i] === "--config" && i + 1 < shareArgs.length) {
1091
+ configPath = shareArgs[++i];
1092
+ } else if (shareArgs[i] === "--list") {
1093
+ list = true;
1094
+ } else if (shareArgs[i] === "--show-config") {
1095
+ showConfig = true;
1096
+ }
1097
+ }
1098
+ await machineShare(machineId, { add, remove, list, configPath, showConfig });
1099
+ } else if (machineSubcommand === "exec") {
1100
+ let machineId;
1101
+ let cwd;
1102
+ let all = false;
1103
+ const cmdParts = [];
1104
+ for (let i = 1; i < machineArgs.length; i++) {
1105
+ if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
1106
+ machineId = machineArgs[++i];
1107
+ } else if (machineArgs[i] === "--cwd" && i + 1 < machineArgs.length) {
1108
+ cwd = machineArgs[++i];
1109
+ } else if (machineArgs[i] === "--all") {
1110
+ all = true;
1111
+ } else {
1112
+ cmdParts.push(machineArgs[i]);
1113
+ }
1114
+ }
1115
+ const command = cmdParts.join(" ");
1116
+ if (!command) {
1117
+ console.error("Usage: svamp machine exec <command> [--cwd <path>] [-m <id> | --all]");
1118
+ process.exit(1);
1119
+ }
1120
+ if (all) {
1121
+ const { fleetExec } = await import('./fleet-D3ycNACq.mjs');
1122
+ await fleetExec(command, { cwd });
1123
+ } else {
1124
+ const { machineExec } = await import('./commands-C_74tXoO.mjs');
1125
+ await machineExec(machineId, command, cwd);
1126
+ }
1127
+ } else if (machineSubcommand === "info") {
1128
+ const { machineInfo } = await import('./commands-C_74tXoO.mjs');
1129
+ let machineId;
1130
+ for (let i = 1; i < machineArgs.length; i++) {
1131
+ if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
1132
+ machineId = machineArgs[++i];
1133
+ }
1134
+ }
1135
+ await machineInfo(machineId);
1136
+ } else if (machineSubcommand === "notify") {
1137
+ const message = machineArgs[1];
1138
+ if (!message) {
1139
+ console.error("Usage: svamp machine notify <message> [--level info|warning|error]");
1140
+ process.exit(1);
1141
+ }
1142
+ let level = "info";
1143
+ for (let i = 2; i < machineArgs.length; i++) {
1144
+ if (machineArgs[i] === "--level" && i + 1 < machineArgs.length) {
1145
+ level = machineArgs[++i];
1146
+ }
1147
+ }
1148
+ const { machineNotify } = await import('./agentCommands-CKd1jXfX.mjs');
1149
+ await machineNotify(message, level);
1150
+ } else if (machineSubcommand === "ls") {
1151
+ const { machineLs } = await import('./commands-C_74tXoO.mjs');
1152
+ let machineId;
1153
+ let showHidden = false;
1154
+ let path;
1155
+ for (let i = 1; i < machineArgs.length; i++) {
1156
+ if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
1157
+ machineId = machineArgs[++i];
1158
+ } else if (machineArgs[i] === "--hidden" || machineArgs[i] === "-a") {
1159
+ showHidden = true;
1160
+ } else if (!path) {
1161
+ path = machineArgs[i];
1162
+ }
1163
+ }
1164
+ await machineLs(machineId, path, showHidden);
1165
+ } else {
1166
+ console.error(`Unknown machine command: ${machineSubcommand}`);
1167
+ printMachineHelp();
1168
+ process.exit(1);
1169
+ }
1170
+ process.exit(0);
1171
+ }
1172
+ async function handleFleetCommand() {
1173
+ const fleetArgs = args.slice(1);
1174
+ const sub = fleetArgs[0];
1175
+ if (!sub || sub === "--help" || sub === "-h") {
1176
+ console.log(`Usage: svamp fleet <subcommand>
1177
+
1178
+ Subcommands (fan out across every machine in your workspace):
1179
+ status Show svamp + claude-code version per machine
1180
+ upgrade-claude [-v X] Install Claude Code (defaults to svamp-cli's pinned version)
1181
+ upgrade-svamp [-v X] [--exclude-self] [--force]
1182
+ npm install -g svamp-cli@X, then restart each daemon.
1183
+ DEFAULT is graceful: each daemon restarts itself only
1184
+ when it has no actively-thinking session (never cuts an
1185
+ in-flight turn; 1h max-wait backstop). --force restarts
1186
+ immediately. --exclude-self skips this machine.
1187
+ (First roll onto a graceful-capable build needs --force.)
1188
+ daemon-restart [--cleanup] Restart daemons (default graceful)
1189
+ push-skill <name> Install/force-refresh a skill on all machines
1190
+
1191
+ Each row reports OK / FAIL / SKIP. Failures don't sink the batch; exit code 1
1192
+ only if at least one machine failed.
1193
+
1194
+ Examples:
1195
+ svamp fleet status
1196
+ svamp fleet upgrade-claude
1197
+ svamp fleet upgrade-svamp -v 0.2.73
1198
+ svamp fleet upgrade-svamp --exclude-self
1199
+ svamp fleet push-skill hypha`);
1200
+ process.exit(0);
1201
+ }
1202
+ const flag = (name, short) => {
1203
+ for (let i = 1; i < fleetArgs.length; i++) {
1204
+ if (fleetArgs[i] === `--${name}` || fleetArgs[i] === short) return fleetArgs[i + 1];
1205
+ }
1206
+ return void 0;
1207
+ };
1208
+ const hasFlag = (name) => fleetArgs.includes(`--${name}`);
1209
+ if (sub === "status") {
1210
+ const { fleetStatus } = await import('./fleet-D3ycNACq.mjs');
1211
+ await fleetStatus();
1212
+ } else if (sub === "upgrade-claude") {
1213
+ const { fleetUpgradeClaude } = await import('./fleet-D3ycNACq.mjs');
1214
+ await fleetUpgradeClaude({ version: flag("version", "-v") });
1215
+ } else if (sub === "upgrade-svamp") {
1216
+ const { fleetUpgradeSvamp } = await import('./fleet-D3ycNACq.mjs');
1217
+ await fleetUpgradeSvamp({ version: flag("version", "-v"), excludeSelf: hasFlag("exclude-self"), force: hasFlag("force") });
1218
+ } else if (sub === "daemon-restart") {
1219
+ const { fleetDaemonRestart } = await import('./fleet-D3ycNACq.mjs');
1220
+ await fleetDaemonRestart({ graceful: !hasFlag("cleanup") });
1221
+ } else if (sub === "push-skill") {
1222
+ const name = fleetArgs[1];
1223
+ const { fleetPushSkill } = await import('./fleet-D3ycNACq.mjs');
1224
+ await fleetPushSkill(name);
1225
+ } else {
1226
+ console.error(`Unknown fleet subcommand: ${sub}`);
1227
+ console.error('Run "svamp fleet --help" to see available subcommands.');
1228
+ process.exit(1);
1229
+ }
1230
+ process.exit(process.exitCode || 0);
1231
+ }
1232
+ async function handleSkillsCommand() {
1233
+ const skillsArgs = args.slice(1);
1234
+ const skillsSubcommand = skillsArgs[0];
1235
+ if (!skillsSubcommand || skillsSubcommand === "--help" || skillsSubcommand === "-h") {
1236
+ await printSkillsHelp();
1237
+ return;
1238
+ }
1239
+ const { skillsFind, skillsInstall, skillsList, skillsRemove, skillsPublish } = await import('./commands-BUhsW3bf.mjs');
1240
+ if (skillsSubcommand === "find" || skillsSubcommand === "search") {
1241
+ const query = skillsArgs.slice(1).filter((a) => !a.startsWith("--")).join(" ");
1242
+ if (!query) {
1243
+ console.error("Usage: svamp skills find <query> [--json]");
1244
+ process.exit(1);
1245
+ }
1246
+ await skillsFind(query, { json: skillsArgs.includes("--json") });
1247
+ } else if (skillsSubcommand === "install" || skillsSubcommand === "add") {
1248
+ if (!skillsArgs[1]) {
1249
+ console.error("Usage: svamp skills install <name> [--force]");
1250
+ process.exit(1);
1251
+ }
1252
+ await skillsInstall(skillsArgs[1], { force: skillsArgs.includes("--force") });
1253
+ } else if (skillsSubcommand === "list" || skillsSubcommand === "ls") {
1254
+ await skillsList();
1255
+ } else if (skillsSubcommand === "remove" || skillsSubcommand === "rm") {
1256
+ if (!skillsArgs[1]) {
1257
+ console.error("Usage: svamp skills remove <name>");
1258
+ process.exit(1);
1259
+ }
1260
+ await skillsRemove(skillsArgs[1]);
1261
+ } else if (skillsSubcommand === "publish") {
1262
+ const publishArgs = skillsArgs.slice(1).filter((a) => !a.startsWith("--"));
1263
+ if (!publishArgs[0]) {
1264
+ console.error("Usage: svamp skills publish <path> [--version <tag>]");
1265
+ process.exit(1);
1266
+ }
1267
+ const versionIdx = skillsArgs.indexOf("--version");
1268
+ const versionTag = versionIdx !== -1 ? skillsArgs[versionIdx + 1] : void 0;
1269
+ await skillsPublish(publishArgs[0], { version: versionTag });
1270
+ } else {
1271
+ console.error(`Unknown skills command: ${skillsSubcommand}`);
1272
+ await printSkillsHelp();
1273
+ process.exit(1);
1274
+ }
1275
+ process.exit(0);
1276
+ }
1277
+ async function loginToHypha() {
1278
+ const anchorArg = args[1] || process.env.SVAMP_INSTANCE_URL || process.env.HYPHA_SERVER_URL;
1279
+ if (!anchorArg) {
1280
+ console.error("Usage: svamp login <frontend-url> (e.g. https://svamp.aicell.io)");
1281
+ console.error(" The frontend serves /svamp.json, which carries the Hypha server URL and");
1282
+ console.error(" routing config. A Hypha server URL also works as a legacy fallback.");
1283
+ process.exit(1);
1284
+ }
1285
+ const anchor = anchorArg.replace(/\/+$/, "");
1286
+ const { loadInstanceConfig } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.as; });
1287
+ let cfg = null;
1288
+ try {
1289
+ cfg = await loadInstanceConfig({ anchor, force: true });
1290
+ } catch {
1291
+ }
1292
+ const configMode = !!(cfg && typeof cfg.hyphaServerUrl === "string" && cfg.hyphaServerUrl.trim());
1293
+ const serverUrl = configMode ? cfg.hyphaServerUrl.trim().replace(/\/+$/, "") : anchor;
1294
+ if (configMode) {
1295
+ console.log(`Found Svamp instance config at ${anchor}`);
1296
+ console.log(` Hypha server: ${serverUrl}`);
1297
+ } else {
1298
+ console.log(`No svamp.json found at ${anchor} \u2014 using it as the Hypha server (legacy mode).`);
1299
+ }
1300
+ console.log(`Logging in to Hypha server: ${serverUrl}`);
1301
+ console.log("A browser window will open for authentication...");
1302
+ try {
1303
+ const mod = await import('hypha-rpc');
1304
+ const hyphaWebsocketClient = mod.hyphaWebsocketClient || mod.default?.hyphaWebsocketClient;
1305
+ if (!hyphaWebsocketClient?.login) {
1306
+ throw new Error("Could not find hyphaWebsocketClient.login in hypha-rpc");
1307
+ }
1308
+ const token = await hyphaWebsocketClient.login({
1309
+ server_url: serverUrl,
1310
+ login_timeout: 120,
1311
+ login_callback: (context) => {
1312
+ console.log(`
1313
+ Please open this URL in your browser:
1314
+ ${context.login_url}
1315
+ `);
1316
+ console.log("Waiting for you to complete login in the browser...\n");
1317
+ }
1318
+ });
1319
+ if (!token) {
1320
+ console.error("Login failed \u2014 no token received");
1321
+ process.exit(1);
1322
+ }
1323
+ const connectToServer = mod.connectToServer || mod.default && mod.default.connectToServer || hyphaWebsocketClient && hyphaWebsocketClient.connectToServer;
1324
+ const server = await connectToServer({ server_url: serverUrl, token });
1325
+ const workspace = server.config.workspace;
1326
+ const userId = server.config.user?.id || workspace;
1327
+ let longLivedToken = token;
1328
+ try {
1329
+ const sixMonthsInSeconds = 6 * 30 * 24 * 60 * 60;
1330
+ longLivedToken = await server.generateToken({ expires_in: sixMonthsInSeconds, workspace });
1331
+ console.log("Generated long-lived token (expires in ~6 months)");
1332
+ } catch (err) {
1333
+ console.warn("Could not generate long-lived token, using login token:", err.message || err);
1334
+ }
1335
+ await server.disconnect();
1336
+ const os = await import('os');
1337
+ const { join } = await import('path');
1338
+ const fs = await import('fs');
1339
+ const homeDir = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
1340
+ if (!fs.existsSync(homeDir)) {
1341
+ fs.mkdirSync(homeDir, { recursive: true });
1342
+ }
1343
+ const envFile = join(homeDir, ".env");
1344
+ const envLines = [];
1345
+ if (fs.existsSync(envFile)) {
1346
+ const existing = fs.readFileSync(envFile, "utf-8").split("\n");
1347
+ for (const line of existing) {
1348
+ const trimmed = line.trim();
1349
+ if (trimmed.startsWith("HYPHA_TOKEN=") || trimmed.startsWith("HYPHA_WORKSPACE=") || trimmed.startsWith("HYPHA_SERVER_URL=") || trimmed.startsWith("SVAMP_INSTANCE_URL=")) {
1350
+ continue;
1351
+ }
1352
+ envLines.push(line);
1353
+ }
1354
+ }
1355
+ if (configMode) {
1356
+ envLines.push(`SVAMP_INSTANCE_URL=${anchor}`);
1357
+ } else {
1358
+ envLines.push(`HYPHA_SERVER_URL=${serverUrl}`);
1359
+ }
1360
+ envLines.push(`HYPHA_TOKEN=${longLivedToken}`);
1361
+ envLines.push(`HYPHA_WORKSPACE=${workspace}`);
1362
+ while (envLines.length > 0 && envLines[envLines.length - 1].trim() === "") {
1363
+ envLines.pop();
1364
+ }
1365
+ fs.writeFileSync(envFile, envLines.join("\n") + "\n", "utf-8");
1366
+ console.log(`
1367
+ Login successful!`);
1368
+ console.log(` User: ${userId}`);
1369
+ console.log(` Workspace: ${workspace}`);
1370
+ console.log(` Token saved to: ${envFile}`);
1371
+ console.log(`
1372
+ You can now start the daemon: svamp daemon start`);
1373
+ } catch (err) {
1374
+ const msg = err.message || String(err);
1375
+ if (msg.includes("WebSocket is not defined")) {
1376
+ console.error(`Login failed: WebSocket is not available in this Node.js version (${process.version}).`);
1377
+ console.error(` hypha-rpc requires Node.js >= 22 (which includes native WebSocket support).`);
1378
+ console.error(` Please upgrade Node.js: nvm install 22 && nvm use 22`);
1379
+ } else {
1380
+ console.error("Login failed:", msg);
1381
+ }
1382
+ process.exit(1);
1383
+ }
1384
+ }
1385
+ async function logoutFromHypha() {
1386
+ const os = await import('os');
1387
+ const { join } = await import('path');
1388
+ const fs = await import('fs');
1389
+ const homeDir = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
1390
+ const envFile = join(homeDir, ".env");
1391
+ console.log("Stopping daemon...");
1392
+ try {
1393
+ await stopDaemon();
1394
+ } catch {
1395
+ }
1396
+ try {
1397
+ const { clearInstanceConfigCache } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.as; });
1398
+ clearInstanceConfigCache();
1399
+ } catch {
1400
+ }
1401
+ if (fs.existsSync(envFile)) {
1402
+ const existing = fs.readFileSync(envFile, "utf-8").split("\n");
1403
+ const kept = [];
1404
+ for (const line of existing) {
1405
+ const trimmed = line.trim();
1406
+ if (trimmed.startsWith("HYPHA_TOKEN=") || trimmed.startsWith("HYPHA_WORKSPACE=") || trimmed.startsWith("HYPHA_SERVER_URL=") || trimmed.startsWith("SVAMP_INSTANCE_URL=")) {
1407
+ continue;
1408
+ }
1409
+ kept.push(line);
1410
+ }
1411
+ while (kept.length > 0 && kept[kept.length - 1].trim() === "") {
1412
+ kept.pop();
1413
+ }
1414
+ if (kept.length > 0) {
1415
+ fs.writeFileSync(envFile, kept.join("\n") + "\n", "utf-8");
1416
+ } else {
1417
+ fs.unlinkSync(envFile);
1418
+ }
1419
+ console.log("Credentials removed");
1420
+ } else {
1421
+ console.log("No credentials found");
1422
+ }
1423
+ console.log("Logged out successfully");
1424
+ }
1425
+ async function installDaemonService() {
1426
+ const os = await import('os');
1427
+ const { join, dirname } = await import('path');
1428
+ const fs = await import('fs');
1429
+ const { execSync } = await import('child_process');
1430
+ const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
1431
+ const logsDir = join(svampHome, "logs");
1432
+ let svampBinary = process.argv[1];
1433
+ try {
1434
+ const resolved = execSync(`which svamp`, { encoding: "utf-8" }).trim();
1435
+ if (resolved) svampBinary = resolved;
1436
+ } catch {
1437
+ }
1438
+ if (!fs.existsSync(svampBinary)) {
1439
+ console.error(`Cannot find svamp binary at: ${svampBinary}`);
1440
+ console.error("Please install svamp-cli globally first.");
1441
+ process.exit(1);
1442
+ }
1443
+ const nodeBinDir = dirname(process.execPath);
1444
+ const shellPath = process.env.PATH || "";
1445
+ const supervisedPath = shellPath.includes(nodeBinDir) ? shellPath : [nodeBinDir, shellPath].filter(Boolean).join(":");
1446
+ if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
1447
+ if (process.platform === "darwin") {
1448
+ const launchAgentsDir = join(os.homedir(), "Library", "LaunchAgents");
1449
+ const plistPath = join(launchAgentsDir, `${LAUNCHD_LABEL}.plist`);
1450
+ if (!fs.existsSync(launchAgentsDir)) fs.mkdirSync(launchAgentsDir, { recursive: true });
1451
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
1452
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1453
+ <plist version="1.0">
1454
+ <dict>
1455
+ <key>Label</key>
1456
+ <string>${LAUNCHD_LABEL}</string>
1457
+ <key>ProgramArguments</key>
1458
+ <array>
1459
+ <string>${svampBinary}</string>
1460
+ <string>daemon</string>
1461
+ <string>start-supervised</string>
1462
+ </array>
1463
+ <key>RunAtLoad</key>
1464
+ <true/>
1465
+ <key>KeepAlive</key>
1466
+ <true/>
1467
+ <key>StandardOutPath</key>
1468
+ <string>${logsDir}/daemon-supervised.log</string>
1469
+ <key>StandardErrorPath</key>
1470
+ <string>${logsDir}/daemon-supervised.log</string>
1471
+ <key>EnvironmentVariables</key>
1472
+ <dict>
1473
+ <key>PATH</key>
1474
+ <string>${supervisedPath}</string>
1475
+ <key>SVAMP_HOME</key>
1476
+ <string>${svampHome}</string>
1477
+ </dict>
1478
+ </dict>
1479
+ </plist>
1480
+ `;
1481
+ fs.writeFileSync(plistPath, plist, "utf-8");
1482
+ console.log(`Plist written: ${plistPath}`);
1483
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
1484
+ try {
1485
+ execSync(`launchctl bootout gui/${uid}/${LAUNCHD_LABEL} 2>/dev/null`, { stdio: "ignore" });
1486
+ } catch {
1487
+ }
1488
+ execSync(`launchctl bootstrap gui/${uid} "${plistPath}"`);
1489
+ try {
1490
+ execSync(`launchctl enable gui/${uid}/${LAUNCHD_LABEL} 2>/dev/null`, { stdio: "ignore" });
1491
+ } catch {
1492
+ }
1493
+ try {
1494
+ execSync(`launchctl kickstart gui/${uid}/${LAUNCHD_LABEL} 2>/dev/null`, { stdio: "ignore" });
1495
+ } catch {
1496
+ }
1497
+ console.log(`
1498
+ Svamp daemon service installed and started!`);
1499
+ console.log(` Service: ${LAUNCHD_LABEL}`);
1500
+ console.log(` Auto-restart on crash: enabled`);
1501
+ console.log(` Log file: ${logsDir}/daemon-supervised.log`);
1502
+ console.log(`
1503
+ To stop the service: svamp daemon stop`);
1504
+ console.log(`To uninstall: svamp daemon uninstall`);
1505
+ } else if (process.platform === "linux") {
1506
+ const hasSystemd = (() => {
1507
+ try {
1508
+ execSync("systemctl --user status 2>/dev/null", { stdio: "pipe" });
1509
+ return true;
1510
+ } catch {
1511
+ return false;
1512
+ }
1513
+ })();
1514
+ if (hasSystemd) {
1515
+ const systemdUserDir = join(os.homedir(), ".config", "systemd", "user");
1516
+ const unitPath = join(systemdUserDir, "svamp-daemon.service");
1517
+ if (!fs.existsSync(systemdUserDir)) fs.mkdirSync(systemdUserDir, { recursive: true });
1518
+ const unit = `[Unit]
1519
+ Description=Svamp Daemon \u2014 AI workspace on Hypha Cloud
1520
+ After=network-online.target
1521
+ Wants=network-online.target
1522
+
1523
+ [Service]
1524
+ ExecStart=${svampBinary} daemon start-supervised
1525
+ Environment=PATH=${supervisedPath}
1526
+ Environment=SVAMP_HOME=${svampHome}
1527
+ Restart=on-failure
1528
+ RestartSec=3
1529
+ StandardOutput=append:${logsDir}/daemon-supervised.log
1530
+ StandardError=append:${logsDir}/daemon-supervised.log
1531
+
1532
+ [Install]
1533
+ WantedBy=default.target
1534
+ `;
1535
+ fs.writeFileSync(unitPath, unit, "utf-8");
1536
+ execSync("systemctl --user daemon-reload");
1537
+ execSync("systemctl --user enable --now svamp-daemon.service");
1538
+ console.log(`
1539
+ Svamp daemon service installed and started!`);
1540
+ console.log(` Service: svamp-daemon.service (systemd user)`);
1541
+ console.log(` Unit file: ${unitPath}`);
1542
+ console.log(` Auto-restart on crash: enabled`);
1543
+ console.log(` Log file: ${logsDir}/daemon-supervised.log`);
1544
+ console.log(`
1545
+ To stop the service: svamp daemon stop`);
1546
+ console.log(`To uninstall: svamp daemon uninstall`);
1547
+ } else {
1548
+ console.log(`
1549
+ No systemd detected. The built-in supervisor is cross-platform.`);
1550
+ console.log(`
1551
+ For Docker, set your container CMD to:`);
1552
+ console.log(` ${svampBinary} daemon start-supervised`);
1553
+ console.log(`
1554
+ Or run in the background:`);
1555
+ console.log(` svamp daemon start`);
1556
+ console.log(`
1557
+ To stop the daemon: svamp daemon stop`);
1558
+ }
1559
+ } else {
1560
+ console.log(`
1561
+ The built-in supervisor is cross-platform.`);
1562
+ console.log(`
1563
+ Run: svamp daemon start`);
1564
+ console.log(`
1565
+ To stop: svamp daemon stop`);
1566
+ }
1567
+ }
1568
+ async function uninstallDaemonService() {
1569
+ const os = await import('os');
1570
+ const { join } = await import('path');
1571
+ const fs = await import('fs');
1572
+ const { execSync } = await import('child_process');
1573
+ if (process.platform === "darwin") {
1574
+ const plistPath = join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
1575
+ if (!fs.existsSync(plistPath)) {
1576
+ console.log("Svamp daemon service is not installed.");
1577
+ return;
1578
+ }
1579
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
1580
+ try {
1581
+ execSync(`launchctl bootout gui/${uid}/${LAUNCHD_LABEL} 2>/dev/null`, { stdio: "pipe" });
1582
+ } catch {
1583
+ try {
1584
+ execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "pipe" });
1585
+ } catch {
1586
+ }
1587
+ }
1588
+ fs.unlinkSync(plistPath);
1589
+ console.log("Svamp daemon service uninstalled (launchd).");
1590
+ console.log("The daemon has been stopped and will no longer auto-start.");
1591
+ } else if (process.platform === "linux") {
1592
+ const unitPath = join(os.homedir(), ".config", "systemd", "user", "svamp-daemon.service");
1593
+ const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
1594
+ const wrapperPath = join(svampHome, "daemon-supervisor.sh");
1595
+ let removed = false;
1596
+ if (fs.existsSync(unitPath)) {
1597
+ try {
1598
+ execSync("systemctl --user disable --now svamp-daemon.service", { stdio: "pipe" });
1599
+ } catch {
1600
+ }
1601
+ fs.unlinkSync(unitPath);
1602
+ try {
1603
+ execSync("systemctl --user daemon-reload", { stdio: "pipe" });
1604
+ } catch {
1605
+ }
1606
+ console.log("Svamp daemon service uninstalled (systemd).");
1607
+ removed = true;
1608
+ }
1609
+ if (fs.existsSync(wrapperPath)) {
1610
+ fs.unlinkSync(wrapperPath);
1611
+ if (!removed) console.log("Svamp daemon supervisor script removed.");
1612
+ removed = true;
1613
+ }
1614
+ if (!removed) {
1615
+ console.log("Svamp daemon service is not installed.");
1616
+ }
1617
+ } else {
1618
+ console.error(`Daemon service uninstall is not supported on ${process.platform}.`);
1619
+ process.exit(1);
1620
+ }
1621
+ }
1622
+ function printHelp() {
1623
+ console.log(`
1624
+ svamp \u2014 AI workspace on Hypha Cloud
1625
+
1626
+ Quick start \u2014 spawn an agent to do a task (autonomous by default):
1627
+ svamp session spawn claude -d ./project --message "fix tests" --wait
1628
+
1629
+ Note: as of 0.2.69 spawn defaults to permission mode "bypassPermissions" so the
1630
+ child agent doesn't get stuck on tool-approval prompts. Pass --require-approval
1631
+ (or -p default / -p acceptEdits) when you want a human to gate tool use.
1632
+
1633
+ Commands:
1634
+ svamp Start interactive Claude session (synced to cloud)
1635
+ svamp login [url] Login to Hypha (opens browser, stores token)
1636
+ svamp logout Logout (stop daemon, remove credentials)
1637
+ svamp daemon start Start the background daemon (required for sessions)
1638
+ svamp daemon status Show daemon status
1639
+ svamp daemon --help Show all daemon commands
1640
+
1641
+ Session management (requires daemon running):
1642
+ svamp session spawn <agent> [opts] Spawn a new agent session (agents: claude, gemini, codex)
1643
+ svamp session send <id> <message> Send message to a session (urgent by default \u2014 wakes the recipient; --when-idle to defer to their next idle turn)
1644
+ svamp session query <dir> <prompt> Stateless one-shot: spawn \u2192 ask \u2192 answer \u2192 delete
1645
+ svamp session wait <id> Wait for agent to become idle
1646
+ svamp session messages <id> Show message history (--last N, --json, --raw)
1647
+ svamp session info <id> Show session status & pending permissions
1648
+ svamp session list List sessions
1649
+ svamp session whoami Show your environment: peers on this machine & other machines, how to reach them
1650
+ svamp session archive <id> Archive a session (preserves history, resumable later)
1651
+ svamp session resume <id> Resume an archived session
1652
+ svamp session delete <id> Permanently delete a session (cannot be undone)
1653
+ svamp session attach <id> Attach interactive terminal (stdin/stdout)
1654
+ svamp session approve/deny <id> Approve or deny pending permission requests
1655
+ svamp session --help Show ALL session commands and detailed options
1656
+
1657
+ Crew (lead delegates features to managed worktree children):
1658
+ svamp feature start "<brief>" Spawn a worktree child of this lead + loop gate (oracle\u2192parent)
1659
+ svamp feature list This lead's feature children (branch, ahead/behind, status)
1660
+ svamp feature done|report (run by a child) signal completion / report progress to the lead
1661
+ svamp feature merge <child> (run by the lead) verify \u2192 merge \u2192 remove worktree \u2192 archive child
1662
+ svamp feature --help Show all crew commands and options
1663
+
1664
+ File serving:
1665
+ svamp serve <name> [directory] Serve a directory via the shared file server
1666
+ svamp serve remove <name> Remove a served mount
1667
+ svamp serve list [--all] List served mounts
1668
+ svamp serve info Show server status and URL
1669
+ svamp serve --help Show all serve commands
1670
+
1671
+ Other:
1672
+ svamp machine --help Machine sharing & security contexts
1673
+ svamp fleet --help Fan-out commands across all your machines (status, upgrade, exec, push-skill)
1674
+ svamp skills --help Skills marketplace (find, install, publish)
1675
+ svamp service --help Service exposure (HTTP services from sandboxes)
1676
+ svamp agent <name> Start local agent session (no daemon needed)
1677
+ svamp --version Show version
1678
+
1679
+ Interactive mode:
1680
+ Run 'svamp' with no arguments to start Claude in your terminal with cloud sync.
1681
+ Messages from the web app auto-switch to remote mode. Space-Space returns to local.
1682
+
1683
+ Environment variables:
1684
+ HYPHA_SERVER_URL Hypha server URL (required for cloud sync)
1685
+ HYPHA_TOKEN Hypha auth token (stored by login command)
1686
+ HYPHA_WORKSPACE Hypha workspace / user ID (stored by login command)
1687
+ SVAMP_HOME Config directory (default: ~/.svamp)
1688
+ `);
1689
+ }
1690
+ function printDaemonHelp() {
1691
+ console.log(`
1692
+ svamp daemon \u2014 Daemon management
1693
+
1694
+ Usage:
1695
+ svamp daemon start Start the daemon (detached)
1696
+ svamp daemon start --no-auto-continue Start without auto-continuing interrupted sessions
1697
+ svamp daemon stop Stop the daemon (sessions preserved for restart)
1698
+ svamp daemon stop --cleanup Stop and mark all sessions as stopped
1699
+ svamp daemon restart Restart now \u2014 picks up new binary, sessions resume seamlessly
1700
+ svamp daemon restart --when-idle Restart only once no session is actively thinking (deferred; 1h max-wait)
1701
+ svamp daemon status Show daemon status
1702
+ svamp daemon install Install as login service (macOS/Linux) \u2014 auto-start at login
1703
+ svamp daemon uninstall Remove login service
1704
+ svamp daemon auth Show Claude API auth mode used for spawned sessions
1705
+ svamp daemon auth use-hypha-proxy <URL>
1706
+ Route Claude through the Hypha LLM proxy with HYPHA_TOKEN.
1707
+ URL is required (no default) \u2014 e.g. https://proxy.hypha.aicell.io
1708
+ for the hypha-cloud cluster, or your own deployment's proxy.
1709
+ svamp daemon auth use-login Use ~/.claude credentials (run "claude login" to set them)
1710
+ svamp daemon auth set <URL> <KEY> Use a custom Anthropic gateway (base URL must not end in /v1)
1711
+
1712
+ Claude auth flags (take effect on next start/restart):
1713
+ svamp daemon start --use-hypha-proxy <URL> (or --hypha-proxy-url URL)
1714
+ svamp daemon start --use-claude-login
1715
+ svamp daemon start --anthropic-base-url URL --anthropic-api-key KEY
1716
+
1717
+ Machine sharing (seed at boot \u2014 equivalent to running 'svamp machine share --add'
1718
+ for each email; idempotent, never removes):
1719
+ svamp daemon start --share alice@example.com,bob@example.com
1720
+ svamp daemon start --share alice@example.com --share bob@example.com
1721
+ `);
1722
+ }
1723
+ async function applyClaudeAuthFlags(argv) {
1724
+ const hasHypha = argv.includes("--use-hypha-proxy");
1725
+ const hasLogin = argv.includes("--use-claude-login") || argv.includes("--use-login");
1726
+ const baseUrlIdx = argv.indexOf("--anthropic-base-url");
1727
+ const apiKeyIdx = argv.indexOf("--anthropic-api-key");
1728
+ const hasCustom = baseUrlIdx !== -1 || apiKeyIdx !== -1;
1729
+ const chosen = [hasHypha, hasLogin, hasCustom].filter(Boolean).length;
1730
+ if (chosen === 0) return;
1731
+ if (chosen > 1) {
1732
+ throw new Error(
1733
+ "--use-hypha-proxy, --use-claude-login, and --anthropic-base-url/--anthropic-api-key are mutually exclusive"
1734
+ );
1735
+ }
1736
+ const mod = await import('./run-b-b_szy6.mjs').then(function (n) { return n.ak; });
1737
+ if (hasHypha) {
1738
+ let url;
1739
+ const hyphaIdx = argv.indexOf("--use-hypha-proxy");
1740
+ if (hyphaIdx !== -1 && argv[hyphaIdx + 1] && !argv[hyphaIdx + 1].startsWith("-")) {
1741
+ url = argv[hyphaIdx + 1];
1742
+ }
1743
+ const urlFlagIdx = argv.indexOf("--hypha-proxy-url");
1744
+ if (urlFlagIdx !== -1) {
1745
+ const v = argv[urlFlagIdx + 1];
1746
+ if (!v || v.startsWith("-")) throw new Error("--hypha-proxy-url requires a value");
1747
+ url = v;
1748
+ }
1749
+ if (!url) {
1750
+ throw new Error(
1751
+ `--use-hypha-proxy requires a proxy URL (no default). Pass it as \`--use-hypha-proxy ${mod.EXAMPLE_HYPHA_PROXY_URL}\` or \`--hypha-proxy-url ${mod.EXAMPLE_HYPHA_PROXY_URL}\`.`
1752
+ );
1753
+ }
1754
+ mod.setClaudeAuthHyphaProxy(url);
1755
+ console.log(`Claude auth configured: hypha-proxy (${url}; uses HYPHA_TOKEN live at each spawn).`);
1756
+ return;
1757
+ }
1758
+ if (hasLogin) {
1759
+ mod.setClaudeAuthLogin();
1760
+ console.log('Claude auth configured: login (uses ~/.claude credentials \u2014 run "claude login" if needed).');
1761
+ return;
1762
+ }
1763
+ if (baseUrlIdx === -1 || apiKeyIdx === -1) {
1764
+ throw new Error("--anthropic-base-url and --anthropic-api-key must be passed together");
1765
+ }
1766
+ const baseUrl = argv[baseUrlIdx + 1];
1767
+ const apiKey = argv[apiKeyIdx + 1];
1768
+ if (!baseUrl || baseUrl.startsWith("--")) throw new Error("--anthropic-base-url requires a value");
1769
+ if (!apiKey || apiKey.startsWith("--")) throw new Error("--anthropic-api-key requires a value");
1770
+ mod.setClaudeAuthCustom(baseUrl, apiKey);
1771
+ console.log(`Claude auth configured: custom (base URL ${baseUrl}).`);
1772
+ }
1773
+ const DAEMON_SHARE_ENV_KEY = "SVAMP_SHARE_EMAILS";
1774
+ async function applyDaemonShareFlag(argv) {
1775
+ const collected = [];
1776
+ for (let i = 0; i < argv.length; i++) {
1777
+ if (argv[i] === "--share" && i + 1 < argv.length) {
1778
+ const value = argv[++i];
1779
+ for (const part of value.split(",")) {
1780
+ const email = part.trim();
1781
+ if (!email) continue;
1782
+ if (!email.includes("@")) {
1783
+ throw new Error(`--share value "${email}" is not a valid email address`);
1784
+ }
1785
+ collected.push(email);
1786
+ }
1787
+ }
1788
+ }
1789
+ if (collected.length === 0) return;
1790
+ const { updateEnvFile } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.ak; });
1791
+ const seen = /* @__PURE__ */ new Set();
1792
+ const deduped = collected.filter((e) => {
1793
+ const k = e.toLowerCase();
1794
+ if (seen.has(k)) return false;
1795
+ seen.add(k);
1796
+ return true;
1797
+ });
1798
+ updateEnvFile({ [DAEMON_SHARE_ENV_KEY]: deduped.join(",") });
1799
+ console.log(`Machine sharing seed updated: ${deduped.join(", ")} (will be added at next daemon start).`);
1800
+ }
1801
+ async function handleWiseAgentCommand(rest) {
1802
+ const sub = rest[0];
1803
+ if (sub === "ask") {
1804
+ const valueOf = (flags) => {
1805
+ for (const f of flags) {
1806
+ const i = rest.indexOf(f);
1807
+ if (i >= 0 && rest[i + 1]) return rest[i + 1];
1808
+ }
1809
+ return void 0;
1810
+ };
1811
+ const sessionId = valueOf(["--session", "-s"]);
1812
+ const machineId = valueOf(["--machine", "-m"]);
1813
+ const json = rest.includes("--json");
1814
+ const consumed = /* @__PURE__ */ new Set();
1815
+ ["--session", "-s", "--machine", "-m"].forEach((f) => {
1816
+ const i = rest.indexOf(f);
1817
+ if (i >= 0) {
1818
+ consumed.add(String(i));
1819
+ consumed.add(String(i + 1));
1820
+ }
1821
+ });
1822
+ const message = rest.slice(1).map((a, idx) => ({ a, idx: idx + 1 })).filter(({ a, idx }) => !a.startsWith("-") && !consumed.has(String(idx))).map(({ a }) => a).join(" ");
1823
+ const { wiseAskCli } = await import('./commands-C_74tXoO.mjs');
1824
+ await wiseAskCli(machineId, message, sessionId, { json });
1825
+ return;
1826
+ }
1827
+ if (sub === "voice") {
1828
+ const valueOf = (flags) => {
1829
+ for (const f of flags) {
1830
+ const i = rest.indexOf(f);
1831
+ if (i >= 0 && rest[i + 1]) return rest[i + 1];
1832
+ }
1833
+ return void 0;
1834
+ };
1835
+ const { runWiseVoiceCli } = await import('./headlessCli-nWnqozcZ.mjs');
1836
+ await runWiseVoiceCli({ voice: valueOf(["--voice"]), wakeKeywordPath: valueOf(["--wake"]), model: valueOf(["--model"]) });
1837
+ return;
1838
+ }
1839
+ if (sub === "join-meeting" || sub === "join") {
1840
+ const valueOf = (flags) => {
1841
+ for (const f of flags) {
1842
+ const i = rest.indexOf(f);
1843
+ if (i >= 0 && rest[i + 1]) return rest[i + 1];
1844
+ }
1845
+ return void 0;
1846
+ };
1847
+ const machineId = valueOf(["--machine", "-m"]);
1848
+ const sessionId = rest.includes("--global") ? void 0 : valueOf(["--session", "-s"]);
1849
+ const json = rest.includes("--json");
1850
+ const mode = valueOf(["--mode"]);
1851
+ const mission = valueOf(["--mission"]);
1852
+ const url = rest.slice(1).find((a) => /^https?:\/\//.test(a)) || "";
1853
+ const { wiseJoinMeetingCli } = await import('./commands-C_74tXoO.mjs');
1854
+ await wiseJoinMeetingCli(machineId, url, sessionId, { json, mode, mission });
1855
+ return;
1856
+ }
1857
+ if (sub === "leave-meeting" || sub === "leave") {
1858
+ const valueOf = (flags) => {
1859
+ for (const f of flags) {
1860
+ const i = rest.indexOf(f);
1861
+ if (i >= 0 && rest[i + 1]) return rest[i + 1];
1862
+ }
1863
+ return void 0;
1864
+ };
1865
+ const { wiseLeaveMeetingCli } = await import('./commands-C_74tXoO.mjs');
1866
+ await wiseLeaveMeetingCli(valueOf(["--machine", "-m"]), valueOf(["--session", "-s"]), { json: rest.includes("--json") });
1867
+ return;
1868
+ }
1869
+ if (sub === "announce") {
1870
+ const valueOf = (flags) => {
1871
+ for (const f of flags) {
1872
+ const i = rest.indexOf(f);
1873
+ if (i >= 0 && rest[i + 1]) return rest[i + 1];
1874
+ }
1875
+ return void 0;
1876
+ };
1877
+ const sessionId = valueOf(["--session", "-s"]) || process.env.SVAMP_SESSION_ID;
1878
+ const machineId = valueOf(["--machine", "-m"]);
1879
+ const json = rest.includes("--json");
1880
+ const consumed = /* @__PURE__ */ new Set();
1881
+ ["--session", "-s", "--machine", "-m"].forEach((f) => {
1882
+ const i = rest.indexOf(f);
1883
+ if (i >= 0) {
1884
+ consumed.add(String(i));
1885
+ consumed.add(String(i + 1));
1886
+ }
1887
+ });
1888
+ const text = rest.slice(1).map((a, idx) => ({ a, idx: idx + 1 })).filter(({ a, idx }) => !a.startsWith("-") && !consumed.has(String(idx))).map(({ a }) => a).join(" ");
1889
+ const { wiseAnnounceCli } = await import('./commands-C_74tXoO.mjs');
1890
+ await wiseAnnounceCli(machineId, text, sessionId, { json });
1891
+ return;
1892
+ }
1893
+ if (sub === "meetings") {
1894
+ const valueOf = (flags) => {
1895
+ for (const f of flags) {
1896
+ const i = rest.indexOf(f);
1897
+ if (i >= 0 && rest[i + 1]) return rest[i + 1];
1898
+ }
1899
+ return void 0;
1900
+ };
1901
+ const { wiseMeetingsCli } = await import('./commands-C_74tXoO.mjs');
1902
+ await wiseMeetingsCli(valueOf(["--machine", "-m"]), { json: rest.includes("--json") });
1903
+ return;
1904
+ }
1905
+ if (sub === "--help" || sub === "-h") {
1906
+ console.log(`Usage:
1907
+ svamp wise ask "<message>" [--session <id>] [-m <machine>] [--json]
1908
+ svamp wise join-meeting <zoom|meet|teams url> [--session <id>|--global] [--mode listen|text|voice] [--mission "<prompt>"] [-m <machine>]
1909
+ --mode listen=silent (transcribe only), text=reply in meeting chat, voice=speak aloud (default).
1910
+ --mission optional focus prompt for the agent; omit for a general meeting mission.
1911
+ svamp wise leave-meeting [--session <id>] [-m <machine>]
1912
+ svamp wise announce "<text>" [--session <id>] [-m <machine>]
1913
+ Post a chat message into the live meeting (defaults --session to
1914
+ $SVAMP_SESSION_ID, so the deep agent can announce when work is done).
1915
+ svamp wise meetings [-m <machine>]
1916
+ Fast WISE turn \u2014 GLOBAL machine-manager by default (daemon status, run
1917
+ commands, inspect/spawn/drive any session); --session <id> scopes to one.
1918
+
1919
+ svamp wise voice [--voice <name>] [--wake <keyword.ppn>] [--model <id>]
1920
+ HEADLESS voice on this machine (mic + speaker, no browser). Needs \`sox\`.
1921
+ --wake <keyword.ppn> = always-on "Hey WISE" (needs PICOVOICE_ACCESS_KEY);
1922
+ omit it for a single always-listening session (Ctrl-C to stop).
1923
+
1924
+ svamp wise-agent auth <command>
1925
+
1926
+ Configure the server-side WISE Agent's fast model (text companion to the deep agent).
1927
+
1928
+ status Show the effective provider / model / base URL / key.
1929
+ use-openai [KEY] Route via OpenAI (default provider). Optional API key.
1930
+ use-hypha-proxy [URL] Route via the Hypha LLM proxy (quota-governed).
1931
+ use-claude-haiku [URL] Fallback: Claude Haiku via the proxy (no OpenAI key).
1932
+ set <BASE_URL> <KEY> Custom OpenAI-compatible gateway: base URL + key together.
1933
+ set-key <KEY> Set just the API key.
1934
+ set-model <model> Override the model id (e.g. gpt-5-mini).
1935
+ set-base-url <url> Override the OpenAI-compatible base URL.
1936
+
1937
+ Env equivalents (read from ~/.svamp/.env or the daemon's environment):
1938
+ OPENAI_API_KEY / WISE_AGENT_API_KEY \xB7 OPENAI_BASE_URL / WISE_AGENT_BASE_URL
1939
+ WISE_AGENT_PROVIDER \xB7 WISE_AGENT_MODEL \xB7 SVAMP_HYPHA_PROXY_URL \xB7 HYPHA_TOKEN
1940
+
1941
+ Persisted to ~/.svamp/.env. Run \`svamp daemon restart\` afterwards to apply.
1942
+ If none is set, hitting a WISE Agent channel returns a clear "not configured" error.`);
1943
+ return;
1944
+ }
1945
+ if (rest[0] && rest[0] !== "auth") {
1946
+ console.error("Usage: svamp wise-agent auth <status|use-openai|use-hypha-proxy|use-claude-haiku|set-model|set-base-url>");
1947
+ process.exitCode = 1;
1948
+ return;
1949
+ }
1950
+ const authArgs = rest.slice(1);
1951
+ const mod = await import('./auth-Ctsyrv9N.mjs');
1952
+ let action;
1953
+ try {
1954
+ action = mod.parseWiseAgentAuthArgs(authArgs);
1955
+ } catch (e) {
1956
+ console.error(e?.message || String(e));
1957
+ process.exitCode = 1;
1958
+ return;
1959
+ }
1960
+ if (action) {
1961
+ const { updateEnvFile } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.ak; });
1962
+ const updates = mod.buildWiseAgentEnvUpdates(action);
1963
+ updateEnvFile(updates);
1964
+ for (const [k, v] of Object.entries(updates)) {
1965
+ if (v === void 0) delete process.env[k];
1966
+ else process.env[k] = v;
1967
+ }
1968
+ console.log("Updated ~/.svamp/.env. Run `svamp daemon restart` to apply.");
1969
+ }
1970
+ const status = mod.describeWiseAgentAuth(process.env);
1971
+ console.log(`WISE Agent: provider=${status.provider} model=${status.model} base=${status.baseUrl} key=${status.keyRedacted}`);
1972
+ }
1973
+ async function handleDaemonAuthCommand(argv) {
1974
+ const sub = (argv[0] || "status").toLowerCase();
1975
+ const mod = await import('./run-b-b_szy6.mjs').then(function (n) { return n.ak; });
1976
+ if (sub === "--help" || sub === "-h" || sub === "help") {
1977
+ console.log(`
1978
+ svamp daemon auth \u2014 Configure how Claude subprocesses authenticate
1979
+
1980
+ Subcommands:
1981
+ svamp daemon auth Show current mode + base URL + redacted API key
1982
+ svamp daemon auth use-hypha-proxy <URL>
1983
+ Route Claude through the Hypha LLM proxy using HYPHA_TOKEN.
1984
+ URL is REQUIRED \u2014 there is no hardcoded default. Use
1985
+ https://proxy.hypha.aicell.io for the hypha-cloud cluster, or
1986
+ your own deployment's proxy. Persisted as SVAMP_HYPHA_PROXY_URL.
1987
+ Token is resolved live at each spawn, so a
1988
+ \`svamp login\` refresh takes effect immediately.
1989
+ svamp daemon auth use-login Use ~/.claude credentials written by \`claude login\`.
1990
+ Removes managed ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY.
1991
+ svamp daemon auth set <URL> <KEY> Use a custom Anthropic gateway. URL must NOT end
1992
+ in /v1 (the SDK appends it automatically).
1993
+
1994
+ All mutations are persisted to ~/.svamp/.env. Run \`svamp daemon restart\` afterwards
1995
+ (or use the equivalent flags on \`svamp daemon start/restart\` to configure + restart in
1996
+ one step: --use-hypha-proxy / --use-claude-login / --anthropic-base-url URL --anthropic-api-key KEY).
1997
+ `);
1998
+ return;
1999
+ }
2000
+ if (sub === "status" || sub === "show") {
2001
+ const s = mod.getClaudeAuthStatus();
2002
+ console.log(`Claude auth mode: ${s.mode}`);
2003
+ if (s.baseUrl) console.log(` ANTHROPIC_BASE_URL: ${s.baseUrl}`);
2004
+ if (s.apiKeyPreview) console.log(` ANTHROPIC_API_KEY : ${s.apiKeyPreview}`);
2005
+ if (s.mode === "login") {
2006
+ console.log(' (Claude subprocesses will use credentials from ~/.claude \u2014 run "claude login" to set them.)');
2007
+ }
2008
+ if (s.hyphaProxyUrlMissing) {
2009
+ console.log(` WARNING: mode=hypha but no proxy URL configured \u2014 run "svamp daemon auth use-hypha-proxy <URL>" (e.g. ${mod.EXAMPLE_HYPHA_PROXY_URL}).`);
2010
+ }
2011
+ if (s.hyphaTokenMissing) {
2012
+ console.log(' WARNING: mode=hypha but HYPHA_TOKEN is not set \u2014 run "svamp login" first.');
2013
+ }
2014
+ return;
2015
+ }
2016
+ if (sub === "use-hypha-proxy") {
2017
+ const url = argv[1] && !argv[1].startsWith("-") ? argv[1] : void 0;
2018
+ if (!url) {
2019
+ console.error("Usage: svamp daemon auth use-hypha-proxy <URL>");
2020
+ console.error(` A proxy URL is required (no default). Example: svamp daemon auth use-hypha-proxy ${mod.EXAMPLE_HYPHA_PROXY_URL}`);
2021
+ process.exit(1);
2022
+ }
2023
+ mod.setClaudeAuthHyphaProxy(url);
2024
+ console.log(`Claude auth set to hypha-proxy (${url}). Run "svamp daemon restart" to apply.`);
2025
+ return;
2026
+ }
2027
+ if (sub === "use-login" || sub === "use-claude-login") {
2028
+ mod.setClaudeAuthLogin();
2029
+ console.log('Claude auth set to login (~/.claude credentials). Run "svamp daemon restart" to apply.');
2030
+ return;
2031
+ }
2032
+ if (sub === "set") {
2033
+ const baseUrl = argv[1];
2034
+ const apiKey = argv[2];
2035
+ if (!baseUrl || !apiKey) {
2036
+ console.error("Usage: svamp daemon auth set <base-url> <api-key>");
2037
+ process.exit(1);
2038
+ }
2039
+ mod.setClaudeAuthCustom(baseUrl, apiKey);
2040
+ console.log(`Claude auth set to custom (${baseUrl}). Run "svamp daemon restart" to apply.`);
2041
+ return;
2042
+ }
2043
+ console.error(`Unknown subcommand: svamp daemon auth ${sub}`);
2044
+ console.error("Available: status, use-hypha-proxy, use-login, set <URL> <KEY>");
2045
+ process.exit(1);
2046
+ }
2047
+ async function handleDaemonCodexAuthCommand(argv) {
2048
+ const sub = (argv[0] || "status").toLowerCase();
2049
+ const { updateEnvFile } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.ak; });
2050
+ const { resolveCodexProvider } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.al; });
2051
+ const redact = (k) => k && k.length > 10 ? `${k.slice(0, 6)}\u2026${k.slice(-4)}` : k ? "***" : "(unset)";
2052
+ if (sub === "--help" || sub === "-h" || sub === "help") {
2053
+ console.log(`
2054
+ svamp daemon codex-auth \u2014 Configure how Codex authenticates (parallels \`svamp daemon auth\`)
2055
+
2056
+ svamp daemon codex-auth Show current Codex auth (provider / native)
2057
+ svamp daemon codex-auth set <BASE_URL> <KEY> [MODEL]
2058
+ Use a custom OpenAI-compatible gateway (e.g. the
2059
+ Hypha proxy or a third-party endpoint). The gateway
2060
+ must speak the OpenAI Responses API. Persisted as
2061
+ SVAMP_CODEX_API_BASE / SVAMP_CODEX_API_KEY / SVAMP_CODEX_MODEL.
2062
+ svamp daemon codex-auth use-openai [KEY] \`codex login --with-api-key\` (arg or $OPENAI_API_KEY),
2063
+ and clear any custom gateway.
2064
+ svamp daemon codex-auth use-chatgpt \`codex login\` (ChatGPT OAuth \u2014 interactive), clear gateway.
2065
+ svamp daemon codex-auth clear Remove the custom gateway (fall back to ~/.codex/auth.json).
2066
+
2067
+ Persisted to ~/.svamp/.env. Run \`svamp daemon restart\` afterwards to apply.
2068
+ The generic LLM_API_BASE / LLM_API_KEY / LLM_MODEL env vars are also honored if already present.
2069
+ `);
2070
+ return;
2071
+ }
2072
+ if (sub === "status" || sub === "show") {
2073
+ const p = resolveCodexProvider(process.env);
2074
+ const { existsSync } = await import('node:fs');
2075
+ const nativeAuth = existsSync(`${process.env.HOME}/.codex/auth.json`);
2076
+ if (p.apiBase && p.apiKey) {
2077
+ console.log("Codex auth: custom OpenAI-compatible gateway");
2078
+ console.log(` base_url : ${p.apiBase}`);
2079
+ console.log(` api_key : ${redact(p.apiKey)}`);
2080
+ console.log(` model : ${p.model ?? "(codex default)"}`);
2081
+ } else {
2082
+ console.log(`Codex auth: native (${nativeAuth ? "~/.codex/auth.json present" : "NOT logged in \u2014 run codex-auth use-openai / use-chatgpt"})`);
2083
+ }
2084
+ return;
2085
+ }
2086
+ if (sub === "set") {
2087
+ const [baseUrl, apiKey, model] = [argv[1], argv[2], argv[3]];
2088
+ if (!baseUrl || !apiKey) {
2089
+ console.error("Usage: svamp daemon codex-auth set <base-url> <api-key> [model]");
2090
+ console.error(" e.g. svamp daemon codex-auth set https://tokenfans.ai/v1 sk-... gpt-5.5");
2091
+ process.exit(1);
2092
+ }
2093
+ updateEnvFile({ SVAMP_CODEX_API_BASE: baseUrl, SVAMP_CODEX_API_KEY: apiKey, SVAMP_CODEX_MODEL: model || "" });
2094
+ console.log(`Codex auth set to custom gateway (${baseUrl}${model ? `, model=${model}` : ""}). Run "svamp daemon restart" to apply.`);
2095
+ return;
2096
+ }
2097
+ if (sub === "use-openai") {
2098
+ const key = argv[1] || process.env.OPENAI_API_KEY;
2099
+ if (!key) {
2100
+ console.error("Usage: svamp daemon codex-auth use-openai <OPENAI_API_KEY> (or set $OPENAI_API_KEY)");
2101
+ process.exit(1);
2102
+ }
2103
+ const { spawnSync } = await import('node:child_process');
2104
+ const r = spawnSync("codex", ["login", "--with-api-key"], { input: key, encoding: "utf8", stdio: ["pipe", "inherit", "inherit"] });
2105
+ if (r.status !== 0) {
2106
+ console.error("codex login failed \u2014 is codex installed? (`npm i -g @openai/codex`)");
2107
+ process.exit(1);
2108
+ }
2109
+ updateEnvFile({ SVAMP_CODEX_API_BASE: "", SVAMP_CODEX_API_KEY: "", SVAMP_CODEX_MODEL: "" });
2110
+ console.log("Codex authenticated via OpenAI API key (~/.codex/auth.json); cleared any custom gateway.");
2111
+ return;
2112
+ }
2113
+ if (sub === "use-chatgpt" || sub === "use-login") {
2114
+ const { spawnSync } = await import('node:child_process');
2115
+ console.log("Launching `codex login` (ChatGPT OAuth \u2014 follow the browser prompt)\u2026");
2116
+ const r = spawnSync("codex", ["login"], { stdio: "inherit" });
2117
+ if (r.status !== 0) {
2118
+ console.error("codex login failed.");
2119
+ process.exit(1);
2120
+ }
2121
+ updateEnvFile({ SVAMP_CODEX_API_BASE: "", SVAMP_CODEX_API_KEY: "", SVAMP_CODEX_MODEL: "" });
2122
+ console.log("Codex authenticated via ChatGPT; cleared any custom gateway.");
2123
+ return;
2124
+ }
2125
+ if (sub === "clear") {
2126
+ updateEnvFile({ SVAMP_CODEX_API_BASE: "", SVAMP_CODEX_API_KEY: "", SVAMP_CODEX_MODEL: "" });
2127
+ console.log('Cleared the custom Codex gateway (falls back to ~/.codex/auth.json). Run "svamp daemon restart".');
2128
+ return;
2129
+ }
2130
+ console.error(`Unknown subcommand: svamp daemon codex-auth ${sub}`);
2131
+ console.error("Available: status, set <URL> <KEY> [MODEL], use-openai [KEY], use-chatgpt, clear");
2132
+ process.exit(1);
2133
+ }
2134
+ function printSessionHelp() {
2135
+ console.log(`
2136
+ svamp session \u2014 Spawn and manage AI agent sessions
2137
+
2138
+ QUICK START \u2014 Spawn a session, give it a task, get results:
2139
+
2140
+ # One-liner: spawn agent, send task, wait for completion (autonomous by default)
2141
+ svamp session spawn claude -d ./my-project --message "fix the failing tests" --wait
2142
+
2143
+ # The command prints the session ID (e.g., "abc12345-..."). Use it to:
2144
+ svamp session messages abc1 --last 10 # read output (prefix match on ID)
2145
+ svamp session send abc1 "now run lint" --wait # send follow-up, wait for done
2146
+ svamp session archive abc1 # archive when finished (resumable)
2147
+ svamp session resume abc1 # bring an archived session back
2148
+
2149
+ # Spawn on a remote/cloud machine:
2150
+ svamp session spawn claude -d /workspace -m cloud-box --message "deploy" --wait
2151
+
2152
+ AGENTS:
2153
+ claude Claude Code \u2014 full coding agent (default)
2154
+ gemini Google Gemini via ACP protocol
2155
+ codex OpenAI Codex via app-server (streaming, resume, custom providers)
2156
+
2157
+ Usage: svamp session spawn <agent> [options]
2158
+ The <agent> argument is required. Use "claude" if unsure.
2159
+
2160
+ COMMANDS:
2161
+
2162
+ Lifecycle:
2163
+ spawn <agent> [-d <path>] [--model <id>] [options] Spawn a new agent session
2164
+ (--model e.g. claude-fable-5, claude-opus-4-8)
2165
+ archive <id> Archive a session (resumable; preserves --resume token)
2166
+ resume <id> Resume an archived session (spawns agent with --resume)
2167
+ delete <id> Permanently delete a session (cannot be undone)
2168
+ list [--active] [--json] List sessions (alias: ls)
2169
+ machines List discoverable machines
2170
+ info <id> [--json] Show status & pending permissions
2171
+
2172
+ Discover:
2173
+ whoami [--json] Your environment: this session, same-machine peers,
2174
+ same-user sessions on other machines, and how to reach them (alias: env)
2175
+
2176
+ Communicate:
2177
+ send <id> <message> [--when-idle] [--wait] [--response] [--btw] [--require-approval] [--timeout N] [--json] [--urgency urgent|normal]
2178
+ Send a message to a running session.
2179
+ By default this is URGENT: it wakes the recipient so they
2180
+ handle it promptly. Pass --when-idle to defer instead \u2014
2181
+ the message lands in the peer's inbox and is processed on
2182
+ their next idle turn (no interrupt). Loop safeguard: a send
2183
+ issued from a turn that was itself triggered by an inbox
2184
+ message is always forced non-urgent (no recursive wake
2185
+ cascades); the daemon's per-sender rate limit + circuit
2186
+ breaker further bound any storm.
2187
+ --when-idle defer: handle on the recipient's
2188
+ next idle turn (opposite of the
2189
+ urgent default)
2190
+ --wait block until the agent goes idle
2191
+ --response block AND print the agent's
2192
+ text answer (implies --wait)
2193
+ --btw side-channel one-shot query via
2194
+ /btw \u2014 does NOT touch the
2195
+ running session's context window
2196
+ --require-approval do NOT pre-flight the target to
2197
+ bypassPermissions. Without this,
2198
+ send with --wait/--response auto-
2199
+ ensures bypass so the agent does
2200
+ not stall on tool prompts.
2201
+ query <directory> <prompt> [--timeout N] [--keep] [--json] [--permission-mode MODE] [--tag NAME]
2202
+ Stateless one-shot: spawn fresh Claude in
2203
+ <directory>, send <prompt>, print answer,
2204
+ delete the session. No existing session is
2205
+ affected. Use for reproducible Q&A.
2206
+ messages <id> [--last N] [--json] Read message history (alias: msgs)
2207
+ wait <id> [--timeout N] [--json] Wait for agent to become idle
2208
+ attach <id> Attach interactive terminal (stdin/stdout)
2209
+
2210
+ Permissions (when agent pauses for approval):
2211
+ approve <id> [request-id] [--json] Approve pending tool permission(s)
2212
+ deny <id> [request-id] [--json] Deny pending tool permission(s)
2213
+
2214
+ Sharing:
2215
+ share <id> --list List shared users
2216
+ share <id> --add <email>[:<role>] Share with user (role: view|interact|admin)
2217
+ share <id> --remove <email> Remove shared user
2218
+ share <id> --public <view|interact|off> Set public link access
2219
+
2220
+ Loop (self-verifying gate \u2014 iterate/gate until criteria are met):
2221
+ loop <id> ["<task>"] --until "<criteria>" [opts]
2222
+ Attach a Stop gate. WITH a task \u2192 kick the agent
2223
+ off and gate until done. WITHOUT (only --until) \u2192
2224
+ HOT-PLUG onto the running session (no kickoff).
2225
+ opts: --oracle "cmd" --agent --parent <id> --max N
2226
+ loop-cancel <id> Cancel the loop / release the gate
2227
+ loop-status <id> Show loop phase + iteration + extensions
2228
+ loop-extend <id> --reason "<why>" Extend the SOFT cap (+turns, bounded) with a reason
2229
+
2230
+ Inbox:
2231
+ inbox send <id> "<body>" [opts] Send a message to session inbox
2232
+ inbox list <id> [--unread] [--json] List inbox messages
2233
+ inbox read <id> <msg-id> Read and mark a message
2234
+ inbox reply <id> <msg-id> "<body>" Reply to sender's session
2235
+ inbox clear <id> [--all] Clear read (or all) messages
2236
+
2237
+ SPAWN OPTIONS:
2238
+ -d, --directory <path> Working directory (REQUIRED for meaningful work)
2239
+ -p, --permission-mode <mode> Permission mode (see below). Default: "bypassPermissions"
2240
+ --require-approval Opt into human-in-the-loop approval (alias for -p default).
2241
+ Without this flag, the agent runs autonomously.
2242
+ --message <msg> Send this message to the agent immediately after spawn
2243
+ --wait Block until agent finishes (idle). Combine with --message
2244
+ for fire-and-forget: spawn \u2192 task \u2192 wait \u2192 done.
2245
+ --timeout <sec> Timeout for --wait (default: no timeout)
2246
+ --worktree Create isolated git worktree branch (.dev/worktree/<name>)
2247
+ --tags <tag1,tag2> Comma-separated tags for session grouping
2248
+ --parent <sessionId> Set parent session (auto-set from SVAMP_SESSION_ID env)
2249
+
2250
+ PERMISSION MODES:
2251
+ bypassPermissions \u2605 Default. Auto-approve everything. No prompts, no pauses.
2252
+ Right choice for automation, CI, agent-spawning-agent.
2253
+ acceptEdits Auto-approve file edits. Still pauses for bash & dangerous tools.
2254
+ default \u26A0 Agent PAUSES and waits for human approval before each tool use.
2255
+ The agent will be stuck (exit code 2) until you run approve/deny.
2256
+ Use only when a human is actively watching the session.
2257
+
2258
+ Tip: pass --require-approval (or -p default) when you want a human gate.
2259
+
2260
+ PERMISSION WORKFLOW (only relevant for "default" and "acceptEdits" modes):
2261
+ 1. svamp session wait <id> --json \u2192 exit code 2 means permission pending
2262
+ 2. svamp session info <id> --json \u2192 shows pendingPermissions array
2263
+ 3. svamp session approve <id> \u2192 approve all pending (or approve <id> <rid>)
2264
+ 4. svamp session deny <id> \u2192 deny all pending (or deny <id> <rid>)
2265
+
2266
+ ISOLATION & SHARING OPTIONS (for spawn):
2267
+ --isolate Force OS-level sandbox (even without sharing)
2268
+ --security-context <path> Apply security context config (JSON file)
2269
+ --share <email> Share session with user (repeatable). Added users have full access; the legacy :role suffix is accepted but ignored.
2270
+ --deny-network Block all network access
2271
+ --deny-read <path> Deny reading path (repeatable)
2272
+ --allow-write <path> Allow writing path (repeatable)
2273
+ --allow-domain <domain> Allow network domain (repeatable)
2274
+
2275
+ MESSAGES OPTIONS:
2276
+ --last N Show last N messages only
2277
+ --after N Start after sequence number N (for pagination)
2278
+ --limit N Max messages to fetch (default: 1000)
2279
+ --json Output as JSON array of {id, seq, role, text, createdAt}
2280
+ --raw With --json: include full raw objects (tool_use, tool_result, thinking)
2281
+
2282
+ LOOP OPTIONS (for loop):
2283
+ --until <text> Success criteria \u2014 how we'll know it's done (the gate's goal)
2284
+ --oracle <cmd> Pass/fail command (tests/build/lint) the gate runs each iteration
2285
+ --agent Independent evaluator judge (on by default)
2286
+ --parent <id> Route the verdict to a lead session (crew)
2287
+ --max <n> Max iterations before giving up (default: 20)
2288
+ --evaluator <on|off> Independent evaluator that judges "done" (default: on)
2289
+ (omit the task and pass only --until to HOT-PLUG the gate onto a running session)
2290
+
2291
+ EXIT CODES (for wait, send --wait, spawn --wait):
2292
+ 0 Agent is idle (task completed successfully)
2293
+ 1 Error (timeout, connection failure, session not found)
2294
+ 2 Agent is waiting for permission approval \u2014 use approve/deny to unblock
2295
+
2296
+ ATTACH COMMANDS (inside an attached session):
2297
+ /quit, /detach Detach (session keeps running)
2298
+ /abort, /cancel Cancel current agent turn
2299
+ /kill Stop the session entirely
2300
+ /info Show session status
2301
+
2302
+ GLOBAL OPTIONS:
2303
+ --machine <id>, -m <id> Target a specific machine (prefix match supported)
2304
+
2305
+ ID MATCHING:
2306
+ Session and machine IDs support prefix matching.
2307
+ "abc1" matches "abc12345-6789-..." \u2014 you only need enough characters to be unique.
2308
+
2309
+ EXAMPLES:
2310
+
2311
+ # === Common: Spawn agent to do a task and wait ===
2312
+ # Default is bypassPermissions \u2014 no -p flag needed for autonomous runs.
2313
+ svamp session spawn claude -d ./my-project \\
2314
+ --message "fix all TypeScript errors, run tsc to verify" --wait
2315
+
2316
+ # === Multi-step: Spawn, then send messages interactively ===
2317
+ ID=$(svamp session spawn claude -d ./proj)
2318
+ svamp session send $ID "first, read the README" --wait
2319
+ svamp session send $ID "now implement the feature described there" --wait
2320
+ svamp session messages $ID --last 20
2321
+
2322
+ # === Spawn child session from another agent (e.g., in a bash tool) ===
2323
+ # bypassPermissions is the default; --wait blocks until done.
2324
+ svamp session spawn claude -d /tmp/test-project \\
2325
+ --message "run the test suite and report results" --wait
2326
+ svamp session messages <child-id> --last 5
2327
+
2328
+ # === Human-in-the-loop: require approval for every tool ===
2329
+ svamp session spawn claude -d ./proj --require-approval \\
2330
+ --message "refactor auth module"
2331
+ svamp session wait abc1 --json # exit code 2 = permission pending
2332
+ svamp session approve abc1 # approve all pending
2333
+ svamp session wait abc1 # wait for completion
2334
+
2335
+ # === Spawn on a remote machine ===
2336
+ svamp session spawn claude -d /workspace -m my-cloud-box \\
2337
+ --message "deploy to staging" --wait
2338
+
2339
+ # === Isolated session with network restrictions ===
2340
+ svamp session spawn claude -d /tmp/sandbox --isolate --deny-network
2341
+
2342
+ # === Loop: self-verifying gate (task \u2192 kickoff; --until only \u2192 hot-plug) ===
2343
+ svamp session loop abc1 "Fix all linting errors" \\
2344
+ --until "eslint reports zero errors" --oracle "npm run lint" --max 15
2345
+ svamp session loop abc1 --until "all tests pass" --oracle "npm test" # hot-plug onto a running session
2346
+ `);
2347
+ }
2348
+ function printMachineHelp() {
2349
+ console.log(`
2350
+ svamp machine \u2014 Machine management
2351
+
2352
+ Usage:
2353
+ svamp machine info Show machine info & status
2354
+ svamp machine exec <command> [--cwd <path>] Run command on one machine
2355
+ svamp machine exec <command> --all Run on EVERY machine (fan-out)
2356
+ svamp machine ls [path] [--hidden] List directory on machine
2357
+ svamp machine share --list List shared users
2358
+ svamp machine share --add <email>[:<role>] Share machine with user
2359
+ svamp machine share --remove <email> Remove shared user
2360
+ svamp machine share --config <path> Apply security context config
2361
+ svamp machine share --show-config Show current security context config
2362
+
2363
+ Options:
2364
+ --machine <id>, -m <id> Target a specific machine (prefix match supported)
2365
+ --cwd <path> Working directory for exec (default: home dir)
2366
+ --hidden, -a Show hidden files in ls
2367
+
2368
+ Examples:
2369
+ svamp machine info
2370
+ svamp machine exec "ls -la /tmp"
2371
+ svamp machine exec "df -h" -m cloud-box --cwd /
2372
+ svamp machine exec "uptime" --all
2373
+ svamp machine ls /home/user/projects
2374
+ svamp machine ls --hidden -m cloud-box
2375
+ svamp machine share --add alice@example.com:admin
2376
+ svamp machine share --config ./security-context.json
2377
+ `);
2378
+ }
2379
+ async function printSkillsHelp() {
2380
+ let browseUrl = "<HYPHA_SERVER_URL>/<workspace>/artifacts/marketplace (set HYPHA_SERVER_URL)";
2381
+ try {
2382
+ const { getArtifactBaseUrl, getSkillsCollectionName } = await import('./run-b-b_szy6.mjs').then(function (n) { return n.at; });
2383
+ browseUrl = `${getArtifactBaseUrl()}/${getSkillsCollectionName()}`;
2384
+ } catch {
2385
+ }
2386
+ console.log(`
2387
+ svamp skills \u2014 Agent skills marketplace
2388
+
2389
+ Usage:
2390
+ svamp skills find <query> [--json] Search for skills in the marketplace
2391
+ svamp skills install <name> [--force] Install skill to ~/.claude/skills/<name>/
2392
+ svamp skills list List locally installed skills
2393
+ svamp skills remove <name> Remove an installed skill
2394
+ svamp skills publish <path> [--version <tag>] Publish a skill to the marketplace
2395
+
2396
+ Skills are stored in ~/.claude/skills/ and automatically detected by Claude Code.
2397
+ Each skill contains a SKILL.md file with instructions and metadata.
2398
+ Skills use git-backed storage \u2014 you can also clone and push via git.
2399
+
2400
+ Browse online: ${browseUrl}
2401
+
2402
+ Examples:
2403
+ svamp skills find "code review"
2404
+ svamp skills install code-review
2405
+ svamp skills publish ./my-skill/
2406
+ svamp skills publish ./my-skill/ --version v1.0
2407
+ `);
2408
+ }
2409
+ function printInteractiveHelp() {
2410
+ console.log(`
2411
+ svamp \u2014 Interactive Claude session with cloud sync
2412
+
2413
+ Usage:
2414
+ svamp [-d <path>] [--resume <id>] [--continue] [--permission-mode <mode>] [-- <claude-args>]
2415
+ svamp start [-d <path>] [...]
2416
+
2417
+ Options:
2418
+ -d, --directory <path> Working directory (default: cwd)
2419
+ -r, --resume <id> Resume a Claude session by ID
2420
+ -c, --continue Continue the last Claude session
2421
+ -p, --permission-mode <m> Permission mode: default, acceptEdits, bypassPermissions, plan
2422
+ -- Pass remaining args to Claude CLI
2423
+
2424
+ Modes:
2425
+ Local mode Full interactive Claude terminal UI (default)
2426
+ Remote mode Processes messages from the web app
2427
+
2428
+ Switching:
2429
+ When a message arrives from the web app \u2192 automatically switches to remote mode
2430
+ Space Space Switch from remote mode back to local mode
2431
+ Ctrl-C Ctrl-C Exit from remote mode
2432
+
2433
+ The session is synced to Hypha Cloud so it's visible in the web app.
2434
+ Works offline if no Hypha credentials are configured.
2435
+ `);
2436
+ }
2437
+ function printAgentHelp() {
2438
+ console.log(`
2439
+ svamp agent \u2014 Interactive agent sessions (local, no daemon required)
2440
+
2441
+ Usage:
2442
+ svamp agent list List known agents
2443
+ svamp agent <name> [-d <path>] Start interactive agent session
2444
+ svamp agent -- <cmd> [args] Start custom ACP agent
2445
+
2446
+ Examples:
2447
+ svamp agent gemini Start Gemini agent in current directory
2448
+ svamp agent gemini -d /tmp/proj Start Gemini agent in /tmp/proj
2449
+ svamp agent codex Start Codex agent
2450
+ svamp agent -- mycli --acp Start custom ACP-compatible agent
2451
+
2452
+ Known agents: gemini (ACP), codex (MCP)
2453
+ `);
2454
+ }
2455
+ main().catch((err) => {
2456
+ console.error("Fatal error:", err);
2457
+ process.exit(1);
2458
+ });
2459
+
2460
+ export { flushAndExit as f };