From f24eebc1960ca24c2853d70ed369b34dd6d475b8 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Mon, 29 Dec 2025 21:30:42 +1000 Subject: [PATCH] feat: add npm-like features to tool detail page and update keyword to tpmjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- CLAUDE.md | 10 +- HOW_TO_PUBLISH_A_TOOL.md | 14 +- LAUNCH_REVIEW.md | 2 +- README.md | 4 +- TPMJS_TALK.md | 10 +- apps/tutorial/src/app/page.tsx | 2 +- .../author-slides/AuthorNextStepsSlide.tsx | 2 +- .../author-slides/HowSyncWorksSlide.tsx | 2 +- .../author-slides/PackageSetupSlide.tsx | 6 +- .../src/components/slides/GetStartedSlide.tsx | 6 +- apps/web/src/app/api/bundlephobia/route.ts | 60 ++++++++ apps/web/src/app/api/sync/changes/route.ts | 4 +- apps/web/src/app/api/sync/keyword/route.ts | 10 +- apps/web/src/app/docs/page.tsx | 14 +- apps/web/src/app/faq/page.tsx | 6 +- apps/web/src/app/how-it-works/page.tsx | 7 +- apps/web/src/app/privacy/page.tsx | 5 +- apps/web/src/app/publish/page.tsx | 16 +-- apps/web/src/app/spec/page.tsx | 13 +- apps/web/src/app/terms/page.tsx | 6 +- .../app/tool/[...slug]/ToolDetailClient.tsx | 23 +++ apps/web/src/app/tool/tool-search/page.tsx | 6 +- .../src/components/ArchitectureDiagram.tsx | 4 +- apps/web/src/components/BundleSize.tsx | 124 +++++++++++++++++ apps/web/src/components/DownloadSparkline.tsx | 131 ++++++++++++++++++ docs/AI_SDK_TOOL_EXECUTION.md | 2 +- docs/REGISTRY_SDK_DESIGN.md | 4 +- docs/building-dynamic-tool-systems.md | 2 +- packages/tools/create-basic-tools/README.md | 2 +- packages/tools/create-basic-tools/src/cli.ts | 2 +- .../src/generators/package-json.ts | 2 +- packages/tools/createBlogPost/CHANGELOG.md | 2 +- packages/tools/createBlogPost/package.json | 2 +- packages/tools/emoji-magic/package.json | 2 +- packages/tools/hello/README.md | 2 +- packages/tools/hello/package.json | 2 +- .../tools/markdown-formatter/package.json | 2 +- packages/tools/package.json | 2 +- packages/tools/registryExecute/package.json | 2 +- packages/tools/registrySearch/package.json | 2 +- packages/tools/search-registry/package.json | 2 +- packages/tools/unsandbox/package.json | 2 +- 42 files changed, 426 insertions(+), 97 deletions(-) create mode 100644 apps/web/src/app/api/bundlephobia/route.ts create mode 100644 apps/web/src/components/BundleSize.tsx create mode 100644 apps/web/src/components/DownloadSparkline.tsx diff --git a/CLAUDE.md b/CLAUDE.md index 089f0a4..da7946e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -684,14 +684,14 @@ The full working implementation is live at [tpmjs.com](https://tpmjs.com). ## 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 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 -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 ### 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`) -**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 * * * *`) **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 3. Validates the `tpmjs` field 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: -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 3. **Validation:** `validateTpmjsField()` checks that the `tpmjs` field meets requirements 4. **Database Insert:** Tool is upserted with initial data diff --git a/HOW_TO_PUBLISH_A_TOOL.md b/HOW_TO_PUBLISH_A_TOOL.md index 565cb8c..9ef0fc4 100644 --- a/HOW_TO_PUBLISH_A_TOOL.md +++ b/HOW_TO_PUBLISH_A_TOOL.md @@ -5,7 +5,7 @@ This guide shows you how to create and publish an AI tool that will be automatic ## Quick Start 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 4. Publish to NPM 5. Your tool will automatically appear on tpmjs.com within 15 minutes @@ -24,18 +24,18 @@ npm init -y ### 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 { "name": "@yourname/my-awesome-tool", "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 @@ -212,7 +212,7 @@ npm publish --access public 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) 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", "description": "A tool for creating structured blog posts with AI-generated content", "type": "module", - "keywords": ["tpmjs-tool", "blog", "content", "ai", "writing"], + "keywords": ["tpmjs", "blog", "content", "ai", "writing"], "exports": { ".": { "types": "./dist/index.d.ts", @@ -402,7 +402,7 @@ Or manually check the structure matches the examples above. ## Troubleshooting **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) - Check the NPM package is public: `npm view yourpackage` diff --git a/LAUNCH_REVIEW.md b/LAUNCH_REVIEW.md index edb20e4..38d77b8 100644 --- a/LAUNCH_REVIEW.md +++ b/LAUNCH_REVIEW.md @@ -282,7 +282,7 @@ this becomes unmanageable. designed for AI agents. Agents can search for tools by description and load them 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. **For Agent Developers:** Use semantic search to find tools: diff --git a/README.md b/README.md index 03b88c6..057aaff 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **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? @@ -24,7 +24,7 @@ npx @tpmjs/create-basic-tools Or add manually to your package.json: ```json { - "keywords": ["tpmjs-tool"], + "keywords": ["tpmjs"], "tpmjs": { "category": "text-analysis" } diff --git a/TPMJS_TALK.md b/TPMJS_TALK.md index 99142bd..5a2b8ed 100644 --- a/TPMJS_TALK.md +++ b/TPMJS_TALK.md @@ -38,13 +38,13 @@ TPMJS runs three automated sync jobs: **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. **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. @@ -61,7 +61,7 @@ To get indexed, a package needs two things: ```json { "name": "@acme/my-tool", - "keywords": ["tpmjs-tool"], + "keywords": ["tpmjs"], "tpmjs": { "category": "web-scraping", "description": "Scrapes URLs and returns structured markdown" @@ -303,7 +303,7 @@ Publish to npm β†’ hope someone finds it β†’ no visibility into usage 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 β†’ execution stats tracked ``` @@ -383,7 +383,7 @@ Discovery is the bottleneck. We're fixing discovery. - **Browse**: https://tpmjs.com/tool-search - **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` --- diff --git a/apps/tutorial/src/app/page.tsx b/apps/tutorial/src/app/page.tsx index a08af14..70a5b0b 100644 --- a/apps/tutorial/src/app/page.tsx +++ b/apps/tutorial/src/app/page.tsx @@ -53,7 +53,7 @@ const tutorials = [ /> ), - features: ['tpmjs-tool keyword', 'Schema extraction', 'Quality scoring'], + features: ['tpmjs keyword', 'Schema extraction', 'Quality scoring'], }, ]; diff --git a/apps/tutorial/src/components/author-slides/AuthorNextStepsSlide.tsx b/apps/tutorial/src/components/author-slides/AuthorNextStepsSlide.tsx index 20051ed..660e5ce 100644 --- a/apps/tutorial/src/components/author-slides/AuthorNextStepsSlide.tsx +++ b/apps/tutorial/src/components/author-slides/AuthorNextStepsSlide.tsx @@ -3,7 +3,7 @@ import { motion } from 'framer-motion'; 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: '3', text: 'npm publish' }, ]; diff --git a/apps/tutorial/src/components/author-slides/HowSyncWorksSlide.tsx b/apps/tutorial/src/components/author-slides/HowSyncWorksSlide.tsx index 3fbc120..f9108e5 100644 --- a/apps/tutorial/src/components/author-slides/HowSyncWorksSlide.tsx +++ b/apps/tutorial/src/components/author-slides/HowSyncWorksSlide.tsx @@ -3,7 +3,7 @@ import { motion } from 'framer-motion'; 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: '3', text: 'We extract schemas from your package.json', color: 'emerald' }, { step: '4', text: 'Your tool appears in search results', color: 'yellow' }, diff --git a/apps/tutorial/src/components/author-slides/PackageSetupSlide.tsx b/apps/tutorial/src/components/author-slides/PackageSetupSlide.tsx index 59a389c..84df22c 100644 --- a/apps/tutorial/src/components/author-slides/PackageSetupSlide.tsx +++ b/apps/tutorial/src/components/author-slides/PackageSetupSlide.tsx @@ -7,7 +7,7 @@ const packageJson = [ { 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: 'keyword', line: ' "tpmjs"' }, { id: 'keywords-close', line: ' ],' }, { id: 'tpmjs-open', line: ' "tpmjs": {' }, { id: 'tools-open', line: ' "tools": [{ ... }]' }, @@ -43,7 +43,7 @@ export function PackageSetupSlide(): React.ReactElement {
-
tpmjs-tool keyword
+
tpmjs keyword
Required. This is how we find you on npm.
diff --git a/apps/tutorial/src/components/slides/GetStartedSlide.tsx b/apps/tutorial/src/components/slides/GetStartedSlide.tsx index 3fcb200..e2461d0 100644 --- a/apps/tutorial/src/components/slides/GetStartedSlide.tsx +++ b/apps/tutorial/src/components/slides/GetStartedSlide.tsx @@ -5,7 +5,7 @@ import { motion } from 'framer-motion'; const packageJsonLines = [ { id: 'open', line: '{' }, { id: 'name', line: ' "name": "@your-org/your-tools",' }, - { id: 'keywords', line: ' "keywords": ["tpmjs-tool"],' }, + { id: 'keywords', line: ' "keywords": ["tpmjs"],' }, { id: 'tpmjs', line: ' "tpmjs": {' }, { id: 'tools', line: ' "tools": [{' }, { id: 'toolName', line: ' "name": "yourTool",' }, @@ -16,7 +16,7 @@ const packageJsonLines = [ ]; 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: '3', text: 'npm publish', color: 'emerald' }, ]; @@ -56,7 +56,7 @@ export function GetStartedSlide(): React.ReactElement {
@@ -40,9 +40,9 @@ export async function POST(request: NextRequest) { const skippedPackages: Array<{ name: string; author: string; reason: string }> = []; try { - // Search for packages with 'tpmjs-tool' keyword + // Search for packages with 'tpmjs' keyword const searchResults = await searchByKeyword({ - keyword: 'tpmjs-tool', + keyword: 'tpmjs', size: 250, // Get up to 250 packages per sync }); @@ -122,7 +122,7 @@ export async function POST(request: NextRequest) { frameworks: validation.packageData.frameworks || [], tier: validation.tier || 'minimal', discoveryMethod: 'keyword', - isOfficial: pkg.keywords?.includes('tpmjs-tool') || false, + isOfficial: pkg.keywords?.includes('tpmjs') || false, npmDownloadsLastMonth: 0, // Will be updated by metrics sync githubStars: githubStars, }, @@ -141,7 +141,7 @@ export async function POST(request: NextRequest) { env: validation.packageData.env ?? undefined, frameworks: validation.packageData.frameworks || [], tier: validation.tier || 'minimal', - isOfficial: pkg.keywords?.includes('tpmjs-tool') || false, + isOfficial: pkg.keywords?.includes('tpmjs') || false, }, }); diff --git a/apps/web/src/app/docs/page.tsx b/apps/web/src/app/docs/page.tsx index 99a70fa..87904a2 100644 --- a/apps/web/src/app/docs/page.tsx +++ b/apps/web/src/app/docs/page.tsx @@ -370,9 +370,7 @@ Use registrySearch to find tools, then registryExecute to run them.\`,

TPMJS automatically discovers tools from npm packages that have the{' '} - - tpmjs-tool - {' '} + tpmjs{' '} keyword. Tools are indexed every 2-15 minutes.

@@ -832,7 +830,7 @@ while (true) {

{[ - { step: '1', label: 'Add tpmjs-tool keyword' }, + { step: '1', label: 'Add tpmjs keyword' }, { step: '2', label: 'Add tpmjs field' }, { step: '3', label: 'Publish to npm' }, { step: '4', label: 'Live in 15 minutes!' }, @@ -878,7 +876,7 @@ while (true) { code={`{ "name": "@yourname/my-tool", "version": "1.0.0", - "keywords": ["tpmjs-tool"], + "keywords": ["tpmjs"], "tpmjs": { "category": "text-analysis", "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?', - 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?', @@ -1155,8 +1153,8 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
  • Ensure you have{' '} - tpmjs-tool in - your keywords + tpmjs in your + keywords
  • Verify your tpmjs field is valid JSON diff --git a/apps/web/src/app/faq/page.tsx b/apps/web/src/app/faq/page.tsx index fa6d7dd..9d0fbc8 100644 --- a/apps/web/src/app/faq/page.tsx +++ b/apps/web/src/app/faq/page.tsx @@ -83,7 +83,7 @@ export default function FAQPage(): React.ReactElement {

    Think of it as npm for AI tools. Developers publish tools to npm with the{' '} - tpmjs-tool + tpmjs {' '} keyword, and TPMJS automatically syncs them to our registry where they can be discovered by AI agents and developers. @@ -97,7 +97,7 @@ export default function FAQPage(): React.ReactElement {

  • Add the{' '} - tpmjs-tool + tpmjs {' '} keyword to your package.json
  • @@ -254,7 +254,7 @@ export default function FAQPage(): React.ReactElement { Keyword Search (every 15 minutes):{' '} Actively searches for packages with the{' '} - tpmjs-tool + tpmjs {' '} keyword diff --git a/apps/web/src/app/how-it-works/page.tsx b/apps/web/src/app/how-it-works/page.tsx index d283f43..9060095 100644 --- a/apps/web/src/app/how-it-works/page.tsx +++ b/apps/web/src/app/how-it-works/page.tsx @@ -108,7 +108,7 @@ export default function HowItWorksPage(): React.ReactElement { code={`{ "name": "@yourname/awesome-tool", "version": "1.0.0", - "keywords": ["tpmjs-tool"], + "keywords": ["tpmjs"], "tpmjs": { "category": "text-analysis", "frameworks": ["vercel-ai"], @@ -232,7 +232,7 @@ const result = await streamText({

    Keyword Search

    - Searches npm for "tpmjs-tool" keyword + Searches npm for "tpmjs" keyword

    Every 15 minutes
    @@ -398,8 +398,7 @@ const result = await streamText({

    Developer publishes to npm

    Package with{' '} - tpmjs-tool{' '} - keyword + tpmjs keyword

~1 second diff --git a/apps/web/src/app/privacy/page.tsx b/apps/web/src/app/privacy/page.tsx index a93be10..ddd48e9 100644 --- a/apps/web/src/app/privacy/page.tsx +++ b/apps/web/src/app/privacy/page.tsx @@ -58,7 +58,7 @@ export default function PrivacyPage(): React.ReactElement {

We automatically collect and index public metadata from npm packages that use the{' '} - tpmjs-tool + tpmjs {' '} keyword. This includes:

@@ -430,8 +430,7 @@ export default function PrivacyPage(): React.ReactElement {

Request deletion of your data. To remove a tool from TPMJS, unpublish it from npm or remove the{' '} - tpmjs-tool{' '} - keyword. + tpmjs keyword.

diff --git a/apps/web/src/app/publish/page.tsx b/apps/web/src/app/publish/page.tsx index 8646123..0065807 100644 --- a/apps/web/src/app/publish/page.tsx +++ b/apps/web/src/app/publish/page.tsx @@ -86,9 +86,8 @@ export default function PublishPage(): React.ReactElement {
  1. Create a new NPM package
  2. - Add{' '} - tpmjs-tool{' '} - to keywords + Add tpmjs to + keywords
  3. Add a tpmjs{' '} @@ -112,8 +111,7 @@ export default function PublishPage(): React.ReactElement { Step 1: Add Required Keyword

    - Add the{' '} - tpmjs-tool{' '} + Add the tpmjs{' '} keyword to your package.json. This is required for automatic discovery.

    @@ -186,7 +184,7 @@ export default function PublishPage(): React.ReactElement { language="json" code={`{ "name": "@yourname/my-awesome-tool", - "keywords": ["tpmjs-tool"], + "keywords": ["tpmjs"], "tpmjs": { "category": "text-analysis" } @@ -211,7 +209,7 @@ export default function PublishPage(): React.ReactElement { language="json" code={`{ "name": "@yourname/sentiment-tool", - "keywords": ["tpmjs-tool"], + "keywords": ["tpmjs"], "tpmjs": { "category": "text-analysis", "frameworks": ["vercel-ai", "langchain"], @@ -336,7 +334,7 @@ npm publish --access public code={`{ "name": "@tpmjs/createblogpost", "version": "0.2.0", - "keywords": ["tpmjs-tool", "blog", "content"], + "keywords": ["tpmjs", "blog", "content"], "tpmjs": { "category": "text-analysis", "frameworks": ["vercel-ai", "langchain"], diff --git a/apps/web/src/app/spec/page.tsx b/apps/web/src/app/spec/page.tsx index 539f6c7..d202fca 100644 --- a/apps/web/src/app/spec/page.tsx +++ b/apps/web/src/app/spec/page.tsx @@ -85,8 +85,7 @@ export default function SpecPage(): React.ReactElement {

    - Add the{' '} - tpmjs-tool{' '} + Add the tpmjs{' '} keyword and a{' '} tpmjs metadata field to your package.json @@ -207,7 +206,7 @@ export default function SpecPage(): React.ReactElement { language="json" code={`{ "name": "@yourname/my-tool", - "keywords": ["tpmjs-tool"], + "keywords": ["tpmjs"], "tpmjs": { "category": "text-analysis" } @@ -341,7 +340,7 @@ export default function SpecPage(): React.ReactElement { language="json" code={`{ "name": "@yourname/sentiment-tool", - "keywords": ["tpmjs-tool"], + "keywords": ["tpmjs"], "tpmjs": { "category": "text-analysis", "frameworks": ["vercel-ai", "langchain"], @@ -681,8 +680,8 @@ export default function SpecPage(): React.ReactElement {

    Searches for{' '} - tpmjs-tool{' '} - keyword every 15 minutes + tpmjs keyword + every 15 minutes

    Every 15 min @@ -760,7 +759,7 @@ export default function SpecPage(): React.ReactElement {
    1️⃣

    - Add tpmjs-tool{' '} + Add tpmjs{' '} keyword

    diff --git a/apps/web/src/app/terms/page.tsx b/apps/web/src/app/terms/page.tsx index 50ae674..acaa9bd 100644 --- a/apps/web/src/app/terms/page.tsx +++ b/apps/web/src/app/terms/page.tsx @@ -63,7 +63,7 @@ export default function TermsPage(): React.ReactElement {
    • Automatic discovery and indexing of npm packages with the{' '} - tpmjs-tool{' '} + tpmjs{' '} keyword
    • A searchable registry of AI tools with quality scoring and health checks
    • @@ -247,7 +247,7 @@ export default function TermsPage(): React.ReactElement {

      By publishing a tool to npm with the{' '} - tpmjs-tool{' '} + tpmjs{' '} keyword, you grant TPMJS a non-exclusive, worldwide, royalty-free license to:

        @@ -321,7 +321,7 @@ export default function TermsPage(): React.ReactElement {

        You may stop using the Service at any time. If you have published tools, they will remain in the registry unless you remove the{' '} - tpmjs-tool{' '} + tpmjs{' '} keyword from your package or unpublish your package from npm.

diff --git a/apps/web/src/app/tool/[...slug]/ToolDetailClient.tsx b/apps/web/src/app/tool/[...slug]/ToolDetailClient.tsx index 6b62f1b..29af85a 100644 --- a/apps/web/src/app/tool/[...slug]/ToolDetailClient.tsx +++ b/apps/web/src/app/tool/[...slug]/ToolDetailClient.tsx @@ -11,6 +11,8 @@ import { Spinner } from '@tpmjs/ui/Spinner/Spinner'; import Link from 'next/link'; import { useState } from 'react'; import { AppHeader } from '~/components/AppHeader'; +import { BundleSize } from '~/components/BundleSize'; +import { DownloadSparkline } from '~/components/DownloadSparkline'; import { Markdown } from '~/components/Markdown'; import { ToolPlayground } from '~/components/ToolPlayground'; @@ -340,6 +342,21 @@ export function ToolDetailClient({ tool, slug }: ToolDetailClientProps): React.R language="bash" showCopy={true} /> + + +
@@ -547,6 +564,9 @@ console.log(result.text);`}

{pkg.npmDownloadsLastMonth?.toLocaleString() || '0'}

+
+ +
{pkg.githubStars != null && (
@@ -574,6 +594,9 @@ console.log(result.text);`} + {/* Bundle Size */} + + {/* NPM Keywords */} {pkg.npmKeywords && pkg.npmKeywords.length > 0 && ( diff --git a/apps/web/src/app/tool/tool-search/page.tsx b/apps/web/src/app/tool/tool-search/page.tsx index a9967b1..d35d1b8 100644 --- a/apps/web/src/app/tool/tool-search/page.tsx +++ b/apps/web/src/app/tool/tool-search/page.tsx @@ -463,7 +463,7 @@ export default function ToolSearchPage(): React.ReactElement { variant="outline" onClick={() => window.open( - 'https://www.npmjs.com/search?q=keywords:tpmjs-tool', + 'https://www.npmjs.com/search?q=keywords:tpmjs', '_blank', 'noopener' ) @@ -483,9 +483,7 @@ export default function ToolSearchPage(): React.ReactElement {

Add{' '} - - tpmjs-tool - {' '} + tpmjs{' '} keyword to your package.json

diff --git a/apps/web/src/components/ArchitectureDiagram.tsx b/apps/web/src/components/ArchitectureDiagram.tsx index 9eb599e..4d625c8 100644 --- a/apps/web/src/components/ArchitectureDiagram.tsx +++ b/apps/web/src/components/ArchitectureDiagram.tsx @@ -24,7 +24,7 @@ const nodeDescriptions: Record = npm: { title: 'NPM Registry', 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': { title: 'Changes Feed', @@ -34,7 +34,7 @@ const nodeDescriptions: Record = 'keyword-search': { title: 'Keyword Search', 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: { title: 'Schema Validation', diff --git a/apps/web/src/components/BundleSize.tsx b/apps/web/src/components/BundleSize.tsx new file mode 100644 index 0000000..47bbebf --- /dev/null +++ b/apps/web/src/components/BundleSize.tsx @@ -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(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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 ( + + + Bundle Size + + +
+
+
+
+ + + ); + } + + if (error || !data) { + return null; + } + + return ( + + + Bundle Size + + +
+ Minified + + {formatBytes(data.size)} + +
+
+ Gzipped + + {formatBytes(data.gzip)} + +
+ {data.dependencyCount > 0 && ( +
+ Dependencies + + {data.dependencyCount} + +
+ )} + + View on Bundlephobia β†’ + +
+
+ ); +} diff --git a/apps/web/src/components/DownloadSparkline.tsx b/apps/web/src/components/DownloadSparkline.tsx new file mode 100644 index 0000000..bfb9cd1 --- /dev/null +++ b/apps/web/src/components/DownloadSparkline.tsx @@ -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([]); + 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
; + } + + if (error || downloads.length === 0) { + return
; + } + + // 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 ( +
+ + {/* Gradient fill under the line */} + + + + + + + + {/* Area fill */} + + + {/* Line */} + + + + {/* Trend indicator */} + {Math.abs(trendPercent) >= 5 && ( +
+ {trendUp ? '↑' : '↓'} + {Math.abs(trendPercent)}% vs last week +
+ )} +
+ ); +} diff --git a/docs/AI_SDK_TOOL_EXECUTION.md b/docs/AI_SDK_TOOL_EXECUTION.md index 4220341..030729f 100644 --- a/docs/AI_SDK_TOOL_EXECUTION.md +++ b/docs/AI_SDK_TOOL_EXECUTION.md @@ -907,7 +907,7 @@ const providers: Record { const lambda = new AWS.Lambda(); const result = await lambda.invoke({ - FunctionName: `tpmjs-tool-${toolId.replace('::', '-')}`, + FunctionName: `tpmjs-${toolId.replace('::', '-')}`, Payload: JSON.stringify({ params, env }), }).promise(); return JSON.parse(result.Payload as string); diff --git a/docs/REGISTRY_SDK_DESIGN.md b/docs/REGISTRY_SDK_DESIGN.md index 82d36ba..cb140ff 100644 --- a/docs/REGISTRY_SDK_DESIGN.md +++ b/docs/REGISTRY_SDK_DESIGN.md @@ -377,7 +377,7 @@ packages/registryExecute/ "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": { "ai": "^4.0.0", "zod": "^3.0.0" @@ -402,7 +402,7 @@ packages/registryExecute/ "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": { "ai": "^4.0.0", "zod": "^3.0.0" diff --git a/docs/building-dynamic-tool-systems.md b/docs/building-dynamic-tool-systems.md index 4bd40c9..6287897 100644 --- a/docs/building-dynamic-tool-systems.md +++ b/docs/building-dynamic-tool-systems.md @@ -98,7 +98,7 @@ Tools declare their capabilities via a `tpmjs` field in package.json: { "name": "@myorg/web-scraper", "version": "1.0.0", - "keywords": ["tpmjs-tool"], + "keywords": ["tpmjs"], "tpmjs": { "category": "web-scraping", "frameworks": ["vercel-ai"], diff --git a/packages/tools/create-basic-tools/README.md b/packages/tools/create-basic-tools/README.md index 7ae8114..9b77f84 100644 --- a/packages/tools/create-basic-tools/README.md +++ b/packages/tools/create-basic-tools/README.md @@ -34,7 +34,7 @@ The CLI asks for just your package name and uses sensible defaults for everythin ```bash $ pnpmx @tpmjs/create-basic-tools -β”Œ create-tpmjs-tool +β”Œ create-tpmjs β”‚ β—‡ Package name β”‚ @myorg/content-tools diff --git a/packages/tools/create-basic-tools/src/cli.ts b/packages/tools/create-basic-tools/src/cli.ts index c0ded42..f61630a 100644 --- a/packages/tools/create-basic-tools/src/cli.ts +++ b/packages/tools/create-basic-tools/src/cli.ts @@ -18,7 +18,7 @@ import * as logger from './utils/logger.js'; * Runs the interactive CLI workflow */ export async function runInteractiveCLI(): Promise { - clack.intro(logger.bold('create-tpmjs-tool')); + clack.intro(logger.bold('create-tpmjs')); // Only ask for package name const name = await clack.text({ diff --git a/packages/tools/create-basic-tools/src/generators/package-json.ts b/packages/tools/create-basic-tools/src/generators/package-json.ts index 09ebdbb..e07b4e4 100644 --- a/packages/tools/create-basic-tools/src/generators/package-json.ts +++ b/packages/tools/create-basic-tools/src/generators/package-json.ts @@ -18,7 +18,7 @@ export function generatePackageJson(config: GeneratorConfig): string { dev: 'tsup --watch', 'type-check': 'tsc --noEmit', }, - keywords: ['tpmjs-tool', 'ai-sdk', packageInfo.category], + keywords: ['tpmjs', 'ai-sdk', packageInfo.category], author: packageInfo.author || '', license: packageInfo.license, tpmjs: { diff --git a/packages/tools/createBlogPost/CHANGELOG.md b/packages/tools/createBlogPost/CHANGELOG.md index 14a165b..2a84ec6 100644 --- a/packages/tools/createBlogPost/CHANGELOG.md +++ b/packages/tools/createBlogPost/CHANGELOG.md @@ -10,4 +10,4 @@ - Supports both Markdown and MDX formats - Automatic slug generation, word count, and reading time calculation - 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 diff --git a/packages/tools/createBlogPost/package.json b/packages/tools/createBlogPost/package.json index 5ed9bb1..319064b 100644 --- a/packages/tools/createBlogPost/package.json +++ b/packages/tools/createBlogPost/package.json @@ -3,7 +3,7 @@ "version": "0.3.0", "description": "A tool for creating structured blog posts with AI-generated content", "type": "module", - "keywords": ["tpmjs-tool", "blog", "content", "ai", "writing"], + "keywords": ["tpmjs", "blog", "content", "ai", "writing"], "exports": { ".": { "types": "./dist/index.d.ts", diff --git a/packages/tools/emoji-magic/package.json b/packages/tools/emoji-magic/package.json index 73cdec7..16528db 100644 --- a/packages/tools/emoji-magic/package.json +++ b/packages/tools/emoji-magic/package.json @@ -9,7 +9,7 @@ "build": "tsc", "type-check": "tsc --noEmit" }, - "keywords": ["tpmjs-tool", "emoji", "ai", "text-processing"], + "keywords": ["tpmjs", "emoji", "ai", "text-processing"], "tpmjs": { "category": "text-analysis", "frameworks": ["vercel-ai"], diff --git a/packages/tools/hello/README.md b/packages/tools/hello/README.md index 1d41575..0e04c23 100644 --- a/packages/tools/hello/README.md +++ b/packages/tools/hello/README.md @@ -64,7 +64,7 @@ This package serves as a template for creating your own TPMJS tools. Key require "description": "Your tool description" } ``` -5. **Include the `tpmjs-tool` keyword** in package.json +5. **Include the `tpmjs` keyword** in package.json ## Development diff --git a/packages/tools/hello/package.json b/packages/tools/hello/package.json index c0ae555..50d552c 100644 --- a/packages/tools/hello/package.json +++ b/packages/tools/hello/package.json @@ -10,7 +10,7 @@ "clean": "rm -rf dist", "type-check": "tsc --noEmit" }, - "keywords": ["tpmjs-tool", "ai-sdk", "hello", "example"], + "keywords": ["tpmjs", "ai-sdk", "hello", "example"], "tpmjs": { "category": "text-analysis", "frameworks": ["vercel-ai"], diff --git a/packages/tools/markdown-formatter/package.json b/packages/tools/markdown-formatter/package.json index e0c41a8..b84975c 100644 --- a/packages/tools/markdown-formatter/package.json +++ b/packages/tools/markdown-formatter/package.json @@ -9,7 +9,7 @@ "build": "tsc", "type-check": "tsc --noEmit" }, - "keywords": ["tpmjs-tool", "markdown", "formatter", "text-processing"], + "keywords": ["tpmjs", "markdown", "formatter", "text-processing"], "tpmjs": { "category": "text-analysis", "frameworks": ["vercel-ai"], diff --git a/packages/tools/package.json b/packages/tools/package.json index aace725..65ffcca 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -11,7 +11,7 @@ "clean": "rm -rf dist", "type-check": "tsc --noEmit" }, - "keywords": ["tpmjs-tool", "ai-sdk", "example"], + "keywords": ["tpmjs", "ai-sdk", "example"], "dependencies": { "ai": "6.0.0-beta.124", "zod": "^4.0.0" diff --git a/packages/tools/registryExecute/package.json b/packages/tools/registryExecute/package.json index ae7bc0d..61acc2a 100644 --- a/packages/tools/registryExecute/package.json +++ b/packages/tools/registryExecute/package.json @@ -9,7 +9,7 @@ "dev": "tsc --watch", "type-check": "tsc --noEmit" }, - "keywords": ["tpmjs-tool", "ai-sdk", "vercel-ai", "registry", "execute"], + "keywords": ["tpmjs", "ai-sdk", "vercel-ai", "registry", "execute"], "dependencies": { "ai": "6.0.0-beta.124" }, diff --git a/packages/tools/registrySearch/package.json b/packages/tools/registrySearch/package.json index 6c0c7a6..1ff85d1 100644 --- a/packages/tools/registrySearch/package.json +++ b/packages/tools/registrySearch/package.json @@ -9,7 +9,7 @@ "dev": "tsc --watch", "type-check": "tsc --noEmit" }, - "keywords": ["tpmjs-tool", "ai-sdk", "vercel-ai", "registry", "search"], + "keywords": ["tpmjs", "ai-sdk", "vercel-ai", "registry", "search"], "dependencies": { "ai": "6.0.0-beta.124" }, diff --git a/packages/tools/search-registry/package.json b/packages/tools/search-registry/package.json index 2a2555a..835e819 100644 --- a/packages/tools/search-registry/package.json +++ b/packages/tools/search-registry/package.json @@ -9,7 +9,7 @@ "dev": "tsc --watch", "type-check": "tsc --noEmit" }, - "keywords": ["tpmjs-tool", "ai", "search", "tool-registry"], + "keywords": ["tpmjs", "ai", "search", "tool-registry"], "dependencies": { "ai": "6.0.0-beta.124", "zod": "^3.23.0" diff --git a/packages/tools/unsandbox/package.json b/packages/tools/unsandbox/package.json index aa1d359..ca57f71 100644 --- a/packages/tools/unsandbox/package.json +++ b/packages/tools/unsandbox/package.json @@ -9,7 +9,7 @@ "dev": "tsc --watch", "type-check": "tsc --noEmit" }, - "keywords": ["tpmjs-tool", "ai-sdk", "code-execution", "sandbox", "unsandbox"], + "keywords": ["tpmjs", "ai-sdk", "code-execution", "sandbox", "unsandbox"], "dependencies": { "@thomasdavis/unsandbox": "^0.0.3" },