- Parse CHANGELOG.md files from SDK and tool packages at build time - Display version history with major/minor/patch badges - Group by SDK packages (ui, types, utils, env) and Tool packages - Add changelog link to navigation header - Fix unescaped apostrophes in docs page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1064 lines
41 KiB
TypeScript
1064 lines
41 KiB
TypeScript
'use client';
|
|
|
|
import { TPMJS_CATEGORIES } from '@tpmjs/types/tpmjs';
|
|
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
|
import { Button } from '@tpmjs/ui/Button/Button';
|
|
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
|
|
import Link from 'next/link';
|
|
import { useEffect, useState } from 'react';
|
|
import { AppHeader } from '~/components/AppHeader';
|
|
|
|
const NAV_SECTIONS = [
|
|
{
|
|
title: 'Getting Started',
|
|
items: [
|
|
{ id: 'introduction', label: 'Introduction' },
|
|
{ id: 'quickstart', label: 'Quick Start' },
|
|
{ id: 'core-concepts', label: 'Core Concepts' },
|
|
],
|
|
},
|
|
{
|
|
title: 'SDK Reference',
|
|
items: [
|
|
{ id: 'installation', label: 'Installation' },
|
|
{ id: 'registry-search', label: 'registrySearchTool' },
|
|
{ id: 'registry-execute', label: 'registryExecuteTool' },
|
|
{ id: 'passing-api-keys', label: 'Passing API Keys' },
|
|
],
|
|
},
|
|
{
|
|
title: 'API Reference',
|
|
items: [
|
|
{ id: 'api-overview', label: 'Overview' },
|
|
{ id: 'api-tools', label: 'GET /api/tools' },
|
|
{ id: 'api-tools-search', label: 'GET /api/tools/search' },
|
|
{ id: 'api-tool-detail', label: 'GET /api/tools/[id]' },
|
|
],
|
|
},
|
|
{
|
|
title: 'Publishing Tools',
|
|
items: [
|
|
{ id: 'publish-overview', label: 'Overview' },
|
|
{ id: 'tpmjs-spec', label: 'TPMJS Specification' },
|
|
{ id: 'metadata-tiers', label: 'Metadata Tiers' },
|
|
{ id: 'quality-score', label: 'Quality Score' },
|
|
],
|
|
},
|
|
{
|
|
title: 'Advanced',
|
|
items: [
|
|
{ id: 'override-execute', label: 'Override Execute' },
|
|
{ id: 'custom-wrappers', label: 'Custom Wrappers' },
|
|
{ id: 'self-hosting', label: 'Self-Hosting' },
|
|
{ id: 'security', label: 'Security' },
|
|
],
|
|
},
|
|
{
|
|
title: 'Resources',
|
|
items: [
|
|
{ id: 'faq', label: 'FAQ' },
|
|
{ id: 'troubleshooting', label: 'Troubleshooting' },
|
|
{ id: 'changelog', label: 'Changelog' },
|
|
],
|
|
},
|
|
];
|
|
|
|
function SidebarNav({
|
|
activeSection,
|
|
onSectionClick,
|
|
}: {
|
|
activeSection: string;
|
|
onSectionClick: (id: string) => void;
|
|
}) {
|
|
return (
|
|
<nav className="space-y-6">
|
|
{NAV_SECTIONS.map((section) => (
|
|
<div key={section.title}>
|
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-foreground-tertiary mb-2">
|
|
{section.title}
|
|
</h3>
|
|
<ul className="space-y-1">
|
|
{section.items.map((item) => (
|
|
<li key={item.id}>
|
|
<button
|
|
type="button"
|
|
onClick={() => onSectionClick(item.id)}
|
|
className={`block w-full text-left px-3 py-1.5 text-sm rounded-md transition-colors ${
|
|
activeSection === item.id
|
|
? 'bg-primary/10 text-primary font-medium'
|
|
: 'text-foreground-secondary hover:text-foreground hover:bg-surface-elevated'
|
|
}`}
|
|
>
|
|
{item.label}
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
function DocSection({
|
|
id,
|
|
title,
|
|
children,
|
|
}: {
|
|
id: string;
|
|
title: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<section id={id} className="scroll-mt-24 mb-16">
|
|
<h2 className="text-2xl font-bold mb-6 text-foreground pb-3 border-b border-border">
|
|
{title}
|
|
</h2>
|
|
{children}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function DocSubSection({ title, children }: { title: string; children: React.ReactNode }) {
|
|
return (
|
|
<div className="mb-8">
|
|
<h3 className="text-lg font-semibold mb-4 text-foreground">{title}</h3>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ParamTable({
|
|
params,
|
|
}: {
|
|
params: { name: string; type: string; required: boolean; description: string }[];
|
|
}) {
|
|
return (
|
|
<div className="overflow-x-auto border border-border rounded-lg">
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="border-b border-border bg-surface">
|
|
<th className="text-left py-3 px-4 text-foreground font-medium">Parameter</th>
|
|
<th className="text-left py-3 px-4 text-foreground font-medium">Type</th>
|
|
<th className="text-left py-3 px-4 text-foreground font-medium">Required</th>
|
|
<th className="text-left py-3 px-4 text-foreground font-medium">Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{params.map((param, i) => (
|
|
<tr
|
|
key={param.name}
|
|
className={i !== params.length - 1 ? 'border-b border-border' : ''}
|
|
>
|
|
<td className="py-3 px-4 font-mono text-primary">{param.name}</td>
|
|
<td className="py-3 px-4 font-mono text-foreground-secondary">{param.type}</td>
|
|
<td className="py-3 px-4">
|
|
{param.required ? (
|
|
<Badge variant="default" size="sm">
|
|
Yes
|
|
</Badge>
|
|
) : (
|
|
<span className="text-foreground-tertiary">No</span>
|
|
)}
|
|
</td>
|
|
<td className="py-3 px-4 text-foreground-secondary">{param.description}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function InfoCard({
|
|
icon,
|
|
title,
|
|
children,
|
|
}: {
|
|
icon: string;
|
|
title: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="p-5 border border-border rounded-lg bg-surface">
|
|
<div className="flex items-start gap-3">
|
|
<span className="text-2xl flex-shrink-0">{icon}</span>
|
|
<div>
|
|
<h4 className="font-semibold text-foreground mb-1">{title}</h4>
|
|
<p className="text-sm text-foreground-secondary">{children}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function DocsPage(): React.ReactElement {
|
|
const [activeSection, setActiveSection] = useState('introduction');
|
|
|
|
useEffect(() => {
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
setActiveSection(entry.target.id);
|
|
}
|
|
});
|
|
},
|
|
{ rootMargin: '-100px 0px -66%' }
|
|
);
|
|
|
|
NAV_SECTIONS.forEach((section) => {
|
|
section.items.forEach((item) => {
|
|
const element = document.getElementById(item.id);
|
|
if (element) observer.observe(element);
|
|
});
|
|
});
|
|
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
const scrollToSection = (id: string) => {
|
|
const element = document.getElementById(id);
|
|
if (element) {
|
|
element.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen flex flex-col bg-background">
|
|
<AppHeader />
|
|
|
|
<div className="flex-1 flex">
|
|
{/* Sidebar */}
|
|
<aside className="hidden lg:block w-64 flex-shrink-0 border-r border-border bg-surface/50">
|
|
<div className="sticky top-0 h-screen overflow-y-auto py-8 px-4">
|
|
<div className="mb-6">
|
|
<h2 className="text-lg font-bold text-foreground">Documentation</h2>
|
|
<p className="text-sm text-foreground-tertiary">v1.0.0</p>
|
|
</div>
|
|
<SidebarNav activeSection={activeSection} onSectionClick={scrollToSection} />
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Main Content */}
|
|
<main className="flex-1 min-w-0">
|
|
<div className="max-w-4xl mx-auto px-6 py-12">
|
|
{/* Hero */}
|
|
<div className="mb-12">
|
|
<h1 className="text-4xl font-bold mb-4 text-foreground">TPMJS Documentation</h1>
|
|
<p className="text-xl text-foreground-secondary mb-6">
|
|
The complete guide to using TPMJS - the registry for AI tools.
|
|
</p>
|
|
<div className="flex flex-wrap gap-3">
|
|
<a
|
|
href="https://www.npmjs.com/package/@tpmjs/registry-search"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<Button variant="default" size="sm">
|
|
Get Started
|
|
</Button>
|
|
</a>
|
|
<a href="https://github.com/tpmjs/tpmjs" target="_blank" rel="noopener noreferrer">
|
|
<Button variant="outline" size="sm">
|
|
View on GitHub
|
|
</Button>
|
|
</a>
|
|
<a href="https://playground.tpmjs.com" target="_blank" rel="noopener noreferrer">
|
|
<Button variant="outline" size="sm">
|
|
Try Playground
|
|
</Button>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ==================== GETTING STARTED ==================== */}
|
|
<DocSection id="introduction" title="Introduction">
|
|
<p className="text-foreground-secondary mb-6">
|
|
TPMJS (Tool Package Manager for JavaScript) is a registry and execution platform for
|
|
AI tools. It enables AI agents to dynamically discover, load, and execute tools from
|
|
npm packages at runtime.
|
|
</p>
|
|
<div className="grid md:grid-cols-3 gap-4 mb-6">
|
|
<InfoCard icon="🔍" title="Discover">
|
|
Search thousands of AI tools from the npm ecosystem
|
|
</InfoCard>
|
|
<InfoCard icon="⚡" title="Execute">
|
|
Run any tool in a secure sandbox - no installation needed
|
|
</InfoCard>
|
|
<InfoCard icon="📦" title="Publish">
|
|
Share your tools with the AI community via npm
|
|
</InfoCard>
|
|
</div>
|
|
<p className="text-foreground-secondary">
|
|
TPMJS works with{' '}
|
|
<a
|
|
href="https://sdk.vercel.ai"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-primary hover:underline"
|
|
>
|
|
Vercel AI SDK
|
|
</a>
|
|
, LangChain, LlamaIndex, and any framework that supports the AI SDK tool format.
|
|
</p>
|
|
</DocSection>
|
|
|
|
<DocSection id="quickstart" title="Quick Start">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Get up and running with TPMJS in under 2 minutes.
|
|
</p>
|
|
<DocSubSection title="1. Install the SDK packages">
|
|
<CodeBlock
|
|
language="bash"
|
|
code="npm install @tpmjs/registry-search @tpmjs/registry-execute"
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="2. Add to your agent">
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`import { streamText } from 'ai';
|
|
import { anthropic } from '@ai-sdk/anthropic';
|
|
import { registrySearchTool } from '@tpmjs/registry-search';
|
|
import { registryExecuteTool } from '@tpmjs/registry-execute';
|
|
|
|
const result = await streamText({
|
|
model: anthropic('claude-sonnet-4-20250514'),
|
|
tools: {
|
|
registrySearch: registrySearchTool,
|
|
registryExecute: registryExecuteTool,
|
|
},
|
|
system: \`You have access to thousands of tools via the TPMJS registry.
|
|
Use registrySearch to find tools, then registryExecute to run them.\`,
|
|
prompt: 'Search for web scraping tools and scrape example.com',
|
|
});`}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="3. That's it!">
|
|
<p className="text-foreground-secondary">
|
|
Your agent can now discover and execute any tool from the TPMJS registry. The
|
|
agent will automatically search for relevant tools and execute them as needed.
|
|
</p>
|
|
</DocSubSection>
|
|
</DocSection>
|
|
|
|
<DocSection id="core-concepts" title="Core Concepts">
|
|
<div className="space-y-6">
|
|
<DocSubSection title="Tool Discovery">
|
|
<p className="text-foreground-secondary mb-4">
|
|
TPMJS automatically discovers tools from npm packages that have the{' '}
|
|
<code className="text-primary bg-surface px-1.5 py-0.5 rounded">
|
|
tpmjs-tool
|
|
</code>{' '}
|
|
keyword. Tools are indexed every 2-15 minutes.
|
|
</p>
|
|
</DocSubSection>
|
|
<DocSubSection title="Sandboxed Execution">
|
|
<p className="text-foreground-secondary mb-4">
|
|
All tools run in an isolated Deno runtime on Railway. They cannot access your
|
|
local filesystem or environment. API keys are passed per-request and never
|
|
stored.
|
|
</p>
|
|
</DocSubSection>
|
|
<DocSubSection title="Quality Scoring">
|
|
<p className="text-foreground-secondary mb-4">
|
|
Every tool receives a quality score (0.00-1.00) based on metadata completeness,
|
|
npm downloads, and GitHub stars. Higher scores mean better visibility in search
|
|
results.
|
|
</p>
|
|
</DocSubSection>
|
|
<DocSubSection title="Health Monitoring">
|
|
<p className="text-foreground-secondary">
|
|
Tools are continuously health-checked to ensure they can be imported and
|
|
executed. Broken tools are flagged and can be filtered from search results.
|
|
</p>
|
|
</DocSubSection>
|
|
</div>
|
|
</DocSection>
|
|
|
|
{/* ==================== SDK REFERENCE ==================== */}
|
|
<DocSection id="installation" title="Installation">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Install the TPMJS SDK packages to give your AI agent access to the tool registry.
|
|
</p>
|
|
<div className="space-y-4">
|
|
<CodeBlock
|
|
language="bash"
|
|
code="npm install @tpmjs/registry-search @tpmjs/registry-execute"
|
|
/>
|
|
<CodeBlock
|
|
language="bash"
|
|
code="pnpm add @tpmjs/registry-search @tpmjs/registry-execute"
|
|
/>
|
|
<CodeBlock
|
|
language="bash"
|
|
code="yarn add @tpmjs/registry-search @tpmjs/registry-execute"
|
|
/>
|
|
</div>
|
|
<div className="mt-6 p-4 border border-border rounded-lg bg-surface">
|
|
<p className="text-sm text-foreground-secondary">
|
|
<strong className="text-foreground">Peer Dependencies:</strong> Both packages
|
|
require <code className="text-primary">ai</code> and{' '}
|
|
<code className="text-primary">zod</code> as peer dependencies. Make sure you have
|
|
them installed.
|
|
</p>
|
|
</div>
|
|
</DocSection>
|
|
|
|
<DocSection id="registry-search" title="registrySearchTool">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Search the TPMJS registry to find tools for any task. Returns metadata including the{' '}
|
|
<code className="text-primary">toolId</code> needed for execution.
|
|
</p>
|
|
<DocSubSection title="Import">
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`import { registrySearchTool } from '@tpmjs/registry-search';`}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="Parameters">
|
|
<ParamTable
|
|
params={[
|
|
{
|
|
name: 'query',
|
|
type: 'string',
|
|
required: true,
|
|
description: 'Search query (keywords, tool names, descriptions)',
|
|
},
|
|
{
|
|
name: 'category',
|
|
type: 'string',
|
|
required: false,
|
|
description: 'Filter by category',
|
|
},
|
|
{
|
|
name: 'limit',
|
|
type: 'number',
|
|
required: false,
|
|
description: 'Max results (1-20, default 5)',
|
|
},
|
|
]}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="Return Value">
|
|
<CodeBlock
|
|
language="json"
|
|
code={`{
|
|
"query": "web scraping",
|
|
"matchCount": 3,
|
|
"tools": [
|
|
{
|
|
"toolId": "@firecrawl/ai-sdk::scrapeTool",
|
|
"name": "scrapeTool",
|
|
"package": "@firecrawl/ai-sdk",
|
|
"description": "Scrape any website into clean markdown",
|
|
"category": "web-scraping",
|
|
"requiredEnvVars": ["FIRECRAWL_API_KEY"],
|
|
"healthStatus": "HEALTHY",
|
|
"qualityScore": 0.9
|
|
}
|
|
]
|
|
}`}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="Available Categories">
|
|
<div className="flex flex-wrap gap-2">
|
|
{TPMJS_CATEGORIES.map((cat) => (
|
|
<Badge key={cat} variant="secondary" size="sm">
|
|
{cat}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
</DocSubSection>
|
|
</DocSection>
|
|
|
|
<DocSection id="registry-execute" title="registryExecuteTool">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Execute any tool from the registry by its{' '}
|
|
<code className="text-primary">toolId</code>. Tools run in a secure sandbox—no local
|
|
installation required.
|
|
</p>
|
|
<DocSubSection title="Import">
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`import { registryExecuteTool } from '@tpmjs/registry-execute';`}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="Parameters">
|
|
<ParamTable
|
|
params={[
|
|
{
|
|
name: 'toolId',
|
|
type: 'string',
|
|
required: true,
|
|
description: 'Tool identifier (format: package::exportName)',
|
|
},
|
|
{
|
|
name: 'params',
|
|
type: 'object',
|
|
required: true,
|
|
description: 'Parameters to pass to the tool',
|
|
},
|
|
{
|
|
name: 'env',
|
|
type: 'object',
|
|
required: false,
|
|
description: 'Environment variables (API keys)',
|
|
},
|
|
]}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="Example">
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`// Execute a web search tool
|
|
const result = await registryExecuteTool.execute({
|
|
toolId: '@exalabs/ai-sdk::webSearch',
|
|
params: { query: 'latest AI news' },
|
|
env: { EXA_API_KEY: 'your-api-key' },
|
|
});
|
|
|
|
// Result:
|
|
// {
|
|
// toolId: '@exalabs/ai-sdk::webSearch',
|
|
// executionTimeMs: 1234,
|
|
// output: { results: [...] }
|
|
// }`}
|
|
/>
|
|
</DocSubSection>
|
|
</DocSection>
|
|
|
|
<DocSection id="passing-api-keys" title="Passing API Keys">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Many tools require API keys (e.g., Firecrawl, Exa). The recommended approach is to
|
|
wrap <code className="text-primary">registryExecuteTool</code> with your
|
|
pre-configured keys.
|
|
</p>
|
|
<DocSubSection title="Create a Wrapper">
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`import { tool } from 'ai';
|
|
import { registryExecuteTool } from '@tpmjs/registry-execute';
|
|
|
|
// Pre-configure your API keys
|
|
const API_KEYS: Record<string, string> = {
|
|
FIRECRAWL_API_KEY: process.env.FIRECRAWL_API_KEY!,
|
|
EXA_API_KEY: process.env.EXA_API_KEY!,
|
|
};
|
|
|
|
// Create a wrapped version that auto-injects keys
|
|
export const registryExecute = tool({
|
|
description: registryExecuteTool.description,
|
|
parameters: registryExecuteTool.parameters,
|
|
execute: async ({ toolId, params }) => {
|
|
return registryExecuteTool.execute({ toolId, params, env: API_KEYS });
|
|
},
|
|
});`}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="Use the Wrapped Tool">
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`import { streamText } from 'ai';
|
|
import { registrySearchTool } from '@tpmjs/registry-search';
|
|
import { registryExecute } from './tools'; // Your wrapped version
|
|
|
|
const result = streamText({
|
|
model: anthropic('claude-sonnet-4-20250514'),
|
|
tools: {
|
|
registrySearch: registrySearchTool,
|
|
registryExecute, // Keys are auto-injected
|
|
},
|
|
prompt: 'Scrape https://example.com and summarize the content',
|
|
});`}
|
|
/>
|
|
</DocSubSection>
|
|
</DocSection>
|
|
|
|
{/* ==================== API REFERENCE ==================== */}
|
|
<DocSection id="api-overview" title="API Overview">
|
|
<p className="text-foreground-secondary mb-6">
|
|
The TPMJS API is a REST API that provides access to the tool registry. All endpoints
|
|
return JSON and are publicly accessible without authentication.
|
|
</p>
|
|
<div className="p-4 border border-border rounded-lg bg-surface">
|
|
<p className="text-sm text-foreground-secondary">
|
|
<strong className="text-foreground">Base URL:</strong>{' '}
|
|
<code className="text-primary">https://tpmjs.com/api</code>
|
|
</p>
|
|
</div>
|
|
</DocSection>
|
|
|
|
<DocSection id="api-tools" title="GET /api/tools">
|
|
<p className="text-foreground-secondary mb-6">
|
|
List all tools with optional filtering and pagination.
|
|
</p>
|
|
<DocSubSection title="Query Parameters">
|
|
<ParamTable
|
|
params={[
|
|
{
|
|
name: 'q',
|
|
type: 'string',
|
|
required: false,
|
|
description: 'Search query',
|
|
},
|
|
{
|
|
name: 'category',
|
|
type: 'string',
|
|
required: false,
|
|
description: 'Filter by category',
|
|
},
|
|
{
|
|
name: 'official',
|
|
type: 'boolean',
|
|
required: false,
|
|
description: 'Filter to official tools only',
|
|
},
|
|
{
|
|
name: 'limit',
|
|
type: 'number',
|
|
required: false,
|
|
description: 'Max results (default 20, max 50)',
|
|
},
|
|
{
|
|
name: 'offset',
|
|
type: 'number',
|
|
required: false,
|
|
description: 'Pagination offset',
|
|
},
|
|
]}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="Example Request">
|
|
<CodeBlock
|
|
language="bash"
|
|
code="curl https://tpmjs.com/api/tools?q=web+scraping&limit=5"
|
|
/>
|
|
</DocSubSection>
|
|
</DocSection>
|
|
|
|
<DocSection id="api-tools-search" title="GET /api/tools/search">
|
|
<p className="text-foreground-secondary mb-6">
|
|
BM25-ranked search optimized for AI agent tool discovery.
|
|
</p>
|
|
<DocSubSection title="Query Parameters">
|
|
<ParamTable
|
|
params={[
|
|
{
|
|
name: 'q',
|
|
type: 'string',
|
|
required: true,
|
|
description: 'Search query',
|
|
},
|
|
{
|
|
name: 'limit',
|
|
type: 'number',
|
|
required: false,
|
|
description: 'Max results (default 5, max 20)',
|
|
},
|
|
]}
|
|
/>
|
|
</DocSubSection>
|
|
</DocSection>
|
|
|
|
<DocSection id="api-tool-detail" title="GET /api/tools/[id]">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Get detailed information about a specific tool.
|
|
</p>
|
|
<DocSubSection title="Path Parameters">
|
|
<ParamTable
|
|
params={[
|
|
{
|
|
name: 'id',
|
|
type: 'string',
|
|
required: true,
|
|
description: 'Tool ID (npm package name)',
|
|
},
|
|
]}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="Example Request">
|
|
<CodeBlock language="bash" code="curl https://tpmjs.com/api/tools/@tpmjs/hello" />
|
|
</DocSubSection>
|
|
</DocSection>
|
|
|
|
{/* ==================== PUBLISHING TOOLS ==================== */}
|
|
<DocSection id="publish-overview" title="Publishing Overview">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Publishing a tool to TPMJS is as simple as publishing to npm with standardized
|
|
metadata.
|
|
</p>
|
|
<div className="grid md:grid-cols-4 gap-4 mb-8">
|
|
{[
|
|
{ step: '1', label: 'Add tpmjs-tool keyword' },
|
|
{ step: '2', label: 'Add tpmjs field' },
|
|
{ step: '3', label: 'Publish to npm' },
|
|
{ step: '4', label: 'Live in 15 minutes!' },
|
|
].map((item) => (
|
|
<div
|
|
key={item.step}
|
|
className="p-4 border border-border rounded-lg bg-surface text-center"
|
|
>
|
|
<div className="text-2xl font-bold text-primary mb-2">{item.step}</div>
|
|
<p className="text-sm text-foreground-secondary">{item.label}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<Link href="/publish">
|
|
<Button variant="default">View Publishing Guide</Button>
|
|
</Link>
|
|
<a
|
|
href="https://github.com/tpmjs/tpmjs/tree/main/packages/tools/create-basic-tools"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<Button variant="outline">Use Generator CLI</Button>
|
|
</a>
|
|
</div>
|
|
</DocSection>
|
|
|
|
<DocSection id="tpmjs-spec" title="TPMJS Specification">
|
|
<p className="text-foreground-secondary mb-6">
|
|
The <code className="text-primary">tpmjs</code> field in package.json describes your
|
|
tool's capabilities.
|
|
</p>
|
|
<CodeBlock
|
|
language="json"
|
|
code={`{
|
|
"name": "@yourname/my-tool",
|
|
"version": "1.0.0",
|
|
"keywords": ["tpmjs-tool"],
|
|
"tpmjs": {
|
|
"category": "text-analysis",
|
|
"frameworks": ["vercel-ai"],
|
|
"tools": [
|
|
{
|
|
"exportName": "myTool",
|
|
"description": "What your tool does (20-500 chars)",
|
|
"parameters": [
|
|
{
|
|
"name": "input",
|
|
"type": "string",
|
|
"description": "The input to process",
|
|
"required": true
|
|
}
|
|
],
|
|
"returns": {
|
|
"type": "string",
|
|
"description": "The processed output"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}`}
|
|
/>
|
|
<div className="mt-6">
|
|
<Link href="/spec">
|
|
<Button variant="outline">View Full Specification</Button>
|
|
</Link>
|
|
</div>
|
|
</DocSection>
|
|
|
|
<DocSection id="metadata-tiers" title="Metadata Tiers">
|
|
<p className="text-foreground-secondary mb-6">
|
|
There are three tiers of metadata. Higher tiers get better visibility and quality
|
|
scores.
|
|
</p>
|
|
<div className="space-y-4">
|
|
<div className="p-4 border border-border rounded-lg bg-surface">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<Badge variant="outline">Tier 1: Minimal</Badge>
|
|
<span className="text-sm text-foreground-tertiary">1x multiplier</span>
|
|
</div>
|
|
<p className="text-sm text-foreground-secondary">
|
|
Required fields only: <code className="text-primary">category</code>,{' '}
|
|
<code className="text-primary">description</code>,{' '}
|
|
<code className="text-primary">exportName</code>
|
|
</p>
|
|
</div>
|
|
<div className="p-4 border border-border rounded-lg bg-surface">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<Badge variant="default">Tier 2: Basic</Badge>
|
|
<span className="text-sm text-foreground-tertiary">2x multiplier</span>
|
|
</div>
|
|
<p className="text-sm text-foreground-secondary">
|
|
+ <code className="text-primary">parameters</code> and{' '}
|
|
<code className="text-primary">returns</code> documentation
|
|
</p>
|
|
</div>
|
|
<div className="p-4 border border-border rounded-lg bg-surface">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<Badge variant="success">Tier 3: Rich</Badge>
|
|
<span className="text-sm text-foreground-tertiary">4x multiplier</span>
|
|
</div>
|
|
<p className="text-sm text-foreground-secondary">
|
|
+ <code className="text-primary">env</code>,{' '}
|
|
<code className="text-primary">frameworks</code>,{' '}
|
|
<code className="text-primary">aiAgent</code> (useCase, limitations, examples)
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</DocSection>
|
|
|
|
<DocSection id="quality-score" title="Quality Score">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Every tool receives a quality score (0.00-1.00) that affects search ranking.
|
|
</p>
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`function calculateQualityScore(params: {
|
|
tier: 'minimal' | 'basic' | 'rich';
|
|
downloads: number;
|
|
githubStars: number;
|
|
}): number {
|
|
const tierScore = tier === 'rich' ? 0.6 : tier === 'basic' ? 0.4 : 0.2;
|
|
const downloadsScore = Math.min(0.3, Math.log10(downloads + 1) / 10);
|
|
const starsScore = Math.min(0.1, Math.log10(githubStars + 1) / 10);
|
|
|
|
return Math.min(1.0, tierScore + downloadsScore + starsScore);
|
|
}`}
|
|
/>
|
|
</DocSection>
|
|
|
|
{/* ==================== ADVANCED ==================== */}
|
|
<DocSection id="override-execute" title="Override Execute">
|
|
<p className="text-foreground-secondary mb-6">
|
|
When you import a tool from npm, you can override its{' '}
|
|
<code className="text-primary">execute</code> function before passing it to your AI
|
|
agent.
|
|
</p>
|
|
<DocSubSection title="Simple Override">
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`import { someTool } from '@tpmjs/some-tool';
|
|
|
|
const myTool = {
|
|
...someTool,
|
|
execute: async (args, options) => {
|
|
console.log('Custom execution with args:', args);
|
|
// Your completely custom implementation
|
|
return { result: 'my custom result' };
|
|
},
|
|
};`}
|
|
/>
|
|
</DocSubSection>
|
|
<DocSubSection title="Wrap with Logging">
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`const wrappedTool = {
|
|
...someTool,
|
|
execute: async (args, options) => {
|
|
console.log(\`[\${new Date().toISOString()}] Calling tool with:\`, args);
|
|
const start = Date.now();
|
|
const result = await someTool.execute(args, options);
|
|
console.log(\`[\${Date.now() - start}ms] Tool returned:\`, result);
|
|
return result;
|
|
},
|
|
};`}
|
|
/>
|
|
</DocSubSection>
|
|
</DocSection>
|
|
|
|
<DocSection id="custom-wrappers" title="Custom Wrappers">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Create reusable wrapper functions for common patterns like caching, retries, and
|
|
rate limiting.
|
|
</p>
|
|
<DocSubSection title="Wrapper Factory">
|
|
<CodeBlock
|
|
language="typescript"
|
|
code={`function wrapTool<T, R>(
|
|
tool: { description: string; parameters: any; execute: (args: T, opts: any) => Promise<R> },
|
|
options: {
|
|
before?: (args: T) => T | Promise<T>;
|
|
after?: (result: R) => R | Promise<R>;
|
|
timeout?: number;
|
|
retries?: number;
|
|
} = {}
|
|
) {
|
|
return {
|
|
...tool,
|
|
execute: async (args: T, execOptions: any): Promise<R> => {
|
|
let processedArgs = options.before ? await options.before(args) : args;
|
|
|
|
let lastError: Error | undefined;
|
|
const maxAttempts = (options.retries ?? 0) + 1;
|
|
|
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
try {
|
|
let result = await tool.execute(processedArgs, execOptions);
|
|
if (options.after) result = await options.after(result);
|
|
return result;
|
|
} catch (error) {
|
|
lastError = error as Error;
|
|
if (attempt < maxAttempts) {
|
|
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 100));
|
|
}
|
|
}
|
|
}
|
|
throw lastError;
|
|
},
|
|
};
|
|
}`}
|
|
/>
|
|
</DocSubSection>
|
|
</DocSection>
|
|
|
|
<DocSection id="self-hosting" title="Self-Hosting">
|
|
<p className="text-foreground-secondary mb-6">
|
|
Both SDK packages support self-hosted registries via environment variables.
|
|
</p>
|
|
<ParamTable
|
|
params={[
|
|
{
|
|
name: 'TPMJS_API_URL',
|
|
type: 'string',
|
|
required: false,
|
|
description: 'Base URL for registry API (default: https://tpmjs.com)',
|
|
},
|
|
{
|
|
name: 'TPMJS_EXECUTOR_URL',
|
|
type: 'string',
|
|
required: false,
|
|
description: 'URL for sandbox executor (default: https://executor.tpmjs.com)',
|
|
},
|
|
]}
|
|
/>
|
|
<div className="mt-4">
|
|
<CodeBlock
|
|
language="bash"
|
|
code={`# Use your own TPMJS registry
|
|
export TPMJS_API_URL=https://registry.mycompany.com
|
|
export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
|
|
/>
|
|
</div>
|
|
</DocSection>
|
|
|
|
<DocSection id="security" title="Security">
|
|
<p className="text-foreground-secondary mb-6">
|
|
TPMJS is designed with security in mind.
|
|
</p>
|
|
<div className="grid md:grid-cols-2 gap-4">
|
|
<InfoCard icon="🏝️" title="Sandboxed Execution">
|
|
All tools run in an isolated Deno runtime. They cannot access your local
|
|
filesystem or environment.
|
|
</InfoCard>
|
|
<InfoCard icon="🔐" title="API Key Isolation">
|
|
API keys are passed per-request and never stored. Each execution is stateless and
|
|
isolated.
|
|
</InfoCard>
|
|
<InfoCard icon="✅" title="Registry-Only Execution">
|
|
Only tools registered in TPMJS can be executed. No arbitrary code execution is
|
|
possible.
|
|
</InfoCard>
|
|
<InfoCard icon="🏥" title="Health Monitoring">
|
|
Every tool is continuously health-checked. Broken tools are flagged and filtered
|
|
from search results.
|
|
</InfoCard>
|
|
</div>
|
|
</DocSection>
|
|
|
|
{/* ==================== RESOURCES ==================== */}
|
|
<DocSection id="faq" title="FAQ">
|
|
<div className="space-y-6">
|
|
{[
|
|
{
|
|
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.',
|
|
},
|
|
{
|
|
q: 'Is TPMJS free to use?',
|
|
a: 'Yes! TPMJS is free for public tools. We may introduce paid tiers for private registries and enterprise features in the future.',
|
|
},
|
|
{
|
|
q: 'Can I use TPMJS with any AI framework?',
|
|
a: 'TPMJS works with any framework that supports the AI SDK tool format, including Vercel AI SDK, LangChain, and LlamaIndex.',
|
|
},
|
|
{
|
|
q: 'How are tools executed?',
|
|
a: 'Tools are dynamically loaded from esm.sh and executed in a sandboxed Deno runtime on Railway. No local installation is required.',
|
|
},
|
|
{
|
|
q: 'Can I run my own TPMJS registry?',
|
|
a: 'Yes! Set the TPMJS_API_URL and TPMJS_EXECUTOR_URL environment variables to point to your own infrastructure.',
|
|
},
|
|
].map((item) => (
|
|
<div key={item.q} className="p-4 border border-border rounded-lg bg-surface">
|
|
<h4 className="font-semibold text-foreground mb-2">{item.q}</h4>
|
|
<p className="text-sm text-foreground-secondary">{item.a}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</DocSection>
|
|
|
|
<DocSection id="troubleshooting" title="Troubleshooting">
|
|
<div className="space-y-6">
|
|
<DocSubSection title="Tool not appearing in registry">
|
|
<ul className="list-disc list-inside space-y-2 text-foreground-secondary">
|
|
<li>
|
|
Ensure you have{' '}
|
|
<code className="text-primary bg-surface px-1 rounded">tpmjs-tool</code> in
|
|
your keywords
|
|
</li>
|
|
<li>
|
|
Verify your <code className="text-primary">tpmjs</code> field is valid JSON
|
|
</li>
|
|
<li>Wait 15 minutes after publishing</li>
|
|
<li>Check the validation errors in the npm package page</li>
|
|
</ul>
|
|
</DocSubSection>
|
|
<DocSubSection title="Execution failing">
|
|
<ul className="list-disc list-inside space-y-2 text-foreground-secondary">
|
|
<li>Check that required environment variables are passed</li>
|
|
<li>Verify the toolId format is correct (package::exportName)</li>
|
|
<li>Check the tool's health status on tpmjs.com</li>
|
|
</ul>
|
|
</DocSubSection>
|
|
</div>
|
|
</DocSection>
|
|
|
|
<DocSection id="changelog" title="Changelog">
|
|
<div className="space-y-4">
|
|
<div className="p-4 border border-border rounded-lg bg-surface">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<Badge variant="default">v1.0.0</Badge>
|
|
<span className="text-sm text-foreground-tertiary">December 2024</span>
|
|
</div>
|
|
<p className="text-sm text-foreground-secondary">
|
|
Initial release with registrySearchTool and registryExecuteTool
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</DocSection>
|
|
|
|
{/* CTA */}
|
|
<section className="text-center py-12 border border-border rounded-lg bg-surface">
|
|
<h2 className="text-2xl font-bold mb-4 text-foreground">Ready to Get Started?</h2>
|
|
<p className="text-foreground-secondary mb-6 max-w-xl mx-auto">
|
|
Give your AI agent access to thousands of tools in minutes.
|
|
</p>
|
|
<div className="flex flex-wrap gap-4 justify-center">
|
|
<a href="https://playground.tpmjs.com" target="_blank" rel="noopener noreferrer">
|
|
<Button variant="default" size="lg">
|
|
Try Playground
|
|
</Button>
|
|
</a>
|
|
<Link href="/tool/tool-search">
|
|
<Button variant="outline" size="lg">
|
|
Browse Tools
|
|
</Button>
|
|
</Link>
|
|
<Link href="/publish">
|
|
<Button variant="outline" size="lg">
|
|
Publish a Tool
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|