--- name: counterfact-repl description: > Interact with Counterfact mock API server programmatically. Inspect and modify context state, configure proxy settings, test endpoints, and control mock behavior. Use when the user mentions "counterfact", "mock server", "REPL", testing APIs, or working with OpenAPI mocks. applyTo: - "**/*.{yaml,yml,json}" - "**/routes/**/*.{ts,js}" - "**/*context.{ts,js}" --- # Counterfact REPL Skill ## Purpose This skill enables AI agents to interact with a running Counterfact mock server through its Admin API. Counterfact provides programmable API mocks based on OpenAPI specifications, with a REPL for runtime manipulation. This skill exposes those capabilities programmatically via HTTP endpoints. ## What is Counterfact? Counterfact is a contract-driven mock API server that: - Generates mock endpoints from OpenAPI specifications - Provides a JavaScript REPL for runtime state manipulation - Allows programmable behavior (not just static responses) - Supports context objects for storing mock state - Enables proxy mode to route requests to real APIs selectively The REPL lets developers manipulate mock state in real-time using JavaScript. This skill exposes the same capabilities via HTTP API. ## When to Use This Skill Invoke this skill when the user: - Mentions "counterfact", "mock server", "REPL", or "OpenAPI" - Wants to test or develop against a mock API - Needs to inspect or modify mock server state - Wants to configure proxy vs. mock routing - Needs to simulate API failures or edge cases - Says things like: - "Add data to the mock" - "Change the mock state" - "Make the API return an error" - "Proxy this endpoint to the real server" - "What data is in the mock store?" - "Simulate a service failure" ## Detecting Counterfact To determine if Counterfact is running, check in this order: 1. **Check for health endpoint** (most reliable): ```http GET http://localhost:3100/_counterfact/api/health ``` If successful, Counterfact is running on port 3100. 2. **Look for package.json**: - Check if `counterfact` is in dependencies or devDependencies - Read scripts to find the port (e.g., `--port 3000`) 3. **Check for routes directory**: - Look for `routes/` or `api/routes/` directory - TypeScript/JavaScript files matching OpenAPI paths 4. **Check for OpenAPI spec**: - Files matching `*.yaml`, `openapi.yaml`, `swagger.yaml` - Look for `openapi:` or `swagger:` in content 5. **Try alternate ports**: - Port 3100 (default) - Port 3000 (common alternative) - Check environment variables or config files ## Admin API Endpoints All Admin API endpoints are prefixed with `/_counterfact/api/` ### Health Check **Request:** ```http GET /_counterfact/api/health ``` **Response:** ```json { "status": "ok", "port": 3100, "uptime": 123.45, "basePath": "/path/to/routes", "prefix": "" } ``` **Use when:** Checking if server is running, getting server info. --- ### List All Contexts **Request:** ```http GET /_counterfact/api/contexts ``` **Response:** ```json { "success": true, "data": { "paths": ["/", "/pets", "/users"], "contexts": { "/": { "rootProperty": "value" }, "/pets": { "pets": [...] }, "/users": { "users": [...] } } } } ``` **Use when:** Discovering what contexts exist, getting overview of all state. --- ### Get Specific Context **Request:** ```http GET /_counterfact/api/contexts/{path} ``` Example: ```http GET /_counterfact/api/contexts/pets ``` **Response:** ```json { "success": true, "data": { "path": "/pets", "context": { "pets": [ { "id": 1, "name": "Fido" }, { "id": 2, "name": "Whiskers" } ] } } } ``` **Use when:** Inspecting state for a specific API path. --- ### Update Context **Request:** ```http POST /_counterfact/api/contexts/{path} Content-Type: application/json { "property": "newValue", "arrayProperty": [...] } ``` Example: ```http POST /_counterfact/api/contexts/pets Content-Type: application/json { "pets": [ { "id": 1, "name": "Fido" }, { "id": 2, "name": "Whiskers" }, { "id": 3, "name": "Rex" } ] } ``` **Response:** ```json { "success": true, "message": "Context updated for path: /pets", "data": { "path": "/pets", "context": { "pets": [...] } } } ``` **Use when:** Adding data, modifying state, simulating conditions. **Important:** The update uses smart diffing - only changed properties are updated, preserving methods and other properties. --- ### Get Full Configuration **Request:** ```http GET /_counterfact/api/config ``` **Response:** ```json { "success": true, "data": { "alwaysFakeOptionals": false, "basePath": "/path/to/routes", "buildCache": false, "generate": { "routes": true, "types": true }, "openApiPath": "/path/to/openapi.yaml", "port": 3100, "proxyUrl": "", "prefix": "", "startRepl": true, "startServer": true, "watch": { "routes": true, "types": true }, "proxyPaths": [] } } ``` **Use when:** Getting full server configuration. --- ### Get Proxy Configuration **Request:** ```http GET /_counterfact/api/config/proxy ``` **Response:** ```json { "success": true, "data": { "proxyUrl": "https://api.example.com", "proxyPaths": [ ["/api/users", true], ["/api/posts", false] ] } } ``` **Use when:** Checking what paths are proxied vs. mocked. --- ### Update Proxy Configuration **Request:** ```http PATCH /_counterfact/api/config/proxy Content-Type: application/json { "proxyUrl": "https://api.example.com", "proxyPaths": [ ["/api/users", true], ["/api/posts", false] ] } ``` **Response:** ```json { "success": true, "message": "Proxy configuration updated", "data": { "proxyUrl": "https://api.example.com", "proxyPaths": [ ["/api/users", true], ["/api/posts", false] ] } } ``` **Use when:** Switching between mock and real API, testing integration. **Notes:** - `proxyUrl` is the base URL to proxy to - `proxyPaths` is an array of `[path, enabled]` tuples - Path of `""` or `"/"` enables proxy globally - Setting path `true` routes to real API, `false` uses mock --- ### List All Routes **Request:** ```http GET /_counterfact/api/routes ``` **Response:** ```json { "success": true, "data": { "routes": [ { "path": "/pets", "methods": { "GET": true, "POST": true } }, { "path": "/pets/{id}", "methods": { "GET": true, "PUT": true, "DELETE": true } } ] } } ``` **Use when:** Discovering available endpoints, understanding API structure. --- ## Common Usage Patterns ### Pattern 1: Inspect Current State **User request:** "What pets are in the store?" **Agent workflow:** ``` 1. GET /_counterfact/api/health → Confirm server running 2. GET /_counterfact/api/contexts/pets → Retrieve pets array 3. Present formatted list to user ``` **Example response:** ``` Currently in the pet store: - ID 1: Fido - ID 2: Whiskers ``` --- ### Pattern 2: Add Test Data **User request:** "Add a new pet named Rex with ID 3" **Agent workflow:** ``` 1. GET /_counterfact/api/contexts/pets → Get current pets 2. Append new pet to array 3. POST /_counterfact/api/contexts/pets Body: { "pets": [...existingPets, newPet] } → Update context 4. Verify by GETting /pets endpoint → Confirm change visible in API ``` --- ### Pattern 3: Simulate Failure **User request:** "Make the user service unavailable" **Agent workflow:** ``` 1. POST /_counterfact/api/contexts/users Body: { "serviceAvailable": false } → Set failure flag 2. Explain that route handlers should check this flag 3. Optionally: Test GET /users to verify error ``` **Note:** The route handler must be coded to check `context.serviceAvailable`. The skill sets the flag, but behavior depends on route implementation. --- ### Pattern 4: Switch to Proxy Mode **User request:** "Route /orders to the real API" **Agent workflow:** ``` 1. GET /_counterfact/api/config/proxy → Check current proxy settings 2. PATCH /_counterfact/api/config/proxy Body: { "proxyUrl": "https://api.production.com", "proxyPaths": [["/orders", true]] } → Enable proxy for /orders 3. Confirm that /orders now hits real server ``` --- ### Pattern 5: Batch Data Setup **User request:** "Set up test data: 3 users and 5 products" **Agent workflow:** ``` 1. Create users array with test data 2. POST /_counterfact/api/contexts/users Body: { "users": [...testUsers] } 3. Create products array with test data 4. POST /_counterfact/api/contexts/products Body: { "products": [...testProducts] } 5. Confirm setup complete ``` --- ## Error Handling ### Server Not Running If health check fails: ``` ❌ Error: Counterfact server not running Suggestion: Start the server with: npx counterfact openapi.yaml ``` ### Context Not Found If context doesn't exist: ``` GET /_counterfact/api/contexts/nonexistent → Returns: { "path": "/nonexistent", "context": {...} } ``` Note: Contexts are hierarchical. If `/api/users` doesn't exist, it returns parent context. ### Invalid JSON If request body is malformed: ``` ← 400 Bad Request { "success": false, "error": "Request body must be a valid JSON object" } ``` ### Server Error If internal error occurs: ``` ← 500 Internal Server Error { "success": false, "error": "Error message", "stack": "..." // Only in development } ``` --- ## Advanced Techniques ### Hierarchical Contexts Contexts are hierarchical. If you have: ``` / → root context /api → api context /api/users → users context ``` Then `GET /_counterfact/api/contexts/api/users/123` returns the `/api/users` context (closest parent). ### Smart Diffing Context updates use smart diffing: ```javascript // Old context: { users: [...], count: 5 } POST { users: [...newUsers] } // Result: { users: [...newUsers], count: 5 } // count is preserved ``` ### Testing Proxied Requests After setting proxy: ``` 1. PATCH /_counterfact/api/config/proxy Body: { "proxyPaths": [["/users", true]] } 2. Make request to http://localhost:3100/users → This now proxies to real server 3. Check response headers for proxy evidence ``` --- ## Limitations 1. **In-memory state**: Changes reset on server restart 2. **No TypeScript validation**: Context can accept any JSON object 3. **No authentication**: Admin API is unauthenticated (local development tool) 4. **No versioning**: API is v1, may evolve 5. **Context discovery**: Must know or discover paths, no automatic schema --- ## Security Considerations ⚠️ **Warning:** The Admin API provides full control over mock server state. **Recommendations:** - Only run Counterfact in development/testing environments - Do not expose the Admin API to untrusted networks - Configure a bearer token when exposing the Admin API beyond local development - Be cautious with context updates from untrusted sources **Current access controls:** - By default, the Admin API only listens on the loopback interface (localhost) - You can require a bearer token for all Admin API requests: - CLI flag: `--admin-api-token ` - Environment variable: `COUNTERFACT_ADMIN_API_TOKEN=` - When a token is configured, clients must send: - HTTP header: `Authorization: Bearer ` --- ## Integration with OpenAPI The skill works best when paired with OpenAPI understanding: 1. Read the OpenAPI spec to understand available paths 2. Map OpenAPI paths to context paths 3. Use OpenAPI schemas to validate context updates 4. Generate realistic test data based on OpenAPI examples Example: ``` 1. Read openapi.yaml 2. Find path /pets with schema: { id: number, name: string } 3. Generate test data: { id: 1, name: "Fido" } 4. POST /_counterfact/api/contexts/pets with test data ``` --- ## Troubleshooting **Issue:** Health check returns 404 **Solution:** Server may not have Admin API. Update to latest Counterfact version. --- **Issue:** Context update doesn't affect API responses **Solution:** Check route handler implementation. It must read from `$.context`. Example route handler: ```typescript export const GET: HTTP_GET = ($) => { return $.response[200].json($.context.pets); }; ``` --- **Issue:** Proxy not working **Solution:** - Verify proxyUrl is set: `GET /_counterfact/api/config/proxy` - Check path is enabled: `proxyPaths` array - Ensure path matches exactly (case-sensitive) --- ## See Also - [Counterfact Documentation](https://counterfact.dev) - [Admin API Examples](./examples.md) - [Counterfact GitHub](https://github.com/pmcelhaney/counterfact)