Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 146 additions & 69 deletions .github/plugin/marketplace.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion .github/workflows/build-website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ on:
- hooks
- workflows
- extensions
- .schemas/canvas.schema.json

permissions:
contents: read
Expand Down
108 changes: 2 additions & 106 deletions .github/workflows/validate-canvas-extensions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ on:
types: [opened, synchronize, reopened]
paths:
- "extensions/**"
- ".schemas/canvas.schema.json"

Comment thread
aaronpowell marked this conversation as resolved.
permissions:
contents: read
Expand All @@ -29,110 +28,7 @@ jobs:
- name: Install dependencies
run: npm ci --ignore-scripts

- name: Validate changed canvas extensions
- name: Validate changed extensions
run: |
set -euo pipefail

# Validate schema structure once, even for schema-only PRs.
if ! node ./eng/validate-json-schema.mjs --schema .schemas/canvas.schema.json; then
echo "❌ .schemas/canvas.schema.json failed schema compilation"
exit 1
fi

# Collect changed extension directories.
# Use null-terminated (-z) output from git diff so filenames containing newlines
# or other special characters are read atomically (matches the pattern in skill-check.yml).
# Each extracted name is then validated against a strict allowlist regex before use,
# rejecting anything containing shell metacharacters ($, (, ), spaces, etc.).
declare -A seen_dirs=()
changed_extensions=()
errors=()

while IFS= read -r -d '' file; do
case "$file" in
extensions/*)
ext_name="${file#extensions/}"
ext_name="${ext_name%%/*}"

if [ "$ext_name" = "external-assets" ]; then
continue
fi

# Allowlist: extension directory names must be lowercase alphanumeric + hyphens only.
# Fail for disallowed names so a PR cannot bypass validation by using a nonconforming folder.
if [[ ! "$ext_name" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
errors+=("\`extensions/$ext_name\`: invalid extension directory name (must match ^[a-z0-9][a-z0-9-]*$).")
continue
fi

if [ -z "${seen_dirs[$ext_name]+x}" ]; then
seen_dirs["$ext_name"]=1
changed_extensions+=("extensions/$ext_name")
fi
;;
esac
done < <(git diff --name-only -z "origin/${{ github.base_ref }}...HEAD")

if [ "${#changed_extensions[@]}" -eq 0 ]; then
if [ "${#errors[@]}" -ne 0 ]; then
echo "❌ Canvas extension validation failed:"
for error in "${errors[@]}"; do
echo "- $error"
done
exit 1
fi
echo "No canvas extension directories changed — skipping validation."
exit 0
fi

echo "Validating ${#changed_extensions[@]} extension(s): ${changed_extensions[*]}"

for ext_dir in "${changed_extensions[@]}"; do
if [ ! -d "$ext_dir" ]; then
echo "$ext_dir no longer exists (deleted?), skipping."
continue
fi

if [ ! -f "$ext_dir/extension.mjs" ]; then
errors+=("\`$ext_dir\`: missing required \`extension.mjs\`.")
fi

if [ ! -f "$ext_dir/canvas.json" ]; then
errors+=("\`$ext_dir\`: missing required \`canvas.json\`.")
continue
fi

if [ ! -f "$ext_dir/assets/preview.png" ]; then
errors+=("\`$ext_dir\`: missing required \`assets/preview.png\`.")
fi

if ! schema_output="$(node ./eng/validate-json-schema.mjs --schema .schemas/canvas.schema.json --data "$ext_dir/canvas.json" 2>&1)"; then
condensed_output="$(echo "$schema_output" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g')"
errors+=("\`$ext_dir/canvas.json\`: schema validation failed against \`.schemas/canvas.schema.json\` ($condensed_output).")
continue
fi

mapfile -t screenshot_paths < <(
node -e 'const fs = require("fs"); const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); const paths = [manifest?.screenshots?.icon?.path, manifest?.screenshots?.gallery?.path].filter((value) => typeof value === "string" && value.trim().length > 0); for (const value of [...new Set(paths)]) { console.log(value); }' "$ext_dir/canvas.json"
)

for screenshot_path in "${screenshot_paths[@]}"; do
if [[ ! "$screenshot_path" =~ ^assets/([A-Za-z0-9_-]+/)*[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*\.(png|jpe?g|gif|webp|svg)$ ]]; then
errors+=("\`$ext_dir/canvas.json\`: screenshot path \`$screenshot_path\` is not a valid assets path.")
continue
fi
if [ ! -f "$ext_dir/$screenshot_path" ]; then
errors+=("\`$ext_dir/canvas.json\`: screenshot path \`$screenshot_path\` does not exist in the extension directory.")
fi
done
done

if [ "${#errors[@]}" -ne 0 ]; then
echo "❌ Canvas extension validation failed:"
for error in "${errors[@]}"; do
echo "- $error"
done
exit 1
fi

echo "✅ All changed canvas extensions passed validation."
npm run plugin:validate
121 changes: 0 additions & 121 deletions .schemas/canvas.schema.json

This file was deleted.

22 changes: 22 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The Awesome GitHub Copilot repository is a community-driven collection of custom
├── hooks/ # Automated workflow hooks (folders with README.md + hooks.json)
├── workflows/ # Agentic Workflows (.md files for GitHub Actions automation)
├── plugins/ # Installable plugin packages (folders with plugin.json)
├── extensions/ # Canvas extensions (each with extension.mjs and plugin metadata)
├── docs/ # Documentation for different resource types
├── eng/ # Build and automation scripts
└── scripts/ # Utility scripts
Expand Down Expand Up @@ -81,6 +82,19 @@ All agent files (`*.agent.md`) and instruction files (`*.instructions.md`) must
- Asset files should be reasonably sized (under 5MB per file)
- Skills follow the [Agent Skills specification](https://agentskills.io/specification)

#### Canvas Extensions (extensions/\*)

- Each extension folder must include `extension.mjs`
- Extension metadata must live at `.github/plugin/plugin.json`
- Extension `plugin.json` **must** follow the convention:
- `name`, `description`, `version` are required
- `logo` **must** be exactly `"assets/preview.png"` (enforced convention)
- `extensions` **must** be exactly `"."` (per [copilot-agent-runtime#9929](https://github.com/github/copilot-agent-runtime/pull/9929))
- Optional: `author`, `keywords` fields
- **Must not** include `x-awesome-copilot` field (use convention-based `assets/preview.png` only)
- Each extension must have `assets/preview.png` as the primary visual asset
- Do not add `canvas.json`; website metadata is sourced from `.github/plugin/plugin.json`

#### Hook Folders (hooks/\*/README.md)

- Each hook is a folder containing a `README.md` file with frontmatter
Expand Down Expand Up @@ -160,6 +174,14 @@ When adding a new agent, instruction, skill, hook, workflow, or plugin:
5. Run `npm run build` to update README.md and marketplace.json
6. Verify the plugin appears in `.github/plugin/marketplace.json`

**For Canvas Extensions:**

1. Create/update the extension in `extensions/<extension-id>/` with `extension.mjs`
2. Add `.github/plugin/plugin.json` metadata (required: `name`, `description`, `version`, `logo: "assets/preview.png"`, `extensions: "."`; optional: `author`, `keywords`)
3. Ensure `assets/preview.png` exists as the primary visual asset
4. Run `npm run plugin:validate` to validate plugin and extension metadata
5. Run `npm run build` to regenerate website data and marketplace output
Comment thread
Copilot marked this conversation as resolved.

**For External Plugins:**

1. Do not open a direct PR that edits `plugins/external.json` for a public third-party plugin submission
Expand Down
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Thank you for your interest in contributing to the Awesome GitHub Copilot reposi
- [Adding Instructions](#adding-instructions)
- [Adding Agents](#adding-an-agent)
- [Adding Skills](#adding-skills)
- [Adding Canvas Extensions](#adding-canvas-extensions)
- [Adding Plugins](#adding-plugins)
- [Adding Hooks](#adding-hooks)
- [Adding Agentic Workflows](#adding-agentic-workflows)
Expand Down Expand Up @@ -137,6 +138,21 @@ Skills are self-contained folders in the `skills/` directory that include a `SKI
3. **Add optional assets**: Keep bundled assets reasonably sized (under 5MB each) and reference them from `SKILL.md`
4. **Validate and update docs**: Run `npm run skill:validate` and then `npm run build` to update the generated README tables

### Adding Canvas Extensions

Canvas extensions live in `extensions/<extension-id>/` and are installable through plugin metadata.

1. **Create/update extension metadata**: Add `.github/plugin/plugin.json` in the extension folder
2. **Use convention-based metadata**: Follow the extension plugin.json structure:
- Required: `name` (matching folder name), `description`, `version`
- Optional: `author`, `keywords`
- `logo` **must** be exactly `"assets/preview.png"` (enforced convention)
- `extensions` **must** be exactly `"."` (per [copilot-agent-runtime#9929](https://github.com/github/copilot-agent-runtime/pull/9929))
- **Never** include `x-awesome-copilot` field (use convention-based assets only)
3. **Screenshot requirements**: Create `assets/preview.png` as your primary visual
4. **Do not add `canvas.json`**: Extension website metadata is now sourced from `.github/plugin/plugin.json`
5. **Validate before submitting**: Run `npm run plugin:validate` to check compliance with conventions

### 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.
Expand Down Expand Up @@ -183,6 +199,7 @@ plugins/my-plugin-id/

- **Declarative content**: Plugin content is specified via `agents`, `commands`, and `skills` arrays in plugin.json — source files live in top-level directories and are materialized into plugins by CI
- **Valid references**: All paths referenced in plugin.json must point to existing source files in the repository
- **Optional extension links**: Curated plugins can reference extensions using `x-awesome-copilot.extensions` with paths like `./extensions/<extension-id>`
- **Instructions excluded**: Instructions are standalone resources and are not part of plugins
- **Clear purpose**: The plugin should solve a specific problem or workflow
- **Validate before submitting**: Run `npm run plugin:validate` to ensure your plugin is valid
Expand Down
Loading
Loading