npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@codivstack/sdk

v2.0.0

Published

Official TypeScript/JavaScript SDK for CodivStack API - Self-hosted BYOC sandbox platform on AWS ECS Fargate

Downloads

366

Readme

@codivstack/sdk

Official TypeScript/JavaScript SDK for CodivStack API. Manage sandboxes, check health, monitor your development environments, and manage workspace files with ease.

Installation

npm install @codivstack/sdk

Quick Start

Basic Usage

import { Sandbox, Health } from '@codivstack/sdk';

// Set your API key as an environment variable
// export CODIVSTACK_API_KEY='your-api-key'

// Check API health
const isHealthy = await Health.check();
console.log('API is healthy:', isHealthy);

// List all sandboxes
const { sandboxes, total } = await Sandbox.list();
console.log(`You have ${total} sandboxes`);

// Create a new sandbox - returns SandboxInstance with all properties
const sandbox = await Sandbox.create({
  size: 'standard',  // One of 11 sizes: nano to massive
  name: 'my-sandbox'
});

// Access all response properties directly
console.log('Sandbox ID:', sandbox.sandboxId);   // "sandbox-abc123"
console.log('Status:', sandbox.status);          // "running"
console.log('Size:', sandbox.size);              // "standard"
console.log('Name:', sandbox.displayName);       // "my-sandbox"
console.log('Message:', sandbox.message);        // "Sandbox created successfully"
console.log('Resources:', sandbox.resources);    // { cpu: "1024", memory: "4096" }

// Write files to sandbox workspace
await sandbox.files.write('/index.js', 'console.log("Hello World");');

// Read files from sandbox
const content = await sandbox.files.read('/index.js');
console.log('File content:', content);

// Run a command
const result = await sandbox.commands.run('node index.js');
console.log('Output:', result.stdout);
console.log('Exit code:', result.exitCode);

// Start dev server in background
await sandbox.commands.startBackground('npm run dev -- --host 0.0.0.0');

// Get current status with metrics
const status = await sandbox.getStatus();
console.log('Status:', status.status);           // "running"
console.log('CPU:', status.metrics?.cpuPercent); // 25.5

// Stop sandbox - returns full response
const stopResult = await sandbox.stop();
console.log('Stop status:', stopResult.status);   // "stopped"
console.log('Message:', stopResult.message);      // "Sandbox stopped successfully"

// Start again - returns full response
const startResult = await sandbox.start();
console.log('Start status:', startResult.status);   // "running"
console.log('Message:', startResult.message);       // "Sandbox started successfully"

Complete Example

import { Sandbox, Health, CodivStackError } from '@codivstack/sdk';

async function manageSandbox() {
  try {
    // Check if API is available
    const isHealthy = await Health.check();
    if (!isHealthy) {
      console.error('API is not available');
      return;
    }

    // List existing sandboxes
    const { sandboxes } = await Sandbox.list();
    console.log(`Found ${sandboxes.length} existing sandboxes`);

    let sandbox;

    if (sandboxes.length > 0 && sandboxes[0].status === 'running') {
      // Use existing running sandbox
      console.log(`Using existing sandbox: ${sandboxes[0].id}`);
      sandbox = await Sandbox.get(sandboxes[0].id);
    } else {
      // Create a new sandbox if none exist
      sandbox = await Sandbox.create({
        size: 'medium',
        name: 'development-sandbox'
      });
      console.log(`Created new sandbox: ${sandbox.sandboxId}`);
    }

    // Write some files to the sandbox
    await sandbox.files.write('/app.js', 'console.log("Hello from CodivStack!");');
    await sandbox.files.write('/package.json', JSON.stringify({
      name: 'my-app',
      version: '1.0.0',
      main: 'app.js'
    }, null, 2));

    // List files in the workspace
    const files = await sandbox.files.list('/');
    console.log('Workspace files:', files.map(f => f.name));

    // Read a file
    const appContent = await sandbox.files.read('/app.js');
    console.log('app.js content:', appContent);

    // Check status
    const status = await sandbox.getStatus();
    console.log(`Sandbox status: ${status.status}`);

    // Get metrics if running
    if (status.status === 'running') {
      const metrics = await sandbox.getMetrics();
      console.log('Metrics:', metrics);
    }

    // Stop the sandbox when done
    await sandbox.stop();
    console.log('Sandbox stopped');

  } catch (error) {
    if (error instanceof CodivStackError) {
      console.error(`API Error (${error.statusCode}):`, error.message);
    } else {
      console.error('Unexpected error:', error);
    }
  }
}

manageSandbox();

Configuration

The SDK reads configuration from environment variables by default:

  • CODIVSTACK_API_KEY - Your API key (required for authenticated requests)
  • CODIVSTACK_API_URL - Base URL (defaults to https://api.codivstack.dev)

Single configure() call configures everything:

import { configure, Sandbox, AgentClient } from '@codivstack/sdk';

// One config for ALL SDK features (Sandbox, Agent, Health, Admin)
configure({
  apiKey: 'your-api-key',
  baseUrl: 'https://api.codivstack.dev'
});

// Create Sandbox (uses global config)
const sandbox = await Sandbox.create({ size: 'standard' });

// Create AgentClient (uses global config automatically)
const agent = new AgentClient();

// Use Agent with the sandbox
for await (const event of agent.chat({
  sandboxId: sandbox.sandboxId,
  message: 'Create a React app'
})) {
  if (event.type === 'chunk') {
    process.stdout.write(event.text);
  }
}

You can also use environment variables without calling configure():

export CODIVSTACK_API_KEY="your-api-key"
export CODIVSTACK_API_URL="https://api.codivstack.dev"

API Reference

Sandbox

Sandbox.list()

List all sandboxes for the current user.

const { sandboxes, total } = await Sandbox.list();

// Response structure:
// {
//   sandboxes: [
//     { id, name, status, size, user_id, workspace_id, task_arn, private_ip, ... }
//   ],
//   total: 5
// }

console.log(`Total: ${total} sandboxes`);
sandboxes.forEach(s => {
  console.log(`${s.id}: ${s.status} (${s.size})`);
});

Returns: Promise<SandboxListResponse>

Sandbox.create(input)

Create a new sandbox with file management capabilities. Uses warm pool for fast ~2 second startup.

const sandbox = await Sandbox.create({
  size: 'standard',           // Optional: One of 11 sizes (defaults to 'nano')
  name: 'my-sandbox',         // Optional: Custom name
  max_runtime_minutes: 120,   // Optional: Custom runtime limit (defaults to plan limit)
  environment: {              // Optional: Environment variables
    NODE_ENV: 'production'
  }
});

// Response properties available directly on sandbox:
console.log(sandbox.sandboxId);     // "sandbox-abc123"
console.log(sandbox.status);        // "running"
console.log(sandbox.size);          // "standard"
console.log(sandbox.displayName);   // "my-sandbox"
console.log(sandbox.message);       // "Sandbox created successfully"
console.log(sandbox.resources);     // { cpu: "1024", memory: "4096" }

// Also includes file and command APIs
await sandbox.files.write('/index.js', 'console.log("Hello");');
await sandbox.commands.run('node index.js');

Returns: Promise<SandboxInstance>

Sandbox.get(sandboxId)

Get an existing sandbox instance with file management capabilities.

const sandbox = await Sandbox.get('sandbox-abc123');

// Same properties as create:
console.log(sandbox.sandboxId);   // "sandbox-abc123"
console.log(sandbox.status);      // "running"
console.log(sandbox.size);        // "standard"

// Use files and commands
await sandbox.files.write('/app.js', 'console.log("App");');
await sandbox.commands.run('node app.js');

Returns: Promise<SandboxInstance>

Sandbox.getStatus(sandboxId)

Get detailed status and metrics for a sandbox.

const status = await Sandbox.getStatus('sandbox-abc123');

// Response structure:
console.log(status.sandboxId);               // "sandbox-abc123"
console.log(status.status);                  // "running" | "stopped" | "pending" | ...
console.log(status.name);                    // "my-sandbox"
console.log(status.size);                    // "standard"
console.log(status.cpu);                     // "1024"
console.log(status.memory);                  // "4096"
console.log(status.createdAt);               // "2024-01-15T10:30:00Z"
console.log(status.startedAt);               // "2024-01-15T10:30:05Z"
console.log(status.maxRuntimeMinutes);       // 60
console.log(status.effectiveRuntimeMinutes); // 60

// Metrics (when running)
if (status.metrics) {
  console.log(status.metrics.cpuPercent);      // 25.5
  console.log(status.metrics.memoryPercent);   // 45.2
  console.log(status.metrics.networkRxBytes);  // 1024
  console.log(status.metrics.networkTxBytes);  // 512
}

Returns: Promise<SandboxStatusResponse>

Sandbox.start(sandboxId)

Start a stopped sandbox.

const result = await Sandbox.start('sandbox-abc123');

// Response structure:
console.log(result.sandboxId);    // "sandbox-abc123"
console.log(result.status);       // "running"
console.log(result.message);      // "Sandbox started successfully"
console.log(result.size);         // "standard"
console.log(result.displayName);  // "my-sandbox"
console.log(result.resources);    // { cpu: "1024", memory: "4096" }

Returns: Promise<SandboxActionResponse>

Sandbox.stop(sandboxId)

Stop a running sandbox.

const result = await Sandbox.stop('sandbox-abc123');

// Response structure:
console.log(result.sandboxId);  // "sandbox-abc123"
console.log(result.status);     // "stopped"
console.log(result.message);    // "Sandbox stopped successfully"

Returns: Promise<SandboxActionResponse>

Sandbox.delete(sandboxId)

Permanently delete a sandbox.

const result = await Sandbox.delete('sandbox-abc123');

// Response structure:
console.log(result.sandboxId);  // "sandbox-abc123"
console.log(result.status);     // "deleted"
console.log(result.message);    // "Sandbox deleted successfully"

Returns: Promise<SandboxActionResponse>

Sandbox.getMetrics(sandboxId)

Get real-time metrics for a running sandbox.

const metrics = await Sandbox.getMetrics('sandbox-abc123');

// Response structure:
console.log(metrics.sandboxId);               // "sandbox-abc123"
console.log(metrics.name);                    // "my-sandbox"
console.log(metrics.size);                    // "standard"

// Detailed metrics
console.log(metrics.metrics.cpuUtilized);     // 256 (mCPU)
console.log(metrics.metrics.cpuReserved);     // 1024 (mCPU)
console.log(metrics.metrics.cpuPercent);      // 25.0
console.log(metrics.metrics.cpuCores);        // 1

console.log(metrics.metrics.memoryUtilized);  // 1843 (MB)
console.log(metrics.metrics.memoryReserved);  // 4096 (MB)
console.log(metrics.metrics.memoryPercent);   // 45.0

console.log(metrics.metrics.networkRxBytes);  // 1024 (bytes/sec)
console.log(metrics.metrics.networkTxBytes);  // 512 (bytes/sec)
console.log(metrics.metrics.timestamp);       // "2024-01-15T10:35:00Z"

Returns: Promise<SandboxMetricsResponse>

Health

Health.check()

Check if the CodivStack API is healthy.

const isHealthy = await Health.check();

Returns: Promise<boolean>

SandboxInstance

When you create or get a sandbox, you receive a SandboxInstance object with properties and methods.

Properties

const sandbox = await Sandbox.create({ size: 'medium', name: 'dev' });

// Available properties:
sandbox.sandboxId     // string - "sandbox-abc123"
sandbox.status        // string - "running" | "stopped" | ...
sandbox.size          // string - "medium"
sandbox.displayName   // string - "dev"
sandbox.message       // string - "Sandbox created successfully"
sandbox.resources     // { cpu: string, memory: string }
sandbox.createdAt     // string - "2024-01-15T10:30:00Z"
sandbox.startedAt     // string - "2024-01-15T10:30:05Z"

// URL properties:
sandbox.wssUrl        // string - "wss://30001-sandbox-abc123.codivstack.dev/terminal"

// Sub-APIs:
sandbox.files         // SandboxFiles - File management
sandbox.commands      // SandboxCommands - Command execution

Security Note: Internal infrastructure details (workspaceId, taskArn, privateIp, image) are intentionally hidden from SDK responses for security.

sandbox.getPreviewUrl(port)

Get the public preview URL for your application running on a specific port.

const sandbox = await Sandbox.create({ size: 'standard' });

// Start Vite dev server in background
await sandbox.commands.run('npm run dev -- --host 0.0.0.0', { background: true });

// Get preview URL for different ports:
sandbox.getPreviewUrl(5173);  // https://5173-sandbox-abc123.codivstack.dev (Vite)
sandbox.getPreviewUrl(3000);  // https://3000-sandbox-abc123.codivstack.dev (React/Next.js)
sandbox.getPreviewUrl(8080);  // https://8080-sandbox-abc123.codivstack.dev (Custom)

// Default port is 3000:
sandbox.getPreviewUrl();      // https://3000-sandbox-abc123.codivstack.dev

Returns: string

Terminal WebSocket URL

The wssUrl property provides direct WebSocket connection to the sandbox terminal (port 30001):

const sandbox = await Sandbox.create({ size: 'standard' });

// Connect to terminal via WebSocket
const ws = new WebSocket(sandbox.wssUrl);
// wss://30001-sandbox-abc123.codivstack.dev/terminal

ws.onopen = () => {
  console.log('Terminal connected');
  ws.send('ls -la\n');  // Send commands
};

ws.onmessage = (event) => {
  console.log('Output:', event.data);  // Receive terminal output
};

Console API (Read-only Workflow Output)

The Console API provides a way to run dev servers and stream their output without allowing user input. Perfect for showing npm run dev output in a read-only pane.

Console uses the same PTY format as Terminal - you can use the same <Terminal /> component for both! The only difference is Console ignores all input (read-only).

Console vs Terminal

| Feature | Console | Terminal | |---------|---------|----------| | Purpose | Workflow output display | Interactive shell | | Input | ❌ Ignored (read-only) | ✅ Full input | | Data Format | Raw PTY stream | Raw PTY stream | | Component | <Terminal /> | <Terminal /> | | Auto-kill on disconnect | ✅ Yes | ✅ Yes | | URL Property | sandbox.console.url | sandbox.wssUrl |

sandbox.console.start(options?)

Start a workflow and stream its output.

const sandbox = await Sandbox.create({ size: 'standard' });

// Start default dev server (npm run dev -- --host 0.0.0.0)
await sandbox.console.start();

// Start custom command
await sandbox.console.start({ command: 'npm run build:watch' });

// Start in specific directory
await sandbox.console.start({
  command: 'npm run dev',
  workdir: '/home/developer/workspace/frontend'
});

Returns:

{
  success: true,
  status: 'running',
  pid: 12345,
  command: 'npm run dev -- --host 0.0.0.0',
  message: 'Console started'
}

sandbox.console.stop(options?)

Stop the running workflow.

// Graceful stop (SIGTERM - like Ctrl+C)
await sandbox.console.stop();

// Force kill (SIGKILL)
await sandbox.console.stop({ signal: 'SIGKILL' });

// Graceful with force fallback after 3 seconds
await sandbox.console.stop({ force: true });

Returns:

{
  success: true,
  message: 'Stop signal sent',
  pid: 12345,
  signal: 'SIGTERM'
}

sandbox.console.status()

Get current console status.

const status = await sandbox.console.status();

console.log(status.status);      // 'running' | 'stopped' | 'starting' | 'stopping'
console.log(status.command);     // 'npm run dev -- --host 0.0.0.0'
console.log(status.pid);         // 12345
console.log(status.bufferSize);  // Number of buffered lines
console.log(status.listeners);   // Number of WebSocket connections

Console WebSocket URL

The console.url property provides the WebSocket URL for read-only output streaming.

Recommended: Use the Terminal component (same component works for both Terminal and Console):

import { Terminal } from '@codivstack/sdk/react';

// Terminal (interactive)
<Terminal wsUrl={sandbox.wssUrl} />

// Console (read-only) - same component!
<Terminal wsUrl={sandbox.console.url} />

Or connect manually with xterm.js:

import { Terminal } from 'xterm';

const sandbox = await Sandbox.create({ size: 'standard' });

// Start the workflow
await sandbox.console.start();

// Connect to console output (raw PTY data, not JSON!)
const ws = new WebSocket(sandbox.console.url);
// wss://30001-sandbox-abc123.codivstack.dev/console

const terminal = new Terminal();
terminal.open(document.getElementById('console'));

ws.onmessage = (event) => {
  // Raw PTY data with ANSI colors - write directly to xterm
  terminal.write(event.data);
};

// Process auto-stops when WebSocket disconnects (page refresh, close tab, etc.)

Complete Console Example

import { Sandbox } from '@codivstack/sdk';
import { Terminal } from '@codivstack/sdk/react';

// React component example
function DevServerConsole() {
  const [sandbox, setSandbox] = useState<SandboxInstance | null>(null);
  const [consoleUrl, setConsoleUrl] = useState<string>('');

  useEffect(() => {
    async function init() {
      // Create sandbox
      const sb = await Sandbox.create({ size: 'standard' });
      setSandbox(sb);
      
      // Write project files
      await sb.files.write('/package.json', JSON.stringify({
        scripts: { dev: 'vite' },
        dependencies: { vite: '^5.0.0' }
      }));
      
      // Install dependencies
      await sb.commands.run('npm install', { timeout: 120000 });
      
      // Start dev server via console
      await sb.console.start({ command: 'npm run dev -- --host 0.0.0.0' });
      
      // Set console URL for Terminal component
      setConsoleUrl(sb.console.url);
    }
    init();
  }, []);

  if (!consoleUrl) return <div>Loading...</div>;

  return (
    <div>
      <h2>Dev Server Output (Read-only)</h2>
      <Terminal wsUrl={consoleUrl} />
      {sandbox && (
        <a href={sandbox.getPreviewUrl(5173)} target="_blank">
          Open Preview
        </a>
      )}
    </div>
  );
}

Note: Process automatically stops when WebSocket disconnects (page refresh, close tab, navigate away). Manual sandbox.console.stop() is optional.

Real-time File System (FsClient)

The FsClient provides WebSocket-based real-time file system access with live change notifications. Perfect for building IDE-like experiences where you need to react to file changes instantly.

Files API vs FsClient

| Feature | Files API (HTTP) | FsClient (WebSocket) | |---------|------------------|----------------------| | Protocol | HTTP REST | WebSocket | | Real-time events | ❌ No | ✅ Yes (add, change, delete) | | Best for | Simple read/write | IDE, file explorers | | Connection | Per-request | Persistent | | Binary files | ❌ No | ✅ Yes (base64) |

sandbox.connectFs(options?)

Connect to the real-time file system WebSocket.

const sandbox = await Sandbox.create({ size: 'standard' });

// Connect with event handler
const fs = await sandbox.connectFs({
  onEvent: (event) => {
    console.log(`File ${event.event}: ${event.path}`);
    // event.event: 'add' | 'change' | 'unlink' | 'unlinkDir' | 'addDir'
  },
  onReady: () => console.log('FS connected'),
  onClose: () => console.log('FS disconnected'),
  onError: (err) => console.error('FS error:', err),
  timeout: 30000  // Connection timeout (default: 30s)
});

Returns: Promise<FsClient>

fs.listDir(path)

List files and directories.

const entries = await fs.listDir('/src');
// Returns: FsEntry[] with name, kind ('file' | 'dir'), size, modified

fs.readFile(path, options?)

Read file content (text or binary).

// Auto-detect binary based on extension
const result = await fs.readFile('/package.json');
console.log(result.content);  // File content
console.log(result.binary);   // false
console.log(result.size);     // File size in bytes

// Force binary mode
const binary = await fs.readFile('/image.png', { binary: true });

fs.readTextFile(path) / fs.readBinaryFile(path)

Convenience methods for reading files.

// Read text file directly
const content = await fs.readTextFile('/app.js');

// Read binary file as Uint8Array
const imageData = await fs.readBinaryFile('/logo.png');

fs.writeFile(path, content, options?)

Write content to a file.

// Write text file
await fs.writeFile('/app.js', 'console.log("Hello");');

// Write binary file (base64 encoded)
await fs.writeFile('/output.png', base64Content, { binary: true });

fs.writeTextFile(path, content) / fs.writeBinaryFile(path, data)

Convenience methods for writing files.

// Write text file
await fs.writeTextFile('/config.json', '{"port": 3000}');

// Write binary file from Uint8Array
await fs.writeBinaryFile('/output.png', imageData);

fs.delete(path)

Delete a file or directory (recursive for directories).

await fs.delete('/old-file.js');
await fs.delete('/temp-folder');  // Deletes entire folder

fs.mkdir(path)

Create a directory (recursive).

await fs.mkdir('/src/components/ui');

fs.rename(path, newPath)

Move or rename a file or directory.

await fs.rename('/old-name.js', '/new-name.js');
await fs.rename('/folder', '/renamed-folder');

fs.stat(path)

Get file or directory stats.

const stat = await fs.stat('/app.js');
console.log(stat.isFile);      // true
console.log(stat.isDirectory); // false
console.log(stat.size);        // 1234
console.log(stat.modified);    // "2024-01-15T10:30:00Z"
console.log(stat.created);     // "2024-01-10T08:00:00Z"

fs.getFullTree(path?)

Get the complete file tree (recursive).

const tree = await fs.getFullTree('/src');
// Returns: FsTreeNode[]
// [
//   {
//     name: 'components',
//     path: 'src/components',
//     kind: 'dir',
//     children: [
//       { name: 'Button.tsx', path: 'src/components/Button.tsx', kind: 'file', size: 1234 }
//     ]
//   }
// ]

Note: node_modules, .git, dist, build, .cache, coverage are automatically excluded.

fs.onEvent(callback)

Subscribe to file system events.

// Subscribe to events
const unsubscribe = fs.onEvent((event) => {
  switch (event.event) {
    case 'add':
      console.log(`File created: ${event.path}`);
      break;
    case 'change':
      console.log(`File modified: ${event.path}`);
      break;
    case 'unlink':
      console.log(`File deleted: ${event.path}`);
      break;
    case 'addDir':
      console.log(`Directory created: ${event.path}`);
      break;
    case 'unlinkDir':
      console.log(`Directory deleted: ${event.path}`);
      break;
  }
});

// Later: unsubscribe
unsubscribe();

fs.disconnect()

Disconnect from the WebSocket.

fs.disconnect();
// or
sandbox.disconnectFs();

Complete FsClient Example

import { Sandbox, FsClient } from '@codivstack/sdk';

async function buildFileExplorer() {
  const sandbox = await Sandbox.create({ size: 'standard' });
  
  // Connect to real-time FS
  const fs = await sandbox.connectFs({
    onEvent: (event) => {
      // Update UI when files change
      console.log(`[${event.event}] ${event.path}`);
      refreshFileTree();
    }
  });
  
  // Get initial file tree
  const tree = await fs.getFullTree();
  renderTree(tree);
  
  // Create a new file
  await fs.writeTextFile('/src/NewComponent.tsx', `
    export function NewComponent() {
      return <div>Hello!</div>;
    }
  `);
  // onEvent will fire with { event: 'add', path: 'src/NewComponent.tsx' }
  
  // Read file content
  const content = await fs.readTextFile('/src/NewComponent.tsx');
  showInEditor(content);
  
  // Handle binary files (images, etc.)
  const imageData = await fs.readBinaryFile('/public/logo.png');
  displayImage(imageData);
  
  // Cleanup on unmount
  return () => fs.disconnect();
}

FsClient Properties

const fs = await sandbox.connectFs();

fs.connected    // boolean - Is WebSocket connected?
fs.wsUrl        // string - WebSocket URL

Binary File Support

FsClient automatically detects binary files by extension and uses base64 encoding:

Supported binary extensions:

  • Images: .png, .jpg, .jpeg, .gif, .webp, .ico, .bmp, .svg
  • Documents: .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx
  • Archives: .zip, .tar, .gz, .rar, .7z
  • Media: .mp3, .mp4, .wav, .avi, .mov, .webm, .ogg
  • Fonts: .woff, .woff2, .ttf, .otf, .eot
  • Other: .exe, .dll, .so, .dylib, .bin, .dat, .db, .sqlite
import { isBinaryFile, BINARY_EXTENSIONS } from '@codivstack/sdk';

// Check if a file is binary
isBinaryFile('/image.png');  // true
isBinaryFile('/app.js');     // false

// Get all binary extensions
console.log(BINARY_EXTENSIONS);  // Set of extensions

sandbox.start()

Start this sandbox (returns full response).

const result = await sandbox.start();

console.log(result.sandboxId);    // "sandbox-abc123"
console.log(result.status);       // "running"
console.log(result.message);      // "Sandbox started successfully"
console.log(result.size);         // "medium"

// Instance properties are also updated:
console.log(sandbox.status);      // "running"

Returns: Promise<SandboxActionResponse>

sandbox.stop()

Stop this sandbox (returns full response).

const result = await sandbox.stop();

console.log(result.sandboxId);  // "sandbox-abc123"
console.log(result.status);     // "stopped"
console.log(result.message);    // "Sandbox stopped successfully"

// Instance property is also updated:
console.log(sandbox.status);    // "stopped"

Returns: Promise<SandboxActionResponse>

sandbox.getStatus()

Get current status and metrics.

const status = await sandbox.getStatus();

console.log(status.sandboxId);             // "sandbox-abc123"
console.log(status.status);                // "running"
console.log(status.metrics?.cpuPercent);   // 25.5
console.log(status.metrics?.memoryPercent);// 45.2

// Instance property is also updated:
console.log(sandbox.status);               // "running"

Returns: Promise<SandboxStatusResponse>

sandbox.getMetrics()

Get real-time metrics.

const metrics = await sandbox.getMetrics();

console.log(metrics.metrics.cpuPercent);     // 25.0
console.log(metrics.metrics.memoryPercent);  // 45.0
console.log(metrics.metrics.networkRxBytes); // 1024

Returns: Promise<SandboxMetricsResponse>

Language Server Protocol (LSP) Client

The LspClient provides WebSocket-based Language Server Protocol support for TypeScript/JavaScript. Perfect for building IDE-like experiences with autocompletion, hover info, diagnostics, and go-to-definition.

LSP Features

| Feature | Method | Description | |---------|--------|-------------| | Autocompletion | completion() | Get code completions at cursor position | | Hover | hover() | Get type info and documentation on hover | | Go to Definition | definition() | Jump to where a symbol is defined | | Find References | references() | Find all usages of a symbol | | Diagnostics | onDiagnostics | Real-time error and warning notifications | | Signature Help | signatureHelp() | Function parameter hints | | Document Symbols | documentSymbols() | List all symbols in a file |

sandbox.connectLsp(options?)

Connect to the Language Server Protocol WebSocket.

const sandbox = await Sandbox.create({ size: 'standard' });

// Connect with diagnostics handler
const lsp = await sandbox.connectLsp({
  onDiagnostics: (uri, diagnostics) => {
    diagnostics.forEach(d => {
      const severity = d.severity === 1 ? 'Error' : d.severity === 2 ? 'Warning' : 'Info';
      console.log(`${severity} at line ${d.range.start.line}: ${d.message}`);
    });
  },
  onReady: () => console.log('LSP connected'),
  onClose: () => console.log('LSP disconnected'),
  onError: (err) => console.error('LSP error:', err),
  timeout: 30000  // Connection timeout (default: 30s)
});

Returns: Promise<LspClient> (already initialized)

lsp.didOpen(path, content, languageId?)

Open a document for editing. Must be called before getting completions/hover for a file.

// Open a TypeScript file
lsp.didOpen('/src/app.ts', fileContent, 'typescript');

// Open a JavaScript file
lsp.didOpen('/src/utils.js', fileContent, 'javascript');

// Default languageId is 'typescript'
lsp.didOpen('/src/main.ts', fileContent);

lsp.didChange(path, content)

Notify that a document has changed.

// When user edits the file
lsp.didChange('/src/app.ts', newContent);

lsp.didClose(path)

Close a document when no longer editing.

lsp.didClose('/src/app.ts');

lsp.completion(path, line, character)

Get autocompletion suggestions at a position.

// Get completions at line 10, character 15 (0-indexed)
const completions = await lsp.completion('/src/app.ts', 10, 15);

completions.forEach(item => {
  console.log(`${item.label} (${item.kind}): ${item.detail || ''}`);
  // Example: "useState (Function): Hook for state management"
});

Returns: Promise<LspCompletionItem[]>

lsp.hover(path, line, character)

Get hover information (type info, documentation) at a position.

const hover = await lsp.hover('/src/app.ts', 5, 10);

if (hover) {
  // hover.contents can be string or markdown
  console.log(hover.contents);
  // Example: "(method) Array.map<U>(callbackfn: ...): U[]"
}

Returns: Promise<LspHover | null>

lsp.definition(path, line, character)

Go to the definition of a symbol.

const locations = await lsp.definition('/src/app.ts', 10, 5);

if (locations && locations.length > 0) {
  const loc = locations[0];
  console.log(`Definition at ${loc.uri}:${loc.range.start.line}`);
  // Example: "Definition at file:///home/developer/workspace/src/utils.ts:25"
}

Returns: Promise<LspLocation[] | null>

lsp.references(path, line, character, includeDeclaration?)

Find all references to a symbol.

const refs = await lsp.references('/src/app.ts', 5, 10);

console.log(`Found ${refs.length} references:`);
refs.forEach(ref => {
  console.log(`  ${ref.uri}:${ref.range.start.line}`);
});

Returns: Promise<LspLocation[]>

lsp.signatureHelp(path, line, character)

Get function signature help (parameter hints).

const sig = await lsp.signatureHelp('/src/app.ts', 10, 20);

if (sig && sig.signatures.length > 0) {
  console.log('Signature:', sig.signatures[0].label);
  console.log('Active param:', sig.activeParameter);
}

Returns: Promise<LspSignatureHelp | null>

lsp.documentSymbols(path)

Get all symbols (functions, classes, variables) in a document.

const symbols = await lsp.documentSymbols('/src/app.ts');

symbols.forEach(sym => {
  console.log(`${sym.name} (${sym.kind}) at line ${sym.location.range.start.line}`);
  // Example: "MyComponent (Function) at line 10"
});

Returns: Promise<LspSymbol[]>

lsp.shutdown() and lsp.disconnect()

Clean shutdown and disconnect.

// Shutdown LSP server gracefully
await lsp.shutdown();

// Disconnect WebSocket
lsp.disconnect();

// Or use the sandbox method (does both)
await sandbox.disconnectLsp();

Complete LSP Example

import { Sandbox, LspClient, LSP_COMPLETION_KINDS } from '@codivstack/sdk';

async function buildCodeEditor() {
  const sandbox = await Sandbox.create({ size: 'standard' });
  
  // Connect to real-time FS for file changes
  const fs = await sandbox.connectFs();
  
  // Connect to LSP for language features
  const lsp = await sandbox.connectLsp({
    onDiagnostics: (uri, diagnostics) => {
      // Update editor error markers
      updateDiagnostics(uri, diagnostics);
    }
  });
  
  // Read and open file
  const content = await fs.readTextFile('/src/app.ts');
  lsp.didOpen('/src/app.ts', content);
  
  // Get completions when user types
  async function onCursorChange(line: number, char: number) {
    const completions = await lsp.completion('/src/app.ts', line, char);
    showCompletionMenu(completions.map(c => ({
      label: c.label,
      kind: LSP_COMPLETION_KINDS[c.kind || 1],
      detail: c.detail
    })));
  }
  
  // Get hover info on mouse over
  async function onHover(line: number, char: number) {
    const hover = await lsp.hover('/src/app.ts', line, char);
    if (hover) {
      showTooltip(hover.contents);
    }
  }
  
  // Go to definition on Ctrl+Click
  async function onCtrlClick(line: number, char: number) {
    const defs = await lsp.definition('/src/app.ts', line, char);
    if (defs && defs.length > 0) {
      navigateTo(defs[0].uri, defs[0].range.start.line);
    }
  }
  
  // Update LSP when user edits
  function onEditorChange(newContent: string) {
    lsp.didChange('/src/app.ts', newContent);
  }
  
  // Cleanup
  return async () => {
    await lsp.shutdown();
    lsp.disconnect();
    fs.disconnect();
  };
}

LSP Type Helpers

The SDK exports helpful constants for LSP types:

import { 
  LSP_COMPLETION_KINDS,    // Completion item kind names
  LSP_SYMBOL_KINDS,        // Symbol kind names
  LSP_DIAGNOSTIC_SEVERITY  // Diagnostic severity names
} from '@codivstack/sdk';

// Usage
const kindName = LSP_COMPLETION_KINDS[item.kind];  // "Function", "Variable", etc.
const severityName = LSP_DIAGNOSTIC_SEVERITY[diag.severity];  // "Error", "Warning", etc.

LspClient Properties

const lsp = await sandbox.connectLsp();

lsp.connected     // boolean - Is WebSocket connected?
lsp.initialized   // boolean - Is LSP server initialized?
lsp.wsUrl         // string - WebSocket URL

File Management

All file paths are workspace-relative. The root / maps to /home/developer/workspace/.

Path examples:

  • /index.js/home/developer/workspace/index.js
  • /src/app.ts/home/developer/workspace/src/app.ts
  • example.txt/home/developer/workspace/example.txt

sandbox.files.list(path)

List files and directories in a path.

const files = await sandbox.files.list('/src');
// Returns: FileInfo[] with name, path, isDirectory, size, modified

sandbox.files.read(path)

Read a file or multiple files from the workspace.

// Read single file
const content = await sandbox.files.read('/index.js');

// Read multiple files
const contents = await sandbox.files.read(['/index.js', '/package.json']);
// Returns: { '/index.js': '...', '/package.json': '...' }

sandbox.files.write(path, data)

Write single or multiple files to the workspace.

// Write single file
await sandbox.files.write('/app.js', 'console.log("Hello");');

// Write multiple files
await sandbox.files.write([
  { path: '/index.js', data: 'console.log("Main");' },
  { path: '/config.json', data: '{"port": 3000}' }
]);

sandbox.files.delete(path)

Delete files or directories from the workspace.

// Delete single file
await sandbox.files.delete('/old.js');

// Delete multiple files
await sandbox.files.delete(['/old.js', '/temp.txt']);

sandbox.files.createDirectory(path)

Create a directory in the workspace.

await sandbox.files.createDirectory('/src/components');

sandbox.files.move(from, to)

Move or rename a file or directory.

await sandbox.files.move('/old.js', '/new.js');

sandbox.files.copy(from, to)

Copy a file.

await sandbox.files.copy('/template.js', '/copy.js');

sandbox.files.exists(path)

Check if a file or directory exists.

const exists = await sandbox.files.exists('/index.js');

Workspace Download

Download the entire workspace as a tar.gz archive. Useful for initial file sync or backup.

sandbox.files.getWorkspaceInfo()

Get workspace information (size, file count, etc.).

const info = await sandbox.files.getWorkspaceInfo();

console.log(info.path);           // "/home/developer/workspace"
console.log(info.size);           // 1234567 (bytes)
console.log(info.sizeHuman);      // "1.2 MB"
console.log(info.fileCount);      // 150
console.log(info.dirCount);       // 25
console.log(info.hasNodeModules); // true
console.log(info.timestamp);      // "2024-01-15T10:30:00Z"

sandbox.files.downloadWorkspace()

Download entire workspace as tar.gz (includes node_modules).

// Download workspace as ArrayBuffer
const tarBuffer = await sandbox.files.downloadWorkspace();

// In Node.js: Save to file
import { writeFileSync } from 'fs';
writeFileSync('workspace.tar.gz', Buffer.from(tarBuffer));

// In browser: Use pako + untar to extract
// const files = await extractTarGz(tarBuffer);

sandbox.files.getWorkspaceDownloadUrl()

Get the direct URL to download workspace tar.gz.

const url = sandbox.files.getWorkspaceDownloadUrl();
// -> https://30001-sandbox-xxx.codivstack.dev/workspace/tar

// Use with fetch
const response = await fetch(url);
const buffer = await response.arrayBuffer();

// Or as download link
const a = document.createElement('a');
a.href = url;
a.download = 'workspace.tar.gz';
a.click();

Complete Workspace Sync Example

import { Sandbox } from '@codivstack/sdk';

async function syncWorkspace() {
  const sandbox = await Sandbox.get('sandbox-abc123');
  
  // Check workspace info first
  const info = await sandbox.files.getWorkspaceInfo();
  console.log(`Workspace: ${info.sizeHuman}, ${info.fileCount} files`);
  
  if (info.hasNodeModules) {
    console.log('Note: node_modules included in download');
  }
  
  // Download entire workspace
  const tarBuffer = await sandbox.files.downloadWorkspace();
  console.log(`Downloaded ${tarBuffer.byteLength} bytes`);
  
  // Save to file (Node.js)
  const fs = await import('fs');
  fs.writeFileSync('workspace-backup.tar.gz', Buffer.from(tarBuffer));
  
  console.log('Workspace saved to workspace-backup.tar.gz');
}

Command Execution

Execute shell commands in your sandbox using the commands API. Perfect for running build scripts, dev servers, and other CLI tools.

sandbox.commands.run(command, options)

Run a shell command and get the output.

// Simple command
const result = await sandbox.commands.run('ls -la');
console.log(result.stdout);

// With timeout (default: 30s)
const result = await sandbox.commands.run('npm test', { timeout: 60000 });

// In specific directory
const result = await sandbox.commands.run('ls', { 
  workdir: '/home/developer/workspace/src' 
});

sandbox.commands.startBackground(command)

Start a long-running process in the background (e.g., dev servers).

// Start Vite dev server
await sandbox.commands.startBackground('npm run dev -- --host 0.0.0.0');

// Start Next.js
await sandbox.commands.startBackground('npm run dev');

// Custom port
await sandbox.commands.startBackground('npm run dev -- --port 3000 --host 0.0.0.0');

sandbox.commands.npmInstall(packages, options)

Install npm packages.

// Install all dependencies from package.json
await sandbox.commands.npmInstall();

// Install specific packages
await sandbox.commands.npmInstall(['express', 'cors']);

// Install dev dependencies
await sandbox.commands.npmInstall(['typescript', '@types/node'], { dev: true });

sandbox.commands.npmRun(script, options)

Run an npm script.

// Run build script
const result = await sandbox.commands.npmRun('build');

// Run dev server in background
await sandbox.commands.npmRun('dev', { 
  background: true, 
  args: ['--host', '0.0.0.0'] 
});

// Run tests with timeout
const testResult = await sandbox.commands.npmRun('test', { timeout: 120000 });

sandbox.commands.kill(target)

Kill a process by name or port.

// Kill by process name
await sandbox.commands.kill('node');

// Kill by port number
await sandbox.commands.kill(3000);

Static Command Methods

You can also use commands without a SandboxInstance:

import { Sandbox, SandboxCommands } from '@codivstack/sdk';

// Get commands API for a sandbox
const commands = Sandbox.commands('sandbox-abc123');

// Or create directly
const commands = new SandboxCommands('sandbox-abc123');

// Run commands
await commands.run('npm install');
await commands.startBackground('npm run dev -- --host 0.0.0.0');

Error Handling

The SDK throws CodivStackError for API errors:

import { Sandbox, CodivStackError } from '@codivstack/sdk';

try {
  const sandbox = await Sandbox.create({ size: 'small' });
} catch (error) {
  if (error instanceof CodivStackError) {
    console.error('Status:', error.statusCode);
    console.error('Message:', error.message);
    
    if (error.isUnauthorized()) {
      console.error('Authentication failed');
    } else if (error.isForbidden()) {
      console.error('Permission denied');
    }
  }
}

Types

The SDK exports TypeScript types for all API responses:

import type {
  SandboxDetails,
  SandboxSummary,
  SandboxCreateInput,
  SandboxStatusDetail,
  SandboxMetrics,
  SandboxSize,
  SandboxStatus,
} from '@codivstack/sdk';

// Example: Type-safe sandbox creation
const createSandbox = async (size: SandboxSize, name: string): Promise<SandboxDetails> => {
  return await Sandbox.create({ size, name });
};

Sandbox Sizes

The SDK supports 11 different sandbox sizes:

| Size | CPU (vCPU) | Memory (GB) | Use Case | |---------|------------|-------------|----------| | nano | 0.25 | 0.5 | Minimal tasks, testing | | tiny | 0.5 | 1 | Light workloads | | small | 0.5 | 2 | Small applications | | basic | 1 | 1 | Basic development | | standard| 1 | 2 | Standard development | | medium | 1 | 4 | Medium applications | | large | 2 | 4 | Large applications | | xlarge | 2 | 8 | Heavy workloads | | xxlarge | 4 | 8 | Very heavy workloads | | huge | 4 | 16 | Intensive computing | | massive | 4 | 30 | Maximum resources |

Sandbox Status

Sandboxes can have the following statuses:

  • 'pending' - Sandbox is being created
  • 'running' - Sandbox is active and running
  • 'stopped' - Sandbox is stopped
  • 'error' - Sandbox encountered an error

Requirements

  • Node.js >= 18.0.0
  • TypeScript >= 5.0.0 (for TypeScript projects)

Environment Variables

| Variable | Description | Required | Default | |----------|-------------|----------|---------| | CODIVSTACK_API_KEY | Your CodivStack API key | Yes (for authenticated requests) | - | | CODIVSTACK_API_BASE | Base URL for the API | No | https://api.codivstack.dev |

Examples

Working with Files

import { Sandbox } from '@codivstack/sdk';

// Create a sandbox and manage files
const sandbox = await Sandbox.create({ size: 'medium' });

// Write multiple files at once
await sandbox.files.write([
  { path: '/app.js', data: 'const express = require("express");' },
  { path: '/package.json', data: JSON.stringify({ name: 'app', version: '1.0.0' }, null, 2) },
  { path: '/README.md', data: '# My App\n\nWelcome to my application!' }
]);

// List all files
const files = await sandbox.files.list('/');
console.log('Created files:', files.map(f => f.name));

// Read multiple files
const contents = await sandbox.files.read(['/app.js', '/package.json']);
console.log('File contents:', contents);

// Copy and move files
await sandbox.files.copy('/app.js', '/app.backup.js');
await sandbox.files.move('/README.md', '/docs/README.md');

// Clean up
await sandbox.stop();

List and Filter Sandboxes

import { Sandbox } from '@codivstack/sdk';

const { sandboxes, total } = await Sandbox.list();
const runningSandboxes = sandboxes.filter(s => s.status === 'running');
console.log(`You have ${runningSandboxes.length} running sandboxes out of ${total} total`);

Create Multiple Sandboxes

import { Sandbox } from '@codivstack/sdk';

const sizes = ['small', 'medium', 'large'] as const;

const sandboxes = await Promise.all(
  sizes.map(size => 
    Sandbox.create({ 
      size, 
      name: `sandbox-${size}` 
    })
  )
);

// Write initial files to each sandbox
for (const sandbox of sandboxes) {
  await sandbox.files.write('/index.js', `console.log("Sandbox: ${sandbox.displayName}");`);
}

console.log('Created sandboxes:', sandboxes.map(s => s.sandboxId));

Run Dev Server

import { Sandbox } from '@codivstack/sdk';

async function startDevEnvironment() {
  // Create a sandbox
  const sandbox = await Sandbox.create({ 
    size: 'medium',
    name: 'dev-environment'
  });

  // Write project files
  await sandbox.files.write([
    { 
      path: '/package.json', 
      data: JSON.stringify({
        name: 'my-app',
        scripts: { dev: 'vite --host 0.0.0.0' },
        dependencies: { vite: '^5.0.0' }
      }, null, 2)
    },
    { 
      path: '/index.html', 
      data: '<html><body><h1>Hello CodivStack!</h1></body></html>' 
    }
  ]);

  // Install dependencies
  console.log('Installing dependencies...');
  await sandbox.commands.npmInstall();

  // Start dev server in background
  console.log('Starting dev server...');
  await sandbox.commands.startBackground('npm run dev');

  console.log(`Dev server running at sandbox: ${sandbox.sandboxId}`);
  console.log('Access via your sandbox subdomain');

  return sandbox;
}

Monitor Sandbox Health

import { Sandbox, Health } from '@codivstack/sdk';

async function monitor() {
  // Check API health
  const apiHealthy = await Health.check();
  if (!apiHealthy) {
    console.error('API is down');
    return;
  }

  // Check all sandboxes
  const { sandboxes } = await Sandbox.list();
  for (const sandboxInfo of sandboxes) {
    const status = await Sandbox.getStatus(sandboxInfo.id);
    console.log(`${sandboxInfo.id}: ${status.status}`);
    
    if (status.status === 'running') {
      const metrics = await Sandbox.getMetrics(sandboxInfo.id);
      console.log(`  CPU: ${metrics.metrics.cpuPercent}%`);
      console.log(`  Memory: ${metrics.metrics.memoryPercent}%`);
    }
  }
}

React Terminal & Console Components

The SDK includes React components for Terminal (interactive shell) and Console (read-only workflow output).

Installation

When using the React components, you also need to install peer dependencies:

npm install @codivstack/sdk xterm @xterm/addon-fit @xterm/addon-web-links

Theme Object

The SDK provides built-in dark and light themes via a single Theme object:

import { Theme } from '@codivstack/sdk/react';

// Access themes
Theme.dark   // Dark terminal theme
Theme.light  // Light terminal theme

// Usage with theme detection
const currentTheme = isDarkMode ? Theme.dark : Theme.light;

Basic Terminal Usage

import { useState, useEffect } from 'react';
import { Terminal, Theme } from '@codivstack/sdk/react';
import { Sandbox } from '@codivstack/sdk';

function App() {
  const [wsUrl, setWsUrl] = useState<string>('');
  const [isDark, setIsDark] = useState(true);

  useEffect(() => {
    async function init() {
      const sandbox = await Sandbox.create({ size: 'standard' });
      setWsUrl(sandbox.wssUrl);
    }
    init();
  }, []);

  if (!wsUrl) return <div>Loading...</div>;

  return (
    <div style={{ width: '100%', height: '500px' }}>
      <Terminal
        wsUrl={wsUrl}
        theme={isDark ? Theme.dark : Theme.light}
        onOpen={() => console.log('Connected!')}
        onClose={() => console.log('Disconnected')}
        onReady={() => console.log('Terminal ready')}
      />
    </div>
  );
}

Console Usage (Read-only Workflow Output)

Console uses the same Terminal component but connects to the console WebSocket endpoint:

import { Terminal, Theme } from '@codivstack/sdk/react';
import { Sandbox } from '@codivstack/sdk';

function DevServerConsole() {
  const [sandbox, setSandbox] = useState<SandboxInstance | null>(null);
  const [consoleUrl, setConsoleUrl] = useState<string>('');

  useEffect(() => {
    async function init() {
      const sb = await Sandbox.create({ size: 'standard' });
      setSandbox(sb);
      
      // Start dev server via console API
      await sb.console.start({ command: 'npm run dev -- --host 0.0.0.0' });
      
      // Set console URL for Terminal component
      setConsoleUrl(sb.console.url);
    }
    init();
  }, []);

  if (!consoleUrl) return <div>Loading...</div>;

  return (
    <div style={{ width: '100%', height: '300px' }}>
      <h3>Dev Server Output (Read-only)</h3>
      <Terminal 
        wsUrl={consoleUrl} 
        theme={Theme.dark}
        connectedMessage=""
        welcomeMessage=""
      />
    </div>
  );
}

useTerminal Hook

For more control, use the useTerminal hook:

import { useTerminal, Theme, getScrollbarStyles } from '@codivstack/sdk/react';

function CustomTerminal({ wsUrl }: { wsUrl: string }) {
  const [isDark, setIsDark] = useState(true);
  const theme = isDark ? Theme.dark : Theme.light;
  
  const { containerRef, ref, isConnected, isConnecting, isReady, error } = useTerminal({
    wsUrl,
    theme,
    fontSize: 14,
    autoReconnect: true,
    onOpen: (ws) => console.log('Connected'),
    onClose: (event) => console.log('Closed:', event.code),
    onData: (data) => console.log('User typed:', data),
    onReady: () => console.log('Terminal ready'),
    onResize: (cols, rows) => console.log(`Resized: ${cols}x${rows}`),
  });

  return (
    <div>
      <style>{getScrollbarStyles(theme)}</style>
      <div>
        Status: {isConnected ? 'Connected' : isConnecting ? 'Connecting...' : 'Disconnected'}
        {isReady && ' (Ready)'}
      </div>
      {error && <div style={{ color: 'red' }}>Error: {error}</div>}
      <div 
        ref={containerRef} 
        className="codivstack-terminal"
        style={{ height: '400px', backgroundColor: theme.background }} 
      />
      <button onClick={() => ref.clear()}>Clear</button>
      <button onClick={() => ref.fit()}>Fit</button>
      <button onClick={() => ref.scrollToBottom()}>Scroll to Bottom</button>
      <button onClick={() => ref.disconnect()}>Disconnect</button>
    </div>
  );
}

Scrollbar Styles

The SDK provides a getScrollbarStyles function for custom scrollbar styling:

import { getScrollbarStyles, Theme } from '@codivstack/sdk/react';

// In your component
<style>{getScrollbarStyles(Theme.dark)}</style>

// Or inject globally once
useEffect(() => {
  const styleEl = document.createElement('style');
  styleEl.textContent = getScrollbarStyles(Theme.dark);
  document.head.appendChild(styleEl);
}, []);

Terminal Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | wsUrl | string | required | WebSocket URL for terminal connection | | theme | TerminalTheme | Theme.dark | Terminal color theme | | fontSize | number | 14 | Font size in pixels | | fontFamily | string | 'Monaco, Menlo...' | Font family | | cursorBlink | boolean | true | Enable cursor blinking | | scrollback | number | 1000 | Number of scrollback lines | | autoConnect | boolean | true | Connect automatically on mount | | autoReconnect | boolean | true | Reconnect on disconnect | | reconnectInterval | number | 3000 | Reconnect delay in ms | | maxReconnectAttempts | number | 10 | Max reconnection attempts | | welcomeMessage | string | - | Message shown on init | | connectedMessage | string | '✓ Connected' | Message on connection | | disconnectedMessage | string | '✗ Disconnected' | Message on disconnect |

Event Callbacks

| Callback | Type | Description | |----------|------|-------------| | onOpen | (ws: WebSocket) => void | WebSocket connected | | onClose | (event: CloseEvent) => void | WebSocket closed | | onError | (event: Event) => void | WebSocket error | | onMessage | (data: string \| ArrayBuffer) => void | Data received | | onData | (data: string) => void | User typed data | | onResize | (cols: number, rows: number) => void | Terminal resized | | onReconnect | (attempt: number) => void | Reconnection attempt | | onReady | () => void | Terminal initialized and ready |

Custom Themes

You can create custom themes or extend the built-in ones:

import { Theme, TerminalTheme } from '@codivstack/sdk/react';

// Extend existing theme
const customDark: TerminalTheme = {
  ...Theme.dark,
  background: '#1a1b26',
  foreground: '#a9b1d6',
};

// Create new theme
const tokyoNight: TerminalTheme = {
  background: '#1a1b26',
  foreground: '#a9b1d6',
  cursor: '#c0caf5',
  cursorAccent: '#1a1b26',
  selectionBackground: '#33467c',
  black: '#32344a',
  red: '#f7768e',
  green: '#9ece6a',
  yellow: '#e0af68',
  blue: '#7aa2f7',
  magenta: '#bb9af7',
  cyan: '#7dcfff',
  white: '#a9b1d6',
  brightBlack: '#444b6a',
  brightRed: '#ff7a93',
  brightGreen: '#b9f27c',
  brightYellow: '#ff9e64',
  brightBlue: '#7da6ff',
  brightMagenta: '#bb9af7',
  brightCyan: '#0db9d7',
  brightWhite: '#acb0d0',
  scrollbarSlider: 'rgba(255, 255, 255, 0.2)',
  scrollbarHoverSlider: 'rgba(255, 255, 255, 0.3)',
};

<Terminal wsUrl={wsUrl} theme={tokyoNight} />

TerminalRef Methods

When using useTerminal, you get access to these methods via ref:

const { ref } = useTerminal({ wsUrl });

ref.write('Hello');        // Write text to terminal
ref.writeln('World');      // Write line to terminal
ref.clear();               // Clear terminal
ref.focus();               // Focus terminal
ref.fit();                 // Fit terminal to container
ref.scrollToBottom();      // Scroll to bottom
ref.refresh();             // Refresh terminal display
ref.connect();             // Manually connect
ref.disconnect();          // Disconnect
ref.terminal;              // Access underlying xterm.js instance
ref.ws;                    // Access WebSocket instance

Terminal vs Console Summary

| Feature | Terminal | Console | |---------|----------|---------| | Purpose | Interactive shell | Workflow output display | | Input | Full input support | Read-only (input ignored) | | URL | sandbox.wssUrl | sandbox.console.url | | Component | <Terminal /> | <Terminal /> (same!) | | Start | Auto-connects | sandbox.console.start() first | | Stop | ref.disconnect() | sandbox.console.stop() |

Complete Example with Both Terminal and Console

import { useState, useEffect } from 'react';
import { Terminal, Theme, useTerminal } from '@codivstack/sdk/react';
import { Sandbox, SandboxInstance } from '@codivstack/sdk';

function IDEPanel() {
  const [sandbox, setSandbox] = useState<SandboxInstance | null>(null);
  const [isDark, setIsDark] = useState(true);
  
  const theme = isDark ? Theme.dark : Theme.light;

  useEffect(() => {
    async function init() {
      const sb = await Sandbox.create({ size: 'standard' });
      setSandbox(sb);
    }
    init();
  }, []);

  if (!sandbox) return <div>Creating sandbox...</div>;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
      {/* Console Panel - Read-only dev server output */}
      <div style={{ flex: 1 }}>
        <h3>Console (npm run dev)</h3>
        <ConsolePanel sandbox={sandbox} theme={theme} />
      </div>
      
      {/* Terminal Panel - Interactive shell */}
      <div style={{ flex: 1 }}>
        <h3>Terminal</h3>
        <Terminal
          wsUrl={sandbox.wssUrl}
          theme={theme}
          welcomeMessage="Welcome to CodivStack Terminal!"
        />
      </div>
    </div>
  );
}

function ConsolePanel({ sandbox, theme }: { sandbox: SandboxInstance; theme: TerminalTheme }) {
  const [consoleUrl, setConsoleUrl] = useState('');
  const [isRunning, setIsRunning] = useState(false);

  const startDevServer = async () => {
    await sandbox.console.start({ command: 'npm run dev -- --host 0.0.0.0' });
    setConsoleUrl(sandbox.console.url);
    setIsRunning(true);
  };

  const stopDevServer = async () => {
    await sandbox.console.stop();
    setIsRunning(false);
  };

  return (
    <div>
      <div>
        {!isRunning ? (
          <button onClick={startDevServer}>Start Dev Server</button>
        ) : (
          <button onClick={stopDevServer}>Stop</button>
        )}
      </div>
      {consoleUrl && (
        <Terminal
          wsUrl={consoleUrl}
          theme={theme}
          connectedMessage=""
          welcomeMessage=""
        />
      )}
    </div>
  );
}

CodivX AI Agent

The SDK includes an AI Agent client for autonomous code development. The agent supports two backends:

  1. Inngest AgentKit + Anthropic Claude (Recommended) - Direct Anthropic API integration with Inngest orchestration
  2. AWS Bedrock (Legacy) - AWS-managed Claude deployment

Inngest AgentKit Integration

CodivX Agent v2.0+ uses Inngest AgentKit with Anthropic Claude (claude-sonnet-4-5-20250929) for real-time AI agent capabilities. Features include:

  • SSE Streaming: Token-by-token text streaming to frontend
  • Inngest Dashboard: Full visibility into agent runs, tool calls, and metrics
  • Direct HTTP: Tool execution via sandbox HTTP API (port 30001)
  • Session Management: Conversation continuity with session IDs

Inngest Event Types

The Inngest AgentKit streams structured events via SSE:

| Event Type | Description | Data Fields | |------------|-------------|-------------| | run.started | Agent run initiated | sessionId, sandboxId, timestamp | | agent.start | Agent iteration started | iteration | | inference.start | LLM inference started | - | | text.delta | Streaming text chunk | delta (token) | | reasoning | Claude's thinking process | text (markdown) | | tool_call.started | Tool execution started | toolName, toolCallId, status, input | | tool_call.completed | Tool execution finished | toolName, toolCallId, status, success, output, error? | | agent.finish | Agent iteration completed | output | | inference.finish | LLM inference completed | usage (tokens) | | run.completed | Agent run finished | success, text, toolCalls | | stream.ended | SSE stream closed | - | | error | Error occurred | error, code? | | debug | Debug info (when debug=true) | data |

Inngest Streaming Example

import { configure, AgentClient } from '@codivstack/sdk';

configure({
  baseUrl: 'https://api.codivstack.com',
  apiKey: 'cs_your_api_key'
});

const agent = new AgentClient();

// Streaming chat with Inngest events
for await (const event of agent.chat({
  sandboxId: 'sandbox-abc123',
  message: 'Create a React todo app'
})) {
  switch (event.type) {
    // Streaming text (token by token)
    case 'text.delta':
      process.stdout.write(event.data.delta);
      break;
    
    // Claude's reasoning/thinking
    case 'reasoning':
      console.log(`\n💭 ${event.data.text}`);
      break;
    
    // Tool started (e.g., writing file)
    case 'tool_call.started':
      console.log(`\n⚡ ${event.data.toolName}: ${event.data.status}`);
      // status: 'reading' | 'writing' | 'executing' | 'listing' | ...
      break;
    
    // Tool completed
    case 'tool_call.completed':
      const icon = event.data.success ? '✅' : '❌';
      console.log(`${icon} ${event.data.toolName}: ${event.data.status}`);
      // status: 'read' | 'written' | 'executed' | 'listed' | 'failed' | ...
      break;
    
    // Run completed
    case 'run.completed':
      console.log('\n✓ Done');
      break;
    
    // Error
    case 'error':
      console.error('Error:', event.data.error);
      break;
  }
}

Inngest Callback-Based Streaming

const result = await agent.chatWithCallbacks({
  sandboxId: 'sandbox-abc123',
  message: 'Read the package.json file'
}, {
  onTextDelta: (text) => process.stdout.write(text),
  onReasoning: (text) => console.log('💭', text),
  onToolCallStarted: (event) => {
    console.log(`⚡ ${event.data.toolName} (${event.data.status})`);
  },
  onToolCallCompleted: (event) => {
    const icon = event.data.success ? '✅' : '❌';
    console.log(`${icon} ${event.data.toolName} (${event.data.status})`);
  },
  onRunStarted: (event) => console.log('Started:', event.data.sessionId),
  onRunCompleted: (event) => console.log('Completed:', event.data.success),
  onError: (err) => console.error('Error:', err.message),
  onStreamEnded: () => console.log('Stream ended')
});

console.log('Full response:', result.text);
console.log('Tool calls:', result.toolCallsStarted.length);

Inngest Tool Status Flow

Each tool call transitions through statuses:

| Tool | tool_call.started status | tool_call.completed status | |------|----------------------------|------------------------------| | readFile | reading | read | | writeFile | writing | written | | listDirectory | listing | listed | | runCommand | executing | executed | | deleteFile | deleting | deleted | | startWorkflow | starting | started | | stopWorkflow | stopping | stopped | | getWorkspaceInfo | fetching | fetched | | (any error) | - | failed |

Inngest Dashboard Visibility

CodivX Agent sends events to Inngest Cloud for monitoring:

  • codivx/chat.started: When a chat session begins
  • codivx/chat.completed: When a chat session completes successfully
  • codivx/chat.failed: When a chat session fails

View these in your Inngest Dashboard under the codivx namespace.

Stream Utilities

import { 
  EventBuffer, 
  textStream, 
  reasoningStream, 
  toolActivityStream 
} from '@codivstack/sdk';

// Collect all events with EventBuffer
const buffer = new EventBuffer();
for await (const event of agent.chat({ sandboxId, message })) {
  buffer.add(event);
}
console.log('Full text:', buffer.getText());
console.log('Stats:', buffer.getStats());
console.log('Usage:', buffer.getUsage());

// Text-only stream (just the deltas)
for await (const text of textStream(agent.chat({ sandboxId, message }))) {
  process.stdout.write(text);
}

// Reasoning-only stream
for await (const reasoning of reasoningStream(agent.chat({ sandboxId, message }))) {
  console.log('Thinking:', reasoning);
}

// Tool activity stream (started + completed events)
for await (const event of toolActivityStream(agent.chat({ sandboxId, message }))) {
  if (event.type === 'tool_call.started') {
    console.log(`Started: ${event.data.toolName}`);
  } else if (event.type === 'tool_call.completed') {
    console.log(`Completed: ${event.data.toolName} - ${event.data.success}`);
  }
}

Inngest TypeScript Types

import type {
  // New Inngest AgentKit events
  AgentEvent,
  RunStartedEvent,
  AgentStartEvent,
  InferenceStartEvent,
  TextDeltaEvent,
  ReasoningEvent,
  ToolCallStartedEvent,
  ToolCallCompletedEvent,
  AgentFinishEvent,
  InferenceFinishEvent,
  RunCompletedEvent,
  StreamEndedEvent,
  ErrorEvent,
  DebugEvent,
  
  // Tool status types
  ToolExecutingStatus,  // 'reading' | 'writing' | 'executing' | ...
  ToolCompletedStatus,  // 'read' | 'written' | 'executed' | 'failed' | ...
  
  // Request/Response
  ChatRequest,
  ChatOptions,
  AgentConfig,
  AgentConfigResponse,
} from '@codivstack/sdk';

Server-Side Configuration

For Inngest AgentKit to work, the server requires:

| Environment Variable | Description | |---------------------|-------------| | ANTHROPIC_API_KEY | Anthropic Claude API key (required) | | INNGEST_EVENT_KEY | Inngest Cloud Event Key (optional, for Dashboard) | | INNGEST_SIGNING_KEY | Inngest Cloud Signing Key (optional) |


Legacy: AWS Bedrock Integration

The legacy Bedrock integration uses event types: thinking, toolCall, toolResult, chunk, done.

Agent Quick Start

Option 1: Use global configure() (Recommended)

Same config works for both Sandbox and Agent:

import { configure, Sandbox, AgentClient } from '@codivstack/sdk';

// Configure ONCE for ALL SDK features
configure({
  baseUrl: 'https://api.codivstack.dev',
  apiKey: 'cs_your_api_key'
});

// Create sandbox (uses global config)
const sandbox = await Sandbox.create({ size: 'standard' });

// Create agent (uses global config automatically)
const agent = new AgentClient();

// Streaming chat with full event handling
for await (