Skip to content

Commit 8537104

Browse files
Add sandbox file browsing and management features
1 parent 77c16c9 commit 8537104

3 files changed

Lines changed: 401 additions & 0 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { Sandbox } from '@e2b/code-interpreter'
2+
import { NextRequest } from 'next/server'
3+
4+
export const maxDuration = 60
5+
export const runtime = 'nodejs'
6+
export const dynamic = 'force-dynamic'
7+
8+
/**
9+
* GET /api/sandbox/[sbxId]/files/content?path=/path/to/file
10+
* Fetches the content of a specific file from an E2B sandbox
11+
*/
12+
export async function GET(
13+
req: NextRequest,
14+
{ params }: { params: { sbxId: string } }
15+
) {
16+
try {
17+
const { sbxId } = params
18+
const searchParams = req.nextUrl.searchParams
19+
const filePath = searchParams.get('path')
20+
21+
if (!sbxId) {
22+
return new Response(
23+
JSON.stringify({ error: 'Missing sandbox ID' }),
24+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
25+
)
26+
}
27+
28+
if (!filePath) {
29+
return new Response(
30+
JSON.stringify({ error: 'Missing file path' }),
31+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
32+
)
33+
}
34+
35+
if (!process.env.E2B_API_KEY) {
36+
return new Response(
37+
JSON.stringify({ error: 'E2B_API_KEY not configured' }),
38+
{ status: 503, headers: { 'Content-Type': 'application/json' } }
39+
)
40+
}
41+
42+
// Connect to existing sandbox
43+
const sbx = await Sandbox.connect(sbxId)
44+
45+
// Read file content from sandbox
46+
// Remove leading slash if present and prepend /home/user/
47+
const fullPath = filePath.startsWith('/')
48+
? `/home/user${filePath}`
49+
: `/home/user/${filePath}`
50+
51+
const result = await sbx.commands.run(`cat "${fullPath}"`)
52+
53+
if (result.exitCode !== 0) {
54+
console.error('Error reading file:', result.stderr)
55+
return new Response(
56+
JSON.stringify({
57+
error: 'Failed to read file',
58+
details: result.stderr,
59+
path: fullPath
60+
}),
61+
{ status: 404, headers: { 'Content-Type': 'application/json' } }
62+
)
63+
}
64+
65+
return new Response(
66+
JSON.stringify({
67+
content: result.stdout,
68+
path: filePath
69+
}),
70+
{ headers: { 'Content-Type': 'application/json' } }
71+
)
72+
} catch (error: any) {
73+
console.error('Error fetching file content:', error)
74+
return new Response(
75+
JSON.stringify({
76+
error: 'Failed to fetch file content',
77+
details: error?.message || 'Unknown error'
78+
}),
79+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
80+
)
81+
}
82+
}
83+
84+
/**
85+
* POST /api/sandbox/[sbxId]/files/content
86+
* Writes content to a specific file in an E2B sandbox
87+
*/
88+
export async function POST(
89+
req: Request,
90+
{ params }: { params: { sbxId: string } }
91+
) {
92+
try {
93+
const { sbxId } = params
94+
const { path: filePath, content } = await req.json()
95+
96+
if (!sbxId) {
97+
return new Response(
98+
JSON.stringify({ error: 'Missing sandbox ID' }),
99+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
100+
)
101+
}
102+
103+
if (!filePath || content === undefined) {
104+
return new Response(
105+
JSON.stringify({ error: 'Missing file path or content' }),
106+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
107+
)
108+
}
109+
110+
if (!process.env.E2B_API_KEY) {
111+
return new Response(
112+
JSON.stringify({ error: 'E2B_API_KEY not configured' }),
113+
{ status: 503, headers: { 'Content-Type': 'application/json' } }
114+
)
115+
}
116+
117+
// Connect to existing sandbox
118+
const sbx = await Sandbox.connect(sbxId)
119+
120+
// Write file content to sandbox
121+
const fullPath = filePath.startsWith('/')
122+
? filePath.substring(1)
123+
: filePath
124+
125+
await sbx.files.write(fullPath, content)
126+
127+
return new Response(
128+
JSON.stringify({
129+
success: true,
130+
path: filePath
131+
}),
132+
{ headers: { 'Content-Type': 'application/json' } }
133+
)
134+
} catch (error: any) {
135+
console.error('Error writing file content:', error)
136+
return new Response(
137+
JSON.stringify({
138+
error: 'Failed to write file content',
139+
details: error?.message || 'Unknown error'
140+
}),
141+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
142+
)
143+
}
144+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { Sandbox } from '@e2b/code-interpreter'
2+
import { FileSystemNode } from '@/components/file-tree'
3+
4+
export const maxDuration = 60
5+
export const runtime = 'nodejs'
6+
export const dynamic = 'force-dynamic'
7+
8+
/**
9+
* GET /api/sandbox/[sbxId]/files
10+
* Fetches the file tree from an E2B sandbox
11+
*/
12+
export async function GET(
13+
req: Request,
14+
{ params }: { params: { sbxId: string } }
15+
) {
16+
try {
17+
const { sbxId } = params
18+
19+
if (!sbxId) {
20+
return new Response(
21+
JSON.stringify({ error: 'Missing sandbox ID' }),
22+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
23+
)
24+
}
25+
26+
if (!process.env.E2B_API_KEY) {
27+
return new Response(
28+
JSON.stringify({ error: 'E2B_API_KEY not configured' }),
29+
{ status: 503, headers: { 'Content-Type': 'application/json' } }
30+
)
31+
}
32+
33+
// Connect to existing sandbox
34+
const sbx = await Sandbox.connect(sbxId)
35+
36+
// Get file tree from sandbox using shell command
37+
const result = await sbx.commands.run(
38+
'find /home/user -type f -o -type d | sort'
39+
)
40+
41+
if (result.exitCode !== 0) {
42+
console.error('Error listing files:', result.stderr)
43+
return new Response(
44+
JSON.stringify({ error: 'Failed to list files', details: result.stderr }),
45+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
46+
)
47+
}
48+
49+
// Parse the file paths into a tree structure
50+
const files = parseFileTree(result.stdout)
51+
52+
return new Response(
53+
JSON.stringify({ files }),
54+
{ headers: { 'Content-Type': 'application/json' } }
55+
)
56+
} catch (error: any) {
57+
console.error('Error fetching sandbox files:', error)
58+
return new Response(
59+
JSON.stringify({
60+
error: 'Failed to fetch sandbox files',
61+
details: error?.message || 'Unknown error'
62+
}),
63+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
64+
)
65+
}
66+
}
67+
68+
/**
69+
* Parse file paths from find command output into a tree structure
70+
*/
71+
function parseFileTree(output: string): FileSystemNode[] {
72+
const lines = output.trim().split('\n').filter(line => line.trim())
73+
const root: FileSystemNode[] = []
74+
const nodeMap = new Map<string, FileSystemNode>()
75+
76+
// Sort paths to ensure parents come before children
77+
const paths = lines
78+
.map(line => line.trim())
79+
.filter(path => path.startsWith('/home/user/'))
80+
.sort()
81+
82+
for (const fullPath of paths) {
83+
// Remove /home/user/ prefix
84+
const relativePath = fullPath.replace('/home/user/', '')
85+
if (!relativePath) continue
86+
87+
const parts = relativePath.split('/')
88+
const name = parts[parts.length - 1]
89+
const parentPath = parts.slice(0, -1).join('/')
90+
91+
// Determine if it's a directory (find includes both files and dirs)
92+
// We'll check if there are children later
93+
const node: FileSystemNode = {
94+
name,
95+
isDirectory: false, // Will be updated if we find children
96+
path: `/${relativePath}`,
97+
children: []
98+
}
99+
100+
nodeMap.set(relativePath, node)
101+
102+
if (parentPath === '') {
103+
// Root level file/directory
104+
root.push(node)
105+
} else {
106+
// Add to parent's children
107+
const parent = nodeMap.get(parentPath)
108+
if (parent) {
109+
if (!parent.children) {
110+
parent.children = []
111+
}
112+
parent.children.push(node)
113+
// Mark parent as directory
114+
parent.isDirectory = true
115+
}
116+
}
117+
}
118+
119+
return root
120+
}

0 commit comments

Comments
 (0)