feat: add npm-like features to tool detail page and update keyword to tpmjs
- Add download trend sparkline chart showing 30-day download history - Add bundle size component with minified/gzipped sizes via bundlephobia proxy - Add more install commands: yarn, bun, deno (in addition to npm, pnpm) - Change discovery keyword from "tpmjs-tool" to "tpmjs" across entire codebase - Update sync endpoints to use new keyword - Update all documentation and package.json files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4a13709a3d
commit
fada29ecbe
42 changed files with 426 additions and 97 deletions
10
CLAUDE.md
10
CLAUDE.md
|
|
@ -684,14 +684,14 @@ The full working implementation is live at [tpmjs.com](https://tpmjs.com).
|
||||||
|
|
||||||
## NPM Package Syncing System
|
## NPM Package Syncing System
|
||||||
|
|
||||||
TPMJS.com automatically mirrors npm packages with the `tpmjs-tool` keyword to keep the tool registry up-to-date. This section documents how the syncing system works.
|
TPMJS.com automatically mirrors npm packages with the `tpmjs` keyword to keep the tool registry up-to-date. This section documents how the syncing system works.
|
||||||
|
|
||||||
### Overview
|
### Overview
|
||||||
|
|
||||||
The sync system uses three automated strategies running on Vercel Cron to discover and update TPMJS tools:
|
The sync system uses three automated strategies running on Vercel Cron to discover and update TPMJS tools:
|
||||||
|
|
||||||
1. **Changes Feed** - Monitors npm's real-time changes feed for all package updates
|
1. **Changes Feed** - Monitors npm's real-time changes feed for all package updates
|
||||||
2. **Keyword Search** - Actively searches npm for packages with the `tpmjs-tool` keyword
|
2. **Keyword Search** - Actively searches npm for packages with the `tpmjs` keyword
|
||||||
3. **Metrics Sync** - Updates download stats and calculates quality scores
|
3. **Metrics Sync** - Updates download stats and calculates quality scores
|
||||||
|
|
||||||
### Sync Endpoints
|
### Sync Endpoints
|
||||||
|
|
@ -735,12 +735,12 @@ All sync endpoints are located in `apps/web/src/app/api/sync/`:
|
||||||
|
|
||||||
#### 2. Keyword Search Sync (`/api/sync/keyword`)
|
#### 2. Keyword Search Sync (`/api/sync/keyword`)
|
||||||
|
|
||||||
**Purpose:** Actively searches npm for packages with the `tpmjs-tool` keyword.
|
**Purpose:** Actively searches npm for packages with the `tpmjs` keyword.
|
||||||
|
|
||||||
**Schedule:** Every 15 minutes (`*/15 * * * *`)
|
**Schedule:** Every 15 minutes (`*/15 * * * *`)
|
||||||
|
|
||||||
**How it works:**
|
**How it works:**
|
||||||
1. Searches npm registry for packages with keyword `tpmjs-tool` (up to 250 results)
|
1. Searches npm registry for packages with keyword `tpmjs` (up to 250 results)
|
||||||
2. Fetches full metadata for each package
|
2. Fetches full metadata for each package
|
||||||
3. Validates the `tpmjs` field
|
3. Validates the `tpmjs` field
|
||||||
4. Upserts tools with `discoveryMethod: 'keyword'`
|
4. Upserts tools with `discoveryMethod: 'keyword'`
|
||||||
|
|
@ -1078,7 +1078,7 @@ curl -X POST http://localhost:3000/api/sync/changes \
|
||||||
|
|
||||||
Here's how a new TPMJS tool gets discovered:
|
Here's how a new TPMJS tool gets discovered:
|
||||||
|
|
||||||
1. **Developer publishes package to npm** with `tpmjs-tool` keyword and `tpmjs` field in package.json
|
1. **Developer publishes package to npm** with `tpmjs` keyword and `tpmjs` field in package.json
|
||||||
2. **Within 2 minutes:** Changes feed sync picks it up from npm's `/_changes` endpoint
|
2. **Within 2 minutes:** Changes feed sync picks it up from npm's `/_changes` endpoint
|
||||||
3. **Validation:** `validateTpmjsField()` checks that the `tpmjs` field meets requirements
|
3. **Validation:** `validateTpmjsField()` checks that the `tpmjs` field meets requirements
|
||||||
4. **Database Insert:** Tool is upserted with initial data
|
4. **Database Insert:** Tool is upserted with initial data
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ This guide shows you how to create and publish an AI tool that will be automatic
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
1. Create a new NPM package
|
1. Create a new NPM package
|
||||||
2. Add `"tpmjs-tool"` to the `keywords` array in package.json
|
2. Add `"tpmjs"` to the `keywords` array in package.json
|
||||||
3. Add a `tpmjs` field with your tool's metadata
|
3. Add a `tpmjs` field with your tool's metadata
|
||||||
4. Publish to NPM
|
4. Publish to NPM
|
||||||
5. Your tool will automatically appear on tpmjs.com within 15 minutes
|
5. Your tool will automatically appear on tpmjs.com within 15 minutes
|
||||||
|
|
@ -24,18 +24,18 @@ npm init -y
|
||||||
|
|
||||||
### 2. Add the Required Keyword
|
### 2. Add the Required Keyword
|
||||||
|
|
||||||
In your `package.json`, add `"tpmjs-tool"` to the keywords array:
|
In your `package.json`, add `"tpmjs"` to the keywords array:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "@yourname/my-awesome-tool",
|
"name": "@yourname/my-awesome-tool",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"keywords": ["tpmjs-tool", "ai", "other-keywords"],
|
"keywords": ["tpmjs", "ai", "other-keywords"],
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Important:** The `"tpmjs-tool"` keyword is REQUIRED for automatic discovery!
|
**Important:** The `"tpmjs"` keyword is REQUIRED for automatic discovery!
|
||||||
|
|
||||||
### 3. Add TPMJS Metadata
|
### 3. Add TPMJS Metadata
|
||||||
|
|
||||||
|
|
@ -212,7 +212,7 @@ npm publish --access public
|
||||||
|
|
||||||
Your tool will be automatically discovered through:
|
Your tool will be automatically discovered through:
|
||||||
|
|
||||||
1. **Keyword Search** - Runs every 15 minutes, searches NPM for `"tpmjs-tool"`
|
1. **Keyword Search** - Runs every 15 minutes, searches NPM for `"tpmjs"`
|
||||||
2. **Changes Feed** - Monitors NPM publishes in real-time (every 2 minutes)
|
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!
|
After publishing, your tool should appear on https://tpmjs.com within 15 minutes!
|
||||||
|
|
@ -229,7 +229,7 @@ Here's the complete `package.json` from the published example:
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"description": "A tool for creating structured blog posts with AI-generated content",
|
"description": "A tool for creating structured blog posts with AI-generated content",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"keywords": ["tpmjs-tool", "blog", "content", "ai", "writing"],
|
"keywords": ["tpmjs", "blog", "content", "ai", "writing"],
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
|
|
@ -402,7 +402,7 @@ Or manually check the structure matches the examples above.
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**Tool not appearing after 15 minutes?**
|
**Tool not appearing after 15 minutes?**
|
||||||
- Check that you added `"tpmjs-tool"` to keywords
|
- Check that you added `"tpmjs"` to keywords
|
||||||
- Verify your `tpmjs` field has required fields (category, description)
|
- Verify your `tpmjs` field has required fields (category, description)
|
||||||
- Check the NPM package is public: `npm view yourpackage`
|
- Check the NPM package is public: `npm view yourpackage`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@ this becomes unmanageable.
|
||||||
designed for AI agents. Agents can search for tools by description and load them
|
designed for AI agents. Agents can search for tools by description and load them
|
||||||
at runtime.
|
at runtime.
|
||||||
|
|
||||||
**For Tool Builders:** Add `tpmjs-tool` keyword to your package.json.
|
**For Tool Builders:** Add `tpmjs` keyword to your package.json.
|
||||||
Your tool appears on tpmjs.com within 15 minutes.
|
Your tool appears on tpmjs.com within 15 minutes.
|
||||||
|
|
||||||
**For Agent Developers:** Use semantic search to find tools:
|
**For Agent Developers:** Use semantic search to find tools:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
**TPMJS is a registry for discovering AI tools published to npm.**
|
**TPMJS is a registry for discovering AI tools published to npm.**
|
||||||
|
|
||||||
Browse, search, and find tools at [tpmjs.com](https://tpmjs.com). Publish your tool by adding the `tpmjs-tool` keyword to your package.json—it appears in the registry within 15 minutes.
|
Browse, search, and find tools at [tpmjs.com](https://tpmjs.com). Publish your tool by adding the `tpmjs` keyword to your package.json—it appears in the registry within 15 minutes.
|
||||||
|
|
||||||
## Why TPMJS?
|
## Why TPMJS?
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ npx @tpmjs/create-basic-tools
|
||||||
Or add manually to your package.json:
|
Or add manually to your package.json:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"keywords": ["tpmjs-tool"],
|
"keywords": ["tpmjs"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis"
|
"category": "text-analysis"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,13 @@ TPMJS runs three automated sync jobs:
|
||||||
|
|
||||||
**1. npm Changes Feed (every 2 minutes)**
|
**1. npm Changes Feed (every 2 minutes)**
|
||||||
```
|
```
|
||||||
npm registry → /_changes endpoint → filter for tpmjs-tool keyword → process
|
npm registry → /_changes endpoint → filter for tpmjs keyword → process
|
||||||
```
|
```
|
||||||
This catches new packages and updates in near-real-time. We track sequence numbers so we never reprocess.
|
This catches new packages and updates in near-real-time. We track sequence numbers so we never reprocess.
|
||||||
|
|
||||||
**2. Keyword Search (every 15 minutes)**
|
**2. Keyword Search (every 15 minutes)**
|
||||||
```
|
```
|
||||||
npm search "tpmjs-tool" → up to 250 results → validate → ingest
|
npm search "tpmjs" → up to 250 results → validate → ingest
|
||||||
```
|
```
|
||||||
Backup mechanism. Catches anything the changes feed missed.
|
Backup mechanism. Catches anything the changes feed missed.
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ To get indexed, a package needs two things:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "@acme/my-tool",
|
"name": "@acme/my-tool",
|
||||||
"keywords": ["tpmjs-tool"],
|
"keywords": ["tpmjs"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "web-scraping",
|
"category": "web-scraping",
|
||||||
"description": "Scrapes URLs and returns structured markdown"
|
"description": "Scrapes URLs and returns structured markdown"
|
||||||
|
|
@ -303,7 +303,7 @@ Publish to npm → hope someone finds it → no visibility into usage
|
||||||
|
|
||||||
After TPMJS:
|
After TPMJS:
|
||||||
```
|
```
|
||||||
Publish to npm with tpmjs-tool keyword → indexed within 2 minutes →
|
Publish to npm with tpmjs keyword → indexed within 2 minutes →
|
||||||
schema auto-extracted → quality scored → discoverable by search →
|
schema auto-extracted → quality scored → discoverable by search →
|
||||||
execution stats tracked
|
execution stats tracked
|
||||||
```
|
```
|
||||||
|
|
@ -383,7 +383,7 @@ Discovery is the bottleneck. We're fixing discovery.
|
||||||
|
|
||||||
- **Browse**: https://tpmjs.com/tool-search
|
- **Browse**: https://tpmjs.com/tool-search
|
||||||
- **Playground**: https://tpmjs.com/playground
|
- **Playground**: https://tpmjs.com/playground
|
||||||
- **Publish**: Add `tpmjs-tool` keyword + `tpmjs` field to your package.json
|
- **Publish**: Add `tpmjs` keyword + `tpmjs` field to your package.json
|
||||||
- **API**: `GET https://tpmjs.com/api/tools`
|
- **API**: `GET https://tpmjs.com/api/tools`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ const tutorials = [
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
features: ['tpmjs-tool keyword', 'Schema extraction', 'Quality scoring'],
|
features: ['tpmjs keyword', 'Schema extraction', 'Quality scoring'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{ num: '1', text: 'Add "tpmjs-tool" to your package.json keywords' },
|
{ num: '1', text: 'Add "tpmjs" to your package.json keywords' },
|
||||||
{ num: '2', text: 'Define your tools in the "tpmjs" field' },
|
{ num: '2', text: 'Define your tools in the "tpmjs" field' },
|
||||||
{ num: '3', text: 'npm publish' },
|
{ num: '3', text: 'npm publish' },
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
const syncSteps = [
|
const syncSteps = [
|
||||||
{ step: '1', text: 'You publish to npm with tpmjs-tool keyword', color: 'purple' },
|
{ step: '1', text: 'You publish to npm with tpmjs keyword', color: 'purple' },
|
||||||
{ step: '2', text: 'Registry detects your package within 2 minutes', color: 'cyan' },
|
{ step: '2', text: 'Registry detects your package within 2 minutes', color: 'cyan' },
|
||||||
{ step: '3', text: 'We extract schemas from your package.json', color: 'emerald' },
|
{ step: '3', text: 'We extract schemas from your package.json', color: 'emerald' },
|
||||||
{ step: '4', text: 'Your tool appears in search results', color: 'yellow' },
|
{ step: '4', text: 'Your tool appears in search results', color: 'yellow' },
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ const packageJson = [
|
||||||
{ id: 'name', line: ' "name": "@your-org/your-tools",' },
|
{ id: 'name', line: ' "name": "@your-org/your-tools",' },
|
||||||
{ id: 'version', line: ' "version": "1.0.0",' },
|
{ id: 'version', line: ' "version": "1.0.0",' },
|
||||||
{ id: 'keywords-open', line: ' "keywords": [' },
|
{ id: 'keywords-open', line: ' "keywords": [' },
|
||||||
{ id: 'keyword', line: ' "tpmjs-tool"' },
|
{ id: 'keyword', line: ' "tpmjs"' },
|
||||||
{ id: 'keywords-close', line: ' ],' },
|
{ id: 'keywords-close', line: ' ],' },
|
||||||
{ id: 'tpmjs-open', line: ' "tpmjs": {' },
|
{ id: 'tpmjs-open', line: ' "tpmjs": {' },
|
||||||
{ id: 'tools-open', line: ' "tools": [{ ... }]' },
|
{ id: 'tools-open', line: ' "tools": [{ ... }]' },
|
||||||
|
|
@ -43,7 +43,7 @@ export function PackageSetupSlide(): React.ReactElement {
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={
|
className={
|
||||||
item.line.includes('tpmjs-tool')
|
item.line.includes('tpmjs')
|
||||||
? 'text-cyan-400'
|
? 'text-cyan-400'
|
||||||
: item.line.includes('"tpmjs"')
|
: item.line.includes('"tpmjs"')
|
||||||
? 'text-purple-400'
|
? 'text-purple-400'
|
||||||
|
|
@ -66,7 +66,7 @@ export function PackageSetupSlide(): React.ReactElement {
|
||||||
className="text-left space-y-4"
|
className="text-left space-y-4"
|
||||||
>
|
>
|
||||||
<div className="p-4 rounded-lg bg-white/5 border border-cyan-500/20">
|
<div className="p-4 rounded-lg bg-white/5 border border-cyan-500/20">
|
||||||
<div className="text-sm font-medium text-cyan-400 mb-1">tpmjs-tool keyword</div>
|
<div className="text-sm font-medium text-cyan-400 mb-1">tpmjs keyword</div>
|
||||||
<div className="text-xs text-white/50">Required. This is how we find you on npm.</div>
|
<div className="text-xs text-white/50">Required. This is how we find you on npm.</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 rounded-lg bg-white/5 border border-purple-500/20">
|
<div className="p-4 rounded-lg bg-white/5 border border-purple-500/20">
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { motion } from 'framer-motion';
|
||||||
const packageJsonLines = [
|
const packageJsonLines = [
|
||||||
{ id: 'open', line: '{' },
|
{ id: 'open', line: '{' },
|
||||||
{ id: 'name', line: ' "name": "@your-org/your-tools",' },
|
{ id: 'name', line: ' "name": "@your-org/your-tools",' },
|
||||||
{ id: 'keywords', line: ' "keywords": ["tpmjs-tool"],' },
|
{ id: 'keywords', line: ' "keywords": ["tpmjs"],' },
|
||||||
{ id: 'tpmjs', line: ' "tpmjs": {' },
|
{ id: 'tpmjs', line: ' "tpmjs": {' },
|
||||||
{ id: 'tools', line: ' "tools": [{' },
|
{ id: 'tools', line: ' "tools": [{' },
|
||||||
{ id: 'toolName', line: ' "name": "yourTool",' },
|
{ id: 'toolName', line: ' "name": "yourTool",' },
|
||||||
|
|
@ -16,7 +16,7 @@ const packageJsonLines = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{ step: '1', text: 'Add tpmjs-tool keyword', color: 'cyan' },
|
{ step: '1', text: 'Add tpmjs keyword', color: 'cyan' },
|
||||||
{ step: '2', text: 'Define tpmjs field with tool metadata', color: 'purple' },
|
{ step: '2', text: 'Define tpmjs field with tool metadata', color: 'purple' },
|
||||||
{ step: '3', text: 'npm publish', color: 'emerald' },
|
{ step: '3', text: 'npm publish', color: 'emerald' },
|
||||||
];
|
];
|
||||||
|
|
@ -56,7 +56,7 @@ export function GetStartedSlide(): React.ReactElement {
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={
|
className={
|
||||||
item.line.includes('tpmjs-tool')
|
item.line.includes('tpmjs')
|
||||||
? 'text-cyan-400'
|
? 'text-cyan-400'
|
||||||
: item.line.includes('tpmjs')
|
: item.line.includes('tpmjs')
|
||||||
? 'text-purple-400'
|
? 'text-purple-400'
|
||||||
|
|
|
||||||
60
apps/web/src/app/api/bundlephobia/route.ts
Normal file
60
apps/web/src/app/api/bundlephobia/route.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export const runtime = 'edge';
|
||||||
|
|
||||||
|
interface BundlephobiaResponse {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
size: number;
|
||||||
|
gzip: number;
|
||||||
|
dependencyCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/bundlephobia?package=@scope/name&version=1.0.0
|
||||||
|
* Proxies bundlephobia API to avoid CORS issues
|
||||||
|
*/
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const packageName = searchParams.get('package');
|
||||||
|
const version = searchParams.get('version');
|
||||||
|
|
||||||
|
if (!packageName) {
|
||||||
|
return NextResponse.json({ error: 'Package name required' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageWithVersion = version ? `${packageName}@${version}` : packageName;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://bundlephobia.com/api/size?package=${encodeURIComponent(packageWithVersion)}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
return NextResponse.json({ error: 'Package not found on bundlephobia' }, { status: 404 });
|
||||||
|
}
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to fetch bundle size' },
|
||||||
|
{ status: response.status }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: BundlephobiaResponse = await response.json();
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
name: data.name,
|
||||||
|
version: data.version,
|
||||||
|
size: data.size,
|
||||||
|
gzip: data.gzip,
|
||||||
|
dependencyCount: data.dependencyCount,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json({ error: 'Failed to fetch bundle size' }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -108,7 +108,7 @@ export async function POST(request: NextRequest) {
|
||||||
frameworks: validation.packageData.frameworks || [],
|
frameworks: validation.packageData.frameworks || [],
|
||||||
tier: validation.tier || 'minimal',
|
tier: validation.tier || 'minimal',
|
||||||
discoveryMethod: 'changes-feed',
|
discoveryMethod: 'changes-feed',
|
||||||
isOfficial: pkg.keywords?.includes('tpmjs-tool') || false,
|
isOfficial: pkg.keywords?.includes('tpmjs') || false,
|
||||||
npmDownloadsLastMonth: 0, // Will be updated by metrics sync
|
npmDownloadsLastMonth: 0, // Will be updated by metrics sync
|
||||||
githubStars: githubStars,
|
githubStars: githubStars,
|
||||||
},
|
},
|
||||||
|
|
@ -127,7 +127,7 @@ export async function POST(request: NextRequest) {
|
||||||
env: validation.packageData.env ?? undefined,
|
env: validation.packageData.env ?? undefined,
|
||||||
frameworks: validation.packageData.frameworks || [],
|
frameworks: validation.packageData.frameworks || [],
|
||||||
tier: validation.tier || 'minimal',
|
tier: validation.tier || 'minimal',
|
||||||
isOfficial: pkg.keywords?.includes('tpmjs-tool') || false,
|
isOfficial: pkg.keywords?.includes('tpmjs') || false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export const maxDuration = 300; // 5 minutes max for cron jobs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/sync/keyword
|
* POST /api/sync/keyword
|
||||||
* Sync tools by searching NPM for 'tpmjs-tool' keyword
|
* Sync tools by searching NPM for 'tpmjs' keyword
|
||||||
*
|
*
|
||||||
* This endpoint is called by Vercel Cron (every 15 minutes)
|
* This endpoint is called by Vercel Cron (every 15 minutes)
|
||||||
* Requires Authorization: Bearer <CRON_SECRET>
|
* Requires Authorization: Bearer <CRON_SECRET>
|
||||||
|
|
@ -40,9 +40,9 @@ export async function POST(request: NextRequest) {
|
||||||
const skippedPackages: Array<{ name: string; author: string; reason: string }> = [];
|
const skippedPackages: Array<{ name: string; author: string; reason: string }> = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Search for packages with 'tpmjs-tool' keyword
|
// Search for packages with 'tpmjs' keyword
|
||||||
const searchResults = await searchByKeyword({
|
const searchResults = await searchByKeyword({
|
||||||
keyword: 'tpmjs-tool',
|
keyword: 'tpmjs',
|
||||||
size: 250, // Get up to 250 packages per sync
|
size: 250, // Get up to 250 packages per sync
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ export async function POST(request: NextRequest) {
|
||||||
frameworks: validation.packageData.frameworks || [],
|
frameworks: validation.packageData.frameworks || [],
|
||||||
tier: validation.tier || 'minimal',
|
tier: validation.tier || 'minimal',
|
||||||
discoveryMethod: 'keyword',
|
discoveryMethod: 'keyword',
|
||||||
isOfficial: pkg.keywords?.includes('tpmjs-tool') || false,
|
isOfficial: pkg.keywords?.includes('tpmjs') || false,
|
||||||
npmDownloadsLastMonth: 0, // Will be updated by metrics sync
|
npmDownloadsLastMonth: 0, // Will be updated by metrics sync
|
||||||
githubStars: githubStars,
|
githubStars: githubStars,
|
||||||
},
|
},
|
||||||
|
|
@ -141,7 +141,7 @@ export async function POST(request: NextRequest) {
|
||||||
env: validation.packageData.env ?? undefined,
|
env: validation.packageData.env ?? undefined,
|
||||||
frameworks: validation.packageData.frameworks || [],
|
frameworks: validation.packageData.frameworks || [],
|
||||||
tier: validation.tier || 'minimal',
|
tier: validation.tier || 'minimal',
|
||||||
isOfficial: pkg.keywords?.includes('tpmjs-tool') || false,
|
isOfficial: pkg.keywords?.includes('tpmjs') || false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -370,9 +370,7 @@ Use registrySearch to find tools, then registryExecute to run them.\`,
|
||||||
<DocSubSection title="Tool Discovery">
|
<DocSubSection title="Tool Discovery">
|
||||||
<p className="text-foreground-secondary mb-4">
|
<p className="text-foreground-secondary mb-4">
|
||||||
TPMJS automatically discovers tools from npm packages that have the{' '}
|
TPMJS automatically discovers tools from npm packages that have the{' '}
|
||||||
<code className="text-primary bg-surface px-1.5 py-0.5 rounded">
|
<code className="text-primary bg-surface px-1.5 py-0.5 rounded">tpmjs</code>{' '}
|
||||||
tpmjs-tool
|
|
||||||
</code>{' '}
|
|
||||||
keyword. Tools are indexed every 2-15 minutes.
|
keyword. Tools are indexed every 2-15 minutes.
|
||||||
</p>
|
</p>
|
||||||
</DocSubSection>
|
</DocSubSection>
|
||||||
|
|
@ -832,7 +830,7 @@ while (true) {
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
||||||
{[
|
{[
|
||||||
{ step: '1', label: 'Add tpmjs-tool keyword' },
|
{ step: '1', label: 'Add tpmjs keyword' },
|
||||||
{ step: '2', label: 'Add tpmjs field' },
|
{ step: '2', label: 'Add tpmjs field' },
|
||||||
{ step: '3', label: 'Publish to npm' },
|
{ step: '3', label: 'Publish to npm' },
|
||||||
{ step: '4', label: 'Live in 15 minutes!' },
|
{ step: '4', label: 'Live in 15 minutes!' },
|
||||||
|
|
@ -878,7 +876,7 @@ while (true) {
|
||||||
code={`{
|
code={`{
|
||||||
"name": "@yourname/my-tool",
|
"name": "@yourname/my-tool",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"keywords": ["tpmjs-tool"],
|
"keywords": ["tpmjs"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis",
|
"category": "text-analysis",
|
||||||
"frameworks": ["vercel-ai"],
|
"frameworks": ["vercel-ai"],
|
||||||
|
|
@ -1118,7 +1116,7 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
q: 'How long does it take for my tool to appear?',
|
q: 'How long does it take for my tool to appear?',
|
||||||
a: 'Tools are discovered within 2-15 minutes of publishing to npm. Make sure you have the "tpmjs-tool" keyword in your package.json.',
|
a: 'Tools are discovered within 2-15 minutes of publishing to npm. Make sure you have the "tpmjs" keyword in your package.json.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
q: 'What is auto-discovery?',
|
q: 'What is auto-discovery?',
|
||||||
|
|
@ -1155,8 +1153,8 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
|
||||||
<ul className="list-disc list-inside space-y-2 text-foreground-secondary">
|
<ul className="list-disc list-inside space-y-2 text-foreground-secondary">
|
||||||
<li>
|
<li>
|
||||||
Ensure you have{' '}
|
Ensure you have{' '}
|
||||||
<code className="text-primary bg-surface px-1 rounded">tpmjs-tool</code> in
|
<code className="text-primary bg-surface px-1 rounded">tpmjs</code> in your
|
||||||
your keywords
|
keywords
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Verify your <code className="text-primary">tpmjs</code> field is valid JSON
|
Verify your <code className="text-primary">tpmjs</code> field is valid JSON
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ export default function FAQPage(): React.ReactElement {
|
||||||
<p>
|
<p>
|
||||||
Think of it as npm for AI tools. Developers publish tools to npm with the{' '}
|
Think of it as npm for AI tools. Developers publish tools to npm with the{' '}
|
||||||
<code className="text-foreground bg-background px-2 py-1 rounded border border-border">
|
<code className="text-foreground bg-background px-2 py-1 rounded border border-border">
|
||||||
tpmjs-tool
|
tpmjs
|
||||||
</code>{' '}
|
</code>{' '}
|
||||||
keyword, and TPMJS automatically syncs them to our registry where they can be
|
keyword, and TPMJS automatically syncs them to our registry where they can be
|
||||||
discovered by AI agents and developers.
|
discovered by AI agents and developers.
|
||||||
|
|
@ -97,7 +97,7 @@ export default function FAQPage(): React.ReactElement {
|
||||||
<li>
|
<li>
|
||||||
Add the{' '}
|
Add the{' '}
|
||||||
<code className="text-foreground bg-background px-2 py-1 rounded border border-border">
|
<code className="text-foreground bg-background px-2 py-1 rounded border border-border">
|
||||||
tpmjs-tool
|
tpmjs
|
||||||
</code>{' '}
|
</code>{' '}
|
||||||
keyword to your package.json
|
keyword to your package.json
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -254,7 +254,7 @@ export default function FAQPage(): React.ReactElement {
|
||||||
<strong className="text-foreground">Keyword Search (every 15 minutes):</strong>{' '}
|
<strong className="text-foreground">Keyword Search (every 15 minutes):</strong>{' '}
|
||||||
Actively searches for packages with the{' '}
|
Actively searches for packages with the{' '}
|
||||||
<code className="text-foreground bg-background px-2 py-1 rounded border border-border">
|
<code className="text-foreground bg-background px-2 py-1 rounded border border-border">
|
||||||
tpmjs-tool
|
tpmjs
|
||||||
</code>{' '}
|
</code>{' '}
|
||||||
keyword
|
keyword
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ export default function HowItWorksPage(): React.ReactElement {
|
||||||
code={`{
|
code={`{
|
||||||
"name": "@yourname/awesome-tool",
|
"name": "@yourname/awesome-tool",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"keywords": ["tpmjs-tool"],
|
"keywords": ["tpmjs"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis",
|
"category": "text-analysis",
|
||||||
"frameworks": ["vercel-ai"],
|
"frameworks": ["vercel-ai"],
|
||||||
|
|
@ -232,7 +232,7 @@ const result = await streamText({
|
||||||
<div className="p-4 border border-border rounded-lg bg-surface">
|
<div className="p-4 border border-border rounded-lg bg-surface">
|
||||||
<h4 className="font-semibold mb-2 text-foreground">Keyword Search</h4>
|
<h4 className="font-semibold mb-2 text-foreground">Keyword Search</h4>
|
||||||
<p className="text-sm text-foreground-secondary">
|
<p className="text-sm text-foreground-secondary">
|
||||||
Searches npm for "tpmjs-tool" keyword
|
Searches npm for "tpmjs" keyword
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2 text-xs text-foreground-tertiary">Every 15 minutes</div>
|
<div className="mt-2 text-xs text-foreground-tertiary">Every 15 minutes</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -398,8 +398,7 @@ const result = await streamText({
|
||||||
<h4 className="font-semibold text-foreground mb-1">Developer publishes to npm</h4>
|
<h4 className="font-semibold text-foreground mb-1">Developer publishes to npm</h4>
|
||||||
<p className="text-sm text-foreground-secondary">
|
<p className="text-sm text-foreground-secondary">
|
||||||
Package with{' '}
|
Package with{' '}
|
||||||
<code className="text-xs bg-surface px-1 py-0.5 rounded">tpmjs-tool</code>{' '}
|
<code className="text-xs bg-surface px-1 py-0.5 rounded">tpmjs</code> keyword
|
||||||
keyword
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-foreground-tertiary ml-auto">~1 second</span>
|
<span className="text-xs text-foreground-tertiary ml-auto">~1 second</span>
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export default function PrivacyPage(): React.ReactElement {
|
||||||
<p className="text-foreground-secondary mb-4">
|
<p className="text-foreground-secondary mb-4">
|
||||||
We automatically collect and index public metadata from npm packages that use the{' '}
|
We automatically collect and index public metadata from npm packages that use the{' '}
|
||||||
<code className="text-foreground bg-background px-2 py-1 rounded text-sm">
|
<code className="text-foreground bg-background px-2 py-1 rounded text-sm">
|
||||||
tpmjs-tool
|
tpmjs
|
||||||
</code>{' '}
|
</code>{' '}
|
||||||
keyword. This includes:
|
keyword. This includes:
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -430,8 +430,7 @@ export default function PrivacyPage(): React.ReactElement {
|
||||||
<p className="text-sm text-foreground-secondary">
|
<p className="text-sm text-foreground-secondary">
|
||||||
Request deletion of your data. To remove a tool from TPMJS, unpublish it from npm
|
Request deletion of your data. To remove a tool from TPMJS, unpublish it from npm
|
||||||
or remove the{' '}
|
or remove the{' '}
|
||||||
<code className="text-xs bg-background px-1 py-0.5 rounded">tpmjs-tool</code>{' '}
|
<code className="text-xs bg-background px-1 py-0.5 rounded">tpmjs</code> keyword.
|
||||||
keyword.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,8 @@ export default function PublishPage(): React.ReactElement {
|
||||||
<ol className="space-y-4 text-foreground-secondary">
|
<ol className="space-y-4 text-foreground-secondary">
|
||||||
<li className="text-lg">Create a new NPM package</li>
|
<li className="text-lg">Create a new NPM package</li>
|
||||||
<li className="text-lg">
|
<li className="text-lg">
|
||||||
Add{' '}
|
Add <code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs</code> to
|
||||||
<code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs-tool</code>{' '}
|
keywords
|
||||||
to keywords
|
|
||||||
</li>
|
</li>
|
||||||
<li className="text-lg">
|
<li className="text-lg">
|
||||||
Add a <code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs</code>{' '}
|
Add a <code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs</code>{' '}
|
||||||
|
|
@ -112,8 +111,7 @@ export default function PublishPage(): React.ReactElement {
|
||||||
Step 1: Add Required Keyword
|
Step 1: Add Required Keyword
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-foreground-secondary mb-6">
|
<p className="text-lg text-foreground-secondary mb-6">
|
||||||
Add the{' '}
|
Add the <code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs</code>{' '}
|
||||||
<code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs-tool</code>{' '}
|
|
||||||
keyword to your package.json. This is required for automatic discovery.
|
keyword to your package.json. This is required for automatic discovery.
|
||||||
</p>
|
</p>
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
|
|
@ -121,7 +119,7 @@ export default function PublishPage(): React.ReactElement {
|
||||||
code={`{
|
code={`{
|
||||||
"name": "@yourname/my-awesome-tool",
|
"name": "@yourname/my-awesome-tool",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"keywords": ["tpmjs-tool", "ai", "text"],
|
"keywords": ["tpmjs", "ai", "text"],
|
||||||
...
|
...
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
|
|
@ -186,7 +184,7 @@ export default function PublishPage(): React.ReactElement {
|
||||||
language="json"
|
language="json"
|
||||||
code={`{
|
code={`{
|
||||||
"name": "@yourname/my-awesome-tool",
|
"name": "@yourname/my-awesome-tool",
|
||||||
"keywords": ["tpmjs-tool"],
|
"keywords": ["tpmjs"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis"
|
"category": "text-analysis"
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +209,7 @@ export default function PublishPage(): React.ReactElement {
|
||||||
language="json"
|
language="json"
|
||||||
code={`{
|
code={`{
|
||||||
"name": "@yourname/sentiment-tool",
|
"name": "@yourname/sentiment-tool",
|
||||||
"keywords": ["tpmjs-tool"],
|
"keywords": ["tpmjs"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis",
|
"category": "text-analysis",
|
||||||
"frameworks": ["vercel-ai", "langchain"],
|
"frameworks": ["vercel-ai", "langchain"],
|
||||||
|
|
@ -336,7 +334,7 @@ npm publish --access public
|
||||||
code={`{
|
code={`{
|
||||||
"name": "@tpmjs/createblogpost",
|
"name": "@tpmjs/createblogpost",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"keywords": ["tpmjs-tool", "blog", "content"],
|
"keywords": ["tpmjs", "blog", "content"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis",
|
"category": "text-analysis",
|
||||||
"frameworks": ["vercel-ai", "langchain"],
|
"frameworks": ["vercel-ai", "langchain"],
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,7 @@ export default function SpecPage(): React.ReactElement {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="text-sm text-foreground-secondary">
|
<p className="text-sm text-foreground-secondary">
|
||||||
Add the{' '}
|
Add the <code className="text-foreground bg-surface px-1 rounded">tpmjs</code>{' '}
|
||||||
<code className="text-foreground bg-surface px-1 rounded">tpmjs-tool</code>{' '}
|
|
||||||
keyword and a{' '}
|
keyword and a{' '}
|
||||||
<code className="text-foreground bg-surface px-1 rounded">tpmjs</code> metadata
|
<code className="text-foreground bg-surface px-1 rounded">tpmjs</code> metadata
|
||||||
field to your package.json
|
field to your package.json
|
||||||
|
|
@ -207,7 +206,7 @@ export default function SpecPage(): React.ReactElement {
|
||||||
language="json"
|
language="json"
|
||||||
code={`{
|
code={`{
|
||||||
"name": "@yourname/my-tool",
|
"name": "@yourname/my-tool",
|
||||||
"keywords": ["tpmjs-tool"],
|
"keywords": ["tpmjs"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis"
|
"category": "text-analysis"
|
||||||
}
|
}
|
||||||
|
|
@ -341,7 +340,7 @@ export default function SpecPage(): React.ReactElement {
|
||||||
language="json"
|
language="json"
|
||||||
code={`{
|
code={`{
|
||||||
"name": "@yourname/sentiment-tool",
|
"name": "@yourname/sentiment-tool",
|
||||||
"keywords": ["tpmjs-tool"],
|
"keywords": ["tpmjs"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis",
|
"category": "text-analysis",
|
||||||
"frameworks": ["vercel-ai", "langchain"],
|
"frameworks": ["vercel-ai", "langchain"],
|
||||||
|
|
@ -681,8 +680,8 @@ export default function SpecPage(): React.ReactElement {
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="text-sm text-foreground-secondary mb-2">
|
<p className="text-sm text-foreground-secondary mb-2">
|
||||||
Searches for{' '}
|
Searches for{' '}
|
||||||
<code className="text-foreground bg-surface px-1 rounded">tpmjs-tool</code>{' '}
|
<code className="text-foreground bg-surface px-1 rounded">tpmjs</code> keyword
|
||||||
keyword every 15 minutes
|
every 15 minutes
|
||||||
</p>
|
</p>
|
||||||
<Badge variant="outline" size="sm">
|
<Badge variant="outline" size="sm">
|
||||||
Every 15 min
|
Every 15 min
|
||||||
|
|
@ -760,7 +759,7 @@ export default function SpecPage(): React.ReactElement {
|
||||||
<CardContent className="pt-6 text-center">
|
<CardContent className="pt-6 text-center">
|
||||||
<div className="text-3xl mb-2">1️⃣</div>
|
<div className="text-3xl mb-2">1️⃣</div>
|
||||||
<p className="text-sm text-foreground-secondary">
|
<p className="text-sm text-foreground-secondary">
|
||||||
Add <code className="text-foreground bg-surface px-1 rounded">tpmjs-tool</code>{' '}
|
Add <code className="text-foreground bg-surface px-1 rounded">tpmjs</code>{' '}
|
||||||
keyword
|
keyword
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ export default function TermsPage(): React.ReactElement {
|
||||||
<ul className="list-disc pl-4 sm:pl-6 space-y-2">
|
<ul className="list-disc pl-4 sm:pl-6 space-y-2">
|
||||||
<li>
|
<li>
|
||||||
Automatic discovery and indexing of npm packages with the{' '}
|
Automatic discovery and indexing of npm packages with the{' '}
|
||||||
<code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs-tool</code>{' '}
|
<code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs</code>{' '}
|
||||||
keyword
|
keyword
|
||||||
</li>
|
</li>
|
||||||
<li>A searchable registry of AI tools with quality scoring and health checks</li>
|
<li>A searchable registry of AI tools with quality scoring and health checks</li>
|
||||||
|
|
@ -247,7 +247,7 @@ export default function TermsPage(): React.ReactElement {
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
By publishing a tool to npm with the{' '}
|
By publishing a tool to npm with the{' '}
|
||||||
<code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs-tool</code>{' '}
|
<code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs</code>{' '}
|
||||||
keyword, you grant TPMJS a non-exclusive, worldwide, royalty-free license to:
|
keyword, you grant TPMJS a non-exclusive, worldwide, royalty-free license to:
|
||||||
</p>
|
</p>
|
||||||
<ul className="list-disc pl-4 sm:pl-6 space-y-2">
|
<ul className="list-disc pl-4 sm:pl-6 space-y-2">
|
||||||
|
|
@ -321,7 +321,7 @@ export default function TermsPage(): React.ReactElement {
|
||||||
<p>
|
<p>
|
||||||
You may stop using the Service at any time. If you have published tools, they will
|
You may stop using the Service at any time. If you have published tools, they will
|
||||||
remain in the registry unless you remove the{' '}
|
remain in the registry unless you remove the{' '}
|
||||||
<code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs-tool</code>{' '}
|
<code className="text-foreground bg-surface px-2 py-1 rounded">tpmjs</code>{' '}
|
||||||
keyword from your package or unpublish your package from npm.
|
keyword from your package or unpublish your package from npm.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import { Spinner } from '@tpmjs/ui/Spinner/Spinner';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { AppHeader } from '~/components/AppHeader';
|
import { AppHeader } from '~/components/AppHeader';
|
||||||
|
import { BundleSize } from '~/components/BundleSize';
|
||||||
|
import { DownloadSparkline } from '~/components/DownloadSparkline';
|
||||||
import { Markdown } from '~/components/Markdown';
|
import { Markdown } from '~/components/Markdown';
|
||||||
import { ToolPlayground } from '~/components/ToolPlayground';
|
import { ToolPlayground } from '~/components/ToolPlayground';
|
||||||
|
|
||||||
|
|
@ -340,6 +342,21 @@ export function ToolDetailClient({ tool, slug }: ToolDetailClientProps): React.R
|
||||||
language="bash"
|
language="bash"
|
||||||
showCopy={true}
|
showCopy={true}
|
||||||
/>
|
/>
|
||||||
|
<CodeBlock
|
||||||
|
code={`yarn add ${pkg.npmPackageName}`}
|
||||||
|
language="bash"
|
||||||
|
showCopy={true}
|
||||||
|
/>
|
||||||
|
<CodeBlock
|
||||||
|
code={`bun add ${pkg.npmPackageName}`}
|
||||||
|
language="bash"
|
||||||
|
showCopy={true}
|
||||||
|
/>
|
||||||
|
<CodeBlock
|
||||||
|
code={`deno add npm:${pkg.npmPackageName}`}
|
||||||
|
language="bash"
|
||||||
|
showCopy={true}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -547,6 +564,9 @@ console.log(result.text);`}
|
||||||
<p className="text-2xl font-bold text-foreground">
|
<p className="text-2xl font-bold text-foreground">
|
||||||
{pkg.npmDownloadsLastMonth?.toLocaleString() || '0'}
|
{pkg.npmDownloadsLastMonth?.toLocaleString() || '0'}
|
||||||
</p>
|
</p>
|
||||||
|
<div className="mt-2">
|
||||||
|
<DownloadSparkline packageName={pkg.npmPackageName} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{pkg.githubStars != null && (
|
{pkg.githubStars != null && (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -574,6 +594,9 @@ console.log(result.text);`}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Bundle Size */}
|
||||||
|
<BundleSize packageName={pkg.npmPackageName} version={pkg.npmVersion} />
|
||||||
|
|
||||||
{/* NPM Keywords */}
|
{/* NPM Keywords */}
|
||||||
{pkg.npmKeywords && pkg.npmKeywords.length > 0 && (
|
{pkg.npmKeywords && pkg.npmKeywords.length > 0 && (
|
||||||
<Card>
|
<Card>
|
||||||
|
|
|
||||||
|
|
@ -463,7 +463,7 @@ export default function ToolSearchPage(): React.ReactElement {
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
window.open(
|
window.open(
|
||||||
'https://www.npmjs.com/search?q=keywords:tpmjs-tool',
|
'https://www.npmjs.com/search?q=keywords:tpmjs',
|
||||||
'_blank',
|
'_blank',
|
||||||
'noopener'
|
'noopener'
|
||||||
)
|
)
|
||||||
|
|
@ -483,9 +483,7 @@ export default function ToolSearchPage(): React.ReactElement {
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-foreground-secondary">
|
<p className="text-sm text-foreground-secondary">
|
||||||
Add{' '}
|
Add{' '}
|
||||||
<code className="px-1.5 py-0.5 bg-muted rounded text-xs">
|
<code className="px-1.5 py-0.5 bg-muted rounded text-xs">tpmjs</code>{' '}
|
||||||
tpmjs-tool
|
|
||||||
</code>{' '}
|
|
||||||
keyword to your package.json
|
keyword to your package.json
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const nodeDescriptions: Record<string, { title: string; description: string }> =
|
||||||
npm: {
|
npm: {
|
||||||
title: 'NPM Registry',
|
title: 'NPM Registry',
|
||||||
description:
|
description:
|
||||||
'The public npm registry containing all JavaScript packages. TPMJS monitors packages with the tpmjs-tool keyword.',
|
'The public npm registry containing all JavaScript packages. TPMJS monitors packages with the tpmjs keyword.',
|
||||||
},
|
},
|
||||||
'changes-feed': {
|
'changes-feed': {
|
||||||
title: 'Changes Feed',
|
title: 'Changes Feed',
|
||||||
|
|
@ -34,7 +34,7 @@ const nodeDescriptions: Record<string, { title: string; description: string }> =
|
||||||
'keyword-search': {
|
'keyword-search': {
|
||||||
title: 'Keyword Search',
|
title: 'Keyword Search',
|
||||||
description:
|
description:
|
||||||
'Periodic search for packages with the tpmjs-tool keyword. Acts as a backup to ensure no packages are missed.',
|
'Periodic search for packages with the tpmjs keyword. Acts as a backup to ensure no packages are missed.',
|
||||||
},
|
},
|
||||||
validation: {
|
validation: {
|
||||||
title: 'Schema Validation',
|
title: 'Schema Validation',
|
||||||
|
|
|
||||||
124
apps/web/src/components/BundleSize.tsx
Normal file
124
apps/web/src/components/BundleSize.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
interface BundleSizeData {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
size: number;
|
||||||
|
gzip: number;
|
||||||
|
dependencyCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BundleSizeProps {
|
||||||
|
packageName: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes(bytes: number): string {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'kB', 'MB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BundleSize({ packageName, version }: BundleSizeProps): React.ReactElement | null {
|
||||||
|
const [data, setData] = useState<BundleSizeData | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchBundleSize = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({ package: packageName, version });
|
||||||
|
const response = await fetch(`/api/bundlephobia?${params}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
setError('not-found');
|
||||||
|
} else {
|
||||||
|
setError('failed');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
setData(result);
|
||||||
|
} catch {
|
||||||
|
setError('failed');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchBundleSize();
|
||||||
|
}, [packageName, version]);
|
||||||
|
|
||||||
|
// Don't render anything if package not found (common for scoped packages)
|
||||||
|
if (error === 'not-found') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Bundle Size</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-2 animate-pulse">
|
||||||
|
<div className="h-4 bg-muted rounded w-24" />
|
||||||
|
<div className="h-4 bg-muted rounded w-20" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Bundle Size</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-foreground-secondary">Minified</span>
|
||||||
|
<span className="text-sm font-mono font-medium text-foreground">
|
||||||
|
{formatBytes(data.size)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-foreground-secondary">Gzipped</span>
|
||||||
|
<span className="text-sm font-mono font-medium text-foreground">
|
||||||
|
{formatBytes(data.gzip)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{data.dependencyCount > 0 && (
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-foreground-secondary">Dependencies</span>
|
||||||
|
<span className="text-sm font-mono font-medium text-foreground">
|
||||||
|
{data.dependencyCount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<a
|
||||||
|
href={`https://bundlephobia.com/package/${packageName}@${version}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center gap-1 text-xs text-foreground-tertiary hover:text-foreground-secondary pt-2"
|
||||||
|
>
|
||||||
|
<span>View on Bundlephobia →</span>
|
||||||
|
</a>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
131
apps/web/src/components/DownloadSparkline.tsx
Normal file
131
apps/web/src/components/DownloadSparkline.tsx
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
interface DownloadDay {
|
||||||
|
downloads: number;
|
||||||
|
day: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DownloadSparklineProps {
|
||||||
|
packageName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DownloadSparkline({ packageName }: DownloadSparklineProps): React.ReactElement {
|
||||||
|
const [downloads, setDownloads] = useState<DownloadDay[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDownloads = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.npmjs.org/downloads/range/last-month/${encodeURIComponent(packageName)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
setError(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setDownloads(data.downloads || []);
|
||||||
|
} catch {
|
||||||
|
setError(true);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchDownloads();
|
||||||
|
}, [packageName]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div className="h-8 w-full animate-pulse bg-muted rounded" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || downloads.length === 0) {
|
||||||
|
return <div className="h-8" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate sparkline path
|
||||||
|
const values = downloads.map((d) => d.downloads);
|
||||||
|
const max = Math.max(...values, 1);
|
||||||
|
const min = Math.min(...values);
|
||||||
|
const range = max - min || 1;
|
||||||
|
|
||||||
|
const width = 200;
|
||||||
|
const height = 32;
|
||||||
|
const padding = 2;
|
||||||
|
|
||||||
|
const points = values.map((value, index) => {
|
||||||
|
const x = padding + (index / (values.length - 1)) * (width - padding * 2);
|
||||||
|
const y = height - padding - ((value - min) / range) * (height - padding * 2);
|
||||||
|
return `${x},${y}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const pathD = `M ${points.join(' L ')}`;
|
||||||
|
|
||||||
|
// Calculate trend (compare first week vs last week)
|
||||||
|
const firstWeek = values.slice(0, 7).reduce((a, b) => a + b, 0);
|
||||||
|
const lastWeek = values.slice(-7).reduce((a, b) => a + b, 0);
|
||||||
|
const trendUp = lastWeek > firstWeek;
|
||||||
|
const trendPercent = firstWeek > 0 ? Math.round(((lastWeek - firstWeek) / firstWeek) * 100) : 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<svg
|
||||||
|
viewBox={`0 0 ${width} ${height}`}
|
||||||
|
className="w-full h-8"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
role="img"
|
||||||
|
aria-label="Download trend over the last 30 days"
|
||||||
|
>
|
||||||
|
{/* Gradient fill under the line */}
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
id={`sparkline-gradient-${packageName.replace(/[^a-z0-9]/gi, '-')}`}
|
||||||
|
x1="0%"
|
||||||
|
y1="0%"
|
||||||
|
x2="0%"
|
||||||
|
y2="100%"
|
||||||
|
>
|
||||||
|
<stop offset="0%" stopColor="currentColor" stopOpacity="0.3" />
|
||||||
|
<stop offset="100%" stopColor="currentColor" stopOpacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
{/* Area fill */}
|
||||||
|
<path
|
||||||
|
d={`${pathD} L ${width - padding},${height - padding} L ${padding},${height - padding} Z`}
|
||||||
|
fill={`url(#sparkline-gradient-${packageName.replace(/[^a-z0-9]/gi, '-')})`}
|
||||||
|
className="text-primary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Line */}
|
||||||
|
<path
|
||||||
|
d={pathD}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className="text-primary"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{/* Trend indicator */}
|
||||||
|
{Math.abs(trendPercent) >= 5 && (
|
||||||
|
<div
|
||||||
|
className={`text-xs flex items-center gap-1 ${trendUp ? 'text-emerald-500' : 'text-red-500'}`}
|
||||||
|
>
|
||||||
|
<span>{trendUp ? '↑' : '↓'}</span>
|
||||||
|
<span>{Math.abs(trendPercent)}% vs last week</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -907,7 +907,7 @@ const providers: Record<ExecutionProvider, (toolId: string, params: any, env?: a
|
||||||
'aws-lambda': async (toolId, params, env) => {
|
'aws-lambda': async (toolId, params, env) => {
|
||||||
const lambda = new AWS.Lambda();
|
const lambda = new AWS.Lambda();
|
||||||
const result = await lambda.invoke({
|
const result = await lambda.invoke({
|
||||||
FunctionName: `tpmjs-tool-${toolId.replace('::', '-')}`,
|
FunctionName: `tpmjs-${toolId.replace('::', '-')}`,
|
||||||
Payload: JSON.stringify({ params, env }),
|
Payload: JSON.stringify({ params, env }),
|
||||||
}).promise();
|
}).promise();
|
||||||
return JSON.parse(result.Payload as string);
|
return JSON.parse(result.Payload as string);
|
||||||
|
|
|
||||||
|
|
@ -377,7 +377,7 @@ packages/registryExecute/
|
||||||
"types": "./dist/index.d.ts"
|
"types": "./dist/index.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs", "tpmjs-tool", "ai-sdk", "vercel-ai", "tools", "registry", "search"],
|
"keywords": ["tpmjs", "tpmjs", "ai-sdk", "vercel-ai", "tools", "registry", "search"],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ai": "^4.0.0",
|
"ai": "^4.0.0",
|
||||||
"zod": "^3.0.0"
|
"zod": "^3.0.0"
|
||||||
|
|
@ -402,7 +402,7 @@ packages/registryExecute/
|
||||||
"types": "./dist/index.d.ts"
|
"types": "./dist/index.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs", "tpmjs-tool", "ai-sdk", "vercel-ai", "tools", "registry", "execute"],
|
"keywords": ["tpmjs", "tpmjs", "ai-sdk", "vercel-ai", "tools", "registry", "execute"],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ai": "^4.0.0",
|
"ai": "^4.0.0",
|
||||||
"zod": "^3.0.0"
|
"zod": "^3.0.0"
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ Tools declare their capabilities via a `tpmjs` field in package.json:
|
||||||
{
|
{
|
||||||
"name": "@myorg/web-scraper",
|
"name": "@myorg/web-scraper",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"keywords": ["tpmjs-tool"],
|
"keywords": ["tpmjs"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "web-scraping",
|
"category": "web-scraping",
|
||||||
"frameworks": ["vercel-ai"],
|
"frameworks": ["vercel-ai"],
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ The CLI asks for just your package name and uses sensible defaults for everythin
|
||||||
```bash
|
```bash
|
||||||
$ pnpmx @tpmjs/create-basic-tools
|
$ pnpmx @tpmjs/create-basic-tools
|
||||||
|
|
||||||
┌ create-tpmjs-tool
|
┌ create-tpmjs
|
||||||
│
|
│
|
||||||
◇ Package name
|
◇ Package name
|
||||||
│ @myorg/content-tools
|
│ @myorg/content-tools
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import * as logger from './utils/logger.js';
|
||||||
* Runs the interactive CLI workflow
|
* Runs the interactive CLI workflow
|
||||||
*/
|
*/
|
||||||
export async function runInteractiveCLI(): Promise<GenerationResult> {
|
export async function runInteractiveCLI(): Promise<GenerationResult> {
|
||||||
clack.intro(logger.bold('create-tpmjs-tool'));
|
clack.intro(logger.bold('create-tpmjs'));
|
||||||
|
|
||||||
// Only ask for package name
|
// Only ask for package name
|
||||||
const name = await clack.text({
|
const name = await clack.text({
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export function generatePackageJson(config: GeneratorConfig): string {
|
||||||
dev: 'tsup --watch',
|
dev: 'tsup --watch',
|
||||||
'type-check': 'tsc --noEmit',
|
'type-check': 'tsc --noEmit',
|
||||||
},
|
},
|
||||||
keywords: ['tpmjs-tool', 'ai-sdk', packageInfo.category],
|
keywords: ['tpmjs', 'ai-sdk', packageInfo.category],
|
||||||
author: packageInfo.author || '',
|
author: packageInfo.author || '',
|
||||||
license: packageInfo.license,
|
license: packageInfo.license,
|
||||||
tpmjs: {
|
tpmjs: {
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,4 @@
|
||||||
- Supports both Markdown and MDX formats
|
- Supports both Markdown and MDX formats
|
||||||
- Automatic slug generation, word count, and reading time calculation
|
- Automatic slug generation, word count, and reading time calculation
|
||||||
- Rich TPMJS metadata including parameters, authentication, pricing, and AI agent guidance
|
- Rich TPMJS metadata including parameters, authentication, pricing, and AI agent guidance
|
||||||
- Tagged with 'tpmjs-tool' keyword for NPM registry discovery
|
- Tagged with 'tpmjs' keyword for NPM registry discovery
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"description": "A tool for creating structured blog posts with AI-generated content",
|
"description": "A tool for creating structured blog posts with AI-generated content",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"keywords": ["tpmjs-tool", "blog", "content", "ai", "writing"],
|
"keywords": ["tpmjs", "blog", "content", "ai", "writing"],
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs-tool", "emoji", "ai", "text-processing"],
|
"keywords": ["tpmjs", "emoji", "ai", "text-processing"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis",
|
"category": "text-analysis",
|
||||||
"frameworks": ["vercel-ai"],
|
"frameworks": ["vercel-ai"],
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ This package serves as a template for creating your own TPMJS tools. Key require
|
||||||
"description": "Your tool description"
|
"description": "Your tool description"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
5. **Include the `tpmjs-tool` keyword** in package.json
|
5. **Include the `tpmjs` keyword** in package.json
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs-tool", "ai-sdk", "hello", "example"],
|
"keywords": ["tpmjs", "ai-sdk", "hello", "example"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis",
|
"category": "text-analysis",
|
||||||
"frameworks": ["vercel-ai"],
|
"frameworks": ["vercel-ai"],
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs-tool", "markdown", "formatter", "text-processing"],
|
"keywords": ["tpmjs", "markdown", "formatter", "text-processing"],
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"category": "text-analysis",
|
"category": "text-analysis",
|
||||||
"frameworks": ["vercel-ai"],
|
"frameworks": ["vercel-ai"],
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs-tool", "ai-sdk", "example"],
|
"keywords": ["tpmjs", "ai-sdk", "example"],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ai": "6.0.0-beta.124",
|
"ai": "6.0.0-beta.124",
|
||||||
"zod": "^4.0.0"
|
"zod": "^4.0.0"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs-tool", "ai-sdk", "vercel-ai", "registry", "execute"],
|
"keywords": ["tpmjs", "ai-sdk", "vercel-ai", "registry", "execute"],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ai": "6.0.0-beta.124"
|
"ai": "6.0.0-beta.124"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs-tool", "ai-sdk", "vercel-ai", "registry", "search"],
|
"keywords": ["tpmjs", "ai-sdk", "vercel-ai", "registry", "search"],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ai": "6.0.0-beta.124"
|
"ai": "6.0.0-beta.124"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs-tool", "ai", "search", "tool-registry"],
|
"keywords": ["tpmjs", "ai", "search", "tool-registry"],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ai": "6.0.0-beta.124",
|
"ai": "6.0.0-beta.124",
|
||||||
"zod": "^3.23.0"
|
"zod": "^3.23.0"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": ["tpmjs-tool", "ai-sdk", "code-execution", "sandbox", "unsandbox"],
|
"keywords": ["tpmjs", "ai-sdk", "code-execution", "sandbox", "unsandbox"],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@thomasdavis/unsandbox": "^0.0.3"
|
"@thomasdavis/unsandbox": "^0.0.3"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue