@slicervm/sdk 0.1.5 → 0.1.6

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/index.d.cts CHANGED
@@ -712,4 +712,70 @@ declare class SlicerClient {
712
712
  getInfo(): Promise<SlicerInfo>;
713
713
  }
714
714
 
715
- export { type AddressMapping, type AgentHealth, type BgDeleteResponse, type BgExecInfo, type BgExecRequest, type BgExecResponse, type BgKillOptions, type BgKillResponse, type BgLogOptions, type BgWaitExitResponse, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, type FSEntry, type FSMkdirRequest, type FSWatchEvent, type FSWatchEventType, type FSWatchRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, type UpdateSecretRequest, VM, VMBg, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, parseAddressMapping, resolveTransport };
715
+ /**
716
+ * Browser-compatible shell adapter for Slicer VMs.
717
+ *
718
+ * Provides frame encode/decode helpers and a `SlicerShellSession` class that
719
+ * wires a standard browser WebSocket to an xterm.js Terminal instance using
720
+ * the Slicer binary shell protocol.
721
+ *
722
+ * This module intentionally avoids Node-only imports so it can be used in
723
+ * browser bundles.
724
+ */
725
+ declare const FRAME_TYPE_DATA = 1;
726
+ declare const FRAME_TYPE_WINDOW_SIZE = 2;
727
+ declare const FRAME_TYPE_SHUTDOWN = 3;
728
+ declare const FRAME_TYPE_HEARTBEAT = 4;
729
+ declare const FRAME_TYPE_SESSION_CLOSE = 5;
730
+ /** Encode a frame into the 5-byte-header binary protocol. */
731
+ declare function encodeFrame(frameType: number, payload?: Uint8Array): Uint8Array;
732
+ /** Parse a binary frame. Returns null if the data is malformed. */
733
+ declare function parseFrame(data: ArrayBuffer): {
734
+ frameType: number;
735
+ payload: Uint8Array;
736
+ } | null;
737
+ interface ShellSessionOptions {
738
+ /** WebSocket URL for the shell endpoint (ws:// or wss://). */
739
+ url: string;
740
+ /** Heartbeat interval in ms. Default 30000. */
741
+ heartbeatIntervalMs?: number;
742
+ /** Called when connection state changes. */
743
+ onStateChange?: (state: 'connecting' | 'connected' | 'disconnected') => void;
744
+ /** Called on error. */
745
+ onError?: (error: string) => void;
746
+ }
747
+ /**
748
+ * Minimal xterm.js Terminal interface — only the methods SlicerShellSession
749
+ * actually calls. Avoids requiring @xterm/xterm as a dependency.
750
+ */
751
+ interface XTermLike {
752
+ onData: (cb: (data: string) => void) => {
753
+ dispose: () => void;
754
+ };
755
+ write: (data: string) => void;
756
+ reset: () => void;
757
+ cols: number;
758
+ rows: number;
759
+ }
760
+ declare class SlicerShellSession {
761
+ private readonly terminal;
762
+ private readonly options;
763
+ private ws;
764
+ private heartbeatTimer;
765
+ private dataDisposable;
766
+ constructor(terminal: XTermLike, options: ShellSessionOptions);
767
+ /** True when the WebSocket is open and relaying. */
768
+ get connected(): boolean;
769
+ /** Open the WebSocket and begin relaying. */
770
+ connect(): void;
771
+ /** Send a graceful shutdown frame and close. */
772
+ disconnect(): void;
773
+ /** Send a window resize. Call this from FitAddon's onResize or a ResizeObserver. */
774
+ resize(cols: number, rows: number): void;
775
+ private sendResize;
776
+ private startHeartbeat;
777
+ private stopHeartbeat;
778
+ private teardown;
779
+ }
780
+
781
+ export { type AddressMapping, type AgentHealth, type BgDeleteResponse, type BgExecInfo, type BgExecRequest, type BgExecResponse, type BgKillOptions, type BgKillResponse, type BgLogOptions, type BgWaitExitResponse, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, FRAME_TYPE_DATA, FRAME_TYPE_HEARTBEAT, FRAME_TYPE_SESSION_CLOSE, FRAME_TYPE_SHUTDOWN, FRAME_TYPE_WINDOW_SIZE, type FSEntry, type FSMkdirRequest, type FSWatchEvent, type FSWatchEventType, type FSWatchRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShellSessionOptions, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, SlicerShellSession, type UpdateSecretRequest, VM, VMBg, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, type XTermLike, encodeFrame, parseAddressMapping, parseFrame, resolveTransport };
package/dist/index.d.ts CHANGED
@@ -712,4 +712,70 @@ declare class SlicerClient {
712
712
  getInfo(): Promise<SlicerInfo>;
713
713
  }
714
714
 
715
- export { type AddressMapping, type AgentHealth, type BgDeleteResponse, type BgExecInfo, type BgExecRequest, type BgExecResponse, type BgKillOptions, type BgKillResponse, type BgLogOptions, type BgWaitExitResponse, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, type FSEntry, type FSMkdirRequest, type FSWatchEvent, type FSWatchEventType, type FSWatchRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, type UpdateSecretRequest, VM, VMBg, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, parseAddressMapping, resolveTransport };
715
+ /**
716
+ * Browser-compatible shell adapter for Slicer VMs.
717
+ *
718
+ * Provides frame encode/decode helpers and a `SlicerShellSession` class that
719
+ * wires a standard browser WebSocket to an xterm.js Terminal instance using
720
+ * the Slicer binary shell protocol.
721
+ *
722
+ * This module intentionally avoids Node-only imports so it can be used in
723
+ * browser bundles.
724
+ */
725
+ declare const FRAME_TYPE_DATA = 1;
726
+ declare const FRAME_TYPE_WINDOW_SIZE = 2;
727
+ declare const FRAME_TYPE_SHUTDOWN = 3;
728
+ declare const FRAME_TYPE_HEARTBEAT = 4;
729
+ declare const FRAME_TYPE_SESSION_CLOSE = 5;
730
+ /** Encode a frame into the 5-byte-header binary protocol. */
731
+ declare function encodeFrame(frameType: number, payload?: Uint8Array): Uint8Array;
732
+ /** Parse a binary frame. Returns null if the data is malformed. */
733
+ declare function parseFrame(data: ArrayBuffer): {
734
+ frameType: number;
735
+ payload: Uint8Array;
736
+ } | null;
737
+ interface ShellSessionOptions {
738
+ /** WebSocket URL for the shell endpoint (ws:// or wss://). */
739
+ url: string;
740
+ /** Heartbeat interval in ms. Default 30000. */
741
+ heartbeatIntervalMs?: number;
742
+ /** Called when connection state changes. */
743
+ onStateChange?: (state: 'connecting' | 'connected' | 'disconnected') => void;
744
+ /** Called on error. */
745
+ onError?: (error: string) => void;
746
+ }
747
+ /**
748
+ * Minimal xterm.js Terminal interface — only the methods SlicerShellSession
749
+ * actually calls. Avoids requiring @xterm/xterm as a dependency.
750
+ */
751
+ interface XTermLike {
752
+ onData: (cb: (data: string) => void) => {
753
+ dispose: () => void;
754
+ };
755
+ write: (data: string) => void;
756
+ reset: () => void;
757
+ cols: number;
758
+ rows: number;
759
+ }
760
+ declare class SlicerShellSession {
761
+ private readonly terminal;
762
+ private readonly options;
763
+ private ws;
764
+ private heartbeatTimer;
765
+ private dataDisposable;
766
+ constructor(terminal: XTermLike, options: ShellSessionOptions);
767
+ /** True when the WebSocket is open and relaying. */
768
+ get connected(): boolean;
769
+ /** Open the WebSocket and begin relaying. */
770
+ connect(): void;
771
+ /** Send a graceful shutdown frame and close. */
772
+ disconnect(): void;
773
+ /** Send a window resize. Call this from FitAddon's onResize or a ResizeObserver. */
774
+ resize(cols: number, rows: number): void;
775
+ private sendResize;
776
+ private startHeartbeat;
777
+ private stopHeartbeat;
778
+ private teardown;
779
+ }
780
+
781
+ export { type AddressMapping, type AgentHealth, type BgDeleteResponse, type BgExecInfo, type BgExecRequest, type BgExecResponse, type BgKillOptions, type BgKillResponse, type BgLogOptions, type BgWaitExitResponse, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, FRAME_TYPE_DATA, FRAME_TYPE_HEARTBEAT, FRAME_TYPE_SESSION_CLOSE, FRAME_TYPE_SHUTDOWN, FRAME_TYPE_WINDOW_SIZE, type FSEntry, type FSMkdirRequest, type FSWatchEvent, type FSWatchEventType, type FSWatchRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShellSessionOptions, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, SlicerShellSession, type UpdateSecretRequest, VM, VMBg, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, type XTermLike, encodeFrame, parseAddressMapping, parseFrame, resolveTransport };
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import os from 'os';
5
5
  import path2 from 'path';
6
6
  import net, { createServer } from 'net';
7
7
  import fs from 'fs';
8
- import { createWebSocketStream, WebSocket } from 'ws';
8
+ import { createWebSocketStream, WebSocket as WebSocket$1 } from 'ws';
9
9
 
10
10
  // src/types.ts
11
11
  var ExecStdioText = "text";
@@ -526,7 +526,7 @@ function openWebSocket(init, mapping) {
526
526
  if (init.transport.kind === "socket") {
527
527
  opts.agent = unixAgent(init.transport.socketPath);
528
528
  }
529
- return new WebSocket(url, opts);
529
+ return new WebSocket$1(url, opts);
530
530
  }
531
531
  function wsURLForVM(transport, hostname) {
532
532
  if (transport.kind === "socket") {
@@ -965,8 +965,8 @@ var VMBg = class {
965
965
  q.set("cmd", req.command);
966
966
  for (const a of req.args ?? []) q.append("args", a);
967
967
  for (const e of req.env ?? []) q.append("env", e);
968
- if (req.uid !== void 0 && req.uid !== 0) q.set("uid", String(req.uid));
969
- if (req.gid !== void 0 && req.gid !== 0) q.set("gid", String(req.gid));
968
+ if (req.uid !== void 0) q.set("uid", String(req.uid));
969
+ if (req.gid !== void 0) q.set("gid", String(req.gid));
970
970
  if (req.shell) q.set("shell", req.shell);
971
971
  if (req.cwd) q.set("cwd", req.cwd);
972
972
  if (req.ringBytes !== void 0 && req.ringBytes > 0) {
@@ -1278,6 +1278,146 @@ var SlicerClient = class _SlicerClient {
1278
1278
  }
1279
1279
  };
1280
1280
 
1281
- export { ExecStdioBase64, ExecStdioText, Forwarder, GiB, HostGroupsAPI, MiB, NonRootUser, SecretExistsError, SecretsAPI, SlicerAPIError, SlicerClient, VM, VMBg, VMFileSystem, VMsAPI, parseAddressMapping, resolveTransport };
1281
+ // src/shell.ts
1282
+ var FRAME_TYPE_DATA = 1;
1283
+ var FRAME_TYPE_WINDOW_SIZE = 2;
1284
+ var FRAME_TYPE_SHUTDOWN = 3;
1285
+ var FRAME_TYPE_HEARTBEAT = 4;
1286
+ var FRAME_TYPE_SESSION_CLOSE = 5;
1287
+ var HEADER_SIZE = 5;
1288
+ function encodeFrame(frameType, payload) {
1289
+ const payloadLen = payload ? payload.byteLength : 0;
1290
+ const buf = new Uint8Array(HEADER_SIZE + payloadLen);
1291
+ const view = new DataView(buf.buffer);
1292
+ view.setUint8(0, frameType);
1293
+ view.setUint32(1, payloadLen, false);
1294
+ if (payload) buf.set(payload, HEADER_SIZE);
1295
+ return buf;
1296
+ }
1297
+ function parseFrame(data) {
1298
+ if (data.byteLength < HEADER_SIZE) return null;
1299
+ const view = new DataView(data);
1300
+ const frameType = view.getUint8(0);
1301
+ const payloadLen = view.getUint32(1, false);
1302
+ if (data.byteLength < HEADER_SIZE + payloadLen) return null;
1303
+ const payload = new Uint8Array(data, HEADER_SIZE, payloadLen);
1304
+ return { frameType, payload };
1305
+ }
1306
+ var encoder = new TextEncoder();
1307
+ var decoder = new TextDecoder();
1308
+ var SlicerShellSession = class {
1309
+ constructor(terminal, options) {
1310
+ this.terminal = terminal;
1311
+ this.options = options;
1312
+ }
1313
+ terminal;
1314
+ options;
1315
+ ws = null;
1316
+ heartbeatTimer = null;
1317
+ dataDisposable = null;
1318
+ /** True when the WebSocket is open and relaying. */
1319
+ get connected() {
1320
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
1321
+ }
1322
+ /** Open the WebSocket and begin relaying. */
1323
+ connect() {
1324
+ if (this.ws) return;
1325
+ this.options.onStateChange?.("connecting");
1326
+ const ws = new WebSocket(this.options.url);
1327
+ ws.binaryType = "arraybuffer";
1328
+ this.ws = ws;
1329
+ ws.onopen = () => {
1330
+ this.options.onStateChange?.("connected");
1331
+ this.terminal.reset();
1332
+ this.sendResize(this.terminal.cols, this.terminal.rows);
1333
+ this.startHeartbeat();
1334
+ };
1335
+ ws.onmessage = (ev) => {
1336
+ if (!(ev.data instanceof ArrayBuffer)) return;
1337
+ const frame = parseFrame(ev.data);
1338
+ if (!frame) return;
1339
+ switch (frame.frameType) {
1340
+ case FRAME_TYPE_DATA:
1341
+ this.terminal.write(decoder.decode(frame.payload));
1342
+ break;
1343
+ case FRAME_TYPE_SHUTDOWN:
1344
+ case FRAME_TYPE_SESSION_CLOSE:
1345
+ this.teardown();
1346
+ break;
1347
+ }
1348
+ };
1349
+ ws.onclose = () => {
1350
+ this.teardown();
1351
+ };
1352
+ ws.onerror = () => {
1353
+ this.options.onError?.("WebSocket error");
1354
+ this.teardown();
1355
+ };
1356
+ this.dataDisposable = this.terminal.onData((data) => {
1357
+ if (!this.connected) return;
1358
+ const payload = encoder.encode(data);
1359
+ this.ws.send(encodeFrame(FRAME_TYPE_DATA, payload));
1360
+ });
1361
+ }
1362
+ /** Send a graceful shutdown frame and close. */
1363
+ disconnect() {
1364
+ if (this.ws) {
1365
+ try {
1366
+ this.ws.send(encodeFrame(FRAME_TYPE_SHUTDOWN));
1367
+ } catch {
1368
+ }
1369
+ }
1370
+ this.teardown();
1371
+ }
1372
+ /** Send a window resize. Call this from FitAddon's onResize or a ResizeObserver. */
1373
+ resize(cols, rows) {
1374
+ if (!this.connected) return;
1375
+ this.sendResize(cols, rows);
1376
+ }
1377
+ // --- internals -------------------------------------------------------------
1378
+ sendResize(cols, rows) {
1379
+ const payload = new Uint8Array(8);
1380
+ const view = new DataView(payload.buffer);
1381
+ view.setUint32(0, cols, false);
1382
+ view.setUint32(4, rows, false);
1383
+ this.ws.send(encodeFrame(FRAME_TYPE_WINDOW_SIZE, payload));
1384
+ }
1385
+ startHeartbeat() {
1386
+ this.stopHeartbeat();
1387
+ const intervalMs = this.options.heartbeatIntervalMs ?? 3e4;
1388
+ this.heartbeatTimer = setInterval(() => {
1389
+ if (!this.connected) return;
1390
+ this.ws.send(encodeFrame(FRAME_TYPE_HEARTBEAT));
1391
+ }, intervalMs);
1392
+ }
1393
+ stopHeartbeat() {
1394
+ if (this.heartbeatTimer !== null) {
1395
+ clearInterval(this.heartbeatTimer);
1396
+ this.heartbeatTimer = null;
1397
+ }
1398
+ }
1399
+ teardown() {
1400
+ this.stopHeartbeat();
1401
+ if (this.dataDisposable) {
1402
+ this.dataDisposable.dispose();
1403
+ this.dataDisposable = null;
1404
+ }
1405
+ if (this.ws) {
1406
+ const ws = this.ws;
1407
+ this.ws = null;
1408
+ ws.onopen = null;
1409
+ ws.onmessage = null;
1410
+ ws.onclose = null;
1411
+ ws.onerror = null;
1412
+ try {
1413
+ ws.close();
1414
+ } catch {
1415
+ }
1416
+ }
1417
+ this.options.onStateChange?.("disconnected");
1418
+ }
1419
+ };
1420
+
1421
+ export { ExecStdioBase64, ExecStdioText, FRAME_TYPE_DATA, FRAME_TYPE_HEARTBEAT, FRAME_TYPE_SESSION_CLOSE, FRAME_TYPE_SHUTDOWN, FRAME_TYPE_WINDOW_SIZE, Forwarder, GiB, HostGroupsAPI, MiB, NonRootUser, SecretExistsError, SecretsAPI, SlicerAPIError, SlicerClient, SlicerShellSession, VM, VMBg, VMFileSystem, VMsAPI, encodeFrame, parseAddressMapping, parseFrame, resolveTransport };
1282
1422
  //# sourceMappingURL=index.js.map
1283
1423
  //# sourceMappingURL=index.js.map