feat: add 3 new tutorials (first-tool, agent-example, playground)

- Build Your First Tool: Step-by-step guide from npm init to published
- Real-World Agent: Complete working example with meta-tools
- Interactive Playground: How to test tools in browser

Each tutorial has 5-6 slides with code examples and animations.
Updated main page to show all 6 tutorials in grid.
This commit is contained in:
Ajax Davis 2025-12-31 01:02:21 +10:00
parent 22a13166b2
commit 979bb92aff
25 changed files with 1886 additions and 1 deletions

View file

@ -0,0 +1,71 @@
'use client';
import { motion } from 'framer-motion';
const nextSteps = [
{
title: 'Add Rich Metadata',
description: 'Include tpmjs field in package.json for better quality scores',
href: '/authors',
icon: '📋',
},
{
title: 'Test in Playground',
description: 'Try your tool at tpmjs.com/tool/your-package/yourTool',
href: '/playground',
icon: '🎮',
},
{
title: 'View on Registry',
description: 'See how agents discover your tool',
href: 'https://tpmjs.com',
icon: '🔍',
},
];
export function FirstToolNextStepsSlide(): React.ReactElement {
return (
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
<div className="absolute inset-0 bg-gradient-to-br from-orange-900/20 via-transparent to-amber-900/20 pointer-events-none" />
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="relative z-10 max-w-4xl w-full"
>
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">You're Published!</h2>
<p className="text-xl text-white/50 mb-12">Your tool is now discoverable by AI agents</p>
<div className="grid md:grid-cols-3 gap-4 max-w-3xl mx-auto">
{nextSteps.map((step, index) => (
<motion.a
key={step.title}
href={step.href}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 + index * 0.1 }}
className="p-5 rounded-xl bg-white/5 border border-orange-500/20 text-left hover:bg-white/10 hover:border-orange-500/40 transition-all group cursor-pointer"
>
<div className="text-3xl mb-3 group-hover:scale-110 transition-transform">
{step.icon}
</div>
<div className="text-sm font-semibold text-orange-400 mb-1">{step.title}</div>
<div className="text-xs text-white/50">{step.description}</div>
</motion.a>
))}
</div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8 }}
className="mt-12 p-6 rounded-xl bg-gradient-to-r from-orange-500/10 to-amber-500/10 border border-orange-500/20 max-w-md mx-auto"
>
<p className="text-white/70 text-sm mb-2">Complete code example:</p>
<code className="text-orange-400 font-mono text-sm">github.com/tpmjs/example-tool</code>
</motion.div>
</motion.div>
</div>
);
}

View file

@ -0,0 +1,87 @@
'use client';
import { motion } from 'framer-motion';
export function FirstToolWelcomeSlide(): React.ReactElement {
return (
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-orange-900/20 via-transparent to-amber-900/20 pointer-events-none" />
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: 'easeOut' }}
className="relative z-10 max-w-4xl"
>
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="mb-6"
>
<span className="text-sm font-mono text-orange-400/60 tracking-widest uppercase">
Step-by-Step Guide
</span>
</motion.div>
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="mb-8"
>
<span className="text-5xl md:text-6xl font-bold bg-gradient-to-r from-orange-400 to-amber-400 bg-clip-text text-transparent">
Build Your First Tool
</span>
</motion.div>
<motion.h1
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, delay: 0.5 }}
className="text-2xl md:text-3xl font-medium text-white/90 mb-6"
>
From npm init to published tool in 10 minutes
</motion.h1>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, delay: 0.7 }}
className="text-lg text-white/50 font-mono max-w-xl mx-auto"
>
Create an AI SDK tool that agents can discover and execute
</motion.p>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 1.2 }}
className="absolute bottom-12 flex flex-col items-center"
>
<motion.div
animate={{ y: [0, 8, 0] }}
transition={{ duration: 1.5, repeat: Number.POSITIVE_INFINITY, ease: 'easeInOut' }}
className="text-white/40"
>
<svg
aria-hidden="true"
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M19 9l-7 7-7-7"
/>
</svg>
</motion.div>
<span className="mt-2 text-sm text-white/30 font-mono">Press any key</span>
</motion.div>
</div>
);
}

View file

@ -0,0 +1,96 @@
'use client';
import { motion } from 'framer-motion';
export function PackageJsonSlide(): React.ReactElement {
return (
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
<div className="absolute inset-0 bg-gradient-to-br from-orange-900/10 via-transparent to-amber-900/10 pointer-events-none" />
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="relative z-10 max-w-4xl w-full"
>
<div className="text-orange-400/60 font-mono text-sm mb-4">Step 3</div>
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">Configure package.json</h2>
<p className="text-xl text-white/50 mb-8">Add the tpmjs-tool keyword for discovery</p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="bg-[#0d1117] rounded-xl border border-white/10 p-6 text-left max-w-2xl mx-auto"
>
<div className="text-xs text-white/40 font-mono mb-3">package.json</div>
<pre className="text-sm font-mono overflow-x-auto">
<code>
<span className="text-white">{'{'}</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">"name"</span>
<span className="text-white">: </span>
<span className="text-amber-300">"my-ai-tool"</span>
<span className="text-white">,</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">"version"</span>
<span className="text-white">: </span>
<span className="text-amber-300">"1.0.0"</span>
<span className="text-white">,</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">"keywords"</span>
<span className="text-white">: [</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-amber-300 bg-orange-500/20 px-1 rounded">"tpmjs-tool"</span>
<span className="text-white/50">{' ← Required for discovery'}</span>
{'\n'}
<span className="text-white">{' ],'}]</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">"main"</span>
<span className="text-white">: </span>
<span className="text-amber-300">"dist/index.js"</span>
<span className="text-white">,</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">"types"</span>
<span className="text-white">: </span>
<span className="text-amber-300">"dist/index.d.ts"</span>
<span className="text-white">,</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">"exports"</span>
<span className="text-white">{': {'}</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">"."</span>
<span className="text-white">: </span>
<span className="text-amber-300">"./dist/index.js"</span>
{'\n'}
<span className="text-white">{' }'}</span>
{'\n'}
<span className="text-white">{'}'}</span>
</code>
</pre>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6 }}
className="mt-8 p-4 rounded-lg bg-orange-500/10 border border-orange-500/20 max-w-md mx-auto"
>
<p className="text-orange-400 text-sm">
<span className="font-semibold">Key:</span> The{' '}
<code className="bg-orange-500/20 px-1 rounded">tpmjs-tool</code> keyword triggers
automatic indexing
</p>
</motion.div>
</motion.div>
</div>
);
}

View file

@ -0,0 +1,68 @@
'use client';
import { motion } from 'framer-motion';
export function ProjectSetupSlide(): React.ReactElement {
return (
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
<div className="absolute inset-0 bg-gradient-to-br from-orange-900/10 via-transparent to-amber-900/10 pointer-events-none" />
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="relative z-10 max-w-4xl w-full"
>
<div className="text-orange-400/60 font-mono text-sm mb-4">Step 1</div>
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">Project Setup</h2>
<p className="text-xl text-white/50 mb-10">Create a new npm package</p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="bg-[#0d1117] rounded-xl border border-white/10 p-6 text-left max-w-2xl mx-auto"
>
<div className="text-xs text-white/40 font-mono mb-3">Terminal</div>
<pre className="text-sm font-mono overflow-x-auto">
<code>
<span className="text-white/50"># Create directory</span>
{'\n'}
<span className="text-orange-400">mkdir</span>
<span className="text-white"> my-ai-tool</span>
{'\n'}
<span className="text-orange-400">cd</span>
<span className="text-white"> my-ai-tool</span>
{'\n\n'}
<span className="text-white/50"># Initialize package</span>
{'\n'}
<span className="text-orange-400">npm</span>
<span className="text-white"> init -y</span>
{'\n\n'}
<span className="text-white/50"># Install dependencies</span>
{'\n'}
<span className="text-orange-400">npm</span>
<span className="text-white"> install ai zod</span>
</code>
</pre>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6 }}
className="mt-8 flex items-center justify-center gap-4 text-white/40 text-sm"
>
<span className="px-3 py-1 rounded-full bg-orange-500/10 text-orange-400 border border-orange-500/20">
ai
</span>
<span className="text-white/20">+</span>
<span className="px-3 py-1 rounded-full bg-amber-500/10 text-amber-400 border border-amber-500/20">
zod
</span>
<span className="text-white/30 ml-2">= AI SDK tools</span>
</motion.div>
</motion.div>
</div>
);
}

View file

@ -0,0 +1,73 @@
'use client';
import { motion } from 'framer-motion';
export function PublishSlide(): React.ReactElement {
return (
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
<div className="absolute inset-0 bg-gradient-to-br from-orange-900/10 via-transparent to-amber-900/10 pointer-events-none" />
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="relative z-10 max-w-4xl w-full"
>
<div className="text-orange-400/60 font-mono text-sm mb-4">Step 4</div>
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">Publish to npm</h2>
<p className="text-xl text-white/50 mb-10">Build and publish your tool</p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="bg-[#0d1117] rounded-xl border border-white/10 p-6 text-left max-w-2xl mx-auto"
>
<div className="text-xs text-white/40 font-mono mb-3">Terminal</div>
<pre className="text-sm font-mono overflow-x-auto">
<code>
<span className="text-white/50"># Build TypeScript</span>
{'\n'}
<span className="text-orange-400">npm</span>
<span className="text-white"> run build</span>
{'\n\n'}
<span className="text-white/50"># Publish to npm</span>
{'\n'}
<span className="text-orange-400">npm</span>
<span className="text-white"> publish</span>
</code>
</pre>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
className="mt-8 grid grid-cols-3 gap-4 max-w-2xl mx-auto"
>
<div className="p-4 rounded-lg bg-white/5 border border-white/10 text-center">
<div className="text-2xl mb-2">📦</div>
<div className="text-xs text-white/50">Published to npm</div>
</div>
<div className="p-4 rounded-lg bg-white/5 border border-white/10 text-center">
<div className="text-2xl mb-2">🔄</div>
<div className="text-xs text-white/50">Synced in ~2 min</div>
</div>
<div className="p-4 rounded-lg bg-white/5 border border-white/10 text-center">
<div className="text-2xl mb-2">✨</div>
<div className="text-xs text-white/50">Live on tpmjs.com</div>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8 }}
className="mt-8 text-white/40 text-sm font-mono"
>
TPMJS syncs from npm's changes feed every 2 minutes
</motion.div>
</motion.div>
</div>
);
}

View file

@ -0,0 +1,107 @@
'use client';
import { motion } from 'framer-motion';
export function WriteToolSlide(): React.ReactElement {
return (
<div className="relative flex flex-col items-center justify-center h-full px-8 text-center">
<div className="absolute inset-0 bg-gradient-to-br from-orange-900/10 via-transparent to-amber-900/10 pointer-events-none" />
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="relative z-10 max-w-4xl w-full"
>
<div className="text-orange-400/60 font-mono text-sm mb-4">Step 2</div>
<h2 className="text-4xl md:text-5xl font-bold text-white mb-4">Write Your Tool</h2>
<p className="text-xl text-white/50 mb-8">
Define schema, description, and execute function
</p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="bg-[#0d1117] rounded-xl border border-white/10 p-6 text-left max-w-2xl mx-auto"
>
<div className="text-xs text-white/40 font-mono mb-3">src/index.ts</div>
<pre className="text-sm font-mono overflow-x-auto">
<code>
<span className="text-purple-400">import</span>
<span className="text-white">{' { tool } '}</span>
<span className="text-purple-400">from</span>
<span className="text-amber-300">{" 'ai'"}</span>
<span className="text-white">;</span>
{'\n'}
<span className="text-purple-400">import</span>
<span className="text-white">{' { z } '}</span>
<span className="text-purple-400">from</span>
<span className="text-amber-300">{" 'zod'"}</span>
<span className="text-white">;</span>
{'\n\n'}
<span className="text-purple-400">export const</span>
<span className="text-cyan-400"> greetingTool</span>
<span className="text-white"> = </span>
<span className="text-yellow-400">tool</span>
<span className="text-white">{'({'}</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">description</span>
<span className="text-white">: </span>
<span className="text-amber-300">'Generate a greeting'</span>
<span className="text-white">,</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">parameters</span>
<span className="text-white">: z.</span>
<span className="text-yellow-400">object</span>
<span className="text-white">{'({'}</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">name</span>
<span className="text-white">: z.</span>
<span className="text-yellow-400">string</span>
<span className="text-white">().</span>
<span className="text-yellow-400">describe</span>
<span className="text-white">(</span>
<span className="text-amber-300">'Name'</span>
<span className="text-white">),</span>
{'\n'}
<span className="text-white">{' }'}),</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-cyan-400">execute</span>
<span className="text-white">: </span>
<span className="text-purple-400">async</span>
<span className="text-white">{' ({ name }) => {'}</span>
{'\n'}
<span className="text-white">{' '}</span>
<span className="text-purple-400">return</span>
<span className="text-white"> </span>
<span className="text-amber-300">`Hello, </span>
<span className="text-white">{'${'}</span>
<span className="text-cyan-400">name</span>
<span className="text-white">{'}'}</span>
<span className="text-amber-300">!`</span>
<span className="text-white">;</span>
{'\n'}
<span className="text-white">{' },'}</span>
{'\n'}
<span className="text-white">{'});'}</span>
</code>
</pre>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6 }}
className="mt-6 text-white/40 text-sm font-mono"
>
The AI SDK tool() wrapper makes it compatible with generateText()
</motion.div>
</motion.div>
</div>
);
}