chore: publish from main

This commit is contained in:
github-actions[bot]
2026-08-13 01:24:35 +00:00
parent 0fbdfba718
commit bd74f084d2
498 changed files with 78 additions and 55 deletions
+3 -3
View File
@@ -9,7 +9,7 @@ The Awesome GitHub Copilot repository is a community-driven collection of custom
- **Skills** - Self-contained folders with instructions and bundled resources for specialized tasks - **Skills** - Self-contained folders with instructions and bundled resources for specialized tasks
- **Hooks** - Automated workflows triggered by specific events during development - **Hooks** - Automated workflows triggered by specific events during development
- **Workflows** - [Agentic Workflows](https://github.github.com/gh-aw) for AI-powered repository automation in GitHub Actions - **Workflows** - [Agentic Workflows](https://github.github.com/gh-aw) for AI-powered repository automation in GitHub Actions
- **Plugins** - Installable packages that group related agents, commands, and skills around specific themes - **Plugins** - Installable packages that group related agents, hooks, and skills around specific themes
## Repository Structure ## Repository Structure
@@ -120,7 +120,7 @@ All agent files (`*.agent.md`) and instruction files (`*.instructions.md`) must
- plugin.json must have `name` field (matching the folder name) - plugin.json must have `name` field (matching the folder name)
- plugin.json must have `description` field (describing the plugin's purpose) - plugin.json must have `description` field (describing the plugin's purpose)
- plugin.json must have `version` field (semantic version, e.g., "1.0.0") - plugin.json must have `version` field (semantic version, e.g., "1.0.0")
- Plugin content is defined declaratively in plugin.json under `extensions.com.github.awesome-copilot` using source-only composition fields (`agents`, `commands`, `hooks`, `skills`, and `extensions`). Source files live in top-level directories and are materialized into plugins by CI. This namespace is stripped from the served manifest — conventional directory discovery handles the materialized content in spec mode. - Plugin content is defined declaratively in plugin.json under `extensions.com.github.awesome-copilot` using source-only composition fields (`agents`, `hooks`, `skills`, and `extensions`). Source files live in top-level directories and are materialized into plugins by CI. This namespace is stripped from the served manifest — skills use the standard `skills/` directory and Copilot-specific content uses `com.github.copilot/`.
- The `marketplace.json` file is automatically generated from all plugins during build - The `marketplace.json` file is automatically generated from all plugins during build
- Plugins are discoverable and installable via GitHub Copilot CLI - Plugins are discoverable and installable via GitHub Copilot CLI
@@ -165,7 +165,7 @@ When adding a new agent, instruction, skill, hook, workflow, or plugin:
**For Plugins:** **For Plugins:**
1. Run `npm run plugin:create -- --name <plugin-name>` to scaffold a new plugin 1. Run `npm run plugin:create -- --name <plugin-name>` to scaffold a new plugin
2. Define agents, commands, hooks, skills, and reusable extensions under `extensions.com.github.awesome-copilot` in `plugin.json` 2. Define agents, hooks, skills, and reusable extensions under `extensions.com.github.awesome-copilot` in `plugin.json`
3. Edit the generated `plugin.json` with your metadata 3. Edit the generated `plugin.json` with your metadata
4. Run `npm run plugin:validate` to validate the plugin structure 4. Run `npm run plugin:validate` to validate the plugin structure
5. Run `npm run build` to update README.md and marketplace.json 5. Run `npm run build` to update README.md and marketplace.json
+4 -5
View File
@@ -153,11 +153,11 @@ Canvas extensions live in `extensions/<extension-id>/` as reusable source compon
### Adding Plugins ### Adding Plugins
Plugins group related agents, commands, and skills around specific themes or workflows, making it easy for users to install comprehensive toolkits via GitHub Copilot CLI. Plugins group related agents, hooks, and skills around specific themes or workflows, making it easy for users to install comprehensive toolkits via GitHub Copilot CLI.
1. **Create your plugin**: Run `npm run plugin:create` to scaffold a new plugin 1. **Create your plugin**: Run `npm run plugin:create` to scaffold a new plugin
2. **Follow the naming convention**: Use descriptive, lowercase folder names with hyphens (e.g., `python-web-development`) 2. **Follow the naming convention**: Use descriptive, lowercase folder names with hyphens (e.g., `python-web-development`)
3. **Define your content**: List agents, commands, hooks, skills, and reusable extensions under `extensions.com.github.awesome-copilot` in `plugin.json` 3. **Define your content**: List agents, hooks, skills, and reusable extensions under `extensions.com.github.awesome-copilot` in `plugin.json`
4. **Test your plugin**: Run `npm run plugin:validate` to verify your plugin structure 4. **Test your plugin**: Run `npm run plugin:validate` to verify your plugin structure
#### Creating a plugin #### Creating a plugin
@@ -174,7 +174,7 @@ plugins/my-plugin-id/
└── README.md # Plugin documentation └── README.md # Plugin documentation
``` ```
> **Note:** Plugin content is defined declaratively in plugin.json under `extensions.com.github.awesome-copilot`. Source files live in top-level directories and are materialized into plugins by CI. This repository namespace is removed from the served manifest. > **Note:** Plugin content is defined declaratively in plugin.json under `extensions.com.github.awesome-copilot`. Source files live in top-level directories and are materialized into plugins by CI. Skills are emitted under `skills/`; Copilot-specific agents, hooks, and extensions are emitted under `com.github.copilot/`. This repository namespace is removed from the served manifest.
#### plugin.json example #### plugin.json example
@@ -191,7 +191,6 @@ plugins/my-plugin-id/
"extensions": { "extensions": {
"com.github.awesome-copilot": { "com.github.awesome-copilot": {
"agents": ["./agents/my-agent.md"], "agents": ["./agents/my-agent.md"],
"commands": ["./commands/my-command.md"],
"skills": ["./skills/my-skill/"] "skills": ["./skills/my-skill/"]
} }
} }
@@ -200,7 +199,7 @@ plugins/my-plugin-id/
#### Plugin Guidelines #### Plugin Guidelines
- **Declarative content**: Plugin content is specified under `extensions.com.github.awesome-copilot` — source files live in top-level directories and are materialized into plugins by CI - **Declarative content**: Plugin content is specified under `extensions.com.github.awesome-copilot` — source files live in top-level directories and are materialized into plugins by CI. Skills use the standard `skills/` directory; Copilot-specific content uses `com.github.copilot/`.
- **Valid references**: All paths referenced in plugin.json must point to existing source files in the repository - **Valid references**: All paths referenced in plugin.json must point to existing source files in the repository
- **Reusable extensions**: Curated plugins can bundle extensions by adding `./extensions/<name>` paths under `extensions.com.github.awesome-copilot.extensions`; the same extension can be listed by multiple plugins - **Reusable extensions**: Curated plugins can bundle extensions by adding `./extensions/<name>` paths under `extensions.com.github.awesome-copilot.extensions`; the same extension can be listed by multiple plugins
- **Instructions excluded**: Instructions are standalone resources and are not part of plugins - **Instructions excluded**: Instructions are standalone resources and are not part of plugins
+50 -22
View File
@@ -7,25 +7,35 @@ import { ROOT_FOLDER } from "./constants.mjs";
const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins"); const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions"); const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions");
const COPILOT_CONTENT_DIR = "com.github.copilot";
const AWESOME_COPILOT_NAMESPACE = "com.github.awesome-copilot";
const MATERIALIZED_SPECS = { const MATERIALIZED_SPECS = {
agents: { agents: {
path: "agents", paths: [path.join(COPILOT_CONTENT_DIR, "agents"), "agents"],
restore(dirPath) { restore(dirPath) {
return collectFiles(dirPath).map((relativePath) => `./agents/${relativePath}`); return collectFiles(dirPath).map((relativePath) => `./agents/${relativePath}`);
}, },
}, },
commands: { hooks: {
path: "commands", paths: [path.join(COPILOT_CONTENT_DIR, "hooks"), "hooks"],
restore(dirPath) { restore(dirPath) {
return collectFiles(dirPath).map((relativePath) => `./commands/${relativePath}`); return collectDirectoriesContainingFile(dirPath, "hooks.json")
.map((relativePath) => `./hooks/${relativePath}/`);
}, },
}, },
skills: { skills: {
path: "skills", paths: ["skills"],
restore(dirPath) { restore(dirPath) {
return collectSkillDirectories(dirPath).map((relativePath) => `./skills/${relativePath}/`); return collectSkillDirectories(dirPath).map((relativePath) => `./skills/${relativePath}/`);
}, },
}, },
extensions: {
paths: [path.join(COPILOT_CONTENT_DIR, "extensions"), "extensions"],
restore(dirPath) {
return collectDirectoriesContainingFile(dirPath, "extension.mjs")
.map((relativePath) => `./extensions/${relativePath}`);
},
},
}; };
function copyDirRecursive(src, dest) { function copyDirRecursive(src, dest) {
@@ -78,22 +88,19 @@ export function restoreManifestFromMaterializedFiles(pluginPath) {
let changed = false; let changed = false;
for (const [field, spec] of Object.entries(MATERIALIZED_SPECS)) { for (const [field, spec] of Object.entries(MATERIALIZED_SPECS)) {
if (Array.isArray(plugin[field])) { const materializedPath = spec.paths
const sortedEntries = sortPluginEntries(plugin[field]); .map((subdir) => path.join(pluginPath, subdir))
if (!arraysEqual(plugin[field], sortedEntries)) { .find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isDirectory());
plugin[field] = sortedEntries; if (!materializedPath) {
changed = true;
}
}
const materializedPath = path.join(pluginPath, spec.path);
if (!fs.existsSync(materializedPath) || !fs.statSync(materializedPath).isDirectory()) {
continue; continue;
} }
const restored = spec.restore(materializedPath); const restored = spec.restore(materializedPath);
if (!arraysEqual(plugin[field], restored)) { const composition = plugin.extensions?.[AWESOME_COPILOT_NAMESPACE];
plugin[field] = restored; if (!arraysEqual(composition?.[field], restored)) {
plugin.extensions ??= {};
plugin.extensions[AWESOME_COPILOT_NAMESPACE] ??= {};
plugin.extensions[AWESOME_COPILOT_NAMESPACE][field] = restored;
changed = true; changed = true;
} }
} }
@@ -112,7 +119,8 @@ function cleanPlugin(pluginPath) {
} }
let removed = 0; let removed = 0;
for (const { path: subdir } of Object.values(MATERIALIZED_SPECS)) { for (const { paths } of Object.values(MATERIALIZED_SPECS)) {
for (const subdir of paths) {
const target = path.join(pluginPath, subdir); const target = path.join(pluginPath, subdir);
if (fs.existsSync(target) && fs.statSync(target).isDirectory()) { if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
const count = countFiles(target); const count = countFiles(target);
@@ -121,6 +129,12 @@ function cleanPlugin(pluginPath) {
console.log(` Removed ${path.basename(pluginPath)}/${subdir}/ (${count} files)`); console.log(` Removed ${path.basename(pluginPath)}/${subdir}/ (${count} files)`);
} }
} }
}
const copilotContentPath = path.join(pluginPath, COPILOT_CONTENT_DIR);
if (fs.existsSync(copilotContentPath) && fs.readdirSync(copilotContentPath).length === 0) {
fs.rmdirSync(copilotContentPath);
}
return { removed, manifestUpdated }; return { removed, manifestUpdated };
} }
@@ -230,6 +244,24 @@ function collectSkillDirectories(dir, rootDir = dir) {
return skillDirs.sort(); return skillDirs.sort();
} }
function collectDirectoriesContainingFile(dir, fileName, rootDir = dir) {
const directories = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (!entry.isDirectory()) {
continue;
}
const entryPath = path.join(dir, entry.name);
if (fs.existsSync(path.join(entryPath, fileName))) {
directories.push(toPosixPath(path.relative(rootDir, entryPath)));
continue;
}
directories.push(...collectDirectoriesContainingFile(entryPath, fileName, rootDir));
}
return directories.sort();
}
function arraysEqual(left, right) { function arraysEqual(left, right) {
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) { if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
return false; return false;
@@ -238,10 +270,6 @@ function arraysEqual(left, right) {
return left.every((value, index) => value === right[index]); return left.every((value, index) => value === right[index]);
} }
function sortPluginEntries(entries) {
return [...entries].sort((left, right) => left.localeCompare(right));
}
function toPosixPath(filePath) { function toPosixPath(filePath) {
return filePath.split(path.sep).join("/"); return filePath.split(path.sep).join("/");
} }
+1 -2
View File
@@ -618,10 +618,9 @@ function generatePluginsData(gitDates, resourceIndex = {}) {
} }
} }
// Build items list from spec fields (agents, commands, skills, mcpServers) // Build items list from supported composition fields.
const items = [ const items = [
...agentItems, ...agentItems,
...(composition.commands || []).map((p) => ({ kind: "prompt", path: p })),
...(composition.skills || []).map((p) => ({ kind: "skill", path: p })), ...(composition.skills || []).map((p) => ({ kind: "skill", path: p })),
...extensionItems, ...extensionItems,
...mcpItems, ...mcpItems,
+11 -11
View File
@@ -9,6 +9,7 @@ const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions"); const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions");
const COPILOT_NAMESPACE = "com.github.copilot"; const COPILOT_NAMESPACE = "com.github.copilot";
const AWESOME_COPILOT_NAMESPACE = "com.github.awesome-copilot"; const AWESOME_COPILOT_NAMESPACE = "com.github.awesome-copilot";
const COPILOT_CONTENT_DIR = COPILOT_NAMESPACE;
/** /**
* Recursively copy a directory. * Recursively copy a directory.
@@ -49,9 +50,6 @@ function resolveSource(relPath) {
if (relPath.startsWith("./hooks/")) { if (relPath.startsWith("./hooks/")) {
return path.join(ROOT_FOLDER, "hooks", relPath.replace(/^\.\/hooks\//, "")); return path.join(ROOT_FOLDER, "hooks", relPath.replace(/^\.\/hooks\//, ""));
} }
if (relPath.startsWith("./commands/")) {
return path.join(ROOT_FOLDER, "commands", relPath.replace(/^\.\/commands\//, ""));
}
return null; return null;
} }
@@ -114,7 +112,7 @@ export function materializePlugins() {
const composition = metadata.extensions?.[AWESOME_COPILOT_NAMESPACE] ?? {}; const composition = metadata.extensions?.[AWESOME_COPILOT_NAMESPACE] ?? {};
// Process repository composition fields. // Process repository composition fields.
for (const field of ["agents", "commands", "hooks", "skills"]) { for (const field of ["agents", "hooks", "skills"]) {
const entries = composition[field]; const entries = composition[field];
if (!Array.isArray(entries)) continue; if (!Array.isArray(entries)) continue;
for (const relPath of entries) { for (const relPath of entries) {
@@ -129,7 +127,10 @@ export function materializePlugins() {
warnings++; warnings++;
continue; continue;
} }
const dest = path.join(pluginPath, relPath.replace(/^\.\//, "").replace(/\/$/, "")); const relativeDestination = relPath.replace(/^\.\//, "").replace(/\/$/, "");
const dest = field === "skills"
? path.join(pluginPath, relativeDestination)
: path.join(pluginPath, COPILOT_CONTENT_DIR, relativeDestination);
fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.mkdirSync(path.dirname(dest), { recursive: true });
if (fs.statSync(src).isDirectory()) copyDirRecursive(src, dest); if (fs.statSync(src).isDirectory()) copyDirRecursive(src, dest);
else fs.copyFileSync(src, dest); else fs.copyFileSync(src, dest);
@@ -153,17 +154,16 @@ export function materializePlugins() {
warnings++; warnings++;
continue; continue;
} }
// Extensions are conventional plugin content and belong under the const dest = path.join(pluginPath, COPILOT_CONTENT_DIR, "extensions", extensionName);
// plugin's top-level extensions directory, not the client namespace.
const dest = path.join(pluginPath, "extensions", extensionName);
copyDirRecursive(src, dest); copyDirRecursive(src, dest);
totalExtensions++; totalExtensions++;
} }
// Emit a spec-compliant served manifest for the marketplace branch. // Emit a spec-compliant served manifest for the marketplace branch.
// Source manifests keep composition fields (agents and skills) // Source manifests keep repository composition fields for build tooling.
// for build tooling. The served manifest retains only Agent Plugins v1.0.0 fields // The served manifest retains only Agent Plugins v1.0.0 fields; standard
// so the runtime uses conventional directory discovery for all content. // skills are discovered from skills/, while Copilot-specific content is
// discovered from com.github.copilot/.
const SPEC_FIELDS = new Set(["$schema", "name", "version", "description", "author", const SPEC_FIELDS = new Set(["$schema", "name", "version", "description", "author",
"homepage", "repository", "license", "keywords", "extensions"]); "homepage", "repository", "license", "keywords", "extensions"]);
const AGENT_PLUGINS_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"; const AGENT_PLUGINS_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
-2
View File
@@ -800,7 +800,6 @@ function generatePluginsSection(pluginsDir) {
: 0; : 0;
const itemCount = const itemCount =
(composition.agents || []).length + (composition.agents || []).length +
(composition.commands || []).length +
(composition.skills || []).length + (composition.skills || []).length +
extensionReferences + extensionReferences +
implicitExtension; implicitExtension;
@@ -869,7 +868,6 @@ function generateFeaturedPluginsSection(pluginsDir) {
: 0; : 0;
const itemCount = const itemCount =
(composition.agents || []).length + (composition.agents || []).length +
(composition.commands || []).length +
(composition.skills || []).length + (composition.skills || []).length +
extensionReferences + extensionReferences +
implicitExtension; implicitExtension;
+3 -4
View File
@@ -120,7 +120,6 @@ function validateSpecPaths(plugin) {
const errors = []; const errors = [];
const specs = { const specs = {
agents: { prefix: "./agents/", suffix: ".md", repoDir: "agents", repoSuffix: ".agent.md" }, agents: { prefix: "./agents/", suffix: ".md", repoDir: "agents", repoSuffix: ".agent.md" },
commands: { prefix: "./commands/", suffix: ".md", repoDir: "commands", repoSuffix: ".md" },
hooks: { prefix: "./hooks/", suffix: "/", repoDir: "hooks", repoFile: "README.md" }, hooks: { prefix: "./hooks/", suffix: "/", repoDir: "hooks", repoFile: "README.md" },
skills: { prefix: "./skills/", suffix: "/", repoDir: "skills", repoFile: "SKILL.md" }, skills: { prefix: "./skills/", suffix: "/", repoDir: "skills", repoFile: "SKILL.md" },
}; };
@@ -215,7 +214,7 @@ function validateExtensionReferences(plugin, pluginDir) {
function validateCompositionNamespace(plugin) { function validateCompositionNamespace(plugin) {
const errors = []; const errors = [];
const compositionFields = ["agents", "commands", "hooks", "mcpServers", "skills"]; const compositionFields = ["agents", "hooks", "mcpServers", "skills"];
const extensions = plugin.extensions; const extensions = plugin.extensions;
const composition = extensions?.[AWESOME_COPILOT_NAMESPACE]; const composition = extensions?.[AWESOME_COPILOT_NAMESPACE];
@@ -291,7 +290,7 @@ function validatePlugin(folderName) {
// Rule 5b: license (shared with external plugins). Non-SPDX is a warning, not an error. // Rule 5b: license (shared with external plugins). Non-SPDX is a warning, not an error.
const warnings = []; const warnings = [];
for (const field of ["agents", "commands", "hooks", "mcpServers", "skills"]) { for (const field of ["agents", "hooks", "mcpServers", "skills"]) {
if (plugin[field] !== undefined) { if (plugin[field] !== undefined) {
errors.push(`${field} must be moved to extensions["${AWESOME_COPILOT_NAMESPACE}"].${field}`); errors.push(`${field} must be moved to extensions["${AWESOME_COPILOT_NAMESPACE}"].${field}`);
} }
@@ -301,7 +300,7 @@ function validatePlugin(folderName) {
errors.push(...licenseResult.errors); errors.push(...licenseResult.errors);
warnings.push(...licenseResult.warnings); warnings.push(...licenseResult.warnings);
// Rule 6: agents, commands, skills paths // Rule 6: agents, hooks, and skills paths
const specErrors = validateSpecPaths(plugin); const specErrors = validateSpecPaths(plugin);
errors.push(...specErrors); errors.push(...specErrors);

Before

Width:  |  Height:  |  Size: 380 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Before

Width:  |  Height:  |  Size: 296 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 518 B

Some files were not shown because too many files have changed in this diff Show More