daytona-integration

Source: Daytona TypeScript SDK Docs

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "daytona-integration" with this command: npx skills add seanchiuai/multishot/seanchiuai-multishot-daytona-integration

Daytona Integration

Source: Daytona TypeScript SDK Docs

Installation

npm install @daytonaio/sdk

SDK Setup

import { Daytona } from '@daytonaio/sdk'

const daytona = new Daytona({ apiKey: process.env.DAYTONA_API_KEY, // Required for auth apiUrl: 'https://app.daytona.io/api', // Default API endpoint target: 'us', // Optional: region preference // Alternative auth: // jwtToken: 'jwt-token', // Requires organizationId // organizationId: 'org-id' })

DaytonaConfig Interface

Property Type Required Description

apiKey

string

No* API key authentication

apiUrl

string

No API endpoint (default: https://app.daytona.io/api )

jwtToken

string

No* JWT authentication (requires organizationId)

organizationId

string

No Required when using JWT

target

string

No Sandbox location preference

*One of apiKey or jwtToken required.

Daytona Class Methods

create()

Create a new sandbox.

const sandbox = await daytona.create({ language: 'typescript', // 'python' | 'typescript' | 'javascript' envVars: { // Environment variables CLAUDE_CODE_OAUTH_TOKEN: token, NODE_ENV: 'development' }, autoStopInterval: 15, // Minutes idle before stop (default: 15) autoArchiveInterval: 10080, // Minutes before archive (default: 7 days) autoDeleteInterval: 43200, // Minutes before deletion labels: { project: 'mvp' }, // Metadata tags ephemeral: false // Auto-cleanup on stop }, { timeout: 60 // Creation timeout in seconds })

Returns: Promise<Sandbox>

CreateSandboxParams

Property Type Default Description

language

'python' | 'typescript' | 'javascript'

'python'

Runtime language

envVars

Record<string, string>

{}

Environment variables

autoStopInterval

number

15

Idle minutes before stop

autoArchiveInterval

number

10080

Minutes before archive

autoDeleteInterval

number

Minutes before deletion

labels

Record<string, string>

Metadata tags

ephemeral

boolean

false

Auto-cleanup on stop

volumes

VolumeMount[]

Volume mounts

image

string | Image

Custom Docker image

resources

Resources

CPU/memory/disk allocation

snapshot

string

Create from snapshot ID

get()

Retrieve a sandbox by ID or name.

const sandbox = await daytona.get('sandbox-id-or-name')

Returns: Promise<Sandbox>

list()

List sandboxes with optional filtering.

const result = await daytona.list( { project: 'mvp' }, // Filter by labels 1, // Page number 10 // Items per page ) // result.sandboxes: Sandbox[] // result.total: number

Returns: Promise<PaginatedSandboxes>

findOne()

Find first sandbox matching filter.

const sandbox = await daytona.findOne({ id: 'sandbox-id', name: 'sandbox-name', labels: { project: 'mvp' } })

Returns: Promise<Sandbox>

delete()

Delete a sandbox permanently.

await daytona.delete(sandbox, 60) // sandbox object, timeout seconds

start() / stop()

await daytona.start(sandbox, 60) // Start and await ready, timeout seconds await daytona.stop(sandbox) // Stop execution

Sandbox Class

Properties

Property Type Description

id

string

Unique sandbox identifier

name

string

Sandbox name

state

SandboxState

'started' | 'stopped' | ...

cpu

number

CPU count

memory

number

Memory in GiB

disk

number

Disk space in GiB

gpu

number

GPU count

env

Record<string, string>

Environment variables

labels

Record<string, string>

Metadata

process

Process

Process execution interface

fs

FileSystem

File system interface

git

Git

Git operations interface

Methods

// Lifecycle await sandbox.start(timeout?) await sandbox.stop(timeout?) await sandbox.delete(timeout) await sandbox.archive() await sandbox.recover(timeout?)

// State await sandbox.refreshData() await sandbox.waitUntilStarted(timeout?) await sandbox.waitUntilStopped(timeout?)

// Configuration await sandbox.setLabels({ key: 'value' }) await sandbox.setAutostopInterval(minutes) await sandbox.setAutoDeleteInterval(minutes) await sandbox.setAutoArchiveInterval(minutes)

// Paths const workDir = await sandbox.getWorkDir() const homeDir = await sandbox.getUserHomeDir()

// Access const previewUrl = await sandbox.getPreviewLink(port) const signedUrl = await sandbox.getSignedPreviewUrl(port, expiresInSeconds?)

Process Class (sandbox.process)

executeCommand()

Execute shell commands.

const response = await sandbox.process.executeCommand( 'npm install', // command '/workspace', // cwd (optional) { NODE_ENV: 'prod' }, // env (optional) 300 // timeout in seconds (optional, 0=indefinite) )

Returns: Promise<ExecuteResponse>

CRITICAL WARNING - Background Processes: executeCommand() with timeout=0 does NOT run background servers properly. Despite documentation saying "0=indefinite", the Promise resolves immediately without spawning a tracked process. Use session-based execution for background processes instead (see Session-based Execution section below).

// ❌ WRONG - Server won't actually run await sandbox.process.executeCommand('npm run dev', '/workspace', undefined, 0)

// ✅ CORRECT - Use sessions for background processes await sandbox.process.createSession('preview-server') await sandbox.process.executeSessionCommand('preview-server', { command: 'npm run dev', async: true }, 0)

ExecuteResponse Interface

Property Type Description

exitCode

number

Process exit status

result

string

stdout content

artifacts

ExecutionArtifacts

Additional data (stdout, charts)

codeRun()

Execute code using appropriate runtime.

const response = await sandbox.process.codeRun( 'console.log("Hello")', // code { argv: [], env: {} }, // params (optional) 60 // timeout in seconds (optional) )

Session-based Execution (for streaming)

// Create a persistent session await sandbox.process.createSession('my-session')

// Execute with async log callbacks await sandbox.process.executeSessionCommand('my-session', { command: 'npm start', async: true }, 300)

// Get logs with streaming callbacks await sandbox.process.getSessionCommandLogs( 'my-session', 'command-id', (stdout: string) => { /* handle stdout chunk / }, (stderr: string) => { / handle stderr chunk */ } )

// Cleanup await sandbox.process.deleteSession('my-session')

PTY (Interactive Terminal)

// Create PTY session const pty = await sandbox.process.createPty({ cols: 80, rows: 24 })

// Connect via WebSocket await sandbox.process.connectPty(pty.sessionId, { onData: (data) => { /* terminal output / }, onExit: (code) => { / process exited */ } })

// Resize await sandbox.process.resizePtySession(pty.sessionId, 120, 40)

// Kill await sandbox.process.killPtySession(pty.sessionId)

File System (sandbox.fs)

// Download file (returns Buffer) const buffer = await sandbox.fs.downloadFile('/workspace/file.txt') const content = buffer.toString()

// Upload file (Buffer or local path) await sandbox.fs.uploadFile(Buffer.from('content'), '/workspace/file.txt') await sandbox.fs.uploadFile('/local/path.txt', '/workspace/file.txt')

// List directory (returns FileInfo[]) const files = await sandbox.fs.listFiles('/workspace') // files[i].name, files[i].size, files[i].isDir

// Create directory await sandbox.fs.createFolder('/workspace/new-dir', '755')

// Delete file/directory await sandbox.fs.deleteFile('/workspace/file.txt') await sandbox.fs.deleteFile('/workspace/dir', true) // recursive

// File details const info = await sandbox.fs.getFileDetails('/workspace/file.txt')

// Search const results = await sandbox.fs.searchFiles('/workspace', '*.ts') const matches = await sandbox.fs.findFiles('/workspace', 'pattern')

Parallel Sandbox Creation (4 agents)

const agentIds = ['agent-a', 'agent-b', 'agent-c', 'agent-d']

const sandboxes = await Promise.all( agentIds.map(async (agentId) => { const sandbox = await daytona.create({ language: 'typescript', envVars: { CLAUDE_CODE_OAUTH_TOKEN: token }, labels: { agentId }, autoStopInterval: 60 }) return { agentId, sandbox } }) )

File Transfer Patterns

Extract from sandbox (winner selection)

// Create tar in sandbox await sandbox.process.executeCommand( 'tar -czf /tmp/project.tar.gz -C /workspace .', undefined, undefined, 60 )

// Download const tarBuffer = await sandbox.fs.downloadFile('/tmp/project.tar.gz')

// Save locally import { writeFileSync } from 'fs' writeFileSync('~/.multishot/runs/run-id/winner.tar.gz', tarBuffer)

Inject into sandbox (next round)

import { readFileSync } from 'fs'

// Upload tar const tarBuffer = readFileSync('~/.multishot/runs/run-id/winner.tar.gz') await sandbox.fs.uploadFile(tarBuffer, '/tmp/project.tar.gz')

// Extract await sandbox.process.executeCommand( 'tar -xzf /tmp/project.tar.gz -C /workspace', undefined, undefined, 60 )

Error Handling

Network Error Retry Pattern

When executing commands that make API calls (Claude Code, npm installs, etc.), implement retry logic for transient network errors:

async function execWithRetry( command: string, maxRetries: number = 3 ): Promise<ExecuteResponse> { let lastError: Error | null = null

for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await sandbox.process.executeCommand( command, workDir, undefined, 900 )

  if (response.exitCode !== 0) {
    throw new Error(`Process exited with code ${response.exitCode}`)
  }

  return response // Success

} catch (err) {
  lastError = err instanceof Error ? err : new Error(String(err))

  // Check if error is retryable (network issues)
  const isRetryable =
    lastError.message.includes('ECONNRESET') ||
    lastError.message.includes('ETIMEDOUT') ||
    lastError.message.includes('ENOTFOUND') ||
    lastError.message.includes('EAI_AGAIN') ||
    lastError.message.includes('socket hang up')

  if (!isRetryable) {
    throw lastError // Non-network error - fail immediately
  }

  if (attempt &#x3C; maxRetries) {
    // Exponential backoff: 5s, 10s, 20s
    const delayMs = 5000 * Math.pow(2, attempt - 1)
    console.warn(`Retry ${attempt}/${maxRetries} in ${delayMs / 1000}s...`)
    await new Promise(resolve => setTimeout(resolve, delayMs))
  } else {
    throw new Error(`Network error after ${maxRetries} retries: ${lastError.message}`)
  }
}

}

throw lastError || new Error('Unknown error') }

Retryable errors:

  • ECONNRESET

  • Connection forcibly closed

  • ETIMEDOUT

  • Connection timed out

  • ENOTFOUND

  • DNS lookup failed

  • EAI_AGAIN

  • Temporary DNS failure

  • socket hang up

  • Connection dropped

Non-retryable errors (fail immediately):

  • Authentication failures

  • Invalid commands

  • Permission errors

  • Validation errors

Benefits:

  • Prevents sandbox waste from transient network drops

  • Critical for parallel agent execution (4 agents = higher network failure chance)

  • Exponential backoff prevents overwhelming failing services

Sandbox Creation

try { const sandbox = await daytona.create({ language: 'typescript' }) } catch (error) { if (error.code === 'SANDBOX_CREATION_FAILED') { // Retry once await new Promise(r => setTimeout(r, 2000)) const sandbox = await daytona.create({ language: 'typescript' }) } }

// Always cleanup in finally try { // ... work with sandbox } finally { await sandbox.delete(30).catch(console.error) }

MVP Implementation Notes

Current implementation uses executeCommand() with retry logic (not session-based streaming):

// SandboxManager.execClaudeCommand() pattern with retries for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await sandbox.process.executeCommand( command, workDir, undefined, 900 // 15 minute timeout ) if (response.result) onStdout?.(response.result) return // Success } catch (err) { // Retry logic for ECONNRESET, ETIMEDOUT, etc. // Exponential backoff: 5s, 10s, 20s } }

Preview servers use session-based execution for background processes:

await sandbox.process.createSession(sessionId) await sandbox.process.executeSessionCommand(sessionId, { command: config.command, async: true }, 0)

Preview URL retrieval uses signed URLs:

const preview = await sandbox.getSignedPreviewUrl(port, 3600) return preview.url

Preview Optimization Patterns

Config Caching

Reduce filesystem operations by caching project type detection results:

class SandboxManager { private previewConfigs: Map<string, PreviewConfig> = new Map()

async detectProjectType(agentId: string): Promise<PreviewConfig> { // Check cache first const cached = this.previewConfigs.get(agentId) if (cached) { console.log(Using cached preview config: ${cached.type}) return cached }

// Detect project type (filesystem operations)
const config = await this.performDetection(agentId)

// Cache result
this.previewConfigs.set(agentId, config)
return config

}

clearPreviewCache(): void { this.previewConfigs.clear() }

async destroySandbox(agentId: string): Promise<void> { // Clear cached config when sandbox destroyed this.previewConfigs.delete(agentId) // ... destroy sandbox } }

Benefits:

  • Repeated preview requests reuse cached config (no filesystem access)

  • Cleared on new run to detect fresh project structure

  • Cleared per-agent on sandbox destruction

Session Reuse

Prevent duplicate preview servers by reusing existing sessions:

interface SandboxInfo { sandbox: Sandbox agentId: string previewSessionId?: string }

async startPreviewServer( agentId: string, config: PreviewConfig ): Promise<void> { const info = this.sandboxes.get(agentId) const sessionId = preview-${agentId}

// Reuse existing session if available if (info.previewSessionId === sessionId) { console.log(Reusing existing preview session: ${sessionId}) return }

// Create new session info.previewSessionId = sessionId await sandbox.process.createSession(sessionId) await sandbox.process.executeSessionCommand(sessionId, { command: config.command, async: true }, 0) }

Benefits:

  • "Preview All" followed by single-agent preview reuses servers

  • Single-agent preview can be clicked multiple times without restart

  • Reduces server startup latency on repeated previews

Server Health Check

Poll server readiness instead of blind sleep:

async waitForServerReady( agentId: string, port: number, maxAttempts: number = 30, intervalMs: number = 1000 ): Promise<boolean> { for (let i = 0; i < maxAttempts; i++) { try { const response = await sandbox.process.executeCommand( curl -f -s -o /dev/null -w "%{http_code}" http://localhost:${port} || echo "000", '/workspace', undefined, 5 )

  const httpCode = response.result.trim()

  // Any HTTP response (200, 404, etc.) indicates server is ready
  if (httpCode !== "000" &#x26;&#x26; httpCode !== "") {
    console.log(`Server ready on port ${port} (HTTP ${httpCode})`)
    return true
  }
} catch {}

await new Promise(r => setTimeout(r, intervalMs))

}

console.warn(Server not ready after ${maxAttempts} attempts) return false }

Usage:

await startPreviewServer(agentId, config) const isReady = await waitForServerReady(agentId, config.port, 30, 1000) if (!isReady) { throw new Error(Server failed to start on port ${config.port}) } const url = await getPreviewUrl(agentId, config.port)

CLI Installation Pattern

Robust CLI installation with verification, fallback, and retry logic:

async installClaudeCLI(agentId: string, maxRetries: number = 3): Promise<void> { const installCmd = 'bash -c "set -o pipefail; curl -fsSL https://claude.ai/install.sh | bash"' let lastError: Error | null = null

for (let attempt = 1; attempt <= maxRetries; attempt++) { try { // Try curl install first const response = await sandbox.process.executeCommand(installCmd) if (response.exitCode !== 0) { throw new Error(Install failed: ${response.result}) }

  // Verify installation
  const verifyResponse = await sandbox.process.executeCommand(
    'claude --version', undefined, undefined, 30
  )
  if (verifyResponse.exitCode !== 0) {
    throw new Error('Claude CLI not found after installation')
  }
  return // Success

} catch (err) {
  // Fallback: try npm install
  try {
    const response = await sandbox.process.executeCommand(
      'npm install -g @anthropic-ai/claude-code',
      undefined, undefined,
      300 // 5 min timeout for npm
    )
    if (response.exitCode !== 0) {
      throw new Error(`NPM install failed: ${response.result}`)
    }

    const verifyResponse = await sandbox.process.executeCommand(
      'claude --version', undefined, undefined, 30
    )
    if (verifyResponse.exitCode !== 0) {
      throw new Error('Claude CLI not found after npm installation')
    }
    return // Success

  } catch (npmErr) {
    lastError = npmErr instanceof Error ? npmErr : new Error(String(npmErr))
    const errorMsg = lastError.message

    // Check if retryable (network errors)
    const isRetryable =
      errorMsg.includes('ETIMEDOUT') ||
      errorMsg.includes('ECONNRESET') ||
      errorMsg.includes('ENOTFOUND') ||
      errorMsg.includes('EAI_AGAIN') ||
      errorMsg.includes('socket hang up')

    if (!isRetryable || attempt >= maxRetries) {
      throw lastError
    }

    // Exponential backoff: 5s, 10s, 20s
    const delayMs = 5000 * Math.pow(2, attempt - 1)
    await new Promise(r => setTimeout(r, delayMs))
  }
}

} throw lastError || new Error('Installation failed') }

Key points:

  • Use set -o pipefail in bash to propagate curl failures

  • Verify CLI is executable with --version check after installation

  • Set timeout on npm install (300s recommended) - network may be slow

  • Retry up to 3 times on network errors (ETIMEDOUT, ECONNRESET, etc.)

  • Exponential backoff prevents overwhelming failing services

  • Daytona sandbox networking may need time to initialize after creation

Best Practices

  • Timeouts: Set appropriate timeouts for long-running commands (15 min for Claude)

  • Cleanup: Always destroy sandboxes in finally blocks

  • Parallel creation: Use Promise.all() for creating multiple sandboxes

  • Session streaming: Use session-based execution for real-time output (not in MVP)

  • Ephemeral mode: Set ephemeral: true for auto-cleanup scenarios

  • CLI verification: Always verify CLI tools are installed after installation commands

  • Error propagation: Throw exceptions on non-zero exit codes to ensure proper error handling

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

claude-code-cli

No summary provided by upstream source.

Repository SourceNeeds Review
General

terminal-streaming

No summary provided by upstream source.

Repository SourceNeeds Review
General

electron-ipc

No summary provided by upstream source.

Repository SourceNeeds Review
General

auth-flow

No summary provided by upstream source.

Repository SourceNeeds Review