@alchemy.run/e2b 0.0.0 → 2.0.0-beta.86

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.
Files changed (284) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +56 -4
  3. package/lib/ApiKey.d.ts +53 -0
  4. package/lib/ApiKey.d.ts.map +1 -0
  5. package/lib/ApiKey.js +107 -0
  6. package/lib/ApiKey.js.map +1 -0
  7. package/lib/AuthProvider.d.ts +60 -0
  8. package/lib/AuthProvider.d.ts.map +1 -0
  9. package/lib/AuthProvider.js +293 -0
  10. package/lib/AuthProvider.js.map +1 -0
  11. package/lib/Bootstrap.d.ts +7 -0
  12. package/lib/Bootstrap.d.ts.map +1 -0
  13. package/lib/Bootstrap.js +25 -0
  14. package/lib/Bootstrap.js.map +1 -0
  15. package/lib/ConnectSandbox.d.ts +67 -0
  16. package/lib/ConnectSandbox.d.ts.map +1 -0
  17. package/lib/ConnectSandbox.js +3 -0
  18. package/lib/ConnectSandbox.js.map +1 -0
  19. package/lib/ConnectSandboxHttp.d.ts +22 -0
  20. package/lib/ConnectSandboxHttp.d.ts.map +1 -0
  21. package/lib/ConnectSandboxHttp.js +30 -0
  22. package/lib/ConnectSandboxHttp.js.map +1 -0
  23. package/lib/Contributions.d.ts +7 -0
  24. package/lib/Contributions.d.ts.map +1 -0
  25. package/lib/Contributions.js +6 -0
  26. package/lib/Contributions.js.map +1 -0
  27. package/lib/CreateSandbox.d.ts +139 -0
  28. package/lib/CreateSandbox.d.ts.map +1 -0
  29. package/lib/CreateSandbox.js +3 -0
  30. package/lib/CreateSandbox.js.map +1 -0
  31. package/lib/CreateSandboxHttp.d.ts +22 -0
  32. package/lib/CreateSandboxHttp.d.ts.map +1 -0
  33. package/lib/CreateSandboxHttp.js +99 -0
  34. package/lib/CreateSandboxHttp.js.map +1 -0
  35. package/lib/CreateSecret.d.ts +57 -0
  36. package/lib/CreateSecret.d.ts.map +1 -0
  37. package/lib/CreateSecret.js +3 -0
  38. package/lib/CreateSecret.js.map +1 -0
  39. package/lib/CreateSecretHttp.d.ts +20 -0
  40. package/lib/CreateSecretHttp.d.ts.map +1 -0
  41. package/lib/CreateSecretHttp.js +64 -0
  42. package/lib/CreateSecretHttp.js.map +1 -0
  43. package/lib/CreateSnapshot.d.ts +53 -0
  44. package/lib/CreateSnapshot.d.ts.map +1 -0
  45. package/lib/CreateSnapshot.js +3 -0
  46. package/lib/CreateSnapshot.js.map +1 -0
  47. package/lib/CreateSnapshotHttp.d.ts +20 -0
  48. package/lib/CreateSnapshotHttp.d.ts.map +1 -0
  49. package/lib/CreateSnapshotHttp.js +41 -0
  50. package/lib/CreateSnapshotHttp.js.map +1 -0
  51. package/lib/CreateTemplate.d.ts +96 -0
  52. package/lib/CreateTemplate.d.ts.map +1 -0
  53. package/lib/CreateTemplate.js +3 -0
  54. package/lib/CreateTemplate.js.map +1 -0
  55. package/lib/CreateTemplateHttp.d.ts +20 -0
  56. package/lib/CreateTemplateHttp.d.ts.map +1 -0
  57. package/lib/CreateTemplateHttp.js +99 -0
  58. package/lib/CreateTemplateHttp.js.map +1 -0
  59. package/lib/CreateVolume.d.ts +58 -0
  60. package/lib/CreateVolume.d.ts.map +1 -0
  61. package/lib/CreateVolume.js +3 -0
  62. package/lib/CreateVolume.js.map +1 -0
  63. package/lib/CreateVolumeHttp.d.ts +20 -0
  64. package/lib/CreateVolumeHttp.d.ts.map +1 -0
  65. package/lib/CreateVolumeHttp.js +51 -0
  66. package/lib/CreateVolumeHttp.js.map +1 -0
  67. package/lib/Credentials.d.ts +18 -0
  68. package/lib/Credentials.d.ts.map +1 -0
  69. package/lib/Credentials.js +55 -0
  70. package/lib/Credentials.js.map +1 -0
  71. package/lib/E2BBindings.d.ts +35 -0
  72. package/lib/E2BBindings.d.ts.map +1 -0
  73. package/lib/E2BBindings.js +127 -0
  74. package/lib/E2BBindings.js.map +1 -0
  75. package/lib/Errors.d.ts +61 -0
  76. package/lib/Errors.d.ts.map +1 -0
  77. package/lib/Errors.js +23 -0
  78. package/lib/Errors.js.map +1 -0
  79. package/lib/InferEnv.d.ts +26 -0
  80. package/lib/InferEnv.d.ts.map +1 -0
  81. package/lib/InferEnv.js +1 -0
  82. package/lib/InferEnv.js.map +1 -0
  83. package/lib/MountVolume.d.ts +86 -0
  84. package/lib/MountVolume.d.ts.map +1 -0
  85. package/lib/MountVolume.js +3 -0
  86. package/lib/MountVolume.js.map +1 -0
  87. package/lib/MountVolumeLive.d.ts +23 -0
  88. package/lib/MountVolumeLive.d.ts.map +1 -0
  89. package/lib/MountVolumeLive.js +41 -0
  90. package/lib/MountVolumeLive.js.map +1 -0
  91. package/lib/Providers.d.ts +14 -0
  92. package/lib/Providers.d.ts.map +1 -0
  93. package/lib/Providers.js +23 -0
  94. package/lib/Providers.js.map +1 -0
  95. package/lib/ReadTeam.d.ts +41 -0
  96. package/lib/ReadTeam.d.ts.map +1 -0
  97. package/lib/ReadTeam.js +3 -0
  98. package/lib/ReadTeam.js.map +1 -0
  99. package/lib/ReadTeamHttp.d.ts +20 -0
  100. package/lib/ReadTeamHttp.d.ts.map +1 -0
  101. package/lib/ReadTeamHttp.js +40 -0
  102. package/lib/ReadTeamHttp.js.map +1 -0
  103. package/lib/ReadVolume.d.ts +25 -0
  104. package/lib/ReadVolume.d.ts.map +1 -0
  105. package/lib/ReadVolume.js +3 -0
  106. package/lib/ReadVolume.js.map +1 -0
  107. package/lib/ReadVolumeHttp.d.ts +20 -0
  108. package/lib/ReadVolumeHttp.d.ts.map +1 -0
  109. package/lib/ReadVolumeHttp.js +27 -0
  110. package/lib/ReadVolumeHttp.js.map +1 -0
  111. package/lib/ReadWebhook.d.ts +32 -0
  112. package/lib/ReadWebhook.d.ts.map +1 -0
  113. package/lib/ReadWebhook.js +3 -0
  114. package/lib/ReadWebhook.js.map +1 -0
  115. package/lib/ReadWebhookHttp.d.ts +20 -0
  116. package/lib/ReadWebhookHttp.d.ts.map +1 -0
  117. package/lib/ReadWebhookHttp.js +31 -0
  118. package/lib/ReadWebhookHttp.js.map +1 -0
  119. package/lib/ReadWriteVolume.d.ts +26 -0
  120. package/lib/ReadWriteVolume.d.ts.map +1 -0
  121. package/lib/ReadWriteVolume.js +3 -0
  122. package/lib/ReadWriteVolume.js.map +1 -0
  123. package/lib/ReadWriteVolumeHttp.d.ts +20 -0
  124. package/lib/ReadWriteVolumeHttp.d.ts.map +1 -0
  125. package/lib/ReadWriteVolumeHttp.js +27 -0
  126. package/lib/ReadWriteVolumeHttp.js.map +1 -0
  127. package/lib/Sandbox.d.ts +295 -0
  128. package/lib/Sandbox.d.ts.map +1 -0
  129. package/lib/Sandbox.js +351 -0
  130. package/lib/Sandbox.js.map +1 -0
  131. package/lib/SandboxClient.d.ts +226 -0
  132. package/lib/SandboxClient.d.ts.map +1 -0
  133. package/lib/SandboxClient.js +1 -0
  134. package/lib/SandboxClient.js.map +1 -0
  135. package/lib/SandboxEvents.d.ts +87 -0
  136. package/lib/SandboxEvents.d.ts.map +1 -0
  137. package/lib/SandboxEvents.js +39 -0
  138. package/lib/SandboxEvents.js.map +1 -0
  139. package/lib/SandboxEventsLive.d.ts +13 -0
  140. package/lib/SandboxEventsLive.d.ts.map +1 -0
  141. package/lib/SandboxEventsLive.js +80 -0
  142. package/lib/SandboxEventsLive.js.map +1 -0
  143. package/lib/Secret.d.ts +105 -0
  144. package/lib/Secret.d.ts.map +1 -0
  145. package/lib/Secret.js +161 -0
  146. package/lib/Secret.js.map +1 -0
  147. package/lib/Snapshot.d.ts +118 -0
  148. package/lib/Snapshot.d.ts.map +1 -0
  149. package/lib/Snapshot.js +158 -0
  150. package/lib/Snapshot.js.map +1 -0
  151. package/lib/Template.d.ts +313 -0
  152. package/lib/Template.d.ts.map +1 -0
  153. package/lib/Template.js +421 -0
  154. package/lib/Template.js.map +1 -0
  155. package/lib/Volume.d.ts +88 -0
  156. package/lib/Volume.d.ts.map +1 -0
  157. package/lib/Volume.js +136 -0
  158. package/lib/Volume.js.map +1 -0
  159. package/lib/VolumeFiles.d.ts +39 -0
  160. package/lib/VolumeFiles.d.ts.map +1 -0
  161. package/lib/VolumeFiles.js +1 -0
  162. package/lib/VolumeFiles.js.map +1 -0
  163. package/lib/Webhook.d.ts +83 -0
  164. package/lib/Webhook.d.ts.map +1 -0
  165. package/lib/Webhook.js +138 -0
  166. package/lib/Webhook.js.map +1 -0
  167. package/lib/WriteSecret.d.ts +34 -0
  168. package/lib/WriteSecret.d.ts.map +1 -0
  169. package/lib/WriteSecret.js +3 -0
  170. package/lib/WriteSecret.js.map +1 -0
  171. package/lib/WriteSecretHttp.d.ts +20 -0
  172. package/lib/WriteSecretHttp.d.ts.map +1 -0
  173. package/lib/WriteSecretHttp.js +37 -0
  174. package/lib/WriteSecretHttp.js.map +1 -0
  175. package/lib/WriteVolume.d.ts +25 -0
  176. package/lib/WriteVolume.d.ts.map +1 -0
  177. package/lib/WriteVolume.js +3 -0
  178. package/lib/WriteVolume.js.map +1 -0
  179. package/lib/WriteVolumeHttp.d.ts +20 -0
  180. package/lib/WriteVolumeHttp.d.ts.map +1 -0
  181. package/lib/WriteVolumeHttp.js +27 -0
  182. package/lib/WriteVolumeHttp.js.map +1 -0
  183. package/lib/index.d.ts +43 -0
  184. package/lib/index.d.ts.map +1 -0
  185. package/lib/index.js +43 -0
  186. package/lib/index.js.map +1 -0
  187. package/lib/internal/Declared.d.ts +29 -0
  188. package/lib/internal/Declared.d.ts.map +1 -0
  189. package/lib/internal/Declared.js +31 -0
  190. package/lib/internal/Declared.js.map +1 -0
  191. package/lib/internal/Login.d.ts +45 -0
  192. package/lib/internal/Login.d.ts.map +1 -0
  193. package/lib/internal/Login.js +157 -0
  194. package/lib/internal/Login.js.map +1 -0
  195. package/lib/internal/Ownership.d.ts +29 -0
  196. package/lib/internal/Ownership.d.ts.map +1 -0
  197. package/lib/internal/Ownership.js +49 -0
  198. package/lib/internal/Ownership.js.map +1 -0
  199. package/lib/internal/Program.d.ts +31 -0
  200. package/lib/internal/Program.d.ts.map +1 -0
  201. package/lib/internal/Program.js +60 -0
  202. package/lib/internal/Program.js.map +1 -0
  203. package/lib/internal/SandboxHandle.d.ts +25 -0
  204. package/lib/internal/SandboxHandle.d.ts.map +1 -0
  205. package/lib/internal/SandboxHandle.js +316 -0
  206. package/lib/internal/SandboxHandle.js.map +1 -0
  207. package/lib/internal/SnapshotBuild.d.ts +19 -0
  208. package/lib/internal/SnapshotBuild.d.ts.map +1 -0
  209. package/lib/internal/SnapshotBuild.js +49 -0
  210. package/lib/internal/SnapshotBuild.js.map +1 -0
  211. package/lib/internal/Tar.d.ts +10 -0
  212. package/lib/internal/Tar.d.ts.map +1 -0
  213. package/lib/internal/Tar.js +61 -0
  214. package/lib/internal/Tar.js.map +1 -0
  215. package/lib/internal/TemplateBuild.d.ts +142 -0
  216. package/lib/internal/TemplateBuild.d.ts.map +1 -0
  217. package/lib/internal/TemplateBuild.js +220 -0
  218. package/lib/internal/TemplateBuild.js.map +1 -0
  219. package/lib/internal/TemplatePlan.d.ts +57 -0
  220. package/lib/internal/TemplatePlan.d.ts.map +1 -0
  221. package/lib/internal/TemplatePlan.js +185 -0
  222. package/lib/internal/TemplatePlan.js.map +1 -0
  223. package/lib/internal/VolumeHttp.d.ts +5 -0
  224. package/lib/internal/VolumeHttp.d.ts.map +1 -0
  225. package/lib/internal/VolumeHttp.js +46 -0
  226. package/lib/internal/VolumeHttp.js.map +1 -0
  227. package/package.json +62 -3
  228. package/src/ApiKey.ts +148 -0
  229. package/src/AuthProvider.ts +437 -0
  230. package/src/Bootstrap.ts +51 -0
  231. package/src/ConnectSandbox.ts +71 -0
  232. package/src/ConnectSandboxHttp.ts +38 -0
  233. package/src/Contributions.ts +9 -0
  234. package/src/CreateSandbox.ts +157 -0
  235. package/src/CreateSandboxHttp.ts +131 -0
  236. package/src/CreateSecret.ts +65 -0
  237. package/src/CreateSecretHttp.ts +94 -0
  238. package/src/CreateSnapshot.ts +64 -0
  239. package/src/CreateSnapshotHttp.ts +55 -0
  240. package/src/CreateTemplate.ts +135 -0
  241. package/src/CreateTemplateHttp.ts +152 -0
  242. package/src/CreateVolume.ts +65 -0
  243. package/src/CreateVolumeHttp.ts +80 -0
  244. package/src/Credentials.ts +81 -0
  245. package/src/E2BBindings.ts +160 -0
  246. package/src/Errors.ts +47 -0
  247. package/src/InferEnv.ts +27 -0
  248. package/src/MountVolume.ts +92 -0
  249. package/src/MountVolumeLive.ts +49 -0
  250. package/src/Providers.ts +48 -0
  251. package/src/ReadTeam.ts +55 -0
  252. package/src/ReadTeamHttp.ts +44 -0
  253. package/src/ReadVolume.ts +29 -0
  254. package/src/ReadVolumeHttp.ts +32 -0
  255. package/src/ReadWebhook.ts +41 -0
  256. package/src/ReadWebhookHttp.ts +42 -0
  257. package/src/ReadWriteVolume.ts +30 -0
  258. package/src/ReadWriteVolumeHttp.ts +32 -0
  259. package/src/Sandbox.ts +581 -0
  260. package/src/SandboxClient.ts +268 -0
  261. package/src/SandboxEvents.ts +101 -0
  262. package/src/SandboxEventsLive.ts +118 -0
  263. package/src/Secret.ts +229 -0
  264. package/src/Snapshot.ts +234 -0
  265. package/src/Template.ts +636 -0
  266. package/src/Volume.ts +173 -0
  267. package/src/VolumeFiles.ts +53 -0
  268. package/src/Webhook.ts +215 -0
  269. package/src/WriteSecret.ts +39 -0
  270. package/src/WriteSecretHttp.ts +43 -0
  271. package/src/WriteVolume.ts +29 -0
  272. package/src/WriteVolumeHttp.ts +32 -0
  273. package/src/globals.d.ts +2 -0
  274. package/src/index.ts +42 -0
  275. package/src/internal/Declared.ts +80 -0
  276. package/src/internal/Login.ts +210 -0
  277. package/src/internal/Ownership.ts +67 -0
  278. package/src/internal/Program.ts +75 -0
  279. package/src/internal/SandboxHandle.ts +534 -0
  280. package/src/internal/SnapshotBuild.ts +74 -0
  281. package/src/internal/Tar.ts +74 -0
  282. package/src/internal/TemplateBuild.ts +383 -0
  283. package/src/internal/TemplatePlan.ts +230 -0
  284. package/src/internal/VolumeHttp.ts +88 -0
@@ -0,0 +1,80 @@
1
+ import type { PropsInput } from "alchemy/Input";
2
+ import * as Output from "alchemy/Output";
3
+ import { isResource } from "alchemy/Resource";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Redacted from "effect/Redacted";
6
+ import type { Providers } from "../Providers.ts";
7
+
8
+ type Leaf =
9
+ | string
10
+ | number
11
+ | boolean
12
+ | bigint
13
+ | symbol
14
+ | null
15
+ | undefined
16
+ | Redacted.Redacted<any>
17
+ | Uint8Array
18
+ | Date
19
+ | ((...args: any[]) => any);
20
+
21
+ /**
22
+ * A prop value, or the declaration of a resource that has it: `template: Node`
23
+ * instead of `template: yield* Node`, at any depth.
24
+ */
25
+ export type Declared<T> = T extends Leaf
26
+ ? T
27
+ : T extends Output.Output<any>
28
+ ? T
29
+ : T extends ReadonlyArray<infer U>
30
+ ? Declared<U>[] | Effect.Effect<T, never, Providers>
31
+ : { [K in keyof T]: Declared<T[K]> } | Effect.Effect<T, never, Providers>;
32
+
33
+ /** Props where any value may be a resource declaration. */
34
+ export type DeclaredProps<P> = { [K in keyof PropsInput<P>]: Declared<PropsInput<P>[K]> };
35
+
36
+ const isPlain = (value: unknown): value is Record<string, unknown> =>
37
+ typeof value === "object" &&
38
+ value !== null &&
39
+ (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
40
+
41
+ /** A declaration still to be yielded: an Effect that isn't an Output or a resource. */
42
+ const isDeclaration = (value: unknown): value is Effect.Effect<unknown, never, Providers> =>
43
+ Effect.isEffect(value) && !Output.isOutput(value) && !isResource(value);
44
+
45
+ const hasDeclarations = (value: unknown): boolean =>
46
+ isDeclaration(value) ||
47
+ (Array.isArray(value) && value.some(hasDeclarations)) ||
48
+ (isPlain(value) && !isResource(value) && Object.values(value).some(hasDeclarations));
49
+
50
+ /** Yield every declaration in `value`, at any depth. */
51
+ export const resolveDeclarations = (value: unknown): Effect.Effect<unknown, never, Providers> =>
52
+ isDeclaration(value)
53
+ ? value
54
+ : Array.isArray(value)
55
+ ? Effect.forEach(value, resolveDeclarations)
56
+ : isPlain(value) && !isResource(value) && hasDeclarations(value)
57
+ ? Effect.map(
58
+ Effect.forEach(Object.entries(value), ([key, entry]) =>
59
+ Effect.map(resolveDeclarations(entry), (resolved) => [key, resolved] as const),
60
+ ),
61
+ Object.fromEntries,
62
+ )
63
+ : Effect.succeed(value);
64
+
65
+ /**
66
+ * Let a resource's props hold declarations of other resources, which are
67
+ * yielded when the resource is: `E2B.Sandbox("DevBox", { template: Node })`.
68
+ * Props without declarations are passed through unchanged.
69
+ */
70
+ export const declarable =
71
+ <R extends { readonly Props: any }>() =>
72
+ <C extends object>(
73
+ resource: C,
74
+ ): C & ((id: string, props: DeclaredProps<R["Props"]>) => Effect.Effect<R, never, Providers>) =>
75
+ new Proxy(resource as C & ((...args: unknown[]) => unknown), {
76
+ apply: (target, self, args: unknown[]) =>
77
+ typeof args[0] === "string" && hasDeclarations(args[1])
78
+ ? Reflect.apply(target, self, [args[0], resolveDeclarations(args[1])])
79
+ : Reflect.apply(target, self, args),
80
+ }) as never;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * E2B's browser login, as the E2B CLI runs it (`e2b auth login`).
3
+ *
4
+ * E2B's console signs the user in and redirects to a loopback URL with the
5
+ * OAuth grant in the query string: a short-lived JWT access token, a refresh
6
+ * token, and the token and revoke endpoints with the public client id they
7
+ * were issued to. Refresh and revoke are standard OAuth 2 requests against
8
+ * those endpoints.
9
+ */
10
+ import * as http from "node:http";
11
+ import type { AddressInfo } from "node:net";
12
+ import * as Data from "effect/Data";
13
+ import * as Deferred from "effect/Deferred";
14
+ import * as Effect from "effect/Effect";
15
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
16
+ import * as HttpClient from "effect/http/HttpClient";
17
+ import * as HttpClientRequest from "effect/http/HttpClientRequest";
18
+ import * as Redacted from "effect/Redacted";
19
+ import * as Schema from "effect/Schema";
20
+
21
+ export class E2BLoginError extends Data.TaggedError("E2BLoginError")<{
22
+ readonly message: string;
23
+ readonly cause?: unknown;
24
+ }> {}
25
+
26
+ /** The grant E2B's console hands the loopback listener. */
27
+ export interface E2BGrant {
28
+ readonly email: string;
29
+ readonly access: Redacted.Redacted<string>;
30
+ readonly refresh: Redacted.Redacted<string>;
31
+ readonly tokenEndpoint: string;
32
+ readonly revokeEndpoint: string;
33
+ readonly clientId: string;
34
+ /** Epoch millis the access token expires at. */
35
+ readonly expires: number;
36
+ }
37
+
38
+ /**
39
+ * The E2B CLI release whose login flow this mirrors. E2B's console reads
40
+ * `cliVersion` and refuses ("CLI update required") a login without a current
41
+ * one. Bump it with the E2B CLI if the console starts refusing it.
42
+ */
43
+ export const E2B_CLI_COMPAT_VERSION = "2.23.0";
44
+
45
+ /** Where the console's CLI login starts, and where it shows the outcome. */
46
+ const loginBase = (domain: string) =>
47
+ `https://${domain === "e2b.app" ? "e2b.dev" : domain}/docs/api/cli`;
48
+
49
+ /** A JWT's `exp`, in epoch millis; 0 when it has none, so it refreshes at once. */
50
+ export const jwtExpiry = (token: string): number => {
51
+ const payload = token.split(".")[1];
52
+ if (payload === undefined) return 0;
53
+ try {
54
+ const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as {
55
+ exp?: number;
56
+ };
57
+ return typeof decoded.exp === "number" ? decoded.exp * 1000 : 0;
58
+ } catch {
59
+ return 0;
60
+ }
61
+ };
62
+
63
+ const isLoginError = (value: E2BGrant | E2BLoginError): value is E2BLoginError => "_tag" in value;
64
+
65
+ const grantOf = (params: URLSearchParams): E2BGrant | E2BLoginError => {
66
+ const error = params.get("error");
67
+ if (error) return new E2BLoginError({ message: `E2B login failed: ${error}` });
68
+ const field = (name: string) => params.get(name) ?? "";
69
+ const grant = {
70
+ email: field("email"),
71
+ accessToken: field("accessToken"),
72
+ refreshToken: field("refreshToken"),
73
+ tokenEndpoint: field("tokenEndpoint"),
74
+ revokeEndpoint: field("revokeEndpoint"),
75
+ clientId: field("clientId"),
76
+ };
77
+ const missing = Object.entries(grant)
78
+ .filter(([, value]) => value === "")
79
+ .map(([key]) => key);
80
+ if (missing.length > 0) {
81
+ return new E2BLoginError({
82
+ message: `E2B login returned an incomplete grant (missing ${missing.join(", ")})`,
83
+ });
84
+ }
85
+ return {
86
+ email: grant.email,
87
+ access: Redacted.make(grant.accessToken),
88
+ refresh: Redacted.make(grant.refreshToken),
89
+ tokenEndpoint: grant.tokenEndpoint,
90
+ revokeEndpoint: grant.revokeEndpoint,
91
+ clientId: grant.clientId,
92
+ expires: jwtExpiry(grant.accessToken),
93
+ };
94
+ };
95
+
96
+ /** Parses a pasted loopback URL (the manual fallback when the listener can't be reached). */
97
+ export const grantFromUrl = (input: string): Effect.Effect<E2BGrant, E2BLoginError> =>
98
+ Effect.gen(function* () {
99
+ const url = yield* Effect.try({
100
+ try: () => new URL(input.trim()),
101
+ catch: () => new E2BLoginError({ message: "Paste the full URL the browser was sent to" }),
102
+ });
103
+ const grant = grantOf(url.searchParams);
104
+ return isLoginError(grant) ? yield* grant : grant;
105
+ });
106
+
107
+ /**
108
+ * Starts the loopback listener on a free port and returns the console URL
109
+ * to open, plus the grant it receives. The listener closes with the scope.
110
+ */
111
+ export const startLogin = Effect.fn(function* (domain: string) {
112
+ const base = loginBase(domain);
113
+ const received = yield* Deferred.make<E2BGrant, E2BLoginError>();
114
+ const server = yield* Effect.acquireRelease(
115
+ Effect.callback<http.Server, E2BLoginError>((resume) => {
116
+ const server = http.createServer((req, res) => {
117
+ const params = new URL(req.url ?? "/", "http://localhost").searchParams;
118
+ const grant = grantOf(params);
119
+ const outcome = new URL(base);
120
+ if (isLoginError(grant)) {
121
+ outcome.searchParams.set("state", "error");
122
+ outcome.searchParams.set("error", grant.message);
123
+ } else {
124
+ outcome.searchParams.set("state", "success");
125
+ outcome.searchParams.set("email", grant.email);
126
+ }
127
+ res.writeHead(302, { Location: outcome.href, Connection: "close" });
128
+ res.end();
129
+ Deferred.doneUnsafe(
130
+ received,
131
+ isLoginError(grant) ? Effect.fail(grant) : Effect.succeed(grant),
132
+ );
133
+ });
134
+ server.once("error", (cause) =>
135
+ resume(
136
+ Effect.fail(new E2BLoginError({ message: "Could not start the login listener", cause })),
137
+ ),
138
+ );
139
+ server.listen(0, "127.0.0.1", () => resume(Effect.succeed(server)));
140
+ }),
141
+ (server) =>
142
+ Effect.sync(() => {
143
+ server.close();
144
+ server.closeAllConnections();
145
+ }),
146
+ );
147
+ const { port } = server.address() as AddressInfo;
148
+ const url = new URL(base);
149
+ url.searchParams.set("next", `http://localhost:${port}`);
150
+ url.searchParams.set("cliVersion", E2B_CLI_COMPAT_VERSION);
151
+ return { url: url.href, grant: Deferred.await(received) };
152
+ });
153
+
154
+ const TokenResponse = Schema.Struct({
155
+ access_token: Schema.String,
156
+ refresh_token: Schema.optional(Schema.String),
157
+ });
158
+
159
+ /** Exchanges the refresh token for a fresh access token (refresh tokens may rotate). */
160
+ export const refreshGrant = (grant: E2BGrant): Effect.Effect<E2BGrant, E2BLoginError> =>
161
+ Effect.gen(function* () {
162
+ const client = yield* HttpClient.HttpClient;
163
+ // Raw HTTP: the token endpoint the console's login redirect handed back.
164
+ const response = yield* client.execute(
165
+ HttpClientRequest.post(grant.tokenEndpoint).pipe(
166
+ HttpClientRequest.setHeader("Accept", "application/json"),
167
+ HttpClientRequest.bodyUrlParams({
168
+ grant_type: "refresh_token",
169
+ refresh_token: Redacted.value(grant.refresh),
170
+ client_id: grant.clientId,
171
+ }),
172
+ ),
173
+ );
174
+ if (response.status >= 400) {
175
+ const body = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
176
+ return yield* new E2BLoginError({
177
+ message: `E2B token refresh failed (${response.status}): ${body.slice(0, 200)}`,
178
+ });
179
+ }
180
+ const tokens = yield* Schema.decodeUnknownEffect(TokenResponse)(yield* response.json);
181
+ return {
182
+ ...grant,
183
+ access: Redacted.make(tokens.access_token),
184
+ refresh:
185
+ tokens.refresh_token === undefined ? grant.refresh : Redacted.make(tokens.refresh_token),
186
+ expires: jwtExpiry(tokens.access_token),
187
+ };
188
+ }).pipe(
189
+ Effect.catchIf(
190
+ (e): e is Exclude<typeof e, E2BLoginError> => e._tag !== "E2BLoginError",
191
+ (cause) => Effect.fail(new E2BLoginError({ message: "E2B token refresh failed", cause })),
192
+ ),
193
+ Effect.provide(FetchHttpClient.layer),
194
+ );
195
+
196
+ /** Revokes the refresh token; best effort, like `e2b auth logout`. */
197
+ export const revokeGrant = (grant: Pick<E2BGrant, "refresh" | "revokeEndpoint" | "clientId">) =>
198
+ Effect.gen(function* () {
199
+ const client = yield* HttpClient.HttpClient;
200
+ yield* client.execute(
201
+ // Raw HTTP: the revoke endpoint the console's login redirect handed back.
202
+ HttpClientRequest.post(grant.revokeEndpoint).pipe(
203
+ HttpClientRequest.bodyUrlParams({
204
+ token: Redacted.value(grant.refresh),
205
+ token_type_hint: "refresh_token",
206
+ client_id: grant.clientId,
207
+ }),
208
+ ),
209
+ );
210
+ }).pipe(Effect.ignore, Effect.provide(FetchHttpClient.layer));
@@ -0,0 +1,67 @@
1
+ import { createPhysicalName } from "alchemy/PhysicalName";
2
+ import { Stack } from "alchemy/Stack";
3
+ import { Stage } from "alchemy/Stage";
4
+ /**
5
+ * Naming and ownership helpers shared by every E2B resource and binding.
6
+ *
7
+ * E2B has metadata only on secrets and sandboxes. Templates, snapshots,
8
+ * volumes, webhooks and API keys carry nothing but a name, so a resource
9
+ * whose name the engine generated (stack, stage, logical id, instance
10
+ * suffix) is recognised as ours by that name; an explicit, user-chosen name
11
+ * found without prior state is reported `Unowned`.
12
+ */
13
+ import { createInternalTags } from "alchemy/Tags";
14
+ import * as Effect from "effect/Effect";
15
+
16
+ /** E2B names: letters, digits, `-` and `_`. Lowercased (secrets and templates are lowercased by E2B). */
17
+ export const e2bName = (id: string, name: string | undefined, maxLength = 64) =>
18
+ name !== undefined
19
+ ? Effect.succeed(name)
20
+ : createPhysicalName({ id, lowercase: true, maxLength, delimiter: "-" });
21
+
22
+ /**
23
+ * Whether a name looks generated by `createPhysicalName` (lowercase, ending
24
+ * in a base32 instance suffix). Used by `list` to report only resources
25
+ * Alchemy created, for nuke.
26
+ */
27
+ export const isGeneratedName = (name: string | undefined): boolean =>
28
+ name !== undefined && /^[a-z0-9][a-z0-9_-]*-[a-z2-7]{8,16}$/.test(name);
29
+
30
+ /** The `alchemy::stack` / `alchemy::stage` / `alchemy::id` stamp for metadata maps. */
31
+ export const ownershipMetadata = (id: string) => createInternalTags(id);
32
+
33
+ /**
34
+ * The stamp runtime-created objects carry: the stack and stage that created
35
+ * them and the binding's logical id. Destroy sweeps by it.
36
+ */
37
+ export const runtimeMetadata = Effect.fn(function* (bindingId: string) {
38
+ const stack = yield* Stack;
39
+ const stage = yield* Stage;
40
+ return {
41
+ "alchemy::stack": stack.name,
42
+ "alchemy::stage": stage,
43
+ "alchemy::binding": bindingId,
44
+ } as Record<string, string>;
45
+ });
46
+
47
+ /** Whether a metadata map carries this resource's ownership stamp. */
48
+ export const hasOwnershipMetadata = Effect.fn(function* (
49
+ id: string,
50
+ metadata: Readonly<Record<string, string>> | null | undefined,
51
+ ) {
52
+ const expected = yield* createInternalTags(id);
53
+ return Object.entries(expected).every(([key, value]) => metadata?.[key] === value);
54
+ });
55
+
56
+ /** Whether a metadata map was stamped by any Alchemy stack (for nuke). */
57
+ export const hasAnyAlchemyMetadata = (
58
+ metadata: Readonly<Record<string, string>> | null | undefined,
59
+ ): boolean => metadata !== undefined && metadata !== null && "alchemy::stack" in metadata;
60
+
61
+ /** User metadata with the internal `alchemy::*` keys removed. */
62
+ export const userMetadata = (
63
+ metadata: Readonly<Record<string, string>> | null | undefined,
64
+ ): Record<string, string> =>
65
+ Object.fromEntries(
66
+ Object.entries(metadata ?? {}).filter(([key]) => !key.startsWith("alchemy::")),
67
+ );
@@ -0,0 +1,75 @@
1
+ /**
2
+ * How a template runs its `main` program.
3
+ *
4
+ * `@alchemy.run/docker/BuildPlan` bundles `main` and adds the layer that
5
+ * runs it with Bun from `/app`; E2B starts that command when it builds the
6
+ * template and captures it running in the template's snapshot, so every
7
+ * sandbox boots with it already serving on `PORT`. An Effect program (the
8
+ * class form) is wrapped in the generated bootstrap entry; an async program
9
+ * (`main` with no Effect impl) is bundled and run as-is.
10
+ */
11
+ import { Stack } from "alchemy/Stack";
12
+ import * as Effect from "effect/Effect";
13
+ import * as Redacted from "effect/Redacted";
14
+
15
+ /** The port a template's program serves on unless `port` says otherwise. */
16
+ export const DEFAULT_PORT = 3000;
17
+
18
+ /**
19
+ * The generated entry: raises the runtime flag BEFORE the user's module is
20
+ * evaluated (hence the dynamic import), then boots the program. It imports
21
+ * only `@alchemy.run/e2b/Bootstrap` and `main` — see `alchemy/Runtime/Bootstrap`.
22
+ */
23
+ export const makeBootstrap =
24
+ (handler: string) =>
25
+ (importPath: string): string =>
26
+ `
27
+ import { bootstrap } from "@alchemy.run/e2b/Bootstrap";
28
+
29
+ globalThis.__ALCHEMY_RUNTIME__ = true;
30
+ const { ${handler}: entrypoint } = await import(${JSON.stringify(importPath)});
31
+
32
+ await bootstrap(entrypoint);
33
+ `;
34
+
35
+ /**
36
+ * A value as the env var the program reads, verbatim. Bound values arrive
37
+ * already packed by the host (`packEnvValue`, so a `Redacted` keeps its
38
+ * marker); packing again would double-encode them, and a user's `env` is read
39
+ * raw (`Config`, `process.env`).
40
+ */
41
+ const envValue = (value: unknown): string | undefined =>
42
+ value === undefined || value === null
43
+ ? undefined
44
+ : Redacted.isRedacted(value)
45
+ ? String(Redacted.value(value))
46
+ : typeof value === "string"
47
+ ? value
48
+ : JSON.stringify(value);
49
+
50
+ /** The program's environment: Alchemy's runtime vars, bound env, then the user's `env`. */
51
+ export const programEnv = Effect.fn(function* (
52
+ port: number,
53
+ bound: Readonly<Record<string, unknown>>,
54
+ env: Readonly<Record<string, unknown>> | undefined,
55
+ ) {
56
+ const stack = yield* Stack;
57
+ const values = Object.fromEntries(
58
+ Object.entries({ ...bound, ...env }).flatMap(([key, value]) => {
59
+ const text = envValue(value);
60
+ return text === undefined ? [] : [[key, text]];
61
+ }),
62
+ ) as Record<string, string>;
63
+ return {
64
+ ALCHEMY_STACK_NAME: stack.name,
65
+ ALCHEMY_STAGE: stack.stage,
66
+ ALCHEMY_PHASE: "runtime",
67
+ HOST: "0.0.0.0",
68
+ PORT: String(port),
69
+ ...values,
70
+ };
71
+ });
72
+
73
+ /** The template's snapshot is taken once the program answers HTTP. */
74
+ export const programReadyCmd = (port: number) =>
75
+ `bun -e "fetch('http://127.0.0.1:${port}/').then(()=>process.exit(0),()=>process.exit(1))"`;