svamp-cli 0.1.23 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-4fyJcaRE.mjs';
1
+ import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-DjfPjgOb.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -6,6 +6,7 @@ import 'path';
6
6
  import 'url';
7
7
  import 'child_process';
8
8
  import 'crypto';
9
+ import './hyphaClient-DLkclazm.mjs';
9
10
  import 'node:crypto';
10
11
  import 'node:fs';
11
12
  import 'node:path';
@@ -65,6 +66,12 @@ async function main() {
65
66
  } else if (daemonSubcommand === "status") {
66
67
  daemonStatus();
67
68
  process.exit(0);
69
+ } else if (daemonSubcommand === "install") {
70
+ await installDaemonService();
71
+ process.exit(0);
72
+ } else if (daemonSubcommand === "uninstall") {
73
+ await uninstallDaemonService();
74
+ process.exit(0);
68
75
  } else {
69
76
  printDaemonHelp();
70
77
  }
@@ -75,7 +82,7 @@ async function main() {
75
82
  } else if (subcommand === "--help" || subcommand === "-h" || !subcommand) {
76
83
  printHelp();
77
84
  } else if (subcommand === "--version" || subcommand === "-v") {
78
- const pkg = await import('./package-C5B0twb8.mjs').catch(() => ({ default: { version: "unknown" } }));
85
+ const pkg = await import('./package-CvnNnsm7.mjs').catch(() => ({ default: { version: "unknown" } }));
79
86
  console.log(`svamp version: ${pkg.default.version}`);
80
87
  } else {
81
88
  console.error(`Unknown command: ${subcommand}`);
@@ -90,7 +97,7 @@ async function handleAgentCommand() {
90
97
  return;
91
98
  }
92
99
  if (agentArgs[0] === "list") {
93
- const { KNOWN_ACP_AGENTS } = await import('./run-4fyJcaRE.mjs').then(function (n) { return n.f; });
100
+ const { KNOWN_ACP_AGENTS } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.e; });
94
101
  console.log("Known ACP agents:");
95
102
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
96
103
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")}`);
@@ -99,10 +106,10 @@ async function handleAgentCommand() {
99
106
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
100
107
  return;
101
108
  }
102
- const { resolveAcpAgentConfig } = await import('./run-4fyJcaRE.mjs').then(function (n) { return n.f; });
103
- const { AcpBackend } = await import('./run-4fyJcaRE.mjs').then(function (n) { return n.e; });
104
- const { GeminiTransport } = await import('./run-4fyJcaRE.mjs').then(function (n) { return n.G; });
105
- const { DefaultTransport } = await import('./run-4fyJcaRE.mjs').then(function (n) { return n.D; });
109
+ const { resolveAcpAgentConfig } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.e; });
110
+ const { AcpBackend } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.c; });
111
+ const { GeminiTransport } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.G; });
112
+ const { DefaultTransport } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.D; });
106
113
  let cwd = process.cwd();
107
114
  const filteredArgs = [];
108
115
  for (let i = 0; i < agentArgs.length; i++) {
@@ -231,7 +238,7 @@ async function handleSessionCommand() {
231
238
  printSessionHelp();
232
239
  return;
233
240
  }
234
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach } = await import('./commands-_uCC3U1U.mjs');
241
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach } = await import('./commands-BOeSil-P.mjs');
235
242
  if (sessionSubcommand === "list" || sessionSubcommand === "ls") {
236
243
  await sessionList();
237
244
  } else if (sessionSubcommand === "spawn") {
@@ -297,16 +304,13 @@ async function loginToHypha() {
297
304
  }
298
305
  const token = await hyphaWebsocketClient.login({
299
306
  server_url: serverUrl,
307
+ login_timeout: 120,
300
308
  login_callback: (context) => {
301
309
  console.log(`
302
310
  Please open this URL in your browser:
303
311
  ${context.login_url}
304
312
  `);
305
- import('child_process').then(({ exec }) => {
306
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
307
- exec(`${cmd} "${context.login_url}"`);
308
- }).catch(() => {
309
- });
313
+ console.log("Waiting for you to complete login in the browser...\n");
310
314
  }
311
315
  });
312
316
  if (!token) {
@@ -314,13 +318,13 @@ Please open this URL in your browser:
314
318
  process.exit(1);
315
319
  }
316
320
  const connectToServer = mod.connectToServer || mod.default && mod.default.connectToServer || hyphaWebsocketClient && hyphaWebsocketClient.connectToServer;
317
- const server = await connectToServer({ server_url: serverUrl, token, transport: "http" });
321
+ const server = await connectToServer({ server_url: serverUrl, token });
318
322
  const workspace = server.config.workspace;
319
323
  const userId = server.config.user?.id || workspace;
320
324
  let longLivedToken = token;
321
325
  try {
322
326
  const sixMonthsInSeconds = 6 * 30 * 24 * 60 * 60;
323
- longLivedToken = await server.generateToken({ expires_in: sixMonthsInSeconds });
327
+ longLivedToken = await server.generateToken({ expires_in: sixMonthsInSeconds, workspace });
324
328
  console.log("Generated long-lived token (expires in ~6 months)");
325
329
  } catch (err) {
326
330
  console.warn("Could not generate long-lived token, using login token:", err.message || err);
@@ -364,6 +368,222 @@ You can now start the daemon: svamp daemon start`);
364
368
  process.exit(1);
365
369
  }
366
370
  }
371
+ const LAUNCHD_LABEL = "io.hypha.svamp.daemon";
372
+ async function installDaemonService() {
373
+ const os = await import('os');
374
+ const { join, dirname } = await import('path');
375
+ const fs = await import('fs');
376
+ const { execSync } = await import('child_process');
377
+ const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
378
+ const logsDir = join(svampHome, "logs");
379
+ let svampBinary = process.argv[1];
380
+ try {
381
+ const resolved = execSync(`which svamp`, { encoding: "utf-8" }).trim();
382
+ if (resolved) svampBinary = resolved;
383
+ } catch {
384
+ }
385
+ if (!fs.existsSync(svampBinary)) {
386
+ console.error(`Cannot find svamp binary at: ${svampBinary}`);
387
+ console.error("Please install svamp-cli globally first.");
388
+ process.exit(1);
389
+ }
390
+ const nodeBinDir = dirname(process.execPath);
391
+ const shellPath = process.env.PATH || "";
392
+ const supervisedPath = shellPath.includes(nodeBinDir) ? shellPath : [nodeBinDir, shellPath].filter(Boolean).join(":");
393
+ if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
394
+ if (process.platform === "darwin") {
395
+ const launchAgentsDir = join(os.homedir(), "Library", "LaunchAgents");
396
+ const plistPath = join(launchAgentsDir, `${LAUNCHD_LABEL}.plist`);
397
+ if (!fs.existsSync(launchAgentsDir)) fs.mkdirSync(launchAgentsDir, { recursive: true });
398
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
399
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
400
+ <plist version="1.0">
401
+ <dict>
402
+ <key>Label</key>
403
+ <string>${LAUNCHD_LABEL}</string>
404
+ <key>ProgramArguments</key>
405
+ <array>
406
+ <string>${svampBinary}</string>
407
+ <string>daemon</string>
408
+ <string>start-sync</string>
409
+ </array>
410
+ <key>RunAtLoad</key>
411
+ <true/>
412
+ <key>KeepAlive</key>
413
+ <dict>
414
+ <!-- Only restart on crash (non-zero exit). svamp daemon stop exits 0. -->
415
+ <key>SuccessfulExit</key>
416
+ <false/>
417
+ </dict>
418
+ <key>ThrottleInterval</key>
419
+ <integer>10</integer>
420
+ <key>StandardOutPath</key>
421
+ <string>${logsDir}/daemon-supervised.log</string>
422
+ <key>StandardErrorPath</key>
423
+ <string>${logsDir}/daemon-supervised.log</string>
424
+ <key>EnvironmentVariables</key>
425
+ <dict>
426
+ <key>PATH</key>
427
+ <string>${supervisedPath}</string>
428
+ <key>SVAMP_HOME</key>
429
+ <string>${svampHome}</string>
430
+ <key>SVAMP_SUPERVISED</key>
431
+ <string>1</string>
432
+ </dict>
433
+ </dict>
434
+ </plist>
435
+ `;
436
+ fs.writeFileSync(plistPath, plist, "utf-8");
437
+ console.log(`Plist written: ${plistPath}`);
438
+ try {
439
+ execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
440
+ } catch {
441
+ }
442
+ execSync(`launchctl load "${plistPath}"`);
443
+ console.log(`
444
+ Svamp daemon service installed and started!`);
445
+ console.log(` Service: ${LAUNCHD_LABEL}`);
446
+ console.log(` Auto-restart on crash: enabled`);
447
+ console.log(` Log file: ${logsDir}/daemon-supervised.log`);
448
+ console.log(`
449
+ To stop the service: svamp daemon stop`);
450
+ console.log(`To uninstall: svamp daemon uninstall`);
451
+ } else if (process.platform === "linux") {
452
+ const hasSystemd = (() => {
453
+ try {
454
+ execSync("systemctl --user status 2>/dev/null", { stdio: "pipe" });
455
+ return true;
456
+ } catch {
457
+ return false;
458
+ }
459
+ })();
460
+ if (hasSystemd) {
461
+ const systemdUserDir = join(os.homedir(), ".config", "systemd", "user");
462
+ const unitPath = join(systemdUserDir, "svamp-daemon.service");
463
+ if (!fs.existsSync(systemdUserDir)) fs.mkdirSync(systemdUserDir, { recursive: true });
464
+ const unit = `[Unit]
465
+ Description=Svamp Daemon \u2014 AI workspace on Hypha Cloud
466
+ After=network-online.target
467
+ Wants=network-online.target
468
+
469
+ [Service]
470
+ ExecStart=${svampBinary} daemon start-sync
471
+ Restart=on-failure
472
+ RestartSec=10
473
+ Environment=PATH=${supervisedPath}
474
+ Environment=SVAMP_HOME=${svampHome}
475
+ Environment=SVAMP_SUPERVISED=1
476
+ StandardOutput=append:${logsDir}/daemon-supervised.log
477
+ StandardError=append:${logsDir}/daemon-supervised.log
478
+
479
+ [Install]
480
+ WantedBy=default.target
481
+ `;
482
+ fs.writeFileSync(unitPath, unit, "utf-8");
483
+ execSync("systemctl --user daemon-reload");
484
+ execSync("systemctl --user enable --now svamp-daemon.service");
485
+ console.log(`
486
+ Svamp daemon service installed and started!`);
487
+ console.log(` Service: svamp-daemon.service (systemd user)`);
488
+ console.log(` Unit file: ${unitPath}`);
489
+ console.log(` Auto-restart on crash: enabled`);
490
+ console.log(` Log file: ${logsDir}/daemon-supervised.log`);
491
+ console.log(`
492
+ To stop the service: svamp daemon stop`);
493
+ console.log(`To uninstall: svamp daemon uninstall`);
494
+ } else {
495
+ const wrapperPath = join(svampHome, "daemon-supervisor.sh");
496
+ const wrapper = `#!/bin/sh
497
+ # Svamp daemon supervisor \u2014 restarts on crash. Run this as your container CMD
498
+ # or in a background process. Stop with: svamp daemon stop
499
+ export PATH="${supervisedPath}"
500
+ export SVAMP_HOME="${svampHome}"
501
+ export SVAMP_SUPERVISED=1
502
+
503
+ mkdir -p "${logsDir}"
504
+ echo "[svamp-supervisor] Starting svamp daemon supervisor" >> "${logsDir}/daemon-supervised.log"
505
+
506
+ while true; do
507
+ "${svampBinary}" daemon start-sync >> "${logsDir}/daemon-supervised.log" 2>&1
508
+ EXIT_CODE=$?
509
+ # Exit code 0 = clean stop (svamp daemon stop). Do not restart.
510
+ if [ "$EXIT_CODE" -eq 0 ]; then
511
+ echo "[svamp-supervisor] Daemon stopped cleanly (exit 0). Not restarting." >> "${logsDir}/daemon-supervised.log"
512
+ break
513
+ fi
514
+ echo "[svamp-supervisor] Daemon exited with code $EXIT_CODE. Restarting in 10s..." >> "${logsDir}/daemon-supervised.log"
515
+ sleep 10
516
+ done
517
+ `;
518
+ fs.writeFileSync(wrapperPath, wrapper, { mode: 493 });
519
+ console.log(`
520
+ Svamp daemon supervisor script written!`);
521
+ console.log(` Script: ${wrapperPath}`);
522
+ console.log(` Log file: ${logsDir}/daemon-supervised.log`);
523
+ console.log(`
524
+ To use in Docker, set your container CMD to:`);
525
+ console.log(` ${wrapperPath}`);
526
+ console.log(`
527
+ Or run in the background:`);
528
+ console.log(` nohup ${wrapperPath} &`);
529
+ console.log(`
530
+ To stop the daemon: svamp daemon stop`);
531
+ }
532
+ } else {
533
+ console.error(`Daemon service install is not supported on ${process.platform}.`);
534
+ console.error("Supported platforms: macOS (launchd), Linux (systemd or wrapper script).");
535
+ process.exit(1);
536
+ }
537
+ }
538
+ async function uninstallDaemonService() {
539
+ const os = await import('os');
540
+ const { join } = await import('path');
541
+ const fs = await import('fs');
542
+ const { execSync } = await import('child_process');
543
+ if (process.platform === "darwin") {
544
+ const plistPath = join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
545
+ if (!fs.existsSync(plistPath)) {
546
+ console.log("Svamp daemon service is not installed.");
547
+ return;
548
+ }
549
+ try {
550
+ execSync(`launchctl unload "${plistPath}"`, { stdio: "pipe" });
551
+ } catch {
552
+ }
553
+ fs.unlinkSync(plistPath);
554
+ console.log("Svamp daemon service uninstalled (launchd).");
555
+ console.log("The daemon has been stopped and will no longer auto-start.");
556
+ } else if (process.platform === "linux") {
557
+ const unitPath = join(os.homedir(), ".config", "systemd", "user", "svamp-daemon.service");
558
+ const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
559
+ const wrapperPath = join(svampHome, "daemon-supervisor.sh");
560
+ let removed = false;
561
+ if (fs.existsSync(unitPath)) {
562
+ try {
563
+ execSync("systemctl --user disable --now svamp-daemon.service", { stdio: "pipe" });
564
+ } catch {
565
+ }
566
+ fs.unlinkSync(unitPath);
567
+ try {
568
+ execSync("systemctl --user daemon-reload", { stdio: "pipe" });
569
+ } catch {
570
+ }
571
+ console.log("Svamp daemon service uninstalled (systemd).");
572
+ removed = true;
573
+ }
574
+ if (fs.existsSync(wrapperPath)) {
575
+ fs.unlinkSync(wrapperPath);
576
+ if (!removed) console.log("Svamp daemon supervisor script removed.");
577
+ removed = true;
578
+ }
579
+ if (!removed) {
580
+ console.log("Svamp daemon service is not installed.");
581
+ }
582
+ } else {
583
+ console.error(`Daemon service uninstall is not supported on ${process.platform}.`);
584
+ process.exit(1);
585
+ }
586
+ }
367
587
  function printHelp() {
368
588
  console.log(`
369
589
  svamp \u2014 Svamp CLI with Hypha transport
@@ -373,6 +593,8 @@ Usage:
373
593
  svamp daemon start Start the daemon (detached)
374
594
  svamp daemon stop Stop the daemon
375
595
  svamp daemon status Show daemon status
596
+ svamp daemon install Install as system service (launchd/systemd/wrapper)
597
+ svamp daemon uninstall Remove system service
376
598
  svamp session list List active daemon sessions
377
599
  svamp session spawn Spawn a new session on the daemon
378
600
  svamp session attach <id> Attach to a session (interactive)
@@ -396,9 +618,11 @@ function printDaemonHelp() {
396
618
  svamp daemon \u2014 Daemon management
397
619
 
398
620
  Usage:
399
- svamp daemon start Start the daemon (detached)
400
- svamp daemon stop Stop the daemon
401
- svamp daemon status Show daemon status
621
+ svamp daemon start Start the daemon (detached)
622
+ svamp daemon stop Stop the daemon
623
+ svamp daemon status Show daemon status
624
+ svamp daemon install Install as launchd service (macOS) \u2014 auto-restart on crash
625
+ svamp daemon uninstall Remove launchd service
402
626
  `);
403
627
  }
404
628
  function printSessionHelp() {