From 2f2b39277094e40eed3c46498c30968d00d1b272 Mon Sep 17 00:00:00 2001 From: Edward Frazer Date: Mon, 11 May 2026 18:03:06 -0700 Subject: [PATCH 01/10] docs(plugin-creator): simplify deeplink shape --- .agents/skills/plugin-creator/SKILL.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.agents/skills/plugin-creator/SKILL.md b/.agents/skills/plugin-creator/SKILL.md index 5c9cb206..7f3bc60a 100644 --- a/.agents/skills/plugin-creator/SKILL.md +++ b/.agents/skills/plugin-creator/SKILL.md @@ -152,11 +152,14 @@ python3 .agents/skills/plugin-creator/scripts/create_basic_plugin.py my-plugin \ - Keep marketplace `source.path` relative to the selected marketplace root as `./plugins/`. - When the workflow created or updated a marketplace-backed plugin, end the final user-facing response with these two Markdown links, in this order, and do not add any text after them: - - `[View ](codex://plugins/view-local?marketplacePath=&pluginName=)` - - `[Share ](codex://plugins/share-local?marketplacePath=&pluginName=)` -- Replace the link-label placeholder and both deeplink placeholders with the real normalized plugin - name plus the real absolute marketplace path from the scaffolded plugin. URL-encode query - parameter values when needed. + - `[View ](codex://plugins/?marketplacePath=)` + - `[Share ](codex://plugins/?marketplacePath=&mode=share)` +- Replace the normalized plugin-name placeholder in both the Markdown label and the deeplink path + segment with the real normalized plugin name. Replace the marketplace path placeholder with the + real absolute `marketplace.json` path from the scaffolded plugin. URL-encode the path segment and + query value when needed. +- Do not add `pluginName` or `hostId` query parameters to these deeplinks. Codex derives both after + the user clicks the link. - Do not emit the `View ` or `Share ` links when no marketplace entry was created or updated. From e6c6fa7f590f4ce37f17153c900432f027ffbd67 Mon Sep 17 00:00:00 2001 From: Edward Frazer Date: Mon, 11 May 2026 18:12:51 -0700 Subject: [PATCH 02/10] docs(plugin-creator): clarify app-only deeplinks --- .agents/skills/plugin-creator/SKILL.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.agents/skills/plugin-creator/SKILL.md b/.agents/skills/plugin-creator/SKILL.md index 7f3bc60a..44dedb45 100644 --- a/.agents/skills/plugin-creator/SKILL.md +++ b/.agents/skills/plugin-creator/SKILL.md @@ -151,7 +151,8 @@ python3 .agents/skills/plugin-creator/scripts/create_basic_plugin.py my-plugin \ - Add `policy.products` only when the user explicitly asks for that override. - Keep marketplace `source.path` relative to the selected marketplace root as `./plugins/`. - When the workflow created or updated a marketplace-backed plugin, end the final user-facing - response with these two Markdown links, in this order, and do not add any text after them: + response with this Codex app handoff block, in this order, and do not add any text after the links. + First write the standalone line `To view this in the Codex app:`. Then write: - `[View ](codex://plugins/?marketplacePath=)` - `[Share ](codex://plugins/?marketplacePath=&mode=share)` - Replace the normalized plugin-name placeholder in both the Markdown label and the deeplink path From 405b32445338a125a22e1d369aeec6130fef6625 Mon Sep 17 00:00:00 2001 From: Edward Frazer Date: Tue, 12 May 2026 13:44:55 -0700 Subject: [PATCH 03/10] feat(plugin-builder): add plugin creation MCP app --- .agents/plugins/marketplace.json | 12 + .../plugin-builder/.codex-plugin/plugin.json | 30 ++ plugins/plugin-builder/.mcp.json | 11 + plugins/plugin-builder/app/app.js | 348 ++++++++++++++ plugins/plugin-builder/app/index.html | 64 +++ plugins/plugin-builder/app/styles.css | 342 ++++++++++++++ plugins/plugin-builder/mcp/server.js | 443 ++++++++++++++++++ .../skills/plugin-creator/SKILL.md | 179 +++++++ .../skills/plugin-creator/agents/openai.yaml | 4 + .../references/plugin-json-spec.md | 173 +++++++ .../scripts/create_basic_plugin.py | 301 ++++++++++++ 11 files changed, 1907 insertions(+) create mode 100644 plugins/plugin-builder/.codex-plugin/plugin.json create mode 100644 plugins/plugin-builder/.mcp.json create mode 100644 plugins/plugin-builder/app/app.js create mode 100644 plugins/plugin-builder/app/index.html create mode 100644 plugins/plugin-builder/app/styles.css create mode 100644 plugins/plugin-builder/mcp/server.js create mode 100644 plugins/plugin-builder/skills/plugin-creator/SKILL.md create mode 100644 plugins/plugin-builder/skills/plugin-creator/agents/openai.yaml create mode 100644 plugins/plugin-builder/skills/plugin-creator/references/plugin-json-spec.md create mode 100755 plugins/plugin-builder/skills/plugin-creator/scripts/create_basic_plugin.py diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index a78865c7..f7d6c217 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -1465,6 +1465,18 @@ "authentication": "ON_INSTALL" }, "category": "Engineering" + }, + { + "name": "plugin-builder", + "source": { + "source": "local", + "path": "./plugins/plugin-builder" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Engineering" } ] } diff --git a/plugins/plugin-builder/.codex-plugin/plugin.json b/plugins/plugin-builder/.codex-plugin/plugin.json new file mode 100644 index 00000000..c5a415d3 --- /dev/null +++ b/plugins/plugin-builder/.codex-plugin/plugin.json @@ -0,0 +1,30 @@ +{ + "name": "plugin-builder", + "version": "0.1.0", + "description": "Create local Codex plugins with the bundled Plugin Creator skill, then review and share the result from a polished right-side app.", + "author": { + "name": "OpenAI", + "url": "https://openai.com/" + }, + "homepage": "https://openai.com/", + "license": "Proprietary", + "keywords": [ + "plugin", + "builder", + "sharing", + "mcp-app" + ], + "skills": "./skills/", + "mcpServers": "./.mcp.json", + "interface": { + "displayName": "Plugin Builder", + "shortDescription": "Create, review, and share newly built local plugins", + "longDescription": "Use the bundled Plugin Creator skill to scaffold a local plugin, then open a read-only Codex app that inspects the plugin's real local contents and continues into View or Share in the desktop app.", + "developerName": "OpenAI", + "category": "Developer Tools", + "websiteURL": "https://openai.com/", + "privacyPolicyURL": "https://openai.com/policies/row-privacy-policy/", + "termsOfServiceURL": "https://openai.com/policies/row-terms-of-use/", + "screenshots": [] + } +} diff --git a/plugins/plugin-builder/.mcp.json b/plugins/plugin-builder/.mcp.json new file mode 100644 index 00000000..e2aac1e6 --- /dev/null +++ b/plugins/plugin-builder/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "plugin-builder": { + "command": "node", + "args": [ + "./mcp/server.js" + ], + "cwd": "." + } + } +} diff --git a/plugins/plugin-builder/app/app.js b/plugins/plugin-builder/app/app.js new file mode 100644 index 00000000..38a07add --- /dev/null +++ b/plugins/plugin-builder/app/app.js @@ -0,0 +1,348 @@ +const summaryView = document.querySelector('[data-view="summary"]'); +const detailView = document.querySelector('[data-view="detail"]'); +const detailContent = document.querySelector("#detail-content"); +const backButton = document.querySelector("#back-button"); +const pluginTitle = document.querySelector("#plugin-title"); +const pluginDescription = document.querySelector("#plugin-description"); +const skillsList = document.querySelector("#skills-list"); +const appsList = document.querySelector("#apps-list"); +const mcpList = document.querySelector("#mcp-list"); +const marketplacesList = document.querySelector("#marketplaces-list"); +const localDetails = document.querySelector("#local-details"); +const viewButton = document.querySelector("#view-plugin"); +const shareButton = document.querySelector("#share-plugin"); + +const state = { + detail: null, + model: readModel(), +}; +let nextRpcId = 1; +const pendingRpc = new Map(); + +function decodePayload(raw) { + if (raw == null) { + return null; + } + if (typeof raw === "string") { + try { + return JSON.parse(raw); + } catch { + return null; + } + } + return typeof raw === "object" ? raw : null; +} + +function modelFromPayload(raw) { + const payload = decodePayload(raw); + if (payload == null) { + return null; + } + if (payload.plugin && Array.isArray(payload.skills)) { + return payload; + } + if (payload.structuredContent) { + return modelFromPayload(payload.structuredContent); + } + if (Array.isArray(payload.content)) { + for (const item of payload.content) { + const found = modelFromPayload(item?.text ?? item); + if (found) { + return found; + } + } + } + return null; +} + +function readModel() { + return ( + modelFromPayload(window.openai?.toolOutput) ?? + modelFromPayload(window.openai?.toolResponseMetadata) ?? { + plugin: { + displayName: "Plugin summary unavailable", + description: "Codex did not receive plugin summary data for this app.", + viewUrl: null, + shareUrl: null, + }, + skills: [], + apps: [], + mcpServers: [], + marketplaces: [], + localDetails: [], + } + ); +} + +function text(value) { + return value == null || value === "" ? "Not provided" : String(value); +} + +function clear(element) { + element.innerHTML = ""; +} + +function sendMcpAppMessage(message) { + if (window.parent === window) { + return; + } + window.parent.postMessage(message, "*"); +} + +function requestMcpApp(method, params) { + const id = nextRpcId; + nextRpcId += 1; + + const promise = new Promise((resolve, reject) => { + pendingRpc.set(id, { reject, resolve }); + window.setTimeout(() => { + const pending = pendingRpc.get(id); + if (!pending) { + return; + } + pendingRpc.delete(id); + reject(new Error(`${method} timed out.`)); + }, 5000); + }); + + sendMcpAppMessage({ + id, + jsonrpc: "2.0", + method, + params, + }); + return promise; +} + +function notifyMcpApp(method, params = {}) { + sendMcpAppMessage({ + jsonrpc: "2.0", + method, + params, + }); +} + +async function connectMcpApp() { + try { + await requestMcpApp("ui/initialize", { + appCapabilities: { + availableDisplayModes: ["inline", "fullscreen"], + }, + appInfo: { + name: "Plugin Builder", + version: "0.1.0", + }, + protocolVersion: "2026-01-26", + }); + notifyMcpApp("ui/notifications/initialized"); + await requestMcpApp("ui/request-display-mode", { + mode: "fullscreen", + }); + } catch { + // The inline summary remains useful if the host does not open a side panel. + } +} + +function openCodexLink(href) { + if (typeof href !== "string" || href.length === 0) { + return; + } + window.openai?.openExternal?.({ href }); +} + +function renderResourceList(element, items, kind) { + clear(element); + if (items.length === 0) { + element.append(createEmptyRow(`No ${kind} found in this plugin.`)); + return; + } + + for (const item of items) { + const button = document.createElement("button"); + button.type = "button"; + button.className = "resource-row"; + button.dataset.kind = kind; + button.dataset.id = item.id; + button.innerHTML = [ + `${escapeHtml(text(item.title))}`, + `${escapeHtml(text(item.summary))}`, + `${escapeHtml(text(item.pathLabel))}`, + ``, + ].join(""); + button.addEventListener("click", () => showDetail(kind, item.id)); + element.append(button); + } +} + +function renderMetaList(element, items) { + clear(element); + if (items.length === 0) { + element.append(createEmptyRow("Nothing to show here yet.")); + return; + } + for (const item of items) { + const row = document.createElement("div"); + row.className = "meta-row"; + row.innerHTML = [ + `${escapeHtml(text(item.label))}`, + `${escapeHtml(text(item.value))}`, + ].join(""); + element.append(row); + } +} + +function createEmptyRow(message) { + const row = document.createElement("div"); + row.className = "empty-row"; + row.textContent = message; + return row; +} + +function renderSummary() { + state.model = readModel(); + const model = state.model; + pluginTitle.textContent = text(model.plugin.displayName); + pluginDescription.textContent = text(model.plugin.description); + viewButton.disabled = !model.plugin.viewUrl; + shareButton.disabled = !model.plugin.shareUrl; + renderResourceList(skillsList, model.skills, "skills"); + renderResourceList(appsList, model.apps, "apps"); + renderResourceList(mcpList, model.mcpServers, "mcpServers"); + renderMetaList(marketplacesList, model.marketplaces); + renderMetaList(localDetails, model.localDetails); +} + +function showDetail(kind, id) { + const detail = + kind === "skills" + ? state.model.skills.find((item) => item.id === id) + : kind === "apps" + ? state.model.apps.find((item) => item.id === id) + : state.model.mcpServers.find((item) => item.id === id); + + if (!detail) { + return; + } + + state.detail = { kind, item: detail }; + summaryView.hidden = true; + detailView.hidden = false; + detailContent.innerHTML = detailMarkup(state.detail); +} + +function hideDetail() { + state.detail = null; + detailView.hidden = true; + summaryView.hidden = false; +} + +function detailMarkup(detail) { + const item = detail.item; + const title = + detail.kind === "skills" + ? `${escapeHtml(text(item.title))} / SKILL.md` + : escapeHtml(text(item.title)); + const kicker = + detail.kind === "skills" + ? "Filesystem skill" + : detail.kind === "apps" + ? "Plugin app" + : "MCP server"; + const blocks = + detail.kind === "skills" + ? skillDetailBlocks(item) + : detail.kind === "apps" + ? appDetailBlocks(item) + : mcpDetailBlocks(item); + + return ` +
+
+ ${escapeHtml(kicker)} +

${title}

+

${escapeHtml(text(item.summary))}

+
+
${blocks}
+
+ `; +} + +function skillDetailBlocks(item) { + const headings = + item.headings?.length > 0 + ? `
    ${item.headings + .map((heading) => `
  • ${escapeHtml(text(heading))}
  • `) + .join("")}
` + : "

No headings detected in this skill body.

"; + return [ + detailBlock("Source", `${escapeHtml(text(item.pathLabel))}`), + detailBlock("Frontmatter summary", `

${escapeHtml(text(item.frontmatterSummary))}

`), + detailBlock("Content outline", headings), + detailBlock("Preview", `

${escapeHtml(text(item.preview))}

`), + ].join(""); +} + +function appDetailBlocks(item) { + return [ + detailBlock("Definition", `${escapeHtml(text(item.pathLabel))}`), + detailBlock("App id", `

${escapeHtml(text(item.appId))}

`), + ].join(""); +} + +function mcpDetailBlocks(item) { + return [ + detailBlock("Definition", `${escapeHtml(text(item.pathLabel))}`), + detailBlock("Command", `${escapeHtml(text(item.commandLabel))}`), + ].join(""); +} + +function detailBlock(title, body) { + return ` +
+

${escapeHtml(title)}

+ ${body} +
+ `; +} + +function escapeHtml(value) { + return String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + +viewButton.addEventListener("click", () => { + openCodexLink(state.model.plugin.viewUrl); +}); +shareButton.addEventListener("click", () => { + openCodexLink(state.model.plugin.shareUrl); +}); +backButton.addEventListener("click", hideDetail); + +window.addEventListener("message", (event) => { + const message = event.data; + if ( + !message || + message.jsonrpc !== "2.0" || + !pendingRpc.has(message.id) || + (!("result" in message) && !("error" in message)) + ) { + return; + } + + const pending = pendingRpc.get(message.id); + pendingRpc.delete(message.id); + if (message.error) { + pending.reject(new Error(message.error.message || "MCP app request failed.")); + return; + } + pending.resolve(message.result || {}); +}); + +window.addEventListener("openai:set_globals", renderSummary); + +renderSummary(); +connectMcpApp(); diff --git a/plugins/plugin-builder/app/index.html b/plugins/plugin-builder/app/index.html new file mode 100644 index 00000000..be361905 --- /dev/null +++ b/plugins/plugin-builder/app/index.html @@ -0,0 +1,64 @@ + + + + + + Plugin Builder + + + +
+
+
+
+

Plugin created

+
+ +
+

Loading plugin

+

+
+
+
+
+ + +
+
+ +
+

Skills

+
+
+ +
+

Apps

+
+
+ +
+

MCP servers

+
+
+ +
+

Marketplaces

+
+
+ +
+

Local details

+
+
+
+ + +
+ + + diff --git a/plugins/plugin-builder/app/styles.css b/plugins/plugin-builder/app/styles.css new file mode 100644 index 00000000..736305c9 --- /dev/null +++ b/plugins/plugin-builder/app/styles.css @@ -0,0 +1,342 @@ +:root { + color-scheme: light dark; + --surface: color-mix(in srgb, Canvas 96%, transparent); + --surface-muted: color-mix(in srgb, Canvas 92%, CanvasText 8%); + --surface-strong: color-mix(in srgb, Canvas 88%, CanvasText 12%); + --border: color-mix(in srgb, CanvasText 12%, transparent); + --text: CanvasText; + --muted: color-mix(in srgb, CanvasText 62%, transparent); + --accent: #111111; + --success: #159947; + --shadow: 0 18px 52px color-mix(in srgb, CanvasText 12%, transparent); + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; +} + +* { + box-sizing: border-box; +} + +html, +body { + min-height: 100%; + margin: 0; +} + +body { + background: transparent; + color: var(--text); +} + +button { + color: inherit; + font: inherit; +} + +.shell { + min-height: 100vh; + padding: 28px 30px 34px; +} + +.hero { + display: flex; + justify-content: space-between; + gap: 28px; + padding-bottom: 26px; + border-bottom: 1px solid var(--border); +} + +.hero-copy { + min-width: 0; +} + +.eyebrow { + margin: 0 0 18px; + color: var(--success); + font-size: 13px; + font-weight: 600; + letter-spacing: 0; +} + +.title-row { + display: flex; + align-items: flex-start; + gap: 18px; +} + +.plugin-mark { + position: relative; + width: 78px; + height: 78px; + flex: 0 0 78px; + border: 1px solid var(--border); + border-radius: 14px; + background: var(--surface); +} + +.plugin-mark::before { + position: absolute; + inset: 19px; + border: 3px solid var(--accent); + border-radius: 8px; + content: ""; +} + +.plugin-mark::after { + position: absolute; + inset: 28px; + border: 2px solid var(--accent); + border-radius: 4px; + content: ""; +} + +h1 { + margin: 4px 0 8px; + font-size: 28px; + font-weight: 650; + letter-spacing: 0; + line-height: 1.08; +} + +.subtitle { + max-width: 560px; + margin: 0; + color: var(--muted); + font-size: 15px; + line-height: 1.45; +} + +.hero-actions { + display: flex; + flex: 0 0 auto; + align-items: flex-start; + gap: 10px; + padding-top: 40px; +} + +.primary-button, +.quiet-button, +.back-button { + border: 1px solid var(--border); + border-radius: 10px; + cursor: pointer; + min-height: 42px; + padding: 0 18px; + transition: + background-color 120ms ease, + border-color 120ms ease, + transform 120ms ease; +} + +.primary-button { + border-color: var(--accent); + background: var(--accent); + color: white; + font-weight: 600; +} + +.quiet-button, +.back-button { + background: var(--surface); +} + +.primary-button:hover, +.quiet-button:hover, +.back-button:hover, +.resource-row:hover { + transform: translateY(-1px); +} + +.content-section { + padding-top: 26px; +} + +.content-section h2, +.detail-header h2 { + margin: 0 0 14px; + font-size: 15px; + font-weight: 650; + letter-spacing: 0; +} + +.resource-list, +.meta-list { + overflow: hidden; + border: 1px solid var(--border); + border-radius: 14px; + background: var(--surface); +} + +.resource-row, +.meta-row, +.empty-row { + width: 100%; + min-height: 70px; + border: 0; + border-bottom: 1px solid var(--border); + background: transparent; + padding: 16px 18px; + text-align: left; +} + +.resource-row:last-child, +.meta-row:last-child, +.empty-row:last-child { + border-bottom: 0; +} + +.resource-row { + display: grid; + grid-template-columns: minmax(160px, 0.8fr) minmax(220px, 1.2fr) auto auto; + align-items: center; + gap: 18px; + cursor: pointer; +} + +.resource-name, +.meta-label { + min-width: 0; + font-size: 14px; + font-weight: 600; +} + +.resource-summary, +.meta-value, +.empty-row { + min-width: 0; + color: var(--muted); + font-size: 14px; + line-height: 1.4; +} + +.resource-path { + color: var(--muted); + font-size: 13px; + white-space: nowrap; +} + +.resource-chevron { + color: var(--muted); + font-size: 18px; +} + +.meta-row { + display: grid; + grid-template-columns: minmax(160px, 0.8fr) minmax(260px, 1.2fr); + align-items: center; + gap: 18px; +} + +.meta-value { + overflow-wrap: anywhere; +} + +.back-button { + margin-bottom: 22px; + min-height: 38px; + padding: 0 14px; + color: var(--muted); +} + +.detail-card { + border: 1px solid var(--border); + border-radius: 16px; + background: var(--surface); + box-shadow: var(--shadow); + padding: 24px; +} + +.detail-header { + display: grid; + gap: 8px; + margin-bottom: 22px; +} + +.detail-kicker { + color: var(--muted); + font-size: 13px; +} + +.detail-title { + margin: 0; + font-size: 26px; + font-weight: 650; + letter-spacing: 0; +} + +.detail-copy { + margin: 0; + color: var(--muted); + font-size: 15px; + line-height: 1.5; +} + +.detail-grid { + display: grid; + gap: 16px; +} + +.detail-block { + border: 1px solid var(--border); + border-radius: 14px; + background: var(--surface-muted); + padding: 18px; +} + +.detail-block h3 { + margin: 0 0 10px; + font-size: 14px; + font-weight: 650; +} + +.detail-block p, +.detail-block li, +.detail-block code { + font-size: 14px; + line-height: 1.55; +} + +.detail-block p { + margin: 0; +} + +.detail-block ul { + margin: 0; + padding-left: 18px; +} + +.detail-code { + display: block; + overflow-wrap: anywhere; + white-space: pre-wrap; +} + +@media (max-width: 860px) { + .shell { + padding: 24px 20px 28px; + } + + .hero { + flex-direction: column; + } + + .hero-actions { + padding-top: 0; + } + + .resource-row, + .meta-row { + grid-template-columns: 1fr; + gap: 8px; + } + + .resource-path { + white-space: normal; + } +} diff --git a/plugins/plugin-builder/mcp/server.js b/plugins/plugin-builder/mcp/server.js new file mode 100644 index 00000000..474a9c37 --- /dev/null +++ b/plugins/plugin-builder/mcp/server.js @@ -0,0 +1,443 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const os = require("node:os"); + +const APP_RESOURCE_URI = "ui://widget/plugin-builder-summary-v1.html"; +const WIDGET_MIME_TYPE = "text/html;profile=mcp-app"; +const pluginRoot = path.resolve(__dirname, ".."); + +const tools = [ + { + name: "open_created_plugin_summary", + title: "Open created plugin summary", + description: + "Render a right-side Codex app for a newly created local plugin, with View and Share handoff actions.", + inputSchema: { + type: "object", + additionalProperties: false, + properties: { + pluginPath: { + type: "string", + description: "Absolute filesystem path to the created plugin root.", + }, + targetMarketplacePath: { + type: "string", + description: + "Absolute filesystem path to the marketplace.json Codex should use for View and Share.", + }, + relatedMarketplacePaths: { + type: "array", + items: { type: "string" }, + description: + "Optional additional absolute marketplace.json paths that also contain this plugin.", + }, + }, + required: ["pluginPath", "targetMarketplacePath"], + }, + _meta: { + ui: { + resourceUri: APP_RESOURCE_URI, + }, + "ui/resourceUri": APP_RESOURCE_URI, + "openai/outputTemplate": APP_RESOURCE_URI, + }, + }, +]; + +function sendResult(id, result) { + process.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}\n`); +} + +function sendError(id, code, message) { + process.stdout.write( + `${JSON.stringify({ + jsonrpc: "2.0", + id, + error: { code, message }, + })}\n`, + ); +} + +function readJson(filePath) { + return JSON.parse(fs.readFileSync(filePath, "utf8")); +} + +function readTextIfPresent(filePath) { + return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : null; +} + +function assertAbsolutePath(value, label) { + if (typeof value !== "string" || !path.isAbsolute(value)) { + throw new Error(`${label} must be an absolute filesystem path.`); + } + return path.resolve(value); +} + +function listDirectories(root) { + if (!fs.existsSync(root)) { + return []; + } + return fs + .readdirSync(root, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => path.join(root, entry.name)) + .sort((left, right) => left.localeCompare(right)); +} + +function parseFrontmatter(markdown) { + if (!markdown.startsWith("---\n")) { + return { body: markdown.trim(), fields: {} }; + } + + const end = markdown.indexOf("\n---\n", 4); + if (end === -1) { + return { body: markdown.trim(), fields: {} }; + } + + const frontmatterBody = markdown.slice(4, end); + const body = markdown.slice(end + 5).trim(); + const fields = {}; + + for (const line of frontmatterBody.split("\n")) { + const separator = line.indexOf(":"); + if (separator === -1) { + continue; + } + const key = line.slice(0, separator).trim(); + const rawValue = line.slice(separator + 1).trim(); + fields[key] = rawValue.replace(/^["']|["']$/g, ""); + } + + return { body, fields }; +} + +function summarizeText(text, maxLength = 180) { + const normalized = String(text ?? "").replace(/\s+/g, " ").trim(); + if (normalized.length <= maxLength) { + return normalized; + } + return `${normalized.slice(0, maxLength - 1).trim()}...`; +} + +function relativeLabel(pluginPath, filePath) { + return path.relative(pluginPath, filePath) || "."; +} + +function readSkills(pluginPath, manifest) { + const skillsRoot = path.resolve( + pluginPath, + typeof manifest.skills === "string" ? manifest.skills : "./skills", + ); + + return listDirectories(skillsRoot) + .map((skillDir) => path.join(skillDir, "SKILL.md")) + .filter((skillPath) => fs.existsSync(skillPath)) + .map((skillPath) => { + const markdown = fs.readFileSync(skillPath, "utf8"); + const parsed = parseFrontmatter(markdown); + const fallbackName = path.basename(path.dirname(skillPath)); + const title = parsed.fields.name || fallbackName; + const summary = + parsed.fields.description || + summarizeText(parsed.body) || + "No skill description provided."; + const headings = parsed.body + .split("\n") + .filter((line) => /^#{1,3}\s+/.test(line)) + .map((line) => line.replace(/^#{1,3}\s+/, "").trim()) + .filter(Boolean) + .slice(0, 6); + + return { + id: fallbackName, + title, + summary, + pathLabel: relativeLabel(pluginPath, skillPath), + frontmatterSummary: parsed.fields.description || "No frontmatter summary provided.", + headings, + preview: summarizeText(parsed.body, 280) || "This skill does not have a Markdown body.", + }; + }); +} + +function readApps(pluginPath, manifest) { + const appPath = path.resolve( + pluginPath, + typeof manifest.apps === "string" ? manifest.apps : "./.app.json", + ); + const appText = readTextIfPresent(appPath); + if (appText == null) { + return []; + } + + const appManifest = JSON.parse(appText); + const apps = appManifest.apps && typeof appManifest.apps === "object" + ? appManifest.apps + : {}; + + return Object.entries(apps).map(([key, value]) => ({ + id: key, + title: key, + summary: value?.description || value?.title || "Plugin app definition.", + pathLabel: relativeLabel(pluginPath, appPath), + appId: value?.id || "No app id provided.", + })); +} + +function readMcpServers(pluginPath, manifest) { + const mcpPath = path.resolve( + pluginPath, + typeof manifest.mcpServers === "string" ? manifest.mcpServers : "./.mcp.json", + ); + const mcpText = readTextIfPresent(mcpPath); + if (mcpText == null) { + return []; + } + + const mcpManifest = JSON.parse(mcpText); + const servers = + mcpManifest.mcpServers && typeof mcpManifest.mcpServers === "object" + ? mcpManifest.mcpServers + : {}; + + return Object.entries(servers).map(([key, value]) => { + const args = Array.isArray(value?.args) ? value.args.join(" ") : ""; + const commandLabel = [value?.command || "No command provided.", args] + .filter(Boolean) + .join(" "); + return { + id: key, + title: key, + summary: value?.cwd + ? `Runs from ${value.cwd === "." ? "the plugin root" : value.cwd}.` + : "Local MCP server definition.", + pathLabel: relativeLabel(pluginPath, mcpPath), + commandLabel, + }; + }); +} + +function describeMarketplace(marketplacePath, targetMarketplacePath) { + const personalMarketplacePath = path.join( + os.homedir(), + ".agents", + "plugins", + "marketplace.json", + ); + const marketplaceRoot = path.dirname(path.dirname(path.dirname(marketplacePath))); + const label = + marketplacePath === personalMarketplacePath + ? "Personal marketplace" + : `${path.basename(marketplaceRoot) || "Workspace"} marketplace`; + const suffix = + marketplacePath === targetMarketplacePath ? " (View and Share target)" : ""; + return { + label: `${label}${suffix}`, + value: marketplacePath, + }; +} + +function buildPluginDeepLink(pluginName, marketplacePath, mode) { + const params = new URLSearchParams({ marketplacePath }); + if (mode === "share") { + params.set("mode", "share"); + } + return `codex://plugins/${encodeURIComponent(pluginName)}?${params.toString()}`; +} + +function uniquePaths(paths) { + return [...new Set(paths)]; +} + +function readPluginSummary(args = {}) { + const pluginPath = assertAbsolutePath(args.pluginPath, "pluginPath"); + const targetMarketplacePath = assertAbsolutePath( + args.targetMarketplacePath, + "targetMarketplacePath", + ); + const manifestPath = path.join(pluginPath, ".codex-plugin", "plugin.json"); + if (!fs.existsSync(manifestPath)) { + throw new Error(`pluginPath does not contain ${manifestPath}.`); + } + + const manifest = readJson(manifestPath); + const pluginName = manifest.name || path.basename(pluginPath); + const displayName = manifest.interface?.displayName || pluginName; + const relatedMarketplacePaths = Array.isArray(args.relatedMarketplacePaths) + ? args.relatedMarketplacePaths.map((value) => + assertAbsolutePath(value, "relatedMarketplacePaths entry"), + ) + : []; + const marketplaces = uniquePaths([ + targetMarketplacePath, + ...relatedMarketplacePaths, + ]).map((marketplacePath) => + describeMarketplace(marketplacePath, targetMarketplacePath), + ); + + return { + plugin: { + name: pluginName, + displayName, + description: + manifest.interface?.shortDescription || + manifest.description || + "No plugin description provided.", + viewUrl: buildPluginDeepLink(pluginName, targetMarketplacePath, "view"), + shareUrl: buildPluginDeepLink(pluginName, targetMarketplacePath, "share"), + }, + skills: readSkills(pluginPath, manifest), + apps: readApps(pluginPath, manifest), + mcpServers: readMcpServers(pluginPath, manifest), + marketplaces, + localDetails: [ + { label: "Plugin path", value: pluginPath }, + { label: "Manifest", value: manifestPath }, + ], + }; +} + +function widgetResourceMeta() { + return { + "openai/widgetDescription": + "A right-side Codex app that presents a newly created local plugin and offers View or Share handoff actions.", + "openai/widgetPrefersBorder": false, + "openai/widgetCSP": { + connect_domains: [], + resource_domains: [], + frame_domains: [], + }, + ui: { + prefersBorder: false, + csp: { + connectDomains: [], + resourceDomains: [], + frameDomains: [], + }, + }, + }; +} + +function widgetHtml() { + const html = fs.readFileSync(path.join(pluginRoot, "app", "index.html"), "utf8"); + const styles = fs.readFileSync(path.join(pluginRoot, "app", "styles.css"), "utf8"); + const script = fs.readFileSync(path.join(pluginRoot, "app", "app.js"), "utf8"); + return html + .replace('', ``) + .replace('', ``); +} + +function toolResult(summary) { + return { + content: [ + { + type: "text", + text: `Opened the Plugin Builder summary for ${summary.plugin.displayName}.`, + }, + ], + structuredContent: summary, + }; +} + +function handleToolCall(params = {}) { + if (params.name !== "open_created_plugin_summary") { + throw new Error(`Unknown tool: ${params.name}`); + } + return toolResult(readPluginSummary(params.arguments || params.args || {})); +} + +function handleRequest(request) { + if (!request?.id && request?.method?.startsWith("notifications/")) { + return; + } + + switch (request.method) { + case "initialize": + sendResult(request.id, { + protocolVersion: request.params?.protocolVersion || "2024-11-05", + capabilities: { + tools: {}, + resources: { + subscribe: false, + listChanged: false, + }, + }, + serverInfo: { + name: "plugin-builder", + version: "0.1.0", + }, + }); + return; + case "tools/list": + sendResult(request.id, { tools }); + return; + case "tools/call": + sendResult(request.id, handleToolCall(request.params)); + return; + case "resources/list": + sendResult(request.id, { + resources: [ + { + uri: APP_RESOURCE_URI, + name: "plugin_creation_summary", + title: "Plugin Builder summary", + description: "Read-only local plugin summary and sharing handoff.", + mimeType: WIDGET_MIME_TYPE, + _meta: widgetResourceMeta(), + }, + ], + }); + return; + case "resources/templates/list": + sendResult(request.id, { resourceTemplates: [] }); + return; + case "resources/read": + if (request.params?.uri !== APP_RESOURCE_URI) { + sendError(request.id, -32602, `Unknown resource: ${request.params?.uri}`); + return; + } + sendResult(request.id, { + contents: [ + { + uri: APP_RESOURCE_URI, + mimeType: WIDGET_MIME_TYPE, + text: widgetHtml(), + _meta: widgetResourceMeta(), + }, + ], + }); + return; + default: + sendError(request.id, -32601, `Unsupported method: ${request.method}`); + } +} + +process.stdin.setEncoding("utf8"); +let buffer = ""; + +process.stdin.on("data", (chunk) => { + buffer += chunk; + let newlineIndex = buffer.indexOf("\n"); + + while (newlineIndex >= 0) { + const line = buffer.slice(0, newlineIndex).trim(); + buffer = buffer.slice(newlineIndex + 1); + newlineIndex = buffer.indexOf("\n"); + + if (!line) { + continue; + } + + let request; + try { + request = JSON.parse(line); + handleRequest(request); + } catch (error) { + sendError( + request?.id ?? null, + -32000, + error instanceof Error ? error.message : String(error), + ); + } + } +}); diff --git a/plugins/plugin-builder/skills/plugin-creator/SKILL.md b/plugins/plugin-builder/skills/plugin-creator/SKILL.md new file mode 100644 index 00000000..44dedb45 --- /dev/null +++ b/plugins/plugin-builder/skills/plugin-creator/SKILL.md @@ -0,0 +1,179 @@ +--- +name: plugin-creator +description: Create and scaffold plugin directories for Codex with a required `.codex-plugin/plugin.json`, optional plugin folders/files, and baseline placeholders you can edit before publishing or testing. Use when Codex needs to create a new personal plugin, add optional plugin structure, or generate or update personal or repo-root `.agents/plugins/marketplace.json` entries for plugin ordering and availability metadata. +--- + +# Plugin Creator + +## Quick Start + +1. Run the scaffold script: + +```bash + # Plugin names are normalized to lower-case hyphen-case and must be <= 64 chars. + # The generated folder and plugin.json name are always the same. +# Run from repo root (or replace .agents/... with the absolute path to this SKILL). +# By default creates in ~/plugins/. +python3 .agents/skills/plugin-creator/scripts/create_basic_plugin.py +``` + +2. Open `/.codex-plugin/plugin.json` and replace `[TODO: ...]` placeholders. + +3. Generate or update the personal marketplace entry when the plugin should appear in Codex UI ordering: + +```bash +# Personal marketplace entries default to ~/.agents/plugins/marketplace.json. +python3 .agents/skills/plugin-creator/scripts/create_basic_plugin.py my-plugin --with-marketplace +``` + +If the current Git repo already has `.agents/plugins/marketplace.json` and the user has not said +whether the plugin is personal or shared with their team, ask before generating a marketplace entry. +When they choose the repo marketplace, use: + +```bash +python3 .agents/skills/plugin-creator/scripts/create_basic_plugin.py my-plugin \ + --path ./plugins \ + --marketplace-path ./.agents/plugins/marketplace.json \ + --with-marketplace +``` + +4. Generate/adjust optional companion folders as needed: + +```bash +python3 .agents/skills/plugin-creator/scripts/create_basic_plugin.py my-plugin \ + --path \ + --marketplace-path \ + --with-skills --with-hooks --with-scripts --with-assets --with-mcp --with-apps --with-marketplace +``` + +`` is the directory where the plugin folder `` will be created (for example `~/code/plugins`). + +## What this skill creates + +- Default marketplace-backed scaffolds are personal: `~/plugins//` plus + `~/.agents/plugins/marketplace.json`. +- If the current Git repo already has `.agents/plugins/marketplace.json` and the user has not said + personal vs team, ask which marketplace to update before generating a marketplace entry. +- Creates plugin root at `///`. +- Always creates `///.codex-plugin/plugin.json`. +- Fills the manifest with the full schema shape, placeholder values, and the complete `interface` section. +- Creates or updates the selected marketplace when `--with-marketplace` is set. + - If the marketplace file does not exist yet, seed top-level `name` plus `interface.displayName` placeholders before adding the first plugin entry. +- `` is normalized using skill-creator naming rules: + - `My Plugin` → `my-plugin` + - `My--Plugin` → `my-plugin` + - underscores, spaces, and punctuation are converted to `-` + - result is lower-case hyphen-delimited with consecutive hyphens collapsed +- Supports optional creation of: + - `skills/` + - `hooks/` + - `scripts/` + - `assets/` + - `.mcp.json` + - `.app.json` + +## Marketplace workflow + +- Personal plugins use `~/.agents/plugins/marketplace.json`. +- Repo/team plugins use `/.agents/plugins/marketplace.json`. +- Marketplace root metadata supports top-level `name` plus optional `interface.displayName`. +- Treat plugin order in `plugins[]` as render order in Codex. Append new entries unless a user explicitly asks to reorder the list. +- `displayName` belongs inside the marketplace `interface` object, not individual `plugins[]` entries. +- Each generated marketplace entry must include all of: + - `policy.installation` + - `policy.authentication` + - `category` +- Default new entries to: + - `policy.installation: "AVAILABLE"` + - `policy.authentication: "ON_INSTALL"` +- Override defaults only when the user explicitly specifies another allowed value. +- Allowed `policy.installation` values: + - `NOT_AVAILABLE` + - `AVAILABLE` + - `INSTALLED_BY_DEFAULT` +- Allowed `policy.authentication` values: + - `ON_INSTALL` + - `ON_USE` +- Treat `policy.products` as an override. Omit it unless the user explicitly requests product gating. +- The generated plugin entry shape is: + +```json +{ + "name": "plugin-name", + "source": { + "source": "local", + "path": "./plugins/plugin-name" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" +} +``` + +- Use `--force` only when intentionally replacing an existing marketplace entry for the same plugin name. +- If the selected marketplace file does not exist yet, create it with top-level `"name"`, an `"interface"` object containing `"displayName"`, and a `plugins` array, then add the new entry. + +- For a brand-new marketplace file, the root object should look like: + +```json +{ + "name": "[TODO: marketplace-name]", + "interface": { + "displayName": "[TODO: Marketplace Display Name]" + }, + "plugins": [ + { + "name": "plugin-name", + "source": { + "source": "local", + "path": "./plugins/plugin-name" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} +``` + +## Required behavior + +- Outer folder name and `plugin.json` `"name"` are always the same normalized plugin name. +- Do not remove required structure; keep `.codex-plugin/plugin.json` present. +- Keep manifest values as placeholders until a human or follow-up step explicitly fills them. +- If creating files inside an existing plugin path, use `--force` only when overwrite is intentional. +- Preserve any existing marketplace `interface.displayName`. +- When generating marketplace entries, always write `policy.installation`, `policy.authentication`, and `category` even if their values are defaults. +- Add `policy.products` only when the user explicitly asks for that override. +- Keep marketplace `source.path` relative to the selected marketplace root as `./plugins/`. +- When the workflow created or updated a marketplace-backed plugin, end the final user-facing + response with this Codex app handoff block, in this order, and do not add any text after the links. + First write the standalone line `To view this in the Codex app:`. Then write: + - `[View ](codex://plugins/?marketplacePath=)` + - `[Share ](codex://plugins/?marketplacePath=&mode=share)` +- Replace the normalized plugin-name placeholder in both the Markdown label and the deeplink path + segment with the real normalized plugin name. Replace the marketplace path placeholder with the + real absolute `marketplace.json` path from the scaffolded plugin. URL-encode the path segment and + query value when needed. +- Do not add `pluginName` or `hostId` query parameters to these deeplinks. Codex derives both after + the user clicks the link. +- Do not emit the `View ` or `Share ` links when no marketplace entry was + created or updated. + +## Reference to exact spec sample + +For the exact canonical sample JSON for both plugin manifests and marketplace entries, use: + +- `references/plugin-json-spec.md` + +## Validation + +After editing `SKILL.md`, run: + +```bash +python3 /scripts/quick_validate.py .agents/skills/plugin-creator +``` diff --git a/plugins/plugin-builder/skills/plugin-creator/agents/openai.yaml b/plugins/plugin-builder/skills/plugin-creator/agents/openai.yaml new file mode 100644 index 00000000..1b934d6e --- /dev/null +++ b/plugins/plugin-builder/skills/plugin-creator/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Plugin Creator" + short_description: "Scaffold plugins and marketplace entries" + default_prompt: "Use $plugin-creator to scaffold a plugin with placeholder plugin.json, optional structure, and a marketplace.json entry." diff --git a/plugins/plugin-builder/skills/plugin-creator/references/plugin-json-spec.md b/plugins/plugin-builder/skills/plugin-creator/references/plugin-json-spec.md new file mode 100644 index 00000000..ee66dfe4 --- /dev/null +++ b/plugins/plugin-builder/skills/plugin-creator/references/plugin-json-spec.md @@ -0,0 +1,173 @@ +# Plugin JSON sample spec + +```json +{ + "name": "plugin-name", + "version": "1.2.0", + "description": "Brief plugin description", + "author": { + "name": "Author Name", + "email": "author@example.com", + "url": "https://github.com/author" + }, + "homepage": "https://docs.example.com/plugin", + "repository": "https://github.com/author/plugin", + "license": "MIT", + "keywords": ["keyword1", "keyword2"], + "skills": "./skills/", + "hooks": "./hooks.json", + "mcpServers": "./.mcp.json", + "apps": "./.app.json", + "interface": { + "displayName": "Plugin Display Name", + "shortDescription": "Short description for subtitle", + "longDescription": "Long description for details page", + "developerName": "OpenAI", + "category": "Productivity", + "capabilities": ["Interactive", "Write"], + "websiteURL": "https://openai.com/", + "privacyPolicyURL": "https://openai.com/policies/row-privacy-policy/", + "termsOfServiceURL": "https://openai.com/policies/row-terms-of-use/", + "defaultPrompt": [ + "Summarize my inbox and draft replies for me.", + "Find open bugs and turn them into Linear tickets.", + "Review today's meetings and flag scheduling gaps." + ], + "brandColor": "#3B82F6", + "composerIcon": "./assets/icon.png", + "logo": "./assets/logo.png", + "screenshots": [ + "./assets/screenshot1.png", + "./assets/screenshot2.png", + "./assets/screenshot3.png" + ] + } +} +``` + +## Field guide + +### Top-level fields + +- `name` (`string`): Plugin identifier (kebab-case, no spaces). Required if `plugin.json` is provided and used as manifest name and component namespace. +- `version` (`string`): Plugin semantic version. +- `description` (`string`): Short purpose summary. +- `author` (`object`): Publisher identity. + - `name` (`string`): Author or team name. + - `email` (`string`): Contact email. + - `url` (`string`): Author/team homepage or profile URL. +- `homepage` (`string`): Documentation URL for plugin usage. +- `repository` (`string`): Source code URL. +- `license` (`string`): License identifier (for example `MIT`, `Apache-2.0`). +- `keywords` (`array` of `string`): Search/discovery tags. +- `skills` (`string`): Relative path to skill directories/files. +- `hooks` (`string`): Hook config path. +- `mcpServers` (`string`): MCP config path. +- `apps` (`string`): App manifest path for plugin integrations. +- `interface` (`object`): Interface/UX metadata block for plugin presentation. + +### `interface` fields + +- `displayName` (`string`): User-facing title shown for the plugin. +- `shortDescription` (`string`): Brief subtitle used in compact views. +- `longDescription` (`string`): Longer description used on details screens. +- `developerName` (`string`): Human-readable publisher name. +- `category` (`string`): Plugin category bucket. +- `capabilities` (`array` of `string`): Capability list from implementation. +- `websiteURL` (`string`): Public website for the plugin. +- `privacyPolicyURL` (`string`): Privacy policy URL. +- `termsOfServiceURL` (`string`): Terms of service URL. +- `defaultPrompt` (`array` of `string`): Starter prompts shown in composer/UX context. + - Include at most 3 strings. Entries after the first 3 are ignored and will not be included. + - Each string is capped at 128 characters. Longer entries are truncated. + - Prefer short starter prompts around 50 characters so they scan well in the UI. +- `brandColor` (`string`): Theme color for the plugin card. +- `composerIcon` (`string`): Path to icon asset. +- `logo` (`string`): Path to logo asset. +- `screenshots` (`array` of `string`): List of screenshot asset paths. + - Screenshot entries must be PNG filenames and stored under `./assets/`. + - Keep file paths relative to plugin root. + +### Path conventions and defaults + +- Path values should be relative and begin with `./`. +- `skills`, `hooks`, and `mcpServers` are supplemented on top of default component discovery; they do not replace defaults. +- Custom path values must follow the plugin root convention and naming/namespacing rules. +- This repo’s scaffold writes `.codex-plugin/plugin.json`; treat that as the manifest location this skill generates. + +# Marketplace JSON sample spec + +`marketplace.json` depends on where the plugin should live: + +- Personal plugin: `~/.agents/plugins/marketplace.json` +- Repo/team plugin: `/.agents/plugins/marketplace.json` + +```json +{ + "name": "openai-curated", + "interface": { + "displayName": "ChatGPT Official" + }, + "plugins": [ + { + "name": "linear", + "source": { + "source": "local", + "path": "./plugins/linear" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} +``` + +## Marketplace field guide + +### Top-level fields + +- `name` (`string`): Marketplace identifier or catalog name. +- `interface` (`object`, optional): Marketplace presentation metadata. +- `plugins` (`array`): Ordered plugin entries. This order determines how Codex renders plugins. + +### `interface` fields + +- `displayName` (`string`, optional): User-facing marketplace title. + +### Plugin entry fields + +- `name` (`string`): Plugin identifier. Match the plugin folder name and `plugin.json` `name`. +- `source` (`object`): Plugin source descriptor. + - `source` (`string`): Use `local` for this repo workflow. + - `path` (`string`): Relative plugin path based on the marketplace root. + - Personal plugin in `~/.agents/plugins/marketplace.json`: `./plugins/` + - Repo/team plugin: `./plugins/` + - The same relative path convention is used for both personal and repo/team marketplaces. + - Example: with `~/.agents/plugins/marketplace.json`, `./plugins/` resolves to `~/plugins/`. +- `policy` (`object`): Marketplace policy block. Always include it. + - `installation` (`string`): Availability policy. + - Allowed values: `NOT_AVAILABLE`, `AVAILABLE`, `INSTALLED_BY_DEFAULT` + - Default for new entries: `AVAILABLE` + - `authentication` (`string`): Authentication timing policy. + - Allowed values: `ON_INSTALL`, `ON_USE` + - Default for new entries: `ON_INSTALL` + - `products` (`array` of `string`, optional): Product override for this plugin entry. Omit it unless product gating is explicitly requested. +- `category` (`string`): Display category bucket. Always include it. + +### Marketplace generation rules + +- `displayName` belongs under the top-level `interface` object, not individual plugin entries. +- When creating a new marketplace file from scratch, seed `interface.displayName` alongside top-level `name`. +- Always include `policy.installation`, `policy.authentication`, and `category` on every generated or updated plugin entry. +- Treat `policy.products` as an override and omit it unless explicitly requested. +- Append new entries unless the user explicitly requests reordering. +- Replace an existing entry for the same plugin only when overwrite is intentional. +- Default new plugin creation to the personal marketplace. +- If the current Git repo already has `.agents/plugins/marketplace.json` and the user has not said + personal or team, ask which marketplace to update before creating the entry. +- Choose marketplace location to match the selected destination: + - Personal plugin: `~/.agents/plugins/marketplace.json` + - Repo/team plugin: `/.agents/plugins/marketplace.json` diff --git a/plugins/plugin-builder/skills/plugin-creator/scripts/create_basic_plugin.py b/plugins/plugin-builder/skills/plugin-creator/scripts/create_basic_plugin.py new file mode 100755 index 00000000..dcb24562 --- /dev/null +++ b/plugins/plugin-builder/skills/plugin-creator/scripts/create_basic_plugin.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +"""Scaffold a plugin directory and optionally update marketplace.json.""" + +from __future__ import annotations + +import argparse +import json +import re +from pathlib import Path +from typing import Any + + +MAX_PLUGIN_NAME_LENGTH = 64 +DEFAULT_PLUGIN_PARENT = Path.home() / "plugins" +DEFAULT_MARKETPLACE_PATH = Path.home() / ".agents" / "plugins" / "marketplace.json" +DEFAULT_INSTALL_POLICY = "AVAILABLE" +DEFAULT_AUTH_POLICY = "ON_INSTALL" +DEFAULT_CATEGORY = "Productivity" +DEFAULT_MARKETPLACE_DISPLAY_NAME = "[TODO: Marketplace Display Name]" +VALID_INSTALL_POLICIES = {"NOT_AVAILABLE", "AVAILABLE", "INSTALLED_BY_DEFAULT"} +VALID_AUTH_POLICIES = {"ON_INSTALL", "ON_USE"} + + +def normalize_plugin_name(plugin_name: str) -> str: + """Normalize a plugin name to lowercase hyphen-case.""" + normalized = plugin_name.strip().lower() + normalized = re.sub(r"[^a-z0-9]+", "-", normalized) + normalized = normalized.strip("-") + normalized = re.sub(r"-{2,}", "-", normalized) + return normalized + + +def validate_plugin_name(plugin_name: str) -> None: + if not plugin_name: + raise ValueError("Plugin name must include at least one letter or digit.") + if len(plugin_name) > MAX_PLUGIN_NAME_LENGTH: + raise ValueError( + f"Plugin name '{plugin_name}' is too long ({len(plugin_name)} characters). " + f"Maximum is {MAX_PLUGIN_NAME_LENGTH} characters." + ) + + +def build_plugin_json(plugin_name: str) -> dict: + return { + "name": plugin_name, + "version": "[TODO: 1.2.0]", + "description": "[TODO: Brief plugin description]", + "author": { + "name": "[TODO: Author Name]", + "email": "[TODO: author@example.com]", + "url": "[TODO: https://github.com/author]", + }, + "homepage": "[TODO: https://docs.example.com/plugin]", + "repository": "[TODO: https://github.com/author/plugin]", + "license": "[TODO: MIT]", + "keywords": ["[TODO: keyword1]", "[TODO: keyword2]"], + "skills": "[TODO: ./skills/]", + "hooks": "[TODO: ./hooks.json]", + "mcpServers": "[TODO: ./.mcp.json]", + "apps": "[TODO: ./.app.json]", + "interface": { + "displayName": "[TODO: Plugin Display Name]", + "shortDescription": "[TODO: Short description for subtitle]", + "longDescription": "[TODO: Long description for details page]", + "developerName": "[TODO: OpenAI]", + "category": "[TODO: Productivity]", + "capabilities": ["[TODO: Interactive]", "[TODO: Write]"], + "websiteURL": "[TODO: https://openai.com/]", + "privacyPolicyURL": "[TODO: https://openai.com/policies/row-privacy-policy/]", + "termsOfServiceURL": "[TODO: https://openai.com/policies/row-terms-of-use/]", + "defaultPrompt": [ + "[TODO: Summarize my inbox and draft replies for me.]", + "[TODO: Find open bugs and turn them into tickets.]", + "[TODO: Review today's meetings and flag gaps.]", + ], + "brandColor": "[TODO: #3B82F6]", + "composerIcon": "[TODO: ./assets/icon.png]", + "logo": "[TODO: ./assets/logo.png]", + "screenshots": [ + "[TODO: ./assets/screenshot1.png]", + "[TODO: ./assets/screenshot2.png]", + "[TODO: ./assets/screenshot3.png]", + ], + }, + } + + +def build_marketplace_entry( + plugin_name: str, + install_policy: str, + auth_policy: str, + category: str, +) -> dict[str, Any]: + return { + "name": plugin_name, + "source": { + "source": "local", + "path": f"./plugins/{plugin_name}", + }, + "policy": { + "installation": install_policy, + "authentication": auth_policy, + }, + "category": category, + } + + +def load_json(path: Path) -> dict[str, Any]: + with path.open() as handle: + return json.load(handle) + + +def build_default_marketplace() -> dict[str, Any]: + return { + "name": "[TODO: marketplace-name]", + "interface": { + "displayName": DEFAULT_MARKETPLACE_DISPLAY_NAME, + }, + "plugins": [], + } + + +def validate_marketplace_interface(payload: dict[str, Any]) -> None: + interface = payload.get("interface") + if interface is not None and not isinstance(interface, dict): + raise ValueError("marketplace.json field 'interface' must be an object.") + + +def update_marketplace_json( + marketplace_path: Path, + plugin_name: str, + install_policy: str, + auth_policy: str, + category: str, + force: bool, +) -> None: + if marketplace_path.exists(): + payload = load_json(marketplace_path) + else: + payload = build_default_marketplace() + + if not isinstance(payload, dict): + raise ValueError(f"{marketplace_path} must contain a JSON object.") + + validate_marketplace_interface(payload) + + plugins = payload.setdefault("plugins", []) + if not isinstance(plugins, list): + raise ValueError(f"{marketplace_path} field 'plugins' must be an array.") + + new_entry = build_marketplace_entry(plugin_name, install_policy, auth_policy, category) + + for index, entry in enumerate(plugins): + if isinstance(entry, dict) and entry.get("name") == plugin_name: + if not force: + raise FileExistsError( + f"Marketplace entry '{plugin_name}' already exists in {marketplace_path}. " + "Use --force to overwrite that entry." + ) + plugins[index] = new_entry + break + else: + plugins.append(new_entry) + + write_json(marketplace_path, payload, force=True) + + +def write_json(path: Path, data: dict, force: bool) -> None: + if path.exists() and not force: + raise FileExistsError(f"{path} already exists. Use --force to overwrite.") + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w") as handle: + json.dump(data, handle, indent=2) + handle.write("\n") + + +def create_stub_file(path: Path, payload: dict, force: bool) -> None: + if path.exists() and not force: + return + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w") as handle: + json.dump(payload, handle, indent=2) + handle.write("\n") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Create a plugin skeleton with placeholder plugin.json." + ) + parser.add_argument("plugin_name") + parser.add_argument( + "--path", + default=str(DEFAULT_PLUGIN_PARENT), + help=( + "Parent directory for plugin creation (defaults to /plugins). " + "Use /plugins when creating a repo/team plugin." + ), + ) + parser.add_argument("--with-skills", action="store_true", help="Create skills/ directory") + parser.add_argument("--with-hooks", action="store_true", help="Create hooks/ directory") + parser.add_argument("--with-scripts", action="store_true", help="Create scripts/ directory") + parser.add_argument("--with-assets", action="store_true", help="Create assets/ directory") + parser.add_argument("--with-mcp", action="store_true", help="Create .mcp.json placeholder") + parser.add_argument("--with-apps", action="store_true", help="Create .app.json placeholder") + parser.add_argument( + "--with-marketplace", + action="store_true", + help=( + "Create or update /.agents/plugins/marketplace.json by default. " + "Marketplace entries always point to ./plugins/ relative to the " + "marketplace root." + ), + ) + parser.add_argument( + "--marketplace-path", + default=str(DEFAULT_MARKETPLACE_PATH), + help=( + "Path to marketplace.json (defaults to /.agents/plugins/marketplace.json). " + "Use /.agents/plugins/marketplace.json for a repo/team plugin." + ), + ) + parser.add_argument( + "--install-policy", + default=DEFAULT_INSTALL_POLICY, + choices=sorted(VALID_INSTALL_POLICIES), + help="Marketplace policy.installation value", + ) + parser.add_argument( + "--auth-policy", + default=DEFAULT_AUTH_POLICY, + choices=sorted(VALID_AUTH_POLICIES), + help="Marketplace policy.authentication value", + ) + parser.add_argument( + "--category", + default=DEFAULT_CATEGORY, + help="Marketplace category value", + ) + parser.add_argument("--force", action="store_true", help="Overwrite existing files") + return parser.parse_args() + + +def main() -> None: + args = parse_args() + raw_plugin_name = args.plugin_name + plugin_name = normalize_plugin_name(raw_plugin_name) + if plugin_name != raw_plugin_name: + print(f"Note: Normalized plugin name from '{raw_plugin_name}' to '{plugin_name}'.") + validate_plugin_name(plugin_name) + + plugin_root = (Path(args.path).expanduser().resolve() / plugin_name) + plugin_root.mkdir(parents=True, exist_ok=True) + + plugin_json_path = plugin_root / ".codex-plugin" / "plugin.json" + write_json(plugin_json_path, build_plugin_json(plugin_name), args.force) + + optional_directories = { + "skills": args.with_skills, + "hooks": args.with_hooks, + "scripts": args.with_scripts, + "assets": args.with_assets, + } + for folder, enabled in optional_directories.items(): + if enabled: + (plugin_root / folder).mkdir(parents=True, exist_ok=True) + + if args.with_mcp: + create_stub_file( + plugin_root / ".mcp.json", + {"mcpServers": {}}, + args.force, + ) + + if args.with_apps: + create_stub_file( + plugin_root / ".app.json", + { + "apps": {}, + }, + args.force, + ) + + if args.with_marketplace: + marketplace_path = Path(args.marketplace_path).expanduser().resolve() + update_marketplace_json( + marketplace_path, + plugin_name, + args.install_policy, + args.auth_policy, + args.category, + args.force, + ) + + print(f"Created plugin scaffold: {plugin_root}") + print(f"plugin manifest: {plugin_json_path}") + if args.with_marketplace: + print(f"marketplace manifest: {marketplace_path}") + + +if __name__ == "__main__": + main() From a6ec7f3145a93b81643def123c6a8b7db3ffab3f Mon Sep 17 00:00:00 2001 From: Edward Frazer Date: Tue, 12 May 2026 14:17:32 -0700 Subject: [PATCH 04/10] feat(plugin-builder): refine artifact UI --- plugins/plugin-builder/app/app.js | 119 +++++++++--- plugins/plugin-builder/app/index.html | 31 +++- plugins/plugin-builder/app/styles.css | 255 ++++++++++++++++---------- plugins/plugin-builder/mcp/server.js | 47 +++-- 4 files changed, 305 insertions(+), 147 deletions(-) diff --git a/plugins/plugin-builder/app/app.js b/plugins/plugin-builder/app/app.js index 38a07add..c4042505 100644 --- a/plugins/plugin-builder/app/app.js +++ b/plugins/plugin-builder/app/app.js @@ -7,6 +7,9 @@ const pluginDescription = document.querySelector("#plugin-description"); const skillsList = document.querySelector("#skills-list"); const appsList = document.querySelector("#apps-list"); const mcpList = document.querySelector("#mcp-list"); +const skillsSection = document.querySelector("#skills-section"); +const appsSection = document.querySelector("#apps-section"); +const mcpSection = document.querySelector("#mcp-section"); const marketplacesList = document.querySelector("#marketplaces-list"); const localDetails = document.querySelector("#local-details"); const viewButton = document.querySelector("#view-plugin"); @@ -152,10 +155,6 @@ function openCodexLink(href) { function renderResourceList(element, items, kind) { clear(element); - if (items.length === 0) { - element.append(createEmptyRow(`No ${kind} found in this plugin.`)); - return; - } for (const item of items) { const button = document.createElement("button"); @@ -164,9 +163,11 @@ function renderResourceList(element, items, kind) { button.dataset.kind = kind; button.dataset.id = item.id; button.innerHTML = [ - `${escapeHtml(text(item.title))}`, - `${escapeHtml(text(item.summary))}`, - `${escapeHtml(text(item.pathLabel))}`, + ``, + ``, + ` ${escapeHtml(text(item.title))}`, + ` ${escapeHtml(text(item.summary))}`, + ``, ``, ].join(""); button.addEventListener("click", () => showDetail(kind, item.id)); @@ -184,20 +185,21 @@ function renderMetaList(element, items) { const row = document.createElement("div"); row.className = "meta-row"; row.innerHTML = [ - `${escapeHtml(text(item.label))}`, - `${escapeHtml(text(item.value))}`, + ``, + ` ${escapeHtml(text(item.label))}`, + ` ${escapeHtml(text(item.value))}`, + ``, + ``, ].join(""); + row.querySelector(".copy-button")?.addEventListener("click", () => { + copyText(text(item.value)); + }); element.append(row); } } -function createEmptyRow(message) { - const row = document.createElement("div"); - row.className = "empty-row"; - row.textContent = message; - return row; -} - function renderSummary() { state.model = readModel(); const model = state.model; @@ -205,13 +207,22 @@ function renderSummary() { pluginDescription.textContent = text(model.plugin.description); viewButton.disabled = !model.plugin.viewUrl; shareButton.disabled = !model.plugin.shareUrl; - renderResourceList(skillsList, model.skills, "skills"); - renderResourceList(appsList, model.apps, "apps"); - renderResourceList(mcpList, model.mcpServers, "mcpServers"); + renderResourceSection(skillsSection, skillsList, model.skills, "skills"); + renderResourceSection(appsSection, appsList, model.apps, "apps"); + renderResourceSection(mcpSection, mcpList, model.mcpServers, "mcpServers"); renderMetaList(marketplacesList, model.marketplaces); renderMetaList(localDetails, model.localDetails); } +function renderResourceSection(section, list, items, kind) { + section.hidden = items.length === 0; + if (items.length === 0) { + clear(list); + return; + } + renderResourceList(list, items, kind); +} + function showDetail(kind, id) { const detail = kind === "skills" @@ -240,11 +251,11 @@ function detailMarkup(detail) { const item = detail.item; const title = detail.kind === "skills" - ? `${escapeHtml(text(item.title))} / SKILL.md` + ? escapeHtml(text(item.title)) : escapeHtml(text(item.title)); const kicker = detail.kind === "skills" - ? "Filesystem skill" + ? "Skill" : detail.kind === "apps" ? "Plugin app" : "MCP server"; @@ -256,13 +267,14 @@ function detailMarkup(detail) { : mcpDetailBlocks(item); return ` -
+
${escapeHtml(kicker)}

${title}

${escapeHtml(text(item.summary))}

+ ${detail.kind === "skills" ? skillSourceRow(item) : ""}
-
${blocks}
+
${blocks}
`; } @@ -275,9 +287,8 @@ function skillDetailBlocks(item) { .join("")}` : "

No headings detected in this skill body.

"; return [ - detailBlock("Source", `${escapeHtml(text(item.pathLabel))}`), - detailBlock("Frontmatter summary", `

${escapeHtml(text(item.frontmatterSummary))}

`), - detailBlock("Content outline", headings), + detailBlock("Summary", `

${escapeHtml(text(item.frontmatterSummary))}

`), + detailBlock("Sections", headings), detailBlock("Preview", `

${escapeHtml(text(item.preview))}

`), ].join(""); } @@ -305,6 +316,62 @@ function detailBlock(title, body) { `; } +function skillSourceRow(item) { + return ` +
+ SKILL.md + ${escapeHtml(text(item.pathLabel))} +
+ `; +} + +function resourceIcon(kind) { + if (kind === "skills") { + return ` + + + + + + + `; + } + if (kind === "apps") { + return ` + + + + + `; + } + return ` + + + + + + + + `; +} + +function copyIcon() { + return ` + + `; +} + +async function copyText(value) { + try { + await navigator.clipboard?.writeText?.(value); + } catch { + // Copy is a convenience affordance; the visible text remains selectable. + } +} + function escapeHtml(value) { return String(value) .replaceAll("&", "&") diff --git a/plugins/plugin-builder/app/index.html b/plugins/plugin-builder/app/index.html index be361905..dae696c9 100644 --- a/plugins/plugin-builder/app/index.html +++ b/plugins/plugin-builder/app/index.html @@ -11,9 +11,14 @@
-

Plugin created

- +

Loading plugin

@@ -21,22 +26,29 @@

Loading plugin

- - + +
-
+

Skills

-
+

Apps

-
+

MCP servers

@@ -53,8 +65,9 @@

Local details

diff --git a/plugins/plugin-builder/app/styles.css b/plugins/plugin-builder/app/styles.css index 736305c9..6b2b9153 100644 --- a/plugins/plugin-builder/app/styles.css +++ b/plugins/plugin-builder/app/styles.css @@ -1,20 +1,17 @@ :root { color-scheme: light dark; - --surface: color-mix(in srgb, Canvas 96%, transparent); - --surface-muted: color-mix(in srgb, Canvas 92%, CanvasText 8%); - --surface-strong: color-mix(in srgb, Canvas 88%, CanvasText 12%); + --surface: color-mix(in srgb, Canvas 98%, transparent); + --surface-muted: color-mix(in srgb, Canvas 95%, CanvasText 5%); --border: color-mix(in srgb, CanvasText 12%, transparent); --text: CanvasText; --muted: color-mix(in srgb, CanvasText 62%, transparent); --accent: #111111; - --success: #159947; - --shadow: 0 18px 52px color-mix(in srgb, CanvasText 12%, transparent); font-family: - Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + Inter, "Segoe UI", sans-serif; } @@ -41,14 +38,15 @@ button { .shell { min-height: 100vh; - padding: 28px 30px 34px; + padding: 24px 28px 34px; } .hero { display: flex; justify-content: space-between; - gap: 28px; - padding-bottom: 26px; + gap: 22px; + align-items: flex-start; + padding-bottom: 24px; border-bottom: 1px solid var(--border); } @@ -56,52 +54,38 @@ button { min-width: 0; } -.eyebrow { - margin: 0 0 18px; - color: var(--success); - font-size: 13px; - font-weight: 600; - letter-spacing: 0; -} - .title-row { display: flex; - align-items: flex-start; - gap: 18px; + align-items: center; + gap: 16px; } .plugin-mark { - position: relative; - width: 78px; - height: 78px; - flex: 0 0 78px; + display: grid; + width: 64px; + height: 64px; + flex: 0 0 64px; + place-items: center; border: 1px solid var(--border); - border-radius: 14px; + border-radius: 10px; background: var(--surface); } -.plugin-mark::before { - position: absolute; - inset: 19px; - border: 3px solid var(--accent); - border-radius: 8px; - content: ""; -} - -.plugin-mark::after { - position: absolute; - inset: 28px; - border: 2px solid var(--accent); - border-radius: 4px; - content: ""; +.plugin-mark svg { + width: 34px; + height: 34px; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 1.8; } h1 { - margin: 4px 0 8px; - font-size: 28px; + margin: 0 0 6px; + font-size: 24px; font-weight: 650; letter-spacing: 0; - line-height: 1.08; + line-height: 1.1; } .subtitle { @@ -115,19 +99,18 @@ h1 { .hero-actions { display: flex; flex: 0 0 auto; - align-items: flex-start; + align-items: center; gap: 10px; - padding-top: 40px; } .primary-button, .quiet-button, -.back-button { +.back-link { border: 1px solid var(--border); - border-radius: 10px; + border-radius: 8px; cursor: pointer; - min-height: 42px; - padding: 0 18px; + min-height: 40px; + padding: 0 16px; transition: background-color 120ms ease, border-color 120ms ease, @@ -135,6 +118,9 @@ h1 { } .primary-button { + display: inline-flex; + align-items: center; + gap: 8px; border-color: var(--accent); background: var(--accent); color: white; @@ -142,24 +128,24 @@ h1 { } .quiet-button, -.back-button { +.back-link { background: var(--surface); } .primary-button:hover, .quiet-button:hover, -.back-button:hover, +.back-link:hover, .resource-row:hover { transform: translateY(-1px); } .content-section { - padding-top: 26px; + padding-top: 22px; } .content-section h2, .detail-header h2 { - margin: 0 0 14px; + margin: 0 0 10px; font-size: 15px; font-weight: 650; letter-spacing: 0; @@ -167,38 +153,58 @@ h1 { .resource-list, .meta-list { - overflow: hidden; - border: 1px solid var(--border); - border-radius: 14px; - background: var(--surface); + border-top: 1px solid var(--border); } .resource-row, -.meta-row, -.empty-row { +.meta-row { width: 100%; - min-height: 70px; + min-height: 58px; border: 0; border-bottom: 1px solid var(--border); background: transparent; - padding: 16px 18px; + padding: 12px 0; text-align: left; } -.resource-row:last-child, -.meta-row:last-child, -.empty-row:last-child { - border-bottom: 0; -} - .resource-row { display: grid; - grid-template-columns: minmax(160px, 0.8fr) minmax(220px, 1.2fr) auto auto; + grid-template-columns: 28px minmax(0, 1fr) auto; align-items: center; - gap: 18px; + gap: 14px; cursor: pointer; } +.resource-icon { + display: grid; + width: 24px; + height: 24px; + place-items: center; + color: var(--text); +} + +.resource-icon svg, +.button-icon, +.copy-button svg { + width: 18px; + height: 18px; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 1.8; +} + +.button-icon { + flex: 0 0 16px; +} + +.resource-copy, +.meta-copy { + display: grid; + min-width: 0; + gap: 3px; +} + .resource-name, .meta-label { min-width: 0; @@ -207,18 +213,18 @@ h1 { } .resource-summary, -.meta-value, -.empty-row { +.meta-value { min-width: 0; color: var(--muted); font-size: 14px; line-height: 1.4; } -.resource-path { - color: var(--muted); - font-size: 13px; - white-space: nowrap; +.resource-summary { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; } .resource-chevron { @@ -228,34 +234,61 @@ h1 { .meta-row { display: grid; - grid-template-columns: minmax(160px, 0.8fr) minmax(260px, 1.2fr); + grid-template-columns: minmax(0, 1fr) auto; align-items: center; - gap: 18px; + gap: 16px; } .meta-value { overflow-wrap: anywhere; } -.back-button { - margin-bottom: 22px; - min-height: 38px; - padding: 0 14px; +.copy-button { + display: grid; + width: 32px; + height: 32px; + place-items: center; + border: 0; + border-radius: 8px; + background: transparent; color: var(--muted); + cursor: pointer; } -.detail-card { - border: 1px solid var(--border); - border-radius: 16px; - background: var(--surface); - box-shadow: var(--shadow); - padding: 24px; +.copy-button:hover { + background: var(--surface-muted); + color: var(--text); +} + +.back-link { + display: inline-flex; + align-items: center; + gap: 6px; + min-height: auto; + margin-bottom: 18px; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + color: var(--muted); + font-size: 14px; + font-weight: 500; +} + +.back-link span:first-child { + font-size: 18px; + line-height: 1; +} + +.detail-sheet { + min-width: 0; } .detail-header { display: grid; gap: 8px; - margin-bottom: 22px; + padding-bottom: 20px; + border-bottom: 1px solid var(--border); } .detail-kicker { @@ -265,7 +298,7 @@ h1 { .detail-title { margin: 0; - font-size: 26px; + font-size: 24px; font-weight: 650; letter-spacing: 0; } @@ -277,20 +310,41 @@ h1 { line-height: 1.5; } -.detail-grid { +.source-row { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 10px; + align-items: center; + min-height: 42px; + margin-top: 8px; + border-top: 1px solid var(--border); +} + +.source-label { + color: var(--muted); + font-size: 13px; + font-weight: 600; +} + +.source-path { + min-width: 0; + overflow-wrap: anywhere; + color: var(--text); + font-size: 13px; + white-space: normal; +} + +.detail-sections { display: grid; - gap: 16px; } .detail-block { - border: 1px solid var(--border); - border-radius: 14px; - background: var(--surface-muted); - padding: 18px; + padding: 18px 0; + border-bottom: 1px solid var(--border); } .detail-block h3 { - margin: 0 0 10px; + margin: 0 0 8px; font-size: 14px; font-weight: 650; } @@ -327,16 +381,15 @@ h1 { } .hero-actions { - padding-top: 0; + width: 100%; + flex-wrap: wrap; } - .resource-row, - .meta-row { - grid-template-columns: 1fr; - gap: 8px; + .resource-row { + grid-template-columns: auto minmax(0, 1fr) auto; } - .resource-path { - white-space: normal; + .meta-row { + grid-template-columns: minmax(0, 1fr) auto; } } diff --git a/plugins/plugin-builder/mcp/server.js b/plugins/plugin-builder/mcp/server.js index 474a9c37..59bc7202 100644 --- a/plugins/plugin-builder/mcp/server.js +++ b/plugins/plugin-builder/mcp/server.js @@ -119,10 +119,39 @@ function summarizeText(text, maxLength = 180) { return `${normalized.slice(0, maxLength - 1).trim()}...`; } +function previewMarkdown(markdown) { + const withoutFences = markdown.replace(/```[\s\S]*?```/g, " "); + const withoutHeadings = withoutFences.replace(/^#{1,6}\s+/gm, ""); + return summarizeText(withoutHeadings, 280) || "This skill does not have a Markdown body."; +} + function relativeLabel(pluginPath, filePath) { return path.relative(pluginPath, filePath) || "."; } +function extractMarkdownHeadings(markdown) { + const headings = []; + let inFence = false; + + for (const line of markdown.split("\n")) { + const trimmed = line.trim(); + if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) { + inFence = !inFence; + continue; + } + if (inFence || !/^#{1,3}\s+/.test(line)) { + continue; + } + + const heading = line.replace(/^#{1,3}\s+/, "").trim(); + if (heading) { + headings.push(heading); + } + } + + return headings.slice(0, 6); +} + function readSkills(pluginPath, manifest) { const skillsRoot = path.resolve( pluginPath, @@ -137,16 +166,13 @@ function readSkills(pluginPath, manifest) { const parsed = parseFrontmatter(markdown); const fallbackName = path.basename(path.dirname(skillPath)); const title = parsed.fields.name || fallbackName; - const summary = + const summary = summarizeText( parsed.fields.description || - summarizeText(parsed.body) || - "No skill description provided."; - const headings = parsed.body - .split("\n") - .filter((line) => /^#{1,3}\s+/.test(line)) - .map((line) => line.replace(/^#{1,3}\s+/, "").trim()) - .filter(Boolean) - .slice(0, 6); + summarizeText(parsed.body) || + "No skill description provided.", + 150, + ); + const headings = extractMarkdownHeadings(parsed.body); return { id: fallbackName, @@ -155,7 +181,7 @@ function readSkills(pluginPath, manifest) { pathLabel: relativeLabel(pluginPath, skillPath), frontmatterSummary: parsed.fields.description || "No frontmatter summary provided.", headings, - preview: summarizeText(parsed.body, 280) || "This skill does not have a Markdown body.", + preview: previewMarkdown(parsed.body), }; }); } @@ -292,7 +318,6 @@ function readPluginSummary(args = {}) { marketplaces, localDetails: [ { label: "Plugin path", value: pluginPath }, - { label: "Manifest", value: manifestPath }, ], }; } From 517d8504a1d49f82ac1f5ff4702e20cdbe25afd5 Mon Sep 17 00:00:00 2001 From: Edward Frazer Date: Tue, 12 May 2026 16:24:31 -0700 Subject: [PATCH 05/10] feat(plugin-builder): rebuild artifact UI in React --- plugins/plugin-builder/app/.gitignore | 2 + plugins/plugin-builder/app/app.js | 415 --- .../app/dist/plugin-builder-widget.html | 137 + plugins/plugin-builder/app/index.html | 69 +- plugins/plugin-builder/app/package-lock.json | 2761 +++++++++++++++++ plugins/plugin-builder/app/package.json | 25 + plugins/plugin-builder/app/postcss.config.cjs | 6 + .../app/scripts/build-widget.mjs | 36 + .../plugin-builder/app/src/codex-theme.css | 60 + plugins/plugin-builder/app/src/main.tsx | 653 ++++ plugins/plugin-builder/app/src/styles.css | 18 + plugins/plugin-builder/app/styles.css | 395 --- .../plugin-builder/app/tailwind.config.cjs | 33 + plugins/plugin-builder/app/tsconfig.json | 13 + plugins/plugin-builder/app/vite.config.ts | 11 + plugins/plugin-builder/mcp/server.js | 15 +- 16 files changed, 3764 insertions(+), 885 deletions(-) create mode 100644 plugins/plugin-builder/app/.gitignore delete mode 100644 plugins/plugin-builder/app/app.js create mode 100644 plugins/plugin-builder/app/dist/plugin-builder-widget.html create mode 100644 plugins/plugin-builder/app/package-lock.json create mode 100644 plugins/plugin-builder/app/package.json create mode 100644 plugins/plugin-builder/app/postcss.config.cjs create mode 100644 plugins/plugin-builder/app/scripts/build-widget.mjs create mode 100644 plugins/plugin-builder/app/src/codex-theme.css create mode 100644 plugins/plugin-builder/app/src/main.tsx create mode 100644 plugins/plugin-builder/app/src/styles.css delete mode 100644 plugins/plugin-builder/app/styles.css create mode 100644 plugins/plugin-builder/app/tailwind.config.cjs create mode 100644 plugins/plugin-builder/app/tsconfig.json create mode 100644 plugins/plugin-builder/app/vite.config.ts diff --git a/plugins/plugin-builder/app/.gitignore b/plugins/plugin-builder/app/.gitignore new file mode 100644 index 00000000..acdce3a9 --- /dev/null +++ b/plugins/plugin-builder/app/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/raw/ diff --git a/plugins/plugin-builder/app/app.js b/plugins/plugin-builder/app/app.js deleted file mode 100644 index c4042505..00000000 --- a/plugins/plugin-builder/app/app.js +++ /dev/null @@ -1,415 +0,0 @@ -const summaryView = document.querySelector('[data-view="summary"]'); -const detailView = document.querySelector('[data-view="detail"]'); -const detailContent = document.querySelector("#detail-content"); -const backButton = document.querySelector("#back-button"); -const pluginTitle = document.querySelector("#plugin-title"); -const pluginDescription = document.querySelector("#plugin-description"); -const skillsList = document.querySelector("#skills-list"); -const appsList = document.querySelector("#apps-list"); -const mcpList = document.querySelector("#mcp-list"); -const skillsSection = document.querySelector("#skills-section"); -const appsSection = document.querySelector("#apps-section"); -const mcpSection = document.querySelector("#mcp-section"); -const marketplacesList = document.querySelector("#marketplaces-list"); -const localDetails = document.querySelector("#local-details"); -const viewButton = document.querySelector("#view-plugin"); -const shareButton = document.querySelector("#share-plugin"); - -const state = { - detail: null, - model: readModel(), -}; -let nextRpcId = 1; -const pendingRpc = new Map(); - -function decodePayload(raw) { - if (raw == null) { - return null; - } - if (typeof raw === "string") { - try { - return JSON.parse(raw); - } catch { - return null; - } - } - return typeof raw === "object" ? raw : null; -} - -function modelFromPayload(raw) { - const payload = decodePayload(raw); - if (payload == null) { - return null; - } - if (payload.plugin && Array.isArray(payload.skills)) { - return payload; - } - if (payload.structuredContent) { - return modelFromPayload(payload.structuredContent); - } - if (Array.isArray(payload.content)) { - for (const item of payload.content) { - const found = modelFromPayload(item?.text ?? item); - if (found) { - return found; - } - } - } - return null; -} - -function readModel() { - return ( - modelFromPayload(window.openai?.toolOutput) ?? - modelFromPayload(window.openai?.toolResponseMetadata) ?? { - plugin: { - displayName: "Plugin summary unavailable", - description: "Codex did not receive plugin summary data for this app.", - viewUrl: null, - shareUrl: null, - }, - skills: [], - apps: [], - mcpServers: [], - marketplaces: [], - localDetails: [], - } - ); -} - -function text(value) { - return value == null || value === "" ? "Not provided" : String(value); -} - -function clear(element) { - element.innerHTML = ""; -} - -function sendMcpAppMessage(message) { - if (window.parent === window) { - return; - } - window.parent.postMessage(message, "*"); -} - -function requestMcpApp(method, params) { - const id = nextRpcId; - nextRpcId += 1; - - const promise = new Promise((resolve, reject) => { - pendingRpc.set(id, { reject, resolve }); - window.setTimeout(() => { - const pending = pendingRpc.get(id); - if (!pending) { - return; - } - pendingRpc.delete(id); - reject(new Error(`${method} timed out.`)); - }, 5000); - }); - - sendMcpAppMessage({ - id, - jsonrpc: "2.0", - method, - params, - }); - return promise; -} - -function notifyMcpApp(method, params = {}) { - sendMcpAppMessage({ - jsonrpc: "2.0", - method, - params, - }); -} - -async function connectMcpApp() { - try { - await requestMcpApp("ui/initialize", { - appCapabilities: { - availableDisplayModes: ["inline", "fullscreen"], - }, - appInfo: { - name: "Plugin Builder", - version: "0.1.0", - }, - protocolVersion: "2026-01-26", - }); - notifyMcpApp("ui/notifications/initialized"); - await requestMcpApp("ui/request-display-mode", { - mode: "fullscreen", - }); - } catch { - // The inline summary remains useful if the host does not open a side panel. - } -} - -function openCodexLink(href) { - if (typeof href !== "string" || href.length === 0) { - return; - } - window.openai?.openExternal?.({ href }); -} - -function renderResourceList(element, items, kind) { - clear(element); - - for (const item of items) { - const button = document.createElement("button"); - button.type = "button"; - button.className = "resource-row"; - button.dataset.kind = kind; - button.dataset.id = item.id; - button.innerHTML = [ - ``, - ``, - ` ${escapeHtml(text(item.title))}`, - ` ${escapeHtml(text(item.summary))}`, - ``, - ``, - ].join(""); - button.addEventListener("click", () => showDetail(kind, item.id)); - element.append(button); - } -} - -function renderMetaList(element, items) { - clear(element); - if (items.length === 0) { - element.append(createEmptyRow("Nothing to show here yet.")); - return; - } - for (const item of items) { - const row = document.createElement("div"); - row.className = "meta-row"; - row.innerHTML = [ - ``, - ` ${escapeHtml(text(item.label))}`, - ` ${escapeHtml(text(item.value))}`, - ``, - ``, - ].join(""); - row.querySelector(".copy-button")?.addEventListener("click", () => { - copyText(text(item.value)); - }); - element.append(row); - } -} - -function renderSummary() { - state.model = readModel(); - const model = state.model; - pluginTitle.textContent = text(model.plugin.displayName); - pluginDescription.textContent = text(model.plugin.description); - viewButton.disabled = !model.plugin.viewUrl; - shareButton.disabled = !model.plugin.shareUrl; - renderResourceSection(skillsSection, skillsList, model.skills, "skills"); - renderResourceSection(appsSection, appsList, model.apps, "apps"); - renderResourceSection(mcpSection, mcpList, model.mcpServers, "mcpServers"); - renderMetaList(marketplacesList, model.marketplaces); - renderMetaList(localDetails, model.localDetails); -} - -function renderResourceSection(section, list, items, kind) { - section.hidden = items.length === 0; - if (items.length === 0) { - clear(list); - return; - } - renderResourceList(list, items, kind); -} - -function showDetail(kind, id) { - const detail = - kind === "skills" - ? state.model.skills.find((item) => item.id === id) - : kind === "apps" - ? state.model.apps.find((item) => item.id === id) - : state.model.mcpServers.find((item) => item.id === id); - - if (!detail) { - return; - } - - state.detail = { kind, item: detail }; - summaryView.hidden = true; - detailView.hidden = false; - detailContent.innerHTML = detailMarkup(state.detail); -} - -function hideDetail() { - state.detail = null; - detailView.hidden = true; - summaryView.hidden = false; -} - -function detailMarkup(detail) { - const item = detail.item; - const title = - detail.kind === "skills" - ? escapeHtml(text(item.title)) - : escapeHtml(text(item.title)); - const kicker = - detail.kind === "skills" - ? "Skill" - : detail.kind === "apps" - ? "Plugin app" - : "MCP server"; - const blocks = - detail.kind === "skills" - ? skillDetailBlocks(item) - : detail.kind === "apps" - ? appDetailBlocks(item) - : mcpDetailBlocks(item); - - return ` -
-
- ${escapeHtml(kicker)} -

${title}

-

${escapeHtml(text(item.summary))}

- ${detail.kind === "skills" ? skillSourceRow(item) : ""} -
-
${blocks}
-
- `; -} - -function skillDetailBlocks(item) { - const headings = - item.headings?.length > 0 - ? `
    ${item.headings - .map((heading) => `
  • ${escapeHtml(text(heading))}
  • `) - .join("")}
` - : "

No headings detected in this skill body.

"; - return [ - detailBlock("Summary", `

${escapeHtml(text(item.frontmatterSummary))}

`), - detailBlock("Sections", headings), - detailBlock("Preview", `

${escapeHtml(text(item.preview))}

`), - ].join(""); -} - -function appDetailBlocks(item) { - return [ - detailBlock("Definition", `${escapeHtml(text(item.pathLabel))}`), - detailBlock("App id", `

${escapeHtml(text(item.appId))}

`), - ].join(""); -} - -function mcpDetailBlocks(item) { - return [ - detailBlock("Definition", `${escapeHtml(text(item.pathLabel))}`), - detailBlock("Command", `${escapeHtml(text(item.commandLabel))}`), - ].join(""); -} - -function detailBlock(title, body) { - return ` -
-

${escapeHtml(title)}

- ${body} -
- `; -} - -function skillSourceRow(item) { - return ` -
- SKILL.md - ${escapeHtml(text(item.pathLabel))} -
- `; -} - -function resourceIcon(kind) { - if (kind === "skills") { - return ` - - - - - - - `; - } - if (kind === "apps") { - return ` - - - - - `; - } - return ` - - - - - - - - `; -} - -function copyIcon() { - return ` - - `; -} - -async function copyText(value) { - try { - await navigator.clipboard?.writeText?.(value); - } catch { - // Copy is a convenience affordance; the visible text remains selectable. - } -} - -function escapeHtml(value) { - return String(value) - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); -} - -viewButton.addEventListener("click", () => { - openCodexLink(state.model.plugin.viewUrl); -}); -shareButton.addEventListener("click", () => { - openCodexLink(state.model.plugin.shareUrl); -}); -backButton.addEventListener("click", hideDetail); - -window.addEventListener("message", (event) => { - const message = event.data; - if ( - !message || - message.jsonrpc !== "2.0" || - !pendingRpc.has(message.id) || - (!("result" in message) && !("error" in message)) - ) { - return; - } - - const pending = pendingRpc.get(message.id); - pendingRpc.delete(message.id); - if (message.error) { - pending.reject(new Error(message.error.message || "MCP app request failed.")); - return; - } - pending.resolve(message.result || {}); -}); - -window.addEventListener("openai:set_globals", renderSummary); - -renderSummary(); -connectMcpApp(); diff --git a/plugins/plugin-builder/app/dist/plugin-builder-widget.html b/plugins/plugin-builder/app/dist/plugin-builder-widget.html new file mode 100644 index 00000000..26014881 --- /dev/null +++ b/plugins/plugin-builder/app/dist/plugin-builder-widget.html @@ -0,0 +1,137 @@ + + + + + + Plugin Builder + + + + +
+ + diff --git a/plugins/plugin-builder/app/index.html b/plugins/plugin-builder/app/index.html index dae696c9..94ca73d4 100644 --- a/plugins/plugin-builder/app/index.html +++ b/plugins/plugin-builder/app/index.html @@ -4,74 +4,9 @@ Plugin Builder - -
-
-
-
-
- -
-

Loading plugin

-

-
-
-
-
- - -
-
- -
-

Skills

-
-
- -
-

Apps

-
-
- -
-

MCP servers

-
-
- -
-

Marketplaces

-
-
- -
-

Local details

-
-
-
- - -
- +
+ diff --git a/plugins/plugin-builder/app/package-lock.json b/plugins/plugin-builder/app/package-lock.json new file mode 100644 index 00000000..1160b7ea --- /dev/null +++ b/plugins/plugin-builder/app/package-lock.json @@ -0,0 +1,2761 @@ +{ + "name": "plugin-builder-app", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "plugin-builder-app", + "version": "0.1.0", + "dependencies": { + "lucide-react": "^0.511.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.5.2", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "vite": "^6.3.5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", + "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.349", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz", + "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.511.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz", + "integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.5" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/plugins/plugin-builder/app/package.json b/plugins/plugin-builder/app/package.json new file mode 100644 index 00000000..3e1a0c7d --- /dev/null +++ b/plugins/plugin-builder/app/package.json @@ -0,0 +1,25 @@ +{ + "name": "plugin-builder-app", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "vite build && node ./scripts/build-widget.mjs", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "lucide-react": "^0.511.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.5.2", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "vite": "^6.3.5" + } +} diff --git a/plugins/plugin-builder/app/postcss.config.cjs b/plugins/plugin-builder/app/postcss.config.cjs new file mode 100644 index 00000000..12a703d9 --- /dev/null +++ b/plugins/plugin-builder/app/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/plugins/plugin-builder/app/scripts/build-widget.mjs b/plugins/plugin-builder/app/scripts/build-widget.mjs new file mode 100644 index 00000000..3e5f0380 --- /dev/null +++ b/plugins/plugin-builder/app/scripts/build-widget.mjs @@ -0,0 +1,36 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const appRoot = path.resolve(scriptDir, ".."); +const rawDir = path.join(appRoot, "dist", "raw"); +const outputPath = path.join(appRoot, "dist", "plugin-builder-widget.html"); +const indexPath = path.join(rawDir, "index.html"); + +function readAsset(reference) { + const normalized = reference.replace(/^\.\//, ""); + return fs.readFileSync(path.join(rawDir, normalized), "utf8"); +} + +let html = fs.readFileSync(indexPath, "utf8"); +const stylesheetMatch = html.match( + /]*>/, +); +const scriptMatch = html.match( + /`); + +fs.mkdirSync(path.dirname(outputPath), { recursive: true }); +fs.writeFileSync(outputPath, html); diff --git a/plugins/plugin-builder/app/src/codex-theme.css b/plugins/plugin-builder/app/src/codex-theme.css new file mode 100644 index 00000000..d5775e70 --- /dev/null +++ b/plugins/plugin-builder/app/src/codex-theme.css @@ -0,0 +1,60 @@ +:root, +[data-theme="light"] { + color-scheme: light; + --codex-accent: var(--color-ring-primary, #339cff); + --codex-font-sans: + var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif); + --codex-font-mono: + var(--font-mono, ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, "Liberation Mono", monospace); + --codex-bg: var(--color-background-primary, #ffffff); + --codex-panel: var(--color-background-secondary, rgba(255, 255, 255, 0.98)); + --codex-raised: var(--color-background-tertiary, rgba(255, 255, 255, 0.99)); + --codex-control: var(--color-background-ghost, rgba(26, 28, 31, 0.05)); + --codex-text: var(--color-text-primary, #1a1c1f); + --codex-text-secondary: var(--color-text-secondary, rgba(26, 28, 31, 0.68)); + --codex-text-tertiary: var(--color-text-tertiary, rgba(26, 28, 31, 0.48)); + --codex-border: var(--color-border-primary, rgba(26, 28, 31, 0.08)); + --codex-border-heavy: var(--color-border-secondary, rgba(26, 28, 31, 0.14)); + --codex-radius-sm: var(--border-radius-sm, 6px); + --codex-radius-md: var(--border-radius-md, 8px); + --codex-radius-lg: var(--border-radius-lg, 10px); + --codex-radius-xl: var(--border-radius-xl, 12px); + --codex-shadow-hairline: var(--shadow-hairline, 0 0 0 0.5px rgba(0, 0, 0, 0.1)); +} + +[data-theme="dark"] { + color-scheme: dark; + --codex-bg: var(--color-background-primary, #181818); + --codex-panel: var(--color-background-secondary, rgba(24, 24, 24, 0.98)); + --codex-raised: var(--color-background-tertiary, rgba(32, 32, 32, 0.99)); + --codex-control: var(--color-background-ghost, rgba(255, 255, 255, 0.05)); + --codex-text: var(--color-text-primary, #ffffff); + --codex-text-secondary: var(--color-text-secondary, rgba(255, 255, 255, 0.7)); + --codex-text-tertiary: var(--color-text-tertiary, rgba(255, 255, 255, 0.5)); + --codex-border: var(--color-border-primary, rgba(255, 255, 255, 0.08)); + --codex-border-heavy: var(--color-border-secondary, rgba(255, 255, 255, 0.16)); +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + min-height: 100%; +} + +body { + margin: 0; + background: var(--codex-bg); + color: var(--codex-text); + font-family: var(--codex-font-sans); + font-size: 14px; + letter-spacing: 0; +} + +button { + font: inherit; + letter-spacing: 0; +} diff --git a/plugins/plugin-builder/app/src/main.tsx b/plugins/plugin-builder/app/src/main.tsx new file mode 100644 index 00000000..d4c89971 --- /dev/null +++ b/plugins/plugin-builder/app/src/main.tsx @@ -0,0 +1,653 @@ +import { + AppWindow, + ChevronLeft, + ChevronRight, + Copy, + FileText, + FolderClosed, + Package2, + Store, + Upload, + Workflow, +} from "lucide-react"; +import { + Fragment, + type ReactElement, + useEffect, + useMemo, + useState, +} from "react"; +import { createRoot } from "react-dom/client"; +import "./styles.css"; + +type PluginSummary = { + name?: string; + displayName?: string; + description?: string; + viewUrl?: string | null; + shareUrl?: string | null; +}; + +type SkillSummary = { + id: string; + title?: string; + summary?: string; + pathLabel?: string; + frontmatterSummary?: string; + headings?: string[]; + preview?: string; +}; + +type AppSummary = { + id: string; + title?: string; + summary?: string; + pathLabel?: string; + appId?: string; +}; + +type McpSummary = { + id: string; + title?: string; + summary?: string; + pathLabel?: string; + commandLabel?: string; +}; + +type MetaRow = { + label?: string; + value?: string; +}; + +type PluginBuilderModel = { + plugin: PluginSummary; + skills: SkillSummary[]; + apps: AppSummary[]; + mcpServers: McpSummary[]; + marketplaces: MetaRow[]; + localDetails: MetaRow[]; +}; + +type DetailSelection = + | { kind: "skill"; item: SkillSummary } + | { kind: "app"; item: AppSummary } + | { kind: "mcp"; item: McpSummary } + | null; + +type PendingRpc = { + resolve: (value: unknown) => void; + reject: (error: Error) => void; +}; + +type HostMessage = { + id?: number; + jsonrpc?: string; + result?: unknown; + error?: { message?: string }; +}; + +declare global { + interface Window { + openai?: { + openExternal?: (payload: { href: string }) => void; + toolOutput?: unknown; + toolResponseMetadata?: unknown; + }; + } +} + +const fallbackModel: PluginBuilderModel = { + plugin: { + displayName: "Loading plugin", + description: "Codex is preparing the local plugin summary.", + viewUrl: null, + shareUrl: null, + }, + skills: [], + apps: [], + mcpServers: [], + marketplaces: [], + localDetails: [], +}; + +const pendingRpc = new Map(); +let nextRpcId = 1; + +function safeText(value: unknown, fallback = "Not provided"): string { + if (value == null || value === "") { + return fallback; + } + return String(value); +} + +function decodePayload(raw: unknown): unknown { + if (raw == null) { + return null; + } + if (typeof raw === "string") { + try { + return JSON.parse(raw); + } catch { + return null; + } + } + return raw; +} + +function modelFromPayload(raw: unknown): PluginBuilderModel | null { + const payload = decodePayload(raw); + if (!payload || typeof payload !== "object") { + return null; + } + + const record = payload as Record; + if ( + record.plugin && + Array.isArray(record.skills) && + Array.isArray(record.apps) && + Array.isArray(record.mcpServers) + ) { + return record as PluginBuilderModel; + } + + if ("structuredContent" in record) { + return modelFromPayload(record.structuredContent); + } + + if (Array.isArray(record.content)) { + for (const item of record.content) { + if (item && typeof item === "object" && "text" in item) { + const nested = modelFromPayload((item as { text?: unknown }).text); + if (nested) { + return nested; + } + } + const nested = modelFromPayload(item); + if (nested) { + return nested; + } + } + } + + return null; +} + +function readModel(): PluginBuilderModel { + return ( + modelFromPayload(window.openai?.toolOutput) ?? + modelFromPayload(window.openai?.toolResponseMetadata) ?? + fallbackModel + ); +} + +function sendMcpAppMessage(message: unknown): void { + if (window.parent === window) { + return; + } + window.parent.postMessage(message, "*"); +} + +function requestMcpApp(method: string, params: unknown): Promise { + const id = nextRpcId; + nextRpcId += 1; + + const request = new Promise((resolve, reject) => { + pendingRpc.set(id, { reject, resolve }); + window.setTimeout(() => { + const pending = pendingRpc.get(id); + if (!pending) { + return; + } + pendingRpc.delete(id); + reject(new Error(`${method} timed out.`)); + }, 5000); + }); + + sendMcpAppMessage({ + id, + jsonrpc: "2.0", + method, + params, + }); + + return request; +} + +function notifyMcpApp(method: string, params: unknown = {}): void { + sendMcpAppMessage({ + jsonrpc: "2.0", + method, + params, + }); +} + +async function connectMcpApp(): Promise { + try { + await requestMcpApp("ui/initialize", { + appCapabilities: { + availableDisplayModes: ["inline", "fullscreen"], + }, + appInfo: { + name: "Plugin Builder", + version: "0.1.0", + }, + protocolVersion: "2026-01-26", + }); + notifyMcpApp("ui/notifications/initialized"); + await requestMcpApp("ui/request-display-mode", { + mode: "fullscreen", + }); + } catch { + // The summary remains useful even when the host keeps the app inline. + } +} + +function openCodexLink(href?: string | null): void { + if (!href) { + return; + } + window.openai?.openExternal?.({ href }); +} + +async function copyValue(value: string): Promise { + try { + await navigator.clipboard.writeText(value); + } catch { + // The value remains visible and selectable if clipboard access is unavailable. + } +} + +function RowIcon({ kind }: { kind: "skill" | "app" | "mcp" }): ReactElement { + if (kind === "skill") { + return