fix(api): remove conflicting [id] route causing build error

Next.js error: "You cannot use different slug names for the same dynamic path ('id' !== 'slug')"

Removed /api/tools/[id]/route.ts to resolve conflict with /api/tools/[slug]/route.ts
The [slug] route already handles fetching tools by package name

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-29 19:59:21 +10:00
parent aff28c4de9
commit 7f5d307c0f
3 changed files with 505 additions and 63 deletions

41
.env.vercel.production Normal file
View file

@ -0,0 +1,41 @@
# Created by Vercel CLI
CRON_SECRET="CRON_SECRET=6c806d35cf6212f489c76414d38d2b6acbc44590ac78bb08aadea28dd04a29d0\n"
DATABASE_URL="postgresql://neondb_owner:npg_euvYo4OTi1lX@ep-broad-darkness-a4lml85k-pooler.us-east-1.aws.neon.tech/neondb?sslmode=require"
DATABASE_URL_UNPOOLED="postgresql://neondb_owner:npg_euvYo4OTi1lX@ep-broad-darkness-a4lml85k.us-east-1.aws.neon.tech/neondb?sslmode=require"
NEXT_PUBLIC_STACK_PROJECT_ID="d786bd3a-a31d-4c6b-9497-5d6803dd9d86"
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY="pck_hafmpkaj047z331x5azv8bk5zggfnbgdedbj9pfqh1rn0"
NX_DAEMON="false"
PGDATABASE="neondb"
PGHOST="ep-broad-darkness-a4lml85k-pooler.us-east-1.aws.neon.tech"
PGHOST_UNPOOLED="ep-broad-darkness-a4lml85k.us-east-1.aws.neon.tech"
PGPASSWORD="npg_euvYo4OTi1lX"
PGUSER="neondb_owner"
POSTGRES_DATABASE="neondb"
POSTGRES_HOST="ep-broad-darkness-a4lml85k-pooler.us-east-1.aws.neon.tech"
POSTGRES_PASSWORD="npg_euvYo4OTi1lX"
POSTGRES_PRISMA_URL="postgresql://neondb_owner:npg_euvYo4OTi1lX@ep-broad-darkness-a4lml85k-pooler.us-east-1.aws.neon.tech/neondb?connect_timeout=15&sslmode=require"
POSTGRES_URL="postgresql://neondb_owner:npg_euvYo4OTi1lX@ep-broad-darkness-a4lml85k-pooler.us-east-1.aws.neon.tech/neondb?sslmode=require"
POSTGRES_URL_NON_POOLING="postgresql://neondb_owner:npg_euvYo4OTi1lX@ep-broad-darkness-a4lml85k.us-east-1.aws.neon.tech/neondb?sslmode=require"
POSTGRES_URL_NO_SSL="postgresql://neondb_owner:npg_euvYo4OTi1lX@ep-broad-darkness-a4lml85k-pooler.us-east-1.aws.neon.tech/neondb"
POSTGRES_USER="neondb_owner"
STACK_SECRET_SERVER_KEY="ssk_p05kwe938wx13rpera9xf1fewc816dwkbq658xcsbwj1g"
TURBO_CACHE="remote:rw"
TURBO_DOWNLOAD_LOCAL_ENABLED="true"
TURBO_REMOTE_ONLY="true"
TURBO_RUN_SUMMARY="true"
VERCEL="1"
VERCEL_ENV="production"
VERCEL_GIT_COMMIT_AUTHOR_LOGIN=""
VERCEL_GIT_COMMIT_AUTHOR_NAME=""
VERCEL_GIT_COMMIT_MESSAGE=""
VERCEL_GIT_COMMIT_REF=""
VERCEL_GIT_COMMIT_SHA=""
VERCEL_GIT_PREVIOUS_SHA=""
VERCEL_GIT_PROVIDER=""
VERCEL_GIT_PULL_REQUEST_ID=""
VERCEL_GIT_REPO_ID=""
VERCEL_GIT_REPO_OWNER=""
VERCEL_GIT_REPO_SLUG=""
VERCEL_OIDC_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im1yay00MzAyZWMxYjY3MGY0OGE5OGFkNjFkYWRlNGEyM2JlNyJ9.eyJpc3MiOiJodHRwczovL29pZGMudmVyY2VsLmNvbS90cG1qcyIsInN1YiI6Im93bmVyOnRwbWpzOnByb2plY3Q6dHBtanMtd2ViOmVudmlyb25tZW50OmRldmVsb3BtZW50Iiwic2NvcGUiOiJvd25lcjp0cG1qczpwcm9qZWN0OnRwbWpzLXdlYjplbnZpcm9ubWVudDpkZXZlbG9wbWVudCIsImF1ZCI6Imh0dHBzOi8vdmVyY2VsLmNvbS90cG1qcyIsIm93bmVyIjoidHBtanMiLCJvd25lcl9pZCI6InRlYW1femtHV0NXYjdWakhvbmk2VmJ5ZmQyc3c4IiwicHJvamVjdCI6InRwbWpzLXdlYiIsInByb2plY3RfaWQiOiJwcmpfNWd1MEkwVzFjUFhkQ3ozd1RjQ0ZIejQzNUJ0MCIsImVudmlyb25tZW50IjoiZGV2ZWxvcG1lbnQiLCJwbGFuIjoicHJvIiwidXNlcl9pZCI6IkxKZk05VzdIdlljb2gyclVCaXRWd283ViIsIm5iZiI6MTc2NDM4OTAxMiwiaWF0IjoxNzY0Mzg5MDEyLCJleHAiOjE3NjQ0MzIyMTJ9.OF4IHrcmteA2lU1tkqHO1a9ITGGrCjCo29G8jI991q8_SQjgHZHVqcBj3AYVKZJDh6BjHib4HyNKdjO8nwUblF2dCFbYDv6y4hwB6jHNpsz32BE1JDKcXEJOKPtg_tBOFUDKtzMkPk7VOPWDVYw8Tz4_HZ_MR3SNoy1Pk9AFL-hEl3E-zR3bAYMDB8tKrIm9y9K4sZF6efMU7BR_J6Bf-i3IsbbrH-Axgq5dewlpogf-xHWmWaTXoUp6UFejNKhSMXqg3sAWTnizYeSGc2Ut6zNuAYPumUPBdQ37Kk7vuRNwS1h7RJz3vtEg6aOuw0-Ld0LdF-tWkfDGsVqanR_sxw"
VERCEL_TARGET_ENV="production"
VERCEL_URL=""

464
HOW_TO_PUBLISH_A_TOOL.md Normal file
View file

@ -0,0 +1,464 @@
# How to Publish a TPMJS Tool
This guide shows you how to create and publish an AI tool that will be automatically discovered and listed on tpmjs.com.
## Quick Start
1. Create a new NPM package
2. Add `"tpmjs-tool"` to the `keywords` array in package.json
3. Add a `tpmjs` field with your tool's metadata
4. Publish to NPM
5. Your tool will automatically appear on tpmjs.com within 15 minutes
## Step-by-Step Guide
### 1. Create Your NPM Package
Create a standard NPM package with your tool implementation:
```bash
mkdir my-awesome-tool
cd my-awesome-tool
npm init -y
```
### 2. Add the Required Keyword
In your `package.json`, add `"tpmjs-tool"` to the keywords array:
```json
{
"name": "@yourname/my-awesome-tool",
"version": "1.0.0",
"keywords": ["tpmjs-tool", "ai", "other-keywords"],
...
}
```
**Important:** The `"tpmjs-tool"` keyword is REQUIRED for automatic discovery!
### 3. Add TPMJS Metadata
Add a `tpmjs` field to your `package.json` with your tool's metadata. There are three tiers:
#### Tier 1: Minimal (Required Fields Only)
The bare minimum to get listed:
```json
{
"tpmjs": {
"category": "text-analysis",
"description": "A concise description of what your tool does",
"example": "const result = await myTool({ input: 'hello' });"
}
}
```
**Required fields:**
- `category` - One of: `text-analysis`, `code-generation`, `data-processing`, `image-generation`, `audio-processing`, `search`, `integration`, `other`
- `description` - Clear description of what the tool does (1-3 sentences)
- `example` - Simple code example showing how to use the tool
#### Tier 2: Basic (Recommended)
Add parameter and return type information:
```json
{
"tpmjs": {
"category": "text-analysis",
"description": "Analyzes sentiment in text and returns a score",
"example": "const result = await analyzeSentiment({ text: 'I love this!' });",
"parameters": [
{
"name": "text",
"type": "string",
"description": "The text to analyze",
"required": true
},
{
"name": "language",
"type": "string",
"description": "Language code (e.g., 'en', 'es')",
"required": false,
"default": "en"
}
],
"returns": {
"type": "SentimentResult",
"description": "Object containing score (-1 to 1) and label (positive/negative/neutral)"
}
}
}
```
#### Tier 3: Rich (Full Documentation)
Complete metadata for maximum visibility:
```json
{
"tpmjs": {
"category": "text-analysis",
"description": "Advanced sentiment analysis with emotion detection",
"example": "const result = await analyzeSentiment({ text: 'I love this!', includeEmotions: true });",
"parameters": [
{
"name": "text",
"type": "string",
"description": "The text to analyze",
"required": true
},
{
"name": "language",
"type": "string",
"description": "Language code",
"required": false,
"default": "en"
},
{
"name": "includeEmotions",
"type": "boolean",
"description": "Whether to include emotion breakdown",
"required": false,
"default": false
}
],
"returns": {
"type": "SentimentResult",
"description": "Object with score, label, and optional emotions array"
},
"authentication": {
"type": "api-key",
"required": true
},
"pricing": {
"model": "pay-per-use",
"cost": "$0.001 per request"
},
"frameworks": ["vercel-ai", "langchain"],
"links": {
"documentation": "https://docs.example.com",
"repository": "https://github.com/yourname/tool",
"homepage": "https://yourwebsite.com"
},
"tags": ["sentiment", "nlp", "emotions", "ai"],
"status": "stable",
"aiAgent": {
"useCase": "Use this tool when users need to analyze sentiment in text, detect emotions, or understand the tone of customer feedback, reviews, or social media posts.",
"limitations": "Only supports English and Spanish. Maximum 10,000 characters per request.",
"examples": [
"Analyze customer review sentiment",
"Detect emotions in user feedback",
"Monitor social media sentiment"
]
}
}
}
```
### 4. Implement Your Tool
Write your tool's implementation. Here's the example from `@tpmjs/createblogpost`:
```typescript
// src/index.ts
export interface BlogPostOptions {
title: string;
author: string;
content: string;
tags?: string[];
format?: 'markdown' | 'mdx';
excerpt?: string;
}
export interface BlogPost {
frontmatter: {
title: string;
author: string;
date: string;
tags: string[];
excerpt?: string;
slug: string;
wordCount: number;
readingTime: number;
};
content: string;
formattedOutput: string;
}
export async function createBlogPost(options: BlogPostOptions): Promise<BlogPost> {
// Your implementation here
const { title, author, content, tags = [], format = 'markdown', excerpt } = options;
// Validate inputs
if (!title || !author || !content) {
throw new Error('Title, author, and content are required');
}
// Process and return result
return {
frontmatter: { /* ... */ },
content,
formattedOutput: '...'
};
}
export default createBlogPost;
```
### 5. Build and Publish
Build your package and publish to NPM:
```bash
# Build your package
npm run build
# Publish to NPM
npm publish --access public
```
### 6. Verification
Your tool will be automatically discovered through:
1. **Keyword Search** - Runs every 15 minutes, searches NPM for `"tpmjs-tool"`
2. **Changes Feed** - Monitors NPM publishes in real-time (every 2 minutes)
After publishing, your tool should appear on https://tpmjs.com within 15 minutes!
You can verify by searching: https://tpmjs.com/api/tools?q=yourpackagename
## Real Example: @tpmjs/createblogpost
Here's the complete `package.json` from the published example:
```json
{
"name": "@tpmjs/createblogpost",
"version": "0.2.0",
"description": "A tool for creating structured blog posts with AI-generated content",
"type": "module",
"keywords": ["tpmjs-tool", "blog", "content", "ai", "writing"],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/ajaxdavis/tpmjs.git",
"directory": "packages/tools/createBlogPost"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "text-analysis",
"description": "Creates structured blog posts with customizable frontmatter, content sections, and SEO metadata. Supports multiple output formats including Markdown and MDX.",
"example": "const post = await createBlogPost({ title: 'My First Post', author: 'John Doe', content: 'Hello World!', tags: ['intro', 'blog'] });",
"parameters": [
{
"name": "title",
"type": "string",
"description": "The title of the blog post",
"required": true
},
{
"name": "author",
"type": "string",
"description": "The author of the blog post",
"required": true
},
{
"name": "content",
"type": "string",
"description": "The main content of the blog post",
"required": true
},
{
"name": "tags",
"type": "string[]",
"description": "Array of tags for categorization",
"required": false,
"default": []
},
{
"name": "format",
"type": "'markdown' | 'mdx'",
"description": "Output format for the blog post",
"required": false,
"default": "markdown"
},
{
"name": "excerpt",
"type": "string",
"description": "Short excerpt or summary of the post",
"required": false
}
],
"returns": {
"type": "BlogPost",
"description": "A structured blog post object with frontmatter, content, and metadata including slug, wordCount, readingTime, and formattedOutput"
},
"authentication": {
"required": false,
"type": "api-key"
},
"pricing": {
"model": "free"
},
"frameworks": ["vercel-ai", "langchain"],
"links": {
"documentation": "https://tpmjs.com/tools/createblogpost",
"repository": "https://github.com/ajaxdavis/tpmjs/tree/main/packages/tools/createBlogPost",
"homepage": "https://tpmjs.com"
},
"tags": ["blog", "content", "markdown", "mdx", "writing", "seo"],
"status": "stable",
"aiAgent": {
"useCase": "Use this tool when users need to generate blog posts, articles, or structured content with proper frontmatter and metadata. Ideal for content management systems, static site generators, and documentation sites.",
"limitations": "Does not include AI content generation - you must provide the content. Only formats and structures existing content.",
"examples": [
"Create a blog post about TypeScript best practices",
"Generate a tutorial post with code examples",
"Format an article with SEO metadata"
]
}
}
}
```
## Field Reference
### Required Fields (Tier 1 - Minimal)
| Field | Type | Description |
|-------|------|-------------|
| `category` | string | Tool category (see categories below) |
| `description` | string | Clear description (1-3 sentences) |
| `example` | string | Code example showing usage |
### Optional Fields (Tier 2 - Basic)
| Field | Type | Description |
|-------|------|-------------|
| `parameters` | array | Array of parameter objects |
| `returns` | object | Return type information |
### Optional Fields (Tier 3 - Rich)
| Field | Type | Description |
|-------|------|-------------|
| `authentication` | object | Auth requirements |
| `pricing` | object | Pricing information |
| `frameworks` | array | Compatible frameworks |
| `links` | object | Related URLs |
| `tags` | array | Additional tags |
| `status` | string | `stable`, `beta`, or `experimental` |
| `aiAgent` | object | AI agent integration info |
### Categories
Choose one of these for the `category` field:
- `text-analysis` - NLP, sentiment, summarization
- `code-generation` - Code generation and transformation
- `data-processing` - Data manipulation and transformation
- `image-generation` - Image creation and editing
- `audio-processing` - Audio/speech processing
- `search` - Search and retrieval
- `integration` - Third-party integrations
- `other` - Anything else
### Authentication Types
If your tool requires authentication:
```json
"authentication": {
"type": "api-key", // or "oauth", "bearer-token", "basic"
"required": true
}
```
### Pricing Models
```json
"pricing": {
"model": "free" // or "pay-per-use", "subscription", "freemium"
}
```
For paid tools, add cost information:
```json
"pricing": {
"model": "pay-per-use",
"cost": "$0.001 per request"
}
```
## Quality Score
Your tool gets a quality score based on:
- **Tier**: Rich (1.0) > Basic (0.5) > Minimal (0.25)
- **Downloads**: Logarithmic scale based on monthly NPM downloads
- **GitHub Stars**: Logarithmic scale based on repository stars
Higher scores = better visibility on tpmjs.com!
## Tips for Success
1. **Use descriptive names** - Make your package name clear and searchable
2. **Complete metadata** - Tier 3 (Rich) tools get 4x the base score
3. **Good documentation** - Link to docs in the `links.documentation` field
4. **Active maintenance** - Regular updates boost download counts
5. **AI-friendly descriptions** - Write the `aiAgent.useCase` field as guidance for AI agents
## Testing Locally
Before publishing, you can validate your `tpmjs` field using the validation schema:
```bash
# In the tpmjs monorepo
pnpm --filter=@tpmjs/types test
```
Or manually check the structure matches the examples above.
## Troubleshooting
**Tool not appearing after 15 minutes?**
- Check that you added `"tpmjs-tool"` to keywords
- Verify your `tpmjs` field has required fields (category, description, example)
- Check the NPM package is public: `npm view yourpackage`
**Tool showing as "minimal" tier?**
- Add `parameters` and `returns` fields for Basic tier
- Add all Rich tier fields for maximum visibility
**Want to force a sync?**
You can manually trigger a sync (requires auth):
```bash
curl -X POST "https://tpmjs.com/api/sync/keyword" \
-H "Authorization: Bearer YOUR_CRON_SECRET"
```
## Support
Questions or issues?
- File an issue: https://github.com/ajaxdavis/tpmjs/issues
- Check the API: https://tpmjs.com/api/tools

View file

@ -1,63 +0,0 @@
import { prisma } from '@tpmjs/db';
import { type NextRequest, NextResponse } from 'next/server';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const maxDuration = 60;
/**
* GET /api/tools/[id]
* Get tool details by ID or package name
*
* Params:
* - id: Tool ID (number) or NPM package name (string)
*/
export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
try {
const { id } = await params;
if (!id) {
return NextResponse.json(
{
success: false,
error: 'Missing ID parameter',
},
{ status: 400 }
);
}
// Try to find by ID first (cuid), then by package name
const tool = await prisma.tool.findFirst({
where: {
OR: [{ id }, { npmPackageName: id }],
},
});
if (!tool) {
return NextResponse.json(
{
success: false,
error: 'Tool not found',
message: `No tool found with ID or package name: ${id}`,
},
{ status: 404 }
);
}
return NextResponse.json({
success: true,
data: tool,
});
} catch (error) {
console.error('Error fetching tool details:', error);
return NextResponse.json(
{
success: false,
error: 'Failed to fetch tool details',
message: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
);
}
}