@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,437 @@
1
+ import * as auth from "@distilled.cloud/e2b/auth";
2
+ import { credentials as e2bCredentials, DEFAULT_DOMAIN } from "@distilled.cloud/e2b/Credentials";
3
+ import {
4
+ AuthError,
5
+ AuthProviderLayer,
6
+ NeedsReauth,
7
+ refreshHint,
8
+ type ConfigureField,
9
+ type ConfigureMethod,
10
+ type ProviderDetails,
11
+ } from "alchemy/Auth/AuthProvider";
12
+ import { browserOAuth } from "alchemy/Auth/BrowserOAuth";
13
+ import { displayRedacted } from "alchemy/Auth/Credentials";
14
+ import { getEnv, getEnvRedacted, mapPromptCancellation } from "alchemy/Auth/Env";
15
+ import { withProfileCredentialsLock } from "alchemy/Auth/Lock";
16
+ import {
17
+ collectFieldValues,
18
+ storedSecret,
19
+ storedValueText,
20
+ validateFieldValues,
21
+ type StoredValues,
22
+ } from "alchemy/Auth/StoredAuthProvider";
23
+ import * as Interaction from "alchemy/Interaction";
24
+ import * as Clock from "effect/Clock";
25
+ import * as Duration from "effect/Duration";
26
+ import * as Effect from "effect/Effect";
27
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
28
+ import * as Layer from "effect/Layer";
29
+ import * as Match from "effect/Match";
30
+ import * as Redacted from "effect/Redacted";
31
+ import * as Schema from "effect/Schema";
32
+ import {
33
+ grantFromUrl,
34
+ refreshGrant,
35
+ revokeGrant,
36
+ startLogin,
37
+ type E2BGrant,
38
+ } from "./internal/Login.ts";
39
+
40
+ export const E2B_AUTH_PROVIDER_NAME = "E2B";
41
+
42
+ /**
43
+ * A profile's E2B values: an OAuth grant from the browser login (`oauth`),
44
+ * or a pasted access token or API key (`stored`).
45
+ */
46
+ export const E2BAuthConfigSchema = Schema.Union([
47
+ Schema.Struct({
48
+ method: Schema.Literal("oauth"),
49
+ email: Schema.String,
50
+ teamId: Schema.String,
51
+ teamName: Schema.String,
52
+ clientId: Schema.String,
53
+ tokenEndpoint: Schema.String,
54
+ revokeEndpoint: Schema.String,
55
+ access: Schema.String,
56
+ refresh: Schema.String,
57
+ expires: Schema.Number,
58
+ domain: Schema.optional(Schema.String),
59
+ }),
60
+ Schema.Struct({
61
+ method: Schema.Literal("stored"),
62
+ accessToken: Schema.optional(Schema.String),
63
+ teamId: Schema.optional(Schema.String),
64
+ apiKey: Schema.optional(Schema.String),
65
+ domain: Schema.optional(Schema.String),
66
+ }),
67
+ ]);
68
+ export type E2BAuthConfig = typeof E2BAuthConfigSchema.Type;
69
+ type OAuthConfig = Extract<E2BAuthConfig, { method: "oauth" }>;
70
+
71
+ /**
72
+ * Resolved E2B credentials.
73
+ *
74
+ * `accessToken` reaches every control-plane endpoint, and it is the only
75
+ * credential that can mint API keys, which bindings and `E2B.ApiKey` need.
76
+ * `teamId` is looked up from the token when unset. `apiKey` is a team API
77
+ * key: enough for resources, not for bindings. `expires` is set for an
78
+ * OAuth access token, so it is refreshed before it lapses.
79
+ */
80
+ export interface E2BResolvedCredentials {
81
+ readonly apiKey: Redacted.Redacted<string> | undefined;
82
+ readonly accessToken: Redacted.Redacted<string> | undefined;
83
+ readonly teamId: string | undefined;
84
+ readonly domain: string;
85
+ readonly expires?: number;
86
+ readonly source: { readonly type: E2BAuthConfig["method"] | "env"; readonly details?: string };
87
+ }
88
+
89
+ const storedFields: ReadonlyArray<ConfigureField> = [
90
+ {
91
+ name: "accessToken",
92
+ label: "E2B access token",
93
+ description: "Reaches every endpoint and mints the API keys bindings need.",
94
+ secret: true,
95
+ optional: true,
96
+ },
97
+ { name: "teamId", label: "E2B team id, if not your default team", optional: true },
98
+ {
99
+ name: "apiKey",
100
+ label: "E2B API key (e2b_…)",
101
+ description: "Only without an access token: enough for resources, not for bindings.",
102
+ secret: true,
103
+ optional: true,
104
+ },
105
+ { name: "domain", label: "E2B domain", optional: true, placeholder: DEFAULT_DOMAIN },
106
+ ];
107
+
108
+ const storedConfig = (values: StoredValues): E2BAuthConfig => ({
109
+ method: "stored",
110
+ ...Object.fromEntries(
111
+ Object.entries(values).flatMap(([key, value]) => {
112
+ const text = storedValueText(value);
113
+ return text === undefined ? [] : [[key, text]];
114
+ }),
115
+ ),
116
+ });
117
+
118
+ const grantOf = (config: OAuthConfig): E2BGrant => ({
119
+ email: config.email,
120
+ access: Redacted.make(config.access),
121
+ refresh: Redacted.make(config.refresh),
122
+ tokenEndpoint: config.tokenEndpoint,
123
+ revokeEndpoint: config.revokeEndpoint,
124
+ clientId: config.clientId,
125
+ expires: config.expires,
126
+ });
127
+
128
+ const withGrant = (config: OAuthConfig, grant: E2BGrant): OAuthConfig => ({
129
+ ...config,
130
+ access: Redacted.value(grant.access),
131
+ refresh: Redacted.value(grant.refresh),
132
+ expires: grant.expires,
133
+ });
134
+
135
+ /** The team the token acts on: its default team, as `e2b auth login` picks it. */
136
+ const defaultTeam = (access: Redacted.Redacted<string>, domain: string) =>
137
+ auth.listTeams({}).pipe(
138
+ Effect.provide(
139
+ Layer.mergeAll(e2bCredentials({ accessToken: access, domain }), FetchHttpClient.layer),
140
+ ),
141
+ Effect.flatMap((teams) => {
142
+ const team = teams.find((t) => t.isDefault) ?? (teams.length === 1 ? teams[0] : undefined);
143
+ return team === undefined
144
+ ? Effect.fail(AuthError.make({ message: "E2B: the account has no default team" }))
145
+ : Effect.succeed(team);
146
+ }),
147
+ Effect.mapError((cause) =>
148
+ cause._tag === "AuthError"
149
+ ? cause
150
+ : AuthError.make({ message: "E2B: could not list the account's teams", cause }),
151
+ ),
152
+ );
153
+
154
+ const readEnvironment = Effect.gen(function* () {
155
+ const apiKey = yield* getEnvRedacted("E2B_API_KEY");
156
+ const accessToken = yield* getEnvRedacted("E2B_ACCESS_TOKEN");
157
+ if (!apiKey && !accessToken) {
158
+ return yield* AuthError.make({
159
+ message:
160
+ "E2B CI credentials not found. Set E2B_ACCESS_TOKEN (or E2B_API_KEY for resources only).",
161
+ });
162
+ }
163
+ return {
164
+ apiKey,
165
+ accessToken,
166
+ teamId: yield* getEnv("E2B_TEAM_ID"),
167
+ domain: (yield* getEnv("E2B_DOMAIN")) ?? DEFAULT_DOMAIN,
168
+ source: { type: "env" as const },
169
+ } satisfies E2BResolvedCredentials;
170
+ });
171
+
172
+ /**
173
+ * Layer that registers the E2B {@link AuthProvider} into the
174
+ * {@link AuthProviders} registry when built. Included in `E2B.providers()`
175
+ * so `alchemy profile edit --add E2B` can configure it.
176
+ *
177
+ * Methods:
178
+ * - `oauth`: E2B's browser login, the one `e2b auth login` runs; the access
179
+ * token refreshes itself.
180
+ * - `stored`: a pasted access token or API key.
181
+ */
182
+ export const E2BAuth = AuthProviderLayer<E2BAuthConfig, E2BResolvedCredentials>()(
183
+ E2B_AUTH_PROVIDER_NAME,
184
+ Effect.gen(function* () {
185
+ const browserLogin = Effect.fn(function* (domain: string) {
186
+ const grant = yield* Effect.scoped(
187
+ Effect.gen(function* () {
188
+ const login = yield* startLogin(domain);
189
+ return yield* browserOAuth({
190
+ provider: "E2B",
191
+ url: login.url,
192
+ callback: login.grant,
193
+ exchange: grantFromUrl,
194
+ });
195
+ }),
196
+ );
197
+ const team = yield* defaultTeam(grant.access, domain);
198
+ yield* Interaction.success(`E2B: logged in as ${grant.email} (team ${team.name}).`);
199
+ return {
200
+ method: "oauth" as const,
201
+ email: grant.email,
202
+ teamId: team.teamID,
203
+ teamName: team.name,
204
+ clientId: grant.clientId,
205
+ tokenEndpoint: grant.tokenEndpoint,
206
+ revokeEndpoint: grant.revokeEndpoint,
207
+ access: Redacted.value(grant.access),
208
+ refresh: Redacted.value(grant.refresh),
209
+ expires: grant.expires,
210
+ ...(domain === DEFAULT_DOMAIN ? {} : { domain }),
211
+ } satisfies OAuthConfig;
212
+ });
213
+
214
+ const configure = (_profileName: string) =>
215
+ Interaction.select({
216
+ message: "E2B authentication method",
217
+ options: [
218
+ {
219
+ value: "oauth" as const,
220
+ label: "Browser login",
221
+ description: "recommended — sign in to E2B; the token refreshes itself",
222
+ },
223
+ {
224
+ value: "stored" as const,
225
+ label: "Access token or API key",
226
+ description: "paste a token and store it in the profile",
227
+ },
228
+ ],
229
+ }).pipe(
230
+ mapPromptCancellation,
231
+ Effect.flatMap((method) =>
232
+ method === "oauth"
233
+ ? browserLogin(DEFAULT_DOMAIN)
234
+ : collectFieldValues(storedFields).pipe(
235
+ Effect.map(storedConfig),
236
+ Effect.tap(() => Interaction.success("E2B: credentials saved.")),
237
+ ),
238
+ ),
239
+ Effect.mapError((cause) =>
240
+ cause._tag === "AuthError"
241
+ ? cause
242
+ : AuthError.make({
243
+ message: `E2B: failed to configure credentials: ${cause.message}`,
244
+ cause,
245
+ }),
246
+ ),
247
+ );
248
+
249
+ const configureMethods: ReadonlyArray<ConfigureMethod> = [
250
+ { method: "oauth", fields: [] },
251
+ { method: "stored", fields: storedFields },
252
+ ];
253
+
254
+ const configureWith = (
255
+ _profileName: string,
256
+ input: { readonly method: string; readonly values: Record<string, string> },
257
+ ) =>
258
+ input.method === "oauth"
259
+ ? browserLogin(DEFAULT_DOMAIN).pipe(
260
+ Effect.mapError((cause) =>
261
+ cause._tag === "AuthError"
262
+ ? cause
263
+ : AuthError.make({ message: `E2B: browser login failed: ${cause.message}`, cause }),
264
+ ),
265
+ )
266
+ : input.method === "stored"
267
+ ? validateFieldValues(E2B_AUTH_PROVIDER_NAME, storedFields, input.values).pipe(
268
+ Effect.map(storedConfig),
269
+ Effect.tap(() => Interaction.success("E2B: credentials saved.")),
270
+ )
271
+ : Effect.fail(
272
+ AuthError.make({
273
+ message: `E2B: unknown method '${input.method}'. Valid methods: oauth, stored.`,
274
+ }),
275
+ );
276
+
277
+ /** Refreshes an OAuth grant that is within a minute of expiring. */
278
+ const freshGrant = Effect.fn(function* (profileName: string, config: OAuthConfig) {
279
+ const now = yield* Clock.currentTimeMillis;
280
+ if (config.expires > now + 60_000) return config;
281
+ return withGrant(
282
+ config,
283
+ yield* refreshGrant(grantOf(config)).pipe(
284
+ Effect.mapError((cause) =>
285
+ NeedsReauth.make({
286
+ provider: E2B_AUTH_PROVIDER_NAME,
287
+ profile: profileName,
288
+ message: `E2B login expired. ${refreshHint(E2B_AUTH_PROVIDER_NAME, profileName)}`,
289
+ cause,
290
+ }),
291
+ ),
292
+ ),
293
+ );
294
+ });
295
+
296
+ const read = (
297
+ profileName: string,
298
+ config: E2BAuthConfig,
299
+ updateConfig?: (config: E2BAuthConfig) => Effect.Effect<void, AuthError>,
300
+ ): Effect.Effect<E2BResolvedCredentials, AuthError | NeedsReauth> =>
301
+ Match.value(config).pipe(
302
+ Match.when({ method: "stored" }, (stored) =>
303
+ Effect.succeed({
304
+ apiKey: storedSecret(stored.apiKey),
305
+ accessToken: storedSecret(stored.accessToken),
306
+ teamId: stored.teamId,
307
+ domain: stored.domain ?? DEFAULT_DOMAIN,
308
+ source: { type: "stored" as const },
309
+ } satisfies E2BResolvedCredentials),
310
+ ),
311
+ Match.when({ method: "oauth" }, (oauth) =>
312
+ Effect.gen(function* () {
313
+ const fresh = yield* freshGrant(profileName, oauth);
314
+ if (fresh !== oauth) yield* updateConfig?.(fresh) ?? Effect.void;
315
+ return {
316
+ apiKey: undefined,
317
+ accessToken: Redacted.make(fresh.access),
318
+ teamId: fresh.teamId,
319
+ domain: fresh.domain ?? DEFAULT_DOMAIN,
320
+ expires: fresh.expires,
321
+ source: { type: "oauth" as const, details: fresh.email },
322
+ } satisfies E2BResolvedCredentials;
323
+ }),
324
+ ),
325
+ Match.exhaustive,
326
+ );
327
+
328
+ const login = (
329
+ profileName: string,
330
+ config: E2BAuthConfig,
331
+ updateConfig?: (config: E2BAuthConfig) => Effect.Effect<void, AuthError>,
332
+ reload?: Effect.Effect<E2BAuthConfig, AuthError>,
333
+ ) =>
334
+ Match.value(config).pipe(
335
+ Match.when({ method: "stored" }, (stored) => Effect.succeed(stored)),
336
+ Match.when({ method: "oauth" }, (oauth) =>
337
+ // Refresh tokens can be single-use: refresh the one stored now,
338
+ // under the lock; a refused refresh falls back to the browser.
339
+ withProfileCredentialsLock(
340
+ profileName,
341
+ Effect.gen(function* () {
342
+ const current = yield* reload ?? Effect.succeed(oauth);
343
+ if (current.method !== "oauth") return current;
344
+ const refreshed = withGrant(current, yield* refreshGrant(grantOf(current)));
345
+ if (updateConfig === undefined) return refreshed;
346
+ yield* updateConfig(refreshed);
347
+ return undefined;
348
+ }),
349
+ ).pipe(
350
+ Effect.catchTag("E2BLoginError", () => browserLogin(oauth.domain ?? DEFAULT_DOMAIN)),
351
+ ),
352
+ ),
353
+ Match.exhaustive,
354
+ Effect.mapError((cause) =>
355
+ cause._tag === "AuthError"
356
+ ? cause
357
+ : AuthError.make({ message: `E2B: login failed: ${cause.message}`, cause }),
358
+ ),
359
+ );
360
+
361
+ const logout = (_profileName: string, config: E2BAuthConfig) =>
362
+ config.method === "oauth" ? revokeGrant(grantOf(config)) : Effect.void;
363
+
364
+ const details = (
365
+ profileName: string,
366
+ config: E2BAuthConfig,
367
+ updateConfig?: (config: E2BAuthConfig) => Effect.Effect<void, AuthError>,
368
+ ) =>
369
+ Effect.all([read(profileName, config, updateConfig), Clock.currentTimeMillis]).pipe(
370
+ Effect.map(([creds, now]): ProviderDetails => {
371
+ const lines: Array<{ key: string; value: string }> = [];
372
+ if (config.method === "oauth") lines.push({ key: "email", value: config.email });
373
+ if (creds.accessToken) {
374
+ lines.push({ key: "accessToken", value: displayRedacted(creds.accessToken) });
375
+ }
376
+ if (creds.expires !== undefined) {
377
+ const remaining = creds.expires - now;
378
+ lines.push({
379
+ key: "expires",
380
+ value:
381
+ remaining <= 0
382
+ ? "expired"
383
+ : `in ${Duration.format(Duration.millis(remaining))} (refreshes itself)`,
384
+ });
385
+ }
386
+ if (creds.apiKey) lines.push({ key: "apiKey", value: displayRedacted(creds.apiKey) });
387
+ lines.push({
388
+ key: "team",
389
+ value:
390
+ config.method === "oauth"
391
+ ? `${config.teamName} (${config.teamId})`
392
+ : (creds.teamId ?? "default"),
393
+ });
394
+ lines.push({ key: "source", value: creds.source.type });
395
+ return { lines };
396
+ }),
397
+ );
398
+
399
+ return {
400
+ configSchema: E2BAuthConfigSchema,
401
+ configure,
402
+ configureWith,
403
+ configureMethods,
404
+ login,
405
+ logout,
406
+ details,
407
+ read,
408
+ readEnvironment,
409
+ environment: [
410
+ {
411
+ name: "E2B_ACCESS_TOKEN",
412
+ required: false,
413
+ secret: true,
414
+ description:
415
+ "Access token. Reaches every endpoint and mints the API keys bindings and E2B.ApiKey need.",
416
+ },
417
+ {
418
+ name: "E2B_TEAM_ID",
419
+ required: false,
420
+ description: "Team id sent with the access token. Defaults to the token's default team.",
421
+ },
422
+ {
423
+ name: "E2B_API_KEY",
424
+ required: false,
425
+ secret: true,
426
+ description:
427
+ "Team API key, sent as X-API-Key. Enough for resources; bindings need the access token.",
428
+ },
429
+ {
430
+ name: "E2B_DOMAIN",
431
+ required: false,
432
+ description: "E2B domain for self-hosted deployments.",
433
+ },
434
+ ],
435
+ };
436
+ }),
437
+ );
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Process bootstrap for `E2B.Template` programs (a Bun process serving the
3
+ * bundled program). The generated entry imports this module and the user's
4
+ * `main`, nothing else — see `alchemy/Runtime/Bootstrap` for why.
5
+ */
6
+ import { BunServices } from "@effect/platform-bun";
7
+ import { BunHttpServer } from "alchemy/Http";
8
+ import { reifyBoundConfigProvider } from "alchemy/Runtime";
9
+ import {
10
+ entrypointLayer,
11
+ resolveProgram,
12
+ runProcess,
13
+ stackFromEnv,
14
+ } from "alchemy/Runtime/Bootstrap";
15
+ import * as ConfigProvider from "effect/ConfigProvider";
16
+ import * as Effect from "effect/Effect";
17
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
18
+ import * as Layer from "effect/Layer";
19
+ import * as Logger from "effect/Logger";
20
+
21
+ /**
22
+ * Resolve the bundled program (the runners registered via `host.run` /
23
+ * serve) and run it with a Bun HTTP server bound to `PORT`, so the returned
24
+ * `{ fetch }` handler is actually served and `host.run` loops stay alive.
25
+ */
26
+ export const bootstrap = (entrypoint: unknown): Promise<void> => {
27
+ const platform = Layer.mergeAll(
28
+ BunServices.layer,
29
+ FetchHttpClient.layer,
30
+ Logger.layer([Logger.consolePretty()]),
31
+ );
32
+
33
+ const program = resolveProgram("program").pipe(
34
+ Effect.provide(
35
+ entrypointLayer(entrypoint).pipe(
36
+ Layer.provideMerge(stackFromEnv),
37
+ Layer.provideMerge(BunHttpServer()),
38
+ Layer.provideMerge(platform),
39
+ Layer.provideMerge(
40
+ Layer.succeed(
41
+ ConfigProvider.ConfigProvider,
42
+ reifyBoundConfigProvider(ConfigProvider.fromEnv(), process.env),
43
+ ),
44
+ ),
45
+ ),
46
+ ),
47
+ Effect.scoped,
48
+ );
49
+
50
+ return runProcess("Docker service", program);
51
+ };
@@ -0,0 +1,71 @@
1
+ import * as Binding from "alchemy/Binding";
2
+ import type * as Effect from "effect/Effect";
3
+ import type { Sandbox } from "./Sandbox.ts";
4
+ import type { ConnectedSandbox } from "./SandboxClient.ts";
5
+
6
+ /**
7
+ * Work with one {@link Sandbox} declared in the stack, from a Worker,
8
+ * Function or any other host: run commands, read and write files, open
9
+ * terminals, call its ports, snapshot or fork it. The handle has the same
10
+ * operations as the sandboxes `CreateSandbox` returns; its `sandboxId` and
11
+ * `url(port)` are Effects, read at runtime.
12
+ *
13
+ * A declared sandbox pauses when it is idle. With `autoResume` (the
14
+ * default), E2B resumes it on the next request, to one of its ports or from
15
+ * any call here. With `autoResume: false`, calls inside it (`exec`,
16
+ * `start`, `files`, `pty`) fail with `SandboxNotRunning` until you call
17
+ * `resume()`.
18
+ *
19
+ * The host gets its own E2B API key, minted at deploy and revoked when the
20
+ * host is destroyed. E2B keys are not scoped, so the key reaches the whole
21
+ * team; it is never the deployer's own.
22
+ *
23
+ * ### Running commands in a dev box
24
+ * **Example:** Pull and test on request
25
+ * ```typescript
26
+ * const devBox = yield* E2B.ConnectSandbox(DevBox);
27
+ * return {
28
+ * fetch: Effect.gen(function* () {
29
+ * yield* devBox.resume({ timeout: "10 minutes" });
30
+ * const test = yield* devBox.exec("git pull && npm test", { cwd: "/home/user/repo" });
31
+ * return yield* HttpServerResponse.json(test);
32
+ * }),
33
+ * };
34
+ * ```
35
+ *
36
+ * ### Streaming a process
37
+ * **Example:** Stream a build's output
38
+ * ```typescript
39
+ * const build = yield* devBox.start("npm run build", { cwd: "/home/user/repo" });
40
+ * const output = build.output.pipe(Stream.map((chunk) => chunk.data));
41
+ * ```
42
+ *
43
+ * ### Calling a program in the sandbox
44
+ * **Example:** Call port 3000
45
+ * ```typescript
46
+ * const port = yield* api.getTcpPort(3000);
47
+ * const response = yield* port.fetch(HttpClientRequest.get("/health"));
48
+ * ```
49
+ *
50
+ * **Example:** Proxy the Worker's request to port 3000
51
+ * ```typescript
52
+ * const port = yield* api.getTcpPort(3000);
53
+ * return yield* port.fetch(yield* HttpServerRequest);
54
+ * ```
55
+ *
56
+ * ### Pausing it early
57
+ * **Example:** Pause when the work is done
58
+ * ```typescript
59
+ * yield* devBox.pause();
60
+ * ```
61
+ *
62
+ * @binding
63
+ * @product Sandbox
64
+ */
65
+ export interface ConnectSandbox extends Binding.Service<
66
+ ConnectSandbox,
67
+ "E2B.ConnectSandbox",
68
+ (sandbox: Sandbox) => Effect.Effect<ConnectedSandbox>
69
+ > {}
70
+
71
+ export const ConnectSandbox = Binding.Service<ConnectSandbox>("E2B.ConnectSandbox");
@@ -0,0 +1,38 @@
1
+ import type * as Output from "alchemy/Output";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import type * as Redacted from "effect/Redacted";
5
+ import { ConnectSandbox } from "./ConnectSandbox.ts";
6
+ import { bindE2B } from "./E2BBindings.ts";
7
+ import { makeConnectedSandbox } from "./internal/SandboxHandle.ts";
8
+ import type { Sandbox } from "./Sandbox.ts";
9
+
10
+ /**
11
+ * HTTP implementation of {@link ConnectSandbox}: E2B's control plane with
12
+ * the host's key, and envd inside the sandbox with the sandbox's own token.
13
+ * Works on any host.
14
+ *
15
+ * ### Provide the layer
16
+ * **Example:** On a Cloudflare Worker
17
+ * ```typescript
18
+ * Effect.gen(function* () {
19
+ * const devBox = yield* E2B.ConnectSandbox(DevBox);
20
+ * // ...
21
+ * }).pipe(Effect.provide(E2B.ConnectSandboxHttp))
22
+ * ```
23
+ *
24
+ * @layer
25
+ * @product Sandbox
26
+ * @provides E2B.ConnectSandbox
27
+ */
28
+ export const ConnectSandboxHttp = Layer.succeed(
29
+ ConnectSandbox,
30
+ Effect.fn("E2B.ConnectSandbox")(function* (sandbox: Sandbox) {
31
+ const auth = yield* bindE2B;
32
+ const sandboxId = yield* (sandbox.sandboxId as Output.Output<string, never>).asEffect();
33
+ const trafficToken = yield* (
34
+ sandbox.trafficAccessToken as Output.Output<Redacted.Redacted<string> | undefined, never>
35
+ ).asEffect();
36
+ return makeConnectedSandbox(auth, sandboxId, trafficToken);
37
+ }),
38
+ );
@@ -0,0 +1,9 @@
1
+ import * as Contributions from "alchemy/Contributions";
2
+
3
+ export default Contributions.make({
4
+ name: "e2b",
5
+ auth: Contributions.lazy(
6
+ () => import("./AuthProvider.ts"),
7
+ (module) => module.E2BAuth,
8
+ ),
9
+ });