@lizard-build/sdk 0.1.35 → 0.1.36

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.js CHANGED
@@ -20,16 +20,27 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AddonsAPI: () => AddonsAPI,
24
+ ApiKeysAPI: () => ApiKeysAPI,
23
25
  AuthenticationError: () => AuthenticationError,
26
+ BillingAPI: () => BillingAPI,
24
27
  CodeSandbox: () => CodeSandbox,
25
28
  ConflictError: () => ConflictError,
26
29
  DeployHandle: () => DeployHandle,
30
+ DomainsAPI: () => DomainsAPI,
27
31
  Lizard: () => Lizard,
28
32
  LizardError: () => LizardError,
33
+ MetricsAPI: () => MetricsAPI,
29
34
  NotFoundError: () => NotFoundError,
35
+ PlatformClient: () => PlatformClient,
36
+ ProjectsAPI: () => ProjectsAPI,
37
+ RegionsAPI: () => RegionsAPI,
30
38
  Sandbox: () => Sandbox,
39
+ SecretsAPI: () => SecretsAPI,
40
+ ServicesAPI: () => ServicesAPI,
31
41
  TimeoutError: () => TimeoutError,
32
42
  Volume: () => Volume,
43
+ WorkspacesAPI: () => WorkspacesAPI,
33
44
  resolveProjectId: () => resolveProjectId
34
45
  });
35
46
  module.exports = __toCommonJS(index_exports);
@@ -475,8 +486,10 @@ var SandboxClient = class {
475
486
  projectId,
476
487
  metadata: opts?.metadata,
477
488
  envs: opts?.envs,
489
+ region: opts?.region,
478
490
  volumeId: opts?.volumeId,
479
- volumeName: opts?.volumeName
491
+ volumeName: opts?.volumeName,
492
+ lizardToken: opts?.lizardToken
480
493
  })
481
494
  });
482
495
  if (!res.ok) await handleApiError(res);
@@ -567,8 +580,14 @@ var Sandbox = class extends SandboxClient {
567
580
  /**
568
581
  * Connect to an existing sandbox by its ID.
569
582
  *
570
- * If the sandbox is currently paused, it will be automatically resumed
571
- * before this call returns.
583
+ * Verifies the sandbox exists and is reachable, then returns a handle to it.
584
+ * Throws `NotFoundError` if it has been killed or has expired.
585
+ *
586
+ * This used to call `resume` first, on the assumption that a sandbox you are
587
+ * reconnecting to might be paused. Sandboxes are pods now and pause/resume is a
588
+ * `501` on every one of them, so that call turned every `connect()` into an
589
+ * error against a perfectly healthy sandbox. Connecting does not need to change
590
+ * a sandbox's state, so it no longer tries to.
572
591
  *
573
592
  * @example
574
593
  * ```ts
@@ -576,7 +595,7 @@ var Sandbox = class extends SandboxClient {
576
595
  * ```
577
596
  */
578
597
  static async connect(sandboxId, opts) {
579
- await SandboxClient.resumeSandbox(sandboxId, opts);
598
+ await SandboxClient.getSandboxInfo(sandboxId, opts);
580
599
  return new this({ sandboxId, ...opts });
581
600
  }
582
601
  /**
@@ -599,37 +618,23 @@ var Sandbox = class extends SandboxClient {
599
618
  return SandboxClient.killSandbox(this.sandboxId, this.resolveOpts(opts));
600
619
  }
601
620
  /**
602
- * Pause the sandbox microVM by freezing its vCPUs.
603
- *
604
- * Memory, filesystem, and running processes are held in the host's RAM —
605
- * nothing is written to disk, so a paused sandbox does not survive a host
606
- * failure. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
607
- *
608
- * Pausing does not buy you time past the original deadline. Resuming pushes the
609
- * expiry forward by however long the sandbox was paused — so a pause/resume cycle
610
- * costs no runtime — but that adjustment only happens on `resume()`. A sandbox left
611
- * paused past its original `expiresAt` is deleted there, within a minute, and
612
- * `resume()` then returns `false`.
613
- *
614
- * In other words: pause and come back *before* the original deadline and you lose
615
- * nothing; leave it paused across the deadline and it is gone. Pass `timeoutMs: 0`
616
- * at create time to opt out of expiry entirely, which is the only way to park a
617
- * sandbox indefinitely.
621
+ * Pause the sandbox by freezing it in place.
618
622
  *
619
- * @returns `true` if paused successfully.
623
+ * @deprecated Not implemented for the current runtime — always throws
624
+ * `LizardError` with HTTP 501. Sandboxes run as pods, and the equivalent is a CRIU
625
+ * checkpoint of the pod, which is not built. To park work across a gap, put it on a
626
+ * {@link Volume} and create a fresh sandbox on that volume later; the volume is the
627
+ * part that is meant to outlive a sandbox.
620
628
  */
621
629
  async pause(opts) {
622
630
  return SandboxClient.pauseSandbox(this.sandboxId, this.resolveOpts(opts));
623
631
  }
624
632
  /**
625
- * Resume a paused sandbox by unfreezing its vCPUs.
633
+ * Resume a paused sandbox.
626
634
  *
627
- * The expiry is pushed forward by the time spent paused, so the timeout measures
628
- * running time rather than wall-clock — provided the original deadline had not
629
- * already passed while paused. See {@link pause}.
630
- *
631
- * @returns `true` if resumed successfully, `false` if the sandbox no longer exists
632
- * (including because it expired while paused).
635
+ * @deprecated Not implemented for the current runtime — always throws
636
+ * `LizardError` with HTTP 501. See {@link pause}. `Sandbox.connect()` no longer
637
+ * calls this, so reconnecting to a running sandbox works without it.
633
638
  */
634
639
  async resume(opts) {
635
640
  return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
@@ -1085,13 +1090,233 @@ var MetricsAPI = class {
1085
1090
  }
1086
1091
  };
1087
1092
 
1093
+ // src/platform/workspaces.ts
1094
+ var WorkspacesAPI = class {
1095
+ constructor(client) {
1096
+ this.client = client;
1097
+ }
1098
+ /**
1099
+ * List workspaces the calling credential can see.
1100
+ *
1101
+ * A scoped API key sees only what it is scoped to: a workspace-scoped key returns
1102
+ * that one workspace, and a project-scoped key returns the workspace containing its
1103
+ * project. A full key returns every workspace the account belongs to.
1104
+ */
1105
+ list() {
1106
+ return this.client.get("/api/workspaces");
1107
+ }
1108
+ /** Create a workspace. The caller becomes its owner. */
1109
+ create(opts) {
1110
+ return this.client.post("/api/workspaces", opts);
1111
+ }
1112
+ /**
1113
+ * Delete a workspace.
1114
+ *
1115
+ * Empty-only by default — the server refuses while any project or sandbox remains,
1116
+ * so this cannot quietly destroy a user's work. Pass `{ force: true }` to delete a
1117
+ * workspace and everything in it; that is irreversible.
1118
+ */
1119
+ delete(id, opts) {
1120
+ const qs = opts?.force ? "" : "?requireEmpty=true";
1121
+ return this.client.delete(`/api/workspaces/${id}${qs}`);
1122
+ }
1123
+ /**
1124
+ * Find a workspace by name, slug, or id, or `null` if there is no such workspace.
1125
+ * Matching is exact; id wins, then slug, then name.
1126
+ */
1127
+ async find(nameOrSlugOrId) {
1128
+ const all = await this.list();
1129
+ return all.find((w) => w.id === nameOrSlugOrId) ?? all.find((w) => w.slug === nameOrSlugOrId) ?? all.find((w) => w.name === nameOrSlugOrId) ?? null;
1130
+ }
1131
+ };
1132
+
1133
+ // src/platform/api-keys.ts
1134
+ var ApiKeysAPI = class {
1135
+ constructor(client) {
1136
+ this.client = client;
1137
+ }
1138
+ /** List the account's API keys. Previews only — full keys are never returned here. */
1139
+ list() {
1140
+ return this.client.get("/api/account/api-keys");
1141
+ }
1142
+ /**
1143
+ * Create an API key.
1144
+ *
1145
+ * The response is the only place the full key appears. Omitting every scope creates
1146
+ * a full-access key; the server rejects an attempt to create one from a key that is
1147
+ * itself scoped.
1148
+ */
1149
+ create(opts) {
1150
+ const scopes = [
1151
+ ...opts.scopes ?? [],
1152
+ ...(opts.workspaces ?? []).map((id) => ({ type: "workspace", id })),
1153
+ ...(opts.projects ?? []).map((id) => ({ type: "project", id }))
1154
+ ];
1155
+ return this.client.post("/api/account/api-keys", { name: opts.name, scopes });
1156
+ }
1157
+ /** Revoke a key by id. It stops working immediately, everywhere. */
1158
+ delete(id) {
1159
+ return this.client.delete(`/api/account/api-keys/${id}`);
1160
+ }
1161
+ };
1162
+
1163
+ // src/platform/regions.ts
1164
+ var RegionsAPI = class {
1165
+ constructor(client) {
1166
+ this.client = client;
1167
+ }
1168
+ /** List every region, including ones not currently accepting workloads. */
1169
+ list() {
1170
+ return this.client.get("/api/regions");
1171
+ }
1172
+ };
1173
+
1174
+ // src/platform/billing.ts
1175
+ var BillingAPI = class {
1176
+ constructor(client) {
1177
+ this.client = client;
1178
+ }
1179
+ /** Current balance, status, burn rate, and runway. */
1180
+ balance() {
1181
+ return this.client.get("/api/billing/balance");
1182
+ }
1183
+ /** A page of balance transactions, newest first. */
1184
+ transactions(opts) {
1185
+ const qs = new URLSearchParams();
1186
+ if (opts?.limit !== void 0) qs.set("limit", String(opts.limit));
1187
+ if (opts?.cursor) qs.set("cursor", opts.cursor);
1188
+ if (opts?.includeUsage) qs.set("includeUsage", "1");
1189
+ const q = qs.toString();
1190
+ return this.client.get(`/api/billing/transactions${q ? `?${q}` : ""}`);
1191
+ }
1192
+ /** Cost summary for the current billing period, broken down by resource. */
1193
+ summary() {
1194
+ return this.client.get("/api/billing/summary");
1195
+ }
1196
+ /** Live (not-yet-invoiced) usage accumulating right now. */
1197
+ live() {
1198
+ return this.client.get("/api/billing/live");
1199
+ }
1200
+ };
1201
+
1202
+ // src/volume/index.ts
1203
+ var Volume = class _Volume {
1204
+ constructor(opts) {
1205
+ this.volumeId = opts.volumeId;
1206
+ this.name = opts.name;
1207
+ this.config = new ConnectionConfig(opts);
1208
+ }
1209
+ /**
1210
+ * Create a volume. Throws `ConflictError` (409) if the project already has one with
1211
+ * this name — use {@link getOrCreate} when you want "make sure this exists" instead.
1212
+ */
1213
+ static async create(projectId, name, opts) {
1214
+ const config = new ConnectionConfig(opts);
1215
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
1216
+ method: "POST",
1217
+ headers: config.headers,
1218
+ body: JSON.stringify({ name, sizeGb: opts?.sizeGb ?? 5, region: opts?.region })
1219
+ });
1220
+ if (!res.ok) await handleApiError(res);
1221
+ const vol = await res.json();
1222
+ return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1223
+ }
1224
+ /**
1225
+ * Return the project's volume with this name, creating it first if it does not
1226
+ * exist yet. This is the reason a volume's name is its key: an agent that wants
1227
+ * "the scratch disk for this task" no longer has to store an id between runs.
1228
+ *
1229
+ * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1230
+ * and never resizes one that is already there.
1231
+ */
1232
+ static async getOrCreate(projectId, name, opts) {
1233
+ const config = new ConnectionConfig(opts);
1234
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
1235
+ method: "POST",
1236
+ headers: config.headers,
1237
+ body: JSON.stringify({ name, sizeGb: opts?.sizeGb ?? 5, region: opts?.region, getOrCreate: true })
1238
+ });
1239
+ if (!res.ok) await handleApiError(res);
1240
+ const vol = await res.json();
1241
+ return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1242
+ }
1243
+ /** Look a volume up by name or by id. */
1244
+ static async get(projectId, nameOrId, opts) {
1245
+ const config = new ConnectionConfig(opts);
1246
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1247
+ headers: config.headers
1248
+ });
1249
+ if (!res.ok) await handleApiError(res);
1250
+ const vol = await res.json();
1251
+ return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1252
+ }
1253
+ static async list(projectId, opts) {
1254
+ const config = new ConnectionConfig(opts);
1255
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
1256
+ headers: config.headers
1257
+ });
1258
+ if (!res.ok) await handleApiError(res);
1259
+ return res.json();
1260
+ }
1261
+ /** Delete a volume by name or by id, without constructing one first. */
1262
+ static async delete(projectId, nameOrId, opts) {
1263
+ const config = new ConnectionConfig(opts);
1264
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1265
+ method: "DELETE",
1266
+ headers: config.headers
1267
+ });
1268
+ if (!res.ok) await handleApiError(res);
1269
+ }
1270
+ async getInfo(projectId) {
1271
+ const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1272
+ headers: this.config.headers
1273
+ });
1274
+ if (!res.ok) await handleApiError(res);
1275
+ return res.json();
1276
+ }
1277
+ async delete(projectId) {
1278
+ const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1279
+ method: "DELETE",
1280
+ headers: this.config.headers
1281
+ });
1282
+ if (!res.ok) await handleApiError(res);
1283
+ }
1284
+ };
1285
+
1088
1286
  // src/lizard.ts
1089
1287
  var Lizard = class {
1090
1288
  constructor(opts) {
1289
+ // ── Volumes, bound to this client's project ───────────────────────────────
1290
+ /**
1291
+ * Persistent volumes in this client's project.
1292
+ *
1293
+ * The same calls as the static {@link Volume} methods, minus the `projectId`
1294
+ * argument — the client already knows it.
1295
+ *
1296
+ * @requires `project` to be set in the constructor.
1297
+ *
1298
+ * @example
1299
+ * ```ts
1300
+ * const lizard = new Lizard({ project: 'my-project' })
1301
+ * const vol = await lizard.volumes.getOrCreate('scratch', { sizeGb: 10 })
1302
+ * const sb = await lizard.create('codex', { volumeName: 'scratch' })
1303
+ * ```
1304
+ */
1305
+ this.volumes = {
1306
+ create: async (name, opts) => Volume.create(await this.projectId(), name, { ...await this.connectionOpts(), ...opts }),
1307
+ getOrCreate: async (name, opts) => Volume.getOrCreate(await this.projectId(), name, { ...await this.connectionOpts(), ...opts }),
1308
+ get: async (nameOrId, opts) => Volume.get(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts }),
1309
+ list: async (opts) => Volume.list(await this.projectId(), { ...await this.connectionOpts(), ...opts }),
1310
+ delete: async (nameOrId, opts) => Volume.delete(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts })
1311
+ };
1091
1312
  this.config = new ConnectionConfig(opts);
1092
1313
  this.projectRef = opts.project;
1093
1314
  const platform = new PlatformClient(opts);
1094
1315
  this._platform = platform;
1316
+ this.workspaces = new WorkspacesAPI(platform);
1317
+ this.apiKeys = new ApiKeysAPI(platform);
1318
+ this.regions = new RegionsAPI(platform);
1319
+ this.billing = new BillingAPI(platform);
1095
1320
  this.projects = new ProjectsAPI(platform);
1096
1321
  this.services = new ServicesAPI(platform);
1097
1322
  this.addons = new AddonsAPI(platform);
@@ -1117,7 +1342,7 @@ var Lizard = class {
1117
1342
  const sandboxOpts = { ...opts, ...await this.connectionOpts(), projectId };
1118
1343
  return template ? Sandbox.create(template, sandboxOpts) : Sandbox.create(sandboxOpts);
1119
1344
  }
1120
- /** Connect to an existing sandbox by ID (resumes it if paused). */
1345
+ /** Connect to an existing sandbox by ID. Throws `NotFoundError` if it is gone. */
1121
1346
  async connect(sandboxId, opts) {
1122
1347
  return Sandbox.connect(sandboxId, await this.connectionOpts(opts));
1123
1348
  }
@@ -1125,6 +1350,20 @@ var Lizard = class {
1125
1350
  async list(opts) {
1126
1351
  return Sandbox.list(await this.connectionOpts(opts));
1127
1352
  }
1353
+ // ── Account ───────────────────────────────────────────────────────────────
1354
+ /**
1355
+ * The account this credential belongs to — the SDK's `lizard whoami`.
1356
+ *
1357
+ * Works for a scoped key as well as a full one: the identity is the account that
1358
+ * created the key, which is what the key's usage bills to.
1359
+ */
1360
+ async whoami() {
1361
+ return this.platform.get("/api/auth/me");
1362
+ }
1363
+ /** The underlying HTTP client, for endpoints this SDK does not wrap yet. */
1364
+ get platform() {
1365
+ return this._platform;
1366
+ }
1128
1367
  };
1129
1368
 
1130
1369
  // src/code-interpreter/types.ts
@@ -1316,101 +1555,28 @@ async function* readLines(body) {
1316
1555
  reader.releaseLock();
1317
1556
  }
1318
1557
  }
1319
-
1320
- // src/volume/index.ts
1321
- var Volume = class _Volume {
1322
- constructor(opts) {
1323
- this.volumeId = opts.volumeId;
1324
- this.name = opts.name;
1325
- this.config = new ConnectionConfig(opts);
1326
- }
1327
- /**
1328
- * Create a volume. Throws `ConflictError` (409) if the project already has one with
1329
- * this name — use {@link getOrCreate} when you want "make sure this exists" instead.
1330
- */
1331
- static async create(projectId, name, opts) {
1332
- const config = new ConnectionConfig(opts);
1333
- const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
1334
- method: "POST",
1335
- headers: config.headers,
1336
- body: JSON.stringify({ name, sizeGb: opts?.sizeGb ?? 5 })
1337
- });
1338
- if (!res.ok) await handleApiError(res);
1339
- const vol = await res.json();
1340
- return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1341
- }
1342
- /**
1343
- * Return the project's volume with this name, creating it first if it does not
1344
- * exist yet. This is the reason a volume's name is its key: an agent that wants
1345
- * "the scratch disk for this task" no longer has to store an id between runs.
1346
- *
1347
- * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1348
- * and never resizes one that is already there.
1349
- */
1350
- static async getOrCreate(projectId, name, opts) {
1351
- const config = new ConnectionConfig(opts);
1352
- const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
1353
- method: "POST",
1354
- headers: config.headers,
1355
- body: JSON.stringify({ name, sizeGb: opts?.sizeGb ?? 5, getOrCreate: true })
1356
- });
1357
- if (!res.ok) await handleApiError(res);
1358
- const vol = await res.json();
1359
- return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1360
- }
1361
- /** Look a volume up by name or by id. */
1362
- static async get(projectId, nameOrId, opts) {
1363
- const config = new ConnectionConfig(opts);
1364
- const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1365
- headers: config.headers
1366
- });
1367
- if (!res.ok) await handleApiError(res);
1368
- const vol = await res.json();
1369
- return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
1370
- }
1371
- static async list(projectId, opts) {
1372
- const config = new ConnectionConfig(opts);
1373
- const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
1374
- headers: config.headers
1375
- });
1376
- if (!res.ok) await handleApiError(res);
1377
- return res.json();
1378
- }
1379
- /** Delete a volume by name or by id, without constructing one first. */
1380
- static async delete(projectId, nameOrId, opts) {
1381
- const config = new ConnectionConfig(opts);
1382
- const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1383
- method: "DELETE",
1384
- headers: config.headers
1385
- });
1386
- if (!res.ok) await handleApiError(res);
1387
- }
1388
- async getInfo(projectId) {
1389
- const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1390
- headers: this.config.headers
1391
- });
1392
- if (!res.ok) await handleApiError(res);
1393
- return res.json();
1394
- }
1395
- async delete(projectId) {
1396
- const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1397
- method: "DELETE",
1398
- headers: this.config.headers
1399
- });
1400
- if (!res.ok) await handleApiError(res);
1401
- }
1402
- };
1403
1558
  // Annotate the CommonJS export names for ESM import in node:
1404
1559
  0 && (module.exports = {
1560
+ AddonsAPI,
1561
+ ApiKeysAPI,
1405
1562
  AuthenticationError,
1563
+ BillingAPI,
1406
1564
  CodeSandbox,
1407
1565
  ConflictError,
1408
1566
  DeployHandle,
1567
+ DomainsAPI,
1409
1568
  Lizard,
1410
1569
  LizardError,
1570
+ MetricsAPI,
1411
1571
  NotFoundError,
1572
+ PlatformClient,
1573
+ ProjectsAPI,
1574
+ RegionsAPI,
1412
1575
  Sandbox,
1576
+ SecretsAPI,
1577
+ ServicesAPI,
1413
1578
  TimeoutError,
1414
1579
  Volume,
1580
+ WorkspacesAPI,
1415
1581
  resolveProjectId
1416
1582
  });