diff --git a/apps/docs/content/docs/ai/install-and-connect.mdx b/apps/docs/content/docs/ai/install-and-connect.mdx index 97ad8452..e331b236 100644 --- a/apps/docs/content/docs/ai/install-and-connect.mdx +++ b/apps/docs/content/docs/ai/install-and-connect.mdx @@ -3,16 +3,13 @@ title: Install and Connect description: Install ProofKit and confirm your agent can see your FileMaker file. --- -import { Callout } from "fumadocs-ui/components/callout"; import { Step, Steps } from "fumadocs-ui/components/steps"; The first milestone is simple: your coding agent should be able to communicate with an open FileMaker file through ProofKit. ProofKit installs several pieces that work together: a FileMaker add-on, a FileMaker plug-in, a connector script, an MCP server, and agent integrations. - - Capture the installer screen for macOS, including any step where the agent integrations are selected or confirmed. - + ## Install ProofKit @@ -36,15 +33,15 @@ ProofKit installs several pieces that work together: a FileMaker add-on, a FileM - **Run the Connect to MCP script.** + **Enable the ProofKit plug-in.** - In FileMaker Pro, run the **Connect to MCP** script installed by the add-on. It opens the connector Web Viewer that registers your file with the local MCP server. + In FileMaker Pro, open the Plug-Ins settings and confirm the ProofKit plug-in is enabled. The **Connect to MCP** script needs this plug-in to communicate with the local MCP server. - **Keep the connector open and enable the plug-in if asked.** + **Run the Connect to MCP script.** - Leave the connector Web Viewer open in Browse mode while you work. The plug-in provides the local API used by the MCP server and the Web Viewer proxy used for verification. + In FileMaker Pro, run the **Connect to MCP** script installed by the add-on. It opens the connector Web Viewer that registers your file with the local MCP server. Leave the connector Web Viewer open in Browse mode while you work. diff --git a/apps/docs/src/components/YouTubeVideo.tsx b/apps/docs/src/components/YouTubeVideo.tsx new file mode 100644 index 00000000..cb017d98 --- /dev/null +++ b/apps/docs/src/components/YouTubeVideo.tsx @@ -0,0 +1,81 @@ +interface YouTubeVideoProps { + title: string; + videoId?: string; + url?: string; +} + +const YOUTUBE_HOSTS = new Set([ + "youtube.com", + "www.youtube.com", + "m.youtube.com", + "youtu.be", + "youtube-nocookie.com", + "www.youtube-nocookie.com", +]); +const YOUTUBE_VIDEO_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/; + +function validateYouTubeVideoId(videoId: string, source: string): string { + if (YOUTUBE_VIDEO_ID_PATTERN.test(videoId)) { + return videoId; + } + + throw new Error(`Invalid YouTube video ID from ${source}: ${videoId}`); +} + +function getYouTubeVideoId({ url, videoId }: Pick): string { + if (videoId) { + return validateYouTubeVideoId(videoId, "videoId"); + } + + if (!url) { + throw new Error("YouTubeVideo requires either a url or videoId."); + } + + const parsedUrl = new URL(url); + const hostname = parsedUrl.hostname.toLowerCase(); + + if (!YOUTUBE_HOSTS.has(hostname)) { + throw new Error(`Unsupported YouTube URL host: ${parsedUrl.hostname}`); + } + + if (hostname === "youtu.be") { + const idFromShortUrl = parsedUrl.pathname.split("/").filter(Boolean)[0]; + if (idFromShortUrl) { + return validateYouTubeVideoId(idFromShortUrl, parsedUrl.href); + } + } + + if (parsedUrl.pathname.startsWith("/embed/")) { + const idFromEmbedPath = parsedUrl.pathname.split("/")[2]; + if (idFromEmbedPath) { + return validateYouTubeVideoId(idFromEmbedPath, parsedUrl.href); + } + } + + const idFromQuery = parsedUrl.searchParams.get("v"); + if (idFromQuery) { + return validateYouTubeVideoId(idFromQuery, parsedUrl.href); + } + + throw new Error(`Unable to extract YouTube video ID from URL: ${parsedUrl.href}`); +} + +export function YouTubeVideo(props: YouTubeVideoProps) { + const videoId = getYouTubeVideoId(props); + const src = `https://www.youtube-nocookie.com/embed/${videoId}`; + + return ( +
+
+