svamp-cli 0.1.75 → 0.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agentCommands-C6iGblcL.mjs → agentCommands-uNFhhdN1.mjs} +108 -1
- package/dist/cli.mjs +254 -95
- package/dist/{commands-a7p1jW-t.mjs → commands-B6FEeZeP.mjs} +129 -61
- package/dist/{commands-BLjcT1Vl.mjs → commands-BYbuedOK.mjs} +68 -4
- package/dist/{commands-Dq8WSqvt.mjs → commands-Cf3mXxPZ.mjs} +2 -2
- package/dist/{commands-UFi0_ESV.mjs → commands-DJoYOM_1.mjs} +25 -25
- package/dist/index.mjs +1 -1
- package/dist/{package-BFnad6d1.mjs → package-DTOqWYBv.mjs} +4 -4
- package/dist/{run-Dy5lxT3M.mjs → run-DqvxMsWh.mjs} +1 -1
- package/dist/{run-lhAjX4NB.mjs → run-DsXDjwLW.mjs} +369 -84
- package/dist/staticServer-CWcmMF5V.mjs +477 -0
- package/dist/{tunnel-C2kqST5d.mjs → tunnel-BDKdemh0.mjs} +51 -9
- package/package.json +4 -4
|
@@ -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-
|
|
5
|
+
import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-DsXDjwLW.mjs';
|
|
6
6
|
import 'os';
|
|
7
7
|
import 'fs/promises';
|
|
8
8
|
import 'fs';
|
|
@@ -613,12 +613,11 @@ function extractMessageText(msg) {
|
|
|
613
613
|
createdAt: msg.createdAt || 0
|
|
614
614
|
};
|
|
615
615
|
}
|
|
616
|
-
async function waitForIdle(
|
|
617
|
-
const svc = await server.getService(`svamp-session-${sessionId}`);
|
|
616
|
+
async function waitForIdle(machine, sessionId, timeoutMs) {
|
|
618
617
|
const pollInterval = 2e3;
|
|
619
618
|
const deadline = Date.now() + timeoutMs;
|
|
620
619
|
while (Date.now() < deadline) {
|
|
621
|
-
const activity = await
|
|
620
|
+
const activity = await machine.sessionRPC(sessionId, "getActivityState", []);
|
|
622
621
|
if (activity?.pendingPermissions?.length > 0) {
|
|
623
622
|
return { idle: false, pendingPermissions: activity.pendingPermissions };
|
|
624
623
|
}
|
|
@@ -629,14 +628,13 @@ async function waitForIdle(server, sessionId, timeoutMs) {
|
|
|
629
628
|
}
|
|
630
629
|
throw new Error("Timeout waiting for agent to become idle");
|
|
631
630
|
}
|
|
632
|
-
async function waitForBusyThenIdle(
|
|
633
|
-
const svc = await server.getService(`svamp-session-${sessionId}`);
|
|
631
|
+
async function waitForBusyThenIdle(machine, sessionId, timeoutMs = 3e5, busyTimeoutMs = 1e4) {
|
|
634
632
|
const pollInterval = 2e3;
|
|
635
633
|
const deadline = Date.now() + timeoutMs;
|
|
636
634
|
const busyDeadline = Date.now() + busyTimeoutMs;
|
|
637
635
|
let sawBusy = false;
|
|
638
636
|
while (Date.now() < deadline) {
|
|
639
|
-
const activity = await
|
|
637
|
+
const activity = await machine.sessionRPC(sessionId, "getActivityState", []);
|
|
640
638
|
if (activity?.pendingPermissions?.length > 0) {
|
|
641
639
|
return { idle: false, pendingPermissions: activity.pendingPermissions };
|
|
642
640
|
}
|
|
@@ -934,7 +932,7 @@ async function sessionSpawn(agent, directory, machineId, opts) {
|
|
|
934
932
|
console.log(`Message sent (seq: ${sendResult.seq})`);
|
|
935
933
|
if (opts.wait) {
|
|
936
934
|
console.log("Waiting for agent to become idle...");
|
|
937
|
-
await waitForBusyThenIdle(
|
|
935
|
+
await waitForBusyThenIdle(machine, result.sessionId);
|
|
938
936
|
console.log("Agent is idle.");
|
|
939
937
|
}
|
|
940
938
|
}
|
|
@@ -1268,18 +1266,22 @@ async function sessionSend(sessionId, message, machineId, opts) {
|
|
|
1268
1266
|
const sessions = await machine.listSessions();
|
|
1269
1267
|
const match = resolveSessionId(sessions, sessionId);
|
|
1270
1268
|
const fullId = match.sessionId;
|
|
1271
|
-
const
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1269
|
+
const { randomUUID } = await import('node:crypto');
|
|
1270
|
+
const inboxMessage = {
|
|
1271
|
+
messageId: randomUUID(),
|
|
1272
|
+
body: message,
|
|
1273
|
+
timestamp: Date.now(),
|
|
1274
|
+
read: false,
|
|
1275
|
+
from: `cli:${os.userInfo().username}`,
|
|
1276
|
+
to: fullId,
|
|
1277
|
+
subject: opts?.subject,
|
|
1278
|
+
urgency: opts?.urgency || "urgent"
|
|
1279
|
+
};
|
|
1280
|
+
const result = await machine.sessionRPC(fullId, "sendInboxMessage", [inboxMessage]);
|
|
1279
1281
|
let waitResult;
|
|
1280
1282
|
if (opts?.wait) {
|
|
1281
1283
|
const timeoutMs = (opts.timeout || 300) * 1e3;
|
|
1282
|
-
waitResult = await waitForBusyThenIdle(
|
|
1284
|
+
waitResult = await waitForBusyThenIdle(machine, fullId, timeoutMs);
|
|
1283
1285
|
}
|
|
1284
1286
|
if (waitResult?.pendingPermissions?.length) {
|
|
1285
1287
|
if (opts?.json) {
|
|
@@ -1287,12 +1289,12 @@ async function sessionSend(sessionId, message, machineId, opts) {
|
|
|
1287
1289
|
sessionId: fullId,
|
|
1288
1290
|
message,
|
|
1289
1291
|
sent: true,
|
|
1290
|
-
|
|
1292
|
+
messageId: result.messageId,
|
|
1291
1293
|
status: "permission-pending",
|
|
1292
1294
|
pendingPermissions: waitResult.pendingPermissions
|
|
1293
1295
|
}));
|
|
1294
1296
|
} else {
|
|
1295
|
-
console.log(`Message sent to session ${fullId.slice(0, 8)} (
|
|
1297
|
+
console.log(`Message sent to session ${fullId.slice(0, 8)} (id: ${result.messageId.slice(0, 8)})`);
|
|
1296
1298
|
console.log("Agent is waiting for permission approval:");
|
|
1297
1299
|
for (const p of waitResult.pendingPermissions) {
|
|
1298
1300
|
const argsStr = JSON.stringify(p.arguments || {}).slice(0, 120);
|
|
@@ -1307,12 +1309,12 @@ Use: svamp session approve ${fullId.slice(0, 8)}`);
|
|
|
1307
1309
|
sessionId: fullId,
|
|
1308
1310
|
message,
|
|
1309
1311
|
sent: true,
|
|
1310
|
-
|
|
1312
|
+
messageId: result.messageId,
|
|
1311
1313
|
waited: !!opts.wait,
|
|
1312
1314
|
status: opts.wait ? "idle" : "sent"
|
|
1313
1315
|
}));
|
|
1314
1316
|
} else {
|
|
1315
|
-
console.log(`Message sent to session ${fullId.slice(0, 8)} (
|
|
1317
|
+
console.log(`Message sent to session ${fullId.slice(0, 8)} (id: ${result.messageId.slice(0, 8)})`);
|
|
1316
1318
|
if (opts?.wait) {
|
|
1317
1319
|
console.log("Agent is idle.");
|
|
1318
1320
|
}
|
|
@@ -1328,7 +1330,7 @@ async function sessionWait(sessionId, machineId, opts) {
|
|
|
1328
1330
|
const match = resolveSessionId(sessions, sessionId);
|
|
1329
1331
|
const fullId = match.sessionId;
|
|
1330
1332
|
const timeoutMs = (opts?.timeout || 300) * 1e3;
|
|
1331
|
-
const result = await waitForIdle(
|
|
1333
|
+
const result = await waitForIdle(machine, fullId, timeoutMs);
|
|
1332
1334
|
if (result.pendingPermissions?.length) {
|
|
1333
1335
|
if (opts?.json) {
|
|
1334
1336
|
console.log(formatJson({
|
|
@@ -1706,78 +1708,144 @@ async function sessionRalphStatus(sessionIdPartial, machineId) {
|
|
|
1706
1708
|
await server.disconnect();
|
|
1707
1709
|
}
|
|
1708
1710
|
}
|
|
1709
|
-
async function
|
|
1711
|
+
async function sessionInboxSend(sessionIdPartial, body, machineId, opts) {
|
|
1710
1712
|
const { server, machine } = await connectAndGetMachine(machineId);
|
|
1711
1713
|
try {
|
|
1712
1714
|
const sessions = await machine.listSessions();
|
|
1713
1715
|
const match = resolveSessionId(sessions, sessionIdPartial);
|
|
1714
1716
|
const fullId = match.sessionId;
|
|
1715
|
-
const
|
|
1716
|
-
const
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1717
|
+
const { randomUUID } = await import('node:crypto');
|
|
1718
|
+
const message = {
|
|
1719
|
+
messageId: randomUUID(),
|
|
1720
|
+
body,
|
|
1721
|
+
timestamp: Date.now(),
|
|
1722
|
+
read: false,
|
|
1723
|
+
from: `cli:${os.userInfo().username}`,
|
|
1724
|
+
to: fullId,
|
|
1725
|
+
subject: opts?.subject,
|
|
1726
|
+
urgency: opts?.urgency || "normal",
|
|
1727
|
+
replyTo: opts?.replyTo,
|
|
1728
|
+
threadId: opts?.threadId
|
|
1722
1729
|
};
|
|
1723
|
-
const
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
console.
|
|
1728
|
-
process.exit(1);
|
|
1730
|
+
const result = await machine.sessionRPC(fullId, "sendInboxMessage", [message]);
|
|
1731
|
+
if (opts?.json) {
|
|
1732
|
+
console.log(formatJson({ sessionId: fullId, messageId: result.messageId, sent: true }));
|
|
1733
|
+
} else {
|
|
1734
|
+
console.log(`Inbox message sent to session ${fullId.slice(0, 8)} (id: ${result.messageId.slice(0, 8)})`);
|
|
1729
1735
|
}
|
|
1730
|
-
console.log(`Message queued on session ${fullId.slice(0, 8)} (${updatedQueue.length} total)`);
|
|
1731
1736
|
} finally {
|
|
1732
1737
|
await server.disconnect();
|
|
1733
1738
|
}
|
|
1734
1739
|
}
|
|
1735
|
-
async function
|
|
1740
|
+
async function sessionInboxList(sessionIdPartial, machineId, opts) {
|
|
1736
1741
|
const { server, machine } = await connectAndGetMachine(machineId);
|
|
1737
1742
|
try {
|
|
1738
1743
|
const sessions = await machine.listSessions();
|
|
1739
1744
|
const match = resolveSessionId(sessions, sessionIdPartial);
|
|
1740
1745
|
const fullId = match.sessionId;
|
|
1741
|
-
const
|
|
1742
|
-
const
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
+
const result = await machine.sessionRPC(fullId, "getInbox", [{ unread: opts?.unread, limit: opts?.limit }]);
|
|
1747
|
+
const messages = result.messages;
|
|
1748
|
+
if (opts?.json) {
|
|
1749
|
+
console.log(formatJson({ sessionId: fullId, messages }));
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
if (messages.length === 0) {
|
|
1753
|
+
console.log(`Inbox for session ${fullId.slice(0, 8)} is empty.`);
|
|
1746
1754
|
return;
|
|
1747
1755
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1756
|
+
const unreadCount = messages.filter((m) => !m.read).length;
|
|
1757
|
+
console.log(`Inbox for session ${fullId.slice(0, 8)} (${messages.length} message(s), ${unreadCount} unread):
|
|
1758
|
+
`);
|
|
1759
|
+
for (const msg of messages) {
|
|
1760
|
+
const status = msg.read ? " " : "\u25CF";
|
|
1761
|
+
const from = msg.from ? ` from ${msg.from}` : "";
|
|
1762
|
+
const subject = msg.subject ? ` \u2014 ${msg.subject}` : "";
|
|
1763
|
+
const urgencyTag = msg.urgency === "urgent" ? " [URGENT]" : "";
|
|
1764
|
+
const date = new Date(msg.timestamp).toLocaleString();
|
|
1765
|
+
const preview = msg.body.length > 100 ? msg.body.slice(0, 97) + "..." : msg.body;
|
|
1766
|
+
console.log(` ${status} ${msg.messageId.slice(0, 8)}${urgencyTag}${from}${subject}`);
|
|
1767
|
+
console.log(` ${date}`);
|
|
1768
|
+
console.log(` ${preview}
|
|
1769
|
+
`);
|
|
1753
1770
|
}
|
|
1754
1771
|
} finally {
|
|
1755
1772
|
await server.disconnect();
|
|
1756
1773
|
}
|
|
1757
1774
|
}
|
|
1758
|
-
async function
|
|
1775
|
+
async function sessionInboxRead(sessionIdPartial, messageId, machineId) {
|
|
1759
1776
|
const { server, machine } = await connectAndGetMachine(machineId);
|
|
1760
1777
|
try {
|
|
1761
1778
|
const sessions = await machine.listSessions();
|
|
1762
1779
|
const match = resolveSessionId(sessions, sessionIdPartial);
|
|
1763
1780
|
const fullId = match.sessionId;
|
|
1764
|
-
const
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1781
|
+
const result = await machine.sessionRPC(fullId, "getInbox", []);
|
|
1782
|
+
const msg = result.messages.find((m) => m.messageId === messageId || m.messageId.startsWith(messageId));
|
|
1783
|
+
if (!msg) {
|
|
1784
|
+
console.error(`Message ${messageId} not found in inbox.`);
|
|
1785
|
+
process.exit(1);
|
|
1786
|
+
}
|
|
1787
|
+
if (!msg.read) {
|
|
1788
|
+
await machine.sessionRPC(fullId, "markInboxRead", [msg.messageId]);
|
|
1770
1789
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1790
|
+
console.log(`From: ${msg.from || "(unknown)"}`);
|
|
1791
|
+
if (msg.subject) console.log(`Subject: ${msg.subject}`);
|
|
1792
|
+
console.log(`Date: ${new Date(msg.timestamp).toLocaleString()}`);
|
|
1793
|
+
if (msg.urgency === "urgent") console.log("Urgency: URGENT");
|
|
1794
|
+
if (msg.replyTo) console.log(`Reply-To: ${msg.replyTo.slice(0, 8)}`);
|
|
1795
|
+
if (msg.threadId) console.log(`Thread: ${msg.threadId.slice(0, 8)}`);
|
|
1796
|
+
console.log(`
|
|
1797
|
+
${msg.body}`);
|
|
1798
|
+
} finally {
|
|
1799
|
+
await server.disconnect();
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
async function sessionInboxReply(sessionIdPartial, messageId, body, machineId) {
|
|
1803
|
+
const { server, machine } = await connectAndGetMachine(machineId);
|
|
1804
|
+
try {
|
|
1805
|
+
const sessions = await machine.listSessions();
|
|
1806
|
+
const match = resolveSessionId(sessions, sessionIdPartial);
|
|
1807
|
+
const fullId = match.sessionId;
|
|
1808
|
+
const result = await machine.sessionRPC(fullId, "getInbox", []);
|
|
1809
|
+
const original = result.messages.find((m) => m.messageId === messageId || m.messageId.startsWith(messageId));
|
|
1810
|
+
if (!original) {
|
|
1811
|
+
console.error(`Message ${messageId} not found in inbox.`);
|
|
1775
1812
|
process.exit(1);
|
|
1776
1813
|
}
|
|
1777
|
-
|
|
1814
|
+
if (!original.fromSession) {
|
|
1815
|
+
console.error("Cannot reply: original message has no fromSession.");
|
|
1816
|
+
process.exit(1);
|
|
1817
|
+
}
|
|
1818
|
+
const { randomUUID } = await import('node:crypto');
|
|
1819
|
+
const reply = {
|
|
1820
|
+
messageId: randomUUID(),
|
|
1821
|
+
body,
|
|
1822
|
+
timestamp: Date.now(),
|
|
1823
|
+
read: false,
|
|
1824
|
+
from: `session:${fullId}`,
|
|
1825
|
+
fromSession: fullId,
|
|
1826
|
+
to: original.fromSession,
|
|
1827
|
+
subject: original.subject ? `Re: ${original.subject}` : void 0,
|
|
1828
|
+
urgency: "normal",
|
|
1829
|
+
replyTo: original.messageId,
|
|
1830
|
+
threadId: original.threadId || original.messageId
|
|
1831
|
+
};
|
|
1832
|
+
const sendResult = await machine.sessionRPC(original.fromSession, "sendInboxMessage", [reply]);
|
|
1833
|
+
console.log(`Reply sent to session ${original.fromSession.slice(0, 8)} (id: ${sendResult.messageId.slice(0, 8)})`);
|
|
1834
|
+
} finally {
|
|
1835
|
+
await server.disconnect();
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
async function sessionInboxClear(sessionIdPartial, machineId, opts) {
|
|
1839
|
+
const { server, machine } = await connectAndGetMachine(machineId);
|
|
1840
|
+
try {
|
|
1841
|
+
const sessions = await machine.listSessions();
|
|
1842
|
+
const match = resolveSessionId(sessions, sessionIdPartial);
|
|
1843
|
+
const fullId = match.sessionId;
|
|
1844
|
+
const result = await machine.sessionRPC(fullId, "clearInbox", [{ all: opts?.all }]);
|
|
1845
|
+
console.log(`Cleared inbox on session ${fullId.slice(0, 8)} (${result.remaining} remaining)`);
|
|
1778
1846
|
} finally {
|
|
1779
1847
|
await server.disconnect();
|
|
1780
1848
|
}
|
|
1781
1849
|
}
|
|
1782
1850
|
|
|
1783
|
-
export { connectAndGetMachine, createWorktree, generateWorktreeName, machineExec, machineInfo, machineLs, machineShare, parseShareArg, renderMessage, resolveSessionId, sessionApprove, sessionAttach, sessionDeny,
|
|
1851
|
+
export { connectAndGetMachine, createWorktree, generateWorktreeName, machineExec, machineInfo, machineLs, machineShare, parseShareArg, renderMessage, resolveSessionId, sessionApprove, sessionAttach, sessionDeny, sessionInboxClear, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxSend, sessionInfo, sessionList, sessionMachines, sessionMessages, sessionRalphCancel, sessionRalphStart, sessionRalphStatus, sessionSend, sessionShare, sessionSpawn, sessionStop, sessionWait };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createServiceGroup, listServiceGroups, getServiceGroup, deleteServiceGroup, addBackend, removeBackend, addPort, removePort, renameSubdomain, getSandboxEnv } from './api-BRbsyqJ4.mjs';
|
|
1
|
+
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);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-
|
|
299
|
+
const { runTunnel } = await import('./tunnel-BDKdemh0.mjs');
|
|
300
300
|
await runTunnel(name, ports);
|
|
301
301
|
}
|
|
302
302
|
} catch (err) {
|
|
@@ -304,6 +304,61 @@ Service is live:`);
|
|
|
304
304
|
process.exit(1);
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
|
+
async function serviceServe(args) {
|
|
308
|
+
const positional = positionalArgs(args);
|
|
309
|
+
const name = positional[0];
|
|
310
|
+
const directory = positional[1] || ".";
|
|
311
|
+
const subdomain = getFlag(args, "--subdomain");
|
|
312
|
+
const healthPath = getFlag(args, "--health-path");
|
|
313
|
+
const healthIntervalStr = getFlag(args, "--health-interval");
|
|
314
|
+
const noListing = hasFlag(args, "--no-listing");
|
|
315
|
+
if (!name) {
|
|
316
|
+
console.error("Usage: svamp service serve <name> [directory] [--subdomain <sub>] [--no-listing]");
|
|
317
|
+
console.error(" directory defaults to current directory");
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
if (subdomain) validateSubdomain(subdomain);
|
|
321
|
+
const healthInterval = healthIntervalStr ? parseInt(healthIntervalStr, 10) : void 0;
|
|
322
|
+
try {
|
|
323
|
+
const { startStaticServer } = await import('./staticServer-CWcmMF5V.mjs');
|
|
324
|
+
const resolvedDir = require("path").resolve(directory);
|
|
325
|
+
console.log(`Serving ${resolvedDir}`);
|
|
326
|
+
const staticServer = await startStaticServer({
|
|
327
|
+
directory: resolvedDir,
|
|
328
|
+
listing: !noListing
|
|
329
|
+
});
|
|
330
|
+
console.log(`Static server listening on 127.0.0.1:${staticServer.port}`);
|
|
331
|
+
const ports = [staticServer.port];
|
|
332
|
+
const env = getSandboxEnv();
|
|
333
|
+
const group = await createServiceGroup(name, ports, {
|
|
334
|
+
subdomain,
|
|
335
|
+
healthPath,
|
|
336
|
+
healthInterval
|
|
337
|
+
});
|
|
338
|
+
if (env.sandboxId) {
|
|
339
|
+
const result = await addBackend(name);
|
|
340
|
+
console.log(`Backend added: ${result.sandbox_id} (${result.pod_ip})`);
|
|
341
|
+
console.log(`
|
|
342
|
+
Serving ${resolvedDir}:`);
|
|
343
|
+
for (const p of group.ports) {
|
|
344
|
+
console.log(` ${p.url}`);
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
console.log(`No SANDBOX_ID detected \u2014 starting reverse tunnel.`);
|
|
348
|
+
const { runTunnel } = await import('./tunnel-BDKdemh0.mjs');
|
|
349
|
+
await runTunnel(name, ports);
|
|
350
|
+
}
|
|
351
|
+
const cleanup = () => {
|
|
352
|
+
staticServer.close();
|
|
353
|
+
process.exit(0);
|
|
354
|
+
};
|
|
355
|
+
process.on("SIGINT", cleanup);
|
|
356
|
+
process.on("SIGTERM", cleanup);
|
|
357
|
+
} catch (err) {
|
|
358
|
+
console.error(`Error serving directory: ${err.message}`);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
307
362
|
async function serviceTunnel(args) {
|
|
308
363
|
const positional = positionalArgs(args);
|
|
309
364
|
const name = positional[0];
|
|
@@ -312,7 +367,7 @@ async function serviceTunnel(args) {
|
|
|
312
367
|
console.error("Usage: svamp service tunnel <name> --port <port> [--port <port2>]");
|
|
313
368
|
process.exit(1);
|
|
314
369
|
}
|
|
315
|
-
const { runTunnel } = await import('./tunnel-
|
|
370
|
+
const { runTunnel } = await import('./tunnel-BDKdemh0.mjs');
|
|
316
371
|
await runTunnel(name, ports);
|
|
317
372
|
}
|
|
318
373
|
async function handleServiceCommand() {
|
|
@@ -344,6 +399,8 @@ async function handleServiceCommand() {
|
|
|
344
399
|
await serviceRename(commandArgs);
|
|
345
400
|
} else if (sub === "expose") {
|
|
346
401
|
await serviceExpose(commandArgs);
|
|
402
|
+
} else if (sub === "serve") {
|
|
403
|
+
await serviceServe(commandArgs);
|
|
347
404
|
} else if (sub === "tunnel") {
|
|
348
405
|
await serviceTunnel(commandArgs);
|
|
349
406
|
} else {
|
|
@@ -367,6 +424,7 @@ Usage:
|
|
|
367
424
|
svamp service remove-port <name> --port <port> Remove port from group
|
|
368
425
|
svamp service rename <name> --port <port> --subdomain <sub> Rename subdomain of a port
|
|
369
426
|
svamp service expose <name> --port <port> [--port <port2>] [options] Create + join (auto-detects tunnel)
|
|
427
|
+
svamp service serve <name> [directory] [options] Serve a directory as static files
|
|
370
428
|
svamp service tunnel <name> --port <port> [--port <port2>] Tunnel local ports to service group
|
|
371
429
|
|
|
372
430
|
Create/Expose options:
|
|
@@ -391,9 +449,15 @@ Environment variables (set by provisioner):
|
|
|
391
449
|
SANDBOX_NAMESPACE User's sandbox namespace
|
|
392
450
|
SANDBOX_ID This pod's sandbox ID
|
|
393
451
|
|
|
452
|
+
Serve options:
|
|
453
|
+
--no-listing Disable directory listing
|
|
454
|
+
--subdomain <sub> Custom subdomain (must contain at least 2 hyphens)
|
|
455
|
+
|
|
394
456
|
Examples:
|
|
395
457
|
svamp service expose my-api --port 8000
|
|
396
458
|
svamp service expose my-api --port 8000 --port 3000 --health-path /health
|
|
459
|
+
svamp service serve my-site ./dist
|
|
460
|
+
svamp service serve my-site (serves current directory)
|
|
397
461
|
svamp service add-port my-api --port 5000
|
|
398
462
|
svamp service remove-port my-api --port 5000
|
|
399
463
|
svamp service list
|
|
@@ -402,4 +466,4 @@ Examples:
|
|
|
402
466
|
`.trim());
|
|
403
467
|
}
|
|
404
468
|
|
|
405
|
-
export { handleServiceCommand, serviceAddBackend, serviceAddPort, serviceCreate, serviceDelete, serviceExpose, serviceInfo, serviceList, serviceRemoveBackend, serviceRemovePort, serviceRename, serviceTunnel };
|
|
469
|
+
export { handleServiceCommand, serviceAddBackend, serviceAddPort, serviceCreate, serviceDelete, serviceExpose, serviceInfo, serviceList, serviceRemoveBackend, serviceRemovePort, serviceRename, serviceServe, serviceTunnel };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { writeFileSync, readFileSync } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
|
-
import { connectAndGetMachine } from './commands-
|
|
3
|
+
import { connectAndGetMachine } from './commands-B6FEeZeP.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-
|
|
8
|
+
import './run-DsXDjwLW.mjs';
|
|
9
9
|
import 'os';
|
|
10
10
|
import 'fs/promises';
|
|
11
11
|
import 'url';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os__default from 'os';
|
|
2
|
-
import
|
|
2
|
+
import fs__default from 'fs';
|
|
3
3
|
import { join, resolve, relative } from 'path';
|
|
4
4
|
|
|
5
5
|
const SKILLS_SERVER = process.env.HYPHA_SKILLS_SERVER || "https://hypha.aicell.io";
|
|
@@ -128,8 +128,8 @@ function normalizeSkill(item) {
|
|
|
128
128
|
const SVAMP_HOME = process.env.SVAMP_HOME || join(os__default.homedir(), ".svamp");
|
|
129
129
|
const ENV_FILE = join(SVAMP_HOME, ".env");
|
|
130
130
|
function loadDotEnv() {
|
|
131
|
-
if (!
|
|
132
|
-
const lines =
|
|
131
|
+
if (!fs__default.existsSync(ENV_FILE)) return;
|
|
132
|
+
const lines = fs__default.readFileSync(ENV_FILE, "utf-8").split("\n");
|
|
133
133
|
for (const line of lines) {
|
|
134
134
|
const trimmed = line.trim();
|
|
135
135
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -143,7 +143,7 @@ function loadDotEnv() {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
function ensureSkillsDir() {
|
|
146
|
-
|
|
146
|
+
fs__default.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
147
147
|
}
|
|
148
148
|
function formatTable(rows, widths) {
|
|
149
149
|
if (rows.length === 0) return "";
|
|
@@ -190,7 +190,7 @@ async function skillsInstall(skillName, opts) {
|
|
|
190
190
|
process.exit(1);
|
|
191
191
|
}
|
|
192
192
|
const targetDir = join(SKILLS_DIR, skillName);
|
|
193
|
-
if (
|
|
193
|
+
if (fs__default.existsSync(targetDir) && !opts?.force) {
|
|
194
194
|
console.error(`Skill "${skillName}" is already installed at ${targetDir}`);
|
|
195
195
|
console.error("Use --force to overwrite.");
|
|
196
196
|
process.exit(1);
|
|
@@ -223,10 +223,10 @@ async function skillsInstall(skillName, opts) {
|
|
|
223
223
|
process.exit(1);
|
|
224
224
|
}
|
|
225
225
|
ensureSkillsDir();
|
|
226
|
-
if (
|
|
227
|
-
|
|
226
|
+
if (fs__default.existsSync(targetDir) && opts?.force) {
|
|
227
|
+
fs__default.rmSync(targetDir, { recursive: true, force: true });
|
|
228
228
|
}
|
|
229
|
-
|
|
229
|
+
fs__default.mkdirSync(targetDir, { recursive: true });
|
|
230
230
|
let downloaded = 0;
|
|
231
231
|
const errors = [];
|
|
232
232
|
for (const filePath of files) {
|
|
@@ -237,23 +237,23 @@ async function skillsInstall(skillName, opts) {
|
|
|
237
237
|
errors.push(` ${filePath}: path outside skill directory (blocked)`);
|
|
238
238
|
continue;
|
|
239
239
|
}
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
fs__default.mkdirSync(join(localPath, ".."), { recursive: true });
|
|
241
|
+
fs__default.writeFileSync(localPath, content, "utf-8");
|
|
242
242
|
downloaded++;
|
|
243
243
|
} catch (err) {
|
|
244
244
|
errors.push(` ${filePath}: ${err.message}`);
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
if (errors.length > 0) {
|
|
248
|
-
|
|
248
|
+
fs__default.rmSync(targetDir, { recursive: true, force: true });
|
|
249
249
|
console.error(`Installation failed \u2014 ${errors.length} file(s) could not be downloaded:`);
|
|
250
250
|
errors.forEach((e) => console.error(e));
|
|
251
251
|
console.error("Partial installation cleaned up. Run the command again to retry.");
|
|
252
252
|
process.exit(1);
|
|
253
253
|
}
|
|
254
254
|
const skillMdPath = join(targetDir, "SKILL.md");
|
|
255
|
-
if (
|
|
256
|
-
const content =
|
|
255
|
+
if (fs__default.existsSync(skillMdPath)) {
|
|
256
|
+
const content = fs__default.readFileSync(skillMdPath, "utf-8");
|
|
257
257
|
const fm = parseFrontmatter(content);
|
|
258
258
|
if (!fm) {
|
|
259
259
|
console.error("Warning: Installed SKILL.md has invalid or missing frontmatter (name/description).");
|
|
@@ -280,7 +280,7 @@ async function collectAllFiles(skillAlias, dir = "") {
|
|
|
280
280
|
}
|
|
281
281
|
async function skillsList() {
|
|
282
282
|
ensureSkillsDir();
|
|
283
|
-
const entries =
|
|
283
|
+
const entries = fs__default.readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
284
284
|
if (entries.length === 0) {
|
|
285
285
|
console.log("No skills installed.");
|
|
286
286
|
console.log(`Install one with: svamp skills install <name>`);
|
|
@@ -293,11 +293,11 @@ async function skillsList() {
|
|
|
293
293
|
const skillMd = join(skillDir, "SKILL.md");
|
|
294
294
|
let description = "-";
|
|
295
295
|
let status = "ok";
|
|
296
|
-
if (!
|
|
296
|
+
if (!fs__default.existsSync(skillMd)) {
|
|
297
297
|
status = "missing SKILL.md";
|
|
298
298
|
} else {
|
|
299
299
|
try {
|
|
300
|
-
const content =
|
|
300
|
+
const content = fs__default.readFileSync(skillMd, "utf-8");
|
|
301
301
|
const fm = parseFrontmatter(content);
|
|
302
302
|
if (fm) {
|
|
303
303
|
description = fm.description.length > 45 ? fm.description.slice(0, 42) + "..." : fm.description;
|
|
@@ -318,34 +318,34 @@ async function skillsRemove(name) {
|
|
|
318
318
|
process.exit(1);
|
|
319
319
|
}
|
|
320
320
|
const targetDir = join(SKILLS_DIR, name);
|
|
321
|
-
if (!
|
|
321
|
+
if (!fs__default.existsSync(targetDir)) {
|
|
322
322
|
console.error(`Skill "${name}" is not installed.`);
|
|
323
323
|
process.exit(1);
|
|
324
324
|
}
|
|
325
|
-
if (!
|
|
325
|
+
if (!fs__default.statSync(targetDir).isDirectory()) {
|
|
326
326
|
console.error(`"${name}" is not a skill directory.`);
|
|
327
327
|
process.exit(1);
|
|
328
328
|
}
|
|
329
|
-
|
|
329
|
+
fs__default.rmSync(targetDir, { recursive: true, force: true });
|
|
330
330
|
console.log(`Removed skill "${name}" from ${targetDir}`);
|
|
331
331
|
}
|
|
332
332
|
async function skillsPublish(skillPath, opts) {
|
|
333
333
|
const absPath = resolve(skillPath);
|
|
334
|
-
if (!
|
|
334
|
+
if (!fs__default.existsSync(absPath)) {
|
|
335
335
|
console.error(`Path does not exist: ${absPath}`);
|
|
336
336
|
process.exit(1);
|
|
337
337
|
}
|
|
338
|
-
if (!
|
|
338
|
+
if (!fs__default.statSync(absPath).isDirectory()) {
|
|
339
339
|
console.error(`Not a directory: ${absPath}`);
|
|
340
340
|
process.exit(1);
|
|
341
341
|
}
|
|
342
342
|
const skillMdPath = join(absPath, "SKILL.md");
|
|
343
|
-
if (!
|
|
343
|
+
if (!fs__default.existsSync(skillMdPath)) {
|
|
344
344
|
console.error(`No SKILL.md found in ${absPath}`);
|
|
345
345
|
console.error("A valid skill must contain a SKILL.md file with name and description frontmatter.");
|
|
346
346
|
process.exit(1);
|
|
347
347
|
}
|
|
348
|
-
const skillMdContent =
|
|
348
|
+
const skillMdContent = fs__default.readFileSync(skillMdPath, "utf-8");
|
|
349
349
|
const manifest = parseFrontmatter(skillMdContent);
|
|
350
350
|
if (!manifest) {
|
|
351
351
|
console.error('SKILL.md must have YAML frontmatter with "name" and "description" fields.');
|
|
@@ -455,7 +455,7 @@ async function skillsPublish(skillPath, opts) {
|
|
|
455
455
|
for (const relPath of filesToUpload) {
|
|
456
456
|
try {
|
|
457
457
|
const fullPath = join(absPath, relPath);
|
|
458
|
-
const content =
|
|
458
|
+
const content = fs__default.readFileSync(fullPath);
|
|
459
459
|
const putUrl = await am.put_file({
|
|
460
460
|
artifact_id: artifactId,
|
|
461
461
|
file_path: relPath,
|
|
@@ -515,7 +515,7 @@ async function skillsPublish(skillPath, opts) {
|
|
|
515
515
|
function collectLocalFiles(dir, base) {
|
|
516
516
|
const root = base || dir;
|
|
517
517
|
const result = [];
|
|
518
|
-
const entries =
|
|
518
|
+
const entries = fs__default.readdirSync(dir, { withFileTypes: true });
|
|
519
519
|
for (const entry of entries) {
|
|
520
520
|
const fullPath = join(dir, entry.name);
|
|
521
521
|
if (entry.name.startsWith(".") || entry.name === "node_modules") 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-
|
|
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-DsXDjwLW.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.
|
|
2
|
+
var version = "0.1.78";
|
|
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-clear-detection.mjs && npx tsx test/test-session-consolidation.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 && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.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",
|
|
@@ -28,8 +28,8 @@ var scripts = {
|
|
|
28
28
|
var dependencies = {
|
|
29
29
|
"@agentclientprotocol/sdk": "^0.14.1",
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
31
|
-
"hypha-rpc": "0.21.
|
|
32
|
-
"node-pty": "
|
|
31
|
+
"hypha-rpc": "0.21.34",
|
|
32
|
+
"node-pty": "1.2.0-beta.11",
|
|
33
33
|
ws: "^8.18.0",
|
|
34
34
|
yaml: "^2.8.2",
|
|
35
35
|
zod: "^3.24.4"
|
|
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { mkdirSync, writeFileSync, existsSync, unlinkSync, readFileSync, watch } from 'node:fs';
|
|
5
|
-
import { c as connectToHypha, a as registerSessionService } from './run-
|
|
5
|
+
import { c as connectToHypha, a as registerSessionService } from './run-DsXDjwLW.mjs';
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { createInterface } from 'node:readline';
|