+**Secure Sandbox SDK for Isolated Code Execution.** Execute AI-generated code, run automation tasks, and test untrusted code with zero risk to your infrastructure.
-
-
-
-
-
-
-
-
-
+## ๐ Quick Start
-## Install
+### Installation
-```sh
-npm add devbox-sdk
+```bash
+npm install devbox-sdk
+```
+
+### Secure Code Execution
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+// Initialize SDK
+const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+})
+
+// Create a secure sandbox
+const sandbox = await sdk.createDevbox({
+ name: 'ai-agent-task',
+ runtime: 'python',
+ resource: { cpu: 1, memory: 512 }
+})
+
+// Execute AI-generated code safely in isolation
+const result = await sandbox.codeRun(`
+import requests
+response = requests.get('https://api.example.com/data')
+print(response.json())
+`)
+
+console.log(result.stdout) // Safe output from isolated execution
+
+// Clean up
+await sandbox.delete()
+await sdk.close()
+```
+
+### Core Features
+
+- **๐ก๏ธ Secure Sandbox Execution** - Isolated container environments for safe code execution
+- **โก Fast Code Execution** - Execute code synchronously or asynchronously with real-time output
+- **๐ File & Git Operations** - Full CRUD operations, batch transfers, and Git integration
+- **๐ Real-time Monitoring** - Monitor file changes and resource usage via WebSocket
+- **๐ Connection Pooling** - Efficient HTTP connection reuse for better performance
+- **๐ Enterprise Security** - Kubernetes-based isolation, path validation, and access control
+
+### Use Cases
+
+**AI Agents & Code Generation**
+```typescript
+// Execute AI-generated code safely
+const aiCode = await llm.generateCode(prompt)
+const result = await sandbox.codeRun(aiCode)
+```
+
+**Automation & Testing**
+```typescript
+// Run untrusted automation scripts
+await sandbox.execSync({
+ command: 'npm test',
+ cwd: '/workspace',
+ timeout: 60000
+})
+```
+
+**CI/CD Tasks**
+```typescript
+// Execute build tasks in isolation
+await sandbox.git.clone({ url: repoUrl, path: '/workspace' })
+await sandbox.execSync({ command: 'npm run build' })
+```
+
+## ๐ก๏ธ Security & Isolation
+
+### Container-Based Isolation
+
+Each sandbox runs in an isolated Kubernetes Pod, ensuring:
+- **Zero cross-contamination** - Each execution is completely isolated
+- **Resource limits** - CPU and memory constraints prevent resource exhaustion
+- **Network isolation** - Controlled network access per sandbox
+- **Path validation** - Prevents directory traversal attacks
+
+### Enterprise Security Features
+
+- **Kubernetes-native** - Built on enterprise-grade container orchestration
+- **Access control** - Kubeconfig-based authentication and authorization
+- **HTTPS/TLS** - All communications encrypted
+- **Input validation** - Comprehensive input sanitization and validation
+
+## ๐ฆ Monorepo Packages
+
+This is a monorepo containing multiple packages:
+
+### devbox-sdk (Main Package)
+The primary TypeScript SDK for secure sandbox execution. See [packages/sdk/README.md](./packages/sdk/README.md) for detailed documentation.
+
+### devbox-shared
+Shared types, errors, and utilities used across the SDK and server. See [packages/shared/README.md](./packages/shared/README.md).
+
+### devbox-server-go
+High-performance HTTP server written in Go, running inside sandbox containers to handle file operations, process execution, and WebSocket connections. See [packages/server-go/README.md](./packages/server-go/README.md).
+
+### devbox-docs
+Documentation website built with Next.js and Fumadocs. Visit the [docs site](./apps/docs) or run `npm run dev:docs` to start locally.
+
+## ๐ ๏ธ Development
+
+### Prerequisites
+
+- Node.js >= 22.0.0
+- npm >= 11.0.0
+- Kubernetes cluster access (for testing)
+
+### Setup
+
+```bash
+# Install dependencies
+npm install
+
+# Build all packages
+npm run build
+
+# Run tests
+npm test
+
+# Lint code
+npm run lint:fix
+```
+
+### Package Scripts
+
+```bash
+# Build specific packages
+npm run build:sdk # Build SDK only
+npm run build:docs # Build docs site
+
+# Development
+npm run dev:docs # Start docs site in dev mode
+
+# Testing
+npm test # Run all tests
+npm run test:watch # Run tests in watch mode
+npm run test:e2e # Run E2E tests
```
-## Usage: CLI
+
+## ๐ Documentation
+
+- [SDK Documentation](./packages/sdk/README.md) - Complete SDK API reference
+- [Architecture Overview](./packages/sdk/ARCHITECTURE.md) - Technical architecture details
+- [API Documentation](./apps/docs/content/docs/api.mdx) - HTTP API reference
+- [Server Documentation](./packages/server-go/docs/README.md) - Server implementation details
+- [Competitor Analysis](./plans/COMPETITOR_ANALYSIS.md) - Competitive positioning
+
+## โก Performance
+
+- **Connection Pooling**: Efficient HTTP connection reuse (>98% reuse rate)
+- **Adaptive Transfer**: Smart file transfer strategies based on file size
+- **Fast Creation**: Quick sandbox initialization
+- **TypeScript**: Full type safety and IDE support
+
+## ๐ง Configuration
+
+### Environment Variables
+
+- `KUBECONFIG` - Kubernetes configuration for sandbox access (required)
+
+### SDK Configuration
+
+```typescript
+const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG,
+ baseUrl: 'https://api.sealos.io', // Optional
+ http: {
+ timeout: 30000, // Request timeout in ms
+ retries: 3, // Retry attempts
+ rejectUnauthorized: true // SSL verification
+ }
+})
+```
+
+## ๐งช Testing
```bash
-// @TODO
-const {} = require('devbox-sdk')
+# Run all tests
+npm test
+
+# Run tests in watch mode
+npm run test:watch
+
+# Run E2E tests
+npm run test:e2e
```
-## Contributing
+## ๐ License
+
+Apache-2.0
+
+## ๐ค Contributing
-Please consult [CONTRIBUTING](./.github/CONTRIBUTING.md) for guidelines on contributing to this project.
+Contributions are welcome! Please read our contributing guidelines and submit pull requests.
-## Author
+## ๐ Support
-**devbox-sdk** ยฉ [zjy365](https://github.com/zjy365), Released under the [Apache-2.0](./LICENSE) License.
\ No newline at end of file
+For issues and questions:
+- Create an issue on [GitHub](https://github.com/zjy365/devbox-sdk/issues)
+- Check the [documentation](./apps/docs)
+- Contact the maintainers
diff --git a/__tests__/app.test.ts b/__tests__/app.test.ts
deleted file mode 100644
index e478d7b..0000000
--- a/__tests__/app.test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { test, describe, beforeEach, mock } from 'node:test'
-import assert from 'node:assert'
-import { add } from '../src/main.ts'
-
-describe('CLI program', () => {
-
- beforeEach(() => {
- // Reset the mocks before each test
- mock.reset()
- });
-
- test('Program sums two arguments', async (t) => {
- const result = await add(1, 1);
- assert.strictEqual(result, 2);
- })
-
-});
\ No newline at end of file
diff --git a/apps/docs/.dockerignore b/apps/docs/.dockerignore
new file mode 100644
index 0000000..89a54d1
--- /dev/null
+++ b/apps/docs/.dockerignore
@@ -0,0 +1,12 @@
+Dockerfile
+.dockerignore
+node_modules
+npm-debug.log
+.next
+.git
+.gitignore
+README.md
+BUILD.md
+.env*.local
+.vercel
+*.log
diff --git a/apps/docs/BUILD.md b/apps/docs/BUILD.md
new file mode 100644
index 0000000..e5da6d5
--- /dev/null
+++ b/apps/docs/BUILD.md
@@ -0,0 +1,119 @@
+# Docker Build Instructions
+
+## ๐ Quick Start (Build from monorepo root)
+
+```bash
+# IMPORTANT: Must be run from the monorepo root directory
+docker build --platform linux/amd64 -f apps/docs/Dockerfile -t devbox-docs:latest .
+```
+
+## ๐ Run the container
+
+```bash
+docker run -p 3000:3000 devbox-docs:latest
+```
+
+Then visit http://localhost:3000
+
+---
+
+## ๐ง Advanced Usage
+
+### Build with buildx
+
+```bash
+docker buildx build --platform linux/amd64 -f apps/docs/Dockerfile -t devbox-docs:latest .
+```
+
+### Build and push to registry
+
+```bash
+docker buildx build --platform linux/amd64 -f apps/docs/Dockerfile -t your-registry/devbox-docs:latest --push .
+```
+
+### Multi-platform build
+
+```bash
+docker buildx build --platform linux/amd64,linux/arm64 -f apps/docs/Dockerfile -t devbox-docs:latest .
+```
+
+### Build with custom tag
+
+```bash
+docker build --platform linux/amd64 -f apps/docs/Dockerfile -t devbox-docs:v1.0.0 .
+```
+
+---
+
+## ๐ฆ Complete Workflow
+
+```bash
+# 1. Make sure you're in the monorepo root
+cd /path/to/devbox-sdk
+
+# 2. Build the image
+docker build --platform linux/amd64 -f apps/docs/Dockerfile -t devbox-docs:latest .
+
+# 3. Run the container
+docker run -d -p 3000:3000 --name devbox-docs devbox-docs:latest
+
+# 4. Check logs
+docker logs -f devbox-docs
+
+# 5. Stop and remove
+docker stop devbox-docs && docker rm devbox-docs
+```
+
+---
+
+## ๐ Troubleshooting
+
+### Fix buildx permission errors
+
+```bash
+sudo chown -R $(whoami) ~/.docker/buildx
+```
+
+### Check container logs
+
+```bash
+docker logs
+```
+
+### Interactive shell for debugging
+
+```bash
+docker run -it --entrypoint sh devbox-docs:latest
+```
+
+### Verify build output
+
+```bash
+docker run --rm devbox-docs:latest ls -la apps/docs
+```
+
+### Clean build (no cache)
+
+```bash
+docker build --no-cache --platform linux/amd64 -f apps/docs/Dockerfile -t devbox-docs:latest .
+```
+
+---
+
+## โ ๏ธ Important Notes
+
+1. **Always build from the monorepo root directory** - The Dockerfile expects workspace structure
+2. **Use `--platform linux/amd64`** for production deployments on x86_64 servers
+3. **Tag with version numbers** for production: `devbox-docs:v1.0.0`
+4. **Test locally first** before pushing to registry
+
+---
+
+## ๐ฏ Why Build from Root?
+
+This project uses npm workspaces:
+- `package-lock.json` is only in the root directory
+- Dependencies are hoisted to root `node_modules`
+- Workspace resolution requires the full monorepo context
+
+Building from `apps/docs` directly won't work without restructuring the project.
diff --git a/apps/docs/Dockerfile b/apps/docs/Dockerfile
new file mode 100644
index 0000000..ab6e817
--- /dev/null
+++ b/apps/docs/Dockerfile
@@ -0,0 +1,58 @@
+# Dockerfile for Next.js app in npm workspaces monorepo
+# MUST be built from the monorepo root directory:
+# docker build --platform linux/amd64 -f apps/docs/Dockerfile -t devbox-docs:latest .
+
+FROM node:22-alpine AS base
+
+# Install dependencies only when needed
+FROM base AS deps
+RUN apk add --no-cache libc6-compat
+WORKDIR /app
+
+# Copy root package files for workspace resolution
+COPY package.json package-lock.json ./
+COPY apps/docs/package.json ./apps/docs/
+
+# Install all dependencies (respects workspaces)
+RUN npm ci
+
+# Rebuild the source code only when needed
+FROM base AS builder
+WORKDIR /app
+
+COPY --from=deps /app/node_modules ./node_modules
+COPY --from=deps /app/apps/docs/node_modules ./apps/docs/node_modules
+
+# Copy only the docs app source
+COPY apps/docs ./apps/docs
+COPY package.json package-lock.json ./
+
+# Build the docs app
+ENV NEXT_TELEMETRY_DISABLED=1
+WORKDIR /app/apps/docs
+RUN npm run build
+
+# Production image
+FROM base AS runner
+WORKDIR /app
+
+ENV NODE_ENV=production
+ENV NEXT_TELEMETRY_DISABLED=1
+
+RUN addgroup --system --gid 1001 nodejs
+RUN adduser --system --uid 1001 nextjs
+
+# Copy standalone output
+COPY --from=builder --chown=nextjs:nodejs /app/apps/docs/.next/standalone ./
+COPY --from=builder --chown=nextjs:nodejs /app/apps/docs/.next/static ./apps/docs/.next/static
+COPY --from=builder --chown=nextjs:nodejs /app/apps/docs/public ./apps/docs/public
+
+USER nextjs
+
+EXPOSE 3000
+
+ENV PORT=3000
+ENV HOSTNAME="0.0.0.0"
+
+# The server.js path in standalone output
+CMD ["node", "apps/docs/server.js"]
diff --git a/apps/docs/app/api/search/route.ts b/apps/docs/app/api/search/route.ts
new file mode 100644
index 0000000..713c1c3
--- /dev/null
+++ b/apps/docs/app/api/search/route.ts
@@ -0,0 +1,7 @@
+import { source } from '@/lib/source'
+import { createFromSource } from 'fumadocs-core/search/server'
+
+export const { GET } = createFromSource(source, {
+ // https://docs.orama.com/docs/orama-js/supported-languages
+ language: 'english',
+})
diff --git a/apps/docs/app/docs/[[...slug]]/page.tsx b/apps/docs/app/docs/[[...slug]]/page.tsx
new file mode 100644
index 0000000..7569311
--- /dev/null
+++ b/apps/docs/app/docs/[[...slug]]/page.tsx
@@ -0,0 +1,48 @@
+import { source } from '@/lib/source'
+import { getMDXComponents } from '@/mdx-components'
+import { createRelativeLink } from 'fumadocs-ui/mdx'
+import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page'
+import type { Metadata } from 'next'
+import { notFound } from 'next/navigation'
+
+type PageProps = {
+ params: Promise<{ slug?: string[] }>
+}
+
+export default async function Page(props: PageProps) {
+ const params = await props.params
+ const page = source.getPage(params.slug)
+ if (!page) notFound()
+
+ const MDX = page.data.body
+
+ return (
+
+ {page.data.title}
+ {page.data.description}
+
+
+
+
+ )
+}
+
+export async function generateStaticParams() {
+ return source.generateParams()
+}
+
+export async function generateMetadata(props: PageProps): Promise {
+ const params = await props.params
+ const page = source.getPage(params.slug)
+ if (!page) notFound()
+
+ return {
+ title: page.data.title,
+ description: page.data.description,
+ }
+}
diff --git a/apps/docs/app/docs/layout.tsx b/apps/docs/app/docs/layout.tsx
new file mode 100644
index 0000000..b0b22ef
--- /dev/null
+++ b/apps/docs/app/docs/layout.tsx
@@ -0,0 +1,16 @@
+import { baseOptions } from '@/lib/layout.shared'
+import { source } from '@/lib/source'
+import { DocsLayout } from 'fumadocs-ui/layouts/docs'
+import type { ReactNode } from 'react'
+
+export default function DocsLayoutWrapper({
+ children,
+}: {
+ children: ReactNode
+}) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/apps/docs/app/globals.css b/apps/docs/app/globals.css
new file mode 100644
index 0000000..2763779
--- /dev/null
+++ b/apps/docs/app/globals.css
@@ -0,0 +1,48 @@
+@import "tailwindcss";
+@import "fumadocs-ui/css/neutral.css";
+@import "fumadocs-ui/css/preset.css";
+
+/* Smooth scrolling only */
+html {
+ scroll-behavior: smooth;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+/* Landing page specific wrapper - all styles scoped to this */
+.landing-page {
+ --landing-bg: #ffffff;
+ --landing-fg: #000000;
+ --landing-muted: #f5f5f5;
+ --landing-muted-fg: #666666;
+ --landing-border: #e5e5e5;
+
+ background-color: var(--landing-bg);
+ color: var(--landing-fg);
+}
+
+/* Landing page typography */
+.landing-page h1,
+.landing-page h2,
+.landing-page h3,
+.landing-page h4,
+.landing-page h5,
+.landing-page h6 {
+ letter-spacing: -0.02em;
+ font-weight: 600;
+ color: var(--landing-fg);
+}
+
+/* Landing page selection */
+.landing-page ::selection {
+ background: #000000;
+ color: #ffffff;
+}
+
+/* Vercel-style glass material for landing page */
+.landing-page .glass {
+ background: rgba(255, 255, 255, 0.8);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border-bottom: 1px solid var(--landing-border);
+}
diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx
new file mode 100644
index 0000000..0dd9512
--- /dev/null
+++ b/apps/docs/app/layout.tsx
@@ -0,0 +1,23 @@
+import { RootProvider } from 'fumadocs-ui/provider/next'
+import type { Metadata } from 'next'
+import type { ReactNode } from 'react'
+import './globals.css'
+
+export const metadata: Metadata = {
+ title: 'Devbox SDK Documentation',
+ description: 'Enterprise TypeScript SDK for Sealos Devbox management',
+}
+
+export default function RootLayout({
+ children,
+}: {
+ children: ReactNode
+}) {
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx
new file mode 100644
index 0000000..7c0fcc3
--- /dev/null
+++ b/apps/docs/app/page.tsx
@@ -0,0 +1,30 @@
+import { AnimatedSection } from '@/components/landing/animated-section'
+import { BentoSection } from '@/components/landing/bento-section'
+import { Footer } from '@/components/landing/footer'
+import { Header } from '@/components/landing/header'
+import { HeroSection } from '@/components/landing/hero-section'
+import { SocialProof } from '@/components/landing/social-proof'
+import { UseCases } from '@/components/landing/use-cases'
+
+export default function HomePage() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/docs/components/landing/animated-section.tsx b/apps/docs/components/landing/animated-section.tsx
new file mode 100644
index 0000000..b217383
--- /dev/null
+++ b/apps/docs/components/landing/animated-section.tsx
@@ -0,0 +1,30 @@
+'use client'
+
+import { cn } from '@/lib/utils'
+import { type HTMLMotionProps, motion } from 'motion/react'
+import type { ReactNode } from 'react'
+
+interface AnimatedSectionProps extends HTMLMotionProps<'div'> {
+ children: ReactNode
+ delay?: number
+}
+
+export function AnimatedSection({
+ children,
+ className,
+ delay = 0,
+ ...props
+}: AnimatedSectionProps) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/apps/docs/components/landing/bento-section.tsx b/apps/docs/components/landing/bento-section.tsx
new file mode 100644
index 0000000..cc013ef
--- /dev/null
+++ b/apps/docs/components/landing/bento-section.tsx
@@ -0,0 +1,128 @@
+'use client'
+
+import { cn } from '@/lib/utils'
+import {
+ Activity,
+ Box,
+ Cpu,
+ GitBranch,
+ Globe,
+ HardDrive,
+ Shield,
+ Terminal,
+ Zap,
+} from 'lucide-react'
+import { motion } from 'motion/react'
+import type { ReactNode } from 'react'
+import { SectionHeader } from './section-header'
+
+interface BentoCardProps {
+ title: string
+ description: string
+ icon: ReactNode
+ className?: string
+ children?: ReactNode
+}
+
+function BentoCard({ title, description, icon, className, children }: BentoCardProps) {
+ return (
+
+
+
+ )
+}
diff --git a/apps/docs/components/landing/use-cases.tsx b/apps/docs/components/landing/use-cases.tsx
new file mode 100644
index 0000000..cf66e27
--- /dev/null
+++ b/apps/docs/components/landing/use-cases.tsx
@@ -0,0 +1,68 @@
+'use client'
+
+import { cn } from '@/lib/utils'
+import { Bot, Code2, Rocket } from 'lucide-react'
+import { SectionHeader } from './section-header'
+
+const cases = [
+ {
+ title: 'AI Agents & Evals',
+ description:
+ 'Provide secure, isolated sandboxes for AI agents to write and execute code without risking your infrastructure. Perfect for code interpretation and automated task execution.',
+ icon: Bot,
+ color: 'text-black',
+ bg: 'bg-[#f5f5f5]',
+ },
+ {
+ title: 'Cloud IDE Backends',
+ description:
+ 'Power your custom cloud IDEs with a robust backend that handles terminals, files, and language servers. Support for all major languages and runtimes out of the box.',
+ icon: Code2,
+ color: 'text-black',
+ bg: 'bg-[#f5f5f5]',
+ },
+ {
+ title: 'CI/CD Pipelines',
+ description:
+ 'Spin up ephemeral environments for testing and building applications in a clean state every time. Faster than traditional VMs and more secure than shared containers.',
+ icon: Rocket,
+ color: 'text-black',
+ bg: 'bg-[#f5f5f5]',
+ },
+]
+
+export function UseCases() {
+ return (
+
+
+
+
+
+ {cases.map(item => (
+
+
+
+
+
{item.title}
+
+ {item.description}
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/docs/content/docs/api/devbox-instance.mdx b/apps/docs/content/docs/api/devbox-instance.mdx
new file mode 100644
index 0000000..4d47bfa
--- /dev/null
+++ b/apps/docs/content/docs/api/devbox-instance.mdx
@@ -0,0 +1,449 @@
+---
+title: DevboxInstance API
+description: Complete API reference for DevboxInstance class
+---
+
+# DevboxInstance API
+
+Represents a single sandbox instance with methods for code execution, file operations, Git integration, and more.
+
+## Properties
+
+### name
+
+```typescript
+readonly name: string
+```
+
+The name of the sandbox instance.
+
+### status
+
+```typescript
+readonly status: string
+```
+
+Current status of the sandbox (e.g., 'Running', 'Stopped').
+
+### runtime
+
+```typescript
+readonly runtime: DevboxRuntime
+```
+
+Runtime environment (e.g., 'node.js', 'python').
+
+### resources
+
+```typescript
+readonly resources: ResourceInfo
+```
+
+Resource allocation information.
+
+### git
+
+```typescript
+readonly git: Git
+```
+
+Git operations interface.
+
+## Lifecycle Methods
+
+### start
+
+Starts the sandbox.
+
+```typescript
+start(): Promise
+```
+
+### pause
+
+Pauses the sandbox.
+
+```typescript
+pause(): Promise
+```
+
+### restart
+
+Restarts the sandbox.
+
+```typescript
+restart(): Promise
+```
+
+### shutdown
+
+Shuts down the sandbox.
+
+```typescript
+shutdown(): Promise
+```
+
+### delete
+
+Deletes the sandbox.
+
+```typescript
+delete(): Promise
+```
+
+### refreshInfo
+
+Refreshes the sandbox information from the API.
+
+```typescript
+refreshInfo(): Promise
+```
+
+## File Operations
+
+### writeFile
+
+Writes content to a file.
+
+```typescript
+writeFile(
+ path: string,
+ content: string | Buffer,
+ options?: WriteOptions
+): Promise
+```
+
+#### Parameters
+
+- `path` (string) - File path
+- `content` (string | Buffer) - File content
+- `options` (object, optional)
+ - `options.encoding` (string) - File encoding ('utf8', 'base64')
+ - `options.mode` (number) - File permissions
+ - `options.createDirs` (boolean) - Create parent directories
+
+### readFile
+
+Reads content from a file.
+
+```typescript
+readFile(path: string, options?: ReadOptions): Promise
+```
+
+#### Parameters
+
+- `path` (string) - File path
+- `options` (object, optional)
+ - `options.encoding` (string) - File encoding
+ - `options.offset` (number) - Read offset
+ - `options.length` (number) - Length to read
+
+### listFiles
+
+Lists files in a directory.
+
+```typescript
+listFiles(path: string): Promise
+```
+
+### batchUpload
+
+Uploads multiple files at once.
+
+```typescript
+batchUpload(options: BatchUploadOptions): Promise
+```
+
+#### Parameters
+
+- `options.files` (FileMap) - Map of file paths to content
+- `options.concurrency` (number, optional) - Max concurrent uploads
+- `options.chunkSize` (number, optional) - Chunk size for large files
+- `options.onProgress` (function, optional) - Progress callback
+
+### downloadFile
+
+Downloads a single file.
+
+```typescript
+downloadFile(
+ path: string,
+ options?: DownloadFileOptions
+): Promise
+```
+
+### downloadFiles
+
+Downloads multiple files.
+
+```typescript
+downloadFiles(
+ paths: string[],
+ options?: { format?: 'tar.gz' | 'tar' | 'multipart' | 'direct' }
+): Promise
+```
+
+### moveFile
+
+Moves a file or directory.
+
+```typescript
+moveFile(
+ from: string,
+ to: string,
+ overwrite?: boolean
+): Promise
+```
+
+### renameFile
+
+Renames a file or directory.
+
+```typescript
+renameFile(
+ path: string,
+ newName: string
+): Promise
+```
+
+### deleteFile
+
+Deletes a file.
+
+```typescript
+deleteFile(path: string): Promise
+```
+
+## Process Execution
+
+### codeRun
+
+Executes code directly (Node.js or Python).
+
+```typescript
+codeRun(
+ code: string,
+ options?: CodeRunOptions
+): Promise
+```
+
+#### Parameters
+
+- `code` (string) - Code to execute
+- `options` (object, optional)
+ - `options.language` ('node' | 'python') - Programming language
+ - `options.cwd` (string) - Working directory
+ - `options.env` (object) - Environment variables
+ - `options.timeout` (number) - Timeout in seconds
+ - `options.argv` (string[]) - Command line arguments
+
+### execSync
+
+Executes a command synchronously.
+
+```typescript
+execSync(options: ProcessExecOptions): Promise
+```
+
+#### Parameters
+
+- `options.command` (string) - Command to execute
+- `options.args` (string[], optional) - Command arguments
+- `options.cwd` (string, optional) - Working directory
+- `options.env` (object, optional) - Environment variables
+- `options.shell` (string, optional) - Shell to use
+- `options.timeout` (number, optional) - Timeout in seconds
+
+### executeCommand
+
+Executes a command asynchronously.
+
+```typescript
+executeCommand(options: ProcessExecOptions): Promise
+```
+
+Returns immediately with `processId` and `pid`.
+
+### execSyncStream
+
+Executes a command with streaming output (SSE).
+
+```typescript
+execSyncStream(options: ProcessExecOptions): Promise
+```
+
+### getProcessStatus
+
+Gets the status of a process.
+
+```typescript
+getProcessStatus(processId: string): Promise
+```
+
+### getProcessLogs
+
+Gets logs from a process.
+
+```typescript
+getProcessLogs(
+ processId: string,
+ options?: { lines?: number }
+): Promise
+```
+
+### killProcess
+
+Kills a running process.
+
+```typescript
+killProcess(
+ processId: string,
+ options?: KillProcessOptions
+): Promise
+```
+
+#### Parameters
+
+- `processId` (string) - Process ID
+- `options` (object, optional)
+ - `options.signal` (string) - Signal to send ('SIGTERM', 'SIGKILL')
+
+### listProcesses
+
+Lists all running processes.
+
+```typescript
+listProcesses(): Promise
+```
+
+## Git Operations
+
+### git.clone
+
+Clones a Git repository.
+
+```typescript
+git.clone(options: GitCloneOptions): Promise
+```
+
+#### Parameters
+
+- `options.url` (string) - Repository URL
+- `options.targetDir` (string) - Target directory
+- `options.branch` (string, optional) - Branch to clone
+- `options.depth` (number, optional) - Shallow clone depth
+- `options.auth` (object, optional) - Authentication
+ - `auth.type` ('https' | 'ssh') - Auth type
+ - `auth.username` (string) - Username (for HTTPS)
+ - `auth.password` (string) - Password/token (for HTTPS)
+ - `auth.privateKey` (string) - Private key (for SSH)
+ - `auth.passphrase` (string, optional) - Passphrase (for SSH)
+
+### git.pull
+
+Pulls changes from a Git repository.
+
+```typescript
+git.pull(options: GitPullOptions): Promise
+```
+
+### git.push
+
+Pushes changes to a Git repository.
+
+```typescript
+git.push(options: GitPushOptions): Promise
+```
+
+### git.status
+
+Gets the status of a Git repository.
+
+```typescript
+git.status(path: string): Promise
+```
+
+### git.branches
+
+Lists branches in a Git repository.
+
+```typescript
+git.branches(path: string): Promise
+```
+
+## Utility Methods
+
+### getPorts
+
+Gets listening ports on the system.
+
+```typescript
+getPorts(): Promise
+```
+
+### isHealthy
+
+Checks if the sandbox is healthy.
+
+```typescript
+isHealthy(): Promise
+```
+
+### waitForReady
+
+Waits for the sandbox to be ready.
+
+```typescript
+waitForReady(
+ timeout?: number,
+ checkInterval?: number
+): Promise
+```
+
+#### Parameters
+
+- `timeout` (number, optional) - Timeout in milliseconds (default: 300000)
+- `checkInterval` (number, optional) - Check interval in milliseconds (default: 2000)
+
+## Complete Example
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+})
+
+const sandbox = await sdk.createDevbox({
+ name: 'example',
+ runtime: 'node.js',
+ resource: { cpu: 1, memory: 512 }
+})
+
+// File operations
+await sandbox.writeFile('app.js', 'console.log("Hello")')
+const content = await sandbox.readFile('app.js')
+
+// Process execution
+const result = await sandbox.codeRun('console.log("Hello")')
+const process = await sandbox.executeCommand({
+ command: 'npm',
+ args: ['install']
+})
+
+// Git operations
+await sandbox.git.clone({
+ url: 'https://github.com/user/repo.git',
+ targetDir: '/workspace/repo'
+})
+
+// Clean up
+await sandbox.delete()
+await sdk.close()
+```
+
+## Next Steps
+
+- Read [Type Definitions](/docs/api/types)
+- Explore [Examples](/docs/examples/ai-agent-workflow)
+
diff --git a/apps/docs/content/docs/api/devbox-sdk.mdx b/apps/docs/content/docs/api/devbox-sdk.mdx
new file mode 100644
index 0000000..5865eca
--- /dev/null
+++ b/apps/docs/content/docs/api/devbox-sdk.mdx
@@ -0,0 +1,252 @@
+---
+title: DevboxSDK API
+description: Complete API reference for DevboxSDK class
+---
+
+# DevboxSDK API
+
+The main SDK class for creating and managing sandboxes.
+
+## Constructor
+
+```typescript
+new DevboxSDK(config: DevboxSDKConfig)
+```
+
+### Parameters
+
+- `config.kubeconfig` (string, required) - Kubernetes configuration file path or content
+- `config.baseUrl` (string, optional) - API base URL
+- `config.http` (object, optional) - HTTP client configuration
+ - `http.timeout` (number) - Request timeout in milliseconds (default: 30000)
+ - `http.retries` (number) - Number of retry attempts (default: 3)
+ - `http.rejectUnauthorized` (boolean) - SSL verification (default: true)
+
+### Example
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG,
+ // Optional configuration
+ http: {
+ timeout: 60000,
+ retries: 5
+ }
+})
+```
+
+## Methods
+
+### createDevbox
+
+Creates a new sandbox instance.
+
+```typescript
+createDevbox(config: DevboxCreateConfig): Promise
+```
+
+#### Parameters
+
+- `config.name` (string, required) - Unique name for the sandbox
+- `config.runtime` (string, required) - Runtime environment (e.g., 'node.js', 'python')
+- `config.resource` (object, required) - Resource allocation
+ - `resource.cpu` (number) - CPU cores
+ - `resource.memory` (number) - Memory in MB
+- `config.ports` (array, optional) - Port mappings
+- `config.env` (array, optional) - Environment variables
+
+#### Returns
+
+`Promise` - The created sandbox instance
+
+#### Example
+
+```typescript
+const sandbox = await sdk.createDevbox({
+ name: 'my-sandbox',
+ runtime: 'node.js',
+ resource: { cpu: 2, memory: 4096 },
+ ports: [{ number: 3000, protocol: 'HTTP' }],
+ env: [{ name: 'NODE_ENV', value: 'production' }]
+})
+```
+
+### getDevbox
+
+Gets an existing sandbox by name.
+
+```typescript
+getDevbox(name: string): Promise
+```
+
+#### Parameters
+
+- `name` (string, required) - Sandbox name
+
+#### Returns
+
+`Promise` - The sandbox instance
+
+#### Example
+
+```typescript
+const sandbox = await sdk.getDevbox('my-sandbox')
+```
+
+### listDevboxes
+
+Lists all available sandboxes.
+
+```typescript
+listDevboxes(): Promise
+```
+
+#### Returns
+
+`Promise` - Array of sandbox instances
+
+#### Example
+
+```typescript
+const sandboxes = await sdk.listDevboxes()
+sandboxes.forEach(sandbox => {
+ console.log(`${sandbox.name}: ${sandbox.status}`)
+})
+```
+
+### getMonitorData
+
+Gets monitoring data for a sandbox.
+
+```typescript
+getMonitorData(
+ devboxName: string,
+ timeRange?: TimeRange
+): Promise
+```
+
+#### Parameters
+
+- `devboxName` (string, required) - Sandbox name
+- `timeRange` (object, optional) - Time range for monitoring data
+ - `timeRange.start` (number) - Start timestamp
+ - `timeRange.end` (number) - End timestamp
+
+#### Returns
+
+`Promise` - Array of monitoring data points
+
+#### Example
+
+```typescript
+const monitorData = await sdk.getMonitorData('my-sandbox', {
+ start: Date.now() - 3600000, // 1 hour ago
+ end: Date.now()
+})
+
+monitorData.forEach(data => {
+ console.log(`CPU: ${data.cpu}%, Memory: ${data.memory}MB`)
+})
+```
+
+### close
+
+Closes all connections and cleans up resources.
+
+```typescript
+close(): Promise
+```
+
+#### Example
+
+```typescript
+await sdk.close()
+```
+
+### getAPIClient
+
+Gets the underlying API client instance.
+
+```typescript
+getAPIClient(): DevboxAPI
+```
+
+### getUrlResolver
+
+Gets the URL resolver instance.
+
+```typescript
+getUrlResolver(): ContainerUrlResolver
+```
+
+## Error Handling
+
+The SDK throws specific error types:
+
+- `DevboxSDKError` - Base error class
+- `AuthenticationError` - Authentication failures
+- `ConnectionError` - Connection failures
+- `DevboxNotFoundError` - Sandbox not found
+- `ValidationError` - Validation errors
+
+```typescript
+import {
+ DevboxSDKError,
+ AuthenticationError,
+ DevboxNotFoundError
+} from 'devbox-sdk'
+
+try {
+ const sandbox = await sdk.getDevbox('nonexistent')
+} catch (error) {
+ if (error instanceof DevboxNotFoundError) {
+ console.error('Sandbox not found')
+ } else if (error instanceof AuthenticationError) {
+ console.error('Authentication failed')
+ }
+}
+```
+
+## Complete Example
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+async function main() {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ try {
+ // List all sandboxes
+ const sandboxes = await sdk.listDevboxes()
+ console.log(`Found ${sandboxes.length} sandboxes`)
+
+ // Create a new sandbox
+ const sandbox = await sdk.createDevbox({
+ name: 'test-sandbox',
+ runtime: 'python',
+ resource: { cpu: 1, memory: 512 }
+ })
+
+ // Get monitoring data
+ const monitorData = await sdk.getMonitorData(sandbox.name)
+ console.log('Monitor data:', monitorData)
+
+ // Clean up
+ await sandbox.delete()
+ } finally {
+ await sdk.close()
+ }
+}
+
+main().catch(console.error)
+```
+
+## Next Steps
+
+- Read [DevboxInstance API](/docs/api/devbox-instance)
+- Explore [Type Definitions](/docs/api/types)
+
diff --git a/apps/docs/content/docs/api/types.mdx b/apps/docs/content/docs/api/types.mdx
new file mode 100644
index 0000000..82cd5be
--- /dev/null
+++ b/apps/docs/content/docs/api/types.mdx
@@ -0,0 +1,210 @@
+---
+title: Type Definitions
+description: Complete type definitions for Devbox SDK
+---
+
+# Type Definitions
+
+Complete TypeScript type definitions for Devbox SDK.
+
+## DevboxSDKConfig
+
+Configuration for DevboxSDK.
+
+```typescript
+interface DevboxSDKConfig {
+ kubeconfig: string
+ baseUrl?: string
+ http?: HttpClientConfig
+}
+```
+
+## DevboxCreateConfig
+
+Configuration for creating a sandbox.
+
+```typescript
+interface DevboxCreateConfig {
+ name: string
+ runtime: DevboxRuntime
+ resource: ResourceInfo
+ ports?: PortConfig[]
+ env?: Record
+}
+```
+
+## ResourceInfo
+
+Resource allocation information.
+
+```typescript
+interface ResourceInfo {
+ cpu: number // CPU cores
+ memory: number // Memory in MB
+}
+```
+
+## ProcessExecOptions
+
+Options for process execution.
+
+```typescript
+interface ProcessExecOptions {
+ command: string
+ args?: string[]
+ cwd?: string
+ env?: Record
+ shell?: string
+ timeout?: number
+}
+```
+
+## CodeRunOptions
+
+Options for code execution.
+
+```typescript
+interface CodeRunOptions {
+ language?: 'node' | 'python'
+ cwd?: string
+ env?: Record
+ timeout?: number
+ argv?: string[]
+}
+```
+
+## SyncExecutionResponse
+
+Response from synchronous execution.
+
+```typescript
+interface SyncExecutionResponse {
+ stdout: string
+ stderr: string
+ exitCode: number
+ durationMs: number
+ startTime: number
+ endTime: number
+ success: boolean
+}
+```
+
+## ProcessExecResponse
+
+Response from asynchronous execution.
+
+```typescript
+interface ProcessExecResponse {
+ processId: string
+ pid: number
+ processStatus: string
+}
+```
+
+## FileChangeEvent
+
+File change event from file watching.
+
+```typescript
+interface FileChangeEvent {
+ type: 'create' | 'update' | 'delete'
+ path: string
+ timestamp: number
+}
+```
+
+## GitCloneOptions
+
+Options for cloning a Git repository.
+
+```typescript
+interface GitCloneOptions {
+ url: string
+ targetDir: string
+ branch?: string
+ depth?: number
+ auth?: {
+ type: 'https' | 'ssh'
+ username?: string
+ password?: string
+ privateKey?: string
+ passphrase?: string
+ }
+}
+```
+
+## Error Types
+
+### DevboxSDKError
+
+Base error class.
+
+```typescript
+class DevboxSDKError extends Error {
+ code: string
+ statusCode?: number
+}
+```
+
+### AuthenticationError
+
+Authentication failures.
+
+```typescript
+class AuthenticationError extends DevboxSDKError {}
+```
+
+### ConnectionError
+
+Connection failures.
+
+```typescript
+class ConnectionError extends DevboxSDKError {}
+```
+
+### FileOperationError
+
+File operation errors.
+
+```typescript
+class FileOperationError extends DevboxSDKError {}
+```
+
+### DevboxNotFoundError
+
+Sandbox not found.
+
+```typescript
+class DevboxNotFoundError extends DevboxSDKError {}
+```
+
+### ValidationError
+
+Validation errors.
+
+```typescript
+class ValidationError extends DevboxSDKError {}
+```
+
+## Import Types
+
+```typescript
+import type {
+ DevboxSDKConfig,
+ DevboxCreateConfig,
+ DevboxInfo,
+ ResourceInfo,
+ ProcessExecOptions,
+ CodeRunOptions,
+ SyncExecutionResponse,
+ ProcessExecResponse,
+ FileChangeEvent,
+ GitCloneOptions
+} from 'devbox-sdk'
+```
+
+## Next Steps
+
+- Read [DevboxSDK API](/docs/api/devbox-sdk)
+- Read [DevboxInstance API](/docs/api/devbox-instance)
+
diff --git a/apps/docs/content/docs/examples/ai-agent-workflow.mdx b/apps/docs/content/docs/examples/ai-agent-workflow.mdx
new file mode 100644
index 0000000..42b27cc
--- /dev/null
+++ b/apps/docs/content/docs/examples/ai-agent-workflow.mdx
@@ -0,0 +1,229 @@
+---
+title: AI Agent Workflow
+description: Complete workflow for executing AI-generated code safely
+---
+
+# AI Agent Workflow
+
+This example demonstrates a complete workflow for executing AI-generated code safely in isolated sandboxes.
+
+## Complete Example
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+async function executeAIAgent(aiGeneratedCode: string) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ try {
+ // Create isolated sandbox
+ const sandbox = await sdk.createDevbox({
+ name: `ai-agent-${Date.now()}`,
+ runtime: 'python',
+ resource: { cpu: 2, memory: 1024 }
+ })
+
+ // Wait for sandbox to be ready
+ await sandbox.waitForReady()
+
+ // Execute AI-generated code
+ const result = await sandbox.codeRun(aiGeneratedCode, {
+ timeout: 30
+ })
+
+ // Check result
+ if (result.exitCode === 0) {
+ return {
+ success: true,
+ output: result.stdout,
+ error: null
+ }
+ } else {
+ return {
+ success: false,
+ output: result.stdout,
+ error: result.stderr
+ }
+ }
+
+ } catch (error) {
+ console.error('Execution failed:', error)
+ return {
+ success: false,
+ output: null,
+ error: error instanceof Error ? error.message : 'Unknown error'
+ }
+ } finally {
+ // Always clean up
+ try {
+ await sandbox.delete()
+ } catch (error) {
+ console.warn('Cleanup failed:', error)
+ }
+ await sdk.close()
+ }
+}
+```
+
+## With File Operations
+
+```typescript
+async function executeAIWithFiles(aiCode: string, files: Record) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ const sandbox = await sdk.createDevbox({
+ name: `ai-task-${Date.now()}`,
+ runtime: 'python',
+ resource: { cpu: 1, memory: 512 }
+ })
+
+ try {
+ await sandbox.waitForReady()
+
+ // Upload required files
+ await sandbox.batchUpload({ files })
+
+ // Execute AI code
+ const result = await sandbox.codeRun(aiCode)
+
+ // Download results if needed
+ const outputFiles = await sandbox.listFiles('/workspace')
+
+ return {
+ success: result.exitCode === 0,
+ output: result.stdout,
+ files: outputFiles.files.map(f => f.name)
+ }
+
+ } finally {
+ await sandbox.delete()
+ await sdk.close()
+ }
+}
+```
+
+## With Error Handling
+
+```typescript
+import {
+ DevboxSDK,
+ DevboxSDKError,
+ FileOperationError,
+ ValidationError
+} from 'devbox-sdk'
+
+async function safeExecuteAI(code: string) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ let sandbox = null
+
+ try {
+ // Validate code before execution
+ if (!code || code.length === 0) {
+ throw new ValidationError('Code cannot be empty')
+ }
+
+ // Create sandbox
+ sandbox = await sdk.createDevbox({
+ name: `ai-${Date.now()}`,
+ runtime: 'python',
+ resource: { cpu: 1, memory: 512 }
+ })
+
+ await sandbox.waitForReady()
+
+ // Execute with timeout
+ const result = await sandbox.codeRun(code, {
+ timeout: 30
+ })
+
+ return {
+ success: result.exitCode === 0,
+ stdout: result.stdout,
+ stderr: result.stderr,
+ exitCode: result.exitCode
+ }
+
+ } catch (error) {
+ if (error instanceof ValidationError) {
+ console.error('Validation error:', error.message)
+ } else if (error instanceof FileOperationError) {
+ console.error('File operation failed:', error.message)
+ } else if (error instanceof DevboxSDKError) {
+ console.error('SDK error:', error.message)
+ } else {
+ console.error('Unexpected error:', error)
+ }
+
+ throw error
+
+ } finally {
+ if (sandbox) {
+ try {
+ await sandbox.delete()
+ } catch (error) {
+ console.warn('Failed to delete sandbox:', error)
+ }
+ }
+ await sdk.close()
+ }
+}
+```
+
+## Batch Processing
+
+Process multiple AI tasks in parallel:
+
+```typescript
+async function processAIBatch(tasks: Array<{ id: string; code: string }>) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ const results = await Promise.allSettled(
+ tasks.map(async (task) => {
+ const sandbox = await sdk.createDevbox({
+ name: `ai-task-${task.id}`,
+ runtime: 'python',
+ resource: { cpu: 1, memory: 512 }
+ })
+
+ try {
+ await sandbox.waitForReady()
+ const result = await sandbox.codeRun(task.code, { timeout: 30 })
+
+ return {
+ id: task.id,
+ success: result.exitCode === 0,
+ output: result.stdout,
+ error: result.stderr
+ }
+ } finally {
+ await sandbox.delete()
+ }
+ })
+ )
+
+ await sdk.close()
+
+ return results.map((result, index) => ({
+ taskId: tasks[index].id,
+ ...(result.status === 'fulfilled' ? result.value : {
+ success: false,
+ error: result.reason?.message || 'Unknown error'
+ })
+ }))
+}
+```
+
+## Next Steps
+
+- Learn about [Automation Tasks](/docs/examples/automation-tasks)
+- Explore [CI/CD Integration](/docs/examples/ci-cd-integration)
+
diff --git a/apps/docs/content/docs/examples/automation-tasks.mdx b/apps/docs/content/docs/examples/automation-tasks.mdx
new file mode 100644
index 0000000..79871e5
--- /dev/null
+++ b/apps/docs/content/docs/examples/automation-tasks.mdx
@@ -0,0 +1,253 @@
+---
+title: Automation Tasks
+description: Run automation scripts safely in isolated environments
+---
+
+# Automation Tasks
+
+Execute untrusted automation scripts safely in isolated sandboxes.
+
+## Basic Automation
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+async function runAutomation(script: string) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ const sandbox = await sdk.createDevbox({
+ name: `automation-${Date.now()}`,
+ runtime: 'node.js',
+ resource: { cpu: 1, memory: 512 }
+ })
+
+ try {
+ await sandbox.waitForReady()
+
+ // Write script
+ await sandbox.writeFile('script.js', script)
+
+ // Execute script
+ const result = await sandbox.execSync({
+ command: 'node',
+ args: ['script.js'],
+ timeout: 60
+ })
+
+ return {
+ success: result.exitCode === 0,
+ output: result.stdout,
+ error: result.stderr
+ }
+
+ } finally {
+ await sandbox.delete()
+ await sdk.close()
+ }
+}
+```
+
+## Build and Test Workflow
+
+```typescript
+async function buildAndTest(projectFiles: Record) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ const sandbox = await sdk.createDevbox({
+ name: `build-${Date.now()}`,
+ runtime: 'node.js',
+ resource: { cpu: 2, memory: 2048 }
+ })
+
+ try {
+ await sandbox.waitForReady()
+
+ // Upload project files
+ await sandbox.batchUpload({ files: projectFiles })
+
+ // Install dependencies
+ const installResult = await sandbox.execSync({
+ command: 'npm',
+ args: ['install'],
+ timeout: 300
+ })
+
+ if (installResult.exitCode !== 0) {
+ throw new Error(`Installation failed: ${installResult.stderr}`)
+ }
+
+ // Run build
+ const buildResult = await sandbox.execSync({
+ command: 'npm',
+ args: ['run', 'build'],
+ timeout: 600
+ })
+
+ if (buildResult.exitCode !== 0) {
+ throw new Error(`Build failed: ${buildResult.stderr}`)
+ }
+
+ // Run tests
+ const testResult = await sandbox.execSync({
+ command: 'npm',
+ args: ['test'],
+ timeout: 300
+ })
+
+ // Download build artifacts
+ const artifacts = await sandbox.downloadFiles([
+ 'dist',
+ 'build'
+ ], { format: 'tar.gz' })
+
+ return {
+ success: testResult.exitCode === 0,
+ buildOutput: buildResult.stdout,
+ testOutput: testResult.stdout,
+ artifacts: artifacts
+ }
+
+ } finally {
+ await sandbox.delete()
+ await sdk.close()
+ }
+}
+```
+
+## Scheduled Tasks
+
+```typescript
+async function runScheduledTask(taskConfig: {
+ name: string
+ command: string
+ args?: string[]
+ cwd?: string
+ timeout?: number
+}) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ const sandbox = await sdk.createDevbox({
+ name: `scheduled-${taskConfig.name}-${Date.now()}`,
+ runtime: 'node.js',
+ resource: { cpu: 1, memory: 512 }
+ })
+
+ try {
+ await sandbox.waitForReady()
+
+ // Execute task asynchronously
+ const process = await sandbox.executeCommand({
+ command: taskConfig.command,
+ args: taskConfig.args,
+ cwd: taskConfig.cwd,
+ timeout: taskConfig.timeout
+ })
+
+ // Monitor process
+ const status = await sandbox.getProcessStatus(process.processId)
+ console.log(`Task ${taskConfig.name} started: ${status.processStatus}`)
+
+ // Wait for completion (or timeout)
+ const maxWait = (taskConfig.timeout || 60) * 1000
+ const startTime = Date.now()
+
+ while (Date.now() - startTime < maxWait) {
+ const currentStatus = await sandbox.getProcessStatus(process.processId)
+
+ if (currentStatus.processStatus === 'completed') {
+ const logs = await sandbox.getProcessLogs(process.processId)
+ return {
+ success: true,
+ output: logs.logs
+ }
+ } else if (currentStatus.processStatus === 'failed') {
+ throw new Error('Task execution failed')
+ }
+
+ await new Promise(resolve => setTimeout(resolve, 1000))
+ }
+
+ // Timeout - kill process
+ await sandbox.killProcess(process.processId)
+ throw new Error('Task execution timeout')
+
+ } finally {
+ await sandbox.delete()
+ await sdk.close()
+ }
+}
+```
+
+## File Processing Pipeline
+
+```typescript
+async function processFiles(
+ files: Record,
+ processor: string
+) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ const sandbox = await sdk.createDevbox({
+ name: `processor-${Date.now()}`,
+ runtime: 'python',
+ resource: { cpu: 2, memory: 1024 }
+ })
+
+ try {
+ await sandbox.waitForReady()
+
+ // Upload files
+ const fileMap: Record = {}
+ for (const [path, content] of Object.entries(files)) {
+ fileMap[`input/${path}`] = content
+ }
+ fileMap['processor.py'] = processor
+
+ await sandbox.batchUpload({ files: fileMap })
+
+ // Run processor
+ const result = await sandbox.execSync({
+ command: 'python3',
+ args: ['processor.py'],
+ timeout: 300
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Processing failed: ${result.stderr}`)
+ }
+
+ // Download processed files
+ const outputFiles = await sandbox.listFiles('output')
+ const processedFiles: Record = {}
+
+ for (const file of outputFiles.files) {
+ const content = await sandbox.readFile(`output/${file.name}`)
+ processedFiles[file.name] = content
+ }
+
+ return {
+ success: true,
+ files: processedFiles,
+ logs: result.stdout
+ }
+
+ } finally {
+ await sandbox.delete()
+ await sdk.close()
+ }
+}
+```
+
+## Next Steps
+
+- Learn about [CI/CD Integration](/docs/examples/ci-cd-integration)
+- Explore [API Reference](/docs/api/devbox-instance)
+
diff --git a/apps/docs/content/docs/examples/ci-cd-integration.mdx b/apps/docs/content/docs/examples/ci-cd-integration.mdx
new file mode 100644
index 0000000..a99cb2c
--- /dev/null
+++ b/apps/docs/content/docs/examples/ci-cd-integration.mdx
@@ -0,0 +1,260 @@
+---
+title: CI/CD Integration
+description: Integrate Devbox SDK into your CI/CD pipeline
+---
+
+# CI/CD Integration
+
+Use Devbox SDK in your CI/CD pipeline to execute build and test tasks in isolated environments.
+
+## GitHub Actions Example
+
+```yaml
+name: Build and Test
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: '22'
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Run tests in sandbox
+ env:
+ KUBECONFIG: ${{ secrets.KUBECONFIG }}
+ run: |
+ node scripts/ci-test.js
+```
+
+```typescript
+// scripts/ci-test.js
+import { DevboxSDK } from 'devbox-sdk'
+import fs from 'fs'
+
+async function runCITests() {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ const sandbox = await sdk.createDevbox({
+ name: `ci-${process.env.GITHUB_RUN_ID}`,
+ runtime: 'node.js',
+ resource: { cpu: 2, memory: 2048 }
+ })
+
+ try {
+ await sandbox.waitForReady()
+
+ // Clone repository
+ await sandbox.git.clone({
+ url: process.env.GITHUB_REPOSITORY_URL,
+ targetDir: '/workspace/repo',
+ auth: {
+ type: 'https',
+ username: process.env.GITHUB_ACTOR,
+ password: process.env.GITHUB_TOKEN
+ }
+ })
+
+ // Install dependencies
+ const installResult = await sandbox.execSync({
+ command: 'npm',
+ args: ['ci'],
+ cwd: '/workspace/repo',
+ timeout: 300
+ })
+
+ if (installResult.exitCode !== 0) {
+ throw new Error(`Installation failed: ${installResult.stderr}`)
+ }
+
+ // Run tests
+ const testResult = await sandbox.execSync({
+ command: 'npm',
+ args: ['test'],
+ cwd: '/workspace/repo',
+ timeout: 600
+ })
+
+ // Upload test results
+ if (testResult.exitCode === 0) {
+ console.log('โ Tests passed')
+ process.exit(0)
+ } else {
+ console.error('โ Tests failed:', testResult.stderr)
+ process.exit(1)
+ }
+
+ } finally {
+ await sandbox.delete()
+ await sdk.close()
+ }
+}
+
+runCITests().catch(error => {
+ console.error('CI test failed:', error)
+ process.exit(1)
+})
+```
+
+## GitLab CI Example
+
+```yaml
+test:
+ script:
+ - npm install
+ - node scripts/ci-test.js
+ variables:
+ KUBECONFIG: $CI_KUBECONFIG
+```
+
+## Jenkins Pipeline Example
+
+```groovy
+pipeline {
+ agent any
+
+ environment {
+ KUBECONFIG = credentials('kubeconfig')
+ }
+
+ stages {
+ stage('Test') {
+ steps {
+ sh 'npm install'
+ sh 'node scripts/ci-test.js'
+ }
+ }
+ }
+}
+```
+
+## Docker Build in Sandbox
+
+```typescript
+async function buildDockerImage(dockerfile: string, context: Record) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ const sandbox = await sdk.createDevbox({
+ name: `docker-build-${Date.now()}`,
+ runtime: 'node.js',
+ resource: { cpu: 4, memory: 4096 }
+ })
+
+ try {
+ await sandbox.waitForReady()
+
+ // Upload Dockerfile and context
+ const files: Record = {
+ 'Dockerfile': dockerfile,
+ ...context
+ }
+ await sandbox.batchUpload({ files })
+
+ // Build Docker image
+ const buildResult = await sandbox.execSync({
+ command: 'docker',
+ args: ['build', '-t', 'my-app', '.'],
+ timeout: 600
+ })
+
+ if (buildResult.exitCode !== 0) {
+ throw new Error(`Docker build failed: ${buildResult.stderr}`)
+ }
+
+ // Export image
+ const exportResult = await sandbox.execSync({
+ command: 'docker',
+ args: ['save', 'my-app', '-o', 'image.tar'],
+ timeout: 300
+ })
+
+ // Download image
+ const imageTar = await sandbox.readFile('image.tar')
+
+ return {
+ success: true,
+ image: imageTar
+ }
+
+ } finally {
+ await sandbox.delete()
+ await sdk.close()
+ }
+}
+```
+
+## Parallel Test Execution
+
+```typescript
+async function runParallelTests(testSuites: string[]) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ const results = await Promise.all(
+ testSuites.map(async (suite, index) => {
+ const sandbox = await sdk.createDevbox({
+ name: `test-${index}-${Date.now()}`,
+ runtime: 'node.js',
+ resource: { cpu: 1, memory: 1024 }
+ })
+
+ try {
+ await sandbox.waitForReady()
+
+ // Clone and setup
+ await sandbox.git.clone({
+ url: process.env.REPO_URL,
+ targetDir: '/workspace/repo'
+ })
+
+ await sandbox.execSync({
+ command: 'npm',
+ args: ['ci'],
+ cwd: '/workspace/repo'
+ })
+
+ // Run specific test suite
+ const result = await sandbox.execSync({
+ command: 'npm',
+ args: ['test', '--', suite],
+ cwd: '/workspace/repo',
+ timeout: 300
+ })
+
+ return {
+ suite,
+ success: result.exitCode === 0,
+ output: result.stdout,
+ error: result.stderr
+ }
+
+ } finally {
+ await sandbox.delete()
+ }
+ })
+ )
+
+ await sdk.close()
+
+ return results
+}
+```
+
+## Next Steps
+
+- Read [API Reference](/docs/api/devbox-sdk)
+- Explore [Guides](/docs/guides/secure-code-execution)
+
diff --git a/apps/docs/content/docs/getting-started/configuration.mdx b/apps/docs/content/docs/getting-started/configuration.mdx
new file mode 100644
index 0000000..7a9a1d0
--- /dev/null
+++ b/apps/docs/content/docs/getting-started/configuration.mdx
@@ -0,0 +1,190 @@
+---
+title: Configuration
+description: Configure Devbox SDK for your needs
+---
+
+# Configuration
+
+## SDK Configuration
+
+When creating a `DevboxSDK` instance, you can configure various options:
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+const sdk = new DevboxSDK({
+ // Required: Kubernetes configuration
+ kubeconfig: process.env.KUBECONFIG,
+
+ // Optional: API base URL
+ baseUrl: 'https://api.sealos.io',
+
+ // Optional: HTTP client configuration
+ http: {
+ timeout: 30000, // Request timeout in milliseconds
+ retries: 3, // Number of retry attempts
+ rejectUnauthorized: true // SSL certificate verification
+ }
+})
+```
+
+### Configuration Options
+
+#### `kubeconfig` (required)
+
+Kubernetes configuration for accessing the Devbox API. Can be:
+- File path: `'/path/to/kubeconfig'`
+- Environment variable: `process.env.KUBECONFIG`
+- Kubeconfig content: Raw YAML string
+
+#### `baseUrl` (optional)
+
+Base URL for the Devbox API. Defaults to the API endpoint from your kubeconfig.
+
+#### `http` (optional)
+
+HTTP client configuration:
+
+- **`timeout`** (number): Request timeout in milliseconds. Default: `30000` (30 seconds)
+- **`retries`** (number): Number of retry attempts for failed requests. Default: `3`
+- **`rejectUnauthorized`** (boolean): Whether to reject unauthorized SSL certificates. Default: `true`
+
+## Sandbox Configuration
+
+When creating a sandbox, you can configure:
+
+```typescript
+const sandbox = await sdk.createDevbox({
+ // Required: Unique name for the sandbox
+ name: 'my-sandbox',
+
+ // Required: Runtime environment
+ runtime: 'node.js', // or 'python', 'next.js', 'react', etc.
+
+ // Required: Resource allocation
+ resource: {
+ cpu: 2, // CPU cores
+ memory: 4096 // Memory in MB
+ },
+
+ // Optional: Port mappings
+ ports: [
+ {
+ number: 3000,
+ protocol: 'HTTP'
+ }
+ ],
+
+ // Optional: Environment variables
+ env: [
+ {
+ name: 'NODE_ENV',
+ value: 'production'
+ }
+ ]
+})
+```
+
+### Runtime Options
+
+Available runtime environments:
+- `node.js` - Node.js runtime
+- `python` - Python runtime
+- `next.js` - Next.js runtime
+- `react` - React runtime
+- And more...
+
+### Resource Limits
+
+Configure CPU and memory limits:
+
+```typescript
+resource: {
+ cpu: 2, // Number of CPU cores (minimum: 1)
+ memory: 4096 // Memory in MB (minimum: 512)
+}
+```
+
+### Port Mappings
+
+Expose ports from the sandbox:
+
+```typescript
+ports: [
+ {
+ number: 3000, // Port number (3000-9999)
+ protocol: 'HTTP' // Protocol: 'HTTP' or 'TCP'
+ }
+]
+```
+
+### Environment Variables
+
+Set environment variables for the sandbox:
+
+```typescript
+env: [
+ {
+ name: 'API_KEY',
+ value: 'your-api-key'
+ },
+ {
+ name: 'DEBUG',
+ value: 'true'
+ }
+]
+```
+
+## Environment Variables
+
+You can also configure the SDK using environment variables:
+
+### `KUBECONFIG`
+
+Path to your Kubernetes configuration file:
+
+```bash
+export KUBECONFIG=/path/to/kubeconfig
+```
+
+## Best Practices
+
+1. **Resource Limits**: Always set appropriate resource limits based on your workload
+2. **Timeout Configuration**: Adjust timeout based on your expected execution time
+3. **Error Handling**: Always handle errors and clean up resources
+4. **Connection Management**: Reuse SDK instances when possible, but always call `close()` when done
+
+## Example: Production Configuration
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG,
+ // Optional: http configuration for advanced use cases
+ http: {
+ timeout: 60000, // 60 seconds for longer operations
+ retries: 5, // More retries for production
+ rejectUnauthorized: true
+ }
+})
+
+// Create sandbox with production settings
+const sandbox = await sdk.createDevbox({
+ name: `prod-task-${Date.now()}`,
+ runtime: 'node.js',
+ resource: {
+ cpu: 4,
+ memory: 8192
+ },
+ env: [
+ { name: 'NODE_ENV', value: 'production' }
+ ]
+})
+```
+
+## Next Steps
+
+- Learn about [Secure Code Execution](/docs/guides/secure-code-execution)
+- Explore [File Operations](/docs/guides/file-operations)
+
diff --git a/apps/docs/content/docs/getting-started/installation.mdx b/apps/docs/content/docs/getting-started/installation.mdx
new file mode 100644
index 0000000..15437cf
--- /dev/null
+++ b/apps/docs/content/docs/getting-started/installation.mdx
@@ -0,0 +1,104 @@
+---
+title: Installation
+description: Install and configure Devbox SDK
+---
+
+# Installation
+
+## Requirements
+
+- **Node.js** >= 22.0.0
+- **npm** >= 11.0.0 (or yarn/pnpm)
+- **Kubernetes cluster access** - You need access to a Kubernetes cluster with Devbox API
+- **Kubeconfig** - Kubernetes configuration file or environment variable
+
+## Install the Package
+
+```bash
+npm install devbox-sdk
+```
+
+Or with yarn:
+
+```bash
+yarn add devbox-sdk
+```
+
+Or with pnpm:
+
+```bash
+pnpm add devbox-sdk
+```
+
+## Kubernetes Configuration
+
+Devbox SDK requires Kubernetes cluster access. You need to provide your Kubernetes configuration in one of the following ways:
+
+### Option 1: Environment Variable
+
+```bash
+export KUBECONFIG=/path/to/your/kubeconfig
+```
+
+### Option 2: File Path
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+const sdk = new DevboxSDK({
+ kubeconfig: '/path/to/your/kubeconfig'
+})
+```
+
+### Option 3: Kubeconfig Content
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+import fs from 'fs'
+
+const kubeconfigContent = fs.readFileSync('/path/to/kubeconfig', 'utf-8')
+
+const sdk = new DevboxSDK({
+ kubeconfig: kubeconfigContent
+})
+```
+
+## Verify Installation
+
+Create a simple test file to verify your installation:
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+})
+
+// Test connection
+const devboxes = await sdk.listDevboxes()
+console.log(`Found ${devboxes.length} devboxes`)
+
+await sdk.close()
+```
+
+## TypeScript Support
+
+Devbox SDK is written in TypeScript and includes full type definitions. No additional `@types` package is needed.
+
+If you're using TypeScript, make sure your `tsconfig.json` includes:
+
+```json
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "target": "ES2022",
+ "moduleResolution": "node"
+ }
+}
+```
+
+## Next Steps
+
+- Read the [Quick Start Guide](/docs/getting-started/quick-start)
+- Learn about [Configuration](/docs/getting-started/configuration)
+
diff --git a/apps/docs/content/docs/getting-started/quick-start.mdx b/apps/docs/content/docs/getting-started/quick-start.mdx
new file mode 100644
index 0000000..4146a26
--- /dev/null
+++ b/apps/docs/content/docs/getting-started/quick-start.mdx
@@ -0,0 +1,178 @@
+---
+title: Quick Start
+description: Get started with Devbox SDK in minutes
+---
+
+# Quick Start
+
+This guide will help you create your first secure sandbox and execute code safely.
+
+## Create Your First Sandbox
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+// Initialize SDK
+const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+})
+
+// Create a sandbox
+const sandbox = await sdk.createDevbox({
+ name: 'my-first-sandbox',
+ runtime: 'python',
+ resource: { cpu: 1, memory: 512 }
+})
+
+console.log(`Created sandbox: ${sandbox.name}`)
+```
+
+## Execute Code
+
+Execute code safely in the isolated sandbox:
+
+```typescript
+// Execute Python code
+const result = await sandbox.codeRun(`
+import requests
+response = requests.get('https://api.github.com')
+print(f"Status: {response.status_code}")
+`)
+
+console.log(result.stdout) // "Status: 200"
+console.log(result.exitCode) // 0
+```
+
+## File Operations
+
+Write and read files in the sandbox:
+
+```typescript
+// Write a file
+await sandbox.writeFile('app.py', `
+def hello():
+ print("Hello from sandbox!")
+
+hello()
+`)
+
+// Read the file
+const content = await sandbox.readFile('app.py')
+console.log(content.toString())
+
+// Execute the file
+const result = await sandbox.execSync({
+ command: 'python3',
+ args: ['app.py']
+})
+console.log(result.stdout) // "Hello from sandbox!"
+```
+
+## Process Management
+
+Execute commands synchronously or asynchronously:
+
+```typescript
+// Synchronous execution (waits for completion)
+const result = await sandbox.execSync({
+ command: 'echo',
+ args: ['Hello World'],
+ cwd: '/workspace'
+})
+
+console.log(result.stdout) // "Hello World"
+console.log(result.exitCode) // 0
+
+// Asynchronous execution (returns immediately)
+const process = await sandbox.executeCommand({
+ command: 'sleep',
+ args: ['10']
+})
+
+console.log(`Process ID: ${process.processId}`)
+
+// Check process status
+const status = await sandbox.getProcessStatus(process.processId)
+console.log(`Status: ${status.processStatus}`)
+```
+
+## Git Operations
+
+Clone and work with Git repositories:
+
+```typescript
+// Clone a repository
+await sandbox.git.clone({
+ url: 'https://github.com/user/repo.git',
+ targetDir: '/workspace/repo'
+})
+
+// Check status
+const status = await sandbox.git.status('/workspace/repo')
+console.log(`Current branch: ${status.branch}`)
+```
+
+## Clean Up
+
+Always clean up resources when done:
+
+```typescript
+// Delete the sandbox
+await sandbox.delete()
+
+// Close SDK connections
+await sdk.close()
+```
+
+## Complete Example
+
+Here's a complete example that demonstrates the full workflow:
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+async function main() {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ try {
+ // Create sandbox
+ const sandbox = await sdk.createDevbox({
+ name: 'example-sandbox',
+ runtime: 'node.js',
+ resource: { cpu: 1, memory: 512 }
+ })
+
+ // Write code
+ await sandbox.writeFile('index.js', `
+ const fs = require('fs');
+ const files = fs.readdirSync('.');
+ console.log('Files:', files.join(', '));
+ `)
+
+ // Execute code
+ const result = await sandbox.codeRun(`
+ const fs = require('fs');
+ const files = fs.readdirSync('.');
+ console.log('Files:', files.join(', '));
+ `)
+
+ console.log(result.stdout)
+
+ // Clean up
+ await sandbox.delete()
+ } finally {
+ await sdk.close()
+ }
+}
+
+main().catch(console.error)
+```
+
+## Next Steps
+
+- Learn about [Secure Code Execution](/docs/guides/secure-code-execution)
+- Explore [File Operations](/docs/guides/file-operations)
+- Read the [API Reference](/docs/api/devbox-sdk)
+
diff --git a/apps/docs/content/docs/guides/file-operations.mdx b/apps/docs/content/docs/guides/file-operations.mdx
new file mode 100644
index 0000000..63f2707
--- /dev/null
+++ b/apps/docs/content/docs/guides/file-operations.mdx
@@ -0,0 +1,274 @@
+---
+title: File Operations
+description: Complete guide to file operations in sandboxes
+---
+
+# File Operations
+
+Devbox SDK provides comprehensive file operations for managing files in isolated sandboxes.
+
+## Writing Files
+
+### Write Text Files
+
+```typescript
+// Write a simple text file
+await sandbox.writeFile('hello.txt', 'Hello, World!')
+
+// Write with encoding
+await sandbox.writeFile('data.txt', 'Hello', {
+ encoding: 'utf8'
+})
+```
+
+### Write Binary Files
+
+```typescript
+// Write binary data
+const imageBuffer = Buffer.from(imageData, 'base64')
+await sandbox.writeFile('image.png', imageBuffer)
+
+// Or with base64 encoding
+await sandbox.writeFile('image.png', imageBuffer, {
+ encoding: 'base64'
+})
+```
+
+### Write Code Files
+
+```typescript
+// Write JavaScript file
+await sandbox.writeFile('app.js', `
+const express = require('express');
+const app = express();
+
+app.get('/', (req, res) => {
+ res.send('Hello World!');
+});
+
+app.listen(3000);
+`)
+
+// Write Python file
+await sandbox.writeFile('app.py', `
+from flask import Flask
+app = Flask(__name__)
+
+@app.route('/')
+def hello():
+ return 'Hello World!'
+
+if __name__ == '__main__':
+ app.run(port=3000)
+`)
+```
+
+## Reading Files
+
+### Read Text Files
+
+```typescript
+// Read file as Buffer
+const buffer = await sandbox.readFile('hello.txt')
+console.log(buffer.toString()) // "Hello, World!"
+
+// Read with options
+const content = await sandbox.readFile('data.txt', {
+ encoding: 'utf8'
+})
+```
+
+### Read Binary Files
+
+```typescript
+// Read binary file
+const imageBuffer = await sandbox.readFile('image.png')
+// imageBuffer is a Buffer
+```
+
+## Listing Files
+
+```typescript
+// List files in directory
+const files = await sandbox.listFiles('/workspace')
+
+console.log('Files:', files.files.map(f => f.name))
+console.log('Directories:', files.directories.map(d => d.name))
+```
+
+## Batch Operations
+
+### Batch Upload
+
+Upload multiple files at once:
+
+```typescript
+await sandbox.batchUpload({
+ files: {
+ 'src/index.js': 'console.log("Hello")',
+ 'src/utils.js': 'export function helper() {}',
+ 'package.json': JSON.stringify({
+ name: 'my-app',
+ version: '1.0.0'
+ })
+ }
+})
+```
+
+### Batch Download
+
+Download multiple files:
+
+```typescript
+// Download as tar.gz (default)
+const archive = await sandbox.downloadFiles([
+ 'src/index.js',
+ 'src/utils.js',
+ 'package.json'
+])
+
+// Download as tar
+const tarArchive = await sandbox.downloadFiles([
+ 'src/index.js',
+ 'src/utils.js'
+], { format: 'tar' })
+
+// Download as multipart
+const multipart = await sandbox.downloadFiles([
+ 'file1.txt',
+ 'file2.txt'
+], { format: 'multipart' })
+```
+
+## Moving and Renaming
+
+### Move Files
+
+```typescript
+// Move file
+await sandbox.moveFile('old/path.txt', 'new/path.txt')
+
+// Move directory
+await sandbox.moveFile('old/dir', 'new/dir')
+
+// Move with overwrite
+await sandbox.moveFile('source.txt', 'dest.txt', true)
+```
+
+### Rename Files
+
+```typescript
+// Rename file
+await sandbox.renameFile('old-name.txt', 'new-name.txt')
+
+// Rename directory
+await sandbox.renameFile('old-dir', 'new-dir')
+```
+
+## Deleting Files
+
+```typescript
+// Delete file
+await sandbox.deleteFile('unwanted.txt')
+
+// Delete directory (if supported)
+await sandbox.deleteFile('unwanted-dir')
+```
+
+## Path Validation
+
+All file operations automatically validate paths to prevent directory traversal attacks:
+
+```typescript
+// These will throw errors:
+await sandbox.readFile('../../../etc/passwd') // โ Path traversal
+await sandbox.readFile('') // โ Empty path
+await sandbox.readFile('/absolute/path') // โ Absolute path (if not allowed)
+```
+
+## Best Practices
+
+### 1. Use Relative Paths
+
+```typescript
+// โ Good
+await sandbox.writeFile('src/index.js', code)
+
+// โ Avoid
+await sandbox.writeFile('/absolute/path/index.js', code)
+```
+
+### 2. Handle Errors
+
+```typescript
+try {
+ await sandbox.readFile('file.txt')
+} catch (error) {
+ if (error instanceof FileOperationError) {
+ console.error('File not found or access denied')
+ }
+}
+```
+
+### 3. Use Batch Operations
+
+For multiple files, use batch operations:
+
+```typescript
+// โ Efficient
+await sandbox.batchUpload({
+ files: {
+ 'file1.js': content1,
+ 'file2.js': content2,
+ 'file3.js': content3
+ }
+})
+
+// โ Less efficient
+await sandbox.writeFile('file1.js', content1)
+await sandbox.writeFile('file2.js', content2)
+await sandbox.writeFile('file3.js', content3)
+```
+
+## Complete Example
+
+```typescript
+async function setupProject(sandbox: DevboxInstance) {
+ // Create project structure
+ await sandbox.batchUpload({
+ files: {
+ 'package.json': JSON.stringify({
+ name: 'my-project',
+ version: '1.0.0',
+ scripts: {
+ start: 'node index.js'
+ }
+ }),
+ 'index.js': `
+ const express = require('express');
+ const app = express();
+ app.get('/', (req, res) => res.send('Hello!'));
+ app.listen(3000);
+ `,
+ '.gitignore': 'node_modules/\n.env'
+ }
+ })
+
+ // Verify files
+ const files = await sandbox.listFiles('.')
+ console.log('Created files:', files.files.map(f => f.name))
+
+ // Watch for changes
+ const ws = await sandbox.watchFiles('.', (event) => {
+ console.log(`File ${event.type}: ${event.path}`)
+ })
+
+ return ws
+}
+```
+
+## Next Steps
+
+- Learn about [Process Management](/docs/guides/process-management)
+- Explore [Git Integration](/docs/guides/git-integration)
+
diff --git a/apps/docs/content/docs/guides/git-integration.mdx b/apps/docs/content/docs/guides/git-integration.mdx
new file mode 100644
index 0000000..c1d5442
--- /dev/null
+++ b/apps/docs/content/docs/guides/git-integration.mdx
@@ -0,0 +1,230 @@
+---
+title: Git Integration
+description: Work with Git repositories in sandboxes
+---
+
+# Git Integration
+
+Devbox SDK provides native Git integration for cloning, pulling, pushing, and managing Git repositories securely.
+
+## Clone Repository
+
+### Public Repository
+
+```typescript
+await sandbox.git.clone({
+ url: 'https://github.com/user/repo.git',
+ targetDir: '/workspace/repo'
+})
+```
+
+### Private Repository (HTTPS)
+
+```typescript
+await sandbox.git.clone({
+ url: 'https://github.com/user/private-repo.git',
+ targetDir: '/workspace/repo',
+ auth: {
+ type: 'https',
+ username: 'your-username',
+ password: 'your-token' // GitHub personal access token
+ }
+})
+```
+
+### Private Repository (SSH)
+
+```typescript
+await sandbox.git.clone({
+ url: 'git@github.com:user/private-repo.git',
+ targetDir: '/workspace/repo',
+ auth: {
+ type: 'ssh',
+ privateKey: process.env.SSH_PRIVATE_KEY,
+ passphrase: process.env.SSH_PASSPHRASE // Optional
+ }
+})
+```
+
+### Clone Specific Branch
+
+```typescript
+await sandbox.git.clone({
+ url: 'https://github.com/user/repo.git',
+ targetDir: '/workspace/repo',
+ branch: 'develop'
+})
+```
+
+### Shallow Clone
+
+```typescript
+await sandbox.git.clone({
+ url: 'https://github.com/user/repo.git',
+ targetDir: '/workspace/repo',
+ depth: 1 // Clone only latest commit
+})
+```
+
+## Pull Changes
+
+```typescript
+// Pull from current branch
+await sandbox.git.pull({
+ path: '/workspace/repo',
+ auth: {
+ type: 'https',
+ username: 'user',
+ password: 'token'
+ }
+})
+
+// Pull specific branch
+await sandbox.git.pull({
+ path: '/workspace/repo',
+ branch: 'main',
+ auth: { /* ... */ }
+})
+```
+
+## Push Changes
+
+```typescript
+await sandbox.git.push({
+ path: '/workspace/repo',
+ branch: 'main',
+ auth: {
+ type: 'https',
+ username: 'user',
+ password: 'token'
+ }
+})
+```
+
+## Check Status
+
+```typescript
+const status = await sandbox.git.status('/workspace/repo')
+
+console.log(`Current branch: ${status.branch}`)
+console.log(`Is clean: ${status.isClean}`)
+console.log(`Changes:`, status.changes)
+```
+
+## List Branches
+
+```typescript
+const branches = await sandbox.git.branches('/workspace/repo')
+
+branches.forEach(branch => {
+ console.log(`Branch: ${branch.name}`)
+ console.log(`Current: ${branch.isCurrent}`)
+ console.log(`Commit: ${branch.commit}`)
+})
+```
+
+## Complete Workflow
+
+```typescript
+async function deployFromGit(sandbox: DevboxInstance) {
+ // Clone repository
+ await sandbox.git.clone({
+ url: 'https://github.com/user/app.git',
+ targetDir: '/workspace/app',
+ auth: {
+ type: 'https',
+ username: process.env.GITHUB_USER,
+ password: process.env.GITHUB_TOKEN
+ }
+ })
+
+ // Check status
+ const status = await sandbox.git.status('/workspace/app')
+ console.log(`Branch: ${status.branch}`)
+
+ // Install dependencies
+ await sandbox.execSync({
+ command: 'npm',
+ args: ['install'],
+ cwd: '/workspace/app'
+ })
+
+ // Build
+ await sandbox.execSync({
+ command: 'npm',
+ args: ['run', 'build'],
+ cwd: '/workspace/app'
+ })
+
+ // Run tests
+ const testResult = await sandbox.execSync({
+ command: 'npm',
+ args: ['test'],
+ cwd: '/workspace/app'
+ })
+
+ if (testResult.exitCode === 0) {
+ console.log('Tests passed!')
+ } else {
+ throw new Error('Tests failed')
+ }
+}
+```
+
+## Authentication Best Practices
+
+### Use Environment Variables
+
+```typescript
+await sandbox.git.clone({
+ url: repoUrl,
+ targetDir: '/workspace/repo',
+ auth: {
+ type: 'https',
+ username: process.env.GIT_USERNAME,
+ password: process.env.GIT_TOKEN // Never hardcode!
+ }
+})
+```
+
+### Use SSH Keys Securely
+
+```typescript
+// Read SSH key from secure storage
+const privateKey = await readSecureKey('ssh-key')
+
+await sandbox.git.clone({
+ url: 'git@github.com:user/repo.git',
+ targetDir: '/workspace/repo',
+ auth: {
+ type: 'ssh',
+ privateKey: privateKey,
+ passphrase: process.env.SSH_PASSPHRASE
+ }
+})
+```
+
+## Error Handling
+
+```typescript
+try {
+ await sandbox.git.clone({
+ url: 'https://github.com/user/repo.git',
+ targetDir: '/workspace/repo'
+ })
+} catch (error) {
+ if (error.message.includes('authentication')) {
+ console.error('Authentication failed')
+ } else if (error.message.includes('not found')) {
+ console.error('Repository not found')
+ } else {
+ console.error('Clone failed:', error)
+ }
+}
+```
+
+## Next Steps
+
+- Explore [API Reference](/docs/api/devbox-instance)
+- Check out [Examples](/docs/examples/ai-agent-workflow)
+
diff --git a/apps/docs/content/docs/guides/process-management.mdx b/apps/docs/content/docs/guides/process-management.mdx
new file mode 100644
index 0000000..dd5922c
--- /dev/null
+++ b/apps/docs/content/docs/guides/process-management.mdx
@@ -0,0 +1,275 @@
+---
+title: Process Management
+description: Execute and manage processes in sandboxes
+---
+
+# Process Management
+
+Devbox SDK provides comprehensive process execution and management capabilities.
+
+## Execution Methods
+
+### Synchronous Execution
+
+Execute a command and wait for completion:
+
+```typescript
+const result = await sandbox.execSync({
+ command: 'echo',
+ args: ['Hello World'],
+ cwd: '/workspace',
+ timeout: 30
+})
+
+console.log(result.stdout) // "Hello World"
+console.log(result.stderr) // ""
+console.log(result.exitCode) // 0
+console.log(result.durationMs) // Execution time in milliseconds
+```
+
+### Asynchronous Execution
+
+Start a process and get process ID immediately:
+
+```typescript
+const process = await sandbox.executeCommand({
+ command: 'npm',
+ args: ['run', 'build'],
+ cwd: '/workspace'
+})
+
+console.log(`Process ID: ${process.processId}`)
+console.log(`PID: ${process.pid}`)
+```
+
+### Stream Execution
+
+Get real-time output using Server-Sent Events:
+
+```typescript
+const stream = await sandbox.execSyncStream({
+ command: 'npm',
+ args: ['run', 'dev']
+})
+
+const reader = stream.getReader()
+const decoder = new TextDecoder()
+
+while (true) {
+ const { done, value } = await reader.read()
+ if (done) break
+
+ const text = decoder.decode(value, { stream: true })
+ console.log(text) // Real-time output
+}
+```
+
+### Code Execution
+
+Execute code strings directly:
+
+```typescript
+// Python code
+const result = await sandbox.codeRun(`
+import requests
+response = requests.get('https://api.github.com')
+print(f"Status: {response.status_code}")
+`)
+
+// Node.js code
+const result = await sandbox.codeRun(`
+const fs = require('fs');
+const files = fs.readdirSync('.');
+console.log('Files:', files.join(', '));
+`)
+```
+
+## Process Options
+
+### Working Directory
+
+```typescript
+await sandbox.execSync({
+ command: 'pwd',
+ cwd: '/workspace/project'
+})
+```
+
+### Environment Variables
+
+```typescript
+await sandbox.execSync({
+ command: 'sh',
+ args: ['-c', 'echo $MY_VAR'],
+ env: {
+ MY_VAR: 'my-value',
+ NODE_ENV: 'production'
+ }
+})
+```
+
+### Timeout
+
+```typescript
+await sandbox.execSync({
+ command: 'sleep',
+ args: ['10'],
+ timeout: 5 // Kill after 5 seconds
+})
+```
+
+### Shell
+
+```typescript
+await sandbox.execSync({
+ command: 'echo $HOME',
+ shell: '/bin/bash'
+})
+```
+
+## Process Management
+
+### Get Process Status
+
+```typescript
+const status = await sandbox.getProcessStatus(processId)
+
+console.log(`Status: ${status.processStatus}`) // 'running', 'completed', 'failed'
+console.log(`PID: ${status.pid}`)
+console.log(`Started: ${status.startedAt}`)
+```
+
+### Get Process Logs
+
+```typescript
+// Get all logs
+const logs = await sandbox.getProcessLogs(processId)
+
+// Get last N lines
+const logs = await sandbox.getProcessLogs(processId, {
+ lines: 100
+})
+
+console.log(logs.logs) // Array of log entries
+```
+
+### Kill Process
+
+```typescript
+// Kill with default signal (SIGTERM)
+await sandbox.killProcess(processId)
+
+// Kill with specific signal
+await sandbox.killProcess(processId, {
+ signal: 'SIGKILL'
+})
+```
+
+### List Processes
+
+```typescript
+const result = await sandbox.listProcesses()
+
+result.processes.forEach(proc => {
+ console.log(`ID: ${proc.id}`)
+ console.log(`PID: ${proc.pid}`)
+ console.log(`Command: ${proc.command}`)
+ console.log(`Status: ${proc.status}`)
+})
+```
+
+## Complete Workflow
+
+```typescript
+async function runBuild(sandbox: DevboxInstance) {
+ // Start build process
+ const process = await sandbox.executeCommand({
+ command: 'npm',
+ args: ['run', 'build'],
+ cwd: '/workspace',
+ timeout: 300
+ })
+
+ // Monitor progress
+ const checkInterval = setInterval(async () => {
+ const status = await sandbox.getProcessStatus(process.processId)
+
+ if (status.processStatus === 'completed') {
+ clearInterval(checkInterval)
+
+ // Get final logs
+ const logs = await sandbox.getProcessLogs(process.processId)
+ console.log('Build completed:', logs.logs)
+ } else if (status.processStatus === 'failed') {
+ clearInterval(checkInterval)
+ console.error('Build failed')
+ }
+ }, 2000)
+
+ // Timeout after 5 minutes
+ setTimeout(() => {
+ clearInterval(checkInterval)
+ sandbox.killProcess(process.processId)
+ }, 300000)
+}
+```
+
+## Error Handling
+
+```typescript
+try {
+ const result = await sandbox.execSync({
+ command: 'nonexistent-command'
+ })
+} catch (error) {
+ if (error instanceof FileOperationError) {
+ console.error('Command not found')
+ } else {
+ console.error('Execution error:', error)
+ }
+}
+```
+
+## Best Practices
+
+### 1. Always Set Timeouts
+
+```typescript
+await sandbox.execSync({
+ command: 'long-running-task',
+ timeout: 60 // Prevent hanging
+})
+```
+
+### 2. Monitor Long-Running Processes
+
+```typescript
+const process = await sandbox.executeCommand({
+ command: 'long-task'
+})
+
+// Check status periodically
+const status = await sandbox.getProcessStatus(process.processId)
+```
+
+### 3. Clean Up Processes
+
+```typescript
+try {
+ const process = await sandbox.executeCommand({...})
+ // ... do work
+} finally {
+ // Kill if still running
+ try {
+ await sandbox.killProcess(process.processId)
+ } catch (error) {
+ // Process may have already completed
+ }
+}
+```
+
+## Next Steps
+
+- Learn about [Git Integration](/docs/guides/git-integration)
+- Explore [API Reference](/docs/api/devbox-instance)
+
diff --git a/apps/docs/content/docs/guides/sdk-architecture.mdx b/apps/docs/content/docs/guides/sdk-architecture.mdx
new file mode 100644
index 0000000..7115dc9
--- /dev/null
+++ b/apps/docs/content/docs/guides/sdk-architecture.mdx
@@ -0,0 +1,140 @@
+---
+title: SDK Architecture & Internals
+description: Deep dive into Devbox SDK's architecture, security model, and performance optimizations
+---
+
+# SDK Architecture & Internals
+
+This guide provides a deep technical analysis of the Devbox SDK. It explains how the SDK manages connections, enforces security, and optimizes data transfer performance.
+
+## High-Level Architecture
+
+The Devbox SDK acts as a smart client that orchestrates secure environments on Kubernetes. It bridges your application with isolated "Devboxes" (Kubernetes Pods) running on the Sealos cloud.
+
+```mermaid
+graph TD
+ Client[Devbox SDK Client]
+
+ subgraph "Control Plane"
+ API[Sealos API]
+ end
+
+ subgraph "Data Plane (User Namespace)"
+ Pod[Devbox Pod]
+ Agent[Agent Server]
+ Runtime[Runtime (Node/Python)]
+ end
+
+ Client -- "1. Create/manage" --> API
+ API -- "2. Schedule" --> Pod
+ Client -- "3. Direct Connection (HTTPS)" --> Agent
+ Agent -- "4. Execute" --> Runtime
+```
+
+1. **Control Plane**: The SDK communicates with the Sealos API (via Kubeconfig) to create and manage the lifecycle of Devboxes (CRDs).
+2. **Data Plane**: Once a Devbox is ready, the SDK establishes a direct, secure connection to the `Agent Server` running inside the Pod.
+
+## Connection Strategy
+
+The `ContainerUrlResolver` class is responsible for establishing robust connections. It employs a multi-tiered resolution strategy to ensure connectivity:
+
+1. **Agent URL (Primary)**: Uses the dedicated `agentServer` URL (e.g., `https://devbox-{id}-agent.domain.com`). This provides the most stable connection with SSL termination.
+2. **Public Address**: If the agent URL is unavailable, it attempts to use the mapped public address/port.
+3. **Private Address**: Inside the cluster, it falls back to the internal service address.
+4. **Pod IP**: As a last resort, it connects directly to the Pod IP (requires direct network access).
+
+### Connection Pooling
+
+To minimize latency, the SDK maintains a connection pool. It caches the resolved URL and authentication tokens, refreshing them only when necessary (e.g., after a restart).
+
+## Performance Optimization
+
+A key challenge in remote execution is handling file transfers efficiently. The SDK implements an adaptive strategy to balance overhead and throughput.
+
+### Adaptive File Writes
+
+The `writeFile` method analyzes the payload size and content type to choose the optimal transport mode:
+
+- **JSON Mode (Small Files < 1MB)**:
+ - Content is base64-encoded and sent as a JSON payload.
+ - **Pros**: Simple, works with standard REST parsers.
+ - **Cons**: ~33% overhead due to base64 encoding; higher memory usage on the server (Standard JSON decoder buffers the entire request).
+
+- **Binary Mode (Large Files > 1MB)**:
+ - Content is sent as raw binary data (`application/octet-stream`).
+ - The target path is passed via query parameters.
+ - **Pros**: Zero encoding overhead; streams directly to disk on the server.
+ - **Cons**: Requires a dedicated endpoint that handles raw streams.
+
+This optimization significantly reduces memory pressure on the Agent Server when uploading large datasets or binaries.
+
+```typescript
+// Internal logic simplified
+const LARGE_FILE_THRESHOLD = 1 * 1024 * 1024; // 1MB
+
+if (contentSize > LARGE_FILE_THRESHOLD) {
+ // Use Binary Mode
+ await client.post('/api/v1/files/write', {
+ params: { path },
+ headers: { 'Content-Type': 'application/octet-stream' },
+ body: content
+ });
+} else {
+ // Use JSON Mode
+ await client.post('/api/v1/files/write', {
+ body: { path, content: toBase64(content) }
+ });
+}
+```
+
+## Security Internals
+
+Security is enforced at multiple layers, from the client SDK down to the kernel isolation.
+
+### Client-Side Path Validation
+
+Before sending any file operation request, the SDK performs strict path validation to prevent **Directory Traversal Attacks**.
+
+The `validatePath` method checks for:
+- Empty paths
+- Paths ending in directory separators
+- Traversal sequences (`../` or `..\`)
+- Root-based traversal attempts
+
+```typescript
+private validatePath(path: string): void {
+ const normalized = path.replace(/\\/g, '/');
+ if (normalized.includes('../')) {
+ throw new Error(`Path traversal detected: ${path}`);
+ }
+}
+```
+
+### Execution Isolation
+
+When you call `codeRun`, the code is not just "eval-ed". It goes through a transformation pipeline:
+
+1. **Language Detection**: The SDK inspects the code to determine if it's Python (checking for `def`, `import`, `print`) or Node.js.
+2. **Base64 Wrapping**: The code is base64 encoded to avoid shell injection vulnerabilities.
+3. **Shell Execution**: The command is wrapped in a secure shell invoker:
+ ```bash
+ # Python Example
+ python3 -u -c "exec(__import__('base64').b64decode('').decode())"
+ ```
+4. **Process Isolation**: The command runs as a non-root user inside the container, restricted by Kubernetes `SecurityContext`.
+
+## Streaming & Real-time Feedback
+
+For long-running tasks, `execSync` is insufficient. The SDK implements `execSyncStream` using **Server-Sent Events (SSE)**.
+
+Unlike standard HTTP requests that buffer the response, the SSE endpoint allows the Agent Server to flush stdout/stderr chunks immediately. The SDK exposes this as a standard Web `ReadableStream`, allowing you to define custom consumers for real-time log processing.
+
+```typescript
+const stream = await sandbox.execSyncStream({ command: 'npm install' });
+const reader = stream.getReader();
+
+while (true) {
+ const { done, value } = await reader.read();
+ // Process chunks in real-time
+}
+```
diff --git a/apps/docs/content/docs/guides/secure-code-execution.mdx b/apps/docs/content/docs/guides/secure-code-execution.mdx
new file mode 100644
index 0000000..3163bbf
--- /dev/null
+++ b/apps/docs/content/docs/guides/secure-code-execution.mdx
@@ -0,0 +1,274 @@
+---
+title: Secure Code Execution
+description: Execute AI-generated and untrusted code safely
+---
+
+# Secure Code Execution
+
+Devbox SDK provides **container-based isolation** for safe code execution. This guide covers best practices for executing AI-generated code, untrusted scripts, and automation tasks.
+
+## Why Secure Execution?
+
+When executing AI-generated code or untrusted scripts, you need:
+
+- **Isolation** - Prevent code from affecting your infrastructure
+- **Resource Limits** - Prevent resource exhaustion attacks
+- **Path Validation** - Prevent directory traversal attacks
+- **Cleanup** - Ensure resources are released after execution
+
+## Basic Code Execution
+
+### Execute Code Strings
+
+The simplest way to execute code is using `codeRun()`:
+
+```typescript
+const result = await sandbox.codeRun(`
+import requests
+response = requests.get('https://api.github.com')
+print(f"Status: {response.status_code}")
+`)
+
+if (result.exitCode === 0) {
+ console.log('Success:', result.stdout)
+} else {
+ console.error('Error:', result.stderr)
+}
+```
+
+### Language Detection
+
+`codeRun()` automatically detects the language (Python or Node.js) based on code patterns:
+
+```typescript
+// Python code (detected automatically)
+await sandbox.codeRun('print("Hello")')
+
+// Node.js code (detected automatically)
+await sandbox.codeRun('console.log("Hello")')
+
+// Explicitly specify language
+await sandbox.codeRun('print("Hello")', {
+ language: 'python'
+})
+```
+
+## Executing Commands
+
+### Synchronous Execution
+
+For commands that need to complete before continuing:
+
+```typescript
+const result = await sandbox.execSync({
+ command: 'npm',
+ args: ['install'],
+ cwd: '/workspace',
+ timeout: 60000
+})
+
+console.log(result.stdout)
+console.log(result.stderr)
+console.log(result.exitCode)
+```
+
+### Asynchronous Execution
+
+For long-running processes:
+
+```typescript
+// Start process
+const process = await sandbox.executeCommand({
+ command: 'npm',
+ args: ['run', 'build']
+})
+
+// Check status later
+const status = await sandbox.getProcessStatus(process.processId)
+console.log(`Status: ${status.processStatus}`)
+
+// Get logs
+const logs = await sandbox.getProcessLogs(process.processId)
+console.log(logs.logs)
+
+// Kill if needed
+await sandbox.killProcess(process.processId)
+```
+
+### Stream Output
+
+For real-time output:
+
+```typescript
+const stream = await sandbox.execSyncStream({
+ command: 'npm',
+ args: ['run', 'dev']
+})
+
+const reader = stream.getReader()
+const decoder = new TextDecoder()
+
+while (true) {
+ const { done, value } = await reader.read()
+ if (done) break
+
+ const text = decoder.decode(value, { stream: true })
+ console.log(text)
+}
+```
+
+## Security Best Practices
+
+### 1. Always Set Resource Limits
+
+```typescript
+const sandbox = await sdk.createDevbox({
+ name: 'secure-task',
+ runtime: 'python',
+ resource: {
+ cpu: 1, // Limit CPU
+ memory: 512 // Limit memory (MB)
+ }
+})
+```
+
+### 2. Use Timeouts
+
+```typescript
+const result = await sandbox.execSync({
+ command: 'python',
+ args: ['script.py'],
+ timeout: 30 // 30 seconds timeout
+})
+```
+
+### 3. Validate Input
+
+```typescript
+function validateCode(code: string): boolean {
+ // Check for dangerous patterns
+ const dangerous = [
+ 'rm -rf',
+ 'format',
+ 'delete',
+ 'shutdown'
+ ]
+
+ return !dangerous.some(pattern =>
+ code.toLowerCase().includes(pattern)
+ )
+}
+
+if (validateCode(userCode)) {
+ await sandbox.codeRun(userCode)
+} else {
+ throw new Error('Code contains dangerous patterns')
+}
+```
+
+### 4. Always Clean Up
+
+```typescript
+try {
+ const sandbox = await sdk.createDevbox({...})
+
+ // Execute code
+ await sandbox.codeRun(code)
+
+} finally {
+ // Always clean up
+ await sandbox.delete()
+ await sdk.close()
+}
+```
+
+## Error Handling
+
+Always handle errors properly:
+
+```typescript
+try {
+ const result = await sandbox.codeRun(code)
+
+ if (result.exitCode !== 0) {
+ console.error('Execution failed:', result.stderr)
+ // Handle error
+ }
+
+} catch (error) {
+ if (error instanceof FileOperationError) {
+ console.error('File operation failed:', error.message)
+ } else if (error instanceof ValidationError) {
+ console.error('Validation error:', error.message)
+ } else {
+ console.error('Unexpected error:', error)
+ }
+} finally {
+ await sandbox.delete()
+}
+```
+
+## AI Agent Workflow
+
+Complete workflow for executing AI-generated code:
+
+```typescript
+async function executeAICode(aiGeneratedCode: string) {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ try {
+ // Create isolated sandbox
+ const sandbox = await sdk.createDevbox({
+ name: `ai-task-${Date.now()}`,
+ runtime: 'python',
+ resource: { cpu: 1, memory: 512 }
+ })
+
+ // Execute AI-generated code
+ const result = await sandbox.codeRun(aiGeneratedCode, {
+ timeout: 30
+ })
+
+ // Check result
+ if (result.exitCode === 0) {
+ return {
+ success: true,
+ output: result.stdout
+ }
+ } else {
+ return {
+ success: false,
+ error: result.stderr
+ }
+ }
+
+ } finally {
+ await sandbox.delete()
+ await sdk.close()
+ }
+}
+```
+
+## Monitoring Execution
+
+Monitor resource usage during execution:
+
+```typescript
+// Get monitor data
+const monitorData = await sdk.getMonitorData(sandbox.name, {
+ start: Date.now() - 60000, // Last minute
+ end: Date.now()
+})
+
+monitorData.forEach(data => {
+ console.log(`CPU: ${data.cpu}%, Memory: ${data.memory}MB`)
+})
+```
+
+## Next Steps
+
+- Learn about [File Operations](/docs/guides/file-operations)
+- Explore [Process Management](/docs/guides/process-management)
+
diff --git a/apps/docs/content/docs/index.mdx b/apps/docs/content/docs/index.mdx
new file mode 100644
index 0000000..a243551
--- /dev/null
+++ b/apps/docs/content/docs/index.mdx
@@ -0,0 +1,136 @@
+---
+title: Devbox SDK
+description: Secure Sandbox SDK for Isolated Code Execution
+---
+
+# Devbox SDK
+
+**Secure Sandbox SDK for Isolated Code Execution.** Execute AI-generated code, run automation tasks, and test untrusted code with zero risk to your infrastructure.
+
+## Why Devbox SDK?
+
+Devbox SDK provides **container-based isolation** for safe code execution. Each sandbox runs in an isolated Kubernetes Pod, ensuring:
+
+- **Zero cross-contamination** - Each execution is completely isolated
+- **Resource limits** - CPU and memory constraints prevent resource exhaustion
+- **Path validation** - Prevents directory traversal attacks
+- **Enterprise security** - Built on Kubernetes with Kubeconfig authentication
+
+## Quick Start
+
+### Installation
+
+```bash
+npm install devbox-sdk
+```
+
+### Your First Sandbox
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+// Initialize SDK
+const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+})
+
+// Create a secure sandbox
+const sandbox = await sdk.createDevbox({
+ name: 'my-first-sandbox',
+ runtime: 'python',
+ resource: { cpu: 1, memory: 512 }
+})
+
+// Execute code safely in isolation
+const result = await sandbox.codeRun('print("Hello from secure sandbox!")')
+console.log(result.stdout) // "Hello from secure sandbox!"
+
+// Clean up
+await sandbox.delete()
+await sdk.close()
+```
+
+## Core Features
+
+### ๐ก๏ธ Secure Code Execution
+
+Execute AI-generated or untrusted code safely in isolated environments:
+
+```typescript
+// Execute AI-generated code
+const aiCode = await llm.generateCode(prompt)
+const result = await sandbox.codeRun(aiCode)
+```
+
+### โก Process Management
+
+Execute commands synchronously or asynchronously with real-time output:
+
+```typescript
+// Synchronous execution
+const result = await sandbox.execSync({
+ command: 'npm install',
+ cwd: '/workspace',
+ timeout: 60000
+})
+
+// Asynchronous execution
+const process = await sandbox.executeCommand({
+ command: 'npm run dev'
+})
+```
+
+### ๐ File Operations
+
+Full CRUD operations with support for text and binary content:
+
+```typescript
+// Write files
+await sandbox.writeFile('app.js', 'console.log("Hello")')
+
+// Read files
+const content = await sandbox.readFile('app.js')
+
+// Batch upload
+await sandbox.batchUpload({
+ files: {
+ 'src/index.js': 'console.log("Hello")',
+ 'package.json': JSON.stringify({ name: 'my-app' })
+ }
+})
+```
+
+### ๐ Git Integration
+
+Clone, pull, push, and manage Git repositories securely:
+
+```typescript
+// Clone repository
+await sandbox.git.clone({
+ url: 'https://github.com/user/repo.git',
+ targetDir: '/workspace/repo'
+})
+
+// Pull changes
+await sandbox.git.pull('/workspace/repo')
+```
+
+## Use Cases
+
+- **AI Agents** - Execute AI-generated code safely
+- **Automation** - Run untrusted automation scripts
+- **CI/CD** - Execute build and test tasks in isolation
+- **Code Evaluation** - Test and evaluate code submissions
+
+## Documentation
+
+- **[Getting Started](/docs/getting-started/installation)** - Installation and setup
+- **[Guides](/docs/guides/secure-code-execution)** - Usage guides and best practices
+- **[API Reference](/docs/api/devbox-sdk)** - Complete API documentation
+- **[Examples](/docs/examples/ai-agent-workflow)** - Real-world examples
+
+## Next Steps
+
+- Read the [Installation Guide](/docs/getting-started/installation)
+- Try the [Quick Start](/docs/getting-started/quick-start)
+- Explore [API Reference](/docs/api/devbox-sdk)
diff --git a/apps/docs/lib/layout.shared.tsx b/apps/docs/lib/layout.shared.tsx
new file mode 100644
index 0000000..a8d2425
--- /dev/null
+++ b/apps/docs/lib/layout.shared.tsx
@@ -0,0 +1,22 @@
+import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'
+
+export function baseOptions(): BaseLayoutProps {
+ return {
+ nav: {
+ title: 'Devbox SDK',
+ url: '/',
+ },
+ links: [
+ {
+ text: 'Docs',
+ url: '/docs',
+ active: 'nested-url',
+ },
+ {
+ text: 'GitHub',
+ url: 'https://github.com/zjy365/devbox-sdk',
+ external: true,
+ },
+ ],
+ }
+}
diff --git a/apps/docs/lib/source.ts b/apps/docs/lib/source.ts
new file mode 100644
index 0000000..e697442
--- /dev/null
+++ b/apps/docs/lib/source.ts
@@ -0,0 +1,7 @@
+import { docs } from '@/.source'
+import { loader } from 'fumadocs-core/source'
+
+export const source = loader({
+ baseUrl: '/docs',
+ source: docs.toFumadocsSource(),
+})
diff --git a/apps/docs/lib/utils.ts b/apps/docs/lib/utils.ts
new file mode 100644
index 0000000..d32b0fe
--- /dev/null
+++ b/apps/docs/lib/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from 'clsx'
+import { twMerge } from 'tailwind-merge'
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/apps/docs/mdx-components.tsx b/apps/docs/mdx-components.tsx
new file mode 100644
index 0000000..9c3eba4
--- /dev/null
+++ b/apps/docs/mdx-components.tsx
@@ -0,0 +1,9 @@
+import defaultMdxComponents from 'fumadocs-ui/mdx'
+import type { MDXComponents } from 'mdx/types'
+
+export function getMDXComponents(components?: MDXComponents): MDXComponents {
+ return {
+ ...defaultMdxComponents,
+ ...components,
+ }
+}
diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs
new file mode 100644
index 0000000..87a461c
--- /dev/null
+++ b/apps/docs/next.config.mjs
@@ -0,0 +1,13 @@
+import { createMDX } from 'fumadocs-mdx/next'
+
+/** @type {import('next').NextConfig} */
+const config = {
+ reactStrictMode: true,
+ output: 'standalone',
+}
+
+const withMDX = createMDX({
+ // configPath: "source.config.ts" // Default is source.config.ts
+})
+
+export default withMDX(config)
diff --git a/apps/docs/package.json b/apps/docs/package.json
new file mode 100644
index 0000000..5dbb336
--- /dev/null
+++ b/apps/docs/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "devbox-docs",
+ "version": "1.0.0",
+ "description": "Documentation website for Devbox SDK",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "biome check .",
+ "lint:fix": "biome check --write ."
+ },
+ "dependencies": {
+ "fumadocs-core": "^16.0.11",
+ "fumadocs-mdx": "^13.0.8",
+ "fumadocs-ui": "^16.0.11",
+ "motion": "^11.0.0",
+ "clsx": "^2.1.0",
+ "tailwind-merge": "^2.2.0",
+ "lucide-react": "0.554.0",
+ "next": "16.0.10",
+ "react": "^19.2.1",
+ "react-dom": "^19.2.1"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4.1.17",
+ "@types/mdx": "^2.0.13",
+ "@types/node": "^20.19.25",
+ "@types/react": "^19.2.4",
+ "@types/react-dom": "^19.2.3",
+ "autoprefixer": "^10.4.22",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^4.1.17",
+ "typescript": "^5.9.3"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+}
diff --git a/apps/docs/postcss.config.js b/apps/docs/postcss.config.js
new file mode 100644
index 0000000..668a5b9
--- /dev/null
+++ b/apps/docs/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ '@tailwindcss/postcss': {},
+ autoprefixer: {},
+ },
+}
diff --git a/apps/docs/public/logo.svg b/apps/docs/public/logo.svg
new file mode 100644
index 0000000..2b3fcc9
--- /dev/null
+++ b/apps/docs/public/logo.svg
@@ -0,0 +1,24 @@
+
diff --git a/apps/docs/public/og.png b/apps/docs/public/og.png
new file mode 100644
index 0000000..2e540d8
Binary files /dev/null and b/apps/docs/public/og.png differ
diff --git a/apps/docs/source.config.ts b/apps/docs/source.config.ts
new file mode 100644
index 0000000..4605ea8
--- /dev/null
+++ b/apps/docs/source.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig, defineDocs } from 'fumadocs-mdx/config'
+
+export const docs = defineDocs({
+ dir: 'content/docs',
+})
+
+export default defineConfig()
diff --git a/apps/docs/tailwind.config.js b/apps/docs/tailwind.config.js
new file mode 100644
index 0000000..fec5e8b
--- /dev/null
+++ b/apps/docs/tailwind.config.js
@@ -0,0 +1,12 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
+ './content/**/*.{md,mdx}',
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json
new file mode 100644
index 0000000..f3ce2db
--- /dev/null
+++ b/apps/docs/tsconfig.json
@@ -0,0 +1,35 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./*"],
+ "@/.source": [".source"]
+ },
+ "target": "ES2017"
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
+ ],
+ "exclude": ["node_modules"]
+}
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..6ce9912
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,59 @@
+{
+ "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
+ "vcs": {
+ "enabled": true,
+ "clientKind": "git",
+ "useIgnoreFile": true
+ },
+ "files": {
+ "ignoreUnknown": false,
+ "ignore": [
+ "dist/**",
+ "node_modules/**",
+ "coverage/**",
+ "*.min.js",
+ "*.min.css",
+ "packages/*/dist/**"
+ ]
+ },
+ "formatter": {
+ "enabled": false
+ },
+ "organizeImports": {
+ "enabled": false
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "complexity": {
+ "noExcessiveCognitiveComplexity": "off"
+ },
+ "suspicious": {
+ "noExplicitAny": "off"
+ },
+ "style": {
+ "noNonNullAssertion": "off"
+ },
+ "correctness": {
+ "noUnusedVariables": "warn"
+ }
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "jsxQuoteStyle": "double",
+ "quoteProperties": "asNeeded",
+ "trailingCommas": "es5",
+ "semicolons": "asNeeded",
+ "arrowParentheses": "asNeeded",
+ "bracketSpacing": true,
+ "bracketSameLine": false,
+ "quoteStyle": "single",
+ "attributePosition": "auto"
+ },
+ "globals": [
+ "Bun"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/eslint.config.js b/eslint.config.js
deleted file mode 100644
index 58f2ef1..0000000
--- a/eslint.config.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import pluginSecurity from 'eslint-plugin-security'
-import neostandard, { resolveIgnoresFromGitignore, plugins } from 'neostandard'
-
-export default [
- ...neostandard({
- ignores: ['__tests__/**/*.ts', ...resolveIgnoresFromGitignore()],
- ts: true, // Enable TypeScript support,
- filesTs: ['src/**/*.ts', '__tests__/**/*.ts']
- }),
- plugins.n.configs['flat/recommended-script'],
- pluginSecurity.configs.recommended,
- {
- rules: {
- 'n/no-process-exit': 'off',
- 'n/no-unsupported-features': 'off',
- 'n/no-unpublished-require': 'off',
- 'security/detect-non-literal-fs-filename': 'off',
- 'security/detect-unsafe-regex': 'error',
- 'security/detect-buffer-noassert': 'error',
- 'security/detect-child-process': 'error',
- 'security/detect-disable-mustache-escape': 'error',
- 'security/detect-eval-with-expression': 'error',
- 'security/detect-no-csrf-before-method-override': 'error',
- 'security/detect-non-literal-regexp': 'error',
- 'security/detect-object-injection': 'off',
- 'security/detect-possible-timing-attacks': 'error',
- 'security/detect-pseudoRandomBytes': 'error',
- 'space-before-function-paren': 'off',
- 'object-curly-spacing': 'off',
- 'no-control-regex': 'off',
- 'n/hashbang': 'off',
- 'n/no-unsupported-features/node-builtins': 'warn'
- },
- languageOptions: {
- ecmaVersion: 2024,
- sourceType: 'module',
- },
- },
-]
\ No newline at end of file
diff --git a/package.json b/package.json
index 0f69c76..b050922 100644
--- a/package.json
+++ b/package.json
@@ -1,62 +1,54 @@
{
- "name": "devbox-sdk",
- "version": "0.0.1",
- "description": "",
- "types": "dist/main.d.ts",
- "type": "module",
- "bin": "./dist/bin/cli.cjs",
- "exports": {
- ".": {
- "import": {
- "types": "./dist/main.d.ts",
- "default": "./dist/main.mjs"
- },
- "require": {
- "types": "./dist/main.d.cts",
- "default": "./dist/main.cjs"
- },
- "default": "./dist/main.mjs"
- },
- "./dist/*": {
- "types": "./dist/*.d.ts",
- "import": "./dist/*.mjs",
- "require": "./dist/*.cjs"
- }
- },
- "engines": {
- "node": ">=22.0.0"
- },
- "packageManager": "npm@8.4.0",
- "files": [
- "dist",
- "src",
- "bin"
- ],
+ "name": "devbox-sdk-monorepo",
+ "version": "1.1.0",
+ "description": "Enterprise TypeScript SDK for Sealos Devbox management with HTTP API + Bun runtime architecture",
+ "private": true,
"scripts": {
- "start": "node --import tsx src/bin/cli.ts",
- "build": "tsc && tsup",
- "lint": "eslint . && npm run lint:lockfile && npm run lint:markdown",
- "lint:markdown": "npx -y markdownlint-cli@0.45.0 -c .github/.markdownlint.yml -i '.git' -i '__tests__' -i '.github' -i '.changeset' -i 'CODE_OF_CONDUCT.md' -i 'CHANGELOG.md' -i 'docs/**' -i 'node_modules' -i 'dist' '**/**.md' --fix",
- "lint:fix": "eslint . --fix",
- "lint:lockfile": "lockfile-lint --path package-lock.json --validate-https --allowed-hosts npm yarn",
- "test": "c8 node --import tsx --test __tests__/**/*.test.ts",
- "test:watch": "c8 node --import tsx --test --watch __tests__/**/*.test.ts",
- "coverage:view": "open coverage/lcov-report/index.html",
+ "build": "turbo run build",
+ "build:sdk": "turbo run build --filter=devbox-sdk",
+ "build:docs": "turbo run build --filter=devbox-docs",
+ "dev:docs": "turbo run dev --filter=devbox-docs",
+ "start:docs": "turbo run start --filter=devbox-docs",
+ "test": "turbo run test",
+ "test:e2e": "turbo run test:e2e",
+ "lint": "turbo run lint",
+ "lint:fix": "turbo run lint:fix",
+ "typecheck": "turbo run typecheck",
+ "clean": "turbo run clean",
"version": "changeset version",
"release": "changeset publish"
},
+ "devDependencies": {
+ "@biomejs/biome": "^1.8.3",
+ "@changesets/changelog-github": "^0.5.0",
+ "@changesets/cli": "^2.27.7",
+ "dotenv": "17.2.3",
+ "tsup": "^8.0.0",
+ "tsx": "^4.19.4",
+ "turbo": "^2.5.8",
+ "typescript": "^5.5.3",
+ "vitest": "4.0.8"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ },
+ "packageManager": "pnpm@9.15.0",
"author": {
"name": "zjy365",
"email": "3161362058@qq.com",
"url": "https://github.com/zjy365"
},
- "publishConfig": {
- "provenance": true,
- "access": "public"
- },
"license": "Apache-2.0",
"keywords": [
- ""
+ "sealos",
+ "devbox",
+ "sdk",
+ "typescript",
+ "cloud-development",
+ "container",
+ "bun",
+ "http-api",
+ "monorepo"
],
"homepage": "https://github.com/zjy365/devbox-sdk",
"bugs": {
@@ -65,43 +57,5 @@
"repository": {
"type": "git",
"url": "https://github.com/zjy365/devbox-sdk.git"
- },
- "devDependencies": {
- "@changesets/changelog-github": "^0.5.0",
- "@changesets/cli": "^2.27.7",
- "@types/node": "^20.14.10",
- "c8": "^10.1.2",
- "eslint": "^9.6.0",
- "eslint-plugin-security": "^3.0.1",
- "husky": "^9.0.11",
- "lint-staged": "^15.2.7",
- "lockfile-lint": "^4.14.0",
- "neostandard": "^0.11.0",
- "tsup": "^8.1.0",
- "tsx": "^4.19.4",
- "typescript": "^5.5.3",
- "validate-conventional-commit": "^1.0.4"
- },
- "lint-staged": {
- "**/*.{js,json}": [
- "npm run lint:fix"
- ]
- },
- "c8": {
- "exclude": [
- "dist/**",
- "coverage/**",
- "__tests__/**",
- "**/*.test.ts",
- "**/*.test.js"
- ],
- "include": [
- "src/**"
- ],
- "reporter": [
- "text",
- "lcov",
- "html"
- ]
}
}
\ No newline at end of file
diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md
new file mode 100644
index 0000000..4f672ae
--- /dev/null
+++ b/packages/sdk/CHANGELOG.md
@@ -0,0 +1,21 @@
+# devbox-sdk
+
+## 1.1.1
+
+### Patch Changes
+
+- Update version to 1.1.1
+
+## 1.1.0
+
+### Minor Changes
+
+- [`1179f96`](https://github.com/zjy365/devbox-sdk/commit/1179f961dfc8a4dab1228a5206c35bf8edeaa862) Thanks [@zjy365](https://github.com/zjy365)! - First stable release of Devbox SDK
+
+ This release marks the first stable version of the Devbox SDK, providing:
+
+ - Complete TypeScript SDK for Sealos Devbox management
+ - HTTP API client with full feature support
+ - File operations, process management, and Git integration
+ - Comprehensive error handling and monitoring
+ - Shared types and utilities package
diff --git a/packages/sdk/README.md b/packages/sdk/README.md
new file mode 100644
index 0000000..1f4be59
--- /dev/null
+++ b/packages/sdk/README.md
@@ -0,0 +1,478 @@
+# devbox-sdk
+
+**Secure Sandbox SDK for Isolated Code Execution.** Execute AI-generated code, run automation tasks, and test untrusted code with zero risk to your infrastructure.
+
+## Installation
+
+```bash
+npm install devbox-sdk
+```
+
+## Requirements
+
+- Node.js >= 22.0.0
+- Kubernetes configuration (`KUBECONFIG` environment variable or file path)
+
+## Quick Start
+
+### Secure Code Execution
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+// Initialize SDK
+const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+})
+
+// Create a secure sandbox
+const sandbox = await sdk.createDevbox({
+ name: 'ai-task',
+ runtime: 'python',
+ resource: { cpu: 1, memory: 512 }
+})
+
+// Execute code safely in isolation
+const result = await sandbox.codeRun('print("Hello from secure sandbox!")')
+console.log(result.stdout) // "Hello from secure sandbox!"
+
+// Clean up
+await sandbox.delete()
+await sdk.close()
+```
+
+## Features
+
+### ๐ก๏ธ Secure Sandbox Execution
+
+Execute code in isolated container environments with zero risk to your infrastructure:
+
+```typescript
+// Create isolated sandbox
+const sandbox = await sdk.createDevbox({
+ name: 'untrusted-code',
+ runtime: 'node.js',
+ resource: { cpu: 2, memory: 4096 }
+})
+
+// Execute AI-generated or untrusted code safely
+const result = await sandbox.codeRun(aiGeneratedCode)
+
+// Each sandbox is completely isolated
+// - No access to host filesystem
+// - Resource limits enforced
+// - Network isolation
+// - Automatic cleanup on deletion
+```
+
+**Security Features:**
+- **Container Isolation** - Each sandbox runs in an isolated Kubernetes Pod
+- **Path Validation** - Prevents directory traversal attacks
+- **Resource Limits** - CPU and memory constraints
+- **Access Control** - Kubeconfig-based authentication
+- **HTTPS/TLS** - All communications encrypted
+
+### โก Fast Code Execution
+
+Execute code synchronously or asynchronously with real-time output:
+
+```typescript
+// Synchronous execution (waits for completion)
+const result = await sandbox.execSync({
+ command: 'python script.py',
+ cwd: '/workspace',
+ timeout: 60000
+})
+console.log(result.stdout)
+console.log(result.exitCode)
+
+// Asynchronous execution (returns immediately)
+const process = await sandbox.exec({
+ command: 'npm run build',
+ cwd: '/workspace'
+})
+
+// Get process status
+const status = await sandbox.getProcessStatus(process.processId)
+
+// Get real-time logs
+const logs = await sandbox.getProcessLogs(process.processId, {
+ lines: 100
+})
+
+// Kill process if needed
+await sandbox.killProcess(process.processId)
+```
+
+**Code Execution Methods:**
+- `codeRun(code, options?)` - Execute code string directly (Node.js/Python)
+- `execSync(options)` - Synchronous command execution
+- `exec(options)` - Asynchronous command execution
+- `execSyncStream(options)` - Stream output in real-time (SSE)
+
+### ๐ File Operations
+
+Full CRUD operations with support for text and binary content:
+
+```typescript
+// Write text file
+await sandbox.writeFile('app.js', 'console.log("Hello")')
+
+// Write binary file
+await sandbox.writeFile('image.png', imageBuffer)
+
+// Read file
+const content = await sandbox.readFile('app.js')
+console.log(content.toString())
+
+// List files
+const files = await sandbox.listFiles('/workspace')
+console.log(files.files)
+
+// Batch upload
+await sandbox.batchUpload({
+ files: {
+ 'src/index.js': 'console.log("Hello")',
+ 'package.json': JSON.stringify({ name: 'my-app' })
+ }
+})
+
+// Download file
+const fileContent = await sandbox.downloadFile('app.js', {
+ format: 'buffer' // or 'base64', 'text'
+})
+
+// Move and rename
+await sandbox.moveFile({ from: '/old/path', to: '/new/path' })
+await sandbox.renameFile({ path: '/old-name', newName: 'new-name' })
+```
+
+### ๐ Git Integration
+
+Clone, pull, push, and manage Git repositories securely:
+
+```typescript
+// Clone repository
+await sandbox.git.clone({
+ url: 'https://github.com/user/repo.git',
+ path: '/workspace/repo',
+ auth: {
+ type: 'https',
+ username: 'user',
+ password: 'token'
+ }
+})
+
+// Pull changes
+await sandbox.git.pull({
+ path: '/workspace/repo',
+ auth: { /* ... */ }
+})
+
+// Push changes
+await sandbox.git.push({
+ path: '/workspace/repo',
+ auth: { /* ... */ }
+})
+
+// Get status
+const status = await sandbox.git.status('/workspace/repo')
+console.log(status.branch)
+console.log(status.changes)
+
+// List branches
+const branches = await sandbox.git.branches('/workspace/repo')
+```
+
+### ๐ Monitoring
+
+Monitor sandbox resource usage and metrics:
+
+```typescript
+// Get monitor data
+const monitorData = await sdk.getMonitorData('sandbox-name', {
+ start: Date.now() - 3600000, // 1 hour ago
+ end: Date.now()
+})
+
+monitorData.forEach(data => {
+ console.log('CPU:', data.cpu)
+ console.log('Memory:', data.memory)
+ console.log('Timestamp:', data.timestamp)
+})
+```
+
+### ๐ Lifecycle Management
+
+Create, start, pause, restart, and delete sandboxes:
+
+```typescript
+// Create sandbox
+const sandbox = await sdk.createDevbox({
+ name: 'my-sandbox',
+ runtime: 'node.js',
+ resource: { cpu: 2, memory: 4096 }
+})
+
+// Control lifecycle
+await sandbox.start()
+await sandbox.pause()
+await sandbox.restart()
+await sandbox.shutdown()
+await sandbox.delete()
+
+// List all sandboxes
+const sandboxes = await sdk.listDevboxes()
+
+// Get existing sandbox
+const existing = await sdk.getDevbox('my-sandbox')
+```
+
+## Use Cases
+
+### AI Agents & Code Generation
+
+```typescript
+// Execute AI-generated code safely
+const aiCode = await llm.generateCode(prompt)
+const result = await sandbox.codeRun(aiCode)
+
+if (result.exitCode !== 0) {
+ console.error('Execution failed:', result.stderr)
+} else {
+ console.log('Result:', result.stdout)
+}
+```
+
+### Automation & Testing
+
+```typescript
+// Run untrusted automation scripts in isolation
+await sandbox.execSync({
+ command: 'npm test',
+ cwd: '/workspace',
+ timeout: 60000
+})
+```
+
+### CI/CD Tasks
+
+```typescript
+// Execute build tasks in isolated environment
+await sandbox.git.clone({ url: repoUrl, path: '/workspace' })
+await sandbox.execSync({ command: 'npm install' })
+await sandbox.execSync({ command: 'npm run build' })
+```
+
+## Configuration
+
+### SDK Configuration
+
+```typescript
+const sdk = new DevboxSDK({
+ // Required: Kubernetes config
+ kubeconfig: process.env.KUBECONFIG, // or file path
+
+ // Optional: API base URL
+ baseUrl: 'https://api.sealos.io',
+
+ // Optional: HTTP client configuration
+ http: {
+ timeout: 30000, // Request timeout in milliseconds
+ retries: 3, // Number of retry attempts
+ rejectUnauthorized: true // SSL certificate verification
+ }
+})
+```
+
+### Sandbox Creation Options
+
+```typescript
+await sdk.createDevbox({
+ name: 'my-sandbox', // Required: Unique name
+ runtime: 'node.js', // Required: Runtime environment
+ resource: { // Required: Resource allocation
+ cpu: 2, // CPU cores
+ memory: 4096 // Memory in MB
+ },
+ ports: [ // Optional: Port mappings
+ { containerPort: 3000, servicePort: 3000 }
+ ],
+ env: [ // Optional: Environment variables
+ { name: 'NODE_ENV', value: 'production' }
+ ]
+})
+```
+
+## API Reference
+
+### DevboxSDK
+
+Main SDK class for managing sandboxes.
+
+#### Methods
+
+- `createDevbox(config: DevboxCreateConfig): Promise` - Create a new sandbox
+- `getDevbox(name: string): Promise` - Get an existing sandbox
+- `listDevboxes(): Promise` - List all sandboxes
+- `getMonitorData(devboxName: string, timeRange?: TimeRange): Promise` - Get monitoring data
+- `close(): Promise` - Close all connections and cleanup
+
+### DevboxInstance
+
+Represents a single sandbox instance with methods for code execution, file operations, and more.
+
+#### Properties
+
+- `name: string` - Sandbox name
+- `status: string` - Current status
+- `runtime: DevboxRuntime` - Runtime environment
+- `resources: ResourceInfo` - Resource allocation
+- `git: Git` - Git operations interface
+
+#### Methods
+
+**Code Execution:**
+- `codeRun(code: string, options?: CodeRunOptions): Promise`
+- `execSync(options: ProcessExecOptions): Promise`
+- `exec(options: ProcessExecOptions): Promise`
+- `execSyncStream(options: ProcessExecOptions): Promise`
+
+**Process Management:**
+- `getProcessStatus(processId: string): Promise`
+- `getProcessLogs(processId: string, options?: { lines?: number }): Promise`
+- `killProcess(processId: string, options?: KillProcessOptions): Promise`
+- `listProcesses(): Promise`
+
+**File Operations:**
+- `writeFile(path: string, content: string | Buffer, options?: WriteOptions): Promise`
+- `readFile(path: string, options?: ReadOptions): Promise`
+- `listFiles(path: string): Promise`
+- `batchUpload(options: BatchUploadOptions): Promise`
+- `downloadFile(path: string, options?: DownloadFileOptions): Promise`
+- `moveFile(options: MoveFileOptions): Promise`
+- `renameFile(options: RenameFileOptions): Promise`
+
+**File Watching:**
+- `watchFiles(path: string, callback: (event: FileChangeEvent) => void): Promise`
+
+**Git Operations:**
+- `git.clone(options: GitCloneOptions): Promise`
+- `git.pull(options: GitPullOptions): Promise`
+- `git.push(options: GitPushOptions): Promise`
+- `git.status(path: string): Promise`
+- `git.branches(path: string): Promise`
+
+**Lifecycle:**
+- `start(): Promise`
+- `pause(): Promise`
+- `restart(): Promise`
+- `shutdown(): Promise`
+- `delete(): Promise`
+- `refreshInfo(): Promise`
+
+## Error Handling
+
+The SDK provides comprehensive error types:
+
+```typescript
+import {
+ DevboxSDKError,
+ AuthenticationError,
+ ConnectionError,
+ FileOperationError,
+ DevboxNotFoundError,
+ ValidationError
+} from 'devbox-sdk'
+
+try {
+ await sandbox.writeFile('/invalid/path', 'content')
+} catch (error) {
+ if (error instanceof FileOperationError) {
+ console.error('File operation failed:', error.message)
+ } else if (error instanceof ValidationError) {
+ console.error('Validation error:', error.message)
+ }
+}
+```
+
+## Security Best Practices
+
+1. **Always validate input** before executing in sandbox
+2. **Set resource limits** to prevent resource exhaustion
+3. **Use HTTPS** for all communications
+4. **Clean up sandboxes** after use to free resources
+5. **Monitor resource usage** to detect anomalies
+6. **Use path validation** for all file operations
+
+## Examples
+
+### Complete AI Agent Workflow
+
+```typescript
+import { DevboxSDK } from 'devbox-sdk'
+
+async function runAIAgent() {
+ const sdk = new DevboxSDK({
+ kubeconfig: process.env.KUBECONFIG
+ })
+
+ try {
+ // Create secure sandbox
+ const sandbox = await sdk.createDevbox({
+ name: 'ai-agent',
+ runtime: 'python',
+ resource: { cpu: 2, memory: 4096 }
+ })
+
+ // Execute AI-generated code
+ const aiCode = await llm.generateCode(userPrompt)
+ const result = await sandbox.codeRun(aiCode)
+
+ if (result.exitCode === 0) {
+ console.log('Success:', result.stdout)
+ } else {
+ console.error('Error:', result.stderr)
+ }
+
+ // Clean up
+ await sandbox.delete()
+ } finally {
+ await sdk.close()
+ }
+}
+
+runAIAgent()
+```
+
+## TypeScript Support
+
+Full TypeScript support with comprehensive type definitions:
+
+```typescript
+import type {
+ DevboxSDKConfig,
+ DevboxCreateConfig,
+ DevboxInfo,
+ FileMap,
+ ProcessExecOptions,
+ GitCloneOptions
+} from 'devbox-sdk'
+```
+
+## Performance
+
+- **Connection Pooling**: Efficient HTTP connection reuse (>98% reuse rate)
+- **Adaptive Transfer**: Smart file transfer strategies based on file size
+- **Fast Creation**: Quick sandbox initialization
+- **Type Safety**: Full TypeScript support prevents runtime errors
+
+## License
+
+Apache-2.0
+
+## Links
+
+- [GitHub Repository](https://github.com/zjy365/devbox-sdk)
+- [Documentation](https://github.com/zjy365/devbox-sdk/tree/main/apps/docs)
+- [Issue Tracker](https://github.com/zjy365/devbox-sdk/issues)
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
new file mode 100644
index 0000000..9b16c0c
--- /dev/null
+++ b/packages/sdk/package.json
@@ -0,0 +1,76 @@
+{
+ "name": "devbox-sdk",
+ "version": "1.1.2",
+ "description": "Enterprise TypeScript SDK for Sealos Devbox management",
+ "main": "./dist/index.cjs",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.ts",
+ "type": "module",
+ "exports": {
+ ".": {
+ "import": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.mjs"
+ },
+ "require": {
+ "types": "./dist/index.d.cts",
+ "default": "./dist/index.cjs"
+ },
+ "default": "./dist/index.mjs"
+ }
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ },
+ "scripts": {
+ "build": "tsup",
+ "dev": "tsup --watch",
+ "test": "vitest run",
+ "test:watch": "vitest watch",
+ "lint": "biome check src/",
+ "lint:fix": "biome check --write src/",
+ "typecheck": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "files": [
+ "dist",
+ "README.md"
+ ],
+ "keywords": [
+ "sealos",
+ "devbox",
+ "sdk",
+ "typescript",
+ "cloud-development",
+ "container",
+ "http-api"
+ ],
+ "author": {
+ "name": "zjy365",
+ "email": "3161362058@qq.com",
+ "url": "https://github.com/zjy365"
+ },
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/zjy365/devbox-sdk#readme",
+ "bugs": {
+ "url": "https://github.com/zjy365/devbox-sdk/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/zjy365/devbox-sdk.git",
+ "directory": "packages/sdk"
+ },
+ "publishConfig": {
+ "access": "public",
+ "registry": "https://registry.npmjs.org/"
+ },
+ "dependencies": {
+ "js-yaml": "^4.1.0"
+ },
+ "devDependencies": {
+ "@types/js-yaml": "^4.0.9",
+ "@types/node": "^20.14.10",
+ "devbox-shared": "workspace:*",
+ "tsup": "^8.0.0"
+ }
+}
\ No newline at end of file
diff --git a/packages/sdk/src/api/auth.ts b/packages/sdk/src/api/auth.ts
new file mode 100644
index 0000000..ee7af95
--- /dev/null
+++ b/packages/sdk/src/api/auth.ts
@@ -0,0 +1,22 @@
+import { DevboxSDKError, ERROR_CODES } from '../utils/error'
+
+export class KubeconfigAuthenticator {
+ private token: string
+
+ constructor(kubeconfig: string) {
+ if (!kubeconfig || typeof kubeconfig !== 'string') {
+ throw new DevboxSDKError(
+ 'kubeconfig is required and must be a string',
+ ERROR_CODES.INVALID_KUBECONFIG
+ )
+ }
+ // URL encoding is required because the devbox API expects it;
+ this.token = encodeURIComponent(kubeconfig)
+ }
+
+ getAuthHeaders(): Record {
+ return {
+ Authorization: this.token,
+ }
+ }
+}
diff --git a/packages/sdk/src/api/client.ts b/packages/sdk/src/api/client.ts
new file mode 100644
index 0000000..5d3b0bd
--- /dev/null
+++ b/packages/sdk/src/api/client.ts
@@ -0,0 +1,683 @@
+/**
+ * Devbox REST API client with kubeconfig authentication
+ */
+
+import type { DevboxCreateConfig, DevboxInfo, MonitorData, TimeRange } from '../core/types'
+import { DevboxSDKError, ERROR_CODES } from '../utils/error'
+import { logger } from '../utils/logger'
+import { parseKubeconfigServerUrl } from '../utils/kubeconfig'
+import { KubeconfigAuthenticator } from './auth'
+import { APIEndpoints } from './endpoints'
+import type {
+ APIClientConfig,
+ APIResponse,
+ ConfigureAutostartRequest,
+ CreateReleaseRequest,
+ DevboxCreateRequest,
+ DevboxCreateResponse,
+ DevboxDetail,
+ DevboxGetResponse,
+ DevboxListApiResponse,
+ DevboxListItem,
+ DevboxListResponse,
+ DevboxSSHInfoResponse,
+ MonitorDataPoint,
+ MonitorRequest,
+ PortConfig,
+ Release,
+ ReleaseListApiResponse,
+ RequestOptions,
+ TemplatesApiResponse,
+ UpdateDevboxRequest,
+} from './types'
+import { DevboxRuntime } from './types'
+
+/**
+ * HTTP client for Sealos API server communication
+ * Used for Devbox lifecycle management (create, start, stop, etc.)
+ */
+class SealosAPIClient {
+ private baseUrl: string
+ private timeout: number
+ private retries: number
+ private rejectUnauthorized: boolean
+ private getAuthHeaders?: () => Record
+
+ constructor(config: {
+ baseUrl?: string
+ timeout?: number
+ retries?: number
+ rejectUnauthorized?: boolean
+ getAuthHeaders?: () => Record
+ }) {
+ this.baseUrl = config.baseUrl || 'https://devbox.usw.sealos.io'
+ this.timeout = config.timeout || 30000
+ this.retries = config.retries || 3
+ this.rejectUnauthorized =
+ config.rejectUnauthorized ?? process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0'
+ this.getAuthHeaders = config.getAuthHeaders
+ if (!this.rejectUnauthorized) {
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+ }
+ }
+
+ async request(
+ method: string,
+ path: string,
+ options: RequestOptions = {}
+ ): Promise> {
+ const url = new URL(path, this.baseUrl)
+
+ // Add query parameters
+ if (options.params) {
+ for (const [key, value] of Object.entries(options.params)) {
+ if (value !== undefined && value !== null) {
+ url.searchParams.append(key, String(value))
+ }
+ }
+ }
+
+ const fetchOptions: RequestInit = {
+ method,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(this.getAuthHeaders ? this.getAuthHeaders() : {}),
+ ...options.headers,
+ },
+ }
+
+ if (options.data) {
+ fetchOptions.body = JSON.stringify(options.data)
+ }
+
+ let lastError: Error = new Error('Unknown error')
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
+ try {
+ const controller = new AbortController()
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout)
+
+ // console.log('fetchOptions',fetchOptions)
+
+ const response = await fetch(url.toString(), {
+ ...fetchOptions,
+ signal: controller.signal,
+ })
+
+ // console.log('response.url', response.ok, url.toString(), fetchOptions,)
+
+ clearTimeout(timeoutId)
+
+ if (!response.ok) {
+ let errorData: { error?: string; code?: string; timestamp?: number } = {}
+ try {
+ const contentType = response.headers.get('content-type') || ''
+ if (contentType.includes('application/json')) {
+ errorData = (await response.json()) as {
+ error?: string
+ code?: string
+ timestamp?: number
+ }
+ } else {
+ const errorText = await response.text().catch(() => 'Unable to read error response')
+ try {
+ errorData = JSON.parse(errorText) as {
+ error?: string
+ code?: string
+ timestamp?: number
+ }
+ } catch {
+ errorData = { error: errorText }
+ }
+ }
+ } catch (e) {
+ // Ignore parsing error, use default error message
+ }
+
+ const errorMessage = errorData.error || response.statusText
+ const errorCode = errorData.code || this.getErrorCodeFromStatus(response.status)
+
+ throw new DevboxSDKError(errorMessage, errorCode, {
+ status: response.status,
+ statusText: response.statusText,
+ timestamp: errorData.timestamp,
+ serverErrorCode: errorData.code,
+ })
+ }
+
+ const contentType = response.headers.get('content-type')
+ const data = contentType?.includes('application/json')
+ ? await response.json()
+ : await response.text()
+
+ logger.info('Response data:', url.toString(), data)
+
+ return {
+ data,
+ status: response.status,
+ statusText: response.statusText,
+ headers: Object.fromEntries(response.headers.entries()),
+ }
+ } catch (error) {
+ lastError = error as Error
+
+ // SSL certificate errors are handled via error throwing, no need to log
+
+ if (attempt === this.retries || !this.shouldRetry(error as Error)) {
+ break
+ }
+
+ // Exponential backoff
+ await new Promise(resolve => setTimeout(resolve, 2 ** attempt * 1000))
+ }
+ }
+ throw lastError
+ }
+
+ private shouldRetry(error: Error): boolean {
+ if (error instanceof DevboxSDKError) {
+ // Don't retry on client errors (4xx) except for timeout errors
+ const nonRetryable4xxCodes = [
+ ERROR_CODES.UNAUTHORIZED,
+ ERROR_CODES.INVALID_TOKEN,
+ ERROR_CODES.TOKEN_EXPIRED,
+ ERROR_CODES.INVALID_REQUEST,
+ ERROR_CODES.MISSING_REQUIRED_FIELD,
+ ERROR_CODES.INVALID_FIELD_VALUE,
+ ERROR_CODES.NOT_FOUND,
+ ERROR_CODES.FILE_NOT_FOUND,
+ ERROR_CODES.PROCESS_NOT_FOUND,
+ ERROR_CODES.SESSION_NOT_FOUND,
+ ERROR_CODES.CONFLICT,
+ ERROR_CODES.VALIDATION_ERROR,
+ ERROR_CODES.AUTHENTICATION_FAILED,
+ ]
+
+ if ((nonRetryable4xxCodes as string[]).includes(error.code)) {
+ return false
+ }
+
+ // Retry on timeout and server errors
+ return (
+ [
+ ERROR_CODES.CONNECTION_TIMEOUT,
+ ERROR_CODES.CONNECTION_FAILED,
+ ERROR_CODES.SERVER_UNAVAILABLE,
+ ERROR_CODES.SERVICE_UNAVAILABLE,
+ ERROR_CODES.OPERATION_TIMEOUT,
+ ERROR_CODES.SESSION_TIMEOUT,
+ ERROR_CODES.INTERNAL_ERROR,
+ ] as string[]
+ ).includes(error.code)
+ }
+ return error.name === 'AbortError' || error.message.includes('fetch')
+ }
+
+ private getErrorCodeFromStatus(status: number): string {
+ switch (status) {
+ case 401:
+ return ERROR_CODES.AUTHENTICATION_FAILED
+ case 403:
+ return ERROR_CODES.AUTHENTICATION_FAILED
+ case 404:
+ return ERROR_CODES.DEVBOX_NOT_FOUND
+ case 408:
+ return ERROR_CODES.CONNECTION_TIMEOUT
+ case 429:
+ return 'TOO_MANY_REQUESTS'
+ case 500:
+ return ERROR_CODES.INTERNAL_ERROR
+ case 502:
+ return ERROR_CODES.SERVER_UNAVAILABLE
+ case 503:
+ return ERROR_CODES.SERVICE_UNAVAILABLE
+ case 504:
+ return ERROR_CODES.CONNECTION_TIMEOUT
+ default:
+ return ERROR_CODES.INTERNAL_ERROR
+ }
+ }
+
+ get(url: string, options?: RequestOptions): Promise> {
+ return this.request('GET', url, options)
+ }
+
+ post(url: string, options?: RequestOptions): Promise> {
+ return this.request('POST', url, options)
+ }
+
+ put(url: string, options?: RequestOptions): Promise> {
+ return this.request('PUT', url, options)
+ }
+
+ delete(url: string, options?: RequestOptions): Promise> {
+ return this.request('DELETE', url, options)
+ }
+}
+
+export class DevboxAPI {
+ private httpClient: SealosAPIClient
+ private authenticator: KubeconfigAuthenticator
+ private endpoints: APIEndpoints
+
+ constructor(config: APIClientConfig) {
+ this.authenticator = new KubeconfigAuthenticator(config.kubeconfig)
+ // Priority: config.baseUrl > kubeconfig server URL > default
+ const kubeconfigUrl = parseKubeconfigServerUrl(config.kubeconfig)
+ const baseUrl = config.baseUrl || kubeconfigUrl || 'https://devbox.usw.sealos.io'
+ this.httpClient = new SealosAPIClient({
+ baseUrl,
+ timeout: config.timeout,
+ retries: config.retries,
+ rejectUnauthorized: config.rejectUnauthorized,
+ getAuthHeaders: () => this.authenticator.getAuthHeaders(),
+ })
+ this.endpoints = new APIEndpoints(baseUrl)
+ }
+
+ /**
+ * Create a new Devbox instance
+ */
+ async createDevbox(config: DevboxCreateConfig): Promise {
+ const request: DevboxCreateRequest = {
+ name: config.name,
+ runtime: config.runtime,
+ resource: config.resource,
+ ports: config.ports?.map(p => ({ number: p.number, protocol: p.protocol })),
+ env: config.env,
+ }
+
+ try {
+ const response = await this.httpClient.post(this.endpoints.devboxCreate(), {
+ data: request,
+ })
+ const responseData = response.data as { data: DevboxCreateResponse }
+ return this.transformCreateResponseToDevboxInfo(
+ responseData.data,
+ config.runtime,
+ config.resource
+ )
+ } catch (error) {
+ throw this.handleAPIError(error, 'Failed to create Devbox')
+ }
+ }
+
+ /**
+ * Get an existing Devbox instance
+ */
+ async getDevbox(name: string): Promise {
+ try {
+ const response = await this.httpClient.get(this.endpoints.devboxGet(name))
+
+ const responseData = response.data as { data: DevboxDetail }
+ return this.transformDetailToDevboxInfo(responseData.data)
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to get Devbox '${name}'`)
+ }
+ }
+
+ /**
+ * List all Devbox instances
+ */
+ async listDevboxes(): Promise {
+ try {
+ const response = await this.httpClient.get(this.endpoints.devboxList())
+ const listResponse = response.data as DevboxListApiResponse
+ return listResponse.data.map(this.transformListItemToDevboxInfo)
+ } catch (error) {
+ throw this.handleAPIError(error, 'Failed to list Devboxes')
+ }
+ }
+
+ /**
+ * Start a Devbox instance
+ */
+ async startDevbox(name: string): Promise {
+ try {
+ await this.httpClient.post(this.endpoints.devboxStart(name), {
+ data: {},
+ })
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to start Devbox '${name}'`)
+ }
+ }
+
+ /**
+ * Pause a Devbox instance
+ */
+ async pauseDevbox(name: string): Promise {
+ try {
+ await this.httpClient.post(this.endpoints.devboxPause(name), {
+ data: {},
+ })
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to pause Devbox '${name}'`)
+ }
+ }
+
+ /**
+ * Restart a Devbox instance
+ */
+ async restartDevbox(name: string): Promise {
+ try {
+ await this.httpClient.post(this.endpoints.devboxRestart(name), {
+ data: {},
+ })
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to restart Devbox '${name}'`)
+ }
+ }
+
+ /**
+ * Delete a Devbox instance
+ */
+ async deleteDevbox(name: string): Promise {
+ try {
+ await this.httpClient.delete(this.endpoints.devboxDelete(name))
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to delete Devbox '${name}'`)
+ }
+ }
+
+ /**
+ * Update a Devbox instance configuration
+ */
+ async updateDevbox(name: string, config: UpdateDevboxRequest): Promise {
+ try {
+ await this.httpClient.request('PATCH', this.endpoints.devboxUpdate(name), {
+ data: config,
+ })
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to update Devbox '${name}'`)
+ }
+ }
+
+ /**
+ * Shutdown a Devbox instance
+ */
+ async shutdownDevbox(name: string): Promise {
+ try {
+ await this.httpClient.post(this.endpoints.devboxShutdown(name), {
+ data: {},
+ })
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to shutdown Devbox '${name}'`)
+ }
+ }
+
+ /**
+ * Get available runtime templates
+ */
+ async getTemplates(): Promise {
+ try {
+ const response = await this.httpClient.get(
+ this.endpoints.devboxTemplates()
+ )
+ return response.data.data
+ } catch (error) {
+ throw this.handleAPIError(error, 'Failed to get templates')
+ }
+ }
+
+ /**
+ * Update port configuration for a Devbox
+ */
+ async updatePorts(name: string, ports: PortConfig[]): Promise {
+ try {
+ await this.httpClient.put(this.endpoints.devboxPorts(name), {
+ data: { ports },
+ })
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to update ports for '${name}'`)
+ }
+ }
+
+ /**
+ * Configure autostart for a Devbox
+ */
+ async configureAutostart(name: string, config?: ConfigureAutostartRequest): Promise {
+ try {
+ await this.httpClient.post(this.endpoints.devboxAutostart(name), {
+ data: config || {},
+ })
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to configure autostart for '${name}'`)
+ }
+ }
+
+ /**
+ * List releases for a Devbox
+ */
+ async listReleases(name: string): Promise {
+ try {
+ const response = await this.httpClient.get(this.endpoints.releaseList(name))
+ const responseData = response.data as ReleaseListApiResponse
+ return responseData.data || []
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to list releases for '${name}'`)
+ }
+ }
+
+ /**
+ * Create a release for a Devbox
+ */
+ async createRelease(name: string, config: CreateReleaseRequest): Promise {
+ try {
+ await this.httpClient.post(this.endpoints.releaseCreate(name), {
+ data: config,
+ })
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to create release for '${name}'`)
+ }
+ }
+
+ /**
+ * Delete a release
+ */
+ async deleteRelease(name: string, tag: string): Promise {
+ try {
+ await this.httpClient.delete(this.endpoints.releaseDelete(name, tag))
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to delete release '${tag}' for '${name}'`)
+ }
+ }
+
+ /**
+ * Deploy a release
+ */
+ async deployRelease(name: string, tag: string): Promise {
+ try {
+ await this.httpClient.post(this.endpoints.releaseDeploy(name, tag), {
+ data: {},
+ })
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to deploy release '${tag}' for '${name}'`)
+ }
+ }
+
+ /**
+ * Get monitoring data for a Devbox instance
+ */
+ async getMonitorData(name: string, timeRange?: TimeRange): Promise {
+ try {
+ const params: MonitorRequest = {
+ start: timeRange?.start || Date.now() - 3600000, // Default 1 hour ago
+ end: timeRange?.end || Date.now(),
+ step: timeRange?.step,
+ }
+
+ const response = await this.httpClient.get(this.endpoints.devboxMonitor(name), {
+ params: params as unknown as Record,
+ })
+
+ const dataPoints = response.data as MonitorDataPoint[]
+ return dataPoints.map(this.transformMonitorData)
+ } catch (error) {
+ throw this.handleAPIError(error, `Failed to get monitor data for '${name}'`)
+ }
+ }
+
+ /**
+ * Test authentication
+ */
+ async testAuth(): Promise {
+ try {
+ await this.httpClient.get(this.endpoints.devboxList())
+ return true
+ } catch (error) {
+ return false
+ }
+ }
+
+ private transformSSHInfoToDevboxInfo(sshInfo: DevboxSSHInfoResponse): DevboxInfo {
+ return {
+ name: sshInfo.name,
+ status: sshInfo.status,
+ runtime: sshInfo.runtime,
+ resources: sshInfo.resources,
+ podIP: sshInfo.podIP,
+ ssh: sshInfo.ssh
+ ? {
+ host: sshInfo.ssh.host,
+ port: sshInfo.ssh.port,
+ user: sshInfo.ssh.user,
+ privateKey: sshInfo.ssh.privateKey,
+ }
+ : undefined,
+ }
+ }
+
+ private transformListItemToDevboxInfo(listItem: DevboxListItem): DevboxInfo {
+ return {
+ name: listItem.name,
+ status: listItem.status,
+ runtime: listItem.runtime,
+ resources: listItem.resources,
+ }
+ }
+
+ /**
+ * Safely convert a string to DevboxRuntime enum
+ * Returns the enum value if valid, otherwise returns a default value
+ */
+ private stringToRuntime(value: string | null | undefined): DevboxRuntime {
+ if (!value) {
+ return DevboxRuntime.TEST_AGENT // Default fallback
+ }
+ // Check if the value matches any enum value
+ const runtimeValues = Object.values(DevboxRuntime) as string[]
+ if (runtimeValues.includes(value)) {
+ return value as DevboxRuntime
+ }
+ // If not found, return default
+ return DevboxRuntime.TEST_AGENT
+ }
+
+ private transformCreateResponseToDevboxInfo(
+ createResponse: DevboxCreateResponse,
+ runtime: DevboxRuntime,
+ resource: { cpu: number; memory: number }
+ ): DevboxInfo {
+ return {
+ name: createResponse.name,
+ status: 'Pending', // New devboxes start in Pending state
+ runtime: runtime, // Use the runtime from the create request
+ resources: {
+ cpu: resource.cpu, // Use the resource from the create request
+ memory: resource.memory, // Use the resource from the create request
+ },
+ ssh: {
+ host: createResponse.domain,
+ port: createResponse.sshPort,
+ user: createResponse.userName,
+ privateKey: createResponse.base64PrivateKey,
+ },
+ }
+ }
+
+ /**
+ * Transform DevboxDetail (actual API response) to DevboxInfo
+ */
+ private transformDetailToDevboxInfo(detail: DevboxDetail): DevboxInfo {
+ // Handle runtime: may be string or enum value
+ const runtime =
+ typeof detail.runtime === 'string' ? this.stringToRuntime(detail.runtime) : detail.runtime
+
+ // Handle SSH info: only set if privateKey exists
+ const ssh = detail.ssh?.privateKey
+ ? {
+ host: detail.ssh.host,
+ port: detail.ssh.port,
+ user: detail.ssh.user,
+ privateKey: detail.ssh.privateKey,
+ }
+ : undefined
+
+ // Extract podIP (from pods array if exists)
+ let podIP: string | undefined
+ if (detail.pods && detail.pods.length > 0) {
+ // Try to extract IP from pods, may need to adjust based on actual API response structure
+ // If API returns pods with IP info, can extract here
+ }
+
+ return {
+ name: detail.name,
+ status: detail.status,
+ runtime,
+ resources: detail.resources,
+ podIP,
+ ssh,
+ ports: detail.ports,
+ agentServer: detail.agentServer,
+ }
+ }
+
+ /**
+ * Transform DevboxGetResponse to DevboxInfo (legacy method, kept for backward compatibility)
+ */
+ private transformGetResponseToDevboxInfo(getResponse: DevboxGetResponse): DevboxInfo {
+ // Handle status: may be string or object
+ const status =
+ typeof getResponse.status === 'string' ? getResponse.status : getResponse.status.value
+
+ // Handle resources: prefer resources object, otherwise use direct cpu/memory fields
+ const resources = getResponse.resources || {
+ cpu: getResponse.cpu || 0,
+ memory: getResponse.memory || 0,
+ }
+
+ // Handle runtime: prefer runtime field, otherwise use iconId
+ const runtime = getResponse.runtime
+ ? this.stringToRuntime(getResponse.runtime)
+ : getResponse.iconId
+ ? this.stringToRuntime(getResponse.iconId)
+ : DevboxRuntime.TEST_AGENT
+
+ return {
+ name: getResponse.name,
+ status,
+ runtime,
+ resources,
+ }
+ }
+
+ private transformMonitorData(dataPoint: MonitorDataPoint): MonitorData {
+ return {
+ cpu: dataPoint.cpu,
+ memory: dataPoint.memory,
+ network: dataPoint.network,
+ disk: dataPoint.disk,
+ timestamp: dataPoint.timestamp,
+ }
+ }
+
+ private handleAPIError(error: unknown, context: string): DevboxSDKError {
+ if (error instanceof DevboxSDKError) {
+ return error
+ }
+
+ const message = error instanceof Error ? error.message : String(error)
+ return new DevboxSDKError(`${context}: ${message}`, ERROR_CODES.INTERNAL_ERROR, {
+ originalError: error,
+ })
+ }
+}
diff --git a/packages/sdk/src/api/endpoints.ts b/packages/sdk/src/api/endpoints.ts
new file mode 100644
index 0000000..86f1cb4
--- /dev/null
+++ b/packages/sdk/src/api/endpoints.ts
@@ -0,0 +1,159 @@
+/**
+ * API endpoint definitions for the Devbox REST API
+ */
+
+import { API_ENDPOINTS } from '../core/constants'
+
+/**
+ * Construct API URLs with proper parameter substitution
+ */
+export class APIEndpoints {
+ private baseUrl: string
+
+ constructor(baseUrl = 'https://devbox.usw.sealos.io/v1') {
+ this.baseUrl = baseUrl
+ }
+
+ /**
+ * Get the base URL
+ */
+ getBaseUrl(): string {
+ return this.baseUrl
+ }
+
+ /**
+ * Construct URL with parameters
+ */
+ private constructUrl(template: string, params: Record = {}): string {
+ let url = template
+ for (const [key, value] of Object.entries(params)) {
+ url = url.replace(`{${key}}`, encodeURIComponent(value))
+ }
+ // Ensure baseUrl doesn't end with / and url starts with /
+ const baseUrl = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl
+ return `${baseUrl}${url}`
+ }
+
+ // Devbox management endpoints
+ devboxList(): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.LIST)
+ }
+
+ devboxCreate(): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.CREATE)
+ }
+
+ devboxGet(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.GET, { name })
+ }
+
+ devboxUpdate(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.UPDATE, { name })
+ }
+
+ devboxDelete(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.DELETE, { name })
+ }
+
+ devboxStart(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.START, { name })
+ }
+
+ devboxPause(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.PAUSE, { name })
+ }
+
+ devboxRestart(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RESTART, { name })
+ }
+
+ devboxShutdown(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.SHUTDOWN, { name })
+ }
+
+ devboxMonitor(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.MONITOR, { name })
+ }
+
+ devboxTemplates(): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.TEMPLATES)
+ }
+
+ devboxPorts(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.PORTS, { name })
+ }
+
+ devboxAutostart(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.AUTOSTART, { name })
+ }
+
+ // Release endpoints
+ releaseList(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.LIST, { name })
+ }
+
+ releaseCreate(name: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.CREATE, { name })
+ }
+
+ releaseDelete(name: string, tag: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.DELETE, { name, tag })
+ }
+
+ releaseDeploy(name: string, tag: string): string {
+ return this.constructUrl(API_ENDPOINTS.DEVBOX.RELEASE.DEPLOY, { name, tag })
+ }
+
+ containerHealth(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.HEALTH}`
+ }
+
+ filesWrite(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.WRITE}`
+ }
+
+ filesRead(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.READ}`
+ }
+
+ filesList(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.LIST}`
+ }
+
+ filesDelete(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.DELETE}`
+ }
+
+ filesBatchUpload(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.BATCH_UPLOAD}`
+ }
+
+ filesBatchDownload(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.BATCH_DOWNLOAD}`
+ }
+
+ filesSearch(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.SEARCH}`
+ }
+
+ filesFind(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.FIND}`
+ }
+
+ filesReplace(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.FILES.REPLACE}`
+ }
+
+ processExec(baseUrl: string): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.PROCESS.EXEC}`
+ }
+
+ processStatus(baseUrl: string, pid: number): string {
+ return `${baseUrl}${API_ENDPOINTS.CONTAINER.PROCESS.STATUS.replace('{pid}', pid.toString())}`
+ }
+
+ // Temporarily disabled - ws module removed
+ // websocket(baseUrl: string): string {
+ // return `${baseUrl}${API_ENDPOINTS.CONTAINER.WEBSOCKET}`
+ // }
+}
diff --git a/packages/sdk/src/api/types.ts b/packages/sdk/src/api/types.ts
new file mode 100644
index 0000000..45e477d
--- /dev/null
+++ b/packages/sdk/src/api/types.ts
@@ -0,0 +1,477 @@
+/**
+ * API response and request type definitions
+ */
+
+/**
+ * Devbox runtime environment enum
+ */
+export enum DevboxRuntime {
+ NUXT3 = 'nuxt3',
+ ANGULAR = 'angular',
+ QUARKUS = 'quarkus',
+ UBUNTU = 'ubuntu',
+ FLASK = 'flask',
+ JAVA = 'java',
+ CHI = 'chi',
+ NET = 'net',
+ IRIS = 'iris',
+ HEXO = 'hexo',
+ PYTHON = 'python',
+ DOCUSAURUS = 'docusaurus',
+ VITEPRESS = 'vitepress',
+ CPP = 'cpp',
+ VUE = 'vue',
+ NGINX = 'nginx',
+ ROCKET = 'rocket',
+ DEBIAN_SSH = 'debian-ssh',
+ VERT_X = 'vert.x',
+ EXPRESS_JS = 'express.js',
+ DJANGO = 'django',
+ NEXT_JS = 'next.js',
+ SEALAF = 'sealaf',
+ GO = 'go',
+ REACT = 'react',
+ PHP = 'php',
+ SVELTE = 'svelte',
+ C = 'c',
+ ASTRO = 'astro',
+ UMI = 'umi',
+ GIN = 'gin',
+ NODE_JS = 'node.js',
+ ECHO = 'echo',
+ RUST = 'rust',
+ TEST_AGENT = 'node-expt-agent'
+}
+
+/**
+ * Port configuration interface
+ */
+export interface PortConfiguration {
+ /** Port number */
+ number: number
+ /** Port protocol (tcp/udp) */
+ protocol: 'tcp' | 'udp'
+ /** Publicly accessible address */
+ publicAddress?: string
+ /** Private container address */
+ privateAddress?: string
+ /** Port name/identifier */
+ name?: string
+ /** Whether port is currently active */
+ isActive?: boolean
+ /** Port status */
+ status?: 'open' | 'closed' | 'pending'
+}
+
+/**
+ * Network configuration interface
+ */
+export interface NetworkConfiguration {
+ /** Network name */
+ name: string
+ /** Network type */
+ type: 'bridge' | 'host' | 'overlay'
+ /** Network subnet */
+ subnet?: string
+ /** Gateway address */
+ gateway?: string
+ /** DNS servers */
+ dns?: string[]
+ /** Network status */
+ status?: 'active' | 'inactive' | 'error'
+ /** IP address assigned to container */
+ ipAddress?: string
+ /** MAC address */
+ macAddress?: string
+}
+
+export interface KubeconfigAuth {
+ kubeconfig: string
+}
+
+export interface APIClientConfig {
+ kubeconfig: string
+ baseUrl?: string
+ timeout?: number
+ retries?: number
+ /** Allow self-signed certificates (ONLY for development/testing, NOT recommended for production) */
+ rejectUnauthorized?: boolean
+}
+
+export interface DevboxCreateRequest {
+ name: string
+ runtime: DevboxRuntime
+ resource: {
+ cpu: number
+ memory: number
+ }
+ ports?: Array<{
+ number: number
+ protocol: string
+ }>
+ env?: Record
+}
+
+export interface DevboxSSHInfoResponse {
+ name: string
+ ssh: {
+ host: string
+ port: number
+ user: string
+ privateKey: string
+ }
+ podIP?: string
+ status: string
+ runtime: DevboxRuntime
+ resources: {
+ cpu: number
+ memory: number
+ }
+}
+
+export interface DevboxCreateResponse {
+ name: string
+ sshPort: number
+ base64PrivateKey: string
+ userName: string
+ workingDir: string
+ domain: string
+ ports: PortConfiguration[]
+ summary: {
+ totalPorts: number
+ successfulPorts: number
+ failedPorts: number
+ }
+}
+
+export interface DevboxGetResponse {
+ name: string
+ iconId?: string // May not exist
+ runtime?: string // Actually included in API response
+ status:
+ | string
+ | {
+ // May be string or object
+ value: string
+ label: string
+ }
+ cpu?: number // in millicores (may not exist, use resources instead)
+ memory?: number // in MB (may not exist, use resources instead)
+ resources?: {
+ // Actually used in API response
+ cpu: number
+ memory: number
+ }
+ sshPort?: number
+ networks?: NetworkConfiguration[]
+ [key: string]: unknown // other fields we don't care about
+}
+
+export interface DevboxListResponse {
+ devboxes: DevboxSSHInfoResponse[]
+}
+
+export interface MonitorRequest {
+ start: number
+ end: number
+ step?: string
+}
+
+export interface MonitorDataPoint {
+ cpu: number
+ memory: number
+ network: {
+ bytesIn: number
+ bytesOut: number
+ }
+ disk: {
+ used: number
+ total: number
+ }
+ timestamp: number
+}
+
+export interface APIResponse {
+ data: T
+ status: number
+ statusText: string
+ headers: Record
+}
+
+/**
+ * HTTP request options
+ */
+export interface RequestOptions {
+ headers?: Record
+ params?: Record
+ data?: unknown
+}
+
+/**
+ * Error detail information
+ */
+export interface ErrorDetail {
+ field?: string
+ reason?: string
+ value?: unknown
+ additionalInfo?: Record
+}
+
+export interface APIError {
+ error: string // Field name returned by server
+ code: string
+ timestamp: number
+ details?: ErrorDetail | ErrorDetail[] | Record
+ // Backward compatibility: keep message field as alias for error
+ message?: string
+}
+
+export interface HealthCheckResponse {
+ status: 'healthy' | 'unhealthy'
+ timestamp: number
+ uptime: number
+ version: string
+}
+
+// ============ Extended Types for Complete API Coverage ============
+
+/**
+ * Port configuration
+ */
+export interface PortConfig {
+ number: number // 1-65535
+ protocol?: 'HTTP' | 'GRPC' | 'WS'
+ exposesPublicDomain?: boolean
+ customDomain?: string
+ portName?: string // Used for updating existing ports
+}
+
+/**
+ * Environment variable configuration
+ */
+export interface EnvVar {
+ name: string
+ value?: string
+ valueFrom?: {
+ secretKeyRef: {
+ name: string
+ key: string
+ }
+ }
+}
+
+/**
+ * Request to create a new Devbox
+ */
+export interface CreateDevboxRequest {
+ name: string
+ runtime: DevboxRuntime
+ resource: {
+ cpu: number // 0.1, 0.2, 0.5, 1, 2, 4, 8, 16
+ memory: number // 0.1, 0.5, 1, 2, 4, 8, 16, 32
+ }
+ ports?: PortConfig[]
+ env?: EnvVar[]
+ autostart?: boolean
+}
+
+/**
+ * Request to update Devbox configuration
+ */
+export interface UpdateDevboxRequest {
+ resource?: {
+ cpu: number
+ memory: number
+ }
+ ports?: PortConfig[]
+}
+
+/**
+ * Devbox list item (simplified info)
+ */
+export interface DevboxListItem {
+ name: string
+ uid: string
+ resourceType: 'devbox'
+ runtime: DevboxRuntime
+ status: string
+ resources: {
+ cpu: number
+ memory: number
+ }
+}
+
+/**
+ * Response from list devboxes API
+ */
+export interface DevboxListApiResponse {
+ data: DevboxListItem[]
+}
+
+/**
+ * Agent server configuration
+ */
+export interface AgentServer {
+ /** Service URL or hostname for the agent server */
+ url: string
+ /** Authentication token for agent server */
+ token: string
+}
+
+/**
+ * Detailed devbox information
+ */
+export interface DevboxDetail {
+ name: string
+ uid: string
+ resourceType: 'devbox'
+ runtime: string | DevboxRuntime // API returns string, but type definition supports enum
+ image: string
+ status: string
+ resources: {
+ cpu: number
+ memory: number
+ }
+ ssh: {
+ host: string
+ port: number
+ user: string
+ workingDir: string
+ privateKey?: string
+ }
+ env?: EnvVar[]
+ ports: Array<{
+ number: number
+ portName: string
+ protocol: string
+ serviceName: string
+ privateAddress: string
+ privateHost: string
+ networkName: string
+ publicHost?: string
+ publicAddress?: string
+ customDomain?: string
+ }>
+ pods?: Array<{
+ name: string
+ status: string
+ }>
+ agentServer?: AgentServer
+}
+
+/**
+ * Response from get devbox API
+ */
+export interface DevboxDetailApiResponse {
+ data: DevboxDetail
+}
+
+/**
+ * Runtime template information
+ */
+export interface RuntimeTemplate {
+ uid: string
+ iconId: string | null
+ name: string
+ kind: 'FRAMEWORK' | 'OS' | 'LANGUAGE' | 'SERVICE' | 'CUSTOM'
+ description: string | null
+ isPublic: boolean
+}
+
+/**
+ * Template configuration
+ */
+export interface TemplateConfig {
+ templateUid: string
+ templateName: string
+ runtimeUid: string
+ runtime: DevboxRuntime | null
+ config: {
+ appPorts?: Array<{
+ name: string
+ port: number
+ protocol: string
+ }>
+ ports?: Array<{
+ containerPort: number
+ name: string
+ protocol: string
+ }>
+ releaseCommand?: string[]
+ releaseArgs?: string[]
+ user?: string
+ workingDir?: string
+ }
+}
+
+/**
+ * Response from get templates API
+ */
+export interface TemplatesApiResponse {
+ data: {
+ runtime: RuntimeTemplate[]
+ config: TemplateConfig[]
+ }
+}
+
+/**
+ * Release status
+ */
+export interface ReleaseStatus {
+ value: string
+ label: string
+}
+
+/**
+ * Release information
+ */
+export interface Release {
+ id: string
+ name: string
+ devboxName: string
+ createTime: string
+ tag: string
+ status: ReleaseStatus
+ description: string
+ image: string
+}
+
+/**
+ * Response from list releases API
+ */
+export interface ReleaseListApiResponse {
+ data: Release[]
+}
+
+/**
+ * Monitor data point with readable time
+ */
+export interface MonitorDataApiPoint {
+ timestamp: number
+ readableTime: string
+ cpu: number
+ memory: number
+}
+
+/**
+ * Response from monitor data API
+ */
+export interface MonitorDataApiResponse {
+ code: 200
+ data: MonitorDataApiPoint[]
+}
+
+/**
+ * Request to create a release
+ */
+export interface CreateReleaseRequest {
+ tag: string
+ releaseDes?: string
+}
+
+/**
+ * Request to configure autostart
+ */
+export interface ConfigureAutostartRequest {
+ execCommand?: string
+}
diff --git a/packages/sdk/src/core/constants.ts b/packages/sdk/src/core/constants.ts
new file mode 100644
index 0000000..3e63b06
--- /dev/null
+++ b/packages/sdk/src/core/constants.ts
@@ -0,0 +1,196 @@
+/**
+ * Global constants for the Devbox SDK
+ */
+
+export const DEFAULT_CONFIG = {
+ /** Default base URL for Devbox API */
+ BASE_URL: 'https://devbox.usw.sealos.io/v1',
+
+ /** Default HTTP server port for containers */
+ CONTAINER_HTTP_PORT: 3000,
+
+ /** Default mock server configuration */
+ MOCK_SERVER: {
+ DEFAULT_URL: 'http://localhost:9757',
+ ENV_VAR: 'MOCK_SERVER_URL',
+ },
+
+ /** Default HTTP client settings */
+ HTTP_CLIENT: {
+ TIMEOUT: 30000, // 30 seconds
+ RETRIES: 3,
+ },
+
+ /** File operation limits */
+ FILE_LIMITS: {
+ MAX_FILE_SIZE: 100 * 1024 * 1024, // 100MB
+ MAX_BATCH_SIZE: 50, // maximum files per batch
+ CHUNK_SIZE: 1024 * 1024, // 1MB chunks for streaming
+ },
+
+ /** Performance targets */
+ PERFORMANCE: {
+ SMALL_FILE_LATENCY_MS: 50, // <50ms for files <1MB
+ LARGE_FILE_THROUGHPUT_MBPS: 15, // >15MB/s for large files
+ CONNECTION_REUSE_RATE: 0.98, // >98% connection reuse
+ STARTUP_TIME_MS: 100, // <100ms Bun server startup
+ },
+} as const
+
+export const API_ENDPOINTS = {
+ /** Devbox management endpoints */
+ DEVBOX: {
+ LIST: '/api/v1/devbox',
+ CREATE: '/api/v1/devbox',
+ GET: '/api/v1/devbox/{name}',
+ UPDATE: '/api/v1/devbox/{name}',
+ DELETE: '/api/v1/devbox/{name}/delete',
+ START: '/api/v1/devbox/{name}/start',
+ PAUSE: '/api/v1/devbox/{name}/pause',
+ RESTART: '/api/v1/devbox/{name}/restart',
+ SHUTDOWN: '/api/v1/devbox/{name}/shutdown',
+ MONITOR: '/api/v1/devbox/{name}/monitor',
+ TEMPLATES: '/api/v1/devbox/templates',
+ PORTS: '/api/v1/devbox/{name}/ports',
+ AUTOSTART: '/api/v1/devbox/{name}/autostart',
+ RELEASE: {
+ LIST: '/api/v1/devbox/{name}/release',
+ CREATE: '/api/v1/devbox/{name}/release',
+ DELETE: '/api/v1/devbox/{name}/release/{tag}',
+ DEPLOY: '/api/v1/devbox/{name}/release/{tag}/deploy',
+ },
+ },
+
+ /** Container server endpoints */
+ CONTAINER: {
+ HEALTH: '/health',
+ FILES: {
+ WRITE: '/api/v1/files/write',
+ READ: '/api/v1/files/read',
+ LIST: '/api/v1/files/list',
+ DELETE: '/api/v1/files/delete',
+ MOVE: '/api/v1/files/move',
+ RENAME: '/api/v1/files/rename',
+ DOWNLOAD: '/api/v1/files/download',
+ BATCH_UPLOAD: '/api/v1/files/batch-upload',
+ BATCH_DOWNLOAD: '/api/v1/files/batch-download',
+ SEARCH: '/api/v1/files/search',
+ FIND: '/api/v1/files/find',
+ REPLACE: '/api/v1/files/replace',
+ },
+ PROCESS: {
+ EXEC: '/api/v1/process/exec',
+ EXEC_SYNC: '/api/v1/process/exec-sync',
+ EXEC_SYNC_STREAM: '/api/v1/process/sync-stream',
+ LIST: '/api/v1/process/list',
+ STATUS: '/api/v1/process/{process_id}/status',
+ KILL: '/api/v1/process/{process_id}/kill',
+ LOGS: '/api/v1/process/{process_id}/logs',
+ },
+ PORTS: '/api/v1/ports',
+ // Temporarily disabled - ws module removed
+ // WEBSOCKET: '/ws',
+ },
+} as const
+
+export const ERROR_CODES = {
+ /** Authentication errors */
+ AUTHENTICATION_FAILED: 'AUTHENTICATION_FAILED',
+ INVALID_KUBECONFIG: 'INVALID_KUBECONFIG',
+ UNAUTHORIZED: 'UNAUTHORIZED',
+ INVALID_TOKEN: 'INVALID_TOKEN',
+ TOKEN_EXPIRED: 'TOKEN_EXPIRED',
+ INSUFFICIENT_PERMISSIONS: 'INSUFFICIENT_PERMISSIONS',
+
+ /** Connection errors */
+ CONNECTION_FAILED: 'CONNECTION_FAILED',
+ CONNECTION_TIMEOUT: 'CONNECTION_TIMEOUT',
+
+ /** Devbox errors */
+ DEVBOX_NOT_FOUND: 'DEVBOX_NOT_FOUND',
+ DEVBOX_NOT_READY: 'DEVBOX_NOT_READY',
+ DEVBOX_CREATION_FAILED: 'DEVBOX_CREATION_FAILED',
+ DEVBOX_OPERATION_FAILED: 'DEVBOX_OPERATION_FAILED',
+
+ /** Validation errors */
+ INVALID_REQUEST: 'INVALID_REQUEST',
+ MISSING_REQUIRED_FIELD: 'MISSING_REQUIRED_FIELD',
+ INVALID_FIELD_VALUE: 'INVALID_FIELD_VALUE',
+ INVALID_JSON_FORMAT: 'INVALID_JSON_FORMAT',
+ INVALID_PATH: 'INVALID_PATH',
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
+
+ /** Resource errors */
+ NOT_FOUND: 'NOT_FOUND',
+ PROCESS_NOT_FOUND: 'PROCESS_NOT_FOUND',
+ SESSION_NOT_FOUND: 'SESSION_NOT_FOUND',
+ FILE_NOT_FOUND: 'FILE_NOT_FOUND',
+ DIRECTORY_NOT_FOUND: 'DIRECTORY_NOT_FOUND',
+
+ /** State errors */
+ CONFLICT: 'CONFLICT',
+ PROCESS_ALREADY_RUNNING: 'PROCESS_ALREADY_RUNNING',
+ PROCESS_NOT_RUNNING: 'PROCESS_NOT_RUNNING',
+ SESSION_INACTIVE: 'SESSION_INACTIVE',
+ RESOURCE_LOCKED: 'RESOURCE_LOCKED',
+ PROCESS_ALREADY_TERMINATED: 'PROCESS_ALREADY_TERMINATED',
+
+ /** Operation errors */
+ OPERATION_TIMEOUT: 'OPERATION_TIMEOUT',
+ OPERATION_FAILED: 'OPERATION_FAILED',
+ EXECUTION_FAILED: 'EXECUTION_FAILED',
+ SIGNAL_FAILED: 'SIGNAL_FAILED',
+
+ /** File operation errors */
+ FILE_OPERATION_ERROR: 'FILE_OPERATION_ERROR',
+ FILE_TOO_LARGE: 'FILE_TOO_LARGE',
+ FILE_TRANSFER_FAILED: 'FILE_TRANSFER_FAILED',
+ PATH_TRAVERSAL_DETECTED: 'PATH_TRAVERSAL_DETECTED',
+ DIRECTORY_NOT_EMPTY: 'DIRECTORY_NOT_EMPTY',
+ DISK_FULL: 'DISK_FULL',
+ FILE_LOCKED: 'FILE_LOCKED',
+
+ /** Process errors */
+ PROCESS_START_FAILED: 'PROCESS_START_FAILED',
+ INVALID_SIGNAL: 'INVALID_SIGNAL',
+ PROCESS_LIMIT_EXCEEDED: 'PROCESS_LIMIT_EXCEEDED',
+
+ /** Session errors */
+ SESSION_CREATION_FAILED: 'SESSION_CREATION_FAILED',
+ SESSION_LIMIT_EXCEEDED: 'SESSION_LIMIT_EXCEEDED',
+ SESSION_TIMEOUT: 'SESSION_TIMEOUT',
+ SHELL_NOT_FOUND: 'SHELL_NOT_FOUND',
+
+ /** WebSocket errors */
+ // Temporarily disabled - ws module removed
+ // WEBSOCKET_CONNECTION_FAILED: 'WEBSOCKET_CONNECTION_FAILED',
+ // INVALID_SUBSCRIPTION: 'INVALID_SUBSCRIPTION',
+ // TARGET_NOT_SUBSCRIBABLE: 'TARGET_NOT_SUBSCRIBABLE',
+
+ /** Server errors */
+ SERVER_UNAVAILABLE: 'SERVER_UNAVAILABLE',
+ HEALTH_CHECK_FAILED: 'HEALTH_CHECK_FAILED',
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
+ SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
+ MAINTENANCE_MODE: 'MAINTENANCE_MODE',
+} as const
+
+export const HTTP_STATUS = {
+ OK: 200,
+ CREATED: 201,
+ ACCEPTED: 202,
+ NO_CONTENT: 204,
+ BAD_REQUEST: 400,
+ UNAUTHORIZED: 401,
+ FORBIDDEN: 403,
+ NOT_FOUND: 404,
+ METHOD_NOT_ALLOWED: 405,
+ TIMEOUT: 408,
+ CONFLICT: 409,
+ GONE: 410,
+ TOO_MANY_REQUESTS: 429,
+ INTERNAL_SERVER_ERROR: 500,
+ BAD_GATEWAY: 502,
+ SERVICE_UNAVAILABLE: 503,
+ GATEWAY_TIMEOUT: 504,
+} as const
diff --git a/packages/sdk/src/core/devbox-instance.ts b/packages/sdk/src/core/devbox-instance.ts
new file mode 100644
index 0000000..2ba0709
--- /dev/null
+++ b/packages/sdk/src/core/devbox-instance.ts
@@ -0,0 +1,913 @@
+/**
+ * Devbox instance class for managing individual Devbox containers
+ */
+
+// FormData and File are globally available in Node.js 22+ (via undici)
+import type { ListFilesResponse } from 'devbox-shared/types'
+import type { DevboxRuntime } from '../api/types'
+import { API_ENDPOINTS } from './constants'
+import type { DevboxSDK } from './devbox-sdk'
+import { Git } from './git/git'
+import type {
+ BatchUploadOptions,
+ CodeRunOptions,
+ DevboxInfo,
+ DownloadFileOptions,
+ // FileChangeEvent, // Temporarily disabled - ws module removed
+ FileMap,
+ FindInFilesOptions,
+ FindInFilesResponse,
+ // FileWatchWebSocket, // Temporarily disabled - ws module removed
+ GetProcessLogsResponse,
+ GetProcessStatusResponse,
+ KillProcessOptions,
+ ListProcessesResponse,
+ MonitorData,
+ MoveFileResponse,
+ PortPreviewUrl,
+ PortsResponse,
+ ProcessExecOptions,
+ ProcessExecResponse,
+ ReadOptions,
+ RenameFileResponse,
+ ReplaceInFilesOptions,
+ ReplaceInFilesResponse,
+ ResourceInfo,
+ SearchFilesOptions,
+ SearchFilesResponse,
+ SyncExecutionResponse,
+ TimeRange,
+ TransferResult,
+ // WatchRequest, // Temporarily disabled - ws module removed
+ WriteOptions,
+} from './types'
+
+export class DevboxInstance {
+ private info: DevboxInfo
+ private sdk: DevboxSDK
+ public readonly git: Git
+
+ constructor(info: DevboxInfo, sdk: DevboxSDK) {
+ this.info = info
+ this.sdk = sdk
+ // Initialize Git with dependency injection
+ this.git = new Git({
+ execSync: options => this.execSync(options),
+ })
+ }
+
+ // Properties
+ get name(): string {
+ return this.info.name
+ }
+
+ get status(): string {
+ return this.info.status
+ }
+
+ get runtime(): DevboxRuntime {
+ return this.info.runtime
+ }
+
+ get resources(): ResourceInfo {
+ return this.info.resources
+ }
+
+ get serverUrl(): string {
+ if (!this.info.podIP) {
+ throw new Error(`Devbox '${this.name}' does not have a pod IP address`)
+ }
+ return `http://${this.info.podIP}:3000`
+ }
+
+ // Lifecycle operations
+ async start(): Promise {
+ const apiClient = this.sdk.getAPIClient()
+ await apiClient.startDevbox(this.name)
+ // Refresh the instance info after starting
+ await this.refreshInfo()
+ }
+
+ async pause(): Promise {
+ const apiClient = this.sdk.getAPIClient()
+ await apiClient.pauseDevbox(this.name)
+ await this.refreshInfo()
+ }
+
+ async restart(): Promise {
+ const apiClient = this.sdk.getAPIClient()
+ await apiClient.restartDevbox(this.name)
+ await this.refreshInfo()
+ }
+
+ async shutdown(): Promise {
+ const apiClient = this.sdk.getAPIClient()
+ await apiClient.shutdownDevbox(this.name)
+ await this.refreshInfo()
+ }
+
+ async delete(): Promise {
+ const apiClient = this.sdk.getAPIClient()
+ await apiClient.deleteDevbox(this.name)
+ }
+
+ /**
+ * Refresh the instance information from the API
+ */
+ async refreshInfo(): Promise {
+ const apiClient = this.sdk.getAPIClient()
+ this.info = await apiClient.getDevbox(this.name)
+ }
+
+ async writeFile(path: string, content: string | Buffer, options?: WriteOptions): Promise {
+ this.validatePath(path)
+ const urlResolver = this.sdk.getUrlResolver()
+ await urlResolver.executeWithConnection(this.name, async client => {
+ // Go server supports three modes based on Content-Type:
+ // 1. JSON mode (application/json): For text and base64-encoded small files
+ // 2. Binary mode (other Content-Type): For binary files, path via query parameter
+ // 3. Multipart mode (multipart/form-data): For browser FormData
+
+ // Determine content size
+ const contentSize = Buffer.isBuffer(content)
+ ? content.length
+ : Buffer.byteLength(content, 'utf-8')
+
+ // Use binary mode for large files (>1MB) to avoid JSON buffering issues
+ // JSON mode requires Go server to buffer entire request body in memory
+ const LARGE_FILE_THRESHOLD = 1 * 1024 * 1024 // 1MB
+ const useBinaryMode = contentSize > LARGE_FILE_THRESHOLD
+
+ if (Buffer.isBuffer(content)) {
+ // For Buffer, use Binary mode by default (more efficient, ~25% less bandwidth)
+ // Unless user explicitly requests base64 encoding
+ if (options?.encoding === 'base64') {
+ // Use JSON mode with base64 encoding
+ const base64Content = content.toString('base64')
+ await client.post('/api/v1/files/write', {
+ body: {
+ path,
+ content: base64Content,
+ encoding: 'base64',
+ },
+ })
+ } else {
+ // Use Binary mode: path via query parameter, binary data as body
+ // Content-Type will be set to application/octet-stream by default
+ // Go server's writeFileBinary expects path in query parameter
+ await client.post('/api/v1/files/write', {
+ params: { path },
+ headers: {
+ 'Content-Type': 'application/octet-stream',
+ },
+ body: content, // Direct binary data
+ })
+ }
+ } else {
+ // For string content
+ if (useBinaryMode && !options?.encoding) {
+ // Convert large string to Buffer and use binary mode
+ // This avoids JSON parser buffering entire request body in Go server
+ const buffer = Buffer.from(content, 'utf-8')
+ await client.post('/api/v1/files/write', {
+ params: { path },
+ headers: {
+ 'Content-Type': 'application/octet-stream',
+ },
+ body: buffer,
+ })
+ } else if (options?.encoding === 'base64') {
+ // User explicitly wants base64 encoding
+ const base64Content = Buffer.from(content, 'utf-8').toString('base64')
+ await client.post('/api/v1/files/write', {
+ body: {
+ path,
+ content: base64Content,
+ encoding: 'base64',
+ },
+ })
+ } else {
+ // Default: send as plain text (no encoding field)
+ // Go server will treat it as plain text when encoding is not set
+ await client.post('/api/v1/files/write', {
+ body: {
+ path,
+ content,
+ },
+ })
+ }
+ }
+ })
+ }
+
+ async readFile(path: string, options?: ReadOptions): Promise {
+ this.validatePath(path)
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ // According to openapi.yaml, /api/v1/files/read is a GET request that returns binary content
+ // Server may return different Content-Types:
+ // - application/octet-stream, image/*, video/*, audio/* -> binary (Buffer)
+ // - text/plain -> text (string)
+ const response = await client.get('/api/v1/files/read', {
+ params: { path, ...options },
+ })
+
+ // HTTP client handles response based on Content-Type:
+ // - Binary content types -> Buffer
+ // - Text content types -> string
+ // Note: Go server's ReadFile endpoint does NOT support encoding parameter
+ // It always returns raw file content. Base64 encoding is only used during
+ // write operations for JSON mode transmission.
+
+ if (Buffer.isBuffer(response.data)) {
+ // Binary content already in Buffer format
+ return response.data
+ }
+
+ // If it's a string, convert to Buffer
+ if (typeof response.data === 'string') {
+ // Go server returns raw file content as text/plain for text files
+ // Convert UTF-8 string to Buffer (preserves Unicode characters correctly)
+ // Note: encoding option is ignored for readFile - server doesn't support it
+ return Buffer.from(response.data, 'utf-8')
+ }
+
+ // Handle ArrayBuffer if present (fallback for safety)
+ if (response.data instanceof ArrayBuffer) {
+ return Buffer.from(new Uint8Array(response.data))
+ }
+ if (response.data instanceof Uint8Array) {
+ return Buffer.from(response.data)
+ }
+
+ // Log the actual type for debugging
+ const dataType = typeof response.data
+ const dataConstructor = response.data?.constructor?.name || 'unknown'
+ throw new Error(
+ `Failed to read file: unexpected response format (type: ${dataType}, constructor: ${dataConstructor})`
+ )
+ })
+ }
+
+ /**
+ * Validate file path to prevent directory traversal attacks
+ */
+ private validatePath(path: string): void {
+ if (!path || path.length === 0) {
+ throw new Error('Path cannot be empty')
+ }
+
+ // Reject paths ending with slash (directory paths)
+ if (path.endsWith('/') || path.endsWith('\\')) {
+ throw new Error('Path cannot end with a directory separator')
+ }
+
+ // Check for directory traversal attempts
+ const normalized = path.replace(/\\/g, '/')
+ if (normalized.includes('../') || normalized.includes('..\\')) {
+ throw new Error(`Path traversal detected: ${path}`)
+ }
+
+ // Ensure absolute paths start from workspace
+ if (normalized.startsWith('/') && (normalized.startsWith('/../') || normalized === '/..')) {
+ throw new Error(`Invalid absolute path: ${path}`)
+ }
+ }
+
+ async deleteFile(path: string): Promise {
+ // Validate path to prevent directory traversal
+ this.validatePath(path)
+ const urlResolver = this.sdk.getUrlResolver()
+ await urlResolver.executeWithConnection(this.name, async client => {
+ await client.post('/api/v1/files/delete', {
+ body: { path },
+ })
+ })
+ }
+
+ async listFiles(path: string): Promise {
+ // Validate path to prevent directory traversal
+ this.validatePath(path)
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.get('/api/v1/files/list', {
+ params: { path },
+ })
+ return response.data
+ })
+ }
+
+ async uploadFiles(
+ files: FileMap,
+ options?: BatchUploadOptions & { targetDir?: string }
+ ): Promise {
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const formData = new FormData()
+
+ let targetDir: string
+ const relativePaths: string[] = []
+ const filePaths = Object.keys(files)
+
+ if (options?.targetDir) {
+ targetDir = options.targetDir.replace(/\/+$/, '') || '.'
+ for (const filePath of filePaths) {
+ if (filePath.startsWith(`${targetDir}/`)) {
+ relativePaths.push(filePath.slice(targetDir.length + 1))
+ } else if (filePath === targetDir) {
+ relativePaths.push('')
+ } else {
+ relativePaths.push(filePath)
+ }
+ }
+ } else {
+ if (filePaths.length === 0) {
+ targetDir = '.'
+ } else {
+ const dirParts = filePaths.map(path => {
+ const parts = path.split('/')
+ return parts.slice(0, -1)
+ })
+
+ if (dirParts.length > 0 && dirParts[0] && dirParts[0].length > 0) {
+ const commonPrefix: string[] = []
+ const minLength = Math.min(...dirParts.map(p => p.length))
+ const firstDirParts = dirParts[0]
+
+ for (let i = 0; i < minLength; i++) {
+ const segment = firstDirParts[i]
+ if (segment && dirParts.every(p => p[i] === segment)) {
+ commonPrefix.push(segment)
+ } else {
+ break
+ }
+ }
+
+ targetDir = commonPrefix.length > 0 ? commonPrefix.join('/') : '.'
+ } else {
+ targetDir = '.'
+ }
+
+ const normalizedTargetDir = targetDir === '.' ? '' : targetDir
+ for (const filePath of filePaths) {
+ if (normalizedTargetDir && filePath.startsWith(`${normalizedTargetDir}/`)) {
+ relativePaths.push(filePath.slice(normalizedTargetDir.length + 1))
+ } else {
+ relativePaths.push(filePath)
+ }
+ }
+ }
+ }
+
+ formData.append('targetDir', targetDir)
+
+ let index = 0
+ for (const [filePath, content] of Object.entries(files)) {
+ const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content)
+ const relativePath = relativePaths[index++] || filePath.split('/').pop() || 'file'
+ // Server doesn't use targetDir parameter, so we need to combine targetDir and relativePath
+ // to form the full path as the filename
+ const fullPath = targetDir === '.' ? relativePath : `${targetDir}/${relativePath}`
+ // Convert Buffer to Uint8Array for File constructor compatibility
+ const uint8Array = new Uint8Array(buffer)
+ const file = new File([uint8Array], fullPath)
+ formData.append('files', file)
+ }
+
+ const response = await client.post('/api/v1/files/batch-upload', {
+ body: formData,
+ })
+ return response.data
+ })
+ }
+
+ async moveFile(
+ source: string,
+ destination: string,
+ overwrite = false
+ ): Promise {
+ this.validatePath(source)
+ this.validatePath(destination)
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.post(API_ENDPOINTS.CONTAINER.FILES.MOVE, {
+ body: {
+ source,
+ destination,
+ overwrite,
+ },
+ })
+ return response.data
+ })
+ }
+
+ /**
+ * Rename a file or directory
+ * @param oldPath Current file or directory path
+ * @param newPath New file or directory path
+ * @returns Rename operation response
+ */
+ async renameFile(oldPath: string, newPath: string): Promise {
+ this.validatePath(oldPath)
+ this.validatePath(newPath)
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.post(API_ENDPOINTS.CONTAINER.FILES.RENAME, {
+ body: {
+ oldPath,
+ newPath,
+ },
+ })
+ return response.data
+ })
+ }
+
+ /**
+ * Search for files by filename pattern (case-insensitive substring match)
+ * @param options Search options including directory and pattern
+ * @returns List of matching file paths
+ */
+ async searchFiles(options: SearchFilesOptions): Promise {
+ if (!options.pattern || options.pattern.trim().length === 0) {
+ throw new Error('Pattern cannot be empty')
+ }
+
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.post(
+ API_ENDPOINTS.CONTAINER.FILES.SEARCH,
+ {
+ body: {
+ dir: options.dir || '.',
+ pattern: options.pattern,
+ },
+ }
+ )
+ return response.data
+ })
+ }
+
+ /**
+ * Find files by content keyword (searches inside text files)
+ * @param options Find options including directory and keyword
+ * @returns List of file paths containing the keyword
+ */
+ async findInFiles(options: FindInFilesOptions): Promise {
+ if (!options.keyword || options.keyword.trim().length === 0) {
+ throw new Error('Keyword cannot be empty')
+ }
+
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.post(
+ API_ENDPOINTS.CONTAINER.FILES.FIND,
+ {
+ body: {
+ dir: options.dir || '.',
+ keyword: options.keyword,
+ },
+ }
+ )
+ return response.data
+ })
+ }
+
+ /**
+ * Replace text in multiple files
+ * @param options Replace options including file paths, from text, and to text
+ * @returns Replacement results for each file
+ */
+ async replaceInFiles(
+ options: ReplaceInFilesOptions
+ ): Promise {
+ if (!options.from || options.from.trim().length === 0) {
+ throw new Error("'from' string cannot be empty")
+ }
+
+ if (!options.files || options.files.length === 0) {
+ throw new Error('At least one file path is required')
+ }
+
+ // Validate all file paths
+ for (const filePath of options.files) {
+ this.validatePath(filePath)
+ }
+
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.post(
+ API_ENDPOINTS.CONTAINER.FILES.REPLACE,
+ {
+ body: {
+ files: options.files,
+ from: options.from,
+ to: options.to,
+ },
+ }
+ )
+ return response.data
+ })
+ }
+
+ /**
+ * Download a single file
+ * @param path File path to download
+ * @returns Buffer containing file content
+ */
+ async downloadFile(path: string): Promise {
+ this.validatePath(path)
+
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.get(
+ `${API_ENDPOINTS.CONTAINER.FILES.DOWNLOAD}?path=${encodeURIComponent(path)}`
+ )
+ return response.data
+ })
+ }
+
+ /**
+ * Download multiple files with format options
+ * @param paths Array of file paths to download
+ * @param options Download options including format
+ * @returns Buffer containing downloaded files (tar.gz, tar, or multipart format)
+ */
+ async downloadFiles(
+ paths: string[],
+ options?: { format?: 'tar.gz' | 'tar' | 'multipart' | 'direct' }
+ ): Promise {
+ if (!paths || paths.length === 0) {
+ throw new Error('At least one file path is required')
+ }
+
+ // Validate all paths
+ for (const path of paths) {
+ this.validatePath(path)
+ }
+
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ // Determine Accept header based on format
+ const headers: Record = {}
+ if (options?.format) {
+ switch (options.format) {
+ case 'tar.gz':
+ headers.Accept = 'application/gzip'
+ break
+ case 'tar':
+ headers.Accept = 'application/x-tar'
+ break
+ case 'multipart':
+ headers.Accept = 'multipart/mixed'
+ break
+ case 'direct':
+ // No Accept header for direct download
+ break
+ }
+ }
+
+ const response = await client.post(API_ENDPOINTS.CONTAINER.FILES.BATCH_DOWNLOAD, {
+ body: { paths, format: options?.format },
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
+ })
+
+ return response.data
+ })
+ }
+
+ /**
+ * Get listening ports on the system
+ * @returns Ports response with list of listening ports (3000-9999 range)
+ */
+ async getPorts(): Promise {
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.get(API_ENDPOINTS.CONTAINER.PORTS)
+ return response.data
+ })
+ }
+
+ /**
+ * Get preview link for a specific port
+ * @param port Port number to get preview link for
+ * @returns Preview URL information
+ */
+ async getPreviewLink(port: number): Promise {
+ // Refresh instance info to get latest port configurations
+ await this.refreshInfo()
+
+ // Check if agentServer exists
+ if (!this.info.agentServer?.url) {
+ throw new Error(
+ `No agentServer URL available for Devbox '${this.name}'. Cannot generate preview link.`
+ )
+ }
+
+ const serviceName = this.info.agentServer.url
+
+ // Get SDK's base URL to extract domain
+ const urlResolver = this.sdk.getUrlResolver()
+ const baseUrl = urlResolver.baseUrl
+
+ // Extract domain part from baseUrl
+ // Example: https://devbox.staging-usw-1.sealos.io -> staging-usw-1.sealos.io
+ const urlObj = new URL(baseUrl)
+ const domain = urlObj.hostname.replace(/^devbox\./, '') // Remove devbox. prefix
+
+ // Build preview URL: https://devbox-{serviceName}-{port}.{domain}
+ const url = `${urlObj.protocol}//devbox-${serviceName}-${port}.${domain}`
+ const protocol = urlObj.protocol.replace(':', '') as 'http' | 'https'
+
+ return {
+ url,
+ port,
+ protocol,
+ }
+ }
+
+ // Temporarily disabled - ws module removed
+ // File watching (instance method)
+ // async watchFiles(
+ // path: string,
+ // callback: (event: FileChangeEvent) => void
+ // ): Promise {
+ // const urlResolver = this.sdk.getUrlResolver()
+ // const serverUrl = await urlResolver.getServerUrl(this.name)
+ // const { default: WebSocket } = await import('ws')
+ // const ws = new WebSocket(`ws://${serverUrl.replace('http://', '')}/ws`) as unknown as FileWatchWebSocket
+
+ // ws.onopen = () => {
+ // const watchRequest: WatchRequest = { type: 'watch', path }
+ // ws.send(JSON.stringify(watchRequest))
+ // }
+
+ // ws.onmessage = (event: any) => {
+ // try {
+ // const data = typeof event.data === 'string' ? event.data : event.data?.toString() || ''
+ // const fileEvent = JSON.parse(data) as FileChangeEvent
+ // callback(fileEvent)
+ // } catch (error) {
+ // console.error('Failed to parse file watch event:', error)
+ // }
+ // }
+
+ // return ws
+ // }
+
+ // Process execution
+ /**
+ * Execute a process asynchronously
+ * @param options Process execution options
+ * @returns Process execution response with process_id and pid
+ */
+ async executeCommand(options: ProcessExecOptions): Promise {
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.post(
+ API_ENDPOINTS.CONTAINER.PROCESS.EXEC,
+ {
+ body: {
+ command: options.command,
+ args: options.args,
+ cwd: options.cwd,
+ env: options.env,
+ shell: options.shell,
+ timeout: options.timeout,
+ },
+ }
+ )
+ return response.data
+ })
+ }
+
+ /**
+ * Execute a process synchronously and wait for completion
+ * @param options Process execution options
+ * @returns Synchronous execution response with stdout, stderr, and exit code
+ */
+ async execSync(options: ProcessExecOptions): Promise {
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.post(
+ API_ENDPOINTS.CONTAINER.PROCESS.EXEC_SYNC,
+ {
+ body: {
+ command: options.command,
+ args: options.args,
+ cwd: options.cwd,
+ env: options.env,
+ shell: options.shell,
+ timeout: options.timeout,
+ },
+ }
+ )
+ return response.data
+ })
+ }
+
+ /**
+ * Execute code directly (Node.js or Python)
+ * @param code Code string to execute
+ * @param options Code execution options
+ * @returns Synchronous execution response with stdout, stderr, and exit code
+ */
+ async codeRun(code: string, options?: CodeRunOptions): Promise {
+ const language = options?.language || this.detectLanguage(code)
+ const command = this.buildCodeCommand(code, language, options?.argv)
+
+ return this.execSync({
+ command,
+ cwd: options?.cwd,
+ env: options?.env,
+ timeout: options?.timeout,
+ })
+ }
+
+ /**
+ * Detect programming language from code string
+ * @param code Code string to analyze
+ * @returns Detected language ('node' or 'python')
+ */
+ private detectLanguage(code: string): 'node' | 'python' {
+ // Python characteristics
+ if (/\bdef\s+\w+\(|^\s*import\s+\w+|print\s*\(|:\s*$/.test(code)) {
+ return 'python'
+ }
+ // Node.js characteristics
+ if (/\brequire\s*\(|module\.exports|console\.log/.test(code)) {
+ return 'node'
+ }
+ return 'node' // Default
+ }
+
+ /**
+ * Build shell command to execute code
+ * @param code Code string to execute
+ * @param language Programming language ('node' or 'python')
+ * @param argv Command line arguments
+ * @returns Shell command string
+ */
+ private buildCodeCommand(code: string, language: 'node' | 'python', argv?: string[]): string {
+ const base64Code = Buffer.from(code).toString('base64')
+ const argvStr = argv && argv.length > 0 ? ` ${argv.join(' ')}` : ''
+
+ if (language === 'python') {
+ // Python: python3 -u -c "exec(__import__('base64').b64decode('').decode())"
+ return `sh -c 'python3 -u -c "exec(__import__(\\"base64\\").b64decode(\\"${base64Code}\\").decode())"${argvStr}'`
+ }
+ // Node.js: echo | base64 --decode | node -e "$(cat)"
+ return `sh -c 'echo ${base64Code} | base64 --decode | node -e "$(cat)"${argvStr}'`
+ }
+
+ /**
+ * Execute a process synchronously with streaming output (SSE)
+ * @param options Process execution options
+ * @returns ReadableStream for Server-Sent Events
+ */
+ async execSyncStream(options: ProcessExecOptions): Promise {
+ const urlResolver = this.sdk.getUrlResolver()
+ const serverUrl = await urlResolver.getServerUrl(this.name)
+ const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.EXEC_SYNC_STREAM
+ const url = `${serverUrl}${endpoint}`
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'text/event-stream',
+ Authorization: 'Bearer 1234', // TODO: remove this
+ },
+ body: JSON.stringify({
+ command: options.command,
+ args: options.args,
+ cwd: options.cwd,
+ env: options.env,
+ shell: options.shell,
+ timeout: options.timeout,
+ }),
+ })
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
+ }
+
+ if (!response.body) {
+ throw new Error('Response body is null')
+ }
+
+ return response.body
+ }
+
+ /**
+ * List all processes
+ * @returns List of all processes with their metadata
+ */
+ async listProcesses(): Promise {
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const response = await client.get(API_ENDPOINTS.CONTAINER.PROCESS.LIST)
+ return response.data
+ })
+ }
+
+ /**
+ * Get process status by process_id
+ * @param processId Process ID (string)
+ * @returns Process status response
+ */
+ async getProcessStatus(processId: string): Promise {
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.STATUS.replace('{process_id}', processId)
+ const response = await client.get(endpoint)
+ return response.data
+ })
+ }
+
+ /**
+ * Kill a process by process_id
+ * @param processId Process ID (string)
+ * @param options Optional kill options (signal)
+ */
+ async killProcess(processId: string, options?: KillProcessOptions): Promise {
+ const urlResolver = this.sdk.getUrlResolver()
+ await urlResolver.executeWithConnection(this.name, async client => {
+ const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.KILL.replace('{process_id}', processId)
+ await client.post(endpoint, {
+ params: options?.signal ? { signal: options.signal } : undefined,
+ })
+ })
+ }
+
+ /**
+ * Get process logs by process_id
+ * @param processId Process ID (string)
+ * @param stream Enable log streaming (default: false)
+ * @returns Process logs response
+ */
+ async getProcessLogs(processId: string, stream = false): Promise {
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.executeWithConnection(this.name, async client => {
+ const endpoint = API_ENDPOINTS.CONTAINER.PROCESS.LOGS.replace('{process_id}', processId)
+ const response = await client.get(endpoint, {
+ params: { stream },
+ })
+ return response.data
+ })
+ }
+
+ // Monitoring
+ async getMonitorData(timeRange?: TimeRange): Promise {
+ return await this.sdk.getMonitorData(this.name, timeRange)
+ }
+
+ // Health check
+ async isHealthy(): Promise {
+ try {
+ const urlResolver = this.sdk.getUrlResolver()
+ return await urlResolver.checkDevboxHealth(this.name)
+ } catch (error) {
+ return false
+ }
+ }
+
+ /**
+ * Wait for the Devbox to be ready and healthy
+ * @param timeout Timeout in milliseconds (default: 300000 = 5 minutes)
+ * @param checkInterval Check interval in milliseconds (default: 2000)
+ */
+ async waitForReady(timeout = 300000, checkInterval = 2000): Promise {
+ const startTime = Date.now()
+
+ while (Date.now() - startTime < timeout) {
+ try {
+ // 1. Check Devbox status via API
+ await this.refreshInfo()
+
+ if (this.status === 'Running') {
+ // 2. Check health status via Bun server
+ const healthy = await this.isHealthy()
+
+ if (healthy) {
+ return
+ }
+ }
+ } catch (error) {
+ // Continue waiting on error
+ }
+
+ // Wait before next check
+ await new Promise(resolve => setTimeout(resolve, checkInterval))
+ }
+
+ throw new Error(`Devbox '${this.name}' did not become ready within ${timeout}ms`)
+ }
+
+ /**
+ * Get detailed information about the instance
+ */
+ async getDetailedInfo(): Promise {
+ await this.refreshInfo()
+ return { ...this.info }
+ }
+}
diff --git a/packages/sdk/src/core/devbox-sdk.ts b/packages/sdk/src/core/devbox-sdk.ts
new file mode 100644
index 0000000..62f8374
--- /dev/null
+++ b/packages/sdk/src/core/devbox-sdk.ts
@@ -0,0 +1,96 @@
+import { DevboxAPI } from '../api/client'
+import { ContainerUrlResolver } from '../http/manager'
+import { DevboxInstance } from './devbox-instance'
+import type {
+ DevboxCreateConfig,
+ DevboxCreateOptions,
+ DevboxInfo,
+ DevboxSDKConfig,
+ MonitorData,
+ TimeRange,
+} from './types'
+
+export class DevboxSDK {
+ private apiClient: DevboxAPI
+ private urlResolver: ContainerUrlResolver
+
+ constructor(config: DevboxSDKConfig) {
+ this.apiClient = new DevboxAPI({
+ kubeconfig: config.kubeconfig,
+ baseUrl: config.baseUrl,
+ timeout: config.http?.timeout,
+ retries: config.http?.retries,
+ rejectUnauthorized: config.http?.rejectUnauthorized,
+ })
+ this.urlResolver = new ContainerUrlResolver(config)
+ this.urlResolver.setAPIClient(this.apiClient)
+ }
+
+ /**
+ * Create a new Devbox instance (async, returns immediately without waiting)
+ * @param config Devbox creation configuration
+ * @returns DevboxInstance (may not be ready immediately - use waitForReady() if needed)
+ * @description This method returns immediately after creating the Devbox without waiting for it to be ready.
+ * The returned instance may not be ready for file operations or commands.
+ * Use `createDevbox()` (default behavior) or call `waitForReady()` on the instance if you need to wait.
+ */
+ async createDevboxAsync(config: DevboxCreateConfig): Promise {
+ const devboxInfo = await this.apiClient.createDevbox(config)
+ return new DevboxInstance(devboxInfo, this)
+ }
+
+ /**
+ * Create a new Devbox instance
+ * @param config Devbox creation configuration
+ * @param options Creation options (waitUntilReady defaults to true)
+ * @returns DevboxInstance (ready for use if waitUntilReady is true)
+ * @description By default, this method waits for the Devbox to be fully ready before returning.
+ * Set `options.waitUntilReady = false` to return immediately without waiting.
+ */
+ async createDevbox(
+ config: DevboxCreateConfig,
+ options: DevboxCreateOptions = {}
+ ): Promise {
+ const {
+ waitUntilReady = true,
+ timeout = 180000, // 3 minutes
+ checkInterval = 2000, // 2 seconds
+ } = options
+
+ const instance = await this.createDevboxAsync(config)
+
+ if (waitUntilReady) {
+ await instance.waitForReady(timeout, checkInterval)
+ }
+
+ return instance
+ }
+
+ async getDevbox(name: string): Promise {
+ const devboxInfo = await this.apiClient.getDevbox(name)
+ return new DevboxInstance(devboxInfo, this)
+ }
+
+ async listDevboxes(): Promise {
+ const devboxes = await this.apiClient.listDevboxes()
+ return devboxes.map((info: DevboxInfo) => new DevboxInstance(info, this))
+ }
+
+ async getMonitorData(devboxName: string, timeRange?: TimeRange): Promise {
+ return await this.apiClient.getMonitorData(devboxName, timeRange)
+ }
+
+ async close(): Promise {
+ await this.urlResolver.closeAllConnections()
+ }
+
+ getAPIClient(): DevboxAPI {
+ return this.apiClient
+ }
+
+ getUrlResolver(): ContainerUrlResolver {
+ return this.urlResolver
+ }
+}
+
+export { DevboxInstance } from './devbox-instance'
diff --git a/packages/sdk/src/core/git/git.ts b/packages/sdk/src/core/git/git.ts
new file mode 100644
index 0000000..9bb405f
--- /dev/null
+++ b/packages/sdk/src/core/git/git.ts
@@ -0,0 +1,545 @@
+/**
+ * Git operations module for DevboxInstance
+ * Provides Git repository operations through a clean API
+ */
+
+import type {
+ GitAuth,
+ GitBranchInfo,
+ GitCloneOptions,
+ GitPullOptions,
+ GitPushOptions,
+ GitStatus,
+ ProcessExecOptions,
+ SyncExecutionResponse,
+} from '../types'
+
+/**
+ * Dependencies interface for Git
+ * Allows dependency injection to avoid circular dependencies
+ */
+export interface GitDependencies {
+ execSync: (options: ProcessExecOptions) => Promise
+}
+
+/**
+ * Git operations class
+ * Provides methods for Git repository operations
+ */
+export class Git {
+ constructor(private deps: GitDependencies) {}
+
+ /**
+ * Build Git URL with authentication
+ */
+ private buildAuthUrl(url: string, auth?: GitAuth): string {
+ if (!auth) return url
+
+ // Handle token authentication
+ if (auth.token) {
+ // Extract host from URL
+ const urlMatch = url.match(/^(https?:\/\/)([^@]+@)?([^\/]+)(\/.+)?$/)
+ if (urlMatch) {
+ const [, protocol, , host, path] = urlMatch
+ return `${protocol}${auth.token}@${host}${path || ''}`
+ }
+ }
+
+ // Handle username/password authentication
+ if (auth.username && (auth.password || auth.token)) {
+ const urlMatch = url.match(/^(https?:\/\/)([^\/]+)(\/.+)?$/)
+ if (urlMatch) {
+ const [, protocol, host, path] = urlMatch
+ const password = auth.password || auth.token || ''
+ return `${protocol}${auth.username}:${password}@${host}${path || ''}`
+ }
+ }
+
+ return url
+ }
+
+ /**
+ * Setup Git authentication environment variables
+ */
+ private setupGitAuth(env: Record = {}, auth?: GitAuth): Record {
+ const gitEnv = { ...env }
+
+ if (auth?.username) {
+ gitEnv.GIT_USERNAME = auth.username
+ }
+
+ if (auth?.password) {
+ gitEnv.GIT_PASSWORD = auth.password
+ } else if (auth?.token) {
+ gitEnv.GIT_PASSWORD = auth.token
+ }
+
+ return gitEnv
+ }
+
+ /**
+ * Parse Git branch list output
+ */
+ private parseGitBranches(stdout: string, currentBranch: string): GitBranchInfo[] {
+ const lines = stdout.split('\n').filter(Boolean)
+ const branches: GitBranchInfo[] = []
+
+ for (const line of lines) {
+ const trimmed = line.trim()
+ if (!trimmed) continue
+
+ const isCurrent = trimmed.startsWith('*')
+ const isRemote = trimmed.includes('remotes/')
+ let name = trimmed.replace(/^\*\s*/, '').trim()
+
+ if (isRemote) {
+ // Extract branch name from remotes/origin/branch-name
+ const match = name.match(/^remotes\/[^/]+\/(.+)$/)
+ if (match?.[1]) {
+ name = match[1]
+ } else {
+ continue
+ }
+ }
+
+ // Get commit hash
+ // This would require additional git command, simplified here
+ branches.push({
+ name,
+ isCurrent: name === currentBranch || isCurrent,
+ isRemote,
+ commit: '', // Will be filled by additional git command if needed
+ })
+ }
+
+ return branches
+ }
+
+ /**
+ * Parse Git status output
+ */
+ private parseGitStatus(stdout: string, branchLine: string): GitStatus {
+ const lines = stdout.split('\n').filter(Boolean)
+ const staged: string[] = []
+ const modified: string[] = []
+ const untracked: string[] = []
+ const deleted: string[] = []
+
+ // Parse porcelain status
+ for (const line of lines) {
+ if (line.length < 3) continue
+
+ const status = line.substring(0, 2)
+ const file = line.substring(3).trim()
+
+ if (status[0] === 'A' || status[0] === 'M' || status[0] === 'R' || status[0] === 'C') {
+ staged.push(file)
+ }
+ if (status[1] === 'M' || status[1] === 'D') {
+ modified.push(file)
+ }
+ if (status === '??') {
+ untracked.push(file)
+ }
+ if (status[0] === 'D' || status[1] === 'D') {
+ deleted.push(file)
+ }
+ }
+
+ // Parse branch line: ## branch-name...origin/branch-name [ahead 1, behind 2]
+ let currentBranch = 'main'
+ let ahead = 0
+ let behind = 0
+
+ if (branchLine) {
+ const branchMatch = branchLine.match(/^##\s+([^.]+)/)
+ if (branchMatch?.[1]) {
+ currentBranch = branchMatch[1]
+ }
+
+ const aheadMatch = branchLine.match(/ahead\s+(\d+)/)
+ if (aheadMatch?.[1]) {
+ ahead = Number.parseInt(aheadMatch[1], 10)
+ }
+
+ const behindMatch = branchLine.match(/behind\s+(\d+)/)
+ if (behindMatch?.[1]) {
+ behind = Number.parseInt(behindMatch[1], 10)
+ }
+ }
+
+ const isClean =
+ staged.length === 0 && modified.length === 0 && untracked.length === 0 && deleted.length === 0
+
+ return {
+ currentBranch,
+ isClean,
+ ahead,
+ behind,
+ staged,
+ modified,
+ untracked,
+ deleted,
+ }
+ }
+
+ /**
+ * Clone a Git repository
+ */
+ async clone(options: GitCloneOptions): Promise {
+ const args: string[] = ['clone']
+ if (options.branch) {
+ args.push('-b', options.branch)
+ }
+ if (options.depth) {
+ args.push('--depth', String(options.depth))
+ }
+ if (options.commit) {
+ args.push('--single-branch')
+ }
+ const authUrl = this.buildAuthUrl(options.url, options.auth)
+ args.push(authUrl)
+ if (options.targetDir) {
+ args.push(options.targetDir)
+ }
+
+ const env = this.setupGitAuth({}, options.auth)
+ const result = await this.deps.execSync({
+ command: 'git',
+ args,
+ env,
+ timeout: 300, // 5 minutes timeout for clone
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Git clone failed: ${result.stderr || result.stdout}`)
+ }
+
+ // If specific commit is requested, checkout that commit
+ if (options.commit && options.targetDir) {
+ await this.deps.execSync({
+ command: 'git',
+ args: ['checkout', options.commit],
+ cwd: options.targetDir,
+ })
+ }
+ }
+
+ /**
+ * Pull changes from remote repository
+ */
+ async pull(repoPath: string, options?: GitPullOptions): Promise {
+ const remote = options?.remote || 'origin'
+
+ // If auth is provided, update remote URL to include credentials
+ if (options?.auth) {
+ const urlResult = await this.deps.execSync({
+ command: 'git',
+ args: ['remote', 'get-url', remote],
+ cwd: repoPath,
+ })
+
+ if (urlResult.exitCode === 0) {
+ const currentUrl = urlResult.stdout.trim()
+ const authUrl = this.buildAuthUrl(currentUrl, options.auth)
+
+ // Update remote URL with authentication
+ await this.deps.execSync({
+ command: 'git',
+ args: ['remote', 'set-url', remote, authUrl],
+ cwd: repoPath,
+ })
+ }
+ }
+
+ const args: string[] = ['pull']
+ if (options?.branch) {
+ args.push(remote, options.branch)
+ }
+
+ const result = await this.deps.execSync({
+ command: 'git',
+ args,
+ cwd: repoPath,
+ timeout: 120, // 2 minutes timeout
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Git pull failed: ${result.stderr || result.stdout}`)
+ }
+ }
+
+ /**
+ * Push changes to remote repository
+ */
+ async push(repoPath: string, options?: GitPushOptions): Promise {
+ const remote = options?.remote || 'origin'
+
+ // If auth is provided, update remote URL to include credentials
+ if (options?.auth) {
+ const urlResult = await this.deps.execSync({
+ command: 'git',
+ args: ['remote', 'get-url', remote],
+ cwd: repoPath,
+ })
+
+ if (urlResult.exitCode === 0) {
+ const currentUrl = urlResult.stdout.trim()
+ const authUrl = this.buildAuthUrl(currentUrl, options.auth)
+
+ // Update remote URL with authentication
+ await this.deps.execSync({
+ command: 'git',
+ args: ['remote', 'set-url', remote, authUrl],
+ cwd: repoPath,
+ })
+ }
+ }
+
+ const args: string[] = ['push']
+ if (options?.force) {
+ args.push('--force')
+ }
+ if (options?.branch) {
+ args.push(remote, options.branch)
+ } else {
+ args.push(remote)
+ }
+
+ const result = await this.deps.execSync({
+ command: 'git',
+ args,
+ cwd: repoPath,
+ timeout: 120, // 2 minutes timeout
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Git push failed: ${result.stderr || result.stdout}`)
+ }
+ }
+
+ /**
+ * List all branches
+ */
+ async branches(repoPath: string): Promise {
+ // Get current branch
+ const currentBranchResult = await this.deps.execSync({
+ command: 'git',
+ args: ['rev-parse', '--abbrev-ref', 'HEAD'],
+ cwd: repoPath,
+ })
+
+ const currentBranch = currentBranchResult.stdout.trim()
+
+ // Get all branches
+ const branchesResult = await this.deps.execSync({
+ command: 'git',
+ args: ['branch', '-a'],
+ cwd: repoPath,
+ })
+
+ if (branchesResult.exitCode !== 0) {
+ throw new Error(`Git branches failed: ${branchesResult.stderr || branchesResult.stdout}`)
+ }
+
+ const branches = this.parseGitBranches(branchesResult.stdout, currentBranch)
+
+ // Get commit hashes for each branch
+ for (const branch of branches) {
+ try {
+ const commitResult = await this.deps.execSync({
+ command: 'git',
+ args: ['rev-parse', branch.isRemote ? `origin/${branch.name}` : branch.name],
+ cwd: repoPath,
+ })
+ if (commitResult.exitCode === 0) {
+ branch.commit = commitResult.stdout.trim()
+ }
+ } catch {
+ // Ignore errors for branches that don't exist
+ }
+ }
+
+ return branches
+ }
+
+ /**
+ * Create a new branch
+ */
+ async createBranch(repoPath: string, branchName: string, checkout = false): Promise {
+ const args = checkout ? ['checkout', '-b', branchName] : ['branch', branchName]
+
+ const result = await this.deps.execSync({
+ command: 'git',
+ args,
+ cwd: repoPath,
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Git create branch failed: ${result.stderr || result.stdout}`)
+ }
+ }
+
+ /**
+ * Delete a branch
+ */
+ async deleteBranch(
+ repoPath: string,
+ branchName: string,
+ force = false,
+ remote = false
+ ): Promise {
+ if (remote) {
+ const result = await this.deps.execSync({
+ command: 'git',
+ args: ['push', 'origin', '--delete', branchName],
+ cwd: repoPath,
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Git delete remote branch failed: ${result.stderr || result.stdout}`)
+ }
+ } else {
+ const args = force ? ['branch', '-D', branchName] : ['branch', '-d', branchName]
+
+ const result = await this.deps.execSync({
+ command: 'git',
+ args,
+ cwd: repoPath,
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Git delete branch failed: ${result.stderr || result.stdout}`)
+ }
+ }
+ }
+
+ /**
+ * Checkout a branch
+ */
+ async checkoutBranch(repoPath: string, branchName: string, create = false): Promise {
+ const args = create ? ['checkout', '-b', branchName] : ['checkout', branchName]
+
+ const result = await this.deps.execSync({
+ command: 'git',
+ args,
+ cwd: repoPath,
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Git checkout failed: ${result.stderr || result.stdout}`)
+ }
+ }
+
+ private normalizePath(repoPath: string, filePath: string): string {
+ const normalize = (p: string): string => {
+ let normalized = p.trim()
+ if (normalized.startsWith('./')) {
+ normalized = normalized.substring(2)
+ }
+ normalized = normalized.replace(/\/$/, '')
+ return normalized
+ }
+
+ const normRepo = normalize(repoPath)
+ const normFile = normalize(filePath)
+
+ if (normFile.startsWith(`${normRepo}/`)) {
+ return normFile.substring(normRepo.length + 1)
+ }
+
+ if (normFile === normRepo) {
+ return '.'
+ }
+
+ if (filePath.startsWith('/')) {
+ const repoIndex = filePath.indexOf(normRepo)
+ if (repoIndex !== -1) {
+ const afterRepo = filePath.substring(repoIndex + normRepo.length)
+ if (afterRepo.startsWith('/')) {
+ return afterRepo.substring(1) || '.'
+ }
+ }
+ }
+
+ return normFile
+ }
+
+ /**
+ * Stage files for commit
+ */
+ async add(repoPath: string, files?: string | string[]): Promise {
+ const args: string[] = ['add']
+ if (!files || (Array.isArray(files) && files.length === 0)) {
+ args.push('.')
+ } else if (typeof files === 'string') {
+ args.push(this.normalizePath(repoPath, files))
+ } else {
+ args.push(...files.map(file => this.normalizePath(repoPath, file)))
+ }
+
+ const result = await this.deps.execSync({
+ command: 'git',
+ args,
+ cwd: repoPath,
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Git add failed: ${result.stderr || result.stdout}`)
+ }
+ }
+
+ /**
+ * Commit changes
+ */
+ async commit(
+ repoPath: string,
+ message: string,
+ author: string,
+ email: string,
+ allowEmpty?: boolean
+ ): Promise {
+ const args: string[] = ['commit']
+ if (allowEmpty) {
+ args.push('--allow-empty')
+ }
+ args.push('--author', `${author} <${email}>`)
+ args.push('-m', message)
+
+ const result = await this.deps.execSync({
+ command: 'git',
+ args,
+ cwd: repoPath,
+ })
+
+ if (result.exitCode !== 0) {
+ throw new Error(`Git commit failed: ${result.stderr || result.stdout}`)
+ }
+ }
+
+ /**
+ * Get repository status
+ */
+ async status(repoPath: string): Promise {
+ // Get porcelain status
+ const porcelainResult = await this.deps.execSync({
+ command: 'git',
+ args: ['status', '--porcelain'],
+ cwd: repoPath,
+ })
+
+ // Get branch status
+ const branchResult = await this.deps.execSync({
+ command: 'git',
+ args: ['status', '-sb'],
+ cwd: repoPath,
+ })
+
+ if (porcelainResult.exitCode !== 0 || branchResult.exitCode !== 0) {
+ throw new Error(`Git status failed: ${branchResult.stderr || branchResult.stdout}`)
+ }
+
+ const branchLine = branchResult.stdout.split('\n')[0] || ''
+ return this.parseGitStatus(porcelainResult.stdout, branchLine)
+ }
+}
diff --git a/packages/sdk/src/core/git/index.ts b/packages/sdk/src/core/git/index.ts
new file mode 100644
index 0000000..3f3c2f8
--- /dev/null
+++ b/packages/sdk/src/core/git/index.ts
@@ -0,0 +1,5 @@
+/**
+ * Git operations module exports
+ */
+export { Git } from './git'
+export type { GitDependencies } from './git'
diff --git a/packages/sdk/src/core/types.ts b/packages/sdk/src/core/types.ts
new file mode 100644
index 0000000..b59e2c1
--- /dev/null
+++ b/packages/sdk/src/core/types.ts
@@ -0,0 +1,569 @@
+/**
+ * Core type definitions for the Devbox SDK
+ */
+
+export interface DevboxSDKConfig {
+ /** kubeconfig content for authentication */
+ kubeconfig: string
+ /** Optional base URL for the Devbox API */
+ baseUrl?: string
+ /** Optional mock server URL for development/testing */
+ mockServerUrl?: string
+ /** HTTP client configuration */
+ http?: HttpClientConfig
+}
+
+export interface HttpClientConfig {
+ /** Request timeout in milliseconds */
+ timeout?: number
+ /** Number of retry attempts */
+ retries?: number
+ /** Proxy configuration */
+ proxy?: string
+ /** Allow self-signed certificates (ONLY for development/testing, NOT recommended for production) */
+ rejectUnauthorized?: boolean
+}
+
+import type { DevboxRuntime } from '../api/types'
+
+export interface DevboxCreateConfig {
+ /** Name of the Devbox instance */
+ name: string
+ /** Runtime environment (node.js, python, go, etc.) */
+ runtime: DevboxRuntime
+ /** Resource allocation */
+ resource: ResourceInfo
+ /** Port configurations */
+ ports?: PortConfig[]
+ /** Environment variables */
+ env?: Record
+}
+
+/**
+ * Options for creating a Devbox instance
+ */
+export interface DevboxCreateOptions {
+ /**
+ * Whether to wait for the Devbox to be fully ready before returning
+ * @default true
+ * @description When true, the method will wait until the Devbox is running and healthy before returning.
+ * When false, the method returns immediately after creation is initiated (Devbox may still be starting).
+ */
+ waitUntilReady?: boolean
+ /**
+ * Maximum time to wait for the Devbox to become ready (in milliseconds)
+ * @default 180000 (3 minutes)
+ * @description Only used when waitUntilReady is true
+ */
+ timeout?: number
+ /**
+ * Interval between health checks (in milliseconds)
+ * @default 2000 (2 seconds)
+ * @description Only used when waitUntilReady is true
+ */
+ checkInterval?: number
+}
+
+export interface ResourceInfo {
+ /** CPU cores allocated */
+ cpu: number
+ /** Memory allocated in GB */
+ memory: number
+}
+
+export interface PortConfig {
+ /** Port number */
+ number: number
+ /** Protocol (HTTP, TCP, etc.) */
+ protocol: string
+}
+
+export interface DevboxInfo {
+ /** Devbox instance name */
+ name: string
+ /** Current status */
+ status: string
+ /** Runtime environment */
+ runtime: DevboxRuntime
+ /** Resource information */
+ resources: ResourceInfo
+ /** Pod IP address */
+ podIP?: string
+ /** SSH connection information */
+ ssh?: SSHInfo
+ /** Port configurations */
+ ports?: Array<{
+ number: number
+ portName: string
+ protocol: string
+ serviceName: string
+ privateAddress: string
+ privateHost: string
+ networkName: string
+ publicHost?: string
+ publicAddress?: string
+ customDomain?: string
+ }>
+ /** Agent server configuration */
+ agentServer?: {
+ url: string
+ token: string
+ }
+}
+
+export interface SSHInfo {
+ /** SSH host */
+ host: string
+ /** SSH port */
+ port: number
+ /** SSH username */
+ user: string
+ /** SSH private key */
+ privateKey: string
+}
+
+export interface FileMap {
+ [path: string]: Buffer | string
+}
+
+export interface WriteOptions {
+ /** File encoding */
+ encoding?: string
+ /** File permissions */
+ mode?: number
+ /** Create parent directories if they don't exist */
+ createDirs?: boolean
+}
+
+export interface ReadOptions {
+ /** File encoding */
+ encoding?: string
+ /** Offset for reading */
+ offset?: number
+ /** Length to read */
+ length?: number
+}
+
+export interface BatchUploadOptions {
+ /** Maximum concurrent uploads */
+ concurrency?: number
+ /** Chunk size for large files */
+ chunkSize?: number
+ /** Progress callback */
+ onProgress?: (progress: TransferProgress) => void
+}
+
+export interface TransferProgress {
+ /** Number of files processed */
+ processed: number
+ /** Total number of files */
+ total: number
+ /** Bytes transferred */
+ bytesTransferred: number
+ /** Total bytes to transfer */
+ totalBytes: number
+ /** Transfer progress percentage */
+ progress: number
+}
+
+export interface TransferResult {
+ /** Transfer was successful */
+ success: boolean
+ /** Upload results for each file */
+ results: Array<{
+ path: string
+ success: boolean
+ size?: number
+ error?: string
+ }>
+ /** Total number of files */
+ totalFiles: number
+ /** Number of successfully uploaded files */
+ successCount: number
+}
+
+export interface TransferError {
+ /** File path */
+ path: string
+ /** Error message */
+ error: string
+ /** Error code */
+ code: string
+}
+
+// File move options
+export interface MoveFileOptions {
+ source: string
+ destination: string
+ overwrite?: boolean
+}
+
+// File move response
+export type MoveFileResponse = Record
+
+// File rename options
+export interface RenameFileOptions {
+ oldPath: string
+ newPath: string
+}
+
+// File rename response
+export type RenameFileResponse = Record
+
+// File download options
+export interface DownloadFileOptions {
+ paths: string[]
+ format?: 'tar.gz' | 'tar' | 'multipart' | 'direct'
+}
+
+// File search options (by filename)
+export interface SearchFilesOptions {
+ /** Search directory path */
+ dir?: string
+ /** Filename pattern to match (case-insensitive substring) */
+ pattern: string
+}
+
+// File search response
+export interface SearchFilesResponse {
+ /** List of matching file paths */
+ files: string[]
+}
+
+// File find options (by content)
+export interface FindInFilesOptions {
+ /** Search directory path */
+ dir?: string
+ /** Keyword to search for in file contents */
+ keyword: string
+}
+
+// File find response
+export interface FindInFilesResponse {
+ /** List of file paths containing the keyword */
+ files: string[]
+}
+
+// File replace options
+export interface ReplaceInFilesOptions {
+ /** List of file paths to replace text in */
+ files: string[]
+ /** Original text to replace */
+ from: string
+ /** Replacement text */
+ to: string
+}
+
+// File replace result
+export interface ReplaceResult {
+ /** File path */
+ file: string
+ /** Operation status: 'success' | 'error' | 'skipped' */
+ status: 'success' | 'error' | 'skipped'
+ /** Number of replacements made */
+ replacements: number
+ /** Error message (if status is error or skipped) */
+ error?: string
+}
+
+// File replace response
+export interface ReplaceInFilesResponse {
+ /** Replacement results for each file */
+ results: ReplaceResult[]
+}
+
+// Ports response
+export interface PortsResponse {
+ ports: number[]
+ lastUpdatedAt: number
+}
+
+// Port preview URL response
+export interface PortPreviewUrl {
+ /** Preview URL for accessing the port */
+ url: string
+ /** Port number */
+ port: number
+ /** Protocol (http/https) */
+ protocol: string
+}
+
+// Temporarily disabled - ws module removed
+// export interface FileChangeEvent {
+// /** Event type (add, change, unlink) */
+// type: 'add' | 'change' | 'unlink'
+// /** File path */
+// path: string
+// /** Event timestamp */
+// timestamp: number
+// }
+
+// /**
+// * WebSocket watch request message
+// */
+// export interface WatchRequest {
+// type: 'watch'
+// path: string
+// recursive?: boolean
+// }
+
+// /**
+// * WebSocket message for file watching
+// */
+// export interface WebSocketMessage {
+// type: 'watch' | 'unwatch' | 'ping' | 'pong'
+// path?: string
+// data?: unknown
+// }
+
+// /**
+// * File watch WebSocket interface
+// */
+// export interface FileWatchWebSocket {
+// onopen: () => void
+// onmessage: (event: { data: string | Buffer | ArrayBuffer }) => void
+// onerror: (error: Event) => void
+// onclose: (event: { code?: number; reason?: string; wasClean?: boolean }) => void
+// send(data: string): void
+// close(code?: number, reason?: string): void
+// readyState: number
+// }
+
+export interface TimeRange {
+ /** Start timestamp */
+ start: number
+ /** End timestamp */
+ end: number
+ /** Step interval */
+ step?: string
+}
+
+export interface MonitorData {
+ /** CPU usage percentage */
+ cpu: number
+ /** Memory usage percentage */
+ memory: number
+ /** Network I/O */
+ network: {
+ /** Bytes received */
+ bytesIn: number
+ /** Bytes sent */
+ bytesOut: number
+ }
+ /** Disk usage */
+ disk: {
+ /** Used bytes */
+ used: number
+ /** Total bytes */
+ total: number
+ }
+ /** Timestamp */
+ timestamp: number
+}
+
+// Process execution request options
+export interface ProcessExecOptions {
+ /** Command to execute */
+ command: string
+ /** Command arguments */
+ args?: string[]
+ /** Working directory */
+ cwd?: string
+ /** Environment variables */
+ env?: Record
+ /** Shell to use for execution */
+ shell?: string
+ /** Timeout in seconds */
+ timeout?: number
+}
+
+// Code execution options
+export interface CodeRunOptions {
+ /** Language to use ('node' | 'python'). If not specified, will auto-detect */
+ language?: 'node' | 'python'
+ /** Command line arguments */
+ argv?: string[]
+ /** Environment variables */
+ env?: Record
+ /** Working directory */
+ cwd?: string
+ /** Timeout in seconds */
+ timeout?: number
+}
+
+// Asynchronous execution response
+export interface ProcessExecResponse {
+ success: boolean
+ processId: string
+ pid: number
+ processStatus: string
+ exitCode?: number
+}
+
+// Synchronous execution response
+export interface SyncExecutionResponse {
+ success: boolean
+ stdout: string
+ stderr: string
+ exitCode?: number
+ durationMs: number
+ startTime: number
+ endTime: number
+}
+
+// Process information
+export interface ProcessInfo {
+ processId: string
+ pid: number
+ command: string
+ processStatus: string
+ startTime: number
+ endTime?: number
+ exitCode?: number
+}
+
+// Process list response
+export interface ListProcessesResponse {
+ success: boolean
+ processes: ProcessInfo[]
+}
+
+// Process status response
+export interface GetProcessStatusResponse {
+ success: boolean
+ processId: string
+ pid: number
+ processStatus: string
+ // startedAt: number // Unix timestamp (seconds)
+}
+
+// Process logs response
+export interface GetProcessLogsResponse {
+ success: boolean
+ processId: string
+ logs: string[]
+}
+
+// Kill process options
+export interface KillProcessOptions {
+ signal?: 'SIGTERM' | 'SIGKILL' | 'SIGINT'
+}
+
+// Legacy types (deprecated, kept for backward compatibility during migration)
+export interface CommandResult {
+ /** Command exit code */
+ exitCode: number
+ /** Standard output */
+ stdout: string
+ /** Standard error */
+ stderr: string
+ /** Execution duration in milliseconds */
+ duration: number
+ /** Process ID */
+ pid?: number
+}
+
+export interface ProcessStatus {
+ /** Process ID */
+ pid: number
+ /** Process state */
+ state: 'running' | 'completed' | 'failed' | 'unknown'
+ /** Exit code if completed */
+ exitCode?: number
+ /** CPU usage */
+ cpu?: number
+ /** Memory usage */
+ memory?: number
+ /** Start time */
+ startTime: number
+ /** Running time in milliseconds */
+ runningTime: number
+}
+
+export type DevboxStatus = 'Creating' | 'Running' | 'Stopped' | 'Error' | 'Deleting' | 'Unknown'
+
+// Git authentication options
+export interface GitAuth {
+ /** Username for authentication */
+ username?: string
+ /** Password for authentication */
+ password?: string
+ /** Personal access token or API token */
+ token?: string
+ /** SSH key path (for SSH authentication) */
+ sshKey?: string
+}
+
+// Git clone options
+export interface GitCloneOptions {
+ /** Repository URL */
+ url: string
+ /** Target directory to clone into */
+ targetDir?: string
+ /** Branch to clone */
+ branch?: string
+ /** Specific commit to checkout */
+ commit?: string
+ /** Shallow clone depth */
+ depth?: number
+ /** Authentication options */
+ auth?: GitAuth
+}
+
+// Git pull options
+export interface GitPullOptions {
+ /** Remote name (default: origin) */
+ remote?: string
+ /** Branch to pull (default: current branch) */
+ branch?: string
+ /** Authentication options */
+ auth?: GitAuth
+}
+
+// Git push options
+export interface GitPushOptions {
+ /** Remote name (default: origin) */
+ remote?: string
+ /** Branch to push (default: current branch) */
+ branch?: string
+ /** Authentication options */
+ auth?: GitAuth
+ /** Force push */
+ force?: boolean
+}
+
+// Git branch information
+export interface GitBranchInfo {
+ /** Branch name */
+ name: string
+ /** Whether this is the current branch */
+ isCurrent: boolean
+ /** Whether this is a remote branch */
+ isRemote: boolean
+ /** Latest commit hash */
+ commit: string
+ /** Number of commits ahead of remote */
+ ahead?: number
+ /** Number of commits behind remote */
+ behind?: number
+}
+
+// Git repository status
+export interface GitStatus {
+ /** Current branch name */
+ currentBranch: string
+ /** Whether working directory is clean */
+ isClean: boolean
+ /** Number of commits ahead of remote */
+ ahead: number
+ /** Number of commits behind remote */
+ behind: number
+ /** Staged files */
+ staged: string[]
+ /** Modified files */
+ modified: string[]
+ /** Untracked files */
+ untracked: string[]
+ /** Deleted files */
+ deleted: string[]
+}
diff --git a/packages/sdk/src/http/client.ts b/packages/sdk/src/http/client.ts
new file mode 100644
index 0000000..5b2d0fb
--- /dev/null
+++ b/packages/sdk/src/http/client.ts
@@ -0,0 +1,166 @@
+import {
+ DevboxSDKError,
+ ERROR_CODES,
+ type ServerResponse,
+ parseServerResponse,
+} from '../utils/error'
+import { logger } from '../utils/logger'
+import type { HTTPResponse, RequestOptions } from './types'
+
+export class DevboxContainerClient {
+ private baseUrl: string
+ private timeout: number
+ private token: string
+
+ constructor(baseUrl: string, timeout: number, token: string) {
+ this.baseUrl = baseUrl
+ this.timeout = timeout
+ this.token = token
+ }
+
+ async get(path: string, options?: RequestOptions): Promise> {
+ return this.request('GET', path, options)
+ }
+
+ async post(path: string, options?: RequestOptions): Promise> {
+ return this.request('POST', path, options)
+ }
+
+ async put(path: string, options?: RequestOptions): Promise> {
+ return this.request('PUT', path, options)
+ }
+
+ async delete(path: string, options?: RequestOptions): Promise> {
+ return this.request('DELETE', path, options)
+ }
+
+ private async request(
+ method: string,
+ path: string,
+ options?: RequestOptions
+ ): Promise> {
+ const url = new URL(path, this.baseUrl)
+
+ if (options?.params) {
+ for (const [key, value] of Object.entries(options.params)) {
+ if (value !== undefined && value !== null) {
+ url.searchParams.append(key, String(value))
+ }
+ }
+ }
+
+ // Check for FormData (undici FormData or browser FormData)
+ const isFormData = options?.body !== undefined && options.body instanceof FormData
+
+ const fetchOptions: RequestInit = {
+ method,
+ headers: {
+ ...(isFormData ? {} : { 'Content-Type': 'application/json' }),
+ ...options?.headers,
+ // Decode base64 token and use as Bearer token
+ Authorization: `Bearer ${Buffer.from(this.token, 'base64').toString('utf-8')}`,
+ },
+ signal: options?.signal,
+ }
+
+ if (options?.body !== undefined) {
+ if (isFormData) {
+ // undici FormData automatically handles Content-Type with boundary
+ fetchOptions.body = options.body as FormData
+ } else if (typeof options.body === 'string') {
+ fetchOptions.body = options.body
+ } else if (
+ Buffer.isBuffer(options.body) ||
+ options.body instanceof ArrayBuffer ||
+ options.body instanceof Uint8Array
+ ) {
+ // Support binary data (Buffer, ArrayBuffer, Uint8Array)
+ // fetch API natively supports these types
+ fetchOptions.body = options.body as unknown as RequestInit['body']
+ } else {
+ fetchOptions.body = JSON.stringify(options.body)
+ }
+ }
+
+ const controller = new AbortController()
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout)
+
+ try {
+ const response = await fetch(url.toString(), {
+ ...fetchOptions,
+ signal: options?.signal || controller.signal,
+ })
+ logger.info('Request URL:', url.toString())
+ clearTimeout(timeoutId)
+
+ if (!response.ok) {
+ let errorData: { error?: string; code?: string; timestamp?: number } = {}
+ try {
+ const contentType = response.headers.get('content-type') || ''
+ if (contentType.includes('application/json')) {
+ errorData = (await response.json()) as {
+ error?: string
+ code?: string
+ timestamp?: number
+ }
+ }
+ } catch {
+ // Ignore JSON parsing errors
+ }
+
+ const errorMessage = errorData.error || response.statusText
+ const errorCode = errorData.code || ERROR_CODES.CONNECTION_FAILED
+
+ throw new DevboxSDKError(errorMessage, errorCode, {
+ status: response.status,
+ statusText: response.statusText,
+ timestamp: errorData.timestamp,
+ serverErrorCode: errorData.code,
+ })
+ }
+
+ const contentType = response.headers.get('content-type') || ''
+ let data: T
+
+ if (contentType.includes('application/json')) {
+ const jsonData = (await response.json()) as ServerResponse
+ data = parseServerResponse(jsonData)
+ } else if (
+ contentType.includes('application/octet-stream') ||
+ contentType.includes('application/gzip') ||
+ contentType.includes('application/x-tar') ||
+ contentType.includes('multipart/') ||
+ contentType.includes('image/') ||
+ contentType.includes('video/') ||
+ contentType.includes('audio/')
+ ) {
+ const arrayBuffer = await response.arrayBuffer()
+ data = Buffer.from(arrayBuffer) as unknown as T
+ } else {
+ const arrayBuffer = await response.arrayBuffer()
+ data = Buffer.from(arrayBuffer) as unknown as T
+ }
+
+
+ return {
+ data,
+ status: response.status,
+ headers: Object.fromEntries(response.headers.entries()),
+ url: response.url,
+ }
+ } catch (error) {
+ logger.error('Request failed:', error)
+ clearTimeout(timeoutId)
+
+ if (error instanceof DevboxSDKError) {
+ throw error
+ }
+
+ throw new DevboxSDKError(
+ `Request failed: ${(error as Error).message}`,
+ ERROR_CODES.CONNECTION_FAILED,
+ { originalError: (error as Error).message }
+ )
+ }
+ }
+}
diff --git a/packages/sdk/src/http/manager.ts b/packages/sdk/src/http/manager.ts
new file mode 100644
index 0000000..8cbbec0
--- /dev/null
+++ b/packages/sdk/src/http/manager.ts
@@ -0,0 +1,203 @@
+import type { DevboxInfo, DevboxSDKConfig } from '../core/types'
+import { DevboxNotReadyError, DevboxSDKError, ERROR_CODES } from '../utils/error'
+import { parseKubeconfigServerUrl } from '../utils/kubeconfig'
+import { DevboxContainerClient } from './client'
+
+interface IDevboxAPIClient {
+ getDevbox(name: string): Promise
+}
+
+export class ContainerUrlResolver {
+ private apiClient?: IDevboxAPIClient
+ private cache: Map = new Map()
+ private readonly CACHE_TTL = 60000
+ private mockServerUrl?: string
+ public readonly baseUrl: string
+ private timeout: number
+
+ constructor(config: DevboxSDKConfig) {
+ this.mockServerUrl = config.mockServerUrl || process.env.MOCK_SERVER_URL
+ // Priority: config.baseUrl > kubeconfig server URL > default
+ const kubeconfigUrl = config.kubeconfig ? parseKubeconfigServerUrl(config.kubeconfig) : null
+ this.baseUrl = config.baseUrl || kubeconfigUrl || 'https://devbox.usw.sealos.io'
+ this.timeout = config.http?.timeout || 30000
+ }
+
+ setAPIClient(apiClient: IDevboxAPIClient): void {
+ this.apiClient = apiClient
+ }
+
+ async executeWithConnection(
+ devboxName: string,
+ operation: (client: DevboxContainerClient) => Promise
+ ): Promise {
+ const devboxInfo = await this.getDevboxInfo(devboxName)
+ const serverUrl = this.extractUrlFromDevboxInfo(devboxInfo!)
+ // console.log('serverUrl', serverUrl)
+
+ // Check if Devbox is ready (has agentServer info)
+ const token = devboxInfo?.agentServer?.token
+ if (!serverUrl || !token) {
+ // Devbox exists but is not ready yet - throw friendly error
+ throw new DevboxNotReadyError(devboxName, devboxInfo?.status, {
+ hasServerUrl: !!serverUrl,
+ hasToken: !!token,
+ currentStatus: devboxInfo?.status,
+ })
+ }
+
+ const client = new DevboxContainerClient(serverUrl, this.timeout, token)
+ return await operation(client)
+ }
+
+ async getServerUrl(devboxName: string): Promise {
+ const configuredUrl = this.getConfiguredServerUrl()
+ if (configuredUrl) {
+ return configuredUrl
+ }
+
+ if (!this.apiClient) {
+ throw new DevboxSDKError(
+ 'API client not set. Call setAPIClient() first.',
+ ERROR_CODES.INTERNAL_ERROR
+ )
+ }
+
+ const cached = this.getFromCache(`url:${devboxName}`)
+ if (cached && typeof cached === 'string') {
+ return cached
+ }
+
+ try {
+ const url = await this.resolveServerUrlFromAPI(devboxName)
+ this.setCache(`url:${devboxName}`, url)
+ return url
+ } catch (error) {
+ if (error instanceof DevboxSDKError) {
+ throw error
+ }
+ throw new DevboxSDKError(
+ `Failed to get server URL for '${devboxName}': ${(error as Error).message}`,
+ ERROR_CODES.CONNECTION_FAILED,
+ { originalError: (error as Error).message }
+ )
+ }
+ }
+
+ private getConfiguredServerUrl(): string | null {
+ if (this.mockServerUrl) {
+ return this.mockServerUrl
+ }
+ return null
+ }
+
+ private async resolveServerUrlFromAPI(devboxName: string): Promise {
+ const devboxInfo = await this.getDevboxInfo(devboxName)
+
+ if (!devboxInfo) {
+ throw new DevboxSDKError(`Devbox '${devboxName}' not found`, ERROR_CODES.DEVBOX_NOT_FOUND)
+ }
+
+ const url = this.extractUrlFromDevboxInfo(devboxInfo)
+ if (!url) {
+ throw new DevboxSDKError(
+ `Devbox '${devboxName}' does not have an accessible URL`,
+ ERROR_CODES.CONNECTION_FAILED
+ )
+ }
+
+ return url
+ }
+
+ private extractUrlFromDevboxInfo(devboxInfo: DevboxInfo): string | null {
+ // Priority 1: Use agentServer URL if available
+ if (devboxInfo.agentServer?.url) {
+ const serviceName = devboxInfo.agentServer.url
+ // Extract domain part from baseUrl
+ // Example: https://devbox.staging-usw-1.sealos.io -> staging-usw-1.sealos.io
+ const urlObj = new URL(this.baseUrl)
+ const domain = urlObj.hostname.replace(/^devbox\./, '') // Remove devbox. prefix
+ // Build complete URL: https://devbox-{serviceName}-agent.{domain}/
+ return `${urlObj.protocol}//devbox-${serviceName}-agent.${domain}`
+ }
+
+ // Priority 2: Use port addresses
+ if (devboxInfo.ports && devboxInfo.ports.length > 0) {
+ const port = devboxInfo.ports[0]
+ if (port?.publicAddress) {
+ return port.publicAddress
+ }
+ if (port?.privateAddress) {
+ return port.privateAddress
+ }
+ }
+
+ // Priority 3: Fallback to podIP
+ if (devboxInfo.podIP) {
+ return `http://${devboxInfo.podIP}:3000`
+ }
+
+ return null
+ }
+
+ private async getDevboxInfo(devboxName: string): Promise {
+ const cached = this.getFromCache(`devbox:${devboxName}`)
+ if (cached) {
+ return cached as DevboxInfo
+ }
+
+ try {
+ if (!this.apiClient) {
+ throw new Error('API client not set')
+ }
+ const devboxInfo = await this.apiClient.getDevbox(devboxName)
+ this.setCache(`devbox:${devboxName}`, devboxInfo)
+ return devboxInfo
+ } catch (error) {
+ return null
+ }
+ }
+
+ private getFromCache(key: string): unknown | null {
+ const entry = this.cache.get(key)
+ if (!entry) return null
+
+ if (Date.now() - entry.timestamp > this.CACHE_TTL) {
+ this.cache.delete(key)
+ return null
+ }
+
+ return entry.data
+ }
+
+ private setCache(key: string, data: unknown): void {
+ this.cache.set(key, {
+ data,
+ timestamp: Date.now(),
+ })
+ }
+
+ clearCache(): void {
+ this.cache.clear()
+ }
+
+ async closeAllConnections(): Promise {
+ this.clearCache()
+ }
+
+ async checkDevboxHealth(devboxName: string): Promise {
+ try {
+ const devboxInfo = await this.getDevboxInfo(devboxName)
+ const serverUrl = this.extractUrlFromDevboxInfo(devboxInfo!)
+ if (!serverUrl) return false
+ const token = devboxInfo?.agentServer?.token
+ if (!token) return false
+ const client = new DevboxContainerClient(serverUrl, this.timeout, token)
+ const response = await client.get<{ healthStatus?: string; status?: number }>('/health')
+ // Check healthStatus field (API returns: { status: 0, healthStatus: "ok", ... })
+ return response.data?.healthStatus === 'ok'
+ } catch (error) {
+ return false
+ }
+ }
+}
diff --git a/packages/sdk/src/http/types.ts b/packages/sdk/src/http/types.ts
new file mode 100644
index 0000000..af7cc93
--- /dev/null
+++ b/packages/sdk/src/http/types.ts
@@ -0,0 +1,24 @@
+/**
+ * HTTP client type definitions
+ */
+
+/**
+ * HTTP request options
+ */
+export interface RequestOptions {
+ headers?: Record
+ body?: unknown
+ params?: Record
+ timeout?: number
+ signal?: AbortSignal
+}
+
+/**
+ * HTTP response wrapper
+ */
+export interface HTTPResponse {
+ data: T
+ status: number
+ headers: Record
+ url: string
+}
diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts
new file mode 100644
index 0000000..34cd21e
--- /dev/null
+++ b/packages/sdk/src/index.ts
@@ -0,0 +1,109 @@
+/**
+ * Devbox SDK - Main Entry Point
+ * Enterprise TypeScript SDK for Sealos Devbox management
+ */
+
+// Basic version export
+export const VERSION = '1.0.0'
+
+// Export core classes
+export { DevboxSDK } from './core/devbox-sdk'
+export { DevboxInstance } from './core/devbox-instance'
+
+// Export API client
+export { DevboxAPI } from './api/client'
+
+export { ContainerUrlResolver } from './http/manager'
+export { DevboxContainerClient } from './http/client'
+
+// Export error handling
+export {
+ DevboxSDKError,
+ AuthenticationError,
+ ConnectionError,
+ FileOperationError,
+ DevboxNotFoundError,
+ DevboxNotReadyError,
+ ValidationError,
+} from './utils/error'
+
+// Export constants
+export {
+ DEFAULT_CONFIG,
+ API_ENDPOINTS,
+ ERROR_CODES,
+ HTTP_STATUS,
+} from './core/constants'
+
+// Export types for TypeScript users
+export type {
+ DevboxSDKConfig,
+ DevboxCreateConfig,
+ DevboxCreateOptions,
+ DevboxInfo,
+ DevboxStatus,
+ PortConfig,
+ SSHInfo,
+ FileMap,
+ WriteOptions,
+ ReadOptions,
+ BatchUploadOptions,
+ TransferResult,
+ TransferProgress,
+ TransferError,
+ // FileChangeEvent, // Temporarily disabled - ws module removed
+ CommandResult,
+ ProcessStatus,
+ MonitorData,
+ TimeRange,
+ ResourceInfo,
+ HttpClientConfig,
+ ProcessExecOptions,
+ ProcessExecResponse,
+ CodeRunOptions,
+ SyncExecutionResponse,
+ ProcessInfo,
+ ListProcessesResponse,
+ GetProcessStatusResponse,
+ GetProcessLogsResponse,
+ KillProcessOptions,
+ GitAuth,
+ GitCloneOptions,
+ GitPullOptions,
+ GitPushOptions,
+ GitBranchInfo,
+ GitStatus,
+ MoveFileOptions,
+ MoveFileResponse,
+ RenameFileOptions,
+ RenameFileResponse,
+ DownloadFileOptions,
+ SearchFilesOptions,
+ SearchFilesResponse,
+ FindInFilesOptions,
+ FindInFilesResponse,
+ ReplaceInFilesOptions,
+ ReplaceInFilesResponse,
+ ReplaceResult,
+ PortsResponse,
+ PortPreviewUrl,
+} from './core/types'
+
+// Export API types and enums
+export { DevboxRuntime } from './api/types'
+export type {
+ APIResponse,
+ CreateDevboxRequest,
+ UpdateDevboxRequest,
+ PortConfig as APIPortConfig,
+ EnvVar,
+ DevboxDetailApiResponse,
+ DevboxListApiResponse,
+ TemplatesApiResponse,
+ ReleaseListApiResponse,
+ MonitorDataApiResponse,
+} from './api/types'
+
+// Default export for convenience
+import { DevboxSDK } from './core/devbox-sdk'
+export default DevboxSDK
diff --git a/packages/sdk/src/monitoring/metrics.ts b/packages/sdk/src/monitoring/metrics.ts
new file mode 100644
index 0000000..0bd3eed
--- /dev/null
+++ b/packages/sdk/src/monitoring/metrics.ts
@@ -0,0 +1,319 @@
+/**
+ * Metrics Collection
+ * Collects and tracks SDK performance metrics
+ */
+
+export interface SDKMetrics {
+ connectionsCreated: number
+ connectionsActive: number
+ filesTransferred: number
+ bytesTransferred: number
+ errors: number
+ avgLatency: number
+ operationsCount: number
+ requestsTotal: number
+ requestsSuccessful: number
+ requestsFailed: number
+ startTime: number
+ uptime: number
+}
+
+export interface OperationStats {
+ count: number
+ min: number
+ max: number
+ avg: number
+ p50: number
+ p95: number
+ p99: number
+ sum: number
+}
+
+export interface DetailedMetrics {
+ operations: Record
+ errors: Record
+ summary: SDKMetrics
+}
+
+/**
+ * Enhanced metrics collector
+ * Provides detailed performance statistics and monitoring data
+ */
+export class MetricsCollector {
+ private metrics: SDKMetrics
+ private operationMetrics: Map = new Map()
+ private errorCounts: Map = new Map()
+ private startTime: number
+
+ constructor() {
+ this.startTime = Date.now()
+ this.metrics = this.createEmptyMetrics()
+ }
+
+ private createEmptyMetrics(): SDKMetrics {
+ return {
+ connectionsCreated: 0,
+ connectionsActive: 0,
+ filesTransferred: 0,
+ bytesTransferred: 0,
+ errors: 0,
+ avgLatency: 0,
+ operationsCount: 0,
+ requestsTotal: 0,
+ requestsSuccessful: 0,
+ requestsFailed: 0,
+ startTime: this.startTime,
+ uptime: 0,
+ }
+ }
+
+ /**
+ * Record operation metrics
+ */
+ recordOperation(name: string, durationMs: number): void {
+ if (!this.operationMetrics.has(name)) {
+ this.operationMetrics.set(name, [])
+ }
+ this.operationMetrics.get(name)!.push(durationMs)
+ this.metrics.operationsCount++
+ }
+
+ /**
+ * Record file transfer
+ */
+ recordTransfer(size: number, latency: number): void {
+ this.metrics.filesTransferred++
+ this.metrics.bytesTransferred += size
+ this.recordOperation('file_transfer', latency)
+ this.recordRequest(true)
+ }
+
+ /**
+ * Record connection creation
+ */
+ recordConnection(): void {
+ this.metrics.connectionsCreated++
+ this.metrics.connectionsActive++
+ }
+
+ /**
+ * Record connection closure
+ */
+ recordConnectionClosed(): void {
+ this.metrics.connectionsActive = Math.max(0, this.metrics.connectionsActive - 1)
+ }
+
+ /**
+ * Record error
+ */
+ recordError(errorType?: string): void {
+ this.metrics.errors++
+ if (errorType) {
+ const count = this.errorCounts.get(errorType) || 0
+ this.errorCounts.set(errorType, count + 1)
+ }
+ this.recordRequest(false)
+ }
+
+ /**
+ * Record request
+ */
+ recordRequest(success: boolean): void {
+ this.metrics.requestsTotal++
+ if (success) {
+ this.metrics.requestsSuccessful++
+ } else {
+ this.metrics.requestsFailed++
+ }
+ }
+
+ /**
+ * Calculate operation statistics
+ */
+ private calculateStats(values: number[]): OperationStats {
+ if (values.length === 0) {
+ return { count: 0, min: 0, max: 0, avg: 0, p50: 0, p95: 0, p99: 0, sum: 0 }
+ }
+
+ const sorted = [...values].sort((a, b) => a - b)
+ const sum = values.reduce((a, b) => a + b, 0)
+
+ return {
+ count: values.length,
+ min: sorted[0] ?? 0,
+ max: sorted[sorted.length - 1] ?? 0,
+ avg: sum / values.length,
+ p50: sorted[Math.floor(sorted.length * 0.5)] ?? 0,
+ p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
+ p99: sorted[Math.floor(sorted.length * 0.99)] ?? 0,
+ sum,
+ }
+ }
+
+ /**
+ * Get basic metrics
+ */
+ getMetrics(): SDKMetrics {
+ const uptime = Date.now() - this.startTime
+ return { ...this.metrics, uptime }
+ }
+
+ /**
+ * Get detailed metrics
+ */
+ getDetailedMetrics(): DetailedMetrics {
+ const operations: Record = {}
+
+ for (const [name, values] of this.operationMetrics) {
+ operations[name] = this.calculateStats(values)
+ }
+
+ const errors: Record = {}
+ for (const [type, count] of this.errorCounts) {
+ errors[type] = count
+ }
+
+ return {
+ operations,
+ errors,
+ summary: this.getMetrics(),
+ }
+ }
+
+ /**
+ * Get operation statistics
+ */
+ getOperationStats(name: string): OperationStats | null {
+ const values = this.operationMetrics.get(name)
+ if (!values || values.length === 0) {
+ return null
+ }
+ return this.calculateStats(values)
+ }
+
+ /**
+ * Export all metrics as JSON
+ */
+ export(): string {
+ return JSON.stringify(this.getDetailedMetrics(), null, 2)
+ }
+
+ /**
+ * Reset all metrics
+ */
+ reset(): void {
+ this.startTime = Date.now()
+ this.metrics = this.createEmptyMetrics()
+ this.operationMetrics.clear()
+ this.errorCounts.clear()
+ }
+
+ /**
+ * Get performance summary
+ */
+ getSummary(): string {
+ const metrics = this.getMetrics()
+ const uptime = Math.floor(metrics.uptime / 1000) // Convert to seconds
+
+ const lines = [
+ '=== SDK Performance Summary ===',
+ `Uptime: ${uptime}s`,
+ `Operations: ${metrics.operationsCount}`,
+ `Requests: ${metrics.requestsTotal} (Success: ${metrics.requestsSuccessful}, Failed: ${metrics.requestsFailed})`,
+ `Connections: ${metrics.connectionsCreated} created, ${metrics.connectionsActive} active`,
+ `Files Transferred: ${metrics.filesTransferred}`,
+ `Bytes Transferred: ${this.formatBytes(metrics.bytesTransferred)}`,
+ `Errors: ${metrics.errors}`,
+ `Success Rate: ${((metrics.requestsSuccessful / metrics.requestsTotal) * 100 || 0).toFixed(2)}%`,
+ ]
+
+ return lines.join('\n')
+ }
+
+ /**
+ * Format bytes
+ */
+ private formatBytes(bytes: number): string {
+ if (bytes === 0) return '0 B'
+ const k = 1024
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
+ return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`
+ }
+}
+
+// Global metrics collector instance
+export const metrics = new MetricsCollector()
+
+/**
+ * Performance monitoring decorator
+ * Automatically records function execution time
+ */
+export function monitored(operationName: string) {
+ return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
+ const originalMethod = descriptor.value
+
+ descriptor.value = async function (...args: any[]) {
+ const startTime = Date.now()
+ try {
+ const result = await originalMethod.apply(this, args)
+ const duration = Date.now() - startTime
+ metrics.recordOperation(operationName, duration)
+ metrics.recordRequest(true)
+ return result
+ } catch (error) {
+ const duration = Date.now() - startTime
+ metrics.recordOperation(operationName, duration)
+ metrics.recordError(operationName)
+ throw error
+ }
+ }
+
+ return descriptor
+ }
+}
+
+/**
+ * Performance tracking utility
+ */
+export class PerformanceTracker {
+ private startTime: number
+
+ constructor(private operationName: string) {
+ this.startTime = Date.now()
+ }
+
+ /**
+ * End tracking and record
+ */
+ end(): number {
+ const duration = Date.now() - this.startTime
+ metrics.recordOperation(this.operationName, duration)
+ return duration
+ }
+
+ /**
+ * End tracking and record as success
+ */
+ success(): number {
+ const duration = this.end()
+ metrics.recordRequest(true)
+ return duration
+ }
+
+ /**
+ * End tracking and record as failure
+ */
+ failure(errorType?: string): number {
+ const duration = this.end()
+ metrics.recordError(errorType)
+ return duration
+ }
+}
+
+/**
+ * Create performance tracker
+ */
+export function track(operationName: string): PerformanceTracker {
+ return new PerformanceTracker(operationName)
+}
diff --git a/packages/sdk/src/security/adapter.ts b/packages/sdk/src/security/adapter.ts
new file mode 100644
index 0000000..cc5fdb3
--- /dev/null
+++ b/packages/sdk/src/security/adapter.ts
@@ -0,0 +1,30 @@
+/**
+ * Security Adapter
+ * Provides enterprise-level security features
+ */
+
+export class SecurityAdapter {
+ private static instance: SecurityAdapter
+
+ static getInstance(): SecurityAdapter {
+ if (!SecurityAdapter.instance) {
+ SecurityAdapter.instance = new SecurityAdapter()
+ }
+ return SecurityAdapter.instance
+ }
+
+ validatePath(path: string): boolean {
+ // Basic path validation to prevent directory traversal
+ const normalizedPath = path.replace(/\\/g, '/')
+ return !normalizedPath.includes('../') && !normalizedPath.startsWith('/')
+ }
+
+ sanitizeInput(input: string): string {
+ // Basic input sanitization
+ return input.trim()
+ }
+
+ validatePermissions(requiredPermissions: string[], userPermissions: string[]): boolean {
+ return requiredPermissions.every(permission => userPermissions.includes(permission))
+ }
+}
diff --git a/packages/sdk/src/transfer/engine.ts b/packages/sdk/src/transfer/engine.ts
new file mode 100644
index 0000000..fea2e14
--- /dev/null
+++ b/packages/sdk/src/transfer/engine.ts
@@ -0,0 +1,48 @@
+/**
+ * File Transfer Engine
+ * Handles file transfer strategies and optimizations
+ */
+
+import type { FileMap, TransferProgress, TransferResult } from '../core/types'
+
+export interface TransferStrategy {
+ name: string
+ canHandle(files: FileMap): boolean
+ transfer(
+ files: FileMap,
+ onProgress?: (progress: TransferProgress) => void
+ ): Promise
+}
+
+export class TransferEngine {
+ private strategies: TransferStrategy[] = []
+
+ constructor() {
+ this.setupDefaultStrategies()
+ }
+
+ private setupDefaultStrategies(): void {
+ // Default strategies will be added here
+ }
+
+ addStrategy(strategy: TransferStrategy): void {
+ this.strategies.push(strategy)
+ }
+
+ async transferFiles(
+ files: FileMap,
+ onProgress?: (progress: TransferProgress) => void
+ ): Promise {
+ // Select appropriate strategy
+ const strategy = this.selectStrategy(files)
+ if (!strategy) {
+ throw new Error('No suitable transfer strategy found')
+ }
+
+ return strategy.transfer(files, onProgress)
+ }
+
+ private selectStrategy(files: FileMap): TransferStrategy | null {
+ return this.strategies.find(strategy => strategy.canHandle(files)) || null
+ }
+}
diff --git a/packages/sdk/src/utils/error.ts b/packages/sdk/src/utils/error.ts
new file mode 100644
index 0000000..e3ae2de
--- /dev/null
+++ b/packages/sdk/src/utils/error.ts
@@ -0,0 +1,200 @@
+/**
+ * Custom error classes for the Devbox SDK
+ */
+
+import { ERROR_CODES } from '../core/constants'
+
+/**
+ * Error context type for additional error information
+ */
+export interface ErrorContext {
+ status?: number
+ statusText?: string
+ timestamp?: number
+ serverErrorCode?: string
+ originalError?: unknown
+ [key: string]: unknown
+}
+
+export class DevboxSDKError extends Error {
+ constructor(
+ message: string,
+ public code: string,
+ public context?: ErrorContext
+ ) {
+ super(message)
+ this.name = 'DevboxSDKError'
+ }
+}
+
+export class AuthenticationError extends DevboxSDKError {
+ constructor(message: string, context?: ErrorContext) {
+ super(message, 'AUTHENTICATION_FAILED', context)
+ this.name = 'AuthenticationError'
+ }
+}
+
+export class ConnectionError extends DevboxSDKError {
+ constructor(message: string, context?: ErrorContext) {
+ super(message, 'CONNECTION_FAILED', context)
+ this.name = 'ConnectionError'
+ }
+}
+
+export class FileOperationError extends DevboxSDKError {
+ constructor(
+ message: string,
+ context?: ErrorContext,
+ code: string = ERROR_CODES.FILE_TRANSFER_FAILED
+ ) {
+ super(message, code, context)
+ this.name = 'FileOperationError'
+ }
+}
+
+export class DevboxNotFoundError extends DevboxSDKError {
+ constructor(devboxName: string, context?: ErrorContext) {
+ super(`Devbox '${devboxName}' not found`, 'DEVBOX_NOT_FOUND', context)
+ this.name = 'DevboxNotFoundError'
+ }
+}
+
+export class DevboxNotReadyError extends DevboxSDKError {
+ constructor(devboxName: string, currentStatus?: string, context?: ErrorContext) {
+ const statusInfo = currentStatus ? ` (current status: ${currentStatus})` : ''
+ super(
+ `Devbox '${devboxName}' is not ready yet${statusInfo}. The devbox may still be starting. Please wait a moment and try again, or use 'await devbox.waitForReady()' to wait until it's fully initialized.`,
+ 'DEVBOX_NOT_READY',
+ context
+ )
+ this.name = 'DevboxNotReadyError'
+ }
+}
+
+export class ValidationError extends DevboxSDKError {
+ constructor(message: string, context?: ErrorContext) {
+ super(message, 'VALIDATION_ERROR', context)
+ this.name = 'ValidationError'
+ }
+}
+
+/**
+ * Server response format: { status: number, message: string, Data: T }
+ * status: 0 = success, other values = error codes
+ */
+export interface ServerResponse {
+ status?: number
+ message?: string
+ Data?: T
+ [key: string]: unknown
+}
+
+/**
+ * Map server status codes to SDK error codes
+ * Server uses custom status codes in response body (e.g., 1404 for not found)
+ */
+function mapServerStatusToErrorCode(status: number): string {
+ switch (status) {
+ case 1404:
+ return ERROR_CODES.FILE_NOT_FOUND
+ case 1400:
+ return ERROR_CODES.VALIDATION_ERROR
+ case 1401:
+ return ERROR_CODES.UNAUTHORIZED
+ case 1403:
+ return ERROR_CODES.INSUFFICIENT_PERMISSIONS
+ case 1422:
+ return ERROR_CODES.INVALID_REQUEST
+ case 1500:
+ return ERROR_CODES.INTERNAL_ERROR
+ case 1409:
+ return ERROR_CODES.CONFLICT
+ case 1600:
+ return ERROR_CODES.OPERATION_FAILED
+ case 500:
+ return ERROR_CODES.INTERNAL_ERROR
+ default:
+ return ERROR_CODES.OPERATION_FAILED
+ }
+}
+
+/**
+ * Parse server JSON response and check for errors in response body
+ * Server may return HTTP 200 with error status in response body
+ * @param jsonData Parsed JSON response from server
+ * @returns Extracted data from response, or throws error if status indicates failure
+ * @throws {DevboxSDKError} If response contains error status
+ */
+export function parseServerResponse(jsonData: ServerResponse): T {
+ // Check if server returned an error in the response body
+ // Server uses status: 0 for success, other values for errors
+ if (jsonData.status !== undefined && jsonData.status !== 0) {
+ const errorCode = mapServerStatusToErrorCode(jsonData.status)
+ const errorMessage = jsonData.message || 'Unknown server error'
+
+ throw createErrorFromServerResponse(errorMessage, errorCode, undefined)
+ }
+
+ // Extract Data field if present (server wraps response in { status, message, Data })
+ // Otherwise use the entire response as data
+ return (jsonData.Data !== undefined ? jsonData.Data : jsonData) as T
+}
+
+/**
+ * Create an appropriate error instance based on server error code
+ * @param error Server error message
+ * @param code Server error code
+ * @param timestamp Optional timestamp from server
+ * @returns Appropriate error instance
+ */
+export function createErrorFromServerResponse(
+ error: string,
+ code: string,
+ timestamp?: number
+): DevboxSDKError {
+ const errorContext = { timestamp, serverErrorCode: code }
+
+ switch (code) {
+ case ERROR_CODES.UNAUTHORIZED:
+ case ERROR_CODES.INVALID_TOKEN:
+ case ERROR_CODES.TOKEN_EXPIRED:
+ case ERROR_CODES.INSUFFICIENT_PERMISSIONS:
+ return new AuthenticationError(error, errorContext)
+
+ case ERROR_CODES.FILE_NOT_FOUND:
+ case ERROR_CODES.DIRECTORY_NOT_FOUND:
+ case ERROR_CODES.FILE_OPERATION_ERROR:
+ case ERROR_CODES.FILE_TOO_LARGE:
+ case ERROR_CODES.FILE_LOCKED:
+ case ERROR_CODES.DIRECTORY_NOT_EMPTY:
+ case ERROR_CODES.DISK_FULL:
+ return new FileOperationError(error, errorContext, code)
+
+ case ERROR_CODES.INVALID_REQUEST:
+ case ERROR_CODES.MISSING_REQUIRED_FIELD:
+ case ERROR_CODES.INVALID_FIELD_VALUE:
+ case ERROR_CODES.INVALID_JSON_FORMAT:
+ case ERROR_CODES.INVALID_PATH:
+ case ERROR_CODES.INVALID_SIGNAL:
+ return new ValidationError(error, errorContext)
+
+ case ERROR_CODES.DEVBOX_NOT_FOUND:
+ case ERROR_CODES.PROCESS_NOT_FOUND:
+ case ERROR_CODES.SESSION_NOT_FOUND:
+ case ERROR_CODES.NOT_FOUND:
+ if (code === ERROR_CODES.DEVBOX_NOT_FOUND) {
+ // Extract devbox name from error message if possible
+ const devboxNameMatch =
+ error.match(/Devbox '([^']+)'/i) || error.match(/devbox[:\s]+([^\s]+)/i)
+ const devboxName = devboxNameMatch?.[1] ?? 'unknown'
+ return new DevboxNotFoundError(devboxName, errorContext)
+ }
+ return new DevboxSDKError(error, code || ERROR_CODES.INTERNAL_ERROR, errorContext)
+
+ default:
+ return new DevboxSDKError(error, code, errorContext)
+ }
+}
+
+// Re-export ERROR_CODES for convenience
+export { ERROR_CODES }
diff --git a/packages/sdk/src/utils/kubeconfig.ts b/packages/sdk/src/utils/kubeconfig.ts
new file mode 100644
index 0000000..53fcebd
--- /dev/null
+++ b/packages/sdk/src/utils/kubeconfig.ts
@@ -0,0 +1,111 @@
+import yaml from 'js-yaml'
+import { DevboxSDKError, ERROR_CODES } from './error'
+
+interface KubeconfigCluster {
+ cluster: {
+ server: string
+ [key: string]: unknown
+ }
+ name: string
+}
+
+interface KubeconfigContext {
+ context: {
+ cluster: string
+ user: string
+ [key: string]: unknown
+ }
+ name: string
+}
+
+interface Kubeconfig {
+ apiVersion?: string
+ clusters?: KubeconfigCluster[]
+ contexts?: KubeconfigContext[]
+ 'current-context'?: string
+ currentContext?: string
+ [key: string]: unknown
+}
+
+/**
+ * Parse kubeconfig YAML string and extract the API server URL
+ * @param kubeconfig - Kubeconfig content as YAML string
+ * @returns The server URL from the current context's cluster, or null if not found
+ * @throws DevboxSDKError if kubeconfig is invalid or cannot be parsed
+ */
+export function parseKubeconfigServerUrl(kubeconfig: string): string | null {
+ if (!kubeconfig || typeof kubeconfig !== 'string') {
+ return null
+ }
+
+ try {
+ const config = yaml.load(kubeconfig) as Kubeconfig
+
+ if (!config) {
+ return null
+ }
+
+ // Get current context (support both 'current-context' and 'currentContext')
+ const currentContextName = config['current-context'] || config.currentContext
+ if (!currentContextName) {
+ return null
+ }
+
+ // Find the current context
+ const contexts = config.contexts || []
+ const currentContext = contexts.find(
+ (ctx: KubeconfigContext) => ctx.name === currentContextName
+ )
+
+ if (!currentContext || !currentContext.context) {
+ return null
+ }
+
+ // Get cluster name from context
+ const clusterName = currentContext.context.cluster
+ if (!clusterName) {
+ return null
+ }
+
+ // Find the cluster
+ const clusters = config.clusters || []
+ const cluster = clusters.find((cl: KubeconfigCluster) => cl.name === clusterName)
+
+ if (!cluster || !cluster.cluster) {
+ return null
+ }
+
+ // Extract server URL
+ const serverUrl = cluster.cluster.server
+ if (!serverUrl || typeof serverUrl !== 'string') {
+ return null
+ }
+
+ // Transform URL: add "devbox." prefix to hostname and remove all ports
+ // Example: https://192.168.12.53.nip.io:6443 -> https://devbox.192.168.12.53.nip.io
+ try {
+ const url = new URL(serverUrl)
+ // Add "devbox." prefix to hostname
+ url.hostname = `devbox.${url.hostname}`
+ // Remove all ports (devbox API uses standard HTTPS port)
+ url.port = ''
+ // Ensure pathname is empty (remove any existing path)
+ url.pathname = ''
+ // Return URL without trailing slash
+ let result = url.toString()
+ // Remove trailing slash if present
+ if (result.endsWith('/')) {
+ result = result.slice(0, -1)
+ }
+ return result
+ } catch (_urlError) {
+ // If URL parsing fails, return original URL
+ return serverUrl
+ }
+ } catch (_error) {
+ // If parsing fails, return null (will fallback to default)
+ // Don't throw error to allow fallback to default URL
+ return null
+ }
+}
+
diff --git a/packages/sdk/src/utils/logger.ts b/packages/sdk/src/utils/logger.ts
new file mode 100644
index 0000000..31f0534
--- /dev/null
+++ b/packages/sdk/src/utils/logger.ts
@@ -0,0 +1,88 @@
+/**
+ * Logger utility for Devbox SDK
+ * Controls log output level via LOG_LEVEL environment variable
+ * Supports: INFO, WARN, ERROR (DEBUG is not supported)
+ * Default: SILENT (no logs output)
+ */
+
+export enum LogLevel {
+ INFO = 'INFO',
+ WARN = 'WARN',
+ ERROR = 'ERROR',
+ SILENT = 'SILENT',
+}
+
+const LOG_LEVEL_PRIORITY: Record = {
+ [LogLevel.INFO]: 1,
+ [LogLevel.WARN]: 2,
+ [LogLevel.ERROR]: 3,
+ [LogLevel.SILENT]: Number.POSITIVE_INFINITY, // Highest priority, suppresses all logs
+}
+
+/**
+ * Parse log level from environment variable
+ * Defaults to SILENT if not set (no logs output)
+ */
+function getLogLevelFromEnv(): LogLevel {
+ const envLevel = process.env.LOG_LEVEL?.toUpperCase()
+
+ if (envLevel === 'INFO') {
+ return LogLevel.INFO
+ }
+ if (envLevel === 'WARN' || envLevel === 'WARNING') {
+ return LogLevel.WARN
+ }
+ if (envLevel === 'ERROR') {
+ return LogLevel.ERROR
+ }
+
+ // Default to SILENT for any other value (including undefined)
+ // This means no logs will be output unless explicitly enabled
+ return LogLevel.SILENT
+}
+
+class Logger {
+ private currentLevel: LogLevel
+
+ constructor() {
+ this.currentLevel = getLogLevelFromEnv()
+ }
+
+ /**
+ * Check if a log level should be output
+ */
+ private shouldLog(level: LogLevel): boolean {
+ return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.currentLevel]
+ }
+
+ /**
+ * Log info message
+ */
+ info(message: string, ...args: unknown[]): void {
+ if (this.shouldLog(LogLevel.INFO)) {
+ console.log(`[INFO] ${message}`, ...args)
+ }
+ }
+
+ /**
+ * Log warning message
+ */
+ warn(message: string, ...args: unknown[]): void {
+ if (this.shouldLog(LogLevel.WARN)) {
+ console.warn(`[WARN] ${message}`, ...args)
+ }
+ }
+
+ /**
+ * Log error message
+ */
+ error(message: string, ...args: unknown[]): void {
+ if (this.shouldLog(LogLevel.ERROR)) {
+ console.error(`[ERROR] ${message}`, ...args)
+ }
+ }
+}
+
+// Export singleton instance
+export const logger = new Logger()
+
diff --git a/packages/sdk/src/utils/retry.ts b/packages/sdk/src/utils/retry.ts
new file mode 100644
index 0000000..bd77402
--- /dev/null
+++ b/packages/sdk/src/utils/retry.ts
@@ -0,0 +1,422 @@
+/**
+ * Retry strategy utilities
+ * Provides automatic retry capability for network requests and critical operations
+ */
+
+/**
+ * Retryable error interface
+ */
+export interface RetryableError {
+ code?: string
+ status?: number
+ statusCode?: number
+ message?: string
+ [key: string]: unknown
+}
+
+export interface RetryOptions {
+ /** Maximum number of retries */
+ maxRetries: number
+ /** Initial delay time in milliseconds */
+ initialDelay: number
+ /** Maximum delay time in milliseconds */
+ maxDelay: number
+ /** Delay growth factor (exponential backoff) */
+ factor: number
+ /** Total timeout in milliseconds, optional */
+ timeout?: number
+ /** Custom retry condition function */
+ shouldRetry?: (error: unknown) => boolean
+ /** Callback before retry */
+ onRetry?: (error: unknown, attempt: number) => void
+}
+
+export const DEFAULT_RETRY_OPTIONS: RetryOptions = {
+ maxRetries: 3,
+ initialDelay: 1000,
+ maxDelay: 30000,
+ factor: 2,
+}
+
+/**
+ * Execute async operation with retry
+ *
+ * @example
+ * ```ts
+ * const result = await withRetry(
+ * () => apiClient.request('/data'),
+ * { maxRetries: 5, initialDelay: 500 }
+ * )
+ * ```
+ */
+/**
+ * Check if operation has timed out
+ */
+function checkTimeout(startTime: number, timeout?: number): void {
+ if (timeout && Date.now() - startTime > timeout) {
+ throw new Error(`Operation timed out after ${timeout}ms`)
+ }
+}
+
+/**
+ * Calculate retry delay time
+ */
+function calculateDelay(attempt: number, opts: RetryOptions): number {
+ return Math.min(opts.initialDelay * opts.factor ** attempt, opts.maxDelay)
+}
+
+/**
+ * Handle retry logging and callbacks
+ */
+function handleRetryCallback(error: unknown, attempt: number, opts: RetryOptions): void {
+ const errorObj = error as Error
+ if (opts.onRetry) {
+ opts.onRetry(error, attempt + 1)
+ }
+}
+
+export async function withRetry(
+ operation: () => Promise,
+ options: Partial = {}
+): Promise {
+ const opts: RetryOptions = { ...DEFAULT_RETRY_OPTIONS, ...options }
+ const startTime = Date.now()
+
+ for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
+ try {
+ checkTimeout(startTime, opts.timeout)
+ return await operation()
+ } catch (error) {
+ const lastError = error as Error
+
+ // Last attempt, throw error directly
+ if (attempt === opts.maxRetries) {
+ throw lastError
+ }
+
+ // Determine if error is retryable
+ const shouldRetry = opts.shouldRetry ? opts.shouldRetry(error) : isRetryable(error)
+
+ if (!shouldRetry) {
+ throw lastError
+ }
+
+ // Calculate delay and wait
+ const delay = calculateDelay(attempt, opts)
+ handleRetryCallback(error, attempt, opts)
+ await sleep(delay)
+ }
+ }
+
+ // This should not be reached, but for type safety
+ throw new Error('Unexpected error in retry logic')
+}
+
+/**
+ * Check if error is a retryable network error
+ */
+function isRetryableNetworkError(errorObj: RetryableError): boolean {
+ const retryableNetworkErrors = [
+ 'ECONNRESET',
+ 'ETIMEDOUT',
+ 'ECONNREFUSED',
+ 'ENOTFOUND',
+ 'ENETUNREACH',
+ 'EAI_AGAIN',
+ ]
+
+ return !!(errorObj.code && retryableNetworkErrors.includes(errorObj.code))
+}
+
+/**
+ * Check if error is a retryable HTTP status code
+ */
+function isRetryableHTTPStatus(errorObj: RetryableError): boolean {
+ const status = errorObj.status || errorObj.statusCode
+
+ if (!status) {
+ return false
+ }
+
+ // 5xx server errors are retryable
+ if (status >= 500 && status < 600) {
+ return true
+ }
+
+ // 429 Too Many Requests is retryable
+ if (status === 429) {
+ return true
+ }
+
+ // 408 Request Timeout is retryable
+ if (status === 408) {
+ return true
+ }
+
+ return false
+}
+
+/**
+ * Check if error is a timeout error
+ */
+function isTimeoutError(errorObj: RetryableError): boolean {
+ if (!errorObj.message) {
+ return false
+ }
+
+ return (
+ errorObj.message.includes('timeout') ||
+ errorObj.message.includes('timed out') ||
+ errorObj.message.includes('ETIMEDOUT')
+ )
+}
+
+/**
+ * Determine if error is retryable
+ */
+function isRetryable(error: unknown): boolean {
+ const errorObj = error as RetryableError
+
+ // Check if it's a DevboxSDKError with a server error code
+ if (errorObj.code) {
+ // Import ERROR_CODES dynamically to avoid circular dependency
+ const ERROR_CODES = {
+ // 4xx errors that should NOT be retried (except specific cases)
+ UNAUTHORIZED: 'UNAUTHORIZED',
+ INVALID_TOKEN: 'INVALID_TOKEN',
+ TOKEN_EXPIRED: 'TOKEN_EXPIRED',
+ INVALID_REQUEST: 'INVALID_REQUEST',
+ MISSING_REQUIRED_FIELD: 'MISSING_REQUIRED_FIELD',
+ INVALID_FIELD_VALUE: 'INVALID_FIELD_VALUE',
+ NOT_FOUND: 'NOT_FOUND',
+ FILE_NOT_FOUND: 'FILE_NOT_FOUND',
+ PROCESS_NOT_FOUND: 'PROCESS_NOT_FOUND',
+ SESSION_NOT_FOUND: 'SESSION_NOT_FOUND',
+ CONFLICT: 'CONFLICT',
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
+ // 4xx errors that CAN be retried
+ OPERATION_TIMEOUT: 'OPERATION_TIMEOUT',
+ SESSION_TIMEOUT: 'SESSION_TIMEOUT',
+ // 5xx errors that CAN be retried
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
+ SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
+ SERVER_UNAVAILABLE: 'SERVER_UNAVAILABLE',
+ CONNECTION_FAILED: 'CONNECTION_FAILED',
+ CONNECTION_TIMEOUT: 'CONNECTION_TIMEOUT',
+ } as const
+
+ // Don't retry on client errors (4xx) except for timeout errors
+ const nonRetryable4xxCodes = [
+ ERROR_CODES.UNAUTHORIZED,
+ ERROR_CODES.INVALID_TOKEN,
+ ERROR_CODES.TOKEN_EXPIRED,
+ ERROR_CODES.INVALID_REQUEST,
+ ERROR_CODES.MISSING_REQUIRED_FIELD,
+ ERROR_CODES.INVALID_FIELD_VALUE,
+ ERROR_CODES.NOT_FOUND,
+ ERROR_CODES.FILE_NOT_FOUND,
+ ERROR_CODES.PROCESS_NOT_FOUND,
+ ERROR_CODES.SESSION_NOT_FOUND,
+ ERROR_CODES.CONFLICT,
+ ERROR_CODES.VALIDATION_ERROR,
+ ]
+
+ if (nonRetryable4xxCodes.includes(errorObj.code as any)) {
+ return false
+ }
+
+ // Retry on timeout and server errors
+ const retryableCodes = [
+ ERROR_CODES.OPERATION_TIMEOUT,
+ ERROR_CODES.SESSION_TIMEOUT,
+ ERROR_CODES.INTERNAL_ERROR,
+ ERROR_CODES.SERVICE_UNAVAILABLE,
+ ERROR_CODES.SERVER_UNAVAILABLE,
+ ERROR_CODES.CONNECTION_FAILED,
+ ERROR_CODES.CONNECTION_TIMEOUT,
+ ]
+
+ if (retryableCodes.includes(errorObj.code as any)) {
+ return true
+ }
+ }
+
+ return (
+ isRetryableNetworkError(errorObj) || isRetryableHTTPStatus(errorObj) || isTimeoutError(errorObj)
+ )
+}
+
+/**
+ * Sleep/delay function
+ */
+function sleep(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms))
+}
+
+/**
+ * Batch operations with retry
+ *
+ * @example
+ * ```ts
+ * const results = await retryBatch(
+ * [task1, task2, task3],
+ * { maxRetries: 2 }
+ * )
+ * ```
+ */
+export async function retryBatch(
+ operations: Array<() => Promise>,
+ options: Partial = {}
+): Promise {
+ return Promise.all(operations.map(op => withRetry(op, options)))
+}
+
+/**
+ * Batch operations with retry (allows partial failures)
+ *
+ * @example
+ * ```ts
+ * const results = await retryBatchSettled(
+ * [task1, task2, task3],
+ * { maxRetries: 2 }
+ * )
+ * ```
+ */
+export async function retryBatchSettled