@promigence/sdk 0.1.0-beta.14 → 0.1.0-beta.16

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PromigenceClient = exports.LONG_POLL_MS = exports.isKeyedCreate = exports.REPO_CREDENTIALS_FIX = exports.CONTEXT_GIT_RE = exports.IMAGE_DIGEST_RE = exports.MAX_SOURCES = exports.NOTICE_HEADER = exports.isWaiting = exports.FILE_TIMEOUT_MS = void 0;
3
+ exports.PromigenceClient = exports.RUN_SETTLE_MS = exports.LONG_POLL_MS = exports.isKeyedCreate = exports.REPO_CREDENTIALS_FIX = exports.CONTEXT_GIT_RE = exports.IMAGE_DIGEST_RE = exports.NO_KEY_FIX = exports.NO_KEY_MESSAGE = exports.MAX_SOURCES = exports.NOTICE_HEADER = exports.OUTPUT_RETENTION_MAX_DAYS = exports.isWaiting = exports.FILE_TIMEOUT_MS = void 0;
4
4
  exports.execOutputBytes = execOutputBytes;
5
5
  exports.configDir = configDir;
6
6
  exports.credentialsPath = credentialsPath;
@@ -10,9 +10,6 @@ exports.storedCredentials = storedCredentials;
10
10
  exports.storedCredentialsText = storedCredentialsText;
11
11
  exports.readCredentialsWithStore = readCredentialsWithStore;
12
12
  exports.readCredentials = readCredentials;
13
- exports.apiUrlFromCompatUrl = apiUrlFromCompatUrl;
14
- exports.apiUrlFromCompatDomain = apiUrlFromCompatDomain;
15
- exports.compatApiKey = compatApiKey;
16
13
  exports.keyOrigin = keyOrigin;
17
14
  exports.imageRefProblem = imageRefProblem;
18
15
  exports.checkImageRef = checkImageRef;
@@ -27,6 +24,7 @@ exports.errorFor = errorFor;
27
24
  exports.checkSandboxId = checkSandboxId;
28
25
  exports.checkSpendCap = checkSpendCap;
29
26
  exports.newIdempotencyKey = newIdempotencyKey;
27
+ exports.runWorkDone = runWorkDone;
30
28
  exports.serverAppMs = serverAppMs;
31
29
  exports.toCommandResult = toCommandResult;
32
30
  const node_fs_1 = require("node:fs");
@@ -75,6 +73,26 @@ function bytesToB64(b) {
75
73
  function modeParam(mode) {
76
74
  return typeof mode === "number" ? mode.toString(8) : mode;
77
75
  }
76
+ function jsonBody(value) {
77
+ return { data: new TextEncoder().encode(JSON.stringify(value)), contentType: "application/json" };
78
+ }
79
+ function fileForm(fields, data) {
80
+ const boundary = `promigence-${globalThis.crypto.randomUUID().replace(/-/g, "")}`;
81
+ let head = "";
82
+ for (const [name, value] of fields) {
83
+ head += `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`;
84
+ }
85
+ head += `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="blob"\r\n`
86
+ + "Content-Type: application/octet-stream\r\n\r\n";
87
+ const enc = new TextEncoder();
88
+ const pre = enc.encode(head);
89
+ const post = enc.encode(`\r\n--${boundary}--\r\n`);
90
+ const out = new Uint8Array(pre.length + data.length + post.length);
91
+ out.set(pre, 0);
92
+ out.set(data, pre.length);
93
+ out.set(post, pre.length + data.length);
94
+ return { data: out, contentType: `multipart/form-data; boundary=${boundary}` };
95
+ }
78
96
  function execOutputBytes(r, stream = "stdout") {
79
97
  const b64 = r[`${stream}_b64`];
80
98
  if (typeof b64 === "string")
@@ -104,6 +122,32 @@ function pageQuery(limit, max, cursor) {
104
122
  q.set("cursor", cursor);
105
123
  return q.toString();
106
124
  }
125
+ exports.OUTPUT_RETENTION_MAX_DAYS = 30;
126
+ function projectUpdateBody(u) {
127
+ const body = {};
128
+ if (u.name !== undefined)
129
+ body["name"] = u.name;
130
+ if (u.outputRetentionDays !== undefined) {
131
+ const d = u.outputRetentionDays;
132
+ if (d !== null && (!Number.isInteger(d) || d < 1 || d > exports.OUTPUT_RETENTION_MAX_DAYS)) {
133
+ throw new core_js_1.InvalidArgumentError(`outputRetentionDays must be a whole number from 1 to ${exports.OUTPUT_RETENTION_MAX_DAYS}, or null, not ${String(d)}`, "Pass null for the organization's setting.");
134
+ }
135
+ body["output_retention_days"] = d;
136
+ }
137
+ if (u.failureCaptures !== undefined) {
138
+ if (u.failureCaptures !== null && typeof u.failureCaptures !== "boolean") {
139
+ throw new core_js_1.InvalidArgumentError("failureCaptures must be true, false or null", "Pass null for the organization's setting.");
140
+ }
141
+ if (Object.keys(body).length) {
142
+ throw new core_js_1.InvalidArgumentError("failureCaptures is set on its own", "Change it in a call of its own, without name or outputRetentionDays.");
143
+ }
144
+ body["failure_captures"] = u.failureCaptures;
145
+ }
146
+ if (!Object.keys(body).length) {
147
+ throw new core_js_1.InvalidArgumentError("projects.update needs something to change", "Pass name, outputRetentionDays or failureCaptures.");
148
+ }
149
+ return body;
150
+ }
107
151
  function expiryFields(o) {
108
152
  const body = {};
109
153
  if (o.expiresInDays !== undefined)
@@ -263,30 +307,6 @@ function readCredentials(env = process.env) {
263
307
  function storedName(store, env) {
264
308
  return store === undefined || store === "file" ? `the credentials file (${credentialsPath(env)})` : (0, credstore_js_1.storeLabel)(store);
265
309
  }
266
- const COMPAT_REFUSED_HOSTS = /(^|\.)e2b(-staging)?\.(app|dev|pro)$/i;
267
- function apiUrlFromCompatUrl(url) {
268
- const u = (url ?? "").trim();
269
- if (!u)
270
- return undefined;
271
- let host = "";
272
- try {
273
- host = new URL(u.includes("://") ? u : `https://${u}`).hostname;
274
- }
275
- catch {
276
- return undefined;
277
- }
278
- return COMPAT_REFUSED_HOSTS.test(host) ? undefined : u;
279
- }
280
- function apiUrlFromCompatDomain(domain) {
281
- const d = (domain ?? "").trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
282
- if (!d || COMPAT_REFUSED_HOSTS.test(d))
283
- return undefined;
284
- return `https://api.${d}/v1`;
285
- }
286
- function compatApiKey(key) {
287
- const k = key?.trim();
288
- return k && !k.startsWith("e2b_") ? k : undefined;
289
- }
290
310
  function keyOrigin(env = process.env) {
291
311
  if (env["PROMIGENCE_API_KEY"]?.trim())
292
312
  return "PROMIGENCE_API_KEY";
@@ -313,12 +333,13 @@ function resolveCredentials(opts, env = process.env) {
313
333
  const inputs = optKey === undefined && envKey === undefined
314
334
  ? undefined
315
335
  : `${optKey ?? "\0"}\n${envKey ?? "\0"}\n${envUrl ?? "\0"}\n${opts.apiUrl ?? "\0"}`;
316
- if (inputs !== undefined && inputs === credsLastInputs && credsLast !== undefined)
317
- return { apiKey: credsLast.apiKey, apiUrl: credsLast.apiUrl };
336
+ if (inputs !== undefined && inputs === credsLastInputs && credsLast !== undefined) {
337
+ return { apiKey: credsLast.apiKey, apiUrl: credsLast.apiUrl, ...(credsLast.origin ? { origin: credsLast.origin } : {}) };
338
+ }
318
339
  const worked = resolveCredentialsFrom(opts, env, set, trim, optKey, envKey, envUrl);
319
340
  if (inputs !== undefined && worked.refusal === undefined) {
320
341
  credsLastInputs = inputs;
321
- credsLast = { apiKey: worked.apiKey, apiUrl: worked.apiUrl };
342
+ credsLast = { apiKey: worked.apiKey, apiUrl: worked.apiUrl, ...(worked.origin ? { origin: worked.origin } : {}) };
322
343
  }
323
344
  return worked;
324
345
  }
@@ -352,8 +373,15 @@ function resolveCredentialsFrom(opts, env, set, trim, optKey, envKey, envUrl) {
352
373
  if (refusal)
353
374
  return { apiKey: undefined, apiUrl, refusal };
354
375
  }
355
- return { apiKey: key, apiUrl };
376
+ const origin = !key ? undefined
377
+ : source === "opts" ? { source: "option" }
378
+ : source === "env" ? { source: "env" }
379
+ : source === "file" ? { source: "stored", ...(store === undefined ? {} : { store }) }
380
+ : { source: "fallback" };
381
+ return { apiKey: key, apiUrl, ...(origin ? { origin } : {}) };
356
382
  }
383
+ exports.NO_KEY_MESSAGE = "no API key: none was given, PROMIGENCE_API_KEY is not set, and none is stored";
384
+ exports.NO_KEY_FIX = "Pass { apiKey } or set PROMIGENCE_API_KEY. No key yet: `promigence login` signs you up or in and stores one.";
357
385
  function keyRefusal(key, from) {
358
386
  if (/^[\x21-\x7E]+$/.test(key))
359
387
  return undefined;
@@ -633,6 +661,8 @@ function snapshotCreateBody(opts) {
633
661
  }
634
662
  if (o.alias)
635
663
  body["alias"] = o.alias;
664
+ if (o.alias && o.moveAlias)
665
+ body["move_alias"] = true;
636
666
  if (o.grade) {
637
667
  if (!o.grade.cmd)
638
668
  throw new core_js_1.InvalidArgumentError("snapshots.create grade needs a cmd", "Pass { grade: { cmd: 'pytest -q tests/' } }.");
@@ -667,6 +697,9 @@ const REFUSED_503_CODES = new Set([
667
697
  "builder_unavailable", "builder_busy", "overloaded", "tier_unavailable", "registry_unavailable",
668
698
  "secrets_unavailable",
669
699
  ]);
700
+ const SDK_FIXES = {
701
+ cap_exceeded: "Raise `spendCap`, lower `timeoutMs` or `count`, or pass `allowPartial` to launch what fits.",
702
+ };
670
703
  function withoutCliHelp(fix) {
671
704
  const out = fix.replace(/\s*See `promigence [^`]*--help`\.?\s*$/, "").trim();
672
705
  if (out === fix.trim())
@@ -676,7 +709,7 @@ function withoutCliHelp(fix) {
676
709
  function errorFor(code, message, fix, extra = {}) {
677
710
  const attach = (e) => {
678
711
  const w = e;
679
- w.docs = extra.docs ?? `${core_js_1.ERRORS_DOCS_URL}#${code}`;
712
+ w.docs = extra.docs !== undefined && e.code === code ? extra.docs : (0, core_js_1.errorsDocsFor)(e.code);
680
713
  if (extra.status !== undefined)
681
714
  w.status = extra.status;
682
715
  if (extra.requestId !== undefined)
@@ -692,6 +725,8 @@ function errorFor(code, message, fix, extra = {}) {
692
725
  if ((code === "usage" || code === "invalid_argument" || code === "unsupported") && (0, core_js_1.previewsOff)(message)) {
693
726
  return attach(new core_js_1.PreviewsUnavailableError());
694
727
  }
728
+ if (!extra.cli && Object.prototype.hasOwnProperty.call(SDK_FIXES, code))
729
+ fix = SDK_FIXES[code];
695
730
  switch (code) {
696
731
  case "auth": return attach(new core_js_1.AuthError(message, fix));
697
732
  case "not_found": return attach(new core_js_1.NotFoundError(message, fix));
@@ -705,8 +740,13 @@ function errorFor(code, message, fix, extra = {}) {
705
740
  case "not_verified": return attach(new core_js_1.NotVerifiedError(message, fix));
706
741
  case "timeout": return attach(new core_js_1.TimeoutError(message, fix));
707
742
  case "usage":
708
- case "invalid_argument":
743
+ case "invalid_argument": {
744
+ const ended = code === "usage" ? (0, core_js_1.sandboxEndedIn)(message) : undefined;
745
+ if (ended !== undefined) {
746
+ return attach(new core_js_1.SandboxEndedError(message, ended.sandboxId, ended.state, extra.cli ? fix : core_js_1.SANDBOX_ENDED_FIX));
747
+ }
709
748
  return attach(new core_js_1.InvalidArgumentError(message, extra.cli ? fix : withoutCliHelp(fix), code));
749
+ }
710
750
  case "not_released": return attach(new core_js_1.NotReleasedError(message));
711
751
  case "service_restart": return attach(new core_js_1.ServiceRestartError(message, fix));
712
752
  default:
@@ -776,6 +816,21 @@ const WAIT_MS_CAP = 30_000;
776
816
  exports.LONG_POLL_MS = 5_000;
777
817
  const BUILD_POLL_MS = 10_000;
778
818
  const PROGRESS_POLL_MS = 200;
819
+ exports.RUN_SETTLE_MS = 5_000;
820
+ function runWorkDone(run, endedSince, now = Date.now()) {
821
+ if (run.state === "completed" || run.state === "cap_killed" || run.state === "aborted")
822
+ return true;
823
+ const sbs = Array.isArray(run.sandboxes) ? run.sandboxes : [];
824
+ const allDone = sbs.length > 0 && sbs.every((s) => s.state === "ended" || s.command_outcome != null);
825
+ if (!allDone) {
826
+ delete endedSince.at;
827
+ return false;
828
+ }
829
+ if (sbs.some((s) => s.state !== "ended"))
830
+ return true;
831
+ endedSince.at ??= now;
832
+ return now - endedSince.at >= exports.RUN_SETTLE_MS;
833
+ }
779
834
  const WAITED_HEADER = "promigence-waited-ms";
780
835
  const seg = (s) => encodeURIComponent(s).replace(/%40/g, "@");
781
836
  function waitQuery(waitMs) {
@@ -813,6 +868,9 @@ class PromigenceClient {
813
868
  apiUrl;
814
869
  #apiKey;
815
870
  #keyRefusal;
871
+ #keyOrigin;
872
+ #onKeyUse;
873
+ #keyUseSaid = false;
816
874
  #bearer;
817
875
  fetchImpl;
818
876
  ownFetch;
@@ -834,6 +892,8 @@ class PromigenceClient {
834
892
  const resolved = resolveCredentials(opts);
835
893
  this.#apiKey = resolved.apiKey;
836
894
  this.#keyRefusal = resolved.refusal;
895
+ this.#keyOrigin = resolved.origin;
896
+ this.#onKeyUse = opts.onKeyUse;
837
897
  this.apiUrl = resolved.apiUrl;
838
898
  const env = opts.fetch === undefined ? (0, proxy_js_1.transportEnv)() : undefined;
839
899
  const proxy = env !== undefined ? (0, proxy_js_1.proxyFor)(this.apiUrl, env) : undefined;
@@ -899,11 +959,18 @@ class PromigenceClient {
899
959
  catch {
900
960
  }
901
961
  }
962
+ get keyOrigin() {
963
+ return this.#keyOrigin;
964
+ }
902
965
  requireKey() {
903
966
  if (this.#keyRefusal)
904
967
  throw this.#keyRefusal;
905
968
  if (!this.#apiKey)
906
- throw new core_js_1.AuthError();
969
+ throw this.cliErrors ? new core_js_1.AuthError(exports.NO_KEY_MESSAGE) : new core_js_1.AuthError(exports.NO_KEY_MESSAGE, exports.NO_KEY_FIX);
970
+ if (!this.#keyUseSaid && this.#onKeyUse !== undefined && this.#keyOrigin !== undefined) {
971
+ this.#keyUseSaid = true;
972
+ this.#onKeyUse(this.#keyOrigin);
973
+ }
907
974
  return this.#apiKey;
908
975
  }
909
976
  bearer() {
@@ -976,7 +1043,8 @@ class PromigenceClient {
976
1043
  const idempotencyKey = opts.idempotencyKey ?? (keyedCreate ? newIdempotencyKey() : undefined);
977
1044
  if (idempotencyKey)
978
1045
  headers["idempotency-key"] = idempotencyKey;
979
- const replayable = opts.method === "GET" || opts.method === "DELETE" || (keyedCreate && Boolean(idempotencyKey));
1046
+ const replayable = opts.method === "GET" || opts.method === "DELETE" || opts.replayable === true
1047
+ || (keyedCreate && Boolean(idempotencyKey));
980
1048
  const timeoutMs = opts.timeoutMs ?? this.requestTimeoutMs;
981
1049
  let dispatcher;
982
1050
  let resentDelete = false;
@@ -1173,7 +1241,7 @@ class PromigenceClient {
1173
1241
  }
1174
1242
  }
1175
1243
  }
1176
- async bytes(method, path, body, opts = {}) {
1244
+ async bytes(method, path, body, opts = {}, read = method === "GET") {
1177
1245
  const url = `${this.apiUrl}${path}`;
1178
1246
  const headers = {
1179
1247
  accept: "application/octet-stream, application/json",
@@ -1183,7 +1251,7 @@ class PromigenceClient {
1183
1251
  if (this.caller)
1184
1252
  headers[caller_js_1.CALLER_HEADER] = this.caller;
1185
1253
  if (body !== undefined)
1186
- headers["content-type"] = "application/octet-stream";
1254
+ headers["content-type"] = body.contentType;
1187
1255
  const signals = [AbortSignal.timeout(opts.timeoutMs ?? exports.FILE_TIMEOUT_MS)];
1188
1256
  if (opts.signal)
1189
1257
  signals.push(opts.signal);
@@ -1197,7 +1265,7 @@ class PromigenceClient {
1197
1265
  method,
1198
1266
  headers,
1199
1267
  signal,
1200
- ...(body === undefined ? {} : { body: body }),
1268
+ ...(body === undefined ? {} : { body: body.data }),
1201
1269
  ...(dispatcher === undefined ? {} : { dispatcher }),
1202
1270
  });
1203
1271
  }
@@ -1217,21 +1285,23 @@ class PromigenceClient {
1217
1285
  }
1218
1286
  catch { }
1219
1287
  const retryAfterMs = parseRetryAfter(res.headers.get("retry-after"));
1220
- const wait = this.refusalBackoff(res.status, retryAfterMs, payload.error?.code, attempt, method === "GET", opts.signal);
1288
+ const wait = this.refusalBackoff(res.status, retryAfterMs, payload.error?.code, attempt, read, opts.signal);
1221
1289
  if (wait !== undefined) {
1222
1290
  this.stats.retries++;
1223
1291
  await sleep(wait);
1224
1292
  continue;
1225
1293
  }
1226
1294
  const code = payload.error?.code ?? codeForStatus(res.status);
1295
+ const rid = headerOf(res, "request-id") ?? headerOf(res, "x-request-id");
1227
1296
  throw errorFor(code, payload.error?.message ?? `HTTP ${res.status} from ${method} ${path}`, payload.error?.fix ?? "See the API response for details.", {
1228
1297
  status: res.status,
1229
1298
  ...bodyExtra(payload),
1230
1299
  ...(retryAfterMs === undefined ? {} : { retryAfterMs }),
1300
+ ...(rid ? { requestId: rid } : {}),
1231
1301
  });
1232
1302
  }
1233
1303
  }
1234
- async stream(path, onChunk, opts = {}) {
1304
+ async stream(method, path, onChunk, opts = {}, body) {
1235
1305
  const url = `${this.apiUrl}${path}`;
1236
1306
  const headers = {
1237
1307
  accept: "application/octet-stream, application/json",
@@ -1240,17 +1310,25 @@ class PromigenceClient {
1240
1310
  };
1241
1311
  if (this.caller)
1242
1312
  headers[caller_js_1.CALLER_HEADER] = this.caller;
1313
+ if (body !== undefined)
1314
+ headers["content-type"] = body.contentType;
1243
1315
  const signals = [AbortSignal.timeout(opts.timeoutMs ?? exports.FILE_TIMEOUT_MS)];
1244
1316
  if (opts.signal)
1245
1317
  signals.push(opts.signal);
1246
1318
  const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
1247
- const timedOut = () => new core_js_1.TimeoutError(`GET ${path} timed out after ${opts.timeoutMs ?? exports.FILE_TIMEOUT_MS} ms`, "Retry; for a large artefact raise timeoutMs.");
1319
+ const timedOut = () => new core_js_1.TimeoutError(`${method} ${path} timed out after ${opts.timeoutMs ?? exports.FILE_TIMEOUT_MS} ms`, "Retry; for a large artefact raise timeoutMs.");
1248
1320
  const dispatcher = this.guardDispatcher();
1249
1321
  for (let attempt = 0;; attempt++) {
1250
1322
  let res;
1251
1323
  this.stats.requests++;
1252
1324
  try {
1253
- res = await this.fetchImpl(url, { method: "GET", headers, signal, ...(dispatcher === undefined ? {} : { dispatcher }) });
1325
+ res = await this.fetchImpl(url, {
1326
+ method,
1327
+ headers,
1328
+ signal,
1329
+ ...(body === undefined ? {} : { body: body.data }),
1330
+ ...(dispatcher === undefined ? {} : { dispatcher }),
1331
+ });
1254
1332
  }
1255
1333
  catch (err) {
1256
1334
  const e = err;
@@ -1299,10 +1377,12 @@ class PromigenceClient {
1299
1377
  continue;
1300
1378
  }
1301
1379
  const code = payload.error?.code ?? codeForStatus(res.status);
1302
- throw errorFor(code, payload.error?.message ?? `HTTP ${res.status} from GET ${path}`, payload.error?.fix ?? "See the API response for details.", {
1380
+ const rid = headerOf(res, "request-id") ?? headerOf(res, "x-request-id");
1381
+ throw errorFor(code, payload.error?.message ?? `HTTP ${res.status} from ${method} ${path}`, payload.error?.fix ?? "See the API response for details.", {
1303
1382
  status: res.status,
1304
1383
  ...bodyExtra(payload),
1305
1384
  ...(retryAfterMs === undefined ? {} : { retryAfterMs }),
1385
+ ...(rid ? { requestId: rid } : {}),
1306
1386
  });
1307
1387
  }
1308
1388
  }
@@ -1378,6 +1458,29 @@ class PromigenceClient {
1378
1458
  const r = await this.request({ method: "GET", path: `/billing/history?${q.toString()}` });
1379
1459
  return pageOf(r, "entries", limit);
1380
1460
  },
1461
+ holds: () => this.request({ method: "GET", path: "/billing/holds" }),
1462
+ buyCredits: (amountUsd) => this.request({ method: "POST", path: "/billing/credits", body: { amount_usd: amountUsd } }),
1463
+ settleCredits: (sessionId) => {
1464
+ if (!sessionId?.trim())
1465
+ throw new core_js_1.InvalidArgumentError("billing.settleCredits needs a session id", "Pass the session_id buyCredits() answered.");
1466
+ return this.request({ method: "POST", path: `/billing/credits/${encodeURIComponent(sessionId)}/settle`, body: {} });
1467
+ },
1468
+ autoReload: () => this.request({ method: "GET", path: "/billing/auto-reload" }),
1469
+ setAutoReload: (opts) => this.request({
1470
+ method: "PUT",
1471
+ path: "/billing/auto-reload",
1472
+ body: {
1473
+ on: opts.on,
1474
+ ...(opts.thresholdUsd === undefined ? {} : { threshold_usd: opts.thresholdUsd }),
1475
+ ...(opts.amountUsd === undefined ? {} : { amount_usd: opts.amountUsd }),
1476
+ ...(opts.authorizationVersion === undefined ? {} : { authorization_version: opts.authorizationVersion }),
1477
+ },
1478
+ }),
1479
+ repayDispute: (disputeId) => {
1480
+ if (!disputeId?.trim())
1481
+ throw new core_js_1.InvalidArgumentError("billing.repayDispute needs a dispute id", "Pass the hold's dispute_id from billing.usage().");
1482
+ return this.request({ method: "POST", path: `/billing/disputes/${encodeURIComponent(disputeId)}/repay`, body: {} });
1483
+ },
1381
1484
  checkout: () => this.request({ method: "POST", path: "/billing/checkout", body: {} }),
1382
1485
  portal: () => this.request({ method: "POST", path: "/billing/portal", body: {} }),
1383
1486
  referrals: () => this.request({ method: "GET", path: "/referrals" }),
@@ -1436,6 +1539,9 @@ class PromigenceClient {
1436
1539
  rename: (org, project, name) => this.request({
1437
1540
  method: "PATCH", path: `/orgs/${orgSeg(org)}/projects/${encodeURIComponent(project)}`, body: { name },
1438
1541
  }),
1542
+ update: async (org, project, update) => this.request({
1543
+ method: "PATCH", path: `/orgs/${orgSeg(org)}/projects/${encodeURIComponent(project)}`, body: projectUpdateBody(update),
1544
+ }),
1439
1545
  },
1440
1546
  };
1441
1547
  }
@@ -1652,12 +1758,8 @@ class PromigenceClient {
1652
1758
  return { sandboxes: r.value, timedOut: r.timedOut, waitedMs: r.waitedMs };
1653
1759
  },
1654
1760
  waitDone: async (runId, opts = {}) => {
1655
- const r = await this.waitLoop(opts, exports.LONG_POLL_MS, { run_id: runId, state: "running" }, (read) => this.runs.get(runId, read), (run) => {
1656
- const terminal = run.state === "completed" || run.state === "cap_killed" || run.state === "aborted";
1657
- const allDone = Array.isArray(run.sandboxes) && run.sandboxes.length > 0 &&
1658
- run.sandboxes.every((s) => s.state === "ended" || s.command_outcome != null);
1659
- return terminal || allDone;
1660
- });
1761
+ const endedSince = {};
1762
+ const r = await this.waitLoop(opts, exports.LONG_POLL_MS, { run_id: runId, state: "running" }, (read) => this.runs.get(runId, read), (run) => runWorkDone(run, endedSince));
1661
1763
  return { run: r.value, timedOut: r.timedOut, waitedMs: r.waitedMs };
1662
1764
  },
1663
1765
  };
@@ -2066,10 +2168,19 @@ class PromigenceClient {
2066
2168
  }), sandboxId);
2067
2169
  return r.ports ?? [];
2068
2170
  },
2069
- revoke: (sandboxId, tokenOrPort) => this.request({
2070
- method: "DELETE",
2071
- path: `/sandboxes/${encodeURIComponent(sandboxId)}/ports/${encodeURIComponent(String(tokenOrPort))}`,
2072
- }),
2171
+ revoke: (sandboxId, tokenOrPort) => {
2172
+ const which = String(tokenOrPort).trim();
2173
+ return /^[0-9]+$/.test(which)
2174
+ ? this.request({
2175
+ method: "DELETE",
2176
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/ports/${which}`,
2177
+ })
2178
+ : this.request({
2179
+ method: "POST",
2180
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/ports/revoke`,
2181
+ body: { token: which },
2182
+ });
2183
+ },
2073
2184
  },
2074
2185
  ssh: {
2075
2186
  open: (sandboxId, opts) => {
@@ -2184,6 +2295,7 @@ class PromigenceClient {
2184
2295
  ...(opts.spendCap === undefined ? {} : { spend_cap: checkSpendCap(opts.spendCap) }),
2185
2296
  },
2186
2297
  }),
2298
+ resumePausedForCredits: () => this.request({ method: "POST", path: "/sandboxes/resume", body: { paused_for: "credits" } }),
2187
2299
  snapshot: (sandboxId, opts = {}) => this.request({
2188
2300
  method: "POST",
2189
2301
  path: `/sandboxes/${encodeURIComponent(sandboxId)}/snapshot`,
@@ -2254,38 +2366,42 @@ class PromigenceClient {
2254
2366
  },
2255
2367
  files: {
2256
2368
  list: (sandboxId, path = "/", opts = {}) => {
2257
- const q = [`path=${encodeURIComponent(path)}`];
2369
+ const body = { path };
2258
2370
  if (opts.recursive)
2259
- q.push("recursive=1");
2371
+ body.recursive = true;
2260
2372
  if (opts.depth !== undefined)
2261
- q.push(`depth=${Math.max(0, Math.floor(opts.depth))}`);
2373
+ body.depth = Math.max(0, Math.floor(opts.depth));
2262
2374
  if (opts.limit !== undefined)
2263
- q.push(`limit=${Math.max(1, Math.floor(opts.limit))}`);
2375
+ body.limit = Math.max(1, Math.floor(opts.limit));
2264
2376
  if (opts.hidden === false)
2265
- q.push("hidden=0");
2377
+ body.hidden = false;
2266
2378
  return this.request({
2267
- method: "GET",
2268
- path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/list?${q.join("&")}`,
2379
+ method: "POST",
2380
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/list`,
2381
+ body,
2382
+ replayable: true,
2269
2383
  });
2270
2384
  },
2271
2385
  search: (sandboxId, path, opts = {}) => {
2272
2386
  if (!opts.pattern && !opts.contains) {
2273
2387
  throw new core_js_1.InvalidArgumentError("files.search needs `pattern`, `contains`, or both", "Pass { pattern: '*.py' } for a name glob, { contains: 'TODO' } for text inside files.");
2274
2388
  }
2275
- const q = [`path=${encodeURIComponent(path)}`];
2389
+ const body = { path };
2276
2390
  if (opts.pattern)
2277
- q.push(`pattern=${encodeURIComponent(opts.pattern)}`);
2391
+ body.pattern = opts.pattern;
2278
2392
  if (opts.contains)
2279
- q.push(`contains=${encodeURIComponent(opts.contains)}`);
2393
+ body.contains = opts.contains;
2280
2394
  if (opts.ignoreCase)
2281
- q.push("ignore_case=1");
2395
+ body.ignore_case = true;
2282
2396
  if (opts.limit !== undefined)
2283
- q.push(`limit=${Math.max(1, Math.floor(opts.limit))}`);
2397
+ body.limit = Math.max(1, Math.floor(opts.limit));
2284
2398
  if (opts.hidden === false)
2285
- q.push("hidden=0");
2399
+ body.hidden = false;
2286
2400
  return this.request({
2287
- method: "GET",
2288
- path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/search?${q.join("&")}`,
2401
+ method: "POST",
2402
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/search`,
2403
+ body,
2404
+ replayable: true,
2289
2405
  });
2290
2406
  },
2291
2407
  mkdir: (sandboxId, path, opts = {}) => this.request({
@@ -2342,11 +2458,13 @@ class PromigenceClient {
2342
2458
  body: { archive, into, ...(opts.strip === undefined ? {} : { strip: Math.max(0, Math.floor(opts.strip)) }) },
2343
2459
  }),
2344
2460
  stat: (sandboxId, path) => this.request({
2345
- method: "GET",
2346
- path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/stat?path=${encodeURIComponent(path)}`,
2461
+ method: "POST",
2462
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files/stat`,
2463
+ body: { path },
2464
+ replayable: true,
2347
2465
  }),
2348
- get: (sandboxId, path, opts = {}) => this.bytes("GET", `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`, undefined, opts),
2349
- download: (sandboxId, path, onChunk, opts = {}) => this.stream(`/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`, onChunk, opts),
2466
+ get: (sandboxId, path, opts = {}) => this.bytes("POST", `/sandboxes/${encodeURIComponent(sandboxId)}/files/read`, jsonBody({ path }), opts, true),
2467
+ download: (sandboxId, path, onChunk, opts = {}) => this.stream("POST", `/sandboxes/${encodeURIComponent(sandboxId)}/files/read`, onChunk, opts, jsonBody({ path })),
2350
2468
  text: async (sandboxId, path, opts = {}) => new TextDecoder().decode(await this.sandboxes.files.get(sandboxId, path, opts)),
2351
2469
  put: async (sandboxId, path, data, opts = {}) => {
2352
2470
  const body = typeof data === "string" ? new TextEncoder().encode(data) : data;
@@ -2356,8 +2474,7 @@ class PromigenceClient {
2356
2474
  }, opts);
2357
2475
  },
2358
2476
  putFrom: async (sandboxId, path, source, opts = {}) => {
2359
- const mode = opts.mode === undefined ? "" : `&mode=${opts.mode.toString(8)}`;
2360
- const base = `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}${mode}`;
2477
+ const url = `/sandboxes/${encodeURIComponent(sandboxId)}/files`;
2361
2478
  let sent = 0;
2362
2479
  let chunks = 0;
2363
2480
  let last;
@@ -2366,7 +2483,12 @@ class PromigenceClient {
2366
2483
  if (slice.length === 0 && sent < source.size) {
2367
2484
  throw new core_js_1.InvalidArgumentError(`the source of ${path} ended at ${sent} of ${source.size} bytes`, "The file shrank while it was being sent; send it again once nothing is writing it.");
2368
2485
  }
2369
- const raw = await this.bytes("PUT", sent === 0 ? base : `${base}&offset=${sent}`, slice, opts);
2486
+ const fields = [["path", path]];
2487
+ if (opts.mode !== undefined)
2488
+ fields.push(["mode", opts.mode.toString(8)]);
2489
+ if (sent > 0)
2490
+ fields.push(["offset", String(sent)]);
2491
+ const raw = await this.bytes("PUT", url, fileForm(fields, slice), opts);
2370
2492
  last = JSON.parse(new TextDecoder().decode(raw));
2371
2493
  sent += slice.length;
2372
2494
  chunks += 1;
@@ -2375,8 +2497,8 @@ class PromigenceClient {
2375
2497
  },
2376
2498
  delete: (sandboxId, path, opts = {}) => this.request({
2377
2499
  method: "DELETE",
2378
- path: `/sandboxes/${encodeURIComponent(sandboxId)}/files?path=${encodeURIComponent(path)}`
2379
- + (opts.recursive ? "&recursive=1" : ""),
2500
+ path: `/sandboxes/${encodeURIComponent(sandboxId)}/files`,
2501
+ body: { path, ...(opts.recursive ? { recursive: true } : {}) },
2380
2502
  }),
2381
2503
  },
2382
2504
  output: async (execId, opts = {}) => {
@@ -2555,10 +2677,10 @@ class PromigenceClient {
2555
2677
  throw new core_js_1.InvalidArgumentError("snapshots.delete needs a snapshot", "Pass a snap_ id, an alias, or alias@commit7.");
2556
2678
  return this.request({ method: "DELETE", path: `/snapshots/${seg(idOrAlias)}` });
2557
2679
  },
2558
- alias: (idOrAlias, alias) => this.request({
2680
+ alias: (idOrAlias, alias, opts = {}) => this.request({
2559
2681
  method: "PUT",
2560
2682
  path: `/snapshots/${seg(idOrAlias)}/alias`,
2561
- body: { alias },
2683
+ body: { alias, ...(alias !== null && opts.moveAlias ? { move_alias: true } : {}) },
2562
2684
  }),
2563
2685
  waitBuild: async (buildId, opts = {}) => {
2564
2686
  const r = await this.waitLoop(opts, BUILD_POLL_MS, { build_id: buildId, state: "running" }, (read) => this.snapshots.status(buildId, read), (b) => b.state !== "running" && b.state !== "queued");
@@ -18,6 +18,7 @@ export declare class CommandTimeoutError extends CommandExitError {
18
18
  readonly code: string;
19
19
  readonly fix: string;
20
20
  readonly guide: string;
21
+ readonly docs: string;
21
22
  constructor(result: CommandResult, timeoutMs: number | undefined);
22
23
  }
23
24
  /**
@@ -16,9 +16,11 @@ class CommandTimeoutError extends core_js_1.CommandExitError {
16
16
  code;
17
17
  fix;
18
18
  guide;
19
+ docs;
19
20
  constructor(result, timeoutMs) {
20
21
  super(result);
21
22
  this.code = "task_timeout";
23
+ this.docs = (0, core_js_1.errorsDocsFor)(this.code);
22
24
  this.timeoutMs = timeoutMs;
23
25
  this.message = timeoutMs === undefined
24
26
  ? `command timed out (killed at its timeout, exit ${result.exitCode})`
@@ -15,9 +15,11 @@
15
15
  * - Empty directories and permission bits travel as they are on disk; a set-user-ID or
16
16
  * set-group-ID bit is cleared, with a note.
17
17
  * - Files that look like credentials (`.env`, private keys, cloud credentials, ...) are refused,
18
- * by name, unless `allowSecrets` says otherwise. The check goes by file name: it is
19
- * best-effort and cannot see what a file holds, so look over the list (`listContext`) before
20
- * you send a directory.
18
+ * by name, unless `allowSecretFiles` names them (each one) or `allowSecrets` says otherwise. The
19
+ * check goes by file name and reads only two kinds of file, to clear the plain false
20
+ * positives: a `.pem` holding certificates or public keys and no private key, and an `.npmrc`
21
+ * with no token or password in it. It is best-effort, so look over the list (`listContext`)
22
+ * before you send a directory.
21
23
  * - Symbolic links travel as links, never as the file they point at.
22
24
  */
23
25
  /** The prefix a packed directory travels under in `dockerfile.context`. */
@@ -65,9 +67,17 @@ export declare function dockerignoreMatcher(text: string): {
65
67
  /**
66
68
  * Does this path look like a credential (`.env`, a private key, cloud or registry credentials, a
67
69
  * token file, ...)? By name only: best-effort, it cannot see what a file holds. Templates such as
68
- * `.env.example` are not.
70
+ * `.env.example` are not. [`secretLikeFile`] also reads the few files whose contents can clear them.
69
71
  */
70
72
  export declare function secretLike(path: string): boolean;
73
+ /** The most of a file [`secretLikeFile`] reads; a larger one is judged by its name alone. */
74
+ export declare const CONTENT_CHECK_MAX_BYTES: number;
75
+ /**
76
+ * [`secretLike`] for a file on disk at `abs` (`rel` is its path in the upload): a name that looks
77
+ * like a credential, unless what the file holds clears it ([`clearedByContent`]). A link, a file
78
+ * over [`CONTENT_CHECK_MAX_BYTES`] and a file that cannot be read keep the name's verdict.
79
+ */
80
+ export declare function secretLikeFile(rel: string, abs: string): boolean;
71
81
  /** What a build context is packed from and sent with. */
72
82
  export interface ContextOptions {
73
83
  /**
@@ -84,6 +94,11 @@ export interface ContextOptions {
84
94
  * nothing is sent.
85
95
  */
86
96
  allowSecrets?: boolean;
97
+ /**
98
+ * Send these files that look like credentials, and only these: paths inside the context,
99
+ * `/`-separated, as `listContext` names them. Every other one is still refused.
100
+ */
101
+ allowSecretFiles?: readonly string[];
87
102
  /** Called with each note: what was left out by default, or changed on the way. */
88
103
  onNote?: (note: string) => void;
89
104
  }