Remote development environments and distributed job execution across Linux nodes.

Cluster SDK

The Cluster SDK (@codmir/cluster-sdk) enables remote development environments and distributed job execution across a pool of Linux machines.

Installation

pnpm add @codmir/cluster-sdk

Quick Start

import { createClusterClient } from '@codmir/cluster-sdk';

const client = createClusterClient({
  baseUrl: 'https://codmir.com/api/cluster',
  token: 'your-auth-token',
});

// Create a dev session (SSH mode)
const session = await client.sessions.create({
  repoUrl: 'https://github.com/org/repo',
  mode: 'ssh',
});

// Get SSH command
console.log(session.sshInfo?.sshCommand);
// ssh -p 22 codmir@192.168.1.10 -t "cd /var/codmir/workspaces/sess_abc123/repo && exec $SHELL -l"

Two IDE Modes

SSH Mode

Use your local IDE (VS Code, Windsurf, JetBrains) with remote workspace:

const session = await client.sessions.create({
  repoUrl: 'https://github.com/org/repo',
  mode: 'ssh',
});

// Copy to clipboard
console.log(session.sshInfo?.sshCommand);

// Or get VS Code SSH config
console.log(session.sshInfo?.vscodeSshConfig);
// Host codmir-sess_abc123
//   HostName 192.168.1.10
//   Port 22
//   User codmir

Browser Mode

VS Code in your browser via code-server:

const session = await client.sessions.create({
  repoUrl: 'https://github.com/org/repo',
  mode: 'browser',
});

// Open in browser
window.open(session.browserInfo?.ideUrl);

// Password if required
console.log(session.browserInfo?.password);

Running Jobs

Execute builds, tests, and other tasks on the cluster:

const job = await client.jobs.run({
  repoUrl: 'https://github.com/org/repo',
  command: 'pnpm install && pnpm build',
});

console.log(job.id, job.status);

Stream Job Logs

const abort = client.jobs.streamLogs(job.id, {
  onEvent: (event) => {
    if ('line' in event) {
      console.log(`[${event.stream}] ${event.line}`);
    }
    if ('status' in event) {
      console.log(`Job ${event.status}`);
    }
  },
  onClose: () => console.log('Stream closed'),
});

// Cancel streaming
abort();

Job with Patch

Apply uncommitted changes before running:

const job = await client.jobs.run({
  repoUrl: 'https://github.com/org/repo',
  command: 'pnpm test',
  patch: `
diff --git a/src/app.ts b/src/app.ts
--- a/src/app.ts
+++ b/src/app.ts
@@ -1 +1 @@
-const x = 1;
+const x = 2;
`,
});

API Reference

Sessions

// Create session
client.sessions.create({
  repoUrl: string;      // Git repository URL
  ref?: string;         // Branch, tag, or commit
  mode: 'ssh' | 'browser';
  labels?: string[];    // Prefer nodes with labels
}): Promise<ClusterSession>

// Get session
client.sessions.get(sessionId: string): Promise<ClusterSession>

// Terminate session
client.sessions.terminate(sessionId: string, force?: boolean): Promise<void>

// List user's sessions
client.sessions.list(): Promise<ClusterSession[]>

Jobs

// Run job
client.jobs.run({
  repoUrl: string;
  ref?: string;
  command: string;
  cwd?: string;           // Relative to workspace
  env?: Record<string, string>;
  timeout?: number;       // ms, default 300000
  patch?: string;         // Git diff to apply
  sessionId?: string;     // Run on session's node
}): Promise<ClusterJob>

// Get job
client.jobs.get(jobId: string): Promise<ClusterJob>

// Cancel job
client.jobs.cancel(jobId: string, reason?: string): Promise<void>

// Stream logs (SSE)
client.jobs.streamLogs(jobId: string, handler: {
  onEvent: (event: JobLogEvent | JobStatusEvent) => void;
  onError?: (error: Error) => void;
  onClose?: () => void;
}): () => void  // Returns abort function

Nodes

// List nodes
client.nodes.list(): Promise<ClusterNode[]>

// Get node
client.nodes.get(nodeId: string): Promise<ClusterNode>

Types

ClusterSession

interface ClusterSession {
  id: string;
  userId: string;
  nodeId: string;
  mode: 'ssh' | 'browser';
  status: 'pending' | 'provisioning' | 'ready' | 'active' | 'terminating' | 'terminated' | 'error';
  context: {
    repoUrl: string;
    ref?: string;
  };
  workspacePath: string;
  sshInfo?: {
    host: string;
    port: number;
    user: string;
    workspacePath: string;
    sshCommand: string;
    vscodeSshConfig: string;
  };
  browserInfo?: {
    ideUrl: string;
    password?: string;
  };
  createdAt: Date;
  error?: string;
}

ClusterJob

interface ClusterJob {
  id: string;
  userId: string;
  nodeId?: string;
  sessionId?: string;
  status: 'queued' | 'assigned' | 'running' | 'completed' | 'failed' | 'cancelled';
  request: {
    repoUrl: string;
    command: string;
    // ...
  };
  exitCode?: number;
  output?: string;
  startedAt?: Date;
  completedAt?: Date;
  error?: string;
}

ClusterNode

interface ClusterNode {
  id: string;
  hostname: string;
  status: 'online' | 'offline' | 'busy' | 'draining' | 'maintenance';
  labels: string[];
  metrics?: {
    cpuUsage: number;
    memoryUsedMb: number;
    memoryTotalMb: number;
    loadAverage: [number, number, number];
    runningSessions: number;
    runningJobs: number;
  };
}

Helpers

import {
  generateSshCommand,
  generateVscodeSshConfig,
  getBrowserIdeUrl,
} from '@codmir/cluster-sdk';

// Generate SSH command
const cmd = generateSshCommand(session);

// Generate VS Code config stanza
const config = generateVscodeSshConfig(session);

// Get browser IDE URL
const url = getBrowserIdeUrl(session);

Error Handling

import { ClusterApiError, ClusterConnectionError } from '@codmir/cluster-sdk';

try {
  await client.sessions.create({ ... });
} catch (error) {
  if (error instanceof ClusterApiError) {
    // API returned error response
    console.log(error.status);  // 400, 401, 404, etc.
    console.log(error.message);
    console.log(error.details);
  }
  if (error instanceof ClusterConnectionError) {
    // Network/connection error
    console.log(error.message);
    console.log(error.cause);
  }
}

Configuration

const client = createClusterClient({
  baseUrl: string;      // API base URL
  token?: string;       // Auth token
  timeout?: number;     // Request timeout (default 30000)
  onError?: (error: Error) => void;  // Global error handler
});

Deployment Modes

Local Mode (Self-hosted)

Direct SSH execution from the Next.js app:

CLUSTER_EXECUTION_MODE=local

Best for development and self-hosted deployments with no timeout constraints.

Cloud Mode (Vercel + Railway)

Jobs queued in Redis, executed by Railway worker:

CLUSTER_EXECUTION_MODE=cloud
CLUSTER_WORKER_URL=https://your-worker.railway.app
CLUSTER_WEBHOOK_SECRET=your-secret
REDIS_URL=redis://...

Best for production Vercel deployments where jobs may exceed the 60s timeout.