OpenAPI Migration Plan for VibeTunnel

Overview

This document outlines the plan to adopt OpenAPI 3.1 for VibeTunnel’s REST API to achieve type safety and consistency between the TypeScript server and Swift clients.

Goals

  1. Single source of truth - Define API contracts once in OpenAPI spec
  2. Type safety - Generate TypeScript and Swift types from the spec
  3. Eliminate inconsistencies - Fix type mismatches between platforms
  4. API documentation - Auto-generate API docs from the spec
  5. Gradual adoption - Migrate endpoint by endpoint without breaking changes

Current Issues

  • Session types differ completely between Mac app and server
  • Git repository types have different field names and optional/required mismatches
  • No standardized error response format
  • Manual type definitions duplicated across platforms
  • Runtime parsing errors due to type mismatches

Implementation Plan

Phase 1: Setup and Infrastructure (Week 1)

1.1 Install Dependencies

# In web directory
pnpm add -D @hey-api/openapi-ts @apidevtools/swagger-cli @stoplight/spectral-cli

1.2 Create Initial OpenAPI Spec

Create web/openapi/openapi.yaml:
openapi: 3.1.0
info:
  title: VibeTunnel API
  version: 1.0.0
  description: Terminal sharing and remote access API
servers:
  - url: http://localhost:4020
    description: Local development server

1.3 Setup Code Generation

TypeScript Generation (web/package.json):
{
  "scripts": {
    "generate:api": "openapi-ts -i openapi/openapi.yaml -o src/generated/api",
    "validate:api": "spectral lint openapi/openapi.yaml",
    "prebuild": "npm run generate:api"
  }
}
Swift Generation (Xcode Build Phase):
  1. Add swift-openapi-generator to Package.swift
  2. Add build phase to run before compilation:
cd "$SRCROOT/../web" && \
swift-openapi-generator generate \
  openapi/openapi.yaml \
  --mode types \
  --mode client \
  --output-directory "$SRCROOT/Generated/OpenAPI"

1.4 Create Shared Components

Define reusable schemas in web/openapi/components/:
# components/errors.yaml
ErrorResponse:
  type: object
  required: [error, timestamp]
  properties:
    error:
      type: string
      description: Human-readable error message
    code:
      type: string
      description: Machine-readable error code
      enum: [
        'INVALID_REQUEST',
        'NOT_FOUND',
        'UNAUTHORIZED',
        'SERVER_ERROR'
      ]
    timestamp:
      type: string
      format: date-time

Phase 2: Migrate Git Endpoints (Week 2)

Start with Git endpoints as they’re well-defined and isolated.

2.1 Define Git Schemas

# openapi/paths/git.yaml
/api/git/repository-info:
  get:
    operationId: getRepositoryInfo
    tags: [git]
    parameters:
      - name: path
        in: query
        required: true
        schema:
          type: string
    responses:
      '200':
        description: Repository information
        content:
          application/json:
            schema:
              $ref: '../components/schemas.yaml#/GitRepositoryInfo'

# components/schemas.yaml
GitRepositoryInfo:
  type: object
  required: [isGitRepo, hasChanges, modifiedCount, untrackedCount, stagedCount, addedCount, deletedCount, aheadCount, behindCount, hasUpstream]
  properties:
    isGitRepo:
      type: boolean
    repoPath:
      type: string
    currentBranch:
      type: string
      nullable: true
    remoteUrl:
      type: string
      nullable: true
    githubUrl:
      type: string
      nullable: true
    hasChanges:
      type: boolean
    modifiedCount:
      type: integer
      minimum: 0
    untrackedCount:
      type: integer
      minimum: 0
    stagedCount:
      type: integer
      minimum: 0
    addedCount:
      type: integer
      minimum: 0
    deletedCount:
      type: integer
      minimum: 0
    aheadCount:
      type: integer
      minimum: 0
    behindCount:
      type: integer
      minimum: 0
    hasUpstream:
      type: boolean

2.2 Update Server Implementation

// src/server/routes/git.ts
import { paths } from '../../generated/api';

type GitRepositoryInfo = paths['/api/git/repository-info']['get']['responses']['200']['content']['application/json'];

router.get('/git/repository-info', async (req, res) => {
  const response: GitRepositoryInfo = {
    isGitRepo: true,
    repoPath: result.repoPath,
    // ... ensure all required fields are included
  };
  res.json(response);
});

2.3 Update Mac Client

// Use generated types
import OpenAPIGenerated

let response = try await client.getRepositoryInfo(path: filePath)
let info = response.body.json // Fully typed!

Phase 3: Migrate Session Endpoints (Week 3)

Session endpoints are more complex due to WebSocket integration.

3.1 Standardize Session Types

SessionInfo:
  type: object
  required: [id, name, workingDir, status, createdAt, pid]
  properties:
    id:
      type: string
      format: uuid
    name:
      type: string
    workingDir:
      type: string
    status:
      type: string
      enum: [starting, running, exited]
    exitCode:
      type: integer
      nullable: true
    createdAt:
      type: string
      format: date-time
    lastActivity:
      type: string
      format: date-time
    pid:
      type: integer
      nullable: true
    command:
      type: array
      items:
        type: string

3.2 Create Session Operations

/api/sessions:
  get:
    operationId: listSessions
    responses:
      '200':
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/SessionInfo'
  
  post:
    operationId: createSession
    requestBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CreateSessionRequest'
    responses:
      '201':
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionInfo'

Phase 4: Runtime Validation (Week 4)

4.1 Add Request Validation Middleware

// src/server/middleware/openapi-validator.ts
import { OpenAPIValidator } from 'express-openapi-validator';

export const openapiValidator = OpenAPIValidator.middleware({
  apiSpec: './openapi/openapi.yaml',
  validateRequests: true,
  validateResponses: true,
});

// Apply to routes
app.use('/api', openapiValidator);

4.2 Add Response Validation in Development

// src/server/utils/validated-response.ts
export function validatedJson<T>(res: Response, data: T): void {
  if (process.env.NODE_ENV === 'development') {
    // Validate against OpenAPI schema
    validateResponse(res.req, data);
  }
  res.json(data);
}

Phase 5: Documentation and Testing (Week 5)

5.1 Generate API Documentation

# Add to package.json
"docs:api": "npx @redocly/cli build-docs openapi/openapi.yaml -o dist/api-docs.html"

5.2 Add Contract Tests

// src/test/contract/git-api.test.ts
import { matchesSchema } from './schema-matcher';

test('GET /api/git/repository-info matches schema', async () => {
  const response = await request(app)
    .get('/api/git/repository-info')
    .query({ path: '/test/repo' });
    
  expect(response.body).toMatchSchema('GitRepositoryInfo');
});

Migration Checklist

Endpoints to Migrate

  • Git APIs (Phase 2)
    • GET /api/git/repo-info
    • GET /api/git/repository-info
    • GET /api/git/remote
    • GET /api/git/status
    • POST /api/git/event
    • GET /api/git/follow
  • Session APIs (Phase 3)
    • GET /api/sessions
    • POST /api/sessions
    • GET /api/sessions/:id
    • DELETE /api/sessions/:id
    • POST /api/sessions/:id/resize
    • POST /api/sessions/:id/input
    • GET /api/sessions/:id/stream (SSE)
  • Repository APIs (Phase 4)
    • GET /api/repositories/discover
    • GET /api/repositories/branches
  • Worktree APIs (Phase 4)
    • GET /api/worktrees
    • POST /api/worktrees
    • DELETE /api/worktrees/:branch
    • POST /api/worktrees/switch

Success Metrics

  1. Zero runtime type errors between Mac app and server
  2. 100% API documentation coverage
  3. Contract tests for all endpoints
  4. Reduced code - Remove manual type definitions
  5. Developer velocity - Faster API development with code generation

Long-term Considerations

Future Enhancements

  1. GraphQL Gateway - Add GraphQL layer on top of REST for complex queries
  2. API Versioning - Use OpenAPI to manage v1/v2 migrations
  3. Client SDKs - Generate SDKs for other platforms (iOS, CLI tools)
  4. Mock Server - Use OpenAPI spec to run mock server for testing

Breaking Changes

When making breaking changes:
  1. Version the API (e.g., /api/v2/)
  2. Deprecate old endpoints with sunset dates
  3. Generate migration guides from schema differences

Resources