Add multi-select filters, light/dark theme, and skill ZIP downloads

- Add multi-select dropdown component for all filter fields
- Implement light/dark theme toggle with system preference detection
- Add client-side ZIP download for skills using JSZip
- Include file lists in skills metadata for download feature
- Add title tooltips to multi-select options for long values
- Update all pages with consistent theme toggle in header
This commit is contained in:
Aaron Powell
2026-01-28 14:59:19 +11:00
parent f8829be835
commit 875219812e
20 changed files with 12575 additions and 8382 deletions

View File

@@ -80,17 +80,34 @@ function generateAgentsData() {
.readdirSync(AGENTS_DIR)
.filter((f) => f.endsWith(".agent.md"));
// Track all unique values for filters
const allModels = new Set();
const allTools = new Set();
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 model = frontmatter?.model || null;
const tools = frontmatter?.tools || [];
const handoffs = frontmatter?.handoffs || [];
// Track unique values
if (model) allModels.add(model);
tools.forEach((t) => allTools.add(t));
agents.push({
id: file.replace(".agent.md", ""),
title: extractTitle(filePath, frontmatter),
description: frontmatter?.description || "",
model: frontmatter?.model || null,
tools: frontmatter?.tools || [],
model: model,
tools: tools,
hasHandoffs: handoffs.length > 0,
handoffs: handoffs.map((h) => ({
label: h.label || "",
agent: h.agent || "",
})),
mcpServers: frontmatter?.["mcp-servers"]
? Object.keys(frontmatter["mcp-servers"])
: [],
@@ -99,7 +116,16 @@ function generateAgentsData() {
});
}
return agents.sort((a, b) => a.title.localeCompare(b.title));
// Sort and return with filter metadata
const sortedAgents = agents.sort((a, b) => a.title.localeCompare(b.title));
return {
items: sortedAgents,
filters: {
models: ["(none)", ...Array.from(allModels).sort()],
tools: Array.from(allTools).sort(),
},
};
}
/**
@@ -111,24 +137,73 @@ function generatePromptsData() {
.readdirSync(PROMPTS_DIR)
.filter((f) => f.endsWith(".prompt.md"));
// Track all unique tools for filters
const allTools = new Set();
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 tools = frontmatter?.tools || [];
tools.forEach((t) => allTools.add(t));
prompts.push({
id: file.replace(".prompt.md", ""),
title: extractTitle(filePath, frontmatter),
description: frontmatter?.description || "",
agent: frontmatter?.agent || null,
model: frontmatter?.model || null,
tools: frontmatter?.tools || [],
tools: tools,
path: relativePath,
filename: file,
});
}
return prompts.sort((a, b) => a.title.localeCompare(b.title));
const sortedPrompts = prompts.sort((a, b) => a.title.localeCompare(b.title));
return {
items: sortedPrompts,
filters: {
tools: Array.from(allTools).sort(),
},
};
}
/**
* Parse applyTo field into an array of patterns
*/
function parseApplyToPatterns(applyTo) {
if (!applyTo) return [];
// Handle array format
if (Array.isArray(applyTo)) {
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);
}
return [];
}
/**
* Extract file extension from a glob pattern
*/
function extractExtensionFromPattern(pattern) {
// Match patterns like **.ts, **/*.js, *.py, etc.
const match = pattern.match(/\*\.(\w+)$/);
if (match) return `.${match[1]}`;
// Match patterns like **/*.{ts,tsx}
const braceMatch = pattern.match(/\*\.\{([^}]+)\}$/);
if (braceMatch) {
return braceMatch[1].split(',').map(ext => `.${ext.trim()}`);
}
return null;
}
/**
@@ -140,22 +215,75 @@ function generateInstructionsData() {
.readdirSync(INSTRUCTIONS_DIR)
.filter((f) => f.endsWith(".instructions.md"));
// Track all unique patterns and extensions for filters
const allPatterns = new Set();
const allExtensions = new Set();
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 applyToRaw = frontmatter?.applyTo || null;
const applyToPatterns = parseApplyToPatterns(applyToRaw);
// Extract extensions from patterns
const extensions = [];
for (const pattern of applyToPatterns) {
allPatterns.add(pattern);
const ext = extractExtensionFromPattern(pattern);
if (ext) {
if (Array.isArray(ext)) {
ext.forEach(e => {
extensions.push(e);
allExtensions.add(e);
});
} else {
extensions.push(ext);
allExtensions.add(ext);
}
}
}
instructions.push({
id: file.replace(".instructions.md", ""),
title: extractTitle(filePath, frontmatter),
description: frontmatter?.description || "",
applyTo: frontmatter?.applyTo || null,
applyTo: applyToRaw,
applyToPatterns: applyToPatterns,
extensions: [...new Set(extensions)],
path: relativePath,
filename: file,
});
}
return instructions.sort((a, b) => a.title.localeCompare(b.title));
const sortedInstructions = instructions.sort((a, b) => a.title.localeCompare(b.title));
return {
items: sortedInstructions,
filters: {
patterns: Array.from(allPatterns).sort(),
extensions: ["(none)", ...Array.from(allExtensions).sort()],
},
};
}
/**
* Categorize a skill based on its name and description
*/
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';
return 'Other';
}
/**
@@ -165,19 +293,26 @@ function generateSkillsData() {
const skills = [];
if (!fs.existsSync(SKILLS_DIR)) {
return skills;
return { items: [], filters: { categories: [], hasAssets: ['Yes', 'No'] } };
}
const folders = fs
.readdirSync(SKILLS_DIR)
.filter((f) => fs.statSync(path.join(SKILLS_DIR, f)).isDirectory());
const allCategories = new Set();
for (const folder of folders) {
const skillPath = path.join(SKILLS_DIR, folder);
const metadata = parseSkillMetadata(skillPath);
if (metadata) {
const relativePath = path.relative(ROOT_FOLDER, skillPath).replace(/\\/g, "/");
const category = categorizeSkill(metadata.name, metadata.description);
allCategories.add(category);
// Get all files in the skill folder recursively
const files = getSkillFiles(skillPath, relativePath);
skills.push({
id: folder,
@@ -188,13 +323,55 @@ function generateSkillsData() {
.join(" "),
description: metadata.description,
assets: metadata.assets,
hasAssets: metadata.assets.length > 0,
assetCount: metadata.assets.length,
category: category,
path: relativePath,
skillFile: `${relativePath}/SKILL.md`,
files: files,
});
}
}
return skills.sort((a, b) => a.title.localeCompare(b.title));
const sortedSkills = skills.sort((a, b) => a.title.localeCompare(b.title));
return {
items: sortedSkills,
filters: {
categories: Array.from(allCategories).sort(),
hasAssets: ['Yes', 'No'],
},
};
}
/**
* Get all files in a skill folder recursively
*/
function getSkillFiles(skillPath, relativePath) {
const files = [];
function walkDir(dir, relDir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
walkDir(fullPath, relPath);
} else {
// Get file size
const stats = fs.statSync(fullPath);
files.push({
path: `${relativePath}/${relPath}`,
name: relPath,
size: stats.size,
});
}
}
}
walkDir(skillPath, '');
return files;
}
/**
@@ -211,17 +388,23 @@ function generateCollectionsData() {
.readdirSync(COLLECTIONS_DIR)
.filter((f) => f.endsWith(".collection.yml"));
// Track all unique tags
const allTags = new Set();
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, "/");
if (data) {
const tags = data.tags || [];
tags.forEach((t) => allTags.add(t));
collections.push({
id: file.replace(".collection.yml", ""),
name: data.name || file.replace(".collection.yml", ""),
description: data.description || "",
tags: data.tags || [],
tags: tags,
featured: data.featured || false,
items: (data.items || []).map((item) => ({
path: item.path,
@@ -235,11 +418,18 @@ function generateCollectionsData() {
}
// Sort with featured first, then alphabetically
return collections.sort((a, b) => {
const sortedCollections = collections.sort((a, b) => {
if (a.featured && !b.featured) return -1;
if (!a.featured && b.featured) return 1;
return a.name.localeCompare(b.name);
});
return {
items: sortedCollections,
filters: {
tags: Array.from(allTags).sort(),
},
};
}
/**
@@ -316,20 +506,25 @@ async function main() {
ensureDataDir();
// Generate all data
const agents = generateAgentsData();
console.log(`✓ Generated ${agents.length} agents`);
const agentsData = generateAgentsData();
const agents = agentsData.items;
console.log(`✓ Generated ${agents.length} agents (${agentsData.filters.models.length} models, ${agentsData.filters.tools.length} tools)`);
const prompts = generatePromptsData();
console.log(`✓ Generated ${prompts.length} prompts`);
const promptsData = generatePromptsData();
const prompts = promptsData.items;
console.log(`✓ Generated ${prompts.length} prompts (${promptsData.filters.tools.length} tools)`);
const instructions = generateInstructionsData();
console.log(`✓ Generated ${instructions.length} instructions`);
const instructionsData = generateInstructionsData();
const instructions = instructionsData.items;
console.log(`✓ Generated ${instructions.length} instructions (${instructionsData.filters.extensions.length} extensions)`);
const skills = generateSkillsData();
console.log(`✓ Generated ${skills.length} skills`);
const skillsData = generateSkillsData();
const skills = skillsData.items;
console.log(`✓ Generated ${skills.length} skills (${skillsData.filters.categories.length} categories)`);
const collections = generateCollectionsData();
console.log(`✓ Generated ${collections.length} collections`);
const collectionsData = generateCollectionsData();
const collections = collectionsData.items;
console.log(`✓ Generated ${collections.length} collections (${collectionsData.filters.tags.length} tags)`);
const searchIndex = generateSearchIndex(agents, prompts, instructions, skills, collections);
console.log(`✓ Generated search index with ${searchIndex.length} items`);
@@ -337,27 +532,27 @@ async function main() {
// Write JSON files
fs.writeFileSync(
path.join(WEBSITE_DATA_DIR, "agents.json"),
JSON.stringify(agents, null, 2)
JSON.stringify(agentsData, null, 2)
);
fs.writeFileSync(
path.join(WEBSITE_DATA_DIR, "prompts.json"),
JSON.stringify(prompts, null, 2)
JSON.stringify(promptsData, null, 2)
);
fs.writeFileSync(
path.join(WEBSITE_DATA_DIR, "instructions.json"),
JSON.stringify(instructions, null, 2)
JSON.stringify(instructionsData, null, 2)
);
fs.writeFileSync(
path.join(WEBSITE_DATA_DIR, "skills.json"),
JSON.stringify(skills, null, 2)
JSON.stringify(skillsData, null, 2)
);
fs.writeFileSync(
path.join(WEBSITE_DATA_DIR, "collections.json"),
JSON.stringify(collections, null, 2)
JSON.stringify(collectionsData, null, 2)
);
fs.writeFileSync(

View File

@@ -1,5 +1,6 @@
/* CSS Variables and Base Styles */
:root {
/* Dark theme (default) */
--color-bg: #0d1117;
--color-bg-secondary: #161b22;
--color-bg-tertiary: #21262d;
@@ -25,9 +26,8 @@
--header-height: 64px;
}
/* Light mode support */
@media (prefers-color-scheme: light) {
:root {
/* Light theme */
[data-theme="light"] {
--color-bg: #ffffff;
--color-bg-secondary: #f6f8fa;
--color-bg-tertiary: #f0f3f6;
@@ -39,6 +39,26 @@
--color-link-hover: #0550ae;
--color-card-bg: #ffffff;
--color-card-hover: #f6f8fa;
--shadow: 0 1px 3px rgba(0,0,0,0.08), 0 8px 24px rgba(0,0,0,0.08);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.15);
}
/* Auto theme based on system preference */
@media (prefers-color-scheme: light) {
:root:not([data-theme="dark"]) {
--color-bg: #ffffff;
--color-bg-secondary: #f6f8fa;
--color-bg-tertiary: #f0f3f6;
--color-border: #d0d7de;
--color-text: #24292f;
--color-text-muted: #57606a;
--color-text-emphasis: #1f2328;
--color-link: #0969da;
--color-link-hover: #0550ae;
--color-card-bg: #ffffff;
--color-card-hover: #f6f8fa;
--shadow: 0 1px 3px rgba(0,0,0,0.08), 0 8px 24px rgba(0,0,0,0.08);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.15);
}
}
@@ -140,6 +160,69 @@ a:hover {
color: var(--color-text-emphasis);
}
/* Theme Toggle */
.header-actions {
display: flex;
align-items: center;
gap: 16px;
}
.theme-toggle {
background: none;
border: none;
cursor: pointer;
padding: 8px;
border-radius: var(--border-radius);
color: var(--color-text);
display: flex;
align-items: center;
justify-content: center;
transition: all var(--transition);
}
.theme-toggle:hover {
background-color: var(--color-bg-tertiary);
color: var(--color-text-emphasis);
}
.theme-toggle svg {
width: 20px;
height: 20px;
}
.theme-toggle .icon-sun,
.theme-toggle .icon-moon {
display: none;
}
/* Show sun icon in dark mode (click to switch to light) */
:root:not([data-theme="light"]) .theme-toggle .icon-sun {
display: block;
}
:root:not([data-theme="light"]) .theme-toggle .icon-moon {
display: none;
}
/* Show moon icon in light mode (click to switch to dark) */
[data-theme="light"] .theme-toggle .icon-sun {
display: none;
}
[data-theme="light"] .theme-toggle .icon-moon {
display: block;
}
/* Handle auto mode with prefers-color-scheme */
@media (prefers-color-scheme: light) {
:root:not([data-theme="dark"]):not([data-theme="light"]) .theme-toggle .icon-sun {
display: none;
}
:root:not([data-theme="dark"]):not([data-theme="light"]) .theme-toggle .icon-moon {
display: block;
}
}
/* Hero Section */
.hero {
background: linear-gradient(180deg, var(--color-bg-secondary) 0%, var(--color-bg) 100%);
@@ -452,6 +535,26 @@ a:hover {
color: var(--color-text);
}
/* Spinner animation */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.spinner {
animation: spin 1s linear infinite;
}
/* Button states */
.btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
/* Modal */
.modal {
position: fixed;
@@ -570,6 +673,308 @@ a:hover {
border-color: var(--color-link);
}
/* Filters Bar */
.filters-bar {
display: flex;
gap: 16px;
margin-bottom: 20px;
flex-wrap: wrap;
align-items: center;
padding: 16px;
background-color: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
}
.filter-group {
display: flex;
align-items: center;
gap: 8px;
}
.filter-group label {
font-size: 13px;
color: var(--color-text-muted);
white-space: nowrap;
}
.filter-group select {
padding: 6px 12px;
font-size: 13px;
background-color: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
color: var(--color-text);
min-width: 150px;
cursor: pointer;
}
.filter-group select:focus {
outline: none;
border-color: var(--color-link);
}
.checkbox-label {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
user-select: none;
}
.checkbox-label input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
}
.btn-small {
padding: 6px 12px;
font-size: 12px;
}
/* Multi-Select Component */
.multi-select {
position: relative;
min-width: 180px;
}
.multi-select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
padding: 6px 12px;
font-size: 13px;
background-color: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
color: var(--color-text);
cursor: pointer;
text-align: left;
transition: all var(--transition);
}
.multi-select-trigger:hover {
border-color: var(--color-text-muted);
}
.multi-select.is-open .multi-select-trigger {
border-color: var(--color-link);
}
.multi-select-display {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--color-text-muted);
}
.multi-select-display.has-value {
color: var(--color-text);
}
.multi-select-arrow {
flex-shrink: 0;
transition: transform var(--transition);
color: var(--color-text-muted);
}
.multi-select.is-open .multi-select-arrow {
transform: rotate(180deg);
}
.multi-select-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
margin-top: 4px;
background-color: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
box-shadow: var(--shadow-lg);
z-index: 100;
display: none;
flex-direction: column;
max-height: 320px;
}
.multi-select.is-open .multi-select-dropdown {
display: flex;
}
.multi-select-search-wrapper {
padding: 8px;
border-bottom: 1px solid var(--color-border);
}
.multi-select-search {
width: 100%;
padding: 8px 10px;
font-size: 13px;
background-color: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
color: var(--color-text);
}
.multi-select-search:focus {
outline: none;
border-color: var(--color-link);
}
.multi-select-options {
flex: 1;
overflow-y: auto;
padding: 4px 0;
}
.multi-select-option {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
cursor: pointer;
transition: background-color var(--transition);
}
.multi-select-option:hover {
background-color: var(--color-bg-tertiary);
}
.multi-select-option input[type="checkbox"] {
display: none;
}
.multi-select-checkbox {
width: 16px;
height: 16px;
border: 1px solid var(--color-border);
border-radius: 3px;
background-color: var(--color-bg);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all var(--transition);
}
.multi-select-option input[type="checkbox"]:checked + .multi-select-checkbox {
background-color: var(--color-link);
border-color: var(--color-link);
}
.multi-select-option input[type="checkbox"]:checked + .multi-select-checkbox::after {
content: '';
width: 10px;
height: 10px;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'/%3E%3C/svg%3E");
background-size: contain;
}
.multi-select-label {
flex: 1;
font-size: 13px;
color: var(--color-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.multi-select-empty {
padding: 16px;
text-align: center;
color: var(--color-text-muted);
font-size: 13px;
}
.multi-select-actions {
display: flex;
gap: 8px;
padding: 8px;
border-top: 1px solid var(--color-border);
}
.multi-select-actions button {
flex: 1;
padding: 6px 12px;
font-size: 12px;
border-radius: var(--border-radius);
cursor: pointer;
transition: all var(--transition);
}
.multi-select-clear {
background-color: transparent;
border: 1px solid var(--color-border);
color: var(--color-text-muted);
}
.multi-select-clear:hover {
background-color: var(--color-bg-tertiary);
color: var(--color-text);
}
.multi-select-done {
background-color: var(--color-link);
border: 1px solid var(--color-link);
color: white;
}
.multi-select-done:hover {
background-color: var(--color-link-hover);
border-color: var(--color-link-hover);
}
/* Tag variants */
.tag-model {
background-color: rgba(88, 166, 255, 0.15);
color: var(--color-link);
}
.tag-none {
background-color: var(--color-bg-tertiary);
color: var(--color-text-muted);
font-style: italic;
}
.tag-handoffs {
background-color: rgba(210, 153, 34, 0.15);
color: var(--color-warning);
}
.tag-extension {
background-color: rgba(35, 134, 54, 0.15);
color: var(--color-success);
}
.tag-category {
background-color: rgba(130, 80, 223, 0.15);
color: #a371f7;
}
.tag-assets {
background-color: rgba(88, 166, 255, 0.15);
color: var(--color-link);
}
.tag-collection {
background-color: rgba(210, 153, 34, 0.15);
color: var(--color-warning);
}
.tag-featured {
background-color: rgba(210, 153, 34, 0.2);
color: var(--color-warning);
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.results-count {
font-size: 14px;
color: var(--color-text-muted);

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
[
{
"items": [
{
"id": "awesome-copilot",
"name": "Awesome Copilot",
@@ -1976,4 +1977,153 @@
"path": "collections/typespec-m365-copilot.collection.yml",
"filename": "typespec-m365-copilot.collection.yml"
}
]
],
"filters": {
"tags": [
"a11y",
"accessibility",
"actor",
"adaptive-cards",
"agent-development",
"agents",
"ai",
"ai-ethics",
"angular",
"api",
"api-plugins",
"architecture",
"aspnet",
"assumption-testing",
"async",
"async-await",
"attributes",
"automation",
"azure",
"best-practices",
"bicep",
"business-intelligence",
"cast-imaging",
"cicd",
"clojure",
"cloud",
"code-apps",
"code-generation",
"code-quality",
"component-framework",
"composer",
"concurrency",
"connectors",
"copilot-sdk",
"copilot-studio",
"csharp",
"css",
"custom-connector",
"data-management",
"data-modeling",
"database",
"dataverse",
"dax",
"dba",
"declarative-agents",
"devops",
"discovery",
"dotnet",
"enterprise",
"epic",
"fastapi",
"fastmcp",
"feature",
"feature-flags",
"frontend",
"gem",
"github-copilot",
"go",
"golang",
"html",
"impact-analysis",
"implementation",
"incident-response",
"infrastructure",
"integration",
"interactive-programming",
"ios",
"java",
"javadoc",
"javascript",
"jest",
"jpa",
"json-rpc",
"junit",
"kotlin",
"kotlin-multiplatform",
"ktor",
"m365-copilot",
"macos",
"macros",
"mcp",
"meta",
"microsoft-365",
"migration",
"model-context-protocol",
"nestjs",
"nodejs",
"nunit",
"observability",
"oncall",
"openapi",
"optimization",
"owasp",
"pcf",
"performance",
"php",
"planning",
"playwright",
"postgresql",
"power-apps",
"power-bi",
"power-platform",
"product",
"project-management",
"prompt-engineering",
"python",
"quality",
"quarkus",
"queries",
"rails",
"react",
"reactive-streams",
"reactor",
"repl",
"research",
"rmcp",
"ruby",
"rust",
"sdk",
"security",
"server-development",
"serverless",
"software-analysis",
"spring-boot",
"springboot",
"sql",
"sql-server",
"swift",
"task",
"tasks",
"tdd",
"team",
"technical-spike",
"terraform",
"testing",
"tokio",
"typescript",
"typespec",
"unit-tests",
"ux",
"validation",
"visualization",
"vue",
"web"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"generated": "2026-01-28T02:42:05.621Z",
"generated": "2026-01-28T03:53:29.513Z",
"counts": {
"agents": 140,
"prompts": 134,

View File

@@ -1,4 +1,5 @@
[
{
"items": [
{
"id": "dotnet-upgrade",
"title": ".NET Upgrade Analysis Prompts",
@@ -1939,4 +1940,84 @@
"path": "prompts/write-coding-standards-from-file.prompt.md",
"filename": "write-coding-standards-from-file.prompt.md"
}
]
],
"filters": {
"tools": [
"Microsoft Docs",
"agent",
"azure_get_schema_for_Bicep",
"bicepschema",
"changes",
"codebase",
"context7/*",
"createFile",
"create_issue",
"create_pull_request",
"edit",
"edit/createFile",
"edit/editFiles",
"editFiles",
"execute",
"extensions",
"fetch",
"findTestFiles",
"get_issue",
"get_issue_comments",
"get_me",
"get_pull_request",
"get_pull_request_comments",
"get_pull_request_diff",
"get_pull_request_files",
"get_pull_request_reviews",
"get_pull_request_status",
"github",
"githubRepo",
"grep_search",
"list_dir",
"list_issues",
"list_pull_requests",
"microsoft.docs.mcp",
"new",
"openSimpleBrowser",
"playwright",
"playwright/*",
"problems",
"pylanceRunCodeSnippet",
"read",
"read_file",
"replace_string_in_file",
"request_copilot_review",
"runCommands",
"runCommands/getTerminalOutput",
"runCommands/runInTerminal",
"runCommands/terminalLastCommand",
"runCommands/terminalSelection",
"runInTerminal",
"runInTerminal2",
"runNotebooks",
"runTasks",
"runTests",
"run_in_terminal",
"search",
"search/codebase",
"search/readFile",
"search/searchResults",
"search/textSearch",
"search_issues",
"terminalCommand",
"testFailure",
"think",
"todo",
"todos",
"update_issue",
"update_pull_request",
"upstash/context7/*",
"usages",
"vscode",
"vscodeAPI",
"web",
"web/fetch",
"writeTest"
]
}
}

View File

@@ -1,12 +1,23 @@
[
{
"items": [
{
"id": "agentic-eval",
"name": "agentic-eval",
"title": "Agentic Eval",
"description": "Patterns and techniques for evaluating and improving AI agent outputs. Use this skill when:\n- Implementing self-critique and reflection loops\n- Building evaluator-optimizer pipelines for quality-critical generation\n- Creating test-driven code refinement workflows\n- Designing rubric-based or LLM-as-judge evaluation systems\n- Adding iterative improvement to agent outputs (code, reports, analysis)\n- Measuring and improving agent response quality",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Testing",
"path": "skills/agentic-eval",
"skillFile": "skills/agentic-eval/SKILL.md"
"skillFile": "skills/agentic-eval/SKILL.md",
"files": [
{
"path": "skills/agentic-eval/SKILL.md",
"name": "SKILL.md",
"size": 5940
}
]
},
{
"id": "appinsights-instrumentation",
@@ -22,8 +33,53 @@
"references/PYTHON.md",
"scripts/appinsights.ps1"
],
"hasAssets": true,
"assetCount": 7,
"category": "Azure",
"path": "skills/appinsights-instrumentation",
"skillFile": "skills/appinsights-instrumentation/SKILL.md"
"skillFile": "skills/appinsights-instrumentation/SKILL.md",
"files": [
{
"path": "skills/appinsights-instrumentation/LICENSE.txt",
"name": "LICENSE.txt",
"size": 1078
},
{
"path": "skills/appinsights-instrumentation/SKILL.md",
"name": "SKILL.md",
"size": 2462
},
{
"path": "skills/appinsights-instrumentation/examples/appinsights.bicep",
"name": "examples/appinsights.bicep",
"size": 759
},
{
"path": "skills/appinsights-instrumentation/references/ASPNETCORE.md",
"name": "references/ASPNETCORE.md",
"size": 1711
},
{
"path": "skills/appinsights-instrumentation/references/AUTO.md",
"name": "references/AUTO.md",
"size": 891
},
{
"path": "skills/appinsights-instrumentation/references/NODEJS.md",
"name": "references/NODEJS.md",
"size": 1815
},
{
"path": "skills/appinsights-instrumentation/references/PYTHON.md",
"name": "references/PYTHON.md",
"size": 1812
},
{
"path": "skills/appinsights-instrumentation/scripts/appinsights.ps1",
"name": "scripts/appinsights.ps1",
"size": 1221
}
]
},
{
"id": "azure-deployment-preflight",
@@ -35,8 +91,33 @@
"references/REPORT-TEMPLATE.md",
"references/VALIDATION-COMMANDS.md"
],
"hasAssets": true,
"assetCount": 3,
"category": "Azure",
"path": "skills/azure-deployment-preflight",
"skillFile": "skills/azure-deployment-preflight/SKILL.md"
"skillFile": "skills/azure-deployment-preflight/SKILL.md",
"files": [
{
"path": "skills/azure-deployment-preflight/SKILL.md",
"name": "SKILL.md",
"size": 7490
},
{
"path": "skills/azure-deployment-preflight/references/ERROR-HANDLING.md",
"name": "references/ERROR-HANDLING.md",
"size": 8896
},
{
"path": "skills/azure-deployment-preflight/references/REPORT-TEMPLATE.md",
"name": "references/REPORT-TEMPLATE.md",
"size": 7458
},
{
"path": "skills/azure-deployment-preflight/references/VALIDATION-COMMANDS.md",
"name": "references/VALIDATION-COMMANDS.md",
"size": 8379
}
]
},
{
"id": "azure-devops-cli",
@@ -44,8 +125,18 @@
"title": "Azure Devops Cli",
"description": "Manage Azure DevOps resources via CLI including projects, repos, pipelines, builds, pull requests, work items, artifacts, and service endpoints. Use when working with Azure DevOps, az commands, devops automation, CI/CD, or when user mentions Azure DevOps CLI.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Azure",
"path": "skills/azure-devops-cli",
"skillFile": "skills/azure-devops-cli/SKILL.md"
"skillFile": "skills/azure-devops-cli/SKILL.md",
"files": [
{
"path": "skills/azure-devops-cli/SKILL.md",
"name": "SKILL.md",
"size": 55003
}
]
},
{
"id": "azure-resource-visualizer",
@@ -56,8 +147,28 @@
"LICENSE.txt",
"assets/template-architecture.md"
],
"hasAssets": true,
"assetCount": 2,
"category": "Azure",
"path": "skills/azure-resource-visualizer",
"skillFile": "skills/azure-resource-visualizer/SKILL.md"
"skillFile": "skills/azure-resource-visualizer/SKILL.md",
"files": [
{
"path": "skills/azure-resource-visualizer/LICENSE.txt",
"name": "LICENSE.txt",
"size": 1078
},
{
"path": "skills/azure-resource-visualizer/SKILL.md",
"name": "SKILL.md",
"size": 9772
},
{
"path": "skills/azure-resource-visualizer/assets/template-architecture.md",
"name": "assets/template-architecture.md",
"size": 970
}
]
},
{
"id": "azure-role-selector",
@@ -67,8 +178,23 @@
"assets": [
"LICENSE.txt"
],
"hasAssets": true,
"assetCount": 1,
"category": "Azure",
"path": "skills/azure-role-selector",
"skillFile": "skills/azure-role-selector/SKILL.md"
"skillFile": "skills/azure-role-selector/SKILL.md",
"files": [
{
"path": "skills/azure-role-selector/LICENSE.txt",
"name": "LICENSE.txt",
"size": 1078
},
{
"path": "skills/azure-role-selector/SKILL.md",
"name": "SKILL.md",
"size": 983
}
]
},
{
"id": "azure-static-web-apps",
@@ -76,8 +202,18 @@
"title": "Azure Static Web Apps",
"description": "Helps create, configure, and deploy Azure Static Web Apps using the SWA CLI. Use when deploying static sites to Azure, setting up SWA local development, configuring staticwebapp.config.json, adding Azure Functions APIs to SWA, or setting up GitHub Actions CI/CD for Static Web Apps.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Azure",
"path": "skills/azure-static-web-apps",
"skillFile": "skills/azure-static-web-apps/SKILL.md"
"skillFile": "skills/azure-static-web-apps/SKILL.md",
"files": [
{
"path": "skills/azure-static-web-apps/SKILL.md",
"name": "SKILL.md",
"size": 9499
}
]
},
{
"id": "chrome-devtools",
@@ -85,8 +221,18 @@
"title": "Chrome Devtools",
"description": "Expert-level browser automation, debugging, and performance analysis using Chrome DevTools MCP. Use for interacting with web pages, capturing screenshots, analyzing network traffic, and profiling performance.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Other",
"path": "skills/chrome-devtools",
"skillFile": "skills/chrome-devtools/SKILL.md"
"skillFile": "skills/chrome-devtools/SKILL.md",
"files": [
{
"path": "skills/chrome-devtools/SKILL.md",
"name": "SKILL.md",
"size": 4145
}
]
},
{
"id": "gh-cli",
@@ -94,8 +240,18 @@
"title": "Gh Cli",
"description": "GitHub CLI (gh) comprehensive reference for repositories, issues, pull requests, Actions, projects, releases, gists, codespaces, organizations, extensions, and all GitHub operations from the command line.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Git & GitHub",
"path": "skills/gh-cli",
"skillFile": "skills/gh-cli/SKILL.md"
"skillFile": "skills/gh-cli/SKILL.md",
"files": [
{
"path": "skills/gh-cli/SKILL.md",
"name": "SKILL.md",
"size": 40503
}
]
},
{
"id": "git-commit",
@@ -103,8 +259,18 @@
"title": "Git Commit",
"description": "Execute git commit with conventional commit message analysis, intelligent staging, and message generation. Use when user asks to commit changes, create a git commit, or mentions \"/commit\". Supports: (1) Auto-detecting type and scope from changes, (2) Generating conventional commit messages from diff, (3) Interactive commit with optional type/scope/description overrides, (4) Intelligent file staging for logical grouping",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Git & GitHub",
"path": "skills/git-commit",
"skillFile": "skills/git-commit/SKILL.md"
"skillFile": "skills/git-commit/SKILL.md",
"files": [
{
"path": "skills/git-commit/SKILL.md",
"name": "SKILL.md",
"size": 3198
}
]
},
{
"id": "github-issues",
@@ -114,8 +280,23 @@
"assets": [
"references/templates.md"
],
"hasAssets": true,
"assetCount": 1,
"category": "Git & GitHub",
"path": "skills/github-issues",
"skillFile": "skills/github-issues/SKILL.md"
"skillFile": "skills/github-issues/SKILL.md",
"files": [
{
"path": "skills/github-issues/SKILL.md",
"name": "SKILL.md",
"size": 4783
},
{
"path": "skills/github-issues/references/templates.md",
"name": "references/templates.md",
"size": 1384
}
]
},
{
"id": "image-manipulation-image-magick",
@@ -123,8 +304,18 @@
"title": "Image Manipulation Image Magick",
"description": "Process and manipulate images using ImageMagick. Supports resizing, format conversion, batch processing, and retrieving image metadata. Use when working with images, creating thumbnails, resizing wallpapers, or performing batch image operations.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Other",
"path": "skills/image-manipulation-image-magick",
"skillFile": "skills/image-manipulation-image-magick/SKILL.md"
"skillFile": "skills/image-manipulation-image-magick/SKILL.md",
"files": [
{
"path": "skills/image-manipulation-image-magick/SKILL.md",
"name": "SKILL.md",
"size": 6963
}
]
},
{
"id": "legacy-circuit-mockups",
@@ -153,8 +344,118 @@
"references/minipro.md",
"references/t48eeprom-programmer.md"
],
"hasAssets": true,
"assetCount": 20,
"category": "Diagrams",
"path": "skills/legacy-circuit-mockups",
"skillFile": "skills/legacy-circuit-mockups/SKILL.md"
"skillFile": "skills/legacy-circuit-mockups/SKILL.md",
"files": [
{
"path": "skills/legacy-circuit-mockups/SKILL.md",
"name": "SKILL.md",
"size": 9249
},
{
"path": "skills/legacy-circuit-mockups/references/28256-eeprom.md",
"name": "references/28256-eeprom.md",
"size": 4667
},
{
"path": "skills/legacy-circuit-mockups/references/555.md",
"name": "references/555.md",
"size": 33114
},
{
"path": "skills/legacy-circuit-mockups/references/6502.md",
"name": "references/6502.md",
"size": 5807
},
{
"path": "skills/legacy-circuit-mockups/references/6522.md",
"name": "references/6522.md",
"size": 5881
},
{
"path": "skills/legacy-circuit-mockups/references/6C62256.md",
"name": "references/6C62256.md",
"size": 4214
},
{
"path": "skills/legacy-circuit-mockups/references/7400-series.md",
"name": "references/7400-series.md",
"size": 4759
},
{
"path": "skills/legacy-circuit-mockups/references/assembly-compiler.md",
"name": "references/assembly-compiler.md",
"size": 4860
},
{
"path": "skills/legacy-circuit-mockups/references/assembly-language.md",
"name": "references/assembly-language.md",
"size": 5359
},
{
"path": "skills/legacy-circuit-mockups/references/basic-electronic-components.md",
"name": "references/basic-electronic-components.md",
"size": 2784
},
{
"path": "skills/legacy-circuit-mockups/references/breadboard.md",
"name": "references/breadboard.md",
"size": 5025
},
{
"path": "skills/legacy-circuit-mockups/references/common-breadboard-components.md",
"name": "references/common-breadboard-components.md",
"size": 6565
},
{
"path": "skills/legacy-circuit-mockups/references/connecting-electronic-components.md",
"name": "references/connecting-electronic-components.md",
"size": 15302
},
{
"path": "skills/legacy-circuit-mockups/references/emulator-28256-eeprom.md",
"name": "references/emulator-28256-eeprom.md",
"size": 5198
},
{
"path": "skills/legacy-circuit-mockups/references/emulator-6502.md",
"name": "references/emulator-6502.md",
"size": 5853
},
{
"path": "skills/legacy-circuit-mockups/references/emulator-6522.md",
"name": "references/emulator-6522.md",
"size": 6698
},
{
"path": "skills/legacy-circuit-mockups/references/emulator-6C62256.md",
"name": "references/emulator-6C62256.md",
"size": 4869
},
{
"path": "skills/legacy-circuit-mockups/references/emulator-lcd.md",
"name": "references/emulator-lcd.md",
"size": 5118
},
{
"path": "skills/legacy-circuit-mockups/references/lcd.md",
"name": "references/lcd.md",
"size": 5291
},
{
"path": "skills/legacy-circuit-mockups/references/minipro.md",
"name": "references/minipro.md",
"size": 4130
},
{
"path": "skills/legacy-circuit-mockups/references/t48eeprom-programmer.md",
"name": "references/t48eeprom-programmer.md",
"size": 4398
}
]
},
{
"id": "make-skill-template",
@@ -162,8 +463,18 @@
"title": "Make Skill Template",
"description": "Create new Agent Skills for GitHub Copilot from prompts or by duplicating this template. Use when asked to \"create a skill\", \"make a new skill\", \"scaffold a skill\", or when building specialized AI capabilities with bundled resources. Generates SKILL.md files with proper frontmatter, directory structure, and optional scripts/references/assets folders.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Git & GitHub",
"path": "skills/make-skill-template",
"skillFile": "skills/make-skill-template/SKILL.md"
"skillFile": "skills/make-skill-template/SKILL.md",
"files": [
{
"path": "skills/make-skill-template/SKILL.md",
"name": "SKILL.md",
"size": 5368
}
]
},
{
"id": "mcp-cli",
@@ -171,8 +482,18 @@
"title": "Mcp Cli",
"description": "Interface for MCP (Model Context Protocol) servers via CLI. Use when you need to interact with external tools, APIs, or data sources through MCP servers, list available MCP servers/tools, or call MCP tools from command line.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "CLI Tools",
"path": "skills/mcp-cli",
"skillFile": "skills/mcp-cli/SKILL.md"
"skillFile": "skills/mcp-cli/SKILL.md",
"files": [
{
"path": "skills/mcp-cli/SKILL.md",
"name": "SKILL.md",
"size": 2539
}
]
},
{
"id": "microsoft-code-reference",
@@ -180,8 +501,18 @@
"title": "Microsoft Code Reference",
"description": "Look up Microsoft API references, find working code samples, and verify SDK code is correct. Use when working with Azure SDKs, .NET libraries, or Microsoft APIs—to find the right method, check parameters, get working examples, or troubleshoot errors. Catches hallucinated methods, wrong signatures, and deprecated patterns by querying official docs.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Azure",
"path": "skills/microsoft-code-reference",
"skillFile": "skills/microsoft-code-reference/SKILL.md"
"skillFile": "skills/microsoft-code-reference/SKILL.md",
"files": [
{
"path": "skills/microsoft-code-reference/SKILL.md",
"name": "SKILL.md",
"size": 3353
}
]
},
{
"id": "microsoft-docs",
@@ -189,8 +520,18 @@
"title": "Microsoft Docs",
"description": "Query official Microsoft documentation to understand concepts, find tutorials, and learn how services work. Use for Azure, .NET, Microsoft 365, Windows, Power Platform, and all Microsoft technologies. Get accurate, current information from learn.microsoft.com and other official Microsoft websites—architecture overviews, quickstarts, configuration guides, limits, and best practices.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Azure",
"path": "skills/microsoft-docs",
"skillFile": "skills/microsoft-docs/SKILL.md"
"skillFile": "skills/microsoft-docs/SKILL.md",
"files": [
{
"path": "skills/microsoft-docs/SKILL.md",
"name": "SKILL.md",
"size": 2142
}
]
},
{
"id": "nuget-manager",
@@ -198,8 +539,18 @@
"title": "Nuget Manager",
"description": "Manage NuGet packages in .NET projects/solutions. Use this skill when adding, removing, or updating NuGet package versions. It enforces using `dotnet` CLI for package management and provides strict procedures for direct file edits only when updating versions.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "CLI Tools",
"path": "skills/nuget-manager",
"skillFile": "skills/nuget-manager/SKILL.md"
"skillFile": "skills/nuget-manager/SKILL.md",
"files": [
{
"path": "skills/nuget-manager/SKILL.md",
"name": "SKILL.md",
"size": 3418
}
]
},
{
"id": "plantuml-ascii",
@@ -207,8 +558,18 @@
"title": "Plantuml Ascii",
"description": "Generate ASCII art diagrams using PlantUML text mode. Use when user asks to create ASCII diagrams, text-based diagrams, terminal-friendly diagrams, or mentions plantuml ascii, text diagram, ascii art diagram. Supports: Converting PlantUML diagrams to ASCII art, Creating sequence diagrams, class diagrams, flowcharts in ASCII format, Generating Unicode-enhanced ASCII art with -utxt flag",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Diagrams",
"path": "skills/plantuml-ascii",
"skillFile": "skills/plantuml-ascii/SKILL.md"
"skillFile": "skills/plantuml-ascii/SKILL.md",
"files": [
{
"path": "skills/plantuml-ascii/SKILL.md",
"name": "SKILL.md",
"size": 6096
}
]
},
{
"id": "prd",
@@ -216,8 +577,18 @@
"title": "Prd",
"description": "Generate high-quality Product Requirements Documents (PRDs) for software systems and AI-powered features. Includes executive summaries, user stories, technical specifications, and risk analysis.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Other",
"path": "skills/prd",
"skillFile": "skills/prd/SKILL.md"
"skillFile": "skills/prd/SKILL.md",
"files": [
{
"path": "skills/prd/SKILL.md",
"name": "SKILL.md",
"size": 4307
}
]
},
{
"id": "refactor",
@@ -225,8 +596,18 @@
"title": "Refactor",
"description": "Surgical code refactoring to improve maintainability without changing behavior. Covers extracting functions, renaming variables, breaking down god functions, improving type safety, eliminating code smells, and applying design patterns. Less drastic than repo-rebuilder; use for gradual improvements.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Other",
"path": "skills/refactor",
"skillFile": "skills/refactor/SKILL.md"
"skillFile": "skills/refactor/SKILL.md",
"files": [
{
"path": "skills/refactor/SKILL.md",
"name": "SKILL.md",
"size": 16842
}
]
},
{
"id": "scoutqa-test",
@@ -234,8 +615,18 @@
"title": "Scoutqa Test",
"description": "This skill should be used when the user asks to \"test this website\", \"run exploratory testing\", \"check for accessibility issues\", \"verify the login flow works\", \"find bugs on this page\", or requests automated QA testing. Triggers on web application testing scenarios including smoke tests, accessibility audits, e-commerce flows, and user flow validation using ScoutQA CLI. IMPORTANT: Use this skill proactively after implementing web application features to verify they work correctly - don't wait for the user to ask for testing.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Testing",
"path": "skills/scoutqa-test",
"skillFile": "skills/scoutqa-test/SKILL.md"
"skillFile": "skills/scoutqa-test/SKILL.md",
"files": [
{
"path": "skills/scoutqa-test/SKILL.md",
"name": "SKILL.md",
"size": 12001
}
]
},
{
"id": "snowflake-semanticview",
@@ -243,8 +634,18 @@
"title": "Snowflake Semanticview",
"description": "Create, alter, and validate Snowflake semantic views using Snowflake CLI (snow). Use when asked to build or troubleshoot semantic views/semantic layer definitions with CREATE/ALTER SEMANTIC VIEW, to validate semantic-view DDL against Snowflake via CLI, or to guide Snowflake CLI installation and connection setup.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "CLI Tools",
"path": "skills/snowflake-semanticview",
"skillFile": "skills/snowflake-semanticview/SKILL.md"
"skillFile": "skills/snowflake-semanticview/SKILL.md",
"files": [
{
"path": "skills/snowflake-semanticview/SKILL.md",
"name": "SKILL.md",
"size": 4411
}
]
},
{
"id": "vscode-ext-commands",
@@ -252,8 +653,18 @@
"title": "Vscode Ext Commands",
"description": "Guidelines for contributing commands in VS Code extensions. Indicates naming convention, visibility, localization and other relevant attributes, following VS Code extension development guidelines, libraries and good practices",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "VS Code",
"path": "skills/vscode-ext-commands",
"skillFile": "skills/vscode-ext-commands/SKILL.md"
"skillFile": "skills/vscode-ext-commands/SKILL.md",
"files": [
{
"path": "skills/vscode-ext-commands/SKILL.md",
"name": "SKILL.md",
"size": 1545
}
]
},
{
"id": "vscode-ext-localization",
@@ -261,8 +672,18 @@
"title": "Vscode Ext Localization",
"description": "Guidelines for proper localization of VS Code extensions, following VS Code extension development guidelines, libraries and good practices",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "VS Code",
"path": "skills/vscode-ext-localization",
"skillFile": "skills/vscode-ext-localization/SKILL.md"
"skillFile": "skills/vscode-ext-localization/SKILL.md",
"files": [
{
"path": "skills/vscode-ext-localization/SKILL.md",
"name": "SKILL.md",
"size": 1473
}
]
},
{
"id": "web-design-reviewer",
@@ -273,8 +694,28 @@
"references/framework-fixes.md",
"references/visual-checklist.md"
],
"hasAssets": true,
"assetCount": 2,
"category": "Diagrams",
"path": "skills/web-design-reviewer",
"skillFile": "skills/web-design-reviewer/SKILL.md"
"skillFile": "skills/web-design-reviewer/SKILL.md",
"files": [
{
"path": "skills/web-design-reviewer/SKILL.md",
"name": "SKILL.md",
"size": 10520
},
{
"path": "skills/web-design-reviewer/references/framework-fixes.md",
"name": "references/framework-fixes.md",
"size": 7437
},
{
"path": "skills/web-design-reviewer/references/visual-checklist.md",
"name": "references/visual-checklist.md",
"size": 5989
}
]
},
{
"id": "webapp-testing",
@@ -284,8 +725,23 @@
"assets": [
"test-helper.js"
],
"hasAssets": true,
"assetCount": 1,
"category": "Testing",
"path": "skills/webapp-testing",
"skillFile": "skills/webapp-testing/SKILL.md"
"skillFile": "skills/webapp-testing/SKILL.md",
"files": [
{
"path": "skills/webapp-testing/SKILL.md",
"name": "SKILL.md",
"size": 3311
},
{
"path": "skills/webapp-testing/test-helper.js",
"name": "test-helper.js",
"size": 1521
}
]
},
{
"id": "workiq-copilot",
@@ -293,7 +749,34 @@
"title": "Workiq Copilot",
"description": "Guides the Copilot CLI on how to use the WorkIQ CLI/MCP server to query Microsoft 365 Copilot data (emails, meetings, docs, Teams, people) for live context, summaries, and recommendations.",
"assets": [],
"hasAssets": false,
"assetCount": 0,
"category": "Microsoft",
"path": "skills/workiq-copilot",
"skillFile": "skills/workiq-copilot/SKILL.md"
"skillFile": "skills/workiq-copilot/SKILL.md",
"files": [
{
"path": "skills/workiq-copilot/SKILL.md",
"name": "SKILL.md",
"size": 5539
}
]
]
}
],
"filters": {
"categories": [
"Azure",
"CLI Tools",
"Diagrams",
"Git & GitHub",
"Microsoft",
"Other",
"Testing",
"VS Code"
],
"hasAssets": [
"Yes",
"No"
]
}
}

View File

@@ -7,6 +7,7 @@
<meta name="description" content="Community-contributed instructions, prompts, agents, and skills for GitHub Copilot">
<link rel="stylesheet" href="css/styles.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🤖</text></svg>">
<script src="js/theme.js"></script>
</head>
<body>
<header class="site-header">
@@ -25,6 +26,15 @@
<a href="pages/tools.html">Tools</a>
<a href="pages/samples.html">Samples</a>
</nav>
<div class="header-actions">
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
</svg>
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
</svg>
</button>
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
@@ -32,6 +42,7 @@
</a>
</div>
</div>
</div>
</header>
<main>

13
website/js/jszip.min.js vendored Normal file

File diff suppressed because one or more lines are too long

209
website/js/multi-select.js Normal file
View File

@@ -0,0 +1,209 @@
/**
* Multi-select dropdown component
* Creates a dropdown with checkboxes for multiple selections
*/
class MultiSelect {
constructor(container, options = {}) {
this.container = typeof container === 'string' ? document.querySelector(container) : container;
this.options = {
placeholder: options.placeholder || 'Select...',
searchable: options.searchable !== false,
onChange: options.onChange || (() => {}),
maxDisplay: options.maxDisplay || 2,
};
this.items = [];
this.selected = new Set();
this.isOpen = false;
this.searchQuery = '';
this.render();
this.setupEventListeners();
}
render() {
this.container.classList.add('multi-select');
this.container.innerHTML = `
<button type="button" class="multi-select-trigger" aria-haspopup="listbox" aria-expanded="false">
<span class="multi-select-display">${this.options.placeholder}</span>
<svg class="multi-select-arrow" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="M4.427 7.427l3.396 3.396a.25.25 0 00.354 0l3.396-3.396A.25.25 0 0011.396 7H4.604a.25.25 0 00-.177.427z"/>
</svg>
</button>
<div class="multi-select-dropdown" role="listbox" aria-multiselectable="true">
${this.options.searchable ? `
<div class="multi-select-search-wrapper">
<input type="text" class="multi-select-search" placeholder="Search..." autocomplete="off">
</div>
` : ''}
<div class="multi-select-options"></div>
<div class="multi-select-actions">
<button type="button" class="multi-select-clear">Clear</button>
<button type="button" class="multi-select-done">Done</button>
</div>
</div>
`;
this.trigger = this.container.querySelector('.multi-select-trigger');
this.display = this.container.querySelector('.multi-select-display');
this.dropdown = this.container.querySelector('.multi-select-dropdown');
this.optionsContainer = this.container.querySelector('.multi-select-options');
this.searchInput = this.container.querySelector('.multi-select-search');
this.clearBtn = this.container.querySelector('.multi-select-clear');
this.doneBtn = this.container.querySelector('.multi-select-done');
}
setupEventListeners() {
// Toggle dropdown
this.trigger.addEventListener('click', (e) => {
e.stopPropagation();
this.toggle();
});
// Search
if (this.searchInput) {
this.searchInput.addEventListener('input', () => {
this.searchQuery = this.searchInput.value.toLowerCase();
this.renderOptions();
});
this.searchInput.addEventListener('click', (e) => e.stopPropagation());
}
// Clear selection
this.clearBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.clearSelection();
});
// Done button
this.doneBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.close();
});
// Close on outside click
document.addEventListener('click', (e) => {
if (!this.container.contains(e.target)) {
this.close();
}
});
// Keyboard navigation
this.container.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.close();
}
});
}
setItems(items) {
this.items = items.map(item => {
if (typeof item === 'string') {
return { value: item, label: item };
}
return item;
});
this.renderOptions();
}
renderOptions() {
const filteredItems = this.items.filter(item => {
if (!this.searchQuery) return true;
return item.label.toLowerCase().includes(this.searchQuery);
});
if (filteredItems.length === 0) {
this.optionsContainer.innerHTML = '<div class="multi-select-empty">No options found</div>';
return;
}
this.optionsContainer.innerHTML = filteredItems.map(item => `
<label class="multi-select-option" data-value="${this.escapeHtml(item.value)}" title="${this.escapeHtml(item.label)}">
<input type="checkbox" ${this.selected.has(item.value) ? 'checked' : ''}>
<span class="multi-select-checkbox"></span>
<span class="multi-select-label">${this.escapeHtml(item.label)}</span>
</label>
`).join('');
// Add change listeners to checkboxes
this.optionsContainer.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', (e) => {
const value = e.target.closest('.multi-select-option').dataset.value;
if (e.target.checked) {
this.selected.add(value);
} else {
this.selected.delete(value);
}
this.updateDisplay();
this.options.onChange(this.getSelected());
});
});
}
updateDisplay() {
const selected = this.getSelected();
if (selected.length === 0) {
this.display.textContent = this.options.placeholder;
this.display.classList.remove('has-value');
} else if (selected.length <= this.options.maxDisplay) {
this.display.textContent = selected.join(', ');
this.display.classList.add('has-value');
} else {
this.display.textContent = `${selected.length} selected`;
this.display.classList.add('has-value');
}
}
toggle() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
open() {
this.isOpen = true;
this.container.classList.add('is-open');
this.trigger.setAttribute('aria-expanded', 'true');
if (this.searchInput) {
this.searchInput.value = '';
this.searchQuery = '';
this.renderOptions();
setTimeout(() => this.searchInput.focus(), 10);
}
}
close() {
this.isOpen = false;
this.container.classList.remove('is-open');
this.trigger.setAttribute('aria-expanded', 'false');
}
getSelected() {
return Array.from(this.selected);
}
setSelected(values) {
this.selected = new Set(values);
this.renderOptions();
this.updateDisplay();
}
clearSelection() {
this.selected.clear();
this.renderOptions();
this.updateDisplay();
this.options.onChange(this.getSelected());
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = MultiSelect;
}

74
website/js/theme.js Normal file
View File

@@ -0,0 +1,74 @@
/**
* Theme management for the Awesome Copilot website
* Supports light/dark mode with user preference storage
*/
const THEME_KEY = 'awesome-copilot-theme';
/**
* Get the current theme preference
* Priority: localStorage > system preference > dark (default)
*/
function getThemePreference() {
const stored = localStorage.getItem(THEME_KEY);
if (stored === 'light' || stored === 'dark') {
return stored;
}
// Check system preference
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
return 'light';
}
return 'dark';
}
/**
* Apply theme to the document
*/
function applyTheme(theme) {
if (theme === 'light') {
document.documentElement.setAttribute('data-theme', 'light');
} else {
document.documentElement.setAttribute('data-theme', 'dark');
}
}
/**
* Toggle between light and dark theme
*/
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme');
const newTheme = current === 'light' ? 'dark' : 'light';
applyTheme(newTheme);
localStorage.setItem(THEME_KEY, newTheme);
}
/**
* Initialize theme on page load
*/
function initTheme() {
// Apply theme immediately to prevent flash
const theme = getThemePreference();
applyTheme(theme);
// Listen for system theme changes
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {
// Only auto-switch if user hasn't set a preference
const stored = localStorage.getItem(THEME_KEY);
if (!stored) {
applyTheme(e.matches ? 'light' : 'dark');
}
});
}
}
// Initialize theme immediately (before DOM ready to prevent flash)
initTheme();
// Setup toggle button after DOM ready
document.addEventListener('DOMContentLoaded', () => {
const toggleBtn = document.getElementById('theme-toggle');
if (toggleBtn) {
toggleBtn.addEventListener('click', toggleTheme);
}
});

View File

@@ -78,6 +78,13 @@ function getGitHubUrl(filePath) {
return `${REPO_GITHUB_URL}/${filePath}`;
}
/**
* Get raw GitHub URL for a file (for fetching content)
*/
function getRawGitHubUrl(filePath) {
return `${REPO_BASE_URL}/${filePath}`;
}
/**
* Show a toast notification
*/

View File

@@ -7,6 +7,7 @@
<meta name="description" content="Custom agents for specialized GitHub Copilot experiences">
<link rel="stylesheet" href="../css/styles.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🤖</text></svg>">
<script src="../js/theme.js"></script>
</head>
<body>
<header class="site-header">
@@ -25,6 +26,15 @@
<a href="tools.html">Tools</a>
<a href="samples.html">Samples</a>
</nav>
<div class="header-actions">
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
</svg>
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
</svg>
</button>
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
@@ -32,6 +42,7 @@
</a>
</div>
</div>
</div>
</header>
<main>
@@ -47,6 +58,26 @@
<div class="search-bar">
<input type="text" id="search-input" placeholder="Search agents..." autocomplete="off">
</div>
<!-- Filters -->
<div class="filters-bar" id="filters-bar">
<div class="filter-group">
<label>Model:</label>
<div id="filter-model" class="multi-select-container"></div>
</div>
<div class="filter-group">
<label>Tool:</label>
<div id="filter-tool" class="multi-select-container"></div>
</div>
<div class="filter-group">
<label class="checkbox-label">
<input type="checkbox" id="filter-handoffs">
Has Handoffs
</label>
</div>
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
</div>
<div class="results-count" id="results-count"></div>
<div class="resource-list" id="resource-list">
<div class="loading">Loading agents...</div>
@@ -100,45 +131,131 @@
<script src="../js/utils.js"></script>
<script src="../js/search.js"></script>
<script src="../js/multi-select.js"></script>
<script src="../js/app.js"></script>
<script>
// Page-specific initialization
const resourceType = 'agent';
const dataFile = 'agents.json';
let allItems = [];
let filters = { models: [], tools: [] };
let search = new FuzzySearch();
let modelSelect, toolSelect;
// Current filter state
let currentFilters = {
models: [],
tools: [],
hasHandoffs: false,
};
async function initPage() {
const list = document.getElementById('resource-list');
const countEl = document.getElementById('results-count');
const searchInput = document.getElementById('search-input');
const handoffsCheckbox = document.getElementById('filter-handoffs');
const clearFiltersBtn = document.getElementById('clear-filters');
// Load data
const data = await fetchData(dataFile);
if (!data) {
if (!data || !data.items) {
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
return;
}
allItems = data;
allItems = data.items;
filters = data.filters;
search.setItems(allItems);
// Initialize multi-select filters
modelSelect = new MultiSelect('#filter-model', {
placeholder: 'All Models',
onChange: (selected) => {
currentFilters.models = selected;
applyFiltersAndRender();
}
});
modelSelect.setItems(filters.models);
toolSelect = new MultiSelect('#filter-tool', {
placeholder: 'All Tools',
onChange: (selected) => {
currentFilters.tools = selected;
applyFiltersAndRender();
}
});
toolSelect.setItems(filters.tools);
// Render all items
renderItems(allItems);
countEl.textContent = `${allItems.length} agents`;
applyFiltersAndRender();
// Setup search
searchInput.addEventListener('input', debounce((e) => {
const query = e.target.value;
const results = query ? search.search(query) : allItems;
renderItems(results, query);
countEl.textContent = `${results.length} of ${allItems.length} agents`;
}, 200));
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
// Setup filter listeners
handoffsCheckbox.addEventListener('change', () => {
currentFilters.hasHandoffs = handoffsCheckbox.checked;
applyFiltersAndRender();
});
clearFiltersBtn.addEventListener('click', () => {
currentFilters = { models: [], tools: [], hasHandoffs: false };
modelSelect.clearSelection();
toolSelect.clearSelection();
handoffsCheckbox.checked = false;
searchInput.value = '';
applyFiltersAndRender();
});
// Setup modal
setupModal();
}
function applyFiltersAndRender() {
const searchInput = document.getElementById('search-input');
const countEl = document.getElementById('results-count');
const query = searchInput.value;
// Start with all items or search results
let results = query ? search.search(query) : [...allItems];
// Apply model filter (OR logic - match any selected model)
if (currentFilters.models.length > 0) {
results = results.filter(item => {
if (currentFilters.models.includes('(none)') && !item.model) {
return true;
}
return currentFilters.models.includes(item.model);
});
}
// Apply tool filter (OR logic - match any selected tool)
if (currentFilters.tools.length > 0) {
results = results.filter(item =>
item.tools?.some(tool => currentFilters.tools.includes(tool))
);
}
// Apply handoffs filter
if (currentFilters.hasHandoffs) {
results = results.filter(item => item.hasHandoffs);
}
renderItems(results, query);
// Update count with filter info
const activeFilters = [];
if (currentFilters.models.length > 0) activeFilters.push(`models: ${currentFilters.models.length}`);
if (currentFilters.tools.length > 0) activeFilters.push(`tools: ${currentFilters.tools.length}`);
if (currentFilters.hasHandoffs) activeFilters.push('has handoffs');
let countText = `${results.length} of ${allItems.length} agents`;
if (activeFilters.length > 0) {
countText += ` (filtered by ${activeFilters.join(', ')})`;
}
countEl.textContent = countText;
}
function renderItems(items, query = '') {
const list = document.getElementById('resource-list');
@@ -146,7 +263,7 @@
list.innerHTML = `
<div class="empty-state">
<h3>No agents found</h3>
<p>Try a different search term</p>
<p>Try a different search term or adjust filters</p>
</div>
`;
return;
@@ -157,12 +274,12 @@
<div class="resource-info">
<div class="resource-title">${query ? search.highlight(item.title, query) : escapeHtml(item.title)}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
${item.tools?.length || item.mcpServers?.length ? `
<div class="resource-meta">
${item.model ? `<span class="resource-tag">Model: ${escapeHtml(item.model)}</span>` : ''}
${item.mcpServers?.slice(0, 3).map(s => `<span class="resource-tag">MCP: ${escapeHtml(s)}</span>`).join('') || ''}
${item.model ? `<span class="resource-tag tag-model">${escapeHtml(item.model)}</span>` : '<span class="resource-tag tag-none">No model</span>'}
${item.hasHandoffs ? '<span class="resource-tag tag-handoffs">Has Handoffs</span>' : ''}
${item.tools?.length ? `<span class="resource-tag">${item.tools.length} tools</span>` : ''}
${item.mcpServers?.length ? `<span class="resource-tag">MCP: ${item.mcpServers.length}</span>` : ''}
</div>
` : ''}
</div>
<div class="resource-actions">
<a href="${getVSCodeInstallUrl(resourceType, item.path)}" class="btn btn-primary" onclick="event.stopPropagation()">

View File

@@ -7,6 +7,7 @@
<meta name="description" content="Curated collections of prompts, instructions, and agents for specific workflows">
<link rel="stylesheet" href="../css/styles.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📦</text></svg>">
<script src="../js/theme.js"></script>
</head>
<body>
<header class="site-header">
@@ -25,6 +26,15 @@
<a href="tools.html">Tools</a>
<a href="samples.html">Samples</a>
</nav>
<div class="header-actions">
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
</svg>
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
</svg>
</button>
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
@@ -32,6 +42,7 @@
</a>
</div>
</div>
</div>
</header>
<main>
@@ -47,6 +58,22 @@
<div class="search-bar">
<input type="text" id="search-input" placeholder="Search collections..." autocomplete="off">
</div>
<!-- Filters -->
<div class="filters-bar" id="filters-bar">
<div class="filter-group">
<label>Tag:</label>
<div id="filter-tag" class="multi-select-container"></div>
</div>
<div class="filter-group">
<label class="checkbox-label">
<input type="checkbox" id="filter-featured">
Featured Only
</label>
</div>
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
</div>
<div class="results-count" id="results-count"></div>
<div class="resource-list" id="resource-list">
<div class="loading">Loading collections...</div>
@@ -96,43 +123,109 @@
<script src="../js/utils.js"></script>
<script src="../js/search.js"></script>
<script src="../js/multi-select.js"></script>
<script src="../js/app.js"></script>
<script>
const resourceType = 'collection';
const dataFile = 'collections.json';
let allItems = [];
let filters = { tags: [] };
let search = new FuzzySearch();
let tagSelect;
// Current filter state
let currentFilters = {
tags: [],
featured: false,
};
async function initPage() {
const list = document.getElementById('resource-list');
const countEl = document.getElementById('results-count');
const searchInput = document.getElementById('search-input');
const featuredCheckbox = document.getElementById('filter-featured');
const clearFiltersBtn = document.getElementById('clear-filters');
const data = await fetchData(dataFile);
if (!data) {
if (!data || !data.items) {
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
return;
}
allItems = data;
allItems = data.items;
filters = data.filters;
search.setItems(allItems.map(item => ({
...item,
title: item.name,
searchText: `${item.name} ${item.description} ${item.tags?.join(' ') || ''}`.toLowerCase()
})));
renderItems(allItems);
countEl.textContent = `${allItems.length} collections`;
searchInput.addEventListener('input', debounce((e) => {
const query = e.target.value;
const results = query ? search.search(query) : allItems;
renderItems(results, query);
countEl.textContent = `${results.length} of ${allItems.length} collections`;
}, 200));
// Initialize multi-select filter
tagSelect = new MultiSelect('#filter-tag', {
placeholder: 'All Tags',
onChange: (selected) => {
currentFilters.tags = selected;
applyFiltersAndRender();
}
});
tagSelect.setItems(filters.tags);
// Render all items
applyFiltersAndRender();
// Setup search
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
featuredCheckbox.addEventListener('change', () => {
currentFilters.featured = featuredCheckbox.checked;
applyFiltersAndRender();
});
clearFiltersBtn.addEventListener('click', () => {
currentFilters = { tags: [], featured: false };
tagSelect.clearSelection();
featuredCheckbox.checked = false;
searchInput.value = '';
applyFiltersAndRender();
});
setupModal();
}
function applyFiltersAndRender() {
const searchInput = document.getElementById('search-input');
const countEl = document.getElementById('results-count');
const query = searchInput.value;
// Start with all items or search results
let results = query ? search.search(query) : [...allItems];
// Apply tag filter (OR logic)
if (currentFilters.tags.length > 0) {
results = results.filter(item =>
item.tags?.some(tag => currentFilters.tags.includes(tag))
);
}
// Apply featured filter
if (currentFilters.featured) {
results = results.filter(item => item.featured);
}
renderItems(results, query);
// Update count with filter info
const activeFilters = [];
if (currentFilters.tags.length > 0) activeFilters.push(`${currentFilters.tags.length} tag${currentFilters.tags.length > 1 ? 's' : ''}`);
if (currentFilters.featured) activeFilters.push('featured');
let countText = `${results.length} of ${allItems.length} collections`;
if (activeFilters.length > 0) {
countText += ` (filtered by ${activeFilters.join(', ')})`;
}
countEl.textContent = countText;
}
function renderItems(items, query = '') {
const list = document.getElementById('resource-list');
@@ -140,7 +233,7 @@
list.innerHTML = `
<div class="empty-state">
<h3>No collections found</h3>
<p>Try a different search term</p>
<p>Try a different search term or adjust filters</p>
</div>
`;
return;
@@ -150,11 +243,11 @@
<div class="resource-item" onclick="openFileModal('${item.path}', '${resourceType}')">
<div class="resource-info">
<div class="resource-title">
${item.featured ? ' ' : ''}${query ? search.highlight(item.name, query) : escapeHtml(item.name)}
${item.featured ? '<span class="tag-featured">⭐ Featured</span> ' : ''}${query ? search.highlight(item.name, query) : escapeHtml(item.name)}
</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${item.tags?.map(tag => `<span class="resource-tag">${escapeHtml(tag)}</span>`).join('') || ''}
${item.tags?.map(tag => `<span class="resource-tag tag-collection">${escapeHtml(tag)}</span>`).join('') || ''}
<span class="resource-tag">${item.items?.length || 0} items</span>
</div>
${item.items?.length ? `

View File

@@ -7,6 +7,7 @@
<meta name="description" content="Coding standards and best practices for GitHub Copilot">
<link rel="stylesheet" href="../css/styles.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📋</text></svg>">
<script src="../js/theme.js"></script>
</head>
<body>
<header class="site-header">
@@ -25,6 +26,15 @@
<a href="tools.html">Tools</a>
<a href="samples.html">Samples</a>
</nav>
<div class="header-actions">
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
</svg>
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
</svg>
</button>
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
@@ -32,6 +42,7 @@
</a>
</div>
</div>
</div>
</header>
<main>
@@ -47,6 +58,16 @@
<div class="search-bar">
<input type="text" id="search-input" placeholder="Search instructions..." autocomplete="off">
</div>
<!-- Filters -->
<div class="filters-bar" id="filters-bar">
<div class="filter-group">
<label>File Extension:</label>
<div id="filter-extension" class="multi-select-container"></div>
</div>
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
</div>
<div class="results-count" id="results-count"></div>
<div class="resource-list" id="resource-list">
<div class="loading">Loading instructions...</div>
@@ -100,39 +121,91 @@
<script src="../js/utils.js"></script>
<script src="../js/search.js"></script>
<script src="../js/multi-select.js"></script>
<script src="../js/app.js"></script>
<script>
const resourceType = 'instruction';
const dataFile = 'instructions.json';
let allItems = [];
let filters = { extensions: [], patterns: [] };
let search = new FuzzySearch();
let extensionSelect;
// Current filter state
let currentFilters = {
extensions: [],
};
async function initPage() {
const list = document.getElementById('resource-list');
const countEl = document.getElementById('results-count');
const searchInput = document.getElementById('search-input');
const clearFiltersBtn = document.getElementById('clear-filters');
const data = await fetchData(dataFile);
if (!data) {
if (!data || !data.items) {
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
return;
}
allItems = data;
allItems = data.items;
filters = data.filters;
search.setItems(allItems);
renderItems(allItems);
countEl.textContent = `${allItems.length} instructions`;
searchInput.addEventListener('input', debounce((e) => {
const query = e.target.value;
const results = query ? search.search(query) : allItems;
renderItems(results, query);
countEl.textContent = `${results.length} of ${allItems.length} instructions`;
}, 200));
// Initialize multi-select filter
extensionSelect = new MultiSelect('#filter-extension', {
placeholder: 'All Extensions',
onChange: (selected) => {
currentFilters.extensions = selected;
applyFiltersAndRender();
}
});
extensionSelect.setItems(filters.extensions);
// Render all items
applyFiltersAndRender();
// Setup search
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
clearFiltersBtn.addEventListener('click', () => {
currentFilters = { extensions: [] };
extensionSelect.clearSelection();
searchInput.value = '';
applyFiltersAndRender();
});
setupModal();
}
function applyFiltersAndRender() {
const searchInput = document.getElementById('search-input');
const countEl = document.getElementById('results-count');
const query = searchInput.value;
// Start with all items or search results
let results = query ? search.search(query) : [...allItems];
// Apply extension filter (OR logic)
if (currentFilters.extensions.length > 0) {
results = results.filter(item => {
if (currentFilters.extensions.includes('(none)') && (!item.extensions || item.extensions.length === 0)) {
return true;
}
return item.extensions?.some(ext => currentFilters.extensions.includes(ext));
});
}
renderItems(results, query);
// Update count with filter info
let countText = `${results.length} of ${allItems.length} instructions`;
if (currentFilters.extensions.length > 0) {
countText += ` (filtered by ${currentFilters.extensions.length} extension${currentFilters.extensions.length > 1 ? 's' : ''})`;
}
countEl.textContent = countText;
}
function renderItems(items, query = '') {
const list = document.getElementById('resource-list');
@@ -140,7 +213,7 @@
list.innerHTML = `
<div class="empty-state">
<h3>No instructions found</h3>
<p>Try a different search term</p>
<p>Try a different search term or adjust filters</p>
</div>
`;
return;
@@ -151,11 +224,9 @@
<div class="resource-info">
<div class="resource-title">${query ? search.highlight(item.title, query) : escapeHtml(item.title)}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
${item.applyTo ? `
<div class="resource-meta">
<span class="resource-tag">Applies to: ${escapeHtml(item.applyTo)}</span>
${item.extensions?.length ? item.extensions.map(ext => `<span class="resource-tag tag-extension">${escapeHtml(ext)}</span>`).join('') : '<span class="resource-tag tag-none">All files</span>'}
</div>
` : ''}
</div>
<div class="resource-actions">
<a href="${getVSCodeInstallUrl('instructions', item.path)}" class="btn btn-primary" onclick="event.stopPropagation()">

View File

@@ -7,6 +7,7 @@
<meta name="description" content="Ready-to-use prompt templates for development tasks with GitHub Copilot">
<link rel="stylesheet" href="../css/styles.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎯</text></svg>">
<script src="../js/theme.js"></script>
</head>
<body>
<header class="site-header">
@@ -25,6 +26,15 @@
<a href="tools.html">Tools</a>
<a href="samples.html">Samples</a>
</nav>
<div class="header-actions">
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
</svg>
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
</svg>
</button>
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
@@ -32,6 +42,7 @@
</a>
</div>
</div>
</div>
</header>
<main>
@@ -47,6 +58,16 @@
<div class="search-bar">
<input type="text" id="search-input" placeholder="Search prompts..." autocomplete="off">
</div>
<!-- Filters -->
<div class="filters-bar" id="filters-bar">
<div class="filter-group">
<label>Tool:</label>
<div id="filter-tool" class="multi-select-container"></div>
</div>
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
</div>
<div class="results-count" id="results-count"></div>
<div class="resource-list" id="resource-list">
<div class="loading">Loading prompts...</div>
@@ -100,39 +121,88 @@
<script src="../js/utils.js"></script>
<script src="../js/search.js"></script>
<script src="../js/multi-select.js"></script>
<script src="../js/app.js"></script>
<script>
const resourceType = 'prompt';
const dataFile = 'prompts.json';
let allItems = [];
let filters = { tools: [] };
let search = new FuzzySearch();
let toolSelect;
// Current filter state
let currentFilters = {
tools: [],
};
async function initPage() {
const list = document.getElementById('resource-list');
const countEl = document.getElementById('results-count');
const searchInput = document.getElementById('search-input');
const clearFiltersBtn = document.getElementById('clear-filters');
const data = await fetchData(dataFile);
if (!data) {
if (!data || !data.items) {
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
return;
}
allItems = data;
allItems = data.items;
filters = data.filters;
search.setItems(allItems);
renderItems(allItems);
countEl.textContent = `${allItems.length} prompts`;
searchInput.addEventListener('input', debounce((e) => {
const query = e.target.value;
const results = query ? search.search(query) : allItems;
renderItems(results, query);
countEl.textContent = `${results.length} of ${allItems.length} prompts`;
}, 200));
// Initialize multi-select filter
toolSelect = new MultiSelect('#filter-tool', {
placeholder: 'All Tools',
onChange: (selected) => {
currentFilters.tools = selected;
applyFiltersAndRender();
}
});
toolSelect.setItems(filters.tools);
// Render all items
applyFiltersAndRender();
// Setup search
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
clearFiltersBtn.addEventListener('click', () => {
currentFilters = { tools: [] };
toolSelect.clearSelection();
searchInput.value = '';
applyFiltersAndRender();
});
setupModal();
}
function applyFiltersAndRender() {
const searchInput = document.getElementById('search-input');
const countEl = document.getElementById('results-count');
const query = searchInput.value;
// Start with all items or search results
let results = query ? search.search(query) : [...allItems];
// Apply tool filter (OR logic)
if (currentFilters.tools.length > 0) {
results = results.filter(item =>
item.tools?.some(tool => currentFilters.tools.includes(tool))
);
}
renderItems(results, query);
// Update count with filter info
let countText = `${results.length} of ${allItems.length} prompts`;
if (currentFilters.tools.length > 0) {
countText += ` (filtered by ${currentFilters.tools.length} tool${currentFilters.tools.length > 1 ? 's' : ''})`;
}
countEl.textContent = countText;
}
function renderItems(items, query = '') {
const list = document.getElementById('resource-list');
@@ -140,7 +210,7 @@
list.innerHTML = `
<div class="empty-state">
<h3>No prompts found</h3>
<p>Try a different search term</p>
<p>Try a different search term or adjust filters</p>
</div>
`;
return;
@@ -152,8 +222,8 @@
<div class="resource-title">${query ? search.highlight(item.title, query) : escapeHtml(item.title)}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${item.model ? `<span class="resource-tag">Model: ${escapeHtml(item.model)}</span>` : ''}
${item.agent ? `<span class="resource-tag">Agent: ${escapeHtml(item.agent)}</span>` : ''}
${item.model ? `<span class="resource-tag tag-model">${escapeHtml(item.model)}</span>` : ''}
${item.tools?.length ? `<span class="resource-tag">${item.tools.length} tools</span>` : ''}
</div>
</div>
<div class="resource-actions">

View File

@@ -7,6 +7,7 @@
<meta name="description" content="Code samples and examples for building with GitHub Copilot">
<link rel="stylesheet" href="../css/styles.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📚</text></svg>">
<script src="../js/theme.js"></script>
</head>
<body>
<header class="site-header">
@@ -25,6 +26,15 @@
<a href="tools.html">Tools</a>
<a href="samples.html" class="active">Samples</a>
</nav>
<div class="header-actions">
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
</svg>
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
</svg>
</button>
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
@@ -32,6 +42,7 @@
</a>
</div>
</div>
</div>
</header>
<main>

View File

@@ -7,6 +7,7 @@
<meta name="description" content="Self-contained agent skills with instructions and bundled resources">
<link rel="stylesheet" href="../css/styles.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
<script src="../js/theme.js"></script>
</head>
<body>
<header class="site-header">
@@ -25,6 +26,15 @@
<a href="tools.html">Tools</a>
<a href="samples.html">Samples</a>
</nav>
<div class="header-actions">
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
</svg>
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
</svg>
</button>
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
@@ -32,6 +42,7 @@
</a>
</div>
</div>
</div>
</header>
<main>
@@ -47,6 +58,22 @@
<div class="search-bar">
<input type="text" id="search-input" placeholder="Search skills..." autocomplete="off">
</div>
<!-- Filters -->
<div class="filters-bar" id="filters-bar">
<div class="filter-group">
<label>Category:</label>
<div id="filter-category" class="multi-select-container"></div>
</div>
<div class="filter-group">
<label class="checkbox-label">
<input type="checkbox" id="filter-has-assets">
Has Bundled Assets
</label>
</div>
<button id="clear-filters" class="btn btn-secondary btn-small">Clear Filters</button>
</div>
<div class="results-count" id="results-count"></div>
<div class="resource-list" id="resource-list">
<div class="loading">Loading skills...</div>
@@ -99,39 +126,104 @@
<script src="../js/utils.js"></script>
<script src="../js/search.js"></script>
<script src="../js/multi-select.js"></script>
<script src="../js/jszip.min.js"></script>
<script src="../js/app.js"></script>
<script>
const resourceType = 'skill';
const dataFile = 'skills.json';
let allItems = [];
let filters = { categories: [], hasAssets: [] };
let search = new FuzzySearch();
let categorySelect;
// Current filter state
let currentFilters = {
categories: [],
hasAssets: false,
};
async function initPage() {
const list = document.getElementById('resource-list');
const countEl = document.getElementById('results-count');
const searchInput = document.getElementById('search-input');
const hasAssetsCheckbox = document.getElementById('filter-has-assets');
const clearFiltersBtn = document.getElementById('clear-filters');
const data = await fetchData(dataFile);
if (!data) {
if (!data || !data.items) {
list.innerHTML = '<div class="empty-state"><h3>Failed to load data</h3></div>';
return;
}
allItems = data;
allItems = data.items;
filters = data.filters;
search.setItems(allItems);
renderItems(allItems);
countEl.textContent = `${allItems.length} skills`;
searchInput.addEventListener('input', debounce((e) => {
const query = e.target.value;
const results = query ? search.search(query) : allItems;
renderItems(results, query);
countEl.textContent = `${results.length} of ${allItems.length} skills`;
}, 200));
// Initialize multi-select filter
categorySelect = new MultiSelect('#filter-category', {
placeholder: 'All Categories',
onChange: (selected) => {
currentFilters.categories = selected;
applyFiltersAndRender();
}
});
categorySelect.setItems(filters.categories);
// Render all items
applyFiltersAndRender();
// Setup search
searchInput.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
hasAssetsCheckbox.addEventListener('change', () => {
currentFilters.hasAssets = hasAssetsCheckbox.checked;
applyFiltersAndRender();
});
clearFiltersBtn.addEventListener('click', () => {
currentFilters = { categories: [], hasAssets: false };
categorySelect.clearSelection();
hasAssetsCheckbox.checked = false;
searchInput.value = '';
applyFiltersAndRender();
});
setupModal();
}
function applyFiltersAndRender() {
const searchInput = document.getElementById('search-input');
const countEl = document.getElementById('results-count');
const query = searchInput.value;
// Start with all items or search results
let results = query ? search.search(query) : [...allItems];
// Apply category filter (OR logic)
if (currentFilters.categories.length > 0) {
results = results.filter(item => currentFilters.categories.includes(item.category));
}
// Apply has assets filter
if (currentFilters.hasAssets) {
results = results.filter(item => item.hasAssets);
}
renderItems(results, query);
// Update count with filter info
const activeFilters = [];
if (currentFilters.categories.length > 0) activeFilters.push(`${currentFilters.categories.length} categor${currentFilters.categories.length > 1 ? 'ies' : 'y'}`);
if (currentFilters.hasAssets) activeFilters.push('has assets');
let countText = `${results.length} of ${allItems.length} skills`;
if (activeFilters.length > 0) {
countText += ` (filtered by ${activeFilters.join(', ')})`;
}
countEl.textContent = countText;
}
function renderItems(items, query = '') {
const list = document.getElementById('resource-list');
@@ -139,7 +231,7 @@
list.innerHTML = `
<div class="empty-state">
<h3>No skills found</h3>
<p>Try a different search term</p>
<p>Try a different search term or adjust filters</p>
</div>
`;
return;
@@ -150,13 +242,20 @@
<div class="resource-info">
<div class="resource-title">${query ? search.highlight(item.title, query) : escapeHtml(item.title)}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
${item.assets?.length ? `
<div class="resource-meta">
<span class="resource-tag">${item.assets.length} bundled asset${item.assets.length === 1 ? '' : 's'}</span>
<span class="resource-tag tag-category">${escapeHtml(item.category)}</span>
${item.hasAssets ? `<span class="resource-tag tag-assets">${item.assetCount} asset${item.assetCount === 1 ? '' : 's'}</span>` : ''}
<span class="resource-tag">${item.files.length} file${item.files.length === 1 ? '' : 's'}</span>
</div>
` : ''}
</div>
<div class="resource-actions">
<button class="btn btn-primary" onclick="event.stopPropagation(); downloadSkill('${item.id}')" title="Download as ZIP">
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"/>
<path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"/>
</svg>
Download
</button>
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary" target="_blank" onclick="event.stopPropagation()">
View Folder
</a>
@@ -165,6 +264,103 @@
`).join('');
}
// Download skill as ZIP file
async function downloadSkill(skillId) {
const skill = allItems.find(item => item.id === skillId);
if (!skill || !skill.files || skill.files.length === 0) {
alert('No files found for this skill');
return;
}
// Show loading state
const btn = event.target.closest('button');
const originalContent = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = `
<svg class="spinner" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="M8 0a8 8 0 1 0 8 8h-1.5A6.5 6.5 0 1 1 8 1.5V0z"/>
</svg>
Preparing...
`;
try {
const zip = new JSZip();
const folder = zip.folder(skill.id);
// Fetch all files in parallel
const fetchPromises = skill.files.map(async (file) => {
const url = getRawGitHubUrl(file.path);
try {
const response = await fetch(url);
if (!response.ok) {
console.warn(`Failed to fetch ${file.path}: ${response.status}`);
return null;
}
const content = await response.text();
return { name: file.name, content };
} catch (err) {
console.warn(`Error fetching ${file.path}:`, err);
return null;
}
});
const results = await Promise.all(fetchPromises);
// Add successfully fetched files to zip
let addedFiles = 0;
for (const result of results) {
if (result) {
folder.file(result.name, result.content);
addedFiles++;
}
}
if (addedFiles === 0) {
throw new Error('Failed to fetch any files');
}
// Generate and download zip
const blob = await zip.generateAsync({ type: 'blob' });
const downloadUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = `${skill.id}.zip`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(downloadUrl);
// Show success
btn.innerHTML = `
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0z"/>
</svg>
Downloaded!
`;
setTimeout(() => {
btn.disabled = false;
btn.innerHTML = originalContent;
}, 2000);
} catch (err) {
console.error('Download failed:', err);
btn.innerHTML = `
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 0 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06z"/>
</svg>
Failed
`;
setTimeout(() => {
btn.disabled = false;
btn.innerHTML = originalContent;
}, 2000);
}
}
document.addEventListener('DOMContentLoaded', initPage);
</script>
</body>

View File

@@ -7,6 +7,7 @@
<meta name="description" content="MCP servers and developer tools for GitHub Copilot">
<link rel="stylesheet" href="../css/styles.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔧</text></svg>">
<script src="../js/theme.js"></script>
</head>
<body>
<header class="site-header">
@@ -25,6 +26,15 @@
<a href="tools.html" class="active">Tools</a>
<a href="samples.html">Samples</a>
</nav>
<div class="header-actions">
<button id="theme-toggle" class="theme-toggle" title="Toggle theme">
<svg class="icon-sun" viewBox="0 0 16 16" fill="currentColor">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0zm0 13a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 8 13zM2.343 2.343a.75.75 0 0 1 1.061 0l1.06 1.061a.75.75 0 0 1-1.06 1.06l-1.06-1.06a.75.75 0 0 1 0-1.06zm9.193 9.193a.75.75 0 0 1 1.06 0l1.061 1.06a.75.75 0 0 1-1.06 1.061l-1.061-1.06a.75.75 0 0 1 0-1.061zM0 8a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 8zm13 0a.75.75 0 0 1 .75-.75h1.5a.75.75 0 0 1 0 1.5h-1.5A.75.75 0 0 1 13 8zM2.343 13.657a.75.75 0 0 1 0-1.061l1.06-1.06a.75.75 0 0 1 1.061 1.06l-1.06 1.06a.75.75 0 0 1-1.061 0zm9.193-9.193a.75.75 0 0 1 0-1.06l1.061-1.061a.75.75 0 0 1 1.06 1.06l-1.06 1.061a.75.75 0 0 1-1.061 0z"/>
</svg>
<svg class="icon-moon" viewBox="0 0 16 16" fill="currentColor">
<path d="M9.598 1.591a.75.75 0 0 1 .785-.175 7 7 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.5 5.5 0 1 0 7.678-7.678z"/>
</svg>
</button>
<a href="https://github.com/github/awesome-copilot" class="github-link" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" width="24" height="24" fill="currentColor">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
@@ -32,6 +42,7 @@
</a>
</div>
</div>
</div>
</header>
<main>