svamp-cli 0.1.87 → 0.1.89

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 ADDED
@@ -0,0 +1,1535 @@
1
+ import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-71Ox10lO.mjs';
2
+ import 'os';
3
+ import 'fs/promises';
4
+ import 'fs';
5
+ import 'path';
6
+ import 'url';
7
+ import 'child_process';
8
+ import 'crypto';
9
+ import 'node:fs';
10
+ import 'node:crypto';
11
+ import 'node:path';
12
+ import 'node:child_process';
13
+ import '@agentclientprotocol/sdk';
14
+ import 'node:os';
15
+ import '@modelcontextprotocol/sdk/client/index.js';
16
+ import '@modelcontextprotocol/sdk/client/stdio.js';
17
+ import '@modelcontextprotocol/sdk/types.js';
18
+ import 'zod';
19
+ import 'node:fs/promises';
20
+ import 'node:util';
21
+
22
+ const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
23
+ if (nodeMajor < 22) {
24
+ console.error(`Error: svamp requires Node.js >= 22 (you have ${process.version}).`);
25
+ console.error(" Node 22+ includes native WebSocket support needed by hypha-rpc.");
26
+ console.error(" Upgrade with: nvm install 22 && nvm alias default 22");
27
+ process.exit(1);
28
+ }
29
+ const args = process.argv.slice(2);
30
+ const subcommand = args[0];
31
+ let daemonSubcommand = args[1];
32
+ async function main() {
33
+ if (subcommand === "login") {
34
+ await loginToHypha();
35
+ } else if (subcommand === "logout") {
36
+ await logoutFromHypha();
37
+ } else if (subcommand === "daemon") {
38
+ if (daemonSubcommand === "restart") {
39
+ await stopDaemon();
40
+ daemonSubcommand = "start";
41
+ }
42
+ if (daemonSubcommand === "start") {
43
+ const { spawn } = await import('child_process');
44
+ const extraArgs = [];
45
+ if (args.includes("--no-auto-continue")) extraArgs.push("--no-auto-continue");
46
+ const child = spawn(process.execPath, [
47
+ "--no-warnings",
48
+ "--no-deprecation",
49
+ ...process.argv.slice(1, 2),
50
+ // the script path
51
+ "daemon",
52
+ "start-supervised",
53
+ ...extraArgs
54
+ ], {
55
+ detached: true,
56
+ stdio: "ignore",
57
+ env: process.env
58
+ });
59
+ child.unref();
60
+ const { existsSync } = await import('fs');
61
+ const { join } = await import('path');
62
+ const os = await import('os');
63
+ const stateFile = join(
64
+ process.env.SVAMP_HOME || join(os.homedir(), ".svamp"),
65
+ "daemon.state.json"
66
+ );
67
+ let started = false;
68
+ for (let i = 0; i < 100; i++) {
69
+ await new Promise((r) => setTimeout(r, 100));
70
+ if (existsSync(stateFile)) {
71
+ started = true;
72
+ break;
73
+ }
74
+ }
75
+ if (started) {
76
+ console.log("Daemon started successfully");
77
+ } else {
78
+ console.error("Failed to start daemon (timeout waiting for state file)");
79
+ process.exit(1);
80
+ }
81
+ process.exit(0);
82
+ } else if (daemonSubcommand === "start-supervised") {
83
+ const { spawn: spawnChild } = await import('child_process');
84
+ const { appendFileSync, mkdirSync, existsSync: fsExists } = await import('fs');
85
+ const { join: pathJoin } = await import('path');
86
+ const osModule = await import('os');
87
+ const svampHome = process.env.SVAMP_HOME || pathJoin(osModule.homedir(), ".svamp");
88
+ const logsDir = pathJoin(svampHome, "logs");
89
+ mkdirSync(logsDir, { recursive: true });
90
+ const logFile = pathJoin(logsDir, "daemon-supervised.log");
91
+ const log = (msg) => {
92
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [supervisor] ${msg}
93
+ `;
94
+ try {
95
+ appendFileSync(logFile, line);
96
+ } catch {
97
+ }
98
+ };
99
+ const supervisorPidFile = pathJoin(svampHome, "supervisor.pid");
100
+ try {
101
+ appendFileSync(supervisorPidFile, "");
102
+ } catch {
103
+ }
104
+ const { writeFileSync: wfs } = await import('fs');
105
+ wfs(supervisorPidFile, String(process.pid), "utf-8");
106
+ const extraSyncArgs = [];
107
+ if (args.includes("--no-auto-continue")) extraSyncArgs.push("--no-auto-continue");
108
+ const BASE_DELAY_MS = 2e3;
109
+ const MAX_DELAY_MS = 3e5;
110
+ const BACKOFF_RESET_UPTIME_MS = 6e4;
111
+ let consecutiveRapidCrashes = 0;
112
+ let currentChild = null;
113
+ let stopping = false;
114
+ const onSignal = (sig) => {
115
+ stopping = true;
116
+ if (currentChild && !currentChild.killed) {
117
+ currentChild.kill(sig);
118
+ }
119
+ };
120
+ process.on("SIGTERM", () => onSignal("SIGTERM"));
121
+ process.on("SIGINT", () => onSignal("SIGINT"));
122
+ process.on("SIGUSR1", () => onSignal("SIGUSR1"));
123
+ log("Supervisor started");
124
+ while (!stopping) {
125
+ const startTime = Date.now();
126
+ const exitCode = await new Promise((resolve) => {
127
+ const child = spawnChild(process.execPath, [
128
+ "--no-warnings",
129
+ "--no-deprecation",
130
+ ...process.argv.slice(1, 2),
131
+ "daemon",
132
+ "start-sync",
133
+ ...extraSyncArgs
134
+ ], {
135
+ stdio: ["ignore", "inherit", "inherit"],
136
+ env: { ...process.env, SVAMP_SUPERVISED: "1" }
137
+ });
138
+ currentChild = child;
139
+ child.on("exit", (code) => resolve(code));
140
+ child.on("error", (err) => {
141
+ log(`Failed to spawn daemon: ${err.message}`);
142
+ resolve(1);
143
+ });
144
+ });
145
+ currentChild = null;
146
+ const uptime = Date.now() - startTime;
147
+ if (stopping) {
148
+ log("Supervisor received stop signal, exiting");
149
+ break;
150
+ }
151
+ if (exitCode === 0) {
152
+ log("Daemon exited cleanly (exit 0), not restarting");
153
+ break;
154
+ }
155
+ if (uptime > BACKOFF_RESET_UPTIME_MS) {
156
+ consecutiveRapidCrashes = 0;
157
+ } else {
158
+ consecutiveRapidCrashes++;
159
+ }
160
+ const backoffExp = Math.min(consecutiveRapidCrashes, 10);
161
+ let delay = Math.min(BASE_DELAY_MS * Math.pow(2, backoffExp), MAX_DELAY_MS);
162
+ delay = Math.max(BASE_DELAY_MS, Math.round(delay + (Math.random() * 0.2 - 0.1) * delay));
163
+ log(`Daemon exited with code ${exitCode} (uptime=${Math.round(uptime / 1e3)}s). Restarting in ${Math.round(delay / 1e3)}s...`);
164
+ await new Promise((resolve) => {
165
+ const timer = setTimeout(resolve, delay);
166
+ const checkStop = setInterval(() => {
167
+ if (stopping) {
168
+ clearTimeout(timer);
169
+ clearInterval(checkStop);
170
+ resolve();
171
+ }
172
+ }, 500);
173
+ setTimeout(() => clearInterval(checkStop), delay + 100);
174
+ });
175
+ }
176
+ try {
177
+ const { unlinkSync: us } = await import('fs');
178
+ us(supervisorPidFile);
179
+ } catch {
180
+ }
181
+ process.exit(0);
182
+ } else if (daemonSubcommand === "start-sync") {
183
+ const noAutoContinue = args.includes("--no-auto-continue");
184
+ await startDaemon({ noAutoContinue });
185
+ process.exit(0);
186
+ } else if (daemonSubcommand === "stop") {
187
+ const cleanup = args.includes("--cleanup");
188
+ await stopDaemon({ cleanup });
189
+ process.exit(0);
190
+ } else if (daemonSubcommand === "status") {
191
+ daemonStatus();
192
+ process.exit(0);
193
+ } else if (daemonSubcommand === "install") {
194
+ await installDaemonService();
195
+ process.exit(0);
196
+ } else if (daemonSubcommand === "uninstall") {
197
+ await uninstallDaemonService();
198
+ process.exit(0);
199
+ } else {
200
+ printDaemonHelp();
201
+ }
202
+ } else if (subcommand === "agent") {
203
+ await handleAgentCommand();
204
+ } else if (subcommand === "session") {
205
+ await handleSessionCommand();
206
+ } else if (subcommand === "machine") {
207
+ await handleMachineCommand();
208
+ } else if (subcommand === "skills") {
209
+ await handleSkillsCommand();
210
+ } else if (subcommand === "service" || subcommand === "svc") {
211
+ const { handleServiceCommand } = await import('./commands-BYbuedOK.mjs');
212
+ await handleServiceCommand();
213
+ } else if (subcommand === "process" || subcommand === "proc") {
214
+ const { processCommand } = await import('./commands-DT4zKcI_.mjs');
215
+ let machineId;
216
+ const processArgs = args.slice(1);
217
+ const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
218
+ if (mIdx !== -1 && mIdx + 1 < processArgs.length) {
219
+ machineId = processArgs[mIdx + 1];
220
+ }
221
+ await processCommand(processArgs.filter((_a, i) => {
222
+ if (processArgs[i] === "--machine" || processArgs[i] === "-m") return false;
223
+ if (i > 0 && (processArgs[i - 1] === "--machine" || processArgs[i - 1] === "-m")) return false;
224
+ return true;
225
+ }), machineId);
226
+ process.exit(0);
227
+ } else if (subcommand === "--help" || subcommand === "-h") {
228
+ printHelp();
229
+ } else if (!subcommand || subcommand === "start") {
230
+ await handleInteractiveCommand();
231
+ } else if (subcommand === "--version" || subcommand === "-v") {
232
+ const pkg = await import('./package-DP0KvJ1N.mjs').catch(() => ({ default: { version: "unknown" } }));
233
+ console.log(`svamp version: ${pkg.default.version}`);
234
+ } else {
235
+ console.error(`Unknown command: ${subcommand}`);
236
+ printHelp();
237
+ process.exit(1);
238
+ }
239
+ }
240
+ async function handleInteractiveCommand() {
241
+ const { runInteractive } = await import('./run-BU9OQRQs.mjs');
242
+ const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
243
+ let directory = process.cwd();
244
+ let resumeSessionId;
245
+ let continueSession = false;
246
+ let permissionMode;
247
+ const claudeArgs = [];
248
+ let pastSeparator = false;
249
+ for (let i = 0; i < interactiveArgs.length; i++) {
250
+ if (pastSeparator) {
251
+ claudeArgs.push(interactiveArgs[i]);
252
+ continue;
253
+ }
254
+ if (interactiveArgs[i] === "--") {
255
+ pastSeparator = true;
256
+ continue;
257
+ }
258
+ if ((interactiveArgs[i] === "-d" || interactiveArgs[i] === "--directory") && i + 1 < interactiveArgs.length) {
259
+ directory = interactiveArgs[++i];
260
+ } else if ((interactiveArgs[i] === "--resume" || interactiveArgs[i] === "-r") && i + 1 < interactiveArgs.length) {
261
+ resumeSessionId = interactiveArgs[++i];
262
+ } else if (interactiveArgs[i] === "--continue" || interactiveArgs[i] === "-c") {
263
+ continueSession = true;
264
+ } else if ((interactiveArgs[i] === "--permission-mode" || interactiveArgs[i] === "-p") && i + 1 < interactiveArgs.length) {
265
+ permissionMode = interactiveArgs[++i];
266
+ } else if (interactiveArgs[i] === "--help" || interactiveArgs[i] === "-h") {
267
+ printInteractiveHelp();
268
+ return;
269
+ }
270
+ }
271
+ await runInteractive({
272
+ directory,
273
+ resumeSessionId,
274
+ continueSession,
275
+ permissionMode,
276
+ claudeArgs: claudeArgs.length > 0 ? claudeArgs : void 0
277
+ });
278
+ }
279
+ async function handleAgentCommand() {
280
+ const agentArgs = args.slice(1);
281
+ if (agentArgs.length === 0 || agentArgs[0] === "--help" || agentArgs[0] === "-h") {
282
+ printAgentHelp();
283
+ return;
284
+ }
285
+ if (agentArgs[0] === "list") {
286
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-71Ox10lO.mjs').then(function (n) { return n.i; });
287
+ console.log("Known agents:");
288
+ for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
289
+ console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
290
+ }
291
+ for (const [name, config2] of Object.entries(KNOWN_MCP_AGENTS2)) {
292
+ console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (MCP)`);
293
+ }
294
+ console.log('\nUse "svamp agent <name>" to start an interactive session.');
295
+ console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
296
+ return;
297
+ }
298
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-71Ox10lO.mjs').then(function (n) { return n.i; });
299
+ let cwd = process.cwd();
300
+ const filteredArgs = [];
301
+ for (let i = 0; i < agentArgs.length; i++) {
302
+ if ((agentArgs[i] === "-d" || agentArgs[i] === "--directory") && i + 1 < agentArgs.length) {
303
+ cwd = agentArgs[i + 1];
304
+ i++;
305
+ } else {
306
+ filteredArgs.push(agentArgs[i]);
307
+ }
308
+ }
309
+ let config;
310
+ try {
311
+ config = resolveAcpAgentConfig(filteredArgs);
312
+ } catch (err) {
313
+ console.error(err.message);
314
+ process.exit(1);
315
+ }
316
+ const logFn = (...logArgs) => {
317
+ if (process.env.DEBUG) console.error("[debug]", ...logArgs);
318
+ };
319
+ console.log(`Starting ${config.agentName} agent in ${cwd}...`);
320
+ let backend;
321
+ if (KNOWN_MCP_AGENTS[config.agentName]) {
322
+ const { CodexMcpBackend } = await import('./run-71Ox10lO.mjs').then(function (n) { return n.j; });
323
+ backend = new CodexMcpBackend({ cwd, log: logFn });
324
+ } else {
325
+ const { AcpBackend } = await import('./run-71Ox10lO.mjs').then(function (n) { return n.h; });
326
+ const { GeminiTransport } = await import('./run-71Ox10lO.mjs').then(function (n) { return n.G; });
327
+ const { DefaultTransport } = await import('./run-71Ox10lO.mjs').then(function (n) { return n.D; });
328
+ const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
329
+ backend = new AcpBackend({
330
+ agentName: config.agentName,
331
+ cwd,
332
+ command: config.command,
333
+ args: config.args,
334
+ transportHandler,
335
+ log: logFn
336
+ });
337
+ }
338
+ let currentText = "";
339
+ backend.onMessage((msg) => {
340
+ switch (msg.type) {
341
+ case "model-output":
342
+ if (msg.textDelta) {
343
+ process.stdout.write(msg.textDelta);
344
+ currentText += msg.textDelta;
345
+ } else if (msg.fullText) {
346
+ process.stdout.write(msg.fullText);
347
+ }
348
+ break;
349
+ case "status":
350
+ if (msg.status === "idle") {
351
+ if (currentText && !currentText.endsWith("\n")) {
352
+ process.stdout.write("\n");
353
+ }
354
+ currentText = "";
355
+ process.stdout.write("\n> ");
356
+ } else if (msg.status === "error") {
357
+ console.error(`
358
+ Error: ${msg.detail}`);
359
+ } else if (msg.status === "stopped") {
360
+ console.log(`
361
+ Agent stopped: ${msg.detail || ""}`);
362
+ }
363
+ break;
364
+ case "tool-call":
365
+ console.log(`
366
+ [tool] ${msg.toolName}(${JSON.stringify(msg.args).substring(0, 100)})`);
367
+ break;
368
+ case "tool-result": {
369
+ const resultStr = typeof msg.result === "string" ? msg.result : JSON.stringify(msg.result);
370
+ if (resultStr.length > 200) {
371
+ console.log(`[tool result] ${resultStr.substring(0, 200)}...`);
372
+ }
373
+ break;
374
+ }
375
+ case "event":
376
+ if (msg.name === "thinking") {
377
+ const payload = msg.payload;
378
+ if (payload?.text) {
379
+ process.stdout.write(`\x1B[90m${payload.text}\x1B[0m`);
380
+ }
381
+ }
382
+ break;
383
+ }
384
+ });
385
+ try {
386
+ const result = await backend.startSession();
387
+ console.log(`Session started: ${result.sessionId}`);
388
+ process.stdout.write("> ");
389
+ } catch (err) {
390
+ const errMsg = err?.message || (typeof err === "object" ? JSON.stringify(err) : String(err));
391
+ console.error(`Failed to start ${config.agentName}: ${errMsg}`);
392
+ process.exit(1);
393
+ }
394
+ const readline = await import('readline');
395
+ const rl = readline.createInterface({
396
+ input: process.stdin,
397
+ output: process.stdout,
398
+ terminal: false
399
+ });
400
+ rl.on("line", async (line) => {
401
+ const trimmed = line.trim();
402
+ if (!trimmed) return;
403
+ if (trimmed === "/quit" || trimmed === "/exit") {
404
+ console.log("Shutting down...");
405
+ await backend.dispose();
406
+ process.exit(0);
407
+ }
408
+ if (trimmed === "/cancel") {
409
+ await backend.cancel("");
410
+ return;
411
+ }
412
+ try {
413
+ await backend.sendPrompt("", trimmed);
414
+ } catch (err) {
415
+ console.error(`Error: ${err.message}`);
416
+ }
417
+ });
418
+ rl.on("close", async () => {
419
+ await backend.dispose();
420
+ process.exit(0);
421
+ });
422
+ process.on("SIGINT", async () => {
423
+ console.log("\nShutting down...");
424
+ await backend.dispose();
425
+ process.exit(0);
426
+ });
427
+ }
428
+ async function handleSessionCommand() {
429
+ const rawSessionArgs = args.slice(1);
430
+ let targetMachineId;
431
+ const sessionArgs = [];
432
+ for (let i = 0; i < rawSessionArgs.length; i++) {
433
+ if ((rawSessionArgs[i] === "--machine" || rawSessionArgs[i] === "-m") && i + 1 < rawSessionArgs.length) {
434
+ targetMachineId = rawSessionArgs[i + 1];
435
+ i++;
436
+ } else {
437
+ sessionArgs.push(rawSessionArgs[i]);
438
+ }
439
+ }
440
+ const sessionSubcommand = sessionArgs[0];
441
+ if (!sessionSubcommand || sessionSubcommand === "--help" || sessionSubcommand === "-h") {
442
+ printSessionHelp();
443
+ return;
444
+ }
445
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionInboxSend, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxClear } = await import('./commands-pv7rhgVn.mjs');
446
+ const parseFlagStr = (flag, shortFlag) => {
447
+ for (let i = 1; i < sessionArgs.length; i++) {
448
+ if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
449
+ return sessionArgs[i + 1];
450
+ }
451
+ }
452
+ return void 0;
453
+ };
454
+ const parseFlagInt = (flag, shortFlag) => {
455
+ const v = parseFlagStr(flag, shortFlag);
456
+ if (v === void 0) return void 0;
457
+ const n = parseInt(v, 10);
458
+ return isNaN(n) ? void 0 : n;
459
+ };
460
+ const hasFlag = (flag) => sessionArgs.includes(flag);
461
+ if (sessionSubcommand === "machines" || sessionSubcommand === "machine") {
462
+ await sessionMachines();
463
+ } else if (sessionSubcommand === "list" || sessionSubcommand === "ls") {
464
+ await sessionList(targetMachineId, {
465
+ active: hasFlag("--active"),
466
+ json: hasFlag("--json")
467
+ });
468
+ } else if (sessionSubcommand === "spawn") {
469
+ const agent = sessionArgs[1] || "claude";
470
+ let dir = process.cwd();
471
+ for (let i = 1; i < sessionArgs.length; i++) {
472
+ if ((sessionArgs[i] === "-d" || sessionArgs[i] === "--directory") && i + 1 < sessionArgs.length) {
473
+ dir = sessionArgs[i + 1];
474
+ i++;
475
+ }
476
+ }
477
+ const message = parseFlagStr("--message");
478
+ const wait = hasFlag("--wait");
479
+ const worktree = hasFlag("--worktree");
480
+ const isolate = hasFlag("--isolate");
481
+ const permissionMode = parseFlagStr("--permission-mode") || parseFlagStr("-p");
482
+ const securityContextPath = parseFlagStr("--security-context");
483
+ const denyNetwork = hasFlag("--deny-network");
484
+ const parentSessionId = parseFlagStr("--parent");
485
+ const tagsFlag = parseFlagStr("--tags");
486
+ const share = [];
487
+ const denyRead = [];
488
+ const allowWrite = [];
489
+ const allowDomain = [];
490
+ const tags = [];
491
+ if (tagsFlag) {
492
+ tags.push(...tagsFlag.split(",").map((t) => t.trim()).filter(Boolean));
493
+ }
494
+ for (let i = 1; i < sessionArgs.length; i++) {
495
+ if (sessionArgs[i] === "--share" && i + 1 < sessionArgs.length) {
496
+ share.push(sessionArgs[++i]);
497
+ } else if (sessionArgs[i] === "--deny-read" && i + 1 < sessionArgs.length) {
498
+ denyRead.push(sessionArgs[++i]);
499
+ } else if (sessionArgs[i] === "--allow-write" && i + 1 < sessionArgs.length) {
500
+ allowWrite.push(sessionArgs[++i]);
501
+ } else if (sessionArgs[i] === "--allow-domain" && i + 1 < sessionArgs.length) {
502
+ allowDomain.push(sessionArgs[++i]);
503
+ }
504
+ }
505
+ const { parseShareArg } = await import('./commands-pv7rhgVn.mjs');
506
+ const shareEntries = share.map((s) => parseShareArg(s));
507
+ await sessionSpawn(agent, dir, targetMachineId, {
508
+ message,
509
+ wait,
510
+ worktree,
511
+ isolate,
512
+ permissionMode,
513
+ securityContextPath,
514
+ share: shareEntries.length > 0 ? shareEntries : void 0,
515
+ denyRead: denyRead.length > 0 ? denyRead : void 0,
516
+ allowWrite: allowWrite.length > 0 ? allowWrite : void 0,
517
+ denyNetwork,
518
+ allowDomain: allowDomain.length > 0 ? allowDomain : void 0,
519
+ tags: tags.length > 0 ? tags : void 0,
520
+ parentSessionId
521
+ });
522
+ } else if (sessionSubcommand === "stop") {
523
+ if (!sessionArgs[1]) {
524
+ console.error("Usage: svamp session stop <session-id>");
525
+ process.exit(1);
526
+ }
527
+ await sessionStop(sessionArgs[1], targetMachineId);
528
+ } else if (sessionSubcommand === "info") {
529
+ if (!sessionArgs[1]) {
530
+ console.error("Usage: svamp session info <session-id>");
531
+ process.exit(1);
532
+ }
533
+ await sessionInfo(sessionArgs[1], targetMachineId, {
534
+ json: hasFlag("--json")
535
+ });
536
+ } else if (sessionSubcommand === "messages" || sessionSubcommand === "msgs") {
537
+ if (!sessionArgs[1]) {
538
+ console.error("Usage: svamp session messages <session-id> [--last N] [--json] [--raw] [--after N] [--limit N]");
539
+ process.exit(1);
540
+ }
541
+ await sessionMessages(sessionArgs[1], targetMachineId, {
542
+ last: parseFlagInt("--last"),
543
+ json: hasFlag("--json"),
544
+ raw: hasFlag("--raw"),
545
+ after: parseFlagInt("--after"),
546
+ limit: parseFlagInt("--limit")
547
+ });
548
+ } else if (sessionSubcommand === "attach") {
549
+ if (!sessionArgs[1]) {
550
+ console.error("Usage: svamp session attach <session-id>");
551
+ process.exit(1);
552
+ }
553
+ await sessionAttach(sessionArgs[1], targetMachineId);
554
+ } else if (sessionSubcommand === "send") {
555
+ if (!sessionArgs[1] || !sessionArgs[2]) {
556
+ console.error('Usage: svamp session send <session-id> <message> [--subject "..."] [--urgency urgent|normal] [--wait] [--timeout N] [--json]');
557
+ process.exit(1);
558
+ }
559
+ await sessionSend(sessionArgs[1], sessionArgs[2], targetMachineId, {
560
+ wait: hasFlag("--wait"),
561
+ timeout: parseFlagInt("--timeout"),
562
+ json: hasFlag("--json"),
563
+ subject: parseFlagStr("--subject"),
564
+ urgency: parseFlagStr("--urgency")
565
+ });
566
+ } else if (sessionSubcommand === "wait") {
567
+ if (!sessionArgs[1]) {
568
+ console.error("Usage: svamp session wait <session-id> [--timeout N] [--json]");
569
+ process.exit(1);
570
+ }
571
+ await sessionWait(sessionArgs[1], targetMachineId, {
572
+ timeout: parseFlagInt("--timeout"),
573
+ json: hasFlag("--json")
574
+ });
575
+ } else if (sessionSubcommand === "share") {
576
+ if (!sessionArgs[1]) {
577
+ console.error("Usage: svamp session share <session-id> --add <email>[:<role>] | --remove <email> | --list | --public <view|interact|off>");
578
+ process.exit(1);
579
+ }
580
+ await sessionShare(sessionArgs[1], targetMachineId, {
581
+ add: parseFlagStr("--add"),
582
+ remove: parseFlagStr("--remove"),
583
+ list: hasFlag("--list"),
584
+ public: parseFlagStr("--public")
585
+ });
586
+ } else if (sessionSubcommand === "approve") {
587
+ if (!sessionArgs[1]) {
588
+ console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
589
+ process.exit(1);
590
+ }
591
+ const { sessionApprove } = await import('./commands-pv7rhgVn.mjs');
592
+ const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
593
+ await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
594
+ json: hasFlag("--json")
595
+ });
596
+ } else if (sessionSubcommand === "deny") {
597
+ if (!sessionArgs[1]) {
598
+ console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
599
+ process.exit(1);
600
+ }
601
+ const { sessionDeny } = await import('./commands-pv7rhgVn.mjs');
602
+ const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
603
+ await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
604
+ json: hasFlag("--json")
605
+ });
606
+ } else if (sessionSubcommand === "ralph-start" || sessionSubcommand === "ralph") {
607
+ if (!sessionArgs[1] || !sessionArgs[2]) {
608
+ console.error('Usage: svamp session ralph-start <session-id> "<task>" [--context-mode fresh|continue] [--completion-promise TEXT] [--max-iterations N] [--cooldown N]');
609
+ process.exit(1);
610
+ }
611
+ const contextModeStr = parseFlagStr("--context-mode") || parseFlagStr("--mode");
612
+ const contextMode = contextModeStr === "fresh" || contextModeStr === "continue" ? contextModeStr : void 0;
613
+ await sessionRalphStart(sessionArgs[1], sessionArgs[2], targetMachineId, {
614
+ completionPromise: parseFlagStr("--completion-promise"),
615
+ maxIterations: parseFlagInt("--max-iterations"),
616
+ cooldownSeconds: parseFlagInt("--cooldown"),
617
+ contextMode
618
+ });
619
+ } else if (sessionSubcommand === "ralph-cancel") {
620
+ if (!sessionArgs[1]) {
621
+ console.error("Usage: svamp session ralph-cancel <session-id>");
622
+ process.exit(1);
623
+ }
624
+ await sessionRalphCancel(sessionArgs[1], targetMachineId);
625
+ } else if (sessionSubcommand === "ralph-status") {
626
+ if (!sessionArgs[1]) {
627
+ console.error("Usage: svamp session ralph-status <session-id>");
628
+ process.exit(1);
629
+ }
630
+ await sessionRalphStatus(sessionArgs[1], targetMachineId);
631
+ } else if (sessionSubcommand === "set-title") {
632
+ const title = sessionArgs[1];
633
+ if (!title) {
634
+ console.error("Usage: svamp session set-title <title>");
635
+ process.exit(1);
636
+ }
637
+ const { sessionSetTitle } = await import('./agentCommands-DWSVkBSG.mjs');
638
+ await sessionSetTitle(title);
639
+ } else if (sessionSubcommand === "set-link") {
640
+ const url = sessionArgs[1];
641
+ if (!url) {
642
+ console.error("Usage: svamp session set-link <url> [label]");
643
+ process.exit(1);
644
+ }
645
+ const label = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
646
+ const { sessionSetLink } = await import('./agentCommands-DWSVkBSG.mjs');
647
+ await sessionSetLink(url, label);
648
+ } else if (sessionSubcommand === "notify") {
649
+ const message = sessionArgs[1];
650
+ if (!message) {
651
+ console.error("Usage: svamp session notify <message> [--level info|warning|error]");
652
+ process.exit(1);
653
+ }
654
+ const level = parseFlagStr("--level") || "info";
655
+ const { sessionNotify } = await import('./agentCommands-DWSVkBSG.mjs');
656
+ await sessionNotify(message, level);
657
+ } else if (sessionSubcommand === "broadcast") {
658
+ const action = sessionArgs[1];
659
+ if (!action) {
660
+ console.error("Usage: svamp session broadcast <action> [args...]\nActions: open-canvas <url> [label], close-canvas, toast <message>");
661
+ process.exit(1);
662
+ }
663
+ const { sessionBroadcast } = await import('./agentCommands-DWSVkBSG.mjs');
664
+ await sessionBroadcast(action, sessionArgs.slice(2).filter((a) => !a.startsWith("--")));
665
+ } else if (sessionSubcommand === "inbox") {
666
+ const inboxSubcmd = sessionArgs[1];
667
+ const agentSessionId = process.env.SVAMP_SESSION_ID;
668
+ if (inboxSubcmd === "send") {
669
+ if (!sessionArgs[2] || !sessionArgs[3]) {
670
+ console.error('Usage: svamp session inbox send <target-session-id> "<body>" [--subject "..."] [--urgency urgent|normal] [--json]');
671
+ process.exit(1);
672
+ }
673
+ if (agentSessionId) {
674
+ const { inboxSend } = await import('./agentCommands-DWSVkBSG.mjs');
675
+ await inboxSend(sessionArgs[2], {
676
+ body: sessionArgs[3],
677
+ subject: parseFlagStr("--subject"),
678
+ urgency: parseFlagStr("--urgency")
679
+ });
680
+ } else {
681
+ await sessionInboxSend(sessionArgs[2], sessionArgs[3], targetMachineId, {
682
+ subject: parseFlagStr("--subject"),
683
+ urgency: parseFlagStr("--urgency"),
684
+ json: hasFlag("--json")
685
+ });
686
+ }
687
+ } else if (inboxSubcmd === "list" || inboxSubcmd === "ls") {
688
+ if (agentSessionId && !sessionArgs[2]) {
689
+ const { inboxList } = await import('./agentCommands-DWSVkBSG.mjs');
690
+ await inboxList({
691
+ unread: hasFlag("--unread"),
692
+ limit: parseFlagInt("--limit"),
693
+ json: hasFlag("--json")
694
+ });
695
+ } else if (sessionArgs[2]) {
696
+ await sessionInboxList(sessionArgs[2], targetMachineId, {
697
+ unread: hasFlag("--unread"),
698
+ limit: parseFlagInt("--limit"),
699
+ json: hasFlag("--json")
700
+ });
701
+ } else {
702
+ console.error("Usage: svamp session inbox list [session-id] [--unread] [--limit N] [--json]");
703
+ process.exit(1);
704
+ }
705
+ } else if (inboxSubcmd === "read") {
706
+ if (!sessionArgs[2]) {
707
+ console.error("Usage: svamp session inbox read <session-id|message-id> [message-id]");
708
+ process.exit(1);
709
+ }
710
+ if (agentSessionId && !sessionArgs[3]) {
711
+ const { inboxList } = await import('./agentCommands-DWSVkBSG.mjs');
712
+ await sessionInboxRead(agentSessionId, sessionArgs[2], targetMachineId);
713
+ } else if (sessionArgs[3]) {
714
+ await sessionInboxRead(sessionArgs[2], sessionArgs[3], targetMachineId);
715
+ } else {
716
+ console.error("Usage: svamp session inbox read <session-id> <message-id>");
717
+ process.exit(1);
718
+ }
719
+ } else if (inboxSubcmd === "reply") {
720
+ if (agentSessionId && sessionArgs[2] && sessionArgs[3] && !sessionArgs[4]) {
721
+ const { inboxReply } = await import('./agentCommands-DWSVkBSG.mjs');
722
+ await inboxReply(sessionArgs[2], sessionArgs[3]);
723
+ } else if (sessionArgs[2] && sessionArgs[3] && sessionArgs[4]) {
724
+ await sessionInboxReply(sessionArgs[2], sessionArgs[3], sessionArgs[4], targetMachineId);
725
+ } else {
726
+ console.error('Usage: svamp session inbox reply <session-id> <message-id> "<body>"');
727
+ process.exit(1);
728
+ }
729
+ } else if (inboxSubcmd === "clear") {
730
+ if (agentSessionId && !sessionArgs[2]) {
731
+ await sessionInboxClear(agentSessionId, targetMachineId, { all: hasFlag("--all") });
732
+ } else if (sessionArgs[2]) {
733
+ await sessionInboxClear(sessionArgs[2], targetMachineId, { all: hasFlag("--all") });
734
+ } else {
735
+ console.error("Usage: svamp session inbox clear [session-id] [--all]");
736
+ process.exit(1);
737
+ }
738
+ } else {
739
+ console.error("Usage: svamp session inbox <send|list|read|reply|clear> [session-id] [args]");
740
+ process.exit(1);
741
+ }
742
+ } else {
743
+ console.error(`Unknown session command: ${sessionSubcommand}`);
744
+ printSessionHelp();
745
+ process.exit(1);
746
+ }
747
+ process.exit(0);
748
+ }
749
+ async function handleMachineCommand() {
750
+ const machineArgs = args.slice(1);
751
+ const machineSubcommand = machineArgs[0];
752
+ if (!machineSubcommand || machineSubcommand === "--help" || machineSubcommand === "-h") {
753
+ printMachineHelp();
754
+ return;
755
+ }
756
+ if (machineSubcommand === "share") {
757
+ const { machineShare } = await import('./commands-pv7rhgVn.mjs');
758
+ let machineId;
759
+ const shareArgs = [];
760
+ for (let i = 1; i < machineArgs.length; i++) {
761
+ if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
762
+ machineId = machineArgs[++i];
763
+ } else {
764
+ shareArgs.push(machineArgs[i]);
765
+ }
766
+ }
767
+ let add;
768
+ let remove;
769
+ let configPath;
770
+ let list = false;
771
+ let showConfig = false;
772
+ for (let i = 0; i < shareArgs.length; i++) {
773
+ if (shareArgs[i] === "--add" && i + 1 < shareArgs.length) {
774
+ add = shareArgs[++i];
775
+ } else if (shareArgs[i] === "--remove" && i + 1 < shareArgs.length) {
776
+ remove = shareArgs[++i];
777
+ } else if (shareArgs[i] === "--config" && i + 1 < shareArgs.length) {
778
+ configPath = shareArgs[++i];
779
+ } else if (shareArgs[i] === "--list") {
780
+ list = true;
781
+ } else if (shareArgs[i] === "--show-config") {
782
+ showConfig = true;
783
+ }
784
+ }
785
+ await machineShare(machineId, { add, remove, list, configPath, showConfig });
786
+ } else if (machineSubcommand === "exec") {
787
+ const { machineExec } = await import('./commands-pv7rhgVn.mjs');
788
+ let machineId;
789
+ let cwd;
790
+ const cmdParts = [];
791
+ for (let i = 1; i < machineArgs.length; i++) {
792
+ if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
793
+ machineId = machineArgs[++i];
794
+ } else if (machineArgs[i] === "--cwd" && i + 1 < machineArgs.length) {
795
+ cwd = machineArgs[++i];
796
+ } else {
797
+ cmdParts.push(machineArgs[i]);
798
+ }
799
+ }
800
+ const command = cmdParts.join(" ");
801
+ if (!command) {
802
+ console.error("Usage: svamp machine exec <command> [--cwd <path>] [-m <id>]");
803
+ process.exit(1);
804
+ }
805
+ await machineExec(machineId, command, cwd);
806
+ } else if (machineSubcommand === "info") {
807
+ const { machineInfo } = await import('./commands-pv7rhgVn.mjs');
808
+ let machineId;
809
+ for (let i = 1; i < machineArgs.length; i++) {
810
+ if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
811
+ machineId = machineArgs[++i];
812
+ }
813
+ }
814
+ await machineInfo(machineId);
815
+ } else if (machineSubcommand === "notify") {
816
+ const message = machineArgs[1];
817
+ if (!message) {
818
+ console.error("Usage: svamp machine notify <message> [--level info|warning|error]");
819
+ process.exit(1);
820
+ }
821
+ let level = "info";
822
+ for (let i = 2; i < machineArgs.length; i++) {
823
+ if (machineArgs[i] === "--level" && i + 1 < machineArgs.length) {
824
+ level = machineArgs[++i];
825
+ }
826
+ }
827
+ const { machineNotify } = await import('./agentCommands-DWSVkBSG.mjs');
828
+ await machineNotify(message, level);
829
+ } else if (machineSubcommand === "ls") {
830
+ const { machineLs } = await import('./commands-pv7rhgVn.mjs');
831
+ let machineId;
832
+ let showHidden = false;
833
+ let path;
834
+ for (let i = 1; i < machineArgs.length; i++) {
835
+ if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
836
+ machineId = machineArgs[++i];
837
+ } else if (machineArgs[i] === "--hidden" || machineArgs[i] === "-a") {
838
+ showHidden = true;
839
+ } else if (!path) {
840
+ path = machineArgs[i];
841
+ }
842
+ }
843
+ await machineLs(machineId, path, showHidden);
844
+ } else {
845
+ console.error(`Unknown machine command: ${machineSubcommand}`);
846
+ printMachineHelp();
847
+ process.exit(1);
848
+ }
849
+ process.exit(0);
850
+ }
851
+ async function handleSkillsCommand() {
852
+ const skillsArgs = args.slice(1);
853
+ const skillsSubcommand = skillsArgs[0];
854
+ if (!skillsSubcommand || skillsSubcommand === "--help" || skillsSubcommand === "-h") {
855
+ printSkillsHelp();
856
+ return;
857
+ }
858
+ const { skillsFind, skillsInstall, skillsList, skillsRemove, skillsPublish } = await import('./commands-DJoYOM_1.mjs');
859
+ if (skillsSubcommand === "find" || skillsSubcommand === "search") {
860
+ const query = skillsArgs.slice(1).filter((a) => !a.startsWith("--")).join(" ");
861
+ if (!query) {
862
+ console.error("Usage: svamp skills find <query> [--json]");
863
+ process.exit(1);
864
+ }
865
+ await skillsFind(query, { json: skillsArgs.includes("--json") });
866
+ } else if (skillsSubcommand === "install" || skillsSubcommand === "add") {
867
+ if (!skillsArgs[1]) {
868
+ console.error("Usage: svamp skills install <name> [--force]");
869
+ process.exit(1);
870
+ }
871
+ await skillsInstall(skillsArgs[1], { force: skillsArgs.includes("--force") });
872
+ } else if (skillsSubcommand === "list" || skillsSubcommand === "ls") {
873
+ await skillsList();
874
+ } else if (skillsSubcommand === "remove" || skillsSubcommand === "rm") {
875
+ if (!skillsArgs[1]) {
876
+ console.error("Usage: svamp skills remove <name>");
877
+ process.exit(1);
878
+ }
879
+ await skillsRemove(skillsArgs[1]);
880
+ } else if (skillsSubcommand === "publish") {
881
+ const publishArgs = skillsArgs.slice(1).filter((a) => !a.startsWith("--"));
882
+ if (!publishArgs[0]) {
883
+ console.error("Usage: svamp skills publish <path> [--version <tag>]");
884
+ process.exit(1);
885
+ }
886
+ const versionIdx = skillsArgs.indexOf("--version");
887
+ const versionTag = versionIdx !== -1 ? skillsArgs[versionIdx + 1] : void 0;
888
+ await skillsPublish(publishArgs[0], { version: versionTag });
889
+ } else {
890
+ console.error(`Unknown skills command: ${skillsSubcommand}`);
891
+ printSkillsHelp();
892
+ process.exit(1);
893
+ }
894
+ process.exit(0);
895
+ }
896
+ async function loginToHypha() {
897
+ const serverUrl = args[1] || process.env.HYPHA_SERVER_URL;
898
+ if (!serverUrl) {
899
+ console.error("Usage: svamp login <server-url>");
900
+ console.error(" Or set HYPHA_SERVER_URL environment variable");
901
+ process.exit(1);
902
+ }
903
+ console.log(`Logging in to Hypha server: ${serverUrl}`);
904
+ console.log("A browser window will open for authentication...");
905
+ try {
906
+ const mod = await import('hypha-rpc');
907
+ const hyphaWebsocketClient = mod.hyphaWebsocketClient || mod.default?.hyphaWebsocketClient;
908
+ if (!hyphaWebsocketClient?.login) {
909
+ throw new Error("Could not find hyphaWebsocketClient.login in hypha-rpc");
910
+ }
911
+ const token = await hyphaWebsocketClient.login({
912
+ server_url: serverUrl,
913
+ login_timeout: 120,
914
+ login_callback: (context) => {
915
+ console.log(`
916
+ Please open this URL in your browser:
917
+ ${context.login_url}
918
+ `);
919
+ console.log("Waiting for you to complete login in the browser...\n");
920
+ }
921
+ });
922
+ if (!token) {
923
+ console.error("Login failed \u2014 no token received");
924
+ process.exit(1);
925
+ }
926
+ const connectToServer = mod.connectToServer || mod.default && mod.default.connectToServer || hyphaWebsocketClient && hyphaWebsocketClient.connectToServer;
927
+ const server = await connectToServer({ server_url: serverUrl, token });
928
+ const workspace = server.config.workspace;
929
+ const userId = server.config.user?.id || workspace;
930
+ let longLivedToken = token;
931
+ try {
932
+ const sixMonthsInSeconds = 6 * 30 * 24 * 60 * 60;
933
+ longLivedToken = await server.generateToken({ expires_in: sixMonthsInSeconds, workspace });
934
+ console.log("Generated long-lived token (expires in ~6 months)");
935
+ } catch (err) {
936
+ console.warn("Could not generate long-lived token, using login token:", err.message || err);
937
+ }
938
+ await server.disconnect();
939
+ const os = await import('os');
940
+ const { join } = await import('path');
941
+ const fs = await import('fs');
942
+ const homeDir = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
943
+ if (!fs.existsSync(homeDir)) {
944
+ fs.mkdirSync(homeDir, { recursive: true });
945
+ }
946
+ const envFile = join(homeDir, ".env");
947
+ const envLines = [];
948
+ if (fs.existsSync(envFile)) {
949
+ const existing = fs.readFileSync(envFile, "utf-8").split("\n");
950
+ for (const line of existing) {
951
+ const trimmed = line.trim();
952
+ if (trimmed.startsWith("HYPHA_TOKEN=") || trimmed.startsWith("HYPHA_WORKSPACE=") || trimmed.startsWith("HYPHA_SERVER_URL=")) {
953
+ continue;
954
+ }
955
+ envLines.push(line);
956
+ }
957
+ }
958
+ envLines.push(`HYPHA_SERVER_URL=${serverUrl}`);
959
+ envLines.push(`HYPHA_TOKEN=${longLivedToken}`);
960
+ envLines.push(`HYPHA_WORKSPACE=${workspace}`);
961
+ while (envLines.length > 0 && envLines[envLines.length - 1].trim() === "") {
962
+ envLines.pop();
963
+ }
964
+ fs.writeFileSync(envFile, envLines.join("\n") + "\n", "utf-8");
965
+ console.log(`
966
+ Login successful!`);
967
+ console.log(` User: ${userId}`);
968
+ console.log(` Workspace: ${workspace}`);
969
+ console.log(` Token saved to: ${envFile}`);
970
+ console.log(`
971
+ You can now start the daemon: svamp daemon start`);
972
+ } catch (err) {
973
+ const msg = err.message || String(err);
974
+ if (msg.includes("WebSocket is not defined")) {
975
+ console.error(`Login failed: WebSocket is not available in this Node.js version (${process.version}).`);
976
+ console.error(` hypha-rpc requires Node.js >= 22 (which includes native WebSocket support).`);
977
+ console.error(` Please upgrade Node.js: nvm install 22 && nvm use 22`);
978
+ } else {
979
+ console.error("Login failed:", msg);
980
+ }
981
+ process.exit(1);
982
+ }
983
+ }
984
+ async function logoutFromHypha() {
985
+ const os = await import('os');
986
+ const { join } = await import('path');
987
+ const fs = await import('fs');
988
+ const homeDir = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
989
+ const envFile = join(homeDir, ".env");
990
+ console.log("Stopping daemon...");
991
+ try {
992
+ await stopDaemon();
993
+ } catch {
994
+ }
995
+ if (fs.existsSync(envFile)) {
996
+ const existing = fs.readFileSync(envFile, "utf-8").split("\n");
997
+ const kept = [];
998
+ for (const line of existing) {
999
+ const trimmed = line.trim();
1000
+ if (trimmed.startsWith("HYPHA_TOKEN=") || trimmed.startsWith("HYPHA_WORKSPACE=") || trimmed.startsWith("HYPHA_SERVER_URL=")) {
1001
+ continue;
1002
+ }
1003
+ kept.push(line);
1004
+ }
1005
+ while (kept.length > 0 && kept[kept.length - 1].trim() === "") {
1006
+ kept.pop();
1007
+ }
1008
+ if (kept.length > 0) {
1009
+ fs.writeFileSync(envFile, kept.join("\n") + "\n", "utf-8");
1010
+ } else {
1011
+ fs.unlinkSync(envFile);
1012
+ }
1013
+ console.log("Credentials removed");
1014
+ } else {
1015
+ console.log("No credentials found");
1016
+ }
1017
+ console.log("Logged out successfully");
1018
+ }
1019
+ const LAUNCHD_LABEL = "io.hypha.svamp.daemon";
1020
+ async function installDaemonService() {
1021
+ const os = await import('os');
1022
+ const { join, dirname } = await import('path');
1023
+ const fs = await import('fs');
1024
+ const { execSync } = await import('child_process');
1025
+ const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
1026
+ const logsDir = join(svampHome, "logs");
1027
+ let svampBinary = process.argv[1];
1028
+ try {
1029
+ const resolved = execSync(`which svamp`, { encoding: "utf-8" }).trim();
1030
+ if (resolved) svampBinary = resolved;
1031
+ } catch {
1032
+ }
1033
+ if (!fs.existsSync(svampBinary)) {
1034
+ console.error(`Cannot find svamp binary at: ${svampBinary}`);
1035
+ console.error("Please install svamp-cli globally first.");
1036
+ process.exit(1);
1037
+ }
1038
+ const nodeBinDir = dirname(process.execPath);
1039
+ const shellPath = process.env.PATH || "";
1040
+ const supervisedPath = shellPath.includes(nodeBinDir) ? shellPath : [nodeBinDir, shellPath].filter(Boolean).join(":");
1041
+ if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
1042
+ if (process.platform === "darwin") {
1043
+ const launchAgentsDir = join(os.homedir(), "Library", "LaunchAgents");
1044
+ const plistPath = join(launchAgentsDir, `${LAUNCHD_LABEL}.plist`);
1045
+ if (!fs.existsSync(launchAgentsDir)) fs.mkdirSync(launchAgentsDir, { recursive: true });
1046
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
1047
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1048
+ <plist version="1.0">
1049
+ <dict>
1050
+ <key>Label</key>
1051
+ <string>${LAUNCHD_LABEL}</string>
1052
+ <key>ProgramArguments</key>
1053
+ <array>
1054
+ <string>${svampBinary}</string>
1055
+ <string>daemon</string>
1056
+ <string>start-supervised</string>
1057
+ </array>
1058
+ <key>RunAtLoad</key>
1059
+ <true/>
1060
+ <key>StandardOutPath</key>
1061
+ <string>${logsDir}/daemon-supervised.log</string>
1062
+ <key>StandardErrorPath</key>
1063
+ <string>${logsDir}/daemon-supervised.log</string>
1064
+ <key>EnvironmentVariables</key>
1065
+ <dict>
1066
+ <key>PATH</key>
1067
+ <string>${supervisedPath}</string>
1068
+ <key>SVAMP_HOME</key>
1069
+ <string>${svampHome}</string>
1070
+ </dict>
1071
+ </dict>
1072
+ </plist>
1073
+ `;
1074
+ fs.writeFileSync(plistPath, plist, "utf-8");
1075
+ console.log(`Plist written: ${plistPath}`);
1076
+ try {
1077
+ execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
1078
+ } catch {
1079
+ }
1080
+ execSync(`launchctl load "${plistPath}"`);
1081
+ console.log(`
1082
+ Svamp daemon service installed and started!`);
1083
+ console.log(` Service: ${LAUNCHD_LABEL}`);
1084
+ console.log(` Auto-restart on crash: enabled`);
1085
+ console.log(` Log file: ${logsDir}/daemon-supervised.log`);
1086
+ console.log(`
1087
+ To stop the service: svamp daemon stop`);
1088
+ console.log(`To uninstall: svamp daemon uninstall`);
1089
+ } else if (process.platform === "linux") {
1090
+ const hasSystemd = (() => {
1091
+ try {
1092
+ execSync("systemctl --user status 2>/dev/null", { stdio: "pipe" });
1093
+ return true;
1094
+ } catch {
1095
+ return false;
1096
+ }
1097
+ })();
1098
+ if (hasSystemd) {
1099
+ const systemdUserDir = join(os.homedir(), ".config", "systemd", "user");
1100
+ const unitPath = join(systemdUserDir, "svamp-daemon.service");
1101
+ if (!fs.existsSync(systemdUserDir)) fs.mkdirSync(systemdUserDir, { recursive: true });
1102
+ const unit = `[Unit]
1103
+ Description=Svamp Daemon \u2014 AI workspace on Hypha Cloud
1104
+ After=network-online.target
1105
+ Wants=network-online.target
1106
+
1107
+ [Service]
1108
+ ExecStart=${svampBinary} daemon start-supervised
1109
+ Environment=PATH=${supervisedPath}
1110
+ Environment=SVAMP_HOME=${svampHome}
1111
+ StandardOutput=append:${logsDir}/daemon-supervised.log
1112
+ StandardError=append:${logsDir}/daemon-supervised.log
1113
+
1114
+ [Install]
1115
+ WantedBy=default.target
1116
+ `;
1117
+ fs.writeFileSync(unitPath, unit, "utf-8");
1118
+ execSync("systemctl --user daemon-reload");
1119
+ execSync("systemctl --user enable --now svamp-daemon.service");
1120
+ console.log(`
1121
+ Svamp daemon service installed and started!`);
1122
+ console.log(` Service: svamp-daemon.service (systemd user)`);
1123
+ console.log(` Unit file: ${unitPath}`);
1124
+ console.log(` Auto-restart on crash: enabled`);
1125
+ console.log(` Log file: ${logsDir}/daemon-supervised.log`);
1126
+ console.log(`
1127
+ To stop the service: svamp daemon stop`);
1128
+ console.log(`To uninstall: svamp daemon uninstall`);
1129
+ } else {
1130
+ console.log(`
1131
+ No systemd detected. The built-in supervisor is cross-platform.`);
1132
+ console.log(`
1133
+ For Docker, set your container CMD to:`);
1134
+ console.log(` ${svampBinary} daemon start-supervised`);
1135
+ console.log(`
1136
+ Or run in the background:`);
1137
+ console.log(` svamp daemon start`);
1138
+ console.log(`
1139
+ To stop the daemon: svamp daemon stop`);
1140
+ }
1141
+ } else {
1142
+ console.log(`
1143
+ The built-in supervisor is cross-platform.`);
1144
+ console.log(`
1145
+ Run: svamp daemon start`);
1146
+ console.log(`
1147
+ To stop: svamp daemon stop`);
1148
+ }
1149
+ }
1150
+ async function uninstallDaemonService() {
1151
+ const os = await import('os');
1152
+ const { join } = await import('path');
1153
+ const fs = await import('fs');
1154
+ const { execSync } = await import('child_process');
1155
+ if (process.platform === "darwin") {
1156
+ const plistPath = join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
1157
+ if (!fs.existsSync(plistPath)) {
1158
+ console.log("Svamp daemon service is not installed.");
1159
+ return;
1160
+ }
1161
+ try {
1162
+ execSync(`launchctl unload "${plistPath}"`, { stdio: "pipe" });
1163
+ } catch {
1164
+ }
1165
+ fs.unlinkSync(plistPath);
1166
+ console.log("Svamp daemon service uninstalled (launchd).");
1167
+ console.log("The daemon has been stopped and will no longer auto-start.");
1168
+ } else if (process.platform === "linux") {
1169
+ const unitPath = join(os.homedir(), ".config", "systemd", "user", "svamp-daemon.service");
1170
+ const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
1171
+ const wrapperPath = join(svampHome, "daemon-supervisor.sh");
1172
+ let removed = false;
1173
+ if (fs.existsSync(unitPath)) {
1174
+ try {
1175
+ execSync("systemctl --user disable --now svamp-daemon.service", { stdio: "pipe" });
1176
+ } catch {
1177
+ }
1178
+ fs.unlinkSync(unitPath);
1179
+ try {
1180
+ execSync("systemctl --user daemon-reload", { stdio: "pipe" });
1181
+ } catch {
1182
+ }
1183
+ console.log("Svamp daemon service uninstalled (systemd).");
1184
+ removed = true;
1185
+ }
1186
+ if (fs.existsSync(wrapperPath)) {
1187
+ fs.unlinkSync(wrapperPath);
1188
+ if (!removed) console.log("Svamp daemon supervisor script removed.");
1189
+ removed = true;
1190
+ }
1191
+ if (!removed) {
1192
+ console.log("Svamp daemon service is not installed.");
1193
+ }
1194
+ } else {
1195
+ console.error(`Daemon service uninstall is not supported on ${process.platform}.`);
1196
+ process.exit(1);
1197
+ }
1198
+ }
1199
+ function printHelp() {
1200
+ console.log(`
1201
+ svamp \u2014 AI workspace on Hypha Cloud
1202
+
1203
+ Quick start \u2014 spawn an agent to do a task (use -p bypassPermissions to run autonomously):
1204
+ svamp session spawn claude -d ./project -p bypassPermissions --message "fix tests" --wait
1205
+
1206
+ Note: without -p bypassPermissions the agent PAUSES waiting for human approval of each
1207
+ tool use. This will cause it to hang in automated/agent contexts. Use bypassPermissions
1208
+ for autonomous work, or "default" only when a human is actively watching.
1209
+
1210
+ Commands:
1211
+ svamp Start interactive Claude session (synced to cloud)
1212
+ svamp login [url] Login to Hypha (opens browser, stores token)
1213
+ svamp logout Logout (stop daemon, remove credentials)
1214
+ svamp daemon start Start the background daemon (required for sessions)
1215
+ svamp daemon status Show daemon status
1216
+ svamp daemon --help Show all daemon commands
1217
+
1218
+ Session management (requires daemon running):
1219
+ svamp session spawn <agent> [opts] Spawn a new agent session (agents: claude, gemini, codex)
1220
+ svamp session send <id> <message> Send message to a session (--wait to block until done)
1221
+ svamp session wait <id> Wait for agent to become idle
1222
+ svamp session messages <id> Show message history (--last N, --json, --raw)
1223
+ svamp session info <id> Show session status & pending permissions
1224
+ svamp session list List sessions
1225
+ svamp session stop <id> Stop a session
1226
+ svamp session attach <id> Attach interactive terminal (stdin/stdout)
1227
+ svamp session approve/deny <id> Approve or deny pending permission requests
1228
+ svamp session --help Show ALL session commands and detailed options
1229
+
1230
+ Other:
1231
+ svamp machine --help Machine sharing & security contexts
1232
+ svamp skills --help Skills marketplace (find, install, publish)
1233
+ svamp service --help Service exposure (HTTP services from sandboxes)
1234
+ svamp agent <name> Start local agent session (no daemon needed)
1235
+ svamp --version Show version
1236
+
1237
+ Interactive mode:
1238
+ Run 'svamp' with no arguments to start Claude in your terminal with cloud sync.
1239
+ Messages from the web app auto-switch to remote mode. Space-Space returns to local.
1240
+
1241
+ Environment variables:
1242
+ HYPHA_SERVER_URL Hypha server URL (required for cloud sync)
1243
+ HYPHA_TOKEN Hypha auth token (stored by login command)
1244
+ HYPHA_WORKSPACE Hypha workspace / user ID (stored by login command)
1245
+ SVAMP_HOME Config directory (default: ~/.svamp)
1246
+ `);
1247
+ }
1248
+ function printDaemonHelp() {
1249
+ console.log(`
1250
+ svamp daemon \u2014 Daemon management
1251
+
1252
+ Usage:
1253
+ svamp daemon start Start the daemon (detached)
1254
+ svamp daemon start --no-auto-continue Start without auto-continuing interrupted sessions
1255
+ svamp daemon stop Stop the daemon (sessions preserved for restart)
1256
+ svamp daemon stop --cleanup Stop and mark all sessions as stopped
1257
+ svamp daemon restart Restart daemon (sessions resume seamlessly)
1258
+ svamp daemon status Show daemon status
1259
+ svamp daemon install Install as login service (macOS/Linux) \u2014 auto-start at login
1260
+ svamp daemon uninstall Remove login service
1261
+ `);
1262
+ }
1263
+ function printSessionHelp() {
1264
+ console.log(`
1265
+ svamp session \u2014 Spawn and manage AI agent sessions
1266
+
1267
+ QUICK START \u2014 Spawn a session, give it a task, get results:
1268
+
1269
+ # One-liner: spawn agent, send task, wait for completion
1270
+ svamp session spawn claude -d ./my-project -p bypassPermissions \\
1271
+ --message "fix the failing tests" --wait
1272
+
1273
+ # The command prints the session ID (e.g., "abc12345-..."). Use it to:
1274
+ svamp session messages abc1 --last 10 # read output (prefix match on ID)
1275
+ svamp session send abc1 "now run lint" --wait # send follow-up, wait for done
1276
+ svamp session stop abc1 # stop when finished
1277
+
1278
+ # Spawn on a remote/cloud machine:
1279
+ svamp session spawn claude -d /workspace -m cloud-box --message "deploy" --wait
1280
+
1281
+ AGENTS:
1282
+ claude Claude Code \u2014 full coding agent (default)
1283
+ gemini Google Gemini via ACP protocol
1284
+ codex OpenAI Codex via MCP protocol
1285
+
1286
+ Usage: svamp session spawn <agent> [options]
1287
+ The <agent> argument is required. Use "claude" if unsure.
1288
+
1289
+ COMMANDS:
1290
+
1291
+ Lifecycle:
1292
+ spawn <agent> [-d <path>] [options] Spawn a new agent session
1293
+ stop <id> Stop a running session
1294
+ list [--active] [--json] List sessions (alias: ls)
1295
+ machines List discoverable machines
1296
+ info <id> [--json] Show status & pending permissions
1297
+
1298
+ Communicate:
1299
+ send <id> <message> [--wait] [--timeout N] [--json]
1300
+ Send a message to a running session
1301
+ messages <id> [--last N] [--json] Read message history (alias: msgs)
1302
+ wait <id> [--timeout N] [--json] Wait for agent to become idle
1303
+ attach <id> Attach interactive terminal (stdin/stdout)
1304
+
1305
+ Permissions (when agent pauses for approval):
1306
+ approve <id> [request-id] [--json] Approve pending tool permission(s)
1307
+ deny <id> [request-id] [--json] Deny pending tool permission(s)
1308
+
1309
+ Sharing:
1310
+ share <id> --list List shared users
1311
+ share <id> --add <email>[:<role>] Share with user (role: view|interact|admin)
1312
+ share <id> --remove <email> Remove shared user
1313
+ share <id> --public <view|interact|off> Set public link access
1314
+
1315
+ Ralph Loop (iterative automation):
1316
+ ralph-start <id> "<task>" [options] Start iterative loop
1317
+ ralph-cancel <id> Cancel loop
1318
+ ralph-status <id> Show loop status
1319
+
1320
+ Inbox:
1321
+ inbox send <id> "<body>" [opts] Send a message to session inbox
1322
+ inbox list <id> [--unread] [--json] List inbox messages
1323
+ inbox read <id> <msg-id> Read and mark a message
1324
+ inbox reply <id> <msg-id> "<body>" Reply to sender's session
1325
+ inbox clear <id> [--all] Clear read (or all) messages
1326
+
1327
+ SPAWN OPTIONS:
1328
+ -d, --directory <path> Working directory (REQUIRED for meaningful work)
1329
+ -p, --permission-mode <mode> Permission mode (see below). Default: "default"
1330
+ --message <msg> Send this message to the agent immediately after spawn
1331
+ --wait Block until agent finishes (idle). Combine with --message
1332
+ for fire-and-forget: spawn \u2192 task \u2192 wait \u2192 done.
1333
+ --timeout <sec> Timeout for --wait (default: no timeout)
1334
+ --worktree Create isolated git worktree branch (.dev/worktree/<name>)
1335
+ --tags <tag1,tag2> Comma-separated tags for session grouping
1336
+ --parent <sessionId> Set parent session (auto-set from SVAMP_SESSION_ID env)
1337
+
1338
+ PERMISSION MODES:
1339
+ default \u26A0 Agent PAUSES and waits for human approval before each tool use.
1340
+ The agent will be stuck (exit code 2) until you run approve/deny.
1341
+ DO NOT use default mode in automated pipelines \u2014 the agent will hang.
1342
+ acceptEdits Auto-approve file edits. Still pauses for bash & dangerous tools.
1343
+ bypassPermissions Auto-approve everything. No prompts, no pauses. Fully autonomous.
1344
+ Use this for automation, CI, or when spawning from another agent.
1345
+
1346
+ \u26A1 For agents/scripts: ALWAYS pass -p bypassPermissions unless you have a human
1347
+ monitoring the session who can approve/deny tool requests in real time.
1348
+
1349
+ PERMISSION WORKFLOW (only relevant for "default" and "acceptEdits" modes):
1350
+ 1. svamp session wait <id> --json \u2192 exit code 2 means permission pending
1351
+ 2. svamp session info <id> --json \u2192 shows pendingPermissions array
1352
+ 3. svamp session approve <id> \u2192 approve all pending (or approve <id> <rid>)
1353
+ 4. svamp session deny <id> \u2192 deny all pending (or deny <id> <rid>)
1354
+
1355
+ ISOLATION & SHARING OPTIONS (for spawn):
1356
+ --isolate Force OS-level sandbox (even without sharing)
1357
+ --security-context <path> Apply security context config (JSON file)
1358
+ --share <email>[:<role>] Share session (repeatable). Role: view|interact|admin
1359
+ --deny-network Block all network access
1360
+ --deny-read <path> Deny reading path (repeatable)
1361
+ --allow-write <path> Allow writing path (repeatable)
1362
+ --allow-domain <domain> Allow network domain (repeatable)
1363
+
1364
+ MESSAGES OPTIONS:
1365
+ --last N Show last N messages only
1366
+ --after N Start after sequence number N (for pagination)
1367
+ --limit N Max messages to fetch (default: 1000)
1368
+ --json Output as JSON array of {id, seq, role, text, createdAt}
1369
+ --raw With --json: include full raw objects (tool_use, tool_result, thinking)
1370
+
1371
+ RALPH LOOP OPTIONS (for ralph-start):
1372
+ --context-mode <mode> fresh (new process each iteration) or continue (same process)
1373
+ --completion-promise <text> Completion signal text (default: DONE)
1374
+ --max-iterations <n> Max iterations (default: 10)
1375
+ --cooldown <sec> Delay between iterations (default: 1)
1376
+
1377
+ EXIT CODES (for wait, send --wait, spawn --wait):
1378
+ 0 Agent is idle (task completed successfully)
1379
+ 1 Error (timeout, connection failure, session not found)
1380
+ 2 Agent is waiting for permission approval \u2014 use approve/deny to unblock
1381
+
1382
+ ATTACH COMMANDS (inside an attached session):
1383
+ /quit, /detach Detach (session keeps running)
1384
+ /abort, /cancel Cancel current agent turn
1385
+ /kill Stop the session entirely
1386
+ /info Show session status
1387
+
1388
+ GLOBAL OPTIONS:
1389
+ --machine <id>, -m <id> Target a specific machine (prefix match supported)
1390
+
1391
+ ID MATCHING:
1392
+ Session and machine IDs support prefix matching.
1393
+ "abc1" matches "abc12345-6789-..." \u2014 you only need enough characters to be unique.
1394
+
1395
+ EXAMPLES:
1396
+
1397
+ # === Common: Spawn agent to do a task and wait ===
1398
+ svamp session spawn claude -d ./my-project -p bypassPermissions \\
1399
+ --message "fix all TypeScript errors, run tsc to verify" --wait
1400
+
1401
+ # === Multi-step: Spawn, then send messages interactively ===
1402
+ ID=$(svamp session spawn claude -d ./proj -p bypassPermissions)
1403
+ svamp session send $ID "first, read the README" --wait
1404
+ svamp session send $ID "now implement the feature described there" --wait
1405
+ svamp session messages $ID --last 20
1406
+
1407
+ # === Spawn child session from another agent (e.g., in a bash tool) ===
1408
+ # Use -p bypassPermissions so the child doesn't get stuck on approvals.
1409
+ # Use --wait so your script blocks until the child finishes.
1410
+ svamp session spawn claude -d /tmp/test-project -p bypassPermissions \\
1411
+ --message "run the test suite and report results" --wait
1412
+ # Then read the child's output:
1413
+ svamp session messages <child-id> --last 5
1414
+
1415
+ # === Monitor with permissions (default mode) ===
1416
+ svamp session spawn claude -d ./proj --message "refactor auth module"
1417
+ svamp session wait abc1 --json # exit code 2 = permission pending
1418
+ svamp session approve abc1 # approve all pending
1419
+ svamp session wait abc1 # wait for completion
1420
+
1421
+ # === Spawn on a remote machine ===
1422
+ svamp session spawn claude -d /workspace -m my-cloud-box \\
1423
+ -p bypassPermissions --message "deploy to staging" --wait
1424
+
1425
+ # === Isolated session with network restrictions ===
1426
+ svamp session spawn claude -d /tmp/sandbox --isolate --deny-network
1427
+
1428
+ # === Ralph Loop: iterative task automation ===
1429
+ svamp session ralph-start abc1 "Fix all linting errors" \\
1430
+ --context-mode fresh --max-iterations 10
1431
+ `);
1432
+ }
1433
+ function printMachineHelp() {
1434
+ console.log(`
1435
+ svamp machine \u2014 Machine management
1436
+
1437
+ Usage:
1438
+ svamp machine info Show machine info & status
1439
+ svamp machine exec <command> [--cwd <path>] Run command on machine
1440
+ svamp machine ls [path] [--hidden] List directory on machine
1441
+ svamp machine share --list List shared users
1442
+ svamp machine share --add <email>[:<role>] Share machine with user
1443
+ svamp machine share --remove <email> Remove shared user
1444
+ svamp machine share --config <path> Apply security context config
1445
+ svamp machine share --show-config Show current security context config
1446
+
1447
+ Options:
1448
+ --machine <id>, -m <id> Target a specific machine (prefix match supported)
1449
+ --cwd <path> Working directory for exec (default: home dir)
1450
+ --hidden, -a Show hidden files in ls
1451
+
1452
+ Examples:
1453
+ svamp machine info
1454
+ svamp machine exec "ls -la /tmp"
1455
+ svamp machine exec "df -h" -m cloud-box --cwd /
1456
+ svamp machine ls /home/user/projects
1457
+ svamp machine ls --hidden -m cloud-box
1458
+ svamp machine share --add alice@example.com:admin
1459
+ svamp machine share --config ./security-context.json
1460
+ `);
1461
+ }
1462
+ function printSkillsHelp() {
1463
+ console.log(`
1464
+ svamp skills \u2014 Agent skills marketplace
1465
+
1466
+ Usage:
1467
+ svamp skills find <query> [--json] Search for skills in the marketplace
1468
+ svamp skills install <name> [--force] Install skill to ~/.claude/skills/<name>/
1469
+ svamp skills list List locally installed skills
1470
+ svamp skills remove <name> Remove an installed skill
1471
+ svamp skills publish <path> [--version <tag>] Publish a skill to the marketplace
1472
+
1473
+ Skills are stored in ~/.claude/skills/ and automatically detected by Claude Code.
1474
+ Each skill contains a SKILL.md file with instructions and metadata.
1475
+ Skills use git-backed storage \u2014 you can also clone and push via git.
1476
+
1477
+ Browse online: https://hypha.aicell.io/hypha-cloud/artifacts/marketplace
1478
+
1479
+ Examples:
1480
+ svamp skills find "code review"
1481
+ svamp skills install code-review
1482
+ svamp skills publish ./my-skill/
1483
+ svamp skills publish ./my-skill/ --version v1.0
1484
+ `);
1485
+ }
1486
+ function printInteractiveHelp() {
1487
+ console.log(`
1488
+ svamp \u2014 Interactive Claude session with cloud sync
1489
+
1490
+ Usage:
1491
+ svamp [-d <path>] [--resume <id>] [--continue] [--permission-mode <mode>] [-- <claude-args>]
1492
+ svamp start [-d <path>] [...]
1493
+
1494
+ Options:
1495
+ -d, --directory <path> Working directory (default: cwd)
1496
+ -r, --resume <id> Resume a Claude session by ID
1497
+ -c, --continue Continue the last Claude session
1498
+ -p, --permission-mode <m> Permission mode: default, acceptEdits, bypassPermissions, plan
1499
+ -- Pass remaining args to Claude CLI
1500
+
1501
+ Modes:
1502
+ Local mode Full interactive Claude terminal UI (default)
1503
+ Remote mode Processes messages from the web app
1504
+
1505
+ Switching:
1506
+ When a message arrives from the web app \u2192 automatically switches to remote mode
1507
+ Space Space Switch from remote mode back to local mode
1508
+ Ctrl-C Ctrl-C Exit from remote mode
1509
+
1510
+ The session is synced to Hypha Cloud so it's visible in the web app.
1511
+ Works offline if no Hypha credentials are configured.
1512
+ `);
1513
+ }
1514
+ function printAgentHelp() {
1515
+ console.log(`
1516
+ svamp agent \u2014 Interactive agent sessions (local, no daemon required)
1517
+
1518
+ Usage:
1519
+ svamp agent list List known agents
1520
+ svamp agent <name> [-d <path>] Start interactive agent session
1521
+ svamp agent -- <cmd> [args] Start custom ACP agent
1522
+
1523
+ Examples:
1524
+ svamp agent gemini Start Gemini agent in current directory
1525
+ svamp agent gemini -d /tmp/proj Start Gemini agent in /tmp/proj
1526
+ svamp agent codex Start Codex agent
1527
+ svamp agent -- mycli --acp Start custom ACP-compatible agent
1528
+
1529
+ Known agents: gemini (ACP), codex (MCP)
1530
+ `);
1531
+ }
1532
+ main().catch((err) => {
1533
+ console.error("Fatal error:", err);
1534
+ process.exit(1);
1535
+ });