diff --git a/.github/ISSUE_TEMPLATE/reply-to-recipe.yml b/.github/ISSUE_TEMPLATE/reply-to-recipe.yml new file mode 100644 index 00000000..c2a26ccb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/reply-to-recipe.yml @@ -0,0 +1,30 @@ +name: Auto-reply to Recipe Submissions + +on: + issues: + types: [opened] + +jobs: + thank-you-comment: + if: contains(github.event.issue.title, '[Recipe]') + runs-on: ubuntu-latest + steps: + - name: Add thank-you comment + uses: actions/github-script@v7 + with: + script: | + const commentBody = [ + "🎉 Thanks for submitting your Goose recipe to the Cookbook!", + "", + "We appreciate you sharing your workflow with the community — our team will review your submission soon.", + "If accepted, it’ll be added to the [Goose Recipes Cookbook](https://block.github.io/goose/recipes) and you’ll receive LLM credits as a thank-you!", + "", + "Stay tuned — and keep those recipes coming 🧑‍🍳🔥" + ].join('\n'); + + github.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); diff --git a/.github/ISSUE_TEMPLATE/submit-recipe.yml b/.github/ISSUE_TEMPLATE/submit-recipe.yml new file mode 100644 index 00000000..bd2670fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/submit-recipe.yml @@ -0,0 +1,51 @@ +name: 🧑‍🍳 Submit a Recipe to the Goose Cookbook +description: Share a reusable Goose session (aka a recipe) to help the community! +title: "[Recipe] " +labels: ["recipe submission"] +body: + - type: markdown + attributes: + value: | + Thanks for contributing to the Goose Cookbook! 🍳 + Recipes are reusable sessions created in Goose Desktop or CLI and shared with the community to help others vibe-code faster. + + 📌 **How to Submit** + - Create your recipe using Goose (“Make Agent from this session”) + - Fill out the JSON below using the format provided + - Paste it into the field and submit the issue — we’ll review and add it to the Cookbook! + + 🪄 **What Happens After?** + - If accepted, we’ll publish your recipe to the [Goose Recipes Cookbook](https://block.github.io/goose/recipes) + - You’ll receive LLM credits as a thank-you! + - Your GitHub handle will be displayed and linked on the recipe card + + - type: textarea + id: recipe-json + attributes: + label: Paste Your Recipe JSON Below + description: Use the structure below and be sure to include your GitHub handle. + placeholder: | + { + "id": "pr-generator", + "title": "PR Generator", + "description": "Generate pull request descriptions based on staged changes and git history.", + "action": "Generate PR", + "category": "Developer", + "extensions": ["GitHub MCP", "Memory"], + "activities": ["Summarize changes", "Create PR branch", "Push PR"], + "recipeUrl": "https://goose.block.xyz/recipe/pr-generator", + "author": "your-github-handle" + } + validations: + required: true + + - type: markdown + attributes: + value: | + 🛠 **Recipe Field Tips** + - `"id"` should be lowercase, hyphenated, and unique (e.g. `my-awesome-recipe`) + - `"action"` describes the core purpose of the recipe (e.g., `Generate Docs`) + - `"category"` is the type of user this recipe is for (e.g., `Developer`, `Entertainment`) + - `"extensions"` are the Goose tools this recipe uses + - `"recipeUrl"` is from Goose Desktop/CLI + - `"author"` is your GitHub handle — we’ll link to your profile in the Cookbook diff --git a/documentation/docusaurus.config.ts b/documentation/docusaurus.config.ts index 3bcc2e3b..6aac8262 100644 --- a/documentation/docusaurus.config.ts +++ b/documentation/docusaurus.config.ts @@ -159,6 +159,10 @@ const config: Config = { to: '/prompt-library', label: 'Prompt Library', }, + { + to: '/recipes', + label: 'Recipe Cookbook', + }, { to: 'deeplink-generator', label: 'Deeplink Generator', diff --git a/documentation/src/components/recipe-card.tsx b/documentation/src/components/recipe-card.tsx new file mode 100644 index 00000000..6139b1c8 --- /dev/null +++ b/documentation/src/components/recipe-card.tsx @@ -0,0 +1,109 @@ +import React from "react"; +import Link from "@docusaurus/Link"; + +export type Recipe = { + id: string; + title: string; + description: string; + extensions: string[]; + activities: string[]; + recipeUrl: string; + action?: string; + author?: string; + persona?: string; +}; + +export function RecipeCard({ recipe }: { recipe: Recipe }) { + return ( + +
+ {/* Optional Glow */} +
+ + {/* Card Container */} +
+
+ {/* Title & Description */} +
+

+ {recipe.title} +

+

+ {recipe.description} +

+
+ + {/* Extensions */} + {recipe.extensions.length > 0 && ( +
+ {recipe.extensions.map((ext, index) => { + const cleanedLabel = ext.replace(/MCP/i, "").trim(); + return ( + + {cleanedLabel} + + ); + })} +
+ )} + + {/* Activities */} + {recipe.activities?.length > 0 && ( +
+ {recipe.activities.map((activity, index) => ( + + {activity} + + ))} +
+ )} +
+ + {/* Footer */} +
+ e.stopPropagation()} + > + Launch Recipe → + + {recipe.author && ( + e.stopPropagation()} + > + {recipe.author} + @{recipe.author} + + )} +
+
+
+ + ); +} diff --git a/documentation/src/pages/recipes/data/recipes/joke-of-the-day.json b/documentation/src/pages/recipes/data/recipes/joke-of-the-day.json new file mode 100644 index 00000000..4d579501 --- /dev/null +++ b/documentation/src/pages/recipes/data/recipes/joke-of-the-day.json @@ -0,0 +1,13 @@ +{ + "id": "joke-of-the-day", + "title": "Joke of the day", + "description": "Will tell you a joke of the day based on the current day", + "instructions": "Your job is to tell a joke of the day", + "author": "DOsinga", + "extensions": ["Developer"], + "activities": ["Tell a joke", "Daily humor"], + "category": "Entertainment", + "recipeUrl": "goose://recipe?config=eyJ2ZXJzaW9uIjoiMS4wLjAiLCJ0aXRsZSI6Ikpva2Ugb2YgdGhlIGRheSIsImRlc2NyaXB0aW9uIjoiV2lsbCB0ZWxsIHlvdSBhIGpva2Ugb2YgdGhlIGRheSBiYXNlZCBvbiB0aGUgY3VycmVudCBkYXkiLCJpbnN0cnVjdGlvbnMiOiJCYXNlZCBvbiB3aGF0IGRheSBpdCBpcyB0b2RheSwgZ2VuZXJhdGUgYSBqb2tlLiBNZW50aW9uIHRoZSBkYXkgb24gdGhlIGZpcnN0IGxpbmUgdGhlbiBhbiBlbXB0eSBsaW5lIGFuZCB0aGVuIHRoZSBqb2tlLiBEb24ndCBqdXN0IHNheSB0aGUgZGF0ZSwgYnV0IGZpZ3VyZSBvdXQgaWYgdGhlcmUncyBhbnkgY3VsdHVyYWwgc2lnbmlmaWNhbmNlLCBsaWtlIG5hdGlvbmFsIHNoZWx2ZXMgZGF5IiwiZXh0ZW5zaW9ucyI6W10sImFjdGl2aXRpZXMiOlsiR2VuZXJhdGUgaG9saWRheS10aGVtZWQgam9rZSIsIklkZW50aWZ5IHNwZWNpYWwgb2JzZXJ2YW5jZSBmb3IgZGF0ZSIsIkNyZWF0ZSB0aGVtZWQgd29yZHBsYXkiLCJNYXRjaCBodW1vciB0byBjdWx0dXJhbCBldmVudCIsIlRlbGwgYSBqb2tlIl0sImF1dGhvciI6eyJjb250YWN0IjoiZWJvbnlsIn19", + "prompt": "Based on what day it is today, generate a joke. Mention the day on the first line then an empty line and then the joke. Don't just say the date, but figure out if there's any cultural significance, like national shelves day" +} + \ No newline at end of file diff --git a/documentation/src/pages/recipes/data/recipes/pull-request-generator.json b/documentation/src/pages/recipes/data/recipes/pull-request-generator.json new file mode 100644 index 00000000..8a571ab7 --- /dev/null +++ b/documentation/src/pages/recipes/data/recipes/pull-request-generator.json @@ -0,0 +1,13 @@ +{ + "id": "pr-generator", + "title": "PR Generator", + "description": "Automatically generate pull request descriptions based on changes in a local git repo.", + "instructions": "Your job is to generate descriptive and helpful pull request descriptions without asking for additional information. Generate commit messages and branch names based on the actual code changes.", + "prompt": "Analyze the staged changes and any unpushed commits in the git repository {{git_repo_path}} to generate a comprehensive pull request description. Work autonomously without requesting additional information.\n\nAnalysis steps:\n1. Get current branch name using `git branch --show-current`\n2. If not on main/master/develop:\n - Check for unpushed commits: `git log @{u}..HEAD` (if upstream exists)\n - Include these commits in the analysis\n3. Check staged changes: `git diff --staged`\n4. Save the staged changes diff for the PR description\n5. Determine the type of change (feature, fix, enhancement, etc.) from the code\n\nGenerate the PR description with:\n1. A clear summary of the changes, including:\n - New staged changes\n - Any unpushed commits (if on a feature branch)\n2. Technical implementation details based on both the diff and unpushed commits\n3. List of modified files and their purpose\n4. Impact analysis (what areas of the codebase are affected)\n5. Testing approach and considerations\n6. Any migration steps or breaking changes\n7. Related issues or dependencies\n\nUse git commands:\n- `git diff --staged` for staged changes\n- `git log @{u}..HEAD` for unpushed commits\n- `git branch --show-current` for current branch\n- `git status` for staged files\n- `git show` for specific commit details\n- `git rev-parse --abbrev-ref --symbolic-full-name @{u}` to check if branch has upstream\n\nFormat the description in markdown with appropriate sections and code blocks where relevant.\n\n{% if push_pr %}\nExecute the following steps for pushing:\n1. Determine branch handling:\n - If current branch is main/master/develop or unrelated:\n - Generate branch name from staged changes (e.g., 'feature-add-user-auth')\n - Create and switch to new branch: `git checkout -b [branch-name]`\n - If current branch matches changes:\n - Continue using current branch\n - Note any unpushed commits\n\n2. Handle commits and push:\n a. If staged changes exist:\n - Create commit using generated message: `git commit -m \"[type]: [summary]\"`\n - Message should be concise and descriptive of actual changes\n b. Push changes:\n - For existing branches: `git push origin HEAD`\n - For new branches: `git push -u origin HEAD`\n\n3. Create PR:\n - Use git/gh commands to create PR with generated description\n - Set base branch appropriately\n - Print PR URL after creation\n\nBranch naming convention:\n- Use kebab-case\n- Prefix with type: feature-, fix-, enhance-, refactor-\n- Keep names concise but descriptive\n- Base on actual code changes\n\nCommit message format:\n- Start with type: feat, fix, enhance, refactor\n- Followed by concise description\n- Based on actual code changes\n- No body text needed for straightforward changes\n\nDo not:\n- Ask for confirmation or additional input\n- Create placeholder content\n- Include TODO items\n- Add WIP markers\n{% endif %}", + "extensions": ["Developer", "Memory"], + "activities": ["Generate PR", "Analyze staged git changes", "Create PR description"], + "action": "Generate PR", + "category": "Developer", + "recipeUrl": "goose://recipe?config=eyJ2ZXJzaW9uIjoiMS4wLjAiLCJ0aXRsZSI6IlB1bGwgUmVxdWVzdCBHZW5lcmF0b3IiLCJkZXNjcmlwdGlvbiI6IkF1dG9tYXRpY2FsbHkgZ2VuZXJhdGUgcHVsbCByZXF1ZXN0IGRlc2NyaXB0aW9ucyBiYXNlZCBvbiBjaGFuZ2VzIGluIGEgbG9jYWwgZ2l0IHJlcG8uIiwiaW5zdHJ1Y3Rpb25zIjoiWW91ciBqb2IgaXMgdG8gZ2VuZXJhdGUgZGVzY3JpcHRpdmUgYW5kIGhlbHBmdWwgcHVsbCByZXF1ZXN0IGRlc2NyaXB0aW9ucyB3aXRob3V0IGFza2luZyBmb3IgYWRkaXRpb25hbCBpbmZvcm1hdGlvbi4gR2VuZXJhdGUgY29tbWl0IG1lc3NhZ2VzIGFuZCBicmFuY2ggbmFtZXMgYmFzZWQgb24gdGhlIGFjdHVhbCBjb2RlIGNoYW5nZXMuIiwiZXh0ZW5zaW9ucyI6W10sImFjdGl2aXRpZXMiOlsiR2VuZXJhdGUgUFIgZGVzY3JpcHRpb24gZnJvbSBjaGFuZ2VzIiwiQW5hbHl6ZSBnaXQgZGlmZiBvdXRwdXQiLCJDcmVhdGUgZmVhdHVyZSBicmFuY2ggYW5kIHB1c2giLCJGb3JtYXQgY29tbWl0IG1lc3NhZ2VzIiwiUmV2aWV3IHVucHVzaGVkIGNvbW1pdHMiXSwiYXV0aG9yIjp7ImNvbnRhY3QiOiJlYm9ueWwifX0=", + "author": "lifeizhou-ap" +} \ No newline at end of file diff --git a/documentation/src/pages/recipes/detail.tsx b/documentation/src/pages/recipes/detail.tsx new file mode 100644 index 00000000..ad2bf2a7 --- /dev/null +++ b/documentation/src/pages/recipes/detail.tsx @@ -0,0 +1,189 @@ +import Layout from "@theme/Layout"; +import { ArrowLeft } from "lucide-react"; +import { useLocation } from "@docusaurus/router"; +import { useEffect, useState } from "react"; +import Link from "@docusaurus/Link"; +import Admonition from "@theme/Admonition"; +import CodeBlock from "@theme/CodeBlock"; +import { Button } from "@site/src/components/ui/button"; +import { getRecipeById } from "@site/src/utils/recipes"; +import type { Recipe } from "@site/src/components/recipe-card"; + +const colorMap: { [key: string]: string } = { + "GitHub MCP": "bg-yellow-100 text-yellow-800 border-yellow-200", + "Context7 MCP": "bg-purple-100 text-purple-800 border-purple-200", + "Memory": "bg-blue-100 text-blue-800 border-blue-200", +}; + +export default function RecipeDetailPage(): JSX.Element { + const location = useLocation(); + const [recipe, setRecipe] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadRecipe = async () => { + try { + setLoading(true); + setError(null); + + const params = new URLSearchParams(location.search); + const id = params.get("id"); + if (!id) { + setError("No recipe ID provided"); + return; + } + + const recipeData = await getRecipeById(id); + if (recipeData) { + setRecipe(recipeData); + } else { + setError("Recipe not found"); + } + } catch (err) { + setError("Failed to load recipe details"); + console.error(err); + } finally { + setLoading(false); + } + }; + + loadRecipe(); + }, [location]); + + if (loading) { + return ( + +
+
+
+
+
+
+
+
+ ); + } + + if (error || !recipe) { + return ( + +
+
+ {error || "Recipe not found"} +
+
+
+ ); + } + + return ( + +
+
+
+ + + + {recipe.author && ( + + {recipe.author} + @{recipe.author} + + )} +
+ +
+

+ {recipe.title} +

+

{recipe.description}

+ + {/* Activities */} + {recipe.activities?.length > 0 && ( +
+

Activities

+
+ {recipe.activities.map((activity, index) => ( + + {activity} + + ))} +
+
+ )} + + {/* Extensions */} + {recipe.extensions?.length > 0 && ( +
+

Extensions

+
+ {recipe.extensions.map((ext, index) => ( + + {ext} + + ))} +
+
+ )} + + {/* Instructions */} + {recipe.instructions && ( +
+

Instructions

+

+ {recipe.instructions} +

+
+ )} + + {/* Prompt */} + {recipe.prompt && ( +
+

Initial Prompt

+ + This prompt auto-starts the recipe when launched in Goose. + + {recipe.prompt} +
+ )} + + {/* Launch Button */} + {recipe.recipeUrl && ( +
+ + Launch Recipe → + +
+ )} +
+
+
+
+ ); +} diff --git a/documentation/src/pages/recipes/index.tsx b/documentation/src/pages/recipes/index.tsx new file mode 100644 index 00000000..ef25ad19 --- /dev/null +++ b/documentation/src/pages/recipes/index.tsx @@ -0,0 +1,217 @@ +import { RecipeCard, Recipe } from "@site/src/components/recipe-card"; +import { searchRecipes } from "@site/src/utils/recipes"; +import { useState, useEffect } from "react"; +import { motion } from "framer-motion"; +import Layout from "@theme/Layout"; +import Admonition from "@theme/Admonition"; +import { Button } from "@site/src/components/ui/button"; +import { SidebarFilter, type SidebarFilterGroup } from "@site/src/components/ui/sidebar-filter"; +import { Menu, X } from "lucide-react"; +import Link from '@docusaurus/Link'; + +export default function RecipePage() { + const [recipes, setRecipes] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedFilters, setSelectedFilters] = useState>({}); + const [isMobileFilterOpen, setIsMobileFilterOpen] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const recipesPerPage = 20; + + const uniqueCategories = Array.from( + new Set(recipes.map((r) => r.category?.toLowerCase()).filter(Boolean)) + ).map((category) => ({ + label: category.replace(/\b\w/g, (l) => l.toUpperCase()), + value: category + })); + + const uniqueExtensions = Array.from( + new Set(recipes.flatMap((r) => + r.extensions.map((ext) => ext.toLowerCase().replace(/\s+/g, "-")) + )) + ).map((ext) => { + const cleanValue = ext.replace(/-mcp$/, ""); + let label = cleanValue.replace(/-/g, " "); + if (label.toLowerCase() === "github") { + label = "GitHub"; + } else { + label = label.replace(/\b\w/g, (l) => l.toUpperCase()); + } + return { + label, + value: ext + }; + }); + + const sidebarFilterGroups: SidebarFilterGroup[] = [ + { + title: "Category", + options: uniqueCategories + }, + { + title: "Extensions Used", + options: uniqueExtensions + } + ]; + + useEffect(() => { + const loadRecipes = async () => { + try { + setIsLoading(true); + setError(null); + const results = await searchRecipes(searchQuery); + setRecipes(results); + } catch (err) { + const errorMessage = + err instanceof Error ? err.message : "Unknown error"; + setError(`Failed to load recipes: ${errorMessage}`); + console.error("Error loading recipes:", err); + } finally { + setIsLoading(false); + } + }; + + const timeoutId = setTimeout(loadRecipes, 300); + return () => clearTimeout(timeoutId); + }, [searchQuery]); + + let filteredRecipes = recipes; + + Object.entries(selectedFilters).forEach(([group, values]) => { + if (values.length > 0) { + filteredRecipes = filteredRecipes.filter((r) => { + if (group === "Category") { + return values.includes(r.category?.toLowerCase()); + } + if (group === "Extensions Used") { + return r.extensions?.some((ext) => + values.includes(ext.toLowerCase().replace(/\s+/g, "-")) + ); + } + return true; + }); + } + }); + + return ( + +
+
+

+ Recipes Cookbook +

+

+ Save time and skip setup — launch any{" "} + + Goose agent recipe + {" "} + shared by the community with a single click. +

+
+ +
+ { + setSearchQuery(e.target.value); + setCurrentPage(1); + }} + /> +
+ +
+ +
+ +
+
+ { + setSelectedFilters(prev => ({ ...prev, [group]: values })); + setCurrentPage(1); + }} + /> +
+ +
+
+

+ {searchQuery + ? `${filteredRecipes.length} result${filteredRecipes.length !== 1 ? "s" : ""} for "${searchQuery}"` + : ""} +

+
+ + {error && ( + +

{error}

+
+ )} + + {isLoading ? ( +
Loading recipes...
+ ) : filteredRecipes.length === 0 ? ( + +

+ {searchQuery + ? "No recipes found matching your search." + : "No recipes have been submitted yet."} +

+
+ ) : ( + <> +
+ {filteredRecipes + .slice((currentPage - 1) * recipesPerPage, currentPage * recipesPerPage) + .map((recipe) => ( + + + + ))} +
+ + {filteredRecipes.length > recipesPerPage && ( +
+ + + + Page {currentPage} of {Math.ceil(filteredRecipes.length / recipesPerPage)} + + + +
+ )} + + )} +
+
+
+
+ ); +} diff --git a/documentation/src/pages/recipes/styles/main.css b/documentation/src/pages/recipes/styles/main.css new file mode 100644 index 00000000..2d1a9d53 --- /dev/null +++ b/documentation/src/pages/recipes/styles/main.css @@ -0,0 +1,179 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@font-face { + font-family: Cash Sans; + src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Regular.woff2) + format("woff2"); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: Cash Sans; + src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Medium.woff2) + format("woff2"); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: Cash Sans; + src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Semibold.woff2) + format("woff2"); + font-weight: 600; + font-style: normal; +} + +@font-face { + font-family: Cash Sans; + src: url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Bold.woff2) + format("woff2"); + font-weight: 700; + font-style: normal; +} + +@layer base { + :root { + /* start arcade colors */ + --constant-white: #ffffff; + --constant-black: #000000; + --grey-10: #101010; + --grey-20: #1e1e1e; + --grey-50: #666666; + --grey-60: #959595; + --grey-80: #cccccc; + --grey-85: #dadada; + --grey-90: #e8e8e8; + --grey-95: #f0f0f0; + --dark-grey-15: #1a1a1a; + --dark-grey-25: #232323; + --dark-grey-30: #2a2a2a; + --dark-grey-40: #333333; + --dark-grey-45: #595959; + --dark-grey-60: #878787; + --dark-grey-90: #e1e1e1; + + --background-app: var(--constant-white); + --background-prominent: var(--grey-80); + --background-standard: var(--grey-90); + --background-subtle: var(--grey-95); + + --border-divider: var(--grey-90); + --border-inverse: var(--constant-white); + --border-prominent: var(--grey-10); + --border-standard: var(--grey-60); + --border-subtle: var(--grey-90); + + --icon-disabled: var(--grey-60); + --icon-extra-subtle: var(--grey-60); + --icon-inverse: var(--constant-white); + --icon-prominent: var(--grey-10); + --icon-standard: var(--grey-20); + --icon-subtle: var(--grey-50); + + --text-placeholder: var(--grey-60); + --text-prominent: var(--grey-10); + --text-standard: var(--grey-20); + --text-subtle: var(--grey-50); + + &.dark { + --background-app: var(--constant-black); + --background-prominent: var(--dark-grey-40); + --background-standard: var(--dark-grey-25); + --background-subtle: var(--dark-grey-15); + + --border-divider: var(--dark-grey-25); + --border-inverse: var(--constant-black); + --border-prominent: var(--constant-white); + --border-standard: var(--dark-grey-45); + --border-subtle: var(--dark-grey-25); + + --icon-disabled: var(--dark-grey-45); + --icon-extra-subtle: var(--dark-grey-45); + --icon-inverse: var(--constant-black); + --icon-prominent: var(--constant-white); + --icon-standard: var(--dark-grey-90); + --icon-subtle: var(--dark-grey-60); + + --text-placeholder: var(--dark-grey-45); + --text-prominent: var(--constant-white); + --text-standard: var(--dark-grey-90); + --text-subtle: var(--dark-grey-60); + } + /* end arcade colors */ + } +} + +@keyframes rotate { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@keyframes appear-top { + 0% { + transform: translateY(-50%); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +/* Command section styles */ +.command-toggle { + display: flex; + align-items: center; + width: 100%; + padding: 0.5rem 0; + font-size: 0.875rem; + color: var(--text-standard); + background: transparent; + border: none; + cursor: pointer; + transition: all 0.2s ease; +} + +.command-toggle:hover { + color: var(--text-prominent); +} + +.command-toggle h4 { + font-size: 0.875rem; + font-weight: 500; + margin: 0; +} + +.command-toggle svg { + width: 16px; + height: 16px; +} + +.command-content { + background-color: var(--background-subtle); + padding: 0.75rem; + border-radius: 0.375rem; + font-size: 0.875rem; + color: var(--text-standard); + margin-top: 0.5rem; +} + +.command-content code { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + font-size: 0.875rem; + background-color: transparent; + border: none; +} + +/* view transitions */ +a.transitioning .home-page-server-name, +.detail-page-server-name { + view-transition-name: server-name; +} diff --git a/documentation/src/pages/recipes/types/index.tsx b/documentation/src/pages/recipes/types/index.tsx new file mode 100644 index 00000000..9840f999 --- /dev/null +++ b/documentation/src/pages/recipes/types/index.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import Layout from '@docusaurus/theme-classic/lib/theme/Layout'; +import CodeBlock from '@docusaurus/theme-classic/lib/theme/CodeBlock'; + +const Types: React.FC = () => { + return ( + +
+

Type Definitions

+

This page contains the type definitions used in the Prompt Library.

+ +

Environment Variable

+ +{`type EnvironmentVariable = { + name: string; + description: string; + required: boolean; +};`} + + +

Extension

+ +{`type Extension = { + name: string; + command: string; + is_builtin: boolean; + link?: string; + installation_notes?: string; + environmentVariables?: EnvironmentVariable[]; +};`} + + +

Category

+ +{`type Category = "business" | "technical" | "productivity";`} + + +

Prompt

+ +{`type Prompt = { + id: string; + title: string; + description: string; + example_prompt: string; + extensions: Extension[]; + category: Category; + featured?: boolean; +};`} + +
+
+ ); +}; + +export default Types; \ No newline at end of file diff --git a/documentation/src/utils/recipes.ts b/documentation/src/utils/recipes.ts new file mode 100644 index 00000000..36a3a0b3 --- /dev/null +++ b/documentation/src/utils/recipes.ts @@ -0,0 +1,47 @@ +import type { Recipe } from "@site/src/components/recipe-card"; + +// Webpack context loader for all JSON files in the recipes folder +const recipeFiles = require.context( + '../pages/recipes/data/recipes', + false, + /\.json$/ +); + +export function getRecipeById(id: string): Recipe | null { + const allRecipes: Recipe[] = recipeFiles + .keys() + .map((key: string) => recipeFiles(key)) + .map((module: any) => module.default || module); + + return allRecipes.find((recipe) => recipe.id === id) || null; +} + +export async function searchRecipes(query: string): Promise { + const allRecipes: Recipe[] = recipeFiles + .keys() + .map((key: string) => recipeFiles(key)) + .map((module: any) => { + const recipe = module.default || module; + + // Normalize fields for filters + return { + ...recipe, + persona: recipe.persona || null, + action: recipe.action || null, + extensions: Array.isArray(recipe.extensions) ? recipe.extensions : [], + }; + }); + + if (query) { + return allRecipes.filter((r) => + r.title.toLowerCase().includes(query.toLowerCase()) || + r.description.toLowerCase().includes(query.toLowerCase()) || + r.action?.toLowerCase().includes(query.toLowerCase()) || + r.activities?.some((activity) => + activity.toLowerCase().includes(query.toLowerCase()) + ) + ); + } + + return allRecipes; +} diff --git a/documentation/tailwind.config.js b/documentation/tailwind.config.js index a58101f0..e5679a7d 100644 --- a/documentation/tailwind.config.js +++ b/documentation/tailwind.config.js @@ -1,7 +1,7 @@ // tailwind.config.js module.exports = { content: ["./src/**/*.{js,jsx,ts,tsx}"], - darkMode: "class", + darkMode: ["class", '[data-theme="dark"]'], corePlugins: { preflight: false, },