chore: publish from main

This commit is contained in:
github-actions[bot]
2026-07-10 03:24:21 +00:00
parent 9a8e446f02
commit 51b6fa0253
56 changed files with 4496 additions and 992 deletions
@@ -0,0 +1,25 @@
---
export type Breadcrumb = {
paths: { name: string; href?: string }[];
title: string;
};
const { paths, title } = Astro.props as Breadcrumb;
---
<nav class="detail-breadcrumbs" aria-label="Breadcrumb">
{
paths.map((path, index) => (
<>
{path.href ? (
<a href={path.href}>{path.name}</a>
) : (
<span>{path.name}</span>
)}
{index < paths.length - 1 && <span aria-hidden="true">/</span>}
</>
))
}
<span aria-hidden="true">/</span>
<span aria-current="page">{title}</span>
</nav>
@@ -0,0 +1,145 @@
---
import { getFileMeta } from "../../lib/file-types";
export interface BrowserFile {
name: string;
path: string;
size?: number;
}
export interface FileBrowserProps {
files: BrowserFile[];
/** Full repo path of the primary file, e.g. skills/foo/SKILL.md or hooks/foo/README.md */
primaryPath: string;
/** Display name of the primary file, e.g. SKILL.md or README.md */
primaryName: string;
/** Pre-rendered HTML for the primary file (shown by default) */
primaryHtml: string;
/** GitHub blob base, e.g. https://github.com/github/awesome-copilot/blob/main */
githubBase: string;
}
const { files, primaryPath, primaryName, primaryHtml, githubBase } =
Astro.props as FileBrowserProps;
// Primary file first, then everything else alphabetically by name.
const sortedFiles = [...files].sort((a, b) => {
if (a.path === primaryPath) return -1;
if (b.path === primaryPath) return 1;
return a.name.localeCompare(b.name);
});
const otherFiles = sortedFiles.filter((f) => f.path !== primaryPath);
const rootFiles = otherFiles.filter((f) => !f.name.includes("/"));
// Group nested files by their top-level folder for optgroup rendering.
const folderGroups = new Map<string, BrowserFile[]>();
for (const file of otherFiles) {
const slash = file.name.indexOf("/");
if (slash === -1) continue;
const folder = file.name.slice(0, slash);
const bucket = folderGroups.get(folder) ?? [];
bucket.push(file);
folderGroups.set(folder, bucket);
}
const sortedFolders = [...folderGroups.keys()].sort((a, b) =>
a.localeCompare(b)
);
const hasMultipleFiles = sortedFiles.length > 1;
function optionAttrs(file: BrowserFile) {
const meta = getFileMeta(file.name);
return {
value: file.path,
"data-file-name": file.name,
"data-file-lang": meta.lang,
"data-file-kind": meta.kind,
};
}
---
<section
class="skill-file-browser"
aria-label="Bundle files"
data-file-browser
data-github-base={githubBase}
data-primary-file={primaryPath}
>
<div class="skill-file-view">
<header class="skill-file-view-header">
{
hasMultipleFiles ? (
<div class="skill-file-picker">
<label class="skill-file-picker-label" for="file-browser-select">
File
</label>
<select
id="file-browser-select"
class="skill-file-select"
data-file-select
aria-label="Select a file to view"
>
<option {...optionAttrs({ name: primaryName, path: primaryPath })} selected>
{primaryName}
</option>
{rootFiles.map((file) => (
<option {...optionAttrs(file)}>{file.name}</option>
))}
{sortedFolders.map((folder) => (
<optgroup label={`${folder}/`}>
{folderGroups.get(folder)!.map((file) => (
<option {...optionAttrs(file)}>
{file.name.slice(folder.length + 1)}
</option>
))}
</optgroup>
))}
</select>
</div>
) : (
<code class="skill-file-current" data-current-file-name>
{primaryName}
</code>
)
}
<div class="skill-file-view-actions">
<button
type="button"
class="btn btn-secondary btn-small"
data-action="copy-file"
title="Copy file contents"
>
<svg
viewBox="0 0 16 16"
width="14"
height="14"
fill="currentColor"
aria-hidden="true"
><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><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"
></path></svg
>
Copy
</button>
<a
class="btn btn-secondary btn-small"
data-file-github
href={`${githubBase}/${primaryPath}`}
target="_blank"
rel="noopener"
title="View file on GitHub">GitHub</a
>
</div>
</header>
<div
class="skill-file-content article-content"
data-file-content
set:html={primaryHtml}
/>
<div class="skill-file-status" data-file-status hidden></div>
</div>
</section>
+13
View File
@@ -0,0 +1,13 @@
---
export type HeaderItem = {
title: string;
description?: string;
};
const { item } = Astro.props as { item: HeaderItem };
---
<header class="detail-header">
<h1>{item.title}</h1>
{item.description && <p class="detail-description">{item.description}</p>}
</header>
@@ -0,0 +1,93 @@
---
export interface PluginIncludedItem {
kind: string;
path: string;
title?: string;
detailUrl?: string | null;
}
export interface IncludedItemsProps {
items: PluginIncludedItem[];
pluginPath: string;
githubBase: string;
}
const { items, pluginPath, githubBase } = Astro.props as IncludedItemsProps;
const KIND_LABELS: Record<string, string> = {
agent: "Agents",
skill: "Skills",
instruction: "Instructions",
hook: "Hooks",
prompt: "Commands",
extension: "Extensions",
};
const KIND_ORDER = ["agent", "skill", "instruction", "hook", "prompt", "extension"];
function githubHref(itemPath: string): string {
const normalized = itemPath.replace(/^\.\/+/, "").replace(/\/+$/, "");
// Repo-root-relative paths (e.g. extensions/foo, plugins/foo) are used as-is;
// plugin-relative paths are resolved against the plugin folder.
const isRepoRelative = /^[a-z0-9._-]+\//i.test(normalized) &&
(normalized.startsWith("extensions/") ||
normalized.startsWith("plugins/") ||
normalized.startsWith("agents/") ||
normalized.startsWith("skills/") ||
normalized.startsWith("instructions/") ||
normalized.startsWith("hooks/"));
const repoPath = isRepoRelative ? normalized : `${pluginPath}/${normalized}`;
// GitHub uses /blob/ for files and /tree/ for directories; /tree/<ref>/<file>
// returns 404. Detect files by a trailing extension on the last path segment.
const lastSegment = repoPath.split("/").pop() ?? "";
const isFile = /\.[a-z0-9]+$/i.test(lastSegment);
const base = isFile ? githubBase.replace("/tree/", "/blob/") : githubBase;
return `${base}/${repoPath}`;
}
const grouped = KIND_ORDER.map((kind) => ({
kind,
label: KIND_LABELS[kind] ?? kind,
entries: (items ?? []).filter((item) => item.kind === kind),
})).filter((group) => group.entries.length > 0);
---
{
grouped.length > 0 && (
<section class="detail-section included-items" aria-labelledby="included-heading">
<h2 id="included-heading">Included items</h2>
<p class="included-items-hint">
This plugin bundles the following resources. Installing the plugin brings
them all in together.
</p>
{grouped.map((group) => (
<div class="included-group">
<h3 class="included-group-title">{group.label}</h3>
<ul class="included-list">
{group.entries.map((item) => {
const href = item.detailUrl ?? githubHref(item.path);
const isExternal = !item.detailUrl;
return (
<li class="included-entry">
<a
class="included-link"
href={href}
{...(isExternal
? { target: "_blank", rel: "noopener" }
: {})}
>
<span class="included-kind-badge">{item.kind}</span>
<span class="included-title">{item.title || item.path}</span>
{isExternal && (
<span class="included-external-hint">GitHub ↗</span>
)}
</a>
</li>
);
})}
</ul>
</div>
))}
</section>
)
}
@@ -0,0 +1,61 @@
---
const { vscodeUrl, insidersUrl, rawMarkdown } = Astro.props as { vscodeUrl: string; insidersUrl: string; rawMarkdown?: string };
---
<div class="install-dropdown" data-install-menu>
<a
class="btn btn-primary install-btn-main"
href={vscodeUrl}
target="_blank"
rel="noopener"
>
<svg
viewBox="0 0 24 24"
width="16"
height="16"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"><path d="M12 5v14M5 12h14"></path></svg
>
Install
</a>
<button
type="button"
class="btn btn-primary install-btn-toggle"
aria-label="More install options"
aria-haspopup="true"
aria-expanded="false"
data-install-toggle
>
<svg
viewBox="0 0 16 16"
width="12"
height="12"
fill="currentColor"
aria-hidden="true"
><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"
></path></svg
>
</button>
<div class="install-dropdown-menu" role="menu">
<a href={vscodeUrl} target="_blank" rel="noopener" role="menuitem"
>Install in VS Code</a
>
<a href={insidersUrl} target="_blank" rel="noopener" role="menuitem"
>Install in VS Code Insiders</a
>
<div class="dropdown-divider" role="separator" aria-hidden="true"></div>
<a href="#" role="menuitem" data-action="download">Download file</a>
{
rawMarkdown && (
<a href="#" role="menuitem" data-action="copy-markdown">
Copy markdown
</a>
)
}
</div>
</div>
+17
View File
@@ -0,0 +1,17 @@
---
const githubUrl = Astro.props.githubUrl as string;
const markdownHtml = Astro.props.markdownHtml as string;
---
<div class="detail-main">
{markdownHtml ? (
<section class="detail-section" aria-labelledby="doc-heading">
<h2 id="doc-heading">Documentation</h2>
<div class="article-content" set:html={markdownHtml} />
</section>
) : (
<section class="detail-section">
<p class="detail-empty">Documentation preview is unavailable. View the file <a href={githubUrl} target="_blank" rel="noopener">on GitHub</a>.</p>
</section>
)}
</div>
@@ -0,0 +1,171 @@
---
/**
* Install actions for plugins and canvas extensions.
*
* Renders a split-button dropdown that deep-links into VS Code, VS Code
* Insiders, and the GitHub Copilot app, plus an optional CLI command block.
* The dropdown open/close is wired by resource-detail.ts via `data-install-menu`.
*/
export interface PluginInstallProps {
isExternal?: boolean;
/** awesome-copilot plugin id (internal installs). */
pluginId?: string | null;
/** Upstream marketplace source (owner/repo or git URL) for external installs. */
externalSource?: string | null;
/** CLI install command (internal only). */
installCommand?: string | null;
/** Heading shown above the buttons. */
label?: string;
/** Note shown for external installs. */
note?: string;
}
const {
isExternal = false,
pluginId = null,
externalSource = null,
installCommand = null,
label,
note,
} = Astro.props as PluginInstallProps;
const MARKETPLACE = "awesome-copilot";
let vscodeUrl: string | null = null;
let insidersUrl: string | null = null;
let ghappUrl: string | null = null;
if (isExternal) {
if (externalSource) {
vscodeUrl = `vscode://chat-plugin/install?source=${encodeURIComponent(
externalSource
)}`;
insidersUrl = `vscode-insiders://chat-plugin/install?source=${encodeURIComponent(
externalSource
)}`;
ghappUrl = `ghapp://plugins/marketplace/add?source=${encodeURIComponent(
externalSource
)}`;
}
} else if (pluginId) {
const plugin = encodeURIComponent(pluginId);
vscodeUrl = `vscode://chat-plugin/install?source=github/awesome-copilot&plugin=${plugin}`;
insidersUrl = `vscode-insiders://chat-plugin/install?source=github/awesome-copilot&plugin=${plugin}`;
ghappUrl = `ghapp://plugins/install?source=${encodeURIComponent(
`${pluginId}@${MARKETPLACE}`
)}`;
}
const ghappLabel = isExternal
? "Open in GitHub Copilot app"
: "Install in GitHub Copilot app";
const primaryUrl = ghappUrl ?? vscodeUrl ?? insidersUrl;
const primaryLabel =
primaryUrl === ghappUrl
? ghappLabel
: primaryUrl === vscodeUrl
? "Install in VS Code"
: "Install in VS Code Insiders";
const primaryIsGhapp = primaryUrl === ghappUrl;
const heading = label ?? (isExternal ? "Install this plugin" : "Install");
---
<div class="skill-install">
<p class="skill-install-label">{heading}</p>
{note && <p class="skill-install-note">{note}</p>}
{
primaryUrl && (
<div class="install-dropdown" data-install-menu>
<a
class="btn btn-primary install-btn-main"
href={primaryUrl}
{...(primaryIsGhapp ? {} : { target: "_blank", rel: "noopener" })}
>
<svg
viewBox="0 0 24 24"
width="16"
height="16"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 5v14M5 12h14" />
</svg>
{primaryLabel}
</a>
<button
type="button"
class="btn btn-primary install-btn-toggle"
aria-label="More install options"
aria-haspopup="true"
aria-expanded="false"
data-install-toggle
>
<svg
viewBox="0 0 16 16"
width="12"
height="12"
fill="currentColor"
aria-hidden="true"
>
<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="install-dropdown-menu" role="menu">
{ghappUrl && (
<a href={ghappUrl} role="menuitem">
{ghappLabel}
</a>
)}
{ghappUrl && (vscodeUrl || insidersUrl) && (
<div class="dropdown-divider" role="separator" aria-hidden="true" />
)}
{vscodeUrl && (
<a href={vscodeUrl} target="_blank" rel="noopener" role="menuitem">
Install in VS Code
</a>
)}
{insidersUrl && (
<a href={insidersUrl} target="_blank" rel="noopener" role="menuitem">
Install in VS Code Insiders
</a>
)}
</div>
</div>
)
}
{
installCommand && (
<>
<p class="skill-install-label">Or install with the CLI</p>
<div class="skill-install-command" data-install-command={installCommand}>
<code tabindex="0">{installCommand}</code>
<button
type="button"
class="skill-install-copy"
data-action="copy-install"
title="Copy install command"
aria-label="Copy install command"
>
<svg
viewBox="0 0 16 16"
width="15"
height="15"
fill="currentColor"
aria-hidden="true"
>
<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>
</button>
</div>
</>
)
}
</div>
@@ -0,0 +1,9 @@
---
const { markdown } = Astro.props as { markdown?: string };
---
{
markdown && (
<textarea data-raw-markdown hidden aria-hidden="true" readonly set:text={markdown}></textarea>
)
}
+104
View File
@@ -0,0 +1,104 @@
---
import type { SidebarChipsProps } from "./SidebarChips.astro";
import SidebarChips from "./SidebarChips.astro";
export interface SidebarProps {
item: any;
lastUpdated?: string;
frontmatterText?: string;
githubUrl: string;
chips?: SidebarChipsProps[];
}
const {
item,
lastUpdated,
frontmatterText,
githubUrl,
chips,
} = Astro.props as SidebarProps;
---
<aside class="detail-sidebar" aria-label="Details">
<section class="detail-card detail-actions-card" data-actions>
<slot name="install" />
<div class="detail-actions-secondary">
<button class="btn btn-secondary" type="button" data-action="share">
<svg
viewBox="0 0 24 24"
width="15"
height="15"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"
></circle><circle cx="18" cy="19" r="3"></circle><path
d="M8.59 13.51l6.83 3.98M15.41 6.51l-6.82 3.98"></path></svg
>
Share
</button>
<a
class="btn btn-secondary"
href={githubUrl}
target="_blank"
rel="noopener"
>
<svg
viewBox="0 0 24 24"
width="15"
height="15"
fill="currentColor"
aria-hidden="true"
><path
d="M12 2C6.48 2 2 6.48 2 12c0 4.42 2.87 8.17 6.84 9.5.5.09.68-.22.68-.48 0-.24-.01-.87-.01-1.71-2.78.6-3.37-1.34-3.37-1.34-.45-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.61.07-.61 1 .07 1.53 1.03 1.53 1.03.89 1.53 2.34 1.09 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.65 0 0 .84-.27 2.75 1.02.8-.22 1.65-.33 2.5-.33.85 0 1.7.11 2.5.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.38.2 2.4.1 2.65.64.7 1.03 1.59 1.03 2.68 0 3.84-2.34 4.69-4.57 4.94.36.31.68.92.68 1.85 0 1.34-.01 2.42-.01 2.75 0 .27.18.58.69.48A10.01 10.01 0 0022 12c0-5.52-4.48-10-10-10z"
></path></svg
>
View on GitHub
</a>
</div>
</section>
<section class="detail-card">
<h2>Details</h2>
<dl class="detail-meta">
{
chips?.map((chip) => (
<SidebarChips
title={chip.title}
items={chip.items}
filterBase={chip.filterBase}
filterParam={chip.filterParam}
/>
))
}
<div>
<dt>Source</dt>
<dd><code class="detail-path">{item.path}</code></dd>
</div>
{
lastUpdated && (
<div>
<dt>Last updated</dt>
<dd>{lastUpdated}</dd>
</div>
)
}
</dl>
</section>
{
frontmatterText && (
<section class="detail-card">
<details class="detail-frontmatter">
<summary>Frontmatter</summary>
<pre>
<code>{frontmatterText}</code>
</pre>
</details>
</section>
)
}
</aside>
@@ -0,0 +1,51 @@
---
export type SidebarChipsProps = {
title: string;
items: string[];
/**
* When both `filterBase` and `filterParam` are provided, each chip is
* rendered as a link to the resource's list page pre-filtered by that value
* (e.g. `/hooks/?tag=testing`), turning read-only metadata into navigation.
*/
filterBase?: string;
filterParam?: string;
};
const { title, items, filterBase, filterParam } =
Astro.props as SidebarChipsProps;
const slug = title
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
const linkable = Boolean(filterBase && filterParam);
const chipHref = (value: string) =>
`${filterBase}?${filterParam}=${encodeURIComponent(value)}`;
---
<div>
<dt>{title}</dt>
<dd>
{
items.length ? (
<div class="detail-chips">
{items.map((item) =>
linkable ? (
<a
class={`resource-tag resource-tag-link tag-${slug}`}
href={chipHref(item)}
aria-label={`Filter by ${title.toLowerCase()}: ${item}`}
>
{item}
</a>
) : (
<span class={`resource-tag tag-${slug}`}>{item}</span>
)
)}
</div>
) : (
<span class="detail-none">Not specified</span>
)
}
</dd>
</div>