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 diff --git a/README.md b/README.md index 4c8b15a..6169276 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`):** @@ -40,7 +39,7 @@ This installs the MCP server registration and the `pluginos-figma` skill in one "mcpServers": { "pluginos": { "command": "npx", - "args": ["pluginos@latest"] + "args": ["-y", "pluginos@latest"] } } } @@ -48,16 +47,23 @@ 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 { "mcpServers": { "pluginos": { "command": "npx", - "args": ["pluginos@latest"] + "args": ["-y", "pluginos@latest"] } } } 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/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/bridge-plugin/src/bootloader.html b/packages/bridge-plugin/src/bootloader.html index 40b1497..e79bf51 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,39 +129,42 @@ }, 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: ["-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"; - 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 copyPrompt() { - doCopy(PROMPT_TEXT, "prompt-desc", "Click here to copy the prompt and paste in your project’s AI chat to configure PluginOS"); + 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(); } + }); } + wireCopy("btn-copy-install", INSTALL_COMMAND); + 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 60faab2..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"] } } }`; @@ -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/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.
- - +
diff --git a/packages/mcp-server/dxt/icon.png b/packages/mcp-server/dxt/icon.png new file mode 100644 index 0000000..9999291 Binary files /dev/null and b/packages/mcp-server/dxt/icon.png differ diff --git a/packages/mcp-server/dxt/manifest.json b/packages/mcp-server/dxt/manifest.json new file mode 100644 index 0000000..b8c3a49 --- /dev/null +++ b/packages/mcp-server/dxt/manifest.json @@ -0,0 +1,33 @@ +{ + "dxt_version": "0.1", + "name": "pluginos", + "display_name": "PluginOS", + "version": "0.4.0", + "description": "Agent-native Figma operations — run 28 Figma operations from Claude Desktop at ~230 tokens per call.", + "long_description": "PluginOS bridges Claude Desktop with Figma through a local MCP server and a companion Figma plugin. After installing this extension, open the PluginOS Bridge plugin inside Figma and Claude Desktop will be able to read selections, run audits, apply variables, and execute design operations on your behalf.", + "author": { + "name": "Dimitrios Arapis", + "url": "https://github.com/LSDimi/pluginos" + }, + "homepage": "https://github.com/LSDimi/pluginos", + "documentation": "https://github.com/LSDimi/pluginos#readme", + "support": "https://github.com/LSDimi/pluginos/issues", + "icon": "icon.png", + "license": "MIT", + "keywords": ["figma", "design", "mcp"], + "server": { + "type": "node", + "entry_point": "npx", + "mcp_config": { + "command": "npx", + "args": ["-y", "pluginos@0.4.0"] + } + }, + "compatibility": { + "claude_desktop": ">=0.10.0", + "platforms": ["darwin", "win32", "linux"], + "runtimes": { + "node": ">=18" + } + } +} 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..c530639 --- /dev/null +++ b/packages/mcp-server/scripts/build-dxt.mjs @@ -0,0 +1,48 @@ +#!/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}`); 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..3fe2394 --- /dev/null +++ b/packages/mcp-server/src/__tests__/build-dxt.test.ts @@ -0,0 +1,36 @@ +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}`); + }); +}); diff --git a/scripts/bump-lockstep.cjs b/scripts/bump-lockstep.cjs index 884b977..ee5b273 100644 --- a/scripts/bump-lockstep.cjs +++ b/scripts/bump-lockstep.cjs @@ -1,8 +1,10 @@ #!/usr/bin/env node /** * Postversion hook for packages/mcp-server. When `npm version` bumps the - * MCP server, propagate the same version string to the four peer manifests - * so the version-lockstep check stays green. + * 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. */ const fs = require("node:fs"); const path = require("node:path"); @@ -10,17 +12,48 @@ const path = require("node:path"); const repoRoot = path.resolve(__dirname, ".."); const newVersion = require(path.join(repoRoot, "packages/mcp-server/package.json")).version; -const targets = [ +// Peer manifests: set top-level version field. +const packageTargets = [ "packages/claude-plugin/package.json", "packages/claude-plugin/.claude-plugin/plugin.json", "packages/shared/package.json", "packages/bridge-plugin/package.json", ]; -for (const rel of targets) { +for (const rel of packageTargets) { const abs = path.join(repoRoot, rel); const j = JSON.parse(fs.readFileSync(abs, "utf8")); j.version = newVersion; fs.writeFileSync(abs, JSON.stringify(j, null, 2) + "\n"); console.log(`Bumped ${rel} → ${newVersion}`); } + +// DXT manifest: bump both `version` and the version pinned in mcp_config.args. +const dxtManifestRel = "packages/mcp-server/dxt/manifest.json"; +const dxtAbs = path.join(repoRoot, dxtManifestRel); +const dxt = JSON.parse(fs.readFileSync(dxtAbs, "utf8")); +dxt.version = newVersion; +if (Array.isArray(dxt?.server?.mcp_config?.args)) { + dxt.server.mcp_config.args = dxt.server.mcp_config.args.map((a) => + /^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}`);