@slicervm/sdk 0.1.5 → 0.1.7

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.cjs CHANGED
@@ -266,6 +266,7 @@ function createVMReqToWire(r) {
266
266
  if (r.ip !== void 0) o.ip = r.ip;
267
267
  if (r.tags !== void 0) o.tags = r.tags;
268
268
  if (r.secrets !== void 0) o.secrets = r.secrets;
269
+ if (r.network !== void 0) o.network = r.network;
269
270
  return o;
270
271
  }
271
272
  function createVMResFromWire(w) {
@@ -976,8 +977,8 @@ var VMBg = class {
976
977
  q.set("cmd", req.command);
977
978
  for (const a of req.args ?? []) q.append("args", a);
978
979
  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));
980
+ if (req.uid !== void 0) q.set("uid", String(req.uid));
981
+ if (req.gid !== void 0) q.set("gid", String(req.gid));
981
982
  if (req.shell) q.set("shell", req.shell);
982
983
  if (req.cwd) q.set("cwd", req.cwd);
983
984
  if (req.ringBytes !== void 0 && req.ringBytes > 0) {
@@ -1262,17 +1263,173 @@ function buildListQuery(opts) {
1262
1263
  return s ? `?${s}` : "";
1263
1264
  }
1264
1265
 
1266
+ // src/proxy.ts
1267
+ var ProxySecretBearer = "bearer";
1268
+ var ProxySecretBasic = "basic";
1269
+ function clientFromWire(w) {
1270
+ return { name: w.name, createdAt: w.created_at };
1271
+ }
1272
+ function clientCreatedFromWire(w) {
1273
+ return { name: w.name, token: w.token, createdAt: w.created_at };
1274
+ }
1275
+ function secretFromWire2(w) {
1276
+ const out = { name: w.name, host: w.host, createdAt: w.created_at };
1277
+ if (w.type) out.type = w.type;
1278
+ return out;
1279
+ }
1280
+ function ruleFromWire(w) {
1281
+ const out = { host: w.host };
1282
+ if (w.secret) out.secret = w.secret;
1283
+ if (w.methods && w.methods.length > 0) out.methods = w.methods;
1284
+ if (w.paths && w.paths.length > 0) out.paths = w.paths;
1285
+ if (w.expires && w.expires !== "0001-01-01T00:00:00Z") out.expires = w.expires;
1286
+ if (w.passthrough) out.passthrough = w.passthrough;
1287
+ return out;
1288
+ }
1289
+ var ProxyAPI = class {
1290
+ constructor(transport) {
1291
+ this.transport = transport;
1292
+ this.clients = new ProxyClientsAPI(transport);
1293
+ this.secrets = new ProxySecretsAPI(transport);
1294
+ this.allows = new ProxyAllowsAPI(transport);
1295
+ }
1296
+ transport;
1297
+ clients;
1298
+ secrets;
1299
+ allows;
1300
+ };
1301
+ var ProxyClientsAPI = class {
1302
+ constructor(transport) {
1303
+ this.transport = transport;
1304
+ }
1305
+ transport;
1306
+ /** Mint a new proxy client. The returned token is shown once. */
1307
+ async create(name, opts = {}) {
1308
+ const body = { name };
1309
+ if (opts.token) body.token = opts.token;
1310
+ const wire = await this.transport.request(
1311
+ "POST",
1312
+ "/proxy/v1/clients",
1313
+ body
1314
+ );
1315
+ return clientCreatedFromWire(wire);
1316
+ }
1317
+ async list() {
1318
+ const wire = await this.transport.request("GET", "/proxy/v1/clients");
1319
+ return (wire ?? []).map(clientFromWire);
1320
+ }
1321
+ /**
1322
+ * Revoke the token, drop every allow rule the client owned, and
1323
+ * remove the client.
1324
+ */
1325
+ async delete(name) {
1326
+ await this.transport.request("DELETE", `/proxy/v1/clients/${encodeURIComponent(name)}`);
1327
+ }
1328
+ /** List a client's allow rules in declaration order (first-match-wins). */
1329
+ async rules(name) {
1330
+ const wire = await this.transport.request(
1331
+ "GET",
1332
+ `/proxy/v1/clients/${encodeURIComponent(name)}`
1333
+ );
1334
+ return (wire ?? []).map(ruleFromWire);
1335
+ }
1336
+ };
1337
+ var ProxySecretsAPI = class {
1338
+ constructor(transport) {
1339
+ this.transport = transport;
1340
+ }
1341
+ transport;
1342
+ async create(req) {
1343
+ const body = {
1344
+ name: req.name,
1345
+ host: req.host,
1346
+ value: req.value
1347
+ };
1348
+ if (req.type) body.type = req.type;
1349
+ await this.transport.request("POST", "/proxy/v1/secrets", body);
1350
+ }
1351
+ async list() {
1352
+ const wire = await this.transport.request("GET", "/proxy/v1/secrets");
1353
+ return (wire ?? []).map(secretFromWire2);
1354
+ }
1355
+ /**
1356
+ * Remove a secret. Allow rules that reference it stop matching until
1357
+ * the secret is recreated or the rule is rewritten.
1358
+ */
1359
+ async delete(name) {
1360
+ await this.transport.request("DELETE", `/proxy/v1/secrets/${encodeURIComponent(name)}`);
1361
+ }
1362
+ };
1363
+ var ProxyAllowsAPI = class {
1364
+ constructor(transport) {
1365
+ this.transport = transport;
1366
+ }
1367
+ transport;
1368
+ /** Add an allow rule. Returns the resolved rule with absolute `expires`. */
1369
+ async add(req) {
1370
+ const body = {
1371
+ client: req.client,
1372
+ host: req.host
1373
+ };
1374
+ if (req.secret) body.secret = req.secret;
1375
+ if (req.methods && req.methods.length > 0) body.methods = req.methods;
1376
+ if (req.paths && req.paths.length > 0) body.paths = req.paths;
1377
+ if (req.ttlSeconds && req.ttlSeconds > 0) body.ttl_seconds = req.ttlSeconds;
1378
+ if (req.passthrough) body.passthrough = true;
1379
+ const wire = await this.transport.request(
1380
+ "POST",
1381
+ "/proxy/v1/allows",
1382
+ body
1383
+ );
1384
+ return ruleFromWire(wire);
1385
+ }
1386
+ /**
1387
+ * Host-bulk revoke: removes **every** rule on the client whose host
1388
+ * matches. For surgical removal of one rule among siblings on the
1389
+ * same host (e.g. several path-scoped rules on `github.com`), use
1390
+ * `removeByTuple` instead.
1391
+ */
1392
+ async remove(client, host) {
1393
+ await this.transport.request(
1394
+ "DELETE",
1395
+ `/proxy/v1/allows/${encodeURIComponent(client)}/${encodeURIComponent(host)}`
1396
+ );
1397
+ }
1398
+ /**
1399
+ * Surgical revoke: removes the single rule whose
1400
+ * (host, methods, paths, passthrough) tuple matches the request.
1401
+ * Pass exactly the same fields you used at create time. Method and
1402
+ * host casing are normalised server-side, so `"GET"` / `"get"` and
1403
+ * `"github.com"` / `"GITHUB.COM"` all match the same stored rule.
1404
+ *
1405
+ * Returns 404 (surfaced as a SlicerAPIError) when no rule matches.
1406
+ */
1407
+ async removeByTuple(req) {
1408
+ const body = {
1409
+ client: req.client,
1410
+ host: req.host
1411
+ };
1412
+ if (req.secret) body.secret = req.secret;
1413
+ if (req.methods && req.methods.length > 0) body.methods = req.methods;
1414
+ if (req.paths && req.paths.length > 0) body.paths = req.paths;
1415
+ if (req.passthrough) body.passthrough = true;
1416
+ await this.transport.request("POST", "/proxy/v1/allows/revoke", body);
1417
+ }
1418
+ };
1419
+
1265
1420
  // src/client.ts
1266
1421
  var SlicerClient = class _SlicerClient {
1267
1422
  transport;
1268
1423
  hostGroups;
1269
1424
  vms;
1270
1425
  secrets;
1426
+ proxy;
1271
1427
  constructor(opts) {
1272
1428
  this.transport = new TransportClient(opts);
1273
1429
  this.hostGroups = new HostGroupsAPI(this.transport);
1274
1430
  this.vms = new VMsAPI(this.transport);
1275
1431
  this.secrets = new SecretsAPI(this.transport);
1432
+ this.proxy = new ProxyAPI(this.transport);
1276
1433
  }
1277
1434
  static fromEnv(overrides = {}) {
1278
1435
  const baseURL = overrides.baseURL ?? process.env.SLICER_URL;
@@ -1289,22 +1446,176 @@ var SlicerClient = class _SlicerClient {
1289
1446
  }
1290
1447
  };
1291
1448
 
1449
+ // src/shell.ts
1450
+ var FRAME_TYPE_DATA = 1;
1451
+ var FRAME_TYPE_WINDOW_SIZE = 2;
1452
+ var FRAME_TYPE_SHUTDOWN = 3;
1453
+ var FRAME_TYPE_HEARTBEAT = 4;
1454
+ var FRAME_TYPE_SESSION_CLOSE = 5;
1455
+ var HEADER_SIZE = 5;
1456
+ function encodeFrame(frameType, payload) {
1457
+ const payloadLen = payload ? payload.byteLength : 0;
1458
+ const buf = new Uint8Array(HEADER_SIZE + payloadLen);
1459
+ const view = new DataView(buf.buffer);
1460
+ view.setUint8(0, frameType);
1461
+ view.setUint32(1, payloadLen, false);
1462
+ if (payload) buf.set(payload, HEADER_SIZE);
1463
+ return buf;
1464
+ }
1465
+ function parseFrame(data) {
1466
+ if (data.byteLength < HEADER_SIZE) return null;
1467
+ const view = new DataView(data);
1468
+ const frameType = view.getUint8(0);
1469
+ const payloadLen = view.getUint32(1, false);
1470
+ if (data.byteLength < HEADER_SIZE + payloadLen) return null;
1471
+ const payload = new Uint8Array(data, HEADER_SIZE, payloadLen);
1472
+ return { frameType, payload };
1473
+ }
1474
+ var encoder = new TextEncoder();
1475
+ var decoder = new TextDecoder();
1476
+ var SlicerShellSession = class {
1477
+ constructor(terminal, options) {
1478
+ this.terminal = terminal;
1479
+ this.options = options;
1480
+ }
1481
+ terminal;
1482
+ options;
1483
+ ws = null;
1484
+ heartbeatTimer = null;
1485
+ dataDisposable = null;
1486
+ /** True when the WebSocket is open and relaying. */
1487
+ get connected() {
1488
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
1489
+ }
1490
+ /** Open the WebSocket and begin relaying. */
1491
+ connect() {
1492
+ if (this.ws) return;
1493
+ this.options.onStateChange?.("connecting");
1494
+ const ws = new WebSocket(this.options.url);
1495
+ ws.binaryType = "arraybuffer";
1496
+ this.ws = ws;
1497
+ ws.onopen = () => {
1498
+ this.options.onStateChange?.("connected");
1499
+ this.terminal.reset();
1500
+ this.sendResize(this.terminal.cols, this.terminal.rows);
1501
+ this.startHeartbeat();
1502
+ };
1503
+ ws.onmessage = (ev) => {
1504
+ if (!(ev.data instanceof ArrayBuffer)) return;
1505
+ const frame = parseFrame(ev.data);
1506
+ if (!frame) return;
1507
+ switch (frame.frameType) {
1508
+ case FRAME_TYPE_DATA:
1509
+ this.terminal.write(decoder.decode(frame.payload));
1510
+ break;
1511
+ case FRAME_TYPE_SHUTDOWN:
1512
+ case FRAME_TYPE_SESSION_CLOSE:
1513
+ this.teardown();
1514
+ break;
1515
+ }
1516
+ };
1517
+ ws.onclose = () => {
1518
+ this.teardown();
1519
+ };
1520
+ ws.onerror = () => {
1521
+ this.options.onError?.("WebSocket error");
1522
+ this.teardown();
1523
+ };
1524
+ this.dataDisposable = this.terminal.onData((data) => {
1525
+ if (!this.connected) return;
1526
+ const payload = encoder.encode(data);
1527
+ this.ws.send(encodeFrame(FRAME_TYPE_DATA, payload));
1528
+ });
1529
+ }
1530
+ /** Send a graceful shutdown frame and close. */
1531
+ disconnect() {
1532
+ if (this.ws) {
1533
+ try {
1534
+ this.ws.send(encodeFrame(FRAME_TYPE_SHUTDOWN));
1535
+ } catch {
1536
+ }
1537
+ }
1538
+ this.teardown();
1539
+ }
1540
+ /** Send a window resize. Call this from FitAddon's onResize or a ResizeObserver. */
1541
+ resize(cols, rows) {
1542
+ if (!this.connected) return;
1543
+ this.sendResize(cols, rows);
1544
+ }
1545
+ // --- internals -------------------------------------------------------------
1546
+ sendResize(cols, rows) {
1547
+ const payload = new Uint8Array(8);
1548
+ const view = new DataView(payload.buffer);
1549
+ view.setUint32(0, cols, false);
1550
+ view.setUint32(4, rows, false);
1551
+ this.ws.send(encodeFrame(FRAME_TYPE_WINDOW_SIZE, payload));
1552
+ }
1553
+ startHeartbeat() {
1554
+ this.stopHeartbeat();
1555
+ const intervalMs = this.options.heartbeatIntervalMs ?? 3e4;
1556
+ this.heartbeatTimer = setInterval(() => {
1557
+ if (!this.connected) return;
1558
+ this.ws.send(encodeFrame(FRAME_TYPE_HEARTBEAT));
1559
+ }, intervalMs);
1560
+ }
1561
+ stopHeartbeat() {
1562
+ if (this.heartbeatTimer !== null) {
1563
+ clearInterval(this.heartbeatTimer);
1564
+ this.heartbeatTimer = null;
1565
+ }
1566
+ }
1567
+ teardown() {
1568
+ this.stopHeartbeat();
1569
+ if (this.dataDisposable) {
1570
+ this.dataDisposable.dispose();
1571
+ this.dataDisposable = null;
1572
+ }
1573
+ if (this.ws) {
1574
+ const ws = this.ws;
1575
+ this.ws = null;
1576
+ ws.onopen = null;
1577
+ ws.onmessage = null;
1578
+ ws.onclose = null;
1579
+ ws.onerror = null;
1580
+ try {
1581
+ ws.close();
1582
+ } catch {
1583
+ }
1584
+ }
1585
+ this.options.onStateChange?.("disconnected");
1586
+ }
1587
+ };
1588
+
1292
1589
  exports.ExecStdioBase64 = ExecStdioBase64;
1293
1590
  exports.ExecStdioText = ExecStdioText;
1591
+ exports.FRAME_TYPE_DATA = FRAME_TYPE_DATA;
1592
+ exports.FRAME_TYPE_HEARTBEAT = FRAME_TYPE_HEARTBEAT;
1593
+ exports.FRAME_TYPE_SESSION_CLOSE = FRAME_TYPE_SESSION_CLOSE;
1594
+ exports.FRAME_TYPE_SHUTDOWN = FRAME_TYPE_SHUTDOWN;
1595
+ exports.FRAME_TYPE_WINDOW_SIZE = FRAME_TYPE_WINDOW_SIZE;
1294
1596
  exports.Forwarder = Forwarder;
1295
1597
  exports.GiB = GiB;
1296
1598
  exports.HostGroupsAPI = HostGroupsAPI;
1297
1599
  exports.MiB = MiB;
1298
1600
  exports.NonRootUser = NonRootUser;
1601
+ exports.ProxyAPI = ProxyAPI;
1602
+ exports.ProxyAllowsAPI = ProxyAllowsAPI;
1603
+ exports.ProxyClientsAPI = ProxyClientsAPI;
1604
+ exports.ProxySecretBasic = ProxySecretBasic;
1605
+ exports.ProxySecretBearer = ProxySecretBearer;
1606
+ exports.ProxySecretsAPI = ProxySecretsAPI;
1299
1607
  exports.SecretExistsError = SecretExistsError;
1300
1608
  exports.SecretsAPI = SecretsAPI;
1301
1609
  exports.SlicerAPIError = SlicerAPIError;
1302
1610
  exports.SlicerClient = SlicerClient;
1611
+ exports.SlicerShellSession = SlicerShellSession;
1303
1612
  exports.VM = VM;
1304
1613
  exports.VMBg = VMBg;
1305
1614
  exports.VMFileSystem = VMFileSystem;
1306
1615
  exports.VMsAPI = VMsAPI;
1616
+ exports.encodeFrame = encodeFrame;
1307
1617
  exports.parseAddressMapping = parseAddressMapping;
1618
+ exports.parseFrame = parseFrame;
1308
1619
  exports.resolveTransport = resolveTransport;
1309
1620
  //# sourceMappingURL=index.cjs.map
1310
1621
  //# sourceMappingURL=index.cjs.map