@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 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 frame of this.transport.requestNDJSON("POST", path3, body)) {
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;