From d1cb70036aaf121230c1dfcb6fd788a402aa5abf Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 25 Jun 2026 04:53:20 +0000
Subject: [PATCH] chore: publish from main
---
eng/generate-website-data.mjs | 26 +++++++++++++++++
extensions/accessibility-kanban/canvas.json | 4 +++
extensions/arcade-canvas/canvas.json | 4 +++
extensions/backlog-swipe-triage/canvas.json | 4 +++
.../chromium-control-canvas/canvas.json | 4 +++
extensions/color-orb/canvas.json | 4 +++
extensions/diagram-viewer/canvas.json | 4 +++
extensions/external.json | 1 +
extensions/feedback-themes/canvas.json | 4 +++
extensions/gesture-review/canvas.json | 4 +++
extensions/release-notes-showcase/canvas.json | 4 +++
extensions/where-was-i/canvas.json | 4 +++
.../src/scripts/pages/extensions-render.ts | 28 ++++++++++++++++++-
website/src/scripts/pages/extensions.ts | 20 +++++++++++++
website/src/scripts/utils.ts | 25 +++++++++++++++++
15 files changed, 139 insertions(+), 1 deletion(-)
diff --git a/eng/generate-website-data.mjs b/eng/generate-website-data.mjs
index f20a8bbe..875eb803 100755
--- a/eng/generate-website-data.mjs
+++ b/eng/generate-website-data.mjs
@@ -101,6 +101,25 @@ function normalizeText(value, fallback = "") {
return typeof value === "string" ? value.trim() : fallback;
}
+/**
+ * Normalize an author value (npm string form or { name, url } object) to
+ * { name, url? } | null. Returns null when no usable name is present.
+ */
+function normalizeAuthor(value) {
+ if (!value) return null;
+ if (typeof value === "string") {
+ const name = value.trim();
+ return name ? { name } : null;
+ }
+ if (typeof value === "object") {
+ const name = normalizeText(value.name);
+ if (!name) return null;
+ const url = normalizeText(value.url);
+ return url ? { name, url } : { name };
+ }
+ return null;
+}
+
/**
* Find the latest git-modified date for any file under a directory.
*/
@@ -1002,6 +1021,10 @@ function generateCanvasManifest(gitDates, commitSha) {
const packageJson = fs.existsSync(packageJsonPath)
? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))
: {};
+ const canvasJsonPath = path.join(extensionDir, "canvas.json");
+ const canvasJson = fs.existsSync(canvasJsonPath)
+ ? JSON.parse(fs.readFileSync(canvasJsonPath, "utf-8"))
+ : {};
const keywords = Array.isArray(packageJson.keywords)
? [...new Set(packageJson.keywords.filter((keyword) => typeof keyword === "string").map((keyword) => keyword.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b))
: [];
@@ -1044,6 +1067,7 @@ function generateCanvasManifest(gitDates, commitSha) {
installUrl,
sourceUrl: null,
external: false,
+ author: normalizeAuthor(canvasJson.author),
keywords,
});
}
@@ -1116,6 +1140,7 @@ function generateCanvasManifest(gitDates, commitSha) {
installUrl,
sourceUrl: sourceUrl || null,
external: true,
+ author: normalizeAuthor(ext?.author),
keywords,
});
}
@@ -1199,6 +1224,7 @@ function writePerExtensionCanvasManifests(canvasManifestData) {
name: item.name,
description: item.description || "Canvas extension",
version: item.version || "1.0.0",
+ ...(item.author ? { author: item.author } : {}),
keywords: Array.isArray(item.keywords)
? [...new Set(item.keywords)].sort((a, b) => a.localeCompare(b))
: [],
diff --git a/extensions/accessibility-kanban/canvas.json b/extensions/accessibility-kanban/canvas.json
index 6bc4538e..c9aecb27 100644
--- a/extensions/accessibility-kanban/canvas.json
+++ b/extensions/accessibility-kanban/canvas.json
@@ -3,6 +3,10 @@
"name": "Accessibility Kanban",
"description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.",
"version": "1.0.0",
+ "author": {
+ "name": "Aaron Powell",
+ "url": "https://github.com/aaronpowell"
+ },
"keywords": [
"accessibility",
"github-issues",
diff --git a/extensions/arcade-canvas/canvas.json b/extensions/arcade-canvas/canvas.json
index 259a1264..daf543c6 100644
--- a/extensions/arcade-canvas/canvas.json
+++ b/extensions/arcade-canvas/canvas.json
@@ -3,6 +3,10 @@
"name": "Agent Arcade",
"description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.",
"version": "1.0.0",
+ "author": {
+ "name": "Dan Wahlin",
+ "url": "https://github.com/DanWahlin"
+ },
"keywords": [
"arcade-games",
"copilot-canvas",
diff --git a/extensions/backlog-swipe-triage/canvas.json b/extensions/backlog-swipe-triage/canvas.json
index b4b5b350..08dd7ead 100644
--- a/extensions/backlog-swipe-triage/canvas.json
+++ b/extensions/backlog-swipe-triage/canvas.json
@@ -3,6 +3,10 @@
"name": "Backlog Swipe Triage",
"description": "Quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.",
"version": "1.0.0",
+ "author": {
+ "name": "James Montemagno",
+ "url": "https://github.com/jamesmontemagno"
+ },
"keywords": [
"agent-assignment",
"backlog-triage",
diff --git a/extensions/chromium-control-canvas/canvas.json b/extensions/chromium-control-canvas/canvas.json
index 79ff4cf6..3325ca63 100644
--- a/extensions/chromium-control-canvas/canvas.json
+++ b/extensions/chromium-control-canvas/canvas.json
@@ -3,6 +3,10 @@
"name": "Chromium Control Canvas",
"description": "Opens a real Chromium window you can navigate and interact with from a Copilot canvas control panel and agent actions.",
"version": "1.0.0",
+ "author": {
+ "name": "Andrea Griffiths",
+ "url": "https://github.com/AndreaGriffiths11"
+ },
"keywords": [
"browser-control",
"chromium-browser",
diff --git a/extensions/color-orb/canvas.json b/extensions/color-orb/canvas.json
index 47a1a32b..a68404ed 100644
--- a/extensions/color-orb/canvas.json
+++ b/extensions/color-orb/canvas.json
@@ -3,6 +3,10 @@
"name": "Color Orb",
"description": "A visual orb that users can ask the agent to recolor while showing a live activity log in the canvas.",
"version": "1.0.0",
+ "author": {
+ "name": "Aaron Powell",
+ "url": "https://github.com/aaronpowell"
+ },
"keywords": [
"agent-actions",
"color-picker",
diff --git a/extensions/diagram-viewer/canvas.json b/extensions/diagram-viewer/canvas.json
index 0fe72fde..ddc3b4d2 100644
--- a/extensions/diagram-viewer/canvas.json
+++ b/extensions/diagram-viewer/canvas.json
@@ -3,6 +3,10 @@
"name": "Diagram Explorer",
"description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.",
"version": "1.0.0",
+ "author": {
+ "name": "Aaron Powell",
+ "url": "https://github.com/aaronpowell"
+ },
"keywords": [
"architecture-mapping",
"canvas-navigation",
diff --git a/extensions/external.json b/extensions/external.json
index c6b98bcc..f0e2f561 100644
--- a/extensions/external.json
+++ b/extensions/external.json
@@ -3,6 +3,7 @@
"id": "coffilot",
"name": "Coffilot",
"description": "Java-focused Copilot canvas extension from jdubois.",
+ "author": { "name": "Julien Dubois", "url": "https://github.com/jdubois" },
"keywords": [
"java",
"canvas",
diff --git a/extensions/feedback-themes/canvas.json b/extensions/feedback-themes/canvas.json
index 01a269be..3bc88213 100644
--- a/extensions/feedback-themes/canvas.json
+++ b/extensions/feedback-themes/canvas.json
@@ -3,6 +3,10 @@
"name": "Feedback Themes",
"description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.",
"version": "1.0.0",
+ "author": {
+ "name": "Aaron Powell",
+ "url": "https://github.com/aaronpowell"
+ },
"keywords": [
"customer-feedback",
"impact-prioritization",
diff --git a/extensions/gesture-review/canvas.json b/extensions/gesture-review/canvas.json
index c05ecddf..61f91cdc 100644
--- a/extensions/gesture-review/canvas.json
+++ b/extensions/gesture-review/canvas.json
@@ -3,6 +3,10 @@
"name": "Gesture PR Review",
"description": "Review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.",
"version": "1.0.0",
+ "author": {
+ "name": "Aaron Powell",
+ "url": "https://github.com/aaronpowell"
+ },
"keywords": [
"camera-input",
"gesture-control",
diff --git a/extensions/release-notes-showcase/canvas.json b/extensions/release-notes-showcase/canvas.json
index a21dbbb2..4b1b1835 100644
--- a/extensions/release-notes-showcase/canvas.json
+++ b/extensions/release-notes-showcase/canvas.json
@@ -3,6 +3,10 @@
"name": "Release Notes Showcase",
"description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.",
"version": "1.0.0",
+ "author": {
+ "name": "James Montemagno",
+ "url": "https://github.com/jamesmontemagno"
+ },
"keywords": [
"changelog",
"contributor-callouts",
diff --git a/extensions/where-was-i/canvas.json b/extensions/where-was-i/canvas.json
index 2909dfb2..bf5a69e3 100644
--- a/extensions/where-was-i/canvas.json
+++ b/extensions/where-was-i/canvas.json
@@ -3,6 +3,10 @@
"name": "Where Was I?",
"description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.",
"version": "1.0.0",
+ "author": {
+ "name": "Aaron Powell",
+ "url": "https://github.com/aaronpowell"
+ },
"keywords": [
"branch-state",
"developer-context",
diff --git a/website/src/scripts/pages/extensions-render.ts b/website/src/scripts/pages/extensions-render.ts
index ac8f2539..8172cef5 100644
--- a/website/src/scripts/pages/extensions-render.ts
+++ b/website/src/scripts/pages/extensions-render.ts
@@ -1,4 +1,10 @@
-import { escapeHtml, getGitHubUrl, getLastUpdatedHtml } from "../utils";
+import {
+ escapeHtml,
+ getGitHubHandle,
+ getGitHubUrl,
+ getLastUpdatedHtml,
+ sanitizeUrl,
+} from "../utils";
import { renderEmptyStateHtml, renderSharedCardHtml } from "./card-render";
export interface RenderableExtension {
@@ -34,6 +40,7 @@ export interface RenderableExtension {
installUrl?: string | null;
sourceUrl?: string | null;
external?: boolean;
+ author?: { name: string; url?: string } | null;
}
export type ExtensionSortOption = "title" | "lastUpdated";
@@ -92,8 +99,27 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string {
`;
+ const authorName = item.author?.name;
+ const authorUrl = item.author?.url;
+ const authorHandle =
+ authorName && authorUrl
+ ? getGitHubHandle(authorUrl, authorName)
+ : authorName || "";
+ const authorHtml = authorName
+ ? `by ${
+ authorUrl
+ ? `${escapeHtml(authorHandle)}`
+ : escapeHtml(authorName)
+ }`
+ : "";
+
const metaHtml = `
${item.external ? 'External' : ""}
+ ${authorHtml}
${getLastUpdatedHtml(item.lastUpdated)}
`;
diff --git a/website/src/scripts/pages/extensions.ts b/website/src/scripts/pages/extensions.ts
index 3c3c9c94..09edf1de 100644
--- a/website/src/scripts/pages/extensions.ts
+++ b/website/src/scripts/pages/extensions.ts
@@ -12,9 +12,11 @@ import {
copyToClipboard,
fetchData,
formatRelativeTime,
+ getGitHubHandle,
getGitHubUrl,
getQueryParam,
getQueryParamValues,
+ sanitizeUrl,
showToast,
updateQueryParams,
} from "../utils";
@@ -178,6 +180,24 @@ function openDetailsModal(
if (item.external) {
metaParts.push('External');
}
+ if (item.author?.name) {
+ const authorName = item.author.name;
+ const authorUrl = item.author.url;
+ const authorHandle = authorUrl
+ ? getGitHubHandle(authorUrl, authorName)
+ : authorName;
+ metaParts.push(
+ authorUrl
+ ? `by ${escapeHtml(authorHandle)}`
+ : `by ${escapeHtml(
+ authorName
+ )}`
+ );
+ }
if (item.lastUpdated) {
metaParts.push(
`Updated ${escapeHtml(
diff --git a/website/src/scripts/utils.ts b/website/src/scripts/utils.ts
index 30cc1fac..b90d7793 100644
--- a/website/src/scripts/utils.ts
+++ b/website/src/scripts/utils.ts
@@ -389,6 +389,31 @@ export function sanitizeUrl(url: string | null | undefined): string {
return "#";
}
+/**
+ * Derive a GitHub @handle from a profile URL
+ * (e.g. "https://github.com/aaronpowell" -> "@aaronpowell").
+ * Falls back to the provided value when the URL is not a github.com profile.
+ */
+export function getGitHubHandle(
+ url: string | null | undefined,
+ fallback = ""
+): string {
+ if (!url) return fallback;
+ try {
+ const parsed = new URL(url);
+ const host = parsed.hostname.replace(/^www\./, "");
+ if (host === "github.com") {
+ const segments = parsed.pathname.split("/").filter(Boolean);
+ if (segments.length === 1) {
+ return `@${segments[0]}`;
+ }
+ }
+ } catch {
+ // Invalid URL
+ }
+ return fallback;
+}
+
/**
* Truncate text with ellipsis
*/