mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-07 02:58:15 +00:00
feat: marketplace supports global install
This commit is contained in:
parent
04d7360da9
commit
74a0c44d59
8 changed files with 93 additions and 29 deletions
25
package-lock.json
generated
25
package-lock.json
generated
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string> {
|
||||
const settingsDir = path.join(this.context.globalStorageUri.fsPath, "settings")
|
||||
await fs.mkdir(settingsDir, { recursive: true })
|
||||
return settingsDir
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,3 +130,12 @@ export interface LocalizationOptions {
|
|||
userLocale: string
|
||||
fallbackLocale: string
|
||||
}
|
||||
|
||||
export interface InstallMarketplaceItemOptions {
|
||||
/**
|
||||
* Specify the installation target
|
||||
*
|
||||
* @default 'project'
|
||||
*/
|
||||
target?: 'global' | 'project'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<MarketplaceItemActionsMenuProp
|
|||
}) => {
|
||||
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<MarketplaceItemActionsMenuProp
|
|||
<span>{t("marketplace:items.card.viewSource")}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* Install Item */}
|
||||
{/* Install (Project) */}
|
||||
{showInstallButton && (
|
||||
<DropdownMenuItem onClick={handleInstall}>
|
||||
<DropdownMenuItem onClick={() => handleInstall({target: 'project'})}>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
<span>{t("marketplace:items.card.install")}</span>
|
||||
<span>{t("marketplace:items.card.installProject")}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{/* Install (Global) */}
|
||||
{showInstallButton && (
|
||||
<DropdownMenuItem onClick={() => handleInstall({target: 'global'})}>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
<span>{t("marketplace:items.card.installGlobal")}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
|
|
|
|||
|
|
@ -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}}"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue