From 2512d7d728e465220427a7e30ebdb76fa4c2f452 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 05:28:37 +0000 Subject: [PATCH] chore: publish from staged --- .../workflows/validate-canvas-extensions.yml | 3 +- eng/generate-website-data.mjs | 521 +++++++++++++++--- extensions/accessibility-kanban/canvas.json | 24 + extensions/accessibility-kanban/package.json | 11 +- extensions/backlog-swipe-triage/canvas.json | 24 + extensions/backlog-swipe-triage/package.json | 12 +- .../chromium-control-canvas/canvas.json | 25 + .../chromium-control-canvas/extension.mjs | 2 +- .../chromium-control-canvas/package.json | 14 +- extensions/color-orb/canvas.json | 24 + extensions/color-orb/package.json | 11 +- extensions/diagram-viewer/canvas.json | 24 + extensions/diagram-viewer/package.json | 11 +- extensions/external.json | 15 + extensions/feedback-themes/canvas.json | 24 + extensions/feedback-themes/package.json | 11 +- extensions/gesture-review/canvas.json | 24 + extensions/gesture-review/extension.mjs | 2 +- extensions/gesture-review/package.json | 11 +- extensions/release-notes-showcase/canvas.json | 24 + .../release-notes-showcase/package.json | 12 +- .../releaseNotesShowcase.mjs | 2 +- extensions/where-was-i/canvas.json | 24 + extensions/where-was-i/extension.mjs | 2 +- extensions/where-was-i/package.json | 18 + website/src/pages/extensions.astro | 7 +- .../src/scripts/pages/extensions-render.ts | 27 + website/src/scripts/pages/extensions.ts | 86 ++- website/src/styles/global.css | 17 + 29 files changed, 923 insertions(+), 89 deletions(-) create mode 100644 extensions/accessibility-kanban/canvas.json create mode 100644 extensions/backlog-swipe-triage/canvas.json create mode 100644 extensions/chromium-control-canvas/canvas.json create mode 100644 extensions/color-orb/canvas.json create mode 100644 extensions/diagram-viewer/canvas.json create mode 100644 extensions/feedback-themes/canvas.json create mode 100644 extensions/gesture-review/canvas.json create mode 100644 extensions/release-notes-showcase/canvas.json create mode 100644 extensions/where-was-i/canvas.json create mode 100644 extensions/where-was-i/package.json diff --git a/.github/workflows/validate-canvas-extensions.yml b/.github/workflows/validate-canvas-extensions.yml index 4699b04b..37a39ae2 100644 --- a/.github/workflows/validate-canvas-extensions.yml +++ b/.github/workflows/validate-canvas-extensions.yml @@ -42,7 +42,8 @@ jobs: if (parts[0] === EXTENSIONS_DIR && parts.length >= 2) { const extName = parts[1]; // Skip the external-assets directory — it's not a canvas extension - if (extName !== EXTERNAL_ASSETS_DIR) { + // Also skip external.json and other files at extensions root level + if (extName !== EXTERNAL_ASSETS_DIR && !extName.includes('.')) { changedExtDirs.add(path.join(EXTENSIONS_DIR, extName)); } } diff --git a/eng/generate-website-data.mjs b/eng/generate-website-data.mjs index b5d9b9b2..89c70679 100755 --- a/eng/generate-website-data.mjs +++ b/eng/generate-website-data.mjs @@ -674,11 +674,33 @@ function generatePluginsData(gitDates) { /** * Generate canvas extensions metadata */ -function getExtensionAssetInfo(extensionDir, relPath, ref) { +function getImageMimeType(filePath) { + const extension = path.extname(filePath).toLowerCase(); + const mimeByExtension = { + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".webp": "image/webp", + ".gif": "image/gif", + }; + return mimeByExtension[extension] || "application/octet-stream"; +} + +function resolveImageUrl(value, ref) { + const normalized = normalizeText(value); + if (!normalized) return null; + if (/^https?:\/\//i.test(normalized)) { + return normalized; + } + const repoPath = normalized.replace(/\\/g, "/").replace(/^\/+/, ""); + return buildRepoImageUrl(repoPath, ref); +} + +function getImageAssetFiles(extensionDir) { const assetDir = path.join(extensionDir, "assets"); if (!fs.existsSync(assetDir)) { - return null; + return []; } const imageExtensions = new Set([ @@ -689,7 +711,35 @@ function getExtensionAssetInfo(extensionDir, relPath, ref) { ".gif", ]); - const preferredNames = [ + return fs + .readdirSync(assetDir) + .filter((file) => imageExtensions.has(path.extname(file).toLowerCase())) + .sort((a, b) => a.localeCompare(b)); +} + +function pickAssetFile(files, preferredNames) { + const preferredLookup = new Set(preferredNames.map((name) => name.toLowerCase())); + for (const file of files) { + if (preferredLookup.has(file.toLowerCase())) { + return file; + } + } + return files[0] || null; +} + +function getExtensionAssetInfo(extensionDir, relPath, ref) { + const files = getImageAssetFiles(extensionDir); + + if (files.length === 0) { + return null; + } + + const iconAsset = pickAssetFile(files, [ + "icon.png", + "icon.jpg", + "icon.jpeg", + "icon.webp", + "icon.gif", "preview.png", "preview.jpg", "preview.jpeg", @@ -705,34 +755,52 @@ function getExtensionAssetInfo(extensionDir, relPath, ref) { "image.jpeg", "image.webp", "image.gif", - ]; + ]); + const galleryAsset = pickAssetFile(files, [ + "gallery.png", + "gallery.jpg", + "gallery.jpeg", + "gallery.webp", + "gallery.gif", + "preview.png", + "preview.jpg", + "preview.jpeg", + "preview.webp", + "preview.gif", + "screenshot.png", + "screenshot.jpg", + "screenshot.jpeg", + "screenshot.webp", + "screenshot.gif", + "image.png", + "image.jpg", + "image.jpeg", + "image.webp", + "image.gif", + ]); - for (const candidate of preferredNames) { - const candidatePath = path.join(assetDir, candidate); - if (fs.existsSync(candidatePath)) { - const assetPath = `${relPath}/assets/${candidate}`; - return { - assetPath, - imageUrl: buildRepoImageUrl(assetPath, ref), - }; - } - } - - const files = fs - .readdirSync(assetDir) - .filter((file) => imageExtensions.has(path.extname(file).toLowerCase())) - .sort((a, b) => a.localeCompare(b)); - - if (files.length === 0) { - return null; - } - - const assetFile = files[0]; - const assetPath = `${relPath}/assets/${assetFile}`; + const iconFile = iconAsset || galleryAsset; + const galleryFile = galleryAsset || iconAsset; + const iconPath = iconFile ? `${relPath}/assets/${iconFile}` : null; + const galleryPath = galleryFile ? `${relPath}/assets/${galleryFile}` : null; return { - assetPath, - imageUrl: buildRepoImageUrl(assetPath, ref), + screenshots: { + icon: iconPath + ? { + path: iconPath, + type: getImageMimeType(iconPath), + } + : null, + gallery: galleryPath + ? { + path: galleryPath, + type: getImageMimeType(galleryPath), + } + : null, + }, + assetPath: iconPath, + imageUrl: iconPath ? buildRepoImageUrl(iconPath, ref) : null, }; } @@ -744,11 +812,174 @@ function buildRepoImageUrl(assetPath, ref) { return `https://raw.githubusercontent.com/github/awesome-copilot/${ref}/${encodedAssetPath}`; } -function generateExtensionsData(gitDates, commitSha) { - const extensions = []; +function extractCanvasMetadataFromSource(source) { + const constants = new Map(); + const constantPattern = + /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|`([^`$]*)`)\s*;/g; + let constantMatch = constantPattern.exec(source); + while (constantMatch) { + const key = constantMatch[1]; + const value = constantMatch[2] ?? constantMatch[3] ?? constantMatch[4] ?? ""; + constants.set(key, value.replace(/\\n/g, "\n").trim()); + constantMatch = constantPattern.exec(source); + } + + function resolveExpression(expr) { + const trimmed = normalizeText(expr); + if (!trimmed) return null; + if ( + (trimmed.startsWith('"') && trimmed.endsWith('"')) || + (trimmed.startsWith("'") && trimmed.endsWith("'")) + ) { + return trimmed + .slice(1, -1) + .replace(/\\n/g, "\n") + .replace(/\\"/g, '"') + .replace(/\\'/g, "'"); + } + if (trimmed.startsWith("`") && trimmed.endsWith("`") && !trimmed.includes("${")) { + return trimmed.slice(1, -1); + } + return constants.get(trimmed) || null; + } + + function findMatchingBrace(startIndex) { + let depth = 0; + let inSingle = false; + let inDouble = false; + let inTemplate = false; + let escaped = false; + for (let i = startIndex; i < source.length; i++) { + const char = source[i]; + if (escaped) { + escaped = false; + continue; + } + if (char === "\\") { + escaped = true; + continue; + } + if (!inDouble && !inTemplate && char === "'" && !inSingle) { + inSingle = true; + continue; + } + if (inSingle && char === "'") { + inSingle = false; + continue; + } + if (!inSingle && !inTemplate && char === '"' && !inDouble) { + inDouble = true; + continue; + } + if (inDouble && char === '"') { + inDouble = false; + continue; + } + if (!inSingle && !inDouble && char === "`" && !inTemplate) { + inTemplate = true; + continue; + } + if (inTemplate && char === "`") { + inTemplate = false; + continue; + } + if (inSingle || inDouble || inTemplate) { + continue; + } + if (char === "{") depth++; + if (char === "}") { + depth--; + if (depth === 0) return i; + } + } + return -1; + } + + function readProp(head, key) { + const pattern = new RegExp(`\\b${key}\\s*:\\s*([^,\\n]+)`); + const match = pattern.exec(head); + return resolveExpression(match?.[1]); + } + + const canvases = []; + let cursor = 0; + while (cursor < source.length) { + const createCanvasIndex = source.indexOf("createCanvas(", cursor); + if (createCanvasIndex === -1) { + break; + } + const objectStart = source.indexOf("{", createCanvasIndex); + if (objectStart === -1) { + break; + } + const objectEnd = findMatchingBrace(objectStart); + if (objectEnd === -1) { + break; + } + const objectContent = source.slice(objectStart + 1, objectEnd); + const header = objectContent.slice(0, 1400); + const id = readProp(header, "id"); + const displayName = readProp(header, "displayName"); + const description = readProp(header, "description"); + if (id || displayName || description) { + canvases.push({ + id: id || null, + displayName: displayName || null, + description: description || null, + }); + } + cursor = objectEnd + 1; + } + + return canvases; +} + +function getExtensionCanvasFiles(extensionDir) { + const queue = [extensionDir]; + const files = []; + while (queue.length > 0) { + const currentDir = queue.shift(); + const entries = fs.readdirSync(currentDir, { withFileTypes: true }); + for (const entry of entries) { + const absolutePath = path.join(currentDir, entry.name); + if (entry.isDirectory()) { + queue.push(absolutePath); + } else if (entry.isFile() && entry.name.endsWith(".mjs")) { + files.push(absolutePath); + } + } + } + return files.sort((a, b) => a.localeCompare(b)); +} + +function normalizeExternalScreenshotRole(value, ref) { + if (!value) return null; + if (typeof value === "string") { + const type = getImageMimeType(value); + return { + path: value.replace(/\\/g, "/"), + type, + imageUrl: resolveImageUrl(value, ref), + }; + } + const pathValue = normalizeText(value.path); + const urlValue = normalizeText(value.url); + if (!pathValue && !urlValue) return null; + const imagePath = pathValue ? pathValue.replace(/\\/g, "/") : null; + const type = normalizeText(value.type) || getImageMimeType(imagePath || urlValue); + const imageUrl = resolveImageUrl(urlValue || imagePath, ref); + return { + path: imagePath, + type, + imageUrl, + }; +} + +function generateCanvasManifest(gitDates, commitSha) { + const items = []; if (!fs.existsSync(EXTENSIONS_DIR)) { - return { items: [] }; + return { items: [], filters: { keywords: [] } }; } const extensionDirs = fs @@ -761,32 +992,61 @@ function generateExtensionsData(gitDates, commitSha) { "extension.mjs" ); return fs.existsSync(extensionEntryPoint); - }); + }) + .sort((a, b) => a.name.localeCompare(b.name)); for (const dir of extensionDirs) { const relPath = `extensions/${dir.name}`; - const assetInfo = getExtensionAssetInfo( - path.join(EXTENSIONS_DIR, dir.name), - relPath, - commitSha - ); + const extensionDir = path.join(EXTENSIONS_DIR, dir.name); + const packageJsonPath = path.join(extensionDir, "package.json"); + const packageJson = fs.existsSync(packageJsonPath) + ? JSON.parse(fs.readFileSync(packageJsonPath, "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)) + : []; + const extensionDescription = normalizeText(packageJson.description, "Canvas extension"); + const extensionName = normalizeText(packageJson.name, dir.name); + const extensionVersion = normalizeText(packageJson.version, "1.0.0"); + const screenshots = getExtensionAssetInfo(extensionDir, relPath, commitSha); + const canvasFiles = getExtensionCanvasFiles(extensionDir); + const canvases = []; + for (const canvasFile of canvasFiles) { + const source = fs.readFileSync(canvasFile, "utf-8"); + canvases.push(...extractCanvasMetadataFromSource(source)); + } + const canvasEntries = canvases.length > 0 + ? canvases + : [{ id: dir.name, displayName: formatDisplayName(dir.name), description: extensionDescription }]; + const installUrl = `https://github.com/github/awesome-copilot/tree/${commitSha}/${relPath.replace( + /\\/g, + "/" + )}`; - extensions.push({ - id: dir.name, - name: formatDisplayName(dir.name), - description: "Canvas extension", - path: relPath, - ref: commitSha, - lastUpdated: getDirectoryLastUpdated(gitDates, relPath), - imageUrl: assetInfo?.imageUrl || null, - assetPath: assetInfo?.assetPath || null, - installUrl: `https://github.com/github/awesome-copilot/tree/${commitSha}/${relPath.replace( - /\\/g, - "/" - )}`, - sourceUrl: null, - external: false, - }); + for (const canvas of canvasEntries) { + const canvasId = normalizeText(canvas.id, dir.name); + const canvasName = normalizeText(canvas.displayName, formatDisplayName(canvasId)); + const canvasDescription = normalizeText(extensionDescription, canvas.description); + items.push({ + id: canvasId, + canvasId, + extensionId: dir.name, + extensionName, + name: canvasName, + version: extensionVersion, + description: canvasDescription, + path: relPath, + ref: commitSha, + lastUpdated: getDirectoryLastUpdated(gitDates, relPath), + screenshots: screenshots?.screenshots || { icon: null, gallery: null }, + imageUrl: screenshots?.imageUrl || null, + assetPath: screenshots?.assetPath || null, + installUrl, + sourceUrl: null, + external: false, + keywords, + }); + } } const externalJsonPath = path.join(EXTENSIONS_DIR, "external.json"); @@ -805,27 +1065,58 @@ function generateExtensionsData(gitDates, commitSha) { } const id = normalizeText(ext?.id || name.toLowerCase().replace(/\s+/g, "-")); - let imageUrl = normalizeText(ext?.imageUrl); - let assetPath = null; - const imagePath = normalizeText(ext?.imagePath); - if (!imageUrl && imagePath) { - const repoAssetPath = imagePath.replace(/\\/g, "/"); - imageUrl = buildRepoImageUrl(repoAssetPath, commitSha); - assetPath = repoAssetPath; - } + const keywords = Array.isArray(ext?.keywords) + ? [...new Set(ext.keywords.filter((keyword) => typeof keyword === "string").map((keyword) => keyword.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b)) + : Array.isArray(ext?.tags) + ? [...new Set(ext.tags.filter((keyword) => typeof keyword === "string").map((keyword) => keyword.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b)) + : []; + const iconScreenshot = + normalizeExternalScreenshotRole(ext?.screenshots?.icon, commitSha) || + normalizeExternalScreenshotRole(ext?.iconPath, commitSha) || + normalizeExternalScreenshotRole(ext?.imagePath, commitSha) || + normalizeExternalScreenshotRole(ext?.iconUrl, commitSha) || + normalizeExternalScreenshotRole(ext?.imageUrl, commitSha); + const galleryScreenshot = + normalizeExternalScreenshotRole(ext?.screenshots?.gallery, commitSha) || + normalizeExternalScreenshotRole(ext?.galleryPath, commitSha) || + normalizeExternalScreenshotRole(ext?.galleryUrl, commitSha) || + iconScreenshot; + const screenshots = { + icon: iconScreenshot + ? { + path: iconScreenshot.path, + type: iconScreenshot.type, + } + : null, + gallery: galleryScreenshot + ? { + path: galleryScreenshot.path, + type: galleryScreenshot.type, + } + : null, + }; + const imageUrl = iconScreenshot?.imageUrl || null; + const assetPath = iconScreenshot?.path || null; + const canvasId = normalizeText(ext?.canvasId, id); - extensions.push({ + items.push({ id, + canvasId, + extensionId: id, + extensionName: name, name, + version: normalizeText(ext?.version, "1.0.0"), description: normalizeText(ext?.description, "External canvas extension"), path: null, ref: null, lastUpdated: null, - imageUrl: imageUrl || null, + screenshots, + imageUrl, assetPath, installUrl, sourceUrl: sourceUrl || null, external: true, + keywords, }); } } @@ -834,11 +1125,98 @@ function generateExtensionsData(gitDates, commitSha) { } } - const sortedExtensions = extensions.sort((a, b) => - a.name.localeCompare(b.name) - ); + const sortedItems = items.sort((a, b) => a.name.localeCompare(b.name)); + const keywordFilters = [...new Set(sortedItems.flatMap((item) => item.keywords || []))] + .filter(Boolean) + .sort((a, b) => a.localeCompare(b)); - return { items: sortedExtensions }; + return { + items: sortedItems, + filters: { + keywords: keywordFilters, + }, + }; +} + +function generateExtensionsData(canvasManifestData) { + if (!canvasManifestData || !Array.isArray(canvasManifestData.items)) { + return { items: [], filters: { keywords: [] } }; + } + + const items = canvasManifestData.items.map((item) => ({ + ...item, + keywords: Array.isArray(item.keywords) ? item.keywords : [], + screenshots: item.screenshots || { icon: null, gallery: null }, + })); + const filters = { + keywords: [...new Set(items.flatMap((item) => item.keywords))] + .filter(Boolean) + .sort((a, b) => a.localeCompare(b)), + }; + + return { items, filters }; +} + +function writePerExtensionCanvasManifests(canvasManifestData) { + const manifests = new Map(); + + function toExtensionRelativePath(assetPath, extensionId) { + const normalizedPath = normalizeText(assetPath).replace(/\\/g, "/"); + if (!normalizedPath) return null; + const prefix = `extensions/${extensionId}/`; + return normalizedPath.startsWith(prefix) + ? normalizedPath.slice(prefix.length) + : normalizedPath; + } + + function toRelativeScreenshots(screenshots, extensionId) { + if (!screenshots) return { icon: null, gallery: null }; + const toRelativeEntry = (entry) => + entry + ? { + ...entry, + path: toExtensionRelativePath(entry.path, extensionId), + } + : null; + return { + icon: toRelativeEntry(screenshots.icon), + gallery: toRelativeEntry(screenshots.gallery), + }; + } + + for (const item of canvasManifestData.items || []) { + if (!item || item.external || !item.extensionId || !item.path) { + continue; + } + + // We assume one canvas per extension folder. + if (manifests.has(item.extensionId)) { + continue; + } + + manifests.set(item.extensionId, { + id: item.canvasId || item.id, + name: item.name, + description: item.description || "Canvas extension", + version: item.version || "1.0.0", + keywords: Array.isArray(item.keywords) + ? [...new Set(item.keywords)].sort((a, b) => a.localeCompare(b)) + : [], + screenshots: toRelativeScreenshots( + item.screenshots || { icon: null, gallery: null }, + item.extensionId + ), + }); + } + + for (const [extensionId, manifest] of manifests.entries()) { + const canvasManifestPath = path.join( + EXTENSIONS_DIR, + extensionId, + "canvas.json" + ); + fs.writeFileSync(canvasManifestPath, JSON.stringify(manifest, null, 2)); + } } /** @@ -1181,9 +1559,12 @@ async function main() { `✓ Generated ${plugins.length} plugins (${pluginsData.filters.tags.length} tags)` ); - const extensionsData = generateExtensionsData(gitDates, commitSha); + const canvasManifestData = generateCanvasManifest(gitDates, commitSha); + const extensionsData = generateExtensionsData(canvasManifestData); const extensions = extensionsData.items; - console.log(`✓ Generated ${extensions.length} extensions`); + console.log( + `✓ Generated ${extensions.length} extensions (${extensionsData.filters.keywords.length} keywords)` + ); const toolsData = generateToolsData(); const tools = toolsData.items; @@ -1248,6 +1629,8 @@ async function main() { JSON.stringify(extensionsData, null, 2) ); + writePerExtensionCanvasManifests(canvasManifestData); + fs.writeFileSync( path.join(WEBSITE_DATA_DIR, "tools.json"), JSON.stringify(toolsData, null, 2) diff --git a/extensions/accessibility-kanban/canvas.json b/extensions/accessibility-kanban/canvas.json new file mode 100644 index 00000000..6bc4538e --- /dev/null +++ b/extensions/accessibility-kanban/canvas.json @@ -0,0 +1,24 @@ +{ + "id": "accessibility-kanban", + "name": "Accessibility Kanban", + "description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.", + "version": "1.0.0", + "keywords": [ + "accessibility", + "github-issues", + "issue-triage", + "kanban-board", + "planning-workflow", + "status-tracking" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} diff --git a/extensions/accessibility-kanban/package.json b/extensions/accessibility-kanban/package.json index 8015543b..48b33dbd 100644 --- a/extensions/accessibility-kanban/package.json +++ b/extensions/accessibility-kanban/package.json @@ -5,5 +5,14 @@ "main": "extension.mjs", "dependencies": { "@github/copilot-sdk": "latest" - } + }, + "description": "Users drag accessibility issues across kanban lanes to plan, track, and complete remediation work.", + "keywords": [ + "accessibility", + "kanban-board", + "issue-triage", + "planning-workflow", + "status-tracking", + "github-issues" + ] } diff --git a/extensions/backlog-swipe-triage/canvas.json b/extensions/backlog-swipe-triage/canvas.json new file mode 100644 index 00000000..b4b5b350 --- /dev/null +++ b/extensions/backlog-swipe-triage/canvas.json @@ -0,0 +1,24 @@ +{ + "id": "backlog-swipe-triage", + "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", + "keywords": [ + "agent-assignment", + "backlog-triage", + "github-issues", + "issue-prioritization", + "swipe-interface", + "workflow-automation" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} diff --git a/extensions/backlog-swipe-triage/package.json b/extensions/backlog-swipe-triage/package.json index 15a0677a..34f71265 100644 --- a/extensions/backlog-swipe-triage/package.json +++ b/extensions/backlog-swipe-triage/package.json @@ -3,8 +3,16 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", - "description": "Swipe-driven backlog triage canvas for reviewing open issues and assigning implementation work.", "dependencies": { "@github/copilot-sdk": "1.0.1" - } + }, + "description": "Users quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.", + "keywords": [ + "backlog-triage", + "swipe-interface", + "issue-prioritization", + "github-issues", + "agent-assignment", + "workflow-automation" + ] } diff --git a/extensions/chromium-control-canvas/canvas.json b/extensions/chromium-control-canvas/canvas.json new file mode 100644 index 00000000..79ff4cf6 --- /dev/null +++ b/extensions/chromium-control-canvas/canvas.json @@ -0,0 +1,25 @@ +{ + "id": "chromium-control-canvas", + "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", + "keywords": [ + "browser-control", + "chromium-browser", + "interactive-canvas", + "playwright-automation", + "screenshots", + "ui-testing", + "web-navigation" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} \ No newline at end of file diff --git a/extensions/chromium-control-canvas/extension.mjs b/extensions/chromium-control-canvas/extension.mjs index f0ccabfe..cc7e97da 100644 --- a/extensions/chromium-control-canvas/extension.mjs +++ b/extensions/chromium-control-canvas/extension.mjs @@ -561,7 +561,7 @@ const session = await joinSession({ id: "chromium-control-canvas", displayName: "Chromium Control Canvas", description: - "Control canvas for a real headful Chromium window driven by Playwright.", + "Opens a real Chromium window you can navigate and interact with from a Copilot canvas control panel and agent actions.", inputSchema: { type: "object", properties: { diff --git a/extensions/chromium-control-canvas/package.json b/extensions/chromium-control-canvas/package.json index f52d5aa6..5b6197b2 100644 --- a/extensions/chromium-control-canvas/package.json +++ b/extensions/chromium-control-canvas/package.json @@ -1,14 +1,22 @@ { "name": "chromium-control-canvas", "version": "1.0.0", - "description": "GitHub Copilot canvas that drives a real headful Chromium window via Playwright.", "main": "extension.mjs", - "keywords": [], "author": "Andrea Griffiths", "license": "MIT", "type": "module", "dependencies": { "@github/copilot-sdk": "latest", "playwright": "^1.60.0" - } + }, + "description": "Opens a real Chromium window you can navigate and interact with from a Copilot canvas control panel and agent actions.", + "keywords": [ + "chromium-browser", + "playwright-automation", + "browser-control", + "interactive-canvas", + "web-navigation", + "screenshots", + "ui-testing" + ] } diff --git a/extensions/color-orb/canvas.json b/extensions/color-orb/canvas.json new file mode 100644 index 00000000..47a1a32b --- /dev/null +++ b/extensions/color-orb/canvas.json @@ -0,0 +1,24 @@ +{ + "id": "color-orb", + "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", + "keywords": [ + "agent-actions", + "color-picker", + "interactive-demo", + "realtime-updates", + "sse-events", + "visual-feedback" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} diff --git a/extensions/color-orb/package.json b/extensions/color-orb/package.json index d3b32848..a28cd5c3 100644 --- a/extensions/color-orb/package.json +++ b/extensions/color-orb/package.json @@ -5,5 +5,14 @@ "main": "extension.mjs", "dependencies": { "@github/copilot-sdk": "latest" - } + }, + "description": "Gives users a visual orb they can ask the agent to recolor while showing a live activity log in the canvas.", + "keywords": [ + "color-picker", + "interactive-demo", + "agent-actions", + "realtime-updates", + "sse-events", + "visual-feedback" + ] } diff --git a/extensions/diagram-viewer/canvas.json b/extensions/diagram-viewer/canvas.json new file mode 100644 index 00000000..0fe72fde --- /dev/null +++ b/extensions/diagram-viewer/canvas.json @@ -0,0 +1,24 @@ +{ + "id": "diagram", + "name": "Diagram Explorer", + "description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.", + "version": "1.0.0", + "keywords": [ + "architecture-mapping", + "canvas-navigation", + "exploratory-analysis", + "interactive-diagrams", + "node-drilldown", + "relationship-visualization" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} diff --git a/extensions/diagram-viewer/package.json b/extensions/diagram-viewer/package.json index c5124d57..054ebcf7 100644 --- a/extensions/diagram-viewer/package.json +++ b/extensions/diagram-viewer/package.json @@ -5,5 +5,14 @@ "main": "extension.mjs", "dependencies": { "@github/copilot-sdk": "latest" - } + }, + "description": "Lets users render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.", + "keywords": [ + "interactive-diagrams", + "architecture-mapping", + "node-drilldown", + "relationship-visualization", + "exploratory-analysis", + "canvas-navigation" + ] } diff --git a/extensions/external.json b/extensions/external.json index 5ce10ab6..c6b98bcc 100644 --- a/extensions/external.json +++ b/extensions/external.json @@ -3,6 +3,21 @@ "id": "coffilot", "name": "Coffilot", "description": "Java-focused Copilot canvas extension from jdubois.", + "keywords": [ + "java", + "canvas", + "productivity" + ], + "screenshots": { + "icon": { + "path": "extensions/external-assets/coffilot-preview.png", + "type": "image/png" + }, + "gallery": { + "path": "extensions/external-assets/coffilot-preview.png", + "type": "image/png" + } + }, "installUrl": "https://github.com/jdubois/coffilot", "sourceUrl": "https://github.com/jdubois/coffilot", "imagePath": "extensions/external-assets/coffilot-preview.png" diff --git a/extensions/feedback-themes/canvas.json b/extensions/feedback-themes/canvas.json new file mode 100644 index 00000000..01a269be --- /dev/null +++ b/extensions/feedback-themes/canvas.json @@ -0,0 +1,24 @@ +{ + "id": "feedback-themes", + "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", + "keywords": [ + "customer-feedback", + "impact-prioritization", + "product-insights", + "signal-grouping", + "theme-analysis", + "trend-discovery" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} \ No newline at end of file diff --git a/extensions/feedback-themes/package.json b/extensions/feedback-themes/package.json index 778b9a58..f769acee 100644 --- a/extensions/feedback-themes/package.json +++ b/extensions/feedback-themes/package.json @@ -5,5 +5,14 @@ "main": "extension.mjs", "dependencies": { "@github/copilot-sdk": "latest" - } + }, + "description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.", + "keywords": [ + "customer-feedback", + "theme-analysis", + "signal-grouping", + "impact-prioritization", + "product-insights", + "trend-discovery" + ] } diff --git a/extensions/gesture-review/canvas.json b/extensions/gesture-review/canvas.json new file mode 100644 index 00000000..c05ecddf --- /dev/null +++ b/extensions/gesture-review/canvas.json @@ -0,0 +1,24 @@ +{ + "id": "gesture-review", + "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", + "keywords": [ + "camera-input", + "gesture-control", + "github-prs", + "hands-free", + "mediapipe", + "pull-request-review" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} diff --git a/extensions/gesture-review/extension.mjs b/extensions/gesture-review/extension.mjs index 94eae7ff..6ec5518a 100644 --- a/extensions/gesture-review/extension.mjs +++ b/extensions/gesture-review/extension.mjs @@ -179,7 +179,7 @@ const canvas = createCanvas({ id: "gesture-review", displayName: "Gesture PR Review", description: - "Interactive PR review using hand gestures. Shows a live camera feed and detects thumbs up (approve) or thumbs down (reject) via MediaPipe hand tracking.", + "Users review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.", actions: [ { name: "show_pr", diff --git a/extensions/gesture-review/package.json b/extensions/gesture-review/package.json index 4e23e484..1f5a911f 100644 --- a/extensions/gesture-review/package.json +++ b/extensions/gesture-review/package.json @@ -5,5 +5,14 @@ "main": "extension.mjs", "dependencies": { "@github/copilot-sdk": "latest" - } + }, + "description": "Users review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.", + "keywords": [ + "pull-request-review", + "gesture-control", + "camera-input", + "hands-free", + "github-prs", + "mediapipe" + ] } diff --git a/extensions/release-notes-showcase/canvas.json b/extensions/release-notes-showcase/canvas.json new file mode 100644 index 00000000..a21dbbb2 --- /dev/null +++ b/extensions/release-notes-showcase/canvas.json @@ -0,0 +1,24 @@ +{ + "id": "release-notes-showcase", + "name": "Release Notes Showcase", + "description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.", + "version": "1.0.0", + "keywords": [ + "changelog", + "contributor-callouts", + "email-export", + "launch-summary", + "product-updates", + "release-notes" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} \ No newline at end of file diff --git a/extensions/release-notes-showcase/package.json b/extensions/release-notes-showcase/package.json index 0bb276cf..cb192cd1 100644 --- a/extensions/release-notes-showcase/package.json +++ b/extensions/release-notes-showcase/package.json @@ -3,8 +3,16 @@ "version": "1.0.0", "type": "module", "main": "extension.mjs", - "description": "Release notes canvas for building polished release communications and exports.", "dependencies": { "@github/copilot-sdk": "1.0.1" - } + }, + "description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.", + "keywords": [ + "release-notes", + "launch-summary", + "changelog", + "contributor-callouts", + "product-updates", + "email-export" + ] } diff --git a/extensions/release-notes-showcase/releaseNotesShowcase.mjs b/extensions/release-notes-showcase/releaseNotesShowcase.mjs index d4a7fa28..5847f624 100644 --- a/extensions/release-notes-showcase/releaseNotesShowcase.mjs +++ b/extensions/release-notes-showcase/releaseNotesShowcase.mjs @@ -621,7 +621,7 @@ export const releaseNotesShowcaseCanvas = createCanvas({ id: CANVAS_ID, displayName: CANVAS_TITLE, description: - "Presents release notes as a high-impact launch summary with contributor callouts and email-ready export output.", + "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.", inputSchema: releaseNotesInputSchema, actions: [ { diff --git a/extensions/where-was-i/canvas.json b/extensions/where-was-i/canvas.json new file mode 100644 index 00000000..2909dfb2 --- /dev/null +++ b/extensions/where-was-i/canvas.json @@ -0,0 +1,24 @@ +{ + "id": "where-was-i", + "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", + "keywords": [ + "branch-state", + "developer-context", + "git-history", + "interrupt-recovery", + "pull-request-context", + "resume-work" + ], + "screenshots": { + "icon": { + "path": "assets/preview.png", + "type": "image/png" + }, + "gallery": { + "path": "assets/preview.png", + "type": "image/png" + } + } +} \ No newline at end of file diff --git a/extensions/where-was-i/extension.mjs b/extensions/where-was-i/extension.mjs index 66e6da89..0d6b7be7 100644 --- a/extensions/where-was-i/extension.mjs +++ b/extensions/where-was-i/extension.mjs @@ -666,7 +666,7 @@ const session = await joinSession({ createCanvas({ id: "where-was-i", displayName: "Where Was I?", - description: "Interrupt Recovery — reconstructs your working context (branch, commits, changes, PRs) so you can resume after being pulled away.", + description: "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.", actions: [ { name: "refresh", diff --git a/extensions/where-was-i/package.json b/extensions/where-was-i/package.json new file mode 100644 index 00000000..5534b4db --- /dev/null +++ b/extensions/where-was-i/package.json @@ -0,0 +1,18 @@ +{ + "name": "where-was-i", + "version": "1.0.0", + "type": "module", + "main": "extension.mjs", + "description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.", + "keywords": [ + "interrupt-recovery", + "developer-context", + "git-history", + "branch-state", + "resume-work", + "pull-request-context" + ], + "dependencies": { + "@github/copilot-sdk": "latest" + } +} diff --git a/website/src/pages/extensions.astro b/website/src/pages/extensions.astro index 78ae6f27..a0955c44 100644 --- a/website/src/pages/extensions.astro +++ b/website/src/pages/extensions.astro @@ -20,9 +20,13 @@ const initialItems = sortExtensions(extensionsData.items, 'title');