Skip to content
Back to skills

Clickup Local Dev Loop

ASecurity

'Set up local development for ClickUp API integrations with testing,

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 11, 2026
developmenttypescriptbashnodetestinggitapidocumentation

Works with

  • claude code
  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned November 11, 2026

npx -y skills add pauldx/claudifying --skill clickup-local-dev-loop --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Clickup Local Dev Loop?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Clickup Local Dev Loop
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pauldx-clickup-local-dev-loop/badge)](https://www.skillsdirectory.com/skills/pauldx-clickup-local-dev-loop)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: clickup-local-dev-loop
description: 'Set up local development for ClickUp API integrations with testing,

  mocking, and hot reload.

  Trigger: "clickup dev setup", "clickup local development", "clickup dev environment",

  "develop with clickup", "clickup testing setup", "mock clickup API".

  '
allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(pnpm:*), Grep
version: 1.0.0
license: MIT
tags:
- saas
- productivity
- clickup
compatibility: Designed for Claude Code
---
# ClickUp Local Dev Loop

## Overview

Set up a fast local development workflow for ClickUp API v2 integrations with hot reload, mocking, and integration testing.

## Project Setup

```bash
mkdir my-clickup-integration && cd $_
npm init -y
npm install -D typescript tsx vitest @types/node dotenv
npx tsc --init --target ES2022 --module nodenext --outDir dist
```

```
my-clickup-integration/
├── src/
│   ├── clickup/
│   │   ├── client.ts       # ClickUp API client (see clickup-sdk-patterns)
│   │   ├── types.ts         # TypeScript interfaces
│   │   └── tasks.ts         # Task operations
│   └── index.ts
├── tests/
│   ├── mocks/
│   │   └── clickup.ts       # Mock ClickUp API responses
│   ├── unit/
│   │   └── tasks.test.ts
│   └── integration/
│       └── clickup.test.ts
├── .env.local                # Local secrets (git-ignored)
├── .env.example              # Template for team
└── package.json
```

## Environment Configuration

```bash
# .env.example (commit this)
CLICKUP_API_TOKEN=pk_your_token_here
CLICKUP_TEAM_ID=your_team_id
CLICKUP_TEST_LIST_ID=your_test_list_id

# .env.local (git-ignored, copy from .env.example)
cp .env.example .env.local
```

```json
{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:integration": "CLICKUP_LIVE=1 vitest --run tests/integration/",
    "build": "tsc",
    "typecheck": "tsc --noEmit"
  }
}
```

## Mock ClickUp API for Unit Tests

```typescript
// tests/mocks/clickup.ts
export const mockTask = {
  id: 'test_task_001',
  name: 'Test Task',
  status: { status: 'to do', color: '#d3d3d3', type: 'open' },
  priority: { id: '3', priority: 'normal', color: '#6fddff' },
  date_created: '1695000000000',
  date_updated: '1695000000000',
  due_date: null,
  assignees: [],
  tags: [],
  url: 'https://app.clickup.com/t/test_task_001',
  list: { id: '900100200300', name: 'Test List' },
  folder: { id: '456', name: 'Test Folder' },
  space: { id: '789' },
  custom_fields: [],
};

export const mockTeam = {
  teams: [{
    id: '1234567',
    name: 'Test Workspace',
    members: [{ user: { id: 183, username: 'testuser', email: 'test@example.com' } }],
  }],
};

// Mock fetch for ClickUp API calls
export function mockClickUpFetch() {
  return vi.fn(async (url: string, options?: RequestInit) => {
    const path = new URL(url).pathname.replace('/api/v2', '');

    const routes: Record<string, any> = {
      '/team': mockTeam,
      '/user': { user: { id: 183, username: 'testuser' } },
    };

    // Match dynamic routes
    if (path.match(/^\/task\/.+/)) return jsonResponse(mockTask);
    if (path.match(/^\/list\/.+\/task$/) && options?.method === 'POST') {
      return jsonResponse({ ...mockTask, ...JSON.parse(options.body as string) });
    }

    return jsonResponse(routes[path] ?? {}, routes[path] ? 200 : 404);
  });
}

function jsonResponse(data: any, status = 200) {
  return new Response(JSON.stringify(data), {
    status,
    headers: {
      'Content-Type': 'application/json',
      'X-RateLimit-Remaining': '95',
      'X-RateLimit-Limit': '100',
      'X-RateLimit-Reset': String(Math.floor(Date.now() / 1000) + 60),
    },
  });
}
```

## Unit Test Example

```typescript
// tests/unit/tasks.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mockClickUpFetch, mockTask } from '../mocks/clickup';

describe('ClickUp Task Operations', () => {
  beforeEach(() => {
    vi.stubGlobal('fetch', mockClickUpFetch());
    vi.stubEnv('CLICKUP_API_TOKEN', 'pk_test_token');
  });

  it('creates a task with required fields', async () => {
    const { createTask } = await import('../../src/clickup/tasks');
    const task = await createTask('list123', { name: 'New Task' });
    expect(task.name).toBe('New Task');
    expect(fetch).toHaveBeenCalledWith(
      expect.stringContaining('/list/list123/task'),
      expect.objectContaining({ method: 'POST' }),
    );
  });

  it('gets a task by ID', async () => {
    const { getTask } = await import('../../src/clickup/tasks');
    const task = await getTask('test_task_001');
    expect(task.id).toBe(mockTask.id);
  });
});
```

## Integration Test (Live API)

```typescript
// tests/integration/clickup.test.ts
import { describe, it, expect } from 'vitest';
import 'dotenv/config';

const LIVE = process.env.CLICKUP_LIVE === '1';

describe.skipIf(!LIVE)('ClickUp Live API', () => {
  it('authenticates and lists workspaces', async () => {
    const response = await fetch('https://api.clickup.com/api/v2/team', {
      headers: { 'Authorization': process.env.CLICKUP_API_TOKEN! },
    });
    expect(response.ok).toBe(true);
    const data = await response.json();
    expect(data.teams.length).toBeGreaterThan(0);
  });
});
```

## Error Handling

| Error | Cause | Solution |
|-------|-------|----------|
| `CLICKUP_API_TOKEN` undefined | Missing .env.local | Copy from .env.example |
| Integration tests fail | No live token | Set `CLICKUP_LIVE=1` and valid token |
| Mock not matching | Route pattern wrong | Check URL path in mock router |

## Resources

- [Vitest Documentation](https://vitest.dev/)
- [tsx (TypeScript Execute)](https://github.com/privatenumber/tsx)
- [ClickUp API Reference](https://developer.clickup.com/)

## Next Steps

See `clickup-sdk-patterns` for production-ready client patterns.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.