svamp-cli 0.2.342 → 0.2.356

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