diff --git a/apps/tutorial/eslint.config.mjs b/apps/tutorial/eslint.config.mjs new file mode 100644 index 0000000..f2ac8a1 --- /dev/null +++ b/apps/tutorial/eslint.config.mjs @@ -0,0 +1,16 @@ +import reactConfig from '@tpmjs/eslint-config/react.js'; + +export default [ + { + ignores: [ + '.next/**', + '.turbo/**', + 'node_modules/**', + '*.config.js', + '*.config.ts', + 'next-env.d.ts', + 'eslint.config.mjs', + ], + }, + ...reactConfig, +]; diff --git a/apps/tutorial/src/components/slides/DiscoverySlide.tsx b/apps/tutorial/src/components/slides/DiscoverySlide.tsx index dd035ca..7592751 100644 --- a/apps/tutorial/src/components/slides/DiscoverySlide.tsx +++ b/apps/tutorial/src/components/slides/DiscoverySlide.tsx @@ -2,76 +2,91 @@ import { motion } from 'framer-motion'; +const searchSchema = [ + ' query: z.string(),', + ' category?: z.enum([...]),', + ' limit?: z.number().max(20),', +]; + +const responseExample = [ + { id: 'open', line: '{' }, + { id: 'tools', line: ' "tools": [{' }, + { id: 'toolId', line: ' "toolId": "@tpmjs/markdown::formatTable",' }, + { id: 'desc', line: ' "description": "Format markdown tables",' }, + { id: 'schema', line: ' "inputSchema": { ... },' }, + { id: 'score', line: ' "qualityScore": 0.92' }, + { id: 'closeArr', line: ' }]' }, + { id: 'close', line: '}' }, +]; + export function DiscoverySlide(): React.ReactElement { return (
- {/* Background pulse */} - +
- - ⚙️ - - -

Schema Extraction

-

- The hard part.{' '} - We run your code in a sandbox. +

searchTpmjsToolsTool

+

+ Agent describes what it needs. Registry returns matches.

- {/* Code block showing extraction */} - -
- {/* comment */} - {'// We extract this from your actual code:'} -
-
inputSchema: {'{'}
-
- type: "object", -
-
properties: {'{'}
-
- url: {'{'} type: "string", format:{' '} - "uri" {'}'} -
-
- timeout: {'{'} type: "number", - default: 30000 {'}'} -
-
{'}'}
-
{'}'}
-
+
+ {/* Input */} + +
inputSchema
+
+
{'z.object({'}
+ {searchSchema.map((line) => ( +
+ {line} +
+ ))} +
{'})'}
+
+
+ + {/* Output */} + +
returns
+
+ {responseExample.map((item) => ( +
+ {item.line} +
+ ))} +
+
+
- {/* Key insight */} - Not documentation.{' '} - - Ground truth from TypeScript types. - + BM25 search over 200+ tools. Ranked by relevance and quality score.
diff --git a/apps/tutorial/src/components/slides/GetStartedSlide.tsx b/apps/tutorial/src/components/slides/GetStartedSlide.tsx index 49844ca..3fcb200 100644 --- a/apps/tutorial/src/components/slides/GetStartedSlide.tsx +++ b/apps/tutorial/src/components/slides/GetStartedSlide.tsx @@ -2,46 +2,31 @@ import { motion } from 'framer-motion'; -const links = [ - { - label: 'Browse Tools', - href: 'https://tpmjs.com/tool-search', - description: 'Search by name, category, quality', - icon: '🔍', - gradient: 'from-cyan-500 to-blue-500', - }, - { - label: 'Publish a Tool', - href: 'https://tpmjs.com/docs/publishing', - description: 'Add tpmjs-tool keyword to npm', - icon: '📦', - gradient: 'from-purple-500 to-pink-500', - }, - { - label: 'The Playground', - href: 'https://tpmjs.com/playground', - description: 'Run tools in browser sandbox', - icon: '🎮', - gradient: 'from-emerald-500 to-teal-500', - }, +const packageJsonLines = [ + { id: 'open', line: '{' }, + { id: 'name', line: ' "name": "@your-org/your-tools",' }, + { id: 'keywords', line: ' "keywords": ["tpmjs-tool"],' }, + { id: 'tpmjs', line: ' "tpmjs": {' }, + { id: 'tools', line: ' "tools": [{' }, + { id: 'toolName', line: ' "name": "yourTool",' }, + { id: 'desc', line: ' "description": "Does something useful"' }, + { id: 'closeArr', line: ' }]' }, + { id: 'closeTpmjs', line: ' }' }, + { id: 'close', line: '}' }, +]; + +const steps = [ + { step: '1', text: 'Add tpmjs-tool keyword', color: 'cyan' }, + { step: '2', text: 'Define tpmjs field with tool metadata', color: 'purple' }, + { step: '3', text: 'npm publish', color: 'emerald' }, ]; export function GetStartedSlide(): React.ReactElement { return (
- {/* Celebratory background */}
-
@@ -52,83 +37,107 @@ export function GetStartedSlide(): React.ReactElement { transition={{ duration: 0.8 }} className="relative z-10 max-w-4xl w-full" > - - 🚀 - - -

Get Started

-

- Indexed automatically. Updated every 2 minutes. +

Publish Your Tool

+

+ Add keyword. Define metadata. Publish. Indexed in 2 minutes.

- {/* CTA buttons */} -
- {links.map((link, index) => ( - -
- {link.icon} -
-
{link.label}
-
{link.description}
-
-
- - {/* Hover arrow */} - -
package.json
+
+ {packageJsonLines.map((item) => ( +
- - - - - ))} + {item.line} +
+ ))} +
+
+ + {/* Steps */} + +
three steps
+
+ {steps.map((item, i) => ( + + + {item.step} + + {item.text} + + ))} +
+ + + npm publish +
That's it. We find you.
+
+
- {/* Footer */} - The missing layer between npm and AI agents. + + Browse Registry + + + Try Playground +
diff --git a/apps/tutorial/src/components/slides/HowItWorksSlide.tsx b/apps/tutorial/src/components/slides/HowItWorksSlide.tsx index d07bccd..d6bcec3 100644 --- a/apps/tutorial/src/components/slides/HowItWorksSlide.tsx +++ b/apps/tutorial/src/components/slides/HowItWorksSlide.tsx @@ -2,89 +2,73 @@ import { motion } from 'framer-motion'; -const steps = [ - { icon: '📦', label: 'npm publish', description: 'Add tpmjs-tool keyword' }, - { icon: '🔄', label: 'Auto-sync', description: 'Indexed in ~2 minutes' }, - { icon: '⚙️', label: 'Schema extracted', description: 'From actual code' }, - { icon: '✅', label: 'Health checked', description: 'Import + execution' }, +const tools = [ + { + name: 'searchTpmjsToolsTool', + package: '@tpmjs/search-registry', + description: 'Agent searches the registry by query, category, or context', + color: 'cyan', + }, + { + name: 'registryExecuteTool', + package: '@tpmjs/registry-execute', + description: 'Agent executes a discovered tool by ID in a sandbox', + color: 'purple', + }, ]; export function HowItWorksSlide(): React.ReactElement { return (
- {/* Background gradient */} -
+
-

How It Works

-

Automated pipeline. No manual curation.

+

Two Meta-Tools

+

Tools that discover and execute other tools

- {/* Flow diagram */} -
- {steps.map((step, index) => ( -
- {/* Step card */} - + {tools.map((tool, index) => ( + +
- - {step.icon} - - {step.label} - {step.description} - - - {/* Arrow */} - {index < steps.length - 1 && ( - -
- - - )} -
+ {tool.package} +
+
+ {tool.name} +
+
{tool.description}
+
))}
- {/* Animated highlight line */} + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + transition={{ delay: 1 }} + className="mt-10 text-white/40 text-sm font-mono" + > + Both are standard AI SDK tools. Pass them to generateText like any other. +
); diff --git a/apps/tutorial/src/components/slides/IntegrationSlide.tsx b/apps/tutorial/src/components/slides/IntegrationSlide.tsx index f284cf9..b861cd7 100644 --- a/apps/tutorial/src/components/slides/IntegrationSlide.tsx +++ b/apps/tutorial/src/components/slides/IntegrationSlide.tsx @@ -1,123 +1,100 @@ 'use client'; import { motion } from 'framer-motion'; -import { useEffect, useState } from 'react'; -const codeLines = [ - { text: '// Quality score: 0.00 - 1.00', delay: 0.5 }, - { text: "const tier = tier === 'rich' ? 0.6 : 0.4;", delay: 0.8 }, - { text: 'const downloads = Math.min(0.2, log10(d));', delay: 1.1 }, - { text: 'const stars = Math.min(0.1, log10(s));', delay: 1.4 }, - { text: '', delay: 1.7 }, - { text: '// Rich = has params, returns, env', delay: 2.0 }, - { text: 'const score = tier + downloads + stars;', delay: 2.3 }, +const executeSchema = [ + ' toolId: z.string(),', + ' params: z.record(z.unknown()),', + ' env?: z.record(z.string()),', ]; -function TypewriterLine({ text, delay }: { text: string; delay: number }) { - const [displayText, setDisplayText] = useState(''); - - useEffect(() => { - if (!text) { - setDisplayText(''); - return; - } - - const timeout = setTimeout(() => { - let i = 0; - const interval = setInterval(() => { - if (i <= text.length) { - setDisplayText(text.slice(0, i)); - i++; - } else { - clearInterval(interval); - } - }, 30); - - return () => clearInterval(interval); - }, delay * 1000); - - return () => clearTimeout(timeout); - }, [text, delay]); - - // Simple text display without syntax highlighting to avoid dangerouslySetInnerHTML - const isComment = displayText.startsWith('//'); - - return ( -
- {displayText || '\u00A0'} -
- ); -} +const flowSteps = [ + { step: '1', text: 'Fetch tool metadata from registry', color: 'cyan' }, + { step: '2', text: 'Import package from esm.sh', color: 'purple' }, + { step: '3', text: 'Execute in isolated sandbox', color: 'emerald' }, + { step: '4', text: 'Return result to agent', color: 'yellow' }, +]; export function IntegrationSlide(): React.ReactElement { return (
- {/* Background */} -
+
- - 📊 - +

registryExecuteTool

+

+ Run any tool from the registry. No install. No bundle. +

-

Quality Scoring

-

Deliberately simple. Not trying to be clever.

+
+ {/* Input */} + +
inputSchema
+
+
{'z.object({'}
+ {executeSchema.map((line) => ( +
+ {line} +
+ ))} +
{'})'}
+
+
- {/* Code block */} - - {/* Window chrome */} -
-
-
-
- agent.ts -
+ {/* Flow */} + +
execution flow
+
+ {flowSteps.map((item, i) => ( + + + {item.step} + + {item.text} + + ))} +
+
+
- {/* Code content */} -
- {codeLines.map((line) => ( - - ))} - -
- - - {/* Score factors */} - {['Tier: 40-60%', 'Downloads: 0-20%', 'Stars: 0-10%', 'Richness: 0-10%'].map((factor) => ( - - {factor} - - ))} + Sandbox runs on Railway. 5s timeout. Isolated per execution.
diff --git a/apps/tutorial/src/components/slides/ProblemSlide.tsx b/apps/tutorial/src/components/slides/ProblemSlide.tsx index 0f4e5ba..6e4cdd5 100644 --- a/apps/tutorial/src/components/slides/ProblemSlide.tsx +++ b/apps/tutorial/src/components/slides/ProblemSlide.tsx @@ -2,103 +2,85 @@ import { motion } from 'framer-motion'; -const floatingIcons = [ - { icon: '🔧', x: '15%', y: '20%', delay: 0 }, - { icon: '⚙️', x: '75%', y: '15%', delay: 0.2 }, - { icon: '🔌', x: '85%', y: '60%', delay: 0.4 }, - { icon: '📦', x: '10%', y: '70%', delay: 0.6 }, - { icon: '🛠️', x: '60%', y: '80%', delay: 0.8 }, - { icon: '🔗', x: '25%', y: '45%', delay: 1 }, - { icon: '📡', x: '80%', y: '35%', delay: 1.2 }, - { icon: '💾', x: '40%', y: '25%', delay: 1.4 }, +const codeLines = [ + "import { weatherTool } from './tools/weather';", + "import { searchTool } from './tools/search';", + "import { calendarTool } from './tools/calendar';", + "import { emailTool } from './tools/email';", + '// ... 47 more imports', + '', + 'const result = await generateText({', + " model: openai('gpt-4-turbo'),", + ' tools: { weather, search, calendar, email, ... },', + '});', ]; export function ProblemSlide(): React.ReactElement { return (
- {/* Floating chaotic icons */} - {floatingIcons.map((item) => ( - - {item.icon} - - ))} +
- {/* Red warning gradient */} -
- - {/* Content */} - - 😵 - - -

The Problem

- -

- npm has 2 million packages. -
- Which ones are AI-callable tools? Which ones{' '} - actually work? +

AI SDK Tools Are Static

+

+ You import them. You bundle them. They're compiled in.

+ {/* Code block */} + +
+
+
+
+ agent.ts +
+
+ {codeLines.map((line, i) => ( + + {line || '\u00A0'} + + ))} +
+ + - {["Can't find them", "Can't trust them", "Can't compare them", 'No schemas'].map( - (word, i) => ( - ( + - {word} - + {text} + ) )} diff --git a/apps/tutorial/src/components/slides/QualitySlide.tsx b/apps/tutorial/src/components/slides/QualitySlide.tsx index e671548..8f71873 100644 --- a/apps/tutorial/src/components/slides/QualitySlide.tsx +++ b/apps/tutorial/src/components/slides/QualitySlide.tsx @@ -1,137 +1,106 @@ 'use client'; -import { animate, motion, useMotionValue } from 'framer-motion'; -import { useEffect, useState } from 'react'; +import { motion } from 'framer-motion'; -function AnimatedGauge({ value, delay = 0 }: { value: number; delay?: number }) { - const progress = useMotionValue(0); - const [displayValue, setDisplayValue] = useState(0); - - useEffect(() => { - const timeout = setTimeout(() => { - animate(progress, value, { - duration: 1.5, - ease: 'easeOut', - }); - }, delay * 1000); - - return () => clearTimeout(timeout); - }, [progress, value, delay]); - - useEffect(() => { - const unsubscribe = progress.on('change', (v) => setDisplayValue(Math.round(v))); - return unsubscribe; - }, [progress]); - - const circumference = 2 * Math.PI * 45; - const strokeDashoffset = circumference - (displayValue / 100) * circumference; - - return ( -
- -
- {displayValue}% -
-
- ); -} +const registryData = [ + { field: 'toolId', value: '"@tpmjs/markdown::formatTable"', color: 'cyan' }, + { field: 'description', value: '"Format markdown tables"', color: 'white' }, + { field: 'inputSchema', value: '{ type: "object", ... }', color: 'purple' }, + { field: 'returnSchema', value: '{ type: "string" }', color: 'purple' }, + { field: 'envKeys', value: '[]', color: 'yellow' }, + { field: 'qualityScore', value: '0.92', color: 'emerald' }, +]; const qualityFactors = [ - { name: 'Import Health', icon: '📦', score: 95, color: 'cyan', desc: 'Can we require() it?' }, - { name: 'Execution Health', icon: '▶️', score: 88, color: 'emerald', desc: 'Does it run at all?' }, - { name: 'HEALTHY', icon: '✅', score: 100, color: 'emerald', desc: 'Passed both checks' }, - { name: 'BROKEN', icon: '❌', score: 0, color: 'yellow', desc: 'Labeled, not hidden' }, + { name: 'Schema Completeness', desc: 'Input/output types defined' }, + { name: 'npm Downloads', desc: 'Community adoption signal' }, + { name: 'Health Status', desc: 'Can it import and execute?' }, ]; export function QualitySlide(): React.ReactElement { return (
- {/* Background */}
- - ⭐ - - -

Health Checks

-

- We don't hide broken tools. We label them. +

What Gets Indexed

+

+ Schemas extracted. Quality scored. Health checked.

- {/* Main gauge */} - - - +
+ {/* Registry entry */} + +
registry entry
+
+
{'{'}
+ {registryData.map((item) => ( +
+ {item.field}: + + {item.value} + +
+ ))} +
{'}'}
+
+
- {/* Quality factors */} -
- {qualityFactors.map((factor, index) => ( - -
{factor.icon}
-
{factor.name}
-
{factor.desc}
-
+ {/* Quality factors */} + +
qualityScore factors
+
+ {qualityFactors.map((factor, i) => ( -
-
- ))} + key={factor.name} + initial={{ opacity: 0, x: 10 }} + animate={{ opacity: 1, x: 0 }} + transition={{ delay: 0.7 + i * 0.1 }} + className="p-3 rounded-lg bg-white/5 border border-white/10" + > +
{factor.name}
+
{factor.desc}
+ + ))} +
+
+ + + Synced from npm every 2 minutes. 200+ tools indexed. +
); diff --git a/apps/tutorial/src/components/slides/SolutionSlide.tsx b/apps/tutorial/src/components/slides/SolutionSlide.tsx index 8837ca7..8035f8c 100644 --- a/apps/tutorial/src/components/slides/SolutionSlide.tsx +++ b/apps/tutorial/src/components/slides/SolutionSlide.tsx @@ -2,84 +2,82 @@ import { motion } from 'framer-motion'; -const gridItems = Array.from({ length: 12 }, (_, i) => ({ - icon: ['🔧', '⚙️', '📦', '🔌', '🛠️', '📡', '💾', '🔗', '🎯', '⚡', '🌐', '🔐'][i], - delay: i * 0.05, -})); +const codeLines = [ + "import { searchTpmjsToolsTool } from '@tpmjs/search-registry';", + "import { registryExecuteTool } from '@tpmjs/registry-execute';", + '', + 'const result = await generateText({', + " model: openai('gpt-4-turbo'),", + ' tools: { search: searchTpmjsToolsTool, execute: registryExecuteTool },', + " prompt: 'Format this markdown table for me...',", + '});', +]; export function SolutionSlide(): React.ReactElement { return (
- {/* Green success gradient */}
- {/* Content */} +

+ What If Tools Were Dynamic? +

+

Two imports. Infinite tools at runtime.

+ + {/* Code block */} - ✨ - - -

What TPMJS Is

- -

- A registry that indexes npm packages - for AI tool use. -
- Extracts schemas from code. Scores - quality. Checks health. -

- - {/* Organized grid of icons */} - - {gridItems.map((item) => ( - - {item.icon} - - ))} +
+
+
+
+ agent.ts +
+
+ {codeLines.map((line, i) => ( + + {line || '\u00A0'} + + ))} +
- {['Registry', 'Schema extraction', 'Quality scoring', 'Health checks'].map((word, i) => ( - ( + - {word} - + {text} + ))} diff --git a/apps/tutorial/src/components/slides/ToolDetailSlide.tsx b/apps/tutorial/src/components/slides/ToolDetailSlide.tsx index a3f8bd9..0881da1 100644 --- a/apps/tutorial/src/components/slides/ToolDetailSlide.tsx +++ b/apps/tutorial/src/components/slides/ToolDetailSlide.tsx @@ -2,111 +2,107 @@ import { motion } from 'framer-motion'; +const roleStyles: Record = { + user: 'bg-white/10 text-white/60', + agent: 'bg-cyan-500/20 text-cyan-400', + registry: 'bg-emerald-500/20 text-emerald-400', + sandbox: 'bg-yellow-500/20 text-yellow-400', +}; + +const colorStyles: Record = { + white: 'text-white/70', + cyan: 'text-cyan-400', + purple: 'text-purple-400', + emerald: 'text-emerald-400', + yellow: 'text-yellow-400', +}; + +const conversation = [ + { + id: 'user-ask', + role: 'user', + content: '"Format this CSV as a markdown table"', + color: 'white', + }, + { + id: 'agent-search', + role: 'agent', + content: 'searchTpmjsToolsTool({ query: "markdown table" })', + color: 'cyan', + }, + { + id: 'registry-found', + role: 'registry', + content: '→ Found: @tpmjs/markdown::formatTable', + color: 'emerald', + }, + { + id: 'agent-execute', + role: 'agent', + content: 'registryExecuteTool({ toolId: "...", params: {...} })', + color: 'purple', + }, + { + id: 'sandbox-result', + role: 'sandbox', + content: '→ Executed in 120ms', + color: 'yellow', + }, + { + id: 'agent-respond', + role: 'agent', + content: '"Here\'s your formatted table: ..."', + color: 'white', + }, +]; + export function ToolDetailSlide(): React.ReactElement { return (
- {/* Background */}
-

What We Store

-

- Everything an agent needs to call a tool correctly +

Full Agent Flow

+

+ User asks. Agent searches. Tool executes. Done.

- {/* Mock tool card */} - - {/* Header */} -
-
-
- - 🔍 - -
-

webSearch

- @tpmjs/web-tools -
-
-
+
+ {conversation.map((msg, i) => ( - Verified + + {msg.role} + + + {msg.content} + + ))} +
+ + +
+ The agent never imported formatTable. + It discovered and executed it at runtime.
- - {/* Description */} - - Search the web and return structured results. Supports query filtering, result limits, - and domain restrictions. - - - {/* Metadata grid */} - -
-
Downloads
-
12.4k
-
-
-
Quality
-
94%
-
-
-
Category
-
Search
-
-
- - {/* Schema preview */} - -
{'// Extracted from actual code'}
-
- inputSchema: JSON Schema -
-
- returnSchema: JSON Schema -
-
- envKeys: ["API_KEY"] -
-
- tier: "rich" -
-
diff --git a/apps/tutorial/src/components/slides/WelcomeSlide.tsx b/apps/tutorial/src/components/slides/WelcomeSlide.tsx index 86b6188..0c6b6f7 100644 --- a/apps/tutorial/src/components/slides/WelcomeSlide.tsx +++ b/apps/tutorial/src/components/slides/WelcomeSlide.tsx @@ -4,76 +4,53 @@ import { motion } from 'framer-motion'; export function WelcomeSlide(): React.ReactElement { return ( -
- {/* Animated background orbs */} -
- - -
+
+ {/* Subtle gradient background */} +
- {/* Logo / Title */} + {/* Main content */} -

- - TPMJS - -

-
- - {/* Tagline */} - - The missing layer between "LLMs can call tools" and "which tool, - exactly?" - + + + TPMJS + + - {/* Decorative line */} - + + A Tool Registry for the AI SDK + - {/* CTA hint */} + + Dynamic tool discovery and execution for agents + + + + {/* Press to continue */} - Press any key to begin + Press any key
);