diff --git a/.env.vercel.production b/.env.vercel.production new file mode 100644 index 0000000..bf0ac19 --- /dev/null +++ b/.env.vercel.production @@ -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="" diff --git a/HOW_TO_PUBLISH_A_TOOL.md b/HOW_TO_PUBLISH_A_TOOL.md new file mode 100644 index 0000000..ba6828c --- /dev/null +++ b/HOW_TO_PUBLISH_A_TOOL.md @@ -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 { + // 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 diff --git a/apps/web/src/app/api/tools/[id]/route.ts b/apps/web/src/app/api/tools/[id]/route.ts deleted file mode 100644 index b9e38e4..0000000 --- a/apps/web/src/app/api/tools/[id]/route.ts +++ /dev/null @@ -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 } - ); - } -}