svamp-cli 0.1.63 → 0.1.65

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.
@@ -0,0 +1,156 @@
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import os from 'node:os';
4
+
5
+ const SVAMP_HOME = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
6
+ function getConfigPath(sessionId) {
7
+ const cwd = process.cwd();
8
+ return join(cwd, ".svamp", sessionId, "config.json");
9
+ }
10
+ function readConfig(configPath) {
11
+ try {
12
+ if (existsSync(configPath)) return JSON.parse(readFileSync(configPath, "utf-8"));
13
+ } catch {
14
+ }
15
+ return {};
16
+ }
17
+ function writeConfig(configPath, config) {
18
+ mkdirSync(dirname(configPath), { recursive: true });
19
+ const tmp = configPath + ".tmp";
20
+ writeFileSync(tmp, JSON.stringify(config, null, 2));
21
+ renameSync(tmp, configPath);
22
+ }
23
+ async function connectAndEmit(event) {
24
+ const ENV_FILE = join(SVAMP_HOME, ".env");
25
+ if (existsSync(ENV_FILE)) {
26
+ const lines = readFileSync(ENV_FILE, "utf-8").split("\n");
27
+ for (const line of lines) {
28
+ const m = line.match(/^([A-Z_]+)=(.*)/);
29
+ if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, "");
30
+ }
31
+ }
32
+ const serverUrl = process.env.HYPHA_SERVER_URL;
33
+ const token = process.env.HYPHA_TOKEN;
34
+ if (!serverUrl || !token) {
35
+ console.error('No Hypha credentials. Run "svamp login" first.');
36
+ process.exit(1);
37
+ }
38
+ const origLog = console.log;
39
+ const origWarn = console.warn;
40
+ const origInfo = console.info;
41
+ console.log = () => {
42
+ };
43
+ console.warn = () => {
44
+ };
45
+ console.info = () => {
46
+ };
47
+ let server;
48
+ try {
49
+ const mod = await import('hypha-rpc');
50
+ const connectToServer = mod.connectToServer || mod.default?.connectToServer;
51
+ server = await connectToServer({ server_url: serverUrl, token, name: "svamp-agent-emit" });
52
+ } catch (err) {
53
+ console.log = origLog;
54
+ console.warn = origWarn;
55
+ console.info = origInfo;
56
+ console.error(`Failed to connect to Hypha: ${err.message}`);
57
+ process.exit(1);
58
+ }
59
+ console.log = origLog;
60
+ console.warn = origWarn;
61
+ console.info = origInfo;
62
+ await server.emit({ ...event, to: "*" });
63
+ await server.disconnect();
64
+ }
65
+ async function sessionSetTitle(title) {
66
+ const sessionId = process.env.SVAMP_SESSION_ID;
67
+ if (!sessionId) {
68
+ console.error("SVAMP_SESSION_ID not set. This command must be run inside a Svamp session.");
69
+ process.exit(1);
70
+ }
71
+ const configPath = getConfigPath(sessionId);
72
+ const config = readConfig(configPath);
73
+ config.title = title.trim();
74
+ writeConfig(configPath, config);
75
+ console.log(`Session title set: "${title.trim()}"`);
76
+ }
77
+ async function sessionSetLink(url, label) {
78
+ const sessionId = process.env.SVAMP_SESSION_ID;
79
+ if (!sessionId) {
80
+ console.error("SVAMP_SESSION_ID not set. This command must be run inside a Svamp session.");
81
+ process.exit(1);
82
+ }
83
+ const resolvedLabel = label?.trim() || (() => {
84
+ try {
85
+ return new URL(url).hostname;
86
+ } catch {
87
+ return "View";
88
+ }
89
+ })();
90
+ const configPath = getConfigPath(sessionId);
91
+ const config = readConfig(configPath);
92
+ config.session_link = { url: url.trim(), label: resolvedLabel };
93
+ writeConfig(configPath, config);
94
+ console.log(`Session link set: "${resolvedLabel}" \u2192 ${url.trim()}`);
95
+ }
96
+ async function sessionNotify(message, level = "info") {
97
+ const sessionId = process.env.SVAMP_SESSION_ID;
98
+ if (!sessionId) {
99
+ console.error("SVAMP_SESSION_ID not set. This command must be run inside a Svamp session.");
100
+ process.exit(1);
101
+ }
102
+ await connectAndEmit({
103
+ type: "svamp:session-notify",
104
+ data: { sessionId, message, level, timestamp: Date.now() }
105
+ });
106
+ console.log(`Notification sent [${level}]: ${message}`);
107
+ }
108
+ async function sessionBroadcast(action, args) {
109
+ const sessionId = process.env.SVAMP_SESSION_ID;
110
+ if (!sessionId) {
111
+ console.error("SVAMP_SESSION_ID not set. This command must be run inside a Svamp session.");
112
+ process.exit(1);
113
+ }
114
+ let payload = { action };
115
+ if (action === "open-canvas") {
116
+ const url = args[0];
117
+ if (!url) {
118
+ console.error("Usage: svamp session broadcast open-canvas <url> [label]");
119
+ process.exit(1);
120
+ }
121
+ const label = args[1] || (() => {
122
+ try {
123
+ return new URL(url).hostname;
124
+ } catch {
125
+ return "View";
126
+ }
127
+ })();
128
+ payload = { action, url, label };
129
+ } else if (action === "close-canvas") ; else if (action === "toast") {
130
+ const message = args[0];
131
+ if (!message) {
132
+ console.error("Usage: svamp session broadcast toast <message>");
133
+ process.exit(1);
134
+ }
135
+ payload = { action, message };
136
+ } else {
137
+ console.error(`Unknown broadcast action: ${action}`);
138
+ console.error("Available actions: open-canvas, close-canvas, toast");
139
+ process.exit(1);
140
+ }
141
+ await connectAndEmit({
142
+ type: "svamp:session-broadcast",
143
+ data: { sessionId, ...payload }
144
+ });
145
+ console.log(`Broadcast sent: ${action}`);
146
+ }
147
+ async function machineNotify(message, level = "info") {
148
+ const machineId = process.env.SVAMP_MACHINE_ID;
149
+ await connectAndEmit({
150
+ type: "svamp:machine-notify",
151
+ data: { machineId, message, level, timestamp: Date.now() }
152
+ });
153
+ console.log(`Machine notification sent [${level}]: ${message}`);
154
+ }
155
+
156
+ export { machineNotify, sessionBroadcast, sessionNotify, sessionSetLink, sessionSetTitle };
@@ -29,14 +29,21 @@ function requireSandboxApiEnv() {
29
29
  }
30
30
  return env;
31
31
  }
32
- async function sandboxFetch(env, path, init) {
32
+ async function sandboxFetch(env, path, init, timeoutMs = 3e4) {
33
33
  const url = `${env.apiUrl.replace(/\/+$/, "")}${path}`;
34
34
  const headers = {
35
35
  "Authorization": `Bearer ${env.apiKey}`,
36
36
  "Content-Type": "application/json",
37
37
  ...init?.headers || {}
38
38
  };
39
- const res = await fetch(url, { ...init, headers });
39
+ const controller = new AbortController();
40
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
41
+ let res;
42
+ try {
43
+ res = await fetch(url, { ...init, headers, signal: controller.signal });
44
+ } finally {
45
+ clearTimeout(timer);
46
+ }
40
47
  if (!res.ok) {
41
48
  const body = await res.text().catch(() => "");
42
49
  let detail = body;
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-BaMf-bAo.mjs';
1
+ import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-DPhSmbr1.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -106,10 +106,10 @@ async function main() {
106
106
  } else if (subcommand === "skills") {
107
107
  await handleSkillsCommand();
108
108
  } else if (subcommand === "service" || subcommand === "svc") {
109
- const { handleServiceCommand } = await import('./commands-dm3PSNsk.mjs');
109
+ const { handleServiceCommand } = await import('./commands-CF32XIau.mjs');
110
110
  await handleServiceCommand();
111
111
  } else if (subcommand === "process" || subcommand === "proc") {
112
- const { processCommand } = await import('./commands-3Zu9sCxa.mjs');
112
+ const { processCommand } = await import('./commands-Dc_6JdE_.mjs');
113
113
  let machineId;
114
114
  const processArgs = args.slice(1);
115
115
  const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
@@ -127,7 +127,7 @@ async function main() {
127
127
  } else if (!subcommand || subcommand === "start") {
128
128
  await handleInteractiveCommand();
129
129
  } else if (subcommand === "--version" || subcommand === "-v") {
130
- const pkg = await import('./package-7U32bRBY.mjs').catch(() => ({ default: { version: "unknown" } }));
130
+ const pkg = await import('./package-CvCDrFPH.mjs').catch(() => ({ default: { version: "unknown" } }));
131
131
  console.log(`svamp version: ${pkg.default.version}`);
132
132
  } else {
133
133
  console.error(`Unknown command: ${subcommand}`);
@@ -136,7 +136,7 @@ async function main() {
136
136
  }
137
137
  }
138
138
  async function handleInteractiveCommand() {
139
- const { runInteractive } = await import('./run-COqTRwXb.mjs');
139
+ const { runInteractive } = await import('./run-DTwMJ7dx.mjs');
140
140
  const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
141
141
  let directory = process.cwd();
142
142
  let resumeSessionId;
@@ -181,7 +181,7 @@ async function handleAgentCommand() {
181
181
  return;
182
182
  }
183
183
  if (agentArgs[0] === "list") {
184
- const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-BaMf-bAo.mjs').then(function (n) { return n.i; });
184
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.i; });
185
185
  console.log("Known agents:");
186
186
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
187
187
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
@@ -193,7 +193,7 @@ async function handleAgentCommand() {
193
193
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
194
194
  return;
195
195
  }
196
- const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-BaMf-bAo.mjs').then(function (n) { return n.i; });
196
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.i; });
197
197
  let cwd = process.cwd();
198
198
  const filteredArgs = [];
199
199
  for (let i = 0; i < agentArgs.length; i++) {
@@ -217,12 +217,12 @@ async function handleAgentCommand() {
217
217
  console.log(`Starting ${config.agentName} agent in ${cwd}...`);
218
218
  let backend;
219
219
  if (KNOWN_MCP_AGENTS[config.agentName]) {
220
- const { CodexMcpBackend } = await import('./run-BaMf-bAo.mjs').then(function (n) { return n.j; });
220
+ const { CodexMcpBackend } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.j; });
221
221
  backend = new CodexMcpBackend({ cwd, log: logFn });
222
222
  } else {
223
- const { AcpBackend } = await import('./run-BaMf-bAo.mjs').then(function (n) { return n.h; });
224
- const { GeminiTransport } = await import('./run-BaMf-bAo.mjs').then(function (n) { return n.G; });
225
- const { DefaultTransport } = await import('./run-BaMf-bAo.mjs').then(function (n) { return n.D; });
223
+ const { AcpBackend } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.h; });
224
+ const { GeminiTransport } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.G; });
225
+ const { DefaultTransport } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.D; });
226
226
  const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
227
227
  backend = new AcpBackend({
228
228
  agentName: config.agentName,
@@ -340,7 +340,7 @@ async function handleSessionCommand() {
340
340
  printSessionHelp();
341
341
  return;
342
342
  }
343
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-BRow9cGp.mjs');
343
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-FAWhkKtz.mjs');
344
344
  const parseFlagStr = (flag, shortFlag) => {
345
345
  for (let i = 1; i < sessionArgs.length; i++) {
346
346
  if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
@@ -400,7 +400,7 @@ async function handleSessionCommand() {
400
400
  allowDomain.push(sessionArgs[++i]);
401
401
  }
402
402
  }
403
- const { parseShareArg } = await import('./commands-BRow9cGp.mjs');
403
+ const { parseShareArg } = await import('./commands-FAWhkKtz.mjs');
404
404
  const shareEntries = share.map((s) => parseShareArg(s));
405
405
  await sessionSpawn(agent, dir, targetMachineId, {
406
406
  message,
@@ -484,7 +484,7 @@ async function handleSessionCommand() {
484
484
  console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
485
485
  process.exit(1);
486
486
  }
487
- const { sessionApprove } = await import('./commands-BRow9cGp.mjs');
487
+ const { sessionApprove } = await import('./commands-FAWhkKtz.mjs');
488
488
  const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
489
489
  await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
490
490
  json: hasFlag("--json")
@@ -494,7 +494,7 @@ async function handleSessionCommand() {
494
494
  console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
495
495
  process.exit(1);
496
496
  }
497
- const { sessionDeny } = await import('./commands-BRow9cGp.mjs');
497
+ const { sessionDeny } = await import('./commands-FAWhkKtz.mjs');
498
498
  const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
499
499
  await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
500
500
  json: hasFlag("--json")
@@ -524,6 +524,40 @@ async function handleSessionCommand() {
524
524
  process.exit(1);
525
525
  }
526
526
  await sessionRalphStatus(sessionArgs[1], targetMachineId);
527
+ } else if (sessionSubcommand === "set-title") {
528
+ const title = sessionArgs[1];
529
+ if (!title) {
530
+ console.error("Usage: svamp session set-title <title>");
531
+ process.exit(1);
532
+ }
533
+ const { sessionSetTitle } = await import('./agentCommands-C6iGblcL.mjs');
534
+ await sessionSetTitle(title);
535
+ } else if (sessionSubcommand === "set-link") {
536
+ const url = sessionArgs[1];
537
+ if (!url) {
538
+ console.error("Usage: svamp session set-link <url> [label]");
539
+ process.exit(1);
540
+ }
541
+ const label = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
542
+ const { sessionSetLink } = await import('./agentCommands-C6iGblcL.mjs');
543
+ await sessionSetLink(url, label);
544
+ } else if (sessionSubcommand === "notify") {
545
+ const message = sessionArgs[1];
546
+ if (!message) {
547
+ console.error("Usage: svamp session notify <message> [--level info|warning|error]");
548
+ process.exit(1);
549
+ }
550
+ const level = parseFlagStr("--level") || "info";
551
+ const { sessionNotify } = await import('./agentCommands-C6iGblcL.mjs');
552
+ await sessionNotify(message, level);
553
+ } else if (sessionSubcommand === "broadcast") {
554
+ const action = sessionArgs[1];
555
+ if (!action) {
556
+ console.error("Usage: svamp session broadcast <action> [args...]\nActions: open-canvas <url> [label], close-canvas, toast <message>");
557
+ process.exit(1);
558
+ }
559
+ const { sessionBroadcast } = await import('./agentCommands-C6iGblcL.mjs');
560
+ await sessionBroadcast(action, sessionArgs.slice(2).filter((a) => !a.startsWith("--")));
527
561
  } else if (sessionSubcommand === "queue") {
528
562
  const queueSubcmd = sessionArgs[1];
529
563
  if (queueSubcmd === "add") {
@@ -563,7 +597,7 @@ async function handleMachineCommand() {
563
597
  return;
564
598
  }
565
599
  if (machineSubcommand === "share") {
566
- const { machineShare } = await import('./commands-BRow9cGp.mjs');
600
+ const { machineShare } = await import('./commands-FAWhkKtz.mjs');
567
601
  let machineId;
568
602
  const shareArgs = [];
569
603
  for (let i = 1; i < machineArgs.length; i++) {
@@ -593,7 +627,7 @@ async function handleMachineCommand() {
593
627
  }
594
628
  await machineShare(machineId, { add, remove, list, configPath, showConfig });
595
629
  } else if (machineSubcommand === "exec") {
596
- const { machineExec } = await import('./commands-BRow9cGp.mjs');
630
+ const { machineExec } = await import('./commands-FAWhkKtz.mjs');
597
631
  let machineId;
598
632
  let cwd;
599
633
  const cmdParts = [];
@@ -613,7 +647,7 @@ async function handleMachineCommand() {
613
647
  }
614
648
  await machineExec(machineId, command, cwd);
615
649
  } else if (machineSubcommand === "info") {
616
- const { machineInfo } = await import('./commands-BRow9cGp.mjs');
650
+ const { machineInfo } = await import('./commands-FAWhkKtz.mjs');
617
651
  let machineId;
618
652
  for (let i = 1; i < machineArgs.length; i++) {
619
653
  if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
@@ -621,8 +655,22 @@ async function handleMachineCommand() {
621
655
  }
622
656
  }
623
657
  await machineInfo(machineId);
658
+ } else if (machineSubcommand === "notify") {
659
+ const message = machineArgs[1];
660
+ if (!message) {
661
+ console.error("Usage: svamp machine notify <message> [--level info|warning|error]");
662
+ process.exit(1);
663
+ }
664
+ let level = "info";
665
+ for (let i = 2; i < machineArgs.length; i++) {
666
+ if (machineArgs[i] === "--level" && i + 1 < machineArgs.length) {
667
+ level = machineArgs[++i];
668
+ }
669
+ }
670
+ const { machineNotify } = await import('./agentCommands-C6iGblcL.mjs');
671
+ await machineNotify(message, level);
624
672
  } else if (machineSubcommand === "ls") {
625
- const { machineLs } = await import('./commands-BRow9cGp.mjs');
673
+ const { machineLs } = await import('./commands-FAWhkKtz.mjs');
626
674
  let machineId;
627
675
  let showHidden = false;
628
676
  let path;
@@ -650,7 +698,7 @@ async function handleSkillsCommand() {
650
698
  printSkillsHelp();
651
699
  return;
652
700
  }
653
- const { skillsFind, skillsInstall, skillsList, skillsRemove, skillsPublish } = await import('./commands-6EyqaoCp.mjs');
701
+ const { skillsFind, skillsInstall, skillsList, skillsRemove, skillsPublish } = await import('./commands-UFi0_ESV.mjs');
654
702
  if (skillsSubcommand === "find" || skillsSubcommand === "search") {
655
703
  const query = skillsArgs.slice(1).filter((a) => !a.startsWith("--")).join(" ");
656
704
  if (!query) {
@@ -1021,6 +1069,11 @@ Usage:
1021
1069
  svamp skills find <query> Search the skills marketplace
1022
1070
  svamp skills install <n> Install a skill from the marketplace
1023
1071
  svamp skills --help Show all skill commands
1072
+ svamp process apply <f.yaml> Create/update a supervised process (idempotent)
1073
+ svamp process list List supervised processes
1074
+ svamp process start <name> Start or create a process
1075
+ svamp process logs <name> Show process log output
1076
+ svamp process --help Show all process commands
1024
1077
  svamp service expose <name> Expose a service from this sandbox
1025
1078
  svamp service list List service groups
1026
1079
  svamp service --help Show all service commands
@@ -1,4 +1,4 @@
1
- import { createServiceGroup, listServiceGroups, getServiceGroup, deleteServiceGroup, addBackend, removeBackend, addPort, removePort, renameSubdomain, getSandboxEnv } from './api-Cegey1dh.mjs';
1
+ import { createServiceGroup, listServiceGroups, getServiceGroup, deleteServiceGroup, addBackend, removeBackend, addPort, removePort, renameSubdomain, getSandboxEnv } from './api-BRbsyqJ4.mjs';
2
2
 
3
3
  function getFlag(args, flag) {
4
4
  const idx = args.indexOf(flag);
@@ -296,7 +296,7 @@ Service is live:`);
296
296
  }
297
297
  } else {
298
298
  console.log(`No SANDBOX_ID detected \u2014 starting reverse tunnel.`);
299
- const { runTunnel } = await import('./tunnel-dl6vFKgd.mjs');
299
+ const { runTunnel } = await import('./tunnel-C3UsqTxi.mjs');
300
300
  await runTunnel(name, ports);
301
301
  }
302
302
  } catch (err) {
@@ -312,7 +312,7 @@ async function serviceTunnel(args) {
312
312
  console.error("Usage: svamp service tunnel <name> --port <port> [--port <port2>]");
313
313
  process.exit(1);
314
314
  }
315
- const { runTunnel } = await import('./tunnel-dl6vFKgd.mjs');
315
+ const { runTunnel } = await import('./tunnel-C3UsqTxi.mjs');
316
316
  await runTunnel(name, ports);
317
317
  }
318
318
  async function handleServiceCommand() {
@@ -1,11 +1,11 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-BRow9cGp.mjs';
3
+ import { connectAndGetMachine } from './commands-FAWhkKtz.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:child_process';
6
6
  import 'node:path';
7
7
  import 'node:os';
8
- import './run-BaMf-bAo.mjs';
8
+ import './run-DPhSmbr1.mjs';
9
9
  import 'os';
10
10
  import 'fs/promises';
11
11
  import 'url';
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { resolve, join } from 'node:path';
4
4
  import os from 'node:os';
5
- import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-BaMf-bAo.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-DPhSmbr1.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
@@ -9,6 +9,18 @@ const SKILLS_DIR = join(os__default.homedir(), ".claude", "skills");
9
9
  function getArtifactBaseUrl() {
10
10
  return `${SKILLS_SERVER}/${SKILLS_WORKSPACE}/artifacts`;
11
11
  }
12
+ async function fetchWithTimeout(url, options = {}, timeoutMs = 3e4) {
13
+ const controller = new AbortController();
14
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
15
+ try {
16
+ return await fetch(url, { ...options, signal: controller.signal });
17
+ } catch (err) {
18
+ if (err.name === "AbortError") throw new Error(`Request timed out after ${timeoutMs}ms: ${url}`);
19
+ throw err;
20
+ } finally {
21
+ clearTimeout(timer);
22
+ }
23
+ }
12
24
  function parseFrontmatter(content) {
13
25
  const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
14
26
  if (!match) return null;
@@ -54,7 +66,7 @@ async function searchSkills(query) {
54
66
  const base = getArtifactBaseUrl();
55
67
  const filters = encodeURIComponent(JSON.stringify({ type: "skill" }));
56
68
  const url = `${base}/${SKILLS_COLLECTION}/children?keywords=${encodeURIComponent(query)}&filters=${filters}&limit=50`;
57
- const resp = await fetch(url);
69
+ const resp = await fetchWithTimeout(url);
58
70
  if (!resp.ok) {
59
71
  if (resp.status === 404) return [];
60
72
  throw new Error(`Search failed: ${resp.status} ${resp.statusText}`);
@@ -65,7 +77,7 @@ async function searchSkills(query) {
65
77
  async function getSkillInfo(skillAlias) {
66
78
  const base = getArtifactBaseUrl();
67
79
  const url = `${base}/${skillAlias}`;
68
- const resp = await fetch(url);
80
+ const resp = await fetchWithTimeout(url);
69
81
  if (!resp.ok) {
70
82
  if (resp.status === 404) return null;
71
83
  throw new Error(`Get skill failed: ${resp.status} ${resp.statusText}`);
@@ -77,7 +89,7 @@ async function listSkillFiles(skillAlias, dir = "") {
77
89
  const base = getArtifactBaseUrl();
78
90
  const pathPart = dir ? `/${dir}/` : "/";
79
91
  const url = `${base}/${skillAlias}/files${pathPart}`;
80
- const resp = await fetch(url);
92
+ const resp = await fetchWithTimeout(url);
81
93
  if (!resp.ok) {
82
94
  throw new Error(`List files failed: ${resp.status} ${resp.statusText}`);
83
95
  }
@@ -87,7 +99,7 @@ async function listSkillFiles(skillAlias, dir = "") {
87
99
  async function downloadSkillFile(skillAlias, filePath) {
88
100
  const base = getArtifactBaseUrl();
89
101
  const url = `${base}/${skillAlias}/files/${filePath}`;
90
- const resp = await fetch(url, { redirect: "follow" });
102
+ const resp = await fetchWithTimeout(url, { redirect: "follow" }, 6e4);
91
103
  if (!resp.ok) {
92
104
  throw new Error(`Download failed for ${filePath}: ${resp.status} ${resp.statusText}`);
93
105
  }
@@ -221,6 +233,10 @@ async function skillsInstall(skillName, opts) {
221
233
  try {
222
234
  const content = await downloadSkillFile(skillName, filePath);
223
235
  const localPath = join(targetDir, filePath);
236
+ if (!localPath.startsWith(targetDir + "/")) {
237
+ errors.push(` ${filePath}: path outside skill directory (blocked)`);
238
+ continue;
239
+ }
224
240
  fs.mkdirSync(join(localPath, ".."), { recursive: true });
225
241
  fs.writeFileSync(localPath, content, "utf-8");
226
242
  downloaded++;
@@ -229,12 +245,10 @@ async function skillsInstall(skillName, opts) {
229
245
  }
230
246
  }
231
247
  if (errors.length > 0) {
232
- console.error(`Warning: ${errors.length} file(s) failed to download:`);
233
- errors.forEach((e) => console.error(e));
234
- }
235
- if (downloaded === 0) {
236
248
  fs.rmSync(targetDir, { recursive: true, force: true });
237
- console.error("Failed to download any files. Installation aborted.");
249
+ console.error(`Installation failed \u2014 ${errors.length} file(s) could not be downloaded:`);
250
+ errors.forEach((e) => console.error(e));
251
+ console.error("Partial installation cleaned up. Run the command again to retry.");
238
252
  process.exit(1);
239
253
  }
240
254
  const skillMdPath = join(targetDir, "SKILL.md");
@@ -383,25 +397,35 @@ async function skillsPublish(skillPath, opts) {
383
397
  }
384
398
  let artifactId;
385
399
  let isUpdate = false;
400
+ let existingArtifact = null;
386
401
  try {
387
- const existing = await am.read({
402
+ existingArtifact = await am.read({
388
403
  artifact_id: `${SKILLS_WORKSPACE}/${manifest.name}`,
389
404
  _rkwargs: true
390
405
  });
391
- artifactId = existing.id;
406
+ } catch {
407
+ }
408
+ if (existingArtifact) {
409
+ artifactId = existingArtifact.id;
392
410
  isUpdate = true;
393
411
  console.log(`Updating existing skill "${manifest.name}"...`);
394
- await am.edit({
395
- artifact_id: artifactId,
396
- manifest: {
397
- name: manifest.name,
398
- description: manifest.description,
399
- ...manifest.metadata && { metadata: manifest.metadata }
400
- },
401
- stage: true,
402
- _rkwargs: true
403
- });
404
- } catch {
412
+ try {
413
+ await am.edit({
414
+ artifact_id: artifactId,
415
+ manifest: {
416
+ name: manifest.name,
417
+ description: manifest.description,
418
+ ...manifest.metadata && { metadata: manifest.metadata }
419
+ },
420
+ stage: true,
421
+ _rkwargs: true
422
+ });
423
+ } catch (err) {
424
+ console.error(`Failed to stage skill artifact for editing: ${err.message}`);
425
+ await server.disconnect();
426
+ process.exit(1);
427
+ }
428
+ } else {
405
429
  try {
406
430
  const created = await am.create({
407
431
  alias: manifest.name,
@@ -441,11 +465,11 @@ async function skillsPublish(skillPath, opts) {
441
465
  uploadErrors.push(`${relPath}: failed to get upload URL`);
442
466
  continue;
443
467
  }
444
- const resp = await fetch(putUrl, {
468
+ const resp = await fetchWithTimeout(putUrl, {
445
469
  method: "PUT",
446
470
  body: content,
447
471
  headers: { "Content-Type": "application/octet-stream" }
448
- });
472
+ }, 12e4);
449
473
  if (!resp.ok) {
450
474
  uploadErrors.push(`${relPath}: upload returned ${resp.status}`);
451
475
  continue;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-BaMf-bAo.mjs';
1
+ export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-DPhSmbr1.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.1.63";
2
+ var version = "0.1.65";
3
3
  var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
4
  var author = "Amun AI AB";
5
5
  var license = "SEE LICENSE IN LICENSE";
@@ -19,7 +19,7 @@ var exports$1 = {
19
19
  var scripts = {
20
20
  build: "rm -rf dist && tsc --noEmit && pkgroll",
21
21
  typecheck: "tsc --noEmit",
22
- test: "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-tunnel-proxy.mjs",
22
+ test: "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs",
23
23
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
24
  dev: "tsx src/cli.ts",
25
25
  "dev:daemon": "tsx src/cli.ts daemon start-sync",