@slicervm/sdk 0.1.7 → 0.1.8

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  TypeScript SDK for the [Slicer](https://slicervm.com) VM control-plane API.
4
4
 
5
- Mirrors the [Go SDK](https://github.com/slicervm/sdk) semantically. The top-level `SlicerClient` exposes `hostGroups`, `vms`, and `secrets` namespaces for control-plane operations; per-VM operations live on a `VM` handle returned from `client.vms.create()` / `client.vms.attach()`.
5
+ Mirrors the [Go SDK](https://github.com/slicervm/sdk) semantically. The top-level `SlicerClient` exposes `hostGroups`, `vms`, `commits`, and `secrets` namespaces for control-plane operations; per-VM operations live on a `VM` handle returned from `client.vms.create()` / `client.vms.attach()`.
6
6
 
7
7
  Supports Unix socket and HTTP(S) transports.
8
8
 
@@ -33,6 +33,34 @@ for await (const f of vm.bg.logs(bg.execId, { follow: true })) {
33
33
  await vm.delete();
34
34
  ```
35
35
 
36
+ ## Cold forks
37
+
38
+ A stopped persistent VM can be committed as an immutable disk parent, then
39
+ forked into cold-booted children. Fork calls always wait until the child agent
40
+ has finalised its guest identity.
41
+
42
+ ```ts
43
+ await vm.shutdown();
44
+ const parent = await vm.commit({
45
+ tags: ['node-build'],
46
+ cacheKey: 'node-build-v1',
47
+ });
48
+
49
+ const child = await parent.fork({
50
+ waitTimeoutSec: 120,
51
+ network: { allow: [], drop: ['0.0.0.0/0'] },
52
+ tags: ['job=node-build'],
53
+ });
54
+
55
+ const description = await child.describe();
56
+ console.log(description.parentCommitId, description.network.effective);
57
+
58
+ const cached = await client.commits.list({ cacheKey: 'node-build-v1' });
59
+ await child.delete();
60
+ await vm.delete();
61
+ await client.commits.delete(parent.commitId);
62
+ ```
63
+
36
64
  ## Status
37
65
 
38
66
  Pre-release. See `ts-plan.md`.
package/dist/index.cjs CHANGED
@@ -284,7 +284,71 @@ function agentHealthFromWire(w) {
284
284
  ...w.agent_uptime !== void 0 && { agentUptime: w.agent_uptime },
285
285
  ...w.agent_version !== void 0 && { agentVersion: w.agent_version },
286
286
  ...w.system_uptime !== void 0 && { systemUptime: w.system_uptime },
287
- ...w.userdata_ran !== void 0 && { userdataRan: w.userdata_ran }
287
+ ...w.userdata_ran !== void 0 && { userdataRan: w.userdata_ran },
288
+ ...w.userdata_exit_code !== void 0 && { userdataExitCode: w.userdata_exit_code }
289
+ };
290
+ }
291
+ function vmCommitFromWire(w) {
292
+ return {
293
+ hostname: w.hostname,
294
+ commitId: w.commit_id,
295
+ status: w.status,
296
+ ...w.parent_status !== void 0 && { parentStatus: w.parent_status },
297
+ mode: w.mode ?? "",
298
+ ...w.tags !== void 0 && { tags: w.tags },
299
+ ...w.labels !== void 0 && { labels: w.labels },
300
+ ...w.cache_key !== void 0 && { cacheKey: w.cache_key }
301
+ };
302
+ }
303
+ function vmCommitInfoFromWire(w) {
304
+ return {
305
+ commitId: w.commit_id,
306
+ sourceHostname: w.source_hostname,
307
+ sourceHostGroup: w.source_host_group,
308
+ createdAt: w.created_at,
309
+ mode: w.mode,
310
+ ...w.tags !== void 0 && { tags: w.tags },
311
+ ...w.labels !== void 0 && { labels: w.labels },
312
+ ...w.cache_key !== void 0 && { cacheKey: w.cache_key }
313
+ };
314
+ }
315
+ function vmCommitDeleteFromWire(w) {
316
+ return { commitId: w.commit_id, status: w.status };
317
+ }
318
+ function vmForkFromWire(w) {
319
+ return {
320
+ hostname: w.hostname,
321
+ sourceHostname: w.source_hostname,
322
+ ...w.commit_id !== void 0 && { commitId: w.commit_id },
323
+ status: w.status,
324
+ ...w.parent_status !== void 0 && { parentStatus: w.parent_status },
325
+ ...w.child_status !== void 0 && { childStatus: w.child_status },
326
+ mode: w.mode ?? ""
327
+ };
328
+ }
329
+ function vmDescriptionFromWire(w) {
330
+ return {
331
+ hostname: w.hostname,
332
+ ...w.hostgroup !== void 0 && { hostGroup: w.hostgroup },
333
+ ...w.ip !== void 0 && { ip: w.ip },
334
+ ...w.ram_bytes !== void 0 && { ramBytes: w.ram_bytes },
335
+ ...w.cpus !== void 0 && { cpus: w.cpus },
336
+ ...w.created_at !== void 0 && { createdAt: w.created_at },
337
+ ...w.arch !== void 0 && { arch: w.arch },
338
+ ...w.tags !== void 0 && { tags: w.tags },
339
+ ...w.status !== void 0 && { status: w.status },
340
+ ...w.persistent !== void 0 && { persistent: w.persistent },
341
+ ...w.storage !== void 0 && { storage: w.storage },
342
+ ...w.image !== void 0 && { image: w.image },
343
+ ...w.commit_id !== void 0 && { commitId: w.commit_id },
344
+ ...w.parent_commit_id !== void 0 && { parentCommitId: w.parent_commit_id },
345
+ network: {
346
+ ...w.network.mode !== void 0 && { mode: w.network.mode },
347
+ ...w.network.policy_source !== void 0 && { policySource: w.network.policy_source },
348
+ hostGroup: w.network.host_group,
349
+ ...w.network.override !== void 0 && { override: w.network.override },
350
+ effective: w.network.effective
351
+ }
288
352
  };
289
353
  }
290
354
  function fsEntryFromWire(w) {
@@ -739,6 +803,48 @@ var VMFileSystem = class {
739
803
  );
740
804
  }
741
805
  };
806
+ var CommittedVM = class {
807
+ constructor(transport, hostGroup, response) {
808
+ this.transport = transport;
809
+ this.hostname = response.hostname;
810
+ this.hostGroup = hostGroup;
811
+ this.commitId = response.commitId;
812
+ this.status = response.status;
813
+ if (response.parentStatus !== void 0) this.parentStatus = response.parentStatus;
814
+ this.mode = response.mode;
815
+ if (response.tags !== void 0) this.tags = response.tags;
816
+ if (response.labels !== void 0) this.labels = response.labels;
817
+ if (response.cacheKey !== void 0) this.cacheKey = response.cacheKey;
818
+ }
819
+ transport;
820
+ hostname;
821
+ hostGroup;
822
+ commitId;
823
+ status;
824
+ parentStatus;
825
+ mode;
826
+ tags;
827
+ labels;
828
+ cacheKey;
829
+ async fork(opts = {}) {
830
+ const res = await forkPath(
831
+ this.transport,
832
+ this.commitId,
833
+ opts
834
+ );
835
+ return new VM(this.transport, {
836
+ hostname: res.hostname,
837
+ hostGroup: this.hostGroup
838
+ });
839
+ }
840
+ async forkRaw(opts = {}) {
841
+ return forkPath(
842
+ this.transport,
843
+ this.commitId,
844
+ opts
845
+ );
846
+ }
847
+ };
742
848
  var VM = class {
743
849
  hostname;
744
850
  hostGroup;
@@ -778,6 +884,14 @@ var VM = class {
778
884
  async logs() {
779
885
  return this.transport.request("GET", `/vm/${encodeURIComponent(this.hostname)}/logs`);
780
886
  }
887
+ /** Return configuration, fork lineage, and effective network policy. */
888
+ async describe() {
889
+ const wire = await this.transport.request(
890
+ "GET",
891
+ `/vm/${encodeURIComponent(this.hostname)}`
892
+ );
893
+ return vmDescriptionFromWire(wire);
894
+ }
781
895
  async waitForAgent(opts = {}) {
782
896
  const timeoutMs = opts.timeoutMs ?? 12e4;
783
897
  const intervalMs = opts.intervalMs ?? 500;
@@ -804,10 +918,15 @@ var VM = class {
804
918
  while (Date.now() < deadline) {
805
919
  try {
806
920
  last = await this.health();
807
- if (last.userdataRan) return last;
808
921
  } catch (err) {
809
922
  lastErr = err;
810
923
  }
924
+ if (last?.userdataRan) {
925
+ if (last.userdataExitCode !== void 0 && last.userdataExitCode !== 0) {
926
+ throw new Error(`userdata failed with exit code ${last.userdataExitCode}`);
927
+ }
928
+ return last;
929
+ }
811
930
  await sleep(intervalMs);
812
931
  }
813
932
  throw new Error(
@@ -839,6 +958,25 @@ var VM = class {
839
958
  async restore() {
840
959
  await this.transport.request("POST", `/vm/${encodeURIComponent(this.hostname)}/restore`);
841
960
  }
961
+ /**
962
+ * Commit a stopped persistent VM disk into an immutable parent.
963
+ */
964
+ async commit(opts = {}) {
965
+ const cacheKey = opts.cacheKey?.trim();
966
+ const tags = opts.tags && opts.tags.length > 0 ? opts.tags : void 0;
967
+ const labels = opts.labels && Object.keys(opts.labels).length > 0 ? opts.labels : void 0;
968
+ const body = tags !== void 0 || labels !== void 0 || cacheKey ? {
969
+ ...tags !== void 0 && { tags },
970
+ ...labels !== void 0 && { labels },
971
+ ...cacheKey && { cache_key: cacheKey }
972
+ } : void 0;
973
+ const wire = await this.transport.request(
974
+ "POST",
975
+ `/vm/${encodeURIComponent(this.hostname)}/commit`,
976
+ body
977
+ );
978
+ return new CommittedVM(this.transport, this.hostGroup, vmCommitFromWire(wire));
979
+ }
842
980
  // --- port forwarding ---------------------------------------------------
843
981
  /**
844
982
  * Open one or more port forwards from the host to this VM. Each spec follows
@@ -958,6 +1096,32 @@ function sleep(ms) {
958
1096
  function errMsg(e) {
959
1097
  return e instanceof Error ? e.message : String(e);
960
1098
  }
1099
+ async function forkPath(transport, commitId, opts) {
1100
+ const id = validateCommitId(commitId);
1101
+ const q = new URLSearchParams();
1102
+ q.set("wait", "agent");
1103
+ if (opts.waitTimeoutSec !== void 0 && opts.waitTimeoutSec > 0) {
1104
+ q.set("timeout", `${opts.waitTimeoutSec}s`);
1105
+ }
1106
+ const query = q.toString() ? `?${q.toString()}` : "";
1107
+ const body = opts.network !== void 0 || (opts.tags?.length ?? 0) > 0 ? {
1108
+ ...opts.network !== void 0 && { network: opts.network },
1109
+ ...(opts.tags?.length ?? 0) > 0 && { tags: opts.tags }
1110
+ } : void 0;
1111
+ const wire = await transport.request(
1112
+ "POST",
1113
+ `/vm/commits/${encodeURIComponent(id)}/fork${query}`,
1114
+ body
1115
+ );
1116
+ return vmForkFromWire(wire);
1117
+ }
1118
+ function validateCommitId(commitId) {
1119
+ const value = commitId.trim();
1120
+ if (!value || value === "." || value === ".." || value.includes("..") || value.includes("/") || value.includes("\\") || value.includes("\0")) {
1121
+ throw new Error(`invalid commit ID ${JSON.stringify(value)}`);
1122
+ }
1123
+ return value;
1124
+ }
961
1125
  var VMBg = class {
962
1126
  constructor(transport, hostname) {
963
1127
  this.transport = transport;
@@ -1216,6 +1380,54 @@ var VMsAPI = class {
1216
1380
  return createVMResFromWire(wire);
1217
1381
  }
1218
1382
  };
1383
+ var CommitsAPI = class {
1384
+ constructor(transport) {
1385
+ this.transport = transport;
1386
+ }
1387
+ transport;
1388
+ async list(opts = {}) {
1389
+ const qs = new URLSearchParams();
1390
+ for (const tag of opts.tags ?? []) {
1391
+ const value = tag.trim();
1392
+ if (value) qs.append("tag", value);
1393
+ }
1394
+ const cacheKey = opts.cacheKey?.trim();
1395
+ const source = opts.source?.trim();
1396
+ const mode = opts.mode?.trim();
1397
+ if (cacheKey) qs.set("cache_key", cacheKey);
1398
+ if (source) qs.set("source", source);
1399
+ if (mode) qs.set("mode", mode);
1400
+ const encoded = qs.toString();
1401
+ const query = encoded ? `?${encoded}` : "";
1402
+ const wire = await this.transport.request("GET", `/vm/commits${query}`);
1403
+ return (wire ?? []).map(vmCommitInfoFromWire);
1404
+ }
1405
+ async delete(commitId) {
1406
+ const id = validateCommitId2(commitId);
1407
+ const wire = await this.transport.request(
1408
+ "DELETE",
1409
+ `/vm/commits/${encodeURIComponent(id)}`
1410
+ );
1411
+ return vmCommitDeleteFromWire(wire);
1412
+ }
1413
+ async fork(commitId, opts = {}) {
1414
+ const id = validateCommitId2(commitId);
1415
+ const qs = new URLSearchParams({ wait: "agent" });
1416
+ if (opts.waitTimeoutSec !== void 0 && opts.waitTimeoutSec > 0) {
1417
+ qs.set("timeout", `${opts.waitTimeoutSec}s`);
1418
+ }
1419
+ const body = opts.network !== void 0 || (opts.tags?.length ?? 0) > 0 ? {
1420
+ ...opts.network !== void 0 && { network: opts.network },
1421
+ ...(opts.tags?.length ?? 0) > 0 && { tags: opts.tags }
1422
+ } : void 0;
1423
+ const wire = await this.transport.request(
1424
+ "POST",
1425
+ `/vm/commits/${encodeURIComponent(id)}/fork?${qs.toString()}`,
1426
+ body
1427
+ );
1428
+ return vmForkFromWire(wire);
1429
+ }
1430
+ };
1219
1431
  var SecretsAPI = class {
1220
1432
  constructor(transport) {
1221
1433
  this.transport = transport;
@@ -1262,10 +1474,18 @@ function buildListQuery(opts) {
1262
1474
  const s = qs.toString();
1263
1475
  return s ? `?${s}` : "";
1264
1476
  }
1477
+ function validateCommitId2(commitId) {
1478
+ const value = commitId.trim();
1479
+ if (!value || value === "." || value === ".." || value.includes("..") || value.includes("/") || value.includes("\\") || value.includes("\0")) {
1480
+ throw new Error(`invalid commit ID ${JSON.stringify(value)}`);
1481
+ }
1482
+ return value;
1483
+ }
1265
1484
 
1266
1485
  // src/proxy.ts
1267
1486
  var ProxySecretBearer = "bearer";
1268
1487
  var ProxySecretBasic = "basic";
1488
+ var ProxySecretOAuthClientCredentials = "oauth-client-creds";
1269
1489
  function clientFromWire(w) {
1270
1490
  return { name: w.name, createdAt: w.created_at };
1271
1491
  }
@@ -1422,12 +1642,14 @@ var SlicerClient = class _SlicerClient {
1422
1642
  transport;
1423
1643
  hostGroups;
1424
1644
  vms;
1645
+ commits;
1425
1646
  secrets;
1426
1647
  proxy;
1427
1648
  constructor(opts) {
1428
1649
  this.transport = new TransportClient(opts);
1429
1650
  this.hostGroups = new HostGroupsAPI(this.transport);
1430
1651
  this.vms = new VMsAPI(this.transport);
1652
+ this.commits = new CommitsAPI(this.transport);
1431
1653
  this.secrets = new SecretsAPI(this.transport);
1432
1654
  this.proxy = new ProxyAPI(this.transport);
1433
1655
  }
@@ -1586,6 +1808,8 @@ var SlicerShellSession = class {
1586
1808
  }
1587
1809
  };
1588
1810
 
1811
+ exports.CommitsAPI = CommitsAPI;
1812
+ exports.CommittedVM = CommittedVM;
1589
1813
  exports.ExecStdioBase64 = ExecStdioBase64;
1590
1814
  exports.ExecStdioText = ExecStdioText;
1591
1815
  exports.FRAME_TYPE_DATA = FRAME_TYPE_DATA;
@@ -1603,6 +1827,7 @@ exports.ProxyAllowsAPI = ProxyAllowsAPI;
1603
1827
  exports.ProxyClientsAPI = ProxyClientsAPI;
1604
1828
  exports.ProxySecretBasic = ProxySecretBasic;
1605
1829
  exports.ProxySecretBearer = ProxySecretBearer;
1830
+ exports.ProxySecretOAuthClientCredentials = ProxySecretOAuthClientCredentials;
1606
1831
  exports.ProxySecretsAPI = ProxySecretsAPI;
1607
1832
  exports.SecretExistsError = SecretExistsError;
1608
1833
  exports.SecretsAPI = SecretsAPI;