diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 7fae37fc..e7f7e81d 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -8,14 +8,15 @@ on: push: branches: ["main"] paths: - - 'website/**' - - 'agents/**' - - 'prompts/**' - - 'instructions/**' - - 'skills/**' - - 'collections/**' - - 'eng/generate-website-data.mjs' - - '.github/workflows/deploy-website.yml' + - "website/**" + - "agents/**" + - "prompts/**" + - "instructions/**" + - "skills/**" + - "collections/**" + - "cookbook/**" + - "eng/generate-website-data.mjs" + - ".github/workflows/deploy-website.yml" # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -43,8 +44,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" - name: Install root dependencies run: npm ci @@ -66,7 +67,7 @@ jobs: - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - path: './website/dist' + path: "./website/dist" # Deployment job deploy: diff --git a/eng/generate-website-data.mjs b/eng/generate-website-data.mjs index ae8e2ec2..fdd14fc0 100644 --- a/eng/generate-website-data.mjs +++ b/eng/generate-website-data.mjs @@ -7,21 +7,20 @@ */ import fs from "fs"; -import path from "path"; +import path, { dirname } from "path"; import { fileURLToPath } from "url"; -import { dirname } from "path"; import { AGENTS_DIR, - INSTRUCTIONS_DIR, - PROMPTS_DIR, - SKILLS_DIR, COLLECTIONS_DIR, COOKBOOK_DIR, + INSTRUCTIONS_DIR, + PROMPTS_DIR, ROOT_FOLDER, + SKILLS_DIR, } from "./constants.mjs"; import { - parseFrontmatter, parseCollectionYaml, + parseFrontmatter, parseSkillMetadata, parseYamlFile, } from "./yaml-parser.mjs"; @@ -63,17 +62,6 @@ function extractTitle(filePath, frontmatter) { .join(" "); } -/** - * Get file content (for preview/full content) - */ -function getFileContent(filePath) { - try { - return fs.readFileSync(filePath, "utf8"); - } catch (e) { - return null; - } -} - /** * Generate agents metadata */ @@ -90,7 +78,9 @@ function generateAgentsData() { for (const file of files) { const filePath = path.join(AGENTS_DIR, file); const frontmatter = parseFrontmatter(filePath); - const relativePath = path.relative(ROOT_FOLDER, filePath).replace(/\\/g, "/"); + const relativePath = path + .relative(ROOT_FOLDER, filePath) + .replace(/\\/g, "/"); const model = frontmatter?.model || null; const tools = frontmatter?.tools || []; @@ -146,7 +136,9 @@ function generatePromptsData() { for (const file of files) { const filePath = path.join(PROMPTS_DIR, file); const frontmatter = parseFrontmatter(filePath); - const relativePath = path.relative(ROOT_FOLDER, filePath).replace(/\\/g, "/"); + const relativePath = path + .relative(ROOT_FOLDER, filePath) + .replace(/\\/g, "/"); const tools = frontmatter?.tools || []; tools.forEach((t) => allTools.add(t)); @@ -181,12 +173,15 @@ function parseApplyToPatterns(applyTo) { // Handle array format if (Array.isArray(applyTo)) { - return applyTo.map(p => p.trim()).filter(p => p.length > 0); + return applyTo.map((p) => p.trim()).filter((p) => p.length > 0); } // Handle string format (comma-separated) - if (typeof applyTo === 'string') { - return applyTo.split(',').map(p => p.trim()).filter(p => p.length > 0); + if (typeof applyTo === "string") { + return applyTo + .split(",") + .map((p) => p.trim()) + .filter((p) => p.length > 0); } return []; @@ -203,7 +198,7 @@ function extractExtensionFromPattern(pattern) { // Match patterns like **/*.{ts,tsx} const braceMatch = pattern.match(/\*\.\{([^}]+)\}$/); if (braceMatch) { - return braceMatch[1].split(',').map(ext => `.${ext.trim()}`); + return braceMatch[1].split(",").map((ext) => `.${ext.trim()}`); } return null; @@ -225,7 +220,9 @@ function generateInstructionsData() { for (const file of files) { const filePath = path.join(INSTRUCTIONS_DIR, file); const frontmatter = parseFrontmatter(filePath); - const relativePath = path.relative(ROOT_FOLDER, filePath).replace(/\\/g, "/"); + const relativePath = path + .relative(ROOT_FOLDER, filePath) + .replace(/\\/g, "/"); const applyToRaw = frontmatter?.applyTo || null; const applyToPatterns = parseApplyToPatterns(applyToRaw); @@ -237,7 +234,7 @@ function generateInstructionsData() { const ext = extractExtensionFromPattern(pattern); if (ext) { if (Array.isArray(ext)) { - ext.forEach(e => { + ext.forEach((e) => { extensions.push(e); allExtensions.add(e); }); @@ -260,7 +257,9 @@ function generateInstructionsData() { }); } - const sortedInstructions = instructions.sort((a, b) => a.title.localeCompare(b.title)); + const sortedInstructions = instructions.sort((a, b) => + a.title.localeCompare(b.title) + ); return { items: sortedInstructions, @@ -277,16 +276,42 @@ function generateInstructionsData() { function categorizeSkill(name, description) { const text = `${name} ${description}`.toLowerCase(); - if (text.includes('azure') || text.includes('appinsights')) return 'Azure'; - if (text.includes('github') || text.includes('gh-cli') || text.includes('git-commit') || text.includes('git ')) return 'Git & GitHub'; - if (text.includes('vscode') || text.includes('vs code')) return 'VS Code'; - if (text.includes('test') || text.includes('qa') || text.includes('playwright')) return 'Testing'; - if (text.includes('microsoft') || text.includes('m365') || text.includes('workiq')) return 'Microsoft'; - if (text.includes('cli') || text.includes('command')) return 'CLI Tools'; - if (text.includes('diagram') || text.includes('plantuml') || text.includes('visual')) return 'Diagrams'; - if (text.includes('nuget') || text.includes('dotnet') || text.includes('.net')) return '.NET'; + if (text.includes("azure") || text.includes("appinsights")) return "Azure"; + if ( + text.includes("github") || + text.includes("gh-cli") || + text.includes("git-commit") || + text.includes("git ") + ) + return "Git & GitHub"; + if (text.includes("vscode") || text.includes("vs code")) return "VS Code"; + if ( + text.includes("test") || + text.includes("qa") || + text.includes("playwright") + ) + return "Testing"; + if ( + text.includes("microsoft") || + text.includes("m365") || + text.includes("workiq") + ) + return "Microsoft"; + if (text.includes("cli") || text.includes("command")) return "CLI Tools"; + if ( + text.includes("diagram") || + text.includes("plantuml") || + text.includes("visual") + ) + return "Diagrams"; + if ( + text.includes("nuget") || + text.includes("dotnet") || + text.includes(".net") + ) + return ".NET"; - return 'Other'; + return "Other"; } /** @@ -296,7 +321,7 @@ function generateSkillsData() { const skills = []; if (!fs.existsSync(SKILLS_DIR)) { - return { items: [], filters: { categories: [], hasAssets: ['Yes', 'No'] } }; + return { items: [], filters: { categories: [], hasAssets: ["Yes", "No"] } }; } const folders = fs @@ -310,7 +335,9 @@ function generateSkillsData() { const metadata = parseSkillMetadata(skillPath); if (metadata) { - const relativePath = path.relative(ROOT_FOLDER, skillPath).replace(/\\/g, "/"); + const relativePath = path + .relative(ROOT_FOLDER, skillPath) + .replace(/\\/g, "/"); const category = categorizeSkill(metadata.name, metadata.description); allCategories.add(category); @@ -342,7 +369,7 @@ function generateSkillsData() { items: sortedSkills, filters: { categories: Array.from(allCategories).sort(), - hasAssets: ['Yes', 'No'], + hasAssets: ["Yes", "No"], }, }; } @@ -373,7 +400,7 @@ function getSkillFiles(skillPath, relativePath) { } } - walkDir(skillPath, ''); + walkDir(skillPath, ""); return files; } @@ -397,7 +424,9 @@ function generateCollectionsData() { for (const file of files) { const filePath = path.join(COLLECTIONS_DIR, file); const data = parseCollectionYaml(filePath); - const relativePath = path.relative(ROOT_FOLDER, filePath).replace(/\\/g, "/"); + const relativePath = path + .relative(ROOT_FOLDER, filePath) + .replace(/\\/g, "/"); if (data) { const tags = data.tags || []; @@ -443,14 +472,14 @@ function generateCollectionsData() { */ function generateToolsData() { const toolsFile = path.join(WEBSITE_SOURCE_DATA_DIR, "tools.yml"); - + if (!fs.existsSync(toolsFile)) { console.warn("No tools.yml file found at", toolsFile); return { items: [], filters: { categories: [], tags: [] } }; } const data = parseYamlFile(toolsFile); - + if (!data || !data.tools) { return { items: [], filters: { categories: [], tags: [] } }; } @@ -461,7 +490,7 @@ function generateToolsData() { const tools = data.tools.map((tool) => { const category = tool.category || "Other"; allCategories.add(category); - + const tags = tool.tags || []; tags.forEach((t) => allTags.add(t)); @@ -498,7 +527,13 @@ function generateToolsData() { /** * Generate a combined index for search */ -function generateSearchIndex(agents, prompts, instructions, skills, collections) { +function generateSearchIndex( + agents, + prompts, + instructions, + skills, + collections +) { const index = []; for (const agent of agents) { @@ -508,7 +543,9 @@ function generateSearchIndex(agents, prompts, instructions, skills, collections) title: agent.title, description: agent.description, path: agent.path, - searchText: `${agent.title} ${agent.description} ${agent.tools.join(" ")}`.toLowerCase(), + searchText: `${agent.title} ${agent.description} ${agent.tools.join( + " " + )}`.toLowerCase(), }); } @@ -530,7 +567,9 @@ function generateSearchIndex(agents, prompts, instructions, skills, collections) title: instruction.title, description: instruction.description, path: instruction.path, - searchText: `${instruction.title} ${instruction.description} ${instruction.applyTo || ""}`.toLowerCase(), + searchText: `${instruction.title} ${instruction.description} ${ + instruction.applyTo || "" + }`.toLowerCase(), }); } @@ -553,7 +592,9 @@ function generateSearchIndex(agents, prompts, instructions, skills, collections) description: collection.description, path: collection.path, tags: collection.tags, - searchText: `${collection.name} ${collection.description} ${collection.tags.join(" ")}`.toLowerCase(), + searchText: `${collection.name} ${ + collection.description + } ${collection.tags.join(" ")}`.toLowerCase(), }); } @@ -565,47 +606,59 @@ function generateSearchIndex(agents, prompts, instructions, skills, collections) */ function generateSamplesData() { const cookbookYamlPath = path.join(COOKBOOK_DIR, "cookbook.yml"); - + if (!fs.existsSync(cookbookYamlPath)) { - console.warn("Warning: cookbook/cookbook.yml not found, skipping samples generation"); - return { cookbooks: [], totalRecipes: 0, totalCookbooks: 0, filters: { languages: [], tags: [] } }; + console.warn( + "Warning: cookbook/cookbook.yml not found, skipping samples generation" + ); + return { + cookbooks: [], + totalRecipes: 0, + totalCookbooks: 0, + filters: { languages: [], tags: [] }, + }; } const cookbookManifest = parseYamlFile(cookbookYamlPath); if (!cookbookManifest || !cookbookManifest.cookbooks) { console.warn("Warning: Invalid cookbook.yml format"); - return { cookbooks: [], totalRecipes: 0, totalCookbooks: 0, filters: { languages: [], tags: [] } }; + return { + cookbooks: [], + totalRecipes: 0, + totalCookbooks: 0, + filters: { languages: [], tags: [] }, + }; } const allLanguages = new Set(); const allTags = new Set(); let totalRecipes = 0; - const cookbooks = cookbookManifest.cookbooks.map(cookbook => { + const cookbooks = cookbookManifest.cookbooks.map((cookbook) => { // Collect languages - cookbook.languages.forEach(lang => allLanguages.add(lang.id)); + cookbook.languages.forEach((lang) => allLanguages.add(lang.id)); // Process recipes and add file paths - const recipes = cookbook.recipes.map(recipe => { + const recipes = cookbook.recipes.map((recipe) => { // Collect tags if (recipe.tags) { - recipe.tags.forEach(tag => allTags.add(tag)); + recipe.tags.forEach((tag) => allTags.add(tag)); } // Build variants with file paths for each language const variants = {}; - cookbook.languages.forEach(lang => { + cookbook.languages.forEach((lang) => { const docPath = `${cookbook.path}/${lang.id}/${recipe.id}.md`; const examplePath = `${cookbook.path}/${lang.id}/recipe/${recipe.id}${lang.extension}`; - + // Check if files exist const docFullPath = path.join(ROOT_FOLDER, docPath); const exampleFullPath = path.join(ROOT_FOLDER, examplePath); - + if (fs.existsSync(docFullPath)) { variants[lang.id] = { doc: docPath, - example: fs.existsSync(exampleFullPath) ? examplePath : null + example: fs.existsSync(exampleFullPath) ? examplePath : null, }; } }); @@ -617,7 +670,7 @@ function generateSamplesData() { name: recipe.name, description: recipe.description, tags: recipe.tags || [], - variants + variants, }; }); @@ -628,7 +681,7 @@ function generateSamplesData() { path: cookbook.path, featured: cookbook.featured || false, languages: cookbook.languages, - recipes + recipes, }; }); @@ -638,8 +691,8 @@ function generateSamplesData() { totalCookbooks: cookbooks.length, filters: { languages: Array.from(allLanguages).sort(), - tags: Array.from(allTags).sort() - } + tags: Array.from(allTags).sort(), + }, }; } @@ -654,32 +707,52 @@ async function main() { // Generate all data const agentsData = generateAgentsData(); const agents = agentsData.items; - console.log(`✓ Generated ${agents.length} agents (${agentsData.filters.models.length} models, ${agentsData.filters.tools.length} tools)`); + console.log( + `✓ Generated ${agents.length} agents (${agentsData.filters.models.length} models, ${agentsData.filters.tools.length} tools)` + ); const promptsData = generatePromptsData(); const prompts = promptsData.items; - console.log(`✓ Generated ${prompts.length} prompts (${promptsData.filters.tools.length} tools)`); + console.log( + `✓ Generated ${prompts.length} prompts (${promptsData.filters.tools.length} tools)` + ); const instructionsData = generateInstructionsData(); const instructions = instructionsData.items; - console.log(`✓ Generated ${instructions.length} instructions (${instructionsData.filters.extensions.length} extensions)`); + console.log( + `✓ Generated ${instructions.length} instructions (${instructionsData.filters.extensions.length} extensions)` + ); const skillsData = generateSkillsData(); const skills = skillsData.items; - console.log(`✓ Generated ${skills.length} skills (${skillsData.filters.categories.length} categories)`); + console.log( + `✓ Generated ${skills.length} skills (${skillsData.filters.categories.length} categories)` + ); const collectionsData = generateCollectionsData(); const collections = collectionsData.items; - console.log(`✓ Generated ${collections.length} collections (${collectionsData.filters.tags.length} tags)`); + console.log( + `✓ Generated ${collections.length} collections (${collectionsData.filters.tags.length} tags)` + ); const toolsData = generateToolsData(); const tools = toolsData.items; - console.log(`✓ Generated ${tools.length} tools (${toolsData.filters.categories.length} categories)`); + console.log( + `✓ Generated ${tools.length} tools (${toolsData.filters.categories.length} categories)` + ); const samplesData = generateSamplesData(); - console.log(`✓ Generated ${samplesData.totalRecipes} recipes in ${samplesData.totalCookbooks} cookbooks (${samplesData.filters.languages.length} languages, ${samplesData.filters.tags.length} tags)`); + console.log( + `✓ Generated ${samplesData.totalRecipes} recipes in ${samplesData.totalCookbooks} cookbooks (${samplesData.filters.languages.length} languages, ${samplesData.filters.tags.length} tags)` + ); - const searchIndex = generateSearchIndex(agents, prompts, instructions, skills, collections); + const searchIndex = generateSearchIndex( + agents, + prompts, + instructions, + skills, + collections + ); console.log(`✓ Generated search index with ${searchIndex.length} items`); // Write JSON files diff --git a/website/astro.config.mjs b/website/astro.config.mjs index e0176df0..87c9e4e6 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -3,8 +3,8 @@ import { defineConfig } from "astro/config"; // https://astro.build/config export default defineConfig({ - site: "https://github.github.io", - base: "/", + site: "https://github.github.io/awesome-copilot", + base: "/awesome-copilot/", output: "static", integrations: [sitemap()], build: { diff --git a/website/public/styles/global.css b/website/public/styles/global.css index edca1783..81f598c7 100644 --- a/website/public/styles/global.css +++ b/website/public/styles/global.css @@ -24,6 +24,12 @@ --color-text: #e4e4ec; --color-text-muted: #9090a8; --color-text-emphasis: #ffffff; + --color-text-primary: var(--color-text); + --color-text-secondary: var(--color-text-muted); + --color-bg-primary: var(--color-bg); + --color-primary: var(--color-accent); + --color-purple-light: #C898FD; + --color-purple-dark: #43179E; --color-link: #B870FF; --color-link-hover: #C898FD; --color-accent: #8534F3; @@ -46,12 +52,14 @@ --border-radius-lg: 16px; --border-radius-xl: 24px; --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -2px rgba(0, 0, 0, 0.2); + --shadow-md: 0 12px 24px -10px rgba(0, 0, 0, 0.4); --shadow-lg: 0 20px 40px -12px rgba(0, 0, 0, 0.5); --shadow-glow: 0 0 40px -10px rgba(133, 52, 243, 0.5); --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1); --transition-slow: 0.4s cubic-bezier(0.4, 0, 0.2, 1); --container-width: 1200px; --header-height: 72px; + --font-mono: 'Monaspace Argon NF', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; } /* Light theme */ @@ -735,6 +743,17 @@ a:hover { border-color: var(--color-border); } +.btn-outline { + background: transparent; + color: var(--color-text); + border-color: var(--color-border); +} + +.btn-outline:hover { + background: var(--color-bg-tertiary); + border-color: var(--color-link); +} + .btn-icon { padding: 8px; background: transparent; @@ -1360,6 +1379,7 @@ a:hover { text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; + line-clamp: 2; -webkit-box-orient: vertical; } diff --git a/website/src/layouts/BaseLayout.astro b/website/src/layouts/BaseLayout.astro index c2b95c18..a8bc2b5c 100644 --- a/website/src/layouts/BaseLayout.astro +++ b/website/src/layouts/BaseLayout.astro @@ -5,80 +5,161 @@ interface Props { activeNav?: string; } -const { title, description = 'Community-driven collection of custom agents, prompts, and instructions for GitHub Copilot', activeNav = '' } = Astro.props; +const { + title, + description = "Community-driven collection of custom agents, prompts, and instructions for GitHub Copilot", + activeNav = "", +} = Astro.props; const base = import.meta.env.BASE_URL; --- - + -
- - -';
}
- const codeEl = modalContent.querySelector('code');
+ const codeEl = modalContent.querySelector("code");
// Setup install dropdown
const vscodeUrl = getVSCodeInstallUrl(type, filePath, false);
const insidersUrl = getVSCodeInstallUrl(type, filePath, true);
-
+
if (vscodeUrl && installDropdown) {
- installDropdown.style.display = 'inline-flex';
- installDropdown.classList.remove('open');
+ installDropdown.style.display = "inline-flex";
+ installDropdown.classList.remove("open");
if (installBtnMain) installBtnMain.href = vscodeUrl;
if (installVscode) installVscode.href = vscodeUrl;
- if (installInsiders) installInsiders.href = insidersUrl || '#';
+ if (installInsiders) installInsiders.href = insidersUrl || "#";
} else if (installDropdown) {
- installDropdown.style.display = 'none';
+ installDropdown.style.display = "none";
}
// Fetch and display content
const fileContent = await fetchFileContent(filePath);
currentFileContent = fileContent;
-
+
if (fileContent && codeEl) {
codeEl.textContent = fileContent;
} else if (codeEl) {
- codeEl.textContent = 'Failed to load file content. Click the button below to view on GitHub.';
+ codeEl.textContent =
+ "Failed to load file content. Click the button below to view on GitHub.";
}
}
@@ -356,27 +403,30 @@ async function openCollectionModal(
downloadBtn: HTMLElement | null
): Promise