@veris-ai/daytona 0.2.1 → 0.3.1

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
@@ -21,11 +21,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ BUNDLED_CA_FILES: () => BUNDLED_CA_FILES,
25
+ BUNDLED_CA_PATCH_SCRIPT: () => BUNDLED_CA_PATCH_SCRIPT,
24
26
  CA_CERT_PATH: () => CA_CERT_PATH,
25
27
  ControlPlane: () => ControlPlane,
26
- DEFAULT_REGISTRY_HOSTS: () => DEFAULT_REGISTRY_HOSTS,
28
+ DAYTONA_API_URL: () => DAYTONA_API_URL,
29
+ DEFAULT_API_BASE: () => DEFAULT_API_BASE,
30
+ DELETE_SANDBOXES: () => DELETE_SANDBOXES,
27
31
  Daytona: () => Daytona,
28
32
  MissingCredentialsError: () => MissingCredentialsError,
33
+ NODE_PROXY_FLAG: () => NODE_PROXY_FLAG,
34
+ NODE_PROXY_PRELOAD: () => NODE_PROXY_PRELOAD,
35
+ NODE_TRUST_FLAG: () => NODE_TRUST_FLAG,
29
36
  ReceiptIntegrityError: () => ReceiptIntegrityError,
30
37
  SDK_VERSION: () => SDK_VERSION,
31
38
  SYSTEM_BUNDLE: () => SYSTEM_BUNDLE,
@@ -39,14 +46,36 @@ __export(index_exports, {
39
46
  VerisGatewayUnreachableError: () => VerisGatewayUnreachableError,
40
47
  VerisUntouchedError: () => VerisUntouchedError,
41
48
  assertHttpControlPlane: () => assertHttpControlPlane,
49
+ buildNetwork: () => buildNetwork,
50
+ canDeleteSandboxes: () => canDeleteSandboxes,
51
+ cannotTeardownWarning: () => cannotTeardownWarning,
42
52
  dataPlaneHosts: () => dataPlaneHosts,
53
+ daytonaApiUrl: () => daytonaApiUrl,
43
54
  default: () => daytona_default,
55
+ directTwinHosts: () => directTwinHosts,
56
+ fetchDaytonaKey: () => fetchDaytonaKey,
44
57
  fetchManual: () => fetchManual,
58
+ gatewayIps: () => gatewayIps,
45
59
  gatewayProxyUrl: () => gatewayProxyUrl,
60
+ isPermissionDenied: () => isPermissionDenied,
46
61
  isVerisSandbox: () => isVerisSandbox,
62
+ missingKeyMessage: () => missingKeyMessage,
63
+ nodeOptionsWithProxy: () => nodeOptionsWithProxy,
64
+ nodeOptionsWithTrust: () => nodeOptionsWithTrust,
65
+ nodeProxyPreloadScript: () => nodeProxyPreloadScript,
66
+ patchBundledCas: () => patchBundledCas,
67
+ profilePath: () => profilePath,
68
+ requireVerisCredentials: () => requireVerisCredentials,
69
+ resolveVerisCredentials: () => resolveVerisCredentials,
70
+ sandboxBrakes: () => sandboxBrakes,
71
+ teardownRefusedMessage: () => teardownRefusedMessage,
72
+ trustPrelude: () => trustPrelude,
47
73
  twinHosts: () => twinHosts,
48
74
  vendorHosts: () => vendorHosts,
49
- vendoredTrustEnv: () => vendoredTrustEnv
75
+ vendoredTrustEnv: () => vendoredTrustEnv,
76
+ verisNodeOptions: () => verisNodeOptions,
77
+ verisOwnsTwin: () => verisOwnsTwin,
78
+ verisTwinId: () => verisTwinId
50
79
  });
51
80
  module.exports = __toCommonJS(index_exports);
52
81
  __reExport(index_exports, require("@daytona/sdk"), module.exports);
@@ -224,14 +253,19 @@ var ControlPlane = class {
224
253
  }
225
254
  if (!res.ok) await this.json(res, `update sandbox ${sandboxId}`);
226
255
  }
227
- /** Extend a twin's TTL so it stays in lockstep with an extended Daytona sandbox. */
228
- async extendTtl(environmentId, sandboxId, ttlMinutes) {
229
- const res = await this.request("PATCH", `/v1/environments/${environmentId}/sandboxes/${sandboxId}`, { ttl_minutes: ttlMinutes });
230
- if (res.status === 404) {
231
- throw new TwinExpiredError(`Veris sandbox ${sandboxId} not found \u2014 cannot extend TTL`, { verisSandboxId: sandboxId });
232
- }
233
- if (!res.ok && res.status !== 405) await this.json(res, `extend TTL of ${sandboxId}`);
234
- }
256
+ // There was an extendTtl() here, and it did nothing.
257
+ //
258
+ // It PATCHed { ttl_minutes } at the sandbox resource, and the server's
259
+ // UpdateSandboxRequest declares only client_base_url. Pydantic ignores
260
+ // unknown fields, so the route answered 200 having changed no TTL at all —
261
+ // a call that reports success while doing nothing, which is worse than no
262
+ // call, because the caller then believes the twin will outlive the run.
263
+ // Nothing here called it; it was API surface promising a thing that could
264
+ // not happen.
265
+ //
266
+ // Bring it back when a route exists to carry it. Until then a twin's TTL is
267
+ // decided once, by ttl_minutes at create — where this SDK already keeps it
268
+ // in step with the Daytona sandbox's own ttlMinutes.
235
269
  /** Create-time preflight: is the gateway infrastructure up, per the control plane? */
236
270
  async gatewayHealth() {
237
271
  const res = await this.request("GET", "/v1/gateway/health");
@@ -245,40 +279,162 @@ var ControlPlane = class {
245
279
  }
246
280
  };
247
281
 
282
+ // src/run-receipt.ts
283
+ var import_node_crypto = require("crypto");
284
+
248
285
  // src/receipt.ts
286
+ var PAGE_LIMIT = 1e3;
287
+ var MAX_PAGES = 20;
288
+ function rowsOf(body) {
289
+ if (!body || !Array.isArray(body.requests)) {
290
+ throw new VerisError("invalid request log: expected requests array", { phase: "receipt" });
291
+ }
292
+ const rows = body.requests;
293
+ for (const r of rows) {
294
+ if (!r || !Number.isSafeInteger(r.id) || r.id <= 0 || typeof r.method !== "string" || typeof r.path !== "string" || typeof r.tier !== "string" || !(r.status === null || typeof r.status === "number")) {
295
+ throw new VerisError("invalid request log row: stable id, method, path, tier and status required", { phase: "receipt" });
296
+ }
297
+ }
298
+ return rows;
299
+ }
249
300
  function parseRequestsBody(body) {
250
- const rows = Array.isArray(body?.requests) ? body.requests : [];
251
- const entries = rows.map((r) => {
252
- const row = r;
253
- return {
254
- method: String(row.method ?? ""),
255
- path: String(row.path ?? ""),
256
- status: typeof row.status === "number" ? row.status : null
257
- };
258
- });
259
- return { count: entries.length, entries };
301
+ const rows = rowsOf(body);
302
+ const entries = rows.filter(
303
+ (r) => ["handler", "fault", "fallback", "fallback-llm", "fallback-replay"].includes(r.tier) && !/^\/veris(?:\/|$)/.test(r.path)
304
+ ).map(({ id, method, path, status, tier }) => ({ id, method, path, status, tier }));
305
+ return { count: entries.length, entries, total: rows.length };
260
306
  }
261
- async function fetchReceiptEntry(svc) {
262
- const url = `${svc.control_url}/veris/requests`;
263
- const res = await fetch(url);
307
+ async function readPage(svc, query) {
308
+ const res = await fetch(`${svc.control_url.replace(/\/$/, "")}/veris/requests?${query}`, {
309
+ signal: AbortSignal.timeout(3e4),
310
+ redirect: "error"
311
+ });
264
312
  const text = await res.text();
265
- if (!res.ok) {
266
- throw new VerisError(`could not read receipt for service '${svc.name}' (${res.status})`, {
267
- phase: "receipt",
268
- responseBody: text.slice(0, 500)
269
- });
313
+ if (!res.ok) throw new VerisError(`could not read receipt for service '${svc.name}' (${res.status})`, { phase: "receipt" });
314
+ let body;
315
+ try {
316
+ body = JSON.parse(text);
317
+ } catch {
318
+ throw new VerisError(`service '${svc.name}' returned a non-JSON receipt body`, { phase: "receipt" });
270
319
  }
271
- let raw;
320
+ return rowsOf(body);
321
+ }
322
+ async function fetchWatermark(svc) {
323
+ const rows = await readPage(svc, new URLSearchParams({ limit: "1", order: "desc" }));
324
+ if (rows.length > 1) throw new VerisError("request log ignored watermark limit", { phase: "receipt" });
325
+ return rows[0]?.id ?? 0;
326
+ }
327
+ async function fetchReceiptEntry(svc, sinceId = 0) {
328
+ if (!Number.isSafeInteger(sinceId) || sinceId < 0) throw new VerisError("invalid receipt watermark", { phase: "receipt" });
329
+ const end = await fetchWatermark(svc);
330
+ if (end < sinceId) throw new VerisError("receipt baseline invalid: log moved backwards; take a new baseline", { phase: "receipt" });
331
+ const entries = [];
332
+ const raw = [];
333
+ let mark = sinceId;
334
+ let incompleteReason;
335
+ for (let page = 0; mark < end; page++) {
336
+ if (page >= MAX_PAGES) {
337
+ incompleteReason = "page-limit";
338
+ break;
339
+ }
340
+ let rows;
341
+ try {
342
+ rows = await readPage(svc, new URLSearchParams({ limit: String(PAGE_LIMIT), order: "asc", since_id: String(mark) }));
343
+ } catch (error) {
344
+ if (!raw.length) throw error;
345
+ incompleteReason = "read-failed";
346
+ break;
347
+ }
348
+ let previous = mark;
349
+ if (!rows.length || rows.some((r) => {
350
+ const bad = r.id <= previous;
351
+ previous = r.id;
352
+ return bad;
353
+ })) {
354
+ incompleteReason = "pagination-not-progressing";
355
+ break;
356
+ }
357
+ const window = rows.filter((r) => r.id <= end);
358
+ raw.push(...window);
359
+ entries.push(...parseRequestsBody({ requests: window }).entries);
360
+ mark = rows[rows.length - 1].id;
361
+ }
362
+ entries.sort((a, b) => b.id - a.id);
363
+ return {
364
+ requests: entries.length,
365
+ controlUrl: svc.control_url,
366
+ entries,
367
+ sinceId,
368
+ untilId: end,
369
+ capped: Boolean(incompleteReason),
370
+ ...incompleteReason ? { incompleteReason } : {},
371
+ raw: { requests: raw }
372
+ };
373
+ }
374
+
375
+ // src/run-receipt.ts
376
+ var HEADER = "x-veris-receipt-baseline";
377
+ function markerOf(row) {
272
378
  try {
273
- raw = JSON.parse(text);
379
+ const headers = typeof row.request_headers === "string" ? JSON.parse(row.request_headers) : row.request_headers;
380
+ return headers?.[HEADER];
274
381
  } catch {
275
- throw new VerisError(`service '${svc.name}' returned a non-JSON receipt body`, {
276
- phase: "receipt",
277
- responseBody: text.slice(0, 500)
382
+ return void 0;
383
+ }
384
+ }
385
+ async function captureBaseline(twinId, sandboxId, services) {
386
+ const marks = await Promise.all(services.map(async (svc) => {
387
+ const marker = (0, import_node_crypto.randomUUID)();
388
+ const res = await fetch(`${svc.control_url.replace(/\/$/, "")}/veris/schema`, {
389
+ headers: { [HEADER]: marker },
390
+ signal: AbortSignal.timeout(3e4),
391
+ redirect: "error"
278
392
  });
393
+ await res.text();
394
+ if (!res.ok) throw new VerisError(`could not establish baseline for '${svc.name}' (${res.status})`, { phase: "receipt" });
395
+ for (let attempt = 0; attempt < 5; attempt++) {
396
+ const rows = await readPage(svc, new URLSearchParams({ limit: "1000", order: "desc" }));
397
+ const anchor = rows.find((r) => markerOf(r) === marker && r.tier === "control" && r.path === "/veris/schema");
398
+ if (anchor) return [svc.name, { controlUrl: svc.control_url, id: anchor.id, marker }];
399
+ await new Promise((resolve) => setTimeout(resolve, 50));
400
+ }
401
+ throw new VerisError(`baseline unavailable for '${svc.name}': trace must retain control request headers`, { phase: "receipt" });
402
+ }));
403
+ return { version: 1, twinId, sandboxId, services: Object.fromEntries(marks) };
404
+ }
405
+ async function validateBaseline(baseline, twinId, sandboxId, services) {
406
+ const invalid = () => new VerisError("receipt baseline invalid: session, services or history changed; take a new baseline before execution", { phase: "receipt" });
407
+ if (baseline.version !== 1 || baseline.twinId !== twinId || baseline.sandboxId !== sandboxId || Object.keys(baseline.services).sort().join("\0") !== services.map((s) => s.name).sort().join("\0")) throw invalid();
408
+ await Promise.all(services.map(async (svc) => {
409
+ const mark = baseline.services[svc.name];
410
+ if (!mark || mark.controlUrl !== svc.control_url || !Number.isSafeInteger(mark.id) || mark.id <= 0) throw invalid();
411
+ const rows = await readPage(svc, new URLSearchParams({ limit: "1", order: "asc", since_id: String(mark.id - 1) }));
412
+ if (rows.length !== 1 || rows[0].id !== mark.id || markerOf(rows[0]) !== mark.marker) throw invalid();
413
+ }));
414
+ }
415
+
416
+ // src/service-control.ts
417
+ async function serviceControl(svc, resource, options = {}) {
418
+ const method = options.method ?? "GET";
419
+ if (!["manual", "schema", "operations", "data", "requests"].includes(resource) || !["GET", "POST", "PATCH"].includes(method) || method !== "GET" && resource !== "data" || method === "GET" && options.body !== void 0 || !/^https?:\/\//.test(svc.control_url)) {
420
+ throw new VerisError("unsupported service control operation");
421
+ }
422
+ const url = new URL(`${svc.control_url.replace(/\/$/, "")}/veris/${resource}`);
423
+ for (const [key, value] of Object.entries(options.query ?? {})) url.searchParams.set(key, value);
424
+ const res = await fetch(url, {
425
+ method,
426
+ redirect: "error",
427
+ signal: AbortSignal.timeout(3e4),
428
+ headers: { "Content-Type": "application/json" },
429
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body)
430
+ });
431
+ const text = await res.text();
432
+ if (!res.ok) throw new VerisError(`service '${svc.name}' ${method} /veris/${resource} failed (${res.status})`);
433
+ try {
434
+ return JSON.parse(text);
435
+ } catch {
436
+ throw new VerisError(`service '${svc.name}' returned invalid control JSON`);
279
437
  }
280
- const { count, entries } = parseRequestsBody(raw);
281
- return { requests: count, controlUrl: svc.control_url, entries, raw };
282
438
  }
283
439
 
284
440
  // src/network.ts
@@ -303,6 +459,9 @@ function twinHosts(services) {
303
459
  }
304
460
  return [...hosts].sort();
305
461
  }
462
+ function directTwinHosts(services) {
463
+ return twinHosts(services.filter((s) => isHttpUrl(s.url) && !(s.routes ?? []).length));
464
+ }
306
465
  function dataPlaneHosts(services) {
307
466
  const hosts = /* @__PURE__ */ new Set();
308
467
  for (const svc of services) {
@@ -352,51 +511,30 @@ var PROCESS_CONTROLLING = /* @__PURE__ */ new Set([
352
511
  function isSafeEnvName(name) {
353
512
  return /^[A-Z][A-Z0-9_]{0,63}$/.test(name) && !PROCESS_CONTROLLING.has(name);
354
513
  }
355
- var DEFAULT_REGISTRY_HOSTS = [
356
- // JS
357
- "registry.npmjs.org",
358
- "registry.yarnpkg.com",
359
- // Python
360
- "pypi.org",
361
- "files.pythonhosted.org",
362
- // Go
363
- "proxy.golang.org",
364
- "sum.golang.org",
365
- // Rust
366
- "crates.io",
367
- "static.crates.io",
368
- "index.crates.io",
369
- // Debian/Ubuntu
370
- "deb.debian.org",
371
- "security.debian.org",
372
- "archive.ubuntu.com",
373
- "security.ubuntu.com",
374
- // Source + container hosts the above routinely redirect to
375
- "github.com",
376
- "codeload.github.com",
377
- "objects.githubusercontent.com",
378
- "raw.githubusercontent.com",
379
- "ghcr.io"
380
- ];
514
+ var IPV4_RE = /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/;
381
515
  function buildNetwork(args) {
382
- const { services, mode, gatewayHosts, allowOut = [], allowRegistries = true } = args;
383
- if (mode === "open") return {};
384
- const domains = [
385
- ...vendorHosts(services),
386
- ...gatewayHosts,
387
- ...dataPlaneHosts(services),
388
- ...allowRegistries ? DEFAULT_REGISTRY_HOSTS : [],
389
- ...allowOut
390
- ].filter((h) => Boolean(h));
391
- return {
392
- // NOT networkBlockAll: that blocks everything including the gateway, and the
393
- // allowlist is what Daytona documents as "unbypassable network-layer
394
- // enforcement". Blocking all and then allowing is not a shape the API
395
- // offers; a non-empty domainAllowList IS the deny-by-default.
396
- domainAllowList: [...new Set(domains)].sort().join(",")
397
- };
516
+ const { mode, gatewayIps: gatewayIps2 } = args;
517
+ if (mode === "open") return { params: {} };
518
+ const ips = [...new Set(gatewayIps2)];
519
+ if (!ips.length) {
520
+ throw new VerisError(
521
+ "the control plane did not name the gateway's IP addresses (egress credential `gateway_ips`) and the gateway hostname could not be resolved, so the sandbox cannot be pinned to it. Upgrade the control plane, or run with veris.egress: 'open' (Daytona still blocks anything that bypasses the proxy).",
522
+ { phase: "credential-mint" }
523
+ );
524
+ }
525
+ const bad = ips.filter((ip) => !IPV4_RE.test(ip));
526
+ if (bad.length) {
527
+ throw new VerisError(
528
+ `the control plane returned gateway addresses that are not IPv4: ${JSON.stringify(bad)} (Daytona's networkAllowList takes IPv4 CIDRs only)`,
529
+ { phase: "credential-mint" }
530
+ );
531
+ }
532
+ return { params: { networkAllowList: ips.map((ip) => `${ip}/32`).join(",") } };
398
533
  }
399
534
 
535
+ // src/gateway.ts
536
+ var import_node_dns = require("dns");
537
+
400
538
  // src/trust.ts
401
539
  var CA_CERT_PATH = "/usr/local/share/ca-certificates/veris-ca.crt";
402
540
  var SYSTEM_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
@@ -424,6 +562,80 @@ function vendoredTrustEnv() {
424
562
  NODE_EXTRA_CA_CERTS: VERIS_BUNDLE
425
563
  };
426
564
  }
565
+ function trustPrelude(env = vendoredTrustEnv()) {
566
+ return Object.entries(env).map(([k, v]) => `export ${k}='${v.replace(/'/g, `'\\''`)}';`).join(" ");
567
+ }
568
+ var BUNDLED_CA_FILES = [
569
+ { sdk: "pip (vendored certifi)", suffix: "pip/_vendor/certifi/cacert.pem" },
570
+ { sdk: "certifi", suffix: "certifi/cacert.pem" },
571
+ { sdk: "botocore", suffix: "botocore/cacert.pem" },
572
+ // Matches both stripe-python's site-packages layout and the stripe-ruby gem.
573
+ { sdk: "stripe", suffix: "stripe/data/ca-certificates.crt" },
574
+ { sdk: "httplib2", suffix: "httplib2/cacerts.txt" }
575
+ ];
576
+ var BUNDLED_CA_PATCH_SCRIPT = "/tmp/veris-patch-bundled-cas.sh";
577
+ var BUNDLED_CA_PATCHED_MARKER = "the Veris CA was appended to ";
578
+ function bundledCaPatchScript() {
579
+ const find = BUNDLED_CA_FILES.map((f) => `find / -xdev \\( -path /proc -o -path /sys -o -path /dev \\) -prune -o -path '*/${f.suffix}' -type f -print 2>/dev/null`).join("; ");
580
+ return [
581
+ `marker=$(sed -n 2p ${VERIS_CA_FILE} 2>/dev/null)`,
582
+ // No CA on disk means nothing to append, and appending nothing to every
583
+ // bundle in the image would be the worst available no-op.
584
+ `[ -n "$marker" ] || { echo "no ${VERIS_CA_FILE} to append" >&2; exit 1; }`,
585
+ // The whole loop is one brace group at the end of the pipe so `n` survives
586
+ // to the summary: a pipeline's last stage is a subshell, and a counter
587
+ // incremented inside a bare `while` would be gone by the time we report it.
588
+ `{ ${find}; } | sort -u | { n=0`,
589
+ `while IFS= read -r f; do`,
590
+ ` [ -w "$f" ] || continue`,
591
+ ` grep -qF "$marker" "$f" && continue`,
592
+ ` { printf '\\n'; cat ${VERIS_CA_FILE}; } >> "$f" || continue`,
593
+ ` n=$((n+1))`,
594
+ ` echo "${BUNDLED_CA_PATCHED_MARKER}$f"`,
595
+ `done`,
596
+ // Silence is the common case and it reads as "did that even run?", so say
597
+ // so. patchBundledCas() keeps only the lines above, so this costs it
598
+ // nothing.
599
+ `if [ "$n" -gt 0 ]; then echo "$n bundled CA file(s) patched"`,
600
+ `else echo "no bundled CA file needed patching \u2014 no SDK that ships its own is installed here, or they already trust the Veris CA"; fi`,
601
+ `}`
602
+ ].join("\n");
603
+ }
604
+ var NODE_TRUST_FLAG = "--use-openssl-ca";
605
+ function nodeOptionsWithTrust(existing) {
606
+ return appendFlag(existing, NODE_TRUST_FLAG);
607
+ }
608
+ var NODE_PROXY_PRELOAD = "/tmp/veris-node-proxy.cjs";
609
+ var NODE_PROXY_FLAG = `--require ${NODE_PROXY_PRELOAD}`;
610
+ function nodeProxyPreloadScript() {
611
+ return [
612
+ `// Preloaded by @veris-ai/daytona through NODE_OPTIONS=--require.`,
613
+ `// Gives every http(s).Agent the proxy environment, which Node applies only`,
614
+ `// to its global agents under NODE_USE_ENV_PROXY=1. An SDK that builds its own`,
615
+ `// Agent would otherwise dial the vendor directly, which this sandbox cannot.`,
616
+ `for (const mod of [require("http"), require("https")]) {`,
617
+ ` const Base = mod.Agent`,
618
+ ` mod.Agent = class Agent extends Base {`,
619
+ ` constructor(options) {`,
620
+ ` super({ proxyEnv: process.env, ...(options || {}) })`,
621
+ ` }`,
622
+ ` }`,
623
+ `}`,
624
+ ``
625
+ ].join("\n");
626
+ }
627
+ function nodeOptionsWithProxy(existing) {
628
+ return appendFlag(existing, NODE_PROXY_FLAG);
629
+ }
630
+ function verisNodeOptions(existing, opts = {}) {
631
+ const withProxy = nodeOptionsWithProxy(existing);
632
+ return opts.trust === false ? withProxy : nodeOptionsWithTrust(withProxy);
633
+ }
634
+ function appendFlag(existing, flag) {
635
+ const base = (existing ?? "").trim();
636
+ if (base === flag || base.startsWith(`${flag} `) || base.endsWith(` ${flag}`) || base.includes(` ${flag} `)) return base;
637
+ return base ? `${base} ${flag}` : flag;
638
+ }
427
639
  var CA_INSTALL_CMD = [
428
640
  "update-ca-certificates",
429
641
  `(keytool -importcert -noprompt -cacerts -storepass changeit -alias veris -file ${CA_CERT_PATH} 2>/dev/null || true)`,
@@ -458,6 +670,16 @@ function gatewayProxyUrl(credential) {
458
670
  }
459
671
  return `http://${encodeURIComponent(credential.username)}:x@${credential.connect_address}`;
460
672
  }
673
+ async function gatewayIps(credential, proxyUrl) {
674
+ if (credential.gateway_ips?.length) return credential.gateway_ips;
675
+ const host = new URL(proxyUrl).hostname;
676
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) return [host];
677
+ try {
678
+ return await import_node_dns.promises.resolve4(host);
679
+ } catch {
680
+ return [];
681
+ }
682
+ }
461
683
  function assertProxyUrl(raw) {
462
684
  let u;
463
685
  try {
@@ -484,6 +706,8 @@ function assertProxyUrl(raw) {
484
706
  }
485
707
  async function installCa(sandbox, caPem) {
486
708
  await sandbox.fs.uploadFile(Buffer.from(caPem, "utf8"), VERIS_CA_FILE);
709
+ await sandbox.fs.uploadFile(Buffer.from(bundledCaPatchScript(), "utf8"), BUNDLED_CA_PATCH_SCRIPT);
710
+ await installNodeProxyPreload(sandbox);
487
711
  const script = [
488
712
  `chmod 0644 ${VERIS_CA_FILE}`,
489
713
  // Public roots first so they keep working; ours appended. `cat` of a
@@ -509,6 +733,13 @@ async function installCa(sandbox, caPem) {
509
733
  );
510
734
  }
511
735
  }
736
+ async function installNodeProxyPreload(sandbox) {
737
+ await sandbox.fs.uploadFile(Buffer.from(nodeProxyPreloadScript(), "utf8"), NODE_PROXY_PRELOAD);
738
+ }
739
+ async function patchBundledCas(sandbox) {
740
+ const r = await sh(sandbox, `sh ${BUNDLED_CA_PATCH_SCRIPT}`, 180).catch((e) => ({ exitCode: 1, result: String(e) }));
741
+ return (r.result ?? "").split("\n").filter((line) => line.startsWith(BUNDLED_CA_PATCHED_MARKER)).map((line) => line.slice(BUNDLED_CA_PATCHED_MARKER.length).trim()).filter(Boolean);
742
+ }
512
743
  async function probeCanary(sandbox, canaryHost, expectedTwinId) {
513
744
  if (!HOSTNAME_RE.test(canaryHost)) {
514
745
  throw new ReceiptIntegrityError(
@@ -562,9 +793,15 @@ var VerisApiImpl = class {
562
793
  this.ctx = ctx;
563
794
  }
564
795
  ctx;
796
+ get ownsTwin() {
797
+ return this.ctx.ownsTwin;
798
+ }
565
799
  get sandboxId() {
566
800
  return this.ctx.twinId;
567
801
  }
802
+ get environmentId() {
803
+ return this.ctx.environmentId;
804
+ }
568
805
  get mode() {
569
806
  return "gateway";
570
807
  }
@@ -590,10 +827,13 @@ var VerisApiImpl = class {
590
827
  }
591
828
  async receipt(service) {
592
829
  await probeCanary(this.ctx.sandbox, this.ctx.canaryHost, this.ctx.twinId);
593
- if (service !== void 0) return fetchReceiptEntry(await this.resolveService(service));
830
+ if (service !== void 0) {
831
+ const svc = await this.resolveService(service);
832
+ return fetchReceiptEntry(svc, this.watermark(svc.name));
833
+ }
594
834
  const services = await this.services();
595
835
  const entries = await Promise.all(
596
- services.filter((s) => isHttpUrl(s.control_url)).map(async (svc) => [svc.name, await fetchReceiptEntry(svc)])
836
+ services.filter((s) => isHttpUrl(s.control_url)).map(async (svc) => [svc.name, await fetchReceiptEntry(svc, this.watermark(svc.name))])
597
837
  );
598
838
  return {
599
839
  services: Object.fromEntries(entries),
@@ -609,10 +849,45 @@ var VerisApiImpl = class {
609
849
  leaks() {
610
850
  return ["udp-quic-possible", "ech-possible"];
611
851
  }
852
+ /** Where this service's log was when the run began. 0 reads all of it. */
853
+ watermark(service) {
854
+ return this.ctx.watermarks?.[service] ?? 0;
855
+ }
856
+ async verifyIntegrity() {
857
+ await probeCanary(this.ctx.sandbox, this.ctx.canaryHost, this.ctx.twinId);
858
+ }
859
+ async receiptBaseline() {
860
+ await this.verifyIntegrity();
861
+ return captureBaseline(
862
+ this.ctx.twinId,
863
+ this.ctx.sandbox.id,
864
+ (await this.services()).filter((s) => isHttpUrl(s.control_url))
865
+ );
866
+ }
867
+ async receiptSince(baseline, service) {
868
+ await this.verifyIntegrity();
869
+ const services = (await this.services()).filter((s) => isHttpUrl(s.control_url));
870
+ if (service !== void 0 && !services.some((s) => s.name === service)) {
871
+ throw new VerisError(`unknown HTTP service '${service}'`, { phase: "receipt" });
872
+ }
873
+ await validateBaseline(baseline, this.ctx.twinId, this.ctx.sandbox.id, services);
874
+ const selected = service === void 0 ? services : services.filter((s) => s.name === service);
875
+ const entries = await Promise.all(selected.map(async (svc) => [svc.name, await fetchReceiptEntry(svc, baseline.services[svc.name].id)]));
876
+ await validateBaseline(baseline, this.ctx.twinId, this.ctx.sandbox.id, await this.services().then((s) => s.filter((v) => isHttpUrl(v.control_url))));
877
+ return { services: Object.fromEntries(entries), mode: "gateway", integrity: "verified", leaks: this.leaks() };
878
+ }
879
+ async control(service, resource, options) {
880
+ const svc = (await this.services()).find((s) => s.name === service);
881
+ if (!svc) throw new VerisError(`unknown service '${service}'`);
882
+ return serviceControl(svc, resource, options);
883
+ }
612
884
  async assertTouched(service, match) {
613
885
  const entry = await this.receipt(service);
614
886
  const need = match?.minRequests ?? 1;
615
887
  const matched = match ? entry.entries.filter((r) => (match.method === void 0 || r.method.toUpperCase() === match.method.toUpperCase()) && (match.path === void 0 || r.path.includes(match.path))) : entry.entries;
888
+ if (matched.length < need && entry.capped) {
889
+ throw new VerisError(`receipt for '${service}' is incomplete; insufficient evidence`, { phase: "receipt" });
890
+ }
616
891
  if (matched.length < need) {
617
892
  const what = match ? `matching ${match.method ?? "ANY"} ${match.path ?? "*"} (${matched.length}/${need})` : "any intercepted requests";
618
893
  throw new VerisUntouchedError(
@@ -629,6 +904,36 @@ var VerisApiImpl = class {
629
904
  getTrustEnv() {
630
905
  return vendoredTrustEnv();
631
906
  }
907
+ /**
908
+ * The same variables as a shell prelude, for a caller that has no env map to
909
+ * fill — anything running a command through a session or a shell it did not
910
+ * build the environment for. Daytona resets SSL_CERT_FILE,
911
+ * REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE and NODE_EXTRA_CA_CERTS to its own CA
912
+ * file inside the sandbox, and that file cannot verify the gateway's leaf, so
913
+ * a command that inherits them fails on certificate validation:
914
+ * `uv sync` dies with "invalid peer certificate: UnknownIssuer".
915
+ *
916
+ * sbx.process.executeCommand(`${sbx.veris.trustPrelude()} uv sync`)
917
+ */
918
+ trustPrelude() {
919
+ return trustPrelude(this.getTrustEnv());
920
+ }
921
+ /**
922
+ * Append the Veris CA to the CA bundles the code under test's own SDKs ship.
923
+ *
924
+ * The variables above reach every client that reads one. stripe-python does
925
+ * not: it passes `verify=stripe.ca_bundle_path`, so the first Stripe call
926
+ * fails with "Could not verify Stripe's SSL certificate" in a sandbox where
927
+ * curl, Node and `requests` all succeed. This patches the file itself, which
928
+ * is the Daytona-shaped version of the veris CLI's --patch-bundled-cas.
929
+ *
930
+ * Call it AFTER installing dependencies — the bundles arrive with them, so
931
+ * there is nothing to patch at create time. Safe to call again; it returns
932
+ * only the files it actually changed.
933
+ */
934
+ patchBundledCas() {
935
+ return patchBundledCas(this.ctx.sandbox);
936
+ }
632
937
  async deliverTo(target, opts = {}) {
633
938
  const url = typeof target === "number" ? (await this.ctx.sandbox.getPreviewLink(target)).url : target;
634
939
  await this.ctx.controlPlane.updateSandbox(
@@ -660,8 +965,70 @@ var VerisApiImpl = class {
660
965
  }
661
966
  };
662
967
 
968
+ // src/profile.ts
969
+ var import_node_fs = require("fs");
970
+ var import_node_os = require("os");
971
+ var import_node_path = require("path");
972
+ var import_yaml = require("yaml");
973
+ var DEFAULT_API_BASE = "https://svc.api.veris.ai";
974
+ function profilePath(home = (0, import_node_os.homedir)()) {
975
+ return (0, import_node_path.join)(home, ".veris", "twin.yaml");
976
+ }
977
+ function readOrUndefined(path) {
978
+ try {
979
+ return (0, import_node_fs.readFileSync)(path, "utf8");
980
+ } catch (e) {
981
+ if (e.code === "ENOENT") return void 0;
982
+ throw e;
983
+ }
984
+ }
985
+ function parseProfileFile(text, path) {
986
+ if (text === void 0) return void 0;
987
+ let parsed;
988
+ try {
989
+ parsed = (0, import_yaml.parse)(text);
990
+ } catch (cause) {
991
+ throw new VerisError(`${path} is not valid YAML`, { phase: "credentials", cause });
992
+ }
993
+ if (parsed === null || parsed === void 0) return {};
994
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
995
+ throw new VerisError(`${path} is not a profile file (expected a mapping)`, { phase: "credentials" });
996
+ }
997
+ return parsed;
998
+ }
999
+ function selectProfileName(env, file) {
1000
+ return env.VERIS_PROFILE || file?.active_profile || "default";
1001
+ }
1002
+ function resolveVerisCredentials(opts = {}) {
1003
+ const env = opts.env ?? process.env;
1004
+ const path = opts.path ?? profilePath();
1005
+ const file = parseProfileFile((opts.readFile ?? readOrUndefined)(path), path);
1006
+ const name = selectProfileName(env, file);
1007
+ const profile = file?.profiles?.[name] ?? void 0;
1008
+ const found = profile !== void 0 && profile !== null;
1009
+ const apiKey = opts.apiKey || env.VERIS_API_KEY || (found ? profile.api_key : void 0) || void 0;
1010
+ const keySource = opts.apiKey ? "option" : env.VERIS_API_KEY ? "env" : apiKey ? "profile" : "none";
1011
+ const profileBase = found ? profile.api_base : void 0;
1012
+ const apiBase = (opts.apiBase || env.VERIS_API_BASE || profileBase || DEFAULT_API_BASE).replace(/\/$/, "");
1013
+ const baseSource = opts.apiBase ? "option" : env.VERIS_API_BASE ? "env" : profileBase ? "profile" : "default";
1014
+ return { apiKey, apiBase, keySource, baseSource, profile: { name, path, found } };
1015
+ }
1016
+ function missingKeyWhere(c) {
1017
+ const where = c.profile.found ? `profile '${c.profile.name}' in ${c.profile.path} has no api_key` : `${c.profile.path} has no profile '${c.profile.name}'`;
1018
+ return `VERIS_API_KEY is not set, and ${where}`;
1019
+ }
1020
+ var KEY_SOURCES_HINT = "Run `veris login` (the veris CLI saves the key there; `--profile <name>` for another plane, and VERIS_PROFILE picks it here), or set VERIS_API_KEY in the environment. Get a key at https://studio.veris.ai";
1021
+ function missingKeyMessage(c) {
1022
+ return `no Veris API key: ${missingKeyWhere(c)}. ${KEY_SOURCES_HINT}`;
1023
+ }
1024
+ function requireVerisCredentials(opts = {}) {
1025
+ const c = resolveVerisCredentials(opts);
1026
+ if (!c.apiKey) throw new MissingCredentialsError(missingKeyMessage(c), { phase: "credentials" });
1027
+ return c;
1028
+ }
1029
+
663
1030
  // src/version.ts
664
- var SDK_VERSION = true ? "0.2.1" : "0.0.0-dev";
1031
+ var SDK_VERSION = true ? "0.3.1" : "0.0.0-dev";
665
1032
 
666
1033
  // src/daytona.ts
667
1034
  var LABEL = {
@@ -671,12 +1038,24 @@ var LABEL = {
671
1038
  mode: "veris_mode",
672
1039
  egress: "veris_egress",
673
1040
  ownsTwin: "veris_owns_twin",
674
- canaryHost: "veris_canary_host"
1041
+ canaryHost: "veris_canary_host",
1042
+ /** Unique to ONE create() call, and the only way back to a sandbox whose
1043
+ * create threw before it could hand the object over — see reapFailedCreate.
1044
+ * The twin id will not do: an attached twin is shared by every sandbox on
1045
+ * it, and deleting the wrong one of those would be worse than the leak. */
1046
+ createId: "veris_create_id"
675
1047
  };
676
1048
  var VERIS_LABEL_KEYS = Object.values(LABEL);
677
1049
  function isVerisSandbox(sbx) {
678
1050
  return typeof sbx.verisSandboxId === "string";
679
1051
  }
1052
+ function verisTwinId(sandbox) {
1053
+ return sandbox.labels?.[LABEL.twinId];
1054
+ }
1055
+ function verisOwnsTwin(sandbox) {
1056
+ const labels = sandbox.labels ?? {};
1057
+ return labels[LABEL.twinId] !== void 0 && labels[LABEL.ownsTwin] !== "false";
1058
+ }
680
1059
  var Daytona = class extends import_sdk.Daytona {
681
1060
  verisDefaults;
682
1061
  constructor(config) {
@@ -696,6 +1075,7 @@ var Daytona = class extends import_sdk.Daytona {
696
1075
  const egress = v.egress ?? "strict";
697
1076
  const ttlMinutes = v.ttlMinutes ?? 60;
698
1077
  const ownsTwin = !v.attachSandboxId;
1078
+ const createId = crypto.randomUUID();
699
1079
  const twin = await this.provisionTwin(controlPlane, v, coords, ttlMinutes);
700
1080
  const cleanupTwin = async () => {
701
1081
  if (ownsTwin) await controlPlane.deleteTwin(twin.environment_id, twin.id).catch(() => {
@@ -703,6 +1083,8 @@ var Daytona = class extends import_sdk.Daytona {
703
1083
  };
704
1084
  let sandbox;
705
1085
  let credential;
1086
+ let watermarks = {};
1087
+ let daytonaAsked = false;
706
1088
  try {
707
1089
  credential = await controlPlane.mintEgressCredential(twin.environment_id, twin.id);
708
1090
  if (!credential) {
@@ -719,17 +1101,22 @@ var Daytona = class extends import_sdk.Daytona {
719
1101
  }
720
1102
  const services = twin.services?.length ? twin.services : await controlPlane.services(twin.id);
721
1103
  const proxyUrl = gatewayProxyUrl(credential);
722
- const network = buildNetwork({
723
- services,
724
- mode: egress,
725
- // The gateway has to be reachable or nothing is: taken from the URL we
726
- // are actually going to use, so the two can never disagree.
727
- gatewayHosts: [new URL(proxyUrl).hostname, credential.canary_host].filter(Boolean),
728
- allowOut: v.allowOut,
729
- allowRegistries: v.allowRegistries
730
- });
1104
+ const network = buildNetwork({ mode: egress, gatewayIps: await gatewayIps(credential, proxyUrl) });
1105
+ watermarks = await readWatermarks(services);
731
1106
  const verisManaged = {
732
1107
  ...v.installCa !== false ? sanitizeTrustEnv(void 0) : {},
1108
+ // Node reads none of the variables above once Daytona has overwritten
1109
+ // them; the trust flag makes it read OpenSSL's store, which the CA
1110
+ // install fills. The preload gives every http(s).Agent the proxy
1111
+ // environment, which NODE_USE_ENV_PROXY below reaches only for the
1112
+ // global ones. Appended, not replaced: a caller's own NODE_OPTIONS keep
1113
+ // working.
1114
+ NODE_OPTIONS: verisNodeOptions(rest.envVars?.NODE_OPTIONS, { trust: v.installCa !== false }),
1115
+ // Node ignores HTTPS_PROXY unless told to, and Daytona blocks anything
1116
+ // that dials out directly — so without this every plain `https.get`
1117
+ // and `fetch` in Node 24+ dies with ECONNRESET instead of reaching
1118
+ // the gateway. Older Node needs a proxy agent; the variable is inert there.
1119
+ NODE_USE_ENV_PROXY: "1",
733
1120
  ...v.dataPlaneEnv !== false ? dataPlaneEnv(services) : {},
734
1121
  VERIS_SANDBOX_ID: twin.id
735
1122
  };
@@ -744,27 +1131,31 @@ var Daytona = class extends import_sdk.Daytona {
744
1131
  [LABEL.egress]: egress,
745
1132
  [LABEL.ownsTwin]: String(ownsTwin),
746
1133
  [LABEL.mode]: "gateway",
747
- [LABEL.canaryHost]: credential.canary_host
1134
+ [LABEL.canaryHost]: credential.canary_host,
1135
+ [LABEL.createId]: createId
748
1136
  },
749
- ...network,
750
- // 3. Where Daytona forwards everything the allowlist permits. Chained,
751
- // not advisory: an unreachable gateway makes allowed traffic 502
752
- // rather than quietly going direct.
1137
+ ...network.params,
1138
+ // 3. Where Daytona forwards everything. Chained, not advisory: an
1139
+ // unreachable gateway makes traffic 502 rather than quietly going
1140
+ // direct.
753
1141
  outboundProxyUrl: proxyUrl,
754
1142
  ttlMinutes: rest.ttlMinutes ?? ttlMinutes
755
1143
  };
1144
+ daytonaAsked = true;
756
1145
  sandbox = await this.baseCreate(createParams, options);
757
1146
  } catch (cause) {
1147
+ const failed = daytonaAsked ? await reapFailedCreate((labels) => super.list({ labels }), createId) : void 0;
758
1148
  await cleanupTwin();
759
1149
  if (cause instanceof VerisError) throw cause;
760
- throw new VerisError("Daytona sandbox create failed", {
1150
+ throw new VerisError(sandboxCreateMessage(cause, failed, rest), {
761
1151
  phase: "sandbox-create",
762
1152
  verisSandboxId: twin.id,
763
1153
  cause
764
1154
  });
765
1155
  }
766
1156
  try {
767
- await installCa(sandbox, credential.ca_pem);
1157
+ if (v.installCa !== false) await installCa(sandbox, credential.ca_pem);
1158
+ else await installNodeProxyPreload(sandbox);
768
1159
  await probeCanary(sandbox, credential.canary_host, twin.id);
769
1160
  } catch (err) {
770
1161
  await sandbox.delete().catch(() => {
@@ -778,7 +1169,8 @@ var Daytona = class extends import_sdk.Daytona {
778
1169
  twinId: twin.id,
779
1170
  egress,
780
1171
  ownsTwin,
781
- canaryHost: credential.canary_host
1172
+ canaryHost: credential.canary_host,
1173
+ watermarks
782
1174
  });
783
1175
  }
784
1176
  /**
@@ -810,9 +1202,10 @@ var Daytona = class extends import_sdk.Daytona {
810
1202
  const labels = sandbox.labels ?? {};
811
1203
  const twinId = labels[LABEL.twinId];
812
1204
  if (!twinId) return sandbox;
813
- const apiKey = this.verisDefaults.apiKey ?? process.env.VERIS_API_KEY;
1205
+ const creds = resolveVerisCredentials({ apiKey: this.verisDefaults.apiKey, apiBase: this.verisDefaults.apiBase });
1206
+ const apiKey = creds.apiKey;
814
1207
  if (!apiKey) return sandbox;
815
- const trustedBase = this.verisDefaults.apiBase ?? process.env.VERIS_API_BASE;
1208
+ const trustedBase = creds.baseSource === "default" ? void 0 : creds.apiBase;
816
1209
  const labelBase = labels[LABEL.apiBase];
817
1210
  if (trustedBase && labelBase && labelBase !== trustedBase) {
818
1211
  throw new VerisError(
@@ -894,19 +1287,48 @@ var Daytona = class extends import_sdk.Daytona {
894
1287
  };
895
1288
  var daytona_default = Daytona;
896
1289
  function resolveCoordinates(v) {
897
- const apiKey = v.apiKey ?? process.env.VERIS_API_KEY;
898
- if (!apiKey) {
899
- throw new MissingCredentialsError(
900
- "no Veris API key: set VERIS_API_KEY in your environment, or pass veris.apiKey. Get one at https://studio.veris.ai",
901
- { phase: "credentials" }
902
- );
903
- }
1290
+ const creds = requireVerisCredentials({ apiKey: v.apiKey, apiBase: v.apiBase });
904
1291
  return {
905
- apiKey,
1292
+ apiKey: creds.apiKey,
906
1293
  environmentId: v.environmentId ?? process.env.VERIS_ENVIRONMENT_ID,
907
- apiBase: (v.apiBase ?? process.env.VERIS_API_BASE ?? "https://svc.api.veris.ai").replace(/\/$/, "")
1294
+ apiBase: creds.apiBase
908
1295
  };
909
1296
  }
1297
+ async function reapFailedCreate(list, createId) {
1298
+ try {
1299
+ for await (const sandbox of list({ [LABEL.createId]: createId })) {
1300
+ if (sandbox.labels?.[LABEL.createId] !== createId) continue;
1301
+ if (!sandbox.errorReason) await sandbox.refreshData().catch(() => {
1302
+ });
1303
+ const found = { id: sandbox.id, state: sandbox.state, errorReason: sandbox.errorReason };
1304
+ return { ...found, deleted: await sandbox.delete().then(() => true, () => false) };
1305
+ }
1306
+ } catch {
1307
+ }
1308
+ return void 0;
1309
+ }
1310
+ function sandboxCreateMessage(cause, failed, params = {}) {
1311
+ const base = "Daytona sandbox create failed";
1312
+ if (!failed) return base;
1313
+ const reason = failed.errorReason?.trim().replace(/[.\s]+$/, "");
1314
+ const parts = [reason ? `${base}: ${reason}.` : `${base}.`];
1315
+ const image = typeof params.image === "string" ? params.image : void 0;
1316
+ if (image && failed.state === "build_failed") {
1317
+ parts.push(
1318
+ `Daytona builds "${image}" on its own servers and pulls it from a registry, so an image that exists only in your local Docker daemon is not one it can see.`
1319
+ );
1320
+ }
1321
+ parts.push(failed.deleted ? `The half-built sandbox ${failed.id} was deleted.` : `The half-built sandbox ${failed.id} could not be deleted \u2014 remove it with \`veris-daytona teardown ${failed.id}\`.`);
1322
+ const raw = cause instanceof Error ? cause.message : "";
1323
+ if (raw) parts.push(`(Daytona SDK: ${raw})`);
1324
+ return parts.join(" ");
1325
+ }
1326
+ async function readWatermarks(services) {
1327
+ const marks = await Promise.all(
1328
+ services.filter((s) => isHttpUrl(s.control_url)).map(async (svc) => [svc.name, await fetchWatermark(svc).catch(() => 0)])
1329
+ );
1330
+ return Object.fromEntries(marks);
1331
+ }
910
1332
  function stripVeris(params) {
911
1333
  const { veris: _veris, ...rest } = params ?? {};
912
1334
  return rest;
@@ -918,13 +1340,86 @@ function reserveLabels(labels) {
918
1340
  }
919
1341
  return out;
920
1342
  }
1343
+
1344
+ // src/daytona-key.ts
1345
+ var DAYTONA_API_URL = "https://app.daytona.io/api";
1346
+ var DELETE_SANDBOXES = "delete:sandboxes";
1347
+ function daytonaApiUrl(env = process.env) {
1348
+ return (env.DAYTONA_API_URL || env.DAYTONA_SERVER_URL || DAYTONA_API_URL).replace(/\/$/, "");
1349
+ }
1350
+ async function fetchDaytonaKey(apiKey, apiUrl = daytonaApiUrl()) {
1351
+ try {
1352
+ const res = await fetch(`${apiUrl}/api-keys/current`, {
1353
+ headers: { Authorization: `Bearer ${apiKey}` }
1354
+ });
1355
+ if (!res.ok) return void 0;
1356
+ const body = await res.json();
1357
+ if (!Array.isArray(body.permissions)) return void 0;
1358
+ return {
1359
+ name: typeof body.name === "string" ? body.name : "",
1360
+ permissions: body.permissions.filter((p) => typeof p === "string")
1361
+ };
1362
+ } catch {
1363
+ return void 0;
1364
+ }
1365
+ }
1366
+ function canDeleteSandboxes(key) {
1367
+ return key === void 0 ? void 0 : key.permissions.includes(DELETE_SANDBOXES);
1368
+ }
1369
+ function describeKey(key) {
1370
+ const name = key.name ? `"${key.name}" ` : "";
1371
+ return `${name}(permissions: ${key.permissions.join(", ") || "none"})`;
1372
+ }
1373
+ var DELETE_PERMISSION_FIX = 'create a key with the "delete sandboxes" permission at https://app.daytona.io/dashboard/keys';
1374
+ function cannotTeardownWarning(key, autoStopMinutes, autoDeleteMinutes) {
1375
+ return `DAYTONA_API_KEY ${describeKey(key)} lacks \`${DELETE_SANDBOXES}\`, so \`veris-daytona teardown\` will be refused for the sandbox about to be created, and \`run\` cannot delete its own. The box still stops after ${autoStopMinutes} idle minutes and is deleted ${autoDeleteMinutes} minutes after it stops. For a teardown that works, ${DELETE_PERMISSION_FIX} and use it instead.`;
1376
+ }
1377
+
1378
+ // src/teardown.ts
1379
+ var TEARDOWN_USAGE = `usage: veris-daytona teardown <daytona-sandbox-id>
1380
+
1381
+ Deletes the Daytona sandbox. The id is the one \`veris-daytona provision\`
1382
+ printed as daytonaSandboxId \u2014 not the Veris twin's id.
1383
+
1384
+ The twin follows the rule it was created under: one this package created is
1385
+ deleted with the sandbox, one it attached to (\`provision\`, or \`run --sandbox\`)
1386
+ is yours and is left running. Which of the two happened is printed.
1387
+
1388
+ needs: DAYTONA_API_KEY with the ${DELETE_SANDBOXES} permission \u2014 a key made with
1389
+ "write sandboxes" alone is refused, and the refusal says when Daytona will stop
1390
+ and delete the box on its own. To reach a twin that must be deleted, a Veris
1391
+ key too: VERIS_API_KEY, or the profile \`veris login\` saved.
1392
+
1393
+ exit code: 0 when the sandbox is deleted; 1 when there is no such sandbox, or
1394
+ the key may not delete one.`;
1395
+ function isPermissionDenied(e) {
1396
+ return typeof e === "object" && e !== null && e.statusCode === 403;
1397
+ }
1398
+ function sandboxBrakes(r) {
1399
+ const stop = r.autoStopMinutes && r.autoStopMinutes > 0 ? `stops after ${r.autoStopMinutes} idle minutes` : "never stops on its own";
1400
+ const del = r.autoDeleteMinutes === void 0 || r.autoDeleteMinutes < 0 ? "is never auto-deleted" : r.autoDeleteMinutes === 0 ? "is deleted as soon as it stops" : `is deleted ${r.autoDeleteMinutes} minutes after it stops`;
1401
+ const destroy = r.expiresAt ? `, and is destroyed at ${r.expiresAt} whatever state it is in` : "";
1402
+ return `${stop} and ${del}${destroy}`;
1403
+ }
1404
+ function teardownRefusedMessage(r) {
1405
+ const key = r.key ? `this DAYTONA_API_KEY ${describeKey(r.key)} lacks the \`${DELETE_SANDBOXES}\` permission` : `Daytona answered 403 (Access denied), which is what a DAYTONA_API_KEY without the \`${DELETE_SANDBOXES}\` permission gets \u2014 the key's own record could not be read to confirm it`;
1406
+ const twin = r.twinId === void 0 ? "" : r.ownsTwin ? ` Its twin ${r.twinId} was deleted before Daytona refused, so nothing is left on the Veris side.` : ` Twin ${r.twinId} is yours and was not touched.`;
1407
+ return `Daytona refused to delete sandbox ${r.sandboxId}: ${key}. The sandbox is still there: it ${sandboxBrakes(r)}.${twin} To delete it sooner, ${DELETE_PERMISSION_FIX}, then run \`veris-daytona teardown ${r.sandboxId}\` with it \u2014 or delete the sandbox in the Daytona dashboard.`;
1408
+ }
921
1409
  // Annotate the CommonJS export names for ESM import in node:
922
1410
  0 && (module.exports = {
1411
+ BUNDLED_CA_FILES,
1412
+ BUNDLED_CA_PATCH_SCRIPT,
923
1413
  CA_CERT_PATH,
924
1414
  ControlPlane,
925
- DEFAULT_REGISTRY_HOSTS,
1415
+ DAYTONA_API_URL,
1416
+ DEFAULT_API_BASE,
1417
+ DELETE_SANDBOXES,
926
1418
  Daytona,
927
1419
  MissingCredentialsError,
1420
+ NODE_PROXY_FLAG,
1421
+ NODE_PROXY_PRELOAD,
1422
+ NODE_TRUST_FLAG,
928
1423
  ReceiptIntegrityError,
929
1424
  SDK_VERSION,
930
1425
  SYSTEM_BUNDLE,
@@ -938,13 +1433,35 @@ function reserveLabels(labels) {
938
1433
  VerisGatewayUnreachableError,
939
1434
  VerisUntouchedError,
940
1435
  assertHttpControlPlane,
1436
+ buildNetwork,
1437
+ canDeleteSandboxes,
1438
+ cannotTeardownWarning,
941
1439
  dataPlaneHosts,
1440
+ daytonaApiUrl,
1441
+ directTwinHosts,
1442
+ fetchDaytonaKey,
942
1443
  fetchManual,
1444
+ gatewayIps,
943
1445
  gatewayProxyUrl,
1446
+ isPermissionDenied,
944
1447
  isVerisSandbox,
1448
+ missingKeyMessage,
1449
+ nodeOptionsWithProxy,
1450
+ nodeOptionsWithTrust,
1451
+ nodeProxyPreloadScript,
1452
+ patchBundledCas,
1453
+ profilePath,
1454
+ requireVerisCredentials,
1455
+ resolveVerisCredentials,
1456
+ sandboxBrakes,
1457
+ teardownRefusedMessage,
1458
+ trustPrelude,
945
1459
  twinHosts,
946
1460
  vendorHosts,
947
1461
  vendoredTrustEnv,
1462
+ verisNodeOptions,
1463
+ verisOwnsTwin,
1464
+ verisTwinId,
948
1465
  ...require("@daytona/sdk")
949
1466
  });
950
1467
  //# sourceMappingURL=index.cjs.map