diff --git a/apps/tutorial/src/app/agents/page.tsx b/apps/tutorial/src/app/agents/page.tsx new file mode 100644 index 0000000..7fa09c8 --- /dev/null +++ b/apps/tutorial/src/app/agents/page.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { AgentSlideshow } from '@/components/AgentSlideshow'; + +export default function AgentsTutorial(): React.ReactElement { + return ( +
+ +
+ ); +} diff --git a/apps/tutorial/src/app/authors/page.tsx b/apps/tutorial/src/app/authors/page.tsx new file mode 100644 index 0000000..8df46c3 --- /dev/null +++ b/apps/tutorial/src/app/authors/page.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { AuthorSlideshow } from '@/components/AuthorSlideshow'; + +export default function AuthorsTutorial(): React.ReactElement { + return ( +
+ +
+ ); +} diff --git a/apps/tutorial/src/app/page.tsx b/apps/tutorial/src/app/page.tsx index 4792ff8..a08af14 100644 --- a/apps/tutorial/src/app/page.tsx +++ b/apps/tutorial/src/app/page.tsx @@ -1,11 +1,182 @@ 'use client'; -import { Slideshow } from '@/components/Slideshow'; +import { motion } from 'framer-motion'; +import Link from 'next/link'; -export default function TutorialPage(): React.ReactElement { +const tutorials = [ + { + id: 'agents', + title: 'For Agent Developers', + subtitle: 'Search & Execute', + description: 'Give your agent access to 200+ tools at runtime. No imports. No bundling.', + href: '/agents', + gradient: 'from-cyan-500 to-blue-600', + icon: ( + + ), + features: ['searchTpmjsToolsTool', 'registryExecuteTool', 'Sandbox execution'], + }, + { + id: 'authors', + title: 'For Package Authors', + subtitle: 'Publish Your Tools', + description: + 'Get your AI SDK tools discovered by agents worldwide. npm publish is all it takes.', + href: '/authors', + gradient: 'from-purple-500 to-pink-600', + icon: ( + + ), + features: ['tpmjs-tool keyword', 'Schema extraction', 'Quality scoring'], + }, +]; + +export default function TutorialHome(): React.ReactElement { return ( -
- +
+ {/* Background */} +
+
+ + {/* Content */} +
+ {/* Header */} + +
+ tpmjs.com + / + tutorials +
+

Learn TPMJS

+

+ The tool registry for the Vercel AI SDK. Choose your path. +

+
+ + {/* Tutorial Cards */} +
+ {tutorials.map((tutorial, index) => ( + + +
+ {/* Gradient glow on hover */} +
+ + {/* Icon */} +
+ {tutorial.icon} +
+ + {/* Content */} +
+
{tutorial.subtitle}
+

{tutorial.title}

+

{tutorial.description}

+ + {/* Features */} +
+ {tutorial.features.map((feature) => ( + + {feature} + + ))} +
+ + {/* CTA */} +
+ Start Tutorial + +
+
+
+ + + ))} +
+ + {/* Footer */} + + + Back to tpmjs.com + + +
); } diff --git a/apps/tutorial/src/components/Slideshow.tsx b/apps/tutorial/src/components/AgentSlideshow.tsx similarity index 61% rename from apps/tutorial/src/components/Slideshow.tsx rename to apps/tutorial/src/components/AgentSlideshow.tsx index 99691a2..dbdcf35 100644 --- a/apps/tutorial/src/components/Slideshow.tsx +++ b/apps/tutorial/src/components/AgentSlideshow.tsx @@ -2,6 +2,7 @@ import { useSlideshow } from '@/hooks/useSlideshow'; import { AnimatePresence } from 'framer-motion'; +import Link from 'next/link'; import { Slide } from './Slide'; import { SlideNavigation } from './SlideNavigation'; import { SlideProgress } from './SlideProgress'; @@ -16,18 +17,18 @@ import { ToolDetailSlide } from './slides/ToolDetailSlide'; import { WelcomeSlide } from './slides/WelcomeSlide'; const SLIDES = [ - WelcomeSlide, - ProblemSlide, - SolutionSlide, - HowItWorksSlide, - DiscoverySlide, - IntegrationSlide, - QualitySlide, - ToolDetailSlide, - GetStartedSlide, + { id: 'welcome', Component: WelcomeSlide }, + { id: 'problem', Component: ProblemSlide }, + { id: 'solution', Component: SolutionSlide }, + { id: 'how-it-works', Component: HowItWorksSlide }, + { id: 'discovery', Component: DiscoverySlide }, + { id: 'integration', Component: IntegrationSlide }, + { id: 'quality', Component: QualitySlide }, + { id: 'tool-detail', Component: ToolDetailSlide }, + { id: 'get-started', Component: GetStartedSlide }, ]; -export function Slideshow(): React.ReactElement { +export function AgentSlideshow(): React.ReactElement { const { currentSlide, totalSlides, next, prev, goTo, direction, isFirst, isLast } = useSlideshow({ totalSlides: SLIDES.length, }); @@ -49,11 +50,28 @@ export function Slideshow(): React.ReactElement { }} /> + {/* Back button */} + + + Tutorials + + {/* Slides */} - {SLIDES.map((SlideContent, index) => ( - - + {SLIDES.map((slide, index) => ( + + ))} diff --git a/apps/tutorial/src/components/AuthorSlideshow.tsx b/apps/tutorial/src/components/AuthorSlideshow.tsx new file mode 100644 index 0000000..2890cd5 --- /dev/null +++ b/apps/tutorial/src/components/AuthorSlideshow.tsx @@ -0,0 +1,91 @@ +'use client'; + +import { useSlideshow } from '@/hooks/useSlideshow'; +import { AnimatePresence } from 'framer-motion'; +import Link from 'next/link'; +import { Slide } from './Slide'; +import { SlideNavigation } from './SlideNavigation'; +import { SlideProgress } from './SlideProgress'; +import { AuthorNextStepsSlide } from './author-slides/AuthorNextStepsSlide'; +import { AuthorWelcomeSlide } from './author-slides/AuthorWelcomeSlide'; +import { GetDiscoveredSlide } from './author-slides/GetDiscoveredSlide'; +import { HealthChecksSlide } from './author-slides/HealthChecksSlide'; +import { HowSyncWorksSlide } from './author-slides/HowSyncWorksSlide'; +import { PackageSetupSlide } from './author-slides/PackageSetupSlide'; +import { QualityScoringSlide } from './author-slides/QualityScoringSlide'; +import { ToolDefinitionSlide } from './author-slides/ToolDefinitionSlide'; +import { WhatIsRegistrySlide } from './author-slides/WhatIsRegistrySlide'; + +const SLIDES = [ + { id: 'welcome', Component: AuthorWelcomeSlide }, + { id: 'what-is', Component: WhatIsRegistrySlide }, + { id: 'sync', Component: HowSyncWorksSlide }, + { id: 'setup', Component: PackageSetupSlide }, + { id: 'definition', Component: ToolDefinitionSlide }, + { id: 'quality', Component: QualityScoringSlide }, + { id: 'health', Component: HealthChecksSlide }, + { id: 'discovered', Component: GetDiscoveredSlide }, + { id: 'next-steps', Component: AuthorNextStepsSlide }, +]; + +export function AuthorSlideshow(): React.ReactElement { + const { currentSlide, totalSlides, next, prev, goTo, direction, isFirst, isLast } = useSlideshow({ + totalSlides: SLIDES.length, + }); + + return ( +
+ {/* Background gradient */} +
+ + {/* Subtle grid pattern */} +
+ + {/* Back button */} + + + Tutorials + + + {/* Slides */} + + {SLIDES.map((slide, index) => ( + + + + ))} + + + {/* Navigation */} + + + {/* Progress */} + + + {/* Keyboard hint */} +
+ Use arrow keys or click to navigate +
+
+ ); +} diff --git a/apps/tutorial/src/components/author-slides/AuthorNextStepsSlide.tsx b/apps/tutorial/src/components/author-slides/AuthorNextStepsSlide.tsx new file mode 100644 index 0000000..20051ed --- /dev/null +++ b/apps/tutorial/src/components/author-slides/AuthorNextStepsSlide.tsx @@ -0,0 +1,82 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const steps = [ + { num: '1', text: 'Add "tpmjs-tool" to your package.json keywords' }, + { num: '2', text: 'Define your tools in the "tpmjs" field' }, + { num: '3', text: 'npm publish' }, +]; + +export function AuthorNextStepsSlide(): React.ReactElement { + return ( +
+
+
+
+ + +

Ready to Publish?

+

Three steps. That's it.

+ +
+ {steps.map((step, i) => ( + + + {step.num} + + {step.text} + + ))} +
+ + + + Browse Existing Tools + + + Try the Playground + + + + + Indexed within 2 minutes. No registration required. + +
+
+ ); +} diff --git a/apps/tutorial/src/components/author-slides/AuthorWelcomeSlide.tsx b/apps/tutorial/src/components/author-slides/AuthorWelcomeSlide.tsx new file mode 100644 index 0000000..99b6e64 --- /dev/null +++ b/apps/tutorial/src/components/author-slides/AuthorWelcomeSlide.tsx @@ -0,0 +1,58 @@ +'use client'; + +import { motion } from 'framer-motion'; + +export function AuthorWelcomeSlide(): React.ReactElement { + return ( +
+
+ + + + + For Package Authors + + + + + Publish Your Tools + + + + Get your AI SDK tools discovered by agents worldwide. +
+ npm publish is all it takes. +
+ + + Press arrow keys or click to continue + +
+
+ ); +} diff --git a/apps/tutorial/src/components/author-slides/GetDiscoveredSlide.tsx b/apps/tutorial/src/components/author-slides/GetDiscoveredSlide.tsx new file mode 100644 index 0000000..539afa1 --- /dev/null +++ b/apps/tutorial/src/components/author-slides/GetDiscoveredSlide.tsx @@ -0,0 +1,72 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const searchExample = [ + { id: 'agent', line: 'Agent: "I need to format some markdown"', color: 'white' }, + { id: 'search', line: 'searchTpmjsToolsTool({ query: "markdown format" })', color: 'cyan' }, + { id: 'result', line: '→ Found: @your-org/tools::formatMarkdownTable', color: 'emerald' }, + { id: 'execute', line: 'registryExecuteTool({ toolId: "...", params })', color: 'purple' }, + { id: 'done', line: '→ Your tool runs. Agent gets result.', color: 'yellow' }, +]; + +export function GetDiscoveredSlide(): React.ReactElement { + return ( +
+
+ + +

How Agents Find You

+

Your tool in action

+ +
+ {searchExample.map((item, i) => ( + + + {item.line} + + + ))} +
+ + +
+ The agent never installed your package. +
+ They found and ran it at runtime. +
+
+
+
+ ); +} diff --git a/apps/tutorial/src/components/author-slides/HealthChecksSlide.tsx b/apps/tutorial/src/components/author-slides/HealthChecksSlide.tsx new file mode 100644 index 0000000..9fd7771 --- /dev/null +++ b/apps/tutorial/src/components/author-slides/HealthChecksSlide.tsx @@ -0,0 +1,91 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const checks = [ + { + status: 'HEALTHY', + icon: '✓', + title: 'Import Health', + desc: 'We require() your package to verify it loads', + color: 'emerald', + }, + { + status: 'HEALTHY', + icon: '✓', + title: 'Execution Health', + desc: 'We call your tool with test params', + color: 'emerald', + }, + { + status: 'BROKEN', + icon: '!', + title: 'Labeled, Not Hidden', + desc: 'Failed tools show warning, still searchable', + color: 'yellow', + }, +]; + +export function HealthChecksSlide(): React.ReactElement { + return ( +
+
+ + +

Health Checks

+

We verify your tools actually work

+ +
+ {checks.map((check, i) => ( + +
+ {check.icon} +
+
+
+ {check.title} + + {check.status} + +
+
{check.desc}
+
+
+ ))} +
+ + + Health checks run hourly. Fix issues anytime by publishing a new version. + +
+
+ ); +} diff --git a/apps/tutorial/src/components/author-slides/HowSyncWorksSlide.tsx b/apps/tutorial/src/components/author-slides/HowSyncWorksSlide.tsx new file mode 100644 index 0000000..3fbc120 --- /dev/null +++ b/apps/tutorial/src/components/author-slides/HowSyncWorksSlide.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const syncSteps = [ + { step: '1', text: 'You publish to npm with tpmjs-tool keyword', color: 'purple' }, + { 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: '4', text: 'Your tool appears in search results', color: 'yellow' }, +]; + +export function HowSyncWorksSlide(): React.ReactElement { + return ( +
+
+ + +

Automatic Discovery

+

Publish once. We handle the rest.

+ +
+ {syncSteps.map((item, i) => ( + + + {item.step} + + {item.text} + + ))} +
+ + + npm publish +
No registration. No API keys. Just npm.
+
+
+
+ ); +} diff --git a/apps/tutorial/src/components/author-slides/PackageSetupSlide.tsx b/apps/tutorial/src/components/author-slides/PackageSetupSlide.tsx new file mode 100644 index 0000000..59a389c --- /dev/null +++ b/apps/tutorial/src/components/author-slides/PackageSetupSlide.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const packageJson = [ + { id: 'open', line: '{' }, + { id: 'name', line: ' "name": "@your-org/your-tools",' }, + { id: 'version', line: ' "version": "1.0.0",' }, + { id: 'keywords-open', line: ' "keywords": [' }, + { id: 'keyword', line: ' "tpmjs-tool"' }, + { id: 'keywords-close', line: ' ],' }, + { id: 'tpmjs-open', line: ' "tpmjs": {' }, + { id: 'tools-open', line: ' "tools": [{ ... }]' }, + { id: 'tpmjs-close', line: ' }' }, + { id: 'close', line: '}' }, +]; + +export function PackageSetupSlide(): React.ReactElement { + return ( +
+
+ + +

Package Setup

+

Two things: a keyword and a tpmjs field

+ +
+ {/* package.json */} + +
package.json
+
+ {packageJson.map((item) => ( +
+ {item.line} +
+ ))} +
+
+ + {/* Explanation */} + +
+
tpmjs-tool keyword
+
Required. This is how we find you on npm.
+
+
+
tpmjs field
+
+ Defines your tools: name, description, schemas. +
+
+
+
tools array
+
One entry per exported tool function.
+
+
+
+
+
+ ); +} diff --git a/apps/tutorial/src/components/author-slides/QualityScoringSlide.tsx b/apps/tutorial/src/components/author-slides/QualityScoringSlide.tsx new file mode 100644 index 0000000..be181ff --- /dev/null +++ b/apps/tutorial/src/components/author-slides/QualityScoringSlide.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const factors = [ + { + name: 'Schema Completeness', + weight: '40%', + desc: 'Rich parameter definitions with types and descriptions', + color: 'cyan', + }, + { + name: 'npm Downloads', + weight: '30%', + desc: 'Community adoption signals quality', + color: 'purple', + }, + { + name: 'Health Status', + weight: '20%', + desc: 'Can it import and execute successfully?', + color: 'emerald', + }, + { + name: 'Documentation', + weight: '10%', + desc: 'Good descriptions help agents choose', + color: 'yellow', + }, +]; + +export function QualityScoringSlide(): React.ReactElement { + return ( +
+
+ + +

Quality Score

+

Higher scores = better search ranking

+ +
+ {factors.map((factor, i) => ( + +
+ + {factor.name} + + {factor.weight} +
+
{factor.desc}
+
+ ))} +
+ + +
qualityScore: 0.92
+
+ Rich schemas + healthy + documented = high score +
+
+
+
+ ); +} diff --git a/apps/tutorial/src/components/author-slides/ToolDefinitionSlide.tsx b/apps/tutorial/src/components/author-slides/ToolDefinitionSlide.tsx new file mode 100644 index 0000000..491070f --- /dev/null +++ b/apps/tutorial/src/components/author-slides/ToolDefinitionSlide.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const toolDef = [ + { id: 'open', line: '{' }, + { id: 'name', line: ' "name": "formatMarkdownTable",' }, + { id: 'desc', line: ' "description": "Format CSV data as markdown table",' }, + { id: 'params-open', line: ' "parameters": [{' }, + { id: 'param-name', line: ' "name": "csvData",' }, + { id: 'param-type', line: ' "type": "string",' }, + { id: 'param-req', line: ' "required": true,' }, + { id: 'param-desc', line: ' "description": "CSV data to format"' }, + { id: 'params-close', line: ' }],' }, + { id: 'returns', line: ' "returns": { "type": "string" }' }, + { id: 'close', line: '}' }, +]; + +export function ToolDefinitionSlide(): React.ReactElement { + return ( +
+
+ + +

Tool Definition

+

+ Each tool needs a name, description, and parameter schema +

+ +
+ {/* Tool JSON */} + +
tpmjs.tools[0]
+
+ {toolDef.map((item) => ( +
+ {item.line} +
+ ))} +
+
+ + {/* Field explanations */} + +
+
name
+
+ Function export name. Must match your code. +
+
+
+
description
+
+ How agents understand what your tool does. +
+
+
+
parameters
+
+ Input schema. Each param needs name, type, required. +
+
+
+
returns
+
+ Output type. Helps agents understand responses. +
+
+
+
+ + + Schemas are converted to Zod for AI SDK compatibility. + +
+
+ ); +} diff --git a/apps/tutorial/src/components/author-slides/WhatIsRegistrySlide.tsx b/apps/tutorial/src/components/author-slides/WhatIsRegistrySlide.tsx new file mode 100644 index 0000000..cad2d4f --- /dev/null +++ b/apps/tutorial/src/components/author-slides/WhatIsRegistrySlide.tsx @@ -0,0 +1,52 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const features = [ + { icon: '🔍', title: 'Discoverable', desc: 'Agents search by natural language query' }, + { icon: '📦', title: 'npm-native', desc: 'Synced from npm every 2 minutes' }, + { icon: '⚡', title: 'Instant Execution', desc: 'Run in sandbox, no install needed' }, +]; + +export function WhatIsRegistrySlide(): React.ReactElement { + return ( +
+
+ + +

What is TPMJS?

+

A tool registry for the Vercel AI SDK

+ +
+ {features.map((feature, index) => ( + +
{feature.icon}
+
{feature.title}
+
{feature.desc}
+
+ ))} +
+ + + AI agents use TPMJS to find and run tools they don't have installed. + +
+
+ ); +}