From b25823ace2ea32589d5255f4a6de3e10aefd6528 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 9 Jun 2026 05:54:09 +0000
Subject: [PATCH] chore: publish from staged
---
.../external-plugins-board/extension.mjs | 580 ++++++++++++++++++
.../external-plugins-board/package-lock.json | 27 +
.../external-plugins-board/package.json | 8 +
3 files changed, 615 insertions(+)
create mode 100644 .github/extensions/external-plugins-board/extension.mjs
create mode 100644 .github/extensions/external-plugins-board/package-lock.json
create mode 100644 .github/extensions/external-plugins-board/package.json
diff --git a/.github/extensions/external-plugins-board/extension.mjs b/.github/extensions/external-plugins-board/extension.mjs
new file mode 100644
index 00000000..1896ec03
--- /dev/null
+++ b/.github/extensions/external-plugins-board/extension.mjs
@@ -0,0 +1,580 @@
+import { createServer } from "node:http";
+import { execFileSync, spawnSync, execSync } from "node:child_process";
+import { dirname } from "node:path";
+import { createRequire } from "node:module";
+import { joinSession, createCanvas } from "@github/copilot-sdk/extension";
+
+const require = createRequire(import.meta.url);
+const { marked } = require("marked");
+
+const servers = new Map();
+let workspacePath = null;
+let lastError = null;
+
+// Fetch live issues from GitHub REST API instead of gh CLI subprocess
+async function fetchLiveIssues(cwd) {
+ try {
+ // Use GitHub REST API to fetch issues
+ // This avoids the subprocess execution restriction
+ const owner = "github";
+ const repo = "awesome-copilot";
+ const label = "external-plugin";
+
+ // Get authentication token from environment or use public access
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
+
+ const headers = {
+ "Accept": "application/vnd.github.v3+json"
+ };
+
+ if (token) {
+ headers["Authorization"] = `token ${token}`;
+ }
+
+ // Fetch issues with external-plugin label
+ const response = await fetch(
+ `https://api.github.com/repos/${owner}/${repo}/issues?labels=${label}&state=open&per_page=100`,
+ { headers }
+ );
+
+ if (!response.ok) {
+ const error = await response.text();
+ throw new Error(`GitHub API error ${response.status}: ${error.substring(0, 200)}`);
+ }
+
+ const issues = await response.json();
+
+ // Filter to only external-plugin labeled issues and map to our format
+ return issues
+ .filter(issue => issue.labels && issue.labels.some(l => l.name === label))
+ .map(issue => ({
+ number: issue.number,
+ title: issue.title,
+ body: issue.body || "",
+ bodyHtml: marked.parse(issue.body || ""),
+ labels: (issue.labels || []).map(l => ({ name: l.name })),
+ pr_url: issue.body?.match(/\[Generated PR\]\(([^)]+)\)/)?.[1],
+ created_at: issue.created_at,
+ updated_at: issue.updated_at
+ }));
+ } catch (err) {
+ lastError = err.message;
+ throw err;
+ }
+}
+
+function renderHtml() {
+ return `
+
+
+
+ External Plugins Board
+
+
+
+ External Plugins Board
+
+
+
+
+
+
+`;
+}
+
+async function startServer(instanceId, cwd) {
+ const server = createServer(async (req, res) => {
+ res.setHeader("Access-Control-Allow-Origin", "*");
+
+ if (req.url === "/" && req.method === "GET") {
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
+ res.end(renderHtml());
+ } else if (req.url === "/api/issues" && req.method === "GET") {
+ try {
+ const issues = await fetchLiveIssues(cwd);
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify(issues || []));
+ } catch (err) {
+ res.writeHead(500, { "Content-Type": "application/json" });
+ res.end(JSON.stringify({ error: err.message }));
+ }
+ } else if (req.url === "/api/issues/update" && req.method === "POST") {
+ let body = "";
+ req.on("data", chunk => { body += chunk; });
+ req.on("end", async () => {
+ try {
+ const { issueNumber, newState } = JSON.parse(body);
+ const labels = ['requires-submitter-fixes', 'ready-for-review', 'approved', 'rejected'];
+ for (const label of labels.filter(l => l !== newState)) {
+ try {
+ spawnSync("gh", [
+ "issue", "edit", issueNumber.toString(),
+ "--remove-label", label
+ ], { cwd, shell: true });
+ } catch (e) {}
+ }
+ spawnSync("gh", [
+ "issue", "edit", issueNumber.toString(),
+ "--add-label", newState
+ ], { cwd, shell: true });
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify({ ok: true }));
+ } catch (err) {
+ res.writeHead(500, { "Content-Type": "application/json" });
+ res.end(JSON.stringify({ error: err.message }));
+ }
+ });
+ } else {
+ res.writeHead(404);
+ res.end("Not found");
+ }
+ });
+
+ await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
+ const port = server.address().port;
+ return { server, url: `http://127.0.0.1:${port}/` };
+}
+
+const session = await joinSession({
+ canvases: [
+ createCanvas({
+ id: "external-plugins-board",
+ displayName: "External Plugins Board",
+ description: "Kanban board for managing external plugin submission issues",
+ open: async (ctx) => {
+ let entry = servers.get(ctx.instanceId);
+ if (!entry) {
+ if (!workspacePath) {
+ const filePath = import.meta.url.replace(/^file:\/\//, '').replace(/\//g, '\\');
+ workspacePath = dirname(dirname(dirname(filePath)));
+ }
+ entry = await startServer(ctx.instanceId, workspacePath);
+ servers.set(ctx.instanceId, entry);
+ }
+ return { title: "External Plugins Board", url: entry.url };
+ },
+ onClose: async (ctx) => {
+ const entry = servers.get(ctx.instanceId);
+ if (entry) {
+ servers.delete(ctx.instanceId);
+ await new Promise(resolve => entry.server.close(() => resolve()));
+ }
+ },
+ }),
+ ],
+});
diff --git a/.github/extensions/external-plugins-board/package-lock.json b/.github/extensions/external-plugins-board/package-lock.json
new file mode 100644
index 00000000..749f14a6
--- /dev/null
+++ b/.github/extensions/external-plugins-board/package-lock.json
@@ -0,0 +1,27 @@
+{
+ "name": "external-plugins-board",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "external-plugins-board",
+ "version": "1.0.0",
+ "dependencies": {
+ "marked": "^15.0.0"
+ }
+ },
+ "node_modules/marked": {
+ "version": "15.0.12",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
+ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ }
+ }
+}
diff --git a/.github/extensions/external-plugins-board/package.json b/.github/extensions/external-plugins-board/package.json
new file mode 100644
index 00000000..495cf54f
--- /dev/null
+++ b/.github/extensions/external-plugins-board/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "external-plugins-board",
+ "version": "1.0.0",
+ "type": "module",
+ "dependencies": {
+ "marked": "^15.0.0"
+ }
+}