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}`);
- }
-}