From 9385aba2494c97714545500d332b2beb0a8f6cf2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 04:33:27 +0000 Subject: [PATCH 1/4] chore(deps): update dependency typescript to v7 --- package-lock.json | 375 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 369 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 154f591..dc005b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "devDependencies": { "@types/node": "^24.0.0", "agent-bundle": "https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@ab5ae66e1d9c2829a092640c586e2b3eacc4be88", - "typescript": "^5.9.3" + "typescript": "^7.0.0" }, "engines": { "node": ">=22.19.0" @@ -998,6 +998,346 @@ "@types/node": "*" } }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -1751,17 +2091,38 @@ "license": "0BSD" }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "tsc": "bin/tsc" }, "engines": { - "node": ">=14.17" + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index 2f9622c..44a9964 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,6 @@ "devDependencies": { "@types/node": "^24.0.0", "agent-bundle": "https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@ab5ae66e1d9c2829a092640c586e2b3eacc4be88", - "typescript": "^5.9.3" + "typescript": "^7.0.0" } } From dcded804edb3423da31ae06259e1d96ba5c120a7 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 18 Sep 2026 19:22:59 +0000 Subject: [PATCH 2/4] build: commit installable Agent Bundle artifacts with npm bot CLI instructions --- .cursor-plugin/marketplace.json | 11 + .github/workflows/ci.yml | 3 + .github/workflows/publish-agent-bundle.yml | 19 +- .gitignore | 1 + README.md | 114 +- artifact/.cursor-plugin/marketplace.json | 1 + artifact/.cursor-plugin/plugin.json | 1 + artifact/INSTALL.md | 162 + artifact/agent-bundle.compile-evidence.json | 1 + artifact/agent-bundle.manifest.json | 1 + artifact/assets/assets/logo.svg | 1 + artifact/assets/data/pstack.json | 612 ++ artifact/assets/data/unified-catalog.json | 5536 +++++++++++++++++++ artifact/assets/public/app.js | 440 ++ artifact/assets/public/index.html | 55 + artifact/assets/public/markdown.js | 213 + artifact/assets/public/picker.js | 211 + artifact/assets/public/styles.css | 367 ++ artifact/commands/plugin-library.md | 24 + artifact/install.mjs | 1332 +++++ artifact/plugin.json | 1 + artifact/scripts/plugin-library-server.mjs | 921 +++ artifact/scripts/plugin-library.mjs | 382 ++ artifact/skills/plugin-library/SKILL.md | 32 + 24 files changed, 10345 insertions(+), 96 deletions(-) create mode 100644 .cursor-plugin/marketplace.json create mode 100644 artifact/.cursor-plugin/marketplace.json create mode 100644 artifact/.cursor-plugin/plugin.json create mode 100644 artifact/INSTALL.md create mode 100644 artifact/agent-bundle.compile-evidence.json create mode 100644 artifact/agent-bundle.manifest.json create mode 100644 artifact/assets/assets/logo.svg create mode 100644 artifact/assets/data/pstack.json create mode 100644 artifact/assets/data/unified-catalog.json create mode 100644 artifact/assets/public/app.js create mode 100644 artifact/assets/public/index.html create mode 100644 artifact/assets/public/markdown.js create mode 100644 artifact/assets/public/picker.js create mode 100644 artifact/assets/public/styles.css create mode 100644 artifact/commands/plugin-library.md create mode 100644 artifact/install.mjs create mode 100644 artifact/plugin.json create mode 100755 artifact/scripts/plugin-library-server.mjs create mode 100644 artifact/scripts/plugin-library.mjs create mode 100644 artifact/skills/plugin-library/SKILL.md diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json new file mode 100644 index 0000000..5b897e7 --- /dev/null +++ b/.cursor-plugin/marketplace.json @@ -0,0 +1,11 @@ +{ + "name": "plugin-library-marketplace", + "owner": { "name": "plugin-library" }, + "plugins": [ + { + "name": "plugin-library", + "description": "Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.", + "source": "./artifact" + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0de96e9..d2b537d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,9 @@ jobs: node-version: "24" cache: npm + - name: Verify committed plugin installs without a build + run: node --test test/install.test.js + - name: Install dependencies run: npm ci diff --git a/.github/workflows/publish-agent-bundle.yml b/.github/workflows/publish-agent-bundle.yml index 24f1f8b..87a19a8 100644 --- a/.github/workflows/publish-agent-bundle.yml +++ b/.github/workflows/publish-agent-bundle.yml @@ -32,14 +32,13 @@ jobs: - name: Build and verify artifact run: npm run check - - name: Publish generated root - env: - GH_TOKEN: ${{ github.token }} + - name: Commit generated artifact on main run: | - git -C artifact init --initial-branch agent-bundle-artifact - git -C artifact config user.name github-actions[bot] - git -C artifact config user.email 41898282+github-actions[bot]@users.noreply.github.com - git -C artifact add --all - git -C artifact commit -m "Publish Agent Bundle from ${GITHUB_SHA}" - git -C artifact remote add origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - git -C artifact push --force origin HEAD:agent-bundle-artifact + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add --force --all artifact + if git diff --cached --quiet; then + exit 0 + fi + git commit -m "build: refresh committed Agent Bundle from ${GITHUB_SHA}" + git push origin HEAD:main diff --git a/.gitignore b/.gitignore index c2111ea..52bd87a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ +# Exclude generated files from build source discovery; CI force-adds this tree. artifact/ dist/ .agent-bundle/ diff --git a/README.md b/README.md index 0f40ae7..63d0594 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,55 @@ # Plugin Library -A local explorer for your agent plugins. Three panes: the plugins on this machine and in the marketplace → a plugin's skills, agents and rules → the full `SKILL.md` rendered in a reading pane, straight from disk. "Send to a bot" picks a target from the live [`gbot`](https://github.com/ScriptedAlchemy/grok-bot-cli) roster and asks before anything is sent. +## Install -Ships as a Cursor / Grok Bot plugin: `/plugin-library` starts the server and opens it. +Requires Node 22.19+ and npm. Generated Agent Bundle artifacts are committed in `artifact/` on `main`; no project dependency installation or local build is needed. -## Tour +Install the bot picker CLI from npm before installing the plugin: -Installed plugins, sorted by how much they ship. Search covers plugin names, descriptions and skill ids. - -![Library](screens/01-library.png) - -pstack's 47 skills, grouped the way the Catalog suggests (Workflows, Bot+style, Principles, Language), with agents and rules below. - -![pstack](screens/02-pstack.png) - -Click a skill and the full `SKILL.md` opens in the reading pane. Front matter becomes chips, sibling reference files become tabs, and relative links between them switch tabs instead of leaving the page. `#/p/9717366/s/why` is a shareable deep link. - -![Reading a skill](screens/03-read-skill.png) - -![A reference file](screens/04-reference-tab.png) - -"Send to a bot" pulls the live roster from `gbot` (bots and groups) and ends in one sentence you confirm. Nothing is sent until you click Send. - -![Pick a bot](screens/05-pick-a-bot.png) - -![Confirm](screens/06-confirm.png) - -Every installed plugin gets the same treatment, not only pstack. - -![Cursor Team Kit](screens/07-cursor-team-kit.png) - -Marketplace listings show catalog copy and can be sent to a bot too. - -![Marketplace](screens/08-marketplace.png) - -## Run from a checkout - -Node 22.19+. The explorer reads `~/.cursor/plugins/cache` and `~/.cursor/plugins/local`; the bot picker needs `gbot` on `PATH` (without it the explorer still works and the picker says the roster is unavailable). - -```bash -git clone https://github.com/ScriptedAlchemy/plugin-library.git -cd plugin-library -npm start # http://127.0.0.1:8787/ -``` - -Or let the launcher manage the process: - -```bash -node bin/plugin-library.mjs open [query] [--json] [--browser] # start if needed, print or open a deep link -node bin/plugin-library.mjs status --json -node bin/plugin-library.mjs stop +```sh +npm install --global grok-bot-cli ``` -`open pstack` resolves to `#/p/9717366`; `open why` to the skill inside it. `open` is idempotent: it reuses a running server or starts one detached (pid in `logs/server.pid`, output in `logs/server.out`). - -Default port **8787** (`PORT`). Binds `127.0.0.1` because it shells out to `gbot` and serves cache files without auth; set `HOST=0.0.0.0` only on a network you trust. `CURSOR_PLUGIN_CACHE`, `CURSOR_PLUGIN_LOCAL` and `GBOT_BIN` override the two plugin directories and the CLI. +### Cursor: install from GitHub -## Install as a plugin +Dashboard → Plugins → Team Marketplaces → Add Marketplace: -The `agent-bundle-artifact` branch is the generated, validated plugin root (built by CI on every push to `main`). +```text +https://github.com/ScriptedAlchemy/plugin-library +``` -**Cursor, from GitHub.** Dashboard → Plugins → Team Marketplaces → Add Marketplace, import `https://github.com/ScriptedAlchemy/plugin-library`, pick the `agent-bundle-artifact` branch, then install **Plugin Library**. (Team Marketplaces need a Teams or Enterprise plan.) +Use the default `main` branch and install **Plugin Library**. Team Marketplaces require a Teams or Enterprise plan. -**Cursor, without a team marketplace.** +### Cursor: install from a clone ```sh -git clone --branch agent-bundle-artifact --depth 1 https://github.com/ScriptedAlchemy/plugin-library.git -cd plugin-library && node ./install.mjs +git clone --depth 1 https://github.com/ScriptedAlchemy/plugin-library.git +cd plugin-library +node artifact/install.mjs ``` -Reload Cursor (`Developer: Reload Window`). `npx --no-install agent-bundle doctor --from . --host cursor` verifies the install. - -**Grok Bot.** The same generated root is an Agent Plugins 1.0.0 package: `plugin.json` plus `skills/`. Hand it to the host's plugin flow; the `plugin-library` skill teaches the bot to answer skill questions from the read-only API of an already-running explorer. The `/plugin-library` slash command is Cursor-only. - -**From a source checkout**, for a local proof: +Reload Cursor with **Developer: Reload Window**, then run `/plugin-library` in an agent session to open the explorer. -```bash -npm ci && npm run build -node artifact/install.mjs -``` +The npm package provides `gbot` on `PATH`. Sign in to Grok Bot before using the bot picker. -In the installed plugin the launcher lives at `scripts/plugin-library.mjs` (the command file uses `${CURSOR_PLUGIN_ROOT}/scripts/plugin-library.mjs`); in this repo it is `bin/plugin-library.mjs`. +### Other Agent Plugins hosts -Then in an agent session: +Use the committed `artifact/` directory with your host's plugin installer. It contains `plugin.json`, skills, bundled executables, and assets. The `/plugin-library` slash command is Cursor-only. -- `/plugin-library` opens the explorer (embedded browser when the session has one, OS browser otherwise). -- `/plugin-library pstack` or `/plugin-library why` deep-links to that plugin or skill. +## Screenshots -Agents never call `POST /api/send`; the bundled command and skill both say so. Sending to a bot is a click you make. +![Plugin library](screens/01-library.png) -## API +![pstack skills](screens/02-pstack.png) -| Route | Returns | -|-------|---------| -| `GET /api/library` | `{ installed, marketplace, groups }`. *Installed* means present in this machine's plugin cache or local plugin directory; catalog rows supply copy, category and ids. Plugins with no catalog row get `local:`-style ids. | -| `GET /api/local///doc/` | `{ meta, markdown }` for any `.md`/`.mdc` inside that plugin | -| `GET /api/local///file/` | any other file inside that plugin (logos, scripts). Symlinks and `..` that leave the plugin root are 404 | -| `GET /api/bots` | live bots + groups from `gbot bots list` / `gbot groups list` (20s cache; `?refresh=1` bypasses) | -| `POST /api/send` | `{ plugin_id, bot_ref, skill_id? }`, same-origin JSON only. Verifies the plugin, skill and target, then runs `gbot send `. Used by the UI after you confirm. | +![Reading a skill](screens/03-read-skill.png) -## Data +![Reference file](screens/04-reference-tab.png) -`data/unified-catalog.json` is a snapshot of the Cursor marketplace (names, descriptions, categories, ids); `data/pstack.json` is the Catalog's suggested grouping for pstack's skills. Both are read server-side only and copied into the built plugin. Everything else (skills, agents, rules, logos) is read live from disk. When a plugin is cached twice (numeric id and slug), the copy Cursor marked `.installed` wins, then the highest version. +![Bot picker](screens/05-pick-a-bot.png) -## Develop +![Send confirmation](screens/06-confirm.png) -```bash -npm ci -npm run check # agent-bundle validate, tsc, build, node --test, artifact validate, packed-install smoke -``` +![Cursor Team Kit](screens/07-cursor-team-kit.png) -Screenshots in `screens/` are 1600×1000 @2x from a headless-Chrome tour with a stub `gbot` roster. +![Marketplace](screens/08-marketplace.png) diff --git a/artifact/.cursor-plugin/marketplace.json b/artifact/.cursor-plugin/marketplace.json new file mode 100644 index 0000000..5fa7888 --- /dev/null +++ b/artifact/.cursor-plugin/marketplace.json @@ -0,0 +1 @@ +{"name":"plugin-library-marketplace","owner":{"name":"plugin-library"},"plugins":[{"description":"Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.","name":"plugin-library","source":"./"}]} diff --git a/artifact/.cursor-plugin/plugin.json b/artifact/.cursor-plugin/plugin.json new file mode 100644 index 0000000..d72b482 --- /dev/null +++ b/artifact/.cursor-plugin/plugin.json @@ -0,0 +1 @@ +{"commands":"./commands/","description":"Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.","displayName":"plugin-library","homepage":"https://github.com/ScriptedAlchemy/plugin-library#readme","license":"MIT","logo":"./assets/assets/logo.svg","name":"plugin-library","repository":"https://github.com/ScriptedAlchemy/plugin-library","skills":"./skills/","version":"0.4.0"} diff --git a/artifact/INSTALL.md b/artifact/INSTALL.md new file mode 100644 index 0000000..58ffbc7 --- /dev/null +++ b/artifact/INSTALL.md @@ -0,0 +1,162 @@ +# Install plugin-library + +Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot. + +Version: `0.4.0` + +Run these commands from this bundle directory. The bundle is self-contained: every command below is +a host command or the bundled installer, and nothing requires the `agent-bundle` CLI. Where that CLI is +mentioned it is optional and automates the same steps. + +## Cursor + +Cursor has no non-interactive plugin install command. The bundled installer supports two delivery modes. + +### Local plugin (default) + +```sh +node ./install.mjs +``` + +It safe-copies the bundle to `~/.cursor/plugins/local/plugin-library`. Restart Cursor or run +`Developer: Reload Window` after installation. Cursor loads rules, skills, MCP servers, and the +manifest-declared `hooks/hooks.json` from that directory; plugin hooks run from the plugin root with +`${CURSOR_PLUGIN_ROOT}` substituted and need no `~/.cursor/hooks.json` entry. + +### Reinstall after a same-version rebuild + +The installer writes an install receipt (`.agent-bundle-install.json`: plugin, version, content hash, +owned files) beside the plugin manifest. Re-running `node ./install.mjs` on an identical artifact +is a no-op that says so. When the installed copy has the same version but different content, the +installer replaces its owned files in place and leaves runtime state (`state/`) untouched: + +```sh +node ./install.mjs # same-version content drift of a receipt-managed copy is replaced +node ./install.mjs --replace # also replace a different installed version, or adopt a pre-receipt copy +``` + +`--force` is an alias for `--replace`. A directory that is not an agent-bundle install of this +plugin is always refused with an installed-versus-artifact content-hash comparison; remove it +manually. The optional `agent-bundle` CLI applies the same policy through +`agent-bundle install cursor --from ./ [--replace]`. + +### Uninstall + +```sh +node ./install.mjs --uninstall --plan # print exactly what would be removed +node ./install.mjs --uninstall # remove the receipt-owned files; keep state/ +node ./install.mjs --uninstall --purge-data --confirm-purge # also remove durable runtime state +node ./install.mjs --uninstall --mode marketplace # remove a staged marketplace repository +``` + +Uninstall removes exactly what the receipt owns: the listed files, the directories the installer +created (including `~/.cursor/plugins/local` when the installer made it), and nothing else. Durable +runtime state under `state/` (state kernel, notices journal) — and, for an Agent Plugins pack with a stdio +server, the `~/.cursor/agent-bundle/plugin-data/` directory the receipt records as `PLUGIN_DATA` — is kept +unless `--purge-data --confirm-purge` is passed (a kept data directory leaves a remnant receipt behind so a later +purge still finds it; an empty one is pruned); unowned files are left in place and listed. A supported older +receipt with no recorded state location retains the current environment's default as unproven; a keep-data run +cannot turn that observation into later purge authority. A directory without a receipt is refused unless +`--force` (which removes a pre-receipt legacy copy by its inventory); owned content that no longer matches +the receipt is refused unless `--force`; a directory that is not this plugin's install is always refused. +A second run is a `Not installed` no-op. With the optional `agent-bundle` CLI, +`agent-bundle uninstall cursor --from ./ [--mode marketplace]` applies the same policy, and +`agent-bundle doctor --from ./` shows the lifecycle stage (placed, registered, enabled, active) with +unobservable stages typed `unavailable`. + +### Marketplace plugin + +```sh +node ./install.mjs --mode marketplace +``` + +It stages a committed Git repository at `~/.cursor/agent-bundle/marketplaces/plugin-library` whose +`.cursor-plugin/marketplace.json` lists this plugin, then prints the exact Cursor step: Customize -> Plugins -> +"Add Plugins from Local Repository" -> select that directory -> Install. Cursor then shows the plugin as a +marketplace install (not "local") and manages it from Customize. `git` must be on PATH. Verify in Cursor: +Customize -> Plugins lists the plugin, and its files appear under `~/.cursor/plugins/cache`. The optional +`agent-bundle` CLI performs the same check with `agent-bundle doctor --host cursor`. + +## Portable Agent Plugin + +Portable is a distribution profile, not a host runtime with one universal install location. +This bundle follows the Agent Plugins open standard (Agent Plugins 1.0.0, https://agent-plugins.org). +Cursor loads this format natively from `~/.cursor/plugins/local/`; restart Cursor or run +`Developer: Reload Window` after copying it. The bundled installer provides the Cursor local copy: + +```sh +node ./install.mjs +``` + +### Other recorded clients + +Each line below is pinned to that client's own documentation on the date shown, and names only the +paths this build actually wrote. Recognizing a document and running what it configures are separate: +`mcp` records that the client reads the emitted `mcp.json` as MCP configuration, while `placeholders` +records that it expands the reserved `${PLUGIN_ROOT}` / `${PLUGIN_DATA}` and provides them to the +process it spawns. A client can do the first without the second, and then a plugin-relative server +is configured but not runnable there. + +- **Antigravity** (docs retrieved 2026-09-06; no product or CLI version is published on any page) loads nothing from this bundle as published. Not loaded: manifest, skills, mcp, placeholders, hooks. +- **Cascade (Devin Desktop)** (docs.devin.ai/desktop retrieved 2026-09-06; Windsurf renamed to Devin Desktop, Cascade documented as the legacy agent beside the Devin Local agent) loads the components it recognizes without reading the manifest. Reads: `skills`. Install: `cp -R skills/ .agents/skills/`. Not loaded: manifest, mcp, placeholders, hooks. + - Partial `skills`: 2026-09-06: the emitted package root is not a Cascade discovery root; each skill directory is copied into .windsurf/skills/, ~/.codeium/windsurf/skills/, or .agents/skills/ before Cascade sees it, and the copy is what loads. +- **Cline** (@cline/cli 0.0.13 exercised 2026-09-06; docs.cline.bot retrieved 2026-09-06 (@cline/sdk 0.0.82)) loads the components it recognizes without reading the manifest. Reads: `skills`. Install: `cp -R skills/ ~/.cline/skills/`. Not loaded: manifest, mcp, placeholders, hooks. + - Partial `skills`: 2026-09-06: the skill tree must be copied into one of Cline's own roots; the emitted package is not a Cline install unit, and no CLI verb in @cline/cli 0.0.13 performs the copy. +- **CodeWhale** (Hmbown/CodeWhale main 19d34a5fb6c07b34e0b7234beb74a1cf1969efb4, docs retrieved 2026-09-06 (native Agent Plugins v1.0.0 support since v0.9.4)) loads this bundle as one plugin. Reads: `plugin.json`, `skills`. Install: `/plugin install ./`. Not loaded: placeholders, hooks. +- **GitHub Copilot CLI** (@github/copilot 1.0.83, installed and exercised 2026-09-06) loads this bundle as one plugin. Reads: `plugin.json`, `skills`. Install: `copilot plugin install `. Not loaded: hooks. A root that also carries `.plugin/plugin.json` uses it for manifest and still reads the rest. A root that also carries `.mcp.json` uses it for mcp and still reads the rest. +- **Devin CLI** (Agent Plugins 1.0.0; docs retrieved 2026-09-06, plugins documented as closed beta) loads this bundle as one plugin. Reads: `plugin.json`, `skills`. Install: `devin plugins install `. Not loaded: hooks. A root that also carries `.devin-plugin/plugin.json` is read as that plugin instead. A root that also carries `.claude-plugin/plugin.json` is read as that plugin instead. +- **Gemini CLI** (@google/gemini-cli 0.58.0, installed and exercised 2026-09-06) loads the components it recognizes without reading the manifest. Reads: `skills`. Install: `gemini skills install /skills/`. Not loaded: manifest, mcp, placeholders, hooks. +- **Grok Build** (xai-org/grok-build main 72a61251fcffb464bcc687aeb5a998e5a98ec0c9, docs retrieved 2026-09-06) loads the components it recognizes without reading the manifest. Reads: `skills`. Install from a marketplace (no local-directory install is verified for this artifact): `grok plugin install --trust`. Not loaded: manifest, mcp, placeholders, hooks. +- **Hermes Agent** (hermes-agent.nousresearch.com developer guide retrieved 2026-09-06; no version is printed on the page) loads this bundle as one plugin. Reads: `plugin.json`, `skills`. Install from a Git repository (no local-directory install is verified for this artifact): `hermes plugins install / --no-enable`. Not loaded: hooks. + - Partial `manifest`: 2026-09-06: the validation rule set is not published: the page never states that the manifest root is treated as closed or what happens to an unknown root key. +- **JetBrains Junie** (junie.jetbrains.com/docs retrieved 2026-09-06, agent-skills page dated 01 September 2026; no CLI version is published on the page) loads the components it recognizes without reading the manifest. Reads: `skills`. Register: `junie --skill-location /skills`. Not loaded: manifest, mcp, placeholders, hooks. + - Partial `skills`: 2026-09-06: the emitted skills/ root is not a default location, so it loads only once registered with --skill-location or the skill-locations config field, and "if a project-level and a user-level skills have the same name, the user-level skill will be skipped". +- **Kiro (Powers)** (kiro.dev/docs/powers pages updated September 2, 2026 and August 4, 2026, retrieved 2026-09-06) loads this bundle as one plugin. Reads: `plugin.json`, `skills`. Install: `Powers panel -> Add Custom Power -> Import power from a folder -> select -> Install`. Not loaded: placeholders, hooks. + - Partial `manifest`: 2026-09-06: Kiro's "Required fields" table additionally requires version, description, author, and keywords, where the canonical schema requires only $schema and name — so a bundle that declares no portable author or keywords metadata does not meet Kiro's tightened manifest, and Kiro publishes no validation-error behavior to say what happens then. +- **OpenClaw** (Agent Plugins 1.0.0; docs retrieved 2026-09-06) loads this bundle as one plugin, but this build also writes `.cursor-plugin/plugin.json`, which it reads as the plugin instead. +- **OpenCode** (opencode-ai 1.18.29 exercised 2026-09-06; opencode.ai/docs retrieved 2026-09-06) loads the components it recognizes without reading the manifest. Reads: `skills`. Install: `cp -R skills/ .agents/skills/`. Not loaded: manifest, mcp, placeholders, hooks. + - Partial `skills`: 2026-09-06: the skill tree must be copied into one of OpenCode's own roots; the emitted package as a whole is not an OpenCode install unit, and skill names must be unique across all roots ("Ensure skill names are unique across all locations"). +- **Pi** (@mariozechner/pi-coding-agent 0.73.1 installed from npm 2026-09-07; packaged docs/skills.md and docs/packages.md read from that release) loads the components it recognizes without reading the manifest. Reads: `skills`. Register: `pi --skill /skills`. Not loaded: manifest, mcp, placeholders, hooks. + - Partial `skills`: 2026-09-07: the emitted skills/ root is not one of the scanned default roots, so it loads only when it is named with --skill or added to the `skills` settings array; discovery inside it is recursive once named. +- **Qoder CLI** (docs retrieved 2026-09-06; no CLI version is published on any page) loads the components it recognizes without reading the manifest. Reads: `skills`. Install: `qoder plugins install --scope user`. Not loaded: manifest, placeholders, hooks. A root that also carries `.mcp.json` uses it for mcp and still reads the rest. +- **Swival** (docs retrieved 2026-09-06; no product version is published on the documentation pages) loads the components it recognizes without reading the manifest. Reads: `skills`. Register: `swival --skills-dir /skills ""`. Not loaded: manifest, mcp, placeholders, hooks. + - Partial `skills`: 2026-09-06: the emitted skills/ root is not a default location, so it loads only once registered with --skills-dir or the swival.toml skills_dir field, and it loses by name to the default roots: "If the same skill name exists in multiple locations, the first one in the precedence order wins", with .swival/skills/ and .agents/skills/ ahead of --skills-dir paths. A registered tree outside the project resolves as external, which Swival adds "as read-only roots". +- **VS Code (Copilot agent plugins)** (code.visualstudio.com/docs/agent-customization/agent-plugins, page footer 9/2/2026, retrieved 2026-09-06) loads this bundle as one plugin. Reads: `plugin.json`, `skills`. Register: `"chat.pluginLocations": { "": true }`. Not loaded: placeholders, hooks. +- **Zed Agent** (zed.dev/docs retrieved 2026-09-06; no page publishes a version or last-updated date) loads the components it recognizes without reading the manifest. Reads: `skills`. Install: `cp -R skills/ ~/.agents/skills/`. Not loaded: manifest, mcp, placeholders, hooks. + - Partial `skills`: 2026-09-06: only the skill folders load, one copy at a time, and the catalog is capped — "50KB catalog budget… Skills that don't fit are dropped from the catalog with a warning in the UI" — so a large emitted skill set is not guaranteed to be wholly visible. + +### Cursor placeholder expansion + +Cursor 3.18.25 spawns the stdio servers of an Agent Plugins package without expanding +`${PLUGIN_ROOT}` / `${PLUGIN_DATA}` in `args`, `env` values, or `cwd`, without providing the +reserved `PLUGIN_ROOT` / `PLUGIN_DATA` variables (spec §9.1), with an omitted `cwd` defaulting to +the home directory, and with plugin-relative `./` commands resolved against the workspace folder +(spec §7.2.1). The installer therefore rewrites `mcp.json` in the Cursor copy only: the plugin root +becomes `~/.cursor/plugins/local/`, the data directory `~/.cursor/agent-bundle/plugin-data/` +(created by the installer), an omitted `cwd` becomes the plugin root, `./` commands resolve against +it, and every stdio server gains `PLUGIN_ROOT` / `PLUGIN_DATA` in its environment. The bundle itself +stays spec-conformant; the pre-expansion document is kept in `.agent-bundle-install.json` (`cursorExpansion`), +and the optional `agent-bundle doctor --host cursor` verifies the expanded paths (`AB7326`). Nothing is changed for +other clients; the recorded clients above name which of them expand the placeholders themselves. + +### Reinstall after a same-version rebuild + +The installer records an install receipt (`.agent-bundle-install.json`) and replaces its owned files in +place when the same version was rebuilt with different content; runtime state (`state/`) is never +touched. Pass `--replace` (alias `--force`) to replace a different installed version or to adopt a +copy installed before receipts existed. Foreign directories are refused with a content-hash +comparison. For a client that manages its own copy, remove and re-add the plugin through that client +when only content changed at the same version. + +### Uninstall + +```sh +node ./install.mjs --uninstall --plan # print exactly what would be removed +node ./install.mjs --uninstall # remove the receipt-owned files; keep state/ +node ./install.mjs --uninstall --purge-data --confirm-purge # also remove durable runtime state +``` + +Uninstall removes exactly what the receipt owns (files, installer-created directories) and keeps +durable runtime state under `state/` (and the recorded `PLUGIN_DATA` directory of an Agent Plugins pack) +unless `--purge-data --confirm-purge` is passed. A missing +receipt or modified owned content is refused unless `--force`; foreign directories are always refused. diff --git a/artifact/agent-bundle.compile-evidence.json b/artifact/agent-bundle.compile-evidence.json new file mode 100644 index 0000000..7703f14 --- /dev/null +++ b/artifact/agent-bundle.compile-evidence.json @@ -0,0 +1 @@ +{"assets":[{"externals":[{"externalType":"node-commonjs","issuers":["lib/bots.js"],"kind":"builtin","request":"child_process","userRequest":"child_process"},{"externalType":"node-commonjs","issuers":["lib/local-plugins.js"],"kind":"builtin","request":"fs","userRequest":"fs"},{"externalType":"module","issuers":["server.js"],"kind":"builtin","request":"node:fs","userRequest":"node:fs"},{"externalType":"module","issuers":["server.js"],"kind":"builtin","request":"node:http","userRequest":"node:http"},{"externalType":"module","issuers":["server.js"],"kind":"builtin","request":"node:path","userRequest":"node:path"},{"externalType":"module","issuers":["server.js"],"kind":"builtin","request":"node:url","userRequest":"node:url"},{"externalType":"node-commonjs","issuers":["lib/local-plugins.js"],"kind":"builtin","request":"os","userRequest":"os"},{"externalType":"node-commonjs","issuers":["lib/local-plugins.js"],"kind":"builtin","request":"path","userRequest":"path"}],"packages":[],"path":"scripts/plugin-library-server.mjs","sha256":"6b2c7548077aad7a68b0dd2716c833eb3597b91cc25f0dd6c24e1cd464326411"},{"externals":[{"externalType":"module","issuers":["bin/plugin-library.mjs"],"kind":"builtin","request":"node:child_process","userRequest":"node:child_process"},{"externalType":"module","issuers":["bin/plugin-library.mjs"],"kind":"builtin","request":"node:fs","userRequest":"node:fs"},{"externalType":"module","issuers":["bin/plugin-library.mjs"],"kind":"builtin","request":"node:path","userRequest":"node:path"},{"externalType":"module","issuers":["bin/plugin-library.mjs"],"kind":"builtin","request":"node:url","userRequest":"node:url"}],"packages":[],"path":"scripts/plugin-library.mjs","sha256":"c295407765af2e4afb8d97bbf6b794191558139b24bcbb4ca950a204f00fd464"}],"coverage":{"rewritable":false,"unobserved":["import()","require()","require.resolve(…)","createRequire(…)(…)","import.meta.resolve(…)"]},"policy":{"name":"closed-world-externals","revision":1},"producer":{"name":"agent-bundle","rspack":"2.2.5","version":"0.2.1"}} diff --git a/artifact/agent-bundle.manifest.json b/artifact/agent-bundle.manifest.json new file mode 100644 index 0000000..630b3cc --- /dev/null +++ b/artifact/agent-bundle.manifest.json @@ -0,0 +1 @@ +{"application":{"description":"Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.","id":"plugin:plugin-library","name":"plugin-library","version":"0.4.0"},"compiler":{"adapters":[{"adapterRevision":"1.13.0","host":"cursor","observedVersion":"2026-08-28","schemas":[{"name":"hooks","revision":"2026-08-28","sha256":"06154b7afa0861df462130b988912b897e7ccf962b8dd20c09193100bcde5d81"},{"name":"marketplace","revision":"2026-08-28","sha256":"1aae96a24c2796419933bc8bfe3a1255394e7199c35740b36325e0ce6dbc253d"},{"name":"mcp","revision":"2026-08-28","sha256":"f3fa4615afefe004c4fbcc09e635d890df0f1ec0cb39540feab72cbd3a31d844"},{"name":"plugin","revision":"2026-08-28","sha256":"a393b758901803fcf5cfe0d77bda8a83e987d32c3377dfce2d9edf445af884ed"}]},{"adapterRevision":"1.10.0","host":"portable","observedVersion":"1.0.0","schemas":[{"name":"mcp","revision":"1.0.0","sha256":"6539175bfcdf43085855183e86da40ea94b166547a72b47ae9a0a390516d3acb"},{"name":"plugin","revision":"1.0.0","sha256":"0a4aad95ce337878ad38802ebf0daa3fde76abe3f65400c86bcbb1ec0b3ab883"}]}],"agentSkills":{"schemaSha256":"6d06b61e423317421de2295ea1fd0c7b4491bfa36c5b7705c28616dfe76d4047","sourceRevision":"69ef37e9424c0a7ea9dd2293b559e43ec8176379","specification":"https://raw.githubusercontent.com/agentskills/agentskills/69ef37e9424c0a7ea9dd2293b559e43ec8176379/docs/specification.mdx"},"producer":{"name":"agent-bundle","version":"0.2.1"},"project":{"configDigest":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003","configPath":"agent-bundle.config.ts","modelDigest":"abe272f372d106e7e2861ec7f687f56eee76e62bf23b2a9a9c16270ac0970298","packageName":"plugin-library","packageVersion":"0.4.0","revision":"656d3a955dcb47dfd37a8c68e400fa365a12ee7f41d61a35f0363252fcf81303","sourceInputs":[{"executable":false,"path":".cursor-plugin/marketplace.json","sha256":"5c15c414c75fde0e2bb7738bd1df4ec111acd8dea98d8773f4e335121ff8c653"},{"executable":false,"path":".github/workflows/ci.yml","sha256":"3c51f0bdc639d14d698407abbd7e8a23f6f7330e521b6a78f67e018d72b1a045"},{"executable":false,"path":".github/workflows/publish-agent-bundle.yml","sha256":"ab36b84abb040c92106246d21e8b5420c8b8f000fc9826d47404ff6b92829cfe"},{"executable":false,"path":".gitignore","sha256":"fc7f785227a74db0c56704f6f615d1c2ec716afe1d9df25b74449367f28955c2"},{"executable":false,"path":"agent-bundle.config.ts","sha256":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003"},{"executable":false,"path":"assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"executable":true,"path":"bin/plugin-library.mjs","sha256":"0c174e75dadbed3c17f19651f7b764fcd080b83d2ada35523fd51fadd82aef2f"},{"executable":false,"path":"data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"executable":false,"path":"data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"executable":false,"path":"lib/bots.js","sha256":"fb697ad2b8b86f50716c310e74765bbcbdf54a65b48a65e9372c8a597e772be3"},{"executable":false,"path":"lib/local-plugins.js","sha256":"32d134641d78a888fa7e0e2e3f11ebad8d1e300eb2807ac5a93c559644612adb"},{"executable":false,"path":"lib/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"LICENSE","sha256":"ff28847df30260ae031d33a736f0e72210a539b1417570bac88a59016157544c"},{"executable":false,"path":"logs/.gitkeep","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"executable":false,"path":"package-lock.json","sha256":"a49aa6a773c3c8fddde93c1dcae1041140c2b9f02fa8c34718f48d385cafde07"},{"executable":false,"path":"package.json","sha256":"2b0cbd0428ab6821eb45e8c6db07425a9000ac79e3a1246afff059c4c2fbff80"},{"executable":false,"path":"public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"executable":false,"path":"public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"executable":false,"path":"public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"executable":false,"path":"public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"executable":false,"path":"public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"executable":false,"path":"README.md","sha256":"6b5162bcd4594a518c4b9fb6184ed12dcfee4a9c44b334f99993d7131b5a0b45"},{"executable":false,"path":"renovate.json","sha256":"e80b4e42a3043bc12fa0640db4bac392d2bf770acf841360d7c8ceeeac2ec1a9"},{"executable":false,"path":"screens/01-library.png","sha256":"f465bcecd299c9413975baa307dd87dc6b8cacbd0498134118df5bac2ef391a5"},{"executable":false,"path":"screens/02-pstack.png","sha256":"8fc2ed63fae8e7dac833ecea75d64ddcad9c3b6a49382e66132c13876b322c41"},{"executable":false,"path":"screens/03-read-skill.png","sha256":"afee8387371e444a6b0c997b45b576c4c6c7e527fca753338583c6e3ce68ddd2"},{"executable":false,"path":"screens/04-reference-tab.png","sha256":"b55ac9ce9e2cd1918e1b8f48a9ed2292b8f50872a2ffb2ce5c70b665c299eaae"},{"executable":false,"path":"screens/05-pick-a-bot.png","sha256":"03a7823afcd85dc6489bd2cb3b572b499cf8999e16e7a2436e1069ca2913607b"},{"executable":false,"path":"screens/06-confirm.png","sha256":"3333936a6af257344f1d3375a7b3dc14b53c1a10012c1340154c810b00c745a3"},{"executable":false,"path":"screens/07-cursor-team-kit.png","sha256":"fa8a42a120d33bfb32a8c7299180eba1dd61d4550228c05aea9038aa59bd3671"},{"executable":false,"path":"screens/08-marketplace.png","sha256":"5267be7f941e853588eee4d27b2083e20c00491a97406020dc7f7334f7619df0"},{"executable":false,"path":"scripts/packed-smoke.mjs","sha256":"ceb8bbf9933c9e35494b8d91f263d95c31324cc48cc5540f7aa4933cdc254697"},{"executable":true,"path":"server.js","sha256":"fc44c043db21f29c743fd074417dc8c357bb0f7491a469b05ae6a37da2dc2ba0"},{"executable":false,"path":"src/commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"executable":false,"path":"src/install-bin.ts","sha256":"766ae5ecdc1331397de9cf32922048d6d9386e270c7e283284a2f95c6953b499"},{"executable":false,"path":"src/skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"},{"executable":false,"path":"test/browser.test.js","sha256":"6c12d42fdb74239c5cc82efc58c3f8602b936c189e7c5d1990bf5b8413cf3ab0"},{"executable":false,"path":"test/data.test.js","sha256":"5b55d8aaa67db2853c7dc7b3bb93cabfe905b83683d3c2e1d156cedcbe802f49"},{"executable":false,"path":"test/helpers.js","sha256":"c32ef9b8c5e7a8817a9a43366212febca54bbf158a14860a27958dcbf78dbdb7"},{"executable":false,"path":"test/install.test.js","sha256":"d3080b2bb3ec577d7cd6adce6f1a5912736a017ce3ded08374b8897485971d4c"},{"executable":false,"path":"test/local-plugins.test.js","sha256":"a04b94b16ebbfb7d2090d0aa3243e758460b3f78a33d3b1ed54b0cd85cb0232f"},{"executable":false,"path":"test/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"test/server.test.js","sha256":"ed64df042f57b09facbd5da93702b0c0b3b691cd478ccade6ea77fce9bdd4e10"},{"executable":false,"path":"tsconfig.json","sha256":"a6599174892162cd37b9b9a046c581b813d933eb3ad267daeeca79d0d48fa9cd"}]},"provenance":[{"path":".cursor-plugin/marketplace.json","sourceInputs":["agent-bundle.config.ts"]},{"path":".cursor-plugin/plugin.json","sourceInputs":["agent-bundle.config.ts","assets/logo.svg","package.json","src/commands/plugin-library.md","src/skills/plugin-library/SKILL.md"]},{"path":"agent-bundle.compile-evidence.json","sourceInputs":["agent-bundle.config.ts"]},{"path":"assets/assets/logo.svg","sourceInputs":["agent-bundle.config.ts","assets/logo.svg"]},{"path":"assets/data/pstack.json","sourceInputs":["data/pstack.json"]},{"path":"assets/data/unified-catalog.json","sourceInputs":["data/unified-catalog.json"]},{"path":"assets/public/app.js","sourceInputs":["public/app.js"]},{"path":"assets/public/index.html","sourceInputs":["public/index.html"]},{"path":"assets/public/markdown.js","sourceInputs":["public/markdown.js"]},{"path":"assets/public/picker.js","sourceInputs":["public/picker.js"]},{"path":"assets/public/styles.css","sourceInputs":["public/styles.css"]},{"path":"commands/plugin-library.md","sourceInputs":["src/commands/plugin-library.md"]},{"path":"INSTALL.md","sourceInputs":["agent-bundle.config.ts"]},{"path":"install.mjs","sourceInputs":["agent-bundle.config.ts"]},{"path":"plugin.json","sourceInputs":["agent-bundle.config.ts","package.json"]},{"path":"scripts/plugin-library-server.mjs","sourceInputs":["agent-bundle.config.ts","lib/bots.js","lib/local-plugins.js","server.js"]},{"path":"scripts/plugin-library.mjs","sourceInputs":["agent-bundle.config.ts","bin/plugin-library.mjs"]},{"path":"skills/plugin-library/SKILL.md","sourceInputs":["src/skills/plugin-library/SKILL.md"]}],"recordVersion":1,"validation":{"artifact":{"status":"passed"},"projections":[{"host":"cursor","status":"passed"},{"host":"portable","status":"passed"}],"source":{"status":"passed"}}},"distribution":{"channels":["local","npm"],"install":{"instructions":"INSTALL.md","script":"install.mjs"},"payloads":[]},"executables":{"bins":[],"hooks":[],"mcpServers":[],"scripts":[{"hosts":["cursor","portable"],"id":"script:plugin-library","mode":"bundle","name":"plugin-library","path":"scripts/plugin-library.mjs"},{"hosts":["cursor","portable"],"id":"script:plugin-library-server","mode":"bundle","name":"plugin-library-server","path":"scripts/plugin-library-server.mjs"}]},"files":[{"bytes":236,"kind":"generated","path":".cursor-plugin/marketplace.json","sha256":"3a7e9c80d1bfdd1a25f0642e5d845ae21e52e4082c5169adff044a9ecce43eaf"},{"bytes":418,"kind":"generated","path":".cursor-plugin/plugin.json","sha256":"5eee1c6d8e738e5246c4f673f838ebef5b45d9c620234fc7332341744d880cbf"},{"bytes":2071,"kind":"generated","path":"agent-bundle.compile-evidence.json","sha256":"eada336633d54da7bf14c73ef13dbcd43844dbd43bed89e349d0f279ce73efe9"},{"bytes":519,"kind":"copy","path":"assets/assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"bytes":23189,"kind":"copy","path":"assets/data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"bytes":181719,"kind":"copy","path":"assets/data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"bytes":19680,"kind":"copy","path":"assets/public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"bytes":2112,"kind":"copy","path":"assets/public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"bytes":7273,"kind":"copy","path":"assets/public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"bytes":8090,"kind":"copy","path":"assets/public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"bytes":18065,"kind":"copy","path":"assets/public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"bytes":953,"kind":"generated","path":"commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"bytes":16134,"kind":"generated","path":"INSTALL.md","sha256":"e37d8d70a103f4bbf0bf862d5f0103ed8b08c41094d7dd403f5863b8acac2d87"},{"bytes":84127,"kind":"generated","path":"install.mjs","sha256":"6bca4b63ecf2cdc2de4cb3dcf516dcf2e7d8f460ae52ec5ee8c5a8955687fe68"},{"bytes":378,"kind":"generated","path":"plugin.json","sha256":"c3d9e182728a589ac3679fc176f0fe0d90c0a9f4fba0857e388fab7d82aba680"},{"bytes":31525,"kind":"bundle","mode":493,"path":"scripts/plugin-library-server.mjs","sha256":"6b2c7548077aad7a68b0dd2716c833eb3597b91cc25f0dd6c24e1cd464326411"},{"bytes":12515,"kind":"bundle","path":"scripts/plugin-library.mjs","sha256":"c295407765af2e4afb8d97bbf6b794191558139b24bcbb4ca950a204f00fd464"},{"bytes":1289,"kind":"copy","path":"skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"}],"manifestVersion":6,"projections":[{"builtInHost":"cursor","documents":{"marketplace":".cursor-plugin/marketplace.json","plugin":".cursor-plugin/plugin.json"},"host":"cursor","marketplace":{"name":"plugin-library-marketplace"}},{"builtInHost":"portable","documents":{"plugin":"plugin.json"},"host":"portable"}],"routes":{"digest":"d3f44eea2211698634fb9e2fc28997db775625774579eb44460cca873ce5c683","events":[],"layouts":[],"providers":[],"scripts":[],"servers":[]},"runtime":{"node":"22.19.0"}} diff --git a/artifact/assets/assets/logo.svg b/artifact/assets/assets/logo.svg new file mode 100644 index 0000000..aa75804 --- /dev/null +++ b/artifact/assets/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/artifact/assets/data/pstack.json b/artifact/assets/data/pstack.json new file mode 100644 index 0000000..c664367 --- /dev/null +++ b/artifact/assets/data/pstack.json @@ -0,0 +1,612 @@ +{ + "schema": "plugin-catalog.plugin.v1", + "source": "GetPlugin", + "plugin_id": "9717366", + "name": "pstack", + "description": "if you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence.", + "installed": true, + "install_state": "yes (user)", + "category": "MCP", + "skill_count": 47, + "connector_count": 0, + "groups": [ + { + "id": "workflows", + "name": "Workflows", + "skill_count": 16, + "skills": [ + { + "id": "architect", + "name": "architect", + "description": "Sketch types, signatures, and module structure before code, then stay in the loop while implementation fills in.", + "group": "Workflows" + }, + { + "id": "arena", + "name": "arena", + "description": "Spawn N parallel candidates at the same task, pick a base, graft the strongest parts of the losers into it.", + "group": "Workflows" + }, + { + "id": "blast-radius", + "name": "blast-radius", + "description": "Find what a change could break somewhere else before it ships, and prove the one fact it's safe because of.", + "group": "Workflows" + }, + { + "id": "create-verification-skill", + "name": "create-verification-skill", + "description": "Generate a project-local verification skill that drives your app the way a user does.", + "group": "Workflows" + }, + { + "id": "figure-it-out", + "name": "figure-it-out", + "description": "Design an auditable playbook when no narrower one fits: large migrations or multi-part changes.", + "group": "Workflows" + }, + { + "id": "how", + "name": "how", + "description": "Code walkthroughs before changing something; placement / ownership / layering questions.", + "group": "Workflows" + }, + { + "id": "interrogate", + "name": "interrogate", + "description": "Adversarial / multi-model review to stress-test code and find blind spots.", + "group": "Workflows" + }, + { + "id": "maintain-verification-skill", + "name": "maintain-verification-skill", + "description": "Periodic pass that keeps a project's verification skill and feature map honest.", + "group": "Workflows" + }, + { + "id": "no-comments", + "name": "no-comments", + "description": "Spawn Comment Sicko, fix accepted findings, and offer encodings for claimed constraints.", + "group": "Workflows" + }, + { + "id": "recall", + "name": "recall", + "description": "Reconstruct recent working context from chat history, live state, and shared record.", + "group": "Workflows" + }, + { + "id": "reflect", + "name": "reflect", + "description": "Spawn parallel review subagents over the transcript; route learnings into skill edits.", + "group": "Workflows" + }, + { + "id": "show-me-your-work", + "name": "show-me-your-work", + "description": "Keep a reviewable TSV decision trail for long-running or unattended work.", + "group": "Workflows" + }, + { + "id": "swarm", + "name": "swarm", + "description": "Fan out N parallel workers, drain them, and return one report.", + "group": "Workflows" + }, + { + "id": "tdd", + "name": "tdd", + "description": "Use when explicitly asked for TDD, a failing test, or a cheap local regression target.", + "group": "Workflows" + }, + { + "id": "teach", + "name": "teach", + "description": "Explain a body of work plainly by weaving how and why into one clear explanation.", + "group": "Workflows" + }, + { + "id": "why", + "name": "why", + "description": "Design rationale, regressions, postmortems, or data-backed thresholds.", + "group": "Workflows" + } + ] + }, + { + "id": "bot-style", + "name": "Bot+style", + "skill_count": 5, + "skills": [ + { + "id": "automate-me", + "name": "automate-me", + "description": "Create/update/refresh a -mode skill from preferences or working style.", + "group": "Bot+style" + }, + { + "id": "bro", + "name": "bro", + "description": "Restate the last message in plain human language, with no jargon.", + "group": "Bot+style" + }, + { + "id": "make-bot-ui", + "name": "make-bot-ui", + "description": "Build a custom UI that wakes a Grok Bot over a webhook.", + "group": "Bot+style" + }, + { + "id": "poteto-mode", + "name": "poteto-mode", + "description": "poteto's agent style: concise detailed responses, deliberate subagents, unslopped prose, simple code, verified work.", + "group": "Bot+style" + }, + { + "id": "setup-pstack", + "name": "setup-pstack", + "description": "Configure which models pstack uses per role and at what reasoning budget.", + "group": "Bot+style" + } + ] + }, + { + "id": "principles", + "name": "Principles", + "skill_count": 23, + "skills": [ + { + "id": "principle-attack-the-premise", + "name": "principle-attack-the-premise", + "description": "When two or more fixes that share one premise have failed the same gate, attack the shared premise.", + "group": "Principles" + }, + { + "id": "principle-boundary-discipline", + "name": "principle-boundary-discipline", + "description": "Concentrate guards at system boundaries (CLI, config, network, external APIs).", + "group": "Principles" + }, + { + "id": "principle-build-the-lever", + "name": "principle-build-the-lever", + "description": "For non-trivial work, build the tool that does it or proves it.", + "group": "Principles" + }, + { + "id": "principle-encode-lessons-in-structure", + "name": "principle-encode-lessons-in-structure", + "description": "Encode recurring corrections as lints, metadata, or structure \u2014 not repeated prose.", + "group": "Principles" + }, + { + "id": "principle-exhaust-the-design-space", + "name": "principle-exhaust-the-design-space", + "description": "For novel UI/architecture with no precedent, build 2\u20133 competing prototypes and compare.", + "group": "Principles" + }, + { + "id": "principle-experience-first", + "name": "principle-experience-first", + "description": "Choose user delight over implementation convenience; ship fewer polished features.", + "group": "Principles" + }, + { + "id": "principle-fix-root-causes", + "name": "principle-fix-root-causes", + "description": "Trace each symptom to its root cause; reproduce first; resist nil-check patches.", + "group": "Principles" + }, + { + "id": "principle-foundational-thinking", + "name": "principle-foundational-thinking", + "description": "Before writing logic: choose core types and data structures; sequence scaffold vs feature.", + "group": "Principles" + }, + { + "id": "principle-guard-the-context-window", + "name": "principle-guard-the-context-window", + "description": "Route bulk to subagents; keep summaries in the main context.", + "group": "Principles" + }, + { + "id": "principle-laziness-protocol", + "name": "principle-laziness-protocol", + "description": "Bias toward deletion and the smallest change when tempted to add abstractions.", + "group": "Principles" + }, + { + "id": "principle-make-operations-idempotent", + "name": "principle-make-operations-idempotent", + "description": "Design commands and loops that converge to the same end state amid retries.", + "group": "Principles" + }, + { + "id": "principle-migrate-callers-then-delete-legacy-apis", + "name": "principle-migrate-callers-then-delete-legacy-apis", + "description": "Migrate callers and delete the old API in the same wave.", + "group": "Principles" + }, + { + "id": "principle-minimize-reader-load", + "name": "principle-minimize-reader-load", + "description": "Count layers between question and answer; reduce hidden state in the reader's head.", + "group": "Principles" + }, + { + "id": "principle-model-the-domain", + "name": "principle-model-the-domain", + "description": "Encode the domain in a structure the code can share instead of repeating shape assumptions.", + "group": "Principles" + }, + { + "id": "principle-never-block-on-the-human", + "name": "principle-never-block-on-the-human", + "description": "On reversible work, proceed and present the result; let the human course-correct after.", + "group": "Principles" + }, + { + "id": "principle-outcome-oriented-execution", + "name": "principle-outcome-oriented-execution", + "description": "Converge on the target architecture; don't preserve smooth intermediate states as goals.", + "group": "Principles" + }, + { + "id": "principle-prove-it-works", + "name": "principle-prove-it-works", + "description": "Verify against the real artifact before declaring done.", + "group": "Principles" + }, + { + "id": "principle-redesign-from-first-principles", + "name": "principle-redesign-from-first-principles", + "description": "Integrate new requirements as if they had been foundational from day one.", + "group": "Principles" + }, + { + "id": "principle-separate-before-serializing-shared-state", + "name": "principle-separate-before-serializing-shared-state", + "description": "Eliminate shared mutable state first; serialize structurally only if needed.", + "group": "Principles" + }, + { + "id": "principle-sequence-verifiable-units", + "name": "principle-sequence-verifiable-units", + "description": "Break multi-step work into small units that can each be verified.", + "group": "Principles" + }, + { + "id": "principle-subtract-before-you-add", + "name": "principle-subtract-before-you-add", + "description": "Remove dead code and stubs first, then build on the cleaned surface.", + "group": "Principles" + }, + { + "id": "principle-test-behavior-not-implementation", + "name": "principle-test-behavior-not-implementation", + "description": "Call code the way users do; assert observed results against literal expectations.", + "group": "Principles" + }, + { + "id": "principle-type-system-discipline", + "name": "principle-type-system-discipline", + "description": "Make illegal states unrepresentable in types and signatures.", + "group": "Principles" + } + ] + }, + { + "id": "language", + "name": "Language", + "skill_count": 3, + "skills": [ + { + "id": "technical-writing", + "name": "technical-writing", + "description": "Layered technical-writing standard: Di\u00e1taxis, Google developer style, STE, Global English.", + "group": "Language" + }, + { + "id": "typescript-best-practices", + "name": "typescript-best-practices", + "description": "TypeScript best practices when reading or editing any .ts or .tsx file.", + "group": "Language" + }, + { + "id": "unslop", + "name": "unslop", + "description": "Cut AI tells from any writing. Must always apply.", + "group": "Language" + } + ] + } + ], + "skills": [ + { + "id": "architect", + "name": "architect", + "description": "Sketch types, signatures, and module structure before code, then stay in the loop while implementation fills in.", + "group": "Workflows" + }, + { + "id": "arena", + "name": "arena", + "description": "Spawn N parallel candidates at the same task, pick a base, graft the strongest parts of the losers into it.", + "group": "Workflows" + }, + { + "id": "automate-me", + "name": "automate-me", + "description": "Create/update/refresh a -mode skill from preferences or working style.", + "group": "Bot+style" + }, + { + "id": "blast-radius", + "name": "blast-radius", + "description": "Find what a change could break somewhere else before it ships, and prove the one fact it's safe because of.", + "group": "Workflows" + }, + { + "id": "bro", + "name": "bro", + "description": "Restate the last message in plain human language, with no jargon.", + "group": "Bot+style" + }, + { + "id": "create-verification-skill", + "name": "create-verification-skill", + "description": "Generate a project-local verification skill that drives your app the way a user does.", + "group": "Workflows" + }, + { + "id": "figure-it-out", + "name": "figure-it-out", + "description": "Design an auditable playbook when no narrower one fits: large migrations or multi-part changes.", + "group": "Workflows" + }, + { + "id": "how", + "name": "how", + "description": "Code walkthroughs before changing something; placement / ownership / layering questions.", + "group": "Workflows" + }, + { + "id": "interrogate", + "name": "interrogate", + "description": "Adversarial / multi-model review to stress-test code and find blind spots.", + "group": "Workflows" + }, + { + "id": "maintain-verification-skill", + "name": "maintain-verification-skill", + "description": "Periodic pass that keeps a project's verification skill and feature map honest.", + "group": "Workflows" + }, + { + "id": "make-bot-ui", + "name": "make-bot-ui", + "description": "Build a custom UI that wakes a Grok Bot over a webhook.", + "group": "Bot+style" + }, + { + "id": "no-comments", + "name": "no-comments", + "description": "Spawn Comment Sicko, fix accepted findings, and offer encodings for claimed constraints.", + "group": "Workflows" + }, + { + "id": "poteto-mode", + "name": "poteto-mode", + "description": "poteto's agent style: concise detailed responses, deliberate subagents, unslopped prose, simple code, verified work.", + "group": "Bot+style" + }, + { + "id": "principle-attack-the-premise", + "name": "principle-attack-the-premise", + "description": "When two or more fixes that share one premise have failed the same gate, attack the shared premise.", + "group": "Principles" + }, + { + "id": "principle-boundary-discipline", + "name": "principle-boundary-discipline", + "description": "Concentrate guards at system boundaries (CLI, config, network, external APIs).", + "group": "Principles" + }, + { + "id": "principle-build-the-lever", + "name": "principle-build-the-lever", + "description": "For non-trivial work, build the tool that does it or proves it.", + "group": "Principles" + }, + { + "id": "principle-encode-lessons-in-structure", + "name": "principle-encode-lessons-in-structure", + "description": "Encode recurring corrections as lints, metadata, or structure \u2014 not repeated prose.", + "group": "Principles" + }, + { + "id": "principle-exhaust-the-design-space", + "name": "principle-exhaust-the-design-space", + "description": "For novel UI/architecture with no precedent, build 2\u20133 competing prototypes and compare.", + "group": "Principles" + }, + { + "id": "principle-experience-first", + "name": "principle-experience-first", + "description": "Choose user delight over implementation convenience; ship fewer polished features.", + "group": "Principles" + }, + { + "id": "principle-fix-root-causes", + "name": "principle-fix-root-causes", + "description": "Trace each symptom to its root cause; reproduce first; resist nil-check patches.", + "group": "Principles" + }, + { + "id": "principle-foundational-thinking", + "name": "principle-foundational-thinking", + "description": "Before writing logic: choose core types and data structures; sequence scaffold vs feature.", + "group": "Principles" + }, + { + "id": "principle-guard-the-context-window", + "name": "principle-guard-the-context-window", + "description": "Route bulk to subagents; keep summaries in the main context.", + "group": "Principles" + }, + { + "id": "principle-laziness-protocol", + "name": "principle-laziness-protocol", + "description": "Bias toward deletion and the smallest change when tempted to add abstractions.", + "group": "Principles" + }, + { + "id": "principle-make-operations-idempotent", + "name": "principle-make-operations-idempotent", + "description": "Design commands and loops that converge to the same end state amid retries.", + "group": "Principles" + }, + { + "id": "principle-migrate-callers-then-delete-legacy-apis", + "name": "principle-migrate-callers-then-delete-legacy-apis", + "description": "Migrate callers and delete the old API in the same wave.", + "group": "Principles" + }, + { + "id": "principle-minimize-reader-load", + "name": "principle-minimize-reader-load", + "description": "Count layers between question and answer; reduce hidden state in the reader's head.", + "group": "Principles" + }, + { + "id": "principle-model-the-domain", + "name": "principle-model-the-domain", + "description": "Encode the domain in a structure the code can share instead of repeating shape assumptions.", + "group": "Principles" + }, + { + "id": "principle-never-block-on-the-human", + "name": "principle-never-block-on-the-human", + "description": "On reversible work, proceed and present the result; let the human course-correct after.", + "group": "Principles" + }, + { + "id": "principle-outcome-oriented-execution", + "name": "principle-outcome-oriented-execution", + "description": "Converge on the target architecture; don't preserve smooth intermediate states as goals.", + "group": "Principles" + }, + { + "id": "principle-prove-it-works", + "name": "principle-prove-it-works", + "description": "Verify against the real artifact before declaring done.", + "group": "Principles" + }, + { + "id": "principle-redesign-from-first-principles", + "name": "principle-redesign-from-first-principles", + "description": "Integrate new requirements as if they had been foundational from day one.", + "group": "Principles" + }, + { + "id": "principle-separate-before-serializing-shared-state", + "name": "principle-separate-before-serializing-shared-state", + "description": "Eliminate shared mutable state first; serialize structurally only if needed.", + "group": "Principles" + }, + { + "id": "principle-sequence-verifiable-units", + "name": "principle-sequence-verifiable-units", + "description": "Break multi-step work into small units that can each be verified.", + "group": "Principles" + }, + { + "id": "principle-subtract-before-you-add", + "name": "principle-subtract-before-you-add", + "description": "Remove dead code and stubs first, then build on the cleaned surface.", + "group": "Principles" + }, + { + "id": "principle-test-behavior-not-implementation", + "name": "principle-test-behavior-not-implementation", + "description": "Call code the way users do; assert observed results against literal expectations.", + "group": "Principles" + }, + { + "id": "principle-type-system-discipline", + "name": "principle-type-system-discipline", + "description": "Make illegal states unrepresentable in types and signatures.", + "group": "Principles" + }, + { + "id": "recall", + "name": "recall", + "description": "Reconstruct recent working context from chat history, live state, and shared record.", + "group": "Workflows" + }, + { + "id": "reflect", + "name": "reflect", + "description": "Spawn parallel review subagents over the transcript; route learnings into skill edits.", + "group": "Workflows" + }, + { + "id": "setup-pstack", + "name": "setup-pstack", + "description": "Configure which models pstack uses per role and at what reasoning budget.", + "group": "Bot+style" + }, + { + "id": "show-me-your-work", + "name": "show-me-your-work", + "description": "Keep a reviewable TSV decision trail for long-running or unattended work.", + "group": "Workflows" + }, + { + "id": "swarm", + "name": "swarm", + "description": "Fan out N parallel workers, drain them, and return one report.", + "group": "Workflows" + }, + { + "id": "tdd", + "name": "tdd", + "description": "Use when explicitly asked for TDD, a failing test, or a cheap local regression target.", + "group": "Workflows" + }, + { + "id": "teach", + "name": "teach", + "description": "Explain a body of work plainly by weaving how and why into one clear explanation.", + "group": "Workflows" + }, + { + "id": "technical-writing", + "name": "technical-writing", + "description": "Layered technical-writing standard: Di\u00e1taxis, Google developer style, STE, Global English.", + "group": "Language" + }, + { + "id": "typescript-best-practices", + "name": "typescript-best-practices", + "description": "TypeScript best practices when reading or editing any .ts or .tsx file.", + "group": "Language" + }, + { + "id": "unslop", + "name": "unslop", + "description": "Cut AI tells from any writing. Must always apply.", + "group": "Language" + }, + { + "id": "why", + "name": "why", + "description": "Design rationale, regressions, postmortems, or data-backed thresholds.", + "group": "Workflows" + } + ], + "notes": [ + "Potato maps to skill id poteto-mode, not a separate marketplace plugin.", + "Group labels are Catalog suggestions for Explorer render (Workflows / Bot+style / Principles / Language); skill ids/names grounded in GetPlugin." + ] +} diff --git a/artifact/assets/data/unified-catalog.json b/artifact/assets/data/unified-catalog.json new file mode 100644 index 0000000..9cfab4a --- /dev/null +++ b/artifact/assets/data/unified-catalog.json @@ -0,0 +1,5536 @@ +{ + "count": 366, + "plugins": [ + { + "stableId": "32922580", + "name": "1inch", + "description": "Quote and execute intent-based (Fusion) and cross-chain token swaps, build and manage limit orders, and call any 1inch product endpoint — portfolio, spot prices, gas, Web3 RPC, and more. Includes Agent Skills with exact product_api recipes by API category (Trading & Liquidity, Wallet & Data, Market Data, Infrastructure). Public tools work without auth; execution tools require authentication from the 1inch Business Portal.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 9, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "768", + "name": "1Password", + "description": "1Password Developer Environments for Cursor: MCP tools to create, import, and manage project secrets; an agent skill with the full import-and-mount workflow; and a hook that validates local .env mounts before shell commands run. Requires the 1Password desktop app on macOS or Linux with Labs MCP Server enabled. Not supported on Windows.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "59964755", + "name": "Aave", + "description": "Connects Cursor to Aave's official MCP server for live V3 and V4 markets, wallet positions, governance, and non-custodial transaction preparation.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "21002971", + "name": "Adobe Developer App Builder", + "description": "Development, customization, testing, and deployment skills for Adobe App Builder projects. Covers project initialization, action scaffolding, React Spectrum UI scaffolding, Jest unit and integration testing, Playwright E2E testing, and GitHub Actions / Azure DevOps / GitLab CI deployment pipelines.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3644", + "name": "Agent Compatibility", + "description": "CLI-backed repo compatibility scans plus agents that audit startup, validation, and docs against reality.", + "category": "MCP", + "installed": "no", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "3644", + "name": "Agent Compatibility", + "skill_count": 1, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "agent-compatibility", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "1.0.0", + "skillCount": 1, + "agentCount": 4, + "hasMcp": false, + "skillNames": [ + "check-agent-compatibility" + ], + "agentNames": [ + "compatibility-scan-review", + "docs-reliability-review", + "startup-review", + "validation-review" + ] + }, + "marketplaceSource": "agent-compatibility" + }, + { + "stableId": "48668144", + "name": "AgentMail", + "description": "AI-native email infrastructure for coding agents. Create inboxes, send and receive emails, manage threads, and automate email workflows.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 8, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809965", + "name": "Ahrefs", + "description": "Research keywords, backlinks, rankings, and site health.", + "category": "Research", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/ahrefs" + }, + { + "stableId": "9345", + "name": "Aikido", + "description": "", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "22025095", + "name": "Airtable", + "description": "Airtable is the database and operations layer for your agents — whether running product, marketing, sales, ops, HR, or a custom business app. It combines structured data with multiplayer visual surfaces (grid, kanban, calendar, gallery, timeline) humans and agents share — plus sync integrations to Jira, Salesforce, Zendesk, Google Drive, and Databricks. Makes Claude fluent in Airtable: bases and schema, records, and collaboration UI. Bundles the official Airtable MCP server.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 8, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "52586766", + "name": "Airwallex AgentOS", + "description": "Bring Airwallex's global financial infrastructure to Cursor. Orchestrate actions across your account in plain language, e.g., set up invoices from a PO, onboard suppliers from invoices, and check current cash position across currencies. AgentOS bundles pre-built finance Skills with MCP servers. A public CLI connects your agent to Airwallex's capabilities.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "52586767", + "name": "Airwallex Developer", + "description": "Airwallex integration skills for Cursor. Code-generation workflows that scaffold, configure, and wire Airwallex APIs into your project.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "51597430", + "name": "Aleph", + "description": "Aleph MCP server. Your one source of truth for financial data.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "59961146", + "name": "Algolia Productivity", + "description": "Connect LLM tools to your Algolia account with user-scoped access for internal workflows.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3315", + "name": "Amazon Location Service", + "description": "Guide developers through adding maps, places search, geocoding, routing, and other geospatial features with Amazon Location Service, including authentication setup, SDK integration, and best practices.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "39669369", + "name": "AMD", + "description": "AMD's verified Agent Skills in one plugin: route image/audio through local AI on Ryzen AI, serve LLMs on AMD Instinct GPUs with vLLM, optimize inference throughput with Hyperloom, and analyze GPU kernel and PyTorch trace performance.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "53338791", + "name": "Amplemarket", + "description": "Search people and companies, enrich leads, run sequences.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/amplemarket" + }, + { + "stableId": "786", + "name": "Amplitude", + "description": "Use Amplitude like an expert - instrument analytics, discover product opportunities, analyze charts, create dashboards, manage experiments, and understand users and accounts", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 26, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "59967207", + "name": "Ando", + "description": "Connect an agent to Ando, the team messaging platform for humans and agents. Wraps the hosted streamable HTTP MCP at https://mcp.ando.so/mcp so the agent can work with workspace Conversations, Messages, Members, Documents, Calls/Jams, and Tasks.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3167", + "name": "Antimetal", + "description": "Bring Antimetal's software investigation intelligence into your editor. Triage problems, investigate root causes, and apply remediations without leaving Cursor.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60747522", + "name": "Antom", + "description": "Antom payment integration guidance for Cursor: product selection, integration mode (Payment Element / Checkout / API-only), and security validation.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "32913266", + "name": "Apify", + "description": "Official Apify agent skills for web scraping, data extraction, and automation", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "32704904", + "name": "Apollo.io", + "description": "Prospect, enrich leads, load outreach sequences, and query sales analytics with Apollo.io — one-click MCP server integration.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "4902", + "name": "Appwrite", + "description": "The Appwrite plugin for Cursor includes skills and MCP servers, allowing AI agents to access your projects and correctly integrate with your projects.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 10, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "23114788", + "name": "Arize", + "description": "Add Arize AX observability to LLM applications — auto-instrumentation, trace export, dataset management, experiment workflows, prompt optimization, and deep linking via the ax CLI.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 12, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579799", + "name": "asana", + "description": "Asana project management integration. Create and manage tasks, search projects, update assignments, track progress, and integrate your development workflow with Asana's work management platform.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26107310", + "name": "Asana", + "description": "Connect Cursor to Asana. Create tasks, update projects, search your workspace, and manage work — directly from the editor.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "49104326", + "name": "Ashby", + "description": "Search candidates, prep interviews, and manage pipeline tasks.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/ashby" + }, + { + "stableId": "4450", + "name": "Astronomer", + "description": "Data engineering plugin - warehouse exploration, pipeline authoring, Airflow integration", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 21, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "7850", + "name": "Atlan", + "description": "Atlan is the context layer for enterprise AI. Connect Cursor to your organization's context repos — the knowledge, data, and semantics your AI agents need to build effectively. Use Atlan's agent skills to search & discover enterprise context, traverse end-to-end lineage, access governed data definitions and glossaries, execute SQL, curate your metadata graph, and ensure data quality — so every coding task in Cursor is grounded in trusted organizational context.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6391", + "name": "Atlassian Forge", + "description": "Forge app builder skill bundle with Forge MCP integration for building, deploying, and troubleshooting Atlassian Forge apps.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "717", + "name": "Atlassian MCP", + "description": "Atlassian plugin for Cursor with MCP and skills for Jira, Confluence, triage, backlogs, status reports, and more.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "49981383", + "name": "Atlassian Teamwork Graph", + "description": "Teamwork Graph CLI is Atlassian's agent-first interface to your entire work context: Jira issues, Confluence pages, Bitbucket PRs, along with your connected third-party data sources. Purpose-built for coding agents like Cursor (terminal or desktop), and powered by Atlassian's context graph. Try: \"draft a Jira issue from this PR and link it to the epic.\" Using Cursor in the browser? Install the Atlassian Rovo MCP plugin instead. Run /twg-setup after install to connect Atlassian Teamwork Graph CLI to Cursor.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "47472300", + "name": "AtScale", + "description": "Query governed business metrics, definitions and context from Cursor via AtScale MCP.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "62459236", + "name": "Attio", + "description": "Search and update CRM records, lists, notes, and tasks.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/attio" + }, + { + "stableId": "4787", + "name": "Auth0", + "description": "A single unified Auth0 skill that detects your framework, feature, and tooling, then loads the right reference guides for adding authentication — login, MFA, Organizations, ACUL, custom domains, branding, JWT validation, migration, and debugging — to any app.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26098676", + "name": "AWS Agents", + "description": "Build, deploy, and operate AI agents on AWS. Skills for scaffolding agents with Amazon Bedrock AgentCore (Strands, LangGraph), connecting tools via Gateway and MCP, multi-agent and A2A orchestration, memory, Cedar policies, evaluation, observability, debugging traces and logs, and production hardening (inbound auth, IAM, rate limiting, cold-start tuning).", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 8, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3318", + "name": "AWS Amplify", + "description": "Build full-stack apps with AWS Amplify Gen 2 using guided workflows for authentication, data models, storage, GraphQL APIs, and Lambda functions.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26098678", + "name": "AWS Core", + "description": "Build, deploy, and operate applications on AWS. Skills to author infrastructure-as-code (CDK, CloudFormation), use core services (Lambda, API Gateway, Step Functions, ECS/Fargate, ECR, IAM, Amazon Bedrock with Knowledge Bases and Guardrails, AWS Blocks), select and operate databases across relational, key-value, document, wide-column, graph, time-series, and in-memory engines (Aurora PostgreSQL/MySQL, Aurora DSQL, RDS, Oracle Database@AWS, DynamoDB, DocumentDB, Keyspaces, Neptune, Timestream, ElastiCache, and MemoryDB), and complete common tasks across observability (CloudWatch, X-Ray, CloudTrail, ADOT), messaging and streaming (SQS, SNS, EventBridge, Kinesis, MSK), AWS SDKs (boto3, JS v3, Swift), and cost optimization.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 24, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26098679", + "name": "AWS Data Analytics", + "description": "Data lake, analytics, search, and ETL workflows with S3 Tables, AWS Glue, Athena, Amazon Redshift, and Amazon OpenSearch Service. Covers managed Iceberg tables on S3 Tables, ingestion from JDBC databases (Oracle, SQL Server, PostgreSQL, MySQL, RDS), Amazon Redshift, Snowflake, BigQuery, and DynamoDB, AWS Glue Data Catalog inventory and asset discovery, federated Athena queries, vector storage and semantic search on Amazon S3 Vectors, and Amazon OpenSearch Service and Serverless (migration from Solr/Elasticsearch, provisioning, vector/semantic/hybrid search, log analytics, trace analytics).", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 9, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6198", + "name": "AWS Databases", + "description": "Expert database guidance for the AWS database portfolio. Design schemas, execute queries, handle migrations, and choose the right database for your workload.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "650", + "name": "AWS Deployments", + "description": "Deploy applications to AWS with architecture recommendations, cost estimates, and IaC deployment. Generate validated AWS architecture diagrams as draw.io XML.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 3, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6199", + "name": "AWS SageMaker", + "description": "Build, train, and deploy AI models with deep AWS AI/ML expertise brought directly into your coding assistants, covering the surface area of Amazon SageMaker AI.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 19, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3317", + "name": "AWS Serverless", + "description": "Design, build, deploy, test, and debug serverless applications with AWS Serverless services.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6392", + "name": "Azure", + "description": "Microsoft Azure MCP and Skills integration for cloud resource management, deployments, and Azure services. Manage your Azure infrastructure, monitor applications, and deploy resources directly from Cursor.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 27, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "2044", + "name": "Azure Cosmos DB", + "description": "Access your Azure Cosmos DB accounts and perform tasks like managing databases, querying data, vector search, and schema discovery.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "55646269", + "name": "Basedash", + "description": "One-click Basedash MCP for analyzing governed company data and managing dashboards and charts.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60856710", + "name": "Bird", + "description": "Drive Bird from your coding agent: operate the Bird API from the terminal with the `bird` CLI, send across email, SMS, and WhatsApp, and audit a domain's email authentication.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "1092", + "name": "Box", + "description": "Box Plugin for Cursor — search, read, and manage content in Box, build Box Platform integrations, and leverage Box AI for Q&A, summarization, and extraction via the Box MCP server.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "2046", + "name": "Braintrust", + "description": "Connect Cursor to Braintrust for AI-powered access to your projects, experiments, and evaluation logs", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809966", + "name": "Brevo", + "description": "Manage contacts, email and SMS campaigns, and CRM deals.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/brevo" + }, + { + "stableId": "56809967", + "name": "Brex", + "description": "Query expenses, receipts, bills, cards, and travel.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/brex" + }, + { + "stableId": "4793", + "name": "Bright Data", + "description": "Web search, content extraction, structured data, and browser automation powered by Bright Data's web intelligence platform.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "47477538", + "name": "Browser Use", + "description": "Give Cursor a real browser — your Chrome or a Browser Use Cloud browser. Use it whenever a task involves a website or web app: browsing, scraping and data extraction, filling forms, testing sites, taking screenshots, automating web workflows.", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "47477538", + "name": "Browser Use", + "skill_count": 1, + "connector_count": 1, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "browser-use", + "versionHash": "4749bcbfe456e5384b98281a8a66119352197f59", + "version": "0.1.0", + "skillCount": 1, + "agentCount": 0, + "hasMcp": true, + "skillNames": [ + "browser-use" + ], + "agentNames": [] + } + }, + { + "stableId": "675", + "name": "Browserbase", + "description": "Browser automation for AI agents: navigate, extract, screenshot, and interact with real web pages via a single CLI.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "546", + "name": "Browserstack", + "description": "BrowserStack integration for Cursor. Test websites and mobile apps on real devices, run automated tests, debug failures, and manage test cases—all using natural language.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "2507579", + "name": "Buildkite", + "description": "Buildkite MCP server and skills for common CI/CD workflows (designing pipelines, troubleshooting builds plus agent runtime, CLI, API)", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809968", + "name": "Calendly", + "description": "Check availability and book, cancel, or reschedule.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/calendly" + }, + { + "stableId": "32197178", + "name": "Canva", + "description": "Create, edit, review, resize, and brand-check Canva designs with the Canva MCP server.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "55887870", + "name": "Cartesia", + "description": "Voice AI for agents: Sonic TTS, Ink STT, voice cloning, and Line voice agents. Hosted Cartesia MCP plus skills that keep API versions current.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56634535", + "name": "Catalyst by Zoho", + "description": "AI agent skill for Catalyst by Zoho — full-stack serverless cloud platform. Covers all services, SDKs, CLI, architecture patterns, pricing, migration guides, and Zoho MCP tool-based resource management.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 16, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60856333", + "name": "Cello", + "description": "Launch and manage your User Referral and Partner program with Cello — for developers and growth teams.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "9294", + "name": "Chainguard", + "description": "Secure container images and hardened library dependencies powered by Chainguard. Query images, check advisories, manage policies, and configure Chainguard Libraries for Java, JavaScript, and Python.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 4, + "skillCountReported": 9, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3983", + "name": "Chargebee", + "description": "Use Chargebee's API integration patterns, webhook handling, SDK usage, and schema references for billing operations in Cursor.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "14371557", + "name": "ChatPRD", + "description": "Product requirements in your editor. Write PRDs from code context, implement from specs, and verify your changes match requirements — powered by ChatPRD's MCP.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "45990366", + "name": "Checkmarx", + "description": "The Checkmarx MCP Server acts as a translation layer that connects your AI assistant to Checkmarx One APIs, enabling developers and security teams to interact with security operations directly via natural language. It provides context-aware remediation tools for code vulnerabilities, secrets, and IaC misconfigurations, along with scan workflows and project management—eliminating context switching and enabling immediate action on security findings within your IDE or terminal.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "21020287", + "name": "Chrome Devtools for Agents", + "description": "Help your agent build, debug, and verify your code correctly. With Chrome DevTools for agents, your AI agent can interact with the Chrome browser to test code, emulate users, and catch bugs using Chrome DevTools’ capabilities before shipping.", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index" + ], + "liveInstalled": { + "plugin_id": "21020287", + "name": "Chrome Devtools for Agents", + "skill_count": 6, + "connector_count": 1, + "installed": true + } + }, + { + "stableId": "2804", + "name": "Circle", + "description": "Ship stablecoin apps faster. Best-practice skills for USDC payments, cross-chain transfers, wallets, and smart contracts — plus Circle's MCP server for real-time SDK and documentation guidance.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 9, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "49339687", + "name": "Circleback", + "description": "Search meetings, transcripts, action items, and emails.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/circleback" + }, + { + "stableId": "50433623", + "name": "CircleCI", + "description": "Debug failing builds, inspect jobs and test results, monitor pipelines, and rerun or cancel workflows across your CircleCI organization", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "2049", + "name": "Cisco ThousandEyes", + "description": "Connect Cursor to ThousandEyes MCP endpoints for network intelligence workflows.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579801", + "name": "clangd-lsp", + "description": "C/C++ language server (clangd) for code intelligence", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579802", + "name": "claude-code-setup", + "description": "Analyze codebases and recommend tailored Claude Code automations such as hooks, skills, MCP servers, and subagents.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579803", + "name": "claude-md-management", + "description": "Tools to maintain and improve CLAUDE.md files - audit quality, capture session learnings, and keep project memory current.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "49110469", + "name": "Clay", + "description": "Enrich people and companies, run AI research agents.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/clay" + }, + { + "stableId": "710", + "name": "Clerk", + "description": "Clerk authentication toolkit for Cursor - setup guides, MCP server, and specialized skills for frameworks, mobile apps, organizations, billing, webhooks, testing, and more.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 20, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3845", + "name": "CLI for Agents", + "description": "Patterns for designing CLIs that coding agents can run reliably: non-interactive flags, layered help with examples, pipelines, actionable errors, idempotency, and dry-run.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "729", + "name": "ClickHouse", + "description": "ClickHouse Cursor plugin: skills (ClickHouse best practices), rules and MCP.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "5069", + "name": "ClickUp", + "description": "Connect Cursor to your ClickUp workspace — manage tasks, track time, and collaborate without switching context.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "407", + "name": "Cloudflare", + "description": "Skills for the Cloudflare developer platform: Workers, Durable Objects, Agents SDK, MCP servers, Wrangler CLI, and web performance", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 4, + "skillCountReported": 9, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3793", + "name": "Cloudinary", + "description": "Use Cloudinary directly in Cursor. Upload, manage, optimize, and transform images and videos at scale using natural language.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 5, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "4457", + "name": "CockroachDB", + "description": "CockroachDB plugin for Cursor — explore schemas, write optimized SQL, debug queries, and manage distributed database clusters directly from your AI coding agent.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 3, + "skillCountReported": 32, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809969", + "name": "Coda", + "description": "Search docs, read pages, and update tables.", + "category": "Documents And Files", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/coda" + }, + { + "stableId": "41762204", + "name": "CodeRabbit", + "description": "Run CodeRabbit reviews for code, PR, security, and quality checks, plus guarded autofix for unresolved GitHub PR feedback in Cursor.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "32661537", + "name": "Composio", + "description": "Connect and operate 1000+ external apps from Cursor via the Composio MCP server. Managed OAuth, intelligent tool routing, and a remote sandbox for bulk data processing.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "680", + "name": "Compound Engineering", + "description": "Brainstorm, plan, debug, review, and compound learnings with AI agents", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 32, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26119850", + "name": "Confidence by Spotify", + "description": "Access Confidence feature flags, experiments, and migration tools directly from Cursor.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 11, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "53497710", + "name": "Context.dev", + "description": "Search, scrape, crawl, extract, parse, monitor, and process the live web with Context.dev.", + "category": "Research", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 11, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579808", + "name": "context7", + "description": "Upstash Context7 MCP server for up-to-date documentation lookup. Pull version-specific documentation and code examples directly from source repositories into your LLM context.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "406", + "name": "Context7", + "description": "Upstash Context7 MCP server for up-to-date documentation lookup. Pull version-specific documentation and code examples directly from source repositories into your LLM context.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "701", + "name": "Continual Learning", + "description": "Incrementally learns durable user preferences and workspace facts from transcript changes and keeps AGENTS.md up to date with plain bullet points.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "continual-learning" + }, + { + "stableId": "233", + "name": "Convex", + "description": "Official Convex plugin for Cursor - reactive backend development with TypeScript, including rules, skills, MCP integration, and automation hooks", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "20988446", + "name": "Coralogix", + "description": "Connects Cursor to the Coralogix Observability MCP Server, letting your AI agent query logs, metrics, traces, and RUM, manage alerts and dashboards — all without leaving the editor.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "10038", + "name": "Corridor", + "description": "Corridor secures your AI-generated code. To get started, please create an API key at https://app.corridor.dev/settings and update your ~/.bashrc or ~/.zshrc to include the following: `export CORRIDOR_API_KEY=your-api-key`", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809970", + "name": "Craft", + "description": "Search, create, and update documents and daily notes.", + "category": "Documents And Files", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/craft" + }, + { + "stableId": "648", + "name": "Create Plugin", + "description": "Scaffold and validate new agent plugins. Handles directory setup, manifest generation, and pre-submission quality checks for the marketplace.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 2, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "create-plugin" + }, + { + "stableId": "53576511", + "name": "CrowdStrike Falcon Foundry", + "description": "CrowdStrike Falcon Foundry development skills for building cybersecurity applications on the Falcon platform. Includes UI development, collections, functions, workflows, API integration, security patterns, and debugging workflows. Swagger 2.0 specs are auto-converted to OpenAPI 3.0 via npx swagger2openapi (requires Node.js; network call to npm registry).", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 11, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "57280658", + "name": "CrowdStrike Falcon Fusion", + "description": "CrowdStrike Falcon Fusion skills for authoring, deploying, and executing Fusion workflows. Includes live action discovery, YAML authoring with schema validation, workflow import and release, execution monitoring, and Falcon Next-Gen SIEM lookup files.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60860738", + "name": "Crustdata", + "description": "Live B2B data on 1B+ people and 200M+ companies: search, enrich, contacts, jobs, and social posts.", + "category": "Research", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579809", + "name": "csharp-lsp", + "description": "C# language server for code intelligence", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "7975", + "name": "Cursor SDK", + "description": "Build apps, scripts, and automations with the TypeScript SDK.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "cursor-sdk" + }, + { + "stableId": "677", + "name": "Cursor Team Kit", + "description": "Internal engineering team workflows for CI, code review, shipping, control-cli, control-ui, verify-this, test reliability, code cleanup, and work summaries. Designed to work without requiring third-party service integrations.", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 18, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "677", + "name": "Cursor Team Kit", + "skill_count": 18, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "cursor-team-kit", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "1.2.0", + "skillCount": 18, + "agentCount": 2, + "hasMcp": false, + "skillNames": [ + "check-compiler-errors", + "control-cli", + "control-ui", + "deslop", + "fix-ci", + "fix-merge-conflicts", + "get-pr-comments", + "loop-on-ci", + "make-pr-easy-to-review", + "new-branch-and-pr", + "pr-review-canvas", + "review-and-ship", + "run-smoke-tests", + "thermo-nuclear-code-quality-review", + "verify-this", + "weekly-review", + "what-did-i-get-done", + "workflow-from-chats" + ], + "agentNames": [ + "ci-watcher", + "thermo-nuclear-code-quality-review" + ] + }, + "marketplaceSource": "cursor-team-kit" + }, + { + "stableId": "56809971", + "name": "Customer.io", + "description": "Build campaigns, manage segments, and query people.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/customer-io" + }, + { + "stableId": "56366542", + "name": "Customer.io", + "description": "Connect Cursor to Customer.io with the official MCP connector and skills for Journeys, Pipelines, Design Studio, and SDK setup.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/customer-io" + }, + { + "stableId": "3579810", + "name": "cwc-makers", + "description": "Seamless onboarding for the Code-with-Claude Makers Cardputer: one /maker-setup command clones the build-with-claude repo, flashes UIFlow firmware, and installs the Claude Buddy app bundle onto a freshly-plugged-in M5Stack Cardputer-Adv.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "44949530", + "name": "D&B Commercial Graph", + "description": "Dun & Bradstreet empowers teams to execute workflows such as entity resolution, Sales & Marketing, Finance, Enterprise Master Data Management, Supply Chain, Compliance and more using relationship-aware business context from the D&B Commercial Graph™. Verified risk data, anchored in the global standard D-U-N-S® Number business identifier, and policy-driven logic provide the foundation for consistent decisioning across systems of record in regulated environments.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26111414", + "name": "D&B Risk Analytics", + "description": "Dun & Bradstreet empowers teams to execute risk workflows such as KYC/KYB, entity resolution, screening, alert triage, and more using relationship-aware business context from the D&B Commercial Graph™. Verified risk data, anchored in the global standard D-U-N-S® Number business identifier, and policy-driven logic provide the foundation for consistent decisioning across systems of record and scalable risk operations in regulated environments.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6643", + "name": "Dagster", + "description": "Expert guidance for working with Dagster and the dg CLI.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "63834585", + "name": "Dart and Flutter", + "description": "Official Cursor plugin for Dart and Flutter that installs Flutter/Dart Skills and Dart MCP server for building natively compiled, visually stunning applications for mobile, web, desktop, and embedded devices from a single codebase.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 24, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60857816", + "name": "Dash Security", + "description": "Discover your AI estate, assess supply-chain and shadow AI risk, investigate agent sessions, and track AI spend through the Dash MCP.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26723531", + "name": "Databricks", + "description": "Databricks skills for the CLI, Apps, Lakebase, Model Serving, Lakeflow Jobs, Spark Declarative Pipelines, Declarative Automation Bundles (DABs), and classic-to-serverless migration.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 10, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "1411", + "name": "Datadog", + "description": "Use Datadog directly in Cursor through a preconfigured Datadog MCP server. Query logs, metrics, traces, dashboards, and more through natural conversation. This plugin is in preview.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "7624", + "name": "DataRobot", + "description": "A collection of DataRobot agent skills for model training, deployment, predictions, monitoring, and more.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 13, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3795", + "name": "dbt Labs", + "description": "Agent skills for dbt: data modeling, analytics engineering, semantic layer metrics, unit testing, job troubleshooting, and dbt MCP server setup. Covers dbt Core and dbt Cloud workflows.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 9, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "63853950", + "name": "Descript", + "description": "Edit video and audio by editing text. Import media, create projects, and use AI to edit — all from your agent.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579811", + "name": "discord", + "description": "Discord channel for Claude Code — messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /discord:access.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6306", + "name": "Docs Canvas", + "description": "Render documentation as a navigable canvas.", + "category": "Canvas", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "6306", + "name": "Docs Canvas", + "skill_count": 1, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "docs-canvas", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "0.1.0", + "skillCount": 1, + "agentCount": 0, + "hasMcp": false, + "skillNames": [ + "docs-canvas" + ], + "agentNames": [] + }, + "marketplaceSource": "docs-canvas" + }, + { + "stableId": "49339688", + "name": "Docusign", + "description": "Manage envelopes, templates, workflows, and agreements.", + "category": "Finance And Legal", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/docusign" + }, + { + "stableId": "53761261", + "name": "Dropbox", + "description": "The Dropbox plugin for Cursor connects your Dropbox files directly to Cursor, so you can search, reference, and organize files, save generated content, and create sharing links - all without switching tools. It respects your existing Dropbox permissions, and Cursor only works with files you already have access to.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "51244788", + "name": "Dynatrace", + "description": "Dynatrace observability skills. DQL query patterns, application and infrastructure monitoring, log analysis, problem investigation, and incident response workflows. Use with dtctl or the Dynatrace MCP server (https://docs.dynatrace.com/docs/shortlink/dynatrace-mcp-server) for live platform access.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 30, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "4331", + "name": "Elastic", + "description": "Elastic skills and documentation — Elasticsearch, Kibana, Observability, Security, Cloud, ES|QL, OpenTelemetry, and MCP docs access", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 26, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56235003", + "name": "ElevenLabs", + "description": "ElevenLabs voice AI — text-to-speech, voice agents, and speech engine skills, agent management via the ElevenLabs hosted MCP server, plus Architect skills for building and iterating on ElevenLabs agents.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 42, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3780", + "name": "Encore", + "description": "Build backends in TypeScript and Go with automatic infrastructure. Includes MCP integration for inspecting services, querying databases, analyzing traces, and calling endpoints. Plus rules, skills, and commands for the full Encore workflow.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 21, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "760", + "name": "Endor Labs Agent Kit", + "description": "Endor Labs Agent Kit setup and security workflow agents and skills for Cursor.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 12, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "842", + "name": "eToro", + "description": "eToro API integration for Cursor — rules, skills, and live API documentation for building on the eToro Public API. Covers OAuth SSO, market data, trading, account-snapshot calculations, agent-portfolios (with bulk, rebalance, and conditional-rule flows), and social features.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "55648770", + "name": "EULER for Partner Managers", + "description": "Skills for partner-relationship managers (customer-admin scope) that orchestrate the EULER MCP: generate a Quarterly Business Review, pre-meeting partner briefings, a whole-portfolio pulse, and approvals triage. Self-contained HTML output styled with the EULER design system.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "55648771", + "name": "EULER for Partners", + "description": "Self-service skills for partners that orchestrate the EULER MCP: view your own performance scorecard, onboarding and certification progress, referral history and deal registrations, and submit a new referral. Self-contained HTML output styled with the EULER design system.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "5636", + "name": "Exa", + "description": "Web search and content extraction powered by Exa AI", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56284038", + "name": "Executor", + "description": "A universal tool gateway for AI agents. Connect anything once, then use it across Cursor and every other agent harness.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579813", + "name": "fakechat", + "description": "Localhost iMessage-style web chat for Claude Code — test surface with file upload and edits. No tokens, no access control.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "48899256", + "name": "Falconer", + "description": "Read, search, and update Falconer documents from Cursor through hosted HTTP MCP.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809972", + "name": "Fathom", + "description": "Search meetings and pull transcripts and summaries.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/fathom" + }, + { + "stableId": "3579815", + "name": "firebase", + "description": "Google Firebase MCP integration. Manage Firestore databases, authentication, cloud functions, hosting, and storage. Build and manage your Firebase backend directly from your development workflow.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3777", + "name": "Firebase", + "description": "The official Firebase Cursor plugin. Prototype, build, and run modern apps with Firebase's backend and AI infrastructure.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 11, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "789", + "name": "Firecrawl", + "description": "Web scraping, crawling, and search for AI agents. Gives Cursor full access to web content through the Firecrawl CLI.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 10, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809973", + "name": "Fireflies", + "description": "Search meeting transcripts, summaries, and action items.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/fireflies" + }, + { + "stableId": "1410", + "name": "Firetiger", + "description": "Cursor plugin for Firetiger agentic operations plugin with skills and MCP access.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "32632546", + "name": "Forge", + "description": "Forge — Work orders, journey progress, artifacts, ForgeScore, dev activity, and project context for AI-assisted development workflows.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579816", + "name": "frontend-design", + "description": "Frontend design skill for UI/UX implementation", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "3579816", + "name": "frontend-design", + "skill_count": 1, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "claude-plugins-official", + "slug": "frontend-design", + "versionHash": "1a2f18b05cf5652fd25403e8d229fc884fb84103", + "version": null, + "skillCount": 1, + "agentCount": 0, + "hasMcp": false, + "skillNames": [ + "frontend-design" + ], + "agentNames": [] + } + }, + { + "stableId": "39789426", + "name": "GitBook", + "description": "Create, configure, and author GitBook documentation sites — site orchestration via the GitBook REST API, Git Sync setup, branding customization, and GitBook-flavored Markdown page authoring.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "47475084", + "name": "GitHits", + "description": "The code context layer for AI coding agents", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579817", + "name": "github", + "description": "Official GitHub MCP server for repository management. Create issues, manage pull requests, review code, search repositories, and interact with GitHub's full API directly from Claude Code.", + "category": "MCP", + "installed": "no", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "48677658", + "name": "GitHub", + "skill_count": 0, + "connector_count": 1, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "github", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "1.0.0", + "skillCount": 0, + "agentCount": 0, + "hasMcp": true, + "skillNames": [], + "agentNames": [] + }, + "marketplaceSource": "third_party/github" + }, + { + "stableId": "48677658", + "name": "GitHub", + "description": "Manage repos, issues, pull requests, and Actions.", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "48677658", + "name": "GitHub", + "skill_count": 0, + "connector_count": 1, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "github", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "1.0.0", + "skillCount": 0, + "agentCount": 0, + "hasMcp": true, + "skillNames": [], + "agentNames": [] + }, + "marketplaceSource": "third_party/github" + }, + { + "stableId": "3579818", + "name": "gitlab", + "description": "GitLab DevOps platform integration. Manage repositories, merge requests, CI/CD pipelines, issues, and wikis. Full access to GitLab's comprehensive DevOps lifecycle tools.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "1220", + "name": "GitLab", + "description": "Connect Cursor to GitLab with the GitLab MCP server. Plan, track, and manage issues, merge requests, and pipelines from your editor.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "734", + "name": "Glean", + "description": "Official Glean plugin — search documents, Slack, and email; explore code across repos; find experts and stakeholders; prep for meetings and onboarding.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 19, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "58732520", + "name": "Glean Developer Docs", + "description": "Search the public Glean developer documentation — APIs, SDKs, MCP, and integration guides for building with Glean.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "45893410", + "name": "Gmail", + "description": "Search, read, draft, and manage email.", + "category": "Featured", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "45893410", + "name": "Gmail", + "skill_count": 0, + "connector_count": 1, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "gmail", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "1.0.0", + "skillCount": 0, + "agentCount": 0, + "hasMcp": true, + "skillNames": [], + "agentNames": [] + }, + "marketplaceSource": "third_party/gmail" + }, + { + "stableId": "56809974", + "name": "GoDaddy", + "description": "Brainstorm domain names and check availability.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/godaddy" + }, + { + "stableId": "62120239", + "name": "Gojiberry AI", + "description": "Connect Cursor to your Gojiberry workspace. Search and update contacts, manage lists and campaigns, refine lead-finding agents, and read Unibox conversations through the hosted MCP at mcp.gojiberry.ai.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "63467641", + "name": "Golf Intelligence by Stracka", + "description": "Golf Intelligence, by Stracka. The highest-quality golf course dataset for developers building a golf app. 20 years of proprietary mapping (laser, drone, airplane, satellite), updated daily. Search is free. Scorecards, GPS, and 3D greens via API. Not a scrape.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "47725204", + "name": "Gong", + "description": "Pull account summaries, deal insights, and call briefs.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/gong" + }, + { + "stableId": "45893411", + "name": "Google Calendar", + "description": "Search events and schedule meetings.", + "category": "Featured", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "45893411", + "name": "Google Calendar", + "skill_count": 0, + "connector_count": 1, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "google-calendar", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "1.0.0", + "skillCount": 0, + "agentCount": 0, + "hasMcp": true, + "skillNames": [], + "agentNames": [] + }, + "marketplaceSource": "third_party/google-calendar" + }, + { + "stableId": "45893413", + "name": "Google Drive", + "description": "Search, read, create, and share files.", + "category": "Featured", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/google-drive" + }, + { + "stableId": "3579819", + "name": "gopls-lsp", + "description": "Go language server for code intelligence and refactoring", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "146017", + "name": "Grafana Cloud", + "description": "Hosted MCP server for AI-assisted Grafana Cloud observability — no local installation required.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "736", + "name": "Grafana Labs", + "description": "Skills and rules for developing and using the Grafana Assistant app and CLI.", + "category": "Data Analytics", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index" + ], + "liveInstalled": { + "plugin_id": "736", + "name": "Grafana Labs", + "skill_count": 1, + "connector_count": 0, + "installed": true + } + }, + { + "stableId": "5609", + "name": "Granola", + "description": "Your meetings in your workflow. Granola gives Cursor access to what your team discussed, decided, and committed to.", + "category": "Featured", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579820", + "name": "greptile", + "description": "AI code review agent for GitHub and GitLab. View and resolve Greptile's PR review comments directly from Claude Code.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "62459237", + "name": "Grok Voice", + "description": "Add Grok voice to an app: realtime speech-to-speech, speech-to-text dictation, text-to-speech read-aloud, and a log-driven fix loop for voice sessions.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 4, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "grok-voice" + }, + { + "stableId": "7194", + "name": "GSAP", + "description": "Official GSAP skills for Cursor, Claude and other AI agents — core animations, timelines, ScrollTrigger, plugins, utilities, React integration, and performance best practices", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 8, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "58735216", + "name": "Gumloop", + "description": "Connect Cursor to Gumloop's hosted MCP server.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809975", + "name": "Guru", + "description": "Search company knowledge and draft verified answers.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/guru" + }, + { + "stableId": "62309982", + "name": "Harmonic", + "description": "Discover, research, and enrich companies and people", + "category": "Research", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "8006", + "name": "Harness", + "description": "Harness Skills + Harness MCP server packaged as a Cursor plugin. Build, debug, deploy, and govern via Harness from Cursor.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 30, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "50420288", + "name": "here.now", + "description": "Publish websites, apps, and files to live URLs at {slug}.here.now or custom domains. Publish HTML apps, documents, images, PDFs, videos, and static files, with access control, custom domains, analytics, and private Drive storage for agent files.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "651", + "name": "Hex", + "description": "AI analytics and data collaboration - connect Cursor to your Hex workspace with MCP", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "14372196", + "name": "HeyGen", + "description": "Create HeyGen avatar videos, personalized video messages, and translated / dubbed videos. Build a persistent digital identity from a photo, generate presenter-led videos with your digital twin, and localize existing videos into 175+ languages with voice cloning and lip-sync.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "5420454", + "name": "Higgsfield", + "description": "Generate images, videos, and more using Higgsfield MCP from Cursor.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579821", + "name": "hookify", + "description": "Easily create hooks to prevent unwanted behaviors by analyzing conversation patterns", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "53543399", + "name": "Hostinger", + "description": "", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 8, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "49339689", + "name": "HubSpot", + "description": "Search and update contacts, companies, deals, and tickets.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/hubspot" + }, + { + "stableId": "735", + "name": "Hugging Face", + "description": "Agent Skills for AI/ML tasks including dataset creation, model training, evaluation, and research paper publishing on Hugging Face Hub", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 18, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "62459238", + "name": "Hunter", + "description": "Find and verify emails, discover companies, and save leads.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/hunter" + }, + { + "stableId": "12838857", + "name": "IcePanel", + "description": "Cursor Plugin for IcePanel - enables AI assistants to manage models, connections, and more across your IcePanel landscape", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579822", + "name": "imessage", + "description": "iMessage channel for Claude Code — reads chat.db directly, sends via AppleScript. Built-in access control; manage pairing, allowlists, and policy via /imessage:access.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "59966080", + "name": "incident.io", + "description": "Makes your agent fluent in incident.io: responding to and investigating incidents, working with on-call schedules and escalations, and authoring the operational content — runbooks, skills, and plugins — that incident.io investigations draw on. Bundles the official incident.io MCP server.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "63835155", + "name": "Infobip", + "description": "Infobip MCP Servers let you build AI agents to interact with the Infobip platform through the Model Context Protocol (MCP). Connect to Infobip and enable your agents to perform actions, such as sending messages over channels like SMS, WhatsApp, or Viber, or managing customer data in a controlled, production-grade environment.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 12, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "57102943", + "name": "Inkbox", + "description": "Give Cursor an Inkbox identity for email, SMS, iMessage, voice calls, contacts, notes, and agent-to-agent tasks.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "49339690", + "name": "Intercom", + "description": "Search conversations, contacts, and Help Center articles.", + "category": "Customer Support", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/intercom" + }, + { + "stableId": "3579823", + "name": "jdtls-lsp", + "description": "Java language server (Eclipse JDT.LS) for code intelligence", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "1291", + "name": "JFrog", + "description": "JFrog Platform integration with MCP, security skills, Agent Package Resolution, supply-chain best practices, and JFrog Agent Guard governance for adding, removing, and listing MCP servers.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809976", + "name": "Jotform", + "description": "Create and edit forms, then read submissions.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/jotform" + }, + { + "stableId": "52606141", + "name": "Juicebox", + "description": "Query recruiting analytics, shortlists, and sourcing agents.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/juicebox" + }, + { + "stableId": "56809977", + "name": "Klaviyo", + "description": "Manage profiles, segments, campaigns, and flows.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/klaviyo" + }, + { + "stableId": "60302089", + "name": "Knock", + "description": "Notify users across email, in-app, and more", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56286216", + "name": "Kody", + "description": "Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "56286216", + "name": "Kody", + "skill_count": 0, + "connector_count": 1, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "kody", + "versionHash": "dcee10be269da1784821f51be97f2393168f0866", + "version": null, + "skillCount": 0, + "agentCount": 0, + "hasMcp": true, + "skillNames": [], + "agentNames": [] + } + }, + { + "stableId": "3579824", + "name": "kotlin-lsp", + "description": "Kotlin language server for code intelligence", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26159727", + "name": "Kraken", + "description": "Agent-first CLI for trading crypto, stocks, forex, and derivatives on Kraken. Paper trading by default; live trading opt-in via API keys.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 51, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "738", + "name": "Langfuse", + "description": "Skills for working with Langfuse — the open-source LLM engineering platform for tracing, prompt management, and evaluation.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579825", + "name": "laravel-boost", + "description": "Laravel development toolkit MCP server. Provides intelligent assistance for Laravel applications including Artisan commands, Eloquent queries, routing, migrations, and framework-specific code generation.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "800", + "name": "LaunchDarkly", + "description": "LaunchDarkly agent skills and mcp server for feature flag management, AI configuration, and skill authoring", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 14, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579827", + "name": "linear", + "description": "Linear issue tracking integration. Create issues, manage projects, update statuses, search across workspaces, and streamline your software development workflow with Linear's modern issue tracker.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "512", + "name": "Linear", + "description": "The product development system for teams and agents. Manage issues, projects, documents, and everything else across your Linear workspace.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "47709840", + "name": "Link", + "description": "Complete purchases with one-time-use payment credentials from a Link wallet, using spend requests the user has approved.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "57763987", + "name": "Linq", + "description": "Build iMessage, RCS, and SMS messaging on Linq. Conventions, guided quickstarts, and MCP access to the Linq API for the linq CLI and @linqapp/sdk.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26115160", + "name": "Lovable", + "description": "Drive Lovable from Cursor: create projects, send work to the Lovable agent, manage Lovable Cloud databases, and deploy, all through the Lovable MCP server.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579828", + "name": "lua-lsp", + "description": "Lua language server for code intelligence", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "41756884", + "name": "Lucid", + "description": "Ideate, diagram, and align teams by connecting Cursor to Lucid. Search, generate, and manage your technical architecture directly from the editor.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "33865099", + "name": "Magic Patterns", + "description": "Use Magic Patterns (magicpatterns.com) from Cursor: prototype ideas, generate UI inspiration, upload local UI, and integrate Magic Patterns designs into your codebase.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "21951242", + "name": "MagicPath", + "description": "Design and build interactive UI on a shared canvas. Bring local code and repositories into MagicPath, install or export designs, and map ideas with diagrams, flowcharts, wireframes, user journeys, and annotations.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809978", + "name": "MailerLite", + "description": "Manage subscribers, groups, campaigns, and automations.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/mailerlite" + }, + { + "stableId": "53566285", + "name": "Mailgun", + "description": "Mailgun workflows for Cursor: sending, deliverability triage, DNS verification, suppressions, templates, Inspect, Optimize, Validate, and cross-domain reporting.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 15, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "21022343", + "name": "Mainframe", + "description": "Create and share short video updates from agent work.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "52964995", + "name": "Manufact", + "description": "Build, test, and deploy MCP servers and apps with mcp-use and Manufact. Includes the Manufact MCP server, the mcp-use docs MCP server, and the mcp-apps-builder skill.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579829", + "name": "math-olympiad", + "description": "Solve competition math (IMO, Putnam, USAMO) with adversarial verification that catches what self-verification misses. Fresh-context verifiers attack proofs with specific failure patterns. Calibrated abstention over bluffing.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579830", + "name": "mcp-server-dev", + "description": "Skills for designing and building MCP servers that work seamlessly with Claude — guides you through deployment models (remote HTTP, MCPB, local), tool design patterns, auth, and interactive MCP apps.", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 3, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "3579830", + "name": "mcp-server-dev", + "skill_count": 3, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "claude-plugins-official", + "slug": "mcp-server-dev", + "versionHash": "1a2f18b05cf5652fd25403e8d229fc884fb84103", + "version": null, + "skillCount": 3, + "agentCount": 0, + "hasMcp": false, + "skillNames": [ + "build-mcp-app", + "build-mcp-server", + "build-mcpb" + ], + "agentNames": [] + } + }, + { + "stableId": "56809979", + "name": "Mem", + "description": "Capture, search, and organize notes and collections.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/mem" + }, + { + "stableId": "5648", + "name": "Mem0", + "description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 16, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809980", + "name": "Mercury", + "description": "Read balances, transactions, statements, and cards.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/mercury" + }, + { + "stableId": "47679354", + "name": "Meta Reality Labs", + "description": "Agent skills for Meta Quest and Horizon OS development. Helps Claude assist with Quest app debugging, performance analysis, project setup, Unity and WebXR workflows, Spatial SDK and Platform SDK integration, store submission checks, and metavr device workflows.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 29, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "47739056", + "name": "Meticulous", + "description": "Agent skills for Meticulous visual regression testing — review test runs, investigate replays, debug diffs.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26156871", + "name": "Microsoft Dataverse", + "description": "Microsoft Dataverse plugin for coding agents — powering CRUD, bulk data operations, advanced queries, schema lifecycle, and environment management through intelligent skills that unify MCP, CLI, and SDK workflows.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 8, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "25808295", + "name": "Mintlify", + "description": "Comprehensive reference for building Mintlify documentation sites.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "2911", + "name": "Miro", + "description": "Secure access to Miro boards. Enables AI to read board context, create diagrams, and generate code with enterprise-grade security.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "11976", + "name": "Mixpanel", + "description": "Mixpanel skills for Cursor — tracking implementation, metric investigation, and more.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "52632771", + "name": "Mobbin", + "description": "Search real-world UI & UX design references for mobile apps, web apps, and websites with Mobbin.", + "category": "Design", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6398877", + "name": "Modern Web Guidance", + "description": "Modern Web Guidance is an agent skill and CLI tool designed to help AI coding agents build web applications using modern, secure, and high-performance APIs rather than outdated workarounds. Supported by the Google Chrome team, it injects expert-curated web platform best practices directly into an agent's context window to prevent the generation of bloated, legacy code.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60854710", + "name": "Monaco", + "description": "", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "21010834", + "name": "Monday.com", + "description": "Seven skills for monday CRM users — first-run setup, morning briefings, forecast dashboards, board diagnosis, bulk data hygiene, workspace setup, and meeting-to-opportunity sync. Each skill publishes a monday artifact (update, doc, or dashboard) so work stays inside monday. Composes the official monday MCP connector; no custom server, no new auth.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3277", + "name": "MongoDB", + "description": "Connect to any MongoDB deployment (Community, Enterprise Advanced, local dev container via Atlas CLI or Atlas clusters) through a self-managed MongoDB MCP Server using your connection string. Explore data, manage collections, optimize queries, and generate reliable code with MongoDB best practices.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "47235838", + "name": "MongoDB Atlas", + "description": "Connect to MongoDB Atlas clusters only through the Atlas Managed MCP Server. Sign in with your Atlas account to explore data, manage collections, optimize queries, generate reliable code with MongoDB best practices, and manage Atlas resources such as clusters, projects, database users, and network access. If you are running a different MongoDB deployment or want to run a local MongoDB MCP Server, install the MongoDB (Self-Managed MCP) plugin instead.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "63373743", + "name": "Monid", + "description": "Discover and run hundreds of data endpoints — web scraping, people/company enrichment, social media, and search — through one interface. Connects to the hosted Monid MCP server; falls back to the Monid CLI when a shell is available.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "13750661", + "name": "Monk.io", + "description": "Deploy and operate full applications with Monk — cloud infra, SaaS integrations, and containerized workloads — from one chat.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "50103284", + "name": "Navan", + "description": "Query expenses, travel bookings, policies, and cards.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/navan" + }, + { + "stableId": "669", + "name": "Neon Postgres", + "description": "Manage your Neon projects, databases, and branches with the Neon agent skills and the Neon MCP Server", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 8, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "20992516", + "name": "Netlify", + "description": "Netlify platform skills — functions, edge functions, blobs, database, identity, image CDN, forms, config, CLI, frameworks, caching, AI gateway, and deployment", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 13, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "404", + "name": "Notion", + "description": "Notion Skills + Notion MCP server packaged as a Cursor plugin.", + "category": "Featured", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 14, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "43019816", + "name": "Nvidia Skills", + "description": "Skills for NVIDIAs ecosystem spans GPU acceleration, CUDA, AI agents, inference, robotics, Physical AI, Omniverse, and simulation. This plugin helps you find the right skills to help in building NVIDIA-powered workflows.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "41749318", + "name": "Observe by Snowflake", + "description": "Agent skills and MCP configuration for deploying and managing Observe's observability infrastructure", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 16, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "63841619", + "name": "Omneky", + "description": "Generate and launch ads, manage products, and report performance in Cursor via Omneky MCP", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "2729", + "name": "Omni", + "description": "Explore, query, model, embed, and manage Omni Analytics through the REST API and embed SDK. Includes 8 skills for model exploration, querying, model building, content browsing, dashboard creation, embedding, AI optimization, and administration — plus 3 specialized subagents and always-on rules for API conventions.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 8, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "57302028", + "name": "OneDrive", + "description": "Browse, search, and read Microsoft OneDrive files.", + "category": "Documents And Files", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/onedrive" + }, + { + "stableId": "38157427", + "name": "OneSignal", + "description": "Connect Cursor to OneSignal through the OneSignal MCP server.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6846", + "name": "OpenSearch", + "description": "OpenSearch skills to help set up and deploy OpenSearch for a variety of use cases: build search applications with semantic, hybrid, neural sparse, and agentic search strategies; analyze observability data with log analytics (PPL and Query DSL) and distributed traces (OpenTelemetry); deploy to Amazon OpenSearch Service or OpenSearch Serverless with Bedrock integration for embeddings and RAG. Just ask your AI assistant to set up a search app, query logs, investigate traces, or deploy to AWS.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 4, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6848", + "name": "Opsera", + "description": "Opsera DevSecOps Agent — AI-powered architecture analysis, security scanning, compliance auditing, and SQL security for your codebase. Free trial included.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "9333", + "name": "Orchestrate", + "description": "Fan a large task out across parallel cloud agents via the Cursor SDK: planners publish tasks, workers hand off back up, and a script reconciles the tree from disk and git.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "orchestrate" + }, + { + "stableId": "56809981", + "name": "Otter.ai", + "description": "Search meeting history and pull full transcripts.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "57302029", + "name": "Outlook", + "description": "Search, read, and send Microsoft Outlook email, and look up contacts.", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/outlook" + }, + { + "stableId": "57302030", + "name": "Outlook Calendar", + "description": "List, create, update, and cancel Microsoft Outlook calendar events.", + "category": "Scheduling", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/outlook-calendar" + }, + { + "stableId": "53338793", + "name": "Outreach", + "description": "Search sequences, prospects, and Kaia meetings.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/outreach" + }, + { + "stableId": "1089", + "name": "PagerDuty", + "description": "PagerDuty MCP server for Cursor - manage incidents, services, schedules, on-call, and more directly from your IDE.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "62966224", + "name": "Palo Alto Networks", + "description": "", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6062", + "name": "Paper", + "description": "Design on a canvas that Cursor can read and write to — built on web standards.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "13955831", + "name": "ParadeDB", + "description": "ParadeDB adds Elastic-quality full-text search, vector retrieval, and aggregations to Postgres, with no second system to deploy and nothing to sync. This plugin teaches agents how to use ParadeDB effectively by giving them access to the documentation MCP server.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "698", + "name": "Parallel", + "description": "Web search, content extraction, deep research, and data enrichment powered by parallel-cli.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3508", + "name": "Pendo", + "description": "Bring Pendo analytics into Cursor with skills for account health, feature adoption, session replays, and feedback analysis", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "704", + "name": "Phantom", + "description": "Give your agent a wallet. Swap, sign, and manage addresses across Phantom's supported chains — and tap into Phantom's docs for context.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 8, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579831", + "name": "php-lsp", + "description": "PHP language server (Intelephense) for code intelligence", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60310094", + "name": "Pi Security", + "description": "Use Pi Security in Cursor IDE to investigate findings, review security posture, plan remediation, and get guidance while you build.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "4339", + "name": "Pinecone", + "description": "Pinecone vector database integration for Cursor. Create and manage indexes, upsert data, and run semantic searches via the Pinecone MCP server. Build document Q&A assistants with citations, or get started fast with /pinecone-quickstart. Great for semantic search, RAG, and agentic AI apps.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 8, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3131", + "name": "Plain", + "description": "Connect Cursor to Plain — manage support threads, customers, tenants, and help center articles directly from your editor.", + "category": "Customer Support", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "741", + "name": "PlanetScale", + "description": "An authenticated hosted MCP server that accesses your PlanetScale organizations, databases, branches, schema, and Insights data.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 19, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579833", + "name": "playground", + "description": "Creates interactive HTML playgrounds — self-contained single-file explorers with visual controls, live preview, and prompt output with copy button", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579834", + "name": "playwright", + "description": "Browser automation and end-to-end testing MCP server by Microsoft. Enables Claude to interact with web pages, take screenshots, fill forms, click elements, and perform automated browser testing workflows.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/playwright" + }, + { + "stableId": "48677659", + "name": "Playwright", + "description": "Navigate, click, screenshot, and test in a real browser.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/playwright" + }, + { + "stableId": "3579836", + "name": "plugin-dev", + "description": "Plugin development toolkit with skills for creating agents, commands, hooks, MCP integrations, and comprehensive plugin structure guidance", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "61018587", + "name": "ponytail", + "description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26177969", + "name": "Port", + "description": "Port MCP Server Gives Cursor's AI agent full engineering context. Port is your system of record for every service, team, dependency, and standard - connect it to Cursor so the agent understands not just your code, but who owns it, what depends on it, and what actions are available to take.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "730", + "name": "PostHog", + "description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Cursor", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 73, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "58737848", + "name": "postiz", + "description": "Social media automation CLI for scheduling posts, managing integrations, uploading media, and tracking analytics across 28+ platforms including X, LinkedIn, Reddit, YouTube, TikTok, Instagram, and more", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "2050", + "name": "Postman", + "description": "Full API lifecycle management for Cursor. Sync collections, generate OpenAPI specs, discover APIs, run tests and Flows, create mocks, improve docs, and audit security. Powered by the Postman MCP Server.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6307", + "name": "PR Review Canvas", + "description": "Render PR diffs as review canvases grouped by importance.", + "category": "Canvas", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "6307", + "name": "PR Review Canvas", + "skill_count": 1, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "pr-review-canvas", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "0.1.0", + "skillCount": 1, + "agentCount": 0, + "hasMcp": false, + "skillNames": [ + "pr-review-canvas" + ], + "agentNames": [] + }, + "marketplaceSource": "pr-review-canvas" + }, + { + "stableId": "353", + "name": "Prisma", + "description": "The official Prisma plugin for Cursor: MCP server integration, rules, skills, and automation for database development", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 40, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "51168001", + "name": "Profound", + "description": "Track AI visibility, sentiment, and citations.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/profound" + }, + { + "stableId": "9717366", + "name": "pstack", + "description": "if you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence.", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 47, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json", + "GetPlugin" + ], + "liveInstalled": { + "plugin_id": "9717366", + "name": "pstack", + "skill_count": 47, + "connector_count": 0, + "installed": true, + "detail": "pstack.json" + }, + "cache": { + "marketplace": "cursor-public", + "slug": "pstack", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "0.15.2", + "skillCount": 47, + "agentCount": 2, + "hasMcp": false, + "skillNames": [ + "architect", + "arena", + "automate-me", + "blast-radius", + "bro", + "create-verification-skill", + "figure-it-out", + "how", + "interrogate", + "maintain-verification-skill", + "make-bot-ui", + "no-comments", + "poteto-mode", + "principle-attack-the-premise", + "principle-boundary-discipline", + "principle-build-the-lever", + "principle-encode-lessons-in-structure", + "principle-exhaust-the-design-space", + "principle-experience-first", + "principle-fix-root-causes", + "principle-foundational-thinking", + "principle-guard-the-context-window", + "principle-laziness-protocol", + "principle-make-operations-idempotent", + "principle-migrate-callers-then-delete-legacy-apis", + "principle-minimize-reader-load", + "principle-model-the-domain", + "principle-never-block-on-the-human", + "principle-outcome-oriented-execution", + "principle-prove-it-works", + "principle-redesign-from-first-principles", + "principle-separate-before-serializing-shared-state", + "principle-sequence-verifiable-units", + "principle-subtract-before-you-add", + "principle-test-behavior-not-implementation", + "principle-type-system-discipline", + "recall", + "reflect", + "setup-pstack", + "show-me-your-work", + "swarm", + "tdd", + "teach", + "technical-writing", + "typescript-best-practices", + "unslop", + "why" + ], + "agentNames": [ + "comment-sicko", + "poteto-agent" + ] + }, + "marketplaceSource": "pstack" + }, + { + "stableId": "3579838", + "name": "pyright-lsp", + "description": "Python language server (Pyright) for type checking and code intelligence", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "3579838", + "name": "pyright-lsp", + "skill_count": 0, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "claude-plugins-official", + "slug": "pyright-lsp", + "versionHash": "1a2f18b05cf5652fd25403e8d229fc884fb84103", + "version": null, + "skillCount": 0, + "agentCount": 0, + "hasMcp": false, + "skillNames": [], + "agentNames": [] + } + }, + { + "stableId": "20785328", + "name": "QuiverAI", + "description": "Create, refine, and vectorize SVG assets with the hosted QuiverAI MCP server.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "63446691", + "name": "Quo", + "description": "Give your agent a phone number. Use Quo to send and review business texts, manage contacts and tasks, and follow up on missed calls.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "29413143", + "name": "Railway", + "description": "Railway agent skills and hosted MCP server for deploying, configuring, monitoring, and troubleshooting apps and infrastructure on Railway from Cursor. Manage services, environments, deployments, databases, object storage, networking, and observability.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "18281353", + "name": "Raisely", + "description": "Connect Cursor to Raisely. Ships the Raisely MCP server so you can query and manage campaigns, donations, supporters, and more from chat.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "32766883", + "name": "Ramp", + "description": "Connect Cursor to Ramp via MCP and drive spend analysis, approvals, transaction cleanup, and vendor workflows.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 14, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "41082969", + "name": "React Doctor", + "description": "Scan, understand, and fix React codebase diagnostics with React Doctor.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809982", + "name": "Readwise", + "description": "Search highlights and Reader documents, save articles.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/readwise" + }, + { + "stableId": "733", + "name": "Redis", + "description": "Redis development best practices — data structures, query engine, vector search, caching, and performance optimization", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "49339676", + "name": "Remotion", + "description": "Remotion video creation skills — best practices, animations, audio, captions, 3D, and more for building programmatic videos with React.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 12, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "1295", + "name": "Render", + "description": "Deploy, debug, and monitor applications on Render. Includes skills, rules, commands, an agent, MCP config, and hooks for the full Render workflow.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 21, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "5188", + "name": "Resend", + "description": "Skills and MCP server for the Resend email platform — sending, receiving, templates, CLI, React Email, and deliverability best practices.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 5, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "42859383", + "name": "Resolve AI", + "description": "Use Resolve for investigations, incidents, and production context.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 13, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "59963775", + "name": "Restream", + "description": "Live-code to an audience from Cursor: stream to social, a webinar page, or Slack, record only, or let AI clip the highlights. Remote MCP server with tools for events, destinations, live chat, and analytics.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26087976", + "name": "RevenueCat", + "description": "Configure your RevenueCat integration and access data from your RevenueCat projects.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 14, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26087977", + "name": "RevenueCat Play Billing", + "description": "Deep Google Play subscription lifecycle skills for the RevenueCat Android SDK — purchases, plan and price changes, payment recovery, webhooks, security.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 20, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "12836227", + "name": "Revolut X", + "description": "Use the Revolut X CLI (revx) for crypto trading, market data, monitoring, and grid bot strategies", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "44746620", + "name": "Revyl", + "description": "Installs the Revyl CLI and signs you in so Cursor can run and verify mobile apps on cloud devices.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "63383076", + "name": "Riverside", + "description": "Connects Cursor to Riverside — search recordings by what was said in them, edit video timelines, and publish clips to social platforms.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26126658", + "name": "Roboflow", + "description": "Roboflow computer vision skills and MCP tools for datasets, annotation, training, workflows, inference, and deployment.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60820336", + "name": "rstack", + "description": "Agent Skills for debugging, tracing, upgrading, and analyzing Rstack projects.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 22, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579840", + "name": "ruby-lsp", + "description": "Ruby language server for code intelligence and analysis", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "739", + "name": "Runlayer", + "description": "Simpler, safer way to run MCPs, Skills, and Agents. Discover what's running, enforce security policies in real time, protect secrets, and get full audit trails — all built for how developers actually work in Cursor.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579841", + "name": "rust-analyzer-lsp", + "description": "Rust language server for code intelligence and analysis", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "3579841", + "name": "rust-analyzer-lsp", + "skill_count": 0, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "claude-plugins-official", + "slug": "rust-analyzer-lsp", + "versionHash": "1a2f18b05cf5652fd25403e8d229fc884fb84103", + "version": null, + "skillCount": 0, + "agentCount": 0, + "hasMcp": false, + "skillNames": [], + "agentNames": [] + } + }, + { + "stableId": "59510268", + "name": "Ryze AI", + "description": "Google Ads, Meta Ads, GA4, Search Console, SEO/GEO/AEO in Cursor — audit, report, optimize ads and rankings", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "47725205", + "name": "Salesforce", + "description": "Query, create, and update records in your org.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/salesforce" + }, + { + "stableId": "1198", + "name": "Sanity", + "description": "Sanity plugin for Cursor with MCP server, agent skills, agent rules, and commands.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "7851", + "name": "Scandit", + "description": "AI agent skills for integrating the Scandit Data Capture SDK — product selection, documentation, and implementation guides for barcode scanning, ID capture, and smart label capture.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 74, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "14021272", + "name": "ScyllaDB", + "description": "Official ScyllaDB agent skills. Bundles guided skills for ScyllaDB Cloud setup, CQL data modeling, and Vector Search; each is invoked independently by the agent when relevant.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "764", + "name": "Semgrep", + "description": "Plugin by Semgrep. Provides security scanning via MCP, hooks, and skills for Cursor.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809983", + "name": "Semrush", + "description": "Research keywords, backlinks, traffic, and competitors.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/semrush" + }, + { + "stableId": "60864284", + "name": "SenseLab", + "description": "Continual learning for your agents. What one works out carries across sessions, tools and machines, is reinforced by how the work turned out, and is shared through team rooms.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "579", + "name": "Sentry", + "description": "Sentry Plugin for Cursor to help with debugging including MCP and skill capabilities.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579843", + "name": "serena", + "description": "Semantic code analysis MCP server providing intelligent code understanding, refactoring suggestions, and codebase navigation through language server protocol integration.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579844", + "name": "session-report", + "description": "Generate an explorable HTML report of Claude Code session usage — tokens, cache efficiency, subagents, skills, and the most expensive prompts — from local ~/.claude/projects transcripts.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6948", + "name": "shadcn/ui", + "description": "UI component and design system framework. Search registries, install components as source code, and audit your project.", + "category": "Agent Orchestration", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "6948", + "name": "shadcn/ui", + "skill_count": 1, + "connector_count": 1, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "shadcn", + "versionHash": "10f1717a3e2a3c16cfbd43877c1e44063d9d749a", + "version": "1.0.0", + "skillCount": 1, + "agentCount": 0, + "hasMcp": false, + "skillNames": [ + "shadcn" + ], + "agentNames": [] + } + }, + { + "stableId": "3970", + "name": "Shopify", + "description": "Shopify developer tools for Cursor — search Shopify docs, generate and validate GraphQL, Liquid, and UI extension code. Skill scripts send usage telemetry (queries, code, model/client identifiers) to shopify.dev by default; set OPT_OUT_INSTRUMENTATION=true to disable.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 21, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "58735209", + "name": "SideShift", + "description": "Operate your SideShift company from Cursor—manage campaigns, creators, applications, content, contracts, recruiting, messages, payouts, and more through secure OAuth.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809984", + "name": "Similarweb", + "description": "Analyze website traffic, audiences, and competitors.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/similarweb" + }, + { + "stableId": "8279", + "name": "Sinch", + "description": "Ship faster with Sinch in Cursor - SMS, WhatsApp, RCS, voice, email, verification, numbers, and more, all with the help of Skills and Sinch MCP.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 15, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579845", + "name": "skill-creator", + "description": "Create new skills, improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, or benchmark skill performance with variance analysis.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "674", + "name": "Slack", + "description": "Slack MCP server. Search channels, send messages, and perform other Slack actions through MCP-compatible clients.", + "category": "Featured", + "installed": "no", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 6, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "674", + "name": "Slack", + "skill_count": 6, + "connector_count": 1, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "slack", + "versionHash": "e75b0cf18f1a19f3fd629e3af9565ee84b8c2ce0", + "version": "1.1.0", + "skillCount": 6, + "agentCount": 0, + "hasMcp": true, + "skillNames": [ + "block-kit", + "create-slack-app", + "slack-api", + "slack-cli", + "slack-messaging", + "slack-search" + ], + "agentNames": [] + } + }, + { + "stableId": "56809985", + "name": "Smartsheet", + "description": "Query and update sheets, rows, and workspaces.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/smartsheet" + }, + { + "stableId": "653", + "name": "Snowflake", + "description": "", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "1292", + "name": "Snyk", + "description": "Snyk security scanning, remediation, and dependency health for Cursor", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "26101348", + "name": "Snyk API & Web", + "description": "Snyk API & Web MCP Server — onboard scan targets, configure authentication, run DAST scans, and triage findings through natural language. Built on FastMCP 2.0.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "20784535", + "name": "SonarQube", + "description": "Automatically enforce SonarQube code quality and security in the agent coding loop — 7,000+ rules, secrets scanning, agentic analysis, and quality gates across 40+ languages.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 9, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "10089", + "name": "Sonatype", + "description": "AI-powered dependency intelligence. Check vulnerabilities, find safer versions, and make better dependency decisions using Sonatype's component data.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "9347", + "name": "Sourcegraph", + "description": "", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "408", + "name": "Stripe", + "description": "Stripe plugin for Cursor to help with Stripe integrations including best practices, API/SDK upgrade guidance, and the Stripe MCP server.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "9297", + "name": "Subtext", + "description": "Review Subtext session recordings and manage privacy rules", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 10, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "652", + "name": "Supabase", + "description": "Access your Supabase projects and perform tasks like managing tables, fetching config, and querying data.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "55647251", + "name": "Superdesign", + "description": "Design or redesign frontend UI, presentations, and marketing graphics on the Superdesign infinite canvas. Uses approved slide outlines, project context, and branchable drafts you refine.", + "category": "Design", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "55647251", + "name": "Superdesign", + "skill_count": 1, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "superdesign", + "versionHash": "f9f05cd988c247dce6c072eaf9ac6b162f2ffc4b", + "version": "0.6.0", + "skillCount": 1, + "agentCount": 0, + "hasMcp": false, + "skillNames": [ + "superdesign" + ], + "agentNames": [] + } + }, + { + "stableId": "55650410", + "name": "Superhuman Mail", + "description": "", + "category": "Inbox And Collaboration", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60293083", + "name": "SuperMe", + "description": "Ask people who've been there, get multiple perspectives, and make better decisions about your work.", + "category": "Research", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "58578698", + "name": "Supermemory", + "description": "Persistent AI memory for Cursor — powered by Supermemory", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "684", + "name": "Superpowers", + "description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques", + "category": "Agent Orchestration", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 14, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "684", + "name": "Superpowers", + "skill_count": 14, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "superpowers", + "versionHash": "d884ae04edebef577e82ff7c4e143debd0bbec99", + "version": "6.1.1", + "skillCount": 14, + "agentCount": 0, + "hasMcp": false, + "skillNames": [ + "brainstorming", + "dispatching-parallel-agents", + "executing-plans", + "finishing-a-development-branch", + "receiving-code-review", + "requesting-code-review", + "subagent-driven-development", + "systematic-debugging", + "test-driven-development", + "using-git-worktrees", + "using-superpowers", + "verification-before-completion", + "writing-plans", + "writing-skills" + ], + "agentNames": [] + } + }, + { + "stableId": "2246", + "name": "Svelte", + "description": "A plugin for all things related to Svelte development, MCP, skills, and more.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579846", + "name": "swift-lsp", + "description": "Swift language server (SourceKit-LSP) for code intelligence", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "7621", + "name": "Tabnine", + "description": "Search, explore, and investigate your team's remote repositories using Tabnine's Context Engine. Enables semantic code search, symbol lookup, file navigation, and OpenAPI spec querying across all indexed repositories. Includes AI coaching to help your team write better code.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "7620", + "name": "Tabnine Context Engine", + "description": "Context Engine CLI (ctx-cli) as focused skills — guided tenant onboarding, code & knowledge-graph search, service investigation, CVE & SAST triage with ready-to-apply fixes, and coding-guideline checks.", + "category": "Agent Orchestration", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 6, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3165", + "name": "Tavily", + "description": "Web search, content extraction, crawling, deep research, and URL discovery. MCP tools plus skills; CLI for terminal agents.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 7, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "63354504", + "name": "Teams", + "description": "Search, read, and send Microsoft Teams chats and channel messages.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/teams" + }, + { + "stableId": "3579847", + "name": "telegram", + "description": "Telegram channel for Claude Code — messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /telegram:access.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "6344", + "name": "Temporal", + "description": "Comprehensive skill for the entire Temporal lifecycle — developing applications, using the Temporal CLI, running and managing Temporal Server, and working with Temporal Cloud.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 0, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "3579848", + "name": "terraform", + "description": "The Terraform MCP Server provides seamless integration with Terraform ecosystem, enabling advanced automation and interaction capabilities for Infrastructure as Code (IaC) development.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "13566132", + "name": "Thermos", + "description": "Thermo-nuclear branch review: deep correctness and security audits plus harsh code-quality rubrics, parallel subagents, thermos orchestration, and optional take-the-wheel and FSD merge-ready flows.", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 3, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "13566132", + "name": "Thermos", + "skill_count": 3, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "thermos", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "1.0.0", + "skillCount": 3, + "agentCount": 2, + "hasMcp": false, + "skillNames": [ + "thermo-nuclear-code-quality-review", + "thermo-nuclear-review", + "thermos" + ], + "agentNames": [ + "thermo-nuclear-code-quality-review-subagent", + "thermo-nuclear-review-subagent" + ] + }, + "marketplaceSource": "thermos" + }, + { + "stableId": "6394", + "name": "ThoughtSpot", + "description": "ThoughtSpot developer documentation — search Visual Embed SDK, REST API v2, and developer guides through MCP-compatible clients.", + "category": "Data Analytics", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "9295", + "name": "TierZero", + "description": "Agentic production engineering for SWE, SRE and DevOps. Resolve and investigate issues against existing observability, CI/CD, and self-improving knowledge bases.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "788", + "name": "tldraw", + "description": "Draw and visually collaborate with your agents inside Cursor", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809986", + "name": "Todoist", + "description": "Create, find, and complete tasks and projects.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/todoist" + }, + { + "stableId": "60857554", + "name": "Transcend Agent Governance", + "description": "Connect Cursor to Transcend Agent Governance for policy-enforced MCP tool access via browser sign-in.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "55647425", + "name": "Treg", + "description": "OpenRouter for tools — 2,896 agent-callable endpoints across 60 providers: SEO and SERP data, backlinks, social and trends, people and company enrichment, ads, scraping. Search by the task, see the price, then call it. Credentials are injected server-side, so the agent never holds a key. Pay per call, not per subscription.", + "category": "Research", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "55065452", + "name": "Tuist", + "description": "Work with Tuist projects from Cursor: migrate Xcode projects to generated workspaces, debug generation, and query builds, test runs, bundles, and cache runs from your Tuist dashboard.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 12, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "5057", + "name": "turbopuffer", + "description": "Turbopuffer vector and full-text search database integration for Cursor", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60858255", + "name": "Turso", + "description": "Manage Turso Cloud (organizations, databases, groups) and run SQL from Cursor via the hosted Turso MCP server — OAuth login, no API token to copy.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "9105", + "name": "Twilio", + "description": "Twilio Skills and MCP provide procedural knowledge for AI coding agents — which APIs to use, in what order, and what to avoid. Covers Messaging, Voice, Verify, SendGrid, and 30+ products.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 57, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809987", + "name": "Typeform", + "description": "Build forms, analyze responses, and manage contacts.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/typeform" + }, + { + "stableId": "3579849", + "name": "typescript-lsp", + "description": "TypeScript/JavaScript language server for enhanced code intelligence", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 0, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "3579849", + "name": "typescript-lsp", + "skill_count": 0, + "connector_count": 0, + "installed": true + }, + "cache": { + "marketplace": "claude-plugins-official", + "slug": "typescript-lsp", + "versionHash": "1a2f18b05cf5652fd25403e8d229fc884fb84103", + "version": null, + "skillCount": 0, + "agentCount": 0, + "hasMcp": false, + "skillNames": [], + "agentNames": [] + } + }, + { + "stableId": "56809988", + "name": "Upwork", + "description": "Search talent, post jobs, and manage contracts.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/upwork" + }, + { + "stableId": "60859419", + "name": "UserTesting", + "description": "Bring real customer feedback into Cursor — create, launch, and retrieve UserTesting results without leaving your editor.", + "category": "Research", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "7619", + "name": "Vantage", + "description": "Query cloud costs, manage cost reports, budgets, alerts, and recommendations across your Vantage workspace", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "649", + "name": "Vercel", + "description": "Build and deploy web apps and agents", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 33, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "1090", + "name": "Webflow", + "description": "Production-ready agent skills for Webflow - CMS management, site auditing, asset optimization, and safe publishing", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 22, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "50389919", + "name": "Whop", + "description": "Build and run your business end-to-end with Whop. Launch your website, accept payments, run ads, and more—directly in Cursor.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "761", + "name": "Wix", + "description": "Build, manage, and deploy Wix sites and apps directly from Cursor. Includes CLI development skills for creating dashboard extensions, backend APIs, site widgets, and service plugins. Connects to the Wix MCP server for site management.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 4, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "58954938", + "name": "Wonder", + "description": "Create and edit designs with Cursor on a canvas where every design is real code.", + "category": "Design", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "60855173", + "name": "WordPress.com", + "description": "Build, publish, and manage WordPress.com sites directly from Cursor.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809989", + "name": "Workable", + "description": "Search candidates, move pipelines, and manage HR records.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/workable" + }, + { + "stableId": "6067", + "name": "WorkOS", + "description": "WorkOS integration skills for AuthKit, SSO, Directory Sync, RBAC, Vault, Audit Logs, migrations, and API references, plus the WorkOS MCP server for managing your workspace.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 2, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "56809990", + "name": "Wrike", + "description": "Search projects, create tasks, and post comments.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/wrike" + }, + { + "stableId": "49086599", + "name": "X", + "description": "Search posts, read timelines, pull trends, and manage bookmarks.", + "category": "Inbox And Collaboration", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 1, + "skillCountReported": 1, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache", + "marketplace.json" + ], + "liveInstalled": { + "plugin_id": "49086599", + "name": "X", + "skill_count": 1, + "connector_count": 1, + "installed": true + }, + "cache": { + "marketplace": "cursor-public", + "slug": "x", + "versionHash": "be432a96ed36e48d05f44bf375864355f62263f9", + "version": "2.2.0", + "skillCount": 1, + "agentCount": 0, + "hasMcp": true, + "skillNames": [ + "x-api-mcp-guide" + ], + "agentNames": [] + }, + "marketplaceSource": "third_party/x" + }, + { + "stableId": "59384947", + "name": "X Ads", + "description": "Manage ad campaigns, create ads, track conversions, and pull performance stats.", + "category": "MCP", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/x-ads" + }, + { + "stableId": "3579800", + "name": "xclaude-plugin", + "description": "Modular iOS development automation suite. 8 workflow-specific MCP servers (600-3500 tokens) with 23 tools across Xcode, Simulator, and IDB. Enable only what you need!", + "category": "MCP", + "installed": "yes (user)", + "installedLive": true, + "connectorCount": 8, + "skillCountReported": 8, + "sources": [ + "SearchPlugins", + "SearchPlugins-installed-index", + "on-disk-cache" + ], + "liveInstalled": { + "plugin_id": "3579800", + "name": "xclaude-plugin", + "skill_count": 8, + "connector_count": 8, + "installed": true + }, + "cache": { + "marketplace": "xclaude-plugin-marketplace", + "slug": "xclaude-plugin", + "versionHash": "1d05d18cd44c501f4431e7c40310bf3963c707e7", + "version": "0.4.0", + "skillCount": 8, + "agentCount": 0, + "hasMcp": true, + "skillNames": [ + "accessibility-testing", + "crash-debugging", + "ios-testing-patterns", + "performance-profiling", + "simulator-workflows", + "state-management", + "ui-automation-workflows", + "xcode-workflows" + ], + "agentNames": [] + } + }, + { + "stableId": "56809991", + "name": "Xero", + "description": "Read and write invoices, contacts, reports, and payroll.", + "category": "Payments", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/xero" + }, + { + "stableId": "762", + "name": "Zapier", + "description": "Connect 9,000+ apps to your AI workflow. Discover, enable, and execute Zapier actions directly from your client.", + "category": "Productivity", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 3, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "49047809", + "name": "Zoom", + "description": "Search meetings, pull transcripts, and work with Zoom Docs.", + "category": "Scheduling", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 0, + "sources": [ + "SearchPlugins", + "marketplace.json" + ], + "marketplaceSource": "third_party/zoom" + }, + { + "stableId": "32610026", + "name": "ZoomInfo", + "description": "Connect AI agents to ZoomInfo's verified GTM context graph: 100M companies, 300M professional contacts, buyer intent signals, and real-time scoops. Build prospect lists, enrich records, deep research key organizations, find decision-makers, and prioritize accounts by intent. The B2B sales intelligence layer for Cursor and any MCP-compatible agent.", + "category": "Sales", + "installed": "no", + "installedLive": false, + "connectorCount": 1, + "skillCountReported": 14, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": "5670", + "name": "Zscaler", + "description": "Manage the Zscaler cloud security platform including ZPA (private access), ZIA (internet access), ZDX (digital experience), ZCC (client connector), EASM (attack surface management), and Z-Insights (security analytics). Includes guided skills for application onboarding, policy audits, incident investigation, and cross-product troubleshooting.", + "category": "Infrastructure", + "installed": "no", + "installedLive": false, + "connectorCount": 2, + "skillCountReported": 0, + "sources": [ + "SearchPlugins" + ] + }, + { + "stableId": null, + "name": "devtools-for-agents", + "slug": "devtools-for-agents", + "description": "Help your agent build, debug, and verify your code correctly. With Chrome DevTools for agents, your AI agent can interact with the Chrome browser to test code, emulate users, and catch bugs using Chrome DevTools’ capabilities before shipping.", + "category": null, + "installed": "cache-present", + "sources": [ + "on-disk-cache" + ], + "cache": { + "marketplace": "cursor-public", + "slug": "devtools-for-agents", + "versionHash": "702d3734f276a18efd67561ae00b88ce954cc515", + "version": "1.2.0", + "skillCount": 6, + "agentCount": 0, + "hasMcp": false, + "skillNames": [ + "a11y-debugging", + "chrome-devtools", + "chrome-devtools-cli", + "debug-optimize-lcp", + "memory-leak-debugging", + "troubleshooting" + ] + } + }, + { + "stableId": null, + "name": "Grafana Assistant", + "slug": "grafana-assistant", + "description": "Skills and rules for developing and using the Grafana Assistant app and CLI.", + "category": null, + "installed": "cache-present", + "sources": [ + "on-disk-cache" + ], + "cache": { + "marketplace": "cursor-public", + "slug": "grafana-assistant", + "versionHash": "0d0526a92f5e5546bdd9e84390100766cc61298f", + "version": "0.2.0", + "skillCount": 1, + "agentCount": 0, + "hasMcp": false, + "skillNames": [ + "grafana-assistant-cli" + ] + } + } + ] +} diff --git a/artifact/assets/public/app.js b/artifact/assets/public/app.js new file mode 100644 index 0000000..d2ccb04 --- /dev/null +++ b/artifact/assets/public/app.js @@ -0,0 +1,440 @@ +/* global Markdown, Picker */ +(function () { + "use strict"; + + const esc = Markdown.escapeHtml; + const $ = (id) => document.getElementById(id); + + const state = { + installed: [], + marketplace: [], + groups: {}, // plugin_id -> [{ name, skillIds }] + view: "installed", + query: "", + pluginId: null, + doc: null, // { kind, id } open in the reader + docSeq: 0, // bumps per openDoc so slow fetches can't paint a stale doc + }; + + const els = { + shell: $("shell"), + search: $("search"), + list: $("pluginList"), + counts: $("counts"), + main: $("mainInner"), + readerHead: $("readerHead"), + readerTabs: $("readerTabs"), + readerBody: $("readerBody"), + readerStatus: $("readerStatus"), + readerApply: $("readerApply"), + picker: $("pickerRoot"), + toast: $("toast"), + }; + + // ---------------------------------------------------------------- utils + async function getJson(url) { + const r = await fetch(url); + const body = await r.json().catch(() => ({})); + if (!r.ok) throw new Error(body.message || body.error || `HTTP ${r.status}`); + return body; + } + function localUrl(key, kind, ...segments) { + return `/api/local/${key}/${kind}/${segments.flatMap((s) => s.split("/")).map(encodeURIComponent).join("/")}`; + } + function hueOf(s) { + let h = 0; + for (const c of String(s)) h = (h * 31 + c.charCodeAt(0)) >>> 0; + return h % 360; + } + function initials(name) { + const parts = String(name || "?").replace(/[^\w\s/-]/g, " ").trim().split(/[\s/-]+/); + return (parts.length > 1 ? parts[0][0] + parts[1][0] : parts[0].slice(0, 2)).toUpperCase(); + } + /** Initials tile; when the plugin ships a logo, an sits on top and is removed if it fails. */ + function logoHtml(p, cls = "") { + const img = p.local && p.local.logo ? `` : ""; + return ``; + } + function wireLogos(root) { + root.querySelectorAll(".logo img").forEach((img) => img.addEventListener("error", () => img.remove())); + } + function plural(n, one, many = one + "s") { + return `${n} ${n === 1 ? one : many}`; + } + function toast(msg, cls = "") { + els.toast.textContent = msg; + els.toast.className = `toast ${cls}`; + clearTimeout(toast.t); + toast.t = setTimeout(() => els.toast.classList.add("hidden"), 3600); + } + function setStatus(msg, cls = "") { + els.readerStatus.textContent = msg; + els.readerStatus.className = `status ${cls}`; + } + function onSendResult(msg, tone) { + toast(msg, tone); + setStatus(msg, tone); + } + + // ---------------------------------------------------------------- data + function findPlugin(id) { + return state.installed.find((p) => p.plugin_id === String(id)) || state.marketplace.find((p) => p.plugin_id === String(id)) || null; + } + + function skillGroups(p) { + const skills = (p.local && p.local.skills) || []; + const groups = state.groups[p.plugin_id]; + if (!groups) return skills.length ? [{ name: "Skills", skills }] : []; + const byId = new Map(skills.map((s) => [s.id, s])); + const seen = new Set(); + const out = []; + for (const g of groups) { + const list = g.skillIds.map((id) => byId.get(id)).filter(Boolean); + list.forEach((s) => seen.add(s.id)); + if (list.length) out.push({ name: g.name, skills: list }); + } + const rest = skills.filter((s) => !seen.has(s.id)); + if (rest.length) out.push({ name: "Other", skills: rest }); + return out; + } + + // ---------------------------------------------------------------- sidebar + function renderList() { + const q = state.query.toLowerCase(); + const match = (p) => + !q || + p.name.toLowerCase().includes(q) || + (p.description || "").toLowerCase().includes(q) || + p.plugin_id.includes(q) || + ((p.local && p.local.skills) || []).some((s) => s.id.includes(q) || s.name.toLowerCase().includes(q)); + + const rows = (state.view === "installed" ? state.installed : state.marketplace).filter(match); + const item = (p) => { + const n = p.skill_count; + const sub = p.local + ? [n ? plural(n, "skill") : null, p.local.agents.length ? plural(p.local.agents.length, "agent") : null, p.local.hasMcp ? "MCP" : null].filter(Boolean).join(" · ") || "connector" + : [p.category, n ? plural(n, "skill") : null].filter(Boolean).join(" · "); + return ``; + }; + + let html; + if (!rows.length) { + html = `
${q ? `Nothing matches “${esc(state.query)}”.` : "Nothing here yet."}
`; + } else if (state.view === "installed") { + const withSkills = rows.filter((p) => p.skill_count > 0); + const without = rows.filter((p) => p.skill_count === 0); + html = withSkills.map(item).join(""); + if (without.length) html += `
Connectors only
${without.map(item).join("")}`; + } else { + html = rows.slice(0, 250).map(item).join(""); + if (rows.length > 250) html += `
Showing 250 of ${rows.length}. Narrow the search.
`; + } + els.list.innerHTML = html; + wireLogos(els.list); + els.list.querySelectorAll(".plugin-item").forEach((b) => b.addEventListener("click", () => navigate(`#/p/${b.dataset.id}`))); + const cur = els.list.querySelector('[aria-current="true"]'); + if (cur) cur.scrollIntoView({ block: "nearest" }); + } + + // ---------------------------------------------------------------- main + function renderWelcome() { + document.documentElement.style.setProperty("--hue", 220); + els.main.innerHTML = `
+

Pick a plugin to explore

+

Every installed plugin shows its real skills, agents and rules straight from disk. Open any skill to read it in full, then hand it to a bot.

+
/ search moveEsc close reader
+
`; + } + + function renderPlugin(p) { + document.documentElement.style.setProperty("--hue", hueOf(p.name)); + const L = p.local; + const byline = []; + if (L && L.author) byline.push(esc(L.author)); + if (L && L.semver) byline.push(`v${esc(L.semver)}`); + if (p.category) byline.push(esc(p.category)); + if (/^\d+$/.test(p.plugin_id)) byline.push(`#${esc(p.plugin_id)}`); + else if (L) byline.push(`${esc(L.key)}`); + + const chips = [p.installed ? `Installed` : `Not installed`]; + if (L) { + if (L.skills.length) chips.push(`${L.skills.length} skills`); + if (L.agents.length) chips.push(`${L.agents.length} agents`); + if (L.rules.length) chips.push(`${L.rules.length} rules`); + if (L.hasMcp) chips.push(`MCP server`); + if (L.hasReadme) chips.push(`README`); + if (L.homepage) chips.push(`Source ↗`); + } else { + if (p.skill_count) chips.push(`${p.skill_count} skills`); + if (p.connector_count) chips.push(`${p.connector_count} connectors`); + } + chips.push(``); + + let html = `
+ ${logoHtml(p, "lg")} +
+

${esc(p.name)}

+ + ${p.description ? `

${esc(p.description)}

` : ""} +
${chips.join("")}
+
+
`; + + const section = (title, items, cardFn) => + items.length + ? `

${esc(title)}

${items.length}
${items.map(cardFn).join("")}
` + : ""; + + if (L) { + for (const g of skillGroups(p)) { + html += `
+

${esc(g.name)}

${g.skills.length}click a skill to read it in full
+
${g.skills.map((s) => skillCard(p, s)).join("")}
+
`; + } + html += section("Agents", L.agents, (a) => docCard(p, "a", a)); + html += section("Rules", L.rules, (r) => docCard(p, "r", r, r.alwaysApply ? "always applied" : "")); + if (!L.skills.length && !L.agents.length && !L.rules.length) { + html += `
This plugin only ships a connector${L.hasMcp ? " (MCP server)" : ""}. There are no skills to read.
`; + } + } else if (p.installed) { + html += `
Installed, but its files aren't in the local plugin cache yet, so skills can't be read here.
`; + } else { + html += `
Marketplace listing. Skill contents become readable once the plugin is installed.
`; + } + + els.main.innerHTML = html; + els.main.scrollTop = 0; + wireLogos(els.main); + els.main.querySelectorAll(".card[data-doc]").forEach((c) => c.addEventListener("click", () => navigate(c.dataset.doc))); + $("sendPlugin").addEventListener("click", () => Picker.openPicker({ root: els.picker, plugin: p, skill: null, onResult: onSendResult })); + markCurrentCard(); + } + + function skillCard(p, s) { + const size = s.bytes > 1024 ? `${(s.bytes / 1024).toFixed(1)} KB` : `${s.bytes} B`; + return ``; + } + + function docCard(p, kind, d, tag = "") { + return ``; + } + + function markCurrentCard() { + const key = state.doc ? `${state.doc.kind}:${state.doc.id}` : null; + els.main.querySelectorAll(".card[data-key]").forEach((c) => c.setAttribute("aria-current", String(c.dataset.key === key))); + } + + // ---------------------------------------------------------------- reader + const KIND_LABEL = { s: "skill", a: "agent", r: "rule", f: "file" }; + + function closeReader() { + state.doc = null; + state.docSeq++; + els.shell.classList.remove("reader-open"); + markCurrentCard(); + } + + /** Resolve a route kind+id to the files the reader shows as tabs. */ + function docTabs(p, kind, id) { + const L = p.local; + switch (kind) { + case "s": { + const skill = L.skills.find((s) => s.id === id); + if (!skill) return null; + return { skill, tabs: [{ label: "SKILL.md", path: skill.file }, ...skill.files.map((f) => ({ label: f, path: `skills/${skill.id}/${f}` }))] }; + } + case "a": + case "r": { + const entry = (kind === "a" ? L.agents : L.rules).find((x) => x.id === id); + return entry ? { skill: null, tabs: [{ label: entry.file, path: entry.file }] } : null; + } + case "f": + return { skill: null, tabs: [{ label: id, path: id }] }; + default: { + const never = kind; + throw new Error(`unhandled doc kind: ${never}`); + } + } + } + + function openDoc(p, kind, id) { + state.doc = { kind, id }; + const seq = ++state.docSeq; + els.shell.classList.add("reader-open"); + markCurrentCard(); + + els.readerHead.innerHTML = `
+
${logoHtml(p)}${esc(p.name)}/${KIND_LABEL[kind]}
+

${esc(id)}

+
+ `; + wireLogos(els.readerHead); + $("closeReader").onclick = () => navigate(`#/p/${p.plugin_id}`); + els.readerBody.innerHTML = `
`; + els.readerTabs.innerHTML = ""; + setStatus(""); + + const resolved = p.local ? docTabs(p, kind, id) : null; + const skill = resolved ? resolved.skill : null; + els.readerApply.textContent = skill ? `Send “${skill.name}” to a bot…` : "Send plugin to a bot…"; + els.readerApply.onclick = () => Picker.openPicker({ root: els.picker, plugin: p, skill, onResult: onSendResult }); + if (!resolved) { + els.readerBody.innerHTML = `
Not found in the local plugin cache.
`; + return; + } + const { tabs } = resolved; + + const selectTab = (i) => { + els.readerTabs.querySelectorAll("button").forEach((x, j) => x.setAttribute("aria-selected", String(i === j))); + loadTab(p.local.key, tabs, i, seq); + }; + if (tabs.length > 1) { + els.readerTabs.innerHTML = tabs.map((t, i) => ``).join(""); + els.readerTabs.querySelectorAll("button").forEach((b, i) => (b.onclick = () => selectTab(i))); + } + selectTab(0); + } + + const dirname = (p) => p.split("/").slice(0, -1).join("/"); + const joinPath = (dir, rel) => (dir ? `${dir}/${rel}` : rel); + + async function loadTab(key, tabs, index, seq) { + const tab = tabs[index]; + const stillCurrent = () => seq === state.docSeq; + try { + if (/\.mdc?$/i.test(tab.path)) { + const { meta, markdown } = await getJson(localUrl(key, "doc", tab.path)); + if (!stillCurrent()) return; + els.readerHead.querySelector(".meta").innerHTML = Object.entries(meta) + .filter(([k]) => k !== "description" && k !== "name") + .map(([k, v]) => `${esc(k)}: ${esc(String(v))}`) + .join(""); + const lead = meta.description ? `

${esc(meta.description)}

` : ""; + const dir = dirname(tab.path); + const base = dir ? localUrl(key, "file", dir) : `/api/local/${key}/file`; + els.readerBody.innerHTML = `
${lead}${Markdown.render(markdown, { base })}
`; + // A relative link that names another tab switches tabs instead of leaving the reader. + els.readerBody.querySelectorAll("a[data-rel]").forEach((a) => { + const target = joinPath(dir, a.dataset.rel); + const i = tabs.findIndex((t) => t.path === target); + if (i >= 0) { + a.removeAttribute("target"); + a.addEventListener("click", (e) => { + e.preventDefault(); + els.readerTabs.querySelectorAll("button").forEach((x, j) => x.setAttribute("aria-selected", String(i === j))); + loadTab(key, tabs, i, seq); + }); + } + }); + } else { + const r = await fetch(localUrl(key, "file", tab.path)); + if (!r.ok) throw new Error(`HTTP ${r.status}`); + const text = await r.text(); + if (!stillCurrent()) return; + els.readerBody.innerHTML = `
${esc(text)}
`; + } + els.readerBody.scrollTop = 0; + } catch (e) { + if (stillCurrent()) els.readerBody.innerHTML = `
${esc(e.message)}
`; + } + } + + // ---------------------------------------------------------------- routing + function navigate(hash) { + if (location.hash === hash) route(); + else location.hash = hash; + } + + function route() { + const m = /^#\/p\/([^/]+)(?:\/([sarf])\/(.+))?$/.exec(location.hash); + if (!m) { + state.pluginId = null; + closeReader(); + renderWelcome(); + renderList(); + return; + } + const p = findPlugin(m[1]); + if (!p) { + state.pluginId = null; + closeReader(); + renderWelcome(); + renderList(); + return; + } + if (state.pluginId !== p.plugin_id) { + state.pluginId = p.plugin_id; + setView(p.installed ? "installed" : "marketplace", false); + renderPlugin(p); + } + renderList(); + if (m[2]) openDoc(p, m[2], decodeURIComponent(m[3])); + else closeReader(); + } + + function setView(view, rerender = true) { + state.view = view; + document.querySelectorAll(".segmented button").forEach((b) => b.setAttribute("aria-pressed", String(b.dataset.view === view))); + if (rerender) renderList(); + } + + // ---------------------------------------------------------------- keyboard + document.addEventListener("keydown", (e) => { + const inField = /^(INPUT|TEXTAREA)$/.test(document.activeElement.tagName); + if (e.key === "Escape") { + if (!els.picker.classList.contains("hidden")) return; // the picker owns Esc while open + if (state.doc) return navigate(`#/p/${state.pluginId}`); + if (inField) document.activeElement.blur(); + return; + } + if (inField) return; + if (e.key === "/") { + e.preventDefault(); + els.search.focus(); + els.search.select(); + } + if (e.key === "ArrowDown" || e.key === "ArrowUp") { + const items = [...els.list.querySelectorAll(".plugin-item")]; + if (!items.length) return; + e.preventDefault(); + const i = items.findIndex((x) => x.getAttribute("aria-current") === "true"); + const next = items[Math.min(items.length - 1, Math.max(0, i + (e.key === "ArrowDown" ? 1 : -1)))]; + navigate(`#/p/${next.dataset.id}`); + } + }); + + els.search.addEventListener("input", () => { + state.query = els.search.value.trim(); + renderList(); + }); + document.querySelectorAll(".segmented button").forEach((b) => b.addEventListener("click", () => setView(b.dataset.view))); + window.addEventListener("hashchange", route); + + // ---------------------------------------------------------------- init + (async function init() { + try { + const lib = await getJson("/api/library"); + state.installed = lib.installed.sort((a, b) => b.skill_count - a.skill_count || a.name.localeCompare(b.name)); + state.marketplace = lib.marketplace.sort((a, b) => b.skill_count - a.skill_count || a.name.localeCompare(b.name)); + state.groups = lib.groups || {}; + els.counts.textContent = `${state.installed.length} installed · ${state.marketplace.length} in marketplace`; + route(); + } catch (e) { + els.counts.textContent = "failed to load"; + els.main.innerHTML = `
${esc(e.message)}
`; + } + })(); +})(); diff --git a/artifact/assets/public/index.html b/artifact/assets/public/index.html new file mode 100644 index 0000000..deef176 --- /dev/null +++ b/artifact/assets/public/index.html @@ -0,0 +1,55 @@ + + + + + + Plugin Library + + + +
+ + +
+
+
+ + +
+ + + + + + + + + diff --git a/artifact/assets/public/markdown.js b/artifact/assets/public/markdown.js new file mode 100644 index 0000000..cd9ffbd --- /dev/null +++ b/artifact/assets/public/markdown.js @@ -0,0 +1,213 @@ +/** + * Small, safe Markdown → HTML renderer for SKILL.md files. + * Every piece of source text is escaped before any tag is emitted, so the + * output can only contain tags this file produces. + */ +(function (global) { + "use strict"; + + function escapeHtml(s) { + return String(s) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); + } + + /** + * Absolute http(s)/mailto/anchor/root links pass through. A relative path is + * resolved against `base` (the directory the document lives in) and tagged + * with data-rel so the host can route it to a sibling tab. + */ + function resolveHref(href, base) { + const h = String(href || "").trim(); + if (/^(https?:|mailto:|#|\/)/i.test(h)) return { href: h, rel: null }; + if (!base || /^[a-z][a-z0-9+.-]*:/i.test(h)) return null; + const parts = []; + for (const seg of h.replace(/^\.\//, "").split("/")) { + if (seg === "..") parts.pop(); + else if (seg && seg !== ".") parts.push(seg); + } + const rel = parts.join("/"); + return rel ? { href: base.replace(/\/$/, "") + "/" + parts.map(encodeURIComponent).join("/"), rel } : null; + } + + function inline(text, base) { + // Protect code spans first so nothing inside them is styled. + const codes = []; + let s = escapeHtml(text).replace(/`([^`]+)`/g, (_, c) => { + codes.push(`${c}`); + return `\u0000${codes.length - 1}\u0000`; + }); + s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (_, alt, src) => { + const r = resolveHref(src, base); + return r ? `${alt}` : alt; + }); + s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_, label, href) => { + const r = resolveHref(href, base); + if (!r) return label; + const relAttr = r.rel ? ` data-rel="${escapeHtml(r.rel)}"` : ""; + return `${label}`; + }); + s = s.replace(/\*\*([^*]+)\*\*/g, "$1"); + s = s.replace(/__([^_]+)__/g, "$1"); + s = s.replace(/(^|[^*\w])\*([^*\n]+)\*(?!\w)/g, "$1$2"); + s = s.replace(/(^|[^_\w])_([^_\n]+)_(?!\w)/g, "$1$2"); + s = s.replace(/~~([^~]+)~~/g, "$1"); + return s.replace(/\u0000(\d+)\u0000/g, (_, i) => codes[Number(i)]); + } + + function slugify(text) { + return String(text) + .toLowerCase() + .replace(/<[^>]+>/g, "") + .replace(/[^a-z0-9\s-]/g, "") + .trim() + .replace(/\s+/g, "-"); + } + + function renderTable(rows, base) { + const cells = (line) => + line + .trim() + .replace(/^\|/, "") + .replace(/\|$/, "") + .split("|") + .map((c) => c.trim()); + const head = cells(rows[0]); + const body = rows.slice(2).map(cells); + let html = ""; + for (const h of head) html += ``; + html += ""; + for (const r of body) { + html += ""; + for (let i = 0; i < head.length; i++) html += ``; + html += ""; + } + return html + "
${inline(h, base)}
${inline(r[i] || "", base)}
"; + } + + function isTableSeparator(line) { + return /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(line); + } + + function render(md, opts = {}) { + const base = opts.base || null; + const lines = String(md || "").replace(/\r\n/g, "\n").split("\n"); + const out = []; + let i = 0; + + function paragraphUntilBlank() { + const buf = []; + while (i < lines.length && lines[i].trim() && !blockStart(lines[i])) { + buf.push(lines[i].trim()); + i++; + } + return buf.join(" "); + } + + function blockStart(line) { + return ( + /^(#{1,6})\s/.test(line) || + /^```/.test(line) || + /^\s*([-*+]|\d+[.)])\s+/.test(line) || + /^>\s?/.test(line) || + /^(-{3,}|\*{3,}|_{3,})\s*$/.test(line) || + (/^\s*\|/.test(line) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) + ); + } + + function list(indent) { + const itemRe = /^(\s*)([-*+]|\d+[.)])\s+(.*)$/; + const first = itemRe.exec(lines[i]); + const ordered = /\d/.test(first[2]); + let html = ordered ? "
    " : "
      "; + while (i < lines.length) { + const m = itemRe.exec(lines[i]); + if (!m || m[1].length < indent) break; + if (m[1].length > indent) { + html = html.replace(/<\/li>$/, "") + list(m[1].length) + ""; + continue; + } + i++; + let text = m[3]; + // Lazy continuation lines (indented, not a new item, not blank). + while ( + i < lines.length && + lines[i].trim() && + !itemRe.test(lines[i]) && + /^\s{2,}/.test(lines[i]) + ) { + text += " " + lines[i].trim(); + i++; + } + const task = /^\[([ xX])\]\s+/.exec(text); + if (task) { + text = text.slice(task[0].length); + const checked = task[1] !== " " ? " checked" : ""; + html += `
    • ${inline(text, base)}
    • `; + } else { + html += `
    • ${inline(text, base)}
    • `; + } + // Allow blank line between items of the same list. + if (i < lines.length && !lines[i].trim() && i + 1 < lines.length) { + const next = itemRe.exec(lines[i + 1]); + if (next && next[1].length >= indent) i++; + } + } + return html + (ordered ? "
" : ""); + } + + while (i < lines.length) { + const line = lines[i]; + if (!line.trim()) { + i++; + continue; + } + let m; + if ((m = /^(#{1,6})\s+(.*?)\s*#*\s*$/.exec(line))) { + const level = m[1].length; + const text = inline(m[2], base); + const id = slugify(m[2]); + out.push(`${text}`); + i++; + continue; + } + if (/^```/.test(line)) { + const lang = line.slice(3).trim().split(/\s+/)[0]; + const buf = []; + i++; + while (i < lines.length && !/^```/.test(lines[i])) buf.push(lines[i++]); + i++; + const cls = lang ? ` class="lang-${escapeHtml(lang)}"` : ""; + out.push(`
${escapeHtml(buf.join("\n"))}
`); + continue; + } + if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) { + out.push("
"); + i++; + continue; + } + if (/^>\s?/.test(line)) { + const buf = []; + while (i < lines.length && /^>\s?/.test(lines[i])) buf.push(lines[i++].replace(/^>\s?/, "")); + out.push(`
${render(buf.join("\n"), opts)}
`); + continue; + } + if (/^\s*\|/.test(line) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) { + const rows = []; + while (i < lines.length && /^\s*\|/.test(lines[i])) rows.push(lines[i++]); + out.push(renderTable(rows, base)); + continue; + } + if (/^\s*([-*+]|\d+[.)])\s+/.test(line)) { + out.push(list(/^(\s*)/.exec(line)[1].length)); + continue; + } + out.push(`

${inline(paragraphUntilBlank(), base)}

`); + } + return out.join("\n"); + } + + global.Markdown = { render, escapeHtml }; +})(window); diff --git a/artifact/assets/public/picker.js b/artifact/assets/public/picker.js new file mode 100644 index 0000000..1ed0383 --- /dev/null +++ b/artifact/assets/public/picker.js @@ -0,0 +1,211 @@ +/* global Markdown */ +/** + * Bot picker + direct send flow. One state object and one render() switch. + * The selected target receives a message only after the user confirms. + */ +(function (global) { + "use strict"; + + const esc = Markdown.escapeHtml; + const AVATAR_COLORS = { + magenta: "#d64ea8", green: "#3fbf7f", cyan: "#2fb7c9", blue: "#4f7fe0", red: "#e05252", + orange: "#e6873a", black: "#3a3f4a", purple: "#8b5cf6", yellow: "#d4b13a", + }; + + function hueOf(s) { + let h = 0; + for (const c of String(s)) h = (h * 31 + c.charCodeAt(0)) >>> 0; + return h % 360; + } + function initials(name) { + const parts = String(name || "?").replace(/[^\w\s/-]/g, " ").trim().split(/[\s/-]+/); + return (parts.length > 1 ? parts[0][0] + parts[1][0] : parts[0].slice(0, 2)).toUpperCase(); + } + function oneLine(s) { + return String(s || "").split(/\n/)[0].trim(); + } + function plural(n, one, many = one + "s") { + return `${n} ${n === 1 ? one : many}`; + } + function avatarHtml(b) { + const style = b.kind === "group" ? "" : `--av:${AVATAR_COLORS[b.avatarColor] || `hsl(${hueOf(b.name)} 55% 45%)`}`; + const shape = b.kind === "group" ? "group" : b.avatarShape || ""; + return `${esc(initials(b.name))}`; + } + + /** + * @param {object} opts + * @param {HTMLElement} opts.root container to render into + * @param {object} opts.plugin { plugin_id, name } + * @param {object|null} opts.skill { id, name } or null for the whole plugin + * @param {(msg: string, tone: string) => void} opts.onResult + */ + function openPicker({ root, plugin, skill, onResult }) { + const s = { stage: "loading", roster: null, query: "", selected: null, error: null }; + + const thing = skill + ? `the skill ${esc(skill.name)} from ${esc(plugin.name)}` + : `${esc(plugin.name)}`; + const target = () => `${esc(s.selected.name)}${s.selected.kind === "group" ? " (group)" : ""}`; + + function close() { + root.classList.add("hidden"); + root.innerHTML = ""; + document.removeEventListener("keydown", onKey); + } + function onKey(e) { + if (e.key === "Escape") { + e.stopPropagation(); + close(); + } + } + document.addEventListener("keydown", onKey); + + async function loadRoster() { + s.stage = "loading"; + render(); + try { + const r = await fetch("/api/bots"); + const j = await r.json(); + if (!j.ok) throw new Error(j.message || j.error || "gbot unavailable"); + s.roster = j; + s.stage = "list"; + } catch (e) { + s.error = e.message; + s.stage = "roster-error"; + } + render(); + } + + async function submit() { + s.stage = "working"; + render(); + const payload = { plugin_id: plugin.plugin_id, bot_ref: s.selected.id }; + if (skill) payload.skill_id = skill.id; + try { + const r = await fetch("/api/send", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + const json = await r.json(); + if (!r.ok || !json.ok) throw new Error(json.message || json.error || `HTTP ${r.status}`); + Object.assign(s, { stage: "done", tone: "ok", message: json.message }); + } catch (e) { + Object.assign(s, { stage: "done", tone: "err", message: String(e.message || e) }); + } + render(); + } + + function rosterHtml() { + const q = s.query.toLowerCase(); + const f = (b) => !q || b.name.toLowerCase().includes(q) || b.description.toLowerCase().includes(q); + const row = (b) => ``; + const bots = s.roster.bots.filter(f); + const groups = s.roster.groups.filter(f); + let html = ""; + if (bots.length) html += `
Bots
${bots.map(row).join("")}`; + if (groups.length) html += `
Groups
${groups.map(row).join("")}`; + return html || `
No bot matches “${esc(s.query)}”.
`; + } + + function confirmHtml() { + switch (s.stage) { + case "confirm": + return { + sentence: `Send ${thing} to ${target()}?`, + fine: "This sends a direct message through gbot.", + cancel: "Cancel", + go: "Send", + action: submit, + }; + case "working": + return { sentence: "Working…", fine: "", cancel: null, go: null, action: null }; + case "loading": + case "list": + case "roster-error": + case "done": + return null; + default: { + const never = s.stage; + throw new Error(`unhandled picker stage: ${never}`); + } + } + } + + function render() { + if (s.stage === "done") { + onResult(s.message, s.tone); + close(); + return; + } + const showRoster = s.stage !== "loading" && s.stage !== "roster-error"; + const confirm = confirmHtml(); + root.innerHTML = `
+ `; + + root.querySelector("[data-close]").onclick = close; + const cancel = root.querySelector("[data-cancel]"); + if (cancel) cancel.onclick = close; + const go = root.querySelector("[data-go]"); + if (go && confirm) go.onclick = confirm.action; + const retry = root.querySelector("[data-retry]"); + if (retry) retry.onclick = loadRoster; + + const search = root.querySelector("[data-search]"); + search.oninput = () => { + s.query = search.value.trim(); + root.querySelector(".picker-list").innerHTML = rosterHtml(); + wireRoster(); + }; + wireRoster(); + if (s.stage === "list" && document.activeElement !== search) search.focus(); + } + + function wireRoster() { + if (s.stage !== "list" && s.stage !== "confirm") return; + root.querySelectorAll(".bot").forEach((b) => { + b.onclick = () => { + s.selected = [...s.roster.bots, ...s.roster.groups].find((x) => x.id === b.dataset.id); + s.stage = "confirm"; + render(); + }; + }); + } + + root.classList.remove("hidden"); + loadRoster(); + return { close }; + } + + global.Picker = { openPicker }; +})(window); diff --git a/artifact/assets/public/styles.css b/artifact/assets/public/styles.css new file mode 100644 index 0000000..6ca5bbb --- /dev/null +++ b/artifact/assets/public/styles.css @@ -0,0 +1,367 @@ +:root { + --bg: #0a0c10; + --bg-2: #0f1218; + --panel: #12161e; + --panel-2: #171c26; + --line: rgba(255, 255, 255, 0.07); + --line-strong: rgba(255, 255, 255, 0.14); + --text: #e7e9ef; + --text-2: #b4bac8; + --muted: #7f879a; + --accent: #8ab4ff; + --accent-ink: #0a1a3a; + --ok: #4fd894; + --warn: #f5c451; + --danger: #ff7b86; + --hue: 220; + --tint: hsl(var(--hue) 80% 65%); + --radius: 12px; + --sidebar-w: 300px; + --reader-w: 600px; + --font: "Inter", "SF Pro Text", system-ui, -apple-system, "Segoe UI", sans-serif; + --mono: "JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace; +} + +* { box-sizing: border-box; } +html, body { height: 100%; } +body { + margin: 0; + font: 14px/1.5 var(--font); + color: var(--text); + background: + radial-gradient(1200px 600px at 20% -10%, hsl(var(--hue) 60% 30% / 0.18), transparent 60%), + var(--bg); + -webkit-font-smoothing: antialiased; + transition: background 0.6s ease; +} +a { color: var(--accent); text-decoration: none; } +a:hover { text-decoration: underline; } +button { font: inherit; color: inherit; cursor: pointer; } +input { font: inherit; color: inherit; } +::selection { background: hsl(var(--hue) 70% 50% / 0.35); } +.hidden { display: none !important; } + +/* Shell -------------------------------------------------------------- */ +.shell { + display: grid; + grid-template-columns: var(--sidebar-w) minmax(0, 1fr) 0; + height: 100vh; + transition: grid-template-columns 0.28s cubic-bezier(0.2, 0.8, 0.2, 1); +} +.shell.reader-open { grid-template-columns: var(--sidebar-w) minmax(0, 1fr) var(--reader-w); } + +/* Sidebar ------------------------------------------------------------ */ +.sidebar { + display: flex; + flex-direction: column; + border-right: 1px solid var(--line); + background: var(--bg-2); + min-height: 0; +} +.brand { + display: flex; + align-items: center; + gap: 10px; + padding: 18px 18px 12px; +} +.brand-mark { + width: 28px; height: 28px; border-radius: 8px; + background: linear-gradient(135deg, #8ab4ff, #c58aff); + box-shadow: 0 4px 14px rgba(138, 180, 255, 0.35); +} +.brand h1 { margin: 0; font-size: 15px; font-weight: 650; letter-spacing: -0.01em; } +.brand .counts { margin: 0; font-size: 12px; color: var(--muted); } + +.search { + margin: 4px 14px 10px; + position: relative; +} +.search input { + width: 100%; + background: var(--panel); + border: 1px solid var(--line); + border-radius: 10px; + padding: 9px 34px 9px 34px; + outline: none; + transition: border-color 0.15s, box-shadow 0.15s; +} +.search input:focus { border-color: var(--line-strong); box-shadow: 0 0 0 3px hsl(var(--hue) 70% 60% / 0.18); } +.search .icon { position: absolute; left: 11px; top: 50%; translate: 0 -50%; color: var(--muted); pointer-events: none; } +.search kbd { + position: absolute; right: 9px; top: 50%; translate: 0 -50%; + font: 11px var(--mono); color: var(--muted); + border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px; +} + +.segmented { + display: grid; grid-template-columns: 1fr 1fr; + margin: 0 14px 12px; + background: var(--panel); + border: 1px solid var(--line); + border-radius: 10px; + padding: 3px; +} +.segmented button { + border: 0; background: transparent; border-radius: 8px; + padding: 6px 10px; color: var(--muted); font-weight: 550; font-size: 13px; + transition: background 0.15s, color 0.15s; +} +.segmented button[aria-pressed="true"] { background: var(--panel-2); color: var(--text); box-shadow: 0 1px 0 rgba(0,0,0,0.4); } + +.plugin-list { flex: 1; overflow-y: auto; padding: 0 8px 16px; scrollbar-width: thin; } +.list-heading { + padding: 12px 10px 6px; + font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); +} +.plugin-item { + display: grid; grid-template-columns: 34px 1fr auto; + gap: 10px; align-items: center; + width: 100%; text-align: left; + padding: 8px 10px; margin: 1px 0; + border: 1px solid transparent; border-radius: 10px; background: transparent; + transition: background 0.12s, border-color 0.12s; +} +.plugin-item:hover { background: rgba(255,255,255,0.035); } +.plugin-item[aria-current="true"] { background: hsl(var(--hue) 70% 60% / 0.12); border-color: hsl(var(--hue) 70% 60% / 0.35); } +.plugin-item > span { min-width: 0; } +.plugin-item .name { display: block; font-weight: 560; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.plugin-item .sub { display: block; font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.plugin-item .count { + font: 11px var(--mono); color: var(--muted); + padding: 2px 7px; border-radius: 999px; background: rgba(255,255,255,0.05); +} +.plugin-item .count.hot { color: var(--text); background: hsl(var(--item-hue, 220) 70% 60% / 0.22); } +.plugin-item.dim .name, .plugin-item.dim .sub { opacity: 0.55; } + +.logo { + width: 34px; height: 34px; border-radius: 9px; flex-shrink: 0; + display: grid; place-items: center; + background: hsl(var(--item-hue, 220) 45% 22%); + color: hsl(var(--item-hue, 220) 90% 85%); + font-weight: 700; font-size: 13px; letter-spacing: 0.02em; + overflow: hidden; + position: relative; +} +.logo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; } +.logo.lg { width: 64px; height: 64px; border-radius: 16px; font-size: 24px; } + +.list-empty { padding: 30px 14px; color: var(--muted); text-align: center; font-size: 13px; } + +/* Main --------------------------------------------------------------- */ +.main { overflow-y: auto; min-width: 0; scrollbar-width: thin; } +.main-inner { max-width: 980px; margin: 0 auto; padding: 32px 36px 80px; } + +.welcome { padding: 18vh 0 0; text-align: center; color: var(--muted); } +.welcome h2 { color: var(--text); font-size: 24px; font-weight: 650; letter-spacing: -0.02em; margin: 0 0 8px; } +.welcome p { margin: 0 auto; max-width: 46ch; } +.welcome .hints { display: flex; gap: 8px; justify-content: center; margin-top: 22px; flex-wrap: wrap; } +.welcome kbd { + font: 12px var(--mono); border: 1px solid var(--line-strong); border-radius: 6px; + padding: 2px 7px; background: var(--panel); color: var(--text-2); +} + +.hero { display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: start; margin-bottom: 26px; } +.hero h2 { margin: 0; font-size: 28px; font-weight: 680; letter-spacing: -0.025em; line-height: 1.15; } +.hero .byline { color: var(--muted); font-size: 13px; margin: 4px 0 10px; display: flex; gap: 6px; flex-wrap: wrap; align-items: center; } +.hero .byline .dot { opacity: 0.5; } +.hero .desc { margin: 0; color: var(--text-2); max-width: 70ch; font-size: 15px; line-height: 1.55; } +.chips { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 14px; } +.chip { + font-size: 12px; padding: 4px 10px; border-radius: 999px; + border: 1px solid var(--line); color: var(--text-2); background: rgba(255,255,255,0.025); + display: inline-flex; gap: 6px; align-items: center; +} +.chip b { font-weight: 650; color: var(--text); } +.chip.ok { color: var(--ok); border-color: rgba(79, 216, 148, 0.3); } +.chip.warn { color: var(--warn); border-color: rgba(245, 196, 81, 0.3); } +.chip.link:hover { border-color: var(--line-strong); text-decoration: none; } + +.section { margin-top: 30px; } +.section-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; } +.section-head h3 { margin: 0; font-size: 15px; font-weight: 650; letter-spacing: -0.01em; } +.section-head .n { font: 12px var(--mono); color: var(--muted); } +.section-head .hint { margin-left: auto; font-size: 12px; color: var(--muted); } + +.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; } +.card { + position: relative; + text-align: left; width: 100%; + background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); + padding: 14px 14px 12px; + display: flex; flex-direction: column; gap: 6px; + transition: transform 0.15s ease, border-color 0.15s, box-shadow 0.15s, background 0.15s; +} +.card:hover { transform: translateY(-1px); border-color: var(--line-strong); background: var(--panel-2); box-shadow: 0 10px 30px rgba(0,0,0,0.35); } +.card[aria-current="true"] { border-color: var(--tint); box-shadow: 0 0 0 1px var(--tint), 0 10px 30px rgba(0,0,0,0.35); } +.card .title { font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 8px; } +.card .title code { font: 12px var(--mono); color: var(--muted); font-weight: 400; } +.card .blurb { + color: var(--muted); font-size: 12.5px; line-height: 1.45; + display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; +} +.card .foot { display: flex; align-items: center; gap: 8px; margin-top: 4px; font-size: 11.5px; color: var(--muted); } +.card .foot .read { margin-left: auto; color: var(--tint); opacity: 0; transition: opacity 0.15s; } +.card:hover .foot .read, .card[aria-current="true"] .foot .read { opacity: 1; } + +.notice { + padding: 14px 16px; border-radius: var(--radius); + background: var(--panel); border: 1px dashed var(--line-strong); color: var(--muted); font-size: 13px; +} + +/* Reader ------------------------------------------------------------- */ +.reader { + border-left: 1px solid var(--line); + background: var(--panel); + display: flex; flex-direction: column; min-width: 0; min-height: 0; + overflow: hidden; +} +.reader-head { + display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: start; + padding: 18px 20px 12px; border-bottom: 1px solid var(--line); +} +.reader-head .crumb { font-size: 12px; color: var(--muted); display: flex; gap: 6px; align-items: center; } +.reader-head .crumb .logo { width: 16px; height: 16px; border-radius: 4px; font-size: 8px; } +.reader-head h2 { margin: 4px 0 0; font-size: 20px; font-weight: 660; letter-spacing: -0.02em; } +.reader-head .meta { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; } +.reader-head .meta .chip { font: 11px var(--mono); padding: 2px 8px; } +.icon-btn { + width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--line); + background: transparent; color: var(--muted); display: grid; place-items: center; + transition: background 0.12s, color 0.12s; +} +.icon-btn:hover { background: rgba(255,255,255,0.05); color: var(--text); } + +.reader-tabs { display: flex; gap: 2px; padding: 8px 14px 0; border-bottom: 1px solid var(--line); overflow-x: auto; } +.reader-tabs button { + border: 0; background: transparent; padding: 8px 10px; color: var(--muted); + font-size: 12.5px; border-bottom: 2px solid transparent; margin-bottom: -1px; white-space: nowrap; + font-family: var(--mono); +} +.reader-tabs button[aria-selected="true"] { color: var(--text); border-bottom-color: var(--tint); } + +.reader-body { flex: 1; overflow-y: auto; padding: 8px 28px 32px; scrollbar-width: thin; } +.reader-foot { + border-top: 1px solid var(--line); padding: 12px 20px; + display: flex; align-items: center; gap: 10px; + background: linear-gradient(to top, var(--panel), rgba(18,22,30,0.9)); +} +.reader-foot .status { font-size: 12.5px; color: var(--muted); flex: 1; min-width: 0; } +.reader-foot .status.ok { color: var(--ok); } +.reader-foot .status.warn { color: var(--warn); } +.reader-foot .status.err { color: var(--danger); } + +.btn { + border: 1px solid transparent; border-radius: 9px; padding: 8px 14px; font-weight: 600; font-size: 13px; + background: var(--tint); color: #06101f; + transition: filter 0.12s, transform 0.08s; +} +.btn:hover { filter: brightness(1.08); } +.btn:active { transform: translateY(1px); } +.btn:disabled { opacity: 0.5; cursor: not-allowed; } +.btn.ghost { background: transparent; color: var(--text-2); border-color: var(--line-strong); } +.btn.ghost:hover { background: rgba(255,255,255,0.04); filter: none; } + +/* Prose -------------------------------------------------------------- */ +.prose { font-size: 14.5px; line-height: 1.65; color: var(--text-2); } +.prose > :first-child { margin-top: 12px; } +.prose h1, .prose h2, .prose h3, .prose h4 { color: var(--text); letter-spacing: -0.015em; scroll-margin-top: 12px; } +.prose h1 { font-size: 22px; margin: 28px 0 10px; } +.prose h2 { font-size: 18px; margin: 26px 0 8px; padding-top: 14px; border-top: 1px solid var(--line); } +.prose h3 { font-size: 15px; margin: 20px 0 6px; } +.prose h4 { font-size: 14px; margin: 16px 0 4px; color: var(--text-2); } +.prose p { margin: 8px 0 12px; } +.prose ul, .prose ol { padding-left: 22px; margin: 6px 0 12px; } +.prose li { margin: 3px 0; } +.prose li.task { list-style: none; margin-left: -18px; } +.prose code { font: 12.5px var(--mono); background: rgba(255,255,255,0.06); border: 1px solid var(--line); padding: 1px 5px; border-radius: 5px; color: var(--text); } +.prose pre { background: var(--bg); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; overflow-x: auto; margin: 10px 0 14px; } +.prose pre code { background: none; border: 0; padding: 0; font-size: 12.5px; line-height: 1.55; } +.prose blockquote { margin: 10px 0; padding: 6px 14px; border-left: 3px solid var(--tint); background: rgba(255,255,255,0.03); border-radius: 0 8px 8px 0; color: var(--text-2); } +.prose blockquote p { margin: 6px 0; } +.prose hr { border: 0; border-top: 1px solid var(--line); margin: 20px 0; } +.prose table { border-collapse: collapse; width: 100%; margin: 10px 0 16px; font-size: 13px; } +.prose th, .prose td { border: 1px solid var(--line); padding: 7px 10px; text-align: left; vertical-align: top; } +.prose th { background: rgba(255,255,255,0.04); color: var(--text); font-weight: 600; } +.prose img { max-width: 100%; border-radius: 8px; } +.prose strong { color: var(--text); } + +/* Bot picker --------------------------------------------------------- */ +.picker-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.45); backdrop-filter: blur(2px); z-index: 20; animation: fade 0.15s ease; } +.picker { + position: fixed; z-index: 21; + left: 50%; top: 50%; translate: -50% -50%; + width: min(560px, calc(100vw - 32px)); max-height: min(640px, calc(100vh - 48px)); + display: flex; flex-direction: column; + background: var(--panel-2); border: 1px solid var(--line-strong); border-radius: 16px; + box-shadow: 0 30px 80px rgba(0,0,0,0.6); + animation: pop 0.18s cubic-bezier(0.2, 0.8, 0.2, 1); + overflow: hidden; +} +@keyframes pop { from { opacity: 0; translate: -50% calc(-50% + 8px); scale: 0.98; } } +@keyframes fade { from { opacity: 0; } } +.picker-head { padding: 16px 18px 10px; } +.picker-head .what { font-size: 12px; color: var(--muted); } +.picker-head .what b { color: var(--text); font-weight: 600; } +.picker-head h3 { margin: 4px 0 10px; font-size: 17px; font-weight: 650; letter-spacing: -0.015em; } +.picker-head input { + width: 100%; background: var(--bg-2); border: 1px solid var(--line); border-radius: 10px; padding: 9px 12px; outline: none; +} +.picker-head input:focus { border-color: var(--line-strong); } +.picker-list { overflow-y: auto; padding: 4px 8px 10px; scrollbar-width: thin; } +.picker-list .list-heading { padding: 10px 10px 4px; } +.bot { + display: grid; grid-template-columns: 30px 1fr auto; gap: 10px; align-items: center; + width: 100%; text-align: left; padding: 8px 10px; border-radius: 10px; border: 0; background: transparent; +} +.bot > span { min-width: 0; } +.bot:hover, .bot[aria-selected="true"] { background: rgba(255,255,255,0.05); } +.bot .bname { font-weight: 560; } +.bot .bdesc { display: block; font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.bot .kind { font: 11px var(--mono); color: var(--muted); } +.avatar { + width: 30px; height: 30px; display: grid; place-items: center; + font-size: 12px; font-weight: 700; color: #fff; + background: var(--av, #556); border-radius: 50%; +} +.avatar.squircle { border-radius: 30%; } +.avatar.hex { clip-path: polygon(25% 5%, 75% 5%, 100% 50%, 75% 95%, 25% 95%, 0 50%); } +.avatar.shield { clip-path: polygon(0 0, 100% 0, 100% 60%, 50% 100%, 0 60%); } +.avatar.blob { border-radius: 60% 40% 55% 45% / 45% 55% 45% 55%; } +.avatar.cloud { border-radius: 50% 50% 45% 45%; } +.avatar.leaf { border-radius: 0 50% 0 50%; } +.avatar.wedge { clip-path: polygon(0 0, 100% 0, 50% 100%); } +.avatar.group { background: linear-gradient(135deg, #556, #334); border-radius: 8px; } +.picker-state { padding: 26px 18px; text-align: center; color: var(--muted); font-size: 13px; } +.picker-state .btn { margin-top: 10px; } + +.confirm { padding: 14px 18px 18px; border-top: 1px solid var(--line); display: grid; gap: 12px; } +.confirm .sentence { font-size: 14px; color: var(--text-2); line-height: 1.5; } +.confirm .sentence b { color: var(--text); } +.confirm .fine { font-size: 12px; color: var(--muted); } +.confirm .row { display: flex; gap: 8px; justify-content: flex-end; } + +/* Toast -------------------------------------------------------------- */ +.toast { + position: fixed; left: 50%; bottom: 22px; translate: -50% 0; z-index: 30; + background: var(--panel-2); border: 1px solid var(--line-strong); color: var(--text); + padding: 10px 14px; border-radius: 10px; font-size: 13px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); + animation: rise 0.2s ease; +} +.toast.ok { border-color: rgba(79,216,148,0.4); } +.toast.err { border-color: rgba(255,123,134,0.5); } +@keyframes rise { from { opacity: 0; translate: -50% 8px; } } + +/* Loading ------------------------------------------------------------ */ +.skeleton { background: linear-gradient(90deg, rgba(255,255,255,0.04), rgba(255,255,255,0.08), rgba(255,255,255,0.04)); background-size: 200% 100%; animation: shimmer 1.2s infinite; border-radius: 6px; height: 12px; } +@keyframes shimmer { to { background-position: -200% 0; } } + +/* Responsive --------------------------------------------------------- */ +@media (max-width: 1180px) { + .shell.reader-open { grid-template-columns: var(--sidebar-w) minmax(0, 1fr) 0; } + .shell.reader-open .reader { position: fixed; right: 0; top: 0; bottom: 0; width: min(var(--reader-w), 100vw); box-shadow: -20px 0 60px rgba(0,0,0,0.5); z-index: 10; } +} +@media (max-width: 900px) { + :root { --sidebar-w: 232px; --reader-w: 100vw; } + .main-inner { padding: 20px 18px 60px; } + .hero h2 { font-size: 22px; } +} diff --git a/artifact/commands/plugin-library.md b/artifact/commands/plugin-library.md new file mode 100644 index 0000000..6a1ceff --- /dev/null +++ b/artifact/commands/plugin-library.md @@ -0,0 +1,24 @@ +# /plugin-library [plugin or skill] + +Launch the explorer and put it in front of the user. + +1. Start (idempotent) and resolve the deep link: + + ```sh + node "${CURSOR_PLUGIN_ROOT}/scripts/plugin-library.mjs" open --json $ARGUMENTS + ``` + + The result is `{ ok, url, resolved, started }`. `url` already contains the + `#/p//s/` route when `$ARGUMENTS` matched a plugin name, plugin id, + or skill id. + +2. Show it in the embedded browser when this session has one: call + `browser_navigate` (cursor-ide-browser) with `url`. Without an embedded + browser, re-run step 1 with `--browser` to hand the URL to the OS browser. + +3. Reply with the URL as a link, whether the server was freshly started, and, + if `resolved` is false with a non-empty argument, say the argument matched + nothing and the library root opened instead. + +Never POST to `/api/send` on the user's behalf. Sending a skill to a bot is a +user-confirmed click in the UI. diff --git a/artifact/install.mjs b/artifact/install.mjs new file mode 100644 index 0000000..bd823bd --- /dev/null +++ b/artifact/install.mjs @@ -0,0 +1,1332 @@ +#!/usr/bin/env node +import { execFile } from 'node:child_process'; +import { createHash, randomUUID } from 'node:crypto'; +import { cp, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, rmdir, writeFile } from 'node:fs/promises'; +import { homedir } from 'node:os'; +import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const pluginName = "plugin-library"; +const pluginVersion = "0.4.0"; +const receiptFile = ".agent-bundle-install.json"; +const receiptFormat = "agent-bundle-install-receipt/2"; +const legacyReceiptFormat = "agent-bundle-install-receipt/1"; +const preservedEntries = ["state"]; +// Runtime roots match case-insensitively: on case-insensitive filesystems State/ is state/. +const isPreservedRoot = (name) => preservedEntries.includes(String(name).toLowerCase()); +const markerFiles = ["INSTALL.md","install.mjs"]; +const source = resolve(fileURLToPath(new URL('.', import.meta.url))); +const artifactManifest = await (async () => { + try { + const value = JSON.parse(await readFile(join(source, 'agent-bundle.manifest.json'), 'utf8')); + if (value === null || typeof value !== 'object' || Array.isArray(value) || !Array.isArray(value.files) || !Array.isArray(value.projections)) { + throw new Error('agent-bundle.manifest.json has no files or projections array.'); + } + return value; + } catch (error) { if (error?.code === 'ENOENT') return undefined; throw error; } +})(); +const installProjection = artifactManifest?.projections.find((projection) => projection?.builtInHost === 'cursor') ?? + artifactManifest?.projections.find((projection) => projection?.builtInHost === 'portable'); +const declaredDocument = (name) => typeof installProjection?.documents?.[name] === 'string' ? installProjection.documents[name] : undefined; +const cursorPluginDocument = declaredDocument('plugin') ?? '.cursor-plugin/plugin.json'; +const cursorRoot = join(homedir(), '.cursor'); +const installRoot = join(cursorRoot, 'plugins', 'local'); +const destination = join(installRoot, pluginName); +const marketplaceRoot = join(cursorRoot, 'agent-bundle', 'marketplaces'); +const marketplaceRepo = join(marketplaceRoot, pluginName); +const marketplacePlugin = join(marketplaceRepo, 'plugins', pluginName); +// Agent Plugins 1.0.0 §9.1 PLUGIN_DATA for the Cursor copy: a writable, install-independent directory the installer creates. +const pluginData = join(cursorRoot, 'agent-bundle', 'plugin-data', pluginName); +const receiptsRoot = join(cursorRoot, 'agent-bundle', 'receipts'); +const marketplaceReceipt = join(receiptsRoot, `${pluginName}.marketplace.json`); +const usage = 'Usage: node install.mjs [--mode local|marketplace] [--replace|--force] [--help]\n node install.mjs --uninstall [--mode local|marketplace] [--keep-data | --purge-data --confirm-purge] [--force] [--plan]'; + +let replace = false; +let force = false; +let uninstall = false; +let plan = false; +let keepData = false; +let purgeData = false; +let confirmPurge = false; +let mode = 'local'; +const argv = process.argv.slice(2); +for (let index = 0; index < argv.length; index += 1) { + const argument = argv[index]; + if (argument === '--replace' || argument === '--force') { replace = true; force = argument === '--force'; continue; } + if (argument === '--uninstall') { uninstall = true; continue; } + if (argument === '--plan') { plan = true; continue; } + if (argument === '--keep-data') { keepData = true; continue; } + if (argument === '--purge-data') { purgeData = true; continue; } + if (argument === '--confirm-purge') { confirmPurge = true; continue; } + if (argument === '--mode') { + mode = argv[index + 1]; + if (mode !== 'local' && mode !== 'marketplace') { console.error(`Install mode must be local or marketplace.\n${usage}`); process.exit(2); } + index += 1; + continue; + } + if (argument === '--help' || argument === '-h') { console.log(usage); process.exit(0); } + console.error(`Unknown installer argument ${JSON.stringify(argument)}.\n${usage}`); + process.exit(2); +} +if (!uninstall && (plan || keepData || purgeData || confirmPurge)) { + console.error(`--plan, --keep-data, --purge-data, and --confirm-purge apply to --uninstall only.\n${usage}`); + process.exit(2); +} +if (uninstall && purgeData && keepData) { console.error(`--keep-data and --purge-data are mutually exclusive.\n${usage}`); process.exit(2); } +if (uninstall && purgeData && !confirmPurge) { + console.error(`--purge-data deletes the plugin's durable runtime state (state kernel, notices journal) and requires --confirm-purge; omit both flags to keep the data.\n${usage}`); + process.exit(2); +} + +const exists = async (path) => { + try { await lstat(path); return true; } + catch (error) { if (error?.code === 'ENOENT') return false; throw error; } +}; +const unsupported = (relative) => new Error(`Refusing unsupported filesystem entry ${JSON.stringify(relative || '.')}.`); +const toPosix = (path) => path.replaceAll('\\', '/'); +const short = (hash) => hash.slice(0, 12); +const sortNames = (names) => [...names].sort((left, right) => left.localeCompare(right)); +const compareTreePaths = (left, right) => { + const leftSegments = left.split('/'); + const rightSegments = right.split('/'); + for (let index = 0; index < Math.min(leftSegments.length, rightSegments.length); index += 1) { + const compared = leftSegments[index].localeCompare(rightSegments[index]); + if (compared !== 0) return compared; + } + return leftSegments.length - rightSegments.length; +}; +// Every ancestor directory of the given POSIX-relative files, deduplicated and sorted. +const directoriesOf = (files) => { + const directories = new Set(); + for (const file of files) { + let directory = dirname(file); + while (directory !== '.' && directory !== '') { directories.add(directory); directory = dirname(directory); } + } + return sortNames(directories); +}; + +// path\0mode\0bytes\0 per file; mode is x for an executable and - otherwise. +const hashEntry = (hash, relative, metadata, bytes) => { + hash.update(toPosix(relative)); + hash.update('\0'); + hash.update((metadata.mode & 0o111) === 0 ? '-' : 'x'); + hash.update('\0'); + hash.update(bytes); + hash.update('\0'); +}; + +// Deterministic tree walk: symlinks and special files refused, the root receipt skipped. `transform` +// maps a file's bytes to what the Cursor copy will hold (the Agent Plugins mcp.json expansion below), so +// the artifact hash describes the installed form and reruns compare like for like. +const readTree = async (root, transform, selectedPaths) => { + const rootMetadata = await lstat(root); + if (rootMetadata.isSymbolicLink() || !rootMetadata.isDirectory()) throw unsupported('.'); + const hash = createHash('sha256'); + const files = []; + const visit = async (relative) => { + const absolute = join(root, relative); + const metadata = await lstat(absolute); + if (metadata.isSymbolicLink() || (!metadata.isDirectory() && !metadata.isFile())) throw unsupported(relative); + if (metadata.isDirectory()) { + for (const entry of (await readdir(absolute)).sort((left, right) => left.localeCompare(right))) await visit(join(relative, entry)); + return; + } + // Every inventoried path must round-trip through a receipt unchanged (no backslash in a POSIX + // name, nothing the receipt reader rejects); refuse the rest up front. + const posixPath = toPosix(relative); + if ((sep === '/' && relative.includes('\\')) || !safeRelative(posixPath)) throw unsupported(sep === '/' ? relative : posixPath); + files.push(posixPath); + const bytes = await readFile(absolute); + hashEntry(hash, relative, metadata, transform === undefined ? bytes : transform(posixPath, bytes)); + }; + if (selectedPaths !== undefined) { + for (const file of selectedPaths) { + if (!(await exists(join(root, file)))) throw new Error(`bundle does not match its manifest: ${file} is missing.`); + await visit(file); + } + return { files, hash: hash.digest('hex') }; + } + for (const entry of (await readdir(root)).sort((left, right) => left.localeCompare(right))) { + if (entry === receiptFile) { + // The receipt is deletion authority: skipped from the hash, must be a regular file. + if (!(await lstat(join(root, entry))).isFile()) throw unsupported(entry); + continue; + } + // Runtime-owned roots (state/) are never plugin content: not hashed, not installed, not owned. + if (isPreservedRoot(entry)) continue; + await visit(entry); + } + return { files, hash: hash.digest('hex') }; +}; +const inventory = (root, transform) => readTree(root, transform, undefined); +const artifactInventory = async (root, transform) => { + if (artifactManifest === undefined) return inventory(root, transform); + const selected = new Set(['agent-bundle.manifest.json', ...artifactManifest.files.map((file) => file.path)]); + for (const file of ['.env', '.env.local']) if (await exists(join(root, file))) selected.add(file); + return readTree(root, transform, [...selected].sort(compareTreePaths)); +}; + +const treeHash = async (root) => (await inventory(root)).hash; + +// An existing entry is one of ours when its exact name is owned, or when the filesystem proves it a case +// alias of an owned name (both spellings realpath to the same on-disk path). Inode equality is not used. +const isOwnedEntry = async (root, owned, relative) => { + if (owned.has(relative)) return true; + const alias = relative.toLowerCase(); + let canonical; + for (const candidate of owned) { + if (candidate.toLowerCase() !== alias) continue; + try { + canonical ??= await realpath(join(root, relative)); + if ((await realpath(join(root, candidate))) === canonical) return true; + } catch (error) { if (error?.code !== 'ENOENT') throw error; } + } + return false; +}; + +// Every directory on the way to a listed path must be a real directory (no symlinked ancestors); +// an owned regular file that a rebuild turns into a directory leaves as stale first, so it is tolerated. +const assertRealAncestors = async (root, files, ownedFiles = new Set()) => { + const checked = new Set(); + for (const file of files) { + let directory = dirname(file); + while (directory !== '.' && directory !== '') { + if (!checked.has(directory)) { + checked.add(directory); + try { + const metadata = await lstat(join(root, directory)); + if (metadata.isSymbolicLink()) throw unsupported(directory); + if (!metadata.isDirectory() && !(metadata.isFile() && await isOwnedEntry(root, ownedFiles, directory))) { + throw unsupported(directory); + } + } catch (error) { if (error?.code !== 'ENOENT') throw error; } + } + directory = dirname(directory); + } + } +}; + +// Hashes exactly the owned files; a missing owned file simply changes the digest. +const hashOwned = async (root, files) => { + await assertRealAncestors(root, files); + const hash = createHash('sha256'); + for (const relative of files) { + const absolute = join(root, relative); + let metadata; + try { metadata = await lstat(absolute); } catch (error) { if (error?.code === 'ENOENT') continue; throw error; } + if (metadata.isSymbolicLink() || !metadata.isFile()) throw unsupported(relative); + hashEntry(hash, relative, metadata, await readFile(absolute)); + } + return hash.digest('hex'); +}; + +// Receipt paths drive deletions: POSIX-relative only, no backslashes, no empty/./.. segments, and no +// segment Windows would normalise onto another entry (reserved characters, colons, trailing dot/space) +// or resolve as a DOS device (NUL, CON.txt, COM1, LPT1.json, ...). +const windowsDeviceName = /^(?:con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³])(?:\.|$)/iu; +// The receipt name is reserved as a top-level entry in every spelling, file or directory (a case alias +// resolves to the receipt itself on case-insensitive filesystems). +const safeRelative = (value) => typeof value === 'string' && value.length > 0 && + value.split('/')[0].toLowerCase() !== receiptFile.toLowerCase() && + !value.includes('\\') && !value.startsWith('/') && !isPreservedRoot(value.split('/')[0]) && + value.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..' && + !/[<>:"|?*]/u.test(segment) && !windowsDeviceName.test(segment) && + [...segment].every((character) => character.charCodeAt(0) >= 0x20) && + !segment.endsWith('.') && !segment.endsWith(' ')); + +const registrationKinds = ["amp-project-plugin","amp-system-plugin","claude-marketplace","claude-plugin","codex-marketplace","codex-plugin","cursor-local-plugin","cursor-marketplace-staging"]; +const isScope = (value) => value === 'local' || value === 'project' || value === 'user'; +const isRegistration = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) && + registrationKinds.includes(value.kind) && + (value.commit === undefined || typeof value.commit === 'string') && (value.id === undefined || typeof value.id === 'string') && + (value.name === undefined || typeof value.name === 'string') && (value.scope === undefined || isScope(value.scope)); +const isStateOwnership = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) && ( + value.kind === 'derived' || + (value.kind === 'marker' && typeof value.marker === 'string' && isAbsolute(value.marker)) || + (value.kind === 'unowned' && ['foreign-marker', 'pre-existing', 'unproven'].includes(value.reason))); +const isReceiptState = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) && + value.owner !== null && typeof value.owner === 'object' && !Array.isArray(value.owner) && + typeof value.owner.id === 'string' && value.owner.id.length > 0 && typeof value.owner.host === 'string' && + ['host-cli', 'local', 'marketplace'].includes(value.owner.mode) && typeof value.owner.plugin === 'string' && isScope(value.owner.scope) && + Array.isArray(value.roots) && value.roots.every((root) => root !== null && typeof root === 'object' && !Array.isArray(root) && + typeof root.root === 'string' && isAbsolute(root.root) && typeof root.canonicalRoot === 'string' && isAbsolute(root.canonicalRoot) && + ['declared', 'derived'].includes(root.source) && Array.isArray(root.servers) && root.servers.every((server) => typeof server === 'string') && + isStateOwnership(root.ownership)); +// Same shape check as the core reader: a receipt missing any field reads as absent. A format/1 receipt (#420) +// is read with its lifecycle fields synthesized (local mode, user scope, one cursor-local-plugin registration, +// no host directories) and `migratedFrom` set; the next replacement rewrites it as the current format. +const readReceiptFile = async (path) => { + let value; + try { + if (!(await lstat(path)).isFile()) throw unsupported(basename(path)); + value = JSON.parse(await readFile(path, 'utf8')); + } catch (error) { if (error?.code === 'ENOENT' || error instanceof SyntaxError) return undefined; throw error; } + if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined; + if ((value.format !== receiptFormat && value.format !== legacyReceiptFormat) || + typeof value.plugin !== 'string' || typeof value.version !== 'string' || + typeof value.host !== 'string' || typeof value.contentHash !== 'string' || typeof value.installedAt !== 'string' || + !Array.isArray(value.files) || !value.files.every(safeRelative) || + !Array.isArray(value.directories) || !value.directories.every(safeRelative) || + (value.state !== undefined && !isReceiptState(value.state)) || + (value.stateRoot !== undefined && (value.stateRoot === null || typeof value.stateRoot !== 'object' || Array.isArray(value.stateRoot) || typeof value.stateRoot.root !== 'string' || !['derived', 'native'].includes(value.stateRoot.source))) || + (value.webDataRoot !== undefined && typeof value.webDataRoot !== 'string')) return undefined; + if (value.format === legacyReceiptFormat) { + return { ...value, format: receiptFormat, hostDirectories: [], migratedFrom: legacyReceiptFormat, mode: 'local', + registrations: [{ kind: 'cursor-local-plugin' }], scope: 'user', updatedAt: value.installedAt }; + } + if (!['host-cli', 'local', 'marketplace'].includes(value.mode) || !isScope(value.scope) || typeof value.updatedAt !== 'string' || + !Array.isArray(value.hostDirectories) || !value.hostDirectories.every(safeRelative) || + !Array.isArray(value.registrations) || !value.registrations.every(isRegistration)) return undefined; + if (value.state !== undefined && (value.state.owner.host !== value.host || value.state.owner.mode !== value.mode || + value.state.owner.plugin !== value.plugin || value.state.owner.scope !== value.scope || + value.state.owner.projectRoot !== value.projectRoot)) { value = { ...value }; delete value.state; } + return value; +}; +const readReceipt = (root) => readReceiptFile(join(root, receiptFile)); +// Atomic receipt write: an exclusively created random sibling (never follows a link) renamed into place. +const writeReceiptFile = async (path, text) => { + const temporary = `${path}.${randomUUID()}.tmp`; + const handle = await open(temporary, 'wx'); + try { + await handle.writeFile(text, 'utf8'); + await handle.close(); + await rename(temporary, path); + } finally { + await rm(temporary, { force: true }); + } +}; +const rmdirIfEmpty = async (path) => { + try { await rmdir(path); return true; } + catch (error) { if (['ENOTEMPTY', 'ENOENT', 'EEXIST', 'ENOTDIR'].includes(error?.code)) return false; throw error; } +}; + +const readManifest = async (root) => { + for (const manifest of declaredDocument('plugin') === undefined ? ['.cursor-plugin/plugin.json', 'plugin.json'] : [declaredDocument('plugin')]) { + try { + const value = JSON.parse(await readFile(join(root, manifest), 'utf8')); + if (value !== null && typeof value === 'object' && typeof value.name === 'string') { + return { name: value.name, ...(typeof value.version === 'string' ? { version: value.version } : {}) }; + } + } catch (error) { if (error?.code !== 'ENOENT' && !(error instanceof SyntaxError)) throw error; } + } + return undefined; +}; + +const hasMarkers = async (root) => { + for (const marker of markerFiles) if (!(await exists(join(root, marker)))) return false; + return true; +}; + +// Agent Plugins packs (root plugin.json with an agent-plugins.org $schema, no .cursor-plugin/plugin.json). +// Observed on Cursor 3.18.25 (docs/audits/2026-09-03-agent-plugins-cursor-ide-proof.md): the loader spawns their +// stdio servers without expanding ${PLUGIN_ROOT}/${PLUGIN_DATA} in args, env values or cwd, without providing +// the reserved PLUGIN_ROOT/PLUGIN_DATA variables (spec 9.1), with an omitted cwd defaulting to the home directory +// and with ./ commands resolved against the workspace folder (spec 7.2.1). The installer therefore expands those +// forms itself in the Cursor copy of mcp.json and records the expansion in the receipt (provenance: derived). +const agentPluginsSchemaPrefix = 'https://agent-plugins.org/schemas/'; +const isAgentPluginsPack = async (root) => { + if (artifactManifest !== undefined) { + const plugin = declaredDocument('plugin'); + if (plugin === undefined) throw new Error('agent-bundle.manifest.json has no Cursor-compatible plugin document.'); + try { + const manifest = JSON.parse(await readFile(join(root, plugin), 'utf8')); + return manifest !== null && typeof manifest === 'object' && typeof manifest.$schema === 'string' && manifest.$schema.startsWith(agentPluginsSchemaPrefix); + } catch (error) { if (error?.code === 'ENOENT' || error?.code === 'EISDIR') return false; throw error; } + } + if (await exists(join(root, '.cursor-plugin', 'plugin.json'))) return false; + try { + const manifest = JSON.parse(await readFile(join(root, 'plugin.json'), 'utf8')); + return manifest !== null && typeof manifest === 'object' && typeof manifest.$schema === 'string' && manifest.$schema.startsWith(agentPluginsSchemaPrefix); + } catch (error) { if (error?.code === 'ENOENT' || error?.code === 'EISDIR' || error instanceof SyntaxError) return false; throw error; } +}; +const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value); +const expandPlaceholders = (value) => value.replaceAll('${PLUGIN_ROOT}', destination).replaceAll('${PLUGIN_DATA}', pluginData); +// A plugin-relative ./ path resolves against the plugin root (spec 7.2.1); anything else is expanded in place. +const expandPath = (value) => value.startsWith('./') ? join(destination, value.slice(2)) : expandPlaceholders(value); +// Every stdio server rewritten into the form Cursor launches; undefined when the document has none to expand +// (skills-only or remote-only packs stay byte-identical to the bundle). Doctor recomputes this exact output +// from the receipt (install/cursor-agent-plugins-launch.ts expandAgentPluginsMcpForCursor); keep the two in step. +const expandAgentPluginsMcp = (text) => { + let document; + try { document = JSON.parse(text); } catch { return undefined; } + if (!isRecord(document) || !isRecord(document.mcpServers)) return undefined; + let expanded = false; + const servers = {}; + for (const [name, server] of Object.entries(document.mcpServers)) { + if (!isRecord(server) || server.type !== 'stdio' || typeof server.command !== 'string') { servers[name] = server; continue; } + expanded = true; + const env = {}; + for (const [key, value] of Object.entries(isRecord(server.env) ? server.env : {})) env[key] = typeof value === 'string' ? expandPlaceholders(value) : value; + env.PLUGIN_ROOT = destination; + env.PLUGIN_DATA = pluginData; + servers[name] = { + ...server, + command: expandPath(server.command), + ...(Array.isArray(server.args) ? { args: server.args.map((argument) => typeof argument === 'string' ? expandPlaceholders(argument) : argument) } : {}), + cwd: typeof server.cwd === 'string' ? expandPath(server.cwd) : destination, + env, + }; + } + if (!expanded) return undefined; + return `${JSON.stringify({ ...document, mcpServers: servers }, null, 2)}\n`; +}; +const agentPluginsPack = await isAgentPluginsPack(source); +const expansion = await (async () => { + if (!agentPluginsPack) return undefined; + let text; + const mcpDocument = declaredDocument('mcp') ?? 'mcp.json'; + try { text = await readFile(join(source, mcpDocument), 'utf8'); } + catch (error) { if (error?.code === 'ENOENT' || error?.code === 'EISDIR') return undefined; throw error; } + const expanded = expandAgentPluginsMcp(text); + return expanded === undefined ? undefined : { documents: { [mcpDocument]: text }, expanded, mcpDocument, pluginData, pluginRoot: destination }; +})(); +// The Cursor copy differs from the bundle in exactly that one file; the artifact inventory hashes the expanded +// form so receipts, no-op reruns and replacement compare the bundle with what the copy must hold. +const cursorTransform = expansion === undefined ? undefined : (relativePath, bytes) => relativePath === expansion.mcpDocument ? Buffer.from(expansion.expanded, "utf8") : bytes; + +// directories: the ones the installer created (all of them on a fresh install); only those are ever pruned. +// hostDirectories: the ones created under ~/.cursor on the way to the plugin root; installedAt carries over from a +// replaced receipt; mode/registrations describe the delivery so --uninstall reverses exactly that. +const receiptFor = (tree, options = {}) => { + const now = new Date().toISOString(); + const mode = options.mode ?? 'local'; + return JSON.stringify({ + contentHash: tree.hash, + // The pre-expansion document bytes and the values substituted, so Doctor validates the Agent Plugins + // contract against what the bundle shipped and proves the expansion against what Cursor spawns. Only a + // local copy that owns files carries it: marketplace staging holds no mcp.json, and a remnant receipt carries + // it only when --uninstall --keep-data preserved a written PLUGIN_DATA directory (options.cursorExpansion). + ...(options.cursorExpansion !== undefined ? { cursorExpansion: options.cursorExpansion } : expansion === undefined || mode !== 'local' || tree.files.length === 0 ? {} : { cursorExpansion: { documents: expansion.documents, pluginData: expansion.pluginData, pluginRoot: expansion.pluginRoot } }), + directories: options.directories ?? directoriesOf(tree.files), + files: tree.files, + format: receiptFormat, + host: 'cursor', + hostDirectories: sortNames(options.hostDirectories ?? []), + installedAt: options.installedAt ?? now, + mode, + plugin: pluginName, + registrations: options.registrations ?? [{ kind: 'cursor-local-plugin' }], + scope: 'user', + ...(options.state === undefined ? {} : { state: options.state }), + ...(options.stateRoot === undefined ? {} : { stateRoot: options.stateRoot }), + updatedAt: now, + version: pluginVersion, + ...(options.webDataRoot === undefined ? {} : { webDataRoot: options.webDataRoot }), + }, null, 2) + '\n'; +}; + +const stateMarkerFile = ".agent-bundle-state-owner.json"; +const expandStatePath = (value, root) => value.replaceAll('${CURSOR_PLUGIN_ROOT}', root).replaceAll('${PLUGIN_ROOT}', root); +const canonicalPath = async (path) => { + try { return await realpath(path); } + catch (error) { if (error?.code !== 'ENOENT') throw error; const parent = dirname(path); return parent === path ? resolve(path) : join(await canonicalPath(parent), basename(path)); } +}; +const stateLocations = async () => { + const canonical = await realpath(destination); + let servers = []; + for (const manifest of declaredDocument('mcp') === undefined ? ['.cursor-plugin/mcp.json', 'mcp.json'] : [declaredDocument('mcp')]) { + let document; + try { document = JSON.parse(await readFile(join(canonical, manifest), 'utf8')); } + catch (error) { if (error?.code === 'ENOENT' || error instanceof SyntaxError) continue; throw error; } + if (document?.mcpServers !== null && typeof document?.mcpServers === 'object' && !Array.isArray(document.mcpServers)) { + servers = Object.entries(document.mcpServers).filter(([, server]) => server !== null && typeof server === 'object' && !Array.isArray(server)).sort(([left], [right]) => left.localeCompare(right)); + break; + } + } + const xdg = process.env.XDG_STATE_HOME ?? ''; + const stateHome = isAbsolute(xdg) ? join(xdg, 'agent-bundle') : join(homedir(), '.agent-bundle', 'state'); + const digest = createHash('sha256').update(canonical).digest('hex').slice(0, 16); + const name = /^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$/u.test(basename(canonical)) ? basename(canonical) : 'plugin'; + const derived = join(stateHome, `${name}-${digest}`); + if (servers.length === 0) { + const inherited = process.env.AGENT_BUNDLE_STATE_ROOT; + if (typeof inherited !== 'string' || inherited.trim() === '') return [{ root: derived, server: 'default', source: 'derived' }]; + const expanded = expandStatePath(inherited, canonical); + if (/\$\{[^}]*\}/u.test(expanded)) return [{ root: derived, server: 'default', source: 'derived' }]; + return isAbsolute(expanded) ? [{ root: resolve(expanded), server: 'default', source: 'declared' }] : []; + } + const locations = []; + for (const [server, definition] of servers) { + const value = definition?.env?.AGENT_BUNDLE_STATE_ROOT ?? process.env.AGENT_BUNDLE_STATE_ROOT; + if (typeof value !== 'string' || value.trim() === '') { locations.push({ root: derived, server, source: 'derived' }); continue; } + const expanded = expandStatePath(value, canonical); + if (/\$\{[^}]*\}/u.test(expanded)) { locations.push({ root: derived, server, source: 'derived' }); continue; } + if (isAbsolute(expanded)) { locations.push({ root: resolve(expanded), server, source: 'declared' }); continue; } + if (typeof definition?.cwd !== 'string') continue; + const expandedCwd = expandStatePath(definition.cwd, canonical); + if (/\$\{[^}]*\}/u.test(expandedCwd)) continue; + const cwd = isAbsolute(expandedCwd) ? resolve(expandedCwd) : resolve(canonical, expandedCwd); + locations.push({ root: resolve(cwd, expanded), server, source: 'declared' }); + } + return locations; +}; +const attachStateOwnership = async (previousState) => { + const receipt = await readReceipt(destination); + if (receipt === undefined) throw new Error(`Installed receipt is missing at ${destination}.`); + const owner = previousState?.owner ?? { host: 'cursor', id: randomUUID(), mode: 'local', plugin: pluginName, scope: 'user' }; + const grouped = new Map(); + for (const location of await stateLocations()) { + const current = grouped.get(location.root); + if (current === undefined) grouped.set(location.root, { ...location, servers: [location.server] }); + else current.servers.push(location.server); + } + const roots = []; + const created = []; + const markerOwns = async (marker) => { + let document; + try { document = JSON.parse(await readFile(marker, 'utf8')); } + catch (error) { if (error?.code === 'ENOENT' || error instanceof SyntaxError) return false; throw error; } + const actual = document?.owner; + return document?.format === 1 && actual?.id === owner.id && actual?.host === owner.host && actual?.mode === owner.mode && + actual?.plugin === owner.plugin && actual?.scope === owner.scope && actual?.projectRoot === owner.projectRoot; + }; + const rollbackCreated = async () => { + for (const root of [...created].reverse()) { + await rm(join(root, stateMarkerFile), { force: true }); + try { await rmdir(root); } catch (error) { if (!['ENOENT', 'ENOTEMPTY'].includes(error?.code)) throw error; } + } + }; + try { + for (const location of grouped.values()) { + if (location.source === 'derived') { roots.push({ canonicalRoot: await canonicalPath(location.root), ownership: { kind: 'derived' }, root: location.root, servers: location.servers, source: 'derived' }); continue; } + try { + const marker = join(location.root, stateMarkerFile); + let existed = true; + try { await lstat(location.root); } catch (error) { if (error?.code !== 'ENOENT') throw error; existed = false; } + let ownership; + if (!existed) { + await mkdir(dirname(location.root), { recursive: true }); + try { + await mkdir(location.root); + created.push(location.root); + const handle = await open(marker, 'wx'); + try { await handle.writeFile(`${JSON.stringify({ format: 1, owner }, null, 2)}\n`, 'utf8'); } finally { await handle.close(); } + ownership = { kind: 'marker', marker }; + } catch (error) { + if (error?.code !== 'EEXIST') { + if (created.at(-1) === location.root) { + created.pop(); + try { await rmdir(location.root); } catch (rollbackError) { if (!['ENOENT', 'ENOTEMPTY'].includes(rollbackError?.code)) throw rollbackError; } + } + throw error; + } + if (created.at(-1) === location.root) created.pop(); + ownership = await markerOwns(marker) ? { kind: 'marker', marker } : { kind: 'unowned', reason: 'foreign-marker' }; + } + } else if (await markerOwns(marker)) ownership = { kind: 'marker', marker }; + else { + let markerExists = true; + try { await lstat(marker); } catch (error) { if (error?.code !== 'ENOENT') throw error; markerExists = false; } + ownership = { kind: 'unowned', reason: markerExists ? 'foreign-marker' : 'pre-existing' }; + } + roots.push({ canonicalRoot: await canonicalPath(location.root), ownership, root: location.root, servers: location.servers, source: 'declared' }); + } catch (error) { + if (!['EACCES', 'ENOTDIR', 'EPERM', 'EROFS'].includes(error?.code)) throw error; + if (created.at(-1) === location.root) { + created.pop(); + await rm(join(location.root, stateMarkerFile), { force: true }); + try { await rmdir(location.root); } catch (rollbackError) { if (!['ENOENT', 'ENOTEMPTY'].includes(rollbackError?.code)) throw rollbackError; } + } + roots.push({ canonicalRoot: resolve(location.root), ownership: { kind: 'unowned', reason: 'unproven' }, root: location.root, servers: location.servers, source: 'declared' }); + } + } + } catch (error) { await rollbackCreated(); throw error; } + try { + await writeReceiptFile(join(destination, receiptFile), `${JSON.stringify({ ...receipt, state: { owner, roots }, updatedAt: new Date().toISOString() }, null, 2)}\n`); + } catch (error) { + await rollbackCreated(); + throw error; + } +}; + +// Staged sibling copy on the destination filesystem so every later rename is atomic. +const stage = async (tree, receiptOptions = {}) => { + const parent = await mkdtemp(join(installRoot, `.${basename(destination)}.stage-`)); + const root = join(parent, 'bundle'); + try { + // Exactly the inventoried content is copied: runtime roots, a stray receipt, and empty directories + // (no plugin content: not hashed, not installed, not owned) never are. + const content = new Set([...tree.files, ...directoriesOf(tree.files)]); + const filter = (path) => { + const relativePath = relative(source, path); + return relativePath === '' || content.has(toPosix(relativePath)); + }; + await cp(source, root, { errorOnExist: true, filter, force: false, recursive: true, verbatimSymlinks: true }); + if (expansion !== undefined) await writeFile(join(root, expansion.mcpDocument), expansion.expanded, 'utf8'); + const staged = await artifactInventory(root); + await writeFile(join(root, receiptFile), receiptFor(staged, receiptOptions), 'utf8'); + return { inventory: staged, parent, root }; + } catch (error) { + await rm(parent, { force: true, recursive: true }); + throw error; + } +}; + +// Prunes the now-empty ancestors of removed files, deepest first, but only installer-created ones: +// a pre-existing directory that merely became an ancestor of an owned file is not ours to delete. +const pruneEmptyDirectories = async (removed, ownedDirectories) => { + const pruned = new Set(); + const candidates = directoriesOf(removed).filter((directory) => ownedDirectories.has(directory)); + for (const directory of candidates.sort((left, right) => right.length - left.length)) { + try { await rmdir(join(destination, directory)); pruned.add(directory); } + catch (error) { if (!['ENOTEMPTY', 'ENOENT', 'EEXIST'].includes(error?.code)) throw error; } + } + return pruned; +}; +// Creates missing ancestors one level at a time and records the ones this run created. +const ensureAncestors = async (file, created) => { + const ancestors = []; + let directory = dirname(file); + while (directory !== '.' && directory !== '') { ancestors.unshift(directory); directory = dirname(directory); } + for (const ancestor of ancestors) { + if (created.has(ancestor)) continue; + try { await mkdir(join(destination, ancestor)); created.add(ancestor); } + catch (error) { if (error?.code !== 'EEXIST') throw error; } + } +}; + +// Unowned entries under root that survive the uninstall, POSIX-relative: files that are neither owned nor runtime state +// (symlinks listed, never followed) plus unowned directories holding nothing retained (`name/`), which the prune never touches. +const listRetained = async (root, owned, ownedDirectories) => { + const retained = []; + const visit = async (relativePath) => { + let entries; + try { entries = (await readdir(join(root, relativePath))).sort((left, right) => left.localeCompare(right)); } + catch (error) { if (error?.code === 'ENOENT') return 0; throw error; } + let kept = 0; + for (const name of entries) { + const child = relativePath === '' ? name : `${relativePath}/${name}`; + if (relativePath === '' && (name === receiptFile || isPreservedRoot(name))) continue; + const metadata = await lstat(join(root, child)); + if (metadata.isDirectory() && !metadata.isSymbolicLink()) { + const below = await visit(child); + if (below === 0 && !ownedDirectories.has(child)) retained.push(`${child}/`); + kept += below === 0 && !ownedDirectories.has(child) ? 1 : below; + continue; + } + if (!owned.has(child)) { retained.push(child); kept += 1; } + } + return kept; + }; + await visit(''); + return retained; +}; +// HEAD of a staged repository from its ref text (no git needed): the commit Cursor imports. +const readHead = async (repoRoot) => { + try { + const head = (await readFile(join(repoRoot, '.git', 'HEAD'), 'utf8')).trim(); + if (!head.startsWith('ref: ')) return /^[0-9a-f]{40}$|^[0-9a-f]{64}$/u.test(head) ? head : undefined; + const ref = head.slice('ref: '.length); + let resolved; + try { resolved = (await readFile(join(repoRoot, '.git', ref), 'utf8')).trim(); } + catch { resolved = (await readFile(join(repoRoot, '.git', 'packed-refs'), 'utf8')).split('\n').find((line) => line.endsWith(` ${ref}`))?.split(' ')[0]; } + return resolved !== undefined && /^[0-9a-f]{40}$|^[0-9a-f]{64}$/u.test(resolved) ? resolved : undefined; + } catch { return undefined; } +}; +const printPaths = (label, paths) => { + console.log(`${label} ${paths.length} ${paths.length === 1 ? "entry" : "entries"}:`); + for (const path of paths) console.log(` ${path}`); +}; +const runtimeStateRoots = async () => { + const canonical = await realpath(destination); + const inherited = process.env.AGENT_BUNDLE_STATE_ROOT ?? ''; + const inheritedStateRoot = inherited.trim() === '' || /\$\{[^}]*\}/u.test(inherited) + ? undefined + : isAbsolute(inherited) ? resolve(inherited) : resolve(canonical, inherited); + let declared; + for (const manifest of declaredDocument('mcp') === undefined ? ['.cursor-plugin/mcp.json', 'mcp.json'] : [declaredDocument('mcp')]) { + let document; + try { document = JSON.parse(await readFile(join(canonical, manifest), 'utf8')); } + catch (error) { if (error?.code === 'ENOENT' || error instanceof SyntaxError) continue; throw error; } + const servers = document !== null && typeof document === 'object' && !Array.isArray(document) ? document.mcpServers : undefined; + if (servers === null || typeof servers !== 'object' || Array.isArray(servers)) continue; + for (const server of Object.values(servers)) { + const env = server !== null && typeof server === 'object' && !Array.isArray(server) ? server.env : undefined; + const value = env !== null && typeof env === 'object' && !Array.isArray(env) ? env.AGENT_BUNDLE_STATE_ROOT : undefined; + if (typeof value !== 'string' || value.trim() === '') continue; + const expanded = value.replaceAll('${CURSOR_PLUGIN_ROOT}', canonical).replaceAll('${PLUGIN_ROOT}', canonical); + if (!/\$\{[^}]*\}/u.test(expanded)) declared = isAbsolute(expanded) ? resolve(expanded) : resolve(canonical, expanded); + if (declared !== undefined) break; + } + if (declared !== undefined) break; + } + const xdg = process.env.XDG_STATE_HOME ?? ''; + const stateHome = isAbsolute(xdg) ? join(xdg, 'agent-bundle') : join(homedir(), '.agent-bundle', 'state'); + const digest = createHash('sha256').update(canonical).digest('hex').slice(0, 16); + const name = /^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$/u.test(basename(canonical)) ? basename(canonical) : 'plugin'; + const segment = `${name}-${digest}`; + const explicitStateRoot = declared ?? inheritedStateRoot; + const webCanonical = resolve(destination); + const webDigest = createHash('sha256').update(webCanonical).digest('hex').slice(0, 16); + const webName = /^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$/u.test(basename(webCanonical)) ? basename(webCanonical) : 'plugin'; + return [explicitStateRoot ?? join(stateHome, segment), join(canonical, 'state'), join(homedir(), '.agent-bundle', 'web-data', `${webName}-${webDigest}`), explicitStateRoot === undefined ? 'derived' : 'native']; +}; + +if (uninstall && mode === 'local') { + const notInstalled = () => { console.log(`Not installed ${pluginName}@${pluginVersion} for cursor (local mode) at ${destination}`); process.exit(0); }; + if (!(await exists(cursorRoot)) || !(await exists(destination))) notInstalled(); + const destinationMetadata = await lstat(destination); + if (destinationMetadata.isSymbolicLink() || !destinationMetadata.isDirectory()) throw unsupported('.'); + const receipt = await readReceipt(destination); + let owned; + let ownedDirectories; + let hostDirectories; + let receiptStatus; + if (receipt === undefined) { + // No receipt: only a legacy layout (emitted install surface + manifest naming this plugin) may go, under --force. + const manifest = await readManifest(destination); + const legacy = manifest?.name === pluginName && await hasMarkers(destination); + if (!legacy) { + throw new Error(`Refusing to uninstall foreign directory ${destination}: it carries no install receipt and is not a ` + + `recognizable agent-bundle install of ${pluginName}${manifest === undefined ? " (no loader manifest)" : ` (manifest names ${JSON.stringify(manifest.name)})`}. ` + + 'Remove it manually if it is stale; --force does not apply to foreign directories.'); + } + if (!force) { + throw new Error(`Refusing to uninstall ${destination} without an install receipt: this copy predates install receipts, so ownership ` + + 'cannot be proven. Re-run with --force to remove its inventoried plugin files (runtime state under state/ is kept unless ' + + '--purge-data --confirm-purge is passed), or reinstall with --replace first to adopt it.'); + } + const tree = await inventory(destination); + owned = tree.files; + ownedDirectories = directoriesOf(tree.files); + hostDirectories = []; + receiptStatus = 'forced-legacy'; + } else { + if (receipt.plugin !== pluginName) { + throw new Error(`Refusing to uninstall ${destination}: its install receipt names plugin ${JSON.stringify(receipt.plugin)}, not ` + + `${JSON.stringify(pluginName)}. Uninstall that plugin from its own bundle instead; --force does not apply.`); + } + const installedHash = await hashOwned(destination, receipt.files); + receiptStatus = receipt.migratedFrom === undefined ? 'consumed' : 'migrated'; + if (installedHash !== receipt.contentHash) { + if (!force) { + throw new Error(`Refusing to uninstall ${destination}: the owned files hash ${short(installedHash)} but the receipt recorded ` + + `${short(receipt.contentHash)}, so the installed copy was modified after installation. Re-run with --force to remove the ` + + 'receipt-owned files anyway (unowned entries are never removed).'); + } + receiptStatus = 'forced-mismatch'; + } + owned = receipt.files; + ownedDirectories = receipt.directories; + hostDirectories = receipt.hostDirectories; + } + // A symlinked ancestor would let a leaf-only delete reach outside the plugin root: refused before any change. + await assertRealAncestors(destination, owned); + const files = []; + for (const file of owned) { + const path = join(destination, file); + let metadata; + try { metadata = await lstat(path); } catch (error) { if (error?.code === 'ENOENT') continue; throw error; } + if (metadata.isSymbolicLink() || !metadata.isFile()) throw unsupported(file); + files.push(path); + } + if (await exists(join(destination, receiptFile))) files.push(join(destination, receiptFile)); + const [resolvedStateDirectory, stateDirectory, resolvedWebDataDirectory, resolvedStateSource] = await runtimeStateRoots(); + const retainedState = []; + const ownedStatePaths = []; + const emptyOwnedStateFiles = []; + const emptyOwnedStateRoots = []; + if (receipt?.state !== undefined) { + for (const root of receipt.state.roots) { + let metadata; + try { metadata = await lstat(root.root); } catch (error) { if (error?.code === 'ENOENT') continue; throw error; } + if (root.ownership.kind === 'unowned') { retainedState.push({ path: root.root, reason: root.ownership.reason }); continue; } + if (!metadata.isDirectory() || metadata.isSymbolicLink()) { retainedState.push({ path: root.root, reason: 'unsupported-entry' }); continue; } + if (await realpath(root.root) !== root.canonicalRoot) { retainedState.push({ path: root.root, reason: 'canonical-path-changed' }); continue; } + if (root.ownership.kind === 'marker') { + let marker; + try { marker = JSON.parse(await readFile(root.ownership.marker, 'utf8')); } catch { marker = undefined; } + const owner = marker?.owner; + const expected = receipt.state.owner; + if (root.ownership.marker !== join(root.root, stateMarkerFile) || marker?.format !== 1 || owner?.id !== expected.id || + owner?.host !== expected.host || owner?.mode !== expected.mode || owner?.plugin !== expected.plugin || + owner?.scope !== expected.scope || owner?.projectRoot !== expected.projectRoot) { retainedState.push({ path: root.root, reason: 'marker-mismatch' }); continue; } + } + const entries = await readdir(root.root); + if (entries.length === 0 || (root.ownership.kind === 'marker' && entries.length === 1 && entries[0] === stateMarkerFile)) { + emptyOwnedStateRoots.push(root.root); + if (root.ownership.kind === 'marker') emptyOwnedStateFiles.push(root.ownership.marker); + continue; + } + ownedStatePaths.push(root.root); + } + } else { + const fallbackStateDirectory = receipt?.stateRoot?.root ?? resolvedStateDirectory; + const fallbackStateSource = receipt?.stateRoot?.source ?? resolvedStateSource; + let metadata; + try { metadata = await lstat(fallbackStateDirectory); } catch (error) { if (error?.code !== 'ENOENT') throw error; } + if (metadata?.isDirectory()) { + if (receipt?.stateRoot?.root === fallbackStateDirectory && fallbackStateSource === 'derived') ownedStatePaths.push(fallbackStateDirectory); + else retainedState.push({ path: fallbackStateDirectory, reason: 'unproven' }); + } + } + const webDataDirectory = receipt?.webDataRoot ?? resolvedWebDataDirectory; + const externalDataPaths = [...ownedStatePaths]; + for (const path of [webDataDirectory]) { + if (path === stateDirectory) continue; + let metadata; + try { metadata = await lstat(path); } catch (error) { if (error?.code === 'ENOENT') continue; throw error; } + if (metadata.isSymbolicLink() || !metadata.isDirectory()) throw unsupported(path); + externalDataPaths.push(path); + } + let stateMetadata; + try { stateMetadata = await lstat(stateDirectory); } catch (error) { if (error?.code !== 'ENOENT') throw error; } + if (stateMetadata !== undefined && (stateMetadata.isSymbolicLink() || !stateMetadata.isDirectory())) throw unsupported('state'); + // A state/ holding nothing is not durable state: pruned like an installer-created directory instead of kept as a remnant. + const emptyState = stateMetadata !== undefined && (await readdir(stateDirectory)).length === 0 ? stateDirectory : undefined; + const dataPaths = [...externalDataPaths, ...(stateMetadata === undefined || emptyState !== undefined ? [] : [stateDirectory])]; + const dataKinds = [...externalDataPaths.map((path) => ownedStatePaths.includes(path) ? `owned framework state root ${path}` : `web-data directory ${path}`), ...(stateMetadata === undefined || emptyState !== undefined ? [] : ['legacy state/ (state kernel, notices journal)'])]; + // The receipt's cursorExpansion records the PLUGIN_DATA directory this installer created for the copy (spec 9.1). Only + // the directory at this home's own plugin-data location is receipt-owned; a written one is durable state (kept or + // purged like state/), an empty one is an installer-created directory that is pruned, a recorded path elsewhere is left alone. + const recordedPluginData = receipt?.cursorExpansion?.pluginData; + const pluginDataRecorded = recordedPluginData === pluginData; + let emptyPluginData; + let foreignNote = ""; + if (recordedPluginData !== undefined && !pluginDataRecorded) { + foreignNote = ` The receipt records PLUGIN_DATA at ${recordedPluginData}, outside this home's agent-bundle/plugin-data; it is not touched.`; + } else if (pluginDataRecorded) { + // Reached only through real directories: a symlinked agent-bundle or plugin-data ancestor would let a recursive purge + // of the leaf follow it outside the Cursor home, so any link on the way is refused before anything is read or removed. + let pluginDataMetadata; + for (const directory of [join(cursorRoot, 'agent-bundle'), join(cursorRoot, 'agent-bundle', 'plugin-data'), pluginData]) { + pluginDataMetadata = undefined; + try { pluginDataMetadata = await lstat(directory); } catch (error) { if (error?.code !== 'ENOENT') throw error; } + if (pluginDataMetadata === undefined) break; + if (pluginDataMetadata.isSymbolicLink() || !pluginDataMetadata.isDirectory()) throw unsupported(relative(cursorRoot, directory)); + } + if (pluginDataMetadata !== undefined) { + if ((await readdir(pluginData)).length === 0) emptyPluginData = pluginData; + else { dataPaths.push(pluginData); dataKinds.push(`the PLUGIN_DATA directory ${pluginData}`); } + } + } + const retainedStateNote = retainedState.length === 0 ? '' : ` Retained ${retainedState.map((entry) => `${entry.path} (${entry.reason})`).join(', ')} because the receipt does not prove exclusive ownership.`; + const dataOutcome = dataPaths.length === 0 ? retainedState.length === 0 ? 'absent' : 'kept' : purgeData ? 'purged' : 'kept'; + const dataDetail = dataPaths.length === 0 && retainedState.length === 0 + ? `No durable runtime state exists (${emptyState === undefined ? 'no state/ under the installed plugin root' : 'state/ under the installed plugin root is empty and is pruned'}${emptyPluginData === undefined ? '' : `; the installer-created PLUGIN_DATA directory ${emptyPluginData} is empty and is pruned`}).${foreignNote}` + : purgeData + ? `${dataPaths.length === 0 ? 'No owned durable runtime state is removed.' : `Durable runtime state — ${dataKinds.join(' and ')} — is removed (--purge-data --confirm-purge).`}${retainedStateNote}${foreignNote}` + : `Durable runtime state${dataKinds.length === 0 ? '' : ` — ${dataKinds.join(' and ')}`} — is kept; pass --purge-data --confirm-purge to remove owned roots.${retainedStateNote}${foreignNote}`; + // External state kept by --keep-data needs the remnant receipt and recorded ownership so a later purge + // removes the same root even though no plugin content remains. + const keepRoot = !purgeData && [...dataPaths, ...retainedState.map((entry) => entry.path)].some((path) => path !== stateDirectory); + const directories = [ + ...ownedDirectories.map((directory) => join(destination, directory)), + ...(keepRoot ? [] : [destination]), + ...hostDirectories.map((directory) => join(cursorRoot, directory)), + ...(emptyPluginData === undefined ? [] : [emptyPluginData]), + ...(emptyState === undefined ? [] : [emptyState]), + ...(pluginDataRecorded ? [join(cursorRoot, 'agent-bundle', 'plugin-data'), join(cursorRoot, 'agent-bundle')] : []), + ...emptyOwnedStateRoots, + ].sort((left, right) => right.length - left.length || left.localeCompare(right)); + files.push(...emptyOwnedStateFiles); + const ownedSet = new Set(owned); + const ownedDirectorySet = new Set(ownedDirectories); + const remnantOnly = receipt !== undefined && receipt.files.length === 0 && receipt.registrations.length === 0; + const purging = purgeData && dataPaths.length > 0; + // A keep-data rerun over a remnant whose preserved data (or retained unowned entries) are still there is the documented + // no-op. Once state/ and the PLUGIN_DATA directory are gone or emptied by hand the remnant guards nothing, and the rerun + // consumes it (receipt, empty plugin root, the host and plugin-data directories it recorded) like an explicit purge would. + const remnantGuards = dataPaths.length > 0 || retainedState.length > 0 || (await listRetained(destination, ownedSet, ownedDirectorySet)).length > 0; + if (remnantOnly && !purgeData && remnantGuards && files.length === 1 && files[0] === join(destination, receiptFile)) { + // A rerun over what an earlier --keep-data uninstall left behind, still keeping the data: nothing to remove, so the + // remnant receipt stays and the run is the documented no-op. + console.log(`Not installed ${pluginName}@${pluginVersion} for cursor (local mode) at ${destination}`); + console.log(`Receipt: remnant (${join(destination, receiptFile)}) — only preserved runtime state remains from an earlier --uninstall --keep-data; pass --purge-data --confirm-purge to remove it.`); + process.exit(0); + } + const purgedDirectories = purging ? dataPaths : []; + const summary = (verb) => { + console.log(`${verb} ${pluginName}@${pluginVersion} for cursor (local mode) at ${destination}${force ? ' [--force]' : ''}`); + console.log(`Receipt: ${receiptStatus} (${join(destination, receiptFile)})`); + console.log(remnantOnly + ? 'Registration cursor-local-plugin: already-absent — Only preserved runtime state remained from an earlier --uninstall --keep-data; no plugin content was registered.' + : `Registration cursor-local-plugin: ${verb === 'Would uninstall' ? 'planned' : 'removed'} — Cursor loads plugins/local/ directly; removing the directory unregisters the plugin at the next window reload.`); + }; + if (plan) { + summary('Would uninstall'); + printPaths('Would remove file', files); + // Exactly the directories the run would prune: deepest first, once everything they hold is itself removed. + const gone = new Set([...files, ...purgedDirectories]); + const prunable = []; + for (const directory of directories) { + let entries; + try { entries = await readdir(directory); } catch (error) { if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') continue; throw error; } + if (entries.every((entry) => gone.has(join(directory, entry)))) { gone.add(directory); prunable.push(directory); } + } + printPaths('Would remove directory', [...purgedDirectories, ...prunable]); + console.log(`Data (${purgeData ? 'purge' : 'keep'}): ${dataOutcome} — ${dataDetail}`); + for (const path of dataPaths) console.log(` ${path}`); + for (const entry of retainedState) console.log(` retained ${entry.path}: ${entry.reason}`); + const retained = await listRetained(destination, ownedSet, ownedDirectorySet); + if (retained.length > 0) printPaths(`Retained unowned under ${destination}:`, retained); + if (!prunable.includes(destination)) console.log(`Remnant receipt (would be written): ${join(destination, receiptFile)} — owns no files; keeps the created host directories receipt-owned for a later purge.`); + process.exit(0); + } + for (const path of files) await rm(path, { force: true }); + for (const path of purgedDirectories) await rm(path, { force: true, recursive: true }); + const pruned = []; + for (const directory of directories) if (await rmdirIfEmpty(directory)) pruned.push(directory); + summary('Uninstalled'); + printPaths('Removed file', files); + printPaths('Removed directory', [...purgedDirectories, ...pruned]); + console.log(`Data (${purgeData ? 'purge' : 'keep'}): ${dataOutcome} — ${dataDetail}`); + for (const path of dataPaths) console.log(` ${path}`); + const retained = await exists(destination) ? await listRetained(destination, ownedSet, ownedDirectorySet) : []; + if (retained.length > 0) printPaths(`Retained unowned under ${destination}:`, retained); + if (await exists(destination)) { + // The plugin root survives (retained runtime state or unowned entries): a remnant receipt owning no files keeps the + // created host directories receipt-owned for a later purge and lets Doctor explain the directory; a reinstall fills it in. + await writeReceiptFile(join(destination, receiptFile), receiptFor({ files: [], hash: createHash('sha256').digest('hex') }, { + // A kept PLUGIN_DATA directory stays receipt-owned through the remnant's expansion record. + ...(keepRoot && receipt?.cursorExpansion !== undefined ? { cursorExpansion: receipt.cursorExpansion } : {}), + directories: [], hostDirectories, installedAt: receipt?.installedAt, registrations: [], + ...(receipt?.state === undefined ? {} : { state: receipt.state }), + ...(keepRoot ? { webDataRoot: webDataDirectory } : {}), + })); + console.log(`Remnant receipt: ${join(destination, receiptFile)} — owns no files; keeps the created host directories receipt-owned for a later purge.`); + } + process.exit(0); +} + +if (uninstall && mode === 'marketplace') { + const receipt = await exists(cursorRoot) ? await readReceiptFile(marketplaceReceipt) : undefined; + const repoExists = await exists(cursorRoot) && await exists(marketplaceRepo); + if (repoExists) { + const repoMetadata = await lstat(marketplaceRepo); + if (repoMetadata.isSymbolicLink() || !repoMetadata.isDirectory()) throw unsupported(relative(cursorRoot, marketplaceRepo)); + } + if (!repoExists && receipt === undefined) { + console.log(`Not installed ${pluginName}@${pluginVersion} for cursor (marketplace mode) at ${marketplaceRepo}`); + process.exit(0); + } + let receiptStatus = receipt === undefined ? 'forced-missing' : receipt.migratedFrom === undefined ? 'consumed' : 'migrated'; + const recorded = receipt?.registrations.find((registration) => registration.kind === 'cursor-marketplace-staging'); + if (repoExists) { + if (receipt === undefined) { + if (!force) { + throw new Error(`Refusing to remove staged Cursor marketplace ${marketplaceRepo} without an install receipt at ${marketplaceReceipt}. ` + + 'Re-run with --force to remove it after verifying it is this plugin\'s staging, or rerun `node install.mjs --mode marketplace` to record a receipt first.'); + } + let stagedName; + try { stagedName = JSON.parse(await readFile(join(marketplaceRepo, '.cursor-plugin', 'marketplace.json'), 'utf8'))?.name; } + catch (error) { if (error?.code !== 'ENOENT' && !(error instanceof SyntaxError)) throw error; } + const stagedPlugin = await readManifest(marketplacePlugin); + if (stagedName !== `${pluginName}-marketplace` || stagedPlugin?.name !== pluginName) { + throw new Error(`Refusing to remove ${marketplaceRepo}: it is not a staged Agent Bundle marketplace for ${pluginName} ` + + `(expected .cursor-plugin/marketplace.json naming ${pluginName}-marketplace and plugins/${pluginName}). --force does not apply.`); + } + } else if (receipt.plugin !== pluginName) { + throw new Error(`Refusing to remove ${marketplaceRepo}: the receipt at ${marketplaceReceipt} names plugin ${JSON.stringify(receipt.plugin)}.`); + } else if (recorded?.commit !== undefined) { + const head = await readHead(marketplaceRepo); + if (head !== recorded.commit) { + if (!force) { + throw new Error(`Refusing to remove ${marketplaceRepo}: its HEAD is ${head ?? 'unresolvable'} but the receipt recorded commit ` + + `${recorded.commit}, so the staged repository changed after staging. Re-run with --force to remove it anyway.`); + } + receiptStatus = 'forced-mismatch'; + } else { + // HEAD matching the receipt proves the commit, not the working tree: entries added since staging are not + // receipt-owned and the removal below is recursive. Unverifiable (no git, status failing) is dirt too. + const dirt = await new Promise((resolvePromise) => { + execFile('git', ['--no-optional-locks', 'status', '--porcelain', '--untracked-files=all', '--ignored=matching'], { cwd: marketplaceRepo }, (error, stdout, stderr) => { + if (error?.code === 'ENOENT') { resolvePromise('git is not available on PATH, so the staged working tree cannot be verified against the receipted commit.'); return; } + if (error) { resolvePromise(`\`git status\` failed in the staged repository (${(stderr || error.message).trim()}), so its working tree cannot be verified against the receipted commit.`); return; } + const entries = stdout.split('\n').map((line) => line.trimEnd()).filter((line) => line !== ''); + if (entries.length === 0) { resolvePromise(undefined); return; } + const shown = entries.slice(0, 5).map((line) => JSON.stringify(line.slice(3))).join(', '); + resolvePromise(`its working tree differs from the receipted commit (${entries.length} uncommitted, untracked, or ignored ${entries.length === 1 ? 'entry' : 'entries'}: ${shown}${entries.length > 5 ? ', …' : ''}) that the receipt does not own.`); + }); + }); + if (dirt !== undefined) { + if (!force) { + throw new Error(`Refusing to remove ${marketplaceRepo}: ${dirt} Move those entries out (or commit them and rerun ` + + '`node install.mjs --mode marketplace`), or re-run with --force to remove them anyway.'); + } + receiptStatus = 'forced-mismatch'; + } + } + } + } + // A completed copy from this staging under Cursor's cache means Cursor imported it; that copy is Cursor-owned. + const cacheSegment = (value) => value.replaceAll(/[^A-Za-z0-9._-]/gu, '-'); + // Whether the staged repository still exists or not: the receipt's commit is what identifies the imported copy. + let imported = false; + if (recorded?.commit !== undefined) { + imported = await exists(join(cursorRoot, 'plugins', 'cache', cacheSegment(`${pluginName}-marketplace`), cacheSegment(pluginName), cacheSegment(recorded.commit), '.cache-complete')); + } + const files = await exists(marketplaceReceipt) ? [marketplaceReceipt] : []; + const directories = repoExists ? [marketplaceRepo] : []; + const summary = (verb) => { + console.log(`${verb} ${pluginName}@${pluginVersion} for cursor (marketplace mode) at ${marketplaceRepo}${force ? ' [--force]' : ''}`); + console.log(`Receipt: ${receiptStatus} (${marketplaceReceipt})`); + console.log(`Registration cursor-marketplace-staging ${pluginName}-marketplace: ${repoExists ? (verb === 'Would uninstall' ? 'planned' : 'removed') : 'already-absent'}`); + if (imported) { + console.log(`Registration cursor-marketplace-staging ${pluginName}-marketplace: manual — Cursor imported this marketplace; its installed-plugin registry is server-assigned and exposes no non-interactive removal verb.`); + } + }; + const finish = (verb, removedDirectories) => { + printPaths(verb === 'Would uninstall' ? 'Would remove file' : 'Removed file', files); + printPaths(verb === 'Would uninstall' ? 'Would remove directory' : 'Removed directory', removedDirectories); + console.log(`Data (${purgeData ? 'purge' : 'keep'}): unavailable — A staged marketplace repository holds no runtime state; a copy Cursor imported from it is Cursor-owned and is not touched.`); + if (imported) { + console.log('Next steps:'); + console.log(` 1. Open Cursor, then Customize -> Plugins, and uninstall "${pluginName}" (marketplace ${pluginName}-marketplace) there.`); + } + }; + if (plan) { + // The plan names exactly what the run would prune: each parent below is removed only once every entry in it is gone. + const gone = new Set([...files, ...directories]); + const prunable = []; + for (const directory of [receiptsRoot, marketplaceRoot, join(cursorRoot, 'agent-bundle')]) { + let entries; + try { entries = await readdir(directory); } catch (error) { if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') continue; throw error; } + if (entries.every((entry) => gone.has(join(directory, entry)))) { gone.add(directory); prunable.push(directory); } + } + summary('Would uninstall'); + finish('Would uninstall', [...directories, ...prunable]); + process.exit(0); + } + if (repoExists) await rm(marketplaceRepo, { force: true, recursive: true }); + for (const path of files) await rm(path, { force: true }); + const pruned = [...directories]; + for (const directory of [receiptsRoot, marketplaceRoot, join(cursorRoot, 'agent-bundle')]) if (await rmdirIfEmpty(directory)) pruned.push(directory); + summary('Uninstalled'); + finish('Uninstalled', pruned); + process.exit(0); +} + +if (!(await exists(cursorRoot)) || !(await lstat(cursorRoot)).isDirectory()) { + throw new Error(`Cursor is not installed in ${cursorRoot}.`); +} + +const git = (cwd, args) => new Promise((resolvePromise, reject) => { + execFile('git', args, { cwd }, (error, stdout, stderr) => { + if (error?.code === 'ENOENT') { + reject(new Error('git is required for --mode marketplace; install git or use the default local mode.')); + return; + } + if (error) { reject(new Error(`git ${args[0]} failed: ${(stderr || stdout || error.message).trim()}`)); return; } + resolvePromise(stdout.trim()); + }); +}); + +const printMarketplaceSteps = (state, commit) => { + console.log(`${state} ${pluginName}@${pluginVersion} for cursor (marketplace mode) at ${marketplaceRepo}`); + console.log(`Marketplace: ${pluginName}-marketplace${commit ? ` @ ${commit}` : ''}`); + console.log('Next steps:'); + console.log(` 1. Open Cursor, then Customize -> Plugins -> "Add Plugins from Local Repository" and select ${marketplaceRepo}.`); + console.log(` 2. Choose "${pluginName}" in the imported marketplace and select Install (user scope).`); + console.log(' 3. Verify with `agent-bundle doctor --host cursor`: the plugin must appear under ~/.cursor/plugins/cache once Cursor has installed it.'); +}; + +const marketplaceManifestPath = join(marketplaceRepo, '.cursor-plugin', 'marketplace.json'); +// The staged repository is a committed Git tree, so its receipt lives in the store beside it; the recorded +// commit lets --uninstall prove the repository is still the one staging wrote. +const writeMarketplaceReceipt = async (commit) => { + const previous = await readReceiptFile(marketplaceReceipt); + const tree = await artifactInventory(source); + if (previous !== undefined && previous.contentHash === tree.hash && previous.registrations[0]?.commit === commit) return; + await mkdir(receiptsRoot, { recursive: true }); + // The committed repository is removed wholesale after a HEAD check; the receipt owns no individual files. + await writeReceiptFile(marketplaceReceipt, receiptFor({ files: [], hash: tree.hash }, { + directories: [], + hostDirectories: [], + installedAt: previous?.installedAt, + mode: 'marketplace', + registrations: [{ ...(commit ? { commit } : {}), kind: 'cursor-marketplace-staging', name: `${pluginName}-marketplace` }], + })); +}; + +if (mode === 'marketplace' && (cursorPluginDocument !== '.cursor-plugin/plugin.json' || !(await exists(join(source, cursorPluginDocument))))) { + throw new Error('--mode marketplace requires a Cursor Plugin (.cursor-plugin/plugin.json); Cursor marketplaces resolve plugins//.cursor-plugin/plugin.json. This bundle is an Agent Plugins (root plugin.json) pack: use the default local mode.'); +} +// Owner and description come from the emitted .cursor-plugin/plugin.json, exactly as `agent-bundle install +// cursor --mode marketplace` derives them, so both entry points stage byte-identical marketplace manifests. +const marketplaceManifestFor = async () => { + const manifest = JSON.parse(await readFile(join(source, cursorPluginDocument), 'utf8')); + const owner = typeof manifest?.author?.name === 'string' ? manifest.author.name : pluginName; + const description = typeof manifest?.description === 'string' ? { description: manifest.description } : {}; + // Entry fields are limited to the pinned Cursor marketplace schema (name/source/description). + return `${JSON.stringify({ + metadata: { description: `Agent Bundle local marketplace for ${pluginName}@${pluginVersion}.` }, + name: `${pluginName}-marketplace`, + owner: { name: owner }, + plugins: [{ ...description, name: pluginName, source: `plugins/${pluginName}` }], + }, null, 2)}\n`; +}; +const blobIndex = async (root, relative = '') => { + const index = new Map(); + for (const name of await readdir(join(root, relative))) { + if (relative === '' && name === '.git') continue; + const child = relative === '' ? name : `${relative}/${name}`; + const metadata = await lstat(join(root, child)); + if (metadata.isDirectory()) { + for (const [path, id] of await blobIndex(root, child)) index.set(path, id); + } else if (metadata.isFile()) { + const bytes = await readFile(join(root, child)); + index.set(child, createHash('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex')); + } + } + return index; +}; +const findNestedGit = async (root, relative = '') => { + for (const name of (await readdir(join(root, relative))).sort()) { + const child = relative === '' ? name : join(relative, name); + if (name === '.git') return child; + const metadata = await lstat(join(root, child)); + if (!metadata.isSymbolicLink() && metadata.isDirectory()) { + const nested = await findNestedGit(root, child); + if (nested !== undefined) return nested; + } + } + return undefined; +}; +if (mode === 'marketplace') { + const marketplaceManifest = await marketplaceManifestFor(); + const nestedGit = await findNestedGit(source); + if (nestedGit !== undefined) { + throw new Error(`--mode marketplace refuses bundle-internal Git metadata at ${JSON.stringify(nestedGit)}: git would record it as an empty gitlink and Cursor would import a plugin without files. Stage from a built bundle directory without .git, or use the default local mode.`); + } + await artifactInventory(source); + await mkdir(marketplaceRoot, { recursive: true }); + if (await exists(marketplaceRepo)) { + let stagedVersion; + try { stagedVersion = JSON.parse(await readFile(join(marketplacePlugin, cursorPluginDocument), "utf8")).version; } + catch (error) { if (error?.code !== 'ENOENT') throw error; } + if (stagedVersion !== undefined && stagedVersion !== pluginVersion) { + throw new Error(`Refusing version collision at ${marketplaceRepo}: found ${stagedVersion}, requested ${pluginVersion}.`); + } + // The staged plugin is an unfiltered copy of the bundle, so both sides hash the files present. + if (await exists(marketplacePlugin) && await exists(join(marketplaceRepo, '.git')) && await treeHash(source) === await treeHash(marketplacePlugin)) { + let stagedManifest; + try { stagedManifest = await readFile(marketplaceManifestPath, 'utf8'); } + catch (error) { if (error?.code !== 'ENOENT') throw error; } + if (stagedManifest !== marketplaceManifest) { + throw new Error(`Refusing content collision at ${marketplaceRepo}: .cursor-plugin/marketplace.json differs from the generated marketplace manifest; remove the staged repository and rerun.`); + } + if (await git(marketplaceRepo, ['status', '--porcelain', '--untracked-files=all', '--ignored=matching']) !== '') { + throw new Error(`Refusing content collision at ${marketplaceRepo}: the working tree differs from the committed HEAD Cursor would import; remove the staged repository and rerun.`); + } + const commit = await git(marketplaceRepo, ['rev-parse', 'HEAD']); + await writeMarketplaceReceipt(commit); + printMarketplaceSteps('Already staged', commit); + process.exit(0); + } + throw new Error(`Refusing content collision at ${marketplaceRepo}.`); + } + const stageParent = await mkdtemp(join(marketplaceRoot, `.${pluginName}.stage-`)); + const stage = join(stageParent, 'repo'); + try { + await mkdir(join(stage, '.cursor-plugin'), { recursive: true }); + await cp(source, join(stage, 'plugins', pluginName), { errorOnExist: true, force: false, recursive: true, verbatimSymlinks: true }); + await writeFile(join(stage, '.cursor-plugin', 'marketplace.json'), marketplaceManifest); + await treeHash(join(stage, 'plugins', pluginName)); + await git(stage, ['init', '-q', '--object-format=sha1']); + // Attributes (text/eol/filter/ident) would rewrite bytes in the index while leaving the tree clean; disable them. + await mkdir(join(stage, '.git', 'info'), { recursive: true }); + await writeFile(join(stage, '.git', 'info', 'attributes'), '* -text -eol -filter -ident -working-tree-encoding -export-ignore -export-subst\n'); + await git(stage, ['-c', 'core.autocrlf=false', '-c', 'core.safecrlf=false', 'add', '--all', '--force']); + await git(stage, ['-c', 'user.name=agent-bundle', '-c', 'user.email=agent-bundle@localhost', '-c', 'commit.gpgsign=false', 'commit', '-q', '-m', `${pluginName}@${pluginVersion}`]); + // Prove the commit holds the staged bytes: every blob id must equal sha1("blob \0" + bytes). + const expected = await blobIndex(stage); + const committed = new Map(); + for (const entry of (await git(stage, ['ls-tree', '-r', '-z', 'HEAD'])).split('\0')) { + if (entry === '') continue; + const tab = entry.indexOf('\t'); + committed.set(entry.slice(tab + 1), entry.slice(0, tab).split(' ')[2]); + } + const drifted = [...expected].filter(([path, id]) => committed.get(path) !== id).map(([path]) => path); + const extra = [...committed.keys()].filter((path) => !expected.has(path)); + if (drifted.length > 0 || extra.length > 0) { + throw new Error(`Cursor marketplace staging failed: the committed tree differs from the staged bundle bytes (${[...drifted, ...extra].map((path) => JSON.stringify(path)).join(', ')}); a Git attribute or filter transformed them.`); + } + const commit = await git(stage, ['rev-parse', 'HEAD']); + await rename(stage, marketplaceRepo); + await writeMarketplaceReceipt(commit); + printMarketplaceSteps('Staged', commit); + } finally { + await rm(stageParent, { force: true, recursive: true }); + } + process.exit(0); +} + +// The artifact is inventoried (and any unsupported entry refused) before anything is created in the home. +const artifact = await artifactInventory(source, cursorTransform); +// The receipt records which host directories this run creates on the way to the plugin root (a fresh Cursor +// home has no plugins/local), so --uninstall can prune exactly those and no more. +const createdHostDirectories = []; +for (const relativePath of ['plugins', 'plugins/local']) { + if (!(await exists(join(cursorRoot, relativePath)))) createdHostDirectories.push(relativePath); +} +await mkdir(installRoot, { recursive: true }); +// Spec 9.1: the data directory exists before any plugin subprocess is launched. +if (expansion !== undefined) await mkdir(pluginData, { recursive: true }); +const reportExpansion = () => { + if (expansion === undefined) return; + console.log(`Expanded Agent Plugins placeholders for Cursor in ${expansion.mcpDocument}: PLUGIN_ROOT=${destination} PLUGIN_DATA=${pluginData} (Cursor does not expand them; recorded in ${receiptFile})`); +}; + +if (!(await exists(destination))) { + const staged = await stage(artifact, { hostDirectories: createdHostDirectories }); + try { + await rename(staged.root, destination); + await attachStateOwnership(); + console.log(`Installed ${pluginName}@${pluginVersion} at ${destination} (content ${short(artifact.hash)})`); + reportExpansion(); + } finally { + await rm(staged.parent, { force: true, recursive: true }); + } + process.exit(0); +} +if (source === destination) { + console.log(`Already installed ${pluginName}@${pluginVersion} at ${destination} (content ${short(artifact.hash)})`); + process.exit(0); +} + +const destinationMetadata = await lstat(destination); +if (destinationMetadata.isSymbolicLink() || !destinationMetadata.isDirectory()) throw unsupported('.'); +const receipt = await readReceipt(destination); +const manifest = await readManifest(destination); +let ownership; +let installedHash; +// --uninstall --keep-data leaves a shell holding only state/: a reinstall fills it back in around the preserved +// durable state instead of refusing it as foreign (nothing in it is anyone's plugin content). +const remnantEntries = (await readdir(destination)).filter((name) => name !== receiptFile); +const stateOnlyRemnant = receipt === undefined + ? remnantEntries.length > 0 && remnantEntries.every(isPreservedRoot) + : receipt.plugin === pluginName && receipt.files.length === 0 && receipt.registrations.length === 0; // remnant receipt from --uninstall --keep-data +if (receipt !== undefined && receipt.plugin === pluginName) { + ownership = 'receipt'; + installedHash = await hashOwned(destination, receipt.files); +} else { + installedHash = (await inventory(destination)).hash; + ownership = stateOnlyRemnant || (receipt === undefined && manifest?.name === pluginName && await hasMarkers(destination)) ? 'legacy' : 'foreign'; +} +const installedVersion = manifest?.version ?? (ownership === 'receipt' ? receipt.version : undefined); +const installedName = manifest?.name ?? (ownership === 'receipt' ? receipt.plugin : pluginName); +const sameContent = installedHash === artifact.hash; +const verdict = installedVersion === undefined + ? (sameContent ? 'same content, installed version unknown' : 'different content, installed version unknown') + : installedVersion === pluginVersion + ? (sameContent ? 'same content' : 'same version, different content') + : (sameContent ? 'same content, different version' : 'different version'); +const detail = `installed ${installedName}@${installedVersion ?? 'unknown version'} content ${short(installedHash)} ` + + `vs artifact ${pluginName}@${pluginVersion} content ${short(artifact.hash)} (${verdict})`; +// Foreign ownership wins over byte equality: a directory that is not ours is never current. +if (ownership === 'foreign') { + throw new Error(`Refusing foreign install at ${destination}: ${detail}; the directory is not an agent-bundle install of ` + + `${pluginName}, so --replace does not apply. Remove it manually if it is stale.`); +} +// A receipt-managed copy is current only when its recorded inventory matches the artifact too. +const inventoryMatches = ownership !== 'receipt' || + (receipt.files.length === artifact.files.length && receipt.files.every((file, index) => file === artifact.files[index])); +if (installedHash === artifact.hash && inventoryMatches) { + if (ownership === 'legacy' && replace) { + // Byte-identical pre-receipt copy: adoption only writes the receipt (adoption created no directories), + // through an exclusively created random sibling so no existing file or link is followed or overwritten. + await writeReceiptFile(join(destination, receiptFile), receiptFor(artifact, { directories: [], hostDirectories: [] })); + await attachStateOwnership(); + console.log(`Adopted ${pluginName}@${pluginVersion} at ${destination} (content ${short(artifact.hash)})`); + reportExpansion(); + process.exit(0); + } + if (ownership === 'receipt' && receipt.migratedFrom !== undefined) { + // An identical receipt-managed copy whose receipt predates the current format is upgraded in place: + // lifecycle fields exactly as the reader synthesized them, and nothing else changes. + await writeReceiptFile(join(destination, receiptFile), receiptFor(artifact, { + directories: receipt.directories, hostDirectories: receipt.hostDirectories, installedAt: receipt.installedAt, + })); + } + if (ownership === 'receipt' && receipt.state === undefined) await attachStateOwnership(); + console.log(`Already installed ${pluginName}@${pluginVersion} at ${destination} (content ${short(artifact.hash)})`); + process.exit(0); +} +if (installedVersion !== undefined && installedVersion !== pluginVersion && !replace) { + throw new Error(`Refusing version collision at ${destination}: ${detail}. Re-run with --replace to replace this agent-bundle install.`); +} +if (ownership === 'legacy' && !replace && !stateOnlyRemnant) { + throw new Error(`Refusing content collision at ${destination}: ${detail}; this copy predates install receipts. ` + + 'Re-run with --replace once to adopt it; later same-version rebuilds replace automatically.'); +} + +// Owned-files-only replacement: stale owned files leave first, staged files rename over their +// predecessors, and the receipt lands last as the commit marker. Unowned entries (runtime state) stay. +const staged = await stage(artifact); +try { + // A legacy copy has no inventory: only files the new artifact also ships count as owned; everything + // else (operator files, stale artifact files, runtime state) stays in place and remains unowned. + const incoming = new Set(staged.inventory.files); + let owned; + if (ownership === 'receipt') { + owned = receipt.files; + } else { + owned = []; + for (const file of (await inventory(destination)).files) { + if (await isOwnedEntry(destination, incoming, file)) owned.push(file); + } + } + const ownedSet = new Set(owned); + const ownedDirectories = new Set(ownership === 'receipt' ? receipt.directories : []); + await assertRealAncestors(destination, owned); + await assertRealAncestors(destination, staged.inventory.files, ownedSet); + // An existing directory at an incoming file path is fine only when it is wholly owned: it and every + // directory beneath were created by the installer, at least one file, every file owned, and no empty + // directory anywhere beneath (no evidence of ownership). + const isWhollyOwnedDirectory = async (relativePath) => { + let files = 0; + const visit = async (directory) => { + if (!(await isOwnedEntry(destination, ownedDirectories, directory))) return false; + const entries = (await readdir(join(destination, directory))).sort((left, right) => left.localeCompare(right)); + if (entries.length === 0) return false; + for (const name of entries) { + const relative = `${directory}/${name}`; + const metadata = await lstat(join(destination, relative)); + if (metadata.isSymbolicLink()) throw unsupported(relative); + if (metadata.isDirectory()) { if (!(await visit(relative))) return false; continue; } + if (!metadata.isFile() || !(await isOwnedEntry(destination, ownedSet, relative))) return false; + files += 1; + } + return true; + }; + return (await visit(relativePath)) && files > 0; + }; + const collisions = []; + for (const file of staged.inventory.files) { + if (ownedSet.has(file)) continue; + const target = join(destination, file); + let metadata; + try { metadata = await lstat(target); } catch (error) { if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') continue; throw error; } + const tolerated = metadata.isDirectory() && !metadata.isSymbolicLink() + ? await isWhollyOwnedDirectory(file) + : metadata.isFile() && await isOwnedEntry(destination, ownedSet, file); + if (!tolerated) collisions.push(file); + } + if (collisions.length > 0) { + throw new Error(`Refusing to overwrite unowned files at ${destination}: ${collisions.join(', ')}. Move them aside manually before replacing.`); + } + const stale = owned.filter((file) => !incoming.has(file)); + for (const file of stale) await rm(join(destination, file), { force: true }); + const pruned = await pruneEmptyDirectories(stale, ownedDirectories); + const created = new Set(); + for (const file of staged.inventory.files) { + await ensureAncestors(file, created); + await rename(join(staged.root, file), join(destination, file)); + } + // The receipt owns what the installer owns now: surviving directories it created before plus the + // ones this replacement created; the first install time and the host directories it created carry + // over. Finalised in the private staging copy, then committed by rename. + const directories = sortNames(new Set([...[...ownedDirectories].filter((directory) => !pruned.has(directory)), ...created])); + const previous = ownership === 'receipt' ? receipt : undefined; + await writeFile(join(staged.root, receiptFile), receiptFor(staged.inventory, { + directories, hostDirectories: previous?.hostDirectories ?? [], installedAt: previous?.installedAt, + }), 'utf8'); + await rename(join(staged.root, receiptFile), join(destination, receiptFile)); + await attachStateOwnership(previous?.state); + if (stateOnlyRemnant) console.log(`Installed ${pluginName}@${pluginVersion} at ${destination} (content ${short(artifact.hash)})`); + else console.log(`Replaced ${pluginName}@${pluginVersion} at ${destination} (content ${short(installedHash)} -> ${short(artifact.hash)})`); + reportExpansion(); +} finally { + await rm(staged.parent, { force: true, recursive: true }); +} diff --git a/artifact/plugin.json b/artifact/plugin.json new file mode 100644 index 0000000..f676ac2 --- /dev/null +++ b/artifact/plugin.json @@ -0,0 +1 @@ +{"$schema":"https://agent-plugins.org/schemas/1.0.0/plugin.schema.json","description":"Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.","homepage":"https://github.com/ScriptedAlchemy/plugin-library#readme","license":"MIT","name":"plugin-library","repository":"https://github.com/ScriptedAlchemy/plugin-library","version":"0.4.0"} diff --git a/artifact/scripts/plugin-library-server.mjs b/artifact/scripts/plugin-library-server.mjs new file mode 100755 index 0000000..a758969 --- /dev/null +++ b/artifact/scripts/plugin-library-server.mjs @@ -0,0 +1,921 @@ +#!/usr/bin/env node +import * as __rspack_external_node_fs_1b05aee1 from "node:fs"; +import * as __rspack_external_node_http_e245b239 from "node:http"; +import * as __rspack_external_node_path_806ed179 from "node:path"; +import * as __rspack_external_node_url_3991086a from "node:url"; +import { createRequire as __rspack_createRequire } from "node:module"; +const __rspack_createRequire_require = __rspack_createRequire(import.meta.url); +var __webpack_modules__ = ({ +"node:fs"(module) { + +module.exports = __rspack_external_node_fs_1b05aee1; + + +}, +"node:http"(module) { + +module.exports = __rspack_external_node_http_e245b239; + + +}, +"node:path"(module) { + +module.exports = __rspack_external_node_path_806ed179; + + +}, +"node:url"(module) { + +module.exports = __rspack_external_node_url_3991086a; + + +}, +"child_process"(module) { +module.exports = __rspack_createRequire_require("child_process"); + +}, +"fs"(module) { +module.exports = __rspack_createRequire_require("fs"); + +}, +"os"(module) { +module.exports = __rspack_createRequire_require("os"); + +}, +"path"(module) { +module.exports = __rspack_createRequire_require("path"); + +}, +"./lib/bots.js"(module, __unused_rspack_exports, __webpack_require__) { + +const { execFile } = __webpack_require__("child_process"); +const GBOT_BIN = process.env.GBOT_BIN || "gbot"; +const TTL_MS = 20000; +let cache = { + at: 0, + value: null +}; +let inflight = null; +function runGbot(args) { + return new Promise((resolve, reject)=>{ + execFile(GBOT_BIN, [ + ...args, + "--json" + ], { + timeout: 15000, + maxBuffer: 8 * 1024 * 1024 + }, (err, stdout, stderr)=>{ + if (err) { + const e = new Error(err.code === "ENOENT" ? `\`${GBOT_BIN}\` not found on PATH` : String(stderr || stdout || err.message).trim()); + e.code = err.code; + return reject(e); + } + try { + resolve(JSON.parse(stdout)); + } catch (parseErr) { + reject(new Error(`gbot ${args.join(" ")}: bad JSON (${parseErr.message})`)); + } + }); + }); +} +/** Send one confirmed instruction directly through gbot. */ async function sendToBot(target, message) { + const result = await runGbot([ + "send", + target, + message + ]); + if (!result || typeof result !== "object") throw new Error("gbot send: expected an object"); + return result; +} +/** Boundary check on gbot output: a shape change surfaces as a 502, not an empty roster. */ function shapeList(list, what) { + if (!Array.isArray(list)) throw new Error(`gbot ${what} list: expected an array`); + return list.map((b)=>{ + if (!b || typeof b.id !== "string" || typeof b.name !== "string") { + throw new Error(`gbot ${what} list: entry without string id/name`); + } + return shapeBot(b); + }); +} +function shapeBot(b) { + return { + id: b.id, + name: b.name, + kind: b.kind || "bot", + description: b.description || "", + avatarColor: b.avatarColor || null, + avatarShape: b.avatarShape || null, + members: Array.isArray(b.members) ? b.members : undefined + }; +} +/** Bots and groups from `gbot`, cached briefly and de-duplicated across callers. */ async function listBots({ force = false } = {}) { + if (!force && cache.value && Date.now() - cache.at < TTL_MS) return cache.value; + if (inflight) return inflight; + inflight = (async ()=>{ + const [bots, groups] = await Promise.all([ + runGbot([ + "bots", + "list" + ]), + runGbot([ + "groups", + "list" + ]) + ]); + const value = { + ok: true, + fetchedAt: new Date().toISOString(), + bots: shapeList(bots, "bots"), + groups: shapeList(groups, "groups") + }; + cache = { + at: Date.now(), + value + }; + return value; + })(); + try { + return await inflight; + } finally{ + inflight = null; + } +} +module.exports = { + listBots, + sendToBot +}; + + +}, +"./lib/local-plugins.js"(module, __unused_rspack_exports, __webpack_require__) { + +const fs = __webpack_require__("fs"); +const os = __webpack_require__("os"); +const path = __webpack_require__("path"); +// Marketplace installs land in cache////; +// hand-installed plugins sit directly in local//. Env overrides exist +// for tests and for hosts that keep the plugin directory elsewhere. +const CACHE_ROOT = process.env.CURSOR_PLUGIN_CACHE || path.join(os.homedir(), ".cursor", "plugins", "cache"); +const LOCAL_ROOT = process.env.CURSOR_PLUGIN_LOCAL || path.join(os.homedir(), ".cursor", "plugins", "local"); +const LOCAL_MARKETPLACE = "local"; +const INDEX_TTL_MS = 15000; +let cached = { + at: 0, + plugins: [] +}; +function listDirs(dir) { + try { + return fs.readdirSync(dir, { + withFileTypes: true + }).filter((d)=>d.isDirectory()).map((d)=>d.name); + } catch { + return []; + } +} +function readText(file) { + try { + return fs.readFileSync(file, "utf8"); + } catch { + return null; + } +} +function readJsonFile(file) { + const raw = readText(file); + if (raw == null) return null; + try { + return JSON.parse(raw); + } catch { + return null; + } +} +function mtimeOf(file) { + try { + return fs.statSync(file).mtimeMs; + } catch { + return 0; + } +} +function unquote(v) { + if (v.startsWith('"') && v.endsWith('"') && v.length >= 2) { + return v.slice(1, -1).replace(/\\(["\\])/g, "$1"); + } + if (v.startsWith("'") && v.endsWith("'") && v.length >= 2) { + return v.slice(1, -1).replace(/''/g, "'"); + } + if (v === "true") return true; + if (v === "false") return false; + return v; +} +/** + * YAML front matter as skills actually write it: `key: scalar`, quoted + * scalars, and `>`/`|` block scalars (folded / literal, with `-`/`+` chomping). + * Nested maps and sequences are not needed by any cached plugin and are skipped. + */ function parseFrontMatter(md) { + const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(md); + if (!m) return { + meta: {}, + body: md + }; + const lines = m[1].split(/\r?\n/); + const meta = {}; + for(let i = 0; i < lines.length; i++){ + const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(lines[i]); + if (!kv) continue; + const value = kv[2].trim(); + const nextIndented = i + 1 < lines.length && /^\s+\S/.test(lines[i + 1]); + const block = /^([>|])[+-]?$/.exec(value); + if (!block) { + if (value === "" && nextIndented) { + // Nested map or sequence: not needed by the UI, skip its body. + while(i + 1 < lines.length && (/^\s+/.test(lines[i + 1]) || lines[i + 1].trim() === ""))i++; + continue; + } + if (value !== "") meta[kv[1]] = unquote(value); + continue; + } + const buf = []; + while(i + 1 < lines.length && (/^\s+\S/.test(lines[i + 1]) || lines[i + 1].trim() === "")){ + buf.push(lines[++i].replace(/^\s+/, "")); + } + while(buf.length && buf[buf.length - 1] === "")buf.pop(); + meta[kv[1]] = block[1] === ">" ? buf.join(" ").replace(/\s+/g, " ").trim() : buf.join("\n"); + } + return { + meta, + body: md.slice(m[0].length) + }; +} +function firstHeading(body) { + const h = /^#\s+(.+)$/m.exec(body); + return h ? h[1].trim() : null; +} +function firstParagraph(body) { + const buf = []; + for (const line of body.split(/\r?\n/)){ + const t = line.trim(); + if (!t || /^#/.test(t) || /^---/.test(t)) { + if (buf.length) break; + continue; + } + buf.push(t); + } + return buf.join(" "); +} +function walkFiles(dir, prefix = "") { + const out = []; + let entries = []; + try { + entries = fs.readdirSync(dir, { + withFileTypes: true + }); + } catch { + return out; + } + for (const e of entries){ + if (e.name.startsWith(".") || e.name === "node_modules") continue; + const rel = prefix ? `${prefix}/${e.name}` : e.name; + if (e.isDirectory()) out.push(...walkFiles(path.join(dir, e.name), rel)); + else out.push(rel); + } + return out; +} +/** Shared shape for skills, agents and rules: a markdown doc with a summary. */ function readDoc(root, file, id) { + const raw = readText(path.join(root, file)); + if (raw == null) return null; + const { meta, body } = parseFrontMatter(raw); + return { + id, + name: typeof meta.name === "string" && meta.name ? meta.name : firstHeading(body) || id, + description: typeof meta.description === "string" ? meta.description : firstParagraph(body), + file, + bytes: Buffer.byteLength(raw, "utf8"), + alwaysApply: meta.alwaysApply === true + }; +} +function readSkills(root) { + const skillsDir = path.join(root, "skills"); + return listDirs(skillsDir).map((id)=>{ + const doc = readDoc(root, `skills/${id}/SKILL.md`, id); + if (!doc) return null; + const files = walkFiles(path.join(skillsDir, id)).filter((f)=>f !== "SKILL.md").sort(); + return { + ...doc, + files + }; + }).filter(Boolean).sort((a, b)=>a.id.localeCompare(b.id)); +} +function readDocsIn(root, sub, ext) { + const dir = path.join(root, sub); + return walkFiles(dir).filter((f)=>ext.test(f)).map((f)=>readDoc(root, `${sub}/${f}`, f.replace(ext, ""))).filter(Boolean); +} +function httpUrl(v) { + return typeof v === "string" && /^https?:\/\//i.test(v) ? v : null; +} +function normalizeName(s) { + return String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, ""); +} +function readManifest(root) { + return readJsonFile(path.join(root, ".cursor-plugin", "plugin.json")) || readJsonFile(path.join(root, "plugin.json")) || {}; +} +/** One plugin directory, before any file inside it has been read. */ function candidate({ marketplace, slug, version, root, installedMarker }) { + const manifest = readManifest(root); + return { + key: `${marketplace}/${slug}`, + marketplace, + slug, + version, + root, + manifest, + installedMarker, + mtime: mtimeOf(root), + packageName: manifest.name || slug, + semver: typeof manifest.version === "string" ? manifest.version : null + }; +} +function scanCache(cacheRoot) { + const out = []; + for (const marketplace of listDirs(cacheRoot)){ + for (const slug of listDirs(path.join(cacheRoot, marketplace))){ + const pluginDir = path.join(cacheRoot, marketplace, slug); + for (const version of listDirs(pluginDir).filter((v)=>!v.startsWith("."))){ + const root = path.join(pluginDir, version); + // Cursor marks the version it actually installed with a sibling `.installed`. + out.push(candidate({ + marketplace, + slug, + version, + root, + installedMarker: fs.existsSync(`${root}.installed`) + })); + } + } + } + return out; +} +function scanLocal(localRoot) { + return listDirs(localRoot).filter((name)=>!name.startsWith(".")).map((name)=>candidate({ + marketplace: LOCAL_MARKETPLACE, + slug: name, + version: "local", + root: path.join(localRoot, name), + installedMarker: true + })); +} +function readContents(c) { + const { manifest, root } = c; + return { + key: c.key, + aliases: c.aliases, + marketplace: c.marketplace, + slug: c.slug, + version: c.version, + root, + name: manifest.displayName || manifest.name || c.slug, + description: manifest.description || "", + author: manifest.author && manifest.author.name ? manifest.author.name : null, + homepage: httpUrl(manifest.homepage) || httpUrl(manifest.repository && manifest.repository.url) || httpUrl(manifest.repository), + semver: c.semver, + logo: manifest.logo && fs.existsSync(path.join(root, manifest.logo)) ? manifest.logo : null, + hasReadme: fs.existsSync(path.join(root, "README.md")), + hasMcp: fs.existsSync(path.join(root, "mcp.json")), + skills: readSkills(root), + agents: readDocsIn(root, "agents", /\.md$/), + rules: readDocsIn(root, "rules", /\.mdc?$/), + matchKeys: c.matchKeys + }; +} +function compareSemver(a, b) { + const pa = String(a || "").split(".").map((n)=>parseInt(n, 10) || 0); + const pb = String(b || "").split(".").map((n)=>parseInt(n, 10) || 0); + for(let i = 0; i < Math.max(pa.length, pb.length); i++){ + if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0); + } + return 0; +} +/** + * The cache often holds one plugin several times: under its numeric id and + * under its slug, sometimes at different versions. Serve the copy Cursor marked + * as installed; otherwise the newest. The winner answers to every directory + * name the plugin was found under. + */ function dedupe(candidates) { + const groups = new Map(); + for (const c of candidates){ + const identity = `${c.marketplace}/${c.packageName}`; + if (!groups.has(identity)) groups.set(identity, []); + groups.get(identity).push(c); + } + const out = []; + for (const group of groups.values()){ + group.sort((a, b)=>Number(b.installedMarker) - Number(a.installedMarker) || compareSemver(b.semver, a.semver) || b.mtime - a.mtime); + const keep = group[0]; + const humanSlug = group.find((c)=>!/^\d+$/.test(c.slug)) || keep; + const aliases = new Set(group.map((c)=>c.key)); + aliases.delete(humanSlug.key); + const names = group.flatMap((c)=>[ + normalizeName(c.manifest.displayName), + normalizeName(c.manifest.name), + normalizeName(c.slug) + ]); + out.push({ + ...keep, + key: humanSlug.key, + slug: humanSlug.slug, + aliases: [ + ...aliases + ], + matchKeys: [ + ...new Set(names.filter(Boolean)) + ] + }); + } + return out; +} +function indexLocalPlugins() { + if (Date.now() - cached.at < INDEX_TTL_MS) return cached.plugins; + const candidates = [ + ...scanCache(CACHE_ROOT), + ...scanLocal(LOCAL_ROOT) + ]; + cached = { + at: Date.now(), + plugins: dedupe(candidates).map(readContents) + }; + return cached.plugins; +} +function answersTo(p, key) { + return p.key === key || p.aliases.includes(key); +} +/** + * Match an installed catalog row to a cached plugin. Order of trust: an + * explicit cache key from the catalog, a directory named after the plugin id, + * an exact normalized-name hit, then a name-containment hit only when it is + * unambiguous. A wrong `null` is honest; a wrong hit would be acted on. + */ function findLocalPlugin(pluginId, name, cacheKey) { + const all = indexLocalPlugins(); + if (cacheKey) { + const hit = all.find((p)=>answersTo(p, cacheKey)); + if (hit) return hit; + } + const id = String(pluginId || ""); + const byId = all.find((p)=>p.slug === id || p.aliases.some((a)=>a.endsWith(`/${id}`))); + if (byId) return byId; + const norm = normalizeName(name); + if (!norm) return null; + const exact = all.filter((p)=>p.matchKeys.includes(norm)); + if (exact.length === 1) return exact[0]; + if (exact.length > 1) return null; + const loose = all.filter((p)=>p.matchKeys.some((k)=>k.length >= 6 && (norm.includes(k) || k.includes(norm)))); + return loose.length === 1 ? loose[0] : null; +} +function getLocalPlugin(key) { + return indexLocalPlugins().find((p)=>answersTo(p, key)) || null; +} +/** The wire shape for a cached plugin. Internal fields (root, aliases, …) stay here. */ function toPublic(p) { + return { + key: p.key, + name: p.name, + description: p.description, + author: p.author, + homepage: p.homepage, + semver: p.semver, + logo: p.logo, + hasReadme: p.hasReadme, + hasMcp: p.hasMcp, + skills: p.skills, + agents: p.agents, + rules: p.rules + }; +} +/** + * Resolve `rel` inside `root`, following symlinks, or return null if the real + * path escapes the root or is not a regular file. + */ function resolveInside(root, rel) { + let realRoot; + let full; + try { + realRoot = fs.realpathSync(root); + full = fs.realpathSync(path.resolve(root, rel)); + } catch { + return null; + } + if (full !== realRoot && !full.startsWith(realRoot + path.sep)) return null; + try { + return fs.statSync(full).isFile() ? full : null; + } catch { + return null; + } +} +module.exports = { + indexLocalPlugins, + dedupe, + compareSemver, + findLocalPlugin, + getLocalPlugin, + toPublic, + resolveInside, + parseFrontMatter +}; + + +}, + +}); +// The module cache +var __webpack_module_cache__ = {}; + +// The require function +function __webpack_require__(moduleId) { + +// Check if module is in cache +var cachedModule = __webpack_module_cache__[moduleId]; +if (cachedModule !== undefined) { +return cachedModule.exports; +} +// Create a new module (and put it into the cache) +var module = (__webpack_module_cache__[moduleId] = { +exports: {} +}); +// Execute the module function +__webpack_modules__[moduleId](module, module.exports, __webpack_require__); + +// Return the exports of the module +return module.exports; + +} + +var __webpack_exports__ = {}; +/* import */ var node_fs__rspack_import_0 = __webpack_require__("node:fs"); +/* import */ var node_http__rspack_import_1 = __webpack_require__("node:http"); +/* import */ var node_path__rspack_import_2 = __webpack_require__("node:path"); +/* import */ var node_url__rspack_import_3 = __webpack_require__("node:url"); +/* import */ var _lib_local_plugins_js__rspack_import_4 = __webpack_require__("./lib/local-plugins.js"); +/* import */ var _lib_bots_js__rspack_import_5 = __webpack_require__("./lib/bots.js"); +// + + + + + + +const { findLocalPlugin, getLocalPlugin, indexLocalPlugins, toPublic, resolveInside, parseFrontMatter } = _lib_local_plugins_js__rspack_import_4; +const { listBots, sendToBot } = _lib_bots_js__rspack_import_5; +const ENTRY_DIR = node_path__rspack_import_2["default"].dirname((0,node_url__rspack_import_3.fileURLToPath)(import.meta.url)); +const ROOT = node_path__rspack_import_2["default"].basename(ENTRY_DIR) === "scripts" ? node_path__rspack_import_2["default"].resolve(ENTRY_DIR, "..") : ENTRY_DIR; +const PUBLIC_DIR = node_fs__rspack_import_0["default"].existsSync(node_path__rspack_import_2["default"].join(ROOT, "public")) ? node_path__rspack_import_2["default"].join(ROOT, "public") : node_path__rspack_import_2["default"].join(ROOT, "assets", "public"); +const PORT = Number(process.env.PORT || 8787); +// Loopback by default: this server shells out to gbot and serves cache files +// without auth. Set HOST=0.0.0.0 explicitly only on a network you trust. +const HOST = process.env.HOST || "127.0.0.1"; +const MIME = { + ".html": "text/html; charset=utf-8", + ".js": "application/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".webp": "image/webp", + ".gif": "image/gif", + ".md": "text/markdown; charset=utf-8", + ".mdc": "text/markdown; charset=utf-8", + ".txt": "text/plain; charset=utf-8" +}; +function readJson(rel) { + const sourcePath = node_path__rspack_import_2["default"].join(ROOT, rel); + const filePath = node_fs__rspack_import_0["default"].existsSync(sourcePath) ? sourcePath : node_path__rspack_import_2["default"].join(ROOT, "assets", rel); + return JSON.parse(node_fs__rspack_import_0["default"].readFileSync(filePath, "utf8")); +} +function send(res, status, body, type = "application/json; charset=utf-8") { + const data = typeof body === "string" ? body : JSON.stringify(body); + res.writeHead(status, { + "Content-Type": type, + "Cache-Control": "no-store" + }); + res.end(data); +} +function sendFile(res, file, cacheControl = "no-store") { + const ext = node_path__rspack_import_2["default"].extname(file).toLowerCase(); + node_fs__rspack_import_0["default"].createReadStream(file).on("open", ()=>{ + res.writeHead(200, { + "Content-Type": MIME[ext] || "application/octet-stream", + "Cache-Control": cacheControl + }); + }).on("error", (err)=>{ + if (!res.headersSent) send(res, 404, { + ok: false, + error: "read_failed", + message: err.code + }); + else res.destroy(); + }).pipe(res); +} +function serveStatic(_req, res, urlPath) { + const rel = urlPath === "/" ? "index.html" : urlPath.replace(/^\/+/, ""); + const file = resolveInside(PUBLIC_DIR, rel); + if (!file) return send(res, 404, { + ok: false, + error: "not_found", + path: urlPath + }); + return sendFile(res, file); +} +function readBody(req) { + return new Promise((resolve, reject)=>{ + const chunks = []; + req.on("data", (c)=>chunks.push(c)); + req.on("end", ()=>{ + const raw = Buffer.concat(chunks).toString("utf8"); + if (!raw) return resolve({}); + try { + resolve(JSON.parse(raw)); + } catch (e) { + reject(e); + } + }); + req.on("error", reject); + }); +} +function isSameOriginJson(req) { + const contentType = req.headers["content-type"]; + if (typeof contentType !== "string" || !/^application\/json(?:;|$)/i.test(contentType)) return false; + const fetchSite = req.headers["sec-fetch-site"]; + if (typeof fetchSite === "string" && fetchSite !== "same-origin") return false; + const origin = req.headers.origin; + if (typeof origin !== "string") return true; + try { + return new node_url__rspack_import_3.URL(origin).origin === `http://${req.headers.host}`; + } catch { + return false; + } +} +async function handleSend(req, res) { + if (!isSameOriginJson(req)) { + return send(res, 403, { + ok: false, + error: "cross_origin_send_refused" + }); + } + let body; + try { + body = await readBody(req); + } catch { + return send(res, 400, { + ok: false, + error: "invalid_json" + }); + } + const plugin_id = body.plugin_id != null ? String(body.plugin_id) : ""; + const skill_id = body.skill_id != null && body.skill_id !== "" ? String(body.skill_id) : undefined; + const bot_ref = body.bot_ref != null ? String(body.bot_ref) : ""; + if (!plugin_id || !bot_ref) { + return send(res, 400, { + ok: false, + error: "bad_request", + message: "plugin_id and bot_ref are required" + }); + } + const library = buildLibrary(); + const plugin = [ + ...library.installed, + ...library.marketplace + ].find((candidate)=>candidate.plugin_id === plugin_id); + if (!plugin) return send(res, 404, { + ok: false, + error: "plugin_not_found" + }); + const skill = skill_id && plugin.local ? plugin.local.skills.find((candidate)=>candidate.id === skill_id) : undefined; + if (skill_id && !skill) return send(res, 404, { + ok: false, + error: "skill_not_found" + }); + let roster; + try { + roster = await listBots(); + } catch (error) { + return send(res, 502, { + ok: false, + error: "gbot_unavailable", + message: error.message + }); + } + const target = [ + ...roster.bots, + ...roster.groups + ].find((candidate)=>candidate.id === bot_ref); + if (!target || bot_ref.startsWith("-")) { + return send(res, 404, { + ok: false, + error: "bot_not_found" + }); + } + const subject = skill ? `the "${skill.name}" skill from the "${plugin.name}" plugin` : `the "${plugin.name}" plugin`; + try { + await sendToBot(bot_ref, `Use ${subject} for the current task.`); + return send(res, 200, { + ok: true, + status: "sent", + plugin_id, + skill_id: skill_id || null, + bot_ref, + message: `Sent ${subject} to the selected Grok Bot target.` + }); + } catch (error) { + return send(res, 502, { + ok: false, + error: "gbot_unavailable", + message: error.message + }); + } +} +function oneLine(s) { + return String(s || "").split(/\n/)[0].trim(); +} +/** + * Plugins present on this machine but absent from the catalog get their cache + * key as id (`local:gbot`, `cursor-public:foo`). Catalog ids are numeric, so + * the colon marks a synthetic id unambiguously and keeps the hash route intact. + */ const syntheticId = (p)=>`${p.marketplace}:${p.slug}`; +/** + * The browse model, joined once here. "Installed" means present in this + * machine's plugin cache or local plugin directory; the catalog supplies + * marketplace copy, category and ids, and the Catalog's pstack dump supplies + * skill grouping. Catalog rows that name their cache directory are matched + * first so a looser name match can't claim a plugin another row owns. + */ function buildLibrary() { + const catalog = (readJson("data/unified-catalog.json").plugins || []).filter((p)=>p.stableId != null); + const cacheHint = (p)=>p.cache && p.cache.slug ? `${p.cache.marketplace}/${p.cache.slug}` : undefined; + const ordered = [ + ...catalog.filter(cacheHint), + ...catalog.filter((p)=>!cacheHint(p)) + ]; + const claimed = new Set(); + const installed = []; + const marketplace = []; + for (const cat of ordered){ + const id = String(cat.stableId); + const hit = findLocalPlugin(id, cat.name, cacheHint(cat)); + const local = hit && !claimed.has(hit.key) ? hit : null; + if (local) claimed.add(local.key); + (local ? installed : marketplace).push({ + plugin_id: id, + name: cat.name || local && local.name || "(unnamed)", + description: oneLine(cat.description) || oneLine(local && local.description), + category: cat.category || null, + installed: Boolean(local), + skill_count: local ? local.skills.length : Number(cat.skillCountReported ?? cat.skillCount) || 0, + connector_count: Number(cat.connectorCount) || 0, + local: local ? toPublic(local) : null + }); + } + // A directory named after a catalog id whose row already claimed its slug + // twin is the same plugin cached twice, not a second install. + const catalogIds = new Set(catalog.map((p)=>String(p.stableId))); + for (const p of indexLocalPlugins()){ + if (claimed.has(p.key) || catalogIds.has(p.slug)) continue; + installed.push({ + plugin_id: syntheticId(p), + name: p.name, + description: oneLine(p.description), + category: null, + installed: true, + skill_count: p.skills.length, + connector_count: p.hasMcp ? 1 : 0, + local: toPublic(p) + }); + } + const groups = {}; + try { + const deep = readJson("data/pstack.json"); + if (deep.plugin_id && Array.isArray(deep.groups)) { + groups[String(deep.plugin_id)] = deep.groups.map((g)=>({ + name: g.name || g.id, + skillIds: (g.skills || []).map((s)=>s.id) + })); + } + } catch { + // no deep dump: skills render ungrouped + } + return { + ok: true, + installed, + marketplace, + groups + }; +} +function handleLibrary(_req, res) { + return send(res, 200, buildLibrary()); +} +/** `/api/local///(doc|file)/` */ function handleLocal(_req, res, pathname) { + const m = /^\/api\/local\/([^/]+)\/([^/]+)\/(doc|file)\/(.+)$/.exec(pathname); + if (!m) return send(res, 404, { + ok: false, + error: "not_found" + }); + const plugin = getLocalPlugin(`${m[1]}/${m[2]}`); + if (!plugin) return send(res, 404, { + ok: false, + error: "plugin_not_cached" + }); + const file = resolveInside(plugin.root, m[4]); + if (!file) return send(res, 404, { + ok: false, + error: "file_not_found" + }); + if (m[3] === "doc") { + if (!/\.mdc?$/i.test(file)) return send(res, 415, { + ok: false, + error: "not_markdown" + }); + const { meta, body } = parseFrontMatter(node_fs__rspack_import_0["default"].readFileSync(file, "utf8")); + return send(res, 200, { + ok: true, + meta, + markdown: body + }); + } + return sendFile(res, file, "private, max-age=300"); +} +async function handleBots(_req, res, _pathname, url) { + try { + return send(res, 200, await listBots({ + force: url.searchParams.has("refresh") + })); + } catch (e) { + return send(res, 502, { + ok: false, + error: "gbot_unavailable", + message: e.message + }); + } +} +/** Route table: method + exact path or prefix. First match wins. */ const ROUTES = [ + [ + "GET", + "/api/library", + handleLibrary + ], + [ + "GET", + "/api/local/", + handleLocal, + "prefix" + ], + [ + "GET", + "/api/bots", + handleBots + ], + [ + "POST", + "/api/send", + handleSend + ] +]; +async function dispatch(req, res) { + let url; + let pathname; + try { + url = new node_url__rspack_import_3.URL(req.url || "/", `http://${req.headers.host || "localhost"}`); + // Reject malformed percent-escapes once, here, so handlers can trust pathname. + pathname = decodeURIComponent(url.pathname); + } catch { + return send(res, 400, { + ok: false, + error: "bad_url" + }); + } + for (const [method, pathPattern, handler, mode] of ROUTES){ + const hit = mode === "prefix" ? pathname.startsWith(pathPattern) : pathname === pathPattern; + if (hit && req.method === method) return handler(req, res, pathname, url); + if (hit) return send(res, 405, { + ok: false, + error: "method_not_allowed" + }); + } + if (req.method === "GET" || req.method === "HEAD") return serveStatic(req, res, pathname); + return send(res, 405, { + ok: false, + error: "method_not_allowed" + }); +} +const server = node_http__rspack_import_1["default"].createServer((req, res)=>{ + Promise.resolve().then(()=>dispatch(req, res)).catch((err)=>{ + console.error(`${req.method} ${req.url} failed:`, err); + if (!res.headersSent) send(res, 500, { + ok: false, + error: "internal", + message: err.message + }); + else res.end(); + }); +}); +server.listen(PORT, HOST, ()=>{ + const { port } = server.address(); + console.log(`plugin-library listening on http://${HOST}:${port}`); + console.log(` UI: http://127.0.0.1:${port}/`); + console.log(` library: GET /api/library`); + console.log(` bots: GET /api/bots`); + console.log(` send: POST /api/send`); +}); + diff --git a/artifact/scripts/plugin-library.mjs b/artifact/scripts/plugin-library.mjs new file mode 100644 index 0000000..8a162c3 --- /dev/null +++ b/artifact/scripts/plugin-library.mjs @@ -0,0 +1,382 @@ +import * as __rspack_external_node_child_process_cd435d64 from "node:child_process"; +import * as __rspack_external_node_fs_1b05aee1 from "node:fs"; +import * as __rspack_external_node_path_806ed179 from "node:path"; +import * as __rspack_external_node_url_3991086a from "node:url"; +var __webpack_modules__ = ({ +"node:child_process"(module) { + +module.exports = __rspack_external_node_child_process_cd435d64; + + +}, +"node:fs"(module) { + +module.exports = __rspack_external_node_fs_1b05aee1; + + +}, +"node:path"(module) { + +module.exports = __rspack_external_node_path_806ed179; + + +}, +"node:url"(module) { + +module.exports = __rspack_external_node_url_3991086a; + + +}, +"./bin/plugin-library.mjs"(__webpack_module__, __unused_rspack___webpack_exports__, __webpack_require__) { +__webpack_require__.a(__webpack_module__, async function (__rspack_load_async_deps, __rspack_async_done) { try { +/* import */ var node_child_process__rspack_import_0 = __webpack_require__("node:child_process"); +/* import */ var node_fs__rspack_import_1 = __webpack_require__("node:fs"); +/* import */ var node_path__rspack_import_2 = __webpack_require__("node:path"); +/* import */ var node_url__rspack_import_3 = __webpack_require__("node:url"); +/** + * plugin-library [--json] [--browser] [--port N] + * + * `start` is idempotent: if the server already answers on the port it is left + * alone, otherwise one is spawned detached and this process waits for health. + * `open` starts, resolves `query` (plugin name, id, or skill id) to a deep link, + * prints it, and with --browser hands it to the OS browser. + */ + + + +const ROOT = node_path__rspack_import_2["default"].resolve(node_path__rspack_import_2["default"].dirname((0,node_url__rspack_import_3.fileURLToPath)(import.meta.url)), ".."); +const PID_FILE = node_path__rspack_import_2["default"].join(ROOT, "logs", "server.pid"); +const LOG_FILE = node_path__rspack_import_2["default"].join(ROOT, "logs", "server.out"); +const SERVER_ENTRY = node_fs__rspack_import_1["default"].existsSync(node_path__rspack_import_2["default"].join(ROOT, "scripts", "plugin-library-server.mjs")) ? node_path__rspack_import_2["default"].join(ROOT, "scripts", "plugin-library-server.mjs") : node_path__rspack_import_2["default"].join(ROOT, "server.js"); +const args = process.argv.slice(2); +const flag = (name)=>{ + const i = args.indexOf(name); + if (i === -1) return false; + args.splice(i, 1); + return true; +}; +const option = (name, fallback)=>{ + const i = args.indexOf(name); + if (i === -1) return fallback; + const v = args[i + 1]; + args.splice(i, 2); + return v; +}; +const json = flag("--json"); +const browser = flag("--browser"); +const port = Number(option("--port", process.env.PORT || 8787)); +const command = args.shift() || "open"; +const query = args.join(" ").trim(); +const base = `http://127.0.0.1:${port}`; +const out = (obj)=>{ + if (json) console.log(JSON.stringify(obj)); + else console.log(obj.url || obj.message || JSON.stringify(obj)); +}; +async function healthy() { + try { + const r = await fetch(`${base}/api/library`, { + signal: AbortSignal.timeout(1500) + }); + return r.ok; + } catch { + return false; + } +} +async function start() { + if (await healthy()) return { + started: false, + pid: readPid() + }; + node_fs__rspack_import_1["default"].mkdirSync(node_path__rspack_import_2["default"].dirname(LOG_FILE), { + recursive: true + }); + const log = node_fs__rspack_import_1["default"].openSync(LOG_FILE, "a"); + const child = (0,node_child_process__rspack_import_0.spawn)(process.execPath, [ + SERVER_ENTRY + ], { + cwd: ROOT, + env: { + ...process.env, + PORT: String(port) + }, + detached: true, + stdio: [ + "ignore", + log, + log + ] + }); + child.unref(); + node_fs__rspack_import_1["default"].writeFileSync(PID_FILE, String(child.pid)); + for(let i = 0; i < 40; i++){ + if (await healthy()) return { + started: true, + pid: child.pid + }; + await new Promise((r)=>setTimeout(r, 150)); + } + throw new Error(`server did not become healthy on ${base}; see ${LOG_FILE}`); +} +function readPid() { + try { + return Number(node_fs__rspack_import_1["default"].readFileSync(PID_FILE, "utf8")); + } catch { + return null; + } +} +function stop() { + const pid = readPid(); + if (!pid) return { + stopped: false, + message: "no pidfile; nothing to stop" + }; + try { + process.kill(pid, "SIGTERM"); + node_fs__rspack_import_1["default"].rmSync(PID_FILE, { + force: true + }); + return { + stopped: true, + pid + }; + } catch (e) { + node_fs__rspack_import_1["default"].rmSync(PID_FILE, { + force: true + }); + return { + stopped: false, + pid, + message: e.code === "ESRCH" ? "process already gone" : e.message + }; + } +} +/** Plugin name / id / skill id → hash route. Exact hits first, then substring. */ async function resolveHash(q) { + if (!q) return ""; + const lib = await (await fetch(`${base}/api/library`)).json(); + const all = [ + ...lib.installed, + ...lib.marketplace + ]; + const norm = (s)=>String(s || "").toLowerCase(); + const nq = norm(q); + const byId = all.find((p)=>p.plugin_id === q); + if (byId) return `#/p/${byId.plugin_id}`; + for (const p of lib.installed){ + const skill = (p.local?.skills || []).find((s)=>norm(s.id) === nq || norm(s.name) === nq); + if (skill) return `#/p/${p.plugin_id}/s/${encodeURIComponent(skill.id)}`; + } + const byName = all.find((p)=>norm(p.name) === nq) || all.find((p)=>norm(p.name).includes(nq)); + if (byName) return `#/p/${byName.plugin_id}`; + for (const p of lib.installed){ + const skill = (p.local?.skills || []).find((s)=>norm(s.id).includes(nq)); + if (skill) return `#/p/${p.plugin_id}/s/${encodeURIComponent(skill.id)}`; + } + return ""; +} +function openInBrowser(url) { + const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"; + return new Promise((resolve)=>(0,node_child_process__rspack_import_0.execFile)(opener, [ + url + ], ()=>resolve())); +} +try { + switch(command){ + case "start": + { + const r = await start(); + out({ + ok: true, + url: `${base}/`, + ...r, + message: r.started ? `started on ${base}` : `already running on ${base}` + }); + break; + } + case "stop": + out({ + ok: true, + ...stop() + }); + break; + case "status": + { + const up = await healthy(); + out({ + ok: true, + running: up, + url: `${base}/`, + pid: readPid(), + message: up ? `running on ${base}` : "not running" + }); + break; + } + case "open": + { + const r = await start(); + const hash = await resolveHash(query); + const url = `${base}/${hash}`; + if (browser) await openInBrowser(url); + out({ + ok: true, + url, + resolved: Boolean(hash) || !query, + query: query || null, + started: r.started + }); + break; + } + default: + console.error(`unknown command: ${command}\nusage: plugin-library [--json] [--browser] [--port N]`); + process.exit(2); + } +} catch (e) { + if (json) console.log(JSON.stringify({ + ok: false, + error: e.message + })); + else console.error(e.message); + process.exit(1); +} + +__rspack_async_done(); +} catch(e) { __rspack_async_done(e); } }, 1); + +}, + +}); +// The module cache +var __webpack_module_cache__ = {}; + +// The require function +function __webpack_require__(moduleId) { + +// Check if module is in cache +var cachedModule = __webpack_module_cache__[moduleId]; +if (cachedModule !== undefined) { +return cachedModule.exports; +} +// Create a new module (and put it into the cache) +var module = (__webpack_module_cache__[moduleId] = { +exports: {} +}); +// Execute the module function +__webpack_modules__[moduleId](module, module.exports, __webpack_require__); + +// Return the exports of the module +return module.exports; + +} + +// expose the module cache +__webpack_require__.c = __webpack_module_cache__; + +// webpack/runtime/async_module +(() => { +var hasSymbol = typeof Symbol === "function"; +var rspackQueues = hasSymbol ? Symbol("rspack queues") : "__rspack_queues"; +var rspackExports = __webpack_require__.aE = hasSymbol ? Symbol("rspack exports") : "__webpack_exports__"; +var rspackError = hasSymbol ? Symbol("rspack error") : "__rspack_error"; +var rspackDone = hasSymbol ? Symbol("rspack done") : "__rspack_done"; +var rspackDefer = __webpack_require__.zS = hasSymbol ? Symbol("rspack defer") : "__rspack_defer"; +__webpack_require__.zT = (asyncDeps) => { + var hasUnresolvedAsyncSubgraph = asyncDeps.some((id) => { + var cache = __webpack_module_cache__[id]; + return !cache || cache[rspackDone] === false; + }); + if (hasUnresolvedAsyncSubgraph) { + return ({ then(onFulfilled, onRejected) { return Promise.all(asyncDeps.map(__webpack_require__)).then(onFulfilled, onRejected) } }); + } +} +var resolveQueue = (queue) => { + if (queue && queue.d < 1) { + queue.d = 1; + queue.forEach((fn) => (fn.r--)); + queue.forEach((fn) => (fn.r-- ? fn.r++ : fn())); + } +} +var wrapDeps = (deps) => { + return deps.map((dep) => { + if (dep !== null && typeof dep === "object") { + if(!dep[rspackQueues] && dep[rspackDefer]) { + var asyncDeps = __webpack_require__.zT(dep[rspackDefer]); + if (asyncDeps) { + var d = dep; + dep = { + then(onFulfilled, onRejected) { + asyncDeps.then(() => (onFulfilled(d)), onRejected); + } + }; + } else return dep; + } + if (dep[rspackQueues]) return dep; + if (dep.then) { + var queue = []; + queue.d = 0; + dep.then((r) => { + obj[rspackExports] = r; + resolveQueue(queue); + },(e) => { + obj[rspackError] = e; + resolveQueue(queue); + }); + var obj = {}; + obj[rspackDefer] = false; + obj[rspackQueues] = (fn) => (fn(queue)); + return obj; + } + } + var ret = {}; + ret[rspackQueues] = () => {}; + ret[rspackExports] = dep; + return ret; + }); +}; +__webpack_require__.a = (module, body, hasAwait, useModuleExports) => { + var queue; + hasAwait && ((queue = []).d = -1); + var depQueues = new Set(); + var exports = module.exports; + var currentDeps; + var outerResolve; + var reject; + var promise = new Promise((resolve, rej) => { + reject = rej; + outerResolve = resolve; + }); + promise[rspackExports] = exports; + promise[rspackQueues] = (fn) => { queue && fn(queue), depQueues.forEach(fn), promise["catch"](() => {}); }; + module.exports = promise; + var asyncModule = module; + if (useModuleExports) { + asyncModule = Object.create(module); + asyncModule.exports = exports; + } + var handle = (deps) => { + currentDeps = wrapDeps(deps); + var fn; + var getResult = () => { + return currentDeps.map((d) => { + if(d[rspackDefer]) return d; + if (d[rspackError]) throw d[rspackError]; + return d[rspackExports]; + }); + } + var promise = new Promise((resolve) => { + fn = () => (resolve(getResult)); + fn.r = 0; + var fnQueue = (q) => (q !== queue && !depQueues.has(q) && (depQueues.add(q), q && !q.d && (fn.r++, q.push(fn)))); + currentDeps.map((dep) => (dep[rspackDefer] || dep[rspackQueues](fnQueue))); + }); + return fn.r ? promise : getResult(); + }; + var done = (err) => ((err ? reject(promise[rspackError] = err) : (useModuleExports && (exports = promise[rspackExports] = asyncModule.exports), outerResolve(exports))), resolveQueue(queue), promise[rspackDone] = true); + body(handle, done, asyncModule); + queue && queue.d < 0 && (queue.d = 0); +}; + +})(); +// module cache are used so entry inlining is disabled +// startup +// Load entry module and return exports +var __webpack_exports__ = __webpack_require__("./bin/plugin-library.mjs"); +__webpack_exports__ = await __webpack_exports__; diff --git a/artifact/skills/plugin-library/SKILL.md b/artifact/skills/plugin-library/SKILL.md new file mode 100644 index 0000000..61069fb --- /dev/null +++ b/artifact/skills/plugin-library/SKILL.md @@ -0,0 +1,32 @@ +--- +name: plugin-library +description: Open or query the Plugin Library explorer when the user wants to browse plugins, read a skill's full SKILL.md, or hand a skill to a Grok Bot. Use for "show me pstack's skills", "open the plugin library", "what does the why skill say". +--- + +# Plugin Library + +A local web explorer over the Cursor plugin cache and the Plugin Catalog. Three +panes: plugins, a plugin's skills / agents / rules, and the full `SKILL.md` of +whatever is clicked. "Send to a bot" picks a target from the live `gbot` +roster and confirms before sending a direct message. + +## Commands + +In Cursor, use `/plugin-library [query]`. On a portable host without Cursor +commands, use the read-only API below only when the explorer is already +running; ask the user to start it rather than guessing an installation path. + +## Read-only API + +- `GET http://127.0.0.1:8787/api/library` returns + `{ installed, marketplace, groups }`. +- `GET http://127.0.0.1:8787/api/local///doc/skills//SKILL.md` + returns `{ meta, markdown }`. + +Quote from `markdown`; never paraphrase a skill as if it were the skill's own +words. + +## Hard rule + +Do not call `POST /api/send`. Sending a plugin or skill to a bot is a +user-confirmed Explorer action; there is no agent-side path around it. From c71d4f382d33e17c44b2b363afc4bb994abe26bf Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 18 Sep 2026 19:27:24 +0000 Subject: [PATCH 3/4] build: derive root marketplace from generated Agent Bundle --- .cursor-plugin/marketplace.json | 6 ++++-- .github/workflows/publish-agent-bundle.yml | 11 ++++++++++- artifact/agent-bundle.manifest.json | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 5b897e7..930e8e1 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -1,10 +1,12 @@ { "name": "plugin-library-marketplace", - "owner": { "name": "plugin-library" }, + "owner": { + "name": "plugin-library" + }, "plugins": [ { - "name": "plugin-library", "description": "Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.", + "name": "plugin-library", "source": "./artifact" } ] diff --git a/.github/workflows/publish-agent-bundle.yml b/.github/workflows/publish-agent-bundle.yml index 87a19a8..033e847 100644 --- a/.github/workflows/publish-agent-bundle.yml +++ b/.github/workflows/publish-agent-bundle.yml @@ -32,11 +32,20 @@ jobs: - name: Build and verify artifact run: npm run check + - name: Generate repository marketplace from artifact + run: | + node --input-type=module <<'JS' + import { readFileSync, writeFileSync } from 'node:fs'; + const marketplace = JSON.parse(readFileSync('artifact/.cursor-plugin/marketplace.json', 'utf8')); + for (const plugin of marketplace.plugins) plugin.source = './artifact'; + writeFileSync('.cursor-plugin/marketplace.json', JSON.stringify(marketplace, null, 2) + '\n'); + JS + - name: Commit generated artifact on main run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git add --force --all artifact + git add --force --all artifact .cursor-plugin/marketplace.json if git diff --cached --quiet; then exit 0 fi diff --git a/artifact/agent-bundle.manifest.json b/artifact/agent-bundle.manifest.json index 630b3cc..6c9d380 100644 --- a/artifact/agent-bundle.manifest.json +++ b/artifact/agent-bundle.manifest.json @@ -1 +1 @@ -{"application":{"description":"Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.","id":"plugin:plugin-library","name":"plugin-library","version":"0.4.0"},"compiler":{"adapters":[{"adapterRevision":"1.13.0","host":"cursor","observedVersion":"2026-08-28","schemas":[{"name":"hooks","revision":"2026-08-28","sha256":"06154b7afa0861df462130b988912b897e7ccf962b8dd20c09193100bcde5d81"},{"name":"marketplace","revision":"2026-08-28","sha256":"1aae96a24c2796419933bc8bfe3a1255394e7199c35740b36325e0ce6dbc253d"},{"name":"mcp","revision":"2026-08-28","sha256":"f3fa4615afefe004c4fbcc09e635d890df0f1ec0cb39540feab72cbd3a31d844"},{"name":"plugin","revision":"2026-08-28","sha256":"a393b758901803fcf5cfe0d77bda8a83e987d32c3377dfce2d9edf445af884ed"}]},{"adapterRevision":"1.10.0","host":"portable","observedVersion":"1.0.0","schemas":[{"name":"mcp","revision":"1.0.0","sha256":"6539175bfcdf43085855183e86da40ea94b166547a72b47ae9a0a390516d3acb"},{"name":"plugin","revision":"1.0.0","sha256":"0a4aad95ce337878ad38802ebf0daa3fde76abe3f65400c86bcbb1ec0b3ab883"}]}],"agentSkills":{"schemaSha256":"6d06b61e423317421de2295ea1fd0c7b4491bfa36c5b7705c28616dfe76d4047","sourceRevision":"69ef37e9424c0a7ea9dd2293b559e43ec8176379","specification":"https://raw.githubusercontent.com/agentskills/agentskills/69ef37e9424c0a7ea9dd2293b559e43ec8176379/docs/specification.mdx"},"producer":{"name":"agent-bundle","version":"0.2.1"},"project":{"configDigest":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003","configPath":"agent-bundle.config.ts","modelDigest":"abe272f372d106e7e2861ec7f687f56eee76e62bf23b2a9a9c16270ac0970298","packageName":"plugin-library","packageVersion":"0.4.0","revision":"656d3a955dcb47dfd37a8c68e400fa365a12ee7f41d61a35f0363252fcf81303","sourceInputs":[{"executable":false,"path":".cursor-plugin/marketplace.json","sha256":"5c15c414c75fde0e2bb7738bd1df4ec111acd8dea98d8773f4e335121ff8c653"},{"executable":false,"path":".github/workflows/ci.yml","sha256":"3c51f0bdc639d14d698407abbd7e8a23f6f7330e521b6a78f67e018d72b1a045"},{"executable":false,"path":".github/workflows/publish-agent-bundle.yml","sha256":"ab36b84abb040c92106246d21e8b5420c8b8f000fc9826d47404ff6b92829cfe"},{"executable":false,"path":".gitignore","sha256":"fc7f785227a74db0c56704f6f615d1c2ec716afe1d9df25b74449367f28955c2"},{"executable":false,"path":"agent-bundle.config.ts","sha256":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003"},{"executable":false,"path":"assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"executable":true,"path":"bin/plugin-library.mjs","sha256":"0c174e75dadbed3c17f19651f7b764fcd080b83d2ada35523fd51fadd82aef2f"},{"executable":false,"path":"data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"executable":false,"path":"data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"executable":false,"path":"lib/bots.js","sha256":"fb697ad2b8b86f50716c310e74765bbcbdf54a65b48a65e9372c8a597e772be3"},{"executable":false,"path":"lib/local-plugins.js","sha256":"32d134641d78a888fa7e0e2e3f11ebad8d1e300eb2807ac5a93c559644612adb"},{"executable":false,"path":"lib/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"LICENSE","sha256":"ff28847df30260ae031d33a736f0e72210a539b1417570bac88a59016157544c"},{"executable":false,"path":"logs/.gitkeep","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"executable":false,"path":"package-lock.json","sha256":"a49aa6a773c3c8fddde93c1dcae1041140c2b9f02fa8c34718f48d385cafde07"},{"executable":false,"path":"package.json","sha256":"2b0cbd0428ab6821eb45e8c6db07425a9000ac79e3a1246afff059c4c2fbff80"},{"executable":false,"path":"public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"executable":false,"path":"public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"executable":false,"path":"public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"executable":false,"path":"public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"executable":false,"path":"public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"executable":false,"path":"README.md","sha256":"6b5162bcd4594a518c4b9fb6184ed12dcfee4a9c44b334f99993d7131b5a0b45"},{"executable":false,"path":"renovate.json","sha256":"e80b4e42a3043bc12fa0640db4bac392d2bf770acf841360d7c8ceeeac2ec1a9"},{"executable":false,"path":"screens/01-library.png","sha256":"f465bcecd299c9413975baa307dd87dc6b8cacbd0498134118df5bac2ef391a5"},{"executable":false,"path":"screens/02-pstack.png","sha256":"8fc2ed63fae8e7dac833ecea75d64ddcad9c3b6a49382e66132c13876b322c41"},{"executable":false,"path":"screens/03-read-skill.png","sha256":"afee8387371e444a6b0c997b45b576c4c6c7e527fca753338583c6e3ce68ddd2"},{"executable":false,"path":"screens/04-reference-tab.png","sha256":"b55ac9ce9e2cd1918e1b8f48a9ed2292b8f50872a2ffb2ce5c70b665c299eaae"},{"executable":false,"path":"screens/05-pick-a-bot.png","sha256":"03a7823afcd85dc6489bd2cb3b572b499cf8999e16e7a2436e1069ca2913607b"},{"executable":false,"path":"screens/06-confirm.png","sha256":"3333936a6af257344f1d3375a7b3dc14b53c1a10012c1340154c810b00c745a3"},{"executable":false,"path":"screens/07-cursor-team-kit.png","sha256":"fa8a42a120d33bfb32a8c7299180eba1dd61d4550228c05aea9038aa59bd3671"},{"executable":false,"path":"screens/08-marketplace.png","sha256":"5267be7f941e853588eee4d27b2083e20c00491a97406020dc7f7334f7619df0"},{"executable":false,"path":"scripts/packed-smoke.mjs","sha256":"ceb8bbf9933c9e35494b8d91f263d95c31324cc48cc5540f7aa4933cdc254697"},{"executable":true,"path":"server.js","sha256":"fc44c043db21f29c743fd074417dc8c357bb0f7491a469b05ae6a37da2dc2ba0"},{"executable":false,"path":"src/commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"executable":false,"path":"src/install-bin.ts","sha256":"766ae5ecdc1331397de9cf32922048d6d9386e270c7e283284a2f95c6953b499"},{"executable":false,"path":"src/skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"},{"executable":false,"path":"test/browser.test.js","sha256":"6c12d42fdb74239c5cc82efc58c3f8602b936c189e7c5d1990bf5b8413cf3ab0"},{"executable":false,"path":"test/data.test.js","sha256":"5b55d8aaa67db2853c7dc7b3bb93cabfe905b83683d3c2e1d156cedcbe802f49"},{"executable":false,"path":"test/helpers.js","sha256":"c32ef9b8c5e7a8817a9a43366212febca54bbf158a14860a27958dcbf78dbdb7"},{"executable":false,"path":"test/install.test.js","sha256":"d3080b2bb3ec577d7cd6adce6f1a5912736a017ce3ded08374b8897485971d4c"},{"executable":false,"path":"test/local-plugins.test.js","sha256":"a04b94b16ebbfb7d2090d0aa3243e758460b3f78a33d3b1ed54b0cd85cb0232f"},{"executable":false,"path":"test/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"test/server.test.js","sha256":"ed64df042f57b09facbd5da93702b0c0b3b691cd478ccade6ea77fce9bdd4e10"},{"executable":false,"path":"tsconfig.json","sha256":"a6599174892162cd37b9b9a046c581b813d933eb3ad267daeeca79d0d48fa9cd"}]},"provenance":[{"path":".cursor-plugin/marketplace.json","sourceInputs":["agent-bundle.config.ts"]},{"path":".cursor-plugin/plugin.json","sourceInputs":["agent-bundle.config.ts","assets/logo.svg","package.json","src/commands/plugin-library.md","src/skills/plugin-library/SKILL.md"]},{"path":"agent-bundle.compile-evidence.json","sourceInputs":["agent-bundle.config.ts"]},{"path":"assets/assets/logo.svg","sourceInputs":["agent-bundle.config.ts","assets/logo.svg"]},{"path":"assets/data/pstack.json","sourceInputs":["data/pstack.json"]},{"path":"assets/data/unified-catalog.json","sourceInputs":["data/unified-catalog.json"]},{"path":"assets/public/app.js","sourceInputs":["public/app.js"]},{"path":"assets/public/index.html","sourceInputs":["public/index.html"]},{"path":"assets/public/markdown.js","sourceInputs":["public/markdown.js"]},{"path":"assets/public/picker.js","sourceInputs":["public/picker.js"]},{"path":"assets/public/styles.css","sourceInputs":["public/styles.css"]},{"path":"commands/plugin-library.md","sourceInputs":["src/commands/plugin-library.md"]},{"path":"INSTALL.md","sourceInputs":["agent-bundle.config.ts"]},{"path":"install.mjs","sourceInputs":["agent-bundle.config.ts"]},{"path":"plugin.json","sourceInputs":["agent-bundle.config.ts","package.json"]},{"path":"scripts/plugin-library-server.mjs","sourceInputs":["agent-bundle.config.ts","lib/bots.js","lib/local-plugins.js","server.js"]},{"path":"scripts/plugin-library.mjs","sourceInputs":["agent-bundle.config.ts","bin/plugin-library.mjs"]},{"path":"skills/plugin-library/SKILL.md","sourceInputs":["src/skills/plugin-library/SKILL.md"]}],"recordVersion":1,"validation":{"artifact":{"status":"passed"},"projections":[{"host":"cursor","status":"passed"},{"host":"portable","status":"passed"}],"source":{"status":"passed"}}},"distribution":{"channels":["local","npm"],"install":{"instructions":"INSTALL.md","script":"install.mjs"},"payloads":[]},"executables":{"bins":[],"hooks":[],"mcpServers":[],"scripts":[{"hosts":["cursor","portable"],"id":"script:plugin-library","mode":"bundle","name":"plugin-library","path":"scripts/plugin-library.mjs"},{"hosts":["cursor","portable"],"id":"script:plugin-library-server","mode":"bundle","name":"plugin-library-server","path":"scripts/plugin-library-server.mjs"}]},"files":[{"bytes":236,"kind":"generated","path":".cursor-plugin/marketplace.json","sha256":"3a7e9c80d1bfdd1a25f0642e5d845ae21e52e4082c5169adff044a9ecce43eaf"},{"bytes":418,"kind":"generated","path":".cursor-plugin/plugin.json","sha256":"5eee1c6d8e738e5246c4f673f838ebef5b45d9c620234fc7332341744d880cbf"},{"bytes":2071,"kind":"generated","path":"agent-bundle.compile-evidence.json","sha256":"eada336633d54da7bf14c73ef13dbcd43844dbd43bed89e349d0f279ce73efe9"},{"bytes":519,"kind":"copy","path":"assets/assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"bytes":23189,"kind":"copy","path":"assets/data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"bytes":181719,"kind":"copy","path":"assets/data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"bytes":19680,"kind":"copy","path":"assets/public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"bytes":2112,"kind":"copy","path":"assets/public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"bytes":7273,"kind":"copy","path":"assets/public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"bytes":8090,"kind":"copy","path":"assets/public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"bytes":18065,"kind":"copy","path":"assets/public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"bytes":953,"kind":"generated","path":"commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"bytes":16134,"kind":"generated","path":"INSTALL.md","sha256":"e37d8d70a103f4bbf0bf862d5f0103ed8b08c41094d7dd403f5863b8acac2d87"},{"bytes":84127,"kind":"generated","path":"install.mjs","sha256":"6bca4b63ecf2cdc2de4cb3dcf516dcf2e7d8f460ae52ec5ee8c5a8955687fe68"},{"bytes":378,"kind":"generated","path":"plugin.json","sha256":"c3d9e182728a589ac3679fc176f0fe0d90c0a9f4fba0857e388fab7d82aba680"},{"bytes":31525,"kind":"bundle","mode":493,"path":"scripts/plugin-library-server.mjs","sha256":"6b2c7548077aad7a68b0dd2716c833eb3597b91cc25f0dd6c24e1cd464326411"},{"bytes":12515,"kind":"bundle","path":"scripts/plugin-library.mjs","sha256":"c295407765af2e4afb8d97bbf6b794191558139b24bcbb4ca950a204f00fd464"},{"bytes":1289,"kind":"copy","path":"skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"}],"manifestVersion":6,"projections":[{"builtInHost":"cursor","documents":{"marketplace":".cursor-plugin/marketplace.json","plugin":".cursor-plugin/plugin.json"},"host":"cursor","marketplace":{"name":"plugin-library-marketplace"}},{"builtInHost":"portable","documents":{"plugin":"plugin.json"},"host":"portable"}],"routes":{"digest":"d3f44eea2211698634fb9e2fc28997db775625774579eb44460cca873ce5c683","events":[],"layouts":[],"providers":[],"scripts":[],"servers":[]},"runtime":{"node":"22.19.0"}} +{"application":{"description":"Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.","id":"plugin:plugin-library","name":"plugin-library","version":"0.4.0"},"compiler":{"adapters":[{"adapterRevision":"1.13.0","host":"cursor","observedVersion":"2026-08-28","schemas":[{"name":"hooks","revision":"2026-08-28","sha256":"06154b7afa0861df462130b988912b897e7ccf962b8dd20c09193100bcde5d81"},{"name":"marketplace","revision":"2026-08-28","sha256":"1aae96a24c2796419933bc8bfe3a1255394e7199c35740b36325e0ce6dbc253d"},{"name":"mcp","revision":"2026-08-28","sha256":"f3fa4615afefe004c4fbcc09e635d890df0f1ec0cb39540feab72cbd3a31d844"},{"name":"plugin","revision":"2026-08-28","sha256":"a393b758901803fcf5cfe0d77bda8a83e987d32c3377dfce2d9edf445af884ed"}]},{"adapterRevision":"1.10.0","host":"portable","observedVersion":"1.0.0","schemas":[{"name":"mcp","revision":"1.0.0","sha256":"6539175bfcdf43085855183e86da40ea94b166547a72b47ae9a0a390516d3acb"},{"name":"plugin","revision":"1.0.0","sha256":"0a4aad95ce337878ad38802ebf0daa3fde76abe3f65400c86bcbb1ec0b3ab883"}]}],"agentSkills":{"schemaSha256":"6d06b61e423317421de2295ea1fd0c7b4491bfa36c5b7705c28616dfe76d4047","sourceRevision":"69ef37e9424c0a7ea9dd2293b559e43ec8176379","specification":"https://raw.githubusercontent.com/agentskills/agentskills/69ef37e9424c0a7ea9dd2293b559e43ec8176379/docs/specification.mdx"},"producer":{"name":"agent-bundle","version":"0.2.1"},"project":{"configDigest":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003","configPath":"agent-bundle.config.ts","modelDigest":"abe272f372d106e7e2861ec7f687f56eee76e62bf23b2a9a9c16270ac0970298","packageName":"plugin-library","packageVersion":"0.4.0","revision":"8e410424a163f4fb6f8c53834789671d9fbf5091a3f73c9c06eac71f27fe81ac","sourceInputs":[{"executable":false,"path":".cursor-plugin/marketplace.json","sha256":"7400cf737ae5bf76e7e2dca734ebfc3fa774f2141f03c054e07df5e1789cddff"},{"executable":false,"path":".github/workflows/ci.yml","sha256":"3c51f0bdc639d14d698407abbd7e8a23f6f7330e521b6a78f67e018d72b1a045"},{"executable":false,"path":".github/workflows/publish-agent-bundle.yml","sha256":"3168f9e09c1fd7e83dfd957efd6d5b3b0c0d10316cc54e38df167a6ef86b0cf3"},{"executable":false,"path":".gitignore","sha256":"fc7f785227a74db0c56704f6f615d1c2ec716afe1d9df25b74449367f28955c2"},{"executable":false,"path":"agent-bundle.config.ts","sha256":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003"},{"executable":false,"path":"assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"executable":true,"path":"bin/plugin-library.mjs","sha256":"0c174e75dadbed3c17f19651f7b764fcd080b83d2ada35523fd51fadd82aef2f"},{"executable":false,"path":"data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"executable":false,"path":"data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"executable":false,"path":"lib/bots.js","sha256":"fb697ad2b8b86f50716c310e74765bbcbdf54a65b48a65e9372c8a597e772be3"},{"executable":false,"path":"lib/local-plugins.js","sha256":"32d134641d78a888fa7e0e2e3f11ebad8d1e300eb2807ac5a93c559644612adb"},{"executable":false,"path":"lib/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"LICENSE","sha256":"ff28847df30260ae031d33a736f0e72210a539b1417570bac88a59016157544c"},{"executable":false,"path":"logs/.gitkeep","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"executable":false,"path":"package-lock.json","sha256":"4619035d7152dae32cdcd226fe2a454a29f6948ed8be8b044661b399018b06d5"},{"executable":false,"path":"package.json","sha256":"06a4e4a5076bc7ed7a407e25d7619b1d97a95fe4cad5dd8627ffe1398601cfe0"},{"executable":false,"path":"public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"executable":false,"path":"public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"executable":false,"path":"public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"executable":false,"path":"public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"executable":false,"path":"public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"executable":false,"path":"README.md","sha256":"6b5162bcd4594a518c4b9fb6184ed12dcfee4a9c44b334f99993d7131b5a0b45"},{"executable":false,"path":"renovate.json","sha256":"e80b4e42a3043bc12fa0640db4bac392d2bf770acf841360d7c8ceeeac2ec1a9"},{"executable":false,"path":"screens/01-library.png","sha256":"f465bcecd299c9413975baa307dd87dc6b8cacbd0498134118df5bac2ef391a5"},{"executable":false,"path":"screens/02-pstack.png","sha256":"8fc2ed63fae8e7dac833ecea75d64ddcad9c3b6a49382e66132c13876b322c41"},{"executable":false,"path":"screens/03-read-skill.png","sha256":"afee8387371e444a6b0c997b45b576c4c6c7e527fca753338583c6e3ce68ddd2"},{"executable":false,"path":"screens/04-reference-tab.png","sha256":"b55ac9ce9e2cd1918e1b8f48a9ed2292b8f50872a2ffb2ce5c70b665c299eaae"},{"executable":false,"path":"screens/05-pick-a-bot.png","sha256":"03a7823afcd85dc6489bd2cb3b572b499cf8999e16e7a2436e1069ca2913607b"},{"executable":false,"path":"screens/06-confirm.png","sha256":"3333936a6af257344f1d3375a7b3dc14b53c1a10012c1340154c810b00c745a3"},{"executable":false,"path":"screens/07-cursor-team-kit.png","sha256":"fa8a42a120d33bfb32a8c7299180eba1dd61d4550228c05aea9038aa59bd3671"},{"executable":false,"path":"screens/08-marketplace.png","sha256":"5267be7f941e853588eee4d27b2083e20c00491a97406020dc7f7334f7619df0"},{"executable":false,"path":"scripts/packed-smoke.mjs","sha256":"ceb8bbf9933c9e35494b8d91f263d95c31324cc48cc5540f7aa4933cdc254697"},{"executable":true,"path":"server.js","sha256":"fc44c043db21f29c743fd074417dc8c357bb0f7491a469b05ae6a37da2dc2ba0"},{"executable":false,"path":"src/commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"executable":false,"path":"src/install-bin.ts","sha256":"766ae5ecdc1331397de9cf32922048d6d9386e270c7e283284a2f95c6953b499"},{"executable":false,"path":"src/skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"},{"executable":false,"path":"test/browser.test.js","sha256":"6c12d42fdb74239c5cc82efc58c3f8602b936c189e7c5d1990bf5b8413cf3ab0"},{"executable":false,"path":"test/data.test.js","sha256":"5b55d8aaa67db2853c7dc7b3bb93cabfe905b83683d3c2e1d156cedcbe802f49"},{"executable":false,"path":"test/helpers.js","sha256":"c32ef9b8c5e7a8817a9a43366212febca54bbf158a14860a27958dcbf78dbdb7"},{"executable":false,"path":"test/install.test.js","sha256":"d3080b2bb3ec577d7cd6adce6f1a5912736a017ce3ded08374b8897485971d4c"},{"executable":false,"path":"test/local-plugins.test.js","sha256":"a04b94b16ebbfb7d2090d0aa3243e758460b3f78a33d3b1ed54b0cd85cb0232f"},{"executable":false,"path":"test/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"test/server.test.js","sha256":"ed64df042f57b09facbd5da93702b0c0b3b691cd478ccade6ea77fce9bdd4e10"},{"executable":false,"path":"tsconfig.json","sha256":"a6599174892162cd37b9b9a046c581b813d933eb3ad267daeeca79d0d48fa9cd"}]},"provenance":[{"path":".cursor-plugin/marketplace.json","sourceInputs":["agent-bundle.config.ts"]},{"path":".cursor-plugin/plugin.json","sourceInputs":["agent-bundle.config.ts","assets/logo.svg","package.json","src/commands/plugin-library.md","src/skills/plugin-library/SKILL.md"]},{"path":"agent-bundle.compile-evidence.json","sourceInputs":["agent-bundle.config.ts"]},{"path":"assets/assets/logo.svg","sourceInputs":["agent-bundle.config.ts","assets/logo.svg"]},{"path":"assets/data/pstack.json","sourceInputs":["data/pstack.json"]},{"path":"assets/data/unified-catalog.json","sourceInputs":["data/unified-catalog.json"]},{"path":"assets/public/app.js","sourceInputs":["public/app.js"]},{"path":"assets/public/index.html","sourceInputs":["public/index.html"]},{"path":"assets/public/markdown.js","sourceInputs":["public/markdown.js"]},{"path":"assets/public/picker.js","sourceInputs":["public/picker.js"]},{"path":"assets/public/styles.css","sourceInputs":["public/styles.css"]},{"path":"commands/plugin-library.md","sourceInputs":["src/commands/plugin-library.md"]},{"path":"INSTALL.md","sourceInputs":["agent-bundle.config.ts"]},{"path":"install.mjs","sourceInputs":["agent-bundle.config.ts"]},{"path":"plugin.json","sourceInputs":["agent-bundle.config.ts","package.json"]},{"path":"scripts/plugin-library-server.mjs","sourceInputs":["agent-bundle.config.ts","lib/bots.js","lib/local-plugins.js","server.js"]},{"path":"scripts/plugin-library.mjs","sourceInputs":["agent-bundle.config.ts","bin/plugin-library.mjs"]},{"path":"skills/plugin-library/SKILL.md","sourceInputs":["src/skills/plugin-library/SKILL.md"]}],"recordVersion":1,"validation":{"artifact":{"status":"passed"},"projections":[{"host":"cursor","status":"passed"},{"host":"portable","status":"passed"}],"source":{"status":"passed"}}},"distribution":{"channels":["local","npm"],"install":{"instructions":"INSTALL.md","script":"install.mjs"},"payloads":[]},"executables":{"bins":[],"hooks":[],"mcpServers":[],"scripts":[{"hosts":["cursor","portable"],"id":"script:plugin-library","mode":"bundle","name":"plugin-library","path":"scripts/plugin-library.mjs"},{"hosts":["cursor","portable"],"id":"script:plugin-library-server","mode":"bundle","name":"plugin-library-server","path":"scripts/plugin-library-server.mjs"}]},"files":[{"bytes":236,"kind":"generated","path":".cursor-plugin/marketplace.json","sha256":"3a7e9c80d1bfdd1a25f0642e5d845ae21e52e4082c5169adff044a9ecce43eaf"},{"bytes":418,"kind":"generated","path":".cursor-plugin/plugin.json","sha256":"5eee1c6d8e738e5246c4f673f838ebef5b45d9c620234fc7332341744d880cbf"},{"bytes":2071,"kind":"generated","path":"agent-bundle.compile-evidence.json","sha256":"eada336633d54da7bf14c73ef13dbcd43844dbd43bed89e349d0f279ce73efe9"},{"bytes":519,"kind":"copy","path":"assets/assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"bytes":23189,"kind":"copy","path":"assets/data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"bytes":181719,"kind":"copy","path":"assets/data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"bytes":19680,"kind":"copy","path":"assets/public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"bytes":2112,"kind":"copy","path":"assets/public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"bytes":7273,"kind":"copy","path":"assets/public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"bytes":8090,"kind":"copy","path":"assets/public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"bytes":18065,"kind":"copy","path":"assets/public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"bytes":953,"kind":"generated","path":"commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"bytes":16134,"kind":"generated","path":"INSTALL.md","sha256":"e37d8d70a103f4bbf0bf862d5f0103ed8b08c41094d7dd403f5863b8acac2d87"},{"bytes":84127,"kind":"generated","path":"install.mjs","sha256":"6bca4b63ecf2cdc2de4cb3dcf516dcf2e7d8f460ae52ec5ee8c5a8955687fe68"},{"bytes":378,"kind":"generated","path":"plugin.json","sha256":"c3d9e182728a589ac3679fc176f0fe0d90c0a9f4fba0857e388fab7d82aba680"},{"bytes":31525,"kind":"bundle","mode":493,"path":"scripts/plugin-library-server.mjs","sha256":"6b2c7548077aad7a68b0dd2716c833eb3597b91cc25f0dd6c24e1cd464326411"},{"bytes":12515,"kind":"bundle","path":"scripts/plugin-library.mjs","sha256":"c295407765af2e4afb8d97bbf6b794191558139b24bcbb4ca950a204f00fd464"},{"bytes":1289,"kind":"copy","path":"skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"}],"manifestVersion":6,"projections":[{"builtInHost":"cursor","documents":{"marketplace":".cursor-plugin/marketplace.json","plugin":".cursor-plugin/plugin.json"},"host":"cursor","marketplace":{"name":"plugin-library-marketplace"}},{"builtInHost":"portable","documents":{"plugin":"plugin.json"},"host":"portable"}],"routes":{"digest":"d3f44eea2211698634fb9e2fc28997db775625774579eb44460cca873ce5c683","events":[],"layouts":[],"providers":[],"scripts":[],"servers":[]},"runtime":{"node":"22.19.0"}} From 589eb8953d2036ea94a1bd689f3bbf283bf47728 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 18 Sep 2026 19:27:41 +0000 Subject: [PATCH 4/4] ci: restrict artifact publication to main --- .github/workflows/publish-agent-bundle.yml | 1 + artifact/agent-bundle.manifest.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-agent-bundle.yml b/.github/workflows/publish-agent-bundle.yml index 033e847..986d7e8 100644 --- a/.github/workflows/publish-agent-bundle.yml +++ b/.github/workflows/publish-agent-bundle.yml @@ -14,6 +14,7 @@ concurrency: jobs: publish: + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 15 steps: diff --git a/artifact/agent-bundle.manifest.json b/artifact/agent-bundle.manifest.json index 6c9d380..b424134 100644 --- a/artifact/agent-bundle.manifest.json +++ b/artifact/agent-bundle.manifest.json @@ -1 +1 @@ -{"application":{"description":"Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.","id":"plugin:plugin-library","name":"plugin-library","version":"0.4.0"},"compiler":{"adapters":[{"adapterRevision":"1.13.0","host":"cursor","observedVersion":"2026-08-28","schemas":[{"name":"hooks","revision":"2026-08-28","sha256":"06154b7afa0861df462130b988912b897e7ccf962b8dd20c09193100bcde5d81"},{"name":"marketplace","revision":"2026-08-28","sha256":"1aae96a24c2796419933bc8bfe3a1255394e7199c35740b36325e0ce6dbc253d"},{"name":"mcp","revision":"2026-08-28","sha256":"f3fa4615afefe004c4fbcc09e635d890df0f1ec0cb39540feab72cbd3a31d844"},{"name":"plugin","revision":"2026-08-28","sha256":"a393b758901803fcf5cfe0d77bda8a83e987d32c3377dfce2d9edf445af884ed"}]},{"adapterRevision":"1.10.0","host":"portable","observedVersion":"1.0.0","schemas":[{"name":"mcp","revision":"1.0.0","sha256":"6539175bfcdf43085855183e86da40ea94b166547a72b47ae9a0a390516d3acb"},{"name":"plugin","revision":"1.0.0","sha256":"0a4aad95ce337878ad38802ebf0daa3fde76abe3f65400c86bcbb1ec0b3ab883"}]}],"agentSkills":{"schemaSha256":"6d06b61e423317421de2295ea1fd0c7b4491bfa36c5b7705c28616dfe76d4047","sourceRevision":"69ef37e9424c0a7ea9dd2293b559e43ec8176379","specification":"https://raw.githubusercontent.com/agentskills/agentskills/69ef37e9424c0a7ea9dd2293b559e43ec8176379/docs/specification.mdx"},"producer":{"name":"agent-bundle","version":"0.2.1"},"project":{"configDigest":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003","configPath":"agent-bundle.config.ts","modelDigest":"abe272f372d106e7e2861ec7f687f56eee76e62bf23b2a9a9c16270ac0970298","packageName":"plugin-library","packageVersion":"0.4.0","revision":"8e410424a163f4fb6f8c53834789671d9fbf5091a3f73c9c06eac71f27fe81ac","sourceInputs":[{"executable":false,"path":".cursor-plugin/marketplace.json","sha256":"7400cf737ae5bf76e7e2dca734ebfc3fa774f2141f03c054e07df5e1789cddff"},{"executable":false,"path":".github/workflows/ci.yml","sha256":"3c51f0bdc639d14d698407abbd7e8a23f6f7330e521b6a78f67e018d72b1a045"},{"executable":false,"path":".github/workflows/publish-agent-bundle.yml","sha256":"3168f9e09c1fd7e83dfd957efd6d5b3b0c0d10316cc54e38df167a6ef86b0cf3"},{"executable":false,"path":".gitignore","sha256":"fc7f785227a74db0c56704f6f615d1c2ec716afe1d9df25b74449367f28955c2"},{"executable":false,"path":"agent-bundle.config.ts","sha256":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003"},{"executable":false,"path":"assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"executable":true,"path":"bin/plugin-library.mjs","sha256":"0c174e75dadbed3c17f19651f7b764fcd080b83d2ada35523fd51fadd82aef2f"},{"executable":false,"path":"data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"executable":false,"path":"data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"executable":false,"path":"lib/bots.js","sha256":"fb697ad2b8b86f50716c310e74765bbcbdf54a65b48a65e9372c8a597e772be3"},{"executable":false,"path":"lib/local-plugins.js","sha256":"32d134641d78a888fa7e0e2e3f11ebad8d1e300eb2807ac5a93c559644612adb"},{"executable":false,"path":"lib/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"LICENSE","sha256":"ff28847df30260ae031d33a736f0e72210a539b1417570bac88a59016157544c"},{"executable":false,"path":"logs/.gitkeep","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"executable":false,"path":"package-lock.json","sha256":"4619035d7152dae32cdcd226fe2a454a29f6948ed8be8b044661b399018b06d5"},{"executable":false,"path":"package.json","sha256":"06a4e4a5076bc7ed7a407e25d7619b1d97a95fe4cad5dd8627ffe1398601cfe0"},{"executable":false,"path":"public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"executable":false,"path":"public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"executable":false,"path":"public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"executable":false,"path":"public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"executable":false,"path":"public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"executable":false,"path":"README.md","sha256":"6b5162bcd4594a518c4b9fb6184ed12dcfee4a9c44b334f99993d7131b5a0b45"},{"executable":false,"path":"renovate.json","sha256":"e80b4e42a3043bc12fa0640db4bac392d2bf770acf841360d7c8ceeeac2ec1a9"},{"executable":false,"path":"screens/01-library.png","sha256":"f465bcecd299c9413975baa307dd87dc6b8cacbd0498134118df5bac2ef391a5"},{"executable":false,"path":"screens/02-pstack.png","sha256":"8fc2ed63fae8e7dac833ecea75d64ddcad9c3b6a49382e66132c13876b322c41"},{"executable":false,"path":"screens/03-read-skill.png","sha256":"afee8387371e444a6b0c997b45b576c4c6c7e527fca753338583c6e3ce68ddd2"},{"executable":false,"path":"screens/04-reference-tab.png","sha256":"b55ac9ce9e2cd1918e1b8f48a9ed2292b8f50872a2ffb2ce5c70b665c299eaae"},{"executable":false,"path":"screens/05-pick-a-bot.png","sha256":"03a7823afcd85dc6489bd2cb3b572b499cf8999e16e7a2436e1069ca2913607b"},{"executable":false,"path":"screens/06-confirm.png","sha256":"3333936a6af257344f1d3375a7b3dc14b53c1a10012c1340154c810b00c745a3"},{"executable":false,"path":"screens/07-cursor-team-kit.png","sha256":"fa8a42a120d33bfb32a8c7299180eba1dd61d4550228c05aea9038aa59bd3671"},{"executable":false,"path":"screens/08-marketplace.png","sha256":"5267be7f941e853588eee4d27b2083e20c00491a97406020dc7f7334f7619df0"},{"executable":false,"path":"scripts/packed-smoke.mjs","sha256":"ceb8bbf9933c9e35494b8d91f263d95c31324cc48cc5540f7aa4933cdc254697"},{"executable":true,"path":"server.js","sha256":"fc44c043db21f29c743fd074417dc8c357bb0f7491a469b05ae6a37da2dc2ba0"},{"executable":false,"path":"src/commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"executable":false,"path":"src/install-bin.ts","sha256":"766ae5ecdc1331397de9cf32922048d6d9386e270c7e283284a2f95c6953b499"},{"executable":false,"path":"src/skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"},{"executable":false,"path":"test/browser.test.js","sha256":"6c12d42fdb74239c5cc82efc58c3f8602b936c189e7c5d1990bf5b8413cf3ab0"},{"executable":false,"path":"test/data.test.js","sha256":"5b55d8aaa67db2853c7dc7b3bb93cabfe905b83683d3c2e1d156cedcbe802f49"},{"executable":false,"path":"test/helpers.js","sha256":"c32ef9b8c5e7a8817a9a43366212febca54bbf158a14860a27958dcbf78dbdb7"},{"executable":false,"path":"test/install.test.js","sha256":"d3080b2bb3ec577d7cd6adce6f1a5912736a017ce3ded08374b8897485971d4c"},{"executable":false,"path":"test/local-plugins.test.js","sha256":"a04b94b16ebbfb7d2090d0aa3243e758460b3f78a33d3b1ed54b0cd85cb0232f"},{"executable":false,"path":"test/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"test/server.test.js","sha256":"ed64df042f57b09facbd5da93702b0c0b3b691cd478ccade6ea77fce9bdd4e10"},{"executable":false,"path":"tsconfig.json","sha256":"a6599174892162cd37b9b9a046c581b813d933eb3ad267daeeca79d0d48fa9cd"}]},"provenance":[{"path":".cursor-plugin/marketplace.json","sourceInputs":["agent-bundle.config.ts"]},{"path":".cursor-plugin/plugin.json","sourceInputs":["agent-bundle.config.ts","assets/logo.svg","package.json","src/commands/plugin-library.md","src/skills/plugin-library/SKILL.md"]},{"path":"agent-bundle.compile-evidence.json","sourceInputs":["agent-bundle.config.ts"]},{"path":"assets/assets/logo.svg","sourceInputs":["agent-bundle.config.ts","assets/logo.svg"]},{"path":"assets/data/pstack.json","sourceInputs":["data/pstack.json"]},{"path":"assets/data/unified-catalog.json","sourceInputs":["data/unified-catalog.json"]},{"path":"assets/public/app.js","sourceInputs":["public/app.js"]},{"path":"assets/public/index.html","sourceInputs":["public/index.html"]},{"path":"assets/public/markdown.js","sourceInputs":["public/markdown.js"]},{"path":"assets/public/picker.js","sourceInputs":["public/picker.js"]},{"path":"assets/public/styles.css","sourceInputs":["public/styles.css"]},{"path":"commands/plugin-library.md","sourceInputs":["src/commands/plugin-library.md"]},{"path":"INSTALL.md","sourceInputs":["agent-bundle.config.ts"]},{"path":"install.mjs","sourceInputs":["agent-bundle.config.ts"]},{"path":"plugin.json","sourceInputs":["agent-bundle.config.ts","package.json"]},{"path":"scripts/plugin-library-server.mjs","sourceInputs":["agent-bundle.config.ts","lib/bots.js","lib/local-plugins.js","server.js"]},{"path":"scripts/plugin-library.mjs","sourceInputs":["agent-bundle.config.ts","bin/plugin-library.mjs"]},{"path":"skills/plugin-library/SKILL.md","sourceInputs":["src/skills/plugin-library/SKILL.md"]}],"recordVersion":1,"validation":{"artifact":{"status":"passed"},"projections":[{"host":"cursor","status":"passed"},{"host":"portable","status":"passed"}],"source":{"status":"passed"}}},"distribution":{"channels":["local","npm"],"install":{"instructions":"INSTALL.md","script":"install.mjs"},"payloads":[]},"executables":{"bins":[],"hooks":[],"mcpServers":[],"scripts":[{"hosts":["cursor","portable"],"id":"script:plugin-library","mode":"bundle","name":"plugin-library","path":"scripts/plugin-library.mjs"},{"hosts":["cursor","portable"],"id":"script:plugin-library-server","mode":"bundle","name":"plugin-library-server","path":"scripts/plugin-library-server.mjs"}]},"files":[{"bytes":236,"kind":"generated","path":".cursor-plugin/marketplace.json","sha256":"3a7e9c80d1bfdd1a25f0642e5d845ae21e52e4082c5169adff044a9ecce43eaf"},{"bytes":418,"kind":"generated","path":".cursor-plugin/plugin.json","sha256":"5eee1c6d8e738e5246c4f673f838ebef5b45d9c620234fc7332341744d880cbf"},{"bytes":2071,"kind":"generated","path":"agent-bundle.compile-evidence.json","sha256":"eada336633d54da7bf14c73ef13dbcd43844dbd43bed89e349d0f279ce73efe9"},{"bytes":519,"kind":"copy","path":"assets/assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"bytes":23189,"kind":"copy","path":"assets/data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"bytes":181719,"kind":"copy","path":"assets/data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"bytes":19680,"kind":"copy","path":"assets/public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"bytes":2112,"kind":"copy","path":"assets/public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"bytes":7273,"kind":"copy","path":"assets/public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"bytes":8090,"kind":"copy","path":"assets/public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"bytes":18065,"kind":"copy","path":"assets/public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"bytes":953,"kind":"generated","path":"commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"bytes":16134,"kind":"generated","path":"INSTALL.md","sha256":"e37d8d70a103f4bbf0bf862d5f0103ed8b08c41094d7dd403f5863b8acac2d87"},{"bytes":84127,"kind":"generated","path":"install.mjs","sha256":"6bca4b63ecf2cdc2de4cb3dcf516dcf2e7d8f460ae52ec5ee8c5a8955687fe68"},{"bytes":378,"kind":"generated","path":"plugin.json","sha256":"c3d9e182728a589ac3679fc176f0fe0d90c0a9f4fba0857e388fab7d82aba680"},{"bytes":31525,"kind":"bundle","mode":493,"path":"scripts/plugin-library-server.mjs","sha256":"6b2c7548077aad7a68b0dd2716c833eb3597b91cc25f0dd6c24e1cd464326411"},{"bytes":12515,"kind":"bundle","path":"scripts/plugin-library.mjs","sha256":"c295407765af2e4afb8d97bbf6b794191558139b24bcbb4ca950a204f00fd464"},{"bytes":1289,"kind":"copy","path":"skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"}],"manifestVersion":6,"projections":[{"builtInHost":"cursor","documents":{"marketplace":".cursor-plugin/marketplace.json","plugin":".cursor-plugin/plugin.json"},"host":"cursor","marketplace":{"name":"plugin-library-marketplace"}},{"builtInHost":"portable","documents":{"plugin":"plugin.json"},"host":"portable"}],"routes":{"digest":"d3f44eea2211698634fb9e2fc28997db775625774579eb44460cca873ce5c683","events":[],"layouts":[],"providers":[],"scripts":[],"servers":[]},"runtime":{"node":"22.19.0"}} +{"application":{"description":"Browse installed and marketplace plugins, read every skill in full, and hand one to a Grok Bot.","id":"plugin:plugin-library","name":"plugin-library","version":"0.4.0"},"compiler":{"adapters":[{"adapterRevision":"1.13.0","host":"cursor","observedVersion":"2026-08-28","schemas":[{"name":"hooks","revision":"2026-08-28","sha256":"06154b7afa0861df462130b988912b897e7ccf962b8dd20c09193100bcde5d81"},{"name":"marketplace","revision":"2026-08-28","sha256":"1aae96a24c2796419933bc8bfe3a1255394e7199c35740b36325e0ce6dbc253d"},{"name":"mcp","revision":"2026-08-28","sha256":"f3fa4615afefe004c4fbcc09e635d890df0f1ec0cb39540feab72cbd3a31d844"},{"name":"plugin","revision":"2026-08-28","sha256":"a393b758901803fcf5cfe0d77bda8a83e987d32c3377dfce2d9edf445af884ed"}]},{"adapterRevision":"1.10.0","host":"portable","observedVersion":"1.0.0","schemas":[{"name":"mcp","revision":"1.0.0","sha256":"6539175bfcdf43085855183e86da40ea94b166547a72b47ae9a0a390516d3acb"},{"name":"plugin","revision":"1.0.0","sha256":"0a4aad95ce337878ad38802ebf0daa3fde76abe3f65400c86bcbb1ec0b3ab883"}]}],"agentSkills":{"schemaSha256":"6d06b61e423317421de2295ea1fd0c7b4491bfa36c5b7705c28616dfe76d4047","sourceRevision":"69ef37e9424c0a7ea9dd2293b559e43ec8176379","specification":"https://raw.githubusercontent.com/agentskills/agentskills/69ef37e9424c0a7ea9dd2293b559e43ec8176379/docs/specification.mdx"},"producer":{"name":"agent-bundle","version":"0.2.1"},"project":{"configDigest":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003","configPath":"agent-bundle.config.ts","modelDigest":"abe272f372d106e7e2861ec7f687f56eee76e62bf23b2a9a9c16270ac0970298","packageName":"plugin-library","packageVersion":"0.4.0","revision":"36a4173687a475cf1b2fdef79259c9bbc76e3e017284257f79a0cc846dd92e49","sourceInputs":[{"executable":false,"path":".cursor-plugin/marketplace.json","sha256":"7400cf737ae5bf76e7e2dca734ebfc3fa774f2141f03c054e07df5e1789cddff"},{"executable":false,"path":".github/workflows/ci.yml","sha256":"3c51f0bdc639d14d698407abbd7e8a23f6f7330e521b6a78f67e018d72b1a045"},{"executable":false,"path":".github/workflows/publish-agent-bundle.yml","sha256":"16d92e1bd374869feacaea726224c1249871cd1fd3b5940fd30d89c626b2ea2d"},{"executable":false,"path":".gitignore","sha256":"fc7f785227a74db0c56704f6f615d1c2ec716afe1d9df25b74449367f28955c2"},{"executable":false,"path":"agent-bundle.config.ts","sha256":"4d5f82c9c5cbe64f48aae4c31f6f715119344b6dbeddde374660d97ea949e003"},{"executable":false,"path":"assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"executable":true,"path":"bin/plugin-library.mjs","sha256":"0c174e75dadbed3c17f19651f7b764fcd080b83d2ada35523fd51fadd82aef2f"},{"executable":false,"path":"data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"executable":false,"path":"data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"executable":false,"path":"lib/bots.js","sha256":"fb697ad2b8b86f50716c310e74765bbcbdf54a65b48a65e9372c8a597e772be3"},{"executable":false,"path":"lib/local-plugins.js","sha256":"32d134641d78a888fa7e0e2e3f11ebad8d1e300eb2807ac5a93c559644612adb"},{"executable":false,"path":"lib/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"LICENSE","sha256":"ff28847df30260ae031d33a736f0e72210a539b1417570bac88a59016157544c"},{"executable":false,"path":"logs/.gitkeep","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"executable":false,"path":"package-lock.json","sha256":"4619035d7152dae32cdcd226fe2a454a29f6948ed8be8b044661b399018b06d5"},{"executable":false,"path":"package.json","sha256":"06a4e4a5076bc7ed7a407e25d7619b1d97a95fe4cad5dd8627ffe1398601cfe0"},{"executable":false,"path":"public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"executable":false,"path":"public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"executable":false,"path":"public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"executable":false,"path":"public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"executable":false,"path":"public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"executable":false,"path":"README.md","sha256":"6b5162bcd4594a518c4b9fb6184ed12dcfee4a9c44b334f99993d7131b5a0b45"},{"executable":false,"path":"renovate.json","sha256":"e80b4e42a3043bc12fa0640db4bac392d2bf770acf841360d7c8ceeeac2ec1a9"},{"executable":false,"path":"screens/01-library.png","sha256":"f465bcecd299c9413975baa307dd87dc6b8cacbd0498134118df5bac2ef391a5"},{"executable":false,"path":"screens/02-pstack.png","sha256":"8fc2ed63fae8e7dac833ecea75d64ddcad9c3b6a49382e66132c13876b322c41"},{"executable":false,"path":"screens/03-read-skill.png","sha256":"afee8387371e444a6b0c997b45b576c4c6c7e527fca753338583c6e3ce68ddd2"},{"executable":false,"path":"screens/04-reference-tab.png","sha256":"b55ac9ce9e2cd1918e1b8f48a9ed2292b8f50872a2ffb2ce5c70b665c299eaae"},{"executable":false,"path":"screens/05-pick-a-bot.png","sha256":"03a7823afcd85dc6489bd2cb3b572b499cf8999e16e7a2436e1069ca2913607b"},{"executable":false,"path":"screens/06-confirm.png","sha256":"3333936a6af257344f1d3375a7b3dc14b53c1a10012c1340154c810b00c745a3"},{"executable":false,"path":"screens/07-cursor-team-kit.png","sha256":"fa8a42a120d33bfb32a8c7299180eba1dd61d4550228c05aea9038aa59bd3671"},{"executable":false,"path":"screens/08-marketplace.png","sha256":"5267be7f941e853588eee4d27b2083e20c00491a97406020dc7f7334f7619df0"},{"executable":false,"path":"scripts/packed-smoke.mjs","sha256":"ceb8bbf9933c9e35494b8d91f263d95c31324cc48cc5540f7aa4933cdc254697"},{"executable":true,"path":"server.js","sha256":"fc44c043db21f29c743fd074417dc8c357bb0f7491a469b05ae6a37da2dc2ba0"},{"executable":false,"path":"src/commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"executable":false,"path":"src/install-bin.ts","sha256":"766ae5ecdc1331397de9cf32922048d6d9386e270c7e283284a2f95c6953b499"},{"executable":false,"path":"src/skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"},{"executable":false,"path":"test/browser.test.js","sha256":"6c12d42fdb74239c5cc82efc58c3f8602b936c189e7c5d1990bf5b8413cf3ab0"},{"executable":false,"path":"test/data.test.js","sha256":"5b55d8aaa67db2853c7dc7b3bb93cabfe905b83683d3c2e1d156cedcbe802f49"},{"executable":false,"path":"test/helpers.js","sha256":"c32ef9b8c5e7a8817a9a43366212febca54bbf158a14860a27958dcbf78dbdb7"},{"executable":false,"path":"test/install.test.js","sha256":"d3080b2bb3ec577d7cd6adce6f1a5912736a017ce3ded08374b8897485971d4c"},{"executable":false,"path":"test/local-plugins.test.js","sha256":"a04b94b16ebbfb7d2090d0aa3243e758460b3f78a33d3b1ed54b0cd85cb0232f"},{"executable":false,"path":"test/package.json","sha256":"8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a"},{"executable":false,"path":"test/server.test.js","sha256":"ed64df042f57b09facbd5da93702b0c0b3b691cd478ccade6ea77fce9bdd4e10"},{"executable":false,"path":"tsconfig.json","sha256":"a6599174892162cd37b9b9a046c581b813d933eb3ad267daeeca79d0d48fa9cd"}]},"provenance":[{"path":".cursor-plugin/marketplace.json","sourceInputs":["agent-bundle.config.ts"]},{"path":".cursor-plugin/plugin.json","sourceInputs":["agent-bundle.config.ts","assets/logo.svg","package.json","src/commands/plugin-library.md","src/skills/plugin-library/SKILL.md"]},{"path":"agent-bundle.compile-evidence.json","sourceInputs":["agent-bundle.config.ts"]},{"path":"assets/assets/logo.svg","sourceInputs":["agent-bundle.config.ts","assets/logo.svg"]},{"path":"assets/data/pstack.json","sourceInputs":["data/pstack.json"]},{"path":"assets/data/unified-catalog.json","sourceInputs":["data/unified-catalog.json"]},{"path":"assets/public/app.js","sourceInputs":["public/app.js"]},{"path":"assets/public/index.html","sourceInputs":["public/index.html"]},{"path":"assets/public/markdown.js","sourceInputs":["public/markdown.js"]},{"path":"assets/public/picker.js","sourceInputs":["public/picker.js"]},{"path":"assets/public/styles.css","sourceInputs":["public/styles.css"]},{"path":"commands/plugin-library.md","sourceInputs":["src/commands/plugin-library.md"]},{"path":"INSTALL.md","sourceInputs":["agent-bundle.config.ts"]},{"path":"install.mjs","sourceInputs":["agent-bundle.config.ts"]},{"path":"plugin.json","sourceInputs":["agent-bundle.config.ts","package.json"]},{"path":"scripts/plugin-library-server.mjs","sourceInputs":["agent-bundle.config.ts","lib/bots.js","lib/local-plugins.js","server.js"]},{"path":"scripts/plugin-library.mjs","sourceInputs":["agent-bundle.config.ts","bin/plugin-library.mjs"]},{"path":"skills/plugin-library/SKILL.md","sourceInputs":["src/skills/plugin-library/SKILL.md"]}],"recordVersion":1,"validation":{"artifact":{"status":"passed"},"projections":[{"host":"cursor","status":"passed"},{"host":"portable","status":"passed"}],"source":{"status":"passed"}}},"distribution":{"channels":["local","npm"],"install":{"instructions":"INSTALL.md","script":"install.mjs"},"payloads":[]},"executables":{"bins":[],"hooks":[],"mcpServers":[],"scripts":[{"hosts":["cursor","portable"],"id":"script:plugin-library","mode":"bundle","name":"plugin-library","path":"scripts/plugin-library.mjs"},{"hosts":["cursor","portable"],"id":"script:plugin-library-server","mode":"bundle","name":"plugin-library-server","path":"scripts/plugin-library-server.mjs"}]},"files":[{"bytes":236,"kind":"generated","path":".cursor-plugin/marketplace.json","sha256":"3a7e9c80d1bfdd1a25f0642e5d845ae21e52e4082c5169adff044a9ecce43eaf"},{"bytes":418,"kind":"generated","path":".cursor-plugin/plugin.json","sha256":"5eee1c6d8e738e5246c4f673f838ebef5b45d9c620234fc7332341744d880cbf"},{"bytes":2071,"kind":"generated","path":"agent-bundle.compile-evidence.json","sha256":"eada336633d54da7bf14c73ef13dbcd43844dbd43bed89e349d0f279ce73efe9"},{"bytes":519,"kind":"copy","path":"assets/assets/logo.svg","sha256":"0205c0641ea70c40f391990660f1597c40fa0b551ba35017d855593756aa3509"},{"bytes":23189,"kind":"copy","path":"assets/data/pstack.json","sha256":"006aa0f86059fe7a5cff7180cc5704b9afdfb0c372f799047d80db57a0f3ca15"},{"bytes":181719,"kind":"copy","path":"assets/data/unified-catalog.json","sha256":"0827bf86b9bbea2d4a956e5ca347fa00bee55f4a9e594648ba82f2a9dce9388d"},{"bytes":19680,"kind":"copy","path":"assets/public/app.js","sha256":"56bfd74a4e742d9fff1db1524037f6d51e74a4ce62798d9bd3ba410d0c5df824"},{"bytes":2112,"kind":"copy","path":"assets/public/index.html","sha256":"4d8629c982cedea3917b2f3484c2acf7dc62d1f6db998e478689903a0ec470d9"},{"bytes":7273,"kind":"copy","path":"assets/public/markdown.js","sha256":"2253f5e57229badf2d62cda1a40d893a7526de74519ee1ba789761d83bc08afb"},{"bytes":8090,"kind":"copy","path":"assets/public/picker.js","sha256":"7afa3bc1ebdbcdce7c59b8f2a7a15eee936af81eb57476077295c787608a175a"},{"bytes":18065,"kind":"copy","path":"assets/public/styles.css","sha256":"de7576607e641abab0233887a1a47c0dce839b5e9d6b7668d918e156bf81e2e9"},{"bytes":953,"kind":"generated","path":"commands/plugin-library.md","sha256":"177c6216fa3e7bebd59e0d4900666a4b1ed204a04c8da037a52bd80bfdaf8070"},{"bytes":16134,"kind":"generated","path":"INSTALL.md","sha256":"e37d8d70a103f4bbf0bf862d5f0103ed8b08c41094d7dd403f5863b8acac2d87"},{"bytes":84127,"kind":"generated","path":"install.mjs","sha256":"6bca4b63ecf2cdc2de4cb3dcf516dcf2e7d8f460ae52ec5ee8c5a8955687fe68"},{"bytes":378,"kind":"generated","path":"plugin.json","sha256":"c3d9e182728a589ac3679fc176f0fe0d90c0a9f4fba0857e388fab7d82aba680"},{"bytes":31525,"kind":"bundle","mode":493,"path":"scripts/plugin-library-server.mjs","sha256":"6b2c7548077aad7a68b0dd2716c833eb3597b91cc25f0dd6c24e1cd464326411"},{"bytes":12515,"kind":"bundle","path":"scripts/plugin-library.mjs","sha256":"c295407765af2e4afb8d97bbf6b794191558139b24bcbb4ca950a204f00fd464"},{"bytes":1289,"kind":"copy","path":"skills/plugin-library/SKILL.md","sha256":"4389f72f63fcadd4db16db952f02034b04a4dca2758dde5db3cb5df31bc2a0d9"}],"manifestVersion":6,"projections":[{"builtInHost":"cursor","documents":{"marketplace":".cursor-plugin/marketplace.json","plugin":".cursor-plugin/plugin.json"},"host":"cursor","marketplace":{"name":"plugin-library-marketplace"}},{"builtInHost":"portable","documents":{"plugin":"plugin.json"},"host":"portable"}],"routes":{"digest":"d3f44eea2211698634fb9e2fc28997db775625774579eb44460cca873ce5c683","events":[],"layouts":[],"providers":[],"scripts":[],"servers":[]},"runtime":{"node":"22.19.0"}}