diff --git a/src/core/config/CustomModesManager.ts b/src/core/config/CustomModesManager.ts index 3e1ccb5571..c388c1a537 100644 --- a/src/core/config/CustomModesManager.ts +++ b/src/core/config/CustomModesManager.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode" import * as path from "path" import * as fs from "fs/promises" +import * as os from "os" import * as yaml from "yaml" import stripBom from "strip-bom" @@ -501,7 +502,7 @@ export class CustomModesManager { await this.onUpdate() } - public async deleteCustomMode(slug: string): Promise { + public async deleteCustomMode(slug: string, fromMarketplace = false): Promise { try { const settingsPath = await this.getCustomModesFilePath() const roomodesPath = await this.getWorkspaceRoomodes() @@ -517,6 +518,9 @@ export class CustomModesManager { throw new Error(t("common:customModes.errors.modeNotFound")) } + // Determine which mode to use for rules folder path calculation + const modeToDelete = projectMode || globalMode + await this.queueWrite(async () => { // Delete from project first if it exists there if (projectMode && roomodesPath) { @@ -528,6 +532,11 @@ export class CustomModesManager { await this.updateModesInFile(settingsPath, (modes) => modes.filter((m) => m.slug !== slug)) } + // Delete associated rules folder + if (modeToDelete) { + await this.deleteRulesFolder(slug, modeToDelete, fromMarketplace) + } + // Clear cache when modes are deleted this.clearCache() await this.refreshMergedState() @@ -538,6 +547,54 @@ export class CustomModesManager { } } + /** + * Deletes the rules folder for a specific mode + * @param slug - The mode slug + * @param mode - The mode configuration to determine the scope + */ + private async deleteRulesFolder(slug: string, mode: ModeConfig, fromMarketplace = false): Promise { + try { + // Determine the scope based on source (project or global) + const scope = mode.source || "global" + + // Determine the rules folder path + let rulesFolderPath: string + if (scope === "project") { + const workspacePath = getWorkspacePath() + if (workspacePath) { + rulesFolderPath = path.join(workspacePath, ".roo", `rules-${slug}`) + } else { + return // No workspace, can't delete project rules + } + } else { + // Global scope - use OS home directory + const homeDir = os.homedir() + rulesFolderPath = path.join(homeDir, ".roo", `rules-${slug}`) + } + + // Check if the rules folder exists and delete it + const rulesFolderExists = await fileExistsAtPath(rulesFolderPath) + if (rulesFolderExists) { + try { + await fs.rm(rulesFolderPath, { recursive: true, force: true }) + logger.info(`Deleted rules folder for mode ${slug}: ${rulesFolderPath}`) + } catch (error) { + logger.error(`Failed to delete rules folder for mode ${slug}: ${error}`) + // Notify the user about the failure + const messageKey = fromMarketplace + ? "common:marketplace.mode.rulesCleanupFailed" + : "common:customModes.errors.rulesCleanupFailed" + vscode.window.showWarningMessage(t(messageKey, { rulesFolderPath })) + // Continue even if folder deletion fails + } + } + } catch (error) { + logger.error(`Error deleting rules folder for mode ${slug}`, { + error: error instanceof Error ? error.message : String(error), + }) + } + } + public async resetCustomModes(): Promise { try { const filePath = await this.getCustomModesFilePath() diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index d7b5d0c9a8..905e657b37 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -158,7 +158,7 @@ export class ClineProvider this.log(`Failed to initialize MCP Hub: ${error}`) }) - this.marketplaceManager = new MarketplaceManager(this.context) + this.marketplaceManager = new MarketplaceManager(this.context, this.customModesManager) } // Adds a new Cline instance to clineStack, marking the start of a new task. diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index d49f34dd25..c739c2ade8 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2229,8 +2229,45 @@ export const webviewMessageHandler = async ( try { await marketplaceManager.removeInstalledMarketplaceItem(message.mpItem, message.mpInstallOptions) await provider.postStateToWebview() + + // Send success message to webview + provider.postMessageToWebview({ + type: "marketplaceRemoveResult", + success: true, + slug: message.mpItem.id, + }) } catch (error) { console.error(`Error removing marketplace item: ${error}`) + + // Show error message to user + vscode.window.showErrorMessage( + `Failed to remove marketplace item: ${error instanceof Error ? error.message : String(error)}`, + ) + + // Send error message to webview + provider.postMessageToWebview({ + type: "marketplaceRemoveResult", + success: false, + error: error instanceof Error ? error.message : String(error), + slug: message.mpItem.id, + }) + } + } else { + // MarketplaceManager not available or missing required parameters + const errorMessage = !marketplaceManager + ? "Marketplace manager is not available" + : "Missing required parameters for marketplace item removal" + console.error(errorMessage) + + vscode.window.showErrorMessage(errorMessage) + + if (message.mpItem?.id) { + provider.postMessageToWebview({ + type: "marketplaceRemoveResult", + success: false, + error: errorMessage, + slug: message.mpItem.id, + }) } } break diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 8ca5ae09a5..39bc1df8f8 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Error en eliminar el mode personalitzat: {{error}}", "resetFailed": "Error en restablir els modes personalitzats: {{error}}", "modeNotFound": "Error d'escriptura: Mode no trobat", - "noWorkspaceForProject": "No s'ha trobat cap carpeta d'espai de treball per al mode específic del projecte" + "noWorkspaceForProject": "No s'ha trobat cap carpeta d'espai de treball per al mode específic del projecte", + "rulesCleanupFailed": "El mode s'ha suprimit correctament, però no s'ha pogut suprimir la carpeta de regles a {{rulesFolderPath}}. És possible que l'hagis de suprimir manualment." }, "scope": { "project": "projecte", "global": "global" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "El mode s'ha eliminat correctament, però no s'ha pogut eliminar la carpeta de regles a {{rulesFolderPath}}. És possible que l'hagis d'eliminar manualment." + } + }, "mdm": { "errors": { "cloud_auth_required": "La teva organització requereix autenticació de Roo Code Cloud. Si us plau, inicia sessió per continuar.", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 8853f4da41..fbd800f602 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Fehler beim Löschen des benutzerdefinierten Modus: {{error}}", "resetFailed": "Fehler beim Zurücksetzen der benutzerdefinierten Modi: {{error}}", "modeNotFound": "Schreibfehler: Modus nicht gefunden", - "noWorkspaceForProject": "Kein Arbeitsbereich-Ordner für projektspezifischen Modus gefunden" + "noWorkspaceForProject": "Kein Arbeitsbereich-Ordner für projektspezifischen Modus gefunden", + "rulesCleanupFailed": "Der Modus wurde erfolgreich gelöscht, aber der Regelordner unter {{rulesFolderPath}} konnte nicht gelöscht werden. Möglicherweise musst du ihn manuell löschen." }, "scope": { "project": "projekt", "global": "global" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "Der Modus wurde erfolgreich entfernt, aber der Regelordner unter {{rulesFolderPath}} konnte nicht gelöscht werden. Möglicherweise musst du ihn manuell löschen." + } + }, "mdm": { "errors": { "cloud_auth_required": "Deine Organisation erfordert eine Roo Code Cloud-Authentifizierung. Bitte melde dich an, um fortzufahren.", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 8adcbfa8cc..db6341c312 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -136,13 +136,19 @@ "deleteFailed": "Failed to delete custom mode: {{error}}", "resetFailed": "Failed to reset custom modes: {{error}}", "modeNotFound": "Write error: Mode not found", - "noWorkspaceForProject": "No workspace folder found for project-specific mode" + "noWorkspaceForProject": "No workspace folder found for project-specific mode", + "rulesCleanupFailed": "Mode deleted successfully, but failed to delete rules folder at {{rulesFolderPath}}. You may need to delete it manually." }, "scope": { "project": "project", "global": "global" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "Mode removed successfully, but failed to delete rules folder at {{rulesFolderPath}}. You may need to delete it manually." + } + }, "mdm": { "errors": { "cloud_auth_required": "Your organization requires Roo Code Cloud authentication. Please sign in to continue.", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 666aa4ec0b..cc04abfdae 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Error al eliminar modo personalizado: {{error}}", "resetFailed": "Error al restablecer modos personalizados: {{error}}", "modeNotFound": "Error de escritura: Modo no encontrado", - "noWorkspaceForProject": "No se encontró carpeta de espacio de trabajo para modo específico del proyecto" + "noWorkspaceForProject": "No se encontró carpeta de espacio de trabajo para modo específico del proyecto", + "rulesCleanupFailed": "El modo se eliminó correctamente, pero no se pudo eliminar la carpeta de reglas en {{rulesFolderPath}}. Es posible que debas eliminarla manualmente." }, "scope": { "project": "proyecto", "global": "global" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "El modo se eliminó correctamente, pero no se pudo eliminar la carpeta de reglas en {{rulesFolderPath}}. Es posible que debas eliminarla manually." + } + }, "mdm": { "errors": { "cloud_auth_required": "Tu organización requiere autenticación de Roo Code Cloud. Por favor, inicia sesión para continuar.", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 1a29a4c374..73f3e3d396 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Échec de la suppression du mode personnalisé : {{error}}", "resetFailed": "Échec de la réinitialisation des modes personnalisés : {{error}}", "modeNotFound": "Erreur d'écriture : Mode non trouvé", - "noWorkspaceForProject": "Aucun dossier d'espace de travail trouvé pour le mode spécifique au projet" + "noWorkspaceForProject": "Aucun dossier d'espace de travail trouvé pour le mode spécifique au projet", + "rulesCleanupFailed": "Le mode a été supprimé avec succès, mais la suppression du dossier de règles à l'adresse {{rulesFolderPath}} a échoué. Vous devrez peut-être le supprimer manuellement." }, "scope": { "project": "projet", "global": "global" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "Le mode a été supprimé avec succès, mais la suppression du dossier de règles à l'adresse {{rulesFolderPath}} a échoué. Vous devrez peut-être le supprimer manuellement." + } + }, "mdm": { "errors": { "cloud_auth_required": "Votre organisation nécessite une authentification Roo Code Cloud. Veuillez vous connecter pour continuer.", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index 34331f6683..03f74e1af5 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -147,13 +147,19 @@ "deleteFailed": "कस्टम मोड डिलीट विफल: {{error}}", "resetFailed": "कस्टम मोड रीसेट विफल: {{error}}", "modeNotFound": "लेखन त्रुटि: मोड नहीं मिला", - "noWorkspaceForProject": "प्रोजेक्ट-विशिष्ट मोड के लिए वर्कस्पेस फ़ोल्डर नहीं मिला" + "noWorkspaceForProject": "प्रोजेक्ट-विशिष्ट मोड के लिए वर्कस्पेस फ़ोल्डर नहीं मिला", + "rulesCleanupFailed": "मोड सफलतापूर्वक हटा दिया गया, लेकिन {{rulesFolderPath}} पर नियम फ़ोल्डर को हटाने में विफल रहा। आपको इसे मैन्युअल रूप से हटाना पड़ सकता है।" }, "scope": { "project": "परियोजना", "global": "वैश्विक" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "मोड सफलतापूर्वक हटा दिया गया, लेकिन {{rulesFolderPath}} पर नियम फ़ोल्डर को हटाने में विफल रहा। आपको इसे मैन्युअल रूप से हटाना पड़ सकता है।" + } + }, "mdm": { "errors": { "cloud_auth_required": "आपके संगठन को Roo Code Cloud प्रमाणीकरण की आवश्यकता है। कृपया जारी रखने के लिए साइन इन करें।", diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index 25e70b3540..822341f529 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Gagal menghapus mode kustom: {{error}}", "resetFailed": "Gagal mereset mode kustom: {{error}}", "modeNotFound": "Kesalahan tulis: Mode tidak ditemukan", - "noWorkspaceForProject": "Tidak ditemukan folder workspace untuk mode khusus proyek" + "noWorkspaceForProject": "Tidak ditemukan folder workspace untuk mode khusus proyek", + "rulesCleanupFailed": "Mode berhasil dihapus, tetapi gagal menghapus folder aturan di {{rulesFolderPath}}. Kamu mungkin perlu menghapusnya secara manual." }, "scope": { "project": "proyek", "global": "global" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "Mode berhasil dihapus, tetapi gagal menghapus folder aturan di {{rulesFolderPath}}. Kamu mungkin perlu menghapusnya secara manual." + } + }, "mdm": { "errors": { "cloud_auth_required": "Organisasi kamu memerlukan autentikasi Roo Code Cloud. Silakan masuk untuk melanjutkan.", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 775175e3d8..7ae45cc4c5 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Eliminazione modalità personalizzata fallita: {{error}}", "resetFailed": "Reset modalità personalizzate fallito: {{error}}", "modeNotFound": "Errore di scrittura: Modalità non trovata", - "noWorkspaceForProject": "Nessuna cartella workspace trovata per la modalità specifica del progetto" + "noWorkspaceForProject": "Nessuna cartella workspace trovata per la modalità specifica del progetto", + "rulesCleanupFailed": "La modalità è stata eliminata con successo, ma non è stato possibile eliminare la cartella delle regole in {{rulesFolderPath}}. Potrebbe essere necessario eliminarla manualmente." }, "scope": { "project": "progetto", "global": "globale" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "La modalità è stata rimossa con successo, ma non è stato possibile eliminare la cartella delle regole in {{rulesFolderPath}}. Potrebbe essere necessario eliminarla manualmente." + } + }, "mdm": { "errors": { "cloud_auth_required": "La tua organizzazione richiede l'autenticazione Roo Code Cloud. Accedi per continuare.", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index ecd60699f8..da8124b48c 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -147,13 +147,19 @@ "deleteFailed": "カスタムモードの削除に失敗しました:{{error}}", "resetFailed": "カスタムモードのリセットに失敗しました:{{error}}", "modeNotFound": "書き込みエラー:モードが見つかりません", - "noWorkspaceForProject": "プロジェクト固有モード用のワークスペースフォルダーが見つかりません" + "noWorkspaceForProject": "プロジェクト固有モード用のワークスペースフォルダーが見つかりません", + "rulesCleanupFailed": "モードは正常に削除されましたが、{{rulesFolderPath}} にあるルールフォルダの削除に失敗しました。手動で削除する必要がある場合があります。" }, "scope": { "project": "プロジェクト", "global": "グローバル" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "モードは正常に削除されましたが、{{rulesFolderPath}} にあるルールフォルダの削除に失敗しました。手動で削除する必要がある場合があります。" + } + }, "mdm": { "errors": { "cloud_auth_required": "あなたの組織では Roo Code Cloud 認証が必要です。続行するにはサインインしてください。", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index e96f728199..a95908ffec 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -147,13 +147,19 @@ "deleteFailed": "사용자 정의 모드 삭제 실패: {{error}}", "resetFailed": "사용자 정의 모드 재설정 실패: {{error}}", "modeNotFound": "쓰기 오류: 모드를 찾을 수 없습니다", - "noWorkspaceForProject": "프로젝트별 모드용 작업 공간 폴더를 찾을 수 없습니다" + "noWorkspaceForProject": "프로젝트별 모드용 작업 공간 폴더를 찾을 수 없습니다", + "rulesCleanupFailed": "모드가 성공적으로 삭제되었지만 {{rulesFolderPath}}의 규칙 폴더를 삭제하지 못했습니다. 수동으로 삭제해야 할 수도 있습니다." }, "scope": { "project": "프로젝트", "global": "글로벌" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "모드가 성공적으로 제거되었지만 {{rulesFolderPath}}의 규칙 폴더를 삭제하지 못했습니다. 수동으로 삭제해야 할 수도 있습니다." + } + }, "mdm": { "errors": { "cloud_auth_required": "조직에서 Roo Code Cloud 인증이 필요합니다. 계속하려면 로그인하세요.", diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index b99e8f2e81..ac7df81e42 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Aangepaste modus verwijderen mislukt: {{error}}", "resetFailed": "Aangepaste modi resetten mislukt: {{error}}", "modeNotFound": "Schrijffout: Modus niet gevonden", - "noWorkspaceForProject": "Geen workspace map gevonden voor projectspecifieke modus" + "noWorkspaceForProject": "Geen workspace map gevonden voor projectspecifieke modus", + "rulesCleanupFailed": "Modus succesvol verwijderd, maar het verwijderen van de regelsmap op {{rulesFolderPath}} is mislukt. Je moet deze mogelijk handmatig verwijderen." }, "scope": { "project": "project", "global": "globaal" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "Modus succesvol verwijderd, maar het verwijderen van de regelsmap op {{rulesFolderPath}} is mislukt. Je moet deze mogelijk handmatig verwijderen." + } + }, "mdm": { "errors": { "cloud_auth_required": "Je organisatie vereist Roo Code Cloud-authenticatie. Log in om door te gaan.", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 7ba7e93514..e24960af89 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Usunięcie trybu niestandardowego nie powiodło się: {{error}}", "resetFailed": "Resetowanie trybów niestandardowych nie powiodło się: {{error}}", "modeNotFound": "Błąd zapisu: Tryb nie został znaleziony", - "noWorkspaceForProject": "Nie znaleziono folderu obszaru roboczego dla trybu specyficznego dla projektu" + "noWorkspaceForProject": "Nie znaleziono folderu obszaru roboczego dla trybu specyficznego dla projektu", + "rulesCleanupFailed": "Tryb został pomyślnie usunięty, ale nie udało się usunąć folderu reguł w {{rulesFolderPath}}. Może być konieczne ręczne usunięcie." }, "scope": { "project": "projekt", "global": "globalny" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "Tryb został pomyślnie usunięty, ale nie udało się usunąć folderu reguł w {{rulesFolderPath}}. Może być konieczne ręczne usunięcie." + } + }, "mdm": { "errors": { "cloud_auth_required": "Twoja organizacja wymaga uwierzytelnienia Roo Code Cloud. Zaloguj się, aby kontynuować.", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 753f7ae5bb..6007beb41a 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Falha ao excluir modo personalizado: {{error}}", "resetFailed": "Falha ao redefinir modos personalizados: {{error}}", "modeNotFound": "Erro de escrita: Modo não encontrado", - "noWorkspaceForProject": "Nenhuma pasta de workspace encontrada para modo específico do projeto" + "noWorkspaceForProject": "Nenhuma pasta de workspace encontrada para modo específico do projeto", + "rulesCleanupFailed": "O modo foi excluído com sucesso, mas falhou ao excluir a pasta de regras em {{rulesFolderPath}}. Você pode precisar excluí-la manualmente." }, "scope": { "project": "projeto", "global": "global" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "O modo foi removido com sucesso, mas falhou ao excluir a pasta de regras em {{rulesFolderPath}}. Você pode precisar excluí-la manualmente." + } + }, "mdm": { "errors": { "cloud_auth_required": "Sua organização requer autenticação do Roo Code Cloud. Faça login para continuar.", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 6431bf0ca9..4d3daaf743 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Не удалось удалить пользовательский режим: {{error}}", "resetFailed": "Не удалось сбросить пользовательские режимы: {{error}}", "modeNotFound": "Ошибка записи: Режим не найден", - "noWorkspaceForProject": "Не найдена папка рабочего пространства для режима, специфичного для проекта" + "noWorkspaceForProject": "Не найдена папка рабочего пространства для режима, специфичного для проекта", + "rulesCleanupFailed": "Режим успешно удален, но не удалось удалить папку правил в {{rulesFolderPath}}. Возможно, вам придется удалить ее вручную." }, "scope": { "project": "проект", "global": "глобальный" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "Режим успешно удален, но не удалось удалить папку правил в {{rulesFolderPath}}. Возможно, вам придется удалить ее вручную." + } + }, "mdm": { "errors": { "cloud_auth_required": "Ваша организация требует аутентификации Roo Code Cloud. Войдите в систему, чтобы продолжить.", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index cfc2a37591..e2dfca734b 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Özel mod silme başarısız: {{error}}", "resetFailed": "Özel modları sıfırlama başarısız: {{error}}", "modeNotFound": "Yazma hatası: Mod bulunamadı", - "noWorkspaceForProject": "Proje özel modu için çalışma alanı klasörü bulunamadı" + "noWorkspaceForProject": "Proje özel modu için çalışma alanı klasörü bulunamadı", + "rulesCleanupFailed": "Mod başarıyla silindi, ancak {{rulesFolderPath}} konumundaki kurallar klasörü silinemedi. Manuel olarak silmeniz gerekebilir." }, "scope": { "project": "proje", "global": "küresel" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "Mod başarıyla kaldırıldı, ancak {{rulesFolderPath}} konumundaki kurallar klasörü silinemedi. Manuel olarak silmeniz gerekebilir." + } + }, "mdm": { "errors": { "cloud_auth_required": "Kuruluşunuz Roo Code Cloud kimlik doğrulaması gerektiriyor. Devam etmek için giriş yapın.", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index b4593a3476..15e4ef8b77 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -147,13 +147,19 @@ "deleteFailed": "Xóa chế độ tùy chỉnh thất bại: {{error}}", "resetFailed": "Đặt lại chế độ tùy chỉnh thất bại: {{error}}", "modeNotFound": "Lỗi ghi: Không tìm thấy chế độ", - "noWorkspaceForProject": "Không tìm thấy thư mục workspace cho chế độ dành riêng cho dự án" + "noWorkspaceForProject": "Không tìm thấy thư mục workspace cho chế độ dành riêng cho dự án", + "rulesCleanupFailed": "Đã xóa chế độ thành công, nhưng không thể xóa thư mục quy tắc tại {{rulesFolderPath}}. Bạn có thể cần xóa thủ công." }, "scope": { "project": "dự án", "global": "toàn cầu" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "Đã xóa chế độ thành công, nhưng không thể xóa thư mục quy tắc tại {{rulesFolderPath}}. Bạn có thể cần xóa thủ công." + } + }, "mdm": { "errors": { "cloud_auth_required": "Tổ chức của bạn yêu cầu xác thực Roo Code Cloud. Vui lòng đăng nhập để tiếp tục.", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 182ab29f33..edbbb6ae8c 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -152,13 +152,19 @@ "deleteFailed": "删除自定义模式失败:{{error}}", "resetFailed": "重置自定义模式失败:{{error}}", "modeNotFound": "写入错误:未找到模式", - "noWorkspaceForProject": "未找到项目特定模式的工作区文件夹" + "noWorkspaceForProject": "未找到项目特定模式的工作区文件夹", + "rulesCleanupFailed": "模式删除成功,但无法删除位于 {{rulesFolderPath}} 的规则文件夹。您可能需要手动删除。" }, "scope": { "project": "项目", "global": "全局" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "模式已成功移除,但无法删除位于 {{rulesFolderPath}} 的规则文件夹。您可能需要手动删除。" + } + }, "mdm": { "errors": { "cloud_auth_required": "您的组织需要 Roo Code Cloud 身份验证。请登录以继续。", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 2746e16ee1..e7887025f1 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -147,13 +147,19 @@ "deleteFailed": "刪除自訂模式失敗:{{error}}", "resetFailed": "重設自訂模式失敗:{{error}}", "modeNotFound": "寫入錯誤:未找到模式", - "noWorkspaceForProject": "未找到專案特定模式的工作區資料夾" + "noWorkspaceForProject": "未找到專案特定模式的工作區資料夾", + "rulesCleanupFailed": "模式已成功刪除,但無法刪除位於 {{rulesFolderPath}} 的規則資料夾。您可能需要手動刪除。" }, "scope": { "project": "專案", "global": "全域" } }, + "marketplace": { + "mode": { + "rulesCleanupFailed": "模式已成功移除,但無法刪除位於 {{rulesFolderPath}} 的規則資料夾。您可能需要手動刪除。" + } + }, "mdm": { "errors": { "cloud_auth_required": "您的組織需要 Roo Code Cloud 身份驗證。請登入以繼續。", diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts index 367fa14888..5c5b9f6d61 100644 --- a/src/services/marketplace/MarketplaceManager.ts +++ b/src/services/marketplace/MarketplaceManager.ts @@ -9,14 +9,18 @@ import { GlobalFileNames } from "../../shared/globalFileNames" import { ensureSettingsDirectoryExists } from "../../utils/globalContext" import { t } from "../../i18n" import { TelemetryService } from "@roo-code/telemetry" +import type { CustomModesManager } from "../../core/config/CustomModesManager" export class MarketplaceManager { private configLoader: RemoteConfigLoader private installer: SimpleInstaller - constructor(private readonly context: vscode.ExtensionContext) { + constructor( + private readonly context: vscode.ExtensionContext, + private readonly customModesManager?: CustomModesManager, + ) { this.configLoader = new RemoteConfigLoader() - this.installer = new SimpleInstaller(context) + this.installer = new SimpleInstaller(context, customModesManager) } async getMarketplaceItems(): Promise<{ items: MarketplaceItem[]; errors?: string[] }> { diff --git a/src/services/marketplace/SimpleInstaller.ts b/src/services/marketplace/SimpleInstaller.ts index 2274b65343..be002e2f1d 100644 --- a/src/services/marketplace/SimpleInstaller.ts +++ b/src/services/marketplace/SimpleInstaller.ts @@ -5,6 +5,7 @@ import * as yaml from "yaml" import type { MarketplaceItem, MarketplaceItemType, InstallMarketplaceItemOptions, McpParameter } from "@roo-code/types" import { GlobalFileNames } from "../../shared/globalFileNames" import { ensureSettingsDirectoryExists } from "../../utils/globalContext" +import type { CustomModesManager } from "../../core/config/CustomModesManager" export interface InstallOptions extends InstallMarketplaceItemOptions { target: "project" | "global" @@ -12,7 +13,10 @@ export interface InstallOptions extends InstallMarketplaceItemOptions { } export class SimpleInstaller { - constructor(private readonly context: vscode.ExtensionContext) {} + constructor( + private readonly context: vscode.ExtensionContext, + private readonly customModesManager?: CustomModesManager, + ) {} async installItem(item: MarketplaceItem, options: InstallOptions): Promise<{ filePath: string; line?: number }> { const { target } = options @@ -40,6 +44,48 @@ export class SimpleInstaller { throw new Error("Mode content should not be an array") } + // If CustomModesManager is available, use importModeWithRules + if (this.customModesManager) { + // Transform marketplace content to import format (wrap in customModes array) + const importData = { + customModes: [yaml.parse(item.content)], + } + const importYaml = yaml.stringify(importData) + + // Call customModesManager.importModeWithRules + const result = await this.customModesManager.importModeWithRules(importYaml, target) + + if (!result.success) { + throw new Error(result.error || "Failed to import mode") + } + + // Return the file path and line number for VS Code to open + const filePath = await this.getModeFilePath(target) + + // Try to find the line number where the mode was added + let line: number | undefined + try { + const fileContent = await fs.readFile(filePath, "utf-8") + const lines = fileContent.split("\n") + const modeData = yaml.parse(item.content) + + // Find the line containing the slug of the added mode + if (modeData?.slug) { + const slugLineIndex = lines.findIndex( + (l) => l.includes(`slug: ${modeData.slug}`) || l.includes(`slug: "${modeData.slug}"`), + ) + if (slugLineIndex >= 0) { + line = slugLineIndex + 1 // Convert to 1-based line number + } + } + } catch (error) { + // If we can't find the line number, that's okay + } + + return { filePath, line } + } + + // Fallback to original implementation if CustomModesManager is not available const filePath = await this.getModeFilePath(target) const modeData = yaml.parse(item.content) @@ -248,56 +294,38 @@ export class SimpleInstaller { } private async removeMode(item: MarketplaceItem, target: "project" | "global"): Promise { - const filePath = await this.getModeFilePath(target) - - try { - const existing = await fs.readFile(filePath, "utf-8") - let existingData: any - - try { - const parsed = yaml.parse(existing) - // Ensure we have a valid object - existingData = parsed && typeof parsed === "object" ? parsed : {} - } catch (parseError) { - // If we can't parse the file, we can't safely remove a mode - const fileName = target === "project" ? ".roomodes" : "custom-modes.yaml" - throw new Error( - `Cannot remove mode: The ${fileName} file contains invalid YAML. ` + - `Please fix the syntax errors before removing modes.`, - ) - } + if (!this.customModesManager) { + throw new Error("CustomModesManager is not available") + } - // Ensure customModes array exists - if (!existingData.customModes) { - existingData.customModes = [] - } + // Parse the item content to get the slug + let content: string + if (Array.isArray(item.content)) { + // Array of McpInstallationMethod objects - use first method + content = item.content[0].content + } else { + content = item.content || "" + } - // Parse the item content to get the slug - let content: string - if (Array.isArray(item.content)) { - // Array of McpInstallationMethod objects - use first method - content = item.content[0].content - } else { - content = item.content - } - const modeData = yaml.parse(content || "") + let modeSlug: string + try { + const modeData = yaml.parse(content) + modeSlug = modeData.slug + } catch (error) { + throw new Error("Invalid mode content: unable to parse YAML") + } - if (!modeData.slug) { - return // Nothing to remove if no slug - } + if (!modeSlug) { + throw new Error("Mode missing slug identifier") + } - // Remove mode with matching slug - existingData.customModes = existingData.customModes.filter((mode: any) => mode.slug !== modeData.slug) + // Get the current modes to determine the source + const modes = await this.customModesManager.getCustomModes() + const mode = modes.find((m) => m.slug === modeSlug) - // Always write back the file, even if empty - await fs.writeFile(filePath, yaml.stringify(existingData, { lineWidth: 0 }), "utf-8") - } catch (error: any) { - if (error.code === "ENOENT") { - // File doesn't exist, nothing to remove - return - } - throw error - } + // Use CustomModesManager to delete the mode configuration + // This also handles rules folder deletion + await this.customModesManager.deleteCustomMode(modeSlug, true) } private async removeMcp(item: MarketplaceItem, target: "project" | "global"): Promise { diff --git a/src/services/marketplace/__tests__/SimpleInstaller.spec.ts b/src/services/marketplace/__tests__/SimpleInstaller.spec.ts index 546eb16f9a..94684056d4 100644 --- a/src/services/marketplace/__tests__/SimpleInstaller.spec.ts +++ b/src/services/marketplace/__tests__/SimpleInstaller.spec.ts @@ -4,10 +4,19 @@ import { SimpleInstaller } from "../SimpleInstaller" import * as fs from "fs/promises" import * as yaml from "yaml" import * as vscode from "vscode" +import * as os from "os" import type { MarketplaceItem } from "@roo-code/types" +import type { CustomModesManager } from "../../../core/config/CustomModesManager" import * as path from "path" +import { fileExistsAtPath } from "../../../utils/fs" -vi.mock("fs/promises") +vi.mock("fs/promises", () => ({ + readFile: vi.fn(), + writeFile: vi.fn(), + mkdir: vi.fn(), + rm: vi.fn(), +})) +vi.mock("os") vi.mock("vscode", () => ({ workspace: { workspaceFolders: [ @@ -20,20 +29,33 @@ vi.mock("vscode", () => ({ }, })) vi.mock("../../../utils/globalContext") +vi.mock("../../../utils/fs") -const mockFs = fs as any +const mockFs = vi.mocked(fs) describe("SimpleInstaller", () => { let installer: SimpleInstaller let mockContext: vscode.ExtensionContext + let mockCustomModesManager: CustomModesManager beforeEach(() => { mockContext = {} as vscode.ExtensionContext - installer = new SimpleInstaller(mockContext) + mockCustomModesManager = { + deleteCustomMode: vi.fn().mockResolvedValue(undefined), + importModeWithRules: vi.fn().mockResolvedValue({ success: true }), + getCustomModes: vi.fn().mockResolvedValue([]), + } as any + installer = new SimpleInstaller(mockContext, mockCustomModesManager) vi.clearAllMocks() // Mock mkdir to always succeed mockFs.mkdir.mockResolvedValue(undefined as any) + // Mock rm to always succeed + mockFs.rm.mockResolvedValue(undefined as any) + // Mock os.homedir + vi.mocked(os.homedir).mockReturnValue("/home/user") + // Mock fileExistsAtPath to return false by default + vi.mocked(fileExistsAtPath).mockResolvedValue(false) }) describe("installMode", () => { @@ -50,128 +72,69 @@ describe("SimpleInstaller", () => { }), } - it("should install mode when .roomodes file does not exist", async () => { - // Mock file not found error + it("should install mode using CustomModesManager", async () => { + // Mock file not found error for getModeFilePath const notFoundError = new Error("File not found") as any notFoundError.code = "ENOENT" mockFs.readFile.mockRejectedValueOnce(notFoundError) - mockFs.writeFile.mockResolvedValueOnce(undefined as any) const result = await installer.installItem(mockModeItem, { target: "project" }) expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes")) - expect(mockFs.writeFile).toHaveBeenCalled() - - // Verify the written content contains the new mode - const writtenContent = mockFs.writeFile.mock.calls[0][1] as string - const writtenData = yaml.parse(writtenContent) - expect(writtenData.customModes).toHaveLength(1) - expect(writtenData.customModes[0].slug).toBe("test") + expect(mockCustomModesManager.importModeWithRules).toHaveBeenCalled() + + // Verify the import was called with correct YAML structure + const importCall = (mockCustomModesManager.importModeWithRules as any).mock.calls[0] + const importedYaml = importCall[0] + const importedData = yaml.parse(importedYaml) + expect(importedData.customModes).toHaveLength(1) + expect(importedData.customModes[0].slug).toBe("test") }) - it("should install mode when .roomodes contains valid YAML", async () => { - const existingContent = yaml.stringify({ - customModes: [{ slug: "existing", name: "Existing Mode", roleDefinition: "Existing", groups: [] }], + it("should handle import failure from CustomModesManager", async () => { + mockCustomModesManager.importModeWithRules = vi.fn().mockResolvedValue({ + success: false, + error: "Import failed", }) - mockFs.readFile.mockResolvedValueOnce(existingContent) - mockFs.writeFile.mockResolvedValueOnce(undefined as any) - - await installer.installItem(mockModeItem, { target: "project" }) - - expect(mockFs.writeFile).toHaveBeenCalled() - const writtenContent = mockFs.writeFile.mock.calls[0][1] as string - const writtenData = yaml.parse(writtenContent) - - // Should contain both existing and new mode - expect(writtenData.customModes).toHaveLength(2) - expect(writtenData.customModes.find((m: any) => m.slug === "existing")).toBeDefined() - expect(writtenData.customModes.find((m: any) => m.slug === "test")).toBeDefined() + await expect(installer.installItem(mockModeItem, { target: "project" })).rejects.toThrow("Import failed") }) - it("should handle empty .roomodes file", async () => { - // Empty file content - mockFs.readFile.mockResolvedValueOnce("") - mockFs.writeFile.mockResolvedValueOnce(undefined as any) - - const result = await installer.installItem(mockModeItem, { target: "project" }) + it("should throw error for array content in mode", async () => { + const arrayContentMode: MarketplaceItem = { + ...mockModeItem, + content: ["content1", "content2"] as any, + } - expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes")) - expect(mockFs.writeFile).toHaveBeenCalled() - - // Verify the written content contains the new mode - const writtenContent = mockFs.writeFile.mock.calls[0][1] as string - const writtenData = yaml.parse(writtenContent) - expect(writtenData.customModes).toHaveLength(1) - expect(writtenData.customModes[0].slug).toBe("test") - }) - - it("should handle .roomodes file with null content", async () => { - // File exists but yaml.parse returns null - mockFs.readFile.mockResolvedValueOnce("---\n") - mockFs.writeFile.mockResolvedValueOnce(undefined as any) - - const result = await installer.installItem(mockModeItem, { target: "project" }) - - expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes")) - expect(mockFs.writeFile).toHaveBeenCalled() - - // Verify the written content contains the new mode - const writtenContent = mockFs.writeFile.mock.calls[0][1] as string - const writtenData = yaml.parse(writtenContent) - expect(writtenData.customModes).toHaveLength(1) - expect(writtenData.customModes[0].slug).toBe("test") - }) - - it("should handle .roomodes file without customModes property", async () => { - // File has valid YAML but no customModes property - const contentWithoutCustomModes = yaml.stringify({ someOtherProperty: "value" }) - mockFs.readFile.mockResolvedValueOnce(contentWithoutCustomModes) - mockFs.writeFile.mockResolvedValueOnce(undefined as any) - - const result = await installer.installItem(mockModeItem, { target: "project" }) - - expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes")) - expect(mockFs.writeFile).toHaveBeenCalled() - - // Verify the written content contains the new mode and preserves other properties - const writtenContent = mockFs.writeFile.mock.calls[0][1] as string - const writtenData = yaml.parse(writtenContent) - expect(writtenData.customModes).toHaveLength(1) - expect(writtenData.customModes[0].slug).toBe("test") - expect(writtenData.someOtherProperty).toBe("value") + await expect(installer.installItem(arrayContentMode, { target: "project" })).rejects.toThrow( + "Mode content should not be an array", + ) }) - it("should throw error when .roomodes contains invalid YAML", async () => { - const invalidYaml = "invalid: yaml: content: {" - - mockFs.readFile.mockResolvedValueOnce(invalidYaml) + it("should throw error for missing content", async () => { + const noContentMode: MarketplaceItem = { + ...mockModeItem, + content: undefined as any, + } - await expect(installer.installItem(mockModeItem, { target: "project" })).rejects.toThrow( - "Cannot install mode: The .roomodes file contains invalid YAML", + await expect(installer.installItem(noContentMode, { target: "project" })).rejects.toThrow( + "Mode item missing content", ) - - // Should NOT write to file - expect(mockFs.writeFile).not.toHaveBeenCalled() }) - it("should replace existing mode with same slug", async () => { - const existingContent = yaml.stringify({ - customModes: [{ slug: "test", name: "Old Test Mode", roleDefinition: "Old role", groups: [] }], - }) + it("should work without CustomModesManager (fallback)", async () => { + const installerWithoutManager = new SimpleInstaller(mockContext) - mockFs.readFile.mockResolvedValueOnce(existingContent) + // Mock file not found + const notFoundError = new Error("File not found") as any + notFoundError.code = "ENOENT" + mockFs.readFile.mockRejectedValueOnce(notFoundError) mockFs.writeFile.mockResolvedValueOnce(undefined as any) - await installer.installItem(mockModeItem, { target: "project" }) + const result = await installerWithoutManager.installItem(mockModeItem, { target: "project" }) - const writtenContent = mockFs.writeFile.mock.calls[0][1] as string - const writtenData = yaml.parse(writtenContent) - - // Should contain only one mode with updated content - expect(writtenData.customModes).toHaveLength(1) - expect(writtenData.customModes[0].slug).toBe("test") - expect(writtenData.customModes[0].name).toBe("Test Mode") // New name + expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes")) + expect(mockFs.writeFile).toHaveBeenCalled() }) }) @@ -254,75 +217,133 @@ describe("SimpleInstaller", () => { }), } - it("should throw error when .roomodes contains invalid YAML during removal", async () => { - const invalidYaml = "invalid: yaml: content: {" + it("should use CustomModesManager to delete mode and clean up rules folder", async () => { + // Mock that the mode exists with project source + vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([ + { slug: "test", name: "Test Mode", source: "project" } as any, + ]) + + await installer.removeItem(mockModeItem, { target: "project" }) - mockFs.readFile.mockResolvedValueOnce(invalidYaml) + // Should call deleteCustomMode with fromMarketplace flag set to true + expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true) + // The rules folder deletion is now handled by CustomModesManager, not SimpleInstaller + expect(fileExistsAtPath).not.toHaveBeenCalled() + expect(mockFs.rm).not.toHaveBeenCalled() + }) - await expect(installer.removeItem(mockModeItem, { target: "project" })).rejects.toThrow( - "Cannot remove mode: The .roomodes file contains invalid YAML", - ) + it("should handle global mode removal with rules cleanup", async () => { + // Mock that the mode exists with global source + vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([ + { slug: "test", name: "Test Mode", source: "global" } as any, + ]) - // Should NOT write to file - expect(mockFs.writeFile).not.toHaveBeenCalled() + await installer.removeItem(mockModeItem, { target: "global" }) + + // Should call deleteCustomMode with fromMarketplace flag set to true + expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true) + // The rules folder deletion is now handled by CustomModesManager, not SimpleInstaller + expect(fileExistsAtPath).not.toHaveBeenCalled() + expect(mockFs.rm).not.toHaveBeenCalled() }) - it("should do nothing when file does not exist", async () => { - const notFoundError = new Error("File not found") as any - notFoundError.code = "ENOENT" - mockFs.readFile.mockRejectedValueOnce(notFoundError) + it("should handle case when rules folder does not exist", async () => { + // Mock that the mode exists + vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([ + { slug: "test", name: "Test Mode", source: "project" } as any, + ]) - // Should not throw await installer.removeItem(mockModeItem, { target: "project" }) - expect(mockFs.writeFile).not.toHaveBeenCalled() + // Should call deleteCustomMode with fromMarketplace flag set to true + expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true) + // The rules folder deletion is now handled by CustomModesManager, not SimpleInstaller + expect(fileExistsAtPath).not.toHaveBeenCalled() + expect(mockFs.rm).not.toHaveBeenCalled() }) - it("should handle empty .roomodes file during removal", async () => { - // Empty file content - mockFs.readFile.mockResolvedValueOnce("") - mockFs.writeFile.mockResolvedValueOnce(undefined as any) + it("should throw error if deleteCustomMode fails", async () => { + // Mock that the mode exists + vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([ + { slug: "test", name: "Test Mode", source: "project" } as any, + ]) + // Mock that deleteCustomMode fails + mockCustomModesManager.deleteCustomMode = vi.fn().mockRejectedValueOnce(new Error("Permission denied")) - // Should not throw - await installer.removeItem(mockModeItem, { target: "project" }) + // Should throw the error from deleteCustomMode + await expect(installer.removeItem(mockModeItem, { target: "project" })).rejects.toThrow("Permission denied") - // Should write back a valid structure with empty customModes - expect(mockFs.writeFile).toHaveBeenCalled() - const writtenContent = mockFs.writeFile.mock.calls[0][1] as string - const writtenData = yaml.parse(writtenContent) - expect(writtenData.customModes).toEqual([]) + expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true) }) - it("should handle .roomodes file with null content during removal", async () => { - // File exists but yaml.parse returns null - mockFs.readFile.mockResolvedValueOnce("---\n") - mockFs.writeFile.mockResolvedValueOnce(undefined as any) + it("should handle mode not found in custom modes list", async () => { + // Mock that the mode doesn't exist in the list + vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([]) - // Should not throw await installer.removeItem(mockModeItem, { target: "project" }) - // Should write back a valid structure with empty customModes - expect(mockFs.writeFile).toHaveBeenCalled() - const writtenContent = mockFs.writeFile.mock.calls[0][1] as string - const writtenData = yaml.parse(writtenContent) - expect(writtenData.customModes).toEqual([]) + expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true) + // Should not attempt to delete rules folder + expect(fileExistsAtPath).not.toHaveBeenCalled() + expect(mockFs.rm).not.toHaveBeenCalled() }) - it("should handle .roomodes file without customModes property during removal", async () => { - // File has valid YAML but no customModes property - const contentWithoutCustomModes = yaml.stringify({ someOtherProperty: "value" }) - mockFs.readFile.mockResolvedValueOnce(contentWithoutCustomModes) - mockFs.writeFile.mockResolvedValueOnce(undefined as any) + it("should throw error when mode content is invalid YAML", async () => { + const invalidModeItem: MarketplaceItem = { + ...mockModeItem, + content: "invalid: yaml: content: {", + } - // Should not throw - await installer.removeItem(mockModeItem, { target: "project" }) + await expect(installer.removeItem(invalidModeItem, { target: "project" })).rejects.toThrow( + "Invalid mode content: unable to parse YAML", + ) - // Should write back the file with the same content (no modes to remove) - expect(mockFs.writeFile).toHaveBeenCalled() - const writtenContent = mockFs.writeFile.mock.calls[0][1] as string - const writtenData = yaml.parse(writtenContent) - expect(writtenData.customModes).toEqual([]) - expect(writtenData.someOtherProperty).toBe("value") + expect(mockCustomModesManager.deleteCustomMode).not.toHaveBeenCalled() + }) + + it("should throw error when mode has no slug", async () => { + const noSlugModeItem: MarketplaceItem = { + ...mockModeItem, + content: yaml.stringify({ + name: "Test Mode", + roleDefinition: "Test role", + groups: ["read"], + }), + } + + await expect(installer.removeItem(noSlugModeItem, { target: "project" })).rejects.toThrow( + "Mode missing slug identifier", + ) + + expect(mockCustomModesManager.deleteCustomMode).not.toHaveBeenCalled() + }) + + it("should handle array content format", async () => { + const arrayContentItem: MarketplaceItem = { + ...mockModeItem, + content: [ + { + content: yaml.stringify({ + slug: "test-array", + name: "Test Array Mode", + roleDefinition: "Test role", + groups: ["read"], + }), + }, + ] as any, + } + + await installer.removeItem(arrayContentItem, { target: "project" }) + + expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test-array", true) + }) + + it("should throw error when CustomModesManager is not available", async () => { + const installerWithoutManager = new SimpleInstaller(mockContext) + + await expect(installerWithoutManager.removeItem(mockModeItem, { target: "project" })).rejects.toThrow( + "CustomModesManager is not available", + ) }) }) }) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index bfcf70deb8..000762e317 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -102,6 +102,7 @@ export interface ExtensionMessage { | "indexCleared" | "codebaseIndexConfig" | "marketplaceInstallResult" + | "marketplaceRemoveResult" | "marketplaceData" | "shareTaskSuccess" | "codeIndexSettingsSaved" diff --git a/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx b/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx index defc138581..13c515ea63 100644 --- a/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx +++ b/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from "react" +import React, { useMemo, useState, useEffect } from "react" import { MarketplaceItem, TelemetryEventName } from "@roo-code/types" import { vscode } from "@/utils/vscode" import { telemetryClient } from "@/utils/TelemetryClient" @@ -10,6 +10,16 @@ import { Button } from "@/components/ui/button" import { StandardTooltip } from "@/components/ui" import { MarketplaceInstallModal } from "./MarketplaceInstallModal" import { useExtensionState } from "@/context/ExtensionStateContext" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui" interface ItemInstalledMetadata { type: string @@ -29,6 +39,30 @@ export const MarketplaceItemCard: React.FC = ({ item, const { t } = useAppTranslation() const { cwd } = useExtensionState() const [showInstallModal, setShowInstallModal] = useState(false) + const [showRemoveConfirm, setShowRemoveConfirm] = useState(false) + const [removeTarget, setRemoveTarget] = useState<"project" | "global">("project") + const [removeError, setRemoveError] = useState(null) + + // Listen for removal result messages + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + const message = event.data + if (message.type === "marketplaceRemoveResult" && message.slug === item.id) { + if (message.success) { + // Removal succeeded - refresh marketplace data + vscode.postMessage({ + type: "fetchMarketplaceData", + }) + } else { + // Removal failed - show error message to user + setRemoveError(message.error || t("marketplace:items.unknownError")) + } + } + } + + window.addEventListener("message", handleMessage) + return () => window.removeEventListener("message", handleMessage) + }, [item.id, t]) const typeLabel = useMemo(() => { const labels: Partial> = { @@ -92,16 +126,8 @@ export const MarketplaceItemCard: React.FC = ({ item, onClick={() => { // Determine which installation to remove (prefer project over global) const target = isInstalledInProject ? "project" : "global" - vscode.postMessage({ - type: "removeInstalledMarketplaceItem", - mpItem: item, - mpInstallOptions: { target }, - }) - - // Request fresh marketplace data to update installed status - vscode.postMessage({ - type: "fetchMarketplaceData", - }) + setRemoveTarget(target) + setShowRemoveConfirm(true) }}> {t("marketplace:items.card.remove")} @@ -116,6 +142,13 @@ export const MarketplaceItemCard: React.FC = ({ item, {t("marketplace:items.card.install")} )} + + {/* Error message display */} + {removeError && ( +
+ {t("marketplace:items.removeFailed", { error: removeError })} +
+ )} @@ -169,6 +202,49 @@ export const MarketplaceItemCard: React.FC = ({ item, onClose={() => setShowInstallModal(false)} hasWorkspace={!!cwd} /> + + {/* Remove Confirmation Dialog */} + + + + + {item.type === "mode" + ? t("marketplace:removeConfirm.mode.title") + : t("marketplace:removeConfirm.mcp.title")} + + + {item.type === "mode" ? ( + <> + {t("marketplace:removeConfirm.mode.message", { modeName: item.name })} +
+ {t("marketplace:removeConfirm.mode.rulesWarning")} +
+ + ) : ( + t("marketplace:removeConfirm.mcp.message", { mcpName: item.name }) + )} +
+
+ + {t("marketplace:removeConfirm.cancel")} + { + // Clear any previous error + setRemoveError(null) + + vscode.postMessage({ + type: "removeInstalledMarketplaceItem", + mpItem: item, + mpInstallOptions: { target: removeTarget }, + }) + + setShowRemoveConfirm(false) + }}> + {t("marketplace:removeConfirm.confirm")} + + +
+
) } diff --git a/webview-ui/src/i18n/locales/ca/marketplace.json b/webview-ui/src/i18n/locales/ca/marketplace.json index 5af3d1eee6..1c4f1f805c 100644 --- a/webview-ui/src/i18n/locales/ca/marketplace.json +++ b/webview-ui/src/i18n/locales/ca/marketplace.json @@ -70,8 +70,12 @@ "installed": "Instal·lat", "removeProjectTooltip": "Eliminar del projecte actual", "removeGlobalTooltip": "Eliminar de la configuració global", - "actionsMenuLabel": "Més accions" - } + "actionsMenuLabel": "Més accions", + "official": "Oficial", + "verified": "Verificat" + }, + "removeFailed": "No s'ha pogut eliminar l'element: {{error}}", + "unknownError": "S'ha produït un error desconegut" }, "install": { "title": "Instal·lar {{name}}", @@ -125,6 +129,19 @@ "maxSources": "Màxim de {{max}} fonts permeses" } }, + "removeConfirm": { + "mode": { + "title": "Eliminar el mode", + "message": "Estàs segur que vols eliminar el mode \"{{modeName}}\"?", + "rulesWarning": "Això també eliminarà qualsevol fitxer de regles associat per a aquest mode." + }, + "mcp": { + "title": "Eliminar el servidor MCP", + "message": "Estàs segur que vols eliminar el servidor MCP \"{{mcpName}}\"?" + }, + "cancel": "Cancel·lar", + "confirm": "Eliminar" + }, "footer": { "issueText": "Has trobat un problema amb un element del marketplace o tens suggeriments per a nous elements? <0>Obre una incidència de GitHub per fer-nos-ho saber!" } diff --git a/webview-ui/src/i18n/locales/de/marketplace.json b/webview-ui/src/i18n/locales/de/marketplace.json index 4632ce5fea..be83e6d6d3 100644 --- a/webview-ui/src/i18n/locales/de/marketplace.json +++ b/webview-ui/src/i18n/locales/de/marketplace.json @@ -70,8 +70,12 @@ "installed": "Installiert", "removeProjectTooltip": "Aus aktuellem Projekt entfernen", "removeGlobalTooltip": "Aus globaler Konfiguration entfernen", - "actionsMenuLabel": "Weitere Aktionen" - } + "actionsMenuLabel": "Weitere Aktionen", + "official": "Offiziell", + "verified": "Verifiziert" + }, + "removeFailed": "Element konnte nicht entfernt werden: {{error}}", + "unknownError": "Unbekannter Fehler aufgetreten" }, "install": { "title": "{{name}} installieren", @@ -125,6 +129,19 @@ "maxSources": "Maximal {{max}} Quellen erlaubt" } }, + "removeConfirm": { + "mode": { + "title": "Modus entfernen", + "message": "Bist du sicher, dass du den Modus „{{modeName}}“ entfernen möchtest?", + "rulesWarning": "Dadurch werden auch alle zugehörigen Regeldateien für diesen Modus entfernt." + }, + "mcp": { + "title": "MCP-Server entfernen", + "message": "Bist du sicher, dass du den MCP-Server „{{mcpName}}“ entfernen möchtest?" + }, + "cancel": "Abbrechen", + "confirm": "Entfernen" + }, "footer": { "issueText": "Problem mit einem Marketplace-Element gefunden oder Vorschläge für neue? <0>Öffne ein GitHub-Issue, um es uns mitzuteilen!" } diff --git a/webview-ui/src/i18n/locales/en/marketplace.json b/webview-ui/src/i18n/locales/en/marketplace.json index c8c9a2f11a..7c20060598 100644 --- a/webview-ui/src/i18n/locales/en/marketplace.json +++ b/webview-ui/src/i18n/locales/en/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "Remove from current project", "removeGlobalTooltip": "Remove from global configuration", "actionsMenuLabel": "More actions" - } + }, + "removeFailed": "Failed to remove item: {{error}}", + "unknownError": "Unknown error occurred" }, "install": { "title": "Install {{name}}", @@ -125,6 +127,19 @@ "maxSources": "Maximum of {{max}} sources allowed" } }, + "removeConfirm": { + "mode": { + "title": "Remove Mode", + "message": "Are you sure you want to remove the mode \"{{modeName}}\"?", + "rulesWarning": "This will also remove any associated rules files for this mode." + }, + "mcp": { + "title": "Remove MCP Server", + "message": "Are you sure you want to remove the MCP server \"{{mcpName}}\"?" + }, + "cancel": "Cancel", + "confirm": "Remove" + }, "footer": { "issueText": "Found a problem with a marketplace item or have suggestions for new ones? <0>Open a GitHub issue to let us know!" } diff --git a/webview-ui/src/i18n/locales/es/marketplace.json b/webview-ui/src/i18n/locales/es/marketplace.json index 918d10ef8d..39a45407ae 100644 --- a/webview-ui/src/i18n/locales/es/marketplace.json +++ b/webview-ui/src/i18n/locales/es/marketplace.json @@ -70,8 +70,12 @@ "installed": "Instalado", "removeProjectTooltip": "Eliminar del proyecto actual", "removeGlobalTooltip": "Eliminar de la configuración global", - "actionsMenuLabel": "Más acciones" - } + "actionsMenuLabel": "Más acciones", + "official": "Oficial", + "verified": "Verificado" + }, + "removeFailed": "No se pudo eliminar el elemento: {{error}}", + "unknownError": "Ocurrió un error desconocido" }, "install": { "title": "Instalar {{name}}", @@ -125,6 +129,19 @@ "maxSources": "Máximo de {{max}} fuentes permitidas" } }, + "removeConfirm": { + "mode": { + "title": "Eliminar modo", + "message": "¿Estás seguro de que quieres eliminar el modo \"{{modeName}}\"?", + "rulesWarning": "Esto también eliminará cualquier archivo de reglas asociado para este modo." + }, + "mcp": { + "title": "Eliminar servidor MCP", + "message": "¿Estás seguro de que quieres eliminar el servidor MCP \"{{mcpName}}\"?" + }, + "cancel": "Cancelar", + "confirm": "Eliminar" + }, "footer": { "issueText": "¿Encontraste un problema con un elemento del marketplace o tienes sugerencias para nuevos? ¡<0>Abre un issue en GitHub para hacérnoslo saber!" } diff --git a/webview-ui/src/i18n/locales/fr/marketplace.json b/webview-ui/src/i18n/locales/fr/marketplace.json index 942d06aa49..05a17150f7 100644 --- a/webview-ui/src/i18n/locales/fr/marketplace.json +++ b/webview-ui/src/i18n/locales/fr/marketplace.json @@ -27,7 +27,7 @@ }, "tags": { "label": "Filtrer par étiquettes :", - "clear": "Effacer les étiquettes", + "clear": "Cliquez pour supprimer '{{count}}' des filtres", "placeholder": "Tapez pour rechercher et sélectionner des étiquettes...", "noResults": "Aucune étiquette correspondante trouvée", "selected": "Affichage des éléments avec l'une des étiquettes sélectionnées", @@ -70,8 +70,12 @@ "installed": "Installé", "removeProjectTooltip": "Supprimer du projet actuel", "removeGlobalTooltip": "Supprimer de la configuration globale", - "actionsMenuLabel": "Plus d'actions" - } + "actionsMenuLabel": "Plus d'actions", + "official": "Officiel", + "verified": "Vérifié" + }, + "removeFailed": "Échec de la suppression de l'élément : {{error}}", + "unknownError": "Une erreur inconnue est survenue" }, "install": { "title": "Installer {{name}}", @@ -125,6 +129,19 @@ "maxSources": "Maximum de {{max}} sources autorisées" } }, + "removeConfirm": { + "mode": { + "title": "Supprimer le mode", + "message": "Êtes-vous sûr de vouloir supprimer le mode « {{modeName}} » ?", + "rulesWarning": "Cela supprimera également tous les fichiers de règles associés à ce mode." + }, + "mcp": { + "title": "Supprimer le serveur MCP", + "message": "Êtes-vous sûr de vouloir supprimer le serveur MCP « {{mcpName}} » ?" + }, + "cancel": "Annuler", + "confirm": "Supprimer" + }, "footer": { "issueText": "Vous avez trouvé un problème avec un élément du marketplace ou avez des suggestions pour de nouveaux éléments ? <0>Ouvrez une issue GitHub pour nous le faire savoir !" } diff --git a/webview-ui/src/i18n/locales/hi/marketplace.json b/webview-ui/src/i18n/locales/hi/marketplace.json index b9c9857b54..07d5be7eb3 100644 --- a/webview-ui/src/i18n/locales/hi/marketplace.json +++ b/webview-ui/src/i18n/locales/hi/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "वर्तमान प्रोजेक्ट से हटाएं", "removeGlobalTooltip": "ग्लोबल कॉन्फ़िगरेशन से हटाएं", "actionsMenuLabel": "अधिक क्रियाएं" - } + }, + "removeFailed": "आइटम हटाने में विफल: {{error}}", + "unknownError": "अज्ञात त्रुटि हुई" }, "install": { "title": "{{name}} इंस्टॉल करें", @@ -125,6 +127,19 @@ "maxSources": "अधिकतम {{max}} स्रोतों की अनुमति है" } }, + "removeConfirm": { + "mode": { + "title": "मोड हटाएं", + "message": "क्या आप वाकई मोड \"{{modeName}}\" को हटाना चाहते हैं?", + "rulesWarning": "यह इस मोड के लिए किसी भी संबंधित नियम फ़ाइल को भी हटा देगा।" + }, + "mcp": { + "title": "MCP सर्वर हटाएं", + "message": "क्या आप वाकई MCP सर्वर \"{{mcpName}}\" को हटाना चाहते हैं?" + }, + "cancel": "रद्द करें", + "confirm": "हटाएं" + }, "footer": { "issueText": "कोई marketplace आइटम के साथ समस्या है या नए आइटम के लिए सुझाव हैं? <0>GitHub issue खोलें हमें बताने के लिए!" } diff --git a/webview-ui/src/i18n/locales/id/marketplace.json b/webview-ui/src/i18n/locales/id/marketplace.json index 153747fdf8..fc663101d3 100644 --- a/webview-ui/src/i18n/locales/id/marketplace.json +++ b/webview-ui/src/i18n/locales/id/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "Hapus dari proyek saat ini", "removeGlobalTooltip": "Hapus dari konfigurasi global", "actionsMenuLabel": "Aksi lainnya" - } + }, + "removeFailed": "Gagal menghapus item: {{error}}", + "unknownError": "Terjadi kesalahan yang tidak diketahui" }, "install": { "title": "Instal {{name}}", @@ -125,6 +127,19 @@ "maxSources": "Maksimal {{max}} sumber diizinkan" } }, + "removeConfirm": { + "mode": { + "title": "Hapus Mode", + "message": "Apakah Anda yakin ingin menghapus mode \"{{modeName}}\"?", + "rulesWarning": "Ini juga akan menghapus semua file aturan terkait untuk mode ini." + }, + "mcp": { + "title": "Hapus Server MCP", + "message": "Apakah Anda yakin ingin menghapus server MCP \"{{mcpName}}\"?" + }, + "cancel": "Batal", + "confirm": "Hapus" + }, "footer": { "issueText": "Menemukan masalah dengan item marketplace atau punya saran untuk yang baru? <0>Buka GitHub issue untuk memberi tahu kami!" } diff --git a/webview-ui/src/i18n/locales/it/marketplace.json b/webview-ui/src/i18n/locales/it/marketplace.json index ea9845277b..ab4aa459fa 100644 --- a/webview-ui/src/i18n/locales/it/marketplace.json +++ b/webview-ui/src/i18n/locales/it/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "Rimuovi dal progetto corrente", "removeGlobalTooltip": "Rimuovi dalla configurazione globale", "actionsMenuLabel": "Altre azioni" - } + }, + "removeFailed": "Impossibile rimuovere l'elemento: {{error}}", + "unknownError": "Si è verificato un errore sconosciuto" }, "install": { "title": "Installa {{name}}", @@ -125,6 +127,19 @@ "maxSources": "Massimo {{max}} fonti consentite" } }, + "removeConfirm": { + "mode": { + "title": "Rimuovi modalità", + "message": "Sei sicuro di voler rimuovere la modalità \"{{modeName}}\"?", + "rulesWarning": "Questo rimuoverà anche eventuali file di regole associati per questa modalità." + }, + "mcp": { + "title": "Rimuovi server MCP", + "message": "Sei sicuro di voler rimuovere il server MCP \"{{mcpName}}\"?" + }, + "cancel": "Annulla", + "confirm": "Rimuovi" + }, "footer": { "issueText": "Hai trovato un problema con un elemento del marketplace o hai suggerimenti per nuovi elementi? <0>Apri un issue GitHub per farcelo sapere!" } diff --git a/webview-ui/src/i18n/locales/ja/marketplace.json b/webview-ui/src/i18n/locales/ja/marketplace.json index d3343eee22..72388d5224 100644 --- a/webview-ui/src/i18n/locales/ja/marketplace.json +++ b/webview-ui/src/i18n/locales/ja/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "現在のプロジェクトから削除", "removeGlobalTooltip": "グローバル設定から削除", "actionsMenuLabel": "その他のアクション" - } + }, + "removeFailed": "アイテムの削除に失敗しました: {{error}}", + "unknownError": "不明なエラーが発生しました" }, "install": { "title": "{{name}}をインストール", @@ -125,6 +127,19 @@ "maxSources": "最大{{max}}個のソースが許可されています" } }, + "removeConfirm": { + "mode": { + "title": "モードを削除", + "message": "モード「{{modeName}}」を本当に削除しますか?", + "rulesWarning": "これにより、このモードに関連するルールファイルも削除されます。" + }, + "mcp": { + "title": "MCPサーバーを削除", + "message": "MCPサーバー「{{mcpName}}」を本当に削除しますか?" + }, + "cancel": "キャンセル", + "confirm": "削除" + }, "footer": { "issueText": "Marketplaceアイテムで問題を見つけた、または新しいアイテムの提案がありますか?<0>GitHub issueを開いてお知らせください!" } diff --git a/webview-ui/src/i18n/locales/ko/marketplace.json b/webview-ui/src/i18n/locales/ko/marketplace.json index b4a0b64980..54ec83863f 100644 --- a/webview-ui/src/i18n/locales/ko/marketplace.json +++ b/webview-ui/src/i18n/locales/ko/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "현재 프로젝트에서 삭제", "removeGlobalTooltip": "전역 설정에서 삭제", "actionsMenuLabel": "추가 작업" - } + }, + "removeFailed": "항목을 제거하지 못했습니다: {{error}}", + "unknownError": "알 수 없는 오류가 발생했습니다" }, "install": { "title": "{{name}} 설치", @@ -125,6 +127,19 @@ "maxSources": "최대 {{max}}개의 소스가 허용됩니다" } }, + "removeConfirm": { + "mode": { + "title": "모드 제거", + "message": "정말로 '{{modeName}}' 모드를 제거하시겠습니까?", + "rulesWarning": "이렇게 하면 이 모드와 관련된 모든 규칙 파일도 제거됩니다." + }, + "mcp": { + "title": "MCP 서버 제거", + "message": "정말로 '{{mcpName}}' MCP 서버를 제거하시겠습니까?" + }, + "cancel": "취소", + "confirm": "제거" + }, "footer": { "issueText": "Marketplace 아이템에 문제가 있거나 새로운 아이템에 대한 제안이 있나요? <0>GitHub issue를 열어서 알려주세요!" } diff --git a/webview-ui/src/i18n/locales/nl/marketplace.json b/webview-ui/src/i18n/locales/nl/marketplace.json index c2fe7cd610..b378375397 100644 --- a/webview-ui/src/i18n/locales/nl/marketplace.json +++ b/webview-ui/src/i18n/locales/nl/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "Verwijderen uit huidig project", "removeGlobalTooltip": "Verwijderen uit globale configuratie", "actionsMenuLabel": "Meer acties" - } + }, + "removeFailed": "Item verwijderen mislukt: {{error}}", + "unknownError": "Onbekende fout opgetreden" }, "install": { "title": "{{name}} installeren", @@ -125,6 +127,19 @@ "maxSources": "Maximaal {{max}} bronnen toegestaan" } }, + "removeConfirm": { + "mode": { + "title": "Modus verwijderen", + "message": "Weet je zeker dat je de modus \"{{modeName}}\" wilt verwijderen?", + "rulesWarning": "Dit verwijdert ook alle bijbehorende regelbestanden voor deze modus." + }, + "mcp": { + "title": "MCP-server verwijderen", + "message": "Weet je zeker dat je de MCP-server \"{{mcpName}}\" wilt verwijderen?" + }, + "cancel": "Annuleren", + "confirm": "Verwijderen" + }, "footer": { "issueText": "Heb je een probleem gevonden met een marketplace-item of heb je suggesties voor nieuwe items? <0>Open een GitHub issue om het ons te laten weten!" } diff --git a/webview-ui/src/i18n/locales/pl/marketplace.json b/webview-ui/src/i18n/locales/pl/marketplace.json index 9acb67ed03..44bdd290d3 100644 --- a/webview-ui/src/i18n/locales/pl/marketplace.json +++ b/webview-ui/src/i18n/locales/pl/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "Usuń z bieżącego projektu", "removeGlobalTooltip": "Usuń z konfiguracji globalnej", "actionsMenuLabel": "Więcej akcji" - } + }, + "removeFailed": "Nie udało się usunąć elementu: {{error}}", + "unknownError": "Wystąpił nieznany błąd" }, "install": { "title": "Zainstaluj {{name}}", @@ -125,6 +127,19 @@ "maxSources": "Maksymalnie {{max}} źródeł dozwolonych" } }, + "removeConfirm": { + "mode": { + "title": "Usuń tryb", + "message": "Czy na pewno chcesz usunąć tryb „{{modeName}}”?", + "rulesWarning": "Spowoduje to również usunięcie wszelkich powiązanych plików reguł dla tego trybu." + }, + "mcp": { + "title": "Usuń serwer MCP", + "message": "Czy na pewno chcesz usunąć serwer MCP „{{mcpName}}”?" + }, + "cancel": "Anuluj", + "confirm": "Usuń" + }, "footer": { "issueText": "Znalazłeś problem z elementem marketplace lub masz sugestie dotyczące nowych elementów? <0>Otwórz issue na GitHub, aby nam o tym powiedzieć!" } diff --git a/webview-ui/src/i18n/locales/pt-BR/marketplace.json b/webview-ui/src/i18n/locales/pt-BR/marketplace.json index b634291eeb..5f472d4e80 100644 --- a/webview-ui/src/i18n/locales/pt-BR/marketplace.json +++ b/webview-ui/src/i18n/locales/pt-BR/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "Remover do projeto atual", "removeGlobalTooltip": "Remover da configuração global", "actionsMenuLabel": "Mais ações" - } + }, + "removeFailed": "Falha ao remover o item: {{error}}", + "unknownError": "Ocorreu um erro desconhecido" }, "install": { "title": "Instalar {{name}}", @@ -125,6 +127,19 @@ "maxSources": "Máximo de {{max}} fontes permitidas" } }, + "removeConfirm": { + "mode": { + "title": "Remover Modo", + "message": "Tem certeza de que deseja remover o modo \"{{modeName}}\"?", + "rulesWarning": "Isso também removerá todos os arquivos de regras associados a este modo." + }, + "mcp": { + "title": "Remover Servidor MCP", + "message": "Tem certeza de que deseja remover o servidor MCP \"{{mcpName}}\"?" + }, + "cancel": "Cancelar", + "confirm": "Remover" + }, "footer": { "issueText": "Encontrou um problema com um item do marketplace ou tem sugestões para novos itens? <0>Abra um issue no GitHub para nos avisar!" } diff --git a/webview-ui/src/i18n/locales/ru/marketplace.json b/webview-ui/src/i18n/locales/ru/marketplace.json index 7a33014cf5..f32d855406 100644 --- a/webview-ui/src/i18n/locales/ru/marketplace.json +++ b/webview-ui/src/i18n/locales/ru/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "Удалить из текущего проекта", "removeGlobalTooltip": "Удалить из глобальной конфигурации", "actionsMenuLabel": "Дополнительные действия" - } + }, + "removeFailed": "Не удалось удалить элемент: {{error}}", + "unknownError": "Произошла неизвестная ошибка" }, "install": { "title": "Установить {{name}}", @@ -125,6 +127,19 @@ "maxSources": "Максимум {{max}} источников разрешено" } }, + "removeConfirm": { + "mode": { + "title": "Удалить режим", + "message": "Вы уверены, что хотите удалить режим «{{modeName}}»?", + "rulesWarning": "Это также удалит все связанные файлы правил для этого режима." + }, + "mcp": { + "title": "Удалить сервер MCP", + "message": "Вы уверены, что хотите удалить сервер MCP «{{mcpName}}»?" + }, + "cancel": "Отмена", + "confirm": "Удалить" + }, "footer": { "issueText": "Нашли проблему с элементом marketplace или есть предложения для новых элементов? <0>Откройте issue на GitHub, чтобы сообщить нам!" } diff --git a/webview-ui/src/i18n/locales/tr/marketplace.json b/webview-ui/src/i18n/locales/tr/marketplace.json index 6c3f857a5f..279ae2c38a 100644 --- a/webview-ui/src/i18n/locales/tr/marketplace.json +++ b/webview-ui/src/i18n/locales/tr/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "Mevcut projeden kaldır", "removeGlobalTooltip": "Global yapılandırmadan kaldır", "actionsMenuLabel": "Daha fazla eylem" - } + }, + "removeFailed": "Öğe kaldırılamadı: {{error}}", + "unknownError": "Bilinmeyen bir hata oluştu" }, "install": { "title": "{{name}} Yükle", @@ -125,6 +127,19 @@ "maxSources": "Maksimum {{max}} kaynak izin verilir" } }, + "removeConfirm": { + "mode": { + "title": "Modu Kaldır", + "message": "\"{{modeName}}\" modunu kaldırmak istediğinizden emin misiniz?", + "rulesWarning": "Bu, bu mod için ilişkili tüm kural dosyalarını da kaldıracaktır." + }, + "mcp": { + "title": "MCP Sunucusunu Kaldır", + "message": "\"{{mcpName}}\" MCP sunucusunu kaldırmak istediğinizden emin misiniz?" + }, + "cancel": "İptal", + "confirm": "Kaldır" + }, "footer": { "issueText": "Bir marketplace öğesi ile ilgili sorun bulduğun veya yeni öğeler için önerilerin var mı? <0>GitHub'da issue aç ve bize bildir!" } diff --git a/webview-ui/src/i18n/locales/vi/marketplace.json b/webview-ui/src/i18n/locales/vi/marketplace.json index d84f2d0e1f..fcb5beefc4 100644 --- a/webview-ui/src/i18n/locales/vi/marketplace.json +++ b/webview-ui/src/i18n/locales/vi/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "Xóa khỏi dự án hiện tại", "removeGlobalTooltip": "Xóa khỏi cấu hình toàn cục", "actionsMenuLabel": "Thêm hành động" - } + }, + "removeFailed": "Không thể xóa mục: {{error}}", + "unknownError": "Đã xảy ra lỗi không xác định" }, "install": { "title": "Cài đặt {{name}}", @@ -125,6 +127,19 @@ "maxSources": "Tối đa {{max}} nguồn được phép" } }, + "removeConfirm": { + "mode": { + "title": "Xóa chế độ", + "message": "Bạn có chắc chắn muốn xóa chế độ \"{{modeName}}\" không?", + "rulesWarning": "Thao tác này cũng sẽ xóa mọi tệp quy tắc được liên kết cho chế độ này." + }, + "mcp": { + "title": "Xóa máy chủ MCP", + "message": "Bạn có chắc chắn muốn xóa máy chủ MCP \"{{mcpName}}\" không?" + }, + "cancel": "Hủy", + "confirm": "Xóa" + }, "footer": { "issueText": "Bạn tìm thấy vấn đề với mục marketplace hoặc có đề xuất cho mục mới? <0>Mở issue GitHub để cho chúng tôi biết!" } diff --git a/webview-ui/src/i18n/locales/zh-CN/marketplace.json b/webview-ui/src/i18n/locales/zh-CN/marketplace.json index ff94aaabcc..598da383d4 100644 --- a/webview-ui/src/i18n/locales/zh-CN/marketplace.json +++ b/webview-ui/src/i18n/locales/zh-CN/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "从当前项目中移除", "removeGlobalTooltip": "从全局配置中移除", "actionsMenuLabel": "更多操作" - } + }, + "removeFailed": "删除失败: {{error}}", + "unknownError": "发生未知错误" }, "install": { "title": "安装 {{name}}", @@ -125,6 +127,19 @@ "maxSources": "最多允许 {{max}} 个源" } }, + "removeConfirm": { + "mode": { + "title": "删除模式", + "message": "您确定要删除\"{{modeName}}\"模式吗?", + "rulesWarning": "这也将删除此模式的任何关联规则文件。" + }, + "mcp": { + "title": "删除 MCP 服务器", + "message": "您确定要删除 MCP 服务器\"{{mcpName}}\"吗?" + }, + "cancel": "取消", + "confirm": "删除" + }, "footer": { "issueText": "发现 marketplace 项目问题或有新项目建议?<0>在 GitHub 开启 issue 告诉我们!" } diff --git a/webview-ui/src/i18n/locales/zh-TW/marketplace.json b/webview-ui/src/i18n/locales/zh-TW/marketplace.json index 3ca00585ca..1ac6ed53c2 100644 --- a/webview-ui/src/i18n/locales/zh-TW/marketplace.json +++ b/webview-ui/src/i18n/locales/zh-TW/marketplace.json @@ -71,7 +71,9 @@ "removeProjectTooltip": "從目前專案中移除", "removeGlobalTooltip": "從全域設定中移除", "actionsMenuLabel": "更多動作" - } + }, + "removeFailed": "移除物品失敗: {{error}}", + "unknownError": "發生未知錯誤" }, "install": { "title": "安裝 {{name}}", @@ -125,6 +127,19 @@ "maxSources": "最多允許 {{max}} 個來源" } }, + "removeConfirm": { + "mode": { + "title": "移除模式", + "message": "您確定要移除「{{modeName}}」模式嗎?", + "rulesWarning": "這也會移除此模式的任何相關規則檔案。" + }, + "mcp": { + "title": "移除 MCP 伺服器", + "message": "您確定要移除 MCP 伺服器「{{mcpName}}」嗎?" + }, + "cancel": "取消", + "confirm": "移除" + }, "footer": { "issueText": "發現 marketplace 項目問題或有新項目建議?<0>在 GitHub 開啟 issue 告訴我們!" }