Clickup local dev loop
Skill ComeOnOliver/skillshub/skills/jeremylongshore/claude-code-plugins-plus-skills/clickup-local-dev-loop
π§ The right skill, one API call. AI agent skills registry with token-efficient skill resolution. 5,000+ skills from 500+ top repos.
npx -y skills add ComeOnOliver/skillshub --skill clickup-local-dev-loopAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
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".
The file declares its own license as MIT. That is the authorβs claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
5.8 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it
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
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
# .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
{
"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
// 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: '[email protected]' } }],
}],
};
// 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
// 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)
// 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
Next Steps
See clickup-sdk-patterns for production-ready client patterns.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.