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/agentCommands-DWSVkBSG.mjs +263 -0
- package/dist/api-BRbsyqJ4.mjs +147 -0
- package/dist/cli.mjs +1535 -0
- package/dist/commands-BYbuedOK.mjs +469 -0
- package/dist/commands-DJoYOM_1.mjs +531 -0
- package/dist/commands-DT4zKcI_.mjs +593 -0
- package/dist/commands-pv7rhgVn.mjs +1899 -0
- package/dist/index.mjs +20 -0
- package/dist/package-DP0KvJ1N.mjs +62 -0
- package/dist/run-71Ox10lO.mjs +8338 -0
- package/dist/run-BU9OQRQs.mjs +1103 -0
- package/dist/staticServer-CWcmMF5V.mjs +477 -0
- package/dist/tunnel-BDKdemh0.mjs +383 -0
- package/package.json +1 -1
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
|
+
});
|