@arker-ai/sdk 0.6.2 → 0.7.0

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/cli.js CHANGED
@@ -3,14 +3,22 @@ import {
3
3
  ARKER_ORG_ID,
4
4
  Arker,
5
5
  ArkerError
6
- } from "./chunk-7BHPVQNG.js";
6
+ } from "./chunk-4NHUAVXK.js";
7
7
 
8
8
  // src/cli.ts
9
9
  import { readFileSync, existsSync } from "fs";
10
10
  import { spawnSync } from "child_process";
11
+ import { createRequire } from "module";
11
12
  import { homedir } from "os";
12
13
  import { join } from "path";
13
14
  import { stdin as input, stdout as output } from "process";
15
+ var VERSION = (() => {
16
+ try {
17
+ return createRequire(import.meta.url)("../package.json").version;
18
+ } catch {
19
+ return "unknown";
20
+ }
21
+ })();
14
22
  function parseArgs(argv) {
15
23
  const positional = [];
16
24
  const flags = {};
@@ -49,6 +57,7 @@ function readFileConfig() {
49
57
  }
50
58
  return {};
51
59
  }
60
+ var DEFAULT_REGION = "us-west-2";
52
61
  function clientFromArgs(args) {
53
62
  const file = readFileConfig();
54
63
  const explicitBaseUrl = args.flags["base-url"] ?? process.env.ARKER_BASE_URL;
@@ -56,14 +65,11 @@ function clientFromArgs(args) {
56
65
  const apiKey = args.flags["api-key"] ?? process.env.ARKER_API_KEY ?? file.apiKey;
57
66
  const baseUrl = explicitBaseUrl ?? (explicitRegion ? void 0 : file.baseUrl);
58
67
  const controlBaseUrl = args.flags["control-base-url"] ?? process.env.ARKER_CONTROL_BASE_URL;
59
- const region = explicitRegion ?? file.region;
68
+ const region = explicitRegion ?? file.region ?? (baseUrl ? void 0 : DEFAULT_REGION);
60
69
  const provider = args.flags.provider ?? process.env.ARKER_PROVIDER;
61
70
  if (!apiKey) {
62
71
  die("Missing API key. Set ARKER_API_KEY or pass --api-key.");
63
72
  }
64
- if (!baseUrl && !region) {
65
- die("Missing region. Set ARKER_REGION or pass --region (e.g. us-west-2). --provider (aws|aws-burst) defaults to aws.");
66
- }
67
73
  return new Arker({ apiKey, baseUrl, region, provider, controlBaseUrl });
68
74
  }
69
75
  function out(value) {
@@ -551,134 +557,6 @@ function bridgePty(pty, options) {
551
557
  });
552
558
  });
553
559
  }
554
- var SSH_PORT_DEFAULT = 22;
555
- function defaultIdentityBase() {
556
- return join(homedir(), ".ssh", "id_ed25519");
557
- }
558
- function resolveLocalSshKey(args) {
559
- const explicit = args.flags.identity;
560
- const privateKeyPath = explicit ? explicit.replace(/\.pub$/, "") : defaultIdentityBase();
561
- const publicKeyPath = `${privateKeyPath}.pub`;
562
- if (!existsSync(publicKeyPath)) {
563
- if (!boolFlag(args, "generate")) {
564
- die(
565
- `no SSH public key at ${publicKeyPath}. Pass --identity <path>, or --generate to create an ed25519 key pair.`
566
- );
567
- }
568
- err(`generating ed25519 key pair at ${privateKeyPath}`);
569
- const gen = spawnSync(
570
- "ssh-keygen",
571
- ["-t", "ed25519", "-N", "", "-f", privateKeyPath, "-C", "arker-cli"],
572
- { stdio: "inherit" }
573
- );
574
- if (gen.status !== 0) die("ssh-keygen failed to generate a key pair");
575
- }
576
- const publicKey = readFileSync(publicKeyPath, "utf8").trim();
577
- if (!publicKey) die(`SSH public key at ${publicKeyPath} is empty`);
578
- return { publicKey, privateKeyPath, publicKeyPath };
579
- }
580
- async function registerAccountSshKey(client, publicKey, label) {
581
- return client._request(
582
- "POST",
583
- "/v1/account/ssh-keys",
584
- { public_key: publicKey, label: label ?? null },
585
- client.baseUrl
586
- );
587
- }
588
- function resolveSshHost(args, client) {
589
- const explicit = args.flags.host ?? process.env.ARKER_SSH_HOST;
590
- if (explicit) return explicit;
591
- if (client.region) return `aws-${client.region}.arker.ai`;
592
- try {
593
- return new URL(client.baseUrl).hostname;
594
- } catch {
595
- die("could not determine SSH host; pass --host <hostname>");
596
- }
597
- }
598
- async function cmdSsh(args, client) {
599
- const vmId = args.flags["vm-id"] ?? args.positional[0];
600
- if (!vmId) {
601
- die(
602
- "usage: arker ssh <vm_id> [--identity <path>] [--generate] [--host <h>] [--port <n>] [--connect|-c] [--skip-register]"
603
- );
604
- }
605
- const { publicKey, privateKeyPath } = resolveLocalSshKey(args);
606
- if (!boolFlag(args, "skip-register")) {
607
- const label = args.flags.label ?? `arker-cli ${homedir().split("/").pop() ?? ""}`.trim();
608
- try {
609
- const key = await registerAccountSshKey(client, publicKey, label);
610
- err(`registered SSH key ${key.fingerprint}${key.label ? ` (${key.label})` : ""}`);
611
- } catch (e) {
612
- if (e instanceof ArkerError && e.status === 409) {
613
- die(`this SSH key is already registered to a different account: ${e.message}`);
614
- }
615
- if (e instanceof ArkerError && e.status === 403) {
616
- die(
617
- "your API key lacks the developer role required to register SSH keys. Register the key in the console, then re-run with --skip-register."
618
- );
619
- }
620
- throw e;
621
- }
622
- }
623
- const host = resolveSshHost(args, client);
624
- const port = numFlag(args, "port") ?? SSH_PORT_DEFAULT;
625
- const sshArgs = [
626
- "-i",
627
- privateKeyPath,
628
- ...port !== 22 ? ["-p", String(port)] : [],
629
- `${vmId}@${host}`
630
- ];
631
- const command = `ssh ${sshArgs.join(" ")}`;
632
- if (boolFlag(args, "connect") || boolFlag(args, "c")) {
633
- err(`connecting: ${command}`);
634
- const r = spawnSync("ssh", sshArgs, { stdio: "inherit" });
635
- process.exit(r.status ?? 1);
636
- }
637
- out(command);
638
- }
639
- async function cmdSshKeys(args, client) {
640
- const sub = args.positional[0];
641
- const rest = args.positional.slice(1);
642
- switch (sub) {
643
- case void 0:
644
- case "ls":
645
- case "list": {
646
- const res = await client._request(
647
- "GET",
648
- "/v1/account/ssh-keys",
649
- void 0,
650
- client.baseUrl
651
- );
652
- if (args.flags.json) return out(res);
653
- for (const k of res.keys) {
654
- out(`${k.id} ${k.fingerprint} ${k.label ?? "\u2014"}`);
655
- }
656
- return;
657
- }
658
- case "add": {
659
- const { publicKey } = resolveLocalSshKey(args);
660
- const label = args.flags.label;
661
- const key = await registerAccountSshKey(client, publicKey, label);
662
- if (args.flags.json) return out(key);
663
- out(`${key.id} ${key.fingerprint} ${key.label ?? "\u2014"}`);
664
- return;
665
- }
666
- case "rm":
667
- case "delete": {
668
- const id = rest[0] ?? die("usage: arker ssh-keys rm <key_id>");
669
- const r = await client._request(
670
- "DELETE",
671
- `/v1/account/ssh-keys/${encodeURIComponent(id)}`,
672
- void 0,
673
- client.baseUrl
674
- );
675
- out(r.deleted ? `deleted ${id}` : "delete failed");
676
- return;
677
- }
678
- default:
679
- die("usage: arker ssh-keys <ls|add|rm> ...");
680
- }
681
- }
682
560
  function numFlag(args, name) {
683
561
  const v = args.flags[name];
684
562
  if (typeof v === "string") return Number(v);
@@ -699,7 +577,7 @@ async function readAllStdin() {
699
577
  function usage() {
700
578
  out(
701
579
  [
702
- "arker \u2014 VM control plane CLI",
580
+ `arker v${VERSION}`,
703
581
  "",
704
582
  "Usage:",
705
583
  " arker <command> [args]",
@@ -716,8 +594,6 @@ function usage() {
716
594
  " arker run <vm> <command> [--session-id <id>] [--session-idx N] run a command",
717
595
  " arker resize <vm> [--memory-mib N] [--vcpu N] [--disk-mib N] resize a VM (PATCH)",
718
596
  " arker shell [vm_id] native PTY shell (forks ubuntu-full if no vm)",
719
- " arker ssh <vm_id> register your SSH key + print the ssh command",
720
- " arker ssh <vm_id> --connect register + drop straight into the ssh session",
721
597
  "",
722
598
  "Resources:",
723
599
  " arker vms <ls|get|rm|fork|run> ...",
@@ -725,12 +601,11 @@ function usage() {
725
601
  " arker sessions <ls|get|create|rm> <vm_id> ...",
726
602
  " arker syncs <ls|create|rm> <vm_id> ...",
727
603
  " arker filesystems <ls|create|get|rm> ... (alias: fs)",
728
- " arker ssh-keys <ls|add|rm> ... manage account SSH keys",
729
604
  "",
730
605
  "Flags:",
731
606
  " --api-key <key> (or env ARKER_API_KEY)",
732
607
  " --region <region> (or env ARKER_REGION; e.g. us-west-2)",
733
- " --provider <aws|aws-burst> (or env ARKER_PROVIDER; default aws)",
608
+ " --provider <aws> (or env ARKER_PROVIDER; default aws)",
734
609
  " --base-url <url> override compute URL (env ARKER_BASE_URL)",
735
610
  " --control-base-url <url> override CF Worker URL (env ARKER_CONTROL_BASE_URL)",
736
611
  " --json emit JSON instead of tabular output",
@@ -753,18 +628,7 @@ function usage() {
753
628
  " --session-id <id> reconnect to an existing PTY session",
754
629
  " --command <path> shell executable path (default: /bin/bash)",
755
630
  " --cols <n> --rows <n> initial terminal size",
756
- " --no-persist close the remote PTY process on disconnect",
757
- "",
758
- "SSH flags:",
759
- " --identity <path> local key (private or .pub); default ~/.ssh/id_ed25519",
760
- " --generate create an ed25519 key pair if none exists",
761
- " --host <hostname> SSH host (or env ARKER_SSH_HOST; default aws-<region>.arker.ai)",
762
- " --port <n> SSH port (default 22)",
763
- " --connect, -c exec ssh instead of just printing the command",
764
- " --skip-register don't register the key (assume already registered)",
765
- " --label <text> label for the registered key",
766
- "",
767
- `Arker org id: ${ARKER_ORG_ID}`
631
+ " --no-persist close the remote PTY process on disconnect"
768
632
  ].join("\n")
769
633
  );
770
634
  process.exit(2);
@@ -794,11 +658,10 @@ async function main() {
794
658
  return await cmdSyncs(args, client);
795
659
  case "shell":
796
660
  return await cmdShell(args, client);
797
- case "ssh":
798
- return await cmdSsh(args, client);
799
- case "ssh-keys":
800
- case "ssh_keys":
801
- return await cmdSshKeys(args, client);
661
+ // SSH is descoped/unsupported and hidden from the interface. The
662
+ // implementation below (cmdSsh / cmdSshKeys) is kept intact; re-add
663
+ // the `ssh` / `ssh-keys` cases here and their help entries to expose
664
+ // it once the server-side SSH path is supported.
802
665
  // Resources.
803
666
  case "vms":
804
667
  return await cmdVms(args, client);
package/dist/daytona.cjs CHANGED
@@ -162,6 +162,7 @@ var Arker = class {
162
162
  provider;
163
163
  apiKey;
164
164
  fetchImpl;
165
+ http2;
165
166
  retry;
166
167
  constructor(opts = {}) {
167
168
  const apiKey = opts.apiKey ?? env("ARKER_API_KEY") ?? env("AUTH_KEY");
@@ -183,6 +184,7 @@ var Arker = class {
183
184
  this.region = region ? normalizeRegion(region) : void 0;
184
185
  this.provider = effectiveProvider;
185
186
  this.fetchImpl = opts.fetch ?? globalThis.fetch;
187
+ this.http2 = opts.fetch === void 0;
186
188
  this.retry = normalizeRetry(opts.retry);
187
189
  if (!this.fetchImpl) throw new Error("fetch is required in this runtime");
188
190
  }
@@ -243,7 +245,10 @@ var Arker = class {
243
245
  egress: src.egress ?? null,
244
246
  disk: src.disk ?? true,
245
247
  durable: src.durable ?? null,
246
- resources
248
+ resources,
249
+ // ARK-125: omit to inherit the source's policy; pass a doc to override
250
+ // (an empty `{ policies: [] }` clears to allow-all, NOT inherit).
251
+ policies: src.policies ?? null
247
252
  };
248
253
  const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
249
254
  const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
@@ -338,30 +343,29 @@ var Arker = class {
338
343
  if (value !== void 0) headers[key] = value;
339
344
  }
340
345
  }
341
- const init = { method, headers };
346
+ let requestBody;
342
347
  if (body !== void 0) {
343
348
  headers["content-type"] = "application/json";
344
- init.body = JSON.stringify(withoutUndefined(body));
349
+ requestBody = JSON.stringify(withoutUndefined(body));
345
350
  }
346
351
  let lastStatus = 0;
347
352
  let lastText = "";
348
353
  let lastError;
349
354
  for (let attempt = 0; attempt < this.retry.attempts; attempt++) {
350
355
  try {
351
- const response = await this.fetchImpl(url, init);
352
- const text = await response.text();
356
+ const { status, ok, text } = await sendRequest(url, { method, headers, body: requestBody }, this.fetchImpl, this.http2);
353
357
  const payload = parseJson(text);
354
358
  const parsedError = extractError(payload);
355
- lastStatus = response.status;
359
+ lastStatus = status;
356
360
  lastText = text;
357
361
  lastError = parsedError;
358
- if (isRetryable(response.status, parsedError) && attempt < this.retry.attempts - 1) {
362
+ if (isRetryable(status, parsedError) && attempt < this.retry.attempts - 1) {
359
363
  await sleep(retryDelay(this.retry, attempt));
360
364
  continue;
361
365
  }
362
- if (parsedError) throw new ArkerError(parsedError.code, parsedError.message, response.status);
363
- if (!response.ok) {
364
- throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${response.status}`, response.status);
366
+ if (parsedError) throw new ArkerError(parsedError.code, parsedError.message, status);
367
+ if (!ok) {
368
+ throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${status}`, status);
365
369
  }
366
370
  return payload;
367
371
  } catch (error) {
@@ -573,6 +577,32 @@ var VM = class _VM {
573
577
  async delete() {
574
578
  return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
575
579
  }
580
+ // ── ARK-125 egress policy ────────────────────────────────────────
581
+ /**
582
+ * Read this VM's outbound egress policy document (ARK-125). Returns an
583
+ * empty doc (`{}`) when no policy is set.
584
+ */
585
+ async getPolicies() {
586
+ return this._client._request("GET", `${vmPath(this.id)}/policies`, void 0, this.baseUrl);
587
+ }
588
+ /**
589
+ * Replace this VM's outbound egress policy with `doc` — an ordered,
590
+ * first-match-wins rule list. An empty doc (`{}` or `{ policies: [] }`)
591
+ * clears the policy to allow-all. Returns the stored policy plus the
592
+ * domains it escalates to MITM and any degrade warnings.
593
+ *
594
+ * await vm.setPolicies({
595
+ * policies: [
596
+ * { type: "network.outbound",
597
+ * match: { domains: ["github.com"], ports: [443] },
598
+ * action: "allow" },
599
+ * { type: "network.outbound", action: "deny" },
600
+ * ],
601
+ * });
602
+ */
603
+ async setPolicies(doc) {
604
+ return this._client._request("PUT", `${vmPath(this.id)}/policies`, doc, this.baseUrl);
605
+ }
576
606
  // ── Syncs: bindings of a filesystem into this VM at a path ────────
577
607
  async listSyncs(opts = {}) {
578
608
  return this._client._request("GET", buildQuery(`${vmPath(this.id)}/syncs`, {
@@ -624,6 +654,20 @@ var VM = class _VM {
624
654
  async deleteSession(sessionId) {
625
655
  return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
626
656
  }
657
+ /**
658
+ * Update a session via `PATCH /v1/vms/{id}/sessions/{sid}`: resize its PTY
659
+ * (`cols`/`rows`) and/or set the idle `timeoutSecs`. Works whether or not a
660
+ * PTY is currently attached — the REST equivalent of {@link PtyConnection.resize}
661
+ * (which sends an in-band control frame on the live WebSocket).
662
+ */
663
+ async updateSession(sessionId, update) {
664
+ return this._client._request(
665
+ "PATCH",
666
+ `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`,
667
+ { cols: update.cols, rows: update.rows, timeout_secs: update.timeoutSecs },
668
+ this.baseUrl
669
+ );
670
+ }
627
671
  async connectPty(options = {}) {
628
672
  const sessionId = options.sessionId ?? sessionIdFrom(await this.createSession());
629
673
  const useTicket = options.useTicket ?? !isNodeRuntime();
@@ -1035,6 +1079,84 @@ function base64ToBytes(text) {
1035
1079
  function bufferConstructor() {
1036
1080
  return globalThis.Buffer;
1037
1081
  }
1082
+ var http2Module;
1083
+ function loadHttp2() {
1084
+ return http2Module ??= (async () => {
1085
+ const proc = globalThis.process;
1086
+ if (!proc?.versions?.node) return null;
1087
+ try {
1088
+ return await import(
1089
+ /* webpackIgnore: true */
1090
+ /* @vite-ignore */
1091
+ "http2"
1092
+ );
1093
+ } catch {
1094
+ return null;
1095
+ }
1096
+ })();
1097
+ }
1098
+ var HTTP2_REQUEST_TIMEOUT_MS = 12e4;
1099
+ var Http2Connection = class {
1100
+ confirmed = false;
1101
+ streams = 0;
1102
+ session;
1103
+ constructor(http2, origin) {
1104
+ this.session = http2.connect(origin);
1105
+ this.session.on("error", () => {
1106
+ });
1107
+ }
1108
+ get closed() {
1109
+ return this.session.closed || this.session.destroyed;
1110
+ }
1111
+ request(method, path, headers, body) {
1112
+ if (this.streams === 0) this.session.ref();
1113
+ this.streams++;
1114
+ return new Promise((resolve, reject) => {
1115
+ const stream = this.session.request({ ...headers, ":method": method, ":path": path });
1116
+ let status = 0;
1117
+ let text = "";
1118
+ stream.setEncoding("utf8");
1119
+ stream.setTimeout(HTTP2_REQUEST_TIMEOUT_MS, () => stream.destroy(new Error("HTTP/2 request timed out")));
1120
+ stream.on("response", (responseHeaders) => {
1121
+ this.confirmed = true;
1122
+ status = Number(responseHeaders[":status"]) || 0;
1123
+ });
1124
+ stream.on("data", (chunk) => {
1125
+ text += chunk;
1126
+ });
1127
+ stream.on("end", () => resolve({ status, ok: status >= 200 && status < 300, text }));
1128
+ stream.on("error", reject);
1129
+ stream.end(body);
1130
+ }).finally(() => {
1131
+ if (--this.streams === 0) this.session.unref();
1132
+ });
1133
+ }
1134
+ };
1135
+ var http2Connections = /* @__PURE__ */ new Map();
1136
+ async function sendRequest(url, init, fetchImpl, http2Enabled) {
1137
+ if (http2Enabled) {
1138
+ const http2 = await loadHttp2();
1139
+ if (http2) {
1140
+ const { origin, pathname, search } = new URL(url);
1141
+ const cached = http2Connections.get(origin);
1142
+ if (cached !== null) {
1143
+ let connection = cached;
1144
+ if (!connection || connection.closed) {
1145
+ connection = new Http2Connection(http2, origin);
1146
+ http2Connections.set(origin, connection);
1147
+ }
1148
+ try {
1149
+ return await connection.request(init.method, `${pathname}${search}`, init.headers, init.body);
1150
+ } catch (error) {
1151
+ if (connection.confirmed) throw error;
1152
+ http2Connections.set(origin, null);
1153
+ }
1154
+ }
1155
+ }
1156
+ }
1157
+ const response = await fetchImpl(url, init);
1158
+ return { status: response.status, ok: response.ok, text: await response.text() };
1159
+ }
1038
1160
 
1039
1161
  // src/compat/arker-provider.ts
1040
1162
  var DEFAULT_REGION = "aws-us-east-1";
package/dist/daytona.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  assertSupportedObjectKeys,
3
3
  createCompatSdk,
4
4
  guardUnsupported
5
- } from "./chunk-35IEV6BU.js";
6
- import "./chunk-7BHPVQNG.js";
5
+ } from "./chunk-YFJEL7PF.js";
6
+ import "./chunk-4NHUAVXK.js";
7
7
 
8
8
  // src/daytona.ts
9
9
  import { daytona as createDaytonaProvider } from "@computesdk/daytona";
package/dist/e2b.cjs CHANGED
@@ -167,6 +167,7 @@ var Arker = class {
167
167
  provider;
168
168
  apiKey;
169
169
  fetchImpl;
170
+ http2;
170
171
  retry;
171
172
  constructor(opts = {}) {
172
173
  const apiKey = opts.apiKey ?? env("ARKER_API_KEY") ?? env("AUTH_KEY");
@@ -188,6 +189,7 @@ var Arker = class {
188
189
  this.region = region ? normalizeRegion(region) : void 0;
189
190
  this.provider = effectiveProvider;
190
191
  this.fetchImpl = opts.fetch ?? globalThis.fetch;
192
+ this.http2 = opts.fetch === void 0;
191
193
  this.retry = normalizeRetry(opts.retry);
192
194
  if (!this.fetchImpl) throw new Error("fetch is required in this runtime");
193
195
  }
@@ -248,7 +250,10 @@ var Arker = class {
248
250
  egress: src.egress ?? null,
249
251
  disk: src.disk ?? true,
250
252
  durable: src.durable ?? null,
251
- resources
253
+ resources,
254
+ // ARK-125: omit to inherit the source's policy; pass a doc to override
255
+ // (an empty `{ policies: [] }` clears to allow-all, NOT inherit).
256
+ policies: src.policies ?? null
252
257
  };
253
258
  const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
254
259
  const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
@@ -343,30 +348,29 @@ var Arker = class {
343
348
  if (value !== void 0) headers[key] = value;
344
349
  }
345
350
  }
346
- const init = { method, headers };
351
+ let requestBody;
347
352
  if (body !== void 0) {
348
353
  headers["content-type"] = "application/json";
349
- init.body = JSON.stringify(withoutUndefined(body));
354
+ requestBody = JSON.stringify(withoutUndefined(body));
350
355
  }
351
356
  let lastStatus = 0;
352
357
  let lastText = "";
353
358
  let lastError;
354
359
  for (let attempt = 0; attempt < this.retry.attempts; attempt++) {
355
360
  try {
356
- const response = await this.fetchImpl(url, init);
357
- const text = await response.text();
361
+ const { status, ok, text } = await sendRequest(url, { method, headers, body: requestBody }, this.fetchImpl, this.http2);
358
362
  const payload = parseJson(text);
359
363
  const parsedError = extractError(payload);
360
- lastStatus = response.status;
364
+ lastStatus = status;
361
365
  lastText = text;
362
366
  lastError = parsedError;
363
- if (isRetryable(response.status, parsedError) && attempt < this.retry.attempts - 1) {
367
+ if (isRetryable(status, parsedError) && attempt < this.retry.attempts - 1) {
364
368
  await sleep(retryDelay(this.retry, attempt));
365
369
  continue;
366
370
  }
367
- if (parsedError) throw new ArkerError(parsedError.code, parsedError.message, response.status);
368
- if (!response.ok) {
369
- throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${response.status}`, response.status);
371
+ if (parsedError) throw new ArkerError(parsedError.code, parsedError.message, status);
372
+ if (!ok) {
373
+ throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${status}`, status);
370
374
  }
371
375
  return payload;
372
376
  } catch (error) {
@@ -578,6 +582,32 @@ var VM = class _VM {
578
582
  async delete() {
579
583
  return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
580
584
  }
585
+ // ── ARK-125 egress policy ────────────────────────────────────────
586
+ /**
587
+ * Read this VM's outbound egress policy document (ARK-125). Returns an
588
+ * empty doc (`{}`) when no policy is set.
589
+ */
590
+ async getPolicies() {
591
+ return this._client._request("GET", `${vmPath(this.id)}/policies`, void 0, this.baseUrl);
592
+ }
593
+ /**
594
+ * Replace this VM's outbound egress policy with `doc` — an ordered,
595
+ * first-match-wins rule list. An empty doc (`{}` or `{ policies: [] }`)
596
+ * clears the policy to allow-all. Returns the stored policy plus the
597
+ * domains it escalates to MITM and any degrade warnings.
598
+ *
599
+ * await vm.setPolicies({
600
+ * policies: [
601
+ * { type: "network.outbound",
602
+ * match: { domains: ["github.com"], ports: [443] },
603
+ * action: "allow" },
604
+ * { type: "network.outbound", action: "deny" },
605
+ * ],
606
+ * });
607
+ */
608
+ async setPolicies(doc) {
609
+ return this._client._request("PUT", `${vmPath(this.id)}/policies`, doc, this.baseUrl);
610
+ }
581
611
  // ── Syncs: bindings of a filesystem into this VM at a path ────────
582
612
  async listSyncs(opts = {}) {
583
613
  return this._client._request("GET", buildQuery(`${vmPath(this.id)}/syncs`, {
@@ -629,6 +659,20 @@ var VM = class _VM {
629
659
  async deleteSession(sessionId) {
630
660
  return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
631
661
  }
662
+ /**
663
+ * Update a session via `PATCH /v1/vms/{id}/sessions/{sid}`: resize its PTY
664
+ * (`cols`/`rows`) and/or set the idle `timeoutSecs`. Works whether or not a
665
+ * PTY is currently attached — the REST equivalent of {@link PtyConnection.resize}
666
+ * (which sends an in-band control frame on the live WebSocket).
667
+ */
668
+ async updateSession(sessionId, update) {
669
+ return this._client._request(
670
+ "PATCH",
671
+ `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`,
672
+ { cols: update.cols, rows: update.rows, timeout_secs: update.timeoutSecs },
673
+ this.baseUrl
674
+ );
675
+ }
632
676
  async connectPty(options = {}) {
633
677
  const sessionId = options.sessionId ?? sessionIdFrom(await this.createSession());
634
678
  const useTicket = options.useTicket ?? !isNodeRuntime();
@@ -1040,6 +1084,84 @@ function base64ToBytes(text) {
1040
1084
  function bufferConstructor() {
1041
1085
  return globalThis.Buffer;
1042
1086
  }
1087
+ var http2Module;
1088
+ function loadHttp2() {
1089
+ return http2Module ??= (async () => {
1090
+ const proc = globalThis.process;
1091
+ if (!proc?.versions?.node) return null;
1092
+ try {
1093
+ return await import(
1094
+ /* webpackIgnore: true */
1095
+ /* @vite-ignore */
1096
+ "http2"
1097
+ );
1098
+ } catch {
1099
+ return null;
1100
+ }
1101
+ })();
1102
+ }
1103
+ var HTTP2_REQUEST_TIMEOUT_MS = 12e4;
1104
+ var Http2Connection = class {
1105
+ confirmed = false;
1106
+ streams = 0;
1107
+ session;
1108
+ constructor(http2, origin) {
1109
+ this.session = http2.connect(origin);
1110
+ this.session.on("error", () => {
1111
+ });
1112
+ }
1113
+ get closed() {
1114
+ return this.session.closed || this.session.destroyed;
1115
+ }
1116
+ request(method, path, headers, body) {
1117
+ if (this.streams === 0) this.session.ref();
1118
+ this.streams++;
1119
+ return new Promise((resolve, reject) => {
1120
+ const stream = this.session.request({ ...headers, ":method": method, ":path": path });
1121
+ let status = 0;
1122
+ let text = "";
1123
+ stream.setEncoding("utf8");
1124
+ stream.setTimeout(HTTP2_REQUEST_TIMEOUT_MS, () => stream.destroy(new Error("HTTP/2 request timed out")));
1125
+ stream.on("response", (responseHeaders) => {
1126
+ this.confirmed = true;
1127
+ status = Number(responseHeaders[":status"]) || 0;
1128
+ });
1129
+ stream.on("data", (chunk) => {
1130
+ text += chunk;
1131
+ });
1132
+ stream.on("end", () => resolve({ status, ok: status >= 200 && status < 300, text }));
1133
+ stream.on("error", reject);
1134
+ stream.end(body);
1135
+ }).finally(() => {
1136
+ if (--this.streams === 0) this.session.unref();
1137
+ });
1138
+ }
1139
+ };
1140
+ var http2Connections = /* @__PURE__ */ new Map();
1141
+ async function sendRequest(url, init, fetchImpl, http2Enabled) {
1142
+ if (http2Enabled) {
1143
+ const http2 = await loadHttp2();
1144
+ if (http2) {
1145
+ const { origin, pathname, search } = new URL(url);
1146
+ const cached = http2Connections.get(origin);
1147
+ if (cached !== null) {
1148
+ let connection = cached;
1149
+ if (!connection || connection.closed) {
1150
+ connection = new Http2Connection(http2, origin);
1151
+ http2Connections.set(origin, connection);
1152
+ }
1153
+ try {
1154
+ return await connection.request(init.method, `${pathname}${search}`, init.headers, init.body);
1155
+ } catch (error) {
1156
+ if (connection.confirmed) throw error;
1157
+ http2Connections.set(origin, null);
1158
+ }
1159
+ }
1160
+ }
1161
+ }
1162
+ const response = await fetchImpl(url, init);
1163
+ return { status: response.status, ok: response.ok, text: await response.text() };
1164
+ }
1043
1165
 
1044
1166
  // src/compat/arker-provider.ts
1045
1167
  var DEFAULT_REGION = "aws-us-east-1";
package/dist/e2b.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  createCompatSdk,
4
4
  guardUnsupported,
5
5
  resolveTimeout
6
- } from "./chunk-35IEV6BU.js";
7
- import "./chunk-7BHPVQNG.js";
6
+ } from "./chunk-YFJEL7PF.js";
7
+ import "./chunk-4NHUAVXK.js";
8
8
 
9
9
  // src/e2b.ts
10
10
  import { e2b as createE2BProvider } from "@computesdk/e2b";