mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-21 02:45:12 +00:00
Add tools catalog with YAML schema and website page
- Create website/data/tools.yml with 6 tools: - Awesome Copilot MCP Server - Awesome GitHub Copilot Browser (VS Code extension) - APM - Agent Package Manager (CLI) - Workspace Architect (npm CLI) - Prompt Registry (VS Code extension) - GitHub Node for Visual Studio - Add .schemas/tools.schema.json for YAML validation - Update eng/generate-website-data.mjs to generate tools.json - Add parseYamlFile() to eng/yaml-parser.mjs - Refactor tools.astro to use external TypeScript module - Create website/src/scripts/pages/tools.ts with: - FuzzySearch integration for search - Category filtering - Copy configuration functionality
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
|
||||
const base = import.meta.env.BASE_URL;
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
---
|
||||
|
||||
<BaseLayout title="Tools" description="MCP servers and developer tools for GitHub Copilot" activeNav="tools">
|
||||
<BaseLayout
|
||||
title="Tools"
|
||||
description="MCP servers and developer tools for GitHub Copilot"
|
||||
activeNav="tools"
|
||||
>
|
||||
<main>
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
@@ -15,50 +17,115 @@ const base = import.meta.env.BASE_URL;
|
||||
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
<div class="tool-card">
|
||||
<div class="tool-header">
|
||||
<h2>🖥️ Awesome Copilot MCP Server</h2>
|
||||
<span class="badge">Official</span>
|
||||
<div class="search-section">
|
||||
<div class="search-bar">
|
||||
<input
|
||||
type="text"
|
||||
id="search-input"
|
||||
placeholder="Search tools..."
|
||||
class="search-input"
|
||||
/>
|
||||
</div>
|
||||
<p>A Model Context Protocol (MCP) server that provides prompts for searching and installing resources directly from this repository.</p>
|
||||
|
||||
<h3>Features</h3>
|
||||
<ul>
|
||||
<li>Search across all agents, prompts, instructions, skills, and collections</li>
|
||||
<li>Install resources directly to your project</li>
|
||||
<li>Browse featured and curated collections</li>
|
||||
</ul>
|
||||
|
||||
<h3>Requirements</h3>
|
||||
<ul>
|
||||
<li>Docker (required to run the server)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Installation</h3>
|
||||
<p>See the <a href="https://github.com/github/awesome-copilot#mcp-server" target="_blank" rel="noopener">README</a> for installation instructions.</p>
|
||||
|
||||
<div class="tool-actions">
|
||||
<a href="https://github.com/github/awesome-copilot#mcp-server" class="btn btn-primary" target="_blank" rel="noopener">
|
||||
View Documentation
|
||||
</a>
|
||||
<div class="filters">
|
||||
<select id="filter-category" class="filter-select">
|
||||
<option value="">All Categories</option>
|
||||
</select>
|
||||
<button id="clear-filters" class="btn btn-secondary btn-small"
|
||||
>Clear</button
|
||||
>
|
||||
</div>
|
||||
<div id="results-count" class="results-count"></div>
|
||||
</div>
|
||||
|
||||
<div id="tools-list"></div>
|
||||
|
||||
<div class="coming-soon">
|
||||
<h2>More Tools Coming Soon</h2>
|
||||
<p>We're working on additional tools to enhance your GitHub Copilot experience. Check back soon!</p>
|
||||
<p>
|
||||
We're working on additional tools to enhance your GitHub Copilot
|
||||
experience. Check back soon!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
<style is:global>
|
||||
.search-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-card-bg);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(133, 52, 243, 0.1);
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-card-bg);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 14px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.results-count {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 48px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 48px;
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: var(--border-radius-lg);
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
background-color: var(--color-card-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: 32px;
|
||||
margin-bottom: 32px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tool-header {
|
||||
@@ -66,6 +133,7 @@ const base = import.meta.env.BASE_URL;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tool-header h2 {
|
||||
@@ -74,34 +142,111 @@ const base = import.meta.env.BASE_URL;
|
||||
color: var(--color-text-emphasis);
|
||||
}
|
||||
|
||||
.badge {
|
||||
background-color: var(--color-accent);
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
.tool-badges {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tool-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tool-card h3 {
|
||||
.tool-badge.featured {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tool-badge.category {
|
||||
background-color: var(--color-purple-light);
|
||||
color: var(--color-purple-dark);
|
||||
}
|
||||
|
||||
.tool-description {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tool-section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.tool-section h3 {
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-emphasis);
|
||||
}
|
||||
|
||||
.tool-card ul {
|
||||
.tool-section ul {
|
||||
margin: 0;
|
||||
padding-left: 24px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.tool-card li {
|
||||
.tool-section li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tool-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.tool-tag {
|
||||
background-color: var(--color-bg-secondary);
|
||||
color: var(--color-text-muted);
|
||||
padding: 4px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.tool-config {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.tool-config h3 {
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-emphasis);
|
||||
}
|
||||
|
||||
.tool-config-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tool-config pre {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tool-config code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
color: var(--color-text-primary);
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.tool-actions {
|
||||
margin-top: 24px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.coming-soon {
|
||||
@@ -120,5 +265,45 @@ const base = import.meta.env.BASE_URL;
|
||||
.coming-soon p {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.copy-config-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
background-color: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
margin-top: 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.copy-config-btn:hover {
|
||||
background-color: var(--color-bg-secondary);
|
||||
color: var(--color-text-primary);
|
||||
border-color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.copy-config-btn.copied {
|
||||
background-color: var(--color-success);
|
||||
color: white;
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
|
||||
/* Search highlight */
|
||||
.search-highlight {
|
||||
background-color: var(--color-warning);
|
||||
color: var(--color-text-emphasis);
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { initToolsPage } from "../scripts/pages/tools";
|
||||
initToolsPage();
|
||||
</script>
|
||||
</BaseLayout>
|
||||
|
||||
264
website/src/scripts/pages/tools.ts
Normal file
264
website/src/scripts/pages/tools.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Tools page functionality
|
||||
*/
|
||||
import { FuzzySearch, type SearchableItem } from '../search';
|
||||
import { fetchData, debounce, escapeHtml } from '../utils';
|
||||
|
||||
export interface Tool extends SearchableItem {
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
featured: boolean;
|
||||
requirements: string[];
|
||||
features: string[];
|
||||
links: {
|
||||
blog?: string;
|
||||
vscode?: string;
|
||||
'vscode-insiders'?: string;
|
||||
'visual-studio'?: string;
|
||||
github?: string;
|
||||
documentation?: string;
|
||||
marketplace?: string;
|
||||
npm?: string;
|
||||
pypi?: string;
|
||||
};
|
||||
configuration?: {
|
||||
type: string;
|
||||
content: string;
|
||||
};
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
interface ToolsData {
|
||||
items: Tool[];
|
||||
filters: {
|
||||
categories: string[];
|
||||
tags: string[];
|
||||
};
|
||||
}
|
||||
|
||||
let allItems: Tool[] = [];
|
||||
let search: FuzzySearch<Tool>;
|
||||
let currentFilters = {
|
||||
categories: [] as string[],
|
||||
query: '',
|
||||
};
|
||||
|
||||
function applyFiltersAndRender(): void {
|
||||
const searchInput = document.getElementById('search-input') as HTMLInputElement;
|
||||
const countEl = document.getElementById('results-count');
|
||||
const query = searchInput?.value || '';
|
||||
currentFilters.query = query;
|
||||
|
||||
let results = query ? search.search(query) : [...allItems];
|
||||
|
||||
if (currentFilters.categories.length > 0) {
|
||||
results = results.filter(item =>
|
||||
currentFilters.categories.includes(item.category)
|
||||
);
|
||||
}
|
||||
|
||||
renderTools(results, query);
|
||||
|
||||
let countText = `${results.length} of ${allItems.length} tools`;
|
||||
if (currentFilters.categories.length > 0) {
|
||||
countText += ` (filtered by ${currentFilters.categories.length} categories)`;
|
||||
}
|
||||
if (countEl) countEl.textContent = countText;
|
||||
}
|
||||
|
||||
function renderTools(tools: Tool[], query = ''): void {
|
||||
const container = document.getElementById('tools-list');
|
||||
if (!container) return;
|
||||
|
||||
if (tools.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<h3>No tools found</h3>
|
||||
<p>Try a different search term or adjust filters</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = tools.map(tool => {
|
||||
const badges: string[] = [];
|
||||
if (tool.featured) {
|
||||
badges.push('<span class="tool-badge featured">Featured</span>');
|
||||
}
|
||||
badges.push(`<span class="tool-badge category">${escapeHtml(tool.category)}</span>`);
|
||||
|
||||
const features = tool.features && tool.features.length > 0
|
||||
? `<div class="tool-section">
|
||||
<h3>Features</h3>
|
||||
<ul>${tool.features.map(f => `<li>${escapeHtml(f)}</li>`).join('')}</ul>
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
const requirements = tool.requirements && tool.requirements.length > 0
|
||||
? `<div class="tool-section">
|
||||
<h3>Requirements</h3>
|
||||
<ul>${tool.requirements.map(r => `<li>${escapeHtml(r)}</li>`).join('')}</ul>
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
const tags = tool.tags && tool.tags.length > 0
|
||||
? `<div class="tool-tags">
|
||||
${tool.tags.map(t => `<span class="tool-tag">${escapeHtml(t)}</span>`).join('')}
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
const config = tool.configuration
|
||||
? `<div class="tool-config">
|
||||
<h3>Configuration</h3>
|
||||
<div class="tool-config-wrapper">
|
||||
<pre><code>${escapeHtml(tool.configuration.content)}</code></pre>
|
||||
</div>
|
||||
<button class="copy-config-btn" data-config="${encodeURIComponent(tool.configuration.content)}">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/>
|
||||
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/>
|
||||
</svg>
|
||||
Copy Configuration
|
||||
</button>
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
const actions: string[] = [];
|
||||
if (tool.links.blog) {
|
||||
actions.push(`<a href="${tool.links.blog}" class="btn btn-secondary" target="_blank" rel="noopener">📖 Blog</a>`);
|
||||
}
|
||||
if (tool.links.marketplace) {
|
||||
actions.push(`<a href="${tool.links.marketplace}" class="btn btn-secondary" target="_blank" rel="noopener">🏪 Marketplace</a>`);
|
||||
}
|
||||
if (tool.links.npm) {
|
||||
actions.push(`<a href="${tool.links.npm}" class="btn btn-secondary" target="_blank" rel="noopener">📦 npm</a>`);
|
||||
}
|
||||
if (tool.links.pypi) {
|
||||
actions.push(`<a href="${tool.links.pypi}" class="btn btn-secondary" target="_blank" rel="noopener">🐍 PyPI</a>`);
|
||||
}
|
||||
if (tool.links.documentation) {
|
||||
actions.push(`<a href="${tool.links.documentation}" class="btn btn-secondary" target="_blank" rel="noopener">📚 Docs</a>`);
|
||||
}
|
||||
if (tool.links.github) {
|
||||
actions.push(`<a href="${tool.links.github}" class="btn btn-secondary" target="_blank" rel="noopener">GitHub</a>`);
|
||||
}
|
||||
if (tool.links.vscode) {
|
||||
actions.push(`<a href="${tool.links.vscode}" class="btn btn-primary" target="_blank" rel="noopener">Install in VS Code</a>`);
|
||||
}
|
||||
if (tool.links['vscode-insiders']) {
|
||||
actions.push(`<a href="${tool.links['vscode-insiders']}" class="btn btn-outline" target="_blank" rel="noopener">VS Code Insiders</a>`);
|
||||
}
|
||||
if (tool.links['visual-studio']) {
|
||||
actions.push(`<a href="${tool.links['visual-studio']}" class="btn btn-outline" target="_blank" rel="noopener">Visual Studio</a>`);
|
||||
}
|
||||
|
||||
const actionsHtml = actions.length > 0
|
||||
? `<div class="tool-actions">${actions.join('')}</div>`
|
||||
: '';
|
||||
|
||||
const titleHtml = query ? search.highlight(tool.name, query) : escapeHtml(tool.name);
|
||||
|
||||
return `
|
||||
<div class="tool-card">
|
||||
<div class="tool-header">
|
||||
<h2>${titleHtml}</h2>
|
||||
<div class="tool-badges">
|
||||
${badges.join('')}
|
||||
</div>
|
||||
</div>
|
||||
<p class="tool-description">${escapeHtml(tool.description)}</p>
|
||||
${features}
|
||||
${requirements}
|
||||
${config}
|
||||
${tags}
|
||||
${actionsHtml}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
setupCopyConfigHandlers();
|
||||
}
|
||||
|
||||
function setupCopyConfigHandlers(): void {
|
||||
document.querySelectorAll('.copy-config-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
const button = e.currentTarget as HTMLButtonElement;
|
||||
const config = decodeURIComponent(button.dataset.config || '');
|
||||
try {
|
||||
await navigator.clipboard.writeText(config);
|
||||
button.classList.add('copied');
|
||||
const originalHtml = button.innerHTML;
|
||||
button.innerHTML = `
|
||||
<svg width="14" height="14" viewBox="0 0 16 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>
|
||||
Copied!
|
||||
`;
|
||||
setTimeout(() => {
|
||||
button.classList.remove('copied');
|
||||
button.innerHTML = originalHtml;
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function initToolsPage(): Promise<void> {
|
||||
const container = document.getElementById('tools-list');
|
||||
const searchInput = document.getElementById('search-input') as HTMLInputElement;
|
||||
const categoryFilter = document.getElementById('filter-category') as HTMLSelectElement;
|
||||
const clearFiltersBtn = document.getElementById('clear-filters');
|
||||
const countEl = document.getElementById('results-count');
|
||||
|
||||
if (container) {
|
||||
container.innerHTML = '<div class="loading">Loading tools...</div>';
|
||||
}
|
||||
|
||||
const data = await fetchData<ToolsData>('tools.json');
|
||||
if (!data || !data.items) {
|
||||
if (container) container.innerHTML = '<div class="empty-state"><h3>Failed to load tools</h3></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Map items to include title for FuzzySearch
|
||||
allItems = data.items.map(item => ({
|
||||
...item,
|
||||
title: item.name, // FuzzySearch uses title
|
||||
}));
|
||||
|
||||
search = new FuzzySearch<Tool>();
|
||||
search.setItems(allItems);
|
||||
|
||||
// Populate category filter
|
||||
if (categoryFilter && data.filters.categories) {
|
||||
categoryFilter.innerHTML = '<option value="">All Categories</option>' +
|
||||
data.filters.categories.map(c => `<option value="${escapeHtml(c)}">${escapeHtml(c)}</option>`).join('');
|
||||
|
||||
categoryFilter.addEventListener('change', () => {
|
||||
currentFilters.categories = categoryFilter.value ? [categoryFilter.value] : [];
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
}
|
||||
|
||||
// Search input handler
|
||||
searchInput?.addEventListener('input', debounce(() => applyFiltersAndRender(), 200));
|
||||
|
||||
// Clear filters
|
||||
clearFiltersBtn?.addEventListener('click', () => {
|
||||
currentFilters = { categories: [], query: '' };
|
||||
if (categoryFilter) categoryFilter.value = '';
|
||||
if (searchInput) searchInput.value = '';
|
||||
applyFiltersAndRender();
|
||||
});
|
||||
|
||||
applyFiltersAndRender();
|
||||
}
|
||||
|
||||
// Auto-initialize when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', initToolsPage);
|
||||
Reference in New Issue
Block a user