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