diff --git a/apps/web/src/app/(profile)/[username]/collections/[slug]/CollectionDetailClient.tsx b/apps/web/src/app/(profile)/[username]/collections/[slug]/CollectionDetailClient.tsx
new file mode 100644
index 0000000..d5c1ef2
--- /dev/null
+++ b/apps/web/src/app/(profile)/[username]/collections/[slug]/CollectionDetailClient.tsx
@@ -0,0 +1,419 @@
+'use client';
+
+import { Badge } from '@tpmjs/ui/Badge/Badge';
+import { Button } from '@tpmjs/ui/Button/Button';
+import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
+import { Icon } from '@tpmjs/ui/Icon/Icon';
+import Link from 'next/link';
+import { useCallback, useState } from 'react';
+import { AppHeader } from '~/components/AppHeader';
+import { ForkButton } from '~/components/ForkButton';
+import { ForkedFromBadge } from '~/components/ForkedFromBadge';
+import { LikeButton } from '~/components/LikeButton';
+import { ScenariosSection } from '~/components/ScenariosSection';
+import { ShareButton } from '~/components/ShareButton';
+import { UseCasesSection } from '~/components/UseCasesSection';
+import { useSession } from '~/lib/auth-client';
+
+export interface CollectionTool {
+ id: string;
+ toolId: string;
+ position: number;
+ note: string | null;
+ tool: {
+ id: string;
+ name: string;
+ description: string;
+ likeCount: number;
+ package: {
+ npmPackageName: string;
+ category: string;
+ };
+ };
+}
+
+export interface UseCaseToolStep {
+ toolName: string;
+ packageName: string;
+ purpose: string;
+ order: number;
+}
+
+export interface UseCase {
+ id: string;
+ userPrompt: string;
+ description: string;
+ toolSequence: UseCaseToolStep[];
+}
+
+export interface PublicCollection {
+ id: string;
+ slug: string; // Already coerced to empty string if null in server component
+ name: string;
+ description: string | null;
+ likeCount: number;
+ toolCount: number;
+ forkCount: number;
+ createdAt: string;
+ createdBy: {
+ id: string;
+ username: string;
+ name: string;
+ image: string | null;
+ };
+ tools: CollectionTool[];
+ forkedFromId: string | null;
+ forkedFrom: {
+ id: string;
+ name: string;
+ slug: string; // Already coerced to empty string if null in server component
+ user: {
+ username: string;
+ };
+ } | null;
+ useCases: UseCase[] | null;
+ useCasesGeneratedAt: string | null;
+}
+
+function McpUrlSection({
+ username,
+ slug,
+ isOwner,
+}: {
+ username: string;
+ slug: string;
+ isOwner: boolean;
+}) {
+ const [copiedUrl, setCopiedUrl] = useState<'http' | 'sse' | null>(null);
+ const [showConfig, setShowConfig] = useState(false);
+ const [showApiExample, setShowApiExample] = useState(false);
+
+ const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
+ const httpUrl = `${baseUrl}/api/mcp/${username}/${slug}/http`;
+ const sseUrl = `${baseUrl}/api/mcp/${username}/${slug}/sse`;
+
+ const copyToClipboard = async (url: string, type: 'http' | 'sse') => {
+ await navigator.clipboard.writeText(url);
+ setCopiedUrl(type);
+ setTimeout(() => setCopiedUrl(null), 2000);
+ };
+
+ const configSnippet = `{
+ "mcpServers": {
+ "tpmjs-${slug}": {
+ "command": "npx",
+ "args": [
+ "mcp-remote",
+ "${httpUrl}"
+ ]
+ }
+ }
+}`;
+
+ const apiExampleSnippet = `// Call a tool with your own credentials
+const response = await fetch("${httpUrl}", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": "Bearer YOUR_TPMJS_API_KEY"
+ },
+ body: JSON.stringify({
+ jsonrpc: "2.0",
+ method: "tools/call",
+ params: {
+ name: "tool-name",
+ arguments: { /* tool args */ },
+ env: {
+ // Your env vars for the tools
+ "API_KEY": "your-key-here"
+ }
+ },
+ id: 1
+ })
+});`;
+
+ return (
+
+
+
+
+
+
MCP Server URLs
+
+
+
+ {/* HTTP Transport */}
+
+
+
+ HTTP Transport
+
+ (recommended)
+
+
+
+ {httpUrl}
+
+
copyToClipboard(httpUrl, 'http')}
+ className="shrink-0"
+ >
+
+ {copiedUrl === 'http' ? 'Copied!' : 'Copy'}
+
+
+
+
+ {/* SSE Transport */}
+
+
+
+ SSE Transport
+
+ (streaming)
+
+
+
+ {sseUrl}
+
+
copyToClipboard(sseUrl, 'sse')}
+ className="shrink-0"
+ >
+
+ {copiedUrl === 'sse' ? 'Copied!' : 'Copy'}
+
+
+
+
+
+ {/* Note for non-owners */}
+ {!isOwner && (
+
+
+
+ You'll need to provide your own API keys for any tools that require them. Pass
+ credentials via the{' '}
+ env parameter in your
+ API calls.
+
+
+ )}
+
+ {/* Config snippet toggle */}
+
+
setShowConfig(!showConfig)}
+ className="flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"
+ >
+
+ Show Claude Desktop config
+
+
+ {showConfig && (
+
+
+
+ )}
+
+ {!isOwner && (
+ <>
+
setShowApiExample(!showApiExample)}
+ className="flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"
+ >
+
+ Show API usage example
+
+
+ {showApiExample && (
+
+
+
+ )}
+ >
+ )}
+
+
+
+ Use these URLs with{' '}
+
+ Claude Desktop, Cursor, or any MCP client
+
+
+
+ );
+}
+
+interface CollectionDetailClientProps {
+ collection: PublicCollection;
+ username: string;
+}
+
+export function CollectionDetailClient({
+ collection: initialCollection,
+ username,
+}: CollectionDetailClientProps) {
+ const { data: session } = useSession();
+ const [collection, setCollection] = useState(initialCollection);
+
+ // Check if current user is the owner
+ const isOwner = session?.user?.id && collection.createdBy?.id === session.user.id;
+
+ // Handler for when use cases are generated
+ const handleUseCasesGenerated = useCallback(
+ (useCases: UseCase[], generatedAt: string) => {
+ setCollection({
+ ...collection,
+ useCases,
+ useCasesGeneratedAt: generatedAt,
+ });
+ },
+ [collection]
+ );
+
+ // Generate tweet text
+ const tweetText = collection.description
+ ? `${collection.name} - ${collection.description.slice(0, 100)}${collection.description.length > 100 ? '...' : ''}`
+ : `Check out "${collection.name}" - a collection of ${collection.toolCount} AI tools`;
+
+ return (
+
+
+
+
+
+ {/* Collection Header */}
+
+
+
{collection.name}
+ {collection.description && (
+
{collection.description}
+ )}
+
+
+ by @{collection.createdBy.username}
+
+ {collection.forkedFrom && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ {/* Stats */}
+
+
+
+ {collection.toolCount} tools
+
+
+
+ {collection.likeCount} likes
+
+ {collection.forkCount > 0 && (
+
+
+ {collection.forkCount} forks
+
+ )}
+
+
+ {/* MCP Server URLs - Available to everyone (non-owners must provide their own credentials) */}
+
+
+ {/* Tools */}
+ {collection.tools.length > 0 ? (
+
+ Tools in Collection
+
+ {collection.tools.map((ct) => (
+
+
+
+
{ct.tool.name}
+
+ {ct.tool.description}
+
+ {ct.note && (
+
+ Note: {ct.note}
+
+ )}
+
+
+ {ct.tool.package.category}
+
+
+ {ct.tool.package.npmPackageName}
+
+
+
+
+
+ {ct.tool.likeCount}
+
+
+
+ ))}
+
+
+ ) : (
+
+
+
This collection is empty.
+
+ )}
+
+ {/* Scenarios Section */}
+ {collection.tools.length > 0 && (
+
+ )}
+
+ {/* Use Cases Section - at the bottom */}
+ {collection.tools.length > 0 && (
+
+ )}
+
+
+
+ );
+}
diff --git a/apps/web/src/app/(profile)/[username]/collections/[slug]/page.tsx b/apps/web/src/app/(profile)/[username]/collections/[slug]/page.tsx
index dbaea37..f1fcc95 100644
--- a/apps/web/src/app/(profile)/[username]/collections/[slug]/page.tsx
+++ b/apps/web/src/app/(profile)/[username]/collections/[slug]/page.tsx
@@ -1,451 +1,201 @@
-'use client';
+import { prisma } from '@tpmjs/db';
+import type { Metadata } from 'next';
+import { notFound } from 'next/navigation';
+import { CollectionDetailClient, type PublicCollection } from './CollectionDetailClient';
-import { Badge } from '@tpmjs/ui/Badge/Badge';
-import { Button } from '@tpmjs/ui/Button/Button';
-import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
-import { Icon } from '@tpmjs/ui/Icon/Icon';
-import Link from 'next/link';
-import { notFound, useParams } from 'next/navigation';
-import { useCallback, useEffect, useState } from 'react';
-import { AppHeader } from '~/components/AppHeader';
-import { ForkButton } from '~/components/ForkButton';
-import { ForkedFromBadge } from '~/components/ForkedFromBadge';
-import { LikeButton } from '~/components/LikeButton';
-import { ScenariosSection } from '~/components/ScenariosSection';
-import { UseCasesSection } from '~/components/UseCasesSection';
-import { useSession } from '~/lib/auth-client';
+export const dynamic = 'force-dynamic';
-interface CollectionTool {
- id: string;
- toolId: string;
- position: number;
- note: string | null;
- tool: {
- id: string;
- name: string;
- description: string;
- likeCount: number;
- package: {
- npmPackageName: string;
- category: string;
- };
- };
+interface CollectionPageProps {
+ params: Promise<{ username: string; slug: string }>;
}
-interface UseCaseToolStep {
- toolName: string;
- packageName: string;
- purpose: string;
- order: number;
-}
+/**
+ * Fetch collection data from database
+ */
+async function getCollection(username: string, slug: string): Promise {
+ // Remove @ prefix if present
+ const cleanUsername = username.startsWith('@') ? username.slice(1) : username;
-interface UseCase {
- id: string;
- userPrompt: string;
- description: string;
- toolSequence: UseCaseToolStep[];
-}
+ const collection = await prisma.collection.findFirst({
+ where: {
+ slug,
+ user: { username: cleanUsername },
+ isPublic: true,
+ },
+ include: {
+ user: {
+ select: {
+ id: true,
+ username: true,
+ name: true,
+ image: true,
+ },
+ },
+ tools: {
+ include: {
+ tool: {
+ include: {
+ package: {
+ select: {
+ npmPackageName: true,
+ category: true,
+ },
+ },
+ },
+ },
+ },
+ orderBy: { position: 'asc' },
+ },
+ forkedFrom: {
+ include: {
+ user: {
+ select: { username: true },
+ },
+ },
+ },
+ },
+ });
-interface PublicCollection {
- id: string;
- slug: string;
- name: string;
- description: string | null;
- likeCount: number;
- toolCount: number;
- forkCount: number;
- createdAt: string;
- createdBy: {
- id: string;
- username: string;
- name: string;
- image: string | null;
- };
- tools: CollectionTool[];
- forkedFromId: string | null;
- forkedFrom: {
- id: string;
- name: string;
- slug: string;
- user: {
- username: string;
- };
- } | null;
- useCases: UseCase[] | null;
- useCasesGeneratedAt: string | null;
-}
-
-function McpUrlSection({
- username,
- slug,
- isOwner,
-}: {
- username: string;
- slug: string;
- isOwner: boolean;
-}) {
- const [copiedUrl, setCopiedUrl] = useState<'http' | 'sse' | null>(null);
- const [showConfig, setShowConfig] = useState(false);
- const [showApiExample, setShowApiExample] = useState(false);
-
- const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
- const httpUrl = `${baseUrl}/api/mcp/${username}/${slug}/http`;
- const sseUrl = `${baseUrl}/api/mcp/${username}/${slug}/sse`;
-
- const copyToClipboard = async (url: string, type: 'http' | 'sse') => {
- await navigator.clipboard.writeText(url);
- setCopiedUrl(type);
- setTimeout(() => setCopiedUrl(null), 2000);
- };
-
- const configSnippet = `{
- "mcpServers": {
- "tpmjs-${slug}": {
- "command": "npx",
- "args": [
- "mcp-remote",
- "${httpUrl}"
- ]
- }
+ if (!collection) {
+ return null;
}
-}`;
- const apiExampleSnippet = `// Call a tool with your own credentials
-const response = await fetch("${httpUrl}", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "Authorization": "Bearer YOUR_TPMJS_API_KEY"
- },
- body: JSON.stringify({
- jsonrpc: "2.0",
- method: "tools/call",
- params: {
- name: "tool-name",
- arguments: { /* tool args */ },
- env: {
- // Your env vars for the tools
- "API_KEY": "your-key-here"
- }
+ // Parse useCases from Json field
+ const useCases = collection.useCases as
+ | {
+ id: string;
+ userPrompt: string;
+ description: string;
+ toolSequence: {
+ toolName: string;
+ packageName: string;
+ purpose: string;
+ order: number;
+ }[];
+ }[]
+ | null;
+
+ return {
+ id: collection.id,
+ slug: collection.slug || '',
+ name: collection.name,
+ description: collection.description,
+ likeCount: collection.likeCount,
+ toolCount: collection.tools.length,
+ forkCount: collection.forkCount,
+ createdAt: collection.createdAt.toISOString(),
+ createdBy: {
+ id: collection.user.id,
+ username: collection.user.username || '',
+ name: collection.user.name || '',
+ image: collection.user.image,
},
- id: 1
- })
-});`;
-
- return (
-
-
-
-
-
-
MCP Server URLs
-
-
-
- {/* HTTP Transport */}
-
-
-
- HTTP Transport
-
- (recommended)
-
-
-
- {httpUrl}
-
-
copyToClipboard(httpUrl, 'http')}
- className="shrink-0"
- >
-
- {copiedUrl === 'http' ? 'Copied!' : 'Copy'}
-
-
-
-
- {/* SSE Transport */}
-
-
-
- SSE Transport
-
- (streaming)
-
-
-
- {sseUrl}
-
-
copyToClipboard(sseUrl, 'sse')}
- className="shrink-0"
- >
-
- {copiedUrl === 'sse' ? 'Copied!' : 'Copy'}
-
-
-
-
-
- {/* Note for non-owners */}
- {!isOwner && (
-
-
-
- You'll need to provide your own API keys for any tools that require them. Pass
- credentials via the{' '}
- env parameter in your
- API calls.
-
-
- )}
-
- {/* Config snippet toggle */}
-
-
setShowConfig(!showConfig)}
- className="flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"
- >
-
- Show Claude Desktop config
-
-
- {showConfig && (
-
-
-
- )}
-
- {!isOwner && (
- <>
-
setShowApiExample(!showApiExample)}
- className="flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"
- >
-
- Show API usage example
-
-
- {showApiExample && (
-
-
-
- )}
- >
- )}
-
-
-
- Use these URLs with{' '}
-
- Claude Desktop, Cursor, or any MCP client
-
-
-
- );
+ tools: collection.tools.map((ct) => ({
+ id: ct.id,
+ toolId: ct.toolId,
+ position: ct.position,
+ note: ct.note,
+ tool: {
+ id: ct.tool.id,
+ name: ct.tool.name,
+ description: ct.tool.description,
+ likeCount: ct.tool.likeCount,
+ package: {
+ npmPackageName: ct.tool.package.npmPackageName,
+ category: ct.tool.package.category,
+ },
+ },
+ })),
+ forkedFromId: collection.forkedFromId,
+ forkedFrom: collection.forkedFrom
+ ? {
+ id: collection.forkedFrom.id,
+ name: collection.forkedFrom.name,
+ slug: collection.forkedFrom.slug || '',
+ user: {
+ username: collection.forkedFrom.user.username || '',
+ },
+ }
+ : null,
+ useCases: useCases ?? null,
+ useCasesGeneratedAt: collection.useCasesGeneratedAt?.toISOString() ?? null,
+ };
}
-export default function PrettyCollectionDetailPage(): React.ReactElement {
- const params = useParams();
- const rawUsername = params.username as string;
- const username = rawUsername.startsWith('@') ? rawUsername.slice(1) : rawUsername;
- const slug = params.slug as string;
- const { data: session } = useSession();
+/**
+ * Generate metadata for OG tags and SEO
+ */
+export async function generateMetadata({ params }: CollectionPageProps): Promise {
+ const { username, slug } = await params;
+ const cleanUsername = username.startsWith('@') ? username.slice(1) : username;
+ const collection = await getCollection(username, slug);
- const [collection, setCollection] = useState(null);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(null);
+ if (!collection) {
+ return {
+ title: 'Collection Not Found | TPMJS',
+ description: 'The requested collection could not be found.',
+ };
+ }
- // Check if current user is the owner
- const isOwner = session?.user?.id && collection?.createdBy?.id === session.user.id;
+ const title = `${collection.name} | TPMJS`;
+ const description =
+ collection.description ||
+ `${collection.name} - A collection of ${collection.toolCount} AI tools curated by @${cleanUsername}`;
- // Handler for when use cases are generated
- const handleUseCasesGenerated = useCallback(
- (useCases: UseCase[], generatedAt: string) => {
- if (collection) {
- setCollection({
- ...collection,
- useCases,
- useCasesGeneratedAt: generatedAt,
- });
- }
+ // Generate a list of tool names for keywords
+ const toolNames = collection.tools.slice(0, 5).map((t) => t.tool.name);
+ const keywords = ['TPMJS', 'AI', 'MCP', 'tools', 'collection', ...toolNames];
+
+ // OG image URL - for now use default, can add custom collection OG later
+ const ogImageUrl = `/api/og/collection/${encodeURIComponent(cleanUsername)}/${encodeURIComponent(slug)}`;
+
+ const canonicalUrl = `https://tpmjs.com/${cleanUsername}/collections/${slug}`;
+
+ return {
+ title,
+ description,
+ keywords,
+ authors: [{ name: `@${cleanUsername}` }],
+ openGraph: {
+ title: collection.name,
+ description,
+ type: 'website',
+ url: canonicalUrl,
+ siteName: 'TPMJS',
+ images: [
+ {
+ url: ogImageUrl,
+ width: 1200,
+ height: 630,
+ alt: `${collection.name} - TPMJS Collection`,
+ },
+ ],
},
- [collection]
- );
+ twitter: {
+ card: 'summary_large_image',
+ title: collection.name,
+ description,
+ site: '@tpmjs_registry',
+ creator: `@${cleanUsername}`,
+ images: [ogImageUrl],
+ },
+ alternates: {
+ canonical: canonicalUrl,
+ },
+ };
+}
- const fetchCollection = useCallback(async () => {
- try {
- const response = await fetch(`/api/public/users/${username}/collections/${slug}`);
- if (response.status === 404) {
- setError('not_found');
- return;
- }
- const data = await response.json();
+/**
+ * Collection detail page - server component
+ */
+export default async function CollectionDetailPage({ params }: CollectionPageProps) {
+ const { username, slug } = await params;
+ const cleanUsername = username.startsWith('@') ? username.slice(1) : username;
+ const collection = await getCollection(username, slug);
- if (data.success) {
- setCollection(data.data);
- } else {
- setError(data.error?.message || 'Failed to load collection');
- }
- } catch {
- setError('Failed to load collection');
- } finally {
- setIsLoading(false);
- }
- }, [username, slug]);
-
- useEffect(() => {
- fetchCollection();
- }, [fetchCollection]);
-
- if (error === 'not_found') {
+ if (!collection) {
notFound();
}
- return (
-
-
-
-
- {isLoading ? (
-
-
-
- ) : error ? (
-
- ) : collection ? (
-
- {/* Collection Header */}
-
-
-
{collection.name}
- {collection.description && (
-
{collection.description}
- )}
-
-
- by @{collection.createdBy.username}
-
- {collection.forkedFrom && (
-
- )}
-
-
-
-
-
-
-
-
- {/* Stats */}
-
-
-
- {collection.toolCount} tools
-
-
-
- {collection.likeCount} likes
-
- {collection.forkCount > 0 && (
-
-
- {collection.forkCount} forks
-
- )}
-
-
- {/* MCP Server URLs - Available to everyone (non-owners must provide their own credentials) */}
-
-
- {/* Tools */}
- {collection.tools.length > 0 ? (
-
- Tools in Collection
-
- {collection.tools.map((ct) => (
-
-
-
-
{ct.tool.name}
-
- {ct.tool.description}
-
- {ct.note && (
-
- Note: {ct.note}
-
- )}
-
-
- {ct.tool.package.category}
-
-
- {ct.tool.package.npmPackageName}
-
-
-
-
-
- {ct.tool.likeCount}
-
-
-
- ))}
-
-
- ) : (
-
-
-
This collection is empty.
-
- )}
-
- {/* Scenarios Section */}
- {collection.tools.length > 0 && (
-
- )}
-
- {/* Use Cases Section - at the bottom */}
- {collection.tools.length > 0 && (
-
- )}
-
- ) : null}
-
-
- );
+ return ;
}
diff --git a/apps/web/src/components/ShareButton.tsx b/apps/web/src/components/ShareButton.tsx
new file mode 100644
index 0000000..7794048
--- /dev/null
+++ b/apps/web/src/components/ShareButton.tsx
@@ -0,0 +1,112 @@
+'use client';
+
+import { Button } from '@tpmjs/ui/Button/Button';
+import { Icon } from '@tpmjs/ui/Icon/Icon';
+import { useState } from 'react';
+
+interface ShareButtonProps {
+ title: string;
+ text?: string;
+ url?: string;
+ hashtags?: string[];
+ via?: string;
+ variant?: 'default' | 'twitter' | 'copy';
+ size?: 'sm' | 'md' | 'lg';
+ className?: string;
+}
+
+export function ShareButton({
+ title,
+ text,
+ url,
+ hashtags = [],
+ via = 'tpmjs_registry',
+ variant = 'twitter',
+ size = 'sm',
+ className,
+}: ShareButtonProps) {
+ const [copied, setCopied] = useState(false);
+
+ const getShareUrl = () => {
+ if (typeof window === 'undefined') return '';
+ return url || window.location.href;
+ };
+
+ const handleTwitterShare = () => {
+ const shareUrl = getShareUrl();
+ const tweetText = text || title;
+ const hashtagsParam = hashtags.length > 0 ? `&hashtags=${hashtags.join(',')}` : '';
+ const viaParam = via ? `&via=${via}` : '';
+
+ const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(shareUrl)}${hashtagsParam}${viaParam}`;
+
+ window.open(twitterUrl, '_blank', 'width=550,height=420');
+ };
+
+ const handleCopyLink = async () => {
+ const shareUrl = getShareUrl();
+ await navigator.clipboard.writeText(shareUrl);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ };
+
+ if (variant === 'copy') {
+ return (
+
+
+ {copied ? 'Copied!' : 'Copy Link'}
+
+ );
+ }
+
+ if (variant === 'twitter') {
+ return (
+
+
+ X/Twitter
+
+
+ Tweet
+
+ );
+ }
+
+ // Default: show both options
+ return (
+
+
+
+ X/Twitter
+
+
+
+
+
+
+
+ );
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cc62e7b..e4604ca 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -454,9 +454,6 @@ importers:
'@oclif/plugin-plugins':
specifier: ^5.4.36
version: 5.4.54
- '@tpmjs/types':
- specifier: workspace:*
- version: link:../types
cli-table3:
specifier: ^0.6.5
version: 0.6.5
@@ -1516,6 +1513,25 @@ importers:
specifier: ^5.9.3
version: 5.9.3
+ packages/tools/official/e2b:
+ dependencies:
+ '@e2b/code-interpreter':
+ specifier: ^1.0.4
+ version: 1.5.1
+ ai:
+ specifier: 6.0.23
+ version: 6.0.23(zod@4.3.5)
+ devDependencies:
+ '@tpmjs/tsconfig':
+ specifier: workspace:*
+ version: link:../../../config/tsconfig
+ tsup:
+ specifier: ^8.5.1
+ version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
+ typescript:
+ specifier: ^5.9.3
+ version: 5.9.3
+
packages/tools/official/effect-size-suite:
dependencies:
ai:
@@ -4474,6 +4490,9 @@ packages:
'@braintree/sanitize-url@7.1.1':
resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==}
+ '@bufbuild/protobuf@2.10.2':
+ resolution: {integrity: sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==}
+
'@changesets/apply-release-plan@7.0.14':
resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==}
@@ -4566,6 +4585,17 @@ packages:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
+ '@connectrpc/connect-web@2.0.0-rc.3':
+ resolution: {integrity: sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw==}
+ peerDependencies:
+ '@bufbuild/protobuf': ^2.2.0
+ '@connectrpc/connect': 2.0.0-rc.3
+
+ '@connectrpc/connect@2.0.0-rc.3':
+ resolution: {integrity: sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ==}
+ peerDependencies:
+ '@bufbuild/protobuf': ^2.2.0
+
'@csstools/color-helpers@5.1.0':
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
engines: {node: '>=18'}
@@ -4604,6 +4634,10 @@ packages:
'@drizzle-team/brocli@0.10.2':
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
+ '@e2b/code-interpreter@1.5.1':
+ resolution: {integrity: sha512-mkyKjAW2KN5Yt0R1I+1lbH3lo+W/g/1+C2lnwlitXk5wqi/g94SEO41XKdmDf5WWpKG3mnxWDR5d6S/lyjmMEw==}
+ engines: {node: '>=18'}
+
'@electric-sql/pglite-socket@0.0.6':
resolution: {integrity: sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==}
hasBin: true
@@ -7747,6 +7781,9 @@ packages:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
+ compare-versions@6.1.1:
+ resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
+
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -8319,6 +8356,10 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
+ e2b@1.13.2:
+ resolution: {integrity: sha512-m8acE/MzMAJo1A57DakR2X1Sl5Mt1tcQO2aJfygNaQHLXby/4xsjF0UeJUB70jF7xntiR41pAMbZEHnkzrT9tw==}
+ engines: {node: '>=18'}
+
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@@ -10518,6 +10559,12 @@ packages:
zod:
optional: true
+ openapi-fetch@0.9.8:
+ resolution: {integrity: sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg==}
+
+ openapi-typescript-helpers@0.0.8:
+ resolution: {integrity: sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g==}
+
opener@1.5.2:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true
@@ -10754,6 +10801,9 @@ packages:
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
+ platform@1.3.6:
+ resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
+
points-on-curve@0.2.0:
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
@@ -13399,6 +13449,8 @@ snapshots:
'@braintree/sanitize-url@7.1.1': {}
+ '@bufbuild/protobuf@2.10.2': {}
+
'@changesets/apply-release-plan@7.0.14':
dependencies:
'@changesets/config': 3.1.2
@@ -13593,6 +13645,15 @@ snapshots:
'@colors/colors@1.5.0':
optional: true
+ '@connectrpc/connect-web@2.0.0-rc.3(@bufbuild/protobuf@2.10.2)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.10.2))':
+ dependencies:
+ '@bufbuild/protobuf': 2.10.2
+ '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.10.2)
+
+ '@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.10.2)':
+ dependencies:
+ '@bufbuild/protobuf': 2.10.2
+
'@csstools/color-helpers@5.1.0': {}
'@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
@@ -13619,6 +13680,10 @@ snapshots:
'@drizzle-team/brocli@0.10.2': {}
+ '@e2b/code-interpreter@1.5.1':
+ dependencies:
+ e2b: 1.13.2
+
'@electric-sql/pglite-socket@0.0.6(@electric-sql/pglite@0.3.2)':
dependencies:
'@electric-sql/pglite': 0.3.2
@@ -16822,6 +16887,8 @@ snapshots:
commander@8.3.0: {}
+ compare-versions@6.1.1: {}
+
concat-map@0.0.1: {}
conf@13.1.0:
@@ -17353,6 +17420,15 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
+ e2b@1.13.2:
+ dependencies:
+ '@bufbuild/protobuf': 2.10.2
+ '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.10.2)
+ '@connectrpc/connect-web': 2.0.0-rc.3(@bufbuild/protobuf@2.10.2)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.10.2))
+ compare-versions: 6.1.1
+ openapi-fetch: 0.9.8
+ platform: 1.3.6
+
eastasianwidth@0.2.0: {}
ecc-jsbn@0.1.2:
@@ -20093,6 +20169,12 @@ snapshots:
ws: 8.19.0
zod: 4.3.5
+ openapi-fetch@0.9.8:
+ dependencies:
+ openapi-typescript-helpers: 0.0.8
+
+ openapi-typescript-helpers@0.0.8: {}
+
opener@1.5.2: {}
optimist@0.6.1:
@@ -20356,6 +20438,8 @@ snapshots:
exsolve: 1.0.8
pathe: 2.0.3
+ platform@1.3.6: {}
+
points-on-curve@0.2.0: {}
points-on-path@0.2.1: