mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-22 23:47:36 +00:00
Add AI Catalog manifest generation for website data
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -35,6 +35,11 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const WEBSITE_DIR = path.join(ROOT_FOLDER, "website");
|
||||
const WEBSITE_DATA_DIR = path.join(WEBSITE_DIR, "public", "data");
|
||||
const WEBSITE_SOURCE_DATA_DIR = path.join(WEBSITE_DIR, "data");
|
||||
const WEBSITE_BASE_URL = "https://awesome-copilot.github.com";
|
||||
const RAW_CONTENT_BASE_URL =
|
||||
"https://raw.githubusercontent.com/github/awesome-copilot/main";
|
||||
const WELL_KNOWN_DIR = path.join(WEBSITE_DIR, "public", ".well-known");
|
||||
const AI_CATALOG_FILE = path.join(WELL_KNOWN_DIR, "ai-catalog.json");
|
||||
|
||||
/**
|
||||
* Ensure the output directory exists
|
||||
@@ -45,6 +50,15 @@ function ensureDataDir() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the .well-known output directory exists
|
||||
*/
|
||||
function ensureWellKnownDir() {
|
||||
if (!fs.existsSync(WELL_KNOWN_DIR)) {
|
||||
fs.mkdirSync(WELL_KNOWN_DIR, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract title from filename or frontmatter
|
||||
*/
|
||||
@@ -101,6 +115,187 @@ function normalizeText(value, fallback = "") {
|
||||
return typeof value === "string" ? value.trim() : fallback;
|
||||
}
|
||||
|
||||
function normalizeRepoPath(value) {
|
||||
return normalizeText(value).replace(/\\/g, "/").replace(/^\/+/, "");
|
||||
}
|
||||
|
||||
function buildRawContentUrl(relativePath) {
|
||||
return `${RAW_CONTENT_BASE_URL}/${normalizeRepoPath(relativePath)}`;
|
||||
}
|
||||
|
||||
function buildWebsiteUrl(route) {
|
||||
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
||||
return new URL(normalizedRoute, `${WEBSITE_BASE_URL}/`).toString();
|
||||
}
|
||||
|
||||
function normalizeDescription(value) {
|
||||
const description = normalizeText(value);
|
||||
return description || "No description provided.";
|
||||
}
|
||||
|
||||
function toUrnSegment(value) {
|
||||
const normalized = normalizeText(value).toLowerCase();
|
||||
const segment = normalized
|
||||
.replace(/[^a-z0-9._-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
return segment || "unknown";
|
||||
}
|
||||
|
||||
function createAiCatalogEntry({
|
||||
category,
|
||||
id,
|
||||
displayName,
|
||||
description,
|
||||
type,
|
||||
url,
|
||||
}) {
|
||||
return {
|
||||
identifier: `urn:ai:awesome-copilot.github.com:${category}:${toUrnSegment(id)}`,
|
||||
displayName: normalizeText(displayName, id),
|
||||
type,
|
||||
url,
|
||||
description: normalizeDescription(description),
|
||||
};
|
||||
}
|
||||
|
||||
function generateAiCatalogData({
|
||||
agents,
|
||||
instructions,
|
||||
skills,
|
||||
hooks,
|
||||
workflows,
|
||||
plugins,
|
||||
}) {
|
||||
const entries = [];
|
||||
|
||||
for (const agent of agents) {
|
||||
entries.push(
|
||||
createAiCatalogEntry({
|
||||
category: "agent",
|
||||
id: agent.id,
|
||||
displayName: agent.title,
|
||||
description: agent.description,
|
||||
type: "text/markdown",
|
||||
url: buildRawContentUrl(agent.path),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (const instruction of instructions) {
|
||||
entries.push(
|
||||
createAiCatalogEntry({
|
||||
category: "instruction",
|
||||
id: instruction.id,
|
||||
displayName: instruction.title,
|
||||
description: instruction.description,
|
||||
type: "text/markdown",
|
||||
url: buildRawContentUrl(instruction.path),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (const skill of skills) {
|
||||
entries.push(
|
||||
createAiCatalogEntry({
|
||||
category: "skill",
|
||||
id: skill.id,
|
||||
displayName: skill.title,
|
||||
description: skill.description,
|
||||
type: "text/markdown",
|
||||
url: buildRawContentUrl(skill.skillFile),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (const hook of hooks) {
|
||||
entries.push(
|
||||
createAiCatalogEntry({
|
||||
category: "hook",
|
||||
id: hook.id,
|
||||
displayName: hook.title,
|
||||
description: hook.description,
|
||||
type: "text/markdown",
|
||||
url: buildRawContentUrl(hook.readmeFile),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (const workflow of workflows) {
|
||||
entries.push(
|
||||
createAiCatalogEntry({
|
||||
category: "workflow",
|
||||
id: workflow.id,
|
||||
displayName: workflow.title,
|
||||
description: workflow.description,
|
||||
type: "text/markdown",
|
||||
url: buildRawContentUrl(workflow.path),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (const plugin of plugins) {
|
||||
const pluginUrl = plugin.external
|
||||
? plugin.homepage ||
|
||||
plugin.repository ||
|
||||
buildWebsiteUrl(`/plugins/?q=${encodeURIComponent(plugin.name)}`)
|
||||
: buildRawContentUrl(`${plugin.path}/.github/plugin/plugin.json`);
|
||||
|
||||
entries.push(
|
||||
createAiCatalogEntry({
|
||||
category: "plugin",
|
||||
id: plugin.id || plugin.name,
|
||||
displayName: plugin.name,
|
||||
description: plugin.description,
|
||||
type: plugin.external ? "text/html" : "application/json",
|
||||
url: pluginUrl,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
entries.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||
|
||||
return {
|
||||
specVersion: "1.0",
|
||||
host: {
|
||||
displayName: "Awesome GitHub Copilot",
|
||||
identifier: "awesome-copilot.github.com",
|
||||
},
|
||||
entries,
|
||||
collections: [
|
||||
{
|
||||
displayName: "Agents",
|
||||
url: buildWebsiteUrl("/agents/"),
|
||||
description: "Specialized GitHub Copilot agents.",
|
||||
},
|
||||
{
|
||||
displayName: "Instructions",
|
||||
url: buildWebsiteUrl("/instructions/"),
|
||||
description: "Custom coding instructions for Copilot.",
|
||||
},
|
||||
{
|
||||
displayName: "Skills",
|
||||
url: buildWebsiteUrl("/skills/"),
|
||||
description: "Agent Skills with bundled assets and instructions.",
|
||||
},
|
||||
{
|
||||
displayName: "Hooks",
|
||||
url: buildWebsiteUrl("/hooks/"),
|
||||
description: "Hooks for automated Copilot coding agent workflows.",
|
||||
},
|
||||
{
|
||||
displayName: "Workflows",
|
||||
url: buildWebsiteUrl("/workflows/"),
|
||||
description: "Agentic workflows for GitHub Actions automation.",
|
||||
},
|
||||
{
|
||||
displayName: "Plugins",
|
||||
url: buildWebsiteUrl("/plugins/"),
|
||||
description: "Curated plugin bundles for common scenarios.",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the latest git-modified date for any file under a directory.
|
||||
*/
|
||||
@@ -1593,6 +1788,16 @@ async function main() {
|
||||
);
|
||||
console.log(`✓ Generated search index with ${searchIndex.length} items`);
|
||||
|
||||
const aiCatalogData = generateAiCatalogData({
|
||||
agents,
|
||||
instructions,
|
||||
skills,
|
||||
hooks,
|
||||
workflows,
|
||||
plugins,
|
||||
});
|
||||
console.log(`✓ Generated AI Catalog with ${aiCatalogData.entries.length} entries`);
|
||||
|
||||
// Write JSON files
|
||||
fs.writeFileSync(
|
||||
path.join(WEBSITE_DATA_DIR, "agents.json"),
|
||||
@@ -1669,7 +1874,11 @@ async function main() {
|
||||
JSON.stringify(manifest, null, 2)
|
||||
);
|
||||
|
||||
ensureWellKnownDir();
|
||||
fs.writeFileSync(AI_CATALOG_FILE, JSON.stringify(aiCatalogData, null, 2));
|
||||
|
||||
console.log(`\n✓ All data written to website/public/data/`);
|
||||
console.log(`✓ AI Catalog written to website/public/.well-known/ai-catalog.json`);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
|
||||
Reference in New Issue
Block a user