diff --git a/apps/website/app/api/og/[...path]/route.tsx b/apps/website/app/api/og/[...path]/route.tsx new file mode 100644 index 00000000..2e648959 --- /dev/null +++ b/apps/website/app/api/og/[...path]/route.tsx @@ -0,0 +1,308 @@ +import { ImageResponse } from "@vercel/og"; +import { getBlogMetadata } from "@/utils/blog"; +import { getDocsMetadata } from "@/utils/docs"; +import { formatDate } from "@/utils/format-date"; +import type { NextRequest } from "next/server"; +import { getBaseUrl } from "@/lib/base-url"; + +const OG_WIDTH = 1200; +const OG_HEIGHT = 630; + +const geistRegular = fetch( + new URL( + "https://cdn.jsdelivr.net/fontsource/fonts/geist-sans@latest/latin-400-normal.woff" + ) +).then((res) => res.arrayBuffer()); + +const XmcpLogo = () => ( + + + + + + +); + +interface OgContent { + readonly title: string; + readonly description: string; + readonly summary: string | undefined; + readonly date: string | undefined; +} + +type ResolveResult = + | { readonly ok: true; readonly content: OgContent } + | { readonly ok: false; readonly response: Response }; + +function resolveOgContent( + type: string, + rest: string[], + baseUrl: string +): ResolveResult { + switch (type) { + case "blog": { + const slug = rest.join("/"); + if (!slug) { + return { + ok: true, + content: { + title: "Blog - xmcp", + description: "Latest updates, guides, and insights about xmcp.", + summary: undefined, + date: undefined, + }, + }; + } + + const meta = getBlogMetadata(slug, baseUrl); + if (!meta) { + return { + ok: false, + response: new Response("Blog post not found", { status: 404 }), + }; + } + + return { + ok: true, + content: { + title: meta.title, + description: meta.description, + summary: meta.summary, + date: meta.date, + }, + }; + } + + case "docs": { + const slug = rest.length === 0 ? undefined : rest; + if (!slug) { + return { + ok: true, + content: { + title: "xmcp Documentation", + description: "The framework for building & shipping MCP servers.", + summary: undefined, + date: undefined, + }, + }; + } + + const meta = getDocsMetadata(slug, baseUrl); + if (!meta) { + return { + ok: false, + response: new Response("Documentation page not found", { + status: 404, + }), + }; + } + + return { + ok: true, + content: { + title: meta.title, + description: meta.description, + summary: meta.summary, + date: undefined, + }, + }; + } + + case "examples": { + return { + ok: true, + content: { + title: "Shaping the future of MCP tooling", + description: + "xmcp now supports building compatible UI resources and tools with the OpenAI Apps SDK, out of the box.", + summary: undefined, + date: "2025-12-11", + }, + }; + } + + default: + return { + ok: false, + response: new Response("Invalid content type", { status: 400 }), + }; + } +} + +export async function GET( + _request: NextRequest, + props: { params: Promise<{ path: string[] }> } +): Promise { + try { + const params = await props.params; + const path = Array.isArray(params.path) ? params.path : [params.path]; + + if (path.length === 0) { + return new Response("Invalid path", { status: 400 }); + } + + const [type, ...rest] = path; + const baseUrl = getBaseUrl(); + + const result = resolveOgContent(type, rest, baseUrl); + if (!result.ok) { + return result.response; + } + + const { title, description, summary, date } = result.content; + + return new ImageResponse( +
+ {/* Background image */} + Background + {/* Noise overlay */} + Noise + {/* Logo - top right */} +
+ +
+ {/* Date tag - top left */} + {date && ( +
+ {formatDate(date)} +
+ )} + {/* Text content - bottom left */} +
+

+ {title} +

+ {(summary || description) && ( +

+ {summary ?? description} +

+ )} +
+
, + { + width: OG_WIDTH, + height: OG_HEIGHT, + fonts: [ + { + name: "Geist", + data: await geistRegular, + style: "normal", + weight: 400, + }, + ], + } + ); + } catch (error) { + console.error("Error generating OG image:", error); + return new Response("Error generating image", { status: 500 }); + } +} diff --git a/apps/website/app/blog/[...slug]/page.tsx b/apps/website/app/blog/[...slug]/page.tsx index 14ee808e..112d490d 100644 --- a/apps/website/app/blog/[...slug]/page.tsx +++ b/apps/website/app/blog/[...slug]/page.tsx @@ -6,6 +6,8 @@ import type { Metadata } from "next"; import { createRelativeLink } from "fumadocs-ui/mdx"; import { CodeBlock } from "@/components/codeblock"; import { BlogPage } from "@/components/layout/blog"; +import { getBlogMetadata } from "@/utils/blog"; +import { getBaseUrl } from "@/lib/base-url"; export default async function Page(props: PageProps<"/blog/[...slug]">) { const params = await props.params; @@ -43,14 +45,37 @@ export async function generateMetadata( props: PageProps<"/blog/[...slug]"> ): Promise { const params = await props.params; - const page = blogSource.getPage(params.slug); - if (!page) notFound(); + const slug = Array.isArray(params.slug) ? params.slug.join("/") : params.slug; + const baseUrl = getBaseUrl(); + const meta = getBlogMetadata(slug, baseUrl); + if (!meta) notFound(); + + const { title, description, ogImageUrl } = meta; return { - title: page.data.title + " | xmcp Blog", - description: page.data.description, - /* openGraph: { - images: getPageImage(page).url, - }, */ + title, + description, + openGraph: { + title, + description, + siteName: "xmcp", + type: "article", + locale: "en_US", + images: { + url: ogImageUrl, + width: 1200, + height: 630, + }, + }, + twitter: { + card: "summary_large_image", + title, + description, + images: { + url: ogImageUrl, + width: 1200, + height: 630, + }, + }, }; } diff --git a/apps/website/app/docs/[[...slug]]/page.tsx b/apps/website/app/docs/[[...slug]]/page.tsx index 3e7aad48..25fb201c 100644 --- a/apps/website/app/docs/[[...slug]]/page.tsx +++ b/apps/website/app/docs/[[...slug]]/page.tsx @@ -11,6 +11,8 @@ import type { Metadata } from "next"; import { createRelativeLink } from "fumadocs-ui/mdx"; import { PageActions } from "@/components/page-actions"; import { CodeBlock } from "@/components/codeblock"; +import { getBaseUrl } from "@/lib/base-url"; +import { getDocsMetadata } from "@/utils/docs"; export default async function Page(props: PageProps<"/docs/[[...slug]]">) { const params = await props.params; @@ -51,14 +53,37 @@ export async function generateMetadata( props: PageProps<"/docs/[[...slug]]"> ): Promise { const params = await props.params; - const page = source.getPage(params.slug); - if (!page) notFound(); + const baseUrl = getBaseUrl(); + const meta = getDocsMetadata(params.slug, baseUrl); + if (!meta) notFound(); + + const title = meta.title + " | xmcp Documentation"; + const description = meta.summary ?? meta.description; return { - title: page.data.title + " | xmcp Documentation", - description: page.data.description, - /* openGraph: { - images: getPageImage(page).url, - }, */ + title, + description, + openGraph: { + title, + description, + siteName: "xmcp", + type: "article", + locale: "en_US", + images: { + url: meta.ogImageUrl, + width: 1200, + height: 630, + }, + }, + twitter: { + card: "summary_large_image", + title, + description, + images: { + url: meta.ogImageUrl, + width: 1200, + height: 630, + }, + }, }; } diff --git a/apps/website/app/examples/page.tsx b/apps/website/app/examples/page.tsx index 20e402cc..b7f1f42f 100644 --- a/apps/website/app/examples/page.tsx +++ b/apps/website/app/examples/page.tsx @@ -1,14 +1,42 @@ import { Metadata } from "next"; import { ExampleCardsList } from "../../components/examples/cards/list"; +import { getBaseUrl } from "@/lib/base-url"; export const dynamic = "force-static"; +const baseUrl = getBaseUrl(); + export const metadata: Metadata = { title: "Examples - xmcp", description: "Explore examples and templates to get started with xmcp. Learn from real-world implementations and best practices.", alternates: { - canonical: "https://xmcp.dev/examples", + canonical: `${baseUrl}/examples`, + }, + openGraph: { + title: "Examples & templates - xmcp", + description: + "Explore examples and templates to get started with xmcp. Learn from real-world implementations and best practices.", + siteName: "xmcp", + type: "website", + locale: "en_US", + url: `${baseUrl}/examples`, + images: { + url: `${baseUrl}/api/og/examples`, + width: 1200, + height: 630, + }, + }, + twitter: { + card: "summary_large_image", + title: "Examples & templates - xmcp", + description: + "Explore examples and templates to get started with xmcp. Learn from real-world implementations and best practices.", + images: { + url: `${baseUrl}/api/og/examples`, + width: 1200, + height: 630, + }, }, }; diff --git a/apps/website/basehub-types.d.ts b/apps/website/basehub-types.d.ts index 1950de5d..416cce5b 100644 --- a/apps/website/basehub-types.d.ts +++ b/apps/website/basehub-types.d.ts @@ -58,10 +58,62 @@ export interface Scalars { Int: number, JSON: any, String: string, + bshb_event__1212762555: `bshb_event__1212762555:${string}`, + schema_bshb_event__1212762555: {repositoryUrl?: string;connection: string;logo?: File;projectName: string;tagline: string;contactEmail: string;}, } export type AnalyticsKeyScope = 'query' | 'send' +export interface ArticleComponent { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + /** Array of search highlight information with field names and HTML markup */ + _highlight: (SearchHighlight[] | null) + _id: Scalars['String'] + _idPath: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + body: Body + ogImage: BlockOgImage + __typename: 'ArticleComponent' +} + +export type ArticleComponentOrderByEnum = '_sys_createdAt__ASC' | '_sys_createdAt__DESC' | '_sys_hash__ASC' | '_sys_hash__DESC' | '_sys_id__ASC' | '_sys_id__DESC' | '_sys_lastModifiedAt__ASC' | '_sys_lastModifiedAt__DESC' | '_sys_slug__ASC' | '_sys_slug__DESC' | '_sys_title__ASC' | '_sys_title__DESC' | 'body__ASC' | 'body__DESC' | 'ogImage__ASC' | 'ogImage__DESC' + +export interface Articles { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _meta: ListMeta + /** The key used to search from the frontend. */ + _searchKey: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item: (ArticleComponent | null) + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items: ArticleComponent[] + __typename: 'Articles' +} + +export interface Assets { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + glLogoMatcap: BlockImage + __typename: 'Assets' +} + export interface BaseRichTextJson { blocks: Scalars['String'] content: Scalars['BSHBRichTextContentSchema'] @@ -99,7 +151,7 @@ export interface BlockColor { __typename: 'BlockColor' } -export type BlockDocument = (_AgentStart) & { __isUnion?: true } +export type BlockDocument = (ArticleComponent | Articles | Assets | Collection | Documentation | McpTemplateComponent | Mcps | Showcase | SidebarTree | SidebarTreeComponent | _AgentStart | articleComponent_AsList | mcpTemplateComponent_AsList | sidebarTreeComponent_AsList) & { __isUnion?: true } export interface BlockDocumentSys { apiNamePath: Scalars['String'] @@ -158,20 +210,7 @@ export interface BlockImage { __typename: 'BlockImage' } -export interface BlockList { - _analyticsKey: Scalars['String'] - _dashboardUrl: Scalars['String'] - _id: Scalars['String'] - _idPath: Scalars['String'] - _meta: ListMeta - /** The key used to search from the frontend. */ - _searchKey: Scalars['String'] - _slug: Scalars['String'] - _slugPath: Scalars['String'] - _sys: BlockDocumentSys - _title: Scalars['String'] - __typename: string -} +export type BlockList = (Articles | Collection | Mcps | SidebarTree | articleComponent_AsList | mcpTemplateComponent_AsList | sidebarTreeComponent_AsList) & { __isUnion?: true } export interface BlockOgImage { height: Scalars['Int'] @@ -182,14 +221,7 @@ export interface BlockOgImage { /** Rich text block */ -export interface BlockRichText { - html: Scalars['String'] - json: RichTextJson - markdown: Scalars['String'] - plainText: Scalars['String'] - readingTime: Scalars['Int'] - __typename: string -} +export type BlockRichText = (Body) & { __isUnion?: true } export interface BlockVideo { aspectRatio: Scalars['String'] @@ -205,6 +237,54 @@ export interface BlockVideo { __typename: 'BlockVideo' } +export interface Body { + html: Scalars['String'] + json: BodyRichText + markdown: Scalars['String'] + plainText: Scalars['String'] + readingTime: Scalars['Int'] + __typename: 'Body' +} + +export interface BodyRichText { + content: Scalars['BSHBRichTextContentSchema'] + toc: Scalars['BSHBRichTextTOCSchema'] + __typename: 'BodyRichText' +} + +export interface Collection { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _meta: ListMeta + /** The key used to search from the frontend. */ + _searchKey: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item: (SidebarTreeComponent | null) + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items: SidebarTreeComponent[] + __typename: 'Collection' +} + +export interface Documentation { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + articles: Articles + sidebarTree: SidebarTree + __typename: 'Documentation' +} + export interface GetUploadSignedURL { signedURL: Scalars['String'] uploadURL: Scalars['String'] @@ -219,6 +299,47 @@ export interface ListMeta { __typename: 'ListMeta' } +export interface McpTemplateComponent { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + /** Array of search highlight information with field names and HTML markup */ + _highlight: (SearchHighlight[] | null) + _id: Scalars['String'] + _idPath: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + connection: Scalars['String'] + logo: BlockImage + name: Scalars['String'] + repositoryUrl: (Scalars['String'] | null) + tag: (Scalars['String'] | null) + tagline: Scalars['String'] + __typename: 'McpTemplateComponent' +} + +export type McpTemplateComponentOrderByEnum = '_sys_createdAt__ASC' | '_sys_createdAt__DESC' | '_sys_hash__ASC' | '_sys_hash__DESC' | '_sys_id__ASC' | '_sys_id__DESC' | '_sys_lastModifiedAt__ASC' | '_sys_lastModifiedAt__DESC' | '_sys_slug__ASC' | '_sys_slug__DESC' | '_sys_title__ASC' | '_sys_title__DESC' | 'connection__ASC' | 'connection__DESC' | 'logo__ASC' | 'logo__DESC' | 'name__ASC' | 'name__DESC' | 'repositoryUrl__ASC' | 'repositoryUrl__DESC' | 'tag__ASC' | 'tag__DESC' | 'tagline__ASC' | 'tagline__DESC' + +export interface Mcps { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _meta: ListMeta + /** The key used to search from the frontend. */ + _searchKey: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item: (McpTemplateComponent | null) + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items: McpTemplateComponent[] + __typename: 'Mcps' +} + export type MediaBlock = (BlockAudio | BlockFile | BlockImage | BlockVideo) & { __isUnion?: true } export type MediaBlockUnion = (BlockAudio | BlockFile | BlockImage | BlockVideo) & { __isUnion?: true } @@ -260,11 +381,16 @@ export interface Query { _agent: (_AgentStart | null) /** Query across the custom AI agents in the repository. */ _agents: _agents + /** Query across all of the instances of a component. Pass in filters and sorts if you want, and get each instance via the `items` key. */ + _componentInstances: _components /** The diff between the current branch and the head commit. */ _diff: Scalars['JSON'] /** The structure of the repository. Used by START. */ _structure: Scalars['JSON'] _sys: RepoSys + assets: Assets + documentation: Documentation + showcase: Showcase __typename: 'Query' } @@ -280,7 +406,7 @@ export interface RepoSys { __typename: 'RepoSys' } -export type RichTextJson = (BaseRichTextJson) & { __isUnion?: true } +export type RichTextJson = (BaseRichTextJson | BodyRichText) & { __isUnion?: true } export interface SearchHighlight { /** The field/path that was matched (e.g., "title", "body.content") */ @@ -290,6 +416,66 @@ export interface SearchHighlight { __typename: 'SearchHighlight' } +export interface Showcase { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + mcps: Mcps + submissions: Submissions + __typename: 'Showcase' +} + +export interface SidebarTree { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _meta: ListMeta + /** The key used to search from the frontend. */ + _searchKey: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item: (SidebarTreeComponent | null) + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items: SidebarTreeComponent[] + __typename: 'SidebarTree' +} + +export interface SidebarTreeComponent { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + /** Array of search highlight information with field names and HTML markup */ + _highlight: (SearchHighlight[] | null) + _id: Scalars['String'] + _idPath: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + collection: Collection + target: ArticleComponent + __typename: 'SidebarTreeComponent' +} + +export type SidebarTreeComponentOrderByEnum = '_sys_createdAt__ASC' | '_sys_createdAt__DESC' | '_sys_hash__ASC' | '_sys_hash__DESC' | '_sys_id__ASC' | '_sys_id__DESC' | '_sys_lastModifiedAt__ASC' | '_sys_lastModifiedAt__DESC' | '_sys_slug__ASC' | '_sys_slug__DESC' | '_sys_title__ASC' | '_sys_title__DESC' | 'collection__ASC' | 'collection__DESC' | 'target__ASC' | 'target__DESC' | 'untitled__ASC' | 'untitled__DESC' + +export interface Submissions { + /** The `adminKey` gives clients the ability to query, delete and update this block's data. **It's not meant to be exposed to the public.** */ + adminKey: Scalars['bshb_event__1212762555'] + /** The `ingestKey` gives clients the ability to send new events to this block. Generally, it's safe to expose it to the public. */ + ingestKey: Scalars['bshb_event__1212762555'] + schema: Scalars['BSHBEventSchema'] + __typename: 'Submissions' +} + export interface TransactionStatus { /** Duration in milliseconds. */ duration: (Scalars['Int'] | null) @@ -411,6 +597,147 @@ export interface _agents { __typename: '_agents' } +export interface _components { + article: articleComponent_AsList + mcpTemplate: mcpTemplateComponent_AsList + sidebarTree: sidebarTreeComponent_AsList + __typename: '_components' +} + +export interface articleComponent_AsList { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _meta: ListMeta + /** The key used to search from the frontend. */ + _searchKey: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item: (ArticleComponent | null) + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items: ArticleComponent[] + __typename: 'articleComponent_AsList' +} + +export interface mcpTemplateComponent_AsList { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _meta: ListMeta + /** The key used to search from the frontend. */ + _searchKey: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item: (McpTemplateComponent | null) + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items: McpTemplateComponent[] + __typename: 'mcpTemplateComponent_AsList' +} + +export interface sidebarTreeComponent_AsList { + _analyticsKey: Scalars['String'] + _dashboardUrl: Scalars['String'] + _id: Scalars['String'] + _idPath: Scalars['String'] + _meta: ListMeta + /** The key used to search from the frontend. */ + _searchKey: Scalars['String'] + _slug: Scalars['String'] + _slugPath: Scalars['String'] + _sys: BlockDocumentSys + _title: Scalars['String'] + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item: (SidebarTreeComponent | null) + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items: SidebarTreeComponent[] + __typename: 'sidebarTreeComponent_AsList' +} + +export interface ArticleComponentGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + /** Array of search highlight information with field names and HTML markup */ + _highlight?: SearchHighlightGenqlSelection + _id?: boolean | number + _idPath?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + body?: BodyGenqlSelection + ogImage?: BlockOgImageGenqlSelection + __typename?: boolean | number + __fragmentOn?: "ArticleComponent" +} + +export interface ArticleComponentFilterInput {AND?: (ArticleComponentFilterInput | null),OR?: (ArticleComponentFilterInput | null),_id?: (StringFilter | null),_slug?: (StringFilter | null),_sys_apiNamePath?: (StringFilter | null),_sys_createdAt?: (DateFilter | null),_sys_hash?: (StringFilter | null),_sys_id?: (StringFilter | null),_sys_idPath?: (StringFilter | null),_sys_lastModifiedAt?: (DateFilter | null),_sys_slug?: (StringFilter | null),_sys_slugPath?: (StringFilter | null),_sys_title?: (StringFilter | null),_title?: (StringFilter | null)} + +export interface ArticleComponentSearchInput { +/** Searchable fields for query */ +by?: (Scalars['String'][] | null), +/** Search query */ +q?: (Scalars['String'] | null)} + +export interface ArticlesGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _meta?: ListMetaGenqlSelection + /** The key used to search from the frontend. */ + _searchKey?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item?: ArticleComponentGenqlSelection + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items?: ArticleComponentGenqlSelection + __typename?: boolean | number + __fragmentOn?: "Articles" +} + +export interface AssetsGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + glLogoMatcap?: BlockImageGenqlSelection + __typename?: boolean | number + __fragmentOn?: "Assets" +} + export interface BaseRichTextJsonGenqlSelection{ blocks?: boolean | number content?: boolean | number @@ -469,7 +796,20 @@ export interface BlockDocumentGenqlSelection{ _slugPath?: boolean | number _sys?: BlockDocumentSysGenqlSelection _title?: boolean | number + on_ArticleComponent?: ArticleComponentGenqlSelection + on_Articles?: ArticlesGenqlSelection + on_Assets?: AssetsGenqlSelection + on_Collection?: CollectionGenqlSelection + on_Documentation?: DocumentationGenqlSelection + on_McpTemplateComponent?: McpTemplateComponentGenqlSelection + on_Mcps?: McpsGenqlSelection + on_Showcase?: ShowcaseGenqlSelection + on_SidebarTree?: SidebarTreeGenqlSelection + on_SidebarTreeComponent?: SidebarTreeComponentGenqlSelection on__AgentStart?: _AgentStartGenqlSelection + on_articleComponent_AsList?: articleComponent_AsListGenqlSelection + on_mcpTemplateComponent_AsList?: mcpTemplateComponent_AsListGenqlSelection + on_sidebarTreeComponent_AsList?: sidebarTreeComponent_AsListGenqlSelection __typename?: boolean | number __fragmentOn?: "BlockDocument" } @@ -552,6 +892,13 @@ export interface BlockListGenqlSelection{ _slugPath?: boolean | number _sys?: BlockDocumentSysGenqlSelection _title?: boolean | number + on_Articles?: ArticlesGenqlSelection + on_Collection?: CollectionGenqlSelection + on_Mcps?: McpsGenqlSelection + on_SidebarTree?: SidebarTreeGenqlSelection + on_articleComponent_AsList?: articleComponent_AsListGenqlSelection + on_mcpTemplateComponent_AsList?: mcpTemplateComponent_AsListGenqlSelection + on_sidebarTreeComponent_AsList?: sidebarTreeComponent_AsListGenqlSelection __typename?: boolean | number __fragmentOn?: "BlockList" } @@ -578,6 +925,7 @@ export interface BlockRichTextGenqlSelection{ readingTime?: { __args: { /** Words per minute, defaults to average 183wpm */ wpm?: (Scalars['Int'] | null)} } | boolean | number + on_Body?: BodyGenqlSelection __typename?: boolean | number __fragmentOn?: "BlockRichText" } @@ -597,8 +945,98 @@ export interface BlockVideoGenqlSelection{ __fragmentOn?: "BlockVideo" } +export interface BodyGenqlSelection{ + html?: { __args: { + /** It automatically generates a unique id for each heading present in the HTML. Enabled by default. */ + slugs?: (Scalars['Boolean'] | null), + /** Inserts a table of contents at the beginning of the HTML. */ + toc?: (Scalars['Boolean'] | null)} } | boolean | number + json?: BodyRichTextGenqlSelection + markdown?: boolean | number + plainText?: boolean | number + readingTime?: { __args: { + /** Words per minute, defaults to average 183wpm */ + wpm?: (Scalars['Int'] | null)} } | boolean | number + __typename?: boolean | number + __fragmentOn?: "Body" +} + +export interface BodyRichTextGenqlSelection{ + content?: boolean | number + toc?: boolean | number + __typename?: boolean | number + __fragmentOn?: "BodyRichText" +} + +export interface CollectionGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _meta?: ListMetaGenqlSelection + /** The key used to search from the frontend. */ + _searchKey?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item?: SidebarTreeComponentGenqlSelection + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items?: SidebarTreeComponentGenqlSelection + __typename?: boolean | number + __fragmentOn?: "Collection" +} + export interface DateFilter {eq?: (Scalars['DateTime'] | null),isAfter?: (Scalars['DateTime'] | null),isBefore?: (Scalars['DateTime'] | null),isNull?: (Scalars['Boolean'] | null),neq?: (Scalars['DateTime'] | null),onOrAfter?: (Scalars['DateTime'] | null),onOrBefore?: (Scalars['DateTime'] | null)} +export interface DocumentationGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + articles?: (ArticlesGenqlSelection & { __args?: { + /** Filter by a field. */ + filter?: (ArticleComponentFilterInput | null), + /** Limit the number of items returned. Defaults to 500. */ + first?: (Scalars['Int'] | null), + /** Order by a field. */ + orderBy?: (ArticleComponentOrderByEnum | null), + /** Search configuration */ + search?: (ArticleComponentSearchInput | null), + /** Skip the first n items. */ + skip?: (Scalars['Int'] | null)} }) + sidebarTree?: (SidebarTreeGenqlSelection & { __args?: { + /** Filter by a field. */ + filter?: (SidebarTreeComponentFilterInput | null), + /** Limit the number of items returned. Defaults to 500. */ + first?: (Scalars['Int'] | null), + /** Order by a field. */ + orderBy?: (SidebarTreeComponentOrderByEnum | null), + /** Search configuration */ + search?: (SidebarTreeComponentSearchInput | null), + /** Skip the first n items. */ + skip?: (Scalars['Int'] | null)} }) + __typename?: boolean | number + __fragmentOn?: "Documentation" +} + export interface GetUploadSignedURLGenqlSelection{ signedURL?: boolean | number uploadURL?: boolean | number @@ -617,6 +1055,67 @@ export interface ListMetaGenqlSelection{ __fragmentOn?: "ListMeta" } +export interface McpTemplateComponentGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + /** Array of search highlight information with field names and HTML markup */ + _highlight?: SearchHighlightGenqlSelection + _id?: boolean | number + _idPath?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + connection?: boolean | number + logo?: BlockImageGenqlSelection + name?: boolean | number + repositoryUrl?: boolean | number + tag?: boolean | number + tagline?: boolean | number + __typename?: boolean | number + __fragmentOn?: "McpTemplateComponent" +} + +export interface McpTemplateComponentFilterInput {AND?: (McpTemplateComponentFilterInput | null),OR?: (McpTemplateComponentFilterInput | null),_id?: (StringFilter | null),_slug?: (StringFilter | null),_sys_apiNamePath?: (StringFilter | null),_sys_createdAt?: (DateFilter | null),_sys_hash?: (StringFilter | null),_sys_id?: (StringFilter | null),_sys_idPath?: (StringFilter | null),_sys_lastModifiedAt?: (DateFilter | null),_sys_slug?: (StringFilter | null),_sys_slugPath?: (StringFilter | null),_sys_title?: (StringFilter | null),_title?: (StringFilter | null),connection?: (StringFilter | null),name?: (StringFilter | null),repositoryUrl?: (StringFilter | null),tag?: (StringFilter | null),tagline?: (StringFilter | null)} + +export interface McpTemplateComponentSearchInput { +/** Searchable fields for query */ +by?: (Scalars['String'][] | null), +/** Search query */ +q?: (Scalars['String'] | null)} + +export interface McpsGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _meta?: ListMetaGenqlSelection + /** The key used to search from the frontend. */ + _searchKey?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item?: McpTemplateComponentGenqlSelection + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items?: McpTemplateComponentGenqlSelection + __typename?: boolean | number + __fragmentOn?: "Mcps" +} + export interface MediaBlockGenqlSelection{ fileName?: boolean | number fileSize?: boolean | number @@ -707,6 +1206,8 @@ export interface QueryGenqlSelection{ id: Scalars['String']} }) /** Query across the custom AI agents in the repository. */ _agents?: _agentsGenqlSelection + /** Query across all of the instances of a component. Pass in filters and sorts if you want, and get each instance via the `items` key. */ + _componentInstances?: _componentsGenqlSelection /** The diff between the current branch and the head commit. */ _diff?: { __args: { /** Simplified diff returns only the items array showing statuses. */ @@ -726,6 +1227,9 @@ export interface QueryGenqlSelection{ /** Whether to include type options in the structure. */ withTypeOptions?: (Scalars['Boolean'] | null)} } | boolean | number _sys?: RepoSysGenqlSelection + assets?: AssetsGenqlSelection + documentation?: DocumentationGenqlSelection + showcase?: ShowcaseGenqlSelection __typename?: boolean | number __fragmentOn?: "Query" } @@ -747,6 +1251,7 @@ export interface RichTextJsonGenqlSelection{ content?: boolean | number toc?: boolean | number on_BaseRichTextJson?: BaseRichTextJsonGenqlSelection + on_BodyRichText?: BodyRichTextGenqlSelection __typename?: boolean | number __fragmentOn?: "RichTextJson" } @@ -762,10 +1267,120 @@ export interface SearchHighlightGenqlSelection{ export interface SelectFilter {excludes?: (Scalars['String'] | null),excludesAll?: (Scalars['String'][] | null),includes?: (Scalars['String'] | null),includesAll?: (Scalars['String'][] | null),includesAny?: (Scalars['String'][] | null),isEmpty?: (Scalars['Boolean'] | null)} +export interface ShowcaseGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + mcps?: (McpsGenqlSelection & { __args?: { + /** Filter by a field. */ + filter?: (McpTemplateComponentFilterInput | null), + /** Limit the number of items returned. Defaults to 500. */ + first?: (Scalars['Int'] | null), + /** Order by a field. */ + orderBy?: (McpTemplateComponentOrderByEnum | null), + /** Search configuration */ + search?: (McpTemplateComponentSearchInput | null), + /** Skip the first n items. */ + skip?: (Scalars['Int'] | null)} }) + submissions?: SubmissionsGenqlSelection + __typename?: boolean | number + __fragmentOn?: "Showcase" +} + +export interface SidebarTreeGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _meta?: ListMetaGenqlSelection + /** The key used to search from the frontend. */ + _searchKey?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item?: SidebarTreeComponentGenqlSelection + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items?: SidebarTreeComponentGenqlSelection + __typename?: boolean | number + __fragmentOn?: "SidebarTree" +} + +export interface SidebarTreeComponentGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + /** Array of search highlight information with field names and HTML markup */ + _highlight?: SearchHighlightGenqlSelection + _id?: boolean | number + _idPath?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + collection?: (CollectionGenqlSelection & { __args?: { + /** Filter by a field. */ + filter?: (SidebarTreeComponentFilterInput | null), + /** Limit the number of items returned. Defaults to 500. */ + first?: (Scalars['Int'] | null), + /** Order by a field. */ + orderBy?: (SidebarTreeComponentOrderByEnum | null), + /** Search configuration */ + search?: (SidebarTreeComponentSearchInput | null), + /** Skip the first n items. */ + skip?: (Scalars['Int'] | null)} }) + target?: ArticleComponentGenqlSelection + __typename?: boolean | number + __fragmentOn?: "SidebarTreeComponent" +} + +export interface SidebarTreeComponentFilterInput {AND?: (SidebarTreeComponentFilterInput | null),OR?: (SidebarTreeComponentFilterInput | null),_id?: (StringFilter | null),_slug?: (StringFilter | null),_sys_apiNamePath?: (StringFilter | null),_sys_createdAt?: (DateFilter | null),_sys_hash?: (StringFilter | null),_sys_id?: (StringFilter | null),_sys_idPath?: (StringFilter | null),_sys_lastModifiedAt?: (DateFilter | null),_sys_slug?: (StringFilter | null),_sys_slugPath?: (StringFilter | null),_sys_title?: (StringFilter | null),_title?: (StringFilter | null),collection?: (ListFilter | null),target?: (SidebarTreeComponentFilterInput__target_0___article | null)} + +export interface SidebarTreeComponentFilterInput__target_0___article {_id?: (StringFilter | null),_slug?: (StringFilter | null),_sys_apiNamePath?: (StringFilter | null),_sys_createdAt?: (DateFilter | null),_sys_hash?: (StringFilter | null),_sys_id?: (StringFilter | null),_sys_idPath?: (StringFilter | null),_sys_lastModifiedAt?: (DateFilter | null),_sys_slug?: (StringFilter | null),_sys_slugPath?: (StringFilter | null),_sys_title?: (StringFilter | null),_title?: (StringFilter | null)} + +export interface SidebarTreeComponentSearchInput { +/** Searchable fields for query */ +by?: (Scalars['String'][] | null), +/** Search query */ +q?: (Scalars['String'] | null)} + export interface StringFilter {contains?: (Scalars['String'] | null),endsWith?: (Scalars['String'] | null),eq?: (Scalars['String'] | null),in?: (Scalars['String'][] | null),isNull?: (Scalars['Boolean'] | null),matches?: (StringMatchesFilter | null),notEq?: (Scalars['String'] | null),notIn?: (Scalars['String'][] | null),startsWith?: (Scalars['String'] | null)} export interface StringMatchesFilter {caseSensitive?: (Scalars['Boolean'] | null),pattern: Scalars['String']} +export interface SubmissionsGenqlSelection{ + /** The `adminKey` gives clients the ability to query, delete and update this block's data. **It's not meant to be exposed to the public.** */ + adminKey?: boolean | number + /** The `ingestKey` gives clients the ability to send new events to this block. Generally, it's safe to expose it to the public. */ + ingestKey?: boolean | number + schema?: boolean | number + __typename?: boolean | number + __fragmentOn?: "Submissions" +} + export interface TargetBlock {focus?: (Scalars['Boolean'] | null),id: Scalars['String'],label: Scalars['String']} export interface TransactionStatusGenqlSelection{ @@ -898,7 +1513,135 @@ export interface _agentsGenqlSelection{ __fragmentOn?: "_agents" } +export interface _componentsGenqlSelection{ + article?: (articleComponent_AsListGenqlSelection & { __args?: { + /** Filter by a field. */ + filter?: (ArticleComponentFilterInput | null), + /** Limit the number of items returned. Defaults to 500. */ + first?: (Scalars['Int'] | null), + /** Order by a field. */ + orderBy?: (ArticleComponentOrderByEnum | null), + /** Search configuration */ + search?: (ArticleComponentSearchInput | null), + /** Skip the first n items. */ + skip?: (Scalars['Int'] | null)} }) + mcpTemplate?: (mcpTemplateComponent_AsListGenqlSelection & { __args?: { + /** Filter by a field. */ + filter?: (McpTemplateComponentFilterInput | null), + /** Limit the number of items returned. Defaults to 500. */ + first?: (Scalars['Int'] | null), + /** Order by a field. */ + orderBy?: (McpTemplateComponentOrderByEnum | null), + /** Search configuration */ + search?: (McpTemplateComponentSearchInput | null), + /** Skip the first n items. */ + skip?: (Scalars['Int'] | null)} }) + sidebarTree?: (sidebarTreeComponent_AsListGenqlSelection & { __args?: { + /** Filter by a field. */ + filter?: (SidebarTreeComponentFilterInput | null), + /** Limit the number of items returned. Defaults to 500. */ + first?: (Scalars['Int'] | null), + /** Order by a field. */ + orderBy?: (SidebarTreeComponentOrderByEnum | null), + /** Search configuration */ + search?: (SidebarTreeComponentSearchInput | null), + /** Skip the first n items. */ + skip?: (Scalars['Int'] | null)} }) + __typename?: boolean | number + __fragmentOn?: "_components" +} + +export interface articleComponent_AsListGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _meta?: ListMetaGenqlSelection + /** The key used to search from the frontend. */ + _searchKey?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item?: ArticleComponentGenqlSelection + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items?: ArticleComponentGenqlSelection + __typename?: boolean | number + __fragmentOn?: "articleComponent_AsList" +} + +export interface mcpTemplateComponent_AsListGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _meta?: ListMetaGenqlSelection + /** The key used to search from the frontend. */ + _searchKey?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item?: McpTemplateComponentGenqlSelection + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items?: McpTemplateComponentGenqlSelection + __typename?: boolean | number + __fragmentOn?: "mcpTemplateComponent_AsList" +} + +export interface sidebarTreeComponent_AsListGenqlSelection{ + _analyticsKey?: { __args: { + /** + * The scope of the analytics key. Use `send` for just ingesting data. Use `query` if you need to show an analytics data in your website. + * + * Have in mind, if you expose your `query` analytics key in the frontend, you'll be exposing all of this block's analytics data to the public. This is generally safe, but it might not be in your case. + */ + scope?: (AnalyticsKeyScope | null)} } | boolean | number + _dashboardUrl?: boolean | number + _id?: boolean | number + _idPath?: boolean | number + _meta?: ListMetaGenqlSelection + /** The key used to search from the frontend. */ + _searchKey?: boolean | number + _slug?: boolean | number + _slugPath?: boolean | number + _sys?: BlockDocumentSysGenqlSelection + _title?: boolean | number + /** Returns the first item in the list, or null if the list is empty. Useful when you expect only one result. */ + item?: SidebarTreeComponentGenqlSelection + /** Returns the list of items after filtering and paginating according to the arguments sent by the client. */ + items?: SidebarTreeComponentGenqlSelection + __typename?: boolean | number + __fragmentOn?: "sidebarTreeComponent_AsList" +} + export interface FragmentsMap { + ArticleComponent: { + root: ArticleComponent, + selection: ArticleComponentGenqlSelection, +} + Articles: { + root: Articles, + selection: ArticlesGenqlSelection, +} + Assets: { + root: Assets, + selection: AssetsGenqlSelection, +} BaseRichTextJson: { root: BaseRichTextJson, selection: BaseRichTextJsonGenqlSelection, @@ -946,6 +1689,22 @@ export interface FragmentsMap { BlockVideo: { root: BlockVideo, selection: BlockVideoGenqlSelection, +} + Body: { + root: Body, + selection: BodyGenqlSelection, +} + BodyRichText: { + root: BodyRichText, + selection: BodyRichTextGenqlSelection, +} + Collection: { + root: Collection, + selection: CollectionGenqlSelection, +} + Documentation: { + root: Documentation, + selection: DocumentationGenqlSelection, } GetUploadSignedURL: { root: GetUploadSignedURL, @@ -954,6 +1713,14 @@ export interface FragmentsMap { ListMeta: { root: ListMeta, selection: ListMetaGenqlSelection, +} + McpTemplateComponent: { + root: McpTemplateComponent, + selection: McpTemplateComponentGenqlSelection, +} + Mcps: { + root: Mcps, + selection: McpsGenqlSelection, } MediaBlock: { root: MediaBlock, @@ -978,6 +1745,22 @@ export interface FragmentsMap { SearchHighlight: { root: SearchHighlight, selection: SearchHighlightGenqlSelection, +} + Showcase: { + root: Showcase, + selection: ShowcaseGenqlSelection, +} + SidebarTree: { + root: SidebarTree, + selection: SidebarTreeGenqlSelection, +} + SidebarTreeComponent: { + root: SidebarTreeComponent, + selection: SidebarTreeComponentGenqlSelection, +} + Submissions: { + root: Submissions, + selection: SubmissionsGenqlSelection, } TransactionStatus: { root: TransactionStatus, @@ -1014,5 +1797,21 @@ export interface FragmentsMap { _agents: { root: _agents, selection: _agentsGenqlSelection, +} + _components: { + root: _components, + selection: _componentsGenqlSelection, +} + articleComponent_AsList: { + root: articleComponent_AsList, + selection: articleComponent_AsListGenqlSelection, +} + mcpTemplateComponent_AsList: { + root: mcpTemplateComponent_AsList, + selection: mcpTemplateComponent_AsListGenqlSelection, +} + sidebarTreeComponent_AsList: { + root: sidebarTreeComponent_AsList, + selection: sidebarTreeComponent_AsListGenqlSelection, } } diff --git a/apps/website/lib/base-url.ts b/apps/website/lib/base-url.ts new file mode 100644 index 00000000..46f7a7b5 --- /dev/null +++ b/apps/website/lib/base-url.ts @@ -0,0 +1,15 @@ +export function getBaseUrl(): string { + if (process.env.VERCEL_URL) { + return `https://${process.env.VERCEL_URL}`; + } + + if (process.env.NODE_ENV === "development") { + return "http://localhost:3000"; + } + + return "https://xmcp.dev"; +} + + + + diff --git a/apps/website/package.json b/apps/website/package.json index 00cfa9d0..cf47067a 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -35,6 +35,7 @@ "@types/mdx": "^2.0.13", "@types/three": "^0.178.1", "@vercel/analytics": "^1.6.1", + "@vercel/og": "^0.8.5", "@vercel/speed-insights": "^1.3.1", "ai": "^5.0.108", "basehub": "^9.5.3", diff --git a/apps/website/public/blog/generate.png b/apps/website/public/blog/generate.png new file mode 100644 index 00000000..ced6b186 Binary files /dev/null and b/apps/website/public/blog/generate.png differ diff --git a/apps/website/public/og/bg.png b/apps/website/public/og/bg.png new file mode 100644 index 00000000..e33b7734 Binary files /dev/null and b/apps/website/public/og/bg.png differ diff --git a/apps/website/public/og/blur.png b/apps/website/public/og/blur.png new file mode 100644 index 00000000..fd6fbc3b Binary files /dev/null and b/apps/website/public/og/blur.png differ diff --git a/apps/website/public/og/noise.png b/apps/website/public/og/noise.png new file mode 100644 index 00000000..4498934a Binary files /dev/null and b/apps/website/public/og/noise.png differ diff --git a/apps/website/utils/blog/index.ts b/apps/website/utils/blog/index.ts index fa440afc..8c5d6f66 100644 --- a/apps/website/utils/blog/index.ts +++ b/apps/website/utils/blog/index.ts @@ -4,28 +4,38 @@ import matter from "gray-matter"; export type BlogCategory = "changelog" | "guides"; export interface BlogFrontmatter { - title?: string; - description?: string; - date?: string; - category?: BlogCategory; - order?: number; - featured?: boolean; - previewImage?: string; - [key: string]: unknown; + readonly title?: string; + readonly description?: string; + readonly summary?: string; + readonly date?: string; + readonly category?: BlogCategory; + readonly order?: number; + readonly featured?: boolean; + readonly previewImage?: string; + readonly [key: string]: unknown; } export interface BlogPost { - slug: string; - title: string; - content: string; - data: BlogFrontmatter; - path: string; - order: number; - category: BlogCategory; - date?: string; - description?: string; - featured?: boolean; - previewImage?: string; + readonly slug: string; + readonly title: string; + readonly content: string; + readonly data: BlogFrontmatter; + readonly path: string; + readonly order: number; + readonly category: BlogCategory; + readonly date?: string; + readonly description?: string; + readonly summary?: string; + readonly featured?: boolean; + readonly previewImage?: string; +} + +export interface BlogMetadata { + readonly title: string; + readonly description: string; + readonly summary: string | undefined; + readonly date: string | undefined; + readonly ogImageUrl: string; } export function getAllBlogPosts(): BlogPost[] { @@ -64,6 +74,7 @@ export function getAllBlogPosts(): BlogPost[] { category, date: data.date, description: data.description, + summary: data.summary, featured: data.featured || false, previewImage: data.previewImage, }); @@ -100,6 +111,27 @@ export function getBlogPostBySlug(slug: string): BlogPost | null { return found || null; } +/** + * Get metadata for a blog post, suitable for generating page metadata and OG images. + */ +export function getBlogMetadata( + slug: string, + baseUrl: string +): BlogMetadata | null { + const post = getBlogPostBySlug(slug); + if (!post) return null; + + return { + title: post.title, + description: post.description ?? "", + summary: post.summary, + date: post.date, + ogImageUrl: post.previewImage + ? `${baseUrl}${post.previewImage}` + : `${baseUrl}/api/og/blog/${slug}`, + }; +} + export function getBlogPostsByCategory(category: BlogCategory): BlogPost[] { const posts = getAllBlogPosts(); return posts.filter((post) => post.category === category); diff --git a/apps/website/utils/docs/index.ts b/apps/website/utils/docs/index.ts new file mode 100644 index 00000000..332258f0 --- /dev/null +++ b/apps/website/utils/docs/index.ts @@ -0,0 +1,79 @@ +import fs from "fs"; +import path from "path"; +import matter from "gray-matter"; +import { source } from "@/lib/source"; + +export interface DocsFrontmatter { + readonly title?: string; + readonly description?: string; + readonly summary?: string; + readonly metadataTitle?: string; + readonly publishedAt?: string; + readonly [key: string]: unknown; +} + +export interface DocsPage { + readonly slug: string; + readonly title: string; + readonly content: string; + readonly data: DocsFrontmatter; + readonly path: string; + readonly description?: string; + readonly summary?: string; +} + +export interface DocsMetadata { + readonly title: string; + readonly description: string; + readonly summary: string | undefined; + readonly ogImageUrl: string; +} + +export const DOCS_DIRECTORY = path.join(process.cwd(), "content/docs"); + +/** + * Get a docs page by its slug, reading the MDX file directly with gray-matter + * to extract all frontmatter fields including custom ones like summary. + */ +export function getDocsPageBySlug(slug: string[] | undefined): DocsPage | null { + const slugPath = slug ? slug.join("/") : "index"; + const filePath = path.join(DOCS_DIRECTORY, `${slugPath}.mdx`); + + if (!fs.existsSync(filePath)) { + return null; + } + + const fileContent = fs.readFileSync(filePath, "utf8"); + const { data, content } = matter(fileContent); + + return { + slug: slugPath, + title: data.title || slugPath, + content, + data: data as DocsFrontmatter, + path: filePath, + description: data.description, + summary: data.summary, + }; +} + +/** + * Get metadata for a docs page, suitable for generating page metadata and OG images. + */ +export function getDocsMetadata( + slug: string[] | undefined, + baseUrl: string +): DocsMetadata | null { + const page = source.getPage(slug); + if (!page) return null; + + const docsPage = getDocsPageBySlug(slug); + const slugPath = slug?.join("/") ?? ""; + + return { + title: page.data.title, + description: docsPage?.description ?? page.data.description ?? "", + summary: docsPage?.summary, + ogImageUrl: `${baseUrl}/api/og/docs/${slugPath}`, + }; +} diff --git a/apps/website/utils/format-date.ts b/apps/website/utils/format-date.ts new file mode 100644 index 00000000..aa25be0b --- /dev/null +++ b/apps/website/utils/format-date.ts @@ -0,0 +1,26 @@ +const MONTHS = [ + "JAN", + "FEB", + "MAR", + "APR", + "MAY", + "JUN", + "JUL", + "AUG", + "SEP", + "OCT", + "NOV", + "DEC", +] as const; + +/** + * Formats a date string to "MMM DD.YYYY" format (e.g., "JAN 01.2025") + */ +export function formatDate(dateString: string): string { + const date = new Date(dateString); + const month = MONTHS[date.getMonth()]; + const day = date.getDate().toString().padStart(2, "0"); + const year = date.getFullYear(); + return `${month} ${day}.${year}`; +} + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 439c7359..41254764 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: '@vercel/analytics': specifier: ^1.6.1 version: 1.6.1(next@15.5.7(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) + '@vercel/og': + specifier: ^0.8.5 + version: 0.8.5 '@vercel/speed-insights': specifier: ^1.3.1 version: 1.3.1(next@15.5.7(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) @@ -830,10 +833,6 @@ importers: react-router-dom: specifier: ^7.10.1 version: 7.10.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - optionalDependencies: - pg: - specifier: ^8.16.3 - version: 8.16.3 devDependencies: '@types/better-sqlite3': specifier: ^7.6.13 @@ -886,6 +885,10 @@ importers: xmcp: specifier: workspace:* version: link:../../xmcp + optionalDependencies: + pg: + specifier: ^8.16.3 + version: 8.16.3 packages/plugins/polar: devDependencies: @@ -2969,6 +2972,10 @@ packages: react-native: optional: true + '@resvg/resvg-wasm@2.4.0': + resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==} + engines: {node: '>= 10'} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -3197,6 +3204,11 @@ packages: '@shikijs/vscode-textmate@9.3.1': resolution: {integrity: sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==} + '@shuding/opentype.js@1.4.0-beta.0': + resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} + engines: {node: '>= 8.0.0'} + hasBin: true + '@simplewebauthn/browser@13.2.2': resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==} @@ -3683,6 +3695,10 @@ packages: vue-router: optional: true + '@vercel/og@0.8.5': + resolution: {integrity: sha512-fHqnxfBYcwkamlEgcIzaZqL8IHT09hR7FZL7UdMTdGJyoaBzM/dY6ulO5Swi4ig30FrBJI9I2C+GLV9sb9vexA==} + engines: {node: '>=16'} + '@vercel/oidc@3.0.5': resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} engines: {node: '>= 20'} @@ -3991,6 +4007,10 @@ packages: resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} engines: {node: '>= 0.6.0'} + base64-js@0.0.8: + resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} + engines: {node: '>= 0.4'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4134,6 +4154,9 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + camera-controls@3.1.2: resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==} engines: {node: '>=22.0.0', npm: '>=10.5.1'} @@ -4323,9 +4346,26 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-background-parser@0.1.0: + resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} + + css-box-shadow@1.0.0-3: + resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==} + + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-gradient-parser@0.0.16: + resolution: {integrity: sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==} + engines: {node: '>=16'} + css-line-break@2.1.0: resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -4475,6 +4515,10 @@ packages: electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + emoji-regex-xs@2.0.1: + resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} + engines: {node: '>=10.0.0'} + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -4864,6 +4908,9 @@ packages: fflate@0.6.10: resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==} + fflate@0.7.4: + resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} + fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -5269,6 +5316,10 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hex-rgb@4.3.0: + resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} + engines: {node: '>=6'} + hls.js@1.6.15: resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==} @@ -5761,6 +5812,9 @@ packages: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} + linebreak@1.1.0: + resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -6341,10 +6395,16 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-css-color@0.2.1: + resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} @@ -6906,6 +6966,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + satori@0.16.0: + resolution: {integrity: sha512-ZvHN3ygzZ8FuxjSNB+mKBiF/NIoqHzlBGbD0MJiT+MvSsFOvotnWOhdTjxKzhHRT2wPC1QbhLzx2q/Y83VhfYQ==} + engines: {node: '>=16'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -7113,6 +7177,9 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string.prototype.codepointat@0.2.1: + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -7290,6 +7357,9 @@ packages: through2@0.6.5: resolution: {integrity: sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==} + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -7477,6 +7547,9 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -7733,6 +7806,9 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + zod-to-json-schema@3.25.0: resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} peerDependencies: @@ -9715,6 +9791,8 @@ snapshots: - '@types/react' - immer + '@resvg/resvg-wasm@2.4.0': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.53.3': @@ -9920,6 +9998,11 @@ snapshots: '@shikijs/vscode-textmate@9.3.1': {} + '@shuding/opentype.js@1.4.0-beta.0': + dependencies: + fflate: 0.7.4 + string.prototype.codepointat: 0.2.1 + '@simplewebauthn/browser@13.2.2': {} '@simplewebauthn/server@13.2.2': @@ -10412,6 +10495,11 @@ snapshots: next: 15.5.7(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react: 19.2.1 + '@vercel/og@0.8.5': + dependencies: + '@resvg/resvg-wasm': 2.4.0 + satori: 0.16.0 + '@vercel/oidc@3.0.5': {} '@vercel/speed-insights@1.3.1(next@15.5.7(@opentelemetry/api@1.9.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)': @@ -10753,6 +10841,8 @@ snapshots: base64-arraybuffer@1.0.2: {} + base64-js@0.0.8: {} + base64-js@1.5.1: {} basehub@9.5.3(@babel/runtime@7.28.4)(@types/react@19.2.7)(react@19.2.1): @@ -10949,6 +11039,8 @@ snapshots: camelcase-css@2.0.1: {} + camelize@1.0.1: {} + camera-controls@3.1.2(three@0.178.0): dependencies: three: 0.178.0 @@ -11105,10 +11197,24 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-background-parser@0.1.0: {} + + css-box-shadow@1.0.0-3: {} + + css-color-keywords@1.0.0: {} + + css-gradient-parser@0.0.16: {} + css-line-break@2.1.0: dependencies: utrie: 1.0.2 + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + cssesc@3.0.0: {} csstype@3.2.3: {} @@ -11241,6 +11347,8 @@ snapshots: electron-to-chromium@1.5.267: {} + emoji-regex-xs@2.0.1: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -11480,7 +11588,7 @@ snapshots: eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1)) @@ -11499,8 +11607,8 @@ snapshots: '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1)) @@ -11529,21 +11637,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) - get-tsconfig: 4.13.0 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.15 - unrs-resolver: 1.11.1 - optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) - transitivePeerDependencies: - - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -11555,22 +11648,11 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.1(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -11581,7 +11663,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -11592,36 +11674,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 9.39.1(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -11952,6 +12005,8 @@ snapshots: fflate@0.6.10: {} + fflate@0.7.4: {} + fflate@0.8.2: {} file-entry-cache@8.0.0: @@ -12482,6 +12537,8 @@ snapshots: property-information: 7.1.0 space-separated-tokens: 2.0.2 + hex-rgb@4.3.0: {} + hls.js@1.6.15: {} html-void-elements@3.0.0: {} @@ -12952,6 +13009,11 @@ snapshots: lilconfig@3.1.3: {} + linebreak@1.1.0: + dependencies: + base64-js: 0.0.8 + unicode-trie: 2.0.0 + lines-and-columns@1.2.4: {} listr2@8.3.3: @@ -13790,10 +13852,17 @@ snapshots: package-json-from-dist@1.0.1: {} + pako@0.2.9: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-css-color@0.2.1: + dependencies: + color-name: 1.1.4 + hex-rgb: 4.3.0 + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -14473,6 +14542,20 @@ snapshots: safer-buffer@2.1.2: {} + satori@0.16.0: + dependencies: + '@shuding/opentype.js': 1.4.0-beta.0 + css-background-parser: 0.1.0 + css-box-shadow: 1.0.0-3 + css-gradient-parser: 0.0.16 + css-to-react-native: 3.2.0 + emoji-regex-xs: 2.0.1 + escape-html: 1.0.3 + linebreak: 1.1.0 + parse-css-color: 0.2.1 + postcss-value-parser: 4.2.0 + yoga-layout: 3.2.1 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -14785,6 +14868,8 @@ snapshots: get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 + string.prototype.codepointat@0.2.1: {} + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -14998,6 +15083,8 @@ snapshots: readable-stream: 1.0.34 xtend: 4.0.2 + tiny-inflate@1.0.3: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -15199,6 +15286,11 @@ snapshots: undici-types@7.16.0: {} + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -15512,6 +15604,8 @@ snapshots: yoctocolors-cjs@2.1.3: {} + yoga-layout@3.2.1: {} + zod-to-json-schema@3.25.0(zod@3.25.76): dependencies: zod: 3.25.76