From cd85d9d995e6bd70778d08e1c8f05750db972d72 Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:52:26 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 15 + README.md | 3 + commands/create-mock-server.md | 1394 +++++++++++++++++ plugin.lock.json | 93 ++ skills/skill-adapter/assets/README.md | 7 + .../skill-adapter/assets/config-template.json | 32 + .../assets/mock_server_template.yaml | 76 + .../skill-adapter/assets/openapi_example.yaml | 145 ++ skills/skill-adapter/assets/skill-schema.json | 28 + skills/skill-adapter/assets/test-data.json | 27 + skills/skill-adapter/references/README.md | 7 + .../references/best-practices.md | 69 + skills/skill-adapter/references/examples.md | 70 + skills/skill-adapter/scripts/README.md | 7 + .../skill-adapter/scripts/helper-template.sh | 42 + skills/skill-adapter/scripts/validation.sh | 32 + 16 files changed, 2047 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 commands/create-mock-server.md create mode 100644 plugin.lock.json create mode 100644 skills/skill-adapter/assets/README.md create mode 100644 skills/skill-adapter/assets/config-template.json create mode 100644 skills/skill-adapter/assets/mock_server_template.yaml create mode 100644 skills/skill-adapter/assets/openapi_example.yaml create mode 100644 skills/skill-adapter/assets/skill-schema.json create mode 100644 skills/skill-adapter/assets/test-data.json create mode 100644 skills/skill-adapter/references/README.md create mode 100644 skills/skill-adapter/references/best-practices.md create mode 100644 skills/skill-adapter/references/examples.md create mode 100644 skills/skill-adapter/scripts/README.md create mode 100755 skills/skill-adapter/scripts/helper-template.sh create mode 100755 skills/skill-adapter/scripts/validation.sh diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..aa1d1fc --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,15 @@ +{ + "name": "api-mock-server", + "description": "Create mock API servers from OpenAPI specs for testing", + "version": "1.0.0", + "author": { + "name": "Jeremy Longshore", + "email": "[email protected]" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b033b74 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# api-mock-server + +Create mock API servers from OpenAPI specs for testing diff --git a/commands/create-mock-server.md b/commands/create-mock-server.md new file mode 100644 index 0000000..16223da --- /dev/null +++ b/commands/create-mock-server.md @@ -0,0 +1,1394 @@ +--- +description: Create a mock API server for testing +shortcut: mock +--- + +# Create Mock API Server + +Generate production-grade mock API servers from OpenAPI specifications with realistic fake data, stateful operations, and customizable response scenarios. Perfect for frontend development, integration testing, and API prototyping without backend dependencies. + +## Design Decisions + +This command generates mock servers using Express.js with faker-js for realistic data generation. The implementation prioritizes: + +1. **OpenAPI-First Approach**: Generates endpoints directly from OpenAPI/Swagger specs to ensure contract compliance +2. **Stateful Mode**: Optional in-memory persistence allows testing CRUD workflows with realistic state management +3. **Scenario-Based Responses**: Conditional logic based on request parameters enables testing edge cases +4. **Network Realism**: Built-in latency simulation and error injection mimic production API behavior + +**Alternatives Considered:** +- JSON Server (limited to simple CRUD, no OpenAPI support) +- Mockoon (GUI-based, not scriptable) +- WireMock (Java-based, heavier dependency) +- Prism (excellent OpenAPI support but less flexible for custom scenarios) + +## When to Use This Command + +**USE WHEN:** +- Developing frontend features before backend APIs are ready +- Testing error handling and edge cases without affecting production +- Creating reproducible test scenarios for QA and CI/CD +- Demonstrating API functionality to stakeholders +- Validating OpenAPI specifications with working examples +- Load testing frontend without backend infrastructure + +**DON'T USE WHEN:** +- You need real business logic or database interactions +- Testing actual backend performance (use staging environment) +- Security testing (mocks bypass authentication layers) +- Integration testing with third-party services +- Production traffic routing (mocks are development tools only) + +## Prerequisites + +**Required:** +- Node.js 18+ installed +- OpenAPI 3.0+ specification file (YAML or JSON) +- Basic understanding of RESTful API concepts +- Port availability (default: 3000) + +**Optional:** +- Docker for containerized deployment +- Postman/Insomnia for API testing +- curl or httpie for command-line testing + +**Install Dependencies:** +```bash +npm install express @faker-js/faker swagger-parser cors +npm install --save-dev nodemon @types/express +``` + +## Step-by-Step Process + +### Step 1: Prepare OpenAPI Specification +Ensure your OpenAPI spec is valid and contains response schemas with examples: +```bash +# Validate OpenAPI spec +npx swagger-cli validate openapi.yaml +``` + +### Step 2: Generate Mock Server Structure +Command analyzes the OpenAPI spec and generates: +- Express route handlers for each endpoint +- Response schemas with faker.js mappings +- Middleware for CORS, logging, error handling +- Optional stateful storage layer + +### Step 3: Configure Mock Behavior +Customize mock server with: +- Response delay ranges (simulate network latency) +- Error injection rates (test error handling) +- Stateful mode (enable in-memory persistence) +- Custom scenario rules (conditional responses) + +### Step 4: Start Mock Server +Launch the generated server with hot-reload: +```bash +npm run dev # Development mode with nodemon +npm start # Production mode +``` + +### Step 5: Test and Iterate +Verify mock endpoints match OpenAPI contract: +```bash +# Test generated endpoints +curl http://localhost:3000/api/users +curl -X POST http://localhost:3000/api/users -d '{"name":"John"}' +``` + +## Output Format + +The command generates a complete Node.js project: + +``` +mock-server/ +├── server.js # Express server entry point +├── routes/ # Generated route handlers +│ ├── users.js # User resource endpoints +│ ├── products.js # Product resource endpoints +│ └── orders.js # Order resource endpoints +├── middleware/ # Request/response middleware +│ ├── cors.js # CORS configuration +│ ├── logger.js # Request logging +│ └── errorHandler.js # Error handling +├── data/ # Stateful mode storage +│ ├── store.js # In-memory data store +│ └── seed.js # Initial data seeding +├── scenarios/ # Custom response logic +│ └── conditionalLogic.js +├── config/ +│ └── server.config.js # Server configuration +├── package.json # Dependencies and scripts +├── .env.example # Environment variables template +└── README.md # Generated documentation +``` + +## Code Examples + +### Example 1: OpenAPI Spec-Based Mock Generation + +**Input OpenAPI Spec (openapi.yaml):** +```yaml +openapi: 3.0.0 +info: + title: User API + version: 1.0.0 +paths: + /api/users: + get: + summary: List all users + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + post: + summary: Create a user + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserInput' + responses: + '201': + description: User created + content: + application/json: + schema: + $ref: '#/components/schemas/User' + /api/users/{id}: + get: + summary: Get user by ID + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: User found + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '404': + description: User not found +components: + schemas: + User: + type: object + properties: + id: + type: string + format: uuid + email: + type: string + format: email + firstName: + type: string + lastName: + type: string + avatar: + type: string + format: uri + createdAt: + type: string + format: date-time + UserInput: + type: object + required: + - email + - firstName + - lastName + properties: + email: + type: string + format: email + firstName: + type: string + lastName: + type: string +``` + +**Command Usage:** +```bash +# Generate mock server from OpenAPI spec +/create-mock-server \ + --spec openapi.yaml \ + --output ./mock-api \ + --port 3000 \ + --delay 100-300 +``` + +**Generated routes/users.js:** +```javascript +import express from 'express'; +import { faker } from '@faker-js/faker'; +import { applyDelay } from '../middleware/delay.js'; + +const router = express.Router(); + +// Generate realistic fake user +function generateUser() { + return { + id: faker.string.uuid(), + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + avatar: faker.image.avatar(), + createdAt: faker.date.past().toISOString() + }; +} + +// GET /api/users - List all users +router.get('/users', applyDelay, (req, res) => { + const count = parseInt(req.query.limit) || 10; + const users = Array.from({ length: count }, generateUser); + + res.json({ + data: users, + pagination: { + total: count, + page: 1, + limit: count + } + }); +}); + +// POST /api/users - Create user +router.post('/users', applyDelay, (req, res) => { + const { email, firstName, lastName } = req.body; + + // Validation + if (!email || !firstName || !lastName) { + return res.status(400).json({ + error: 'Missing required fields', + required: ['email', 'firstName', 'lastName'] + }); + } + + const newUser = { + id: faker.string.uuid(), + email, + firstName, + lastName, + avatar: faker.image.avatar(), + createdAt: new Date().toISOString() + }; + + res.status(201).json(newUser); +}); + +// GET /api/users/:id - Get user by ID +router.get('/users/:id', applyDelay, (req, res) => { + const { id } = req.params; + + // Simulate 404 for specific IDs + if (id === '00000000-0000-0000-0000-000000000000') { + return res.status(404).json({ + error: 'User not found', + id + }); + } + + const user = { + ...generateUser(), + id // Use the requested ID + }; + + res.json(user); +}); + +export default router; +``` + +**Generated server.js:** +```javascript +import express from 'express'; +import cors from 'cors'; +import { config } from './config/server.config.js'; +import logger from './middleware/logger.js'; +import errorHandler from './middleware/errorHandler.js'; +import usersRouter from './routes/users.js'; + +const app = express(); + +// Middleware +app.use(cors()); +app.use(express.json()); +app.use(logger); + +// Routes +app.use('/api', usersRouter); + +// Health check +app.get('/health', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +// Error handling +app.use(errorHandler); + +// Start server +const PORT = process.env.PORT || config.port; +app.listen(PORT, () => { + console.log(`Mock API server running on http://localhost:${PORT}`); + console.log(`API documentation: http://localhost:${PORT}/api-docs`); +}); +``` + +### Example 2: Stateful Mock Server with CRUD Operations + +**Command Usage:** +```bash +# Generate stateful mock server with persistent data +/create-mock-server \ + --spec openapi.yaml \ + --output ./mock-api \ + --stateful \ + --seed 50 \ + --port 3000 +``` + +**Generated data/store.js (In-Memory Database):** +```javascript +import { faker } from '@faker-js/faker'; + +class MockStore { + constructor() { + this.collections = { + users: new Map(), + products: new Map(), + orders: new Map() + }; + this.idCounters = { + users: 1, + products: 1, + orders: 1 + }; + } + + // Generic CRUD operations + create(collection, data) { + const id = data.id || String(this.idCounters[collection]++); + const record = { + ...data, + id, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + this.collections[collection].set(id, record); + return record; + } + + findAll(collection, filters = {}) { + const records = Array.from(this.collections[collection].values()); + + // Apply filters + return records.filter(record => { + return Object.entries(filters).every(([key, value]) => { + if (value === undefined) return true; + return String(record[key]).toLowerCase().includes(String(value).toLowerCase()); + }); + }); + } + + findById(collection, id) { + return this.collections[collection].get(id) || null; + } + + update(collection, id, data) { + const existing = this.findById(collection, id); + if (!existing) return null; + + const updated = { + ...existing, + ...data, + id, // Prevent ID change + updatedAt: new Date().toISOString() + }; + this.collections[collection].set(id, updated); + return updated; + } + + delete(collection, id) { + const record = this.findById(collection, id); + if (!record) return false; + return this.collections[collection].delete(id); + } + + count(collection) { + return this.collections[collection].size; + } + + clear(collection) { + this.collections[collection].clear(); + } + + // Seed data + seed(collection, count, generator) { + for (let i = 0; i < count; i++) { + this.create(collection, generator()); + } + } +} + +// Singleton instance +export const store = new MockStore(); +``` + +**Generated data/seed.js:** +```javascript +import { faker } from '@faker-js/faker'; +import { store } from './store.js'; + +export function seedDatabase() { + console.log('Seeding database...'); + + // Seed users + store.seed('users', 50, () => ({ + email: faker.internet.email(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + avatar: faker.image.avatar(), + role: faker.helpers.arrayElement(['user', 'admin', 'moderator']), + status: faker.helpers.arrayElement(['active', 'inactive', 'suspended']) + })); + + // Seed products + store.seed('products', 100, () => ({ + name: faker.commerce.productName(), + description: faker.commerce.productDescription(), + price: parseFloat(faker.commerce.price()), + category: faker.commerce.department(), + inStock: faker.datatype.boolean(), + sku: faker.string.alphanumeric(8).toUpperCase(), + image: faker.image.urlLoremFlickr({ category: 'product' }) + })); + + // Seed orders + const users = store.findAll('users'); + const products = store.findAll('products'); + + store.seed('orders', 200, () => { + const user = faker.helpers.arrayElement(users); + const orderItems = faker.helpers.arrayElements(products, { min: 1, max: 5 }); + const total = orderItems.reduce((sum, item) => sum + item.price, 0); + + return { + userId: user.id, + items: orderItems.map(item => ({ + productId: item.id, + quantity: faker.number.int({ min: 1, max: 5 }), + price: item.price + })), + total: parseFloat(total.toFixed(2)), + status: faker.helpers.arrayElement(['pending', 'processing', 'shipped', 'delivered', 'cancelled']), + shippingAddress: { + street: faker.location.streetAddress(), + city: faker.location.city(), + state: faker.location.state(), + zip: faker.location.zipCode(), + country: faker.location.country() + } + }; + }); + + console.log(`Seeded ${store.count('users')} users`); + console.log(`Seeded ${store.count('products')} products`); + console.log(`Seeded ${store.count('orders')} orders`); +} +``` + +**Stateful routes/users.js:** +```javascript +import express from 'express'; +import { store } from '../data/store.js'; +import { applyDelay } from '../middleware/delay.js'; + +const router = express.Router(); + +// GET /api/users - List with filtering and pagination +router.get('/users', applyDelay, (req, res) => { + const { search, role, status, page = 1, limit = 20 } = req.query; + + // Apply filters + const filters = {}; + if (search) filters.firstName = search; // Partial match + if (role) filters.role = role; + if (status) filters.status = status; + + const allUsers = store.findAll('users', filters); + + // Pagination + const pageNum = parseInt(page); + const limitNum = parseInt(limit); + const startIndex = (pageNum - 1) * limitNum; + const endIndex = startIndex + limitNum; + const paginatedUsers = allUsers.slice(startIndex, endIndex); + + res.json({ + data: paginatedUsers, + pagination: { + total: allUsers.length, + page: pageNum, + limit: limitNum, + totalPages: Math.ceil(allUsers.length / limitNum) + } + }); +}); + +// POST /api/users - Create with validation +router.post('/users', applyDelay, (req, res) => { + const { email, firstName, lastName, role = 'user' } = req.body; + + if (!email || !firstName || !lastName) { + return res.status(400).json({ + error: 'Validation failed', + details: { + email: !email ? 'Email is required' : null, + firstName: !firstName ? 'First name is required' : null, + lastName: !lastName ? 'Last name is required' : null + } + }); + } + + // Check for duplicate email + const existing = store.findAll('users').find(u => u.email === email); + if (existing) { + return res.status(409).json({ + error: 'User with this email already exists', + existingId: existing.id + }); + } + + const newUser = store.create('users', { + email, + firstName, + lastName, + role, + status: 'active', + avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${email}` + }); + + res.status(201).json(newUser); +}); + +// GET /api/users/:id - Get by ID +router.get('/users/:id', applyDelay, (req, res) => { + const user = store.findById('users', req.params.id); + + if (!user) { + return res.status(404).json({ + error: 'User not found', + id: req.params.id + }); + } + + res.json(user); +}); + +// PUT /api/users/:id - Update +router.put('/users/:id', applyDelay, (req, res) => { + const { email, firstName, lastName, role, status } = req.body; + + const updated = store.update('users', req.params.id, { + email, + firstName, + lastName, + role, + status + }); + + if (!updated) { + return res.status(404).json({ + error: 'User not found', + id: req.params.id + }); + } + + res.json(updated); +}); + +// DELETE /api/users/:id +router.delete('/users/:id', applyDelay, (req, res) => { + const deleted = store.delete('users', req.params.id); + + if (!deleted) { + return res.status(404).json({ + error: 'User not found', + id: req.params.id + }); + } + + res.status(204).send(); +}); + +export default router; +``` + +### Example 3: Dynamic Response Scenarios with Conditional Logic + +**Command Usage:** +```bash +# Generate mock server with custom scenarios +/create-mock-server \ + --spec openapi.yaml \ + --output ./mock-api \ + --scenarios \ + --error-rate 5 \ + --port 3000 +``` + +**Generated scenarios/conditionalLogic.js:** +```javascript +/** + * Scenario-based response logic for realistic API behavior + */ + +// Simulate different response scenarios based on request data +export const scenarios = { + + // Payment processing scenarios + payment: { + // Test card numbers trigger specific responses + testCards: { + '4242424242424242': { status: 'success', message: 'Payment approved' }, + '4000000000000002': { status: 'declined', message: 'Card declined' }, + '4000000000009995': { status: 'error', message: 'Insufficient funds' }, + '4000000000000069': { status: 'error', message: 'Card expired' }, + '4000000000000127': { status: 'error', message: 'Incorrect CVC' } + }, + + handler: (cardNumber, amount) => { + // Check test cards + if (scenarios.payment.testCards[cardNumber]) { + return scenarios.payment.testCards[cardNumber]; + } + + // Amount-based scenarios + if (amount > 10000) { + return { + status: 'pending', + message: 'Large transaction requires manual review', + reviewId: `REV-${Date.now()}` + }; + } + + // Random 5% failure rate for realistic testing + if (Math.random() < 0.05) { + return { + status: 'error', + message: 'Payment gateway timeout', + retryable: true + }; + } + + return { + status: 'success', + message: 'Payment approved', + transactionId: `TXN-${Date.now()}` + }; + } + }, + + // Authentication scenarios + auth: { + // Test credentials + testUsers: { + 'admin@example.com': { password: 'admin123', role: 'admin', mfaEnabled: true }, + 'user@example.com': { password: 'user123', role: 'user', mfaEnabled: false }, + 'locked@example.com': { password: 'locked123', locked: true }, + 'expired@example.com': { password: 'expired123', passwordExpired: true } + }, + + handler: (email, password) => { + const user = scenarios.auth.testUsers[email]; + + if (!user) { + return { + status: 401, + error: 'Invalid credentials' + }; + } + + if (user.locked) { + return { + status: 403, + error: 'Account locked due to multiple failed login attempts', + unlockTime: new Date(Date.now() + 3600000).toISOString() + }; + } + + if (user.passwordExpired) { + return { + status: 403, + error: 'Password expired', + passwordResetRequired: true + }; + } + + if (user.password !== password) { + return { + status: 401, + error: 'Invalid credentials', + attemptsRemaining: 3 + }; + } + + if (user.mfaEnabled) { + return { + status: 200, + mfaRequired: true, + mfaToken: `MFA-${Date.now()}` + }; + } + + return { + status: 200, + token: `JWT-${Date.now()}`, + user: { email, role: user.role } + }; + } + }, + + // Rate limiting scenarios + rateLimit: { + requestCounts: new Map(), + + handler: (clientId, limit = 100, window = 60000) => { + const now = Date.now(); + const key = `${clientId}-${Math.floor(now / window)}`; + + const count = scenarios.rateLimit.requestCounts.get(key) || 0; + scenarios.rateLimit.requestCounts.set(key, count + 1); + + // Clean old entries + for (const [k, v] of scenarios.rateLimit.requestCounts.entries()) { + if (k.split('-')[1] < Math.floor((now - window * 2) / window)) { + scenarios.rateLimit.requestCounts.delete(k); + } + } + + if (count >= limit) { + return { + status: 429, + error: 'Rate limit exceeded', + retryAfter: Math.ceil((window - (now % window)) / 1000), + limit, + remaining: 0 + }; + } + + return { + status: 200, + limit, + remaining: limit - count - 1, + reset: Math.floor((now + window) / 1000) + }; + } + }, + + // Error injection for testing + errorInjection: { + handler: (errorRate = 0.05) => { + const rand = Math.random(); + + if (rand < errorRate * 0.2) { + return { status: 500, error: 'Internal server error' }; + } + + if (rand < errorRate * 0.4) { + return { status: 503, error: 'Service temporarily unavailable', retryAfter: 30 }; + } + + if (rand < errorRate * 0.6) { + return { status: 504, error: 'Gateway timeout' }; + } + + if (rand < errorRate) { + return { status: 502, error: 'Bad gateway' }; + } + + return null; // No error + } + }, + + // Conditional responses based on headers + headerBased: { + handler: (headers) => { + // API version handling + if (headers['api-version'] === '1.0') { + return { format: 'legacy', deprecationWarning: true }; + } + + // Feature flags + const features = (headers['x-features'] || '').split(','); + const enabledFeatures = {}; + features.forEach(f => { + enabledFeatures[f.trim()] = true; + }); + + // A/B testing + const variant = headers['x-variant'] || 'control'; + + return { + format: 'current', + features: enabledFeatures, + variant + }; + } + } +}; + +// Middleware to apply scenarios +export function applyScenarios(req, res, next) { + // Add scenario helpers to request + req.scenarios = scenarios; + + // Add error injection + const injectedError = scenarios.errorInjection.handler( + parseFloat(process.env.ERROR_RATE) || 0.05 + ); + + if (injectedError) { + return res.status(injectedError.status).json(injectedError); + } + + // Add rate limiting + const clientId = req.ip || req.headers['x-client-id'] || 'anonymous'; + const rateLimitResult = scenarios.rateLimit.handler(clientId); + + res.set({ + 'X-RateLimit-Limit': rateLimitResult.limit, + 'X-RateLimit-Remaining': rateLimitResult.remaining, + 'X-RateLimit-Reset': rateLimitResult.reset + }); + + if (rateLimitResult.status === 429) { + res.set('Retry-After', rateLimitResult.retryAfter); + return res.status(429).json({ + error: rateLimitResult.error, + retryAfter: rateLimitResult.retryAfter + }); + } + + next(); +} +``` + +**Using scenarios in routes/payments.js:** +```javascript +import express from 'express'; +import { scenarios } from '../scenarios/conditionalLogic.js'; +import { applyDelay } from '../middleware/delay.js'; + +const router = express.Router(); + +// POST /api/payments +router.post('/payments', applyDelay, (req, res) => { + const { cardNumber, amount, currency = 'USD' } = req.body; + + // Validation + if (!cardNumber || !amount) { + return res.status(400).json({ + error: 'Missing required fields', + required: ['cardNumber', 'amount'] + }); + } + + // Apply payment scenario logic + const result = scenarios.payment.handler(cardNumber, amount); + + const response = { + ...result, + amount, + currency, + timestamp: new Date().toISOString() + }; + + // Set status code based on result + const statusCode = result.status === 'success' ? 200 : + result.status === 'pending' ? 202 : + result.status === 'declined' ? 402 : 400; + + res.status(statusCode).json(response); +}); + +// POST /api/auth/login +router.post('/auth/login', applyDelay, (req, res) => { + const { email, password } = req.body; + + const result = scenarios.auth.handler(email, password); + + res.status(result.status).json(result); +}); + +export default router; +``` + +## Configuration Options + +### Command-Line Flags + +```bash +/create-mock-server [OPTIONS] + +OPTIONS: + --spec OpenAPI specification file (YAML/JSON) + Required. Supports OpenAPI 3.0+ and Swagger 2.0 + + --output Output directory for generated server + Default: ./mock-server + + --port Server port + Default: 3000 + + --delay Response delay range in milliseconds + Format: "min-max" (e.g., "100-300") + Default: "50-150" + + --stateful Enable stateful mode with in-memory persistence + Default: false (stateless) + + --seed Number of records to seed per collection (stateful mode) + Default: 20 + + --scenarios Enable conditional response scenarios + Default: false + + --error-rate Error injection rate (0-100) + Default: 0 (no errors) + + --cors Enable CORS with permissive settings + Default: true + + --log-level Logging verbosity: error, warn, info, debug + Default: info + + --hot-reload Enable hot-reload with nodemon + Default: true for dev mode + +EXAMPLES: + # Basic stateless mock + /create-mock-server --spec api.yaml --port 3000 + + # Stateful with 100 seed records + /create-mock-server --spec api.yaml --stateful --seed 100 + + # With error injection and scenarios + /create-mock-server --spec api.yaml --scenarios --error-rate 10 --delay 200-500 + + # Production-like config + /create-mock-server --spec api.yaml --delay 50-200 --log-level warn --no-hot-reload +``` + +### Environment Variables + +Create `.env` file in generated project: + +```bash +# Server configuration +PORT=3000 +NODE_ENV=development +LOG_LEVEL=info + +# Mock behavior +STATEFUL_MODE=true +ERROR_RATE=0.05 +MIN_DELAY=100 +MAX_DELAY=300 + +# CORS settings +CORS_ORIGIN=* +CORS_CREDENTIALS=false + +# Data seeding +SEED_USERS=50 +SEED_PRODUCTS=100 +SEED_ORDERS=200 + +# Feature flags +ENABLE_SCENARIOS=true +ENABLE_RATE_LIMITING=true +RATE_LIMIT_REQUESTS=100 +RATE_LIMIT_WINDOW=60000 + +# Authentication (for protected mock endpoints) +API_KEY=mock-api-key-12345 +REQUIRE_AUTH=false +``` + +## Error Handling + +### Common Errors and Solutions + +**1. OpenAPI Spec Validation Failed** +``` +Error: Invalid OpenAPI specification + - Missing required field: info.version + - Invalid path parameter syntax +``` +**Solution:** +```bash +# Validate spec first +npx swagger-cli validate openapi.yaml + +# Check for common issues: +# - Missing required fields (info, paths) +# - Invalid references ($ref) +# - Incorrect schema syntax +``` + +**2. Port Already in Use** +``` +Error: listen EADDRINUSE: address already in use :::3000 +``` +**Solution:** +```bash +# Find process using port +lsof -i :3000 + +# Kill process or use different port +/create-mock-server --spec api.yaml --port 3001 +``` + +**3. Faker.js Schema Mapping Failed** +``` +Warning: Could not map schema type 'customType' to faker method +``` +**Solution:** +- Add custom mapping in generated `config/faker-mappings.js` +- Or use default faker method as fallback +```javascript +// Custom mapping example +const customMappings = { + 'ipAddress': () => faker.internet.ip(), + 'macAddress': () => faker.internet.mac(), + 'customType': () => faker.string.alphanumeric(10) +}; +``` + +**4. Stateful Store Memory Limit** +``` +Error: JavaScript heap out of memory +``` +**Solution:** +```bash +# Increase Node.js heap size +NODE_OPTIONS="--max-old-space-size=4096" npm start + +# Or reduce seed count +/create-mock-server --spec api.yaml --stateful --seed 50 +``` + +**5. CORS Issues in Browser** +``` +Access to fetch at 'http://localhost:3000' has been blocked by CORS policy +``` +**Solution:** +```javascript +// Generated CORS config already permissive, but can customize: +// middleware/cors.js +export const corsOptions = { + origin: process.env.CORS_ORIGIN || '*', + credentials: process.env.CORS_CREDENTIALS === 'true', + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'] +}; +``` + +## Best Practices + +### DO: +1. Always validate OpenAPI spec before generating mock server +2. Use stateful mode for testing complex CRUD workflows +3. Set realistic delay ranges (100-300ms) to catch race conditions +4. Enable error injection (5-10%) to test error handling +5. Seed sufficient data (50+ records) for pagination testing +6. Use scenario-based responses for authentication and payments +7. Add request logging for debugging +8. Version your OpenAPI spec alongside mock server +9. Document test scenarios in README +10. Use Docker for consistent mock server deployment + +### DON'T: +1. Don't rely on mocks for performance testing (no real database) +2. Don't commit generated node_modules to version control +3. Don't expose mock servers to public internet (development only) +4. Don't use real production data in mock servers +5. Don't skip response schema validation +6. Don't hardcode test data (use faker.js for variety) +7. Don't forget to update mocks when API spec changes +8. Don't use mocks as production API substitutes + +### TIPS: +- Use `--seed` generously for realistic datasets +- Combine `--delay` with `--error-rate` for chaos testing +- Create separate mock configs for different test scenarios +- Use Docker Compose for multi-service mock environments +- Add health check endpoint for orchestration tools +- Export Postman collection from generated server for QA +- Use Git tags to version mock server with API spec versions + +## Related Commands + +- `/validate-openapi` - Validate OpenAPI specification before mocking +- `/test-api-endpoints` - Test generated mock endpoints automatically +- `/generate-api-client` - Generate SDK clients from same OpenAPI spec +- `/api-load-test` - Load test mock server to verify performance +- `/create-api-docs` - Generate interactive documentation from OpenAPI spec +- `/api-contract-test` - Validate mock responses match OpenAPI contract + +## Performance Considerations + +### Mock Server Performance +- **Stateless Mode**: Handles 1000+ req/sec on modest hardware +- **Stateful Mode**: 500-800 req/sec with in-memory store (10k records) +- **With Delay Simulation**: Throughput = 1000 / avg_delay_ms requests/sec +- **Memory Usage**: ~50MB base, +5MB per 10k seeded records + +### Optimization Tips +1. Use stateless mode for high-concurrency tests +2. Reduce seed count if memory is constrained +3. Disable logging in production mode (`LOG_LEVEL=error`) +4. Use Redis for stateful storage beyond 100k records +5. Enable clustering for multi-core utilization: +```javascript +// server.js with clustering +import cluster from 'cluster'; +import os from 'os'; + +if (cluster.isPrimary) { + const numCPUs = os.cpus().length; + for (let i = 0; i < numCPUs; i++) { + cluster.fork(); + } +} else { + // Start Express server + app.listen(PORT); +} +``` + +### Monitoring Mock Performance +```bash +# Request latency +curl -w "@curl-format.txt" http://localhost:3000/api/users + +# Concurrent requests +ab -n 1000 -c 10 http://localhost:3000/api/users + +# Memory usage +NODE_ENV=production node --inspect server.js +# Visit chrome://inspect for profiling +``` + +## Security Notes for Mock Data + +### Important Security Warnings +1. **Never use production data** - Mocks should use synthetic data only +2. **No real credentials** - All passwords, API keys, tokens should be fake +3. **No PII leakage** - Use faker.js to generate realistic but fake personal data +4. **Disable in production** - Mock servers are development tools only +5. **Network isolation** - Bind to localhost or use Docker networks + +### Secure Mock Configuration +```javascript +// config/security.js +export const securityConfig = { + // Bind to localhost only (not 0.0.0.0) + host: process.env.HOST || '127.0.0.1', + + // Disable in production + enabled: process.env.NODE_ENV !== 'production', + + // Optional API key for mock server access + apiKey: process.env.MOCK_API_KEY, + + // Rate limiting + rateLimit: { + windowMs: 60000, + max: 100 + } +}; + +// Middleware +if (securityConfig.apiKey) { + app.use((req, res, next) => { + const providedKey = req.headers['x-api-key']; + if (providedKey !== securityConfig.apiKey) { + return res.status(401).json({ error: 'Invalid API key' }); + } + next(); + }); +} +``` + +### Data Sanitization +```javascript +// Ensure no real data leaks +import { faker } from '@faker-js/faker'; + +faker.seed(12345); // Reproducible fake data + +// Sanitize any user input +function sanitizeForMock(data) { + return { + ...data, + email: faker.internet.email(), + phone: faker.phone.number(), + ssn: 'XXX-XX-' + faker.string.numeric(4), + creditCard: '****-****-****-' + faker.string.numeric(4) + }; +} +``` + +## Troubleshooting Guide + +### Issue: Mock server not starting +**Symptoms:** Port binding errors, module not found +**Diagnosis:** +```bash +# Check port availability +netstat -an | grep 3000 + +# Check Node version +node --version # Should be 18+ + +# Check dependencies +npm list +``` +**Resolution:** +- Kill process on port: `kill -9 $(lsof -t -i:3000)` +- Reinstall dependencies: `rm -rf node_modules && npm install` +- Use different port: `PORT=3001 npm start` + +### Issue: OpenAPI spec not generating routes +**Symptoms:** No routes registered, empty routes folder +**Diagnosis:** +```bash +# Validate OpenAPI spec +npx swagger-cli validate openapi.yaml + +# Check for required fields +jq '.paths' openapi.yaml +``` +**Resolution:** +- Ensure `paths` section exists with at least one endpoint +- Check `$ref` references are valid +- Verify schema components are defined + +### Issue: Faker.js generating same data +**Symptoms:** Identical data on each request +**Diagnosis:** Seed is set globally +**Resolution:** +```javascript +// Remove global seed or set per-request +// Bad: +faker.seed(12345); // At module level + +// Good: +function generateUser() { + faker.seed(Date.now() + Math.random()); // Unique seed + return { ... }; +} +``` + +### Issue: Stateful store losing data +**Symptoms:** Data disappears on server restart +**Diagnosis:** In-memory store is ephemeral +**Resolution:** +```javascript +// Add persistence to disk +import fs from 'fs'; + +class PersistentStore extends MockStore { + constructor(filename = 'mock-data.json') { + super(); + this.filename = filename; + this.load(); + } + + save() { + const data = { + users: Array.from(this.collections.users.entries()), + products: Array.from(this.collections.products.entries()) + }; + fs.writeFileSync(this.filename, JSON.stringify(data, null, 2)); + } + + load() { + if (fs.existsSync(this.filename)) { + const data = JSON.parse(fs.readFileSync(this.filename, 'utf8')); + this.collections.users = new Map(data.users); + this.collections.products = new Map(data.products); + } + } +} + +// Save on changes +router.post('/users', (req, res) => { + const user = store.create('users', req.body); + store.save(); // Persist to disk + res.json(user); +}); +``` + +### Issue: CORS errors in production +**Symptoms:** Browser blocks requests +**Diagnosis:** CORS origin mismatch +**Resolution:** +```javascript +// Whitelist specific origins +const corsOptions = { + origin: (origin, callback) => { + const whitelist = [ + 'http://localhost:3000', + 'https://app.example.com' + ]; + if (!origin || whitelist.includes(origin)) { + callback(null, true); + } else { + callback(new Error('Not allowed by CORS')); + } + } +}; + +app.use(cors(corsOptions)); +``` + +## Version History + +### v1.2.0 (Current) +- Added scenario-based conditional responses +- Implemented rate limiting simulation +- Enhanced error injection with configurable rates +- Added persistent storage option for stateful mode +- Improved OpenAPI 3.1 support + +### v1.1.0 +- Added stateful mode with in-memory CRUD operations +- Implemented data seeding with faker.js +- Added pagination and filtering support +- Enhanced delay simulation with configurable ranges + +### v1.0.0 +- Initial release with OpenAPI 3.0 support +- Basic stateless mock generation +- Faker.js integration for realistic data +- Express.js server generation +- CORS and logging middleware + +### Planned Features (v1.3.0) +- WebSocket mock support +- GraphQL mock generation +- Redis-backed stateful storage +- Request/response recording for replay +- Docker Compose generation +- Postman collection export +- OpenAPI 3.1 webhooks support diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..78042d6 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,93 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:jeremylongshore/claude-code-plugins-plus:plugins/api-development/api-mock-server", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "a7389e09d54ac786d04d6d609f55b4baebb7ea7e", + "treeHash": "25333cdd9676d3cced0a2e09cb6c2da53b381292ba8c4c454071edb482ebaf73", + "generatedAt": "2025-11-28T10:18:07.308005Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "api-mock-server", + "description": "Create mock API servers from OpenAPI specs for testing", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "4e940cba3477f36075704085ee043a3b22a5ae972484bae7d8dbd8c44f1e12e8" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "ddaca7911415dc45dafc5c247a0ab31aaa5aca0ec7d89f77a4aa514cd11d083b" + }, + { + "path": "commands/create-mock-server.md", + "sha256": "fb4d0187ef110d9f9686b93b683ab58d2d810ac8957497cde8000c87bb821f10" + }, + { + "path": "skills/skill-adapter/references/examples.md", + "sha256": "922bbc3c4ebf38b76f515b5c1998ebde6bf902233e00e2c5a0e9176f975a7572" + }, + { + "path": "skills/skill-adapter/references/best-practices.md", + "sha256": "c8f32b3566252f50daacd346d7045a1060c718ef5cfb07c55a0f2dec5f1fb39e" + }, + { + "path": "skills/skill-adapter/references/README.md", + "sha256": "a97fb38c76cea79142976527b2696e887ca7ce9526f38db1690997a2ce2a57ed" + }, + { + "path": "skills/skill-adapter/scripts/helper-template.sh", + "sha256": "0881d5660a8a7045550d09ae0acc15642c24b70de6f08808120f47f86ccdf077" + }, + { + "path": "skills/skill-adapter/scripts/validation.sh", + "sha256": "92551a29a7f512d2036e4f1fb46c2a3dc6bff0f7dde4a9f699533e446db48502" + }, + { + "path": "skills/skill-adapter/scripts/README.md", + "sha256": "e487693b9ebaa4e9fadfc10592a2248512c7d02e46ead4f1725f38f300c6901a" + }, + { + "path": "skills/skill-adapter/assets/test-data.json", + "sha256": "ac17dca3d6e253a5f39f2a2f1b388e5146043756b05d9ce7ac53a0042eee139d" + }, + { + "path": "skills/skill-adapter/assets/README.md", + "sha256": "061e36cbbb0702e59b317d1a87a7f1feb634298ce387fd63b4dd731ceebc5d90" + }, + { + "path": "skills/skill-adapter/assets/mock_server_template.yaml", + "sha256": "4fedeaad0a28edb7d5e58ee5b6af131cc20a5f7c1503ba555a2e553e1b2232cb" + }, + { + "path": "skills/skill-adapter/assets/openapi_example.yaml", + "sha256": "804c5ab9edd579aa71b511bf687c70d8d7e6840700d0265aa22d08896bb0530a" + }, + { + "path": "skills/skill-adapter/assets/skill-schema.json", + "sha256": "f5639ba823a24c9ac4fb21444c0717b7aefde1a4993682897f5bf544f863c2cd" + }, + { + "path": "skills/skill-adapter/assets/config-template.json", + "sha256": "0c2ba33d2d3c5ccb266c0848fc43caa68a2aa6a80ff315d4b378352711f83e1c" + } + ], + "dirSha256": "25333cdd9676d3cced0a2e09cb6c2da53b381292ba8c4c454071edb482ebaf73" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/skill-adapter/assets/README.md b/skills/skill-adapter/assets/README.md new file mode 100644 index 0000000..f641aab --- /dev/null +++ b/skills/skill-adapter/assets/README.md @@ -0,0 +1,7 @@ +# Assets + +Bundled resources for api-mock-server skill + +- [ ] mock_server_template.yaml: A template YAML file for configuring the mock server, including placeholders for the OpenAPI spec, response configurations, and other settings. +- [ ] example_responses/: A directory containing example responses for different API endpoints, including successful responses, error responses, and edge cases. +- [ ] openapi_example.yaml: Example OpenAPI spec file to be used as a starting point. diff --git a/skills/skill-adapter/assets/config-template.json b/skills/skill-adapter/assets/config-template.json new file mode 100644 index 0000000..16f1712 --- /dev/null +++ b/skills/skill-adapter/assets/config-template.json @@ -0,0 +1,32 @@ +{ + "skill": { + "name": "skill-name", + "version": "1.0.0", + "enabled": true, + "settings": { + "verbose": false, + "autoActivate": true, + "toolRestrictions": true + } + }, + "triggers": { + "keywords": [ + "example-trigger-1", + "example-trigger-2" + ], + "patterns": [] + }, + "tools": { + "allowed": [ + "Read", + "Grep", + "Bash" + ], + "restricted": [] + }, + "metadata": { + "author": "Plugin Author", + "category": "general", + "tags": [] + } +} diff --git a/skills/skill-adapter/assets/mock_server_template.yaml b/skills/skill-adapter/assets/mock_server_template.yaml new file mode 100644 index 0000000..3509739 --- /dev/null +++ b/skills/skill-adapter/assets/mock_server_template.yaml @@ -0,0 +1,76 @@ +# Configuration for the API Mock Server Plugin + +# --- OpenAPI Specification --- +# Path to your OpenAPI (Swagger) specification file (YAML or JSON). +# This file defines the API endpoints, request parameters, and response schemas. +openapi_spec_path: "REPLACE_ME/path/to/your/openapi.yaml" + +# --- Server Settings --- +# Host and port for the mock server to listen on. +host: "0.0.0.0" # Listen on all interfaces +port: 8080 + +# --- Response Configuration --- +# Default settings for generating responses. These can be overridden per-endpoint. +default_response: + # Simulate network latency (in milliseconds). + latency: 50 # milliseconds + # Probability of returning an error (0.0 to 1.0). + error_rate: 0.05 # 5% chance of error + # Default HTTP status code to return for successful requests. + success_status_code: 200 + +# Endpoint-specific overrides. Use the OpenAPI path as the key. +endpoints: + "/users": + # Override the default latency for this endpoint. + latency: 100 + # Example of a custom response for a specific HTTP method and status code. + GET: + 200: + # Custom response body (JSON). If not specified, will generate a fake response. + body: + message: "Successfully retrieved users" + users: + - id: "user1" + name: "John Doe" + - id: "user2" + name: "Jane Smith" + 500: + body: + error: "Simulated server error" + status_code: 500 + + "/products/{product_id}": + GET: + 404: + body: + error: "Product not found" + status_code: 404 + + +# --- Data Persistence --- +# Whether to persist data between requests (stateful mocking). +stateful: true +# Path to a file where data will be stored. If empty, data will be stored in memory. +data_file: "REPLACE_ME/path/to/data.json" # Example: data.json + +# --- Security --- +# API Key authentication (example). +api_key: + enabled: false + # Header name for the API Key. + header_name: "X-API-Key" + # Valid API Key value. + valid_key: "YOUR_API_KEY_HERE" + +# --- Logging --- +# Configure logging level. Options: DEBUG, INFO, WARNING, ERROR, CRITICAL +log_level: INFO + +# --- Advanced Settings --- +# CORS configuration (optional). +cors: + enabled: true + # Allowed origins (e.g., "http://localhost:3000"). Use "*" to allow all origins (not recommended for production). + allowed_origins: "*" \ No newline at end of file diff --git a/skills/skill-adapter/assets/openapi_example.yaml b/skills/skill-adapter/assets/openapi_example.yaml new file mode 100644 index 0000000..f5d0b7d --- /dev/null +++ b/skills/skill-adapter/assets/openapi_example.yaml @@ -0,0 +1,145 @@ +# openapi_example.yaml +# This file defines an example OpenAPI specification for the API Mock Server plugin. +# It's designed to be a starting point and should be customized to your specific API. + +openapi: 3.0.0 +info: + title: Example API + version: 1.0.0 + description: A simple example API for demonstration purposes. + +servers: + - url: http://localhost:8080 # The base URL where the mock server will run. Change if needed. + description: Local development server + +paths: + /users: + get: + summary: Get a list of users + description: Returns a list of all users. + operationId: getUsers + responses: + '200': + description: A list of users. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + '500': + description: Internal server error. Simulates a possible error response. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error message. + example: "Internal Server Error" + post: + summary: Create a new user + description: Creates a new user. + operationId: createUser + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserCreate' + responses: + '201': + description: User created successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Bad request. Invalid input. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error message. + example: "Invalid input data" + + /users/{userId}: + get: + summary: Get a user by ID + description: Returns a single user by their ID. + operationId: getUserById + parameters: + - in: path + name: userId + schema: + type: integer + required: true + description: The ID of the user to retrieve. + responses: + '200': + description: The user object. + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '404': + description: User not found. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error message. + example: "User not found" + +components: + schemas: + User: + type: object + properties: + id: + type: integer + description: The user's ID. + example: 123 + name: + type: string + description: The user's name. + example: "John Doe" + email: + type: string + format: email + description: The user's email address. + example: "john.doe@example.com" + # Add more user properties as needed + # Example: + # role: + # type: string + # description: The user's role. + # example: "admin" + required: + - id + - name + - email + + UserCreate: + type: object + properties: + name: + type: string + description: The user's name. + example: "Jane Doe" + email: + type: string + format: email + description: The user's email address. + example: "jane.doe@example.com" + # Add more user creation properties as needed + required: + - name + - email \ No newline at end of file diff --git a/skills/skill-adapter/assets/skill-schema.json b/skills/skill-adapter/assets/skill-schema.json new file mode 100644 index 0000000..8dc154c --- /dev/null +++ b/skills/skill-adapter/assets/skill-schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Claude Skill Configuration", + "type": "object", + "required": ["name", "description"], + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "maxLength": 64, + "description": "Skill identifier (lowercase, hyphens only)" + }, + "description": { + "type": "string", + "maxLength": 1024, + "description": "What the skill does and when to use it" + }, + "allowed-tools": { + "type": "string", + "description": "Comma-separated list of allowed tools" + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "Semantic version (x.y.z)" + } + } +} diff --git a/skills/skill-adapter/assets/test-data.json b/skills/skill-adapter/assets/test-data.json new file mode 100644 index 0000000..f0cd871 --- /dev/null +++ b/skills/skill-adapter/assets/test-data.json @@ -0,0 +1,27 @@ +{ + "testCases": [ + { + "name": "Basic activation test", + "input": "trigger phrase example", + "expected": { + "activated": true, + "toolsUsed": ["Read", "Grep"], + "success": true + } + }, + { + "name": "Complex workflow test", + "input": "multi-step trigger example", + "expected": { + "activated": true, + "steps": 3, + "toolsUsed": ["Read", "Write", "Bash"], + "success": true + } + } + ], + "fixtures": { + "sampleInput": "example data", + "expectedOutput": "processed result" + } +} diff --git a/skills/skill-adapter/references/README.md b/skills/skill-adapter/references/README.md new file mode 100644 index 0000000..f51583b --- /dev/null +++ b/skills/skill-adapter/references/README.md @@ -0,0 +1,7 @@ +# References + +Bundled resources for api-mock-server skill + +- [ ] openapi_specification.md: A detailed explanation of the OpenAPI specification and best practices for creating API specifications. +- [ ] mock_server_configuration.md: Documentation on the configuration options for the mock server, including how to customize responses, simulate latency, and handle errors. +- [ ] example_openapi_specs.md: A collection of example OpenAPI specifications for different types of APIs (e.g., REST, GraphQL) to serve as a starting point for users. diff --git a/skills/skill-adapter/references/best-practices.md b/skills/skill-adapter/references/best-practices.md new file mode 100644 index 0000000..3505048 --- /dev/null +++ b/skills/skill-adapter/references/best-practices.md @@ -0,0 +1,69 @@ +# Skill Best Practices + +Guidelines for optimal skill usage and development. + +## For Users + +### Activation Best Practices + +1. **Use Clear Trigger Phrases** + - Match phrases from skill description + - Be specific about intent + - Provide necessary context + +2. **Provide Sufficient Context** + - Include relevant file paths + - Specify scope of analysis + - Mention any constraints + +3. **Understand Tool Permissions** + - Check allowed-tools in frontmatter + - Know what the skill can/cannot do + - Request appropriate actions + +### Workflow Optimization + +- Start with simple requests +- Build up to complex workflows +- Verify each step before proceeding +- Use skill consistently for related tasks + +## For Developers + +### Skill Development Guidelines + +1. **Clear Descriptions** + - Include explicit trigger phrases + - Document all capabilities + - Specify limitations + +2. **Proper Tool Permissions** + - Use minimal necessary tools + - Document security implications + - Test with restricted tools + +3. **Comprehensive Documentation** + - Provide usage examples + - Document common pitfalls + - Include troubleshooting guide + +### Maintenance + +- Keep version updated +- Test after tool updates +- Monitor user feedback +- Iterate on descriptions + +## Performance Tips + +- Scope skills to specific domains +- Avoid overlapping trigger phrases +- Keep descriptions under 1024 chars +- Test activation reliability + +## Security Considerations + +- Never include secrets in skill files +- Validate all inputs +- Use read-only tools when possible +- Document security requirements diff --git a/skills/skill-adapter/references/examples.md b/skills/skill-adapter/references/examples.md new file mode 100644 index 0000000..b1d8bd2 --- /dev/null +++ b/skills/skill-adapter/references/examples.md @@ -0,0 +1,70 @@ +# Skill Usage Examples + +This document provides practical examples of how to use this skill effectively. + +## Basic Usage + +### Example 1: Simple Activation + +**User Request:** +``` +[Describe trigger phrase here] +``` + +**Skill Response:** +1. Analyzes the request +2. Performs the required action +3. Returns results + +### Example 2: Complex Workflow + +**User Request:** +``` +[Describe complex scenario] +``` + +**Workflow:** +1. Step 1: Initial analysis +2. Step 2: Data processing +3. Step 3: Result generation +4. Step 4: Validation + +## Advanced Patterns + +### Pattern 1: Chaining Operations + +Combine this skill with other tools: +``` +Step 1: Use this skill for [purpose] +Step 2: Chain with [other tool] +Step 3: Finalize with [action] +``` + +### Pattern 2: Error Handling + +If issues occur: +- Check trigger phrase matches +- Verify context is available +- Review allowed-tools permissions + +## Tips & Best Practices + +- ✅ Be specific with trigger phrases +- ✅ Provide necessary context +- ✅ Check tool permissions match needs +- ❌ Avoid vague requests +- ❌ Don't mix unrelated tasks + +## Common Issues + +**Issue:** Skill doesn't activate +**Solution:** Use exact trigger phrases from description + +**Issue:** Unexpected results +**Solution:** Check input format and context + +## See Also + +- Main SKILL.md for full documentation +- scripts/ for automation helpers +- assets/ for configuration examples diff --git a/skills/skill-adapter/scripts/README.md b/skills/skill-adapter/scripts/README.md new file mode 100644 index 0000000..f84e8b3 --- /dev/null +++ b/skills/skill-adapter/scripts/README.md @@ -0,0 +1,7 @@ +# Scripts + +Bundled resources for api-mock-server skill + +- [ ] create_mock_server.py: Script to generate a mock server based on an OpenAPI spec. It should take the OpenAPI spec as input and output the mock server configuration. +- [ ] update_mock_server.py: Script to update an existing mock server with a new OpenAPI spec or configuration changes. +- [ ] validate_openapi_spec.py: Script to validate an OpenAPI spec for correctness and compatibility before creating a mock server. diff --git a/skills/skill-adapter/scripts/helper-template.sh b/skills/skill-adapter/scripts/helper-template.sh new file mode 100755 index 0000000..c4aae90 --- /dev/null +++ b/skills/skill-adapter/scripts/helper-template.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Helper script template for skill automation +# Customize this for your skill's specific needs + +set -e + +function show_usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -v, --verbose Enable verbose output" + echo "" +} + +# Parse arguments +VERBOSE=false + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + *) + echo "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Your skill logic here +if [ "$VERBOSE" = true ]; then + echo "Running skill automation..." +fi + +echo "✅ Complete" diff --git a/skills/skill-adapter/scripts/validation.sh b/skills/skill-adapter/scripts/validation.sh new file mode 100755 index 0000000..590af58 --- /dev/null +++ b/skills/skill-adapter/scripts/validation.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Skill validation helper +# Validates skill activation and functionality + +set -e + +echo "🔍 Validating skill..." + +# Check if SKILL.md exists +if [ ! -f "../SKILL.md" ]; then + echo "❌ Error: SKILL.md not found" + exit 1 +fi + +# Validate frontmatter +if ! grep -q "^---$" "../SKILL.md"; then + echo "❌ Error: No frontmatter found" + exit 1 +fi + +# Check required fields +if ! grep -q "^name:" "../SKILL.md"; then + echo "❌ Error: Missing 'name' field" + exit 1 +fi + +if ! grep -q "^description:" "../SKILL.md"; then + echo "❌ Error: Missing 'description' field" + exit 1 +fi + +echo "✅ Skill validation passed"