@slicervm/sdk 0.1.4 → 0.1.5
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/README.md +7 -0
- package/dist/index.cjs +166 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -1
- package/dist/index.d.ts +138 -1
- package/dist/index.js +166 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,13 @@ console.log(result.stdout);
|
|
|
23
23
|
await vm.fs.writeFile('/tmp/hello.txt', 'hi');
|
|
24
24
|
console.log((await vm.fs.readFile('/tmp/hello.txt')).toString());
|
|
25
25
|
|
|
26
|
+
// Background exec — detached, survives disconnect, logs into an agent-side
|
|
27
|
+
// ring buffer. Manage via vm.bg.list/info/logs/kill/wait/remove.
|
|
28
|
+
const bg = await vm.bg.exec({ command: 'npm', args: ['run', 'dev'], cwd: '/app' });
|
|
29
|
+
for await (const f of vm.bg.logs(bg.execId, { follow: true })) {
|
|
30
|
+
if (f.stdoutBytes) process.stdout.write(f.stdoutBytes);
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
await vm.delete();
|
|
27
34
|
```
|
|
28
35
|
|
package/dist/index.cjs
CHANGED
|
@@ -745,6 +745,7 @@ var VM = class {
|
|
|
745
745
|
createdAt;
|
|
746
746
|
arch;
|
|
747
747
|
fs;
|
|
748
|
+
bg;
|
|
748
749
|
transport;
|
|
749
750
|
constructor(transport, init) {
|
|
750
751
|
this.transport = transport;
|
|
@@ -754,6 +755,7 @@ var VM = class {
|
|
|
754
755
|
if (init.createdAt !== void 0) this.createdAt = init.createdAt;
|
|
755
756
|
if (init.arch !== void 0) this.arch = init.arch;
|
|
756
757
|
this.fs = new VMFileSystem(transport, this.hostname);
|
|
758
|
+
this.bg = new VMBg(transport, this.hostname);
|
|
757
759
|
}
|
|
758
760
|
// --- lifecycle --------------------------------------------------------
|
|
759
761
|
async delete() {
|
|
@@ -955,6 +957,169 @@ function sleep(ms) {
|
|
|
955
957
|
function errMsg(e) {
|
|
956
958
|
return e instanceof Error ? e.message : String(e);
|
|
957
959
|
}
|
|
960
|
+
var VMBg = class {
|
|
961
|
+
constructor(transport, hostname) {
|
|
962
|
+
this.transport = transport;
|
|
963
|
+
this.hostname = hostname;
|
|
964
|
+
}
|
|
965
|
+
transport;
|
|
966
|
+
hostname;
|
|
967
|
+
/**
|
|
968
|
+
* Launch a long-running process. `command` + `args` is the deterministic
|
|
969
|
+
* exec form (no shell). Set `shell: '/bin/bash'` (or similar) to opt in
|
|
970
|
+
* to shell semantics — `$VAR` expansion, globs, `&&`/`||`, etc.
|
|
971
|
+
*/
|
|
972
|
+
async exec(req) {
|
|
973
|
+
if (!req.command) throw new Error("vm.bg.exec: command is required");
|
|
974
|
+
const q = new URLSearchParams();
|
|
975
|
+
q.set("background", "true");
|
|
976
|
+
q.set("cmd", req.command);
|
|
977
|
+
for (const a of req.args ?? []) q.append("args", a);
|
|
978
|
+
for (const e of req.env ?? []) q.append("env", e);
|
|
979
|
+
if (req.uid !== void 0 && req.uid !== 0) q.set("uid", String(req.uid));
|
|
980
|
+
if (req.gid !== void 0 && req.gid !== 0) q.set("gid", String(req.gid));
|
|
981
|
+
if (req.shell) q.set("shell", req.shell);
|
|
982
|
+
if (req.cwd) q.set("cwd", req.cwd);
|
|
983
|
+
if (req.ringBytes !== void 0 && req.ringBytes > 0) {
|
|
984
|
+
q.set("ring_bytes", String(req.ringBytes));
|
|
985
|
+
}
|
|
986
|
+
q.set("stdio", "base64");
|
|
987
|
+
const path3 = `/vm/${encodeURIComponent(this.hostname)}/exec?${q.toString()}`;
|
|
988
|
+
const wire = await this.transport.request("POST", path3);
|
|
989
|
+
return bgExecResponseFromWire(wire);
|
|
990
|
+
}
|
|
991
|
+
/** All background execs the agent currently tracks (running + exited-not-reaped). */
|
|
992
|
+
async list() {
|
|
993
|
+
const wire = await this.transport.request(
|
|
994
|
+
"GET",
|
|
995
|
+
`/vm/${encodeURIComponent(this.hostname)}/exec`
|
|
996
|
+
);
|
|
997
|
+
return (wire ?? []).map(bgExecInfoFromWire);
|
|
998
|
+
}
|
|
999
|
+
/** Latest status snapshot for one bg exec. Throws 404 if reaped or never existed. */
|
|
1000
|
+
async info(execId) {
|
|
1001
|
+
const wire = await this.transport.request(
|
|
1002
|
+
"GET",
|
|
1003
|
+
`/vm/${encodeURIComponent(this.hostname)}/exec/${encodeURIComponent(execId)}`
|
|
1004
|
+
);
|
|
1005
|
+
return bgExecInfoFromWire(wire);
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* NDJSON log stream. Yields one frame per log line — `started`, `stdout`,
|
|
1009
|
+
* `stderr`, `exit`, plus optional `gap` frames if the ring evicted history
|
|
1010
|
+
* before the requested cursor. Frames carry `data` base64-encoded; the SDK
|
|
1011
|
+
* also populates `dataBytes` / `stdoutBytes` / `stderrBytes` Buffers.
|
|
1012
|
+
*
|
|
1013
|
+
* `follow: false` (default) replays from the cursor and ends when the ring
|
|
1014
|
+
* is drained. `follow: true` keeps the stream open until the child exits or
|
|
1015
|
+
* the caller breaks out.
|
|
1016
|
+
*/
|
|
1017
|
+
async *logs(execId, opts = {}) {
|
|
1018
|
+
const q = new URLSearchParams();
|
|
1019
|
+
if (opts.follow) q.set("follow", "true");
|
|
1020
|
+
if (opts.fromId !== void 0 && opts.fromId > 0) q.set("from_id", String(opts.fromId));
|
|
1021
|
+
const path3 = `/vm/${encodeURIComponent(this.hostname)}/exec/${encodeURIComponent(execId)}/logs` + (q.toString() ? `?${q.toString()}` : "");
|
|
1022
|
+
for await (const raw of this.transport.requestNDJSON("GET", path3)) {
|
|
1023
|
+
const frame = normalizeExecFrame(raw);
|
|
1024
|
+
if (frame.encoding === "base64") {
|
|
1025
|
+
if (frame.data) frame.dataBytes = Buffer.from(frame.data, "base64");
|
|
1026
|
+
if (frame.stdout) frame.stdoutBytes = Buffer.from(frame.stdout, "base64");
|
|
1027
|
+
if (frame.stderr) frame.stderrBytes = Buffer.from(frame.stderr, "base64");
|
|
1028
|
+
}
|
|
1029
|
+
yield frame;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Signal a running bg exec. Default: SIGTERM with a 5 s grace period before
|
|
1034
|
+
* the agent escalates to SIGKILL. No-op (running=false) if the child has
|
|
1035
|
+
* already exited.
|
|
1036
|
+
*/
|
|
1037
|
+
async kill(execId, opts = {}) {
|
|
1038
|
+
const body = {};
|
|
1039
|
+
if (opts.signal) body.signal = opts.signal;
|
|
1040
|
+
if (opts.graceMs !== void 0) body.grace_ms = opts.graceMs;
|
|
1041
|
+
const wire = await this.transport.request(
|
|
1042
|
+
"POST",
|
|
1043
|
+
`/vm/${encodeURIComponent(this.hostname)}/exec/${encodeURIComponent(execId)}/kill`,
|
|
1044
|
+
body
|
|
1045
|
+
);
|
|
1046
|
+
return bgKillResponseFromWire(wire);
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Long-poll until the child exits or `timeoutSec` elapses. Returns
|
|
1050
|
+
* `timedOut: true` if the deadline hit. Server default for `timeoutSec=0`
|
|
1051
|
+
* is 30 s.
|
|
1052
|
+
*/
|
|
1053
|
+
async wait(execId, timeoutSec = 0) {
|
|
1054
|
+
const q = new URLSearchParams();
|
|
1055
|
+
if (timeoutSec > 0) q.set("timeout", String(timeoutSec));
|
|
1056
|
+
const path3 = `/vm/${encodeURIComponent(this.hostname)}/exec/${encodeURIComponent(execId)}/wait-exit` + (q.toString() ? `?${q.toString()}` : "");
|
|
1057
|
+
const wire = await this.transport.request("GET", path3);
|
|
1058
|
+
return bgWaitExitFromWire(wire);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Reap a bg exec's ring buffer + registry entry. Does NOT kill a running
|
|
1062
|
+
* process — pair with `kill()` for "stop and clean up". After remove,
|
|
1063
|
+
* info/logs/kill/wait return 410 Gone.
|
|
1064
|
+
*/
|
|
1065
|
+
async remove(execId) {
|
|
1066
|
+
const wire = await this.transport.request(
|
|
1067
|
+
"DELETE",
|
|
1068
|
+
`/vm/${encodeURIComponent(this.hostname)}/exec/${encodeURIComponent(execId)}`
|
|
1069
|
+
);
|
|
1070
|
+
return bgDeleteFromWire(wire);
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
function bgExecResponseFromWire(w) {
|
|
1074
|
+
return {
|
|
1075
|
+
execId: w.exec_id,
|
|
1076
|
+
pid: w.pid,
|
|
1077
|
+
startedAt: w.started_at,
|
|
1078
|
+
ringBytes: w.ring_bytes
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function bgExecInfoFromWire(w) {
|
|
1082
|
+
const out = {
|
|
1083
|
+
execId: w.exec_id,
|
|
1084
|
+
pid: w.pid,
|
|
1085
|
+
command: w.command,
|
|
1086
|
+
startedAt: w.started_at,
|
|
1087
|
+
running: w.running,
|
|
1088
|
+
bytesWritten: w.bytes_written,
|
|
1089
|
+
bytesDropped: w.bytes_dropped,
|
|
1090
|
+
nextId: w.next_id,
|
|
1091
|
+
ringBytes: w.ring_bytes
|
|
1092
|
+
};
|
|
1093
|
+
if (w.args !== void 0) out.args = w.args;
|
|
1094
|
+
if (w.cwd !== void 0) out.cwd = w.cwd;
|
|
1095
|
+
if (w.uid !== void 0) out.uid = w.uid;
|
|
1096
|
+
if (w.exit_code !== void 0) out.exitCode = w.exit_code;
|
|
1097
|
+
if (w.signal !== void 0) out.signal = w.signal;
|
|
1098
|
+
if (w.ended_at !== void 0) out.endedAt = w.ended_at;
|
|
1099
|
+
return out;
|
|
1100
|
+
}
|
|
1101
|
+
function bgKillResponseFromWire(w) {
|
|
1102
|
+
return {
|
|
1103
|
+
execId: w.exec_id,
|
|
1104
|
+
pid: w.pid,
|
|
1105
|
+
running: w.running,
|
|
1106
|
+
signalSent: w.signal_sent
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
function bgWaitExitFromWire(w) {
|
|
1110
|
+
const out = {
|
|
1111
|
+
execId: w.exec_id,
|
|
1112
|
+
running: w.running,
|
|
1113
|
+
timedOut: w.timed_out
|
|
1114
|
+
};
|
|
1115
|
+
if (w.exit_code !== void 0) out.exitCode = w.exit_code;
|
|
1116
|
+
if (w.signal !== void 0) out.signal = w.signal;
|
|
1117
|
+
if (w.ended_at !== void 0) out.endedAt = w.ended_at;
|
|
1118
|
+
return out;
|
|
1119
|
+
}
|
|
1120
|
+
function bgDeleteFromWire(w) {
|
|
1121
|
+
return { execId: w.exec_id, reaped: w.reaped };
|
|
1122
|
+
}
|
|
958
1123
|
|
|
959
1124
|
// src/namespaces.ts
|
|
960
1125
|
var HostGroupsAPI = class {
|
|
@@ -1136,6 +1301,7 @@ exports.SecretsAPI = SecretsAPI;
|
|
|
1136
1301
|
exports.SlicerAPIError = SlicerAPIError;
|
|
1137
1302
|
exports.SlicerClient = SlicerClient;
|
|
1138
1303
|
exports.VM = VM;
|
|
1304
|
+
exports.VMBg = VMBg;
|
|
1139
1305
|
exports.VMFileSystem = VMFileSystem;
|
|
1140
1306
|
exports.VMsAPI = VMsAPI;
|
|
1141
1307
|
exports.parseAddressMapping = parseAddressMapping;
|