From 52b653f27164f7bde1d1e7da57fec42b9f088b50 Mon Sep 17 00:00:00 2001 From: NamesMT Date: Sun, 27 Apr 2025 20:24:07 +0000 Subject: [PATCH 1/8] feat: marketplace install MVP --- package-lock.json | 233 ++++++++++++++++-- package.json | 2 + src/core/webview/marketplaceMessageHandler.ts | 15 ++ src/core/webview/webviewMessageHandler.ts | 1 + .../marketplace/MarketplaceManager.ts | 23 ++ src/services/marketplace/MetadataScanner.ts | 2 + src/services/marketplace/schemas.ts | 2 + src/services/marketplace/types.ts | 4 + src/shared/WebviewMessage.ts | 4 +- .../components/MarketplaceItemActionsMenu.tsx | 62 +++++ .../components/MarketplaceItemCard.tsx | 23 +- .../src/i18n/locales/en/marketplace.json | 1 + 12 files changed, 340 insertions(+), 32 deletions(-) create mode 100644 webview-ui/src/components/marketplace/components/MarketplaceItemActionsMenu.tsx diff --git a/package-lock.json b/package-lock.json index c0c6f89e94..3aafbe16f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "cheerio": "^1.0.0", "chokidar": "^4.0.1", "clone-deep": "^4.0.1", + "config-rocket": "^0.3.15", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", @@ -53,6 +54,7 @@ "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "reconnecting-eventsource": "^1.6.4", + "roo-rocket": "^0.3.6", "say": "^0.16.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", @@ -10546,6 +10548,34 @@ "node": ">= 0.8" } }, + "node_modules/c12": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.0.3.tgz", + "integrity": "sha512-uC3MacKBb0Z15o5QWCHvHWj5Zv34pGQj9P+iXKSpTuSGFS0KKhUWf4t9AJ+gWjYOdmWCPEGpEzm8sS0iqbpo1w==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.4.7", + "exsolve": "^1.0.4", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.1.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -10775,6 +10805,15 @@ "node": ">=8" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/cjs-module-lexer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", @@ -11027,11 +11066,44 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, + "node_modules/config-rocket": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/config-rocket/-/config-rocket-0.3.15.tgz", + "integrity": "sha512-LnVOp3mZYeThh9kGwlb9XaCx0jlNgdacm7S+tjo/E05UzfMu57PHpZCTmF2pA9p7mrMUPSrzrvukBed1D4GU4A==", + "license": "MIT", + "dependencies": { + "c12": "^3.0.3", + "citty": "^0.1.6", + "confbox": "^0.2.2", + "consola": "^3.4.2", + "defu": "^6.1.4", + "fflate": "^0.8.2", + "hookable": "^5.5.3", + "pathe": "^2.0.3", + "std-env": "^3.9.0", + "tinyglobby": "^0.2.13" + }, + "bin": { + "config-rocket": "dist/cli-entry.mjs", + "rocket-zip": "dist/cli-rocket-zip-entry.mjs" + }, + "engines": { + "node": ">=20.13.1" + }, + "funding": { + "url": "https://github.com/sponsors/namesmt" + } + }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" @@ -11568,6 +11640,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, "node_modules/degenerator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", @@ -11609,6 +11687,12 @@ "node": ">= 0.8" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -11769,7 +11853,6 @@ "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -12702,6 +12785,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "license": "MIT" }, + "node_modules/exsolve": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz", + "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==", + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -12880,6 +12969,12 @@ "pend": "~1.2.0" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/figures": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", @@ -13468,6 +13563,23 @@ "node": ">= 14" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -13696,6 +13808,12 @@ "node": ">= 0.4" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -15466,7 +15584,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -16920,6 +17037,12 @@ } } }, + "node_modules/node-fetch-native": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", + "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", + "license": "MIT" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -17230,6 +17353,25 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/nypm": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.0.tgz", + "integrity": "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "pathe": "^2.0.3", + "pkg-types": "^2.0.0", + "tinyexec": "^0.3.2" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -17287,6 +17429,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -17811,6 +17959,12 @@ "node": ">=16" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, "node_modules/pdf-parse": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz", @@ -17836,6 +17990,12 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -17957,6 +18117,17 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", + "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.1", + "exsolve": "^1.0.1", + "pathe": "^2.0.3" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -18417,6 +18588,16 @@ "node": ">=0.10.0" } }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -18790,6 +18971,28 @@ "fsevents": "~2.3.2" } }, + "node_modules/roo-rocket": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/roo-rocket/-/roo-rocket-0.3.6.tgz", + "integrity": "sha512-xB2WYrBoKLVLu5Usi1oZGEzyf1qJSKSIX+reNZH4ZSHStxCZQN1R556s8Q/B5m6owsBcpYrv8qQNqg1VMrfSAg==", + "license": "Apache-2.0", + "dependencies": { + "citty": "^0.1.6", + "confbox": "^0.2.2", + "config-rocket": "^0.3.13", + "consola": "^3.4.2", + "defu": "^6.1.4", + "hookable": "^5.5.3", + "pathe": "^2.0.3", + "std-env": "^3.9.0" + }, + "bin": { + "roo-rocket": "dist/cli-entry.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/namesmt" + } + }, "node_modules/router": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz", @@ -19432,6 +19635,12 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "license": "MIT" + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -20057,17 +20266,15 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", - "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", - "dev": true, + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", "license": "MIT", "dependencies": { - "fdir": "^6.4.3", + "fdir": "^6.4.4", "picomatch": "^4.0.2" }, "engines": { @@ -20078,10 +20285,9 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", - "dev": true, + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -20096,7 +20302,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" diff --git a/package.json b/package.json index 1efbdeb357..86564a68ea 100644 --- a/package.json +++ b/package.json @@ -447,6 +447,7 @@ "cheerio": "^1.0.0", "chokidar": "^4.0.1", "clone-deep": "^4.0.1", + "config-rocket": "^0.3.15", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", @@ -473,6 +474,7 @@ "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "reconnecting-eventsource": "^1.6.4", + "roo-rocket": "^0.3.6", "say": "^0.16.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", diff --git a/src/core/webview/marketplaceMessageHandler.ts b/src/core/webview/marketplaceMessageHandler.ts index 5b389941ef..599069c754 100644 --- a/src/core/webview/marketplaceMessageHandler.ts +++ b/src/core/webview/marketplaceMessageHandler.ts @@ -217,6 +217,21 @@ export async function handleMarketplaceMessages( return true } + case "installMarketplaceItem": { + if (message.marketplaceItem) { + try { + await marketplaceManager.installMarketplaceItem(message.marketplaceItem) + } catch (error) { + vscode.window.showErrorMessage( + `Failed to open URL: ${error instanceof Error ? error.message : String(error)}`, + ) + } + } else { + console.error("Marketplace: installMarketplaceItem called without `marketplaceItem`") + } + return true + } + case "refreshMarketplaceSource": { if (message.url) { try { diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 4474607b8a..b4523f295f 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1282,6 +1282,7 @@ export const webviewMessageHandler = async ( marketplaceManager && (message.type === "marketplaceSources" || message.type === "openExternal" || + message.type === "installMarketplaceItem" || message.type === "refreshMarketplaceSource" || message.type === "filterMarketplaceItems") ) { diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index 8ff476e81e..897e7c6e47 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -564,4 +564,27 @@ export class MarketplaceManager { } } } + + async installMarketplaceItem(item: MarketplaceItem) { + if (!vscode.workspace.workspaceFolders?.length) + return vscode.window.showErrorMessage("Cannot load current workspace folder") + + if (!item.binaryUrl || !item.binaryHash) + return vscode.window.showErrorMessage("Item does not have a binary URL or hash") + + const workspacePath = vscode.workspace.workspaceFolders[0].uri.fsPath + const { hookable } = await import("roo-rocket") + const { unpackFromUrl } = await import("config-rocket/cli") + + vscode.window.showInformationMessage(`Installing item: "${item.name}"`) + await unpackFromUrl(item.binaryUrl, { + hookable, + nonAssemblyBehavior: true, + sha256: item.binaryHash, + cwd: workspacePath + }) + vscode.window.showInformationMessage(`Item "${item.name}" installed successfully`) + + return true + } } diff --git a/src/services/marketplace/MetadataScanner.ts b/src/services/marketplace/MetadataScanner.ts index af2495efcd..241bdd0746 100644 --- a/src/services/marketplace/MetadataScanner.ts +++ b/src/services/marketplace/MetadataScanner.ts @@ -253,6 +253,8 @@ export class MetadataScanner { description: metadata.description, type: metadata.type, version: metadata.version, + binaryUrl: metadata.binaryUrl, + binaryHash: metadata.binaryHash, tags: metadata.tags, url: `${repoUrl}/tree/main/${urlPath}`, repoUrl, diff --git a/src/services/marketplace/schemas.ts b/src/services/marketplace/schemas.ts index 86763cbfe3..df70da0bc6 100644 --- a/src/services/marketplace/schemas.ts +++ b/src/services/marketplace/schemas.ts @@ -8,6 +8,8 @@ export const baseMetadataSchema = z.object({ name: z.string().min(1, "Name is required"), description: z.string(), version: z.string().regex(/^\d+\.\d+\.\d+$/, "Version must be in semver format (e.g., 1.0.0)"), + binaryUrl: z.string().url("Binary URL must be a valid URL").optional(), + binaryHash: z.string().optional(), tags: z.array(z.string()).optional(), author: z.string().optional(), authorUrl: z.string().url("Author URL must be a valid URL").optional(), diff --git a/src/services/marketplace/types.ts b/src/services/marketplace/types.ts index 2827b6bb38..bfcde18d36 100644 --- a/src/services/marketplace/types.ts +++ b/src/services/marketplace/types.ts @@ -24,6 +24,8 @@ export interface BaseMetadata { name: string description: string version: string + binaryUrl?: string + binaryHash?: string tags?: string[] author?: string authorUrl?: string @@ -78,6 +80,8 @@ export interface MarketplaceItem { authorUrl?: string tags?: string[] version?: string + binaryUrl?: string + binaryHash?: string lastUpdated?: string sourceUrl?: string defaultBranch?: string diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 7ab688439e..ab1fb195e3 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -2,7 +2,7 @@ import { z } from "zod" import { ApiConfiguration } from "./api" import { Mode, PromptComponent, ModeConfig } from "./modes" -import { MarketplaceSource } from "../services/marketplace/types" +import { MarketplaceItem, MarketplaceSource } from "../services/marketplace/types" export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse" @@ -129,6 +129,7 @@ export interface WebviewMessage { | "fetchMarketplaceItems" | "filterMarketplaceItems" | "marketplaceButtonClicked" + | "installMarketplaceItem" | "refreshMarketplaceSource" | "repositoryRefreshComplete" | "openExternal" @@ -161,6 +162,7 @@ export interface WebviewMessage { sources?: MarketplaceSource[] filters?: { type?: string; search?: string; tags?: string[] } url?: string // For openExternal + marketplaceItem?: MarketplaceItem hasSystemPromptOverride?: boolean historyPreviewCollapsed?: boolean } diff --git a/webview-ui/src/components/marketplace/components/MarketplaceItemActionsMenu.tsx b/webview-ui/src/components/marketplace/components/MarketplaceItemActionsMenu.tsx new file mode 100644 index 0000000000..cf4c298520 --- /dev/null +++ b/webview-ui/src/components/marketplace/components/MarketplaceItemActionsMenu.tsx @@ -0,0 +1,62 @@ +import React from "react" +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { MoreVertical, ExternalLink, Download } from "lucide-react" +import { MarketplaceItem } from "../../../../../src/services/marketplace/types" +import { vscode } from "@/utils/vscode" +import { useAppTranslation } from "@/i18n/TranslationContext" + +interface MarketplaceItemActionsMenuProps { + item: MarketplaceItem + handleOpenSourceUrl: () => void +} + +export const MarketplaceItemActionsMenu: React.FC = ({ + item, + handleOpenSourceUrl, +}) => { + const { t } = useAppTranslation() + + const handleInstall = () => { + vscode.postMessage({ + type: "installMarketplaceItem", + marketplaceItem: item + }) + } + + // Don't show for `package` items for now + const showInstallButton = item.type !== "package" // Don't show for package containers + + return ( + + + + + + {/* View Source / External Link Item */} + + + {t("marketplace:items.card.viewSource")} + + + {/* Install Item */} + {showInstallButton && ( + + + {t("marketplace:items.card.install")} + + )} + + + ) +} diff --git a/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx b/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx index b7d2c20f09..4c467499af 100644 --- a/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx +++ b/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx @@ -1,5 +1,4 @@ import React, { useMemo, useCallback } from "react" -import { Button } from "@/components/ui/button" import { MarketplaceItem } from "../../../../../src/services/marketplace/types" import { vscode } from "@/utils/vscode" import { groupItemsByType, GroupedItems } from "../utils/grouping" @@ -7,6 +6,7 @@ import { ExpandableSection } from "./ExpandableSection" import { TypeGroup } from "./TypeGroup" import { ViewState } from "../MarketplaceViewStateManager" import { useAppTranslation } from "@/i18n/TranslationContext" +import { MarketplaceItemActionsMenu } from "./MarketplaceItemActionsMenu" interface MarketplaceItemCardProps { item: MarketplaceItem @@ -64,7 +64,7 @@ export const MarketplaceItemCard: React.FC = ({ }, [item.type]) // Memoize URL calculation - const urlToOpen = useMemo(() => { + const itemSourceUrl = useMemo(() => { if (item.sourceUrl && isValidUrl(item.sourceUrl)) { return item.sourceUrl } @@ -80,12 +80,12 @@ export const MarketplaceItemCard: React.FC = ({ return url }, [item.sourceUrl, item.repoUrl, item.defaultBranch, item.path]) - const handleOpenUrl = useCallback(() => { + const handleOpenSourceUrl = useCallback(() => { vscode.postMessage({ type: "openExternal", - url: urlToOpen, + url: itemSourceUrl, }) - }, [urlToOpen]) + }, [itemSourceUrl]) // Group items by type const groupedItems = useMemo(() => { @@ -194,18 +194,7 @@ export const MarketplaceItemCard: React.FC = ({ )} - + {item.type === "package" && ( diff --git a/webview-ui/src/i18n/locales/en/marketplace.json b/webview-ui/src/i18n/locales/en/marketplace.json index dd5c7a6848..3c69e2f69b 100644 --- a/webview-ui/src/i18n/locales/en/marketplace.json +++ b/webview-ui/src/i18n/locales/en/marketplace.json @@ -55,6 +55,7 @@ "card": { "by": "by {{author}}", "from": "from {{source}}", + "install": "Install", "viewSource": "View", "viewOnSource": "View on {{source}}" } From 04d7360da972b6f52744eb8c19064f968c5f9952 Mon Sep 17 00:00:00 2001 From: NamesMT Date: Sun, 27 Apr 2025 20:41:07 +0000 Subject: [PATCH 2/8] fix: correct error message --- src/core/webview/marketplaceMessageHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/webview/marketplaceMessageHandler.ts b/src/core/webview/marketplaceMessageHandler.ts index 599069c754..be73b59945 100644 --- a/src/core/webview/marketplaceMessageHandler.ts +++ b/src/core/webview/marketplaceMessageHandler.ts @@ -223,7 +223,7 @@ export async function handleMarketplaceMessages( await marketplaceManager.installMarketplaceItem(message.marketplaceItem) } catch (error) { vscode.window.showErrorMessage( - `Failed to open URL: ${error instanceof Error ? error.message : String(error)}`, + `Failed to install item "${message.marketplaceItem.name}": ${error instanceof Error ? error.message : String(error)}`, ) } } else { From 74a0c44d59259cede9a29e85125d514ba0c7e257 Mon Sep 17 00:00:00 2001 From: NamesMT Date: Mon, 28 Apr 2025 06:57:39 +0000 Subject: [PATCH 3/8] feat: marketplace supports global install --- package-lock.json | 25 ++++++---- package.json | 4 +- src/core/webview/marketplaceMessageHandler.ts | 8 ++-- .../marketplace/MarketplaceManager.ts | 47 +++++++++++++++++-- src/services/marketplace/types.ts | 9 ++++ src/shared/WebviewMessage.ts | 5 +- .../components/MarketplaceItemActionsMenu.tsx | 21 ++++++--- .../src/i18n/locales/en/marketplace.json | 3 +- 8 files changed, 93 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3aafbe16f6..df6fa47c2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "cheerio": "^1.0.0", "chokidar": "^4.0.1", "clone-deep": "^4.0.1", - "config-rocket": "^0.3.15", + "config-rocket": "^0.3.18", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", @@ -54,7 +54,7 @@ "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "reconnecting-eventsource": "^1.6.4", - "roo-rocket": "^0.3.6", + "roo-rocket": "^0.3.7", "say": "^0.16.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", @@ -11073,9 +11073,9 @@ "license": "MIT" }, "node_modules/config-rocket": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/config-rocket/-/config-rocket-0.3.15.tgz", - "integrity": "sha512-LnVOp3mZYeThh9kGwlb9XaCx0jlNgdacm7S+tjo/E05UzfMu57PHpZCTmF2pA9p7mrMUPSrzrvukBed1D4GU4A==", + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/config-rocket/-/config-rocket-0.3.18.tgz", + "integrity": "sha512-jY5EDbI4q0/Qz8Lm4p8JPFw/jJk3Kcg7n+ZZQUNEMcWpbDjw9DIT5K6G1ypuBGycUsXfhDXvycoVkNDEierVDQ==", "license": "MIT", "dependencies": { "c12": "^3.0.3", @@ -18972,14 +18972,13 @@ } }, "node_modules/roo-rocket": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/roo-rocket/-/roo-rocket-0.3.6.tgz", - "integrity": "sha512-xB2WYrBoKLVLu5Usi1oZGEzyf1qJSKSIX+reNZH4ZSHStxCZQN1R556s8Q/B5m6owsBcpYrv8qQNqg1VMrfSAg==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/roo-rocket/-/roo-rocket-0.3.7.tgz", + "integrity": "sha512-DvCHydroTqrJ4q0n8zrhTEcFlHIkJLIVePko30OZvIr5XsPPGbmdPKMPMWiv+s3I91/Oy70VHzTSJUxK4nyVxw==", "license": "Apache-2.0", "dependencies": { "citty": "^0.1.6", "confbox": "^0.2.2", - "config-rocket": "^0.3.13", "consola": "^3.4.2", "defu": "^6.1.4", "hookable": "^5.5.3", @@ -18991,6 +18990,14 @@ }, "funding": { "url": "https://github.com/sponsors/namesmt" + }, + "peerDependencies": { + "config-rocket": "^0.3.16" + }, + "peerDependenciesMeta": { + "config-rocket": { + "optional": false + } } }, "node_modules/router": { diff --git a/package.json b/package.json index 86564a68ea..acd0eb70d0 100644 --- a/package.json +++ b/package.json @@ -447,7 +447,7 @@ "cheerio": "^1.0.0", "chokidar": "^4.0.1", "clone-deep": "^4.0.1", - "config-rocket": "^0.3.15", + "config-rocket": "^0.3.18", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", @@ -474,7 +474,7 @@ "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "reconnecting-eventsource": "^1.6.4", - "roo-rocket": "^0.3.6", + "roo-rocket": "^0.3.7", "say": "^0.16.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", diff --git a/src/core/webview/marketplaceMessageHandler.ts b/src/core/webview/marketplaceMessageHandler.ts index be73b59945..17ec326058 100644 --- a/src/core/webview/marketplaceMessageHandler.ts +++ b/src/core/webview/marketplaceMessageHandler.ts @@ -218,16 +218,16 @@ export async function handleMarketplaceMessages( } case "installMarketplaceItem": { - if (message.marketplaceItem) { + if (message.mpItem) { try { - await marketplaceManager.installMarketplaceItem(message.marketplaceItem) + await marketplaceManager.installMarketplaceItem(message.mpItem, message.mpInstallOptions) } catch (error) { vscode.window.showErrorMessage( - `Failed to install item "${message.marketplaceItem.name}": ${error instanceof Error ? error.message : String(error)}`, + `Failed to install item "${message.mpItem.name}":\n${error instanceof Error ? error.message : String(error)}`, ) } } else { - console.error("Marketplace: installMarketplaceItem called without `marketplaceItem`") + console.error("Marketplace: installMarketplaceItem called without `mpItem`") } return true } diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index 897e7c6e47..bdbcaff6a9 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -9,9 +9,11 @@ import { ComponentType, ComponentMetadata, LocalizationOptions, + InstallMarketplaceItemOptions, } from "./types" import { validateSource, validateSources } from "../../shared/MarketplaceValidation" import { getUserLocale } from "./utils" +import { GlobalFileNames } from "src/shared/globalFileNames" /** * Service for managing marketplace data @@ -565,26 +567,61 @@ export class MarketplaceManager { } } - async installMarketplaceItem(item: MarketplaceItem) { + async installMarketplaceItem(item: MarketplaceItem, options?: InstallMarketplaceItemOptions) { + const { + target = 'project' + } = options || {} + if (!vscode.workspace.workspaceFolders?.length) return vscode.window.showErrorMessage("Cannot load current workspace folder") if (!item.binaryUrl || !item.binaryHash) return vscode.window.showErrorMessage("Item does not have a binary URL or hash") - const workspacePath = vscode.workspace.workspaceFolders[0].uri.fsPath - const { hookable } = await import("roo-rocket") + const cwd = target === 'global' + ? await this.ensureSettingsDirectoryExists() + : vscode.workspace.workspaceFolders[0].uri.fsPath + const { createHookable } = await import("roo-rocket") const { unpackFromUrl } = await import("config-rocket/cli") + // Create a custom hookable instance to support global installations + // Currently, we only supports `.roomodes` and `.roo/mcp.json` files for global installation. + const customHookable = createHookable() + customHookable.hook('onExtract', ({ unzipped }) => { + const allowedFiles = new Set(['.roo/mcp.json', '.roomodes']) + console.log({unzipped}) + for (const key in unzipped) { + if (!allowedFiles.has(key)) + throw new Error('Unsupported file for global installation: ' + key) + } + }) + customHookable.hook('onFileOutput', (state) => { + if (target === 'global') { + if (state.filePath.endsWith('/.roo/mcp.json')) + state.filePath = state.filePath.replace('.roo/mcp.json', GlobalFileNames.mcpSettings) + else if (state.filePath.endsWith('/.roomodes')) + state.filePath = state.filePath.replace('.roomodes', GlobalFileNames.customModes) + } + }) + vscode.window.showInformationMessage(`Installing item: "${item.name}"`) await unpackFromUrl(item.binaryUrl, { - hookable, + hookable: customHookable, nonAssemblyBehavior: true, sha256: item.binaryHash, - cwd: workspacePath + cwd }) vscode.window.showInformationMessage(`Item "${item.name}" installed successfully`) return true } + + /** + * Copied from `src/core/config/CustomModesManager.ts`, if in the future we add ClineProvider ref to this class, we can remove this and use the one from there. + */ + private async ensureSettingsDirectoryExists(): Promise { + const settingsDir = path.join(this.context.globalStorageUri.fsPath, "settings") + await fs.mkdir(settingsDir, { recursive: true }) + return settingsDir + } } diff --git a/src/services/marketplace/types.ts b/src/services/marketplace/types.ts index bfcde18d36..eab0b6caa8 100644 --- a/src/services/marketplace/types.ts +++ b/src/services/marketplace/types.ts @@ -130,3 +130,12 @@ export interface LocalizationOptions { userLocale: string fallbackLocale: string } + +export interface InstallMarketplaceItemOptions { + /** + * Specify the installation target + * + * @default 'project' + */ + target?: 'global' | 'project' +} diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index ab1fb195e3..57c6372cff 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -2,7 +2,7 @@ import { z } from "zod" import { ApiConfiguration } from "./api" import { Mode, PromptComponent, ModeConfig } from "./modes" -import { MarketplaceItem, MarketplaceSource } from "../services/marketplace/types" +import { InstallMarketplaceItemOptions, MarketplaceItem, MarketplaceSource } from "../services/marketplace/types" export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse" @@ -162,7 +162,8 @@ export interface WebviewMessage { sources?: MarketplaceSource[] filters?: { type?: string; search?: string; tags?: string[] } url?: string // For openExternal - marketplaceItem?: MarketplaceItem + mpItem?: MarketplaceItem + mpInstallOptions?: InstallMarketplaceItemOptions hasSystemPromptOverride?: boolean historyPreviewCollapsed?: boolean } diff --git a/webview-ui/src/components/marketplace/components/MarketplaceItemActionsMenu.tsx b/webview-ui/src/components/marketplace/components/MarketplaceItemActionsMenu.tsx index cf4c298520..c8b0708728 100644 --- a/webview-ui/src/components/marketplace/components/MarketplaceItemActionsMenu.tsx +++ b/webview-ui/src/components/marketplace/components/MarketplaceItemActionsMenu.tsx @@ -7,7 +7,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { MoreVertical, ExternalLink, Download } from "lucide-react" -import { MarketplaceItem } from "../../../../../src/services/marketplace/types" +import { InstallMarketplaceItemOptions, MarketplaceItem } from "../../../../../src/services/marketplace/types" import { vscode } from "@/utils/vscode" import { useAppTranslation } from "@/i18n/TranslationContext" @@ -22,10 +22,11 @@ export const MarketplaceItemActionsMenu: React.FC { const { t } = useAppTranslation() - const handleInstall = () => { + const handleInstall = (options?: InstallMarketplaceItemOptions) => { vscode.postMessage({ type: "installMarketplaceItem", - marketplaceItem: item + mpItem: item, + mpInstallOptions: options }) } @@ -49,11 +50,19 @@ export const MarketplaceItemActionsMenu: React.FC{t("marketplace:items.card.viewSource")} - {/* Install Item */} + {/* Install (Project) */} {showInstallButton && ( - + handleInstall({target: 'project'})}> - {t("marketplace:items.card.install")} + {t("marketplace:items.card.installProject")} + + )} + + {/* Install (Global) */} + {showInstallButton && ( + handleInstall({target: 'global'})}> + + {t("marketplace:items.card.installGlobal")} )} diff --git a/webview-ui/src/i18n/locales/en/marketplace.json b/webview-ui/src/i18n/locales/en/marketplace.json index 3c69e2f69b..deabe9b44a 100644 --- a/webview-ui/src/i18n/locales/en/marketplace.json +++ b/webview-ui/src/i18n/locales/en/marketplace.json @@ -55,7 +55,8 @@ "card": { "by": "by {{author}}", "from": "from {{source}}", - "install": "Install", + "installProject": "Install (Project)", + "installGlobal": "Install (Global)", "viewSource": "View", "viewOnSource": "View on {{source}}" } From 26be7b3ac79d7d16f8cb8fb4ac05e5469e6f6a0c Mon Sep 17 00:00:00 2001 From: NamesMT Date: Mon, 28 Apr 2025 07:54:10 +0000 Subject: [PATCH 4/8] fix: should not require workspace folder for `global` install (+ formatting) --- src/services/marketplace/MarketplaceManager.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index bdbcaff6a9..09356db713 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -572,24 +572,24 @@ export class MarketplaceManager { target = 'project' } = options || {} - if (!vscode.workspace.workspaceFolders?.length) + if (target === 'project' && !vscode.workspace.workspaceFolders?.length) return vscode.window.showErrorMessage("Cannot load current workspace folder") + const cwd = target === 'project' + ? vscode.workspace.workspaceFolders![0].uri.fsPath + : await this.ensureSettingsDirectoryExists() + const { createHookable } = await import("roo-rocket") + const { unpackFromUrl } = await import("config-rocket/cli") + if (!item.binaryUrl || !item.binaryHash) return vscode.window.showErrorMessage("Item does not have a binary URL or hash") - const cwd = target === 'global' - ? await this.ensureSettingsDirectoryExists() - : vscode.workspace.workspaceFolders[0].uri.fsPath - const { createHookable } = await import("roo-rocket") - const { unpackFromUrl } = await import("config-rocket/cli") - // Create a custom hookable instance to support global installations // Currently, we only supports `.roomodes` and `.roo/mcp.json` files for global installation. const customHookable = createHookable() customHookable.hook('onExtract', ({ unzipped }) => { const allowedFiles = new Set(['.roo/mcp.json', '.roomodes']) - console.log({unzipped}) + console.log({ unzipped }) for (const key in unzipped) { if (!allowedFiles.has(key)) throw new Error('Unsupported file for global installation: ' + key) From 3539550b0c78e8554e7ec483072912c0eab3d984 Mon Sep 17 00:00:00 2001 From: NamesMT Date: Mon, 28 Apr 2025 10:52:04 +0000 Subject: [PATCH 5/8] feat: delegate the hooks declaration to `roo-rocket`, prepare for CLI feat --- package-lock.json | 18 +++++------ package.json | 4 +-- .../marketplace/MarketplaceManager.ts | 32 +++++++++---------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index df6fa47c2f..7205f2c5f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "cheerio": "^1.0.0", "chokidar": "^4.0.1", "clone-deep": "^4.0.1", - "config-rocket": "^0.3.18", + "config-rocket": "^0.3.19", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", @@ -54,7 +54,7 @@ "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "reconnecting-eventsource": "^1.6.4", - "roo-rocket": "^0.3.7", + "roo-rocket": "^0.3.9", "say": "^0.16.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", @@ -11073,9 +11073,9 @@ "license": "MIT" }, "node_modules/config-rocket": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/config-rocket/-/config-rocket-0.3.18.tgz", - "integrity": "sha512-jY5EDbI4q0/Qz8Lm4p8JPFw/jJk3Kcg7n+ZZQUNEMcWpbDjw9DIT5K6G1ypuBGycUsXfhDXvycoVkNDEierVDQ==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/config-rocket/-/config-rocket-0.3.19.tgz", + "integrity": "sha512-6VwCdqHH5G/Q4D5byHiZsFbF3Dp2RYYNrA3RPreYYTmxvkuaGYy+KcWm7yfdwwoyVPFkuIeN5LTOHJjGMtW+rw==", "license": "MIT", "dependencies": { "c12": "^3.0.3", @@ -18972,9 +18972,9 @@ } }, "node_modules/roo-rocket": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/roo-rocket/-/roo-rocket-0.3.7.tgz", - "integrity": "sha512-DvCHydroTqrJ4q0n8zrhTEcFlHIkJLIVePko30OZvIr5XsPPGbmdPKMPMWiv+s3I91/Oy70VHzTSJUxK4nyVxw==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/roo-rocket/-/roo-rocket-0.3.9.tgz", + "integrity": "sha512-w5rKihu6lIkq2fIFYAyhKzZYqSnaVWKvlebNT8uKekyeizfaU89FHzow6TB+Gah57mdaZyulcylKl1hmNRzjXg==", "license": "Apache-2.0", "dependencies": { "citty": "^0.1.6", @@ -18992,7 +18992,7 @@ "url": "https://github.com/sponsors/namesmt" }, "peerDependencies": { - "config-rocket": "^0.3.16" + "config-rocket": "^0.3.19" }, "peerDependenciesMeta": { "config-rocket": { diff --git a/package.json b/package.json index acd0eb70d0..88da6e6f34 100644 --- a/package.json +++ b/package.json @@ -447,7 +447,7 @@ "cheerio": "^1.0.0", "chokidar": "^4.0.1", "clone-deep": "^4.0.1", - "config-rocket": "^0.3.18", + "config-rocket": "^0.3.19", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", @@ -474,7 +474,7 @@ "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "reconnecting-eventsource": "^1.6.4", - "roo-rocket": "^0.3.7", + "roo-rocket": "^0.3.9", "say": "^0.16.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index 09356db713..dfb9e0e5b7 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -14,6 +14,7 @@ import { import { validateSource, validateSources } from "../../shared/MarketplaceValidation" import { getUserLocale } from "./utils" import { GlobalFileNames } from "src/shared/globalFileNames" +import { assertsMpContext, MarketplaceContext, registerMarketplaceHooks } from "roo-rocket" /** * Service for managing marketplace data @@ -584,25 +585,22 @@ export class MarketplaceManager { if (!item.binaryUrl || !item.binaryHash) return vscode.window.showErrorMessage("Item does not have a binary URL or hash") + // Creates `mpContext` to delegate context to `roo-rocket` + const mpContext = (target === 'project' + ? { target } + : { + target, + globalFileNames: { + mcp: GlobalFileNames.mcpSettings, + mode: GlobalFileNames.customModes, + } + } + ) satisfies MarketplaceContext + assertsMpContext(mpContext) + // Create a custom hookable instance to support global installations - // Currently, we only supports `.roomodes` and `.roo/mcp.json` files for global installation. const customHookable = createHookable() - customHookable.hook('onExtract', ({ unzipped }) => { - const allowedFiles = new Set(['.roo/mcp.json', '.roomodes']) - console.log({ unzipped }) - for (const key in unzipped) { - if (!allowedFiles.has(key)) - throw new Error('Unsupported file for global installation: ' + key) - } - }) - customHookable.hook('onFileOutput', (state) => { - if (target === 'global') { - if (state.filePath.endsWith('/.roo/mcp.json')) - state.filePath = state.filePath.replace('.roo/mcp.json', GlobalFileNames.mcpSettings) - else if (state.filePath.endsWith('/.roomodes')) - state.filePath = state.filePath.replace('.roomodes', GlobalFileNames.customModes) - } - }) + registerMarketplaceHooks(customHookable, mpContext) vscode.window.showInformationMessage(`Installing item: "${item.name}"`) await unpackFromUrl(item.binaryUrl, { From 3ac4bf61f2dfa492131e87cb9b4842185c925685 Mon Sep 17 00:00:00 2001 From: NamesMT Date: Mon, 28 Apr 2025 12:16:35 +0000 Subject: [PATCH 6/8] feat: binary configurable pack detection, auto-select installation method --- package-lock.json | 8 ++-- package.json | 2 +- .../marketplace/MarketplaceManager.ts | 45 ++++++++++++++----- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7205f2c5f8..4b3c557da0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "cheerio": "^1.0.0", "chokidar": "^4.0.1", "clone-deep": "^4.0.1", - "config-rocket": "^0.3.19", + "config-rocket": "^0.3.20", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", @@ -11073,9 +11073,9 @@ "license": "MIT" }, "node_modules/config-rocket": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/config-rocket/-/config-rocket-0.3.19.tgz", - "integrity": "sha512-6VwCdqHH5G/Q4D5byHiZsFbF3Dp2RYYNrA3RPreYYTmxvkuaGYy+KcWm7yfdwwoyVPFkuIeN5LTOHJjGMtW+rw==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/config-rocket/-/config-rocket-0.3.20.tgz", + "integrity": "sha512-Zim3CGz4Psu2OvLU3U7dGhtj+Nzr+fAh51S2/YUO6vAjQUQa1Ll06+I53W1xMQFI4TcLBYKSpJV/jy+YfUZnHg==", "license": "MIT", "dependencies": { "c12": "^3.0.3", diff --git a/package.json b/package.json index 88da6e6f34..ee8b38ee37 100644 --- a/package.json +++ b/package.json @@ -447,7 +447,7 @@ "cheerio": "^1.0.0", "chokidar": "^4.0.1", "clone-deep": "^4.0.1", - "config-rocket": "^0.3.19", + "config-rocket": "^0.3.20", "default-shell": "^2.2.0", "delay": "^6.0.0", "diff": "^5.2.0", diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index dfb9e0e5b7..c360fe97a6 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -15,6 +15,8 @@ import { validateSource, validateSources } from "../../shared/MarketplaceValidat import { getUserLocale } from "./utils" import { GlobalFileNames } from "src/shared/globalFileNames" import { assertsMpContext, MarketplaceContext, registerMarketplaceHooks } from "roo-rocket" +import { uint8IsConfigPackWithParameters } from 'config-rocket' +import { unpackFromUint8 } from "config-rocket/cli" /** * Service for managing marketplace data @@ -573,6 +575,8 @@ export class MarketplaceManager { target = 'project' } = options || {} + vscode.window.showInformationMessage(`Installing item: "${item.name}"`) + if (target === 'project' && !vscode.workspace.workspaceFolders?.length) return vscode.window.showErrorMessage("Cannot load current workspace folder") @@ -598,18 +602,29 @@ export class MarketplaceManager { ) satisfies MarketplaceContext assertsMpContext(mpContext) - // Create a custom hookable instance to support global installations - const customHookable = createHookable() - registerMarketplaceHooks(customHookable, mpContext) + const binaryUint8 = await fetchBinary(item.binaryUrl) + + // Install via CLI if binary is a configurable pack. + if (await uint8IsConfigPackWithParameters(binaryUint8)) { + vscode.window.showInformationMessage(`"${item.name}" is a configurable pack, invoking CLI to install...`) + vscode.window.showInformationMessage(`*CLI install WIP*`) + } + // Fast install for non-configurable packs. + else { + // Create a custom hookable instance to support global installations + const customHookable = createHookable() + registerMarketplaceHooks(customHookable, mpContext) + + vscode.window.showInformationMessage(`"${item.name}" is non-configurable, fast install...`) + await unpackFromUint8(binaryUint8, { + hookable: customHookable, + nonAssemblyBehavior: true, + sha256: item.binaryHash, + cwd + }) + vscode.window.showInformationMessage(`"${item.name}" installed successfully`) + } - vscode.window.showInformationMessage(`Installing item: "${item.name}"`) - await unpackFromUrl(item.binaryUrl, { - hookable: customHookable, - nonAssemblyBehavior: true, - sha256: item.binaryHash, - cwd - }) - vscode.window.showInformationMessage(`Item "${item.name}" installed successfully`) return true } @@ -623,3 +638,11 @@ export class MarketplaceManager { return settingsDir } } + +async function fetchBinary(url: string) { + const res = await fetch(url) + if (!res.ok) + throw new Error(`Failed to download binary from ${url}`) + + return new Uint8Array(await res.arrayBuffer()) +} From 086523655babb910c5ea9cb2acdf3d7786ab9fd2 Mon Sep 17 00:00:00 2001 From: NamesMT Date: Mon, 28 Apr 2025 14:48:52 +0000 Subject: [PATCH 7/8] feat: working CLI interactive install mode --- package-lock.json | 8 ++-- package.json | 2 +- .../marketplace/MarketplaceManager.ts | 40 ++++++++++++++++--- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b3c557da0..7adc2a2837 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "reconnecting-eventsource": "^1.6.4", - "roo-rocket": "^0.3.9", + "roo-rocket": "^0.3.12", "say": "^0.16.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", @@ -18972,9 +18972,9 @@ } }, "node_modules/roo-rocket": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/roo-rocket/-/roo-rocket-0.3.9.tgz", - "integrity": "sha512-w5rKihu6lIkq2fIFYAyhKzZYqSnaVWKvlebNT8uKekyeizfaU89FHzow6TB+Gah57mdaZyulcylKl1hmNRzjXg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/roo-rocket/-/roo-rocket-0.3.12.tgz", + "integrity": "sha512-oy9pXRcys5VS3OhtzScve97UJkQ00bjacPpVJQ8GeIlDsMGfQl1ega2X95/rRyWFGsFT1jr/ri4q4AUs2BNzOg==", "license": "Apache-2.0", "dependencies": { "citty": "^0.1.6", diff --git a/package.json b/package.json index ee8b38ee37..a7a58bdfa4 100644 --- a/package.json +++ b/package.json @@ -474,7 +474,7 @@ "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "reconnecting-eventsource": "^1.6.4", - "roo-rocket": "^0.3.9", + "roo-rocket": "^0.3.13", "say": "^0.16.0", "serialize-error": "^11.0.3", "simple-git": "^3.27.0", diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index c360fe97a6..902347ab3c 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -14,9 +14,10 @@ import { import { validateSource, validateSources } from "../../shared/MarketplaceValidation" import { getUserLocale } from "./utils" import { GlobalFileNames } from "src/shared/globalFileNames" -import { assertsMpContext, MarketplaceContext, registerMarketplaceHooks } from "roo-rocket" -import { uint8IsConfigPackWithParameters } from 'config-rocket' +import { TerminalRegistry } from "src/integrations/terminal/TerminalRegistry" +import { assertsMpContext, createHookable, MarketplaceContext, registerMarketplaceHooks } from "roo-rocket" import { unpackFromUint8 } from "config-rocket/cli" +import { uint8IsConfigPackWithParameters } from 'config-rocket' /** * Service for managing marketplace data @@ -583,8 +584,6 @@ export class MarketplaceManager { const cwd = target === 'project' ? vscode.workspace.workspaceFolders![0].uri.fsPath : await this.ensureSettingsDirectoryExists() - const { createHookable } = await import("roo-rocket") - const { unpackFromUrl } = await import("config-rocket/cli") if (!item.binaryUrl || !item.binaryHash) return vscode.window.showErrorMessage("Item does not have a binary URL or hash") @@ -605,9 +604,38 @@ export class MarketplaceManager { const binaryUint8 = await fetchBinary(item.binaryUrl) // Install via CLI if binary is a configurable pack. + // TODO: think of a way to send the binary to the npx process if (await uint8IsConfigPackWithParameters(binaryUint8)) { - vscode.window.showInformationMessage(`"${item.name}" is a configurable pack, invoking CLI to install...`) - vscode.window.showInformationMessage(`*CLI install WIP*`) + vscode.window.showInformationMessage(`"${item.name}" is configurable, install through interactive CLI...`) + + let pResult: string[] = [] + let pExitCode: number | undefined + // We don't want to create a new terminal at the global dir, so I'm not using cwd here + const terminalClass = await TerminalRegistry.getOrCreateTerminal(vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath ?? '', false, `IMI-${item.name}`) + terminalClass.terminal.show() + await terminalClass.runCommand(`npx --yes roo-rocket@latest --mp="${JSON.stringify(mpContext).replaceAll(/"/g, '\\"')}" --cwd="${cwd}" --sha256="${item.binaryHash}" --url="${item.binaryUrl}"`, { + onLine: (line) => { + pResult.push(line) + }, + onShellExecutionComplete: (details) => { + pExitCode = details.exitCode + }, + }) + + if (pExitCode === 0) + vscode.window.showInformationMessage(`"${item.name}" CLI reported success!`) + else { + console.error(pResult) + // Revert so error search is potentially faster + pResult.reverse() + // Search for error line in the result + const errorLine = ( + pResult.find(line => /^((\r)?\n)+ ERROR /.test(line)) ?? // Prefer formatting error + pResult.find(line => /error/i.test(line)) ?? // General error + 'N/A' + ) + return vscode.window.showErrorMessage(`"${item.name}" CLI reported error: (${pExitCode}): ${errorLine}`) + } } // Fast install for non-configurable packs. else { From 324be54e4eddc9073a39aa1600628cf2abbeb9fe Mon Sep 17 00:00:00 2001 From: NamesMT Date: Mon, 28 Apr 2025 15:02:59 +0000 Subject: [PATCH 8/8] chore: minor rewording --- src/services/marketplace/MarketplaceManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index 902347ab3c..c34ad6d67c 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -606,7 +606,7 @@ export class MarketplaceManager { // Install via CLI if binary is a configurable pack. // TODO: think of a way to send the binary to the npx process if (await uint8IsConfigPackWithParameters(binaryUint8)) { - vscode.window.showInformationMessage(`"${item.name}" is configurable, install through interactive CLI...`) + vscode.window.showInformationMessage(`"${item.name}" is configurable, invoking interactive CLI...`) let pResult: string[] = [] let pExitCode: number | undefined