svamp-cli 0.2.223 → 0.2.225

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