diff --git a/README.md b/README.md index 6169276..6c7d5cb 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,7 @@ 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 of them. - -**Claude Desktop (recommended for designers — one click):** +**Recommended — Claude Desktop (one click, no terminal):** 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. @@ -32,6 +30,11 @@ Pick whichever tool you're using. The Bridge Plugin (step 2) is the same for all No JSON editing, no terminal. Note: Claude.ai web is **not** supported — it cannot reach local MCP servers. +Using Cursor or Claude Code CLI? Open the section below for the right config. + +
+Other AI tools (Cursor, Claude Code CLI, manual config) + **Cursor (`.cursor/mcp.json`):** ```json @@ -56,22 +59,11 @@ Then paste the Tier 1 rules below into `.cursorrules` so Cursor prefers PluginOS Installs the MCP server registration and the `pluginos-figma` skill in one step. -**Manual (advanced — edit `claude_desktop_config.json` directly):** - -```json -{ - "mcpServers": { - "pluginos": { - "command": "npx", - "args": ["-y", "pluginos@latest"] - } - } -} -``` +**Manual (other MCP-capable clients):** Use the same `mcpServers` JSON block as Cursor above and paste the Tier 1 rules below into your project's custom instructions. -Then paste the Tier 1 rules below into your project's custom instructions. +
-**Tier 1 rules (Cursor / Claude Desktop):** +**Tier 1 rules (paste into Cursor / Claude Desktop / other clients):** ``` When working with Figma, always use PluginOS tools exclusively: diff --git a/eslint.config.js b/eslint.config.js index d4b3105..23ec785 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -14,6 +14,8 @@ export default tseslint.config( "**/*.cjs", "**/*.mjs", "!eslint.config.js", + ".claude/", + ".worktrees/", ], }, { diff --git a/packages/bridge-plugin/src/bootloader.html b/packages/bridge-plugin/src/bootloader.html index 8de2307..83a9e3d 100644 --- a/packages/bridge-plugin/src/bootloader.html +++ b/packages/bridge-plugin/src/bootloader.html @@ -2,12 +2,25 @@ @@ -130,21 +143,27 @@ }, 1000); } - var MCP_CONFIG_JSON = JSON.stringify({ mcpServers: { pluginos: { command: "npx", args: ["-y", "pluginos@0.4.2"] } } }, null, 2); + var MCP_CONFIG_JSON = JSON.stringify({ mcpServers: { pluginos: { command: "npx", args: ["-y", "pluginos@<%= MCP_VERSION %>"] } } }, 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"; - var DXT_URL = "https://github.com/LSDimi/pluginos/releases/latest/download/pluginos.dxt"; + var DXT_URL = "<%= DXT_URL %>"; + + function flashButton(btn, label) { + if (btn.classList.contains("copied")) return; + var orig = btn.textContent; + btn.classList.add("copied"); + btn.textContent = label; + setTimeout(function() { + btn.textContent = orig; + btn.classList.remove("copied"); + }, 2500); + } 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 onOk() { flashButton(btn, "Copied!"); } function onFail() { btn.textContent = "Copy failed"; } function fallback() { try { @@ -170,13 +189,7 @@ { pluginMessage: { type: "open-external", url: DXT_URL } }, "*" ); - btn.classList.add("copied"); - var orig = btn.textContent; - btn.textContent = "✓ Opening in browser…"; - setTimeout(function() { - btn.textContent = orig; - btn.classList.remove("copied"); - }, 2500); + flashButton(btn, "✓ Opening in browser…"); }); })(); wireCopy("btn-copy-install", INSTALL_COMMAND); diff --git a/packages/bridge-plugin/src/constants.json b/packages/bridge-plugin/src/constants.json new file mode 100644 index 0000000..0c9d38f --- /dev/null +++ b/packages/bridge-plugin/src/constants.json @@ -0,0 +1,3 @@ +{ + "DXT_URL": "https://github.com/LSDimi/pluginos/releases/latest/download/pluginos.dxt" +} diff --git a/packages/bridge-plugin/src/ui-entry.ts b/packages/bridge-plugin/src/ui-entry.ts index 9910b28..bd4a0e9 100644 --- a/packages/bridge-plugin/src/ui-entry.ts +++ b/packages/bridge-plugin/src/ui-entry.ts @@ -1,3 +1,7 @@ +import { DXT_URL } from "./constants.json"; + +declare const __MCP_VERSION__: string; + const TIER_1_RULES = `When working with Figma, always use PluginOS tools exclusively: - Use \`list_operations\` (pluginos) first to discover available Figma operations. - Use \`run_operation\` (pluginos) to execute them. @@ -11,7 +15,7 @@ const MCP_CONFIG_JSON = `{ "mcpServers": { "pluginos": { "command": "npx", - "args": ["-y", "pluginos@0.4.2"] + "args": ["-y", "pluginos@${__MCP_VERSION__}"] } } }`; @@ -19,8 +23,6 @@ const MCP_CONFIG_JSON = `{ const INSTALL_COMMAND = `/plugin marketplace add github:LSDimi/pluginos /plugin install pluginos`; -const DXT_URL = "https://github.com/LSDimi/pluginos/releases/latest/download/pluginos.dxt"; - const PORT_MIN = 9500; const PORT_MAX = 9510; const RECONNECT_DELAY = 3000; @@ -49,6 +51,7 @@ function showView(view: "setup" | "connected") { } function flashCopied(btn: HTMLButtonElement, label = "✓ Copied") { + if (btn.classList.contains("copied")) return; const original = btn.textContent; btn.classList.add("copied"); btn.textContent = label; diff --git a/packages/bridge-plugin/tsconfig.json b/packages/bridge-plugin/tsconfig.json index bafe3dc..d06c933 100644 --- a/packages/bridge-plugin/tsconfig.json +++ b/packages/bridge-plugin/tsconfig.json @@ -5,6 +5,7 @@ "rootDir": "src", "outDir": "dist", "module": "ESNext", + "resolveJsonModule": true, "lib": ["ES2017", "DOM"], "typeRoots": ["../../node_modules/@types", "../../node_modules/@figma"], "types": ["plugin-typings"] diff --git a/packages/bridge-plugin/webpack.config.js b/packages/bridge-plugin/webpack.config.js index fa88ec2..5e11bee 100644 --- a/packages/bridge-plugin/webpack.config.js +++ b/packages/bridge-plugin/webpack.config.js @@ -1,7 +1,11 @@ const path = require("path"); +const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlInlineScriptPlugin = require("html-inline-script-webpack-plugin"); +const { DXT_URL } = require("./src/constants.json"); +const { version: MCP_VERSION } = require("../mcp-server/package.json"); + const noModernSyntax = { arrowFunction: true, const: true, @@ -41,6 +45,9 @@ module.exports = (env, argv) => [ resolve: { extensions: [".ts", ".js"] }, devtool: false, plugins: [ + new webpack.DefinePlugin({ + __MCP_VERSION__: JSON.stringify(MCP_VERSION), + }), new HtmlWebpackPlugin({ template: "./src/ui.html", filename: "ui.html", @@ -60,6 +67,7 @@ module.exports = (env, argv) => [ template: "./src/bootloader.html", filename: "bootloader.html", inject: false, + templateParameters: { DXT_URL, MCP_VERSION }, }), ], mode: argv.mode || "production", diff --git a/packages/claude-plugin/skills/pluginos-figma/SKILL.md b/packages/claude-plugin/skills/pluginos-figma/SKILL.md index 026fb1b..9df2c55 100644 --- a/packages/claude-plugin/skills/pluginos-figma/SKILL.md +++ b/packages/claude-plugin/skills/pluginos-figma/SKILL.md @@ -15,17 +15,18 @@ You have `pluginos` MCP tools available AND the user is doing Figma work: design ## Tool routing (iron rule) -Always try `pluginos.*` first: +**Step 0: always call `pluginos.get_status` first** to confirm the bridge plugin is connected before any Figma work. If it returns disconnected, follow the Connection troubleshooting steps below — do NOT silently fall back to `mcp__Figma__*`. + +Then prefer `pluginos.*`: - `pluginos.list_operations` — discover what's available (only needed once per session; see quick-list below). - `pluginos.run_operation` — execute a registered operation. - `pluginos.execute_figma` — arbitrary plugin JS, only when no registered op fits. -Fall back to `mcp__Figma__*` tools ONLY when: +**Avoid `mcp__Figma__*` tools** (`get_design_context`, `get_variable_defs`, `get_screenshot`, etc.). They bypass the plugin and return raw, token-heavy node dumps; PluginOS returns pre-summarized, structured results at ~230 tokens/call. The only acceptable fallbacks to `mcp__Figma__*` are: -- PluginOS explicitly returns `no_operation_available`. -- The user requests Figma Code Connect mapping / `get_design_context`-style code generation. -- PluginOS is unreachable (see Connection troubleshooting below). +- PluginOS explicitly returns `no_operation_available` AND `execute_figma` cannot reasonably do the job. +- The user explicitly requests Figma Code Connect mapping or `get_design_context`-style code generation. Never mix: one-shot a Figma task with either PluginOS or Figma MCP, don't interleave. diff --git a/scripts/bump-lockstep.cjs b/scripts/bump-lockstep.cjs index ee5b273..64ebb31 100644 --- a/scripts/bump-lockstep.cjs +++ b/scripts/bump-lockstep.cjs @@ -2,9 +2,12 @@ /** * Postversion hook for packages/mcp-server. When `npm version` bumps the * MCP server, propagate the same version string to every file that pins the - * pluginos version: peer package.json manifests, the DXT manifest (both the - * top-level `version` and its `server.mcp_config.args` pin), and the - * hardcoded npx args inside the Figma plugin's UI sources. + * pluginos version: peer package.json manifests and the DXT manifest (both + * the top-level `version` and its `server.mcp_config.args` pin). + * + * Note: the Figma plugin UI (`bridge-plugin/src/ui-entry.ts` and + * `bootloader.html`) reads `mcp-server/package.json#version` at webpack + * build time, so no source-file rewrite is needed there. */ const fs = require("node:fs"); const path = require("node:path"); @@ -40,20 +43,3 @@ if (Array.isArray(dxt?.server?.mcp_config?.args)) { } 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}`); - } -}