svamp-cli 0.2.72 → 0.2.73
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
|
@@ -267,6 +267,13 @@ async function main() {
|
|
|
267
267
|
process.exit(1);
|
|
268
268
|
}
|
|
269
269
|
await handleMachineCommand();
|
|
270
|
+
} else if (subcommand === "fleet") {
|
|
271
|
+
const { isSandboxed } = await import('./sandboxDetect-DNTcbgWD.mjs');
|
|
272
|
+
if (isSandboxed()) {
|
|
273
|
+
console.error("svamp fleet: Fleet commands are not available in sandboxed sessions.");
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
await handleFleetCommand();
|
|
270
277
|
} else if (subcommand === "skills") {
|
|
271
278
|
const { isSandboxed } = await import('./sandboxDetect-DNTcbgWD.mjs');
|
|
272
279
|
if (isSandboxed()) {
|
|
@@ -315,7 +322,7 @@ async function main() {
|
|
|
315
322
|
} else if (!subcommand || subcommand === "start") {
|
|
316
323
|
await handleInteractiveCommand();
|
|
317
324
|
} else if (subcommand === "--version" || subcommand === "-v") {
|
|
318
|
-
const pkg = await import('./package-
|
|
325
|
+
const pkg = await import('./package-O54lUaPi.mjs').catch(() => ({ default: { version: "unknown" } }));
|
|
319
326
|
console.log(`svamp version: ${pkg.default.version}`);
|
|
320
327
|
} else {
|
|
321
328
|
console.error(`Unknown command: ${subcommand}`);
|
|
@@ -980,6 +987,65 @@ async function handleMachineCommand() {
|
|
|
980
987
|
}
|
|
981
988
|
process.exit(0);
|
|
982
989
|
}
|
|
990
|
+
async function handleFleetCommand() {
|
|
991
|
+
const fleetArgs = args.slice(1);
|
|
992
|
+
const sub = fleetArgs[0];
|
|
993
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
994
|
+
console.log(`Usage: svamp fleet <subcommand>
|
|
995
|
+
|
|
996
|
+
Subcommands (fan out across every machine in your workspace):
|
|
997
|
+
status Show svamp + claude-code version per machine
|
|
998
|
+
exec "<command>" Run a shell command on every machine
|
|
999
|
+
upgrade-claude [-v X] Install Claude Code (defaults to svamp-cli's pinned version)
|
|
1000
|
+
upgrade-svamp [-v X] npm install -g svamp-cli@X + svamp daemon restart
|
|
1001
|
+
daemon-restart [--cleanup] Restart daemons (default graceful)
|
|
1002
|
+
push-skill <name> Install/force-refresh a skill on all machines
|
|
1003
|
+
|
|
1004
|
+
Each row reports OK / FAIL / SKIP. Failures don't sink the batch; exit code 1
|
|
1005
|
+
only if at least one machine failed.
|
|
1006
|
+
|
|
1007
|
+
Examples:
|
|
1008
|
+
svamp fleet status
|
|
1009
|
+
svamp fleet exec "uptime"
|
|
1010
|
+
svamp fleet upgrade-claude
|
|
1011
|
+
svamp fleet upgrade-svamp -v 0.2.73
|
|
1012
|
+
svamp fleet push-skill hypha`);
|
|
1013
|
+
process.exit(0);
|
|
1014
|
+
}
|
|
1015
|
+
const flag = (name, short) => {
|
|
1016
|
+
for (let i = 1; i < fleetArgs.length; i++) {
|
|
1017
|
+
if (fleetArgs[i] === `--${name}` || short && fleetArgs[i] === short) return fleetArgs[i + 1];
|
|
1018
|
+
}
|
|
1019
|
+
return void 0;
|
|
1020
|
+
};
|
|
1021
|
+
const hasFlag = (name) => fleetArgs.includes(`--${name}`);
|
|
1022
|
+
if (sub === "status") {
|
|
1023
|
+
const { fleetStatus } = await import('./fleet-B9CWHUv1.mjs');
|
|
1024
|
+
await fleetStatus();
|
|
1025
|
+
} else if (sub === "exec") {
|
|
1026
|
+
const command = fleetArgs.slice(1).filter((a) => !a.startsWith("--")).join(" ");
|
|
1027
|
+
const { fleetExec } = await import('./fleet-B9CWHUv1.mjs');
|
|
1028
|
+
await fleetExec(command, { cwd: flag("cwd") });
|
|
1029
|
+
} else if (sub === "upgrade-claude") {
|
|
1030
|
+
const { fleetUpgradeClaude } = await import('./fleet-B9CWHUv1.mjs');
|
|
1031
|
+
await fleetUpgradeClaude({ version: flag("version", "-v") });
|
|
1032
|
+
} else if (sub === "upgrade-svamp") {
|
|
1033
|
+
const { fleetUpgradeSvamp } = await import('./fleet-B9CWHUv1.mjs');
|
|
1034
|
+
await fleetUpgradeSvamp({ version: flag("version", "-v") });
|
|
1035
|
+
} else if (sub === "daemon-restart") {
|
|
1036
|
+
const { fleetDaemonRestart } = await import('./fleet-B9CWHUv1.mjs');
|
|
1037
|
+
await fleetDaemonRestart({ graceful: !hasFlag("cleanup") });
|
|
1038
|
+
} else if (sub === "push-skill") {
|
|
1039
|
+
const name = fleetArgs[1];
|
|
1040
|
+
const { fleetPushSkill } = await import('./fleet-B9CWHUv1.mjs');
|
|
1041
|
+
await fleetPushSkill(name);
|
|
1042
|
+
} else {
|
|
1043
|
+
console.error(`Unknown fleet subcommand: ${sub}`);
|
|
1044
|
+
console.error('Run "svamp fleet --help" to see available subcommands.');
|
|
1045
|
+
process.exit(1);
|
|
1046
|
+
}
|
|
1047
|
+
process.exit(process.exitCode || 0);
|
|
1048
|
+
}
|
|
983
1049
|
async function handleSkillsCommand() {
|
|
984
1050
|
const skillsArgs = args.slice(1);
|
|
985
1051
|
const skillsSubcommand = skillsArgs[0];
|
|
@@ -1371,6 +1437,7 @@ Commands:
|
|
|
1371
1437
|
|
|
1372
1438
|
Other:
|
|
1373
1439
|
svamp machine --help Machine sharing & security contexts
|
|
1440
|
+
svamp fleet --help Fan-out commands across all your machines (status, upgrade, exec, push-skill)
|
|
1374
1441
|
svamp skills --help Skills marketplace (find, install, publish)
|
|
1375
1442
|
svamp service --help Service exposure (HTTP services from sandboxes)
|
|
1376
1443
|
svamp agent <name> Start local agent session (no daemon needed)
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { c as connectToHypha } from './run-CC-0bNTe.mjs';
|
|
5
|
+
import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-HydRNEt7.mjs';
|
|
6
|
+
import 'os';
|
|
7
|
+
import 'fs/promises';
|
|
8
|
+
import 'fs';
|
|
9
|
+
import 'path';
|
|
10
|
+
import 'url';
|
|
11
|
+
import 'child_process';
|
|
12
|
+
import 'crypto';
|
|
13
|
+
import 'util';
|
|
14
|
+
import 'node:crypto';
|
|
15
|
+
import 'node:child_process';
|
|
16
|
+
import '@agentclientprotocol/sdk';
|
|
17
|
+
import '@modelcontextprotocol/sdk/client/index.js';
|
|
18
|
+
import '@modelcontextprotocol/sdk/client/stdio.js';
|
|
19
|
+
import '@modelcontextprotocol/sdk/types.js';
|
|
20
|
+
import 'zod';
|
|
21
|
+
import 'node:fs/promises';
|
|
22
|
+
import 'node:util';
|
|
23
|
+
|
|
24
|
+
const SVAMP_HOME = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
|
|
25
|
+
const DAEMON_STATE_FILE = join(SVAMP_HOME, "daemon.state.json");
|
|
26
|
+
const ENV_FILE = join(SVAMP_HOME, ".env");
|
|
27
|
+
function loadDotEnv() {
|
|
28
|
+
if (!existsSync(ENV_FILE)) return;
|
|
29
|
+
for (const raw of readFileSync(ENV_FILE, "utf-8").split("\n")) {
|
|
30
|
+
const line = raw.trim();
|
|
31
|
+
if (!line || line.startsWith("#")) continue;
|
|
32
|
+
const eq = line.indexOf("=");
|
|
33
|
+
if (eq === -1) continue;
|
|
34
|
+
const k = line.slice(0, eq).trim();
|
|
35
|
+
const v = line.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
36
|
+
if (!process.env[k]) process.env[k] = v;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function readDaemonState() {
|
|
40
|
+
if (!existsSync(DAEMON_STATE_FILE)) return null;
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(readFileSync(DAEMON_STATE_FILE, "utf-8"));
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function suppressHyphaLogs() {
|
|
48
|
+
const ol = console.log, ow = console.warn, oi = console.info, oe = console.error;
|
|
49
|
+
const sw = process.stdout.write.bind(process.stdout);
|
|
50
|
+
const ew = process.stderr.write.bind(process.stderr);
|
|
51
|
+
const isLog = (chunk) => typeof chunk === "string" && (chunk.includes("WebSocket connection") || chunk.includes("Connection established") || chunk.includes("registering service built-in") || chunk.includes("registered service") || chunk.includes("registered all") || chunk.includes("Subscribing to client_") || chunk.includes("subscribed to client_") || chunk.includes("subscribe to client_") || chunk.includes("Cleaning up all sessions") || chunk.includes("WebSocket connection disconnected") || chunk.includes("local RPC disconnection"));
|
|
52
|
+
console.log = () => {
|
|
53
|
+
};
|
|
54
|
+
console.warn = () => {
|
|
55
|
+
};
|
|
56
|
+
console.info = () => {
|
|
57
|
+
};
|
|
58
|
+
console.error = (...args) => {
|
|
59
|
+
if (args.some((a) => isLog(a))) return;
|
|
60
|
+
oe(...args);
|
|
61
|
+
};
|
|
62
|
+
process.stdout.write = (c, ...a) => isLog(c) ? true : sw(c, ...a);
|
|
63
|
+
process.stderr.write = (c, ...a) => isLog(c) ? true : ew(c, ...a);
|
|
64
|
+
return () => {
|
|
65
|
+
console.log = ol;
|
|
66
|
+
console.warn = ow;
|
|
67
|
+
console.info = oi;
|
|
68
|
+
console.error = oe;
|
|
69
|
+
process.stdout.write = sw;
|
|
70
|
+
process.stderr.write = ew;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function truncate(s, n) {
|
|
74
|
+
if (!s) return s;
|
|
75
|
+
return s.length <= n ? s : s.slice(0, Math.max(1, n - 1)) + "\u2026";
|
|
76
|
+
}
|
|
77
|
+
async function discoverMachines() {
|
|
78
|
+
loadDotEnv();
|
|
79
|
+
const state = readDaemonState();
|
|
80
|
+
const serverUrl = process.env.HYPHA_SERVER_URL || state?.hyphaServerUrl;
|
|
81
|
+
const token = process.env.HYPHA_TOKEN;
|
|
82
|
+
if (!serverUrl) {
|
|
83
|
+
console.error('No Hypha server URL. Run "svamp login <url>" first.');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const restore = suppressHyphaLogs();
|
|
87
|
+
let server;
|
|
88
|
+
try {
|
|
89
|
+
server = await connectToHypha({ serverUrl, token, name: "svamp-fleet-cli" });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
restore();
|
|
92
|
+
console.error(`Failed to connect to Hypha: ${err.message}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const services = await server.listServices({ query: { type: "svamp-machine" }, include_unlisted: true, _rkwargs: true });
|
|
96
|
+
restore();
|
|
97
|
+
const machines = await Promise.all(services.map(async (svc) => {
|
|
98
|
+
const serviceId = svc.id || svc.name;
|
|
99
|
+
try {
|
|
100
|
+
const rpc = await server.getService(serviceId);
|
|
101
|
+
const info = await rpc.getMachineInfo();
|
|
102
|
+
const label = info.metadata?.displayName || info.metadata?.host || info.metadata?.hostname || serviceId;
|
|
103
|
+
return { serviceId, machineId: info.machineId || serviceId, label, rpc };
|
|
104
|
+
} catch {
|
|
105
|
+
return { serviceId, machineId: serviceId, label: "-", rpc: null };
|
|
106
|
+
}
|
|
107
|
+
}));
|
|
108
|
+
return { server, machines };
|
|
109
|
+
}
|
|
110
|
+
async function fanOut(machines, fn) {
|
|
111
|
+
return Promise.all(machines.map(async (m) => {
|
|
112
|
+
if (!m.rpc) return { machine: m, ok: false, error: "unreachable" };
|
|
113
|
+
try {
|
|
114
|
+
const result = await fn(m);
|
|
115
|
+
return { machine: m, ok: true, result };
|
|
116
|
+
} catch (e) {
|
|
117
|
+
return { machine: m, ok: false, error: e?.message || String(e) };
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
function printResultRow(m, status, detail) {
|
|
122
|
+
const id = truncate(m.machineId, 18).padEnd(20);
|
|
123
|
+
const label = truncate(m.label, 22).padEnd(24);
|
|
124
|
+
const color = status === "OK" ? "\x1B[32m" : status === "SKIP" ? "\x1B[33m" : "\x1B[31m";
|
|
125
|
+
console.log(`${id} ${label} ${color}${status.padEnd(5)}\x1B[0m ${truncate(detail, 60)}`);
|
|
126
|
+
}
|
|
127
|
+
function printHeader(headers, widths) {
|
|
128
|
+
const line = headers.map((h, i) => h.padEnd(widths[i])).join(" ");
|
|
129
|
+
console.log(line);
|
|
130
|
+
console.log("-".repeat(line.length));
|
|
131
|
+
}
|
|
132
|
+
async function fleetStatus() {
|
|
133
|
+
const { server, machines } = await discoverMachines();
|
|
134
|
+
if (machines.length === 0) {
|
|
135
|
+
console.log("No machines found.");
|
|
136
|
+
await server.disconnect();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
printHeader(["MACHINE ID", "LABEL", "CLAUDE", "SVAMP", "STATUS"], [20, 24, 12, 10, 12]);
|
|
141
|
+
const rows = await fanOut(machines, async (m) => {
|
|
142
|
+
const [claudeRes, svampRes] = await Promise.all([
|
|
143
|
+
m.rpc.bash("claude --version 2>/dev/null || echo unknown").catch(() => null),
|
|
144
|
+
m.rpc.bash("svamp --version 2>/dev/null || echo unknown").catch(() => null)
|
|
145
|
+
]);
|
|
146
|
+
const claude = (claudeRes?.stdout || "unknown").trim().split(/\s+/)[0] || "unknown";
|
|
147
|
+
const svamp = (svampRes?.stdout || "unknown").trim().split(/\s+/)[0] || "unknown";
|
|
148
|
+
const info = await m.rpc.getMachineInfo();
|
|
149
|
+
return { claude, svamp, status: info.daemonState?.status || "unknown" };
|
|
150
|
+
});
|
|
151
|
+
for (const r of rows) {
|
|
152
|
+
const id = truncate(r.machine.machineId, 18).padEnd(20);
|
|
153
|
+
const label = truncate(r.machine.label, 22).padEnd(24);
|
|
154
|
+
const claude = (r.ok ? r.result.claude : "-").padEnd(12);
|
|
155
|
+
const svamp = (r.ok ? r.result.svamp : "-").padEnd(10);
|
|
156
|
+
const status = r.ok ? r.result.status : r.error || "fail";
|
|
157
|
+
const color = r.ok && r.result.status === "running" ? "\x1B[32m" : "\x1B[31m";
|
|
158
|
+
console.log(`${id} ${label} ${claude} ${svamp} ${color}${status}\x1B[0m`);
|
|
159
|
+
}
|
|
160
|
+
console.log(`
|
|
161
|
+
${machines.length} machine(s).`);
|
|
162
|
+
} finally {
|
|
163
|
+
await server.disconnect();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function fleetExec(command, opts) {
|
|
167
|
+
if (!command) {
|
|
168
|
+
console.error('Usage: svamp fleet exec "<command>"');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
const { server, machines } = await discoverMachines();
|
|
172
|
+
if (machines.length === 0) {
|
|
173
|
+
console.log("No machines found.");
|
|
174
|
+
await server.disconnect();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
printHeader(["MACHINE ID", "LABEL", "EXIT", "OUTPUT"], [20, 24, 6, 60]);
|
|
179
|
+
const rows = await fanOut(machines, async (m) => {
|
|
180
|
+
return await m.rpc.bash(command, opts?.cwd || void 0);
|
|
181
|
+
});
|
|
182
|
+
let failures = 0;
|
|
183
|
+
for (const r of rows) {
|
|
184
|
+
const id = truncate(r.machine.machineId, 18).padEnd(20);
|
|
185
|
+
const label = truncate(r.machine.label, 22).padEnd(24);
|
|
186
|
+
if (!r.ok) {
|
|
187
|
+
console.log(`${id} ${label} ${"-".padEnd(6)} \x1B[31m${truncate(r.error || "error", 60)}\x1B[0m`);
|
|
188
|
+
failures += 1;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const exit = String(r.result.exitCode).padEnd(6);
|
|
192
|
+
const out = (r.result.stdout?.trim() || r.result.stderr?.trim() || "").replace(/\s+/g, " ");
|
|
193
|
+
const color = r.result.exitCode === 0 ? "" : "\x1B[31m";
|
|
194
|
+
const reset = color ? "\x1B[0m" : "";
|
|
195
|
+
console.log(`${id} ${label} ${exit} ${color}${truncate(out, 60)}${reset}`);
|
|
196
|
+
if (r.result.exitCode !== 0) failures += 1;
|
|
197
|
+
}
|
|
198
|
+
console.log(`
|
|
199
|
+
${rows.length - failures}/${rows.length} succeeded.`);
|
|
200
|
+
if (failures > 0) process.exitCode = 1;
|
|
201
|
+
} finally {
|
|
202
|
+
await server.disconnect();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function fleetUpgradeClaude(opts) {
|
|
206
|
+
const version = opts?.version || PINNED_CLAUDE_CODE_VERSION;
|
|
207
|
+
const { server, machines } = await discoverMachines();
|
|
208
|
+
if (machines.length === 0) {
|
|
209
|
+
console.log("No machines found.");
|
|
210
|
+
await server.disconnect();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
console.log(`Upgrading Claude Code to ${version} on ${machines.length} machine(s)...
|
|
214
|
+
`);
|
|
215
|
+
try {
|
|
216
|
+
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
217
|
+
const cmd = `claude install ${version} 2>&1 || claude update 2>&1`;
|
|
218
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
219
|
+
let failures = 0;
|
|
220
|
+
for (const r of rows) {
|
|
221
|
+
if (!r.ok) {
|
|
222
|
+
printResultRow(r.machine, "FAIL", r.error || "");
|
|
223
|
+
failures += 1;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const ok = r.result.exitCode === 0;
|
|
227
|
+
const detail = (r.result.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
|
|
228
|
+
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
229
|
+
if (!ok) failures += 1;
|
|
230
|
+
}
|
|
231
|
+
console.log(`
|
|
232
|
+
${rows.length - failures}/${rows.length} machines on ${version}.`);
|
|
233
|
+
if (failures > 0) process.exitCode = 1;
|
|
234
|
+
} finally {
|
|
235
|
+
await server.disconnect();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async function fleetUpgradeSvamp(opts) {
|
|
239
|
+
const version = opts?.version || "latest";
|
|
240
|
+
const { server, machines } = await discoverMachines();
|
|
241
|
+
if (machines.length === 0) {
|
|
242
|
+
console.log("No machines found.");
|
|
243
|
+
await server.disconnect();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
console.log(`Upgrading svamp-cli to ${version} on ${machines.length} machine(s)...
|
|
247
|
+
`);
|
|
248
|
+
try {
|
|
249
|
+
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
250
|
+
const cmd = `bash -lc 'npm install -g svamp-cli@${version} 2>&1 | tail -5 && svamp daemon restart 2>&1 | tail -5'`;
|
|
251
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
252
|
+
let failures = 0;
|
|
253
|
+
for (const r of rows) {
|
|
254
|
+
if (!r.ok) {
|
|
255
|
+
printResultRow(r.machine, "FAIL", r.error || "");
|
|
256
|
+
failures += 1;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const ok = r.result.exitCode === 0;
|
|
260
|
+
const detail = (r.result.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
|
|
261
|
+
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
262
|
+
if (!ok) failures += 1;
|
|
263
|
+
}
|
|
264
|
+
console.log(`
|
|
265
|
+
${rows.length - failures}/${rows.length} machines upgraded.`);
|
|
266
|
+
console.log("Note: daemons will auto-converge Claude Code to the pinned version on restart.");
|
|
267
|
+
if (failures > 0) process.exitCode = 1;
|
|
268
|
+
} finally {
|
|
269
|
+
await server.disconnect();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async function fleetDaemonRestart(opts) {
|
|
273
|
+
const { server, machines } = await discoverMachines();
|
|
274
|
+
if (machines.length === 0) {
|
|
275
|
+
console.log("No machines found.");
|
|
276
|
+
await server.disconnect();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
281
|
+
const cmd = opts?.graceful === false ? `svamp daemon stop --cleanup 2>&1 && svamp daemon start 2>&1` : `svamp daemon restart 2>&1`;
|
|
282
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
283
|
+
let failures = 0;
|
|
284
|
+
for (const r of rows) {
|
|
285
|
+
if (!r.ok) {
|
|
286
|
+
printResultRow(r.machine, "FAIL", r.error || "");
|
|
287
|
+
failures += 1;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const ok = r.result.exitCode === 0;
|
|
291
|
+
const detail = (r.result.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
|
|
292
|
+
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
293
|
+
if (!ok) failures += 1;
|
|
294
|
+
}
|
|
295
|
+
console.log(`
|
|
296
|
+
${rows.length - failures}/${rows.length} restarted.`);
|
|
297
|
+
if (failures > 0) process.exitCode = 1;
|
|
298
|
+
} finally {
|
|
299
|
+
await server.disconnect();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function fleetPushSkill(name) {
|
|
303
|
+
if (!name) {
|
|
304
|
+
console.error("Usage: svamp fleet push-skill <name>");
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
const { server, machines } = await discoverMachines();
|
|
308
|
+
if (machines.length === 0) {
|
|
309
|
+
console.log("No machines found.");
|
|
310
|
+
await server.disconnect();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
console.log(`Pushing skill "${name}" to ${machines.length} machine(s)...
|
|
314
|
+
`);
|
|
315
|
+
try {
|
|
316
|
+
printHeader(["MACHINE ID", "LABEL", "STATE", "DETAIL"], [20, 24, 6, 60]);
|
|
317
|
+
const cmd = `svamp skills install ${name} --force 2>&1`;
|
|
318
|
+
const rows = await fanOut(machines, async (m) => await m.rpc.bash(cmd, void 0));
|
|
319
|
+
let failures = 0;
|
|
320
|
+
for (const r of rows) {
|
|
321
|
+
if (!r.ok) {
|
|
322
|
+
printResultRow(r.machine, "FAIL", r.error || "");
|
|
323
|
+
failures += 1;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const ok = r.result.exitCode === 0;
|
|
327
|
+
const detail = (r.result.stdout?.trim().split("\n").slice(-1)[0] || r.result.stderr?.trim().slice(-200) || "").replace(/\s+/g, " ");
|
|
328
|
+
printResultRow(r.machine, ok ? "OK" : "FAIL", detail);
|
|
329
|
+
if (!ok) failures += 1;
|
|
330
|
+
}
|
|
331
|
+
console.log(`
|
|
332
|
+
${rows.length - failures}/${rows.length} updated.`);
|
|
333
|
+
if (failures > 0) process.exitCode = 1;
|
|
334
|
+
} finally {
|
|
335
|
+
await server.disconnect();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export { fleetDaemonRestart, fleetExec, fleetPushSkill, fleetStatus, fleetUpgradeClaude, fleetUpgradeSvamp };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "svamp-cli";
|
|
2
|
-
var version = "0.2.
|
|
2
|
+
var version = "0.2.73";
|
|
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 bin/skills && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && tsc --noEmit && pkgroll",
|
|
21
21
|
typecheck: "tsc --noEmit",
|
|
22
|
-
test: "npx tsx test/test-context-window.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.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-isolation-decision.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-claude-auth.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-session-send-query.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-supervisor-lock.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && node test/pinnedClaudeCode.test.mjs",
|
|
22
|
+
test: "npx tsx test/test-context-window.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.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-isolation-decision.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-claude-auth.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-session-send-query.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-supervisor-lock.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.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",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svamp-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.73",
|
|
4
4
|
"description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
|
|
5
5
|
"author": "Amun AI AB",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "rm -rf dist bin/skills && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && tsc --noEmit && pkgroll",
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
|
-
"test": "npx tsx test/test-context-window.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.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-isolation-decision.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-claude-auth.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-session-send-query.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-supervisor-lock.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && node test/pinnedClaudeCode.test.mjs",
|
|
23
|
+
"test": "npx tsx test/test-context-window.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.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-isolation-decision.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-claude-auth.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-session-send-query.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-supervisor-lock.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs",
|
|
24
24
|
"test:hypha": "node --no-warnings test/test-hypha-service.mjs",
|
|
25
25
|
"dev": "tsx src/cli.ts",
|
|
26
26
|
"dev:daemon": "tsx src/cli.ts daemon start-sync",
|