@slicervm/sdk 0.1.3 → 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 +184 -1
- 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 +184 -2
- 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() {
|
|
@@ -869,7 +871,8 @@ var VM = class {
|
|
|
869
871
|
*/
|
|
870
872
|
async *exec(req) {
|
|
871
873
|
const { path: path3, body } = buildExecPath(this.hostname, req, false);
|
|
872
|
-
for await (const
|
|
874
|
+
for await (const raw of this.transport.requestNDJSON("POST", path3, body)) {
|
|
875
|
+
const frame = normalizeExecFrame(raw);
|
|
873
876
|
if (frame.encoding === "base64") {
|
|
874
877
|
if (frame.data) frame.dataBytes = Buffer.from(frame.data, "base64");
|
|
875
878
|
if (frame.stdout) frame.stdoutBytes = Buffer.from(frame.stdout, "base64");
|
|
@@ -910,6 +913,22 @@ var VM = class {
|
|
|
910
913
|
};
|
|
911
914
|
}
|
|
912
915
|
};
|
|
916
|
+
function normalizeExecFrame(raw) {
|
|
917
|
+
const frame = {};
|
|
918
|
+
if (raw.timestamp !== void 0) frame.timestamp = raw.timestamp;
|
|
919
|
+
if (raw.type !== void 0) frame.type = raw.type;
|
|
920
|
+
if (raw.pid !== void 0) frame.pid = raw.pid;
|
|
921
|
+
if (raw.encoding !== void 0) frame.encoding = raw.encoding;
|
|
922
|
+
if (raw.data !== void 0) frame.data = raw.data;
|
|
923
|
+
if (raw.started_at !== void 0) frame.startedAt = raw.started_at;
|
|
924
|
+
if (raw.ended_at !== void 0) frame.endedAt = raw.ended_at;
|
|
925
|
+
if (raw.signal !== void 0) frame.signal = raw.signal;
|
|
926
|
+
if (raw.stdout !== void 0) frame.stdout = raw.stdout;
|
|
927
|
+
if (raw.stderr !== void 0) frame.stderr = raw.stderr;
|
|
928
|
+
if (raw.exit_code !== void 0) frame.exitCode = raw.exit_code;
|
|
929
|
+
if (raw.error !== void 0) frame.error = raw.error;
|
|
930
|
+
return frame;
|
|
931
|
+
}
|
|
913
932
|
function buildExecPath(hostname, req, buffered) {
|
|
914
933
|
const q = new URLSearchParams();
|
|
915
934
|
if (req.command) q.set("cmd", req.command);
|
|
@@ -938,6 +957,169 @@ function sleep(ms) {
|
|
|
938
957
|
function errMsg(e) {
|
|
939
958
|
return e instanceof Error ? e.message : String(e);
|
|
940
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
|
+
}
|
|
941
1123
|
|
|
942
1124
|
// src/namespaces.ts
|
|
943
1125
|
var HostGroupsAPI = class {
|
|
@@ -1119,6 +1301,7 @@ exports.SecretsAPI = SecretsAPI;
|
|
|
1119
1301
|
exports.SlicerAPIError = SlicerAPIError;
|
|
1120
1302
|
exports.SlicerClient = SlicerClient;
|
|
1121
1303
|
exports.VM = VM;
|
|
1304
|
+
exports.VMBg = VMBg;
|
|
1122
1305
|
exports.VMFileSystem = VMFileSystem;
|
|
1123
1306
|
exports.VMsAPI = VMsAPI;
|
|
1124
1307
|
exports.parseAddressMapping = parseAddressMapping;
|