vercel-labs-emulate

Local drop-in API emulation for Vercel, GitHub, and Google services in CI and no-network sandboxes

3,835 stars

Best use case

vercel-labs-emulate is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Local drop-in API emulation for Vercel, GitHub, and Google services in CI and no-network sandboxes

Teams using vercel-labs-emulate should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/vercel-labs-emulate/SKILL.md --create-dirs "https://raw.githubusercontent.com/openclaw/skills/main/skills/adisinghstudent/vercel-labs-emulate/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/vercel-labs-emulate/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How vercel-labs-emulate Compares

Feature / Agentvercel-labs-emulateStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Local drop-in API emulation for Vercel, GitHub, and Google services in CI and no-network sandboxes

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# vercel-labs/emulate

> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.

`emulate` provides fully stateful, production-fidelity local HTTP servers that replace Vercel, GitHub, and Google APIs. Designed for CI pipelines and no-network sandboxes — not mocks, real in-memory state with proper pagination, OAuth, webhooks, and cascading deletes.

## Installation

```bash
# CLI (no install needed)
npx emulate

# Or install as a dev dependency
npm install --save-dev emulate
```

## CLI Usage

```bash
# Start all services with defaults
npx emulate

# Start specific services
npx emulate --service vercel,github

# Custom base port (auto-increments per service)
npx emulate --port 3000

# Start with seed data
npx emulate --seed emulate.config.yaml

# Generate a starter config
npx emulate init

# Generate config for a specific service
npx emulate init --service github

# List available services
npx emulate list
```

Default ports:
- **Vercel** → `http://localhost:4000`
- **GitHub** → `http://localhost:4001`
- **Google** → `http://localhost:4002`

Port can also be set via `EMULATE_PORT` or `PORT` environment variables.

## Programmatic API

```typescript
import { createEmulator, type Emulator } from 'emulate'

// Start a single service
const github = await createEmulator({ service: 'github', port: 4001 })
const vercel = await createEmulator({ service: 'vercel', port: 4002 })

console.log(github.url)  // 'http://localhost:4001'
console.log(vercel.url)  // 'http://localhost:4002'

// Reset state (replays seed data)
github.reset()

// Shutdown
await github.close()
await vercel.close()
```

### Options

| Option | Default | Description |
|--------|---------|-------------|
| `service` | *(required)* | `'github'`, `'vercel'`, or `'google'` |
| `port` | `4000` | Port for the HTTP server |
| `seed` | none | Inline seed data object (same shape as YAML config) |

### Instance Methods

| Method | Description |
|--------|-------------|
| `url` | Base URL of the running server |
| `reset()` | Wipe in-memory store and replay seed data |
| `close()` | Shut down the server (returns Promise) |

## Vitest / Jest Setup

```typescript
// vitest.setup.ts
import { createEmulator, type Emulator } from 'emulate'

let github: Emulator
let vercel: Emulator

beforeAll(async () => {
  ;[github, vercel] = await Promise.all([
    createEmulator({ service: 'github', port: 4001 }),
    createEmulator({ service: 'vercel', port: 4002 }),
  ])
  process.env.GITHUB_URL = github.url
  process.env.VERCEL_URL = vercel.url
})

afterEach(() => {
  github.reset()
  vercel.reset()
})

afterAll(() => Promise.all([github.close(), vercel.close()]))
```

```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    setupFiles: ['./vitest.setup.ts'],
    environment: 'node',
  },
})
```

## Seed Configuration

Create `emulate.config.yaml` in your project root (auto-detected):

```yaml
# Auth tokens
tokens:
  my_token:
    login: admin
    scopes: [repo, user]

vercel:
  users:
    - username: developer
      name: Developer
      email: dev@example.com
  teams:
    - slug: my-team
      name: My Team
  projects:
    - name: my-app
      team: my-team
      framework: nextjs

github:
  users:
    - login: octocat
      name: The Octocat
      email: octocat@github.com
  orgs:
    - login: my-org
      name: My Organization
  repos:
    - owner: octocat
      name: hello-world
      language: JavaScript
      auto_init: true

google:
  users:
    - email: testuser@example.com
      name: Test User
  oauth_clients:
    - client_id: my-client-id.apps.googleusercontent.com
      client_secret: $GOOGLE_CLIENT_SECRET
      redirect_uris:
        - http://localhost:3000/api/auth/callback/google
```

### Inline Seed (Programmatic)

```typescript
const github = await createEmulator({
  service: 'github',
  port: 4001,
  seed: {
    users: [
      { login: 'testuser', name: 'Test User', email: 'test@example.com' }
    ],
    repos: [
      { owner: 'testuser', name: 'my-repo', language: 'TypeScript', auto_init: true }
    ],
  },
})
```

## OAuth Configuration

### GitHub OAuth Apps

```yaml
github:
  oauth_apps:
    - client_id: $GITHUB_CLIENT_ID
      client_secret: $GITHUB_CLIENT_SECRET
      name: My Web App
      redirect_uris:
        - http://localhost:3000/api/auth/callback/github
```

> Without `oauth_apps` configured, the emulator accepts any `client_id` (backward-compatible). With apps configured, strict validation is enforced.

### GitHub Apps (JWT Auth)

```yaml
github:
  apps:
    - app_id: 12345
      slug: my-github-app
      name: My GitHub App
      private_key: |
        -----BEGIN RSA PRIVATE KEY-----
        ...your PEM key...
        -----END RSA PRIVATE KEY-----
      permissions:
        contents: read
        issues: write
      events: [push, pull_request]
      installations:
        - installation_id: 100
          account: my-org
          repository_selection: all
```

Sign JWTs with `{ iss: "<app_id>" }` using RS256 — the emulator verifies the signature.

### Vercel Integrations

```yaml
vercel:
  integrations:
    - client_id: $VERCEL_CLIENT_ID
      client_secret: $VERCEL_CLIENT_SECRET
      name: My Vercel App
      redirect_uris:
        - http://localhost:3000/api/auth/callback/vercel
```

## Real-World Test Patterns

### Testing a GitHub API Client

```typescript
import { createEmulator } from 'emulate'
import { Octokit } from '@octokit/rest'

describe('GitHub integration', () => {
  let emulator: Awaited<ReturnType<typeof createEmulator>>
  let octokit: Octokit

  beforeAll(async () => {
    emulator = await createEmulator({
      service: 'github',
      port: 4001,
      seed: {
        users: [{ login: 'testuser', name: 'Test User' }],
        repos: [{ owner: 'testuser', name: 'my-repo', auto_init: true }],
      },
    })

    octokit = new Octokit({
      baseUrl: emulator.url,
      auth: 'any-token',
    })
  })

  afterEach(() => emulator.reset())
  afterAll(() => emulator.close())

  it('creates and fetches an issue', async () => {
    const { data: issue } = await octokit.issues.create({
      owner: 'testuser',
      repo: 'my-repo',
      title: 'Test issue',
      body: 'This is a test',
    })

    expect(issue.number).toBe(1)
    expect(issue.state).toBe('open')

    const { data: fetched } = await octokit.issues.get({
      owner: 'testuser',
      repo: 'my-repo',
      issue_number: issue.number,
    })

    expect(fetched.title).toBe('Test issue')
  })
})
```

### Testing a Vercel Deployment Workflow

```typescript
import { createEmulator } from 'emulate'

describe('Vercel deployment', () => {
  let emulator: Awaited<ReturnType<typeof createEmulator>>

  beforeAll(async () => {
    emulator = await createEmulator({
      service: 'vercel',
      port: 4002,
      seed: {
        users: [{ username: 'dev', email: 'dev@example.com' }],
        projects: [{ name: 'my-app', framework: 'nextjs' }],
      },
    })
    process.env.VERCEL_API_URL = emulator.url
  })

  afterEach(() => emulator.reset())
  afterAll(() => emulator.close())

  it('creates a deployment and transitions to READY', async () => {
    const res = await fetch(`${emulator.url}/v13/deployments`, {
      method: 'POST',
      headers: {
        Authorization: 'Bearer any-token',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ name: 'my-app', target: 'production' }),
    })

    const deployment = await res.json()
    expect(deployment.readyState).toBe('READY')
  })
})
```

### Testing Multiple Services Together

```typescript
import { createEmulator, type Emulator } from 'emulate'

let github: Emulator
let vercel: Emulator
let google: Emulator

beforeAll(async () => {
  ;[github, vercel, google] = await Promise.all([
    createEmulator({ service: 'github', port: 4001 }),
    createEmulator({ service: 'vercel', port: 4002 }),
    createEmulator({ service: 'google', port: 4003 }),
  ])

  // Point your app's env vars at local emulators
  process.env.GITHUB_API_URL = github.url
  process.env.VERCEL_API_URL = vercel.url
  process.env.GOOGLE_API_URL = google.url
})

afterEach(() => {
  github.reset()
  vercel.reset()
  google.reset()
})

afterAll(() => Promise.all([github.close(), vercel.close(), google.close()]))
```

## CI Configuration

### GitHub Actions

```yaml
# .github/workflows/test.yml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - name: Run tests with emulated APIs
        run: npm test
        env:
          # Emulators start in vitest.setup.ts — no extra service needed
          NODE_ENV: test
```

### Docker / No-Network Sandbox

```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Tests start emulators programmatically — no outbound network needed
RUN npm test
```

## Key API Endpoints Reference

### GitHub Emulator

```
GET    /user                                    # authenticated user
GET    /repos/:owner/:repo                      # get repo
POST   /user/repos                              # create repo
POST   /repos/:owner/:repo/issues               # create issue
PATCH  /repos/:owner/:repo/issues/:number       # update issue
POST   /repos/:owner/:repo/pulls                # create PR
PUT    /repos/:owner/:repo/pulls/:number/merge  # merge PR
GET    /search/repositories                     # search repos
GET    /search/issues                           # search issues
```

### Vercel Emulator

```
GET    /v2/user                          # authenticated user
GET    /v2/teams                         # list teams
POST   /v11/projects                     # create project
GET    /v10/projects                     # list projects
POST   /v13/deployments                  # create deployment (auto → READY)
GET    /v13/deployments/:idOrUrl         # get deployment
POST   /v10/projects/:id/env             # create env vars
GET    /v10/projects/:id/env             # list env vars
```

## Troubleshooting

**Port already in use**
```bash
# Use a different base port
npx emulate --port 5000
# Or set via env
EMULATE_PORT=5000 npx emulate
```

**Tests interfering with each other**
```typescript
// Always call reset() in afterEach, not afterAll
afterEach(() => emulator.reset())
```

**OAuth strict validation rejecting requests**
- If you configure `oauth_apps` or `integrations`, only matching `client_id` values are accepted
- Remove the `oauth_apps` block to fall back to accept-any mode

**Emulator not receiving requests from app code**
```typescript
// Make sure your app reads the URL from env at request time, not module load time
// ✅ Good
async function fetchUser() {
  return fetch(`${process.env.GITHUB_API_URL}/user`)
}

// ❌ Bad — captured before emulator starts
const API_URL = process.env.GITHUB_API_URL
```

**GitHub App JWT auth failing**
- JWT must have `{ iss: "<app_id>" }` as a string or number matching the configured `app_id`
- Must be signed RS256 with the exact private key from config
- The emulator verifies the signature — use a real RSA key pair in tests

Related Skills

elevenlabs

3891
from openclaw/skills

Converts text to natural speech using ElevenLabs for clinical and healthcare use cases. Use when generating patient instructions, discharge summaries, medication reminders, multilingual health messages, or accessible voice content for the OpenClaw Clinical Hackathon.

vercel-ai-sdk

3891
from openclaw/skills

Vercel AI SDK for building chat interfaces with streaming. Use when implementing useChat hook, handling tool calls, streaming responses, or building chat UI. Triggers on useChat, @ai-sdk/react, UIMessage, ChatStatus, streamText, toUIMessageStreamResponse, addToolOutput, onToolCall, sendMessage.

vercel-react-best-practices

3891
from openclaw/skills

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.

vercel-composition-patterns

3891
from openclaw/skills

React composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.

elevenlabs-speech

3891
from openclaw/skills

Text-to-Speech and Speech-to-Text using ElevenLabs AI. Use when the user wants to convert text to speech, transcribe voice messages, or work with voice in multiple languages. Supports high-quality AI voices and accurate transcription.

elevenlabs-conversational

3891
from openclaw/skills

Full ElevenLabs platform integration — text-to-speech, voice cloning, and Conversational AI agent creation. Not just TTS — build interactive voice agents with emotion control, streaming audio, and phone system integration. Use for voice synthesis, cloning, or building conversational AI agents.

---

3891
from openclaw/skills

name: article-factory-wechat

Content & Documentation

humanizer

3891
from openclaw/skills

Remove signs of AI-generated writing from text. Use when editing or reviewing text to make it sound more natural and human-written. Based on Wikipedia's comprehensive "Signs of AI writing" guide. Detects and fixes patterns including: inflated symbolism, promotional language, superficial -ing analyses, vague attributions, em dash overuse, rule of three, AI vocabulary words, negative parallelisms, and excessive conjunctive phrases.

Content & Documentation

find-skills

3891
from openclaw/skills

Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.

General Utilities

tavily-search

3891
from openclaw/skills

Use Tavily API for real-time web search and content extraction. Use when: user needs real-time web search results, research, or current information from the web. Requires Tavily API key.

Data & Research

baidu-search

3891
from openclaw/skills

Search the web using Baidu AI Search Engine (BDSE). Use for live information, documentation, or research topics.

Data & Research

agent-autonomy-kit

3891
from openclaw/skills

Stop waiting for prompts. Keep working.

Workflow & Productivity