From ac6fdb87e74ae13323c90c96cd2e6e5178dab084 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Mon, 19 Jan 2026 18:10:43 +1000 Subject: [PATCH] feat(web): add tweet button and OG tags to collection page - Add ShareButton component for Twitter/X sharing - Refactor collection page to server component with generateMetadata - Add proper OpenGraph and Twitter Card meta tags for social sharing - Extract client-side logic to CollectionDetailClient component --- .../[slug]/CollectionDetailClient.tsx | 419 ++++++++++++ .../[username]/collections/[slug]/page.tsx | 610 ++++++------------ apps/web/src/components/ShareButton.tsx | 112 ++++ pnpm-lock.yaml | 90 ++- 4 files changed, 798 insertions(+), 433 deletions(-) create mode 100644 apps/web/src/app/(profile)/[username]/collections/[slug]/CollectionDetailClient.tsx create mode 100644 apps/web/src/components/ShareButton.tsx 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} +
+ +
+
+ + {/* SSE Transport */} +
+
+ + SSE Transport + + (streaming) +
+
+
+ {sseUrl} +
+ +
+
+
+ + {/* 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 */} +
+ + + {showConfig && ( +
+ +
+ )} + + {!isOwner && ( + <> + + + {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} -
- -
-
- - {/* SSE Transport */} -
-
- - SSE Transport - - (streaming) -
-
-
- {sseUrl} -
- -
-
-
- - {/* 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 */} -
- - - {showConfig && ( -
- -
- )} - - {!isOwner && ( - <> - - - {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 ? ( -
-

{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 ( + + ); + } + + if (variant === 'twitter') { + return ( + + ); + } + + // Default: show both options + return ( +
+ + +
+ ); +} 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: