@lakitu/sdk 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,8 @@
1
1
  /**
2
2
  * lakitu build
3
3
  *
4
- * Build E2B sandbox template with pre-deployed Convex functions.
5
- *
6
- * Strategy:
7
- * 1. Start local convex-backend
8
- * 2. Deploy sandbox functions with `convex dev --once`
9
- * 3. Capture the state directory
10
- * 4. Build E2B template with pre-built state baked in
4
+ * Build E2B sandbox template using Dockerfile approach.
5
+ * Uses fromDockerfile() to leverage Docker's COPY instead of SDK's problematic tar streaming.
11
6
  */
12
7
  interface BuildOptions {
13
8
  base?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../cli/commands/build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAcH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAyTD,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,iBAoBhD"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../cli/commands/build.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAiMD,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,iBAgBhD"}
@@ -1,30 +1,22 @@
1
1
  /**
2
2
  * lakitu build
3
3
  *
4
- * Build E2B sandbox template with pre-deployed Convex functions.
5
- *
6
- * Strategy:
7
- * 1. Start local convex-backend
8
- * 2. Deploy sandbox functions with `convex dev --once`
9
- * 3. Capture the state directory
10
- * 4. Build E2B template with pre-built state baked in
4
+ * Build E2B sandbox template using Dockerfile approach.
5
+ * Uses fromDockerfile() to leverage Docker's COPY instead of SDK's problematic tar streaming.
11
6
  */
12
7
  import { Template, defaultBuildLogger, waitForPort } from "e2b";
13
8
  import { existsSync, mkdirSync, rmSync, cpSync, writeFileSync, readFileSync, readdirSync } from "fs";
14
9
  import { join, dirname } from "path";
15
- import { execSync, spawn } from "child_process";
10
+ import { execSync } from "child_process";
16
11
  import { fileURLToPath } from "url";
17
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
- // Handle both TS source (cli/commands) and compiled JS (dist/cli/commands)
19
13
  const PACKAGE_ROOT = __dirname.includes("/dist/")
20
14
  ? join(__dirname, "../../..")
21
15
  : join(__dirname, "../..");
22
16
  function findApiKey() {
23
- // 1. Environment variable
24
17
  if (process.env.E2B_API_KEY) {
25
18
  return { key: process.env.E2B_API_KEY, source: "E2B_API_KEY env var" };
26
19
  }
27
- // 2. .env files
28
20
  const envPaths = [
29
21
  join(process.cwd(), ".env.local"),
30
22
  join(process.cwd(), ".env"),
@@ -38,15 +30,14 @@ function findApiKey() {
38
30
  }
39
31
  catch { /* not found */ }
40
32
  }
41
- // 3. E2B CLI config
42
33
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
43
34
  try {
44
35
  const configPath = join(homeDir, ".e2b/config.json");
45
36
  const config = JSON.parse(readFileSync(configPath, "utf-8"));
46
37
  if (config.teamApiKey)
47
- return { key: config.teamApiKey, source: "~/.e2b/config.json (teamApiKey)" };
38
+ return { key: config.teamApiKey, source: "~/.e2b/config.json" };
48
39
  if (config.accessToken)
49
- return { key: config.accessToken, source: "~/.e2b/config.json (accessToken)" };
40
+ return { key: config.accessToken, source: "~/.e2b/config.json" };
50
41
  }
51
42
  catch { /* not found */ }
52
43
  return null;
@@ -55,15 +46,7 @@ function preflightCheck() {
55
46
  const result = findApiKey();
56
47
  if (!result) {
57
48
  console.error("āŒ E2B API key not found\n");
58
- console.error("To fix this, do ONE of the following:\n");
59
- console.error(" 1. Set environment variable:");
60
- console.error(" export E2B_API_KEY=your_key\n");
61
- console.error(" 2. Add to .env.local:");
62
- console.error(" echo 'E2B_API_KEY=your_key' >> .env.local\n");
63
- console.error(" 3. Login via E2B CLI:");
64
- console.error(" npm install -g @e2b/cli");
65
- console.error(" e2b auth login\n");
66
- console.error("Get your API key at: https://e2b.dev/dashboard\n");
49
+ console.error("Set E2B_API_KEY in .env.local or run 'e2b auth login'\n");
67
50
  process.exit(1);
68
51
  }
69
52
  console.log(`šŸ”‘ Using API key from ${result.source}\n`);
@@ -72,85 +55,7 @@ function preflightCheck() {
72
55
  async function sleep(ms) {
73
56
  return new Promise(resolve => setTimeout(resolve, ms));
74
57
  }
75
- /**
76
- * Pre-build Convex locally: start backend, deploy functions, capture state
77
- */
78
- async function prebuildConvex() {
79
- const stateDir = "/tmp/lakitu-convex-state";
80
- const sandboxConvexDir = join(PACKAGE_ROOT, "convex/sandbox");
81
- console.log("=== Pre-building Convex locally ===");
82
- // Clean up any existing state
83
- rmSync(stateDir, { recursive: true, force: true });
84
- mkdirSync(stateDir, { recursive: true });
85
- // Kill any existing convex-backend
86
- try {
87
- execSync("pkill -f convex-backend", { stdio: "ignore" });
88
- await sleep(1000);
89
- }
90
- catch { /* not running */ }
91
- console.log("Starting local convex-backend...");
92
- // Filter out Bun's node shim from PATH so convex-backend finds real Node.js
93
- const cleanPath = (process.env.PATH || "")
94
- .split(":")
95
- .filter(p => !p.includes("bun-node-"))
96
- .join(":");
97
- // Start convex-backend in background
98
- const backend = spawn("convex-backend", [
99
- join(stateDir, "convex_local_backend.sqlite3"),
100
- "--port", "3210",
101
- "--site-proxy-port", "3211",
102
- "--local-storage", stateDir,
103
- ], {
104
- cwd: stateDir,
105
- stdio: "pipe",
106
- env: { ...process.env, PATH: cleanPath },
107
- });
108
- // Wait for backend to be ready
109
- console.log("Waiting for backend to be ready...");
110
- for (let i = 0; i < 30; i++) {
111
- try {
112
- const res = await fetch("http://127.0.0.1:3210/version");
113
- if (res.ok) {
114
- console.log(`Backend ready after ${i + 1} seconds`);
115
- break;
116
- }
117
- }
118
- catch { /* not ready yet */ }
119
- if (i === 29) {
120
- backend.kill();
121
- throw new Error("Backend failed to start after 30 seconds");
122
- }
123
- await sleep(1000);
124
- }
125
- // Deploy functions using convex dev --once
126
- console.log("Deploying functions with convex dev --once...");
127
- const tempEnvFile = "/tmp/lakitu-convex-env";
128
- writeFileSync(tempEnvFile, `CONVEX_SELF_HOSTED_URL=http://127.0.0.1:3210
129
- CONVEX_SELF_HOSTED_ADMIN_KEY=0135d8598650f8f5cb0f30c34ec2e2bb62793bc28717c8eb6fb577996d50be5f4281b59181095065c5d0f86a2c31ddbe9b597ec62b47ded69782cd
130
- `);
131
- try {
132
- // Run from package root where convex.json is (specifies functions: "convex/sandbox")
133
- execSync(`npx convex dev --once --typecheck disable --env-file ${tempEnvFile}`, {
134
- cwd: PACKAGE_ROOT,
135
- stdio: "inherit",
136
- env: { ...process.env, CONVEX_DEPLOYMENT: undefined, PATH: cleanPath },
137
- });
138
- console.log("Functions deployed successfully!");
139
- }
140
- catch (error) {
141
- backend.kill();
142
- throw new Error("Convex deploy failed");
143
- }
144
- // Give backend a moment to flush state
145
- await sleep(2000);
146
- // Stop backend gracefully
147
- console.log("Stopping backend...");
148
- backend.kill("SIGTERM");
149
- await sleep(1000);
150
- console.log("=== Pre-build complete ===\n");
151
- return stateDir;
152
- }
153
- // Base template: Ubuntu + Bun + Convex Backend + Node.js
58
+ // Base template using standard Template builder
154
59
  const baseTemplate = Template()
155
60
  .fromImage("e2bdev/code-interpreter:latest")
156
61
  .runCmd("sudo apt-get update && sudo apt-get install -y git curl sqlite3 libsqlite3-dev build-essential unzip")
@@ -170,47 +75,6 @@ const baseTemplate = Template()
170
75
  CONVEX_URL: "http://localhost:3210",
171
76
  LOCAL_CONVEX_URL: "http://localhost:3210",
172
77
  });
173
- // Custom template: Add Lakitu code + PRE-BUILT Convex state + AUTO-START backend
174
- // Uses tar files to bypass E2B's extraction issues with directory COPY
175
- function customTemplate(baseId, buildDir, hasProjectKsa) {
176
- const copyKsaCmd = hasProjectKsa
177
- ? 'tar -xzf /tmp/project-ksa.tar.gz -C /home/user && cp -r /home/user/project-ksa/*.ts /home/user/lakitu/ksa/ && '
178
- : '';
179
- return Template()
180
- .fromTemplate(baseId)
181
- // Copy tar archives instead of directories
182
- .copy(`${buildDir}/lakitu.tar.gz`, "/tmp/lakitu.tar.gz")
183
- .copy(`${buildDir}/start.sh`, "/home/user/start.sh")
184
- .copy(`${buildDir}/convex-state.tar.gz`, "/tmp/convex-state.tar.gz")
185
- .copy(hasProjectKsa ? `${buildDir}/project-ksa.tar.gz` : `${buildDir}/start.sh`, hasProjectKsa ? "/tmp/project-ksa.tar.gz" : "/tmp/dummy.sh")
186
- // Extract tars and set up
187
- .runCmd(`
188
- cd /home/user && tar -xzf /tmp/lakitu.tar.gz && \
189
- mkdir -p /home/user/.convex/convex-backend-state && \
190
- tar -xzf /tmp/convex-state.tar.gz -C /home/user/.convex/convex-backend-state && \
191
- mv /home/user/.convex/convex-backend-state/convex-state /home/user/.convex/convex-backend-state/lakitu && \
192
- ${copyKsaCmd}chmod +x /home/user/start.sh && \
193
- export HOME=/home/user && \
194
- export PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin" && \
195
- cd /home/user/lakitu && bun install && \
196
- echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/pdf/pdf-generator.ts "$@"' | sudo tee /usr/local/bin/generate-pdf && \
197
- sudo chmod +x /usr/local/bin/generate-pdf && \
198
- echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/browser/agent-browser-cli.ts "$@"' | sudo tee /usr/local/bin/agent-browser && \
199
- sudo chmod +x /usr/local/bin/agent-browser && \
200
- ln -sf /home/user/lakitu/ksa /home/user/ksa && \
201
- chown -R user:user /home/user/lakitu /home/user/ksa /home/user/.convex && \
202
- rm /tmp/*.tar.gz /tmp/dummy.sh 2>/dev/null || true && \
203
- echo "KSA modules:" && ls /home/user/lakitu/ksa/*.ts 2>/dev/null | head -20
204
- `)
205
- .setEnvs({
206
- HOME: "/home/user",
207
- PATH: "/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin",
208
- CONVEX_URL: "http://localhost:3210",
209
- LOCAL_CONVEX_URL: "http://localhost:3210",
210
- CONVEX_LOCAL_STORAGE: "/home/user/.convex/convex-backend-state/lakitu",
211
- })
212
- .setStartCmd("/home/user/start.sh", waitForPort(3210));
213
- }
214
78
  async function buildBase(apiKey) {
215
79
  console.log("šŸ”§ Building Lakitu base template...\n");
216
80
  const result = await Template.build(baseTemplate, {
@@ -223,13 +87,10 @@ async function buildBase(apiKey) {
223
87
  }
224
88
  async function buildCustom(apiKey, baseId) {
225
89
  const buildDir = "/tmp/lakitu-build";
226
- // Step 1: Pre-build Convex locally
227
- const stateDir = await prebuildConvex();
228
- // Step 2: Prepare build context
229
- console.log("šŸ“¦ Preparing build context...");
90
+ console.log("šŸ“¦ Preparing Dockerfile build context...");
230
91
  rmSync(buildDir, { recursive: true, force: true });
231
92
  mkdirSync(buildDir, { recursive: true });
232
- // Copy lakitu source - only runtime files needed in sandbox
93
+ // Copy lakitu source
233
94
  execSync(`rsync -av \
234
95
  --exclude='node_modules' \
235
96
  --exclude='.git' \
@@ -246,53 +107,81 @@ async function buildCustom(apiKey, baseId) {
246
107
  --exclude='vitest.config.ts' \
247
108
  --exclude='CLAUDE.md' \
248
109
  --exclude='scripts' \
110
+ --exclude='convex/cloud' \
249
111
  ${PACKAGE_ROOT}/ ${join(buildDir, "lakitu")}/`, {
250
112
  stdio: "pipe",
251
113
  });
252
- // Copy user's project KSAs from lakitu/ folder to separate directory (if exists)
114
+ // Copy user's project KSAs
253
115
  const userKsaDir = join(process.cwd(), "lakitu");
254
- let hasProjectKsa = false;
255
116
  if (existsSync(userKsaDir)) {
256
117
  console.log(" Copying project KSAs from lakitu/...");
257
118
  const ksaFiles = readdirSync(userKsaDir).filter((f) => f.endsWith(".ts"));
258
- if (ksaFiles.length > 0) {
259
- mkdirSync(join(buildDir, "project-ksa"), { recursive: true });
260
- for (const file of ksaFiles) {
261
- cpSync(join(userKsaDir, file), join(buildDir, "project-ksa", file));
262
- }
263
- hasProjectKsa = true;
264
- console.log(` āœ“ Copied ${ksaFiles.length} project KSAs`);
119
+ for (const file of ksaFiles) {
120
+ cpSync(join(userKsaDir, file), join(buildDir, "lakitu/ksa", file));
265
121
  }
122
+ console.log(` āœ“ Copied ${ksaFiles.length} project KSAs`);
266
123
  }
267
124
  // Copy start script
268
125
  cpSync(join(PACKAGE_ROOT, "template/e2b/start.sh"), join(buildDir, "start.sh"));
269
- // Add timestamp file to force E2B cache invalidation
270
- // This ensures E2B re-uploads the lakitu directory instead of using stale cache
271
- writeFileSync(join(buildDir, "lakitu/.build-timestamp"), Date.now().toString());
272
- // Copy pre-built Convex state
273
- cpSync(stateDir, join(buildDir, "convex-state"), { recursive: true });
274
- // Create tar archives to bypass E2B's extraction issues
275
- console.log(" Creating tar archives...");
276
- execSync(`cd ${buildDir} && tar -czf lakitu.tar.gz lakitu`, { stdio: "pipe" });
277
- execSync(`cd ${buildDir} && tar -czf convex-state.tar.gz convex-state`, { stdio: "pipe" });
278
- if (hasProjectKsa) {
279
- execSync(`cd ${buildDir} && tar -czf project-ksa.tar.gz project-ksa`, { stdio: "pipe" });
280
- }
126
+ // Create Dockerfile that builds on base template
127
+ const dockerfile = `# Lakitu Custom Template
128
+ FROM ${baseId}
129
+
130
+ # Copy lakitu code (using Docker's COPY, not SDK's tar streaming)
131
+ COPY --chown=user:user lakitu/ /home/user/lakitu/
132
+ COPY --chown=user:user start.sh /home/user/start.sh
133
+
134
+ # Install dependencies and set up
135
+ RUN chmod +x /home/user/start.sh && \\
136
+ export HOME=/home/user && \\
137
+ export PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin" && \\
138
+ cd /home/user/lakitu && /home/user/.bun/bin/bun install && \\
139
+ echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/pdf/pdf-generator.ts "$@"' | sudo tee /usr/local/bin/generate-pdf && \\
140
+ sudo chmod +x /usr/local/bin/generate-pdf && \\
141
+ echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/browser/agent-browser-cli.ts "$@"' | sudo tee /usr/local/bin/agent-browser && \\
142
+ sudo chmod +x /usr/local/bin/agent-browser && \\
143
+ ln -sf /home/user/lakitu/ksa /home/user/ksa && \\
144
+ chown -R user:user /home/user/lakitu /home/user/ksa
145
+
146
+ # Pre-deploy Convex functions
147
+ RUN export HOME=/home/user && \\
148
+ export PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin" && \\
149
+ export CONVEX_LOCAL_STORAGE=/home/user/.convex/convex-backend-state/lakitu && \\
150
+ mkdir -p $CONVEX_LOCAL_STORAGE && \\
151
+ cd /home/user/lakitu && \\
152
+ convex-backend --port 3210 --site-proxy-port 3211 --local-storage $CONVEX_LOCAL_STORAGE & \\
153
+ sleep 5 && \\
154
+ ./node_modules/.bin/convex dev --once --typecheck disable && \\
155
+ sleep 2 && \\
156
+ pkill convex-backend || true && \\
157
+ chown -R user:user /home/user/.convex
158
+
159
+ ENV HOME=/home/user
160
+ ENV PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin"
161
+ ENV CONVEX_URL="http://localhost:3210"
162
+ ENV LOCAL_CONVEX_URL="http://localhost:3210"
163
+ ENV CONVEX_LOCAL_STORAGE="/home/user/.convex/convex-backend-state/lakitu"
164
+
165
+ USER user
166
+ WORKDIR /home/user/workspace
167
+ `;
168
+ writeFileSync(join(buildDir, "Dockerfile"), dockerfile);
281
169
  console.log(" āœ“ Build context ready\n");
282
- // Step 3: Build E2B template with pre-built state
283
- console.log(`šŸ”§ Building Lakitu custom template on ${baseId}...\n`);
284
- const result = await Template.build(customTemplate(baseId, buildDir, hasProjectKsa), {
170
+ console.log(`šŸ”§ Building Lakitu custom template (Dockerfile method)...\n`);
171
+ // Use fromDockerfile which leverages Docker's COPY instead of SDK tar streaming
172
+ const template = Template()
173
+ .fromDockerfile(join(buildDir, "Dockerfile"))
174
+ .setStartCmd("/home/user/start.sh", waitForPort(3210));
175
+ const result = await Template.build(template, {
285
176
  alias: "lakitu",
286
177
  apiKey,
287
178
  onBuildLogs: defaultBuildLogger(),
288
179
  });
289
180
  console.log(`\nāœ… Custom template: ${result.templateId}`);
290
- console.log(" Functions are PRE-DEPLOYED - sandbox starts instantly!");
291
181
  return result.templateId;
292
182
  }
293
183
  export async function build(options) {
294
- console.log("šŸ„ Lakitu Template Builder\n");
295
- // Preflight: Check for API key before doing any work
184
+ console.log("šŸ„ Lakitu Template Builder (Dockerfile method)\n");
296
185
  const apiKey = preflightCheck();
297
186
  if (options.base) {
298
187
  await buildBase(apiKey);
@@ -301,11 +190,9 @@ export async function build(options) {
301
190
  await buildCustom(apiKey, options.baseId || "lakitu-base");
302
191
  }
303
192
  else {
304
- // Build both
305
193
  const baseId = await buildBase(apiKey);
306
194
  await buildCustom(apiKey, baseId);
307
195
  }
308
196
  console.log("\nšŸŽ‰ Build complete!");
309
- // Force exit - E2B SDK keeps event loop alive
310
197
  process.exit(0);
311
198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lakitu/sdk",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Self-hosted AI agent framework for Convex + E2B with code execution",
5
5
  "type": "module",
6
6
  "main": "./dist/sdk/index.js",
@@ -0,0 +1,138 @@
1
+ #!/bin/bash
2
+ # Build E2B template using Dockerfile (bypasses SDK .copy() issues)
3
+ set -e
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
+ LAKITU_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
7
+ PROJECT_ROOT="$(cd "$LAKITU_DIR/../.." && pwd)"
8
+ BUILD_CONTEXT="/tmp/lakitu-docker-build"
9
+
10
+ echo "=== Lakitu Docker Build ==="
11
+ echo "Script dir: $SCRIPT_DIR"
12
+ echo "Lakitu dir: $LAKITU_DIR"
13
+ echo "Project root: $PROJECT_ROOT"
14
+
15
+ # Clean and create build context
16
+ rm -rf "$BUILD_CONTEXT"
17
+ mkdir -p "$BUILD_CONTEXT"
18
+
19
+ # Copy lakitu source (excluding unnecessary files)
20
+ echo "Copying lakitu source..."
21
+ rsync -av \
22
+ --exclude='node_modules' \
23
+ --exclude='.git' \
24
+ --exclude='template' \
25
+ --exclude='assets' \
26
+ --exclude='cli' \
27
+ --exclude='tests' \
28
+ --exclude='dist' \
29
+ --exclude='.github' \
30
+ --exclude='.env*' \
31
+ --exclude='convex/cloud' \
32
+ "$LAKITU_DIR/" "$BUILD_CONTEXT/lakitu/"
33
+
34
+ # Copy project KSAs if they exist
35
+ if [ -d "$PROJECT_ROOT/lakitu" ]; then
36
+ echo "Copying project KSAs..."
37
+ cp "$PROJECT_ROOT/lakitu/"*.ts "$BUILD_CONTEXT/lakitu/ksa/" 2>/dev/null || true
38
+ echo "Copied $(ls "$PROJECT_ROOT/lakitu/"*.ts 2>/dev/null | wc -l) project KSAs"
39
+ fi
40
+
41
+ # Copy Dockerfile and scripts
42
+ echo "Copying Docker files..."
43
+ cp "$SCRIPT_DIR/e2b/start.sh" "$BUILD_CONTEXT/"
44
+ cp "$SCRIPT_DIR/e2b/prebuild.sh" "$BUILD_CONTEXT/"
45
+
46
+ # Create Dockerfile in build context
47
+ cat > "$BUILD_CONTEXT/Dockerfile" << 'DOCKERFILE'
48
+ # Lakitu E2B Sandbox Template
49
+ FROM e2bdev/code-interpreter:latest
50
+
51
+ # System dependencies
52
+ RUN apt-get update && apt-get install -y \
53
+ git curl sqlite3 libsqlite3-dev \
54
+ build-essential unzip \
55
+ && rm -rf /var/lib/apt/lists/*
56
+
57
+ # Bun runtime
58
+ RUN curl -fsSL https://bun.sh/install | bash
59
+ ENV PATH="/root/.bun/bin:/home/user/.bun/bin:$PATH"
60
+
61
+ # Node.js for npx convex
62
+ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
63
+ apt-get install -y nodejs && \
64
+ rm -rf /var/lib/apt/lists/*
65
+
66
+ # Convex local backend
67
+ RUN curl -L -o /tmp/convex.zip "https://github.com/get-convex/convex-backend/releases/download/precompiled-2026-01-08-272e7f4/convex-local-backend-x86_64-unknown-linux-gnu.zip" && \
68
+ unzip /tmp/convex.zip -d /tmp && \
69
+ mv /tmp/convex-local-backend /usr/local/bin/convex-backend && \
70
+ chmod +x /usr/local/bin/convex-backend && \
71
+ rm /tmp/convex.zip
72
+
73
+ # Directory structure
74
+ RUN mkdir -p /home/user/workspace /home/user/.convex/convex-backend-state/lakitu /home/user/artifacts && \
75
+ chown -R user:user /home/user
76
+
77
+ # Copy lakitu code
78
+ COPY --chown=user:user lakitu/ /home/user/lakitu/
79
+
80
+ # Copy scripts
81
+ COPY --chown=user:user start.sh /home/user/start.sh
82
+ COPY --chown=user:user prebuild.sh /home/user/prebuild.sh
83
+ RUN chmod +x /home/user/start.sh /home/user/prebuild.sh
84
+
85
+ # Install dependencies
86
+ WORKDIR /home/user/lakitu
87
+ RUN /root/.bun/bin/bun install
88
+
89
+ # Create CLI tools
90
+ RUN echo '#!/bin/bash\nbun run /home/user/lakitu/runtime/pdf/pdf-generator.ts "$@"' > /usr/local/bin/generate-pdf && \
91
+ chmod +x /usr/local/bin/generate-pdf && \
92
+ echo '#!/bin/bash\nbun run /home/user/lakitu/runtime/browser/agent-browser-cli.ts "$@"' > /usr/local/bin/agent-browser && \
93
+ chmod +x /usr/local/bin/agent-browser
94
+
95
+ # Symlink KSA
96
+ RUN ln -sf /home/user/lakitu/ksa /home/user/ksa
97
+
98
+ # Pre-deploy Convex functions (bakes them into image)
99
+ RUN /home/user/prebuild.sh
100
+
101
+ # Fix ownership
102
+ RUN chown -R user:user /home/user
103
+
104
+ # Environment
105
+ ENV HOME=/home/user
106
+ ENV PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin"
107
+ ENV CONVEX_URL="http://localhost:3210"
108
+ ENV LOCAL_CONVEX_URL="http://localhost:3210"
109
+ ENV CONVEX_LOCAL_STORAGE="/home/user/.convex/convex-backend-state/lakitu"
110
+
111
+ USER user
112
+ WORKDIR /home/user/workspace
113
+ DOCKERFILE
114
+
115
+ # Create e2b.toml
116
+ cat > "$BUILD_CONTEXT/e2b.toml" << 'TOML'
117
+ template_id = "lakitu"
118
+ template_name = "lakitu"
119
+ dockerfile = "Dockerfile"
120
+ start_cmd = "/home/user/start.sh"
121
+ TOML
122
+
123
+ echo ""
124
+ echo "Build context ready at: $BUILD_CONTEXT"
125
+ echo "Contents:"
126
+ ls -la "$BUILD_CONTEXT"
127
+ echo ""
128
+ echo "Lakitu KSA modules:"
129
+ ls "$BUILD_CONTEXT/lakitu/ksa/"*.ts 2>/dev/null | head -10
130
+ echo ""
131
+
132
+ # Run E2B template build
133
+ echo "=== Running e2b template build ==="
134
+ cd "$BUILD_CONTEXT"
135
+ e2b template build
136
+
137
+ echo ""
138
+ echo "=== Build complete! ==="
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Build E2B template using Dockerfile (bypasses SDK .copy() issues)
4
+ *
5
+ * Uses Build System v2's fromDockerfile() which leverages Docker's build context
6
+ * instead of the problematic .copy() tar streaming.
7
+ */
8
+
9
+ import { Template, defaultBuildLogger, waitForPort } from "e2b";
10
+ import { $ } from "bun";
11
+ import { existsSync, readdirSync } from "fs";
12
+ import { join, dirname } from "path";
13
+
14
+ const SCRIPT_DIR = dirname(import.meta.path);
15
+ const LAKITU_DIR = join(SCRIPT_DIR, "..");
16
+ const PROJECT_ROOT = join(LAKITU_DIR, "../..");
17
+ const BUILD_CONTEXT = "/tmp/lakitu-docker-build";
18
+
19
+ async function getApiKey(): Promise<string> {
20
+ if (process.env.E2B_API_KEY) return process.env.E2B_API_KEY;
21
+
22
+ const envPaths = [
23
+ join(process.cwd(), ".env.local"),
24
+ join(PROJECT_ROOT, ".env.local"),
25
+ ];
26
+
27
+ for (const path of envPaths) {
28
+ try {
29
+ const content = await Bun.file(path).text();
30
+ const match = content.match(/E2B_API_KEY=(.+)/);
31
+ if (match) return match[1].trim();
32
+ } catch { /* not found */ }
33
+ }
34
+
35
+ throw new Error("E2B_API_KEY not found");
36
+ }
37
+
38
+ async function prepareBuildContext() {
39
+ console.log("=== Preparing Docker build context ===");
40
+
41
+ // Clean and create
42
+ await $`rm -rf ${BUILD_CONTEXT}`.quiet();
43
+ await $`mkdir -p ${BUILD_CONTEXT}`.quiet();
44
+
45
+ // Copy lakitu source
46
+ console.log("Copying lakitu source...");
47
+ await $`rsync -av \
48
+ --exclude='node_modules' \
49
+ --exclude='.git' \
50
+ --exclude='template' \
51
+ --exclude='assets' \
52
+ --exclude='cli' \
53
+ --exclude='tests' \
54
+ --exclude='dist' \
55
+ --exclude='.github' \
56
+ --exclude='.env*' \
57
+ --exclude='convex/cloud' \
58
+ ${LAKITU_DIR}/ ${BUILD_CONTEXT}/lakitu/`.quiet();
59
+
60
+ // Copy project KSAs
61
+ const projectKsaDir = join(PROJECT_ROOT, "lakitu");
62
+ if (existsSync(projectKsaDir)) {
63
+ console.log("Copying project KSAs...");
64
+ const ksaFiles = readdirSync(projectKsaDir).filter(f => f.endsWith(".ts"));
65
+ for (const file of ksaFiles) {
66
+ await $`cp ${join(projectKsaDir, file)} ${BUILD_CONTEXT}/lakitu/ksa/`.quiet();
67
+ }
68
+ console.log(` Copied ${ksaFiles.length} project KSAs`);
69
+ }
70
+
71
+ // Copy scripts
72
+ await $`cp ${SCRIPT_DIR}/e2b/start.sh ${BUILD_CONTEXT}/`.quiet();
73
+ await $`cp ${SCRIPT_DIR}/e2b/prebuild.sh ${BUILD_CONTEXT}/`.quiet();
74
+
75
+ // Create Dockerfile
76
+ const dockerfile = `# Lakitu E2B Sandbox Template
77
+ FROM e2bdev/code-interpreter:latest
78
+
79
+ # System dependencies
80
+ RUN apt-get update && apt-get install -y \\
81
+ git curl sqlite3 libsqlite3-dev \\
82
+ build-essential unzip \\
83
+ && rm -rf /var/lib/apt/lists/*
84
+
85
+ # Bun runtime
86
+ RUN curl -fsSL https://bun.sh/install | bash
87
+ ENV PATH="/root/.bun/bin:/home/user/.bun/bin:\$PATH"
88
+
89
+ # Node.js for npx convex
90
+ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \\
91
+ apt-get install -y nodejs && \\
92
+ rm -rf /var/lib/apt/lists/*
93
+
94
+ # Convex local backend
95
+ RUN curl -L -o /tmp/convex.zip "https://github.com/get-convex/convex-backend/releases/download/precompiled-2026-01-08-272e7f4/convex-local-backend-x86_64-unknown-linux-gnu.zip" && \\
96
+ unzip /tmp/convex.zip -d /tmp && \\
97
+ mv /tmp/convex-local-backend /usr/local/bin/convex-backend && \\
98
+ chmod +x /usr/local/bin/convex-backend && \\
99
+ rm /tmp/convex.zip
100
+
101
+ # Directory structure
102
+ RUN mkdir -p /home/user/workspace /home/user/.convex/convex-backend-state/lakitu /home/user/artifacts && \\
103
+ chown -R user:user /home/user
104
+
105
+ # Copy lakitu code
106
+ COPY --chown=user:user lakitu/ /home/user/lakitu/
107
+
108
+ # Copy scripts
109
+ COPY --chown=user:user start.sh /home/user/start.sh
110
+ COPY --chown=user:user prebuild.sh /home/user/prebuild.sh
111
+ RUN chmod +x /home/user/start.sh /home/user/prebuild.sh
112
+
113
+ # Install dependencies
114
+ WORKDIR /home/user/lakitu
115
+ RUN /root/.bun/bin/bun install
116
+
117
+ # Create CLI tools
118
+ RUN echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/pdf/pdf-generator.ts "\$@"' > /usr/local/bin/generate-pdf && \\
119
+ chmod +x /usr/local/bin/generate-pdf && \\
120
+ echo '#!/bin/bash\\nbun run /home/user/lakitu/runtime/browser/agent-browser-cli.ts "\$@"' > /usr/local/bin/agent-browser && \\
121
+ chmod +x /usr/local/bin/agent-browser
122
+
123
+ # Symlink KSA
124
+ RUN ln -sf /home/user/lakitu/ksa /home/user/ksa
125
+
126
+ # Pre-deploy Convex functions
127
+ RUN /home/user/prebuild.sh
128
+
129
+ # Fix ownership
130
+ RUN chown -R user:user /home/user
131
+
132
+ # Environment
133
+ ENV HOME=/home/user
134
+ ENV PATH="/home/user/.bun/bin:/usr/local/bin:/usr/bin:/bin"
135
+ ENV CONVEX_URL="http://localhost:3210"
136
+ ENV LOCAL_CONVEX_URL="http://localhost:3210"
137
+ ENV CONVEX_LOCAL_STORAGE="/home/user/.convex/convex-backend-state/lakitu"
138
+
139
+ USER user
140
+ WORKDIR /home/user/workspace
141
+ `;
142
+
143
+ await Bun.write(join(BUILD_CONTEXT, "Dockerfile"), dockerfile);
144
+
145
+ console.log("Build context ready:");
146
+ await $`ls -la ${BUILD_CONTEXT}`;
147
+ console.log("\nKSA modules:");
148
+ await $`ls ${BUILD_CONTEXT}/lakitu/ksa/*.ts 2>/dev/null | head -10`;
149
+ }
150
+
151
+ async function build() {
152
+ console.log("šŸ„ Lakitu Docker Build (v2 fromDockerfile)\n");
153
+
154
+ const apiKey = await getApiKey();
155
+ console.log("šŸ”‘ API key found\n");
156
+
157
+ await prepareBuildContext();
158
+
159
+ console.log("\n=== Building E2B template from Dockerfile ===\n");
160
+
161
+ const template = Template()
162
+ .fromDockerfile(join(BUILD_CONTEXT, "Dockerfile"))
163
+ .setStartCmd("/home/user/start.sh", waitForPort(3210));
164
+
165
+ const result = await Template.build(template, {
166
+ alias: "lakitu",
167
+ apiKey,
168
+ onBuildLogs: defaultBuildLogger(),
169
+ });
170
+
171
+ console.log(`\nāœ… Template built: ${result.templateId}`);
172
+ process.exit(0);
173
+ }
174
+
175
+ build().catch(e => {
176
+ console.error("Build failed:", e);
177
+ process.exit(1);
178
+ });