From b4aa95de1a4db99d2de526f15e4186d78cb8b7da Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 07:43:26 +0200 Subject: [PATCH 01/12] feat(mcp-server): add DXT manifest for Claude Desktop install Co-Authored-By: Claude Sonnet 4.6 --- packages/mcp-server/dxt/icon.png | Bin 0 -> 68 bytes packages/mcp-server/dxt/manifest.json | 33 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 packages/mcp-server/dxt/icon.png create mode 100644 packages/mcp-server/dxt/manifest.json diff --git a/packages/mcp-server/dxt/icon.png b/packages/mcp-server/dxt/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ec39d6c4f67c4a25ade6f81b7e338e31a861c5a3 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|+R!EZ7UAxIA4PLn`JZGcW==0.10.0", + "platforms": ["darwin", "win32", "linux"], + "runtimes": { + "node": ">=18" + } + } +} From 562de9664dad83c927678b65194d180c3090db86 Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 07:47:35 +0200 Subject: [PATCH 02/12] build(mcp-server): add build:dxt script producing pluginos.dxt Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 22 ++++++++++ packages/mcp-server/package.json | 5 ++- packages/mcp-server/scripts/build-dxt.mjs | 50 +++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 packages/mcp-server/scripts/build-dxt.mjs diff --git a/package-lock.json b/package-lock.json index d0682e5..48b64ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1279,6 +1279,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/adm-zip": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.8.tgz", + "integrity": "sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -2009,6 +2019,16 @@ "node": ">=0.4.0" } }, + "node_modules/adm-zip": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", + "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, "node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", @@ -7669,8 +7689,10 @@ }, "devDependencies": { "@pluginos/shared": "*", + "@types/adm-zip": "^0.5.8", "@types/ws": "^8.5.0", "@vitest/coverage-v8": "^2.1.9", + "adm-zip": "^0.5.17", "tsup": "^8.5.1", "tsx": "^4.19.0", "typescript": "^5.5.0", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 066941e..c68d349 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -13,10 +13,11 @@ ], "scripts": { "build": "tsup && cp ../bridge-plugin/dist/ui.html dist/ui.html", + "build:dxt": "node scripts/build-dxt.mjs", "dev": "tsx watch src/index.ts", "test": "vitest run", "test:coverage": "vitest run --coverage", - "prepublishOnly": "npm run build", + "prepublishOnly": "npm run build && npm run build:dxt", "postversion": "node ../../scripts/bump-lockstep.cjs", "release:patch": "npm version patch && npm publish --access public", "release:minor": "npm version minor && npm publish --access public" @@ -50,8 +51,10 @@ }, "devDependencies": { "@pluginos/shared": "*", + "@types/adm-zip": "^0.5.8", "@types/ws": "^8.5.0", "@vitest/coverage-v8": "^2.1.9", + "adm-zip": "^0.5.17", "tsup": "^8.5.1", "tsx": "^4.19.0", "typescript": "^5.5.0", diff --git a/packages/mcp-server/scripts/build-dxt.mjs b/packages/mcp-server/scripts/build-dxt.mjs new file mode 100644 index 0000000..12dec49 --- /dev/null +++ b/packages/mcp-server/scripts/build-dxt.mjs @@ -0,0 +1,50 @@ +#!/usr/bin/env node +import { readFileSync, mkdirSync, existsSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import AdmZip from "adm-zip"; + +const here = dirname(fileURLToPath(import.meta.url)); +const pkgRoot = join(here, ".."); +const dxtDir = join(pkgRoot, "dxt"); +const distDir = join(pkgRoot, "dist"); +const outFile = join(distDir, "pluginos.dxt"); + +const manifestPath = join(dxtDir, "manifest.json"); +const iconPath = join(dxtDir, "icon.png"); + +if (!existsSync(manifestPath)) { + console.error(`[build-dxt] missing ${manifestPath}`); + process.exit(1); +} + +const manifest = JSON.parse(readFileSync(manifestPath, "utf8")); +const pkg = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")); + +if (manifest.version !== pkg.version) { + console.error( + `[build-dxt] version mismatch: manifest=${manifest.version} package=${pkg.version}` + ); + process.exit(1); +} + +if (Array.isArray(manifest?.server?.mcp_config?.args)) { + const versioned = manifest.server.mcp_config.args.find((a) => + /^pluginos@/.test(a) + ); + if (versioned && versioned !== `pluginos@${pkg.version}`) { + console.error( + `[build-dxt] manifest mcp_config args pin ${versioned} but package is ${pkg.version}` + ); + process.exit(1); + } +} + +mkdirSync(distDir, { recursive: true }); + +const zip = new AdmZip(); +zip.addLocalFile(manifestPath); +if (existsSync(iconPath)) zip.addLocalFile(iconPath); +zip.writeZip(outFile); + +console.log(`[build-dxt] wrote ${outFile}`); From 51a74f5870ba490fdea06ad66af359dc690eb024 Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 07:51:48 +0200 Subject: [PATCH 03/12] test(mcp-server): cover build:dxt script output --- .../src/__tests__/build-dxt.test.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 packages/mcp-server/src/__tests__/build-dxt.test.ts diff --git a/packages/mcp-server/src/__tests__/build-dxt.test.ts b/packages/mcp-server/src/__tests__/build-dxt.test.ts new file mode 100644 index 0000000..3311e9b --- /dev/null +++ b/packages/mcp-server/src/__tests__/build-dxt.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { execSync } from "node:child_process"; +import { existsSync, readFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import AdmZip from "adm-zip"; + +const here = dirname(fileURLToPath(import.meta.url)); +const pkgRoot = join(here, "..", ".."); +const outFile = join(pkgRoot, "dist", "pluginos.dxt"); + +describe("build:dxt", () => { + beforeAll(() => { + execSync("node scripts/build-dxt.mjs", { cwd: pkgRoot, stdio: "inherit" }); + }); + + it("produces a pluginos.dxt file", () => { + expect(existsSync(outFile)).toBe(true); + }); + + it("includes manifest.json with the package version", () => { + const zip = new AdmZip(outFile); + const entry = zip.getEntry("manifest.json"); + expect(entry).not.toBeNull(); + const manifest = JSON.parse(entry!.getData().toString("utf8")); + const pkg = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")); + expect(manifest.version).toBe(pkg.version); + }); + + it("declares pluginos@ as the mcp command", () => { + const zip = new AdmZip(outFile); + const manifest = JSON.parse( + zip.getEntry("manifest.json")!.getData().toString("utf8") + ); + const pkg = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")); + expect(manifest.server.mcp_config.args).toContain(`pluginos@${pkg.version}`); + }); +}); From 9f49a3317675875900429d061fb714ed0d7d8358 Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 12:11:55 +0200 Subject: [PATCH 04/12] ci: build and upload pluginos.dxt on v* tags --- .github/workflows/release.yml | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e7aed2b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + dxt: + name: Build and upload pluginos.dxt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build shared + run: npm run build:shared + + - name: Build mcp-server + run: npm run build -w packages/mcp-server + + - name: Build DXT artifact + run: npm run build:dxt -w packages/mcp-server + + - name: Upload DXT to release + uses: softprops/action-gh-release@v2 + with: + files: packages/mcp-server/dist/pluginos.dxt + fail_on_unmatched_files: true From acf2f25c9bf32597ca7bd6c5138f6ab7bd02b06c Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 12:22:27 +0200 Subject: [PATCH 05/12] feat(bridge-plugin): lead setup with Claude Desktop DXT download card Co-Authored-By: Claude Sonnet 4.6 --- packages/bridge-plugin/src/ui.html | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/bridge-plugin/src/ui.html b/packages/bridge-plugin/src/ui.html index 4ced296..b1df989 100644 --- a/packages/bridge-plugin/src/ui.html +++ b/packages/bridge-plugin/src/ui.html @@ -29,6 +29,9 @@ .btn { font: inherit; font-size: 12px; font-weight: 500; padding: 6px 12px; border-radius: 8px; border: 1px solid var(--card-border); background: transparent; color: var(--text-primary); cursor: pointer; } .btn:hover { background: var(--cream); } .btn.copied { background: var(--accent); color: #FFF; border-color: var(--accent); } + .btn-primary { background: var(--text-primary); color: var(--card-bg); border-color: var(--text-primary); text-decoration: none; display: inline-block; } + .btn-primary:hover { opacity: 0.9; } + a.btn { text-decoration: none; } .cards-container { padding: 12px; } * { box-sizing: border-box; margin: 0; padding: 0; } .header { @@ -207,32 +210,31 @@
-
- -
Claude Code
-
Two commands: add the marketplace, then install the plugin. Copy and paste into Claude Code — both lines at once. This registers the PluginOS MCP server and the built-in skill that teaches Claude to use it.
+
+ +
Claude Desktop
+
One-click install. Download the PluginOS extension for Claude Desktop, double-click to install, then return here. No terminal, no JSON.
- + Download for Claude Desktop
Cursor
-
Two-step setup. Add PluginOS as an MCP server in Cursor settings, then paste the usage rules into .cursorrules so Cursor reaches for PluginOS first.
+
For engineers. Add PluginOS as an MCP server in Cursor settings, then paste the usage rules into .cursorrules.
-
- -
Claude chat
-
For Claude Desktop without Claude Code. Add PluginOS as an MCP server, then paste the usage rules into a Claude Project's custom instructions so Claude follows them in every message.
+
+ +
Claude Code (CLI)
+
For terminal users. Two commands — paste both at once into Claude Code to register the MCP server and skill.
- - +
From b71f7984adef6655d0747b70a8cf70f696461ab3 Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 12:27:59 +0200 Subject: [PATCH 06/12] feat(bridge-plugin): mirror DXT-first card order in bootloader fallback --- packages/bridge-plugin/src/bootloader.html | 120 ++++++++++++--------- 1 file changed, 68 insertions(+), 52 deletions(-) diff --git a/packages/bridge-plugin/src/bootloader.html b/packages/bridge-plugin/src/bootloader.html index 40b1497..50545dc 100644 --- a/packages/bridge-plugin/src/bootloader.html +++ b/packages/bridge-plugin/src/bootloader.html @@ -25,18 +25,17 @@ .status-sub { font-size: 10px; color: var(--figma-color-text-secondary, #999); margin-top: 1px; } .divider { height: 1px; background: var(--figma-color-border, #e5e5e5); } .setup-section { padding: 10px 12px 0; display: none; } - .action-card { - width: 100%; display: flex; align-items: center; gap: 10px; - padding: 8px 10px; margin-bottom: 7px; - background: var(--figma-color-bg-secondary, #f5f5f5); - border: 1px solid var(--figma-color-border, #e5e5e5); - border-radius: 6px; cursor: pointer; text-align: left; - font-family: inherit; font-size: 11px; transition: background 0.15s; - } - .action-card:hover { background: var(--figma-color-bg-hover, #ebebeb); } - .action-icon { font-size: 15px; flex-shrink: 0; width: 18px; text-align: center; } - .action-title { font-size: 11px; font-weight: 600; color: var(--figma-color-text); } - .action-desc { font-size: 10px; color: var(--figma-color-text-secondary, #999); margin-top: 1px; } + .setup-cards { display: flex; flex-direction: column; gap: 8px; } + .card { padding: 10px 12px; background: var(--figma-color-bg-secondary, #f5f5f5); border: 1px solid var(--figma-color-border, #e5e5e5); border-radius: 6px; } + .card-title { font-size: 11px; font-weight: 600; color: var(--figma-color-text); margin-bottom: 4px; } + .card-desc { font-size: 10px; color: var(--figma-color-text-secondary, #999); margin-bottom: 8px; } + .card-actions { display: flex; flex-wrap: wrap; gap: 6px; } + .btn { font-family: inherit; font-size: 10px; padding: 4px 10px; border: 1px solid var(--figma-color-border, #e5e5e5); border-radius: 4px; background: var(--figma-color-bg, #fff); color: var(--figma-color-text, #111); cursor: pointer; transition: background 0.15s; } + .btn:hover { background: var(--figma-color-bg-hover, #ebebeb); } + .btn.copied { color: #1bc47d; border-color: #1bc47d; } + .btn-primary { background: var(--figma-color-text, #111); color: var(--figma-color-bg, #fff); border-color: var(--figma-color-text, #111); text-decoration: none; display: inline-block; } + .btn-primary:hover { opacity: 0.9; } + a.btn { text-decoration: none; } .progress-section { padding: 8px 12px 10px; display: none; } .progress-bar { height: 2px; background: var(--figma-color-border, #e5e5e5); border-radius: 1px; overflow: hidden; } .progress-fill { height: 100%; width: 0%; background: #f5a623; border-radius: 1px; } @@ -59,20 +58,32 @@
- - +
- +
+ +
+
Claude Code (CLI)
+
For terminal users. Two commands — paste both at once into Claude Code to register the MCP server and skill.
+
+ +
+
+
@@ -118,36 +129,41 @@ }, 1000); } - var PROMPT_TEXT = "Start the PluginOS MCP server if it’s not already running: `npx pluginos`\n\nThen find this project’s agent instructions file (CLAUDE.md, AGENTS.md, .cursorrules, or equivalent — create one if none exists) and add the following rules:\n\n- For ALL Figma operations, use PluginOS tools exclusively (pluginos MCP)\n- Call list_operations first to discover what operations are available\n- Use run_operation to execute them\n- Use execute_figma only for one-off custom logic not covered by built-in operations\n- Never use mcp__Figma__* tools — they bypass the plugin and return raw, token-heavy data (~10x token cost)\n- If PluginOS returns “No plugin connected”, ask the user to open the PluginOS Bridge plugin in Figma first\n\nConfirm once the server is running and the instruction file has been updated."; - var CONFIG_TEXT = JSON.stringify({ mcpServers: { pluginos: { command: "npx", args: ["pluginos@latest"] } } }, null, 2); + var MCP_CONFIG_JSON = JSON.stringify({ mcpServers: { pluginos: { command: "npx", args: ["pluginos@latest"] } } }, null, 2); + var TIER_1_RULES = "When working with Figma, always use PluginOS tools exclusively.\n- Use list_operations (pluginos) first to discover available Figma operations.\n- Use run_operation (pluginos) to execute them.\n- Use execute_figma (pluginos) only for one-off custom logic not covered by built-in ops."; + var INSTALL_COMMAND = "claude mcp add pluginos -- npx pluginos@latest\nclaude \"$(curl -fsSL https://raw.githubusercontent.com/LSDimi/pluginos/main/packages/claude-plugin/skills/pluginos-figma/SKILL.md)\""; - function doCopy(text, descId, revertText) { - var d = el(descId); - function onOk() { - d.textContent = "✓ Copied!"; - setTimeout(function() { d.textContent = revertText; }, 2500); - } - function onFail() { d.textContent = "Copy failed — please try again"; } - function fallback() { - try { - var ta = document.createElement("textarea"); - ta.value = text; - ta.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;"; - document.body.appendChild(ta); - ta.focus(); ta.select(); - document.execCommand("copy") ? onOk() : onFail(); - document.body.removeChild(ta); - } catch(e) { onFail(); } - } - if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(text).then(onOk).catch(fallback); - } else { fallback(); } - } - function copyConfig() { - doCopy(CONFIG_TEXT, "copy-desc", "Paste in your IDE’s MCP server list or Claude Connectors"); + function wireCopy(btnId, text) { + var btn = document.getElementById(btnId); + if (!btn) return; + btn.addEventListener("click", function() { + function onOk() { + btn.classList.add("copied"); + var orig = btn.textContent; + btn.textContent = "Copied!"; + setTimeout(function() { btn.textContent = orig; btn.classList.remove("copied"); }, 2500); + } + function onFail() { btn.textContent = "Copy failed"; } + function fallback() { + try { + var ta = document.createElement("textarea"); + ta.value = text; + ta.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;"; + document.body.appendChild(ta); + ta.focus(); ta.select(); + document.execCommand("copy") ? onOk() : onFail(); + document.body.removeChild(ta); + } catch(e) { onFail(); } + } + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(onOk).catch(fallback); + } else { fallback(); } + }); } - function copyPrompt() { - doCopy(PROMPT_TEXT, "prompt-desc", "Click here to copy the prompt and paste in your project’s AI chat to configure PluginOS"); + wireCopy("btn-copy-install", INSTALL_COMMAND); + wireCopy("btn-copy-mcp-cursor", MCP_CONFIG_JSON); + wireCopy("btn-copy-rules-cursor", TIER_1_RULES); + } function tryBootload() { From c7fbf2ec82044556f06ccec23979e373c873b8e2 Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 12:28:41 +0200 Subject: [PATCH 07/12] fix(bridge-plugin): align bootloader INSTALL_COMMAND and MCP config with ui-entry.ts --- packages/bridge-plugin/src/bootloader.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/bridge-plugin/src/bootloader.html b/packages/bridge-plugin/src/bootloader.html index 50545dc..5232744 100644 --- a/packages/bridge-plugin/src/bootloader.html +++ b/packages/bridge-plugin/src/bootloader.html @@ -129,9 +129,9 @@ }, 1000); } - var MCP_CONFIG_JSON = JSON.stringify({ mcpServers: { pluginos: { command: "npx", args: ["pluginos@latest"] } } }, null, 2); - var TIER_1_RULES = "When working with Figma, always use PluginOS tools exclusively.\n- Use list_operations (pluginos) first to discover available Figma operations.\n- Use run_operation (pluginos) to execute them.\n- Use execute_figma (pluginos) only for one-off custom logic not covered by built-in ops."; - var INSTALL_COMMAND = "claude mcp add pluginos -- npx pluginos@latest\nclaude \"$(curl -fsSL https://raw.githubusercontent.com/LSDimi/pluginos/main/packages/claude-plugin/skills/pluginos-figma/SKILL.md)\""; + var MCP_CONFIG_JSON = JSON.stringify({ mcpServers: { pluginos: { command: "npx", args: ["pluginos@0.4.0"] } } }, null, 2); + var TIER_1_RULES = "When working with Figma, always use PluginOS tools exclusively:\n- Use `list_operations` (pluginos) first to discover available Figma operations.\n- Use `run_operation` (pluginos) to execute them.\n- Use `execute_figma` (pluginos) only for one-off custom logic not covered by built-in ops.\n- Do NOT use `mcp__Figma__*` tools — they bypass the plugin and return raw, token-heavy data. PluginOS returns pre-summarized, structured results at ~230 tokens/call.\n- If PluginOS returns \"No plugin connected\", open the PluginOS Bridge plugin in Figma before retrying.\n\nAudit/lint/check operations default to `scope: \"selection\"`. Pass `scope: \"page\"` explicitly (and `confirm: true` for pages over 500 nodes) to scan the whole page. Responses carry `_hint` and `_next_hints` fields — respect them when deciding what to do next."; + var INSTALL_COMMAND = "/plugin marketplace add github:LSDimi/pluginos\n/plugin install pluginos"; function wireCopy(btnId, text) { var btn = document.getElementById(btnId); From f364e788ceb3c08e6ebd7ad5deb916bf3d5f89a7 Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 12:42:08 +0200 Subject: [PATCH 08/12] refactor(bridge-plugin): drop Claude Chat install wiring (out of scope) - Remove btn-copy-mcp-chat and btn-copy-rules-chat listeners from ui-entry.ts - Ignore .mjs files in eslint (matching .js/.cjs convention) so build-dxt.mjs lints clean --- eslint.config.js | 9 ++++++++- packages/bridge-plugin/src/ui-entry.ts | 10 ---------- packages/mcp-server/scripts/build-dxt.mjs | 4 +--- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index f867a79..d4b3105 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -7,7 +7,14 @@ export default tseslint.config( ...tseslint.configs.recommended, eslintConfigPrettier, { - ignores: ["**/dist/", "**/node_modules/", "**/*.js", "**/*.cjs", "!eslint.config.js"], + ignores: [ + "**/dist/", + "**/node_modules/", + "**/*.js", + "**/*.cjs", + "**/*.mjs", + "!eslint.config.js", + ], }, { files: ["**/*.ts"], diff --git a/packages/bridge-plugin/src/ui-entry.ts b/packages/bridge-plugin/src/ui-entry.ts index 60faab2..4a3e621 100644 --- a/packages/bridge-plugin/src/ui-entry.ts +++ b/packages/bridge-plugin/src/ui-entry.ts @@ -285,16 +285,6 @@ document .addEventListener("click", (e) => copyToClipboard(TIER_1_RULES, e.currentTarget as HTMLButtonElement) ); -document - .getElementById("btn-copy-mcp-chat")! - .addEventListener("click", (e) => - copyToClipboard(MCP_CONFIG_JSON, e.currentTarget as HTMLButtonElement) - ); -document - .getElementById("btn-copy-rules-chat")! - .addEventListener("click", (e) => - copyToClipboard(TIER_1_RULES, e.currentTarget as HTMLButtonElement) - ); // Forward messages from code.js (plugin sandbox) to WebSocket window.onmessage = (event: MessageEvent) => { diff --git a/packages/mcp-server/scripts/build-dxt.mjs b/packages/mcp-server/scripts/build-dxt.mjs index 12dec49..c530639 100644 --- a/packages/mcp-server/scripts/build-dxt.mjs +++ b/packages/mcp-server/scripts/build-dxt.mjs @@ -29,9 +29,7 @@ if (manifest.version !== pkg.version) { } if (Array.isArray(manifest?.server?.mcp_config?.args)) { - const versioned = manifest.server.mcp_config.args.find((a) => - /^pluginos@/.test(a) - ); + const versioned = manifest.server.mcp_config.args.find((a) => /^pluginos@/.test(a)); if (versioned && versioned !== `pluginos@${pkg.version}`) { console.error( `[build-dxt] manifest mcp_config args pin ${versioned} but package is ${pkg.version}` From 56b00f7ba0fc6d2ab55b5cb4300841c94fc06fe8 Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 12:44:30 +0200 Subject: [PATCH 09/12] docs: document DXT install as primary path for Claude Desktop --- README.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4c8b15a..29b4af2 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,15 @@ PluginOS takes a fundamentally different approach: ### 1. Install for your agent -Pick whichever tool you're using. The Bridge Plugin (step 2) is the same for all three. +Pick whichever tool you're using. The Bridge Plugin (step 2) is the same for all of them. -**Claude Code (recommended — one command):** +**Claude Desktop (recommended for designers — one click):** -```bash -/plugin marketplace add github:LSDimi/pluginos -/plugin install pluginos -``` +1. Download [`pluginos.dxt`](https://github.com/LSDimi/pluginos/releases/latest/download/pluginos.dxt) from the latest GitHub Release. +2. Double-click the downloaded file. Claude Desktop opens an install dialog. +3. Confirm. PluginOS appears in Claude Desktop's connector list. -This installs the MCP server registration and the `pluginos-figma` skill in one step. +No JSON editing, no terminal. Note: Claude.ai web is **not** supported — it cannot reach local MCP servers. **Cursor (`.cursor/mcp.json`):** @@ -48,9 +47,16 @@ This installs the MCP server registration and the `pluginos-figma` skill in one Then paste the Tier 1 rules below into `.cursorrules` so Cursor prefers PluginOS over the generic Figma MCP. -**Claude chat / Desktop (no Code):** +**Claude Code (CLI — engineers):** + +```bash +/plugin marketplace add github:LSDimi/pluginos +/plugin install pluginos +``` + +Installs the MCP server registration and the `pluginos-figma` skill in one step. -Add the same MCP server block to Claude Desktop's `mcp.json`: +**Manual (advanced — edit `claude_desktop_config.json` directly):** ```json { From 771ecd889b967f6ec292a8e5cc16afdfecef8fde Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 13:38:55 +0200 Subject: [PATCH 10/12] style: prettier autoformat on build-dxt test --- packages/mcp-server/src/__tests__/build-dxt.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/mcp-server/src/__tests__/build-dxt.test.ts b/packages/mcp-server/src/__tests__/build-dxt.test.ts index 3311e9b..3fe2394 100644 --- a/packages/mcp-server/src/__tests__/build-dxt.test.ts +++ b/packages/mcp-server/src/__tests__/build-dxt.test.ts @@ -29,9 +29,7 @@ describe("build:dxt", () => { it("declares pluginos@ as the mcp command", () => { const zip = new AdmZip(outFile); - const manifest = JSON.parse( - zip.getEntry("manifest.json")!.getData().toString("utf8") - ); + const manifest = JSON.parse(zip.getEntry("manifest.json")!.getData().toString("utf8")); const pkg = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")); expect(manifest.server.mcp_config.args).toContain(`pluginos@${pkg.version}`); }); From 45dbc4709d766f65ea869d4ee54069277fc93818 Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 14:05:04 +0200 Subject: [PATCH 11/12] fix(bridge-plugin): address PR #6 review comments - bootloader.html: remove stray closing brace that was leftover from the removed copyPrompt function (was a JS syntax error flagged critical) - bootloader.html: use download attribute instead of target="_blank" on the Claude Desktop download link, matching ui.html behavior - Add -y flag to npx args in MCP_CONFIG_JSON (bootloader.html, ui-entry.ts, README) so non-interactive MCP hosts (Cursor, Claude Desktop) skip the install prompt, matching the DXT manifest's mcp_config --- README.md | 4 ++-- packages/bridge-plugin/src/bootloader.html | 8 +++----- packages/bridge-plugin/src/ui-entry.ts | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 29b4af2..6169276 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ No JSON editing, no terminal. Note: Claude.ai web is **not** supported — it ca "mcpServers": { "pluginos": { "command": "npx", - "args": ["pluginos@latest"] + "args": ["-y", "pluginos@latest"] } } } @@ -63,7 +63,7 @@ Installs the MCP server registration and the `pluginos-figma` skill in one step. "mcpServers": { "pluginos": { "command": "npx", - "args": ["pluginos@latest"] + "args": ["-y", "pluginos@latest"] } } } diff --git a/packages/bridge-plugin/src/bootloader.html b/packages/bridge-plugin/src/bootloader.html index 5232744..e79bf51 100644 --- a/packages/bridge-plugin/src/bootloader.html +++ b/packages/bridge-plugin/src/bootloader.html @@ -63,7 +63,7 @@
Claude Desktop
One-click install. Download the PluginOS extension for Claude Desktop, double-click to install, then return here. No terminal, no JSON.
@@ -129,7 +129,7 @@ }, 1000); } - var MCP_CONFIG_JSON = JSON.stringify({ mcpServers: { pluginos: { command: "npx", args: ["pluginos@0.4.0"] } } }, null, 2); + var MCP_CONFIG_JSON = JSON.stringify({ mcpServers: { pluginos: { command: "npx", args: ["-y", "pluginos@0.4.0"] } } }, null, 2); var TIER_1_RULES = "When working with Figma, always use PluginOS tools exclusively:\n- Use `list_operations` (pluginos) first to discover available Figma operations.\n- Use `run_operation` (pluginos) to execute them.\n- Use `execute_figma` (pluginos) only for one-off custom logic not covered by built-in ops.\n- Do NOT use `mcp__Figma__*` tools — they bypass the plugin and return raw, token-heavy data. PluginOS returns pre-summarized, structured results at ~230 tokens/call.\n- If PluginOS returns \"No plugin connected\", open the PluginOS Bridge plugin in Figma before retrying.\n\nAudit/lint/check operations default to `scope: \"selection\"`. Pass `scope: \"page\"` explicitly (and `confirm: true` for pages over 500 nodes) to scan the whole page. Responses carry `_hint` and `_next_hints` fields — respect them when deciding what to do next."; var INSTALL_COMMAND = "/plugin marketplace add github:LSDimi/pluginos\n/plugin install pluginos"; @@ -164,9 +164,7 @@ wireCopy("btn-copy-mcp-cursor", MCP_CONFIG_JSON); wireCopy("btn-copy-rules-cursor", TIER_1_RULES); - } - - function tryBootload() { + function tryBootload() { setDot("dot-yellow"); setStatus("Connecting…", "Searching for PluginOS server"); showSection("setup-section", false); diff --git a/packages/bridge-plugin/src/ui-entry.ts b/packages/bridge-plugin/src/ui-entry.ts index 4a3e621..bd9fb5e 100644 --- a/packages/bridge-plugin/src/ui-entry.ts +++ b/packages/bridge-plugin/src/ui-entry.ts @@ -11,7 +11,7 @@ const MCP_CONFIG_JSON = `{ "mcpServers": { "pluginos": { "command": "npx", - "args": ["pluginos@0.4.0"] + "args": ["-y", "pluginos@0.4.0"] } } }`; From 96162ce3af77de20db299f18ef943fad33c4c2f0 Mon Sep 17 00:00:00 2001 From: Dimitrios Arapis Date: Thu, 23 Apr 2026 14:21:37 +0200 Subject: [PATCH 12/12] chore: real DXT icon + extend version lockstep for DXT artifact - Replace placeholder 128x128 transparent icon with Logo PluginOS.png (176x176) - bump-lockstep.cjs now also bumps dxt/manifest.json (version field + the pluginos@ pin inside server.mcp_config.args) and patches the version strings in ui-entry.ts and bootloader.html so `npm version` keeps every version reference in sync automatically - check-version-lockstep.cjs now includes dxt/manifest.json, so CI fails if the DXT manifest ever drifts from the package version --- packages/mcp-server/dxt/icon.png | Bin 68 -> 40187 bytes scripts/bump-lockstep.cjs | 41 ++++++++++++++++++++++++++--- scripts/check-version-lockstep.cjs | 7 ++++- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/packages/mcp-server/dxt/icon.png b/packages/mcp-server/dxt/icon.png index ec39d6c4f67c4a25ade6f81b7e338e31a861c5a3..999929159c86e564672d3feab32e4b9f97fc7506 100644 GIT binary patch literal 40187 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 LNkvXXu0mjf%S)q+ literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|+R!EZ7UAxIA4PLn`JZGcW= + /^pluginos@/.test(a) ? `pluginos@${newVersion}` : a + ); +} +fs.writeFileSync(dxtAbs, JSON.stringify(dxt, null, 2) + "\n"); +console.log(`Bumped ${dxtManifestRel} → ${newVersion}`); + +// Sources that hardcode `pluginos@` in copy-paste MCP config snippets. +// These use a regex swap so formatting is preserved. +const sourceTargets = [ + "packages/bridge-plugin/src/ui-entry.ts", + "packages/bridge-plugin/src/bootloader.html", +]; + +for (const rel of sourceTargets) { + const abs = path.join(repoRoot, rel); + const before = fs.readFileSync(abs, "utf8"); + const after = before.replace(/pluginos@\d+\.\d+\.\d+/g, `pluginos@${newVersion}`); + if (after !== before) { + fs.writeFileSync(abs, after); + console.log(`Bumped ${rel} → pluginos@${newVersion}`); + } +} diff --git a/scripts/check-version-lockstep.cjs b/scripts/check-version-lockstep.cjs index 4a3eeab..6563354 100644 --- a/scripts/check-version-lockstep.cjs +++ b/scripts/check-version-lockstep.cjs @@ -30,6 +30,11 @@ const targets = [ path: "packages/claude-plugin/.claude-plugin/plugin.json", field: "version", }, + { + label: "packages/mcp-server/dxt/manifest.json", + path: "packages/mcp-server/dxt/manifest.json", + field: "version", + }, ]; const readings = targets.map((t) => { @@ -52,4 +57,4 @@ if (versions.size !== 1) { } const [version] = readings.map((r) => r.version); -console.log(`[check-version-lockstep] ✓ All 5 manifests share version ${version}`); +console.log(`[check-version-lockstep] ✓ All ${readings.length} manifests share version ${version}`);